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