Peek definition support for tables, views and stored procedures (#160)

* Add support for peek/go to definition
Add unit tests for definition

Microsoft/vscode-mssql#253 and Microsoft/vscode-mssql#268

* Format Strings

* Add integration tests

* Refactor variable names

* Remove test file

* Remove LIVE_CONNECTION definition

* Change method name

* Write files to a separate directory

* Refactor GetDefinition

* Remove unnecessary whitespace and modify variable name

* Check intellisense settings

* Refactor code to be scalable and modify tests

* Refactor to facilitate CodeGen

* Reorder methods

* Modify method to strip bracket syntax

* Add one_second constant

* Add comments

* Modify null check

* Modify GetSchema code to account for spaces

* Alter variable names and modify null checks

* Remove timeout callback and refactor null check

* remove LIVE_CONNECTION_TEST definition
This commit is contained in:
Sharon Ravindran
2016-12-02 12:16:21 -08:00
committed by GitHub
parent dd3592fe30
commit 82a7a01304
9 changed files with 742 additions and 10 deletions

View File

@@ -150,7 +150,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Hosting
Capabilities = new ServerCapabilities
{
TextDocumentSync = TextDocumentSyncKind.Incremental,
DefinitionProvider = false,
DefinitionProvider = true,
ReferencesProvider = false,
DocumentHighlightProvider = false,
HoverProvider = true,

View File

@@ -33,6 +33,8 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
/// </summary>
public sealed class LanguageService
{
private const int OneSecond = 1000;
internal const string DefaultBatchSeperator = "GO";
internal const int DiagnosticParseDelay = 750;
@@ -41,7 +43,9 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
internal const int BindingTimeout = 500;
internal const int OnConnectionWaitTimeout = 300000;
internal const int OnConnectionWaitTimeout = 300 * OneSecond;
internal const int PeekDefinitionTimeout = 10 * OneSecond;
private static ConnectionService connectionService = null;
@@ -198,7 +202,6 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
// Register the requests that this service will handle
// turn off until needed (10/28/2016)
// serviceHost.SetRequestHandler(DefinitionRequest.Type, HandleDefinitionRequest);
// serviceHost.SetRequestHandler(ReferencesRequest.Type, HandleReferencesRequest);
// serviceHost.SetRequestHandler(DocumentHighlightRequest.Type, HandleDocumentHighlightRequest);
@@ -206,6 +209,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
serviceHost.SetRequestHandler(CompletionResolveRequest.Type, HandleCompletionResolveRequest);
serviceHost.SetRequestHandler(HoverRequest.Type, HandleHoverRequest);
serviceHost.SetRequestHandler(CompletionRequest.Type, HandleCompletionRequest);
serviceHost.SetRequestHandler(DefinitionRequest.Type, HandleDefinitionRequest);
// Register a no-op shutdown task for validation of the shutdown logic
serviceHost.RegisterShutdownTask(async (shutdownParams, shutdownRequestContext) =>
@@ -293,15 +297,25 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
}
}
// turn off this code until needed (10/28/2016)
#if false
private static async Task HandleDefinitionRequest(
TextDocumentPosition textDocumentPosition,
RequestContext<Location[]> requestContext)
internal static async Task HandleDefinitionRequest(TextDocumentPosition textDocumentPosition, RequestContext<Location[]> requestContext)
{
await Task.FromResult(true);
if (WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings.IsIntelliSenseEnabled)
{
// Retrieve document and connection
ConnectionInfo connInfo;
var scriptFile = LanguageService.WorkspaceServiceInstance.Workspace.GetFile(textDocumentPosition.TextDocument.Uri);
LanguageService.ConnectionServiceInstance.TryFindConnection(scriptFile.ClientFilePath, out connInfo);
Location[] locations = LanguageService.Instance.GetDefinition(textDocumentPosition, scriptFile, connInfo);
if (locations != null)
{
await requestContext.SendResult(locations);
}
}
}
// turn off this code until needed (10/28/2016)
#if false
private static async Task HandleReferencesRequest(
ReferencesParams referencesParams,
RequestContext<Location[]> requestContext)
@@ -654,6 +668,106 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
return completionItem;
}
/// <summary>
/// Get definition for a selected sql object using SMO Scripting
/// </summary>
/// <param name="textDocumentPosition"></param>
/// <param name="scriptFile"></param>
/// <param name="connInfo"></param>
/// <returns> Location with the URI of the script file</returns>
internal Location[] GetDefinition(TextDocumentPosition textDocumentPosition, ScriptFile scriptFile, ConnectionInfo connInfo)
{
// Parse sql
ScriptParseInfo scriptParseInfo = GetScriptParseInfo(textDocumentPosition.TextDocument.Uri);
if (scriptParseInfo == null)
{
return null;
}
if (RequiresReparse(scriptParseInfo, scriptFile))
{
scriptParseInfo.ParseResult = ParseAndBind(scriptFile, connInfo);
}
// Get token from selected text
Token selectedToken = GetToken(scriptParseInfo, textDocumentPosition.Position.Line + 1, textDocumentPosition.Position.Character);
if (selectedToken == null)
{
return null;
}
// Strip "[" and "]"(if present) from the token text to enable matching with the suggestions.
// The suggestion title does not contain any sql punctuation
string tokenText = TextUtilities.RemoveSquareBracketSyntax(selectedToken.Text);
if (scriptParseInfo.IsConnected && Monitor.TryEnter(scriptParseInfo.BuildingMetadataLock))
{
try
{
// Queue the task with the binding queue
QueueItem queueItem = this.BindingQueue.QueueBindingOperation(
key: scriptParseInfo.ConnectionKey,
bindingTimeout: LanguageService.PeekDefinitionTimeout,
bindOperation: (bindingContext, cancelToken) =>
{
// Get suggestions for the token
int parserLine = textDocumentPosition.Position.Line + 1;
int parserColumn = textDocumentPosition.Position.Character + 1;
IEnumerable<Declaration> declarationItems = Resolver.FindCompletions(
scriptParseInfo.ParseResult,
parserLine, parserColumn,
bindingContext.MetadataDisplayInfoProvider);
// Match token with the suggestions(declaration items) returned
string schemaName = this.GetSchemaName(scriptParseInfo, textDocumentPosition.Position, scriptFile);
PeekDefinition peekDefinition = new PeekDefinition(connInfo);
return peekDefinition.GetScript(declarationItems, tokenText, schemaName);
});
// wait for the queue item
queueItem.ItemProcessed.WaitOne();
return queueItem.GetResultAsT<Location[]>();
}
finally
{
Monitor.Exit(scriptParseInfo.BuildingMetadataLock);
}
}
return null;
}
/// <summary>
/// Extract schema name for a token, if present
/// </summary>
/// <param name="scriptParseInfo"></param>
/// <param name="position"></param>
/// <param name="scriptFile"></param>
/// <returns> schema nama</returns>
private string GetSchemaName(ScriptParseInfo scriptParseInfo, Position position, ScriptFile scriptFile)
{
// Offset index by 1 for sql parser
int startLine = position.Line + 1;
int startColumn = position.Character + 1;
// Get schema name
if (scriptParseInfo != null && scriptParseInfo.ParseResult != null && scriptParseInfo.ParseResult.Script != null && scriptParseInfo.ParseResult.Script.Tokens != null)
{
var tokenIndex = scriptParseInfo.ParseResult.Script.TokenManager.FindToken(startLine, startColumn);
var prevTokenIndex = scriptParseInfo.ParseResult.Script.TokenManager.GetPreviousSignificantTokenIndex(tokenIndex);
var prevTokenText = scriptParseInfo.ParseResult.Script.TokenManager.GetText(prevTokenIndex);
if (prevTokenText != null && prevTokenText.Equals("."))
{
var schemaTokenIndex = scriptParseInfo.ParseResult.Script.TokenManager.GetPreviousSignificantTokenIndex(prevTokenIndex);
Token schemaToken = scriptParseInfo.ParseResult.Script.TokenManager.GetToken(schemaTokenIndex);
return TextUtilities.RemoveSquareBracketSyntax(schemaToken.Text);
}
}
// if no schema name, returns null
return null;
}
/// <summary>
/// Get quick info hover tooltips for the current position
/// </summary>

View File

@@ -0,0 +1,227 @@
//
// 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.IO;
using System.Collections.Generic;
using System.Collections.Specialized;
using Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
using Microsoft.SqlTools.ServiceLayer.Connection;
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
{
/// <summary>
/// Peek Definition/ Go to definition implementation
/// Script sql objects and write create scripts to file
/// </summary>
internal class PeekDefinition
{
private ConnectionInfo connectionInfo;
private string tempPath;
internal delegate StringCollection ScriptGetter(string objectName, string schemaName);
// Dictionary that holds the script getter for each type
private Dictionary<DeclarationType, ScriptGetter> sqlScriptGetters =
new Dictionary<DeclarationType, ScriptGetter>();
// Dictionary that holds the object name (as appears on the TSQL create statement)
private Dictionary<DeclarationType, string> sqlObjectTypes = new Dictionary<DeclarationType, string>();
private Database Database
{
get
{
if (this.connectionInfo.SqlConnection != null)
{
Server server = new Server(this.connectionInfo.SqlConnection.DataSource);
return server.Databases[this.connectionInfo.SqlConnection.Database];
}
return null;
}
}
internal PeekDefinition(ConnectionInfo connInfo)
{
this.connectionInfo = connInfo;
DirectoryInfo tempScriptDirectory = Directory.CreateDirectory(Path.GetTempPath() + "mssql_definition");
this.tempPath = tempScriptDirectory.FullName;
Initialize();
}
/// <summary>
/// Add getters for each sql object supported by peek definition
/// </summary>
private void Initialize()
{
//Add script getters for each sql object
//Add tables to supported types
AddSupportedType(DeclarationType.Table, GetTableScripts, "Table");
//Add views to supported types
AddSupportedType(DeclarationType.View, GetViewScripts, "view");
//Add stored procedures to supported types
AddSupportedType(DeclarationType.StoredProcedure, GetStoredProcedureScripts, "Procedure");
}
/// <summary>
/// Add the given type, scriptgetter and the typeName string to the respective dictionaries
/// </summary>
private void AddSupportedType(DeclarationType type, ScriptGetter scriptGetter, string typeName)
{
sqlScriptGetters.Add(type, scriptGetter);
sqlObjectTypes.Add(type, typeName);
}
/// <summary>
/// Convert a file to a location array containing a location object as expected by the extension
/// </summary>
private Location[] GetLocationFromFile(string tempFileName, int lineNumber)
{
Location[] locations = new[] {
new Location {
Uri = new Uri(tempFileName).AbsoluteUri,
Range = new Range {
Start = new Position { Line = lineNumber, Character = 1},
End = new Position { Line = lineNumber + 1, Character = 1}
}
}
};
return locations;
}
/// <summary>
/// Get line number for the create statement
/// </summary>
private int GetStartOfCreate(string script, string createString)
{
string[] lines = script.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
for (int lineNumber = 0; lineNumber < lines.Length; lineNumber++)
{
if (lines[lineNumber].IndexOf( createString, StringComparison.OrdinalIgnoreCase) >= 0)
{
return lineNumber;
}
}
return 0;
}
/// <summary>
/// Get the script of the selected token based on the type of the token
/// </summary>
/// <param name="declarationItems"></param>
/// <param name="tokenText"></param>
/// <param name="schemaName"></param>
/// <returns>Location object of the script file</returns>
internal Location[] GetScript(IEnumerable<Declaration> declarationItems, string tokenText, string schemaName)
{
foreach (Declaration declarationItem in declarationItems)
{
if (declarationItem.Title == null)
{
continue;
}
if (declarationItem.Title.Equals(tokenText))
{
// Script object using SMO based on type
DeclarationType type = declarationItem.Type;
if (sqlScriptGetters.ContainsKey(type) && sqlObjectTypes.ContainsKey(type))
{
return GetSqlObjectDefinition(
sqlScriptGetters[type],
tokenText,
schemaName,
sqlObjectTypes[type]
);
}
return null;
}
}
return null;
}
/// <summary>
/// Script a table using SMO
/// </summary>
/// <param name="tableName">Table name</param>
/// <param name="schemaName">Schema name</param>
/// <returns>String collection of scripts</returns>
internal StringCollection GetTableScripts(string tableName, string schemaName)
{
return (schemaName != null) ? Database?.Tables[tableName, schemaName]?.Script()
: Database?.Tables[tableName]?.Script();
}
/// <summary>
/// Script a view using SMO
/// </summary>
/// <param name="viewName">View name</param>
/// <param name="schemaName">Schema name </param>
/// <returns>String collection of scripts</returns>
internal StringCollection GetViewScripts(string viewName, string schemaName)
{
return (schemaName != null) ? Database?.Views[viewName, schemaName]?.Script()
: Database?.Views[viewName]?.Script();
}
/// <summary>
/// Script a stored procedure using SMO
/// </summary>
/// <param name="storedProcedureName">Stored Procedure name</param>
/// <param name="schemaName">Schema Name</param>
/// <returns>String collection of scripts</returns>
internal StringCollection GetStoredProcedureScripts(string viewName, string schemaName)
{
return (schemaName != null) ? Database?.StoredProcedures[viewName, schemaName]?.Script()
: Database?.StoredProcedures[viewName]?.Script();
}
/// <summary>
/// Script a object using SMO and write to a file.
/// </summary>
/// <param name="sqlScriptGetter">Function that returns the SMO scripts for an object</param>
/// <param name="objectName">SQL object name</param>
/// <param name="schemaName">Schema name or null</param>
/// <param name="objectType">Type of SQL object</param>
/// <returns>Location object representing URI and range of the script file</returns>
internal Location[] GetSqlObjectDefinition(
ScriptGetter sqlScriptGetter,
string objectName,
string schemaName,
string objectType)
{
StringCollection scripts = sqlScriptGetter(objectName, schemaName);
string tempFileName = (schemaName != null) ? Path.Combine(this.tempPath, string.Format("{0}.{1}.sql", schemaName, objectName))
: Path.Combine(this.tempPath, string.Format("{0}.sql", objectName));
if (scripts != null)
{
int lineNumber = 0;
using (StreamWriter scriptFile = new StreamWriter(File.Open(tempFileName, FileMode.Create, FileAccess.ReadWrite)))
{
foreach (string script in scripts)
{
string createSyntax = string.Format("CREATE {0}", objectType);
if (script.IndexOf(createSyntax, StringComparison.OrdinalIgnoreCase) >= 0)
{
scriptFile.WriteLine(script);
lineNumber = GetStartOfCreate(script, createSyntax);
}
}
}
return GetLocationFromFile(tempFileName, lineNumber);
}
return null;
}
}
}

View File

@@ -68,7 +68,7 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlContext
}
/// <summary>
/// Gets a flag determining if suggestons are enabled
/// Gets a flag determining if suggestions are enabled
/// </summary>
public bool IsSuggestionsEnabled
{
@@ -90,6 +90,17 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlContext
&& this.SqlTools.IntelliSense.EnableQuickInfo.Value;
}
}
/// <summary>
/// Gets a flag determining if IntelliSense is enabled
/// </summary>
public bool IsIntelliSenseEnabled
{
get
{
return this.SqlTools.IntelliSense.EnableIntellisense;
}
}
}
/// <summary>

View File

@@ -109,5 +109,19 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility
|| ch == '('
|| ch == ')';
}
/// <summary>
/// Remove square bracket syntax from a token string
/// </summary>
/// <param name="tokenText"></param>
/// <returns> string with outer brackets removed</returns>
public static string RemoveSquareBracketSyntax(string tokenText)
{
if(tokenText.StartsWith("[") && tokenText.EndsWith("]"))
{
return tokenText.Substring(1, tokenText.Length - 2);
}
return tokenText;
}
}
}

View File

@@ -0,0 +1,279 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System.Collections.Generic;
using System.Threading.Tasks;
using System.IO;
using System;
using Microsoft.SqlServer.Management.SqlParser.Binder;
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
using Microsoft.SqlServer.Management.SqlParser.Parser;
using Microsoft.SqlTools.ServiceLayer.Connection;
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
using Microsoft.SqlTools.ServiceLayer.LanguageServices;
using Microsoft.SqlTools.ServiceLayer.SqlContext;
using Microsoft.SqlTools.ServiceLayer.Test.QueryExecution;
using Microsoft.SqlTools.ServiceLayer.Workspace;
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
using Microsoft.SqlTools.Test.Utility;
using Location = Microsoft.SqlTools.ServiceLayer.Workspace.Contracts.Location;
using Moq;
using Xunit;
namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
{
/// <summary>
/// Tests for the language service peek definition/ go to definition feature
/// </summary>
public class PeekDefinitionTests
{
private const int TaskTimeout = 30000;
private readonly string testScriptUri = TestObjects.ScriptUri;
private readonly string testConnectionKey = "testdbcontextkey";
private Mock<ConnectedBindingQueue> bindingQueue;
private Mock<WorkspaceService<SqlToolsSettings>> workspaceService;
private Mock<RequestContext<Location[]>> requestContext;
private Mock<IBinder> binder;
private TextDocumentPosition textDocument;
private const string OwnerUri = "testFile1";
private const string ViewOwnerUri = "testFile2";
private const string TriggerOwnerUri = "testFile3";
private void InitializeTestObjects()
{
// initial cursor position in the script file
textDocument = new TextDocumentPosition
{
TextDocument = new TextDocumentIdentifier {Uri = this.testScriptUri},
Position = new Position
{
Line = 0,
Character = 23
}
};
// default settings are stored in the workspace service
WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings = new SqlToolsSettings();
// set up file for returning the query
var fileMock = new Mock<ScriptFile>();
fileMock.SetupGet(file => file.Contents).Returns(Common.StandardQuery);
fileMock.SetupGet(file => file.ClientFilePath).Returns(this.testScriptUri);
// set up workspace mock
workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
.Returns(fileMock.Object);
// setup binding queue mock
bindingQueue = new Mock<ConnectedBindingQueue>();
bindingQueue.Setup(q => q.AddConnectionContext(It.IsAny<ConnectionInfo>()))
.Returns(this.testConnectionKey);
// inject mock instances into the Language Service
LanguageService.WorkspaceServiceInstance = workspaceService.Object;
LanguageService.ConnectionServiceInstance = TestObjects.GetTestConnectionService();
ConnectionInfo connectionInfo = TestObjects.GetTestConnectionInfo();
LanguageService.ConnectionServiceInstance.OwnerToConnectionMap.Add(this.testScriptUri, connectionInfo);
LanguageService.Instance.BindingQueue = bindingQueue.Object;
// setup the mock for SendResult
requestContext = new Mock<RequestContext<Location[]>>();
requestContext.Setup(rc => rc.SendResult(It.IsAny<Location[]>()))
.Returns(Task.FromResult(0));
// setup the IBinder mock
binder = new Mock<IBinder>();
binder.Setup(b => b.Bind(
It.IsAny<IEnumerable<ParseResult>>(),
It.IsAny<string>(),
It.IsAny<BindMode>()));
var testScriptParseInfo = new ScriptParseInfo();
LanguageService.Instance.AddOrUpdateScriptParseInfo(this.testScriptUri, testScriptParseInfo);
testScriptParseInfo.IsConnected = true;
testScriptParseInfo.ConnectionKey = LanguageService.Instance.BindingQueue.AddConnectionContext(connectionInfo);
// setup the binding context object
ConnectedBindingContext bindingContext = new ConnectedBindingContext();
bindingContext.Binder = binder.Object;
bindingContext.MetadataDisplayInfoProvider = new MetadataDisplayInfoProvider();
LanguageService.Instance.BindingQueue.BindingContextMap.Add(testScriptParseInfo.ConnectionKey, bindingContext);
}
/// <summary>
/// Tests the definition event handler. When called with no active connection, no definition is sent
/// </summary>
[Fact]
public void DefinitionsHandlerWithNoConnectionTest()
{
InitializeTestObjects();
// request the completion list
Task handleCompletion = LanguageService.HandleDefinitionRequest(textDocument, requestContext.Object);
handleCompletion.Wait(TaskTimeout);
// verify that send result was not called
requestContext.Verify(m => m.SendResult(It.IsAny<Location[]>()), Times.Never());
}
#if LIVE_CONNECTION_TESTS
/// <summary>
/// Test get definition for a table object with active connection
/// </summary>
[Fact]
public void GetTableDefinitionTest()
{
// Get live connectionInfo
ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfoForDefinition();
PeekDefinition peekDefinition = new PeekDefinition(connInfo);
string objectName = "test_table";
string schemaName = null;
string objectType = "TABLE";
Location[] locations = peekDefinition.GetSqlObjectDefinition(peekDefinition.GetTableScripts, objectName, schemaName, objectType);
Assert.NotNull(locations);
Cleanup(locations);
}
[Fact]
public void GetUnsupportedDefinitionForFullScript()
{
ScriptFile scriptFile;
TextDocumentPosition textDocument = new TextDocumentPosition
{
TextDocument = new TextDocumentIdentifier { Uri = OwnerUri },
Position = new Position
{
Line = 0,
Character = 20
}
};
ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfo(out scriptFile);
scriptFile.Contents = "select * from dbo.func ()";
var languageService = LanguageService.Instance;
ScriptParseInfo scriptInfo = new ScriptParseInfo { IsConnected = true };
languageService.ScriptParseInfoMap.Add(OwnerUri, scriptInfo);
var locations = languageService.GetDefinition(textDocument, scriptFile, connInfo);
Assert.Null(locations);
}
/// <summary>
/// Test get definition for a view object with active connection
/// </summary>
[Fact]
public void GetViewDefinitionTest()
{
ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfoForDefinition();
PeekDefinition peekDefinition = new PeekDefinition(connInfo);
string objectName = "objects";
string schemaName = "sys";
string objectType = "VIEW";
Location[] locations = peekDefinition.GetSqlObjectDefinition(peekDefinition.GetViewScripts, objectName, schemaName, objectType);
Assert.NotNull(locations);
Cleanup(locations);
}
/// <summary>
/// Test get definition for an invalid view object with no schema name and with active connection
/// </summary>
[Fact]
public void GetViewDefinitionInvalidObjectTest()
{
ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfoForDefinition();
PeekDefinition peekDefinition = new PeekDefinition(connInfo);
string objectName = "objects";
string schemaName = null;
string objectType = "VIEW";
Location[] locations = peekDefinition.GetSqlObjectDefinition(peekDefinition.GetViewScripts, objectName, schemaName, objectType);
Assert.Null(locations);
}
/// <summary>
/// Test get definition for a stored procedure object with active connection
/// </summary>
[Fact]
public void GetStoredProcedureDefinitionTest()
{
ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfoForDefinition();
PeekDefinition peekDefinition = new PeekDefinition(connInfo);
string objectName = "SP1";
string schemaName = "dbo";
string objectType = "PROCEDURE";
Location[] locations = peekDefinition.GetSqlObjectDefinition(peekDefinition.GetStoredProcedureScripts, objectName, schemaName, objectType);
Assert.NotNull(locations);
Cleanup(locations);
}
/// <summary>
/// Test get definition for a stored procedure object that does not exist with active connection
/// </summary>
[Fact]
public void GetStoredProcedureDefinitionFailureTest()
{
ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfoForDefinition();
PeekDefinition peekDefinition = new PeekDefinition(connInfo);
string objectName = "SP2";
string schemaName = "dbo";
string objectType = "PROCEDURE";
Location[] locations = peekDefinition.GetSqlObjectDefinition(peekDefinition.GetStoredProcedureScripts, objectName, schemaName, objectType);
Assert.Null(locations);
}
/// <summary>
/// Test get definition for a stored procedure object with active connection and no schema
/// </summary>
[Fact]
public void GetStoredProcedureDefinitionWithoutSchemaTest()
{
ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfoForDefinition();
PeekDefinition peekDefinition = new PeekDefinition(connInfo);
string objectName = "SP1";
string schemaName = null;
string objectType = "PROCEDURE";
Location[] locations = peekDefinition.GetSqlObjectDefinition(peekDefinition.GetStoredProcedureScripts, objectName, schemaName, objectType);
Assert.NotNull(locations);
Cleanup(locations);
}
/// <summary>
/// Helper method to clean up script files
/// </summary>
private void Cleanup(Location[] locations)
{
Uri fileUri = new Uri(locations[0].Uri);
if (File.Exists(fileUri.LocalPath))
{
try
{
File.Delete(fileUri.LocalPath);
}
catch(Exception)
{
}
}
}
#endif
}
}

View File

@@ -191,6 +191,27 @@ namespace Microsoft.SqlTools.Test.Utility
connectionService.TryFindConnection(ownerUri, out connInfo);
return connInfo;
}
public static ConnectionInfo InitLiveConnectionInfoForDefinition()
{
TestObjects.InitializeTestServices();
string ownerUri = ScriptUri;
var connectionService = TestObjects.GetLiveTestConnectionService();
var connectionResult =
connectionService
.Connect(new ConnectParams()
{
OwnerUri = ownerUri,
Connection = TestObjects.GetIntegratedTestConnectionDetails()
});
connectionResult.Wait();
ConnectionInfo connInfo = null;
connectionService.TryFindConnection(ownerUri, out connInfo);
return connInfo;
}
}
/// <summary>

View File

@@ -211,6 +211,49 @@ namespace Microsoft.SqlTools.ServiceLayer.TestDriver.Tests
}
}
/// <summary>
/// Peek Definition/ Go to definition
/// </summary>
/// <returns></returns>
[Fact]
public async Task DefinitionTest()
{
using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile())
using (TestHelper testHelper = new TestHelper())
{
string query = "SELECT * FROM sys.objects";
int lineNumber = 0;
int position = 23;
testHelper.WriteToFile(queryTempFile.FilePath, query);
DidOpenTextDocumentNotification openParams = new DidOpenTextDocumentNotification
{
TextDocument = new TextDocumentItem
{
Uri = queryTempFile.FilePath,
LanguageId = "enu",
Version = 1,
Text = query
}
};
await testHelper.RequestOpenDocumentNotification(openParams);
Thread.Sleep(500);
bool connected = await testHelper.Connect(queryTempFile.FilePath, ConnectionTestUtils.LocalhostConnection);
Assert.True(connected, "Connection is successful");
Thread.Sleep(10000);
// Request definition for "objects"
Location[] locations = await testHelper.RequestDefinition(queryTempFile.FilePath, query, lineNumber, position);
Assert.True(locations != null, "Location is not null and not empty");
await testHelper.Disconnect(queryTempFile.FilePath);
}
}
/// <summary>
/// Validate the configuration change event
/// </summary>

View File

@@ -257,6 +257,29 @@ namespace Microsoft.SqlTools.ServiceLayer.TestDriver.Tests
return result;
}
/// <summary>
/// Request definition( peek definition/go to definition) for a sql object in a sql string
/// </summary>
public async Task<Location[]> RequestDefinition(string ownerUri, string text, int line, int character)
{
// Write the text to a backing file
lock (fileLock)
{
System.IO.File.WriteAllText(ownerUri, text);
}
var definitionParams = new TextDocumentPosition();
definitionParams.TextDocument = new TextDocumentIdentifier();
definitionParams.TextDocument.Uri = ownerUri;
definitionParams.Position = new Position();
definitionParams.Position.Line = line;
definitionParams.Position.Character = character;
// Send definition request
var result = await Driver.SendRequest(DefinitionRequest.Type, definitionParams);
return result;
}
/// <summary>
/// Run a query using a given connection bound to a URI
/// </summary>