Merge branch 'release/preview2' for candidate release

This commit is contained in:
Kevin Cunnane
2016-12-09 16:37:11 -08:00
132 changed files with 9952 additions and 2451 deletions

7
.mention-bot Normal file
View File

@@ -0,0 +1,7 @@
{
"maxReviewers": 3,
"requiredOrgs": ["Microsoft"],
"skipAlreadyAssignedPR": true,
"skipAlreadyMentionedPR": true,
"skipCollaboratorPR": false
}

View File

@@ -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
View 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"

View File

@@ -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

View File

@@ -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; }
}
}

View File

@@ -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)
{

View File

@@ -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();

View File

@@ -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

View File

@@ -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; }
}

View File

@@ -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");
}

View File

@@ -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;

View File

@@ -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");
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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);

View File

@@ -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
{

View File

@@ -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;
}
}
}

View File

@@ -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

View File

@@ -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; }
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -0,0 +1,34 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System.Collections.Generic;
using Microsoft.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);
}
}
}

View File

@@ -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())

View File

@@ -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");
}
}

View File

@@ -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";
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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();

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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>

View File

@@ -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");
}
}

View File

@@ -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; }
}

View File

@@ -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");
}
}

View File

@@ -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()
{
}
}
}

View File

@@ -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>

View File

@@ -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
};
}
}
}

View File

@@ -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
}
}

View File

@@ -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();
}
}

View File

@@ -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);

View File

@@ -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);
}
}
}

View File

@@ -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);

View File

@@ -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

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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
});
}

View File

@@ -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;
}
}
}
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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;
}
}
}

View File

@@ -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
}
}

View File

@@ -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(

View File

@@ -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",

View File

@@ -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";

View File

@@ -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">

View File

@@ -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

View File

@@ -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"

View File

@@ -0,0 +1,4 @@
set DOTNETCONFIG=-c Integration
cmd /c npm install
gulp

View File

@@ -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)
{
}
}
}

View File

@@ -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>

View 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;
}
}
}

View File

@@ -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")]

View 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);
}
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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);
}
}
}
}

View 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": {}
}
}

View File

@@ -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)]

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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

View File

@@ -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]

View File

@@ -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
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,93 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System.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;
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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
}

View File

@@ -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
}
}

View File

@@ -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);
}
}
}

View File

@@ -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

View File

@@ -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
}
}

View File

@@ -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 */}
}
}
}

View File

@@ -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));
}
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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;
}
}
}

View File

@@ -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));
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -0,0 +1,49 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using 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);
}
}
}

View File

@@ -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