diff --git a/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs b/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs
index a10948ec..6cdd62aa 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs
@@ -73,7 +73,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
///
// Callback for ondisconnect handler
///
- public delegate Task OnDisconnectHandler(ConnectionSummary summary);
+ public delegate Task OnDisconnectHandler(ConnectionSummary summary, string ownerUri);
///
/// List of onconnection handlers
@@ -241,7 +241,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
// Invoke callback notifications
foreach (var activity in this.onDisconnectActivities)
{
- activity(info.ConnectionDetails);
+ activity(info.ConnectionDetails, disconnectParams.OwnerUri);
}
// Success
diff --git a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/AutoCompleteHelper.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/AutoCompleteHelper.cs
index 1069e18d..ffc9811c 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/AutoCompleteHelper.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/AutoCompleteHelper.cs
@@ -4,8 +4,13 @@
//
using System.Collections.Generic;
+using Microsoft.SqlServer.Management.SqlParser.Binder;
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
+using Microsoft.SqlServer.Management.SqlParser.Parser;
+using Microsoft.SqlTools.ServiceLayer.Connection;
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
+using Microsoft.SqlTools.ServiceLayer.SqlContext;
+using Microsoft.SqlTools.ServiceLayer.Workspace;
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
@@ -487,7 +492,6 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
Label = autoCompleteItem.Title,
Kind = CompletionItemKind.Variable,
Detail = autoCompleteItem.Title,
- Documentation = autoCompleteItem.Description,
TextEdit = new TextEdit
{
NewText = autoCompleteItem.Title,
@@ -510,5 +514,76 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
return completions.ToArray();
}
+
+ ///
+ /// Preinitialize the parser and binder with common metadata.
+ /// This should front load the long binding wait to the time the
+ /// connection is established. Once this is completed other binding
+ /// requests should be faster.
+ ///
+ ///
+ ///
+ internal static void PrepopulateCommonMetadata(ConnectionInfo info, ScriptParseInfo scriptInfo)
+ {
+ if (scriptInfo.IsConnected)
+ {
+ var scriptFile = WorkspaceService.Instance.Workspace.GetFile(info.OwnerUri);
+ LanguageService.Instance.ParseAndBind(scriptFile, info);
+
+ if (scriptInfo.BuildingMetadataEvent.WaitOne(LanguageService.OnConnectionWaitTimeout))
+ {
+ try
+ {
+ scriptInfo.BuildingMetadataEvent.Reset();
+
+ // parse a simple statement that returns common metadata
+ ParseResult parseResult = Parser.Parse(
+ "select ",
+ scriptInfo.ParseOptions);
+
+ List parseResults = new List();
+ parseResults.Add(parseResult);
+ scriptInfo.Binder.Bind(
+ parseResults,
+ info.ConnectionDetails.DatabaseName,
+ BindMode.Batch);
+
+ // get the completion list from SQL Parser
+ var suggestions = Resolver.FindCompletions(
+ parseResult, 1, 8,
+ scriptInfo.MetadataDisplayInfoProvider);
+
+ // this forces lazy evaluation of the suggestion metadata
+ AutoCompleteHelper.ConvertDeclarationsToCompletionItems(suggestions, 1, 8, 8);
+
+ parseResult = Parser.Parse(
+ "exec ",
+ scriptInfo.ParseOptions);
+
+ parseResults = new List();
+ parseResults.Add(parseResult);
+ scriptInfo.Binder.Bind(
+ parseResults,
+ info.ConnectionDetails.DatabaseName,
+ BindMode.Batch);
+
+ // get the completion list from SQL Parser
+ suggestions = Resolver.FindCompletions(
+ parseResult, 1, 6,
+ scriptInfo.MetadataDisplayInfoProvider);
+
+ // this forces lazy evaluation of the suggestion metadata
+ AutoCompleteHelper.ConvertDeclarationsToCompletionItems(suggestions, 1, 6, 6);
+ }
+ catch
+ {
+ }
+ finally
+ {
+ scriptInfo.BuildingMetadataEvent.Set();
+ }
+ }
+ }
+ }
}
}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs
index 6986314e..cc834adc 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs
@@ -8,6 +8,13 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using Microsoft.SqlServer.Management.Common;
+using Microsoft.SqlServer.Management.SqlParser;
+using Microsoft.SqlServer.Management.SqlParser.Binder;
+using Microsoft.SqlServer.Management.SqlParser.Intellisense;
+using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
+using Microsoft.SqlServer.Management.SqlParser.Parser;
+using Microsoft.SqlServer.Management.SmoMetadataProvider;
using Microsoft.SqlTools.ServiceLayer.Connection;
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection;
@@ -15,16 +22,9 @@ using Microsoft.SqlTools.ServiceLayer.Hosting;
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
using Microsoft.SqlTools.ServiceLayer.SqlContext;
+using Microsoft.SqlTools.ServiceLayer.Utility;
using Microsoft.SqlTools.ServiceLayer.Workspace;
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
-using Microsoft.SqlServer.Management.Common;
-using Microsoft.SqlServer.Management.SqlParser;
-using Microsoft.SqlTools.ServiceLayer.Utility;
-using Microsoft.SqlServer.Management.SqlParser.Binder;
-using Microsoft.SqlServer.Management.SqlParser.Intellisense;
-using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
-using Microsoft.SqlServer.Management.SqlParser.Parser;
-using Microsoft.SqlServer.Management.SmoMetadataProvider;
using Location = Microsoft.SqlTools.ServiceLayer.Workspace.Contracts.Location;
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
@@ -35,17 +35,21 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
///
public sealed class LanguageService
{
- public const string DefaultBatchSeperator = "GO";
+ internal const string DefaultBatchSeperator = "GO";
- private const int DiagnosticParseDelay = 750;
+ internal const int DiagnosticParseDelay = 750;
- private const int FindCompletionsTimeout = 3000;
+ internal const int FindCompletionsTimeout = 3000;
- private const int FindCompletionStartTimeout = 50;
+ internal const int FindCompletionStartTimeout = 50;
- private const int OnConnectionWaitTimeout = 30000;
+ internal const int OnConnectionWaitTimeout = 300000;
- private bool ShouldEnableAutocomplete()
+ private object parseMapLock = new object();
+
+ private ScriptParseInfo currentCompletionParseInfo;
+
+ internal bool ShouldEnableAutocomplete()
{
return true;
}
@@ -105,12 +109,12 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
private static CancellationTokenSource ExistingRequestCancellation { get; set; }
- private SqlToolsSettings CurrentSettings
+ internal SqlToolsSettings CurrentSettings
{
get { return WorkspaceService.Instance.CurrentSettings; }
}
- private Workspace.Workspace CurrentWorkspace
+ internal Workspace.Workspace CurrentWorkspace
{
get { return WorkspaceService.Instance.Workspace; }
}
@@ -119,7 +123,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
/// Gets or sets the current SQL Tools context
///
///
- private SqlToolsContext Context { get; set; }
+ internal SqlToolsContext Context { get; set; }
#endregion
@@ -196,11 +200,25 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
await requestContext.SendResult(completionItems);
}
+ ///
+ /// Handle the resolve completion request event to provide additional
+ /// autocomplete metadata to the currently select completion item
+ ///
+ ///
+ ///
+ ///
+ private static async Task HandleCompletionResolveRequest(
+ CompletionItem completionItem,
+ RequestContext requestContext)
+ {
+ completionItem = LanguageService.Instance.ResolveCompletionItem(completionItem);
+ await requestContext.SendResult(completionItem);
+ }
+
private static async Task HandleDefinitionRequest(
TextDocumentPosition textDocumentPosition,
RequestContext requestContext)
{
- Logger.Write(LogLevel.Verbose, "HandleDefinitionRequest");
await Task.FromResult(true);
}
@@ -208,15 +226,6 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
ReferencesParams referencesParams,
RequestContext requestContext)
{
- Logger.Write(LogLevel.Verbose, "HandleReferencesRequest");
- await Task.FromResult(true);
- }
-
- private static async Task HandleCompletionResolveRequest(
- CompletionItem completionItem,
- RequestContext requestContext)
- {
- Logger.Write(LogLevel.Verbose, "HandleCompletionResolveRequest");
await Task.FromResult(true);
}
@@ -224,7 +233,6 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
TextDocumentPosition textDocumentPosition,
RequestContext requestContext)
{
- Logger.Write(LogLevel.Verbose, "HandleSignatureHelpRequest");
await Task.FromResult(true);
}
@@ -232,7 +240,6 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
TextDocumentPosition textDocumentPosition,
RequestContext requestContext)
{
- Logger.Write(LogLevel.Verbose, "HandleDocumentHighlightRequest");
await Task.FromResult(true);
}
@@ -240,7 +247,6 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
TextDocumentPosition textDocumentPosition,
RequestContext requestContext)
{
- Logger.Write(LogLevel.Verbose, "HandleHoverRequest");
await Task.FromResult(true);
}
@@ -258,9 +264,12 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
ScriptFile scriptFile,
EventContext eventContext)
{
- await this.RunScriptDiagnostics(
- new ScriptFile[] { scriptFile },
- eventContext);
+ if (!IsPreviewWindow(scriptFile))
+ {
+ await RunScriptDiagnostics(
+ new ScriptFile[] { scriptFile },
+ eventContext);
+ }
await Task.FromResult(true);
}
@@ -327,8 +336,10 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
/// it is the last URI connected to a particular connection,
/// then remove the cache.
///
- public async Task RemoveAutoCompleteCacheUriReference(ConnectionSummary summary)
+ public async Task RemoveAutoCompleteCacheUriReference(ConnectionSummary summary, string ownerUri)
{
+ RemoveScriptParseInfo(ownerUri);
+
// currently this method is disabled, but we need to reimplement now that the
// implementation of the 'cache' has changed.
await Task.FromResult(0);
@@ -342,16 +353,8 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
///
public ParseResult ParseAndBind(ScriptFile scriptFile, ConnectionInfo connInfo)
{
- ScriptParseInfo parseInfo = null;
- if (this.ScriptParseInfoMap.ContainsKey(scriptFile.ClientFilePath))
- {
- parseInfo = this.ScriptParseInfoMap[scriptFile.ClientFilePath];
- }
- else
- {
- parseInfo = new ScriptParseInfo();
- this.ScriptParseInfoMap.Add(scriptFile.ClientFilePath, parseInfo);
- }
+ // get or create the current parse info object
+ ScriptParseInfo parseInfo = GetScriptParseInfo(scriptFile.ClientFilePath, createIfNotExists: true);
if (parseInfo.BuildingMetadataEvent.WaitOne(LanguageService.FindCompletionsTimeout))
{
@@ -398,7 +401,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
}
///
- /// Update the cached autocomplete candidate list when the user connects to a database
+ /// Update the autocomplete metadata provider when the user connects to a database
///
///
public async Task UpdateLanguageServiceOnConnection(ConnectionInfo info)
@@ -407,43 +410,37 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
{
if (ShouldEnableAutocomplete())
{
- ScriptParseInfo scriptInfo =
- this.ScriptParseInfoMap.ContainsKey(info.OwnerUri)
- ? this.ScriptParseInfoMap[info.OwnerUri]
- : new ScriptParseInfo();
-
- try
+ ScriptParseInfo scriptInfo = GetScriptParseInfo(info.OwnerUri, createIfNotExists: true);
+ if (scriptInfo.BuildingMetadataEvent.WaitOne(LanguageService.OnConnectionWaitTimeout))
{
- scriptInfo.BuildingMetadataEvent.WaitOne(LanguageService.OnConnectionWaitTimeout);
- scriptInfo.BuildingMetadataEvent.Reset();
-
- var sqlConn = info.SqlConnection as ReliableSqlConnection;
- if (sqlConn != null)
+ try
{
- ServerConnection serverConn = new ServerConnection(sqlConn.GetUnderlyingConnection());
- scriptInfo.MetadataDisplayInfoProvider = new MetadataDisplayInfoProvider();
- scriptInfo.MetadataProvider = SmoMetadataProvider.CreateConnectedProvider(serverConn);
- scriptInfo.Binder = BinderProvider.CreateBinder(scriptInfo.MetadataProvider);
- scriptInfo.ServerConnection = new ServerConnection(sqlConn.GetUnderlyingConnection());
- this.ScriptParseInfoMap[info.OwnerUri] = scriptInfo;
+ scriptInfo.BuildingMetadataEvent.Reset();
+ var sqlConn = info.SqlConnection as ReliableSqlConnection;
+ if (sqlConn != null)
+ {
+ ServerConnection serverConn = new ServerConnection(sqlConn.GetUnderlyingConnection());
+ scriptInfo.MetadataDisplayInfoProvider = new MetadataDisplayInfoProvider();
+ scriptInfo.MetadataProvider = SmoMetadataProvider.CreateConnectedProvider(serverConn);
+ scriptInfo.Binder = BinderProvider.CreateBinder(scriptInfo.MetadataProvider);
+ scriptInfo.ServerConnection = new ServerConnection(sqlConn.GetUnderlyingConnection());
+ scriptInfo.IsConnected = true;
+ }
+ }
+ catch (Exception)
+ {
+ scriptInfo.IsConnected = false;
+ }
+ finally
+ {
+ // Set Metadata Build event to Signal state.
+ // (Tell Language Service that I am ready with Metadata Provider Object)
+ scriptInfo.BuildingMetadataEvent.Set();
}
}
- catch (Exception)
- {
- scriptInfo.IsConnected = false;
- }
- finally
- {
- // Set Metadata Build event to Signal state.
- // (Tell Language Service that I am ready with Metadata Provider Object)
- scriptInfo.BuildingMetadataEvent.Set();
- }
- if (scriptInfo.IsConnected)
- {
- var scriptFile = WorkspaceService.Instance.Workspace.GetFile(info.OwnerUri);
- ParseAndBind(scriptFile, info);
- }
+ // populate SMO metadata provider with most common info
+ AutoCompleteHelper.PrepopulateCommonMetadata(info, scriptInfo);
}
});
}
@@ -466,6 +463,28 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|| !string.Equals(prevSqlText, currentSqlText);
}
+ ///
+ /// Resolves the details and documentation for a completion item
+ ///
+ ///
+ internal CompletionItem ResolveCompletionItem(CompletionItem completionItem)
+ {
+ var scriptParseInfo = LanguageService.Instance.currentCompletionParseInfo;
+ if (scriptParseInfo != null && scriptParseInfo.CurrentSuggestions != null)
+ {
+ foreach (var suggestion in scriptParseInfo.CurrentSuggestions)
+ {
+ if (string.Equals(suggestion.Title, completionItem.Label))
+ {
+ completionItem.Detail = suggestion.DatabaseQualifiedName;
+ completionItem.Documentation = suggestion.Description;
+ break;
+ }
+ }
+ }
+ return completionItem;
+ }
+
///
/// Return the completion item list for the current text position.
/// This method does not await cache builds since it expects to return quickly
@@ -479,20 +498,22 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
string filePath = textDocumentPosition.TextDocument.Uri;
int startLine = textDocumentPosition.Position.Line;
int startColumn = TextUtilities.PositionOfPrevDelimeter(
- scriptFile.Contents,
+ scriptFile.Contents,
textDocumentPosition.Position.Line,
textDocumentPosition.Position.Character);
int endColumn = textDocumentPosition.Position.Character;
+ this.currentCompletionParseInfo = null;
+
// Take a reference to the list at a point in time in case we update and replace the list
- if (connInfo == null
- || !LanguageService.Instance.ScriptParseInfoMap.ContainsKey(textDocumentPosition.TextDocument.Uri))
+
+ ScriptParseInfo scriptParseInfo = GetScriptParseInfo(textDocumentPosition.TextDocument.Uri);
+ if (connInfo == null || scriptParseInfo == null)
{
return AutoCompleteHelper.GetDefaultCompletionItems(startLine, startColumn, endColumn);
}
// reparse and bind the SQL statement if needed
- var scriptParseInfo = ScriptParseInfoMap[textDocumentPosition.TextDocument.Uri];
if (RequiresReparse(scriptParseInfo, scriptFile))
{
ParseAndBind(scriptFile, connInfo);
@@ -511,15 +532,18 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
try
{
// get the completion list from SQL Parser
- var suggestions = Resolver.FindCompletions(
+ scriptParseInfo.CurrentSuggestions = Resolver.FindCompletions(
scriptParseInfo.ParseResult,
textDocumentPosition.Position.Line + 1,
textDocumentPosition.Position.Character + 1,
scriptParseInfo.MetadataDisplayInfoProvider);
+ // cache the current script parse info object to resolve completions later
+ this.currentCompletionParseInfo = scriptParseInfo;
+
// convert the suggestion list to the VS Code format
return AutoCompleteHelper.ConvertDeclarationsToCompletionItems(
- suggestions,
+ scriptParseInfo.CurrentSuggestions,
startLine,
startColumn,
endColumn);
@@ -685,5 +709,76 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
}
#endregion
+
+ private void AddOrUpdateScriptParseInfo(string uri, ScriptParseInfo scriptInfo)
+ {
+ lock (this.parseMapLock)
+ {
+ if (this.ScriptParseInfoMap.ContainsKey(uri))
+ {
+ this.ScriptParseInfoMap[uri] = scriptInfo;
+ }
+ else
+ {
+ this.ScriptParseInfoMap.Add(uri, scriptInfo);
+ }
+
+ }
+ }
+
+ private ScriptParseInfo GetScriptParseInfo(string uri, bool createIfNotExists = false)
+ {
+ lock (this.parseMapLock)
+ {
+ if (this.ScriptParseInfoMap.ContainsKey(uri))
+ {
+ return this.ScriptParseInfoMap[uri];
+ }
+ else if (createIfNotExists)
+ {
+ ScriptParseInfo scriptInfo = new ScriptParseInfo();
+ this.ScriptParseInfoMap.Add(uri, scriptInfo);
+ return scriptInfo;
+ }
+ else
+ {
+ return null;
+ }
+ }
+ }
+
+ private bool RemoveScriptParseInfo(string uri)
+ {
+ lock (this.parseMapLock)
+ {
+ if (this.ScriptParseInfoMap.ContainsKey(uri))
+ {
+ var scriptInfo = this.ScriptParseInfoMap[uri];
+ scriptInfo.ServerConnection.Disconnect();
+ scriptInfo.ServerConnection = null;
+ return this.ScriptParseInfoMap.Remove(uri);
+ }
+ else
+ {
+ return false;
+ }
+ }
+ }
+
+ ///
+ /// Returns a flag indicating if the ScriptFile refers to the output window.
+ ///
+ ///
+ private bool IsPreviewWindow(ScriptFile scriptFile)
+ {
+ if (scriptFile != null && !string.IsNullOrWhiteSpace(scriptFile.ClientFilePath))
+ {
+ return scriptFile.ClientFilePath.StartsWith("tsqloutput:");
+ }
+ else
+ {
+ return false;
+ }
+ }
}
}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/ScriptParseInfo.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/ScriptParseInfo.cs
index 48fb2cce..7dca96ab 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/ScriptParseInfo.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/ScriptParseInfo.cs
@@ -3,14 +3,16 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
+using System;
+using System.Collections.Generic;
+using System.Threading;
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.SmoMetadataProvider;
using Microsoft.SqlServer.Management.SqlParser.Binder;
using Microsoft.SqlServer.Management.SqlParser.Common;
+using Microsoft.SqlServer.Management.SqlParser.Intellisense;
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
using Microsoft.SqlServer.Management.SqlParser.Parser;
-using System;
-using System.Threading;
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
{
@@ -33,7 +35,6 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
get { return this.buildingMetadataEvent; }
}
-
///
/// Gets or sets a flag determining is the LanguageService is connected
///
@@ -56,7 +57,6 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
isQuotedIdentifierSet: true,
compatibilityLevel: DatabaseCompatibilityLevel,
transactSqlVersion: TransactSqlVersion);
- this.IsConnected = true;
}
}
@@ -143,6 +143,11 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
///
public MetadataDisplayInfoProvider MetadataDisplayInfoProvider { get; set; }
+ ///
+ /// Gets or sets the current autocomplete suggestion list
+ ///
+ public IEnumerable CurrentSuggestions { get; set; }
+
///
/// Gets the database compatibility level from a server version
///
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Utility/Logger.cs b/src/Microsoft.SqlTools.ServiceLayer/Utility/Logger.cs
index f9d966e8..5c4adae4 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/Utility/Logger.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/Utility/Logger.cs
@@ -131,6 +131,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility
internal class LogWriter : IDisposable
{
+ private object logLock = new object();
private TextWriter textWriter;
private LogLevel minimumLogLevel = LogLevel.Verbose;
@@ -170,24 +171,28 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility
if (this.textWriter != null &&
logLevel >= this.minimumLogLevel)
{
- // Print the timestamp and log level
- this.textWriter.WriteLine(
- "{0} [{1}] - Method \"{2}\" at line {3} of {4}\r\n",
- DateTime.Now,
- logLevel.ToString().ToUpper(),
- callerName,
- callerLineNumber,
- callerSourceFile);
-
- // Print out indented message lines
- foreach (var messageLine in logMessage.Split('\n'))
+ // System.IO is not thread safe
+ lock (this.logLock)
{
- this.textWriter.WriteLine(" " + messageLine.TrimEnd());
- }
+ // Print the timestamp and log level
+ this.textWriter.WriteLine(
+ "{0} [{1}] - Method \"{2}\" at line {3} of {4}\r\n",
+ DateTime.Now,
+ logLevel.ToString().ToUpper(),
+ callerName,
+ callerLineNumber,
+ callerSourceFile);
- // Finish with a newline and flush the writer
- this.textWriter.WriteLine();
- this.textWriter.Flush();
+ // Print out indented message lines
+ foreach (var messageLine in logMessage.Split('\n'))
+ {
+ this.textWriter.WriteLine(" " + messageLine.TrimEnd());
+ }
+
+ // Finish with a newline and flush the writer
+ this.textWriter.WriteLine();
+ this.textWriter.Flush();
+ }
}
}
diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/Connection/ConnectionServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/Connection/ConnectionServiceTests.cs
index 07c202cb..82b5aca3 100644
--- a/test/Microsoft.SqlTools.ServiceLayer.Test/Connection/ConnectionServiceTests.cs
+++ b/test/Microsoft.SqlTools.ServiceLayer.Test/Connection/ConnectionServiceTests.cs
@@ -138,8 +138,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection
// register disconnect callback
connectionService.RegisterOnDisconnectTask(
- (result) => {
+ (result, uri) => {
callbackInvoked = true;
+ Assert.True(uri.Equals(ownerUri));
return Task.FromResult(true);
}
);
@@ -433,8 +434,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection
// register disconnect callback
connectionService.RegisterOnDisconnectTask(
- (result) => {
+ (result, uri) => {
callbackInvoked = true;
+ Assert.True(uri.Equals(ownerUri));
return Task.FromResult(true);
}
);
diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/LanguageServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/LanguageServiceTests.cs
index bb07c56a..34f7e405 100644
--- a/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/LanguageServiceTests.cs
+++ b/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/LanguageServiceTests.cs
@@ -21,9 +21,13 @@ using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
using Microsoft.SqlServer.Management.SqlParser.Parser;
using Microsoft.SqlTools.ServiceLayer.Connection;
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
+using Microsoft.SqlTools.ServiceLayer.Credentials;
using Microsoft.SqlTools.ServiceLayer.LanguageServices;
+using Microsoft.SqlTools.ServiceLayer.QueryExecution;
+using Microsoft.SqlTools.ServiceLayer.SqlContext;
using Microsoft.SqlTools.ServiceLayer.Test.QueryExecution;
using Microsoft.SqlTools.ServiceLayer.Test.Utility;
+using Microsoft.SqlTools.ServiceLayer.Workspace;
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
using Microsoft.SqlTools.Test.Utility;
using Moq;
@@ -148,6 +152,150 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
#endregion
+ #region "General Language Service tests"
+
+ ///
+ /// Check that autocomplete is enabled by default
+ ///
+ [Fact]
+ public void CheckAutocompleteEnabledByDefault()
+ {
+ // get test service
+ LanguageService service = TestObjects.GetTestLanguageService();
+ Assert.True(service.ShouldEnableAutocomplete());
+ }
+
+ ///
+ /// Test the service initialization code path and verify nothing throws
+ ///
+ [Fact]
+ public void ServiceInitiailzation()
+ {
+ InitializeTestServices();
+
+ Assert.True(LanguageService.Instance.Context != null);
+ Assert.True(LanguageService.Instance.ConnectionServiceInstance != null);
+ Assert.True(LanguageService.Instance.CurrentSettings != null);
+ Assert.True(LanguageService.Instance.CurrentWorkspace != null);
+
+ LanguageService.Instance.ConnectionServiceInstance = null;
+ Assert.True(LanguageService.Instance.ConnectionServiceInstance == null);
+ }
+
+ ///
+ /// Test the service initialization code path and verify nothing throws
+ ///
+ [Fact]
+ public void UpdateLanguageServiceOnConnection()
+ {
+ string ownerUri = "file://my/sample/file.sql";
+ var connectionService = TestObjects.GetTestConnectionService();
+ var connectionResult =
+ connectionService
+ .Connect(new ConnectParams()
+ {
+ OwnerUri = ownerUri,
+ Connection = TestObjects.GetTestConnectionDetails()
+ });
+
+ ConnectionInfo connInfo = null;
+ connectionService.TryFindConnection(ownerUri, out connInfo);
+
+ var task = LanguageService.Instance.UpdateLanguageServiceOnConnection(connInfo);
+ task.Wait();
+ }
+
+ ///
+ /// Test the service initialization code path and verify nothing throws
+ ///
+ [Fact]
+ public void PrepopulateCommonMetadata()
+ {
+ InitializeTestServices();
+
+ string sqlFilePath = GetTestSqlFile();
+ ScriptFile scriptFile = WorkspaceService.Instance.Workspace.GetFile(sqlFilePath);
+
+ string ownerUri = scriptFile.ClientFilePath;
+ var connectionService = TestObjects.GetTestConnectionService();
+ var connectionResult =
+ connectionService
+ .Connect(new ConnectParams()
+ {
+ OwnerUri = ownerUri,
+ Connection = TestObjects.GetTestConnectionDetails()
+ });
+
+ ConnectionInfo connInfo = null;
+ connectionService.TryFindConnection(ownerUri, out connInfo);
+
+ ScriptParseInfo scriptInfo = new ScriptParseInfo();
+ scriptInfo.IsConnected = true;
+
+ AutoCompleteHelper.PrepopulateCommonMetadata(connInfo, scriptInfo);
+ }
+
+ private string GetTestSqlFile()
+ {
+ string filePath = Path.Combine(
+ Path.GetDirectoryName(Assembly.GetEntryAssembly().Location),
+ "sqltest.sql");
+
+ if (File.Exists(filePath))
+ {
+ File.Delete(filePath);
+ }
+
+ File.WriteAllText(filePath, "SELECT * FROM sys.objects\n");
+
+ return filePath;
+ }
+
+ private void InitializeTestServices()
+ {
+ const string hostName = "SQL Tools Service Host";
+ const string hostProfileId = "SQLToolsService";
+ Version hostVersion = new Version(1,0);
+
+ // set up the host details and profile paths
+ var hostDetails = new HostDetails(hostName, hostProfileId, hostVersion);
+ var profilePaths = new ProfilePaths(hostProfileId, "baseAllUsersPath", "baseCurrentUserPath");
+ SqlToolsContext sqlToolsContext = new SqlToolsContext(hostDetails, profilePaths);
+
+ // Grab the instance of the service host
+ Hosting.ServiceHost serviceHost = Hosting.ServiceHost.Instance;
+
+ // Start the service
+ serviceHost.Start().Wait();
+
+ // Initialize the services that will be hosted here
+ WorkspaceService.Instance.InitializeService(serviceHost);
+ LanguageService.Instance.InitializeService(serviceHost, sqlToolsContext);
+ ConnectionService.Instance.InitializeService(serviceHost);
+ CredentialService.Instance.InitializeService(serviceHost);
+ QueryExecutionService.Instance.InitializeService(serviceHost);
+
+ serviceHost.Initialize();
+ }
+
+ private Hosting.ServiceHost GetTestServiceHost()
+ {
+ // set up the host details and profile paths
+ var hostDetails = new HostDetails("Test Service Host", "SQLToolsService", new Version(1,0));
+ var profilePaths = new ProfilePaths("SQLToolsService", "baseAllUsersPath", "baseCurrentUserPath");
+ SqlToolsContext context = new SqlToolsContext(hostDetails, profilePaths);
+
+ // Grab the instance of the service host
+ Hosting.ServiceHost host = Hosting.ServiceHost.Instance;
+
+ // Start the service
+ host.Start().Wait();
+
+ return host;
+ }
+
+ #endregion
+
#region "Autocomplete Tests"
// This test currently requires a live database connection to initialize