diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Batch.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Batch.cs
new file mode 100644
index 00000000..38528b60
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Batch.cs
@@ -0,0 +1,263 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Data.Common;
+using System.Data.SqlClient;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
+
+namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
+{
+ ///
+ /// This class represents a batch within a query
+ ///
+ public class Batch
+ {
+ private const string RowsAffectedFormat = "({0} row(s) affected)";
+
+ #region Properties
+ ///
+ /// The text of batch that will be executed
+ ///
+ public string BatchText { get; set; }
+
+ ///
+ /// Whether or not this batch has an error
+ ///
+ public bool HasError { get; set; }
+
+ ///
+ /// Whether or not this batch has been executed, regardless of success or failure
+ ///
+ public bool HasExecuted { get; set; }
+
+ ///
+ /// Internal representation of the messages so we can modify internally
+ ///
+ private List resultMessages;
+
+ ///
+ /// Messages that have come back from the server
+ ///
+ public IEnumerable ResultMessages
+ {
+ get { return resultMessages; }
+ }
+
+ ///
+ /// Internal representation of the result sets so we can modify internally
+ ///
+ private List resultSets;
+
+ ///
+ /// The result sets of the batch execution
+ ///
+ public IEnumerable ResultSets
+ {
+ get { return resultSets; }
+ }
+
+ ///
+ /// Property for generating a set result set summaries from the result sets
+ ///
+ public ResultSetSummary[] ResultSummaries
+ {
+ get
+ {
+ return ResultSets.Select((set, index) => new ResultSetSummary()
+ {
+ ColumnInfo = set.Columns,
+ Id = index,
+ RowCount = set.Rows.Count
+ }).ToArray();
+ }
+ }
+
+ ///
+ /// The 0-indexed line number that this batch started on
+ ///
+ internal int StartLine { get; set; }
+
+ #endregion
+
+ public Batch(string batchText, int startLine)
+ {
+ // Sanity check for input
+ if (string.IsNullOrEmpty(batchText))
+ {
+ throw new ArgumentNullException(nameof(batchText), "Query text cannot be null");
+ }
+
+ // Initialize the internal state
+ BatchText = batchText;
+ StartLine = startLine - 1; // -1 to make sure that the line number of the batch is 0-indexed, since SqlParser gives 1-indexed line numbers
+ HasExecuted = false;
+ resultSets = new List();
+ resultMessages = new List();
+ }
+
+ ///
+ /// Executes this batch and captures any server messages that are returned.
+ ///
+ /// The connection to use to execute the batch
+ /// Token for cancelling the execution
+ public async Task Execute(DbConnection conn, CancellationToken cancellationToken)
+ {
+ // Sanity check to make sure we haven't already run this batch
+ if (HasExecuted)
+ {
+ throw new InvalidOperationException("Batch has already executed.");
+ }
+
+ try
+ {
+ // Register the message listener to *this instance* of the batch
+ // Note: This is being done to associate messages with batches
+ SqlConnection sqlConn = conn as SqlConnection;
+ if (sqlConn != null)
+ {
+ sqlConn.InfoMessage += StoreDbMessage;
+ }
+
+ // Create a command that we'll use for executing the query
+ using (DbCommand command = conn.CreateCommand())
+ {
+ command.CommandText = BatchText;
+ command.CommandType = CommandType.Text;
+
+ // Execute the command to get back a reader
+ using (DbDataReader reader = await command.ExecuteReaderAsync(cancellationToken))
+ {
+ do
+ {
+ // Skip this result set if there aren't any rows
+ if (!reader.HasRows && reader.FieldCount == 0)
+ {
+ // Create a message with the number of affected rows -- IF the query affects rows
+ resultMessages.Add(reader.RecordsAffected >= 0
+ ? string.Format(RowsAffectedFormat, reader.RecordsAffected)
+ : "Command(s) completed successfully.");
+ continue;
+ }
+
+ // Read until we hit the end of the result set
+ ResultSet resultSet = new ResultSet();
+ while (await reader.ReadAsync(cancellationToken))
+ {
+ resultSet.AddRow(reader);
+ }
+
+ // Read off the column schema information
+ if (reader.CanGetColumnSchema())
+ {
+ resultSet.Columns = reader.GetColumnSchema().ToArray();
+ }
+
+ // Add the result set to the results of the query
+ resultSets.Add(resultSet);
+
+ // Add a message for the number of rows the query returned
+ resultMessages.Add(string.Format(RowsAffectedFormat, resultSet.Rows.Count));
+ } while (await reader.NextResultAsync(cancellationToken));
+ }
+ }
+ }
+ catch (DbException dbe)
+ {
+ HasError = true;
+ UnwrapDbException(dbe);
+ }
+ catch (Exception)
+ {
+ HasError = true;
+ throw;
+ }
+ finally
+ {
+ // Remove the message event handler from the connection
+ SqlConnection sqlConn = conn as SqlConnection;
+ if (sqlConn != null)
+ {
+ sqlConn.InfoMessage -= StoreDbMessage;
+ }
+
+ // Mark that we have executed
+ HasExecuted = true;
+ }
+ }
+
+ ///
+ /// Generates a subset of the rows from a result set of the batch
+ ///
+ /// The index for selecting the result set
+ /// The starting row of the results
+ /// How many rows to retrieve
+ /// A subset of results
+ public ResultSetSubset GetSubset(int resultSetIndex, int startRow, int rowCount)
+ {
+ // Sanity check to make sure we have valid numbers
+ if (resultSetIndex < 0 || resultSetIndex >= resultSets.Count)
+ {
+ throw new ArgumentOutOfRangeException(nameof(resultSetIndex), "Result set index cannot be less than 0" +
+ "or greater than the number of result sets");
+ }
+
+ // Retrieve the result set
+ return resultSets[resultSetIndex].GetSubset(startRow, rowCount);
+ }
+
+ #region Private Helpers
+
+ ///
+ /// 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.
+ ///
+ /// Object that fired the event
+ /// Arguments from the event
+ private void StoreDbMessage(object sender, SqlInfoMessageEventArgs args)
+ {
+ resultMessages.Add(args.Message);
+ }
+
+ ///
+ /// Attempts to convert a to a that
+ /// contains much more info about Sql Server errors. The exception is then unwrapped and
+ /// messages are formatted and stored in . If the exception
+ /// cannot be converted to SqlException, the message is written to the messages list.
+ ///
+ /// The exception to unwrap
+ private void UnwrapDbException(DbException dbe)
+ {
+ SqlException se = dbe as SqlException;
+ if (se != null)
+ {
+ foreach (var error in se.Errors)
+ {
+ SqlError sqlError = error as SqlError;
+ if (sqlError != null)
+ {
+ int lineNumber = sqlError.LineNumber + StartLine;
+ string message = String.Format("Msg {0}, Level {1}, State {2}, Line {3}{4}{5}",
+ sqlError.Number, sqlError.Class, sqlError.State, lineNumber,
+ Environment.NewLine, sqlError.Message);
+ resultMessages.Add(message);
+ }
+ }
+ }
+ else
+ {
+ resultMessages.Add(dbe.Message);
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/BatchSummary.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/BatchSummary.cs
new file mode 100644
index 00000000..73d1d4c8
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/BatchSummary.cs
@@ -0,0 +1,33 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
+{
+ ///
+ /// Summary of a batch within a query
+ ///
+ public class BatchSummary
+ {
+ ///
+ /// Whether or not the batch was successful. True indicates errors, false indicates success
+ ///
+ public bool HasError { get; set; }
+
+ ///
+ /// The ID of the result set within the query results
+ ///
+ public int Id { get; set; }
+
+ ///
+ /// Any messages that came back from the server during execution of the batch
+ ///
+ public string[] Messages { get; set; }
+
+ ///
+ /// The summaries of the result sets inside the batch
+ ///
+ public ResultSetSummary[] ResultSetSummaries { get; set; }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/QueryExecuteCompleteNotification.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/QueryExecuteCompleteNotification.cs
index f81edb62..90c8c7b3 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/QueryExecuteCompleteNotification.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/QueryExecuteCompleteNotification.cs
@@ -17,20 +17,10 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
///
public string OwnerUri { get; set; }
- ///
- /// Any messages that came back from the server during execution of the query
- ///
- public string[] Messages { get; set; }
-
- ///
- /// Whether or not the query was successful. True indicates errors, false indicates success
- ///
- public bool HasError { get; set; }
-
///
/// Summaries of the result sets that were returned with the query
///
- public ResultSetSummary[] ResultSetSummaries { get; set; }
+ public BatchSummary[] BatchSummaries { get; set; }
}
public class QueryExecuteCompleteEvent
diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/QueryExecuteSubsetRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/QueryExecuteSubsetRequest.cs
index cdf434bb..2c861502 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/QueryExecuteSubsetRequest.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/QueryExecuteSubsetRequest.cs
@@ -17,6 +17,11 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
///
public string OwnerUri { get; set; }
+ ///
+ /// Index of the batch to get the results from
+ ///
+ public int BatchIndex { get; set; }
+
///
/// Index of the result set to get the results from
///
diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/ResultSetSummary.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/ResultSetSummary.cs
index 5f8de12a..b0a6d75c 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/ResultSetSummary.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/ResultSetSummary.cs
@@ -13,7 +13,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
public class ResultSetSummary
{
///
- /// The ID of the result set within the query results
+ /// The ID of the result set within the batch results
///
public int Id { get; set; }
diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Query.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Query.cs
index 5ecf82ec..73d91e5e 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Query.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Query.cs
@@ -4,15 +4,14 @@
//
using System;
-using System.Collections.Generic;
-using System.Data;
using System.Data.Common;
-using System.Data.SqlClient;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using Microsoft.SqlServer.Management.SqlParser.Parser;
using Microsoft.SqlTools.ServiceLayer.Connection;
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
+using Microsoft.SqlTools.ServiceLayer.SqlContext;
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
{
@@ -21,10 +20,35 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
///
public class Query : IDisposable
{
- private const string RowsAffectedFormat = "({0} row(s) affected)";
-
#region Properties
+ ///
+ /// The batches underneath this query
+ ///
+ internal Batch[] Batches { get; set; }
+
+ ///
+ /// The summaries of the batches underneath this query
+ ///
+ public BatchSummary[] BatchSummaries
+ {
+ get
+ {
+ if (!HasExecuted)
+ {
+ throw new InvalidOperationException("Query has not been executed.");
+ }
+
+ return Batches.Select((batch, index) => new BatchSummary
+ {
+ Id = index,
+ HasError = batch.HasError,
+ Messages = batch.ResultMessages.ToArray(),
+ ResultSetSummaries = batch.ResultSummaries
+ }).ToArray();
+ }
+ }
+
///
/// Cancellation token source, used for cancelling async db actions
///
@@ -34,49 +58,34 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
/// The connection info associated with the file editor owner URI, used to create a new
/// connection upon execution of the query
///
- public ConnectionInfo EditorConnection { get; set; }
+ private ConnectionInfo EditorConnection { get; set; }
- ///
- /// Whether or not the query has an error
- ///
- public bool HasError { get; set; }
+ private bool HasExecuteBeenCalled { get; set; }
///
/// Whether or not the query has completed executed, regardless of success or failure
///
- public bool HasExecuted { get; set; }
+ ///
+ /// Don't touch the setter unless you're doing unit tests!
+ ///
+ public bool HasExecuted
+ {
+ get { return Batches.Length == 0 ? HasExecuteBeenCalled : Batches.All(b => b.HasExecuted); }
+ internal set
+ {
+ HasExecuteBeenCalled = value;
+ foreach (var batch in Batches)
+ {
+ batch.HasExecuted = value;
+ }
+ }
+ }
///
/// The text of the query to execute
///
public string QueryText { get; set; }
- ///
- /// Messages that have come back from the server
- ///
- public List ResultMessages { get; set; }
-
- ///
- /// The result sets of the query execution
- ///
- public List ResultSets { get; set; }
-
- ///
- /// Property for generating a set result set summaries from the result sets
- ///
- public ResultSetSummary[] ResultSummary
- {
- get
- {
- return ResultSets.Select((set, index) => new ResultSetSummary
- {
- ColumnInfo = set.Columns,
- Id = index,
- RowCount = set.Rows.Count
- }).ToArray();
- }
- }
-
#endregion
///
@@ -84,10 +93,11 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
///
/// The text of the query to execute
/// The information of the connection to use to execute the query
- public Query(string queryText, ConnectionInfo connection)
+ /// Settings for how to execute the query, from the user
+ public Query(string queryText, ConnectionInfo connection, QueryExecutionSettings settings)
{
// Sanity check for input
- if (String.IsNullOrEmpty(queryText))
+ if (string.IsNullOrEmpty(queryText))
{
throw new ArgumentNullException(nameof(queryText), "Query text cannot be null");
}
@@ -95,14 +105,24 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
{
throw new ArgumentNullException(nameof(connection), "Connection cannot be null");
}
+ if (settings == null)
+ {
+ throw new ArgumentNullException(nameof(settings), "Settings cannot be null");
+ }
// Initialize the internal state
QueryText = queryText;
EditorConnection = connection;
- HasExecuted = false;
- ResultSets = new List();
- ResultMessages = new List();
cancellationSource = new CancellationTokenSource();
+
+ // Process the query into batches
+ ParseResult parseResult = Parser.Parse(queryText, new ParseOptions
+ {
+ BatchSeparator = settings.BatchSeparator
+ });
+ // NOTE: We only want to process batches that have statements (ie, ignore comments and empty lines)
+ Batches = parseResult.Script.Batches.Where(b => b.Statements.Count > 0)
+ .Select(b => new Batch(b.Sql, b.StartLocation.LineNumber)).ToArray();
}
///
@@ -110,98 +130,38 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
///
public async Task Execute()
{
- // Sanity check to make sure we haven't already run this query
- if (HasExecuted)
+ // Mark that we've internally executed
+ HasExecuteBeenCalled = true;
+
+ // Don't actually execute if there aren't any batches to execute
+ if (Batches.Length == 0)
{
- throw new InvalidOperationException("Query has already executed.");
+ return;
}
- // Create a connection from the connection details
- try
+ // Open up a connection for querying the database
+ string connectionString = ConnectionService.BuildConnectionString(EditorConnection.ConnectionDetails);
+ using (DbConnection conn = EditorConnection.Factory.CreateSqlConnection(connectionString))
{
- string connectionString = ConnectionService.BuildConnectionString(EditorConnection.ConnectionDetails);
- using (DbConnection conn = EditorConnection.Factory.CreateSqlConnection(connectionString))
+ await conn.OpenAsync();
+
+ // We need these to execute synchronously, otherwise the user will be very unhappy
+ foreach (Batch b in Batches)
{
- // If we have the message listener, bind to it
- SqlConnection sqlConn = conn as SqlConnection;
- if (sqlConn != null)
- {
- sqlConn.InfoMessage += StoreDbMessage;
- }
-
- await conn.OpenAsync(cancellationSource.Token);
-
- // Create a command that we'll use for executing the query
- using (DbCommand command = conn.CreateCommand())
- {
- command.CommandText = QueryText;
- command.CommandType = CommandType.Text;
-
- // Execute the command to get back a reader
- using (DbDataReader reader = await command.ExecuteReaderAsync(cancellationSource.Token))
- {
- do
- {
- // Skip this result set if there aren't any rows
- if (!reader.HasRows && reader.FieldCount == 0)
- {
- // Create a message with the number of affected rows -- IF the query affects rows
- ResultMessages.Add(reader.RecordsAffected >= 0
- ? string.Format(RowsAffectedFormat, reader.RecordsAffected)
- : "Command Executed Successfully");
-
- continue;
- }
-
- // Read until we hit the end of the result set
- ResultSet resultSet = new ResultSet();
- while (await reader.ReadAsync(cancellationSource.Token))
- {
- resultSet.AddRow(reader);
- }
-
- // Read off the column schema information
- if (reader.CanGetColumnSchema())
- {
- resultSet.Columns = reader.GetColumnSchema().ToArray();
- }
-
- // Add the result set to the results of the query
- ResultSets.Add(resultSet);
-
- // Add a message for the number of rows the query returned
- ResultMessages.Add(string.Format(RowsAffectedFormat, resultSet.Rows.Count));
-
- } while (await reader.NextResultAsync(cancellationSource.Token));
- }
- }
+ await b.Execute(conn, cancellationSource.Token);
}
}
- catch (DbException dbe)
- {
- HasError = true;
- UnwrapDbException(dbe);
- }
- catch (Exception)
- {
- HasError = true;
- throw;
- }
- finally
- {
- // Mark that we have executed
- HasExecuted = true;
- }
}
///
/// Retrieves a subset of the result sets
///
+ /// The index for selecting the batch item
/// The index for selecting the result set
/// The starting row of the results
/// How many rows to retrieve
/// A subset of results
- public ResultSetSubset GetSubset(int resultSetIndex, int startRow, int rowCount)
+ public ResultSetSubset GetSubset(int batchIndex, int resultSetIndex, int startRow, int rowCount)
{
// Sanity check that the results are available
if (!HasExecuted)
@@ -209,30 +169,14 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
throw new InvalidOperationException("The query has not completed, yet.");
}
- // Sanity check to make sure we have valid numbers
- if (resultSetIndex < 0 || resultSetIndex >= ResultSets.Count)
+ // Sanity check to make sure that the batch is within bounds
+ if (batchIndex < 0 || batchIndex >= Batches.Length)
{
- throw new ArgumentOutOfRangeException(nameof(resultSetIndex), "Result set index cannot be less than 0" +
+ throw new ArgumentOutOfRangeException(nameof(batchIndex), "Result set index cannot be less than 0" +
"or greater than the number of result sets");
}
- ResultSet targetResultSet = ResultSets[resultSetIndex];
- if (startRow < 0 || startRow >= targetResultSet.Rows.Count)
- {
- throw new ArgumentOutOfRangeException(nameof(startRow), "Start row cannot be less than 0 " +
- "or greater than the number of rows in the resultset");
- }
- if (rowCount <= 0)
- {
- throw new ArgumentOutOfRangeException(nameof(rowCount), "Row count must be a positive integer");
- }
- // Retrieve the subset of the results as per the request
- object[][] rows = targetResultSet.Rows.Skip(startRow).Take(rowCount).ToArray();
- return new ResultSetSubset
- {
- Rows = rows,
- RowCount = rows.Length
- };
+ return Batches[batchIndex].GetSubset(resultSetIndex, startRow, rowCount);
}
///
@@ -250,48 +194,6 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
cancellationSource.Cancel();
}
- ///
- /// 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.
- ///
- /// Object that fired the event
- /// Arguments from the event
- private void StoreDbMessage(object sender, SqlInfoMessageEventArgs args)
- {
- ResultMessages.Add(args.Message);
- }
-
- ///
- /// Attempts to convert a to a that
- /// contains much more info about Sql Server errors. The exception is then unwrapped and
- /// messages are formatted and stored in . If the exception
- /// cannot be converted to SqlException, the message is written to the messages list.
- ///
- /// The exception to unwrap
- private void UnwrapDbException(DbException dbe)
- {
- SqlException se = dbe as SqlException;
- if (se != null)
- {
- foreach (var error in se.Errors)
- {
- SqlError sqlError = error as SqlError;
- if (sqlError != null)
- {
- string message = String.Format("Msg {0}, Level {1}, State {2}, Line {3}{4}{5}",
- sqlError.Number, sqlError.Class, sqlError.State, sqlError.LineNumber,
- Environment.NewLine, sqlError.Message);
- ResultMessages.Add(message);
- }
- }
- }
- else
- {
- ResultMessages.Add(dbe.Message);
- }
- }
-
#region IDisposable Implementation
private bool disposed;
diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/QueryExecutionService.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/QueryExecutionService.cs
index 5b26eafc..f23925ab 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/QueryExecutionService.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/QueryExecutionService.cs
@@ -10,6 +10,8 @@ using Microsoft.SqlTools.ServiceLayer.Connection;
using Microsoft.SqlTools.ServiceLayer.Hosting;
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
+using Microsoft.SqlTools.ServiceLayer.SqlContext;
+using Microsoft.SqlTools.ServiceLayer.Workspace;
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
{
@@ -60,6 +62,8 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
private readonly Lazy> queries =
new Lazy>(() => new ConcurrentDictionary());
+ private SqlToolsSettings Settings { get { return WorkspaceService.Instance.CurrentSettings; } }
+
#endregion
///
@@ -81,6 +85,13 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
Dispose();
return Task.FromResult(0);
});
+
+ // Register a handler for when the configuration changes
+ WorkspaceService.Instance.RegisterConfigChangeCallback((oldSettings, newSettings, eventContext) =>
+ {
+ Settings.QueryExecutionSettings.Update(newSettings.QueryExecutionSettings);
+ return Task.FromResult(0);
+ });
}
#region Request Handlers
@@ -123,7 +134,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
var result = new QueryExecuteSubsetResult
{
Message = null,
- ResultSubset = query.GetSubset(
+ ResultSubset = query.GetSubset(subsetParams.BatchIndex,
subsetParams.ResultSetIndex, subsetParams.RowsStartIndex, subsetParams.RowsCount)
};
await requestContext.SendResult(result);
@@ -251,8 +262,11 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
ActiveQueries.TryRemove(executeParams.OwnerUri, out oldQuery);
}
+ // Retrieve the current settings for executing the query with
+ QueryExecutionSettings settings = WorkspaceService.Instance.CurrentSettings.QueryExecutionSettings;
+
// If we can't add the query now, it's assumed the query is in progress
- Query newQuery = new Query(executeParams.QueryText, connectionInfo);
+ Query newQuery = new Query(executeParams.QueryText, connectionInfo, settings);
if (!ActiveQueries.TryAdd(executeParams.OwnerUri, newQuery))
{
await requestContext.SendResult(new QueryExecuteResult
@@ -292,10 +306,8 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
await Task.WhenAll(executeTask);
QueryExecuteCompleteParams eventParams = new QueryExecuteCompleteParams
{
- HasError = query.HasError,
- Messages = query.ResultMessages.ToArray(),
OwnerUri = executeParams.OwnerUri,
- ResultSetSummaries = query.ResultSummary
+ BatchSummaries = query.BatchSummaries
};
await requestContext.SendEvent(QueryExecuteCompleteEvent.Type, eventParams);
}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/ResultSet.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/ResultSet.cs
index fed08ea3..058dc54c 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/ResultSet.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/ResultSet.cs
@@ -3,8 +3,11 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
+using System;
using System.Collections.Generic;
using System.Data.Common;
+using System.Linq;
+using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
{
@@ -33,5 +36,33 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
}
Rows.Add(row.ToArray());
}
+
+ ///
+ /// Generates a subset of the rows from the result set
+ ///
+ /// The starting row of the results
+ /// How many rows to retrieve
+ /// A subset of results
+ public ResultSetSubset GetSubset(int startRow, int rowCount)
+ {
+ // Sanity check to make sure that the row and the row count are within bounds
+ if (startRow < 0 || startRow >= Rows.Count)
+ {
+ throw new ArgumentOutOfRangeException(nameof(startRow), "Start row cannot be less than 0 " +
+ "or greater than the number of rows in the resultset");
+ }
+ if (rowCount <= 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(rowCount), "Row count must be a positive integer");
+ }
+
+ // Retrieve the subset of the results as per the request
+ object[][] rows = Rows.Skip(startRow).Take(rowCount).ToArray();
+ return new ResultSetSubset
+ {
+ Rows = rows,
+ RowCount = rows.Length
+ };
+ }
}
}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/SqlContext/QueryExecutionSettings.cs b/src/Microsoft.SqlTools.ServiceLayer/SqlContext/QueryExecutionSettings.cs
new file mode 100644
index 00000000..4934a4da
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/SqlContext/QueryExecutionSettings.cs
@@ -0,0 +1,38 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+namespace Microsoft.SqlTools.ServiceLayer.SqlContext
+{
+ ///
+ /// Collection of settings related to the execution of queries
+ ///
+ public class QueryExecutionSettings
+ {
+ ///
+ /// Default value for batch separator (de facto standard as per SSMS)
+ ///
+ private const string DefaultBatchSeparator = "GO";
+
+ private string batchSeparator;
+
+ ///
+ /// The configured batch separator, will use a default if a value was not configured
+ ///
+ public string BatchSeparator
+ {
+ get { return batchSeparator ?? DefaultBatchSeparator; }
+ set { batchSeparator = value; }
+ }
+
+ ///
+ /// Update the current settings with the new settings
+ ///
+ /// The new settings
+ public void Update(QueryExecutionSettings newSettings)
+ {
+ BatchSeparator = newSettings.BatchSeparator;
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/SqlContext/SqlToolsSettings.cs b/src/Microsoft.SqlTools.ServiceLayer/SqlContext/SqlToolsSettings.cs
index 07ea0ffe..198884f2 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/SqlContext/SqlToolsSettings.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/SqlContext/SqlToolsSettings.cs
@@ -17,6 +17,7 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlContext
public SqlToolsSettings()
{
this.ScriptAnalysis = new ScriptAnalysisSettings();
+ this.QueryExecutionSettings = new QueryExecutionSettings();
}
public bool EnableProfileLoading { get; set; }
@@ -31,6 +32,8 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlContext
this.ScriptAnalysis.Update(settings.ScriptAnalysis, workspaceRootPath);
}
}
+
+ public QueryExecutionSettings QueryExecutionSettings { get; set; }
}
///
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Workspace/WorkspaceService.cs b/src/Microsoft.SqlTools.ServiceLayer/Workspace/WorkspaceService.cs
index f47cacb9..939a4ab5 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/Workspace/WorkspaceService.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/Workspace/WorkspaceService.cs
@@ -44,6 +44,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace
ConfigChangeCallbacks = new List();
TextDocChangeCallbacks = new List();
TextDocOpenCallbacks = new List();
+
+ CurrentSettings = new TConfig();
}
#endregion
@@ -52,7 +54,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace
public Workspace Workspace { get; private set; }
- public TConfig CurrentSettings { get; private set; }
+ public TConfig CurrentSettings { get; internal set; }
///
/// Delegate for callbacks that occur when the configuration for the workspace changes
@@ -101,7 +103,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace
{
// Create a workspace that will handle state for the session
Workspace = new Workspace();
- CurrentSettings = new TConfig();
// Register the handlers for when changes to the workspae occur
serviceHost.SetEventHandler(DidChangeTextDocumentNotification.Type, HandleDidChangeTextDocumentNotification);
diff --git a/src/Microsoft.SqlTools.ServiceLayer/project.json b/src/Microsoft.SqlTools.ServiceLayer/project.json
index 0cfc0788..1636feae 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/project.json
+++ b/src/Microsoft.SqlTools.ServiceLayer/project.json
@@ -7,7 +7,7 @@
},
"dependencies": {
"Newtonsoft.Json": "9.0.1",
- "Microsoft.SqlServer.SqlParser": "140.1.4",
+ "Microsoft.SqlServer.SqlParser": "140.1.5",
"System.Data.Common": "4.1.0",
"System.Data.SqlClient": "4.1.0"
},
diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/CancelTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/CancelTests.cs
index dbc344f8..05df93b9 100644
--- a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/CancelTests.cs
+++ b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/CancelTests.cs
@@ -20,7 +20,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
// If:
// ... I request a query (doesn't matter what kind) and execute it
var queryService = Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true);
- var executeParams = new QueryExecuteParams { QueryText = "Doesn't Matter", OwnerUri = Common.OwnerUri };
+ var executeParams = new QueryExecuteParams { QueryText = Common.StandardQuery, OwnerUri = Common.OwnerUri };
var executeRequest = Common.GetQueryExecuteResultContextMock(null, null, null);
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait();
queryService.ActiveQueries[Common.OwnerUri].HasExecuted = false; // Fake that it hasn't completed execution
@@ -46,7 +46,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
// If:
// ... I request a query (doesn't matter what kind) and wait for execution
var queryService = Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true);
- var executeParams = new QueryExecuteParams {QueryText = "Doesn't Matter", OwnerUri = Common.OwnerUri};
+ var executeParams = new QueryExecuteParams {QueryText = Common.StandardQuery, OwnerUri = Common.OwnerUri};
var executeRequest = Common.GetQueryExecuteResultContextMock(null, null, null);
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait();
diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Common.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Common.cs
index 8b3b4286..6d265de2 100644
--- a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Common.cs
+++ b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Common.cs
@@ -1,7 +1,13 @@
-using System;
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
+using System.Threading;
using System.Threading.Tasks;
using Microsoft.SqlTools.ServiceLayer.Connection;
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
@@ -9,6 +15,7 @@ using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
+using Microsoft.SqlTools.ServiceLayer.SqlContext;
using Microsoft.SqlTools.ServiceLayer.Test.Utility;
using Moq;
using Moq.Protected;
@@ -17,16 +24,22 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
{
public class Common
{
+ public const string StandardQuery = "SELECT * FROM sys.objects";
+
+ public const string InvalidQuery = "SELECT *** FROM sys.objects";
+
+ public const string NoOpQuery = "-- No ops here, just us chickens.";
+
public const string OwnerUri = "testFile";
- public static readonly Dictionary[] StandardTestData =
+ public const int StandardRows = 5;
+
+ public const int StandardColumns = 5;
+
+ public static Dictionary[] StandardTestData
{
- new Dictionary { {"col1", "val11"}, { "col2", "val12"}, { "col3", "val13"}, { "col4", "col14"} },
- new Dictionary { {"col1", "val21"}, { "col2", "val22"}, { "col3", "val23"}, { "col4", "col24"} },
- new Dictionary { {"col1", "val31"}, { "col2", "val32"}, { "col3", "val33"}, { "col4", "col34"} },
- new Dictionary { {"col1", "val41"}, { "col2", "val42"}, { "col3", "val43"}, { "col4", "col44"} },
- new Dictionary { {"col1", "val51"}, { "col2", "val52"}, { "col3", "val53"}, { "col4", "col54"} },
- };
+ get { return GetTestData(StandardRows, StandardColumns); }
+ }
public static Dictionary[] GetTestData(int columns, int rows)
{
@@ -44,9 +57,17 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
return output;
}
+ public static Batch GetBasicExecutedBatch()
+ {
+ Batch batch = new Batch(StandardQuery, 1);
+ batch.Execute(CreateTestConnection(new[] {StandardTestData}, false), CancellationToken.None).Wait();
+ return batch;
+ }
+
public static Query GetBasicExecutedQuery()
{
- Query query = new Query("SIMPLE QUERY", CreateTestConnectionInfo(new[] { StandardTestData }, false));
+ ConnectionInfo ci = CreateTestConnectionInfo(new[] {StandardTestData}, false);
+ Query query = new Query(StandardQuery, ci, new QueryExecutionSettings());
query.Execute().Wait();
return query;
}
diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/DisposeTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/DisposeTests.cs
index c0fed697..8ff0affd 100644
--- a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/DisposeTests.cs
+++ b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/DisposeTests.cs
@@ -1,4 +1,9 @@
-using System;
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+using System;
using System.Threading.Tasks;
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/ExecuteTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/ExecuteTests.cs
index 9ed19c50..ee98f8a2 100644
--- a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/ExecuteTests.cs
+++ b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/ExecuteTests.cs
@@ -1,9 +1,20 @@
-using System;
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+using System;
+using System.Data.Common;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
using Microsoft.SqlTools.ServiceLayer.Connection;
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
+using Microsoft.SqlTools.ServiceLayer.SqlContext;
+using Microsoft.SqlTools.ServiceLayer.Workspace;
using Moq;
using Xunit;
@@ -11,191 +22,206 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
{
public class ExecuteTests
{
- #region Query Class Tests
+ #region Batch Class Tests
[Fact]
- public void QueryCreationTest()
+ public void BatchCreationTest()
{
- // If I create a new query...
- Query query = new Query("NO OP", Common.CreateTestConnectionInfo(null, false));
+ // If I create a new batch...
+ Batch batch = new Batch(Common.StandardQuery, 1);
// Then:
- // ... It should not have executed
- Assert.False(query.HasExecuted, "The query should not have executed.");
+ // ... The text of the batch should be stored
+ Assert.NotEmpty(batch.BatchText);
+
+ // ... It should not have executed and no error
+ Assert.False(batch.HasExecuted, "The query should not have executed.");
+ Assert.False(batch.HasError, "The batch should not have an error");
// ... The results should be empty
- Assert.Empty(query.ResultSets);
- Assert.Empty(query.ResultSummary);
+ Assert.Empty(batch.ResultSets);
+ Assert.Empty(batch.ResultSummaries);
+ Assert.Empty(batch.ResultMessages);
+
+ // ... The start line of the batch should be 0
+ Assert.Equal(0, batch.StartLine);
}
[Fact]
- public void QueryExecuteNoResultSets()
+ public void BatchExecuteNoResultSets()
{
// If I execute a query that should get no result sets
- Query query = new Query("Query with no result sets", Common.CreateTestConnectionInfo(null, false));
- query.Execute().Wait();
+ Batch batch = new Batch(Common.StandardQuery, 1);
+ batch.Execute(GetConnection(Common.CreateTestConnectionInfo(null, false)), CancellationToken.None).Wait();
// Then:
// ... It should have executed without error
- Assert.True(query.HasExecuted, "The query should have been marked executed.");
- Assert.False(query.HasError);
-
+ Assert.True(batch.HasExecuted, "The query should have been marked executed.");
+ Assert.False(batch.HasError, "The batch should not have an error");
+
// ... The results should be empty
- Assert.Empty(query.ResultSets);
- Assert.Empty(query.ResultSummary);
+ Assert.Empty(batch.ResultSets);
+ Assert.Empty(batch.ResultSummaries);
// ... The results should not be null
- Assert.NotNull(query.ResultSets);
- Assert.NotNull(query.ResultSummary);
+ Assert.NotNull(batch.ResultSets);
+ Assert.NotNull(batch.ResultSummaries);
// ... There should be a message for how many rows were affected
- Assert.Equal(1, query.ResultMessages.Count);
+ Assert.Equal(1, batch.ResultMessages.Count());
}
[Fact]
- public void QueryExecuteQueryOneResultSet()
+ public void BatchExecuteOneResultSet()
{
- ConnectionInfo ci = Common.CreateTestConnectionInfo(new[] {Common.StandardTestData}, false);
+ int resultSets = 1;
+ ConnectionInfo ci = Common.CreateTestConnectionInfo(new[] { Common.StandardTestData }, false);
// If I execute a query that should get one result set
- int resultSets = 1;
- int rows = 5;
- int columns = 4;
- Query query = new Query("Query with one result sets", ci);
- query.Execute().Wait();
+ Batch batch = new Batch(Common.StandardQuery, 1);
+ batch.Execute(GetConnection(ci), CancellationToken.None).Wait();
// Then:
// ... It should have executed without error
- Assert.True(query.HasExecuted, "The query should have been marked executed.");
- Assert.False(query.HasError);
+ Assert.True(batch.HasExecuted, "The batch should have been marked executed.");
+ Assert.False(batch.HasError, "The batch should not have an error");
// ... There should be exactly one result set
- Assert.Equal(resultSets, query.ResultSets.Count);
+ Assert.Equal(resultSets, batch.ResultSets.Count());
+ Assert.Equal(resultSets, batch.ResultSummaries.Length);
// ... Inside the result set should be with 5 rows
- Assert.Equal(rows, query.ResultSets[0].Rows.Count);
+ Assert.Equal(Common.StandardRows, batch.ResultSets.First().Rows.Count);
+ Assert.Equal(Common.StandardRows, batch.ResultSummaries[0].RowCount);
// ... Inside the result set should have 5 columns and 5 column definitions
- Assert.Equal(columns, query.ResultSets[0].Rows[0].Length);
- Assert.Equal(columns, query.ResultSets[0].Columns.Length);
-
- // ... There should be exactly one result set summary
- Assert.Equal(resultSets, query.ResultSummary.Length);
-
- // ... Inside the result summary, there should be 5 column definitions
- Assert.Equal(columns, query.ResultSummary[0].ColumnInfo.Length);
-
- // ... Inside the result summary, there should be 5 rows
- Assert.Equal(rows, query.ResultSummary[0].RowCount);
+ Assert.Equal(Common.StandardColumns, batch.ResultSets.First().Rows[0].Length);
+ Assert.Equal(Common.StandardColumns, batch.ResultSets.First().Columns.Length);
+ Assert.Equal(Common.StandardColumns, batch.ResultSummaries[0].ColumnInfo.Length);
// ... There should be a message for how many rows were affected
- Assert.Equal(resultSets, query.ResultMessages.Count);
+ Assert.Equal(resultSets, batch.ResultMessages.Count());
}
[Fact]
- public void QueryExecuteQueryTwoResultSets()
+ public void BatchExecuteTwoResultSets()
{
- var dataset = new[] {Common.StandardTestData, Common.StandardTestData};
+ var dataset = new[] { Common.StandardTestData, Common.StandardTestData };
int resultSets = dataset.Length;
- int rows = Common.StandardTestData.Length;
- int columns = Common.StandardTestData[0].Count;
ConnectionInfo ci = Common.CreateTestConnectionInfo(dataset, false);
// If I execute a query that should get two result sets
- Query query = new Query("Query with two result sets", ci);
- query.Execute().Wait();
+ Batch batch = new Batch(Common.StandardQuery, 1);
+ batch.Execute(GetConnection(ci), CancellationToken.None).Wait();
// Then:
// ... It should have executed without error
- Assert.True(query.HasExecuted, "The query should have been marked executed.");
- Assert.False(query.HasError);
+ Assert.True(batch.HasExecuted, "The batch should have been marked executed.");
+ Assert.False(batch.HasError, "The batch should not have an error");
// ... There should be exactly two result sets
- Assert.Equal(resultSets, query.ResultSets.Count);
+ Assert.Equal(resultSets, batch.ResultSets.Count());
- foreach (ResultSet rs in query.ResultSets)
+ foreach (ResultSet rs in batch.ResultSets)
{
// ... Each result set should have 5 rows
- Assert.Equal(rows, rs.Rows.Count);
+ Assert.Equal(Common.StandardRows, rs.Rows.Count);
// ... Inside each result set should be 5 columns and 5 column definitions
- Assert.Equal(columns, rs.Rows[0].Length);
- Assert.Equal(columns, rs.Columns.Length);
+ Assert.Equal(Common.StandardColumns, rs.Rows[0].Length);
+ Assert.Equal(Common.StandardColumns, rs.Columns.Length);
}
// ... There should be exactly two result set summaries
- Assert.Equal(resultSets, query.ResultSummary.Length);
+ Assert.Equal(resultSets, batch.ResultSummaries.Length);
- foreach (ResultSetSummary rs in query.ResultSummary)
+ foreach (ResultSetSummary rs in batch.ResultSummaries)
{
- // ... Inside each result summary, there should be 5 column definitions
- Assert.Equal(columns, rs.ColumnInfo.Length);
-
// ... Inside each result summary, there should be 5 rows
- Assert.Equal(rows, rs.RowCount);
+ Assert.Equal(Common.StandardRows, rs.RowCount);
+
+ // ... Inside each result summary, there should be 5 column definitions
+ Assert.Equal(Common.StandardColumns, rs.ColumnInfo.Length);
}
// ... There should be a message for how many rows were affected
- Assert.Equal(resultSets, query.ResultMessages.Count);
+ Assert.Equal(resultSets, batch.ResultMessages.Count());
}
[Fact]
- public void QueryExecuteInvalidQuery()
+ public void BatchExecuteInvalidQuery()
{
ConnectionInfo ci = Common.CreateTestConnectionInfo(null, true);
- // If I execute a query that is invalid
- Query query = new Query("Invalid query", ci);
- query.Execute().Wait();
+ // If I execute a batch that is invalid
+ Batch batch = new Batch(Common.StandardQuery, 1);
+ batch.Execute(GetConnection(ci), CancellationToken.None).Wait();
// Then:
// ... It should have executed with error
- Assert.True(query.HasExecuted);
- Assert.True(query.HasError);
-
- // ... There should be plenty of messages for the eror
- Assert.NotEmpty(query.ResultMessages);
+ Assert.True(batch.HasExecuted);
+ Assert.True(batch.HasError);
+
+ // ... There should be no result sets
+ Assert.Empty(batch.ResultSets);
+ Assert.Empty(batch.ResultSummaries);
+
+ // ... There should be plenty of messages for the error
+ Assert.NotEmpty(batch.ResultMessages);
}
[Fact]
- public void QueryExecuteExecutedQuery()
+ public async Task BatchExecuteExecuted()
{
- ConnectionInfo ci = Common.CreateTestConnectionInfo(new[] {Common.StandardTestData}, false);
+ ConnectionInfo ci = Common.CreateTestConnectionInfo(new[] { Common.StandardTestData }, false);
- // If I execute a query
- Query query = new Query("Any query", ci);
- query.Execute().Wait();
+ // If I execute a batch
+ Batch batch = new Batch(Common.StandardQuery, 1);
+ batch.Execute(GetConnection(ci), CancellationToken.None).Wait();
// Then:
// ... It should have executed without error
- Assert.True(query.HasExecuted, "The query should have been marked executed.");
- Assert.False(query.HasError);
+ Assert.True(batch.HasExecuted, "The batch should have been marked executed.");
+ Assert.False(batch.HasError, "The batch should not have an error");
// If I execute it again
// Then:
- // ... It should throw an invalid operation exception wrapped in an aggregate exception
- AggregateException ae = Assert.Throws(() => query.Execute().Wait());
- Assert.Equal(1, ae.InnerExceptions.Count);
- Assert.IsType(ae.InnerExceptions[0]);
+ // ... It should throw an invalid operation exception
+ await Assert.ThrowsAsync(() =>
+ batch.Execute(GetConnection(ci), CancellationToken.None));
// ... The data should still be available without error
- Assert.False(query.HasError);
- Assert.True(query.HasExecuted, "The query should still be marked executed.");
- Assert.NotEmpty(query.ResultSets);
- Assert.NotEmpty(query.ResultSummary);
+ Assert.False(batch.HasError, "The batch should not be in an error condition");
+ Assert.True(batch.HasExecuted, "The batch should still be marked executed.");
+ Assert.NotEmpty(batch.ResultSets);
+ Assert.NotEmpty(batch.ResultSummaries);
}
[Theory]
[InlineData("")]
- [InlineData(" ")]
[InlineData(null)]
- public void QueryExecuteNoQuery(string query)
+ public void BatchExecuteNoSql(string query)
{
// If:
- // ... I create a query that has an empty query
+ // ... I create a batch that has an empty query
// Then:
// ... It should throw an exception
- Assert.Throws(() => new Query(query, null));
+ Assert.Throws(() => new Batch(query, 1));
+ }
+
+ #endregion
+
+ #region Query Class Tests
+
+ [Fact]
+ public void QueryExecuteNoQueryText()
+ {
+ // If:
+ // ... I create a query that has a null query text
+ // Then:
+ // ... It should throw an exception
+ Assert.Throws(() =>
+ new Query(null, Common.CreateTestConnectionInfo(null, false), new QueryExecutionSettings()));
}
[Fact]
@@ -205,7 +231,154 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
// ... I create a query that has a null connection info
// Then:
// ... It should throw an exception
- Assert.Throws(() => new Query("Some Query", null));
+ Assert.Throws(() => new Query("Some Query", null, new QueryExecutionSettings()));
+ }
+
+ [Fact]
+ public void QueryExecuteNoSettings()
+ {
+ // If:
+ // ... I create a query that has a null settings
+ // Then:
+ // ... It should throw an exception
+ Assert.Throws(() =>
+ new Query("Some query", Common.CreateTestConnectionInfo(null, false), null));
+ }
+
+ [Fact]
+ public void QueryExecuteSingleBatch()
+ {
+ // If:
+ // ... I create a query from a single batch (without separator)
+ ConnectionInfo ci = Common.CreateTestConnectionInfo(null, false);
+ Query query = new Query(Common.StandardQuery, ci, new QueryExecutionSettings());
+
+ // Then:
+ // ... I should get a single batch to execute that hasn't been executed
+ Assert.NotEmpty(query.QueryText);
+ Assert.NotEmpty(query.Batches);
+ Assert.Equal(1, query.Batches.Length);
+ Assert.False(query.HasExecuted);
+ Assert.Throws(() => query.BatchSummaries);
+
+ // If:
+ // ... I then execute the query
+ query.Execute().Wait();
+
+ // Then:
+ // ... The query should have completed successfully with one batch summary returned
+ Assert.True(query.HasExecuted);
+ Assert.NotEmpty(query.BatchSummaries);
+ Assert.Equal(1, query.BatchSummaries.Length);
+ }
+
+ [Fact]
+ public void QueryExecuteNoOpBatch()
+ {
+ // If:
+ // ... I create a query from a single batch that does nothing
+ ConnectionInfo ci = Common.CreateTestConnectionInfo(null, false);
+ Query query = new Query(Common.NoOpQuery, ci, new QueryExecutionSettings());
+
+ // Then:
+ // ... I should get no batches back
+ Assert.NotEmpty(query.QueryText);
+ Assert.Empty(query.Batches);
+ Assert.False(query.HasExecuted);
+ Assert.Throws(() => query.BatchSummaries);
+
+ // If:
+ // ... I Then execute the query
+ query.Execute().Wait();
+
+ // Then:
+ // ... The query should have completed successfully with no batch summaries returned
+ Assert.True(query.HasExecuted);
+ Assert.Empty(query.BatchSummaries);
+ }
+
+ [Fact]
+ public void QueryExecuteMultipleBatches()
+ {
+ // If:
+ // ... I create a query from two batches (with separator)
+ ConnectionInfo ci = Common.CreateTestConnectionInfo(null, false);
+ string queryText = string.Format("{0}\r\nGO\r\n{0}", Common.StandardQuery);
+ Query query = new Query(queryText, ci, new QueryExecutionSettings());
+
+ // Then:
+ // ... I should get back two batches to execute that haven't been executed
+ Assert.NotEmpty(query.QueryText);
+ Assert.NotEmpty(query.Batches);
+ Assert.Equal(2, query.Batches.Length);
+ Assert.False(query.HasExecuted);
+ Assert.Throws(() => query.BatchSummaries);
+
+ // If:
+ // ... I then execute the query
+ query.Execute().Wait();
+
+ // Then:
+ // ... The query should have completed successfully with two batch summaries returned
+ Assert.True(query.HasExecuted);
+ Assert.NotEmpty(query.BatchSummaries);
+ Assert.Equal(2, query.BatchSummaries.Length);
+ }
+
+ [Fact]
+ public void QueryExecuteMultipleBatchesWithNoOp()
+ {
+ // If:
+ // ... I create a query from a two batches (with separator)
+ ConnectionInfo ci = Common.CreateTestConnectionInfo(null, false);
+ string queryText = string.Format("{0}\r\nGO\r\n{1}", Common.StandardQuery, Common.NoOpQuery);
+ Query query = new Query(queryText, ci, new QueryExecutionSettings());
+
+ // Then:
+ // ... I should get back one batch to execute that hasn't been executed
+ Assert.NotEmpty(query.QueryText);
+ Assert.NotEmpty(query.Batches);
+ Assert.Equal(1, query.Batches.Length);
+ Assert.False(query.HasExecuted);
+ Assert.Throws(() => query.BatchSummaries);
+
+ // If:
+ // .. I then execute the query
+ query.Execute().Wait();
+
+ // ... The query should have completed successfully with one batch summary returned
+ Assert.True(query.HasExecuted);
+ Assert.NotEmpty(query.BatchSummaries);
+ Assert.Equal(1, query.BatchSummaries.Length);
+ }
+
+ [Fact]
+ public void QueryExecuteInvalidBatch()
+ {
+ // If:
+ // ... I create a query from an invalid batch
+ ConnectionInfo ci = Common.CreateTestConnectionInfo(null, true);
+ Query query = new Query(Common.InvalidQuery, ci, new QueryExecutionSettings());
+
+ // Then:
+ // ... I should get back a query with one batch not executed
+ Assert.NotEmpty(query.QueryText);
+ Assert.NotEmpty(query.Batches);
+ Assert.Equal(1, query.Batches.Length);
+ Assert.False(query.HasExecuted);
+ Assert.Throws(() => query.BatchSummaries);
+
+ // If:
+ // ... I then execute the query
+ query.Execute().Wait();
+
+ // Then:
+ // ... There should be an error on the batch
+ Assert.True(query.HasExecuted);
+ Assert.NotEmpty(query.BatchSummaries);
+ Assert.Equal(1, query.BatchSummaries.Length);
+ Assert.True(query.BatchSummaries[0].HasError);
+ Assert.NotEmpty(query.BatchSummaries[0].Messages);
}
#endregion
@@ -215,10 +388,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
[Fact]
public void QueryExecuteValidNoResultsTest()
{
+ // Given:
+ // ... Default settings are stored in the workspace service
+ WorkspaceService.Instance.CurrentSettings = new SqlToolsSettings();
+
// If:
// ... I request to execute a valid query with no results
var queryService = Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true);
- var queryParams = new QueryExecuteParams { QueryText = "Doesn't Matter", OwnerUri = Common.OwnerUri };
+ var queryParams = new QueryExecuteParams { QueryText = Common.StandardQuery, OwnerUri = Common.OwnerUri };
QueryExecuteResult result = null;
QueryExecuteCompleteParams completeParams = null;
@@ -227,14 +404,15 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
// Then:
// ... No Errors should have been sent
- // ... A successful result should have been sent with messages
+ // ... A successful result should have been sent with messages on the first batch
// ... A completion event should have been fired with empty results
- // ... There should be one active query
VerifyQueryExecuteCallCount(requestContext, Times.Once(), Times.Once(), Times.Never());
Assert.Null(result.Messages);
- Assert.NotEmpty(completeParams.Messages);
- Assert.Empty(completeParams.ResultSetSummaries);
- Assert.False(completeParams.HasError);
+ Assert.Equal(1, completeParams.BatchSummaries.Length);
+ Assert.Empty(completeParams.BatchSummaries[0].ResultSetSummaries);
+ Assert.NotEmpty(completeParams.BatchSummaries[0].Messages);
+
+ // ... There should be one active query
Assert.Equal(1, queryService.ActiveQueries.Count);
}
@@ -244,7 +422,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
// If:
// ... I request to execute a valid query with results
var queryService = Common.GetPrimedExecutionService(Common.CreateMockFactory(new[] { Common.StandardTestData }, false), true);
- var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QueryText = "Doesn't Matter" };
+ var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QueryText = Common.StandardQuery };
QueryExecuteResult result = null;
QueryExecuteCompleteParams completeParams = null;
@@ -255,12 +433,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
// ... No errors should have been sent
// ... A successful result should have been sent with messages
// ... A completion event should have been fired with one result
- // ... There should be one active query
VerifyQueryExecuteCallCount(requestContext, Times.Once(), Times.Once(), Times.Never());
Assert.Null(result.Messages);
- Assert.NotEmpty(completeParams.Messages);
- Assert.NotEmpty(completeParams.ResultSetSummaries);
- Assert.False(completeParams.HasError);
+ Assert.Equal(1, completeParams.BatchSummaries.Length);
+ Assert.NotEmpty(completeParams.BatchSummaries[0].ResultSetSummaries);
+ Assert.NotEmpty(completeParams.BatchSummaries[0].Messages);
+ Assert.False(completeParams.BatchSummaries[0].HasError);
+
+ // ... There should be one active query
Assert.Equal(1, queryService.ActiveQueries.Count);
}
@@ -270,7 +450,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
// If:
// ... I request to execute a query using a file URI that isn't connected
var queryService = Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), false);
- var queryParams = new QueryExecuteParams { OwnerUri = "notConnected", QueryText = "Doesn't Matter" };
+ var queryParams = new QueryExecuteParams { OwnerUri = "notConnected", QueryText = Common.StandardQuery };
QueryExecuteResult result = null;
var requestContext = Common.GetQueryExecuteResultContextMock(qer => result = qer, null, null);
@@ -293,7 +473,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
// If:
// ... I request to execute a query
var queryService = Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true);
- var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QueryText = "Some Query" };
+ var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QueryText = Common.StandardQuery };
// Note, we don't care about the results of the first request
var firstRequestContext = Common.GetQueryExecuteResultContextMock(null, null, null);
@@ -322,7 +502,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
// If:
// ... I request to execute a query
var queryService = Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true);
- var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QueryText = "Some Query" };
+ var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QueryText = Common.StandardQuery };
// Note, we don't care about the results of the first request
var firstRequestContext = Common.GetQueryExecuteResultContextMock(null, null, null);
@@ -340,7 +520,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
// ... There should only be one active query
VerifyQueryExecuteCallCount(secondRequestContext, Times.Once(), Times.Once(), Times.Never());
Assert.Null(result.Messages);
- Assert.False(complete.HasError);
+ Assert.False(complete.BatchSummaries.Any(b => b.HasError));
Assert.Equal(1, queryService.ActiveQueries.Count);
}
@@ -376,7 +556,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
// If:
// ... I request to execute a query that is invalid
var queryService = Common.GetPrimedExecutionService(Common.CreateMockFactory(null, true), true);
- var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QueryText = "Bad query!" };
+ var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QueryText = Common.StandardQuery };
QueryExecuteResult result = null;
QueryExecuteCompleteParams complete = null;
@@ -389,8 +569,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
// ... A completion event should have been sent with error
VerifyQueryExecuteCallCount(requestContext, Times.Once(), Times.Once(), Times.Never());
Assert.Null(result.Messages);
- Assert.True(complete.HasError);
- Assert.NotEmpty(complete.Messages);
+ Assert.Equal(1, complete.BatchSummaries.Length);
+ Assert.True(complete.BatchSummaries[0].HasError);
+ Assert.NotEmpty(complete.BatchSummaries[0].Messages);
}
#endregion
@@ -403,5 +584,10 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
It.IsAny()), sendEventCalls);
mock.Verify(rc => rc.SendError(It.IsAny