From 3410e23960a789310737a5c334ddbb4f126ec5f2 Mon Sep 17 00:00:00 2001 From: Charles Gagnon Date: Mon, 19 Sep 2022 16:15:59 -0700 Subject: [PATCH 1/3] Merge commit '76575f29e964f3487684b84e279444279296f5c2' into chgagnon/mergeFromMain1 # Conflicts: # src/SqlAsyncCollector.cs # test/GlobalSuppressions.cs --- src/SqlAsyncCollector.cs | 8 +++- src/SqlBindingUtilities.cs | 4 +- test/Common/ProductColumnTypes.cs | 16 +++++++ test/Database/Tables/ProductsColumnTypes.sql | 5 +++ test/GlobalSuppressions.cs | 2 + .../SqlInputBindingIntegrationTests.cs | 40 ++++++++++++++++++ .../SqlOutputBindingIntegrationTests.cs | 21 ++++++++++ .../test-csharp/AddProductColumnTypes.cs | 35 ++++++++++++++++ .../GetProductColumnTypesSerialization.cs | 40 ++++++++++++++++++ ...olumnTypesSerializationDifferentCulture.cs | 42 +++++++++++++++++++ .../AddProductColumnTypes/function.json | 27 ++++++++++++ .../test-js/AddProductColumnTypes/index.js | 18 ++++++++ .../function.json | 28 +++++++++++++ .../index.js | 11 +++++ ....Azure.WebJobs.Extensions.Sql.Tests.csproj | 19 ++++----- 15 files changed, 302 insertions(+), 14 deletions(-) create mode 100644 test/Common/ProductColumnTypes.cs create mode 100644 test/Database/Tables/ProductsColumnTypes.sql create mode 100644 test/Integration/test-csharp/AddProductColumnTypes.cs create mode 100644 test/Integration/test-csharp/GetProductColumnTypesSerialization.cs create mode 100644 test/Integration/test-csharp/GetProductColumnTypesSerializationDifferentCulture.cs create mode 100644 test/Integration/test-js/AddProductColumnTypes/function.json create mode 100644 test/Integration/test-js/AddProductColumnTypes/index.js create mode 100644 test/Integration/test-js/GetProductsColumnTypesSerialization/function.json create mode 100644 test/Integration/test-js/GetProductsColumnTypesSerialization/index.js diff --git a/src/SqlAsyncCollector.cs b/src/SqlAsyncCollector.cs index 2b385449c..05d2f77b9 100644 --- a/src/SqlAsyncCollector.cs +++ b/src/SqlAsyncCollector.cs @@ -349,6 +349,8 @@ private static void GenerateDataQueryForMerge(TableInformation table, IEnumerabl public class TableInformation { + private const string ISO_8061_DATETIME_FORMAT = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fff"; + public IEnumerable PrimaryKeys { get; } /// @@ -391,9 +393,13 @@ public TableInformation(IEnumerable primaryKeys, IDictionary /// Used to determine the columns of the table as well as the next SQL row to process /// The built dictionary - public static IReadOnlyDictionary BuildDictionaryFromSqlRow(SqlDataReader reader) + public static IReadOnlyDictionary BuildDictionaryFromSqlRow(SqlDataReader reader) { - return Enumerable.Range(0, reader.FieldCount).ToDictionary(reader.GetName, i => reader.GetValue(i).ToString()); + return Enumerable.Range(0, reader.FieldCount).ToDictionary(reader.GetName, i => reader.GetValue(i)); } /// diff --git a/test/Common/ProductColumnTypes.cs b/test/Common/ProductColumnTypes.cs new file mode 100644 index 000000000..97e36eb0b --- /dev/null +++ b/test/Common/ProductColumnTypes.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.Azure.WebJobs.Extensions.Sql.Tests.Common +{ + public class ProductColumnTypes + { + public int ProductID { get; set; } + + public DateTime Datetime { get; set; } + + public DateTime Datetime2 { get; set; } + } +} diff --git a/test/Database/Tables/ProductsColumnTypes.sql b/test/Database/Tables/ProductsColumnTypes.sql new file mode 100644 index 000000000..a552b9db1 --- /dev/null +++ b/test/Database/Tables/ProductsColumnTypes.sql @@ -0,0 +1,5 @@ +CREATE TABLE [ProductsColumnTypes] ( + [ProductId] [int] NOT NULL PRIMARY KEY, + [Datetime] [datetime], + [Datetime2] [datetime2] +) \ No newline at end of file diff --git a/test/GlobalSuppressions.cs b/test/GlobalSuppressions.cs index 56034261e..b0fa905ab 100644 --- a/test/GlobalSuppressions.cs +++ b/test/GlobalSuppressions.cs @@ -17,3 +17,5 @@ [assembly: SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Unused parameter is required by functions binding", Scope = "member", Target = "~M:Microsoft.Azure.WebJobs.Extensions.Sql.Tests.Integration.ReservedPrimaryKeyColumnNamesTrigger.Run(System.Collections.Generic.IReadOnlyList{Microsoft.Azure.WebJobs.Extensions.Sql.SqlChange{Microsoft.Azure.WebJobs.Extensions.Sql.Samples.Common.Product})")] [assembly: SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Unused parameter is required by functions binding", Scope = "member", Target = "~M:Microsoft.Azure.WebJobs.Extensions.Sql.Tests.Integration.TableNotPresentTrigger.Run(System.Collections.Generic.IReadOnlyList{Microsoft.Azure.WebJobs.Extensions.Sql.SqlChange{Microsoft.Azure.WebJobs.Extensions.Sql.Samples.Common.Product})")] [assembly: SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Unused parameter is required by functions binding", Scope = "member", Target = "~M:Microsoft.Azure.WebJobs.Extensions.Sql.Tests.Integration.UnsupportedColumnTypesTrigger.Run(System.Collections.Generic.IReadOnlyList{Microsoft.Azure.WebJobs.Extensions.Sql.SqlChange{Microsoft.Azure.WebJobs.Extensions.Sql.Samples.Common.Product})")] +[assembly: SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "", Scope = "member", Target = "~M:Microsoft.Azure.WebJobs.Extensions.Sql.Samples.InputBindingSamples.GetProductsColumnTypesSerializationDifferentCulture.Run(Microsoft.AspNetCore.Http.HttpRequest,System.Collections.Generic.IAsyncEnumerable{Microsoft.Azure.WebJobs.Extensions.Sql.Tests.Common.ProductColumnTypes},Microsoft.Extensions.Logging.ILogger)~System.Threading.Tasks.Task{Microsoft.AspNetCore.Mvc.IActionResult}")] +[assembly: SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "", Scope = "member", Target = "~M:Microsoft.Azure.WebJobs.Extensions.Sql.Samples.InputBindingSamples.GetProductsColumnTypesSerialization.Run(Microsoft.AspNetCore.Http.HttpRequest,System.Collections.Generic.IAsyncEnumerable{Microsoft.Azure.WebJobs.Extensions.Sql.Tests.Common.ProductColumnTypes},Microsoft.Extensions.Logging.ILogger)~System.Threading.Tasks.Task{Microsoft.AspNetCore.Mvc.IActionResult}")] diff --git a/test/Integration/SqlInputBindingIntegrationTests.cs b/test/Integration/SqlInputBindingIntegrationTests.cs index 9dc6532b9..7b63908a1 100644 --- a/test/Integration/SqlInputBindingIntegrationTests.cs +++ b/test/Integration/SqlInputBindingIntegrationTests.cs @@ -130,5 +130,45 @@ public async void GetProductNamesViewTest(SupportedLanguages lang) Assert.Equal(expectedResponse, TestUtils.CleanJsonString(actualResponse), StringComparer.OrdinalIgnoreCase); } + + /// + /// Verifies that serializing an item with various data types works when the language is + /// set to a non-enUS language. + /// + [Theory] + [SqlInlineData()] + [UnsupportedLanguages(SupportedLanguages.JavaScript)] // Javascript doesn't have the concept of a runtime language used during serialization + public async void GetProductsColumnTypesSerializationDifferentCultureTest(SupportedLanguages lang) + { + this.StartFunctionHost(nameof(GetProductsColumnTypesSerializationDifferentCulture), lang, true); + + this.ExecuteNonQuery("INSERT INTO [dbo].[ProductsColumnTypes] VALUES (" + + "999, " + // ProductId + "GETDATE(), " + // Datetime field + "GETDATE())"); // Datetime2 field + + await this.SendInputRequest("getproducts-columntypesserializationdifferentculture"); + + // If we get here the test has succeeded - it'll throw an exception if serialization fails + } + + /// + /// Verifies that serializing an item with various data types works as expected + /// + [Theory] + [SqlInlineData()] + public async void GetProductsColumnTypesSerializationTest(SupportedLanguages lang) + { + this.StartFunctionHost(nameof(GetProductsColumnTypesSerialization), lang, true); + + this.ExecuteNonQuery("INSERT INTO [dbo].[ProductsColumnTypes] VALUES (" + + "999, " + // ProductId + "GETDATE(), " + // Datetime field + "GETDATE())"); // Datetime2 field + + await this.SendInputRequest("getproducts-columntypesserialization"); + + // If we get here the test has succeeded - it'll throw an exception if serialization fails + } } } diff --git a/test/Integration/SqlOutputBindingIntegrationTests.cs b/test/Integration/SqlOutputBindingIntegrationTests.cs index 8f2f693c7..5b2d17d63 100644 --- a/test/Integration/SqlOutputBindingIntegrationTests.cs +++ b/test/Integration/SqlOutputBindingIntegrationTests.cs @@ -99,6 +99,27 @@ public void AddProductArrayTest(SupportedLanguages lang) Assert.Equal(2, this.ExecuteScalar("SELECT ProductId FROM Products WHERE Cost = 12")); } + /// + /// Test compatability with converting various data types to their respective + /// SQL server types. + /// + /// The language to run the test against + [Theory] + [SqlInlineData()] + public void AddProductColumnTypesTest(SupportedLanguages lang) + { + this.StartFunctionHost(nameof(AddProductColumnTypes), lang, true); + + var queryParameters = new Dictionary() + { + { "productId", "999" } + }; + + this.SendOutputGetRequest("addproduct-columntypes", queryParameters).Wait(); + + // If we get here then the test is successful - an exception will be thrown if there were any problems + } + [Theory] [SqlInlineData()] [UnsupportedLanguages(SupportedLanguages.JavaScript)] // Collectors are only available in C# diff --git a/test/Integration/test-csharp/AddProductColumnTypes.cs b/test/Integration/test-csharp/AddProductColumnTypes.cs new file mode 100644 index 000000000..180290ab1 --- /dev/null +++ b/test/Integration/test-csharp/AddProductColumnTypes.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Azure.WebJobs.Extensions.Http; +using Microsoft.Azure.WebJobs.Extensions.Sql.Tests.Common; + +namespace Microsoft.Azure.WebJobs.Extensions.Sql.Tests.Integration +{ + public static class AddProductColumnTypes + { + /// + /// This function is used to test compatability with converting various data types to their respective + /// SQL server types. + /// + [FunctionName(nameof(AddProductColumnTypes))] + public static IActionResult Run( + [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "addproduct-columntypes")] HttpRequest req, + [Sql("dbo.ProductsColumnTypes", ConnectionStringSetting = "SqlConnectionString")] out ProductColumnTypes product) + { + product = new ProductColumnTypes() + { + ProductID = int.Parse(req.Query["productId"]), + Datetime = DateTime.UtcNow, + Datetime2 = DateTime.UtcNow + }; + + // Items were inserted successfully so return success, an exception would be thrown if there + // was any issues + return new OkObjectResult("Success!"); + } + } +} diff --git a/test/Integration/test-csharp/GetProductColumnTypesSerialization.cs b/test/Integration/test-csharp/GetProductColumnTypesSerialization.cs new file mode 100644 index 000000000..836c305ca --- /dev/null +++ b/test/Integration/test-csharp/GetProductColumnTypesSerialization.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Text.Json; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Azure.WebJobs.Extensions.Http; +using Microsoft.Azure.WebJobs.Extensions.Sql.Tests.Common; +using Microsoft.Extensions.Logging; + +namespace Microsoft.Azure.WebJobs.Extensions.Sql.Samples.InputBindingSamples +{ + public static class GetProductsColumnTypesSerialization + { + /// + /// This function verifies that serializing an item with various data types + /// works as expected. + /// Note this uses IAsyncEnumerable because IEnumerable serializes the entire table directly, + /// instead of each item one by one (which is where issues can occur) + /// + [FunctionName(nameof(GetProductsColumnTypesSerialization))] + public static async Task Run( + [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "getproducts-columntypesserialization")] + HttpRequest req, + [Sql("SELECT * FROM [dbo].[ProductsColumnTypes]", + CommandType = System.Data.CommandType.Text, + ConnectionStringSetting = "SqlConnectionString")] + IAsyncEnumerable products, + ILogger log) + { + await foreach (ProductColumnTypes item in products) + { + log.LogInformation(JsonSerializer.Serialize(item)); + } + return new OkObjectResult(products); + } + } +} diff --git a/test/Integration/test-csharp/GetProductColumnTypesSerializationDifferentCulture.cs b/test/Integration/test-csharp/GetProductColumnTypesSerializationDifferentCulture.cs new file mode 100644 index 000000000..9417ba46a --- /dev/null +++ b/test/Integration/test-csharp/GetProductColumnTypesSerializationDifferentCulture.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Globalization; +using System.Text.Json; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Azure.WebJobs.Extensions.Http; +using Microsoft.Azure.WebJobs.Extensions.Sql.Tests.Common; +using Microsoft.Extensions.Logging; + +namespace Microsoft.Azure.WebJobs.Extensions.Sql.Samples.InputBindingSamples +{ + public static class GetProductsColumnTypesSerializationDifferentCulture + { + /// + /// This function verifies that serializing an item with various data types + /// works when the language is set to a non-enUS language. + /// Note this uses IAsyncEnumerable because IEnumerable serializes the entire table directly, + /// instead of each item one by one (which is where issues can occur) + /// + [FunctionName(nameof(GetProductsColumnTypesSerializationDifferentCulture))] + public static async Task Run( + [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "getproducts-columntypesserializationdifferentculture")] + HttpRequest req, + [Sql("SELECT * FROM [dbo].[ProductsColumnTypes]", + CommandType = System.Data.CommandType.Text, + ConnectionStringSetting = "SqlConnectionString")] + IAsyncEnumerable products, + ILogger log) + { + CultureInfo.CurrentCulture = new CultureInfo("it-IT", false); + await foreach (ProductColumnTypes item in products) + { + log.LogInformation(JsonSerializer.Serialize(item)); + } + return new OkObjectResult(products); + } + } +} diff --git a/test/Integration/test-js/AddProductColumnTypes/function.json b/test/Integration/test-js/AddProductColumnTypes/function.json new file mode 100644 index 000000000..3b69ac6ae --- /dev/null +++ b/test/Integration/test-js/AddProductColumnTypes/function.json @@ -0,0 +1,27 @@ +{ + "bindings": [ + { + "authLevel": "function", + "name": "req", + "direction": "in", + "type": "httpTrigger", + "methods": [ + "get" + ], + "route": "addproduct-columntypes" + }, + { + "name": "$return", + "type": "http", + "direction": "out" + }, + { + "name": "product", + "type": "sql", + "direction": "out", + "commandText": "[dbo].[ProductsColumnTypes]", + "connectionStringSetting": "SqlConnectionString" + } + ], + "disabled": false +} \ No newline at end of file diff --git a/test/Integration/test-js/AddProductColumnTypes/index.js b/test/Integration/test-js/AddProductColumnTypes/index.js new file mode 100644 index 000000000..8d83ac300 --- /dev/null +++ b/test/Integration/test-js/AddProductColumnTypes/index.js @@ -0,0 +1,18 @@ +/** + * This function is used to test compatability with converting various data types to their respective + * SQL server types. + */ +module.exports = async function (context, req) { + const product = { + "productId": req.query.productId, + "datetime": Date.now(), + "datetime2": Date.now() + }; + + context.bindings.product = JSON.stringify(product); + + return { + status: 201, + body: product + }; +} \ No newline at end of file diff --git a/test/Integration/test-js/GetProductsColumnTypesSerialization/function.json b/test/Integration/test-js/GetProductsColumnTypesSerialization/function.json new file mode 100644 index 000000000..0554a9f7d --- /dev/null +++ b/test/Integration/test-js/GetProductsColumnTypesSerialization/function.json @@ -0,0 +1,28 @@ +{ + "bindings": [ + { + "authLevel": "function", + "name": "req", + "type": "httpTrigger", + "direction": "in", + "methods": [ + "get" + ], + "route": "getproducts-columntypesserialization" + }, + { + "name": "$return", + "type": "http", + "direction": "out" + }, + { + "name": "products", + "type": "sql", + "direction": "in", + "commandText": "SELECT * FROM [dbo].[ProductsColumnTypes]", + "commandType": "Text", + "connectionStringSetting": "SqlConnectionString" + } + ], + "disabled": false +} \ No newline at end of file diff --git a/test/Integration/test-js/GetProductsColumnTypesSerialization/index.js b/test/Integration/test-js/GetProductsColumnTypesSerialization/index.js new file mode 100644 index 000000000..732f2c8af --- /dev/null +++ b/test/Integration/test-js/GetProductsColumnTypesSerialization/index.js @@ -0,0 +1,11 @@ +/** + * This function verifies that serializing an item with various data types + * works as expected. + */ +module.exports = async function (context, req, products) { + context.log(JSON.stringify(products)); + return { + status: 200, + body: products + }; +} \ No newline at end of file diff --git a/test/Microsoft.Azure.WebJobs.Extensions.Sql.Tests.csproj b/test/Microsoft.Azure.WebJobs.Extensions.Sql.Tests.csproj index 54c504783..cad70ffbc 100644 --- a/test/Microsoft.Azure.WebJobs.Extensions.Sql.Tests.csproj +++ b/test/Microsoft.Azure.WebJobs.Extensions.Sql.Tests.csproj @@ -15,8 +15,14 @@ - - + + + + + + + Always + @@ -32,13 +38,4 @@ - - - - <_CSharpTestSqlFiles Include="Integration\test-csharp\Database\**\*.*" /> - - - - - From 942736c422129ddcfa7d0aed2cc58deb5b86f839 Mon Sep 17 00:00:00 2001 From: Charles Gagnon Date: Tue, 20 Sep 2022 09:35:04 -0700 Subject: [PATCH 2/3] Fix --- src/TriggerBinding/SqlTableChangeMonitor.cs | 30 ++++++++++----------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/TriggerBinding/SqlTableChangeMonitor.cs b/src/TriggerBinding/SqlTableChangeMonitor.cs index 08f6f60ee..4842c7a8d 100644 --- a/src/TriggerBinding/SqlTableChangeMonitor.cs +++ b/src/TriggerBinding/SqlTableChangeMonitor.cs @@ -60,7 +60,7 @@ internal sealed class SqlTableChangeMonitor : IDisposable private readonly IDictionary _telemetryProps; - private IReadOnlyList> _rows; + private IReadOnlyList> _rows; private int _leaseRenewalCount; private State _state; @@ -121,7 +121,7 @@ public SqlTableChangeMonitor( this._telemetryProps = telemetryProps; this._rowsLock = new SemaphoreSlim(1, 1); - this._rows = new List>(); + this._rows = new List>(); this._leaseRenewalCount = 0; this._state = State.CheckingForChanges; @@ -222,7 +222,7 @@ private async Task GetTableChangesAsync(SqlConnection connection, CancellationTo } this._logger.LogDebugWithThreadId($"END UpdateTablesPreInvocation Duration={setLastSyncVersionDurationMs}ms"); - var rows = new List>(); + var rows = new List>(); // Use the version number to query for new changes. using (SqlCommand getChangesCommand = this.BuildGetChangesCommand(connection, transaction)) @@ -292,7 +292,7 @@ private async Task GetTableChangesAsync(SqlConnection connection, CancellationTo { // If there's an exception in any part of the process, we want to clear all of our data in memory and // retry checking for changes again. - this._rows = new List>(); + this._rows = new List>(); this._logger.LogError($"Failed to check for changes in table '{this._userTable.FullName}' due to exception: {e.GetType()}. Exception message: {e.Message}"); TelemetryInstance.TrackException(TelemetryErrorName.GetChanges, e, this._telemetryProps); } @@ -477,7 +477,7 @@ private async Task ClearRowsAsync() this._leaseRenewalCount = 0; this._state = State.CheckingForChanges; - this._rows = new List>(); + this._rows = new List>(); this._logger.LogDebugWithThreadId("ReleaseRowsLock - ClearRows"); this._rowsLock.Release(); @@ -576,9 +576,9 @@ private async Task ReleaseLeasesAsync(SqlConnection connection, CancellationToke private long RecomputeLastSyncVersion() { var changeVersionSet = new SortedSet(); - foreach (IReadOnlyDictionary row in this._rows) + foreach (IReadOnlyDictionary row in this._rows) { - string changeVersion = row["SYS_CHANGE_VERSION"]; + string changeVersion = row["SYS_CHANGE_VERSION"].ToString(); changeVersionSet.Add(long.Parse(changeVersion, CultureInfo.InvariantCulture)); } @@ -599,13 +599,13 @@ private IReadOnlyList> ProcessChanges() { this._logger.LogDebugWithThreadId("BEGIN ProcessChanges"); var changes = new List>(); - foreach (IReadOnlyDictionary row in this._rows) + foreach (IReadOnlyDictionary row in this._rows) { SqlChangeOperation operation = GetChangeOperation(row); // If the row has been deleted, there is no longer any data for it in the user table. The best we can do // is populate the row-item with the primary key values of the row. - Dictionary item = operation == SqlChangeOperation.Delete + Dictionary item = operation == SqlChangeOperation.Delete ? this._primaryKeyColumns.ToDictionary(col => col, col => row[col]) : this._userTableColumns.ToDictionary(col => col, col => row[col]); @@ -621,9 +621,9 @@ private IReadOnlyList> ProcessChanges() /// The (combined) row from the change table and leases table /// Thrown if the value of the "SYS_CHANGE_OPERATION" column is none of "I", "U", or "D" /// SqlChangeOperation.Insert for an insert, SqlChangeOperation.Update for an update, and SqlChangeOperation.Delete for a delete - private static SqlChangeOperation GetChangeOperation(IReadOnlyDictionary row) + private static SqlChangeOperation GetChangeOperation(IReadOnlyDictionary row) { - string operation = row["SYS_CHANGE_OPERATION"]; + string operation = row["SYS_CHANGE_OPERATION"].ToString(); switch (operation) { case "I": return SqlChangeOperation.Insert; @@ -704,14 +704,14 @@ LEFT OUTER JOIN {this._userTable.BracketQuotedFullName} AS u ON {userTableJoinCo /// The transaction to add to the returned SqlCommand /// Dictionary representing the table rows on which leases should be acquired /// The SqlCommand populated with the query and appropriate parameters - private SqlCommand BuildAcquireLeasesCommand(SqlConnection connection, SqlTransaction transaction, IReadOnlyList> rows) + private SqlCommand BuildAcquireLeasesCommand(SqlConnection connection, SqlTransaction transaction, IReadOnlyList> rows) { var acquireLeasesQuery = new StringBuilder(); for (int rowIndex = 0; rowIndex < rows.Count; rowIndex++) { string valuesList = string.Join(", ", this._primaryKeyColumns.Select((_, colIndex) => $"@{rowIndex}_{colIndex}")); - string changeVersion = rows[rowIndex]["SYS_CHANGE_VERSION"]; + string changeVersion = rows[rowIndex]["SYS_CHANGE_VERSION"].ToString(); acquireLeasesQuery.Append($@" IF NOT EXISTS (SELECT * FROM {this._leasesTableName} WITH (TABLOCKX) WHERE {this._rowMatchConditions[rowIndex]}) @@ -761,7 +761,7 @@ private SqlCommand BuildReleaseLeasesCommand(SqlConnection connection, SqlTransa for (int rowIndex = 0; rowIndex < this._rows.Count; rowIndex++) { - string changeVersion = this._rows[rowIndex]["SYS_CHANGE_VERSION"]; + string changeVersion = this._rows[rowIndex]["SYS_CHANGE_VERSION"].ToString(); releaseLeasesQuery.Append($@" SELECT @current_change_version = {SqlTriggerConstants.LeasesTableChangeVersionColumnName} @@ -843,7 +843,7 @@ FROM CHANGETABLE(CHANGES {this._userTable.BracketQuotedFullName}, @current_last_ /// rebuild the SqlParameters each time. /// private SqlCommand GetSqlCommandWithParameters(string commandText, SqlConnection connection, - SqlTransaction transaction, IReadOnlyList> rows) + SqlTransaction transaction, IReadOnlyList> rows) { var command = new SqlCommand(commandText, connection, transaction); From 89f79810d4e7d9e726b566b755f5f2343a286782 Mon Sep 17 00:00:00 2001 From: Charles Gagnon Date: Tue, 20 Sep 2022 11:04:59 -0700 Subject: [PATCH 3/3] Move test scripts --- .../Database/Tables/ProductsWithReservedPrimaryKeyColumnNames.sql | 0 .../Database/Tables/ProductsWithUnsupportedColumnTypes.sql | 0 .../Database/Tables/ProductsWithoutPrimaryKey.sql | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename test/{Integration/test-csharp => }/Database/Tables/ProductsWithReservedPrimaryKeyColumnNames.sql (100%) rename test/{Integration/test-csharp => }/Database/Tables/ProductsWithUnsupportedColumnTypes.sql (100%) rename test/{Integration/test-csharp => }/Database/Tables/ProductsWithoutPrimaryKey.sql (100%) diff --git a/test/Integration/test-csharp/Database/Tables/ProductsWithReservedPrimaryKeyColumnNames.sql b/test/Database/Tables/ProductsWithReservedPrimaryKeyColumnNames.sql similarity index 100% rename from test/Integration/test-csharp/Database/Tables/ProductsWithReservedPrimaryKeyColumnNames.sql rename to test/Database/Tables/ProductsWithReservedPrimaryKeyColumnNames.sql diff --git a/test/Integration/test-csharp/Database/Tables/ProductsWithUnsupportedColumnTypes.sql b/test/Database/Tables/ProductsWithUnsupportedColumnTypes.sql similarity index 100% rename from test/Integration/test-csharp/Database/Tables/ProductsWithUnsupportedColumnTypes.sql rename to test/Database/Tables/ProductsWithUnsupportedColumnTypes.sql diff --git a/test/Integration/test-csharp/Database/Tables/ProductsWithoutPrimaryKey.sql b/test/Database/Tables/ProductsWithoutPrimaryKey.sql similarity index 100% rename from test/Integration/test-csharp/Database/Tables/ProductsWithoutPrimaryKey.sql rename to test/Database/Tables/ProductsWithoutPrimaryKey.sql