mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-28 09:35:37 -05:00
Merge pull request #53 from Microsoft/dev
Merge to master for dogfood release.
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -36,6 +36,7 @@ node_modules
|
||||
packages
|
||||
reports
|
||||
opencovertests.xml
|
||||
outputCobertura.xml
|
||||
sqltools.xml
|
||||
|
||||
# Cross building rootfs
|
||||
@@ -275,3 +276,7 @@ Session.vim
|
||||
|
||||
# Visual Studio Code
|
||||
.vscode/
|
||||
|
||||
# Stuff from cake
|
||||
/artifacts/
|
||||
/.tools/
|
||||
153
build.cake
153
build.cake
@@ -81,7 +81,8 @@ Task("Cleanup")
|
||||
/// Pre-build setup tasks.
|
||||
/// </summary>
|
||||
Task("Setup")
|
||||
.IsDependentOn("BuildEnvironment")
|
||||
.IsDependentOn("InstallDotnet")
|
||||
.IsDependentOn("InstallXUnit")
|
||||
.IsDependentOn("PopulateRuntimes")
|
||||
.Does(() =>
|
||||
{
|
||||
@@ -92,7 +93,6 @@ Task("Setup")
|
||||
/// Use default RID (+ win7-x86 on Windows) for now.
|
||||
/// </summary>
|
||||
Task("PopulateRuntimes")
|
||||
.IsDependentOn("BuildEnvironment")
|
||||
.Does(() =>
|
||||
{
|
||||
buildPlan.Rids = new string[]
|
||||
@@ -112,43 +112,65 @@ Task("PopulateRuntimes")
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Install/update build environment.
|
||||
/// Install dotnet if it isn't already installed
|
||||
/// </summary>
|
||||
Task("BuildEnvironment")
|
||||
Task("InstallDotnet")
|
||||
.Does(() =>
|
||||
{
|
||||
var installScript = $"dotnet-install.{shellExtension}";
|
||||
System.IO.Directory.CreateDirectory(dotnetFolder);
|
||||
var scriptPath = System.IO.Path.Combine(dotnetFolder, installScript);
|
||||
using (WebClient client = new WebClient())
|
||||
{
|
||||
client.DownloadFile($"{buildPlan.DotNetInstallScriptURL}/{installScript}", scriptPath);
|
||||
}
|
||||
if (!IsRunningOnWindows())
|
||||
{
|
||||
Run("chmod", $"+x '{scriptPath}'");
|
||||
}
|
||||
var installArgs = $"-Channel {buildPlan.DotNetChannel}";
|
||||
if (!String.IsNullOrEmpty(buildPlan.DotNetVersion))
|
||||
{
|
||||
installArgs = $"{installArgs} -Version {buildPlan.DotNetVersion}";
|
||||
}
|
||||
if (!buildPlan.UseSystemDotNetPath)
|
||||
{
|
||||
installArgs = $"{installArgs} -InstallDir {dotnetFolder}";
|
||||
}
|
||||
Run(shell, $"{shellArgument} {scriptPath} {installArgs}");
|
||||
try
|
||||
{
|
||||
Run(dotnetcli, "--info");
|
||||
}
|
||||
catch (Win32Exception)
|
||||
{
|
||||
throw new Exception(".NET CLI binary cannot be found.");
|
||||
}
|
||||
// Determine if `dotnet` is installed
|
||||
var dotnetInstalled = true;
|
||||
try
|
||||
{
|
||||
Run(dotnetcli, "--info");
|
||||
Information("dotnet is already installed, will skip download/install");
|
||||
}
|
||||
catch(Win32Exception)
|
||||
{
|
||||
// If we get this exception, dotnet isn't installed
|
||||
dotnetInstalled = false;
|
||||
}
|
||||
|
||||
System.IO.Directory.CreateDirectory(toolsFolder);
|
||||
// Install dotnet if it isn't already installed
|
||||
if (!dotnetInstalled)
|
||||
{
|
||||
var installScript = $"dotnet-install.{shellExtension}";
|
||||
System.IO.Directory.CreateDirectory(dotnetFolder);
|
||||
var scriptPath = System.IO.Path.Combine(dotnetFolder, installScript);
|
||||
using (WebClient client = new WebClient())
|
||||
{
|
||||
client.DownloadFile($"{buildPlan.DotNetInstallScriptURL}/{installScript}", scriptPath);
|
||||
}
|
||||
if (!IsRunningOnWindows())
|
||||
{
|
||||
Run("chmod", $"+x '{scriptPath}'");
|
||||
}
|
||||
var installArgs = $"-Channel {buildPlan.DotNetChannel}";
|
||||
if (!String.IsNullOrEmpty(buildPlan.DotNetVersion))
|
||||
{
|
||||
installArgs = $"{installArgs} -Version {buildPlan.DotNetVersion}";
|
||||
}
|
||||
if (!buildPlan.UseSystemDotNetPath)
|
||||
{
|
||||
installArgs = $"{installArgs} -InstallDir {dotnetFolder}";
|
||||
}
|
||||
Run(shell, $"{shellArgument} {scriptPath} {installArgs}");
|
||||
try
|
||||
{
|
||||
Run(dotnetcli, "--info");
|
||||
}
|
||||
catch (Win32Exception)
|
||||
{
|
||||
throw new Exception(".NET CLI failed to be installed");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Installs XUnit nuget package
|
||||
Task("InstallXUnit")
|
||||
.Does(() =>
|
||||
{
|
||||
// Install the tools
|
||||
var nugetPath = Environment.GetEnvironmentVariable("NUGET_EXE");
|
||||
var arguments = $"install xunit.runner.console -ExcludeVersion -NoCache -Prerelease -OutputDirectory \"{toolsFolder}\"";
|
||||
if (IsRunningOnWindows())
|
||||
@@ -208,14 +230,6 @@ Task("TestAll")
|
||||
.IsDependentOn("TestCore")
|
||||
.Does(() =>{});
|
||||
|
||||
/// <summary>
|
||||
/// Run all tests for Travis CI .NET Desktop and .NET Core
|
||||
/// </summary>
|
||||
Task("TravisTestAll")
|
||||
.IsDependentOn("Cleanup")
|
||||
.IsDependentOn("TestAll")
|
||||
.Does(() =>{});
|
||||
|
||||
/// <summary>
|
||||
/// Run tests for .NET Core (using .NET CLI).
|
||||
/// </summary>
|
||||
@@ -345,6 +359,7 @@ Task("RestrictToLocalRuntime")
|
||||
/// </summary>
|
||||
Task("LocalPublish")
|
||||
.IsDependentOn("Restore")
|
||||
.IsDependentOn("SrGen")
|
||||
.IsDependentOn("RestrictToLocalRuntime")
|
||||
.IsDependentOn("OnlyPublish")
|
||||
.Does(() =>
|
||||
@@ -451,20 +466,6 @@ Task("Local")
|
||||
{
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Build centered around producing the final artifacts for Travis
|
||||
///
|
||||
/// The tests are run as a different task "TestAll"
|
||||
/// </summary>
|
||||
Task("Travis")
|
||||
.IsDependentOn("Cleanup")
|
||||
.IsDependentOn("Restore")
|
||||
.IsDependentOn("AllPublish")
|
||||
// .IsDependentOn("TestPublished")
|
||||
.Does(() =>
|
||||
{
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Update the package versions within project.json files.
|
||||
/// Uses depversion.json file as input.
|
||||
@@ -492,6 +493,46 @@ Task("SetPackageVersions")
|
||||
}
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Executes SRGen to create a resx file and associated designer C# file
|
||||
/// </summary>
|
||||
Task("SRGen")
|
||||
.Does(() =>
|
||||
{
|
||||
var projects = System.IO.Directory.GetFiles(sourceFolder, "project.json", SearchOption.AllDirectories).ToList();
|
||||
foreach(var project in projects) {
|
||||
var projectDir = System.IO.Path.GetDirectoryName(project);
|
||||
var projectName = (new System.IO.DirectoryInfo(projectDir)).Name;
|
||||
var projectStrings = System.IO.Path.Combine(projectDir, "sr.strings");
|
||||
|
||||
if (!System.IO.File.Exists(projectStrings))
|
||||
{
|
||||
Information("Project {0} doesn't contain 'sr.strings' file", projectName);
|
||||
continue;
|
||||
}
|
||||
|
||||
var srgenPath = System.IO.Path.Combine(toolsFolder, "Microsoft.DataTools.SrGen", "lib", "netcoreapp1.0", "srgen.dll");
|
||||
var outputResx = System.IO.Path.Combine(projectDir, "sr.resx");
|
||||
var outputCs = System.IO.Path.Combine(projectDir, "sr.cs");
|
||||
|
||||
// Delete preexisting resx and designer files
|
||||
if (System.IO.File.Exists(outputResx))
|
||||
{
|
||||
System.IO.File.Delete(outputResx);
|
||||
}
|
||||
if (System.IO.File.Exists(outputCs))
|
||||
{
|
||||
System.IO.File.Delete(outputCs);
|
||||
}
|
||||
|
||||
// Run SRGen
|
||||
var dotnetArgs = string.Format("{0} -or \"{1}\" -oc \"{2}\" -ns \"{3}\" -an \"{4}\" -cn SR -l CS \"{5}\"",
|
||||
srgenPath, outputResx, outputCs, projectName, projectName, projectStrings);
|
||||
Information("{0}", dotnetArgs);
|
||||
Run(dotnetcli, dotnetArgs);
|
||||
}
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Default Task aliases to Local.
|
||||
/// </summary>
|
||||
|
||||
@@ -106,5 +106,7 @@ if (!(Test-Path $CAKE_EXE)) {
|
||||
|
||||
# Start Cake
|
||||
Write-Host "Running build script..."
|
||||
Invoke-Expression "& `"$CAKE_EXE`" `"$Script`" -verbosity=`"$Verbosity`" $UseMono $UseDryRun $ScriptArgs"
|
||||
$v = "& `"$CAKE_EXE`" `"$Script`" -verbosity=`"$Verbosity`" $UseMono $UseDryRun $ScriptArgs"
|
||||
Write-Host $v
|
||||
Invoke-Expression $v
|
||||
exit $LASTEXITCODE
|
||||
|
||||
@@ -2,4 +2,5 @@
|
||||
<packages>
|
||||
<package id="Cake" version="0.10.1" />
|
||||
<package id="Newtonsoft.Json" version="8.0.3" />
|
||||
<package id="Microsoft.DataTools.SrGen" version="1.0.0" />
|
||||
</packages>
|
||||
|
||||
@@ -9,6 +9,7 @@ EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{32DC973E-9EEA-4694-B1C2-B031167AB945}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.gitignore = .gitignore
|
||||
BUILD.md = BUILD.md
|
||||
global.json = global.json
|
||||
nuget.config = nuget.config
|
||||
README.md = README.md
|
||||
@@ -18,6 +19,25 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.SqlTools.ServiceL
|
||||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.SqlTools.ServiceLayer.Test", "test\Microsoft.SqlTools.ServiceLayer.Test\Microsoft.SqlTools.ServiceLayer.Test.xproj", "{2D771D16-9D85-4053-9F79-E2034737DEEF}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "scripts", "scripts", "{B7D21727-2926-452B-9610-3ADB0BB6D789}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
scripts\archiving.cake = scripts\archiving.cake
|
||||
scripts\artifacts.cake = scripts\artifacts.cake
|
||||
scripts\cake-bootstrap.ps1 = scripts\cake-bootstrap.ps1
|
||||
scripts\cake-bootstrap.sh = scripts\cake-bootstrap.sh
|
||||
scripts\packages.config = scripts\packages.config
|
||||
scripts\runhelpers.cake = scripts\runhelpers.cake
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{F9978D78-78FE-4E92-A7D6-D436B7683EF6}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
build.cake = build.cake
|
||||
build.cmd = build.cmd
|
||||
build.json = build.json
|
||||
build.ps1 = build.ps1
|
||||
build.sh = build.sh
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -39,5 +59,6 @@ Global
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{0D61DC2B-DA66-441D-B9D0-F76C98F780F9} = {2BBD7364-054F-4693-97CD-1C395E3E84A9}
|
||||
{2D771D16-9D85-4053-9F79-E2034737DEEF} = {AB9CA2B8-6F70-431C-8A1D-67479D8A7BE4}
|
||||
{B7D21727-2926-452B-9610-3ADB0BB6D789} = {F9978D78-78FE-4E92-A7D6-D436B7683EF6}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
@@ -11,6 +11,7 @@ using System.Data.SqlClient;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.EditorServices.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
||||
@@ -169,12 +170,45 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
|
||||
|
||||
ownerToConnectionMap[connectionParams.OwnerUri] = connectionInfo;
|
||||
|
||||
// Update with the actual database name in connectionInfo and result
|
||||
// Doing this here as we know the connection is open - expect to do this only on connecting
|
||||
connectionInfo.ConnectionDetails.DatabaseName = connectionInfo.SqlConnection.Database;
|
||||
response.ConnectionSummary = new ConnectionSummary()
|
||||
{
|
||||
ServerName = connectionInfo.ConnectionDetails.ServerName,
|
||||
DatabaseName = connectionInfo.ConnectionDetails.DatabaseName,
|
||||
UserName = connectionInfo.ConnectionDetails.UserName,
|
||||
};
|
||||
|
||||
// invoke callback notifications
|
||||
foreach (var activity in this.onConnectionActivities)
|
||||
{
|
||||
activity(connectionInfo);
|
||||
}
|
||||
|
||||
// try to get information about the connected SQL Server instance
|
||||
try
|
||||
{
|
||||
ReliableConnectionHelper.ServerInfo serverInfo = ReliableConnectionHelper.GetServerVersion(connectionInfo.SqlConnection);
|
||||
response.ServerInfo = new Contracts.ServerInfo()
|
||||
{
|
||||
ServerMajorVersion = serverInfo.ServerMajorVersion,
|
||||
ServerMinorVersion = serverInfo.ServerMinorVersion,
|
||||
ServerReleaseVersion = serverInfo.ServerReleaseVersion,
|
||||
EngineEditionId = serverInfo.EngineEditionId,
|
||||
ServerVersion = serverInfo.ServerVersion,
|
||||
ServerLevel = serverInfo.ServerLevel,
|
||||
ServerEdition = serverInfo.ServerEdition,
|
||||
IsCloud = serverInfo.IsCloud,
|
||||
AzureVersion = serverInfo.AzureVersion,
|
||||
OsVersion = serverInfo.OsVersion
|
||||
};
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
response.Messages = ex.ToString();
|
||||
}
|
||||
|
||||
// return the connection result
|
||||
response.ConnectionId = connectionInfo.ConnectionId.ToString();
|
||||
return response;
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Connection.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -19,5 +19,15 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection.Contracts
|
||||
/// Gets or sets any connection error messages
|
||||
/// </summary>
|
||||
public string Messages { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Information about the connected server.
|
||||
/// </summary>
|
||||
public ServerInfo ServerInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the actual Connection established, including Database Name
|
||||
/// </summary>
|
||||
public ConnectionSummary ConnectionSummary { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Connection.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// Contract for information on the connected SQL Server instance.
|
||||
/// </summary>
|
||||
public class ServerInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// The major version of the SQL Server instance.
|
||||
/// </summary>
|
||||
public int ServerMajorVersion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The minor version of the SQL Server instance.
|
||||
/// </summary>
|
||||
public int ServerMinorVersion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The build of the SQL Server instance.
|
||||
/// </summary>
|
||||
public int ServerReleaseVersion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The ID of the engine edition of the SQL Server instance.
|
||||
/// </summary>
|
||||
public int EngineEditionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// String containing the full server version text.
|
||||
/// </summary>
|
||||
public string ServerVersion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// String describing the product level of the server.
|
||||
/// </summary>
|
||||
public string ServerLevel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The edition of the SQL Server instance.
|
||||
/// </summary>
|
||||
public string ServerEdition { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the SQL Server instance is running in the cloud (Azure) or not.
|
||||
/// </summary>
|
||||
public bool IsCloud { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The version of Azure that the SQL Server instance is running on, if applicable.
|
||||
/// </summary>
|
||||
public int AzureVersion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The Operating System version string of the machine running the SQL Server instance.
|
||||
/// </summary>
|
||||
public string OsVersion { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,452 @@
|
||||
//
|
||||
// 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.Reflection;
|
||||
using Microsoft.SqlTools.EditorServices.Utility;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
|
||||
{
|
||||
/// <summary>
|
||||
/// This class represents connection (and other) settings specified by called of the DacFx API. DacFx
|
||||
/// cannot rely on the registry to supply override values therefore setting overrides must be made
|
||||
/// by the top-of-the-stack
|
||||
/// </summary>
|
||||
internal sealed class AmbientSettings
|
||||
{
|
||||
private const string LogicalContextName = "__LocalContextConfigurationName";
|
||||
|
||||
internal enum StreamBackingStore
|
||||
{
|
||||
// MemoryStream
|
||||
Memory = 0,
|
||||
|
||||
// FileStream
|
||||
File = 1
|
||||
}
|
||||
|
||||
// Internal for test purposes
|
||||
internal const string MasterReferenceFilePathIndex = "MasterReferenceFilePath";
|
||||
internal const string DatabaseLockTimeoutIndex = "DatabaseLockTimeout";
|
||||
internal const string QueryTimeoutIndex = "QueryTimeout";
|
||||
internal const string LongRunningQueryTimeoutIndex = "LongRunningQueryTimeout";
|
||||
internal const string AlwaysRetryOnTransientFailureIndex = "AlwaysRetryOnTransientFailure";
|
||||
internal const string MaxDataReaderDegreeOfParallelismIndex = "MaxDataReaderDegreeOfParallelism";
|
||||
internal const string ConnectionRetryHandlerIndex = "ConnectionRetryHandler";
|
||||
internal const string TraceRowCountFailureIndex = "TraceRowCountFailure";
|
||||
internal const string TableProgressUpdateIntervalIndex = "TableProgressUpdateInterval";
|
||||
internal const string UseOfflineDataReaderIndex = "UseOfflineDataReader";
|
||||
internal const string StreamBackingStoreForOfflineDataReadingIndex = "StreamBackingStoreForOfflineDataReading";
|
||||
internal const string DisableIndexesForDataPhaseIndex = "DisableIndexesForDataPhase";
|
||||
internal const string ReliableDdlEnabledIndex = "ReliableDdlEnabled";
|
||||
internal const string ImportModelDatabaseIndex = "ImportModelDatabase";
|
||||
internal const string SupportAlwaysEncryptedIndex = "SupportAlwaysEncrypted";
|
||||
internal const string SkipObjectTypeBlockingIndex = "SkipObjectTypeBlocking";
|
||||
internal const string DoNotSerializeQueryStoreSettingsIndex = "DoNotSerializeQueryStoreSettings";
|
||||
internal const string AlwaysEncryptedWizardMigrationIndex = "AlwaysEncryptedWizardMigration";
|
||||
|
||||
private static readonly AmbientData _defaultSettings;
|
||||
|
||||
static AmbientSettings()
|
||||
{
|
||||
_defaultSettings = new AmbientData();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Access to the default ambient settings. Access to these settings is made available
|
||||
/// for SSDT scenarios where settings are read from the registry and not set explicitly through
|
||||
/// the API
|
||||
/// </summary>
|
||||
public static AmbientData DefaultSettings
|
||||
{
|
||||
get { return _defaultSettings; }
|
||||
}
|
||||
|
||||
public static string MasterReferenceFilePath
|
||||
{
|
||||
get { return GetValue<string>(MasterReferenceFilePathIndex); }
|
||||
}
|
||||
|
||||
public static int LockTimeoutMilliSeconds
|
||||
{
|
||||
get { return GetValue<int>(DatabaseLockTimeoutIndex); }
|
||||
}
|
||||
|
||||
public static int QueryTimeoutSeconds
|
||||
{
|
||||
get { return GetValue<int>(QueryTimeoutIndex); }
|
||||
}
|
||||
|
||||
public static int LongRunningQueryTimeoutSeconds
|
||||
{
|
||||
get { return GetValue<int>(LongRunningQueryTimeoutIndex); }
|
||||
}
|
||||
|
||||
public static Action<SqlServerRetryError> ConnectionRetryMessageHandler
|
||||
{
|
||||
get { return GetValue<Action<SqlServerRetryError>>(ConnectionRetryHandlerIndex); }
|
||||
}
|
||||
|
||||
public static bool AlwaysRetryOnTransientFailure
|
||||
{
|
||||
get { return GetValue<bool>(AlwaysRetryOnTransientFailureIndex); }
|
||||
}
|
||||
|
||||
public static int MaxDataReaderDegreeOfParallelism
|
||||
{
|
||||
get { return GetValue<int>(MaxDataReaderDegreeOfParallelismIndex); }
|
||||
}
|
||||
|
||||
public static int TableProgressUpdateInterval
|
||||
{
|
||||
// value of zero means do not fire 'heartbeat' progress events. Non-zero values will
|
||||
// fire a heartbeat progress event every n seconds.
|
||||
get { return GetValue<int>(TableProgressUpdateIntervalIndex); }
|
||||
}
|
||||
|
||||
public static bool TraceRowCountFailure
|
||||
{
|
||||
get { return GetValue<bool>(TraceRowCountFailureIndex); }
|
||||
}
|
||||
|
||||
public static bool UseOfflineDataReader
|
||||
{
|
||||
get { return GetValue<bool>(UseOfflineDataReaderIndex); }
|
||||
}
|
||||
|
||||
public static StreamBackingStore StreamBackingStoreForOfflineDataReading
|
||||
{
|
||||
get { return GetValue<StreamBackingStore>(StreamBackingStoreForOfflineDataReadingIndex); }
|
||||
}
|
||||
|
||||
public static bool DisableIndexesForDataPhase
|
||||
{
|
||||
get { return GetValue<bool>(DisableIndexesForDataPhaseIndex); }
|
||||
}
|
||||
|
||||
public static bool ReliableDdlEnabled
|
||||
{
|
||||
get { return GetValue<bool>(ReliableDdlEnabledIndex); }
|
||||
}
|
||||
|
||||
public static bool ImportModelDatabase
|
||||
{
|
||||
get { return GetValue<bool>(ImportModelDatabaseIndex); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setting that shows whether Always Encrypted is supported.
|
||||
/// If false, then reverse engineering and script interpretation of a database with any Always Encrypted object will fail
|
||||
/// </summary>
|
||||
public static bool SupportAlwaysEncrypted
|
||||
{
|
||||
get { return GetValue<bool>(SupportAlwaysEncryptedIndex); }
|
||||
}
|
||||
|
||||
public static bool AlwaysEncryptedWizardMigration
|
||||
{
|
||||
get { return GetValue<bool>(AlwaysEncryptedWizardMigrationIndex); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setting that determines whether checks for unsupported object types are performed.
|
||||
/// If false, unsupported object types will prevent extract from being performed.
|
||||
/// Default value is false.
|
||||
/// </summary>
|
||||
public static bool SkipObjectTypeBlocking
|
||||
{
|
||||
get { return GetValue<bool>(SkipObjectTypeBlockingIndex); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setting that determines whether the Database Options that store Query Store settings will be left out during package serialization.
|
||||
/// Default value is false.
|
||||
/// </summary>
|
||||
public static bool DoNotSerializeQueryStoreSettings
|
||||
{
|
||||
get { return GetValue<bool>(DoNotSerializeQueryStoreSettingsIndex); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called by top-of-stack API to setup/configure settings that should be used
|
||||
/// throughout the API (lower in the stack). The settings are reverted once the returned context
|
||||
/// has been disposed.
|
||||
/// </summary>
|
||||
public static IStackSettingsContext CreateSettingsContext()
|
||||
{
|
||||
return new StackConfiguration();
|
||||
}
|
||||
|
||||
private static T1 GetValue<T1>(string configIndex)
|
||||
{
|
||||
IAmbientDataDirectAccess config = _defaultSettings;
|
||||
|
||||
return (T1)config.Data[configIndex].Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Data-transfer object that represents a specific configuration
|
||||
/// </summary>
|
||||
public class AmbientData : IAmbientDataDirectAccess
|
||||
{
|
||||
private readonly Dictionary<string, AmbientValue> _configuration;
|
||||
|
||||
public AmbientData()
|
||||
{
|
||||
_configuration = new Dictionary<string, AmbientValue>(StringComparer.OrdinalIgnoreCase);
|
||||
_configuration[DatabaseLockTimeoutIndex] = new AmbientValue(5000);
|
||||
_configuration[QueryTimeoutIndex] = new AmbientValue(60);
|
||||
_configuration[LongRunningQueryTimeoutIndex] = new AmbientValue(0);
|
||||
_configuration[AlwaysRetryOnTransientFailureIndex] = new AmbientValue(false);
|
||||
_configuration[ConnectionRetryHandlerIndex] = new AmbientValue(typeof(Action<SqlServerRetryError>), null);
|
||||
_configuration[MaxDataReaderDegreeOfParallelismIndex] = new AmbientValue(8);
|
||||
_configuration[TraceRowCountFailureIndex] = new AmbientValue(false); // default: throw DacException on rowcount mismatch during import/export data validation
|
||||
_configuration[TableProgressUpdateIntervalIndex] = new AmbientValue(300); // default: fire heartbeat progress update events every 5 minutes
|
||||
_configuration[UseOfflineDataReaderIndex] = new AmbientValue(false);
|
||||
_configuration[StreamBackingStoreForOfflineDataReadingIndex] = new AmbientValue(StreamBackingStore.File); //applicable only when UseOfflineDataReader is set to true
|
||||
_configuration[MasterReferenceFilePathIndex] = new AmbientValue(typeof(string), null);
|
||||
// Defect 1210884: Enable an option to allow secondary index, check and fk constraints to stay enabled during data upload with import in DACFX for IES
|
||||
_configuration[DisableIndexesForDataPhaseIndex] = new AmbientValue(true);
|
||||
_configuration[ReliableDdlEnabledIndex] = new AmbientValue(false);
|
||||
_configuration[ImportModelDatabaseIndex] = new AmbientValue(true);
|
||||
_configuration[SupportAlwaysEncryptedIndex] = new AmbientValue(false);
|
||||
_configuration[AlwaysEncryptedWizardMigrationIndex] = new AmbientValue(false);
|
||||
_configuration[SkipObjectTypeBlockingIndex] = new AmbientValue(false);
|
||||
_configuration[DoNotSerializeQueryStoreSettingsIndex] = new AmbientValue(false);
|
||||
}
|
||||
|
||||
public string MasterReferenceFilePath
|
||||
{
|
||||
get { return (string)_configuration[MasterReferenceFilePathIndex].Value; }
|
||||
set { _configuration[MasterReferenceFilePathIndex].Value = value; }
|
||||
}
|
||||
|
||||
public int LockTimeoutMilliSeconds
|
||||
{
|
||||
get { return (int)_configuration[DatabaseLockTimeoutIndex].Value; }
|
||||
set { _configuration[DatabaseLockTimeoutIndex].Value = value; }
|
||||
}
|
||||
public int QueryTimeoutSeconds
|
||||
{
|
||||
get { return (int)_configuration[QueryTimeoutIndex].Value; }
|
||||
set { _configuration[QueryTimeoutIndex].Value = value; }
|
||||
}
|
||||
public int LongRunningQueryTimeoutSeconds
|
||||
{
|
||||
get { return (int)_configuration[LongRunningQueryTimeoutIndex].Value; }
|
||||
set { _configuration[LongRunningQueryTimeoutIndex].Value = value; }
|
||||
}
|
||||
public bool AlwaysRetryOnTransientFailure
|
||||
{
|
||||
get { return (bool)_configuration[AlwaysRetryOnTransientFailureIndex].Value; }
|
||||
set { _configuration[AlwaysRetryOnTransientFailureIndex].Value = value; }
|
||||
}
|
||||
public Action<SqlServerRetryError> ConnectionRetryMessageHandler
|
||||
{
|
||||
get { return (Action<SqlServerRetryError>)_configuration[ConnectionRetryHandlerIndex].Value; }
|
||||
set { _configuration[ConnectionRetryHandlerIndex].Value = value; }
|
||||
}
|
||||
public bool TraceRowCountFailure
|
||||
{
|
||||
get { return (bool)_configuration[TraceRowCountFailureIndex].Value; }
|
||||
set { _configuration[TraceRowCountFailureIndex].Value = value; }
|
||||
}
|
||||
public int TableProgressUpdateInterval
|
||||
{
|
||||
get { return (int)_configuration[TableProgressUpdateIntervalIndex].Value; }
|
||||
set { _configuration[TableProgressUpdateIntervalIndex].Value = value; }
|
||||
}
|
||||
|
||||
public bool UseOfflineDataReader
|
||||
{
|
||||
get { return (bool)_configuration[UseOfflineDataReaderIndex].Value; }
|
||||
set { _configuration[UseOfflineDataReaderIndex].Value = value; }
|
||||
}
|
||||
|
||||
public StreamBackingStore StreamBackingStoreForOfflineDataReading
|
||||
{
|
||||
get { return (StreamBackingStore)_configuration[StreamBackingStoreForOfflineDataReadingIndex].Value; }
|
||||
set { _configuration[StreamBackingStoreForOfflineDataReadingIndex].Value = value; }
|
||||
}
|
||||
|
||||
public bool DisableIndexesForDataPhase
|
||||
{
|
||||
get { return (bool)_configuration[DisableIndexesForDataPhaseIndex].Value; }
|
||||
set { _configuration[DisableIndexesForDataPhaseIndex].Value = value; }
|
||||
}
|
||||
|
||||
public bool ReliableDdlEnabled
|
||||
{
|
||||
get { return (bool)_configuration[ReliableDdlEnabledIndex].Value; }
|
||||
set { _configuration[ReliableDdlEnabledIndex].Value = value; }
|
||||
}
|
||||
|
||||
public bool ImportModelDatabase
|
||||
{
|
||||
get { return (bool)_configuration[ImportModelDatabaseIndex].Value; }
|
||||
set { _configuration[ImportModelDatabaseIndex].Value = value; }
|
||||
}
|
||||
|
||||
internal bool SupportAlwaysEncrypted
|
||||
{
|
||||
get { return (bool)_configuration[SupportAlwaysEncryptedIndex].Value; }
|
||||
set { _configuration[SupportAlwaysEncryptedIndex].Value = value; }
|
||||
}
|
||||
|
||||
internal bool AlwaysEncryptedWizardMigration
|
||||
{
|
||||
get { return (bool)_configuration[AlwaysEncryptedWizardMigrationIndex].Value; }
|
||||
set { _configuration[AlwaysEncryptedWizardMigrationIndex].Value = value; }
|
||||
}
|
||||
|
||||
internal bool SkipObjectTypeBlocking
|
||||
{
|
||||
get { return (bool)_configuration[SkipObjectTypeBlockingIndex].Value; }
|
||||
set { _configuration[SkipObjectTypeBlockingIndex].Value = value; }
|
||||
}
|
||||
|
||||
internal bool DoNotSerializeQueryStoreSettings
|
||||
{
|
||||
get { return (bool)_configuration[DoNotSerializeQueryStoreSettingsIndex].Value; }
|
||||
set { _configuration[DoNotSerializeQueryStoreSettingsIndex].Value = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides a way to bulk populate settings from a dictionary
|
||||
/// </summary>
|
||||
public void PopulateSettings(IDictionary<string, object> settingsCollection)
|
||||
{
|
||||
if (settingsCollection != null)
|
||||
{
|
||||
Dictionary<string, object> newSettings = new Dictionary<string, object>();
|
||||
|
||||
// We know all the values are set on the current configuration
|
||||
foreach (KeyValuePair<string, object> potentialPair in settingsCollection)
|
||||
{
|
||||
AmbientValue currentValue;
|
||||
if (_configuration.TryGetValue(potentialPair.Key, out currentValue))
|
||||
{
|
||||
object newValue = potentialPair.Value;
|
||||
newSettings[potentialPair.Key] = newValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (newSettings.Count > 0)
|
||||
{
|
||||
foreach (KeyValuePair<string, object> newSetting in newSettings)
|
||||
{
|
||||
_configuration[newSetting.Key].Value = newSetting.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs the Ambient Settings
|
||||
/// </summary>
|
||||
public void TraceSettings()
|
||||
{
|
||||
// NOTE: logging as warning so we can get this data in the IEService DacFx logs
|
||||
Logger.Write(LogLevel.Warning, Resources.LoggingAmbientSettings);
|
||||
|
||||
foreach (KeyValuePair<string, AmbientValue> setting in _configuration)
|
||||
{
|
||||
// Log Ambient Settings
|
||||
Logger.Write(
|
||||
LogLevel.Warning,
|
||||
string.Format(
|
||||
Resources.AmbientSettingFormat,
|
||||
setting.Key,
|
||||
setting.Value == null ? setting.Value : setting.Value.Value));
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary<string, AmbientValue> IAmbientDataDirectAccess.Data
|
||||
{
|
||||
get { return _configuration; }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This class is used as value in the dictionary to ensure that the type of value is correct.
|
||||
/// </summary>
|
||||
private class AmbientValue
|
||||
{
|
||||
private readonly Type _type;
|
||||
private readonly bool _isTypeNullable;
|
||||
private object _value;
|
||||
|
||||
public AmbientValue(object value)
|
||||
: this(value == null ? null : value.GetType(), value)
|
||||
{
|
||||
}
|
||||
|
||||
public AmbientValue(Type type, object value)
|
||||
{
|
||||
if (type == null)
|
||||
{
|
||||
throw new ArgumentNullException("type");
|
||||
}
|
||||
_type = type;
|
||||
_isTypeNullable = !type.GetTypeInfo().IsValueType || Nullable.GetUnderlyingType(type) != null;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public object Value
|
||||
{
|
||||
get { return _value; }
|
||||
set
|
||||
{
|
||||
if ((_isTypeNullable && value == null) || _type.GetTypeInfo().IsInstanceOfType(value))
|
||||
{
|
||||
_value = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Write(LogLevel.Error, string.Format(Resources.UnableToAssignValue, value.GetType().FullName, _type.FullName));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This private interface allows pass-through access directly to member data
|
||||
/// </summary>
|
||||
private interface IAmbientDataDirectAccess
|
||||
{
|
||||
Dictionary<string, AmbientValue> Data { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This class encapsulated the concept of configuration that is set on the stack and
|
||||
/// flows across multiple threads as part of the logical call context
|
||||
/// </summary>
|
||||
private sealed class StackConfiguration : IStackSettingsContext
|
||||
{
|
||||
private readonly AmbientData _data;
|
||||
|
||||
public StackConfiguration()
|
||||
{
|
||||
_data = new AmbientData();
|
||||
//CallContext.LogicalSetData(LogicalContextName, _data);
|
||||
}
|
||||
|
||||
public AmbientData Settings
|
||||
{
|
||||
get { return _data; }
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
//CallContext.LogicalSetData(LogicalContextName, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
//
|
||||
// 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.Data;
|
||||
using System.Data.SqlClient;
|
||||
using System.Linq;
|
||||
using Microsoft.SqlTools.EditorServices.Utility;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
|
||||
{
|
||||
/// <summary>
|
||||
/// This class caches server information for subsequent use
|
||||
/// </summary>
|
||||
internal static class CachedServerInfo
|
||||
{
|
||||
private struct CachedInfo
|
||||
{
|
||||
public bool IsAzure;
|
||||
public DateTime LastUpdate;
|
||||
}
|
||||
|
||||
private static ConcurrentDictionary<string, CachedInfo> _cache;
|
||||
private static object _cacheLock;
|
||||
private const int _maxCacheSize = 1024;
|
||||
private const int _deleteBatchSize = 512;
|
||||
|
||||
private const int MinimalQueryTimeoutSecondsForAzure = 300;
|
||||
|
||||
static CachedServerInfo()
|
||||
{
|
||||
_cache = new ConcurrentDictionary<string, CachedInfo>(StringComparer.OrdinalIgnoreCase);
|
||||
_cacheLock = new object();
|
||||
}
|
||||
|
||||
public static int GetQueryTimeoutSeconds(IDbConnection connection)
|
||||
{
|
||||
string dataSource = SafeGetDataSourceFromConnection(connection);
|
||||
return GetQueryTimeoutSeconds(dataSource);
|
||||
}
|
||||
|
||||
public static int GetQueryTimeoutSeconds(string dataSource)
|
||||
{
|
||||
//keep existing behavior and return the default ambient settings
|
||||
//if the provided data source is null or whitespace, or the original
|
||||
//setting is already 0 which means no limit.
|
||||
int originalValue = AmbientSettings.QueryTimeoutSeconds;
|
||||
if (string.IsNullOrWhiteSpace(dataSource)
|
||||
|| (originalValue == 0))
|
||||
{
|
||||
return originalValue;
|
||||
}
|
||||
|
||||
CachedInfo info;
|
||||
bool hasFound = _cache.TryGetValue(dataSource, out info);
|
||||
|
||||
if (hasFound && info.IsAzure
|
||||
&& originalValue < MinimalQueryTimeoutSecondsForAzure)
|
||||
{
|
||||
return MinimalQueryTimeoutSecondsForAzure;
|
||||
}
|
||||
else
|
||||
{
|
||||
return originalValue;
|
||||
}
|
||||
}
|
||||
|
||||
public static void AddOrUpdateIsAzure(IDbConnection connection, bool isAzure)
|
||||
{
|
||||
Validate.IsNotNull(nameof(connection), connection);
|
||||
|
||||
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(connection.ConnectionString);
|
||||
AddOrUpdateIsAzure(builder.DataSource, isAzure);
|
||||
}
|
||||
|
||||
public static void AddOrUpdateIsAzure(string dataSource, bool isAzure)
|
||||
{
|
||||
Validate.IsNotNullOrWhitespaceString(nameof(dataSource), dataSource);
|
||||
CachedInfo info;
|
||||
bool hasFound = _cache.TryGetValue(dataSource, out info);
|
||||
|
||||
if (hasFound && info.IsAzure == isAzure)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
lock (_cacheLock)
|
||||
{
|
||||
if (! _cache.ContainsKey(dataSource))
|
||||
{
|
||||
//delete a batch of old elements when we try to add a new one and
|
||||
//the capacity limitation is hit
|
||||
if (_cache.Keys.Count > _maxCacheSize - 1)
|
||||
{
|
||||
var keysToDelete = _cache
|
||||
.OrderBy(x => x.Value.LastUpdate)
|
||||
.Take(_deleteBatchSize)
|
||||
.Select(pair => pair.Key);
|
||||
|
||||
foreach (string key in keysToDelete)
|
||||
{
|
||||
_cache.TryRemove(key, out info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
info.IsAzure = isAzure;
|
||||
info.LastUpdate = DateTime.UtcNow;
|
||||
_cache.AddOrUpdate(dataSource, info, (key, oldValue) => info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string SafeGetDataSourceFromConnection(IDbConnection connection)
|
||||
{
|
||||
if (connection == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(connection.ConnectionString);
|
||||
return builder.DataSource;
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger.Write(LogLevel.Error, String.Format(Resources.FailedToParseConnectionString, connection.ConnectionString));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains common constants used throughout ReliableConnection code.
|
||||
/// </summary>
|
||||
internal static class Constants
|
||||
{
|
||||
internal const int UndefinedErrorCode = 0;
|
||||
|
||||
internal const string Local = "(local)";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
//
|
||||
// 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.Globalization;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
|
||||
{
|
||||
/// <summary>
|
||||
/// This class is used to encapsulate all the information needed by the DataSchemaErrorTaskService to create a corresponding entry in the Visual Studio Error List.
|
||||
/// A component should add this Error Object to the <see cref="ErrorManager"/> for such purpose.
|
||||
/// Errors and their children are expected to be thread-safe. Ideally, this means that
|
||||
/// the objects are just data-transfer-objects initialized during construction.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
internal class DataSchemaError
|
||||
{
|
||||
internal const string DefaultPrefix = "SQL";
|
||||
private const int MaxErrorCode = 99999;
|
||||
protected const int UndefinedErrorCode = 0;
|
||||
|
||||
public DataSchemaError() : this(string.Empty, ErrorSeverity.Unknown)
|
||||
{
|
||||
}
|
||||
|
||||
public DataSchemaError(string message, ErrorSeverity severity)
|
||||
: this(message, string.Empty, severity)
|
||||
{
|
||||
}
|
||||
|
||||
public DataSchemaError(string message, Exception innerException, ErrorSeverity severity)
|
||||
: this(message, innerException, string.Empty, 0, severity)
|
||||
{
|
||||
}
|
||||
|
||||
public DataSchemaError(string message, string document, ErrorSeverity severity)
|
||||
: this(message, document, 0, 0, DefaultPrefix, UndefinedErrorCode, severity)
|
||||
{
|
||||
}
|
||||
|
||||
public DataSchemaError(string message, string document, int errorCode, ErrorSeverity severity)
|
||||
: this(message, document, 0, 0, DefaultPrefix, errorCode, severity)
|
||||
{
|
||||
}
|
||||
|
||||
public DataSchemaError(string message, string document, int line, int column, ErrorSeverity severity)
|
||||
: this(message, document,line, column, DefaultPrefix, UndefinedErrorCode, severity)
|
||||
{
|
||||
}
|
||||
|
||||
public DataSchemaError(DataSchemaError source, ErrorSeverity severity)
|
||||
: this(source.Message, source.Document, source.Line, source.Column, source.Prefix, source.ErrorCode, severity)
|
||||
{
|
||||
}
|
||||
|
||||
public DataSchemaError(
|
||||
Exception exception,
|
||||
string prefix,
|
||||
int errorCode,
|
||||
ErrorSeverity severity)
|
||||
: this(exception, string.Empty, 0, 0, prefix, errorCode, severity)
|
||||
{
|
||||
}
|
||||
|
||||
public DataSchemaError(
|
||||
string message,
|
||||
Exception exception,
|
||||
string prefix,
|
||||
int errorCode,
|
||||
ErrorSeverity severity)
|
||||
: this(message, exception, string.Empty, 0, 0, prefix, errorCode, severity)
|
||||
{
|
||||
}
|
||||
|
||||
public DataSchemaError(
|
||||
Exception exception,
|
||||
string document,
|
||||
int line,
|
||||
int column,
|
||||
string prefix,
|
||||
int errorCode,
|
||||
ErrorSeverity severity)
|
||||
: this(exception.Message, exception, document, line, column, prefix, errorCode, severity)
|
||||
{
|
||||
}
|
||||
|
||||
public DataSchemaError(
|
||||
string message,
|
||||
string document,
|
||||
int line,
|
||||
int column,
|
||||
string prefix,
|
||||
int errorCode,
|
||||
ErrorSeverity severity)
|
||||
: this(message, null, document, line, column, prefix, errorCode, severity)
|
||||
{
|
||||
}
|
||||
|
||||
public DataSchemaError(
|
||||
string message,
|
||||
Exception exception,
|
||||
string document,
|
||||
int line,
|
||||
int column,
|
||||
string prefix,
|
||||
int errorCode,
|
||||
ErrorSeverity severity)
|
||||
{
|
||||
if (errorCode > MaxErrorCode || errorCode < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("errorCode");
|
||||
}
|
||||
|
||||
Document = document;
|
||||
Severity = severity;
|
||||
Line = line;
|
||||
Column = column;
|
||||
Message = message;
|
||||
Exception = exception;
|
||||
|
||||
ErrorCode = errorCode;
|
||||
Prefix = prefix;
|
||||
IsPriorityEditable = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The filename of the error. It corresponds to the File column on the Visual Studio Error List window.
|
||||
/// </summary>
|
||||
public string Document { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The severity of the error
|
||||
/// </summary>
|
||||
public ErrorSeverity Severity { get; private set; }
|
||||
|
||||
public int ErrorCode { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Line Number of the error
|
||||
/// </summary>
|
||||
public int Line { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Column Number of the error
|
||||
/// </summary>
|
||||
public int Column { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Prefix of the error
|
||||
/// </summary>
|
||||
public string Prefix { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// If the error has any special help topic, this property may hold the ID to the same.
|
||||
/// </summary>
|
||||
public string HelpKeyword { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Exception associated with the error, or null
|
||||
/// </summary>
|
||||
public Exception Exception { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Message
|
||||
/// </summary>
|
||||
public string Message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Should this message honor the "treat warnings as error" flag?
|
||||
/// </summary>
|
||||
public Boolean IsPriorityEditable { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Represents the error code used in MSBuild output. This is the prefix and the
|
||||
/// error code
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string BuildErrorCode
|
||||
{
|
||||
get { return FormatErrorCode(Prefix, ErrorCode); }
|
||||
}
|
||||
|
||||
internal Boolean IsBuildErrorCodeDefined
|
||||
{
|
||||
get { return (ErrorCode != UndefinedErrorCode); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// true if this error is being displayed in ErrorList. More of an Accounting Mechanism to be used internally.
|
||||
/// </summary>
|
||||
internal bool IsOnDisplay { get; set; }
|
||||
|
||||
internal static string FormatErrorCode(string prefix, int code)
|
||||
{
|
||||
return string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"{0}{1:d5}",
|
||||
prefix,
|
||||
code);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// String form of this error.
|
||||
/// NB: This is for debugging only.
|
||||
/// </summary>
|
||||
/// <returns>String form of the error.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, "{0} - {1}({2},{3}): {4}", FormatErrorCode(Prefix, ErrorCode), Document, Line, Column, Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
//
|
||||
// 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.Data;
|
||||
using System.Data.SqlClient;
|
||||
using Microsoft.SqlTools.EditorServices.Utility;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Wraps <see cref="IDbCommand"/> objects that could be a <see cref="SqlCommand"/> or
|
||||
/// a <see cref="ReliableSqlConnection.ReliableSqlCommand"/>, providing common methods across both.
|
||||
/// </summary>
|
||||
internal sealed class DbCommandWrapper
|
||||
{
|
||||
private readonly IDbCommand _command;
|
||||
private readonly bool _isReliableCommand;
|
||||
|
||||
public DbCommandWrapper(IDbCommand command)
|
||||
{
|
||||
Validate.IsNotNull(nameof(command), command);
|
||||
if (command is ReliableSqlConnection.ReliableSqlCommand)
|
||||
{
|
||||
_isReliableCommand = true;
|
||||
}
|
||||
else if (!(command is SqlCommand))
|
||||
{
|
||||
throw new InvalidOperationException(Resources.InvalidCommandType);
|
||||
}
|
||||
_command = command;
|
||||
}
|
||||
|
||||
public static bool IsSupportedCommand(IDbCommand command)
|
||||
{
|
||||
return command is ReliableSqlConnection.ReliableSqlCommand
|
||||
|| command is SqlCommand;
|
||||
}
|
||||
|
||||
|
||||
public event StatementCompletedEventHandler StatementCompleted
|
||||
{
|
||||
add
|
||||
{
|
||||
SqlCommand sqlCommand = GetAsSqlCommand();
|
||||
sqlCommand.StatementCompleted += value;
|
||||
}
|
||||
remove
|
||||
{
|
||||
SqlCommand sqlCommand = GetAsSqlCommand();
|
||||
sqlCommand.StatementCompleted -= value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets this as a SqlCommand by casting (if we know it is actually a SqlCommand)
|
||||
/// or by getting the underlying command (if it's a ReliableSqlCommand)
|
||||
/// </summary>
|
||||
private SqlCommand GetAsSqlCommand()
|
||||
{
|
||||
if (_isReliableCommand)
|
||||
{
|
||||
return ((ReliableSqlConnection.ReliableSqlCommand) _command).GetUnderlyingCommand();
|
||||
}
|
||||
return (SqlCommand) _command;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
//
|
||||
// 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.Data;
|
||||
using System.Data.SqlClient;
|
||||
using Microsoft.SqlTools.EditorServices.Utility;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
|
||||
{
|
||||
/// <summary>
|
||||
/// Wraps <see cref="IDbConnection"/> objects that could be a <see cref="SqlConnection"/> or
|
||||
/// a <see cref="ReliableSqlConnection"/>, providing common methods across both.
|
||||
/// </summary>
|
||||
internal sealed class DbConnectionWrapper
|
||||
{
|
||||
private readonly IDbConnection _connection;
|
||||
private readonly bool _isReliableConnection;
|
||||
|
||||
public DbConnectionWrapper(IDbConnection connection)
|
||||
{
|
||||
Validate.IsNotNull(nameof(connection), connection);
|
||||
if (connection is ReliableSqlConnection)
|
||||
{
|
||||
_isReliableConnection = true;
|
||||
}
|
||||
else if (!(connection is SqlConnection))
|
||||
{
|
||||
throw new InvalidOperationException(Resources.InvalidConnectionType);
|
||||
}
|
||||
|
||||
_connection = connection;
|
||||
}
|
||||
|
||||
public static bool IsSupportedConnection(IDbConnection connection)
|
||||
{
|
||||
return connection is ReliableSqlConnection
|
||||
|| connection is SqlConnection;
|
||||
}
|
||||
|
||||
public event SqlInfoMessageEventHandler InfoMessage
|
||||
{
|
||||
add
|
||||
{
|
||||
SqlConnection conn = GetAsSqlConnection();
|
||||
conn.InfoMessage += value;
|
||||
}
|
||||
remove
|
||||
{
|
||||
SqlConnection conn = GetAsSqlConnection();
|
||||
conn.InfoMessage -= value;
|
||||
}
|
||||
}
|
||||
|
||||
public string DataSource
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_isReliableConnection)
|
||||
{
|
||||
return ((ReliableSqlConnection) _connection).DataSource;
|
||||
}
|
||||
return ((SqlConnection)_connection).DataSource;
|
||||
}
|
||||
}
|
||||
|
||||
public string ServerVersion
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_isReliableConnection)
|
||||
{
|
||||
return ((ReliableSqlConnection)_connection).ServerVersion;
|
||||
}
|
||||
return ((SqlConnection)_connection).ServerVersion;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets this as a SqlConnection by casting (if we know it is actually a SqlConnection)
|
||||
/// or by getting the underlying connection (if it's a ReliableSqlConnection)
|
||||
/// </summary>
|
||||
public SqlConnection GetAsSqlConnection()
|
||||
{
|
||||
if (_isReliableConnection)
|
||||
{
|
||||
return ((ReliableSqlConnection) _connection).GetUnderlyingConnection();
|
||||
}
|
||||
return (SqlConnection) _connection;
|
||||
}
|
||||
|
||||
/*
|
||||
TODO - IClonable does not exist in .NET Core.
|
||||
/// <summary>
|
||||
/// Clones the connection and ensures it's opened.
|
||||
/// If it's a SqlConnection it will clone it,
|
||||
/// and for ReliableSqlConnection it will clone the underling connection.
|
||||
/// The reason the entire ReliableSqlConnection is not cloned is that it includes
|
||||
/// several callbacks and we don't want to try and handle deciding how to clone these
|
||||
/// yet.
|
||||
/// </summary>
|
||||
public SqlConnection CloneAndOpenConnection()
|
||||
{
|
||||
SqlConnection conn = GetAsSqlConnection();
|
||||
SqlConnection clonedConn = ((ICloneable) conn).Clone() as SqlConnection;
|
||||
clonedConn.Open();
|
||||
return clonedConn;
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
|
||||
{
|
||||
internal enum ErrorSeverity
|
||||
{
|
||||
Unknown = 0,
|
||||
Error,
|
||||
Warning,
|
||||
Message
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
|
||||
{
|
||||
/// <summary>
|
||||
/// This interface controls the lifetime of settings created as part of the
|
||||
/// top-of-stack API. Changes made to this context's AmbientData instance will
|
||||
/// flow to lower in the stack while this object is not disposed.
|
||||
/// </summary>
|
||||
internal interface IStackSettingsContext : IDisposable
|
||||
{
|
||||
AmbientSettings.AmbientData Settings { get; }
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,247 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
// This code is copied from the source described in the comment below.
|
||||
|
||||
// =======================================================================================
|
||||
// Microsoft Windows Server AppFabric Customer Advisory Team (CAT) Best Practices Series
|
||||
//
|
||||
// This sample is supplemental to the technical guidance published on the community
|
||||
// blog at http://blogs.msdn.com/appfabriccat/ and copied from
|
||||
// sqlmain ./sql/manageability/mfx/common/
|
||||
//
|
||||
// =======================================================================================
|
||||
// Copyright © 2012 Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
|
||||
// EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. YOU BEAR THE RISK OF USING IT.
|
||||
// =======================================================================================
|
||||
|
||||
// namespace Microsoft.AppFabricCAT.Samples.Azure.TransientFaultHandling.SqlAzure
|
||||
// namespace Microsoft.SqlServer.Management.Common
|
||||
|
||||
using System;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.Data.SqlClient;
|
||||
using System.Diagnostics.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a reliable way of opening connections to and executing commands
|
||||
/// taking into account potential network unreliability and a requirement for connection retry.
|
||||
/// </summary>
|
||||
internal sealed partial class ReliableSqlConnection
|
||||
{
|
||||
internal class ReliableSqlCommand : DbCommand
|
||||
{
|
||||
private const int Dummy = 0;
|
||||
private readonly SqlCommand _command;
|
||||
|
||||
// connection is settable
|
||||
private ReliableSqlConnection _connection;
|
||||
|
||||
public ReliableSqlCommand()
|
||||
: this(null, Dummy)
|
||||
{
|
||||
}
|
||||
|
||||
public ReliableSqlCommand(ReliableSqlConnection connection)
|
||||
: this(connection, Dummy)
|
||||
{
|
||||
Contract.Requires(connection != null);
|
||||
}
|
||||
|
||||
private ReliableSqlCommand(ReliableSqlConnection connection, int dummy)
|
||||
{
|
||||
if (connection != null)
|
||||
{
|
||||
_connection = connection;
|
||||
_command = connection.CreateSqlCommand();
|
||||
}
|
||||
else
|
||||
{
|
||||
_command = new SqlCommand();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_command.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the text command to run against the data source.
|
||||
/// </summary>
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2100:Review SQL queries for security vulnerabilities")]
|
||||
public override string CommandText
|
||||
{
|
||||
get { return _command.CommandText; }
|
||||
set { _command.CommandText = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the wait time before terminating the attempt to execute a command and generating an error.
|
||||
/// </summary>
|
||||
public override int CommandTimeout
|
||||
{
|
||||
get { return _command.CommandTimeout; }
|
||||
set { _command.CommandTimeout = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value that specifies how the <see cref="System.Data.Common.DbCommand.CommandText"/> property is interpreted.
|
||||
/// </summary>
|
||||
public override CommandType CommandType
|
||||
{
|
||||
get { return _command.CommandType; }
|
||||
set { _command.CommandType = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="System.Data.Common.DbConnection"/> used by this <see cref="System.Data.Common.DbCommand"/>.
|
||||
/// </summary>
|
||||
protected override DbConnection DbConnection
|
||||
{
|
||||
get
|
||||
{
|
||||
return _connection;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException("value");
|
||||
}
|
||||
|
||||
ReliableSqlConnection newConnection = value as ReliableSqlConnection;
|
||||
|
||||
if (newConnection == null)
|
||||
{
|
||||
throw new InvalidOperationException(Resources.OnlyReliableConnectionSupported);
|
||||
}
|
||||
|
||||
_connection = newConnection;
|
||||
_command.Connection = _connection._underlyingConnection;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="System.Data.IDataParameterCollection"/>.
|
||||
/// </summary>
|
||||
protected override DbParameterCollection DbParameterCollection
|
||||
{
|
||||
get { return _command.Parameters; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the transaction within which the Command object of a .NET Framework data provider executes.
|
||||
/// </summary>
|
||||
protected override DbTransaction DbTransaction
|
||||
{
|
||||
get { return _command.Transaction; }
|
||||
set { _command.Transaction = value as SqlTransaction; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the command object should be visible in a customized interface control.
|
||||
/// </summary>
|
||||
public override bool DesignTimeVisible
|
||||
{
|
||||
get { return _command.DesignTimeVisible; }
|
||||
set { _command.DesignTimeVisible = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets how command results are applied to the System.Data.DataRow when
|
||||
/// used by the System.Data.IDataAdapter.Update(System.Data.DataSet) method of
|
||||
/// a <see cref="System.Data.Common.DbDataAdapter"/>.
|
||||
/// </summary>
|
||||
public override UpdateRowSource UpdatedRowSource
|
||||
{
|
||||
get { return _command.UpdatedRowSource; }
|
||||
set { _command.UpdatedRowSource = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to cancels the execution of an <see cref="System.Data.IDbCommand"/>.
|
||||
/// </summary>
|
||||
public override void Cancel()
|
||||
{
|
||||
_command.Cancel();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of an <see cref="System.Data.IDbDataParameter"/> object.
|
||||
/// </summary>
|
||||
/// <returns>An <see cref="IDbDataParameter"/> object.</returns>
|
||||
protected override DbParameter CreateDbParameter()
|
||||
{
|
||||
return _command.CreateParameter();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes an SQL statement against the Connection object of a .NET Framework
|
||||
/// data provider, and returns the number of rows affected.
|
||||
/// </summary>
|
||||
/// <returns>The number of rows affected.</returns>
|
||||
public override int ExecuteNonQuery()
|
||||
{
|
||||
ValidateConnectionIsSet();
|
||||
return _connection.ExecuteNonQuery(_command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the <see cref="System.Data.IDbCommand.CommandText"/> against the <see cref="System.Data.IDbCommand.Connection"/>
|
||||
/// and builds an <see cref="System.Data.IDataReader"/> using one of the <see cref="System.Data.CommandBehavior"/> values.
|
||||
/// </summary>
|
||||
/// <param name="behavior">One of the <see cref="System.Data.CommandBehavior"/> values.</param>
|
||||
/// <returns>An <see cref="System.Data.IDataReader"/> object.</returns>
|
||||
protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior)
|
||||
{
|
||||
ValidateConnectionIsSet();
|
||||
return (DbDataReader)_connection.ExecuteReader(_command, behavior);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the query, and returns the first column of the first row in the
|
||||
/// resultset returned by the query. Extra columns or rows are ignored.
|
||||
/// </summary>
|
||||
/// <returns>The first column of the first row in the resultset.</returns>
|
||||
public override object ExecuteScalar()
|
||||
{
|
||||
ValidateConnectionIsSet();
|
||||
return _connection.ExecuteScalar(_command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a prepared (or compiled) version of the command on the data source.
|
||||
/// </summary>
|
||||
public override void Prepare()
|
||||
{
|
||||
_command.Prepare();
|
||||
}
|
||||
|
||||
internal SqlCommand GetUnderlyingCommand()
|
||||
{
|
||||
return _command;
|
||||
}
|
||||
|
||||
private void ValidateConnectionIsSet()
|
||||
{
|
||||
if (_connection == null)
|
||||
{
|
||||
throw new InvalidOperationException(Resources.ConnectionPropertyNotSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,548 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
// This code is copied from the source described in the comment below.
|
||||
|
||||
// =======================================================================================
|
||||
// Microsoft Windows Server AppFabric Customer Advisory Team (CAT) Best Practices Series
|
||||
//
|
||||
// This sample is supplemental to the technical guidance published on the community
|
||||
// blog at http://blogs.msdn.com/appfabriccat/ and copied from
|
||||
// sqlmain ./sql/manageability/mfx/common/
|
||||
//
|
||||
// =======================================================================================
|
||||
// Copyright © 2012 Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
|
||||
// EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. YOU BEAR THE RISK OF USING IT.
|
||||
// =======================================================================================
|
||||
|
||||
// namespace Microsoft.AppFabricCAT.Samples.Azure.TransientFaultHandling.SqlAzure
|
||||
// namespace Microsoft.SqlServer.Management.Common
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.Data.SqlClient;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using Microsoft.SqlTools.EditorServices.Utility;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a reliable way of opening connections to and executing commands
|
||||
/// taking into account potential network unreliability and a requirement for connection retry.
|
||||
/// </summary>
|
||||
internal sealed partial class ReliableSqlConnection : DbConnection, IDisposable
|
||||
{
|
||||
private const string QueryAzureSessionId = "SELECT CONVERT(NVARCHAR(36), CONTEXT_INFO())";
|
||||
|
||||
private readonly SqlConnection _underlyingConnection;
|
||||
private readonly RetryPolicy _connectionRetryPolicy;
|
||||
private RetryPolicy _commandRetryPolicy;
|
||||
private Guid _azureSessionId;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the ReliableSqlConnection class with a given connection string
|
||||
/// and a policy defining whether to retry a request if the connection fails to be opened or a command
|
||||
/// fails to be successfully executed.
|
||||
/// </summary>
|
||||
/// <param name="connectionString">The connection string used to open the SQL Azure database.</param>
|
||||
/// <param name="connectionRetryPolicy">The retry policy defining whether to retry a request if a connection fails to be established.</param>
|
||||
/// <param name="commandRetryPolicy">The retry policy defining whether to retry a request if a command fails to be executed.</param>
|
||||
public ReliableSqlConnection(string connectionString, RetryPolicy connectionRetryPolicy, RetryPolicy commandRetryPolicy)
|
||||
{
|
||||
_underlyingConnection = new SqlConnection(connectionString);
|
||||
_connectionRetryPolicy = connectionRetryPolicy ?? RetryPolicyFactory.CreateNoRetryPolicy();
|
||||
_commandRetryPolicy = commandRetryPolicy ?? RetryPolicyFactory.CreateNoRetryPolicy();
|
||||
|
||||
_underlyingConnection.StateChange += OnConnectionStateChange;
|
||||
_connectionRetryPolicy.RetryOccurred += RetryConnectionCallback;
|
||||
_commandRetryPolicy.RetryOccurred += RetryCommandCallback;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs application-defined tasks associated with freeing, releasing, or
|
||||
/// resetting managed and unmanaged resources.
|
||||
/// </summary>
|
||||
/// <param name="disposing">A flag indicating that managed resources must be released.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
if (_connectionRetryPolicy != null)
|
||||
{
|
||||
_connectionRetryPolicy.RetryOccurred -= RetryConnectionCallback;
|
||||
}
|
||||
|
||||
if (_commandRetryPolicy != null)
|
||||
{
|
||||
_commandRetryPolicy.RetryOccurred -= RetryCommandCallback;
|
||||
}
|
||||
|
||||
_underlyingConnection.StateChange -= OnConnectionStateChange;
|
||||
if (_underlyingConnection.State == ConnectionState.Open)
|
||||
{
|
||||
_underlyingConnection.Close();
|
||||
}
|
||||
|
||||
_underlyingConnection.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2100:Review SQL queries for security vulnerabilities")]
|
||||
internal static void SetLockAndCommandTimeout(IDbConnection conn)
|
||||
{
|
||||
Validate.IsNotNull(nameof(conn), conn);
|
||||
|
||||
// Make sure we use the underlying connection as ReliableConnection.Open also calls
|
||||
// this method
|
||||
ReliableSqlConnection reliableConn = conn as ReliableSqlConnection;
|
||||
if (reliableConn != null)
|
||||
{
|
||||
conn = reliableConn._underlyingConnection;
|
||||
}
|
||||
|
||||
const string setLockTimeout = @"set LOCK_TIMEOUT {0}";
|
||||
|
||||
using (IDbCommand cmd = conn.CreateCommand())
|
||||
{
|
||||
cmd.CommandText = string.Format(CultureInfo.InvariantCulture, setLockTimeout, AmbientSettings.LockTimeoutMilliSeconds);
|
||||
cmd.CommandType = CommandType.Text;
|
||||
cmd.CommandTimeout = CachedServerInfo.GetQueryTimeoutSeconds(conn);
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
|
||||
internal static void SetDefaultAnsiSettings(IDbConnection conn)
|
||||
{
|
||||
Validate.IsNotNull(nameof(conn), conn);
|
||||
|
||||
// Make sure we use the underlying connection as ReliableConnection.Open also calls
|
||||
// this method
|
||||
ReliableSqlConnection reliableConn = conn as ReliableSqlConnection;
|
||||
if (reliableConn != null)
|
||||
{
|
||||
conn = reliableConn._underlyingConnection;
|
||||
}
|
||||
|
||||
// Configure the connection with proper ANSI settings and lock timeout
|
||||
using (IDbCommand cmd = conn.CreateCommand())
|
||||
{
|
||||
cmd.CommandTimeout = CachedServerInfo.GetQueryTimeoutSeconds(conn);
|
||||
cmd.CommandText = @"SET ANSI_NULLS, ANSI_PADDING, ANSI_WARNINGS, ARITHABORT, CONCAT_NULL_YIELDS_NULL, QUOTED_IDENTIFIER ON;
|
||||
SET NUMERIC_ROUNDABORT OFF;";
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the connection string for opening a connection to the SQL Azure database.
|
||||
/// </summary>
|
||||
public override string ConnectionString
|
||||
{
|
||||
get { return _underlyingConnection.ConnectionString; }
|
||||
set { _underlyingConnection.ConnectionString = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the policy which decides whether to retry a connection request, based on how many
|
||||
/// times the request has been made and the reason for the last failure.
|
||||
/// </summary>
|
||||
public RetryPolicy ConnectionRetryPolicy
|
||||
{
|
||||
get { return _connectionRetryPolicy; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the policy which decides whether to retry a command, based on how many
|
||||
/// times the request has been made and the reason for the last failure.
|
||||
/// </summary>
|
||||
public RetryPolicy CommandRetryPolicy
|
||||
{
|
||||
get { return _commandRetryPolicy; }
|
||||
set
|
||||
{
|
||||
Validate.IsNotNull(nameof(value), value);
|
||||
|
||||
if (_commandRetryPolicy != null)
|
||||
{
|
||||
_commandRetryPolicy.RetryOccurred -= RetryCommandCallback;
|
||||
}
|
||||
|
||||
_commandRetryPolicy = value;
|
||||
_commandRetryPolicy.RetryOccurred += RetryCommandCallback;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the server name from the underlying connection.
|
||||
/// </summary>
|
||||
public override string DataSource
|
||||
{
|
||||
get { return _underlyingConnection.DataSource; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the server version from the underlying connection.
|
||||
/// </summary>
|
||||
public override string ServerVersion
|
||||
{
|
||||
get { return _underlyingConnection.ServerVersion; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If the underlying SqlConnection absolutely has to be accessed, for instance
|
||||
/// to pass to external APIs that require this type of connection, then this
|
||||
/// can be used.
|
||||
/// </summary>
|
||||
/// <returns><see cref="SqlConnection"/></returns>
|
||||
public SqlConnection GetUnderlyingConnection()
|
||||
{
|
||||
return _underlyingConnection;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Begins a database transaction with the specified System.Data.IsolationLevel value.
|
||||
/// </summary>
|
||||
/// <param name="level">One of the System.Data.IsolationLevel values.</param>
|
||||
/// <returns>An object representing the new transaction.</returns>
|
||||
protected override DbTransaction BeginDbTransaction(IsolationLevel level)
|
||||
{
|
||||
return _underlyingConnection.BeginTransaction(level);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the current database for an open Connection object.
|
||||
/// </summary>
|
||||
/// <param name="databaseName">The name of the database to use in place of the current database.</param>
|
||||
public override void ChangeDatabase(string databaseName)
|
||||
{
|
||||
_underlyingConnection.ChangeDatabase(databaseName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens a database connection with the settings specified by the ConnectionString
|
||||
/// property of the provider-specific Connection object.
|
||||
/// </summary>
|
||||
public override void Open()
|
||||
{
|
||||
OpenConnection();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes the connection to the database.
|
||||
/// </summary>
|
||||
public override void Close()
|
||||
{
|
||||
_underlyingConnection.Close();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the time to wait while trying to establish a connection before terminating
|
||||
/// the attempt and generating an error.
|
||||
/// </summary>
|
||||
public override int ConnectionTimeout
|
||||
{
|
||||
get { return _underlyingConnection.ConnectionTimeout; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates and returns an object implementing the IDbCommand interface which is associated
|
||||
/// with the underlying SqlConnection.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="IDbCommand"/> object.</returns>
|
||||
protected override DbCommand CreateDbCommand()
|
||||
{
|
||||
return CreateReliableCommand();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates and returns an object implementing the IDbCommand interface which is associated
|
||||
/// with the underlying SqlConnection.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="SqlCommand"/> object.</returns>
|
||||
public SqlCommand CreateSqlCommand()
|
||||
{
|
||||
return _underlyingConnection.CreateCommand();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the current database or the database to be used after a
|
||||
/// connection is opened.
|
||||
/// </summary>
|
||||
public override string Database
|
||||
{
|
||||
get { return _underlyingConnection.Database; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current state of the connection.
|
||||
/// </summary>
|
||||
public override ConnectionState State
|
||||
{
|
||||
get { return _underlyingConnection.State; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an info message event listener.
|
||||
/// </summary>
|
||||
/// <param name="handler">An info message event listener.</param>
|
||||
public void AddInfoMessageHandler(SqlInfoMessageEventHandler handler)
|
||||
{
|
||||
_underlyingConnection.InfoMessage += handler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes an info message event listener.
|
||||
/// </summary>
|
||||
/// <param name="handler">An info message event listener.</param>
|
||||
public void RemoveInfoMessageHandler(SqlInfoMessageEventHandler handler)
|
||||
{
|
||||
_underlyingConnection.InfoMessage -= handler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears underlying connection pool.
|
||||
/// </summary>
|
||||
public void ClearPool()
|
||||
{
|
||||
if (_underlyingConnection != null)
|
||||
{
|
||||
SqlConnection.ClearPool(_underlyingConnection);
|
||||
}
|
||||
}
|
||||
|
||||
private void RetryCommandCallback(RetryState retryState)
|
||||
{
|
||||
RetryPolicyUtils.RaiseSchemaAmbientRetryMessage(retryState, SqlSchemaModelErrorCodes.ServiceActions.CommandRetry, _azureSessionId);
|
||||
}
|
||||
|
||||
private void RetryConnectionCallback(RetryState retryState)
|
||||
{
|
||||
RetryPolicyUtils.RaiseSchemaAmbientRetryMessage(retryState, SqlSchemaModelErrorCodes.ServiceActions.ConnectionRetry, _azureSessionId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens a database connection with the settings specified by the ConnectionString and ConnectionRetryPolicy properties.
|
||||
/// </summary>
|
||||
/// <returns>An object representing the open connection.</returns>
|
||||
private SqlConnection OpenConnection()
|
||||
{
|
||||
// Check if retry policy was specified, if not, disable retries by executing the Open method using RetryPolicy.NoRetry.
|
||||
_connectionRetryPolicy.ExecuteAction(() =>
|
||||
{
|
||||
if (_underlyingConnection.State != ConnectionState.Open)
|
||||
{
|
||||
_underlyingConnection.Open();
|
||||
}
|
||||
SetLockAndCommandTimeout(_underlyingConnection);
|
||||
SetDefaultAnsiSettings(_underlyingConnection);
|
||||
});
|
||||
|
||||
return _underlyingConnection;
|
||||
}
|
||||
|
||||
public void OnConnectionStateChange(object sender, StateChangeEventArgs e)
|
||||
{
|
||||
SqlConnection conn = (SqlConnection)sender;
|
||||
switch (e.CurrentState)
|
||||
{
|
||||
case ConnectionState.Open:
|
||||
RetreiveSessionId();
|
||||
break;
|
||||
case ConnectionState.Broken:
|
||||
case ConnectionState.Closed:
|
||||
_azureSessionId = Guid.Empty;
|
||||
break;
|
||||
case ConnectionState.Connecting:
|
||||
case ConnectionState.Executing:
|
||||
case ConnectionState.Fetching:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void RetreiveSessionId()
|
||||
{
|
||||
try
|
||||
{
|
||||
using (IDbCommand command = CreateReliableCommand())
|
||||
{
|
||||
command.CommandText = QueryAzureSessionId;
|
||||
object result = command.ExecuteScalar();
|
||||
|
||||
// Only returns a session id for SQL Azure
|
||||
if (DBNull.Value != result)
|
||||
{
|
||||
string sessionId = (string)command.ExecuteScalar();
|
||||
_azureSessionId = new Guid(sessionId);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (SqlException exception)
|
||||
{
|
||||
Logger.Write(LogLevel.Error, Resources.UnableToRetrieveAzureSessionId + exception.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates and returns a ReliableSqlCommand object associated
|
||||
/// with the underlying SqlConnection.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="ReliableSqlCommand"/> object.</returns>
|
||||
private ReliableSqlCommand CreateReliableCommand()
|
||||
{
|
||||
return new ReliableSqlCommand(this);
|
||||
}
|
||||
|
||||
private void VerifyConnectionOpen(IDbCommand command)
|
||||
{
|
||||
// Verify whether or not the connection is valid and is open. This code may be retried therefore
|
||||
// it is important to ensure that a connection is re-established should it have previously failed.
|
||||
if (command.Connection == null)
|
||||
{
|
||||
command.Connection = this;
|
||||
}
|
||||
|
||||
if (command.Connection.State != ConnectionState.Open)
|
||||
{
|
||||
SqlConnection.ClearPool(_underlyingConnection);
|
||||
|
||||
command.Connection.Open();
|
||||
}
|
||||
}
|
||||
|
||||
private IDataReader ExecuteReader(IDbCommand command, CommandBehavior behavior)
|
||||
{
|
||||
Tuple<string, bool>[] sessionSettings = null;
|
||||
return _commandRetryPolicy.ExecuteAction<IDataReader>(() =>
|
||||
{
|
||||
VerifyConnectionOpen(command);
|
||||
sessionSettings = CacheOrReplaySessionSettings(command, sessionSettings);
|
||||
|
||||
return command.ExecuteReader(behavior);
|
||||
});
|
||||
}
|
||||
|
||||
// Because retry loses session settings, cache session settings or reply if the settings are already cached.
|
||||
internal Tuple<string, bool>[] CacheOrReplaySessionSettings(IDbCommand originalCommand, Tuple<string, bool>[] sessionSettings)
|
||||
{
|
||||
if (sessionSettings == null)
|
||||
{
|
||||
sessionSettings = QuerySessionSettings(originalCommand);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetSessionSettings(originalCommand.Connection, sessionSettings);
|
||||
}
|
||||
|
||||
return sessionSettings;
|
||||
}
|
||||
|
||||
private object ExecuteScalar(IDbCommand command)
|
||||
{
|
||||
Tuple<string,bool>[] sessionSettings = null;
|
||||
return _commandRetryPolicy.ExecuteAction(() =>
|
||||
{
|
||||
VerifyConnectionOpen(command);
|
||||
sessionSettings = CacheOrReplaySessionSettings(command, sessionSettings);
|
||||
|
||||
return command.ExecuteScalar();
|
||||
});
|
||||
}
|
||||
|
||||
private Tuple<string, bool>[] QuerySessionSettings(IDbCommand originalCommand)
|
||||
{
|
||||
Tuple<string,bool>[] sessionSettings = new Tuple<string,bool>[2];
|
||||
|
||||
IDbConnection connection = originalCommand.Connection;
|
||||
using (IDbCommand localCommand = connection.CreateCommand())
|
||||
{
|
||||
// Executing a reader requires preservation of any pending transaction created by the calling command
|
||||
localCommand.Transaction = originalCommand.Transaction;
|
||||
localCommand.CommandText = "SELECT ISNULL(SESSIONPROPERTY ('ANSI_NULLS'), 0), ISNULL(SESSIONPROPERTY ('QUOTED_IDENTIFIER'), 1)";
|
||||
using (IDataReader reader = localCommand.ExecuteReader())
|
||||
{
|
||||
if (reader.Read())
|
||||
{
|
||||
sessionSettings[0] = Tuple.Create("ANSI_NULLS", ((int)reader[0] == 1));
|
||||
sessionSettings[1] = Tuple.Create("QUOTED_IDENTIFIER", ((int)reader[1] ==1));
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Assert(false, "Reader cannot be empty");
|
||||
}
|
||||
}
|
||||
return sessionSettings;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetSessionSettings(IDbConnection connection, params Tuple<string, bool>[] settings)
|
||||
{
|
||||
List<string> setONOptions = new List<string>();
|
||||
List<string> setOFFOptions = new List<string>();
|
||||
if(settings != null)
|
||||
{
|
||||
foreach (Tuple<string, bool> setting in settings)
|
||||
{
|
||||
if (setting.Item2)
|
||||
{
|
||||
setONOptions.Add(setting.Item1);
|
||||
}
|
||||
else
|
||||
{
|
||||
setOFFOptions.Add(setting.Item1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SetSessionSettings(connection, setONOptions, "ON");
|
||||
SetSessionSettings(connection, setOFFOptions, "OFF");
|
||||
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2100:Review SQL queries for security vulnerabilities")]
|
||||
private static void SetSessionSettings(IDbConnection connection, List<string> sessionOptions, string onOff)
|
||||
{
|
||||
if (sessionOptions.Count > 0)
|
||||
{
|
||||
using (IDbCommand localCommand = connection.CreateCommand())
|
||||
{
|
||||
StringBuilder builder = new StringBuilder("SET ");
|
||||
for (int i = 0; i < sessionOptions.Count; i++)
|
||||
{
|
||||
if (i > 0)
|
||||
{
|
||||
builder.Append(',');
|
||||
}
|
||||
builder.Append(sessionOptions[i]);
|
||||
}
|
||||
builder.Append(" ");
|
||||
builder.Append(onOff);
|
||||
localCommand.CommandText = builder.ToString();
|
||||
localCommand.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int ExecuteNonQuery(IDbCommand command)
|
||||
{
|
||||
Tuple<string, bool>[] sessionSettings = null;
|
||||
return _commandRetryPolicy.ExecuteAction<int>(() =>
|
||||
{
|
||||
VerifyConnectionOpen(command);
|
||||
sessionSettings = CacheOrReplaySessionSettings(command, sessionSettings);
|
||||
|
||||
return command.ExecuteNonQuery();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains string resources used throughout ReliableConnection code.
|
||||
/// </summary>
|
||||
internal static class Resources
|
||||
{
|
||||
internal static string AmbientSettingFormat
|
||||
{
|
||||
get
|
||||
{
|
||||
return "{0}: {1}";
|
||||
}
|
||||
}
|
||||
|
||||
internal static string ConnectionPassedToIsCloudShouldBeOpen
|
||||
{
|
||||
get
|
||||
{
|
||||
return "connection passed to IsCloud should be open.";
|
||||
}
|
||||
}
|
||||
|
||||
internal static string ConnectionPropertyNotSet
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Connection property has not been initialized.";
|
||||
}
|
||||
}
|
||||
|
||||
internal static string ExceptionCannotBeRetried
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Exception cannot be retried because of err #{0}:{1}";
|
||||
}
|
||||
}
|
||||
|
||||
internal static string ErrorParsingConnectionString
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Error parsing connection string {0}";
|
||||
}
|
||||
}
|
||||
|
||||
internal static string FailedToCacheIsCloud
|
||||
{
|
||||
get
|
||||
{
|
||||
return "failed to cache the server property of IsAzure";
|
||||
}
|
||||
}
|
||||
|
||||
internal static string FailedToParseConnectionString
|
||||
{
|
||||
get
|
||||
{
|
||||
return "failed to parse the provided connection string: {0}";
|
||||
}
|
||||
}
|
||||
|
||||
internal static string IgnoreOnException
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Retry number {0}. Ignoring Exception: {1}";
|
||||
}
|
||||
}
|
||||
|
||||
internal static string InvalidCommandType
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Unsupported command object. Use SqlCommand or ReliableSqlCommand.";
|
||||
}
|
||||
}
|
||||
|
||||
internal static string InvalidConnectionType
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Unsupported connection object. Use SqlConnection or ReliableSqlConnection.";
|
||||
}
|
||||
}
|
||||
|
||||
internal static string LoggingAmbientSettings
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Logging Ambient Settings...";
|
||||
}
|
||||
}
|
||||
|
||||
internal static string Mode
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Mode";
|
||||
}
|
||||
}
|
||||
|
||||
internal static string OnlyReliableConnectionSupported
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Connection property can only be set to a value of type ReliableSqlConnection.";
|
||||
}
|
||||
}
|
||||
|
||||
internal static string RetryOnException
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Retry number {0}. Delaying {1} ms before next retry. Exception: {2}";
|
||||
}
|
||||
}
|
||||
|
||||
internal static string ThrottlingTypeInfo
|
||||
{
|
||||
get
|
||||
{
|
||||
return "ThrottlingTypeInfo";
|
||||
}
|
||||
}
|
||||
|
||||
internal static string UnableToAssignValue
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Unable to assign the value of type {0} to {1}";
|
||||
}
|
||||
}
|
||||
|
||||
internal static string UnableToRetrieveAzureSessionId
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Unable to retrieve Azure session-id.";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
// This code is copied from the source described in the comment below.
|
||||
|
||||
// =======================================================================================
|
||||
// Microsoft Windows Server AppFabric Customer Advisory Team (CAT) Best Practices Series
|
||||
//
|
||||
// This sample is supplemental to the technical guidance published on the community
|
||||
// blog at http://blogs.msdn.com/appfabriccat/ and copied from
|
||||
// sqlmain ./sql/manageability/mfx/common/
|
||||
//
|
||||
// =======================================================================================
|
||||
// Copyright © 2012 Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
|
||||
// EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. YOU BEAR THE RISK OF USING IT.
|
||||
// =======================================================================================
|
||||
|
||||
// namespace Microsoft.SQL.CAT.BestPractices.SqlAzure.Framework
|
||||
// namespace Microsoft.SqlServer.Management.Common
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a arguments for event handler which will be invoked whenever a retry condition is encountered.
|
||||
/// </summary>
|
||||
internal sealed class RetryCallbackEventArgs : EventArgs
|
||||
{
|
||||
private readonly int _retryCount;
|
||||
private readonly Exception _exception;
|
||||
private readonly TimeSpan _delay;
|
||||
|
||||
public RetryCallbackEventArgs(int retryCount, Exception exception, TimeSpan delay)
|
||||
{
|
||||
_retryCount = retryCount;
|
||||
_exception = exception;
|
||||
_delay = delay;
|
||||
}
|
||||
|
||||
public TimeSpan Delay
|
||||
{
|
||||
get { return _delay; }
|
||||
}
|
||||
|
||||
public Exception Exception
|
||||
{
|
||||
get { return _exception; }
|
||||
}
|
||||
|
||||
public int RetryCount
|
||||
{
|
||||
get { return _retryCount; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
//
|
||||
|
||||
// This code is copied from the source described in the comment below.
|
||||
|
||||
// =======================================================================================
|
||||
// Microsoft Windows Server AppFabric Customer Advisory Team (CAT) Best Practices Series
|
||||
//
|
||||
// This sample is supplemental to the technical guidance published on the community
|
||||
// blog at http://blogs.msdn.com/appfabriccat/ and copied from
|
||||
// sqlmain ./sql/manageability/mfx/common/
|
||||
//
|
||||
// =======================================================================================
|
||||
// Copyright © 2012 Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
|
||||
// EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. YOU BEAR THE RISK OF USING IT.
|
||||
// =======================================================================================
|
||||
|
||||
// namespace Microsoft.SQL.CAT.BestPractices.SqlAzure.Framework
|
||||
// namespace Microsoft.SqlServer.Management.Common
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
|
||||
{
|
||||
/// <summary>
|
||||
/// The special type of exception that provides managed exit from a retry loop. The user code can use this
|
||||
/// exception to notify the retry policy that no further retry attempts are required.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
internal sealed class RetryLimitExceededException : Exception
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Data.SqlClient;
|
||||
using Microsoft.SqlTools.EditorServices.Utility;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
|
||||
{
|
||||
internal abstract partial class RetryPolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides the error detection logic for temporary faults that are commonly found during data transfer.
|
||||
/// </summary>
|
||||
internal sealed class DataTransferErrorDetectionStrategy : ErrorDetectionStrategyBase, IErrorDetectionStrategy
|
||||
{
|
||||
private static readonly DataTransferErrorDetectionStrategy instance = new DataTransferErrorDetectionStrategy();
|
||||
|
||||
public static DataTransferErrorDetectionStrategy Instance
|
||||
{
|
||||
get { return instance; }
|
||||
}
|
||||
|
||||
protected override bool CanRetrySqlException(SqlException sqlException)
|
||||
{
|
||||
// Enumerate through all errors found in the exception.
|
||||
foreach (SqlError err in sqlException.Errors)
|
||||
{
|
||||
RetryPolicyUtils.AppendThrottlingDataIfIsThrottlingError(sqlException, err);
|
||||
if (RetryPolicyUtils.IsNonRetryableDataTransferError(err.Number))
|
||||
{
|
||||
Logger.Write(LogLevel.Error, string.Format(Resources.ExceptionCannotBeRetried, err.Number, err.Message));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Default is to treat all SqlException as retriable.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
//
|
||||
// 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.Data.SqlClient;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
|
||||
{
|
||||
internal abstract partial class RetryPolicy
|
||||
{
|
||||
public interface IErrorDetectionStrategy
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines whether the specified exception represents a temporary failure that can be compensated by a retry.
|
||||
/// </summary>
|
||||
/// <param name="ex">The exception object to be verified.</param>
|
||||
/// <returns>True if the specified exception is considered as temporary, otherwise false.</returns>
|
||||
bool CanRetry(Exception ex);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified exception can be ignored.
|
||||
/// </summary>
|
||||
/// <param name="ex">The exception object to be verified.</param>
|
||||
/// <returns>True if the specified exception is considered as non-harmful.</returns>
|
||||
bool ShouldIgnoreError(Exception ex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base class with common retry logic. The core behavior for retrying non SqlExceptions is the same
|
||||
/// across retry policies
|
||||
/// </summary>
|
||||
internal abstract class ErrorDetectionStrategyBase : IErrorDetectionStrategy
|
||||
{
|
||||
public bool CanRetry(Exception ex)
|
||||
{
|
||||
if (ex != null)
|
||||
{
|
||||
SqlException sqlException;
|
||||
if ((sqlException = ex as SqlException) != null)
|
||||
{
|
||||
return CanRetrySqlException(sqlException);
|
||||
}
|
||||
if (ex is InvalidOperationException)
|
||||
{
|
||||
// Operations can throw this exception if the connection is killed before the write starts to the server
|
||||
// However if there's an inner SqlException it may be a CLR load failure or other non-transient error
|
||||
if (ex.InnerException != null
|
||||
&& ex.InnerException is SqlException)
|
||||
{
|
||||
return CanRetry(ex.InnerException);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (ex is TimeoutException)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool ShouldIgnoreError(Exception ex)
|
||||
{
|
||||
if (ex != null)
|
||||
{
|
||||
SqlException sqlException;
|
||||
if ((sqlException = ex as SqlException) != null)
|
||||
{
|
||||
return ShouldIgnoreSqlException(sqlException);
|
||||
}
|
||||
if (ex is InvalidOperationException)
|
||||
{
|
||||
// Operations can throw this exception if the connection is killed before the write starts to the server
|
||||
// However if there's an inner SqlException it may be a CLR load failure or other non-transient error
|
||||
if (ex.InnerException != null
|
||||
&& ex.InnerException is SqlException)
|
||||
{
|
||||
return ShouldIgnoreError(ex.InnerException);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected virtual bool ShouldIgnoreSqlException(SqlException sqlException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected abstract bool CanRetrySqlException(SqlException sqlException);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Data.SqlClient;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
|
||||
{
|
||||
internal abstract partial class RetryPolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides the error detection logic for temporary faults that are commonly found in SQL Azure.
|
||||
/// The same errors CAN occur on premise also, but they are not seen as often.
|
||||
/// </summary>
|
||||
internal sealed class NetworkConnectivityErrorDetectionStrategy : ErrorDetectionStrategyBase, IErrorDetectionStrategy
|
||||
{
|
||||
private static NetworkConnectivityErrorDetectionStrategy instance = new NetworkConnectivityErrorDetectionStrategy();
|
||||
|
||||
public static NetworkConnectivityErrorDetectionStrategy Instance
|
||||
{
|
||||
get { return instance; }
|
||||
}
|
||||
|
||||
protected override bool CanRetrySqlException(SqlException sqlException)
|
||||
{
|
||||
// Enumerate through all errors found in the exception.
|
||||
bool foundRetryableError = false;
|
||||
foreach (SqlError err in sqlException.Errors)
|
||||
{
|
||||
RetryPolicyUtils.AppendThrottlingDataIfIsThrottlingError(sqlException, err);
|
||||
if (!RetryPolicyUtils.IsRetryableNetworkConnectivityError(err.Number))
|
||||
{
|
||||
// If any error is not retryable then cannot retry
|
||||
return false;
|
||||
}
|
||||
foundRetryableError = true;
|
||||
}
|
||||
return foundRetryableError;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
//
|
||||
// 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.Data.SqlClient;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
|
||||
{
|
||||
internal abstract partial class RetryPolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides the error detection logic for temporary faults that are commonly found in SQL Azure.
|
||||
/// This strategy is similar to SqlAzureTemporaryErrorDetectionStrategy, but it exposes ways
|
||||
/// to accept a certain exception and treat it as passing.
|
||||
/// For example, if we are retrying, and we get a failure that an object already exists, we might
|
||||
/// want to consider this as passing since the first execution that has timed out (or failed for some other temporary error)
|
||||
/// might have managed to create the object.
|
||||
/// </summary>
|
||||
internal sealed class SqlAzureTemporaryAndIgnorableErrorDetectionStrategy : ErrorDetectionStrategyBase, IErrorDetectionStrategy
|
||||
{
|
||||
/// <summary>
|
||||
/// Azure error that can be ignored
|
||||
/// </summary>
|
||||
private readonly IList<int> ignorableAzureErrors = null;
|
||||
|
||||
public SqlAzureTemporaryAndIgnorableErrorDetectionStrategy(params int[] ignorableErrors)
|
||||
{
|
||||
this.ignorableAzureErrors = ignorableErrors;
|
||||
}
|
||||
|
||||
protected override bool CanRetrySqlException(SqlException sqlException)
|
||||
{
|
||||
// Enumerate through all errors found in the exception.
|
||||
bool foundRetryableError = false;
|
||||
foreach (SqlError err in sqlException.Errors)
|
||||
{
|
||||
RetryPolicyUtils.AppendThrottlingDataIfIsThrottlingError(sqlException, err);
|
||||
if (!RetryPolicyUtils.IsRetryableAzureError(err.Number))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foundRetryableError = true;
|
||||
}
|
||||
return foundRetryableError;
|
||||
}
|
||||
|
||||
protected override bool ShouldIgnoreSqlException(SqlException sqlException)
|
||||
{
|
||||
int errorNumber = sqlException.Number;
|
||||
|
||||
if (ignorableAzureErrors == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return ignorableAzureErrors.Contains(errorNumber);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Data.SqlClient;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
|
||||
{
|
||||
internal abstract partial class RetryPolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides the error detection logic for temporary faults that are commonly found in SQL Azure.
|
||||
/// The same errors CAN occur on premise also, but they are not seen as often.
|
||||
/// </summary>
|
||||
internal sealed class SqlAzureTemporaryErrorDetectionStrategy : ErrorDetectionStrategyBase, IErrorDetectionStrategy
|
||||
{
|
||||
private static SqlAzureTemporaryErrorDetectionStrategy instance = new SqlAzureTemporaryErrorDetectionStrategy();
|
||||
|
||||
public static SqlAzureTemporaryErrorDetectionStrategy Instance
|
||||
{
|
||||
get { return instance; }
|
||||
}
|
||||
|
||||
protected override bool CanRetrySqlException(SqlException sqlException)
|
||||
{
|
||||
// Enumerate through all errors found in the exception.
|
||||
bool foundRetryableError = false;
|
||||
foreach (SqlError err in sqlException.Errors)
|
||||
{
|
||||
RetryPolicyUtils.AppendThrottlingDataIfIsThrottlingError(sqlException, err);
|
||||
if (!RetryPolicyUtils.IsRetryableAzureError(err.Number))
|
||||
{
|
||||
// If any error is not retryable then cannot retry
|
||||
return false;
|
||||
}
|
||||
foundRetryableError = true;
|
||||
}
|
||||
return foundRetryableError;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,357 @@
|
||||
//
|
||||
// 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.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
|
||||
{
|
||||
internal abstract partial class RetryPolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements an object holding the decoded reason code returned from SQL Azure when encountering throttling conditions.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class ThrottlingReason
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the error number that corresponds to throttling conditions reported by SQL Azure.
|
||||
/// </summary>
|
||||
public const int ThrottlingErrorNumber = 40501;
|
||||
|
||||
/// <summary>
|
||||
/// Gets an unknown throttling condition in the event the actual throttling condition cannot be determined.
|
||||
/// </summary>
|
||||
public static ThrottlingReason Unknown
|
||||
{
|
||||
get
|
||||
{
|
||||
var unknownCondition = new ThrottlingReason() { ThrottlingMode = ThrottlingMode.Unknown };
|
||||
unknownCondition.throttledResources.Add(Tuple.Create(ThrottledResourceType.Unknown, ThrottlingType.Unknown));
|
||||
|
||||
return unknownCondition;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maintains a collection of key-value pairs where a key is resource type and a value is the type of throttling applied to the given resource type.
|
||||
/// </summary>
|
||||
private readonly IList<Tuple<ThrottledResourceType, ThrottlingType>> throttledResources = new List<Tuple<ThrottledResourceType, ThrottlingType>>(9);
|
||||
|
||||
/// <summary>
|
||||
/// Provides a compiled regular expression used for extracting the reason code from the error message.
|
||||
/// </summary>
|
||||
private static readonly Regex sqlErrorCodeRegEx = new Regex(@"Code:\s*(\d+)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value that reflects the throttling mode in SQL Azure.
|
||||
/// </summary>
|
||||
public ThrottlingMode ThrottlingMode
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of resources in SQL Azure that were subject to throttling conditions.
|
||||
/// </summary>
|
||||
public IEnumerable<Tuple<ThrottledResourceType, ThrottlingType>> ThrottledResources
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.throttledResources;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines throttling conditions from the specified SQL exception.
|
||||
/// </summary>
|
||||
/// <param name="ex">The <see cref="SqlException"/> object containing information relevant to an error returned by SQL Server when encountering throttling conditions.</param>
|
||||
/// <returns>An instance of the object holding the decoded reason codes returned from SQL Azure upon encountering throttling conditions.</returns>
|
||||
public static ThrottlingReason FromException(SqlException ex)
|
||||
{
|
||||
if (ex != null)
|
||||
{
|
||||
foreach (SqlError error in ex.Errors)
|
||||
{
|
||||
if (error.Number == ThrottlingErrorNumber)
|
||||
{
|
||||
return FromError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Unknown;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines the throttling conditions from the specified SQL error.
|
||||
/// </summary>
|
||||
/// <param name="error">The <see cref="SqlError"/> object containing information relevant to a warning or error returned by SQL Server.</param>
|
||||
/// <returns>An instance of the object holding the decoded reason codes returned from SQL Azure when encountering throttling conditions.</returns>
|
||||
public static ThrottlingReason FromError(SqlError error)
|
||||
{
|
||||
if (error != null)
|
||||
{
|
||||
var match = sqlErrorCodeRegEx.Match(error.Message);
|
||||
int reasonCode = 0;
|
||||
|
||||
if (match.Success && Int32.TryParse(match.Groups[1].Value, out reasonCode))
|
||||
{
|
||||
return FromReasonCode(reasonCode);
|
||||
}
|
||||
}
|
||||
|
||||
return Unknown;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines the throttling conditions from the specified reason code.
|
||||
/// </summary>
|
||||
/// <param name="reasonCode">The reason code returned by SQL Azure which contains the throttling mode and the exceeded resource types.</param>
|
||||
/// <returns>An instance of the object holding the decoded reason codes returned from SQL Azure when encountering throttling conditions.</returns>
|
||||
public static ThrottlingReason FromReasonCode(int reasonCode)
|
||||
{
|
||||
if (reasonCode > 0)
|
||||
{
|
||||
// Decode throttling mode from the last 2 bits.
|
||||
ThrottlingMode throttlingMode = (ThrottlingMode)(reasonCode & 3);
|
||||
|
||||
var condition = new ThrottlingReason() { ThrottlingMode = throttlingMode };
|
||||
|
||||
// Shift 8 bits to truncate throttling mode.
|
||||
int groupCode = reasonCode >> 8;
|
||||
|
||||
// Determine throttling type for all well-known resources that may be subject to throttling conditions.
|
||||
condition.throttledResources.Add(Tuple.Create(ThrottledResourceType.PhysicalDatabaseSpace, (ThrottlingType)(groupCode & 3)));
|
||||
condition.throttledResources.Add(Tuple.Create(ThrottledResourceType.PhysicalLogSpace, (ThrottlingType)((groupCode = groupCode >> 2) & 3)));
|
||||
condition.throttledResources.Add(Tuple.Create(ThrottledResourceType.LogWriteIODelay, (ThrottlingType)((groupCode = groupCode >> 2) & 3)));
|
||||
condition.throttledResources.Add(Tuple.Create(ThrottledResourceType.DataReadIODelay, (ThrottlingType)((groupCode = groupCode >> 2) & 3)));
|
||||
condition.throttledResources.Add(Tuple.Create(ThrottledResourceType.CPU, (ThrottlingType)((groupCode = groupCode >> 2) & 3)));
|
||||
condition.throttledResources.Add(Tuple.Create(ThrottledResourceType.DatabaseSize, (ThrottlingType)((groupCode = groupCode >> 2) & 3)));
|
||||
condition.throttledResources.Add(Tuple.Create(ThrottledResourceType.Internal, (ThrottlingType)((groupCode = groupCode >> 2) & 3)));
|
||||
condition.throttledResources.Add(Tuple.Create(ThrottledResourceType.WorkerThreads, (ThrottlingType)((groupCode = groupCode >> 2) & 3)));
|
||||
condition.throttledResources.Add(Tuple.Create(ThrottledResourceType.Internal, (ThrottlingType)((groupCode = groupCode >> 2) & 3)));
|
||||
|
||||
return condition;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether physical data file space throttling was reported by SQL Azure.
|
||||
/// </summary>
|
||||
public bool IsThrottledOnDataSpace
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.throttledResources.Where(x => x.Item1 == ThrottledResourceType.PhysicalDatabaseSpace).Count() > 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether physical log space throttling was reported by SQL Azure.
|
||||
/// </summary>
|
||||
public bool IsThrottledOnLogSpace
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.throttledResources.Where(x => x.Item1 == ThrottledResourceType.PhysicalLogSpace).Count() > 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether transaction activity throttling was reported by SQL Azure.
|
||||
/// </summary>
|
||||
public bool IsThrottledOnLogWrite
|
||||
{
|
||||
get { return this.throttledResources.Where(x => x.Item1 == ThrottledResourceType.LogWriteIODelay).Count() > 0; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether data read activity throttling was reported by SQL Azure.
|
||||
/// </summary>
|
||||
public bool IsThrottledOnDataRead
|
||||
{
|
||||
get { return this.throttledResources.Where(x => x.Item1 == ThrottledResourceType.DataReadIODelay).Count() > 0; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether CPU throttling was reported by SQL Azure.
|
||||
/// </summary>
|
||||
public bool IsThrottledOnCPU
|
||||
{
|
||||
get { return this.throttledResources.Where(x => x.Item1 == ThrottledResourceType.CPU).Count() > 0; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether database size throttling was reported by SQL Azure.
|
||||
/// </summary>
|
||||
public bool IsThrottledOnDatabaseSize
|
||||
{
|
||||
get { return this.throttledResources.Where(x => x.Item1 == ThrottledResourceType.DatabaseSize).Count() > 0; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether concurrent requests throttling was reported by SQL Azure.
|
||||
/// </summary>
|
||||
public bool IsThrottledOnWorkerThreads
|
||||
{
|
||||
get { return this.throttledResources.Where(x => x.Item1 == ThrottledResourceType.WorkerThreads).Count() > 0; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether throttling conditions were not determined with certainty.
|
||||
/// </summary>
|
||||
public bool IsUnknown
|
||||
{
|
||||
get { return ThrottlingMode == ThrottlingMode.Unknown; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a textual representation the current ThrottlingReason object including the information held with respect to throttled resources.
|
||||
/// </summary>
|
||||
/// <returns>A string that represents the current ThrottlingReason object.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder result = new StringBuilder();
|
||||
|
||||
result.AppendFormat(Resources.Mode, ThrottlingMode);
|
||||
|
||||
var resources = this.throttledResources.Where(x => x.Item1 != ThrottledResourceType.Internal).
|
||||
Select<Tuple<ThrottledResourceType, ThrottlingType>, string>(x => String.Format(CultureInfo.CurrentCulture, Resources.ThrottlingTypeInfo, x.Item1, x.Item2)).
|
||||
OrderBy(x => x).ToArray();
|
||||
|
||||
result.Append(String.Join(", ", resources));
|
||||
|
||||
return result.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
#region ThrottlingMode enumeration
|
||||
/// <summary>
|
||||
/// Defines the possible throttling modes in SQL Azure.
|
||||
/// </summary>
|
||||
public enum ThrottlingMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Corresponds to "No Throttling" throttling mode whereby all SQL statements can be processed.
|
||||
/// </summary>
|
||||
NoThrottling = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Corresponds to "Reject Update / Insert" throttling mode whereby SQL statements such as INSERT, UPDATE, CREATE TABLE and CREATE INDEX are rejected.
|
||||
/// </summary>
|
||||
RejectUpdateInsert = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Corresponds to "Reject All Writes" throttling mode whereby SQL statements such as INSERT, UPDATE, DELETE, CREATE, DROP are rejected.
|
||||
/// </summary>
|
||||
RejectAllWrites = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Corresponds to "Reject All" throttling mode whereby all SQL statements are rejected.
|
||||
/// </summary>
|
||||
RejectAll = 3,
|
||||
|
||||
/// <summary>
|
||||
/// Corresponds to an unknown throttling mode whereby throttling mode cannot be determined with certainty.
|
||||
/// </summary>
|
||||
Unknown = -1
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region ThrottlingType enumeration
|
||||
/// <summary>
|
||||
/// Defines the possible throttling types in SQL Azure.
|
||||
/// </summary>
|
||||
public enum ThrottlingType
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that no throttling was applied to a given resource.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Corresponds to a Soft throttling type. Soft throttling is applied when machine resources such as, CPU, IO, storage, and worker threads exceed
|
||||
/// predefined safety thresholds despite the load balancer’s best efforts.
|
||||
/// </summary>
|
||||
Soft = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Corresponds to a Hard throttling type. Hard throttling is applied when the machine is out of resources, for example storage space.
|
||||
/// With hard throttling, no new connections are allowed to the databases hosted on the machine until resources are freed up.
|
||||
/// </summary>
|
||||
Hard = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Corresponds to an unknown throttling type in the event when the throttling type cannot be determined with certainty.
|
||||
/// </summary>
|
||||
Unknown = 3
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region ThrottledResourceType enumeration
|
||||
/// <summary>
|
||||
/// Defines the types of resources in SQL Azure which may be subject to throttling conditions.
|
||||
/// </summary>
|
||||
public enum ThrottledResourceType
|
||||
{
|
||||
/// <summary>
|
||||
/// Corresponds to "Physical Database Space" resource which may be subject to throttling.
|
||||
/// </summary>
|
||||
PhysicalDatabaseSpace = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Corresponds to "Physical Log File Space" resource which may be subject to throttling.
|
||||
/// </summary>
|
||||
PhysicalLogSpace = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Corresponds to "Transaction Log Write IO Delay" resource which may be subject to throttling.
|
||||
/// </summary>
|
||||
LogWriteIODelay = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Corresponds to "Database Read IO Delay" resource which may be subject to throttling.
|
||||
/// </summary>
|
||||
DataReadIODelay = 3,
|
||||
|
||||
/// <summary>
|
||||
/// Corresponds to "CPU" resource which may be subject to throttling.
|
||||
/// </summary>
|
||||
CPU = 4,
|
||||
|
||||
/// <summary>
|
||||
/// Corresponds to "Database Size" resource which may be subject to throttling.
|
||||
/// </summary>
|
||||
DatabaseSize = 5,
|
||||
|
||||
/// <summary>
|
||||
/// Corresponds to "SQL Worker Thread Pool" resource which may be subject to throttling.
|
||||
/// </summary>
|
||||
WorkerThreads = 7,
|
||||
|
||||
/// <summary>
|
||||
/// Corresponds to an internal resource which may be subject to throttling.
|
||||
/// </summary>
|
||||
Internal = 6,
|
||||
|
||||
/// <summary>
|
||||
/// Corresponds to an unknown resource type in the event when the actual resource cannot be determined with certainty.
|
||||
/// </summary>
|
||||
Unknown = -1
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,542 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
// This code is copied from the source described in the comment below.
|
||||
|
||||
// =======================================================================================
|
||||
// Microsoft Windows Server AppFabric Customer Advisory Team (CAT) Best Practices Series
|
||||
//
|
||||
// This sample is supplemental to the technical guidance published on the community
|
||||
// blog at http://blogs.msdn.com/appfabriccat/ and copied from
|
||||
// sqlmain ./sql/manageability/mfx/common/
|
||||
//
|
||||
// =======================================================================================
|
||||
// Copyright © 2012 Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
|
||||
// EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. YOU BEAR THE RISK OF USING IT.
|
||||
// =======================================================================================
|
||||
|
||||
// namespace Microsoft.SQL.CAT.BestPractices.SqlAzure.Framework
|
||||
// namespace Microsoft.SqlServer.Management.Common
|
||||
|
||||
using System;
|
||||
using System.Data.SqlClient;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using Microsoft.SqlTools.EditorServices.Utility;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements a policy defining and implementing the retry mechanism for unreliable actions.
|
||||
/// </summary>
|
||||
internal abstract partial class RetryPolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a callback delegate which will be invoked whenever a retry condition is encountered.
|
||||
/// </summary>
|
||||
/// <param name="retryState">The state of current retry attempt.</param>
|
||||
internal delegate void RetryCallbackDelegate(RetryState retryState);
|
||||
|
||||
/// <summary>
|
||||
/// Defines a callback delegate which will be invoked whenever an error is ignored on retry.
|
||||
/// </summary>
|
||||
/// <param name="retryState">The state of current retry attempt.</param>
|
||||
internal delegate void IgnoreErrorCallbackDelegate(RetryState retryState);
|
||||
|
||||
private readonly IErrorDetectionStrategy _errorDetectionStrategy;
|
||||
|
||||
protected RetryPolicy(IErrorDetectionStrategy strategy)
|
||||
{
|
||||
Contract.Assert(strategy != null);
|
||||
|
||||
_errorDetectionStrategy = strategy;
|
||||
this.FastFirstRetry = true;
|
||||
|
||||
//TODO Defect 1078447 Validate whether CommandTimeout needs to be used differently in schema/data scenarios
|
||||
this.CommandTimeoutInSeconds = AmbientSettings.LongRunningQueryTimeoutSeconds;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An instance of a callback delegate which will be invoked whenever a retry condition is encountered.
|
||||
/// </summary>
|
||||
public event RetryCallbackDelegate RetryOccurred;
|
||||
|
||||
/// <summary>
|
||||
/// An instance of a callback delegate which will be invoked whenever an error is ignored on retry.
|
||||
/// </summary>
|
||||
public event IgnoreErrorCallbackDelegate IgnoreErrorOccurred;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not the very first retry attempt will be made immediately
|
||||
/// whereas the subsequent retries will remain subject to retry interval.
|
||||
/// </summary>
|
||||
public bool FastFirstRetry { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the timeout in seconds of sql commands
|
||||
/// </summary>
|
||||
public int CommandTimeoutInSeconds
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the error detection strategy of this retry policy
|
||||
/// </summary>
|
||||
internal IErrorDetectionStrategy ErrorDetectionStrategy
|
||||
{
|
||||
get
|
||||
{
|
||||
return _errorDetectionStrategy;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// We should only ignore errors if they happen after the first retry.
|
||||
/// This flag is used to allow the ignore even on first try, for testing purposes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This flag is currently being used for TESTING PURPOSES ONLY.
|
||||
/// </remarks>
|
||||
internal bool ShouldIgnoreOnFirstTry
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
protected static bool IsLessThanMaxRetryCount(int currentRetryCount, int maxRetryCount)
|
||||
{
|
||||
return currentRetryCount <= maxRetryCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Repetitively executes the specified action while it satisfies the current retry policy.
|
||||
/// </summary>
|
||||
/// <param name="action">A delegate representing the executable action which doesn't return any results.</param>
|
||||
/// <param name="token">Cancellation token to cancel action between retries.</param>
|
||||
public void ExecuteAction(Action action, CancellationToken? token = null)
|
||||
{
|
||||
ExecuteAction(
|
||||
_ => action(), token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Repetitively executes the specified action while it satisfies the current retry policy.
|
||||
/// </summary>
|
||||
/// <param name="action">A delegate representing the executable action which doesn't return any results.</param>
|
||||
/// <param name="token">Cancellation token to cancel action between retries.</param>
|
||||
public void ExecuteAction(Action<RetryState> action, CancellationToken? token = null)
|
||||
{
|
||||
ExecuteAction<object>(
|
||||
retryState =>
|
||||
{
|
||||
action(retryState);
|
||||
return null;
|
||||
}, token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Repetitively executes the specified action while it satisfies the current retry policy.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of result expected from the executable action.</typeparam>
|
||||
/// <param name="func">A delegate representing the executable action which returns the result of type T.</param>
|
||||
/// <param name="token">Cancellation token to cancel action between retries.</param>
|
||||
/// <returns>The result from the action.</returns>
|
||||
public T ExecuteAction<T>(Func<T> func, CancellationToken? token = null)
|
||||
{
|
||||
return ExecuteAction(
|
||||
_ => func(), token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Repetitively executes the specified action while it satisfies the current retry policy.
|
||||
/// </summary>
|
||||
/// <typeparam name="R">The type of result expected from the executable action.</typeparam>
|
||||
/// <param name="func">A delegate representing the executable action which returns the result of type R.</param>
|
||||
/// <param name="token">Cancellation token to cancel action between retries.</param>
|
||||
/// <returns>The result from the action.</returns>
|
||||
public R ExecuteAction<R>(Func<RetryState, R> func, CancellationToken? token = null)
|
||||
{
|
||||
RetryState retryState = CreateRetryState();
|
||||
|
||||
if (token != null)
|
||||
{
|
||||
token.Value.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
return func(retryState);
|
||||
}
|
||||
catch (RetryLimitExceededException limitExceededEx)
|
||||
{
|
||||
// The user code can throw a RetryLimitExceededException to force the exit from the retry loop.
|
||||
// The RetryLimitExceeded exception can have an inner exception attached to it. This is the exception
|
||||
// which we will have to throw up the stack so that callers can handle it.
|
||||
if (limitExceededEx.InnerException != null)
|
||||
{
|
||||
throw limitExceededEx.InnerException;
|
||||
}
|
||||
|
||||
return default(R);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
retryState.LastError = ex;
|
||||
|
||||
if (retryState.RetryCount > 0 || this.ShouldIgnoreOnFirstTry)
|
||||
{
|
||||
// If we can ignore this error, then break out of the loop and consider this execution as passing
|
||||
// We return the default value for the type R
|
||||
if (ShouldIgnoreError(retryState))
|
||||
{
|
||||
OnIgnoreErrorOccurred(retryState);
|
||||
return default(R);
|
||||
}
|
||||
}
|
||||
|
||||
retryState.RetryCount++;
|
||||
|
||||
if (!ShouldRetry(retryState))
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
OnRetryOccurred(retryState);
|
||||
|
||||
if ((retryState.RetryCount > 1 || !FastFirstRetry) && !retryState.IsDelayDisabled)
|
||||
{
|
||||
Thread.Sleep(retryState.Delay);
|
||||
}
|
||||
|
||||
// check for cancellation after delay.
|
||||
if (token != null)
|
||||
{
|
||||
token.Value.ThrowIfCancellationRequested();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual RetryState CreateRetryState()
|
||||
{
|
||||
return new RetryState();
|
||||
}
|
||||
|
||||
public bool IsRetryableException(Exception ex)
|
||||
{
|
||||
return ErrorDetectionStrategy.CanRetry(ex);
|
||||
}
|
||||
|
||||
public bool ShouldRetry(RetryState retryState)
|
||||
{
|
||||
bool canRetry = ErrorDetectionStrategy.CanRetry(retryState.LastError);
|
||||
bool shouldRetry = canRetry
|
||||
&& ShouldRetryImpl(retryState);
|
||||
|
||||
Logger.Write(LogLevel.Error,
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"Retry requested: Retry count = {0}. Delay = {1}, SQL Error Number = {2}, Can retry error = {3}, Will retry = {4}",
|
||||
retryState.RetryCount,
|
||||
retryState.Delay,
|
||||
GetErrorNumber(retryState.LastError),
|
||||
canRetry,
|
||||
shouldRetry));
|
||||
|
||||
// Perform an extra check in the delay interval. Should prevent from accidentally ending up with the value of -1 which will block a thread indefinitely.
|
||||
// In addition, any other negative numbers will cause an ArgumentOutOfRangeException fault which will be thrown by Thread.Sleep.
|
||||
if (retryState.Delay.TotalMilliseconds < 0)
|
||||
{
|
||||
retryState.Delay = TimeSpan.Zero;
|
||||
}
|
||||
return shouldRetry;
|
||||
}
|
||||
|
||||
public bool ShouldIgnoreError(RetryState retryState)
|
||||
{
|
||||
bool shouldIgnoreError = ErrorDetectionStrategy.ShouldIgnoreError(retryState.LastError);
|
||||
|
||||
Logger.Write(LogLevel.Error,
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"Ignore Error requested: Retry count = {0}. Delay = {1}, SQL Error Number = {2}, Should Ignore Error = {3}",
|
||||
retryState.RetryCount,
|
||||
retryState.Delay,
|
||||
GetErrorNumber(retryState.LastError),
|
||||
shouldIgnoreError));
|
||||
|
||||
return shouldIgnoreError;
|
||||
}
|
||||
|
||||
/* TODO - Error code does not exist in SqlException for .NET Core
|
||||
private static int? GetErrorCode(Exception ex)
|
||||
{
|
||||
SqlException sqlEx= ex as SqlException;
|
||||
if (sqlEx == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return sqlEx.ErrorCode;
|
||||
}
|
||||
*/
|
||||
|
||||
private static int? GetErrorNumber(Exception ex)
|
||||
{
|
||||
SqlException sqlEx = ex as SqlException;
|
||||
if (sqlEx == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return sqlEx.Number;
|
||||
}
|
||||
|
||||
protected abstract bool ShouldRetryImpl(RetryState retryState);
|
||||
|
||||
/// <summary>
|
||||
/// Notifies the subscribers whenever a retry condition is encountered.
|
||||
/// </summary>
|
||||
/// <param name="retryState">The state of current retry attempt.</param>
|
||||
protected virtual void OnRetryOccurred(RetryState retryState)
|
||||
{
|
||||
var retryOccurred = RetryOccurred;
|
||||
if (retryOccurred != null)
|
||||
{
|
||||
retryOccurred(retryState);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notifies the subscribers whenever an error is ignored on retry.
|
||||
/// </summary>
|
||||
/// <param name="retryState">The state of current retry attempt.</param>
|
||||
protected virtual void OnIgnoreErrorOccurred(RetryState retryState)
|
||||
{
|
||||
var ignoreErrorOccurred = IgnoreErrorOccurred;
|
||||
if (ignoreErrorOccurred != null)
|
||||
{
|
||||
ignoreErrorOccurred(retryState);
|
||||
}
|
||||
}
|
||||
|
||||
internal class FixedDelayPolicy : RetryPolicy
|
||||
{
|
||||
private readonly int _maxRetryCount;
|
||||
private readonly TimeSpan _intervalBetweenRetries;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new instance of the TRetryPolicy class with the specified number of retry attempts and time interval between retries.
|
||||
/// </summary>
|
||||
/// <param name="strategy">The <see cref="RetryPolicy.IErrorDetectionStrategy"/> to use when checking whether an error is retryable</param>
|
||||
/// <param name="maxRetryCount">The max number of retry attempts. Should be 1-indexed.</param>
|
||||
/// <param name="intervalBetweenRetries">The interval between retries.</param>
|
||||
public FixedDelayPolicy(IErrorDetectionStrategy strategy, int maxRetryCount, TimeSpan intervalBetweenRetries)
|
||||
: base(strategy)
|
||||
{
|
||||
Contract.Assert(maxRetryCount >= 0, "maxRetryCount cannot be a negative number");
|
||||
Contract.Assert(intervalBetweenRetries.Ticks >= 0, "intervalBetweenRetries cannot be negative");
|
||||
|
||||
_maxRetryCount = maxRetryCount;
|
||||
_intervalBetweenRetries = intervalBetweenRetries;
|
||||
}
|
||||
|
||||
protected override bool ShouldRetryImpl(RetryState retryState)
|
||||
{
|
||||
Contract.Assert(retryState != null);
|
||||
|
||||
if (IsLessThanMaxRetryCount(retryState.RetryCount, _maxRetryCount))
|
||||
{
|
||||
retryState.Delay = _intervalBetweenRetries;
|
||||
return true;
|
||||
}
|
||||
|
||||
retryState.Delay = TimeSpan.Zero;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal class ProgressiveRetryPolicy : RetryPolicy
|
||||
{
|
||||
private readonly int _maxRetryCount;
|
||||
private readonly TimeSpan _initialInterval;
|
||||
private readonly TimeSpan _increment;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new instance of the TRetryPolicy class with the specified number of retry attempts and parameters defining the progressive delay between retries.
|
||||
/// </summary>
|
||||
/// <param name="strategy">The <see cref="RetryPolicy.IErrorDetectionStrategy"/> to use when checking whether an error is retryable</param>
|
||||
/// <param name="maxRetryCount">The maximum number of retry attempts. Should be 1-indexed.</param>
|
||||
/// <param name="initialInterval">The initial interval which will apply for the first retry.</param>
|
||||
/// <param name="increment">The incremental time value which will be used for calculating the progressive delay between retries.</param>
|
||||
public ProgressiveRetryPolicy(IErrorDetectionStrategy strategy, int maxRetryCount, TimeSpan initialInterval, TimeSpan increment)
|
||||
: base(strategy)
|
||||
{
|
||||
Contract.Assert(maxRetryCount >= 0, "maxRetryCount cannot be a negative number");
|
||||
Contract.Assert(initialInterval.Ticks >= 0, "retryInterval cannot be negative");
|
||||
Contract.Assert(increment.Ticks >= 0, "retryInterval cannot be negative");
|
||||
|
||||
_maxRetryCount = maxRetryCount;
|
||||
_initialInterval = initialInterval;
|
||||
_increment = increment;
|
||||
}
|
||||
|
||||
protected override bool ShouldRetryImpl(RetryState retryState)
|
||||
{
|
||||
Contract.Assert(retryState != null);
|
||||
|
||||
if (IsLessThanMaxRetryCount(retryState.RetryCount, _maxRetryCount))
|
||||
{
|
||||
retryState.Delay = TimeSpan.FromMilliseconds(_initialInterval.TotalMilliseconds + (_increment.TotalMilliseconds * (retryState.RetryCount - 1)));
|
||||
return true;
|
||||
}
|
||||
|
||||
retryState.Delay = TimeSpan.Zero;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal class ExponentialDelayRetryPolicy : RetryPolicy
|
||||
{
|
||||
private readonly int _maxRetryCount;
|
||||
private readonly double _intervalFactor;
|
||||
private readonly TimeSpan _minInterval;
|
||||
private readonly TimeSpan _maxInterval;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new instance of the TRetryPolicy class with the specified number of retry attempts and parameters defining the progressive delay between retries.
|
||||
/// </summary>
|
||||
/// <param name="strategy">The <see cref="RetryPolicy.IErrorDetectionStrategy"/> to use when checking whether an error is retryable</param>
|
||||
/// <param name="maxRetryCount">The maximum number of retry attempts.</param>
|
||||
/// <param name="intervalFactor">Controls the speed at which the delay increases - the retryCount is raised to this power as
|
||||
/// part of the function </param>
|
||||
/// <param name="minInterval">Minimum interval between retries. The basis for all backoff calculations</param>
|
||||
/// <param name="maxInterval">Maximum interval between retries. Backoff will not take longer than this period.</param>
|
||||
public ExponentialDelayRetryPolicy(IErrorDetectionStrategy strategy, int maxRetryCount, double intervalFactor, TimeSpan minInterval, TimeSpan maxInterval)
|
||||
: base(strategy)
|
||||
{
|
||||
Contract.Assert(maxRetryCount >= 0, "maxRetryCount cannot be a negative number");
|
||||
Contract.Assert(intervalFactor > 1, "intervalFactor Must be > 1 so that the delay increases exponentially");
|
||||
Contract.Assert(minInterval.Ticks >= 0, "minInterval cannot be negative");
|
||||
Contract.Assert(maxInterval.Ticks >= 0, "maxInterval cannot be negative");
|
||||
Contract.Assert(maxInterval.Ticks >= minInterval.Ticks, "maxInterval must be greater than minInterval");
|
||||
|
||||
_maxRetryCount = maxRetryCount;
|
||||
_intervalFactor = intervalFactor;
|
||||
_minInterval = minInterval;
|
||||
_maxInterval = maxInterval;
|
||||
}
|
||||
|
||||
protected override bool ShouldRetryImpl(RetryState retryState)
|
||||
{
|
||||
Contract.Assert(retryState != null);
|
||||
|
||||
if (IsLessThanMaxRetryCount(retryState.RetryCount, _maxRetryCount))
|
||||
{
|
||||
retryState.Delay = RetryPolicyUtils.CalcExponentialRetryDelay(retryState.RetryCount, _intervalFactor, _minInterval, _maxInterval);
|
||||
return true;
|
||||
}
|
||||
|
||||
retryState.Delay = TimeSpan.Zero;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal class TimeBasedRetryPolicy : RetryPolicy
|
||||
{
|
||||
private readonly TimeSpan _minTotalRetryTimeLimit;
|
||||
private readonly TimeSpan _maxTotalRetryTimeLimit;
|
||||
private readonly double _totalRetryTimeLimitRate;
|
||||
|
||||
private readonly TimeSpan _minInterval;
|
||||
private readonly TimeSpan _maxInterval;
|
||||
private readonly double _intervalFactor;
|
||||
|
||||
private readonly Stopwatch _stopwatch;
|
||||
|
||||
public TimeBasedRetryPolicy(
|
||||
IErrorDetectionStrategy strategy,
|
||||
TimeSpan minTotalRetryTimeLimit,
|
||||
TimeSpan maxTotalRetryTimeLimit,
|
||||
double totalRetryTimeLimitRate,
|
||||
TimeSpan minInterval,
|
||||
TimeSpan maxInterval,
|
||||
double intervalFactor)
|
||||
: base(strategy)
|
||||
{
|
||||
Contract.Assert(minTotalRetryTimeLimit.Ticks >= 0);
|
||||
Contract.Assert(maxTotalRetryTimeLimit.Ticks >= minTotalRetryTimeLimit.Ticks);
|
||||
Contract.Assert(totalRetryTimeLimitRate >= 0);
|
||||
|
||||
Contract.Assert(minInterval.Ticks >= 0);
|
||||
Contract.Assert(maxInterval.Ticks >= minInterval.Ticks);
|
||||
Contract.Assert(intervalFactor >= 1);
|
||||
|
||||
_minTotalRetryTimeLimit = minTotalRetryTimeLimit;
|
||||
_maxTotalRetryTimeLimit = maxTotalRetryTimeLimit;
|
||||
_totalRetryTimeLimitRate = totalRetryTimeLimitRate;
|
||||
|
||||
_minInterval = minInterval;
|
||||
_maxInterval = maxInterval;
|
||||
_intervalFactor = intervalFactor;
|
||||
|
||||
_stopwatch = Stopwatch.StartNew();
|
||||
}
|
||||
|
||||
protected override bool ShouldRetryImpl(RetryState retryStateObj)
|
||||
{
|
||||
Contract.Assert(retryStateObj is RetryStateEx);
|
||||
RetryStateEx retryState = (RetryStateEx)retryStateObj;
|
||||
|
||||
// Calculate the delay as exponential value based on the number of retries.
|
||||
retryState.Delay =
|
||||
RetryPolicyUtils.CalcExponentialRetryDelay(
|
||||
retryState.RetryCount,
|
||||
_intervalFactor,
|
||||
_minInterval,
|
||||
_maxInterval);
|
||||
|
||||
// Add the delay to the total retry time
|
||||
retryState.TotalRetryTime = retryState.TotalRetryTime + retryState.Delay;
|
||||
|
||||
// Calculate the maximum total retry time depending on how long ago was the task (this retry policy) started.
|
||||
// Longer running tasks are less eager to abort since, more work is has been done.
|
||||
TimeSpan totalRetryTimeLimit = checked(TimeSpan.FromMilliseconds(
|
||||
Math.Max(
|
||||
Math.Min(
|
||||
_stopwatch.ElapsedMilliseconds * _totalRetryTimeLimitRate,
|
||||
_maxTotalRetryTimeLimit.TotalMilliseconds),
|
||||
_minTotalRetryTimeLimit.TotalMilliseconds)));
|
||||
|
||||
if (retryState.TotalRetryTime <= totalRetryTimeLimit)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
retryState.Delay = TimeSpan.Zero;
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override RetryState CreateRetryState()
|
||||
{
|
||||
return new RetryStateEx { TotalRetryTime = TimeSpan.Zero };
|
||||
}
|
||||
|
||||
private sealed class RetryStateEx : RetryState
|
||||
{
|
||||
public TimeSpan TotalRetryTime { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,459 @@
|
||||
//
|
||||
// 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.Globalization;
|
||||
using Microsoft.SqlTools.EditorServices.Utility;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
|
||||
{
|
||||
internal sealed class RetryPolicyDefaults
|
||||
{
|
||||
/// <summary>
|
||||
/// The default number of retry attempts.
|
||||
/// </summary>
|
||||
public const int DefaulSchemaRetryCount = 6;
|
||||
|
||||
/// <summary>
|
||||
/// The default number of retry attempts for create database.
|
||||
/// </summary>
|
||||
public const int DefaultCreateDatabaseRetryCount = 5;
|
||||
|
||||
/// <summary>
|
||||
/// The default amount of time defining an interval between retries.
|
||||
/// </summary>
|
||||
public static readonly TimeSpan DefaultSchemaMinInterval = TimeSpan.FromSeconds(2.75);
|
||||
|
||||
/// <summary>
|
||||
/// The default factor to use when determining exponential backoff between retries.
|
||||
/// </summary>
|
||||
public const double DefaultBackoffIntervalFactor = 2.0;
|
||||
|
||||
/// <summary>
|
||||
/// The default maximum time between retries.
|
||||
/// </summary>
|
||||
public static readonly TimeSpan DefaultMaxRetryInterval = TimeSpan.FromSeconds(60);
|
||||
|
||||
/// <summary>
|
||||
/// The default number of retry attempts.
|
||||
/// </summary>
|
||||
public static readonly int DefaultDataCommandRetryCount = 5;
|
||||
|
||||
/// <summary>
|
||||
/// The default number of retry attempts for a connection related error
|
||||
/// </summary>
|
||||
public static readonly int DefaultConnectionRetryCount = 6;
|
||||
|
||||
/// <summary>
|
||||
/// The default amount of time defining an interval between retries.
|
||||
/// </summary>
|
||||
public static readonly TimeSpan DefaultDataMinInterval = TimeSpan.FromSeconds(1.0);
|
||||
|
||||
/// <summary>
|
||||
/// The default amount of time defining a time increment between retry attempts in the progressive delay policy.
|
||||
/// </summary>
|
||||
public static readonly TimeSpan DefaultProgressiveRetryIncrement = TimeSpan.FromMilliseconds(500);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements a collection of the RetryPolicyInfo elements holding retry policy settings.
|
||||
/// </summary>
|
||||
internal sealed class RetryPolicyFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns a default policy that does no retries, it just invokes action exactly once.
|
||||
/// </summary>
|
||||
public static readonly RetryPolicy NoRetryPolicy = RetryPolicyFactory.CreateNoRetryPolicy();
|
||||
|
||||
/// <summary>
|
||||
/// Returns a default policy that does no retries, it just invokes action exactly once.
|
||||
/// </summary>
|
||||
public static readonly RetryPolicy PrimaryKeyViolationRetryPolicy = RetryPolicyFactory.CreatePrimaryKeyCommandRetryPolicy();
|
||||
|
||||
/// <summary>
|
||||
/// Implements a strategy that ignores any transient errors.
|
||||
/// Internal for testing purposes only
|
||||
/// </summary>
|
||||
internal sealed class TransientErrorIgnoreStrategy : RetryPolicy.IErrorDetectionStrategy
|
||||
{
|
||||
private static readonly TransientErrorIgnoreStrategy _instance = new TransientErrorIgnoreStrategy();
|
||||
|
||||
public static TransientErrorIgnoreStrategy Instance
|
||||
{
|
||||
get { return _instance; }
|
||||
}
|
||||
|
||||
public bool CanRetry(Exception ex)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool ShouldIgnoreError(Exception ex)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates and returns a default Retry Policy for Schema based operations.
|
||||
/// </summary>
|
||||
/// <returns>An instance of <see cref="RetryPolicy"/> class.</returns>
|
||||
internal static RetryPolicy CreateDefaultSchemaCommandRetryPolicy(bool useRetry, int retriesPerPhase = RetryPolicyDefaults.DefaulSchemaRetryCount)
|
||||
{
|
||||
RetryPolicy policy;
|
||||
|
||||
if (useRetry)
|
||||
{
|
||||
policy = new RetryPolicy.ExponentialDelayRetryPolicy(
|
||||
RetryPolicy.SqlAzureTemporaryErrorDetectionStrategy.Instance,
|
||||
retriesPerPhase,
|
||||
RetryPolicyDefaults.DefaultBackoffIntervalFactor,
|
||||
RetryPolicyDefaults.DefaultSchemaMinInterval,
|
||||
RetryPolicyDefaults.DefaultMaxRetryInterval);
|
||||
policy.FastFirstRetry = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
policy = CreateNoRetryPolicy();
|
||||
}
|
||||
|
||||
return policy;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates and returns a default Retry Policy for Schema based connection operations.
|
||||
/// </summary>
|
||||
/// <remarks>The RetryOccured event is wired to raise an RaiseAmbientRetryMessage message for a connection retry. </remarks>
|
||||
/// <returns>An instance of <see cref="RetryPolicy"/> class.</returns>
|
||||
internal static RetryPolicy CreateSchemaConnectionRetryPolicy(int retriesPerPhase)
|
||||
{
|
||||
RetryPolicy policy = new RetryPolicy.ExponentialDelayRetryPolicy(
|
||||
RetryPolicy.SqlAzureTemporaryErrorDetectionStrategy.Instance,
|
||||
retriesPerPhase,
|
||||
RetryPolicyDefaults.DefaultBackoffIntervalFactor,
|
||||
RetryPolicyDefaults.DefaultSchemaMinInterval,
|
||||
RetryPolicyDefaults.DefaultMaxRetryInterval);
|
||||
policy.RetryOccurred += DataConnectionFailureRetry;
|
||||
return policy;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates and returns a default Retry Policy for Schema based command operations.
|
||||
/// </summary>
|
||||
/// <remarks>The RetryOccured event is wired to raise an RaiseAmbientRetryMessage message for a command retry. </remarks>
|
||||
/// <returns>An instance of <see cref="RetryPolicy"/> class.</returns>
|
||||
internal static RetryPolicy CreateSchemaCommandRetryPolicy(int retriesPerPhase)
|
||||
{
|
||||
RetryPolicy policy = new RetryPolicy.ExponentialDelayRetryPolicy(
|
||||
RetryPolicy.SqlAzureTemporaryErrorDetectionStrategy.Instance,
|
||||
retriesPerPhase,
|
||||
RetryPolicyDefaults.DefaultBackoffIntervalFactor,
|
||||
RetryPolicyDefaults.DefaultSchemaMinInterval,
|
||||
RetryPolicyDefaults.DefaultMaxRetryInterval);
|
||||
policy.FastFirstRetry = false;
|
||||
policy.RetryOccurred += CommandFailureRetry;
|
||||
return policy;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates and returns a Retry Policy for database creation operations.
|
||||
/// </summary>
|
||||
/// <param name="ignorableErrorNumbers">Errors to ignore if they occur after first retry</param>
|
||||
/// <remarks>
|
||||
/// The RetryOccured event is wired to raise an RaiseAmbientRetryMessage message for a command retry.
|
||||
/// The IgnoreErrorOccurred event is wired to raise an RaiseAmbientIgnoreMessage message for ignore.
|
||||
/// </remarks>
|
||||
/// <returns>An instance of <see cref="RetryPolicy"/> class.</returns>
|
||||
internal static RetryPolicy CreateDatabaseCommandRetryPolicy(params int[] ignorableErrorNumbers)
|
||||
{
|
||||
RetryPolicy.SqlAzureTemporaryAndIgnorableErrorDetectionStrategy errorDetectionStrategy =
|
||||
new RetryPolicy.SqlAzureTemporaryAndIgnorableErrorDetectionStrategy(ignorableErrorNumbers);
|
||||
|
||||
// 30, 60, 60, 60, 60 second retries
|
||||
RetryPolicy policy = new RetryPolicy.ExponentialDelayRetryPolicy(
|
||||
errorDetectionStrategy,
|
||||
RetryPolicyDefaults.DefaultCreateDatabaseRetryCount /* maxRetryCount */,
|
||||
RetryPolicyDefaults.DefaultBackoffIntervalFactor,
|
||||
TimeSpan.FromSeconds(30) /* minInterval */,
|
||||
TimeSpan.FromSeconds(60) /* maxInterval */);
|
||||
|
||||
policy.FastFirstRetry = false;
|
||||
policy.RetryOccurred += CreateDatabaseCommandFailureRetry;
|
||||
policy.IgnoreErrorOccurred += CreateDatabaseCommandFailureIgnore;
|
||||
|
||||
return policy;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates and returns an "ignoreable" command Retry Policy.
|
||||
/// </summary>
|
||||
/// <param name="ignorableErrorNumbers">Errors to ignore if they occur after first retry</param>
|
||||
/// <remarks>
|
||||
/// The RetryOccured event is wired to raise an RaiseAmbientRetryMessage message for a command retry.
|
||||
/// The IgnoreErrorOccurred event is wired to raise an RaiseAmbientIgnoreMessage message for ignore.
|
||||
/// </remarks>
|
||||
/// <returns>An instance of <see cref="RetryPolicy"/> class.</returns>
|
||||
internal static RetryPolicy CreateElementCommandRetryPolicy(params int[] ignorableErrorNumbers)
|
||||
{
|
||||
Debug.Assert(ignorableErrorNumbers != null);
|
||||
|
||||
RetryPolicy.SqlAzureTemporaryAndIgnorableErrorDetectionStrategy errorDetectionStrategy =
|
||||
new RetryPolicy.SqlAzureTemporaryAndIgnorableErrorDetectionStrategy(ignorableErrorNumbers);
|
||||
|
||||
RetryPolicy policy = new RetryPolicy.ExponentialDelayRetryPolicy(
|
||||
errorDetectionStrategy,
|
||||
RetryPolicyDefaults.DefaulSchemaRetryCount,
|
||||
RetryPolicyDefaults.DefaultBackoffIntervalFactor,
|
||||
RetryPolicyDefaults.DefaultSchemaMinInterval,
|
||||
RetryPolicyDefaults.DefaultMaxRetryInterval);
|
||||
|
||||
policy.FastFirstRetry = false;
|
||||
policy.RetryOccurred += ElementCommandFailureRetry;
|
||||
policy.IgnoreErrorOccurred += ElementCommandFailureIgnore;
|
||||
|
||||
return policy;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates and returns an "primary key violation" command Retry Policy.
|
||||
/// </summary>
|
||||
/// <param name="ignorableErrorNumbers">Errors to ignore if they occur after first retry</param>
|
||||
/// <remarks>
|
||||
/// The RetryOccured event is wired to raise an RaiseAmbientRetryMessage message for a command retry.
|
||||
/// The IgnoreErrorOccurred event is wired to raise an RaiseAmbientIgnoreMessage message for ignore.
|
||||
/// </remarks>
|
||||
/// <returns>An instance of <see cref="RetryPolicy"/> class.</returns>
|
||||
internal static RetryPolicy CreatePrimaryKeyCommandRetryPolicy()
|
||||
{
|
||||
RetryPolicy.SqlAzureTemporaryAndIgnorableErrorDetectionStrategy errorDetectionStrategy =
|
||||
new RetryPolicy.SqlAzureTemporaryAndIgnorableErrorDetectionStrategy(SqlErrorNumbers.PrimaryKeyViolationErrorNumber);
|
||||
|
||||
RetryPolicy policy = new RetryPolicy.ExponentialDelayRetryPolicy(
|
||||
errorDetectionStrategy,
|
||||
RetryPolicyDefaults.DefaulSchemaRetryCount,
|
||||
RetryPolicyDefaults.DefaultBackoffIntervalFactor,
|
||||
RetryPolicyDefaults.DefaultSchemaMinInterval,
|
||||
RetryPolicyDefaults.DefaultMaxRetryInterval);
|
||||
|
||||
policy.FastFirstRetry = true;
|
||||
policy.RetryOccurred += CommandFailureRetry;
|
||||
policy.IgnoreErrorOccurred += CommandFailureIgnore;
|
||||
|
||||
return policy;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Policy that will never allow retries to occur.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static RetryPolicy CreateNoRetryPolicy()
|
||||
{
|
||||
return new RetryPolicy.FixedDelayPolicy(TransientErrorIgnoreStrategy.Instance, 0, TimeSpan.Zero);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Policy that is optimized for data-related script update operations.
|
||||
/// This is extremely error tolerant and uses a Time based delay policy that backs
|
||||
/// off until some overall length of delay has occurred. It is not as long-running
|
||||
/// as the ConnectionManager data transfer retry policy since that's intended for bulk upload
|
||||
/// of large amounts of data, whereas this is for individual batch scripts executed by the
|
||||
/// batch execution engine.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static RetryPolicy CreateDataScriptUpdateRetryPolicy()
|
||||
{
|
||||
return new RetryPolicy.TimeBasedRetryPolicy(
|
||||
RetryPolicy.DataTransferErrorDetectionStrategy.Instance,
|
||||
TimeSpan.FromMinutes(7),
|
||||
TimeSpan.FromMinutes(7),
|
||||
0.1,
|
||||
TimeSpan.FromMilliseconds(250),
|
||||
TimeSpan.FromSeconds(30),
|
||||
1.5);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the default retry policy dedicated to handling exceptions with SQL connections
|
||||
/// </summary>
|
||||
/// <returns>The RetryPolicy policy</returns>
|
||||
public static RetryPolicy CreateFastDataRetryPolicy()
|
||||
{
|
||||
RetryPolicy retryPolicy = new RetryPolicy.FixedDelayPolicy(
|
||||
RetryPolicy.NetworkConnectivityErrorDetectionStrategy.Instance,
|
||||
RetryPolicyDefaults.DefaultDataCommandRetryCount,
|
||||
TimeSpan.FromMilliseconds(5));
|
||||
|
||||
retryPolicy.FastFirstRetry = true;
|
||||
retryPolicy.RetryOccurred += DataConnectionFailureRetry;
|
||||
return retryPolicy;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the default retry policy dedicated to handling exceptions with SQL connections.
|
||||
/// No logging or other message handler is attached to the policy
|
||||
/// </summary>
|
||||
/// <returns>The RetryPolicy policy</returns>
|
||||
public static RetryPolicy CreateDefaultSchemaConnectionRetryPolicy()
|
||||
{
|
||||
return CreateDefaultConnectionRetryPolicy();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the default retry policy dedicated to handling exceptions with SQL connections.
|
||||
/// Adds an event handler to log and notify listeners of data connection retries
|
||||
/// </summary>
|
||||
/// <returns>The RetryPolicy policy</returns>
|
||||
public static RetryPolicy CreateDefaultDataConnectionRetryPolicy()
|
||||
{
|
||||
RetryPolicy retryPolicy = CreateDefaultConnectionRetryPolicy();
|
||||
retryPolicy.RetryOccurred += DataConnectionFailureRetry;
|
||||
return retryPolicy;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the default retry policy dedicated to handling exceptions with SQL connections
|
||||
/// </summary>
|
||||
/// <returns>The RetryPolicy policy</returns>
|
||||
public static RetryPolicy CreateDefaultConnectionRetryPolicy()
|
||||
{
|
||||
// Note: No longer use Ado.net Connection Pooling and hence do not need TimeBasedRetryPolicy to
|
||||
// conform to the backoff requirements in this case
|
||||
RetryPolicy retryPolicy = new RetryPolicy.ExponentialDelayRetryPolicy(
|
||||
RetryPolicy.NetworkConnectivityErrorDetectionStrategy.Instance,
|
||||
RetryPolicyDefaults.DefaultConnectionRetryCount,
|
||||
RetryPolicyDefaults.DefaultBackoffIntervalFactor,
|
||||
RetryPolicyDefaults.DefaultSchemaMinInterval,
|
||||
RetryPolicyDefaults.DefaultMaxRetryInterval);
|
||||
|
||||
retryPolicy.FastFirstRetry = true;
|
||||
return retryPolicy;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the default retry policy dedicated to handling retryable conditions with data transfer SQL commands.
|
||||
/// </summary>
|
||||
/// <returns>The RetryPolicy policy</returns>
|
||||
public static RetryPolicy CreateDefaultDataSqlCommandRetryPolicy()
|
||||
{
|
||||
RetryPolicy retryPolicy = new RetryPolicy.ExponentialDelayRetryPolicy(
|
||||
RetryPolicy.SqlAzureTemporaryErrorDetectionStrategy.Instance,
|
||||
RetryPolicyDefaults.DefaultDataCommandRetryCount,
|
||||
RetryPolicyDefaults.DefaultBackoffIntervalFactor,
|
||||
RetryPolicyDefaults.DefaultDataMinInterval,
|
||||
RetryPolicyDefaults.DefaultMaxRetryInterval);
|
||||
|
||||
retryPolicy.FastFirstRetry = true;
|
||||
retryPolicy.RetryOccurred += CommandFailureRetry;
|
||||
return retryPolicy;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the default retry policy dedicated to handling retryable conditions with data transfer SQL commands.
|
||||
/// </summary>
|
||||
/// <returns>The RetryPolicy policy</returns>
|
||||
public static RetryPolicy CreateDefaultDataTransferRetryPolicy()
|
||||
{
|
||||
RetryPolicy retryPolicy = new RetryPolicy.TimeBasedRetryPolicy(
|
||||
RetryPolicy.DataTransferErrorDetectionStrategy.Instance,
|
||||
TimeSpan.FromMinutes(20),
|
||||
TimeSpan.FromMinutes(240),
|
||||
0.1,
|
||||
TimeSpan.FromMilliseconds(250),
|
||||
TimeSpan.FromMinutes(2),
|
||||
2);
|
||||
|
||||
retryPolicy.FastFirstRetry = true;
|
||||
retryPolicy.RetryOccurred += CommandFailureRetry;
|
||||
return retryPolicy;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the retry policy to handle data migration for column encryption.
|
||||
/// </summary>
|
||||
/// <returns>The RetryPolicy policy</returns>
|
||||
public static RetryPolicy CreateColumnEncryptionTransferRetryPolicy()
|
||||
{
|
||||
RetryPolicy retryPolicy = new RetryPolicy.TimeBasedRetryPolicy(
|
||||
RetryPolicy.DataTransferErrorDetectionStrategy.Instance,
|
||||
TimeSpan.FromMinutes(5),
|
||||
TimeSpan.FromMinutes(5),
|
||||
0.1,
|
||||
TimeSpan.FromMilliseconds(250),
|
||||
TimeSpan.FromMinutes(2),
|
||||
2);
|
||||
|
||||
retryPolicy.FastFirstRetry = true;
|
||||
retryPolicy.RetryOccurred += CommandFailureRetry;
|
||||
return retryPolicy;
|
||||
}
|
||||
|
||||
private static void DataConnectionFailureRetry(RetryState retryState)
|
||||
{
|
||||
Logger.Write(LogLevel.Normal, string.Format(CultureInfo.InvariantCulture,
|
||||
"Connection retry number {0}. Delaying {1} ms before retry. Exception: {2}",
|
||||
retryState.RetryCount,
|
||||
retryState.Delay.TotalMilliseconds.ToString(CultureInfo.InvariantCulture),
|
||||
retryState.LastError.ToString()));
|
||||
|
||||
RetryPolicyUtils.RaiseAmbientRetryMessage(retryState, SqlSchemaModelErrorCodes.ServiceActions.ConnectionRetry);
|
||||
}
|
||||
|
||||
private static void CommandFailureRetry(RetryState retryState, string commandKeyword)
|
||||
{
|
||||
Logger.Write(LogLevel.Normal, string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"{0} retry number {1}. Delaying {2} ms before retry. Exception: {3}",
|
||||
commandKeyword,
|
||||
retryState.RetryCount,
|
||||
retryState.Delay.TotalMilliseconds.ToString(CultureInfo.InvariantCulture),
|
||||
retryState.LastError.ToString()));
|
||||
|
||||
RetryPolicyUtils.RaiseAmbientRetryMessage(retryState, SqlSchemaModelErrorCodes.ServiceActions.CommandRetry);
|
||||
}
|
||||
|
||||
private static void CommandFailureIgnore(RetryState retryState, string commandKeyword)
|
||||
{
|
||||
Logger.Write(LogLevel.Normal, string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"{0} retry number {1}. Ignoring failure. Exception: {2}",
|
||||
commandKeyword,
|
||||
retryState.RetryCount,
|
||||
retryState.LastError.ToString()));
|
||||
|
||||
RetryPolicyUtils.RaiseAmbientIgnoreMessage(retryState, SqlSchemaModelErrorCodes.ServiceActions.CommandRetry);
|
||||
}
|
||||
|
||||
private static void CommandFailureRetry(RetryState retryState)
|
||||
{
|
||||
CommandFailureRetry(retryState, "Command");
|
||||
}
|
||||
|
||||
private static void CommandFailureIgnore(RetryState retryState)
|
||||
{
|
||||
CommandFailureIgnore(retryState, "Command");
|
||||
}
|
||||
|
||||
private static void CreateDatabaseCommandFailureRetry(RetryState retryState)
|
||||
{
|
||||
CommandFailureRetry(retryState, "Database Command");
|
||||
}
|
||||
|
||||
private static void CreateDatabaseCommandFailureIgnore(RetryState retryState)
|
||||
{
|
||||
CommandFailureIgnore(retryState, "Database Command");
|
||||
}
|
||||
|
||||
private static void ElementCommandFailureRetry(RetryState retryState)
|
||||
{
|
||||
CommandFailureRetry(retryState, "Element Command");
|
||||
}
|
||||
|
||||
private static void ElementCommandFailureIgnore(RetryState retryState)
|
||||
{
|
||||
CommandFailureIgnore(retryState, "Element Command");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,476 @@
|
||||
//
|
||||
// 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.SqlTools.EditorServices.Utility;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
|
||||
{
|
||||
internal static class RetryPolicyUtils
|
||||
{
|
||||
/// <summary>
|
||||
/// Approved list of transient errors that should be retryable during Network connection stages
|
||||
/// </summary>
|
||||
private static readonly HashSet<int> _retryableNetworkConnectivityErrors;
|
||||
/// <summary>
|
||||
/// Approved list of transient errors that should be retryable on Azure
|
||||
/// </summary>
|
||||
private static readonly HashSet<int> _retryableAzureErrors;
|
||||
/// <summary>
|
||||
/// Blocklist of non-transient errors that should stop retry during data transfer operations
|
||||
/// </summary>
|
||||
private static readonly HashSet<int> _nonRetryableDataTransferErrors;
|
||||
|
||||
static RetryPolicyUtils()
|
||||
{
|
||||
_retryableNetworkConnectivityErrors = new HashSet<int>
|
||||
{
|
||||
/// A severe error occurred on the current command. The results, if any, should be discarded.
|
||||
0,
|
||||
|
||||
//// DBNETLIB Error Code: 20
|
||||
//// The instance of SQL Server you attempted to connect to does not support encryption.
|
||||
(int) ProcessNetLibErrorCode.EncryptionNotSupported,
|
||||
|
||||
//// DBNETLIB Error Code: -2
|
||||
//// Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding.
|
||||
(int)ProcessNetLibErrorCode.Timeout,
|
||||
|
||||
//// SQL Error Code: 64
|
||||
//// A connection was successfully established with the server, but then an error occurred during the login process.
|
||||
//// (provider: TCP Provider, error: 0 - The specified network name is no longer available.)
|
||||
64,
|
||||
|
||||
//// SQL Error Code: 233
|
||||
//// The client was unable to establish a connection because of an error during connection initialization process before login.
|
||||
//// Possible causes include the following: the client tried to connect to an unsupported version of SQL Server; the server was too busy
|
||||
//// to accept new connections; or there was a resource limitation (insufficient memory or maximum allowed connections) on the server.
|
||||
//// (provider: TCP Provider, error: 0 - An existing connection was forcibly closed by the remote host.)
|
||||
233,
|
||||
|
||||
//// SQL Error Code: 10053
|
||||
//// A transport-level error has occurred when receiving results from the server.
|
||||
//// An established connection was aborted by the software in your host machine.
|
||||
10053,
|
||||
|
||||
//// SQL Error Code: 10054
|
||||
//// A transport-level error has occurred when sending the request to the server.
|
||||
//// (provider: TCP Provider, error: 0 - An existing connection was forcibly closed by the remote host.)
|
||||
10054,
|
||||
|
||||
//// SQL Error Code: 10060
|
||||
//// A network-related or instance-specific error occurred while establishing a connection to SQL Server.
|
||||
//// The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server
|
||||
//// is configured to allow remote connections. (provider: TCP Provider, error: 0 - A connection attempt failed
|
||||
//// because the connected party did not properly respond after a period of time, or established connection failed
|
||||
//// because connected host has failed to respond.)"}
|
||||
10060,
|
||||
|
||||
// SQL Error Code: 11001
|
||||
// A network-related or instance-specific error occurred while establishing a connection to SQL Server.
|
||||
// The server was not found or was not accessible. Verify that the instance name is correct and that SQL
|
||||
// Server is configured to allow remote connections. (provider: TCP Provider, error: 0 - No such host is known.)
|
||||
11001,
|
||||
|
||||
//// SQL Error Code: 40613
|
||||
//// Database XXXX on server YYYY is not currently available. Please retry the connection later. If the problem persists, contact customer
|
||||
//// support, and provide them the session tracing ID of ZZZZZ.
|
||||
40613,
|
||||
};
|
||||
|
||||
_retryableAzureErrors = new HashSet<int>
|
||||
{
|
||||
//// SQL Error Code: 40
|
||||
//// Could not open a connection to SQL Server
|
||||
//// (provider: Named Pipes Provider, error: 40 Could not open a connection to SQL Server)
|
||||
40,
|
||||
|
||||
//// SQL Error Code: 121
|
||||
//// A transport-level error has occurred when receiving results from the server.
|
||||
//// (provider: TCP Provider, error: 0 - The semaphore timeout period has expired.)
|
||||
121,
|
||||
|
||||
//// SQL Error Code: 913 (noticed intermittently on SNAP runs with connected unit tests)
|
||||
//// Could not find database ID %d. Database may not be activated yet or may be in transition. Reissue the query once the database is available.
|
||||
//// If you do not think this error is due to a database that is transitioning its state and this error continues to occur, contact your primary support provider.
|
||||
//// Please have available for review the Microsoft SQL Server error log and any additional information relevant to the circumstances when the error occurred.
|
||||
913,
|
||||
|
||||
//// SQL Error Code: 1205
|
||||
//// Transaction (Process ID %d) was deadlocked on %.*ls resources with another process and has been chosen as the deadlock victim. Rerun the transaction.
|
||||
1205,
|
||||
|
||||
//// SQL Error Code: 40501
|
||||
//// The service is currently busy. Retry the request after 10 seconds. Code: (reason code to be decoded).
|
||||
RetryPolicy.ThrottlingReason.ThrottlingErrorNumber,
|
||||
|
||||
//// SQL Error Code: 10928
|
||||
//// Resource ID: %d. The %s limit for the database is %d and has been reached.
|
||||
10928,
|
||||
|
||||
//// SQL Error Code: 10929
|
||||
//// Resource ID: %d. The %s minimum guarantee is %d, maximum limit is %d and the current usage for the database is %d.
|
||||
//// However, the server is currently too busy to support requests greater than %d for this database.
|
||||
10929,
|
||||
|
||||
//// SQL Error Code: 40143
|
||||
//// The service has encountered an error processing your request. Please try again.
|
||||
40143,
|
||||
|
||||
//// SQL Error Code: 40197
|
||||
//// The service has encountered an error processing your request. Please try again.
|
||||
40197,
|
||||
|
||||
//// Sql Error Code: 40549 (not supposed to be used anymore as of Q2 2011)
|
||||
//// Session is terminated because you have a long-running transaction. Try shortening your transaction.
|
||||
40549,
|
||||
|
||||
//// Sql Error Code: 40550 (not supposed to be used anymore as of Q2 2011)
|
||||
//// The session has been terminated because it has acquired too many locks. Try reading or modifying fewer rows in a single transaction.
|
||||
40550,
|
||||
|
||||
//// Sql Error Code: 40551 (not supposed to be used anymore as of Q2 2011)
|
||||
//// The session has been terminated because of excessive TEMPDB usage. Try modifying your query to reduce the temporary table space usage.
|
||||
40551,
|
||||
|
||||
//// Sql Error Code: 40552 (not supposed to be used anymore as of Q2 2011)
|
||||
//// The session has been terminated because of excessive transaction log space usage. Try modifying fewer rows in a single transaction.
|
||||
40552,
|
||||
|
||||
//// Sql Error Code: 40553 (not supposed to be used anymore as of Q2 2011)
|
||||
//// The session has been terminated because of excessive memory usage. Try modifying your query to process fewer rows.
|
||||
40553,
|
||||
|
||||
//// SQL Error Code: 40627
|
||||
//// Operation on server YYY and database XXX is in progress. Please wait a few minutes before trying again.
|
||||
40627,
|
||||
|
||||
//// SQL Error Code: 40671 (DB CRUD)
|
||||
//// Unable to '%.*ls' '%.*ls' on server '%.*ls'. Please retry the connection later.
|
||||
40671,
|
||||
|
||||
//// SQL Error Code: 40676 (DB CRUD)
|
||||
//// '%.*ls' request was received but may not be processed completely at this time,
|
||||
//// please query the sys.dm_operation_status table in the master database for status.
|
||||
40676,
|
||||
|
||||
//// SQL Error Code: 45133
|
||||
//// A connection failed while the operation was still in progress, and the outcome of the operation is unknown.
|
||||
45133,
|
||||
};
|
||||
|
||||
foreach(int errorNum in _retryableNetworkConnectivityErrors)
|
||||
{
|
||||
_retryableAzureErrors.Add(errorNum);
|
||||
}
|
||||
|
||||
_nonRetryableDataTransferErrors = new HashSet<int>
|
||||
{
|
||||
//// Syntax error
|
||||
156,
|
||||
|
||||
//// Cannot insert duplicate key row in object '%.*ls' with unique index '%.*ls'. The duplicate key value is %ls.
|
||||
2601,
|
||||
|
||||
//// Violation of %ls constraint '%.*ls'. Cannot insert duplicate key in object '%.*ls'. The duplicate key value is %ls.
|
||||
2627,
|
||||
|
||||
//// Cannot find index '%.*ls'.
|
||||
2727,
|
||||
|
||||
//// SqlClr stack error
|
||||
6522,
|
||||
|
||||
//// Divide by zero error encountered.
|
||||
8134,
|
||||
|
||||
//// Could not repair this error.
|
||||
8922,
|
||||
|
||||
//// Bug 1110540: This error means the table is corrupted due to hardware failure, so we do not want to retry.
|
||||
//// Table error: Object ID %d. The text, ntext, or image node at page %S_PGID, slot %d, text ID %I64d is referenced by page %S_PGID, slot %d, but was not seen in the scan.
|
||||
8965,
|
||||
|
||||
//// The query processor is unable to produce a plan because the clustered index is disabled.
|
||||
8655,
|
||||
|
||||
//// The query processor is unable to produce a plan because table is unavailable because the heap is corrupted
|
||||
8674,
|
||||
|
||||
//// SqlClr permission / load error.
|
||||
//// Example Message: An error occurred in the Microsoft .NET Framework while trying to load assembly
|
||||
10314,
|
||||
|
||||
//// '%ls' is not supported in this version of SQL Server.
|
||||
40514,
|
||||
|
||||
//// The database 'XYZ' has reached its size quota. Partition or delete data, drop indexes, or consult the documentation for possible resolutions
|
||||
40544,
|
||||
};
|
||||
}
|
||||
|
||||
public static bool IsRetryableNetworkConnectivityError(int errorNumber)
|
||||
{
|
||||
return _retryableNetworkConnectivityErrors.Contains(errorNumber);
|
||||
}
|
||||
|
||||
public static bool IsRetryableAzureError(int errorNumber)
|
||||
{
|
||||
return _retryableAzureErrors.Contains(errorNumber) || _retryableNetworkConnectivityErrors.Contains(errorNumber);
|
||||
}
|
||||
|
||||
public static bool IsNonRetryableDataTransferError(int errorNumber)
|
||||
{
|
||||
return _nonRetryableDataTransferErrors.Contains(errorNumber);
|
||||
}
|
||||
|
||||
public static void AppendThrottlingDataIfIsThrottlingError(SqlException sqlException, SqlError error)
|
||||
{
|
||||
//// SQL Error Code: 40501
|
||||
//// The service is currently busy. Retry the request after 10 seconds. Code: (reason code to be decoded).
|
||||
if(error.Number == RetryPolicy.ThrottlingReason.ThrottlingErrorNumber)
|
||||
{
|
||||
// Decode the reason code from the error message to determine the grounds for throttling.
|
||||
var condition = RetryPolicy.ThrottlingReason.FromError(error);
|
||||
|
||||
// Attach the decoded values as additional attributes to the original SQL exception.
|
||||
sqlException.Data[condition.ThrottlingMode.GetType().Name] = condition.ThrottlingMode.ToString();
|
||||
sqlException.Data[condition.GetType().Name] = condition;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Calculates the length of time to delay a retry based on the number of retries up to this point.
|
||||
/// As the number of retries increases, the timeout increases exponentially based on the intervalFactor.
|
||||
/// Uses default values for the intervalFactor (<see cref="RetryPolicyDefaults.DefaultBackoffIntervalFactor"/>), minInterval
|
||||
/// (<see cref="RetryPolicyDefaults.DefaultSchemaMinInterval"/>) and maxInterval (<see cref="RetryPolicyDefaults.DefaultMaxRetryInterval"/>)
|
||||
/// </summary>
|
||||
/// <param name="currentRetryCount">Total number of retries including the current retry</param>
|
||||
/// <returns>TimeSpan defining the length of time to delay</returns>
|
||||
internal static TimeSpan CalcExponentialRetryDelayWithSchemaDefaults(int currentRetryCount)
|
||||
{
|
||||
return CalcExponentialRetryDelay(currentRetryCount,
|
||||
RetryPolicyDefaults.DefaultBackoffIntervalFactor,
|
||||
RetryPolicyDefaults.DefaultSchemaMinInterval,
|
||||
RetryPolicyDefaults.DefaultMaxRetryInterval);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the length of time to delay a retry based on the number of retries up to this point.
|
||||
/// As the number of retries increases, the timeout increases exponentially based on the intervalFactor.
|
||||
/// A very large retry count can cause huge delay, so the maxInterval is used to cap delay time at a sensible
|
||||
/// upper bound
|
||||
/// </summary>
|
||||
/// <param name="currentRetryCount">Total number of retries including the current retry</param>
|
||||
/// <param name="intervalFactor">Controls the speed at which the delay increases - the retryCount is raised to this power as
|
||||
/// part of the function </param>
|
||||
/// <param name="minInterval">Minimum interval between retries. The basis for all backoff calculations</param>
|
||||
/// <param name="maxInterval">Maximum interval between retries. Backoff will not take longer than this period.</param>
|
||||
/// <returns>TimeSpan defining the length of time to delay</returns>
|
||||
internal static TimeSpan CalcExponentialRetryDelay(int currentRetryCount, double intervalFactor, TimeSpan minInterval, TimeSpan maxInterval)
|
||||
{
|
||||
try
|
||||
{
|
||||
return checked(TimeSpan.FromMilliseconds(
|
||||
Math.Max(
|
||||
Math.Min(
|
||||
Math.Pow(intervalFactor, currentRetryCount - 1) * minInterval.TotalMilliseconds,
|
||||
maxInterval.TotalMilliseconds
|
||||
),
|
||||
minInterval.TotalMilliseconds)
|
||||
));
|
||||
}
|
||||
catch (OverflowException)
|
||||
{
|
||||
// If numbers are too large, could conceivably overflow the double.
|
||||
// Since the maxInterval is the largest TimeSpan expected, can safely return this here
|
||||
return maxInterval;
|
||||
}
|
||||
}
|
||||
|
||||
internal static void RaiseAmbientRetryMessage(RetryState retryState, int errorCode)
|
||||
{
|
||||
Action<SqlServerRetryError> retryMsgHandler = AmbientSettings.ConnectionRetryMessageHandler;
|
||||
if (retryMsgHandler != null)
|
||||
{
|
||||
string msg = SqlServerRetryError.FormatRetryMessage(
|
||||
retryState.RetryCount,
|
||||
retryState.Delay,
|
||||
retryState.LastError);
|
||||
|
||||
retryMsgHandler(new SqlServerRetryError(
|
||||
msg,
|
||||
retryState.LastError,
|
||||
retryState.RetryCount,
|
||||
errorCode,
|
||||
ErrorSeverity.Warning));
|
||||
}
|
||||
}
|
||||
|
||||
internal static void RaiseAmbientIgnoreMessage(RetryState retryState, int errorCode)
|
||||
{
|
||||
Action<SqlServerRetryError> retryMsgHandler = AmbientSettings.ConnectionRetryMessageHandler;
|
||||
if (retryMsgHandler != null)
|
||||
{
|
||||
string msg = SqlServerRetryError.FormatIgnoreMessage(
|
||||
retryState.RetryCount,
|
||||
retryState.LastError);
|
||||
|
||||
retryMsgHandler(new SqlServerRetryError(
|
||||
msg,
|
||||
retryState.LastError,
|
||||
retryState.RetryCount,
|
||||
errorCode,
|
||||
ErrorSeverity.Warning));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Traces the Schema retry information before raising the retry message
|
||||
/// </summary>
|
||||
/// <param name="retryState"></param>
|
||||
/// <param name="errorCode"></param>
|
||||
/// <param name="azureSessionId"></param>
|
||||
internal static void RaiseSchemaAmbientRetryMessage(RetryState retryState, int errorCode, Guid azureSessionId)
|
||||
{
|
||||
Logger.Write(LogLevel.Warning, string.Format(
|
||||
"Retry occurred: session: {0}; attempt - {1}; delay - {2}; exception - \"{3}\"",
|
||||
azureSessionId,
|
||||
retryState.RetryCount,
|
||||
retryState.Delay,
|
||||
retryState.LastError
|
||||
));
|
||||
|
||||
RaiseAmbientRetryMessage(retryState, errorCode);
|
||||
}
|
||||
|
||||
#region ProcessNetLibErrorCode enumeration
|
||||
|
||||
/// <summary>
|
||||
/// Error codes reported by the DBNETLIB module.
|
||||
/// </summary>
|
||||
internal enum ProcessNetLibErrorCode
|
||||
{
|
||||
/// <summary>
|
||||
/// Zero bytes were returned
|
||||
/// </summary>
|
||||
ZeroBytes = -3,
|
||||
|
||||
/// <summary>
|
||||
/// Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding.
|
||||
/// </summary>
|
||||
Timeout = -2,
|
||||
|
||||
/// <summary>
|
||||
/// An unknown net lib error
|
||||
/// </summary>
|
||||
Unknown = -1,
|
||||
|
||||
/// <summary>
|
||||
/// Out of memory
|
||||
/// </summary>
|
||||
InsufficientMemory = 1,
|
||||
|
||||
/// <summary>
|
||||
/// User or machine level access denied
|
||||
/// </summary>
|
||||
AccessDenied = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Connection was already busy processing another request
|
||||
/// </summary>
|
||||
ConnectionBusy = 3,
|
||||
|
||||
/// <summary>
|
||||
/// The connection was broken without a proper disconnect
|
||||
/// </summary>
|
||||
ConnectionBroken = 4,
|
||||
|
||||
/// <summary>
|
||||
/// The connection has reached a limit
|
||||
/// </summary>
|
||||
ConnectionLimit = 5,
|
||||
|
||||
/// <summary>
|
||||
/// Name resolution failed for the given server name
|
||||
/// </summary>
|
||||
ServerNotFound = 6,
|
||||
|
||||
/// <summary>
|
||||
/// Network transport could not be found
|
||||
/// </summary>
|
||||
NetworkNotFound = 7,
|
||||
|
||||
/// <summary>
|
||||
/// A resource required could not be allocated
|
||||
/// </summary>
|
||||
InsufficientResources = 8,
|
||||
|
||||
/// <summary>
|
||||
/// Network stack denied the request as too busy
|
||||
/// </summary>
|
||||
NetworkBusy = 9,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to access the requested network
|
||||
/// </summary>
|
||||
NetworkAccessDenied = 10,
|
||||
|
||||
/// <summary>
|
||||
/// Internal error
|
||||
/// </summary>
|
||||
GeneralError = 11,
|
||||
|
||||
/// <summary>
|
||||
/// The network mode was set incorrectly
|
||||
/// </summary>
|
||||
IncorrectMode = 12,
|
||||
|
||||
/// <summary>
|
||||
/// The given name was not found
|
||||
/// </summary>
|
||||
NameNotFound = 13,
|
||||
|
||||
/// <summary>
|
||||
/// Connection was invalid
|
||||
/// </summary>
|
||||
InvalidConnection = 14,
|
||||
|
||||
/// <summary>
|
||||
/// A read or write error occurred
|
||||
/// </summary>
|
||||
ReadWriteError = 15,
|
||||
|
||||
/// <summary>
|
||||
/// Unable to allocate an additional handle
|
||||
/// </summary>
|
||||
TooManyHandles = 16,
|
||||
|
||||
/// <summary>
|
||||
/// The server reported an error
|
||||
/// </summary>
|
||||
ServerError = 17,
|
||||
|
||||
/// <summary>
|
||||
/// SSL failed
|
||||
/// </summary>
|
||||
SSLError = 18,
|
||||
|
||||
/// <summary>
|
||||
/// Encryption failed with an error
|
||||
/// </summary>
|
||||
EncryptionError = 19,
|
||||
|
||||
/// <summary>
|
||||
/// Remote endpoint does not support encryption
|
||||
/// </summary>
|
||||
EncryptionNotSupported = 20
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
|
||||
{
|
||||
internal class RetryState
|
||||
{
|
||||
private int _retryCount = 0;
|
||||
private TimeSpan _delay = TimeSpan.Zero;
|
||||
private Exception _lastError = null;
|
||||
private bool _isDelayDisabled = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current retry attempt count.
|
||||
/// </summary>
|
||||
public int RetryCount
|
||||
{
|
||||
get
|
||||
{
|
||||
return _retryCount;
|
||||
}
|
||||
set
|
||||
{
|
||||
_retryCount = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the delay indicating how long the current thread will be suspended for before the next iteration will be invoked.
|
||||
/// </summary>
|
||||
public TimeSpan Delay
|
||||
{
|
||||
get
|
||||
{
|
||||
return _delay;
|
||||
}
|
||||
set
|
||||
{
|
||||
_delay = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the exception which caused the retry conditions to occur.
|
||||
/// </summary>
|
||||
public Exception LastError
|
||||
{
|
||||
get
|
||||
{
|
||||
return _lastError;
|
||||
}
|
||||
set
|
||||
{
|
||||
_lastError = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether we should ignore delay in order to be able to execute our tests faster
|
||||
/// </summary>
|
||||
/// <remarks>Intended for test use ONLY</remarks>
|
||||
internal bool IsDelayDisabled
|
||||
{
|
||||
get
|
||||
{
|
||||
return _isDelayDisabled;
|
||||
}
|
||||
set
|
||||
{
|
||||
_isDelayDisabled = value;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Reset()
|
||||
{
|
||||
this.IsDelayDisabled = false;
|
||||
this.RetryCount = 0;
|
||||
this.Delay = TimeSpan.Zero;
|
||||
this.LastError = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
|
||||
{
|
||||
static class SqlConnectionHelperScripts
|
||||
{
|
||||
public const string EngineEdition = "SELECT SERVERPROPERTY('EngineEdition'), SERVERPROPERTY('productversion'), SERVERPROPERTY ('productlevel'), SERVERPROPERTY ('edition'), (SELECT CASE WHEN EXISTS (SELECT TOP 1 1 from [sys].[all_columns] WITH (NOLOCK) WHERE name = N'xml_index_type' AND OBJECT_ID(N'sys.xml_indexes') = object_id) THEN 1 ELSE 0 END AS SXI_PRESENT)";
|
||||
public const string EngineEditionWithLock = "SELECT SERVERPROPERTY('EngineEdition'), SERVERPROPERTY('productversion'), SERVERPROPERTY ('productlevel'), SERVERPROPERTY ('edition'), (SELECT CASE WHEN EXISTS (SELECT TOP 1 1 from [sys].[all_columns] WHERE name = N'xml_index_type' AND OBJECT_ID(N'sys.xml_indexes') = object_id) THEN 1 ELSE 0 END AS SXI_PRESENT)";
|
||||
|
||||
public const string CheckDatabaseReadonly = @"EXEC sp_dboption '{0}', 'read only'";
|
||||
|
||||
public const string GetDatabaseFilePathAndName = @"
|
||||
DECLARE @filepath nvarchar(260),
|
||||
@rc int
|
||||
|
||||
EXEC master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',N'Software\Microsoft\MSSQLServer\MSSQLServer',N'DefaultData', @filepath output, 'no_output'
|
||||
|
||||
IF ((@filepath IS NOT NULL) AND (CHARINDEX(N'\', @filepath, len(@filepath)) = 0))
|
||||
SELECT @filepath = @filepath + N'\'
|
||||
|
||||
IF (@filepath IS NULL)
|
||||
SELECT @filepath = [sdf].[physical_name]
|
||||
FROM [master].[sys].[database_files] AS [sdf]
|
||||
WHERE [file_id] = 1
|
||||
|
||||
SELECT @filepath AS FilePath
|
||||
";
|
||||
|
||||
public const string GetDatabaseLogPathAndName = @"
|
||||
DECLARE @filepath nvarchar(260),
|
||||
@rc int
|
||||
|
||||
EXEC master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',N'Software\Microsoft\MSSQLServer\MSSQLServer',N'DefaultLog', @filepath output, 'no_output'
|
||||
|
||||
IF ((@filepath IS NOT NULL) AND (CHARINDEX(N'\', @filepath, len(@filepath)) = 0))
|
||||
SELECT @filepath = @filepath + N'\'
|
||||
|
||||
IF (@filepath IS NULL)
|
||||
SELECT @filepath = [ldf].[physical_name]
|
||||
FROM [master].[sys].[database_files] AS [ldf]
|
||||
WHERE [file_id] = 2
|
||||
|
||||
SELECT @filepath AS FilePath
|
||||
";
|
||||
|
||||
public const string GetOsVersion = @"SELECT OSVersion = RIGHT(@@version, LEN(@@version)- 3 -charindex (' ON ', @@version))";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
|
||||
{
|
||||
/// <summary>
|
||||
/// Constants for SQL Error numbers
|
||||
/// </summary>
|
||||
internal static class SqlErrorNumbers
|
||||
{
|
||||
// Database XYZ already exists. Choose a different database name.
|
||||
internal const int DatabaseAlreadyExistsErrorNumber = 1801;
|
||||
|
||||
// Cannot drop the database 'x', because it does not exist or you do not have permission.
|
||||
internal const int DatabaseAlreadyDroppedErrorNumber = 3701;
|
||||
|
||||
// Database 'x' was created\altered successfully, but some properties could not be displayed.
|
||||
internal const int DatabaseCrudMetadataUpdateErrorNumber = 45166;
|
||||
|
||||
// Violation of PRIMARY KEY constraint 'x'.
|
||||
// Cannot insert duplicate key in object 'y'. The duplicate key value is (z).
|
||||
internal const int PrimaryKeyViolationErrorNumber = 2627;
|
||||
|
||||
// There is already an object named 'x' in the database.
|
||||
internal const int ObjectAlreadyExistsErrorNumber = 2714;
|
||||
|
||||
// Cannot drop the object 'x', because it does not exist or you do not have permission.
|
||||
internal const int ObjectAlreadyDroppedErrorNumber = 3701;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,465 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
|
||||
{
|
||||
internal static class SqlSchemaModelErrorCodes
|
||||
{
|
||||
private const int ParserErrorCodeStartIndex = 46000;
|
||||
private const int ParserErrorCodeEndIndex = 46499;
|
||||
|
||||
public static bool IsParseErrorCode(int errorCode)
|
||||
{
|
||||
return
|
||||
(errorCode >= ParserErrorCodeStartIndex) &&
|
||||
(errorCode <= ParserErrorCodeEndIndex);
|
||||
}
|
||||
|
||||
public static bool IsInterpretationErrorCode(int errorCode)
|
||||
{
|
||||
return
|
||||
(errorCode >= Interpretation.InterpretationBaseCode) &&
|
||||
(errorCode <= Interpretation.InterpretationEndCode);
|
||||
}
|
||||
|
||||
public static bool IsStatementFilterError(int errorCode)
|
||||
{
|
||||
return
|
||||
(errorCode > StatementFilter.StatementFilterBaseCode) &&
|
||||
(errorCode <= StatementFilter.StatementFilterMaxErrorCode);
|
||||
}
|
||||
|
||||
public static class StatementFilter
|
||||
{
|
||||
public const int StatementFilterBaseCode = 70000;
|
||||
|
||||
public const int UnrecognizedStatement = StatementFilterBaseCode + 1;
|
||||
public const int ServerObject = StatementFilterBaseCode + 2;
|
||||
public const int AtMostTwoPartName = StatementFilterBaseCode + 3;
|
||||
public const int AlterTableAddColumn = StatementFilterBaseCode + 4;
|
||||
public const int ConstraintAll = StatementFilterBaseCode + 5;
|
||||
public const int TriggerAll = StatementFilterBaseCode + 6;
|
||||
public const int CreateSchemaWithoutName = StatementFilterBaseCode + 7;
|
||||
public const int CreateSchemaElements = StatementFilterBaseCode + 8;
|
||||
public const int AlterAssembly = StatementFilterBaseCode + 9;
|
||||
public const int CreateStoplist = StatementFilterBaseCode + 10;
|
||||
public const int UnsupportedPermission = StatementFilterBaseCode + 11;
|
||||
public const int TopLevelExecuteWithResultSets = StatementFilterBaseCode + 12;
|
||||
public const int AlterTableAddConstraint = StatementFilterBaseCode + 13;
|
||||
public const int DatabaseOnlyObjectInServerProject = StatementFilterBaseCode + 14;
|
||||
public const int UnsupportedBySqlAzure = StatementFilterBaseCode + 15;
|
||||
public const int UnsupportedSecurityObjectKind = StatementFilterBaseCode + 16;
|
||||
public const int StatementNotSupportedForCurrentRelease = StatementFilterBaseCode + 17;
|
||||
public const int ServerPermissionsNotAllowed = StatementFilterBaseCode + 18;
|
||||
public const int DeprecatedSyntax = StatementFilterBaseCode + 19;
|
||||
public const int SetRemoteData = StatementFilterBaseCode + 20;
|
||||
public const int StatementFilterMaxErrorCode = StatementFilterBaseCode + 499;
|
||||
}
|
||||
|
||||
public static class Interpretation
|
||||
{
|
||||
public const int InterpretationBaseCode = 70500;
|
||||
|
||||
public const int InvalidTopLevelStatement = InterpretationBaseCode + 1;
|
||||
public const int InvalidAssemblySource = InterpretationBaseCode + 2;
|
||||
public const int InvalidDatabaseName = InterpretationBaseCode + 3;
|
||||
public const int OnlyTwoPartNameAllowed = InterpretationBaseCode + 4;
|
||||
public const int SecurityObjectCannotBeNull = InterpretationBaseCode + 5;
|
||||
public const int UnknownPermission = InterpretationBaseCode + 6;
|
||||
public const int UnsupportedAll = InterpretationBaseCode + 7;
|
||||
public const int InvalidColumnList = InterpretationBaseCode + 8;
|
||||
public const int ColumnsAreNotAllowed = InterpretationBaseCode + 9;
|
||||
public const int InvalidDataType = InterpretationBaseCode + 10;
|
||||
public const int InvalidObjectName = InterpretationBaseCode + 11;
|
||||
public const int InvalidObjectChildName = InterpretationBaseCode + 12;
|
||||
public const int NoGlobalTemporarySymmetricKey = InterpretationBaseCode + 13;
|
||||
public const int NoGlobalTemporarySymmetricKey_Warning = InterpretationBaseCode + 14;
|
||||
public const int NameCannotBeNull = InterpretationBaseCode + 15;
|
||||
public const int NameCannotBeNull_Warning = InterpretationBaseCode + 16;
|
||||
public const int InvalidLoginName = InterpretationBaseCode + 17;
|
||||
public const int InvalidLoginName_Warning = InterpretationBaseCode + 18;
|
||||
public const int MoreAliasesThanColumns = InterpretationBaseCode + 19;
|
||||
public const int FewerAliasesThanColumns = InterpretationBaseCode + 20;
|
||||
public const int InvalidTimestampReturnType = InterpretationBaseCode + 21;
|
||||
public const int VariableParameterAtTopLevelStatement = InterpretationBaseCode + 22;
|
||||
public const int CannotCreateTempTable = InterpretationBaseCode + 23;
|
||||
public const int MultipleNullabilityConstraintError = InterpretationBaseCode + 24;
|
||||
public const int MultipleNullabilityConstraintWarning = InterpretationBaseCode + 25;
|
||||
public const int ColumnIsntAllowedForAssemblySource = InterpretationBaseCode + 26;
|
||||
public const int InvalidUserName = InterpretationBaseCode + 27;
|
||||
public const int InvalidWindowsLogin = InterpretationBaseCode + 28;
|
||||
public const int InvalidWindowsLogin_Warning = InterpretationBaseCode + 29;
|
||||
public const int CannotHaveUsingForPrimaryXmlIndex = InterpretationBaseCode + 30;
|
||||
public const int UsingIsRequiredForSecondaryXmlIndex = InterpretationBaseCode + 31;
|
||||
public const int XmlIndexTypeIsRequiredForSecondaryXmlIndex = InterpretationBaseCode + 32;
|
||||
public const int UnsupportedAlterCryptographicProvider = InterpretationBaseCode + 33;
|
||||
public const int HttpForSoapOnly = InterpretationBaseCode + 34;
|
||||
public const int UnknownEventTypeOrGroup = InterpretationBaseCode + 35;
|
||||
public const int CannotAddLogFileToFilegroup = InterpretationBaseCode + 36;
|
||||
public const int BuiltInTypeExpected = InterpretationBaseCode + 37;
|
||||
public const int MissingArgument = InterpretationBaseCode + 38;
|
||||
public const int InvalidArgument = InterpretationBaseCode + 39;
|
||||
public const int IncompleteBoundingBoxCoordinates = InterpretationBaseCode + 40;
|
||||
public const int XMaxLessThanXMin = InterpretationBaseCode + 41;
|
||||
public const int YMaxLessThanYMin = InterpretationBaseCode + 42;
|
||||
public const int InvalidCoordinate = InterpretationBaseCode + 43;
|
||||
public const int InvalidValue = InterpretationBaseCode + 44;
|
||||
public const int InvalidIdentityValue = InterpretationBaseCode + 45;
|
||||
public const int InvalidPriorityLevel = InterpretationBaseCode + 46;
|
||||
public const int TriggerIsNotForEvent = InterpretationBaseCode + 47;
|
||||
public const int SyntaxError = InterpretationBaseCode + 48;
|
||||
public const int UnsupportedPintable = InterpretationBaseCode + 49;
|
||||
public const int DuplicateEventType = InterpretationBaseCode + 50;
|
||||
public const int ClearAndBasicAreNotAllowed = InterpretationBaseCode + 51;
|
||||
public const int AssemblyCorruptErrorCode = InterpretationBaseCode + 57;
|
||||
public const int DynamicQuery = InterpretationBaseCode + 58;
|
||||
public const int OnlyLcidAllowed = InterpretationBaseCode + 59;
|
||||
public const int WildCardNotAllowed = InterpretationBaseCode + 60;
|
||||
public const int CannotBindSchema = InterpretationBaseCode + 61;
|
||||
public const int TableTypeNotAllowFunctionCall = InterpretationBaseCode + 62;
|
||||
public const int ColumnNotAllowed = InterpretationBaseCode + 63;
|
||||
public const int OwnerRequiredForEndpoint = InterpretationBaseCode + 64;
|
||||
public const int PartitionNumberMustBeInteger = InterpretationBaseCode + 65;
|
||||
public const int DuplicatedPartitionNumber = InterpretationBaseCode + 66;
|
||||
public const int FromPartitionGreaterThanToPartition = InterpretationBaseCode + 67;
|
||||
public const int CannotSpecifyPartitionNumber = InterpretationBaseCode + 68;
|
||||
public const int MissingColumnNameError = InterpretationBaseCode + 69;
|
||||
public const int MissingColumnNameWarning = InterpretationBaseCode + 70;
|
||||
public const int UnknownTableSourceError = InterpretationBaseCode + 71;
|
||||
public const int UnknownTableSourceWarning = InterpretationBaseCode + 72;
|
||||
public const int TooManyPartsForCteOrAliasError = InterpretationBaseCode + 73;
|
||||
public const int TooManyPartsForCteOrAliasWarning = InterpretationBaseCode + 74;
|
||||
public const int ServerAuditInvalidQueueDelayValue = InterpretationBaseCode + 75;
|
||||
public const int WrongEventType = InterpretationBaseCode + 76;
|
||||
public const int CantCreateUddtFromXmlError = InterpretationBaseCode + 77;
|
||||
public const int CantCreateUddtFromXmlWarning = InterpretationBaseCode + 78;
|
||||
public const int CantCreateUddtFromUddtError = InterpretationBaseCode + 79;
|
||||
public const int CantCreateUddtFromUddtWarning = InterpretationBaseCode + 80;
|
||||
public const int ForReplicationIsNotSupported = InterpretationBaseCode + 81;
|
||||
public const int TooLongIdentifier = InterpretationBaseCode + 82;
|
||||
public const int InvalidLanguageTerm = InterpretationBaseCode + 83;
|
||||
public const int InvalidParameterOrOption = InterpretationBaseCode + 85;
|
||||
public const int TableLevelForeignKeyWithNoColumnsError = InterpretationBaseCode + 86;
|
||||
public const int TableLevelForeignKeyWithNoColumnsWarning = InterpretationBaseCode + 87;
|
||||
public const int ConstraintEnforcementIsIgnored = InterpretationBaseCode + 88;
|
||||
public const int DeprecatedBackupOption = InterpretationBaseCode + 89;
|
||||
public const int UndeclaredVariableParameter = InterpretationBaseCode + 90;
|
||||
public const int UnsupportedAlgorithm = InterpretationBaseCode + 91;
|
||||
public const int InvalidLanguageNameOrAliasWarning = InterpretationBaseCode + 92;
|
||||
public const int UnsupportedRevoke = InterpretationBaseCode + 93;
|
||||
public const int InvalidPermissionTypeAgainstObject = InterpretationBaseCode + 94;
|
||||
public const int InvalidPermissionObjectType = InterpretationBaseCode + 95;
|
||||
public const int CannotDetermineSecurableFromPermission = InterpretationBaseCode + 96;
|
||||
public const int InvalidColumnListForSecurableType = InterpretationBaseCode + 97;
|
||||
public const int InvalidUserDefaultLanguage = InterpretationBaseCode + 98;
|
||||
public const int CannotSpecifyGridParameterForAutoGridSpatialIndex = InterpretationBaseCode + 99;
|
||||
public const int UnsupportedSpatialTessellationScheme = InterpretationBaseCode + 100;
|
||||
public const int CannotSpecifyBoundingBoxForGeography = InterpretationBaseCode + 101;
|
||||
public const int InvalidSearchPropertyId = InterpretationBaseCode + 102;
|
||||
public const int OnlineSpatialIndex = InterpretationBaseCode + 103;
|
||||
public const int SqlCmdVariableInObjectName = InterpretationBaseCode + 104;
|
||||
public const int SubqueriesNotAllowed = InterpretationBaseCode + 105;
|
||||
public const int ArgumentReplaceNotSupported = InterpretationBaseCode + 106;
|
||||
public const int DuplicateArgument = InterpretationBaseCode + 107;
|
||||
public const int UnsupportedNoPopulationChangeTrackingOption = InterpretationBaseCode + 108;
|
||||
public const int UnsupportedResourceManagerLocationProperty = InterpretationBaseCode + 109;
|
||||
public const int RequiredExternalDataSourceLocationPropertyMissing = InterpretationBaseCode + 110;
|
||||
public const int UnsupportedSerdeMethodProperty = InterpretationBaseCode + 111;
|
||||
public const int UnsupportedFormatOptionsProperty = InterpretationBaseCode + 112;
|
||||
public const int RequiredSerdeMethodPropertyMissing = InterpretationBaseCode + 113;
|
||||
public const int TableLevelIndexWithNoColumnsError = InterpretationBaseCode + 114;
|
||||
public const int TableLevelIndexWithNoColumnsWarning = InterpretationBaseCode + 115;
|
||||
public const int InvalidIndexOption = InterpretationBaseCode + 116;
|
||||
public const int TypeAndSIDMustBeUsedTogether = InterpretationBaseCode + 117;
|
||||
public const int TypeCannotBeUsedWithLoginOption = InterpretationBaseCode + 118;
|
||||
public const int InvalidUserType = InterpretationBaseCode + 119;
|
||||
public const int InvalidUserSid = InterpretationBaseCode + 120;
|
||||
public const int InvalidPartitionFunctionDataType = InterpretationBaseCode + 121;
|
||||
public const int RequiredExternalTableLocationPropertyMissing = InterpretationBaseCode + 122;
|
||||
public const int UnsupportedRejectSampleValueProperty = InterpretationBaseCode + 123;
|
||||
public const int RequiredExternalDataSourceDatabasePropertyMissing = InterpretationBaseCode + 124;
|
||||
public const int RequiredExternalDataSourceShardMapNamePropertyMissing = InterpretationBaseCode + 125;
|
||||
public const int InvalidPropertyForExternalDataSourceType = InterpretationBaseCode + 126;
|
||||
public const int UnsupportedExternalDataSourceTypeInCurrentPlatform = InterpretationBaseCode + 127;
|
||||
public const int UnsupportedExternalTableProperty = InterpretationBaseCode + 128;
|
||||
public const int MaskingFunctionIsEmpty = InterpretationBaseCode + 129;
|
||||
public const int InvalidMaskingFunctionFormat = InterpretationBaseCode + 130;
|
||||
public const int CannotCreateAlwaysEncryptedObject = InterpretationBaseCode + 131;
|
||||
public const int ExternalTableSchemaOrObjectNameMissing = InterpretationBaseCode + 132;
|
||||
public const int CannotCreateTemporalTableWithoutHistoryTableName = InterpretationBaseCode + 133;
|
||||
public const int TemporalPeriodColumnMustNotBeNullable = InterpretationBaseCode + 134;
|
||||
public const int InterpretationEndCode = InterpretationBaseCode + 499;
|
||||
}
|
||||
|
||||
public static class ModelBuilder
|
||||
{
|
||||
private const int ModelBuilderBaseCode = 71000;
|
||||
|
||||
public const int CannotFindMainElement = ModelBuilderBaseCode + 1;
|
||||
public const int CannotFindColumnSourceGrantForColumnRevoke = ModelBuilderBaseCode + 2;
|
||||
public const int AssemblyReferencesNotSupported = ModelBuilderBaseCode + 3;
|
||||
public const int NoSourceForColumn = ModelBuilderBaseCode + 5;
|
||||
public const int MoreThanOneStatementPerBatch = ModelBuilderBaseCode + 6;
|
||||
public const int MaximumSizeExceeded = ModelBuilderBaseCode + 7;
|
||||
}
|
||||
|
||||
public static class Validation
|
||||
{
|
||||
private const int ValidationBaseCode = 71500;
|
||||
|
||||
public const int AllReferencesMustBeResolved = ValidationBaseCode + 1;
|
||||
public const int AllReferencesMustBeResolved_Warning = ValidationBaseCode + 2;
|
||||
public const int AssemblyVisibilityRule = ValidationBaseCode + 3;
|
||||
public const int BreakContinueOnlyInWhile = ValidationBaseCode + 4;
|
||||
public const int ClrObjectAssemblyReference_InvalidAssembly = ValidationBaseCode + 5;
|
||||
public const int ClrObjectAssemblyReference = ValidationBaseCode + 6;
|
||||
public const int ColumnUserDefinedTableType = ValidationBaseCode + 7;
|
||||
public const int DuplicateName = ValidationBaseCode + 8;
|
||||
public const int DuplicateName_Warning = ValidationBaseCode + 9;
|
||||
public const int DuplicateVariableParameterName_TemporaryTable = ValidationBaseCode + 10;
|
||||
public const int DuplicateVariableParameterName_Variable = ValidationBaseCode + 11;
|
||||
public const int EndPointRule_DATABASE_MIRRORING = ValidationBaseCode + 12;
|
||||
public const int EndPointRule_SERVICE_BROKER = ValidationBaseCode + 13;
|
||||
public const int ForeignKeyColumnTypeNumberMustMatch_NumberOfColumns = ValidationBaseCode + 14;
|
||||
public const int ForeignKeyColumnTypeNumberMustMatch_TypeMismatch = ValidationBaseCode + 15;
|
||||
public const int ForeignKeyReferencePKUnique = ValidationBaseCode + 16;
|
||||
public const int FullTextIndexColumn = ValidationBaseCode + 17;
|
||||
public const int IdentityColumnValidation_InvalidType = ValidationBaseCode + 18;
|
||||
public const int IdentityColumnValidation_MoreThanOneIdentity = ValidationBaseCode + 19;
|
||||
public const int InsertIntoIdentityColumn = ValidationBaseCode + 20;
|
||||
public const int MatchingSignatureNotFoundInAssembly = ValidationBaseCode + 21;
|
||||
public const int MatchingTypeNotFoundInAssembly = ValidationBaseCode + 22;
|
||||
public const int MaxColumnInIndexKey = ValidationBaseCode + 25;
|
||||
public const int MaxColumnInTable_1024Columns = ValidationBaseCode + 26;
|
||||
public const int MultiFullTextIndexOnTable = ValidationBaseCode + 28;
|
||||
public const int NonNullPrimaryKey_NonNullSimpleColumn = ValidationBaseCode + 29;
|
||||
public const int NonNullPrimaryKey_NotPersistedComputedColumn = ValidationBaseCode + 30;
|
||||
public const int OneClusteredIndex = ValidationBaseCode + 31;
|
||||
public const int OneMasterKey = ValidationBaseCode + 32;
|
||||
public const int OnePrimaryKey = ValidationBaseCode + 33;
|
||||
public const int PrimaryXMLIndexClustered = ValidationBaseCode + 34;
|
||||
public const int SelectAssignRetrieval = ValidationBaseCode + 35;
|
||||
public const int SubroutineParameterReadOnly_NonUDTTReadOnly = ValidationBaseCode + 36;
|
||||
public const int SubroutineParameterReadOnly_UDTTReadOnly = ValidationBaseCode + 37;
|
||||
public const int UsingXMLIndex = ValidationBaseCode + 38;
|
||||
public const int VardecimalOptionRule = ValidationBaseCode + 39;
|
||||
public const int WildCardExpansion = ValidationBaseCode + 40;
|
||||
public const int WildCardExpansion_Warning = ValidationBaseCode + 41;
|
||||
public const int XMLIndexOnlyXMLTypeColumn = ValidationBaseCode + 42;
|
||||
public const int TableVariablePrefix = ValidationBaseCode + 44;
|
||||
public const int FileStream_FILESTREAMON = ValidationBaseCode + 45;
|
||||
public const int FileStream_ROWGUIDCOLUMN = ValidationBaseCode + 46;
|
||||
public const int MaxColumnInTable100_Columns = ValidationBaseCode + 47;
|
||||
public const int XMLIndexOnlyXMLTypeColumn_SparseColumnSet = ValidationBaseCode + 48;
|
||||
public const int ClrObjectAssemblyReference_ParameterTypeMismatch = ValidationBaseCode + 50;
|
||||
public const int OneDefaultConstraintPerColumn = ValidationBaseCode + 51;
|
||||
public const int PermissionStatementValidation_DuplicatePermissionOnSecurable = ValidationBaseCode + 52;
|
||||
public const int PermissionStatementValidation_ConflictingPermissionsOnSecurable = ValidationBaseCode + 53;
|
||||
public const int PermissionStatementValidation_ConflictingColumnStatements = ValidationBaseCode + 54;
|
||||
public const int PermissionOnObjectSecurableValidation_InvalidPermissionForObject = ValidationBaseCode + 55;
|
||||
public const int SequenceValueValidation_ValueOutOfRange = ValidationBaseCode + 56;
|
||||
public const int SequenceValueValidation_InvalidDataType = ValidationBaseCode + 57;
|
||||
public const int MismatchedName_Warning = ValidationBaseCode + 58;
|
||||
public const int DifferentNameCasing_Warning = ValidationBaseCode + 59;
|
||||
public const int OneClusteredIndexAzure = ValidationBaseCode + 60;
|
||||
public const int AllExternalReferencesMustBeResolved = ValidationBaseCode + 61;
|
||||
public const int AllExternalReferencesMustBeResolved_Warning = ValidationBaseCode + 62;
|
||||
public const int ExternalObjectWildCardExpansion_Warning = ValidationBaseCode + 63;
|
||||
public const int UnsupportedElementForDataPackage = ValidationBaseCode + 64;
|
||||
public const int InvalidFileStreamOptions = ValidationBaseCode + 65;
|
||||
public const int StorageShouldNotSetOnDifferentInstance = ValidationBaseCode + 66;
|
||||
public const int TableShouldNotHaveStorage = ValidationBaseCode + 67;
|
||||
public static int MemoryOptimizedObjectsValidation_NonMemoryOptimizedTableCannotBeAccessed = ValidationBaseCode + 68;
|
||||
public static int MemoryOptimizedObjectsValidation_SyntaxNotSupportedOnHekatonElement = ValidationBaseCode + 69;
|
||||
public static int MemoryOptimizedObjectsValidation_ValidatePrimaryKeyForSchemaAndDataTables = ValidationBaseCode + 70;
|
||||
public static int MemoryOptimizedObjectsValidation_ValidatePrimaryKeyForSchemaOnlyTables = ValidationBaseCode + 71;
|
||||
public static int MemoryOptimizedObjectsValidation_OnlyNotNullableColumnsOnIndexes = ValidationBaseCode + 72;
|
||||
public static int MemoryOptimizedObjectsValidation_HashIndexesOnlyOnMemoryOptimizedObjects = ValidationBaseCode + 73;
|
||||
public static int MemoryOptimizedObjectsValidation_OptionOnlyForHashIndexes = ValidationBaseCode + 74;
|
||||
public static int IncrementalStatisticsValidation_FilterNotSupported = ValidationBaseCode + 75;
|
||||
public static int IncrementalStatisticsValidation_ViewNotSupported = ValidationBaseCode + 76;
|
||||
public static int IncrementalStatisticsValidation_IndexNotPartitionAligned = ValidationBaseCode + 77;
|
||||
public static int AzureV12SurfaceAreaValidation = ValidationBaseCode + 78;
|
||||
public static int DuplicatedTargetObjectReferencesInSecurityPolicy = ValidationBaseCode + 79;
|
||||
public static int MultipleSecurityPoliciesOnTargetObject = ValidationBaseCode + 80;
|
||||
public static int ExportedRowsMayBeIncomplete = ValidationBaseCode + 81;
|
||||
public static int ExportedRowsMayContainSomeMaskedData = ValidationBaseCode + 82;
|
||||
public const int EncryptedColumnValidation_EncryptedPrimaryKey = ValidationBaseCode + 83;
|
||||
public const int EncryptedColumnValidation_EncryptedUniqueColumn = ValidationBaseCode + 84;
|
||||
public const int EncryptedColumnValidation_EncryptedCheckConstraint = ValidationBaseCode + 85;
|
||||
public const int EncryptedColumnValidation_PrimaryKeyForeignKeyEncryptionMismatch = ValidationBaseCode + 86;
|
||||
public const int EncryptedColumnValidation_UnsupportedDataType = ValidationBaseCode + 87;
|
||||
public const int MemoryOptimizedObjectsValidation_UnSupportedOption = ValidationBaseCode + 88;
|
||||
public const int MasterKeyExistsForCredential = ValidationBaseCode + 89;
|
||||
public const int MemoryOptimizedObjectsValidation_InvalidForeignKeyRelationship = ValidationBaseCode + 90;
|
||||
public const int MemoryOptimizedObjectsValidation_UnsupportedForeignKeyReference = ValidationBaseCode + 91;
|
||||
public const int EncryptedColumnValidation_RowGuidColumn = ValidationBaseCode + 92;
|
||||
public const int EncryptedColumnValidation_EncryptedClusteredIndex = ValidationBaseCode + 93;
|
||||
public const int EncryptedColumnValidation_EncryptedNonClusteredIndex = ValidationBaseCode + 94;
|
||||
public const int EncryptedColumnValidation_DependentComputedColumn = ValidationBaseCode + 95;
|
||||
public const int EncryptedColumnValidation_EncryptedFullTextColumn = ValidationBaseCode + 96;
|
||||
public const int EncryptedColumnValidation_EncryptedSparseColumnSet = ValidationBaseCode + 97;
|
||||
public const int EncryptedColumnValidation_EncryptedStatisticsColumn = ValidationBaseCode + 98;
|
||||
public const int EncryptedColumnValidation_EncryptedPartitionColumn = ValidationBaseCode + 99;
|
||||
public const int EncryptedColumnValidation_PrimaryKeyChangeTrackingColumn = ValidationBaseCode + 100;
|
||||
public const int EncryptedColumnValidation_ChangeDataCaptureOn = ValidationBaseCode + 101;
|
||||
public const int EncryptedColumnValidation_FilestreamColumn = ValidationBaseCode + 102;
|
||||
public const int EncryptedColumnValidation_MemoryOptimizedTable = ValidationBaseCode + 103;
|
||||
public const int EncryptedColumnValidation_MaskedEncryptedColumn = ValidationBaseCode + 104;
|
||||
public const int EncryptedColumnValidation_EncryptedIdentityColumn = ValidationBaseCode + 105;
|
||||
public const int EncryptedColumnValidation_EncryptedDefaultConstraint = ValidationBaseCode + 106;
|
||||
public const int TemporalValidation_InvalidPeriodSpecification = ValidationBaseCode + 107;
|
||||
public const int TemporalValidation_MultipleCurrentTables = ValidationBaseCode + 108;
|
||||
public const int TemporalValidation_SchemaMismatch = ValidationBaseCode + 109;
|
||||
public const int TemporalValidation_ComputedColumns = ValidationBaseCode + 110;
|
||||
public const int TemporalValidation_NoAlwaysEncryptedCols = ValidationBaseCode + 111;
|
||||
public static int IndexesOnExternalTable = ValidationBaseCode + 112;
|
||||
public static int TriggersOnExternalTable = ValidationBaseCode + 113;
|
||||
public const int StretchValidation_ExportBlocked = ValidationBaseCode + 114;
|
||||
public const int StretchValidation_ImportBlocked = ValidationBaseCode + 115;
|
||||
public const int DeploymentBlocked = ValidationBaseCode + 116;
|
||||
public const int NoBlockPredicatesTargetingViews = ValidationBaseCode + 117;
|
||||
public const int SchemaBindingOnSecurityPoliciesValidation = ValidationBaseCode + 118;
|
||||
public const int SecurityPredicateTargetObjectValidation = ValidationBaseCode + 119;
|
||||
public const int TemporalValidation_SchemaMismatch_ColumnCount = ValidationBaseCode + 120;
|
||||
public const int AkvValidation_AuthenticationFailed = ValidationBaseCode + 121;
|
||||
public const int TemporalValidation_PrimaryKey = ValidationBaseCode + 122;
|
||||
}
|
||||
|
||||
public static class SqlMSBuild
|
||||
{
|
||||
private const int MSBuildBaseCode = 72000;
|
||||
|
||||
public const int FileDoesNotExist = MSBuildBaseCode + 1;
|
||||
public const int UnknownDeployError = MSBuildBaseCode + 2;
|
||||
public const int InvalidProperty = MSBuildBaseCode + 3;
|
||||
public const int CollationError = MSBuildBaseCode + 4;
|
||||
public const int InvalidSqlClrDefinition = MSBuildBaseCode + 5;
|
||||
public const int SQL_PrePostFatalParserError = MSBuildBaseCode + 6;
|
||||
public const int SQL_PrePostSyntaxCheckError = MSBuildBaseCode + 7;
|
||||
public const int SQL_PrePostVariableError = MSBuildBaseCode + 8;
|
||||
public const int SQL_CycleError = MSBuildBaseCode + 9;
|
||||
public const int SQL_NoConnectionStringNoServerVerification = MSBuildBaseCode + 10;
|
||||
public const int SQL_VardecimalMismatch = MSBuildBaseCode + 11;
|
||||
public const int SQL_NoAlterFileSystemObject = MSBuildBaseCode + 12;
|
||||
public const int SQL_SqlCmdVariableOverrideError = MSBuildBaseCode + 13;
|
||||
public const int SQL_BatchError = MSBuildBaseCode + 14;
|
||||
public const int SQL_DataLossError = MSBuildBaseCode + 15;
|
||||
public const int SQL_ExecutionError = MSBuildBaseCode + 16;
|
||||
public const int SQL_UncheckedConstraint = MSBuildBaseCode + 17;
|
||||
public const int SQL_UnableToImportElements = MSBuildBaseCode + 18;
|
||||
public const int SQL_TargetReadOnlyError = MSBuildBaseCode + 19;
|
||||
public const int SQL_UnsupportedCompatibilityMode = MSBuildBaseCode + 20;
|
||||
public const int SQL_IncompatibleDSPVersions = MSBuildBaseCode + 21;
|
||||
public const int SQL_CouldNotLoadSymbols = MSBuildBaseCode + 22;
|
||||
public const int SQL_ContainmentlMismatch = MSBuildBaseCode + 23;
|
||||
public const int SQL_PrePostExpectedNoTSqlError = MSBuildBaseCode + 24;
|
||||
public const int ReferenceErrorCode = MSBuildBaseCode + 25;
|
||||
public const int FileError = MSBuildBaseCode + 26;
|
||||
public const int MissingReference = MSBuildBaseCode + 27;
|
||||
public const int SerializationError = MSBuildBaseCode + 28;
|
||||
public const int DeploymentContributorVerificationError = MSBuildBaseCode + 29;
|
||||
public const int Deployment_PossibleRuntimeError = MSBuildBaseCode + 30;
|
||||
public const int Deployment_BlockingDependency = MSBuildBaseCode + 31;
|
||||
public const int Deployment_TargetObjectLoss = MSBuildBaseCode + 32;
|
||||
public const int Deployment_MissingDependency = MSBuildBaseCode + 33;
|
||||
public const int Deployment_PossibleDataLoss = MSBuildBaseCode + 34;
|
||||
public const int Deployment_NotSupportedOperation = MSBuildBaseCode + 35;
|
||||
public const int Deployment_Information = MSBuildBaseCode + 36;
|
||||
public const int Deployment_UnsupportedDSP = MSBuildBaseCode + 37;
|
||||
public const int Deployment_SkipManagementScopedChange = MSBuildBaseCode + 38;
|
||||
public const int StaticCodeAnalysis_GeneralException = MSBuildBaseCode + 39;
|
||||
public const int StaticCodeAnalysis_ResultsFileIOException = MSBuildBaseCode + 40;
|
||||
public const int StaticCodeAnalysis_FailToCreateTaskHost = MSBuildBaseCode + 41;
|
||||
public const int StaticCodeAnalysis_InvalidDataSchemaModel = MSBuildBaseCode + 42;
|
||||
public const int StaticCodeAnalysis_InvalidElement = MSBuildBaseCode + 43;
|
||||
public const int Deployment_NoClusteredIndex = MSBuildBaseCode + 44;
|
||||
public const int Deployment_DetailedScriptExecutionError = MSBuildBaseCode + 45;
|
||||
}
|
||||
|
||||
public static class Refactoring
|
||||
{
|
||||
private const int RefactoringBaseCode = 72500;
|
||||
|
||||
public const int FailedToLoadFile = RefactoringBaseCode + 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// These codes are used to message specific actions for extract and deployment operations.
|
||||
/// The primary consumer of these codes is the Import/Export service.
|
||||
/// </summary>
|
||||
public static class ServiceActions
|
||||
{
|
||||
public const int ServiceActionsBaseCode = 73000;
|
||||
public const int ServiceActionsMaxCode = 73000 + 0xFF;
|
||||
|
||||
// Note: These codes are defined so that the lower 3 bits indicate one of three
|
||||
// event stages: Started (0x01), Done/Complete (0x02), Done/Failed (0x04)
|
||||
public const int DeployInitializeStart = ServiceActionsBaseCode + 0x01;
|
||||
public const int DeployInitializeSuccess = ServiceActionsBaseCode + 0x02;
|
||||
public const int DeployInitializeFailure = ServiceActionsBaseCode + 0x04;
|
||||
|
||||
public const int DeployAnalysisStart = ServiceActionsBaseCode + 0x11;
|
||||
public const int DeployAnalysisSuccess = ServiceActionsBaseCode + 0x12;
|
||||
public const int DeployAnalysisFailure = ServiceActionsBaseCode + 0x14;
|
||||
|
||||
public const int DeployExecuteScriptStart = ServiceActionsBaseCode + 0x21;
|
||||
public const int DeployExecuteScriptSuccess = ServiceActionsBaseCode + 0x22;
|
||||
public const int DeployExecuteScriptFailure = ServiceActionsBaseCode + 0x24;
|
||||
|
||||
public const int DataImportStart = ServiceActionsBaseCode + 0x41;
|
||||
public const int DataImportSuccess = ServiceActionsBaseCode + 0x42;
|
||||
public const int DataImportFailure = ServiceActionsBaseCode + 0x44;
|
||||
|
||||
public const int ExtractSchemaStart = ServiceActionsBaseCode + 0x61;
|
||||
public const int ExtractSchemaSuccess = ServiceActionsBaseCode + 0x62;
|
||||
public const int ExtractSchemaFailure = ServiceActionsBaseCode + 0x64;
|
||||
|
||||
public const int ExportVerifyStart = ServiceActionsBaseCode + 0x71;
|
||||
public const int ExportVerifySuccess = ServiceActionsBaseCode + 0x72;
|
||||
public const int ExportVerifyFailure = ServiceActionsBaseCode + 0x74;
|
||||
|
||||
public const int ExportDataStart = ServiceActionsBaseCode + 0x81;
|
||||
public const int ExportDataSuccess = ServiceActionsBaseCode + 0x82;
|
||||
public const int ExportDataFailure = ServiceActionsBaseCode + 0x84;
|
||||
|
||||
public const int EnableIndexesDataStart = ServiceActionsBaseCode + 0xb1;
|
||||
public const int EnableIndexesDataSuccess = ServiceActionsBaseCode + 0xb2;
|
||||
public const int EnableIndexesDataFailure = ServiceActionsBaseCode + 0xb4;
|
||||
|
||||
public const int DisableIndexesDataStart = ServiceActionsBaseCode + 0xc1;
|
||||
public const int DisableIndexesDataSuccess = ServiceActionsBaseCode + 0xc2;
|
||||
public const int DisableIndexesDataFailure = ServiceActionsBaseCode + 0xc4;
|
||||
|
||||
public const int EnableIndexDataStart = ServiceActionsBaseCode + 0xd1;
|
||||
public const int EnableIndexDataSuccess = ServiceActionsBaseCode + 0xd2;
|
||||
public const int EnableIndexDataFailure = ServiceActionsBaseCode + 0xd4;
|
||||
|
||||
public const int DisableIndexDataStart = ServiceActionsBaseCode + 0xe1;
|
||||
public const int DisableIndexDataSuccess = ServiceActionsBaseCode + 0xe2;
|
||||
public const int DisableIndexDataFailure = ServiceActionsBaseCode + 0xe4;
|
||||
|
||||
public const int ColumnEncryptionDataMigrationStart = ServiceActionsBaseCode + 0xf1;
|
||||
public const int ColumnEncryptionDataMigrationSuccess = ServiceActionsBaseCode + 0xf2;
|
||||
public const int ColumnEncryptionDataMigrationFailure = ServiceActionsBaseCode + 0xf4;
|
||||
|
||||
// These codes do not set the lower 3 bits
|
||||
public const int ConnectionRetry = ServiceActionsBaseCode + 0x90;
|
||||
public const int CommandRetry = ServiceActionsBaseCode + 0x91;
|
||||
public const int GeneralProgress = ServiceActionsBaseCode + 0x92;
|
||||
public const int TypeFidelityLoss = ServiceActionsBaseCode + 0x93;
|
||||
public const int TableProgress = ServiceActionsBaseCode + 0x94;
|
||||
public const int ImportBlocked = ServiceActionsBaseCode + 0x95;
|
||||
public const int DataPrecisionLoss = ServiceActionsBaseCode + 0x96;
|
||||
public const int DataRowCount = ServiceActionsBaseCode + 0x98;
|
||||
|
||||
public const int DataException = ServiceActionsBaseCode + 0xA0;
|
||||
public const int LogEntry = ServiceActionsBaseCode + 0xA1;
|
||||
public const int GeneralInfo = ServiceActionsBaseCode + 0xA2;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an error produced by SQL Server database schema provider
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
internal class SqlServerError : DataSchemaError
|
||||
{
|
||||
private const string SqlServerPrefix = "SQL";
|
||||
private const string DefaultHelpKeyword = "vs.teamsystem.datatools.DefaultErrorMessageHelp";
|
||||
|
||||
public SqlServerError(string message, string document, ErrorSeverity severity)
|
||||
: this(message, null, document, 0, 0, Constants.UndefinedErrorCode, severity)
|
||||
{
|
||||
}
|
||||
|
||||
public SqlServerError(string message, string document, int errorCode, ErrorSeverity severity)
|
||||
: this(message, null, document, 0, 0, errorCode, severity)
|
||||
{
|
||||
}
|
||||
|
||||
public SqlServerError(Exception exception, string document, int errorCode, ErrorSeverity severity)
|
||||
: this(exception, document, 0, 0, errorCode, severity)
|
||||
{
|
||||
}
|
||||
|
||||
public SqlServerError(string message, string document, int line, int column, ErrorSeverity severity)
|
||||
: this(message, null, document, line, column, Constants.UndefinedErrorCode, severity)
|
||||
{
|
||||
}
|
||||
|
||||
public SqlServerError(
|
||||
Exception exception,
|
||||
string document,
|
||||
int line,
|
||||
int column,
|
||||
int errorCode,
|
||||
ErrorSeverity severity) :
|
||||
this(exception.Message, exception, document, line, column, errorCode, severity)
|
||||
{
|
||||
}
|
||||
|
||||
public SqlServerError(
|
||||
string message,
|
||||
string document,
|
||||
int line,
|
||||
int column,
|
||||
int errorCode,
|
||||
ErrorSeverity severity) :
|
||||
this(message, null, document, line, column, errorCode, severity)
|
||||
{
|
||||
}
|
||||
|
||||
public SqlServerError(
|
||||
string message,
|
||||
Exception exception,
|
||||
string document,
|
||||
int line,
|
||||
int column,
|
||||
int errorCode,
|
||||
ErrorSeverity severity) :
|
||||
base(message, exception, document, line, column, SqlServerPrefix, errorCode, severity)
|
||||
{
|
||||
this.HelpKeyword = DefaultHelpKeyword;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
|
||||
{
|
||||
/// <summary>
|
||||
/// Captures extended information about a specific error and a retry
|
||||
/// </summary>
|
||||
internal class SqlServerRetryError : SqlServerError
|
||||
{
|
||||
private int _retryCount;
|
||||
private int _errorCode;
|
||||
|
||||
public SqlServerRetryError(string message, Exception ex, int retryCount, int errorCode, ErrorSeverity severity)
|
||||
: base(ex, message, errorCode, severity)
|
||||
{
|
||||
_retryCount = retryCount;
|
||||
_errorCode = errorCode;
|
||||
}
|
||||
|
||||
public int RetryCount
|
||||
{
|
||||
get { return _retryCount; }
|
||||
}
|
||||
|
||||
public static string FormatRetryMessage(int retryCount, TimeSpan delay, Exception transientException)
|
||||
{
|
||||
string message = string.Format(
|
||||
CultureInfo.CurrentCulture,
|
||||
Resources.RetryOnException,
|
||||
retryCount,
|
||||
delay.TotalMilliseconds.ToString(CultureInfo.CurrentCulture),
|
||||
transientException.ToString());
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
public static string FormatIgnoreMessage(int retryCount, Exception exception)
|
||||
{
|
||||
string message = string.Format(
|
||||
CultureInfo.CurrentCulture,
|
||||
Resources.IgnoreOnException,
|
||||
retryCount,
|
||||
exception.ToString());
|
||||
|
||||
return message;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
//
|
||||
|
||||
using System.Data.Common;
|
||||
using System.Data.SqlClient;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Connection
|
||||
{
|
||||
@@ -20,7 +20,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
|
||||
/// </summary>
|
||||
public DbConnection CreateSqlConnection(string connectionString)
|
||||
{
|
||||
return new SqlConnection(connectionString);
|
||||
RetryPolicy connectionRetryPolicy = RetryPolicyFactory.CreateDefaultConnectionRetryPolicy();
|
||||
RetryPolicy commandRetryPolicy = RetryPolicyFactory.CreateDefaultConnectionRetryPolicy();
|
||||
return new ReliableSqlConnection(connectionString, connectionRetryPolicy, commandRetryPolicy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,13 +136,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Hosting
|
||||
TextDocumentSync = TextDocumentSyncKind.Incremental,
|
||||
DefinitionProvider = true,
|
||||
ReferencesProvider = true,
|
||||
DocumentHighlightProvider = true,
|
||||
DocumentSymbolProvider = true,
|
||||
WorkspaceSymbolProvider = true,
|
||||
DocumentHighlightProvider = true,
|
||||
CompletionProvider = new CompletionOptions
|
||||
{
|
||||
ResolveProvider = true,
|
||||
TriggerCharacters = new string[] { ".", "-", ":", "\\" }
|
||||
TriggerCharacters = new string[] { ".", "-", ":", "\\", ",", " " }
|
||||
},
|
||||
SignatureHelpProvider = new SignatureHelpOptions
|
||||
{
|
||||
|
||||
@@ -0,0 +1,514 @@
|
||||
//
|
||||
// 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.SqlServer.Management.SqlParser.Intellisense;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
{
|
||||
/// <summary>
|
||||
/// Main class for Language Service functionality including anything that reqires knowledge of
|
||||
/// the language to perfom, such as definitions, intellisense, etc.
|
||||
/// </summary>
|
||||
public static class AutoCompleteHelper
|
||||
{
|
||||
private static readonly string[] DefaultCompletionText = new string[]
|
||||
{
|
||||
"absolute",
|
||||
"accent_sensitivity",
|
||||
"action",
|
||||
"activation",
|
||||
"add",
|
||||
"address",
|
||||
"admin",
|
||||
"after",
|
||||
"aggregate",
|
||||
"algorithm",
|
||||
"allow_page_locks",
|
||||
"allow_row_locks",
|
||||
"allow_snapshot_isolation",
|
||||
"alter",
|
||||
"always",
|
||||
"ansi_null_default",
|
||||
"ansi_nulls",
|
||||
"ansi_padding",
|
||||
"ansi_warnings",
|
||||
"application",
|
||||
"arithabort",
|
||||
"as",
|
||||
"asc",
|
||||
"assembly",
|
||||
"asymmetric",
|
||||
"at",
|
||||
"atomic",
|
||||
"audit",
|
||||
"authentication",
|
||||
"authorization",
|
||||
"auto",
|
||||
"auto_close",
|
||||
"auto_shrink",
|
||||
"auto_update_statistics",
|
||||
"auto_update_statistics_async",
|
||||
"availability",
|
||||
"backup",
|
||||
"before",
|
||||
"begin",
|
||||
"binary",
|
||||
"bit",
|
||||
"block",
|
||||
"break",
|
||||
"browse",
|
||||
"bucket_count",
|
||||
"bulk",
|
||||
"by",
|
||||
"call",
|
||||
"caller",
|
||||
"card",
|
||||
"cascade",
|
||||
"case",
|
||||
"catalog",
|
||||
"catch",
|
||||
"change_tracking",
|
||||
"changes",
|
||||
"char",
|
||||
"character",
|
||||
"check",
|
||||
"checkpoint",
|
||||
"close",
|
||||
"clustered",
|
||||
"collection",
|
||||
"column",
|
||||
"column_encryption_key",
|
||||
"columnstore",
|
||||
"commit",
|
||||
"compatibility_level",
|
||||
"compress_all_row_groups",
|
||||
"compression",
|
||||
"compression_delay",
|
||||
"compute",
|
||||
"concat_null_yields_null",
|
||||
"configuration",
|
||||
"connect",
|
||||
"constraint",
|
||||
"containstable",
|
||||
"continue",
|
||||
"create",
|
||||
"cube",
|
||||
"current",
|
||||
"current_date",
|
||||
"cursor",
|
||||
"cursor_close_on_commit",
|
||||
"cursor_default",
|
||||
"data",
|
||||
"data_compression",
|
||||
"database",
|
||||
"date",
|
||||
"date_correlation_optimization",
|
||||
"datefirst",
|
||||
"datetime",
|
||||
"datetime2",
|
||||
"days",
|
||||
"db_chaining",
|
||||
"dbcc",
|
||||
"deallocate",
|
||||
"dec",
|
||||
"decimal",
|
||||
"declare",
|
||||
"default",
|
||||
"delayed_durability",
|
||||
"delete",
|
||||
"deny",
|
||||
"desc",
|
||||
"description",
|
||||
"disable_broker",
|
||||
"disabled",
|
||||
"disk",
|
||||
"distinct",
|
||||
"distributed",
|
||||
"double",
|
||||
"drop",
|
||||
"drop_existing",
|
||||
"dump",
|
||||
"durability",
|
||||
"dynamic",
|
||||
"else",
|
||||
"enable",
|
||||
"encrypted",
|
||||
"encryption_type",
|
||||
"end",
|
||||
"end-exec",
|
||||
"entry",
|
||||
"errlvl",
|
||||
"escape",
|
||||
"event",
|
||||
"except",
|
||||
"exec",
|
||||
"execute",
|
||||
"exit",
|
||||
"external",
|
||||
"fast_forward",
|
||||
"fetch",
|
||||
"file",
|
||||
"filegroup",
|
||||
"filename",
|
||||
"filestream",
|
||||
"fillfactor",
|
||||
"filter",
|
||||
"first",
|
||||
"float",
|
||||
"for",
|
||||
"foreign",
|
||||
"freetext",
|
||||
"freetexttable",
|
||||
"from",
|
||||
"full",
|
||||
"fullscan",
|
||||
"fulltext",
|
||||
"function",
|
||||
"generated",
|
||||
"geography",
|
||||
"get",
|
||||
"global",
|
||||
"go",
|
||||
"goto",
|
||||
"grant",
|
||||
"group",
|
||||
"hash",
|
||||
"hashed",
|
||||
"having",
|
||||
"hidden",
|
||||
"hierarchyid",
|
||||
"holdlock",
|
||||
"hours",
|
||||
"identity",
|
||||
"identity_insert",
|
||||
"identitycol",
|
||||
"if",
|
||||
"ignore_dup_key",
|
||||
"image",
|
||||
"immediate",
|
||||
"include",
|
||||
"index",
|
||||
"inflectional",
|
||||
"insensitive",
|
||||
"insert",
|
||||
"instead",
|
||||
"int",
|
||||
"integer",
|
||||
"integrated",
|
||||
"intersect",
|
||||
"into",
|
||||
"isolation",
|
||||
"json",
|
||||
"key",
|
||||
"kill",
|
||||
"language",
|
||||
"last",
|
||||
"legacy_cardinality_estimation",
|
||||
"level",
|
||||
"lineno",
|
||||
"load",
|
||||
"local",
|
||||
"locate",
|
||||
"location",
|
||||
"login",
|
||||
"masked",
|
||||
"master",
|
||||
"maxdop",
|
||||
"memory_optimized",
|
||||
"merge",
|
||||
"message",
|
||||
"modify",
|
||||
"move",
|
||||
"multi_user",
|
||||
"namespace",
|
||||
"national",
|
||||
"native_compilation",
|
||||
"nchar",
|
||||
"next",
|
||||
"no",
|
||||
"nocheck",
|
||||
"nocount",
|
||||
"nonclustered",
|
||||
"none",
|
||||
"norecompute",
|
||||
"now",
|
||||
"numeric",
|
||||
"numeric_roundabort",
|
||||
"object",
|
||||
"of",
|
||||
"off",
|
||||
"offsets",
|
||||
"on",
|
||||
"online",
|
||||
"open",
|
||||
"opendatasource",
|
||||
"openquery",
|
||||
"openrowset",
|
||||
"openxml",
|
||||
"option",
|
||||
"order",
|
||||
"out",
|
||||
"output",
|
||||
"over",
|
||||
"owner",
|
||||
"pad_index",
|
||||
"page",
|
||||
"page_verify",
|
||||
"parameter_sniffing",
|
||||
"parameterization",
|
||||
"partial",
|
||||
"partition",
|
||||
"password",
|
||||
"path",
|
||||
"percent",
|
||||
"percentage",
|
||||
"period",
|
||||
"persisted",
|
||||
"plan",
|
||||
"policy",
|
||||
"population",
|
||||
"precision",
|
||||
"predicate",
|
||||
"primary",
|
||||
"print",
|
||||
"prior",
|
||||
"proc",
|
||||
"procedure",
|
||||
"public",
|
||||
"query_optimizer_hotfixes",
|
||||
"query_store",
|
||||
"quoted_identifier",
|
||||
"raiserror",
|
||||
"range",
|
||||
"raw",
|
||||
"read",
|
||||
"read_committed_snapshot",
|
||||
"read_only",
|
||||
"read_write",
|
||||
"readonly",
|
||||
"readtext",
|
||||
"real",
|
||||
"rebuild",
|
||||
"receive",
|
||||
"reconfigure",
|
||||
"recovery",
|
||||
"recursive",
|
||||
"recursive_triggers",
|
||||
"references",
|
||||
"relative",
|
||||
"remove",
|
||||
"reorganize",
|
||||
"replication",
|
||||
"required",
|
||||
"restart",
|
||||
"restore",
|
||||
"restrict",
|
||||
"resume",
|
||||
"return",
|
||||
"returns",
|
||||
"revert",
|
||||
"revoke",
|
||||
"role",
|
||||
"rollback",
|
||||
"rollup",
|
||||
"row",
|
||||
"rowcount",
|
||||
"rowguidcol",
|
||||
"rows",
|
||||
"rule",
|
||||
"sample",
|
||||
"save",
|
||||
"schema",
|
||||
"schemabinding",
|
||||
"scoped",
|
||||
"scroll",
|
||||
"secondary",
|
||||
"security",
|
||||
"securityaudit",
|
||||
"select",
|
||||
"semantickeyphrasetable",
|
||||
"semanticsimilaritydetailstable",
|
||||
"semanticsimilaritytable",
|
||||
"send",
|
||||
"sent",
|
||||
"sequence",
|
||||
"server",
|
||||
"session",
|
||||
"set",
|
||||
"sets",
|
||||
"setuser",
|
||||
"shutdown",
|
||||
"simple",
|
||||
"smallint",
|
||||
"smallmoney",
|
||||
"snapshot",
|
||||
"sort_in_tempdb",
|
||||
"sql",
|
||||
"standard",
|
||||
"start",
|
||||
"started",
|
||||
"state",
|
||||
"statement",
|
||||
"static",
|
||||
"statistics",
|
||||
"statistics_norecompute",
|
||||
"status",
|
||||
"stopped",
|
||||
"supported",
|
||||
"symmetric",
|
||||
"sysname",
|
||||
"system",
|
||||
"system_time",
|
||||
"system_versioning",
|
||||
"table",
|
||||
"tablesample",
|
||||
"take",
|
||||
"target",
|
||||
"textimage_on",
|
||||
"textsize",
|
||||
"then",
|
||||
"thesaurus",
|
||||
"throw",
|
||||
"time",
|
||||
"timestamp",
|
||||
"tinyint",
|
||||
"to",
|
||||
"top",
|
||||
"tran",
|
||||
"transaction",
|
||||
"trigger",
|
||||
"truncate",
|
||||
"trustworthy",
|
||||
"try",
|
||||
"tsql",
|
||||
"type",
|
||||
"union",
|
||||
"unique",
|
||||
"uniqueidentifier",
|
||||
"unlimited",
|
||||
"updatetext",
|
||||
"use",
|
||||
"user",
|
||||
"using",
|
||||
"value",
|
||||
"values",
|
||||
"varchar",
|
||||
"varying",
|
||||
"version",
|
||||
"view",
|
||||
"waitfor",
|
||||
"weight",
|
||||
"when",
|
||||
"where",
|
||||
"while",
|
||||
"with",
|
||||
"within",
|
||||
"within group",
|
||||
"without",
|
||||
"writetext",
|
||||
"xact_abort",
|
||||
"xml",
|
||||
"zone"
|
||||
};
|
||||
|
||||
internal static CompletionItem[] GetDefaultCompletionItems(
|
||||
int row,
|
||||
int startColumn,
|
||||
int endColumn)
|
||||
{
|
||||
var completionItems = new CompletionItem[DefaultCompletionText.Length];
|
||||
for (int i = 0; i < DefaultCompletionText.Length; ++i)
|
||||
{
|
||||
completionItems[i] = CreateDefaultCompletionItem(
|
||||
DefaultCompletionText[i].ToUpper(),
|
||||
row,
|
||||
startColumn,
|
||||
endColumn);
|
||||
}
|
||||
return completionItems;
|
||||
}
|
||||
|
||||
private static CompletionItem CreateDefaultCompletionItem(
|
||||
string label,
|
||||
int row,
|
||||
int startColumn,
|
||||
int endColumn)
|
||||
{
|
||||
return new CompletionItem()
|
||||
{
|
||||
Label = label,
|
||||
Kind = CompletionItemKind.Keyword,
|
||||
Detail = label + " keyword",
|
||||
TextEdit = new TextEdit
|
||||
{
|
||||
NewText = label,
|
||||
Range = new Range
|
||||
{
|
||||
Start = new Position
|
||||
{
|
||||
Line = row,
|
||||
Character = startColumn
|
||||
},
|
||||
End = new Position
|
||||
{
|
||||
Line = row,
|
||||
Character = endColumn
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a list of Declaration objects to CompletionItem objects
|
||||
/// since VS Code expects CompletionItems but SQL Parser works with Declarations
|
||||
/// </summary>
|
||||
/// <param name="suggestions"></param>
|
||||
/// <param name="cursorRow"></param>
|
||||
/// <param name="cursorColumn"></param>
|
||||
/// <returns></returns>
|
||||
internal static CompletionItem[] ConvertDeclarationsToCompletionItems(
|
||||
IEnumerable<Declaration> suggestions,
|
||||
int row,
|
||||
int startColumn,
|
||||
int endColumn)
|
||||
{
|
||||
List<CompletionItem> completions = new List<CompletionItem>();
|
||||
foreach (var autoCompleteItem in suggestions)
|
||||
{
|
||||
// convert the completion item candidates into CompletionItems
|
||||
completions.Add(new CompletionItem()
|
||||
{
|
||||
Label = autoCompleteItem.Title,
|
||||
Kind = CompletionItemKind.Variable,
|
||||
Detail = autoCompleteItem.Title,
|
||||
Documentation = autoCompleteItem.Description,
|
||||
TextEdit = new TextEdit
|
||||
{
|
||||
NewText = autoCompleteItem.Title,
|
||||
Range = new Range
|
||||
{
|
||||
Start = new Position
|
||||
{
|
||||
Line = row,
|
||||
Character = startColumn
|
||||
},
|
||||
End = new Position
|
||||
{
|
||||
Line = row,
|
||||
Character = endColumn
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return completions.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,323 +0,0 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.SqlClient;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlServer.Management.Common;
|
||||
using Microsoft.SqlServer.Management.SmoMetadataProvider;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Binder;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
|
||||
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
{
|
||||
/// <summary>
|
||||
/// Main class for Autocomplete functionality
|
||||
/// </summary>
|
||||
public class AutoCompleteService
|
||||
{
|
||||
#region Singleton Instance Implementation
|
||||
|
||||
/// <summary>
|
||||
/// Singleton service instance
|
||||
/// </summary>
|
||||
private static Lazy<AutoCompleteService> instance
|
||||
= new Lazy<AutoCompleteService>(() => new AutoCompleteService());
|
||||
|
||||
/// <summary>
|
||||
/// Gets the singleton service instance
|
||||
/// </summary>
|
||||
public static AutoCompleteService Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
return instance.Value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default, parameterless constructor.
|
||||
/// Internal constructor for use in test cases only
|
||||
/// </summary>
|
||||
internal AutoCompleteService()
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private ConnectionService connectionService = null;
|
||||
|
||||
/// <summary>
|
||||
/// Internal for testing purposes only
|
||||
/// </summary>
|
||||
internal ConnectionService ConnectionServiceInstance
|
||||
{
|
||||
get
|
||||
{
|
||||
if(connectionService == null)
|
||||
{
|
||||
connectionService = ConnectionService.Instance;
|
||||
}
|
||||
return connectionService;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
connectionService = value;
|
||||
}
|
||||
}
|
||||
|
||||
public void InitializeService(ServiceHost serviceHost)
|
||||
{
|
||||
// Register auto-complete request handler
|
||||
serviceHost.SetRequestHandler(CompletionRequest.Type, HandleCompletionRequest);
|
||||
|
||||
// Register a callback for when a connection is created
|
||||
ConnectionServiceInstance.RegisterOnConnectionTask(UpdateAutoCompleteCache);
|
||||
|
||||
// Register a callback for when a connection is closed
|
||||
ConnectionServiceInstance.RegisterOnDisconnectTask(RemoveAutoCompleteCacheUriReference);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Auto-complete completion provider request callback
|
||||
/// </summary>
|
||||
/// <param name="textDocumentPosition"></param>
|
||||
/// <param name="requestContext"></param>
|
||||
/// <returns></returns>
|
||||
private static async Task HandleCompletionRequest(
|
||||
TextDocumentPosition textDocumentPosition,
|
||||
RequestContext<CompletionItem[]> requestContext)
|
||||
{
|
||||
// get the current list of completion items and return to client
|
||||
var scriptFile = WorkspaceService<SqlToolsSettings>.Instance.Workspace.GetFile(
|
||||
textDocumentPosition.TextDocument.Uri);
|
||||
|
||||
ConnectionInfo connInfo;
|
||||
ConnectionService.Instance.TryFindConnection(
|
||||
scriptFile.ClientFilePath,
|
||||
out connInfo);
|
||||
|
||||
var completionItems = Instance.GetCompletionItems(
|
||||
textDocumentPosition, scriptFile, connInfo);
|
||||
|
||||
await requestContext.SendResult(completionItems);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a reference to an autocomplete cache from a URI. If
|
||||
/// it is the last URI connected to a particular connection,
|
||||
/// then remove the cache.
|
||||
/// </summary>
|
||||
public async Task RemoveAutoCompleteCacheUriReference(ConnectionSummary summary)
|
||||
{
|
||||
// currently this method is disabled, but we need to reimplement now that the
|
||||
// implementation of the 'cache' has changed.
|
||||
await Task.FromResult(0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the cached autocomplete candidate list when the user connects to a database
|
||||
/// </summary>
|
||||
/// <param name="info"></param>
|
||||
public async Task UpdateAutoCompleteCache(ConnectionInfo info)
|
||||
{
|
||||
await Task.Run( () =>
|
||||
{
|
||||
if (!LanguageService.Instance.ScriptParseInfoMap.ContainsKey(info.OwnerUri))
|
||||
{
|
||||
var sqlConn = info.SqlConnection as SqlConnection;
|
||||
if (sqlConn != null)
|
||||
{
|
||||
var srvConn = new ServerConnection(sqlConn);
|
||||
var displayInfoProvider = new MetadataDisplayInfoProvider();
|
||||
var metadataProvider = SmoMetadataProvider.CreateConnectedProvider(srvConn);
|
||||
var binder = BinderProvider.CreateBinder(metadataProvider);
|
||||
|
||||
LanguageService.Instance.ScriptParseInfoMap.Add(info.OwnerUri,
|
||||
new ScriptParseInfo()
|
||||
{
|
||||
Binder = binder,
|
||||
MetadataProvider = metadataProvider,
|
||||
MetadataDisplayInfoProvider = displayInfoProvider
|
||||
});
|
||||
|
||||
var scriptFile = WorkspaceService<SqlToolsSettings>.Instance.Workspace.GetFile(info.OwnerUri);
|
||||
|
||||
LanguageService.Instance.ParseAndBind(scriptFile, info);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find the position of the previous delimeter for autocomplete token replacement.
|
||||
/// SQL Parser may have similar functionality in which case we'll delete this method.
|
||||
/// </summary>
|
||||
/// <param name="sql"></param>
|
||||
/// <param name="startRow"></param>
|
||||
/// <param name="startColumn"></param>
|
||||
/// <returns></returns>
|
||||
private int PositionOfPrevDelimeter(string sql, int startRow, int startColumn)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(sql))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
int prevLineColumns = 0;
|
||||
for (int i = 0; i < startRow; ++i)
|
||||
{
|
||||
while (sql[prevLineColumns] != '\n' && prevLineColumns < sql.Length)
|
||||
{
|
||||
++prevLineColumns;
|
||||
}
|
||||
++prevLineColumns;
|
||||
}
|
||||
|
||||
startColumn += prevLineColumns;
|
||||
|
||||
if (startColumn - 1 < sql.Length)
|
||||
{
|
||||
while (--startColumn >= prevLineColumns)
|
||||
{
|
||||
if (sql[startColumn] == ' '
|
||||
|| sql[startColumn] == '\t'
|
||||
|| sql[startColumn] == '\n'
|
||||
|| sql[startColumn] == '.'
|
||||
|| sql[startColumn] == '+'
|
||||
|| sql[startColumn] == '-'
|
||||
|| sql[startColumn] == '*'
|
||||
|| sql[startColumn] == '>'
|
||||
|| sql[startColumn] == '<'
|
||||
|| sql[startColumn] == '='
|
||||
|| sql[startColumn] == '/'
|
||||
|| sql[startColumn] == '%')
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return startColumn + 1 - prevLineColumns;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a reparse and bind is required to provide autocomplete
|
||||
/// </summary>
|
||||
/// <param name="info"></param>
|
||||
/// <returns>TEMP: Currently hard-coded to false for perf</returns>
|
||||
private bool RequiresReparse(ScriptParseInfo info)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a list of Declaration objects to CompletionItem objects
|
||||
/// since VS Code expects CompletionItems but SQL Parser works with Declarations
|
||||
/// </summary>
|
||||
/// <param name="suggestions"></param>
|
||||
/// <param name="cursorRow"></param>
|
||||
/// <param name="cursorColumn"></param>
|
||||
/// <returns></returns>
|
||||
private CompletionItem[] ConvertDeclarationsToCompletionItems(
|
||||
IEnumerable<Declaration> suggestions,
|
||||
int row,
|
||||
int startColumn,
|
||||
int endColumn)
|
||||
{
|
||||
List<CompletionItem> completions = new List<CompletionItem>();
|
||||
foreach (var autoCompleteItem in suggestions)
|
||||
{
|
||||
// convert the completion item candidates into CompletionItems
|
||||
completions.Add(new CompletionItem()
|
||||
{
|
||||
Label = autoCompleteItem.Title,
|
||||
Kind = CompletionItemKind.Keyword,
|
||||
Detail = autoCompleteItem.Title,
|
||||
Documentation = autoCompleteItem.Description,
|
||||
TextEdit = new TextEdit
|
||||
{
|
||||
NewText = autoCompleteItem.Title,
|
||||
Range = new Range
|
||||
{
|
||||
Start = new Position
|
||||
{
|
||||
Line = row,
|
||||
Character = startColumn
|
||||
},
|
||||
End = new Position
|
||||
{
|
||||
Line = row,
|
||||
Character = endColumn
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return completions.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the completion item list for the current text position.
|
||||
/// This method does not await cache builds since it expects to return quickly
|
||||
/// </summary>
|
||||
/// <param name="textDocumentPosition"></param>
|
||||
public CompletionItem[] GetCompletionItems(
|
||||
TextDocumentPosition textDocumentPosition,
|
||||
ScriptFile scriptFile,
|
||||
ConnectionInfo connInfo)
|
||||
{
|
||||
string filePath = textDocumentPosition.TextDocument.Uri;
|
||||
|
||||
// Take a reference to the list at a point in time in case we update and replace the list
|
||||
if (connInfo == null
|
||||
|| !LanguageService.Instance.ScriptParseInfoMap.ContainsKey(textDocumentPosition.TextDocument.Uri))
|
||||
{
|
||||
return new CompletionItem[0];
|
||||
}
|
||||
|
||||
// reparse and bind the SQL statement if needed
|
||||
var scriptParseInfo = LanguageService.Instance.ScriptParseInfoMap[textDocumentPosition.TextDocument.Uri];
|
||||
if (RequiresReparse(scriptParseInfo))
|
||||
{
|
||||
LanguageService.Instance.ParseAndBind(scriptFile, connInfo);
|
||||
}
|
||||
|
||||
if (scriptParseInfo.ParseResult == null)
|
||||
{
|
||||
return new CompletionItem[0];
|
||||
}
|
||||
|
||||
// get the completion list from SQL Parser
|
||||
var suggestions = Resolver.FindCompletions(
|
||||
scriptParseInfo.ParseResult,
|
||||
textDocumentPosition.Position.Line + 1,
|
||||
textDocumentPosition.Position.Character + 1,
|
||||
scriptParseInfo.MetadataDisplayInfoProvider);
|
||||
|
||||
// convert the suggestion list to the VS Code format
|
||||
return ConvertDeclarationsToCompletionItems(
|
||||
suggestions,
|
||||
textDocumentPosition.Position.Line,
|
||||
PositionOfPrevDelimeter(
|
||||
scriptFile.Contents,
|
||||
textDocumentPosition.Position.Line,
|
||||
textDocumentPosition.Position.Character),
|
||||
textDocumentPosition.Position.Character);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
{
|
||||
/// <summary>
|
||||
/// Main class for Language Service functionality including anything that reqires knowledge of
|
||||
/// the language to perfom, such as definitions, intellisense, etc.
|
||||
/// </summary>
|
||||
public static class DiagnosticsHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Send the diagnostic results back to the host application
|
||||
/// </summary>
|
||||
/// <param name="scriptFile"></param>
|
||||
/// <param name="semanticMarkers"></param>
|
||||
/// <param name="eventContext"></param>
|
||||
internal static async Task PublishScriptDiagnostics(
|
||||
ScriptFile scriptFile,
|
||||
ScriptFileMarker[] semanticMarkers,
|
||||
EventContext eventContext)
|
||||
{
|
||||
var allMarkers = scriptFile.SyntaxMarkers != null
|
||||
? scriptFile.SyntaxMarkers.Concat(semanticMarkers)
|
||||
: semanticMarkers;
|
||||
|
||||
// Always send syntax and semantic errors. We want to
|
||||
// make sure no out-of-date markers are being displayed.
|
||||
await eventContext.SendEvent(
|
||||
PublishDiagnosticsNotification.Type,
|
||||
new PublishDiagnosticsNotification
|
||||
{
|
||||
Uri = scriptFile.ClientFilePath,
|
||||
Diagnostics =
|
||||
allMarkers
|
||||
.Select(GetDiagnosticFromMarker)
|
||||
.ToArray()
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a ScriptFileMarker to a Diagnostic that is Language Service compatible
|
||||
/// </summary>
|
||||
/// <param name="scriptFileMarker"></param>
|
||||
/// <returns></returns>
|
||||
internal static Diagnostic GetDiagnosticFromMarker(ScriptFileMarker scriptFileMarker)
|
||||
{
|
||||
return new Diagnostic
|
||||
{
|
||||
Severity = MapDiagnosticSeverity(scriptFileMarker.Level),
|
||||
Message = scriptFileMarker.Message,
|
||||
Range = new Range
|
||||
{
|
||||
Start = new Position
|
||||
{
|
||||
Line = scriptFileMarker.ScriptRegion.StartLineNumber - 1,
|
||||
Character = scriptFileMarker.ScriptRegion.StartColumnNumber - 1
|
||||
},
|
||||
End = new Position
|
||||
{
|
||||
Line = scriptFileMarker.ScriptRegion.EndLineNumber - 1,
|
||||
Character = scriptFileMarker.ScriptRegion.EndColumnNumber - 1
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Map ScriptFileMarker severity to Diagnostic severity
|
||||
/// </summary>
|
||||
/// <param name="markerLevel"></param>
|
||||
internal static DiagnosticSeverity MapDiagnosticSeverity(ScriptFileMarkerLevel markerLevel)
|
||||
{
|
||||
switch (markerLevel)
|
||||
{
|
||||
case ScriptFileMarkerLevel.Error:
|
||||
return DiagnosticSeverity.Error;
|
||||
|
||||
case ScriptFileMarkerLevel.Warning:
|
||||
return DiagnosticSeverity.Warning;
|
||||
|
||||
case ScriptFileMarkerLevel.Information:
|
||||
return DiagnosticSeverity.Information;
|
||||
|
||||
default:
|
||||
return DiagnosticSeverity.Error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,22 +5,28 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.EditorServices.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||
using System.Linq;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Parser;
|
||||
using Location = Microsoft.SqlTools.ServiceLayer.Workspace.Contracts.Location;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Binder;
|
||||
using Microsoft.SqlServer.Management.Common;
|
||||
using Microsoft.SqlServer.Management.SqlParser;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Binder;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
|
||||
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Parser;
|
||||
using Microsoft.SqlServer.Management.SmoMetadataProvider;
|
||||
|
||||
using Location = Microsoft.SqlTools.ServiceLayer.Workspace.Contracts.Location;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
{
|
||||
@@ -30,6 +36,42 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
/// </summary>
|
||||
public sealed class LanguageService
|
||||
{
|
||||
public const string DefaultBatchSeperator = "GO";
|
||||
|
||||
private const int DiagnosticParseDelay = 750;
|
||||
|
||||
private const int FindCompletionsTimeout = 3000;
|
||||
|
||||
private const int FindCompletionStartTimeout = 50;
|
||||
|
||||
private const int OnConnectionWaitTimeout = 30000;
|
||||
|
||||
private bool ShouldEnableAutocomplete()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
private ConnectionService connectionService = null;
|
||||
|
||||
/// <summary>
|
||||
/// Internal for testing purposes only
|
||||
/// </summary>
|
||||
internal ConnectionService ConnectionServiceInstance
|
||||
{
|
||||
get
|
||||
{
|
||||
if(connectionService == null)
|
||||
{
|
||||
connectionService = ConnectionService.Instance;
|
||||
}
|
||||
return connectionService;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
connectionService = value;
|
||||
}
|
||||
}
|
||||
|
||||
#region Singleton Instance Implementation
|
||||
|
||||
@@ -98,8 +140,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
serviceHost.SetRequestHandler(SignatureHelpRequest.Type, HandleSignatureHelpRequest);
|
||||
serviceHost.SetRequestHandler(DocumentHighlightRequest.Type, HandleDocumentHighlightRequest);
|
||||
serviceHost.SetRequestHandler(HoverRequest.Type, HandleHoverRequest);
|
||||
serviceHost.SetRequestHandler(DocumentSymbolRequest.Type, HandleDocumentSymbolRequest);
|
||||
serviceHost.SetRequestHandler(WorkspaceSymbolRequest.Type, HandleWorkspaceSymbolRequest);
|
||||
serviceHost.SetRequestHandler(CompletionRequest.Type, HandleCompletionRequest);
|
||||
|
||||
// Register a no-op shutdown task for validation of the shutdown logic
|
||||
serviceHost.RegisterShutdownTask(async (shutdownParams, shutdownRequestContext) =>
|
||||
@@ -115,104 +156,47 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
WorkspaceService<SqlToolsSettings>.Instance.RegisterTextDocChangeCallback(HandleDidChangeTextDocumentNotification);
|
||||
|
||||
// Register the file open update handler
|
||||
WorkspaceService<SqlToolsSettings>.Instance.RegisterTextDocOpenCallback(HandleDidOpenTextDocumentNotification);
|
||||
WorkspaceService<SqlToolsSettings>.Instance.RegisterTextDocOpenCallback(HandleDidOpenTextDocumentNotification);
|
||||
|
||||
// Register a callback for when a connection is created
|
||||
ConnectionServiceInstance.RegisterOnConnectionTask(UpdateLanguageServiceOnConnection);
|
||||
|
||||
// Register a callback for when a connection is closed
|
||||
ConnectionServiceInstance.RegisterOnDisconnectTask(RemoveAutoCompleteCacheUriReference);
|
||||
|
||||
// Store the SqlToolsContext for future use
|
||||
Context = context;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Request Handlers
|
||||
|
||||
/// <summary>
|
||||
/// Parses the SQL text and binds it to the SMO metadata provider if connected
|
||||
/// Auto-complete completion provider request callback
|
||||
/// </summary>
|
||||
/// <param name="filePath"></param>
|
||||
/// <param name="sqlText"></param>
|
||||
/// <param name="textDocumentPosition"></param>
|
||||
/// <param name="requestContext"></param>
|
||||
/// <returns></returns>
|
||||
public ParseResult ParseAndBind(ScriptFile scriptFile, ConnectionInfo connInfo)
|
||||
private static async Task HandleCompletionRequest(
|
||||
TextDocumentPosition textDocumentPosition,
|
||||
RequestContext<CompletionItem[]> requestContext)
|
||||
{
|
||||
ScriptParseInfo parseInfo = null;
|
||||
if (this.ScriptParseInfoMap.ContainsKey(scriptFile.ClientFilePath))
|
||||
{
|
||||
parseInfo = this.ScriptParseInfoMap[scriptFile.ClientFilePath];
|
||||
}
|
||||
// get the current list of completion items and return to client
|
||||
var scriptFile = WorkspaceService<SqlToolsSettings>.Instance.Workspace.GetFile(
|
||||
textDocumentPosition.TextDocument.Uri);
|
||||
|
||||
// parse current SQL file contents to retrieve a list of errors
|
||||
ParseOptions parseOptions = new ParseOptions();
|
||||
ParseResult parseResult = Parser.IncrementalParse(
|
||||
scriptFile.Contents,
|
||||
parseInfo != null ? parseInfo.ParseResult : null,
|
||||
parseOptions);
|
||||
|
||||
// save previous result for next incremental parse
|
||||
if (parseInfo != null)
|
||||
{
|
||||
parseInfo.ParseResult = parseResult;
|
||||
}
|
||||
|
||||
if (connInfo != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
List<ParseResult> parseResults = new List<ParseResult>();
|
||||
parseResults.Add(parseResult);
|
||||
parseInfo.Binder.Bind(
|
||||
parseResults,
|
||||
connInfo.ConnectionDetails.DatabaseName,
|
||||
BindMode.Batch);
|
||||
}
|
||||
catch (ConnectionException)
|
||||
{
|
||||
Logger.Write(LogLevel.Error, "Hit connection exception while binding - disposing binder object...");
|
||||
}
|
||||
catch (SqlParserInternalBinderError)
|
||||
{
|
||||
Logger.Write(LogLevel.Error, "Hit connection exception while binding - disposing binder object...");
|
||||
}
|
||||
}
|
||||
|
||||
return parseResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of semantic diagnostic marks for the provided script file
|
||||
/// </summary>
|
||||
/// <param name="scriptFile"></param>
|
||||
public ScriptFileMarker[] GetSemanticMarkers(ScriptFile scriptFile)
|
||||
{
|
||||
ConnectionInfo connInfo;
|
||||
ConnectionService.Instance.TryFindConnection(
|
||||
scriptFile.ClientFilePath,
|
||||
out connInfo);
|
||||
|
||||
var parseResult = ParseAndBind(scriptFile, connInfo);
|
||||
|
||||
// build a list of SQL script file markers from the errors
|
||||
List<ScriptFileMarker> markers = new List<ScriptFileMarker>();
|
||||
foreach (var error in parseResult.Errors)
|
||||
{
|
||||
markers.Add(new ScriptFileMarker()
|
||||
{
|
||||
Message = error.Message,
|
||||
Level = ScriptFileMarkerLevel.Error,
|
||||
ScriptRegion = new ScriptRegion()
|
||||
{
|
||||
File = scriptFile.FilePath,
|
||||
StartLineNumber = error.Start.LineNumber,
|
||||
StartColumnNumber = error.Start.ColumnNumber,
|
||||
StartOffset = 0,
|
||||
EndLineNumber = error.End.LineNumber,
|
||||
EndColumnNumber = error.End.ColumnNumber,
|
||||
EndOffset = 0
|
||||
}
|
||||
});
|
||||
}
|
||||
var completionItems = Instance.GetCompletionItems(
|
||||
textDocumentPosition, scriptFile, connInfo);
|
||||
|
||||
return markers.ToArray();
|
||||
await requestContext.SendResult(completionItems);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Request Handlers
|
||||
|
||||
private static async Task HandleDefinitionRequest(
|
||||
TextDocumentPosition textDocumentPosition,
|
||||
RequestContext<Location[]> requestContext)
|
||||
@@ -261,22 +245,6 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
await Task.FromResult(true);
|
||||
}
|
||||
|
||||
private static async Task HandleDocumentSymbolRequest(
|
||||
DocumentSymbolParams documentSymbolParams,
|
||||
RequestContext<SymbolInformation[]> requestContext)
|
||||
{
|
||||
Logger.Write(LogLevel.Verbose, "HandleDocumentSymbolRequest");
|
||||
await Task.FromResult(true);
|
||||
}
|
||||
|
||||
private static async Task HandleWorkspaceSymbolRequest(
|
||||
WorkspaceSymbolParams workspaceSymbolParams,
|
||||
RequestContext<SymbolInformation[]> requestContext)
|
||||
{
|
||||
Logger.Write(LogLevel.Verbose, "HandleWorkspaceSymbolRequest");
|
||||
await Task.FromResult(true);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Handlers for Events from Other Services
|
||||
@@ -336,7 +304,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
|
||||
foreach (var scriptFile in WorkspaceService<SqlToolsSettings>.Instance.Workspace.GetOpenedFiles())
|
||||
{
|
||||
await PublishScriptDiagnostics(scriptFile, emptyAnalysisDiagnostics, eventContext);
|
||||
await DiagnosticsHelper.PublishScriptDiagnostics(scriptFile, emptyAnalysisDiagnostics, eventContext);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -352,7 +320,269 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Helpers
|
||||
|
||||
#region "AutoComplete Provider methods"
|
||||
|
||||
/// <summary>
|
||||
/// Remove a reference to an autocomplete cache from a URI. If
|
||||
/// it is the last URI connected to a particular connection,
|
||||
/// then remove the cache.
|
||||
/// </summary>
|
||||
public async Task RemoveAutoCompleteCacheUriReference(ConnectionSummary summary)
|
||||
{
|
||||
// currently this method is disabled, but we need to reimplement now that the
|
||||
// implementation of the 'cache' has changed.
|
||||
await Task.FromResult(0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses the SQL text and binds it to the SMO metadata provider if connected
|
||||
/// </summary>
|
||||
/// <param name="filePath"></param>
|
||||
/// <param name="sqlText"></param>
|
||||
/// <returns></returns>
|
||||
public ParseResult ParseAndBind(ScriptFile scriptFile, ConnectionInfo connInfo)
|
||||
{
|
||||
ScriptParseInfo parseInfo = null;
|
||||
if (this.ScriptParseInfoMap.ContainsKey(scriptFile.ClientFilePath))
|
||||
{
|
||||
parseInfo = this.ScriptParseInfoMap[scriptFile.ClientFilePath];
|
||||
}
|
||||
else
|
||||
{
|
||||
parseInfo = new ScriptParseInfo();
|
||||
this.ScriptParseInfoMap.Add(scriptFile.ClientFilePath, parseInfo);
|
||||
}
|
||||
|
||||
if (parseInfo.BuildingMetadataEvent.WaitOne(LanguageService.FindCompletionsTimeout))
|
||||
{
|
||||
try
|
||||
{
|
||||
parseInfo.BuildingMetadataEvent.Reset();
|
||||
|
||||
// parse current SQL file contents to retrieve a list of errors
|
||||
ParseResult parseResult = Parser.IncrementalParse(
|
||||
scriptFile.Contents,
|
||||
parseInfo.ParseResult,
|
||||
parseInfo.ParseOptions);
|
||||
|
||||
parseInfo.ParseResult = parseResult;
|
||||
|
||||
if (connInfo != null && parseInfo.IsConnected)
|
||||
{
|
||||
try
|
||||
{
|
||||
List<ParseResult> parseResults = new List<ParseResult>();
|
||||
parseResults.Add(parseResult);
|
||||
parseInfo.Binder.Bind(
|
||||
parseResults,
|
||||
connInfo.ConnectionDetails.DatabaseName,
|
||||
BindMode.Batch);
|
||||
}
|
||||
catch (ConnectionException)
|
||||
{
|
||||
Logger.Write(LogLevel.Error, "Hit connection exception while binding - disposing binder object...");
|
||||
}
|
||||
catch (SqlParserInternalBinderError)
|
||||
{
|
||||
Logger.Write(LogLevel.Error, "Hit connection exception while binding - disposing binder object...");
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
parseInfo.BuildingMetadataEvent.Set();
|
||||
}
|
||||
}
|
||||
|
||||
return parseInfo.ParseResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the cached autocomplete candidate list when the user connects to a database
|
||||
/// </summary>
|
||||
/// <param name="info"></param>
|
||||
public async Task UpdateLanguageServiceOnConnection(ConnectionInfo info)
|
||||
{
|
||||
await Task.Run( () =>
|
||||
{
|
||||
if (ShouldEnableAutocomplete())
|
||||
{
|
||||
ScriptParseInfo scriptInfo =
|
||||
this.ScriptParseInfoMap.ContainsKey(info.OwnerUri)
|
||||
? this.ScriptParseInfoMap[info.OwnerUri]
|
||||
: new ScriptParseInfo();
|
||||
|
||||
try
|
||||
{
|
||||
scriptInfo.BuildingMetadataEvent.WaitOne(LanguageService.OnConnectionWaitTimeout);
|
||||
scriptInfo.BuildingMetadataEvent.Reset();
|
||||
|
||||
var sqlConn = info.SqlConnection as ReliableSqlConnection;
|
||||
if (sqlConn != null)
|
||||
{
|
||||
ServerConnection serverConn = new ServerConnection(sqlConn.GetUnderlyingConnection());
|
||||
scriptInfo.MetadataDisplayInfoProvider = new MetadataDisplayInfoProvider();
|
||||
scriptInfo.MetadataProvider = SmoMetadataProvider.CreateConnectedProvider(serverConn);
|
||||
scriptInfo.Binder = BinderProvider.CreateBinder(scriptInfo.MetadataProvider);
|
||||
scriptInfo.ServerConnection = new ServerConnection(sqlConn.GetUnderlyingConnection());
|
||||
this.ScriptParseInfoMap[info.OwnerUri] = scriptInfo;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
scriptInfo.IsConnected = false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Set Metadata Build event to Signal state.
|
||||
// (Tell Language Service that I am ready with Metadata Provider Object)
|
||||
scriptInfo.BuildingMetadataEvent.Set();
|
||||
}
|
||||
|
||||
if (scriptInfo.IsConnected)
|
||||
{
|
||||
var scriptFile = WorkspaceService<SqlToolsSettings>.Instance.Workspace.GetFile(info.OwnerUri);
|
||||
ParseAndBind(scriptFile, info);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a reparse and bind is required to provide autocomplete
|
||||
/// </summary>
|
||||
/// <param name="info"></param>
|
||||
private bool RequiresReparse(ScriptParseInfo info, ScriptFile scriptFile)
|
||||
{
|
||||
if (info.ParseResult == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
string prevSqlText = info.ParseResult.Script.Sql;
|
||||
string currentSqlText = scriptFile.Contents;
|
||||
|
||||
return prevSqlText.Length != currentSqlText.Length
|
||||
|| !string.Equals(prevSqlText, currentSqlText);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the completion item list for the current text position.
|
||||
/// This method does not await cache builds since it expects to return quickly
|
||||
/// </summary>
|
||||
/// <param name="textDocumentPosition"></param>
|
||||
public CompletionItem[] GetCompletionItems(
|
||||
TextDocumentPosition textDocumentPosition,
|
||||
ScriptFile scriptFile,
|
||||
ConnectionInfo connInfo)
|
||||
{
|
||||
string filePath = textDocumentPosition.TextDocument.Uri;
|
||||
int startLine = textDocumentPosition.Position.Line;
|
||||
int startColumn = TextUtilities.PositionOfPrevDelimeter(
|
||||
scriptFile.Contents,
|
||||
textDocumentPosition.Position.Line,
|
||||
textDocumentPosition.Position.Character);
|
||||
int endColumn = textDocumentPosition.Position.Character;
|
||||
|
||||
// Take a reference to the list at a point in time in case we update and replace the list
|
||||
if (connInfo == null
|
||||
|| !LanguageService.Instance.ScriptParseInfoMap.ContainsKey(textDocumentPosition.TextDocument.Uri))
|
||||
{
|
||||
return AutoCompleteHelper.GetDefaultCompletionItems(startLine, startColumn, endColumn);
|
||||
}
|
||||
|
||||
// reparse and bind the SQL statement if needed
|
||||
var scriptParseInfo = ScriptParseInfoMap[textDocumentPosition.TextDocument.Uri];
|
||||
if (RequiresReparse(scriptParseInfo, scriptFile))
|
||||
{
|
||||
ParseAndBind(scriptFile, connInfo);
|
||||
}
|
||||
|
||||
if (scriptParseInfo.ParseResult == null)
|
||||
{
|
||||
return AutoCompleteHelper.GetDefaultCompletionItems(startLine, startColumn, endColumn);
|
||||
}
|
||||
|
||||
if (scriptParseInfo.IsConnected
|
||||
&& scriptParseInfo.BuildingMetadataEvent.WaitOne(LanguageService.FindCompletionStartTimeout))
|
||||
{
|
||||
scriptParseInfo.BuildingMetadataEvent.Reset();
|
||||
Task<CompletionItem[]> findCompletionsTask = Task.Run(() => {
|
||||
try
|
||||
{
|
||||
// get the completion list from SQL Parser
|
||||
var suggestions = Resolver.FindCompletions(
|
||||
scriptParseInfo.ParseResult,
|
||||
textDocumentPosition.Position.Line + 1,
|
||||
textDocumentPosition.Position.Character + 1,
|
||||
scriptParseInfo.MetadataDisplayInfoProvider);
|
||||
|
||||
// convert the suggestion list to the VS Code format
|
||||
return AutoCompleteHelper.ConvertDeclarationsToCompletionItems(
|
||||
suggestions,
|
||||
startLine,
|
||||
startColumn,
|
||||
endColumn);
|
||||
}
|
||||
finally
|
||||
{
|
||||
scriptParseInfo.BuildingMetadataEvent.Set();
|
||||
}
|
||||
});
|
||||
|
||||
findCompletionsTask.Wait(LanguageService.FindCompletionsTimeout);
|
||||
if (findCompletionsTask.IsCompleted
|
||||
&& findCompletionsTask.Result != null
|
||||
&& findCompletionsTask.Result.Length > 0)
|
||||
{
|
||||
return findCompletionsTask.Result;
|
||||
}
|
||||
}
|
||||
|
||||
return AutoCompleteHelper.GetDefaultCompletionItems(startLine, startColumn, endColumn);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Diagnostic Provider methods
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of semantic diagnostic marks for the provided script file
|
||||
/// </summary>
|
||||
/// <param name="scriptFile"></param>
|
||||
internal ScriptFileMarker[] GetSemanticMarkers(ScriptFile scriptFile)
|
||||
{
|
||||
ConnectionInfo connInfo;
|
||||
ConnectionService.Instance.TryFindConnection(
|
||||
scriptFile.ClientFilePath,
|
||||
out connInfo);
|
||||
|
||||
var parseResult = ParseAndBind(scriptFile, connInfo);
|
||||
|
||||
// build a list of SQL script file markers from the errors
|
||||
List<ScriptFileMarker> markers = new List<ScriptFileMarker>();
|
||||
foreach (var error in parseResult.Errors)
|
||||
{
|
||||
markers.Add(new ScriptFileMarker()
|
||||
{
|
||||
Message = error.Message,
|
||||
Level = ScriptFileMarkerLevel.Error,
|
||||
ScriptRegion = new ScriptRegion()
|
||||
{
|
||||
File = scriptFile.FilePath,
|
||||
StartLineNumber = error.Start.LineNumber,
|
||||
StartColumnNumber = error.Start.ColumnNumber,
|
||||
StartOffset = 0,
|
||||
EndLineNumber = error.End.LineNumber,
|
||||
EndColumnNumber = error.End.ColumnNumber,
|
||||
EndOffset = 0
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return markers.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs script diagnostics on changed files
|
||||
@@ -401,7 +631,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
Task.Factory.StartNew(
|
||||
() =>
|
||||
DelayThenInvokeDiagnostics(
|
||||
750,
|
||||
LanguageService.DiagnosticParseDelay,
|
||||
filesToAnalyze,
|
||||
eventContext,
|
||||
ExistingRequestCancellation.Token),
|
||||
@@ -451,85 +681,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
ScriptFileMarker[] semanticMarkers = GetSemanticMarkers(scriptFile);
|
||||
Logger.Write(LogLevel.Verbose, "Analysis complete.");
|
||||
|
||||
await PublishScriptDiagnostics(scriptFile, semanticMarkers, eventContext);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send the diagnostic results back to the host application
|
||||
/// </summary>
|
||||
/// <param name="scriptFile"></param>
|
||||
/// <param name="semanticMarkers"></param>
|
||||
/// <param name="eventContext"></param>
|
||||
private static async Task PublishScriptDiagnostics(
|
||||
ScriptFile scriptFile,
|
||||
ScriptFileMarker[] semanticMarkers,
|
||||
EventContext eventContext)
|
||||
{
|
||||
var allMarkers = scriptFile.SyntaxMarkers != null
|
||||
? scriptFile.SyntaxMarkers.Concat(semanticMarkers)
|
||||
: semanticMarkers;
|
||||
|
||||
// Always send syntax and semantic errors. We want to
|
||||
// make sure no out-of-date markers are being displayed.
|
||||
await eventContext.SendEvent(
|
||||
PublishDiagnosticsNotification.Type,
|
||||
new PublishDiagnosticsNotification
|
||||
{
|
||||
Uri = scriptFile.ClientFilePath,
|
||||
Diagnostics =
|
||||
allMarkers
|
||||
.Select(GetDiagnosticFromMarker)
|
||||
.ToArray()
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a ScriptFileMarker to a Diagnostic that is Language Service compatible
|
||||
/// </summary>
|
||||
/// <param name="scriptFileMarker"></param>
|
||||
/// <returns></returns>
|
||||
private static Diagnostic GetDiagnosticFromMarker(ScriptFileMarker scriptFileMarker)
|
||||
{
|
||||
return new Diagnostic
|
||||
{
|
||||
Severity = MapDiagnosticSeverity(scriptFileMarker.Level),
|
||||
Message = scriptFileMarker.Message,
|
||||
Range = new Range
|
||||
{
|
||||
Start = new Position
|
||||
{
|
||||
Line = scriptFileMarker.ScriptRegion.StartLineNumber - 1,
|
||||
Character = scriptFileMarker.ScriptRegion.StartColumnNumber - 1
|
||||
},
|
||||
End = new Position
|
||||
{
|
||||
Line = scriptFileMarker.ScriptRegion.EndLineNumber - 1,
|
||||
Character = scriptFileMarker.ScriptRegion.EndColumnNumber - 1
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Map ScriptFileMarker severity to Diagnostic severity
|
||||
/// </summary>
|
||||
/// <param name="markerLevel"></param>
|
||||
private static DiagnosticSeverity MapDiagnosticSeverity(ScriptFileMarkerLevel markerLevel)
|
||||
{
|
||||
switch (markerLevel)
|
||||
{
|
||||
case ScriptFileMarkerLevel.Error:
|
||||
return DiagnosticSeverity.Error;
|
||||
|
||||
case ScriptFileMarkerLevel.Warning:
|
||||
return DiagnosticSeverity.Warning;
|
||||
|
||||
case ScriptFileMarkerLevel.Information:
|
||||
return DiagnosticSeverity.Information;
|
||||
|
||||
default:
|
||||
return DiagnosticSeverity.Error;
|
||||
await DiagnosticsHelper.PublishScriptDiagnostics(scriptFile, semanticMarkers, eventContext);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,10 +3,14 @@
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Microsoft.SqlServer.Management.Common;
|
||||
using Microsoft.SqlServer.Management.SmoMetadataProvider;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Binder;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Common;
|
||||
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Parser;
|
||||
using Microsoft.SqlServer.Management.SmoMetadataProvider;
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
{
|
||||
@@ -15,6 +19,110 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
/// </summary>
|
||||
internal class ScriptParseInfo
|
||||
{
|
||||
private ManualResetEvent buildingMetadataEvent = new ManualResetEvent(initialState: true);
|
||||
|
||||
private ParseOptions parseOptions = new ParseOptions();
|
||||
|
||||
private ServerConnection serverConnection;
|
||||
|
||||
/// <summary>
|
||||
/// Event which tells if MetadataProvider is built fully or not
|
||||
/// </summary>
|
||||
public ManualResetEvent BuildingMetadataEvent
|
||||
{
|
||||
get { return this.buildingMetadataEvent; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a flag determining is the LanguageService is connected
|
||||
/// </summary>
|
||||
public bool IsConnected { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the LanguageService SMO ServerConnection
|
||||
/// </summary>
|
||||
public ServerConnection ServerConnection
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.serverConnection;
|
||||
}
|
||||
set
|
||||
{
|
||||
this.serverConnection = value;
|
||||
this.parseOptions = new ParseOptions(
|
||||
batchSeparator: LanguageService.DefaultBatchSeperator,
|
||||
isQuotedIdentifierSet: true,
|
||||
compatibilityLevel: DatabaseCompatibilityLevel,
|
||||
transactSqlVersion: TransactSqlVersion);
|
||||
this.IsConnected = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Language Service ServerVersion
|
||||
/// </summary>
|
||||
public ServerVersion ServerVersion
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.ServerConnection != null
|
||||
? this.ServerConnection.ServerVersion
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current DataEngineType
|
||||
/// </summary>
|
||||
public DatabaseEngineType DatabaseEngineType
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.ServerConnection != null
|
||||
? this.ServerConnection.DatabaseEngineType
|
||||
: DatabaseEngineType.Standalone;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current connections TransactSqlVersion
|
||||
/// </summary>
|
||||
public TransactSqlVersion TransactSqlVersion
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.IsConnected
|
||||
? GetTransactSqlVersion(this.ServerVersion)
|
||||
: TransactSqlVersion.Current;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current DatabaseCompatibilityLevel
|
||||
/// </summary>
|
||||
public DatabaseCompatibilityLevel DatabaseCompatibilityLevel
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.IsConnected
|
||||
? GetDatabaseCompatibilityLevel(this.ServerVersion)
|
||||
: DatabaseCompatibilityLevel.Current;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current ParseOptions
|
||||
/// </summary>
|
||||
public ParseOptions ParseOptions
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.parseOptions;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the SMO binder for schema-aware intellisense
|
||||
/// </summary>
|
||||
@@ -28,13 +136,63 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
/// <summary>
|
||||
/// Gets or set the SMO metadata provider that's bound to the current connection
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public SmoMetadataProvider MetadataProvider { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the SMO metadata display info provider
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public MetadataDisplayInfoProvider MetadataDisplayInfoProvider { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the database compatibility level from a server version
|
||||
/// </summary>
|
||||
/// <param name="serverVersion"></param>
|
||||
private static DatabaseCompatibilityLevel GetDatabaseCompatibilityLevel(ServerVersion serverVersion)
|
||||
{
|
||||
int versionMajor = Math.Max(serverVersion.Major, 8);
|
||||
|
||||
switch (versionMajor)
|
||||
{
|
||||
case 8:
|
||||
return DatabaseCompatibilityLevel.Version80;
|
||||
case 9:
|
||||
return DatabaseCompatibilityLevel.Version90;
|
||||
case 10:
|
||||
return DatabaseCompatibilityLevel.Version100;
|
||||
case 11:
|
||||
return DatabaseCompatibilityLevel.Version110;
|
||||
case 12:
|
||||
return DatabaseCompatibilityLevel.Version120;
|
||||
case 13:
|
||||
return DatabaseCompatibilityLevel.Version130;
|
||||
default:
|
||||
return DatabaseCompatibilityLevel.Current;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the transaction sql version from a server version
|
||||
/// </summary>
|
||||
/// <param name="serverVersion"></param>
|
||||
private static TransactSqlVersion GetTransactSqlVersion(ServerVersion serverVersion)
|
||||
{
|
||||
int versionMajor = Math.Max(serverVersion.Major, 9);
|
||||
|
||||
switch (versionMajor)
|
||||
{
|
||||
case 9:
|
||||
case 10:
|
||||
// In case of 10.0 we still use Version 10.5 as it is the closest available.
|
||||
return TransactSqlVersion.Version105;
|
||||
case 11:
|
||||
return TransactSqlVersion.Version110;
|
||||
case 12:
|
||||
return TransactSqlVersion.Version120;
|
||||
case 13:
|
||||
return TransactSqlVersion.Version130;
|
||||
default:
|
||||
return TransactSqlVersion.Current;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,6 @@ namespace Microsoft.SqlTools.ServiceLayer
|
||||
|
||||
// Initialize the services that will be hosted here
|
||||
WorkspaceService<SqlToolsSettings>.Instance.InitializeService(serviceHost);
|
||||
AutoCompleteService.Instance.InitializeService(serviceHost);
|
||||
LanguageService.Instance.InitializeService(serviceHost, sqlToolsContext);
|
||||
ConnectionService.Instance.InitializeService(serviceHost);
|
||||
CredentialService.Instance.InitializeService(serviceHost);
|
||||
|
||||
@@ -11,6 +11,7 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.EditorServices.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage;
|
||||
|
||||
@@ -137,10 +138,10 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
{
|
||||
// Register the message listener to *this instance* of the batch
|
||||
// Note: This is being done to associate messages with batches
|
||||
SqlConnection sqlConn = conn as SqlConnection;
|
||||
ReliableSqlConnection sqlConn = conn as ReliableSqlConnection;
|
||||
if (sqlConn != null)
|
||||
{
|
||||
sqlConn.InfoMessage += StoreDbMessage;
|
||||
sqlConn.GetUnderlyingConnection().InfoMessage += StoreDbMessage;
|
||||
}
|
||||
|
||||
// Create a command that we'll use for executing the query
|
||||
|
||||
@@ -148,7 +148,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
|
||||
}
|
||||
else
|
||||
{
|
||||
DataType = DataType;
|
||||
DataType = column.DataType;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
//
|
||||
// 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 Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// Parameters for the save results request
|
||||
/// </summary>
|
||||
public class SaveResultsRequestParams
|
||||
{
|
||||
/// <summary>
|
||||
/// The path of the file to save results in
|
||||
/// </summary>
|
||||
public string FilePath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Index of the batch to get the results from
|
||||
/// </summary>
|
||||
public int BatchIndex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Index of the result set to get the results from
|
||||
/// </summary>
|
||||
public int ResultSetIndex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// URI for the editor that called save results
|
||||
/// </summary>
|
||||
public string OwnerUri { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parameters to save results as CSV
|
||||
/// </summary>
|
||||
public class SaveResultsAsCsvRequestParams: SaveResultsRequestParams{
|
||||
|
||||
/// <summary>
|
||||
/// CSV - Write values in quotes
|
||||
/// </summary>
|
||||
public Boolean ValueInQuotes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The encoding of the file to save results in
|
||||
/// </summary>
|
||||
public string FileEncoding { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Include headers of columns in CSV
|
||||
/// </summary>
|
||||
public bool IncludeHeaders { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parameters to save results as JSON
|
||||
/// </summary>
|
||||
public class SaveResultsAsJsonRequestParams: SaveResultsRequestParams{
|
||||
//TODO: define config for save as JSON
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parameters for the save results result
|
||||
/// </summary>
|
||||
public class SaveResultRequestResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Error messages for saving to file.
|
||||
/// </summary>
|
||||
public string Messages { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request type to save results as CSV
|
||||
/// </summary>
|
||||
public class SaveResultsAsCsvRequest
|
||||
{
|
||||
public static readonly
|
||||
RequestType<SaveResultsAsCsvRequestParams, SaveResultRequestResult> Type =
|
||||
RequestType<SaveResultsAsCsvRequestParams, SaveResultRequestResult>.Create("query/saveCsv");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request type to save results as JSON
|
||||
/// </summary>
|
||||
public class SaveResultsAsJsonRequest
|
||||
{
|
||||
public static readonly
|
||||
RequestType<SaveResultsAsJsonRequestParams, SaveResultRequestResult> Type =
|
||||
RequestType<SaveResultsAsJsonRequestParams, SaveResultRequestResult>.Create("query/saveJson");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,13 +14,10 @@ using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
{
|
||||
/// <summary>
|
||||
/// Reader for SSMS formatted file streams
|
||||
/// Reader for service buffer formatted file streams
|
||||
/// </summary>
|
||||
public class ServiceBufferFileStreamReader : IFileStreamReader
|
||||
{
|
||||
// Most of this code is based on code from the Microsoft.SqlServer.Management.UI.Grid, SSMS DataStorage
|
||||
// $\Data Tools\SSMS_XPlat\sql\ssms\core\DataStorage\src\FileStreamReader.cs
|
||||
|
||||
private const int DefaultBufferSize = 8192;
|
||||
|
||||
#region Member Variables
|
||||
|
||||
@@ -14,13 +14,10 @@ using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
{
|
||||
/// <summary>
|
||||
/// Writer for SSMS formatted file streams
|
||||
/// Writer for service buffer formatted file streams
|
||||
/// </summary>
|
||||
public class ServiceBufferFileStreamWriter : IFileStreamWriter
|
||||
{
|
||||
// Most of this code is based on code from the Microsoft.SqlServer.Management.UI.Grid, SSMS DataStorage
|
||||
// $\Data Tools\SSMS_XPlat\sql\ssms\core\DataStorage\src\FileStreamWriter.cs
|
||||
|
||||
#region Properties
|
||||
|
||||
public const int DefaultBufferLength = 8192;
|
||||
|
||||
@@ -10,6 +10,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Parser;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
@@ -192,11 +193,11 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
{
|
||||
await conn.OpenAsync();
|
||||
|
||||
SqlConnection sqlConn = conn as SqlConnection;
|
||||
ReliableSqlConnection sqlConn = conn as ReliableSqlConnection;
|
||||
if (sqlConn != null)
|
||||
{
|
||||
// Subscribe to database informational messages
|
||||
sqlConn.InfoMessage += OnInfoMessage;
|
||||
sqlConn.GetUnderlyingConnection().InfoMessage += OnInfoMessage;
|
||||
}
|
||||
|
||||
// We need these to execute synchronously, otherwise the user will be very unhappy
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
// 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.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting;
|
||||
@@ -13,6 +14,7 @@ using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
{
|
||||
@@ -98,6 +100,8 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
serviceHost.SetRequestHandler(QueryExecuteSubsetRequest.Type, HandleResultSubsetRequest);
|
||||
serviceHost.SetRequestHandler(QueryDisposeRequest.Type, HandleDisposeRequest);
|
||||
serviceHost.SetRequestHandler(QueryCancelRequest.Type, HandleCancelRequest);
|
||||
serviceHost.SetRequestHandler(SaveResultsAsCsvRequest.Type, HandleSaveResultsAsCsvRequest);
|
||||
serviceHost.SetRequestHandler(SaveResultsAsJsonRequest.Type, HandleSaveResultsAsJsonRequest);
|
||||
|
||||
// Register handler for shutdown event
|
||||
serviceHost.RegisterShutdownTask((shutdownParams, requestContext) =>
|
||||
@@ -256,6 +260,124 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process request to save a resultSet to a file in CSV format
|
||||
/// </summary>
|
||||
public async Task HandleSaveResultsAsCsvRequest( SaveResultsAsCsvRequestParams saveParams,
|
||||
RequestContext<SaveResultRequestResult> requestContext)
|
||||
{
|
||||
// retrieve query for OwnerUri
|
||||
Query result;
|
||||
if (!ActiveQueries.TryGetValue(saveParams.OwnerUri, out result))
|
||||
{
|
||||
await requestContext.SendResult(new SaveResultRequestResult
|
||||
{
|
||||
Messages = "Failed to save results, ID not found."
|
||||
});
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
using (StreamWriter csvFile = new StreamWriter(File.Open(saveParams.FilePath, FileMode.Create)))
|
||||
{
|
||||
// get the requested resultSet from query
|
||||
Batch selectedBatch = result.Batches[saveParams.BatchIndex];
|
||||
ResultSet selectedResultSet = (selectedBatch.ResultSets.ToList())[saveParams.ResultSetIndex];
|
||||
if (saveParams.IncludeHeaders)
|
||||
{
|
||||
// write column names to csv
|
||||
await csvFile.WriteLineAsync( string.Join( ",", selectedResultSet.Columns.Select( column => SaveResults.EncodeCsvField(column.ColumnName) ?? string.Empty)));
|
||||
}
|
||||
|
||||
// write rows to csv
|
||||
foreach (var row in selectedResultSet.Rows)
|
||||
{
|
||||
await csvFile.WriteLineAsync( string.Join( ",", row.Select( field => SaveResults.EncodeCsvField((field != null) ? field.ToString(): string.Empty))));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
// Delete file when exception occurs
|
||||
if (File.Exists(saveParams.FilePath))
|
||||
{
|
||||
File.Delete(saveParams.FilePath);
|
||||
}
|
||||
await requestContext.SendError(ex.Message);
|
||||
return;
|
||||
}
|
||||
await requestContext.SendResult(new SaveResultRequestResult
|
||||
{
|
||||
Messages = "Success"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process request to save a resultSet to a file in JSON format
|
||||
/// </summary>
|
||||
public async Task HandleSaveResultsAsJsonRequest( SaveResultsAsJsonRequestParams saveParams,
|
||||
RequestContext<SaveResultRequestResult> requestContext)
|
||||
{
|
||||
// retrieve query for OwnerUri
|
||||
Query result;
|
||||
if (!ActiveQueries.TryGetValue(saveParams.OwnerUri, out result))
|
||||
{
|
||||
await requestContext.SendResult(new SaveResultRequestResult
|
||||
{
|
||||
Messages = "Failed to save results, ID not found."
|
||||
});
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
using (StreamWriter jsonFile = new StreamWriter(File.Open(saveParams.FilePath, FileMode.Create)))
|
||||
using (JsonWriter jsonWriter = new JsonTextWriter(jsonFile) )
|
||||
{
|
||||
jsonWriter.Formatting = Formatting.Indented;
|
||||
jsonWriter.WriteStartArray();
|
||||
|
||||
// get the requested resultSet from query
|
||||
Batch selectedBatch = result.Batches[saveParams.BatchIndex];
|
||||
ResultSet selectedResultSet = (selectedBatch.ResultSets.ToList())[saveParams.ResultSetIndex];
|
||||
|
||||
// write each row to JSON
|
||||
foreach (var row in selectedResultSet.Rows)
|
||||
{
|
||||
jsonWriter.WriteStartObject();
|
||||
foreach (var field in row.Select((value,i) => new {value, i}))
|
||||
{
|
||||
jsonWriter.WritePropertyName(selectedResultSet.Columns[field.i].ColumnName);
|
||||
if (field.value != null)
|
||||
{
|
||||
jsonWriter.WriteValue(field.value);
|
||||
}
|
||||
else
|
||||
{
|
||||
jsonWriter.WriteNull();
|
||||
}
|
||||
}
|
||||
jsonWriter.WriteEndObject();
|
||||
}
|
||||
jsonWriter.WriteEndArray();
|
||||
}
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
// Delete file when exception occurs
|
||||
if (File.Exists(saveParams.FilePath))
|
||||
{
|
||||
File.Delete(saveParams.FilePath);
|
||||
}
|
||||
await requestContext.SendError(ex.Message);
|
||||
return;
|
||||
}
|
||||
await requestContext.SendResult(new SaveResultRequestResult
|
||||
{
|
||||
Messages = "Success"
|
||||
});
|
||||
return;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Private Helpers
|
||||
|
||||
@@ -111,6 +111,14 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
/// </summary>
|
||||
public long RowCount { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The rows of this result set
|
||||
/// </summary>
|
||||
public IEnumerable<object[]> Rows
|
||||
{
|
||||
get { return FileOffsets.Select(offset => fileStreamReader.ReadRow(offset, Columns)); }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
//
|
||||
// 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.Text;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
{
|
||||
internal class SaveResults{
|
||||
|
||||
/// Method ported from SSMS
|
||||
|
||||
/// <summary>
|
||||
/// Encodes a single field for inserting into a CSV record. The following rules are applied:
|
||||
/// <list type="bullet">
|
||||
/// <item><description>All double quotes (") are replaced with a pair of consecutive double quotes</description></item>
|
||||
/// </list>
|
||||
/// The entire field is also surrounded by a pair of double quotes if any of the following conditions are met:
|
||||
/// <list type="bullet">
|
||||
/// <item><description>The field begins or ends with a space</description></item>
|
||||
/// <item><description>The field begins or ends with a tab</description></item>
|
||||
/// <item><description>The field contains the ListSeparator string</description></item>
|
||||
/// <item><description>The field contains the '\n' character</description></item>
|
||||
/// <item><description>The field contains the '\r' character</description></item>
|
||||
/// <item><description>The field contains the '"' character</description></item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
/// <param name="field">The field to encode</param>
|
||||
/// <returns>The CSV encoded version of the original field</returns>
|
||||
internal static String EncodeCsvField(String field)
|
||||
{
|
||||
StringBuilder sbField = new StringBuilder(field);
|
||||
|
||||
//Whether this field has special characters which require it to be embedded in quotes
|
||||
bool embedInQuotes = false;
|
||||
|
||||
//Check for leading/trailing spaces
|
||||
if (sbField.Length > 0 &&
|
||||
(sbField[0] == ' ' ||
|
||||
sbField[0] == '\t' ||
|
||||
sbField[sbField.Length - 1] == ' ' ||
|
||||
sbField[sbField.Length - 1] == '\t'))
|
||||
{
|
||||
embedInQuotes = true;
|
||||
}
|
||||
else
|
||||
{ //List separator being in the field will require quotes
|
||||
if (field.Contains(","))
|
||||
{
|
||||
embedInQuotes = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < sbField.Length; ++i)
|
||||
{
|
||||
//Check whether this character is a special character
|
||||
if (sbField[i] == '\r' ||
|
||||
sbField[i] == '\n' ||
|
||||
sbField[i] == '"')
|
||||
{ //If even one character requires embedding the whole field will
|
||||
//be embedded in quotes so we can just break out now
|
||||
embedInQuotes = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Replace all quotes in the original field with double quotes
|
||||
sbField.Replace("\"", "\"\"");
|
||||
|
||||
String ret = sbField.ToString();
|
||||
|
||||
if (embedInQuotes)
|
||||
{
|
||||
ret = "\"" + ret + "\"";
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,12 +8,6 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlContext
|
||||
/// </summary>
|
||||
public class SqlToolsSettings
|
||||
{
|
||||
// TODO: Is this needed? I can't make sense of this comment.
|
||||
// NOTE: This property is capitalized as 'SqlTools' because the
|
||||
// mode name sent from the client is written as 'SqlTools' and
|
||||
// JSON.net is using camelCasing.
|
||||
//public ServiceHostSettings SqlTools { get; set; }
|
||||
|
||||
public SqlToolsSettings()
|
||||
{
|
||||
this.ScriptAnalysis = new ScriptAnalysisSettings();
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
@@ -54,9 +55,29 @@ namespace Microsoft.SqlTools.EditorServices.Utility
|
||||
/// Optional. Specifies the minimum log message level to write to the log file.
|
||||
/// </param>
|
||||
public static void Initialize(
|
||||
string logFilePath = "SqlToolsService.log",
|
||||
string logFilePath = "sqltools",
|
||||
LogLevel minimumLogLevel = LogLevel.Normal)
|
||||
{
|
||||
// get a unique number to prevent conflicts of two process launching at the same time
|
||||
int uniqueId;
|
||||
try
|
||||
{
|
||||
uniqueId = Process.GetCurrentProcess().Id;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// if the pid look up fails for any reason, just use a random number
|
||||
uniqueId = new Random().Next(1000, 9999);
|
||||
}
|
||||
|
||||
// make the log path unique
|
||||
string fullFileName = string.Format(
|
||||
"{0}_{1,4:D4}{2,2:D2}{3,2:D2}{4,2:D2}{5,2:D2}{6,2:D2}{7}.log",
|
||||
logFilePath,
|
||||
DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day,
|
||||
DateTime.Now.Hour, DateTime.Now.Minute, DateTime.Now.Second,
|
||||
uniqueId);
|
||||
|
||||
if (logWriter != null)
|
||||
{
|
||||
logWriter.Dispose();
|
||||
@@ -66,7 +87,7 @@ namespace Microsoft.SqlTools.EditorServices.Utility
|
||||
logWriter =
|
||||
new LogWriter(
|
||||
minimumLogLevel,
|
||||
logFilePath,
|
||||
fullFileName,
|
||||
true);
|
||||
}
|
||||
|
||||
|
||||
62
src/Microsoft.SqlTools.ServiceLayer/Utility/TextUtilities.cs
Normal file
62
src/Microsoft.SqlTools.ServiceLayer/Utility/TextUtilities.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
//
|
||||
// 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.EditorServices.Utility
|
||||
{
|
||||
public static class TextUtilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Find the position of the previous delimeter for autocomplete token replacement.
|
||||
/// SQL Parser may have similar functionality in which case we'll delete this method.
|
||||
/// </summary>
|
||||
/// <param name="sql"></param>
|
||||
/// <param name="startRow"></param>
|
||||
/// <param name="startColumn"></param>
|
||||
/// <returns></returns>
|
||||
public static int PositionOfPrevDelimeter(string sql, int startRow, int startColumn)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(sql))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
int prevLineColumns = 0;
|
||||
for (int i = 0; i < startRow; ++i)
|
||||
{
|
||||
while (sql[prevLineColumns] != '\n' && prevLineColumns < sql.Length)
|
||||
{
|
||||
++prevLineColumns;
|
||||
}
|
||||
++prevLineColumns;
|
||||
}
|
||||
|
||||
startColumn += prevLineColumns;
|
||||
|
||||
if (startColumn - 1 < sql.Length)
|
||||
{
|
||||
while (--startColumn >= prevLineColumns)
|
||||
{
|
||||
if (sql[startColumn] == ' '
|
||||
|| sql[startColumn] == '\t'
|
||||
|| sql[startColumn] == '\n'
|
||||
|| sql[startColumn] == '.'
|
||||
|| sql[startColumn] == '+'
|
||||
|| sql[startColumn] == '-'
|
||||
|| sql[startColumn] == '*'
|
||||
|| sql[startColumn] == '>'
|
||||
|| sql[startColumn] == '<'
|
||||
|| sql[startColumn] == '='
|
||||
|| sql[startColumn] == '/'
|
||||
|| sql[startColumn] == '%')
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return startColumn + 1 - prevLineColumns;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -124,13 +124,28 @@ namespace Microsoft.SqlTools.EditorServices.Utility
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws ArgumentException if the value is null or an empty string.
|
||||
/// </summary>
|
||||
/// <param name="parameterName">The name of the parameter being validated.</param>
|
||||
/// <param name="valueToCheck">The value of the parameter being validated.</param>
|
||||
public static void IsNotNullOrEmptyString(string parameterName, string valueToCheck)
|
||||
{
|
||||
if (string.IsNullOrEmpty(valueToCheck))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"Parameter contains a null, empty, or whitespace string.",
|
||||
parameterName);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws ArgumentException if the value is null, an empty string,
|
||||
/// or a string containing only whitespace.
|
||||
/// </summary>
|
||||
/// <param name="parameterName">The name of the parameter being validated.</param>
|
||||
/// <param name="valueToCheck">The value of the parameter being validated.</param>
|
||||
public static void IsNotNullOrEmptyString(string parameterName, string valueToCheck)
|
||||
public static void IsNotNullOrWhitespaceString(string parameterName, string valueToCheck)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(valueToCheck))
|
||||
{
|
||||
|
||||
@@ -61,7 +61,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace
|
||||
/// </exception>
|
||||
public ScriptFile GetFile(string filePath)
|
||||
{
|
||||
Validate.IsNotNullOrEmptyString("filePath", filePath);
|
||||
Validate.IsNotNullOrWhitespaceString("filePath", filePath);
|
||||
|
||||
// Resolve the full file path
|
||||
string resolvedFilePath = this.ResolveFilePath(filePath);
|
||||
@@ -153,7 +153,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace
|
||||
/// <returns></returns>
|
||||
public ScriptFile GetFileBuffer(string filePath, string initialBuffer)
|
||||
{
|
||||
Validate.IsNotNullOrEmptyString("filePath", filePath);
|
||||
Validate.IsNotNullOrWhitespaceString("filePath", filePath);
|
||||
|
||||
// Resolve the full file path
|
||||
string resolvedFilePath = this.ResolveFilePath(filePath);
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"System.Security.SecureString": "4.0.0",
|
||||
"System.Collections.Specialized": "4.0.1",
|
||||
"System.ComponentModel.TypeConverter": "4.1.0",
|
||||
"System.Diagnostics.Contracts": "4.0.1",
|
||||
"System.Diagnostics.TraceSource": "4.0.0",
|
||||
"NETStandard.Library": "1.6.0",
|
||||
"Microsoft.NETCore.Runtime.CoreCLR": "1.0.2",
|
||||
@@ -23,7 +24,12 @@
|
||||
},
|
||||
"frameworks": {
|
||||
"netcoreapp1.0": {
|
||||
"imports": "dnxcore50"
|
||||
"dependencies": {
|
||||
"Microsoft.NETCore.App": {
|
||||
"version": "1.0.0"
|
||||
}
|
||||
},
|
||||
"imports": "dnxcore50"
|
||||
}
|
||||
},
|
||||
"runtimes": {
|
||||
|
||||
@@ -17,6 +17,7 @@ dotnet build %WORKINGDIR%..\..\src\Microsoft.SqlTools.ServiceLayer\project.json
|
||||
|
||||
REM run the tests through OpenCover and generate a report
|
||||
"%WORKINGDIR%packages\OpenCover.4.6.519\tools\OpenCover.Console.exe" -register:user -target:dotnet.exe -targetargs:"test %WORKINGDIR%..\Microsoft.SqlTools.ServiceLayer.Test\project.json" -oldstyle -filter:"+[Microsoft.SqlTools.*]* -[xunit*]*" -output:coverage.xml -searchdirs:%WORKINGDIR%..\Microsoft.SqlTools.ServiceLayer.Test\bin\Debug\netcoreapp1.0
|
||||
"%WORKINGDIR%packages\OpenCoverToCoberturaConverter.0.2.4.0\tools\OpenCoverToCoberturaConverter.exe" -input:coverage.xml -output:outputCobertura.xml -sources:%WORKINGDIR%..\..\src\Microsoft.SqlTools.ServiceLayer
|
||||
"%WORKINGDIR%packages\ReportGenerator.2.4.5.0\tools\ReportGenerator.exe" "-reports:coverage.xml" "-targetdir:%WORKINGDIR%\reports"
|
||||
|
||||
REM restore original project.json
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="OpenCover" version="4.6.519" />
|
||||
<package id="OpenCoverToCoberturaConverter " version="0.2.4" />
|
||||
<package id="ReportGenerator" version="2.4.5" />
|
||||
</packages>
|
||||
|
||||
@@ -52,6 +52,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection
|
||||
return connectionMock.Object;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that we can connect to the default database when no database name is
|
||||
/// provided as a parameter.
|
||||
/// </summary>
|
||||
@@ -75,6 +76,43 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection
|
||||
Assert.NotEmpty(connectionResult.ConnectionId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that we can connect to the default database when no database name is
|
||||
/// provided as a parameter.
|
||||
/// </summary>
|
||||
[Theory]
|
||||
[InlineDataAttribute("master")]
|
||||
[InlineDataAttribute("nonMasterDb")]
|
||||
public void ConnectToDefaultDatabaseRespondsWithActualDbName(string expectedDbName)
|
||||
{
|
||||
// Given connecting with empty database name will return the expected DB name
|
||||
var connectionMock = new Mock<DbConnection> { CallBase = true };
|
||||
connectionMock.Setup(c => c.Database).Returns(expectedDbName);
|
||||
|
||||
var mockFactory = new Mock<ISqlConnectionFactory>();
|
||||
mockFactory.Setup(factory => factory.CreateSqlConnection(It.IsAny<string>()))
|
||||
.Returns(connectionMock.Object);
|
||||
|
||||
var connectionService = new ConnectionService(mockFactory.Object);
|
||||
|
||||
// When I connect with an empty DB name
|
||||
var connectionDetails = TestObjects.GetTestConnectionDetails();
|
||||
connectionDetails.DatabaseName = string.Empty;
|
||||
|
||||
var connectionResult =
|
||||
connectionService
|
||||
.Connect(new ConnectParams()
|
||||
{
|
||||
OwnerUri = "file:///my/test/file.sql",
|
||||
Connection = connectionDetails
|
||||
});
|
||||
|
||||
// Then I expect connection to succeed and the Summary to include the correct DB name
|
||||
Assert.NotEmpty(connectionResult.ConnectionId);
|
||||
Assert.NotNull(connectionResult.ConnectionSummary);
|
||||
Assert.Equal(expectedDbName, connectionResult.ConnectionSummary.DatabaseName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that when a connection is started for a URI with an already existing
|
||||
/// connection, we disconnect first before connecting.
|
||||
@@ -218,7 +256,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection
|
||||
|
||||
// check that the connection was successful
|
||||
Assert.NotEmpty(connectionResult.ConnectionId);
|
||||
Assert.Null(connectionResult.Messages);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -164,7 +164,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
|
||||
textDocument.Position.Character = 7;
|
||||
scriptFile.Contents = "select ";
|
||||
|
||||
var autoCompleteService = AutoCompleteService.Instance;
|
||||
var autoCompleteService = LanguageService.Instance;
|
||||
var completions = autoCompleteService.GetCompletionItems(
|
||||
textDocument,
|
||||
scriptFile,
|
||||
|
||||
@@ -0,0 +1,302 @@
|
||||
// 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.IO;
|
||||
using System.Threading.Tasks;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests for saving a result set to a file
|
||||
/// </summary>
|
||||
public class SaveResultsTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Test save results to a file as CSV with correct parameters
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void SaveResultsAsCsvSuccessTest()
|
||||
{
|
||||
// Execute a query
|
||||
var queryService = Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true);
|
||||
var executeParams = new QueryExecuteParams { QueryText = Common.StandardQuery, OwnerUri = Common.OwnerUri };
|
||||
var executeRequest = GetQueryExecuteResultContextMock(null, null, null);
|
||||
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait();
|
||||
|
||||
// Request to save the results as csv with correct parameters
|
||||
var saveParams = new SaveResultsAsCsvRequestParams { OwnerUri = Common.OwnerUri, ResultSetIndex = 0, BatchIndex = 0 };
|
||||
saveParams.FilePath = "testwrite.csv";
|
||||
saveParams.IncludeHeaders = true;
|
||||
SaveResultRequestResult result = null;
|
||||
var saveRequest = GetSaveResultsContextMock(qcr => result = qcr, null);
|
||||
queryService.ActiveQueries[Common.OwnerUri].Batches[0] = Common.GetBasicExecutedBatch();
|
||||
queryService.HandleSaveResultsAsCsvRequest(saveParams, saveRequest.Object).Wait();
|
||||
|
||||
// Expect to see a file successfully created in filepath and a success message
|
||||
Assert.Equal("Success", result.Messages);
|
||||
Assert.True(File.Exists(saveParams.FilePath));
|
||||
VerifySaveResultsCallCount(saveRequest, Times.Once(), Times.Never());
|
||||
|
||||
// Delete temp file after test
|
||||
if (File.Exists(saveParams.FilePath))
|
||||
{
|
||||
File.Delete(saveParams.FilePath);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test handling exception in saving results to CSV file
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void SaveResultsAsCsvExceptionTest()
|
||||
{
|
||||
// Execute a query
|
||||
var queryService = Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true);
|
||||
var executeParams = new QueryExecuteParams { QueryText = Common.StandardQuery, OwnerUri = Common.OwnerUri };
|
||||
var executeRequest = GetQueryExecuteResultContextMock(null, null, null);
|
||||
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait();
|
||||
|
||||
// Request to save the results as csv with incorrect filepath
|
||||
var saveParams = new SaveResultsAsCsvRequestParams { OwnerUri = Common.OwnerUri, ResultSetIndex = 0, BatchIndex = 0 };
|
||||
if (RuntimeInformation.IsOSPlatform( OSPlatform.Windows))
|
||||
{
|
||||
saveParams.FilePath = "G:\\test.csv";
|
||||
}
|
||||
else
|
||||
{
|
||||
saveParams.FilePath = "/test.csv";
|
||||
}
|
||||
// SaveResultRequestResult result = null;
|
||||
string errMessage = null;
|
||||
var saveRequest = GetSaveResultsContextMock( null, err => errMessage = (string) err);
|
||||
queryService.ActiveQueries[Common.OwnerUri].Batches[0] = Common.GetBasicExecutedBatch();
|
||||
queryService.HandleSaveResultsAsCsvRequest(saveParams, saveRequest.Object).Wait();
|
||||
|
||||
// Expect to see error message
|
||||
Assert.NotNull(errMessage);
|
||||
VerifySaveResultsCallCount(saveRequest, Times.Never(), Times.Once());
|
||||
Assert.False(File.Exists(saveParams.FilePath));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test saving results to CSV file when the requested result set is no longer active
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void SaveResultsAsCsvQueryNotFoundTest()
|
||||
{
|
||||
// Execute a query
|
||||
var queryService = Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true);
|
||||
var executeParams = new QueryExecuteParams { QueryText = Common.StandardQuery, OwnerUri = Common.OwnerUri };
|
||||
var executeRequest = GetQueryExecuteResultContextMock(null, null, null);
|
||||
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait();
|
||||
|
||||
// Request to save the results as csv with query that is no longer active
|
||||
var saveParams = new SaveResultsAsCsvRequestParams { OwnerUri = "falseuri", ResultSetIndex = 0, BatchIndex = 0 };
|
||||
saveParams.FilePath = "testwrite.csv";
|
||||
SaveResultRequestResult result = null;
|
||||
var saveRequest = GetSaveResultsContextMock(qcr => result = qcr, null);
|
||||
queryService.HandleSaveResultsAsCsvRequest(saveParams, saveRequest.Object).Wait();
|
||||
|
||||
// Expect message that save failed
|
||||
Assert.Equal("Failed to save results, ID not found.", result.Messages);
|
||||
Assert.False(File.Exists(saveParams.FilePath));
|
||||
VerifySaveResultsCallCount(saveRequest, Times.Once(), Times.Never());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test save results to a file as JSON with correct parameters
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void SaveResultsAsJsonSuccessTest()
|
||||
{
|
||||
// Execute a query
|
||||
var queryService = Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true);
|
||||
var executeParams = new QueryExecuteParams { QueryText = Common.StandardQuery, OwnerUri = Common.OwnerUri };
|
||||
var executeRequest = GetQueryExecuteResultContextMock(null, null, null);
|
||||
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait();
|
||||
|
||||
// Request to save the results as json with correct parameters
|
||||
var saveParams = new SaveResultsAsJsonRequestParams { OwnerUri = Common.OwnerUri, ResultSetIndex = 0, BatchIndex = 0 };
|
||||
saveParams.FilePath = "testwrite.json";
|
||||
SaveResultRequestResult result = null;
|
||||
var saveRequest = GetSaveResultsContextMock(qcr => result = qcr, null);
|
||||
queryService.ActiveQueries[Common.OwnerUri].Batches[0] = Common.GetBasicExecutedBatch();
|
||||
queryService.HandleSaveResultsAsJsonRequest(saveParams, saveRequest.Object).Wait();
|
||||
|
||||
// Expect to see a file successfully created in filepath and a success message
|
||||
Assert.Equal("Success", result.Messages);
|
||||
Assert.True(File.Exists(saveParams.FilePath));
|
||||
VerifySaveResultsCallCount(saveRequest, Times.Once(), Times.Never());
|
||||
|
||||
// Delete temp file after test
|
||||
if (File.Exists(saveParams.FilePath))
|
||||
{
|
||||
File.Delete(saveParams.FilePath);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test handling exception in saving results to JSON file
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void SaveResultsAsJsonExceptionTest()
|
||||
{
|
||||
// Execute a query
|
||||
var queryService = Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true);
|
||||
var executeParams = new QueryExecuteParams { QueryText = Common.StandardQuery, OwnerUri = Common.OwnerUri };
|
||||
var executeRequest = GetQueryExecuteResultContextMock(null, null, null);
|
||||
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait();
|
||||
|
||||
// Request to save the results as json with incorrect filepath
|
||||
var saveParams = new SaveResultsAsJsonRequestParams { OwnerUri = Common.OwnerUri, ResultSetIndex = 0, BatchIndex = 0 };
|
||||
if (RuntimeInformation.IsOSPlatform( OSPlatform.Windows))
|
||||
{
|
||||
saveParams.FilePath = "G:\\test.json";
|
||||
}
|
||||
else
|
||||
{
|
||||
saveParams.FilePath = "/test.json";
|
||||
}
|
||||
// SaveResultRequestResult result = null;
|
||||
string errMessage = null;
|
||||
var saveRequest = GetSaveResultsContextMock( null, err => errMessage = (string) err);
|
||||
queryService.ActiveQueries[Common.OwnerUri].Batches[0] = Common.GetBasicExecutedBatch();
|
||||
queryService.HandleSaveResultsAsJsonRequest(saveParams, saveRequest.Object).Wait();
|
||||
|
||||
// Expect to see error message
|
||||
Assert.NotNull(errMessage);
|
||||
VerifySaveResultsCallCount(saveRequest, Times.Never(), Times.Once());
|
||||
Assert.False(File.Exists(saveParams.FilePath));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test saving results to JSON file when the requested result set is no longer active
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void SaveResultsAsJsonQueryNotFoundTest()
|
||||
{
|
||||
// Execute a query
|
||||
var queryService = Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true);
|
||||
var executeParams = new QueryExecuteParams { QueryText = Common.StandardQuery, OwnerUri = Common.OwnerUri };
|
||||
var executeRequest = GetQueryExecuteResultContextMock(null, null, null);
|
||||
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait();
|
||||
|
||||
// Request to save the results as json with query that is no longer active
|
||||
var saveParams = new SaveResultsAsJsonRequestParams { OwnerUri = "falseuri", ResultSetIndex = 0, BatchIndex = 0 };
|
||||
saveParams.FilePath = "testwrite.json";
|
||||
SaveResultRequestResult result = null;
|
||||
var saveRequest = GetSaveResultsContextMock(qcr => result = qcr, null);
|
||||
queryService.HandleSaveResultsAsJsonRequest(saveParams, saveRequest.Object).Wait();
|
||||
|
||||
// Expect message that save failed
|
||||
Assert.Equal("Failed to save results, ID not found.", result.Messages);
|
||||
Assert.False(File.Exists(saveParams.FilePath));
|
||||
VerifySaveResultsCallCount(saveRequest, Times.Once(), Times.Never());
|
||||
}
|
||||
|
||||
#region Mocking
|
||||
|
||||
/// <summary>
|
||||
/// Mock the requestContext for saving a result set
|
||||
/// </summary>
|
||||
/// <param name="resultCallback"></param>
|
||||
/// <param name="errorCallback"></param>
|
||||
/// <returns></returns>
|
||||
private static Mock<RequestContext<SaveResultRequestResult>> GetSaveResultsContextMock(
|
||||
Action<SaveResultRequestResult> resultCallback,
|
||||
Action<object> errorCallback)
|
||||
{
|
||||
var requestContext = new Mock<RequestContext<SaveResultRequestResult>>();
|
||||
|
||||
// Setup the mock for SendResult
|
||||
var sendResultFlow = requestContext
|
||||
.Setup(rc => rc.SendResult(It.IsAny<SaveResultRequestResult> ()))
|
||||
.Returns(Task.FromResult(0));
|
||||
if (resultCallback != null)
|
||||
{
|
||||
sendResultFlow.Callback(resultCallback);
|
||||
}
|
||||
|
||||
// Setup the mock for SendError
|
||||
var sendErrorFlow = requestContext
|
||||
.Setup(rc => rc.SendError(It.IsAny<object>()))
|
||||
.Returns(Task.FromResult(0));
|
||||
if (errorCallback != null)
|
||||
{
|
||||
sendErrorFlow.Callback(errorCallback);
|
||||
}
|
||||
|
||||
return requestContext;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify the call count for sendResult and error
|
||||
/// </summary>
|
||||
/// <param name="mock"></param>
|
||||
/// <param name="sendResultCalls"></param>
|
||||
/// <param name="sendErrorCalls"></param>
|
||||
private static void VerifySaveResultsCallCount(Mock<RequestContext<SaveResultRequestResult>> mock,
|
||||
Times sendResultCalls, Times sendErrorCalls)
|
||||
{
|
||||
mock.Verify(rc => rc.SendResult(It.IsAny<SaveResultRequestResult>()), sendResultCalls);
|
||||
mock.Verify(rc => rc.SendError(It.IsAny<object>()), sendErrorCalls);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mock request context for executing a query
|
||||
/// </summary>
|
||||
/// <param name="resultCallback"></param>
|
||||
/// <param name="Action<EventType<QueryExecuteCompleteParams>"></param>
|
||||
/// <param name="eventCallback"></param>
|
||||
/// <param name="errorCallback"></param>
|
||||
/// <returns></returns>
|
||||
public static Mock<RequestContext<QueryExecuteResult>> GetQueryExecuteResultContextMock(
|
||||
Action<QueryExecuteResult> resultCallback,
|
||||
Action<EventType<QueryExecuteCompleteParams>, QueryExecuteCompleteParams> eventCallback,
|
||||
Action<object> errorCallback)
|
||||
{
|
||||
var requestContext = new Mock<RequestContext<QueryExecuteResult>>();
|
||||
|
||||
// Setup the mock for SendResult
|
||||
var sendResultFlow = requestContext
|
||||
.Setup(rc => rc.SendResult(It.IsAny<QueryExecuteResult>()))
|
||||
.Returns(Task.FromResult(0));
|
||||
if (resultCallback != null)
|
||||
{
|
||||
sendResultFlow.Callback(resultCallback);
|
||||
}
|
||||
|
||||
// Setup the mock for SendEvent
|
||||
var sendEventFlow = requestContext.Setup(rc => rc.SendEvent(
|
||||
It.Is<EventType<QueryExecuteCompleteParams>>(m => m == QueryExecuteCompleteEvent.Type),
|
||||
It.IsAny<QueryExecuteCompleteParams>()))
|
||||
.Returns(Task.FromResult(0));
|
||||
if (eventCallback != null)
|
||||
{
|
||||
sendEventFlow.Callback(eventCallback);
|
||||
}
|
||||
|
||||
// Setup the mock for SendError
|
||||
var sendErrorFlow = requestContext.Setup(rc => rc.SendError(It.IsAny<object>()))
|
||||
.Returns(Task.FromResult(0));
|
||||
if (errorCallback != null)
|
||||
{
|
||||
sendErrorFlow.Callback(errorCallback);
|
||||
}
|
||||
|
||||
return requestContext;
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
@@ -89,7 +89,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
#region Service Intergration Tests
|
||||
|
||||
[Fact]
|
||||
public void SubsetServiceValidTest()
|
||||
public async Task SubsetServiceValidTest()
|
||||
{
|
||||
// If:
|
||||
// ... I have a query that has results (doesn't matter what)
|
||||
@@ -97,13 +97,13 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
Common.CreateMockFactory(new[] {Common.StandardTestData}, false), true);
|
||||
var executeParams = new QueryExecuteParams {QueryText = "Doesn'tMatter", OwnerUri = Common.OwnerUri};
|
||||
var executeRequest = RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(null, QueryExecuteCompleteEvent.Type, null, null);
|
||||
queryService.HandleExecuteRequest(executeParams, executeRequest.Object).Wait();
|
||||
await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
|
||||
|
||||
// ... And I then ask for a valid set of results from it
|
||||
var subsetParams = new QueryExecuteSubsetParams {OwnerUri = Common.OwnerUri, RowsCount = 1, ResultSetIndex = 0, RowsStartIndex = 0};
|
||||
QueryExecuteSubsetResult result = null;
|
||||
var subsetRequest = GetQuerySubsetResultContextMock(qesr => result = qesr, null);
|
||||
queryService.HandleResultSubsetRequest(subsetParams, subsetRequest.Object).Wait();
|
||||
await queryService.HandleResultSubsetRequest(subsetParams, subsetRequest.Object);
|
||||
|
||||
// Then:
|
||||
// ... I should have a successful result
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
//
|
||||
// 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.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.SqlTools.EditorServices.Utility;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Test.ServiceHost
|
||||
{
|
||||
/// <summary>
|
||||
/// Logger test cases
|
||||
/// </summary>
|
||||
public class LoggerTests
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Test to verify that the logger initialization is generating a valid file
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void LoggerDefaultFile()
|
||||
{
|
||||
// delete any existing log files from the current directory
|
||||
Directory.GetFiles(Directory.GetCurrentDirectory())
|
||||
.Where(fileName
|
||||
=> fileName.Contains("sqltools_")
|
||||
&& fileName.EndsWith(".log", StringComparison.OrdinalIgnoreCase))
|
||||
.ToList()
|
||||
.ForEach(fileName => File.Delete(fileName));
|
||||
|
||||
// initialize the logger
|
||||
Logger.Initialize(
|
||||
logFilePath: Path.Combine(Directory.GetCurrentDirectory(), "sqltools"),
|
||||
minimumLogLevel: LogLevel.Verbose);
|
||||
|
||||
// close the logger
|
||||
Logger.Close();
|
||||
|
||||
// find the name of the new log file
|
||||
string logFileName = Directory.GetFiles(Directory.GetCurrentDirectory())
|
||||
.Where(fileName
|
||||
=> fileName.Contains("sqltools_")
|
||||
&& fileName.EndsWith(".log", StringComparison.OrdinalIgnoreCase)).SingleOrDefault();
|
||||
|
||||
// validate the log file was created with desired name
|
||||
Assert.True(!string.IsNullOrWhiteSpace(logFileName));
|
||||
if (!string.IsNullOrWhiteSpace(logFileName))
|
||||
{
|
||||
Assert.True(logFileName.Length > "sqltools_.log".Length);
|
||||
Assert.True(File.Exists(logFileName));
|
||||
|
||||
// delete the test log file
|
||||
if (File.Exists(logFileName))
|
||||
{
|
||||
File.Delete(logFileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -67,14 +67,6 @@ namespace Microsoft.SqlTools.Test.Utility
|
||||
return new LanguageService();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a test autocomplete service instance
|
||||
/// </summary>
|
||||
public static AutoCompleteService GetAutoCompleteService()
|
||||
{
|
||||
return AutoCompleteService.Instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a test sql connection factory instance
|
||||
/// </summary>
|
||||
|
||||
Reference in New Issue
Block a user