From 14f5a3e0f129d89e15147e7e9cd5bf5da6c6826a Mon Sep 17 00:00:00 2001 From: Justin M <63619224+JustinMDotNet@users.noreply.github.com> Date: Mon, 24 Aug 2020 13:18:00 -0700 Subject: [PATCH] 3278 Kusto Unit Test Refactor (#1053) * 3278 Moved functions related to Metadata from DataSourceFactory to MetadataFactory.cs * 3278 Refactored DataSourceFactory to not be static. Added IDataSourceFactory interface. * 3278 Refactored IKustoIntellisenseHelper.cs to not be static. Added IKustoIntellisenseHelper.cs interface. * 3278 Removed unused functions from Scripter and deleted ScripterCore.cs because it was unused. Refactored Scripter.cs methods to not be static and created IScripter.cs * 3278 Refactored datsasourceConnectionFactory in ConnectionService to use MEF through the InitializeService function * 3278 Refactored IAutoCompleteHelper to not be static and added IAutoCompleteHelper interface. * 3278 Removed unused classes DatabaseFullAccessException and FeatureWithFullDbAccess. Refactored ObjectExplorerService to use ImportingConstructor attribute. Removed commented out in KustoResultsReader. Removed unused functions from DatabaseLocksManager. Removed unused IDataSourceConnectionFactory from ConnectionService * 3278 Moved SqlConnectionOpener class to new file. Added new interfaces IConnectedBindingQueue.cs and ISqlConnectionOpener.cs Added sqlConnectionOpener to dependency in ConnectedBindingQueue. * 3278 Removed needsMetadata param from ConnectedBindingQueue. Added param to AddConnectionContext where it's used * 3278 Refactored ConnectedBindingQueue to use MEF. Refactored usages to run against interface. * 3278 Corrected ServiceHost namespace. Removed unused dependency to sqlToolsContext in LanguageService. Updated dependency in MetadataService and LanguageService to IProtocolEndpoint instead of ServiceHost * 3278 Added back NextResult function and summary to KustoResultsReader. Renamed instance to _instance in ConnectionService and DatabaseLocksManager to stay consistent. Changed OpenDataSourceConnection to private in ConnectionService * 3278 Converted helper methods back to static and removed backing interfaces * 3278 Reverted AdminService > InitializeService to use ServiceHost as param --- .../Admin/AdminService.cs | 16 +- .../Connection/ConnectionService.cs | 60 +- .../Connection/DataSourceConnectionFactory.cs | 5 +- .../Connection/DatabaseFullAccessException.cs | 28 - .../Connection/DatabaseLocksManager.cs | 86 +-- .../Connection/FeatureWithFullDbAccess.cs | 40 - .../DataSource/DataReaderWrapper.cs | 4 +- .../DataSource/DataSourceFactory.cs | 95 +-- .../KustoIntellisenseHelper.cs | 5 +- .../DataSource/KustoDataSource.cs | 17 +- .../DataSource/KustoResultsReader.cs | 19 +- .../DataSource/Metadata/MetadataFactory.cs | 104 +++ .../ReliableDataSourceConnection.cs | 16 +- .../HostLoader.cs | 15 +- .../LanguageServices/AutoCompleteHelper.cs | 3 +- .../LanguageServices/ConnectedBindingQueue.cs | 81 +-- .../IConnectedBindingQueue.cs | 26 + .../LanguageServices/ISqlConnectionOpener.cs | 13 + .../LanguageServices/LanguageService.cs | 63 +- .../LanguageServices/SqlConnectionOpener.cs | 15 + .../Metadata/MetadataService.cs | 14 +- .../ObjectExplorer/ObjectExplorerService.cs | 86 +-- src/Microsoft.Kusto.ServiceLayer/Program.cs | 3 - .../QueryExecution/QueryExecutionService.cs | 1 - .../Scripting/IScripter.cs | 12 + .../Scripting/ScriptAsScriptingOperation.cs | 19 +- .../Scripting/Scripter.cs | 81 +-- .../Scripting/ScripterCore.cs | 683 ------------------ .../Scripting/ScriptingScriptOperation.cs | 3 +- .../Scripting/ScriptingService.cs | 13 +- .../Scripting/SmoScriptingOperation.cs | 6 +- .../ServiceHost.cs | 8 +- .../Workspace/WorkspaceService.cs | 1 - .../ObjectExplorer/ObjectExplorerService.cs | 9 - 34 files changed, 355 insertions(+), 1295 deletions(-) delete mode 100644 src/Microsoft.Kusto.ServiceLayer/Connection/DatabaseFullAccessException.cs delete mode 100644 src/Microsoft.Kusto.ServiceLayer/Connection/FeatureWithFullDbAccess.cs create mode 100644 src/Microsoft.Kusto.ServiceLayer/DataSource/Metadata/MetadataFactory.cs create mode 100644 src/Microsoft.Kusto.ServiceLayer/LanguageServices/IConnectedBindingQueue.cs create mode 100644 src/Microsoft.Kusto.ServiceLayer/LanguageServices/ISqlConnectionOpener.cs create mode 100644 src/Microsoft.Kusto.ServiceLayer/LanguageServices/SqlConnectionOpener.cs create mode 100644 src/Microsoft.Kusto.ServiceLayer/Scripting/IScripter.cs delete mode 100644 src/Microsoft.Kusto.ServiceLayer/Scripting/ScripterCore.cs diff --git a/src/Microsoft.Kusto.ServiceLayer/Admin/AdminService.cs b/src/Microsoft.Kusto.ServiceLayer/Admin/AdminService.cs index 3c86dbbd..dfcadf16 100644 --- a/src/Microsoft.Kusto.ServiceLayer/Admin/AdminService.cs +++ b/src/Microsoft.Kusto.ServiceLayer/Admin/AdminService.cs @@ -12,7 +12,6 @@ using Microsoft.Kusto.ServiceLayer.Admin.Contracts; using Microsoft.Kusto.ServiceLayer.Connection; using Microsoft.Kusto.ServiceLayer.DataSource; using Microsoft.Kusto.ServiceLayer.DataSource.Metadata; -using Microsoft.Kusto.ServiceLayer.Hosting; using Microsoft.Kusto.ServiceLayer.Utility; namespace Microsoft.Kusto.ServiceLayer.Admin @@ -26,13 +25,6 @@ namespace Microsoft.Kusto.ServiceLayer.Admin private static ConnectionService connectionService = null; - /// - /// Default, parameterless constructor. - /// - internal AdminService() - { - } - /// /// Internal for testing purposes only /// @@ -72,7 +64,7 @@ namespace Microsoft.Kusto.ServiceLayer.Admin /// /// Handle get database info request /// - internal static async Task HandleGetDatabaseInfoRequest( + internal async Task HandleGetDatabaseInfoRequest( GetDatabaseInfoParams databaseParams, RequestContext requestContext) { @@ -114,18 +106,18 @@ namespace Microsoft.Kusto.ServiceLayer.Admin /// /// /// - internal static DatabaseInfo GetDatabaseInfo(ConnectionInfo connInfo) + internal DatabaseInfo GetDatabaseInfo(ConnectionInfo connInfo) { if(!string.IsNullOrEmpty(connInfo.ConnectionDetails.DatabaseName)){ ReliableDataSourceConnection connection; connInfo.TryGetConnection("Default", out connection); IDataSource dataSource = connection.GetUnderlyingConnection(); - DataSourceObjectMetadata objectMetadata = DataSourceFactory.CreateClusterMetadata(connInfo.ConnectionDetails.ServerName); + DataSourceObjectMetadata objectMetadata = MetadataFactory.CreateClusterMetadata(connInfo.ConnectionDetails.ServerName); List metadata = dataSource.GetChildObjects(objectMetadata, true).ToList(); var databaseMetadata = metadata.Where(o => o.Name == connInfo.ConnectionDetails.DatabaseName); - List databaseInfo = DataSourceFactory.ConvertToDatabaseInfo(databaseMetadata); + List databaseInfo = MetadataFactory.ConvertToDatabaseInfo(databaseMetadata); return databaseInfo.ElementAtOrDefault(0); } diff --git a/src/Microsoft.Kusto.ServiceLayer/Connection/ConnectionService.cs b/src/Microsoft.Kusto.ServiceLayer/Connection/ConnectionService.cs index da287c1d..f89915b7 100644 --- a/src/Microsoft.Kusto.ServiceLayer/Connection/ConnectionService.cs +++ b/src/Microsoft.Kusto.ServiceLayer/Connection/ConnectionService.cs @@ -38,18 +38,13 @@ namespace Microsoft.Kusto.ServiceLayer.Connection /// /// Singleton service instance /// - private static readonly Lazy instance + private static readonly Lazy _instance = new Lazy(() => new ConnectionService()); /// /// Gets the singleton service instance /// - public static ConnectionService Instance => instance.Value; - - /// - /// The SQL connection factory object - /// - private IDataSourceConnectionFactory connectionFactory; + public static ConnectionService Instance => _instance.Value; private DatabaseLocksManager lockedDatabaseManager; @@ -113,9 +108,6 @@ namespace Microsoft.Kusto.ServiceLayer.Connection /// public ConnectionService() { - var defaultQueue = new ConnectedBindingQueue(needsMetadata: false); - connectedQueues.AddOrUpdate("Default", defaultQueue, (key, old) => defaultQueue); - this.LockedDatabaseManager.ConnectionService = this; } /// @@ -181,25 +173,7 @@ namespace Microsoft.Kusto.ServiceLayer.Connection /// /// Gets the SQL connection factory instance /// - public IDataSourceConnectionFactory ConnectionFactory - { - get - { - if (this.connectionFactory == null) - { - this.connectionFactory = new DataSourceConnectionFactory(); - } - return this.connectionFactory; - } - - internal set { this.connectionFactory = value; } - } - - /// - /// Test constructor that injects dependency interfaces - /// - /// - public ConnectionService(IDataSourceConnectionFactory testFactory) => this.connectionFactory = testFactory; + private IDataSourceConnectionFactory _dataSourceConnectionFactory; // Attempts to link a URI to an actively used connection for this URI public virtual bool TryFindConnection(string ownerUri, out ConnectionInfo connectionInfo) => this.OwnerToConnectionMap.TryGetValue(ownerUri, out connectionInfo); @@ -254,7 +228,7 @@ namespace Microsoft.Kusto.ServiceLayer.Connection bool connectionChanged = false; if (!OwnerToConnectionMap.TryGetValue(connectionParams.OwnerUri, out connectionInfo)) { - connectionInfo = new ConnectionInfo(ConnectionFactory, connectionParams.OwnerUri, connectionParams.Connection); + connectionInfo = new ConnectionInfo(_dataSourceConnectionFactory, connectionParams.OwnerUri, connectionParams.Connection); } else if (IsConnectionChanged(connectionParams, connectionInfo)) { @@ -267,7 +241,7 @@ namespace Microsoft.Kusto.ServiceLayer.Connection if (connectionChanged) { - connectionInfo = new ConnectionInfo(ConnectionFactory, connectionParams.OwnerUri, connectionParams.Connection); + connectionInfo = new ConnectionInfo(_dataSourceConnectionFactory, connectionParams.OwnerUri, connectionParams.Connection); } // Try to open a connection with the given ConnectParams @@ -362,11 +336,6 @@ namespace Microsoft.Kusto.ServiceLayer.Connection return false; } - private bool IsDefaultConnectionType(string connectionType) - { - return string.IsNullOrEmpty(connectionType) || ConnectionType.Default.Equals(connectionType, StringComparison.CurrentCultureIgnoreCase); - } - private void DisconnectExistingConnectionIfNeeded(ConnectParams connectionParams, ConnectionInfo connectionInfo, bool disconnectAll) { // Resolve if it is an existing connection @@ -425,7 +394,7 @@ namespace Microsoft.Kusto.ServiceLayer.Connection var reliableConnection = connection as ReliableDataSourceConnection; IDataSource dataSource = reliableConnection.GetUnderlyingConnection(); - DataSourceObjectMetadata clusterMetadata = DataSourceFactory.CreateClusterMetadata(connectionInfo.ConnectionDetails.ServerName); + DataSourceObjectMetadata clusterMetadata = MetadataFactory.CreateClusterMetadata(connectionInfo.ConnectionDetails.ServerName); DiagnosticsInfo clusterDiagnostics = dataSource.GetDiagnostics(clusterMetadata); ReliableConnectionHelper.ServerInfo serverInfo = DataSourceFactory.ConvertToServerinfoFormat(DataSourceType.Kusto, clusterDiagnostics); @@ -801,14 +770,14 @@ namespace Microsoft.Kusto.ServiceLayer.Connection ConnectionDetails connectionDetails = info.ConnectionDetails.Clone(); IDataSource dataSource = OpenDataSourceConnection(info); - DataSourceObjectMetadata objectMetadata = DataSourceFactory.CreateClusterMetadata(info.ConnectionDetails.ServerName); + DataSourceObjectMetadata objectMetadata = MetadataFactory.CreateClusterMetadata(info.ConnectionDetails.ServerName); ListDatabasesResponse response = new ListDatabasesResponse(); // Mainly used by "manage" dashboard if(listDatabasesParams.IncludeDetails.HasTrue()){ IEnumerable databaseMetadataInfo = dataSource.GetChildObjects(objectMetadata, true); - List metadata = DataSourceFactory.ConvertToDatabaseInfo(databaseMetadataInfo); + List metadata = MetadataFactory.ConvertToDatabaseInfo(databaseMetadataInfo); response.Databases = metadata.ToArray(); return response; @@ -819,9 +788,14 @@ namespace Microsoft.Kusto.ServiceLayer.Connection return response; } - public void InitializeService(IProtocolEndpoint serviceHost) + public void InitializeService(IProtocolEndpoint serviceHost, IDataSourceConnectionFactory dataSourceConnectionFactory, + IConnectedBindingQueue connectedBindingQueue) { - this.ServiceHost = serviceHost; + ServiceHost = serviceHost; + _dataSourceConnectionFactory = dataSourceConnectionFactory; + + connectedQueues.AddOrUpdate("Default", connectedBindingQueue, (key, old) => connectedBindingQueue); + LockedDatabaseManager.ConnectionService = this; // Register request and event handlers with the Service Host serviceHost.SetRequestHandler(ConnectionRequest.Type, HandleConnectRequest); @@ -1429,12 +1403,12 @@ namespace Microsoft.Kusto.ServiceLayer.Connection /// The connection info to connect with /// A plaintext string that will be included in the application name for the connection /// A SqlConnection created with the given connection info - internal static IDataSource OpenDataSourceConnection(ConnectionInfo connInfo, string featureName = null) + private IDataSource OpenDataSourceConnection(ConnectionInfo connInfo, string featureName = null) { try { // generate connection string - string connectionString = ConnectionService.BuildConnectionString(connInfo.ConnectionDetails); + string connectionString = BuildConnectionString(connInfo.ConnectionDetails); // TODOKusto: Pass in type of DataSource needed to make this generic. Hard coded to Kusto right now. return DataSourceFactory.Create(DataSourceType.Kusto, connectionString, connInfo.ConnectionDetails.AzureAccountToken); diff --git a/src/Microsoft.Kusto.ServiceLayer/Connection/DataSourceConnectionFactory.cs b/src/Microsoft.Kusto.ServiceLayer/Connection/DataSourceConnectionFactory.cs index c676decd..03dac2f4 100644 --- a/src/Microsoft.Kusto.ServiceLayer/Connection/DataSourceConnectionFactory.cs +++ b/src/Microsoft.Kusto.ServiceLayer/Connection/DataSourceConnectionFactory.cs @@ -3,8 +3,8 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -using System.Data.Common; -using Microsoft.Kusto.ServiceLayer.Connection; +using System.Composition; +using Microsoft.Kusto.ServiceLayer.DataSource; using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection; namespace Microsoft.Kusto.ServiceLayer.Connection @@ -14,6 +14,7 @@ namespace Microsoft.Kusto.ServiceLayer.Connection /// The purpose of the factory is to make it easier to mock out the database /// in 'offline' unit test scenarios. /// + [Export(typeof(IDataSourceConnectionFactory))] public class DataSourceConnectionFactory : IDataSourceConnectionFactory { /// diff --git a/src/Microsoft.Kusto.ServiceLayer/Connection/DatabaseFullAccessException.cs b/src/Microsoft.Kusto.ServiceLayer/Connection/DatabaseFullAccessException.cs deleted file mode 100644 index f1a2d11c..00000000 --- a/src/Microsoft.Kusto.ServiceLayer/Connection/DatabaseFullAccessException.cs +++ /dev/null @@ -1,28 +0,0 @@ -// -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// - -using System; - -namespace Microsoft.Kusto.ServiceLayer.Connection -{ - public class DatabaseFullAccessException: Exception - { - public DatabaseFullAccessException() - : base() - { - } - - public DatabaseFullAccessException(string message, Exception exception) - : base(message, exception) - { - } - - - public DatabaseFullAccessException(string message) - : base(message) - { - } - } -} diff --git a/src/Microsoft.Kusto.ServiceLayer/Connection/DatabaseLocksManager.cs b/src/Microsoft.Kusto.ServiceLayer/Connection/DatabaseLocksManager.cs index c1aeb0df..4fac28c5 100644 --- a/src/Microsoft.Kusto.ServiceLayer/Connection/DatabaseLocksManager.cs +++ b/src/Microsoft.Kusto.ServiceLayer/Connection/DatabaseLocksManager.cs @@ -3,7 +3,6 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -using Microsoft.Kusto.ServiceLayer.LanguageServices; using System; using System.Collections.Generic; using System.Threading; @@ -12,100 +11,23 @@ namespace Microsoft.Kusto.ServiceLayer.Connection { public class DatabaseLocksManager: IDisposable { - internal DatabaseLocksManager(int waitToGetFullAccess) - { - this.waitToGetFullAccess = waitToGetFullAccess; - } - - private static DatabaseLocksManager instance = new DatabaseLocksManager(DefaultWaitToGetFullAccess); + private static readonly DatabaseLocksManager _instance = new DatabaseLocksManager(); public static DatabaseLocksManager Instance { get { - return instance; + return _instance; } } public ConnectionService ConnectionService { get; set; } - private Dictionary databaseAccessEvents = new Dictionary(); - private object databaseAccessLock = new object(); - public const int DefaultWaitToGetFullAccess = 10000; - public int waitToGetFullAccess = DefaultWaitToGetFullAccess; - - private ManualResetEvent GetResetEvent(string serverName, string databaseName) - { - string key = GenerateKey(serverName, databaseName); - ManualResetEvent resetEvent = null; - lock (databaseAccessLock) - { - if (!databaseAccessEvents.TryGetValue(key, out resetEvent)) - { - resetEvent = new ManualResetEvent(true); - databaseAccessEvents.Add(key, resetEvent); - } - } - - return resetEvent; - } - - public bool GainFullAccessToDatabase(string serverName, string databaseName) - { - /* - * TODO: add the lock so not two process can get full access at the same time - ManualResetEvent resetEvent = GetResetEvent(serverName, databaseName); - if (resetEvent.WaitOne(this.waitToGetFullAccess)) - { - resetEvent.Reset(); - - foreach (IConnectedBindingQueue item in ConnectionService.ConnectedQueues) - { - item.CloseConnections(serverName, databaseName); - } - return true; - } - else - { - throw new DatabaseFullAccessException($"Waited more than {waitToGetFullAccess} milli seconds for others to release the lock"); - } - */ - foreach (IConnectedBindingQueue item in ConnectionService.ConnectedQueues) - { - item.CloseConnections(serverName, databaseName, DefaultWaitToGetFullAccess); - } - return true; - - } - - public bool ReleaseAccess(string serverName, string databaseName) - { - /* - ManualResetEvent resetEvent = GetResetEvent(serverName, databaseName); - - foreach (IConnectedBindingQueue item in ConnectionService.ConnectedQueues) - { - item.OpenConnections(serverName, databaseName); - } - - resetEvent.Set(); - */ - foreach (IConnectedBindingQueue item in ConnectionService.ConnectedQueues) - { - item.OpenConnections(serverName, databaseName, DefaultWaitToGetFullAccess); - } - return true; - - } - - private string GenerateKey(string serverName, string databaseName) - { - return $"{serverName.ToLowerInvariant()}-{databaseName.ToLowerInvariant()}"; - } + private readonly Dictionary _databaseAccessEvents = new Dictionary(); public void Dispose() { - foreach (var resetEvent in databaseAccessEvents) + foreach (var resetEvent in _databaseAccessEvents) { resetEvent.Value.Dispose(); } diff --git a/src/Microsoft.Kusto.ServiceLayer/Connection/FeatureWithFullDbAccess.cs b/src/Microsoft.Kusto.ServiceLayer/Connection/FeatureWithFullDbAccess.cs deleted file mode 100644 index 9be034c7..00000000 --- a/src/Microsoft.Kusto.ServiceLayer/Connection/FeatureWithFullDbAccess.cs +++ /dev/null @@ -1,40 +0,0 @@ -// -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// - -namespace Microsoft.Kusto.ServiceLayer.Connection -{ - /// - /// Any operation that needs full access to databas should implement this interface. - /// Make sure to call GainAccessToDatabase before the operation and ReleaseAccessToDatabase after - /// - public interface IFeatureWithFullDbAccess - { - /// - /// Database Lock Manager - /// - DatabaseLocksManager LockedDatabaseManager { get; set; } - - /// - /// Makes sure the feature has fill access to the database - /// - bool GainAccessToDatabase(); - - /// - /// Release the access to db - /// - bool ReleaseAccessToDatabase(); - - /// - /// Server name - /// - string ServerName { get; } - - /// - /// Database name - /// - string DatabaseName { get; } - } - -} diff --git a/src/Microsoft.Kusto.ServiceLayer/DataSource/DataReaderWrapper.cs b/src/Microsoft.Kusto.ServiceLayer/DataSource/DataReaderWrapper.cs index 89ee3616..61caa060 100644 --- a/src/Microsoft.Kusto.ServiceLayer/DataSource/DataReaderWrapper.cs +++ b/src/Microsoft.Kusto.ServiceLayer/DataSource/DataReaderWrapper.cs @@ -2,13 +2,11 @@ // Copyright (c) Microsoft. All Rights Reserved. // using System; -using System.Text.RegularExpressions; -using System.Linq; using System.Data; namespace Microsoft.Kusto.ServiceLayer.DataSource { - class DataReaderWrapper:IDataReader + public class DataReaderWrapper : IDataReader { private readonly IDataReader _inner ; public DataReaderWrapper(IDataReader inner) diff --git a/src/Microsoft.Kusto.ServiceLayer/DataSource/DataSourceFactory.cs b/src/Microsoft.Kusto.ServiceLayer/DataSource/DataSourceFactory.cs index 3e78d500..59678a51 100644 --- a/src/Microsoft.Kusto.ServiceLayer/DataSource/DataSourceFactory.cs +++ b/src/Microsoft.Kusto.ServiceLayer/DataSource/DataSourceFactory.cs @@ -1,22 +1,15 @@ using System; using System.Collections.Generic; -using System.Linq; using Microsoft.Kusto.ServiceLayer.Utility; -using Microsoft.Kusto.ServiceLayer.Admin.Contracts; using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection; -using Microsoft.Kusto.ServiceLayer.Metadata.Contracts; using Microsoft.Kusto.ServiceLayer.DataSource.DataSourceIntellisense; -using Microsoft.Kusto.ServiceLayer.DataSource.Metadata; using Microsoft.Kusto.ServiceLayer.LanguageServices.Contracts; using Microsoft.Kusto.ServiceLayer.Workspace.Contracts; using Microsoft.Kusto.ServiceLayer.LanguageServices.Completion; namespace Microsoft.Kusto.ServiceLayer.DataSource { - /// - /// Data source factory. - /// - public static class DataSourceFactory + public class DataSourceFactory { public static IDataSource Create(DataSourceType dataSourceType, string connectionString, string azureAccountToken) { @@ -26,57 +19,16 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource switch (dataSourceType) { case DataSourceType.Kusto: - { - return new KustoDataSource(connectionString, azureAccountToken); - } + { + return new KustoDataSource(connectionString, azureAccountToken); + } default: - throw new ArgumentException($"Unsupported data source type \"{dataSourceType}\"", nameof(dataSourceType)); + throw new ArgumentException($"Unsupported data source type \"{dataSourceType}\"", + nameof(dataSourceType)); } } - public static DataSourceObjectMetadata CreateClusterMetadata(string clusterName) - { - ValidationUtils.IsArgumentNotNullOrWhiteSpace(clusterName, nameof(clusterName)); - - return new DataSourceObjectMetadata{ - MetadataType = DataSourceMetadataType.Cluster, - MetadataTypeName = DataSourceMetadataType.Cluster.ToString(), - Name = clusterName, - PrettyName = clusterName, - Urn = $"{clusterName}" - }; - } - - public static DataSourceObjectMetadata CreateDatabaseMetadata(DataSourceObjectMetadata clusterMetadata, string databaseName) - { - ValidationUtils.IsTrue(clusterMetadata.MetadataType == DataSourceMetadataType.Cluster, nameof(clusterMetadata)); - ValidationUtils.IsArgumentNotNullOrWhiteSpace(databaseName, nameof(databaseName)); - - return new DatabaseMetadata{ - ClusterName = clusterMetadata.Name, - MetadataType = DataSourceMetadataType.Database, - MetadataTypeName = DataSourceMetadataType.Database.ToString(), - Name = databaseName, - PrettyName = databaseName, - Urn = $"{clusterMetadata.Urn}.{databaseName}" - }; - } - - public static FolderMetadata CreateFolderMetadata(DataSourceObjectMetadata parentMetadata, string path, string name) - { - ValidationUtils.IsNotNull(parentMetadata, nameof(parentMetadata)); - - return new FolderMetadata{ - MetadataType = DataSourceMetadataType.Folder, - MetadataTypeName = DataSourceMetadataType.Folder.ToString(), - Name = name, - PrettyName = name, - ParentMetadata = parentMetadata, - Urn = $"{path}.{name}" - }; - } - // Gets default keywords for intellisense when there is no connection. public static CompletionItem[] GetDefaultAutoComplete(DataSourceType dataSourceType, ScriptDocumentInfo scriptDocumentInfo, Position textDocumentPosition){ switch (dataSourceType) @@ -105,41 +57,6 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource } } - // Converts database details shown on cluster manage dashboard to DatabaseInfo type. Add DataSourceType as param if required to show different properties - public static List ConvertToDatabaseInfo(IEnumerable clusterDBDetails) - { - var databaseDetails = new List(); - - if(typeof(DatabaseMetadata) == clusterDBDetails.FirstOrDefault().GetType()){ - foreach(var dbDetail in clusterDBDetails) - { - DatabaseInfo databaseInfo = new DatabaseInfo(); - Int64.TryParse(dbDetail.SizeInMB.ToString(), out long sum_OriginalSize); - databaseInfo.Options["name"] = dbDetail.Name; - databaseInfo.Options["sizeInMB"] = (sum_OriginalSize /(1024 * 1024)).ToString(); - databaseDetails.Add(databaseInfo); - } - } - - return databaseDetails; - } - - // Converts tables details shown on database manage dashboard to ObjectMetadata type. Add DataSourceType as param if required to show different properties - public static List ConvertToObjectMetadata(IEnumerable dbChildDetails) - { - var databaseChildDetails = new List(); - - foreach(var childDetail in dbChildDetails) - { - ObjectMetadata dbChildInfo = new ObjectMetadata(); - dbChildInfo.Name = childDetail.PrettyName; - dbChildInfo.MetadataTypeName = childDetail.MetadataTypeName; - dbChildInfo.MetadataType = MetadataType.Table; // Add mapping here. - databaseChildDetails.Add(dbChildInfo); - } - return databaseChildDetails; - } - public static ReliableConnectionHelper.ServerInfo ConvertToServerinfoFormat(DataSourceType dataSourceType, DiagnosticsInfo clusterDiagnostics) { switch (dataSourceType) diff --git a/src/Microsoft.Kusto.ServiceLayer/DataSource/DataSourceIntellisense/KustoIntellisenseHelper.cs b/src/Microsoft.Kusto.ServiceLayer/DataSource/DataSourceIntellisense/KustoIntellisenseHelper.cs index fa423e3c..75677e1f 100644 --- a/src/Microsoft.Kusto.ServiceLayer/DataSource/DataSourceIntellisense/KustoIntellisenseHelper.cs +++ b/src/Microsoft.Kusto.ServiceLayer/DataSource/DataSourceIntellisense/KustoIntellisenseHelper.cs @@ -22,9 +22,8 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource.DataSourceIntellisense /// /// Kusto specific class for intellisense helper functions. /// - public static class KustoIntellisenseHelper + public class KustoIntellisenseHelper { - public class ShowDatabasesResult { public string DatabaseName; @@ -162,7 +161,7 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource.DataSourceIntellisense /// /// Loads the schema for the specified databasea into a a . /// - public static async Task LoadDatabaseAsync(IDataSource dataSource, string databaseName, bool throwOnError = false) + private static async Task LoadDatabaseAsync(IDataSource dataSource, string databaseName, bool throwOnError = false) { var members = new List(); CancellationTokenSource source = new CancellationTokenSource(); diff --git a/src/Microsoft.Kusto.ServiceLayer/DataSource/KustoDataSource.cs b/src/Microsoft.Kusto.ServiceLayer/DataSource/KustoDataSource.cs index f54ffb86..7cba553c 100644 --- a/src/Microsoft.Kusto.ServiceLayer/DataSource/KustoDataSource.cs +++ b/src/Microsoft.Kusto.ServiceLayer/DataSource/KustoDataSource.cs @@ -88,9 +88,12 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource ClusterName = GetClusterName(connectionString); DatabaseName = GetDatabaseName(connectionString); UserToken = azureAccountToken; - SchemaState = Task.Run(() => KustoIntellisenseHelper.AddOrUpdateDatabaseAsync(this, GlobalState.Default, DatabaseName, ClusterName, throwOnError: false)).Result; + SchemaState = Task.Run(() => + KustoIntellisenseHelper.AddOrUpdateDatabaseAsync(this, GlobalState.Default, DatabaseName, ClusterName, + throwOnError: false)).Result; // Check if a connection can be made - ValidationUtils.IsTrue(Exists().Result, $"Unable to connect. ClusterName = {ClusterName}, DatabaseName = {DatabaseName}"); + ValidationUtils.IsTrue(Exists().Result, + $"Unable to connect. ClusterName = {ClusterName}, DatabaseName = {DatabaseName}"); } /// @@ -736,7 +739,7 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource if (tableInfos.Any(x => !string.IsNullOrWhiteSpace(x.Folder))) { // create Table folder to hold functions tables - var tableFolder = DataSourceFactory.CreateFolderMetadata(databaseMetadata, rootTableFolderKey.ToString(), "Tables"); + var tableFolder = MetadataFactory.CreateFolderMetadata(databaseMetadata, rootTableFolderKey.ToString(), "Tables"); _folderMetadata.AddRange(rootTableFolderKey.ToString(), new List {tableFolder}); rootTableFolderKey.Append($".{tableFolder.Name}"); @@ -782,7 +785,7 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource continue; } - var folder = DataSourceFactory.CreateFolderMetadata(objectMetadata, rootTableFolderKey, columnGroup.Key); + var folder = MetadataFactory.CreateFolderMetadata(objectMetadata, rootTableFolderKey, columnGroup.Key); tableFolders.Add(folder); } @@ -800,7 +803,7 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource // create Functions folder to hold functions folders var rootFunctionFolderKey = $"{databaseMetadata.Urn}"; - var rootFunctionFolder = DataSourceFactory.CreateFolderMetadata(databaseMetadata, rootFunctionFolderKey, "Functions"); + var rootFunctionFolder = MetadataFactory.CreateFolderMetadata(databaseMetadata, rootFunctionFolderKey, "Functions"); _folderMetadata.AddRange(rootFunctionFolderKey, new List {rootFunctionFolder}); // create each folder to hold functions @@ -834,13 +837,13 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource var topFolder = subFolders.First(); var folderKey = functionFolder.Urn; - var folder = DataSourceFactory.CreateFolderMetadata(databaseMetadata, folderKey, topFolder); + var folder = MetadataFactory.CreateFolderMetadata(databaseMetadata, folderKey, topFolder); functionFolders.SafeAdd(folderKey, folder); for (int i = 1; i < subFolders.Length; i++) { folderKey = $"{folderKey}.{subFolders[i - 1]}"; - var subFolder = DataSourceFactory.CreateFolderMetadata(databaseMetadata, folderKey, subFolders[i]); + var subFolder = MetadataFactory.CreateFolderMetadata(databaseMetadata, folderKey, subFolders[i]); functionFolders.SafeAdd(folderKey, subFolder); } } diff --git a/src/Microsoft.Kusto.ServiceLayer/DataSource/KustoResultsReader.cs b/src/Microsoft.Kusto.ServiceLayer/DataSource/KustoResultsReader.cs index c5bea819..9e9207ca 100644 --- a/src/Microsoft.Kusto.ServiceLayer/DataSource/KustoResultsReader.cs +++ b/src/Microsoft.Kusto.ServiceLayer/DataSource/KustoResultsReader.cs @@ -4,19 +4,18 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource { internal class KustoResultsReader : DataReaderWrapper { - public KustoResultsReader(IDataReader reader) - : base(reader) + public KustoResultsReader(IDataReader reader) : base(reader) { } - + /// - /// Kusto returns 3 results tables - QueryResults, QueryProperties, QueryStatus. When returning query results - /// we want the caller to only read the first table. We override the NextResult function here to only return one table - /// from the IDataReader. - /// - /*public override bool NextResult() - { - return false; + /// Kusto returns 3 results tables - QueryResults, QueryProperties, QueryStatus. When returning query results + /// we want the caller to only read the first table. We override the NextResult function here to only return one table + /// from the IDataReader. + /// + /*public override bool NextResult() + { + return false; }*/ } } \ No newline at end of file diff --git a/src/Microsoft.Kusto.ServiceLayer/DataSource/Metadata/MetadataFactory.cs b/src/Microsoft.Kusto.ServiceLayer/DataSource/Metadata/MetadataFactory.cs new file mode 100644 index 00000000..ba846c36 --- /dev/null +++ b/src/Microsoft.Kusto.ServiceLayer/DataSource/Metadata/MetadataFactory.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Kusto.ServiceLayer.Admin.Contracts; +using Microsoft.Kusto.ServiceLayer.Metadata.Contracts; +using Microsoft.Kusto.ServiceLayer.Utility; + +namespace Microsoft.Kusto.ServiceLayer.DataSource.Metadata +{ + public class MetadataFactory + { + public static DataSourceObjectMetadata CreateClusterMetadata(string clusterName) + { + ValidationUtils.IsArgumentNotNullOrWhiteSpace(clusterName, nameof(clusterName)); + + return new DataSourceObjectMetadata + { + MetadataType = DataSourceMetadataType.Cluster, + MetadataTypeName = DataSourceMetadataType.Cluster.ToString(), + Name = clusterName, + PrettyName = clusterName, + Urn = $"{clusterName}" + }; + } + + public static DataSourceObjectMetadata CreateDatabaseMetadata(DataSourceObjectMetadata clusterMetadata, + string databaseName) + { + ValidationUtils.IsTrue(clusterMetadata.MetadataType == DataSourceMetadataType.Cluster, + nameof(clusterMetadata)); + ValidationUtils.IsArgumentNotNullOrWhiteSpace(databaseName, nameof(databaseName)); + + return new DatabaseMetadata + { + ClusterName = clusterMetadata.Name, + MetadataType = DataSourceMetadataType.Database, + MetadataTypeName = DataSourceMetadataType.Database.ToString(), + Name = databaseName, + PrettyName = databaseName, + Urn = $"{clusterMetadata.Urn}.{databaseName}" + }; + } + + public static FolderMetadata CreateFolderMetadata(DataSourceObjectMetadata parentMetadata, string path, string name) + { + ValidationUtils.IsNotNull(parentMetadata, nameof(parentMetadata)); + + return new FolderMetadata + { + MetadataType = DataSourceMetadataType.Folder, + MetadataTypeName = DataSourceMetadataType.Folder.ToString(), + Name = name, + PrettyName = name, + ParentMetadata = parentMetadata, + Urn = $"{path}.{name}" + }; + } + + /// + /// Converts database details shown on cluster manage dashboard to DatabaseInfo type. Add DataSourceType as param if required to show different properties + /// + /// + /// + public static List ConvertToDatabaseInfo(IEnumerable clusterDBDetails) + { + var databaseDetails = new List(); + + if (typeof(DatabaseMetadata) == clusterDBDetails.FirstOrDefault().GetType()) + { + foreach (var dbDetail in clusterDBDetails) + { + DatabaseInfo databaseInfo = new DatabaseInfo(); + Int64.TryParse(dbDetail.SizeInMB.ToString(), out long sum_OriginalSize); + databaseInfo.Options["name"] = dbDetail.Name; + databaseInfo.Options["sizeInMB"] = (sum_OriginalSize / (1024 * 1024)).ToString(); + databaseDetails.Add(databaseInfo); + } + } + + return databaseDetails; + } + + /// + /// Converts tables details shown on database manage dashboard to ObjectMetadata type. Add DataSourceType as param if required to show different properties + /// + /// + /// + public static List ConvertToObjectMetadata(IEnumerable dbChildDetails) + { + var databaseChildDetails = new List(); + + foreach (var childDetail in dbChildDetails) + { + ObjectMetadata dbChildInfo = new ObjectMetadata(); + dbChildInfo.Name = childDetail.PrettyName; + dbChildInfo.MetadataTypeName = childDetail.MetadataTypeName; + dbChildInfo.MetadataType = MetadataType.Table; // Add mapping here. + databaseChildDetails.Add(dbChildInfo); + } + + return databaseChildDetails; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.Kusto.ServiceLayer/DataSource/ReliableDataSourceConnection.cs b/src/Microsoft.Kusto.ServiceLayer/DataSource/ReliableDataSourceConnection.cs index dd1e0bb5..a45ad9d4 100644 --- a/src/Microsoft.Kusto.ServiceLayer/DataSource/ReliableDataSourceConnection.cs +++ b/src/Microsoft.Kusto.ServiceLayer/DataSource/ReliableDataSourceConnection.cs @@ -21,16 +21,8 @@ // ======================================================================================= using System; -using System.Collections.Generic; -using System.Data; -using System.Collections; -using System.Data.Common; using System.Data.SqlClient; -using System.Diagnostics; -using System.Globalization; -using System.Text; using System.Threading; -using System.Text.RegularExpressions; using System.Threading.Tasks; using Microsoft.SqlTools.Utility; using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection; @@ -42,15 +34,15 @@ namespace Microsoft.Kusto.ServiceLayer.Connection /// Provides a reliable way of opening connections to and executing commands /// taking into account potential network unreliability and a requirement for connection retry. /// - public sealed partial class ReliableDataSourceConnection : IDisposable + public sealed class ReliableDataSourceConnection : IDisposable { private IDataSource _dataSource; private readonly RetryPolicy _connectionRetryPolicy; private RetryPolicy _commandRetryPolicy; - private Guid _azureSessionId = Guid.NewGuid(); + private readonly Guid _azureSessionId = Guid.NewGuid(); - private string _connectionString; - private string _azureAccountToken; + private readonly string _connectionString; + private readonly string _azureAccountToken; /// /// Initializes a new instance of the ReliableKustoClient class with a given connection string diff --git a/src/Microsoft.Kusto.ServiceLayer/HostLoader.cs b/src/Microsoft.Kusto.ServiceLayer/HostLoader.cs index c0c0e187..d6e577b3 100644 --- a/src/Microsoft.Kusto.ServiceLayer/HostLoader.cs +++ b/src/Microsoft.Kusto.ServiceLayer/HostLoader.cs @@ -11,7 +11,8 @@ using Microsoft.SqlTools.Hosting.Protocol; using Microsoft.Kusto.ServiceLayer.Admin; using Microsoft.Kusto.ServiceLayer.Metadata; using Microsoft.Kusto.ServiceLayer.Connection; -using Microsoft.Kusto.ServiceLayer.Hosting; +using Microsoft.Kusto.ServiceLayer.DataSource; +using Microsoft.Kusto.ServiceLayer.DataSource.Metadata; using Microsoft.Kusto.ServiceLayer.LanguageServices; using Microsoft.Kusto.ServiceLayer.QueryExecution; using Microsoft.Kusto.ServiceLayer.Scripting; @@ -66,6 +67,10 @@ namespace Microsoft.Kusto.ServiceLayer ExtensionServiceProvider serviceProvider = ExtensionServiceProvider.CreateDefaultServiceProvider(inclusionList); serviceProvider.RegisterSingleService(sqlToolsContext); serviceProvider.RegisterSingleService(serviceHost); + + var scripter = serviceProvider.GetService(); + var dataSourceConnectionFactory = serviceProvider.GetService(); + var connectedBindingQueue = serviceProvider.GetService(); // Initialize and register singleton services so they're accessible for any MEF service. In the future, these // could be updated to be IComposableServices, which would avoid the requirement to define a singleton instance @@ -73,10 +78,10 @@ namespace Microsoft.Kusto.ServiceLayer WorkspaceService.Instance.InitializeService(serviceHost); serviceProvider.RegisterSingleService(WorkspaceService.Instance); - LanguageService.Instance.InitializeService(serviceHost, sqlToolsContext); + LanguageService.Instance.InitializeService(serviceHost, connectedBindingQueue); serviceProvider.RegisterSingleService(LanguageService.Instance); - ConnectionService.Instance.InitializeService(serviceHost); + ConnectionService.Instance.InitializeService(serviceHost, dataSourceConnectionFactory, connectedBindingQueue); serviceProvider.RegisterSingleService(ConnectionService.Instance); CredentialService.Instance.InitializeService(serviceHost); @@ -85,7 +90,7 @@ namespace Microsoft.Kusto.ServiceLayer QueryExecutionService.Instance.InitializeService(serviceHost); serviceProvider.RegisterSingleService(QueryExecutionService.Instance); - ScriptingService.Instance.InitializeService(serviceHost); + ScriptingService.Instance.InitializeService(serviceHost, scripter); serviceProvider.RegisterSingleService(ScriptingService.Instance); AdminService.Instance.InitializeService(serviceHost); @@ -93,7 +98,7 @@ namespace Microsoft.Kusto.ServiceLayer MetadataService.Instance.InitializeService(serviceHost); serviceProvider.RegisterSingleService(MetadataService.Instance); - + InitializeHostedServices(serviceProvider, serviceHost); serviceHost.ServiceProvider = serviceProvider; diff --git a/src/Microsoft.Kusto.ServiceLayer/LanguageServices/AutoCompleteHelper.cs b/src/Microsoft.Kusto.ServiceLayer/LanguageServices/AutoCompleteHelper.cs index 509e3722..79c5ac91 100644 --- a/src/Microsoft.Kusto.ServiceLayer/LanguageServices/AutoCompleteHelper.cs +++ b/src/Microsoft.Kusto.ServiceLayer/LanguageServices/AutoCompleteHelper.cs @@ -2,6 +2,7 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. // + using Range = Microsoft.Kusto.ServiceLayer.Workspace.Contracts.Range; using Microsoft.Kusto.ServiceLayer.LanguageServices.Contracts; using Microsoft.Kusto.ServiceLayer.Workspace.Contracts; @@ -11,7 +12,7 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices /// /// All the conversion of intellisense info to vscode format is done in this class. /// - public static class AutoCompleteHelper + public class AutoCompleteHelper { /// /// Create a completion item from the default item text since VS Code expects CompletionItems diff --git a/src/Microsoft.Kusto.ServiceLayer/LanguageServices/ConnectedBindingQueue.cs b/src/Microsoft.Kusto.ServiceLayer/LanguageServices/ConnectedBindingQueue.cs index 49391ea8..dd7405b9 100644 --- a/src/Microsoft.Kusto.ServiceLayer/LanguageServices/ConnectedBindingQueue.cs +++ b/src/Microsoft.Kusto.ServiceLayer/LanguageServices/ConnectedBindingQueue.cs @@ -4,8 +4,7 @@ // using System; -using System.Data.SqlClient; -using Microsoft.SqlServer.Management.Common; +using System.Composition; using Microsoft.SqlServer.Management.SmoMetadataProvider; using Microsoft.SqlServer.Management.SqlParser.Binder; using Microsoft.SqlServer.Management.SqlParser.MetadataProvider; @@ -14,81 +13,36 @@ using Microsoft.Kusto.ServiceLayer.Connection.Contracts; using Microsoft.Kusto.ServiceLayer.SqlContext; using Microsoft.Kusto.ServiceLayer.Workspace; using Microsoft.Kusto.ServiceLayer.DataSource; -using System.Threading; namespace Microsoft.Kusto.ServiceLayer.LanguageServices { - public interface IConnectedBindingQueue - { - void CloseConnections(string serverName, string databaseName, int millisecondsTimeout); - void OpenConnections(string serverName, string databaseName, int millisecondsTimeout); - string AddConnectionContext(ConnectionInfo connInfo, string featureName = null, bool overwrite = false); - void Dispose(); - QueueItem QueueBindingOperation( - string key, - Func bindOperation, - Func timeoutOperation = null, - Func errorHandler = null, - int? bindingTimeout = null, - int? waitForLockTimeout = null); - } - - public class SqlConnectionOpener - { - /// - /// Virtual method used to support mocking and testing - /// - public virtual ServerConnection OpenServerConnection(ConnectionInfo connInfo, string featureName) - { - return ConnectionService.OpenServerConnection(connInfo, featureName); - } - } - /// /// ConnectedBindingQueue class for processing online binding requests /// + [Export(typeof(IConnectedBindingQueue))] public class ConnectedBindingQueue : BindingQueue, IConnectedBindingQueue { internal const int DefaultBindingTimeout = 500; - - internal const int DefaultMinimumConnectionTimeout = 30; - - /// - /// flag determing if the connection queue requires online metadata objects - /// it's much cheaper to not construct these objects if not needed - /// - private bool needsMetadata; - private SqlConnectionOpener connectionOpener; + private readonly ISqlConnectionOpener _connectionOpener; /// /// Gets the current settings /// - internal SqlToolsSettings CurrentSettings + private SqlToolsSettings CurrentSettings { get { return WorkspaceService.Instance.CurrentSettings; } } - public ConnectedBindingQueue() - : this(true) - { - } - - public ConnectedBindingQueue(bool needsMetadata) + [ImportingConstructor] + public ConnectedBindingQueue(ISqlConnectionOpener sqlConnectionOpener) { - this.needsMetadata = needsMetadata; - this.connectionOpener = new SqlConnectionOpener(); - } - - // For testing purposes only - internal void SetConnectionOpener(SqlConnectionOpener opener) - { - this.connectionOpener = opener; + _connectionOpener = sqlConnectionOpener; } /// /// Generate a unique key based on the ConnectionInfo object /// - /// + /// internal static string GetConnectionContextKey(ConnectionDetails details) { string key = string.Format("{0}_{1}_{2}_{3}", @@ -114,7 +68,8 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices /// /// Generate a unique key based on the ConnectionInfo object /// - /// + /// + /// private string GetConnectionContextKey(string serverName, string databaseName) { return string.Format("{0}_{1}", @@ -156,7 +111,7 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices } } - public void RemoveBindigContext(ConnectionInfo connInfo) + public void RemoveBindingContext(ConnectionInfo connInfo) { string connectionKey = GetConnectionContextKey(connInfo.ConnectionDetails); if (BindingContextExists(connectionKey)) @@ -168,9 +123,11 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices /// /// Use a ConnectionInfo item to create a connected binding context /// - /// Connection info used to create binding context - /// Overwrite existing context - public virtual string AddConnectionContext(ConnectionInfo connInfo, string featureName = null, bool overwrite = false) + /// Connection info used to create binding context + /// + /// + /// Overwrite existing context + public string AddConnectionContext(ConnectionInfo connInfo, bool needMetadata, string featureName = null, bool overwrite = false) { if (connInfo == null) { @@ -191,7 +148,7 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices return connectionKey; } } - IBindingContext bindingContext = this.GetOrCreateBindingContext(connectionKey); + IBindingContext bindingContext = GetOrCreateBindingContext(connectionKey); if (bindingContext.BindingLock.WaitOne()) { @@ -200,12 +157,12 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices bindingContext.BindingLock.Reset(); // populate the binding context to work with the SMO metadata provider - bindingContext.ServerConnection = connectionOpener.OpenServerConnection(connInfo, featureName); + bindingContext.ServerConnection = _connectionOpener.OpenServerConnection(connInfo, featureName); string connectionString = ConnectionService.BuildConnectionString(connInfo.ConnectionDetails); bindingContext.DataSource = DataSourceFactory.Create(DataSourceType.Kusto, connectionString, connInfo.ConnectionDetails.AzureAccountToken); - if (this.needsMetadata) + if (needMetadata) { bindingContext.SmoMetadataProvider = SmoMetadataProvider.CreateConnectedProvider(bindingContext.ServerConnection); bindingContext.MetadataDisplayInfoProvider = new MetadataDisplayInfoProvider(); diff --git a/src/Microsoft.Kusto.ServiceLayer/LanguageServices/IConnectedBindingQueue.cs b/src/Microsoft.Kusto.ServiceLayer/LanguageServices/IConnectedBindingQueue.cs new file mode 100644 index 00000000..f777fdb3 --- /dev/null +++ b/src/Microsoft.Kusto.ServiceLayer/LanguageServices/IConnectedBindingQueue.cs @@ -0,0 +1,26 @@ +using System; +using System.Threading; +using Microsoft.Kusto.ServiceLayer.Connection; + +namespace Microsoft.Kusto.ServiceLayer.LanguageServices +{ + public interface IConnectedBindingQueue + { + event BindingQueue.UnhandledExceptionDelegate OnUnhandledException; + + void CloseConnections(string serverName, string databaseName, int millisecondsTimeout); + void OpenConnections(string serverName, string databaseName, int millisecondsTimeout); + string AddConnectionContext(ConnectionInfo connInfo, bool needMetadata, string featureName = null, bool overwrite = false); + void Dispose(); + bool IsBindingContextConnected(string key); + void RemoveBindingContext(ConnectionInfo connInfo); + + QueueItem QueueBindingOperation( + string key, + Func bindOperation, + Func timeoutOperation = null, + Func errorHandler = null, + int? bindingTimeout = null, + int? waitForLockTimeout = null); + } +} \ No newline at end of file diff --git a/src/Microsoft.Kusto.ServiceLayer/LanguageServices/ISqlConnectionOpener.cs b/src/Microsoft.Kusto.ServiceLayer/LanguageServices/ISqlConnectionOpener.cs new file mode 100644 index 00000000..a2eba050 --- /dev/null +++ b/src/Microsoft.Kusto.ServiceLayer/LanguageServices/ISqlConnectionOpener.cs @@ -0,0 +1,13 @@ +using Microsoft.Kusto.ServiceLayer.Connection; +using Microsoft.SqlServer.Management.Common; + +namespace Microsoft.Kusto.ServiceLayer.LanguageServices +{ + public interface ISqlConnectionOpener + { + /// + /// Virtual method used to support mocking and testing + /// + ServerConnection OpenServerConnection(ConnectionInfo connInfo, string featureName); + } +} \ No newline at end of file diff --git a/src/Microsoft.Kusto.ServiceLayer/LanguageServices/LanguageService.cs b/src/Microsoft.Kusto.ServiceLayer/LanguageServices/LanguageService.cs index a2ed3e6e..7a347456 100644 --- a/src/Microsoft.Kusto.ServiceLayer/LanguageServices/LanguageService.cs +++ b/src/Microsoft.Kusto.ServiceLayer/LanguageServices/LanguageService.cs @@ -18,10 +18,8 @@ using Microsoft.Kusto.ServiceLayer.DataSource; using Microsoft.Kusto.ServiceLayer.DataSource.DataSourceIntellisense; using Microsoft.Kusto.ServiceLayer.Connection; using Microsoft.Kusto.ServiceLayer.Connection.Contracts; -using Microsoft.Kusto.ServiceLayer.Hosting; using Microsoft.Kusto.ServiceLayer.LanguageServices.Completion; using Microsoft.Kusto.ServiceLayer.LanguageServices.Contracts; -using SqlToolsContext = Microsoft.SqlTools.ServiceLayer.SqlContext.SqlToolsContext; using Microsoft.Kusto.ServiceLayer.Utility; using Microsoft.Kusto.ServiceLayer.Workspace; using Microsoft.Kusto.ServiceLayer.Workspace.Contracts; @@ -82,7 +80,7 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices private ScriptParseInfo currentCompletionParseInfo; - private ConnectedBindingQueue bindingQueue = new ConnectedBindingQueue(); + private IConnectedBindingQueue _bindingQueue; private ParseOptions defaultParseOptions = new ParseOptions( batchSeparator: LanguageService.DefaultBatchSeperator, @@ -125,22 +123,6 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices #region Properties - /// - /// Gets or sets the binding queue instance - /// Internal for testing purposes only - /// - internal ConnectedBindingQueue BindingQueue - { - get - { - return this.bindingQueue; - } - set - { - this.bindingQueue = value; - } - } - /// /// Internal for testing purposes only /// @@ -151,7 +133,7 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices if (connectionService == null) { connectionService = ConnectionService.Instance; - connectionService.RegisterConnectedQueue("LanguageService", bindingQueue); + connectionService.RegisterConnectedQueue("LanguageService", _bindingQueue); } return connectionService; } @@ -216,12 +198,6 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices get { return WorkspaceServiceInstance.Workspace; } } - /// - /// Gets or sets the current SQL Tools context - /// - /// - internal SqlToolsContext Context { get; set; } - #endregion #region Public Methods @@ -231,23 +207,20 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices /// /// /// - public void InitializeService(ServiceHost serviceHost, SqlToolsContext context) + /// + /// + public void InitializeService(ServiceHost serviceHost, IConnectedBindingQueue connectedBindingQueue) { + _bindingQueue = connectedBindingQueue; // Register the requests that this service will handle - // turn off until needed (10/28/2016) - // serviceHost.SetRequestHandler(ReferencesRequest.Type, HandleReferencesRequest); - // serviceHost.SetRequestHandler(DocumentHighlightRequest.Type, HandleDocumentHighlightRequest); - //serviceHost.SetRequestHandler(SignatureHelpRequest.Type, HandleSignatureHelpRequest); // Kusto api doesnt support this as of now. Implement it wherever applicable. Hover help is closest to signature help serviceHost.SetRequestHandler(CompletionResolveRequest.Type, HandleCompletionResolveRequest); serviceHost.SetRequestHandler(HoverRequest.Type, HandleHoverRequest); serviceHost.SetRequestHandler(CompletionRequest.Type, HandleCompletionRequest); serviceHost.SetRequestHandler(DefinitionRequest.Type, HandleDefinitionRequest); // Parses "Go to definition" functionality serviceHost.SetRequestHandler(SyntaxParseRequest.Type, HandleSyntaxParseRequest); // Parses syntax errors - //serviceHost.SetRequestHandler(CompletionExtLoadRequest.Type, HandleCompletionExtLoadRequest); serviceHost.SetEventHandler(RebuildIntelliSenseNotification.Type, HandleRebuildIntelliSenseNotification); - //serviceHost.SetEventHandler(LanguageFlavorChangeNotification.Type, HandleDidChangeLanguageFlavorNotification); // Register a no-op shutdown task for validation of the shutdown logic serviceHost.RegisterShutdownTask(async (shutdownParams, shutdownRequestContext) => @@ -274,10 +247,7 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices // Register a callback for when a connection is closed ConnectionServiceInstance.RegisterOnDisconnectTask(RemoveAutoCompleteCacheUriReference); - - // Store the SqlToolsContext for future use - Context = context; - + } #endregion @@ -599,7 +569,7 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices { try { - this.BindingQueue.AddConnectionContext(connInfo, featureName: "LanguageService", overwrite: true); + _bindingQueue.AddConnectionContext(connInfo, true, featureName: "LanguageService", overwrite: true); RemoveScriptParseInfo(rebuildParams.OwnerUri); UpdateLanguageServiceOnConnection(connInfo).Wait(); } @@ -724,8 +694,8 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices { try { - scriptInfo.ConnectionKey = this.BindingQueue.AddConnectionContext(connInfo, "languageService"); - scriptInfo.IsConnected = this.BindingQueue.IsBindingContextConnected(scriptInfo.ConnectionKey); + scriptInfo.ConnectionKey = _bindingQueue.AddConnectionContext(connInfo, true,"languageService"); + scriptInfo.IsConnected = _bindingQueue.IsBindingContextConnected(scriptInfo.ConnectionKey); } catch (Exception ex) { @@ -822,13 +792,6 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices /// internal Hover GetHoverItem(TextDocumentPosition textDocumentPosition, ScriptFile scriptFile) { - int startLine = textDocumentPosition.Position.Line; - int startColumn = TextUtilities.PositionOfPrevDelimeter( - scriptFile.Contents, - textDocumentPosition.Position.Line, - textDocumentPosition.Position.Character); - int endColumn = textDocumentPosition.Position.Character; - ScriptParseInfo scriptParseInfo = GetScriptParseInfo(scriptFile.ClientUri); ConnectionInfo connInfo; ConnectionServiceInstance.TryFindConnection( @@ -841,7 +804,7 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices { try { - QueueItem queueItem = this.BindingQueue.QueueBindingOperation( + QueueItem queueItem = _bindingQueue.QueueBindingOperation( key: scriptParseInfo.ConnectionKey, bindingTimeout: LanguageService.HoverTimeout, bindOperation: (bindingContext, cancelToken) => @@ -1191,9 +1154,9 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices public void Dispose() { - if (bindingQueue != null) + if (_bindingQueue != null) { - bindingQueue.Dispose(); + _bindingQueue.Dispose(); } } } diff --git a/src/Microsoft.Kusto.ServiceLayer/LanguageServices/SqlConnectionOpener.cs b/src/Microsoft.Kusto.ServiceLayer/LanguageServices/SqlConnectionOpener.cs new file mode 100644 index 00000000..eb010220 --- /dev/null +++ b/src/Microsoft.Kusto.ServiceLayer/LanguageServices/SqlConnectionOpener.cs @@ -0,0 +1,15 @@ +using System.Composition; +using Microsoft.Kusto.ServiceLayer.Connection; +using Microsoft.SqlServer.Management.Common; + +namespace Microsoft.Kusto.ServiceLayer.LanguageServices +{ + [Export(typeof(ISqlConnectionOpener))] + public class SqlConnectionOpener : ISqlConnectionOpener + { + public ServerConnection OpenServerConnection(ConnectionInfo connInfo, string featureName) + { + return ConnectionService.OpenServerConnection(connInfo, featureName); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.Kusto.ServiceLayer/Metadata/MetadataService.cs b/src/Microsoft.Kusto.ServiceLayer/Metadata/MetadataService.cs index 36015f55..61582a3b 100644 --- a/src/Microsoft.Kusto.ServiceLayer/Metadata/MetadataService.cs +++ b/src/Microsoft.Kusto.ServiceLayer/Metadata/MetadataService.cs @@ -4,12 +4,10 @@ // using System; -using System.Linq; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.SqlTools.Hosting.Protocol; using Microsoft.Kusto.ServiceLayer.Connection; -using Microsoft.Kusto.ServiceLayer.Hosting; using Microsoft.Kusto.ServiceLayer.Metadata.Contracts; using Microsoft.Kusto.ServiceLayer.Utility; using Microsoft.Kusto.ServiceLayer.DataSource; @@ -22,7 +20,7 @@ namespace Microsoft.Kusto.ServiceLayer.Metadata /// public sealed class MetadataService { - private static readonly Lazy LazyInstance = new Lazy(() => new MetadataService()); + private static readonly Lazy LazyInstance = new Lazy(); public static MetadataService Instance => LazyInstance.Value; @@ -52,8 +50,8 @@ namespace Microsoft.Kusto.ServiceLayer.Metadata /// Initializes the Metadata Service instance /// /// - /// - public void InitializeService(ServiceHost serviceHost) + /// + public void InitializeService(IProtocolEndpoint serviceHost) { serviceHost.SetRequestHandler(MetadataListRequest.Type, HandleMetadataListRequest); } @@ -79,11 +77,11 @@ namespace Microsoft.Kusto.ServiceLayer.Metadata connInfo.TryGetConnection("Default", out connection); IDataSource dataSource = connection.GetUnderlyingConnection(); - DataSourceObjectMetadata objectMetadata = DataSourceFactory.CreateClusterMetadata(connInfo.ConnectionDetails.ServerName); - DataSourceObjectMetadata databaseMetadata = DataSourceFactory.CreateDatabaseMetadata(objectMetadata, connInfo.ConnectionDetails.DatabaseName); + DataSourceObjectMetadata objectMetadata = MetadataFactory.CreateClusterMetadata(connInfo.ConnectionDetails.ServerName); + DataSourceObjectMetadata databaseMetadata = MetadataFactory.CreateDatabaseMetadata(objectMetadata, connInfo.ConnectionDetails.DatabaseName); IEnumerable databaseChildMetadataInfo = dataSource.GetChildObjects(databaseMetadata, true); - metadata = DataSourceFactory.ConvertToObjectMetadata(databaseChildMetadataInfo); + metadata = MetadataFactory.ConvertToObjectMetadata(databaseChildMetadataInfo); } await requestContext.SendResult(new MetadataQueryResult diff --git a/src/Microsoft.Kusto.ServiceLayer/ObjectExplorer/ObjectExplorerService.cs b/src/Microsoft.Kusto.ServiceLayer/ObjectExplorer/ObjectExplorerService.cs index b16677cd..4b48dfa0 100644 --- a/src/Microsoft.Kusto.ServiceLayer/ObjectExplorer/ObjectExplorerService.cs +++ b/src/Microsoft.Kusto.ServiceLayer/ObjectExplorer/ObjectExplorerService.cs @@ -38,15 +38,15 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer [Export(typeof(IHostedService))] public class ObjectExplorerService : HostedService, IComposableService, IHostedService, IDisposable { + private readonly IConnectedBindingQueue _connectedBindingQueue; internal const string uriPrefix = "objectexplorer://"; // Instance of the connection service, used to get the connection info for a given owner URI private ConnectionService connectionService; - private IProtocolEndpoint serviceHost; + private IProtocolEndpoint _serviceHost; private ConcurrentDictionary sessionMap; private readonly Lazy>> applicableNodeChildFactories; private IMultiServiceProvider serviceProvider; - private ConnectedBindingQueue bindingQueue = new ConnectedBindingQueue(needsMetadata: false); private string connectionName = "ObjectExplorer"; /// @@ -57,34 +57,15 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer /// /// Singleton constructor /// - public ObjectExplorerService() + [ImportingConstructor] + public ObjectExplorerService(IConnectedBindingQueue connectedBindingQueue) { + _connectedBindingQueue = connectedBindingQueue; sessionMap = new ConcurrentDictionary(); - applicableNodeChildFactories = new Lazy>>(() => PopulateFactories()); + applicableNodeChildFactories = new Lazy>>(PopulateFactories); NodePathGenerator.Initialize(); } - internal ConnectedBindingQueue ConnectedBindingQueue - { - get - { - return bindingQueue; - } - set - { - this.bindingQueue = value; - } - } - - /// - /// Internal for testing only - /// - internal ObjectExplorerService(ExtensionServiceProvider serviceProvider) - : this() - { - SetServiceProvider(serviceProvider); - } - private Dictionary> ApplicableNodeChildFactories { get @@ -116,7 +97,7 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer connectionService = provider.GetService(); try { - connectionService.RegisterConnectedQueue(connectionName, bindingQueue); + connectionService.RegisterConnectedQueue(connectionName, _connectedBindingQueue); } catch(Exception ex) @@ -132,9 +113,9 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer public override void InitializeService(IProtocolEndpoint serviceHost) { Logger.Write(TraceEventType.Verbose, "ObjectExplorer service initialized"); - this.serviceHost = serviceHost; + _serviceHost = serviceHost; - this.ConnectedBindingQueue.OnUnhandledException += OnUnhandledException; + _connectedBindingQueue.OnUnhandledException += OnUnhandledException; // Register handlers for requests serviceHost.SetRequestHandler(CreateSessionRequest.Type, HandleCreateSessionRequest); @@ -217,7 +198,7 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer if (!sessionMap.TryGetValue(uri, out session)) { Logger.Write(TraceEventType.Verbose, $"Cannot expand object explorer node. Couldn't find session for uri. {uri} "); - await serviceHost.SendEvent(ExpandCompleteNotification.Type, new ExpandResponse + await _serviceHost.SendEvent(ExpandCompleteNotification.Type, new ExpandResponse { SessionId = expandParams.SessionId, NodePath = expandParams.NodePath, @@ -247,7 +228,7 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer if (!sessionMap.TryGetValue(uri, out session)) { Logger.Write(TraceEventType.Verbose, $"Cannot expand object explorer node. Couldn't find session for uri. {uri} "); - await serviceHost.SendEvent(ExpandCompleteNotification.Type, new ExpandResponse + await _serviceHost.SendEvent(ExpandCompleteNotification.Type, new ExpandResponse { SessionId = refreshParams.SessionId, NodePath = refreshParams.NodePath, @@ -319,7 +300,7 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer { if (session != null && session.ConnectionInfo != null) { - bindingQueue.RemoveBindigContext(session.ConnectionInfo); + _connectedBindingQueue.RemoveBindingContext(session.ConnectionInfo); } } connectionService.Disconnect(new DisconnectParams() @@ -352,7 +333,7 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer ErrorMessage = result.Exception != null ? result.Exception.Message : $"Failed to create session for session id {uri}" }; - await serviceHost.SendEvent(CreateSessionCompleteNotification.Type, response); + await _serviceHost.SendEvent(CreateSessionCompleteNotification.Type, response); } return result; }).ContinueWithOnFaulted(null); @@ -388,7 +369,7 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer SessionId = uri, ErrorMessage = session.ErrorMessage }; - await serviceHost.SendEvent(CreateSessionCompleteNotification.Type, response); + await _serviceHost.SendEvent(CreateSessionCompleteNotification.Type, response); return response; } return null; @@ -426,8 +407,8 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer try { int timeout = (int)TimeSpan.FromSeconds(settings?.ExpandTimeout ?? ObjectExplorerSettings.DefaultExpandTimeout).TotalMilliseconds; - QueueItem queueItem = bindingQueue.QueueBindingOperation( - key: bindingQueue.AddConnectionContext(session.ConnectionInfo, connectionName), + QueueItem queueItem = _connectedBindingQueue.QueueBindingOperation( + key: _connectedBindingQueue.AddConnectionContext(session.ConnectionInfo, false, connectionName, false), bindingTimeout: timeout, waitForLockTimeout: timeout, bindOperation: (bindingContext, cancelToken) => @@ -502,8 +483,8 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer } int timeout = (int)TimeSpan.FromSeconds(settings?.CreateSessionTimeout ?? ObjectExplorerSettings.DefaultCreateSessionTimeout).TotalMilliseconds; - QueueItem queueItem = bindingQueue.QueueBindingOperation( - key: bindingQueue.AddConnectionContext(connectionInfo, connectionName), + QueueItem queueItem = _connectedBindingQueue.QueueBindingOperation( + key: _connectedBindingQueue.AddConnectionContext(connectionInfo, false, connectionName), bindingTimeout: timeout, waitForLockTimeout: timeout, bindOperation: (bindingContext, cancelToken) => @@ -564,7 +545,7 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer ErrorMessage = errorMessage, SessionId = uri }; - await serviceHost.SendEvent(CreateSessionCompleteNotification.Type, result); + await _serviceHost.SendEvent(CreateSessionCompleteNotification.Type, result); } internal async Task SendSessionDisconnectedNotification(string uri, bool success, string errorMessage) @@ -576,7 +557,7 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer ErrorMessage = errorMessage, SessionId = uri }; - await serviceHost.SendEvent(SessionDisconnectedNotification.Type, result); + await _serviceHost.SendEvent(SessionDisconnectedNotification.Type, result); } private void RunExpandTask(ObjectExplorerSession session, ExpandParams expandParams, bool forceRefresh = false) @@ -594,7 +575,7 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer cancellationTokenSource.Cancel(); ExpandResponse response = CreateExpandResponse(session, expandParams); response.ErrorMessage = result.Exception != null ? result.Exception.Message: $"Failed to expand node: {expandParams.NodePath} in session {session.Uri}"; - await serviceHost.SendEvent(ExpandCompleteNotification.Type, response); + await _serviceHost.SendEvent(ExpandCompleteNotification.Type, response); } return result; }).ContinueWithOnFaulted(null); @@ -636,7 +617,7 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer } else { - await serviceHost.SendEvent(ExpandCompleteNotification.Type, response); + await _serviceHost.SendEvent(ExpandCompleteNotification.Type, response); } } @@ -752,10 +733,10 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer public void Dispose() { - if (bindingQueue != null) + if (_connectedBindingQueue != null) { - bindingQueue.OnUnhandledException -= OnUnhandledException; - bindingQueue.Dispose(); + _connectedBindingQueue.OnUnhandledException -= OnUnhandledException; + _connectedBindingQueue.Dispose(); } } @@ -787,21 +768,12 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer internal class ObjectExplorerSession { - private ConnectionService connectionService; - private IMultiServiceProvider serviceProvider; - - // TODO decide whether a cache is needed to handle lookups in elements with a large # children - //private const int Cachesize = 10000; - //private Cache cache; - - public ObjectExplorerSession(string uri, TreeNode root, IMultiServiceProvider serviceProvider, ConnectionService connectionService) + public ObjectExplorerSession(string uri, TreeNode root) { Validate.IsNotNullOrEmptyString("uri", uri); Validate.IsNotNull("root", root); Uri = uri; Root = root; - this.serviceProvider = serviceProvider; - this.connectionService = connectionService; } public string Uri { get; private set; } @@ -813,13 +785,13 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer public static ObjectExplorerSession CreateSession(ConnectionCompleteParams response, IMultiServiceProvider serviceProvider, ServerConnection serverConnection, IDataSource dataSource, bool isDefaultOrSystemDatabase) { - DataSourceObjectMetadata objectMetadata = DataSourceFactory.CreateClusterMetadata(dataSource.ClusterName); + DataSourceObjectMetadata objectMetadata = MetadataFactory.CreateClusterMetadata(dataSource.ClusterName); ServerNode rootNode = new ServerNode(response, serviceProvider, serverConnection, dataSource, objectMetadata); - var session = new ObjectExplorerSession(response.OwnerUri, rootNode, serviceProvider, serviceProvider.GetService()); + var session = new ObjectExplorerSession(response.OwnerUri, rootNode); if (!isDefaultOrSystemDatabase) { - DataSourceObjectMetadata databaseMetadata = DataSourceFactory.CreateDatabaseMetadata(objectMetadata, response.ConnectionSummary.DatabaseName); + DataSourceObjectMetadata databaseMetadata = MetadataFactory.CreateDatabaseMetadata(objectMetadata, response.ConnectionSummary.DatabaseName); // Assuming the databases are in a folder under server node DataSourceTreeNode databaseNode = new DataSourceTreeNode(dataSource, databaseMetadata) { diff --git a/src/Microsoft.Kusto.ServiceLayer/Program.cs b/src/Microsoft.Kusto.ServiceLayer/Program.cs index 409a56cd..34abf395 100644 --- a/src/Microsoft.Kusto.ServiceLayer/Program.cs +++ b/src/Microsoft.Kusto.ServiceLayer/Program.cs @@ -2,13 +2,10 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; -using Microsoft.Kusto.ServiceLayer.Hosting; using Microsoft.SqlTools.ServiceLayer.SqlContext; using Microsoft.Kusto.ServiceLayer.Utility; using Microsoft.SqlTools.Utility; -using System.IO; using System.Diagnostics; -//using SqlToolsContext = Microsoft.SqlTools.ServiceLayer.SqlContext.SqlToolsContext; namespace Microsoft.Kusto.ServiceLayer { diff --git a/src/Microsoft.Kusto.ServiceLayer/QueryExecution/QueryExecutionService.cs b/src/Microsoft.Kusto.ServiceLayer/QueryExecution/QueryExecutionService.cs index ec0358cb..82eafe0a 100644 --- a/src/Microsoft.Kusto.ServiceLayer/QueryExecution/QueryExecutionService.cs +++ b/src/Microsoft.Kusto.ServiceLayer/QueryExecution/QueryExecutionService.cs @@ -16,7 +16,6 @@ using Microsoft.Kusto.ServiceLayer.QueryExecution.DataStorage; using Microsoft.Kusto.ServiceLayer.SqlContext; using Microsoft.Kusto.ServiceLayer.Workspace; using Microsoft.Kusto.ServiceLayer.Workspace.Contracts; -using Microsoft.Kusto.ServiceLayer.Hosting; using Microsoft.SqlTools.Utility; using System.Diagnostics; diff --git a/src/Microsoft.Kusto.ServiceLayer/Scripting/IScripter.cs b/src/Microsoft.Kusto.ServiceLayer/Scripting/IScripter.cs new file mode 100644 index 00000000..eec9584e --- /dev/null +++ b/src/Microsoft.Kusto.ServiceLayer/Scripting/IScripter.cs @@ -0,0 +1,12 @@ +using Microsoft.Kusto.ServiceLayer.DataSource; +using Microsoft.Kusto.ServiceLayer.Scripting.Contracts; +using Microsoft.SqlServer.Management.Sdk.Sfc; + +namespace Microsoft.Kusto.ServiceLayer.Scripting +{ + public interface IScripter + { + string SelectFromTableOrView(IDataSource dataSource, Urn urn); + string AlterFunction(IDataSource dataSource, ScriptingObject scriptingObject); + } +} \ No newline at end of file diff --git a/src/Microsoft.Kusto.ServiceLayer/Scripting/ScriptAsScriptingOperation.cs b/src/Microsoft.Kusto.ServiceLayer/Scripting/ScriptAsScriptingOperation.cs index 4f22ac49..ea29f267 100644 --- a/src/Microsoft.Kusto.ServiceLayer/Scripting/ScriptAsScriptingOperation.cs +++ b/src/Microsoft.Kusto.ServiceLayer/Scripting/ScriptAsScriptingOperation.cs @@ -26,6 +26,7 @@ namespace Microsoft.Kusto.ServiceLayer.Scripting /// public class ScriptAsScriptingOperation : SmoScriptingOperation { + private readonly IScripter _scripter; private static readonly Dictionary scriptCompatibilityMap = LoadScriptCompatibilityMap(); /// /// Left delimiter for an named object @@ -37,15 +38,11 @@ namespace Microsoft.Kusto.ServiceLayer.Scripting /// public const char RightDelimiter = ']'; - public ScriptAsScriptingOperation(ScriptingParams parameters, IDataSource dataSource): base(parameters) + public ScriptAsScriptingOperation(ScriptingParams parameters, string azureAccountToken, IScripter scripter) : base(parameters) { - Validate.IsNotNull("dataSource", dataSource); - DataSource = dataSource; - } - - public ScriptAsScriptingOperation(ScriptingParams parameters, string azureAccountToken) : base(parameters) - { - DataSource = DataSourceFactory.Create(DataSourceType.Kusto, this.Parameters.ConnectionString, azureAccountToken); + DataSource = DataSourceFactory.Create(DataSourceType.Kusto, this.Parameters.ConnectionString, + azureAccountToken); + _scripter = scripter; } internal IDataSource DataSource { get; set; } @@ -150,7 +147,7 @@ namespace Microsoft.Kusto.ServiceLayer.Scripting // select from table if (string.Equals(scriptingObject.Type, "Table", StringComparison.CurrentCultureIgnoreCase)) { - return new Scripter().SelectFromTableOrView(dataSource, objectUrn); + return _scripter.SelectFromTableOrView(dataSource, objectUrn); } return string.Empty; @@ -163,7 +160,7 @@ namespace Microsoft.Kusto.ServiceLayer.Scripting if (string.Equals(scriptingObject.Type, "Function", StringComparison.CurrentCultureIgnoreCase)) { - return new Scripter().AlterFunction(dataSource, scriptingObject); + return _scripter.AlterFunction(dataSource, scriptingObject); } return string.Empty; @@ -177,7 +174,7 @@ namespace Microsoft.Kusto.ServiceLayer.Scripting /// The object name. /// Whether to schema qualify the object or not /// The object name, quoted as appropriate and schema-qualified if the option is set - static private string GenerateSchemaQualifiedName(string schema, string objectName, bool schemaQualify) + private static string GenerateSchemaQualifiedName(string schema, string objectName, bool schemaQualify) { var qualifiedName = new StringBuilder(); diff --git a/src/Microsoft.Kusto.ServiceLayer/Scripting/Scripter.cs b/src/Microsoft.Kusto.ServiceLayer/Scripting/Scripter.cs index 3fffc444..827e78fa 100644 --- a/src/Microsoft.Kusto.ServiceLayer/Scripting/Scripter.cs +++ b/src/Microsoft.Kusto.ServiceLayer/Scripting/Scripter.cs @@ -3,73 +3,34 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -using Microsoft.SqlServer.Management.Smo; -using Microsoft.SqlServer.Management.SqlParser.Intellisense; -using Microsoft.SqlServer.Management.Common; +using System.Composition; +using Microsoft.Kusto.ServiceLayer.Scripting.Contracts; +using Microsoft.Kusto.ServiceLayer.DataSource; +using Microsoft.SqlServer.Management.Sdk.Sfc; +using System.Text; namespace Microsoft.Kusto.ServiceLayer.Scripting { - internal partial class Scripter + [Export(typeof(IScripter))] + public class Scripter : IScripter { - private void Initialize() + public string SelectFromTableOrView(IDataSource dataSource, Urn urn) { - // Instantiate the mapping dictionaries + StringBuilder selectQuery = new StringBuilder(); - // Mapping for supported type - AddSupportedType(DeclarationType.Table, "Table", "table", typeof(Table)); - AddSupportedType(DeclarationType.View, "View", "view", typeof(View)); - AddSupportedType(DeclarationType.StoredProcedure, "StoredProcedure", "stored procedure", typeof(StoredProcedure)); - AddSupportedType(DeclarationType.Schema, "Schema", "schema", typeof(Schema)); - AddSupportedType(DeclarationType.UserDefinedDataType, "UserDefinedDataType", "user-defined data type", typeof(UserDefinedDataType)); - AddSupportedType(DeclarationType.UserDefinedTableType, "UserDefinedTableType", "user-defined table type", typeof(UserDefinedTableType)); - AddSupportedType(DeclarationType.Synonym, "Synonym", "", typeof(Synonym)); - AddSupportedType(DeclarationType.ScalarValuedFunction, "Function", "scalar-valued function", typeof(UserDefinedFunction)); - AddSupportedType(DeclarationType.TableValuedFunction, "Function", "table-valued function", typeof(UserDefinedFunction)); + // TODOKusto: Can we combine this with snippets. All queries generated here could also be snippets. + // TODOKusto: Extract into the Kusto folder. + selectQuery.Append($"{KustoQueryUtils.EscapeName(urn.GetAttribute("Name"))}"); + selectQuery.Append($"{KustoQueryUtils.StatementSeparator}"); + selectQuery.Append("limit 1000"); - // Mapping for database engine edition - targetDatabaseEngineEditionMap.Add(DatabaseEngineEdition.Unknown, "SqlServerEnterpriseEdition"); //default case - targetDatabaseEngineEditionMap.Add(DatabaseEngineEdition.Personal, "SqlServerPersonalEdition"); - targetDatabaseEngineEditionMap.Add(DatabaseEngineEdition.Standard, "SqlServerStandardEdition"); - targetDatabaseEngineEditionMap.Add(DatabaseEngineEdition.Enterprise, "SqlServerEnterpriseEdition"); - targetDatabaseEngineEditionMap.Add(DatabaseEngineEdition.Express, "SqlServerExpressEdition"); - targetDatabaseEngineEditionMap.Add(DatabaseEngineEdition.SqlDatabase, "SqlAzureDatabaseEdition"); - targetDatabaseEngineEditionMap.Add(DatabaseEngineEdition.SqlDataWarehouse, "SqlDatawarehouseEdition"); - targetDatabaseEngineEditionMap.Add(DatabaseEngineEdition.SqlStretchDatabase, "SqlServerStretchEdition"); - targetDatabaseEngineEditionMap.Add(DatabaseEngineEdition.SqlManagedInstance, "SqlServerManagedInstance"); + return selectQuery.ToString(); + } - // Mapping for database engine type - serverVersionMap.Add(9, "Script90Compat"); - serverVersionMap.Add(10, "Script100Compat"); - serverVersionMap.Add(11, "Script110Compat"); - serverVersionMap.Add(12, "Script120Compat"); - serverVersionMap.Add(13, "Script140Compat"); - serverVersionMap.Add(14, "Script140Compat"); - serverVersionMap.Add(15, "Script140Compat"); - - // Mapping the object types for scripting - objectScriptMap.Add("table", "Table"); - objectScriptMap.Add("view", "View"); - objectScriptMap.Add("function", "Function"); - objectScriptMap.Add("storedprocedure", "Procedure"); - objectScriptMap.Add("userdefinedfunction", "Function"); - objectScriptMap.Add("tablevaluedfunction", "Function"); - objectScriptMap.Add("userdefineddatatype", "Type"); - objectScriptMap.Add("user", "User"); - objectScriptMap.Add("default", "Default"); - objectScriptMap.Add("rule", "Rule"); - objectScriptMap.Add("databaserole", "Role"); - objectScriptMap.Add("applicationrole", "Application Role"); - objectScriptMap.Add("sqlassembly", "Assembly"); - objectScriptMap.Add("ddltrigger", "Trigger"); - objectScriptMap.Add("synonym", "Synonym"); - objectScriptMap.Add("xmlschemacollection", "Xml Schema Collection"); - objectScriptMap.Add("schema", "Schema"); - objectScriptMap.Add("planguide", "sp_create_plan_guide"); - objectScriptMap.Add("userdefinedtype", "Type"); - objectScriptMap.Add("userdefinedaggregate", "Aggregate"); - objectScriptMap.Add("fulltextcatalog", "Fulltext Catalog"); - objectScriptMap.Add("userdefinedtabletype", "Type"); + public string AlterFunction(IDataSource dataSource, ScriptingObject scriptingObject) + { + var functionName = scriptingObject.Name.Substring(0, scriptingObject.Name.IndexOf('(')); + return dataSource.GenerateAlterFunctionScript(functionName); } } -} - \ No newline at end of file +} \ No newline at end of file diff --git a/src/Microsoft.Kusto.ServiceLayer/Scripting/ScripterCore.cs b/src/Microsoft.Kusto.ServiceLayer/Scripting/ScripterCore.cs deleted file mode 100644 index ab1207fe..00000000 --- a/src/Microsoft.Kusto.ServiceLayer/Scripting/ScripterCore.cs +++ /dev/null @@ -1,683 +0,0 @@ -// -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Microsoft.SqlServer.Management.Common; -using Microsoft.SqlServer.Management.Smo; -using Microsoft.SqlServer.Management.SqlParser.Intellisense; -using Microsoft.SqlServer.Management.SqlParser.MetadataProvider; -using Microsoft.SqlServer.Management.SqlParser.Parser; -using Microsoft.SqlTools.Hosting.Protocol; -using Microsoft.Kusto.ServiceLayer.Connection; -using Microsoft.Kusto.ServiceLayer.LanguageServices; -using Microsoft.Kusto.ServiceLayer.Utility; -using Microsoft.Kusto.ServiceLayer.Workspace.Contracts; -using Microsoft.Kusto.ServiceLayer.Scripting.Contracts; -using Microsoft.Kusto.ServiceLayer.DataSource; -using Location = Microsoft.Kusto.ServiceLayer.Workspace.Contracts.Location; -using Microsoft.SqlServer.Management.Sdk.Sfc; -using System.Text; -using System.Data; -using Range = Microsoft.Kusto.ServiceLayer.Workspace.Contracts.Range; - -namespace Microsoft.Kusto.ServiceLayer.Scripting -{ - internal partial class Scripter - { - private bool error; - private string errorMessage; - private IDataSource DataSource { get; set; } - private ConnectionInfo connectionInfo; - private string tempPath; - - // Dictionary that holds the object name (as appears on the TSQL create statement) - private Dictionary sqlObjectTypes = new Dictionary(); - - private Dictionary sqlObjectTypesFromQuickInfo = new Dictionary(); - - private Dictionary targetDatabaseEngineEditionMap = new Dictionary(); - - private Dictionary serverVersionMap = new Dictionary(); - - private Dictionary objectScriptMap = new Dictionary(); - - internal Scripter() {} - - /// - /// Initialize a Peek Definition helper object - /// - /// Data Source - internal Scripter(IDataSource dataSource, ConnectionInfo connInfo) - { - this.DataSource = dataSource; - this.connectionInfo = connInfo; - this.tempPath = FileUtilities.GetPeekDefinitionTempFolder(); - Initialize(); - } - - /// - /// Add the given type, scriptgetter and the typeName string to the respective dictionaries - /// - private void AddSupportedType(DeclarationType type, string typeName, string quickInfoType, Type smoObjectType) - { - sqlObjectTypes.Add(type, typeName); - if (!string.IsNullOrEmpty(quickInfoType)) - { - sqlObjectTypesFromQuickInfo.Add(quickInfoType.ToLowerInvariant(), typeName); - } - } - - /// - /// Get the script of the selected token based on the type of the token - /// - /// - /// - /// - /// Location object of the script file - internal DefinitionResult GetScript(ParseResult parseResult, Position position, IMetadataDisplayInfoProvider metadataDisplayInfoProvider, string tokenText, string schemaName) - { - int parserLine = position.Line; - int parserColumn = position.Character; - // Get DeclarationItems from The Intellisense Resolver for the selected token. The type of the selected token is extracted from the declarationItem. - IEnumerable declarationItems = GetCompletionsForToken(parseResult, parserLine, parserColumn, metadataDisplayInfoProvider); - if (declarationItems != null && declarationItems.Count() > 0) - { - foreach (Declaration declarationItem in declarationItems) - { - if (declarationItem.Title == null) - { - continue; - } - - StringComparison caseSensitivity = 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); - } - } - } - else - { - // if no declarationItem matched the selected token, we try to find the type of the token using QuickInfo.Text - string quickInfoText = GetQuickInfoForToken(parseResult, parserLine, parserColumn, metadataDisplayInfoProvider); - return GetDefinitionUsingQuickInfoText(quickInfoText, tokenText, schemaName); - } - // no definition found - return GetDefinitionErrorResult(SR.PeekDefinitionNoResultsError); - } - - /// - /// Script an object using the type extracted from quickInfo Text - /// - /// the text from the quickInfo for the selected token - /// The text of the selected token - /// Schema name - /// - internal DefinitionResult GetDefinitionUsingQuickInfoText(string quickInfoText, string tokenText, string schemaName) - { - StringComparison caseSensitivity = StringComparison.OrdinalIgnoreCase; - string tokenType = GetTokenTypeFromQuickInfo(quickInfoText, tokenText, caseSensitivity); - if (tokenType != null) - { - if (sqlObjectTypesFromQuickInfo.ContainsKey(tokenType.ToLowerInvariant())) - { - // With SqlLogin authentication, the defaultSchema property throws an Exception when accessed. - // This workaround ensures that a schema name is present by attempting - // to get the schema name from the declaration item. - // 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, caseSensitivity); - schemaName = this.GetSchemaFromDatabaseQualifiedName(fullObjectName, tokenText); - } - Location[] locations = GetSqlObjectDefinition( - tokenText, - schemaName, - sqlObjectTypesFromQuickInfo[tokenType.ToLowerInvariant()] - ); - DefinitionResult result = new DefinitionResult - { - IsErrorResult = this.error, - Message = this.errorMessage, - Locations = locations - }; - return result; - } - else - { - // If a type was found but is not in sqlScriptGettersFromQuickInfo, then the type is not supported - return GetDefinitionErrorResult(SR.PeekDefinitionTypeNotSupportedError); - } - } - // no definition found - return GetDefinitionErrorResult(SR.PeekDefinitionNoResultsError); - } - - /// - /// Script a object using the type extracted from declarationItem - /// - /// The Declaration object that matched with the selected token - /// The text of the selected token - /// Schema name - /// - internal DefinitionResult GetDefinitionUsingDeclarationType(DeclarationType type, string databaseQualifiedName, string tokenText, string schemaName) - { - if (sqlObjectTypes.ContainsKey(type)) - { - // With SqlLogin authentication, the defaultSchema property throws an Exception when accessed. - // This workaround ensures that a schema name is present by attempting - // to get the schema name from the declaration item. - // 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 = databaseQualifiedName; - schemaName = this.GetSchemaFromDatabaseQualifiedName(fullObjectName, tokenText); - } - Location[] locations = GetSqlObjectDefinition( - tokenText, - schemaName, - sqlObjectTypes[type] - ); - DefinitionResult result = new DefinitionResult - { - IsErrorResult = this.error, - Message = this.errorMessage, - Locations = locations - }; - return result; - } - // If a type was found but is not in sqlScriptGetters, then the type is not supported - return GetDefinitionErrorResult(SR.PeekDefinitionTypeNotSupportedError); - } - - /// - /// Script a object using SMO and write to a file. - /// - /// Function that returns the SMO scripts for an object - /// SQL object name - /// Schema name or null - /// Type of SQL object - /// Location object representing URI and range of the script file - internal Location[] GetSqlObjectDefinition( - string objectName, - string schemaName, - string objectType) - { - // script file destination - string tempFileName = (schemaName != null) ? Path.Combine(this.tempPath, string.Format("{0}.{1}.sql", schemaName, objectName)) - : Path.Combine(this.tempPath, string.Format("{0}.sql", objectName)); - - SmoScriptingOperation operation = InitScriptOperation(objectName, schemaName, objectType); - operation.Execute(); - string script = operation.ScriptText; - - bool objectFound = false; - int createStatementLineNumber = 0; - - File.WriteAllText(tempFileName, script); - string[] lines = File.ReadAllLines(tempFileName); - int lineCount = 0; - string createSyntax = null; - if (objectScriptMap.ContainsKey(objectType.ToLower())) - { - createSyntax = string.Format("CREATE"); - foreach (string line in lines) - { - if (LineContainsObject(line, objectName, createSyntax)) - { - createStatementLineNumber = lineCount; - objectFound = true; - break; - } - lineCount++; - } - } - if (objectFound) - { - Location[] locations = GetLocationFromFile(tempFileName, createStatementLineNumber); - return locations; - } - else - { - this.error = true; - this.errorMessage = SR.PeekDefinitionNoResultsError; - return null; - } - } - - #region Helper Methods - /// - /// Return schema name from the full name of the database. If schema is missing return dbo as schema name. - /// - /// The full database qualified name(database.schema.object) - /// Object name - /// Schema name - internal string GetSchemaFromDatabaseQualifiedName(string fullObjectName, string objectName) - { - if (!string.IsNullOrEmpty(fullObjectName)) - { - string[] tokens = fullObjectName.Split('.'); - for (int i = tokens.Length - 1; i > 0; i--) - { - if (tokens[i].Equals(objectName)) - { - return tokens[i - 1]; - } - } - } - return "dbo"; - } - - /// - /// Convert a file to a location array containing a location object as expected by the extension - /// - internal Location[] GetLocationFromFile(string tempFileName, int lineNumber) - { - // Get absolute Uri based on uri format. This works around a dotnetcore URI bug for linux paths. - if (Path.DirectorySeparatorChar.Equals('/')) - { - tempFileName = "file:" + tempFileName; - } - else - { - tempFileName = new Uri(tempFileName).AbsoluteUri; - } - // Create a location array containing the tempFile Uri, as expected by VSCode. - Location[] locations = new[] - { - new Location - { - Uri = tempFileName, - Range = new Range - { - Start = new Position { Line = lineNumber, Character = 0}, - End = new Position { Line = lineNumber + 1, Character = 0} - } - } - }; - return locations; - } - - /// - /// Helper method to create definition error result object - /// - /// Error message - /// DefinitionResult - internal DefinitionResult GetDefinitionErrorResult(string errorMessage) - { - return new DefinitionResult - { - IsErrorResult = true, - Message = errorMessage, - Locations = null - }; - } - - /// - /// Return full object name(database.schema.objectName) from the quickInfo text("type database.schema.objectName") - /// - /// QuickInfo Text for this token - /// Token Text - /// StringComparison enum - /// - internal string GetFullObjectNameFromQuickInfo(string quickInfoText, string tokenText, StringComparison caseSensitivity) - { - if (string.IsNullOrEmpty(quickInfoText) || string.IsNullOrEmpty(tokenText)) - { - return null; - } - // extract full object name from quickInfo text - string[] tokens = quickInfoText.Split(' '); - List tokenList = tokens.Where(el => el.IndexOf(tokenText, caseSensitivity) >= 0).ToList(); - return (tokenList?.Count() > 0) ? tokenList[0] : null; - } - - /// - /// Return token type from the quickInfo text("type database.schema.objectName") - /// - /// QuickInfo Text for this token - /// - /// StringComparison enum - /// - internal string GetTokenTypeFromQuickInfo(string quickInfoText, string tokenText, StringComparison caseSensitivity) - { - if (string.IsNullOrEmpty(quickInfoText) || string.IsNullOrEmpty(tokenText)) - { - return null; - } - // 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).IndexOf(tokenText, caseSensitivity) >= 0).Select(el => el.i).ToList(); - return (indexList?.Count() > 0) ? String.Join(" ", tokens.Take(indexList[0])) : null; - } - - - /// - /// Wrapper method that calls Resolver.GetQuickInfo - /// - internal string GetQuickInfoForToken(ParseResult parseResult, int parserLine, int parserColumn, IMetadataDisplayInfoProvider metadataDisplayInfoProvider) - { - if (parseResult == null || metadataDisplayInfoProvider == null) - { - return null; - } - Babel.CodeObjectQuickInfo quickInfo = Resolver.GetQuickInfo( - parseResult, parserLine, parserColumn, metadataDisplayInfoProvider); - return quickInfo?.Text; - } - - /// - /// Wrapper method that calls Resolver.FindCompletions - /// - /// - /// - /// - /// - /// - internal IEnumerable GetCompletionsForToken(ParseResult parseResult, int parserLine, int parserColumn, IMetadataDisplayInfoProvider metadataDisplayInfoProvider) - { - if (parseResult == null || metadataDisplayInfoProvider == null) - { - return null; - } - return Resolver.FindCompletions( - parseResult, parserLine, parserColumn, metadataDisplayInfoProvider); - } - - /// - /// Wrapper method that calls Resolver.FindCompletions - /// - /// - /// - /// - /// - /// - internal SmoScriptingOperation InitScriptOperation(string objectName, string schemaName, string objectType) - { - // object that has to be scripted - ScriptingObject scriptingObject = new ScriptingObject - { - Name = objectName, - Schema = schemaName, - Type = objectType - }; - - // scripting options - ScriptOptions options = new ScriptOptions - { - ScriptCreateDrop = "ScriptCreate", - TypeOfDataToScript = "SchemaOnly", - ScriptStatistics = "ScriptStatsNone", - ScriptExtendedProperties = false, - ScriptUseDatabase = false, - IncludeIfNotExists = false, - GenerateScriptForDependentObjects = false, - IncludeDescriptiveHeaders = false, - ScriptCheckConstraints = false, - ScriptChangeTracking = false, - ScriptDataCompressionOptions = false, - ScriptForeignKeys = false, - ScriptFullTextIndexes = false, - ScriptIndexes = false, - ScriptPrimaryKeys = false, - ScriptTriggers = false, - UniqueKeys = false - - }; - - List objectList = new List(); - objectList.Add(scriptingObject); - - // create parameters for the scripting operation - - ScriptingParams parameters = new ScriptingParams - { - ConnectionString = ConnectionService.BuildConnectionString(this.connectionInfo.ConnectionDetails), - ScriptingObjects = objectList, - ScriptOptions = options, - ScriptDestination = "ToEditor" - }; - - return new ScriptAsScriptingOperation(parameters, DataSource); - } - - internal bool LineContainsObject(string line, string objectName, string createSyntax) - { - if (line.IndexOf(createSyntax, StringComparison.OrdinalIgnoreCase) >= 0 && - line.IndexOf(objectName, StringComparison.OrdinalIgnoreCase) >=0) - { - return true; - } - return false; - } - - internal static class ScriptingGlobals - { - /// - /// Left delimiter for an named object - /// - public const char LeftDelimiter = '['; - - /// - /// right delimiter for a named object - /// - public const char RightDelimiter = ']'; - } - - internal static class ScriptingUtils - { - /// - /// Quote the name of a given sql object. - /// - /// object - /// quoted object name - internal static string QuoteObjectName(string sqlObject) - { - return QuoteObjectName(sqlObject, ']'); - } - - /// - /// Quotes the name of a given sql object - /// - /// object - /// quote to use - /// - internal static string QuoteObjectName(string sqlObject, char quote) - { - int len = sqlObject.Length; - StringBuilder result = new StringBuilder(sqlObject.Length); - for (int i = 0; i < len; i++) - { - if (sqlObject[i] == quote) - { - result.Append(quote); - } - result.Append(sqlObject[i]); - } - return result.ToString(); - } - } - - internal static string SelectAllValuesFromTransmissionQueue(Urn urn) - { - string script = string.Empty; - StringBuilder selectQuery = new StringBuilder(); - - /* - SELECT TOP *, casted_message_body = - CASE MESSAGE_TYPE_NAME WHEN 'X' - THEN CAST(MESSAGE_BODY AS NVARCHAR(MAX)) - ELSE MESSAGE_BODY - END - FROM [new].[sys].[transmission_queue] - */ - selectQuery.Append("SELECT TOP (1000) "); - selectQuery.Append("*, casted_message_body = \r\nCASE message_type_name WHEN 'X' \r\n THEN CAST(message_body AS NVARCHAR(MAX)) \r\n ELSE message_body \r\nEND \r\n"); - - // from clause - selectQuery.Append("FROM "); - Urn dbUrn = urn; - - // database - while (dbUrn.Parent != null && dbUrn.Type != "Database") - { - dbUrn = dbUrn.Parent; - } - selectQuery.AppendFormat("{0}{1}{2}", - ScriptingGlobals.LeftDelimiter, - ScriptingUtils.QuoteObjectName(dbUrn.GetAttribute("Name"), ScriptingGlobals.RightDelimiter), - ScriptingGlobals.RightDelimiter); - //SYS - selectQuery.AppendFormat(".{0}sys{1}", - ScriptingGlobals.LeftDelimiter, - ScriptingGlobals.RightDelimiter); - //TRANSMISSION QUEUE - selectQuery.AppendFormat(".{0}transmission_queue{1}", - ScriptingGlobals.LeftDelimiter, - ScriptingGlobals.RightDelimiter); - - script = selectQuery.ToString(); - return script; - } - - internal static string SelectAllValues(Urn urn) - { - string script = string.Empty; - StringBuilder selectQuery = new StringBuilder(); - selectQuery.Append("SELECT TOP (1000) "); - selectQuery.Append("*, casted_message_body = \r\nCASE message_type_name WHEN 'X' \r\n THEN CAST(message_body AS NVARCHAR(MAX)) \r\n ELSE message_body \r\nEND \r\n"); - - // from clause - selectQuery.Append("FROM "); - Urn dbUrn = urn; - - // database - while (dbUrn.Parent != null && dbUrn.Type != "Database") - { - dbUrn = dbUrn.Parent; - } - selectQuery.AppendFormat("{0}{1}{2}", - ScriptingGlobals.LeftDelimiter, - ScriptingUtils.QuoteObjectName(dbUrn.GetAttribute("Name"), ScriptingGlobals.RightDelimiter), - ScriptingGlobals.RightDelimiter); - // schema - selectQuery.AppendFormat(".{0}{1}{2}", - ScriptingGlobals.LeftDelimiter, - ScriptingUtils.QuoteObjectName(urn.GetAttribute("Schema"), ScriptingGlobals.RightDelimiter), - ScriptingGlobals.RightDelimiter); - // object - selectQuery.AppendFormat(".{0}{1}{2}", - ScriptingGlobals.LeftDelimiter, - ScriptingUtils.QuoteObjectName(urn.GetAttribute("Name"), ScriptingGlobals.RightDelimiter), - ScriptingGlobals.RightDelimiter); - - //Adding no lock in the end. - selectQuery.AppendFormat(" WITH(NOLOCK)"); - - script = selectQuery.ToString(); - return script; - } - - internal DataTable GetColumnNames(Server server, Urn urn, bool isDw) - { - List filterExpressions = new List(); - if (server.Version.Major >= 10) - { - // We don't have to include sparce columns as all the sparce columns data. - // Can be obtain from column set columns. - filterExpressions.Add("@IsSparse=0"); - } - - // Check if we're called for EDIT for SQL2016+/Sterling+. - // We need to omit temporal columns if such are present on this table. - if (server.Version.Major >= 13 || (DatabaseEngineType.SqlAzureDatabase == server.DatabaseEngineType && server.Version.Major >= 12)) - { - // We're called in order to generate a list of columns for EDIT TOP N rows. - // Don't return auto-generated, auto-populated, read-only temporal columns. - filterExpressions.Add("@GeneratedAlwaysType=0"); - } - - // Check if we're called for SQL2017/Sterling+. - // We need to omit graph internal columns if such are present on this table. - if (server.Version.Major >= 14 || (DatabaseEngineType.SqlAzureDatabase == server.DatabaseEngineType && !isDw)) - { - // from Smo.GraphType: - // 0 = None - // 1 = GraphId - // 2 = GraphIdComputed - // 3 = GraphFromId - // 4 = GraphFromObjId - // 5 = GraphFromIdComputed - // 6 = GraphToId - // 7 = GraphToObjId - // 8 = GraphToIdComputed - // - // We only want to show types 0, 2, 5, and 8: - filterExpressions.Add("(@GraphType=0 or @GraphType=2 or @GraphType=5 or @GraphType=8)"); - } - - Request request = new Request(); - // If we have any filters on the columns, add them. - if (filterExpressions.Count > 0) - { - request.Urn = String.Format("{0}/Column[{1}]", urn.ToString(), string.Join(" and ", filterExpressions.ToArray())); - } - else - { - request.Urn = String.Format("{0}/Column", urn.ToString()); - } - - request.Fields = new String[] { "Name" }; - - // get the columns in the order they were created - OrderBy order = new OrderBy(); - order.Dir = OrderBy.Direction.Asc; - order.Field = "ID"; - request.OrderByList = new OrderBy[] { order }; - - Enumerator en = new Enumerator(); - - // perform the query. - DataTable dt = null; - EnumResult result = en.Process(server.ConnectionContext, request); - - if (result.Type == ResultType.DataTable) - { - dt = result; - } - else - { - dt = ((DataSet)result).Tables[0]; - } - return dt; - } - - internal string SelectFromTableOrView(IDataSource dataSource, Urn urn) - { - StringBuilder selectQuery = new StringBuilder(); - - // TODOKusto: Can we combine this with snippets. All queries generated here could also be snippets. - // TODOKusto: Extract into the Kusto folder. - selectQuery.Append($"{KustoQueryUtils.EscapeName(urn.GetAttribute("Name"))}"); - selectQuery.Append($"{KustoQueryUtils.StatementSeparator}"); - selectQuery.Append("limit 1000"); - - return selectQuery.ToString(); - } - - internal string AlterFunction(IDataSource dataSource, ScriptingObject scriptingObject) - { - var functionName = scriptingObject.Name.Substring(0, scriptingObject.Name.IndexOf('(')); - return dataSource.GenerateAlterFunctionScript(functionName); - } - - #endregion - } -} \ No newline at end of file diff --git a/src/Microsoft.Kusto.ServiceLayer/Scripting/ScriptingScriptOperation.cs b/src/Microsoft.Kusto.ServiceLayer/Scripting/ScriptingScriptOperation.cs index de0fedc1..119838a4 100644 --- a/src/Microsoft.Kusto.ServiceLayer/Scripting/ScriptingScriptOperation.cs +++ b/src/Microsoft.Kusto.ServiceLayer/Scripting/ScriptingScriptOperation.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.Data.SqlClient; using System.Diagnostics; using System.Linq; +using Microsoft.Kusto.ServiceLayer.DataSource; using Microsoft.SqlServer.Management.SqlScriptPublish; using Microsoft.Kusto.ServiceLayer.Scripting.Contracts; using Microsoft.SqlTools.Utility; @@ -28,7 +29,7 @@ namespace Microsoft.Kusto.ServiceLayer.Scripting private string azureAccessToken; - public ScriptingScriptOperation(ScriptingParams parameters, string azureAccessToken): base(parameters) + public ScriptingScriptOperation(ScriptingParams parameters, string azureAccessToken) : base(parameters) { this.azureAccessToken = azureAccessToken; } diff --git a/src/Microsoft.Kusto.ServiceLayer/Scripting/ScriptingService.cs b/src/Microsoft.Kusto.ServiceLayer/Scripting/ScriptingService.cs index 97b391b9..76cf725e 100644 --- a/src/Microsoft.Kusto.ServiceLayer/Scripting/ScriptingService.cs +++ b/src/Microsoft.Kusto.ServiceLayer/Scripting/ScriptingService.cs @@ -10,7 +10,7 @@ using System.Threading.Tasks; using Microsoft.SqlTools.Hosting.Protocol; using Microsoft.SqlTools.Hosting.Protocol.Contracts; using Microsoft.Kusto.ServiceLayer.Connection; -using Microsoft.Kusto.ServiceLayer.Hosting; +using Microsoft.Kusto.ServiceLayer.DataSource; using Microsoft.Kusto.ServiceLayer.Scripting.Contracts; using Microsoft.SqlTools.Utility; using Microsoft.Kusto.ServiceLayer.Utility; @@ -21,19 +21,21 @@ namespace Microsoft.Kusto.ServiceLayer.Scripting /// Main class for Scripting Service functionality /// public sealed class ScriptingService : IDisposable - { + { private const int ScriptingOperationTimeout = 60000; private static readonly Lazy LazyInstance = new Lazy(() => new ScriptingService()); public static ScriptingService Instance => LazyInstance.Value; - private static ConnectionService connectionService = null; + private static ConnectionService connectionService; private readonly Lazy> operations = new Lazy>(() => new ConcurrentDictionary()); private bool disposed; + + private IScripter _scripter; /// /// Internal for testing purposes only @@ -64,8 +66,9 @@ namespace Microsoft.Kusto.ServiceLayer.Scripting /// /// /// - public void InitializeService(ServiceHost serviceHost) + public void InitializeService(ServiceHost serviceHost, IScripter scripter) { + _scripter = scripter; serviceHost.SetRequestHandler(ScriptingRequest.Type, this.HandleScriptExecuteRequest); serviceHost.SetRequestHandler(ScriptingCancelRequest.Type, this.HandleScriptCancelRequest); serviceHost.SetRequestHandler(ScriptingListObjectsRequest.Type, this.HandleListObjectsRequest); @@ -132,7 +135,7 @@ namespace Microsoft.Kusto.ServiceLayer.Scripting } else { - operation = new ScriptAsScriptingOperation(parameters, accessToken); + operation = new ScriptAsScriptingOperation(parameters, accessToken, _scripter); } operation.PlanNotification += (sender, e) => requestContext.SendEvent(ScriptingPlanNotificationEvent.Type, e).Wait(); diff --git a/src/Microsoft.Kusto.ServiceLayer/Scripting/SmoScriptingOperation.cs b/src/Microsoft.Kusto.ServiceLayer/Scripting/SmoScriptingOperation.cs index ede7f1db..de880dcb 100644 --- a/src/Microsoft.Kusto.ServiceLayer/Scripting/SmoScriptingOperation.cs +++ b/src/Microsoft.Kusto.ServiceLayer/Scripting/SmoScriptingOperation.cs @@ -22,7 +22,7 @@ namespace Microsoft.Kusto.ServiceLayer.Scripting /// public abstract class SmoScriptingOperation : ScriptingOperation { - private bool disposed = false; + private bool _disposed; public SmoScriptingOperation(ScriptingParams parameters) { @@ -177,10 +177,10 @@ namespace Microsoft.Kusto.ServiceLayer.Scripting /// public override void Dispose() { - if (!disposed) + if (!_disposed) { this.Cancel(); - disposed = true; + _disposed = true; } } diff --git a/src/Microsoft.Kusto.ServiceLayer/ServiceHost.cs b/src/Microsoft.Kusto.ServiceLayer/ServiceHost.cs index a5b4dafe..d6f66dc4 100644 --- a/src/Microsoft.Kusto.ServiceLayer/ServiceHost.cs +++ b/src/Microsoft.Kusto.ServiceLayer/ServiceHost.cs @@ -5,20 +5,20 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Reflection; using System.Threading.Tasks; +using Microsoft.Kusto.ServiceLayer.Connection; +using Microsoft.Kusto.ServiceLayer.Utility; using Microsoft.SqlTools.Extensibility; using Microsoft.SqlTools.Hosting; using Microsoft.SqlTools.Hosting.Contracts; using Microsoft.SqlTools.Hosting.Protocol; using Microsoft.SqlTools.Hosting.Protocol.Channel; using Microsoft.SqlTools.Utility; -using Microsoft.Kusto.ServiceLayer.Connection; -using Microsoft.Kusto.ServiceLayer.Utility; -using System.Diagnostics; -namespace Microsoft.Kusto.ServiceLayer.Hosting +namespace Microsoft.Kusto.ServiceLayer { /// /// SQL Tools VS Code Language Server request handler. Provides the entire JSON RPC diff --git a/src/Microsoft.Kusto.ServiceLayer/Workspace/WorkspaceService.cs b/src/Microsoft.Kusto.ServiceLayer/Workspace/WorkspaceService.cs index 6bcb09c7..b0e6a9c9 100644 --- a/src/Microsoft.Kusto.ServiceLayer/Workspace/WorkspaceService.cs +++ b/src/Microsoft.Kusto.ServiceLayer/Workspace/WorkspaceService.cs @@ -10,7 +10,6 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using Microsoft.SqlTools.Hosting.Protocol; -using Microsoft.Kusto.ServiceLayer.Hosting; using Microsoft.Kusto.ServiceLayer.Workspace.Contracts; using Microsoft.SqlTools.Utility; using Range = Microsoft.Kusto.ServiceLayer.Workspace.Contracts.Range; diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/ObjectExplorerService.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/ObjectExplorerService.cs index b62e7bb7..7ba674ea 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/ObjectExplorerService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/ObjectExplorerService.cs @@ -73,15 +73,6 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer { this.bindingQueue = value; } - } - - /// - /// Internal for testing only - /// - internal ObjectExplorerService(ExtensionServiceProvider serviceProvider) - : this() - { - SetServiceProvider(serviceProvider); } private Dictionary> ApplicableNodeChildFactories