diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Batch.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Batch.cs
new file mode 100644
index 00000000..95a418c9
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Batch.cs
@@ -0,0 +1,195 @@
+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
+{
+ 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 the query has an error
+ ///
+ public bool HasError { get; set; }
+
+ public bool HasExecuted { 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[] ResultSummaries
+ {
+ get
+ {
+ return ResultSets.Select((set, index) => new ResultSetSummary()
+ {
+ ColumnInfo = set.Columns,
+ Id = index,
+ RowCount = set.Rows.Count
+ }).ToArray();
+ }
+ }
+
+ #endregion
+
+ public Batch(string batchText)
+ {
+ // Sanity check for input
+ if (string.IsNullOrEmpty(batchText))
+ {
+ throw new ArgumentNullException(nameof(batchText), "Query text cannot be null");
+ }
+
+ // Initialize the internal state
+ BatchText = batchText;
+ HasExecuted = false;
+ ResultSets = new List();
+ ResultMessages = new List();
+ }
+
+ public async Task Execute(DbConnection conn, CancellationToken cancellationToken)
+ {
+ 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)
+ : "Commad Executed 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);
+ } 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;
+ }
+ }
+
+ #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)
+ {
+ 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);
+ }
+ }
+
+ #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..8b6303be 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/QueryExecuteCompleteNotification.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/QueryExecuteCompleteNotification.cs
@@ -22,15 +22,10 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
///
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/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 d9a886d4..829741af 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Query.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Query.cs
@@ -5,12 +5,11 @@
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;
@@ -23,6 +22,25 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
{
#region Properties
+ ///
+ /// The batches underneath this query
+ ///
+ private IEnumerable Batches { get; set; }
+
+ ///
+ /// The summaries of the batches underneath this query
+ ///
+ public BatchSummary[] BatchSummaries
+ {
+ get { 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,47 +52,19 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
///
public ConnectionInfo EditorConnection { get; set; }
- ///
- /// Whether or not the query has an error
- ///
- public bool HasError { get; set; }
-
///
/// Whether or not the query has completed executed, regardless of success or failure
///
- public bool HasExecuted { get; set; }
+ public bool HasExecuted
+ {
+ get { return Batches.All(b => b.HasExecuted); }
+ }
///
/// 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
///
@@ -97,10 +87,11 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
// 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);
+ Batches = parseResult.Script.Batches.Select(b => new Batch(b.Sql));
}
///
@@ -114,80 +105,16 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
throw new InvalidOperationException("Query has already executed.");
}
- DbConnection conn = null;
-
- // 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 (conn = EditorConnection.Factory.CreateSqlConnection(connectionString))
+ // 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
- {
- // Create a message with the number of affected rows
- if (reader.RecordsAffected >= 0)
- {
- ResultMessages.Add(String.Format("({0} row(s) affected)", reader.RecordsAffected));
- }
-
- if (!reader.HasRows && reader.FieldCount == 0)
- {
- 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);
- } 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;
- }
}
///
@@ -246,48 +173,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;