diff --git a/src/DurableTask.SqlServer.AzureFunctions/SqlDurabilityOptions.cs b/src/DurableTask.SqlServer.AzureFunctions/SqlDurabilityOptions.cs index 3b7fc99..53ebc72 100644 --- a/src/DurableTask.SqlServer.AzureFunctions/SqlDurabilityOptions.cs +++ b/src/DurableTask.SqlServer.AzureFunctions/SqlDurabilityOptions.cs @@ -17,7 +17,7 @@ class SqlDurabilityOptions public string ConnectionStringName { get; set; } = "SQLDB_Connection"; [JsonProperty("taskHubName")] - public string TaskHubName { get; set; } = "default"; + public string TaskHubName { get; set; } = SqlOrchestrationServiceSettings.DefaultTaskHubName; [JsonProperty("taskEventLockTimeout")] public TimeSpan TaskEventLockTimeout { get; set; } = TimeSpan.FromMinutes(2); diff --git a/src/DurableTask.SqlServer.AzureFunctions/SqlScaleMonitor.cs b/src/DurableTask.SqlServer.AzureFunctions/SqlScaleMonitor.cs index 447dc3e..2e6b363 100644 --- a/src/DurableTask.SqlServer.AzureFunctions/SqlScaleMonitor.cs +++ b/src/DurableTask.SqlServer.AzureFunctions/SqlScaleMonitor.cs @@ -26,7 +26,7 @@ class SqlScaleMonitor : IScaleMonitor public SqlScaleMonitor(SqlOrchestrationService service, string taskHubName) { this.service = service ?? throw new ArgumentNullException(nameof(service)); - this.Descriptor = new ScaleMonitorDescriptor($"DurableTask-SqlServer:{taskHubName ?? "default"}"); + this.Descriptor = new ScaleMonitorDescriptor($"DurableTask-SqlServer:{taskHubName ?? SqlOrchestrationServiceSettings.DefaultTaskHubName}"); } /// diff --git a/src/DurableTask.SqlServer/Scripts/drop-schema.sql b/src/DurableTask.SqlServer/Scripts/drop-schema.sql index f50f629..3dd6d95 100644 --- a/src/DurableTask.SqlServer/Scripts/drop-schema.sql +++ b/src/DurableTask.SqlServer/Scripts/drop-schema.sql @@ -2,8 +2,11 @@ -- Licensed under the MIT License. -- Functions +DROP FUNCTION IF EXISTS __SchemaNamePlaceholder__._CurrentTaskHub DROP FUNCTION IF EXISTS __SchemaNamePlaceholder__.CurrentTaskHub +DROP FUNCTION IF EXISTS __SchemaNamePlaceholder__._GetScaleMetric DROP FUNCTION IF EXISTS __SchemaNamePlaceholder__.GetScaleMetric +DROP FUNCTION IF EXISTS __SchemaNamePlaceholder__._GetScaleRecommendation DROP FUNCTION IF EXISTS __SchemaNamePlaceholder__.GetScaleRecommendation -- Views diff --git a/src/DurableTask.SqlServer/Scripts/logic.sql b/src/DurableTask.SqlServer/Scripts/logic.sql index 9e6fef2..25e0b73 100644 --- a/src/DurableTask.SqlServer/Scripts/logic.sql +++ b/src/DurableTask.SqlServer/Scripts/logic.sql @@ -80,7 +80,7 @@ IF TYPE_ID(N'__SchemaNamePlaceholder__.TaskEvents') IS NULL GO -CREATE OR ALTER FUNCTION __SchemaNamePlaceholder__.CurrentTaskHub() +CREATE OR ALTER FUNCTION __SchemaNamePlaceholder__._CurrentTaskHub(@TaskHubName varchar(150)) RETURNS varchar(50) WITH EXECUTE AS CALLER AS @@ -90,31 +90,38 @@ BEGIN -- 1: Task hub names are inferred from the user credential DECLARE @taskHubMode sql_variant = (SELECT TOP 1 [Value] FROM GlobalSettings WHERE [Name] = 'TaskHubMode'); - DECLARE @taskHub varchar(150) - - IF @taskHubMode = 0 - SET @taskHub = APP_NAME() - IF @taskHubMode = 1 - SET @taskHub = USER_NAME() - - IF @taskHub IS NULL - SET @taskHub = 'default' + DECLARE @taskHub varchar(150); + IF @taskHubMode = 1 + SET @taskHub = USER_NAME(); + ELSE IF @TaskHubName is NULL + SET @taskHub = APP_NAME(); + ELSE + SET @taskHub = @TaskHubName; + -- if the name is too long, keep the first 16 characters and hash the rest IF LEN(@taskHub) > 50 - SET @taskHub = CONVERT(varchar(16), @taskHub) + '__' + CONVERT(varchar(32), HASHBYTES('MD5', @taskHub), 2) + SET @taskHub = CONVERT(varchar(16), @taskHub) + '__' + CONVERT(varchar(32), HASHBYTES('MD5', @taskHub), 2); - RETURN @taskHub + RETURN @taskHub; END GO - -CREATE OR ALTER FUNCTION __SchemaNamePlaceholder__.GetScaleMetric() +CREATE OR ALTER FUNCTION __SchemaNamePlaceholder__.CurrentTaskHub() + RETURNS varchar(50) + WITH EXECUTE AS CALLER +AS +BEGIN + RETURN __SchemaNamePlaceholder__._CurrentTaskHub(null); +END +GO + +CREATE OR ALTER FUNCTION __SchemaNamePlaceholder__._GetScaleMetric(@TaskHubName varchar(150)) RETURNS INT WITH EXECUTE AS CALLER AS BEGIN - DECLARE @taskHub varchar(50) = __SchemaNamePlaceholder__.CurrentTaskHub() + DECLARE @taskHub varchar(50) = __SchemaNamePlaceholder__._CurrentTaskHub(@TaskHubName) DECLARE @now datetime2 = SYSUTCDATETIME() DECLARE @liveInstances bigint = 0 @@ -135,13 +142,21 @@ BEGIN END GO +CREATE OR ALTER FUNCTION __SchemaNamePlaceholder__.GetScaleMetric() + RETURNS INT + WITH EXECUTE AS CALLER +AS +BEGIN + RETURN __SchemaNamePlaceholder__._GetScaleMetric(null); +END +GO -CREATE OR ALTER FUNCTION __SchemaNamePlaceholder__.GetScaleRecommendation(@MaxOrchestrationsPerWorker real, @MaxActivitiesPerWorker real) +CREATE OR ALTER FUNCTION __SchemaNamePlaceholder__._GetScaleRecommendation(@MaxOrchestrationsPerWorker real, @MaxActivitiesPerWorker real, @TaskHubName varchar(150)) RETURNS INT WITH EXECUTE AS CALLER AS BEGIN - DECLARE @taskHub varchar(50) = __SchemaNamePlaceholder__.CurrentTaskHub() + DECLARE @taskHub varchar(50) = __SchemaNamePlaceholder__._CurrentTaskHub(@TaskHubName) DECLARE @now datetime2 = SYSUTCDATETIME() DECLARE @liveInstances bigint = 0 @@ -168,6 +183,14 @@ BEGIN END GO +CREATE OR ALTER FUNCTION __SchemaNamePlaceholder__.GetScaleRecommendation(@MaxOrchestrationsPerWorker real, @MaxActivitiesPerWorker real) + RETURNS INT + WITH EXECUTE AS CALLER +AS +BEGIN + RETURN __SchemaNamePlaceholder__.GetScaleRecommendation(@MaxOrchestrationsPerWorker, @MaxActivitiesPerWorker, NULL); +END +GO CREATE OR ALTER VIEW __SchemaNamePlaceholder__.vInstances AS @@ -183,21 +206,21 @@ AS I.[RuntimeStatus], I.[TraceContext], (SELECT TOP 1 [Text] FROM Payloads P WHERE - P.[TaskHub] = __SchemaNamePlaceholder__.CurrentTaskHub() AND + P.[TaskHub] = I.[TaskHub] AND P.[InstanceID] = I.[InstanceID] AND P.[PayloadID] = I.[CustomStatusPayloadID]) AS [CustomStatusText], (SELECT TOP 1 [Text] FROM Payloads P WHERE - P.[TaskHub] = __SchemaNamePlaceholder__.CurrentTaskHub() AND + P.[TaskHub] = I.[TaskHub] AND P.[InstanceID] = I.[InstanceID] AND P.[PayloadID] = I.[InputPayloadID]) AS [InputText], (SELECT TOP 1 [Text] FROM Payloads P WHERE - P.[TaskHub] = __SchemaNamePlaceholder__.CurrentTaskHub() AND + P.[TaskHub] = I.[TaskHub] AND P.[InstanceID] = I.[InstanceID] AND P.[PayloadID] = I.[OutputPayloadID]) AS [OutputText], I.[ParentInstanceID] FROM Instances I - WHERE - I.[TaskHub] = __SchemaNamePlaceholder__.CurrentTaskHub() + -- like operator is the simplest way to keep indexed seek with conditional where + WHERE I.[TaskHub] LIKE __SchemaNamePlaceholder__._CurrentTaskHub('%') GO CREATE OR ALTER VIEW __SchemaNamePlaceholder__.vHistory @@ -216,12 +239,12 @@ AS H.[VisibleTime], H.[TraceContext], (SELECT TOP 1 [Text] FROM Payloads P WHERE - P.[TaskHub] = __SchemaNamePlaceholder__.CurrentTaskHub() AND + P.[TaskHub] = H.[TaskHub] AND P.[InstanceID] = H.[InstanceID] AND P.[PayloadID] = H.[DataPayloadID]) AS [Payload] FROM History H - WHERE - H.[TaskHub] = __SchemaNamePlaceholder__.CurrentTaskHub() + -- like operator is the simplest way to keep indexed seek with conditional where + WHERE H.[TaskHub] LIKE __SchemaNamePlaceholder__._CurrentTaskHub('%') GO @@ -233,10 +256,11 @@ CREATE OR ALTER PROCEDURE __SchemaNamePlaceholder__.CreateInstance @InputText varchar(MAX) = NULL, @StartTime datetime2 = NULL, @DedupeStatuses varchar(MAX) = 'Pending,Running', - @TraceContext varchar(800) = NULL + @TraceContext varchar(800) = NULL, + @TaskHubName varchar(150) = NULL AS BEGIN - DECLARE @TaskHub varchar(50) = __SchemaNamePlaceholder__.CurrentTaskHub() + DECLARE @TaskHub varchar(50) = __SchemaNamePlaceholder__._CurrentTaskHub(@TaskHubName) DECLARE @EventType varchar(30) = 'ExecutionStarted' DECLARE @RuntimeStatus varchar(30) = 'Pending' @@ -267,7 +291,7 @@ BEGIN -- Purge the existing instance data so that it can be overwritten DECLARE @instancesToPurge InstanceIDs INSERT INTO @instancesToPurge VALUES (@InstanceID) - EXEC __SchemaNamePlaceholder__.PurgeInstanceStateByID @instancesToPurge + EXEC __SchemaNamePlaceholder__.PurgeInstanceStateByID @instancesToPurge, @TaskHubName END COMMIT TRANSACTION @@ -341,10 +365,11 @@ GO CREATE OR ALTER PROCEDURE __SchemaNamePlaceholder__.GetInstanceHistory @InstanceID varchar(100), - @GetInputsAndOutputs bit = 0 + @GetInputsAndOutputs bit = 0, + @TaskHubName varchar(150) = NULL AS BEGIN - DECLARE @TaskHub varchar(50) = __SchemaNamePlaceholder__.CurrentTaskHub() + DECLARE @TaskHub varchar(50) = __SchemaNamePlaceholder__._CurrentTaskHub(@TaskHubName) DECLARE @ParentInstanceID varchar(100) DECLARE @Version varchar(100) @@ -387,12 +412,13 @@ CREATE OR ALTER PROCEDURE __SchemaNamePlaceholder__.RaiseEvent @Name varchar(300), @InstanceID varchar(100) = NULL, @PayloadText varchar(MAX) = NULL, - @DeliveryTime datetime2 = NULL + @DeliveryTime datetime2 = NULL, + @TaskHubName varchar(150) = NULL AS BEGIN BEGIN TRANSACTION - DECLARE @TaskHub varchar(50) = __SchemaNamePlaceholder__.CurrentTaskHub() + DECLARE @TaskHub varchar(50) = __SchemaNamePlaceholder__._CurrentTaskHub(@TaskHubName) -- External event messages must target new instances or they must use -- the "auto start" instance ID format of @orchestrationname@identifier. @@ -453,7 +479,8 @@ GO CREATE OR ALTER PROCEDURE __SchemaNamePlaceholder__.TerminateInstance @InstanceID varchar(100), - @Reason varchar(max) = NULL + @Reason varchar(max) = NULL, + @TaskHubName varchar(150) = NULL AS BEGIN BEGIN TRANSACTION @@ -463,7 +490,7 @@ BEGIN -- order across all stored procedures that execute within a transaction. -- Table order for this sproc: Instances --> (NewEvents --> Payloads --> NewEvents) - DECLARE @TaskHub varchar(50) = __SchemaNamePlaceholder__.CurrentTaskHub() + DECLARE @TaskHub varchar(50) = __SchemaNamePlaceholder__._CurrentTaskHub(@TaskHubName) DECLARE @existingStatus varchar(30) = ( SELECT TOP 1 existing.[RuntimeStatus] @@ -511,10 +538,11 @@ GO CREATE OR ALTER PROCEDURE __SchemaNamePlaceholder__.PurgeInstanceStateByID - @InstanceIDs InstanceIDs READONLY + @InstanceIDs InstanceIDs READONLY, + @TaskHubName varchar(150) = NULL AS BEGIN - DECLARE @TaskHub varchar(50) = __SchemaNamePlaceholder__.CurrentTaskHub() + DECLARE @TaskHub varchar(50) = __SchemaNamePlaceholder__._CurrentTaskHub(@TaskHubName) BEGIN TRANSACTION @@ -535,10 +563,11 @@ GO CREATE OR ALTER PROCEDURE __SchemaNamePlaceholder__.PurgeInstanceStateByTime @ThresholdTime datetime2, - @FilterType tinyint = 0 + @FilterType tinyint = 0, + @TaskHubName varchar(150) = NULL AS BEGIN - DECLARE @TaskHub varchar(50) = __SchemaNamePlaceholder__.CurrentTaskHub() + DECLARE @TaskHub varchar(50) = __SchemaNamePlaceholder__._CurrentTaskHub(@TaskHubName) DECLARE @instanceIDs InstanceIDs @@ -563,7 +592,7 @@ BEGIN END DECLARE @deletedInstances int - EXECUTE @deletedInstances = __SchemaNamePlaceholder__.PurgeInstanceStateByID @instanceIDs + EXECUTE @deletedInstances = __SchemaNamePlaceholder__.PurgeInstanceStateByID @instanceIDs, @TaskHubName RETURN @deletedInstances END GO @@ -596,7 +625,8 @@ GO CREATE OR ALTER PROCEDURE __SchemaNamePlaceholder__._LockNextOrchestration @BatchSize int, @LockedBy varchar(100), - @LockExpiration datetime2 + @LockExpiration datetime2, + @TaskHubName varchar(150) = NULL AS BEGIN DECLARE @now datetime2 = SYSUTCDATETIME() @@ -604,7 +634,7 @@ BEGIN DECLARE @parentInstanceID varchar(100) DECLARE @version varchar(100) DECLARE @runtimeStatus varchar(30) - DECLARE @TaskHub varchar(50) = __SchemaNamePlaceholder__.CurrentTaskHub() + DECLARE @TaskHub varchar(50) = __SchemaNamePlaceholder__._CurrentTaskHub(@TaskHubName) BEGIN TRANSACTION @@ -715,12 +745,13 @@ CREATE OR ALTER PROCEDURE __SchemaNamePlaceholder__._CheckpointOrchestration @DeletedEvents MessageIDs READONLY, @NewHistoryEvents HistoryEvents READONLY, @NewOrchestrationEvents OrchestrationEvents READONLY, - @NewTaskEvents TaskEvents READONLY + @NewTaskEvents TaskEvents READONLY, + @TaskHubName varchar(150) = NULL AS BEGIN BEGIN TRANSACTION - DECLARE @TaskHub varchar(50) = __SchemaNamePlaceholder__.CurrentTaskHub() + DECLARE @TaskHub varchar(50) = __SchemaNamePlaceholder__._CurrentTaskHub(@TaskHubName) DECLARE @InputPayloadID uniqueidentifier DECLARE @CustomStatusPayloadID uniqueidentifier @@ -1011,10 +1042,11 @@ GO CREATE OR ALTER PROCEDURE __SchemaNamePlaceholder__._DiscardEventsAndUnlockInstance @InstanceID varchar(100), - @DeletedEvents MessageIDs READONLY + @DeletedEvents MessageIDs READONLY, + @TaskHubName varchar(150) = NULL AS BEGIN - DECLARE @taskHub varchar(50) = __SchemaNamePlaceholder__.CurrentTaskHub() + DECLARE @taskHub varchar(50) = __SchemaNamePlaceholder__._CurrentTaskHub(@TaskHubName) -- We return the list of deleted messages so that the caller can issue a -- warning about missing messages @@ -1034,7 +1066,8 @@ GO CREATE OR ALTER PROCEDURE __SchemaNamePlaceholder__._AddOrchestrationEvents - @NewOrchestrationEvents OrchestrationEvents READONLY + @NewOrchestrationEvents OrchestrationEvents READONLY, + @TaskHubName varchar(150) = NULL AS BEGIN BEGIN TRANSACTION @@ -1044,7 +1077,7 @@ BEGIN -- order across all stored procedures that execute within a transaction. -- Table order for this sproc: Payloads --> NewEvents - DECLARE @TaskHub varchar(50) = __SchemaNamePlaceholder__.CurrentTaskHub() + DECLARE @TaskHub varchar(50) = __SchemaNamePlaceholder__._CurrentTaskHub(@TaskHubName) -- External event messages can create new instances -- NOTE: There is a chance this could result in deadlocks if two @@ -1122,10 +1155,11 @@ CREATE OR ALTER PROCEDURE __SchemaNamePlaceholder__.QuerySingleOrchestration @InstanceID varchar(100), @ExecutionID varchar(50) = NULL, @FetchInput bit = 1, - @FetchOutput bit = 1 + @FetchOutput bit = 1, + @TaskHubName varchar(150) = NULL AS BEGIN - DECLARE @TaskHub varchar(50) = __SchemaNamePlaceholder__.CurrentTaskHub() + DECLARE @TaskHub varchar(50) = __SchemaNamePlaceholder__._CurrentTaskHub(@TaskHubName) SELECT TOP 1 I.[InstanceID], @@ -1168,10 +1202,11 @@ CREATE OR ALTER PROCEDURE __SchemaNamePlaceholder__._QueryManyOrchestrations @CreatedTimeTo datetime2 = NULL, @RuntimeStatusFilter varchar(200) = NULL, @InstanceIDPrefix varchar(100) = NULL, - @ExcludeSubOrchestrations bit = 0 + @ExcludeSubOrchestrations bit = 0, + @TaskHubName varchar(150) = NULL AS BEGIN - DECLARE @TaskHub varchar(50) = __SchemaNamePlaceholder__.CurrentTaskHub() + DECLARE @TaskHub varchar(50) = __SchemaNamePlaceholder__._CurrentTaskHub(@TaskHubName) SELECT I.[InstanceID], @@ -1213,10 +1248,11 @@ GO CREATE OR ALTER PROCEDURE __SchemaNamePlaceholder__._LockNextTask @LockedBy varchar(100), - @LockExpiration datetime2 + @LockExpiration datetime2, + @TaskHubName varchar(150) = NULL AS BEGIN - DECLARE @TaskHub varchar(50) = __SchemaNamePlaceholder__.CurrentTaskHub() + DECLARE @TaskHub varchar(50) = __SchemaNamePlaceholder__._CurrentTaskHub(@TaskHubName) DECLARE @now datetime2 = SYSUTCDATETIME() DECLARE @SequenceNumber bigint @@ -1270,10 +1306,11 @@ GO CREATE OR ALTER PROCEDURE __SchemaNamePlaceholder__._RenewOrchestrationLocks @InstanceID varchar(100), - @LockExpiration datetime2 + @LockExpiration datetime2, + @TaskHubName varchar(150) = NULL AS BEGIN - DECLARE @TaskHub varchar(50) = __SchemaNamePlaceholder__.CurrentTaskHub() + DECLARE @TaskHub varchar(50) = __SchemaNamePlaceholder__._CurrentTaskHub(@TaskHubName) UPDATE Instances SET [LockExpiration] = @LockExpiration @@ -1284,10 +1321,11 @@ GO CREATE OR ALTER PROCEDURE __SchemaNamePlaceholder__._RenewTaskLocks @RenewingTasks MessageIDs READONLY, - @LockExpiration datetime2 + @LockExpiration datetime2, + @TaskHubName varchar(150) = NULL AS BEGIN - DECLARE @TaskHub varchar(50) = __SchemaNamePlaceholder__.CurrentTaskHub() + DECLARE @TaskHub varchar(50) = __SchemaNamePlaceholder__._CurrentTaskHub(@TaskHubName) UPDATE N SET [LockExpiration] = @LockExpiration @@ -1300,10 +1338,11 @@ GO CREATE OR ALTER PROCEDURE __SchemaNamePlaceholder__._CompleteTasks @CompletedTasks MessageIDs READONLY, - @Results TaskEvents READONLY + @Results TaskEvents READONLY, + @TaskHubName varchar(150) = NULL AS BEGIN - DECLARE @TaskHub varchar(50) = __SchemaNamePlaceholder__.CurrentTaskHub() + DECLARE @TaskHub varchar(50) = __SchemaNamePlaceholder__._CurrentTaskHub(@TaskHubName) BEGIN TRANSACTION @@ -1397,12 +1436,13 @@ GO CREATE OR ALTER PROCEDURE __SchemaNamePlaceholder__._RewindInstance @InstanceID varchar(100), - @Reason varchar(max) = NULL + @Reason varchar(max) = NULL, + @TaskHubName varchar(150) = NULL AS BEGIN BEGIN TRANSACTION - EXEC __SchemaNamePlaceholder__._RewindInstanceRecursive @InstanceID, @Reason + EXEC __SchemaNamePlaceholder__._RewindInstanceRecursive @InstanceID, @Reason, @TaskHubName COMMIT TRANSACTION END @@ -1411,10 +1451,11 @@ GO CREATE OR ALTER PROCEDURE __SchemaNamePlaceholder__._RewindInstanceRecursive @InstanceID varchar(100), - @Reason varchar(max) = NULL + @Reason varchar(max) = NULL, + @TaskHubName varchar(150) = NULL AS BEGIN - DECLARE @TaskHub varchar(50) = __SchemaNamePlaceholder__.CurrentTaskHub() + DECLARE @TaskHub varchar(50) = __SchemaNamePlaceholder__._CurrentTaskHub(@TaskHubName) -- *** IMPORTANT *** -- To prevent deadlocks, it is important to maintain consistent table access @@ -1516,7 +1557,7 @@ BEGIN WHILE @@FETCH_STATUS = 0 BEGIN -- Call rewind recursively on the failing suborchestrations - EXECUTE __SchemaNamePlaceholder__._RewindInstanceRecursive @subOrchestrationInstanceID, @Reason + EXECUTE __SchemaNamePlaceholder__._RewindInstanceRecursive @subOrchestrationInstanceID, @Reason, @TaskHubName FETCH NEXT FROM subOrchestrationCursor INTO @subOrchestrationInstanceID END CLOSE subOrchestrationCursor diff --git a/src/DurableTask.SqlServer/Scripts/permissions.sql b/src/DurableTask.SqlServer/Scripts/permissions.sql index 1566cd9..8dae446 100644 --- a/src/DurableTask.SqlServer/Scripts/permissions.sql +++ b/src/DurableTask.SqlServer/Scripts/permissions.sql @@ -14,8 +14,11 @@ END -- database user can access data created by another database user. -- Functions +GRANT EXECUTE ON OBJECT::__SchemaNamePlaceholder__._GetScaleMetric TO __SchemaNamePlaceholder___runtime GRANT EXECUTE ON OBJECT::__SchemaNamePlaceholder__.GetScaleMetric TO __SchemaNamePlaceholder___runtime +GRANT EXECUTE ON OBJECT::__SchemaNamePlaceholder__._GetScaleRecommendation TO __SchemaNamePlaceholder___runtime GRANT EXECUTE ON OBJECT::__SchemaNamePlaceholder__.GetScaleRecommendation TO __SchemaNamePlaceholder___runtime +GRANT EXECUTE ON OBJECT::__SchemaNamePlaceholder__._CurrentTaskHub TO __SchemaNamePlaceholder___runtime GRANT EXECUTE ON OBJECT::__SchemaNamePlaceholder__.CurrentTaskHub TO __SchemaNamePlaceholder___runtime -- Public sprocs diff --git a/src/DurableTask.SqlServer/SqlDbManager.cs b/src/DurableTask.SqlServer/SqlDbManager.cs index f3b8a88..1bd877f 100644 --- a/src/DurableTask.SqlServer/SqlDbManager.cs +++ b/src/DurableTask.SqlServer/SqlDbManager.cs @@ -117,6 +117,17 @@ public async Task CreateOrUpgradeSchemaAsync(bool recreateIfExists) await SqlUtils.ExecuteNonQueryAsync(command, this.traceHelper); } + // Update task hub naming mode + using (SqlCommand command = dbLock.CreateCommand()) + { + command.CommandText = $"{this.settings.SchemaName}.SetGlobalSetting"; + command.CommandType = CommandType.StoredProcedure; + command.Parameters.Add("@Name", SqlDbType.VarChar, 300).Value = "TaskHubMode"; + command.Parameters.Add("@Value", SqlDbType.Variant).Value = this.settings.CreateMultitenantTaskHub ? 1 : 0; + + await SqlUtils.ExecuteNonQueryAsync(command, this.traceHelper); + } + await dbLock.CommitAsync(); } diff --git a/src/DurableTask.SqlServer/SqlOrchestrationService.cs b/src/DurableTask.SqlServer/SqlOrchestrationService.cs index 47f56a2..ea948f6 100644 --- a/src/DurableTask.SqlServer/SqlOrchestrationService.cs +++ b/src/DurableTask.SqlServer/SqlOrchestrationService.cs @@ -31,6 +31,7 @@ public class SqlOrchestrationService : OrchestrationServiceBase readonly SqlDbManager dbManager; readonly string lockedByValue; readonly string userId; + string taskHubNameValue; public SqlOrchestrationService(SqlOrchestrationServiceSettings? settings) { @@ -47,6 +48,7 @@ public SqlOrchestrationService(SqlOrchestrationServiceSettings? settings) this.dbManager = new SqlDbManager(this.settings, this.traceHelper); this.lockedByValue = $"{this.settings.AppName},{Process.GetCurrentProcess().Id}"; this.userId = new SqlConnectionStringBuilder(this.settings.TaskHubConnectionString).UserID ?? string.Empty; + this.taskHubNameValue = settings?.TaskHubName ?? SqlOrchestrationServiceSettings.DefaultTaskHubName; } public override int MaxConcurrentTaskOrchestrationWorkItems => this.settings.MaxActiveOrchestrations; @@ -88,14 +90,46 @@ async Task GetAndOpenConnectionAsync(CancellationToken cancelToke return connection; } - SqlCommand GetSprocCommand(SqlConnection connection, string sprocName) + SqlCommand GetTaskHubSprocCommand(SqlConnection connection, string sprocName) { - SqlCommand command = connection.CreateCommand(); - command.CommandType = CommandType.StoredProcedure; - command.CommandText = sprocName; + SqlCommand command = connection.GetSprocCommand(string.Concat(this.settings.SchemaName, ".", sprocName)); + this.AddTaskHubNameParameter(command); return command; } + SqlCommand GetTaskHubFuncCommand(SqlConnection connection, string funcName, SqlDbType retType, int retSize = 0) + { + SqlCommand command = connection.GetFuncCommand(string.Concat(this.settings.SchemaName, ".", funcName), retType, retSize); + this.AddTaskHubNameParameter(command); + return command; + } + + void AddTaskHubNameParameter(SqlCommand command) + => command.AddTaskHubNameParameter(this.taskHubNameValue); + + /// + /// Cache value of task hub name parameter in the TaskHubMode=0 + /// + /// + /// CurrentTaskHub T-SQL function compresses task hub value to fit column + /// of size 50. In TaskHubMode=0 one can avoid this cost by caching result + /// of the transformation. Caching makes more sense for worker, as it will + /// typically issue more calls. In TaskHubMode=1 parameter name is ignored. + /// + async Task CacheTaskHubNameValue() + { + if (this.taskHubNameValue.Length > 50) + { + using SqlConnection connection = await this.GetAndOpenConnectionAsync(this.ShutdownToken); + using SqlCommand command = this.GetTaskHubFuncCommand(connection, "_CurrentTaskHub", SqlDbType.VarChar, 50); + var taskHubName = await command.ExecuteFuncAsync(this.ShutdownToken); + // Logic in t-sql function will preserve first 16 characters. + // Also, value is never null from executing db function + if (0 == string.Compare(taskHubName, 0, this.taskHubNameValue, 0, 16, StringComparison.OrdinalIgnoreCase)) + this.taskHubNameValue = taskHubName; + } + } + public override Task CreateAsync(bool recreateInstanceStore) => this.dbManager.CreateOrUpgradeSchemaAsync(recreateInstanceStore); @@ -112,6 +146,12 @@ public override Task DeleteAsync(bool deleteInstanceStore) return this.dbManager.DeleteSchemaAsync(); } + public override async Task StartAsync() + { + await base.StartAsync(); + await this.CacheTaskHubNameValue(); + } + public override async Task LockNextTaskOrchestrationWorkItemAsync( TimeSpan receiveTimeout, CancellationToken cancellationToken) @@ -121,7 +161,7 @@ public override Task DeleteAsync(bool deleteInstanceStore) do { using SqlConnection connection = await this.GetAndOpenConnectionAsync(cancellationToken); - using SqlCommand command = this.GetSprocCommand(connection, $"{this.settings.SchemaName}._LockNextOrchestration"); + using SqlCommand command = this.GetTaskHubSprocCommand(connection, "_LockNextOrchestration"); int batchSize = this.settings.WorkItemBatchSize; DateTime lockExpiration = DateTime.UtcNow.Add(this.settings.WorkItemLockTimeout); @@ -233,9 +273,7 @@ public override Task DeleteAsync(bool deleteInstanceStore) reader.Close(); // Delete the events and release the orchestration instance lock - using SqlCommand discardCommand = this.GetSprocCommand( - connection, - $"{this.settings.SchemaName}._DiscardEventsAndUnlockInstance"); + using SqlCommand discardCommand = this.GetTaskHubSprocCommand(connection, "_DiscardEventsAndUnlockInstance"); discardCommand.Parameters.Add("@InstanceID", SqlDbType.VarChar, 100).Value = instanceId; discardCommand.Parameters.AddMessageIdParameter("@DeletedEvents", messages, this.settings.SchemaName); try @@ -316,7 +354,7 @@ await SqlUtils.ExecuteNonQueryAsync( public override async Task RenewTaskOrchestrationWorkItemLockAsync(TaskOrchestrationWorkItem workItem) { using SqlConnection connection = await this.GetAndOpenConnectionAsync(); - using SqlCommand command = this.GetSprocCommand(connection, $"{this.settings.SchemaName}._RenewOrchestrationLocks"); + using SqlCommand command = this.GetTaskHubSprocCommand(connection, "_RenewOrchestrationLocks"); DateTime lockExpiration = DateTime.UtcNow.Add(this.settings.WorkItemLockTimeout); @@ -343,7 +381,7 @@ public override async Task CompleteTaskOrchestrationWorkItemAsync( Stopwatch sw = Stopwatch.StartNew(); using SqlConnection connection = await this.GetAndOpenConnectionAsync(); - using SqlCommand command = this.GetSprocCommand(connection, $"{this.settings.SchemaName}._CheckpointOrchestration"); + using SqlCommand command = this.GetTaskHubSprocCommand(connection, "_CheckpointOrchestration"); OrchestrationInstance instance = newRuntimeState.OrchestrationInstance!; IList newEvents = newRuntimeState.NewEvents; @@ -417,7 +455,7 @@ public override async Task CompleteTaskOrchestrationWorkItemAsync( while (!shutdownCancellationToken.IsCancellationRequested) { using SqlConnection connection = await this.GetAndOpenConnectionAsync(shutdownCancellationToken); - using SqlCommand command = this.GetSprocCommand(connection, $"{this.settings.SchemaName}._LockNextTask"); + using SqlCommand command = this.GetTaskHubSprocCommand(connection, "_LockNextTask"); DateTime lockExpiration = DateTime.UtcNow.Add(this.settings.WorkItemLockTimeout); @@ -457,7 +495,7 @@ public override async Task CompleteTaskOrchestrationWorkItemAsync( public override async Task RenewTaskActivityWorkItemLockAsync(TaskActivityWorkItem workItem) { using SqlConnection connection = await this.GetAndOpenConnectionAsync(); - using SqlCommand command = this.GetSprocCommand(connection, $"{this.settings.SchemaName}._RenewTaskLocks"); + using SqlCommand command = this.GetTaskHubSprocCommand(connection, "_RenewTaskLocks"); DateTime lockExpiration = DateTime.UtcNow.Add(this.settings.WorkItemLockTimeout); @@ -474,7 +512,7 @@ public override async Task RenewTaskActivityWorkItemLockAs public override async Task CompleteTaskActivityWorkItemAsync(TaskActivityWorkItem workItem, TaskMessage responseMessage) { using SqlConnection connection = await this.GetAndOpenConnectionAsync(); - using SqlCommand command = this.GetSprocCommand(connection, $"{this.settings.SchemaName}._CompleteTasks"); + using SqlCommand command = this.GetTaskHubSprocCommand(connection, "_CompleteTasks"); command.Parameters.AddMessageIdParameter("@CompletedTasks", workItem.TaskMessage, this.settings.SchemaName); command.Parameters.AddTaskEventsParameter("@Results", responseMessage, this.settings.SchemaName); @@ -504,7 +542,7 @@ public override async Task CompleteTaskActivityWorkItemAsync(TaskActivityWorkIte public override async Task CreateTaskOrchestrationAsync(TaskMessage creationMessage, OrchestrationStatus[] dedupeStatuses) { using SqlConnection connection = await this.GetAndOpenConnectionAsync(); - using SqlCommand command = this.GetSprocCommand(connection, $"{this.settings.SchemaName}.CreateInstance"); + using SqlCommand command = this.GetTaskHubSprocCommand(connection, "CreateInstance"); ExecutionStartedEvent startEvent = (ExecutionStartedEvent)creationMessage.Event; OrchestrationInstance instance = startEvent.OrchestrationInstance; @@ -535,7 +573,7 @@ public override async Task CreateTaskOrchestrationAsync(TaskMessage creationMess public override async Task SendTaskOrchestrationMessageAsync(TaskMessage message) { using SqlConnection connection = await this.GetAndOpenConnectionAsync(); - using SqlCommand command = this.GetSprocCommand(connection, $"{this.settings.SchemaName}._AddOrchestrationEvents"); + using SqlCommand command = this.GetTaskHubSprocCommand(connection, "_AddOrchestrationEvents"); command.Parameters.AddOrchestrationEventsParameter("@NewOrchestrationEvents", message, this.settings.SchemaName); @@ -584,7 +622,7 @@ public override async Task WaitForOrchestrationAsync( CancellationToken cancellationToken) { using SqlConnection connection = await this.GetAndOpenConnectionAsync(cancellationToken); - using SqlCommand command = this.GetSprocCommand(connection, $"{this.settings.SchemaName}.QuerySingleOrchestration"); + using SqlCommand command = this.GetTaskHubSprocCommand(connection, "QuerySingleOrchestration"); command.Parameters.Add("@InstanceID", SqlDbType.VarChar, size: 100).Value = instanceId; command.Parameters.Add("@ExecutionID", SqlDbType.VarChar, size: 50).Value = executionId; @@ -608,7 +646,7 @@ public override async Task WaitForOrchestrationAsync( public override async Task GetOrchestrationHistoryAsync(string instanceId, string? executionIdFilter) { using SqlConnection connection = await this.GetAndOpenConnectionAsync(); - using SqlCommand command = this.GetSprocCommand(connection, $"{this.settings.SchemaName}.GetInstanceHistory"); + using SqlCommand command = this.GetTaskHubSprocCommand(connection, "GetInstanceHistory"); command.Parameters.Add("@InstanceID", SqlDbType.VarChar, size: 100).Value = instanceId; command.Parameters.Add("@GetInputsAndOutputs", SqlDbType.Bit).Value = true; // There's no clean way for the caller to specify this currently @@ -653,7 +691,7 @@ static List ReadHistoryEvents( public override async Task ForceTerminateTaskOrchestrationAsync(string instanceId, string? reason) { using SqlConnection connection = await this.GetAndOpenConnectionAsync(); - using SqlCommand command = this.GetSprocCommand(connection, $"{this.settings.SchemaName}.TerminateInstance"); + using SqlCommand command = this.GetTaskHubSprocCommand(connection, "TerminateInstance"); command.Parameters.Add("@InstanceID", SqlDbType.VarChar, size: 100).Value = instanceId; command.Parameters.Add("@Reason", SqlDbType.VarChar).Value = reason; @@ -669,7 +707,7 @@ public async Task PurgeOrchestrationHistoryAsync(IEnumerable instan } using SqlConnection connection = await this.GetAndOpenConnectionAsync(); - using SqlCommand command = this.GetSprocCommand(connection, $"{this.settings.SchemaName}.PurgeInstanceStateByID"); + using SqlCommand command = this.GetTaskHubSprocCommand(connection, "PurgeInstanceStateByID"); SqlParameter instancesDeletedReturnValue = command.Parameters.Add("@InstancesDeleted", SqlDbType.Int); instancesDeletedReturnValue.Direction = ParameterDirection.ReturnValue; @@ -695,7 +733,7 @@ public override async Task PurgeOrchestrationHistoryAsync( OrchestrationStateTimeRangeFilterType timeRangeFilterType) { using SqlConnection connection = await this.GetAndOpenConnectionAsync(); - using SqlCommand command = this.GetSprocCommand(connection, $"{this.settings.SchemaName}.PurgeInstanceStateByTime"); + using SqlCommand command = this.GetTaskHubSprocCommand(connection, "PurgeInstanceStateByTime"); command.Parameters.Add("@ThresholdTime", SqlDbType.DateTime2).Value = maxThresholdDateTimeUtc; command.Parameters.Add("@FilterType", SqlDbType.TinyInt).Value = (int)timeRangeFilterType; @@ -800,7 +838,7 @@ public async Task> GetManyOrchestrations DateTime createdTimeTo = query.CreatedTimeTo.ToSqlUtcDateTime(DateTime.MaxValue); using SqlConnection connection = await this.GetAndOpenConnectionAsync(cancellationToken); - using SqlCommand command = this.GetSprocCommand(connection, $"{this.settings.SchemaName}._QueryManyOrchestrations"); + using SqlCommand command = this.GetTaskHubSprocCommand(connection, "_QueryManyOrchestrations"); command.Parameters.Add("@PageSize", SqlDbType.SmallInt).Value = query.PageSize; command.Parameters.Add("@PageNumber", SqlDbType.Int).Value = query.PageNumber; @@ -841,7 +879,7 @@ public async Task> GetManyOrchestrations public async Task RewindTaskOrchestrationAsync(string instanceId, string reason) { using SqlConnection connection = await this.GetAndOpenConnectionAsync(); - using SqlCommand command = this.GetSprocCommand(connection, $"{this.settings.SchemaName}._RewindInstance"); + using SqlCommand command = this.GetTaskHubSprocCommand(connection, "_RewindInstance"); command.Parameters.Add("@InstanceID", SqlDbType.VarChar, size: 100).Value = instanceId; command.Parameters.Add("@Reason", SqlDbType.VarChar).Value = reason; @@ -854,7 +892,7 @@ public async Task RewindTaskOrchestrationAsync(string instanceId, string reason) /// /// /// - /// This method calls the dt.GetScaleRecommendation SQL function and gets back a number that represents + /// This method calls the dt._GetScaleRecommendation SQL function and gets back a number that represents /// the recommended number of worker replicas that should be allocated to handle the current event backlog. /// The SQL function takes the values of and /// as inputs. @@ -871,12 +909,10 @@ public async Task RewindTaskOrchestrationAsync(string instanceId, string reason) public async Task GetRecommendedReplicaCountAsync(int? currentReplicaCount = null, CancellationToken cancellationToken = default) { using SqlConnection connection = await this.GetAndOpenConnectionAsync(cancellationToken); - using SqlCommand command = connection.CreateCommand(); - command.CommandText = $"SELECT {this.settings.SchemaName}.GetScaleRecommendation(@MaxConcurrentOrchestrations, @MaxConcurrentActivities)"; - command.Parameters.Add("@MaxConcurrentOrchestrations", SqlDbType.Int).Value = this.MaxConcurrentTaskOrchestrationWorkItems; - command.Parameters.Add("@MaxConcurrentActivities", SqlDbType.Int).Value = this.MaxConcurrentTaskActivityWorkItems; - - int recommendedReplicaCount = (int)await command.ExecuteScalarAsync(cancellationToken); + using SqlCommand command = this.GetTaskHubFuncCommand(connection, "_GetScaleRecommendation", SqlDbType.Int); + command.Parameters.Add("@MaxOrchestrationsPerWorker", SqlDbType.Int).Value = this.MaxConcurrentTaskOrchestrationWorkItems; + command.Parameters.Add("@MaxActivitiesPerWorker", SqlDbType.Int).Value = this.MaxConcurrentTaskActivityWorkItems; + int recommendedReplicaCount = await command.ExecuteFuncAsync(cancellationToken); if (currentReplicaCount != null && currentReplicaCount != recommendedReplicaCount) { this.traceHelper.ReplicaCountChangeRecommended(currentReplicaCount.Value, recommendedReplicaCount); diff --git a/src/DurableTask.SqlServer/SqlOrchestrationServiceSettings.cs b/src/DurableTask.SqlServer/SqlOrchestrationServiceSettings.cs index 048a723..a7fd1c9 100644 --- a/src/DurableTask.SqlServer/SqlOrchestrationServiceSettings.cs +++ b/src/DurableTask.SqlServer/SqlOrchestrationServiceSettings.cs @@ -14,34 +14,49 @@ namespace DurableTask.SqlServer /// public class SqlOrchestrationServiceSettings { + /// + /// Default value for property + /// + public static readonly string DefaultTaskHubName = "default"; + + /// + /// Default value for property + /// + public static readonly string DefaultSchemaName = "dt"; + /// /// Initializes a new instance of the class. /// /// The connection string for connecting to the database. /// Optional. The name of the task hub. If not specified, a default name will be used. /// Optional. The name of the schema. If not specified, the default 'dt' value will be used. - public SqlOrchestrationServiceSettings(string connectionString, string? taskHubName = null, string? schemaName = null) + /// Optional. When true, task hub name is added to connection string. + /// Default value is true to match behavior of connection pool before introduction of this parameter. + public SqlOrchestrationServiceSettings(string connectionString, string? taskHubName = null, string? schemaName = null, bool addTaskHubToConnectionString = true) { if (string.IsNullOrEmpty(connectionString)) { throw new ArgumentNullException(nameof(connectionString)); } - this.TaskHubName = taskHubName ?? "default"; - this.SchemaName = schemaName ?? "dt"; + this.TaskHubName = taskHubName ?? DefaultTaskHubName; + this.SchemaName = schemaName ?? DefaultSchemaName; - var builder = new SqlConnectionStringBuilder(connectionString) - { - // We use the task hub name as the application name so that - // stored procedures have easy access to this information. - ApplicationName = this.TaskHubName, - }; + var builder = new SqlConnectionStringBuilder(connectionString); if (string.IsNullOrEmpty(builder.InitialCatalog)) { throw new ArgumentException("Database or Initial Catalog must be specified in the connection string.", nameof(connectionString)); } + if (addTaskHubToConnectionString) + { + // We use the task hub name as the application name so that + // stored procedures have easy access to this information. + builder.ApplicationName = this.TaskHubName; + this.ConnectionStringHasTaskHubName = true; + } + this.DatabaseName = builder.InitialCatalog; this.TaskHubConnectionString = builder.ToString(); } @@ -71,7 +86,15 @@ public SqlOrchestrationServiceSettings(string connectionString, string? taskHubN public string SchemaName { get; } /// - /// Gets or sets the name of the app. Used for logging purposes. + /// True when connection string + /// property is set to the value of the . + /// + [JsonProperty("connectionStringHasTaskHubName ")] + public bool ConnectionStringHasTaskHubName { get; } + + /// + /// Gets or sets the name of the app. Used as prefix of the lock owner, + /// and need to be unique across machines that connect to the hub. /// [JsonProperty("appName")] public string AppName { get; set; } = Environment.MachineName; @@ -150,6 +173,14 @@ public SqlOrchestrationServiceSettings(string connectionString, string? taskHubN [JsonProperty("createDatabaseIfNotExists")] public bool CreateDatabaseIfNotExists { get; set; } + /// + /// Gets or sets a flag indicating wether task hub is created for multitenant naming + /// (derived from sql server user name) or for application specified naming. + /// + /// + [JsonProperty("createMultitenantTaskHub ")] + public bool CreateMultitenantTaskHub { get; set; } = true; + /// /// Gets a SQL connection string associated with the configured task hub. /// diff --git a/src/DurableTask.SqlServer/SqlUtils.cs b/src/DurableTask.SqlServer/SqlUtils.cs index a06ed2b..614fb95 100644 --- a/src/DurableTask.SqlServer/SqlUtils.cs +++ b/src/DurableTask.SqlServer/SqlUtils.cs @@ -512,6 +512,15 @@ static IEnumerable GetInstanceIdRecords(IEnumerable insta return param; } + internal static void AddTaskHubNameParameter(this SqlCommand command, string? value) + { + command.Parameters.Add(new SqlParameter("@TaskHubName", SqlDbType.VarChar, 150) + { + IsNullable = true, + Value = value ?? (object)DBNull.Value + }); + } + public static Task ExecuteReaderAsync( DbCommand command, LogHelper traceHelper, @@ -577,6 +586,45 @@ static async Task ExecuteSprocAndTraceAsync( } } + /// + /// Allocate command to execute stored procedure + /// + /// Existing database connection + /// Stored procedure name with schema + public static SqlCommand GetSprocCommand(this SqlConnection connection, string sprocName) + { + SqlCommand command = connection.CreateCommand(); + command.CommandType = CommandType.StoredProcedure; + command.CommandText = sprocName; + return command; + } + + /// + /// Returns command with out-of-the box return value parameter to be used + /// with + /// + /// Existing database connection + /// Function name with schema prefix + /// Return type of the function + /// Optional return type size + public static SqlCommand GetFuncCommand(this SqlConnection connection, string funcName, SqlDbType retType, int retSize = 0) + { + SqlCommand command = connection.GetSprocCommand(funcName); + command.Parameters.Add(new SqlParameter("@RETURN_VALUE", retType, retSize) + { + Direction = ParameterDirection.ReturnValue, + IsNullable = true + }); + return command; + } + + public static async Task ExecuteFuncAsync(this DbCommand command, CancellationToken cancellationToken) + { + await command.ExecuteNonQueryAsync(cancellationToken); + object value = command.Parameters[0].Value; + return value == DBNull.Value ? default! : (T) value; + } + public static bool IsUniqueKeyViolation(SqlException exception) { return exception.Errors.Cast().Any(e => e.Class == 14 && (e.Number == 2601 || e.Number == 2627)); diff --git a/test/DurableTask.SqlServer.Tests/Integration/DatabaseManagement.cs b/test/DurableTask.SqlServer.Tests/Integration/DatabaseManagement.cs index fd3469f..e380f65 100644 --- a/test/DurableTask.SqlServer.Tests/Integration/DatabaseManagement.cs +++ b/test/DurableTask.SqlServer.Tests/Integration/DatabaseManagement.cs @@ -99,7 +99,8 @@ public async Task CanCreateAndDropSchema(bool isDatabaseMissing) LogAssert.ExecutedSqlScript("schema-1.2.0.sql"), LogAssert.ExecutedSqlScript("logic.sql"), LogAssert.ExecutedSqlScript("permissions.sql"), - LogAssert.SprocCompleted("dt._UpdateVersion")) + LogAssert.SprocCompleted("dt._UpdateVersion"), + LogAssert.SprocCompleted("dt.SetGlobalSetting")) .EndOfLog(); await this.ValidateDatabaseSchemaAsync(testDb); @@ -159,7 +160,8 @@ public async Task CanCreateAndDropSchemaWithCustomSchemaName(bool isDatabaseMiss LogAssert.ExecutedSqlScript("schema-1.2.0.sql"), LogAssert.ExecutedSqlScript("logic.sql"), LogAssert.ExecutedSqlScript("permissions.sql"), - LogAssert.SprocCompleted($"{schemaName}._UpdateVersion")) + LogAssert.SprocCompleted($"{schemaName}._UpdateVersion"), + LogAssert.SprocCompleted($"{schemaName}.SetGlobalSetting")) .EndOfLog(); await this.ValidateDatabaseSchemaAsync(testDb, schemaName); @@ -221,7 +223,8 @@ public async Task CanCreateAndDropMultipleSchemas(bool isDatabaseMissing) LogAssert.ExecutedSqlScript("schema-1.2.0.sql"), LogAssert.ExecutedSqlScript("logic.sql"), LogAssert.ExecutedSqlScript("permissions.sql"), - LogAssert.SprocCompleted($"{firstTestSchemaName}._UpdateVersion")) + LogAssert.SprocCompleted($"{firstTestSchemaName}._UpdateVersion"), + LogAssert.SprocCompleted($"{firstTestSchemaName}.SetGlobalSetting")) .Expect( LogAssert.CheckedDatabase()) .Expect( @@ -231,7 +234,8 @@ public async Task CanCreateAndDropMultipleSchemas(bool isDatabaseMissing) LogAssert.ExecutedSqlScript("schema-1.2.0.sql"), LogAssert.ExecutedSqlScript("logic.sql"), LogAssert.ExecutedSqlScript("permissions.sql"), - LogAssert.SprocCompleted($"{secondTestSchemaName}._UpdateVersion")) + LogAssert.SprocCompleted($"{secondTestSchemaName}._UpdateVersion"), + LogAssert.SprocCompleted($"{secondTestSchemaName}.SetGlobalSetting")) .EndOfLog(); await this.ValidateDatabaseSchemaAsync(testDb, secondTestSchemaName); @@ -314,7 +318,8 @@ public async Task CanCreateIfNotExists(bool isDatabaseMissing) LogAssert.ExecutedSqlScript("schema-1.2.0.sql"), LogAssert.ExecutedSqlScript("logic.sql"), LogAssert.ExecutedSqlScript("permissions.sql"), - LogAssert.SprocCompleted("dt._UpdateVersion")) + LogAssert.SprocCompleted("dt._UpdateVersion"), + LogAssert.SprocCompleted("dt.SetGlobalSetting")) .EndOfLog(); await this.ValidateDatabaseSchemaAsync(testDb); @@ -368,6 +373,7 @@ public async Task SchemaCreationIsSerializedAndIdempotent(bool isDatabaseMissing LogAssert.ExecutedSqlScript("logic.sql"), LogAssert.ExecutedSqlScript("permissions.sql"), LogAssert.SprocCompleted("dt._UpdateVersion"), + LogAssert.SprocCompleted("dt.SetGlobalSetting"), // 2nd LogAssert.AcquiredAppLock(), LogAssert.SprocCompleted("dt._GetVersions"), @@ -452,8 +458,11 @@ async Task ValidateDatabaseSchemaAsync(TestDatabase database, string schemaName var expectedFunctionNames = new HashSet(StringComparer.Ordinal) { + $"{schemaName}._CurrentTaskHub", $"{schemaName}.CurrentTaskHub", + $"{schemaName}._GetScaleMetric", $"{schemaName}.GetScaleMetric", + $"{schemaName}._GetScaleRecommendation", $"{schemaName}.GetScaleRecommendation", }; diff --git a/test/DurableTask.SqlServer.Tests/Utils/SharedTestHelpers.cs b/test/DurableTask.SqlServer.Tests/Utils/SharedTestHelpers.cs index 16d4f29..e6c6a5d 100644 --- a/test/DurableTask.SqlServer.Tests/Utils/SharedTestHelpers.cs +++ b/test/DurableTask.SqlServer.Tests/Utils/SharedTestHelpers.cs @@ -30,9 +30,17 @@ public static string GetTestName(ITestOutputHelper output) public static string GetDefaultConnectionString(string database = "DurableDB") { - // The default for local development on a Windows OS - string defaultConnectionString = $"Server=localhost;Database={database};Trusted_Connection=True;Encrypt=False;"; - var builder = new SqlConnectionStringBuilder(defaultConnectionString); + // Externally configured connection string for local environemnt (workaround defaults below). + // Fixme: CoreScenarios.MultiInstanceQueries require Initial Catalog=DurableDB to be set. + string environmentConnectionString = Environment.GetEnvironmentVariable("SQLDB_Connection"); + + var connectionString = !string.IsNullOrWhiteSpace(environmentConnectionString) + ? environmentConnectionString + // The default for local development on a Windows OS + : $"Server=localhost;Trusted_Connection=True;Encrypt=False;"; + + var builder = new SqlConnectionStringBuilder(connectionString); + builder.InitialCatalog = database; // The use of SA_PASSWORD is intended for use with the mssql docker container string saPassword = Environment.GetEnvironmentVariable("SA_PASSWORD");