mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-02-09 01:32:38 -05:00
Initial metadata and scripting services (#280)
* Initial metadata service and scripting service files * Simple metadata lookup with SMO objects * Add metadata type class * Remove SMO from metadata service. * Cleanup metadata service SQL * Initial MetadataService test * Add scripting commands * Add metadata test case * Remove sleep used for testing * Use random table name in metadata test * Add scripting tests
This commit is contained in:
@@ -10,7 +10,9 @@ using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.EditData;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices;
|
||||
using Microsoft.SqlTools.ServiceLayer.Metadata;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
||||
using Microsoft.SqlTools.ServiceLayer.Scripting;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
||||
|
||||
@@ -76,6 +78,12 @@ namespace Microsoft.SqlTools.ServiceLayer
|
||||
EditDataService.Instance.InitializeService(serviceHost);
|
||||
serviceProvider.RegisterSingleService(EditDataService.Instance);
|
||||
|
||||
MetadataService.Instance.InitializeService(serviceHost);
|
||||
serviceProvider.RegisterSingleService(MetadataService.Instance);
|
||||
|
||||
ScriptingService.Instance.InitializeService(serviceHost);
|
||||
serviceProvider.RegisterSingleService(ScriptingService.Instance);
|
||||
|
||||
InitializeHostedServices(serviceProvider, serviceHost);
|
||||
|
||||
serviceHost.InitializeRequestHandlers();
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Metadata.Contracts
|
||||
{
|
||||
public class MetadataQueryParams
|
||||
{
|
||||
public string OwnerUri { get; set; }
|
||||
}
|
||||
|
||||
public class MetadataQueryResult
|
||||
{
|
||||
public ObjectMetadata[] Metadata { get; set; }
|
||||
}
|
||||
|
||||
public class MetadataListRequest
|
||||
{
|
||||
public static readonly
|
||||
RequestType<MetadataQueryParams, MetadataQueryResult> Type =
|
||||
RequestType<MetadataQueryParams, MetadataQueryResult>.Create("metadata/list");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Metadata.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// Metadata type enumeration
|
||||
/// </summary>
|
||||
public enum MetadataType
|
||||
{
|
||||
Table = 0,
|
||||
View = 1,
|
||||
SProc = 2,
|
||||
Function = 3
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Object metadata information
|
||||
/// </summary>
|
||||
public class ObjectMetadata
|
||||
{
|
||||
public MetadataType MetadataType { get; set; }
|
||||
|
||||
public string MetadataTypeName { get; set; }
|
||||
|
||||
public string Schema { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
||||
167
src/Microsoft.SqlTools.ServiceLayer/Metadata/MetadataService.cs
Normal file
167
src/Microsoft.SqlTools.ServiceLayer/Metadata/MetadataService.cs
Normal file
@@ -0,0 +1,167 @@
|
||||
//
|
||||
// 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.Data.SqlClient;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting;
|
||||
using Microsoft.SqlTools.ServiceLayer.Metadata.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Metadata
|
||||
{
|
||||
/// <summary>
|
||||
/// Main class for Metadata Service functionality
|
||||
/// </summary>
|
||||
public sealed class MetadataService
|
||||
{
|
||||
private static readonly Lazy<MetadataService> LazyInstance = new Lazy<MetadataService>(() => new MetadataService());
|
||||
|
||||
public static MetadataService Instance => LazyInstance.Value;
|
||||
|
||||
private static ConnectionService connectionService = null;
|
||||
|
||||
/// <summary>
|
||||
/// Internal for testing purposes only
|
||||
/// </summary>
|
||||
internal static ConnectionService ConnectionServiceInstance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (connectionService == null)
|
||||
{
|
||||
connectionService = ConnectionService.Instance;
|
||||
}
|
||||
return connectionService;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
connectionService = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the Metadata Service instance
|
||||
/// </summary>
|
||||
/// <param name="serviceHost"></param>
|
||||
/// <param name="context"></param>
|
||||
public void InitializeService(ServiceHost serviceHost)
|
||||
{
|
||||
serviceHost.SetRequestHandler(MetadataListRequest.Type, HandleMetadataListRequest);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle a metadata query request
|
||||
/// </summary>
|
||||
internal static async Task HandleMetadataListRequest(
|
||||
MetadataQueryParams metadataParams,
|
||||
RequestContext<MetadataQueryResult> requestContext)
|
||||
{
|
||||
try
|
||||
{
|
||||
ConnectionInfo connInfo;
|
||||
MetadataService.ConnectionServiceInstance.TryFindConnection(
|
||||
metadataParams.OwnerUri,
|
||||
out connInfo);
|
||||
|
||||
var metadata = new List<ObjectMetadata>();
|
||||
if (connInfo != null)
|
||||
{
|
||||
SqlConnection sqlConn = OpenMetadataConnection(connInfo);
|
||||
ReadMetadata(sqlConn, metadata);
|
||||
}
|
||||
|
||||
await requestContext.SendResult(new MetadataQueryResult()
|
||||
{
|
||||
Metadata = metadata.ToArray()
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await requestContext.SendError(ex.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a SqlConnection to use for querying metadata
|
||||
/// </summary>
|
||||
internal static SqlConnection OpenMetadataConnection(ConnectionInfo connInfo)
|
||||
{
|
||||
try
|
||||
{
|
||||
// increase the connection timeout to at least 30 seconds and and build connection string
|
||||
// enable PersistSecurityInfo to handle issues in SMO where the connection context is lost in reconnections
|
||||
int? originalTimeout = connInfo.ConnectionDetails.ConnectTimeout;
|
||||
bool? originalPersistSecurityInfo = connInfo.ConnectionDetails.PersistSecurityInfo;
|
||||
connInfo.ConnectionDetails.ConnectTimeout = Math.Max(30, originalTimeout ?? 0);
|
||||
connInfo.ConnectionDetails.PersistSecurityInfo = true;
|
||||
string connectionString = ConnectionService.BuildConnectionString(connInfo.ConnectionDetails);
|
||||
connInfo.ConnectionDetails.ConnectTimeout = originalTimeout;
|
||||
connInfo.ConnectionDetails.PersistSecurityInfo = originalPersistSecurityInfo;
|
||||
|
||||
// open a dedicated binding server connection
|
||||
SqlConnection sqlConn = new SqlConnection(connectionString);
|
||||
sqlConn.Open();
|
||||
return sqlConn;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read metadata for the current connection
|
||||
/// </summary>
|
||||
internal static void ReadMetadata(SqlConnection sqlConn, List<ObjectMetadata> metadata)
|
||||
{
|
||||
string sql =
|
||||
@"SELECT s.name AS schema_name, o.[name] AS object_name, o.[type] AS object_type
|
||||
FROM sys.all_objects o
|
||||
INNER JOIN sys.schemas s ON o.schema_id = s.schema_id
|
||||
WHERE o.is_ms_shipped != 1
|
||||
AND (o.[type] = 'P' OR o.[type] = 'V' OR o.[type] = 'U')
|
||||
ORDER BY object_type, schema_name, object_name";
|
||||
|
||||
using (SqlCommand sqlCommand = new SqlCommand(sql, sqlConn))
|
||||
{
|
||||
using (var reader = sqlCommand.ExecuteReader())
|
||||
{
|
||||
while (reader.Read())
|
||||
{
|
||||
var schemaName = reader[0] as string;
|
||||
var objectName = reader[1] as string;
|
||||
var objectType = reader[2] as string;
|
||||
|
||||
MetadataType metadataType;
|
||||
if (objectType.StartsWith("V"))
|
||||
{
|
||||
metadataType = MetadataType.View;
|
||||
}
|
||||
else if (objectType.StartsWith("P"))
|
||||
{
|
||||
metadataType = MetadataType.SProc;
|
||||
}
|
||||
else
|
||||
{
|
||||
metadataType = MetadataType.Table;
|
||||
}
|
||||
|
||||
metadata.Add(new ObjectMetadata
|
||||
{
|
||||
MetadataType = metadataType,
|
||||
Schema = schemaName,
|
||||
Name = objectName
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Metadata.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Scripting.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of scripting operation requested
|
||||
/// </summary>
|
||||
public enum ScriptOperation
|
||||
{
|
||||
Select = 0,
|
||||
Create = 1,
|
||||
Insert = 2,
|
||||
Update = 3,
|
||||
Delete = 4
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Script as request parameter type
|
||||
/// </summary>
|
||||
public class ScriptingScriptAsParams
|
||||
{
|
||||
public string OwnerUri { get; set; }
|
||||
|
||||
public ScriptOperation Operation { get; set; }
|
||||
|
||||
public ObjectMetadata Metadata { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Script as request result type
|
||||
/// </summary>
|
||||
public class ScriptingScriptAsResult
|
||||
{
|
||||
public string OwnerUri { get; set; }
|
||||
|
||||
public string Script { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Script as request message type
|
||||
/// </summary>
|
||||
public class ScriptingScriptAsRequest
|
||||
{
|
||||
public static readonly
|
||||
RequestType<ScriptingScriptAsParams, ScriptingScriptAsResult> Type =
|
||||
RequestType<ScriptingScriptAsParams, ScriptingScriptAsResult>.Create("scripting/scriptas");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
//
|
||||
// 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.Threading.Tasks;
|
||||
using Microsoft.SqlTools.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting;
|
||||
using Microsoft.SqlTools.ServiceLayer.Scripting.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Scripting
|
||||
{
|
||||
/// <summary>
|
||||
/// Main class for Scripting Service functionality
|
||||
/// </summary>
|
||||
public sealed class ScriptingService
|
||||
{
|
||||
private static readonly Lazy<ScriptingService> LazyInstance = new Lazy<ScriptingService>(() => new ScriptingService());
|
||||
|
||||
public static ScriptingService Instance => LazyInstance.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the Scripting Service instance
|
||||
/// </summary>
|
||||
/// <param name="serviceHost"></param>
|
||||
/// <param name="context"></param>
|
||||
public void InitializeService(ServiceHost serviceHost)
|
||||
{
|
||||
serviceHost.SetRequestHandler(ScriptingScriptAsRequest.Type, HandleScriptingScriptAsRequest);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles script as request messages
|
||||
/// </summary>
|
||||
/// <param name="scriptingParams"></param>
|
||||
/// <param name="requestContext"></param>
|
||||
internal static async Task HandleScriptingScriptAsRequest(
|
||||
ScriptingScriptAsParams scriptingParams,
|
||||
RequestContext<ScriptingScriptAsResult> requestContext)
|
||||
{
|
||||
string script = string.Empty;
|
||||
if (scriptingParams.Operation == ScriptOperation.Select)
|
||||
{
|
||||
script = string.Format(
|
||||
@"SELECT *
|
||||
FROM {0}.{1}",
|
||||
scriptingParams.Metadata.Schema, scriptingParams.Metadata.Name);
|
||||
}
|
||||
else if (scriptingParams.Operation == ScriptOperation.Create)
|
||||
{
|
||||
script = string.Format(
|
||||
@"CREATE {0}.{1}",
|
||||
scriptingParams.Metadata.Schema, scriptingParams.Metadata.Name);
|
||||
}
|
||||
else if (scriptingParams.Operation == ScriptOperation.Update)
|
||||
{
|
||||
script = string.Format(
|
||||
@"UPDATE {0}.{1}",
|
||||
scriptingParams.Metadata.Schema, scriptingParams.Metadata.Name);
|
||||
}
|
||||
else if (scriptingParams.Operation == ScriptOperation.Insert)
|
||||
{
|
||||
script = string.Format(
|
||||
@"INSERT {0}.{1}",
|
||||
scriptingParams.Metadata.Schema, scriptingParams.Metadata.Name);
|
||||
}
|
||||
else if (scriptingParams.Operation == ScriptOperation.Delete)
|
||||
{
|
||||
script = string.Format(
|
||||
@"DELETE {0}.{1}",
|
||||
scriptingParams.Metadata.Schema, scriptingParams.Metadata.Name);
|
||||
}
|
||||
|
||||
await requestContext.SendResult(new ScriptingScriptAsResult()
|
||||
{
|
||||
OwnerUri = scriptingParams.OwnerUri,
|
||||
Script = script
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user