From f6d883a7cc7a74eaffda6e8059bc91806606e8bc Mon Sep 17 00:00:00 2001 From: Mark Carrington <31017244+MarkMpn@users.noreply.github.com> Date: Tue, 9 Apr 2024 21:02:38 +0100 Subject: [PATCH] Improved error reporting with TDS endpoint --- MarkMpn.Sql4Cds.Engine/Ado/Sql4CdsCommand.cs | 2 +- .../Ado/SqlDataReaderWrapper.cs | 9 +++--- .../ExecutionPlan/SqlNode.cs | 6 ++-- MarkMpn.Sql4Cds.SSMS/DmlExecute.cs | 30 +++++++++++++++---- .../Reflection/ReflectionObjectBase.cs | 11 ++++++- 5 files changed, 41 insertions(+), 17 deletions(-) diff --git a/MarkMpn.Sql4Cds.Engine/Ado/Sql4CdsCommand.cs b/MarkMpn.Sql4Cds.Engine/Ado/Sql4CdsCommand.cs index fb89d8cc..58a07c9a 100644 --- a/MarkMpn.Sql4Cds.Engine/Ado/Sql4CdsCommand.cs +++ b/MarkMpn.Sql4Cds.Engine/Ado/Sql4CdsCommand.cs @@ -293,7 +293,7 @@ protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior) } } - return new SqlDataReaderWrapper(_connection, this, con, cmd, _connection.Database, node, _cts.Token); + return new SqlDataReaderWrapper(con, cmd, behavior, node, _cts.Token); } var options = new CancellationTokenOptionsWrapper(_connection.Options, _cts); diff --git a/MarkMpn.Sql4Cds.Engine/Ado/SqlDataReaderWrapper.cs b/MarkMpn.Sql4Cds.Engine/Ado/SqlDataReaderWrapper.cs index c19eaa6b..e384a34c 100644 --- a/MarkMpn.Sql4Cds.Engine/Ado/SqlDataReaderWrapper.cs +++ b/MarkMpn.Sql4Cds.Engine/Ado/SqlDataReaderWrapper.cs @@ -10,20 +10,18 @@ namespace MarkMpn.Sql4Cds.Engine { class SqlDataReaderWrapper : DbDataReader { - private Sql4CdsConnection _connection; private SqlConnection _sqlConnection; private SqlCommand _sqlCommand; private SqlDataReader _sqlDataReader; private readonly SqlNode _node; - public SqlDataReaderWrapper(Sql4CdsConnection connection, Sql4CdsCommand command, SqlConnection sqlConnection, SqlCommand sqlCommand, string dataSource, SqlNode node, CancellationToken cancellationToken) + public SqlDataReaderWrapper(SqlConnection sqlConnection, SqlCommand sqlCommand, CommandBehavior behavior, SqlNode node, CancellationToken cancellationToken) { - _connection = connection; _sqlConnection = sqlConnection; _sqlCommand = sqlCommand; cancellationToken.Register(() => _sqlCommand.Cancel()); - HandleException(() => _sqlDataReader = sqlCommand.ExecuteReader()); + HandleException(() => _sqlDataReader = sqlCommand.ExecuteReader(behavior)); _node = node; foreach (SqlParameter parameter in sqlCommand.Parameters) @@ -196,7 +194,8 @@ private T HandleException(Func action) } catch (SqlException ex) { - throw new Sql4CdsException(new Sql4CdsError(ex.Class, ex.LineNumber, ex.Number, null, ex.Server, ex.State, ex.Message), ex); + var error = new Sql4CdsError(ex.Class, ex.LineNumber + _node.LineNumber - 1, ex.Number, String.IsNullOrEmpty(ex.Procedure) ? null : ex.Procedure, ex.Server, ex.State, ex.Message); + throw new Sql4CdsException(error, new QueryExecutionException(error, ex) { Node = _node }); } } } diff --git a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/SqlNode.cs b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/SqlNode.cs index b78c4b5f..187ef022 100644 --- a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/SqlNode.cs +++ b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/SqlNode.cs @@ -119,13 +119,11 @@ public DbDataReader Execute(NodeExecutionContext context, CommandBehavior behavi }; } - var reader = cmd.ExecuteReader(behavior | CommandBehavior.CloseConnection); - - return reader; + return new SqlDataReaderWrapper(con, cmd, behavior | CommandBehavior.CloseConnection, this, context.Options.CancellationToken); } catch (SqlException ex) { - throw new QueryExecutionException(new Sql4CdsError(ex.Class, ex.Number, ex.Message), ex) + throw new QueryExecutionException(new Sql4CdsError(ex.Class, ex.LineNumber + LineNumber - 1, ex.Number, String.IsNullOrEmpty(ex.Procedure) ? null : ex.Procedure, ex.Server, ex.State, ex.Message), ex) { Node = this }; diff --git a/MarkMpn.Sql4Cds.SSMS/DmlExecute.cs b/MarkMpn.Sql4Cds.SSMS/DmlExecute.cs index b7465d18..7655f676 100644 --- a/MarkMpn.Sql4Cds.SSMS/DmlExecute.cs +++ b/MarkMpn.Sql4Cds.SSMS/DmlExecute.cs @@ -11,6 +11,7 @@ using MarkMpn.Sql4Cds.Engine.ExecutionPlan; using Microsoft.SqlServer.Management.QueryExecution; using Microsoft.SqlServer.Management.UI.VSIntegration; +using Microsoft.VisualStudio.PlatformUI; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; @@ -127,14 +128,14 @@ private void OnExecuteQuery(string Guid, int ID, object CustomIn, object CustomO con.InfoMessage += (s, msg) => { - sqlScriptEditorControl.Results.AddStringToMessages(msg.Message + "\r\n\r\n"); + sqlScriptEditorControl.Results.AddStringToMessages(msg.Message.Message + "\r\n\r\n"); resultFlag |= 1; // Success }; cmd.StatementCompleted += (s, stmt) => { if (tabPage != null) - ShowPlan(sqlScriptEditorControl, tabPage, stmt.Statement, dataSource, true); + ShowPlan(sqlScriptEditorControl, tabPage, stmt.Statement, dataSource, true, null); if (stmt.Message != null) sqlScriptEditorControl.Results.AddStringToMessages(stmt.Message + "\r\n\r\n"); @@ -170,6 +171,10 @@ private void OnExecuteQuery(string Guid, int ID, object CustomIn, object CustomO catch (Exception ex) { AddException(sqlScriptEditorControl, textSpan, ex); + + if (ex is Sql4CdsException sqlEx && sqlEx.InnerException is QueryExecutionException qee && tabPage != null) + ShowPlan(sqlScriptEditorControl, tabPage, GetRootNode(qee.Node), dataSource, true, qee); + resultFlag |= 2; // Failure } @@ -190,6 +195,19 @@ private void OnExecuteQuery(string Guid, int ID, object CustomIn, object CustomO } } + private IRootExecutionPlanNode GetRootNode(IExecutionPlanNode node) + { + while (node != null) + { + if (node is IRootExecutionPlanNode root) + return root; + + node = node.Parent; + } + + return null; + } + private void OnCancelQuery(string Guid, int ID, object CustomIn, object CustomOut, ref bool CancelDefault) { ThreadHelper.ThrowIfNotOnUIThread(); @@ -255,7 +273,7 @@ private void OnShowEstimatedPlan(string Guid, int ID, object CustomIn, object Cu sqlScriptEditorControl.Results.ResultsTabCtrl.SelectedTab = tabPage; foreach (var query in plans) - ShowPlan(sqlScriptEditorControl, tabPage, query, dataSource, false); + ShowPlan(sqlScriptEditorControl, tabPage, query, dataSource, false, null); var resultFlag = 1; // Success @@ -289,7 +307,7 @@ private TabPage AddShowPlanTab(SqlScriptEditorControlWrapper sqlScriptEditorCont return tabPage; } - private void ShowPlan(SqlScriptEditorControlWrapper sqlScriptEditorControl, TabPage tabPage, IRootExecutionPlanNode query, DataSource dataSource, bool executed) + private void ShowPlan(SqlScriptEditorControlWrapper sqlScriptEditorControl, TabPage tabPage, IRootExecutionPlanNode query, DataSource dataSource, bool executed, QueryExecutionException ex) { if (tabPage.InvokeRequired) { @@ -297,7 +315,7 @@ private void ShowPlan(SqlScriptEditorControlWrapper sqlScriptEditorControl, TabP { ThreadHelper.ThrowIfNotOnUIThread(); - ShowPlan(sqlScriptEditorControl, tabPage, query, dataSource, executed); + ShowPlan(sqlScriptEditorControl, tabPage, query, dataSource, executed, ex); })); return; } @@ -316,7 +334,7 @@ private void ShowPlan(SqlScriptEditorControlWrapper sqlScriptEditorControl, TabP AutoEllipsis = true, UseMnemonic = false }; - var planView = new ExecutionPlanView { Dock = DockStyle.Fill, Executed = executed, DataSources = new Dictionary { [dataSource.Name] = dataSource } }; + var planView = new ExecutionPlanView { Dock = DockStyle.Fill, Executed = executed, DataSources = new Dictionary { [dataSource.Name] = dataSource }, Exception = ex }; planView.Plan = query; planView.NodeSelected += (s, e) => diff --git a/MarkMpn.Sql4Cds.SSMS/Reflection/ReflectionObjectBase.cs b/MarkMpn.Sql4Cds.SSMS/Reflection/ReflectionObjectBase.cs index 208f0e2a..6a5a06fe 100644 --- a/MarkMpn.Sql4Cds.SSMS/Reflection/ReflectionObjectBase.cs +++ b/MarkMpn.Sql4Cds.SSMS/Reflection/ReflectionObjectBase.cs @@ -79,7 +79,16 @@ protected object InvokeMethod(object target, string methodName, params object[] .ToArray(); if (method.Length == 1) - return method[0].Invoke(target, args); + { + try + { + return method[0].Invoke(target, args); + } + catch (TargetInvocationException ex) + { + throw ex.InnerException; + } + } if (method.Length > 1) {