Added new Kusto ServiceLayer (#1009)

* Copy smoModel some rename

* Copy entire service layer

* Building copy

* Fixing some references

* Launch profile

* Resolve namespace issues

* Compiling tests. Correct manifest.

* Fixing localization resources

* ReliableKustoClient

* Some trimming of extra code and Kusto code

* Kusto client creation in bindingContent

* Removing Smo and new Kusto classes

* More trimming

* Kusto schema hookup

* Solidying DataSource abstraction

* Solidifying further

* Latest refatoring

* More refactoring

* Building and launching Kusto service layer

* Working model which enumerates databases

* Refactoring to pass IDataSource to all tree nodes

* Removing some dependencies on the context

* Working with tables and schema

* Comment checkin

* Refactoring to give out select script

* Query created and sent back to ADS

* Fix query generation

* Fix listing of databases

* Tunneling the query through.

* Successful query execution

* Return only results table

* Deleting Cms

* Delete DacFx

* Delete SchemaCompare and TaskServices

* Change build definition to not stop at launch

* Fix error after merge

* Save Kusto results in different formats (#935)

* save results as csv etc

* some fixes

Co-authored-by: Monica Gupta <mogupt@microsoft.com>

* 2407 Added OrderBy clause in KustoDataSource > GetDatabaseMetaData and GetColumnMetadata (#959)

* 2405 Defaulted Options when setting ServerInfo in ConnectionService > GetConnectionCompleteParams (#965)

* 2747 Fixed IsUnknownType error for Kusto (#989)

* 2747 Removed unused directives in Kusto > DbColumnWrapper. Refactored IsUnknownType to handle null DataTypeName

* 2747 Reverted IsUnknownType change in DbColumnWrapper. Changed DataTypeName to get calue from ColumnType. Refactored SafeGetValue to type check before hard casting to reduce case exceptions.

* Added EmbeddedResourceUseDependentUponConvention to Microsoft.Kusto.ServiceLayer.csproj. Also renamed DACfx to match Microsoft.SqlTools.ServiceLayer. Added to compile Exclude="**/obj/**/*.cs"

* Srahman cleanup sql code (#992)

* Removed Management and Security Service Code.

* Remove FileBrowser service

* Comment why we are using SqlServer library

* Remove SQL specific type definitions

* clean up formatter service (#996)

Co-authored-by: Monica Gupta <mogupt@microsoft.com>

* Code clean up and Kusto intellisense (#994)

* Code clean up and Kusto intellisense

* Addressed few comments

* Addressed few comments

* addressed comments

Co-authored-by: Monica Gupta <mogupt@microsoft.com>

* Return multiple tables for Kusto

* Changes required for Kusto manage dashboard (#1039)

* Changes required for manage dashboard

* Addressed comments

Co-authored-by: Monica Gupta <mogupt@microsoft.com>

* 2728 Kusto function support (#1038)

* loc update (#914)

* loc update

* loc updates

* 2728 moved ColumnInfo and KustoResultsReader to separate files. Added Folder and Function to TreeNode.cs

* 2728 Added FunctionInfo. Added Folder to ColumnInfo. Removed partial class from KustoResultsReader. Set Function.IsAlwaysLeaf=true in TreeNode.cs. In KustoDataSource changed tableMetadata type to TableMetaData. Added folder and function dictionaries. Refactored GetSchema function. Renamed GenerateColumnMetadataKey to GenerateMetadataKey

* 2728 Added FunctionInfo. Added Folder to ColumnInfo. Removed partial class from KustoResultsReader. Set Function.IsAlwaysLeaf=true in TreeNode.cs. In KustoDataSource changed tableMetadata type to TableMetaData. Added folder and function dictionaries. Refactored GetSchema function. Renamed GenerateColumnMetadataKey to GenerateMetadataKey

* 2728 Created new SqlConnection within using block. Refactored KustoDataSource > columnmetadata to sort on get instead of insert.

* 2728 Added GetFunctionInfo function to KustoDataSource.

* 2728 Reverted change to Microsoft.Kusto.ServiceLayer.csproj from merge

* 2728 Reverted change to SqlTools.ServiceLayer\Localization\transXliff

* 2728 Reverted change to sr.de.xlf and sr.zh-hans.xlf

* 2728 Refactored KustoDataSource Function folders to support subfolders

* 2728 Refactored KustoDataSource to use urn for folders, functions, and tables instead of name.

* Merge remote-tracking branch 'origin/main' into feature-ADE

# Conflicts:
#	Packages.props

* 2728 Moved metadata files into Metadata subdirectory. Added GenerateAlterFunction to IDataSource and DataSourceBase.

* 2728 Added summary information to SafeAdd in SystemExtensions. Renamed local variable in SetTableMetadata

* 2728 Moved SafeAdd from SystemExtensions to KustoQueryUtils. Added check when getting database schema to return existing records before querying again. Added AddRange function to KustoQueryUtils. Created SetFolderMetadataForFunctions method.

* 2728 Added DatabaseKeyPrefix to only return tables to a database for the dashboard. Added logic to store all database tables within the tableMetadata dictionary for the dashboard.

* 2728 Created TableInfo and moved info objects into Models directory. Refactored KustoDataSource to lazy load columns for tables. Refactored logic to load tables using cslschema instead of schema.

* 2728 Renamed LoadColumnSchema to GetTableSchema to be consistent.

Co-authored-by: khoiph1 <khoiph@microsoft.com>

* Addressed comments

Co-authored-by: Shafiq Rahman <srahman@microsoft.com>
Co-authored-by: Monica Gupta <mogupt@microsoft.com>
Co-authored-by: Justin M <63619224+JustinMDotNet@users.noreply.github.com>
Co-authored-by: rkselfhost <rkselfhost@outlook.com>
Co-authored-by: khoiph1 <khoiph@microsoft.com>
This commit is contained in:
Monica Gupta
2020-08-12 15:34:38 -07:00
committed by GitHub
parent d2f5bfaa16
commit 148b6e398d
276 changed files with 75983 additions and 1 deletions

View File

@@ -0,0 +1,28 @@
//
// 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.Kusto.ServiceLayer.Scripting.Contracts
{
/// <summary>
/// Parameters to cancel a scripting request.
/// </summary>
public class ScriptingCancelParams : ScriptingEventParams { }
/// <summary>
/// Parameters returned from a scripting request.
/// </summary>
public class ScriptingCancelResult { }
/// <summary>
/// Defines the scripting cancel request type.
/// </summary>
public class ScriptingCancelRequest
{
public static readonly RequestType<ScriptingCancelParams, ScriptingCancelResult> Type =
RequestType<ScriptingCancelParams, ScriptingCancelResult>.Create("scripting/scriptCancel");
}
}

View File

@@ -0,0 +1,49 @@
//
// 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.Kusto.ServiceLayer.Scripting.Contracts
{
/// <summary>
/// Parameters sent to when a scripting operation has completed.
/// </summary>
public class ScriptingCompleteParams : ScriptingEventParams
{
/// <summary>
/// Get or sets the error details for an error that occurred during the scripting operation.
/// </summary>
public string ErrorDetails { get; set; }
/// <summary>
/// Get or sets the error message for an error that occurred during the scripting operation.
/// </summary>
public string ErrorMessage { get; set; }
/// <summary>
/// Get or sets a value to indicate an error occurred during the scripting operation.
/// </summary>
public bool HasError { get; set; }
/// <summary>
/// Get or sets a value to indicate the scripting operation was canceled.
/// </summary>
public bool Canceled { get; set; }
/// <summary>
/// Get or sets a value to indicate the scripting operation successfully completed.
/// </summary>
public bool Success { get; set; }
}
/// <summary>
/// Event sent to indicate a scripting operation has completed.
/// </summary>
public class ScriptingCompleteEvent
{
public static readonly EventType<ScriptingCompleteParams> Type =
EventType<ScriptingCompleteParams>.Create("scripting/scriptComplete");
}
}

View File

@@ -0,0 +1,24 @@
//
// 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.Scripting.Contracts
{
/// <summary>
/// Base class for all scripting event parameters.
/// </summary>
public abstract class ScriptingEventParams
{
/// <summary>
/// Gets or sets the operation id of the scripting operation this event is associated with.
/// </summary>
public string OperationId { get; set; }
/// <summary>
/// Gets or sets the sequence number. The sequence number starts at 1, and is incremented each time a scripting event is
/// raised for the current scripting operation.
/// </summary>
public int SequenceNumber { get; set; }
}
}

View File

@@ -0,0 +1,35 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System.Collections.Generic;
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
namespace Microsoft.Kusto.ServiceLayer.Scripting.Contracts
{
/// <summary>
/// Parameters sent when a list objects operation has completed.
/// </summary>
public class ScriptingListObjectsCompleteParams : ScriptingCompleteParams
{
/// <summary>
/// Gets or sets the list of database objects returned from the list objects operation.
/// </summary>
public List<ScriptingObject> ScriptingObjects { get; set; }
/// <summary>
/// Gets or sets the count of database object returned from the list objects operation.
/// </summary>
public int Count { get; set; }
}
/// <summary>
/// Event sent to indicate a list objects operation has completed.
/// </summary>
public class ScriptingListObjectsCompleteEvent
{
public static readonly EventType<ScriptingListObjectsCompleteParams> Type =
EventType<ScriptingListObjectsCompleteParams>.Create("scripting/listObjectsComplete");
}
}

View File

@@ -0,0 +1,35 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System.Collections.Generic;
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
namespace Microsoft.Kusto.ServiceLayer.Scripting.Contracts
{
/// <summary>
/// Parameters for a list objects request.
/// </summary>
public class ScriptingListObjectsParams
{
public string ConnectionString { get; set; }
}
/// <summary>
/// Parameters returned from a list objects request.
/// </summary>
public class ScriptingListObjectsResult
{
public string OperationId { get; set; }
}
/// <summary>
/// Defines the scripting list objects request type.
/// </summary>
public class ScriptingListObjectsRequest
{
public static readonly RequestType<ScriptingListObjectsParams, ScriptingListObjectsResult> Type =
RequestType<ScriptingListObjectsParams, ScriptingListObjectsResult>.Create("scripting/listObjects");
}
}

View File

@@ -0,0 +1,93 @@
//
// 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;
namespace Microsoft.Kusto.ServiceLayer.Scripting.Contracts
{
/// <summary>
/// Class to represent a database object that can be scripted.
/// </summary>
public sealed class ScriptingObject : IEquatable<ScriptingObject>
{
/// <summary>
/// Gets or sets the database object type.
/// </summary>
/// <remarks>
/// This underlying values are determined by the SqlScriptPublishModel.GetDatabaseObjectTypes() and
/// can change depending on the version of SMO used by the tools service. Values can be:
/// Table,
/// View,
/// StoredProcedure,
/// UserDefinedFunction,
/// UserDefinedDataType,
/// User,
/// Default,
/// Rule,
/// DatabaseRole,
/// ApplicationRole,
/// SqlAssembly,
/// DdlTrigger,
/// Synonym,
/// XmlSchemaCollection,
/// Schema,
/// PlanGuide,
/// UserDefinedType,
/// UserDefinedAggregate,
/// FullTextCatalog,
/// UserDefinedTableType
/// </remarks>
public string Type { get; set; }
/// <summary>
/// Gets or sets the schema of the database object.
/// </summary>
public string Schema { get; set; }
/// <summary>
/// Gets or sets the database object name.
/// </summary>
public string Name { get; set; }
public override string ToString()
{
string objectName = string.IsNullOrEmpty(this.Schema)
? this.Name
: this.Schema + "." + this.Name;
return objectName;
}
public override int GetHashCode()
{
return
StringComparer.OrdinalIgnoreCase.GetHashCode(this.Type ?? string.Empty) ^
StringComparer.OrdinalIgnoreCase.GetHashCode(this.Schema ?? string.Empty) ^
StringComparer.OrdinalIgnoreCase.GetHashCode(this.Name ?? string.Empty);
}
public override bool Equals(object obj)
{
return
obj != null &&
this.GetType() == obj.GetType() &&
this.Equals((ScriptingObject)obj);
}
public bool Equals(ScriptingObject other)
{
if (other == null)
{
return false;
}
return
string.Equals(this.Type, other.Type, StringComparison.OrdinalIgnoreCase) &&
string.Equals(this.Schema, other.Schema, StringComparison.OrdinalIgnoreCase) &&
string.Equals(this.Name, other.Name, StringComparison.OrdinalIgnoreCase);
}
}
}

View File

@@ -0,0 +1,21 @@
//
// 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.Scripting.Contracts
{
/// <summary>
/// Scripting Operation type
/// </summary>
public enum ScriptingOperationType
{
Select = 0,
Create = 1,
Insert = 2,
Update = 3,
Delete = 4,
Execute = 5,
Alter = 6
}
}

View File

@@ -0,0 +1,261 @@
//
// 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.Scripting.Contracts
{
/// <summary>
/// Defines the scripting options.
/// </summary>
public class ScriptOptions
{
/// <summary>
/// Generate ANSI padding statements
/// </summary>
public virtual bool? ScriptAnsiPadding { get; set; } = false;
/// <summary>
/// Append the generated script to a file
/// </summary>
public virtual bool? AppendToFile { get; set; } = false;
/// <summary>
/// Continue to script if an error occurs. Otherwise, stop.
/// </summary>
/// <remarks>
/// The default is true.
/// </remarks>
public virtual bool? ContinueScriptingOnError { get; set; } = true;
/// <summary>
/// Convert user-defined data types to base types.
/// </summary>
public virtual bool? ConvertUDDTToBaseType { get; set; } = false;
/// <summary>
/// Generate script for dependent objects for each object scripted.
/// </summary>
/// <remarks>
/// The default is false.
/// </remarks>
public virtual bool? GenerateScriptForDependentObjects { get; set; } = false;
/// <summary>
/// Include descriptive headers for each object generated.
/// </summary>
/// <remarks>
/// The default is true.
/// </remarks>
public virtual bool? IncludeDescriptiveHeaders { get; set; } = true;
/// <summary>
/// Check that an object with the given name exists before dropping or altering or that an object with the given name does not exist before creating.
/// </summary>
public virtual bool? IncludeIfNotExists { get; set; } = false;
/// <summary>
/// Script options to set vardecimal storage format.
/// </summary>
public virtual bool? IncludeVarDecimal { get; set; } = true;
/// <summary>
/// Include system generated constraint names to enforce declarative referential integrity.
/// </summary>
public virtual bool? ScriptDriIncludeSystemNames { get; set; } = false;
/// <summary>
/// Include statements in the script that are not supported on the specified SQL Server database engine type.
/// </summary>
public virtual bool? IncludeUnsupportedStatements { get; set; } = true;
/// <summary>
/// Prefix object names with the object schema.
/// </summary>
/// <remarks>
/// The default is true.
/// </remarks>
public virtual bool? SchemaQualify { get; set; } = true;
/// <summary>
/// Script options to set bindings option.
/// </summary>
public virtual bool? Bindings { get; set; } = false;
/// <summary>
/// Script the objects that use collation.
/// </summary>
public virtual bool? Collation { get; set; } = false;
/// <summary>
/// Script the default values.
/// </summary>
/// <remarks>
/// The default is true.
/// </remarks>
public virtual bool? Default { get; set; } = true;
/// <summary>
/// Script Object CREATE/DROP statements.
/// Possible values:
/// ScriptCreate
/// ScriptDrop
/// ScriptCreateDrop
/// </summary>
/// <remarks>
/// The default is ScriptCreate.
/// </remarks>
public virtual string ScriptCreateDrop { get; set; } = "ScriptCreate";
/// <summary>
/// Script the Extended Properties for each object scripted.
/// </summary>
/// <remarks>
/// The default is true.
/// </remarks>
public virtual bool? ScriptExtendedProperties { get; set; } = true;
/// <summary>
/// Script only features compatible with the specified version of SQL Server. Possible values:
/// Script90Compat
/// Script100Compat
/// Script105Compat
/// Script110Compat
/// Script120Compat
/// Script130Compat
/// Script140Compat
/// Script150Compat
/// </summary>
/// <remarks>
/// The default is Script140Compat.
/// </remarks>
public virtual string ScriptCompatibilityOption { get; set; } = "Script140Compat";
/// <summary>
/// Script only features compatible with the specified SQL Server database engine type.
/// Possible Values:
/// SingleInstance
/// SqlAzure
/// </summary>
public virtual string TargetDatabaseEngineType { get; set; } = "SingleInstance";
/// <summary>
/// Script only features compatible with the specified SQL Server database engine edition.
/// Possible Values:
/// SqlServerPersonalEdition
/// SqlServerStandardEdition
/// SqlServerEnterpriseEdition
/// SqlServerExpressEdition
/// SqlAzureDatabaseEdition
/// SqlDatawarehouseEdition
/// SqlServerStretchEdition
/// SqlManagedInstanceEdition
/// </summary>
public virtual string TargetDatabaseEngineEdition { get; set; } = "SqlServerEnterpriseEdition";
/// <summary>
/// Script all logins available on the server. Passwords will not be scripted.
/// </summary>
public virtual bool? ScriptLogins { get; set; } = false;
/// <summary>
/// Generate object-level permissions.
/// </summary>
public virtual bool? ScriptObjectLevelPermissions { get; set; } = false;
/// <summary>
/// Script owner for the objects.
/// </summary>
public virtual bool? ScriptOwner { get; set; } = false;
/// <summary>
/// Script statistics, and optionally include histograms, for each selected table or view.
/// Possible values:
/// ScriptStatsNone
/// ScriptStatsDDL
/// ScriptStatsAll
/// </summary>
/// <remarks>
/// The default value is ScriptStatsNone.
/// </remarks>
public virtual string ScriptStatistics { get; set; } = "ScriptStatsNone";
/// <summary>
/// Generate USE DATABASE statement.
/// </summary>
public virtual bool? ScriptUseDatabase { get; set; } = true;
/// <summary>
/// Generate script that contains schema only or schema and data.
/// Possible Values:
/// SchemaAndData
/// DataOnly
/// SchemaOnly
/// </summary>
/// <remarks>
/// The default value is SchemaOnly.
/// </remarks>
public virtual string TypeOfDataToScript { get; set; } = "SchemaOnly";
/// <summary>
/// Scripts the change tracking information.
/// </summary>
public virtual bool? ScriptChangeTracking { get; set; } = false;
/// <summary>
/// Script the check constraints for each table or view scripted.
/// </summary>
/// <remarks>
/// The default value is true.
/// </remarks>
public virtual bool? ScriptCheckConstraints { get; set; } = true;
/// <summary>
/// Scripts the data compression information.
/// </summary>
public virtual bool? ScriptDataCompressionOptions { get; set; } = false;
/// <summary>
/// Script the foreign keys for each table scripted.
/// </summary>
/// <remarks>
/// The default value is true.
/// </remarks>
public virtual bool? ScriptForeignKeys { get; set; } = true;
/// <summary>
/// Script the full-text indexes for each table or indexed view scripted.
/// </summary>
public virtual bool? ScriptFullTextIndexes { get; set; } = true;
/// <summary>
/// Script the indexes (including XML and clustered indexes) for each table or indexed view scripted.
/// </summary>
/// <remarks>
/// The default value is true.
/// </remarks>
public virtual bool? ScriptIndexes { get; set; } = true;
/// <summary>
/// Script the primary keys for each table or view scripted
/// </summary>
/// <remarks>
/// The default value is true.
/// </remarks>
public virtual bool? ScriptPrimaryKeys { get; set; } = true;
/// <summary>
/// Script the triggers for each table or view scripted
/// </summary>
public virtual bool? ScriptTriggers { get; set; } = true;
/// <summary>
/// Script the unique keys for each table or view scripted.
/// </summary>
/// <remarks>
/// The default value is true.
/// </remarks>
public virtual bool? UniqueKeys { get; set; } = true;
}
}

View File

@@ -0,0 +1,34 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System.Collections.Generic;
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
namespace Microsoft.Kusto.ServiceLayer.Scripting.Contracts
{
/// <summary>
/// Parameters to indicate the script operation has resolved the objects to be scripted.
/// </summary>
public class ScriptingPlanNotificationParams : ScriptingEventParams
{
/// <summary>
/// Gets or sets the list of database objects whose progress has changed.
/// </summary>
public List<ScriptingObject> ScriptingObjects { get; set; }
/// <summary>
/// Gets or sets the count of database objects whose progress has changed.
/// </summary>
public int Count { get; set; }
}
/// <summary>
/// Event sent to indicate a script operation has determined which objects will be scripted.
/// </summary>
public class ScriptingPlanNotificationEvent
{
public static readonly EventType<ScriptingPlanNotificationParams> Type = EventType<ScriptingPlanNotificationParams>.Create("scripting/scriptPlanNotification");
}
}

View File

@@ -0,0 +1,57 @@
//
// 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.Kusto.ServiceLayer.Scripting.Contracts
{
/// <summary>
/// Parameters sent when a scripting operation has made progress.
/// </summary>
public class ScriptingProgressNotificationParams : ScriptingEventParams
{
/// <summary>
/// Gets or sets the scripting object whose progress has changed.
/// </summary>
public ScriptingObject ScriptingObject { get; set; }
/// <summary>
/// Gets or sets the status of the scripting operation for the scripting object.
/// </summary>
/// <remarks>
/// Values can be: 'Completed', 'Progress', and 'Error'.
/// </remarks>
public string Status { get; set; }
/// <summary>
/// Gets or count of completed scripting operations.
/// </summary>
public int CompletedCount { get; set; }
/// <summary>
/// Gets this total count of objects to script.
/// </summary>
public int TotalCount { get; set; }
/// <summary>
/// Gets or sets the error details if an error occurred scripting a database object.
/// </summary>
public string ErrorDetails { get; set; }
/// <summary>
/// Get or sets the error message for an error that occurred scripting a database object.
/// </summary>
public string ErrorMessage { get; set; }
}
/// <summary>
/// Event to indicate the scripting operation has made progress.
/// </summary>
public class ScriptingProgressNotificationEvent
{
public static readonly EventType<ScriptingProgressNotificationParams> Type =
EventType<ScriptingProgressNotificationParams>.Create("scripting/scriptProgressNotification");
}
}

View File

@@ -0,0 +1,101 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System.Collections.Generic;
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
namespace Microsoft.Kusto.ServiceLayer.Scripting.Contracts
{
/// <summary>
/// Parameters for a script request.
/// </summary>
public class ScriptingParams
{
/// <summary>
/// Gets or sets the file path used when writing out the script.
/// </summary>
public string FilePath { get; set; }
/// <summary>
/// Gets or sets whether scripting to a single file or file per object.
/// </summary>
public string ScriptDestination { get; set; }
/// <summary>
/// Gets or sets connection string of the target database the scripting operation will run against.
/// </summary>
public string ConnectionString { get; set; }
/// <summary>
/// Gets or sets a list of scripting objects to script.
/// </summary>
public List<ScriptingObject> ScriptingObjects { get; set; }
/// <summary>
/// Gets or sets a list of scripting object which specify the include criteria of objects to script.
/// </summary>
public List<ScriptingObject> IncludeObjectCriteria { get; set; }
/// <summary>
/// Gets or sets a list of scripting object which specify the exclude criteria of objects to not script.
/// </summary>
public List<ScriptingObject> ExcludeObjectCriteria { get; set; }
/// <summary>
/// Gets or sets a list of schema name of objects to script.
/// </summary>
public List<string> IncludeSchemas { get; set; }
/// <summary>
/// Gets or sets a list of schema name of objects to not script.
/// </summary>
public List<string> ExcludeSchemas { get; set; }
/// <summary>
/// Gets or sets a list of type name of objects to script.
/// </summary>
public List<string> IncludeTypes { get; set; }
/// <summary>
/// Gets or sets a list of type name of objects to not script
/// </summary>
public List<string> ExcludeTypes { get; set; }
/// <summary>
/// Gets or sets the scripting options.
/// </summary>
public ScriptOptions ScriptOptions { get; set; }
/// <summary>
/// Gets or sets the connection owner URI
/// </summary>
public string OwnerUri { get; set; }
/// <summary>
/// The script operation
/// </summary>
public ScriptingOperationType Operation { get; set; } = ScriptingOperationType.Create;
}
/// <summary>
/// Parameters returned from a script request.
/// </summary>
public class ScriptingResult
{
public string OperationId { get; set; }
public string Script { get; set; }
}
/// <summary>
/// Defines the scripting request type.
/// </summary>
public class ScriptingRequest
{
public static readonly RequestType<ScriptingParams, ScriptingResult> Type =
RequestType<ScriptingParams, ScriptingResult>.Create("scripting/script");
}
}

View File

@@ -0,0 +1,712 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using Microsoft.Kusto.ServiceLayer.Scripting.Contracts;
using Microsoft.SqlTools.Utility;
namespace Microsoft.Kusto.ServiceLayer.Scripting
{
/// <summary>
/// A wrpaper of ScriptOptions to map the option name with the oen in SMO.ScriptingOptions
/// </summary>
public class ScriptAsOptions : ScriptOptions
{
public ScriptAsOptions(ScriptOptions scriptOptions)
{
Validate.IsNotNull(nameof(scriptOptions), scriptOptions);
ScriptOptions = scriptOptions;
}
public ScriptOptions ScriptOptions { get; private set; }
/// <summary>
/// Generate ANSI padding statements
/// </summary>
public override bool? ScriptAnsiPadding
{
get
{
return ScriptOptions.ScriptAnsiPadding;
}
set
{
ScriptOptions.ScriptAnsiPadding = value;
}
}
/// <summary>
/// Append the generated script to a file
/// </summary>
public override bool? AppendToFile
{
get
{
return ScriptOptions.AppendToFile;
}
set
{
ScriptOptions.AppendToFile = value;
}
}
/// <summary>
/// Continue to script if an error occurs. Otherwise, stop.
/// </summary>
/// <remarks>
/// The default is true.
/// </remarks>
public override bool? ContinueScriptingOnError
{
get
{
return ScriptOptions.ContinueScriptingOnError;
}
set
{
ScriptOptions.ContinueScriptingOnError = value;
}
}
/// <summary>
/// Convert user-defined data types to base types.
/// </summary>
public override bool? ConvertUDDTToBaseType
{
get
{
return ScriptOptions.ConvertUDDTToBaseType;
}
set
{
ScriptOptions.ConvertUDDTToBaseType = value;
}
}
/// <summary>
/// Generate script for dependent objects for each object scripted.
/// </summary>
/// <remarks>
/// The default is false.
/// </remarks>
public override bool? GenerateScriptForDependentObjects
{
get
{
return ScriptOptions.GenerateScriptForDependentObjects;
}
set
{
ScriptOptions.GenerateScriptForDependentObjects = value;
}
}
/// <summary>
/// Include descriptive headers for each object generated.
/// </summary>
/// <remarks>
/// The default is true.
/// </remarks>
public override bool? IncludeDescriptiveHeaders
{
get
{
return ScriptOptions.IncludeDescriptiveHeaders;
}
set
{
ScriptOptions.IncludeDescriptiveHeaders = value;
}
}
/// <summary>
/// Check that an object with the given name exists before dropping or altering or that an object with the given name does not exist before creating.
/// </summary>
public override bool? IncludeIfNotExists
{
get
{
return ScriptOptions.IncludeIfNotExists;
}
set
{
ScriptOptions.IncludeIfNotExists = value;
}
}
/// <summary>
/// Script options to set vardecimal storage format.
/// </summary>
public override bool? IncludeVarDecimal
{
get
{
return ScriptOptions.IncludeVarDecimal;
}
set
{
ScriptOptions.IncludeVarDecimal = value;
}
}
/// <summary>
/// Include system generated constraint names to enforce declarative referential integrity.
/// </summary>
public override bool? ScriptDriIncludeSystemNames
{
get
{
return ScriptOptions.ScriptDriIncludeSystemNames;
}
set
{
ScriptOptions.ScriptDriIncludeSystemNames = value;
}
}
/// <summary>
/// Include statements in the script that are not supported on the specified SQL Server database engine type.
/// </summary>
public override bool? IncludeUnsupportedStatements
{
get
{
return ScriptOptions.IncludeUnsupportedStatements;
}
set
{
ScriptOptions.IncludeUnsupportedStatements = value;
}
}
/// <summary>
/// Prefix object names with the object schema.
/// </summary>
/// <remarks>
/// The default is true.
/// </remarks>
public override bool? SchemaQualify
{
get
{
return ScriptOptions.SchemaQualify;
}
set
{
ScriptOptions.SchemaQualify = value;
}
}
/// <summary>
/// Script options to set bindings option.
/// </summary>
public override bool? Bindings
{
get
{
return ScriptOptions.Bindings;
}
set
{
ScriptOptions.Bindings = value;
}
}
/// <summary>
/// Script the objects that use collation.
/// </summary>
public override bool? Collation
{
get
{
return ScriptOptions.Collation;
}
set
{
ScriptOptions.Collation = value;
}
}
/// <summary>
/// Script the default values.
/// </summary>
/// <remarks>
/// The default is true.
/// </remarks>
public override bool? Default
{
get
{
return ScriptOptions.Default;
}
set
{
ScriptOptions.Default = value;
}
}
/// <summary>
/// Script Object CREATE/DROP statements.
/// Possible values:
/// ScriptCreate
/// ScriptDrop
/// ScriptCreateDrop
/// ScriptSelect
/// </summary>
/// <remarks>
/// The default is ScriptCreate.
/// </remarks>
public override string ScriptCreateDrop
{
get
{
return ScriptOptions.ScriptCreateDrop;
}
set
{
ScriptOptions.ScriptCreateDrop = value;
}
}
/// <summary>
/// Script the Extended Properties for each object scripted.
/// </summary>
/// <remarks>
/// The default is true.
/// </remarks>
public override bool? ScriptExtendedProperties
{
get
{
return ScriptOptions.ScriptExtendedProperties;
}
set
{
ScriptOptions.ScriptExtendedProperties = value;
}
}
/// <summary>
/// Script only features compatible with the specified version of SQL Server. Possible values:
/// Script90Compat
/// Script100Compat
/// Script105Compat
/// Script110Compat
/// Script120Compat
/// Script130Compat
/// Script140Compat
/// </summary>
/// <remarks>
/// The default is Script140Compat.
/// </remarks>
public override string ScriptCompatibilityOption
{
get
{
return ScriptOptions.ScriptCompatibilityOption;
}
set
{
ScriptOptions.ScriptCompatibilityOption = value;
}
}
/// <summary>
/// Script only features compatible with the specified SQL Server database engine type.
/// Possible Values:
/// SingleInstance
/// SqlAzure
/// </summary>
public override string TargetDatabaseEngineType
{
get
{
return ScriptOptions.TargetDatabaseEngineType;
}
set
{
ScriptOptions.TargetDatabaseEngineType = value;
}
}
/// <summary>
/// Script only features compatible with the specified SQL Server database engine edition.
/// Possible Values:
/// SqlServerPersonalEdition
/// SqlServerStandardEdition
/// SqlServerEnterpriseEdition
/// SqlServerExpressEdition
/// SqlAzureDatabaseEdition
/// SqlDatawarehouseEdition
/// SqlServerStretchEdition
/// </summary>
public override string TargetDatabaseEngineEdition
{
get
{
return ScriptOptions.TargetDatabaseEngineEdition;
}
set
{
ScriptOptions.TargetDatabaseEngineEdition = value;
}
}
/// <summary>
/// Script all logins available on the server. Passwords will not be scripted.
/// </summary>
public override bool? ScriptLogins
{
get
{
return ScriptOptions.ScriptLogins;
}
set
{
ScriptOptions.ScriptLogins = value;
}
}
/// <summary>
/// Generate object-level permissions.
/// </summary>
public override bool? ScriptObjectLevelPermissions
{
get
{
return ScriptOptions.ScriptObjectLevelPermissions;
}
set
{
ScriptOptions.ScriptObjectLevelPermissions = value;
}
}
/// <summary>
/// Script owner for the objects.
/// </summary>
public override bool? ScriptOwner
{
get
{
return ScriptOptions.ScriptOwner;
}
set
{
ScriptOptions.ScriptOwner = value;
}
}
/// <summary>
/// Script statistics, and optionally include histograms, for each selected table or view.
/// Possible values:
/// ScriptStatsNone
/// ScriptStatsDDL
/// ScriptStatsAll
/// </summary>
/// <remarks>
/// The default value is ScriptStatsNone.
/// </remarks>
public override string ScriptStatistics
{
get
{
return ScriptOptions.ScriptStatistics;
}
set
{
ScriptOptions.ScriptStatistics = value;
}
}
/// <summary>
/// Generate USE DATABASE statement.
/// </summary>
public override bool? ScriptUseDatabase
{
get
{
return ScriptOptions.ScriptUseDatabase;
}
set
{
ScriptOptions.ScriptUseDatabase = value;
}
}
/// <summary>
/// Generate script that contains schema only or schema and data.
/// Possible Values:
/// SchemaAndData
/// DataOnly
/// SchemaOnly
/// </summary>
/// <remarks>
/// The default value is SchemaOnly.
/// </remarks>
public override string TypeOfDataToScript
{
get
{
return ScriptOptions.TypeOfDataToScript;
}
set
{
ScriptOptions.TypeOfDataToScript = value;
}
}
/// <summary>
/// Scripts the change tracking information.
/// </summary>
public override bool? ScriptChangeTracking
{
get
{
return ScriptOptions.ScriptChangeTracking;
}
set
{
ScriptOptions.ScriptChangeTracking = value;
}
}
/// <summary>
/// Script the check constraints for each table or view scripted.
/// </summary>
/// <remarks>
/// The default value is true.
/// </remarks>
public override bool? ScriptCheckConstraints
{
get
{
return ScriptOptions.ScriptCheckConstraints;
}
set
{
ScriptOptions.ScriptCheckConstraints = value;
}
}
/// <summary>
/// Scripts the data compression information.
/// </summary>
public override bool? ScriptDataCompressionOptions
{
get
{
return ScriptOptions.ScriptDataCompressionOptions;
}
set
{
ScriptOptions.ScriptDataCompressionOptions = value;
}
}
/// <summary>
/// Script the foreign keys for each table scripted.
/// </summary>
/// <remarks>
/// The default value is true.
/// </remarks>
public override bool? ScriptForeignKeys
{
get
{
return ScriptOptions.ScriptForeignKeys;
}
set
{
ScriptOptions.ScriptForeignKeys = value;
}
}
/// <summary>
/// Script the full-text indexes for each table or indexed view scripted.
/// </summary>
public override bool? ScriptFullTextIndexes
{
get
{
return ScriptOptions.ScriptFullTextIndexes;
}
set
{
ScriptOptions.ScriptFullTextIndexes = value;
}
}
/// <summary>
/// Script the indexes (including XML and clustered indexes) for each table or indexed view scripted.
/// </summary>
/// <remarks>
/// The default value is true.
/// </remarks>
public override bool? ScriptIndexes
{
get
{
return ScriptOptions.ScriptIndexes;
}
set
{
ScriptOptions.ScriptIndexes = value;
}
}
/// <summary>
/// Script the primary keys for each table or view scripted
/// </summary>
/// <remarks>
/// The default value is true.
/// </remarks>
public override bool? ScriptPrimaryKeys
{
get
{
return ScriptOptions.ScriptPrimaryKeys;
}
set
{
ScriptOptions.ScriptPrimaryKeys = value;
}
}
/// <summary>
/// Script the triggers for each table or view scripted
/// </summary>
public override bool? ScriptTriggers
{
get
{
return ScriptOptions.ScriptTriggers;
}
set
{
ScriptOptions.ScriptTriggers = value;
}
}
/// <summary>
/// Script the unique keys for each table or view scripted.
/// </summary>
/// <remarks>
/// The default value is true.
/// </remarks>
public override bool? UniqueKeys
{
get
{
return ScriptOptions.UniqueKeys;
}
set
{
ScriptOptions.UniqueKeys = value;
}
}
/// <summary>
/// Returns Generate ANSI padding statements
/// </summary>
public bool? AnsiPadding { get { return ScriptOptions.ScriptAnsiPadding; } }
/// <summary>
/// Returns ConvertUDDTToBaseType
/// </summary>
public bool? ConvertUserDefinedDataTypesToBaseType { get { return ScriptOptions.ConvertUDDTToBaseType; } }
/// <summary>
/// Returns IncludeDescriptiveHeaders
/// </summary>
public bool? IncludeHeaders { get { return ScriptOptions.IncludeDescriptiveHeaders; } }
/// <summary>
/// Returns ScriptDriIncludeSystemNames
/// </summary>
public bool? DriIncludeSystemNames { get { return ScriptOptions.ScriptDriIncludeSystemNames; } }
/// <summary>
/// Returns SchemaQualify
/// </summary>
public bool? SchemaQualifyForeignKeysReferences { get { return ScriptOptions.SchemaQualify; } }
/// <summary>
/// Returns false if Collation is true
/// </summary>
public bool? NoCollation { get { return !ScriptOptions.Collation; } }
/// <summary>
/// Returns the value of Default Property
/// </summary>
public bool? DriDefaults { get { return ScriptOptions.Default; } }
/// <summary>
/// Returns the value of ScriptExtendedProperties Property
/// </summary>
public bool? ExtendedProperties { get { return ScriptOptions.ScriptExtendedProperties; } }
/// <summary>
/// Returns the value of ScriptObjectLevelPermissions Property
/// </summary>
public bool? Permissions { get { return ScriptOptions.ScriptObjectLevelPermissions; } }
/// <summary>
/// Returns the value of ScriptStatistics Property
/// </summary>
public string Statistics { get { return ScriptOptions.ScriptStatistics; } }
/// <summary>
/// Returns the value of ScriptChangeTracking Property
/// </summary>
public bool? ChangeTracking { get { return ScriptOptions.ScriptChangeTracking; } }
/// <summary>
/// Returns the value of ScriptCheckConstraints Property
/// </summary>
public bool? DriChecks { get { return ScriptOptions.ScriptCheckConstraints; } }
/// <summary>
/// Returns the value of ScriptDataCompressionOptions Property
/// </summary>
public bool? ScriptDataCompression { get { return ScriptOptions.ScriptDataCompressionOptions; } }
/// <summary>
/// Returns the value of ScriptForeignKeys Property
/// </summary>
public bool? DriForeignKeys { get { return ScriptOptions.ScriptForeignKeys; } }
/// <summary>
/// Returns the value of ScriptFullTextIndexes Property
/// </summary>
public bool? FullTextIndexes { get { return ScriptOptions.ScriptFullTextIndexes; } }
/// <summary>
/// Returns the value of ScriptIndexes Property
/// </summary>
public bool? Indexes { get { return ScriptOptions.ScriptIndexes; } }
/// <summary>
/// Returns the value of ScriptPrimaryKeys Property
/// </summary>
public bool? DriPrimaryKey { get { return ScriptOptions.ScriptPrimaryKeys; } }
/// <summary>
/// Returns the value of ScriptTriggers Property
/// </summary>
public bool? Triggers { get { return ScriptOptions.ScriptTriggers; } }
/// <summary>
/// Returns the value of UniqueKeys Property
/// </summary>
public bool? DriUniqueKeys { get { return ScriptOptions.UniqueKeys; } }
}
}

View File

@@ -0,0 +1,552 @@
//
// 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 Microsoft.Kusto.ServiceLayer.Scripting.Contracts;
using Microsoft.Kusto.ServiceLayer.DataSource;
using Microsoft.SqlTools.Utility;
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.Smo;
using System.Collections.Specialized;
using System.Text;
using System.Globalization;
using Microsoft.SqlServer.Management.SqlScriptPublish;
using Microsoft.Kusto.ServiceLayer.Utility;
using Microsoft.SqlServer.Management.Sdk.Sfc;
using System.Diagnostics;
namespace Microsoft.Kusto.ServiceLayer.Scripting
{
/// <summary>
/// Class to generate script as for one smo object
/// </summary>
public class ScriptAsScriptingOperation : SmoScriptingOperation
{
private static readonly Dictionary<string, SqlServerVersion> scriptCompatibilityMap = LoadScriptCompatibilityMap();
/// <summary>
/// Left delimiter for an named object
/// </summary>
public const char LeftDelimiter = '[';
/// <summary>
/// right delimiter for a named object
/// </summary>
public const char RightDelimiter = ']';
public ScriptAsScriptingOperation(ScriptingParams parameters, IDataSource dataSource): base(parameters)
{
Validate.IsNotNull("dataSource", dataSource);
DataSource = dataSource;
}
public ScriptAsScriptingOperation(ScriptingParams parameters, string azureAccountToken) : base(parameters)
{
DataSource = DataSourceFactory.Create(DataSourceType.Kusto, this.Parameters.ConnectionString, azureAccountToken);
}
internal IDataSource DataSource { get; set; }
private string serverName;
private string databaseName;
private bool disconnectAtDispose = false;
public override void Execute()
{
try
{
this.CancellationToken.ThrowIfCancellationRequested();
this.ValidateScriptDatabaseParams();
this.CancellationToken.ThrowIfCancellationRequested();
string resultScript = string.Empty;
UrnCollection urns = CreateUrns(DataSource);
ScriptingOptions options = new ScriptingOptions();
SetScriptBehavior(options);
ScriptAsOptions scriptAsOptions = new ScriptAsOptions(this.Parameters.ScriptOptions);
PopulateAdvancedScriptOptions(scriptAsOptions, options);
options.WithDependencies = false;
// TODO: Not including the header by default. We have to get this option from client
options.IncludeHeaders = false;
// Scripting data is not avaialable in the scripter
options.ScriptData = false;
SetScriptingOptions(options);
switch (this.Parameters.Operation)
{
case ScriptingOperationType.Select:
resultScript = GenerateScriptSelect(DataSource, urns);
break;
case ScriptingOperationType.Alter:
resultScript = GenerateScriptAlter(DataSource, urns);
break;
}
this.CancellationToken.ThrowIfCancellationRequested();
Logger.Write(
TraceEventType.Verbose,
string.Format(
"Sending script complete notification event for operation {0}",
this.OperationId
));
ScriptText = resultScript;
this.SendCompletionNotificationEvent(new ScriptingCompleteParams
{
Success = true,
});
this.SendPlanNotificationEvent(new ScriptingPlanNotificationParams
{
ScriptingObjects = this.Parameters.ScriptingObjects,
Count = 1,
});
}
catch (Exception e)
{
if (e.IsOperationCanceledException())
{
Logger.Write(TraceEventType.Information, string.Format("Scripting operation {0} was canceled", this.OperationId));
this.SendCompletionNotificationEvent(new ScriptingCompleteParams
{
Canceled = true,
});
}
else
{
Logger.Write(TraceEventType.Error, string.Format("Scripting operation {0} failed with exception {1}", this.OperationId, e));
this.SendCompletionNotificationEvent(new ScriptingCompleteParams
{
OperationId = OperationId,
HasError = true,
ErrorMessage = $"{SR.ScriptingGeneralError} {e.Message}",
ErrorDetails = e.ToString(),
});
}
}
finally
{
if (disconnectAtDispose && DataSource != null)
{
DataSource.Dispose();
}
}
}
private string GenerateScriptSelect(IDataSource dataSource, UrnCollection urns)
{
ScriptingObject scriptingObject = this.Parameters.ScriptingObjects[0];
Urn objectUrn = urns[0];
// select from table
if (string.Equals(scriptingObject.Type, "Table", StringComparison.CurrentCultureIgnoreCase))
{
return new Scripter().SelectFromTableOrView(dataSource, objectUrn);
}
return string.Empty;
}
private string GenerateScriptAlter(IDataSource dataSource, UrnCollection urns)
{
ScriptingObject scriptingObject = this.Parameters.ScriptingObjects[0];
Urn objectUrn = urns[0];
if (string.Equals(scriptingObject.Type, "Function", StringComparison.CurrentCultureIgnoreCase))
{
return new Scripter().AlterFunction(dataSource, scriptingObject);
}
return string.Empty;
}
/// <summary>
/// Generate a schema qualified name (e.g. [schema].[objectName]) for an object if the option for SchemaQualify is true
/// </summary>
/// <param name="schema">The schema name. May be null or empty in which case it will be ignored</param>
/// <param name="objectName">The object name.</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>
static private string GenerateSchemaQualifiedName(string schema, string objectName, bool schemaQualify)
{
var qualifiedName = new StringBuilder();
if (schemaQualify && !String.IsNullOrEmpty(schema))
{
// schema.name
qualifiedName.AppendFormat(CultureInfo.InvariantCulture, "{0}.{1}", GetDelimitedString(schema), GetDelimitedString(objectName));
}
else
{
// name
qualifiedName.AppendFormat(CultureInfo.InvariantCulture, "{0}", GetDelimitedString(objectName));
}
return qualifiedName.ToString();
}
/// <summary>
/// getting delimited string
/// </summary>
/// <param name="str">string</param>
/// <returns>string</returns>
static private string GetDelimitedString(string str)
{
if (string.IsNullOrEmpty(str))
{
return String.Empty;
}
else
{
StringBuilder qualifiedName = new StringBuilder();
qualifiedName.AppendFormat("{0}{1}{2}",
LeftDelimiter,
QuoteObjectName(str),
RightDelimiter);
return qualifiedName.ToString();
}
}
/// <summary>
/// turn a smo datatype object into a type that can be inserted into tsql, e.g. nvarchar(20)
/// </summary>
/// <param name="type"></param>
/// <param name="options"></param>
/// <returns></returns>
internal static string GetDatatype(DataType type, ScriptingOptions options)
{
// string we'll return.
string rv = string.Empty;
string dataType = type.Name;
switch (type.SqlDataType)
{
// char, nchar, nchar, nvarchar, varbinary, nvarbinary are all displayed as type(length)
// length of -1 is taken to be type(max). max isn't localizable.
case SqlDataType.Char:
case SqlDataType.NChar:
case SqlDataType.VarChar:
case SqlDataType.NVarChar:
case SqlDataType.Binary:
case SqlDataType.VarBinary:
rv = string.Format(CultureInfo.InvariantCulture,
"{0}({1})",
dataType,
type.MaximumLength);
break;
case SqlDataType.VarCharMax:
case SqlDataType.NVarCharMax:
case SqlDataType.VarBinaryMax:
rv = string.Format(CultureInfo.InvariantCulture,
"{0}(max)",
dataType);
break;
// numeric and decimal are displayed as type precision,scale
case SqlDataType.Numeric:
case SqlDataType.Decimal:
rv = string.Format(CultureInfo.InvariantCulture,
"{0}({1},{2})",
dataType,
type.NumericPrecision,
type.NumericScale);
break;
//time, datetimeoffset and datetime2 are displayed as type scale
case SqlDataType.Time:
case SqlDataType.DateTimeOffset:
case SqlDataType.DateTime2:
rv = string.Format(CultureInfo.InvariantCulture,
"{0}({1})",
dataType,
type.NumericScale);
break;
// anything else is just type.
case SqlDataType.Xml:
if (type.Schema != null && type.Schema.Length > 0 && dataType != null && dataType.Length > 0)
{
rv = String.Format(CultureInfo.InvariantCulture
, "xml ({0}{2}{1}.{0}{3}{1})"
, LeftDelimiter
, RightDelimiter
, QuoteObjectName(type.Schema)
, QuoteObjectName(dataType));
}
else
{
rv = "xml";
}
break;
case SqlDataType.UserDefinedDataType:
case SqlDataType.UserDefinedTableType:
case SqlDataType.UserDefinedType:
//User defined types may be in a non-DBO schema so append it if necessary
rv = GenerateSchemaQualifiedName(type.Schema, dataType, options.SchemaQualify);
break;
default:
rv = dataType;
break;
}
return rv;
}
/// <summary>
/// Double quotes certain characters in object name
/// </summary>
/// <param name="sqlObject"></param>
public static string QuoteObjectName(string sqlObject)
{
int len = sqlObject.Length;
StringBuilder result = new StringBuilder(sqlObject.Length);
for (int i = 0; i < len; i++)
{
if (sqlObject[i] == ']')
{
result.Append(']');
}
result.Append(sqlObject[i]);
}
return result.ToString();
}
private static void WriteUseDatabase(Database parentObject, StringBuilder stringBuilder , ScriptingOptions options)
{
if (options.IncludeDatabaseContext)
{
string useDb = string.Format(CultureInfo.InvariantCulture, "USE {0}", CommonConstants.DefaultBatchSeperator);
if (!options.NoCommandTerminator)
{
stringBuilder.Append(useDb);
}
else
{
stringBuilder.Append(useDb);
stringBuilder.Append(Environment.NewLine);
}
}
}
private string GetScript(ScriptingOptions options, StringCollection stringCollection)
{
StringBuilder sb = new StringBuilder();
foreach (var item in stringCollection)
{
sb.Append(item);
if (options != null && !options.NoCommandTerminator)
{
//Ensure the batch separator is always on a new line (to avoid syntax errors)
//but don't write an extra if we already have one as this can affect definitions
//of objects such as Stored Procedures (see TFS#9125366)
sb.AppendFormat(CultureInfo.InvariantCulture, "{0}{1}{2}",
item.EndsWith(Environment.NewLine) ? string.Empty : Environment.NewLine,
CommonConstants.DefaultBatchSeperator,
Environment.NewLine);
}
else
{
sb.AppendFormat(CultureInfo.InvariantCulture, Environment.NewLine);
}
}
return sb.ToString();
}
private UrnCollection CreateUrns(IDataSource dataSource)
{
IEnumerable<ScriptingObject> selectedObjects = new List<ScriptingObject>(this.Parameters.ScriptingObjects);
serverName = dataSource.ClusterName;
databaseName = new SqlConnectionStringBuilder(this.Parameters.ConnectionString).InitialCatalog;
UrnCollection urnCollection = new UrnCollection();
foreach (var scriptingObject in selectedObjects)
{
if(string.IsNullOrEmpty(scriptingObject.Schema))
{
// TODO: get the default schema
scriptingObject.Schema = "dbo";
}
urnCollection.Add(scriptingObject.ToUrn(serverName, databaseName));
}
return urnCollection;
}
private void SetScriptBehavior(ScriptingOptions options)
{
// TODO: have to add Scripting behavior to Smo ScriptingOptions class
// so it would support ScriptDropAndScreate
switch (this.Parameters.ScriptOptions.ScriptCreateDrop)
{
case "ScriptCreate":
options.ScriptDrops = false;
break;
case "ScriptDrop":
options.ScriptDrops = true;
break;
default:
options.ScriptDrops = false;
break;
}
}
private static Dictionary<string, SqlServerVersion> LoadScriptCompatibilityMap()
{
return new Dictionary<string, SqlServerVersion>
{
{SqlScriptOptions.ScriptCompatibilityOptions.Script150Compat.ToString(), SqlServerVersion.Version150},
{SqlScriptOptions.ScriptCompatibilityOptions.Script140Compat.ToString(), SqlServerVersion.Version140},
{SqlScriptOptions.ScriptCompatibilityOptions.Script130Compat.ToString(), SqlServerVersion.Version130},
{SqlScriptOptions.ScriptCompatibilityOptions.Script120Compat.ToString(), SqlServerVersion.Version120},
{SqlScriptOptions.ScriptCompatibilityOptions.Script110Compat.ToString(), SqlServerVersion.Version110},
{SqlScriptOptions.ScriptCompatibilityOptions.Script105Compat.ToString(), SqlServerVersion.Version105},
{SqlScriptOptions.ScriptCompatibilityOptions.Script100Compat.ToString(), SqlServerVersion.Version100},
{SqlScriptOptions.ScriptCompatibilityOptions.Script90Compat.ToString(), SqlServerVersion.Version90}
};
}
private void SetScriptingOptions(ScriptingOptions scriptingOptions)
{
scriptingOptions.AllowSystemObjects = true;
// setting this forces SMO to correctly script objects that have been renamed
scriptingOptions.EnforceScriptingOptions = true;
//We always want role memberships for users and database roles to be scripted
scriptingOptions.IncludeDatabaseRoleMemberships = true;
SqlServerVersion targetServerVersion;
if(scriptCompatibilityMap.TryGetValue(this.Parameters.ScriptOptions.ScriptCompatibilityOption, out targetServerVersion))
{
scriptingOptions.TargetServerVersion = targetServerVersion;
}
else
{
//If you are getting this assertion fail it means you are working for higher
//version of SQL Server. You need to update this part of code.
Logger.Write(TraceEventType.Warning, "This part of the code is not updated corresponding to latest version change");
}
// for cloud scripting to work we also have to have Script Compat set to 105.
// the defaults from scripting options should take care of it
object targetDatabaseEngineType;
if (Enum.TryParse(typeof(SqlScriptOptions.ScriptDatabaseEngineType), this.Parameters.ScriptOptions.TargetDatabaseEngineType, out targetDatabaseEngineType))
{
switch ((SqlScriptOptions.ScriptDatabaseEngineType)targetDatabaseEngineType)
{
case SqlScriptOptions.ScriptDatabaseEngineType.SingleInstance:
scriptingOptions.TargetDatabaseEngineType = DatabaseEngineType.Standalone;
break;
case SqlScriptOptions.ScriptDatabaseEngineType.SqlAzure:
scriptingOptions.TargetDatabaseEngineType = DatabaseEngineType.SqlAzureDatabase;
break;
}
}
object targetDatabaseEngineEdition;
if (Enum.TryParse(typeof(SqlScriptOptions.ScriptDatabaseEngineEdition), this.Parameters.ScriptOptions.TargetDatabaseEngineEdition, out targetDatabaseEngineEdition))
{
switch ((SqlScriptOptions.ScriptDatabaseEngineEdition)targetDatabaseEngineEdition)
{
case SqlScriptOptions.ScriptDatabaseEngineEdition.SqlServerPersonalEdition:
scriptingOptions.TargetDatabaseEngineEdition = DatabaseEngineEdition.Personal;
break;
case SqlScriptOptions.ScriptDatabaseEngineEdition.SqlServerStandardEdition:
scriptingOptions.TargetDatabaseEngineEdition = DatabaseEngineEdition.Standard;
break;
case SqlScriptOptions.ScriptDatabaseEngineEdition.SqlServerEnterpriseEdition:
scriptingOptions.TargetDatabaseEngineEdition = DatabaseEngineEdition.Enterprise;
break;
case SqlScriptOptions.ScriptDatabaseEngineEdition.SqlServerExpressEdition:
scriptingOptions.TargetDatabaseEngineEdition = DatabaseEngineEdition.Express;
break;
case SqlScriptOptions.ScriptDatabaseEngineEdition.SqlAzureDatabaseEdition:
scriptingOptions.TargetDatabaseEngineEdition = DatabaseEngineEdition.SqlDatabase;
break;
case SqlScriptOptions.ScriptDatabaseEngineEdition.SqlDatawarehouseEdition:
scriptingOptions.TargetDatabaseEngineEdition = DatabaseEngineEdition.SqlDataWarehouse;
break;
case SqlScriptOptions.ScriptDatabaseEngineEdition.SqlServerStretchEdition:
scriptingOptions.TargetDatabaseEngineEdition = DatabaseEngineEdition.SqlStretchDatabase;
break;
case SqlScriptOptions.ScriptDatabaseEngineEdition.SqlServerManagedInstanceEdition:
scriptingOptions.TargetDatabaseEngineEdition = DatabaseEngineEdition.SqlManagedInstance;
break;
default:
scriptingOptions.TargetDatabaseEngineEdition = DatabaseEngineEdition.Standard;
break;
}
}
if (scriptingOptions.TargetDatabaseEngineEdition == DatabaseEngineEdition.SqlDataWarehouse)
{
scriptingOptions.Triggers = false;
}
scriptingOptions.NoVardecimal = false; //making IncludeVarDecimal true for DPW
// scripting of stats is a combination of the Statistics
// and the OptimizerData flag
object scriptStatistics;
if (Enum.TryParse(typeof(SqlScriptOptions.ScriptStatisticsOptions), this.Parameters.ScriptOptions.ScriptStatistics, out scriptStatistics))
{
switch ((SqlScriptOptions.ScriptStatisticsOptions)scriptStatistics)
{
case SqlScriptOptions.ScriptStatisticsOptions.ScriptStatsAll:
scriptingOptions.Statistics = true;
scriptingOptions.OptimizerData = true;
break;
case SqlScriptOptions.ScriptStatisticsOptions.ScriptStatsDDL:
scriptingOptions.Statistics = true;
scriptingOptions.OptimizerData = false;
break;
case SqlScriptOptions.ScriptStatisticsOptions.ScriptStatsNone:
scriptingOptions.Statistics = false;
scriptingOptions.OptimizerData = false;
break;
}
}
// If Histogram and Update Statics are True then include DriIncludeSystemNames and AnsiPadding by default
if (scriptingOptions.Statistics == true && scriptingOptions.OptimizerData == true)
{
scriptingOptions.DriIncludeSystemNames = true;
scriptingOptions.AnsiPadding = true;
}
}
private void ScripterScriptingError(object sender, ScriptingErrorEventArgs e)
{
this.CancellationToken.ThrowIfCancellationRequested();
Logger.Write(
TraceEventType.Verbose,
string.Format(
"Sending scripting error progress event, Urn={0}, OperationId={1}, Completed={2}, Error={3}",
e.Current,
this.OperationId,
false,
e?.InnerException?.ToString() ?? "null"));
this.SendProgressNotificationEvent(new ScriptingProgressNotificationParams
{
ScriptingObject = e.Current?.ToScriptingObject(),
Status = "Failed",
ErrorMessage = e?.InnerException?.Message,
ErrorDetails = e?.InnerException?.ToString(),
});
}
}
}

View File

@@ -0,0 +1,75 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
using Microsoft.SqlServer.Management.Common;
namespace Microsoft.Kusto.ServiceLayer.Scripting
{
internal partial class Scripter
{
private void Initialize()
{
// Instantiate the mapping dictionaries
// Mapping for supported type
AddSupportedType(DeclarationType.Table, "Table", "table", typeof(Table));
AddSupportedType(DeclarationType.View, "View", "view", typeof(View));
AddSupportedType(DeclarationType.StoredProcedure, "StoredProcedure", "stored procedure", typeof(StoredProcedure));
AddSupportedType(DeclarationType.Schema, "Schema", "schema", typeof(Schema));
AddSupportedType(DeclarationType.UserDefinedDataType, "UserDefinedDataType", "user-defined data type", typeof(UserDefinedDataType));
AddSupportedType(DeclarationType.UserDefinedTableType, "UserDefinedTableType", "user-defined table type", typeof(UserDefinedTableType));
AddSupportedType(DeclarationType.Synonym, "Synonym", "", typeof(Synonym));
AddSupportedType(DeclarationType.ScalarValuedFunction, "Function", "scalar-valued function", typeof(UserDefinedFunction));
AddSupportedType(DeclarationType.TableValuedFunction, "Function", "table-valued function", typeof(UserDefinedFunction));
// Mapping for database engine edition
targetDatabaseEngineEditionMap.Add(DatabaseEngineEdition.Unknown, "SqlServerEnterpriseEdition"); //default case
targetDatabaseEngineEditionMap.Add(DatabaseEngineEdition.Personal, "SqlServerPersonalEdition");
targetDatabaseEngineEditionMap.Add(DatabaseEngineEdition.Standard, "SqlServerStandardEdition");
targetDatabaseEngineEditionMap.Add(DatabaseEngineEdition.Enterprise, "SqlServerEnterpriseEdition");
targetDatabaseEngineEditionMap.Add(DatabaseEngineEdition.Express, "SqlServerExpressEdition");
targetDatabaseEngineEditionMap.Add(DatabaseEngineEdition.SqlDatabase, "SqlAzureDatabaseEdition");
targetDatabaseEngineEditionMap.Add(DatabaseEngineEdition.SqlDataWarehouse, "SqlDatawarehouseEdition");
targetDatabaseEngineEditionMap.Add(DatabaseEngineEdition.SqlStretchDatabase, "SqlServerStretchEdition");
targetDatabaseEngineEditionMap.Add(DatabaseEngineEdition.SqlManagedInstance, "SqlServerManagedInstance");
// Mapping for database engine type
serverVersionMap.Add(9, "Script90Compat");
serverVersionMap.Add(10, "Script100Compat");
serverVersionMap.Add(11, "Script110Compat");
serverVersionMap.Add(12, "Script120Compat");
serverVersionMap.Add(13, "Script140Compat");
serverVersionMap.Add(14, "Script140Compat");
serverVersionMap.Add(15, "Script140Compat");
// Mapping the object types for scripting
objectScriptMap.Add("table", "Table");
objectScriptMap.Add("view", "View");
objectScriptMap.Add("function", "Function");
objectScriptMap.Add("storedprocedure", "Procedure");
objectScriptMap.Add("userdefinedfunction", "Function");
objectScriptMap.Add("tablevaluedfunction", "Function");
objectScriptMap.Add("userdefineddatatype", "Type");
objectScriptMap.Add("user", "User");
objectScriptMap.Add("default", "Default");
objectScriptMap.Add("rule", "Rule");
objectScriptMap.Add("databaserole", "Role");
objectScriptMap.Add("applicationrole", "Application Role");
objectScriptMap.Add("sqlassembly", "Assembly");
objectScriptMap.Add("ddltrigger", "Trigger");
objectScriptMap.Add("synonym", "Synonym");
objectScriptMap.Add("xmlschemacollection", "Xml Schema Collection");
objectScriptMap.Add("schema", "Schema");
objectScriptMap.Add("planguide", "sp_create_plan_guide");
objectScriptMap.Add("userdefinedtype", "Type");
objectScriptMap.Add("userdefinedaggregate", "Aggregate");
objectScriptMap.Add("fulltextcatalog", "Fulltext Catalog");
objectScriptMap.Add("userdefinedtabletype", "Type");
}
}
}

View File

@@ -0,0 +1,683 @@
//
// 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

@@ -0,0 +1,130 @@
//
// 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.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.SqlServer.Management.Sdk.Sfc;
using Microsoft.SqlServer.Management.SqlScriptPublish;
using Microsoft.Kusto.ServiceLayer.Scripting.Contracts;
using Microsoft.SqlTools.Utility;
namespace Microsoft.Kusto.ServiceLayer.Scripting
{
/// <summary>
/// Extension methods used by the scripting service.
/// </summary>
internal static class ScriptingExtensionMethods
{
/// <summary>
/// Gets the status of a scripting operation for the passed scripting event.
/// </summary>
/// <param name="e">The scripting event.</param>
/// <returns>The status.</returns>
public static string GetStatus(this ScriptEventArgs e)
{
Validate.IsNotNull("e", e);
string status = null;
if (e.Error != null)
{
status = "Error";
}
else if (e.Completed)
{
status = "Completed";
}
else
{
status = "Progress";
}
return status;
}
/// <summary>
/// Returns a list of ScriptingObject instances for the passed SqlScriptPublishModel instance.
/// </summary>
/// <param name="publishModel">The sql script publish model instance.</param>
/// <returns>The list of scripting objects.</returns>
public static List<ScriptingObject> GetDatabaseObjects(this SqlScriptPublishModel publishModel)
{
Validate.IsNotNull("publishModel", publishModel);
List<ScriptingObject> databaseObjects = new List<ScriptingObject>();
IEnumerable<DatabaseObjectType> objectTypes = publishModel.GetDatabaseObjectTypes();
Logger.Write(
TraceEventType.Verbose,
string.Format(
"Loaded SMO object type count {0}, types: {1}",
objectTypes.Count(),
string.Join(", ", objectTypes)));
foreach (DatabaseObjectType objectType in objectTypes)
{
IEnumerable<KeyValuePair<string, string>> databaseObjectsOfType = publishModel.EnumChildrenForDatabaseObjectType(objectType);
Logger.Write(
TraceEventType.Verbose,
string.Format(
"Loaded SMO urn object count {0} for type {1}, urns: {2}",
objectType,
databaseObjectsOfType.Count(),
string.Join(", ", databaseObjectsOfType.Select(p => p.Value))));
databaseObjects.AddRange(databaseObjectsOfType.Select(d => new Urn(d.Value).ToScriptingObject()));
}
return databaseObjects;
}
/// <summary>
/// Creates a SMO Urn instance based on the passed ScriptingObject instance.
/// </summary>
/// <param name="scriptingObject">The scripting object instance.</param>
/// <param name="database">The name of the database referenced by the Urn.</param>
/// <returns>The Urn instance.</returns>
public static Urn ToUrn(this ScriptingObject scriptingObject, string server, string database)
{
Validate.IsNotNull("scriptingObject", scriptingObject);
Validate.IsNotNullOrEmptyString("server", server);
Validate.IsNotNullOrWhitespaceString("scriptingObject.Name", scriptingObject.Name);
Validate.IsNotNullOrWhitespaceString("scriptingObject.Type", scriptingObject.Type);
// Leaving the server name blank will automatically match whatever the server SMO is running against.
string urn = string.Format(
"Server[@Name='{0}']/Database[@Name='{1}']/{2}[@Name='{3}' {4}]",
server.ToUpper(),
Urn.EscapeString(database),
scriptingObject.Type,
Urn.EscapeString(scriptingObject.Name),
scriptingObject.Schema != null ? string.Format("and @Schema = '{0}'", Urn.EscapeString(scriptingObject.Schema)) : string.Empty);
return new Urn(urn);
}
/// <summary>
/// Creates a ScriptingObject instance based on the passed SMO Urn instance.
/// </summary>
/// <param name="urn">The urn instance.</param>
/// <returns>The scripting object instance.</returns>
public static ScriptingObject ToScriptingObject(this Urn urn)
{
Validate.IsNotNull("urn", urn);
return new ScriptingObject
{
Type = urn.Type,
Schema = urn.GetAttribute("Schema"),
Name = urn.GetAttribute("Name"),
};
}
}
}

View File

@@ -0,0 +1,134 @@
//
// 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.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.SqlServer.Management.Sdk.Sfc;
using Microsoft.SqlServer.Management.SqlScriptPublish;
using Microsoft.SqlTools.Hosting.Protocol;
using Microsoft.Kusto.ServiceLayer.Scripting.Contracts;
using Microsoft.SqlTools.Utility;
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
using System.Diagnostics;
namespace Microsoft.Kusto.ServiceLayer.Scripting
{
/// <summary>
/// Class to represent an in-progress list objects operation.
/// </summary>
public sealed class ScriptingListObjectsOperation : ScriptingOperation
{
private bool disposed = false;
public ScriptingListObjectsOperation(ScriptingListObjectsParams parameters)
{
Validate.IsNotNull("parameters", parameters);
this.Parameters = parameters;
}
private ScriptingListObjectsParams Parameters { get; set; }
/// <summary>
/// Event raised when a the list object operation is complete.
/// </summary>
/// <remarks>
/// An event can be completed by the following conditions: success, cancel, error.
/// </remarks>
public event EventHandler<ScriptingListObjectsCompleteParams> CompleteNotification;
public override void Execute()
{
if (this.CancellationToken.IsCancellationRequested)
{
throw new OperationCanceledException(this.CancellationToken);
}
SqlScriptPublishModel publishModel = null;
try
{
this.ValidateScriptDatabaseParams();
publishModel = new SqlScriptPublishModel(this.Parameters.ConnectionString);
List<ScriptingObject> databaseObjects = publishModel.GetDatabaseObjects();
Logger.Write(
TraceEventType.Verbose,
string.Format(
"Sending list object completion notification count {0}, objects: {1}",
databaseObjects,
string.Join(", ", databaseObjects)));
this.SendCompletionNotificationEvent(new ScriptingListObjectsCompleteParams
{
OperationId = this.OperationId,
ScriptingObjects = databaseObjects,
Count = databaseObjects.Count,
Success = true,
SequenceNumber = 1,
});
}
catch (Exception e)
{
Logger.Write(TraceEventType.Information, string.Format("Scripting operation {0} was canceled", this.OperationId));
if (e.IsOperationCanceledException())
{
this.SendCompletionNotificationEvent(new ScriptingListObjectsCompleteParams
{
OperationId = this.OperationId,
Canceled = true,
});
}
else
{
Logger.Write(TraceEventType.Error, string.Format("Scripting operation {0} failed with exception {1}", this.OperationId, e));
this.SendCompletionNotificationEvent(new ScriptingListObjectsCompleteParams
{
OperationId = this.OperationId,
HasError = true,
ErrorMessage = e.Message,
ErrorDetails = e.ToString(),
});
}
}
}
private void SendCompletionNotificationEvent(ScriptingListObjectsCompleteParams parameters)
{
this.CompleteNotification?.Invoke(this, parameters);
}
private void ValidateScriptDatabaseParams()
{
try
{
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(this.Parameters.ConnectionString);
}
catch (Exception e)
{
throw new ArgumentException(SR.ScriptingListObjectsCompleteParams_ConnectionString_Property_Invalid, e);
}
}
/// <summary>
/// Disposes the scripting operation.
/// </summary>
public override void Dispose()
{
if (!disposed)
{
this.Cancel();
disposed = true;
}
}
}
}

View File

@@ -0,0 +1,245 @@
//
// 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.Linq;
using Microsoft.Kusto.ServiceLayer.Scripting.Contracts;
using Microsoft.SqlTools.Utility;
namespace Microsoft.Kusto.ServiceLayer.Scripting
{
/// <summary>
/// Implements matching logic to filter scripting objects based on an
/// include/exclude criteria.
/// </summary>
/// <remarks>
/// First, objects are included by the include filter. Then, objects are removed by
/// the exclude filter. Matches are made by comparing case insensitive strings for the
/// ScriptingObject Type, Schema, and Name properties. Wildcards '*' are supported for
/// the ScriptingObject Schema and Name properties. Matching on ScriptingObject Type
/// property must be an exact match.
///
/// Examples:
///
/// Include ScriptingObject { Type = null, Schema = "dbo", Name = null }
/// -> matches all objects in the dbo schema.
///
/// Include ScriptingObject { Type = "Table", Schema = "dbo", Name = null }
/// -> matches all tables in the dbo schema.
///
/// Include ScriptingObject { Type = "Table", Schema = null, Name = "Emp*" }
/// -> matches all table names that start with "Emp"
///
/// Include ScriptingObject { Type = "View", Schema = null, Name = "Emp*" }
/// Include ScriptingObject { Type = "Table", Schema = null, Name = "Emp*" }
/// -> matches all table and views with names that start with "Emp"
///
/// Include ScriptingObject { Type = "Table", Schema = null, Name = null }
/// Exclude ScriptingObject { Type = null, Schema = "HumanResources", Name = null }
/// -> matches all tables except tables in the "HumanResources" schema
///
/// </remarks>
public static class ScriptingObjectMatcher
{
private const string Wildcard = "*";
/// <summary>
/// Given a collection of candidate scripting objects, filters the items that match
/// based on the passed include and exclude criteria.
/// </summary>
/// <param name="includeCriteria">The include object criteria.</param>
/// <param name="excludeCriteria">The exclude object criteria.</param>
/// <param name="includeSchemas">The include schema filter.</param>
/// <param name="excludeSchemas">The exclude schema filter.</param>
/// <param name="includeTypes">The include type filter.</param>
/// <param name="excludeTypes">The exclude type filter.</param>
/// <param name="candidates">The candidate object to filter.</param>
/// <returns>The matching scripting objects.</returns>
public static IEnumerable<ScriptingObject> Match(
ScriptingObject includeCriteria,
ScriptingObject excludeCriteria,
string includeSchemas,
string excludeSchemas,
string includeTypes,
string excludeTypes,
IEnumerable<ScriptingObject> candidates)
{
return Match(
includeCriteria == null ? new ScriptingObject[0] : new[] { includeCriteria },
excludeCriteria == null ? new ScriptingObject[0] : new[] { excludeCriteria },
includeSchemas == null ? new List<string>(): new List<string> { includeSchemas },
excludeSchemas == null ? new List<string>(): new List<string> { excludeSchemas },
includeTypes == null ? new List<string>(): new List<string> { includeTypes },
excludeTypes == null ? new List<string>(): new List<string> { excludeTypes },
candidates);
}
/// <summary>
/// Given a collection of candidate scripting objects, filters the items that match
/// based on the passed include and exclude criteria.
/// </summary>
/// <param name="includeCriteria">The collection of include object criteria items.</param>
/// <param name="excludeCriteria">The collection of exclude object criteria items.</param>
/// <param name="includeSchemas">The collection of include schema items.</param>
/// <param name="excludeSchemas">The collection of exclude schema items.</param>
/// <param name="includeTypes">The collection of include type items.</param>
/// <param name="excludeTypes">The collection of exclude type items.</param>
/// <param name="candidates">The candidate object to filter.</param>
/// <returns>The matching scripting objects.</returns>
public static IEnumerable<ScriptingObject> Match(
IEnumerable<ScriptingObject> includeCriteria,
IEnumerable<ScriptingObject> excludeCriteria,
IEnumerable<string> includeSchemas,
IEnumerable<string> excludeSchemas,
IEnumerable<string> includeTypes,
IEnumerable<string> excludeTypes,
IEnumerable<ScriptingObject> candidates)
{
Validate.IsNotNull("candidates", candidates);
IEnumerable<ScriptingObject> matchedObjects = new List<ScriptingObject>();
if (includeCriteria != null && includeCriteria.Any())
{
foreach (ScriptingObject scriptingObjectCriteria in includeCriteria)
{
IEnumerable<ScriptingObject> matches = MatchCriteria(scriptingObjectCriteria, candidates);
matchedObjects = matchedObjects.Union(matches);
}
}
else
{
matchedObjects = candidates;
}
if (excludeCriteria != null && excludeCriteria.Any())
{
foreach (ScriptingObject scriptingObjectCriteria in excludeCriteria)
{
IEnumerable<ScriptingObject> matches = MatchCriteria(scriptingObjectCriteria, matchedObjects);
matchedObjects = matchedObjects.Except(matches);
}
}
// Apply additional filters if included.
matchedObjects = ExcludeSchemaAndOrType(excludeSchemas, excludeTypes, matchedObjects);
matchedObjects = IncludeSchemaAndOrType(includeSchemas, includeTypes, matchedObjects);
return matchedObjects;
}
private static IEnumerable<ScriptingObject> ExcludeSchemaAndOrType(IEnumerable<string> excludeSchemas, IEnumerable<string> excludeTypes,
IEnumerable<ScriptingObject> candidates)
{
// Given a list of candidates, we remove any objects that match the excluded schema and/or type.
IEnumerable<ScriptingObject> remainingObjects = candidates;
IEnumerable<ScriptingObject> matches = null;
if (excludeSchemas != null && excludeSchemas.Any())
{
foreach (string exclude_schema in excludeSchemas)
{
matches = MatchCriteria(exclude_schema, (candidate) => { return candidate.Schema; }, candidates);
remainingObjects = remainingObjects.Except(matches);
}
}
if (excludeTypes != null && excludeTypes.Any())
{
foreach (string exclude_type in excludeTypes)
{
matches = remainingObjects.Where(o => string.Equals(exclude_type, o.Type, StringComparison.OrdinalIgnoreCase));
remainingObjects = remainingObjects.Except(matches);
}
}
return remainingObjects;
}
private static IEnumerable<ScriptingObject> IncludeSchemaAndOrType(IEnumerable<string> includeSchemas, IEnumerable<string> includeTypes,
IEnumerable<ScriptingObject> candidates)
{
// Given a list of candidates, we return a new list of scripting objects that match
// the schema and/or type filter.
IEnumerable<ScriptingObject> matchedSchema = new List<ScriptingObject>();
IEnumerable<ScriptingObject> matchedType = new List<ScriptingObject>();
IEnumerable<ScriptingObject> matchedObjects = new List<ScriptingObject>();
IEnumerable<ScriptingObject> matches = null;
if (includeSchemas != null && includeSchemas.Any())
{
foreach (string include_schema in includeSchemas)
{
matches = MatchCriteria(include_schema, (candidate) => { return candidate.Schema; }, candidates);
matchedSchema = matchedSchema.Union(matches);
}
matchedObjects = matchedSchema;
}
else
{
matchedObjects = candidates;
}
if (includeTypes != null && includeTypes.Any())
{
foreach (string include_type in includeTypes)
{
matches = matchedObjects.Where(o => string.Equals(include_type, o.Type, StringComparison.OrdinalIgnoreCase));
matchedType = matchedType.Union(matches);
}
matchedObjects = matchedType;
}
return matchedObjects;
}
private static IEnumerable<ScriptingObject> MatchCriteria(ScriptingObject criteria, IEnumerable<ScriptingObject> candidates)
{
Validate.IsNotNull("criteria", criteria);
Validate.IsNotNull("candidates", candidates);
IEnumerable<ScriptingObject> matchedObjects = candidates;
if (!string.IsNullOrWhiteSpace(criteria.Type))
{
matchedObjects = matchedObjects.Where(o => string.Equals(criteria.Type, o.Type, StringComparison.OrdinalIgnoreCase));
}
matchedObjects = MatchCriteria(criteria.Schema, (candidate) => { return candidate.Schema; }, matchedObjects);
matchedObjects = MatchCriteria(criteria.Name, (candidate) => { return candidate.Name; }, matchedObjects);
return matchedObjects;
}
private static IEnumerable<ScriptingObject> MatchCriteria(string property, Func<ScriptingObject, string> propertySelector, IEnumerable<ScriptingObject> candidates)
{
IEnumerable<ScriptingObject> matchedObjects = candidates;
if (!string.IsNullOrWhiteSpace(property))
{
if (property.Equals(Wildcard, StringComparison.OrdinalIgnoreCase))
{
// Don't filter any objects
}
if (property.EndsWith(Wildcard, StringComparison.OrdinalIgnoreCase))
{
matchedObjects = candidates.Where(
o =>
propertySelector(o) != null &&
propertySelector(o).StartsWith(
propertySelector(o).Substring(0, propertySelector(o).Length - 1),
StringComparison.OrdinalIgnoreCase));
}
else
{
matchedObjects = matchedObjects.Where(o => string.Equals(property, propertySelector(o), StringComparison.OrdinalIgnoreCase));
}
}
return matchedObjects;
}
}
}

View File

@@ -0,0 +1,57 @@
//
// 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.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.SqlTools.Utility;
namespace Microsoft.Kusto.ServiceLayer.Scripting
{
/// <summary>
/// Base class for scripting operations. Because scripting operations can be very long
/// running, there my be multiple concurrent scripting operations. To distinguish events
/// between concurrent scripting operations, use the operation id.
/// </summary>
public abstract class ScriptingOperation : IDisposable
{
private CancellationTokenSource cancellation = new CancellationTokenSource();
protected ScriptingOperation()
{
this.OperationId = Guid.NewGuid().ToString();
}
protected CancellationToken CancellationToken { get { return this.cancellation.Token; } }
/// <summary>
/// Gets the unique id associated with this instance.
/// </summary>
public string OperationId { get; private set; }
/// <summary>
/// Excecutes the scripting operation.
/// </summary>
public abstract void Execute();
/// <summary>
/// Cancels the scripting operation.
/// </summary>
public virtual void Cancel()
{
if (!this.cancellation.IsCancellationRequested)
{
Logger.Write(TraceEventType.Verbose, string.Format("Cancel invoked for OperationId {0}", this.OperationId));
this.cancellation.Cancel();
}
}
/// <summary>
/// Disposes the scripting operation.
/// </summary>
public abstract void Dispose();
}
}

View File

@@ -0,0 +1,298 @@
//
// 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.Diagnostics;
using System.Linq;
using Microsoft.SqlServer.Management.SqlScriptPublish;
using Microsoft.Kusto.ServiceLayer.Scripting.Contracts;
using Microsoft.SqlTools.Utility;
namespace Microsoft.Kusto.ServiceLayer.Scripting
{
/// <summary>
/// Class to represent an in-progress script operation.
/// </summary>
public sealed class ScriptingScriptOperation : SmoScriptingOperation
{
private int scriptedObjectCount = 0;
private int totalScriptedObjectCount = 0;
private int eventSequenceNumber = 1;
private string azureAccessToken;
public ScriptingScriptOperation(ScriptingParams parameters, string azureAccessToken): base(parameters)
{
this.azureAccessToken = azureAccessToken;
}
public override void Execute()
{
SqlScriptPublishModel publishModel = null;
try
{
this.CancellationToken.ThrowIfCancellationRequested();
this.ValidateScriptDatabaseParams();
publishModel = BuildPublishModel();
publishModel.ScriptItemsCollected += this.OnPublishModelScriptItemsCollected;
publishModel.ScriptProgress += this.OnPublishModelScriptProgress;
publishModel.ScriptError += this.OnPublishModelScriptError;
publishModel.AllowSystemObjects = true;
ScriptDestination destination = !string.IsNullOrWhiteSpace(this.Parameters.ScriptDestination)
? (ScriptDestination)Enum.Parse(typeof(ScriptDestination), this.Parameters.ScriptDestination)
: ScriptDestination.ToSingleFile;
// SMO is currently hardcoded to produce UTF-8 encoding when running on dotnet core.
ScriptOutputOptions outputOptions = new ScriptOutputOptions
{
SaveFileMode = ScriptFileMode.Overwrite,
SaveFileName = this.Parameters.FilePath,
ScriptDestination = destination,
};
this.CancellationToken.ThrowIfCancellationRequested();
publishModel.GenerateScript(outputOptions);
this.CancellationToken.ThrowIfCancellationRequested();
Logger.Write(
TraceEventType.Verbose,
string.Format(
"Sending script complete notification event for operation {0}, sequence number {1} with total count {2} and scripted count {3}",
this.OperationId,
this.eventSequenceNumber,
this.totalScriptedObjectCount,
this.scriptedObjectCount));
ScriptText = publishModel.RawScript;
this.SendCompletionNotificationEvent(new ScriptingCompleteParams
{
Success = true,
});
}
catch (Exception e)
{
if (e.IsOperationCanceledException())
{
Logger.Write(TraceEventType.Information, string.Format("Scripting operation {0} was canceled", this.OperationId));
this.SendCompletionNotificationEvent(new ScriptingCompleteParams
{
Canceled = true,
});
}
else
{
Logger.Write(TraceEventType.Error, string.Format("Scripting operation {0} failed with exception {1}", this.OperationId, e));
this.SendCompletionNotificationEvent(new ScriptingCompleteParams
{
HasError = true,
ErrorMessage = e.Message,
ErrorDetails = e.ToString(),
});
}
}
finally
{
if (publishModel != null)
{
publishModel.ScriptItemsCollected -= this.OnPublishModelScriptItemsCollected;
publishModel.ScriptProgress -= this.OnPublishModelScriptProgress;
publishModel.ScriptError -= this.OnPublishModelScriptError;
}
}
}
protected override void SendCompletionNotificationEvent(ScriptingCompleteParams parameters)
{
base.SendCompletionNotificationEvent(parameters);
}
protected override void SendPlanNotificationEvent(ScriptingPlanNotificationParams parameters)
{
base.SendPlanNotificationEvent(parameters);
}
protected override void SendProgressNotificationEvent(ScriptingProgressNotificationParams parameters)
{
base.SendProgressNotificationEvent(parameters);
}
protected override void SetCommonEventProperties(ScriptingEventParams parameters)
{
base.SetCommonEventProperties(parameters);
parameters.SequenceNumber = this.eventSequenceNumber;
this.eventSequenceNumber += 1;
}
private SqlScriptPublishModel BuildPublishModel()
{
SqlScriptPublishModel publishModel = new SqlScriptPublishModel(this.Parameters.ConnectionString);
// See if any filtering criteria was specified. If not, we're scripting the entire database. Otherwise, the filtering
// criteria should include the target objects to script.
//
bool hasObjectsSpecified = this.Parameters.ScriptingObjects != null && this.Parameters.ScriptingObjects.Any();
bool hasCriteriaSpecified =
(this.Parameters.IncludeObjectCriteria != null && this.Parameters.IncludeObjectCriteria.Any()) ||
(this.Parameters.ExcludeObjectCriteria != null && this.Parameters.ExcludeObjectCriteria.Any()) ||
(this.Parameters.IncludeSchemas != null && this.Parameters.IncludeSchemas.Any()) ||
(this.Parameters.ExcludeSchemas != null && this.Parameters.ExcludeSchemas.Any()) ||
(this.Parameters.IncludeTypes != null && this.Parameters.IncludeTypes.Any()) ||
(this.Parameters.ExcludeTypes != null && this.Parameters.ExcludeTypes.Any());
bool scriptAllObjects = !hasObjectsSpecified && !hasCriteriaSpecified;
// In the getter for SqlScriptPublishModel.AdvancedOptions, there is some strange logic which will
// cause the SqlScriptPublishModel.AdvancedOptions to get reset and lose all values based the ordering
// of when SqlScriptPublishModel.ScriptAllObjects is set.
//
publishModel.ScriptAllObjects = scriptAllObjects;
if (scriptAllObjects)
{
// Due to the getter logic within publishModel.AdvancedOptions, we explicitly populate the options
// after we determine what objects we are scripting.
//
PopulateAdvancedScriptOptions(this.Parameters.ScriptOptions, publishModel.AdvancedOptions);
return publishModel;
}
IEnumerable<ScriptingObject> selectedObjects = new List<ScriptingObject>();
if (hasCriteriaSpecified)
{
// This is an expensive remote call to load all objects from the database.
//
List<ScriptingObject> allObjects = publishModel.GetDatabaseObjects();
selectedObjects = ScriptingObjectMatcher.Match(
this.Parameters.IncludeObjectCriteria,
this.Parameters.ExcludeObjectCriteria,
this.Parameters.IncludeSchemas,
this.Parameters.ExcludeSchemas,
this.Parameters.IncludeTypes,
this.Parameters.ExcludeTypes,
allObjects);
}
if (hasObjectsSpecified)
{
selectedObjects = selectedObjects.Union(this.Parameters.ScriptingObjects);
}
// Populating advanced options after we select our objects in question, otherwise we lose all
// advanced options. After this call to PopulateAdvancedScriptOptions, DO NOT reference the
// publishModel.AdvancedOptions getter as it will reset the options in the model.
//
PopulateAdvancedScriptOptions(this.Parameters.ScriptOptions, publishModel.AdvancedOptions);
Logger.Write(
TraceEventType.Information,
string.Format(
"Scripting object count {0}, objects: {1}",
selectedObjects.Count(),
string.Join(", ", selectedObjects)));
string server = GetServerNameFromLiveInstance(this.Parameters.ConnectionString, this.azureAccessToken);
string database = new SqlConnectionStringBuilder(this.Parameters.ConnectionString).InitialCatalog;
foreach (ScriptingObject scriptingObject in selectedObjects)
{
publishModel.SelectedObjects.Add(scriptingObject.ToUrn(server, database));
}
return publishModel;
}
private void OnPublishModelScriptError(object sender, ScriptEventArgs e)
{
this.CancellationToken.ThrowIfCancellationRequested();
Logger.Write(
TraceEventType.Verbose,
string.Format(
"Sending scripting error progress event, Urn={0}, OperationId={1}, Sequence={2}, Completed={3}, Error={4}",
e.Urn,
this.OperationId,
this.eventSequenceNumber,
e.Completed,
e?.Error?.ToString() ?? "null"));
// Keep scripting...it's a best effort operation.
e.ContinueScripting = true;
this.SendProgressNotificationEvent(new ScriptingProgressNotificationParams
{
ScriptingObject = e.Urn?.ToScriptingObject(),
Status = e.GetStatus(),
CompletedCount = this.scriptedObjectCount,
TotalCount = this.totalScriptedObjectCount,
ErrorMessage = e?.Error?.Message,
ErrorDetails = e?.Error?.ToString(),
});
}
private void OnPublishModelScriptItemsCollected(object sender, ScriptItemsArgs e)
{
this.CancellationToken.ThrowIfCancellationRequested();
List<ScriptingObject> scriptingObjects = e.Urns.Select(urn => urn.ToScriptingObject()).ToList();
this.totalScriptedObjectCount = scriptingObjects.Count;
Logger.Write(
TraceEventType.Verbose,
string.Format(
"Sending scripting plan notification event OperationId={0}, Sequence={1}, Count={2}, Objects: {3}",
this.OperationId,
this.eventSequenceNumber,
this.totalScriptedObjectCount,
string.Join(", ", e.Urns)));
this.SendPlanNotificationEvent(new ScriptingPlanNotificationParams
{
ScriptingObjects = scriptingObjects,
Count = scriptingObjects.Count,
});
}
private void OnPublishModelScriptProgress(object sender, ScriptEventArgs e)
{
this.CancellationToken.ThrowIfCancellationRequested();
if (e.Completed)
{
this.scriptedObjectCount += 1;
}
Logger.Write(
TraceEventType.Verbose,
string.Format(
"Sending progress event, Urn={0}, OperationId={1}, Sequence={2}, Status={3}, Error={4}",
e.Urn,
this.OperationId,
this.eventSequenceNumber,
e.GetStatus(),
e?.Error?.ToString() ?? "null"));
this.SendProgressNotificationEvent(new ScriptingProgressNotificationParams
{
ScriptingObject = e.Urn.ToScriptingObject(),
Status = e.GetStatus(),
CompletedCount = this.scriptedObjectCount,
TotalCount = this.totalScriptedObjectCount,
ErrorMessage = e?.Error?.Message,
ErrorDetails = e?.Error?.ToString(),
});
}
}
}

View File

@@ -0,0 +1,254 @@
//
// 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.Concurrent;
using System.Diagnostics;
using System.Threading.Tasks;
using Microsoft.SqlTools.Hosting.Protocol;
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
using Microsoft.Kusto.ServiceLayer.Connection;
using Microsoft.Kusto.ServiceLayer.Hosting;
using Microsoft.Kusto.ServiceLayer.Scripting.Contracts;
using Microsoft.SqlTools.Utility;
using Microsoft.Kusto.ServiceLayer.Utility;
namespace Microsoft.Kusto.ServiceLayer.Scripting
{
/// <summary>
/// Main class for Scripting Service functionality
/// </summary>
public sealed class ScriptingService : IDisposable
{
private const int ScriptingOperationTimeout = 60000;
private static readonly Lazy<ScriptingService> LazyInstance = new Lazy<ScriptingService>(() => new ScriptingService());
public static ScriptingService Instance => LazyInstance.Value;
private static ConnectionService connectionService = null;
private readonly Lazy<ConcurrentDictionary<string, ScriptingOperation>> operations =
new Lazy<ConcurrentDictionary<string, ScriptingOperation>>(() => new ConcurrentDictionary<string, ScriptingOperation>());
private bool disposed;
/// <summary>
/// Internal for testing purposes only
/// </summary>
internal static ConnectionService ConnectionServiceInstance
{
get
{
if (connectionService == null)
{
connectionService = ConnectionService.Instance;
}
return connectionService;
}
set
{
connectionService = value;
}
}
/// <summary>
/// The collection of active operations
/// </summary>
internal ConcurrentDictionary<string, ScriptingOperation> ActiveOperations => operations.Value;
/// <summary>
/// Initializes the Scripting Service instance
/// </summary>
/// <param name="serviceHost"></param>
/// <param name="context"></param>
public void InitializeService(ServiceHost serviceHost)
{
serviceHost.SetRequestHandler(ScriptingRequest.Type, this.HandleScriptExecuteRequest);
serviceHost.SetRequestHandler(ScriptingCancelRequest.Type, this.HandleScriptCancelRequest);
serviceHost.SetRequestHandler(ScriptingListObjectsRequest.Type, this.HandleListObjectsRequest);
// Register handler for shutdown event
serviceHost.RegisterShutdownTask((shutdownParams, requestContext) =>
{
this.Dispose();
return Task.FromResult(0);
});
}
/// <summary>
/// Handles request to execute start the list objects operation.
/// </summary>
private async Task HandleListObjectsRequest(ScriptingListObjectsParams parameters, RequestContext<ScriptingListObjectsResult> requestContext)
{
try
{
ScriptingListObjectsOperation operation = new ScriptingListObjectsOperation(parameters);
operation.CompleteNotification += (sender, e) => requestContext.SendEvent(ScriptingListObjectsCompleteEvent.Type, e);
RunTask(requestContext, operation);
await requestContext.SendResult(new ScriptingListObjectsResult { OperationId = operation.OperationId });
}
catch (Exception e)
{
await requestContext.SendError(e);
}
}
/// <summary>
/// Handles request to start the scripting operation
/// </summary>
public async Task HandleScriptExecuteRequest(ScriptingParams parameters, RequestContext<ScriptingResult> requestContext)
{
SmoScriptingOperation operation = null;
try
{
// if a connection string wasn't provided as a parameter then
// use the owner uri property to lookup its associated ConnectionInfo
// and then build a connection string out of that
ConnectionInfo connInfo = null;
string accessToken = null;
if (parameters.ConnectionString == null)
{
ScriptingService.ConnectionServiceInstance.TryFindConnection(parameters.OwnerUri, out connInfo);
if (connInfo != null)
{
parameters.ConnectionString = ConnectionService.BuildConnectionString(connInfo.ConnectionDetails);
accessToken = connInfo.ConnectionDetails.AzureAccountToken;
}
else
{
throw new Exception("Could not find ConnectionInfo");
}
}
if (!ShouldCreateScriptAsOperation(parameters))
{
operation = new ScriptingScriptOperation(parameters, accessToken);
}
else
{
operation = new ScriptAsScriptingOperation(parameters, accessToken);
}
operation.PlanNotification += (sender, e) => requestContext.SendEvent(ScriptingPlanNotificationEvent.Type, e).Wait();
operation.ProgressNotification += (sender, e) => requestContext.SendEvent(ScriptingProgressNotificationEvent.Type, e).Wait();
operation.CompleteNotification += (sender, e) => this.SendScriptingCompleteEvent(requestContext, ScriptingCompleteEvent.Type, e, operation, parameters.ScriptDestination);
RunTask(requestContext, operation);
}
catch (Exception e)
{
await requestContext.SendError(e);
}
}
private bool ShouldCreateScriptAsOperation(ScriptingParams parameters)
{
// Scripting as operation should be used to script one object.
// Scripting data and scripting to file is not supported by scripting as operation
// To script Select, alter and execute use scripting as operation. The other operation doesn't support those types
if( (parameters.ScriptingObjects != null && parameters.ScriptingObjects.Count == 1 && parameters.ScriptOptions != null
&& parameters.ScriptOptions.TypeOfDataToScript == "SchemaOnly" && parameters.ScriptDestination == "ToEditor") ||
parameters.Operation == ScriptingOperationType.Select || parameters.Operation == ScriptingOperationType.Execute ||
parameters.Operation == ScriptingOperationType.Alter)
{
return true;
}
else
{
return false;
}
}
/// <summary>
/// Handles request to cancel a script operation.
/// </summary>
public async Task HandleScriptCancelRequest(ScriptingCancelParams parameters, RequestContext<ScriptingCancelResult> requestContext)
{
try
{
ScriptingOperation operation = null;
if (this.ActiveOperations.TryRemove(parameters.OperationId, out operation))
{
operation.Cancel();
}
else
{
Logger.Write(TraceEventType.Information, string.Format("Operation {0} was not found", operation.OperationId));
}
await requestContext.SendResult(new ScriptingCancelResult());
}
catch (Exception e)
{
await requestContext.SendError(e);
}
}
private async void SendScriptingCompleteEvent<TParams>(RequestContext<ScriptingResult> requestContext, EventType<TParams> eventType, TParams parameters,
SmoScriptingOperation operation, string scriptDestination)
{
await requestContext.SendEvent(eventType, parameters);
switch (scriptDestination)
{
case "ToEditor":
await requestContext.SendResult(new ScriptingResult { OperationId = operation.OperationId, Script = operation.ScriptText });
break;
case "ToSingleFile":
await requestContext.SendResult(new ScriptingResult { OperationId = operation.OperationId });
break;
default:
await requestContext.SendError(string.Format("Operation {0} failed", operation.ToString()));
break;
}
}
/// <summary>
/// Runs the async task that performs the scripting operation.
/// </summary>
private void RunTask<T>(RequestContext<T> context, ScriptingOperation operation)
{
ScriptingTask = Task.Run(async () =>
{
try
{
this.ActiveOperations[operation.OperationId] = operation;
operation.Execute();
}
catch (Exception e)
{
await context.SendError(e);
}
finally
{
ScriptingOperation temp;
this.ActiveOperations.TryRemove(operation.OperationId, out temp);
}
}).ContinueWithOnFaulted(async t => await context.SendError(t.Exception));
}
internal Task ScriptingTask { get; set; }
/// <summary>
/// Disposes the scripting service and all active scripting operations.
/// </summary>
public void Dispose()
{
if (!disposed)
{
disposed = true;
foreach (ScriptingScriptOperation operation in this.ActiveOperations.Values)
{
operation.Dispose();
}
}
}
}
}

View File

@@ -0,0 +1,188 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using Microsoft.SqlServer.Management.Common;
using Microsoft.Kusto.ServiceLayer.Connection;
using Microsoft.Kusto.ServiceLayer.Scripting.Contracts;
using Microsoft.Kusto.ServiceLayer.DataSource;
using Microsoft.SqlTools.Utility;
using System;
using System.Data.SqlClient;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using static Microsoft.SqlServer.Management.SqlScriptPublish.SqlScriptOptions;
namespace Microsoft.Kusto.ServiceLayer.Scripting
{
/// <summary>
/// Base class for all SMO scripting operations
/// </summary>
public abstract class SmoScriptingOperation : ScriptingOperation
{
private bool disposed = false;
public SmoScriptingOperation(ScriptingParams parameters)
{
Validate.IsNotNull("parameters", parameters);
this.Parameters = parameters;
}
protected ScriptingParams Parameters { get; set; }
public string ScriptText { get; protected set; }
/// <remarks>
/// An event can be completed by the following conditions: success, cancel, error.
/// </remarks>
public event EventHandler<ScriptingCompleteParams> CompleteNotification;
/// <summary>
/// Event raised when a scripting operation has made forward progress.
/// </summary>
public event EventHandler<ScriptingProgressNotificationParams> ProgressNotification;
/// <summary>
/// Event raised when a scripting operation has resolved which database objects will be scripted.
/// </summary>
public event EventHandler<ScriptingPlanNotificationParams> PlanNotification;
protected virtual void SendCompletionNotificationEvent(ScriptingCompleteParams parameters)
{
this.SetCommonEventProperties(parameters);
this.CompleteNotification?.Invoke(this, parameters);
}
protected virtual void SendProgressNotificationEvent(ScriptingProgressNotificationParams parameters)
{
this.SetCommonEventProperties(parameters);
this.ProgressNotification?.Invoke(this, parameters);
}
protected virtual void SendPlanNotificationEvent(ScriptingPlanNotificationParams parameters)
{
this.SetCommonEventProperties(parameters);
this.PlanNotification?.Invoke(this, parameters);
}
protected virtual void SetCommonEventProperties(ScriptingEventParams parameters)
{
parameters.OperationId = this.OperationId;
}
protected string GetServerNameFromLiveInstance(string connectionString, string azureAccessToken)
{
string serverName = string.Empty;
using(var dataSource = DataSourceFactory.Create(DataSourceType.Kusto, connectionString, azureAccessToken))
{
serverName = dataSource.ClusterName;
}
Logger.Write(TraceEventType.Verbose, string.Format("Resolved server name '{0}'", serverName));
return serverName;
}
protected void ValidateScriptDatabaseParams()
{
try
{
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(this.Parameters.ConnectionString);
}
catch (Exception e)
{
throw new ArgumentException(SR.ScriptingParams_ConnectionString_Property_Invalid, e);
}
if (this.Parameters.FilePath == null && this.Parameters.ScriptDestination != "ToEditor")
{
throw new ArgumentException(SR.ScriptingParams_FilePath_Property_Invalid);
}
else if (this.Parameters.FilePath != null && this.Parameters.ScriptDestination != "ToEditor")
{
if (!Directory.Exists(Path.GetDirectoryName(this.Parameters.FilePath)))
{
throw new ArgumentException(SR.ScriptingParams_FilePath_Property_Invalid);
}
}
}
protected static void PopulateAdvancedScriptOptions(ScriptOptions scriptOptionsParameters, object advancedOptions)
{
if (scriptOptionsParameters == null)
{
Logger.Write(TraceEventType.Verbose, "No advanced options set, the ScriptOptions object is null.");
return;
}
foreach (PropertyInfo optionPropInfo in scriptOptionsParameters.GetType().GetProperties())
{
PropertyInfo advancedOptionPropInfo = advancedOptions.GetType().GetProperty(optionPropInfo.Name);
if (advancedOptionPropInfo == null)
{
Logger.Write(TraceEventType.Warning, string.Format("Invalid property info name {0} could not be mapped to a property on SqlScriptOptions.", optionPropInfo.Name));
continue;
}
object optionValue = optionPropInfo.GetValue(scriptOptionsParameters, index: null);
if (optionValue == null)
{
Logger.Write(TraceEventType.Verbose, string.Format("Skipping ScriptOptions.{0} since value is null", optionPropInfo.Name));
continue;
}
//
// The ScriptOptions property types from the request will be either a string or a bool?.
// The SqlScriptOptions property types from SMO will all be an Enum. Using reflection, we
// map the request ScriptOptions values to the SMO SqlScriptOptions values.
//
try
{
object smoValue = null;
if (optionPropInfo.PropertyType == typeof(bool?))
{
if (advancedOptionPropInfo.PropertyType == typeof(bool))
{
smoValue = (bool)optionValue;
}
else
{
smoValue = (bool)optionValue ? BooleanTypeOptions.True : BooleanTypeOptions.False;
}
}
else
{
smoValue = Enum.Parse(advancedOptionPropInfo.PropertyType, (string)optionValue, ignoreCase: true);
}
Logger.Write(TraceEventType.Verbose, string.Format("Setting ScriptOptions.{0} to value {1}", optionPropInfo.Name, smoValue));
advancedOptionPropInfo.SetValue(advancedOptions, smoValue);
}
catch (Exception e)
{
Logger.Write(
TraceEventType.Warning,
string.Format("An exception occurred setting option {0} to value {1}: {2}", optionPropInfo.Name, optionValue, e));
}
}
}
/// <summary>
/// Disposes the scripting operation.
/// </summary>
public override void Dispose()
{
if (!disposed)
{
this.Cancel();
disposed = true;
}
}
}
}