Skip to content

Commit

Permalink
Merge pull request #430 from MarkMpn/executeas
Browse files Browse the repository at this point in the history
Error Reporting
  • Loading branch information
MarkMpn authored Feb 12, 2024
2 parents 7b0e4ef + a62c677 commit c6c6ae8
Show file tree
Hide file tree
Showing 46 changed files with 2,215 additions and 516 deletions.
62 changes: 44 additions & 18 deletions MarkMpn.Sql4Cds.Controls/ExecutionPlanView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ class Line
private List<Line> _lines;
private int _maxY;
private int _maxBottom;
private ToolTip _toolTip;
private IExecutionPlanNode _tooltipNode;

const int _offset = 32;
private readonly Size _iconSize = new Size(32, 32);
Expand All @@ -38,6 +40,13 @@ public ExecutionPlanView()
{
AutoScroll = true;
DoubleBuffered = true;

_toolTip = new ToolTip
{
InitialDelay = 1000, // Hover the mouse for 1 second before showing the tooltip
AutoPopDelay = 5000, // Show the tooltip for 5 seconds once triggered
ReshowDelay = 1000, // Delay 1 second between different controls
};
}

public bool Executed { get; set; }
Expand Down Expand Up @@ -167,7 +176,7 @@ protected override void OnPaint(PaintEventArgs e)
e.Graphics.DrawImage(image, iconRect);
}

if (Exception?.Node == kvp.Key)
if (Exception?.Node == kvp.Key || (kvp.Key is IExecutionPlanNodeWarning warning && warning.Warning != null))
{
// Show an error icon overlay
e.Graphics.DrawImage(SystemIcons.Error.ToBitmap(), new Rectangle(iconRect.Location, new Size(16, 16)));
Expand Down Expand Up @@ -287,32 +296,49 @@ protected override void OnMouseClick(MouseEventArgs e)
base.OnMouseClick(e);
Focus();

var hit = false;
var hit = HitTest(e.Location);

foreach (var node in _nodeLocations)
{
if (OriginToClient(node.Value).Contains(e.Location))
{
if (Selected != null)
Invalidate(Selected);
if (Selected != null)
Invalidate(Selected);

Selected = node.Key;
Selected = hit;

Invalidate(Selected);
if (Selected != null)
Invalidate(Selected);

OnNodeSelected(EventArgs.Empty);
OnNodeSelected(EventArgs.Empty);
}

hit = true;
break;
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);

var hit = HitTest(e.Location);

if (hit != _tooltipNode)
{
if (Exception != null && Exception.Node == hit)
_toolTip.SetToolTip(this, Exception.Message);
else if (hit is IExecutionPlanNodeWarning warning && warning.Warning != null)
_toolTip.SetToolTip(this, warning.Warning);
else if (hit is IFetchXmlExecutionPlanNode fetch)
_toolTip.SetToolTip(this, fetch.FetchXmlString);
else
_toolTip.SetToolTip(this, null);

_tooltipNode = hit;
}
}

if (!hit && Selected != null)
private IExecutionPlanNode HitTest(Point pt)
{
foreach (var node in _nodeLocations)
{
Invalidate(Selected);
Selected = null;
OnNodeSelected(EventArgs.Empty);
if (OriginToClient(node.Value).Contains(pt))
return node.Key;
}

return null;
}

protected override void OnGotFocus(EventArgs e)
Expand Down
147 changes: 147 additions & 0 deletions MarkMpn.Sql4Cds.Engine.Tests/AdoProviderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1755,6 +1755,47 @@ END CATCH
}
}

[TestMethod]
public void Rethrow()
{
using (var con = new Sql4CdsConnection(_localDataSource))
using (var cmd = con.CreateCommand())
{
cmd.CommandText = @"
BEGIN TRY
SELECT * FROM invalid_table;
END TRY
BEGIN CATCH
SELECT @@ERROR, ERROR_NUMBER(), ERROR_SEVERITY(), ERROR_STATE(), ERROR_PROCEDURE(), ERROR_LINE(), ERROR_MESSAGE();
THROW;
END CATCH";

using (var reader = cmd.ExecuteReader())
{
Assert.IsTrue(reader.Read());
Assert.AreEqual(208, reader.GetInt32(0));
Assert.AreEqual(208, reader.GetInt32(1));
Assert.AreEqual(16, reader.GetInt32(2));
Assert.AreEqual(1, reader.GetInt32(3));
Assert.IsTrue(reader.IsDBNull(4));
Assert.AreEqual(2, reader.GetInt32(5));
Assert.AreEqual("Invalid object name 'invalid_table'", reader.GetString(6));
Assert.IsFalse(reader.Read());

try
{
reader.NextResult();
Assert.Fail();
}
catch (Sql4CdsException ex)
{
Assert.AreEqual(208, ex.Number);
Assert.AreEqual(2, ex.LineNumber);
}
}
}
}

[DataTestMethod]
[DataRow("SELECT FORMATMESSAGE('Signed int %i, %d %i, %d, %+i, %+d, %+i, %+d', 5, -5, 50, -50, -11, -11, 11, 11)", "Signed int 5, -5 50, -50, -11, -11, +11, +11")]
[DataRow("SELECT FORMATMESSAGE('Signed int with up to 3 leading zeros %03i', 5)", "Signed int with up to 3 leading zeros 005")]
Expand All @@ -1779,5 +1820,111 @@ public void FormatMessage(string query, string expected)
Assert.AreEqual(expected, actual);
}
}

[DataTestMethod]
[DataRow("accountid", "uniqueidentifier", 8169)]
[DataRow("employees", "int", 245)]
[DataRow("createdon", "datetime", 241)]
[DataRow("turnover", "money", 235)]
[DataRow("new_decimalprop", "decimal", 8114)]
[DataRow("new_doubleprop", "float", 8114)]
public void ConversionErrors(string column, string type, int expectedError)
{
var tableName = column.StartsWith("new_") ? "new_customentity" : "account";

using (var con = new Sql4CdsConnection(_localDataSource))
using (var cmd = con.CreateCommand())
{
var accountId = Guid.NewGuid();
_context.Data["account"] = new Dictionary<Guid, Entity>
{
[accountId] = new Entity("account", accountId)
{
["accountid"] = accountId,
["employees"] = 10,
["createdon"] = DateTime.Now,
["turnover"] = new Money(1_000_000),
["address1_latitude"] = 45.0D
}
};
_context.Data["new_customentity"] = new Dictionary<Guid, Entity>
{
[accountId] = new Entity("new_customentity", accountId)
{
["new_customentityid"] = accountId,
["new_decimalprop"] = 123.45M,
["new_doubleprop"] = 123.45D
}
};

var queries = new[]
{
// The error should be thrown when filtering by a column in FetchXML
$"SELECT * FROM {tableName} WHERE {column} = 'test'",

// The same error should also be thrown when comparing the values in an expression
$"SELECT CASE WHEN {column} = 'test' then 1 else 0 end FROM {tableName}",

// And also when converting a value directly without a comparison
$"SELECT CAST('test' AS {type})"
};

foreach (var query in queries)
{
// The error should not be thrown when generating an estimated plan
cmd.CommandText = query;
cmd.Prepare();

try
{
cmd.ExecuteNonQuery();
Assert.Fail();
}
catch (Sql4CdsException ex)
{
Assert.AreEqual(expectedError, ex.Number);
}
}
}
}

[TestMethod]
public void MetadataGuidConversionErrors()
{
// Failures converting string to guid should be handled in the same way for metadata queries as for FetchXML
using (var con = new Sql4CdsConnection(_localDataSource))
using (var cmd = con.CreateCommand())
{
cmd.CommandText = "SELECT logicalname FROM metadata.entity WHERE metadataid = 'test'";
cmd.Prepare();

try
{
cmd.ExecuteNonQuery();
Assert.Fail();
}
catch (Sql4CdsException ex)
{
Assert.AreEqual(8169, ex.Number);
}
}
}

[TestMethod]
public void MetadataEnumConversionErrors()
{
// Enum values are presented as simple strings, so there should be no error when converting invalid values
using (var con = new Sql4CdsConnection(_localDataSource))
using (var cmd = con.CreateCommand())
{
cmd.CommandText = "SELECT logicalname FROM metadata.entity WHERE ownershiptype = 'test'";
cmd.Prepare();

using (var reader = cmd.ExecuteReader())
{
Assert.IsFalse(reader.Read());
}
}
}
}
}
54 changes: 53 additions & 1 deletion MarkMpn.Sql4Cds.Engine.Tests/CteTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,21 @@ WITH cte AS (
planBuilder.Build(query, null, out _);
}

[TestMethod]
[ExpectedException(typeof(NotSupportedQueryFragmentException))]
public void MissingAnchorQuery()
{
var planBuilder = new ExecutionPlanBuilder(_localDataSource.Values, this);

var query = @"
WITH cte (x, y) AS (
SELECT x, y FROM cte
)
SELECT * FROM cte";

planBuilder.Build(query, null, out _);
}

[TestMethod]
public void AliasedAnonymousColumn()
{
Expand All @@ -475,7 +490,7 @@ WITH cte (id, fname, lname) AS (
Assert.AreEqual("lname", select.ColumnSet[2].OutputColumn);
Assert.AreEqual("contact.lastname", select.ColumnSet[2].SourceColumn);
var compute = AssertNode<ComputeScalarNode>(select.Source);
Assert.AreEqual("firstname + ''", compute.Columns["Expr1"].ToSql());
Assert.AreEqual("contact.firstname + ''", compute.Columns["Expr1"].ToSql());
var fetch = AssertNode<FetchXmlScan>(compute.Source);
AssertFetchXml(fetch, @"
<fetch>
Expand All @@ -490,6 +505,43 @@ WITH cte (id, fname, lname) AS (
</fetch>");
}

[TestMethod]
public void SelectStarFromValues()
{
var planBuilder = new ExecutionPlanBuilder(_localDataSource.Values, this);

var query = @"
WITH source_data_cte AS (
SELECT *
FROM (VALUES
('M', 'B', '6152-000358'),
('M', 'B', '6152-000530'),
('M', 'B', '6152-000531'),
('B', 'C', '97048786'),
('C', 'D', '35528661'),
('A', 'B', '97680998')
) AS source_data (Column1, Column2, Column3)
)
SELECT *
FROM source_data_cte;";

var plans = planBuilder.Build(query, null, out _);

Assert.AreEqual(1, plans.Length);

var select = AssertNode<SelectNode>(plans[0]);
Assert.AreEqual("Column1", select.ColumnSet[0].OutputColumn);
Assert.AreEqual("source_data_cte.Column1", select.ColumnSet[0].SourceColumn);
Assert.AreEqual("Column2", select.ColumnSet[1].OutputColumn);
Assert.AreEqual("source_data_cte.Column2", select.ColumnSet[1].SourceColumn);
Assert.AreEqual("Column3", select.ColumnSet[2].OutputColumn);
Assert.AreEqual("source_data_cte.Column3", select.ColumnSet[2].SourceColumn);

var constantScan = AssertNode<ConstantScanNode>(select.Source);
Assert.AreEqual("source_data_cte", constantScan.Alias);
}

[TestMethod]
public void SimpleRecursion()
{
Expand Down
Loading

0 comments on commit c6c6ae8

Please sign in to comment.