diff --git a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/PeekDefinition.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/PeekDefinition.cs index b6233ab2..edfcd694 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/PeekDefinition.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/PeekDefinition.cs @@ -72,10 +72,10 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices { try { - // Reuse existing connection + // Reuse existing connection Server server = new Server(this.serverConnection); // The default database name is the database name of the server connection - string dbName = this.serverConnection.DatabaseName; + string dbName = this.serverConnection.DatabaseName; if (this.connectionInfo != null) { // If there is a query DbConnection, use that connection to get the database name @@ -86,10 +86,11 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices if (!string.IsNullOrEmpty(connection.Database)) { dbName = connection.Database; - } - } + } + } } this.database = new Database(server, dbName); + this.database.Refresh(); } catch (ConnectionFailureException cfe) { @@ -146,8 +147,13 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices { continue; } - // if declarartionItem matches the selected token, script SMO using that type - if (declarationItem.Title.Equals(tokenText)) + if (this.Database == null) + { + return GetDefinitionErrorResult(SR.PeekDefinitionDatabaseError); + } + StringComparison caseSensitivity = this.Database.CaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; + // if declarationItem matches the selected token, script SMO using that type + if (declarationItem.Title.Equals (tokenText, caseSensitivity)) { return GetDefinitionUsingDeclarationType(declarationItem.Type, declarationItem.DatabaseQualifiedName, tokenText, schemaName); } @@ -172,7 +178,12 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices /// internal DefinitionResult GetDefinitionUsingQuickInfoText(string quickInfoText, string tokenText, string schemaName) { - string tokenType = GetTokenTypeFromQuickInfo(quickInfoText, tokenText); + if (this.Database == null) + { + return GetDefinitionErrorResult(SR.PeekDefinitionDatabaseError); + } + StringComparison caseSensitivity = this.Database.CaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; + string tokenType = GetTokenTypeFromQuickInfo(quickInfoText, tokenText, caseSensitivity); if (tokenType != null) { if (sqlScriptGettersFromQuickInfo.ContainsKey(tokenType.ToLowerInvariant())) @@ -183,7 +194,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices // If all fails, the default schema name is assumed to be "dbo" if ((connectionInfo != null && connectionInfo.ConnectionDetails.AuthenticationType.Equals(Constants.SqlLoginAuthenticationType)) && string.IsNullOrEmpty(schemaName)) { - string fullObjectName = this.GetFullObjectNameFromQuickInfo(quickInfoText, tokenText); + string fullObjectName = this.GetFullObjectNameFromQuickInfo(quickInfoText, tokenText, caseSensitivity); schemaName = this.GetSchemaFromDatabaseQualifiedName(fullObjectName, tokenText); } Location[] locations = GetSqlObjectDefinition( @@ -377,8 +388,9 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices /// /// QuickInfo Text for this token /// Token Text + /// StringComparison enum /// - internal string GetFullObjectNameFromQuickInfo(string quickInfoText, string tokenText) + internal string GetFullObjectNameFromQuickInfo(string quickInfoText, string tokenText, StringComparison caseSensitivity) { if (string.IsNullOrEmpty(quickInfoText) || string.IsNullOrEmpty(tokenText)) { @@ -386,7 +398,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices } // extract full object name from quickInfo text string[] tokens = quickInfoText.Split(' '); - List tokenList = tokens.Where(el => el.Contains(tokenText)).ToList(); + List tokenList = tokens.Where(el => el.IndexOf(tokenText, caseSensitivity) >= 0).ToList(); return (tokenList?.Count() > 0) ? tokenList[0] : null; } @@ -395,8 +407,9 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices /// /// QuickInfo Text for this token /// + /// StringComparison enum /// - internal string GetTokenTypeFromQuickInfo(string quickInfoText, string tokenText) + internal string GetTokenTypeFromQuickInfo(string quickInfoText, string tokenText, StringComparison caseSensitivity) { if (string.IsNullOrEmpty(quickInfoText) || string.IsNullOrEmpty(tokenText)) { @@ -404,7 +417,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices } // extract string denoting the token type from quickInfo text string[] tokens = quickInfoText.Split(' '); - List indexList = tokens.Select((s, i) => new { i, s }).Where(el => (el.s).Contains(tokenText)).Select(el => el.i).ToList(); + List indexList = tokens.Select((s, i) => new { i, s }).Where(el => (el.s).IndexOf(tokenText, caseSensitivity) >= 0 ).Select(el => el.i).ToList(); return (indexList?.Count() > 0) ? String.Join(" ", tokens.Take(indexList[0])) : null; } diff --git a/src/Microsoft.SqlTools.ServiceLayer/sr.cs b/src/Microsoft.SqlTools.ServiceLayer/sr.cs index 78d207e2..f45c58a1 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/sr.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/sr.cs @@ -381,6 +381,14 @@ namespace Microsoft.SqlTools.ServiceLayer } } + public static string PeekDefinitionDatabaseError + { + get + { + return Keys.GetString(Keys.PeekDefinitionDatabaseError); + } + } + public static string PeekDefinitionNotConnectedError { get @@ -643,6 +651,9 @@ namespace Microsoft.SqlTools.ServiceLayer public const string PeekDefinitionNoResultsError = "PeekDefinitionNoResultsError"; + public const string PeekDefinitionDatabaseError = "PeekDefinitionDatabaseError"; + + public const string PeekDefinitionNotConnectedError = "PeekDefinitionNotConnectedError"; diff --git a/src/Microsoft.SqlTools.ServiceLayer/sr.resx b/src/Microsoft.SqlTools.ServiceLayer/sr.resx index 49f0cd01..f50b8318 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/sr.resx +++ b/src/Microsoft.SqlTools.ServiceLayer/sr.resx @@ -343,6 +343,10 @@ No results were found. + + No database object was retrieved. + + Please connect to a server. diff --git a/src/Microsoft.SqlTools.ServiceLayer/sr.strings b/src/Microsoft.SqlTools.ServiceLayer/sr.strings index ff627a91..13a58333 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/sr.strings +++ b/src/Microsoft.SqlTools.ServiceLayer/sr.strings @@ -160,6 +160,8 @@ PeekDefinitionError(string errorMessage) = An unexpected error occurred during P PeekDefinitionNoResultsError = No results were found. +PeekDefinitionDatabaseError = No database object was retrieved. + PeekDefinitionNotConnectedError = Please connect to a server. PeekDefinitionTimedoutError = Operation timed out. diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/LanguageServer/PeekDefinitionTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/LanguageServer/PeekDefinitionTests.cs index 34c7e426..65f0fa72 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/LanguageServer/PeekDefinitionTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/LanguageServer/PeekDefinitionTests.cs @@ -29,24 +29,24 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.LanguageServices private const string OwnerUri = "testFile1"; private const string ReturnTableFunctionName = "pd_returnTable"; private const string ReturnTableTableFunctionQuery = @" -CREATE FUNCTION [dbo].[" + ReturnTableFunctionName + @"] () -RETURNS TABLE -AS -RETURN -( - select * from master.dbo.spt_monitor -); +CREATE FUNCTION [dbo].[" + ReturnTableFunctionName + @"] () +RETURNS TABLE +AS +RETURN +( + select * from master.dbo.spt_monitor +); GO"; private const string AddTwoFunctionName = "pd_addTwo"; private const string AddTwoFunctionQuery = @" -CREATE FUNCTION[dbo].[" + AddTwoFunctionName + @"](@number int) +CREATE FUNCTION[dbo].[" + AddTwoFunctionName + @"](@number int) RETURNS int -AS +AS BEGIN RETURN @number + 2; - END; + END; GO"; @@ -147,11 +147,14 @@ GO"; /// Test GetDefinition with an unsupported type(schema - dbo). Expect a error result. /// [Fact] - public void GetUnsupportedDefinitionErrorTest() + public async Task GetUnsupportedDefinitionErrorTest() { + TestConnectionResult connectionResult = TestObjects.InitLiveConnectionInfo(); + connectionResult.ScriptFile.Contents = "select * from dbo.func ()"; + string ownerUri = connectionResult.ScriptFile.ClientFilePath; TextDocumentPosition textDocument = new TextDocumentPosition { - TextDocument = new TextDocumentIdentifier { Uri = OwnerUri }, + TextDocument = new TextDocumentIdentifier { Uri = ownerUri }, Position = new Position { Line = 0, @@ -159,17 +162,16 @@ GO"; Character = 15 } }; - - TestConnectionResult connectionResult = TestObjects.InitLiveConnectionInfo(); - connectionResult.ScriptFile.Contents = "select * from dbo.func ()"; - var languageService = new LanguageService(); - ScriptParseInfo scriptInfo = new ScriptParseInfo { IsConnected = true }; - languageService.ScriptParseInfoMap.Add(OwnerUri, scriptInfo); + connectionResult.TextDocumentPosition = textDocument; + var languageService = LanguageService.Instance; + await languageService.UpdateLanguageServiceOnConnection(connectionResult.ConnectionInfo); + ScriptParseInfo parseInfo = languageService.GetScriptParseInfo(connectionResult.ScriptFile.ClientFilePath); + Assert.NotNull(parseInfo); // When I call the language service var result = languageService.GetDefinition(textDocument, connectionResult.ScriptFile, connectionResult.ConnectionInfo); - // Then I expect null locations and an error to be reported + // Then I expect non null result with error flag set Assert.NotNull(result); Assert.True(result.IsErrorResult); } @@ -642,7 +644,7 @@ GO"; /// /// Test get definition using quickInfo text for a view object with active connection - /// Expect a non-null result with location + /// Expect a non-null result with location /// [Fact] public void GetDefinitionUsingQuickInfoTextWithNonexistentObjectTest() diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/PeekDefinitionTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/PeekDefinitionTests.cs index 0309ba01..e75948b9 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/PeekDefinitionTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/PeekDefinitionTests.cs @@ -233,7 +233,23 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices PeekDefinition peekDefinition = new PeekDefinition(null, null); string objectName = "testTable"; string quickInfoText = "table master.dbo.testTable"; - string result = peekDefinition.GetFullObjectNameFromQuickInfo(quickInfoText, objectName); + string result = peekDefinition.GetFullObjectNameFromQuickInfo(quickInfoText, objectName, StringComparison.Ordinal); + string expected = "master.dbo.testTable"; + Assert.Equal(expected, result); + } + + /// + /// Test extracting the full object name from quickInfoText with case insensitive comparison. + /// Given a valid object name string and a vaild quickInfo string containing the object name + /// Expect the full object name (database.schema.objectName) + /// + [Fact] + public void GetFullObjectNameFromQuickInfoWithValidStringsandIgnoreCaseTest() + { + PeekDefinition peekDefinition = new PeekDefinition(null, null); + string objectName = "testtable"; + string quickInfoText = "table master.dbo.testTable"; + string result = peekDefinition.GetFullObjectNameFromQuickInfo(quickInfoText, objectName, StringComparison.OrdinalIgnoreCase); string expected = "master.dbo.testTable"; Assert.Equal(expected, result); } @@ -251,17 +267,17 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices string objectName = null; string quickInfoText = "table master.dbo.testTable"; - string result = peekDefinition.GetFullObjectNameFromQuickInfo(quickInfoText, objectName); + string result = peekDefinition.GetFullObjectNameFromQuickInfo(quickInfoText, objectName, StringComparison.Ordinal); Assert.Equal(expected, result); quickInfoText = null; objectName = "tableName"; - result = peekDefinition.GetFullObjectNameFromQuickInfo(quickInfoText, objectName); + result = peekDefinition.GetFullObjectNameFromQuickInfo(quickInfoText, objectName, StringComparison.Ordinal); Assert.Equal(expected, result); quickInfoText = null; objectName = null; - result = peekDefinition.GetFullObjectNameFromQuickInfo(quickInfoText, objectName); + result = peekDefinition.GetFullObjectNameFromQuickInfo(quickInfoText, objectName, StringComparison.Ordinal); Assert.Equal(expected, result); } @@ -276,7 +292,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices PeekDefinition peekDefinition = new PeekDefinition(null, null); string objectName = "test"; string quickInfoText = "table master.dbo.tableName"; - string result = peekDefinition.GetFullObjectNameFromQuickInfo(quickInfoText, objectName); + string result = peekDefinition.GetFullObjectNameFromQuickInfo(quickInfoText, objectName, StringComparison.Ordinal); string expected = null; Assert.Equal(expected, result); } @@ -292,7 +308,24 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices PeekDefinition peekDefinition = new PeekDefinition(null, null); string objectName = "tableName"; string quickInfoText = "table master.dbo.tableName"; - string result = peekDefinition.GetTokenTypeFromQuickInfo(quickInfoText, objectName); + string result = peekDefinition.GetTokenTypeFromQuickInfo(quickInfoText, objectName, StringComparison.Ordinal); + string expected = "table"; + Assert.Equal(expected, result); + } + + + /// + /// Test extracting the object type from quickInfoText with case insensitive comparison. + /// Given a valid object name string and a vaild quickInfo string containing the object name + /// Expect correct object type + /// + [Fact] + public void GetTokenTypeFromQuickInfoWithValidStringsandIgnoreCaseTest() + { + PeekDefinition peekDefinition = new PeekDefinition(null, null); + string objectName = "tablename"; + string quickInfoText = "table master.dbo.tableName"; + string result = peekDefinition.GetTokenTypeFromQuickInfo(quickInfoText, objectName, StringComparison.OrdinalIgnoreCase); string expected = "table"; Assert.Equal(expected, result); } @@ -310,17 +343,17 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices string objectName = null; string quickInfoText = "table master.dbo.testTable"; - string result = peekDefinition.GetTokenTypeFromQuickInfo(quickInfoText, objectName); + string result = peekDefinition.GetTokenTypeFromQuickInfo(quickInfoText, objectName, StringComparison.Ordinal); Assert.Equal(expected, result); quickInfoText = null; objectName = "tableName"; - result = peekDefinition.GetTokenTypeFromQuickInfo(quickInfoText, objectName); + result = peekDefinition.GetTokenTypeFromQuickInfo(quickInfoText, objectName, StringComparison.Ordinal); Assert.Equal(expected, result); quickInfoText = null; objectName = null; - result = peekDefinition.GetTokenTypeFromQuickInfo(quickInfoText, objectName); + result = peekDefinition.GetTokenTypeFromQuickInfo(quickInfoText, objectName, StringComparison.Ordinal); Assert.Equal(expected, result); } @@ -335,7 +368,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices PeekDefinition peekDefinition = new PeekDefinition(null, null); string objectName = "test"; string quickInfoText = "table master.dbo.tableName"; - string result = peekDefinition.GetTokenTypeFromQuickInfo(quickInfoText, objectName); + string result = peekDefinition.GetTokenTypeFromQuickInfo(quickInfoText, objectName, StringComparison.Ordinal); string expected = null; Assert.Equal(expected, result); }