diff --git a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/AutoCompleteService.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/AutoCompleteService.cs index 9af007a6..715a975d 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/AutoCompleteService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/AutoCompleteService.cs @@ -54,12 +54,6 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices /// public IEnumerable AutoCompleteList { get; private set; } - public void InitializeService(ServiceHost serviceHost) - { - // Register a callback for when a connection is created - ConnectionService.Instance.RegisterOnConnectionTask(UpdateAutoCompleteCache); - } - /// /// Update the cached autocomplete candidate list when the user connects to a database /// TODO: Update with refactoring/async @@ -71,16 +65,17 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices command.CommandText = "SELECT name FROM sys.tables"; command.CommandTimeout = 15; command.CommandType = CommandType.Text; - var reader = await command.ExecuteReaderAsync(); - - List results = new List(); - while (await reader.ReadAsync()) + using (var reader = await command.ExecuteReaderAsync()) { - results.Add(reader[0].ToString()); - } - AutoCompleteList = results; - await Task.FromResult(0); + List results = new List(); + while (await reader.ReadAsync()) + { + results.Add(reader[0].ToString()); + } + + AutoCompleteList = results; + } } /// diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecutionServices/Contracts/QueryDisposeRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecutionServices/Contracts/QueryDisposeRequest.cs new file mode 100644 index 00000000..e34e7dbc --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecutionServices/Contracts/QueryDisposeRequest.cs @@ -0,0 +1,37 @@ +// +// 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 Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts; + +namespace Microsoft.SqlTools.ServiceLayer.QueryExecutionServices.Contracts +{ + /// + /// Parameters for the query dispose request + /// + public class QueryDisposeParams + { + public Guid QueryId { get; set; } + } + + /// + /// Parameters to return as the result of a query dispose request + /// + public class QueryDisposeResult + { + /// + /// Any error messages that occurred during disposing the result set. Optional, can be set + /// to null if there were no errors. + /// + public string Messages { get; set; } + } + + public class QueryDisposeRequest + { + public static readonly + RequestType Type = + RequestType.Create("query/dispose"); + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecutionServices/Contracts/QueryExecuteCompleteNotification.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecutionServices/Contracts/QueryExecuteCompleteNotification.cs new file mode 100644 index 00000000..22a210c2 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecutionServices/Contracts/QueryExecuteCompleteNotification.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Microsoft.SqlTools.ServiceLayer.QueryExecutionServices.Contracts +{ + public class QueryExecuteCompleteNotification + { + /// + /// 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 Error { get; set; } + + /// + /// Summaries of the result sets that were returned with the query + /// + public ResultSetSummary[] ResultSetSummaries { get; set; } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecutionServices/Contracts/QueryExecuteRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecutionServices/Contracts/QueryExecuteRequest.cs new file mode 100644 index 00000000..95fd1548 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecutionServices/Contracts/QueryExecuteRequest.cs @@ -0,0 +1,44 @@ +// +// 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 Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts; + +namespace Microsoft.SqlTools.ServiceLayer.QueryExecutionServices.Contracts +{ + /// + /// Parameters for the query execute request + /// + public class QueryExecuteParams + { + /// + /// The text of the query to execute + /// + public string QueryText { get; set; } + + /// + /// URI for the editor that is asking for the query execute + /// + public string OwnerUri { get; set; } + } + + /// + /// Parameters for the query execute result + /// + public class QueryExecuteResult + { + /// + /// Connection error messages. Optional, can be set to null to indicate no errors + /// + public string Messages { get; set; } + } + + public class QueryExecuteRequest + { + public static readonly + RequestType Type = + RequestType.Create("query/execute"); + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecutionServices/Contracts/QueryExecuteResultsRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecutionServices/Contracts/QueryExecuteResultsRequest.cs new file mode 100644 index 00000000..7a31dcb4 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecutionServices/Contracts/QueryExecuteResultsRequest.cs @@ -0,0 +1,54 @@ +// +// 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 Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts; + +namespace Microsoft.SqlTools.ServiceLayer.QueryExecutionServices.Contracts +{ + /// + /// Parameters for a query result subset retrieval request + /// + public class QueryExecuteSubsetParams + { + /// + /// ID of the query to look up the results for + /// + public Guid QueryId { get; set; } + + /// + /// Index of the result set to get the results from + /// + public int ResultSetIndex { get; set; } + + /// + /// Beginning index of the rows to return from the selected resultset. This index will be + /// included in the results. + /// + public int RowsStartIndex { get; set; } + + /// + /// Number of rows to include in the result of this request. If the number of the rows + /// exceeds the number of rows available after the start index, all available rows after + /// the start index will be returned. + /// + public int RowsCount { get; set; } + } + + /// + /// + /// + public class QueryExecuteSubsetResult + { + + } + + public class QueryExecuteSubsetRequest + { + public static readonly + RequestType Type = + RequestType.Create("query/subset"); + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecutionServices/Contracts/ResultSet.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecutionServices/Contracts/ResultSet.cs new file mode 100644 index 00000000..c3ec6f3d --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecutionServices/Contracts/ResultSet.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Data.Common; + +namespace Microsoft.SqlTools.ServiceLayer.QueryExecutionServices.Contracts +{ + public class ResultSet + { + public DbColumn[] Columns { get; set; } + + public List Rows { get; private set; } + + public ResultSet() + { + Rows = new List(); + } + + /// + /// Add a row of data to the result set using a that has already + /// read in a row. + /// + /// A that has already had a read performed + public void AddRow(DbDataReader reader) + { + List row = new List(); + for (int i = 0; i < reader.FieldCount; ++i) + { + row.Add(reader.GetValue(i)); + } + Rows.Add(row.ToArray()); + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecutionServices/Contracts/ResultSetSubset.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecutionServices/Contracts/ResultSetSubset.cs new file mode 100644 index 00000000..092e58b3 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecutionServices/Contracts/ResultSetSubset.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Microsoft.SqlTools.ServiceLayer.QueryExecutionServices.Contracts +{ + public class ResultSetSubset + { + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecutionServices/Contracts/ResultSetSummary.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecutionServices/Contracts/ResultSetSummary.cs new file mode 100644 index 00000000..b989c135 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecutionServices/Contracts/ResultSetSummary.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Data.Common; +using System.Linq; +using System.Threading.Tasks; + +namespace Microsoft.SqlTools.ServiceLayer.QueryExecutionServices.Contracts +{ + public class ResultSetSummary + { + /// + /// The ID of the result set within the query results + /// + public int Id { get; set; } + + /// + /// The number of rows that was returned with the resultset + /// + public long RowCount { get; set; } + + /// + /// Details about the columns that are provided as solutions + /// + public DbColumn ColumnInfo { get; set; } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecutionServices/Query.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecutionServices/Query.cs new file mode 100644 index 00000000..d2a49bb3 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecutionServices/Query.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.SqlTools.ServiceLayer.QueryExecutionServices.Contracts; + +namespace Microsoft.SqlTools.ServiceLayer.QueryExecutionServices +{ + public class Query //: IDisposable + { + public string QueryText { get; set; } + + public DbConnection SqlConnection { get; set; } + + private readonly CancellationTokenSource cancellationSource; + + public List ResultSets { get; set; } + + public Query(string queryText, DbConnection connection) + { + QueryText = queryText; + SqlConnection = connection; + ResultSets = new List(); + cancellationSource = new CancellationTokenSource(); + } + + public async Task Execute() + { + // Open the connection, if it's not already open + if ((SqlConnection.State & ConnectionState.Open) == 0) + { + await SqlConnection.OpenAsync(cancellationSource.Token); + } + + // Create a command that we'll use for executing the query + using (DbCommand command = SqlConnection.CreateCommand()) + { + command.CommandText = QueryText; + command.CommandType = CommandType.Text; + + // Execute the command to get back a reader + using (DbDataReader reader = await command.ExecuteReaderAsync(cancellationSource.Token)) + { + do + { + // Create a new result set that we'll use to store all the data + ResultSet resultSet = new ResultSet(); + if (reader.CanGetColumnSchema()) + { + resultSet.Columns = reader.GetColumnSchema().ToArray(); + } + + // Read until we hit the end of the result set + while (await reader.ReadAsync(cancellationSource.Token)) + { + resultSet.AddRow(reader); + } + + // Add the result set to the results of the query + ResultSets.Add(resultSet); + } while (await reader.NextResultAsync(cancellationSource.Token)); + } + } + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecutionServices/QueryExecutionService.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecutionServices/QueryExecutionService.cs new file mode 100644 index 00000000..b3ec4886 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecutionServices/QueryExecutionService.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Concurrent; +using System.Threading.Tasks; +using Microsoft.SqlTools.ServiceLayer.Hosting; +using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol; +using Microsoft.SqlTools.ServiceLayer.QueryExecutionServices.Contracts; + +namespace Microsoft.SqlTools.ServiceLayer.QueryExecutionServices +{ + public sealed class QueryExecutionService + { + #region Singleton Instance Implementation + + private static readonly Lazy instance = new Lazy(() => new QueryExecutionService()); + + public static QueryExecutionService Instance + { + get { return instance.Value; } + } + + private QueryExecutionService() { } + + #endregion + + #region Properties + + private readonly Lazy> queries = + new Lazy>(() => new ConcurrentDictionary()); + + private ConcurrentDictionary Queries + { + get { return queries.Value; } + } + + #endregion + + #region Public Methods + + /// + /// + /// + /// + public void InitializeService(ServiceHost serviceHost) + { + // Register handlers for requests + serviceHost.SetRequestHandler(QueryExecuteRequest.Type, HandleExecuteRequest); + serviceHost.SetRequestHandler(QueryExecuteSubsetRequest.Type, HandleResultSubsetRequest); + serviceHost.SetRequestHandler(QueryDisposeRequest.Type, HandleDisposeRequest); + + // Register handlers for events + } + + #endregion + + #region Request Handlers + + private async Task HandleExecuteRequest(QueryExecuteParams executeParams, + RequestContext requestContext) + { + + } + + private async Task HandleResultSubsetRequest(QueryExecuteSubsetParams subsetParams, + RequestContext requestContext) + { + await Task.FromResult(0); + } + + private async Task HandleDisposeRequest(QueryDisposeParams disposeParams, + RequestContext requestContext) + { + string messages = null; + + Query result; + if (!Queries.TryRemove(disposeParams., out result)) + { + messages = "Failed to dispose query, ID not found."; + } + + await requestContext.SendResult(new QueryDisposeResult + { + Messages = messages + }); + } + + #endregion + + } +}