From a514b3cad6f4c888163b14d9e11984c87eae4605 Mon Sep 17 00:00:00 2001 From: Maddy Koripalli Date: Wed, 5 Jul 2023 00:20:36 -0700 Subject: [PATCH 01/10] bracketed name for reserved words as table name --- src/TriggerBinding/SqlTriggerListener.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/TriggerBinding/SqlTriggerListener.cs b/src/TriggerBinding/SqlTriggerListener.cs index 83a6a7ac..a72266b8 100644 --- a/src/TriggerBinding/SqlTriggerListener.cs +++ b/src/TriggerBinding/SqlTriggerListener.cs @@ -82,8 +82,8 @@ public SqlTriggerListener(string connectionString, string tableName, string user } this._hasConfiguredMaxChangesPerWorker = configuredMaxChangesPerWorker != null; - this._scaleMonitor = new SqlTriggerScaleMonitor(this._userFunctionId, this._userTable.FullName, this._connectionString, this._maxChangesPerWorker, this._logger); - this._targetScaler = new SqlTriggerTargetScaler(this._userFunctionId, this._userTable.FullName, this._connectionString, this._maxChangesPerWorker, this._logger); + this._scaleMonitor = new SqlTriggerScaleMonitor(this._userFunctionId, this._userTable.BracketQuotedFullName, this._connectionString, this._maxChangesPerWorker, this._logger); + this._targetScaler = new SqlTriggerTargetScaler(this._userFunctionId, this._userTable.BracketQuotedFullName, this._connectionString, this._maxChangesPerWorker, this._logger); } public void Cancel() From 60154b4cf6466e05998d1edf6741761a82524908 Mon Sep 17 00:00:00 2001 From: Maddy Koripalli Date: Wed, 5 Jul 2023 12:37:22 -0700 Subject: [PATCH 02/10] add test --- src/TriggerBinding/SqlTriggerListener.cs | 2 +- .../SqlTriggerMetricsProvider.cs | 2 +- test/Database/Tables/User.sql | 7 ++ .../SqlTriggerBindingIntegrationTests.cs | 65 +++++++++++++++++++ .../test-csharp/ReservedTableNameTrigger.cs | 42 ++++++++++++ 5 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 test/Database/Tables/User.sql create mode 100644 test/Integration/test-csharp/ReservedTableNameTrigger.cs diff --git a/src/TriggerBinding/SqlTriggerListener.cs b/src/TriggerBinding/SqlTriggerListener.cs index a72266b8..f2b9f165 100644 --- a/src/TriggerBinding/SqlTriggerListener.cs +++ b/src/TriggerBinding/SqlTriggerListener.cs @@ -119,7 +119,7 @@ public async Task StartAsync(CancellationToken cancellationToken) await VerifyDatabaseSupported(connection, this._logger, cancellationToken); - int userTableId = await GetUserTableIdAsync(connection, this._userTable.FullName, this._logger, cancellationToken); + int userTableId = await GetUserTableIdAsync(connection, this._userTable.BracketQuotedFullName, this._logger, cancellationToken); IReadOnlyList<(string name, string type)> primaryKeyColumns = await GetPrimaryKeyColumnsAsync(connection, userTableId, this._logger, this._userTable.FullName, cancellationToken); IReadOnlyList userTableColumns = await this.GetUserTableColumnsAsync(connection, userTableId, cancellationToken); diff --git a/src/TriggerBinding/SqlTriggerMetricsProvider.cs b/src/TriggerBinding/SqlTriggerMetricsProvider.cs index e5c2ea99..33045be3 100644 --- a/src/TriggerBinding/SqlTriggerMetricsProvider.cs +++ b/src/TriggerBinding/SqlTriggerMetricsProvider.cs @@ -54,7 +54,7 @@ private async Task GetUnprocessedChangeCountAsync() { await connection.OpenAsync(); - int userTableId = await GetUserTableIdAsync(connection, this._userTable.FullName, this._logger, CancellationToken.None); + int userTableId = await GetUserTableIdAsync(connection, this._userTable.BracketQuotedFullName, this._logger, CancellationToken.None); IReadOnlyList<(string name, string type)> primaryKeyColumns = await GetPrimaryKeyColumnsAsync(connection, userTableId, this._logger, this._userTable.FullName, CancellationToken.None); // Use a transaction to automatically release the app lock when we're done executing the query diff --git a/test/Database/Tables/User.sql b/test/Database/Tables/User.sql new file mode 100644 index 00000000..8bebefac --- /dev/null +++ b/test/Database/Tables/User.sql @@ -0,0 +1,7 @@ +DROP TABLE IF EXISTS [dbo].[User]; + +CREATE TABLE [dbo].[User] ( + [UserId] [int] NOT NULL PRIMARY KEY, + [UserName] [nvarchar](50) NOT NULL, + [FullName] [nvarchar](max) NULL +) diff --git a/test/Integration/SqlTriggerBindingIntegrationTests.cs b/test/Integration/SqlTriggerBindingIntegrationTests.cs index 3fbb3520..00cd3c97 100644 --- a/test/Integration/SqlTriggerBindingIntegrationTests.cs +++ b/test/Integration/SqlTriggerBindingIntegrationTests.cs @@ -656,6 +656,71 @@ JOIN sys.columns c Assert.True(1 == (int)this.ExecuteScalar("SELECT 1 FROM sys.columns WHERE Name = N'LastAccessTime' AND Object_ID = Object_ID(N'[az_func].[GlobalState]')"), $"{GlobalStateTableName} should have {LastAccessTimeColumnName} column after restarting the listener."); } + /// + /// Tests that trigger function executes on table whose name is a reserved word (User). + /// + [Theory] + [SqlInlineData()] + [UnsupportedLanguages(SupportedLanguages.JavaScript, SupportedLanguages.Python, SupportedLanguages.PowerShell, SupportedLanguages.Csx, SupportedLanguages.Java, SupportedLanguages.OutOfProc)] + public async void ReservedTableNameTest(SupportedLanguages lang) + { + this.SetChangeTrackingForTable("User"); + this.StartFunctionHost(nameof(ReservedTableNameTrigger), lang, true); + User expectedResponse = Utils.JsonDeserializeObject(/*lang=json,strict*/ "{\"UserId\":999,\"UserName\":\"test\",\"FullName\":\"Testy Test\"}"); + int index = 0; + string messagePrefix = "SQL Changes: "; + + var taskCompletion = new TaskCompletionSource(); + + void MonitorOutputData(object sender, DataReceivedEventArgs e) + { + if (e.Data != null && (index = e.Data.IndexOf(messagePrefix, StringComparison.Ordinal)) >= 0) + { + string json = e.Data[(index + messagePrefix.Length)..]; + // Sometimes we'll get messages that have extra logging content on the same line - so to prevent that from breaking + // the deserialization we look for the end of the changes array and only use that. + // (This is fine since we control what content is in the array so know that none of the items have a ] in them) + json = json[..(json.IndexOf(']') + 1)]; + IReadOnlyList> changes; + try + { + changes = Utils.JsonDeserializeObject>>(json); + } + catch (Exception ex) + { + throw new InvalidOperationException($"Exception deserializing JSON content. Error={ex.Message} Json=\"{json}\"", ex); + } + Assert.Equal(SqlChangeOperation.Insert, changes[0].Operation); // Expected change operation + User user = changes[0].Item; + Assert.NotNull(user); // user deserialized correctly + Assert.Equal(expectedResponse, user); // user has the expected values + taskCompletion.SetResult(true); + } + }; + + // Set up listener for the changes coming in + foreach (Process functionHost in this.FunctionHostList) + { + functionHost.OutputDataReceived += MonitorOutputData; + } + + // Now that we've set up our listener trigger the actions to monitor + this.ExecuteNonQuery("INSERT INTO [dbo].[User] VALUES (" + + "999, " + // UserId, + "'test', " + // UserName + "'Testy Test')"); // FullName + + // Now wait until either we timeout or we've gotten all the expected changes, whichever comes first + this.LogOutput($"[{DateTime.UtcNow:u}] Waiting for Insert changes (10000ms)"); + await taskCompletion.Task.TimeoutAfter(TimeSpan.FromMilliseconds(10000), $"Timed out waiting for Insert changes."); + + // Unhook handler since we're done monitoring these changes so we aren't checking other changes done later + foreach (Process functionHost in this.FunctionHostList) + { + functionHost.OutputDataReceived -= MonitorOutputData; + } + } + /// /// Ensures that all column types are serialized correctly. /// diff --git a/test/Integration/test-csharp/ReservedTableNameTrigger.cs b/test/Integration/test-csharp/ReservedTableNameTrigger.cs new file mode 100644 index 00000000..a4fad723 --- /dev/null +++ b/test/Integration/test-csharp/ReservedTableNameTrigger.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 Microsoft.Extensions.Logging; + +namespace Microsoft.Azure.WebJobs.Extensions.Sql.Tests.Integration +{ + public static class ReservedTableNameTrigger + { + /// + /// Used in verification of the error message when the user table contains columns of unsupported SQL types. + /// + [FunctionName(nameof(ReservedTableNameTrigger))] + public static void Run( + [SqlTrigger("[dbo].[User]", "SqlConnectionString")] + IReadOnlyList> changes, + ILogger logger) + { + // The output is used to inspect the trigger binding parameter in test methods. + logger.LogInformation("SQL Changes: " + Utils.JsonSerializeObject(changes)); + } + } + + public class User + { + public string UserName { get; set; } + public int UserId { get; set; } + public string FullName { get; set; } + + public override bool Equals(object obj) + { + if (obj is User) + { + var that = obj as User; + return this.UserId == that.UserId && this.UserName == that.UserName && this.FullName == that.FullName; + } + + return false; + } + } +} From e7847d9698deb71039677c0299f11cb6dce14a73 Mon Sep 17 00:00:00 2001 From: Maddy Koripalli Date: Wed, 5 Jul 2023 14:19:53 -0700 Subject: [PATCH 03/10] add tests --- test-outofproc/ReservedTableNameTrigger.cs | 44 +++++++++++++++++++ .../SqlTriggerBindingIntegrationTests.cs | 2 +- .../ReservedTableNameTrigger/function.json | 12 +++++ .../test-csx/ReservedTableNameTrigger/run.csx | 30 +++++++++++++ .../main/java/com/function/Common/User.java | 31 +++++++++++++ .../function/ReservedTableNameTrigger.java | 29 ++++++++++++ .../ReservedTableNameTrigger/function.json | 12 +++++ .../test-js/ReservedTableNameTrigger/index.js | 6 +++ .../ReservedTableNameTrigger/function.json | 12 +++++ .../ReservedTableNameTrigger/run.ps1 | 9 ++++ .../ReservedTableNameTrigger/__init__.py | 7 +++ .../ReservedTableNameTrigger/function.json | 12 +++++ 12 files changed, 205 insertions(+), 1 deletion(-) create mode 100644 test-outofproc/ReservedTableNameTrigger.cs create mode 100644 test/Integration/test-csx/ReservedTableNameTrigger/function.json create mode 100644 test/Integration/test-csx/ReservedTableNameTrigger/run.csx create mode 100644 test/Integration/test-java/src/main/java/com/function/Common/User.java create mode 100644 test/Integration/test-java/src/main/java/com/function/ReservedTableNameTrigger.java create mode 100644 test/Integration/test-js/ReservedTableNameTrigger/function.json create mode 100644 test/Integration/test-js/ReservedTableNameTrigger/index.js create mode 100644 test/Integration/test-powershell/ReservedTableNameTrigger/function.json create mode 100644 test/Integration/test-powershell/ReservedTableNameTrigger/run.ps1 create mode 100644 test/Integration/test-python/ReservedTableNameTrigger/__init__.py create mode 100644 test/Integration/test-python/ReservedTableNameTrigger/function.json diff --git a/test-outofproc/ReservedTableNameTrigger.cs b/test-outofproc/ReservedTableNameTrigger.cs new file mode 100644 index 00000000..e9cbcf57 --- /dev/null +++ b/test-outofproc/ReservedTableNameTrigger.cs @@ -0,0 +1,44 @@ +// 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 Microsoft.Extensions.Logging; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Azure.Functions.Worker.Extensions.Sql; + +namespace DotnetIsolatedTests +{ + public static class ReservedTableNameTrigger + { + /// + /// Used in verification of the trigger function execution on table with reserved keys as name. + /// + [Function(nameof(ReservedTableNameTrigger))] + public static void Run( + [SqlTrigger("[dbo].[User]", "SqlConnectionString")] + IReadOnlyList> changes, + FunctionContext context) + { + ILogger logger = context.GetLogger("ReservedTableNameTrigger"); + logger.LogInformation("SQL Changes: " + Utils.JsonSerializeObject(changes)); + } + } + + public class User + { + public string UserName { get; set; } + public int UserId { get; set; } + public string FullName { get; set; } + + public override bool Equals(object obj) + { + if (obj is User) + { + var that = obj as User; + return this.UserId == that.UserId && this.UserName == that.UserName && this.FullName == that.FullName; + } + + return false; + } + } +} diff --git a/test/Integration/SqlTriggerBindingIntegrationTests.cs b/test/Integration/SqlTriggerBindingIntegrationTests.cs index 00cd3c97..55538d00 100644 --- a/test/Integration/SqlTriggerBindingIntegrationTests.cs +++ b/test/Integration/SqlTriggerBindingIntegrationTests.cs @@ -661,7 +661,7 @@ JOIN sys.columns c /// [Theory] [SqlInlineData()] - [UnsupportedLanguages(SupportedLanguages.JavaScript, SupportedLanguages.Python, SupportedLanguages.PowerShell, SupportedLanguages.Csx, SupportedLanguages.Java, SupportedLanguages.OutOfProc)] + [UnsupportedLanguages(SupportedLanguages.JavaScript, SupportedLanguages.Python, SupportedLanguages.PowerShell, SupportedLanguages.Csx, SupportedLanguages.Java, SupportedLanguages.CSharp)] public async void ReservedTableNameTest(SupportedLanguages lang) { this.SetChangeTrackingForTable("User"); diff --git a/test/Integration/test-csx/ReservedTableNameTrigger/function.json b/test/Integration/test-csx/ReservedTableNameTrigger/function.json new file mode 100644 index 00000000..7e073322 --- /dev/null +++ b/test/Integration/test-csx/ReservedTableNameTrigger/function.json @@ -0,0 +1,12 @@ +{ + "bindings": [ + { + "name": "changes", + "type": "sqlTrigger", + "direction": "in", + "tableName": "[dbo].[User]", + "connectionStringSetting": "SqlConnectionString" + } + ], + "disabled": false +} \ No newline at end of file diff --git a/test/Integration/test-csx/ReservedTableNameTrigger/run.csx b/test/Integration/test-csx/ReservedTableNameTrigger/run.csx new file mode 100644 index 00000000..58791922 --- /dev/null +++ b/test/Integration/test-csx/ReservedTableNameTrigger/run.csx @@ -0,0 +1,30 @@ +#r "Microsoft.Azure.WebJobs.Extensions.Sql" + +using System.Net; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Primitives; +using Microsoft.Azure.WebJobs.Extensions.Sql; + +public static void Run(IReadOnlyList> changes, ILogger log) +{ + // The output is used to inspect the trigger binding parameter in test methods. + log.LogInformation("SQL Changes: " + Microsoft.Azure.WebJobs.Extensions.Sql.Utils.JsonSerializeObject(changes)); +} + +public class User +{ + public string UserName { get; set; } + public int UserId { get; set; } + public string FullName { get; set; } + + public override bool Equals(object obj) + { + if (obj is User) + { + var that = obj as User; + return this.UserId == that.UserId && this.UserName == that.UserName && this.FullName == that.FullName; + } + + return false; + } +} \ No newline at end of file diff --git a/test/Integration/test-java/src/main/java/com/function/Common/User.java b/test/Integration/test-java/src/main/java/com/function/Common/User.java new file mode 100644 index 00000000..5052fefa --- /dev/null +++ b/test/Integration/test-java/src/main/java/com/function/Common/User.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for + * license information. + */ + +package com.function.Common; + +public class User { + private int UserId; + private String UserName; + private String FullName; + + public User(int userId, String userName, String fullName) { + UserId = userId; + UserName = userName; + FullName = fullName; + } + + public int getUserId() { + return UserId; + } + + public String getUserName() { + return UserName; + } + + public String getFullName() { + return FullName; + } +} diff --git a/test/Integration/test-java/src/main/java/com/function/ReservedTableNameTrigger.java b/test/Integration/test-java/src/main/java/com/function/ReservedTableNameTrigger.java new file mode 100644 index 00000000..97e4603a --- /dev/null +++ b/test/Integration/test-java/src/main/java/com/function/ReservedTableNameTrigger.java @@ -0,0 +1,29 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for + * license information. + */ + +package com.function; + +import com.function.Common.User; +import com.google.gson.Gson; +import com.microsoft.azure.functions.ExecutionContext; +import com.microsoft.azure.functions.annotation.FunctionName; +import com.microsoft.azure.functions.sql.annotation.SQLTrigger; + +import java.util.logging.Level; + +public class ReservedTableNameTrigger { + @FunctionName("ReservedTableNameTrigger") + public void run( + @SQLTrigger( + name = "changes", + tableName = "[dbo].[User]", + connectionStringSetting = "SqlConnectionString") + User[] changes, + ExecutionContext context) throws Exception { + + context.getLogger().log(Level.INFO, "SQL Changes: " + new Gson().toJson(changes)); + } +} \ No newline at end of file diff --git a/test/Integration/test-js/ReservedTableNameTrigger/function.json b/test/Integration/test-js/ReservedTableNameTrigger/function.json new file mode 100644 index 00000000..93bd051b --- /dev/null +++ b/test/Integration/test-js/ReservedTableNameTrigger/function.json @@ -0,0 +1,12 @@ +{ + "bindings": [ + { + "name": "changes", + "type": "sqlTrigger", + "direction": "in", + "tableName": "[dbo].[User]", + "connectionStringSetting": "SqlConnectionString" + } + ], + "disabled": false + } \ No newline at end of file diff --git a/test/Integration/test-js/ReservedTableNameTrigger/index.js b/test/Integration/test-js/ReservedTableNameTrigger/index.js new file mode 100644 index 00000000..5886e5c8 --- /dev/null +++ b/test/Integration/test-js/ReservedTableNameTrigger/index.js @@ -0,0 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +module.exports = async function (context, changes) { + context.log(`SQL Changes: ${JSON.stringify(changes)}`) +} \ No newline at end of file diff --git a/test/Integration/test-powershell/ReservedTableNameTrigger/function.json b/test/Integration/test-powershell/ReservedTableNameTrigger/function.json new file mode 100644 index 00000000..93bd051b --- /dev/null +++ b/test/Integration/test-powershell/ReservedTableNameTrigger/function.json @@ -0,0 +1,12 @@ +{ + "bindings": [ + { + "name": "changes", + "type": "sqlTrigger", + "direction": "in", + "tableName": "[dbo].[User]", + "connectionStringSetting": "SqlConnectionString" + } + ], + "disabled": false + } \ No newline at end of file diff --git a/test/Integration/test-powershell/ReservedTableNameTrigger/run.ps1 b/test/Integration/test-powershell/ReservedTableNameTrigger/run.ps1 new file mode 100644 index 00000000..e8c4559d --- /dev/null +++ b/test/Integration/test-powershell/ReservedTableNameTrigger/run.ps1 @@ -0,0 +1,9 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. + +using namespace System.Net + +param($changes) + +$changesJson = $changes | ConvertTo-Json -Compress -AsArray +Write-Host "SQL Changes: $changesJson" \ No newline at end of file diff --git a/test/Integration/test-python/ReservedTableNameTrigger/__init__.py b/test/Integration/test-python/ReservedTableNameTrigger/__init__.py new file mode 100644 index 00000000..688c1e16 --- /dev/null +++ b/test/Integration/test-python/ReservedTableNameTrigger/__init__.py @@ -0,0 +1,7 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import logging + +def main(changes): + logging.info("SQL Changes: %s", changes) diff --git a/test/Integration/test-python/ReservedTableNameTrigger/function.json b/test/Integration/test-python/ReservedTableNameTrigger/function.json new file mode 100644 index 00000000..93bd051b --- /dev/null +++ b/test/Integration/test-python/ReservedTableNameTrigger/function.json @@ -0,0 +1,12 @@ +{ + "bindings": [ + { + "name": "changes", + "type": "sqlTrigger", + "direction": "in", + "tableName": "[dbo].[User]", + "connectionStringSetting": "SqlConnectionString" + } + ], + "disabled": false + } \ No newline at end of file From 73992db37025ae45f52d71a57fa1fd9189317802 Mon Sep 17 00:00:00 2001 From: Maddy Koripalli Date: Thu, 6 Jul 2023 16:13:31 -0700 Subject: [PATCH 04/10] fix csx test --- test/Integration/test-csharp/ReservedTableNameTrigger.cs | 2 +- test/Integration/test-csx/ReservedTableNameTrigger/run.csx | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/test/Integration/test-csharp/ReservedTableNameTrigger.cs b/test/Integration/test-csharp/ReservedTableNameTrigger.cs index a4fad723..079c1790 100644 --- a/test/Integration/test-csharp/ReservedTableNameTrigger.cs +++ b/test/Integration/test-csharp/ReservedTableNameTrigger.cs @@ -9,7 +9,7 @@ namespace Microsoft.Azure.WebJobs.Extensions.Sql.Tests.Integration public static class ReservedTableNameTrigger { /// - /// Used in verification of the error message when the user table contains columns of unsupported SQL types. + /// Used in verification of the trigger function execution on table with reserved keys as name. /// [FunctionName(nameof(ReservedTableNameTrigger))] public static void Run( diff --git a/test/Integration/test-csx/ReservedTableNameTrigger/run.csx b/test/Integration/test-csx/ReservedTableNameTrigger/run.csx index 58791922..1996eeb2 100644 --- a/test/Integration/test-csx/ReservedTableNameTrigger/run.csx +++ b/test/Integration/test-csx/ReservedTableNameTrigger/run.csx @@ -1,8 +1,10 @@ +#r "Newtonsoft.Json" #r "Microsoft.Azure.WebJobs.Extensions.Sql" using System.Net; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Primitives; +using Newtonsoft.Json; using Microsoft.Azure.WebJobs.Extensions.Sql; public static void Run(IReadOnlyList> changes, ILogger log) From c4aba617d15a4caf320551bd90762f48405b9017 Mon Sep 17 00:00:00 2001 From: maddydev Date: Fri, 7 Jul 2023 13:34:39 -0700 Subject: [PATCH 05/10] enable test only for csharp --- test/Integration/SqlTriggerBindingIntegrationTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Integration/SqlTriggerBindingIntegrationTests.cs b/test/Integration/SqlTriggerBindingIntegrationTests.cs index 55538d00..00cd3c97 100644 --- a/test/Integration/SqlTriggerBindingIntegrationTests.cs +++ b/test/Integration/SqlTriggerBindingIntegrationTests.cs @@ -661,7 +661,7 @@ JOIN sys.columns c /// [Theory] [SqlInlineData()] - [UnsupportedLanguages(SupportedLanguages.JavaScript, SupportedLanguages.Python, SupportedLanguages.PowerShell, SupportedLanguages.Csx, SupportedLanguages.Java, SupportedLanguages.CSharp)] + [UnsupportedLanguages(SupportedLanguages.JavaScript, SupportedLanguages.Python, SupportedLanguages.PowerShell, SupportedLanguages.Csx, SupportedLanguages.Java, SupportedLanguages.OutOfProc)] public async void ReservedTableNameTest(SupportedLanguages lang) { this.SetChangeTrackingForTable("User"); From 5f267835249962a37dd2fecab4135ba1ba7ddef1 Mon Sep 17 00:00:00 2001 From: Maddy Koripalli Date: Fri, 7 Jul 2023 15:20:42 -0700 Subject: [PATCH 06/10] fix TableNotPresentTest --- test/Integration/SqlTriggerBindingIntegrationTests.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/Integration/SqlTriggerBindingIntegrationTests.cs b/test/Integration/SqlTriggerBindingIntegrationTests.cs index 00cd3c97..951194da 100644 --- a/test/Integration/SqlTriggerBindingIntegrationTests.cs +++ b/test/Integration/SqlTriggerBindingIntegrationTests.cs @@ -472,7 +472,7 @@ public void TableNotPresentTriggerTest(SupportedLanguages lang) nameof(TableNotPresentTrigger), lang, true, - "Could not find table: 'dbo.TableNotPresent'."); + "Could not find table: '[dbo].[TableNotPresent]'."); } /// @@ -661,7 +661,6 @@ JOIN sys.columns c /// [Theory] [SqlInlineData()] - [UnsupportedLanguages(SupportedLanguages.JavaScript, SupportedLanguages.Python, SupportedLanguages.PowerShell, SupportedLanguages.Csx, SupportedLanguages.Java, SupportedLanguages.OutOfProc)] public async void ReservedTableNameTest(SupportedLanguages lang) { this.SetChangeTrackingForTable("User"); From b76a23251eec489588b71abfa1392ed4b22d7a9f Mon Sep 17 00:00:00 2001 From: Maddy Koripalli Date: Fri, 7 Jul 2023 18:52:34 -0700 Subject: [PATCH 07/10] revert GetUserTableIdAsync change --- src/TriggerBinding/SqlTriggerListener.cs | 2 +- src/TriggerBinding/SqlTriggerMetricsProvider.cs | 2 +- test/Integration/SqlTriggerBindingIntegrationTests.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/TriggerBinding/SqlTriggerListener.cs b/src/TriggerBinding/SqlTriggerListener.cs index f2b9f165..a72266b8 100644 --- a/src/TriggerBinding/SqlTriggerListener.cs +++ b/src/TriggerBinding/SqlTriggerListener.cs @@ -119,7 +119,7 @@ public async Task StartAsync(CancellationToken cancellationToken) await VerifyDatabaseSupported(connection, this._logger, cancellationToken); - int userTableId = await GetUserTableIdAsync(connection, this._userTable.BracketQuotedFullName, this._logger, cancellationToken); + int userTableId = await GetUserTableIdAsync(connection, this._userTable.FullName, this._logger, cancellationToken); IReadOnlyList<(string name, string type)> primaryKeyColumns = await GetPrimaryKeyColumnsAsync(connection, userTableId, this._logger, this._userTable.FullName, cancellationToken); IReadOnlyList userTableColumns = await this.GetUserTableColumnsAsync(connection, userTableId, cancellationToken); diff --git a/src/TriggerBinding/SqlTriggerMetricsProvider.cs b/src/TriggerBinding/SqlTriggerMetricsProvider.cs index 33045be3..e5c2ea99 100644 --- a/src/TriggerBinding/SqlTriggerMetricsProvider.cs +++ b/src/TriggerBinding/SqlTriggerMetricsProvider.cs @@ -54,7 +54,7 @@ private async Task GetUnprocessedChangeCountAsync() { await connection.OpenAsync(); - int userTableId = await GetUserTableIdAsync(connection, this._userTable.BracketQuotedFullName, this._logger, CancellationToken.None); + int userTableId = await GetUserTableIdAsync(connection, this._userTable.FullName, this._logger, CancellationToken.None); IReadOnlyList<(string name, string type)> primaryKeyColumns = await GetPrimaryKeyColumnsAsync(connection, userTableId, this._logger, this._userTable.FullName, CancellationToken.None); // Use a transaction to automatically release the app lock when we're done executing the query diff --git a/test/Integration/SqlTriggerBindingIntegrationTests.cs b/test/Integration/SqlTriggerBindingIntegrationTests.cs index 951194da..8c79b3df 100644 --- a/test/Integration/SqlTriggerBindingIntegrationTests.cs +++ b/test/Integration/SqlTriggerBindingIntegrationTests.cs @@ -472,7 +472,7 @@ public void TableNotPresentTriggerTest(SupportedLanguages lang) nameof(TableNotPresentTrigger), lang, true, - "Could not find table: '[dbo].[TableNotPresent]'."); + "Could not find table: 'dbo.TableNotPresent'."); } /// From efc572ee00cd3087307928ad8a853832abe2d911 Mon Sep 17 00:00:00 2001 From: Maddy Koripalli Date: Fri, 7 Jul 2023 19:36:31 -0700 Subject: [PATCH 08/10] refactor GetUserTableIdAsync --- src/TriggerBinding/SqlTriggerListener.cs | 2 +- src/TriggerBinding/SqlTriggerMetricsProvider.cs | 2 +- src/TriggerBinding/SqlTriggerUtils.cs | 9 ++++----- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/TriggerBinding/SqlTriggerListener.cs b/src/TriggerBinding/SqlTriggerListener.cs index a72266b8..bbea24a3 100644 --- a/src/TriggerBinding/SqlTriggerListener.cs +++ b/src/TriggerBinding/SqlTriggerListener.cs @@ -119,7 +119,7 @@ public async Task StartAsync(CancellationToken cancellationToken) await VerifyDatabaseSupported(connection, this._logger, cancellationToken); - int userTableId = await GetUserTableIdAsync(connection, this._userTable.FullName, this._logger, cancellationToken); + int userTableId = await GetUserTableIdAsync(connection, this._userTable, this._logger, cancellationToken); IReadOnlyList<(string name, string type)> primaryKeyColumns = await GetPrimaryKeyColumnsAsync(connection, userTableId, this._logger, this._userTable.FullName, cancellationToken); IReadOnlyList userTableColumns = await this.GetUserTableColumnsAsync(connection, userTableId, cancellationToken); diff --git a/src/TriggerBinding/SqlTriggerMetricsProvider.cs b/src/TriggerBinding/SqlTriggerMetricsProvider.cs index e5c2ea99..67ad5ea6 100644 --- a/src/TriggerBinding/SqlTriggerMetricsProvider.cs +++ b/src/TriggerBinding/SqlTriggerMetricsProvider.cs @@ -54,7 +54,7 @@ private async Task GetUnprocessedChangeCountAsync() { await connection.OpenAsync(); - int userTableId = await GetUserTableIdAsync(connection, this._userTable.FullName, this._logger, CancellationToken.None); + int userTableId = await GetUserTableIdAsync(connection, this._userTable, this._logger, CancellationToken.None); IReadOnlyList<(string name, string type)> primaryKeyColumns = await GetPrimaryKeyColumnsAsync(connection, userTableId, this._logger, this._userTable.FullName, CancellationToken.None); // Use a transaction to automatically release the app lock when we're done executing the query diff --git a/src/TriggerBinding/SqlTriggerUtils.cs b/src/TriggerBinding/SqlTriggerUtils.cs index 89066ca0..9d704be4 100644 --- a/src/TriggerBinding/SqlTriggerUtils.cs +++ b/src/TriggerBinding/SqlTriggerUtils.cs @@ -85,13 +85,12 @@ FROM sys.indexes AS i /// Returns the object ID of the user table. /// /// SQL connection used to connect to user database - /// Name of the user table + /// SqlObject user table /// Facilitates logging of messages /// Cancellation token to pass to the command /// Thrown in case of error when querying the object ID for the user table - public static async Task GetUserTableIdAsync(SqlConnection connection, string userTableName, ILogger logger, CancellationToken cancellationToken) + internal static async Task GetUserTableIdAsync(SqlConnection connection, SqlObject userTable, ILogger logger, CancellationToken cancellationToken) { - var userTable = new SqlObject(userTableName); string getObjectIdQuery = $"SELECT OBJECT_ID(N{userTable.QuotedFullName}, 'U');"; using (var getObjectIdCommand = new SqlCommand(getObjectIdQuery, connection)) @@ -99,14 +98,14 @@ public static async Task GetUserTableIdAsync(SqlConnection connection, stri { if (!await reader.ReadAsync(cancellationToken)) { - throw new InvalidOperationException($"Received empty response when querying the object ID for table: '{userTableName}'."); + throw new InvalidOperationException($"Received empty response when querying the object ID for table: '{userTable.FullName}'."); } object userTableId = reader.GetValue(0); if (userTableId is DBNull) { - throw new InvalidOperationException($"Could not find table: '{userTableName}'."); + throw new InvalidOperationException($"Could not find table: '{userTable.FullName}'."); } logger.LogDebug($"GetUserTableId TableId={userTableId}"); return (int)userTableId; From a28b542f694790ca150d9564e53f82e946241c10 Mon Sep 17 00:00:00 2001 From: maddydev Date: Sun, 9 Jul 2023 00:49:57 -0700 Subject: [PATCH 09/10] comment out Java test --- test/Integration/SqlTriggerBindingIntegrationTests.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/Integration/SqlTriggerBindingIntegrationTests.cs b/test/Integration/SqlTriggerBindingIntegrationTests.cs index 8c79b3df..b88b83c3 100644 --- a/test/Integration/SqlTriggerBindingIntegrationTests.cs +++ b/test/Integration/SqlTriggerBindingIntegrationTests.cs @@ -661,6 +661,8 @@ JOIN sys.columns c /// [Theory] [SqlInlineData()] + [UnsupportedLanguages(SupportedLanguages.Java)] // test timing out for Java + public async void ReservedTableNameTest(SupportedLanguages lang) { this.SetChangeTrackingForTable("User"); From 2c745af5807950bf24685e279758c49c99beafed Mon Sep 17 00:00:00 2001 From: maddydev Date: Wed, 12 Jul 2023 01:01:43 -0700 Subject: [PATCH 10/10] refactor code to use SqlObject --- src/TriggerBinding/SqlTriggerListener.cs | 4 ++-- src/TriggerBinding/SqlTriggerScaleMonitor.cs | 6 +++--- src/TriggerBinding/SqlTriggerTargetScaler.cs | 4 ++-- test/Integration/SqlTriggerBindingIntegrationTests.cs | 1 - test/Unit/TriggerBinding/SqlTriggerScaleMonitorTests.cs | 4 ++-- 5 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/TriggerBinding/SqlTriggerListener.cs b/src/TriggerBinding/SqlTriggerListener.cs index bbea24a3..bf578ff6 100644 --- a/src/TriggerBinding/SqlTriggerListener.cs +++ b/src/TriggerBinding/SqlTriggerListener.cs @@ -82,8 +82,8 @@ public SqlTriggerListener(string connectionString, string tableName, string user } this._hasConfiguredMaxChangesPerWorker = configuredMaxChangesPerWorker != null; - this._scaleMonitor = new SqlTriggerScaleMonitor(this._userFunctionId, this._userTable.BracketQuotedFullName, this._connectionString, this._maxChangesPerWorker, this._logger); - this._targetScaler = new SqlTriggerTargetScaler(this._userFunctionId, this._userTable.BracketQuotedFullName, this._connectionString, this._maxChangesPerWorker, this._logger); + this._scaleMonitor = new SqlTriggerScaleMonitor(this._userFunctionId, this._userTable, this._connectionString, this._maxChangesPerWorker, this._logger); + this._targetScaler = new SqlTriggerTargetScaler(this._userFunctionId, this._userTable, this._connectionString, this._maxChangesPerWorker, this._logger); } public void Cancel() diff --git a/src/TriggerBinding/SqlTriggerScaleMonitor.cs b/src/TriggerBinding/SqlTriggerScaleMonitor.cs index 24f3435a..c30f54e3 100644 --- a/src/TriggerBinding/SqlTriggerScaleMonitor.cs +++ b/src/TriggerBinding/SqlTriggerScaleMonitor.cs @@ -25,11 +25,11 @@ internal sealed class SqlTriggerScaleMonitor : IScaleMonitor private readonly IDictionary _telemetryProps = new Dictionary(); private readonly int _maxChangesPerWorker; - public SqlTriggerScaleMonitor(string userFunctionId, string userTableName, string connectionString, int maxChangesPerWorker, ILogger logger) + public SqlTriggerScaleMonitor(string userFunctionId, SqlObject userTable, string connectionString, int maxChangesPerWorker, ILogger logger) { _ = !string.IsNullOrEmpty(userFunctionId) ? true : throw new ArgumentNullException(userFunctionId); - _ = !string.IsNullOrEmpty(userTableName) ? true : throw new ArgumentNullException(userTableName); - this._userTable = new SqlObject(userTableName); + _ = userTable != null ? true : throw new ArgumentNullException(nameof(userTable)); + this._userTable = userTable; // Do not convert the scale-monitor ID to lower-case string since SQL table names can be case-sensitive // depending on the collation of the current database. this.Descriptor = new ScaleMonitorDescriptor($"{userFunctionId}-SqlTrigger-{this._userTable.FullName}", userFunctionId); diff --git a/src/TriggerBinding/SqlTriggerTargetScaler.cs b/src/TriggerBinding/SqlTriggerTargetScaler.cs index b56b9962..b1aabf5e 100644 --- a/src/TriggerBinding/SqlTriggerTargetScaler.cs +++ b/src/TriggerBinding/SqlTriggerTargetScaler.cs @@ -15,9 +15,9 @@ internal sealed class SqlTriggerTargetScaler : ITargetScaler private readonly SqlTriggerMetricsProvider _metricsProvider; private readonly int _maxChangesPerWorker; - public SqlTriggerTargetScaler(string userFunctionId, string userTableName, string connectionString, int maxChangesPerWorker, ILogger logger) + public SqlTriggerTargetScaler(string userFunctionId, SqlObject userTable, string connectionString, int maxChangesPerWorker, ILogger logger) { - this._metricsProvider = new SqlTriggerMetricsProvider(connectionString, logger, new SqlObject(userTableName), userFunctionId); + this._metricsProvider = new SqlTriggerMetricsProvider(connectionString, logger, userTable, userFunctionId); this.TargetScalerDescriptor = new TargetScalerDescriptor(userFunctionId); this._maxChangesPerWorker = maxChangesPerWorker; } diff --git a/test/Integration/SqlTriggerBindingIntegrationTests.cs b/test/Integration/SqlTriggerBindingIntegrationTests.cs index b88b83c3..f5f93b26 100644 --- a/test/Integration/SqlTriggerBindingIntegrationTests.cs +++ b/test/Integration/SqlTriggerBindingIntegrationTests.cs @@ -662,7 +662,6 @@ JOIN sys.columns c [Theory] [SqlInlineData()] [UnsupportedLanguages(SupportedLanguages.Java)] // test timing out for Java - public async void ReservedTableNameTest(SupportedLanguages lang) { this.SetChangeTrackingForTable("User"); diff --git a/test/Unit/TriggerBinding/SqlTriggerScaleMonitorTests.cs b/test/Unit/TriggerBinding/SqlTriggerScaleMonitorTests.cs index a2f37abb..b7d5db85 100644 --- a/test/Unit/TriggerBinding/SqlTriggerScaleMonitorTests.cs +++ b/test/Unit/TriggerBinding/SqlTriggerScaleMonitorTests.cs @@ -233,7 +233,7 @@ private static IScaleMonitor GetScaleMonitor(string tableName { return new SqlTriggerScaleMonitor( userFunctionId, - tableName, + new SqlObject(tableName), "testConnectionString", SqlTriggerListener.DefaultMaxChangesPerWorker, Mock.Of()); @@ -245,7 +245,7 @@ private static (IScaleMonitor monitor, List logMessag IScaleMonitor monitor = new SqlTriggerScaleMonitor( "testUserFunctionId", - "testTableName", + new SqlObject("testTableName"), "testConnectionString", maxChangesPerWorker, mockLogger.Object);