Skip to content

Commit

Permalink
Added logging
Browse files Browse the repository at this point in the history
  • Loading branch information
MarkMpn committed Dec 24, 2021
1 parent 0140229 commit 2e1991d
Show file tree
Hide file tree
Showing 9 changed files with 331 additions and 235 deletions.
15 changes: 10 additions & 5 deletions MarkMpn.Sql4Cds.SSMS/CommandBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using MarkMpn.Sql4Cds.Engine;
using Microsoft.ApplicationInsights;
using Microsoft.SqlServer.Management.UI.VSIntegration;
using Microsoft.VisualStudio.Shell;
using Microsoft.Xrm.Tooling.Connector;

namespace MarkMpn.Sql4Cds.SSMS
Expand Down Expand Up @@ -57,13 +58,18 @@ protected string GetQuery()
/// Gets the details of the currently active connection
/// </summary>
/// <returns></returns>
protected SqlConnectionStringBuilder GetConnectionInfo()
protected SqlConnectionStringBuilder GetConnectionInfo(bool log)
{
var scriptFactory = new ScriptFactoryWrapper(ServiceCache.ScriptFactory);
var sqlScriptEditorControl = scriptFactory.GetCurrentlyActiveFrameDocView(ServiceCache.VSMonitorSelection, false, out _);

if (sqlScriptEditorControl?.ConnectionString == null)
{
if (log)
VsShellUtilities.LogMessage("SQL 4 CDS", "No currently active window or connection", Microsoft.VisualStudio.Shell.Interop.__ACTIVITYLOG_ENTRYTYPE.ALE_INFORMATION);

return null;
}

return new SqlConnectionStringBuilder(sqlScriptEditorControl.ConnectionString);
}
Expand All @@ -74,7 +80,7 @@ protected SqlConnectionStringBuilder GetConnectionInfo()
/// <returns></returns>
protected bool IsDataverse()
{
var conStr = GetConnectionInfo();
var conStr = GetConnectionInfo(false);

return IsDataverse(conStr);
}
Expand Down Expand Up @@ -110,7 +116,7 @@ protected bool IsDataverse(SqlConnectionStringBuilder conStr)
protected CrmServiceClient ConnectCDS()
{
// Get the server name based on the current SQL connection
var conStr = GetConnectionInfo();
var conStr = GetConnectionInfo(true);
return ConnectCDS(conStr);
}

Expand Down Expand Up @@ -151,11 +157,10 @@ protected CrmServiceClient ConnectCDS(SqlConnectionStringBuilder conStr)
protected AttributeMetadataCache GetMetadataCache()
{
// Get the server name based on the current SQL connection
var conStr = GetConnectionInfo();
var conStr = GetConnectionInfo(true);
return GetMetadataCache(conStr);
}


/// <summary>
/// Gets metadata details for the given connection
/// </summary>
Expand Down
213 changes: 110 additions & 103 deletions MarkMpn.Sql4Cds.SSMS/DmlExecute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,136 +50,143 @@ private void OnExecuteQuery(string Guid, int ID, object CustomIn, object CustomO
{
ThreadHelper.ThrowIfNotOnUIThread();

if (ActiveDocument == null)
return;

if (!IsDataverse())
return;

// We are running a query against the Dataverse TDS endpoint, so check if there are any DML statements in the query

// Get the SQL editor object
var scriptFactory = new ScriptFactoryWrapper(ServiceCache.ScriptFactory);
var sqlScriptEditorControl = scriptFactory.GetCurrentlyActiveFrameDocView(ServiceCache.VSMonitorSelection, false, out _);
var textSpan = sqlScriptEditorControl.GetSelectedTextSpan();
var sql = textSpan.Text;

// Quick check first so we don't spend a long time connecting to CDS just to find there's a simple SELECT query
if (sql.IndexOf("INSERT", StringComparison.OrdinalIgnoreCase) == -1 &&
sql.IndexOf("UPDATE", StringComparison.OrdinalIgnoreCase) == -1 &&
sql.IndexOf("DELETE", StringComparison.OrdinalIgnoreCase) == -1)
return;

// Allow user to bypass SQL 4 CDS logic in case of problematic queries
if (sql.IndexOf("Bypass SQL 4 CDS", StringComparison.OrdinalIgnoreCase) != -1 ||
sql.IndexOf("Bypass SQL4CDS", StringComparison.OrdinalIgnoreCase) != -1)
return;

// Store the options being used for these queries so we can cancel them later
var options = new QueryExecutionOptions(sqlScriptEditorControl, Package.Settings);
var metadata = GetMetadataCache();
var org = ConnectCDS();
var dataSource = new DataSource { Name = "local", Metadata = metadata, TableSizeCache = new TableSizeCache(org, metadata), Connection = org };

// We've possibly got a DML statement, so parse the query properly to get the details
var converter = new ExecutionPlanBuilder(new[] { dataSource }, options)
{
TDSEndpointAvailable = true,
QuotedIdentifiers = sqlScriptEditorControl.QuotedIdentifiers
};
IRootExecutionPlanNode[] queries;

try
{
queries = converter.Build(sql);
}
catch (Exception ex)
{
CancelDefault = true;
ShowError(sqlScriptEditorControl, textSpan, ex);
return;
}
if (ActiveDocument == null)
return;

if (!IsDataverse())
return;

// We are running a query against the Dataverse TDS endpoint, so check if there are any DML statements in the query

// Get the SQL editor object
var scriptFactory = new ScriptFactoryWrapper(ServiceCache.ScriptFactory);
var sqlScriptEditorControl = scriptFactory.GetCurrentlyActiveFrameDocView(ServiceCache.VSMonitorSelection, false, out _);
var textSpan = sqlScriptEditorControl.GetSelectedTextSpan();
var sql = textSpan.Text;

// Quick check first so we don't spend a long time connecting to CDS just to find there's a simple SELECT query
if (sql.IndexOf("INSERT", StringComparison.OrdinalIgnoreCase) == -1 &&
sql.IndexOf("UPDATE", StringComparison.OrdinalIgnoreCase) == -1 &&
sql.IndexOf("DELETE", StringComparison.OrdinalIgnoreCase) == -1)
return;

// Allow user to bypass SQL 4 CDS logic in case of problematic queries
if (sql.IndexOf("Bypass SQL 4 CDS", StringComparison.OrdinalIgnoreCase) != -1 ||
sql.IndexOf("Bypass SQL4CDS", StringComparison.OrdinalIgnoreCase) != -1)
return;

// Store the options being used for these queries so we can cancel them later
var options = new QueryExecutionOptions(sqlScriptEditorControl, Package.Settings);
var metadata = GetMetadataCache();
var org = ConnectCDS();
var dataSource = new DataSource { Name = "local", Metadata = metadata, TableSizeCache = new TableSizeCache(org, metadata), Connection = org };

// We've possibly got a DML statement, so parse the query properly to get the details
var converter = new ExecutionPlanBuilder(new[] { dataSource }, options)
{
TDSEndpointAvailable = true,
QuotedIdentifiers = sqlScriptEditorControl.QuotedIdentifiers
};
IRootExecutionPlanNode[] queries;

var dmlQueries = queries.OfType<IDmlQueryExecutionPlanNode>().ToArray();
var hasSelect = queries.Length > dmlQueries.Length;
var hasDml = dmlQueries.Length > 0;
try
{
queries = converter.Build(sql);
}
catch (Exception ex)
{
CancelDefault = true;
ShowError(sqlScriptEditorControl, textSpan, ex);
return;
}

if (hasSelect && hasDml)
{
// Can't mix SELECT and DML queries as we can't show results in the grid and SSMS can't execute the DML queries
CancelDefault = true;
ShowError(sqlScriptEditorControl, textSpan, new ApplicationException("Cannot mix SELECT queries with DML queries. Execute SELECT statements in a separate batch to INSERT/UPDATE/DELETE"));
return;
}
var dmlQueries = queries.OfType<IDmlQueryExecutionPlanNode>().ToArray();
var hasSelect = queries.Length > dmlQueries.Length;
var hasDml = dmlQueries.Length > 0;

if (hasSelect)
return;
if (hasSelect && hasDml)
{
// Can't mix SELECT and DML queries as we can't show results in the grid and SSMS can't execute the DML queries
CancelDefault = true;
ShowError(sqlScriptEditorControl, textSpan, new ApplicationException("Cannot mix SELECT queries with DML queries. Execute SELECT statements in a separate batch to INSERT/UPDATE/DELETE"));
return;
}

// We need to execute the DML statements directly
CancelDefault = true;
if (hasSelect)
return;

// Show the queries starting to run
sqlScriptEditorControl.StandardPrepareBeforeExecute();
sqlScriptEditorControl.OnExecutionStarted(sqlScriptEditorControl, EventArgs.Empty);
sqlScriptEditorControl.ToggleResultsControl(true);
sqlScriptEditorControl.Results.StartExecution();
// We need to execute the DML statements directly
CancelDefault = true;

_options[ActiveDocument] = options;
var doc = ActiveDocument;
// Show the queries starting to run
sqlScriptEditorControl.StandardPrepareBeforeExecute();
sqlScriptEditorControl.OnExecutionStarted(sqlScriptEditorControl, EventArgs.Empty);
sqlScriptEditorControl.ToggleResultsControl(true);
sqlScriptEditorControl.Results.StartExecution();

// Run the queries in a background thread
var task = new System.Threading.Tasks.Task(async () =>
{
var resultFlag = 0;
_options[ActiveDocument] = options;
var doc = ActiveDocument;

foreach (var query in dmlQueries)
// Run the queries in a background thread
var task = new System.Threading.Tasks.Task(async () =>
{
if (options.Cancelled)
break;
var resultFlag = 0;

try
foreach (var query in dmlQueries)
{
_ai.TrackEvent("Execute", new Dictionary<string, string> { ["QueryType"] = query.GetType().Name, ["Source"] = "SSMS" });
var msg = query.Execute(new Dictionary<string, DataSource>(StringComparer.OrdinalIgnoreCase) { [dataSource.Name] = dataSource }, options, null, null);
if (options.Cancelled)
break;

sqlScriptEditorControl.Results.AddStringToMessages(msg + "\r\n\r\n");
try
{
_ai.TrackEvent("Execute", new Dictionary<string, string> { ["QueryType"] = query.GetType().Name, ["Source"] = "SSMS" });
var msg = query.Execute(new Dictionary<string, DataSource>(StringComparer.OrdinalIgnoreCase) { [dataSource.Name] = dataSource }, options, null, null);

resultFlag |= 1; // Success
}
catch (Exception ex)
{
var error = ex;
sqlScriptEditorControl.Results.AddStringToMessages(msg + "\r\n\r\n");

if (ex is PartialSuccessException partial)
resultFlag |= 1; // Success
}
catch (Exception ex)
{
error = partial.InnerException;
var error = ex;

if (partial.Result is string msg)
if (ex is PartialSuccessException partial)
{
sqlScriptEditorControl.Results.AddStringToMessages(msg + "\r\n\r\n");
resultFlag |= 1; // Success
error = partial.InnerException;

if (partial.Result is string msg)
{
sqlScriptEditorControl.Results.AddStringToMessages(msg + "\r\n\r\n");
resultFlag |= 1; // Success
}
}
}

_ai.TrackException(error, new Dictionary<string, string> { ["Sql"] = sql, ["Source"] = "SSMS" });
_ai.TrackException(error, new Dictionary<string, string> { ["Sql"] = sql, ["Source"] = "SSMS" });

AddException(sqlScriptEditorControl, textSpan, error);
resultFlag |= 2; // Failure
AddException(sqlScriptEditorControl, textSpan, error);
resultFlag |= 2; // Failure
}
}
}

if (options.Cancelled)
resultFlag = 4; // Cancel
if (options.Cancelled)
resultFlag = 4; // Cancel

await Package.JoinableTaskFactory.SwitchToMainThreadAsync();

await Package.JoinableTaskFactory.SwitchToMainThreadAsync();
sqlScriptEditorControl.Results.OnSqlExecutionCompletedInt(resultFlag);

sqlScriptEditorControl.Results.OnSqlExecutionCompletedInt(resultFlag);

_options.Remove(doc);
});
_options.Remove(doc);
});

options.Task = task;
task.Start();
options.Task = task;
task.Start();
}
catch (Exception ex)
{
VsShellUtilities.LogError("SQL 4 CDS", ex.ToString());
}
}

private void OnCancelQuery(string Guid, int ID, object CustomIn, object CustomOut, ref bool CancelDefault)
Expand Down
Loading

0 comments on commit 2e1991d

Please sign in to comment.