diff --git a/.travis.yml b/.travis.yml index 3d44e322..fba7ebd4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,5 +36,7 @@ install: script: - dotnet build src/Microsoft.SqlTools.ServiceLayer - dotnet test test/Microsoft.SqlTools.ServiceLayer.UnitTests + - dotnet build src/Microsoft.Kusto.ServiceLayer + - dotnet test test/Microsoft.Kusto.ServiceLayer.UnitTests - dotnet build src/Microsoft.SqlTools.CoreServices - dotnet test test/Microsoft.SqlTools.Hosting.UnitTests \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml index cc8ed7c5..a87d669d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -17,10 +17,12 @@ before_build: build_script: - dotnet build src/Microsoft.SqlTools.ServiceLayer + - dotnet build src/Microsoft.Kusto.ServiceLayer - dotnet build src/Microsoft.SqlTools.CoreServices test_script: - dotnet test test/Microsoft.SqlTools.ServiceLayer.UnitTests + - dotnet test test/Microsoft.Kusto.ServiceLayer.UnitTests - dotnet test test/Microsoft.SqlTools.Hosting.UnitTests after_test: diff --git a/azure-pipelines/build.yml b/azure-pipelines/build.yml index b26a3526..735a3096 100644 --- a/azure-pipelines/build.yml +++ b/azure-pipelines/build.yml @@ -45,17 +45,35 @@ steps: projects: '$(Build.SourcesDirectory)/src/Microsoft.SqlTools.ServiceLayer' arguments: '--configfile $(Build.SourcesDirectory)/nuget.config' +- task: DotNetCoreCLI@1 + displayName: 'dotnet restore src/Microsoft.Kusto.ServiceLayer' + inputs: + command: restore + projects: '$(Build.SourcesDirectory)/src/Microsoft.Kusto.ServiceLayer' + arguments: '--configfile $(Build.SourcesDirectory)/nuget.config' + - task: DotNetCoreCLI@2 displayName: 'dotnet build src/Microsoft.SqlTools.ServiceLayer' inputs: projects: '$(Build.SourcesDirectory)/src/Microsoft.SqlTools.ServiceLayer' +- task: DotNetCoreCLI@2 + displayName: 'dotnet build src/Microsoft.Kusto.ServiceLayer' + inputs: + projects: '$(Build.SourcesDirectory)/src/Microsoft.Kusto.ServiceLayer' + - task: DotNetCoreCLI@2 displayName: 'dotnet build src/Microsoft.SqlTools.ServiceLayer --configuration Release' inputs: projects: '$(Build.SourcesDirectory)/src/Microsoft.SqlTools.ServiceLayer ' arguments: '--configuration Release' +- task: DotNetCoreCLI@2 + displayName: 'dotnet build src/Microsoft.Kusto.ServiceLayer --configuration Release' + inputs: + projects: '$(Build.SourcesDirectory)/src/Microsoft.Kusto.ServiceLayer ' + arguments: '--configuration Release' + - task: BatchScript@1 displayName: 'Run script build.cmd' inputs: @@ -69,6 +87,12 @@ steps: command: restore projects: test/Microsoft.SqlTools.ServiceLayer.UnitTests +- task: DotNetCoreCLI@1 + displayName: 'dotnet restore test/Microsoft.Kusto.ServiceLayer.UnitTests' + inputs: + command: restore + projects: test/Microsoft.Kusto.ServiceLayer.UnitTests + - task: DotNetCoreCLI@1 displayName: 'dotnet test test/Microsoft.SqlTools.ServiceLayer.UnitTests' inputs: @@ -76,6 +100,13 @@ steps: projects: test/Microsoft.SqlTools.ServiceLayer.UnitTests arguments: '--logger "trx;LogFileName=xunit.trx"' +- task: DotNetCoreCLI@1 + displayName: 'dotnet test test/Microsoft.Kusto.ServiceLayer.UnitTests' + inputs: + command: test + projects: test/Microsoft.Kusto.ServiceLayer.UnitTests + arguments: '--logger "trx;LogFileName=xunit.trx"' + - task: Npm@1 displayName: 'npm install -g gulp-cli' inputs: @@ -221,4 +252,4 @@ schedules: displayName: Mon-Fri at 5:00AM branches: include: - - main + - main \ No newline at end of file diff --git a/azure-pipelines/release.yml b/azure-pipelines/release.yml index 5c432589..5f431662 100644 --- a/azure-pipelines/release.yml +++ b/azure-pipelines/release.yml @@ -64,4 +64,4 @@ schedules: displayName: Mon-Fri at 7:00UTC branches: include: - - main + - main \ No newline at end of file diff --git a/build.json b/build.json index 4dd10dd4..a4d06ecc 100644 --- a/build.json +++ b/build.json @@ -10,15 +10,19 @@ "TestProjects": { "Microsoft.SqlTools.ServiceLayer.UnitTests": [ "netcoreapp3.1" - ] + ], + "Microsoft.Kusto.ServiceLayer.UnitTests": [ + "netcoreapp3.1" + ] }, - "Frameworks": [ + "Frameworks": [ "netcoreapp3.1" ], - "MainProjects": [ + "MainProjects": [ "Microsoft.SqlTools.Credentials", "Microsoft.SqlTools.ResourceProvider", - "Microsoft.SqlTools.ServiceLayer" + "Microsoft.SqlTools.ServiceLayer", + "Microsoft.Kusto.ServiceLayer" ], "PackageProjects": [ "Microsoft.SqlTools.CoreServices", @@ -26,4 +30,4 @@ "Microsoft.SqlTools.Hosting.Contracts", "Microsoft.SqlTools.Hosting.v2" ] -} +} \ No newline at end of file diff --git a/sqltoolsservice.sln b/sqltoolsservice.sln index fd58bf20..e46e2091 100644 --- a/sqltoolsservice.sln +++ b/sqltoolsservice.sln @@ -112,6 +112,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "azure-pipelines", "azure-pi azure-pipelines\release.yml = azure-pipelines\release.yml EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Kusto.ServiceLayer.UnitTests", "test\Microsoft.Kusto.ServiceLayer.UnitTests\Microsoft.Kusto.ServiceLayer.UnitTests.csproj", "{AFCDED82-B659-4BE1-86ED-0F4F8BC661AE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -257,6 +259,12 @@ Global {E0C941C8-91F2-4BE1-8B79-AC88EDB78729}.Integration|Any CPU.Build.0 = Debug|Any CPU {E0C941C8-91F2-4BE1-8B79-AC88EDB78729}.Release|Any CPU.ActiveCfg = Release|Any CPU {E0C941C8-91F2-4BE1-8B79-AC88EDB78729}.Release|Any CPU.Build.0 = Release|Any CPU + {AFCDED82-B659-4BE1-86ED-0F4F8BC661AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AFCDED82-B659-4BE1-86ED-0F4F8BC661AE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AFCDED82-B659-4BE1-86ED-0F4F8BC661AE}.Integration|Any CPU.ActiveCfg = Debug|Any CPU + {AFCDED82-B659-4BE1-86ED-0F4F8BC661AE}.Integration|Any CPU.Build.0 = Debug|Any CPU + {AFCDED82-B659-4BE1-86ED-0F4F8BC661AE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AFCDED82-B659-4BE1-86ED-0F4F8BC661AE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -287,6 +295,7 @@ Global {D3696EFA-FB1E-4848-A726-FF7B168AFB96} = {AB9CA2B8-6F70-431C-8A1D-67479D8A7BE4} {0EC2B30C-0652-49AE-9594-85B3C3E9CA21} = {AB9CA2B8-6F70-431C-8A1D-67479D8A7BE4} {E0C941C8-91F2-4BE1-8B79-AC88EDB78729} = {2BBD7364-054F-4693-97CD-1C395E3E84A9} + {AFCDED82-B659-4BE1-86ED-0F4F8BC661AE} = {AB9CA2B8-6F70-431C-8A1D-67479D8A7BE4} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {B31CDF4B-2851-45E5-8C5F-BE97125D9DD8} diff --git a/src/Microsoft.Kusto.ServiceLayer/Admin/AdminService.cs b/src/Microsoft.Kusto.ServiceLayer/Admin/AdminService.cs index dfcadf16..eba00e04 100644 --- a/src/Microsoft.Kusto.ServiceLayer/Admin/AdminService.cs +++ b/src/Microsoft.Kusto.ServiceLayer/Admin/AdminService.cs @@ -21,50 +21,28 @@ namespace Microsoft.Kusto.ServiceLayer.Admin /// public class AdminService { - private static readonly Lazy instance = new Lazy(() => new AdminService()); + private static readonly Lazy _instance = new Lazy(() => new AdminService()); - private static ConnectionService connectionService = null; - - /// - /// Internal for testing purposes only - /// - internal static ConnectionService ConnectionServiceInstance - { - get - { - if (AdminService.connectionService == null) - { - AdminService.connectionService = ConnectionService.Instance; - } - return AdminService.connectionService; - } - - set - { - AdminService.connectionService = value; - } - } + private static ConnectionService _connectionService; /// /// Gets the singleton instance object /// - public static AdminService Instance - { - get { return instance.Value; } - } + public static AdminService Instance => _instance.Value; /// /// Initializes the service instance /// - public void InitializeService(ServiceHost serviceHost) + public void InitializeService(ServiceHost serviceHost, ConnectionService connectionService) { serviceHost.SetRequestHandler(GetDatabaseInfoRequest.Type, HandleGetDatabaseInfoRequest); + _connectionService = connectionService; } /// /// Handle get database info request /// - internal async Task HandleGetDatabaseInfoRequest( + private async Task HandleGetDatabaseInfoRequest( GetDatabaseInfoParams databaseParams, RequestContext requestContext) { @@ -72,10 +50,7 @@ namespace Microsoft.Kusto.ServiceLayer.Admin { Func requestHandler = async () => { - ConnectionInfo connInfo; - AdminService.ConnectionServiceInstance.TryFindConnection( - databaseParams.OwnerUri, - out connInfo); + _connectionService.TryFindConnection(databaseParams.OwnerUri, out var connInfo); DatabaseInfo info = null; if (connInfo != null) @@ -100,29 +75,31 @@ namespace Microsoft.Kusto.ServiceLayer.Admin await requestContext.SendError(ex.ToString()); } } - + /// /// Return database info for a specific database /// /// /// - internal DatabaseInfo GetDatabaseInfo(ConnectionInfo connInfo) + public DatabaseInfo GetDatabaseInfo(ConnectionInfo connInfo) { - if(!string.IsNullOrEmpty(connInfo.ConnectionDetails.DatabaseName)){ - ReliableDataSourceConnection connection; - connInfo.TryGetConnection("Default", out connection); - IDataSource dataSource = connection.GetUnderlyingConnection(); - 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 = MetadataFactory.ConvertToDatabaseInfo(databaseMetadata); - - return databaseInfo.ElementAtOrDefault(0); + if (string.IsNullOrEmpty(connInfo.ConnectionDetails.DatabaseName)) + { + return null; } + + ReliableDataSourceConnection connection; + connInfo.TryGetConnection("Default", out connection); + IDataSource dataSource = connection.GetUnderlyingConnection(); + DataSourceObjectMetadata objectMetadata = + MetadataFactory.CreateClusterMetadata(connInfo.ConnectionDetails.ServerName); - return null; + List metadata = dataSource.GetChildObjects(objectMetadata, true).ToList(); + var databaseMetadata = metadata.Where(o => o.Name == connInfo.ConnectionDetails.DatabaseName); + + 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 f89915b7..9a63e974 100644 --- a/src/Microsoft.Kusto.ServiceLayer/Connection/ConnectionService.cs +++ b/src/Microsoft.Kusto.ServiceLayer/Connection/ConnectionService.cs @@ -59,6 +59,8 @@ namespace Microsoft.Kusto.ServiceLayer.Connection private ConcurrentDictionary connectedQueues = new ConcurrentDictionary(); + private IDataSourceFactory _dataSourceFactory; + /// /// Map from script URIs to ConnectionInfo objects /// This is internal for testing access only @@ -397,7 +399,7 @@ namespace Microsoft.Kusto.ServiceLayer.Connection DataSourceObjectMetadata clusterMetadata = MetadataFactory.CreateClusterMetadata(connectionInfo.ConnectionDetails.ServerName); DiagnosticsInfo clusterDiagnostics = dataSource.GetDiagnostics(clusterMetadata); - ReliableConnectionHelper.ServerInfo serverInfo = DataSourceFactory.ConvertToServerinfoFormat(DataSourceType.Kusto, clusterDiagnostics); + ReliableConnectionHelper.ServerInfo serverInfo = DataSourceFactory.ConvertToServerInfoFormat(DataSourceType.Kusto, clusterDiagnostics); response.ServerInfo = new ServerInfo { @@ -789,10 +791,11 @@ namespace Microsoft.Kusto.ServiceLayer.Connection } public void InitializeService(IProtocolEndpoint serviceHost, IDataSourceConnectionFactory dataSourceConnectionFactory, - IConnectedBindingQueue connectedBindingQueue) + IConnectedBindingQueue connectedBindingQueue, IDataSourceFactory dataSourceFactory) { ServiceHost = serviceHost; _dataSourceConnectionFactory = dataSourceConnectionFactory; + _dataSourceFactory = dataSourceFactory; connectedQueues.AddOrUpdate("Default", connectedBindingQueue, (key, old) => connectedBindingQueue); LockedDatabaseManager.ConnectionService = this; @@ -1411,7 +1414,7 @@ namespace Microsoft.Kusto.ServiceLayer.Connection 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); + return _dataSourceFactory.Create(DataSourceType.Kusto, connectionString, connInfo.ConnectionDetails.AzureAccountToken); } catch (Exception ex) { diff --git a/src/Microsoft.Kusto.ServiceLayer/Connection/DataSourceConnectionFactory.cs b/src/Microsoft.Kusto.ServiceLayer/Connection/DataSourceConnectionFactory.cs index 03dac2f4..567a70ce 100644 --- a/src/Microsoft.Kusto.ServiceLayer/Connection/DataSourceConnectionFactory.cs +++ b/src/Microsoft.Kusto.ServiceLayer/Connection/DataSourceConnectionFactory.cs @@ -17,6 +17,14 @@ namespace Microsoft.Kusto.ServiceLayer.Connection [Export(typeof(IDataSourceConnectionFactory))] public class DataSourceConnectionFactory : IDataSourceConnectionFactory { + private readonly IDataSourceFactory _dataSourceFactory; + + [ImportingConstructor] + public DataSourceConnectionFactory(IDataSourceFactory dataSourceFactory) + { + _dataSourceFactory = dataSourceFactory; + } + /// /// Creates a new SqlConnection object /// @@ -24,7 +32,7 @@ namespace Microsoft.Kusto.ServiceLayer.Connection { RetryPolicy connectionRetryPolicy = RetryPolicyFactory.CreateDefaultConnectionRetryPolicy(); RetryPolicy commandRetryPolicy = RetryPolicyFactory.CreateDefaultConnectionRetryPolicy(); - return new ReliableDataSourceConnection(connectionString, connectionRetryPolicy, commandRetryPolicy, azureAccountToken); + return new ReliableDataSourceConnection(connectionString, connectionRetryPolicy, commandRetryPolicy, azureAccountToken, _dataSourceFactory); } } } diff --git a/src/Microsoft.Kusto.ServiceLayer/DataSource/DataSourceFactory.cs b/src/Microsoft.Kusto.ServiceLayer/DataSource/DataSourceFactory.cs index 59678a51..594db8c4 100644 --- a/src/Microsoft.Kusto.ServiceLayer/DataSource/DataSourceFactory.cs +++ b/src/Microsoft.Kusto.ServiceLayer/DataSource/DataSourceFactory.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Composition; using Microsoft.Kusto.ServiceLayer.Utility; using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection; using Microsoft.Kusto.ServiceLayer.DataSource.DataSourceIntellisense; @@ -9,9 +10,10 @@ using Microsoft.Kusto.ServiceLayer.LanguageServices.Completion; namespace Microsoft.Kusto.ServiceLayer.DataSource { - public class DataSourceFactory + [Export(typeof(IDataSourceFactory))] + public class DataSourceFactory : IDataSourceFactory { - public static IDataSource Create(DataSourceType dataSourceType, string connectionString, string azureAccountToken) + public IDataSource Create(DataSourceType dataSourceType, string connectionString, string azureAccountToken) { ValidationUtils.IsArgumentNotNullOrWhiteSpace(connectionString, nameof(connectionString)); ValidationUtils.IsArgumentNotNullOrWhiteSpace(azureAccountToken, nameof(azureAccountToken)); @@ -57,15 +59,16 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource } } - public static ReliableConnectionHelper.ServerInfo ConvertToServerinfoFormat(DataSourceType dataSourceType, DiagnosticsInfo clusterDiagnostics) + public static ReliableConnectionHelper.ServerInfo ConvertToServerInfoFormat(DataSourceType dataSourceType, DiagnosticsInfo clusterDiagnostics) { switch (dataSourceType) { case DataSourceType.Kusto: { - ReliableConnectionHelper.ServerInfo serverInfo = new ReliableConnectionHelper.ServerInfo(); - serverInfo.Options = new Dictionary(clusterDiagnostics.Options); - return serverInfo; + return new ReliableConnectionHelper.ServerInfo + { + Options = new Dictionary(clusterDiagnostics.Options) + }; } default: diff --git a/src/Microsoft.Kusto.ServiceLayer/DataSource/IDataSourceFactory.cs b/src/Microsoft.Kusto.ServiceLayer/DataSource/IDataSourceFactory.cs new file mode 100644 index 00000000..0b5b70a1 --- /dev/null +++ b/src/Microsoft.Kusto.ServiceLayer/DataSource/IDataSourceFactory.cs @@ -0,0 +1,7 @@ +namespace Microsoft.Kusto.ServiceLayer.DataSource +{ + public interface IDataSourceFactory + { + IDataSource Create(DataSourceType dataSourceType, string connectionString, string azureAccountToken); + } +} \ 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 a45ad9d4..3e80d718 100644 --- a/src/Microsoft.Kusto.ServiceLayer/DataSource/ReliableDataSourceConnection.cs +++ b/src/Microsoft.Kusto.ServiceLayer/DataSource/ReliableDataSourceConnection.cs @@ -43,6 +43,7 @@ namespace Microsoft.Kusto.ServiceLayer.Connection private readonly string _connectionString; private readonly string _azureAccountToken; + private readonly IDataSourceFactory _dataSourceFactory; /// /// Initializes a new instance of the ReliableKustoClient class with a given connection string @@ -52,11 +53,15 @@ namespace Microsoft.Kusto.ServiceLayer.Connection /// The connection string used to open the SQL Azure database. /// The retry policy defining whether to retry a request if a connection fails to be established. /// The retry policy defining whether to retry a request if a command fails to be executed. - public ReliableDataSourceConnection(string connectionString, RetryPolicy connectionRetryPolicy, RetryPolicy commandRetryPolicy, string azureAccountToken) + /// + /// + public ReliableDataSourceConnection(string connectionString, RetryPolicy connectionRetryPolicy, + RetryPolicy commandRetryPolicy, string azureAccountToken, IDataSourceFactory dataSourceFactory) { _connectionString = connectionString; _azureAccountToken = azureAccountToken; - _dataSource = DataSourceFactory.Create(DataSourceType.Kusto, connectionString, azureAccountToken); + _dataSourceFactory = dataSourceFactory; + _dataSource = dataSourceFactory.Create(DataSourceType.Kusto, connectionString, azureAccountToken); _connectionRetryPolicy = connectionRetryPolicy ?? RetryPolicyFactory.CreateNoRetryPolicy(); _commandRetryPolicy = commandRetryPolicy ?? RetryPolicyFactory.CreateNoRetryPolicy(); @@ -112,42 +117,45 @@ namespace Microsoft.Kusto.ServiceLayer.Connection /// public string ConnectionString { get; set; } - /// - /// Gets the policy which decides whether to retry a connection request, based on how many - /// times the request has been made and the reason for the last failure. + /// + /// Gets the policy which decides whether to retry a connection request, based on how many + /// times the request has been made and the reason for the last failure. /// + // ReSharper disable once UnusedMember.Global public RetryPolicy ConnectionRetryPolicy - { - get { return _connectionRetryPolicy; } - } + { + get { return _connectionRetryPolicy; } + } - /// - /// Gets the policy which decides whether to retry a command, based on how many - /// times the request has been made and the reason for the last failure. + /// + /// Gets the policy which decides whether to retry a command, based on how many + /// times the request has been made and the reason for the last failure. /// - public RetryPolicy CommandRetryPolicy - { - get { return _commandRetryPolicy; } - set - { - Validate.IsNotNull(nameof(value), value); + // ReSharper disable once UnusedMember.Global + public RetryPolicy CommandRetryPolicy + { + get { return _commandRetryPolicy; } + set + { + Validate.IsNotNull(nameof(value), value); - if (_commandRetryPolicy != null) - { - _commandRetryPolicy.RetryOccurred -= RetryCommandCallback; - } + if (_commandRetryPolicy != null) + { + _commandRetryPolicy.RetryOccurred -= RetryCommandCallback; + } - _commandRetryPolicy = value; - _commandRetryPolicy.RetryOccurred += RetryCommandCallback; - } - } + _commandRetryPolicy = value; + _commandRetryPolicy.RetryOccurred += RetryCommandCallback; + } + } - /// - /// Gets the server name from the underlying connection. - /// - public string ClusterName - { - get { return _dataSource.ClusterName; } + /// + /// Gets the server name from the underlying connection. + /// + // ReSharper disable once UnusedMember.Global + public string ClusterName + { + get { return _dataSource.ClusterName; } } /// @@ -182,7 +190,7 @@ namespace Microsoft.Kusto.ServiceLayer.Connection { _connectionRetryPolicy.ExecuteAction(() => { - _dataSource = DataSourceFactory.Create(DataSourceType.Kusto, _connectionString, _azureAccountToken); + _dataSource = _dataSourceFactory.Create(DataSourceType.Kusto, _connectionString, _azureAccountToken); }); } } @@ -219,14 +227,15 @@ namespace Microsoft.Kusto.ServiceLayer.Connection public void Close() { } - - /// - /// Gets the time to wait while trying to establish a connection before terminating - /// the attempt and generating an error. - /// + + /// + /// Gets the time to wait while trying to establish a connection before terminating + /// the attempt and generating an error. + /// + // ReSharper disable once UnusedMember.Global public int ConnectionTimeout - { - get { return 30; } + { + get { return 30; } } /// @@ -237,14 +246,6 @@ namespace Microsoft.Kusto.ServiceLayer.Connection { get { return _dataSource.DatabaseName; } } - - private void VerifyConnectionOpen(ReliableDataSourceConnection conn) - { - if(conn.GetUnderlyingConnection() == null) - { - conn.Open(); - } - } } } diff --git a/src/Microsoft.Kusto.ServiceLayer/HostLoader.cs b/src/Microsoft.Kusto.ServiceLayer/HostLoader.cs index d6e577b3..fc98d834 100644 --- a/src/Microsoft.Kusto.ServiceLayer/HostLoader.cs +++ b/src/Microsoft.Kusto.ServiceLayer/HostLoader.cs @@ -71,6 +71,7 @@ namespace Microsoft.Kusto.ServiceLayer var scripter = serviceProvider.GetService(); var dataSourceConnectionFactory = serviceProvider.GetService(); var connectedBindingQueue = serviceProvider.GetService(); + var dataSourceFactory = 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 @@ -78,10 +79,10 @@ namespace Microsoft.Kusto.ServiceLayer WorkspaceService.Instance.InitializeService(serviceHost); serviceProvider.RegisterSingleService(WorkspaceService.Instance); - LanguageService.Instance.InitializeService(serviceHost, connectedBindingQueue); + LanguageService.Instance.InitializeService(serviceHost, connectedBindingQueue, dataSourceFactory); serviceProvider.RegisterSingleService(LanguageService.Instance); - ConnectionService.Instance.InitializeService(serviceHost, dataSourceConnectionFactory, connectedBindingQueue); + ConnectionService.Instance.InitializeService(serviceHost, dataSourceConnectionFactory, connectedBindingQueue, dataSourceFactory); serviceProvider.RegisterSingleService(ConnectionService.Instance); CredentialService.Instance.InitializeService(serviceHost); @@ -90,10 +91,10 @@ namespace Microsoft.Kusto.ServiceLayer QueryExecutionService.Instance.InitializeService(serviceHost); serviceProvider.RegisterSingleService(QueryExecutionService.Instance); - ScriptingService.Instance.InitializeService(serviceHost, scripter); + ScriptingService.Instance.InitializeService(serviceHost, scripter, dataSourceFactory); serviceProvider.RegisterSingleService(ScriptingService.Instance); - AdminService.Instance.InitializeService(serviceHost); + AdminService.Instance.InitializeService(serviceHost, ConnectionService.Instance); serviceProvider.RegisterSingleService(AdminService.Instance); MetadataService.Instance.InitializeService(serviceHost); diff --git a/src/Microsoft.Kusto.ServiceLayer/LanguageServices/ConnectedBindingQueue.cs b/src/Microsoft.Kusto.ServiceLayer/LanguageServices/ConnectedBindingQueue.cs index dd7405b9..ee947257 100644 --- a/src/Microsoft.Kusto.ServiceLayer/LanguageServices/ConnectedBindingQueue.cs +++ b/src/Microsoft.Kusto.ServiceLayer/LanguageServices/ConnectedBindingQueue.cs @@ -24,6 +24,7 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices { internal const int DefaultBindingTimeout = 500; private readonly ISqlConnectionOpener _connectionOpener; + private readonly IDataSourceFactory _dataSourceFactory; /// /// Gets the current settings @@ -34,9 +35,10 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices } [ImportingConstructor] - public ConnectedBindingQueue(ISqlConnectionOpener sqlConnectionOpener) + public ConnectedBindingQueue(ISqlConnectionOpener sqlConnectionOpener, IDataSourceFactory dataSourceFactory) { _connectionOpener = sqlConnectionOpener; + _dataSourceFactory = dataSourceFactory; } /// @@ -160,7 +162,7 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices bindingContext.ServerConnection = _connectionOpener.OpenServerConnection(connInfo, featureName); string connectionString = ConnectionService.BuildConnectionString(connInfo.ConnectionDetails); - bindingContext.DataSource = DataSourceFactory.Create(DataSourceType.Kusto, connectionString, connInfo.ConnectionDetails.AzureAccountToken); + bindingContext.DataSource = _dataSourceFactory.Create(DataSourceType.Kusto, connectionString, connInfo.ConnectionDetails.AzureAccountToken); if (needMetadata) { diff --git a/src/Microsoft.Kusto.ServiceLayer/LanguageServices/LanguageService.cs b/src/Microsoft.Kusto.ServiceLayer/LanguageServices/LanguageService.cs index 7a347456..7fdcdfbf 100644 --- a/src/Microsoft.Kusto.ServiceLayer/LanguageServices/LanguageService.cs +++ b/src/Microsoft.Kusto.ServiceLayer/LanguageServices/LanguageService.cs @@ -209,7 +209,7 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices /// /// /// - public void InitializeService(ServiceHost serviceHost, IConnectedBindingQueue connectedBindingQueue) + public void InitializeService(ServiceHost serviceHost, IConnectedBindingQueue connectedBindingQueue, IDataSourceFactory dataSourceFactory) { _bindingQueue = connectedBindingQueue; // Register the requests that this service will handle @@ -854,7 +854,7 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices if (scriptParseInfo == null) { var scriptDocInfo = ScriptDocumentInfo.CreateDefaultDocumentInfo(textDocumentPosition, scriptFile); - resultCompletionItems = resultCompletionItems = DataSourceFactory.GetDefaultAutoComplete(DataSourceType.Kusto, scriptDocInfo, textDocumentPosition.Position); //TODO_KUSTO: DataSourceFactory.GetDefaultAutoComplete 1st param should get the datasource type generically instead of hard coded DataSourceType.Kusto + resultCompletionItems = DataSourceFactory.GetDefaultAutoComplete(DataSourceType.Kusto, scriptDocInfo, textDocumentPosition.Position); //TODO_KUSTO: DataSourceFactory.GetDefaultAutoComplete 1st param should get the datasource type generically instead of hard coded DataSourceType.Kusto return resultCompletionItems; } diff --git a/src/Microsoft.Kusto.ServiceLayer/Scripting/ScriptAsScriptingOperation.cs b/src/Microsoft.Kusto.ServiceLayer/Scripting/ScriptAsScriptingOperation.cs index ce67a623..3b54e1a1 100644 --- a/src/Microsoft.Kusto.ServiceLayer/Scripting/ScriptAsScriptingOperation.cs +++ b/src/Microsoft.Kusto.ServiceLayer/Scripting/ScriptAsScriptingOperation.cs @@ -11,11 +11,7 @@ using Microsoft.Kusto.ServiceLayer.DataSource; using Microsoft.SqlTools.Utility; using Microsoft.SqlServer.Management.Common; using Microsoft.SqlServer.Management.Smo; -using System.Collections.Specialized; -using System.Text; -using System.Globalization; using Microsoft.SqlServer.Management.SqlScriptPublish; -using Microsoft.Kusto.ServiceLayer.Utility; using Microsoft.SqlServer.Management.Sdk.Sfc; using System.Diagnostics; @@ -28,19 +24,10 @@ namespace Microsoft.Kusto.ServiceLayer.Scripting { private readonly IScripter _scripter; private static readonly Dictionary scriptCompatibilityMap = LoadScriptCompatibilityMap(); - /// - /// Left delimiter for an named object - /// - public const char LeftDelimiter = '['; - /// - /// right delimiter for a named object - /// - public const char RightDelimiter = ']'; - - public ScriptAsScriptingOperation(ScriptingParams parameters, string azureAccountToken, IScripter scripter) : base(parameters) + public ScriptAsScriptingOperation(ScriptingParams parameters, string azureAccountToken, IScripter scripter, IDataSourceFactory dataSourceFactory) : base(parameters, dataSourceFactory) { - DataSource = DataSourceFactory.Create(DataSourceType.Kusto, this.Parameters.ConnectionString, + DataSource = _dataSourceFactory.Create(DataSourceType.Kusto, this.Parameters.ConnectionString, azureAccountToken); _scripter = scripter; } @@ -177,200 +164,6 @@ namespace Microsoft.Kusto.ServiceLayer.Scripting } - /// - /// Generate a schema qualified name (e.g. [schema].[objectName]) for an object if the option for SchemaQualify is true - /// - /// The schema name. May be null or empty in which case it will be ignored - /// 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 - private static string GenerateSchemaQualifiedName(string schema, string objectName, bool schemaQualify) - { - var qualifiedName = new StringBuilder(); - - if (schemaQualify && !String.IsNullOrEmpty(schema)) - { - // schema.name - qualifiedName.AppendFormat(CultureInfo.InvariantCulture, "{0}.{1}", GetDelimitedString(schema), GetDelimitedString(objectName)); - } - else - { - // name - qualifiedName.AppendFormat(CultureInfo.InvariantCulture, "{0}", GetDelimitedString(objectName)); - } - - return qualifiedName.ToString(); - } - - /// - /// getting delimited string - /// - /// string - /// string - static private string GetDelimitedString(string str) - { - if (string.IsNullOrEmpty(str)) - { - return String.Empty; - } - else - { - StringBuilder qualifiedName = new StringBuilder(); - qualifiedName.AppendFormat("{0}{1}{2}", - LeftDelimiter, - QuoteObjectName(str), - RightDelimiter); - return qualifiedName.ToString(); - } - } - - /// - /// turn a smo datatype object into a type that can be inserted into tsql, e.g. nvarchar(20) - /// - /// - /// - /// - internal static string GetDatatype(DataType type, ScriptingOptions options) - { - // string we'll return. - string rv = string.Empty; - - string dataType = type.Name; - switch (type.SqlDataType) - { - // char, nchar, nchar, nvarchar, varbinary, nvarbinary are all displayed as type(length) - // length of -1 is taken to be type(max). max isn't localizable. - case SqlDataType.Char: - case SqlDataType.NChar: - case SqlDataType.VarChar: - case SqlDataType.NVarChar: - case SqlDataType.Binary: - case SqlDataType.VarBinary: - rv = string.Format(CultureInfo.InvariantCulture, - "{0}({1})", - dataType, - type.MaximumLength); - break; - case SqlDataType.VarCharMax: - case SqlDataType.NVarCharMax: - case SqlDataType.VarBinaryMax: - rv = string.Format(CultureInfo.InvariantCulture, - "{0}(max)", - dataType); - break; - // numeric and decimal are displayed as type precision,scale - case SqlDataType.Numeric: - case SqlDataType.Decimal: - rv = string.Format(CultureInfo.InvariantCulture, - "{0}({1},{2})", - dataType, - type.NumericPrecision, - type.NumericScale); - break; - //time, datetimeoffset and datetime2 are displayed as type scale - case SqlDataType.Time: - case SqlDataType.DateTimeOffset: - case SqlDataType.DateTime2: - rv = string.Format(CultureInfo.InvariantCulture, - "{0}({1})", - dataType, - type.NumericScale); - break; - // anything else is just type. - case SqlDataType.Xml: - if (type.Schema != null && type.Schema.Length > 0 && dataType != null && dataType.Length > 0) - { - rv = String.Format(CultureInfo.InvariantCulture - , "xml ({0}{2}{1}.{0}{3}{1})" - , LeftDelimiter - , RightDelimiter - , QuoteObjectName(type.Schema) - , QuoteObjectName(dataType)); - } - else - { - rv = "xml"; - } - break; - case SqlDataType.UserDefinedDataType: - case SqlDataType.UserDefinedTableType: - case SqlDataType.UserDefinedType: - //User defined types may be in a non-DBO schema so append it if necessary - rv = GenerateSchemaQualifiedName(type.Schema, dataType, options.SchemaQualify); - break; - default: - rv = dataType; - break; - - } - return rv; - } - - /// - /// Double quotes certain characters in object name - /// - /// - public static string QuoteObjectName(string sqlObject) - { - - int len = sqlObject.Length; - StringBuilder result = new StringBuilder(sqlObject.Length); - for (int i = 0; i < len; i++) - { - if (sqlObject[i] == ']') - { - result.Append(']'); - } - result.Append(sqlObject[i]); - } - - return result.ToString(); - } - - private static void WriteUseDatabase(Database parentObject, StringBuilder stringBuilder , ScriptingOptions options) - { - if (options.IncludeDatabaseContext) - { - string useDb = string.Format(CultureInfo.InvariantCulture, "USE {0}", CommonConstants.DefaultBatchSeperator); - if (!options.NoCommandTerminator) - { - stringBuilder.Append(useDb); - - } - else - { - stringBuilder.Append(useDb); - stringBuilder.Append(Environment.NewLine); - } - } - } - - private string GetScript(ScriptingOptions options, StringCollection stringCollection) - { - StringBuilder sb = new StringBuilder(); - - foreach (var item in stringCollection) - { - sb.Append(item); - if (options != null && !options.NoCommandTerminator) - { - //Ensure the batch separator is always on a new line (to avoid syntax errors) - //but don't write an extra if we already have one as this can affect definitions - //of objects such as Stored Procedures (see TFS#9125366) - sb.AppendFormat(CultureInfo.InvariantCulture, "{0}{1}{2}", - item.EndsWith(Environment.NewLine) ? string.Empty : Environment.NewLine, - CommonConstants.DefaultBatchSeperator, - Environment.NewLine); - } - else - { - sb.AppendFormat(CultureInfo.InvariantCulture, Environment.NewLine); - } - } - - return sb.ToString(); - } - private UrnCollection CreateUrns(IDataSource dataSource) { IEnumerable selectedObjects = new List(this.Parameters.ScriptingObjects); @@ -533,27 +326,5 @@ namespace Microsoft.Kusto.ServiceLayer.Scripting scriptingOptions.AnsiPadding = true; } } - - private void ScripterScriptingError(object sender, ScriptingErrorEventArgs e) - { - this.CancellationToken.ThrowIfCancellationRequested(); - - Logger.Write( - TraceEventType.Verbose, - string.Format( - "Sending scripting error progress event, Urn={0}, OperationId={1}, Completed={2}, Error={3}", - e.Current, - this.OperationId, - false, - e?.InnerException?.ToString() ?? "null")); - - this.SendProgressNotificationEvent(new ScriptingProgressNotificationParams - { - ScriptingObject = e.Current?.ToScriptingObject(), - Status = "Failed", - ErrorMessage = e?.InnerException?.Message, - ErrorDetails = e?.InnerException?.ToString(), - }); - } } } diff --git a/src/Microsoft.Kusto.ServiceLayer/Scripting/ScriptingScriptOperation.cs b/src/Microsoft.Kusto.ServiceLayer/Scripting/ScriptingScriptOperation.cs index 119838a4..8b8b3aa3 100644 --- a/src/Microsoft.Kusto.ServiceLayer/Scripting/ScriptingScriptOperation.cs +++ b/src/Microsoft.Kusto.ServiceLayer/Scripting/ScriptingScriptOperation.cs @@ -29,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, IDataSourceFactory dataSourceFactory) : base(parameters, dataSourceFactory) { this.azureAccessToken = azureAccessToken; } diff --git a/src/Microsoft.Kusto.ServiceLayer/Scripting/ScriptingService.cs b/src/Microsoft.Kusto.ServiceLayer/Scripting/ScriptingService.cs index 76cf725e..deffcc2e 100644 --- a/src/Microsoft.Kusto.ServiceLayer/Scripting/ScriptingService.cs +++ b/src/Microsoft.Kusto.ServiceLayer/Scripting/ScriptingService.cs @@ -36,6 +36,7 @@ namespace Microsoft.Kusto.ServiceLayer.Scripting private bool disposed; private IScripter _scripter; + private IDataSourceFactory _dataSourceFactory; /// /// Internal for testing purposes only @@ -66,9 +67,10 @@ namespace Microsoft.Kusto.ServiceLayer.Scripting /// /// /// - public void InitializeService(ServiceHost serviceHost, IScripter scripter) + public void InitializeService(ServiceHost serviceHost, IScripter scripter, IDataSourceFactory dataSourceFactory) { _scripter = scripter; + _dataSourceFactory = dataSourceFactory; serviceHost.SetRequestHandler(ScriptingRequest.Type, this.HandleScriptExecuteRequest); serviceHost.SetRequestHandler(ScriptingCancelRequest.Type, this.HandleScriptCancelRequest); serviceHost.SetRequestHandler(ScriptingListObjectsRequest.Type, this.HandleListObjectsRequest); @@ -131,11 +133,11 @@ namespace Microsoft.Kusto.ServiceLayer.Scripting if (!ShouldCreateScriptAsOperation(parameters)) { - operation = new ScriptingScriptOperation(parameters, accessToken); + operation = new ScriptingScriptOperation(parameters, accessToken, _dataSourceFactory); } else { - operation = new ScriptAsScriptingOperation(parameters, accessToken, _scripter); + operation = new ScriptAsScriptingOperation(parameters, accessToken, _scripter, _dataSourceFactory); } 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 de880dcb..33108281 100644 --- a/src/Microsoft.Kusto.ServiceLayer/Scripting/SmoScriptingOperation.cs +++ b/src/Microsoft.Kusto.ServiceLayer/Scripting/SmoScriptingOperation.cs @@ -3,8 +3,6 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -using Microsoft.SqlServer.Management.Common; -using Microsoft.Kusto.ServiceLayer.Connection; using Microsoft.Kusto.ServiceLayer.Scripting.Contracts; using Microsoft.Kusto.ServiceLayer.DataSource; using Microsoft.SqlTools.Utility; @@ -22,10 +20,12 @@ namespace Microsoft.Kusto.ServiceLayer.Scripting /// public abstract class SmoScriptingOperation : ScriptingOperation { + protected readonly IDataSourceFactory _dataSourceFactory; private bool _disposed; - public SmoScriptingOperation(ScriptingParams parameters) + protected SmoScriptingOperation(ScriptingParams parameters, IDataSourceFactory dataSourceFactory) { + _dataSourceFactory = dataSourceFactory; Validate.IsNotNull("parameters", parameters); this.Parameters = parameters; @@ -77,7 +77,7 @@ namespace Microsoft.Kusto.ServiceLayer.Scripting { string serverName = string.Empty; - using(var dataSource = DataSourceFactory.Create(DataSourceType.Kusto, connectionString, azureAccessToken)) + using(var dataSource = _dataSourceFactory.Create(DataSourceType.Kusto, connectionString, azureAccessToken)) { serverName = dataSource.ClusterName; } diff --git a/src/Microsoft.Kusto.ServiceLayer/Workspace/Contracts/ScriptFile.cs b/src/Microsoft.Kusto.ServiceLayer/Workspace/Contracts/ScriptFile.cs index f4a1f466..a5132801 100644 --- a/src/Microsoft.Kusto.ServiceLayer/Workspace/Contracts/ScriptFile.cs +++ b/src/Microsoft.Kusto.ServiceLayer/Workspace/Contracts/ScriptFile.cs @@ -98,14 +98,6 @@ namespace Microsoft.Kusto.ServiceLayer.Workspace.Contracts #region Constructors - /// - /// Add a default constructor for testing - /// - public ScriptFile() - { - ClientUri = "test.sql"; - } - /// /// Creates a new ScriptFile instance by reading file contents from /// the given TextReader. diff --git a/src/Microsoft.SqlTools.ServiceLayer/Microsoft.SqlTools.ServiceLayer.csproj b/src/Microsoft.SqlTools.ServiceLayer/Microsoft.SqlTools.ServiceLayer.csproj index 9007d1d9..779ac94f 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Microsoft.SqlTools.ServiceLayer.csproj +++ b/src/Microsoft.SqlTools.ServiceLayer/Microsoft.SqlTools.ServiceLayer.csproj @@ -33,6 +33,7 @@ + @@ -48,20 +49,10 @@ - + - - + + diff --git a/test/Microsoft.Kusto.ServiceLayer.UnitTests/Admin/Contracts/AdminServiceTests.cs b/test/Microsoft.Kusto.ServiceLayer.UnitTests/Admin/Contracts/AdminServiceTests.cs new file mode 100644 index 00000000..aa9094a4 --- /dev/null +++ b/test/Microsoft.Kusto.ServiceLayer.UnitTests/Admin/Contracts/AdminServiceTests.cs @@ -0,0 +1,27 @@ +using Microsoft.Kusto.ServiceLayer.Admin; +using Microsoft.Kusto.ServiceLayer.Connection; +using Microsoft.Kusto.ServiceLayer.Connection.Contracts; +using Moq; +using NUnit.Framework; + +namespace Microsoft.Kusto.ServiceLayer.UnitTests.Admin.Contracts +{ + public class AdminServiceTests + { + [TestCase(null)] + [TestCase("")] + public void GetDatabaseInfo_Returns_Null_For_Invalid_DatabaseName(string databaseName) + { + var dataSourceConnectionFactory = new Mock(); + var connectionDetails = new ConnectionDetails + { + DatabaseName = databaseName + }; + var connectionInfo = new ConnectionInfo(dataSourceConnectionFactory.Object, "", connectionDetails); + + var adminService = new AdminService(); + var databaseInfo = adminService.GetDatabaseInfo(connectionInfo); + Assert.IsNull(databaseInfo); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.Kusto.ServiceLayer.UnitTests/DataSource/DataSourceFactoryTests.cs b/test/Microsoft.Kusto.ServiceLayer.UnitTests/DataSource/DataSourceFactoryTests.cs new file mode 100644 index 00000000..91db92bb --- /dev/null +++ b/test/Microsoft.Kusto.ServiceLayer.UnitTests/DataSource/DataSourceFactoryTests.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using Microsoft.Kusto.ServiceLayer.DataSource; +using Microsoft.Kusto.ServiceLayer.DataSource.DataSourceIntellisense; +using Microsoft.Kusto.ServiceLayer.LanguageServices.Completion; +using Microsoft.Kusto.ServiceLayer.Workspace.Contracts; +using NUnit.Framework; + +namespace Microsoft.Kusto.ServiceLayer.UnitTests.DataSource +{ + public class DataSourceFactoryTests + { + [TestCase(typeof(ArgumentNullException), "", "AzureAccountToken")] + [TestCase(typeof(ArgumentNullException), "ConnectionString", "")] + [TestCase(typeof(ArgumentException), "ConnectionString", "AzureAccountToken")] + public void Create_Throws_Exceptions_For_InvalidParams(Type exceptionType, + string connectionString, + string azureAccountToken) + { + var dataSourceFactory = new DataSourceFactory(); + Assert.Throws(exceptionType, + () => dataSourceFactory.Create(DataSourceType.None, connectionString, azureAccountToken)); + } + + [Test] + public void GetDefaultAutoComplete_Throws_ArgumentException_For_InvalidDataSourceType() + { + Assert.Throws(() => + DataSourceFactory.GetDefaultAutoComplete(DataSourceType.None, null, null)); + } + + [Test] + public void GetDefaultAutoComplete_Returns_CompletionItems() + { + var textDocumentPosition = new TextDocumentPosition + { + Position = new Position() + }; + var scriptFile = new ScriptFile("", "", ""); + var scriptParseInfo = new ScriptParseInfo(); + var documentInfo = new ScriptDocumentInfo(textDocumentPosition, scriptFile, scriptParseInfo); + var position = new Position(); + + var completionItems = DataSourceFactory.GetDefaultAutoComplete(DataSourceType.Kusto, documentInfo, position); + Assert.AreNotEqual(0, completionItems.Length); + } + + [Test] + public void GetDefaultSemanticMarkers_Throws_ArgumentException_For_InvalidDataSourceType() + { + Assert.Throws(() => + DataSourceFactory.GetDefaultSemanticMarkers(DataSourceType.None, null, null, null)); + } + + [Test] + public void GetDefaultSemanticMarkers_Returns_ScriptFileMarker() + { + var parseInfo = new ScriptParseInfo(); + var file = new ScriptFile("", "", ""); + var queryText = ".show databases"; + + var semanticMarkers = DataSourceFactory.GetDefaultSemanticMarkers(DataSourceType.Kusto, parseInfo, file, queryText); + + Assert.AreNotEqual(0, semanticMarkers.Length); + } + + [Test] + public void ConvertToServerInfoFormat_Throws_ArgumentException_For_InvalidDataSourceType() + { + Assert.Throws(() => + DataSourceFactory.ConvertToServerInfoFormat(DataSourceType.None, null)); + } + + [Test] + public void ConvertToServerInfoFormat_Returns_ServerInfo_With_Options() + { + var diagnosticsInfo = new DiagnosticsInfo + { + Options = new Dictionary + { + {"Key", "Object"} + } + }; + + var serverInfo = DataSourceFactory.ConvertToServerInfoFormat(DataSourceType.Kusto, diagnosticsInfo); + + Assert.IsNotNull(serverInfo.Options); + Assert.AreEqual(diagnosticsInfo.Options["Key"], serverInfo.Options["Key"]); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.Kusto.ServiceLayer.UnitTests/DataSource/Metadata/MetadataFactoryTests.cs b/test/Microsoft.Kusto.ServiceLayer.UnitTests/DataSource/Metadata/MetadataFactoryTests.cs new file mode 100644 index 00000000..fad1f75a --- /dev/null +++ b/test/Microsoft.Kusto.ServiceLayer.UnitTests/DataSource/Metadata/MetadataFactoryTests.cs @@ -0,0 +1,164 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Kusto.ServiceLayer.DataSource.Metadata; +using Microsoft.Kusto.ServiceLayer.Metadata.Contracts; +using NUnit.Framework; + +namespace Microsoft.Kusto.ServiceLayer.UnitTests.DataSource.Metadata +{ + public class MetadataFactoryTests + { + [Test] + public void CreateClusterMetadata_ThrowsNullException_For_NullClusterName() + { + Assert.Throws(() => MetadataFactory.CreateClusterMetadata(null)); + } + + [Test] + [TestCase("")] + [TestCase(null)] + [TestCase(" ")] + public void CreateDatabaseMetadata_ThrowsNullException_For_InvalidDatabaseName(string databaseName) + { + var testMetadata = new DataSourceObjectMetadata + { + MetadataType = DataSourceMetadataType.Cluster + }; + + Assert.Throws(() => MetadataFactory.CreateDatabaseMetadata(testMetadata, databaseName)); + } + + [Test] + public void CreateDatabaseMetadata_ThrowsNullException_For_InvalidMetadataType() + { + var testMetadata = new DataSourceObjectMetadata + { + MetadataType = DataSourceMetadataType.Database + }; + + Assert.Throws(() => MetadataFactory.CreateDatabaseMetadata(testMetadata, "FakeDatabaseName")); + } + + [Test] + public void CreateFolderMetadata_ThrowsNullException_For_NullMetadata() + { + Assert.Throws(() => MetadataFactory.CreateFolderMetadata(null, "", "")); + } + + [Test] + public void CreateClusterMetadata_Returns_DataSourceObjectMetadata() + { + string clusterName = "FakeClusterName"; + + var objectMetadata = MetadataFactory.CreateClusterMetadata(clusterName); + + Assert.AreEqual(DataSourceMetadataType.Cluster, objectMetadata.MetadataType); + Assert.AreEqual(DataSourceMetadataType.Cluster.ToString(), objectMetadata.MetadataTypeName); + Assert.AreEqual(clusterName, objectMetadata.Name); + Assert.AreEqual(clusterName, objectMetadata.PrettyName); + Assert.AreEqual(clusterName, objectMetadata.Urn); + } + + [Test] + public void CreateDatabaseMetadata_Returns_DataSourceObjectMetadata() + { + string databaseName = "FakeDatabaseName"; + var clusterMetadata = new DataSourceObjectMetadata + { + MetadataType = DataSourceMetadataType.Cluster, + Name = "FakeClusterName", + Urn = "FakeClusterName" + }; + + + var objectMetadata = MetadataFactory.CreateDatabaseMetadata(clusterMetadata, databaseName); + + Assert.AreEqual(DataSourceMetadataType.Database, objectMetadata.MetadataType); + Assert.AreEqual(DataSourceMetadataType.Database.ToString(), objectMetadata.MetadataTypeName); + Assert.AreEqual(databaseName, objectMetadata.Name); + Assert.AreEqual(databaseName, objectMetadata.PrettyName); + Assert.AreEqual($"{clusterMetadata.Urn}.{databaseName}", objectMetadata.Urn); + } + + [Test] + public void CreateFolderMetadata_Returns_FolderMetadata() + { + string path = "FakeCluster.FakeDatabase.FakeFolder"; + string name = "FakeFolderName"; + var parentMetadata = new DataSourceObjectMetadata(); + + var objectMetadata = MetadataFactory.CreateFolderMetadata(parentMetadata, path, name); + + Assert.AreEqual(DataSourceMetadataType.Folder, objectMetadata.MetadataType); + Assert.AreEqual(DataSourceMetadataType.Folder.ToString(), objectMetadata.MetadataTypeName); + Assert.AreEqual(name, objectMetadata.Name); + Assert.AreEqual(name, objectMetadata.PrettyName); + Assert.AreEqual($"{path}.{name}", objectMetadata.Urn); + Assert.AreEqual(parentMetadata, objectMetadata.ParentMetadata); + } + + [Test] + public void ConvertToDatabaseInfo_Returns_EmptyList_For_NonDatabaseMetadata() + { + var inputList = new List + { + new DataSourceObjectMetadata + { + Name = "FakeClusterName" + } + }; + + var databaseInfos = MetadataFactory.ConvertToDatabaseInfo(inputList); + + Assert.AreEqual(0, databaseInfos.Count); + } + + [Test] + public void ConvertToDatabaseInfo_Returns_DatabaseInfoList() + { + var databaseMetadata = new DatabaseMetadata + { + Name = "FakeDatabaseName", + SizeInMB = "2097152" // stored in bytes + }; + + var inputList = new List + { + databaseMetadata + }; + + var databaseInfos = MetadataFactory.ConvertToDatabaseInfo(inputList); + + Assert.AreEqual(1, databaseInfos.Count); + + var databaseInfo = databaseInfos.Single(); + Assert.AreEqual(databaseMetadata.Name, databaseInfo.Options["name"]); + Assert.AreEqual("2", databaseInfo.Options["sizeInMB"]); + } + + [Test] + public void ConvertToObjectMetadata_Returns_ListObjectMetadata() + { + var databaseMetadata = new DataSourceObjectMetadata + { + PrettyName = "FakeDatabaseName", + MetadataTypeName = "Table" + }; + + var inputList = new List + { + databaseMetadata + }; + + var objectMetadatas = MetadataFactory.ConvertToObjectMetadata(inputList); + + Assert.AreEqual(1, objectMetadatas.Count); + var objectMetadata = objectMetadatas.Single(); + + Assert.AreEqual(databaseMetadata.PrettyName, objectMetadata.Name); + Assert.AreEqual(databaseMetadata.MetadataTypeName, objectMetadata.MetadataTypeName); + Assert.AreEqual(MetadataType.Table, objectMetadata.MetadataType); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.Kusto.ServiceLayer.UnitTests/LanguageServices/AutoCompleteHelperTests.cs b/test/Microsoft.Kusto.ServiceLayer.UnitTests/LanguageServices/AutoCompleteHelperTests.cs new file mode 100644 index 00000000..f19d3e29 --- /dev/null +++ b/test/Microsoft.Kusto.ServiceLayer.UnitTests/LanguageServices/AutoCompleteHelperTests.cs @@ -0,0 +1,78 @@ +using System.Linq; +using Microsoft.Kusto.ServiceLayer.LanguageServices; +using Microsoft.Kusto.ServiceLayer.LanguageServices.Contracts; +using NUnit.Framework; + +namespace Microsoft.Kusto.ServiceLayer.UnitTests.LanguageServices +{ + public class AutoCompleteHelperTests + { + [Test] + public void CreateCompletionItem_Returns_CompletionItem() + { + string label = ""; + string detail = ""; + string insertText = ""; + var itemKind = CompletionItemKind.Method; + int row = 1; + int startColumn = 2; + int endColumn = 3; + + var completionItem = AutoCompleteHelper.CreateCompletionItem(label, detail, insertText, itemKind, row, startColumn, endColumn); + + Assert.IsNotNull(completionItem); + Assert.AreEqual(label, completionItem.Label); + Assert.AreEqual(itemKind, completionItem.Kind); + Assert.AreEqual(detail, completionItem.Detail); + Assert.AreEqual(insertText, completionItem.InsertText); + + Assert.IsNotNull(completionItem.TextEdit); + Assert.AreEqual(insertText, completionItem.TextEdit.NewText); + + Assert.IsNotNull(completionItem.TextEdit.Range); + + Assert.IsNotNull(completionItem.TextEdit.Range.Start); + Assert.AreEqual(row, completionItem.TextEdit.Range.Start.Line); + Assert.AreEqual(startColumn, completionItem.TextEdit.Range.Start.Character); + + Assert.IsNotNull(completionItem.TextEdit.Range.End); + Assert.AreEqual(row, completionItem.TextEdit.Range.End.Line); + Assert.AreEqual(endColumn, completionItem.TextEdit.Range.End.Character); + } + + [Test] + public void ConvertQuickInfoToHover_Returns_Null_For_Null_QuickInfoText() + { + var hover = AutoCompleteHelper.ConvertQuickInfoToHover(null, "", 0, 0, 0); + Assert.IsNull(hover); + } + + [Test] + public void ConvertQuickInfoToHover_Returns_Hover() + { + string quickInfoText = ""; + string language = ""; + int row = 0; + int startColumn = 0; + int endColumn = 0; + + var hover = AutoCompleteHelper.ConvertQuickInfoToHover(quickInfoText, language, row, startColumn, endColumn); + + Assert.IsNotNull(hover); + + Assert.AreEqual(1, hover.Contents.Length); + var content = hover.Contents.First(); + Assert.AreEqual(language, content.Language); + Assert.AreEqual(quickInfoText, content.Value); + + Assert.IsNotNull(hover.Range); + Assert.IsNotNull(hover.Range.Value.Start); + Assert.AreEqual(row, hover.Range.Value.Start.Line); + Assert.AreEqual(startColumn, hover.Range.Value.Start.Character); + + Assert.IsNotNull(hover.Range.Value.End); + Assert.AreEqual(row, hover.Range.Value.End.Line); + Assert.AreEqual(endColumn, hover.Range.Value.End.Character); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.Kusto.ServiceLayer.UnitTests/Microsoft.Kusto.ServiceLayer.UnitTests.csproj b/test/Microsoft.Kusto.ServiceLayer.UnitTests/Microsoft.Kusto.ServiceLayer.UnitTests.csproj new file mode 100644 index 00000000..0feae737 --- /dev/null +++ b/test/Microsoft.Kusto.ServiceLayer.UnitTests/Microsoft.Kusto.ServiceLayer.UnitTests.csproj @@ -0,0 +1,20 @@ + + + + netcoreapp3.1 + + false + + + + + + + + + + + + + +