mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-13 17:23:02 -05:00
Removing script as feature from service layer to sqlcore (#2189)
This commit is contained in:
@@ -11,8 +11,6 @@ using System.Composition.Hosting;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Loader;
|
||||
using Microsoft.Extensions.DependencyModel;
|
||||
using Microsoft.SqlTools.Utility;
|
||||
|
||||
namespace Microsoft.SqlTools.Extensibility
|
||||
@@ -53,7 +51,6 @@ namespace Microsoft.SqlTools.Extensibility
|
||||
string assemblyPath = typeof(ExtensionStore).GetTypeInfo().Assembly.Location;
|
||||
string directory = Path.GetDirectoryName(assemblyPath);
|
||||
|
||||
AssemblyLoadContext context = new AssemblyLoader(directory);
|
||||
var assemblyPaths = Directory.GetFiles(directory, "*.dll", SearchOption.TopDirectoryOnly);
|
||||
|
||||
List<Assembly> assemblies = new List<Assembly>();
|
||||
@@ -77,9 +74,7 @@ namespace Microsoft.SqlTools.Extensibility
|
||||
|
||||
try
|
||||
{
|
||||
assemblies.Add(
|
||||
context.LoadFromAssemblyName(
|
||||
AssemblyLoadContext.GetAssemblyName(path)));
|
||||
assemblies.Add(Assembly.LoadFrom(path));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
@@ -193,7 +188,7 @@ namespace Microsoft.SqlTools.Extensibility
|
||||
try
|
||||
{
|
||||
Logger.Verbose("Loading service assembly: " + path);
|
||||
assemblies.Add(AssemblyLoadContext.Default.LoadFromAssemblyPath(path));
|
||||
assemblies.Add(Assembly.LoadFrom(path));
|
||||
Logger.Verbose("Loaded service assembly: " + path);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -281,13 +276,9 @@ namespace Microsoft.SqlTools.Extensibility
|
||||
|
||||
public static ContainerConfiguration WithAssembliesInPath(this ContainerConfiguration configuration, string path, AttributedModelProvider conventions, SearchOption searchOption = SearchOption.TopDirectoryOnly)
|
||||
{
|
||||
AssemblyLoadContext context = new AssemblyLoader(path);
|
||||
var assemblyNames = Directory
|
||||
.GetFiles(path, "*.dll", searchOption)
|
||||
.Select(AssemblyLoadContext.GetAssemblyName);
|
||||
|
||||
var assemblies = assemblyNames
|
||||
.Select(context.LoadFromAssemblyName)
|
||||
var assemblyPaths = Directory.GetFiles(path, "*.dll", SearchOption.TopDirectoryOnly);
|
||||
var assemblies = assemblyPaths
|
||||
.Select(path => Assembly.LoadFrom(path))
|
||||
.ToList();
|
||||
|
||||
configuration = configuration.WithAssemblies(assemblies, conventions);
|
||||
@@ -295,37 +286,4 @@ namespace Microsoft.SqlTools.Extensibility
|
||||
return configuration;
|
||||
}
|
||||
}
|
||||
|
||||
public class AssemblyLoader : AssemblyLoadContext
|
||||
{
|
||||
private string folderPath;
|
||||
|
||||
public AssemblyLoader(string folderPath)
|
||||
{
|
||||
this.folderPath = folderPath;
|
||||
}
|
||||
|
||||
protected override Assembly Load(AssemblyName assemblyName)
|
||||
{
|
||||
var deps = DependencyContext.Default;
|
||||
var res = deps.CompileLibraries.Where(d => d.Name.Equals(assemblyName.Name)).ToList();
|
||||
if (res.Count > 0)
|
||||
{
|
||||
return Assembly.Load(new AssemblyName(res.First().Name));
|
||||
}
|
||||
else
|
||||
{
|
||||
var apiApplicationFileInfo = new FileInfo($"{folderPath}{Path.DirectorySeparatorChar}{assemblyName.Name}.dll");
|
||||
if (File.Exists(apiApplicationFileInfo.FullName))
|
||||
{
|
||||
// Creating a new AssemblyContext instance for the same folder puts us at risk
|
||||
// of loading the same DLL in multiple contexts, which leads to some unpredictable
|
||||
// behavior in the loader. See https://github.com/dotnet/coreclr/issues/19632
|
||||
|
||||
return LoadFromAssemblyPath(apiApplicationFileInfo.FullName);
|
||||
}
|
||||
}
|
||||
return Assembly.Load(assemblyName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<!-- Targeting both net7.0 and net472 so that other things such as PS cmdlets can use this
|
||||
which need to support a wider range of machines -->
|
||||
<TargetFrameworks>net7.0;net472</TargetFrameworks>
|
||||
<TargetFrameworks>net7.0;net472;netstandard2.0</TargetFrameworks>
|
||||
<LangVersion>9.0</LangVersion>
|
||||
<Nullable>disable</Nullable>
|
||||
<AssemblyName>Microsoft.SqlTools.ManagedBatchParser</AssemblyName>
|
||||
@@ -37,7 +37,6 @@
|
||||
<NuspecProperties>version=$(PackageVersion)</NuspecProperties>
|
||||
<!-- Disable CA1852 (Seal internal types) as it depends on SrGen Tool -->
|
||||
<NoWarn>$(NoWarn);CA1852</NoWarn>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" />
|
||||
|
||||
@@ -16,6 +16,7 @@ using Microsoft.SqlTools.ServiceLayer.Agent.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlServer.Management.Smo.Agent;
|
||||
using Microsoft.SqlTools.ServiceLayer.Management;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Agent
|
||||
{
|
||||
@@ -43,7 +44,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Agent
|
||||
}
|
||||
using (SqlConnection connection = new SqlConnection(ConnectionService.BuildConnectionString(connInfo.ConnectionDetails)))
|
||||
{
|
||||
connection.RetryLogicProvider = Connection.ReliableConnection.SqlRetryProviders.ServerlessDBRetryProvider();
|
||||
connection.RetryLogicProvider = SqlRetryProviders.ServerlessDBRetryProvider();
|
||||
connection.Open();
|
||||
using (SqlCommand sqlQueryCommand = new SqlCommand(sqlQuery, connection))
|
||||
{
|
||||
|
||||
@@ -821,22 +821,6 @@ namespace Microsoft.SqlTools.ServiceLayer
|
||||
}
|
||||
}
|
||||
|
||||
public static string ScriptingParams_ConnectionString_Property_Invalid
|
||||
{
|
||||
get
|
||||
{
|
||||
return Keys.GetString(Keys.ScriptingParams_ConnectionString_Property_Invalid);
|
||||
}
|
||||
}
|
||||
|
||||
public static string ScriptingParams_FilePath_Property_Invalid
|
||||
{
|
||||
get
|
||||
{
|
||||
return Keys.GetString(Keys.ScriptingParams_FilePath_Property_Invalid);
|
||||
}
|
||||
}
|
||||
|
||||
public static string ScriptingListObjectsCompleteParams_ConnectionString_Property_Invalid
|
||||
{
|
||||
get
|
||||
@@ -845,30 +829,6 @@ namespace Microsoft.SqlTools.ServiceLayer
|
||||
}
|
||||
}
|
||||
|
||||
public static string StoredProcedureScriptParameterComment
|
||||
{
|
||||
get
|
||||
{
|
||||
return Keys.GetString(Keys.StoredProcedureScriptParameterComment);
|
||||
}
|
||||
}
|
||||
|
||||
public static string ScriptingGeneralError
|
||||
{
|
||||
get
|
||||
{
|
||||
return Keys.GetString(Keys.ScriptingGeneralError);
|
||||
}
|
||||
}
|
||||
|
||||
public static string ScriptingExecuteNotSupportedError
|
||||
{
|
||||
get
|
||||
{
|
||||
return Keys.GetString(Keys.ScriptingExecuteNotSupportedError);
|
||||
}
|
||||
}
|
||||
|
||||
public static string BackupTaskName
|
||||
{
|
||||
get
|
||||
@@ -11651,24 +11611,9 @@ namespace Microsoft.SqlTools.ServiceLayer
|
||||
public const string SqlScriptFormatterScalarTypeMissingScale = "SqlScriptFormatterScalarTypeMissingScale";
|
||||
|
||||
|
||||
public const string ScriptingParams_ConnectionString_Property_Invalid = "ScriptingParams_ConnectionString_Property_Invalid";
|
||||
|
||||
|
||||
public const string ScriptingParams_FilePath_Property_Invalid = "ScriptingParams_FilePath_Property_Invalid";
|
||||
|
||||
|
||||
public const string ScriptingListObjectsCompleteParams_ConnectionString_Property_Invalid = "ScriptingListObjectsCompleteParams_ConnectionString_Property_Invalid";
|
||||
|
||||
|
||||
public const string StoredProcedureScriptParameterComment = "StoredProcedureScriptParameterComment";
|
||||
|
||||
|
||||
public const string ScriptingGeneralError = "ScriptingGeneralError";
|
||||
|
||||
|
||||
public const string ScriptingExecuteNotSupportedError = "ScriptingExecuteNotSupportedError";
|
||||
|
||||
|
||||
public const string BackupTaskName = "BackupTaskName";
|
||||
|
||||
|
||||
|
||||
@@ -678,30 +678,10 @@
|
||||
<value>Scalar column missing scale</value>
|
||||
<comment></comment>
|
||||
</data>
|
||||
<data name="ScriptingParams_ConnectionString_Property_Invalid" xml:space="preserve">
|
||||
<value>Error parsing ScriptingParams.ConnectionString property.</value>
|
||||
<comment></comment>
|
||||
</data>
|
||||
<data name="ScriptingParams_FilePath_Property_Invalid" xml:space="preserve">
|
||||
<value>Invalid directory specified by the ScriptingParams.FilePath property.</value>
|
||||
<comment></comment>
|
||||
</data>
|
||||
<data name="ScriptingListObjectsCompleteParams_ConnectionString_Property_Invalid" xml:space="preserve">
|
||||
<value>Error parsing ScriptingListObjectsCompleteParams.ConnectionString property.</value>
|
||||
<comment></comment>
|
||||
</data>
|
||||
<data name="StoredProcedureScriptParameterComment" xml:space="preserve">
|
||||
<value>-- TODO: Set parameter values here.</value>
|
||||
<comment></comment>
|
||||
</data>
|
||||
<data name="ScriptingGeneralError" xml:space="preserve">
|
||||
<value>An error occurred while scripting the objects.</value>
|
||||
<comment></comment>
|
||||
</data>
|
||||
<data name="ScriptingExecuteNotSupportedError" xml:space="preserve">
|
||||
<value>Scripting as Execute is only supported for Stored Procedures</value>
|
||||
<comment></comment>
|
||||
</data>
|
||||
<data name="BackupTaskName" xml:space="preserve">
|
||||
<value>Backup Database</value>
|
||||
<comment></comment>
|
||||
|
||||
@@ -330,19 +330,8 @@ SqlScriptFormatterScalarTypeMissingScale = Scalar column missing scale
|
||||
############################################################################
|
||||
# Scripting Service
|
||||
|
||||
ScriptingParams_ConnectionString_Property_Invalid = Error parsing ScriptingParams.ConnectionString property.
|
||||
|
||||
ScriptingParams_FilePath_Property_Invalid = Invalid directory specified by the ScriptingParams.FilePath property.
|
||||
|
||||
ScriptingListObjectsCompleteParams_ConnectionString_Property_Invalid = Error parsing ScriptingListObjectsCompleteParams.ConnectionString property.
|
||||
|
||||
|
||||
StoredProcedureScriptParameterComment = -- TODO: Set parameter values here.
|
||||
|
||||
ScriptingGeneralError = An error occurred while scripting the objects.
|
||||
ScriptingExecuteNotSupportedError = Scripting as Execute is only supported for Stored Procedures
|
||||
|
||||
|
||||
############################################################################
|
||||
# Backup Service
|
||||
BackupTaskName = Backup Database
|
||||
|
||||
@@ -457,21 +457,6 @@
|
||||
<target state="new">Table or view requested for edit could not be found</target>
|
||||
<note></note>
|
||||
</trans-unit>
|
||||
<trans-unit id="ScriptingParams_ConnectionString_Property_Invalid">
|
||||
<source>Error parsing ScriptingParams.ConnectionString property.</source>
|
||||
<target state="new">Error parsing ScriptingParams.ConnectionString property.</target>
|
||||
<note></note>
|
||||
</trans-unit>
|
||||
<trans-unit id="ScriptingParams_FilePath_Property_Invalid">
|
||||
<source>Invalid directory specified by the ScriptingParams.FilePath property.</source>
|
||||
<target state="new">Invalid directory specified by the ScriptingParams.FilePath property.</target>
|
||||
<note></note>
|
||||
</trans-unit>
|
||||
<trans-unit id="ScriptingListObjectsCompleteParams_ConnectionString_Property_Invalid">
|
||||
<source>Error parsing ScriptingListObjectsCompleteParams.ConnectionString property.</source>
|
||||
<target state="new">Error parsing ScriptingListObjectsCompleteParams.ConnectionString property.</target>
|
||||
<note></note>
|
||||
</trans-unit>
|
||||
<trans-unit id="QueryServiceResultSetHasNoResults">
|
||||
<source>Query has no results to return</source>
|
||||
<target state="new">Query has no results to return</target>
|
||||
@@ -688,21 +673,6 @@
|
||||
<target state="new">Scalar column missing scale</target>
|
||||
<note></note>
|
||||
</trans-unit>
|
||||
<trans-unit id="StoredProcedureScriptParameterComment">
|
||||
<source>-- TODO: Set parameter values here.</source>
|
||||
<target state="new">-- TODO: Set parameter values here.</target>
|
||||
<note></note>
|
||||
</trans-unit>
|
||||
<trans-unit id="ScriptingGeneralError">
|
||||
<source>An error occurred while scripting the objects.</source>
|
||||
<target state="new">An error occurred while scripting the objects.</target>
|
||||
<note></note>
|
||||
</trans-unit>
|
||||
<trans-unit id="ScriptingExecuteNotSupportedError">
|
||||
<source>Scripting as Execute is only supported for Stored Procedures</source>
|
||||
<target state="new">Scripting as Execute is only supported for Stored Procedures</target>
|
||||
<note></note>
|
||||
</trans-unit>
|
||||
<trans-unit id="EditDataMultiTableNotSupported">
|
||||
<source>EditData queries targeting multiple tables are not supported</source>
|
||||
<target state="new">EditData queries targeting multiple tables are not supported</target>
|
||||
@@ -7273,6 +7243,11 @@ The Query Processor estimates that implementing the following index could improv
|
||||
<note>.
|
||||
Parameters: 0 - table (string) </note>
|
||||
</trans-unit>
|
||||
<trans-unit id="ScriptingListObjectsCompleteParams_ConnectionString_Property_Invalid">
|
||||
<source>Error parsing ScriptingListObjectsCompleteParams.ConnectionString property.</source>
|
||||
<target state="new">Error parsing ScriptingListObjectsCompleteParams.ConnectionString property.</target>
|
||||
<note></note>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
@@ -6,6 +6,7 @@
|
||||
#nullable disable
|
||||
|
||||
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
|
||||
using Microsoft.SqlTools.SqlCore.Scripting.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Scripting.Contracts
|
||||
{
|
||||
|
||||
@@ -5,41 +5,11 @@
|
||||
|
||||
#nullable disable
|
||||
|
||||
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
|
||||
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
|
||||
using Microsoft.SqlTools.SqlCore.Scripting.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.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>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
|
||||
using Microsoft.SqlTools.SqlCore.Scripting.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Scripting.Contracts
|
||||
{
|
||||
|
||||
@@ -5,27 +5,11 @@
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
|
||||
using Microsoft.SqlTools.SqlCore.Scripting.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.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>
|
||||
|
||||
@@ -6,48 +6,10 @@
|
||||
#nullable disable
|
||||
|
||||
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
|
||||
using Microsoft.SqlTools.SqlCore.Scripting.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.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>
|
||||
|
||||
@@ -5,83 +5,11 @@
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
|
||||
using Microsoft.SqlTools.SqlCore.Scripting.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.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>
|
||||
|
||||
@@ -20,14 +20,13 @@ using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices;
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Scripting.Contracts;
|
||||
using Microsoft.SqlTools.Utility;
|
||||
using ConnectionType = Microsoft.SqlTools.ServiceLayer.Connection.ConnectionType;
|
||||
using Location = Microsoft.SqlTools.ServiceLayer.Workspace.Contracts.Location;
|
||||
using Microsoft.SqlServer.Management.Sdk.Sfc;
|
||||
using System.Text;
|
||||
using System.Data;
|
||||
using Range = Microsoft.SqlTools.ServiceLayer.Workspace.Contracts.Range;
|
||||
using Microsoft.SqlTools.SqlCore.Scripting;
|
||||
using Microsoft.SqlTools.SqlCore.Scripting.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Scripting
|
||||
{
|
||||
@@ -540,303 +539,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Scripting
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the value whether the server supports XTP or not s
|
||||
internal static bool IsXTPSupportedOnServer(Server server)
|
||||
{
|
||||
bool isXTPSupported = false;
|
||||
if (server.ConnectionContext.ExecuteScalar("SELECT SERVERPROPERTY('IsXTPSupported')") != DBNull.Value)
|
||||
{
|
||||
isXTPSupported = server.IsXTPSupported;
|
||||
}
|
||||
return isXTPSupported;
|
||||
}
|
||||
}
|
||||
|
||||
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 && !isDw))
|
||||
{
|
||||
// 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 EDIT for SQL2022+/Sterling+.
|
||||
// We need to omit dropped ledger columns if such are present
|
||||
if (server.Version.Major >= 16 || (DatabaseEngineType.SqlAzureDatabase == server.DatabaseEngineType && server.Version.Major >= 12 && !isDw))
|
||||
{
|
||||
filterExpressions.Add("@IsDroppedLedgerColumn=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(Server server, Urn urn, bool isDw)
|
||||
{
|
||||
DataTable dt = GetColumnNames(server, urn, isDw);
|
||||
StringBuilder selectQuery = new StringBuilder();
|
||||
|
||||
// build the first line
|
||||
if (dt != null && dt.Rows.Count > 0)
|
||||
{
|
||||
|
||||
selectQuery.Append("SELECT TOP (1000) ");
|
||||
|
||||
// first column
|
||||
selectQuery.AppendFormat("{0}{1}{2}\r\n",
|
||||
ScriptingGlobals.LeftDelimiter,
|
||||
ScriptingUtils.QuoteObjectName(dt.Rows[0][0] as string, ScriptingGlobals.RightDelimiter),
|
||||
ScriptingGlobals.RightDelimiter);
|
||||
// add all other columns on separate lines. Make the names align.
|
||||
for (int i = 1; i < dt.Rows.Count; i++)
|
||||
{
|
||||
selectQuery.AppendFormat(" ,{0}{1}{2}\r\n",
|
||||
ScriptingGlobals.LeftDelimiter,
|
||||
ScriptingUtils.QuoteObjectName(dt.Rows[i][0] as string, ScriptingGlobals.RightDelimiter),
|
||||
ScriptingGlobals.RightDelimiter);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
selectQuery.Append("SELECT TOP (1000) * ");
|
||||
}
|
||||
|
||||
// from clause
|
||||
selectQuery.Append(" FROM ");
|
||||
|
||||
if (server.ServerType != DatabaseEngineType.SqlAzureDatabase)
|
||||
{
|
||||
// Azure doesn't allow qualifying object names with the DB, so only add it on if we're not in Azure database URN
|
||||
Urn dbUrn = urn.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);
|
||||
|
||||
// In Hekaton M5, if it's a memory optimized table, we need to provide SNAPSHOT hint for SELECT.
|
||||
if (urn.Type.Equals("Table") && ScriptingUtils.IsXTPSupportedOnServer(server))
|
||||
{
|
||||
try
|
||||
{
|
||||
Table table = (Table)server.GetSmoObject(urn);
|
||||
table.Refresh();
|
||||
if (table.IsMemoryOptimized)
|
||||
{
|
||||
selectQuery.Append(" WITH (SNAPSHOT)");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// log any exceptions determining if InMemory, but don't treat as fatal exception
|
||||
Logger.Error("Could not determine if is InMemory table " + ex.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
return selectQuery.ToString();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,8 @@ using System.Collections.Generic;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.SqlServer.Management.SqlScriptPublish;
|
||||
using Microsoft.SqlTools.ServiceLayer.Scripting.Contracts;
|
||||
using Microsoft.SqlTools.SqlCore.Scripting;
|
||||
using Microsoft.SqlTools.SqlCore.Scripting.Contracts;
|
||||
using Microsoft.SqlTools.Utility;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Scripting
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.SqlTools.ServiceLayer.Scripting.Contracts;
|
||||
using Microsoft.SqlTools.SqlCore.Scripting.Contracts;
|
||||
using Microsoft.SqlTools.Utility;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Scripting
|
||||
|
||||
@@ -10,8 +10,9 @@ using System.Collections.Generic;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using System.Linq;
|
||||
using Microsoft.SqlServer.Management.SqlScriptPublish;
|
||||
using Microsoft.SqlTools.ServiceLayer.Scripting.Contracts;
|
||||
using Microsoft.SqlTools.Utility;
|
||||
using Microsoft.SqlTools.SqlCore.Scripting;
|
||||
using Microsoft.SqlTools.SqlCore.Scripting.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Scripting
|
||||
{
|
||||
|
||||
@@ -18,6 +18,8 @@ using Microsoft.SqlTools.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
using static Microsoft.SqlTools.Utility.SqlConstants;
|
||||
using System.Linq;
|
||||
using Microsoft.SqlTools.SqlCore.Scripting;
|
||||
using Microsoft.SqlTools.SqlCore.Scripting.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Scripting
|
||||
{
|
||||
|
||||
@@ -1725,6 +1725,54 @@ namespace Microsoft.SqlTools.SqlCore
|
||||
}
|
||||
}
|
||||
|
||||
public static string ScriptingParams_ConnectionString_Property_Invalid
|
||||
{
|
||||
get
|
||||
{
|
||||
return Keys.GetString(Keys.ScriptingParams_ConnectionString_Property_Invalid);
|
||||
}
|
||||
}
|
||||
|
||||
public static string ScriptingParams_FilePath_Property_Invalid
|
||||
{
|
||||
get
|
||||
{
|
||||
return Keys.GetString(Keys.ScriptingParams_FilePath_Property_Invalid);
|
||||
}
|
||||
}
|
||||
|
||||
public static string ScriptingListObjectsCompleteParams_ConnectionString_Property_Invalid
|
||||
{
|
||||
get
|
||||
{
|
||||
return Keys.GetString(Keys.ScriptingListObjectsCompleteParams_ConnectionString_Property_Invalid);
|
||||
}
|
||||
}
|
||||
|
||||
public static string StoredProcedureScriptParameterComment
|
||||
{
|
||||
get
|
||||
{
|
||||
return Keys.GetString(Keys.StoredProcedureScriptParameterComment);
|
||||
}
|
||||
}
|
||||
|
||||
public static string ScriptingGeneralError
|
||||
{
|
||||
get
|
||||
{
|
||||
return Keys.GetString(Keys.ScriptingGeneralError);
|
||||
}
|
||||
}
|
||||
|
||||
public static string ScriptingExecuteNotSupportedError
|
||||
{
|
||||
get
|
||||
{
|
||||
return Keys.GetString(Keys.ScriptingExecuteNotSupportedError);
|
||||
}
|
||||
}
|
||||
|
||||
[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
public class Keys
|
||||
{
|
||||
@@ -2369,6 +2417,24 @@ namespace Microsoft.SqlTools.SqlCore
|
||||
public const string FilterInPrimaryKeyDescription = "FilterInPrimaryKeyDescription";
|
||||
|
||||
|
||||
public const string ScriptingParams_ConnectionString_Property_Invalid = "ScriptingParams_ConnectionString_Property_Invalid";
|
||||
|
||||
|
||||
public const string ScriptingParams_FilePath_Property_Invalid = "ScriptingParams_FilePath_Property_Invalid";
|
||||
|
||||
|
||||
public const string ScriptingListObjectsCompleteParams_ConnectionString_Property_Invalid = "ScriptingListObjectsCompleteParams_ConnectionString_Property_Invalid";
|
||||
|
||||
|
||||
public const string StoredProcedureScriptParameterComment = "StoredProcedureScriptParameterComment";
|
||||
|
||||
|
||||
public const string ScriptingGeneralError = "ScriptingGeneralError";
|
||||
|
||||
|
||||
public const string ScriptingExecuteNotSupportedError = "ScriptingExecuteNotSupportedError";
|
||||
|
||||
|
||||
private Keys()
|
||||
{ }
|
||||
|
||||
|
||||
@@ -965,4 +965,28 @@
|
||||
<value>Include or exclude objects based on whether the column is in a primary key.</value>
|
||||
<comment></comment>
|
||||
</data>
|
||||
<data name="ScriptingParams_ConnectionString_Property_Invalid" xml:space="preserve">
|
||||
<value>Error parsing ScriptingParams.ConnectionString property.</value>
|
||||
<comment></comment>
|
||||
</data>
|
||||
<data name="ScriptingParams_FilePath_Property_Invalid" xml:space="preserve">
|
||||
<value>Invalid directory specified by the ScriptingParams.FilePath property.</value>
|
||||
<comment></comment>
|
||||
</data>
|
||||
<data name="ScriptingListObjectsCompleteParams_ConnectionString_Property_Invalid" xml:space="preserve">
|
||||
<value>Error parsing ScriptingListObjectsCompleteParams.ConnectionString property.</value>
|
||||
<comment></comment>
|
||||
</data>
|
||||
<data name="StoredProcedureScriptParameterComment" xml:space="preserve">
|
||||
<value>-- TODO: Set parameter values here.</value>
|
||||
<comment></comment>
|
||||
</data>
|
||||
<data name="ScriptingGeneralError" xml:space="preserve">
|
||||
<value>An error occurred while scripting the objects.</value>
|
||||
<comment></comment>
|
||||
</data>
|
||||
<data name="ScriptingExecuteNotSupportedError" xml:space="preserve">
|
||||
<value>Scripting as Execute is only supported for Stored Procedures</value>
|
||||
<comment></comment>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -430,3 +430,17 @@ FilterIsNativelyCompiledDescription = Include or exclude objects based on whethe
|
||||
FilterInPrimaryKey = In Primary Key
|
||||
FilterInPrimaryKeyDescription = Include or exclude objects based on whether the column is in a primary key.
|
||||
|
||||
############################################################################
|
||||
# Scripting Service
|
||||
|
||||
ScriptingParams_ConnectionString_Property_Invalid = Error parsing ScriptingParams.ConnectionString property.
|
||||
|
||||
ScriptingParams_FilePath_Property_Invalid = Invalid directory specified by the ScriptingParams.FilePath property.
|
||||
|
||||
ScriptingListObjectsCompleteParams_ConnectionString_Property_Invalid = Error parsing ScriptingListObjectsCompleteParams.ConnectionString property.
|
||||
|
||||
|
||||
StoredProcedureScriptParameterComment = -- TODO: Set parameter values here.
|
||||
|
||||
ScriptingGeneralError = An error occurred while scripting the objects.
|
||||
ScriptingExecuteNotSupportedError = Scripting as Execute is only supported for Stored Procedures
|
||||
|
||||
@@ -1062,6 +1062,36 @@
|
||||
<target state="new">Schema and Data</target>
|
||||
<note></note>
|
||||
</trans-unit>
|
||||
<trans-unit id="ScriptingParams_ConnectionString_Property_Invalid">
|
||||
<source>Error parsing ScriptingParams.ConnectionString property.</source>
|
||||
<target state="new">Error parsing ScriptingParams.ConnectionString property.</target>
|
||||
<note></note>
|
||||
</trans-unit>
|
||||
<trans-unit id="ScriptingParams_FilePath_Property_Invalid">
|
||||
<source>Invalid directory specified by the ScriptingParams.FilePath property.</source>
|
||||
<target state="new">Invalid directory specified by the ScriptingParams.FilePath property.</target>
|
||||
<note></note>
|
||||
</trans-unit>
|
||||
<trans-unit id="ScriptingListObjectsCompleteParams_ConnectionString_Property_Invalid">
|
||||
<source>Error parsing ScriptingListObjectsCompleteParams.ConnectionString property.</source>
|
||||
<target state="new">Error parsing ScriptingListObjectsCompleteParams.ConnectionString property.</target>
|
||||
<note></note>
|
||||
</trans-unit>
|
||||
<trans-unit id="StoredProcedureScriptParameterComment">
|
||||
<source>-- TODO: Set parameter values here.</source>
|
||||
<target state="new">-- TODO: Set parameter values here.</target>
|
||||
<note></note>
|
||||
</trans-unit>
|
||||
<trans-unit id="ScriptingGeneralError">
|
||||
<source>An error occurred while scripting the objects.</source>
|
||||
<target state="new">An error occurred while scripting the objects.</target>
|
||||
<note></note>
|
||||
</trans-unit>
|
||||
<trans-unit id="ScriptingExecuteNotSupportedError">
|
||||
<source>Scripting as Execute is only supported for Stored Procedures</source>
|
||||
<target state="new">Scripting as Execute is only supported for Stored Procedures</target>
|
||||
<note></note>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
@@ -26,6 +26,8 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../Microsoft.SqlTools.Hosting/Microsoft.SqlTools.Hosting.csproj" PrivateAssets="all" />
|
||||
<ProjectReference Include="../Microsoft.SqlTools.ManagedBatchParser/Microsoft.SqlTools.ManagedBatchParser.csproj" PrivateAssets="all" />
|
||||
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Localization\*.resx" />
|
||||
@@ -41,7 +43,7 @@
|
||||
<InternalsVisibleTo Include="DynamicProxyGenAssembly2" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="bin\$(Configuration)\**\Microsoft.SqlTools.Hosting*.dll">
|
||||
<Content Include="bin\$(Configuration)\**\Microsoft.SqlTools.*.dll">
|
||||
<Pack>true</Pack>
|
||||
<PackagePath>lib\</PackagePath>
|
||||
</Content>
|
||||
|
||||
@@ -10,11 +10,10 @@ using System.Threading.Tasks;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.SqlServer.Management.Common;
|
||||
using Microsoft.SqlTools.SqlCore.Connection;
|
||||
using Microsoft.SqlTools.SqlCore.ObjectExplorer;
|
||||
using Microsoft.SqlTools.SqlCore.ObjectExplorer.Nodes;
|
||||
using Microsoft.SqlTools.SqlCore.ObjectExplorer.SmoModel;
|
||||
|
||||
namespace Microsoft.SqlTools.CoreSql.ObjectExplorer
|
||||
namespace Microsoft.SqlTools.SqlCore.ObjectExplorer
|
||||
{
|
||||
/// <summary>
|
||||
/// Stateless object explorer class can be used to handle object explorer requests without creating a session. It requires a connection string and a node path to query objects from the server.
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Azure.Core;
|
||||
using Microsoft.SqlTools.SqlCore.Scripting.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.SqlCore.Scripting
|
||||
{
|
||||
public class AsyncScriptAsScriptingOperation
|
||||
{
|
||||
public static async Task<string> GetScriptAsScript(ScriptingParams parameters, AccessToken? accessToken)
|
||||
{
|
||||
var scriptAsOperation = new ScriptAsScriptingOperation(parameters, accessToken?.Token);
|
||||
TaskCompletionSource<string> scriptAsTask = new TaskCompletionSource<string>();
|
||||
|
||||
scriptAsOperation.CompleteNotification += (sender, args) =>
|
||||
{
|
||||
if (args.HasError)
|
||||
{
|
||||
scriptAsTask.SetException(new Exception(args.ErrorMessage));
|
||||
}
|
||||
scriptAsTask.SetResult(scriptAsOperation.ScriptText);
|
||||
};
|
||||
|
||||
scriptAsOperation.ProgressNotification += (sender, args) =>
|
||||
{
|
||||
if(args.ErrorMessage != null)
|
||||
{
|
||||
scriptAsTask.SetException(new Exception(args.ErrorMessage));
|
||||
}
|
||||
};
|
||||
|
||||
scriptAsOperation.Execute();
|
||||
return await scriptAsTask.Task;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
namespace Microsoft.SqlTools.SqlCore.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; }
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,7 @@
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Scripting.Contracts
|
||||
namespace Microsoft.SqlTools.SqlCore.Scripting.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for all scripting event parameters.
|
||||
@@ -3,11 +3,9 @@
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Scripting.Contracts
|
||||
namespace Microsoft.SqlTools.SqlCore.Scripting.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// Class to represent a database object that can be scripted.
|
||||
@@ -3,7 +3,7 @@
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Scripting.Contracts
|
||||
namespace Microsoft.SqlTools.SqlCore.Scripting.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// Scripting Operation type
|
||||
@@ -0,0 +1,81 @@
|
||||
//
|
||||
// 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;
|
||||
|
||||
namespace Microsoft.SqlTools.SqlCore.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;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// 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;
|
||||
|
||||
namespace Microsoft.SqlTools.SqlCore.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; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
namespace Microsoft.SqlTools.SqlCore.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; }
|
||||
}
|
||||
}
|
||||
@@ -3,12 +3,9 @@
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
#nullable disable
|
||||
|
||||
using Microsoft.SqlTools.ServiceLayer.Scripting.Contracts;
|
||||
using Microsoft.SqlTools.Utility;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Scripting
|
||||
namespace Microsoft.SqlTools.SqlCore.Scripting
|
||||
{
|
||||
/// <summary>
|
||||
/// A wrpaper of ScriptOptions to map the option name with the oen in SMO.ScriptingOptions
|
||||
@@ -3,8 +3,6 @@
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
@@ -15,12 +13,13 @@ using Microsoft.SqlServer.Management.Common;
|
||||
using Microsoft.SqlServer.Management.Sdk.Sfc;
|
||||
using Microsoft.SqlServer.Management.Smo;
|
||||
using Microsoft.SqlServer.Management.SqlScriptPublish;
|
||||
using Microsoft.SqlTools.ServiceLayer.Scripting.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection;
|
||||
using Microsoft.SqlTools.SqlCore.Connection;
|
||||
using Microsoft.SqlTools.SqlCore.Scripting.Contracts;
|
||||
using Microsoft.SqlTools.SqlCore.Utility;
|
||||
using Microsoft.SqlTools.Utility;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Scripting
|
||||
namespace Microsoft.SqlTools.SqlCore.Scripting
|
||||
{
|
||||
/// <summary>
|
||||
/// Class to generate script as for one smo object
|
||||
@@ -47,7 +46,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Scripting
|
||||
public ScriptAsScriptingOperation(ScriptingParams parameters, string azureAccountToken) : base(parameters)
|
||||
{
|
||||
SqlConnection sqlConnection = new SqlConnection(this.Parameters.ConnectionString);
|
||||
sqlConnection.RetryLogicProvider = Connection.ReliableConnection.SqlRetryProviders.ServerlessDBRetryProvider();
|
||||
sqlConnection.RetryLogicProvider = SqlRetryProviders.ServerlessDBRetryProvider();
|
||||
if (azureAccountToken != null)
|
||||
{
|
||||
sqlConnection.AccessToken = azureAccountToken;
|
||||
@@ -176,14 +175,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Scripting
|
||||
// select from service broker
|
||||
if (string.Compare(typeName, "ServiceBroker", StringComparison.CurrentCultureIgnoreCase) == 0)
|
||||
{
|
||||
script = Scripter.SelectAllValuesFromTransmissionQueue(objectUrn);
|
||||
script = ScriptingHelper.SelectAllValuesFromTransmissionQueue(objectUrn);
|
||||
}
|
||||
|
||||
// select from queues
|
||||
else if (string.Compare(typeName, "Queues", StringComparison.CurrentCultureIgnoreCase) == 0 ||
|
||||
string.Compare(typeName, "SystemQueues", StringComparison.CurrentCultureIgnoreCase) == 0)
|
||||
{
|
||||
script = Scripter.SelectAllValues(objectUrn);
|
||||
script = ScriptingHelper.SelectAllValues(objectUrn);
|
||||
}
|
||||
|
||||
// select from table or view
|
||||
@@ -191,7 +190,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Scripting
|
||||
{
|
||||
Database db = server.Databases[databaseName];
|
||||
bool isDw = db.IsSqlDw;
|
||||
script = new Scripter().SelectFromTableOrView(server, objectUrn, isDw);
|
||||
script = ScriptingHelper.SelectFromTableOrView(server, objectUrn, isDw);
|
||||
}
|
||||
|
||||
return script;
|
||||
@@ -589,10 +588,10 @@ namespace Microsoft.SqlTools.ServiceLayer.Scripting
|
||||
|
||||
// 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))
|
||||
SqlScriptOptions.ScriptDatabaseEngineType targetDatabaseEngineType;
|
||||
if (Enum.TryParse<SqlScriptOptions.ScriptDatabaseEngineType>(this.Parameters.ScriptOptions.TargetDatabaseEngineType, out targetDatabaseEngineType))
|
||||
{
|
||||
switch ((SqlScriptOptions.ScriptDatabaseEngineType)targetDatabaseEngineType)
|
||||
switch (targetDatabaseEngineType)
|
||||
{
|
||||
case SqlScriptOptions.ScriptDatabaseEngineType.SingleInstance:
|
||||
scriptingOptions.TargetDatabaseEngineType = DatabaseEngineType.Standalone;
|
||||
@@ -603,10 +602,10 @@ namespace Microsoft.SqlTools.ServiceLayer.Scripting
|
||||
}
|
||||
}
|
||||
|
||||
object targetDatabaseEngineEdition;
|
||||
if (Enum.TryParse(typeof(SqlScriptOptions.ScriptDatabaseEngineEdition), this.Parameters.ScriptOptions.TargetDatabaseEngineEdition, out targetDatabaseEngineEdition))
|
||||
SqlScriptOptions.ScriptDatabaseEngineEdition targetDatabaseEngineEdition;
|
||||
if (Enum.TryParse<SqlScriptOptions.ScriptDatabaseEngineEdition>(this.Parameters.ScriptOptions.TargetDatabaseEngineEdition, out targetDatabaseEngineEdition))
|
||||
{
|
||||
switch ((SqlScriptOptions.ScriptDatabaseEngineEdition)targetDatabaseEngineEdition)
|
||||
switch (targetDatabaseEngineEdition)
|
||||
{
|
||||
case SqlScriptOptions.ScriptDatabaseEngineEdition.SqlServerPersonalEdition:
|
||||
scriptingOptions.TargetDatabaseEngineEdition = DatabaseEngineEdition.Personal;
|
||||
@@ -650,10 +649,10 @@ namespace Microsoft.SqlTools.ServiceLayer.Scripting
|
||||
|
||||
// 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))
|
||||
SqlScriptOptions.ScriptStatisticsOptions scriptStatistics;
|
||||
if (Enum.TryParse<SqlScriptOptions.ScriptStatisticsOptions>(this.Parameters.ScriptOptions.ScriptStatistics, out scriptStatistics))
|
||||
{
|
||||
switch ((SqlScriptOptions.ScriptStatisticsOptions)scriptStatistics)
|
||||
switch (scriptStatistics)
|
||||
{
|
||||
case SqlScriptOptions.ScriptStatisticsOptions.ScriptStatsAll:
|
||||
scriptingOptions.Statistics = true;
|
||||
@@ -3,22 +3,20 @@
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.SqlServer.Management.Sdk.Sfc;
|
||||
using Microsoft.SqlServer.Management.SqlScriptPublish;
|
||||
using Microsoft.SqlTools.ServiceLayer.Scripting.Contracts;
|
||||
using Microsoft.SqlTools.SqlCore.Scripting.Contracts;
|
||||
using Microsoft.SqlTools.Utility;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Scripting
|
||||
namespace Microsoft.SqlTools.SqlCore.Scripting
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods used by the scripting service.
|
||||
/// </summary>
|
||||
internal static class ScriptingExtensionMethods
|
||||
public static class ScriptingExtensionMethods
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the status of a scripting operation for the passed scripting event.
|
||||
318
src/Microsoft.SqlTools.SqlCore/Scripting/ScriptingHelper.cs
Normal file
318
src/Microsoft.SqlTools.SqlCore/Scripting/ScriptingHelper.cs
Normal file
@@ -0,0 +1,318 @@
|
||||
//
|
||||
// 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;
|
||||
using System.Text;
|
||||
using Microsoft.SqlServer.Management.Common;
|
||||
using Microsoft.SqlServer.Management.Sdk.Sfc;
|
||||
using Microsoft.SqlServer.Management.Smo;
|
||||
using Microsoft.SqlTools.Utility;
|
||||
|
||||
namespace Microsoft.SqlTools.SqlCore.Scripting
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper class for scripting
|
||||
/// </summary>
|
||||
public static class ScriptingHelper
|
||||
{
|
||||
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,
|
||||
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,
|
||||
QuoteObjectName(dbUrn.GetAttribute("Name"), ScriptingGlobals.RightDelimiter),
|
||||
ScriptingGlobals.RightDelimiter);
|
||||
// schema
|
||||
selectQuery.AppendFormat(".{0}{1}{2}",
|
||||
ScriptingGlobals.LeftDelimiter,
|
||||
QuoteObjectName(urn.GetAttribute("Schema"), ScriptingGlobals.RightDelimiter),
|
||||
ScriptingGlobals.RightDelimiter);
|
||||
// object
|
||||
selectQuery.AppendFormat(".{0}{1}{2}",
|
||||
ScriptingGlobals.LeftDelimiter,
|
||||
QuoteObjectName(urn.GetAttribute("Name"), ScriptingGlobals.RightDelimiter),
|
||||
ScriptingGlobals.RightDelimiter);
|
||||
|
||||
//Adding no lock in the end.
|
||||
selectQuery.AppendFormat(" WITH(NOLOCK)");
|
||||
|
||||
script = selectQuery.ToString();
|
||||
return script;
|
||||
}
|
||||
|
||||
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 = ']';
|
||||
}
|
||||
|
||||
|
||||
|
||||
static 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 && !isDw))
|
||||
{
|
||||
// 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 EDIT for SQL2022+/Sterling+.
|
||||
// We need to omit dropped ledger columns if such are present
|
||||
if (server.Version.Major >= 16 || (DatabaseEngineType.SqlAzureDatabase == server.DatabaseEngineType && server.Version.Major >= 12 && !isDw))
|
||||
{
|
||||
filterExpressions.Add("@IsDroppedLedgerColumn=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;
|
||||
}
|
||||
|
||||
public static string SelectFromTableOrView(Server server, Urn urn, bool isDw)
|
||||
{
|
||||
DataTable dt = GetColumnNames(server, urn, isDw);
|
||||
StringBuilder selectQuery = new StringBuilder();
|
||||
|
||||
// build the first line
|
||||
if (dt != null && dt.Rows.Count > 0)
|
||||
{
|
||||
|
||||
selectQuery.Append("SELECT TOP (1000) ");
|
||||
|
||||
// first column
|
||||
selectQuery.AppendFormat("{0}{1}{2}\r\n",
|
||||
ScriptingGlobals.LeftDelimiter,
|
||||
QuoteObjectName(dt.Rows[0][0] as string, ScriptingGlobals.RightDelimiter),
|
||||
ScriptingGlobals.RightDelimiter);
|
||||
// add all other columns on separate lines. Make the names align.
|
||||
for (int i = 1; i < dt.Rows.Count; i++)
|
||||
{
|
||||
selectQuery.AppendFormat(" ,{0}{1}{2}\r\n",
|
||||
ScriptingGlobals.LeftDelimiter,
|
||||
QuoteObjectName(dt.Rows[i][0] as string, ScriptingGlobals.RightDelimiter),
|
||||
ScriptingGlobals.RightDelimiter);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
selectQuery.Append("SELECT TOP (1000) * ");
|
||||
}
|
||||
|
||||
// from clause
|
||||
selectQuery.Append(" FROM ");
|
||||
|
||||
if (server.ServerType != DatabaseEngineType.SqlAzureDatabase)
|
||||
{
|
||||
// Azure doesn't allow qualifying object names with the DB, so only add it on if we're not in Azure database URN
|
||||
Urn dbUrn = urn.Parent;
|
||||
selectQuery.AppendFormat("{0}{1}{2}.",
|
||||
ScriptingGlobals.LeftDelimiter,
|
||||
QuoteObjectName(dbUrn.GetAttribute("Name"), ScriptingGlobals.RightDelimiter),
|
||||
ScriptingGlobals.RightDelimiter);
|
||||
}
|
||||
|
||||
// schema
|
||||
selectQuery.AppendFormat("{0}{1}{2}.",
|
||||
ScriptingGlobals.LeftDelimiter,
|
||||
QuoteObjectName(urn.GetAttribute("Schema"), ScriptingGlobals.RightDelimiter),
|
||||
ScriptingGlobals.RightDelimiter);
|
||||
// object
|
||||
selectQuery.AppendFormat("{0}{1}{2}",
|
||||
ScriptingGlobals.LeftDelimiter,
|
||||
QuoteObjectName(urn.GetAttribute("Name"), ScriptingGlobals.RightDelimiter),
|
||||
ScriptingGlobals.RightDelimiter);
|
||||
|
||||
// In Hekaton M5, if it's a memory optimized table, we need to provide SNAPSHOT hint for SELECT.
|
||||
if (urn.Type.Equals("Table") && IsXTPSupportedOnServer(server))
|
||||
{
|
||||
try
|
||||
{
|
||||
Table table = (Table)server.GetSmoObject(urn);
|
||||
table.Refresh();
|
||||
if (table.IsMemoryOptimized)
|
||||
{
|
||||
selectQuery.Append(" WITH (SNAPSHOT)");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// log any exceptions determining if InMemory, but don't treat as fatal exception
|
||||
Logger.Error("Could not determine if is InMemory table " + ex.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
return selectQuery.ToString();
|
||||
}
|
||||
|
||||
/// <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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the value whether the server supports XTP or not s
|
||||
internal static bool IsXTPSupportedOnServer(Server server)
|
||||
{
|
||||
bool isXTPSupported = false;
|
||||
if (server.ConnectionContext.ExecuteScalar("SELECT SERVERPROPERTY('IsXTPSupported')") != DBNull.Value)
|
||||
{
|
||||
isXTPSupported = server.IsXTPSupported;
|
||||
}
|
||||
return isXTPSupported;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -3,13 +3,11 @@
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Microsoft.SqlTools.Utility;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Scripting
|
||||
namespace Microsoft.SqlTools.SqlCore.Scripting
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for scripting operations. Because scripting operations can be very long
|
||||
@@ -3,9 +3,7 @@
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Scripting.Contracts
|
||||
namespace Microsoft.SqlTools.SqlCore.Scripting
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the scripting options.
|
||||
@@ -3,19 +3,18 @@
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.SqlServer.Management.Common;
|
||||
using Microsoft.SqlTools.ServiceLayer.Scripting.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection;
|
||||
using Microsoft.SqlTools.SqlCore.Connection;
|
||||
using Microsoft.SqlTools.SqlCore.Scripting.Contracts;
|
||||
using Microsoft.SqlTools.Utility;
|
||||
using static Microsoft.SqlServer.Management.SqlScriptPublish.SqlScriptOptions;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Scripting
|
||||
namespace Microsoft.SqlTools.SqlCore.Scripting
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for all SMO scripting operations
|
||||
@@ -78,7 +77,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Scripting
|
||||
string serverName = null;
|
||||
using (SqlConnection connection = new SqlConnection(connectionString))
|
||||
{
|
||||
connection.RetryLogicProvider = Connection.ReliableConnection.SqlRetryProviders.ServerlessDBRetryProvider();
|
||||
connection.RetryLogicProvider = SqlRetryProviders.ServerlessDBRetryProvider();
|
||||
if (azureAccessToken != null)
|
||||
{
|
||||
connection.AccessToken = azureAccessToken;
|
||||
@@ -7,9 +7,8 @@ using System;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Common;
|
||||
using Microsoft.SqlTools.CoreSql.ObjectExplorer;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Common.Extensions;
|
||||
using Microsoft.SqlTools.SqlCore.ObjectExplorer;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Common.Extensions;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.ObjectExplorer
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
//
|
||||
// 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 System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Common;
|
||||
using Microsoft.SqlTools.SqlCore.Scripting;
|
||||
using Microsoft.SqlTools.SqlCore.Scripting.Contracts;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Scripting
|
||||
{
|
||||
public class AsyncScriptAsScriptingOperationTests
|
||||
{
|
||||
public static IEnumerable<TestCaseData> ScriptAsTestCases
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return new TestCaseData(
|
||||
"CREATE TABLE testTable1 (c1 int)",
|
||||
new ScriptingParams()
|
||||
{
|
||||
ScriptDestination = "ToEditor",
|
||||
ScriptingObjects = new List<ScriptingObject>()
|
||||
{
|
||||
new ScriptingObject()
|
||||
{
|
||||
Name = "testTable1",
|
||||
Schema = "dbo",
|
||||
Type = "Table",
|
||||
|
||||
}
|
||||
},
|
||||
Operation = ScriptingOperationType.Select,
|
||||
ScriptOptions = new ScriptOptions()
|
||||
{
|
||||
ScriptCreateDrop = "ScriptSelect"
|
||||
}
|
||||
},
|
||||
new List<string>() { "SELECT TOP (1000) [c1]" });
|
||||
|
||||
yield return new TestCaseData(
|
||||
"CREATE TABLE testTable1 (c1 int)",
|
||||
new ScriptingParams()
|
||||
{
|
||||
ScriptDestination = "ToEditor",
|
||||
ScriptingObjects = new List<ScriptingObject>()
|
||||
{
|
||||
new ScriptingObject()
|
||||
{
|
||||
Name = "testTable1",
|
||||
Schema = "dbo",
|
||||
Type = "Table"
|
||||
}
|
||||
},
|
||||
Operation = ScriptingOperationType.Delete,
|
||||
ScriptOptions = new ScriptOptions()
|
||||
{
|
||||
ScriptCreateDrop = "ScriptDrop"
|
||||
}
|
||||
},
|
||||
new List<string> { "DROP TABLE [dbo].[testTable1]" }
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
@"CREATE TABLE testTable1 (c1 int)
|
||||
GO
|
||||
CREATE CLUSTERED INDEX [ClusteredIndex-1] ON [dbo].[testTable1]
|
||||
(
|
||||
[c1] ASC
|
||||
)
|
||||
GO
|
||||
",
|
||||
new ScriptingParams()
|
||||
{
|
||||
ScriptDestination = "ToEditor",
|
||||
ScriptingObjects = new List<ScriptingObject>()
|
||||
{
|
||||
new ScriptingObject()
|
||||
{
|
||||
Name = "testTable1",
|
||||
Schema = "dbo",
|
||||
Type = "Table"
|
||||
}
|
||||
},
|
||||
Operation = ScriptingOperationType.Create,
|
||||
ScriptOptions = new ScriptOptions()
|
||||
{
|
||||
ScriptCreateDrop = "ScriptCreate"
|
||||
}
|
||||
},
|
||||
new List<string> { "CREATE TABLE [dbo].[testTable1]", "CREATE CLUSTERED INDEX [ClusteredIndex-1] ON [dbo].[testTable1]" }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCaseSource("ScriptAsTestCases")]
|
||||
public async Task TestCommonScenarios(
|
||||
string query, ScriptingParams scriptingParams, List<string> expectedScriptContents)
|
||||
{
|
||||
var testDb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, false, null, query, "ScriptingTests");
|
||||
scriptingParams.ConnectionString = testDb.ConnectionString;
|
||||
|
||||
var actualScript = await AsyncScriptAsScriptingOperation.GetScriptAsScript(scriptingParams, null);
|
||||
|
||||
foreach(var expectedStr in expectedScriptContents)
|
||||
{
|
||||
Assert.That(actualScript, Does.Contain(expectedStr));
|
||||
}
|
||||
|
||||
await testDb.CleanupAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,8 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using static Microsoft.SqlTools.ServiceLayer.IntegrationTests.Utility.LiveConnectionHelper;
|
||||
using Microsoft.SqlTools.SqlCore.Scripting.Contracts;
|
||||
using Microsoft.SqlTools.SqlCore.Scripting;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Scripting
|
||||
{
|
||||
|
||||
@@ -8,8 +8,9 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Scripting.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Common;
|
||||
using Microsoft.SqlTools.SqlCore.Scripting;
|
||||
using Microsoft.SqlTools.SqlCore.Scripting.Contracts;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.PerfTests
|
||||
|
||||
@@ -20,6 +20,7 @@ using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Microsoft.SqlTools.ServiceLayer.TestDriver.Driver;
|
||||
using Microsoft.SqlTools.ServiceLayer.TestDriver.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||
using Microsoft.SqlTools.SqlCore.Scripting.Contracts;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Test.Common
|
||||
|
||||
@@ -12,6 +12,8 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Scripting.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Common;
|
||||
using Microsoft.SqlTools.SqlCore.Scripting;
|
||||
using Microsoft.SqlTools.SqlCore.Scripting.Contracts;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.TestDriver.Tests
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
#nullable disable
|
||||
|
||||
using Microsoft.SqlServer.Management.Sdk.Sfc;
|
||||
using Microsoft.SqlTools.ServiceLayer.Scripting;
|
||||
using Microsoft.SqlTools.ServiceLayer.Scripting.Contracts;
|
||||
using Microsoft.SqlTools.SqlCore.Scripting.Contracts;
|
||||
using Microsoft.SqlTools.SqlCore.Scripting;
|
||||
using NUnit.Framework;
|
||||
using Assert = NUnit.Framework.Assert;
|
||||
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.SqlTools.ServiceLayer.Scripting;
|
||||
using Microsoft.SqlTools.ServiceLayer.Scripting.Contracts;
|
||||
using Microsoft.SqlTools.SqlCore.Scripting;
|
||||
using Microsoft.SqlTools.SqlCore.Scripting.Contracts;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Scripting
|
||||
@@ -451,7 +452,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Scripting
|
||||
{
|
||||
for (int i = 0; i < TestObjects.Length; i++)
|
||||
{
|
||||
Assert.AreEqual(Scripter.ScriptingUtils.QuoteObjectName(TestObjects[i]), ExpectedObjects[i]);
|
||||
Assert.AreEqual(ScriptingHelper.QuoteObjectName(TestObjects[i]), ExpectedObjects[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user