diff --git a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs index 87001466..c788e734 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs @@ -1679,5 +1679,42 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices FileUtilities.SafeDirectoryDelete(FileUtilities.PeekDefinitionTempFolder, true); } } + + internal string ParseStatementAtPosition(string sql, int line, int column) + { + // adjust from 0-based to 1-based index + int parserLine = line + 1; + int parserColumn = column + 1; + + // parse current SQL file contents to retrieve a list of errors + ParseResult parseResult = Parser.Parse(sql, this.DefaultParseOptions); + if (parseResult != null && parseResult.Script != null && parseResult.Script.Batches != null) + { + foreach (var batch in parseResult.Script.Batches) + { + if (batch.Statements == null) + { + continue; + } + + // check if the batch matches parameters + if (batch.StartLocation.LineNumber <= parserLine + && batch.EndLocation.LineNumber >= parserLine) + { + foreach (var statement in batch.Statements) + { + // check if the statement matches parameters + if (statement.StartLocation.LineNumber <= parserLine + && statement.EndLocation.LineNumber >= parserLine) + { + return statement.Sql; + } + } + } + } + } + + return string.Empty; + } } } \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/ExecuteRequests/ExecuteDocumentStatementRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/ExecuteRequests/ExecuteDocumentStatementRequest.cs new file mode 100644 index 00000000..2c575b43 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/ExecuteRequests/ExecuteDocumentStatementRequest.cs @@ -0,0 +1,32 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using Microsoft.SqlTools.Hosting.Protocol.Contracts; + +namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts.ExecuteRequests +{ + /// + /// Parameters for executing a query from a document open in the workspace + /// + public class ExecuteDocumentStatementParams : ExecuteRequestParamsBase + { + /// + /// Line in the document for the location of the SQL statement + /// + public int Line { get; set; } + + /// + /// Column in the document for the location of the SQL statement + /// + public int Column { get; set; } + } + + public class ExecuteDocumentStatementRequest + { + public static readonly + RequestType Type = + RequestType.Create("query/executedocumentstatement"); + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/QueryExecutionService.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/QueryExecutionService.cs index 857f3cb1..088d6f16 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/QueryExecutionService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/QueryExecutionService.cs @@ -129,6 +129,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution { // Register handlers for requests serviceHost.SetRequestHandler(ExecuteDocumentSelectionRequest.Type, HandleExecuteRequest); + serviceHost.SetRequestHandler(ExecuteDocumentStatementRequest.Type, HandleExecuteRequest); serviceHost.SetRequestHandler(ExecuteStringRequest.Type, HandleExecuteRequest); serviceHost.SetRequestHandler(SubsetRequest.Type, HandleResultSubsetRequest); serviceHost.SetRequestHandler(QueryDisposeRequest.Type, HandleDisposeRequest); @@ -691,32 +692,14 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution ExecuteDocumentSelectionParams docRequest = request as ExecuteDocumentSelectionParams; if (docRequest != null) { - // Get the document from the parameters - ScriptFile queryFile = WorkspaceService.Workspace.GetFile(docRequest.OwnerUri); - if (queryFile == null) - { - return string.Empty; - } - // If a selection was not provided, use the entire document - if (docRequest.QuerySelection == null) - { - return queryFile.Contents; - } + return GetSqlTextFromSelectionData(docRequest.OwnerUri, docRequest.QuerySelection); + } - // A selection was provided, so get the lines in the selected range - string[] queryTextArray = queryFile.GetLinesInRange( - new BufferRange( - new BufferPosition( - docRequest.QuerySelection.StartLine + 1, - docRequest.QuerySelection.StartColumn + 1 - ), - new BufferPosition( - docRequest.QuerySelection.EndLine + 1, - docRequest.QuerySelection.EndColumn + 1 - ) - ) - ); - return string.Join(Environment.NewLine, queryTextArray); + // If it is a document statement, we'll retrieve the text from the document + ExecuteDocumentStatementParams stmtRequest = request as ExecuteDocumentStatementParams; + if (stmtRequest != null) + { + return GetSqlStatementAtPosition(stmtRequest.OwnerUri, stmtRequest.Line, stmtRequest.Column); } // If it is an ExecuteStringParams, return the text as is @@ -730,6 +713,55 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution throw new InvalidCastException("Invalid request type"); } + /// + /// Return portion of document corresponding to the selection range + /// + internal string GetSqlTextFromSelectionData(string ownerUri, SelectionData selection) + { + // Get the document from the parameters + ScriptFile queryFile = WorkspaceService.Workspace.GetFile(ownerUri); + if (queryFile == null) + { + return string.Empty; + } + // If a selection was not provided, use the entire document + if (selection == null) + { + return queryFile.Contents; + } + + // A selection was provided, so get the lines in the selected range + string[] queryTextArray = queryFile.GetLinesInRange( + new BufferRange( + new BufferPosition( + selection.StartLine + 1, + selection.StartColumn + 1 + ), + new BufferPosition( + selection.EndLine + 1, + selection.EndColumn + 1 + ) + ) + ); + return string.Join(Environment.NewLine, queryTextArray); + } + + /// + /// Return portion of document corresponding to the statement at the line and column + /// + internal string GetSqlStatementAtPosition(string ownerUri, int line, int column) + { + // Get the document from the parameters + ScriptFile queryFile = WorkspaceService.Workspace.GetFile(ownerUri); + if (queryFile == null) + { + return string.Empty; + } + + return LanguageServices.LanguageService.Instance.ParseStatementAtPosition( + queryFile.Contents, line, column); + } + /// Internal for testing purposes internal Task UpdateSettings(SqlToolsSettings newSettings, SqlToolsSettings oldSettings, EventContext eventContext) { diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/QueryExecution/Execution/ServiceIntegrationTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/QueryExecution/Execution/ServiceIntegrationTests.cs index 4a4a6952..929feb8b 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/QueryExecution/Execution/ServiceIntegrationTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/QueryExecution/Execution/ServiceIntegrationTests.cs @@ -21,6 +21,20 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.Execution #region Get SQL Tests + [Fact] + public void ExecuteDocumentStatementTest() + { + string query = string.Format("{0}{1}GO{1}{0}", Constants.StandardQuery, Environment.NewLine); + var workspaceService = GetDefaultWorkspaceService(query); + var queryService = new QueryExecutionService(null, workspaceService); + + var queryParams = new ExecuteDocumentStatementParams { OwnerUri = Constants.OwnerUri, Line = 0, Column = 0 }; + var queryText = queryService.GetSqlText(queryParams); + + // The text should match the standard query + Assert.Equal(queryText, Constants.StandardQuery); + } + [Fact] public void GetSqlTextFromDocumentRequestFull() {