mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-29 09:35:38 -05:00
Merge branch 'release/preview2' for candidate release
This commit is contained in:
7
.mention-bot
Normal file
7
.mention-bot
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"maxReviewers": 3,
|
||||
"requiredOrgs": ["Microsoft"],
|
||||
"skipAlreadyAssignedPR": true,
|
||||
"skipAlreadyMentionedPR": true,
|
||||
"skipCollaboratorPR": false
|
||||
}
|
||||
12
README.md
12
README.md
@@ -3,7 +3,6 @@ The SQL Tools Service is an application that provides core functionality for var
|
||||
* Connection management
|
||||
* Language Service support using VS Code protocol
|
||||
* Query execution and resultset management
|
||||
* Schema discovery
|
||||
|
||||
# Contribution Guidelines
|
||||
|
||||
@@ -130,12 +129,8 @@ on this check so that our project will always have good generated documentation.
|
||||
|
||||
- **The build and unit tests must run green**
|
||||
|
||||
When you submit your pull request, our automated build system on AppVeyor will attempt to run a
|
||||
Release build of your changes and then run all unit tests against the build. If you notice that
|
||||
any of your unit tests have failed, please fix them by creating a new commit and then pushing it
|
||||
to your branch. If you see that some unrelated test has failed, try re-running the build for your
|
||||
pull request. If you continue to see issues, write a comment on the pull request and we will
|
||||
look into it.
|
||||
Run all unit tests and code coverage tests to ensure all tests are passing and code coverage numbers
|
||||
and not negatively impacted by your change.
|
||||
|
||||
- **Respond to code review feedback**
|
||||
|
||||
@@ -147,6 +142,5 @@ on this check so that our project will always have good generated documentation.
|
||||
|
||||
Once your final changes have been accepted, we may ask you to do a final rebase to have your commits
|
||||
so that they follow our commit guidelines. If specific guidance is given, please follow it when
|
||||
rebasing your commits. Once you do your final push and we see the AppVeyor build pass, we will
|
||||
merge your changes!
|
||||
rebasing your commits. Once you do your final push we will merge your changes!
|
||||
|
||||
|
||||
17
RefreshDllsForTestRun.cmd
Normal file
17
RefreshDllsForTestRun.cmd
Normal file
@@ -0,0 +1,17 @@
|
||||
SET WORKINGDIR=%~dp0
|
||||
SET _TargetLocation=%1
|
||||
SET _BuildConfiguration=%2
|
||||
IF [%_BuildConfiguration%] NEQ [] GOTO Start
|
||||
SET _BuildConfiguration=Debug
|
||||
|
||||
:Start
|
||||
SET _PerfTestSourceLocation="%WORKINGDIR%\test\Microsoft.SqlTools.ServiceLayer.PerfTests\bin\%_BuildConfiguration%\netcoreapp1.0\win7-x64\publish"
|
||||
SET _ServiceSourceLocation="%WORKINGDIR%\src\Microsoft.SqlTools.ServiceLayer\bin\%_BuildConfiguration%\netcoreapp1.0\win7-x64\publish"
|
||||
|
||||
|
||||
|
||||
dotnet publish %WORKINGDIR%\test\Microsoft.SqlTools.ServiceLayer.PerfTests -c %_BuildConfiguration%
|
||||
dotnet publish %WORKINGDIR%\src\Microsoft.SqlTools.ServiceLayer -c %_BuildConfiguration%
|
||||
|
||||
XCOPY /i /E /y %_PerfTestSourceLocation% "%_TargetLocation%\Tests"
|
||||
XCOPY /i /E /y %_ServiceSourceLocation% "%_TargetLocation%\Microsoft.SqlTools.ServiceLayer"
|
||||
@@ -19,6 +19,8 @@ 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("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.SqlTools.ServiceLayer.TestDriver", "test\Microsoft.SqlTools.ServiceLayer.TestDriver\Microsoft.SqlTools.ServiceLayer.TestDriver.xproj", "{CC785604-6277-4878-8DA9-360C47158E96}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "scripts", "scripts", "{B7D21727-2926-452B-9610-3ADB0BB6D789}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
scripts\archiving.cake = scripts\archiving.cake
|
||||
@@ -38,6 +40,19 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{F9978D78
|
||||
build.sh = build.sh
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CodeCoverage", "CodeCoverage", "{87D9C7D9-18F4-4AB9-B20D-66C02B6075E2}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
test\CodeCoverage\codecoverage.bat = test\CodeCoverage\codecoverage.bat
|
||||
test\CodeCoverage\gulpfile.js = test\CodeCoverage\gulpfile.js
|
||||
test\CodeCoverage\nuget.config = test\CodeCoverage\nuget.config
|
||||
test\CodeCoverage\package.json = test\CodeCoverage\package.json
|
||||
test\CodeCoverage\packages.config = test\CodeCoverage\packages.config
|
||||
test\CodeCoverage\ReplaceText.vbs = test\CodeCoverage\ReplaceText.vbs
|
||||
test\CodeCoverage\runintegration.bat = test\CodeCoverage\runintegration.bat
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.SqlTools.ServiceLayer.PerfTests", "test\Microsoft.SqlTools.ServiceLayer.PerfTests\Microsoft.SqlTools.ServiceLayer.PerfTests.xproj", "{7E5968AB-83D7-4738-85A2-416A50F13D2F}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -52,6 +67,14 @@ Global
|
||||
{2D771D16-9D85-4053-9F79-E2034737DEEF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{2D771D16-9D85-4053-9F79-E2034737DEEF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{2D771D16-9D85-4053-9F79-E2034737DEEF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{CC785604-6277-4878-8DA9-360C47158E96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{CC785604-6277-4878-8DA9-360C47158E96}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{CC785604-6277-4878-8DA9-360C47158E96}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{CC785604-6277-4878-8DA9-360C47158E96}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7E5968AB-83D7-4738-85A2-416A50F13D2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7E5968AB-83D7-4738-85A2-416A50F13D2F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7E5968AB-83D7-4738-85A2-416A50F13D2F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7E5968AB-83D7-4738-85A2-416A50F13D2F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -59,6 +82,9 @@ Global
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{0D61DC2B-DA66-441D-B9D0-F76C98F780F9} = {2BBD7364-054F-4693-97CD-1C395E3E84A9}
|
||||
{2D771D16-9D85-4053-9F79-E2034737DEEF} = {AB9CA2B8-6F70-431C-8A1D-67479D8A7BE4}
|
||||
{CC785604-6277-4878-8DA9-360C47158E96} = {AB9CA2B8-6F70-431C-8A1D-67479D8A7BE4}
|
||||
{B7D21727-2926-452B-9610-3ADB0BB6D789} = {F9978D78-78FE-4E92-A7D6-D436B7683EF6}
|
||||
{87D9C7D9-18F4-4AB9-B20D-66C02B6075E2} = {AB9CA2B8-6F70-431C-8A1D-67479D8A7BE4}
|
||||
{7E5968AB-83D7-4738-85A2-416A50F13D2F} = {AB9CA2B8-6F70-431C-8A1D-67479D8A7BE4}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
|
||||
OwnerUri = ownerUri;
|
||||
ConnectionDetails = details;
|
||||
ConnectionId = Guid.NewGuid();
|
||||
IntellisenseMetrics = new InteractionMetrics<double>(new int[] { 50, 100, 200, 500, 1000, 2000 });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -49,5 +50,15 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
|
||||
/// The connection to the SQL database that commands will be run against.
|
||||
/// </summary>
|
||||
public DbConnection SqlConnection { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Intellisense Metrics
|
||||
/// </summary>
|
||||
public InteractionMetrics<double> IntellisenseMetrics { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns true is the db connection is to a SQL db
|
||||
/// </summary>
|
||||
public bool IsAzure { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
||||
@@ -29,7 +30,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
|
||||
/// <summary>
|
||||
/// Singleton service instance
|
||||
/// </summary>
|
||||
private static Lazy<ConnectionService> instance
|
||||
private static readonly Lazy<ConnectionService> instance
|
||||
= new Lazy<ConnectionService>(() => new ConnectionService());
|
||||
|
||||
/// <summary>
|
||||
@@ -48,11 +49,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
|
||||
/// </summary>
|
||||
private ISqlConnectionFactory connectionFactory;
|
||||
|
||||
private Dictionary<string, ConnectionInfo> ownerToConnectionMap = new Dictionary<string, ConnectionInfo>();
|
||||
private readonly Dictionary<string, ConnectionInfo> ownerToConnectionMap = new Dictionary<string, ConnectionInfo>();
|
||||
|
||||
private ConcurrentDictionary<string, CancellationTokenSource> ownerToCancellationTokenSourceMap = new ConcurrentDictionary<string, CancellationTokenSource>();
|
||||
private readonly ConcurrentDictionary<string, CancellationTokenSource> ownerToCancellationTokenSourceMap = new ConcurrentDictionary<string, CancellationTokenSource>();
|
||||
|
||||
private Object cancellationTokenSourceLock = new Object();
|
||||
private readonly object cancellationTokenSourceLock = new object();
|
||||
|
||||
/// <summary>
|
||||
/// Map from script URIs to ConnectionInfo objects
|
||||
@@ -77,9 +78,10 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor is private since it's a singleton class
|
||||
/// Default constructor should be private since it's a singleton class, but we need a constructor
|
||||
/// for use in unit test mocking.
|
||||
/// </summary>
|
||||
private ConnectionService()
|
||||
public ConnectionService()
|
||||
{
|
||||
}
|
||||
|
||||
@@ -129,7 +131,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
|
||||
}
|
||||
|
||||
// Attempts to link a URI to an actively used connection for this URI
|
||||
public bool TryFindConnection(string ownerUri, out ConnectionInfo connectionInfo)
|
||||
public virtual bool TryFindConnection(string ownerUri, out ConnectionInfo connectionInfo)
|
||||
{
|
||||
return this.ownerToConnectionMap.TryGetValue(ownerUri, out connectionInfo);
|
||||
}
|
||||
@@ -172,21 +174,16 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
|
||||
connectionInfo = new ConnectionInfo(ConnectionFactory, connectionParams.OwnerUri, connectionParams.Connection);
|
||||
|
||||
// try to connect
|
||||
var response = new ConnectionCompleteParams();
|
||||
response.OwnerUri = connectionParams.OwnerUri;
|
||||
var response = new ConnectionCompleteParams {OwnerUri = connectionParams.OwnerUri};
|
||||
CancellationTokenSource source = null;
|
||||
try
|
||||
{
|
||||
// build the connection string from the input parameters
|
||||
string connectionString = ConnectionService.BuildConnectionString(connectionInfo.ConnectionDetails);
|
||||
string connectionString = BuildConnectionString(connectionInfo.ConnectionDetails);
|
||||
|
||||
// create a sql connection instance
|
||||
connectionInfo.SqlConnection = connectionInfo.Factory.CreateSqlConnection(connectionString);
|
||||
|
||||
// turning on MARS to avoid break in LanguageService with multiple editors
|
||||
// we'll remove this once ConnectionService is refactored to not own the LanguageService connection
|
||||
connectionInfo.ConnectionDetails.MultipleActiveResultSets = true;
|
||||
|
||||
// Add a cancellation token source so that the connection OpenAsync() can be cancelled
|
||||
using (source = new CancellationTokenSource())
|
||||
{
|
||||
@@ -264,7 +261,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
|
||||
// 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()
|
||||
response.ConnectionSummary = new ConnectionSummary
|
||||
{
|
||||
ServerName = connectionInfo.ConnectionDetails.ServerName,
|
||||
DatabaseName = connectionInfo.ConnectionDetails.DatabaseName,
|
||||
@@ -272,7 +269,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
|
||||
};
|
||||
|
||||
// invoke callback notifications
|
||||
invokeOnConnectionActivities(connectionInfo);
|
||||
InvokeOnConnectionActivities(connectionInfo);
|
||||
|
||||
// try to get information about the connected SQL Server instance
|
||||
try
|
||||
@@ -281,7 +278,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
|
||||
DbConnection connection = reliableConnection != null ? reliableConnection.GetUnderlyingConnection() : connectionInfo.SqlConnection;
|
||||
|
||||
ReliableConnectionHelper.ServerInfo serverInfo = ReliableConnectionHelper.GetServerVersion(connection);
|
||||
response.ServerInfo = new Contracts.ServerInfo()
|
||||
response.ServerInfo = new ServerInfo
|
||||
{
|
||||
ServerMajorVersion = serverInfo.ServerMajorVersion,
|
||||
ServerMinorVersion = serverInfo.ServerMinorVersion,
|
||||
@@ -294,6 +291,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
|
||||
AzureVersion = serverInfo.AzureVersion,
|
||||
OsVersion = serverInfo.OsVersion
|
||||
};
|
||||
connectionInfo.IsAzure = serverInfo.IsCloud;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
@@ -360,6 +358,23 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ServiceHost != null)
|
||||
{
|
||||
// Send a telemetry notification for intellisense performance metrics
|
||||
ServiceHost.SendEvent(TelemetryNotification.Type, new TelemetryParams()
|
||||
{
|
||||
Params = new TelemetryProperties
|
||||
{
|
||||
Properties = new Dictionary<string, string>
|
||||
{
|
||||
{ "IsAzure", info.IsAzure ? "1" : "0" }
|
||||
},
|
||||
EventName = TelemetryEventNames.IntellisenseQuantile,
|
||||
Measures = info.IntellisenseMetrics.Quantile
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Close the connection
|
||||
info.SqlConnection.Close();
|
||||
|
||||
@@ -402,7 +417,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
|
||||
connection.Open();
|
||||
|
||||
List<string> results = new List<string>();
|
||||
var systemDatabases = new string[] {"master", "model", "msdb", "tempdb"};
|
||||
var systemDatabases = new[] {"master", "model", "msdb", "tempdb"};
|
||||
using (DbCommand command = connection.CreateCommand())
|
||||
{
|
||||
command.CommandText = "SELECT name FROM sys.databases ORDER BY name ASC";
|
||||
@@ -476,7 +491,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
|
||||
|
||||
try
|
||||
{
|
||||
RunConnectRequestHandlerTask(connectParams, requestContext);
|
||||
RunConnectRequestHandlerTask(connectParams);
|
||||
await requestContext.SendResult(true);
|
||||
}
|
||||
catch
|
||||
@@ -485,7 +500,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
|
||||
}
|
||||
}
|
||||
|
||||
private void RunConnectRequestHandlerTask(ConnectParams connectParams, RequestContext<bool> requestContext)
|
||||
private void RunConnectRequestHandlerTask(ConnectParams connectParams)
|
||||
{
|
||||
// create a task to connect asynchronously so that other requests are not blocked in the meantime
|
||||
Task.Run(async () =>
|
||||
@@ -493,7 +508,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
|
||||
try
|
||||
{
|
||||
// open connection based on request details
|
||||
ConnectionCompleteParams result = await ConnectionService.Instance.Connect(connectParams);
|
||||
ConnectionCompleteParams result = await Instance.Connect(connectParams);
|
||||
await ServiceHost.SendEvent(ConnectionCompleteNotification.Type, result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -518,7 +533,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
|
||||
|
||||
try
|
||||
{
|
||||
bool result = ConnectionService.Instance.CancelConnect(cancelParams);
|
||||
bool result = Instance.CancelConnect(cancelParams);
|
||||
await requestContext.SendResult(result);
|
||||
}
|
||||
catch(Exception ex)
|
||||
@@ -538,7 +553,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
|
||||
|
||||
try
|
||||
{
|
||||
bool result = ConnectionService.Instance.Disconnect(disconnectParams);
|
||||
bool result = Instance.Disconnect(disconnectParams);
|
||||
await requestContext.SendResult(result);
|
||||
}
|
||||
catch(Exception ex)
|
||||
@@ -559,7 +574,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
|
||||
|
||||
try
|
||||
{
|
||||
ListDatabasesResponse result = ConnectionService.Instance.ListDatabases(listDatabasesParams);
|
||||
ListDatabasesResponse result = Instance.ListDatabases(listDatabasesParams);
|
||||
await requestContext.SendResult(result);
|
||||
}
|
||||
catch(Exception ex)
|
||||
@@ -582,10 +597,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
|
||||
/// <param name="connectionDetails"></param>
|
||||
public static string BuildConnectionString(ConnectionDetails connectionDetails)
|
||||
{
|
||||
SqlConnectionStringBuilder connectionBuilder = new SqlConnectionStringBuilder();
|
||||
connectionBuilder["Data Source"] = connectionDetails.ServerName;
|
||||
connectionBuilder["User Id"] = connectionDetails.UserName;
|
||||
connectionBuilder["Password"] = connectionDetails.Password;
|
||||
SqlConnectionStringBuilder connectionBuilder = new SqlConnectionStringBuilder
|
||||
{
|
||||
["Data Source"] = connectionDetails.ServerName,
|
||||
["User Id"] = connectionDetails.UserName,
|
||||
["Password"] = connectionDetails.Password
|
||||
};
|
||||
|
||||
// Check for any optional parameters
|
||||
if (!string.IsNullOrEmpty(connectionDetails.DatabaseName))
|
||||
@@ -725,7 +742,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
|
||||
|
||||
// Fire a connection changed event
|
||||
ConnectionChangedParams parameters = new ConnectionChangedParams();
|
||||
ConnectionSummary summary = (ConnectionSummary)(info.ConnectionDetails);
|
||||
ConnectionSummary summary = info.ConnectionDetails;
|
||||
parameters.Connection = summary.Clone();
|
||||
parameters.OwnerUri = ownerUri;
|
||||
ServiceHost.SendEvent(ConnectionChangedNotification.Type, parameters);
|
||||
@@ -744,7 +761,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
|
||||
}
|
||||
}
|
||||
|
||||
private void invokeOnConnectionActivities(ConnectionInfo connectionInfo)
|
||||
private void InvokeOnConnectionActivities(ConnectionInfo connectionInfo)
|
||||
{
|
||||
foreach (var activity in this.onConnectionActivities)
|
||||
{
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
|
||||
/// <summary>
|
||||
/// Provides the error detection logic for temporary faults that are commonly found during data transfer.
|
||||
/// </summary>
|
||||
internal sealed class DataTransferErrorDetectionStrategy : ErrorDetectionStrategyBase, IErrorDetectionStrategy
|
||||
internal class DataTransferErrorDetectionStrategy : ErrorDetectionStrategyBase, IErrorDetectionStrategy
|
||||
{
|
||||
private static readonly DataTransferErrorDetectionStrategy instance = new DataTransferErrorDetectionStrategy();
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
|
||||
/// 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
|
||||
internal class SqlAzureTemporaryAndIgnorableErrorDetectionStrategy : ErrorDetectionStrategyBase, IErrorDetectionStrategy
|
||||
{
|
||||
/// <summary>
|
||||
/// Azure error that can be ignored
|
||||
|
||||
@@ -533,7 +533,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
|
||||
return new RetryStateEx { TotalRetryTime = TimeSpan.Zero };
|
||||
}
|
||||
|
||||
private sealed class RetryStateEx : RetryState
|
||||
internal sealed class RetryStateEx : RetryState
|
||||
{
|
||||
public TimeSpan TotalRetryTime { get; set; }
|
||||
}
|
||||
|
||||
@@ -390,7 +390,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
|
||||
return retryPolicy;
|
||||
}
|
||||
|
||||
private static void DataConnectionFailureRetry(RetryState retryState)
|
||||
internal static void DataConnectionFailureRetry(RetryState retryState)
|
||||
{
|
||||
Logger.Write(LogLevel.Normal, string.Format(CultureInfo.InvariantCulture,
|
||||
"Connection retry number {0}. Delaying {1} ms before retry. Exception: {2}",
|
||||
@@ -401,7 +401,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
|
||||
RetryPolicyUtils.RaiseAmbientRetryMessage(retryState, SqlSchemaModelErrorCodes.ServiceActions.ConnectionRetry);
|
||||
}
|
||||
|
||||
private static void CommandFailureRetry(RetryState retryState, string commandKeyword)
|
||||
internal static void CommandFailureRetry(RetryState retryState, string commandKeyword)
|
||||
{
|
||||
Logger.Write(LogLevel.Normal, string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
@@ -414,7 +414,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
|
||||
RetryPolicyUtils.RaiseAmbientRetryMessage(retryState, SqlSchemaModelErrorCodes.ServiceActions.CommandRetry);
|
||||
}
|
||||
|
||||
private static void CommandFailureIgnore(RetryState retryState, string commandKeyword)
|
||||
internal static void CommandFailureIgnore(RetryState retryState, string commandKeyword)
|
||||
{
|
||||
Logger.Write(LogLevel.Normal, string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
@@ -426,32 +426,32 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
|
||||
RetryPolicyUtils.RaiseAmbientIgnoreMessage(retryState, SqlSchemaModelErrorCodes.ServiceActions.CommandRetry);
|
||||
}
|
||||
|
||||
private static void CommandFailureRetry(RetryState retryState)
|
||||
internal static void CommandFailureRetry(RetryState retryState)
|
||||
{
|
||||
CommandFailureRetry(retryState, "Command");
|
||||
}
|
||||
|
||||
private static void CommandFailureIgnore(RetryState retryState)
|
||||
internal static void CommandFailureIgnore(RetryState retryState)
|
||||
{
|
||||
CommandFailureIgnore(retryState, "Command");
|
||||
}
|
||||
|
||||
private static void CreateDatabaseCommandFailureRetry(RetryState retryState)
|
||||
internal static void CreateDatabaseCommandFailureRetry(RetryState retryState)
|
||||
{
|
||||
CommandFailureRetry(retryState, "Database Command");
|
||||
}
|
||||
|
||||
private static void CreateDatabaseCommandFailureIgnore(RetryState retryState)
|
||||
internal static void CreateDatabaseCommandFailureIgnore(RetryState retryState)
|
||||
{
|
||||
CommandFailureIgnore(retryState, "Database Command");
|
||||
}
|
||||
|
||||
private static void ElementCommandFailureRetry(RetryState retryState)
|
||||
internal static void ElementCommandFailureRetry(RetryState retryState)
|
||||
{
|
||||
CommandFailureRetry(retryState, "Element Command");
|
||||
}
|
||||
|
||||
private static void ElementCommandFailureIgnore(RetryState retryState)
|
||||
internal static void ElementCommandFailureIgnore(RetryState retryState)
|
||||
{
|
||||
CommandFailureIgnore(retryState, "Element Command");
|
||||
}
|
||||
|
||||
@@ -271,21 +271,21 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
|
||||
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 MemoryOptimizedObjectsValidation_NonMemoryOptimizedTableCannotBeAccessed = ValidationBaseCode + 68;
|
||||
public const int MemoryOptimizedObjectsValidation_SyntaxNotSupportedOnHekatonElement = ValidationBaseCode + 69;
|
||||
public const int MemoryOptimizedObjectsValidation_ValidatePrimaryKeyForSchemaAndDataTables = ValidationBaseCode + 70;
|
||||
public const int MemoryOptimizedObjectsValidation_ValidatePrimaryKeyForSchemaOnlyTables = ValidationBaseCode + 71;
|
||||
public const int MemoryOptimizedObjectsValidation_OnlyNotNullableColumnsOnIndexes = ValidationBaseCode + 72;
|
||||
public const int MemoryOptimizedObjectsValidation_HashIndexesOnlyOnMemoryOptimizedObjects = ValidationBaseCode + 73;
|
||||
public const int MemoryOptimizedObjectsValidation_OptionOnlyForHashIndexes = ValidationBaseCode + 74;
|
||||
public const int IncrementalStatisticsValidation_FilterNotSupported = ValidationBaseCode + 75;
|
||||
public const int IncrementalStatisticsValidation_ViewNotSupported = ValidationBaseCode + 76;
|
||||
public const int IncrementalStatisticsValidation_IndexNotPartitionAligned = ValidationBaseCode + 77;
|
||||
public const int AzureV12SurfaceAreaValidation = ValidationBaseCode + 78;
|
||||
public const int DuplicatedTargetObjectReferencesInSecurityPolicy = ValidationBaseCode + 79;
|
||||
public const int MultipleSecurityPoliciesOnTargetObject = ValidationBaseCode + 80;
|
||||
public const int ExportedRowsMayBeIncomplete = ValidationBaseCode + 81;
|
||||
public const 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;
|
||||
@@ -315,8 +315,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection
|
||||
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 IndexesOnExternalTable = ValidationBaseCode + 112;
|
||||
public const int TriggersOnExternalTable = ValidationBaseCode + 113;
|
||||
public const int StretchValidation_ExportBlocked = ValidationBaseCode + 114;
|
||||
public const int StretchValidation_ImportBlocked = ValidationBaseCode + 115;
|
||||
public const int DeploymentBlocked = ValidationBaseCode + 116;
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Credentials
|
||||
/// Default constructor is private since it's a singleton class
|
||||
/// </summary>
|
||||
private CredentialService()
|
||||
: this(null, new LinuxCredentialStore.StoreConfig()
|
||||
: this(null, new StoreConfig()
|
||||
{ CredentialFolder = DefaultSecretsFolder, CredentialFile = DefaultSecretsFile, IsRelativeToUserHomeDir = true})
|
||||
{
|
||||
}
|
||||
@@ -56,7 +56,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Credentials
|
||||
/// <summary>
|
||||
/// Internal for testing purposes only
|
||||
/// </summary>
|
||||
internal CredentialService(ICredentialStore store, LinuxCredentialStore.StoreConfig config)
|
||||
internal CredentialService(ICredentialStore store, StoreConfig config)
|
||||
{
|
||||
this.credStore = store != null ? store : GetStoreForOS(config);
|
||||
}
|
||||
@@ -64,12 +64,13 @@ namespace Microsoft.SqlTools.ServiceLayer.Credentials
|
||||
/// <summary>
|
||||
/// Internal for testing purposes only
|
||||
/// </summary>
|
||||
internal static ICredentialStore GetStoreForOS(LinuxCredentialStore.StoreConfig config)
|
||||
internal static ICredentialStore GetStoreForOS(StoreConfig config)
|
||||
{
|
||||
if(RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
return new Win32CredentialStore();
|
||||
}
|
||||
#if !WINDOWS_ONLY_BUILD
|
||||
else if(RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
{
|
||||
return new OSXCredentialStore();
|
||||
@@ -78,6 +79,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Credentials
|
||||
{
|
||||
return new LinuxCredentialStore(config);
|
||||
}
|
||||
#endif
|
||||
throw new InvalidOperationException("Platform not currently supported");
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,9 @@ using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Credentials.Linux
|
||||
{
|
||||
|
||||
#if !WINDOWS_ONLY_BUILD
|
||||
|
||||
public class FileTokenStorage
|
||||
{
|
||||
private const int OwnerAccessMode = 384; // Permission 0600 - owner read/write, nobody else has access
|
||||
@@ -84,4 +87,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Credentials.Linux
|
||||
Interop.Sys.ChMod(filePath, OwnerAccessMode);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
@@ -8,6 +8,9 @@ using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Credentials
|
||||
{
|
||||
|
||||
#if !WINDOWS_ONLY_BUILD
|
||||
|
||||
internal static partial class Interop
|
||||
{
|
||||
/// <summary>Common Unix errno error codes.</summary>
|
||||
@@ -218,4 +221,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Credentials
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
@@ -8,6 +8,9 @@ using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Credentials
|
||||
{
|
||||
|
||||
#if !WINDOWS_ONLY_BUILD
|
||||
|
||||
internal static partial class Interop
|
||||
{
|
||||
internal static partial class Sys
|
||||
@@ -37,6 +40,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Credentials
|
||||
internal const string SystemNative = "System.Native";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
@@ -15,6 +14,18 @@ using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Credentials.Linux
|
||||
{
|
||||
/// <summary>
|
||||
/// Store configuration struct
|
||||
/// </summary>
|
||||
internal struct StoreConfig
|
||||
{
|
||||
public string CredentialFolder { get; set; }
|
||||
public string CredentialFile { get; set; }
|
||||
public bool IsRelativeToUserHomeDir { get; set; }
|
||||
}
|
||||
|
||||
#if !WINDOWS_ONLY_BUILD
|
||||
|
||||
/// <summary>
|
||||
/// Linux implementation of the credential store.
|
||||
///
|
||||
@@ -25,13 +36,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Credentials.Linux
|
||||
/// </summary>
|
||||
internal class LinuxCredentialStore : ICredentialStore
|
||||
{
|
||||
internal struct StoreConfig
|
||||
{
|
||||
public string CredentialFolder { get; set; }
|
||||
public string CredentialFile { get; set; }
|
||||
public bool IsRelativeToUserHomeDir { get; set; }
|
||||
}
|
||||
|
||||
private string credentialFolderPath;
|
||||
private string credentialFileName;
|
||||
private FileTokenStorage storage;
|
||||
@@ -228,4 +232,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Credentials.Linux
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
@@ -10,6 +10,9 @@ using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Credentials.OSX
|
||||
{
|
||||
|
||||
#if !WINDOWS_ONLY_BUILD
|
||||
|
||||
/// <summary>
|
||||
/// OSX implementation of the credential store
|
||||
/// </summary>
|
||||
@@ -155,4 +158,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Credentials.OSX
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -163,8 +163,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol
|
||||
TParams typedParams = default(TParams);
|
||||
if (eventMessage.Contents != null)
|
||||
{
|
||||
// TODO: Catch parse errors!
|
||||
typedParams = eventMessage.Contents.ToObject<TParams>();
|
||||
try
|
||||
{
|
||||
typedParams = eventMessage.Contents.ToObject<TParams>();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Write(LogLevel.Verbose, ex.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
return eventHandler(typedParams, eventContext);
|
||||
|
||||
@@ -150,14 +150,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Hosting
|
||||
Capabilities = new ServerCapabilities
|
||||
{
|
||||
TextDocumentSync = TextDocumentSyncKind.Incremental,
|
||||
DefinitionProvider = false,
|
||||
DefinitionProvider = true,
|
||||
ReferencesProvider = false,
|
||||
DocumentHighlightProvider = false,
|
||||
HoverProvider = true,
|
||||
CompletionProvider = new CompletionOptions
|
||||
{
|
||||
ResolveProvider = true,
|
||||
TriggerCharacters = new string[] { ".", "-", ":", "\\" }
|
||||
TriggerCharacters = new string[] { ".", "-", ":", "\\", "[" }
|
||||
},
|
||||
SignatureHelpProvider = new SignatureHelpOptions
|
||||
{
|
||||
|
||||
@@ -5,16 +5,16 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Binder;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Parser;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Completion;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||
|
||||
@@ -30,8 +30,6 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
|
||||
private static WorkspaceService<SqlToolsSettings> workspaceServiceInstance;
|
||||
|
||||
private static Regex ValidSqlNameRegex = new Regex(@"^[\p{L}_@][\p{L}\p{N}@$#_]{0,127}$");
|
||||
|
||||
private static CompletionItem[] emptyCompletionList = new CompletionItem[0];
|
||||
|
||||
private static readonly string[] DefaultCompletionText = new string[]
|
||||
@@ -372,12 +370,13 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
/// <param name="endColumn"></param>
|
||||
/// <param name="useLowerCase"></param>
|
||||
internal static CompletionItem[] GetDefaultCompletionItems(
|
||||
int row,
|
||||
int startColumn,
|
||||
int endColumn,
|
||||
bool useLowerCase,
|
||||
string tokenText = null)
|
||||
ScriptDocumentInfo scriptDocumentInfo,
|
||||
bool useLowerCase)
|
||||
{
|
||||
int row = scriptDocumentInfo.StartLine;
|
||||
int startColumn = scriptDocumentInfo.StartColumn;
|
||||
int endColumn = scriptDocumentInfo.EndColumn;
|
||||
string tokenText = scriptDocumentInfo.TokenText;
|
||||
// determine how many default completion items there will be
|
||||
int listSize = DefaultCompletionText.Length;
|
||||
if (!string.IsNullOrWhiteSpace(tokenText))
|
||||
@@ -408,7 +407,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
if (string.IsNullOrWhiteSpace(tokenText) || completionText.StartsWith(tokenText, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
completionItems[completionItemIndex] = CreateDefaultCompletionItem(
|
||||
useLowerCase ? completionText.ToLower() : completionText.ToUpper(),
|
||||
useLowerCase ? completionText.ToLowerInvariant() : completionText.ToUpperInvariant(),
|
||||
row,
|
||||
startColumn,
|
||||
endColumn);
|
||||
@@ -432,7 +431,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
int startColumn,
|
||||
int endColumn)
|
||||
{
|
||||
return CreateCompletionItem(label, label + " keyword", label, CompletionItemKind.Keyword, row, startColumn, endColumn);
|
||||
return SqlCompletionItem.CreateCompletionItem(label, label + " keyword", label, CompletionItemKind.Keyword, row, startColumn, endColumn);
|
||||
}
|
||||
|
||||
internal static CompletionItem[] AddTokenToItems(CompletionItem[] currentList, Token token, int row,
|
||||
@@ -446,49 +445,12 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
))
|
||||
{
|
||||
var list = currentList.ToList();
|
||||
list.Insert(0, CreateCompletionItem(token.Text, token.Text, token.Text, CompletionItemKind.Text, row, startColumn, endColumn));
|
||||
list.Insert(0, SqlCompletionItem.CreateCompletionItem(token.Text, token.Text, token.Text, CompletionItemKind.Text, row, startColumn, endColumn));
|
||||
return list.ToArray();
|
||||
}
|
||||
return currentList;
|
||||
}
|
||||
|
||||
private static CompletionItem CreateCompletionItem(
|
||||
string label,
|
||||
string detail,
|
||||
string insertText,
|
||||
CompletionItemKind kind,
|
||||
int row,
|
||||
int startColumn,
|
||||
int endColumn)
|
||||
{
|
||||
CompletionItem item = new CompletionItem()
|
||||
{
|
||||
Label = label,
|
||||
Kind = kind,
|
||||
Detail = detail,
|
||||
InsertText = insertText,
|
||||
TextEdit = new TextEdit
|
||||
{
|
||||
NewText = insertText,
|
||||
Range = new Range
|
||||
{
|
||||
Start = new Position
|
||||
{
|
||||
Line = row,
|
||||
Character = startColumn
|
||||
},
|
||||
End = new Position
|
||||
{
|
||||
Line = row,
|
||||
Character = endColumn
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a list of Declaration objects to CompletionItem objects
|
||||
/// since VS Code expects CompletionItems but SQL Parser works with Declarations
|
||||
@@ -501,56 +463,22 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
IEnumerable<Declaration> suggestions,
|
||||
int row,
|
||||
int startColumn,
|
||||
int endColumn)
|
||||
int endColumn,
|
||||
string tokenText = null)
|
||||
{
|
||||
List<CompletionItem> completions = new List<CompletionItem>();
|
||||
|
||||
foreach (var autoCompleteItem in suggestions)
|
||||
{
|
||||
string insertText = GetCompletionItemInsertName(autoCompleteItem);
|
||||
CompletionItemKind kind = CompletionItemKind.Variable;
|
||||
switch (autoCompleteItem.Type)
|
||||
{
|
||||
case DeclarationType.Schema:
|
||||
kind = CompletionItemKind.Module;
|
||||
break;
|
||||
case DeclarationType.Column:
|
||||
kind = CompletionItemKind.Field;
|
||||
break;
|
||||
case DeclarationType.Table:
|
||||
case DeclarationType.View:
|
||||
kind = CompletionItemKind.File;
|
||||
break;
|
||||
case DeclarationType.Database:
|
||||
kind = CompletionItemKind.Method;
|
||||
break;
|
||||
case DeclarationType.ScalarValuedFunction:
|
||||
case DeclarationType.TableValuedFunction:
|
||||
case DeclarationType.BuiltInFunction:
|
||||
kind = CompletionItemKind.Value;
|
||||
break;
|
||||
default:
|
||||
kind = CompletionItemKind.Unit;
|
||||
break;
|
||||
}
|
||||
SqlCompletionItem sqlCompletionItem = new SqlCompletionItem(autoCompleteItem, tokenText);
|
||||
|
||||
// convert the completion item candidates into CompletionItems
|
||||
completions.Add(CreateCompletionItem(autoCompleteItem.Title, autoCompleteItem.Title, insertText, kind, row, startColumn, endColumn));
|
||||
completions.Add(sqlCompletionItem.CreateCompletionItem(row, startColumn, endColumn));
|
||||
}
|
||||
|
||||
return completions.ToArray();
|
||||
}
|
||||
|
||||
private static string GetCompletionItemInsertName(Declaration autoCompleteItem)
|
||||
{
|
||||
string insertText = autoCompleteItem.Title;
|
||||
if (!string.IsNullOrEmpty(autoCompleteItem.Title) && !ValidSqlNameRegex.IsMatch(autoCompleteItem.Title))
|
||||
{
|
||||
insertText = string.Format(CultureInfo.InvariantCulture, "[{0}]", autoCompleteItem.Title);
|
||||
}
|
||||
return insertText;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Preinitialize the parser and binder with common metadata.
|
||||
/// This should front load the long binding wait to the time the
|
||||
@@ -566,7 +494,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
{
|
||||
if (scriptInfo.IsConnected)
|
||||
{
|
||||
var scriptFile = AutoCompleteHelper.WorkspaceServiceInstance.Workspace.GetFile(info.OwnerUri);
|
||||
var scriptFile = AutoCompleteHelper.WorkspaceServiceInstance.Workspace.GetFile(info.OwnerUri);
|
||||
LanguageService.Instance.ParseAndBind(scriptFile, info);
|
||||
|
||||
if (Monitor.TryEnter(scriptInfo.BuildingMetadataLock, LanguageService.OnConnectionWaitTimeout))
|
||||
@@ -679,5 +607,77 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a SQL Parser List of MethodHelpText objects into a VS Code SignatureHelp object
|
||||
/// </summary>
|
||||
internal static SignatureHelp ConvertMethodHelpTextListToSignatureHelp(List<Babel.MethodHelpText> methods, Babel.MethodNameAndParamLocations locations, int line, int column)
|
||||
{
|
||||
Validate.IsNotNull(nameof(methods), methods);
|
||||
Validate.IsNotNull(nameof(locations), locations);
|
||||
Validate.IsGreaterThan(nameof(line), line, 0);
|
||||
Validate.IsGreaterThan(nameof(column), column, 0);
|
||||
|
||||
SignatureHelp help = new SignatureHelp();
|
||||
|
||||
help.Signatures = methods.Select(method =>
|
||||
{
|
||||
return new SignatureInformation()
|
||||
{
|
||||
// Signature label format: <name> param1, param2, ..., paramn RETURNS <type>
|
||||
Label = method.Name + " " + method.Parameters.Select(parameter => parameter.Display).Aggregate((l, r) => l + "," + r) + " " + method.Type,
|
||||
Documentation = method.Description,
|
||||
Parameters = method.Parameters.Select(parameter =>
|
||||
{
|
||||
return new ParameterInformation()
|
||||
{
|
||||
Label = parameter.Display,
|
||||
Documentation = parameter.Description
|
||||
};
|
||||
}).ToArray()
|
||||
};
|
||||
}).Where(method => method.Label.Contains(locations.Name)).ToArray();
|
||||
|
||||
if (help.Signatures.Length == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Find the matching method signature at the cursor's location
|
||||
// For now, take the first match (since we've already filtered by name above)
|
||||
help.ActiveSignature = 0;
|
||||
|
||||
// Determine the current parameter at the cursor
|
||||
int currentParameter = -1; // Default case: not on any particular parameter
|
||||
if (locations.ParamStartLocation != null)
|
||||
{
|
||||
// Is the cursor past the function name?
|
||||
var location = locations.ParamStartLocation.Value;
|
||||
if (line > location.LineNumber || (line == location.LineNumber && line == location.LineNumber && column >= location.ColumnNumber))
|
||||
{
|
||||
currentParameter = 0;
|
||||
}
|
||||
}
|
||||
foreach (var location in locations.ParamSeperatorLocations)
|
||||
{
|
||||
// Is the cursor past a comma ',' and at least on the next parameter?
|
||||
if (line > location.LineNumber || (line == location.LineNumber && column > location.ColumnNumber))
|
||||
{
|
||||
currentParameter++;
|
||||
}
|
||||
}
|
||||
if (locations.ParamEndLocation != null)
|
||||
{
|
||||
// Is the cursor past the end of the parameter list on a different token?
|
||||
var location = locations.ParamEndLocation.Value;
|
||||
if (line > location.LineNumber || (line == location.LineNumber && line == location.LineNumber && column > location.ColumnNumber))
|
||||
{
|
||||
currentParameter = -1;
|
||||
}
|
||||
}
|
||||
help.ActiveParameter = currentParameter;
|
||||
|
||||
return help;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,6 +112,17 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a binding context already exists for the provided context key
|
||||
/// </summary>
|
||||
protected bool BindingContextExists(string key)
|
||||
{
|
||||
lock (this.bindingContextLock)
|
||||
{
|
||||
return this.BindingContextMap.ContainsKey(key);
|
||||
}
|
||||
}
|
||||
|
||||
private bool HasPendingQueueItems
|
||||
{
|
||||
get
|
||||
|
||||
@@ -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.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Completion
|
||||
{
|
||||
/// <summary>
|
||||
/// Includes the objects created by auto completion service
|
||||
/// </summary>
|
||||
public class AutoCompletionResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates new instance
|
||||
/// </summary>
|
||||
public AutoCompletionResult()
|
||||
{
|
||||
Stopwatch = new Stopwatch();
|
||||
Stopwatch.Start();
|
||||
}
|
||||
|
||||
private Stopwatch Stopwatch { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Completes the results to calculate the duration
|
||||
/// </summary>
|
||||
public void CompleteResult(CompletionItem[] completionItems)
|
||||
{
|
||||
Stopwatch.Stop();
|
||||
CompletionItems = completionItems;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The number of milliseconds to process the result
|
||||
/// </summary>
|
||||
public double Duration
|
||||
{
|
||||
get
|
||||
{
|
||||
return Stopwatch.ElapsedMilliseconds;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Completion list
|
||||
/// </summary>
|
||||
public CompletionItem[] CompletionItems { get; private set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
|
||||
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Parser;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Completion
|
||||
{
|
||||
/// <summary>
|
||||
/// A service to create auto complete list for given script document
|
||||
/// </summary>
|
||||
internal class CompletionService
|
||||
{
|
||||
private ConnectedBindingQueue BindingQueue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Created new instance given binding queue
|
||||
/// </summary>
|
||||
public CompletionService(ConnectedBindingQueue bindingQueue)
|
||||
{
|
||||
BindingQueue = bindingQueue;
|
||||
}
|
||||
|
||||
private ISqlParserWrapper sqlParserWrapper;
|
||||
|
||||
/// <summary>
|
||||
/// SQL parser wrapper to create the completion list
|
||||
/// </summary>
|
||||
public ISqlParserWrapper SqlParserWrapper
|
||||
{
|
||||
get
|
||||
{
|
||||
if(this.sqlParserWrapper == null)
|
||||
{
|
||||
this.sqlParserWrapper = new SqlParserWrapper();
|
||||
}
|
||||
return this.sqlParserWrapper;
|
||||
}
|
||||
set
|
||||
{
|
||||
this.sqlParserWrapper = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a completion list given connection and document info
|
||||
/// </summary>
|
||||
public AutoCompletionResult CreateCompletions(
|
||||
ConnectionInfo connInfo,
|
||||
ScriptDocumentInfo scriptDocumentInfo,
|
||||
bool useLowerCaseSuggestions)
|
||||
{
|
||||
AutoCompletionResult result = new AutoCompletionResult();
|
||||
// check if the file is connected and the file lock is available
|
||||
if (scriptDocumentInfo.ScriptParseInfo.IsConnected && Monitor.TryEnter(scriptDocumentInfo.ScriptParseInfo.BuildingMetadataLock))
|
||||
{
|
||||
try
|
||||
{
|
||||
QueueItem queueItem = AddToQueue(connInfo, scriptDocumentInfo.ScriptParseInfo, scriptDocumentInfo, useLowerCaseSuggestions);
|
||||
|
||||
// wait for the queue item
|
||||
queueItem.ItemProcessed.WaitOne();
|
||||
var completionResult = queueItem.GetResultAsT<AutoCompletionResult>();
|
||||
if (completionResult != null && completionResult.CompletionItems != null && completionResult.CompletionItems.Length > 0)
|
||||
{
|
||||
result = completionResult;
|
||||
}
|
||||
else if (!ShouldShowCompletionList(scriptDocumentInfo.Token))
|
||||
{
|
||||
result.CompleteResult(AutoCompleteHelper.EmptyCompletionList);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Monitor.Exit(scriptDocumentInfo.ScriptParseInfo.BuildingMetadataLock);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private QueueItem AddToQueue(
|
||||
ConnectionInfo connInfo,
|
||||
ScriptParseInfo scriptParseInfo,
|
||||
ScriptDocumentInfo scriptDocumentInfo,
|
||||
bool useLowerCaseSuggestions)
|
||||
{
|
||||
// queue the completion task with the binding queue
|
||||
QueueItem queueItem = this.BindingQueue.QueueBindingOperation(
|
||||
key: scriptParseInfo.ConnectionKey,
|
||||
bindingTimeout: LanguageService.BindingTimeout,
|
||||
bindOperation: (bindingContext, cancelToken) =>
|
||||
{
|
||||
return CreateCompletionsFromSqlParser(connInfo, scriptParseInfo, scriptDocumentInfo, bindingContext.MetadataDisplayInfoProvider);
|
||||
},
|
||||
timeoutOperation: (bindingContext) =>
|
||||
{
|
||||
// return the default list if the connected bind fails
|
||||
return CreateDefaultCompletionItems(scriptParseInfo, scriptDocumentInfo, useLowerCaseSuggestions);
|
||||
});
|
||||
return queueItem;
|
||||
}
|
||||
|
||||
private static bool ShouldShowCompletionList(Token token)
|
||||
{
|
||||
bool result = true;
|
||||
if (token != null)
|
||||
{
|
||||
switch (token.Id)
|
||||
{
|
||||
case (int)Tokens.LEX_MULTILINE_COMMENT:
|
||||
case (int)Tokens.LEX_END_OF_LINE_COMMENT:
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private AutoCompletionResult CreateDefaultCompletionItems(ScriptParseInfo scriptParseInfo, ScriptDocumentInfo scriptDocumentInfo, bool useLowerCaseSuggestions)
|
||||
{
|
||||
AutoCompletionResult result = new AutoCompletionResult();
|
||||
CompletionItem[] completionList = AutoCompleteHelper.GetDefaultCompletionItems(scriptDocumentInfo, useLowerCaseSuggestions);
|
||||
result.CompleteResult(completionList);
|
||||
return result;
|
||||
}
|
||||
|
||||
private AutoCompletionResult CreateCompletionsFromSqlParser(
|
||||
ConnectionInfo connInfo,
|
||||
ScriptParseInfo scriptParseInfo,
|
||||
ScriptDocumentInfo scriptDocumentInfo,
|
||||
MetadataDisplayInfoProvider metadataDisplayInfoProvider)
|
||||
{
|
||||
AutoCompletionResult result = new AutoCompletionResult();
|
||||
IEnumerable<Declaration> suggestions = SqlParserWrapper.FindCompletions(
|
||||
scriptParseInfo.ParseResult,
|
||||
scriptDocumentInfo.ParserLine,
|
||||
scriptDocumentInfo.ParserColumn,
|
||||
metadataDisplayInfoProvider);
|
||||
|
||||
// get the completion list from SQL Parser
|
||||
scriptParseInfo.CurrentSuggestions = suggestions;
|
||||
|
||||
// convert the suggestion list to the VS Code format
|
||||
CompletionItem[] completionList = AutoCompleteHelper.ConvertDeclarationsToCompletionItems(
|
||||
scriptParseInfo.CurrentSuggestions,
|
||||
scriptDocumentInfo.StartLine,
|
||||
scriptDocumentInfo.StartColumn,
|
||||
scriptDocumentInfo.EndColumn,
|
||||
scriptDocumentInfo.TokenText);
|
||||
|
||||
result.CompleteResult(completionList);
|
||||
|
||||
//The bucket for number of milliseconds will take to send back auto complete list
|
||||
connInfo.IntellisenseMetrics.UpdateMetrics(result.Duration, 1, (k2, v2) => v2 + 1);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Completion
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a completion item from SQL parser declaration item
|
||||
/// </summary>
|
||||
public class SqlCompletionItem
|
||||
{
|
||||
private static Regex ValidSqlNameRegex = new Regex(@"^[\p{L}_@][\p{L}\p{N}@$#_]{0,127}$");
|
||||
|
||||
/// <summary>
|
||||
/// Create new instance given the SQL parser declaration
|
||||
/// </summary>
|
||||
public SqlCompletionItem(Declaration declaration, string tokenText) :
|
||||
this(declaration == null ? null : declaration.Title, declaration == null ? DeclarationType.Table : declaration.Type, tokenText)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates new instance given declaration title and type
|
||||
/// </summary>
|
||||
public SqlCompletionItem(string declarationTitle, DeclarationType declarationType, string tokenText)
|
||||
{
|
||||
Validate.IsNotNullOrEmptyString("declarationTitle", declarationTitle);
|
||||
|
||||
DeclarationTitle = declarationTitle;
|
||||
DeclarationType = declarationType;
|
||||
TokenText = tokenText;
|
||||
|
||||
Init();
|
||||
}
|
||||
|
||||
private void Init()
|
||||
{
|
||||
InsertText = GetCompletionItemInsertName();
|
||||
Label = DeclarationTitle;
|
||||
if (StartsWithBracket(TokenText))
|
||||
{
|
||||
Label = WithBracket(Label);
|
||||
InsertText = WithBracket(InsertText);
|
||||
}
|
||||
Detail = Label;
|
||||
Kind = CreateCompletionItemKind();
|
||||
}
|
||||
|
||||
private CompletionItemKind CreateCompletionItemKind()
|
||||
{
|
||||
CompletionItemKind kind = CompletionItemKind.Variable;
|
||||
switch (DeclarationType)
|
||||
{
|
||||
case DeclarationType.Schema:
|
||||
kind = CompletionItemKind.Module;
|
||||
break;
|
||||
case DeclarationType.Column:
|
||||
kind = CompletionItemKind.Field;
|
||||
break;
|
||||
case DeclarationType.Table:
|
||||
case DeclarationType.View:
|
||||
kind = CompletionItemKind.File;
|
||||
break;
|
||||
case DeclarationType.Database:
|
||||
kind = CompletionItemKind.Method;
|
||||
break;
|
||||
case DeclarationType.ScalarValuedFunction:
|
||||
case DeclarationType.TableValuedFunction:
|
||||
case DeclarationType.BuiltInFunction:
|
||||
kind = CompletionItemKind.Value;
|
||||
break;
|
||||
default:
|
||||
kind = CompletionItemKind.Unit;
|
||||
break;
|
||||
}
|
||||
|
||||
return kind;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Declaration Title
|
||||
/// </summary>
|
||||
public string DeclarationTitle { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Token text from the editor
|
||||
/// </summary>
|
||||
public string TokenText { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// SQL declaration type
|
||||
/// </summary>
|
||||
public DeclarationType DeclarationType { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Completion item label
|
||||
/// </summary>
|
||||
public string Label { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Completion item kind
|
||||
/// </summary>
|
||||
public CompletionItemKind Kind { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Completion insert text
|
||||
/// </summary>
|
||||
public string InsertText { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Completion item detail
|
||||
/// </summary>
|
||||
public string Detail { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a completion item given the editor info
|
||||
/// </summary>
|
||||
public CompletionItem CreateCompletionItem(
|
||||
int row,
|
||||
int startColumn,
|
||||
int endColumn)
|
||||
{
|
||||
return CreateCompletionItem(Label, Detail, InsertText, Kind, row, startColumn, endColumn);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a completion item
|
||||
/// </summary>
|
||||
public static CompletionItem CreateCompletionItem(
|
||||
string label,
|
||||
string detail,
|
||||
string insertText,
|
||||
CompletionItemKind kind,
|
||||
int row,
|
||||
int startColumn,
|
||||
int endColumn)
|
||||
{
|
||||
CompletionItem item = new CompletionItem()
|
||||
{
|
||||
Label = label,
|
||||
Kind = kind,
|
||||
Detail = detail,
|
||||
InsertText = insertText,
|
||||
TextEdit = new TextEdit
|
||||
{
|
||||
NewText = insertText,
|
||||
Range = new Range
|
||||
{
|
||||
Start = new Position
|
||||
{
|
||||
Line = row,
|
||||
Character = startColumn
|
||||
},
|
||||
End = new Position
|
||||
{
|
||||
Line = row,
|
||||
Character = endColumn
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
private string GetCompletionItemInsertName()
|
||||
{
|
||||
string insertText = DeclarationTitle;
|
||||
if (!string.IsNullOrEmpty(DeclarationTitle) && !ValidSqlNameRegex.IsMatch(DeclarationTitle))
|
||||
{
|
||||
insertText = WithBracket(DeclarationTitle);
|
||||
}
|
||||
return insertText;
|
||||
}
|
||||
|
||||
private bool HasBrackets(string text)
|
||||
{
|
||||
return text != null && text.StartsWith("[") && text.EndsWith("]");
|
||||
}
|
||||
|
||||
private bool StartsWithBracket(string text)
|
||||
{
|
||||
return text != null && text.StartsWith("[");
|
||||
}
|
||||
|
||||
private string WithBracket(string text)
|
||||
{
|
||||
if (!HasBrackets(text))
|
||||
{
|
||||
return string.Format(CultureInfo.InvariantCulture, "[{0}]", text);
|
||||
}
|
||||
else
|
||||
{
|
||||
return text;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
|
||||
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Parser;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Completion
|
||||
{
|
||||
/// <summary>
|
||||
/// SqlParserWrapper interface
|
||||
/// </summary>
|
||||
public interface ISqlParserWrapper
|
||||
{
|
||||
IEnumerable<Declaration> FindCompletions(ParseResult parseResult, int line, int col, IMetadataDisplayInfoProvider displayInfoProvider);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A wrapper class around SQL parser methods to make the operations testable
|
||||
/// </summary>
|
||||
public class SqlParserWrapper : ISqlParserWrapper
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates completion list given SQL script info
|
||||
/// </summary>
|
||||
public IEnumerable<Declaration> FindCompletions(ParseResult parseResult, int line, int col, IMetadataDisplayInfoProvider displayInfoProvider)
|
||||
{
|
||||
return Resolver.FindCompletions(parseResult, line, col, displayInfoProvider);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -61,6 +61,11 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
|
||||
// lookup the current binding context
|
||||
string connectionKey = GetConnectionContextKey(connInfo);
|
||||
if (BindingContextExists(connectionKey))
|
||||
{
|
||||
// no need to populate the context again since the context already exists
|
||||
return connectionKey;
|
||||
}
|
||||
IBindingContext bindingContext = this.GetOrCreateBindingContext(connectionKey);
|
||||
|
||||
if (bindingContext.BindingLock.WaitOne())
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// Parameters sent back with an IntelliSense ready event
|
||||
/// </summary>
|
||||
public class IntelliSenseReadyParams
|
||||
{
|
||||
/// <summary>
|
||||
/// URI identifying the text document
|
||||
/// </summary>
|
||||
public string OwnerUri { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event sent when the language service is finished updating after a connection
|
||||
/// </summary>
|
||||
public class IntelliSenseReadyNotification
|
||||
{
|
||||
public static readonly
|
||||
EventType<IntelliSenseReadyParams> Type =
|
||||
EventType<IntelliSenseReadyParams>.Create("textDocument/intelliSenseReady");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts
|
||||
{
|
||||
public class TelemetryProperties
|
||||
{
|
||||
public string EventName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Telemetry properties
|
||||
/// </summary>
|
||||
public Dictionary<string, string> Properties { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Telemetry measures
|
||||
/// </summary>
|
||||
public Dictionary<string, double> Measures { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parameters sent back with an IntelliSense ready event
|
||||
/// </summary>
|
||||
public class TelemetryParams
|
||||
{
|
||||
public TelemetryProperties Params { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event sent when the language service needs to add a telemetry event
|
||||
/// </summary>
|
||||
public class TelemetryNotification
|
||||
{
|
||||
public static readonly
|
||||
EventType<TelemetryParams> Type =
|
||||
EventType<TelemetryParams>.Create("telemetry/sqlevent");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List of telemetry events
|
||||
/// </summary>
|
||||
public static class TelemetryEventNames
|
||||
{
|
||||
/// <summary>
|
||||
/// telemetry event name for auto complete response time
|
||||
/// </summary>
|
||||
public const string IntellisenseQuantile = "IntellisenseQuantile";
|
||||
|
||||
/// <summary>
|
||||
/// telemetry even name for when definition is requested
|
||||
/// </summary>
|
||||
public const string PeekDefinitionRequested = "PeekDefinitionRequested";
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer
|
||||
{
|
||||
/// <summary>
|
||||
/// A class to calculate the value for the metrics using the given bucket
|
||||
/// </summary>
|
||||
public class InteractionMetrics<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates new instance given a bucket of metrics
|
||||
/// </summary>
|
||||
public InteractionMetrics(int[] metrics)
|
||||
{
|
||||
Validate.IsNotNull("metrics", metrics);
|
||||
if(metrics.Length == 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("metrics");
|
||||
}
|
||||
|
||||
Counters = new ConcurrentDictionary<string, T>();
|
||||
if (!IsSorted(metrics))
|
||||
{
|
||||
Array.Sort(metrics);
|
||||
}
|
||||
Metrics = metrics;
|
||||
}
|
||||
|
||||
private ConcurrentDictionary<string, T> Counters { get; }
|
||||
|
||||
private object perfCountersLock = new object();
|
||||
|
||||
/// <summary>
|
||||
/// The metrics bucket
|
||||
/// </summary>
|
||||
public int[] Metrics { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given list is sorted
|
||||
/// </summary>
|
||||
private bool IsSorted(int[] metrics)
|
||||
{
|
||||
if (metrics.Length > 1)
|
||||
{
|
||||
int previous = metrics[0];
|
||||
for (int i = 1; i < metrics.Length; i++)
|
||||
{
|
||||
if(metrics[i] < previous)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
previous = metrics[i];
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update metric value given new number
|
||||
/// </summary>
|
||||
public void UpdateMetrics(double duration, T newValue, Func<string, T, T> updateValueFactory)
|
||||
{
|
||||
int metric = Metrics[Metrics.Length - 1];
|
||||
for (int i = 0; i < Metrics.Length; i++)
|
||||
{
|
||||
if (duration <= Metrics[i])
|
||||
{
|
||||
metric = Metrics[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
string key = metric.ToString();
|
||||
Counters.AddOrUpdate(key, newValue, updateValueFactory);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the quantile
|
||||
/// </summary>
|
||||
public Dictionary<string, T> Quantile
|
||||
{
|
||||
get
|
||||
{
|
||||
return Counters.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ 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.Completion;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
@@ -33,6 +34,8 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
/// </summary>
|
||||
public sealed class LanguageService
|
||||
{
|
||||
private const int OneSecond = 1000;
|
||||
|
||||
internal const string DefaultBatchSeperator = "GO";
|
||||
|
||||
internal const int DiagnosticParseDelay = 750;
|
||||
@@ -41,7 +44,9 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
|
||||
internal const int BindingTimeout = 500;
|
||||
|
||||
internal const int OnConnectionWaitTimeout = 300000;
|
||||
internal const int OnConnectionWaitTimeout = 300 * OneSecond;
|
||||
|
||||
internal const int PeekDefinitionTimeout = 10 * OneSecond;
|
||||
|
||||
private static ConnectionService connectionService = null;
|
||||
|
||||
@@ -196,13 +201,16 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
public void InitializeService(ServiceHost serviceHost, SqlToolsContext context)
|
||||
{
|
||||
// Register the requests that this service will handle
|
||||
serviceHost.SetRequestHandler(DefinitionRequest.Type, HandleDefinitionRequest);
|
||||
serviceHost.SetRequestHandler(ReferencesRequest.Type, HandleReferencesRequest);
|
||||
serviceHost.SetRequestHandler(CompletionResolveRequest.Type, HandleCompletionResolveRequest);
|
||||
|
||||
// turn off until needed (10/28/2016)
|
||||
// serviceHost.SetRequestHandler(ReferencesRequest.Type, HandleReferencesRequest);
|
||||
// serviceHost.SetRequestHandler(DocumentHighlightRequest.Type, HandleDocumentHighlightRequest);
|
||||
|
||||
serviceHost.SetRequestHandler(SignatureHelpRequest.Type, HandleSignatureHelpRequest);
|
||||
serviceHost.SetRequestHandler(DocumentHighlightRequest.Type, HandleDocumentHighlightRequest);
|
||||
serviceHost.SetRequestHandler(CompletionResolveRequest.Type, HandleCompletionResolveRequest);
|
||||
serviceHost.SetRequestHandler(HoverRequest.Type, HandleHoverRequest);
|
||||
serviceHost.SetRequestHandler(CompletionRequest.Type, HandleCompletionRequest);
|
||||
serviceHost.SetRequestHandler(DefinitionRequest.Type, HandleDefinitionRequest);
|
||||
|
||||
// Register a no-op shutdown task for validation of the shutdown logic
|
||||
serviceHost.RegisterShutdownTask(async (shutdownParams, shutdownRequestContext) =>
|
||||
@@ -290,13 +298,34 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task HandleDefinitionRequest(
|
||||
TextDocumentPosition textDocumentPosition,
|
||||
RequestContext<Location[]> requestContext)
|
||||
internal static async Task HandleDefinitionRequest(TextDocumentPosition textDocumentPosition, RequestContext<Location[]> requestContext)
|
||||
{
|
||||
await Task.FromResult(true);
|
||||
if (WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings.IsIntelliSenseEnabled)
|
||||
{
|
||||
// Retrieve document and connection
|
||||
ConnectionInfo connInfo;
|
||||
var scriptFile = LanguageService.WorkspaceServiceInstance.Workspace.GetFile(textDocumentPosition.TextDocument.Uri);
|
||||
LanguageService.ConnectionServiceInstance.TryFindConnection(scriptFile.ClientFilePath, out connInfo);
|
||||
|
||||
Location[] locations = LanguageService.Instance.GetDefinition(textDocumentPosition, scriptFile, connInfo);
|
||||
if (locations != null)
|
||||
{
|
||||
await requestContext.SendResult(locations);
|
||||
|
||||
// Send a notification to signal that definition is sent
|
||||
await ServiceHost.Instance.SendEvent(TelemetryNotification.Type, new TelemetryParams()
|
||||
{
|
||||
Params = new TelemetryProperties
|
||||
{
|
||||
EventName = TelemetryEventNames.PeekDefinitionRequested
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// turn off this code until needed (10/28/2016)
|
||||
#if false
|
||||
private static async Task HandleReferencesRequest(
|
||||
ReferencesParams referencesParams,
|
||||
RequestContext<Location[]> requestContext)
|
||||
@@ -304,19 +333,39 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
await Task.FromResult(true);
|
||||
}
|
||||
|
||||
private static async Task HandleSignatureHelpRequest(
|
||||
TextDocumentPosition textDocumentPosition,
|
||||
RequestContext<SignatureHelp> requestContext)
|
||||
{
|
||||
await Task.FromResult(true);
|
||||
}
|
||||
|
||||
private static async Task HandleDocumentHighlightRequest(
|
||||
TextDocumentPosition textDocumentPosition,
|
||||
RequestContext<DocumentHighlight[]> requestContext)
|
||||
{
|
||||
await Task.FromResult(true);
|
||||
}
|
||||
#endif
|
||||
|
||||
private static async Task HandleSignatureHelpRequest(
|
||||
TextDocumentPosition textDocumentPosition,
|
||||
RequestContext<SignatureHelp> requestContext)
|
||||
{
|
||||
// check if Intellisense suggestions are enabled
|
||||
if (!WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings.IsSuggestionsEnabled)
|
||||
{
|
||||
await Task.FromResult(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
ScriptFile scriptFile = WorkspaceService<SqlToolsSettings>.Instance.Workspace.GetFile(
|
||||
textDocumentPosition.TextDocument.Uri);
|
||||
|
||||
SignatureHelp help = LanguageService.Instance.GetSignatureHelp(textDocumentPosition, scriptFile);
|
||||
if (help != null)
|
||||
{
|
||||
await requestContext.SendResult(help);
|
||||
}
|
||||
else
|
||||
{
|
||||
await requestContext.SendResult(new SignatureHelp());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task HandleHoverRequest(
|
||||
TextDocumentPosition textDocumentPosition,
|
||||
@@ -361,7 +410,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
eventContext);
|
||||
}
|
||||
|
||||
await Task.FromResult(true);
|
||||
await Task.FromResult(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -555,7 +604,10 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
}
|
||||
}
|
||||
|
||||
AutoCompleteHelper.PrepopulateCommonMetadata(info, scriptInfo, this.BindingQueue);
|
||||
AutoCompleteHelper.PrepopulateCommonMetadata(info, scriptInfo, this.BindingQueue);
|
||||
|
||||
// Send a notification to signal that autocomplete is ready
|
||||
ServiceHost.Instance.SendEvent(IntelliSenseReadyNotification.Type, new IntelliSenseReadyParams() {OwnerUri = info.OwnerUri});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -626,6 +678,106 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
return completionItem;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get definition for a selected sql object using SMO Scripting
|
||||
/// </summary>
|
||||
/// <param name="textDocumentPosition"></param>
|
||||
/// <param name="scriptFile"></param>
|
||||
/// <param name="connInfo"></param>
|
||||
/// <returns> Location with the URI of the script file</returns>
|
||||
internal Location[] GetDefinition(TextDocumentPosition textDocumentPosition, ScriptFile scriptFile, ConnectionInfo connInfo)
|
||||
{
|
||||
// Parse sql
|
||||
ScriptParseInfo scriptParseInfo = GetScriptParseInfo(textDocumentPosition.TextDocument.Uri);
|
||||
if (scriptParseInfo == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (RequiresReparse(scriptParseInfo, scriptFile))
|
||||
{
|
||||
scriptParseInfo.ParseResult = ParseAndBind(scriptFile, connInfo);
|
||||
}
|
||||
|
||||
// Get token from selected text
|
||||
Token selectedToken = ScriptDocumentInfo.GetToken(scriptParseInfo, textDocumentPosition.Position.Line + 1, textDocumentPosition.Position.Character);
|
||||
if (selectedToken == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
// Strip "[" and "]"(if present) from the token text to enable matching with the suggestions.
|
||||
// The suggestion title does not contain any sql punctuation
|
||||
string tokenText = TextUtilities.RemoveSquareBracketSyntax(selectedToken.Text);
|
||||
|
||||
if (scriptParseInfo.IsConnected && Monitor.TryEnter(scriptParseInfo.BuildingMetadataLock))
|
||||
{
|
||||
try
|
||||
{
|
||||
// Queue the task with the binding queue
|
||||
QueueItem queueItem = this.BindingQueue.QueueBindingOperation(
|
||||
key: scriptParseInfo.ConnectionKey,
|
||||
bindingTimeout: LanguageService.PeekDefinitionTimeout,
|
||||
bindOperation: (bindingContext, cancelToken) =>
|
||||
{
|
||||
// Get suggestions for the token
|
||||
int parserLine = textDocumentPosition.Position.Line + 1;
|
||||
int parserColumn = textDocumentPosition.Position.Character + 1;
|
||||
IEnumerable<Declaration> declarationItems = Resolver.FindCompletions(
|
||||
scriptParseInfo.ParseResult,
|
||||
parserLine, parserColumn,
|
||||
bindingContext.MetadataDisplayInfoProvider);
|
||||
|
||||
// Match token with the suggestions(declaration items) returned
|
||||
string schemaName = this.GetSchemaName(scriptParseInfo, textDocumentPosition.Position, scriptFile);
|
||||
PeekDefinition peekDefinition = new PeekDefinition(connInfo);
|
||||
return peekDefinition.GetScript(declarationItems, tokenText, schemaName);
|
||||
|
||||
|
||||
});
|
||||
|
||||
// wait for the queue item
|
||||
queueItem.ItemProcessed.WaitOne();
|
||||
return queueItem.GetResultAsT<Location[]>();
|
||||
}
|
||||
finally
|
||||
{
|
||||
Monitor.Exit(scriptParseInfo.BuildingMetadataLock);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extract schema name for a token, if present
|
||||
/// </summary>
|
||||
/// <param name="scriptParseInfo"></param>
|
||||
/// <param name="position"></param>
|
||||
/// <param name="scriptFile"></param>
|
||||
/// <returns> schema nama</returns>
|
||||
private string GetSchemaName(ScriptParseInfo scriptParseInfo, Position position, ScriptFile scriptFile)
|
||||
{
|
||||
// Offset index by 1 for sql parser
|
||||
int startLine = position.Line + 1;
|
||||
int startColumn = position.Character + 1;
|
||||
|
||||
// Get schema name
|
||||
if (scriptParseInfo != null && scriptParseInfo.ParseResult != null && scriptParseInfo.ParseResult.Script != null && scriptParseInfo.ParseResult.Script.Tokens != null)
|
||||
{
|
||||
var tokenIndex = scriptParseInfo.ParseResult.Script.TokenManager.FindToken(startLine, startColumn);
|
||||
var prevTokenIndex = scriptParseInfo.ParseResult.Script.TokenManager.GetPreviousSignificantTokenIndex(tokenIndex);
|
||||
var prevTokenText = scriptParseInfo.ParseResult.Script.TokenManager.GetText(prevTokenIndex);
|
||||
if (prevTokenText != null && prevTokenText.Equals("."))
|
||||
{
|
||||
var schemaTokenIndex = scriptParseInfo.ParseResult.Script.TokenManager.GetPreviousSignificantTokenIndex(prevTokenIndex);
|
||||
Token schemaToken = scriptParseInfo.ParseResult.Script.TokenManager.GetToken(schemaTokenIndex);
|
||||
return TextUtilities.RemoveSquareBracketSyntax(schemaToken.Text);
|
||||
}
|
||||
}
|
||||
// if no schema name, returns null
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get quick info hover tooltips for the current position
|
||||
/// </summary>
|
||||
@@ -682,171 +834,137 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the completion item list for the current text position.
|
||||
/// This method does not await cache builds since it expects to return quickly
|
||||
/// Get function signature help for the current position
|
||||
/// </summary>
|
||||
/// <param name="textDocumentPosition"></param>
|
||||
public CompletionItem[] GetCompletionItems(
|
||||
TextDocumentPosition textDocumentPosition,
|
||||
ScriptFile scriptFile,
|
||||
ConnectionInfo connInfo)
|
||||
internal SignatureHelp GetSignatureHelp(TextDocumentPosition textDocumentPosition, ScriptFile scriptFile)
|
||||
{
|
||||
// initialize some state to parse and bind the current script file
|
||||
this.currentCompletionParseInfo = null;
|
||||
CompletionItem[] resultCompletionItems = null;
|
||||
string filePath = textDocumentPosition.TextDocument.Uri;
|
||||
int startLine = textDocumentPosition.Position.Line;
|
||||
int parserLine = textDocumentPosition.Position.Line + 1;
|
||||
int startColumn = TextUtilities.PositionOfPrevDelimeter(
|
||||
scriptFile.Contents,
|
||||
textDocumentPosition.Position.Line,
|
||||
textDocumentPosition.Position.Character);
|
||||
int endColumn = TextUtilities.PositionOfNextDelimeter(
|
||||
scriptFile.Contents,
|
||||
textDocumentPosition.Position.Line,
|
||||
textDocumentPosition.Position.Character);
|
||||
int parserColumn = textDocumentPosition.Position.Character + 1;
|
||||
bool useLowerCaseSuggestions = this.CurrentSettings.SqlTools.IntelliSense.LowerCaseSuggestions.Value;
|
||||
int endColumn = textDocumentPosition.Position.Character;
|
||||
|
||||
// get the current script parse info object
|
||||
ScriptParseInfo scriptParseInfo = GetScriptParseInfo(textDocumentPosition.TextDocument.Uri);
|
||||
|
||||
if (scriptParseInfo == null)
|
||||
{
|
||||
return AutoCompleteHelper.GetDefaultCompletionItems(
|
||||
startLine,
|
||||
startColumn,
|
||||
endColumn,
|
||||
useLowerCaseSuggestions);
|
||||
// Cache not set up yet - skip and wait until later
|
||||
return null;
|
||||
}
|
||||
|
||||
ConnectionInfo connInfo;
|
||||
LanguageService.ConnectionServiceInstance.TryFindConnection(
|
||||
scriptFile.ClientFilePath,
|
||||
out connInfo);
|
||||
|
||||
// reparse and bind the SQL statement if needed
|
||||
if (RequiresReparse(scriptParseInfo, scriptFile))
|
||||
{
|
||||
ParseAndBind(scriptFile, connInfo);
|
||||
}
|
||||
|
||||
// if the parse failed then return the default list
|
||||
if (scriptParseInfo.ParseResult == null)
|
||||
if (scriptParseInfo.ParseResult != null)
|
||||
{
|
||||
return AutoCompleteHelper.GetDefaultCompletionItems(
|
||||
startLine,
|
||||
startColumn,
|
||||
endColumn,
|
||||
useLowerCaseSuggestions);
|
||||
}
|
||||
|
||||
// need to adjust line & column for base-1 parser indices
|
||||
Token token = GetToken(scriptParseInfo, parserLine, parserColumn);
|
||||
string tokenText = token != null ? token.Text : null;
|
||||
|
||||
// check if the file is connected and the file lock is available
|
||||
if (scriptParseInfo.IsConnected && Monitor.TryEnter(scriptParseInfo.BuildingMetadataLock))
|
||||
{
|
||||
try
|
||||
{
|
||||
// queue the completion task with the binding queue
|
||||
QueueItem queueItem = this.BindingQueue.QueueBindingOperation(
|
||||
key: scriptParseInfo.ConnectionKey,
|
||||
bindingTimeout: LanguageService.BindingTimeout,
|
||||
bindOperation: (bindingContext, cancelToken) =>
|
||||
{
|
||||
// get the completion list from SQL Parser
|
||||
scriptParseInfo.CurrentSuggestions = Resolver.FindCompletions(
|
||||
scriptParseInfo.ParseResult,
|
||||
parserLine,
|
||||
parserColumn,
|
||||
bindingContext.MetadataDisplayInfoProvider);
|
||||
|
||||
// cache the current script parse info object to resolve completions later
|
||||
this.currentCompletionParseInfo = scriptParseInfo;
|
||||
|
||||
// convert the suggestion list to the VS Code format
|
||||
return AutoCompleteHelper.ConvertDeclarationsToCompletionItems(
|
||||
scriptParseInfo.CurrentSuggestions,
|
||||
startLine,
|
||||
startColumn,
|
||||
endColumn);
|
||||
},
|
||||
timeoutOperation: (bindingContext) =>
|
||||
{
|
||||
// return the default list if the connected bind fails
|
||||
return AutoCompleteHelper.GetDefaultCompletionItems(
|
||||
startLine,
|
||||
startColumn,
|
||||
endColumn,
|
||||
useLowerCaseSuggestions,
|
||||
tokenText);
|
||||
});
|
||||
|
||||
// wait for the queue item
|
||||
queueItem.ItemProcessed.WaitOne();
|
||||
|
||||
var completionItems = queueItem.GetResultAsT<CompletionItem[]>();
|
||||
if (completionItems != null && completionItems.Length > 0)
|
||||
{
|
||||
resultCompletionItems = completionItems;
|
||||
}
|
||||
else if (!ShouldShowCompletionList(token))
|
||||
{
|
||||
resultCompletionItems = AutoCompleteHelper.EmptyCompletionList;
|
||||
}
|
||||
}
|
||||
finally
|
||||
if (Monitor.TryEnter(scriptParseInfo.BuildingMetadataLock))
|
||||
{
|
||||
Monitor.Exit(scriptParseInfo.BuildingMetadataLock);
|
||||
}
|
||||
}
|
||||
|
||||
// if there are no completions then provide the default list
|
||||
if (resultCompletionItems == null)
|
||||
{
|
||||
resultCompletionItems = AutoCompleteHelper.GetDefaultCompletionItems(
|
||||
startLine,
|
||||
startColumn,
|
||||
endColumn,
|
||||
useLowerCaseSuggestions,
|
||||
tokenText);
|
||||
}
|
||||
|
||||
return resultCompletionItems;
|
||||
}
|
||||
|
||||
private static Token GetToken(ScriptParseInfo scriptParseInfo, int startLine, int startColumn)
|
||||
{
|
||||
if (scriptParseInfo != null && scriptParseInfo.ParseResult != null && scriptParseInfo.ParseResult.Script != null && scriptParseInfo.ParseResult.Script.Tokens != null)
|
||||
{
|
||||
var tokenIndex = scriptParseInfo.ParseResult.Script.TokenManager.FindToken(startLine, startColumn);
|
||||
if (tokenIndex >= 0)
|
||||
{
|
||||
// return the current token
|
||||
int currentIndex = 0;
|
||||
foreach (var token in scriptParseInfo.ParseResult.Script.Tokens)
|
||||
try
|
||||
{
|
||||
if (currentIndex == tokenIndex)
|
||||
{
|
||||
return token;
|
||||
}
|
||||
++currentIndex;
|
||||
QueueItem queueItem = this.BindingQueue.QueueBindingOperation(
|
||||
key: scriptParseInfo.ConnectionKey,
|
||||
bindingTimeout: LanguageService.BindingTimeout,
|
||||
bindOperation: (bindingContext, cancelToken) =>
|
||||
{
|
||||
// get the list of possible current methods for signature help
|
||||
var methods = Resolver.FindMethods(
|
||||
scriptParseInfo.ParseResult,
|
||||
startLine + 1,
|
||||
endColumn + 1,
|
||||
bindingContext.MetadataDisplayInfoProvider);
|
||||
|
||||
// get positional information on the current method
|
||||
var methodLocations = Resolver.GetMethodNameAndParams(scriptParseInfo.ParseResult,
|
||||
startLine + 1,
|
||||
endColumn + 1,
|
||||
bindingContext.MetadataDisplayInfoProvider);
|
||||
|
||||
if (methodLocations != null)
|
||||
{
|
||||
// convert from the parser format to the VS Code wire format
|
||||
return AutoCompleteHelper.ConvertMethodHelpTextListToSignatureHelp(methods,
|
||||
methodLocations,
|
||||
startLine + 1,
|
||||
endColumn + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
queueItem.ItemProcessed.WaitOne();
|
||||
return queueItem.GetResultAsT<SignatureHelp>();
|
||||
}
|
||||
finally
|
||||
{
|
||||
Monitor.Exit(scriptParseInfo.BuildingMetadataLock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// return null if there isn't a tooltip for the current location
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool ShouldShowCompletionList(Token token)
|
||||
/// <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)
|
||||
{
|
||||
bool result = true;
|
||||
if (token != null)
|
||||
// initialize some state to parse and bind the current script file
|
||||
this.currentCompletionParseInfo = null;
|
||||
CompletionItem[] resultCompletionItems = null;
|
||||
CompletionService completionService = new CompletionService(BindingQueue);
|
||||
bool useLowerCaseSuggestions = this.CurrentSettings.SqlTools.IntelliSense.LowerCaseSuggestions.Value;
|
||||
|
||||
// get the current script parse info object
|
||||
ScriptParseInfo scriptParseInfo = GetScriptParseInfo(textDocumentPosition.TextDocument.Uri);
|
||||
|
||||
if (scriptParseInfo == null)
|
||||
{
|
||||
switch (token.Id)
|
||||
{
|
||||
case (int)Tokens.LEX_MULTILINE_COMMENT:
|
||||
case (int)Tokens.LEX_END_OF_LINE_COMMENT:
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
return AutoCompleteHelper.GetDefaultCompletionItems(ScriptDocumentInfo.CreateDefaultDocumentInfo(textDocumentPosition, scriptFile), useLowerCaseSuggestions);
|
||||
}
|
||||
return result;
|
||||
|
||||
ScriptDocumentInfo scriptDocumentInfo = new ScriptDocumentInfo(textDocumentPosition, scriptFile, scriptParseInfo);
|
||||
|
||||
// reparse and bind the SQL statement if needed
|
||||
if (RequiresReparse(scriptParseInfo, scriptFile))
|
||||
{
|
||||
ParseAndBind(scriptFile, connInfo);
|
||||
}
|
||||
|
||||
// if the parse failed then return the default list
|
||||
if (scriptParseInfo.ParseResult == null)
|
||||
{
|
||||
return AutoCompleteHelper.GetDefaultCompletionItems(scriptDocumentInfo, useLowerCaseSuggestions);
|
||||
}
|
||||
AutoCompletionResult result = completionService.CreateCompletions(connInfo, scriptDocumentInfo, useLowerCaseSuggestions);
|
||||
// cache the current script parse info object to resolve completions later
|
||||
this.currentCompletionParseInfo = scriptParseInfo;
|
||||
resultCompletionItems = result.CompletionItems;
|
||||
|
||||
// if there are no completions then provide the default list
|
||||
if (resultCompletionItems == null)
|
||||
{
|
||||
resultCompletionItems = AutoCompleteHelper.GetDefaultCompletionItems(scriptDocumentInfo, useLowerCaseSuggestions);
|
||||
}
|
||||
|
||||
return resultCompletionItems;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -868,23 +986,26 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
|
||||
// build a list of SQL script file markers from the errors
|
||||
List<ScriptFileMarker> markers = new List<ScriptFileMarker>();
|
||||
foreach (var error in parseResult.Errors)
|
||||
if (parseResult != null && parseResult.Errors != null)
|
||||
{
|
||||
markers.Add(new ScriptFileMarker()
|
||||
foreach (var error in parseResult.Errors)
|
||||
{
|
||||
Message = error.Message,
|
||||
Level = ScriptFileMarkerLevel.Error,
|
||||
ScriptRegion = new ScriptRegion()
|
||||
markers.Add(new ScriptFileMarker()
|
||||
{
|
||||
File = scriptFile.FilePath,
|
||||
StartLineNumber = error.Start.LineNumber,
|
||||
StartColumnNumber = error.Start.ColumnNumber,
|
||||
StartOffset = 0,
|
||||
EndLineNumber = error.End.LineNumber,
|
||||
EndColumnNumber = error.End.ColumnNumber,
|
||||
EndOffset = 0
|
||||
}
|
||||
});
|
||||
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();
|
||||
|
||||
@@ -0,0 +1,279 @@
|
||||
//
|
||||
// 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.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Data.SqlClient;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.SqlServer.Management.Smo;
|
||||
using Microsoft.SqlServer.Management.Common;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||
{
|
||||
/// <summary>
|
||||
/// Peek Definition/ Go to definition implementation
|
||||
/// Script sql objects and write create scripts to file
|
||||
/// </summary>
|
||||
internal class PeekDefinition
|
||||
{
|
||||
private ConnectionInfo connectionInfo;
|
||||
private string tempPath;
|
||||
|
||||
internal delegate StringCollection ScriptGetter(string objectName, string schemaName);
|
||||
|
||||
// Dictionary that holds the script getter for each type
|
||||
private Dictionary<DeclarationType, ScriptGetter> sqlScriptGetters =
|
||||
new Dictionary<DeclarationType, ScriptGetter>();
|
||||
|
||||
// Dictionary that holds the object name (as appears on the TSQL create statement)
|
||||
private Dictionary<DeclarationType, string> sqlObjectTypes = new Dictionary<DeclarationType, string>();
|
||||
|
||||
private Database Database
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.connectionInfo.SqlConnection != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Get server object from connection
|
||||
string connectionString = ConnectionService.BuildConnectionString(this.connectionInfo.ConnectionDetails);
|
||||
SqlConnection sqlConn = new SqlConnection(connectionString);
|
||||
sqlConn.Open();
|
||||
ServerConnection serverConn = new ServerConnection(sqlConn);
|
||||
Server server = new Server(serverConn);
|
||||
return server.Databases[this.connectionInfo.SqlConnection.Database];
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
Logger.Write(LogLevel.Error, "Exception at PeekDefinition Database.get() : " + ex.Message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
internal PeekDefinition(ConnectionInfo connInfo)
|
||||
{
|
||||
this.connectionInfo = connInfo;
|
||||
DirectoryInfo tempScriptDirectory = Directory.CreateDirectory(Path.GetTempPath() + "mssql_definition");
|
||||
this.tempPath = tempScriptDirectory.FullName;
|
||||
Initialize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add getters for each sql object supported by peek definition
|
||||
/// </summary>
|
||||
private void Initialize()
|
||||
{
|
||||
//Add script getters for each sql object
|
||||
|
||||
//Add tables to supported types
|
||||
AddSupportedType(DeclarationType.Table, GetTableScripts, "Table");
|
||||
|
||||
//Add views to supported types
|
||||
AddSupportedType(DeclarationType.View, GetViewScripts, "view");
|
||||
|
||||
//Add stored procedures to supported types
|
||||
AddSupportedType(DeclarationType.StoredProcedure, GetStoredProcedureScripts, "Procedure");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add the given type, scriptgetter and the typeName string to the respective dictionaries
|
||||
/// </summary>
|
||||
private void AddSupportedType(DeclarationType type, ScriptGetter scriptGetter, string typeName)
|
||||
{
|
||||
sqlScriptGetters.Add(type, scriptGetter);
|
||||
sqlObjectTypes.Add(type, typeName);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a file to a location array containing a location object as expected by the extension
|
||||
/// </summary>
|
||||
internal Location[] GetLocationFromFile(string tempFileName, int lineNumber)
|
||||
{
|
||||
if (Path.DirectorySeparatorChar.Equals('/'))
|
||||
{
|
||||
tempFileName = "file:" + tempFileName;
|
||||
}
|
||||
else
|
||||
{
|
||||
tempFileName = new Uri(tempFileName).AbsoluteUri;
|
||||
}
|
||||
Location[] locations = new[] {
|
||||
new Location {
|
||||
Uri = tempFileName,
|
||||
Range = new Range {
|
||||
Start = new Position { Line = lineNumber, Character = 1},
|
||||
End = new Position { Line = lineNumber + 1, Character = 1}
|
||||
}
|
||||
}
|
||||
};
|
||||
return locations;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get line number for the create statement
|
||||
/// </summary>
|
||||
private int GetStartOfCreate(string script, string createString)
|
||||
{
|
||||
string[] lines = script.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
|
||||
for (int lineNumber = 0; lineNumber < lines.Length; lineNumber++)
|
||||
{
|
||||
if (lines[lineNumber].IndexOf( createString, StringComparison.OrdinalIgnoreCase) >= 0)
|
||||
{
|
||||
return lineNumber;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the script of the selected token based on the type of the token
|
||||
/// </summary>
|
||||
/// <param name="declarationItems"></param>
|
||||
/// <param name="tokenText"></param>
|
||||
/// <param name="schemaName"></param>
|
||||
/// <returns>Location object of the script file</returns>
|
||||
internal Location[] GetScript(IEnumerable<Declaration> declarationItems, string tokenText, string schemaName)
|
||||
{
|
||||
foreach (Declaration declarationItem in declarationItems)
|
||||
{
|
||||
if (declarationItem.Title == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (declarationItem.Title.Equals(tokenText))
|
||||
{
|
||||
// Script object using SMO based on type
|
||||
DeclarationType type = declarationItem.Type;
|
||||
if (sqlScriptGetters.ContainsKey(type) && sqlObjectTypes.ContainsKey(type))
|
||||
{
|
||||
// On *nix and mac systems, the defaultSchema property throws an Exception when accessed.
|
||||
// This workaround ensures that a schema name is present by attempting
|
||||
// to get the schema name from the declaration item
|
||||
// If all fails, the default schema name is assumed to be "dbo"
|
||||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && string.IsNullOrEmpty(schemaName))
|
||||
{
|
||||
string fullObjectName = declarationItem.DatabaseQualifiedName;
|
||||
schemaName = this.GetSchemaFromDatabaseQualifiedName(fullObjectName, tokenText);
|
||||
}
|
||||
return GetSqlObjectDefinition(
|
||||
sqlScriptGetters[type],
|
||||
tokenText,
|
||||
schemaName,
|
||||
sqlObjectTypes[type]
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return schema name from the full name of the database. If schema is missing return dbo as schema name.
|
||||
/// </summary>
|
||||
/// <param name="fullObjectName"> The full database qualified name(database.schema.object)</param>
|
||||
/// <param name="objectName"> Object name</param>
|
||||
/// <returns>Schema name</returns>
|
||||
internal string GetSchemaFromDatabaseQualifiedName(string fullObjectName, string objectName)
|
||||
{
|
||||
string[] tokens = fullObjectName.Split('.');
|
||||
for (int i = tokens.Length - 1; i > 0; i--)
|
||||
{
|
||||
if(tokens[i].Equals(objectName))
|
||||
{
|
||||
return tokens[i-1];
|
||||
}
|
||||
}
|
||||
return "dbo";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Script a table using SMO
|
||||
/// </summary>
|
||||
/// <param name="tableName">Table name</param>
|
||||
/// <param name="schemaName">Schema name</param>
|
||||
/// <returns>String collection of scripts</returns>
|
||||
internal StringCollection GetTableScripts(string tableName, string schemaName)
|
||||
{
|
||||
return (schemaName != null) ? Database?.Tables[tableName, schemaName]?.Script()
|
||||
: Database?.Tables[tableName]?.Script();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Script a view using SMO
|
||||
/// </summary>
|
||||
/// <param name="viewName">View name</param>
|
||||
/// <param name="schemaName">Schema name </param>
|
||||
/// <returns>String collection of scripts</returns>
|
||||
internal StringCollection GetViewScripts(string viewName, string schemaName)
|
||||
{
|
||||
return (schemaName != null) ? Database?.Views[viewName, schemaName]?.Script()
|
||||
: Database?.Views[viewName]?.Script();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Script a stored procedure using SMO
|
||||
/// </summary>
|
||||
/// <param name="storedProcedureName">Stored Procedure name</param>
|
||||
/// <param name="schemaName">Schema Name</param>
|
||||
/// <returns>String collection of scripts</returns>
|
||||
internal StringCollection GetStoredProcedureScripts(string viewName, string schemaName)
|
||||
{
|
||||
return (schemaName != null) ? Database?.StoredProcedures[viewName, schemaName]?.Script()
|
||||
: Database?.StoredProcedures[viewName]?.Script();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Script a object using SMO and write to a file.
|
||||
/// </summary>
|
||||
/// <param name="sqlScriptGetter">Function that returns the SMO scripts for an object</param>
|
||||
/// <param name="objectName">SQL object name</param>
|
||||
/// <param name="schemaName">Schema name or null</param>
|
||||
/// <param name="objectType">Type of SQL object</param>
|
||||
/// <returns>Location object representing URI and range of the script file</returns>
|
||||
internal Location[] GetSqlObjectDefinition(
|
||||
ScriptGetter sqlScriptGetter,
|
||||
string objectName,
|
||||
string schemaName,
|
||||
string objectType)
|
||||
{
|
||||
StringCollection scripts = sqlScriptGetter(objectName, schemaName);
|
||||
string tempFileName = (schemaName != null) ? Path.Combine(this.tempPath, string.Format("{0}.{1}.sql", schemaName, objectName))
|
||||
: Path.Combine(this.tempPath, string.Format("{0}.sql", objectName));
|
||||
|
||||
if (scripts != null)
|
||||
{
|
||||
int lineNumber = 0;
|
||||
using (StreamWriter scriptFile = new StreamWriter(File.Open(tempFileName, FileMode.Create, FileAccess.ReadWrite)))
|
||||
{
|
||||
|
||||
foreach (string script in scripts)
|
||||
{
|
||||
string createSyntax = string.Format("CREATE {0}", objectType);
|
||||
if (script.IndexOf(createSyntax, StringComparison.OrdinalIgnoreCase) >= 0)
|
||||
{
|
||||
scriptFile.WriteLine(script);
|
||||
lineNumber = GetStartOfCreate(script, createSyntax);
|
||||
}
|
||||
}
|
||||
}
|
||||
return GetLocationFromFile(tempFileName, lineNumber);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Microsoft.SqlServer.Management.SqlParser.Parser;
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Completion
|
||||
{
|
||||
/// <summary>
|
||||
/// A class to calculate the numbers used by SQL parser using the text positions and content
|
||||
/// </summary>
|
||||
internal class ScriptDocumentInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Create new instance
|
||||
/// </summary>
|
||||
public ScriptDocumentInfo(TextDocumentPosition textDocumentPosition, ScriptFile scriptFile, ScriptParseInfo scriptParseInfo)
|
||||
: this(textDocumentPosition, scriptFile)
|
||||
{
|
||||
Validate.IsNotNull(nameof(scriptParseInfo), scriptParseInfo);
|
||||
|
||||
ScriptParseInfo = scriptParseInfo;
|
||||
// need to adjust line & column for base-1 parser indices
|
||||
Token = GetToken(scriptParseInfo, ParserLine, ParserColumn);
|
||||
}
|
||||
|
||||
private ScriptDocumentInfo(TextDocumentPosition textDocumentPosition, ScriptFile scriptFile)
|
||||
{
|
||||
StartLine = textDocumentPosition.Position.Line;
|
||||
ParserLine = textDocumentPosition.Position.Line + 1;
|
||||
StartColumn = TextUtilities.PositionOfPrevDelimeter(
|
||||
scriptFile.Contents,
|
||||
textDocumentPosition.Position.Line,
|
||||
textDocumentPosition.Position.Character);
|
||||
EndColumn = TextUtilities.PositionOfNextDelimeter(
|
||||
scriptFile.Contents,
|
||||
textDocumentPosition.Position.Line,
|
||||
textDocumentPosition.Position.Character);
|
||||
ParserColumn = textDocumentPosition.Position.Character + 1;
|
||||
Contents = scriptFile.Contents;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="ScriptDocumentInfo"/> with no backing <see cref="ScriptParseInfo"/> defined
|
||||
/// </summary>
|
||||
/// <param name="textDocumentPosition">A <see cref="TextDocumentPosition"/></param>
|
||||
/// <param name="scriptFile">A <see cref="ScriptFile"/> to process</param>
|
||||
/// <returns></returns>
|
||||
public static ScriptDocumentInfo CreateDefaultDocumentInfo(TextDocumentPosition textDocumentPosition, ScriptFile scriptFile)
|
||||
{
|
||||
return new ScriptDocumentInfo(textDocumentPosition, scriptFile);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a string containing the full contents of the file.
|
||||
/// </summary>
|
||||
public string Contents { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Script Parse Info Instance
|
||||
/// </summary>
|
||||
public ScriptParseInfo ScriptParseInfo { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Start Line
|
||||
/// </summary>
|
||||
public int StartLine { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Parser Line
|
||||
/// </summary>
|
||||
public int ParserLine { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Start Column
|
||||
/// </summary>
|
||||
public int StartColumn { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// end Column
|
||||
/// </summary>
|
||||
public int EndColumn { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Parser Column
|
||||
/// </summary>
|
||||
public int ParserColumn { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The token text in the file content used for completion list
|
||||
/// </summary>
|
||||
public string TokenText
|
||||
{
|
||||
get
|
||||
{
|
||||
return Token != null ? Token.Text : null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The token in the file content used for completion list
|
||||
/// </summary>
|
||||
public Token Token { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the token that will be used by SQL parser for creating the completion list
|
||||
/// </summary>
|
||||
internal static Token GetToken(ScriptParseInfo scriptParseInfo, int startLine, int startColumn)
|
||||
{
|
||||
if (scriptParseInfo != null && scriptParseInfo.ParseResult != null && scriptParseInfo.ParseResult.Script != null && scriptParseInfo.ParseResult.Script.Tokens != null)
|
||||
{
|
||||
var tokenIndex = scriptParseInfo.ParseResult.Script.TokenManager.FindToken(startLine, startColumn);
|
||||
if (tokenIndex >= 0)
|
||||
{
|
||||
// return the current token
|
||||
int currentIndex = 0;
|
||||
foreach (var token in scriptParseInfo.ParseResult.Script.Tokens)
|
||||
{
|
||||
if (currentIndex == tokenIndex)
|
||||
{
|
||||
return token;
|
||||
}
|
||||
++currentIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,21 +57,50 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
|
||||
#endregion
|
||||
|
||||
internal Batch(string batchText, int startLine, int startColumn, int endLine, int endColumn, IFileStreamFactory outputFileFactory)
|
||||
internal Batch(string batchText, SelectionData selection, int ordinalId, IFileStreamFactory outputFileFactory)
|
||||
{
|
||||
// Sanity check for input
|
||||
Validate.IsNotNullOrEmptyString(nameof(batchText), batchText);
|
||||
Validate.IsNotNull(nameof(outputFileFactory), outputFileFactory);
|
||||
Validate.IsGreaterThan(nameof(ordinalId), ordinalId, 0);
|
||||
|
||||
// Initialize the internal state
|
||||
BatchText = batchText;
|
||||
Selection = new SelectionData(startLine, startColumn, endLine, endColumn);
|
||||
Selection = selection;
|
||||
executionStartTime = DateTime.Now;
|
||||
HasExecuted = false;
|
||||
Id = ordinalId;
|
||||
resultSets = new List<ResultSet>();
|
||||
resultMessages = new List<ResultMessage>();
|
||||
this.outputFileFactory = outputFileFactory;
|
||||
}
|
||||
|
||||
#region Events
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronous handler for when batches are completed
|
||||
/// </summary>
|
||||
/// <param name="batch">The batch that completed</param>
|
||||
public delegate Task BatchAsyncEventHandler(Batch batch);
|
||||
|
||||
/// <summary>
|
||||
/// Event that will be called when the batch has completed execution
|
||||
/// </summary>
|
||||
public event BatchAsyncEventHandler BatchCompletion;
|
||||
|
||||
/// <summary>
|
||||
/// Event to call when the batch has started execution
|
||||
/// </summary>
|
||||
public event BatchAsyncEventHandler BatchStart;
|
||||
|
||||
/// <summary>
|
||||
/// Event that will be called when the resultset has completed execution. It will not be
|
||||
/// called from the Batch but from the ResultSet instance
|
||||
/// </summary>
|
||||
public event ResultSet.ResultSetAsyncEventHandler ResultSetCompletion;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
@@ -113,6 +142,11 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
/// </summary>
|
||||
public bool HasExecuted { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Ordinal of the batch in the query
|
||||
/// </summary>
|
||||
public int Id { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Messages that have come back from the server
|
||||
/// </summary>
|
||||
@@ -136,12 +170,39 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
{
|
||||
get
|
||||
{
|
||||
return ResultSets.Select((set, index) => new ResultSetSummary()
|
||||
lock (resultSets)
|
||||
{
|
||||
ColumnInfo = set.Columns,
|
||||
Id = index,
|
||||
RowCount = set.RowCount
|
||||
}).ToArray();
|
||||
return resultSets.Select(set => set.Summary).ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="BatchSummary"/> based on the batch instance
|
||||
/// </summary>
|
||||
public BatchSummary Summary
|
||||
{
|
||||
get
|
||||
{
|
||||
// Batch summary with information available at start
|
||||
BatchSummary summary = new BatchSummary
|
||||
{
|
||||
HasError = HasError,
|
||||
Id = Id,
|
||||
Selection = Selection,
|
||||
ExecutionStart = ExecutionStartTimeStamp
|
||||
};
|
||||
|
||||
// Add on extra details if we finished executing it
|
||||
if (HasExecuted)
|
||||
{
|
||||
summary.ResultSetSummaries = ResultSummaries;
|
||||
summary.Messages = ResultMessages.ToArray();
|
||||
summary.ExecutionEnd = ExecutionEndTimeStamp;
|
||||
summary.ExecutionElapsed = ExecutionElapsedTime;
|
||||
}
|
||||
|
||||
return summary;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,10 +228,18 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
throw new InvalidOperationException("Batch has already executed.");
|
||||
}
|
||||
|
||||
// Notify that we've started execution
|
||||
if (BatchStart != null)
|
||||
{
|
||||
await BatchStart(this);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
DbCommand command = null;
|
||||
// Register the message listener to *this instance* of the batch
|
||||
// Note: This is being done to associate messages with batches
|
||||
ReliableSqlConnection sqlConn = conn as ReliableSqlConnection;
|
||||
DbCommand command;
|
||||
if (sqlConn != null)
|
||||
{
|
||||
// Register the message listener to *this instance* of the batch
|
||||
@@ -179,7 +248,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
command = sqlConn.GetUnderlyingConnection().CreateCommand();
|
||||
|
||||
// Add a handler for when the command completes
|
||||
SqlCommand sqlCommand = (SqlCommand) command;
|
||||
SqlCommand sqlCommand = (SqlCommand)command;
|
||||
sqlCommand.StatementCompleted += StatementCompletedHandler;
|
||||
}
|
||||
else
|
||||
@@ -202,6 +271,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
// Execute the command to get back a reader
|
||||
using (DbDataReader reader = await command.ExecuteReaderAsync(cancellationToken))
|
||||
{
|
||||
int resultSetOrdinal = 0;
|
||||
do
|
||||
{
|
||||
// Skip this result set if there aren't any rows (ie, UPDATE/DELETE/etc queries)
|
||||
@@ -211,11 +281,16 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
}
|
||||
|
||||
// This resultset has results (ie, SELECT/etc queries)
|
||||
ResultSet resultSet = new ResultSet(reader, outputFileFactory);
|
||||
|
||||
ResultSet resultSet = new ResultSet(reader, resultSetOrdinal, Id, outputFileFactory);
|
||||
resultSet.ResultCompletion += ResultSetCompletion;
|
||||
|
||||
// Add the result set to the results of the query
|
||||
resultSets.Add(resultSet);
|
||||
|
||||
lock (resultSets)
|
||||
{
|
||||
resultSets.Add(resultSet);
|
||||
resultSetOrdinal++;
|
||||
}
|
||||
|
||||
// Read until we hit the end of the result set
|
||||
await resultSet.ReadResultToEnd(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
@@ -258,6 +333,12 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
// Mark that we have executed
|
||||
HasExecuted = true;
|
||||
executionEndTime = DateTime.Now;
|
||||
|
||||
// Fire an event to signify that the batch has completed
|
||||
if (BatchCompletion != null)
|
||||
{
|
||||
await BatchCompletion(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,14 +351,21 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
/// <returns>A subset of results</returns>
|
||||
public Task<ResultSetSubset> GetSubset(int resultSetIndex, int startRow, int rowCount)
|
||||
{
|
||||
// Sanity check to make sure we have valid numbers
|
||||
if (resultSetIndex < 0 || resultSetIndex >= resultSets.Count)
|
||||
ResultSet targetResultSet;
|
||||
lock (resultSets)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(resultSetIndex), SR.QueryServiceSubsetResultSetOutOfRange);
|
||||
// Sanity check to make sure we have valid numbers
|
||||
if (resultSetIndex < 0 || resultSetIndex >= resultSets.Count)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(resultSetIndex),
|
||||
SR.QueryServiceSubsetResultSetOutOfRange);
|
||||
}
|
||||
|
||||
targetResultSet = resultSets[resultSetIndex];
|
||||
}
|
||||
|
||||
// Retrieve the result set
|
||||
return resultSets[resultSetIndex].GetSubset(startRow, rowCount);
|
||||
return targetResultSet.GetSubset(startRow, rowCount);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -377,9 +465,12 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
foreach (ResultSet r in ResultSets)
|
||||
lock (resultSets)
|
||||
{
|
||||
r.Dispose();
|
||||
foreach (ResultSet r in resultSets)
|
||||
{
|
||||
r.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -152,6 +152,13 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor, used for deserializing JSON RPC only
|
||||
/// </summary>
|
||||
public DbColumnWrapper()
|
||||
{
|
||||
}
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// Parameters to be sent back as part of a QueryExecuteBatchCompleteEvent to indicate that a
|
||||
/// batch of a query completed.
|
||||
/// </summary>
|
||||
public class QueryExecuteBatchNotificationParams
|
||||
{
|
||||
/// <summary>
|
||||
/// Summary of the batch that just completed
|
||||
/// </summary>
|
||||
public BatchSummary BatchSummary { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// URI for the editor that owns the query
|
||||
/// </summary>
|
||||
public string OwnerUri { get; set; }
|
||||
}
|
||||
|
||||
public class QueryExecuteBatchCompleteEvent
|
||||
{
|
||||
public static readonly
|
||||
EventType<QueryExecuteBatchNotificationParams> Type =
|
||||
EventType<QueryExecuteBatchNotificationParams>.Create("query/batchComplete");
|
||||
}
|
||||
|
||||
public class QueryExecuteBatchStartEvent
|
||||
{
|
||||
public static readonly
|
||||
EventType<QueryExecuteBatchNotificationParams> Type =
|
||||
EventType<QueryExecuteBatchNotificationParams>.Create("query/batchStart");
|
||||
}
|
||||
}
|
||||
@@ -7,21 +7,6 @@ using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// Container class for a selection range from file
|
||||
/// </summary>
|
||||
public class SelectionData {
|
||||
public int StartLine { get; set; }
|
||||
public int StartColumn { get; set; }
|
||||
public int EndLine { get; set; }
|
||||
public int EndColumn { get; set; }
|
||||
public SelectionData(int startLine, int startColumn, int endLine, int endColumn) {
|
||||
StartLine = startLine;
|
||||
StartColumn = startColumn;
|
||||
EndLine = endLine;
|
||||
EndColumn = endColumn;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Parameters for the query execute request
|
||||
/// </summary>
|
||||
@@ -44,7 +29,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
|
||||
public class QueryExecuteResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Connection error messages. Optional, can be set to null to indicate no errors
|
||||
/// Informational messages from the query runner. Optional, can be set to null.
|
||||
/// </summary>
|
||||
public string Messages { get; set; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
|
||||
{
|
||||
public class QueryExecuteResultSetCompleteParams
|
||||
{
|
||||
public ResultSetSummary ResultSetSummary { get; set; }
|
||||
|
||||
public string OwnerUri { get; set; }
|
||||
}
|
||||
|
||||
public class QueryExecuteResultSetCompleteEvent
|
||||
{
|
||||
public static readonly
|
||||
EventType<QueryExecuteResultSetCompleteParams> Type =
|
||||
EventType<QueryExecuteResultSetCompleteParams>.Create("query/resultSetComplete");
|
||||
}
|
||||
}
|
||||
@@ -40,5 +40,12 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
|
||||
Time = DateTime.Now.ToString("o");
|
||||
Message = message;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor, used for deserializing JSON RPC only
|
||||
/// </summary>
|
||||
public ResultMessage()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,11 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The ID of the batch set within the query
|
||||
/// </summary>
|
||||
public int BatchId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The number of rows that was returned with the resultset
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// Container class for a selection range from file
|
||||
/// </summary>
|
||||
/// TODO: Remove this in favor of buffer range end-to-end
|
||||
public class SelectionData
|
||||
{
|
||||
public SelectionData() { }
|
||||
|
||||
public SelectionData(int startLine, int startColumn, int endLine, int endColumn)
|
||||
{
|
||||
StartLine = startLine;
|
||||
StartColumn = startColumn;
|
||||
EndLine = endLine;
|
||||
EndColumn = endColumn;
|
||||
}
|
||||
|
||||
#region Properties
|
||||
|
||||
public int EndColumn { get; set; }
|
||||
|
||||
public int EndLine { get; set; }
|
||||
|
||||
public int StartColumn { get; set; }
|
||||
public int StartLine { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
public BufferRange ToBufferRange()
|
||||
{
|
||||
return new BufferRange(StartLine, StartColumn, EndLine, EndColumn);
|
||||
}
|
||||
|
||||
public static SelectionData FromBufferRange(BufferRange range)
|
||||
{
|
||||
return new SelectionData
|
||||
{
|
||||
StartLine = range.Start.Line,
|
||||
StartColumn = range.Start.Column,
|
||||
EndLine = range.End.Line,
|
||||
EndColumn = range.End.Column
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,273 +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.Diagnostics;
|
||||
using System.IO;
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
{
|
||||
/// <summary>
|
||||
/// Wrapper for a file stream, providing simplified creation, deletion, read, and write
|
||||
/// functionality.
|
||||
/// </summary>
|
||||
public class FileStreamWrapper : IFileStreamWrapper
|
||||
{
|
||||
#region Member Variables
|
||||
|
||||
private byte[] buffer;
|
||||
private int bufferDataSize;
|
||||
private FileStream fileStream;
|
||||
private long startOffset;
|
||||
private long currentOffset;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new FileStreamWrapper and initializes its state.
|
||||
/// </summary>
|
||||
public FileStreamWrapper()
|
||||
{
|
||||
// Initialize the internal state
|
||||
bufferDataSize = 0;
|
||||
startOffset = 0;
|
||||
currentOffset = 0;
|
||||
}
|
||||
|
||||
#region IFileStreamWrapper Implementation
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the wrapper by creating the internal buffer and opening the requested file.
|
||||
/// If the file does not already exist, it will be created.
|
||||
/// </summary>
|
||||
/// <param name="fileName">Name of the file to open/create</param>
|
||||
/// <param name="bufferLength">The length of the internal buffer</param>
|
||||
/// <param name="accessMethod">
|
||||
/// Whether or not the wrapper will be used for reading. If <c>true</c>, any calls to a
|
||||
/// method that writes will cause an InvalidOperationException
|
||||
/// </param>
|
||||
public void Init(string fileName, int bufferLength, FileAccess accessMethod)
|
||||
{
|
||||
// Sanity check for valid buffer length, fileName, and accessMethod
|
||||
Validate.IsGreaterThan(nameof(bufferLength), bufferLength, 0);
|
||||
Validate.IsNotNullOrWhitespaceString(nameof(fileName), fileName);
|
||||
if (accessMethod == FileAccess.Write)
|
||||
{
|
||||
throw new ArgumentException(SR.QueryServiceFileWrapperWriteOnly, nameof(fileName));
|
||||
}
|
||||
|
||||
// Setup the buffer
|
||||
buffer = new byte[bufferLength];
|
||||
|
||||
// Open the requested file for reading/writing, creating one if it doesn't exist
|
||||
fileStream = new FileStream(fileName, FileMode.OpenOrCreate, accessMethod, FileShare.ReadWrite,
|
||||
bufferLength, false /*don't use asyncio*/);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads data into a buffer from the current offset into the file
|
||||
/// </summary>
|
||||
/// <param name="buf">The buffer to output the read data to</param>
|
||||
/// <param name="bytes">The number of bytes to read into the buffer</param>
|
||||
/// <returns>The number of bytes read</returns>
|
||||
public int ReadData(byte[] buf, int bytes)
|
||||
{
|
||||
return ReadData(buf, bytes, currentOffset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads data into a buffer from the specified offset into the file
|
||||
/// </summary>
|
||||
/// <param name="buf">The buffer to output the read data to</param>
|
||||
/// <param name="bytes">The number of bytes to read into the buffer</param>
|
||||
/// <param name="offset">The offset into the file to start reading bytes from</param>
|
||||
/// <returns>The number of bytes read</returns>
|
||||
public int ReadData(byte[] buf, int bytes, long offset)
|
||||
{
|
||||
// Make sure that we're initialized before performing operations
|
||||
if (buffer == null)
|
||||
{
|
||||
throw new InvalidOperationException(SR.QueryServiceFileWrapperNotInitialized);
|
||||
}
|
||||
|
||||
MoveTo(offset);
|
||||
|
||||
int bytesCopied = 0;
|
||||
while (bytesCopied < bytes)
|
||||
{
|
||||
int bufferOffset, bytesToCopy;
|
||||
GetByteCounts(bytes, bytesCopied, out bufferOffset, out bytesToCopy);
|
||||
Buffer.BlockCopy(buffer, bufferOffset, buf, bytesCopied, bytesToCopy);
|
||||
bytesCopied += bytesToCopy;
|
||||
|
||||
if (bytesCopied < bytes && // did not get all the bytes yet
|
||||
bufferDataSize == buffer.Length) // since current data buffer is full we should continue reading the file
|
||||
{
|
||||
// move forward one full length of the buffer
|
||||
MoveTo(startOffset + buffer.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
// copied all the bytes requested or possible, adjust the current buffer pointer
|
||||
currentOffset += bytesToCopy;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return bytesCopied;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes data to the underlying filestream, with buffering.
|
||||
/// </summary>
|
||||
/// <param name="buf">The buffer of bytes to write to the filestream</param>
|
||||
/// <param name="bytes">The number of bytes to write</param>
|
||||
/// <returns>The number of bytes written</returns>
|
||||
public int WriteData(byte[] buf, int bytes)
|
||||
{
|
||||
// Make sure that we're initialized before performing operations
|
||||
if (buffer == null)
|
||||
{
|
||||
throw new InvalidOperationException(SR.QueryServiceFileWrapperNotInitialized);
|
||||
}
|
||||
if (!fileStream.CanWrite)
|
||||
{
|
||||
throw new InvalidOperationException(SR.QueryServiceFileWrapperReadOnly);
|
||||
}
|
||||
|
||||
int bytesCopied = 0;
|
||||
while (bytesCopied < bytes)
|
||||
{
|
||||
int bufferOffset, bytesToCopy;
|
||||
GetByteCounts(bytes, bytesCopied, out bufferOffset, out bytesToCopy);
|
||||
Buffer.BlockCopy(buf, bytesCopied, buffer, bufferOffset, bytesToCopy);
|
||||
bytesCopied += bytesToCopy;
|
||||
|
||||
// adjust the current buffer pointer
|
||||
currentOffset += bytesToCopy;
|
||||
|
||||
if (bytesCopied < bytes) // did not get all the bytes yet
|
||||
{
|
||||
Debug.Assert((int)(currentOffset - startOffset) == buffer.Length);
|
||||
// flush buffer
|
||||
Flush();
|
||||
}
|
||||
}
|
||||
Debug.Assert(bytesCopied == bytes);
|
||||
return bytesCopied;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flushes the internal buffer to the filestream
|
||||
/// </summary>
|
||||
public void Flush()
|
||||
{
|
||||
// Make sure that we're initialized before performing operations
|
||||
if (buffer == null)
|
||||
{
|
||||
throw new InvalidOperationException(SR.QueryServiceFileWrapperNotInitialized);
|
||||
}
|
||||
if (!fileStream.CanWrite)
|
||||
{
|
||||
throw new InvalidOperationException(SR.QueryServiceFileWrapperReadOnly);
|
||||
}
|
||||
|
||||
// Make sure we are at the right place in the file
|
||||
Debug.Assert(fileStream.Position == startOffset);
|
||||
|
||||
int bytesToWrite = (int)(currentOffset - startOffset);
|
||||
fileStream.Write(buffer, 0, bytesToWrite);
|
||||
startOffset += bytesToWrite;
|
||||
fileStream.Flush();
|
||||
|
||||
Debug.Assert(startOffset == currentOffset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the given file (ideally, created with this wrapper) from the filesystem
|
||||
/// </summary>
|
||||
/// <param name="fileName">The path to the file to delete</param>
|
||||
public static void DeleteFile(string fileName)
|
||||
{
|
||||
File.Delete(fileName);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Perform calculations to determine how many bytes to copy and what the new buffer offset
|
||||
/// will be for copying.
|
||||
/// </summary>
|
||||
/// <param name="bytes">Number of bytes requested to copy</param>
|
||||
/// <param name="bytesCopied">Number of bytes copied so far</param>
|
||||
/// <param name="bufferOffset">New offset to start copying from/to</param>
|
||||
/// <param name="bytesToCopy">Number of bytes to copy in this iteration</param>
|
||||
private void GetByteCounts(int bytes, int bytesCopied, out int bufferOffset, out int bytesToCopy)
|
||||
{
|
||||
bufferOffset = (int) (currentOffset - startOffset);
|
||||
bytesToCopy = bytes - bytesCopied;
|
||||
if (bytesToCopy > buffer.Length - bufferOffset)
|
||||
{
|
||||
bytesToCopy = buffer.Length - bufferOffset;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the internal buffer to the specified offset into the file
|
||||
/// </summary>
|
||||
/// <param name="offset">Offset into the file to move to</param>
|
||||
private void MoveTo(long offset)
|
||||
{
|
||||
if (buffer.Length > bufferDataSize || // buffer is not completely filled
|
||||
offset < startOffset || // before current buffer start
|
||||
offset >= (startOffset + buffer.Length)) // beyond current buffer end
|
||||
{
|
||||
// init the offset
|
||||
startOffset = offset;
|
||||
|
||||
// position file pointer
|
||||
fileStream.Seek(startOffset, SeekOrigin.Begin);
|
||||
|
||||
// fill in the buffer
|
||||
bufferDataSize = fileStream.Read(buffer, 0, buffer.Length);
|
||||
}
|
||||
// make sure to record where we are
|
||||
currentOffset = offset;
|
||||
}
|
||||
|
||||
#region IDisposable Implementation
|
||||
|
||||
private bool disposed;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (disposing && fileStream != null)
|
||||
{
|
||||
if(fileStream.CanWrite) { Flush(); }
|
||||
fileStream.Dispose();
|
||||
}
|
||||
|
||||
disposed = true;
|
||||
}
|
||||
|
||||
~FileStreamWrapper()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,22 +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.IO;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for a wrapper around a filesystem reader/writer, mainly for unit testing purposes
|
||||
/// </summary>
|
||||
public interface IFileStreamWrapper : IDisposable
|
||||
{
|
||||
void Init(string fileName, int bufferSize, FileAccess fileAccessMode);
|
||||
int ReadData(byte[] buffer, int bytes);
|
||||
int ReadData(byte[] buffer, int bytes, long fileOffset);
|
||||
int WriteData(byte[] buffer, int bytes);
|
||||
void Flush();
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
using System;
|
||||
using System.Data.SqlTypes;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
{
|
||||
@@ -25,7 +26,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
int WriteDouble(double val);
|
||||
int WriteDecimal(decimal val);
|
||||
int WriteSqlDecimal(SqlDecimal val);
|
||||
int WriteDateTime(DateTime val);
|
||||
int WriteDateTime(DbColumnWrapper column, DateTime val);
|
||||
int WriteDateTimeOffset(DateTimeOffset dtoVal);
|
||||
int WriteTimeSpan(TimeSpan val);
|
||||
int WriteString(string val);
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
/// <returns>A <see cref="ServiceBufferFileStreamReader"/></returns>
|
||||
public IFileStreamReader GetReader(string fileName)
|
||||
{
|
||||
return new ServiceBufferFileStreamReader(new FileStreamWrapper(), fileName);
|
||||
return new ServiceBufferFileStreamReader(new FileStream(fileName, FileMode.Open, FileAccess.Read));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -42,7 +42,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
/// <returns>A <see cref="ServiceBufferFileStreamWriter"/></returns>
|
||||
public IFileStreamWriter GetWriter(string fileName, int maxCharsToStore, int maxXmlCharsToStore)
|
||||
{
|
||||
return new ServiceBufferFileStreamWriter(new FileStreamWrapper(), fileName, maxCharsToStore, maxXmlCharsToStore);
|
||||
return new ServiceBufferFileStreamWriter(new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite), maxCharsToStore, maxXmlCharsToStore);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -51,14 +51,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
/// <param name="fileName">The file to dispose of</param>
|
||||
public void DisposeFile(string fileName)
|
||||
{
|
||||
try
|
||||
{
|
||||
FileStreamWrapper.DeleteFile(fileName);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// If we have problems deleting the file from a temp location, we don't really care
|
||||
}
|
||||
FileUtils.SafeFileDelete(fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,22 +23,24 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
|
||||
private byte[] buffer;
|
||||
|
||||
private readonly IFileStreamWrapper fileStream;
|
||||
private readonly Stream fileStream;
|
||||
|
||||
private Dictionary<Type, Func<long, FileStreamReadResult>> readMethods;
|
||||
private readonly Dictionary<Type, Func<long, FileStreamReadResult>> readMethods;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new ServiceBufferFileStreamReader and initializes its state
|
||||
/// </summary>
|
||||
/// <param name="fileWrapper">The filestream wrapper to read from</param>
|
||||
/// <param name="fileName">The name of the file to read from</param>
|
||||
public ServiceBufferFileStreamReader(IFileStreamWrapper fileWrapper, string fileName)
|
||||
/// <param name="stream">The filestream to read from</param>
|
||||
public ServiceBufferFileStreamReader(Stream stream)
|
||||
{
|
||||
// Open file for reading/writing
|
||||
fileStream = fileWrapper;
|
||||
fileStream.Init(fileName, DefaultBufferSize, FileAccess.Read);
|
||||
if (!stream.CanRead || !stream.CanSeek)
|
||||
{
|
||||
throw new InvalidOperationException("Stream must be readable and seekable");
|
||||
}
|
||||
fileStream = stream;
|
||||
|
||||
// Create internal buffer
|
||||
buffer = new byte[DefaultBufferSize];
|
||||
@@ -258,11 +260,26 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
/// <returns>A DateTime</returns>
|
||||
public FileStreamReadResult ReadDateTime(long offset)
|
||||
{
|
||||
return ReadCellHelper(offset, length =>
|
||||
{
|
||||
long ticks = BitConverter.ToInt64(buffer, 0);
|
||||
return new DateTime(ticks);
|
||||
});
|
||||
int precision = 0;
|
||||
|
||||
return ReadCellHelper(offset,
|
||||
length =>
|
||||
{
|
||||
precision = BitConverter.ToInt32(buffer, 0);
|
||||
long ticks = BitConverter.ToInt64(buffer, 4);
|
||||
return new DateTime(ticks);
|
||||
}, null,
|
||||
time =>
|
||||
{
|
||||
string format = "yyyy-MM-dd HH:mm:ss";
|
||||
if (precision > 0)
|
||||
{
|
||||
// Output the number milliseconds equivalent to the precision
|
||||
// NOTE: string('f', precision) will output ffff for precision=4
|
||||
format += "." + new string('f', precision);
|
||||
}
|
||||
return time.ToString(format);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -372,7 +389,8 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
{
|
||||
// read in length information
|
||||
int lengthValue;
|
||||
int lengthLength = fileStream.ReadData(buffer, 1, offset);
|
||||
fileStream.Seek(offset, SeekOrigin.Begin);
|
||||
int lengthLength = fileStream.Read(buffer, 0, 1);
|
||||
if (buffer[0] != 0xFF)
|
||||
{
|
||||
// one byte is enough
|
||||
@@ -381,7 +399,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
else
|
||||
{
|
||||
// read in next 4 bytes
|
||||
lengthLength += fileStream.ReadData(buffer, 4);
|
||||
lengthLength += fileStream.Read(buffer, 0, 4);
|
||||
|
||||
// reconstruct the length
|
||||
lengthValue = BitConverter.ToInt32(buffer, 0);
|
||||
@@ -433,7 +451,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
else
|
||||
{
|
||||
AssureBufferLength(length.ValueLength);
|
||||
fileStream.ReadData(buffer, length.ValueLength);
|
||||
fileStream.Read(buffer, 0, length.ValueLength);
|
||||
T resultObject = convertFunc(length.ValueLength);
|
||||
result.RawObject = resultObject;
|
||||
result.DisplayValue = toStringFunc == null ? result.RawObject.ToString() : toStringFunc(resultObject);
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
|
||||
#region Member Variables
|
||||
|
||||
private readonly IFileStreamWrapper fileStream;
|
||||
private readonly Stream fileStream;
|
||||
private readonly int maxCharsToStore;
|
||||
private readonly int maxXmlCharsToStore;
|
||||
|
||||
@@ -38,22 +38,24 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
/// <summary>
|
||||
/// Functions to use for writing various types to a file
|
||||
/// </summary>
|
||||
private readonly Dictionary<Type, Func<object, int>> writeMethods;
|
||||
private readonly Dictionary<Type, Func<object, DbColumnWrapper, int>> writeMethods;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new writer
|
||||
/// </summary>
|
||||
/// <param name="fileWrapper">The file wrapper to use as the underlying file stream</param>
|
||||
/// <param name="fileName">Name of the file to write to</param>
|
||||
/// <param name="stream">The file wrapper to use as the underlying file stream</param>
|
||||
/// <param name="maxCharsToStore">Maximum number of characters to store for long text fields</param>
|
||||
/// <param name="maxXmlCharsToStore">Maximum number of characters to store for XML fields</param>
|
||||
public ServiceBufferFileStreamWriter(IFileStreamWrapper fileWrapper, string fileName, int maxCharsToStore, int maxXmlCharsToStore)
|
||||
public ServiceBufferFileStreamWriter(Stream stream, int maxCharsToStore, int maxXmlCharsToStore)
|
||||
{
|
||||
// open file for reading/writing
|
||||
fileStream = fileWrapper;
|
||||
fileStream.Init(fileName, DefaultBufferLength, FileAccess.ReadWrite);
|
||||
if (!stream.CanWrite || !stream.CanSeek)
|
||||
{
|
||||
throw new InvalidOperationException("Stream must be writable and seekable.");
|
||||
}
|
||||
fileStream = stream;
|
||||
|
||||
// create internal buffer
|
||||
byteBuffer = new byte[DefaultBufferLength];
|
||||
@@ -72,37 +74,78 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
this.maxXmlCharsToStore = maxXmlCharsToStore;
|
||||
|
||||
// Define what methods to use to write a type to the file
|
||||
writeMethods = new Dictionary<Type, Func<object, int>>
|
||||
writeMethods = new Dictionary<Type, Func<object, DbColumnWrapper, int>>
|
||||
{
|
||||
{typeof(string), val => WriteString((string) val)},
|
||||
{typeof(short), val => WriteInt16((short) val)},
|
||||
{typeof(int), val => WriteInt32((int) val)},
|
||||
{typeof(long), val => WriteInt64((long) val)},
|
||||
{typeof(byte), val => WriteByte((byte) val)},
|
||||
{typeof(char), val => WriteChar((char) val)},
|
||||
{typeof(bool), val => WriteBoolean((bool) val)},
|
||||
{typeof(double), val => WriteDouble((double) val) },
|
||||
{typeof(float), val => WriteSingle((float) val) },
|
||||
{typeof(decimal), val => WriteDecimal((decimal) val) },
|
||||
{typeof(DateTime), val => WriteDateTime((DateTime) val) },
|
||||
{typeof(DateTimeOffset), val => WriteDateTimeOffset((DateTimeOffset) val) },
|
||||
{typeof(TimeSpan), val => WriteTimeSpan((TimeSpan) val) },
|
||||
{typeof(byte[]), val => WriteBytes((byte[]) val)},
|
||||
{typeof(string), (val, col) => WriteString((string) val)},
|
||||
{typeof(short), (val, col) => WriteInt16((short) val)},
|
||||
{typeof(int), (val, col) => WriteInt32((int) val)},
|
||||
{typeof(long), (val, col) => WriteInt64((long) val)},
|
||||
{typeof(byte), (val, col) => WriteByte((byte) val)},
|
||||
{typeof(char), (val, col) => WriteChar((char) val)},
|
||||
{typeof(bool), (val, col) => WriteBoolean((bool) val)},
|
||||
{typeof(double), (val, col) => WriteDouble((double) val) },
|
||||
{typeof(float), (val, col) => WriteSingle((float) val) },
|
||||
{typeof(decimal), (val, col) => WriteDecimal((decimal) val) },
|
||||
{typeof(DateTime), (val, col) => WriteDateTime(col, (DateTime) val) },
|
||||
{typeof(DateTimeOffset), (val, col) => WriteDateTimeOffset((DateTimeOffset) val) },
|
||||
{typeof(TimeSpan), (val, col) => WriteTimeSpan((TimeSpan) val) },
|
||||
{typeof(byte[]), (val, col) => WriteBytes((byte[]) val)},
|
||||
|
||||
{typeof(SqlString), val => WriteNullable((SqlString) val, obj => WriteString((string) obj))},
|
||||
{typeof(SqlInt16), val => WriteNullable((SqlInt16) val, obj => WriteInt16((short) obj))},
|
||||
{typeof(SqlInt32), val => WriteNullable((SqlInt32) val, obj => WriteInt32((int) obj))},
|
||||
{typeof(SqlInt64), val => WriteNullable((SqlInt64) val, obj => WriteInt64((long) obj)) },
|
||||
{typeof(SqlByte), val => WriteNullable((SqlByte) val, obj => WriteByte((byte) obj)) },
|
||||
{typeof(SqlBoolean), val => WriteNullable((SqlBoolean) val, obj => WriteBoolean((bool) obj)) },
|
||||
{typeof(SqlDouble), val => WriteNullable((SqlDouble) val, obj => WriteDouble((double) obj)) },
|
||||
{typeof(SqlSingle), val => WriteNullable((SqlSingle) val, obj => WriteSingle((float) obj)) },
|
||||
{typeof(SqlDecimal), val => WriteNullable((SqlDecimal) val, obj => WriteSqlDecimal((SqlDecimal) obj)) },
|
||||
{typeof(SqlDateTime), val => WriteNullable((SqlDateTime) val, obj => WriteDateTime((DateTime) obj)) },
|
||||
{typeof(SqlBytes), val => WriteNullable((SqlBytes) val, obj => WriteBytes((byte[]) obj)) },
|
||||
{typeof(SqlBinary), val => WriteNullable((SqlBinary) val, obj => WriteBytes((byte[]) obj)) },
|
||||
{typeof(SqlGuid), val => WriteNullable((SqlGuid) val, obj => WriteGuid((Guid) obj)) },
|
||||
{typeof(SqlMoney), val => WriteNullable((SqlMoney) val, obj => WriteMoney((SqlMoney) obj)) }
|
||||
{
|
||||
typeof(SqlString),
|
||||
(val, col) => WriteNullable((SqlString) val, obj => WriteString((string) obj))
|
||||
},
|
||||
{
|
||||
typeof(SqlInt16),
|
||||
(val, col) => WriteNullable((SqlInt16) val, obj => WriteInt16((short) obj))
|
||||
},
|
||||
{
|
||||
typeof(SqlInt32),
|
||||
(val, col) => WriteNullable((SqlInt32) val, obj => WriteInt32((int) obj))
|
||||
},
|
||||
{
|
||||
typeof(SqlInt64),
|
||||
(val, col) => WriteNullable((SqlInt64) val, obj => WriteInt64((long) obj))
|
||||
},
|
||||
{
|
||||
typeof(SqlByte),
|
||||
(val, col) => WriteNullable((SqlByte) val, obj => WriteByte((byte) obj))
|
||||
},
|
||||
{
|
||||
typeof(SqlBoolean),
|
||||
(val, col) => WriteNullable((SqlBoolean) val, obj => WriteBoolean((bool) obj)) },
|
||||
{
|
||||
typeof(SqlDouble),
|
||||
(val, col) => WriteNullable((SqlDouble) val, obj => WriteDouble((double) obj))
|
||||
},
|
||||
{
|
||||
typeof(SqlSingle),
|
||||
(val, col) => WriteNullable((SqlSingle) val, obj => WriteSingle((float) obj))
|
||||
},
|
||||
{
|
||||
typeof(SqlDecimal),
|
||||
(val, col) => WriteNullable((SqlDecimal) val, obj => WriteSqlDecimal((SqlDecimal) obj))
|
||||
},
|
||||
{
|
||||
typeof(SqlDateTime),
|
||||
(val, col) => WriteNullable((SqlDateTime) val, obj => WriteDateTime(col, (DateTime) obj))
|
||||
},
|
||||
{
|
||||
typeof(SqlBytes),
|
||||
(val, col) => WriteNullable((SqlBytes) val, obj => WriteBytes((byte[]) obj))
|
||||
},
|
||||
{
|
||||
typeof(SqlBinary),
|
||||
(val, col) => WriteNullable((SqlBinary) val, obj => WriteBytes((byte[]) obj))
|
||||
},
|
||||
{
|
||||
typeof(SqlGuid),
|
||||
(val, col) => WriteNullable((SqlGuid) val, obj => WriteGuid((Guid) obj))
|
||||
},
|
||||
{
|
||||
typeof(SqlMoney),
|
||||
(val, col) => WriteNullable((SqlMoney) val, obj => WriteMoney((SqlMoney) obj))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -188,10 +231,10 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
}
|
||||
|
||||
// Use the appropriate writing method for the type
|
||||
Func<object, int> writeMethod;
|
||||
Func<object, DbColumnWrapper, int> writeMethod;
|
||||
if (writeMethods.TryGetValue(tVal, out writeMethod))
|
||||
{
|
||||
rowBytes += writeMethod(values[i]);
|
||||
rowBytes += writeMethod(values[i], ci);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -212,7 +255,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
public int WriteNull()
|
||||
{
|
||||
byteBuffer[0] = 0x00;
|
||||
return fileStream.WriteData(byteBuffer, 1);
|
||||
return WriteHelper(byteBuffer, 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -224,7 +267,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
byteBuffer[0] = 0x02; // length
|
||||
shortBuffer[0] = val;
|
||||
Buffer.BlockCopy(shortBuffer, 0, byteBuffer, 1, 2);
|
||||
return fileStream.WriteData(byteBuffer, 3);
|
||||
return WriteHelper(byteBuffer, 3);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -236,7 +279,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
byteBuffer[0] = 0x04; // length
|
||||
intBuffer[0] = val;
|
||||
Buffer.BlockCopy(intBuffer, 0, byteBuffer, 1, 4);
|
||||
return fileStream.WriteData(byteBuffer, 5);
|
||||
return WriteHelper(byteBuffer, 5);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -248,7 +291,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
byteBuffer[0] = 0x08; // length
|
||||
longBuffer[0] = val;
|
||||
Buffer.BlockCopy(longBuffer, 0, byteBuffer, 1, 8);
|
||||
return fileStream.WriteData(byteBuffer, 9);
|
||||
return WriteHelper(byteBuffer, 9);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -260,7 +303,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
byteBuffer[0] = 0x02; // length
|
||||
charBuffer[0] = val;
|
||||
Buffer.BlockCopy(charBuffer, 0, byteBuffer, 1, 2);
|
||||
return fileStream.WriteData(byteBuffer, 3);
|
||||
return WriteHelper(byteBuffer, 3);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -271,7 +314,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
{
|
||||
byteBuffer[0] = 0x01; // length
|
||||
byteBuffer[1] = (byte) (val ? 0x01 : 0x00);
|
||||
return fileStream.WriteData(byteBuffer, 2);
|
||||
return WriteHelper(byteBuffer, 2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -282,7 +325,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
{
|
||||
byteBuffer[0] = 0x01; // length
|
||||
byteBuffer[1] = val;
|
||||
return fileStream.WriteData(byteBuffer, 2);
|
||||
return WriteHelper(byteBuffer, 2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -294,7 +337,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
byteBuffer[0] = 0x04; // length
|
||||
floatBuffer[0] = val;
|
||||
Buffer.BlockCopy(floatBuffer, 0, byteBuffer, 1, 4);
|
||||
return fileStream.WriteData(byteBuffer, 5);
|
||||
return WriteHelper(byteBuffer, 5);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -306,7 +349,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
byteBuffer[0] = 0x08; // length
|
||||
doubleBuffer[0] = val;
|
||||
Buffer.BlockCopy(doubleBuffer, 0, byteBuffer, 1, 8);
|
||||
return fileStream.WriteData(byteBuffer, 9);
|
||||
return WriteHelper(byteBuffer, 9);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -330,7 +373,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
|
||||
// data value
|
||||
Buffer.BlockCopy(arrInt32, 0, byteBuffer, 3, iLen - 3);
|
||||
iTotalLen += fileStream.WriteData(byteBuffer, iLen);
|
||||
iTotalLen += WriteHelper(byteBuffer, iLen);
|
||||
return iTotalLen; // len+data
|
||||
}
|
||||
|
||||
@@ -346,18 +389,31 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
int iTotalLen = WriteLength(iLen); // length
|
||||
|
||||
Buffer.BlockCopy(arrInt32, 0, byteBuffer, 0, iLen);
|
||||
iTotalLen += fileStream.WriteData(byteBuffer, iLen);
|
||||
iTotalLen += WriteHelper(byteBuffer, iLen);
|
||||
|
||||
return iTotalLen; // len+data
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a DateTime to the file
|
||||
/// Writes a DateTime to the file as precision and ticks
|
||||
/// </summary>
|
||||
/// <returns>Number of bytes used to store the DateTime</returns>
|
||||
public int WriteDateTime(DateTime dtVal)
|
||||
public int WriteDateTime(DbColumnWrapper col, DateTime dtVal)
|
||||
{
|
||||
return WriteInt64(dtVal.Ticks);
|
||||
// Length
|
||||
var length = WriteLength(12);
|
||||
|
||||
// Precision
|
||||
intBuffer[0] = col.NumericScale ?? 3;
|
||||
Buffer.BlockCopy(intBuffer, 0, byteBuffer, 0, 4);
|
||||
|
||||
// Ticks
|
||||
longBuffer[0] = dtVal.Ticks;
|
||||
Buffer.BlockCopy(longBuffer, 0, byteBuffer, 4, 8);
|
||||
|
||||
length += WriteHelper(byteBuffer, 12);
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -374,7 +430,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
longBufferOffset[0] = dtoVal.Ticks;
|
||||
longBufferOffset[1] = dtoVal.Offset.Ticks;
|
||||
Buffer.BlockCopy(longBufferOffset, 0, byteBuffer, 1, 16);
|
||||
return fileStream.WriteData(byteBuffer, 17);
|
||||
return WriteHelper(byteBuffer, 17);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -406,7 +462,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
byteBuffer[3] = 0x00;
|
||||
byteBuffer[4] = 0x00;
|
||||
|
||||
iTotalLen = fileStream.WriteData(byteBuffer, 5);
|
||||
iTotalLen = WriteHelper(byteBuffer, 5);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -415,7 +471,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
|
||||
// convert char array into byte array and write it out
|
||||
iTotalLen = WriteLength(bytes.Length);
|
||||
iTotalLen += fileStream.WriteData(bytes, bytes.Length);
|
||||
iTotalLen += WriteHelper(bytes, bytes.Length);
|
||||
}
|
||||
return iTotalLen; // len+data
|
||||
}
|
||||
@@ -438,12 +494,12 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
byteBuffer[3] = 0x00;
|
||||
byteBuffer[4] = 0x00;
|
||||
|
||||
iTotalLen = fileStream.WriteData(byteBuffer, 5);
|
||||
iTotalLen = WriteHelper(byteBuffer, 5);
|
||||
}
|
||||
else
|
||||
{
|
||||
iTotalLen = WriteLength(bytesVal.Length);
|
||||
iTotalLen += fileStream.WriteData(bytesVal, bytesVal.Length);
|
||||
iTotalLen += WriteHelper(bytesVal, bytesVal.Length);
|
||||
}
|
||||
return iTotalLen; // len+data
|
||||
}
|
||||
@@ -507,7 +563,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
int iTmp = iLen & 0x000000FF;
|
||||
|
||||
byteBuffer[0] = Convert.ToByte(iTmp);
|
||||
return fileStream.WriteData(byteBuffer, 1);
|
||||
return WriteHelper(byteBuffer, 1);
|
||||
}
|
||||
// The length won't fit in 1 byte, so we need to use 1 byte to signify that the length
|
||||
// is a full 4 bytes.
|
||||
@@ -516,7 +572,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
// convert int32 into array of bytes
|
||||
intBuffer[0] = iLen;
|
||||
Buffer.BlockCopy(intBuffer, 0, byteBuffer, 1, 4);
|
||||
return fileStream.WriteData(byteBuffer, 5);
|
||||
return WriteHelper(byteBuffer, 5);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -532,6 +588,12 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
return val.IsNull ? WriteNull() : valueWriteFunc(val);
|
||||
}
|
||||
|
||||
private int WriteHelper(byte[] buffer, int length)
|
||||
{
|
||||
fileStream.Write(buffer, 0, length);
|
||||
return length;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable Implementation
|
||||
|
||||
@@ -304,7 +304,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||
/// This code is take almost verbatim from Microsoft.SqlServer.Management.UI.Grid, SSMS
|
||||
/// DataStorage, StorageDataReader class.
|
||||
/// </remarks>
|
||||
private class StringWriterWithMaxCapacity : StringWriter
|
||||
internal class StringWriterWithMaxCapacity : StringWriter
|
||||
{
|
||||
private bool stopWriting;
|
||||
|
||||
|
||||
@@ -51,11 +51,6 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
/// </summary>
|
||||
private bool hasExecuteBeenCalled;
|
||||
|
||||
/// <summary>
|
||||
/// The factory to use for outputting the results of this query
|
||||
/// </summary>
|
||||
private readonly IFileStreamFactory outputFileFactory;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
@@ -77,7 +72,6 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
QueryText = queryText;
|
||||
editorConnection = connection;
|
||||
cancellationSource = new CancellationTokenSource();
|
||||
outputFileFactory = outputFactory;
|
||||
|
||||
// Process the query into batches
|
||||
ParseResult parseResult = Parser.Parse(queryText, new ParseOptions
|
||||
@@ -85,27 +79,35 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
BatchSeparator = settings.BatchSeparator
|
||||
});
|
||||
// NOTE: We only want to process batches that have statements (ie, ignore comments and empty lines)
|
||||
Batches = parseResult.Script.Batches.Where(b => b.Statements.Count > 0)
|
||||
.Select(b => new Batch(b.Sql,
|
||||
b.StartLocation.LineNumber - 1,
|
||||
b.StartLocation.ColumnNumber - 1,
|
||||
b.EndLocation.LineNumber - 1,
|
||||
b.EndLocation.ColumnNumber - 1,
|
||||
outputFileFactory)).ToArray();
|
||||
var batchSelection = parseResult.Script.Batches
|
||||
.Where(batch => batch.Statements.Count > 0)
|
||||
.Select((batch, index) =>
|
||||
new Batch(batch.Sql,
|
||||
new SelectionData(
|
||||
batch.StartLocation.LineNumber - 1,
|
||||
batch.StartLocation.ColumnNumber - 1,
|
||||
batch.EndLocation.LineNumber - 1,
|
||||
batch.EndLocation.ColumnNumber - 1),
|
||||
index, outputFactory));
|
||||
Batches = batchSelection.ToArray();
|
||||
}
|
||||
|
||||
#region Properties
|
||||
#region Events
|
||||
|
||||
/// <summary>
|
||||
/// Delegate type for callback when a query completes or fails
|
||||
/// Event to be called when a batch is completed.
|
||||
/// </summary>
|
||||
/// <param name="q">The query that completed</param>
|
||||
public delegate Task QueryAsyncEventHandler(Query q);
|
||||
public event Batch.BatchAsyncEventHandler BatchCompleted;
|
||||
|
||||
/// <summary>
|
||||
/// Event to be called when a batch starts execution.
|
||||
/// </summary>
|
||||
public event Batch.BatchAsyncEventHandler BatchStarted;
|
||||
|
||||
/// <summary>
|
||||
/// Delegate type for callback when a query connection fails
|
||||
/// </summary>
|
||||
/// <param name="q">The query that completed</param>
|
||||
/// <param name="message">Error message for the failing query</param>
|
||||
public delegate Task QueryAsyncErrorEventHandler(string message);
|
||||
|
||||
/// <summary>
|
||||
@@ -123,6 +125,21 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
/// </summary>
|
||||
public event QueryAsyncErrorEventHandler QueryConnectionException;
|
||||
|
||||
/// <summary>
|
||||
/// Event to be called when a resultset has completed.
|
||||
/// </summary>
|
||||
public event ResultSet.ResultSetAsyncEventHandler ResultSetCompleted;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Delegate type for callback when a query completes or fails
|
||||
/// </summary>
|
||||
/// <param name="q">The query that completed</param>
|
||||
public delegate Task QueryAsyncEventHandler(Query q);
|
||||
|
||||
/// <summary>
|
||||
/// The batches underneath this query
|
||||
/// </summary>
|
||||
@@ -139,21 +156,14 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
{
|
||||
throw new InvalidOperationException("Query has not been executed.");
|
||||
}
|
||||
|
||||
return Batches.Select((batch, index) => new BatchSummary
|
||||
{
|
||||
Id = index,
|
||||
ExecutionStart = batch.ExecutionStartTimeStamp,
|
||||
ExecutionEnd = batch.ExecutionEndTimeStamp,
|
||||
ExecutionElapsed = batch.ExecutionElapsedTime,
|
||||
HasError = batch.HasError,
|
||||
Messages = batch.ResultMessages.ToArray(),
|
||||
ResultSetSummaries = batch.ResultSummaries,
|
||||
Selection = batch.Selection
|
||||
}).ToArray();
|
||||
return Batches.Select(b => b.Summary).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Storage for the async task for execution. Set as internal in order to await completion
|
||||
/// in unit tests.
|
||||
/// </summary>
|
||||
internal Task ExecutionTask { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -214,12 +224,6 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
/// <returns>A subset of results</returns>
|
||||
public Task<ResultSetSubset> GetSubset(int batchIndex, int resultSetIndex, int startRow, int rowCount)
|
||||
{
|
||||
// Sanity check that the results are available
|
||||
if (!HasExecuted)
|
||||
{
|
||||
throw new InvalidOperationException(SR.QueryServiceSubsetNotCompleted);
|
||||
}
|
||||
|
||||
// Sanity check to make sure that the batch is within bounds
|
||||
if (batchIndex < 0 || batchIndex >= Batches.Length)
|
||||
{
|
||||
@@ -256,11 +260,11 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
{
|
||||
await conn.OpenAsync();
|
||||
}
|
||||
catch(Exception exception)
|
||||
catch (Exception exception)
|
||||
{
|
||||
this.HasExecuted = true;
|
||||
this.HasExecuted = true;
|
||||
if (QueryConnectionException != null)
|
||||
{
|
||||
{
|
||||
await QueryConnectionException(exception.Message);
|
||||
}
|
||||
return;
|
||||
@@ -278,6 +282,9 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
// We need these to execute synchronously, otherwise the user will be very unhappy
|
||||
foreach (Batch b in Batches)
|
||||
{
|
||||
b.BatchStart += BatchStarted;
|
||||
b.BatchCompletion += BatchCompleted;
|
||||
b.ResultSetCompletion += ResultSetCompleted;
|
||||
await b.Execute(conn, cancellationSource.Token);
|
||||
}
|
||||
|
||||
|
||||
@@ -424,6 +424,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
OwnerUri = executeParams.OwnerUri,
|
||||
BatchSummaries = q.BatchSummaries
|
||||
};
|
||||
|
||||
await requestContext.SendEvent(QueryExecuteCompleteEvent.Type, eventParams);
|
||||
};
|
||||
|
||||
@@ -442,13 +443,54 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
query.QueryFailed += callback;
|
||||
query.QueryConnectionException += errorCallback;
|
||||
|
||||
// Setup the batch callbacks
|
||||
Batch.BatchAsyncEventHandler batchStartCallback = async b =>
|
||||
{
|
||||
QueryExecuteBatchNotificationParams eventParams = new QueryExecuteBatchNotificationParams
|
||||
{
|
||||
BatchSummary = b.Summary,
|
||||
OwnerUri = executeParams.OwnerUri
|
||||
};
|
||||
await requestContext.SendEvent(QueryExecuteBatchStartEvent.Type, eventParams);
|
||||
};
|
||||
query.BatchStarted += batchStartCallback;
|
||||
|
||||
Batch.BatchAsyncEventHandler batchCompleteCallback = async b =>
|
||||
{
|
||||
QueryExecuteBatchNotificationParams eventParams = new QueryExecuteBatchNotificationParams
|
||||
{
|
||||
BatchSummary = b.Summary,
|
||||
OwnerUri = executeParams.OwnerUri
|
||||
};
|
||||
await requestContext.SendEvent(QueryExecuteBatchCompleteEvent.Type, eventParams);
|
||||
};
|
||||
query.BatchCompleted += batchCompleteCallback;
|
||||
|
||||
// Setup the ResultSet completion callback
|
||||
ResultSet.ResultSetAsyncEventHandler resultCallback = async r =>
|
||||
{
|
||||
QueryExecuteResultSetCompleteParams eventParams = new QueryExecuteResultSetCompleteParams
|
||||
{
|
||||
ResultSetSummary = r.Summary,
|
||||
OwnerUri = executeParams.OwnerUri
|
||||
};
|
||||
await requestContext.SendEvent(QueryExecuteResultSetCompleteEvent.Type, eventParams);
|
||||
};
|
||||
query.ResultSetCompleted += resultCallback;
|
||||
|
||||
// Launch this as an asynchronous task
|
||||
query.Execute();
|
||||
|
||||
// Send back a result showing we were successful
|
||||
string messages = null;
|
||||
if (query.Batches.Length == 0)
|
||||
{
|
||||
// If there were no batches to execute, send back an informational message that the commands were completed successfully
|
||||
messages = SR.QueryServiceCompletedSuccessfully;
|
||||
}
|
||||
await requestContext.SendResult(new QueryExecuteResult
|
||||
{
|
||||
Messages = null
|
||||
Messages = messages
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.Common;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
@@ -17,6 +16,10 @@ using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
{
|
||||
/// <summary>
|
||||
/// Class that represents a resultset the was generated from a query. Contains logic for
|
||||
/// storing and retrieving results. Is contained by a Batch class.
|
||||
/// </summary>
|
||||
public class ResultSet : IDisposable
|
||||
{
|
||||
#region Constants
|
||||
@@ -35,20 +38,31 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
|
||||
#region Member Variables
|
||||
|
||||
/// <summary>
|
||||
/// The reader to use for this resultset
|
||||
/// </summary>
|
||||
private readonly StorageDataReader dataReader;
|
||||
|
||||
/// <summary>
|
||||
/// For IDisposable pattern, whether or not object has been disposed
|
||||
/// </summary>
|
||||
private bool disposed;
|
||||
|
||||
/// <summary>
|
||||
/// A list of offsets into the buffer file that correspond to where rows start
|
||||
/// </summary>
|
||||
private readonly LongList<long> fileOffsets;
|
||||
|
||||
/// <summary>
|
||||
/// The factory to use to get reading/writing handlers
|
||||
/// </summary>
|
||||
private readonly IFileStreamFactory fileStreamFactory;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the result set has been read in from the database
|
||||
/// Whether or not the result set has been read in from the database,
|
||||
/// set as internal in order to fake value in unit tests
|
||||
/// </summary>
|
||||
private bool hasBeenRead;
|
||||
internal bool hasBeenRead;
|
||||
|
||||
/// <summary>
|
||||
/// Whether resultSet is a 'for xml' or 'for json' result
|
||||
@@ -60,15 +74,10 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
/// </summary>
|
||||
private readonly string outputFileName;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the resultSet is in the process of being disposed
|
||||
/// </summary>
|
||||
private bool isBeingDisposed;
|
||||
|
||||
/// <summary>
|
||||
/// All save tasks currently saving this ResultSet
|
||||
/// </summary>
|
||||
private ConcurrentDictionary<string, Task> saveTasks;
|
||||
private readonly ConcurrentDictionary<string, Task> saveTasks;
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -76,17 +85,21 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
/// Creates a new result set and initializes its state
|
||||
/// </summary>
|
||||
/// <param name="reader">The reader from executing a query</param>
|
||||
/// <param name="ordinal">The ID of the resultset, the ordinal of the result within the batch</param>
|
||||
/// <param name="batchOrdinal">The ID of the batch, the ordinal of the batch within the query</param>
|
||||
/// <param name="factory">Factory for creating a reader/writer</param>
|
||||
public ResultSet(DbDataReader reader, IFileStreamFactory factory)
|
||||
public ResultSet(DbDataReader reader, int ordinal, int batchOrdinal, IFileStreamFactory factory)
|
||||
{
|
||||
// Sanity check to make sure we got a reader
|
||||
Validate.IsNotNull(nameof(reader), SR.QueryServiceResultSetReaderNull);
|
||||
|
||||
DataReader = new StorageDataReader(reader);
|
||||
dataReader = new StorageDataReader(reader);
|
||||
Id = ordinal;
|
||||
BatchId = batchOrdinal;
|
||||
|
||||
// Initialize the storage
|
||||
outputFileName = factory.CreateFile();
|
||||
FileOffsets = new LongList<long>();
|
||||
fileOffsets = new LongList<long>();
|
||||
|
||||
// Store the factory
|
||||
fileStreamFactory = factory;
|
||||
@@ -96,17 +109,22 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronous handler for when a resultset has completed
|
||||
/// </summary>
|
||||
/// <param name="resultSet">The result set that completed</param>
|
||||
public delegate Task ResultSetAsyncEventHandler(ResultSet resultSet);
|
||||
|
||||
/// <summary>
|
||||
/// Event that will be called when the result set has completed execution
|
||||
/// </summary>
|
||||
public event ResultSetAsyncEventHandler ResultCompletion;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the resultSet is in the process of being disposed
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal bool IsBeingDisposed
|
||||
{
|
||||
get
|
||||
{
|
||||
return isBeingDisposed;
|
||||
}
|
||||
}
|
||||
internal bool IsBeingDisposed { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The columns for this result set
|
||||
@@ -114,14 +132,14 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
public DbColumnWrapper[] Columns { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The reader to use for this resultset
|
||||
/// ID of the result set, relative to the batch
|
||||
/// </summary>
|
||||
private StorageDataReader DataReader { get; set; }
|
||||
public int Id { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of offsets into the buffer file that correspond to where rows start
|
||||
/// ID of the batch set, relative to the query
|
||||
/// </summary>
|
||||
private LongList<long> FileOffsets { get; set; }
|
||||
public int BatchId { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of characters to store for a field
|
||||
@@ -138,6 +156,23 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
/// </summary>
|
||||
public long RowCount { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Generates a summary of this result set
|
||||
/// </summary>
|
||||
public ResultSetSummary Summary
|
||||
{
|
||||
get
|
||||
{
|
||||
return new ResultSetSummary
|
||||
{
|
||||
ColumnInfo = Columns,
|
||||
Id = Id,
|
||||
BatchId = BatchId,
|
||||
RowCount = RowCount
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
@@ -178,18 +213,19 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
if (isSingleColumnXmlJsonResultSet)
|
||||
{
|
||||
// Iterate over all the rows and process them into a list of string builders
|
||||
IEnumerable<string> rowValues = FileOffsets.Select(rowOffset => fileStreamReader.ReadRow(rowOffset, Columns)[0].DisplayValue);
|
||||
// ReSharper disable once AccessToDisposedClosure The lambda is used immediately in string.Join call
|
||||
IEnumerable<string> rowValues = fileOffsets.Select(rowOffset => fileStreamReader.ReadRow(rowOffset, Columns)[0].DisplayValue);
|
||||
rows = new[] { new[] { string.Join(string.Empty, rowValues) } };
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
// Figure out which rows we need to read back
|
||||
IEnumerable<long> rowOffsets = FileOffsets.Skip(startRow).Take(rowCount);
|
||||
IEnumerable<long> rowOffsets = fileOffsets.Skip(startRow).Take(rowCount);
|
||||
|
||||
// Iterate over the rows we need and process them into output
|
||||
rows = rowOffsets.Select(rowOffset =>
|
||||
fileStreamReader.ReadRow(rowOffset, Columns).Select(cell => cell.DisplayValue).ToArray())
|
||||
// ReSharper disable once AccessToDisposedClosure The lambda is used immediately in .ToArray call
|
||||
rows = rowOffsets.Select(rowOffset => fileStreamReader.ReadRow(rowOffset, Columns)
|
||||
.Select(cell => cell.DisplayValue).ToArray())
|
||||
.ToArray();
|
||||
|
||||
}
|
||||
@@ -209,29 +245,41 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
/// <param name="cancellationToken">Cancellation token for cancelling the query</param>
|
||||
public async Task ReadResultToEnd(CancellationToken cancellationToken)
|
||||
{
|
||||
// Mark that result has been read
|
||||
hasBeenRead = true;
|
||||
|
||||
// Open a writer for the file
|
||||
using (IFileStreamWriter fileWriter = fileStreamFactory.GetWriter(outputFileName, MaxCharsToStore, MaxXmlCharsToStore))
|
||||
try
|
||||
{
|
||||
// If we can initialize the columns using the column schema, use that
|
||||
if (!DataReader.DbDataReader.CanGetColumnSchema())
|
||||
{
|
||||
throw new InvalidOperationException(SR.QueryServiceResultSetNoColumnSchema);
|
||||
}
|
||||
Columns = DataReader.Columns;
|
||||
long currentFileOffset = 0;
|
||||
// Mark that result has been read
|
||||
hasBeenRead = true;
|
||||
|
||||
while (await DataReader.ReadAsync(cancellationToken))
|
||||
// Open a writer for the file
|
||||
var fileWriter = fileStreamFactory.GetWriter(outputFileName, MaxCharsToStore, MaxCharsToStore);
|
||||
using (fileWriter)
|
||||
{
|
||||
RowCount++;
|
||||
FileOffsets.Add(currentFileOffset);
|
||||
currentFileOffset += fileWriter.WriteRow(DataReader);
|
||||
// If we can initialize the columns using the column schema, use that
|
||||
if (!dataReader.DbDataReader.CanGetColumnSchema())
|
||||
{
|
||||
throw new InvalidOperationException(SR.QueryServiceResultSetNoColumnSchema);
|
||||
}
|
||||
Columns = dataReader.Columns;
|
||||
long currentFileOffset = 0;
|
||||
|
||||
while (await dataReader.ReadAsync(cancellationToken))
|
||||
{
|
||||
RowCount++;
|
||||
fileOffsets.Add(currentFileOffset);
|
||||
currentFileOffset += fileWriter.WriteRow(dataReader);
|
||||
}
|
||||
}
|
||||
// Check if resultset is 'for xml/json'. If it is, set isJson/isXml value in column metadata
|
||||
SingleColumnXmlJsonResultSet();
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Fire off a result set completion event if we have one
|
||||
if (ResultCompletion != null)
|
||||
{
|
||||
await ResultCompletion(this);
|
||||
}
|
||||
}
|
||||
// Check if resultset is 'for xml/json'. If it is, set isJson/isXml value in column metadata
|
||||
SingleColumnXmlJsonResultSet();
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -251,7 +299,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
return;
|
||||
}
|
||||
|
||||
isBeingDisposed = true;
|
||||
IsBeingDisposed = true;
|
||||
// Check if saveTasks are running for this ResultSet
|
||||
if (!saveTasks.IsEmpty)
|
||||
{
|
||||
@@ -263,7 +311,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
fileStreamFactory.DisposeFile(outputFileName);
|
||||
}
|
||||
disposed = true;
|
||||
isBeingDisposed = false;
|
||||
IsBeingDisposed = false;
|
||||
});
|
||||
}
|
||||
else
|
||||
@@ -274,7 +322,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
fileStreamFactory.DisposeFile(outputFileName);
|
||||
}
|
||||
disposed = true;
|
||||
isBeingDisposed = false;
|
||||
IsBeingDisposed = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,10 +336,11 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
/// If the result set represented by this class corresponds to a single JSON
|
||||
/// column that contains results of "for json" query, set isJson = true
|
||||
/// </summary>
|
||||
private void SingleColumnXmlJsonResultSet() {
|
||||
private void SingleColumnXmlJsonResultSet()
|
||||
{
|
||||
|
||||
if (Columns?.Length == 1 && RowCount != 0)
|
||||
{
|
||||
{
|
||||
if (Columns[0].ColumnName.Equals(NameOfForXMLColumn, StringComparison.Ordinal))
|
||||
{
|
||||
Columns[0].IsXml = true;
|
||||
@@ -303,7 +352,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
Columns[0].IsJson = true;
|
||||
isSingleColumnXmlJsonResultSet = true;
|
||||
RowCount = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
/// </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)
|
||||
internal static string EncodeCsvField(string field)
|
||||
{
|
||||
StringBuilder sbField = new StringBuilder(field);
|
||||
|
||||
@@ -102,9 +102,8 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
|
||||
//Replace all quotes in the original field with double quotes
|
||||
sbField.Replace("\"", "\"\"");
|
||||
|
||||
String ret = sbField.ToString();
|
||||
|
||||
string ret = sbField.ToString();
|
||||
|
||||
if (embedInQuotes)
|
||||
{
|
||||
ret = "\"" + ret + "\"";
|
||||
@@ -121,7 +120,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
internal static bool IsSaveSelection(SaveResultsRequestParams saveParams)
|
||||
{
|
||||
return (saveParams.ColumnStartIndex != null && saveParams.ColumnEndIndex != null
|
||||
&& saveParams.RowEndIndex != null && saveParams.RowEndIndex != null);
|
||||
&& saveParams.RowStartIndex != null && saveParams.RowEndIndex != null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -68,7 +68,7 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlContext
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a flag determining if suggestons are enabled
|
||||
/// Gets a flag determining if suggestions are enabled
|
||||
/// </summary>
|
||||
public bool IsSuggestionsEnabled
|
||||
{
|
||||
@@ -90,6 +90,17 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlContext
|
||||
&& this.SqlTools.IntelliSense.EnableQuickInfo.Value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a flag determining if IntelliSense is enabled
|
||||
/// </summary>
|
||||
public bool IsIntelliSenseEnabled
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.SqlTools.IntelliSense.EnableIntellisense;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -109,5 +109,19 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility
|
||||
|| ch == '('
|
||||
|| ch == ')';
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove square bracket syntax from a token string
|
||||
/// </summary>
|
||||
/// <param name="tokenText"></param>
|
||||
/// <returns> string with outer brackets removed</returns>
|
||||
public static string RemoveSquareBracketSyntax(string tokenText)
|
||||
{
|
||||
if(tokenText.StartsWith("[") && tokenText.EndsWith("]"))
|
||||
{
|
||||
return tokenText.Substring(1, tokenText.Length - 2);
|
||||
}
|
||||
return tokenText;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,15 +94,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the array of filepaths dot sourced in this ScriptFile
|
||||
/// </summary>
|
||||
public string[] ReferencedFiles
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
@@ -299,9 +290,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts
|
||||
this.FileLines.Insert(currentLineNumber - 1, finalLine);
|
||||
currentLineNumber++;
|
||||
}
|
||||
|
||||
// Parse the script again to be up-to-date
|
||||
this.ParseFileContents();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -447,97 +435,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts
|
||||
.Split('\n')
|
||||
.Select(line => line.TrimEnd('\r'))
|
||||
.ToList();
|
||||
|
||||
// Parse the contents to get syntax tree and errors
|
||||
this.ParseFileContents();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
/// <summary>
|
||||
/// Parses the current file contents to get the AST, tokens,
|
||||
/// and parse errors.
|
||||
/// </summary>
|
||||
private void ParseFileContents()
|
||||
{
|
||||
#if false
|
||||
ParseError[] parseErrors = null;
|
||||
|
||||
// First, get the updated file range
|
||||
int lineCount = this.FileLines.Count;
|
||||
if (lineCount > 0)
|
||||
{
|
||||
this.FileRange =
|
||||
new BufferRange(
|
||||
new BufferPosition(1, 1),
|
||||
new BufferPosition(
|
||||
lineCount + 1,
|
||||
this.FileLines[lineCount - 1].Length + 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
this.FileRange = BufferRange.None;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
#if SqlToolsv5r2
|
||||
// This overload appeared with Windows 10 Update 1
|
||||
if (this.SqlToolsVersion.Major >= 5 &&
|
||||
this.SqlToolsVersion.Build >= 10586)
|
||||
{
|
||||
// Include the file path so that module relative
|
||||
// paths are evaluated correctly
|
||||
this.ScriptAst =
|
||||
Parser.ParseInput(
|
||||
this.Contents,
|
||||
this.FilePath,
|
||||
out this.scriptTokens,
|
||||
out parseErrors);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.ScriptAst =
|
||||
Parser.ParseInput(
|
||||
this.Contents,
|
||||
out this.scriptTokens,
|
||||
out parseErrors);
|
||||
}
|
||||
#else
|
||||
this.ScriptAst =
|
||||
Parser.ParseInput(
|
||||
this.Contents,
|
||||
out this.scriptTokens,
|
||||
out parseErrors);
|
||||
#endif
|
||||
}
|
||||
catch (RuntimeException ex)
|
||||
{
|
||||
var parseError =
|
||||
new ParseError(
|
||||
null,
|
||||
ex.ErrorRecord.FullyQualifiedErrorId,
|
||||
ex.Message);
|
||||
|
||||
parseErrors = new[] { parseError };
|
||||
this.scriptTokens = new Token[0];
|
||||
this.ScriptAst = null;
|
||||
}
|
||||
|
||||
// Translate parse errors into syntax markers
|
||||
this.SyntaxMarkers =
|
||||
parseErrors
|
||||
.Select(ScriptFileMarker.FromParseError)
|
||||
.ToArray();
|
||||
|
||||
//Get all dot sourced referenced files and store them
|
||||
this.ReferencedFiles =
|
||||
AstOperations.FindDotSourcedIncludes(this.ScriptAst);
|
||||
#endif
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,30 +202,39 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace
|
||||
DidChangeTextDocumentParams textChangeParams,
|
||||
EventContext eventContext)
|
||||
{
|
||||
StringBuilder msg = new StringBuilder();
|
||||
msg.Append("HandleDidChangeTextDocumentNotification");
|
||||
List<ScriptFile> changedFiles = new List<ScriptFile>();
|
||||
|
||||
// A text change notification can batch multiple change requests
|
||||
foreach (var textChange in textChangeParams.ContentChanges)
|
||||
try
|
||||
{
|
||||
string fileUri = textChangeParams.TextDocument.Uri ?? textChangeParams.TextDocument.Uri;
|
||||
msg.AppendLine(string.Format(" File: {0}", fileUri));
|
||||
StringBuilder msg = new StringBuilder();
|
||||
msg.Append("HandleDidChangeTextDocumentNotification");
|
||||
List<ScriptFile> changedFiles = new List<ScriptFile>();
|
||||
|
||||
ScriptFile changedFile = Workspace.GetFile(fileUri);
|
||||
// A text change notification can batch multiple change requests
|
||||
foreach (var textChange in textChangeParams.ContentChanges)
|
||||
{
|
||||
string fileUri = textChangeParams.TextDocument.Uri ?? textChangeParams.TextDocument.Uri;
|
||||
msg.AppendLine(string.Format(" File: {0}", fileUri));
|
||||
|
||||
changedFile.ApplyChange(
|
||||
GetFileChangeDetails(
|
||||
textChange.Range.Value,
|
||||
textChange.Text));
|
||||
ScriptFile changedFile = Workspace.GetFile(fileUri);
|
||||
|
||||
changedFiles.Add(changedFile);
|
||||
changedFile.ApplyChange(
|
||||
GetFileChangeDetails(
|
||||
textChange.Range.Value,
|
||||
textChange.Text));
|
||||
|
||||
changedFiles.Add(changedFile);
|
||||
}
|
||||
|
||||
Logger.Write(LogLevel.Verbose, msg.ToString());
|
||||
|
||||
var handlers = TextDocChangeCallbacks.Select(t => t(changedFiles.ToArray(), eventContext));
|
||||
return Task.WhenAll(handlers);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Swallow exceptions here to prevent us from crashing
|
||||
// TODO: this probably means the ScriptFile model is in a bad state or out of sync with the actual file; we should recover here
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
Logger.Write(LogLevel.Verbose, msg.ToString());
|
||||
|
||||
var handlers = TextDocChangeCallbacks.Select(t => t(changedFiles.ToArray(), eventContext));
|
||||
return Task.WhenAll(handlers);
|
||||
}
|
||||
|
||||
internal async Task HandleDidOpenTextDocumentNotification(
|
||||
|
||||
@@ -5,6 +5,16 @@
|
||||
"debugType": "portable",
|
||||
"emitEntryPoint": true
|
||||
},
|
||||
"configurations": {
|
||||
"Integration": {
|
||||
"buildOptions": {
|
||||
"define": [
|
||||
"WINDOWS_ONLY_BUILD"
|
||||
],
|
||||
"emitEntryPoint": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"Newtonsoft.Json": "9.0.1",
|
||||
"System.Data.Common": "4.1.0",
|
||||
|
||||
@@ -173,11 +173,11 @@ namespace Microsoft.SqlTools.ServiceLayer
|
||||
}
|
||||
}
|
||||
|
||||
public static string QueryServiceSubsetNotCompleted
|
||||
public static string QueryServiceSubsetBatchNotCompleted
|
||||
{
|
||||
get
|
||||
{
|
||||
return Keys.GetString(Keys.QueryServiceSubsetNotCompleted);
|
||||
return Keys.GetString(Keys.QueryServiceSubsetBatchNotCompleted);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -468,7 +468,7 @@ namespace Microsoft.SqlTools.ServiceLayer
|
||||
public const string QueryServiceQueryCancelled = "QueryServiceQueryCancelled";
|
||||
|
||||
|
||||
public const string QueryServiceSubsetNotCompleted = "QueryServiceSubsetNotCompleted";
|
||||
public const string QueryServiceSubsetBatchNotCompleted = "QueryServiceSubsetBatchNotCompleted";
|
||||
|
||||
|
||||
public const string QueryServiceSubsetBatchOutOfRange = "QueryServiceSubsetBatchOutOfRange";
|
||||
|
||||
@@ -209,8 +209,8 @@
|
||||
<value>Query was canceled by user</value>
|
||||
<comment></comment>
|
||||
</data>
|
||||
<data name="QueryServiceSubsetNotCompleted" xml:space="preserve">
|
||||
<value>The query has not completed, yet</value>
|
||||
<data name="QueryServiceSubsetBatchNotCompleted" xml:space="preserve">
|
||||
<value>The batch has not completed, yet</value>
|
||||
<comment></comment>
|
||||
</data>
|
||||
<data name="QueryServiceSubsetBatchOutOfRange" xml:space="preserve">
|
||||
|
||||
@@ -83,7 +83,7 @@ QueryServiceQueryCancelled = Query was canceled by user
|
||||
|
||||
### Subset Request
|
||||
|
||||
QueryServiceSubsetNotCompleted = The query has not completed, yet
|
||||
QueryServiceSubsetBatchNotCompleted = The batch has not completed, yet
|
||||
|
||||
QueryServiceSubsetBatchOutOfRange = Batch index cannot be less than 0 or greater than the number of batches
|
||||
|
||||
|
||||
@@ -13,10 +13,26 @@ REM we should remove this step on OpenCover supports portable PDB
|
||||
cscript /nologo ReplaceText.vbs %WORKINGDIR%..\..\src\Microsoft.SqlTools.ServiceLayer\project.json portable full
|
||||
|
||||
REM rebuild the SqlToolsService project
|
||||
dotnet build %WORKINGDIR%..\..\src\Microsoft.SqlTools.ServiceLayer\project.json
|
||||
dotnet build %WORKINGDIR%..\..\src\Microsoft.SqlTools.ServiceLayer\project.json %DOTNETCONFIG%
|
||||
|
||||
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
|
||||
dotnet build %WORKINGDIR%..\..\test\Microsoft.SqlTools.ServiceLayer.Test\project.json %DOTNETCONFIG%
|
||||
dotnet build %WORKINGDIR%..\..\test\Microsoft.SqlTools.ServiceLayer.TestDriver\project.json %DOTNETCONFIG%
|
||||
|
||||
SET TEST_SERVER=localhost
|
||||
SET SQLTOOLSSERVICE_EXE=%WORKINGDIR%..\..\src\Microsoft.SqlTools.ServiceLayer\bin\Integration\netcoreapp1.0\win7-x64\Microsoft.SqlTools.ServiceLayer.exe
|
||||
SET SERVICECODECOVERAGE=True
|
||||
SET CODECOVERAGETOOL="%WORKINGDIR%packages\OpenCover.4.6.519\tools\OpenCover.Console.exe"
|
||||
SET CODECOVERAGEOUTPUT=coverage.xml
|
||||
|
||||
dotnet.exe test %WORKINGDIR%..\Microsoft.SqlTools.ServiceLayer.TestDriver\project.json %DOTNETCONFIG%"
|
||||
|
||||
SET SERVICECODECOVERAGE=FALSE
|
||||
|
||||
"%WORKINGDIR%packages\OpenCover.4.6.519\tools\OpenCover.Console.exe" -mergeoutput -register:user -target:dotnet.exe -targetargs:"test %WORKINGDIR%..\Microsoft.SqlTools.ServiceLayer.TestDriver\project.json %DOTNETCONFIG%" -oldstyle -filter:"+[Microsoft.SqlTools.*]* -[xunit*]*" -output:coverage.xml -searchdirs:%WORKINGDIR%..\Microsoft.SqlTools.ServiceLayer.TestDriver\bin\Debug\netcoreapp1.0
|
||||
|
||||
"%WORKINGDIR%packages\OpenCover.4.6.519\tools\OpenCover.Console.exe" -mergeoutput -register:user -target:dotnet.exe -targetargs:"test %WORKINGDIR%..\Microsoft.SqlTools.ServiceLayer.Test\project.json %DOTNETCONFIG%" -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"
|
||||
|
||||
|
||||
4
test/CodeCoverage/runintegration.bat
Normal file
4
test/CodeCoverage/runintegration.bat
Normal file
@@ -0,0 +1,4 @@
|
||||
set DOTNETCONFIG=-c Integration
|
||||
|
||||
cmd /c npm install
|
||||
gulp
|
||||
@@ -0,0 +1,39 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.TestDriver.Utility;
|
||||
using Xunit.Sdk;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.PerfTests
|
||||
{
|
||||
/// <summary>
|
||||
/// The attribute for each test to create the test db before the test starts
|
||||
/// </summary>
|
||||
public class CreateTestDbAttribute : BeforeAfterTestAttribute
|
||||
{
|
||||
public CreateTestDbAttribute(TestServerType serverType)
|
||||
{
|
||||
ServerType = serverType;
|
||||
}
|
||||
|
||||
public CreateTestDbAttribute(int serverType)
|
||||
{
|
||||
ServerType = (TestServerType)serverType;
|
||||
}
|
||||
|
||||
public TestServerType ServerType { get; set; }
|
||||
public override void Before(MethodInfo methodUnderTest)
|
||||
{
|
||||
Task task = Common.CreateTestDatabase(ServerType);
|
||||
task.Wait();
|
||||
}
|
||||
|
||||
public override void After(MethodInfo methodUnderTest)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>7e5968ab-83d7-4738-85a2-416a50f13d2f</ProjectGuid>
|
||||
<RootNamespace>Microsoft.SqlTools.ServiceLayer.PerfTests</RootNamespace>
|
||||
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
|
||||
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
|
||||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
</Project>
|
||||
31
test/Microsoft.SqlTools.ServiceLayer.PerfTests/Program.cs
Normal file
31
test/Microsoft.SqlTools.ServiceLayer.PerfTests/Program.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// 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.TestDriver.Driver;
|
||||
using Microsoft.SqlTools.ServiceLayer.TestDriver.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.PerfTests
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
internal static int Main(string[] args)
|
||||
{
|
||||
if (args.Length < 1)
|
||||
{
|
||||
Console.WriteLine("Microsoft.SqlTools.ServiceLayer.PerfTests.exe [tests]" + Environment.NewLine +
|
||||
" [tests] is a space-separated list of tests to run." + Environment.NewLine +
|
||||
" They are qualified within the Microsoft.SqlTools.ServiceLayer.TestDriver.PerfTests namespace" + Environment.NewLine +
|
||||
$"Be sure to set the environment variable {ServiceTestDriver.ServiceHostEnvironmentVariable} to the full path of the sqltoolsservice executable.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
Logger.Initialize("testdriver", LogLevel.Verbose);
|
||||
|
||||
return TestRunner.RunTests(args, "Microsoft.SqlTools.ServiceLayer.PerfTests.").Result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("Microsoft.SqlTools.ServiceLayer.PerfTests")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("7e5968ab-83d7-4738-85a2-416a50f13d2f")]
|
||||
111
test/Microsoft.SqlTools.ServiceLayer.PerfTests/Tests/Common.cs
Normal file
111
test/Microsoft.SqlTools.ServiceLayer.PerfTests/Tests/Common.cs
Normal file
@@ -0,0 +1,111 @@
|
||||
//
|
||||
// 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;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.TestDriver.Scripts;
|
||||
using Microsoft.SqlTools.ServiceLayer.TestDriver.Tests;
|
||||
using Microsoft.SqlTools.ServiceLayer.TestDriver.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.PerfTests
|
||||
{
|
||||
public class Common
|
||||
{
|
||||
public const string PerfTestDatabaseName = "SQLToolsCrossPlatPerfTestDb";
|
||||
public const string MasterDatabaseName = "master";
|
||||
|
||||
|
||||
internal static async Task ExecuteWithTimeout(TestTimer timer, int timeout, Func<Task<bool>> repeatedCode,
|
||||
TimeSpan? delay = null, [CallerMemberName] string testName = "")
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (await repeatedCode())
|
||||
{
|
||||
timer.EndAndPrint(testName);
|
||||
break;
|
||||
}
|
||||
if (timer.TotalMilliSecondsUntilNow >= timeout)
|
||||
{
|
||||
Assert.True(false, $"{testName} timed out after {timeout} milliseconds");
|
||||
break;
|
||||
}
|
||||
if (delay.HasValue)
|
||||
{
|
||||
await Task.Delay(delay.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static async Task<bool> ConnectAsync(TestHelper testHelper, TestServerType serverType, string query, string ownerUri, string databaseName)
|
||||
{
|
||||
testHelper.WriteToFile(ownerUri, query);
|
||||
|
||||
DidOpenTextDocumentNotification openParams = new DidOpenTextDocumentNotification
|
||||
{
|
||||
TextDocument = new TextDocumentItem
|
||||
{
|
||||
Uri = ownerUri,
|
||||
LanguageId = "enu",
|
||||
Version = 1,
|
||||
Text = query
|
||||
}
|
||||
};
|
||||
|
||||
await testHelper.RequestOpenDocumentNotification(openParams);
|
||||
|
||||
Thread.Sleep(500);
|
||||
var connectParams = await testHelper.GetDatabaseConnectionAsync(serverType, databaseName);
|
||||
|
||||
bool connected = await testHelper.Connect(ownerUri, connectParams);
|
||||
Assert.True(connected, "Connection is successful");
|
||||
Console.WriteLine($"Connection to {connectParams.Connection.ServerName} is successful");
|
||||
|
||||
return connected;
|
||||
}
|
||||
|
||||
internal static async Task<T> CalculateRunTime<T>(Func<Task<T>> testToRun, bool printResult, [CallerMemberName] string testName = "")
|
||||
{
|
||||
TestTimer timer = new TestTimer() { PrintResult = printResult };
|
||||
T result = await testToRun();
|
||||
timer.EndAndPrint(testName);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create the test db if not already exists
|
||||
/// </summary>
|
||||
internal static async Task CreateTestDatabase(TestServerType serverType)
|
||||
{
|
||||
using (TestHelper testHelper = new TestHelper())
|
||||
using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile())
|
||||
{
|
||||
string databaseName = Common.PerfTestDatabaseName;
|
||||
string createDatabaseQuery = Scripts.CreateDatabaseQuery.Replace("#DatabaseName#", databaseName);
|
||||
await RunQuery(testHelper, serverType, Common.MasterDatabaseName, createDatabaseQuery);
|
||||
Console.WriteLine(string.Format(CultureInfo.InvariantCulture, "Verified test database '{0}' is created", databaseName));
|
||||
await RunQuery(testHelper, serverType, databaseName, Scripts.CreateDatabaseObjectsQuery);
|
||||
Console.WriteLine(string.Format(CultureInfo.InvariantCulture, "Verified test database '{0}' SQL types are created", databaseName));
|
||||
}
|
||||
}
|
||||
|
||||
internal static async Task RunQuery(TestHelper testHelper, TestServerType serverType, string databaseName, string query)
|
||||
{
|
||||
using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile())
|
||||
{
|
||||
await Common.ConnectAsync(testHelper, serverType, query, queryTempFile.FilePath, databaseName);
|
||||
var queryResult = await Common.CalculateRunTime(() => testHelper.RunQuery(queryTempFile.FilePath, query, 50000), false);
|
||||
|
||||
await testHelper.Disconnect(queryTempFile.FilePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.TestDriver.Scripts;
|
||||
using Microsoft.SqlTools.ServiceLayer.TestDriver.Tests;
|
||||
using Microsoft.SqlTools.ServiceLayer.TestDriver.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.PerfTests
|
||||
{
|
||||
public class ConnectionTests
|
||||
{
|
||||
|
||||
[Fact]
|
||||
[CreateTestDb(TestServerType.Azure)]
|
||||
public async Task ConnectAzureTest()
|
||||
{
|
||||
TestServerType serverType = TestServerType.Azure;
|
||||
using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile())
|
||||
using (TestHelper testHelper = new TestHelper())
|
||||
{
|
||||
const string query = Scripts.TestDbSimpleSelectQuery;
|
||||
testHelper.WriteToFile(queryTempFile.FilePath, query);
|
||||
|
||||
DidOpenTextDocumentNotification openParams = new DidOpenTextDocumentNotification
|
||||
{
|
||||
TextDocument = new TextDocumentItem
|
||||
{
|
||||
Uri = queryTempFile.FilePath,
|
||||
LanguageId = "enu",
|
||||
Version = 1,
|
||||
Text = query
|
||||
}
|
||||
};
|
||||
|
||||
await testHelper.RequestOpenDocumentNotification(openParams);
|
||||
|
||||
Thread.Sleep(500);
|
||||
var connected = await Common.CalculateRunTime(async () =>
|
||||
{
|
||||
var connectParams = await testHelper.GetDatabaseConnectionAsync(serverType, Common.PerfTestDatabaseName);
|
||||
return await testHelper.Connect(queryTempFile.FilePath, connectParams);
|
||||
}, true);
|
||||
Assert.True(connected, "Connection was not successful");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[CreateTestDb(TestServerType.OnPrem)]
|
||||
public async Task ConnectOnPremTest()
|
||||
{
|
||||
TestServerType serverType = TestServerType.OnPrem;
|
||||
|
||||
using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile())
|
||||
using (TestHelper testHelper = new TestHelper())
|
||||
{
|
||||
const string query = Scripts.TestDbSimpleSelectQuery;
|
||||
testHelper.WriteToFile(queryTempFile.FilePath, query);
|
||||
|
||||
DidOpenTextDocumentNotification openParams = new DidOpenTextDocumentNotification
|
||||
{
|
||||
TextDocument = new TextDocumentItem
|
||||
{
|
||||
Uri = queryTempFile.FilePath,
|
||||
LanguageId = "enu",
|
||||
Version = 1,
|
||||
Text = query
|
||||
}
|
||||
};
|
||||
|
||||
await testHelper.RequestOpenDocumentNotification(openParams);
|
||||
|
||||
Thread.Sleep(500);
|
||||
var connected = await Common.CalculateRunTime(async () =>
|
||||
{
|
||||
var connectParams = await testHelper.GetDatabaseConnectionAsync(serverType, Common.PerfTestDatabaseName);
|
||||
return await testHelper.Connect(queryTempFile.FilePath, connectParams);
|
||||
}, true);
|
||||
Assert.True(connected, "Connection was not successful");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[CreateTestDb(TestServerType.OnPrem)]
|
||||
public async Task DisconnectTest()
|
||||
{
|
||||
TestServerType serverType = TestServerType.OnPrem;
|
||||
|
||||
using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile())
|
||||
using (TestHelper testHelper = new TestHelper())
|
||||
{
|
||||
await Common.ConnectAsync(testHelper, serverType, Scripts.TestDbSimpleSelectQuery, queryTempFile.FilePath, Common.PerfTestDatabaseName);
|
||||
Thread.Sleep(1000);
|
||||
var connected = await Common.CalculateRunTime(() => testHelper.Disconnect(queryTempFile.FilePath), true);
|
||||
Assert.True(connected);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
//
|
||||
// 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.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.TestDriver.Scripts;
|
||||
using Microsoft.SqlTools.ServiceLayer.TestDriver.Tests;
|
||||
using Microsoft.SqlTools.ServiceLayer.TestDriver.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.PerfTests
|
||||
{
|
||||
public class IntellisenseTests
|
||||
{
|
||||
[Fact]
|
||||
[CreateTestDb(TestServerType.OnPrem)]
|
||||
public async Task HoverTestOnPrem()
|
||||
{
|
||||
TestServerType serverType = TestServerType.OnPrem;
|
||||
using (TestHelper testHelper = new TestHelper())
|
||||
using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile())
|
||||
{
|
||||
const string query = Scripts.TestDbSimpleSelectQuery;
|
||||
await Common.ConnectAsync(testHelper, serverType, query, queryTempFile.FilePath, Common.PerfTestDatabaseName);
|
||||
Hover hover = await Common.CalculateRunTime(() => testHelper.RequestHover(queryTempFile.FilePath, query, 0, Scripts.TestDbComplexSelectQueries.Length + 1), true);
|
||||
Assert.NotNull(hover);
|
||||
await testHelper.Disconnect(queryTempFile.FilePath);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[CreateTestDb(TestServerType.OnPrem)]
|
||||
public async Task SuggestionsTest()
|
||||
{
|
||||
TestServerType serverType = TestServerType.OnPrem;
|
||||
using (TestHelper testHelper = new TestHelper())
|
||||
using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile())
|
||||
{
|
||||
const string query = Scripts.TestDbSimpleSelectQuery;
|
||||
await Common.ConnectAsync(testHelper, serverType, query, queryTempFile.FilePath, Common.PerfTestDatabaseName);
|
||||
await ValidateCompletionResponse(testHelper, queryTempFile.FilePath, false, Common.PerfTestDatabaseName, true);
|
||||
await ValidateCompletionResponse(testHelper, queryTempFile.FilePath, true, Common.PerfTestDatabaseName, false);
|
||||
await testHelper.Disconnect(queryTempFile.FilePath);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[CreateTestDb(TestServerType.OnPrem)]
|
||||
public async Task DiagnosticsTests()
|
||||
{
|
||||
TestServerType serverType = TestServerType.OnPrem;
|
||||
await Common.CreateTestDatabase(serverType);
|
||||
|
||||
using (TestHelper testHelper = new TestHelper())
|
||||
using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile())
|
||||
{
|
||||
await Common.ConnectAsync(testHelper, serverType, Scripts.TestDbSimpleSelectQuery, queryTempFile.FilePath, Common.PerfTestDatabaseName);
|
||||
|
||||
Thread.Sleep(500);
|
||||
var contentChanges = new TextDocumentChangeEvent[1];
|
||||
contentChanges[0] = new TextDocumentChangeEvent()
|
||||
{
|
||||
Range = new Range
|
||||
{
|
||||
Start = new Position
|
||||
{
|
||||
Line = 0,
|
||||
Character = 5
|
||||
},
|
||||
End = new Position
|
||||
{
|
||||
Line = 0,
|
||||
Character = 6
|
||||
}
|
||||
},
|
||||
RangeLength = 1,
|
||||
Text = "z"
|
||||
};
|
||||
DidChangeTextDocumentParams changeParams = new DidChangeTextDocumentParams
|
||||
{
|
||||
ContentChanges = contentChanges,
|
||||
TextDocument = new VersionedTextDocumentIdentifier
|
||||
{
|
||||
Version = 2,
|
||||
Uri = queryTempFile.FilePath
|
||||
}
|
||||
};
|
||||
|
||||
TestTimer timer = new TestTimer() { PrintResult = true };
|
||||
await testHelper.RequestChangeTextDocumentNotification(changeParams);
|
||||
await Common.ExecuteWithTimeout(timer, 60000, async () =>
|
||||
{
|
||||
var completeEvent = await testHelper.Driver.WaitForEvent(PublishDiagnosticsNotification.Type, 15000);
|
||||
return completeEvent?.Diagnostics != null && completeEvent.Diagnostics.Length > 0;
|
||||
});
|
||||
await testHelper.Disconnect(queryTempFile.FilePath);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[CreateTestDb(TestServerType.Azure)]
|
||||
public async Task BindingCacheColdAzureSimpleQuery()
|
||||
{
|
||||
TestServerType serverType = TestServerType.Azure;
|
||||
using (TestHelper testHelper = new TestHelper())
|
||||
{
|
||||
await VerifyBindingLoadScenario(testHelper, serverType, Scripts.TestDbSimpleSelectQuery, false);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[CreateTestDb(TestServerType.OnPrem)]
|
||||
public async Task BindingCacheColdOnPremSimpleQuery()
|
||||
{
|
||||
TestServerType serverType = TestServerType.OnPrem;
|
||||
using (TestHelper testHelper = new TestHelper())
|
||||
{
|
||||
await VerifyBindingLoadScenario(testHelper, TestServerType.OnPrem, Scripts.TestDbSimpleSelectQuery, false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[CreateTestDb(TestServerType.Azure)]
|
||||
public async Task BindingCacheWarmAzureSimpleQuery()
|
||||
{
|
||||
TestServerType serverType = TestServerType.Azure;
|
||||
using (TestHelper testHelper = new TestHelper())
|
||||
using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile())
|
||||
{
|
||||
const string query = Scripts.TestDbSimpleSelectQuery;
|
||||
await VerifyBindingLoadScenario(testHelper, serverType, query, true);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[CreateTestDb(TestServerType.OnPrem)]
|
||||
public async Task BindingCacheWarmOnPremSimpleQuery()
|
||||
{
|
||||
TestServerType serverType = TestServerType.OnPrem;
|
||||
|
||||
using (TestHelper testHelper = new TestHelper())
|
||||
using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile())
|
||||
{
|
||||
const string query = Scripts.TestDbSimpleSelectQuery;
|
||||
await VerifyBindingLoadScenario(testHelper, serverType, query, true);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[CreateTestDb(TestServerType.Azure)]
|
||||
public async Task BindingCacheColdAzureComplexQuery()
|
||||
{
|
||||
TestServerType serverType = TestServerType.Azure;
|
||||
|
||||
using (TestHelper testHelper = new TestHelper())
|
||||
{
|
||||
await VerifyBindingLoadScenario(testHelper, serverType, Scripts.TestDbComplexSelectQueries,false);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[CreateTestDb(TestServerType.Azure)]
|
||||
public async Task BindingCacheColdOnPremComplexQuery()
|
||||
{
|
||||
TestServerType serverType = TestServerType.Azure;
|
||||
using (TestHelper testHelper = new TestHelper())
|
||||
{
|
||||
await VerifyBindingLoadScenario(testHelper, TestServerType.OnPrem, Scripts.TestDbComplexSelectQueries, false);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[CreateTestDb(TestServerType.Azure)]
|
||||
public async Task BindingCacheWarmAzureComplexQuery()
|
||||
{
|
||||
using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile())
|
||||
using (TestHelper testHelper = new TestHelper())
|
||||
{
|
||||
string query = Scripts.TestDbComplexSelectQueries;
|
||||
const TestServerType serverType = TestServerType.Azure;
|
||||
await VerifyBindingLoadScenario(testHelper, serverType, query, true);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[CreateTestDb(TestServerType.OnPrem)]
|
||||
public async Task BindingCacheWarmOnPremComplexQuery()
|
||||
{
|
||||
using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile())
|
||||
using (TestHelper testHelper = new TestHelper())
|
||||
{
|
||||
string query = Scripts.TestDbComplexSelectQueries;
|
||||
const TestServerType serverType = TestServerType.OnPrem;
|
||||
await VerifyBindingLoadScenario(testHelper, serverType, query, true);
|
||||
}
|
||||
}
|
||||
|
||||
#region Private Helper Methods
|
||||
|
||||
private async Task VerifyBindingLoadScenario(
|
||||
TestHelper testHelper,
|
||||
TestServerType serverType,
|
||||
string query,
|
||||
bool preLoad,
|
||||
[CallerMemberName] string testName = "")
|
||||
{
|
||||
string databaseName = Common.PerfTestDatabaseName;
|
||||
if (preLoad)
|
||||
{
|
||||
await VerifyCompletationLoaded(testHelper, serverType, Scripts.TestDbSimpleSelectQuery,
|
||||
databaseName, printResult: false, testName: testName);
|
||||
Console.WriteLine("Intellisense cache loaded.");
|
||||
}
|
||||
await VerifyCompletationLoaded(testHelper, serverType, query, databaseName,
|
||||
printResult: true, testName: testName);
|
||||
}
|
||||
|
||||
private async Task VerifyCompletationLoaded(
|
||||
TestHelper testHelper,
|
||||
TestServerType serverType,
|
||||
string query,
|
||||
string databaseName,
|
||||
bool printResult,
|
||||
string testName)
|
||||
{
|
||||
using (SelfCleaningTempFile testTempFile = new SelfCleaningTempFile())
|
||||
{
|
||||
testHelper.WriteToFile(testTempFile.FilePath, query);
|
||||
await Common.ConnectAsync(testHelper, serverType, query, testTempFile.FilePath, databaseName);
|
||||
await ValidateCompletionResponse(testHelper, testTempFile.FilePath, printResult, databaseName,
|
||||
waitForIntelliSense: true, testName: testName);
|
||||
await testHelper.Disconnect(testTempFile.FilePath);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task ValidateCompletionResponse(
|
||||
TestHelper testHelper,
|
||||
string ownerUri,
|
||||
bool printResult,
|
||||
string databaseName,
|
||||
bool waitForIntelliSense,
|
||||
[CallerMemberName] string testName = "")
|
||||
{
|
||||
TestTimer timer = new TestTimer() { PrintResult = printResult };
|
||||
bool isReady = !waitForIntelliSense;
|
||||
await Common.ExecuteWithTimeout(timer, 150000, async () =>
|
||||
{
|
||||
if (isReady)
|
||||
{
|
||||
string query = Scripts.SelectQuery;
|
||||
CompletionItem[] completions = await testHelper.RequestCompletion(ownerUri, query, 0, query.Length + 1);
|
||||
return completions != null && completions.Any(x => x.Label == databaseName);
|
||||
}
|
||||
else
|
||||
{
|
||||
var completeEvent = await testHelper.Driver.WaitForEvent(IntelliSenseReadyNotification.Type, 100000);
|
||||
isReady = completeEvent.OwnerUri == ownerUri;
|
||||
if (isReady)
|
||||
{
|
||||
Console.WriteLine("IntelliSense cache is loaded.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}, testName: testName);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
//
|
||||
// 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.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.TestDriver.Scripts;
|
||||
using Microsoft.SqlTools.ServiceLayer.TestDriver.Tests;
|
||||
using Microsoft.SqlTools.ServiceLayer.TestDriver.Utility;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.PerfTests
|
||||
{
|
||||
public class QueryExecutionTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task QueryResultSummaryOnPremTest()
|
||||
{
|
||||
TestServerType serverType = TestServerType.OnPrem;
|
||||
|
||||
using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile())
|
||||
using (TestHelper testHelper = new TestHelper())
|
||||
{
|
||||
const string query = Scripts.MasterBasicQuery;
|
||||
|
||||
await Common.ConnectAsync(testHelper, serverType, query, queryTempFile.FilePath, Common.MasterDatabaseName);
|
||||
var queryResult = await Common.CalculateRunTime(() => testHelper.RunQuery(queryTempFile.FilePath, query), true);
|
||||
|
||||
Assert.NotNull(queryResult);
|
||||
Assert.True(queryResult.BatchSummaries.Any(x => x.ResultSetSummaries.Any(r => r.RowCount > 0)));
|
||||
|
||||
await testHelper.Disconnect(queryTempFile.FilePath);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task QueryResultFirstOnPremTest()
|
||||
{
|
||||
TestServerType serverType = TestServerType.OnPrem;
|
||||
|
||||
using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile())
|
||||
using (TestHelper testHelper = new TestHelper())
|
||||
{
|
||||
const string query = Scripts.MasterBasicQuery;
|
||||
|
||||
await Common.ConnectAsync(testHelper, serverType, query, queryTempFile.FilePath, Common.MasterDatabaseName);
|
||||
|
||||
var queryResult = await Common.CalculateRunTime(async () =>
|
||||
{
|
||||
await testHelper.RunQuery(queryTempFile.FilePath, query);
|
||||
return await testHelper.ExecuteSubset(queryTempFile.FilePath, 0, 0, 0, 100);
|
||||
}, true);
|
||||
|
||||
Assert.NotNull(queryResult);
|
||||
Assert.NotNull(queryResult.ResultSubset);
|
||||
Assert.True(queryResult.ResultSubset.Rows.Any());
|
||||
|
||||
await testHelper.Disconnect(queryTempFile.FilePath);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[CreateTestDb(TestServerType.OnPrem)]
|
||||
public async Task CancelQueryOnPremTest()
|
||||
{
|
||||
TestServerType serverType = TestServerType.OnPrem;
|
||||
|
||||
using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile())
|
||||
using (TestHelper testHelper = new TestHelper())
|
||||
{
|
||||
await Common.ConnectAsync(testHelper, serverType, Scripts.DelayQuery, queryTempFile.FilePath, Common.PerfTestDatabaseName);
|
||||
var queryParams = new QueryExecuteParams
|
||||
{
|
||||
OwnerUri = queryTempFile.FilePath,
|
||||
QuerySelection = null
|
||||
};
|
||||
|
||||
var result = await testHelper.Driver.SendRequest(QueryExecuteRequest.Type, queryParams);
|
||||
if (result != null && string.IsNullOrEmpty(result.Messages))
|
||||
{
|
||||
TestTimer timer = new TestTimer() { PrintResult = true };
|
||||
await Common.ExecuteWithTimeout(timer, 100000, async () =>
|
||||
{
|
||||
var cancelQueryResult = await testHelper.CancelQuery(queryTempFile.FilePath);
|
||||
return true;
|
||||
}, TimeSpan.FromMilliseconds(10));
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.True(false, "Failed to run the query");
|
||||
}
|
||||
|
||||
await testHelper.Disconnect(queryTempFile.FilePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.TestDriver.Scripts;
|
||||
using Microsoft.SqlTools.ServiceLayer.TestDriver.Tests;
|
||||
using Microsoft.SqlTools.ServiceLayer.TestDriver.Utility;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.PerfTests
|
||||
{
|
||||
public class SaveResultsTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task TestSaveResultsToCsvTest()
|
||||
{
|
||||
TestServerType serverType = TestServerType.OnPrem;
|
||||
|
||||
using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile())
|
||||
using (SelfCleaningTempFile outputTempFile = new SelfCleaningTempFile())
|
||||
using (TestHelper testHelper = new TestHelper())
|
||||
{
|
||||
const string query = Scripts.MasterBasicQuery;
|
||||
|
||||
// Execute a query
|
||||
await Common.ConnectAsync(testHelper, serverType, query, queryTempFile.FilePath, Common.MasterDatabaseName);
|
||||
await testHelper.RunQuery(queryTempFile.FilePath, query);
|
||||
await Common.CalculateRunTime(() => testHelper.SaveAsCsv(queryTempFile.FilePath, outputTempFile.FilePath, 0, 0), true);
|
||||
await testHelper.Disconnect(queryTempFile.FilePath);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TestSaveResultsToJsonTest()
|
||||
{
|
||||
TestServerType serverType = TestServerType.OnPrem;
|
||||
|
||||
using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile())
|
||||
using (SelfCleaningTempFile outputTempFile = new SelfCleaningTempFile())
|
||||
using (TestHelper testHelper = new TestHelper())
|
||||
{
|
||||
const string query = Scripts.MasterBasicQuery;
|
||||
|
||||
// Execute a query
|
||||
await Common.ConnectAsync(testHelper, serverType, query, queryTempFile.FilePath, Common.MasterDatabaseName);
|
||||
await testHelper.RunQuery(queryTempFile.FilePath, query);
|
||||
await Common.CalculateRunTime(() => testHelper.SaveAsJson(queryTempFile.FilePath, outputTempFile.FilePath, 0, 0), true);
|
||||
await testHelper.Disconnect(queryTempFile.FilePath);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
35
test/Microsoft.SqlTools.ServiceLayer.PerfTests/project.json
Normal file
35
test/Microsoft.SqlTools.ServiceLayer.PerfTests/project.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "Microsoft.SqlTools.ServiceLayer.PerfTests",
|
||||
"version": "1.0.0-*",
|
||||
"buildOptions": {
|
||||
"debugType": "portable",
|
||||
"emitEntryPoint": true
|
||||
},
|
||||
"dependencies": {
|
||||
"xunit": "2.1.0",
|
||||
"dotnet-test-xunit": "1.0.0-rc2-192208-24",
|
||||
"Microsoft.SqlTools.ServiceLayer": {
|
||||
"target": "project"
|
||||
},
|
||||
"Microsoft.SqlTools.ServiceLayer.TestDriver": "1.0.0-*"
|
||||
},
|
||||
|
||||
"testRunner": "xunit",
|
||||
|
||||
"frameworks": {
|
||||
"netcoreapp1.0": {
|
||||
"dependencies": {
|
||||
"Microsoft.NETCore.App": {
|
||||
"version": "1.0.0"
|
||||
}
|
||||
},
|
||||
"imports": [
|
||||
"dotnet5.4",
|
||||
"portable-net451+win8"
|
||||
]
|
||||
}
|
||||
},
|
||||
"runtimes": {
|
||||
"win7-x64": {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Xunit;
|
||||
|
||||
[assembly: CollectionBehavior(DisableTestParallelization = true)]
|
||||
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
|
||||
using System.Threading;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Completion;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Test.Completion
|
||||
{
|
||||
public class AutoCompletionResultTest
|
||||
{
|
||||
[Fact]
|
||||
public void MetricsShouldGetSortedGivenUnSortedArray()
|
||||
{
|
||||
AutoCompletionResult result = new AutoCompletionResult();
|
||||
int duration = 2000;
|
||||
Thread.Sleep(duration);
|
||||
result.CompleteResult(new CompletionItem[] { });
|
||||
Assert.True(result.Duration >= duration);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Completion;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Test.Completion
|
||||
{
|
||||
public class ScriptDocumentInfoTest
|
||||
{
|
||||
[Fact]
|
||||
public void MetricsShouldGetSortedGivenUnSortedArray()
|
||||
{
|
||||
TextDocumentPosition doc = new TextDocumentPosition()
|
||||
{
|
||||
TextDocument = new TextDocumentIdentifier
|
||||
{
|
||||
Uri = "script file"
|
||||
},
|
||||
Position = new Position()
|
||||
{
|
||||
Line = 1,
|
||||
Character = 14
|
||||
}
|
||||
};
|
||||
ScriptFile scriptFile = new ScriptFile()
|
||||
{
|
||||
Contents = "Select * from sys.all_objects"
|
||||
};
|
||||
|
||||
ScriptParseInfo scriptParseInfo = new ScriptParseInfo();
|
||||
ScriptDocumentInfo docInfo = new ScriptDocumentInfo(doc, scriptFile, scriptParseInfo);
|
||||
|
||||
Assert.Equal(docInfo.StartLine, 1);
|
||||
Assert.Equal(docInfo.ParserLine, 2);
|
||||
Assert.Equal(docInfo.StartColumn, 44);
|
||||
Assert.Equal(docInfo.EndColumn, 14);
|
||||
Assert.Equal(docInfo.ParserColumn, 15);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -809,6 +809,36 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection
|
||||
Assert.True(callbackInvoked);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test ConnectionSummaryComparer
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void TestConnectionSummaryComparer()
|
||||
{
|
||||
var summary1 = new ConnectionSummary()
|
||||
{
|
||||
ServerName = "localhost",
|
||||
DatabaseName = "master",
|
||||
UserName = "user"
|
||||
};
|
||||
|
||||
var summary2 = new ConnectionSummary()
|
||||
{
|
||||
ServerName = "localhost",
|
||||
DatabaseName = "master",
|
||||
UserName = "user"
|
||||
};
|
||||
|
||||
var comparer = new ConnectionSummaryComparer();
|
||||
Assert.True(comparer.Equals(summary1, summary2));
|
||||
|
||||
summary2.DatabaseName = "tempdb";
|
||||
Assert.False(comparer.Equals(summary1, summary2));
|
||||
Assert.False(comparer.Equals(null, summary2));
|
||||
|
||||
Assert.False(summary1.GetHashCode() == summary2.GetHashCode());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify when a connection is created that the URI -> Connection mapping is created in the connection service.
|
||||
/// </summary>
|
||||
@@ -861,5 +891,74 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test that the connection complete notification type can be created.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void TestConnectionCompleteNotificationIsCreated()
|
||||
{
|
||||
Assert.NotNull(ConnectionCompleteNotification.Type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test that the connection summary comparer creates a hash code correctly
|
||||
/// <summary>
|
||||
[Theory]
|
||||
[InlineData(true, null, null ,null)]
|
||||
[InlineData(false, null, null, null)]
|
||||
[InlineData(false, null, null, "sa")]
|
||||
[InlineData(false, null, "test", null)]
|
||||
[InlineData(false, null, "test", "sa")]
|
||||
[InlineData(false, "server", null, null)]
|
||||
[InlineData(false, "server", null, "sa")]
|
||||
[InlineData(false, "server", "test", null)]
|
||||
[InlineData(false, "server", "test", "sa")]
|
||||
public void TestConnectionSummaryComparerHashCode(bool objectNull, string serverName, string databaseName, string userName)
|
||||
{
|
||||
// Given a connection summary and comparer object
|
||||
ConnectionSummary summary = null;
|
||||
if (!objectNull)
|
||||
{
|
||||
summary = new ConnectionSummary()
|
||||
{
|
||||
ServerName = serverName,
|
||||
DatabaseName = databaseName,
|
||||
UserName = userName
|
||||
};
|
||||
}
|
||||
ConnectionSummaryComparer comparer = new ConnectionSummaryComparer();
|
||||
|
||||
// If I compute a hash code
|
||||
int hashCode = comparer.GetHashCode(summary);
|
||||
if (summary == null || (serverName == null && databaseName == null && userName == null))
|
||||
{
|
||||
// Then I expect it to be 31 for a null summary
|
||||
Assert.Equal(31, hashCode);
|
||||
}
|
||||
else
|
||||
{
|
||||
// And not 31 otherwise
|
||||
Assert.NotEqual(31, hashCode);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConnectParamsAreInvalidIfConnectionIsNull()
|
||||
{
|
||||
// Given connection parameters where the connection property is null
|
||||
ConnectParams parameters = new ConnectParams();
|
||||
parameters.OwnerUri = "my/sql/file.sql";
|
||||
parameters.Connection = null;
|
||||
|
||||
string errorMessage;
|
||||
|
||||
// If I check if the parameters are valid
|
||||
Assert.False(parameters.IsValid(out errorMessage));
|
||||
|
||||
// Then I expect an error message
|
||||
Assert.NotNull(errorMessage);
|
||||
Assert.NotEmpty(errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,12 +6,20 @@
|
||||
#if LIVE_CONNECTION_TESTS
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.Data.SqlClient;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||
using Microsoft.SqlTools.Test.Utility;
|
||||
using Xunit;
|
||||
using static Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection.ReliableConnectionHelper;
|
||||
using static Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection.RetryPolicy;
|
||||
using static Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection.RetryPolicy.TimeBasedRetryPolicy;
|
||||
using static Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection.SqlSchemaModelErrorCodes;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Test.Connection
|
||||
{
|
||||
@@ -21,6 +29,134 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection
|
||||
/// </summary>
|
||||
public class ReliableConnectionTests
|
||||
{
|
||||
internal class TestDataTransferErrorDetectionStrategy : DataTransferErrorDetectionStrategy
|
||||
{
|
||||
public bool InvokeCanRetrySqlException(SqlException exception)
|
||||
{
|
||||
return CanRetrySqlException(exception);
|
||||
}
|
||||
}
|
||||
internal class TestSqlAzureTemporaryAndIgnorableErrorDetectionStrategy : SqlAzureTemporaryAndIgnorableErrorDetectionStrategy
|
||||
{
|
||||
public TestSqlAzureTemporaryAndIgnorableErrorDetectionStrategy()
|
||||
: base (new int[] { 100 })
|
||||
{
|
||||
}
|
||||
|
||||
public bool InvokeCanRetrySqlException(SqlException exception)
|
||||
{
|
||||
return CanRetrySqlException(exception);
|
||||
}
|
||||
|
||||
public bool InvokeShouldIgnoreSqlException(SqlException exception)
|
||||
{
|
||||
return ShouldIgnoreSqlException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
internal class TestFixedDelayPolicy : FixedDelayPolicy
|
||||
{
|
||||
public TestFixedDelayPolicy(
|
||||
IErrorDetectionStrategy strategy,
|
||||
int maxRetryCount,
|
||||
TimeSpan intervalBetweenRetries)
|
||||
: base(strategy,
|
||||
maxRetryCount,
|
||||
intervalBetweenRetries)
|
||||
{
|
||||
}
|
||||
|
||||
public bool InvokeShouldRetryImpl(RetryState retryStateObj)
|
||||
{
|
||||
return ShouldRetryImpl(retryStateObj);
|
||||
}
|
||||
}
|
||||
|
||||
internal class TestProgressiveRetryPolicy : ProgressiveRetryPolicy
|
||||
{
|
||||
public TestProgressiveRetryPolicy(
|
||||
IErrorDetectionStrategy strategy,
|
||||
int maxRetryCount,
|
||||
TimeSpan initialInterval,
|
||||
TimeSpan increment)
|
||||
: base(strategy,
|
||||
maxRetryCount,
|
||||
initialInterval,
|
||||
increment)
|
||||
{
|
||||
}
|
||||
|
||||
public bool InvokeShouldRetryImpl(RetryState retryStateObj)
|
||||
{
|
||||
return ShouldRetryImpl(retryStateObj);
|
||||
}
|
||||
}
|
||||
|
||||
internal class TestTimeBasedRetryPolicy : TimeBasedRetryPolicy
|
||||
{
|
||||
public TestTimeBasedRetryPolicy(
|
||||
IErrorDetectionStrategy strategy,
|
||||
TimeSpan minTotalRetryTimeLimit,
|
||||
TimeSpan maxTotalRetryTimeLimit,
|
||||
double totalRetryTimeLimitRate,
|
||||
TimeSpan minInterval,
|
||||
TimeSpan maxInterval,
|
||||
double intervalFactor)
|
||||
: base(
|
||||
strategy,
|
||||
minTotalRetryTimeLimit,
|
||||
maxTotalRetryTimeLimit,
|
||||
totalRetryTimeLimitRate,
|
||||
minInterval,
|
||||
maxInterval,
|
||||
intervalFactor)
|
||||
{
|
||||
}
|
||||
|
||||
public bool InvokeShouldRetryImpl(RetryState retryStateObj)
|
||||
{
|
||||
return ShouldRetryImpl(retryStateObj);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FixedDelayPolicyTest()
|
||||
{
|
||||
TestFixedDelayPolicy policy = new TestFixedDelayPolicy(
|
||||
strategy: new NetworkConnectivityErrorDetectionStrategy(),
|
||||
maxRetryCount: 3,
|
||||
intervalBetweenRetries: TimeSpan.FromMilliseconds(100));
|
||||
bool shouldRety = policy.InvokeShouldRetryImpl(new RetryStateEx());
|
||||
Assert.True(shouldRety);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ProgressiveRetryPolicyTest()
|
||||
{
|
||||
TestProgressiveRetryPolicy policy = new TestProgressiveRetryPolicy(
|
||||
strategy: new NetworkConnectivityErrorDetectionStrategy(),
|
||||
maxRetryCount: 3,
|
||||
initialInterval: TimeSpan.FromMilliseconds(100),
|
||||
increment: TimeSpan.FromMilliseconds(100));
|
||||
bool shouldRety = policy.InvokeShouldRetryImpl(new RetryStateEx());
|
||||
Assert.True(shouldRety);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TimeBasedRetryPolicyTest()
|
||||
{
|
||||
TestTimeBasedRetryPolicy policy = new TestTimeBasedRetryPolicy(
|
||||
strategy: new NetworkConnectivityErrorDetectionStrategy(),
|
||||
minTotalRetryTimeLimit: TimeSpan.FromMilliseconds(100),
|
||||
maxTotalRetryTimeLimit: TimeSpan.FromMilliseconds(100),
|
||||
totalRetryTimeLimitRate: 100,
|
||||
minInterval: TimeSpan.FromMilliseconds(100),
|
||||
maxInterval: TimeSpan.FromMilliseconds(100),
|
||||
intervalFactor: 1);
|
||||
bool shouldRety = policy.InvokeShouldRetryImpl(new RetryStateEx());
|
||||
Assert.True(shouldRety);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Environment variable that stores the name of the test server hosting the SQL Server instance.
|
||||
/// </summary>
|
||||
@@ -338,6 +474,269 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection
|
||||
Assert.NotEmpty(info.ServerVersion);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Validate ambient static settings
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void AmbientSettingsStaticPropertiesTest()
|
||||
{
|
||||
var defaultSettings = AmbientSettings.DefaultSettings;
|
||||
Assert.NotNull(defaultSettings);
|
||||
var masterReferenceFilePath = AmbientSettings.MasterReferenceFilePath;
|
||||
var maxDataReaderDegreeOfParallelism = AmbientSettings.MaxDataReaderDegreeOfParallelism;
|
||||
var tableProgressUpdateInterval = AmbientSettings.TableProgressUpdateInterval;
|
||||
var traceRowCountFailure = AmbientSettings.TraceRowCountFailure;
|
||||
var useOfflineDataReader = AmbientSettings.UseOfflineDataReader;
|
||||
var streamBackingStoreForOfflineDataReading = AmbientSettings.StreamBackingStoreForOfflineDataReading;
|
||||
var disableIndexesForDataPhase = AmbientSettings.DisableIndexesForDataPhase;
|
||||
var reliableDdlEnabled = AmbientSettings.ReliableDdlEnabled;
|
||||
var importModelDatabase = AmbientSettings.ImportModelDatabase;
|
||||
var supportAlwaysEncrypted = AmbientSettings.SupportAlwaysEncrypted;
|
||||
var alwaysEncryptedWizardMigration = AmbientSettings.AlwaysEncryptedWizardMigration;
|
||||
var skipObjectTypeBlocking =AmbientSettings.SkipObjectTypeBlocking;
|
||||
var doNotSerializeQueryStoreSettings = AmbientSettings.DoNotSerializeQueryStoreSettings;
|
||||
var lockTimeoutMilliSeconds = AmbientSettings.LockTimeoutMilliSeconds;
|
||||
var queryTimeoutSeconds = AmbientSettings.QueryTimeoutSeconds;
|
||||
var longRunningQueryTimeoutSeconds = AmbientSettings.LongRunningQueryTimeoutSeconds;
|
||||
var alwaysRetryOnTransientFailure = AmbientSettings.AlwaysRetryOnTransientFailure;
|
||||
var connectionRetryMessageHandler = AmbientSettings.ConnectionRetryMessageHandler;
|
||||
|
||||
using (var settingsContext = AmbientSettings.CreateSettingsContext())
|
||||
{
|
||||
var settings = settingsContext.Settings;
|
||||
Assert.NotNull(settings);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate ambient settings populate
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void AmbientSettingsPopulateTest()
|
||||
{
|
||||
var data = new AmbientSettings.AmbientData();
|
||||
|
||||
var masterReferenceFilePath = data.MasterReferenceFilePath;
|
||||
data.MasterReferenceFilePath = masterReferenceFilePath;
|
||||
var lockTimeoutMilliSeconds = data.LockTimeoutMilliSeconds;
|
||||
data.LockTimeoutMilliSeconds = lockTimeoutMilliSeconds;
|
||||
var queryTimeoutSeconds = data.QueryTimeoutSeconds;
|
||||
data.QueryTimeoutSeconds = queryTimeoutSeconds;
|
||||
var longRunningQueryTimeoutSeconds = data.LongRunningQueryTimeoutSeconds;
|
||||
data.LongRunningQueryTimeoutSeconds = longRunningQueryTimeoutSeconds;
|
||||
var alwaysRetryOnTransientFailure = data.AlwaysRetryOnTransientFailure;
|
||||
data.AlwaysRetryOnTransientFailure = alwaysRetryOnTransientFailure;
|
||||
var connectionRetryMessageHandler = data.ConnectionRetryMessageHandler;
|
||||
data.ConnectionRetryMessageHandler = connectionRetryMessageHandler;
|
||||
var traceRowCountFailure = data.TraceRowCountFailure;
|
||||
data.TraceRowCountFailure = traceRowCountFailure;
|
||||
var tableProgressUpdateInterval = data.TableProgressUpdateInterval;
|
||||
data.TableProgressUpdateInterval = tableProgressUpdateInterval;
|
||||
var useOfflineDataReader = data.UseOfflineDataReader;
|
||||
data.UseOfflineDataReader = useOfflineDataReader;
|
||||
var streamBackingStoreForOfflineDataReading = data.StreamBackingStoreForOfflineDataReading;
|
||||
data.StreamBackingStoreForOfflineDataReading = streamBackingStoreForOfflineDataReading;
|
||||
var disableIndexesForDataPhase = data.DisableIndexesForDataPhase;
|
||||
data.DisableIndexesForDataPhase = disableIndexesForDataPhase;
|
||||
var reliableDdlEnabled = data.ReliableDdlEnabled;
|
||||
data.ReliableDdlEnabled = reliableDdlEnabled;
|
||||
var importModelDatabase = data.ImportModelDatabase;
|
||||
data.ImportModelDatabase = importModelDatabase;
|
||||
var supportAlwaysEncrypted = data.SupportAlwaysEncrypted;
|
||||
data.SupportAlwaysEncrypted = supportAlwaysEncrypted;
|
||||
var alwaysEncryptedWizardMigration = data.AlwaysEncryptedWizardMigration;
|
||||
data.AlwaysEncryptedWizardMigration = alwaysEncryptedWizardMigration;
|
||||
var skipObjectTypeBlocking = data.SkipObjectTypeBlocking;
|
||||
data.SkipObjectTypeBlocking = skipObjectTypeBlocking;
|
||||
var doNotSerializeQueryStoreSettings = data.DoNotSerializeQueryStoreSettings;
|
||||
data.DoNotSerializeQueryStoreSettings = doNotSerializeQueryStoreSettings;
|
||||
|
||||
Dictionary<string, object> settings = new Dictionary<string, object>();
|
||||
settings.Add("LockTimeoutMilliSeconds", 10000);
|
||||
data.PopulateSettings(settings);
|
||||
settings["LockTimeoutMilliSeconds"] = 15000;
|
||||
data.PopulateSettings(settings);
|
||||
data.TraceSettings();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RetryPolicyFactoryTest()
|
||||
{
|
||||
Assert.NotNull(RetryPolicyFactory.NoRetryPolicy);
|
||||
Assert.NotNull(RetryPolicyFactory.PrimaryKeyViolationRetryPolicy);
|
||||
|
||||
RetryPolicy noRetyPolicy = RetryPolicyFactory.CreateDefaultSchemaCommandRetryPolicy(useRetry: false);
|
||||
|
||||
var retryState = new RetryStateEx();
|
||||
retryState.LastError = new Exception();
|
||||
RetryPolicyFactory.DataConnectionFailureRetry(retryState);
|
||||
RetryPolicyFactory.CommandFailureRetry(retryState, "command");
|
||||
RetryPolicyFactory.CommandFailureIgnore(retryState, "command");
|
||||
RetryPolicyFactory.ElementCommandFailureIgnore(retryState);
|
||||
RetryPolicyFactory.ElementCommandFailureRetry(retryState);
|
||||
RetryPolicyFactory.CreateDatabaseCommandFailureIgnore(retryState);
|
||||
RetryPolicyFactory.CreateDatabaseCommandFailureRetry(retryState);
|
||||
RetryPolicyFactory.CommandFailureIgnore(retryState);
|
||||
RetryPolicyFactory.CommandFailureRetry(retryState);
|
||||
|
||||
var transientPolicy = new RetryPolicyFactory.TransientErrorIgnoreStrategy();
|
||||
Assert.False(transientPolicy.CanRetry(new Exception()));
|
||||
Assert.False(transientPolicy.ShouldIgnoreError(new Exception()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReliableConnectionHelperTest()
|
||||
{
|
||||
ScriptFile scriptFile;
|
||||
ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfo(out scriptFile);
|
||||
|
||||
Assert.True(ReliableConnectionHelper.IsAuthenticatingDatabaseMaster(connInfo.SqlConnection));
|
||||
|
||||
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder();
|
||||
Assert.True(ReliableConnectionHelper.IsAuthenticatingDatabaseMaster(builder));
|
||||
ReliableConnectionHelper.TryAddAlwaysOnConnectionProperties(builder, new SqlConnectionStringBuilder());
|
||||
|
||||
Assert.NotNull(ReliableConnectionHelper.GetServerName(connInfo.SqlConnection));
|
||||
Assert.NotNull(ReliableConnectionHelper.ReadServerVersion(connInfo.SqlConnection));
|
||||
|
||||
Assert.NotNull(ReliableConnectionHelper.GetAsSqlConnection(connInfo.SqlConnection));
|
||||
|
||||
ServerInfo info = ReliableConnectionHelper.GetServerVersion(connInfo.SqlConnection);
|
||||
Assert.NotNull(ReliableConnectionHelper.IsVersionGreaterThan2012RTM(info));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DataSchemaErrorTests()
|
||||
{
|
||||
var error = new DataSchemaError();
|
||||
Assert.NotNull(error);
|
||||
var isOnDisplay = error.IsOnDisplay;
|
||||
var isBuildErrorCodeDefined = error.IsBuildErrorCodeDefined;
|
||||
var buildErrorCode = error.BuildErrorCode;
|
||||
var isPriorityEditable = error.IsPriorityEditable;
|
||||
var message = error.Message;
|
||||
var exception = error.Exception;
|
||||
var prefix = error.Prefix;
|
||||
var column = error.Column;
|
||||
var line =error.Line;
|
||||
var errorCode =error.ErrorCode;
|
||||
var severity = error.Severity;
|
||||
var document = error.Document;
|
||||
|
||||
Assert.NotNull(error.ToString());
|
||||
Assert.NotNull(DataSchemaError.FormatErrorCode("ex", 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InitReliableSqlConnectionTest()
|
||||
{
|
||||
ScriptFile scriptFile;
|
||||
ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfo(out scriptFile);
|
||||
|
||||
var connection = connInfo.SqlConnection as ReliableSqlConnection;
|
||||
var command = new ReliableSqlConnection.ReliableSqlCommand(connection);
|
||||
Assert.NotNull(command.Connection);
|
||||
|
||||
var retryPolicy = connection.CommandRetryPolicy;
|
||||
connection.CommandRetryPolicy = retryPolicy;
|
||||
Assert.True(connection.CommandRetryPolicy == retryPolicy);
|
||||
connection.ChangeDatabase("master");
|
||||
Assert.True(connection.ConnectionTimeout > 0);
|
||||
connection.ClearPool();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ThrottlingReasonTests()
|
||||
{
|
||||
var reason = RetryPolicy.ThrottlingReason.Unknown;
|
||||
Assert.NotNull(reason.ThrottlingMode);
|
||||
Assert.NotNull(reason.ThrottledResources);
|
||||
|
||||
try
|
||||
{
|
||||
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder();
|
||||
builder.InitialCatalog = "master";
|
||||
builder.IntegratedSecurity = false;
|
||||
builder.DataSource = "localhost";
|
||||
builder.UserID = "invalid";
|
||||
builder.Password = "..";
|
||||
SqlConnection conn = new SqlConnection(builder.ToString());
|
||||
conn.Open();
|
||||
}
|
||||
catch (SqlException sqlException)
|
||||
{
|
||||
var exceptionReason = RetryPolicy.ThrottlingReason.FromException(sqlException);
|
||||
Assert.NotNull(exceptionReason);
|
||||
|
||||
var errorReason = RetryPolicy.ThrottlingReason.FromError(sqlException.Errors[0]);
|
||||
Assert.NotNull(errorReason);
|
||||
|
||||
var detectionStrategy = new TestDataTransferErrorDetectionStrategy();
|
||||
Assert.True(detectionStrategy.InvokeCanRetrySqlException(sqlException));
|
||||
Assert.True(detectionStrategy.CanRetry(new InvalidOperationException()));
|
||||
Assert.False(detectionStrategy.ShouldIgnoreError(new InvalidOperationException()));
|
||||
|
||||
var detectionStrategy2 = new TestSqlAzureTemporaryAndIgnorableErrorDetectionStrategy();
|
||||
Assert.NotNull(detectionStrategy2.InvokeCanRetrySqlException(sqlException));
|
||||
Assert.NotNull(detectionStrategy2.InvokeShouldIgnoreSqlException(sqlException));
|
||||
}
|
||||
|
||||
var unknownCodeReason = RetryPolicy.ThrottlingReason.FromReasonCode(-1);
|
||||
var codeReason = RetryPolicy.ThrottlingReason.FromReasonCode(2601);
|
||||
Assert.NotNull(codeReason);
|
||||
|
||||
Assert.NotNull(codeReason.IsThrottledOnDataSpace);
|
||||
Assert.NotNull(codeReason.IsThrottledOnLogSpace);
|
||||
Assert.NotNull(codeReason.IsThrottledOnLogWrite);
|
||||
Assert.NotNull(codeReason.IsThrottledOnDataRead);
|
||||
Assert.NotNull(codeReason.IsThrottledOnCPU);
|
||||
Assert.NotNull(codeReason.IsThrottledOnDatabaseSize);
|
||||
Assert.NotNull(codeReason.IsThrottledOnWorkerThreads);
|
||||
Assert.NotNull(codeReason.IsUnknown);
|
||||
Assert.NotNull(codeReason.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RetryErrorsTest()
|
||||
{
|
||||
var sqlServerRetryError = new SqlServerRetryError(
|
||||
"test message", new Exception(),
|
||||
1, 200, ErrorSeverity.Warning);
|
||||
Assert.True(sqlServerRetryError.RetryCount == 1);
|
||||
Assert.NotNull(SqlServerRetryError.FormatRetryMessage(1, TimeSpan.FromSeconds(15), new Exception()));
|
||||
Assert.NotNull(SqlServerRetryError.FormatIgnoreMessage(1, new Exception()));
|
||||
|
||||
var sqlServerError1 = new SqlServerError("test message", "document", ErrorSeverity.Warning);
|
||||
var sqlServerError2 = new SqlServerError("test message", "document", 1, ErrorSeverity.Warning);
|
||||
var sqlServerError3 = new SqlServerError(new Exception(), "document",1, ErrorSeverity.Warning);
|
||||
var sqlServerError4 = new SqlServerError("test message", "document", 1, 2, ErrorSeverity.Warning);
|
||||
var sqlServerError5 = new SqlServerError(new Exception(), "document", 1, 2, 3, ErrorSeverity.Warning);
|
||||
var sqlServerError6 = new SqlServerError("test message", "document", 1, 2, 3, ErrorSeverity.Warning);
|
||||
var sqlServerError7 = new SqlServerError("test message", new Exception(), "document", 1, 2, 3, ErrorSeverity.Warning);
|
||||
|
||||
Assert.True(SqlSchemaModelErrorCodes.IsParseErrorCode(46010));
|
||||
Assert.True(SqlSchemaModelErrorCodes.IsInterpretationErrorCode(Interpretation.InterpretationBaseCode+ 1));
|
||||
Assert.True(SqlSchemaModelErrorCodes.IsStatementFilterError(StatementFilter.StatementFilterBaseCode + 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RetryCallbackEventArgsTest()
|
||||
{
|
||||
var exception = new Exception();
|
||||
var timespan = TimeSpan.FromMinutes(1);
|
||||
|
||||
// Given a RetryCallbackEventArgs object with certain parameters
|
||||
var args = new RetryCallbackEventArgs(5, exception, timespan);
|
||||
|
||||
// If I check the properties on the object
|
||||
// Then I expect the values to be the same as the values I passed into the constructor
|
||||
Assert.Equal(5, args.RetryCount);
|
||||
Assert.Equal(exception, args.Exception);
|
||||
Assert.Equal(timespan, args.Delay);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // LIVE_CONNECTION_TESTS
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection
|
||||
/// </summary>
|
||||
public class CredentialServiceTests : IDisposable
|
||||
{
|
||||
private static readonly LinuxCredentialStore.StoreConfig config = new LinuxCredentialStore.StoreConfig()
|
||||
private static readonly StoreConfig config = new StoreConfig()
|
||||
{
|
||||
CredentialFolder = ".testsecrets",
|
||||
CredentialFile = "sqltestsecrets.json",
|
||||
@@ -61,6 +61,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection
|
||||
{
|
||||
credStore.DeletePassword(credentialId);
|
||||
credStore.DeletePassword(otherCredId);
|
||||
|
||||
#if !WINDOWS_ONLY_BUILD
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
{
|
||||
string credsFolder = ((LinuxCredentialStore)credStore).CredentialFolderPath;
|
||||
@@ -69,6 +71,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection
|
||||
Directory.Delete(credsFolder, true);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -15,23 +15,25 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Credentials
|
||||
[Fact]
|
||||
public void GetEUidReturnsInt()
|
||||
{
|
||||
#if !WINDOWS_ONLY_BUILD
|
||||
TestUtils.RunIfLinux(() =>
|
||||
{
|
||||
Assert.NotNull(Interop.Sys.GetEUid());
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetHomeDirectoryFromPwFindsHomeDir()
|
||||
{
|
||||
|
||||
#if !WINDOWS_ONLY_BUILD
|
||||
TestUtils.RunIfLinux(() =>
|
||||
{
|
||||
string userDir = LinuxCredentialStore.GetHomeDirectoryFromPw();
|
||||
Assert.StartsWith("/", userDir);
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -120,20 +120,5 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
|
||||
// verify that send result was called with a completion array
|
||||
requestContext.Verify(m => m.SendResult(It.IsAny<CompletionItem[]>()), Times.Once());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test the service initialization code path and verify nothing throws
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async void UpdateLanguageServiceOnConnection()
|
||||
{
|
||||
InitializeTestObjects();
|
||||
|
||||
AutoCompleteHelper.WorkspaceServiceInstance = workspaceService.Object;
|
||||
|
||||
ConnectionInfo connInfo = TestObjects.GetTestConnectionInfo();
|
||||
|
||||
await LanguageService.Instance.UpdateLanguageServiceOnConnection(connInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
|
||||
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Completion;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServer
|
||||
{
|
||||
public class CompletionServiceTest
|
||||
{
|
||||
[Fact]
|
||||
public void CompletionItemsShouldCreatedUsingSqlParserIfTheProcessDoesNotTimeout()
|
||||
{
|
||||
ConnectedBindingQueue bindingQueue = new ConnectedBindingQueue();
|
||||
ScriptDocumentInfo docInfo = CreateScriptDocumentInfo();
|
||||
CompletionService completionService = new CompletionService(bindingQueue);
|
||||
ConnectionInfo connectionInfo = new ConnectionInfo(null, null, null);
|
||||
bool useLowerCaseSuggestions = true;
|
||||
CompletionItem[] defaultCompletionList = AutoCompleteHelper.GetDefaultCompletionItems(docInfo, useLowerCaseSuggestions);
|
||||
|
||||
List<Declaration> declarations = new List<Declaration>();
|
||||
|
||||
var sqlParserWrapper = new Mock<ISqlParserWrapper>();
|
||||
sqlParserWrapper.Setup(x => x.FindCompletions(docInfo.ScriptParseInfo.ParseResult, docInfo.ParserLine, docInfo.ParserColumn,
|
||||
It.IsAny<IMetadataDisplayInfoProvider>())).Returns(declarations);
|
||||
completionService.SqlParserWrapper = sqlParserWrapper.Object;
|
||||
|
||||
AutoCompletionResult result = completionService.CreateCompletions(connectionInfo, docInfo, useLowerCaseSuggestions);
|
||||
Assert.NotNull(result);
|
||||
Assert.NotEqual(result.CompletionItems == null ? 0 : result.CompletionItems.Count(), defaultCompletionList.Count());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CompletionItemsShouldCreatedUsingDefaultListIfTheSqlParserProcessTimesout()
|
||||
{
|
||||
ConnectedBindingQueue bindingQueue = new ConnectedBindingQueue();
|
||||
ScriptDocumentInfo docInfo = CreateScriptDocumentInfo();
|
||||
CompletionService completionService = new CompletionService(bindingQueue);
|
||||
ConnectionInfo connectionInfo = new ConnectionInfo(null, null, null);
|
||||
bool useLowerCaseSuggestions = true;
|
||||
List<Declaration> declarations = new List<Declaration>();
|
||||
CompletionItem[] defaultCompletionList = AutoCompleteHelper.GetDefaultCompletionItems(docInfo, useLowerCaseSuggestions);
|
||||
|
||||
var sqlParserWrapper = new Mock<ISqlParserWrapper>();
|
||||
sqlParserWrapper.Setup(x => x.FindCompletions(docInfo.ScriptParseInfo.ParseResult, docInfo.ParserLine, docInfo.ParserColumn,
|
||||
It.IsAny<IMetadataDisplayInfoProvider>())).Callback(() => Thread.Sleep(LanguageService.BindingTimeout + 100)).Returns(declarations);
|
||||
completionService.SqlParserWrapper = sqlParserWrapper.Object;
|
||||
|
||||
AutoCompletionResult result = completionService.CreateCompletions(connectionInfo, docInfo, useLowerCaseSuggestions);
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(result.CompletionItems.Count(), defaultCompletionList.Count());
|
||||
Thread.Sleep(3000);
|
||||
Assert.True(connectionInfo.IntellisenseMetrics.Quantile.Any());
|
||||
}
|
||||
|
||||
private ScriptDocumentInfo CreateScriptDocumentInfo()
|
||||
{
|
||||
TextDocumentPosition doc = new TextDocumentPosition()
|
||||
{
|
||||
TextDocument = new TextDocumentIdentifier
|
||||
{
|
||||
Uri = "script file"
|
||||
},
|
||||
Position = new Position()
|
||||
{
|
||||
Line = 1,
|
||||
Character = 14
|
||||
}
|
||||
};
|
||||
ScriptFile scriptFile = new ScriptFile()
|
||||
{
|
||||
Contents = "Select * from sys.all_objects"
|
||||
};
|
||||
|
||||
ScriptParseInfo scriptParseInfo = new ScriptParseInfo() { IsConnected = true };
|
||||
ScriptDocumentInfo docInfo = new ScriptDocumentInfo(doc, scriptFile, scriptParseInfo);
|
||||
|
||||
return docInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.Collections.Generic;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServer
|
||||
{
|
||||
public class InteractionMetricsTest
|
||||
{
|
||||
[Fact]
|
||||
public void MetricsShouldGetSortedGivenUnSortedArray()
|
||||
{
|
||||
int[] metrics = new int[] { 4, 8, 1, 11, 3 };
|
||||
int[] expected = new int[] { 1, 3, 4, 8, 11 };
|
||||
InteractionMetrics<int> interactionMetrics = new InteractionMetrics<int>(metrics);
|
||||
|
||||
Assert.Equal(interactionMetrics.Metrics, expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MetricsShouldThrowExceptionGivenNullInput()
|
||||
{
|
||||
int[] metrics = null;
|
||||
Assert.Throws<ArgumentNullException>(() => new InteractionMetrics<int>(metrics));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MetricsShouldThrowExceptionGivenEmptyInput()
|
||||
{
|
||||
int[] metrics = new int[] { };
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => new InteractionMetrics<int>(metrics));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MetricsShouldNotChangeGivenSortedArray()
|
||||
{
|
||||
int[] metrics = new int[] { 1, 3, 4, 8, 11 };
|
||||
int[] expected = new int[] { 1, 3, 4, 8, 11 };
|
||||
InteractionMetrics<int> interactionMetrics = new InteractionMetrics<int>(metrics);
|
||||
|
||||
Assert.Equal(interactionMetrics.Metrics, expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MetricsShouldNotChangeGivenArrayWithOneItem()
|
||||
{
|
||||
int[] metrics = new int[] { 11 };
|
||||
int[] expected = new int[] { 11 };
|
||||
InteractionMetrics<int> interactionMetrics = new InteractionMetrics<int>(metrics);
|
||||
|
||||
Assert.Equal(interactionMetrics.Metrics, expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MetricsCalculateQuantileCorrectlyGivenSeveralUpdates()
|
||||
{
|
||||
int[] metrics = new int[] { 50, 100, 300, 500, 1000, 2000 };
|
||||
Func<string, double, double> updateValueFactory = (k, current) => current + 1;
|
||||
InteractionMetrics<double> interactionMetrics = new InteractionMetrics<double>(metrics);
|
||||
interactionMetrics.UpdateMetrics(54.4, 1, updateValueFactory);
|
||||
interactionMetrics.UpdateMetrics(345, 1, updateValueFactory);
|
||||
interactionMetrics.UpdateMetrics(23, 1, updateValueFactory);
|
||||
interactionMetrics.UpdateMetrics(51, 1, updateValueFactory);
|
||||
interactionMetrics.UpdateMetrics(500, 1, updateValueFactory);
|
||||
interactionMetrics.UpdateMetrics(4005, 1, updateValueFactory);
|
||||
interactionMetrics.UpdateMetrics(2500, 1, updateValueFactory);
|
||||
interactionMetrics.UpdateMetrics(123, 1, updateValueFactory);
|
||||
|
||||
Dictionary<string, double> quantile = interactionMetrics.Quantile;
|
||||
Assert.NotNull(quantile);
|
||||
Assert.Equal(quantile.Count, 5);
|
||||
Assert.Equal(quantile["50"], 1);
|
||||
Assert.Equal(quantile["100"], 2);
|
||||
Assert.Equal(quantile["300"], 1);
|
||||
Assert.Equal(quantile["500"], 2);
|
||||
Assert.Equal(quantile["2000"], 2);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,28 +3,14 @@
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Credentials;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.QueryExecution;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||
using Microsoft.SqlTools.Test.Utility;
|
||||
using Moq;
|
||||
using Moq.Protected;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServer
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests for the ServiceHost Language Service tests
|
||||
@@ -140,131 +126,107 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
|
||||
Assert.Equal(10, fileMarkers[1].ScriptRegion.EndColumnNumber);
|
||||
Assert.Equal(3, fileMarkers[1].ScriptRegion.EndLineNumber);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetSignatureHelpReturnsNullIfParseInfoNotInitialized()
|
||||
{
|
||||
// Given service doesn't have parseinfo intialized for a document
|
||||
const string docContent = "SELECT * FROM sys.objects";
|
||||
LanguageService service = TestObjects.GetTestLanguageService();
|
||||
var scriptFile = new ScriptFile();
|
||||
scriptFile.SetFileContents(docContent);
|
||||
|
||||
// When requesting SignatureHelp
|
||||
SignatureHelp signatureHelp = service.GetSignatureHelp(CreateDummyDocPosition(), scriptFile);
|
||||
|
||||
// Then null is returned as no parse info can be used to find the signature
|
||||
Assert.Null(signatureHelp);
|
||||
}
|
||||
|
||||
private TextDocumentPosition CreateDummyDocPosition()
|
||||
{
|
||||
return new TextDocumentPosition
|
||||
{
|
||||
TextDocument = new TextDocumentIdentifier { Uri = TestObjects.ScriptUri },
|
||||
Position = new Position
|
||||
{
|
||||
Line = 0,
|
||||
Character = 0
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region "General Language Service tests"
|
||||
|
||||
#if LIVE_CONNECTION_TESTS
|
||||
|
||||
private static void GetLiveAutoCompleteTestObjects(
|
||||
out TextDocumentPosition textDocument,
|
||||
out ScriptFile scriptFile,
|
||||
out ConnectionInfo connInfo)
|
||||
{
|
||||
textDocument = new TextDocumentPosition
|
||||
{
|
||||
TextDocument = new TextDocumentIdentifier {Uri = TestObjects.ScriptUri},
|
||||
Position = new Position
|
||||
{
|
||||
Line = 0,
|
||||
Character = 0
|
||||
}
|
||||
};
|
||||
|
||||
connInfo = TestObjects.InitLiveConnectionInfo(out scriptFile);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test the service initialization code path and verify nothing throws
|
||||
/// </summary>
|
||||
// Test is causing failures in build lab..investigating to reenable
|
||||
//[Fact]
|
||||
public void ServiceInitiailzation()
|
||||
[Fact]
|
||||
public void ServiceInitialization()
|
||||
{
|
||||
InitializeTestServices();
|
||||
try
|
||||
{
|
||||
TestObjects.InitializeTestServices();
|
||||
}
|
||||
catch (System.ArgumentException)
|
||||
{
|
||||
|
||||
}
|
||||
Assert.True(LanguageService.Instance.Context != null);
|
||||
Assert.True(LanguageService.ConnectionServiceInstance != null);
|
||||
Assert.True(LanguageService.Instance.CurrentSettings != null);
|
||||
Assert.True(LanguageService.Instance.CurrentWorkspace != null);
|
||||
|
||||
LanguageService.ConnectionServiceInstance = null;
|
||||
Assert.True(LanguageService.ConnectionServiceInstance == null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test the service initialization code path and verify nothing throws
|
||||
/// </summary>
|
||||
// Test is causing failures in build lab..investigating to reenable
|
||||
//[Fact]
|
||||
[Fact]
|
||||
public void PrepopulateCommonMetadata()
|
||||
{
|
||||
InitializeTestServices();
|
||||
ScriptFile scriptFile;
|
||||
ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfo(out scriptFile);
|
||||
|
||||
string sqlFilePath = GetTestSqlFile();
|
||||
ScriptFile scriptFile = WorkspaceService<SqlToolsSettings>.Instance.Workspace.GetFile(sqlFilePath);
|
||||
|
||||
string ownerUri = scriptFile.ClientFilePath;
|
||||
var connectionService = TestObjects.GetTestConnectionService();
|
||||
var connectionResult =
|
||||
connectionService
|
||||
.Connect(new ConnectParams()
|
||||
{
|
||||
OwnerUri = ownerUri,
|
||||
Connection = TestObjects.GetTestConnectionDetails()
|
||||
});
|
||||
|
||||
ConnectionInfo connInfo = null;
|
||||
connectionService.TryFindConnection(ownerUri, out connInfo);
|
||||
|
||||
ScriptParseInfo scriptInfo = new ScriptParseInfo();
|
||||
scriptInfo.IsConnected = true;
|
||||
ScriptParseInfo scriptInfo = new ScriptParseInfo {IsConnected = true};
|
||||
|
||||
AutoCompleteHelper.PrepopulateCommonMetadata(connInfo, scriptInfo, null);
|
||||
}
|
||||
|
||||
private string GetTestSqlFile()
|
||||
{
|
||||
string filePath = Path.Combine(
|
||||
Path.GetDirectoryName(Assembly.GetEntryAssembly().Location),
|
||||
"sqltest.sql");
|
||||
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
File.Delete(filePath);
|
||||
}
|
||||
|
||||
File.WriteAllText(filePath, "SELECT * FROM sys.objects\n");
|
||||
|
||||
return filePath;
|
||||
}
|
||||
|
||||
private void InitializeTestServices()
|
||||
{
|
||||
const string hostName = "SQL Tools Service Host";
|
||||
const string hostProfileId = "SQLToolsService";
|
||||
Version hostVersion = new Version(1,0);
|
||||
|
||||
// set up the host details and profile paths
|
||||
var hostDetails = new HostDetails(hostName, hostProfileId, hostVersion);
|
||||
SqlToolsContext sqlToolsContext = new SqlToolsContext(hostDetails);
|
||||
|
||||
// Grab the instance of the service host
|
||||
Hosting.ServiceHost serviceHost = Hosting.ServiceHost.Instance;
|
||||
|
||||
// Start the service
|
||||
serviceHost.Start().Wait();
|
||||
|
||||
// Initialize the services that will be hosted here
|
||||
WorkspaceService<SqlToolsSettings>.Instance.InitializeService(serviceHost);
|
||||
LanguageService.Instance.InitializeService(serviceHost, sqlToolsContext);
|
||||
ConnectionService.Instance.InitializeService(serviceHost);
|
||||
CredentialService.Instance.InitializeService(serviceHost);
|
||||
QueryExecutionService.Instance.InitializeService(serviceHost);
|
||||
|
||||
serviceHost.Initialize();
|
||||
}
|
||||
|
||||
private Hosting.ServiceHost GetTestServiceHost()
|
||||
{
|
||||
// set up the host details and profile paths
|
||||
var hostDetails = new HostDetails("Test Service Host", "SQLToolsService", new Version(1,0));
|
||||
SqlToolsContext context = new SqlToolsContext(hostDetails);
|
||||
|
||||
// Grab the instance of the service host
|
||||
Hosting.ServiceHost host = Hosting.ServiceHost.Instance;
|
||||
|
||||
// Start the service
|
||||
host.Start().Wait();
|
||||
|
||||
return host;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Autocomplete Tests"
|
||||
|
||||
// This test currently requires a live database connection to initialize
|
||||
// SMO connected metadata provider. Since we don't want a live DB dependency
|
||||
// in the CI unit tests this scenario is currently disabled.
|
||||
//[Fact]
|
||||
[Fact]
|
||||
public void AutoCompleteFindCompletions()
|
||||
{
|
||||
TextDocumentPosition textDocument;
|
||||
ConnectionInfo connInfo;
|
||||
ScriptFile scriptFile;
|
||||
Common.GetAutoCompleteTestObjects(out textDocument, out scriptFile, out connInfo);
|
||||
GetLiveAutoCompleteTestObjects(out textDocument, out scriptFile, out connInfo);
|
||||
|
||||
textDocument.Position.Character = 7;
|
||||
scriptFile.Contents = "select ";
|
||||
@@ -278,32 +240,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
|
||||
Assert.True(completions.Length > 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a mock db command that returns a predefined result set
|
||||
/// </summary>
|
||||
public static DbCommand CreateTestCommand(Dictionary<string, string>[][] data)
|
||||
{
|
||||
var commandMock = new Mock<DbCommand> { CallBase = true };
|
||||
var commandMockSetup = commandMock.Protected()
|
||||
.Setup<DbDataReader>("ExecuteDbDataReader", It.IsAny<CommandBehavior>());
|
||||
|
||||
commandMockSetup.Returns(new TestDbDataReader(data));
|
||||
|
||||
return commandMock.Object;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a mock db connection that returns predefined data when queried for a result set
|
||||
/// </summary>
|
||||
public DbConnection CreateMockDbConnection(Dictionary<string, string>[][] data)
|
||||
{
|
||||
var connectionMock = new Mock<DbConnection> { CallBase = true };
|
||||
connectionMock.Protected()
|
||||
.Setup<DbCommand>("CreateDbCommand")
|
||||
.Returns(CreateTestCommand(data));
|
||||
|
||||
return connectionMock.Object;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -0,0 +1,376 @@
|
||||
//
|
||||
// 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.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Binder;
|
||||
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Parser;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.QueryExecution;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||
using Microsoft.SqlTools.Test.Utility;
|
||||
using Location = Microsoft.SqlTools.ServiceLayer.Workspace.Contracts.Location;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests for the language service peek definition/ go to definition feature
|
||||
/// </summary>
|
||||
public class PeekDefinitionTests
|
||||
{
|
||||
private const int TaskTimeout = 30000;
|
||||
|
||||
private readonly string testScriptUri = TestObjects.ScriptUri;
|
||||
|
||||
private readonly string testConnectionKey = "testdbcontextkey";
|
||||
|
||||
private Mock<ConnectedBindingQueue> bindingQueue;
|
||||
|
||||
private Mock<WorkspaceService<SqlToolsSettings>> workspaceService;
|
||||
|
||||
private Mock<RequestContext<Location[]>> requestContext;
|
||||
|
||||
private Mock<IBinder> binder;
|
||||
|
||||
private TextDocumentPosition textDocument;
|
||||
|
||||
private const string OwnerUri = "testFile1";
|
||||
|
||||
private void InitializeTestObjects()
|
||||
{
|
||||
// initial cursor position in the script file
|
||||
textDocument = new TextDocumentPosition
|
||||
{
|
||||
TextDocument = new TextDocumentIdentifier {Uri = this.testScriptUri},
|
||||
Position = new Position
|
||||
{
|
||||
Line = 0,
|
||||
Character = 23
|
||||
}
|
||||
};
|
||||
|
||||
// default settings are stored in the workspace service
|
||||
WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings = new SqlToolsSettings();
|
||||
|
||||
// set up file for returning the query
|
||||
var fileMock = new Mock<ScriptFile>();
|
||||
fileMock.SetupGet(file => file.Contents).Returns(Common.StandardQuery);
|
||||
fileMock.SetupGet(file => file.ClientFilePath).Returns(this.testScriptUri);
|
||||
|
||||
// set up workspace mock
|
||||
workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
|
||||
.Returns(fileMock.Object);
|
||||
|
||||
// setup binding queue mock
|
||||
bindingQueue = new Mock<ConnectedBindingQueue>();
|
||||
bindingQueue.Setup(q => q.AddConnectionContext(It.IsAny<ConnectionInfo>()))
|
||||
.Returns(this.testConnectionKey);
|
||||
|
||||
// inject mock instances into the Language Service
|
||||
LanguageService.WorkspaceServiceInstance = workspaceService.Object;
|
||||
LanguageService.ConnectionServiceInstance = TestObjects.GetTestConnectionService();
|
||||
ConnectionInfo connectionInfo = TestObjects.GetTestConnectionInfo();
|
||||
LanguageService.ConnectionServiceInstance.OwnerToConnectionMap.Add(this.testScriptUri, connectionInfo);
|
||||
LanguageService.Instance.BindingQueue = bindingQueue.Object;
|
||||
|
||||
// setup the mock for SendResult
|
||||
requestContext = new Mock<RequestContext<Location[]>>();
|
||||
requestContext.Setup(rc => rc.SendResult(It.IsAny<Location[]>()))
|
||||
.Returns(Task.FromResult(0));
|
||||
|
||||
// setup the IBinder mock
|
||||
binder = new Mock<IBinder>();
|
||||
binder.Setup(b => b.Bind(
|
||||
It.IsAny<IEnumerable<ParseResult>>(),
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<BindMode>()));
|
||||
|
||||
var testScriptParseInfo = new ScriptParseInfo();
|
||||
LanguageService.Instance.AddOrUpdateScriptParseInfo(this.testScriptUri, testScriptParseInfo);
|
||||
testScriptParseInfo.IsConnected = true;
|
||||
testScriptParseInfo.ConnectionKey = LanguageService.Instance.BindingQueue.AddConnectionContext(connectionInfo);
|
||||
|
||||
// setup the binding context object
|
||||
ConnectedBindingContext bindingContext = new ConnectedBindingContext();
|
||||
bindingContext.Binder = binder.Object;
|
||||
bindingContext.MetadataDisplayInfoProvider = new MetadataDisplayInfoProvider();
|
||||
LanguageService.Instance.BindingQueue.BindingContextMap.Add(testScriptParseInfo.ConnectionKey, bindingContext);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Tests the definition event handler. When called with no active connection, no definition is sent
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void DefinitionsHandlerWithNoConnectionTest()
|
||||
{
|
||||
InitializeTestObjects();
|
||||
// request the completion list
|
||||
Task handleCompletion = LanguageService.HandleDefinitionRequest(textDocument, requestContext.Object);
|
||||
handleCompletion.Wait(TaskTimeout);
|
||||
|
||||
// verify that send result was not called
|
||||
requestContext.Verify(m => m.SendResult(It.IsAny<Location[]>()), Times.Never());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests creating location objects on windows and non-windows systems
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetLocationFromFileForValidFilePathTest()
|
||||
{
|
||||
String filePath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "C:\\test\\script.sql" : "/test/script.sql";
|
||||
PeekDefinition peekDefinition = new PeekDefinition(null);
|
||||
Location[] locations = peekDefinition.GetLocationFromFile(filePath, 0);
|
||||
|
||||
String expectedFilePath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "file:///C:/test/script.sql" : "file:/test/script.sql";
|
||||
Assert.Equal(locations[0].Uri, expectedFilePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test PeekDefinition.GetSchemaFromDatabaseQualifiedName with a valid database name
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetSchemaFromDatabaseQualifiedNameWithValidNameTest()
|
||||
{
|
||||
PeekDefinition peekDefinition = new PeekDefinition(null);
|
||||
string validDatabaseQualifiedName = "master.test.test_table";
|
||||
string objectName = "test_table";
|
||||
string expectedSchemaName = "test";
|
||||
|
||||
string actualSchemaName = peekDefinition.GetSchemaFromDatabaseQualifiedName(validDatabaseQualifiedName, objectName);
|
||||
Assert.Equal(actualSchemaName, expectedSchemaName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test PeekDefinition.GetSchemaFromDatabaseQualifiedName with a valid object name and no schema
|
||||
/// </summary>
|
||||
|
||||
[Fact]
|
||||
public void GetSchemaFromDatabaseQualifiedNameWithNoSchemaTest()
|
||||
{
|
||||
PeekDefinition peekDefinition = new PeekDefinition(null);
|
||||
string validDatabaseQualifiedName = "test_table";
|
||||
string objectName = "test_table";
|
||||
string expectedSchemaName = "dbo";
|
||||
|
||||
string actualSchemaName = peekDefinition.GetSchemaFromDatabaseQualifiedName(validDatabaseQualifiedName, objectName);
|
||||
Assert.Equal(actualSchemaName, expectedSchemaName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test PeekDefinition.GetSchemaFromDatabaseQualifiedName with a invalid database name
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetSchemaFromDatabaseQualifiedNameWithInvalidNameTest()
|
||||
{
|
||||
PeekDefinition peekDefinition = new PeekDefinition(null);
|
||||
string validDatabaseQualifiedName = "x.y.z";
|
||||
string objectName = "test_table";
|
||||
string expectedSchemaName = "dbo";
|
||||
|
||||
string actualSchemaName = peekDefinition.GetSchemaFromDatabaseQualifiedName(validDatabaseQualifiedName, objectName);
|
||||
Assert.Equal(actualSchemaName, expectedSchemaName);
|
||||
}
|
||||
|
||||
#if LIVE_CONNECTION_TESTS
|
||||
/// <summary>
|
||||
/// Test get definition for a table object with active connection
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetValidTableDefinitionTest()
|
||||
{
|
||||
// Get live connectionInfo
|
||||
ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfoForDefinition();
|
||||
PeekDefinition peekDefinition = new PeekDefinition(connInfo);
|
||||
string objectName = "test_table";
|
||||
string schemaName = null;
|
||||
string objectType = "TABLE";
|
||||
|
||||
// Get locations for valid table object
|
||||
Location[] locations = peekDefinition.GetSqlObjectDefinition(peekDefinition.GetTableScripts, objectName, schemaName, objectType);
|
||||
Assert.NotNull(locations);
|
||||
Cleanup(locations);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test get definition for a invalid table object with active connection
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetTableDefinitionInvalidObjectTest()
|
||||
{
|
||||
// Get live connectionInfo
|
||||
ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfoForDefinition();
|
||||
PeekDefinition peekDefinition = new PeekDefinition(connInfo);
|
||||
string objectName = "test_invalid";
|
||||
string schemaName = null;
|
||||
string objectType = "TABLE";
|
||||
|
||||
// Get locations for invalid table object
|
||||
Location[] locations = peekDefinition.GetSqlObjectDefinition(peekDefinition.GetTableScripts, objectName, schemaName, objectType);
|
||||
Assert.Null(locations);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test get definition for a valid table object with schema and active connection
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetTableDefinitionWithSchemaTest()
|
||||
{
|
||||
// Get live connectionInfo
|
||||
ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfoForDefinition();
|
||||
PeekDefinition peekDefinition = new PeekDefinition(connInfo);
|
||||
string objectName = "test_table";
|
||||
string schemaName = "dbo";
|
||||
string objectType = "TABLE";
|
||||
|
||||
// Get locations for valid table object with schema name
|
||||
Location[] locations = peekDefinition.GetSqlObjectDefinition(peekDefinition.GetTableScripts, objectName, schemaName, objectType);
|
||||
Assert.NotNull(locations);
|
||||
Cleanup(locations);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test GetDefinition with an unsupported type(function)
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetUnsupportedDefinitionForFullScript()
|
||||
{
|
||||
|
||||
ScriptFile scriptFile;
|
||||
TextDocumentPosition textDocument = new TextDocumentPosition
|
||||
{
|
||||
TextDocument = new TextDocumentIdentifier { Uri = OwnerUri },
|
||||
Position = new Position
|
||||
{
|
||||
Line = 0,
|
||||
Character = 20
|
||||
}
|
||||
};
|
||||
ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfo(out scriptFile);
|
||||
scriptFile.Contents = "select * from dbo.func ()";
|
||||
|
||||
var languageService = LanguageService.Instance;
|
||||
ScriptParseInfo scriptInfo = new ScriptParseInfo { IsConnected = true };
|
||||
languageService.ScriptParseInfoMap.Add(OwnerUri, scriptInfo);
|
||||
|
||||
var locations = languageService.GetDefinition(textDocument, scriptFile, connInfo);
|
||||
Assert.Null(locations);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test get definition for a view object with active connection
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetValidViewDefinitionTest()
|
||||
{
|
||||
ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfoForDefinition();
|
||||
PeekDefinition peekDefinition = new PeekDefinition(connInfo);
|
||||
string objectName = "objects";
|
||||
string schemaName = "sys";
|
||||
string objectType = "VIEW";
|
||||
|
||||
Location[] locations = peekDefinition.GetSqlObjectDefinition(peekDefinition.GetViewScripts, objectName, schemaName, objectType);
|
||||
Assert.NotNull(locations);
|
||||
Cleanup(locations);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test get definition for an invalid view object with no schema name and with active connection
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetViewDefinitionInvalidObjectTest()
|
||||
{
|
||||
ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfoForDefinition();
|
||||
PeekDefinition peekDefinition = new PeekDefinition(connInfo);
|
||||
string objectName = "objects";
|
||||
string schemaName = null;
|
||||
string objectType = "VIEW";
|
||||
|
||||
Location[] locations = peekDefinition.GetSqlObjectDefinition(peekDefinition.GetViewScripts, objectName, schemaName, objectType);
|
||||
Assert.Null(locations);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test get definition for a stored procedure object with active connection
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetStoredProcedureDefinitionTest()
|
||||
{
|
||||
ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfoForDefinition();
|
||||
PeekDefinition peekDefinition = new PeekDefinition(connInfo);
|
||||
string objectName = "SP1";
|
||||
string schemaName = "dbo";
|
||||
string objectType = "PROCEDURE";
|
||||
|
||||
Location[] locations = peekDefinition.GetSqlObjectDefinition(peekDefinition.GetStoredProcedureScripts, objectName, schemaName, objectType);
|
||||
Assert.NotNull(locations);
|
||||
Cleanup(locations);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test get definition for a stored procedure object that does not exist with active connection
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetStoredProcedureDefinitionFailureTest()
|
||||
{
|
||||
ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfoForDefinition();
|
||||
PeekDefinition peekDefinition = new PeekDefinition(connInfo);
|
||||
string objectName = "SP2";
|
||||
string schemaName = "dbo";
|
||||
string objectType = "PROCEDURE";
|
||||
|
||||
Location[] locations = peekDefinition.GetSqlObjectDefinition(peekDefinition.GetStoredProcedureScripts, objectName, schemaName, objectType);
|
||||
Assert.Null(locations);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test get definition for a stored procedure object with active connection and no schema
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetStoredProcedureDefinitionWithoutSchemaTest()
|
||||
{
|
||||
ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfoForDefinition();
|
||||
PeekDefinition peekDefinition = new PeekDefinition(connInfo);
|
||||
string objectName = "SP1";
|
||||
string schemaName = null;
|
||||
string objectType = "PROCEDURE";
|
||||
|
||||
Location[] locations = peekDefinition.GetSqlObjectDefinition(peekDefinition.GetStoredProcedureScripts, objectName, schemaName, objectType);
|
||||
Assert.NotNull(locations);
|
||||
Cleanup(locations);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to clean up script files
|
||||
/// </summary>
|
||||
private void Cleanup(Location[] locations)
|
||||
{
|
||||
Uri fileUri = new Uri(locations[0].Uri);
|
||||
if (File.Exists(fileUri.LocalPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(fileUri.LocalPath);
|
||||
}
|
||||
catch(Exception)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Completion;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServer
|
||||
{
|
||||
public class SqlCompletionItemTests
|
||||
{
|
||||
[Fact]
|
||||
public void InsertTextShouldIncludeBracketGivenNameWithSpace()
|
||||
{
|
||||
string declarationTitle = "name with space";
|
||||
string expected = "[" + declarationTitle + "]";
|
||||
|
||||
DeclarationType declarationType = DeclarationType.Table;
|
||||
string tokenText = "";
|
||||
SqlCompletionItem item = new SqlCompletionItem(declarationTitle, declarationType, tokenText);
|
||||
CompletionItem completionItem = item.CreateCompletionItem(0, 1, 2);
|
||||
|
||||
Assert.True(completionItem.InsertText.StartsWith("[") && completionItem.InsertText.EndsWith("]"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InsertTextShouldIncludeBracketGivenNameWithSpecialCharacter()
|
||||
{
|
||||
string declarationTitle = "name @";
|
||||
string expected = "[" + declarationTitle + "]";
|
||||
DeclarationType declarationType = DeclarationType.Table;
|
||||
string tokenText = "";
|
||||
SqlCompletionItem item = new SqlCompletionItem(declarationTitle, declarationType, tokenText);
|
||||
CompletionItem completionItem = item.CreateCompletionItem(0, 1, 2);
|
||||
|
||||
Assert.Equal(completionItem.InsertText, expected);
|
||||
Assert.Equal(completionItem.Detail, declarationTitle);
|
||||
Assert.Equal(completionItem.Label, declarationTitle);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LabelShouldIncludeBracketGivenTokenWithBracket()
|
||||
{
|
||||
string declarationTitle = "name";
|
||||
string expected = "[" + declarationTitle + "]";
|
||||
DeclarationType declarationType = DeclarationType.Table;
|
||||
string tokenText = "[";
|
||||
SqlCompletionItem item = new SqlCompletionItem(declarationTitle, declarationType, tokenText);
|
||||
CompletionItem completionItem = item.CreateCompletionItem(0, 1, 2);
|
||||
|
||||
Assert.Equal(completionItem.Label, expected);
|
||||
Assert.Equal(completionItem.InsertText, expected);
|
||||
Assert.Equal(completionItem.Detail, expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LabelShouldIncludeBracketGivenTokenWithBrackets()
|
||||
{
|
||||
string declarationTitle = "name";
|
||||
string expected = "[" + declarationTitle + "]";
|
||||
DeclarationType declarationType = DeclarationType.Table;
|
||||
string tokenText = "[]";
|
||||
SqlCompletionItem item = new SqlCompletionItem(declarationTitle, declarationType, tokenText);
|
||||
CompletionItem completionItem = item.CreateCompletionItem(0, 1, 2);
|
||||
|
||||
Assert.Equal(completionItem.Label, expected);
|
||||
Assert.Equal(completionItem.InsertText, expected);
|
||||
Assert.Equal(completionItem.Detail, expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LabelShouldIncludeBracketGivenSqlObjectNameWithBracket()
|
||||
{
|
||||
string declarationTitle = @"Bracket\[";
|
||||
string expected = "[" + declarationTitle + "]";
|
||||
DeclarationType declarationType = DeclarationType.Table;
|
||||
string tokenText = "";
|
||||
SqlCompletionItem item = new SqlCompletionItem(declarationTitle, declarationType, tokenText);
|
||||
CompletionItem completionItem = item.CreateCompletionItem(0, 1, 2);
|
||||
|
||||
Assert.Equal(completionItem.Label, declarationTitle);
|
||||
Assert.Equal(completionItem.InsertText, expected);
|
||||
Assert.Equal(completionItem.Detail, declarationTitle);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LabelShouldIncludeBracketGivenSqlObjectNameWithBracketAndTokenWithBracket()
|
||||
{
|
||||
string declarationTitle = @"Bracket\[";
|
||||
string expected = "[" + declarationTitle + "]";
|
||||
DeclarationType declarationType = DeclarationType.Table;
|
||||
string tokenText = "[]";
|
||||
SqlCompletionItem item = new SqlCompletionItem(declarationTitle, declarationType, tokenText);
|
||||
CompletionItem completionItem = item.CreateCompletionItem(0, 1, 2);
|
||||
|
||||
Assert.Equal(completionItem.Label, expected);
|
||||
Assert.Equal(completionItem.InsertText, expected);
|
||||
Assert.Equal(completionItem.Detail, expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LabelShouldNotIncludeBracketGivenNameWithBrackets()
|
||||
{
|
||||
string declarationTitle = "[name]";
|
||||
string expected = declarationTitle;
|
||||
DeclarationType declarationType = DeclarationType.Table;
|
||||
string tokenText = "[]";
|
||||
SqlCompletionItem item = new SqlCompletionItem(declarationTitle, declarationType, tokenText);
|
||||
CompletionItem completionItem = item.CreateCompletionItem(0, 1, 2);
|
||||
|
||||
Assert.Equal(completionItem.Label, expected);
|
||||
Assert.Equal(completionItem.InsertText, expected);
|
||||
Assert.Equal(completionItem.Detail, expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LabelShouldIncludeBracketGivenNameWithOneBracket()
|
||||
{
|
||||
string declarationTitle = "[name";
|
||||
string expected = "[" + declarationTitle + "]";
|
||||
DeclarationType declarationType = DeclarationType.Table;
|
||||
string tokenText = "[]";
|
||||
SqlCompletionItem item = new SqlCompletionItem(declarationTitle, declarationType, tokenText);
|
||||
CompletionItem completionItem = item.CreateCompletionItem(0, 1, 2);
|
||||
|
||||
Assert.Equal(completionItem.Label, expected);
|
||||
Assert.Equal(completionItem.InsertText, expected);
|
||||
Assert.Equal(completionItem.Detail, expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KindShouldBeModuleGivenSchemaDeclarationType()
|
||||
{
|
||||
CompletionItemKind expectedType = CompletionItemKind.Module;
|
||||
DeclarationType declarationType = DeclarationType.Schema;
|
||||
ValidateDeclarationType(declarationType, expectedType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KindShouldBeFieldGivenColumnDeclarationType()
|
||||
{
|
||||
CompletionItemKind expectedType = CompletionItemKind.Field;
|
||||
DeclarationType declarationType = DeclarationType.Column;
|
||||
ValidateDeclarationType(declarationType, expectedType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KindShouldBeFileGivenTableDeclarationType()
|
||||
{
|
||||
CompletionItemKind expectedType = CompletionItemKind.File;
|
||||
DeclarationType declarationType = DeclarationType.Table;
|
||||
ValidateDeclarationType(declarationType, expectedType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KindShouldBeFileGivenViewDeclarationType()
|
||||
{
|
||||
CompletionItemKind expectedType = CompletionItemKind.File;
|
||||
DeclarationType declarationType = DeclarationType.View;
|
||||
ValidateDeclarationType(declarationType, expectedType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KindShouldBeMethodGivenDatabaseDeclarationType()
|
||||
{
|
||||
CompletionItemKind expectedType = CompletionItemKind.Method;
|
||||
DeclarationType declarationType = DeclarationType.Database;
|
||||
ValidateDeclarationType(declarationType, expectedType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KindShouldBeValueGivenScalarValuedFunctionDeclarationType()
|
||||
{
|
||||
CompletionItemKind expectedType = CompletionItemKind.Value;
|
||||
DeclarationType declarationType = DeclarationType.ScalarValuedFunction;
|
||||
ValidateDeclarationType(declarationType, expectedType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KindShouldBeValueGivenTableValuedFunctionDeclarationType()
|
||||
{
|
||||
CompletionItemKind expectedType = CompletionItemKind.Value;
|
||||
DeclarationType declarationType = DeclarationType.TableValuedFunction;
|
||||
ValidateDeclarationType(declarationType, expectedType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KindShouldBeUnitGivenUnknownDeclarationType()
|
||||
{
|
||||
CompletionItemKind expectedType = CompletionItemKind.Unit;
|
||||
DeclarationType declarationType = DeclarationType.XmlIndex;
|
||||
ValidateDeclarationType(declarationType, expectedType);
|
||||
}
|
||||
|
||||
private void ValidateDeclarationType(DeclarationType declarationType, CompletionItemKind expectedType)
|
||||
{
|
||||
string declarationTitle = "name";
|
||||
string tokenText = "";
|
||||
SqlCompletionItem item = new SqlCompletionItem(declarationTitle, declarationType, tokenText);
|
||||
CompletionItem completionItem = item.CreateCompletionItem(0, 1, 2);
|
||||
|
||||
|
||||
Assert.Equal(completionItem.Kind, expectedType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,8 +32,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
|
||||
// If:
|
||||
// ... I request a query (doesn't matter what kind) and execute it
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, workspaceService.Object);
|
||||
var executeParams = new QueryExecuteParams { QuerySelection = Common.GetSubSectionDocument(), OwnerUri = Common.OwnerUri };
|
||||
var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService.Object);
|
||||
var executeParams = new QueryExecuteParams { QuerySelection = Common.SubsectionDocument, OwnerUri = Common.OwnerUri };
|
||||
var executeRequest =
|
||||
RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(null, QueryExecuteCompleteEvent.Type, null, null);
|
||||
await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
|
||||
@@ -44,7 +44,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
var cancelParams = new QueryCancelParams {OwnerUri = Common.OwnerUri};
|
||||
QueryCancelResult result = null;
|
||||
var cancelRequest = GetQueryCancelResultContextMock(qcr => result = qcr, null);
|
||||
queryService.HandleCancelRequest(cancelParams, cancelRequest.Object).Wait();
|
||||
await queryService.HandleCancelRequest(cancelParams, cancelRequest.Object);
|
||||
|
||||
// Then:
|
||||
// ... I should have seen a successful event (no messages)
|
||||
@@ -68,7 +68,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
.Returns(fileMock.Object);
|
||||
// If:
|
||||
// ... I request a query (doesn't matter what kind) and wait for execution
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, workspaceService.Object);
|
||||
var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService.Object);
|
||||
var executeParams = new QueryExecuteParams {QuerySelection = Common.WholeDocument, OwnerUri = Common.OwnerUri};
|
||||
var executeRequest =
|
||||
RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(null, QueryExecuteCompleteEvent.Type, null, null);
|
||||
@@ -91,17 +91,17 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async void CancelNonExistantTest()
|
||||
public async Task CancelNonExistantTest()
|
||||
{
|
||||
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
// If:
|
||||
// ... I request to cancel a query that doesn't exist
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), false, workspaceService.Object);
|
||||
var queryService = Common.GetPrimedExecutionService(null, false, false, workspaceService.Object);
|
||||
var cancelParams = new QueryCancelParams {OwnerUri = "Doesn't Exist"};
|
||||
QueryCancelResult result = null;
|
||||
var cancelRequest = GetQueryCancelResultContextMock(qcr => result = qcr, null);
|
||||
queryService.HandleCancelRequest(cancelParams, cancelRequest.Object).Wait();
|
||||
await queryService.HandleCancelRequest(cancelParams, cancelRequest.Object);
|
||||
|
||||
// Then:
|
||||
// ... I should have seen a result event with an error message
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
//
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.IO;
|
||||
using System.Data.SqlClient;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
|
||||
using Microsoft.SqlServer.Management.Common;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage;
|
||||
@@ -29,37 +26,61 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
{
|
||||
public class Common
|
||||
{
|
||||
public const SelectionData WholeDocument = null;
|
||||
|
||||
public const string StandardQuery = "SELECT * FROM sys.objects";
|
||||
#region Constants
|
||||
|
||||
public const string InvalidQuery = "SELECT *** FROM sys.objects";
|
||||
|
||||
public const string NoOpQuery = "-- No ops here, just us chickens.";
|
||||
|
||||
public const string UdtQuery = "SELECT hierarchyid::Parse('/')";
|
||||
public const int Ordinal = 100; // We'll pick something other than default(int)
|
||||
|
||||
public const string OwnerUri = "testFile";
|
||||
|
||||
public const int StandardRows = 5;
|
||||
|
||||
public const int StandardColumns = 5;
|
||||
|
||||
public static string TestServer { get; set; }
|
||||
public const string StandardQuery = "SELECT * FROM sys.objects";
|
||||
|
||||
public static string TestDatabase { get; set; }
|
||||
public const int StandardRows = 5;
|
||||
|
||||
static Common()
|
||||
public const string UdtQuery = "SELECT hierarchyid::Parse('/')";
|
||||
|
||||
public const SelectionData WholeDocument = null;
|
||||
|
||||
public static readonly ConnectionDetails StandardConnectionDetails = new ConnectionDetails
|
||||
{
|
||||
TestServer = "sqltools11";
|
||||
TestDatabase = "master";
|
||||
}
|
||||
DatabaseName = "123",
|
||||
Password = "456",
|
||||
ServerName = "789",
|
||||
UserName = "012"
|
||||
};
|
||||
|
||||
public static readonly SelectionData SubsectionDocument = new SelectionData(0, 0, 2, 2);
|
||||
|
||||
#endregion
|
||||
|
||||
public static Dictionary<string, string>[] StandardTestData
|
||||
{
|
||||
get { return GetTestData(StandardRows, StandardColumns); }
|
||||
}
|
||||
|
||||
#region Public Methods
|
||||
|
||||
public static Batch GetBasicExecutedBatch()
|
||||
{
|
||||
Batch batch = new Batch(StandardQuery, SubsectionDocument, 1, GetFileStreamFactory(new Dictionary<string, byte[]>()));
|
||||
batch.Execute(CreateTestConnection(new[] {StandardTestData}, false), CancellationToken.None).Wait();
|
||||
return batch;
|
||||
}
|
||||
|
||||
public static Query GetBasicExecutedQuery()
|
||||
{
|
||||
ConnectionInfo ci = CreateTestConnectionInfo(new[] {StandardTestData}, false);
|
||||
Query query = new Query(StandardQuery, ci, new QueryExecutionSettings(), GetFileStreamFactory(new Dictionary<string, byte[]>()));
|
||||
query.Execute();
|
||||
query.ExecutionTask.Wait();
|
||||
return query;
|
||||
}
|
||||
|
||||
public static Dictionary<string, string>[] GetTestData(int columns, int rows)
|
||||
{
|
||||
Dictionary<string, string>[] output = new Dictionary<string, string>[rows];
|
||||
@@ -76,92 +97,39 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
return output;
|
||||
}
|
||||
|
||||
public static SelectionData GetSubSectionDocument()
|
||||
public static async Task AwaitExecution(QueryExecutionService service, QueryExecuteParams qeParams,
|
||||
RequestContext<QueryExecuteResult> requestContext)
|
||||
{
|
||||
return new SelectionData(0, 0, 2, 2);
|
||||
await service.HandleExecuteRequest(qeParams, requestContext);
|
||||
if (service.ActiveQueries.ContainsKey(qeParams.OwnerUri) && service.ActiveQueries[qeParams.OwnerUri].ExecutionTask != null)
|
||||
{
|
||||
await service.ActiveQueries[qeParams.OwnerUri].ExecutionTask;
|
||||
}
|
||||
}
|
||||
|
||||
public static Batch GetBasicExecutedBatch()
|
||||
{
|
||||
Batch batch = new Batch(StandardQuery, 0, 0, 2, 2, GetFileStreamFactory());
|
||||
batch.Execute(CreateTestConnection(new[] {StandardTestData}, false), CancellationToken.None).Wait();
|
||||
return batch;
|
||||
}
|
||||
|
||||
public static Query GetBasicExecutedQuery()
|
||||
{
|
||||
ConnectionInfo ci = CreateTestConnectionInfo(new[] {StandardTestData}, false);
|
||||
Query query = new Query(StandardQuery, ci, new QueryExecutionSettings(), GetFileStreamFactory());
|
||||
query.Execute();
|
||||
query.ExecutionTask.Wait();
|
||||
return query;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region FileStreamWriteMocking
|
||||
|
||||
public static IFileStreamFactory GetFileStreamFactory()
|
||||
public static IFileStreamFactory GetFileStreamFactory(Dictionary<string, byte[]> storage)
|
||||
{
|
||||
Mock<IFileStreamFactory> mock = new Mock<IFileStreamFactory>();
|
||||
mock.Setup(fsf => fsf.CreateFile())
|
||||
.Returns(() =>
|
||||
{
|
||||
string fileName = Guid.NewGuid().ToString();
|
||||
storage.Add(fileName, new byte[8192]);
|
||||
return fileName;
|
||||
});
|
||||
mock.Setup(fsf => fsf.GetReader(It.IsAny<string>()))
|
||||
.Returns(new ServiceBufferFileStreamReader(new InMemoryWrapper(), It.IsAny<string>()));
|
||||
.Returns<string>(output => new ServiceBufferFileStreamReader(new MemoryStream(storage[output])));
|
||||
mock.Setup(fsf => fsf.GetWriter(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>()))
|
||||
.Returns(new ServiceBufferFileStreamWriter(new InMemoryWrapper(), It.IsAny<string>(), 1024,
|
||||
1024));
|
||||
.Returns<string, int, int>((output, chars, xml) => new ServiceBufferFileStreamWriter(
|
||||
new MemoryStream(storage[output]), chars, xml));
|
||||
|
||||
return mock.Object;
|
||||
}
|
||||
|
||||
public class InMemoryWrapper : IFileStreamWrapper
|
||||
{
|
||||
private readonly byte[] storage = new byte[8192];
|
||||
private readonly MemoryStream memoryStream;
|
||||
private bool readingOnly;
|
||||
|
||||
public InMemoryWrapper()
|
||||
{
|
||||
memoryStream = new MemoryStream(storage);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// We'll dispose this via a special method
|
||||
}
|
||||
|
||||
public void Init(string fileName, int bufferSize, FileAccess fAccess)
|
||||
{
|
||||
readingOnly = fAccess == FileAccess.Read;
|
||||
}
|
||||
|
||||
public int ReadData(byte[] buffer, int bytes)
|
||||
{
|
||||
return ReadData(buffer, bytes, memoryStream.Position);
|
||||
}
|
||||
|
||||
public int ReadData(byte[] buffer, int bytes, long fileOffset)
|
||||
{
|
||||
memoryStream.Seek(fileOffset, SeekOrigin.Begin);
|
||||
return memoryStream.Read(buffer, 0, bytes);
|
||||
}
|
||||
|
||||
public int WriteData(byte[] buffer, int bytes)
|
||||
{
|
||||
if (readingOnly) { throw new InvalidOperationException(); }
|
||||
memoryStream.Write(buffer, 0, bytes);
|
||||
memoryStream.Flush();
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public void Flush()
|
||||
{
|
||||
if (readingOnly) { throw new InvalidOperationException(); }
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
memoryStream.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region DbConnection Mocking
|
||||
@@ -193,7 +161,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
var connectionMock = new Mock<DbConnection> { CallBase = true };
|
||||
connectionMock.Protected()
|
||||
.Setup<DbCommand>("CreateDbCommand")
|
||||
.Returns(CreateTestCommand(data, throwOnRead));
|
||||
.Returns(() => CreateTestCommand(data, throwOnRead));
|
||||
connectionMock.Setup(dbc => dbc.Open())
|
||||
.Callback(() => connectionMock.SetupGet(dbc => dbc.State).Returns(ConnectionState.Open));
|
||||
connectionMock.Setup(dbc => dbc.Close())
|
||||
@@ -206,90 +174,45 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
{
|
||||
var mockFactory = new Mock<ISqlConnectionFactory>();
|
||||
mockFactory.Setup(factory => factory.CreateSqlConnection(It.IsAny<string>()))
|
||||
.Returns(CreateTestConnection(data, throwOnRead));
|
||||
.Returns(() => CreateTestConnection(data, throwOnRead));
|
||||
|
||||
return mockFactory.Object;
|
||||
}
|
||||
|
||||
public static ConnectionInfo CreateTestConnectionInfo(Dictionary<string, string>[][] data, bool throwOnRead)
|
||||
{
|
||||
// Create connection info
|
||||
ConnectionDetails connDetails = new ConnectionDetails
|
||||
{
|
||||
UserName = "sa",
|
||||
Password = "Yukon900",
|
||||
DatabaseName = Common.TestDatabase,
|
||||
ServerName = Common.TestServer
|
||||
};
|
||||
|
||||
return new ConnectionInfo(CreateMockFactory(data, throwOnRead), OwnerUri, connDetails);
|
||||
return new ConnectionInfo(CreateMockFactory(data, throwOnRead), OwnerUri, StandardConnectionDetails);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Service Mocking
|
||||
|
||||
public static void GetAutoCompleteTestObjects(
|
||||
out TextDocumentPosition textDocument,
|
||||
out ScriptFile scriptFile,
|
||||
out ConnectionInfo connInfo
|
||||
)
|
||||
|
||||
public static QueryExecutionService GetPrimedExecutionService(Dictionary<string, string>[][] data, bool isConnected, bool throwOnRead, WorkspaceService<SqlToolsSettings> workspaceService)
|
||||
{
|
||||
textDocument = new TextDocumentPosition
|
||||
{
|
||||
TextDocument = new TextDocumentIdentifier {Uri = OwnerUri},
|
||||
Position = new Position
|
||||
{
|
||||
Line = 0,
|
||||
Character = 0
|
||||
}
|
||||
};
|
||||
// Create a place for the temp "files" to be written
|
||||
Dictionary<string, byte[]> storage = new Dictionary<string, byte[]>();
|
||||
|
||||
connInfo = Common.CreateTestConnectionInfo(null, false);
|
||||
// Create the connection factory with the dataset
|
||||
var factory = CreateTestConnectionInfo(data, throwOnRead).Factory;
|
||||
|
||||
LanguageService.Instance.ScriptParseInfoMap.Add(textDocument.TextDocument.Uri, new ScriptParseInfo());
|
||||
|
||||
scriptFile = new ScriptFile {ClientFilePath = textDocument.TextDocument.Uri};
|
||||
// Mock the connection service
|
||||
var connectionService = new Mock<ConnectionService>();
|
||||
ConnectionInfo ci = new ConnectionInfo(factory, OwnerUri, StandardConnectionDetails);
|
||||
ConnectionInfo outValMock;
|
||||
connectionService
|
||||
.Setup(service => service.TryFindConnection(It.IsAny<string>(), out outValMock))
|
||||
.OutCallback((string owner, out ConnectionInfo connInfo) => connInfo = isConnected ? ci : null)
|
||||
.Returns(isConnected);
|
||||
|
||||
return new QueryExecutionService(connectionService.Object, workspaceService) {BufferFileStreamFactory = GetFileStreamFactory(storage)};
|
||||
}
|
||||
|
||||
public static ServerConnection GetServerConnection(ConnectionInfo connection)
|
||||
{
|
||||
string connectionString = ConnectionService.BuildConnectionString(connection.ConnectionDetails);
|
||||
var sqlConnection = new SqlConnection(connectionString);
|
||||
return new ServerConnection(sqlConnection);
|
||||
}
|
||||
|
||||
public static ConnectionDetails GetTestConnectionDetails()
|
||||
{
|
||||
return new ConnectionDetails
|
||||
{
|
||||
DatabaseName = "123",
|
||||
Password = "456",
|
||||
ServerName = "789",
|
||||
UserName = "012"
|
||||
};
|
||||
}
|
||||
|
||||
public static async Task<QueryExecutionService> GetPrimedExecutionService(ISqlConnectionFactory factory, bool isConnected, WorkspaceService<SqlToolsSettings> workspaceService)
|
||||
{
|
||||
var connectionService = new ConnectionService(factory);
|
||||
if (isConnected)
|
||||
{
|
||||
await connectionService.Connect(new ConnectParams
|
||||
{
|
||||
Connection = GetTestConnectionDetails(),
|
||||
OwnerUri = OwnerUri
|
||||
});
|
||||
}
|
||||
return new QueryExecutionService(connectionService, workspaceService) {BufferFileStreamFactory = GetFileStreamFactory()};
|
||||
}
|
||||
|
||||
public static WorkspaceService<SqlToolsSettings> GetPrimedWorkspaceService()
|
||||
public static WorkspaceService<SqlToolsSettings> GetPrimedWorkspaceService(string query)
|
||||
{
|
||||
// Set up file for returning the query
|
||||
var fileMock = new Mock<ScriptFile>();
|
||||
fileMock.SetupGet(file => file.Contents).Returns(StandardQuery);
|
||||
fileMock.SetupGet(file => file.Contents).Returns(query);
|
||||
|
||||
// Set up workspace mock
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
@@ -300,6 +223,5 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,218 +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.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.DataStorage
|
||||
{
|
||||
public class FileStreamWrapperTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData("")]
|
||||
[InlineData(" ")]
|
||||
public void InitInvalidFilenameParameter(string fileName)
|
||||
{
|
||||
// If:
|
||||
// ... I have a file stream wrapper that is initialized with invalid fileName
|
||||
// Then:
|
||||
// ... It should throw an argument null exception
|
||||
using (FileStreamWrapper fsw = new FileStreamWrapper())
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() => fsw.Init(fileName, 8192, FileAccess.Read));
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0)]
|
||||
[InlineData(-1)]
|
||||
public void InitInvalidBufferLength(int bufferLength)
|
||||
{
|
||||
// If:
|
||||
// ... I have a file stream wrapper that is initialized with an invalid buffer length
|
||||
// Then:
|
||||
// ... I should throw an argument out of range exception
|
||||
using (FileStreamWrapper fsw = new FileStreamWrapper())
|
||||
{
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => fsw.Init("validFileName", bufferLength, FileAccess.Read));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InitInvalidFileAccessMode()
|
||||
{
|
||||
// If:
|
||||
// ... I attempt to open a file stream wrapper that is initialized with an invalid file
|
||||
// access mode
|
||||
// Then:
|
||||
// ... I should get an invalid argument exception
|
||||
using (FileStreamWrapper fsw = new FileStreamWrapper())
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() => fsw.Init("validFileName", 8192, FileAccess.Write));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InitSuccessful()
|
||||
{
|
||||
string fileName = Path.GetTempFileName();
|
||||
|
||||
try
|
||||
{
|
||||
using (FileStreamWrapper fsw = new FileStreamWrapper())
|
||||
{
|
||||
// If:
|
||||
// ... I have a file stream wrapper that is initialized with valid parameters
|
||||
fsw.Init(fileName, 8192, FileAccess.ReadWrite);
|
||||
|
||||
// Then:
|
||||
// ... The file should exist
|
||||
FileInfo fileInfo = new FileInfo(fileName);
|
||||
Assert.True(fileInfo.Exists);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Cleanup:
|
||||
// ... Delete the file that was created
|
||||
try { File.Delete(fileName); } catch { /* Don't care */ }
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PerformOpWithoutInit()
|
||||
{
|
||||
byte[] buf = new byte[10];
|
||||
|
||||
using (FileStreamWrapper fsw = new FileStreamWrapper())
|
||||
{
|
||||
// If:
|
||||
// ... I have a file stream wrapper that hasn't been initialized
|
||||
// Then:
|
||||
// ... Attempting to perform any operation will result in an exception
|
||||
Assert.Throws<InvalidOperationException>(() => fsw.ReadData(buf, 1));
|
||||
Assert.Throws<InvalidOperationException>(() => fsw.ReadData(buf, 1, 0));
|
||||
Assert.Throws<InvalidOperationException>(() => fsw.WriteData(buf, 1));
|
||||
Assert.Throws<InvalidOperationException>(() => fsw.Flush());
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PerformWriteOpOnReadOnlyWrapper()
|
||||
{
|
||||
byte[] buf = new byte[10];
|
||||
|
||||
using (FileStreamWrapper fsw = new FileStreamWrapper())
|
||||
{
|
||||
// If:
|
||||
// ... I have a readonly file stream wrapper
|
||||
// Then:
|
||||
// ... Attempting to perform any write operation should result in an exception
|
||||
Assert.Throws<InvalidOperationException>(() => fsw.WriteData(buf, 1));
|
||||
Assert.Throws<InvalidOperationException>(() => fsw.Flush());
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(1024, 20, 10)] // Standard scenario
|
||||
[InlineData(1024, 100, 100)] // Requested more bytes than there are
|
||||
[InlineData(5, 20, 10)] // Internal buffer too small, force a move-to operation
|
||||
public void ReadData(int internalBufferLength, int outBufferLength, int requestedBytes)
|
||||
{
|
||||
// Setup:
|
||||
// ... I have a file that has a handful of bytes in it
|
||||
string fileName = Path.GetTempFileName();
|
||||
const string stringToWrite = "hello";
|
||||
CreateTestFile(fileName, stringToWrite);
|
||||
byte[] targetBytes = Encoding.Unicode.GetBytes(stringToWrite);
|
||||
|
||||
try
|
||||
{
|
||||
// If:
|
||||
// ... I have a file stream wrapper that has been initialized to an existing file
|
||||
// ... And I read some bytes from it
|
||||
int bytesRead;
|
||||
byte[] buf = new byte[outBufferLength];
|
||||
using (FileStreamWrapper fsw = new FileStreamWrapper())
|
||||
{
|
||||
fsw.Init(fileName, internalBufferLength, FileAccess.Read);
|
||||
bytesRead = fsw.ReadData(buf, targetBytes.Length);
|
||||
}
|
||||
|
||||
// Then:
|
||||
// ... I should get those bytes back
|
||||
Assert.Equal(targetBytes.Length, bytesRead);
|
||||
Assert.True(targetBytes.Take(targetBytes.Length).SequenceEqual(buf.Take(targetBytes.Length)));
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Cleanup:
|
||||
// ... Delete the test file
|
||||
CleanupTestFile(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(1024)] // Standard scenario
|
||||
[InlineData(10)] // Internal buffer too small, forces a flush
|
||||
public void WriteData(int internalBufferLength)
|
||||
{
|
||||
string fileName = Path.GetTempFileName();
|
||||
byte[] bytesToWrite = Encoding.Unicode.GetBytes("hello");
|
||||
|
||||
try
|
||||
{
|
||||
// If:
|
||||
// ... I have a file stream that has been initialized
|
||||
// ... And I write some bytes to it
|
||||
using (FileStreamWrapper fsw = new FileStreamWrapper())
|
||||
{
|
||||
fsw.Init(fileName, internalBufferLength, FileAccess.ReadWrite);
|
||||
int bytesWritten = fsw.WriteData(bytesToWrite, bytesToWrite.Length);
|
||||
|
||||
Assert.Equal(bytesToWrite.Length, bytesWritten);
|
||||
}
|
||||
|
||||
// Then:
|
||||
// ... The file I wrote to should contain only the bytes I wrote out
|
||||
using (FileStream fs = File.OpenRead(fileName))
|
||||
{
|
||||
byte[] readBackBytes = new byte[1024];
|
||||
int bytesRead = fs.Read(readBackBytes, 0, readBackBytes.Length);
|
||||
|
||||
Assert.Equal(bytesToWrite.Length, bytesRead); // If bytes read is not equal, then more or less of the original string was written to the file
|
||||
Assert.True(bytesToWrite.SequenceEqual(readBackBytes.Take(bytesRead)));
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Cleanup:
|
||||
// ... Delete the test file
|
||||
CleanupTestFile(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
private static void CreateTestFile(string fileName, string value)
|
||||
{
|
||||
using (FileStream fs = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite))
|
||||
{
|
||||
byte[] bytesToWrite = Encoding.Unicode.GetBytes(value);
|
||||
fs.Write(bytesToWrite, 0, bytesToWrite.Length);
|
||||
fs.Flush();
|
||||
}
|
||||
}
|
||||
|
||||
private static void CleanupTestFile(string fileName)
|
||||
{
|
||||
try { File.Delete(fileName); } catch { /* Don't Care */}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,45 +6,102 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.SqlTypes;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Utility;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.DataStorage
|
||||
{
|
||||
public class ReaderWriterPairTest
|
||||
{
|
||||
[Fact]
|
||||
public void ReaderInvalidStreamCannotRead()
|
||||
{
|
||||
// If: I create a service buffer file stream reader with a stream that cannot be read
|
||||
// Then: I should get an exception
|
||||
var invalidStream = new Mock<Stream>();
|
||||
invalidStream.SetupGet(s => s.CanRead).Returns(false);
|
||||
invalidStream.SetupGet(s => s.CanSeek).Returns(true);
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
{
|
||||
ServiceBufferFileStreamReader obj = new ServiceBufferFileStreamReader(invalidStream.Object);
|
||||
obj.Dispose();
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReaderInvalidStreamCannotSeek()
|
||||
{
|
||||
// If: I create a service buffer file stream reader with a stream that cannot seek
|
||||
// Then: I should get an exception
|
||||
var invalidStream = new Mock<Stream>();
|
||||
invalidStream.SetupGet(s => s.CanRead).Returns(true);
|
||||
invalidStream.SetupGet(s => s.CanSeek).Returns(false);
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
{
|
||||
ServiceBufferFileStreamReader obj = new ServiceBufferFileStreamReader(invalidStream.Object);
|
||||
obj.Dispose();
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriterInvalidStreamCannotWrite()
|
||||
{
|
||||
// If: I create a service buffer file stream writer with a stream that cannot be read
|
||||
// Then: I should get an exception
|
||||
var invalidStream = new Mock<Stream>();
|
||||
invalidStream.SetupGet(s => s.CanWrite).Returns(false);
|
||||
invalidStream.SetupGet(s => s.CanSeek).Returns(true);
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
{
|
||||
ServiceBufferFileStreamWriter obj = new ServiceBufferFileStreamWriter(invalidStream.Object, 1024, 1024);
|
||||
obj.Dispose();
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriterInvalidStreamCannotSeek()
|
||||
{
|
||||
// If: I create a service buffer file stream writer with a stream that cannot seek
|
||||
// Then: I should get an exception
|
||||
var invalidStream = new Mock<Stream>();
|
||||
invalidStream.SetupGet(s => s.CanWrite).Returns(true);
|
||||
invalidStream.SetupGet(s => s.CanSeek).Returns(false);
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
{
|
||||
ServiceBufferFileStreamWriter obj = new ServiceBufferFileStreamWriter(invalidStream.Object, 1024, 1024);
|
||||
obj.Dispose();
|
||||
});
|
||||
}
|
||||
|
||||
private static void VerifyReadWrite<T>(int valueLength, T value, Func<ServiceBufferFileStreamWriter, T, int> writeFunc, Func<ServiceBufferFileStreamReader, FileStreamReadResult> readFunc)
|
||||
{
|
||||
// Setup: Create a mock file stream wrapper
|
||||
Common.InMemoryWrapper mockWrapper = new Common.InMemoryWrapper();
|
||||
try
|
||||
// Setup: Create a mock file stream
|
||||
byte[] storage = new byte[8192];
|
||||
|
||||
// If:
|
||||
// ... I write a type T to the writer
|
||||
using (ServiceBufferFileStreamWriter writer = new ServiceBufferFileStreamWriter(new MemoryStream(storage), 10, 10))
|
||||
{
|
||||
// If:
|
||||
// ... I write a type T to the writer
|
||||
using (ServiceBufferFileStreamWriter writer = new ServiceBufferFileStreamWriter(mockWrapper, "abc", 10, 10))
|
||||
{
|
||||
int writtenBytes = writeFunc(writer, value);
|
||||
Assert.Equal(valueLength, writtenBytes);
|
||||
}
|
||||
|
||||
// ... And read the type T back
|
||||
FileStreamReadResult outValue;
|
||||
using (ServiceBufferFileStreamReader reader = new ServiceBufferFileStreamReader(mockWrapper, "abc"))
|
||||
{
|
||||
outValue = readFunc(reader);
|
||||
}
|
||||
|
||||
// Then:
|
||||
Assert.Equal(value, outValue.Value.RawObject);
|
||||
Assert.Equal(valueLength, outValue.TotalLength);
|
||||
Assert.NotNull(outValue.Value);
|
||||
int writtenBytes = writeFunc(writer, value);
|
||||
Assert.Equal(valueLength, writtenBytes);
|
||||
}
|
||||
finally
|
||||
|
||||
// ... And read the type T back
|
||||
FileStreamReadResult outValue;
|
||||
using (ServiceBufferFileStreamReader reader = new ServiceBufferFileStreamReader(new MemoryStream(storage)))
|
||||
{
|
||||
// Cleanup: Close the wrapper
|
||||
mockWrapper.Close();
|
||||
outValue = readFunc(reader);
|
||||
}
|
||||
|
||||
// Then:
|
||||
Assert.Equal(value, outValue.Value.RawObject);
|
||||
Assert.Equal(valueLength, outValue.TotalLength);
|
||||
Assert.NotNull(outValue.Value);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -174,18 +231,21 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.DataStorage
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DateTimeTest()
|
||||
[Theory]
|
||||
[InlineData(3)] // Scale 3 = DATETIME
|
||||
[InlineData(7)] // Scale 7 = DATETIME2
|
||||
public void DateTimeTest(int scale)
|
||||
{
|
||||
// Setup: Create some test values
|
||||
// Setup: Create some test values and a column with scale set
|
||||
// NOTE: We are doing these here instead of InlineData because DateTime values can't be written as constant expressions
|
||||
DbColumnWrapper col = new DbColumnWrapper(new TestDbColumn("dbcol", scale));
|
||||
DateTime[] testValues =
|
||||
{
|
||||
DateTime.Now, DateTime.UtcNow, DateTime.MinValue, DateTime.MaxValue
|
||||
};
|
||||
foreach (DateTime value in testValues)
|
||||
{
|
||||
VerifyReadWrite(sizeof(long) + 1, value, (writer, val) => writer.WriteDateTime(val), reader => reader.ReadDateTime(0));
|
||||
VerifyReadWrite(sizeof(long) + sizeof(int) + 1, value, (writer, val) => writer.WriteDateTime(col, val), reader => reader.ReadDateTime(0));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,16 +282,17 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.DataStorage
|
||||
[Fact]
|
||||
public void StringNullTest()
|
||||
{
|
||||
// Setup: Create a mock file stream wrapper
|
||||
Common.InMemoryWrapper mockWrapper = new Common.InMemoryWrapper();
|
||||
|
||||
// If:
|
||||
// ... I write null as a string to the writer
|
||||
using (ServiceBufferFileStreamWriter writer = new ServiceBufferFileStreamWriter(mockWrapper, "abc", 10, 10))
|
||||
// Setup: Create a mock file stream
|
||||
using (MemoryStream stream = new MemoryStream(new byte[8192]))
|
||||
{
|
||||
// Then:
|
||||
// ... I should get an argument null exception
|
||||
Assert.Throws<ArgumentNullException>(() => writer.WriteString(null));
|
||||
// If:
|
||||
// ... I write null as a string to the writer
|
||||
using (ServiceBufferFileStreamWriter writer = new ServiceBufferFileStreamWriter(stream, 10, 10))
|
||||
{
|
||||
// Then:
|
||||
// ... I should get an argument null exception
|
||||
Assert.Throws<ArgumentNullException>(() => writer.WriteString(null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,15 +320,16 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.DataStorage
|
||||
public void BytesNullTest()
|
||||
{
|
||||
// Setup: Create a mock file stream wrapper
|
||||
Common.InMemoryWrapper mockWrapper = new Common.InMemoryWrapper();
|
||||
|
||||
// If:
|
||||
// ... I write null as a string to the writer
|
||||
using (ServiceBufferFileStreamWriter writer = new ServiceBufferFileStreamWriter(mockWrapper, "abc", 10, 10))
|
||||
using (MemoryStream stream = new MemoryStream(new byte[8192]))
|
||||
{
|
||||
// Then:
|
||||
// ... I should get an argument null exception
|
||||
Assert.Throws<ArgumentNullException>(() => writer.WriteBytes(null));
|
||||
// If:
|
||||
// ... I write null as a string to the writer
|
||||
using (ServiceBufferFileStreamWriter writer = new ServiceBufferFileStreamWriter(stream, 10, 10))
|
||||
{
|
||||
// Then:
|
||||
// ... I should get an argument null exception
|
||||
Assert.Throws<ArgumentNullException>(() => writer.WriteBytes(null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
#if LIVE_CONNECTION_TESTS
|
||||
|
||||
using System.Data.Common;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||
using Microsoft.SqlTools.Test.Utility;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.DataStorage
|
||||
{
|
||||
public class StorageDataReaderTests
|
||||
{
|
||||
private StorageDataReader GetTestStorageDataReader(out DbDataReader reader, string query)
|
||||
{
|
||||
ScriptFile scriptFile;
|
||||
ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfo(out scriptFile);
|
||||
|
||||
var command = connInfo.SqlConnection.CreateCommand();
|
||||
command.CommandText = query;
|
||||
reader = command.ExecuteReader();
|
||||
|
||||
return new StorageDataReader(reader);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate GetBytesWithMaxCapacity
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetBytesWithMaxCapacityTest()
|
||||
{
|
||||
DbDataReader reader;
|
||||
var storageReader = GetTestStorageDataReader(
|
||||
out reader,
|
||||
"SELECT CAST([name] as TEXT) As TextName FROM sys.all_columns");
|
||||
|
||||
reader.Read();
|
||||
Assert.False(storageReader.IsDBNull(0));
|
||||
|
||||
byte[] bytes = storageReader.GetBytesWithMaxCapacity(0, 100);
|
||||
Assert.NotNull(bytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate GetCharsWithMaxCapacity
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetCharsWithMaxCapacityTest()
|
||||
{
|
||||
DbDataReader reader;
|
||||
var storageReader = GetTestStorageDataReader(
|
||||
out reader,
|
||||
"SELECT name FROM sys.all_columns");
|
||||
|
||||
reader.Read();
|
||||
Assert.False(storageReader.IsDBNull(0));
|
||||
|
||||
string shortName = storageReader.GetCharsWithMaxCapacity(0, 2);
|
||||
Assert.True(shortName.Length == 2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate GetXmlWithMaxCapacity
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetXmlWithMaxCapacityTest()
|
||||
{
|
||||
DbDataReader reader;
|
||||
var storageReader = GetTestStorageDataReader(
|
||||
out reader,
|
||||
"SELECT CAST('<xml>Test XML context</xml>' AS XML) As XmlColumn");
|
||||
|
||||
reader.Read();
|
||||
Assert.False(storageReader.IsDBNull(0));
|
||||
|
||||
string shortXml = storageReader.GetXmlWithMaxCapacity(0, 2);
|
||||
Assert.True(shortXml.Length == 3);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate StringWriterWithMaxCapacity Write test
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void StringWriterWithMaxCapacityTest()
|
||||
{
|
||||
var writer = new StorageDataReader.StringWriterWithMaxCapacity(null, 100);
|
||||
string output = "...";
|
||||
writer.Write(output);
|
||||
Assert.True(writer.ToString().Equals(output));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -28,7 +28,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
var mockDataReader = Common.CreateTestConnection(null, false).CreateCommand().ExecuteReaderAsync().Result;
|
||||
|
||||
// If: I setup a single resultset and then dispose it
|
||||
ResultSet rs = new ResultSet(mockDataReader, mockFileStreamFactory.Object);
|
||||
ResultSet rs = new ResultSet(mockDataReader, Common.Ordinal, Common.Ordinal, mockFileStreamFactory.Object);
|
||||
rs.Dispose();
|
||||
|
||||
// Then: The file that was created should have been deleted
|
||||
@@ -47,7 +47,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
.Returns(fileMock.Object);
|
||||
// If:
|
||||
// ... I request a query (doesn't matter what kind)
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, workspaceService.Object);
|
||||
var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService.Object);
|
||||
var executeParams = new QueryExecuteParams {QuerySelection = null, OwnerUri = Common.OwnerUri};
|
||||
var executeRequest = RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(null, QueryExecuteCompleteEvent.Type, null, null);
|
||||
await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
|
||||
@@ -59,7 +59,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
var disposeRequest = GetQueryDisposeResultContextMock(qdr => {
|
||||
result = qdr;
|
||||
}, null);
|
||||
queryService.HandleDisposeRequest(disposeParams, disposeRequest.Object).Wait();
|
||||
await queryService.HandleDisposeRequest(disposeParams, disposeRequest.Object);
|
||||
|
||||
// Then:
|
||||
// ... I should have seen a successful result
|
||||
@@ -75,11 +75,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
// If:
|
||||
// ... I attempt to dispose a query that doesn't exist
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), false, workspaceService.Object);
|
||||
var queryService = Common.GetPrimedExecutionService(null, false, false, workspaceService.Object);
|
||||
var disposeParams = new QueryDisposeParams {OwnerUri = Common.OwnerUri};
|
||||
QueryDisposeResult result = null;
|
||||
var disposeRequest = GetQueryDisposeResultContextMock(qdr => result = qdr, null);
|
||||
queryService.HandleDisposeRequest(disposeParams, disposeRequest.Object).Wait();
|
||||
await queryService.HandleDisposeRequest(disposeParams, disposeRequest.Object);
|
||||
|
||||
// Then:
|
||||
// ... I should have gotten an error result
|
||||
@@ -99,8 +99,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
|
||||
.Returns(fileMock.Object);
|
||||
// ... We need a query service
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true,
|
||||
workspaceService.Object);
|
||||
var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService.Object);
|
||||
|
||||
|
||||
// If:
|
||||
// ... I execute some bogus query
|
||||
|
||||
@@ -5,666 +5,17 @@
|
||||
|
||||
//#define USE_LIVE_CONNECTION
|
||||
|
||||
using System;
|
||||
using System.Data.Common;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
{
|
||||
public class ExecuteTests
|
||||
{
|
||||
#region Batch Class Tests
|
||||
|
||||
[Fact]
|
||||
public void BatchCreationTest()
|
||||
{
|
||||
// If I create a new batch...
|
||||
Batch batch = new Batch(Common.StandardQuery, 0, 0, 2, 2, Common.GetFileStreamFactory());
|
||||
|
||||
// Then:
|
||||
// ... The text of the batch should be stored
|
||||
Assert.NotEmpty(batch.BatchText);
|
||||
|
||||
// ... It should not have executed and no error
|
||||
Assert.False(batch.HasExecuted, "The query should not have executed.");
|
||||
Assert.False(batch.HasError, "The batch should not have an error");
|
||||
|
||||
// ... The results should be empty
|
||||
Assert.Empty(batch.ResultSets);
|
||||
Assert.Empty(batch.ResultSummaries);
|
||||
Assert.Empty(batch.ResultMessages);
|
||||
|
||||
// ... The start line of the batch should be 0
|
||||
Assert.Equal(0, batch.Selection.StartLine);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BatchExecuteNoResultSets()
|
||||
{
|
||||
// If I execute a query that should get no result sets
|
||||
Batch batch = new Batch(Common.StandardQuery, 0, 0, 2, 2, Common.GetFileStreamFactory());
|
||||
batch.Execute(GetConnection(Common.CreateTestConnectionInfo(null, false)), CancellationToken.None).Wait();
|
||||
|
||||
// Then:
|
||||
// ... It should have executed without error
|
||||
Assert.True(batch.HasExecuted, "The query should have been marked executed.");
|
||||
Assert.False(batch.HasError, "The batch should not have an error");
|
||||
|
||||
// ... The results should be empty
|
||||
Assert.Empty(batch.ResultSets);
|
||||
Assert.Empty(batch.ResultSummaries);
|
||||
|
||||
// ... The results should not be null
|
||||
Assert.NotNull(batch.ResultSets);
|
||||
Assert.NotNull(batch.ResultSummaries);
|
||||
|
||||
// ... There should be a message for how many rows were affected
|
||||
Assert.Equal(1, batch.ResultMessages.Count());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BatchExecuteOneResultSet()
|
||||
{
|
||||
int resultSets = 1;
|
||||
ConnectionInfo ci = Common.CreateTestConnectionInfo(new[] { Common.StandardTestData }, false);
|
||||
|
||||
// If I execute a query that should get one result set
|
||||
Batch batch = new Batch(Common.StandardQuery, 0, 0, 2, 2, Common.GetFileStreamFactory());
|
||||
batch.Execute(GetConnection(ci), CancellationToken.None).Wait();
|
||||
|
||||
// Then:
|
||||
// ... It should have executed without error
|
||||
Assert.True(batch.HasExecuted, "The batch should have been marked executed.");
|
||||
Assert.False(batch.HasError, "The batch should not have an error");
|
||||
|
||||
// ... There should be exactly one result set
|
||||
Assert.Equal(resultSets, batch.ResultSets.Count());
|
||||
Assert.Equal(resultSets, batch.ResultSummaries.Length);
|
||||
|
||||
// ... Inside the result set should be with 5 rows
|
||||
Assert.Equal(Common.StandardRows, batch.ResultSets.First().RowCount);
|
||||
Assert.Equal(Common.StandardRows, batch.ResultSummaries[0].RowCount);
|
||||
|
||||
// ... Inside the result set should have 5 columns
|
||||
Assert.Equal(Common.StandardColumns, batch.ResultSets.First().Columns.Length);
|
||||
Assert.Equal(Common.StandardColumns, batch.ResultSummaries[0].ColumnInfo.Length);
|
||||
|
||||
// ... There should be a message for how many rows were affected
|
||||
Assert.Equal(resultSets, batch.ResultMessages.Count());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BatchExecuteTwoResultSets()
|
||||
{
|
||||
var dataset = new[] { Common.StandardTestData, Common.StandardTestData };
|
||||
int resultSets = dataset.Length;
|
||||
ConnectionInfo ci = Common.CreateTestConnectionInfo(dataset, false);
|
||||
|
||||
// If I execute a query that should get two result sets
|
||||
Batch batch = new Batch(Common.StandardQuery, 0, 0, 1, 1, Common.GetFileStreamFactory());
|
||||
batch.Execute(GetConnection(ci), CancellationToken.None).Wait();
|
||||
|
||||
// Then:
|
||||
// ... It should have executed without error
|
||||
Assert.True(batch.HasExecuted, "The batch should have been marked executed.");
|
||||
Assert.False(batch.HasError, "The batch should not have an error");
|
||||
|
||||
// ... There should be exactly two result sets
|
||||
Assert.Equal(resultSets, batch.ResultSets.Count());
|
||||
|
||||
foreach (ResultSet rs in batch.ResultSets)
|
||||
{
|
||||
// ... Each result set should have 5 rows
|
||||
Assert.Equal(Common.StandardRows, rs.RowCount);
|
||||
|
||||
// ... Inside each result set should be 5 columns
|
||||
Assert.Equal(Common.StandardColumns, rs.Columns.Length);
|
||||
}
|
||||
|
||||
// ... There should be exactly two result set summaries
|
||||
Assert.Equal(resultSets, batch.ResultSummaries.Length);
|
||||
|
||||
foreach (ResultSetSummary rs in batch.ResultSummaries)
|
||||
{
|
||||
// ... Inside each result summary, there should be 5 rows
|
||||
Assert.Equal(Common.StandardRows, rs.RowCount);
|
||||
|
||||
// ... Inside each result summary, there should be 5 column definitions
|
||||
Assert.Equal(Common.StandardColumns, rs.ColumnInfo.Length);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BatchExecuteInvalidQuery()
|
||||
{
|
||||
ConnectionInfo ci = Common.CreateTestConnectionInfo(null, true);
|
||||
|
||||
// If I execute a batch that is invalid
|
||||
Batch batch = new Batch(Common.StandardQuery, 0, 0, 2, 2, Common.GetFileStreamFactory());
|
||||
batch.Execute(GetConnection(ci), CancellationToken.None).Wait();
|
||||
|
||||
// Then:
|
||||
// ... It should have executed with error
|
||||
Assert.True(batch.HasExecuted);
|
||||
Assert.True(batch.HasError);
|
||||
|
||||
// ... There should be no result sets
|
||||
Assert.Empty(batch.ResultSets);
|
||||
Assert.Empty(batch.ResultSummaries);
|
||||
|
||||
// ... There should be plenty of messages for the error
|
||||
Assert.NotEmpty(batch.ResultMessages);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BatchExecuteExecuted()
|
||||
{
|
||||
ConnectionInfo ci = Common.CreateTestConnectionInfo(new[] { Common.StandardTestData }, false);
|
||||
|
||||
// If I execute a batch
|
||||
Batch batch = new Batch(Common.StandardQuery, 0, 0, 2, 2, Common.GetFileStreamFactory());
|
||||
batch.Execute(GetConnection(ci), CancellationToken.None).Wait();
|
||||
|
||||
// Then:
|
||||
// ... It should have executed without error
|
||||
Assert.True(batch.HasExecuted, "The batch should have been marked executed.");
|
||||
Assert.False(batch.HasError, "The batch should not have an error");
|
||||
|
||||
// If I execute it again
|
||||
// Then:
|
||||
// ... It should throw an invalid operation exception
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
||||
batch.Execute(GetConnection(ci), CancellationToken.None));
|
||||
|
||||
// ... The data should still be available without error
|
||||
Assert.False(batch.HasError, "The batch should not be in an error condition");
|
||||
Assert.True(batch.HasExecuted, "The batch should still be marked executed.");
|
||||
Assert.NotEmpty(batch.ResultSets);
|
||||
Assert.NotEmpty(batch.ResultSummaries);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData(null)]
|
||||
public void BatchExecuteNoSql(string query)
|
||||
{
|
||||
// If:
|
||||
// ... I create a batch that has an empty query
|
||||
// Then:
|
||||
// ... It should throw an exception
|
||||
Assert.Throws<ArgumentException>(() => new Batch(query, 0, 0, 2, 2, Common.GetFileStreamFactory()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BatchNoBufferFactory()
|
||||
{
|
||||
// If:
|
||||
// ... I create a batch that has no file stream factory
|
||||
// Then:
|
||||
// ... It should throw an exception
|
||||
Assert.Throws<ArgumentNullException>(() => new Batch("stuff", 0, 0, 2, 2, null));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Query Class Tests
|
||||
|
||||
[Fact]
|
||||
public void QueryExecuteNoQueryText()
|
||||
{
|
||||
// If:
|
||||
// ... I create a query that has a null query text
|
||||
// Then:
|
||||
// ... It should throw an exception
|
||||
Assert.Throws<ArgumentException>(() =>
|
||||
new Query(null, Common.CreateTestConnectionInfo(null, false), new QueryExecutionSettings(), Common.GetFileStreamFactory()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void QueryExecuteNoConnectionInfo()
|
||||
{
|
||||
// If:
|
||||
// ... I create a query that has a null connection info
|
||||
// Then:
|
||||
// ... It should throw an exception
|
||||
Assert.Throws<ArgumentNullException>(() => new Query("Some Query", null, new QueryExecutionSettings(), Common.GetFileStreamFactory()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void QueryExecuteNoSettings()
|
||||
{
|
||||
// If:
|
||||
// ... I create a query that has a null settings
|
||||
// Then:
|
||||
// ... It should throw an exception
|
||||
Assert.Throws<ArgumentNullException>(() =>
|
||||
new Query("Some query", Common.CreateTestConnectionInfo(null, false), null, Common.GetFileStreamFactory()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void QueryExecuteNoBufferFactory()
|
||||
{
|
||||
// If:
|
||||
// ... I create a query that has a null file stream factory
|
||||
// Then:
|
||||
// ... It should throw an exception
|
||||
Assert.Throws<ArgumentNullException>(() =>
|
||||
new Query("Some query", Common.CreateTestConnectionInfo(null, false), new QueryExecutionSettings(),null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void QueryExecuteSingleBatch()
|
||||
{
|
||||
// If:
|
||||
// ... I create a query from a single batch (without separator)
|
||||
ConnectionInfo ci = Common.CreateTestConnectionInfo(null, false);
|
||||
Query query = new Query(Common.StandardQuery, ci, new QueryExecutionSettings(), Common.GetFileStreamFactory());
|
||||
|
||||
// Then:
|
||||
// ... I should get a single batch to execute that hasn't been executed
|
||||
Assert.NotEmpty(query.QueryText);
|
||||
Assert.NotEmpty(query.Batches);
|
||||
Assert.Equal(1, query.Batches.Length);
|
||||
Assert.False(query.HasExecuted);
|
||||
Assert.Throws<InvalidOperationException>(() => query.BatchSummaries);
|
||||
|
||||
// If:
|
||||
// ... I then execute the query
|
||||
query.Execute();
|
||||
query.ExecutionTask.Wait();
|
||||
|
||||
// Then:
|
||||
// ... The query should have completed successfully with one batch summary returned
|
||||
Assert.True(query.HasExecuted);
|
||||
Assert.NotEmpty(query.BatchSummaries);
|
||||
Assert.Equal(1, query.BatchSummaries.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void QueryExecuteNoOpBatch()
|
||||
{
|
||||
// If:
|
||||
// ... I create a query from a single batch that does nothing
|
||||
ConnectionInfo ci = Common.CreateTestConnectionInfo(null, false);
|
||||
Query query = new Query(Common.NoOpQuery, ci, new QueryExecutionSettings(), Common.GetFileStreamFactory());
|
||||
|
||||
// Then:
|
||||
// ... I should get no batches back
|
||||
Assert.NotEmpty(query.QueryText);
|
||||
Assert.Empty(query.Batches);
|
||||
Assert.False(query.HasExecuted);
|
||||
Assert.Throws<InvalidOperationException>(() => query.BatchSummaries);
|
||||
|
||||
// If:
|
||||
// ... I Then execute the query
|
||||
query.Execute();
|
||||
query.ExecutionTask.Wait();
|
||||
|
||||
// Then:
|
||||
// ... The query should have completed successfully with no batch summaries returned
|
||||
Assert.True(query.HasExecuted);
|
||||
Assert.Empty(query.BatchSummaries);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void QueryExecuteMultipleBatches()
|
||||
{
|
||||
// If:
|
||||
// ... I create a query from two batches (with separator)
|
||||
ConnectionInfo ci = Common.CreateTestConnectionInfo(null, false);
|
||||
string queryText = string.Format("{0}\r\nGO\r\n{0}", Common.StandardQuery);
|
||||
Query query = new Query(queryText, ci, new QueryExecutionSettings(), Common.GetFileStreamFactory());
|
||||
|
||||
// Then:
|
||||
// ... I should get back two batches to execute that haven't been executed
|
||||
Assert.NotEmpty(query.QueryText);
|
||||
Assert.NotEmpty(query.Batches);
|
||||
Assert.Equal(2, query.Batches.Length);
|
||||
Assert.False(query.HasExecuted);
|
||||
Assert.Throws<InvalidOperationException>(() => query.BatchSummaries);
|
||||
|
||||
// If:
|
||||
// ... I then execute the query
|
||||
query.Execute();
|
||||
query.ExecutionTask.Wait();
|
||||
|
||||
// Then:
|
||||
// ... The query should have completed successfully with two batch summaries returned
|
||||
Assert.True(query.HasExecuted);
|
||||
Assert.NotEmpty(query.BatchSummaries);
|
||||
Assert.Equal(2, query.BatchSummaries.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void QueryExecuteMultipleBatchesWithNoOp()
|
||||
{
|
||||
// If:
|
||||
// ... I create a query from a two batches (with separator)
|
||||
ConnectionInfo ci = Common.CreateTestConnectionInfo(null, false);
|
||||
string queryText = string.Format("{0}\r\nGO\r\n{1}", Common.StandardQuery, Common.NoOpQuery);
|
||||
Query query = new Query(queryText, ci, new QueryExecutionSettings(), Common.GetFileStreamFactory());
|
||||
|
||||
// Then:
|
||||
// ... I should get back one batch to execute that hasn't been executed
|
||||
Assert.NotEmpty(query.QueryText);
|
||||
Assert.NotEmpty(query.Batches);
|
||||
Assert.Equal(1, query.Batches.Length);
|
||||
Assert.False(query.HasExecuted);
|
||||
Assert.Throws<InvalidOperationException>(() => query.BatchSummaries);
|
||||
|
||||
// If:
|
||||
// .. I then execute the query
|
||||
query.Execute();
|
||||
query.ExecutionTask.Wait();
|
||||
|
||||
// ... The query should have completed successfully with one batch summary returned
|
||||
Assert.True(query.HasExecuted);
|
||||
Assert.NotEmpty(query.BatchSummaries);
|
||||
Assert.Equal(1, query.BatchSummaries.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void QueryExecuteInvalidBatch()
|
||||
{
|
||||
// If:
|
||||
// ... I create a query from an invalid batch
|
||||
ConnectionInfo ci = Common.CreateTestConnectionInfo(null, true);
|
||||
Query query = new Query(Common.InvalidQuery, ci, new QueryExecutionSettings(), Common.GetFileStreamFactory());
|
||||
|
||||
// Then:
|
||||
// ... I should get back a query with one batch not executed
|
||||
Assert.NotEmpty(query.QueryText);
|
||||
Assert.NotEmpty(query.Batches);
|
||||
Assert.Equal(1, query.Batches.Length);
|
||||
Assert.False(query.HasExecuted);
|
||||
Assert.Throws<InvalidOperationException>(() => query.BatchSummaries);
|
||||
|
||||
// If:
|
||||
// ... I then execute the query
|
||||
query.Execute();
|
||||
query.ExecutionTask.Wait();
|
||||
|
||||
// Then:
|
||||
// ... There should be an error on the batch
|
||||
Assert.True(query.HasExecuted);
|
||||
Assert.NotEmpty(query.BatchSummaries);
|
||||
Assert.Equal(1, query.BatchSummaries.Length);
|
||||
Assert.True(query.BatchSummaries[0].HasError);
|
||||
Assert.NotEmpty(query.BatchSummaries[0].Messages);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Service Tests
|
||||
|
||||
[Fact]
|
||||
public async void QueryExecuteValidNoResultsTest()
|
||||
{
|
||||
// Given:
|
||||
// ... Default settings are stored in the workspace service
|
||||
WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings = new SqlToolsSettings();
|
||||
|
||||
// Set up file for returning the query
|
||||
var fileMock = new Mock<ScriptFile>();
|
||||
fileMock.SetupGet(file => file.Contents).Returns(Common.StandardQuery);
|
||||
// Set up workspace mock
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
|
||||
.Returns(fileMock.Object);
|
||||
// If:
|
||||
// ... I request to execute a valid query with no results
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, workspaceService.Object);
|
||||
var queryParams = new QueryExecuteParams { QuerySelection = Common.WholeDocument, OwnerUri = Common.OwnerUri };
|
||||
|
||||
QueryExecuteResult result = null;
|
||||
QueryExecuteCompleteParams completeParams = null;
|
||||
var requestContext =
|
||||
RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(
|
||||
resultCallback: qer => result = qer,
|
||||
expectedEvent: QueryExecuteCompleteEvent.Type,
|
||||
eventCallback: (et, cp) => completeParams = cp,
|
||||
errorCallback: null);
|
||||
await AwaitExecution(queryService, queryParams, requestContext.Object);
|
||||
|
||||
// Then:
|
||||
// ... No Errors should have been sent
|
||||
// ... A successful result should have been sent with messages on the first batch
|
||||
// ... A completion event should have been fired with empty results
|
||||
VerifyQueryExecuteCallCount(requestContext, Times.Once(), Times.Once(), Times.Never());
|
||||
Assert.Null(result.Messages);
|
||||
Assert.Equal(1, completeParams.BatchSummaries.Length);
|
||||
Assert.Empty(completeParams.BatchSummaries[0].ResultSetSummaries);
|
||||
Assert.NotEmpty(completeParams.BatchSummaries[0].Messages);
|
||||
|
||||
// ... There should be one active query
|
||||
Assert.Equal(1, queryService.ActiveQueries.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async void QueryExecuteValidResultsTest()
|
||||
{
|
||||
|
||||
// Set up file for returning the query
|
||||
var fileMock = new Mock<ScriptFile>();
|
||||
fileMock.SetupGet(file => file.Contents).Returns(Common.StandardQuery);
|
||||
// Set up workspace mock
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
|
||||
.Returns(fileMock.Object);
|
||||
// If:
|
||||
// ... I request to execute a valid query with results
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(new[] { Common.StandardTestData }, false), true,
|
||||
workspaceService.Object);
|
||||
var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QuerySelection = Common.WholeDocument };
|
||||
|
||||
QueryExecuteResult result = null;
|
||||
QueryExecuteCompleteParams completeParams = null;
|
||||
var requestContext =
|
||||
RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(
|
||||
resultCallback: qer => result = qer,
|
||||
expectedEvent: QueryExecuteCompleteEvent.Type,
|
||||
eventCallback: (et, cp) => completeParams = cp,
|
||||
errorCallback: null);
|
||||
await AwaitExecution(queryService, queryParams, requestContext.Object);
|
||||
|
||||
// Then:
|
||||
// ... No errors should have been sent
|
||||
// ... A successful result should have been sent with messages
|
||||
// ... A completion event should have been fired with one result
|
||||
VerifyQueryExecuteCallCount(requestContext, Times.Once(), Times.Once(), Times.Never());
|
||||
Assert.Null(result.Messages);
|
||||
Assert.Equal(1, completeParams.BatchSummaries.Length);
|
||||
Assert.NotEmpty(completeParams.BatchSummaries[0].ResultSetSummaries);
|
||||
Assert.NotEmpty(completeParams.BatchSummaries[0].Messages);
|
||||
Assert.False(completeParams.BatchSummaries[0].HasError);
|
||||
|
||||
// ... There should be one active query
|
||||
Assert.Equal(1, queryService.ActiveQueries.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async void QueryExecuteUnconnectedUriTest()
|
||||
{
|
||||
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
// If:
|
||||
// ... I request to execute a query using a file URI that isn't connected
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), false, workspaceService.Object);
|
||||
var queryParams = new QueryExecuteParams { OwnerUri = "notConnected", QuerySelection = Common.WholeDocument };
|
||||
|
||||
object error = null;
|
||||
var requestContext = RequestContextMocks.Create<QueryExecuteResult>(null)
|
||||
.AddErrorHandling(e => error = e);
|
||||
await queryService.HandleExecuteRequest(queryParams, requestContext.Object);
|
||||
|
||||
// Then:
|
||||
// ... An error should have been returned
|
||||
// ... No result should have been returned
|
||||
// ... No completion event should have been fired
|
||||
// ... There should be no active queries
|
||||
VerifyQueryExecuteCallCount(requestContext, Times.Never(), Times.Never(), Times.Once());
|
||||
Assert.IsType<string>(error);
|
||||
Assert.NotEmpty((string)error);
|
||||
Assert.Empty(queryService.ActiveQueries);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async void QueryExecuteInProgressTest()
|
||||
{
|
||||
|
||||
// Set up file for returning the query
|
||||
var fileMock = new Mock<ScriptFile>();
|
||||
fileMock.SetupGet(file => file.Contents).Returns(Common.StandardQuery);
|
||||
// Set up workspace mock
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
|
||||
.Returns(fileMock.Object);
|
||||
|
||||
// If:
|
||||
// ... I request to execute a query
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, workspaceService.Object);
|
||||
var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QuerySelection = Common.WholeDocument };
|
||||
|
||||
// Note, we don't care about the results of the first request
|
||||
var firstRequestContext = RequestContextMocks.Create<QueryExecuteResult>(null);
|
||||
await AwaitExecution(queryService, queryParams, firstRequestContext.Object);
|
||||
|
||||
// ... And then I request another query without waiting for the first to complete
|
||||
queryService.ActiveQueries[Common.OwnerUri].HasExecuted = false; // Simulate query hasn't finished
|
||||
object error = null;
|
||||
var secondRequestContext = RequestContextMocks.Create<QueryExecuteResult>(null)
|
||||
.AddErrorHandling(e => error = e);
|
||||
await AwaitExecution(queryService, queryParams, secondRequestContext.Object);
|
||||
|
||||
// Then:
|
||||
// ... An error should have been sent
|
||||
// ... A result should have not have been sent
|
||||
// ... No completion event should have been fired
|
||||
// ... The original query should exist
|
||||
VerifyQueryExecuteCallCount(secondRequestContext, Times.Never(), Times.Never(), Times.Once());
|
||||
Assert.IsType<string>(error);
|
||||
Assert.NotEmpty((string)error);
|
||||
Assert.Contains(Common.OwnerUri, queryService.ActiveQueries.Keys);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async void QueryExecuteCompletedTest()
|
||||
{
|
||||
|
||||
// Set up file for returning the query
|
||||
var fileMock = new Mock<ScriptFile>();
|
||||
fileMock.SetupGet(file => file.Contents).Returns(Common.StandardQuery);
|
||||
// Set up workspace mock
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
|
||||
.Returns(fileMock.Object);
|
||||
|
||||
// If:
|
||||
// ... I request to execute a query
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, workspaceService.Object);
|
||||
var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QuerySelection = Common.WholeDocument };
|
||||
|
||||
// Note, we don't care about the results of the first request
|
||||
var firstRequestContext = RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(null, QueryExecuteCompleteEvent.Type, null, null);
|
||||
await AwaitExecution(queryService, queryParams, firstRequestContext.Object);
|
||||
|
||||
// ... And then I request another query after waiting for the first to complete
|
||||
QueryExecuteResult result = null;
|
||||
QueryExecuteCompleteParams complete = null;
|
||||
var secondRequestContext =
|
||||
RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(qer => result = qer, QueryExecuteCompleteEvent.Type, (et, qecp) => complete = qecp, null);
|
||||
await AwaitExecution(queryService, queryParams, secondRequestContext.Object);
|
||||
|
||||
// Then:
|
||||
// ... No errors should have been sent
|
||||
// ... A result should have been sent with no errors
|
||||
// ... There should only be one active query
|
||||
VerifyQueryExecuteCallCount(secondRequestContext, Times.Once(), Times.Once(), Times.Never());
|
||||
Assert.Null(result.Messages);
|
||||
Assert.False(complete.BatchSummaries.Any(b => b.HasError));
|
||||
Assert.Equal(1, queryService.ActiveQueries.Count);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
public async Task QueryExecuteMissingSelectionTest(SelectionData selection)
|
||||
{
|
||||
|
||||
// Set up file for returning the query
|
||||
var fileMock = new Mock<ScriptFile>();
|
||||
fileMock.SetupGet(file => file.Contents).Returns("");
|
||||
// Set up workspace mock
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
|
||||
.Returns(fileMock.Object);
|
||||
// If:
|
||||
// ... I request to execute a query with a missing query string
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, workspaceService.Object);
|
||||
var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QuerySelection = selection };
|
||||
|
||||
object errorResult = null;
|
||||
var requestContext = RequestContextMocks.Create<QueryExecuteResult>(null)
|
||||
.AddErrorHandling(error => errorResult = error);
|
||||
await queryService.HandleExecuteRequest(queryParams, requestContext.Object);
|
||||
|
||||
// Then:
|
||||
// ... Am error should have been sent
|
||||
// ... No result should have been sent
|
||||
// ... No completion event should have been fired
|
||||
// ... An active query should not have been added
|
||||
VerifyQueryExecuteCallCount(requestContext, Times.Never(), Times.Never(), Times.Once());
|
||||
Assert.NotNull(errorResult);
|
||||
Assert.IsType<string>(errorResult);
|
||||
Assert.DoesNotContain(Common.OwnerUri, queryService.ActiveQueries.Keys);
|
||||
|
||||
// ... There should not be an active query
|
||||
Assert.Empty(queryService.ActiveQueries);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async void QueryExecuteInvalidQueryTest()
|
||||
{
|
||||
// Set up file for returning the query
|
||||
var fileMock = new Mock<ScriptFile>();
|
||||
fileMock.SetupGet(file => file.Contents).Returns(Common.StandardQuery);
|
||||
// Set up workspace mock
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
|
||||
.Returns(fileMock.Object);
|
||||
// If:
|
||||
// ... I request to execute a query that is invalid
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, true), true, workspaceService.Object);
|
||||
var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QuerySelection = Common.WholeDocument };
|
||||
|
||||
QueryExecuteResult result = null;
|
||||
QueryExecuteCompleteParams complete = null;
|
||||
var requestContext =
|
||||
RequestContextMocks.SetupRequestContextMock<QueryExecuteResult, QueryExecuteCompleteParams>(qer => result = qer, QueryExecuteCompleteEvent.Type, (et, qecp) => complete = qecp, null);
|
||||
await AwaitExecution(queryService, queryParams, requestContext.Object);
|
||||
|
||||
// Then:
|
||||
// ... No errors should have been sent
|
||||
// ... A result should have been sent with success (we successfully started the query)
|
||||
// ... A completion event should have been sent with error
|
||||
VerifyQueryExecuteCallCount(requestContext, Times.Once(), Times.Once(), Times.Never());
|
||||
Assert.Null(result.Messages);
|
||||
Assert.Equal(1, complete.BatchSummaries.Length);
|
||||
Assert.True(complete.BatchSummaries[0].HasError);
|
||||
Assert.NotEmpty(complete.BatchSummaries[0].Messages);
|
||||
}
|
||||
|
||||
#if USE_LIVE_CONNECTION
|
||||
[Fact]
|
||||
@@ -693,28 +44,5 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
Assert.NotEmpty(query.BatchSummaries[0].Messages);
|
||||
}
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
private static void VerifyQueryExecuteCallCount(Mock<RequestContext<QueryExecuteResult>> mock, Times sendResultCalls, Times sendEventCalls, Times sendErrorCalls)
|
||||
{
|
||||
mock.Verify(rc => rc.SendResult(It.IsAny<QueryExecuteResult>()), sendResultCalls);
|
||||
mock.Verify(rc => rc.SendEvent(
|
||||
It.Is<EventType<QueryExecuteCompleteParams>>(m => m == QueryExecuteCompleteEvent.Type),
|
||||
It.IsAny<QueryExecuteCompleteParams>()), sendEventCalls);
|
||||
mock.Verify(rc => rc.SendError(It.IsAny<object>()), sendErrorCalls);
|
||||
}
|
||||
|
||||
private static DbConnection GetConnection(ConnectionInfo info)
|
||||
{
|
||||
return info.Factory.CreateSqlConnection(ConnectionService.BuildConnectionString(info.ConnectionDetails));
|
||||
}
|
||||
|
||||
private static async Task AwaitExecution(QueryExecutionService service, QueryExecuteParams qeParams,
|
||||
RequestContext<QueryExecuteResult> requestContext)
|
||||
{
|
||||
await service.HandleExecuteRequest(qeParams, requestContext);
|
||||
await service.ActiveQueries[qeParams.OwnerUri].ExecutionTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,399 @@
|
||||
//
|
||||
// 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.Common;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution
|
||||
{
|
||||
public class BatchTests
|
||||
{
|
||||
[Fact]
|
||||
public void BatchCreationTest()
|
||||
{
|
||||
// If I create a new batch...
|
||||
Batch batch = new Batch(Common.StandardQuery, Common.SubsectionDocument, Common.Ordinal, Common.GetFileStreamFactory(null));
|
||||
|
||||
// Then:
|
||||
// ... The text of the batch should be stored
|
||||
Assert.NotEmpty(batch.BatchText);
|
||||
|
||||
// ... It should not have executed and no error
|
||||
Assert.False(batch.HasExecuted, "The query should not have executed.");
|
||||
Assert.False(batch.HasError, "The batch should not have an error");
|
||||
|
||||
// ... The results should be empty
|
||||
Assert.Empty(batch.ResultSets);
|
||||
Assert.Empty(batch.ResultSummaries);
|
||||
Assert.Empty(batch.ResultMessages);
|
||||
|
||||
// ... The start line of the batch should be 0
|
||||
Assert.Equal(0, batch.Selection.StartLine);
|
||||
|
||||
// ... It's ordinal ID should be what I set it to
|
||||
Assert.Equal(Common.Ordinal, batch.Id);
|
||||
|
||||
// ... The summary should have the same info
|
||||
Assert.False(batch.Summary.HasError);
|
||||
Assert.Equal(Common.Ordinal, batch.Summary.Id);
|
||||
Assert.Null(batch.Summary.ResultSetSummaries);
|
||||
Assert.Null(batch.Summary.Messages);
|
||||
Assert.Equal(0, batch.Summary.Selection.StartLine);
|
||||
Assert.NotEqual(default(DateTime).ToString("o"), batch.Summary.ExecutionStart); // Should have been set at construction
|
||||
Assert.Null(batch.Summary.ExecutionEnd);
|
||||
Assert.Null(batch.Summary.ExecutionElapsed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Note: This test also tests the start notification feature
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void BatchExecuteNoResultSets()
|
||||
{
|
||||
// Setup:
|
||||
// ... Create a callback for batch start
|
||||
BatchSummary batchSummaryFromStart = null;
|
||||
Batch.BatchAsyncEventHandler batchStartCallback = b =>
|
||||
{
|
||||
batchSummaryFromStart = b.Summary;
|
||||
return Task.FromResult(0);
|
||||
};
|
||||
|
||||
// ... Create a callback for batch completion
|
||||
BatchSummary batchSummaryFromCompletion = null;
|
||||
Batch.BatchAsyncEventHandler batchCompleteCallback = b =>
|
||||
{
|
||||
batchSummaryFromCompletion = b.Summary;
|
||||
return Task.FromResult(0);
|
||||
};
|
||||
|
||||
// ... Create a callback for result completion
|
||||
bool resultCallbackFired = false;
|
||||
ResultSet.ResultSetAsyncEventHandler resultSetCallback = r =>
|
||||
{
|
||||
resultCallbackFired = true;
|
||||
return Task.FromResult(0);
|
||||
};
|
||||
|
||||
// If I execute a query that should get no result sets
|
||||
var fileStreamFactory = Common.GetFileStreamFactory(new Dictionary<string, byte[]>());
|
||||
Batch batch = new Batch(Common.StandardQuery, Common.SubsectionDocument, Common.Ordinal, fileStreamFactory);
|
||||
batch.BatchStart += batchStartCallback;
|
||||
batch.BatchCompletion += batchCompleteCallback;
|
||||
batch.ResultSetCompletion += resultSetCallback;
|
||||
batch.Execute(GetConnection(Common.CreateTestConnectionInfo(null, false)), CancellationToken.None).Wait();
|
||||
|
||||
// Then:
|
||||
// ... It should have executed without error
|
||||
Assert.True(batch.HasExecuted, "The query should have been marked executed.");
|
||||
Assert.False(batch.HasError, "The batch should not have an error");
|
||||
|
||||
// ... The results should be empty
|
||||
Assert.Empty(batch.ResultSets);
|
||||
Assert.Empty(batch.ResultSummaries);
|
||||
|
||||
// ... The results should not be null
|
||||
Assert.NotNull(batch.ResultSets);
|
||||
Assert.NotNull(batch.ResultSummaries);
|
||||
|
||||
// ... There should be a message for how many rows were affected
|
||||
Assert.Equal(1, batch.ResultMessages.Count());
|
||||
|
||||
// ... The callback for batch start should have been called
|
||||
// ... The info from it should have been basic
|
||||
Assert.NotNull(batchSummaryFromStart);
|
||||
Assert.False(batchSummaryFromStart.HasError);
|
||||
Assert.Equal(Common.Ordinal, batchSummaryFromStart.Id);
|
||||
Assert.Equal(Common.SubsectionDocument, batchSummaryFromStart.Selection);
|
||||
Assert.True(DateTime.Parse(batchSummaryFromStart.ExecutionStart) > default(DateTime));
|
||||
Assert.Null(batchSummaryFromStart.ResultSetSummaries);
|
||||
Assert.Null(batchSummaryFromStart.Messages);
|
||||
Assert.Null(batchSummaryFromStart.ExecutionElapsed);
|
||||
Assert.Null(batchSummaryFromStart.ExecutionEnd);
|
||||
|
||||
// ... The callback for batch completion should have been fired
|
||||
// ... The summary should match the expected info
|
||||
Assert.NotNull(batchSummaryFromCompletion);
|
||||
Assert.False(batchSummaryFromCompletion.HasError);
|
||||
Assert.Equal(Common.Ordinal, batchSummaryFromCompletion.Id);
|
||||
Assert.Equal(0, batchSummaryFromCompletion.ResultSetSummaries.Length);
|
||||
Assert.Equal(1, batchSummaryFromCompletion.Messages.Length);
|
||||
Assert.Equal(Common.SubsectionDocument, batchSummaryFromCompletion.Selection);
|
||||
Assert.True(DateTime.Parse(batchSummaryFromCompletion.ExecutionStart) > default(DateTime));
|
||||
Assert.True(DateTime.Parse(batchSummaryFromCompletion.ExecutionEnd) > default(DateTime));
|
||||
Assert.NotNull(batchSummaryFromCompletion.ExecutionElapsed);
|
||||
|
||||
// ... The callback for the result set should NOT have been fired
|
||||
Assert.False(resultCallbackFired);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BatchExecuteOneResultSet()
|
||||
{
|
||||
const int resultSets = 1;
|
||||
ConnectionInfo ci = Common.CreateTestConnectionInfo(new[] { Common.StandardTestData }, false);
|
||||
|
||||
// Setup: Create a callback for batch completion
|
||||
BatchSummary batchSummaryFromCallback = null;
|
||||
Batch.BatchAsyncEventHandler batchCallback = b =>
|
||||
{
|
||||
batchSummaryFromCallback = b.Summary;
|
||||
return Task.FromResult(0);
|
||||
};
|
||||
|
||||
// ... Create a callback for result set completion
|
||||
bool resultCallbackFired = false;
|
||||
ResultSet.ResultSetAsyncEventHandler resultSetCallback = r =>
|
||||
{
|
||||
resultCallbackFired = true;
|
||||
return Task.FromResult(0);
|
||||
};
|
||||
|
||||
// If I execute a query that should get one result set
|
||||
var fileStreamFactory = Common.GetFileStreamFactory(new Dictionary<string, byte[]>());
|
||||
Batch batch = new Batch(Common.StandardQuery, Common.SubsectionDocument, Common.Ordinal, fileStreamFactory);
|
||||
batch.BatchCompletion += batchCallback;
|
||||
batch.ResultSetCompletion += resultSetCallback;
|
||||
batch.Execute(GetConnection(ci), CancellationToken.None).Wait();
|
||||
|
||||
// Then:
|
||||
// ... It should have executed without error
|
||||
Assert.True(batch.HasExecuted, "The batch should have been marked executed.");
|
||||
Assert.False(batch.HasError, "The batch should not have an error");
|
||||
|
||||
// ... There should be exactly one result set
|
||||
Assert.Equal(resultSets, batch.ResultSets.Count);
|
||||
Assert.Equal(resultSets, batch.ResultSummaries.Length);
|
||||
|
||||
// ... Inside the result set should be with 5 rows
|
||||
Assert.Equal(Common.StandardRows, batch.ResultSets.First().RowCount);
|
||||
Assert.Equal(Common.StandardRows, batch.ResultSummaries[0].RowCount);
|
||||
|
||||
// ... Inside the result set should have 5 columns
|
||||
Assert.Equal(Common.StandardColumns, batch.ResultSets.First().Columns.Length);
|
||||
Assert.Equal(Common.StandardColumns, batch.ResultSummaries[0].ColumnInfo.Length);
|
||||
|
||||
// ... There should be a message for how many rows were affected
|
||||
Assert.Equal(resultSets, batch.ResultMessages.Count());
|
||||
|
||||
// ... The callback for batch completion should have been fired
|
||||
Assert.NotNull(batchSummaryFromCallback);
|
||||
|
||||
// ... The callback for resultset completion should have been fired
|
||||
Assert.True(resultCallbackFired); // We only want to validate that it happened, validation of the
|
||||
// summary is done in result set tests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BatchExecuteTwoResultSets()
|
||||
{
|
||||
var dataset = new[] { Common.StandardTestData, Common.StandardTestData };
|
||||
int resultSets = dataset.Length;
|
||||
ConnectionInfo ci = Common.CreateTestConnectionInfo(dataset, false);
|
||||
|
||||
// Setup: Create a callback for batch completion
|
||||
BatchSummary batchSummaryFromCallback = null;
|
||||
Batch.BatchAsyncEventHandler batchCallback = b =>
|
||||
{
|
||||
batchSummaryFromCallback = b.Summary;
|
||||
return Task.FromResult(0);
|
||||
};
|
||||
|
||||
// ... Create a callback for resultset completion
|
||||
int resultSummaryCount = 0;
|
||||
ResultSet.ResultSetAsyncEventHandler resultSetCallback = r =>
|
||||
{
|
||||
resultSummaryCount++;
|
||||
return Task.FromResult(0);
|
||||
};
|
||||
|
||||
// If I execute a query that should get two result sets
|
||||
var fileStreamFactory = Common.GetFileStreamFactory(new Dictionary<string, byte[]>());
|
||||
Batch batch = new Batch(Common.StandardQuery, Common.SubsectionDocument, Common.Ordinal, fileStreamFactory);
|
||||
batch.BatchCompletion += batchCallback;
|
||||
batch.ResultSetCompletion += resultSetCallback;
|
||||
batch.Execute(GetConnection(ci), CancellationToken.None).Wait();
|
||||
|
||||
// Then:
|
||||
// ... It should have executed without error
|
||||
Assert.True(batch.HasExecuted, "The batch should have been marked executed.");
|
||||
Assert.False(batch.HasError, "The batch should not have an error");
|
||||
|
||||
// ... There should be exactly two result sets
|
||||
Assert.Equal(resultSets, batch.ResultSets.Count());
|
||||
|
||||
foreach (ResultSet rs in batch.ResultSets)
|
||||
{
|
||||
// ... Each result set should have 5 rows
|
||||
Assert.Equal(Common.StandardRows, rs.RowCount);
|
||||
|
||||
// ... Inside each result set should be 5 columns
|
||||
Assert.Equal(Common.StandardColumns, rs.Columns.Length);
|
||||
}
|
||||
|
||||
// ... There should be exactly two result set summaries
|
||||
Assert.Equal(resultSets, batch.ResultSummaries.Length);
|
||||
|
||||
foreach (ResultSetSummary rs in batch.ResultSummaries)
|
||||
{
|
||||
// ... Inside each result summary, there should be 5 rows
|
||||
Assert.Equal(Common.StandardRows, rs.RowCount);
|
||||
|
||||
// ... Inside each result summary, there should be 5 column definitions
|
||||
Assert.Equal(Common.StandardColumns, rs.ColumnInfo.Length);
|
||||
}
|
||||
|
||||
// ... The callback for batch completion should have been fired
|
||||
Assert.NotNull(batchSummaryFromCallback);
|
||||
|
||||
// ... The callback for result set completion should have been fired
|
||||
Assert.Equal(2, resultSummaryCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BatchExecuteInvalidQuery()
|
||||
{
|
||||
// Setup:
|
||||
// ... Create a callback for batch start
|
||||
bool batchStartCalled = false;
|
||||
Batch.BatchAsyncEventHandler batchStartCallback = b =>
|
||||
{
|
||||
batchStartCalled = true;
|
||||
return Task.FromResult(0);
|
||||
};
|
||||
|
||||
// ... Create a callback for batch completion
|
||||
BatchSummary batchSummaryFromCallback = null;
|
||||
Batch.BatchAsyncEventHandler batchCompleteCallback = b =>
|
||||
{
|
||||
batchSummaryFromCallback = b.Summary;
|
||||
return Task.FromResult(0);
|
||||
};
|
||||
|
||||
// ... Create a callback that will fail the test if it's called
|
||||
ResultSet.ResultSetAsyncEventHandler resultSetCallback = r =>
|
||||
{
|
||||
throw new Exception("ResultSet callback was called when it should not have been.");
|
||||
};
|
||||
|
||||
ConnectionInfo ci = Common.CreateTestConnectionInfo(null, true);
|
||||
|
||||
// If I execute a batch that is invalid
|
||||
var fileStreamFactory = Common.GetFileStreamFactory(new Dictionary<string, byte[]>());
|
||||
Batch batch = new Batch(Common.StandardQuery, Common.SubsectionDocument, Common.Ordinal, fileStreamFactory);
|
||||
batch.BatchStart += batchStartCallback;
|
||||
batch.BatchCompletion += batchCompleteCallback;
|
||||
batch.ResultSetCompletion += resultSetCallback;
|
||||
batch.Execute(GetConnection(ci), CancellationToken.None).Wait();
|
||||
|
||||
// Then:
|
||||
// ... It should have executed with error
|
||||
Assert.True(batch.HasExecuted);
|
||||
Assert.True(batch.HasError);
|
||||
|
||||
// ... There should be no result sets
|
||||
Assert.Empty(batch.ResultSets);
|
||||
Assert.Empty(batch.ResultSummaries);
|
||||
|
||||
// ... There should be plenty of messages for the error
|
||||
Assert.NotEmpty(batch.ResultMessages);
|
||||
|
||||
// ... The callback for batch completion should have been fired
|
||||
Assert.NotNull(batchSummaryFromCallback);
|
||||
|
||||
// ... The callback for batch start should have been fired
|
||||
Assert.True(batchStartCalled);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BatchExecuteExecuted()
|
||||
{
|
||||
ConnectionInfo ci = Common.CreateTestConnectionInfo(new[] { Common.StandardTestData }, false);
|
||||
|
||||
// If I execute a batch
|
||||
var fileStreamFactory = Common.GetFileStreamFactory(new Dictionary<string, byte[]>());
|
||||
Batch batch = new Batch(Common.StandardQuery, Common.SubsectionDocument, Common.Ordinal, fileStreamFactory);
|
||||
batch.Execute(GetConnection(ci), CancellationToken.None).Wait();
|
||||
|
||||
// Then:
|
||||
// ... It should have executed without error
|
||||
Assert.True(batch.HasExecuted, "The batch should have been marked executed.");
|
||||
Assert.False(batch.HasError, "The batch should not have an error");
|
||||
|
||||
// Setup for part 2:
|
||||
// ... Create a callback for batch completion
|
||||
Batch.BatchAsyncEventHandler completeCallback = b =>
|
||||
{
|
||||
throw new Exception("Batch completion callback should not have been called");
|
||||
};
|
||||
|
||||
// ... Create a callback for batch start
|
||||
Batch.BatchAsyncEventHandler startCallback = b =>
|
||||
{
|
||||
throw new Exception("Batch start callback should not have been called");
|
||||
};
|
||||
|
||||
// If I execute it again
|
||||
// Then:
|
||||
// ... It should throw an invalid operation exception
|
||||
batch.BatchStart += startCallback;
|
||||
batch.BatchCompletion += completeCallback;
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
||||
batch.Execute(GetConnection(ci), CancellationToken.None));
|
||||
|
||||
// ... The data should still be available without error
|
||||
Assert.False(batch.HasError, "The batch should not be in an error condition");
|
||||
Assert.True(batch.HasExecuted, "The batch should still be marked executed.");
|
||||
Assert.NotEmpty(batch.ResultSets);
|
||||
Assert.NotEmpty(batch.ResultSummaries);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData(null)]
|
||||
public void BatchExecuteNoSql(string query)
|
||||
{
|
||||
// If:
|
||||
// ... I create a batch that has an empty query
|
||||
// Then:
|
||||
// ... It should throw an exception
|
||||
Assert.Throws<ArgumentException>(() => new Batch(query, Common.SubsectionDocument, Common.Ordinal, Common.GetFileStreamFactory(null)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BatchNoBufferFactory()
|
||||
{
|
||||
// If:
|
||||
// ... I create a batch that has no file stream factory
|
||||
// Then:
|
||||
// ... It should throw an exception
|
||||
Assert.Throws<ArgumentNullException>(() => new Batch("stuff", Common.SubsectionDocument, Common.Ordinal, null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BatchInvalidOrdinal()
|
||||
{
|
||||
// If:
|
||||
// ... I create a batch has has an ordinal less than 0
|
||||
// Then:
|
||||
// ... It should throw an exception
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => new Batch("stuff", Common.SubsectionDocument, -1, Common.GetFileStreamFactory(null)));
|
||||
}
|
||||
|
||||
private static DbConnection GetConnection(ConnectionInfo info)
|
||||
{
|
||||
return info.Factory.CreateSqlConnection(ConnectionService.BuildConnectionString(info.ConnectionDetails));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,316 @@
|
||||
//
|
||||
// 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.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution
|
||||
{
|
||||
public class QueryTests
|
||||
{
|
||||
|
||||
[Fact]
|
||||
public void QueryExecuteNoQueryText()
|
||||
{
|
||||
// If:
|
||||
// ... I create a query that has a null query text
|
||||
// Then:
|
||||
// ... It should throw an exception
|
||||
Assert.Throws<ArgumentException>(() =>
|
||||
new Query(null, Common.CreateTestConnectionInfo(null, false), new QueryExecutionSettings(), Common.GetFileStreamFactory(null)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void QueryExecuteNoConnectionInfo()
|
||||
{
|
||||
// If:
|
||||
// ... I create a query that has a null connection info
|
||||
// Then:
|
||||
// ... It should throw an exception
|
||||
Assert.Throws<ArgumentNullException>(() => new Query("Some Query", null, new QueryExecutionSettings(), Common.GetFileStreamFactory(null)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void QueryExecuteNoSettings()
|
||||
{
|
||||
// If:
|
||||
// ... I create a query that has a null settings
|
||||
// Then:
|
||||
// ... It should throw an exception
|
||||
Assert.Throws<ArgumentNullException>(() =>
|
||||
new Query("Some query", Common.CreateTestConnectionInfo(null, false), null, Common.GetFileStreamFactory(null)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void QueryExecuteNoBufferFactory()
|
||||
{
|
||||
// If:
|
||||
// ... I create a query that has a null file stream factory
|
||||
// Then:
|
||||
// ... It should throw an exception
|
||||
Assert.Throws<ArgumentNullException>(() =>
|
||||
new Query("Some query", Common.CreateTestConnectionInfo(null, false), new QueryExecutionSettings(), null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void QueryExecuteSingleBatch()
|
||||
{
|
||||
// Setup:
|
||||
// ... Create a callback for atch start
|
||||
int batchStartCallbacksReceived = 0;
|
||||
Batch.BatchAsyncEventHandler batchStartCallback = b =>
|
||||
{
|
||||
batchStartCallbacksReceived++;
|
||||
return Task.FromResult(0);
|
||||
};
|
||||
|
||||
// ... Create a callback for batch completion
|
||||
int batchCompleteCallbacksReceived = 0;
|
||||
Batch.BatchAsyncEventHandler batchCompleteCallback = summary =>
|
||||
{
|
||||
batchCompleteCallbacksReceived++;
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
// If:
|
||||
// ... I create a query from a single batch (without separator)
|
||||
ConnectionInfo ci = Common.CreateTestConnectionInfo(null, false);
|
||||
var fileStreamFactory = Common.GetFileStreamFactory(new Dictionary<string, byte[]>());
|
||||
Query query = new Query(Common.StandardQuery, ci, new QueryExecutionSettings(), fileStreamFactory);
|
||||
query.BatchStarted += batchStartCallback;
|
||||
query.BatchCompleted += batchCompleteCallback;
|
||||
|
||||
// Then:
|
||||
// ... I should get a single batch to execute that hasn't been executed
|
||||
Assert.NotEmpty(query.QueryText);
|
||||
Assert.NotEmpty(query.Batches);
|
||||
Assert.Equal(1, query.Batches.Length);
|
||||
Assert.False(query.HasExecuted);
|
||||
Assert.Throws<InvalidOperationException>(() => query.BatchSummaries);
|
||||
|
||||
// If:
|
||||
// ... I then execute the query
|
||||
query.Execute();
|
||||
query.ExecutionTask.Wait();
|
||||
|
||||
// Then:
|
||||
// ... The query should have completed successfully with one batch summary returned
|
||||
Assert.True(query.HasExecuted);
|
||||
Assert.NotEmpty(query.BatchSummaries);
|
||||
Assert.Equal(1, query.BatchSummaries.Length);
|
||||
|
||||
// ... The batch callbacks should have been called precisely 1 time
|
||||
Assert.Equal(1, batchStartCallbacksReceived);
|
||||
Assert.Equal(1, batchCompleteCallbacksReceived);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void QueryExecuteNoOpBatch()
|
||||
{
|
||||
// Setup:
|
||||
// ... Create a callback for batch startup
|
||||
Batch.BatchAsyncEventHandler batchStartCallback = b =>
|
||||
{
|
||||
throw new Exception("Batch startup callback should not have been called.");
|
||||
};
|
||||
|
||||
// ... Create a callback for batch completion
|
||||
Batch.BatchAsyncEventHandler batchCompletionCallback = summary =>
|
||||
{
|
||||
throw new Exception("Batch completion callback was called");
|
||||
};
|
||||
|
||||
// If:
|
||||
// ... I create a query from a single batch that does nothing
|
||||
ConnectionInfo ci = Common.CreateTestConnectionInfo(null, false);
|
||||
var fileStreamFactory = Common.GetFileStreamFactory(new Dictionary<string, byte[]>());
|
||||
Query query = new Query(Common.NoOpQuery, ci, new QueryExecutionSettings(), fileStreamFactory);
|
||||
query.BatchStarted += batchStartCallback;
|
||||
query.BatchCompleted += batchCompletionCallback;
|
||||
|
||||
// Then:
|
||||
// ... I should get no batches back
|
||||
Assert.NotEmpty(query.QueryText);
|
||||
Assert.Empty(query.Batches);
|
||||
Assert.False(query.HasExecuted);
|
||||
Assert.Throws<InvalidOperationException>(() => query.BatchSummaries);
|
||||
|
||||
// If:
|
||||
// ... I Then execute the query
|
||||
query.Execute();
|
||||
query.ExecutionTask.Wait();
|
||||
|
||||
// Then:
|
||||
// ... The query should have completed successfully with no batch summaries returned
|
||||
Assert.True(query.HasExecuted);
|
||||
Assert.Empty(query.BatchSummaries);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void QueryExecuteMultipleBatches()
|
||||
{
|
||||
// Setup:
|
||||
// ... Create a callback for batch start
|
||||
int batchStartCallbacksReceived = 0;
|
||||
Batch.BatchAsyncEventHandler batchStartCallback = b =>
|
||||
{
|
||||
batchStartCallbacksReceived++;
|
||||
return Task.FromResult(0);
|
||||
};
|
||||
|
||||
// ... Create a callback for batch completion
|
||||
int batchCompletedCallbacksReceived = 0;
|
||||
Batch.BatchAsyncEventHandler batchCompletedCallback = summary =>
|
||||
{
|
||||
batchCompletedCallbacksReceived++;
|
||||
return Task.FromResult(0);
|
||||
};
|
||||
|
||||
// If:
|
||||
// ... I create a query from two batches (with separator)
|
||||
ConnectionInfo ci = Common.CreateTestConnectionInfo(null, false);
|
||||
string queryText = string.Format("{0}\r\nGO\r\n{0}", Common.StandardQuery);
|
||||
var fileStreamFactory = Common.GetFileStreamFactory(new Dictionary<string, byte[]>());
|
||||
Query query = new Query(queryText, ci, new QueryExecutionSettings(), fileStreamFactory);
|
||||
query.BatchStarted += batchStartCallback;
|
||||
query.BatchCompleted += batchCompletedCallback;
|
||||
|
||||
// Then:
|
||||
// ... I should get back two batches to execute that haven't been executed
|
||||
Assert.NotEmpty(query.QueryText);
|
||||
Assert.NotEmpty(query.Batches);
|
||||
Assert.Equal(2, query.Batches.Length);
|
||||
Assert.False(query.HasExecuted);
|
||||
Assert.Throws<InvalidOperationException>(() => query.BatchSummaries);
|
||||
|
||||
// If:
|
||||
// ... I then execute the query
|
||||
query.Execute();
|
||||
query.ExecutionTask.Wait();
|
||||
|
||||
// Then:
|
||||
// ... The query should have completed successfully with two batch summaries returned
|
||||
Assert.True(query.HasExecuted);
|
||||
Assert.NotEmpty(query.BatchSummaries);
|
||||
Assert.Equal(2, query.BatchSummaries.Length);
|
||||
|
||||
// ... The batch start and completion callbacks should have been called precisely 2 times
|
||||
Assert.Equal(2, batchStartCallbacksReceived);
|
||||
Assert.Equal(2, batchCompletedCallbacksReceived);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void QueryExecuteMultipleBatchesWithNoOp()
|
||||
{
|
||||
// Setup:
|
||||
// ... Create a callback for batch start
|
||||
int batchStartCallbacksReceived = 0;
|
||||
Batch.BatchAsyncEventHandler batchStartCallback = b =>
|
||||
{
|
||||
batchStartCallbacksReceived++;
|
||||
return Task.FromResult(0);
|
||||
};
|
||||
|
||||
// ... Create a callback for batch completion
|
||||
int batchCompletionCallbacksReceived = 0;
|
||||
Batch.BatchAsyncEventHandler batchCompletionCallback = summary =>
|
||||
{
|
||||
batchCompletionCallbacksReceived++;
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
// If:
|
||||
// ... I create a query from a two batches (with separator)
|
||||
ConnectionInfo ci = Common.CreateTestConnectionInfo(null, false);
|
||||
string queryText = string.Format("{0}\r\nGO\r\n{1}", Common.StandardQuery, Common.NoOpQuery);
|
||||
var fileStreamFactory = Common.GetFileStreamFactory(new Dictionary<string, byte[]>());
|
||||
Query query = new Query(queryText, ci, new QueryExecutionSettings(), fileStreamFactory);
|
||||
query.BatchStarted += batchStartCallback;
|
||||
query.BatchCompleted += batchCompletionCallback;
|
||||
|
||||
// Then:
|
||||
// ... I should get back one batch to execute that hasn't been executed
|
||||
Assert.NotEmpty(query.QueryText);
|
||||
Assert.NotEmpty(query.Batches);
|
||||
Assert.Equal(1, query.Batches.Length);
|
||||
Assert.False(query.HasExecuted);
|
||||
Assert.Throws<InvalidOperationException>(() => query.BatchSummaries);
|
||||
|
||||
// If:
|
||||
// .. I then execute the query
|
||||
query.Execute();
|
||||
query.ExecutionTask.Wait();
|
||||
|
||||
// ... The query should have completed successfully with one batch summary returned
|
||||
Assert.True(query.HasExecuted);
|
||||
Assert.NotEmpty(query.BatchSummaries);
|
||||
Assert.Equal(1, query.BatchSummaries.Length);
|
||||
|
||||
// ... The batch callbacks should have been called precisely 1 time
|
||||
Assert.Equal(1, batchStartCallbacksReceived);
|
||||
Assert.Equal(1, batchCompletionCallbacksReceived);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void QueryExecuteInvalidBatch()
|
||||
{
|
||||
// Setup:
|
||||
// ... Create a callback for batch start
|
||||
int batchStartCallbacksReceived = 0;
|
||||
Batch.BatchAsyncEventHandler batchStartCallback = b =>
|
||||
{
|
||||
batchStartCallbacksReceived++;
|
||||
return Task.FromResult(0);
|
||||
};
|
||||
|
||||
// ... Create a callback for batch completion
|
||||
int batchCompletionCallbacksReceived = 0;
|
||||
Batch.BatchAsyncEventHandler batchCompltionCallback = summary =>
|
||||
{
|
||||
batchCompletionCallbacksReceived++;
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
// If:
|
||||
// ... I create a query from an invalid batch
|
||||
ConnectionInfo ci = Common.CreateTestConnectionInfo(null, true);
|
||||
var fileStreamFactory = Common.GetFileStreamFactory(new Dictionary<string, byte[]>());
|
||||
Query query = new Query(Common.InvalidQuery, ci, new QueryExecutionSettings(), fileStreamFactory);
|
||||
query.BatchStarted += batchStartCallback;
|
||||
query.BatchCompleted += batchCompltionCallback;
|
||||
|
||||
// Then:
|
||||
// ... I should get back a query with one batch not executed
|
||||
Assert.NotEmpty(query.QueryText);
|
||||
Assert.NotEmpty(query.Batches);
|
||||
Assert.Equal(1, query.Batches.Length);
|
||||
Assert.False(query.HasExecuted);
|
||||
Assert.Throws<InvalidOperationException>(() => query.BatchSummaries);
|
||||
|
||||
// If:
|
||||
// ... I then execute the query
|
||||
query.Execute();
|
||||
query.ExecutionTask.Wait();
|
||||
|
||||
// Then:
|
||||
// ... There should be an error on the batch
|
||||
Assert.True(query.HasExecuted);
|
||||
Assert.NotEmpty(query.BatchSummaries);
|
||||
Assert.Equal(1, query.BatchSummaries.Length);
|
||||
Assert.True(query.BatchSummaries[0].HasError);
|
||||
Assert.NotEmpty(query.BatchSummaries[0].Messages);
|
||||
|
||||
// ... The batch callbacks should have been called once
|
||||
Assert.Equal(1, batchStartCallbacksReceived);
|
||||
Assert.Equal(1, batchCompletionCallbacksReceived);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
//
|
||||
// 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.Common;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution
|
||||
{
|
||||
public class ResultSetTests
|
||||
{
|
||||
[Fact]
|
||||
public void ResultCreation()
|
||||
{
|
||||
// If:
|
||||
// ... I create a new result set with a valid db data reader
|
||||
DbDataReader mockReader = GetReader(null, false, string.Empty);
|
||||
ResultSet resultSet = new ResultSet(mockReader, Common.Ordinal, Common.Ordinal, Common.GetFileStreamFactory(new Dictionary<string, byte[]>()));
|
||||
|
||||
// Then:
|
||||
// ... There should not be any data read yet
|
||||
Assert.Null(resultSet.Columns);
|
||||
Assert.Equal(0, resultSet.RowCount);
|
||||
Assert.Equal(Common.Ordinal, resultSet.Id);
|
||||
|
||||
// ... The summary should include the same info
|
||||
Assert.Null(resultSet.Summary.ColumnInfo);
|
||||
Assert.Equal(0, resultSet.Summary.RowCount);
|
||||
Assert.Equal(Common.Ordinal, resultSet.Summary.Id);
|
||||
Assert.Equal(Common.Ordinal, resultSet.Summary.BatchId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResultCreationInvalidReader()
|
||||
{
|
||||
// If:
|
||||
// ... I create a new result set without a reader
|
||||
// Then:
|
||||
// ... It should throw an exception
|
||||
Assert.Throws<ArgumentNullException>(() => new ResultSet(null, Common.Ordinal, Common.Ordinal, null));
|
||||
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadToEndSuccess()
|
||||
{
|
||||
// Setup: Create a callback for resultset completion
|
||||
ResultSetSummary resultSummaryFromCallback = null;
|
||||
ResultSet.ResultSetAsyncEventHandler callback = r =>
|
||||
{
|
||||
resultSummaryFromCallback = r.Summary;
|
||||
return Task.FromResult(0);
|
||||
};
|
||||
|
||||
// If:
|
||||
// ... I create a new resultset with a valid db data reader that has data
|
||||
// ... and I read it to the end
|
||||
DbDataReader mockReader = GetReader(new [] {Common.StandardTestData}, false, Common.StandardQuery);
|
||||
var fileStreamFactory = Common.GetFileStreamFactory(new Dictionary<string, byte[]>());
|
||||
ResultSet resultSet = new ResultSet(mockReader, Common.Ordinal, Common.Ordinal, fileStreamFactory);
|
||||
resultSet.ResultCompletion += callback;
|
||||
await resultSet.ReadResultToEnd(CancellationToken.None);
|
||||
|
||||
// Then:
|
||||
// ... The columns should be set
|
||||
// ... There should be rows to read back
|
||||
Assert.NotNull(resultSet.Columns);
|
||||
Assert.Equal(Common.StandardColumns, resultSet.Columns.Length);
|
||||
Assert.Equal(Common.StandardRows, resultSet.RowCount);
|
||||
|
||||
// ... The summary should have the same info
|
||||
Assert.NotNull(resultSet.Summary.ColumnInfo);
|
||||
Assert.Equal(Common.StandardColumns, resultSet.Summary.ColumnInfo.Length);
|
||||
Assert.Equal(Common.StandardRows, resultSet.Summary.RowCount);
|
||||
|
||||
// ... The callback for result set completion should have been fired
|
||||
Assert.NotNull(resultSummaryFromCallback);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("JSON")]
|
||||
[InlineData("XML")]
|
||||
public async Task ReadToEndForXmlJson(string forType)
|
||||
{
|
||||
// Setup:
|
||||
// ... Build a FOR XML or FOR JSON data set
|
||||
string columnName = string.Format("{0}_F52E2B61-18A1-11d1-B105-00805F49916B", forType);
|
||||
List<Dictionary<string, string>> data = new List<Dictionary<string, string>>();
|
||||
for(int i = 0; i < Common.StandardRows; i++)
|
||||
{
|
||||
data.Add(new Dictionary<string, string> { { columnName, "test data"} });
|
||||
}
|
||||
Dictionary<string, string>[][] dataSets = {data.ToArray()};
|
||||
|
||||
// ... Create a callback for resultset completion
|
||||
ResultSetSummary resultSummary = null;
|
||||
ResultSet.ResultSetAsyncEventHandler callback = r =>
|
||||
{
|
||||
resultSummary = r.Summary;
|
||||
return Task.FromResult(0);
|
||||
};
|
||||
|
||||
// If:
|
||||
// ... I create a new resultset with a valid db data reader that is FOR XML/JSON
|
||||
// ... and I read it to the end
|
||||
DbDataReader mockReader = GetReader(dataSets, false, Common.StandardQuery);
|
||||
var fileStreamFactory = Common.GetFileStreamFactory(new Dictionary<string, byte[]>());
|
||||
ResultSet resultSet = new ResultSet(mockReader, Common.Ordinal, Common.Ordinal, fileStreamFactory);
|
||||
resultSet.ResultCompletion += callback;
|
||||
await resultSet.ReadResultToEnd(CancellationToken.None);
|
||||
|
||||
// Then:
|
||||
// ... There should only be one column
|
||||
// ... There should only be one row
|
||||
// ... The result should be marked as complete
|
||||
Assert.Equal(1, resultSet.Columns.Length);
|
||||
Assert.Equal(1, resultSet.RowCount);
|
||||
|
||||
// ... The callback should have been called
|
||||
Assert.NotNull(resultSummary);
|
||||
|
||||
// If:
|
||||
// ... I attempt to read back the results
|
||||
// Then:
|
||||
// ... I should only get one row
|
||||
var subset = await resultSet.GetSubset(0, 10);
|
||||
Assert.Equal(1, subset.RowCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetSubsetWithoutExecution()
|
||||
{
|
||||
// If:
|
||||
// ... I create a new result set with a valid db data reader without executing it
|
||||
DbDataReader mockReader = GetReader(null, false, string.Empty);
|
||||
var fileStreamFactory = Common.GetFileStreamFactory(new Dictionary<string, byte[]>());
|
||||
ResultSet resultSet = new ResultSet(mockReader, Common.Ordinal, Common.Ordinal, fileStreamFactory);
|
||||
|
||||
// Then:
|
||||
// ... Attempting to read a subset should fail miserably
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() => resultSet.GetSubset(0, 0));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(-1, 0)] // Too small start row
|
||||
[InlineData(20, 0)] // Too large start row
|
||||
[InlineData(0, -1)] // Negative row count
|
||||
public async Task GetSubsetInvalidParameters(int startRow, int rowCount)
|
||||
{
|
||||
// If:
|
||||
// ... I create a new result set with a valid db data reader
|
||||
// ... And execute the result
|
||||
DbDataReader mockReader = GetReader(new[] {Common.StandardTestData}, false, Common.StandardQuery);
|
||||
var fileStreamFactory = Common.GetFileStreamFactory(new Dictionary<string, byte[]>());
|
||||
ResultSet resultSet = new ResultSet(mockReader, Common.Ordinal, Common.Ordinal, fileStreamFactory);
|
||||
await resultSet.ReadResultToEnd(CancellationToken.None);
|
||||
|
||||
// ... And attempt to get a subset with invalid parameters
|
||||
// Then:
|
||||
// ... It should throw an exception for an invalid parameter
|
||||
await Assert.ThrowsAsync<ArgumentOutOfRangeException>(() => resultSet.GetSubset(startRow, rowCount));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 3)] // Standard scenario, 3 rows should come back
|
||||
[InlineData(0, 20)] // Asking for too many rows, 5 rows should come back
|
||||
[InlineData(1, 3)] // Standard scenario from non-zero start
|
||||
[InlineData(1, 20)] // Asking for too many rows at a non-zero start
|
||||
public async Task GetSubsetSuccess(int startRow, int rowCount)
|
||||
{
|
||||
// If:
|
||||
// ... I create a new result set with a valid db data reader
|
||||
// ... And execute the result set
|
||||
DbDataReader mockReader = GetReader(new[] { Common.StandardTestData }, false, Common.StandardQuery);
|
||||
var fileStreamFactory = Common.GetFileStreamFactory(new Dictionary<string, byte[]>());
|
||||
ResultSet resultSet = new ResultSet(mockReader, Common.Ordinal, Common.Ordinal, fileStreamFactory);
|
||||
await resultSet.ReadResultToEnd(CancellationToken.None);
|
||||
|
||||
// ... And attempt to get a subset with valid number of rows
|
||||
ResultSetSubset subset = await resultSet.GetSubset(startRow, rowCount);
|
||||
|
||||
// Then:
|
||||
// ... There should be rows in the subset, either the number of rows or the number of
|
||||
// rows requested or the number of rows in the result set, whichever is lower
|
||||
long availableRowsFromStart = resultSet.RowCount - startRow;
|
||||
Assert.Equal(Math.Min(availableRowsFromStart, rowCount), subset.RowCount);
|
||||
|
||||
// ... The rows should have the same number of columns as the resultset
|
||||
Assert.Equal(resultSet.Columns.Length, subset.Rows[0].Length);
|
||||
}
|
||||
|
||||
private static DbDataReader GetReader(Dictionary<string, string>[][] dataSet, bool throwOnRead, string query)
|
||||
{
|
||||
var info = Common.CreateTestConnectionInfo(dataSet, throwOnRead);
|
||||
var connection = info.Factory.CreateSqlConnection(ConnectionService.BuildConnectionString(info.ConnectionDetails));
|
||||
var command = connection.CreateCommand();
|
||||
command.CommandText = query;
|
||||
return command.ExecuteReader();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,481 @@
|
||||
//
|
||||
// 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.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution
|
||||
{
|
||||
public class ServiceIntegrationTests
|
||||
{
|
||||
|
||||
[Fact]
|
||||
public async void QueryExecuteSingleBatchNoResultsTest()
|
||||
{
|
||||
// Given:
|
||||
// ... Default settings are stored in the workspace service
|
||||
// ... A workspace with a standard query is configured
|
||||
WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings = new SqlToolsSettings();
|
||||
var workspaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery);
|
||||
|
||||
// If:
|
||||
// ... I request to execute a valid query with no results
|
||||
var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService);
|
||||
var queryParams = new QueryExecuteParams { QuerySelection = Common.WholeDocument, OwnerUri = Common.OwnerUri };
|
||||
|
||||
QueryExecuteResult result = null;
|
||||
QueryExecuteCompleteParams completeParams = null;
|
||||
QueryExecuteBatchNotificationParams batchStartParams = null;
|
||||
QueryExecuteBatchNotificationParams batchCompleteParams = null;
|
||||
var requestContext = RequestContextMocks.Create<QueryExecuteResult>(qer => result = qer)
|
||||
.AddEventHandling(QueryExecuteCompleteEvent.Type, (et, p) => completeParams = p)
|
||||
.AddEventHandling(QueryExecuteBatchStartEvent.Type, (et, p) => batchStartParams = p)
|
||||
.AddEventHandling(QueryExecuteBatchCompleteEvent.Type, (et, p) => batchCompleteParams = p)
|
||||
.AddEventHandling(QueryExecuteResultSetCompleteEvent.Type, null);
|
||||
await Common.AwaitExecution(queryService, queryParams, requestContext.Object);
|
||||
|
||||
// Then:
|
||||
// ... No Errors should have been sent
|
||||
// ... A successful result should have been sent with messages on the first batch
|
||||
// ... A completion event should have been fired with empty results
|
||||
// ... A batch completion event should have been fired with empty results
|
||||
// ... A result set completion event should not have been fired
|
||||
VerifyQueryExecuteCallCount(requestContext, Times.Once(), Times.Once(), Times.Once(), Times.Once(), Times.Never(), Times.Never());
|
||||
Assert.Null(result.Messages);
|
||||
|
||||
Assert.Equal(1, completeParams.BatchSummaries.Length);
|
||||
Assert.Empty(completeParams.BatchSummaries[0].ResultSetSummaries);
|
||||
Assert.NotEmpty(completeParams.BatchSummaries[0].Messages);
|
||||
|
||||
// ... Batch start summary should not contain result sets, messages, but should contain owner URI
|
||||
Assert.NotNull(batchStartParams);
|
||||
Assert.NotNull(batchStartParams.BatchSummary);
|
||||
Assert.Null(batchStartParams.BatchSummary.Messages);
|
||||
Assert.Null(batchStartParams.BatchSummary.ResultSetSummaries);
|
||||
Assert.Equal(Common.OwnerUri, batchStartParams.OwnerUri);
|
||||
|
||||
// ... Batch completion summary should contain result sets, messages, and the owner URI
|
||||
Assert.NotNull(batchCompleteParams);
|
||||
Assert.NotNull(batchCompleteParams.BatchSummary);
|
||||
Assert.Empty(batchCompleteParams.BatchSummary.ResultSetSummaries);
|
||||
Assert.NotEmpty(batchCompleteParams.BatchSummary.Messages);
|
||||
Assert.Equal(Common.OwnerUri, batchCompleteParams.OwnerUri);
|
||||
|
||||
// ... There should be one active query
|
||||
Assert.Equal(1, queryService.ActiveQueries.Count);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public async void QueryExecuteSingleBatchSingleResultTest()
|
||||
{
|
||||
// Given:
|
||||
// ... A workspace with a standard query is configured
|
||||
var workspaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery);
|
||||
|
||||
// If:
|
||||
// ... I request to execute a valid query with results
|
||||
var queryService = Common.GetPrimedExecutionService(new[] { Common.StandardTestData }, true, false, workspaceService);
|
||||
var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QuerySelection = Common.WholeDocument };
|
||||
|
||||
QueryExecuteResult result = null;
|
||||
QueryExecuteCompleteParams completeParams = null;
|
||||
QueryExecuteBatchNotificationParams batchStartParams = null;
|
||||
QueryExecuteBatchNotificationParams batchCompleteParams = null;
|
||||
QueryExecuteResultSetCompleteParams resultCompleteParams = null;
|
||||
var requestContext = RequestContextMocks.Create<QueryExecuteResult>(qer => result = qer)
|
||||
.AddEventHandling(QueryExecuteCompleteEvent.Type, (et, p) => completeParams = p)
|
||||
.AddEventHandling(QueryExecuteBatchStartEvent.Type, (et, p) => batchStartParams = p)
|
||||
.AddEventHandling(QueryExecuteBatchCompleteEvent.Type, (et, p) => batchCompleteParams = p)
|
||||
.AddEventHandling(QueryExecuteResultSetCompleteEvent.Type, (et, p) => resultCompleteParams = p);
|
||||
await Common.AwaitExecution(queryService, queryParams, requestContext.Object);
|
||||
|
||||
// Then:
|
||||
// ... No errors should have been sent
|
||||
// ... A successful result should have been sent without messages
|
||||
// ... A completion event should have been fired with one result
|
||||
// ... A batch completion event should have been fired
|
||||
// ... A resultset completion event should have been fired
|
||||
VerifyQueryExecuteCallCount(requestContext, Times.Once(), Times.Once(), Times.Once(), Times.Once(), Times.Once(), Times.Never());
|
||||
Assert.Null(result.Messages);
|
||||
|
||||
Assert.Equal(1, completeParams.BatchSummaries.Length);
|
||||
Assert.NotEmpty(completeParams.BatchSummaries[0].ResultSetSummaries);
|
||||
Assert.NotEmpty(completeParams.BatchSummaries[0].Messages);
|
||||
Assert.False(completeParams.BatchSummaries[0].HasError);
|
||||
|
||||
// ... Batch start summary should not contain result sets, messages, but should contain owner URI
|
||||
Assert.NotNull(batchStartParams);
|
||||
Assert.NotNull(batchStartParams.BatchSummary);
|
||||
Assert.Null(batchStartParams.BatchSummary.Messages);
|
||||
Assert.Null(batchStartParams.BatchSummary.ResultSetSummaries);
|
||||
Assert.Equal(Common.OwnerUri, batchStartParams.OwnerUri);
|
||||
|
||||
Assert.NotNull(batchCompleteParams);
|
||||
Assert.NotEmpty(batchCompleteParams.BatchSummary.ResultSetSummaries);
|
||||
Assert.NotEmpty(batchCompleteParams.BatchSummary.Messages);
|
||||
Assert.Equal(Common.OwnerUri, batchCompleteParams.OwnerUri);
|
||||
|
||||
Assert.NotNull(resultCompleteParams);
|
||||
Assert.Equal(Common.StandardColumns, resultCompleteParams.ResultSetSummary.ColumnInfo.Length);
|
||||
Assert.Equal(Common.StandardRows, resultCompleteParams.ResultSetSummary.RowCount);
|
||||
Assert.Equal(Common.OwnerUri, resultCompleteParams.OwnerUri);
|
||||
|
||||
// ... There should be one active query
|
||||
Assert.Equal(1, queryService.ActiveQueries.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task QueryExecuteSingleBatchMultipleResultTest()
|
||||
{
|
||||
// Given:
|
||||
// ... A workspace with a standard query is configured
|
||||
var workspaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery);
|
||||
|
||||
// If:
|
||||
// ... I request to execute a valid query with one batch and multiple result sets
|
||||
var dataset = new[] { Common.StandardTestData, Common.StandardTestData };
|
||||
var queryService = Common.GetPrimedExecutionService(dataset, true, false, workspaceService);
|
||||
var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QuerySelection = Common.WholeDocument };
|
||||
|
||||
QueryExecuteResult result = null;
|
||||
QueryExecuteCompleteParams completeParams = null;
|
||||
QueryExecuteBatchNotificationParams batchStartParams = null;
|
||||
QueryExecuteBatchNotificationParams batchCompleteParams = null;
|
||||
List<QueryExecuteResultSetCompleteParams> resultCompleteParams = new List<QueryExecuteResultSetCompleteParams>();
|
||||
var requestContext = RequestContextMocks.Create<QueryExecuteResult>(qer => result = qer)
|
||||
.AddEventHandling(QueryExecuteCompleteEvent.Type, (et, p) => completeParams = p)
|
||||
.AddEventHandling(QueryExecuteBatchStartEvent.Type, (et, p) => batchStartParams = p)
|
||||
.AddEventHandling(QueryExecuteBatchCompleteEvent.Type, (et, p) => batchCompleteParams = p)
|
||||
.AddEventHandling(QueryExecuteResultSetCompleteEvent.Type, (et, p) => resultCompleteParams.Add(p));
|
||||
await Common.AwaitExecution(queryService, queryParams, requestContext.Object);
|
||||
|
||||
// Then:
|
||||
// ... No errors should have been sent
|
||||
// ... A successful result should have been sent without messages
|
||||
// ... A completion event should have been fired with one result
|
||||
// ... A batch completion event should have been fired
|
||||
// ... Two resultset completion events should have been fired
|
||||
VerifyQueryExecuteCallCount(requestContext, Times.Once(), Times.Once(), Times.Once(), Times.Once(), Times.Exactly(2), Times.Never());
|
||||
Assert.Null(result.Messages);
|
||||
|
||||
Assert.Equal(1, completeParams.BatchSummaries.Length);
|
||||
Assert.NotEmpty(completeParams.BatchSummaries[0].ResultSetSummaries);
|
||||
Assert.NotEmpty(completeParams.BatchSummaries[0].Messages);
|
||||
Assert.False(completeParams.BatchSummaries[0].HasError);
|
||||
|
||||
// ... Batch start summary should not contain result sets, messages, but should contain owner URI
|
||||
Assert.NotNull(batchStartParams);
|
||||
Assert.NotNull(batchStartParams.BatchSummary);
|
||||
Assert.Null(batchStartParams.BatchSummary.Messages);
|
||||
Assert.Null(batchStartParams.BatchSummary.ResultSetSummaries);
|
||||
Assert.Equal(Common.OwnerUri, batchStartParams.OwnerUri);
|
||||
|
||||
Assert.NotNull(batchCompleteParams);
|
||||
Assert.NotEmpty(batchCompleteParams.BatchSummary.ResultSetSummaries);
|
||||
Assert.NotEmpty(batchCompleteParams.BatchSummary.Messages);
|
||||
Assert.Equal(Common.OwnerUri, batchCompleteParams.OwnerUri);
|
||||
|
||||
Assert.Equal(2, resultCompleteParams.Count);
|
||||
foreach (var resultParam in resultCompleteParams)
|
||||
{
|
||||
Assert.NotNull(resultCompleteParams);
|
||||
Assert.Equal(Common.StandardColumns, resultParam.ResultSetSummary.ColumnInfo.Length);
|
||||
Assert.Equal(Common.StandardRows, resultParam.ResultSetSummary.RowCount);
|
||||
Assert.Equal(Common.OwnerUri, resultParam.OwnerUri);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task QueryExecuteMultipleBatchSingleResultTest()
|
||||
{
|
||||
// Given:
|
||||
// ... A workspace with a standard query is configured
|
||||
var workspaceService = Common.GetPrimedWorkspaceService(string.Format("{0}\r\nGO\r\n{0}", Common.StandardQuery));
|
||||
|
||||
// If:
|
||||
// ... I request a to execute a valid query with multiple batches
|
||||
var dataSet = new[] { Common.StandardTestData };
|
||||
var queryService = Common.GetPrimedExecutionService(dataSet, true, false, workspaceService);
|
||||
var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QuerySelection = Common.WholeDocument };
|
||||
|
||||
QueryExecuteResult result = null;
|
||||
QueryExecuteCompleteParams completeParams = null;
|
||||
List<QueryExecuteBatchNotificationParams> batchStartParams = new List<QueryExecuteBatchNotificationParams>();
|
||||
List<QueryExecuteBatchNotificationParams> batchCompleteParams = new List<QueryExecuteBatchNotificationParams>();
|
||||
List<QueryExecuteResultSetCompleteParams> resultCompleteParams = new List<QueryExecuteResultSetCompleteParams>();
|
||||
var requestContext = RequestContextMocks.Create<QueryExecuteResult>(qer => result = qer)
|
||||
.AddEventHandling(QueryExecuteCompleteEvent.Type, (et, p) => completeParams = p)
|
||||
.AddEventHandling(QueryExecuteBatchStartEvent.Type, (et, p) => batchStartParams.Add(p))
|
||||
.AddEventHandling(QueryExecuteBatchCompleteEvent.Type, (et, p) => batchCompleteParams.Add(p))
|
||||
.AddEventHandling(QueryExecuteResultSetCompleteEvent.Type, (et, p) => resultCompleteParams.Add(p));
|
||||
await Common.AwaitExecution(queryService, queryParams, requestContext.Object);
|
||||
|
||||
// Then:
|
||||
// ... No errors should have been sent
|
||||
// ... A successful result should have been sent without messages
|
||||
|
||||
VerifyQueryExecuteCallCount(requestContext, Times.Once(), Times.Once(), Times.Exactly(2), Times.Exactly(2), Times.Exactly(2), Times.Never());
|
||||
Assert.Null(result.Messages);
|
||||
|
||||
// ... A completion event should have been fired with one two batch summaries, one result each
|
||||
Assert.Equal(2, completeParams.BatchSummaries.Length);
|
||||
Assert.Equal(1, completeParams.BatchSummaries[0].ResultSetSummaries.Length);
|
||||
Assert.Equal(1, completeParams.BatchSummaries[1].ResultSetSummaries.Length);
|
||||
Assert.NotEmpty(completeParams.BatchSummaries[0].Messages);
|
||||
Assert.NotEmpty(completeParams.BatchSummaries[1].Messages);
|
||||
|
||||
// ... Two batch start events should have been fired
|
||||
Assert.Equal(2, batchStartParams.Count);
|
||||
foreach (var batch in batchStartParams)
|
||||
{
|
||||
Assert.Null(batch.BatchSummary.Messages);
|
||||
Assert.Null(batch.BatchSummary.ResultSetSummaries);
|
||||
Assert.Equal(Common.OwnerUri, batch.OwnerUri);
|
||||
}
|
||||
|
||||
// ... Two batch completion events should have been fired
|
||||
Assert.Equal(2, batchCompleteParams.Count);
|
||||
foreach (var batch in batchCompleteParams)
|
||||
{
|
||||
Assert.NotEmpty(batch.BatchSummary.ResultSetSummaries);
|
||||
Assert.NotEmpty(batch.BatchSummary.Messages);
|
||||
Assert.Equal(Common.OwnerUri, batch.OwnerUri);
|
||||
}
|
||||
|
||||
// ... Two resultset completion events should have been fired
|
||||
Assert.Equal(2, resultCompleteParams.Count);
|
||||
foreach (var resultParam in resultCompleteParams)
|
||||
{
|
||||
Assert.NotNull(resultParam.ResultSetSummary);
|
||||
Assert.Equal(Common.StandardColumns, resultParam.ResultSetSummary.ColumnInfo.Length);
|
||||
Assert.Equal(Common.StandardRows, resultParam.ResultSetSummary.RowCount);
|
||||
Assert.Equal(Common.OwnerUri, resultParam.OwnerUri);
|
||||
}
|
||||
|
||||
// ... There should be one active query
|
||||
Assert.Equal(1, queryService.ActiveQueries.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async void QueryExecuteUnconnectedUriTest()
|
||||
{
|
||||
// Given:
|
||||
// ... A workspace with a standard query is configured
|
||||
var workspaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery);
|
||||
|
||||
// If:
|
||||
// ... I request to execute a query using a file URI that isn't connected
|
||||
var queryService = Common.GetPrimedExecutionService(null, false, false, workspaceService);
|
||||
var queryParams = new QueryExecuteParams { OwnerUri = "notConnected", QuerySelection = Common.WholeDocument };
|
||||
|
||||
object error = null;
|
||||
var requestContext = RequestContextMocks.Create<QueryExecuteResult>(null)
|
||||
.AddErrorHandling(e => error = e);
|
||||
await Common.AwaitExecution(queryService, queryParams, requestContext.Object);
|
||||
|
||||
// Then:
|
||||
// ... An error should have been returned
|
||||
// ... No result should have been returned
|
||||
// ... No completion event should have been fired
|
||||
// ... There should be no active queries
|
||||
VerifyQueryExecuteCallCount(requestContext, Times.Never(), Times.Never(), Times.Never(), Times.Never(), Times.Never(), Times.Once());
|
||||
Assert.IsType<string>(error);
|
||||
Assert.NotEmpty((string)error);
|
||||
Assert.Empty(queryService.ActiveQueries);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async void QueryExecuteInProgressTest()
|
||||
{
|
||||
// Given:
|
||||
// ... A workspace with a standard query is configured
|
||||
var workspaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery);
|
||||
|
||||
// If:
|
||||
// ... I request to execute a query
|
||||
var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService);
|
||||
var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QuerySelection = Common.WholeDocument };
|
||||
|
||||
// Note, we don't care about the results of the first request
|
||||
var firstRequestContext = RequestContextMocks.Create<QueryExecuteResult>(null);
|
||||
await Common.AwaitExecution(queryService, queryParams, firstRequestContext.Object);
|
||||
|
||||
// ... And then I request another query without waiting for the first to complete
|
||||
queryService.ActiveQueries[Common.OwnerUri].HasExecuted = false; // Simulate query hasn't finished
|
||||
object error = null;
|
||||
var secondRequestContext = RequestContextMocks.Create<QueryExecuteResult>(null)
|
||||
.AddErrorHandling(e => error = e);
|
||||
await Common.AwaitExecution(queryService, queryParams, secondRequestContext.Object);
|
||||
|
||||
// Then:
|
||||
// ... An error should have been sent
|
||||
// ... A result should have not have been sent
|
||||
// ... No completion event should have been fired
|
||||
// ... A batch completion event should have fired, but not a resultset event
|
||||
// ... There should only be one active query
|
||||
VerifyQueryExecuteCallCount(secondRequestContext, Times.Never(), Times.AtMostOnce(), Times.AtMostOnce(), Times.AtMostOnce(), Times.Never(), Times.Once());
|
||||
Assert.IsType<string>(error);
|
||||
Assert.NotEmpty((string)error);
|
||||
Assert.Equal(1, queryService.ActiveQueries.Count);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public async void QueryExecuteCompletedTest()
|
||||
{
|
||||
// Given:
|
||||
// ... A workspace with a standard query is configured
|
||||
var workspaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery);
|
||||
|
||||
// If:
|
||||
// ... I request to execute a query
|
||||
var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService);
|
||||
var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QuerySelection = Common.WholeDocument };
|
||||
|
||||
// Note, we don't care about the results of the first request
|
||||
var firstRequestContext = RequestContextMocks.Create<QueryExecuteResult>(null);
|
||||
await Common.AwaitExecution(queryService, queryParams, firstRequestContext.Object);
|
||||
|
||||
// ... And then I request another query after waiting for the first to complete
|
||||
QueryExecuteResult result = null;
|
||||
QueryExecuteCompleteParams complete = null;
|
||||
QueryExecuteBatchNotificationParams batchStart = null;
|
||||
QueryExecuteBatchNotificationParams batchComplete = null;
|
||||
var secondRequestContext = RequestContextMocks.Create<QueryExecuteResult>(qer => result = qer)
|
||||
.AddEventHandling(QueryExecuteCompleteEvent.Type, (et, qecp) => complete = qecp)
|
||||
.AddEventHandling(QueryExecuteBatchStartEvent.Type, (et, p) => batchStart = p)
|
||||
.AddEventHandling(QueryExecuteBatchCompleteEvent.Type, (et, p) => batchComplete = p);
|
||||
await Common.AwaitExecution(queryService, queryParams, secondRequestContext.Object);
|
||||
|
||||
// Then:
|
||||
// ... No errors should have been sent
|
||||
// ... A result should have been sent with no errors
|
||||
// ... There should only be one active query
|
||||
// ... A batch completion event should have fired, but not a result set completion event
|
||||
VerifyQueryExecuteCallCount(secondRequestContext, Times.Once(), Times.Once(), Times.Once(), Times.Once(), Times.Never(), Times.Never());
|
||||
Assert.Null(result.Messages);
|
||||
|
||||
Assert.False(complete.BatchSummaries.Any(b => b.HasError));
|
||||
Assert.Equal(1, queryService.ActiveQueries.Count);
|
||||
|
||||
Assert.NotNull(batchStart);
|
||||
Assert.NotNull(batchComplete);
|
||||
Assert.False(batchComplete.BatchSummary.HasError);
|
||||
Assert.Equal(complete.OwnerUri, batchComplete.OwnerUri);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task QueryExecuteMissingSelectionTest()
|
||||
{
|
||||
// Given:
|
||||
// ... A workspace with a standard query is configured
|
||||
var workspaceService = Common.GetPrimedWorkspaceService(string.Empty);
|
||||
|
||||
// If:
|
||||
// ... I request to execute a query with a missing query string
|
||||
var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService);
|
||||
var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QuerySelection = null };
|
||||
|
||||
object errorResult = null;
|
||||
var requestContext = RequestContextMocks.Create<QueryExecuteResult>(null)
|
||||
.AddErrorHandling(error => errorResult = error);
|
||||
await queryService.HandleExecuteRequest(queryParams, requestContext.Object);
|
||||
|
||||
|
||||
// Then:
|
||||
// ... Am error should have been sent
|
||||
// ... No result should have been sent
|
||||
// ... No completion events should have been fired
|
||||
// ... An active query should not have been added
|
||||
VerifyQueryExecuteCallCount(requestContext, Times.Never(), Times.Never(), Times.Never(), Times.Never(), Times.Never(), Times.Once());
|
||||
Assert.NotNull(errorResult);
|
||||
Assert.IsType<string>(errorResult);
|
||||
Assert.DoesNotContain(Common.OwnerUri, queryService.ActiveQueries.Keys);
|
||||
|
||||
// ... There should not be an active query
|
||||
Assert.Empty(queryService.ActiveQueries);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async void QueryExecuteInvalidQueryTest()
|
||||
{
|
||||
// Given:
|
||||
// ... A workspace with a standard query is configured
|
||||
var workspaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery);
|
||||
|
||||
// If:
|
||||
// ... I request to execute a query that is invalid
|
||||
var queryService = Common.GetPrimedExecutionService(null, true, true, workspaceService);
|
||||
var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QuerySelection = Common.WholeDocument };
|
||||
|
||||
QueryExecuteResult result = null;
|
||||
QueryExecuteCompleteParams complete = null;
|
||||
QueryExecuteBatchNotificationParams batchStart = null;
|
||||
QueryExecuteBatchNotificationParams batchComplete = null;
|
||||
var requestContext = RequestContextMocks.Create<QueryExecuteResult>(qer => result = qer)
|
||||
.AddEventHandling(QueryExecuteCompleteEvent.Type, (et, qecp) => complete = qecp)
|
||||
.AddEventHandling(QueryExecuteBatchStartEvent.Type, (et, p) => batchStart = p)
|
||||
.AddEventHandling(QueryExecuteBatchCompleteEvent.Type, (et, p) => batchComplete = p);
|
||||
await Common.AwaitExecution(queryService, queryParams, requestContext.Object);
|
||||
|
||||
// Then:
|
||||
// ... No errors should have been sent
|
||||
// ... A result should have been sent with success (we successfully started the query)
|
||||
// ... A completion event (query, batch, not resultset) should have been sent with error
|
||||
VerifyQueryExecuteCallCount(requestContext, Times.Once(), Times.Once(), Times.Once(), Times.Once(), Times.Never(), Times.Never());
|
||||
Assert.Null(result.Messages);
|
||||
|
||||
Assert.Equal(1, complete.BatchSummaries.Length);
|
||||
Assert.True(complete.BatchSummaries[0].HasError);
|
||||
Assert.NotEmpty(complete.BatchSummaries[0].Messages);
|
||||
|
||||
Assert.NotNull(batchStart);
|
||||
Assert.False(batchStart.BatchSummary.HasError);
|
||||
Assert.Null(batchStart.BatchSummary.Messages);
|
||||
Assert.Null(batchStart.BatchSummary.ResultSetSummaries);
|
||||
Assert.Equal(Common.OwnerUri, batchStart.OwnerUri);
|
||||
|
||||
Assert.NotNull(batchComplete);
|
||||
Assert.True(batchComplete.BatchSummary.HasError);
|
||||
Assert.NotEmpty(batchComplete.BatchSummary.Messages);
|
||||
Assert.Equal(Common.OwnerUri, batchComplete.OwnerUri);
|
||||
}
|
||||
|
||||
private static void VerifyQueryExecuteCallCount(Mock<RequestContext<QueryExecuteResult>> mock,
|
||||
Times sendResultCalls,
|
||||
Times sendCompletionEventCalls,
|
||||
Times sendBatchStartEvent,
|
||||
Times sendBatchCompletionEvent,
|
||||
Times sendResultCompleteEvent,
|
||||
Times sendErrorCalls)
|
||||
{
|
||||
mock.Verify(rc => rc.SendResult(It.IsAny<QueryExecuteResult>()), sendResultCalls);
|
||||
mock.Verify(rc => rc.SendEvent(
|
||||
It.Is<EventType<QueryExecuteCompleteParams>>(m => m == QueryExecuteCompleteEvent.Type),
|
||||
It.IsAny<QueryExecuteCompleteParams>()), sendCompletionEventCalls);
|
||||
mock.Verify(rc => rc.SendEvent(
|
||||
It.Is<EventType<QueryExecuteBatchNotificationParams>>(m => m == QueryExecuteBatchCompleteEvent.Type),
|
||||
It.IsAny<QueryExecuteBatchNotificationParams>()), sendBatchCompletionEvent);
|
||||
mock.Verify(rc => rc.SendEvent(
|
||||
It.Is<EventType<QueryExecuteBatchNotificationParams>>(m => m== QueryExecuteBatchStartEvent.Type),
|
||||
It.IsAny<QueryExecuteBatchNotificationParams>()), sendBatchStartEvent);
|
||||
mock.Verify(rc => rc.SendEvent(
|
||||
It.Is<EventType<QueryExecuteResultSetCompleteParams>>(m => m == QueryExecuteResultSetCompleteEvent.Type),
|
||||
It.IsAny<QueryExecuteResultSetCompleteParams>()), sendResultCompleteEvent);
|
||||
|
||||
mock.Verify(rc => rc.SendError(It.IsAny<object>()), sendErrorCalls);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,16 +3,13 @@
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
@@ -30,11 +27,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
public async void SaveResultsAsCsvSuccessTest()
|
||||
{
|
||||
// Execute a query
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, Common.GetPrimedWorkspaceService());
|
||||
var workplaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery);
|
||||
var queryService = Common.GetPrimedExecutionService(new [] {Common.StandardTestData}, true, false, workplaceService);
|
||||
var executeParams = new QueryExecuteParams { QuerySelection = Common.WholeDocument, OwnerUri = Common.OwnerUri };
|
||||
var executeRequest = RequestContextMocks.Create<QueryExecuteResult>(null);
|
||||
await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
|
||||
await queryService.ActiveQueries[Common.OwnerUri].ExecutionTask;
|
||||
await Common.AwaitExecution(queryService, executeParams, executeRequest.Object);
|
||||
|
||||
// Request to save the results as csv with correct parameters
|
||||
var saveParams = new SaveResultsAsCsvRequestParams
|
||||
@@ -47,18 +44,16 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
};
|
||||
SaveResultRequestResult result = null;
|
||||
var saveRequest = GetSaveResultsContextMock(qcr => result = qcr, null);
|
||||
queryService.ActiveQueries[Common.OwnerUri].Batches[0] = Common.GetBasicExecutedBatch();
|
||||
|
||||
// Call save results and wait on the save task
|
||||
await queryService.HandleSaveResultsAsCsvRequest(saveParams, saveRequest.Object);
|
||||
ResultSet selectedResultSet = queryService.ActiveQueries[saveParams.OwnerUri].Batches[saveParams.BatchIndex].ResultSets[saveParams.ResultSetIndex];
|
||||
Task saveTask = selectedResultSet.GetSaveTask(saveParams.FilePath);
|
||||
await saveTask;
|
||||
await selectedResultSet.GetSaveTask(saveParams.FilePath);
|
||||
|
||||
// Expect to see a file successfully created in filepath and a success message
|
||||
VerifySaveResultsCallCount(saveRequest, Times.Once(), Times.Never());
|
||||
Assert.Null(result.Messages);
|
||||
Assert.True(File.Exists(saveParams.FilePath));
|
||||
VerifySaveResultsCallCount(saveRequest, Times.Once(), Times.Never());
|
||||
|
||||
// Delete temp file after test
|
||||
if (File.Exists(saveParams.FilePath))
|
||||
@@ -74,11 +69,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
public async void SaveResultsAsCsvWithSelectionSuccessTest()
|
||||
{
|
||||
// Execute a query
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, Common.GetPrimedWorkspaceService());
|
||||
var workspaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery);
|
||||
var queryService = Common.GetPrimedExecutionService(new []{Common.StandardTestData}, true, false, workspaceService);
|
||||
var executeParams = new QueryExecuteParams { QuerySelection = Common.WholeDocument , OwnerUri = Common.OwnerUri };
|
||||
var executeRequest = RequestContextMocks.Create<QueryExecuteResult>(null);
|
||||
await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
|
||||
await queryService.ActiveQueries[Common.OwnerUri].ExecutionTask;
|
||||
await Common.AwaitExecution(queryService, executeParams, executeRequest.Object);
|
||||
|
||||
// Request to save the results as csv with correct parameters
|
||||
var saveParams = new SaveResultsAsCsvRequestParams
|
||||
@@ -95,7 +90,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
};
|
||||
SaveResultRequestResult result = null;
|
||||
var saveRequest = GetSaveResultsContextMock(qcr => result = qcr, null);
|
||||
queryService.ActiveQueries[Common.OwnerUri].Batches[0] = Common.GetBasicExecutedBatch();
|
||||
|
||||
// Call save results and wait on the save task
|
||||
await queryService.HandleSaveResultsAsCsvRequest(saveParams, saveRequest.Object);
|
||||
@@ -104,9 +98,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
await saveTask;
|
||||
|
||||
// Expect to see a file successfully created in filepath and a success message
|
||||
VerifySaveResultsCallCount(saveRequest, Times.Once(), Times.Never());
|
||||
Assert.Null(result.Messages);
|
||||
Assert.True(File.Exists(saveParams.FilePath));
|
||||
VerifySaveResultsCallCount(saveRequest, Times.Once(), Times.Never());
|
||||
|
||||
// Delete temp file after test
|
||||
if (File.Exists(saveParams.FilePath))
|
||||
@@ -120,13 +114,13 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async void SaveResultsAsCsvExceptionTest()
|
||||
{
|
||||
{
|
||||
// Execute a query
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, Common.GetPrimedWorkspaceService());
|
||||
var workspaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery);
|
||||
var queryService = Common.GetPrimedExecutionService(new[] {Common.StandardTestData}, true, false, workspaceService);
|
||||
var executeParams = new QueryExecuteParams { QuerySelection = Common.WholeDocument, OwnerUri = Common.OwnerUri };
|
||||
var executeRequest = RequestContextMocks.Create<QueryExecuteResult>(null);
|
||||
await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
|
||||
await queryService.ActiveQueries[Common.OwnerUri].ExecutionTask;
|
||||
await Common.AwaitExecution(queryService, executeParams, executeRequest.Object);
|
||||
|
||||
// Request to save the results as csv with incorrect filepath
|
||||
var saveParams = new SaveResultsAsCsvRequestParams
|
||||
@@ -139,17 +133,15 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
|
||||
SaveResultRequestError errMessage = null;
|
||||
var saveRequest = GetSaveResultsContextMock( null, err => errMessage = (SaveResultRequestError) err);
|
||||
queryService.ActiveQueries[Common.OwnerUri].Batches[0] = Common.GetBasicExecutedBatch();
|
||||
|
||||
// Call save results and wait on the save task
|
||||
await queryService.HandleSaveResultsAsCsvRequest(saveParams, saveRequest.Object);
|
||||
ResultSet selectedResultSet = queryService.ActiveQueries[saveParams.OwnerUri].Batches[saveParams.BatchIndex].ResultSets[saveParams.ResultSetIndex];
|
||||
Task saveTask = selectedResultSet.GetSaveTask(saveParams.FilePath);
|
||||
await saveTask;
|
||||
await selectedResultSet.GetSaveTask(saveParams.FilePath);
|
||||
|
||||
// Expect to see error message
|
||||
Assert.NotNull(errMessage);
|
||||
VerifySaveResultsCallCount(saveRequest, Times.Never(), Times.Once());
|
||||
Assert.NotNull(errMessage);
|
||||
Assert.False(File.Exists(saveParams.FilePath));
|
||||
}
|
||||
|
||||
@@ -157,11 +149,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
/// Test saving results to CSV file when the requested result set is no longer active
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async void SaveResultsAsCsvQueryNotFoundTest()
|
||||
public async Task SaveResultsAsCsvQueryNotFoundTest()
|
||||
{
|
||||
// Create a query execution service
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, workspaceService.Object);
|
||||
var workspaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery);
|
||||
var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService);
|
||||
|
||||
// Request to save the results as csv with query that is no longer active
|
||||
var saveParams = new SaveResultsAsCsvRequestParams
|
||||
@@ -173,12 +165,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
};
|
||||
SaveResultRequestResult result = null;
|
||||
var saveRequest = GetSaveResultsContextMock(qcr => result = qcr, null);
|
||||
queryService.HandleSaveResultsAsCsvRequest(saveParams, saveRequest.Object).Wait();
|
||||
await queryService.HandleSaveResultsAsCsvRequest(saveParams, saveRequest.Object);
|
||||
|
||||
// Expect message that save failed
|
||||
VerifySaveResultsCallCount(saveRequest, Times.Once(), Times.Never());
|
||||
Assert.NotNull(result.Messages);
|
||||
Assert.False(File.Exists(saveParams.FilePath));
|
||||
VerifySaveResultsCallCount(saveRequest, Times.Once(), Times.Never());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -188,11 +180,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
public async void SaveResultsAsJsonSuccessTest()
|
||||
{
|
||||
// Execute a query
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, Common.GetPrimedWorkspaceService());
|
||||
var workspaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery);
|
||||
var queryService = Common.GetPrimedExecutionService(new[] {Common.StandardTestData}, true, false, workspaceService);
|
||||
var executeParams = new QueryExecuteParams { QuerySelection = Common.WholeDocument, OwnerUri = Common.OwnerUri };
|
||||
var executeRequest = RequestContextMocks.Create<QueryExecuteResult>(null);
|
||||
await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
|
||||
await queryService.ActiveQueries[Common.OwnerUri].ExecutionTask;
|
||||
await Common.AwaitExecution(queryService, executeParams, executeRequest.Object);
|
||||
|
||||
// Request to save the results as json with correct parameters
|
||||
var saveParams = new SaveResultsAsJsonRequestParams
|
||||
@@ -200,19 +192,16 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
OwnerUri = Common.OwnerUri,
|
||||
ResultSetIndex = 0,
|
||||
BatchIndex = 0,
|
||||
FilePath = "testwrite_4.json"
|
||||
FilePath = "testwrite_4.json"
|
||||
};
|
||||
SaveResultRequestResult result = null;
|
||||
var saveRequest = GetSaveResultsContextMock(qcr => result = qcr, null);
|
||||
queryService.ActiveQueries[Common.OwnerUri].Batches[0] = Common.GetBasicExecutedBatch();
|
||||
|
||||
// Call save results and wait on the save task
|
||||
await queryService.HandleSaveResultsAsJsonRequest(saveParams, saveRequest.Object);
|
||||
ResultSet selectedResultSet = queryService.ActiveQueries[saveParams.OwnerUri].Batches[saveParams.BatchIndex].ResultSets[saveParams.ResultSetIndex];
|
||||
Task saveTask = selectedResultSet.GetSaveTask(saveParams.FilePath);
|
||||
await saveTask;
|
||||
|
||||
|
||||
|
||||
// Expect to see a file successfully created in filepath and a success message
|
||||
Assert.Null(result.Messages);
|
||||
@@ -233,11 +222,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
public async void SaveResultsAsJsonWithSelectionSuccessTest()
|
||||
{
|
||||
// Execute a query
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, Common.GetPrimedWorkspaceService());
|
||||
var workspaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery);
|
||||
var queryService = Common.GetPrimedExecutionService(new[] { Common.StandardTestData }, true, false, workspaceService);
|
||||
var executeParams = new QueryExecuteParams { QuerySelection = Common.WholeDocument , OwnerUri = Common.OwnerUri };
|
||||
var executeRequest = RequestContextMocks.Create<QueryExecuteResult>(null);
|
||||
await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
|
||||
await queryService.ActiveQueries[Common.OwnerUri].ExecutionTask;
|
||||
await Common.AwaitExecution(queryService, executeParams, executeRequest.Object);
|
||||
|
||||
// Request to save the results as json with correct parameters
|
||||
var saveParams = new SaveResultsAsJsonRequestParams
|
||||
@@ -253,18 +242,16 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
};
|
||||
SaveResultRequestResult result = null;
|
||||
var saveRequest = GetSaveResultsContextMock(qcr => result = qcr, null);
|
||||
queryService.ActiveQueries[Common.OwnerUri].Batches[0] = Common.GetBasicExecutedBatch();
|
||||
|
||||
// Call save results and wait on the save task
|
||||
await queryService.HandleSaveResultsAsJsonRequest(saveParams, saveRequest.Object);
|
||||
ResultSet selectedResultSet = queryService.ActiveQueries[saveParams.OwnerUri].Batches[saveParams.BatchIndex].ResultSets[saveParams.ResultSetIndex];
|
||||
Task saveTask = selectedResultSet.GetSaveTask(saveParams.FilePath);
|
||||
await saveTask;
|
||||
|
||||
await selectedResultSet.GetSaveTask(saveParams.FilePath);
|
||||
|
||||
// Expect to see a file successfully created in filepath and a success message
|
||||
VerifySaveResultsCallCount(saveRequest, Times.Once(), Times.Never());
|
||||
Assert.Null(result.Messages);
|
||||
Assert.True(File.Exists(saveParams.FilePath));
|
||||
VerifySaveResultsCallCount(saveRequest, Times.Once(), Times.Never());
|
||||
|
||||
// Delete temp file after test
|
||||
if (File.Exists(saveParams.FilePath))
|
||||
@@ -280,11 +267,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
public async void SaveResultsAsJsonExceptionTest()
|
||||
{
|
||||
// Execute a query
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, Common.GetPrimedWorkspaceService());
|
||||
var workspaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery);
|
||||
var queryService = Common.GetPrimedExecutionService(new [] {Common.StandardTestData}, true, false, workspaceService);
|
||||
var executeParams = new QueryExecuteParams { QuerySelection = Common.WholeDocument, OwnerUri = Common.OwnerUri };
|
||||
var executeRequest = RequestContextMocks.Create<QueryExecuteResult>(null);
|
||||
await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
|
||||
await queryService.ActiveQueries[Common.OwnerUri].ExecutionTask;
|
||||
await Common.AwaitExecution(queryService, executeParams, executeRequest.Object);
|
||||
|
||||
// Request to save the results as json with incorrect filepath
|
||||
var saveParams = new SaveResultsAsJsonRequestParams
|
||||
@@ -303,8 +290,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
// Call save results and wait on the save task
|
||||
await queryService.HandleSaveResultsAsJsonRequest(saveParams, saveRequest.Object);
|
||||
ResultSet selectedResultSet = queryService.ActiveQueries[saveParams.OwnerUri].Batches[saveParams.BatchIndex].ResultSets[saveParams.ResultSetIndex];
|
||||
Task saveTask = selectedResultSet.GetSaveTask(saveParams.FilePath);
|
||||
await saveTask;
|
||||
await selectedResultSet.GetSaveTask(saveParams.FilePath);
|
||||
|
||||
// Expect to see error message
|
||||
Assert.NotNull(errMessage);
|
||||
@@ -316,12 +302,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
/// Test saving results to JSON file when the requested result set is no longer active
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async void SaveResultsAsJsonQueryNotFoundTest()
|
||||
public async Task SaveResultsAsJsonQueryNotFoundTest()
|
||||
{
|
||||
|
||||
// Create a query service
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, workspaceService.Object);
|
||||
var workspaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery);
|
||||
var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService);
|
||||
|
||||
// Request to save the results as json with query that is no longer active
|
||||
var saveParams = new SaveResultsAsJsonRequestParams
|
||||
@@ -333,7 +318,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
};
|
||||
SaveResultRequestResult result = null;
|
||||
var saveRequest = GetSaveResultsContextMock(qcr => result = qcr, null);
|
||||
queryService.HandleSaveResultsAsJsonRequest(saveParams, saveRequest.Object).Wait();
|
||||
await queryService.HandleSaveResultsAsJsonRequest(saveParams, saveRequest.Object);
|
||||
|
||||
// Expect message that save failed
|
||||
Assert.Equal("Failed to save results, ID not found.", result.Messages);
|
||||
@@ -353,25 +338,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
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);
|
||||
}
|
||||
var requestContext = RequestContextMocks.Create(resultCallback)
|
||||
.AddErrorHandling(errorCallback);
|
||||
|
||||
return requestContext;
|
||||
}
|
||||
|
||||
@@ -4,15 +4,13 @@
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
@@ -23,9 +21,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
#region ResultSet Class Tests
|
||||
|
||||
[Theory]
|
||||
[InlineData(0,2)]
|
||||
[InlineData(0,20)]
|
||||
[InlineData(1,2)]
|
||||
[InlineData(0, 2)]
|
||||
[InlineData(0, 20)]
|
||||
[InlineData(1, 2)]
|
||||
public void ResultSetValidTest(int startRow, int rowCount)
|
||||
{
|
||||
// Setup:
|
||||
@@ -60,6 +58,17 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
Assert.ThrowsAsync<ArgumentOutOfRangeException>(() => rs.GetSubset(rowStartIndex, rowCount)).Wait();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ResultSetNotReadTest()
|
||||
{
|
||||
// If:
|
||||
// ... I have a resultset that hasn't been executed and I request a valid result set from it
|
||||
// Then:
|
||||
// ... It should throw an exception for having not been read
|
||||
ResultSet rs = new ResultSet(new TestDbDataReader(null), Common.Ordinal, Common.Ordinal, Common.GetFileStreamFactory(new Dictionary<string, byte[]>()));
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() => rs.GetSubset(0, 1));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Batch Class Tests
|
||||
@@ -99,18 +108,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
|
||||
#region Query Class Tests
|
||||
|
||||
[Fact]
|
||||
public void SubsetUnexecutedQueryTest()
|
||||
{
|
||||
// If I have a query that has *not* been executed
|
||||
Query q = new Query(Common.StandardQuery, Common.CreateTestConnectionInfo(null, false), new QueryExecutionSettings(), Common.GetFileStreamFactory());
|
||||
|
||||
// ... And I ask for a subset with valid arguments
|
||||
// Then:
|
||||
// ... It should throw an exception
|
||||
Assert.ThrowsAsync<InvalidOperationException>(() => q.GetSubset(0, 0, 0, 2)).Wait();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(-1)] // Invalid batch, too low
|
||||
[InlineData(2)] // Invalid batch, too high
|
||||
@@ -132,26 +129,17 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
[Fact]
|
||||
public async Task SubsetServiceValidTest()
|
||||
{
|
||||
|
||||
// Set up file for returning the query
|
||||
var fileMock = new Mock<ScriptFile>();
|
||||
fileMock.SetupGet(file => file.Contents).Returns(Common.StandardQuery);
|
||||
// Set up workspace mock
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
|
||||
.Returns(fileMock.Object);
|
||||
// If:
|
||||
// ... I have a query that has results (doesn't matter what)
|
||||
var queryService = await Common.GetPrimedExecutionService(
|
||||
Common.CreateMockFactory(new[] {Common.StandardTestData}, false), true,
|
||||
workspaceService.Object);
|
||||
var workspaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery);
|
||||
var queryService = Common.GetPrimedExecutionService(new[] {Common.StandardTestData}, true, false, workspaceService);
|
||||
var executeParams = new QueryExecuteParams {QuerySelection = null, OwnerUri = Common.OwnerUri};
|
||||
var executeRequest = RequestContextMocks.Create<QueryExecuteResult>(null);
|
||||
await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
|
||||
await queryService.ActiveQueries[Common.OwnerUri].ExecutionTask;
|
||||
|
||||
// ... 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};
|
||||
var subsetParams = new QueryExecuteSubsetParams { OwnerUri = Common.OwnerUri, RowsCount = 1, ResultSetIndex = 0, RowsStartIndex = 0 };
|
||||
QueryExecuteSubsetResult result = null;
|
||||
var subsetRequest = GetQuerySubsetResultContextMock(qesr => result = qesr, null);
|
||||
await queryService.HandleResultSubsetRequest(subsetParams, subsetRequest.Object);
|
||||
@@ -166,17 +154,16 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async void SubsetServiceMissingQueryTest()
|
||||
public async Task SubsetServiceMissingQueryTest()
|
||||
{
|
||||
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
// If:
|
||||
// ... I ask for a set of results for a file that hasn't executed a query
|
||||
var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, workspaceService.Object);
|
||||
var workspaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery);
|
||||
var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService);
|
||||
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 an error result
|
||||
@@ -188,32 +175,23 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async void SubsetServiceUnexecutedQueryTest()
|
||||
public async Task SubsetServiceUnexecutedQueryTest()
|
||||
{
|
||||
|
||||
// Set up file for returning the query
|
||||
var fileMock = new Mock<ScriptFile>();
|
||||
fileMock.SetupGet(file => file.Contents).Returns(Common.StandardQuery);
|
||||
// Set up workspace mock
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
|
||||
.Returns(fileMock.Object);
|
||||
// If:
|
||||
// ... I have a query that hasn't finished executing (doesn't matter what)
|
||||
var queryService = await Common.GetPrimedExecutionService(
|
||||
Common.CreateMockFactory(new[] { Common.StandardTestData }, false), true,
|
||||
workspaceService.Object);
|
||||
var workspaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery);
|
||||
var queryService = Common.GetPrimedExecutionService(new[] { Common.StandardTestData }, true, false, workspaceService);
|
||||
var executeParams = new QueryExecuteParams { QuerySelection = null, OwnerUri = Common.OwnerUri };
|
||||
var executeRequest = RequestContextMocks.Create<QueryExecuteResult>(null);
|
||||
await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
|
||||
await queryService.ActiveQueries[Common.OwnerUri].ExecutionTask;
|
||||
queryService.ActiveQueries[Common.OwnerUri].HasExecuted = false;
|
||||
queryService.ActiveQueries[Common.OwnerUri].Batches[0].ResultSets[0].hasBeenRead = false;
|
||||
|
||||
// ... 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 get an error result
|
||||
@@ -226,11 +204,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
||||
|
||||
[Fact]
|
||||
public async void SubsetServiceOutOfRangeSubsetTest()
|
||||
{
|
||||
{
|
||||
// If:
|
||||
// ... I have a query that doesn't have any result sets
|
||||
var queryService = await Common.GetPrimedExecutionService(
|
||||
Common.CreateMockFactory(null, false), true, Common.GetPrimedWorkspaceService());
|
||||
var workspaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery);
|
||||
var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService);
|
||||
var executeParams = new QueryExecuteParams { QuerySelection = null, OwnerUri = Common.OwnerUri };
|
||||
var executeRequest = RequestContextMocks.Create<QueryExecuteResult>(null);
|
||||
await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Test.ServiceHost
|
||||
{
|
||||
public class AsyncLockTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task AsyncLockSynchronizesAccess()
|
||||
{
|
||||
AsyncLock asyncLock = new AsyncLock();
|
||||
|
||||
Task<IDisposable> lockOne = asyncLock.LockAsync();
|
||||
Task<IDisposable> lockTwo = asyncLock.LockAsync();
|
||||
|
||||
Assert.Equal(TaskStatus.RanToCompletion, lockOne.Status);
|
||||
Assert.Equal(TaskStatus.WaitingForActivation, lockTwo.Status);
|
||||
lockOne.Result.Dispose();
|
||||
|
||||
await lockTwo;
|
||||
Assert.Equal(TaskStatus.RanToCompletion, lockTwo.Status);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AsyncLockCancelsWhenRequested()
|
||||
{
|
||||
CancellationTokenSource cts = new CancellationTokenSource();
|
||||
AsyncLock asyncLock = new AsyncLock();
|
||||
|
||||
Task<IDisposable> lockOne = asyncLock.LockAsync();
|
||||
Task<IDisposable> lockTwo = asyncLock.LockAsync(cts.Token);
|
||||
|
||||
// Cancel the second lock before the first is released
|
||||
cts.Cancel();
|
||||
lockOne.Result.Dispose();
|
||||
|
||||
Assert.Equal(TaskStatus.RanToCompletion, lockOne.Status);
|
||||
Assert.Equal(TaskStatus.Canceled, lockTwo.Status);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
//
|
||||
// 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.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Test.ServiceHost
|
||||
{
|
||||
public class AsyncQueueTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task AsyncQueueSynchronizesAccess()
|
||||
{
|
||||
ConcurrentBag<int> outputItems = new ConcurrentBag<int>();
|
||||
AsyncQueue<int> inputQueue = new AsyncQueue<int>(Enumerable.Range(0, 100));
|
||||
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
try
|
||||
{
|
||||
// Start 5 consumers
|
||||
await Task.WhenAll(
|
||||
Task.Run(() => ConsumeItems(inputQueue, outputItems, cancellationTokenSource.Token)),
|
||||
Task.Run(() => ConsumeItems(inputQueue, outputItems, cancellationTokenSource.Token)),
|
||||
Task.Run(() => ConsumeItems(inputQueue, outputItems, cancellationTokenSource.Token)),
|
||||
Task.Run(() => ConsumeItems(inputQueue, outputItems, cancellationTokenSource.Token)),
|
||||
Task.Run(() => ConsumeItems(inputQueue, outputItems, cancellationTokenSource.Token)),
|
||||
Task.Run(
|
||||
async () =>
|
||||
{
|
||||
// Wait for a bit and then add more items to the queue
|
||||
await Task.Delay(250);
|
||||
|
||||
foreach (var i in Enumerable.Range(100, 200))
|
||||
{
|
||||
await inputQueue.EnqueueAsync(i);
|
||||
}
|
||||
|
||||
// Cancel the waiters
|
||||
cancellationTokenSource.Cancel();
|
||||
}));
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
// Do nothing, this is expected.
|
||||
}
|
||||
|
||||
// At this point, numbers 0 through 299 should be in the outputItems
|
||||
IEnumerable<int> expectedItems = Enumerable.Range(0, 300);
|
||||
Assert.Equal(0, expectedItems.Except(outputItems).Count());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AsyncQueueSkipsCancelledTasks()
|
||||
{
|
||||
AsyncQueue<int> inputQueue = new AsyncQueue<int>();
|
||||
|
||||
// Queue up a couple of tasks to wait for input
|
||||
CancellationTokenSource cancellationSource = new CancellationTokenSource();
|
||||
Task<int> taskOne = inputQueue.DequeueAsync(cancellationSource.Token);
|
||||
Task<int> taskTwo = inputQueue.DequeueAsync();
|
||||
|
||||
// Cancel the first task and then enqueue a number
|
||||
cancellationSource.Cancel();
|
||||
await inputQueue.EnqueueAsync(1);
|
||||
|
||||
// Did the second task get the number?
|
||||
Assert.Equal(TaskStatus.Canceled, taskOne.Status);
|
||||
Assert.Equal(TaskStatus.RanToCompletion, taskTwo.Status);
|
||||
Assert.Equal(1, taskTwo.Result);
|
||||
}
|
||||
|
||||
private async Task ConsumeItems(
|
||||
AsyncQueue<int> inputQueue,
|
||||
ConcurrentBag<int> outputItems,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
int consumedItem = await inputQueue.DequeueAsync(cancellationToken);
|
||||
outputItems.Add(consumedItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user