From 89ca0c1fde443aa3eb5487d79ce3d5147d487330 Mon Sep 17 00:00:00 2001 From: Karl Burtram Date: Wed, 24 Aug 2016 23:03:43 -0700 Subject: [PATCH] Inital SMO autocomplete commit --- .../Connection/ConnectionService.cs | 9 ++ .../LanguageServices/IntellisenseCache.cs | 25 +++-- .../LanguageServices/LanguageService.cs | 56 +++++++++++ .../project.json | 6 +- .../LanguageServer/LanguageServiceTests.cs | 93 +++++++++++++++++++ .../project.json | 4 + 6 files changed, 186 insertions(+), 7 deletions(-) diff --git a/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs b/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs index ca868391..36ed613a 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; using System.Data.SqlClient; using System.Threading.Tasks; +using Microsoft.SqlServer.Management.Common; using Microsoft.SqlTools.EditorServices.Utility; using Microsoft.SqlTools.ServiceLayer.Connection.Contracts; using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol; @@ -291,5 +292,13 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection } return connectionBuilder.ToString(); } + + public static ServerConnection GetServerConnection(ConnectionInfo connection) + { + string connectionString = BuildConnectionString(connection.ConnectionDetails); + var sqlConnection = new SqlConnection(connectionString); + return new ServerConnection(sqlConnection); + } + } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/IntellisenseCache.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/IntellisenseCache.cs index eea72771..51e1a1b7 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/IntellisenseCache.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/IntellisenseCache.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Threading.Tasks; +using Microsoft.SqlServer.Management.SqlParser.Intellisense; using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.Connection.Contracts; using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts; @@ -72,10 +73,22 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices { List completions = new List(); + // Take a reference to the list at a point in time in case we update and replace the list + //var suggestions = AutoCompleteList; + if (!LanguageService.Instance.ScriptParseInfoMap.ContainsKey(textDocumentPosition.Uri)) + { + return completions; + } + + var scriptParseInfo = LanguageService.Instance.ScriptParseInfoMap[textDocumentPosition.Uri]; + var suggestions = Resolver.FindCompletions( + scriptParseInfo.ParseResult, + textDocumentPosition.Position.Line, + textDocumentPosition.Position.Character, + scriptParseInfo.MetadataDisplayInfoProvider); + int i = 0; - // Take a reference to the list at a point in time in case we update and replace the list - var suggestions = AutoCompleteList; // the completion list will be null is user not connected to server if (this.AutoCompleteList != null) { @@ -85,13 +98,13 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices // convert the completion item candidates into CompletionItems completions.Add(new CompletionItem() { - Label = autoCompleteItem, + Label = autoCompleteItem.Title, Kind = CompletionItemKind.Keyword, - Detail = autoCompleteItem + " details", - Documentation = autoCompleteItem + " documentation", + Detail = autoCompleteItem.Title + " details", + Documentation = autoCompleteItem.Title + " documentation", TextEdit = new TextEdit { - NewText = autoCompleteItem, + NewText = autoCompleteItem.Title, Range = new Range { Start = new Position diff --git a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs index f380f1bb..d5203f7a 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs @@ -19,6 +19,10 @@ using System.Linq; using Microsoft.SqlServer.Management.SqlParser.Parser; using Location = Microsoft.SqlTools.ServiceLayer.Workspace.Contracts.Location; using Microsoft.SqlTools.ServiceLayer.Connection; +using Microsoft.SqlServer.Management.SqlParser.Binder; +using Microsoft.SqlServer.Management.SmoMetadataProvider; +using Microsoft.SqlServer.Management.Common; +using Microsoft.SqlServer.Management.SqlParser.MetadataProvider; namespace Microsoft.SqlTools.ServiceLayer.LanguageServices { @@ -33,6 +37,28 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices private static readonly Lazy instance = new Lazy(() => new LanguageService()); + private Lazy> scriptParseInfoMap + = new Lazy>(() => new Dictionary()); + + internal class ScriptParseInfo + { + public IBinder Binder { get; set; } + + public ParseResult ParseResult { get; set; } + + public SmoMetadataProvider MetadataProvider { get; set; } + + public MetadataDisplayInfoProvider MetadataDisplayInfoProvider { get; set; } + } + + internal Dictionary ScriptParseInfoMap + { + get + { + return this.scriptParseInfoMap.Value; + } + } + public static LanguageService Instance { get { return instance.Value; } @@ -125,6 +151,36 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices // save previous result for next incremental parse this.prevParseResult = parseResult; + ConnectionInfo connInfo; + bool isConnected = ConnectionService.Instance.TryFindConnection(scriptFile.ClientFilePath, out connInfo); + if (isConnected) + { + if (!this.ScriptParseInfoMap.ContainsKey(scriptFile.ClientFilePath)) + { + var srvConn = ConnectionService.GetServerConnection(connInfo); + var metadataProvider = SmoMetadataProvider.CreateConnectedProvider(srvConn); + var binder = BinderProvider.CreateBinder(metadataProvider); + var displayInfoProvider = new MetadataDisplayInfoProvider(); + + this.ScriptParseInfoMap.Add(scriptFile.ClientFilePath, + new ScriptParseInfo() + { + Binder = binder, + ParseResult = parseResult, + MetadataProvider = metadataProvider, + MetadataDisplayInfoProvider = displayInfoProvider + }); + } + + ScriptParseInfo parseInfo = this.ScriptParseInfoMap[scriptFile.ClientFilePath]; + List parseResults = new List(); + parseResults.Add(parseResult); + parseInfo.Binder.Bind( + parseResults, + connInfo.ConnectionDetails.DatabaseName, + BindMode.Batch); + } + // build a list of SQL script file markers from the errors List markers = new List(); foreach (var error in parseResult.Errors) diff --git a/src/Microsoft.SqlTools.ServiceLayer/project.json b/src/Microsoft.SqlTools.ServiceLayer/project.json index 0cfc0788..328ec553 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/project.json +++ b/src/Microsoft.SqlTools.ServiceLayer/project.json @@ -9,7 +9,11 @@ "Newtonsoft.Json": "9.0.1", "Microsoft.SqlServer.SqlParser": "140.1.4", "System.Data.Common": "4.1.0", - "System.Data.SqlClient": "4.1.0" + "System.Data.SqlClient": "4.1.0", + "Microsoft.SqlServer.Smo": "140.1.2", + "System.Security.SecureString": "4.0.0", + "System.Collections.Specialized": "4.0.1", + "System.ComponentModel.TypeConverter": "4.1.0" }, "frameworks": { "netcoreapp1.0": { diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/LanguageServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/LanguageServiceTests.cs index 9462d384..139daea2 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/LanguageServiceTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/LanguageServiceTests.cs @@ -3,10 +3,21 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // +using System; using System.Collections.Generic; using System.Data; using System.Data.Common; +using System.Data.SqlClient; +using System.Reflection; using System.Threading.Tasks; +using Microsoft.SqlServer.Management.Common; +using Microsoft.SqlServer.Management.Smo; +using Microsoft.SqlServer.Management.SmoMetadataProvider; +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.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.Connection.Contracts; using Microsoft.SqlTools.ServiceLayer.LanguageServices; @@ -26,6 +37,85 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices { #region "Diagnostics tests" + [Fact] + public void TestSmo() + { + SqlConnectionStringBuilder connectionBuilder = new SqlConnectionStringBuilder(); + connectionBuilder["Data Source"] = "sqltools11"; + connectionBuilder["Integrated Security"] = false; + connectionBuilder["User Id"] = "sa"; + connectionBuilder["Password"] = "Yukon900"; + connectionBuilder["Initial Catalog"] = "master"; + string connectionString = connectionBuilder.ToString(); + + var conn = new SqlConnection(connectionString); + var sqlConn = new ServerConnection(conn); + + var server = new Server(sqlConn); + string s = ""; + foreach (Database db2 in server.Databases) + { + s += db2.Name; + } + + var metadata = SmoMetadataProvider.CreateConnectedProvider(sqlConn); + var db = metadata.Server.Databases["master"]; + } + + [Fact] + public void TestSmoMetadataProvider() + { + SqlConnectionStringBuilder connectionBuilder = new SqlConnectionStringBuilder(); + //connectionBuilder["Data Source"] = "sqltools11"; + connectionBuilder["Data Source"] = "localhost"; + connectionBuilder["Integrated Security"] = false; + connectionBuilder["User Id"] = "sa"; + connectionBuilder["Password"] = "Yukon900"; + connectionBuilder["Initial Catalog"] = "master"; + + try + { + var sqlConnection = new SqlConnection(connectionBuilder.ToString()); + var connection = new ServerConnection(sqlConnection); + var metadataProvider = SmoMetadataProvider.CreateConnectedProvider(connection); + var binder = BinderProvider.CreateBinder(metadataProvider); + var displayInfoProvider = new MetadataDisplayInfoProvider(); + + //string sql = @"SELECT * FROM sys.objects;"; + + string sql = @"SELECT "; + + ParseOptions parseOptions = new ParseOptions(); + ParseResult parseResult = Parser.IncrementalParse( + sql, + null, + parseOptions); + + List parseResults = new List(); + parseResults.Add(parseResult); + binder.Bind(parseResults, "master", BindMode.Batch); + + var comp = Resolver.FindCompletions(parseResult, 1, 8, displayInfoProvider); + comp.Add(null); + } + finally + { + // Check if we failed to create a binder object. If so, we temporarely + // use a no-op binder which has the effect of turning off binding. We + // also set a timer that after the specified timeout expires removes + // the no-op timer (object becomes dead) which would give clients of + // this class an opportunity to remove it and create a new one. + } + } + + + // [Fact] + // public void TestAltParse() + // { + // var ls = new LanguageService(); + // ls.AltParse(); + // } + /// /// Verify that the latest SqlParser (2016 as of this writing) is used by default /// @@ -318,3 +408,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices } } + + + diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/project.json b/test/Microsoft.SqlTools.ServiceLayer.Test/project.json index 23c97d0b..5ce743e0 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/project.json +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/project.json @@ -9,6 +9,10 @@ "System.Runtime.Serialization.Primitives": "4.1.1", "System.Data.Common": "4.1.0", "System.Data.SqlClient": "4.1.0", + "Microsoft.SqlServer.Smo": "140.1.2", + "System.Security.SecureString": "4.0.0", + "System.Collections.Specialized": "4.0.1", + "System.ComponentModel.TypeConverter": "4.1.0", "xunit": "2.1.0", "dotnet-test-xunit": "1.0.0-rc2-192208-24", "moq.netcore": "4.4.0-beta8",