diff --git a/src/Microsoft.SqlTools.ServiceLayer/EditData/Contracts/EditInitializeFiltering.cs b/src/Microsoft.SqlTools.ServiceLayer/EditData/Contracts/EditInitializeFiltering.cs new file mode 100644 index 00000000..4074953c --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/EditData/Contracts/EditInitializeFiltering.cs @@ -0,0 +1,18 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +namespace Microsoft.SqlTools.ServiceLayer.EditData.Contracts +{ + /// + /// Parameters for filtering a the rows in a table to make querying easier + /// + public class EditInitializeFiltering + { + /// + /// Limit the records queried from the database to this many. If null, all rows are returned + /// + public int? LimitResults { get; set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/EditData/Contracts/EditInitializeRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/EditData/Contracts/EditInitializeRequest.cs index b03c0ff3..49533757 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/EditData/Contracts/EditInitializeRequest.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/EditData/Contracts/EditInitializeRequest.cs @@ -12,6 +12,11 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.Contracts /// public class EditInitializeParams : SessionOperationParams { + /// + /// Filtering parameters + /// + public EditInitializeFiltering Filters { get; set; } + /// /// The object to use for generating an edit script /// diff --git a/src/Microsoft.SqlTools.ServiceLayer/EditData/EditDataService.cs b/src/Microsoft.SqlTools.ServiceLayer/EditData/EditDataService.cs index 1f7abc26..5f0b65f4 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/EditData/EditDataService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/EditData/EditDataService.cs @@ -162,14 +162,14 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData Validate.IsNotNullOrWhitespaceString(nameof(initParams.ObjectType), initParams.ObjectType); // Create a session and add it to the session list - EditSession session = new EditSession(metadataFactory, initParams.ObjectName, initParams.ObjectType); + EditSession session = new EditSession(metadataFactory); if (!ActiveSessions.TryAdd(initParams.OwnerUri, session)) { throw new InvalidOperationException(SR.EditDataSessionAlreadyExists); } // Initialize the session - session.Initialize(connector, queryRunner, executionSuccessHandler, executionFailureHandler); + session.Initialize(initParams, connector, queryRunner, executionSuccessHandler, executionFailureHandler); } catch (Exception e) { diff --git a/src/Microsoft.SqlTools.ServiceLayer/EditData/EditSession.cs b/src/Microsoft.SqlTools.ServiceLayer/EditData/EditSession.cs index b0eec669..3b8db94e 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/EditData/EditSession.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/EditData/EditSession.cs @@ -9,6 +9,7 @@ using System.Collections.Generic; using System.Data.Common; using System.IO; using System.Linq; +using System.Text; using System.Threading.Tasks; using Microsoft.SqlTools.ServiceLayer.EditData.Contracts; using Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement; @@ -29,25 +30,17 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData private readonly IEditMetadataFactory metadataFactory; private EditTableMetadata objectMetadata; - private readonly string objectName; - private readonly string objectType; /// /// Constructs a new edit session bound to the result set and metadat object provided /// /// Factory for creating metadata - /// The name of the object to edit - /// The type of the object to edit - public EditSession(IEditMetadataFactory metaFactory, string objName, string objType) + public EditSession(IEditMetadataFactory metaFactory) { Validate.IsNotNull(nameof(metaFactory), metaFactory); - Validate.IsNotNullOrWhitespaceString(nameof(objName), objName); - Validate.IsNotNullOrWhitespaceString(nameof(objType), objType); // Setup the internal state metadataFactory = metaFactory; - objectName = objName; - objectType = objType; } #region Properties @@ -86,7 +79,22 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData #region Public Methods - public void Initialize(Connector connector, QueryRunner queryRunner, Func successHandler, Func errorHandler) + /// + /// Initializes the edit session, asynchronously, by retrieving metadata about the table to + /// edit and querying the table for the rows of the table. + /// + /// Parameters for initializing the edit session + /// Delegate that will return a DbConnection when executed + /// + /// Delegate that will run the requested query and return a + /// object on execution + /// + /// Func to call when initialization has completed successfully + /// Func to call when initialization has completed with errors + /// + /// When session is already initialized or in progress of initializing + /// + public void Initialize(EditInitializeParams initParams, Connector connector, QueryRunner queryRunner, Func successHandler, Func errorHandler) { if (IsInitialized) { @@ -98,13 +106,17 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData throw new InvalidOperationException(SR.EditDataSessionAlreadyInitializing); } + Validate.IsNotNullOrWhitespaceString(nameof(initParams.ObjectName), initParams.ObjectName); + Validate.IsNotNullOrWhitespaceString(nameof(initParams.ObjectType), initParams.ObjectType); + Validate.IsNotNull(nameof(initParams.Filters), initParams.Filters); + Validate.IsNotNull(nameof(connector), connector); Validate.IsNotNull(nameof(queryRunner), queryRunner); Validate.IsNotNull(nameof(successHandler), successHandler); Validate.IsNotNull(nameof(errorHandler), errorHandler); // Start up the initialize process - InitializeTask = InitializeInternal(connector, queryRunner, successHandler, errorHandler); + InitializeTask = InitializeInternal(initParams, connector, queryRunner, successHandler, errorHandler); } /// @@ -403,16 +415,19 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData #endregion - private async Task InitializeInternal(Connector connector, QueryRunner queryRunner, - Func successHandler, Func failureHandler) + #region Private Helpers + + private async Task InitializeInternal(EditInitializeParams initParams, Connector connector, + QueryRunner queryRunner, Func successHandler, Func failureHandler) { try { // Step 1) Look up the SMO metadata - objectMetadata = metadataFactory.GetObjectMetadata(await connector(), objectName, objectType); + objectMetadata = metadataFactory.GetObjectMetadata(await connector(), initParams.ObjectName, + initParams.ObjectType); // Step 2) Get and execute a query for the rows in the object we're looking up - EditSessionQueryExecutionState state = await queryRunner(ConstructInitializeQuery()); + EditSessionQueryExecutionState state = await queryRunner(ConstructInitializeQuery(objectMetadata, initParams.Filters)); if (state.Query == null) { // TODO: Move to SR file @@ -467,13 +482,33 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData } } - private string ConstructInitializeQuery() + /// + /// Constructs a query for selecting rows in a table based on the filters provided. + /// Internal for unit testing purposes only. + /// + internal static string ConstructInitializeQuery(EditTableMetadata metadata, EditInitializeFiltering initFilters) { - // Using the columns we know, put together a query for the rows in the table - var columns = objectMetadata.Columns.Select(col => col.EscapedName); - var columnClause = string.Join(", ", columns); + StringBuilder queryBuilder = new StringBuilder("SELECT "); - return $"SELECT ${columnClause} FROM ${objectMetadata.EscapedMultipartName}"; + // If there is a filter for top n rows, then apply it + if (initFilters.LimitResults.HasValue) + { + if (initFilters.LimitResults < 0) + { + throw new ArgumentOutOfRangeException(nameof(initFilters.LimitResults), SR.EditDataFilteringNegativeLimit); + } + queryBuilder.AppendFormat("TOP {0} ", initFilters.LimitResults.Value); + } + + // Using the columns we know, add them to the query + var columns = metadata.Columns.Select(col => col.EscapedName); + var columnClause = string.Join(", ", columns); + queryBuilder.Append(columnClause); + + // Add the FROM + queryBuilder.AppendFormat(" FROM {0}", metadata.EscapedMultipartName); + + return queryBuilder.ToString(); } private void ThrowIfNotInitialized() @@ -484,6 +519,8 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData } } + #endregion + /// /// State object to return upon completion of an edit session intialization query /// diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs index 818a3b01..a7c6883c 100755 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs @@ -413,6 +413,14 @@ namespace Microsoft.SqlTools.ServiceLayer } } + public static string EditDataFilteringNegativeLimit + { + get + { + return Keys.GetString(Keys.EditDataFilteringNegativeLimit); + } + } + public static string EditDataQueryFailed { get @@ -1098,6 +1106,9 @@ namespace Microsoft.SqlTools.ServiceLayer public const string EditDataSessionAlreadyInitializing = "EditDataSessionAlreadyInitializing"; + public const string EditDataFilteringNegativeLimit = "EditDataFilteringNegativeLimit"; + + public const string EditDataUnsupportedObjectType = "EditDataUnsupportedObjectType"; diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx index 5768fa52..d52e5e9b 100755 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx @@ -374,6 +374,10 @@ Edit session has already been initialized or is in the process of initializing + + Result limit cannot be negative + + Database object {0} cannot be used for editing. . diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings index 8e7b7d6f..da300ed4 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings @@ -176,6 +176,8 @@ EditDataSessionAlreadyInitialized = Edit session has already been initialized EditDataSessionAlreadyInitializing = Edit session has already been initialized or is in the process of initializing +EditDataFilteringNegativeLimit = Result limit cannot be negative + EditDataUnsupportedObjectType(string typeName) = Database object {0} cannot be used for editing. EditDataQueryFailed = Query execution failed, see messages for details diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf index 529bd088..2922cecb 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf @@ -591,6 +591,11 @@ Query execution failed, see messages for details + + Result limit cannot be negative + Result limit cannot be negative + + \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/EditData/Common.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/EditData/Common.cs index a02dffb8..1a0c2f2f 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/EditData/Common.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/EditData/Common.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.SqlTools.ServiceLayer.EditData; +using Microsoft.SqlTools.ServiceLayer.EditData.Contracts; using Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement; using Microsoft.SqlTools.ServiceLayer.QueryExecution; using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts; @@ -22,21 +23,42 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData { public const string OwnerUri = "testFile"; + public static EditInitializeParams BasicInitializeParameters + { + get + { + return new EditInitializeParams + { + Filters = new EditInitializeFiltering(), + ObjectName = "tbl", + ObjectType = "tbl" + }; + } + } + public static async Task GetCustomSession(Query q, EditTableMetadata etm) { + // Step 1) Create the Session object // Mock metadata factory Mock metaFactory = new Mock(); metaFactory .Setup(f => f.GetObjectMetadata(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(etm); - EditSession session = new EditSession(metaFactory.Object, "tbl", "tbl"); + EditSession session = new EditSession(metaFactory.Object); + + // Step 2) Initialize the Session + // Mock connector that does nothing EditSession.Connector connector = () => Task.FromResult(null); + + // Mock query runner that returns the query we were provided EditSession.QueryRunner queryRunner = (s) => Task.FromResult(new EditSession.EditSessionQueryExecutionState(q)); - session.Initialize(connector, queryRunner, () => Task.FromResult(0), (e) => Task.FromResult(0)); + // Initialize + session.Initialize(BasicInitializeParameters, connector, queryRunner, () => Task.FromResult(0), (e) => Task.FromResult(0)); await session.InitializeTask; + return session; } diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/EditData/SessionTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/EditData/SessionTests.cs index 50837834..180c6c87 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/EditData/SessionTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/EditData/SessionTests.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.Data.Common; using System.IO; using System.Linq; +using System.Text.RegularExpressions; using System.Threading.Tasks; using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.EditData; @@ -32,31 +33,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData { // If: I create a session object with a null metadata factory // Then: It should throw an exception - Assert.Throws(() => new EditSession(null, Constants.OwnerUri, Constants.OwnerUri)); - } - - [Theory] - [InlineData(null)] - [InlineData("")] - [InlineData(" \t\r\n")] - public void SessionConstructionNullObjectName(string objName) - { - // If: I create a session object with a null or whitespace object name - // Then: It should throw an exception - Mock mockFactory = new Mock(); - Assert.Throws(() => new EditSession(mockFactory.Object, objName, Constants.OwnerUri)); - } - - [Theory] - [InlineData(null)] - [InlineData("")] - [InlineData(" \t\r\n")] - public void SessionConstructionNullObjectType(string objType) - { - // If: I create a session object with a null or whitespace object type - // Then: It should throw an exception - Mock mockFactory = new Mock(); - Assert.Throws(() => new EditSession(mockFactory.Object, Constants.OwnerUri, objType)); + Assert.Throws(() => new EditSession(null)); } [Fact] @@ -64,7 +41,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData { // If: I create a session object with a proper arguments Mock mockFactory = new Mock(); - EditSession s = new EditSession(mockFactory.Object, Constants.OwnerUri, Constants.OwnerUri); + EditSession s = new EditSession(mockFactory.Object); // Then: // ... The edit cache should not exist @@ -138,7 +115,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData // Setup: // ... Create a session without initializing Mock emf = new Mock(); - EditSession s = new EditSession(emf.Object, Constants.OwnerUri, Constants.OwnerUri); + EditSession s = new EditSession(emf.Object); // If: I ask to create a row without initializing // Then: I should get an exception @@ -311,12 +288,11 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData // Setup: // ... Create a session and fake that it has been initialized Mock emf = new Mock(); - EditSession s = new EditSession(emf.Object, Constants.OwnerUri, Constants.OwnerUri); - s.IsInitialized = true; + EditSession s = new EditSession(emf.Object) {IsInitialized = true}; // If: I initialize it // Then: I should get an exception - Assert.Throws(() => s.Initialize(null, null, null, null)); + Assert.Throws(() => s.Initialize(null, null, null, null, null)); } [Fact] @@ -325,37 +301,75 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData // Setup: // ... Create a session and fake that it is in progress of initializing Mock emf = new Mock(); - EditSession s = new EditSession(emf.Object, Constants.OwnerUri, Constants.OwnerUri); - s.InitializeTask = new Task(() => { }); + EditSession s = new EditSession(emf.Object) {InitializeTask = new Task(() => {})}; // If: I initialize it // Then: I should get an exception - Assert.Throws(() => s.Initialize(null, null, null, null)); + Assert.Throws(() => s.Initialize(null, null, null, null, null)); } [Theory] [MemberData(nameof(InitializeNullParamsData))] - public void InitializeNullParams(EditSession.Connector c, EditSession.QueryRunner qr, - Func sh, Func fh) + public void InitializeNullParams(EditInitializeParams initParams, EditSession.Connector c, + EditSession.QueryRunner qr, Func sh, Func fh) { // Setup: // ... Create a session that hasn't been initialized Mock emf = new Mock(); - EditSession s = new EditSession(emf.Object, Constants.OwnerUri, Constants.OwnerUri); + EditSession s = new EditSession(emf.Object); // If: I initialize it with a missing parameter // Then: It should throw an exception - Assert.ThrowsAny(() => s.Initialize(c, qr, sh, fh)); + Assert.ThrowsAny(() => s.Initialize(initParams, c, qr, sh, fh)); } public static IEnumerable InitializeNullParamsData { get { - yield return new object[] {null, DoNothingQueryRunner, DoNothingSuccessHandler, DoNothingFailureHandler}; - yield return new object[] {DoNothingConnector, null, DoNothingSuccessHandler, DoNothingFailureHandler}; - yield return new object[] {DoNothingConnector, DoNothingQueryRunner, null, DoNothingFailureHandler}; - yield return new object[] {DoNothingConnector, DoNothingQueryRunner, DoNothingSuccessHandler, null}; + yield return new object[] {Common.BasicInitializeParameters, null, DoNothingQueryRunner, DoNothingSuccessHandler, DoNothingFailureHandler}; + yield return new object[] {Common.BasicInitializeParameters, DoNothingConnector, null, DoNothingSuccessHandler, DoNothingFailureHandler}; + yield return new object[] {Common.BasicInitializeParameters, DoNothingConnector, DoNothingQueryRunner, null, DoNothingFailureHandler}; + yield return new object[] {Common.BasicInitializeParameters, DoNothingConnector, DoNothingQueryRunner, DoNothingSuccessHandler, null}; + + string[] nullOrWhitespace = {null, string.Empty, " \t\r\n"}; + + // Tests with invalid object name or type + foreach (string str in nullOrWhitespace) + { + // Invalid object name + var eip1 = new EditInitializeParams + { + ObjectName = str, + ObjectType = "table", + Filters = new EditInitializeFiltering() + }; + yield return new object[] {eip1, DoNothingConnector, DoNothingQueryRunner, DoNothingSuccessHandler, DoNothingFailureHandler}; + + // Invalid object type + var eip2 = new EditInitializeParams + { + ObjectName = "tbl", + ObjectType = str, + Filters = new EditInitializeFiltering() + }; + yield return new object[] {eip2, DoNothingConnector, DoNothingQueryRunner, DoNothingSuccessHandler, DoNothingFailureHandler}; + } + + // Test with null init filters + yield return new object[] + { + new EditInitializeParams + { + ObjectName = "tbl", + ObjectType = "table", + Filters = null + }, + DoNothingConnector, + DoNothingQueryRunner, + DoNothingSuccessHandler, + DoNothingFailureHandler + }; } } @@ -369,14 +383,14 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData .Throws(); // ... Create a session that hasn't been initialized - EditSession s = new EditSession(emf.Object, Constants.OwnerUri, Constants.OwnerUri); + EditSession s = new EditSession(emf.Object); // ... Create a mock for verifying the failure handler will be called var successHandler = DoNothingSuccessMock; var failureHandler = DoNothingFailureMock; // If: I initalize the session with a metadata factory that will fail - s.Initialize(DoNothingConnector, DoNothingQueryRunner, successHandler.Object, failureHandler.Object); + s.Initialize(Common.BasicInitializeParameters, DoNothingConnector, DoNothingQueryRunner, successHandler.Object, failureHandler.Object); await s.InitializeTask; // Then: @@ -402,7 +416,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData .Returns(etm); // ... Create a session that hasn't been initialized - EditSession s = new EditSession(emf.Object, Constants.OwnerUri, Constants.OwnerUri); + EditSession s = new EditSession(emf.Object); // ... Create a query runner that will fail via exception Mock qr = new Mock(); @@ -413,7 +427,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData var failureHandler = DoNothingFailureMock; // If: I initialize the session with a query runner that will fail - s.Initialize(DoNothingConnector, qr.Object, successHandler.Object, failureHandler.Object); + s.Initialize(Common.BasicInitializeParameters, DoNothingConnector, qr.Object, successHandler.Object, failureHandler.Object); await s.InitializeTask; // Then: @@ -441,7 +455,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData .Returns(etm); // ... Create a session that hasn't been initialized - EditSession s = new EditSession(emf.Object, Constants.OwnerUri, Constants.OwnerUri); + EditSession s = new EditSession(emf.Object); // ... Create a query runner that will fail via returning a null query Mock qr = new Mock(); @@ -453,7 +467,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData var failureHandler = DoNothingFailureMock; // If: I initialize the session with a query runner that will fail - s.Initialize(DoNothingConnector, qr.Object, successHandler.Object, failureHandler.Object); + s.Initialize(Common.BasicInitializeParameters, DoNothingConnector, qr.Object, successHandler.Object, failureHandler.Object); await s.InitializeTask; // Then: @@ -480,7 +494,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData .Returns(etm); // ... Create a session that hasn't been initialized - EditSession s = new EditSession(emf.Object, Constants.OwnerUri, Constants.OwnerUri); + EditSession s = new EditSession(emf.Object); // ... Create a query runner that will return a successful query Mock qr = new Mock(); @@ -492,7 +506,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData var failureHandler = DoNothingFailureMock; // If: I initialize the session with a query runner that will fail - s.Initialize(DoNothingConnector, qr.Object, successHandler.Object, failureHandler.Object); + s.Initialize(Common.BasicInitializeParameters, DoNothingConnector, qr.Object, successHandler.Object, failureHandler.Object); await s.InitializeTask; // Then: @@ -519,7 +533,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData // Setup: // ... Create a session without initializing Mock emf = new Mock(); - EditSession s = new EditSession(emf.Object, Constants.OwnerUri, Constants.OwnerUri); + EditSession s = new EditSession(emf.Object); // If: I ask to delete a row without initializing // Then: I should get an exception @@ -570,7 +584,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData // Setup: // ... Create a session without initializing Mock emf = new Mock(); - EditSession s = new EditSession(emf.Object, Constants.OwnerUri, Constants.OwnerUri); + EditSession s = new EditSession(emf.Object); // If: I ask to revert a row without initializing // Then: I should get an exception @@ -606,7 +620,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData // Setup: // ... Create a session without initializing Mock emf = new Mock(); - EditSession s = new EditSession(emf.Object, Constants.OwnerUri, Constants.OwnerUri); + EditSession s = new EditSession(emf.Object); // If: I ask to revert a cell without initializing // Then: I should get an exception @@ -623,7 +637,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData // Setup: // ... Create a session without initializing Mock emf = new Mock(); - EditSession s = new EditSession(emf.Object, Constants.OwnerUri, Constants.OwnerUri); + EditSession s = new EditSession(emf.Object); // If: I ask to update a cell without initializing // Then: I should get an exception @@ -677,7 +691,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData // Setup: // ... Create a session without initializing Mock emf = new Mock(); - EditSession s = new EditSession(emf.Object, Constants.OwnerUri, Constants.OwnerUri); + EditSession s = new EditSession(emf.Object); // If: I ask to update a cell without initializing // Then: I should get an exception @@ -844,7 +858,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData // Setup: // ... Create a session without initializing Mock emf = new Mock(); - EditSession s = new EditSession(emf.Object, Constants.OwnerUri, Constants.OwnerUri); + EditSession s = new EditSession(emf.Object); // If: I ask to script edits without initializing // Then: I should get an exception @@ -902,7 +916,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData // Setup: // ... Create a session without initializing Mock emf = new Mock(); - EditSession s = new EditSession(emf.Object, Constants.OwnerUri, Constants.OwnerUri); + EditSession s = new EditSession(emf.Object); // If: I ask to script edits without initializing // Then: I should get an exception @@ -1057,6 +1071,91 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData #endregion + #region Construct Initialize Query Tests + + [Fact] + public void ConstructQueryWithoutLimit() + { + // Setup: Create a metadata provider for some basic columns + var cols = Common.GetColumns(false); + var etm = Common.GetStandardMetadata(cols); + + // If: I generate a query for selecting rows without a limit + EditInitializeFiltering eif = new EditInitializeFiltering + { + LimitResults = null + }; + string query = EditSession.ConstructInitializeQuery(etm, eif); + + // Then: + // ... The query should look like a select statement + Regex selectRegex = new Regex("SELECT (.+) FROM (.+)", RegexOptions.IgnoreCase); + var match = selectRegex.Match(query); + Assert.True(match.Success); + + // ... There should be columns in it + Assert.Equal(etm.Columns.Length, match.Groups[1].Value.Split(',').Length); + + // ... The table name should be in it + Assert.Equal(etm.EscapedMultipartName, match.Groups[2].Value); + + // ... It should NOT have a TOP clause in it + Assert.DoesNotContain("TOP", query); + } + + [Fact] + public void ConstructQueryNegativeLimit() + { + // Setup: Create a metadata provider for some basic columns + var cols = Common.GetColumns(false); + var etm = Common.GetStandardMetadata(cols); + + // If: I generate a query for selecting rows with a negative limit + // Then: An exception should be thrown + EditInitializeFiltering eif = new EditInitializeFiltering + { + LimitResults = -1 + }; + Assert.Throws(() => EditSession.ConstructInitializeQuery(etm, eif)); + } + + [Theory] + [InlineData(0)] // Yes, zero is valid + [InlineData(10)] + [InlineData(1000)] + public void ConstructQueryWithLimit(int limit) + { + // Setup: Create a metadata provider for some basic columns + var cols = Common.GetColumns(false); + var etm = Common.GetStandardMetadata(cols); + + // If: I generate a query for selecting rows without a limit + EditInitializeFiltering eif = new EditInitializeFiltering + { + LimitResults = limit + }; + string query = EditSession.ConstructInitializeQuery(etm, eif); + + // Then: + // ... The query should look like a select statement + Regex selectRegex = new Regex(@"SELECT TOP (\d+) (.+) FROM (.+)", RegexOptions.IgnoreCase); + var match = selectRegex.Match(query); + Assert.True(match.Success); + + // ... There should be columns in it + Assert.Equal(etm.Columns.Length, match.Groups[2].Value.Split(',').Length); + + // ... The table name should be in it + Assert.Equal(etm.EscapedMultipartName, match.Groups[3].Value); + + // ... The top count should be equal to what we provided + int limitFromQuery; + Assert.True(int.TryParse(match.Groups[1].Value, out limitFromQuery)); + Assert.Equal(limit, limitFromQuery); + } + + #endregion + private static EditSession.Connector DoNothingConnector { get { return () => Task.FromResult(null); }