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
This commit is contained in:
Justin M
2020-08-24 13:18:00 -07:00
committed by GitHub
parent 61ada47820
commit 14f5a3e0f1
34 changed files with 355 additions and 1295 deletions

View File

@@ -12,7 +12,6 @@ using Microsoft.Kusto.ServiceLayer.Admin.Contracts;
using Microsoft.Kusto.ServiceLayer.Connection; using Microsoft.Kusto.ServiceLayer.Connection;
using Microsoft.Kusto.ServiceLayer.DataSource; using Microsoft.Kusto.ServiceLayer.DataSource;
using Microsoft.Kusto.ServiceLayer.DataSource.Metadata; using Microsoft.Kusto.ServiceLayer.DataSource.Metadata;
using Microsoft.Kusto.ServiceLayer.Hosting;
using Microsoft.Kusto.ServiceLayer.Utility; using Microsoft.Kusto.ServiceLayer.Utility;
namespace Microsoft.Kusto.ServiceLayer.Admin namespace Microsoft.Kusto.ServiceLayer.Admin
@@ -26,13 +25,6 @@ namespace Microsoft.Kusto.ServiceLayer.Admin
private static ConnectionService connectionService = null; private static ConnectionService connectionService = null;
/// <summary>
/// Default, parameterless constructor.
/// </summary>
internal AdminService()
{
}
/// <summary> /// <summary>
/// Internal for testing purposes only /// Internal for testing purposes only
/// </summary> /// </summary>
@@ -72,7 +64,7 @@ namespace Microsoft.Kusto.ServiceLayer.Admin
/// <summary> /// <summary>
/// Handle get database info request /// Handle get database info request
/// </summary> /// </summary>
internal static async Task HandleGetDatabaseInfoRequest( internal async Task HandleGetDatabaseInfoRequest(
GetDatabaseInfoParams databaseParams, GetDatabaseInfoParams databaseParams,
RequestContext<GetDatabaseInfoResponse> requestContext) RequestContext<GetDatabaseInfoResponse> requestContext)
{ {
@@ -114,18 +106,18 @@ namespace Microsoft.Kusto.ServiceLayer.Admin
/// </summary> /// </summary>
/// <param name="connInfo"></param> /// <param name="connInfo"></param>
/// <returns></returns> /// <returns></returns>
internal static DatabaseInfo GetDatabaseInfo(ConnectionInfo connInfo) internal DatabaseInfo GetDatabaseInfo(ConnectionInfo connInfo)
{ {
if(!string.IsNullOrEmpty(connInfo.ConnectionDetails.DatabaseName)){ if(!string.IsNullOrEmpty(connInfo.ConnectionDetails.DatabaseName)){
ReliableDataSourceConnection connection; ReliableDataSourceConnection connection;
connInfo.TryGetConnection("Default", out connection); connInfo.TryGetConnection("Default", out connection);
IDataSource dataSource = connection.GetUnderlyingConnection(); IDataSource dataSource = connection.GetUnderlyingConnection();
DataSourceObjectMetadata objectMetadata = DataSourceFactory.CreateClusterMetadata(connInfo.ConnectionDetails.ServerName); DataSourceObjectMetadata objectMetadata = MetadataFactory.CreateClusterMetadata(connInfo.ConnectionDetails.ServerName);
List<DataSourceObjectMetadata> metadata = dataSource.GetChildObjects(objectMetadata, true).ToList(); List<DataSourceObjectMetadata> metadata = dataSource.GetChildObjects(objectMetadata, true).ToList();
var databaseMetadata = metadata.Where(o => o.Name == connInfo.ConnectionDetails.DatabaseName); var databaseMetadata = metadata.Where(o => o.Name == connInfo.ConnectionDetails.DatabaseName);
List<DatabaseInfo> databaseInfo = DataSourceFactory.ConvertToDatabaseInfo(databaseMetadata); List<DatabaseInfo> databaseInfo = MetadataFactory.ConvertToDatabaseInfo(databaseMetadata);
return databaseInfo.ElementAtOrDefault(0); return databaseInfo.ElementAtOrDefault(0);
} }

View File

@@ -38,18 +38,13 @@ namespace Microsoft.Kusto.ServiceLayer.Connection
/// <summary> /// <summary>
/// Singleton service instance /// Singleton service instance
/// </summary> /// </summary>
private static readonly Lazy<ConnectionService> instance private static readonly Lazy<ConnectionService> _instance
= new Lazy<ConnectionService>(() => new ConnectionService()); = new Lazy<ConnectionService>(() => new ConnectionService());
/// <summary> /// <summary>
/// Gets the singleton service instance /// Gets the singleton service instance
/// </summary> /// </summary>
public static ConnectionService Instance => instance.Value; public static ConnectionService Instance => _instance.Value;
/// <summary>
/// The SQL connection factory object
/// </summary>
private IDataSourceConnectionFactory connectionFactory;
private DatabaseLocksManager lockedDatabaseManager; private DatabaseLocksManager lockedDatabaseManager;
@@ -113,9 +108,6 @@ namespace Microsoft.Kusto.ServiceLayer.Connection
/// </summary> /// </summary>
public ConnectionService() public ConnectionService()
{ {
var defaultQueue = new ConnectedBindingQueue(needsMetadata: false);
connectedQueues.AddOrUpdate("Default", defaultQueue, (key, old) => defaultQueue);
this.LockedDatabaseManager.ConnectionService = this;
} }
/// <summary> /// <summary>
@@ -181,25 +173,7 @@ namespace Microsoft.Kusto.ServiceLayer.Connection
/// <summary> /// <summary>
/// Gets the SQL connection factory instance /// Gets the SQL connection factory instance
/// </summary> /// </summary>
public IDataSourceConnectionFactory ConnectionFactory private IDataSourceConnectionFactory _dataSourceConnectionFactory;
{
get
{
if (this.connectionFactory == null)
{
this.connectionFactory = new DataSourceConnectionFactory();
}
return this.connectionFactory;
}
internal set { this.connectionFactory = value; }
}
/// <summary>
/// Test constructor that injects dependency interfaces
/// </summary>
/// <param name="testFactory"></param>
public ConnectionService(IDataSourceConnectionFactory testFactory) => this.connectionFactory = testFactory;
// Attempts to link a URI to an actively used connection for this URI // 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); 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; bool connectionChanged = false;
if (!OwnerToConnectionMap.TryGetValue(connectionParams.OwnerUri, out connectionInfo)) 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)) else if (IsConnectionChanged(connectionParams, connectionInfo))
{ {
@@ -267,7 +241,7 @@ namespace Microsoft.Kusto.ServiceLayer.Connection
if (connectionChanged) 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 // Try to open a connection with the given ConnectParams
@@ -362,11 +336,6 @@ namespace Microsoft.Kusto.ServiceLayer.Connection
return false; 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) private void DisconnectExistingConnectionIfNeeded(ConnectParams connectionParams, ConnectionInfo connectionInfo, bool disconnectAll)
{ {
// Resolve if it is an existing connection // Resolve if it is an existing connection
@@ -425,7 +394,7 @@ namespace Microsoft.Kusto.ServiceLayer.Connection
var reliableConnection = connection as ReliableDataSourceConnection; var reliableConnection = connection as ReliableDataSourceConnection;
IDataSource dataSource = reliableConnection.GetUnderlyingConnection(); IDataSource dataSource = reliableConnection.GetUnderlyingConnection();
DataSourceObjectMetadata clusterMetadata = DataSourceFactory.CreateClusterMetadata(connectionInfo.ConnectionDetails.ServerName); DataSourceObjectMetadata clusterMetadata = MetadataFactory.CreateClusterMetadata(connectionInfo.ConnectionDetails.ServerName);
DiagnosticsInfo clusterDiagnostics = dataSource.GetDiagnostics(clusterMetadata); DiagnosticsInfo clusterDiagnostics = dataSource.GetDiagnostics(clusterMetadata);
ReliableConnectionHelper.ServerInfo serverInfo = DataSourceFactory.ConvertToServerinfoFormat(DataSourceType.Kusto, clusterDiagnostics); ReliableConnectionHelper.ServerInfo serverInfo = DataSourceFactory.ConvertToServerinfoFormat(DataSourceType.Kusto, clusterDiagnostics);
@@ -801,14 +770,14 @@ namespace Microsoft.Kusto.ServiceLayer.Connection
ConnectionDetails connectionDetails = info.ConnectionDetails.Clone(); ConnectionDetails connectionDetails = info.ConnectionDetails.Clone();
IDataSource dataSource = OpenDataSourceConnection(info); IDataSource dataSource = OpenDataSourceConnection(info);
DataSourceObjectMetadata objectMetadata = DataSourceFactory.CreateClusterMetadata(info.ConnectionDetails.ServerName); DataSourceObjectMetadata objectMetadata = MetadataFactory.CreateClusterMetadata(info.ConnectionDetails.ServerName);
ListDatabasesResponse response = new ListDatabasesResponse(); ListDatabasesResponse response = new ListDatabasesResponse();
// Mainly used by "manage" dashboard // Mainly used by "manage" dashboard
if(listDatabasesParams.IncludeDetails.HasTrue()){ if(listDatabasesParams.IncludeDetails.HasTrue()){
IEnumerable<DataSourceObjectMetadata> databaseMetadataInfo = dataSource.GetChildObjects(objectMetadata, true); IEnumerable<DataSourceObjectMetadata> databaseMetadataInfo = dataSource.GetChildObjects(objectMetadata, true);
List<DatabaseInfo> metadata = DataSourceFactory.ConvertToDatabaseInfo(databaseMetadataInfo); List<DatabaseInfo> metadata = MetadataFactory.ConvertToDatabaseInfo(databaseMetadataInfo);
response.Databases = metadata.ToArray(); response.Databases = metadata.ToArray();
return response; return response;
@@ -819,9 +788,14 @@ namespace Microsoft.Kusto.ServiceLayer.Connection
return response; 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 // Register request and event handlers with the Service Host
serviceHost.SetRequestHandler(ConnectionRequest.Type, HandleConnectRequest); serviceHost.SetRequestHandler(ConnectionRequest.Type, HandleConnectRequest);
@@ -1429,12 +1403,12 @@ namespace Microsoft.Kusto.ServiceLayer.Connection
/// <param name="connInfo">The connection info to connect with</param> /// <param name="connInfo">The connection info to connect with</param>
/// <param name="featureName">A plaintext string that will be included in the application name for the connection</param> /// <param name="featureName">A plaintext string that will be included in the application name for the connection</param>
/// <returns>A SqlConnection created with the given connection info</returns> /// <returns>A SqlConnection created with the given connection info</returns>
internal static IDataSource OpenDataSourceConnection(ConnectionInfo connInfo, string featureName = null) private IDataSource OpenDataSourceConnection(ConnectionInfo connInfo, string featureName = null)
{ {
try try
{ {
// generate connection string // 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. // 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);

View File

@@ -3,8 +3,8 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information. // Licensed under the MIT license. See LICENSE file in the project root for full license information.
// //
using System.Data.Common; using System.Composition;
using Microsoft.Kusto.ServiceLayer.Connection; using Microsoft.Kusto.ServiceLayer.DataSource;
using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection; using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection;
namespace Microsoft.Kusto.ServiceLayer.Connection 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 /// The purpose of the factory is to make it easier to mock out the database
/// in 'offline' unit test scenarios. /// in 'offline' unit test scenarios.
/// </summary> /// </summary>
[Export(typeof(IDataSourceConnectionFactory))]
public class DataSourceConnectionFactory : IDataSourceConnectionFactory public class DataSourceConnectionFactory : IDataSourceConnectionFactory
{ {
/// <summary> /// <summary>

View File

@@ -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)
{
}
}
}

View File

@@ -3,7 +3,6 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information. // Licensed under the MIT license. See LICENSE file in the project root for full license information.
// //
using Microsoft.Kusto.ServiceLayer.LanguageServices;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
@@ -12,100 +11,23 @@ namespace Microsoft.Kusto.ServiceLayer.Connection
{ {
public class DatabaseLocksManager: IDisposable public class DatabaseLocksManager: IDisposable
{ {
internal DatabaseLocksManager(int waitToGetFullAccess) private static readonly DatabaseLocksManager _instance = new DatabaseLocksManager();
{
this.waitToGetFullAccess = waitToGetFullAccess;
}
private static DatabaseLocksManager instance = new DatabaseLocksManager(DefaultWaitToGetFullAccess);
public static DatabaseLocksManager Instance public static DatabaseLocksManager Instance
{ {
get get
{ {
return instance; return _instance;
} }
} }
public ConnectionService ConnectionService { get; set; } public ConnectionService ConnectionService { get; set; }
private Dictionary<string, ManualResetEvent> databaseAccessEvents = new Dictionary<string, ManualResetEvent>(); private readonly Dictionary<string, ManualResetEvent> _databaseAccessEvents = new Dictionary<string, ManualResetEvent>();
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()}";
}
public void Dispose() public void Dispose()
{ {
foreach (var resetEvent in databaseAccessEvents) foreach (var resetEvent in _databaseAccessEvents)
{ {
resetEvent.Value.Dispose(); resetEvent.Value.Dispose();
} }

View File

@@ -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
{
/// <summary>
/// Any operation that needs full access to databas should implement this interface.
/// Make sure to call GainAccessToDatabase before the operation and ReleaseAccessToDatabase after
/// </summary>
public interface IFeatureWithFullDbAccess
{
/// <summary>
/// Database Lock Manager
/// </summary>
DatabaseLocksManager LockedDatabaseManager { get; set; }
/// <summary>
/// Makes sure the feature has fill access to the database
/// </summary>
bool GainAccessToDatabase();
/// <summary>
/// Release the access to db
/// </summary>
bool ReleaseAccessToDatabase();
/// <summary>
/// Server name
/// </summary>
string ServerName { get; }
/// <summary>
/// Database name
/// </summary>
string DatabaseName { get; }
}
}

View File

@@ -2,13 +2,11 @@
// Copyright (c) Microsoft. All Rights Reserved. // Copyright (c) Microsoft. All Rights Reserved.
// </copyright> // </copyright>
using System; using System;
using System.Text.RegularExpressions;
using System.Linq;
using System.Data; using System.Data;
namespace Microsoft.Kusto.ServiceLayer.DataSource namespace Microsoft.Kusto.ServiceLayer.DataSource
{ {
class DataReaderWrapper:IDataReader public class DataReaderWrapper : IDataReader
{ {
private readonly IDataReader _inner ; private readonly IDataReader _inner ;
public DataReaderWrapper(IDataReader inner) public DataReaderWrapper(IDataReader inner)

View File

@@ -1,22 +1,15 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Microsoft.Kusto.ServiceLayer.Utility; using Microsoft.Kusto.ServiceLayer.Utility;
using Microsoft.Kusto.ServiceLayer.Admin.Contracts;
using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection; using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection;
using Microsoft.Kusto.ServiceLayer.Metadata.Contracts;
using Microsoft.Kusto.ServiceLayer.DataSource.DataSourceIntellisense; using Microsoft.Kusto.ServiceLayer.DataSource.DataSourceIntellisense;
using Microsoft.Kusto.ServiceLayer.DataSource.Metadata;
using Microsoft.Kusto.ServiceLayer.LanguageServices.Contracts; using Microsoft.Kusto.ServiceLayer.LanguageServices.Contracts;
using Microsoft.Kusto.ServiceLayer.Workspace.Contracts; using Microsoft.Kusto.ServiceLayer.Workspace.Contracts;
using Microsoft.Kusto.ServiceLayer.LanguageServices.Completion; using Microsoft.Kusto.ServiceLayer.LanguageServices.Completion;
namespace Microsoft.Kusto.ServiceLayer.DataSource namespace Microsoft.Kusto.ServiceLayer.DataSource
{ {
/// <summary> public class DataSourceFactory
/// Data source factory.
/// </summary>
public static class DataSourceFactory
{ {
public static IDataSource Create(DataSourceType dataSourceType, string connectionString, string azureAccountToken) public static IDataSource Create(DataSourceType dataSourceType, string connectionString, string azureAccountToken)
{ {
@@ -26,57 +19,16 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource
switch (dataSourceType) switch (dataSourceType)
{ {
case DataSourceType.Kusto: case DataSourceType.Kusto:
{ {
return new KustoDataSource(connectionString, azureAccountToken); return new KustoDataSource(connectionString, azureAccountToken);
} }
default: 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<ArgumentException>(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. // Gets default keywords for intellisense when there is no connection.
public static CompletionItem[] GetDefaultAutoComplete(DataSourceType dataSourceType, ScriptDocumentInfo scriptDocumentInfo, Position textDocumentPosition){ public static CompletionItem[] GetDefaultAutoComplete(DataSourceType dataSourceType, ScriptDocumentInfo scriptDocumentInfo, Position textDocumentPosition){
switch (dataSourceType) 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<DatabaseInfo> ConvertToDatabaseInfo(IEnumerable<DataSourceObjectMetadata> clusterDBDetails)
{
var databaseDetails = new List<DatabaseInfo>();
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<ObjectMetadata> ConvertToObjectMetadata(IEnumerable<DataSourceObjectMetadata> dbChildDetails)
{
var databaseChildDetails = new List<ObjectMetadata>();
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) public static ReliableConnectionHelper.ServerInfo ConvertToServerinfoFormat(DataSourceType dataSourceType, DiagnosticsInfo clusterDiagnostics)
{ {
switch (dataSourceType) switch (dataSourceType)

View File

@@ -22,9 +22,8 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource.DataSourceIntellisense
/// <summary> /// <summary>
/// Kusto specific class for intellisense helper functions. /// Kusto specific class for intellisense helper functions.
/// </summary> /// </summary>
public static class KustoIntellisenseHelper public class KustoIntellisenseHelper
{ {
public class ShowDatabasesResult public class ShowDatabasesResult
{ {
public string DatabaseName; public string DatabaseName;
@@ -162,7 +161,7 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource.DataSourceIntellisense
/// <summary> /// <summary>
/// Loads the schema for the specified databasea into a a <see cref="DatabaseSymbol"/>. /// Loads the schema for the specified databasea into a a <see cref="DatabaseSymbol"/>.
/// </summary> /// </summary>
public static async Task<DatabaseSymbol> LoadDatabaseAsync(IDataSource dataSource, string databaseName, bool throwOnError = false) private static async Task<DatabaseSymbol> LoadDatabaseAsync(IDataSource dataSource, string databaseName, bool throwOnError = false)
{ {
var members = new List<Symbol>(); var members = new List<Symbol>();
CancellationTokenSource source = new CancellationTokenSource(); CancellationTokenSource source = new CancellationTokenSource();

View File

@@ -88,9 +88,12 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource
ClusterName = GetClusterName(connectionString); ClusterName = GetClusterName(connectionString);
DatabaseName = GetDatabaseName(connectionString); DatabaseName = GetDatabaseName(connectionString);
UserToken = azureAccountToken; 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 // Check if a connection can be made
ValidationUtils.IsTrue<ArgumentException>(Exists().Result, $"Unable to connect. ClusterName = {ClusterName}, DatabaseName = {DatabaseName}"); ValidationUtils.IsTrue<ArgumentException>(Exists().Result,
$"Unable to connect. ClusterName = {ClusterName}, DatabaseName = {DatabaseName}");
} }
/// <summary> /// <summary>
@@ -736,7 +739,7 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource
if (tableInfos.Any(x => !string.IsNullOrWhiteSpace(x.Folder))) if (tableInfos.Any(x => !string.IsNullOrWhiteSpace(x.Folder)))
{ {
// create Table folder to hold functions tables // 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<FolderMetadata> {tableFolder}); _folderMetadata.AddRange(rootTableFolderKey.ToString(), new List<FolderMetadata> {tableFolder});
rootTableFolderKey.Append($".{tableFolder.Name}"); rootTableFolderKey.Append($".{tableFolder.Name}");
@@ -782,7 +785,7 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource
continue; continue;
} }
var folder = DataSourceFactory.CreateFolderMetadata(objectMetadata, rootTableFolderKey, columnGroup.Key); var folder = MetadataFactory.CreateFolderMetadata(objectMetadata, rootTableFolderKey, columnGroup.Key);
tableFolders.Add(folder); tableFolders.Add(folder);
} }
@@ -800,7 +803,7 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource
// create Functions folder to hold functions folders // create Functions folder to hold functions folders
var rootFunctionFolderKey = $"{databaseMetadata.Urn}"; var rootFunctionFolderKey = $"{databaseMetadata.Urn}";
var rootFunctionFolder = DataSourceFactory.CreateFolderMetadata(databaseMetadata, rootFunctionFolderKey, "Functions"); var rootFunctionFolder = MetadataFactory.CreateFolderMetadata(databaseMetadata, rootFunctionFolderKey, "Functions");
_folderMetadata.AddRange(rootFunctionFolderKey, new List<FolderMetadata> {rootFunctionFolder}); _folderMetadata.AddRange(rootFunctionFolderKey, new List<FolderMetadata> {rootFunctionFolder});
// create each folder to hold functions // create each folder to hold functions
@@ -834,13 +837,13 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource
var topFolder = subFolders.First(); var topFolder = subFolders.First();
var folderKey = functionFolder.Urn; var folderKey = functionFolder.Urn;
var folder = DataSourceFactory.CreateFolderMetadata(databaseMetadata, folderKey, topFolder); var folder = MetadataFactory.CreateFolderMetadata(databaseMetadata, folderKey, topFolder);
functionFolders.SafeAdd(folderKey, folder); functionFolders.SafeAdd(folderKey, folder);
for (int i = 1; i < subFolders.Length; i++) for (int i = 1; i < subFolders.Length; i++)
{ {
folderKey = $"{folderKey}.{subFolders[i - 1]}"; 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); functionFolders.SafeAdd(folderKey, subFolder);
} }
} }

View File

@@ -4,19 +4,18 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource
{ {
internal class KustoResultsReader : DataReaderWrapper internal class KustoResultsReader : DataReaderWrapper
{ {
public KustoResultsReader(IDataReader reader) public KustoResultsReader(IDataReader reader) : base(reader)
: base(reader)
{ {
} }
/// <summary> /// <summary>
/// Kusto returns 3 results tables - QueryResults, QueryProperties, QueryStatus. When returning query results /// 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 /// we want the caller to only read the first table. We override the NextResult function here to only return one table
/// from the IDataReader. /// from the IDataReader.
/// </summary> /// </summary>
/*public override bool NextResult() /*public override bool NextResult()
{ {
return false; return false;
}*/ }*/
} }
} }

View File

@@ -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<ArgumentException>(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}"
};
}
/// <summary>
/// Converts database details shown on cluster manage dashboard to DatabaseInfo type. Add DataSourceType as param if required to show different properties
/// </summary>
/// <param name="clusterDBDetails"></param>
/// <returns></returns>
public static List<DatabaseInfo> ConvertToDatabaseInfo(IEnumerable<DataSourceObjectMetadata> clusterDBDetails)
{
var databaseDetails = new List<DatabaseInfo>();
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;
}
/// <summary>
/// Converts tables details shown on database manage dashboard to ObjectMetadata type. Add DataSourceType as param if required to show different properties
/// </summary>
/// <param name="dbChildDetails"></param>
/// <returns></returns>
public static List<ObjectMetadata> ConvertToObjectMetadata(IEnumerable<DataSourceObjectMetadata> dbChildDetails)
{
var databaseChildDetails = new List<ObjectMetadata>();
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;
}
}
}

View File

@@ -21,16 +21,8 @@
// ======================================================================================= // =======================================================================================
using System; using System;
using System.Collections.Generic;
using System.Data;
using System.Collections;
using System.Data.Common;
using System.Data.SqlClient; using System.Data.SqlClient;
using System.Diagnostics;
using System.Globalization;
using System.Text;
using System.Threading; using System.Threading;
using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.SqlTools.Utility; using Microsoft.SqlTools.Utility;
using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection; 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 /// Provides a reliable way of opening connections to and executing commands
/// taking into account potential network unreliability and a requirement for connection retry. /// taking into account potential network unreliability and a requirement for connection retry.
/// </summary> /// </summary>
public sealed partial class ReliableDataSourceConnection : IDisposable public sealed class ReliableDataSourceConnection : IDisposable
{ {
private IDataSource _dataSource; private IDataSource _dataSource;
private readonly RetryPolicy _connectionRetryPolicy; private readonly RetryPolicy _connectionRetryPolicy;
private RetryPolicy _commandRetryPolicy; private RetryPolicy _commandRetryPolicy;
private Guid _azureSessionId = Guid.NewGuid(); private readonly Guid _azureSessionId = Guid.NewGuid();
private string _connectionString; private readonly string _connectionString;
private string _azureAccountToken; private readonly string _azureAccountToken;
/// <summary> /// <summary>
/// Initializes a new instance of the ReliableKustoClient class with a given connection string /// Initializes a new instance of the ReliableKustoClient class with a given connection string

View File

@@ -11,7 +11,8 @@ using Microsoft.SqlTools.Hosting.Protocol;
using Microsoft.Kusto.ServiceLayer.Admin; using Microsoft.Kusto.ServiceLayer.Admin;
using Microsoft.Kusto.ServiceLayer.Metadata; using Microsoft.Kusto.ServiceLayer.Metadata;
using Microsoft.Kusto.ServiceLayer.Connection; 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.LanguageServices;
using Microsoft.Kusto.ServiceLayer.QueryExecution; using Microsoft.Kusto.ServiceLayer.QueryExecution;
using Microsoft.Kusto.ServiceLayer.Scripting; using Microsoft.Kusto.ServiceLayer.Scripting;
@@ -66,6 +67,10 @@ namespace Microsoft.Kusto.ServiceLayer
ExtensionServiceProvider serviceProvider = ExtensionServiceProvider.CreateDefaultServiceProvider(inclusionList); ExtensionServiceProvider serviceProvider = ExtensionServiceProvider.CreateDefaultServiceProvider(inclusionList);
serviceProvider.RegisterSingleService(sqlToolsContext); serviceProvider.RegisterSingleService(sqlToolsContext);
serviceProvider.RegisterSingleService(serviceHost); serviceProvider.RegisterSingleService(serviceHost);
var scripter = serviceProvider.GetService<IScripter>();
var dataSourceConnectionFactory = serviceProvider.GetService<IDataSourceConnectionFactory>();
var connectedBindingQueue = serviceProvider.GetService<IConnectedBindingQueue>();
// Initialize and register singleton services so they're accessible for any MEF service. In the future, these // 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 // 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<SqlToolsSettings>.Instance.InitializeService(serviceHost); WorkspaceService<SqlToolsSettings>.Instance.InitializeService(serviceHost);
serviceProvider.RegisterSingleService(WorkspaceService<SqlToolsSettings>.Instance); serviceProvider.RegisterSingleService(WorkspaceService<SqlToolsSettings>.Instance);
LanguageService.Instance.InitializeService(serviceHost, sqlToolsContext); LanguageService.Instance.InitializeService(serviceHost, connectedBindingQueue);
serviceProvider.RegisterSingleService(LanguageService.Instance); serviceProvider.RegisterSingleService(LanguageService.Instance);
ConnectionService.Instance.InitializeService(serviceHost); ConnectionService.Instance.InitializeService(serviceHost, dataSourceConnectionFactory, connectedBindingQueue);
serviceProvider.RegisterSingleService(ConnectionService.Instance); serviceProvider.RegisterSingleService(ConnectionService.Instance);
CredentialService.Instance.InitializeService(serviceHost); CredentialService.Instance.InitializeService(serviceHost);
@@ -85,7 +90,7 @@ namespace Microsoft.Kusto.ServiceLayer
QueryExecutionService.Instance.InitializeService(serviceHost); QueryExecutionService.Instance.InitializeService(serviceHost);
serviceProvider.RegisterSingleService(QueryExecutionService.Instance); serviceProvider.RegisterSingleService(QueryExecutionService.Instance);
ScriptingService.Instance.InitializeService(serviceHost); ScriptingService.Instance.InitializeService(serviceHost, scripter);
serviceProvider.RegisterSingleService(ScriptingService.Instance); serviceProvider.RegisterSingleService(ScriptingService.Instance);
AdminService.Instance.InitializeService(serviceHost); AdminService.Instance.InitializeService(serviceHost);
@@ -93,7 +98,7 @@ namespace Microsoft.Kusto.ServiceLayer
MetadataService.Instance.InitializeService(serviceHost); MetadataService.Instance.InitializeService(serviceHost);
serviceProvider.RegisterSingleService(MetadataService.Instance); serviceProvider.RegisterSingleService(MetadataService.Instance);
InitializeHostedServices(serviceProvider, serviceHost); InitializeHostedServices(serviceProvider, serviceHost);
serviceHost.ServiceProvider = serviceProvider; serviceHost.ServiceProvider = serviceProvider;

View File

@@ -2,6 +2,7 @@
// Copyright (c) Microsoft. All rights reserved. // Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information. // Licensed under the MIT license. See LICENSE file in the project root for full license information.
// //
using Range = Microsoft.Kusto.ServiceLayer.Workspace.Contracts.Range; using Range = Microsoft.Kusto.ServiceLayer.Workspace.Contracts.Range;
using Microsoft.Kusto.ServiceLayer.LanguageServices.Contracts; using Microsoft.Kusto.ServiceLayer.LanguageServices.Contracts;
using Microsoft.Kusto.ServiceLayer.Workspace.Contracts; using Microsoft.Kusto.ServiceLayer.Workspace.Contracts;
@@ -11,7 +12,7 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices
/// <summary> /// <summary>
/// All the conversion of intellisense info to vscode format is done in this class. /// All the conversion of intellisense info to vscode format is done in this class.
/// </summary> /// </summary>
public static class AutoCompleteHelper public class AutoCompleteHelper
{ {
/// <summary> /// <summary>
/// Create a completion item from the default item text since VS Code expects CompletionItems /// Create a completion item from the default item text since VS Code expects CompletionItems

View File

@@ -4,8 +4,7 @@
// //
using System; using System;
using System.Data.SqlClient; using System.Composition;
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.SmoMetadataProvider; using Microsoft.SqlServer.Management.SmoMetadataProvider;
using Microsoft.SqlServer.Management.SqlParser.Binder; using Microsoft.SqlServer.Management.SqlParser.Binder;
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider; 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.SqlContext;
using Microsoft.Kusto.ServiceLayer.Workspace; using Microsoft.Kusto.ServiceLayer.Workspace;
using Microsoft.Kusto.ServiceLayer.DataSource; using Microsoft.Kusto.ServiceLayer.DataSource;
using System.Threading;
namespace Microsoft.Kusto.ServiceLayer.LanguageServices 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<IBindingContext, CancellationToken, object> bindOperation,
Func<IBindingContext, object> timeoutOperation = null,
Func<Exception, object> errorHandler = null,
int? bindingTimeout = null,
int? waitForLockTimeout = null);
}
public class SqlConnectionOpener
{
/// <summary>
/// Virtual method used to support mocking and testing
/// </summary>
public virtual ServerConnection OpenServerConnection(ConnectionInfo connInfo, string featureName)
{
return ConnectionService.OpenServerConnection(connInfo, featureName);
}
}
/// <summary> /// <summary>
/// ConnectedBindingQueue class for processing online binding requests /// ConnectedBindingQueue class for processing online binding requests
/// </summary> /// </summary>
[Export(typeof(IConnectedBindingQueue))]
public class ConnectedBindingQueue : BindingQueue<ConnectedBindingContext>, IConnectedBindingQueue public class ConnectedBindingQueue : BindingQueue<ConnectedBindingContext>, IConnectedBindingQueue
{ {
internal const int DefaultBindingTimeout = 500; internal const int DefaultBindingTimeout = 500;
private readonly ISqlConnectionOpener _connectionOpener;
internal const int DefaultMinimumConnectionTimeout = 30;
/// <summary>
/// flag determing if the connection queue requires online metadata objects
/// it's much cheaper to not construct these objects if not needed
/// </summary>
private bool needsMetadata;
private SqlConnectionOpener connectionOpener;
/// <summary> /// <summary>
/// Gets the current settings /// Gets the current settings
/// </summary> /// </summary>
internal SqlToolsSettings CurrentSettings private SqlToolsSettings CurrentSettings
{ {
get { return WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings; } get { return WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings; }
} }
public ConnectedBindingQueue() [ImportingConstructor]
: this(true) public ConnectedBindingQueue(ISqlConnectionOpener sqlConnectionOpener)
{
}
public ConnectedBindingQueue(bool needsMetadata)
{ {
this.needsMetadata = needsMetadata; _connectionOpener = sqlConnectionOpener;
this.connectionOpener = new SqlConnectionOpener();
}
// For testing purposes only
internal void SetConnectionOpener(SqlConnectionOpener opener)
{
this.connectionOpener = opener;
} }
/// <summary> /// <summary>
/// Generate a unique key based on the ConnectionInfo object /// Generate a unique key based on the ConnectionInfo object
/// </summary> /// </summary>
/// <param name="connInfo"></param> /// <param name="details"></param>
internal static string GetConnectionContextKey(ConnectionDetails details) internal static string GetConnectionContextKey(ConnectionDetails details)
{ {
string key = string.Format("{0}_{1}_{2}_{3}", string key = string.Format("{0}_{1}_{2}_{3}",
@@ -114,7 +68,8 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices
/// <summary> /// <summary>
/// Generate a unique key based on the ConnectionInfo object /// Generate a unique key based on the ConnectionInfo object
/// </summary> /// </summary>
/// <param name="connInfo"></param> /// <param name="serverName"></param>
/// <param name="databaseName"></param>
private string GetConnectionContextKey(string serverName, string databaseName) private string GetConnectionContextKey(string serverName, string databaseName)
{ {
return string.Format("{0}_{1}", 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); string connectionKey = GetConnectionContextKey(connInfo.ConnectionDetails);
if (BindingContextExists(connectionKey)) if (BindingContextExists(connectionKey))
@@ -168,9 +123,11 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices
/// <summary> /// <summary>
/// Use a ConnectionInfo item to create a connected binding context /// Use a ConnectionInfo item to create a connected binding context
/// </summary> /// </summary>
/// <param name="connInfo">Connection info used to create binding context</param> /// <param name="connInfo">Connection info used to create binding context</param>
/// <param name="overwrite">Overwrite existing context</param> /// <param name="needMetadata"></param>
public virtual string AddConnectionContext(ConnectionInfo connInfo, string featureName = null, bool overwrite = false) /// <param name="featureName"></param>
/// <param name="overwrite">Overwrite existing context</param>
public string AddConnectionContext(ConnectionInfo connInfo, bool needMetadata, string featureName = null, bool overwrite = false)
{ {
if (connInfo == null) if (connInfo == null)
{ {
@@ -191,7 +148,7 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices
return connectionKey; return connectionKey;
} }
} }
IBindingContext bindingContext = this.GetOrCreateBindingContext(connectionKey); IBindingContext bindingContext = GetOrCreateBindingContext(connectionKey);
if (bindingContext.BindingLock.WaitOne()) if (bindingContext.BindingLock.WaitOne())
{ {
@@ -200,12 +157,12 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices
bindingContext.BindingLock.Reset(); bindingContext.BindingLock.Reset();
// populate the binding context to work with the SMO metadata provider // 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); 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 (this.needsMetadata) if (needMetadata)
{ {
bindingContext.SmoMetadataProvider = SmoMetadataProvider.CreateConnectedProvider(bindingContext.ServerConnection); bindingContext.SmoMetadataProvider = SmoMetadataProvider.CreateConnectedProvider(bindingContext.ServerConnection);
bindingContext.MetadataDisplayInfoProvider = new MetadataDisplayInfoProvider(); bindingContext.MetadataDisplayInfoProvider = new MetadataDisplayInfoProvider();

View File

@@ -0,0 +1,26 @@
using System;
using System.Threading;
using Microsoft.Kusto.ServiceLayer.Connection;
namespace Microsoft.Kusto.ServiceLayer.LanguageServices
{
public interface IConnectedBindingQueue
{
event BindingQueue<ConnectedBindingContext>.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<IBindingContext, CancellationToken, object> bindOperation,
Func<IBindingContext, object> timeoutOperation = null,
Func<Exception, object> errorHandler = null,
int? bindingTimeout = null,
int? waitForLockTimeout = null);
}
}

View File

@@ -0,0 +1,13 @@
using Microsoft.Kusto.ServiceLayer.Connection;
using Microsoft.SqlServer.Management.Common;
namespace Microsoft.Kusto.ServiceLayer.LanguageServices
{
public interface ISqlConnectionOpener
{
/// <summary>
/// Virtual method used to support mocking and testing
/// </summary>
ServerConnection OpenServerConnection(ConnectionInfo connInfo, string featureName);
}
}

View File

@@ -18,10 +18,8 @@ using Microsoft.Kusto.ServiceLayer.DataSource;
using Microsoft.Kusto.ServiceLayer.DataSource.DataSourceIntellisense; using Microsoft.Kusto.ServiceLayer.DataSource.DataSourceIntellisense;
using Microsoft.Kusto.ServiceLayer.Connection; using Microsoft.Kusto.ServiceLayer.Connection;
using Microsoft.Kusto.ServiceLayer.Connection.Contracts; using Microsoft.Kusto.ServiceLayer.Connection.Contracts;
using Microsoft.Kusto.ServiceLayer.Hosting;
using Microsoft.Kusto.ServiceLayer.LanguageServices.Completion; using Microsoft.Kusto.ServiceLayer.LanguageServices.Completion;
using Microsoft.Kusto.ServiceLayer.LanguageServices.Contracts; using Microsoft.Kusto.ServiceLayer.LanguageServices.Contracts;
using SqlToolsContext = Microsoft.SqlTools.ServiceLayer.SqlContext.SqlToolsContext;
using Microsoft.Kusto.ServiceLayer.Utility; using Microsoft.Kusto.ServiceLayer.Utility;
using Microsoft.Kusto.ServiceLayer.Workspace; using Microsoft.Kusto.ServiceLayer.Workspace;
using Microsoft.Kusto.ServiceLayer.Workspace.Contracts; using Microsoft.Kusto.ServiceLayer.Workspace.Contracts;
@@ -82,7 +80,7 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices
private ScriptParseInfo currentCompletionParseInfo; private ScriptParseInfo currentCompletionParseInfo;
private ConnectedBindingQueue bindingQueue = new ConnectedBindingQueue(); private IConnectedBindingQueue _bindingQueue;
private ParseOptions defaultParseOptions = new ParseOptions( private ParseOptions defaultParseOptions = new ParseOptions(
batchSeparator: LanguageService.DefaultBatchSeperator, batchSeparator: LanguageService.DefaultBatchSeperator,
@@ -125,22 +123,6 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices
#region Properties #region Properties
/// <summary>
/// Gets or sets the binding queue instance
/// Internal for testing purposes only
/// </summary>
internal ConnectedBindingQueue BindingQueue
{
get
{
return this.bindingQueue;
}
set
{
this.bindingQueue = value;
}
}
/// <summary> /// <summary>
/// Internal for testing purposes only /// Internal for testing purposes only
/// </summary> /// </summary>
@@ -151,7 +133,7 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices
if (connectionService == null) if (connectionService == null)
{ {
connectionService = ConnectionService.Instance; connectionService = ConnectionService.Instance;
connectionService.RegisterConnectedQueue("LanguageService", bindingQueue); connectionService.RegisterConnectedQueue("LanguageService", _bindingQueue);
} }
return connectionService; return connectionService;
} }
@@ -216,12 +198,6 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices
get { return WorkspaceServiceInstance.Workspace; } get { return WorkspaceServiceInstance.Workspace; }
} }
/// <summary>
/// Gets or sets the current SQL Tools context
/// </summary>
/// <returns></returns>
internal SqlToolsContext Context { get; set; }
#endregion #endregion
#region Public Methods #region Public Methods
@@ -231,23 +207,20 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices
/// </summary> /// </summary>
/// <param name="serviceHost"></param> /// <param name="serviceHost"></param>
/// <param name="context"></param> /// <param name="context"></param>
public void InitializeService(ServiceHost serviceHost, SqlToolsContext context) /// <param name="dataSourceFactory"></param>
/// <param name="connectedBindingQueue"></param>
public void InitializeService(ServiceHost serviceHost, IConnectedBindingQueue connectedBindingQueue)
{ {
_bindingQueue = connectedBindingQueue;
// Register the requests that this service will handle // 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(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(CompletionResolveRequest.Type, HandleCompletionResolveRequest);
serviceHost.SetRequestHandler(HoverRequest.Type, HandleHoverRequest); serviceHost.SetRequestHandler(HoverRequest.Type, HandleHoverRequest);
serviceHost.SetRequestHandler(CompletionRequest.Type, HandleCompletionRequest); serviceHost.SetRequestHandler(CompletionRequest.Type, HandleCompletionRequest);
serviceHost.SetRequestHandler(DefinitionRequest.Type, HandleDefinitionRequest); // Parses "Go to definition" functionality serviceHost.SetRequestHandler(DefinitionRequest.Type, HandleDefinitionRequest); // Parses "Go to definition" functionality
serviceHost.SetRequestHandler(SyntaxParseRequest.Type, HandleSyntaxParseRequest); // Parses syntax errors serviceHost.SetRequestHandler(SyntaxParseRequest.Type, HandleSyntaxParseRequest); // Parses syntax errors
//serviceHost.SetRequestHandler(CompletionExtLoadRequest.Type, HandleCompletionExtLoadRequest);
serviceHost.SetEventHandler(RebuildIntelliSenseNotification.Type, HandleRebuildIntelliSenseNotification); serviceHost.SetEventHandler(RebuildIntelliSenseNotification.Type, HandleRebuildIntelliSenseNotification);
//serviceHost.SetEventHandler(LanguageFlavorChangeNotification.Type, HandleDidChangeLanguageFlavorNotification);
// Register a no-op shutdown task for validation of the shutdown logic // Register a no-op shutdown task for validation of the shutdown logic
serviceHost.RegisterShutdownTask(async (shutdownParams, shutdownRequestContext) => serviceHost.RegisterShutdownTask(async (shutdownParams, shutdownRequestContext) =>
@@ -274,10 +247,7 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices
// Register a callback for when a connection is closed // Register a callback for when a connection is closed
ConnectionServiceInstance.RegisterOnDisconnectTask(RemoveAutoCompleteCacheUriReference); ConnectionServiceInstance.RegisterOnDisconnectTask(RemoveAutoCompleteCacheUriReference);
// Store the SqlToolsContext for future use
Context = context;
} }
#endregion #endregion
@@ -599,7 +569,7 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices
{ {
try try
{ {
this.BindingQueue.AddConnectionContext(connInfo, featureName: "LanguageService", overwrite: true); _bindingQueue.AddConnectionContext(connInfo, true, featureName: "LanguageService", overwrite: true);
RemoveScriptParseInfo(rebuildParams.OwnerUri); RemoveScriptParseInfo(rebuildParams.OwnerUri);
UpdateLanguageServiceOnConnection(connInfo).Wait(); UpdateLanguageServiceOnConnection(connInfo).Wait();
} }
@@ -724,8 +694,8 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices
{ {
try try
{ {
scriptInfo.ConnectionKey = this.BindingQueue.AddConnectionContext(connInfo, "languageService"); scriptInfo.ConnectionKey = _bindingQueue.AddConnectionContext(connInfo, true,"languageService");
scriptInfo.IsConnected = this.BindingQueue.IsBindingContextConnected(scriptInfo.ConnectionKey); scriptInfo.IsConnected = _bindingQueue.IsBindingContextConnected(scriptInfo.ConnectionKey);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -822,13 +792,6 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices
/// <param name="scriptFile"></param> /// <param name="scriptFile"></param>
internal Hover GetHoverItem(TextDocumentPosition textDocumentPosition, ScriptFile scriptFile) 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); ScriptParseInfo scriptParseInfo = GetScriptParseInfo(scriptFile.ClientUri);
ConnectionInfo connInfo; ConnectionInfo connInfo;
ConnectionServiceInstance.TryFindConnection( ConnectionServiceInstance.TryFindConnection(
@@ -841,7 +804,7 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices
{ {
try try
{ {
QueueItem queueItem = this.BindingQueue.QueueBindingOperation( QueueItem queueItem = _bindingQueue.QueueBindingOperation(
key: scriptParseInfo.ConnectionKey, key: scriptParseInfo.ConnectionKey,
bindingTimeout: LanguageService.HoverTimeout, bindingTimeout: LanguageService.HoverTimeout,
bindOperation: (bindingContext, cancelToken) => bindOperation: (bindingContext, cancelToken) =>
@@ -1191,9 +1154,9 @@ namespace Microsoft.Kusto.ServiceLayer.LanguageServices
public void Dispose() public void Dispose()
{ {
if (bindingQueue != null) if (_bindingQueue != null)
{ {
bindingQueue.Dispose(); _bindingQueue.Dispose();
} }
} }
} }

View File

@@ -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);
}
}
}

View File

@@ -4,12 +4,10 @@
// //
using System; using System;
using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.SqlTools.Hosting.Protocol; using Microsoft.SqlTools.Hosting.Protocol;
using Microsoft.Kusto.ServiceLayer.Connection; using Microsoft.Kusto.ServiceLayer.Connection;
using Microsoft.Kusto.ServiceLayer.Hosting;
using Microsoft.Kusto.ServiceLayer.Metadata.Contracts; using Microsoft.Kusto.ServiceLayer.Metadata.Contracts;
using Microsoft.Kusto.ServiceLayer.Utility; using Microsoft.Kusto.ServiceLayer.Utility;
using Microsoft.Kusto.ServiceLayer.DataSource; using Microsoft.Kusto.ServiceLayer.DataSource;
@@ -22,7 +20,7 @@ namespace Microsoft.Kusto.ServiceLayer.Metadata
/// </summary> /// </summary>
public sealed class MetadataService public sealed class MetadataService
{ {
private static readonly Lazy<MetadataService> LazyInstance = new Lazy<MetadataService>(() => new MetadataService()); private static readonly Lazy<MetadataService> LazyInstance = new Lazy<MetadataService>();
public static MetadataService Instance => LazyInstance.Value; public static MetadataService Instance => LazyInstance.Value;
@@ -52,8 +50,8 @@ namespace Microsoft.Kusto.ServiceLayer.Metadata
/// Initializes the Metadata Service instance /// Initializes the Metadata Service instance
/// </summary> /// </summary>
/// <param name="serviceHost"></param> /// <param name="serviceHost"></param>
/// <param name="context"></param> /// <param name="metadataFactory"></param>
public void InitializeService(ServiceHost serviceHost) public void InitializeService(IProtocolEndpoint serviceHost)
{ {
serviceHost.SetRequestHandler(MetadataListRequest.Type, HandleMetadataListRequest); serviceHost.SetRequestHandler(MetadataListRequest.Type, HandleMetadataListRequest);
} }
@@ -79,11 +77,11 @@ namespace Microsoft.Kusto.ServiceLayer.Metadata
connInfo.TryGetConnection("Default", out connection); connInfo.TryGetConnection("Default", out connection);
IDataSource dataSource = connection.GetUnderlyingConnection(); IDataSource dataSource = connection.GetUnderlyingConnection();
DataSourceObjectMetadata objectMetadata = DataSourceFactory.CreateClusterMetadata(connInfo.ConnectionDetails.ServerName); DataSourceObjectMetadata objectMetadata = MetadataFactory.CreateClusterMetadata(connInfo.ConnectionDetails.ServerName);
DataSourceObjectMetadata databaseMetadata = DataSourceFactory.CreateDatabaseMetadata(objectMetadata, connInfo.ConnectionDetails.DatabaseName); DataSourceObjectMetadata databaseMetadata = MetadataFactory.CreateDatabaseMetadata(objectMetadata, connInfo.ConnectionDetails.DatabaseName);
IEnumerable<DataSourceObjectMetadata> databaseChildMetadataInfo = dataSource.GetChildObjects(databaseMetadata, true); IEnumerable<DataSourceObjectMetadata> databaseChildMetadataInfo = dataSource.GetChildObjects(databaseMetadata, true);
metadata = DataSourceFactory.ConvertToObjectMetadata(databaseChildMetadataInfo); metadata = MetadataFactory.ConvertToObjectMetadata(databaseChildMetadataInfo);
} }
await requestContext.SendResult(new MetadataQueryResult await requestContext.SendResult(new MetadataQueryResult

View File

@@ -38,15 +38,15 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer
[Export(typeof(IHostedService))] [Export(typeof(IHostedService))]
public class ObjectExplorerService : HostedService<ObjectExplorerService>, IComposableService, IHostedService, IDisposable public class ObjectExplorerService : HostedService<ObjectExplorerService>, IComposableService, IHostedService, IDisposable
{ {
private readonly IConnectedBindingQueue _connectedBindingQueue;
internal const string uriPrefix = "objectexplorer://"; internal const string uriPrefix = "objectexplorer://";
// Instance of the connection service, used to get the connection info for a given owner URI // Instance of the connection service, used to get the connection info for a given owner URI
private ConnectionService connectionService; private ConnectionService connectionService;
private IProtocolEndpoint serviceHost; private IProtocolEndpoint _serviceHost;
private ConcurrentDictionary<string, ObjectExplorerSession> sessionMap; private ConcurrentDictionary<string, ObjectExplorerSession> sessionMap;
private readonly Lazy<Dictionary<string, HashSet<ChildFactory>>> applicableNodeChildFactories; private readonly Lazy<Dictionary<string, HashSet<ChildFactory>>> applicableNodeChildFactories;
private IMultiServiceProvider serviceProvider; private IMultiServiceProvider serviceProvider;
private ConnectedBindingQueue bindingQueue = new ConnectedBindingQueue(needsMetadata: false);
private string connectionName = "ObjectExplorer"; private string connectionName = "ObjectExplorer";
/// <summary> /// <summary>
@@ -57,34 +57,15 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer
/// <summary> /// <summary>
/// Singleton constructor /// Singleton constructor
/// </summary> /// </summary>
public ObjectExplorerService() [ImportingConstructor]
public ObjectExplorerService(IConnectedBindingQueue connectedBindingQueue)
{ {
_connectedBindingQueue = connectedBindingQueue;
sessionMap = new ConcurrentDictionary<string, ObjectExplorerSession>(); sessionMap = new ConcurrentDictionary<string, ObjectExplorerSession>();
applicableNodeChildFactories = new Lazy<Dictionary<string, HashSet<ChildFactory>>>(() => PopulateFactories()); applicableNodeChildFactories = new Lazy<Dictionary<string, HashSet<ChildFactory>>>(PopulateFactories);
NodePathGenerator.Initialize(); NodePathGenerator.Initialize();
} }
internal ConnectedBindingQueue ConnectedBindingQueue
{
get
{
return bindingQueue;
}
set
{
this.bindingQueue = value;
}
}
/// <summary>
/// Internal for testing only
/// </summary>
internal ObjectExplorerService(ExtensionServiceProvider serviceProvider)
: this()
{
SetServiceProvider(serviceProvider);
}
private Dictionary<string, HashSet<ChildFactory>> ApplicableNodeChildFactories private Dictionary<string, HashSet<ChildFactory>> ApplicableNodeChildFactories
{ {
get get
@@ -116,7 +97,7 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer
connectionService = provider.GetService<ConnectionService>(); connectionService = provider.GetService<ConnectionService>();
try try
{ {
connectionService.RegisterConnectedQueue(connectionName, bindingQueue); connectionService.RegisterConnectedQueue(connectionName, _connectedBindingQueue);
} }
catch(Exception ex) catch(Exception ex)
@@ -132,9 +113,9 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer
public override void InitializeService(IProtocolEndpoint serviceHost) public override void InitializeService(IProtocolEndpoint serviceHost)
{ {
Logger.Write(TraceEventType.Verbose, "ObjectExplorer service initialized"); Logger.Write(TraceEventType.Verbose, "ObjectExplorer service initialized");
this.serviceHost = serviceHost; _serviceHost = serviceHost;
this.ConnectedBindingQueue.OnUnhandledException += OnUnhandledException; _connectedBindingQueue.OnUnhandledException += OnUnhandledException;
// Register handlers for requests // Register handlers for requests
serviceHost.SetRequestHandler(CreateSessionRequest.Type, HandleCreateSessionRequest); serviceHost.SetRequestHandler(CreateSessionRequest.Type, HandleCreateSessionRequest);
@@ -217,7 +198,7 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer
if (!sessionMap.TryGetValue(uri, out session)) if (!sessionMap.TryGetValue(uri, out session))
{ {
Logger.Write(TraceEventType.Verbose, $"Cannot expand object explorer node. Couldn't find session for uri. {uri} "); 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, SessionId = expandParams.SessionId,
NodePath = expandParams.NodePath, NodePath = expandParams.NodePath,
@@ -247,7 +228,7 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer
if (!sessionMap.TryGetValue(uri, out session)) if (!sessionMap.TryGetValue(uri, out session))
{ {
Logger.Write(TraceEventType.Verbose, $"Cannot expand object explorer node. Couldn't find session for uri. {uri} "); 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, SessionId = refreshParams.SessionId,
NodePath = refreshParams.NodePath, NodePath = refreshParams.NodePath,
@@ -319,7 +300,7 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer
{ {
if (session != null && session.ConnectionInfo != null) if (session != null && session.ConnectionInfo != null)
{ {
bindingQueue.RemoveBindigContext(session.ConnectionInfo); _connectedBindingQueue.RemoveBindingContext(session.ConnectionInfo);
} }
} }
connectionService.Disconnect(new DisconnectParams() 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}" 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; return result;
}).ContinueWithOnFaulted(null); }).ContinueWithOnFaulted(null);
@@ -388,7 +369,7 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer
SessionId = uri, SessionId = uri,
ErrorMessage = session.ErrorMessage ErrorMessage = session.ErrorMessage
}; };
await serviceHost.SendEvent(CreateSessionCompleteNotification.Type, response); await _serviceHost.SendEvent(CreateSessionCompleteNotification.Type, response);
return response; return response;
} }
return null; return null;
@@ -426,8 +407,8 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer
try try
{ {
int timeout = (int)TimeSpan.FromSeconds(settings?.ExpandTimeout ?? ObjectExplorerSettings.DefaultExpandTimeout).TotalMilliseconds; int timeout = (int)TimeSpan.FromSeconds(settings?.ExpandTimeout ?? ObjectExplorerSettings.DefaultExpandTimeout).TotalMilliseconds;
QueueItem queueItem = bindingQueue.QueueBindingOperation( QueueItem queueItem = _connectedBindingQueue.QueueBindingOperation(
key: bindingQueue.AddConnectionContext(session.ConnectionInfo, connectionName), key: _connectedBindingQueue.AddConnectionContext(session.ConnectionInfo, false, connectionName, false),
bindingTimeout: timeout, bindingTimeout: timeout,
waitForLockTimeout: timeout, waitForLockTimeout: timeout,
bindOperation: (bindingContext, cancelToken) => bindOperation: (bindingContext, cancelToken) =>
@@ -502,8 +483,8 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer
} }
int timeout = (int)TimeSpan.FromSeconds(settings?.CreateSessionTimeout ?? ObjectExplorerSettings.DefaultCreateSessionTimeout).TotalMilliseconds; int timeout = (int)TimeSpan.FromSeconds(settings?.CreateSessionTimeout ?? ObjectExplorerSettings.DefaultCreateSessionTimeout).TotalMilliseconds;
QueueItem queueItem = bindingQueue.QueueBindingOperation( QueueItem queueItem = _connectedBindingQueue.QueueBindingOperation(
key: bindingQueue.AddConnectionContext(connectionInfo, connectionName), key: _connectedBindingQueue.AddConnectionContext(connectionInfo, false, connectionName),
bindingTimeout: timeout, bindingTimeout: timeout,
waitForLockTimeout: timeout, waitForLockTimeout: timeout,
bindOperation: (bindingContext, cancelToken) => bindOperation: (bindingContext, cancelToken) =>
@@ -564,7 +545,7 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer
ErrorMessage = errorMessage, ErrorMessage = errorMessage,
SessionId = uri SessionId = uri
}; };
await serviceHost.SendEvent(CreateSessionCompleteNotification.Type, result); await _serviceHost.SendEvent(CreateSessionCompleteNotification.Type, result);
} }
internal async Task SendSessionDisconnectedNotification(string uri, bool success, string errorMessage) internal async Task SendSessionDisconnectedNotification(string uri, bool success, string errorMessage)
@@ -576,7 +557,7 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer
ErrorMessage = errorMessage, ErrorMessage = errorMessage,
SessionId = uri SessionId = uri
}; };
await serviceHost.SendEvent(SessionDisconnectedNotification.Type, result); await _serviceHost.SendEvent(SessionDisconnectedNotification.Type, result);
} }
private void RunExpandTask(ObjectExplorerSession session, ExpandParams expandParams, bool forceRefresh = false) private void RunExpandTask(ObjectExplorerSession session, ExpandParams expandParams, bool forceRefresh = false)
@@ -594,7 +575,7 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer
cancellationTokenSource.Cancel(); cancellationTokenSource.Cancel();
ExpandResponse response = CreateExpandResponse(session, expandParams); ExpandResponse response = CreateExpandResponse(session, expandParams);
response.ErrorMessage = result.Exception != null ? result.Exception.Message: $"Failed to expand node: {expandParams.NodePath} in session {session.Uri}"; 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; return result;
}).ContinueWithOnFaulted(null); }).ContinueWithOnFaulted(null);
@@ -636,7 +617,7 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer
} }
else 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() public void Dispose()
{ {
if (bindingQueue != null) if (_connectedBindingQueue != null)
{ {
bindingQueue.OnUnhandledException -= OnUnhandledException; _connectedBindingQueue.OnUnhandledException -= OnUnhandledException;
bindingQueue.Dispose(); _connectedBindingQueue.Dispose();
} }
} }
@@ -787,21 +768,12 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer
internal class ObjectExplorerSession internal class ObjectExplorerSession
{ {
private ConnectionService connectionService; public ObjectExplorerSession(string uri, TreeNode root)
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<string, NodeMapping> cache;
public ObjectExplorerSession(string uri, TreeNode root, IMultiServiceProvider serviceProvider, ConnectionService connectionService)
{ {
Validate.IsNotNullOrEmptyString("uri", uri); Validate.IsNotNullOrEmptyString("uri", uri);
Validate.IsNotNull("root", root); Validate.IsNotNull("root", root);
Uri = uri; Uri = uri;
Root = root; Root = root;
this.serviceProvider = serviceProvider;
this.connectionService = connectionService;
} }
public string Uri { get; private set; } 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) 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); ServerNode rootNode = new ServerNode(response, serviceProvider, serverConnection, dataSource, objectMetadata);
var session = new ObjectExplorerSession(response.OwnerUri, rootNode, serviceProvider, serviceProvider.GetService<ConnectionService>()); var session = new ObjectExplorerSession(response.OwnerUri, rootNode);
if (!isDefaultOrSystemDatabase) 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 // Assuming the databases are in a folder under server node
DataSourceTreeNode databaseNode = new DataSourceTreeNode(dataSource, databaseMetadata) { DataSourceTreeNode databaseNode = new DataSourceTreeNode(dataSource, databaseMetadata) {

View File

@@ -2,13 +2,10 @@
// Copyright (c) Microsoft. All rights reserved. // Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information. // Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System; using System;
using Microsoft.Kusto.ServiceLayer.Hosting;
using Microsoft.SqlTools.ServiceLayer.SqlContext; using Microsoft.SqlTools.ServiceLayer.SqlContext;
using Microsoft.Kusto.ServiceLayer.Utility; using Microsoft.Kusto.ServiceLayer.Utility;
using Microsoft.SqlTools.Utility; using Microsoft.SqlTools.Utility;
using System.IO;
using System.Diagnostics; using System.Diagnostics;
//using SqlToolsContext = Microsoft.SqlTools.ServiceLayer.SqlContext.SqlToolsContext;
namespace Microsoft.Kusto.ServiceLayer namespace Microsoft.Kusto.ServiceLayer
{ {

View File

@@ -16,7 +16,6 @@ using Microsoft.Kusto.ServiceLayer.QueryExecution.DataStorage;
using Microsoft.Kusto.ServiceLayer.SqlContext; using Microsoft.Kusto.ServiceLayer.SqlContext;
using Microsoft.Kusto.ServiceLayer.Workspace; using Microsoft.Kusto.ServiceLayer.Workspace;
using Microsoft.Kusto.ServiceLayer.Workspace.Contracts; using Microsoft.Kusto.ServiceLayer.Workspace.Contracts;
using Microsoft.Kusto.ServiceLayer.Hosting;
using Microsoft.SqlTools.Utility; using Microsoft.SqlTools.Utility;
using System.Diagnostics; using System.Diagnostics;

View File

@@ -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);
}
}

View File

@@ -26,6 +26,7 @@ namespace Microsoft.Kusto.ServiceLayer.Scripting
/// </summary> /// </summary>
public class ScriptAsScriptingOperation : SmoScriptingOperation public class ScriptAsScriptingOperation : SmoScriptingOperation
{ {
private readonly IScripter _scripter;
private static readonly Dictionary<string, SqlServerVersion> scriptCompatibilityMap = LoadScriptCompatibilityMap(); private static readonly Dictionary<string, SqlServerVersion> scriptCompatibilityMap = LoadScriptCompatibilityMap();
/// <summary> /// <summary>
/// Left delimiter for an named object /// Left delimiter for an named object
@@ -37,15 +38,11 @@ namespace Microsoft.Kusto.ServiceLayer.Scripting
/// </summary> /// </summary>
public const char RightDelimiter = ']'; 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 = DataSourceFactory.Create(DataSourceType.Kusto, this.Parameters.ConnectionString,
DataSource = dataSource; azureAccountToken);
} _scripter = scripter;
public ScriptAsScriptingOperation(ScriptingParams parameters, string azureAccountToken) : base(parameters)
{
DataSource = DataSourceFactory.Create(DataSourceType.Kusto, this.Parameters.ConnectionString, azureAccountToken);
} }
internal IDataSource DataSource { get; set; } internal IDataSource DataSource { get; set; }
@@ -150,7 +147,7 @@ namespace Microsoft.Kusto.ServiceLayer.Scripting
// select from table // select from table
if (string.Equals(scriptingObject.Type, "Table", StringComparison.CurrentCultureIgnoreCase)) if (string.Equals(scriptingObject.Type, "Table", StringComparison.CurrentCultureIgnoreCase))
{ {
return new Scripter().SelectFromTableOrView(dataSource, objectUrn); return _scripter.SelectFromTableOrView(dataSource, objectUrn);
} }
return string.Empty; return string.Empty;
@@ -163,7 +160,7 @@ namespace Microsoft.Kusto.ServiceLayer.Scripting
if (string.Equals(scriptingObject.Type, "Function", StringComparison.CurrentCultureIgnoreCase)) if (string.Equals(scriptingObject.Type, "Function", StringComparison.CurrentCultureIgnoreCase))
{ {
return new Scripter().AlterFunction(dataSource, scriptingObject); return _scripter.AlterFunction(dataSource, scriptingObject);
} }
return string.Empty; return string.Empty;
@@ -177,7 +174,7 @@ namespace Microsoft.Kusto.ServiceLayer.Scripting
/// <param name="objectName">The object name.</param> /// <param name="objectName">The object name.</param>
/// <param name="schemaQualify">Whether to schema qualify the object or not</param> /// <param name="schemaQualify">Whether to schema qualify the object or not</param>
/// <returns>The object name, quoted as appropriate and schema-qualified if the option is set</returns> /// <returns>The object name, quoted as appropriate and schema-qualified if the option is set</returns>
static private string GenerateSchemaQualifiedName(string schema, string objectName, bool schemaQualify) private static string GenerateSchemaQualifiedName(string schema, string objectName, bool schemaQualify)
{ {
var qualifiedName = new StringBuilder(); var qualifiedName = new StringBuilder();

View File

@@ -3,73 +3,34 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information. // Licensed under the MIT license. See LICENSE file in the project root for full license information.
// //
using Microsoft.SqlServer.Management.Smo; using System.Composition;
using Microsoft.SqlServer.Management.SqlParser.Intellisense; using Microsoft.Kusto.ServiceLayer.Scripting.Contracts;
using Microsoft.SqlServer.Management.Common; using Microsoft.Kusto.ServiceLayer.DataSource;
using Microsoft.SqlServer.Management.Sdk.Sfc;
using System.Text;
namespace Microsoft.Kusto.ServiceLayer.Scripting 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 // TODOKusto: Can we combine this with snippets. All queries generated here could also be snippets.
AddSupportedType(DeclarationType.Table, "Table", "table", typeof(Table)); // TODOKusto: Extract into the Kusto folder.
AddSupportedType(DeclarationType.View, "View", "view", typeof(View)); selectQuery.Append($"{KustoQueryUtils.EscapeName(urn.GetAttribute("Name"))}");
AddSupportedType(DeclarationType.StoredProcedure, "StoredProcedure", "stored procedure", typeof(StoredProcedure)); selectQuery.Append($"{KustoQueryUtils.StatementSeparator}");
AddSupportedType(DeclarationType.Schema, "Schema", "schema", typeof(Schema)); selectQuery.Append("limit 1000");
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));
// Mapping for database engine edition return selectQuery.ToString();
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");
// Mapping for database engine type public string AlterFunction(IDataSource dataSource, ScriptingObject scriptingObject)
serverVersionMap.Add(9, "Script90Compat"); {
serverVersionMap.Add(10, "Script100Compat"); var functionName = scriptingObject.Name.Substring(0, scriptingObject.Name.IndexOf('('));
serverVersionMap.Add(11, "Script110Compat"); return dataSource.GenerateAlterFunctionScript(functionName);
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");
} }
} }
} }

View File

@@ -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<DeclarationType, string> sqlObjectTypes = new Dictionary<DeclarationType, string>();
private Dictionary<string, string> sqlObjectTypesFromQuickInfo = new Dictionary<string, string>();
private Dictionary<DatabaseEngineEdition, string> targetDatabaseEngineEditionMap = new Dictionary<DatabaseEngineEdition, string>();
private Dictionary<int, string> serverVersionMap = new Dictionary<int, string>();
private Dictionary<string, string> objectScriptMap = new Dictionary<string, string>();
internal Scripter() {}
/// <summary>
/// Initialize a Peek Definition helper object
/// </summary>
/// <param name="dataSource">Data Source</param>
internal Scripter(IDataSource dataSource, ConnectionInfo connInfo)
{
this.DataSource = dataSource;
this.connectionInfo = connInfo;
this.tempPath = FileUtilities.GetPeekDefinitionTempFolder();
Initialize();
}
/// <summary>
/// Add the given type, scriptgetter and the typeName string to the respective dictionaries
/// </summary>
private void AddSupportedType(DeclarationType type, string typeName, string quickInfoType, Type smoObjectType)
{
sqlObjectTypes.Add(type, typeName);
if (!string.IsNullOrEmpty(quickInfoType))
{
sqlObjectTypesFromQuickInfo.Add(quickInfoType.ToLowerInvariant(), typeName);
}
}
/// <summary>
/// Get the script of the selected token based on the type of the token
/// </summary>
/// <param name="declarationItems"></param>
/// <param name="tokenText"></param>
/// <param name="schemaName"></param>
/// <returns>Location object of the script file</returns>
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<Declaration> 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);
}
/// <summary>
/// Script an object using the type extracted from quickInfo Text
/// </summary>
/// <param name="quickInfoText">the text from the quickInfo for the selected token</param>
/// <param name="tokenText">The text of the selected token</param>
/// <param name="schemaName">Schema name</param>
/// <returns></returns>
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);
}
/// <summary>
/// Script a object using the type extracted from declarationItem
/// </summary>
/// <param name="declarationItem">The Declaration object that matched with the selected token</param>
/// <param name="tokenText">The text of the selected token</param>
/// <param name="schemaName">Schema name</param>
/// <returns></returns>
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);
}
/// <summary>
/// Script a object using SMO and write to a file.
/// </summary>
/// <param name="sqlScriptGetter">Function that returns the SMO scripts for an object</param>
/// <param name="objectName">SQL object name</param>
/// <param name="schemaName">Schema name or null</param>
/// <param name="objectType">Type of SQL object</param>
/// <returns>Location object representing URI and range of the script file</returns>
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
/// <summary>
/// Return schema name from the full name of the database. If schema is missing return dbo as schema name.
/// </summary>
/// <param name="fullObjectName"> The full database qualified name(database.schema.object)</param>
/// <param name="objectName"> Object name</param>
/// <returns>Schema name</returns>
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";
}
/// <summary>
/// Convert a file to a location array containing a location object as expected by the extension
/// </summary>
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;
}
/// <summary>
/// Helper method to create definition error result object
/// </summary>
/// <param name="errorMessage">Error message</param>
/// <returns> DefinitionResult</returns>
internal DefinitionResult GetDefinitionErrorResult(string errorMessage)
{
return new DefinitionResult
{
IsErrorResult = true,
Message = errorMessage,
Locations = null
};
}
/// <summary>
/// Return full object name(database.schema.objectName) from the quickInfo text("type database.schema.objectName")
/// </summary>
/// <param name="quickInfoText">QuickInfo Text for this token</param>
/// <param name="tokenText">Token Text</param>
/// <param name="caseSensitivity">StringComparison enum</param>
/// <returns></returns>
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<string> tokenList = tokens.Where(el => el.IndexOf(tokenText, caseSensitivity) >= 0).ToList();
return (tokenList?.Count() > 0) ? tokenList[0] : null;
}
/// <summary>
/// Return token type from the quickInfo text("type database.schema.objectName")
/// </summary>
/// <param name="quickInfoText">QuickInfo Text for this token</param>
/// <param name="tokenText"Token Text></param>
/// <param name="caseSensitivity">StringComparison enum</param>
/// <returns></returns>
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<int> 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;
}
/// <summary>
/// Wrapper method that calls Resolver.GetQuickInfo
/// </summary>
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;
}
/// <summary>
/// Wrapper method that calls Resolver.FindCompletions
/// </summary>
/// <param name="parseResult"></param>
/// <param name="parserLine"></param>
/// <param name="parserColumn"></param>
/// <param name="metadataDisplayInfoProvider"></param>
/// <returns></returns>
internal IEnumerable<Declaration> GetCompletionsForToken(ParseResult parseResult, int parserLine, int parserColumn, IMetadataDisplayInfoProvider metadataDisplayInfoProvider)
{
if (parseResult == null || metadataDisplayInfoProvider == null)
{
return null;
}
return Resolver.FindCompletions(
parseResult, parserLine, parserColumn, metadataDisplayInfoProvider);
}
/// <summary>
/// Wrapper method that calls Resolver.FindCompletions
/// </summary>
/// <param name="objectName"></param>
/// <param name="schemaName"></param>
/// <param name="objectType"></param>
/// <param name="tempFileName"></param>
/// <returns></returns>
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<ScriptingObject> objectList = new List<ScriptingObject>();
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
{
/// <summary>
/// Left delimiter for an named object
/// </summary>
public const char LeftDelimiter = '[';
/// <summary>
/// right delimiter for a named object
/// </summary>
public const char RightDelimiter = ']';
}
internal static class ScriptingUtils
{
/// <summary>
/// Quote the name of a given sql object.
/// </summary>
/// <param name="sqlObject">object</param>
/// <returns>quoted object name</returns>
internal static string QuoteObjectName(string sqlObject)
{
return QuoteObjectName(sqlObject, ']');
}
/// <summary>
/// Quotes the name of a given sql object
/// </summary>
/// <param name="sqlObject">object</param>
/// <param name="quote">quote to use</param>
/// <returns></returns>
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<string> filterExpressions = new List<string>();
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
}
}

View File

@@ -8,6 +8,7 @@ using System.Collections.Generic;
using System.Data.SqlClient; using System.Data.SqlClient;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using Microsoft.Kusto.ServiceLayer.DataSource;
using Microsoft.SqlServer.Management.SqlScriptPublish; using Microsoft.SqlServer.Management.SqlScriptPublish;
using Microsoft.Kusto.ServiceLayer.Scripting.Contracts; using Microsoft.Kusto.ServiceLayer.Scripting.Contracts;
using Microsoft.SqlTools.Utility; using Microsoft.SqlTools.Utility;
@@ -28,7 +29,7 @@ namespace Microsoft.Kusto.ServiceLayer.Scripting
private string azureAccessToken; private string azureAccessToken;
public ScriptingScriptOperation(ScriptingParams parameters, string azureAccessToken): base(parameters) public ScriptingScriptOperation(ScriptingParams parameters, string azureAccessToken) : base(parameters)
{ {
this.azureAccessToken = azureAccessToken; this.azureAccessToken = azureAccessToken;
} }

View File

@@ -10,7 +10,7 @@ using System.Threading.Tasks;
using Microsoft.SqlTools.Hosting.Protocol; using Microsoft.SqlTools.Hosting.Protocol;
using Microsoft.SqlTools.Hosting.Protocol.Contracts; using Microsoft.SqlTools.Hosting.Protocol.Contracts;
using Microsoft.Kusto.ServiceLayer.Connection; using Microsoft.Kusto.ServiceLayer.Connection;
using Microsoft.Kusto.ServiceLayer.Hosting; using Microsoft.Kusto.ServiceLayer.DataSource;
using Microsoft.Kusto.ServiceLayer.Scripting.Contracts; using Microsoft.Kusto.ServiceLayer.Scripting.Contracts;
using Microsoft.SqlTools.Utility; using Microsoft.SqlTools.Utility;
using Microsoft.Kusto.ServiceLayer.Utility; using Microsoft.Kusto.ServiceLayer.Utility;
@@ -21,19 +21,21 @@ namespace Microsoft.Kusto.ServiceLayer.Scripting
/// Main class for Scripting Service functionality /// Main class for Scripting Service functionality
/// </summary> /// </summary>
public sealed class ScriptingService : IDisposable public sealed class ScriptingService : IDisposable
{ {
private const int ScriptingOperationTimeout = 60000; private const int ScriptingOperationTimeout = 60000;
private static readonly Lazy<ScriptingService> LazyInstance = new Lazy<ScriptingService>(() => new ScriptingService()); private static readonly Lazy<ScriptingService> LazyInstance = new Lazy<ScriptingService>(() => new ScriptingService());
public static ScriptingService Instance => LazyInstance.Value; public static ScriptingService Instance => LazyInstance.Value;
private static ConnectionService connectionService = null; private static ConnectionService connectionService;
private readonly Lazy<ConcurrentDictionary<string, ScriptingOperation>> operations = private readonly Lazy<ConcurrentDictionary<string, ScriptingOperation>> operations =
new Lazy<ConcurrentDictionary<string, ScriptingOperation>>(() => new ConcurrentDictionary<string, ScriptingOperation>()); new Lazy<ConcurrentDictionary<string, ScriptingOperation>>(() => new ConcurrentDictionary<string, ScriptingOperation>());
private bool disposed; private bool disposed;
private IScripter _scripter;
/// <summary> /// <summary>
/// Internal for testing purposes only /// Internal for testing purposes only
@@ -64,8 +66,9 @@ namespace Microsoft.Kusto.ServiceLayer.Scripting
/// </summary> /// </summary>
/// <param name="serviceHost"></param> /// <param name="serviceHost"></param>
/// <param name="context"></param> /// <param name="context"></param>
public void InitializeService(ServiceHost serviceHost) public void InitializeService(ServiceHost serviceHost, IScripter scripter)
{ {
_scripter = scripter;
serviceHost.SetRequestHandler(ScriptingRequest.Type, this.HandleScriptExecuteRequest); serviceHost.SetRequestHandler(ScriptingRequest.Type, this.HandleScriptExecuteRequest);
serviceHost.SetRequestHandler(ScriptingCancelRequest.Type, this.HandleScriptCancelRequest); serviceHost.SetRequestHandler(ScriptingCancelRequest.Type, this.HandleScriptCancelRequest);
serviceHost.SetRequestHandler(ScriptingListObjectsRequest.Type, this.HandleListObjectsRequest); serviceHost.SetRequestHandler(ScriptingListObjectsRequest.Type, this.HandleListObjectsRequest);
@@ -132,7 +135,7 @@ namespace Microsoft.Kusto.ServiceLayer.Scripting
} }
else else
{ {
operation = new ScriptAsScriptingOperation(parameters, accessToken); operation = new ScriptAsScriptingOperation(parameters, accessToken, _scripter);
} }
operation.PlanNotification += (sender, e) => requestContext.SendEvent(ScriptingPlanNotificationEvent.Type, e).Wait(); operation.PlanNotification += (sender, e) => requestContext.SendEvent(ScriptingPlanNotificationEvent.Type, e).Wait();

View File

@@ -22,7 +22,7 @@ namespace Microsoft.Kusto.ServiceLayer.Scripting
/// </summary> /// </summary>
public abstract class SmoScriptingOperation : ScriptingOperation public abstract class SmoScriptingOperation : ScriptingOperation
{ {
private bool disposed = false; private bool _disposed;
public SmoScriptingOperation(ScriptingParams parameters) public SmoScriptingOperation(ScriptingParams parameters)
{ {
@@ -177,10 +177,10 @@ namespace Microsoft.Kusto.ServiceLayer.Scripting
/// </summary> /// </summary>
public override void Dispose() public override void Dispose()
{ {
if (!disposed) if (!_disposed)
{ {
this.Cancel(); this.Cancel();
disposed = true; _disposed = true;
} }
} }

View File

@@ -5,20 +5,20 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Kusto.ServiceLayer.Connection;
using Microsoft.Kusto.ServiceLayer.Utility;
using Microsoft.SqlTools.Extensibility; using Microsoft.SqlTools.Extensibility;
using Microsoft.SqlTools.Hosting; using Microsoft.SqlTools.Hosting;
using Microsoft.SqlTools.Hosting.Contracts; using Microsoft.SqlTools.Hosting.Contracts;
using Microsoft.SqlTools.Hosting.Protocol; using Microsoft.SqlTools.Hosting.Protocol;
using Microsoft.SqlTools.Hosting.Protocol.Channel; using Microsoft.SqlTools.Hosting.Protocol.Channel;
using Microsoft.SqlTools.Utility; 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
{ {
/// <summary> /// <summary>
/// SQL Tools VS Code Language Server request handler. Provides the entire JSON RPC /// SQL Tools VS Code Language Server request handler. Provides the entire JSON RPC

View File

@@ -10,7 +10,6 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.SqlTools.Hosting.Protocol; using Microsoft.SqlTools.Hosting.Protocol;
using Microsoft.Kusto.ServiceLayer.Hosting;
using Microsoft.Kusto.ServiceLayer.Workspace.Contracts; using Microsoft.Kusto.ServiceLayer.Workspace.Contracts;
using Microsoft.SqlTools.Utility; using Microsoft.SqlTools.Utility;
using Range = Microsoft.Kusto.ServiceLayer.Workspace.Contracts.Range; using Range = Microsoft.Kusto.ServiceLayer.Workspace.Contracts.Range;

View File

@@ -73,15 +73,6 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer
{ {
this.bindingQueue = value; this.bindingQueue = value;
} }
}
/// <summary>
/// Internal for testing only
/// </summary>
internal ObjectExplorerService(ExtensionServiceProvider serviceProvider)
: this()
{
SetServiceProvider(serviceProvider);
} }
private Dictionary<string, HashSet<ChildFactory>> ApplicableNodeChildFactories private Dictionary<string, HashSet<ChildFactory>> ApplicableNodeChildFactories