mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-02-17 02:51:45 -05:00
Handle errors during execution as info messages instead of exceptions (#596)
This commit is contained in:
@@ -344,16 +344,16 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
|||||||
{
|
{
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
// Verify that the cancellation token hasn't benn cancelled
|
// Verify that the cancellation token hasn't been canceled
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
// Skip this result set if there aren't any rows (ie, UPDATE/DELETE/etc queries)
|
// Skip this result set if there aren't any rows (i.e. UPDATE/DELETE/etc queries)
|
||||||
if (!reader.HasRows && reader.FieldCount == 0)
|
if (!reader.HasRows && reader.FieldCount == 0)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This resultset has results (ie, SELECT/etc queries)
|
// This resultset has results (i.e. SELECT/etc queries)
|
||||||
ResultSet resultSet = new ResultSet(resultSets.Count, Id, outputFileFactory);
|
ResultSet resultSet = new ResultSet(resultSets.Count, Id, outputFileFactory);
|
||||||
resultSet.ResultCompletion += ResultSetCompletion;
|
resultSet.ResultCompletion += ResultSetCompletion;
|
||||||
|
|
||||||
@@ -520,14 +520,70 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Delegate handler for storing messages that are returned from the server
|
/// Delegate handler for storing messages that are returned from the server
|
||||||
/// NOTE: Only messages that are below a certain severity will be returned via this
|
|
||||||
/// mechanism. Anything above that level will trigger an exception.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sender">Object that fired the event</param>
|
/// <param name="sender">Object that fired the event</param>
|
||||||
/// <param name="args">Arguments from the event</param>
|
/// <param name="args">Arguments from the event</param>
|
||||||
private void ServerMessageHandler(object sender, SqlInfoMessageEventArgs args)
|
private async void ServerMessageHandler(object sender, SqlInfoMessageEventArgs args)
|
||||||
{
|
{
|
||||||
SendMessage(args.Message, false).Wait();
|
foreach (SqlError error in args.Errors)
|
||||||
|
{
|
||||||
|
await HandleSqlErrorMessage(error.Number, error.Class, error.State, error.LineNumber, error.Procedure, error.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handle a single SqlError's error message by processing and displaying it. The arguments come from the error being handled
|
||||||
|
/// </summary>
|
||||||
|
internal async Task HandleSqlErrorMessage(int errorNumber, byte errorClass, byte state, int lineNumber, string procedure, string message)
|
||||||
|
{
|
||||||
|
// Did the database context change (error code 5701)?
|
||||||
|
if (errorNumber == 5701)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string detailedMessage;
|
||||||
|
if (string.IsNullOrEmpty(procedure))
|
||||||
|
{
|
||||||
|
detailedMessage = string.Format("Msg {0}, Level {1}, State {2}, Line {3}{4}{5}",
|
||||||
|
errorNumber, errorClass, state, lineNumber + Selection.StartLine,
|
||||||
|
Environment.NewLine, message);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
detailedMessage = string.Format("Msg {0}, Level {1}, State {2}, Procedure {3}, Line {4}{5}{6}",
|
||||||
|
errorNumber, errorClass, state, procedure, lineNumber,
|
||||||
|
Environment.NewLine, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isError;
|
||||||
|
if (errorClass > 10)
|
||||||
|
{
|
||||||
|
isError = true;
|
||||||
|
}
|
||||||
|
else if (errorClass > 0 && errorNumber > 0)
|
||||||
|
{
|
||||||
|
isError = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
isError = false;
|
||||||
|
detailedMessage = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (detailedMessage != null)
|
||||||
|
{
|
||||||
|
await SendMessage(detailedMessage, isError);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await SendMessage(message, isError);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isError)
|
||||||
|
{
|
||||||
|
this.HasError = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -379,6 +379,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
|||||||
if (sqlConn != null)
|
if (sqlConn != null)
|
||||||
{
|
{
|
||||||
// Subscribe to database informational messages
|
// Subscribe to database informational messages
|
||||||
|
sqlConn.GetUnderlyingConnection().FireInfoMessageEventOnUserErrors = true;
|
||||||
sqlConn.GetUnderlyingConnection().InfoMessage += OnInfoMessage;
|
sqlConn.GetUnderlyingConnection().InfoMessage += OnInfoMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using System.Data.Common;
|
using System.Data.Common;
|
||||||
|
using System.Data.SqlClient;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@@ -354,7 +355,75 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.Execution
|
|||||||
Assert.True(messageCalls == 1);
|
Assert.True(messageCalls == 1);
|
||||||
batch.StatementCompletedHandler(null, new StatementCompletedEventArgs(2));
|
batch.StatementCompletedHandler(null, new StatementCompletedEventArgs(2));
|
||||||
Assert.True(messageCalls == 2);
|
Assert.True(messageCalls == 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ServerMessageHandlerShowsErrorMessages()
|
||||||
|
{
|
||||||
|
// Set up the batch to track message calls
|
||||||
|
Batch batch = new Batch(Constants.StandardQuery, Common.SubsectionDocument, Common.Ordinal, MemoryFileSystem.GetFileStreamFactory());
|
||||||
|
int errorMessageCalls = 0;
|
||||||
|
int infoMessageCalls = 0;
|
||||||
|
string actualMessage = null;
|
||||||
|
batch.BatchMessageSent += args =>
|
||||||
|
{
|
||||||
|
if (args.IsError)
|
||||||
|
{
|
||||||
|
errorMessageCalls++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
infoMessageCalls++;
|
||||||
|
}
|
||||||
|
actualMessage = args.Message;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
// If I call the server message handler with an error message
|
||||||
|
var errorMessage = "error message";
|
||||||
|
await batch.HandleSqlErrorMessage(1, 15, 0, 1, string.Empty, errorMessage);
|
||||||
|
|
||||||
|
// Then one error message call should be recorded
|
||||||
|
Assert.Equal(1, errorMessageCalls);
|
||||||
|
Assert.Equal(0, infoMessageCalls);
|
||||||
|
|
||||||
|
// And the actual message should be a formatted version of the error message
|
||||||
|
Assert.True(actualMessage.Length > errorMessage.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ServerMessageHandlerShowsInfoMessages()
|
||||||
|
{
|
||||||
|
// Set up the batch to track message calls
|
||||||
|
Batch batch = new Batch(Constants.StandardQuery, Common.SubsectionDocument, Common.Ordinal, MemoryFileSystem.GetFileStreamFactory());
|
||||||
|
int errorMessageCalls = 0;
|
||||||
|
int infoMessageCalls = 0;
|
||||||
|
string actualMessage = null;
|
||||||
|
batch.BatchMessageSent += args =>
|
||||||
|
{
|
||||||
|
if (args.IsError)
|
||||||
|
{
|
||||||
|
errorMessageCalls++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
infoMessageCalls++;
|
||||||
|
}
|
||||||
|
actualMessage = args.Message;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
// If I call the server message handler with an info message
|
||||||
|
var infoMessage = "info message";
|
||||||
|
await batch.HandleSqlErrorMessage(0, 0, 0, 1, string.Empty, infoMessage);
|
||||||
|
|
||||||
|
// Then one info message call should be recorded
|
||||||
|
Assert.Equal(0, errorMessageCalls);
|
||||||
|
Assert.Equal(1, infoMessageCalls);
|
||||||
|
|
||||||
|
// And the actual message should be the exact info message
|
||||||
|
Assert.Equal(infoMessage, actualMessage);
|
||||||
|
}
|
||||||
|
|
||||||
private static DbConnection GetConnection(ConnectionInfo info)
|
private static DbConnection GetConnection(ConnectionInfo info)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user