Add error handling for Azure Exceptions (#177)

* Add error handling for Azure Exceptions

* Add SRGen for string

* Add specific exception messages

* Move DefinitionResult class

* Add SqlLogin constant

* Add error scenarios

* revert timeout duration

* Modify tests

* Modify tests

* Add tests

* Revert live connection definition

* Modify DefinitionsHandlerWithNoConnectionTest

* fix test after merge

* Code review changes

* Code review changes

* Code review changes
This commit is contained in:
Sharon Ravindran
2016-12-14 16:04:47 -08:00
committed by GitHub
parent 6f15ac0b8f
commit 9c6162282a
12 changed files with 431 additions and 94 deletions

View File

@@ -13,6 +13,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol
public const string ContentLengthFormatString = "Content-Length: {0}\r\n\r\n"; public const string ContentLengthFormatString = "Content-Length: {0}\r\n\r\n";
public static readonly JsonSerializerSettings JsonSerializerSettings; public static readonly JsonSerializerSettings JsonSerializerSettings;
public static readonly string SqlLoginAuthenticationType = "SqlLogin";
static Constants() static Constants()
{ {
JsonSerializerSettings = new JsonSerializerSettings(); JsonSerializerSettings = new JsonSerializerSettings();

View File

@@ -57,7 +57,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
/// <summary> /// <summary>
/// Queue a binding request item /// Queue a binding request item
/// </summary> /// </summary>
public QueueItem QueueBindingOperation( public virtual QueueItem QueueBindingOperation(
string key, string key,
Func<IBindingContext, CancellationToken, object> bindOperation, Func<IBindingContext, CancellationToken, object> bindOperation,
Func<IBindingContext, object> timeoutOperation = null, Func<IBindingContext, object> timeoutOperation = null,

View File

@@ -14,5 +14,16 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts
RequestType<TextDocumentPosition, Location[]> Type = RequestType<TextDocumentPosition, Location[]> Type =
RequestType<TextDocumentPosition, Location[]>.Create("textDocument/definition"); RequestType<TextDocumentPosition, Location[]>.Create("textDocument/definition");
} }
/// <summary>
/// Error object for Definition
/// </summary>
public class DefinitionError
{
/// <summary>
/// Error message
/// </summary>
public string message { get; set; }
}
} }

View File

@@ -236,6 +236,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
// Store the SqlToolsContext for future use // Store the SqlToolsContext for future use
Context = context; Context = context;
} }
#endregion #endregion
@@ -317,11 +318,17 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
ConnectionInfo connInfo; ConnectionInfo connInfo;
var scriptFile = LanguageService.WorkspaceServiceInstance.Workspace.GetFile(textDocumentPosition.TextDocument.Uri); var scriptFile = LanguageService.WorkspaceServiceInstance.Workspace.GetFile(textDocumentPosition.TextDocument.Uri);
LanguageService.ConnectionServiceInstance.TryFindConnection(scriptFile.ClientFilePath, out connInfo); LanguageService.ConnectionServiceInstance.TryFindConnection(scriptFile.ClientFilePath, out connInfo);
DefinitionResult definitionResult = LanguageService.Instance.GetDefinition(textDocumentPosition, scriptFile, connInfo);
Location[] locations = LanguageService.Instance.GetDefinition(textDocumentPosition, scriptFile, connInfo); if (definitionResult != null)
if (locations != null) {
{ if (definitionResult.IsErrorResult)
await requestContext.SendResult(locations); {
await requestContext.SendError( new DefinitionError { message = definitionResult.Message });
}
else
{
await requestContext.SendResult(definitionResult.Locations);
}
} }
} }
DocumentStatusHelper.SendStatusChange(requestContext, textDocumentPosition, DocumentStatusHelper.DefinitionRequestCompleted); DocumentStatusHelper.SendStatusChange(requestContext, textDocumentPosition, DocumentStatusHelper.DefinitionRequestCompleted);
@@ -688,7 +695,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
/// <param name="scriptFile"></param> /// <param name="scriptFile"></param>
/// <param name="connInfo"></param> /// <param name="connInfo"></param>
/// <returns> Location with the URI of the script file</returns> /// <returns> Location with the URI of the script file</returns>
internal Location[] GetDefinition(TextDocumentPosition textDocumentPosition, ScriptFile scriptFile, ConnectionInfo connInfo) internal DefinitionResult GetDefinition(TextDocumentPosition textDocumentPosition, ScriptFile scriptFile, ConnectionInfo connInfo)
{ {
// Parse sql // Parse sql
ScriptParseInfo scriptParseInfo = GetScriptParseInfo(textDocumentPosition.TextDocument.Uri); ScriptParseInfo scriptParseInfo = GetScriptParseInfo(textDocumentPosition.TextDocument.Uri);
@@ -726,27 +733,57 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
int parserLine = textDocumentPosition.Position.Line + 1; int parserLine = textDocumentPosition.Position.Line + 1;
int parserColumn = textDocumentPosition.Position.Character + 1; int parserColumn = textDocumentPosition.Position.Character + 1;
IEnumerable<Declaration> declarationItems = Resolver.FindCompletions( IEnumerable<Declaration> declarationItems = Resolver.FindCompletions(
scriptParseInfo.ParseResult, scriptParseInfo.ParseResult,
parserLine, parserColumn, parserLine, parserColumn,
bindingContext.MetadataDisplayInfoProvider); bindingContext.MetadataDisplayInfoProvider);
// Match token with the suggestions(declaration items) returned // Match token with the suggestions(declaration items) returned
string schemaName = GetSchemaName(scriptParseInfo, textDocumentPosition.Position, scriptFile);
PeekDefinition peekDefinition = new PeekDefinition(bindingContext.ServerConnection); string schemaName = this.GetSchemaName(scriptParseInfo, textDocumentPosition.Position, scriptFile);
return peekDefinition.GetScript(declarationItems, tokenText, schemaName); PeekDefinition peekDefinition = new PeekDefinition(bindingContext.ServerConnection, connInfo);
return peekDefinition.GetScript(declarationItems, tokenText, schemaName);
},
timeoutOperation: (bindingContext) =>
{
// return error result
return new DefinitionResult
{
IsErrorResult = true,
Message = SR.PeekDefinitionTimedoutError,
Locations = null
};
}); });
// wait for the queue item // wait for the queue item
queueItem.ItemProcessed.WaitOne(); queueItem.ItemProcessed.WaitOne();
return queueItem.GetResultAsT<Location[]>(); return queueItem.GetResultAsT<DefinitionResult>();
}
catch (Exception ex)
{
// if any exceptions are raised return error result with message
Logger.Write(LogLevel.Error, "Exception in GetDefinition " + ex.ToString());
return new DefinitionResult
{
IsErrorResult = true,
Message = SR.PeekDefinitionError(ex.Message),
Locations = null
};
} }
finally finally
{ {
Monitor.Exit(scriptParseInfo.BuildingMetadataLock); Monitor.Exit(scriptParseInfo.BuildingMetadataLock);
} }
} }
else
return null; {
// User is not connected.
return new DefinitionResult
{
IsErrorResult = true,
Message = SR.PeekDefinitionNotConnectedError,
Locations = null
};
}
} }
/// <summary> /// <summary>
@@ -755,7 +792,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
/// <param name="scriptParseInfo"></param> /// <param name="scriptParseInfo"></param>
/// <param name="position"></param> /// <param name="position"></param>
/// <param name="scriptFile"></param> /// <param name="scriptFile"></param>
/// <returns> schema nama</returns> /// <returns> schema name</returns>
private string GetSchemaName(ScriptParseInfo scriptParseInfo, Position position, ScriptFile scriptFile) private string GetSchemaName(ScriptParseInfo scriptParseInfo, Position position, ScriptFile scriptFile)
{ {
// Offset index by 1 for sql parser // Offset index by 1 for sql parser

View File

@@ -7,12 +7,12 @@ using System.IO;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Data.SqlClient; using System.Data.SqlClient;
using System.Runtime.InteropServices;
using Microsoft.SqlServer.Management.Smo; using Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlServer.Management.Common; using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.SqlParser.Intellisense; using Microsoft.SqlServer.Management.SqlParser.Intellisense;
using Microsoft.SqlTools.ServiceLayer.Utility; using Microsoft.SqlTools.ServiceLayer.Utility;
using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.Connection;
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts; using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
@@ -23,10 +23,11 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
/// </summary> /// </summary>
internal class PeekDefinition internal class PeekDefinition
{ {
private bool error;
private string errorMessage;
private ServerConnection serverConnection; private ServerConnection serverConnection;
private ConnectionInfo connectionInfo;
private Database database; private Database database;
private string tempPath; private string tempPath;
internal delegate StringCollection ScriptGetter(string objectName, string schemaName); internal delegate StringCollection ScriptGetter(string objectName, string schemaName);
@@ -42,9 +43,10 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
/// Initialize a Peek Definition helper object /// Initialize a Peek Definition helper object
/// </summary> /// </summary>
/// <param name="serverConnection">SMO Server connection</param> /// <param name="serverConnection">SMO Server connection</param>
internal PeekDefinition(ServerConnection serverConnection) internal PeekDefinition(ServerConnection serverConnection, ConnectionInfo connInfo)
{ {
this.serverConnection = serverConnection; this.serverConnection = serverConnection;
this.connectionInfo = connInfo;
DirectoryInfo tempScriptDirectory = Directory.CreateDirectory(Path.GetTempPath() + "mssql_definition"); DirectoryInfo tempScriptDirectory = Directory.CreateDirectory(Path.GetTempPath() + "mssql_definition");
this.tempPath = tempScriptDirectory.FullName; this.tempPath = tempScriptDirectory.FullName;
@@ -62,20 +64,29 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
try try
{ {
// Get server object from connection // Get server object from connection
SqlConnection sqlConn = new SqlConnection(this.serverConnection.ConnectionString); SqlConnection sqlConn = new SqlConnection(this.serverConnection.ConnectionString);
sqlConn.Open(); sqlConn.Open();
ServerConnection peekConnection = new ServerConnection(sqlConn); ServerConnection peekConnection = new ServerConnection(sqlConn);
Server server = new Server(peekConnection); Server server = new Server(peekConnection);
this.database = new Database(server, peekConnection.DatabaseName); this.database = new Database(server, peekConnection.DatabaseName);
}
catch (ConnectionFailureException cfe)
{
Logger.Write(LogLevel.Error, "Exception at PeekDefinition Database.get() : " + cfe.Message);
this.error = true;
this.errorMessage = (connectionInfo != null && connectionInfo.IsAzure)? SR.PeekDefinitionAzureError(cfe.Message) : SR.PeekDefinitionError(cfe.Message);
return null;
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Write(LogLevel.Error, "Exception at PeekDefinition Database.get() : " + ex.Message); Logger.Write(LogLevel.Error, "Exception at PeekDefinition Database.get() : " + ex.Message);
} this.error = true;
this.errorMessage = SR.PeekDefinitionError(ex.Message);
return null;
}
} }
} }
return this.database; return this.database;
} }
} }
@@ -114,7 +125,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
{ {
if (Path.DirectorySeparatorChar.Equals('/')) if (Path.DirectorySeparatorChar.Equals('/'))
{ {
tempFileName = "file:" + tempFileName; tempFileName = "file:" + tempFileName;
} }
else else
{ {
@@ -140,7 +151,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
string[] lines = script.Split(new string[] { Environment.NewLine }, StringSplitOptions.None); string[] lines = script.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
for (int lineNumber = 0; lineNumber < lines.Length; lineNumber++) for (int lineNumber = 0; lineNumber < lines.Length; lineNumber++)
{ {
if (lines[lineNumber].IndexOf( createString, StringComparison.OrdinalIgnoreCase) >= 0) if (lines[lineNumber].IndexOf(createString, StringComparison.OrdinalIgnoreCase) >= 0)
{ {
return lineNumber; return lineNumber;
} }
@@ -155,7 +166,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
/// <param name="tokenText"></param> /// <param name="tokenText"></param>
/// <param name="schemaName"></param> /// <param name="schemaName"></param>
/// <returns>Location object of the script file</returns> /// <returns>Location object of the script file</returns>
internal Location[] GetScript(IEnumerable<Declaration> declarationItems, string tokenText, string schemaName) internal DefinitionResult GetScript(IEnumerable<Declaration> declarationItems, string tokenText, string schemaName)
{ {
foreach (Declaration declarationItem in declarationItems) foreach (Declaration declarationItem in declarationItems)
{ {
@@ -167,29 +178,38 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
if (declarationItem.Title.Equals(tokenText)) if (declarationItem.Title.Equals(tokenText))
{ {
// Script object using SMO based on type // Script object using SMO based on type
DeclarationType type = declarationItem.Type; DeclarationType type = declarationItem.Type;
if (sqlScriptGetters.ContainsKey(type) && sqlObjectTypes.ContainsKey(type)) if (sqlScriptGetters.ContainsKey(type) && sqlObjectTypes.ContainsKey(type))
{ {
// On *nix and mac systems, the defaultSchema property throws an Exception when accessed. // On *nix and mac systems, the defaultSchema property throws an Exception when accessed.
// This workaround ensures that a schema name is present by attempting // This workaround ensures that a schema name is present by attempting
// to get the schema name from the declaration item // to get the schema name from the declaration item
// If all fails, the default schema name is assumed to be "dbo" // If all fails, the default schema name is assumed to be "dbo"
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && string.IsNullOrEmpty(schemaName)) if ((connectionInfo != null && connectionInfo.ConnectionDetails.AuthenticationType.Equals(Constants.SqlLoginAuthenticationType)) && string.IsNullOrEmpty(schemaName))
{ {
string fullObjectName = declarationItem.DatabaseQualifiedName; string fullObjectName = declarationItem.DatabaseQualifiedName;
schemaName = this.GetSchemaFromDatabaseQualifiedName(fullObjectName, tokenText); schemaName = this.GetSchemaFromDatabaseQualifiedName(fullObjectName, tokenText);
} }
return GetSqlObjectDefinition( Location[] locations = GetSqlObjectDefinition(
sqlScriptGetters[type], sqlScriptGetters[type],
tokenText, tokenText,
schemaName, schemaName,
sqlObjectTypes[type] sqlObjectTypes[type]
); );
DefinitionResult result = new DefinitionResult
{
IsErrorResult = this.error,
Message = this.errorMessage,
Locations = locations
};
return result;
} }
return null; // sql object type is currently not supported
return GetDefinitionErrorResult(SR.PeekDefinitionTypeNotSupportedError);
} }
} }
return null; // no definition found
return GetDefinitionErrorResult(SR.PeekDefinitionNoResultsError);
} }
/// <summary> /// <summary>
@@ -203,9 +223,9 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
string[] tokens = fullObjectName.Split('.'); string[] tokens = fullObjectName.Split('.');
for (int i = tokens.Length - 1; i > 0; i--) for (int i = tokens.Length - 1; i > 0; i--)
{ {
if(tokens[i].Equals(objectName)) if (tokens[i].Equals(objectName))
{ {
return tokens[i-1]; return tokens[i - 1];
} }
} }
return "dbo"; return "dbo";
@@ -226,7 +246,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
: new Table(this.Database, tableName, schemaName); : new Table(this.Database, tableName, schemaName);
table.Refresh(); table.Refresh();
return table.Script(); return table.Script();
} }
catch (Exception ex) catch (Exception ex)
@@ -251,7 +271,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
: new View(this.Database, viewName, schemaName); : new View(this.Database, viewName, schemaName);
view.Refresh(); view.Refresh();
return view.Script(); return view.Script();
} }
catch (Exception ex) catch (Exception ex)
@@ -276,7 +296,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
: new StoredProcedure(this.Database, sprocName, schemaName); : new StoredProcedure(this.Database, sprocName, schemaName);
sproc.Refresh(); sproc.Refresh();
return sproc.Script(); return sproc.Script();
} }
catch (Exception ex) catch (Exception ex)
@@ -300,30 +320,49 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
string schemaName, string schemaName,
string objectType) string objectType)
{ {
StringCollection scripts = sqlScriptGetter(objectName, schemaName); StringCollection scripts = sqlScriptGetter(objectName, schemaName);
string tempFileName = (schemaName != null) ? Path.Combine(this.tempPath, string.Format("{0}.{1}.sql", schemaName, objectName)) string tempFileName = (schemaName != null) ? Path.Combine(this.tempPath, string.Format("{0}.{1}.sql", schemaName, objectName))
: Path.Combine(this.tempPath, string.Format("{0}.sql", objectName)); : Path.Combine(this.tempPath, string.Format("{0}.sql", objectName));
if (scripts != null) if (scripts != null)
{
int lineNumber = 0;
using (StreamWriter scriptFile = new StreamWriter(File.Open(tempFileName, FileMode.Create, FileAccess.ReadWrite)))
{ {
int lineNumber = 0;
using (StreamWriter scriptFile = new StreamWriter(File.Open(tempFileName, FileMode.Create, FileAccess.ReadWrite)))
{
foreach (string script in scripts) foreach (string script in scripts)
{
string createSyntax = string.Format("CREATE {0}", objectType);
if (script.IndexOf(createSyntax, StringComparison.OrdinalIgnoreCase) >= 0)
{ {
string createSyntax = string.Format("CREATE {0}", objectType); scriptFile.WriteLine(script);
if (script.IndexOf(createSyntax, StringComparison.OrdinalIgnoreCase) >= 0) lineNumber = GetStartOfCreate(script, createSyntax);
{
scriptFile.WriteLine(script);
lineNumber = GetStartOfCreate(script, createSyntax);
}
} }
} }
return GetLocationFromFile(tempFileName, lineNumber);
} }
return GetLocationFromFile(tempFileName, lineNumber);
}
else
{
this.error = true;
this.errorMessage = SR.PeekDefinitionNoResultsError;
return null;
}
}
return null; /// <summary>
/// Helper method to create definition error result object
/// </summary>
/// <param name="errorMessage">Error message</param>
/// <returns> DefinitionResult</returns>
internal DefinitionResult GetDefinitionErrorResult(string errorMessage)
{
return new DefinitionResult
{
IsErrorResult = true,
Message = errorMessage,
Locations = null
};
} }
} }
} }

View File

@@ -0,0 +1,28 @@
//
// 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.ServiceLayer.Workspace.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
{
/// <summary>
/// /// Result object for PeekDefinition
/// </summary>
public class DefinitionResult
{
/// <summary>
/// True, if definition error occured
/// </summary>
public bool IsErrorResult;
/// <summary>
/// Error message, if any
/// </summary>
public string Message { get; set; }
/// <summary>
/// Location object representing the definition script file
/// </summary>
public Location[] Locations;
}
}

View File

@@ -39,7 +39,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
/// <summary> /// <summary>
/// Gets or sets an event to signal when this queue item has been processed /// Gets or sets an event to signal when this queue item has been processed
/// </summary> /// </summary>
public ManualResetEvent ItemProcessed { get; set; } public virtual ManualResetEvent ItemProcessed { get; set; }
/// <summary> /// <summary>
/// Gets or sets the result of the queued task /// Gets or sets the result of the queued task

View File

@@ -341,6 +341,38 @@ namespace Microsoft.SqlTools.ServiceLayer
} }
} }
public static string PeekDefinitionNoResultsError
{
get
{
return Keys.GetString(Keys.PeekDefinitionNoResultsError);
}
}
public static string PeekDefinitionNotConnectedError
{
get
{
return Keys.GetString(Keys.PeekDefinitionNotConnectedError);
}
}
public static string PeekDefinitionTimedoutError
{
get
{
return Keys.GetString(Keys.PeekDefinitionTimedoutError);
}
}
public static string PeekDefinitionTypeNotSupportedError
{
get
{
return Keys.GetString(Keys.PeekDefinitionTypeNotSupportedError);
}
}
public static string WorkspaceServicePositionLineOutOfRange public static string WorkspaceServicePositionLineOutOfRange
{ {
get get
@@ -384,6 +416,16 @@ namespace Microsoft.SqlTools.ServiceLayer
return Keys.GetString(Keys.QueryServiceQueryFailed, message); return Keys.GetString(Keys.QueryServiceQueryFailed, message);
} }
public static string PeekDefinitionAzureError(string errorMessage)
{
return Keys.GetString(Keys.PeekDefinitionAzureError, errorMessage);
}
public static string PeekDefinitionError(string errorMessage)
{
return Keys.GetString(Keys.PeekDefinitionError, errorMessage);
}
public static string WorkspaceServicePositionColumnOutOfRange(int line) public static string WorkspaceServicePositionColumnOutOfRange(int line)
{ {
return Keys.GetString(Keys.WorkspaceServicePositionColumnOutOfRange, line); return Keys.GetString(Keys.WorkspaceServicePositionColumnOutOfRange, line);
@@ -397,7 +439,7 @@ namespace Microsoft.SqlTools.ServiceLayer
[System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class Keys public class Keys
{ {
static ResourceManager resourceManager = new ResourceManager(typeof(SR)); static ResourceManager resourceManager = new ResourceManager("Microsoft.SqlTools.ServiceLayer.SR", typeof(SR).GetTypeInfo().Assembly);
static CultureInfo _culture = null; static CultureInfo _culture = null;
@@ -540,6 +582,24 @@ namespace Microsoft.SqlTools.ServiceLayer
public const string QueryServiceResultSetNoColumnSchema = "QueryServiceResultSetNoColumnSchema"; public const string QueryServiceResultSetNoColumnSchema = "QueryServiceResultSetNoColumnSchema";
public const string PeekDefinitionAzureError = "PeekDefinitionAzureError";
public const string PeekDefinitionError = "PeekDefinitionError";
public const string PeekDefinitionNoResultsError = "PeekDefinitionNoResultsError";
public const string PeekDefinitionNotConnectedError = "PeekDefinitionNotConnectedError";
public const string PeekDefinitionTimedoutError = "PeekDefinitionTimedoutError";
public const string PeekDefinitionTypeNotSupportedError = "PeekDefinitionTypeNotSupportedError";
public const string WorkspaceServicePositionLineOutOfRange = "WorkspaceServicePositionLineOutOfRange"; public const string WorkspaceServicePositionLineOutOfRange = "WorkspaceServicePositionLineOutOfRange";

View File

@@ -308,6 +308,32 @@
<value>Could not retrieve column schema for result set</value> <value>Could not retrieve column schema for result set</value>
<comment></comment> <comment></comment>
</data> </data>
<data name="PeekDefinitionAzureError" xml:space="preserve">
<value>This feature is currently not supported on Azure SQL DB and Data Warehouse: {0}</value>
<comment>.
Parameters: 0 - errorMessage (string) </comment>
</data>
<data name="PeekDefinitionError" xml:space="preserve">
<value>An unexpected error occurred during Peek Definition execution: {0}</value>
<comment>.
Parameters: 0 - errorMessage (string) </comment>
</data>
<data name="PeekDefinitionNoResultsError" xml:space="preserve">
<value>No results were found.</value>
<comment></comment>
</data>
<data name="PeekDefinitionNotConnectedError" xml:space="preserve">
<value>Please connect to a server.</value>
<comment></comment>
</data>
<data name="PeekDefinitionTimedoutError" xml:space="preserve">
<value>Operation timed out.</value>
<comment></comment>
</data>
<data name="PeekDefinitionTypeNotSupportedError" xml:space="preserve">
<value>This object type is currently not supported by this feature.</value>
<comment></comment>
</data>
<data name="WorkspaceServicePositionLineOutOfRange" xml:space="preserve"> <data name="WorkspaceServicePositionLineOutOfRange" xml:space="preserve">
<value>Position is outside of file line range</value> <value>Position is outside of file line range</value>
<comment></comment> <comment></comment>

View File

@@ -139,6 +139,21 @@ QueryServiceResultSetRowCountOutOfRange = Row count must be a positive integer
QueryServiceResultSetNoColumnSchema = Could not retrieve column schema for result set QueryServiceResultSetNoColumnSchema = Could not retrieve column schema for result set
############################################################################
# Language Service
PeekDefinitionAzureError(string errorMessage) = This feature is currently not supported on Azure SQL DB and Data Warehouse: {0}
PeekDefinitionError(string errorMessage) = An unexpected error occurred during Peek Definition execution: {0}
PeekDefinitionNoResultsError = No results were found.
PeekDefinitionNotConnectedError = Please connect to a server.
PeekDefinitionTimedoutError = Operation timed out.
PeekDefinitionTypeNotSupportedError = This object type is currently not supported by this feature.
############################################################################ ############################################################################
# Workspace Service # Workspace Service

View File

@@ -3,11 +3,14 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information. // Licensed under the MIT license. See LICENSE file in the project root for full license information.
// //
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Runtime.InteropServices; using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Runtime.InteropServices;
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.SqlParser.Binder; using Microsoft.SqlServer.Management.SqlParser.Binder;
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider; using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
using Microsoft.SqlServer.Management.SqlParser.Parser; using Microsoft.SqlServer.Management.SqlParser.Parser;
using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.Connection;
@@ -91,8 +94,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
requestContext = new Mock<RequestContext<Location[]>>(); requestContext = new Mock<RequestContext<Location[]>>();
requestContext.Setup(rc => rc.SendResult(It.IsAny<Location[]>())) requestContext.Setup(rc => rc.SendResult(It.IsAny<Location[]>()))
.Returns(Task.FromResult(0)); .Returns(Task.FromResult(0));
requestContext.Setup(r => r.SendEvent(It.IsAny<EventType<TelemetryParams>>(), It.IsAny<TelemetryParams>())); requestContext.Setup(rc => rc.SendError(It.IsAny<DefinitionError>())).Returns(Task.FromResult(0));;
requestContext.Setup(r => r.SendEvent(It.IsAny<EventType<StatusChangeParams>>(), It.IsAny<StatusChangeParams>())); requestContext.Setup(r => r.SendEvent(It.IsAny<EventType<TelemetryParams>>(), It.IsAny<TelemetryParams>())).Returns(Task.FromResult(0));;
requestContext.Setup(r => r.SendEvent(It.IsAny<EventType<StatusChangeParams>>(), It.IsAny<StatusChangeParams>())).Returns(Task.FromResult(0));;
// setup the IBinder mock // setup the IBinder mock
binder = new Mock<IBinder>(); binder = new Mock<IBinder>();
@@ -103,7 +107,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
var testScriptParseInfo = new ScriptParseInfo(); var testScriptParseInfo = new ScriptParseInfo();
LanguageService.Instance.AddOrUpdateScriptParseInfo(this.testScriptUri, testScriptParseInfo); LanguageService.Instance.AddOrUpdateScriptParseInfo(this.testScriptUri, testScriptParseInfo);
testScriptParseInfo.IsConnected = true; testScriptParseInfo.IsConnected = false;
testScriptParseInfo.ConnectionKey = LanguageService.Instance.BindingQueue.AddConnectionContext(connectionInfo); testScriptParseInfo.ConnectionKey = LanguageService.Instance.BindingQueue.AddConnectionContext(connectionInfo);
// setup the binding context object // setup the binding context object
@@ -115,18 +119,19 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
/// <summary> /// <summary>
/// Tests the definition event handler. When called with no active connection, no definition is sent /// Tests the definition event handler. When called with no active connection, an error is sent
/// </summary> /// </summary>
[Fact] [Fact]
public async Task DefinitionsHandlerWithNoConnectionTest() public async Task DefinitionsHandlerWithNoConnectionTest()
{ {
TestObjects.InitializeTestServices(); TestObjects.InitializeTestServices();
InitializeTestObjects(); InitializeTestObjects();
// request the completion list // request definition
await Task.WhenAny(LanguageService.HandleDefinitionRequest(textDocument, requestContext.Object), Task.Delay(TaskTimeout)); var definitionTask = await Task.WhenAny(LanguageService.HandleDefinitionRequest(textDocument, requestContext.Object), Task.Delay(TaskTimeout));
await definitionTask;
// verify that send result was not called // verify that send result was not called and send error was called
requestContext.Verify(m => m.SendResult(It.IsAny<Location[]>()), Times.Never()); requestContext.Verify(m => m.SendResult(It.IsAny<Location[]>()), Times.Never());
requestContext.Verify(m => m.SendError(It.IsAny<DefinitionError>()), Times.Once());
} }
/// <summary> /// <summary>
@@ -136,7 +141,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
public void GetLocationFromFileForValidFilePathTest() public void GetLocationFromFileForValidFilePathTest()
{ {
String filePath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "C:\\test\\script.sql" : "/test/script.sql"; String filePath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "C:\\test\\script.sql" : "/test/script.sql";
PeekDefinition peekDefinition = new PeekDefinition(null); PeekDefinition peekDefinition = new PeekDefinition(null, null);
Location[] locations = peekDefinition.GetLocationFromFile(filePath, 0); Location[] locations = peekDefinition.GetLocationFromFile(filePath, 0);
String expectedFilePath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "file:///C:/test/script.sql" : "file:/test/script.sql"; String expectedFilePath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "file:///C:/test/script.sql" : "file:/test/script.sql";
@@ -149,11 +154,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
[Fact] [Fact]
public void GetSchemaFromDatabaseQualifiedNameWithValidNameTest() public void GetSchemaFromDatabaseQualifiedNameWithValidNameTest()
{ {
PeekDefinition peekDefinition = new PeekDefinition(null); PeekDefinition peekDefinition = new PeekDefinition(null, null);
string validDatabaseQualifiedName = "master.test.test_table"; string validDatabaseQualifiedName = "master.test.test_table";
string objectName = "test_table"; string objectName = "test_table";
string expectedSchemaName = "test"; string expectedSchemaName = "test";
string actualSchemaName = peekDefinition.GetSchemaFromDatabaseQualifiedName(validDatabaseQualifiedName, objectName); string actualSchemaName = peekDefinition.GetSchemaFromDatabaseQualifiedName(validDatabaseQualifiedName, objectName);
Assert.Equal(actualSchemaName, expectedSchemaName); Assert.Equal(actualSchemaName, expectedSchemaName);
} }
@@ -165,11 +170,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
[Fact] [Fact]
public void GetSchemaFromDatabaseQualifiedNameWithNoSchemaTest() public void GetSchemaFromDatabaseQualifiedNameWithNoSchemaTest()
{ {
PeekDefinition peekDefinition = new PeekDefinition(null); PeekDefinition peekDefinition = new PeekDefinition(null, null);
string validDatabaseQualifiedName = "test_table"; string validDatabaseQualifiedName = "test_table";
string objectName = "test_table"; string objectName = "test_table";
string expectedSchemaName = "dbo"; string expectedSchemaName = "dbo";
string actualSchemaName = peekDefinition.GetSchemaFromDatabaseQualifiedName(validDatabaseQualifiedName, objectName); string actualSchemaName = peekDefinition.GetSchemaFromDatabaseQualifiedName(validDatabaseQualifiedName, objectName);
Assert.Equal(actualSchemaName, expectedSchemaName); Assert.Equal(actualSchemaName, expectedSchemaName);
} }
@@ -180,15 +185,15 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
[Fact] [Fact]
public void GetSchemaFromDatabaseQualifiedNameWithInvalidNameTest() public void GetSchemaFromDatabaseQualifiedNameWithInvalidNameTest()
{ {
PeekDefinition peekDefinition = new PeekDefinition(null); PeekDefinition peekDefinition = new PeekDefinition(null, null);
string validDatabaseQualifiedName = "x.y.z"; string validDatabaseQualifiedName = "x.y.z";
string objectName = "test_table"; string objectName = "test_table";
string expectedSchemaName = "dbo"; string expectedSchemaName = "dbo";
string actualSchemaName = peekDefinition.GetSchemaFromDatabaseQualifiedName(validDatabaseQualifiedName, objectName); string actualSchemaName = peekDefinition.GetSchemaFromDatabaseQualifiedName(validDatabaseQualifiedName, objectName);
Assert.Equal(actualSchemaName, expectedSchemaName); Assert.Equal(actualSchemaName, expectedSchemaName);
} }
#if LIVE_CONNECTION_TESTS #if LIVE_CONNECTION_TESTS
/// <summary> /// <summary>
/// Test get definition for a table object with active connection /// Test get definition for a table object with active connection
@@ -196,9 +201,13 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
[Fact] [Fact]
public void GetValidTableDefinitionTest() public void GetValidTableDefinitionTest()
{ {
// Get live connectionInfo // Get live connectionInfo and serverConnection
PeekDefinition peekDefinition = new PeekDefinition(TestObjects.InitLiveConnectionInfoForDefinition()); ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfoForDefinition();
ServerConnection serverConnection = TestObjects.InitLiveServerConnectionForDefinition(connInfo);
PeekDefinition peekDefinition = new PeekDefinition(serverConnection, connInfo);
string objectName = "spt_monitor"; string objectName = "spt_monitor";
string schemaName = null; string schemaName = null;
string objectType = "TABLE"; string objectType = "TABLE";
@@ -214,8 +223,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
[Fact] [Fact]
public void GetTableDefinitionInvalidObjectTest() public void GetTableDefinitionInvalidObjectTest()
{ {
// Get live connectionInfo // Get live connectionInfo and serverConnection
PeekDefinition peekDefinition = new PeekDefinition(TestObjects.InitLiveConnectionInfoForDefinition()); ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfoForDefinition();
ServerConnection serverConnection = TestObjects.InitLiveServerConnectionForDefinition(connInfo);
PeekDefinition peekDefinition = new PeekDefinition(serverConnection, connInfo);
string objectName = "test_invalid"; string objectName = "test_invalid";
string schemaName = null; string schemaName = null;
string objectType = "TABLE"; string objectType = "TABLE";
@@ -231,24 +243,102 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
[Fact] [Fact]
public void GetTableDefinitionWithSchemaTest() public void GetTableDefinitionWithSchemaTest()
{ {
// Get live connectionInfo // Get live connectionInfo and serverConnection
PeekDefinition peekDefinition = new PeekDefinition(TestObjects.InitLiveConnectionInfoForDefinition()); ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfoForDefinition();
ServerConnection serverConnection = TestObjects.InitLiveServerConnectionForDefinition(connInfo);
PeekDefinition peekDefinition = new PeekDefinition(serverConnection, connInfo);
string objectName = "spt_monitor"; string objectName = "spt_monitor";
string schemaName = "dbo"; string schemaName = "dbo";
string objectType = "TABLE"; string objectType = "TABLE";
// Get locations for valid table object with schema name // Get locations for valid table object with schema name
Location[] locations = peekDefinition.GetSqlObjectDefinition(peekDefinition.GetTableScripts, objectName, schemaName, objectType); Location[] locations = peekDefinition.GetSqlObjectDefinition(peekDefinition.GetTableScripts, objectName, schemaName, objectType);
Assert.NotNull(locations); Assert.NotNull(locations);
Cleanup(locations); Cleanup(locations);
} }
/// <summary> /// <summary>
/// Test GetDefinition with an unsupported type(function) /// Test GetDefinition with an unsupported type(schema - dbo). Expect a error result.
/// </summary> /// </summary>
[Fact] [Fact]
public void GetUnsupportedDefinitionForFullScript() public void GetUnsupportedDefinitionErrorTest()
{ {
ScriptFile scriptFile;
TextDocumentPosition textDocument = new TextDocumentPosition
{
TextDocument = new TextDocumentIdentifier { Uri = OwnerUri },
Position = new Position
{
Line = 0,
// test for 'dbo'
Character = 16
}
};
ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfo(out scriptFile);
scriptFile.Contents = "select * from dbo.func ()";
var languageService = new LanguageService();
ScriptParseInfo scriptInfo = new ScriptParseInfo { IsConnected = true };
languageService.ScriptParseInfoMap.Add(OwnerUri, scriptInfo);
// When I call the language service
var result = languageService.GetDefinition(textDocument, scriptFile, connInfo);
// Then I expect null locations and an error to be reported
Assert.NotNull(result);
Assert.True(result.IsErrorResult);
}
/// <summary>
/// Get Definition for a object with no definition. Expect a error result
/// </summary>
[Fact]
public void GetDefinitionWithNoResultsFoundError()
{
ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfoForDefinition();
ServerConnection serverConnection = TestObjects.InitLiveServerConnectionForDefinition(connInfo);
PeekDefinition peekDefinition = new PeekDefinition(serverConnection, connInfo);
string objectName = "from";
List<Declaration> declarations = new List<Declaration>();
DefinitionResult result = peekDefinition.GetScript(declarations, objectName, null);
Assert.NotNull(result);
Assert.True(result.IsErrorResult);
Assert.Equal(SR.PeekDefinitionNoResultsError, result.Message);
}
/// <summary>
/// Test GetDefinition with a forced timeout. Expect a error result.
/// </summary>
[Fact]
public void GetDefinitionTimeoutTest()
{
// Given a binding queue that will automatically time out
var languageService = new LanguageService();
Mock<ConnectedBindingQueue> queueMock = new Mock<ConnectedBindingQueue>();
languageService.BindingQueue = queueMock.Object;
ManualResetEvent mre = new ManualResetEvent(true); // Do not block
Mock<QueueItem> itemMock = new Mock<QueueItem>();
itemMock.Setup(i => i.ItemProcessed).Returns(mre);
DefinitionResult timeoutResult = null;
queueMock.Setup(q => q.QueueBindingOperation(
It.IsAny<string>(),
It.IsAny<Func<IBindingContext, CancellationToken, object>>(),
It.IsAny<Func<IBindingContext, object>>(),
It.IsAny<int?>(),
It.IsAny<int?>()))
.Callback<string, Func<IBindingContext, CancellationToken, object>, Func<IBindingContext, object>, int?, int?>(
(key, bindOperation, timeoutOperation, blah, blah2) =>
{
timeoutResult = (DefinitionResult) timeoutOperation((IBindingContext)null);
itemMock.Object.Result = timeoutResult;
})
.Returns(() => itemMock.Object);
ScriptFile scriptFile; ScriptFile scriptFile;
TextDocumentPosition textDocument = new TextDocumentPosition TextDocumentPosition textDocument = new TextDocumentPosition
@@ -263,12 +353,17 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfo(out scriptFile); ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfo(out scriptFile);
scriptFile.Contents = "select * from dbo.func ()"; scriptFile.Contents = "select * from dbo.func ()";
var languageService = LanguageService.Instance;
ScriptParseInfo scriptInfo = new ScriptParseInfo { IsConnected = true }; ScriptParseInfo scriptInfo = new ScriptParseInfo { IsConnected = true };
languageService.ScriptParseInfoMap.Add(OwnerUri, scriptInfo); languageService.ScriptParseInfoMap.Add(OwnerUri, scriptInfo);
var locations = languageService.GetDefinition(textDocument, scriptFile, connInfo); // When I call the language service
Assert.Null(locations); var result = languageService.GetDefinition(textDocument, scriptFile, connInfo);
// Then I expect null locations and an error to be reported
Assert.NotNull(result);
Assert.True(result.IsErrorResult);
// Check timeout message
Assert.Equal(SR.PeekDefinitionTimedoutError, result.Message);
} }
/// <summary> /// <summary>
@@ -276,8 +371,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
/// </summary> /// </summary>
[Fact] [Fact]
public void GetValidViewDefinitionTest() public void GetValidViewDefinitionTest()
{ {
PeekDefinition peekDefinition = new PeekDefinition(TestObjects.InitLiveConnectionInfoForDefinition()); ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfoForDefinition();
ServerConnection serverConnection = TestObjects.InitLiveServerConnectionForDefinition(connInfo);
PeekDefinition peekDefinition = new PeekDefinition(serverConnection, connInfo);
string objectName = "objects"; string objectName = "objects";
string schemaName = "sys"; string schemaName = "sys";
string objectType = "VIEW"; string objectType = "VIEW";
@@ -293,7 +391,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
[Fact] [Fact]
public void GetViewDefinitionInvalidObjectTest() public void GetViewDefinitionInvalidObjectTest()
{ {
PeekDefinition peekDefinition = new PeekDefinition(TestObjects.InitLiveConnectionInfoForDefinition()); // Get live connectionInfo and serverConnection
ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfoForDefinition();
ServerConnection serverConnection = TestObjects.InitLiveServerConnectionForDefinition(connInfo);
PeekDefinition peekDefinition = new PeekDefinition(serverConnection, connInfo);
string objectName = "objects"; string objectName = "objects";
string schemaName = null; string schemaName = null;
string objectType = "VIEW"; string objectType = "VIEW";
@@ -308,8 +410,13 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
[Fact] [Fact]
public void GetStoredProcedureDefinitionTest() public void GetStoredProcedureDefinitionTest()
{ {
PeekDefinition peekDefinition = new PeekDefinition(TestObjects.InitLiveConnectionInfoForDefinition()); // Get live connectionInfo and serverConnection
ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfoForDefinition();
ServerConnection serverConnection = TestObjects.InitLiveServerConnectionForDefinition(connInfo);
PeekDefinition peekDefinition = new PeekDefinition(serverConnection, connInfo);
string objectName = "sp_MSrepl_startup"; string objectName = "sp_MSrepl_startup";
string schemaName = "dbo"; string schemaName = "dbo";
string objectType = "PROCEDURE"; string objectType = "PROCEDURE";
@@ -324,7 +431,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
[Fact] [Fact]
public void GetStoredProcedureDefinitionFailureTest() public void GetStoredProcedureDefinitionFailureTest()
{ {
PeekDefinition peekDefinition = new PeekDefinition(TestObjects.InitLiveConnectionInfoForDefinition()); // Get live connectionInfo and serverConnection
ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfoForDefinition();
ServerConnection serverConnection = TestObjects.InitLiveServerConnectionForDefinition(connInfo);
PeekDefinition peekDefinition = new PeekDefinition(serverConnection, connInfo);
string objectName = "SP2"; string objectName = "SP2";
string schemaName = "dbo"; string schemaName = "dbo";
string objectType = "PROCEDURE"; string objectType = "PROCEDURE";
@@ -339,7 +450,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
[Fact] [Fact]
public void GetStoredProcedureDefinitionWithoutSchemaTest() public void GetStoredProcedureDefinitionWithoutSchemaTest()
{ {
PeekDefinition peekDefinition = new PeekDefinition(TestObjects.InitLiveConnectionInfoForDefinition()); // Get live connectionInfo and serverConnection
ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfoForDefinition();
ServerConnection serverConnection = TestObjects.InitLiveServerConnectionForDefinition(connInfo);
PeekDefinition peekDefinition = new PeekDefinition(serverConnection, connInfo);
string objectName = "sp_MSrepl_startup"; string objectName = "sp_MSrepl_startup";
string schemaName = null; string schemaName = null;
string objectType = "PROCEDURE"; string objectType = "PROCEDURE";

View File

@@ -194,7 +194,7 @@ namespace Microsoft.SqlTools.Test.Utility
return connInfo; return connInfo;
} }
public static ServerConnection InitLiveConnectionInfoForDefinition() public static ConnectionInfo InitLiveConnectionInfoForDefinition()
{ {
TestObjects.InitializeTestServices(); TestObjects.InitializeTestServices();
@@ -212,7 +212,11 @@ namespace Microsoft.SqlTools.Test.Utility
ConnectionInfo connInfo = null; ConnectionInfo connInfo = null;
connectionService.TryFindConnection(ownerUri, out connInfo); connectionService.TryFindConnection(ownerUri, out connInfo);
return connInfo;
}
public static ServerConnection InitLiveServerConnectionForDefinition(ConnectionInfo connInfo)
{
SqlConnection sqlConn = new SqlConnection(ConnectionService.BuildConnectionString(connInfo.ConnectionDetails)); SqlConnection sqlConn = new SqlConnection(ConnectionService.BuildConnectionString(connInfo.ConnectionDetails));
return new ServerConnection(sqlConn); return new ServerConnection(sqlConn);
} }