mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-02-17 02:51:45 -05:00
Merge branch 'dev' into feature/testSmo
This commit is contained in:
@@ -155,7 +155,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
// that are not backed by a SQL connection
|
// that are not backed by a SQL connection
|
||||||
ConnectionInfo info;
|
ConnectionInfo info;
|
||||||
IntellisenseCache cache;
|
IntellisenseCache cache;
|
||||||
if (ConnectionServiceInstance.TryFindConnection(textDocumentPosition.Uri, out info)
|
if (ConnectionServiceInstance.TryFindConnection(textDocumentPosition.TextDocument.Uri, out info)
|
||||||
&& caches.TryGetValue((ConnectionSummary)info.ConnectionDetails, out cache))
|
&& caches.TryGetValue((ConnectionSummary)info.ConnectionDetails, out cache))
|
||||||
{
|
{
|
||||||
return cache.GetAutoCompleteItems(textDocumentPosition).ToArray();
|
return cache.GetAutoCompleteItems(textDocumentPosition).ToArray();
|
||||||
|
|||||||
@@ -269,7 +269,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static async Task HandleDocumentSymbolRequest(
|
private static async Task HandleDocumentSymbolRequest(
|
||||||
TextDocumentIdentifier textDocumentIdentifier,
|
DocumentSymbolParams documentSymbolParams,
|
||||||
RequestContext<SymbolInformation[]> requestContext)
|
RequestContext<SymbolInformation[]> requestContext)
|
||||||
{
|
{
|
||||||
Logger.Write(LogLevel.Verbose, "HandleDocumentSymbolRequest");
|
Logger.Write(LogLevel.Verbose, "HandleDocumentSymbolRequest");
|
||||||
|
|||||||
263
src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Batch.cs
Normal file
263
src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Batch.cs
Normal file
@@ -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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class represents a batch within a query
|
||||||
|
/// </summary>
|
||||||
|
public class Batch
|
||||||
|
{
|
||||||
|
private const string RowsAffectedFormat = "({0} row(s) affected)";
|
||||||
|
|
||||||
|
#region Properties
|
||||||
|
/// <summary>
|
||||||
|
/// The text of batch that will be executed
|
||||||
|
/// </summary>
|
||||||
|
public string BatchText { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether or not this batch has an error
|
||||||
|
/// </summary>
|
||||||
|
public bool HasError { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether or not this batch has been executed, regardless of success or failure
|
||||||
|
/// </summary>
|
||||||
|
public bool HasExecuted { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal representation of the messages so we can modify internally
|
||||||
|
/// </summary>
|
||||||
|
private List<string> resultMessages;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Messages that have come back from the server
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<string> ResultMessages
|
||||||
|
{
|
||||||
|
get { return resultMessages; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal representation of the result sets so we can modify internally
|
||||||
|
/// </summary>
|
||||||
|
private List<ResultSet> resultSets;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The result sets of the batch execution
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<ResultSet> ResultSets
|
||||||
|
{
|
||||||
|
get { return resultSets; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Property for generating a set result set summaries from the result sets
|
||||||
|
/// </summary>
|
||||||
|
public ResultSetSummary[] ResultSummaries
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return ResultSets.Select((set, index) => new ResultSetSummary()
|
||||||
|
{
|
||||||
|
ColumnInfo = set.Columns,
|
||||||
|
Id = index,
|
||||||
|
RowCount = set.Rows.Count
|
||||||
|
}).ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The 0-indexed line number that this batch started on
|
||||||
|
/// </summary>
|
||||||
|
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<ResultSet>();
|
||||||
|
resultMessages = new List<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Executes this batch and captures any server messages that are returned.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="conn">The connection to use to execute the batch</param>
|
||||||
|
/// <param name="cancellationToken">Token for cancelling the execution</param>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a subset of the rows from a result set of the batch
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="resultSetIndex">The index for selecting the result set</param>
|
||||||
|
/// <param name="startRow">The starting row of the results</param>
|
||||||
|
/// <param name="rowCount">How many rows to retrieve</param>
|
||||||
|
/// <returns>A subset of results</returns>
|
||||||
|
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
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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>
|
||||||
|
/// <param name="sender">Object that fired the event</param>
|
||||||
|
/// <param name="args">Arguments from the event</param>
|
||||||
|
private void StoreDbMessage(object sender, SqlInfoMessageEventArgs args)
|
||||||
|
{
|
||||||
|
resultMessages.Add(args.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to convert a <see cref="DbException"/> to a <see cref="SqlException"/> that
|
||||||
|
/// contains much more info about Sql Server errors. The exception is then unwrapped and
|
||||||
|
/// messages are formatted and stored in <see cref="ResultMessages"/>. If the exception
|
||||||
|
/// cannot be converted to SqlException, the message is written to the messages list.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dbe">The exception to unwrap</param>
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
/// Summary of a batch within a query
|
||||||
|
/// </summary>
|
||||||
|
public class BatchSummary
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Whether or not the batch was successful. True indicates errors, false indicates success
|
||||||
|
/// </summary>
|
||||||
|
public bool HasError { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The ID of the result set within the query results
|
||||||
|
/// </summary>
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Any messages that came back from the server during execution of the batch
|
||||||
|
/// </summary>
|
||||||
|
public string[] Messages { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The summaries of the result sets inside the batch
|
||||||
|
/// </summary>
|
||||||
|
public ResultSetSummary[] ResultSetSummaries { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,20 +17,10 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string OwnerUri { get; set; }
|
public string OwnerUri { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Any messages that came back from the server during execution of the query
|
|
||||||
/// </summary>
|
|
||||||
public string[] Messages { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether or not the query was successful. True indicates errors, false indicates success
|
|
||||||
/// </summary>
|
|
||||||
public bool HasError { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Summaries of the result sets that were returned with the query
|
/// Summaries of the result sets that were returned with the query
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ResultSetSummary[] ResultSetSummaries { get; set; }
|
public BatchSummary[] BatchSummaries { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class QueryExecuteCompleteEvent
|
public class QueryExecuteCompleteEvent
|
||||||
|
|||||||
@@ -17,6 +17,11 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string OwnerUri { get; set; }
|
public string OwnerUri { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Index of the batch to get the results from
|
||||||
|
/// </summary>
|
||||||
|
public int BatchIndex { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Index of the result set to get the results from
|
/// Index of the result set to get the results from
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
|
|||||||
public class ResultSetSummary
|
public class ResultSetSummary
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The ID of the result set within the query results
|
/// The ID of the result set within the batch results
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -4,15 +4,14 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Data;
|
|
||||||
using System.Data.Common;
|
using System.Data.Common;
|
||||||
using System.Data.SqlClient;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.SqlServer.Management.SqlParser.Parser;
|
||||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||||
{
|
{
|
||||||
@@ -21,10 +20,35 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class Query : IDisposable
|
public class Query : IDisposable
|
||||||
{
|
{
|
||||||
private const string RowsAffectedFormat = "({0} row(s) affected)";
|
|
||||||
|
|
||||||
#region Properties
|
#region Properties
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The batches underneath this query
|
||||||
|
/// </summary>
|
||||||
|
internal Batch[] Batches { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The summaries of the batches underneath this query
|
||||||
|
/// </summary>
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Cancellation token source, used for cancelling async db actions
|
/// Cancellation token source, used for cancelling async db actions
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -34,49 +58,34 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
|||||||
/// The connection info associated with the file editor owner URI, used to create a new
|
/// The connection info associated with the file editor owner URI, used to create a new
|
||||||
/// connection upon execution of the query
|
/// connection upon execution of the query
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ConnectionInfo EditorConnection { get; set; }
|
private ConnectionInfo EditorConnection { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
private bool HasExecuteBeenCalled { get; set; }
|
||||||
/// Whether or not the query has an error
|
|
||||||
/// </summary>
|
|
||||||
public bool HasError { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether or not the query has completed executed, regardless of success or failure
|
/// Whether or not the query has completed executed, regardless of success or failure
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool HasExecuted { get; set; }
|
/// <remarks>
|
||||||
|
/// Don't touch the setter unless you're doing unit tests!
|
||||||
|
/// </remarks>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The text of the query to execute
|
/// The text of the query to execute
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string QueryText { get; set; }
|
public string QueryText { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Messages that have come back from the server
|
|
||||||
/// </summary>
|
|
||||||
public List<string> ResultMessages { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The result sets of the query execution
|
|
||||||
/// </summary>
|
|
||||||
public List<ResultSet> ResultSets { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Property for generating a set result set summaries from the result sets
|
|
||||||
/// </summary>
|
|
||||||
public ResultSetSummary[] ResultSummary
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return ResultSets.Select((set, index) => new ResultSetSummary
|
|
||||||
{
|
|
||||||
ColumnInfo = set.Columns,
|
|
||||||
Id = index,
|
|
||||||
RowCount = set.Rows.Count
|
|
||||||
}).ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -84,10 +93,11 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="queryText">The text of the query to execute</param>
|
/// <param name="queryText">The text of the query to execute</param>
|
||||||
/// <param name="connection">The information of the connection to use to execute the query</param>
|
/// <param name="connection">The information of the connection to use to execute the query</param>
|
||||||
public Query(string queryText, ConnectionInfo connection)
|
/// <param name="settings">Settings for how to execute the query, from the user</param>
|
||||||
|
public Query(string queryText, ConnectionInfo connection, QueryExecutionSettings settings)
|
||||||
{
|
{
|
||||||
// Sanity check for input
|
// Sanity check for input
|
||||||
if (String.IsNullOrEmpty(queryText))
|
if (string.IsNullOrEmpty(queryText))
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(queryText), "Query text cannot be null");
|
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");
|
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
|
// Initialize the internal state
|
||||||
QueryText = queryText;
|
QueryText = queryText;
|
||||||
EditorConnection = connection;
|
EditorConnection = connection;
|
||||||
HasExecuted = false;
|
|
||||||
ResultSets = new List<ResultSet>();
|
|
||||||
ResultMessages = new List<string>();
|
|
||||||
cancellationSource = new CancellationTokenSource();
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -110,98 +130,38 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task Execute()
|
public async Task Execute()
|
||||||
{
|
{
|
||||||
// Sanity check to make sure we haven't already run this query
|
// Mark that we've internally executed
|
||||||
if (HasExecuted)
|
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
|
// Open up a connection for querying the database
|
||||||
try
|
|
||||||
{
|
|
||||||
string connectionString = ConnectionService.BuildConnectionString(EditorConnection.ConnectionDetails);
|
string connectionString = ConnectionService.BuildConnectionString(EditorConnection.ConnectionDetails);
|
||||||
using (DbConnection conn = EditorConnection.Factory.CreateSqlConnection(connectionString))
|
using (DbConnection conn = EditorConnection.Factory.CreateSqlConnection(connectionString))
|
||||||
{
|
{
|
||||||
// If we have the message listener, bind to it
|
await conn.OpenAsync();
|
||||||
SqlConnection sqlConn = conn as SqlConnection;
|
|
||||||
if (sqlConn != null)
|
|
||||||
{
|
|
||||||
sqlConn.InfoMessage += StoreDbMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
await conn.OpenAsync(cancellationSource.Token);
|
// We need these to execute synchronously, otherwise the user will be very unhappy
|
||||||
|
foreach (Batch b in Batches)
|
||||||
// Create a command that we'll use for executing the query
|
|
||||||
using (DbCommand command = conn.CreateCommand())
|
|
||||||
{
|
{
|
||||||
command.CommandText = QueryText;
|
await b.Execute(conn, cancellationSource.Token);
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (DbException dbe)
|
|
||||||
{
|
|
||||||
HasError = true;
|
|
||||||
UnwrapDbException(dbe);
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
HasError = true;
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
// Mark that we have executed
|
|
||||||
HasExecuted = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves a subset of the result sets
|
/// Retrieves a subset of the result sets
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="batchIndex">The index for selecting the batch item</param>
|
||||||
/// <param name="resultSetIndex">The index for selecting the result set</param>
|
/// <param name="resultSetIndex">The index for selecting the result set</param>
|
||||||
/// <param name="startRow">The starting row of the results</param>
|
/// <param name="startRow">The starting row of the results</param>
|
||||||
/// <param name="rowCount">How many rows to retrieve</param>
|
/// <param name="rowCount">How many rows to retrieve</param>
|
||||||
/// <returns>A subset of results</returns>
|
/// <returns>A subset of results</returns>
|
||||||
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
|
// Sanity check that the results are available
|
||||||
if (!HasExecuted)
|
if (!HasExecuted)
|
||||||
@@ -209,30 +169,14 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
|||||||
throw new InvalidOperationException("The query has not completed, yet.");
|
throw new InvalidOperationException("The query has not completed, yet.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sanity check to make sure we have valid numbers
|
// Sanity check to make sure that the batch is within bounds
|
||||||
if (resultSetIndex < 0 || resultSetIndex >= ResultSets.Count)
|
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");
|
"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
|
return Batches[batchIndex].GetSubset(resultSetIndex, startRow, rowCount);
|
||||||
object[][] rows = targetResultSet.Rows.Skip(startRow).Take(rowCount).ToArray();
|
|
||||||
return new ResultSetSubset
|
|
||||||
{
|
|
||||||
Rows = rows,
|
|
||||||
RowCount = rows.Length
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -250,48 +194,6 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
|||||||
cancellationSource.Cancel();
|
cancellationSource.Cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 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>
|
|
||||||
/// <param name="sender">Object that fired the event</param>
|
|
||||||
/// <param name="args">Arguments from the event</param>
|
|
||||||
private void StoreDbMessage(object sender, SqlInfoMessageEventArgs args)
|
|
||||||
{
|
|
||||||
ResultMessages.Add(args.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Attempts to convert a <see cref="DbException"/> to a <see cref="SqlException"/> that
|
|
||||||
/// contains much more info about Sql Server errors. The exception is then unwrapped and
|
|
||||||
/// messages are formatted and stored in <see cref="ResultMessages"/>. If the exception
|
|
||||||
/// cannot be converted to SqlException, the message is written to the messages list.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="dbe">The exception to unwrap</param>
|
|
||||||
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
|
#region IDisposable Implementation
|
||||||
|
|
||||||
private bool disposed;
|
private bool disposed;
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ using Microsoft.SqlTools.ServiceLayer.Connection;
|
|||||||
using Microsoft.SqlTools.ServiceLayer.Hosting;
|
using Microsoft.SqlTools.ServiceLayer.Hosting;
|
||||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||||
{
|
{
|
||||||
@@ -60,6 +62,8 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
|||||||
private readonly Lazy<ConcurrentDictionary<string, Query>> queries =
|
private readonly Lazy<ConcurrentDictionary<string, Query>> queries =
|
||||||
new Lazy<ConcurrentDictionary<string, Query>>(() => new ConcurrentDictionary<string, Query>());
|
new Lazy<ConcurrentDictionary<string, Query>>(() => new ConcurrentDictionary<string, Query>());
|
||||||
|
|
||||||
|
private SqlToolsSettings Settings { get { return WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings; } }
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -81,6 +85,13 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
|||||||
Dispose();
|
Dispose();
|
||||||
return Task.FromResult(0);
|
return Task.FromResult(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Register a handler for when the configuration changes
|
||||||
|
WorkspaceService<SqlToolsSettings>.Instance.RegisterConfigChangeCallback((oldSettings, newSettings, eventContext) =>
|
||||||
|
{
|
||||||
|
Settings.QueryExecutionSettings.Update(newSettings.QueryExecutionSettings);
|
||||||
|
return Task.FromResult(0);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Request Handlers
|
#region Request Handlers
|
||||||
@@ -123,7 +134,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
|||||||
var result = new QueryExecuteSubsetResult
|
var result = new QueryExecuteSubsetResult
|
||||||
{
|
{
|
||||||
Message = null,
|
Message = null,
|
||||||
ResultSubset = query.GetSubset(
|
ResultSubset = query.GetSubset(subsetParams.BatchIndex,
|
||||||
subsetParams.ResultSetIndex, subsetParams.RowsStartIndex, subsetParams.RowsCount)
|
subsetParams.ResultSetIndex, subsetParams.RowsStartIndex, subsetParams.RowsCount)
|
||||||
};
|
};
|
||||||
await requestContext.SendResult(result);
|
await requestContext.SendResult(result);
|
||||||
@@ -251,8 +262,11 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
|||||||
ActiveQueries.TryRemove(executeParams.OwnerUri, out oldQuery);
|
ActiveQueries.TryRemove(executeParams.OwnerUri, out oldQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Retrieve the current settings for executing the query with
|
||||||
|
QueryExecutionSettings settings = WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings.QueryExecutionSettings;
|
||||||
|
|
||||||
// If we can't add the query now, it's assumed the query is in progress
|
// 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))
|
if (!ActiveQueries.TryAdd(executeParams.OwnerUri, newQuery))
|
||||||
{
|
{
|
||||||
await requestContext.SendResult(new QueryExecuteResult
|
await requestContext.SendResult(new QueryExecuteResult
|
||||||
@@ -292,10 +306,8 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
|||||||
await Task.WhenAll(executeTask);
|
await Task.WhenAll(executeTask);
|
||||||
QueryExecuteCompleteParams eventParams = new QueryExecuteCompleteParams
|
QueryExecuteCompleteParams eventParams = new QueryExecuteCompleteParams
|
||||||
{
|
{
|
||||||
HasError = query.HasError,
|
|
||||||
Messages = query.ResultMessages.ToArray(),
|
|
||||||
OwnerUri = executeParams.OwnerUri,
|
OwnerUri = executeParams.OwnerUri,
|
||||||
ResultSetSummaries = query.ResultSummary
|
BatchSummaries = query.BatchSummaries
|
||||||
};
|
};
|
||||||
await requestContext.SendEvent(QueryExecuteCompleteEvent.Type, eventParams);
|
await requestContext.SendEvent(QueryExecuteCompleteEvent.Type, eventParams);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,11 @@
|
|||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Data.Common;
|
using System.Data.Common;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||||
{
|
{
|
||||||
@@ -33,5 +36,33 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
|||||||
}
|
}
|
||||||
Rows.Add(row.ToArray());
|
Rows.Add(row.ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a subset of the rows from the result set
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="startRow">The starting row of the results</param>
|
||||||
|
/// <param name="rowCount">How many rows to retrieve</param>
|
||||||
|
/// <returns>A subset of results</returns>
|
||||||
|
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
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Collection of settings related to the execution of queries
|
||||||
|
/// </summary>
|
||||||
|
public class QueryExecutionSettings
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Default value for batch separator (de facto standard as per SSMS)
|
||||||
|
/// </summary>
|
||||||
|
private const string DefaultBatchSeparator = "GO";
|
||||||
|
|
||||||
|
private string batchSeparator;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The configured batch separator, will use a default if a value was not configured
|
||||||
|
/// </summary>
|
||||||
|
public string BatchSeparator
|
||||||
|
{
|
||||||
|
get { return batchSeparator ?? DefaultBatchSeparator; }
|
||||||
|
set { batchSeparator = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update the current settings with the new settings
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="newSettings">The new settings</param>
|
||||||
|
public void Update(QueryExecutionSettings newSettings)
|
||||||
|
{
|
||||||
|
BatchSeparator = newSettings.BatchSeparator;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlContext
|
|||||||
public SqlToolsSettings()
|
public SqlToolsSettings()
|
||||||
{
|
{
|
||||||
this.ScriptAnalysis = new ScriptAnalysisSettings();
|
this.ScriptAnalysis = new ScriptAnalysisSettings();
|
||||||
|
this.QueryExecutionSettings = new QueryExecutionSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool EnableProfileLoading { get; set; }
|
public bool EnableProfileLoading { get; set; }
|
||||||
@@ -31,6 +32,8 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlContext
|
|||||||
this.ScriptAnalysis.Update(settings.ScriptAnalysis, workspaceRootPath);
|
this.ScriptAnalysis.Update(settings.ScriptAnalysis, workspaceRootPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public QueryExecutionSettings QueryExecutionSettings { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -25,31 +25,64 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts
|
|||||||
/// Defines a position in a text document.
|
/// Defines a position in a text document.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DebuggerDisplay("TextDocumentPosition = {Position.Line}:{Position.Character}")]
|
[DebuggerDisplay("TextDocumentPosition = {Position.Line}:{Position.Character}")]
|
||||||
public class TextDocumentPosition : TextDocumentIdentifier
|
public class TextDocumentPosition
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the document identifier.
|
||||||
|
/// </summary>
|
||||||
|
public TextDocumentIdentifier TextDocument { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the position in the document.
|
/// Gets or sets the position in the document.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Position Position { get; set; }
|
public Position Position { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DidOpenTextDocumentNotification : TextDocumentIdentifier
|
/// <summary>
|
||||||
|
/// Defines a text document.
|
||||||
|
/// </summary>
|
||||||
|
[DebuggerDisplay("TextDocumentItem = {Uri}")]
|
||||||
|
public class TextDocumentItem
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the URI which identifies the path of the
|
||||||
|
/// text document.
|
||||||
|
/// </summary>
|
||||||
|
public string Uri { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the language of the document
|
||||||
|
/// </summary>
|
||||||
|
public string LanguageId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the version of the document
|
||||||
|
/// </summary>
|
||||||
|
public int Version { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the full content of the document.
|
||||||
|
/// </summary>
|
||||||
|
public string Text { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DidOpenTextDocumentNotification
|
||||||
{
|
{
|
||||||
public static readonly
|
public static readonly
|
||||||
EventType<DidOpenTextDocumentNotification> Type =
|
EventType<DidOpenTextDocumentNotification> Type =
|
||||||
EventType<DidOpenTextDocumentNotification>.Create("textDocument/didOpen");
|
EventType<DidOpenTextDocumentNotification>.Create("textDocument/didOpen");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the full content of the opened document.
|
/// Gets or sets the opened document.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Text { get; set; }
|
public TextDocumentItem TextDocument { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DidCloseTextDocumentNotification
|
public class DidCloseTextDocumentNotification
|
||||||
{
|
{
|
||||||
public static readonly
|
public static readonly
|
||||||
EventType<TextDocumentIdentifier> Type =
|
EventType<DidCloseTextDocumentParams> Type =
|
||||||
EventType<TextDocumentIdentifier>.Create("textDocument/didClose");
|
EventType<DidCloseTextDocumentParams>.Create("textDocument/didClose");
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DidChangeTextDocumentNotification
|
public class DidChangeTextDocumentNotification
|
||||||
@@ -59,9 +92,20 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts
|
|||||||
EventType<DidChangeTextDocumentParams>.Create("textDocument/didChange");
|
EventType<DidChangeTextDocumentParams>.Create("textDocument/didChange");
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DidChangeTextDocumentParams : TextDocumentIdentifier
|
public class DidCloseTextDocumentParams
|
||||||
{
|
{
|
||||||
public TextDocumentUriChangeEvent TextDocument { get; set; }
|
/// <summary>
|
||||||
|
/// Gets or sets the closed document.
|
||||||
|
/// </summary>
|
||||||
|
public TextDocumentItem TextDocument { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DidChangeTextDocumentParams
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the changed document.
|
||||||
|
/// </summary>
|
||||||
|
public VersionedTextDocumentIdentifier TextDocument { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the list of changes to the document content.
|
/// Gets or sets the list of changes to the document content.
|
||||||
@@ -69,13 +113,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts
|
|||||||
public TextDocumentChangeEvent[] ContentChanges { get; set; }
|
public TextDocumentChangeEvent[] ContentChanges { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TextDocumentUriChangeEvent
|
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the Uri of the changed text document
|
/// Define a specific version of a text document
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Uri { get; set; }
|
public class VersionedTextDocumentIdentifier : TextDocumentIdentifier
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the Version of the changed text document
|
/// Gets or sets the Version of the changed text document
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -43,8 +43,16 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts
|
|||||||
public class DocumentSymbolRequest
|
public class DocumentSymbolRequest
|
||||||
{
|
{
|
||||||
public static readonly
|
public static readonly
|
||||||
RequestType<TextDocumentIdentifier, SymbolInformation[]> Type =
|
RequestType<DocumentSymbolParams, SymbolInformation[]> Type =
|
||||||
RequestType<TextDocumentIdentifier, SymbolInformation[]>.Create("textDocument/documentSymbol");
|
RequestType<DocumentSymbolParams, SymbolInformation[]>.Create("textDocument/documentSymbol");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defines a set of parameters to send document symbol request
|
||||||
|
/// </summary>
|
||||||
|
public class DocumentSymbolParams
|
||||||
|
{
|
||||||
|
public TextDocumentIdentifier TextDocument { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class WorkspaceSymbolRequest
|
public class WorkspaceSymbolRequest
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace
|
|||||||
ConfigChangeCallbacks = new List<ConfigChangeCallback>();
|
ConfigChangeCallbacks = new List<ConfigChangeCallback>();
|
||||||
TextDocChangeCallbacks = new List<TextDocChangeCallback>();
|
TextDocChangeCallbacks = new List<TextDocChangeCallback>();
|
||||||
TextDocOpenCallbacks = new List<TextDocOpenCallback>();
|
TextDocOpenCallbacks = new List<TextDocOpenCallback>();
|
||||||
|
|
||||||
|
CurrentSettings = new TConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -52,7 +54,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace
|
|||||||
|
|
||||||
public Workspace Workspace { get; private set; }
|
public Workspace Workspace { get; private set; }
|
||||||
|
|
||||||
public TConfig CurrentSettings { get; private set; }
|
public TConfig CurrentSettings { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Delegate for callbacks that occur when the configuration for the workspace changes
|
/// 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
|
// Create a workspace that will handle state for the session
|
||||||
Workspace = new Workspace();
|
Workspace = new Workspace();
|
||||||
CurrentSettings = new TConfig();
|
|
||||||
|
|
||||||
// Register the handlers for when changes to the workspae occur
|
// Register the handlers for when changes to the workspae occur
|
||||||
serviceHost.SetEventHandler(DidChangeTextDocumentNotification.Type, HandleDidChangeTextDocumentNotification);
|
serviceHost.SetEventHandler(DidChangeTextDocumentNotification.Type, HandleDidChangeTextDocumentNotification);
|
||||||
@@ -181,7 +182,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace
|
|||||||
// A text change notification can batch multiple change requests
|
// A text change notification can batch multiple change requests
|
||||||
foreach (var textChange in textChangeParams.ContentChanges)
|
foreach (var textChange in textChangeParams.ContentChanges)
|
||||||
{
|
{
|
||||||
string fileUri = textChangeParams.Uri ?? textChangeParams.TextDocument.Uri;
|
string fileUri = textChangeParams.TextDocument.Uri ?? textChangeParams.TextDocument.Uri;
|
||||||
msg.AppendLine(string.Format(" File: {0}", fileUri));
|
msg.AppendLine(string.Format(" File: {0}", fileUri));
|
||||||
|
|
||||||
ScriptFile changedFile = Workspace.GetFile(fileUri);
|
ScriptFile changedFile = Workspace.GetFile(fileUri);
|
||||||
@@ -207,7 +208,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace
|
|||||||
Logger.Write(LogLevel.Verbose, "HandleDidOpenTextDocumentNotification");
|
Logger.Write(LogLevel.Verbose, "HandleDidOpenTextDocumentNotification");
|
||||||
|
|
||||||
// read the SQL file contents into the ScriptFile
|
// read the SQL file contents into the ScriptFile
|
||||||
ScriptFile openedFile = Workspace.GetFileBuffer(openParams.Uri, openParams.Text);
|
ScriptFile openedFile = Workspace.GetFileBuffer(openParams.TextDocument.Uri, openParams.TextDocument.Text);
|
||||||
|
|
||||||
// Propagate the changes to the event handlers
|
// Propagate the changes to the event handlers
|
||||||
var textDocOpenTasks = TextDocOpenCallbacks.Select(
|
var textDocOpenTasks = TextDocOpenCallbacks.Select(
|
||||||
@@ -217,7 +218,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected Task HandleDidCloseTextDocumentNotification(
|
protected Task HandleDidCloseTextDocumentNotification(
|
||||||
TextDocumentIdentifier closeParams,
|
DidCloseTextDocumentParams closeParams,
|
||||||
EventContext eventContext)
|
EventContext eventContext)
|
||||||
{
|
{
|
||||||
Logger.Write(LogLevel.Verbose, "HandleDidCloseTextDocumentNotification");
|
Logger.Write(LogLevel.Verbose, "HandleDidCloseTextDocumentNotification");
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Newtonsoft.Json": "9.0.1",
|
"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.Common": "4.1.0",
|
||||||
"System.Data.SqlClient": "4.1.0",
|
"System.Data.SqlClient": "4.1.0",
|
||||||
"Microsoft.SqlServer.Smo": "140.1.2",
|
"Microsoft.SqlServer.Smo": "140.1.2",
|
||||||
|
|||||||
@@ -287,7 +287,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
|
|||||||
|
|
||||||
// Check that we get table suggestions for an autocomplete request
|
// Check that we get table suggestions for an autocomplete request
|
||||||
TextDocumentPosition position = new TextDocumentPosition();
|
TextDocumentPosition position = new TextDocumentPosition();
|
||||||
position.Uri = connectionRequest.OwnerUri;
|
position.TextDocument = new TextDocumentIdentifier();
|
||||||
|
position.TextDocument.Uri = connectionRequest.OwnerUri;
|
||||||
position.Position = new Position();
|
position.Position = new Position();
|
||||||
position.Position.Line = 1;
|
position.Position.Line = 1;
|
||||||
position.Position.Character = 1;
|
position.Position.Character = 1;
|
||||||
@@ -381,7 +382,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
|
|||||||
|
|
||||||
// Check that we get 2 different table suggestions for autocomplete requests
|
// Check that we get 2 different table suggestions for autocomplete requests
|
||||||
TextDocumentPosition position = new TextDocumentPosition();
|
TextDocumentPosition position = new TextDocumentPosition();
|
||||||
position.Uri = connectionRequest.OwnerUri;
|
position.TextDocument = new TextDocumentIdentifier();
|
||||||
|
position.TextDocument.Uri = connectionRequest.OwnerUri;
|
||||||
position.Position = new Position();
|
position.Position = new Position();
|
||||||
position.Position.Line = 1;
|
position.Position.Line = 1;
|
||||||
position.Position.Character = 1;
|
position.Position.Character = 1;
|
||||||
@@ -392,7 +394,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
|
|||||||
Assert.Equal("model", items[1].Label);
|
Assert.Equal("model", items[1].Label);
|
||||||
|
|
||||||
TextDocumentPosition position2 = new TextDocumentPosition();
|
TextDocumentPosition position2 = new TextDocumentPosition();
|
||||||
position2.Uri = connectionRequest2.OwnerUri;
|
position2.TextDocument = new TextDocumentIdentifier();
|
||||||
|
position2.TextDocument.Uri = connectionRequest2.OwnerUri;
|
||||||
position2.Position = new Position();
|
position2.Position = new Position();
|
||||||
position2.Position.Line = 1;
|
position2.Position.Line = 1;
|
||||||
position2.Position.Character = 1;
|
position2.Position.Character = 1;
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
|||||||
// If:
|
// If:
|
||||||
// ... I request a query (doesn't matter what kind) and execute it
|
// ... I request a query (doesn't matter what kind) and execute it
|
||||||
var queryService = Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true);
|
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);
|
var executeRequest = Common.GetQueryExecuteResultContextMock(null, null, null);
|
||||||
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait();
|
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait();
|
||||||
queryService.ActiveQueries[Common.OwnerUri].HasExecuted = false; // Fake that it hasn't completed execution
|
queryService.ActiveQueries[Common.OwnerUri].HasExecuted = false; // Fake that it hasn't completed execution
|
||||||
@@ -46,7 +46,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
|||||||
// If:
|
// If:
|
||||||
// ... I request a query (doesn't matter what kind) and wait for execution
|
// ... I request a query (doesn't matter what kind) and wait for execution
|
||||||
var queryService = Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true);
|
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);
|
var executeRequest = Common.GetQueryExecuteResultContextMock(null, null, null);
|
||||||
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait();
|
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait();
|
||||||
|
|
||||||
|
|||||||
@@ -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.Collections.Generic;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using System.Data.Common;
|
using System.Data.Common;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||||
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
|
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.Hosting.Protocol.Contracts;
|
||||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
||||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||||
using Microsoft.SqlTools.ServiceLayer.Test.Utility;
|
using Microsoft.SqlTools.ServiceLayer.Test.Utility;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Moq.Protected;
|
using Moq.Protected;
|
||||||
@@ -17,16 +24,22 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
|||||||
{
|
{
|
||||||
public class Common
|
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 const string OwnerUri = "testFile";
|
||||||
|
|
||||||
public static readonly Dictionary<string, string>[] StandardTestData =
|
public const int StandardRows = 5;
|
||||||
|
|
||||||
|
public const int StandardColumns = 5;
|
||||||
|
|
||||||
|
public static Dictionary<string, string>[] StandardTestData
|
||||||
{
|
{
|
||||||
new Dictionary<string, string> { {"col1", "val11"}, { "col2", "val12"}, { "col3", "val13"}, { "col4", "col14"} },
|
get { return GetTestData(StandardRows, StandardColumns); }
|
||||||
new Dictionary<string, string> { {"col1", "val21"}, { "col2", "val22"}, { "col3", "val23"}, { "col4", "col24"} },
|
}
|
||||||
new Dictionary<string, string> { {"col1", "val31"}, { "col2", "val32"}, { "col3", "val33"}, { "col4", "col34"} },
|
|
||||||
new Dictionary<string, string> { {"col1", "val41"}, { "col2", "val42"}, { "col3", "val43"}, { "col4", "col44"} },
|
|
||||||
new Dictionary<string, string> { {"col1", "val51"}, { "col2", "val52"}, { "col3", "val53"}, { "col4", "col54"} },
|
|
||||||
};
|
|
||||||
|
|
||||||
public static Dictionary<string, string>[] GetTestData(int columns, int rows)
|
public static Dictionary<string, string>[] GetTestData(int columns, int rows)
|
||||||
{
|
{
|
||||||
@@ -44,9 +57,17 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
|||||||
return output;
|
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()
|
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();
|
query.Execute().Wait();
|
||||||
return query;
|
return query;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 System.Threading.Tasks;
|
||||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||||
|
|||||||
@@ -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.Connection;
|
||||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
||||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
@@ -11,191 +22,206 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
|||||||
{
|
{
|
||||||
public class ExecuteTests
|
public class ExecuteTests
|
||||||
{
|
{
|
||||||
#region Query Class Tests
|
#region Batch Class Tests
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void QueryCreationTest()
|
public void BatchCreationTest()
|
||||||
{
|
{
|
||||||
// If I create a new query...
|
// If I create a new batch...
|
||||||
Query query = new Query("NO OP", Common.CreateTestConnectionInfo(null, false));
|
Batch batch = new Batch(Common.StandardQuery, 1);
|
||||||
|
|
||||||
// Then:
|
// Then:
|
||||||
// ... It should not have executed
|
// ... The text of the batch should be stored
|
||||||
Assert.False(query.HasExecuted, "The query should not have executed.");
|
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
|
// ... The results should be empty
|
||||||
Assert.Empty(query.ResultSets);
|
Assert.Empty(batch.ResultSets);
|
||||||
Assert.Empty(query.ResultSummary);
|
Assert.Empty(batch.ResultSummaries);
|
||||||
|
Assert.Empty(batch.ResultMessages);
|
||||||
|
|
||||||
|
// ... The start line of the batch should be 0
|
||||||
|
Assert.Equal(0, batch.StartLine);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void QueryExecuteNoResultSets()
|
public void BatchExecuteNoResultSets()
|
||||||
{
|
{
|
||||||
// If I execute a query that should get no result sets
|
// If I execute a query that should get no result sets
|
||||||
Query query = new Query("Query with no result sets", Common.CreateTestConnectionInfo(null, false));
|
Batch batch = new Batch(Common.StandardQuery, 1);
|
||||||
query.Execute().Wait();
|
batch.Execute(GetConnection(Common.CreateTestConnectionInfo(null, false)), CancellationToken.None).Wait();
|
||||||
|
|
||||||
// Then:
|
// Then:
|
||||||
// ... It should have executed without error
|
// ... It should have executed without error
|
||||||
Assert.True(query.HasExecuted, "The query should have been marked executed.");
|
Assert.True(batch.HasExecuted, "The query should have been marked executed.");
|
||||||
Assert.False(query.HasError);
|
Assert.False(batch.HasError, "The batch should not have an error");
|
||||||
|
|
||||||
// ... The results should be empty
|
// ... The results should be empty
|
||||||
Assert.Empty(query.ResultSets);
|
Assert.Empty(batch.ResultSets);
|
||||||
Assert.Empty(query.ResultSummary);
|
Assert.Empty(batch.ResultSummaries);
|
||||||
|
|
||||||
// ... The results should not be null
|
// ... The results should not be null
|
||||||
Assert.NotNull(query.ResultSets);
|
Assert.NotNull(batch.ResultSets);
|
||||||
Assert.NotNull(query.ResultSummary);
|
Assert.NotNull(batch.ResultSummaries);
|
||||||
|
|
||||||
// ... There should be a message for how many rows were affected
|
// ... There should be a message for how many rows were affected
|
||||||
Assert.Equal(1, query.ResultMessages.Count);
|
Assert.Equal(1, batch.ResultMessages.Count());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[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
|
// If I execute a query that should get one result set
|
||||||
int resultSets = 1;
|
Batch batch = new Batch(Common.StandardQuery, 1);
|
||||||
int rows = 5;
|
batch.Execute(GetConnection(ci), CancellationToken.None).Wait();
|
||||||
int columns = 4;
|
|
||||||
Query query = new Query("Query with one result sets", ci);
|
|
||||||
query.Execute().Wait();
|
|
||||||
|
|
||||||
// Then:
|
// Then:
|
||||||
// ... It should have executed without error
|
// ... It should have executed without error
|
||||||
Assert.True(query.HasExecuted, "The query should have been marked executed.");
|
Assert.True(batch.HasExecuted, "The batch should have been marked executed.");
|
||||||
Assert.False(query.HasError);
|
Assert.False(batch.HasError, "The batch should not have an error");
|
||||||
|
|
||||||
// ... There should be exactly one result set
|
// ... 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
|
// ... 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
|
// ... Inside the result set should have 5 columns and 5 column definitions
|
||||||
Assert.Equal(columns, query.ResultSets[0].Rows[0].Length);
|
Assert.Equal(Common.StandardColumns, batch.ResultSets.First().Rows[0].Length);
|
||||||
Assert.Equal(columns, query.ResultSets[0].Columns.Length);
|
Assert.Equal(Common.StandardColumns, batch.ResultSets.First().Columns.Length);
|
||||||
|
Assert.Equal(Common.StandardColumns, batch.ResultSummaries[0].ColumnInfo.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);
|
|
||||||
|
|
||||||
// ... There should be a message for how many rows were affected
|
// ... There should be a message for how many rows were affected
|
||||||
Assert.Equal(resultSets, query.ResultMessages.Count);
|
Assert.Equal(resultSets, batch.ResultMessages.Count());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[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 resultSets = dataset.Length;
|
||||||
int rows = Common.StandardTestData.Length;
|
|
||||||
int columns = Common.StandardTestData[0].Count;
|
|
||||||
ConnectionInfo ci = Common.CreateTestConnectionInfo(dataset, false);
|
ConnectionInfo ci = Common.CreateTestConnectionInfo(dataset, false);
|
||||||
|
|
||||||
// If I execute a query that should get two result sets
|
// If I execute a query that should get two result sets
|
||||||
Query query = new Query("Query with two result sets", ci);
|
Batch batch = new Batch(Common.StandardQuery, 1);
|
||||||
query.Execute().Wait();
|
batch.Execute(GetConnection(ci), CancellationToken.None).Wait();
|
||||||
|
|
||||||
// Then:
|
// Then:
|
||||||
// ... It should have executed without error
|
// ... It should have executed without error
|
||||||
Assert.True(query.HasExecuted, "The query should have been marked executed.");
|
Assert.True(batch.HasExecuted, "The batch should have been marked executed.");
|
||||||
Assert.False(query.HasError);
|
Assert.False(batch.HasError, "The batch should not have an error");
|
||||||
|
|
||||||
// ... There should be exactly two result sets
|
// ... 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
|
// ... 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
|
// ... Inside each result set should be 5 columns and 5 column definitions
|
||||||
Assert.Equal(columns, rs.Rows[0].Length);
|
Assert.Equal(Common.StandardColumns, rs.Rows[0].Length);
|
||||||
Assert.Equal(columns, rs.Columns.Length);
|
Assert.Equal(Common.StandardColumns, rs.Columns.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ... There should be exactly two result set summaries
|
// ... 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
|
// ... 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
|
// ... There should be a message for how many rows were affected
|
||||||
Assert.Equal(resultSets, query.ResultMessages.Count);
|
Assert.Equal(resultSets, batch.ResultMessages.Count());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void QueryExecuteInvalidQuery()
|
public void BatchExecuteInvalidQuery()
|
||||||
{
|
{
|
||||||
ConnectionInfo ci = Common.CreateTestConnectionInfo(null, true);
|
ConnectionInfo ci = Common.CreateTestConnectionInfo(null, true);
|
||||||
|
|
||||||
// If I execute a query that is invalid
|
// If I execute a batch that is invalid
|
||||||
Query query = new Query("Invalid query", ci);
|
Batch batch = new Batch(Common.StandardQuery, 1);
|
||||||
query.Execute().Wait();
|
batch.Execute(GetConnection(ci), CancellationToken.None).Wait();
|
||||||
|
|
||||||
// Then:
|
// Then:
|
||||||
// ... It should have executed with error
|
// ... It should have executed with error
|
||||||
Assert.True(query.HasExecuted);
|
Assert.True(batch.HasExecuted);
|
||||||
Assert.True(query.HasError);
|
Assert.True(batch.HasError);
|
||||||
|
|
||||||
// ... There should be plenty of messages for the eror
|
// ... There should be no result sets
|
||||||
Assert.NotEmpty(query.ResultMessages);
|
Assert.Empty(batch.ResultSets);
|
||||||
|
Assert.Empty(batch.ResultSummaries);
|
||||||
|
|
||||||
|
// ... There should be plenty of messages for the error
|
||||||
|
Assert.NotEmpty(batch.ResultMessages);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[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
|
// If I execute a batch
|
||||||
Query query = new Query("Any query", ci);
|
Batch batch = new Batch(Common.StandardQuery, 1);
|
||||||
query.Execute().Wait();
|
batch.Execute(GetConnection(ci), CancellationToken.None).Wait();
|
||||||
|
|
||||||
// Then:
|
// Then:
|
||||||
// ... It should have executed without error
|
// ... It should have executed without error
|
||||||
Assert.True(query.HasExecuted, "The query should have been marked executed.");
|
Assert.True(batch.HasExecuted, "The batch should have been marked executed.");
|
||||||
Assert.False(query.HasError);
|
Assert.False(batch.HasError, "The batch should not have an error");
|
||||||
|
|
||||||
// If I execute it again
|
// If I execute it again
|
||||||
// Then:
|
// Then:
|
||||||
// ... It should throw an invalid operation exception wrapped in an aggregate exception
|
// ... It should throw an invalid operation exception
|
||||||
AggregateException ae = Assert.Throws<AggregateException>(() => query.Execute().Wait());
|
await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
||||||
Assert.Equal(1, ae.InnerExceptions.Count);
|
batch.Execute(GetConnection(ci), CancellationToken.None));
|
||||||
Assert.IsType<InvalidOperationException>(ae.InnerExceptions[0]);
|
|
||||||
|
|
||||||
// ... The data should still be available without error
|
// ... The data should still be available without error
|
||||||
Assert.False(query.HasError);
|
Assert.False(batch.HasError, "The batch should not be in an error condition");
|
||||||
Assert.True(query.HasExecuted, "The query should still be marked executed.");
|
Assert.True(batch.HasExecuted, "The batch should still be marked executed.");
|
||||||
Assert.NotEmpty(query.ResultSets);
|
Assert.NotEmpty(batch.ResultSets);
|
||||||
Assert.NotEmpty(query.ResultSummary);
|
Assert.NotEmpty(batch.ResultSummaries);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("")]
|
[InlineData("")]
|
||||||
[InlineData(" ")]
|
|
||||||
[InlineData(null)]
|
[InlineData(null)]
|
||||||
public void QueryExecuteNoQuery(string query)
|
public void BatchExecuteNoSql(string query)
|
||||||
{
|
{
|
||||||
// If:
|
// If:
|
||||||
// ... I create a query that has an empty query
|
// ... I create a batch that has an empty query
|
||||||
// Then:
|
// Then:
|
||||||
// ... It should throw an exception
|
// ... It should throw an exception
|
||||||
Assert.Throws<ArgumentNullException>(() => new Query(query, null));
|
Assert.Throws<ArgumentNullException>(() => 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<ArgumentNullException>(() =>
|
||||||
|
new Query(null, Common.CreateTestConnectionInfo(null, false), new QueryExecutionSettings()));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -205,7 +231,154 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
|||||||
// ... I create a query that has a null connection info
|
// ... I create a query that has a null connection info
|
||||||
// Then:
|
// Then:
|
||||||
// ... It should throw an exception
|
// ... It should throw an exception
|
||||||
Assert.Throws<ArgumentNullException>(() => new Query("Some Query", null));
|
Assert.Throws<ArgumentNullException>(() => 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<ArgumentNullException>(() =>
|
||||||
|
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<InvalidOperationException>(() => 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<InvalidOperationException>(() => 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<InvalidOperationException>(() => 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<InvalidOperationException>(() => 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<InvalidOperationException>(() => 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
|
#endregion
|
||||||
@@ -215,10 +388,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void QueryExecuteValidNoResultsTest()
|
public void QueryExecuteValidNoResultsTest()
|
||||||
{
|
{
|
||||||
|
// Given:
|
||||||
|
// ... Default settings are stored in the workspace service
|
||||||
|
WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings = new SqlToolsSettings();
|
||||||
|
|
||||||
// If:
|
// If:
|
||||||
// ... I request to execute a valid query with no results
|
// ... I request to execute a valid query with no results
|
||||||
var queryService = Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true);
|
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;
|
QueryExecuteResult result = null;
|
||||||
QueryExecuteCompleteParams completeParams = null;
|
QueryExecuteCompleteParams completeParams = null;
|
||||||
@@ -227,14 +404,15 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
|||||||
|
|
||||||
// Then:
|
// Then:
|
||||||
// ... No Errors should have been sent
|
// ... 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
|
// ... A completion event should have been fired with empty results
|
||||||
// ... There should be one active query
|
|
||||||
VerifyQueryExecuteCallCount(requestContext, Times.Once(), Times.Once(), Times.Never());
|
VerifyQueryExecuteCallCount(requestContext, Times.Once(), Times.Once(), Times.Never());
|
||||||
Assert.Null(result.Messages);
|
Assert.Null(result.Messages);
|
||||||
Assert.NotEmpty(completeParams.Messages);
|
Assert.Equal(1, completeParams.BatchSummaries.Length);
|
||||||
Assert.Empty(completeParams.ResultSetSummaries);
|
Assert.Empty(completeParams.BatchSummaries[0].ResultSetSummaries);
|
||||||
Assert.False(completeParams.HasError);
|
Assert.NotEmpty(completeParams.BatchSummaries[0].Messages);
|
||||||
|
|
||||||
|
// ... There should be one active query
|
||||||
Assert.Equal(1, queryService.ActiveQueries.Count);
|
Assert.Equal(1, queryService.ActiveQueries.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,7 +422,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
|||||||
// If:
|
// If:
|
||||||
// ... I request to execute a valid query with results
|
// ... I request to execute a valid query with results
|
||||||
var queryService = Common.GetPrimedExecutionService(Common.CreateMockFactory(new[] { Common.StandardTestData }, false), true);
|
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;
|
QueryExecuteResult result = null;
|
||||||
QueryExecuteCompleteParams completeParams = null;
|
QueryExecuteCompleteParams completeParams = null;
|
||||||
@@ -255,12 +433,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
|||||||
// ... No errors should have been sent
|
// ... No errors should have been sent
|
||||||
// ... A successful result should have been sent with messages
|
// ... A successful result should have been sent with messages
|
||||||
// ... A completion event should have been fired with one result
|
// ... A completion event should have been fired with one result
|
||||||
// ... There should be one active query
|
|
||||||
VerifyQueryExecuteCallCount(requestContext, Times.Once(), Times.Once(), Times.Never());
|
VerifyQueryExecuteCallCount(requestContext, Times.Once(), Times.Once(), Times.Never());
|
||||||
Assert.Null(result.Messages);
|
Assert.Null(result.Messages);
|
||||||
Assert.NotEmpty(completeParams.Messages);
|
Assert.Equal(1, completeParams.BatchSummaries.Length);
|
||||||
Assert.NotEmpty(completeParams.ResultSetSummaries);
|
Assert.NotEmpty(completeParams.BatchSummaries[0].ResultSetSummaries);
|
||||||
Assert.False(completeParams.HasError);
|
Assert.NotEmpty(completeParams.BatchSummaries[0].Messages);
|
||||||
|
Assert.False(completeParams.BatchSummaries[0].HasError);
|
||||||
|
|
||||||
|
// ... There should be one active query
|
||||||
Assert.Equal(1, queryService.ActiveQueries.Count);
|
Assert.Equal(1, queryService.ActiveQueries.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,7 +450,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
|||||||
// If:
|
// If:
|
||||||
// ... I request to execute a query using a file URI that isn't connected
|
// ... I request to execute a query using a file URI that isn't connected
|
||||||
var queryService = Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), false);
|
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;
|
QueryExecuteResult result = null;
|
||||||
var requestContext = Common.GetQueryExecuteResultContextMock(qer => result = qer, null, null);
|
var requestContext = Common.GetQueryExecuteResultContextMock(qer => result = qer, null, null);
|
||||||
@@ -293,7 +473,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
|||||||
// If:
|
// If:
|
||||||
// ... I request to execute a query
|
// ... I request to execute a query
|
||||||
var queryService = Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true);
|
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
|
// Note, we don't care about the results of the first request
|
||||||
var firstRequestContext = Common.GetQueryExecuteResultContextMock(null, null, null);
|
var firstRequestContext = Common.GetQueryExecuteResultContextMock(null, null, null);
|
||||||
@@ -322,7 +502,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
|||||||
// If:
|
// If:
|
||||||
// ... I request to execute a query
|
// ... I request to execute a query
|
||||||
var queryService = Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true);
|
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
|
// Note, we don't care about the results of the first request
|
||||||
var firstRequestContext = Common.GetQueryExecuteResultContextMock(null, null, null);
|
var firstRequestContext = Common.GetQueryExecuteResultContextMock(null, null, null);
|
||||||
@@ -340,7 +520,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
|||||||
// ... There should only be one active query
|
// ... There should only be one active query
|
||||||
VerifyQueryExecuteCallCount(secondRequestContext, Times.Once(), Times.Once(), Times.Never());
|
VerifyQueryExecuteCallCount(secondRequestContext, Times.Once(), Times.Once(), Times.Never());
|
||||||
Assert.Null(result.Messages);
|
Assert.Null(result.Messages);
|
||||||
Assert.False(complete.HasError);
|
Assert.False(complete.BatchSummaries.Any(b => b.HasError));
|
||||||
Assert.Equal(1, queryService.ActiveQueries.Count);
|
Assert.Equal(1, queryService.ActiveQueries.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -376,7 +556,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
|||||||
// If:
|
// If:
|
||||||
// ... I request to execute a query that is invalid
|
// ... I request to execute a query that is invalid
|
||||||
var queryService = Common.GetPrimedExecutionService(Common.CreateMockFactory(null, true), true);
|
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;
|
QueryExecuteResult result = null;
|
||||||
QueryExecuteCompleteParams complete = null;
|
QueryExecuteCompleteParams complete = null;
|
||||||
@@ -389,8 +569,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
|||||||
// ... A completion event should have been sent with error
|
// ... A completion event should have been sent with error
|
||||||
VerifyQueryExecuteCallCount(requestContext, Times.Once(), Times.Once(), Times.Never());
|
VerifyQueryExecuteCallCount(requestContext, Times.Once(), Times.Once(), Times.Never());
|
||||||
Assert.Null(result.Messages);
|
Assert.Null(result.Messages);
|
||||||
Assert.True(complete.HasError);
|
Assert.Equal(1, complete.BatchSummaries.Length);
|
||||||
Assert.NotEmpty(complete.Messages);
|
Assert.True(complete.BatchSummaries[0].HasError);
|
||||||
|
Assert.NotEmpty(complete.BatchSummaries[0].Messages);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -403,5 +584,10 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
|||||||
It.IsAny<QueryExecuteCompleteParams>()), sendEventCalls);
|
It.IsAny<QueryExecuteCompleteParams>()), sendEventCalls);
|
||||||
mock.Verify(rc => rc.SendError(It.IsAny<object>()), sendErrorCalls);
|
mock.Verify(rc => rc.SendError(It.IsAny<object>()), sendErrorCalls);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private DbConnection GetConnection(ConnectionInfo info)
|
||||||
|
{
|
||||||
|
return info.Factory.CreateSqlConnection(ConnectionService.BuildConnectionString(info.ConnectionDetails));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
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 System.Threading.Tasks;
|
||||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
||||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
@@ -10,18 +16,18 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
|||||||
{
|
{
|
||||||
public class SubsetTests
|
public class SubsetTests
|
||||||
{
|
{
|
||||||
#region Query Class Tests
|
#region Batch Class Tests
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(2)]
|
[InlineData(2)]
|
||||||
[InlineData(20)]
|
[InlineData(20)]
|
||||||
public void SubsetValidTest(int rowCount)
|
public void BatchSubsetValidTest(int rowCount)
|
||||||
{
|
{
|
||||||
// If I have an executed query
|
// If I have an executed batch
|
||||||
Query q = Common.GetBasicExecutedQuery();
|
Batch b = Common.GetBasicExecutedBatch();
|
||||||
|
|
||||||
// ... And I ask for a subset with valid arguments
|
// ... And I ask for a subset with valid arguments
|
||||||
ResultSetSubset subset = q.GetSubset(0, 0, rowCount);
|
ResultSetSubset subset = b.GetSubset(0, 0, rowCount);
|
||||||
|
|
||||||
// Then:
|
// Then:
|
||||||
// I should get the requested number of rows
|
// I should get the requested number of rows
|
||||||
@@ -29,18 +35,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
|||||||
Assert.Equal(Math.Min(rowCount, Common.StandardTestData.Length), subset.Rows.Length);
|
Assert.Equal(Math.Min(rowCount, Common.StandardTestData.Length), subset.Rows.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void SubsetUnexecutedQueryTest()
|
|
||||||
{
|
|
||||||
// If I have a query that has *not* been executed
|
|
||||||
Query q = new Query("NO OP", Common.CreateTestConnectionInfo(null, false));
|
|
||||||
|
|
||||||
// ... And I ask for a subset with valid arguments
|
|
||||||
// Then:
|
|
||||||
// ... It should throw an exception
|
|
||||||
Assert.Throws<InvalidOperationException>(() => q.GetSubset(0, 0, 2));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(-1, 0, 2)] // Invalid result set, too low
|
[InlineData(-1, 0, 2)] // Invalid result set, too low
|
||||||
[InlineData(2, 0, 2)] // Invalid result set, too high
|
[InlineData(2, 0, 2)] // Invalid result set, too high
|
||||||
@@ -48,7 +42,37 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
|||||||
[InlineData(0, 10, 2)] // Invalid start index, too high
|
[InlineData(0, 10, 2)] // Invalid start index, too high
|
||||||
[InlineData(0, 0, -1)] // Invalid row count, too low
|
[InlineData(0, 0, -1)] // Invalid row count, too low
|
||||||
[InlineData(0, 0, 0)] // Invalid row count, zero
|
[InlineData(0, 0, 0)] // Invalid row count, zero
|
||||||
public void SubsetInvalidParamsTest(int resultSetIndex, int rowStartInex, int rowCount)
|
public void BatchSubsetInvalidParamsTest(int resultSetIndex, int rowStartInex, int rowCount)
|
||||||
|
{
|
||||||
|
// If I have an executed batch
|
||||||
|
Batch b = Common.GetBasicExecutedBatch();
|
||||||
|
|
||||||
|
// ... And I ask for a subset with an invalid result set index
|
||||||
|
// Then:
|
||||||
|
// ... It should throw an exception
|
||||||
|
Assert.Throws<ArgumentOutOfRangeException>(() => b.GetSubset(resultSetIndex, rowStartInex, rowCount));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Query Class Tests
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SubsetUnexecutedQueryTest()
|
||||||
|
{
|
||||||
|
// If I have a query that has *not* been executed
|
||||||
|
Query q = new Query(Common.StandardQuery, Common.CreateTestConnectionInfo(null, false), new QueryExecutionSettings());
|
||||||
|
|
||||||
|
// ... And I ask for a subset with valid arguments
|
||||||
|
// Then:
|
||||||
|
// ... It should throw an exception
|
||||||
|
Assert.Throws<InvalidOperationException>(() => q.GetSubset(0, 0, 0, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(-1)] // Invalid batch, too low
|
||||||
|
[InlineData(2)] // Invalid batch, too high
|
||||||
|
public void QuerySubsetInvalidParamsTest(int batchIndex)
|
||||||
{
|
{
|
||||||
// If I have an executed query
|
// If I have an executed query
|
||||||
Query q = Common.GetBasicExecutedQuery();
|
Query q = Common.GetBasicExecutedQuery();
|
||||||
@@ -56,7 +80,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
|||||||
// ... And I ask for a subset with an invalid result set index
|
// ... And I ask for a subset with an invalid result set index
|
||||||
// Then:
|
// Then:
|
||||||
// ... It should throw an exception
|
// ... It should throw an exception
|
||||||
Assert.Throws<ArgumentOutOfRangeException>(() => q.GetSubset(resultSetIndex, rowStartInex, rowCount));
|
Assert.Throws<ArgumentOutOfRangeException>(() => q.GetSubset(batchIndex, 0, 0, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
@@ -15,11 +15,10 @@
|
|||||||
"System.ComponentModel.TypeConverter": "4.1.0",
|
"System.ComponentModel.TypeConverter": "4.1.0",
|
||||||
"xunit": "2.1.0",
|
"xunit": "2.1.0",
|
||||||
"dotnet-test-xunit": "1.0.0-rc2-192208-24",
|
"dotnet-test-xunit": "1.0.0-rc2-192208-24",
|
||||||
"moq.netcore": "4.4.0-beta8",
|
"moq": "4.6.36-alpha",
|
||||||
"Microsoft.SqlTools.ServiceLayer": {
|
"Microsoft.SqlTools.ServiceLayer": {
|
||||||
"target": "project"
|
"target": "project"
|
||||||
},
|
}
|
||||||
"System.Diagnostics.TraceSource": "4.0.0"
|
|
||||||
},
|
},
|
||||||
"testRunner": "xunit",
|
"testRunner": "xunit",
|
||||||
"frameworks": {
|
"frameworks": {
|
||||||
|
|||||||
Reference in New Issue
Block a user