From dad8041db503ea47ae4dbaa150e0792229380950 Mon Sep 17 00:00:00 2001 From: Warwick Schroeder Date: Thu, 22 Oct 2026 09:26:55 +0800 Subject: [PATCH 1/3] Add migration script approval tests for PostgreSQL and SQL Server --- docs/testing.md | 2 + ...pt_is_unchanged.custom_schema.approved.txt | 94 +++++ ...t_is_unchanged.default_schema.approved.txt | 88 +++++ .../MigrationScriptApprovals.cs | 39 ++ ...pt_is_unchanged.custom_schema.approved.txt | 118 ++++++ ...t_is_unchanged.default_schema.approved.txt | 117 ++++++ .../MigrationScriptApprovals.cs | 39 ++ ...pt_is_unchanged.custom_schema.approved.txt | 335 ++++++++++++++++ ...t_is_unchanged.default_schema.approved.txt | 329 ++++++++++++++++ .../MigrationScriptApprovals.cs | 38 ++ ...pt_is_unchanged.custom_schema.approved.txt | 359 ++++++++++++++++++ ...t_is_unchanged.default_schema.approved.txt | 358 +++++++++++++++++ .../MigrationScriptApprovals.cs | 38 ++ src/TestHelper/MigrationScript.cs | 15 + 14 files changed, 1969 insertions(+) create mode 100644 src/ServiceControl.Audit.Persistence.Tests.PostgreSql/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.custom_schema.approved.txt create mode 100644 src/ServiceControl.Audit.Persistence.Tests.PostgreSql/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.default_schema.approved.txt create mode 100644 src/ServiceControl.Audit.Persistence.Tests.PostgreSql/MigrationScriptApprovals.cs create mode 100644 src/ServiceControl.Audit.Persistence.Tests.SqlServer/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.custom_schema.approved.txt create mode 100644 src/ServiceControl.Audit.Persistence.Tests.SqlServer/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.default_schema.approved.txt create mode 100644 src/ServiceControl.Audit.Persistence.Tests.SqlServer/MigrationScriptApprovals.cs create mode 100644 src/ServiceControl.Persistence.Tests.PostgreSql/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.custom_schema.approved.txt create mode 100644 src/ServiceControl.Persistence.Tests.PostgreSql/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.default_schema.approved.txt create mode 100644 src/ServiceControl.Persistence.Tests.PostgreSql/MigrationScriptApprovals.cs create mode 100644 src/ServiceControl.Persistence.Tests.SqlServer/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.custom_schema.approved.txt create mode 100644 src/ServiceControl.Persistence.Tests.SqlServer/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.default_schema.approved.txt create mode 100644 src/ServiceControl.Persistence.Tests.SqlServer/MigrationScriptApprovals.cs create mode 100644 src/TestHelper/MigrationScript.cs diff --git a/docs/testing.md b/docs/testing.md index af20a9d4f9..ecfb2e4f41 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -27,6 +27,8 @@ On Windows, [PackagedRavenDBTests](../src/ServiceControlInstaller.Engine.UnitTes Persistence tests check assumptions at the persistence seam level by exercising each persister. For local setup details, see [Local testing of persistence providers](testing-persistence.md). +The SQL Server and PostgreSQL persistence test projects contain a `MigrationScriptApprovals` test. It approves the script EF Core generates from all of the persister's migrations, with and without a configured schema. A migration that creates, drops, or rebuilds an index on a large table can take a long time and block writes. When the test fails, record any index change in the upgrade guide of the next release before you approve the new script. If the script changed inside an existing migration, for example through a shared SQL constant, add a new migration instead. Databases that already ran the old migration never run it again. A package update that changes only the formatting of the script needs no upgrade guide entry. + ## Transport tests Transport tests run the transport test suite for each transport. diff --git a/src/ServiceControl.Audit.Persistence.Tests.PostgreSql/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.custom_schema.approved.txt b/src/ServiceControl.Audit.Persistence.Tests.PostgreSql/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.custom_schema.approved.txt new file mode 100644 index 0000000000..28c7abf906 --- /dev/null +++ b/src/ServiceControl.Audit.Persistence.Tests.PostgreSql/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.custom_schema.approved.txt @@ -0,0 +1,94 @@ +DO $EF$ +BEGIN + IF NOT EXISTS(SELECT 1 FROM pg_namespace WHERE nspname = 'custom_schema') THEN + CREATE SCHEMA custom_schema; + END IF; +END $EF$; +CREATE TABLE IF NOT EXISTS custom_schema."__AuditMigrationsHistory" ( + migration_id character varying(150) NOT NULL, + product_version character varying(32) NOT NULL, + CONSTRAINT pk___audit_migrations_history PRIMARY KEY (migration_id) +); + +START TRANSACTION; +CREATE TABLE custom_schema.audit_messages ( + created_on timestamp with time zone NOT NULL, + id bigint GENERATED BY DEFAULT AS IDENTITY, + unique_message_id uuid NOT NULL, + message_id text, + message_type text, + time_sent timestamp with time zone, + processed_at timestamp with time zone NOT NULL, + conversation_id character varying(450), + is_system_message boolean NOT NULL, + status integer NOT NULL, + sending_endpoint_name text, + sending_endpoint_host_id uuid, + sending_endpoint_host text, + receiving_endpoint_name character varying(450), + receiving_endpoint_host_id uuid, + receiving_endpoint_host text, + critical_time_ticks bigint, + processing_time_ticks bigint, + delivery_time_ticks bigint, + headers_json text NOT NULL, + body_text text, + body_state integer NOT NULL, + body_size integer NOT NULL, + body_content_type text, + CONSTRAINT pk_audit_messages PRIMARY KEY (created_on, id) +) PARTITION BY RANGE (created_on); + +CREATE TABLE custom_schema.failed_audit_imports ( + unique_message_id uuid NOT NULL, + failed_at timestamp with time zone NOT NULL, + message_id text, + headers_json text NOT NULL, + body bytea NOT NULL, + exception_info text, + CONSTRAINT pk_failed_audit_imports PRIMARY KEY (unique_message_id) +); + +CREATE TABLE custom_schema.saga_snapshots ( + created_on timestamp with time zone NOT NULL, + id bigint GENERATED BY DEFAULT AS IDENTITY, + saga_id uuid NOT NULL, + saga_type text, + status integer NOT NULL, + start_time timestamp with time zone NOT NULL, + finish_time timestamp with time zone NOT NULL, + processed_at timestamp with time zone NOT NULL, + endpoint text, + state_after_change text, + initiating_message_json text, + outgoing_messages_json text, + CONSTRAINT pk_saga_snapshots PRIMARY KEY (created_on, id) +) PARTITION BY RANGE (created_on); + +CREATE INDEX ix_audit_messages_conversation_id ON custom_schema.audit_messages (conversation_id); + +CREATE INDEX ix_audit_messages_processed_at ON custom_schema.audit_messages (processed_at); + +CREATE INDEX ix_audit_messages_receiving_endpoint_name_created_on ON custom_schema.audit_messages (receiving_endpoint_name, created_on); + +CREATE INDEX ix_audit_messages_time_sent ON custom_schema.audit_messages (time_sent); + +CREATE INDEX ix_audit_messages_unique_message_id ON custom_schema.audit_messages (unique_message_id); + +CREATE INDEX ix_failed_audit_imports_failed_at_unique_message_id ON custom_schema.failed_audit_imports (failed_at, unique_message_id); + +CREATE INDEX ix_saga_snapshots_saga_id_finish_time ON custom_schema.saga_snapshots (saga_id, finish_time); + +INSERT INTO custom_schema."__AuditMigrationsHistory" (migration_id, product_version) +VALUES ('20260928032741_Initial', ''); + +COMMIT; + +START TRANSACTION; +CREATE INDEX ix_audit_messages_full_text ON "custom_schema".audit_messages USING GIN (to_tsvector('simple', headers_json || ' ' || substring(COALESCE(body_text, ''), 1, 262144) || ' ' || replace(replace(COALESCE(message_type, ''), '.', ' '), '+', ' '))) + +INSERT INTO custom_schema."__AuditMigrationsHistory" (migration_id, product_version) +VALUES ('20260928032745_AddFullTextSearch', ''); + +COMMIT; + diff --git a/src/ServiceControl.Audit.Persistence.Tests.PostgreSql/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.default_schema.approved.txt b/src/ServiceControl.Audit.Persistence.Tests.PostgreSql/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.default_schema.approved.txt new file mode 100644 index 0000000000..2a40bc52fe --- /dev/null +++ b/src/ServiceControl.Audit.Persistence.Tests.PostgreSql/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.default_schema.approved.txt @@ -0,0 +1,88 @@ +CREATE TABLE IF NOT EXISTS "__AuditMigrationsHistory" ( + migration_id character varying(150) NOT NULL, + product_version character varying(32) NOT NULL, + CONSTRAINT pk___audit_migrations_history PRIMARY KEY (migration_id) +); + +START TRANSACTION; +CREATE TABLE audit_messages ( + created_on timestamp with time zone NOT NULL, + id bigint GENERATED BY DEFAULT AS IDENTITY, + unique_message_id uuid NOT NULL, + message_id text, + message_type text, + time_sent timestamp with time zone, + processed_at timestamp with time zone NOT NULL, + conversation_id character varying(450), + is_system_message boolean NOT NULL, + status integer NOT NULL, + sending_endpoint_name text, + sending_endpoint_host_id uuid, + sending_endpoint_host text, + receiving_endpoint_name character varying(450), + receiving_endpoint_host_id uuid, + receiving_endpoint_host text, + critical_time_ticks bigint, + processing_time_ticks bigint, + delivery_time_ticks bigint, + headers_json text NOT NULL, + body_text text, + body_state integer NOT NULL, + body_size integer NOT NULL, + body_content_type text, + CONSTRAINT pk_audit_messages PRIMARY KEY (created_on, id) +) PARTITION BY RANGE (created_on); + +CREATE TABLE failed_audit_imports ( + unique_message_id uuid NOT NULL, + failed_at timestamp with time zone NOT NULL, + message_id text, + headers_json text NOT NULL, + body bytea NOT NULL, + exception_info text, + CONSTRAINT pk_failed_audit_imports PRIMARY KEY (unique_message_id) +); + +CREATE TABLE saga_snapshots ( + created_on timestamp with time zone NOT NULL, + id bigint GENERATED BY DEFAULT AS IDENTITY, + saga_id uuid NOT NULL, + saga_type text, + status integer NOT NULL, + start_time timestamp with time zone NOT NULL, + finish_time timestamp with time zone NOT NULL, + processed_at timestamp with time zone NOT NULL, + endpoint text, + state_after_change text, + initiating_message_json text, + outgoing_messages_json text, + CONSTRAINT pk_saga_snapshots PRIMARY KEY (created_on, id) +) PARTITION BY RANGE (created_on); + +CREATE INDEX ix_audit_messages_conversation_id ON audit_messages (conversation_id); + +CREATE INDEX ix_audit_messages_processed_at ON audit_messages (processed_at); + +CREATE INDEX ix_audit_messages_receiving_endpoint_name_created_on ON audit_messages (receiving_endpoint_name, created_on); + +CREATE INDEX ix_audit_messages_time_sent ON audit_messages (time_sent); + +CREATE INDEX ix_audit_messages_unique_message_id ON audit_messages (unique_message_id); + +CREATE INDEX ix_failed_audit_imports_failed_at_unique_message_id ON failed_audit_imports (failed_at, unique_message_id); + +CREATE INDEX ix_saga_snapshots_saga_id_finish_time ON saga_snapshots (saga_id, finish_time); + +INSERT INTO "__AuditMigrationsHistory" (migration_id, product_version) +VALUES ('20260928032741_Initial', ''); + +COMMIT; + +START TRANSACTION; +CREATE INDEX ix_audit_messages_full_text ON audit_messages USING GIN (to_tsvector('simple', headers_json || ' ' || substring(COALESCE(body_text, ''), 1, 262144) || ' ' || replace(replace(COALESCE(message_type, ''), '.', ' '), '+', ' '))) + +INSERT INTO "__AuditMigrationsHistory" (migration_id, product_version) +VALUES ('20260928032745_AddFullTextSearch', ''); + +COMMIT; + diff --git a/src/ServiceControl.Audit.Persistence.Tests.PostgreSql/MigrationScriptApprovals.cs b/src/ServiceControl.Audit.Persistence.Tests.PostgreSql/MigrationScriptApprovals.cs new file mode 100644 index 0000000000..bc9404cea1 --- /dev/null +++ b/src/ServiceControl.Audit.Persistence.Tests.PostgreSql/MigrationScriptApprovals.cs @@ -0,0 +1,39 @@ +namespace ServiceControl.Audit.Persistence.Tests +{ + using System; + using Microsoft.EntityFrameworkCore.Infrastructure; + using Microsoft.EntityFrameworkCore.Migrations; + using Microsoft.Extensions.DependencyInjection; + using NUnit.Framework; + using Particular.Approvals; + using ServiceControl.Audit.Persistence.EFCore.Abstractions; + using ServiceControl.Audit.Persistence.EFCore.DbContexts; + using ServiceControl.Audit.Persistence.EFCore.PostgreSql; + using TestHelper; + + // A changed index can block writes on a large table for a long time. Record it in the upgrade guide and never edit an existing migration, see docs/testing.md. + [TestFixture] + class MigrationScriptApprovals + { + [TestCase(null)] + [TestCase("custom_schema")] + public void Verify_migration_script_is_unchanged(string schema) + { + var settings = new PersistenceSettings(TimeSpan.FromDays(1), true, 100000); + settings.PersisterSpecificSettings[EFPersistenceConfigurationBase.ConnectionStringKey] = "Host=unused"; + if (schema is not null) + { + settings.PersisterSpecificSettings[EFPersistenceConfigurationBase.SchemaKey] = schema; + } + + var services = new ServiceCollection(); + new PostgreSqlPersistenceConfiguration().Create(settings).AddInstaller(services); + + using var serviceProvider = services.BuildServiceProvider(); + using var scope = serviceProvider.CreateScope(); + var script = scope.ServiceProvider.GetRequiredService().GetService().GenerateScript(); + + Approver.Verify(script, MigrationScript.WithoutProductVersion, schema ?? "default_schema"); + } + } +} diff --git a/src/ServiceControl.Audit.Persistence.Tests.SqlServer/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.custom_schema.approved.txt b/src/ServiceControl.Audit.Persistence.Tests.SqlServer/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.custom_schema.approved.txt new file mode 100644 index 0000000000..fd4c5902e6 --- /dev/null +++ b/src/ServiceControl.Audit.Persistence.Tests.SqlServer/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.custom_schema.approved.txt @@ -0,0 +1,118 @@ +IF OBJECT_ID(N'[custom_schema].[__AuditMigrationsHistory]') IS NULL +BEGIN + IF SCHEMA_ID(N'custom_schema') IS NULL EXEC(N'CREATE SCHEMA [custom_schema];'); + CREATE TABLE [custom_schema].[__AuditMigrationsHistory] ( + [MigrationId] nvarchar(150) NOT NULL, + [ProductVersion] nvarchar(32) NOT NULL, + CONSTRAINT [PK___AuditMigrationsHistory] PRIMARY KEY ([MigrationId]) + ); +END; +GO + +BEGIN TRANSACTION; +CREATE TABLE [custom_schema].[AuditMessages] ( + [CreatedOn] datetime2 NOT NULL, + [Id] bigint NOT NULL IDENTITY, + [UniqueMessageId] uniqueidentifier NOT NULL, + [MessageId] nvarchar(max) NULL, + [MessageType] nvarchar(max) NULL, + [TimeSent] datetime2 NULL, + [ProcessedAt] datetime2 NOT NULL, + [ConversationId] nvarchar(450) NULL, + [IsSystemMessage] bit NOT NULL, + [Status] int NOT NULL, + [SendingEndpointName] nvarchar(max) NULL, + [SendingEndpointHostId] uniqueidentifier NULL, + [SendingEndpointHost] nvarchar(max) NULL, + [ReceivingEndpointName] nvarchar(450) NULL, + [ReceivingEndpointHostId] uniqueidentifier NULL, + [ReceivingEndpointHost] nvarchar(max) NULL, + [CriticalTimeTicks] bigint NULL, + [ProcessingTimeTicks] bigint NULL, + [DeliveryTimeTicks] bigint NULL, + [HeadersJson] nvarchar(max) NOT NULL, + [BodyText] nvarchar(max) NULL, + [BodyState] int NOT NULL, + [BodySize] int NOT NULL, + [BodyContentType] nvarchar(max) NULL, + CONSTRAINT [PK_AuditMessages] PRIMARY KEY ([CreatedOn], [Id]) +); + +CREATE TABLE [custom_schema].[FailedAuditImports] ( + [UniqueMessageId] uniqueidentifier NOT NULL, + [FailedAt] datetime2 NOT NULL, + [MessageId] nvarchar(max) NULL, + [HeadersJson] nvarchar(max) NOT NULL, + [Body] varbinary(max) NOT NULL, + [ExceptionInfo] nvarchar(max) NULL, + CONSTRAINT [PK_FailedAuditImports] PRIMARY KEY ([UniqueMessageId]) +); + +CREATE TABLE [custom_schema].[SagaSnapshots] ( + [CreatedOn] datetime2 NOT NULL, + [Id] bigint NOT NULL IDENTITY, + [SagaId] uniqueidentifier NOT NULL, + [SagaType] nvarchar(max) NULL, + [Status] int NOT NULL, + [StartTime] datetime2 NOT NULL, + [FinishTime] datetime2 NOT NULL, + [ProcessedAt] datetime2 NOT NULL, + [Endpoint] nvarchar(max) NULL, + [StateAfterChange] nvarchar(max) NULL, + [InitiatingMessageJson] nvarchar(max) NULL, + [OutgoingMessagesJson] nvarchar(max) NULL, + CONSTRAINT [PK_SagaSnapshots] PRIMARY KEY ([CreatedOn], [Id]) +); + +CREATE INDEX [IX_AuditMessages_ConversationId] ON [custom_schema].[AuditMessages] ([ConversationId]); + +CREATE UNIQUE INDEX [IX_AuditMessages_Id] ON [custom_schema].[AuditMessages] ([Id]); + +CREATE INDEX [IX_AuditMessages_ProcessedAt] ON [custom_schema].[AuditMessages] ([ProcessedAt]); + +CREATE INDEX [IX_AuditMessages_ReceivingEndpointName_CreatedOn] ON [custom_schema].[AuditMessages] ([ReceivingEndpointName], [CreatedOn]); + +CREATE INDEX [IX_AuditMessages_TimeSent] ON [custom_schema].[AuditMessages] ([TimeSent]); + +CREATE INDEX [IX_AuditMessages_UniqueMessageId] ON [custom_schema].[AuditMessages] ([UniqueMessageId]); + +CREATE INDEX [IX_FailedAuditImports_FailedAt_UniqueMessageId] ON [custom_schema].[FailedAuditImports] ([FailedAt], [UniqueMessageId]); + +CREATE INDEX [IX_SagaSnapshots_SagaId_FinishTime] ON [custom_schema].[SagaSnapshots] ([SagaId], [FinishTime]); + +INSERT INTO [custom_schema].[__AuditMigrationsHistory] ([MigrationId], [ProductVersion]) +VALUES (N'20260928032748_Initial', N''); + +COMMIT; +GO + +IF SERVERPROPERTY('IsFullTextInstalled') <> 1 +BEGIN + THROW 50000, 'ServiceControl requires the SQL Server Full-Text Search feature, which is not installed on this instance. Install it and run setup again.', 1; +END +GO + +IF NOT EXISTS (SELECT 1 FROM sys.fulltext_catalogs WHERE name = 'ServiceControlFullTextCatalog') +BEGIN + BEGIN TRY + EXEC('CREATE FULLTEXT CATALOG ServiceControlFullTextCatalog'); + END TRY + BEGIN CATCH + IF NOT EXISTS (SELECT 1 FROM sys.fulltext_catalogs WHERE name = 'ServiceControlFullTextCatalog') THROW; + END CATCH +END +GO + +IF NOT EXISTS (SELECT 1 FROM sys.fulltext_indexes WHERE object_id = OBJECT_ID('[custom_schema].[AuditMessages]')) +BEGIN + EXEC('CREATE FULLTEXT INDEX ON [custom_schema].[AuditMessages](HeadersJson LANGUAGE 0, BodyText LANGUAGE 0) + KEY INDEX IX_AuditMessages_Id + ON ServiceControlFullTextCatalog + WITH (CHANGE_TRACKING AUTO, STOPLIST = OFF)'); +END +GO + +INSERT INTO [custom_schema].[__AuditMigrationsHistory] ([MigrationId], [ProductVersion]) +VALUES (N'20260928032751_AddFullTextSearch', N''); +GO + diff --git a/src/ServiceControl.Audit.Persistence.Tests.SqlServer/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.default_schema.approved.txt b/src/ServiceControl.Audit.Persistence.Tests.SqlServer/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.default_schema.approved.txt new file mode 100644 index 0000000000..7bef1a47e6 --- /dev/null +++ b/src/ServiceControl.Audit.Persistence.Tests.SqlServer/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.default_schema.approved.txt @@ -0,0 +1,117 @@ +IF OBJECT_ID(N'[__AuditMigrationsHistory]') IS NULL +BEGIN + CREATE TABLE [__AuditMigrationsHistory] ( + [MigrationId] nvarchar(150) NOT NULL, + [ProductVersion] nvarchar(32) NOT NULL, + CONSTRAINT [PK___AuditMigrationsHistory] PRIMARY KEY ([MigrationId]) + ); +END; +GO + +BEGIN TRANSACTION; +CREATE TABLE [AuditMessages] ( + [CreatedOn] datetime2 NOT NULL, + [Id] bigint NOT NULL IDENTITY, + [UniqueMessageId] uniqueidentifier NOT NULL, + [MessageId] nvarchar(max) NULL, + [MessageType] nvarchar(max) NULL, + [TimeSent] datetime2 NULL, + [ProcessedAt] datetime2 NOT NULL, + [ConversationId] nvarchar(450) NULL, + [IsSystemMessage] bit NOT NULL, + [Status] int NOT NULL, + [SendingEndpointName] nvarchar(max) NULL, + [SendingEndpointHostId] uniqueidentifier NULL, + [SendingEndpointHost] nvarchar(max) NULL, + [ReceivingEndpointName] nvarchar(450) NULL, + [ReceivingEndpointHostId] uniqueidentifier NULL, + [ReceivingEndpointHost] nvarchar(max) NULL, + [CriticalTimeTicks] bigint NULL, + [ProcessingTimeTicks] bigint NULL, + [DeliveryTimeTicks] bigint NULL, + [HeadersJson] nvarchar(max) NOT NULL, + [BodyText] nvarchar(max) NULL, + [BodyState] int NOT NULL, + [BodySize] int NOT NULL, + [BodyContentType] nvarchar(max) NULL, + CONSTRAINT [PK_AuditMessages] PRIMARY KEY ([CreatedOn], [Id]) +); + +CREATE TABLE [FailedAuditImports] ( + [UniqueMessageId] uniqueidentifier NOT NULL, + [FailedAt] datetime2 NOT NULL, + [MessageId] nvarchar(max) NULL, + [HeadersJson] nvarchar(max) NOT NULL, + [Body] varbinary(max) NOT NULL, + [ExceptionInfo] nvarchar(max) NULL, + CONSTRAINT [PK_FailedAuditImports] PRIMARY KEY ([UniqueMessageId]) +); + +CREATE TABLE [SagaSnapshots] ( + [CreatedOn] datetime2 NOT NULL, + [Id] bigint NOT NULL IDENTITY, + [SagaId] uniqueidentifier NOT NULL, + [SagaType] nvarchar(max) NULL, + [Status] int NOT NULL, + [StartTime] datetime2 NOT NULL, + [FinishTime] datetime2 NOT NULL, + [ProcessedAt] datetime2 NOT NULL, + [Endpoint] nvarchar(max) NULL, + [StateAfterChange] nvarchar(max) NULL, + [InitiatingMessageJson] nvarchar(max) NULL, + [OutgoingMessagesJson] nvarchar(max) NULL, + CONSTRAINT [PK_SagaSnapshots] PRIMARY KEY ([CreatedOn], [Id]) +); + +CREATE INDEX [IX_AuditMessages_ConversationId] ON [AuditMessages] ([ConversationId]); + +CREATE UNIQUE INDEX [IX_AuditMessages_Id] ON [AuditMessages] ([Id]); + +CREATE INDEX [IX_AuditMessages_ProcessedAt] ON [AuditMessages] ([ProcessedAt]); + +CREATE INDEX [IX_AuditMessages_ReceivingEndpointName_CreatedOn] ON [AuditMessages] ([ReceivingEndpointName], [CreatedOn]); + +CREATE INDEX [IX_AuditMessages_TimeSent] ON [AuditMessages] ([TimeSent]); + +CREATE INDEX [IX_AuditMessages_UniqueMessageId] ON [AuditMessages] ([UniqueMessageId]); + +CREATE INDEX [IX_FailedAuditImports_FailedAt_UniqueMessageId] ON [FailedAuditImports] ([FailedAt], [UniqueMessageId]); + +CREATE INDEX [IX_SagaSnapshots_SagaId_FinishTime] ON [SagaSnapshots] ([SagaId], [FinishTime]); + +INSERT INTO [__AuditMigrationsHistory] ([MigrationId], [ProductVersion]) +VALUES (N'20260928032748_Initial', N''); + +COMMIT; +GO + +IF SERVERPROPERTY('IsFullTextInstalled') <> 1 +BEGIN + THROW 50000, 'ServiceControl requires the SQL Server Full-Text Search feature, which is not installed on this instance. Install it and run setup again.', 1; +END +GO + +IF NOT EXISTS (SELECT 1 FROM sys.fulltext_catalogs WHERE name = 'ServiceControlFullTextCatalog') +BEGIN + BEGIN TRY + EXEC('CREATE FULLTEXT CATALOG ServiceControlFullTextCatalog'); + END TRY + BEGIN CATCH + IF NOT EXISTS (SELECT 1 FROM sys.fulltext_catalogs WHERE name = 'ServiceControlFullTextCatalog') THROW; + END CATCH +END +GO + +IF NOT EXISTS (SELECT 1 FROM sys.fulltext_indexes WHERE object_id = OBJECT_ID('AuditMessages')) +BEGIN + EXEC('CREATE FULLTEXT INDEX ON AuditMessages(HeadersJson LANGUAGE 0, BodyText LANGUAGE 0) + KEY INDEX IX_AuditMessages_Id + ON ServiceControlFullTextCatalog + WITH (CHANGE_TRACKING AUTO, STOPLIST = OFF)'); +END +GO + +INSERT INTO [__AuditMigrationsHistory] ([MigrationId], [ProductVersion]) +VALUES (N'20260928032751_AddFullTextSearch', N''); +GO + diff --git a/src/ServiceControl.Audit.Persistence.Tests.SqlServer/MigrationScriptApprovals.cs b/src/ServiceControl.Audit.Persistence.Tests.SqlServer/MigrationScriptApprovals.cs new file mode 100644 index 0000000000..937cff594d --- /dev/null +++ b/src/ServiceControl.Audit.Persistence.Tests.SqlServer/MigrationScriptApprovals.cs @@ -0,0 +1,39 @@ +namespace ServiceControl.Audit.Persistence.Tests +{ + using System; + using Microsoft.EntityFrameworkCore.Infrastructure; + using Microsoft.EntityFrameworkCore.Migrations; + using Microsoft.Extensions.DependencyInjection; + using NUnit.Framework; + using Particular.Approvals; + using ServiceControl.Audit.Persistence.EFCore.Abstractions; + using ServiceControl.Audit.Persistence.EFCore.DbContexts; + using ServiceControl.Audit.Persistence.EFCore.SqlServer; + using TestHelper; + + // A changed index can block writes on a large table for a long time. Record it in the upgrade guide and never edit an existing migration, see docs/testing.md. + [TestFixture] + class MigrationScriptApprovals + { + [TestCase(null)] + [TestCase("custom_schema")] + public void Verify_migration_script_is_unchanged(string schema) + { + var settings = new PersistenceSettings(TimeSpan.FromDays(1), true, 100000); + settings.PersisterSpecificSettings[EFPersistenceConfigurationBase.ConnectionStringKey] = "Server=unused"; + if (schema is not null) + { + settings.PersisterSpecificSettings[EFPersistenceConfigurationBase.SchemaKey] = schema; + } + + var services = new ServiceCollection(); + new SqlServerPersistenceConfiguration().Create(settings).AddInstaller(services); + + using var serviceProvider = services.BuildServiceProvider(); + using var scope = serviceProvider.CreateScope(); + var script = scope.ServiceProvider.GetRequiredService().GetService().GenerateScript(); + + Approver.Verify(script, MigrationScript.WithoutProductVersion, schema ?? "default_schema"); + } + } +} diff --git a/src/ServiceControl.Persistence.Tests.PostgreSql/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.custom_schema.approved.txt b/src/ServiceControl.Persistence.Tests.PostgreSql/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.custom_schema.approved.txt new file mode 100644 index 0000000000..f432879bc8 --- /dev/null +++ b/src/ServiceControl.Persistence.Tests.PostgreSql/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.custom_schema.approved.txt @@ -0,0 +1,335 @@ +DO $EF$ +BEGIN + IF NOT EXISTS(SELECT 1 FROM pg_namespace WHERE nspname = 'custom_schema') THEN + CREATE SCHEMA custom_schema; + END IF; +END $EF$; +CREATE TABLE IF NOT EXISTS custom_schema."__EFMigrationsHistory" ( + migration_id character varying(150) NOT NULL, + product_version character varying(32) NOT NULL, + CONSTRAINT pk___ef_migrations_history PRIMARY KEY (migration_id) +); + +START TRANSACTION; +CREATE TABLE custom_schema.archive_operations ( + request_id character varying(64) NOT NULL, + archive_type integer NOT NULL, + operation_type integer NOT NULL, + group_name text NOT NULL, + total_number_of_messages integer NOT NULL, + number_of_messages_processed integer NOT NULL, + number_of_batches integer NOT NULL, + current_batch integer NOT NULL, + started timestamp with time zone NOT NULL, + initiated_by_id character varying(450), + initiated_by_name character varying(450), + operation_id character varying(450), + CONSTRAINT pk_archive_operations PRIMARY KEY (request_id, archive_type, operation_type) +); + +CREATE TABLE custom_schema.custom_checks ( + id uuid NOT NULL, + custom_check_id text NOT NULL, + category text NOT NULL, + status integer NOT NULL, + reported_at timestamp with time zone NOT NULL, + failure_reason text, + originating_endpoint_name text NOT NULL, + originating_endpoint_host_id uuid NOT NULL, + originating_endpoint_host text NOT NULL, + CONSTRAINT pk_custom_checks PRIMARY KEY (id) +); + +CREATE TABLE custom_schema.endpoint_settings ( + name character varying(450) NOT NULL, + track_instances boolean NOT NULL, + CONSTRAINT pk_endpoint_settings PRIMARY KEY (name) +); + +CREATE TABLE custom_schema.event_log_items ( + id bigint GENERATED BY DEFAULT AS IDENTITY, + description text NOT NULL, + severity integer NOT NULL, + raised_at timestamp with time zone NOT NULL, + related_to text[] NOT NULL, + category character varying(450) NOT NULL, + event_type character varying(450) NOT NULL, + CONSTRAINT pk_event_log_items PRIMARY KEY (id) +); + +CREATE TABLE custom_schema.external_integration_dispatch_requests ( + id bigint GENERATED BY DEFAULT AS IDENTITY, + dispatch_context_type_name character varying(450) NOT NULL, + dispatch_context_json text NOT NULL, + CONSTRAINT pk_external_integration_dispatch_requests PRIMARY KEY (id) +); + +CREATE TABLE custom_schema.failed_error_imports ( + unique_message_id uuid NOT NULL, + failed_at timestamp with time zone NOT NULL, + message_id character varying(450) NOT NULL, + headers_json text NOT NULL, + body bytea NOT NULL, + body_stored_externally boolean NOT NULL, + exception_info text NOT NULL, + CONSTRAINT pk_failed_error_imports PRIMARY KEY (unique_message_id) +); + +CREATE TABLE custom_schema.failed_message_edits ( + unique_message_id uuid NOT NULL, + edit_id character varying(450) NOT NULL, + CONSTRAINT pk_failed_message_edits PRIMARY KEY (unique_message_id) +); + +CREATE TABLE custom_schema.failed_message_retries ( + unique_message_id uuid NOT NULL, + retry_batch_id uuid NOT NULL, + stage_attempts integer NOT NULL, + CONSTRAINT pk_failed_message_retries PRIMARY KEY (unique_message_id) +); + +CREATE TABLE custom_schema.failed_messages ( + unique_message_id uuid NOT NULL, + status integer NOT NULL, + status_changed_at timestamp with time zone NOT NULL, + last_modified timestamp with time zone NOT NULL, + number_of_processing_attempts integer NOT NULL, + first_time_of_failure timestamp with time zone NOT NULL, + last_time_of_failure timestamp with time zone NOT NULL, + last_attempted_at timestamp with time zone NOT NULL, + message_id character varying(450), + message_type text, + time_sent timestamp with time zone, + conversation_id character varying(450), + sending_endpoint_name character varying(450), + sending_endpoint_host_id uuid, + sending_endpoint_host character varying(450), + receiving_endpoint_name character varying(450), + receiving_endpoint_host_id uuid, + receiving_endpoint_host character varying(450), + exception_type text, + exception_message text, + is_system_message boolean NOT NULL, + headers_json text NOT NULL, + body_text text, + body_stored_externally boolean NOT NULL, + body_size integer NOT NULL, + body_content_type character varying(450), + failing_endpoint_address character varying(450) NOT NULL, + CONSTRAINT pk_failed_messages PRIMARY KEY (unique_message_id) +); + +CREATE TABLE custom_schema.group_comments ( + group_id character varying(64) NOT NULL, + comment text NOT NULL, + CONSTRAINT pk_group_comments PRIMARY KEY (group_id) +); + +CREATE TABLE custom_schema.historic_retry_operations ( + id bigint GENERATED BY DEFAULT AS IDENTITY, + request_id character varying(400) NOT NULL, + retry_type integer NOT NULL, + start_time timestamp with time zone NOT NULL, + completion_time timestamp with time zone NOT NULL, + originator text, + failed boolean NOT NULL, + number_of_messages_processed integer NOT NULL, + CONSTRAINT pk_historic_retry_operations PRIMARY KEY (id) +); + +CREATE TABLE custom_schema.known_endpoints ( + id uuid NOT NULL, + name character varying(450) NOT NULL, + host_id uuid NOT NULL, + host character varying(450) NOT NULL, + monitored boolean NOT NULL, + CONSTRAINT pk_known_endpoints PRIMARY KEY (id) +); + +CREATE TABLE custom_schema.licensing_endpoints ( + normalized_name character varying(300) NOT NULL, + throughput_source integer NOT NULL, + name character varying(300) NOT NULL, + sanitized_name character varying(450) NOT NULL, + normalized_sanitized_name character varying(450) NOT NULL, + user_indicator character varying(450) NOT NULL, + scope character varying(450), + endpoint_indicators text[] NOT NULL, + CONSTRAINT pk_licensing_endpoints PRIMARY KEY (normalized_name, throughput_source) +); + +CREATE TABLE custom_schema.message_redirects ( + from_physical_address character varying(450) NOT NULL, + to_physical_address character varying(450) NOT NULL, + last_modified timestamp with time zone NOT NULL, + CONSTRAINT pk_message_redirects PRIMARY KEY (from_physical_address) +); + +CREATE TABLE custom_schema.retry_batch_now_forwarding ( + id integer NOT NULL, + retry_batch_id uuid NOT NULL, + CONSTRAINT pk_retry_batch_now_forwarding PRIMARY KEY (id) +); + +CREATE TABLE custom_schema.retry_batches ( + id uuid NOT NULL, + status integer NOT NULL, + retry_session_id character varying(450) NOT NULL, + request_id character varying(450) NOT NULL, + retry_type integer NOT NULL, + initial_batch_size integer NOT NULL, + start_time timestamp with time zone NOT NULL, + last timestamp with time zone, + staging_id character varying(450), + context text, + originator text, + classifier text, + initiated_by_id character varying(450), + initiated_by_name text, + operation_id character varying(450), + CONSTRAINT pk_retry_batches PRIMARY KEY (id) +); + +CREATE TABLE custom_schema.settings ( + key character varying(450) NOT NULL, + value text NOT NULL, + CONSTRAINT pk_settings PRIMARY KEY (key) +); + +CREATE TABLE custom_schema.subscriptions ( + message_type character varying(200) NOT NULL, + transport_address character varying(200) NOT NULL, + endpoint character varying(450) NOT NULL, + CONSTRAINT pk_subscriptions PRIMARY KEY (message_type, transport_address) +); + +CREATE TABLE custom_schema.unacknowledged_retry_operations ( + request_id character varying(400) NOT NULL, + retry_type integer NOT NULL, + start_time timestamp with time zone NOT NULL, + completion_time timestamp with time zone NOT NULL, + last timestamp with time zone NOT NULL, + originator text, + classifier character varying(450), + failed boolean NOT NULL, + number_of_messages_processed integer NOT NULL, + CONSTRAINT pk_unacknowledged_retry_operations PRIMARY KEY (request_id, retry_type) +); + +CREATE TABLE custom_schema.failed_message_groups ( + failed_message_unique_id uuid NOT NULL, + group_id character varying(64) NOT NULL, + title text NOT NULL, + type character varying(255) NOT NULL, + CONSTRAINT pk_failed_message_groups PRIMARY KEY (failed_message_unique_id, group_id), + CONSTRAINT fk_failed_message_groups_failed_messages_failed_message_unique FOREIGN KEY (failed_message_unique_id) REFERENCES custom_schema.failed_messages (unique_message_id) ON DELETE CASCADE +); + +CREATE TABLE custom_schema.licensing_endpoint_throughput ( + normalized_name character varying(300) NOT NULL, + throughput_source integer NOT NULL, + date_utc date NOT NULL, + message_count bigint NOT NULL, + CONSTRAINT pk_licensing_endpoint_throughput PRIMARY KEY (normalized_name, throughput_source, date_utc), + CONSTRAINT fk_licensing_endpoint_throughput_licensing_endpoints_normalize FOREIGN KEY (normalized_name, throughput_source) REFERENCES custom_schema.licensing_endpoints (normalized_name, throughput_source) ON DELETE CASCADE +); + +CREATE INDEX ix_archive_operations_started ON custom_schema.archive_operations (started); + +CREATE INDEX ix_custom_checks_reported_at ON custom_schema.custom_checks (reported_at); + +CREATE INDEX ix_custom_checks_status_reported_at ON custom_schema.custom_checks (status, reported_at); + +CREATE INDEX ix_event_log_items_raised_at_id ON custom_schema.event_log_items (raised_at DESC, id DESC); + +CREATE INDEX ix_failed_error_imports_failed_at ON custom_schema.failed_error_imports (failed_at); + +CREATE INDEX ix_failed_message_edits_edit_id ON custom_schema.failed_message_edits (edit_id); + +CREATE INDEX ix_failed_message_groups_group_id ON custom_schema.failed_message_groups (group_id); + +CREATE INDEX ix_failed_message_groups_type_group_id ON custom_schema.failed_message_groups (type, group_id); + +CREATE INDEX ix_failed_message_retries_retry_batch_id ON custom_schema.failed_message_retries (retry_batch_id); + +CREATE INDEX ix_failed_messages_conversation_id ON custom_schema.failed_messages (conversation_id); + +CREATE INDEX ix_failed_messages_failing_endpoint_address ON custom_schema.failed_messages (failing_endpoint_address); + +CREATE INDEX ix_failed_messages_receiving_endpoint_name ON custom_schema.failed_messages (receiving_endpoint_name); + +CREATE INDEX ix_failed_messages_status_changed_at ON custom_schema.failed_messages (status_changed_at) WHERE status IN (2, 4); + +CREATE INDEX ix_failed_messages_status_last_modified ON custom_schema.failed_messages (status, last_modified); + +CREATE INDEX ix_failed_messages_time_sent ON custom_schema.failed_messages (time_sent); + +CREATE INDEX ix_historic_retry_operations_completion_time_id ON custom_schema.historic_retry_operations (completion_time DESC, id DESC); + +CREATE INDEX ix_licensing_endpoint_throughput_date_utc ON custom_schema.licensing_endpoint_throughput (date_utc); + +CREATE INDEX ix_licensing_endpoints_normalized_sanitized_name ON custom_schema.licensing_endpoints (normalized_sanitized_name); + +CREATE INDEX ix_retry_batches_status_retry_session_id ON custom_schema.retry_batches (status, retry_session_id); + +INSERT INTO custom_schema."__EFMigrationsHistory" (migration_id, product_version) +VALUES ('20260902040418_Initial', ''); + +COMMIT; + +START TRANSACTION; +CREATE INDEX ix_failed_messages_full_text ON "custom_schema".failed_messages USING GIN (to_tsvector('simple', headers_json || ' ' || COALESCE(body_text, '') || ' ' || replace(replace(COALESCE(message_type, ''), '.', ' '), '+', ' '))) + +INSERT INTO custom_schema."__EFMigrationsHistory" (migration_id, product_version) +VALUES ('20260902040422_AddFullTextSearch', ''); + +COMMIT; + +START TRANSACTION; +DROP INDEX custom_schema.ix_failed_messages_status_last_modified; + +DROP INDEX custom_schema.ix_failed_message_groups_type_group_id; + +ALTER TABLE custom_schema.failed_messages ALTER COLUMN message_type TYPE character varying(450); + +CREATE INDEX ix_failed_messages_status_last_modified ON custom_schema.failed_messages (status, last_modified) INCLUDE (first_time_of_failure, last_time_of_failure); + +CREATE INDEX ix_failed_messages_status_last_time_of_failure ON custom_schema.failed_messages (status, last_time_of_failure); + +CREATE INDEX ix_failed_messages_status_message_type_unique_message_id ON custom_schema.failed_messages (status, message_type, unique_message_id); + +CREATE INDEX ix_failed_message_groups_type_group_id ON custom_schema.failed_message_groups (type, group_id) INCLUDE (title); + +INSERT INTO custom_schema."__EFMigrationsHistory" (migration_id, product_version) +VALUES ('20260911041328_AddMissingIndexes', ''); + +COMMIT; + +START TRANSACTION; +DROP INDEX custom_schema.ix_failed_messages_status_last_modified; + +DROP INDEX custom_schema.ix_failed_message_groups_type_group_id; + +CREATE INDEX ix_failed_messages_status_last_modified_unique_message_id ON custom_schema.failed_messages (status, last_modified, unique_message_id) INCLUDE (first_time_of_failure, last_time_of_failure); + +CREATE INDEX ix_failed_message_groups_type_group_id ON custom_schema.failed_message_groups (type, group_id) INCLUDE (failed_message_unique_id, title); + +INSERT INTO custom_schema."__EFMigrationsHistory" (migration_id, product_version) +VALUES ('20260915050538_AddAdditionalIndexCoverage', ''); + +COMMIT; + +START TRANSACTION; +ALTER TABLE custom_schema.failed_messages ALTER COLUMN message_id TYPE text; + +ALTER TABLE custom_schema.failed_error_imports ALTER COLUMN message_id TYPE text; + +DROP INDEX IF EXISTS "custom_schema".ix_failed_messages_full_text + +CREATE INDEX ix_failed_messages_full_text ON "custom_schema".failed_messages USING GIN (to_tsvector('simple', headers_json || ' ' || substring(COALESCE(body_text, ''), 1, 262144) || ' ' || replace(replace(COALESCE(message_type, ''), '.', ' '), '+', ' '))) + +INSERT INTO custom_schema."__EFMigrationsHistory" (migration_id, product_version) +VALUES ('20260928064706_WidenMessageIdAndCapFullTextBody', ''); + +COMMIT; + diff --git a/src/ServiceControl.Persistence.Tests.PostgreSql/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.default_schema.approved.txt b/src/ServiceControl.Persistence.Tests.PostgreSql/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.default_schema.approved.txt new file mode 100644 index 0000000000..bfaa7b5f58 --- /dev/null +++ b/src/ServiceControl.Persistence.Tests.PostgreSql/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.default_schema.approved.txt @@ -0,0 +1,329 @@ +CREATE TABLE IF NOT EXISTS "__EFMigrationsHistory" ( + migration_id character varying(150) NOT NULL, + product_version character varying(32) NOT NULL, + CONSTRAINT pk___ef_migrations_history PRIMARY KEY (migration_id) +); + +START TRANSACTION; +CREATE TABLE archive_operations ( + request_id character varying(64) NOT NULL, + archive_type integer NOT NULL, + operation_type integer NOT NULL, + group_name text NOT NULL, + total_number_of_messages integer NOT NULL, + number_of_messages_processed integer NOT NULL, + number_of_batches integer NOT NULL, + current_batch integer NOT NULL, + started timestamp with time zone NOT NULL, + initiated_by_id character varying(450), + initiated_by_name character varying(450), + operation_id character varying(450), + CONSTRAINT pk_archive_operations PRIMARY KEY (request_id, archive_type, operation_type) +); + +CREATE TABLE custom_checks ( + id uuid NOT NULL, + custom_check_id text NOT NULL, + category text NOT NULL, + status integer NOT NULL, + reported_at timestamp with time zone NOT NULL, + failure_reason text, + originating_endpoint_name text NOT NULL, + originating_endpoint_host_id uuid NOT NULL, + originating_endpoint_host text NOT NULL, + CONSTRAINT pk_custom_checks PRIMARY KEY (id) +); + +CREATE TABLE endpoint_settings ( + name character varying(450) NOT NULL, + track_instances boolean NOT NULL, + CONSTRAINT pk_endpoint_settings PRIMARY KEY (name) +); + +CREATE TABLE event_log_items ( + id bigint GENERATED BY DEFAULT AS IDENTITY, + description text NOT NULL, + severity integer NOT NULL, + raised_at timestamp with time zone NOT NULL, + related_to text[] NOT NULL, + category character varying(450) NOT NULL, + event_type character varying(450) NOT NULL, + CONSTRAINT pk_event_log_items PRIMARY KEY (id) +); + +CREATE TABLE external_integration_dispatch_requests ( + id bigint GENERATED BY DEFAULT AS IDENTITY, + dispatch_context_type_name character varying(450) NOT NULL, + dispatch_context_json text NOT NULL, + CONSTRAINT pk_external_integration_dispatch_requests PRIMARY KEY (id) +); + +CREATE TABLE failed_error_imports ( + unique_message_id uuid NOT NULL, + failed_at timestamp with time zone NOT NULL, + message_id character varying(450) NOT NULL, + headers_json text NOT NULL, + body bytea NOT NULL, + body_stored_externally boolean NOT NULL, + exception_info text NOT NULL, + CONSTRAINT pk_failed_error_imports PRIMARY KEY (unique_message_id) +); + +CREATE TABLE failed_message_edits ( + unique_message_id uuid NOT NULL, + edit_id character varying(450) NOT NULL, + CONSTRAINT pk_failed_message_edits PRIMARY KEY (unique_message_id) +); + +CREATE TABLE failed_message_retries ( + unique_message_id uuid NOT NULL, + retry_batch_id uuid NOT NULL, + stage_attempts integer NOT NULL, + CONSTRAINT pk_failed_message_retries PRIMARY KEY (unique_message_id) +); + +CREATE TABLE failed_messages ( + unique_message_id uuid NOT NULL, + status integer NOT NULL, + status_changed_at timestamp with time zone NOT NULL, + last_modified timestamp with time zone NOT NULL, + number_of_processing_attempts integer NOT NULL, + first_time_of_failure timestamp with time zone NOT NULL, + last_time_of_failure timestamp with time zone NOT NULL, + last_attempted_at timestamp with time zone NOT NULL, + message_id character varying(450), + message_type text, + time_sent timestamp with time zone, + conversation_id character varying(450), + sending_endpoint_name character varying(450), + sending_endpoint_host_id uuid, + sending_endpoint_host character varying(450), + receiving_endpoint_name character varying(450), + receiving_endpoint_host_id uuid, + receiving_endpoint_host character varying(450), + exception_type text, + exception_message text, + is_system_message boolean NOT NULL, + headers_json text NOT NULL, + body_text text, + body_stored_externally boolean NOT NULL, + body_size integer NOT NULL, + body_content_type character varying(450), + failing_endpoint_address character varying(450) NOT NULL, + CONSTRAINT pk_failed_messages PRIMARY KEY (unique_message_id) +); + +CREATE TABLE group_comments ( + group_id character varying(64) NOT NULL, + comment text NOT NULL, + CONSTRAINT pk_group_comments PRIMARY KEY (group_id) +); + +CREATE TABLE historic_retry_operations ( + id bigint GENERATED BY DEFAULT AS IDENTITY, + request_id character varying(400) NOT NULL, + retry_type integer NOT NULL, + start_time timestamp with time zone NOT NULL, + completion_time timestamp with time zone NOT NULL, + originator text, + failed boolean NOT NULL, + number_of_messages_processed integer NOT NULL, + CONSTRAINT pk_historic_retry_operations PRIMARY KEY (id) +); + +CREATE TABLE known_endpoints ( + id uuid NOT NULL, + name character varying(450) NOT NULL, + host_id uuid NOT NULL, + host character varying(450) NOT NULL, + monitored boolean NOT NULL, + CONSTRAINT pk_known_endpoints PRIMARY KEY (id) +); + +CREATE TABLE licensing_endpoints ( + normalized_name character varying(300) NOT NULL, + throughput_source integer NOT NULL, + name character varying(300) NOT NULL, + sanitized_name character varying(450) NOT NULL, + normalized_sanitized_name character varying(450) NOT NULL, + user_indicator character varying(450) NOT NULL, + scope character varying(450), + endpoint_indicators text[] NOT NULL, + CONSTRAINT pk_licensing_endpoints PRIMARY KEY (normalized_name, throughput_source) +); + +CREATE TABLE message_redirects ( + from_physical_address character varying(450) NOT NULL, + to_physical_address character varying(450) NOT NULL, + last_modified timestamp with time zone NOT NULL, + CONSTRAINT pk_message_redirects PRIMARY KEY (from_physical_address) +); + +CREATE TABLE retry_batch_now_forwarding ( + id integer NOT NULL, + retry_batch_id uuid NOT NULL, + CONSTRAINT pk_retry_batch_now_forwarding PRIMARY KEY (id) +); + +CREATE TABLE retry_batches ( + id uuid NOT NULL, + status integer NOT NULL, + retry_session_id character varying(450) NOT NULL, + request_id character varying(450) NOT NULL, + retry_type integer NOT NULL, + initial_batch_size integer NOT NULL, + start_time timestamp with time zone NOT NULL, + last timestamp with time zone, + staging_id character varying(450), + context text, + originator text, + classifier text, + initiated_by_id character varying(450), + initiated_by_name text, + operation_id character varying(450), + CONSTRAINT pk_retry_batches PRIMARY KEY (id) +); + +CREATE TABLE settings ( + key character varying(450) NOT NULL, + value text NOT NULL, + CONSTRAINT pk_settings PRIMARY KEY (key) +); + +CREATE TABLE subscriptions ( + message_type character varying(200) NOT NULL, + transport_address character varying(200) NOT NULL, + endpoint character varying(450) NOT NULL, + CONSTRAINT pk_subscriptions PRIMARY KEY (message_type, transport_address) +); + +CREATE TABLE unacknowledged_retry_operations ( + request_id character varying(400) NOT NULL, + retry_type integer NOT NULL, + start_time timestamp with time zone NOT NULL, + completion_time timestamp with time zone NOT NULL, + last timestamp with time zone NOT NULL, + originator text, + classifier character varying(450), + failed boolean NOT NULL, + number_of_messages_processed integer NOT NULL, + CONSTRAINT pk_unacknowledged_retry_operations PRIMARY KEY (request_id, retry_type) +); + +CREATE TABLE failed_message_groups ( + failed_message_unique_id uuid NOT NULL, + group_id character varying(64) NOT NULL, + title text NOT NULL, + type character varying(255) NOT NULL, + CONSTRAINT pk_failed_message_groups PRIMARY KEY (failed_message_unique_id, group_id), + CONSTRAINT fk_failed_message_groups_failed_messages_failed_message_unique FOREIGN KEY (failed_message_unique_id) REFERENCES failed_messages (unique_message_id) ON DELETE CASCADE +); + +CREATE TABLE licensing_endpoint_throughput ( + normalized_name character varying(300) NOT NULL, + throughput_source integer NOT NULL, + date_utc date NOT NULL, + message_count bigint NOT NULL, + CONSTRAINT pk_licensing_endpoint_throughput PRIMARY KEY (normalized_name, throughput_source, date_utc), + CONSTRAINT fk_licensing_endpoint_throughput_licensing_endpoints_normalize FOREIGN KEY (normalized_name, throughput_source) REFERENCES licensing_endpoints (normalized_name, throughput_source) ON DELETE CASCADE +); + +CREATE INDEX ix_archive_operations_started ON archive_operations (started); + +CREATE INDEX ix_custom_checks_reported_at ON custom_checks (reported_at); + +CREATE INDEX ix_custom_checks_status_reported_at ON custom_checks (status, reported_at); + +CREATE INDEX ix_event_log_items_raised_at_id ON event_log_items (raised_at DESC, id DESC); + +CREATE INDEX ix_failed_error_imports_failed_at ON failed_error_imports (failed_at); + +CREATE INDEX ix_failed_message_edits_edit_id ON failed_message_edits (edit_id); + +CREATE INDEX ix_failed_message_groups_group_id ON failed_message_groups (group_id); + +CREATE INDEX ix_failed_message_groups_type_group_id ON failed_message_groups (type, group_id); + +CREATE INDEX ix_failed_message_retries_retry_batch_id ON failed_message_retries (retry_batch_id); + +CREATE INDEX ix_failed_messages_conversation_id ON failed_messages (conversation_id); + +CREATE INDEX ix_failed_messages_failing_endpoint_address ON failed_messages (failing_endpoint_address); + +CREATE INDEX ix_failed_messages_receiving_endpoint_name ON failed_messages (receiving_endpoint_name); + +CREATE INDEX ix_failed_messages_status_changed_at ON failed_messages (status_changed_at) WHERE status IN (2, 4); + +CREATE INDEX ix_failed_messages_status_last_modified ON failed_messages (status, last_modified); + +CREATE INDEX ix_failed_messages_time_sent ON failed_messages (time_sent); + +CREATE INDEX ix_historic_retry_operations_completion_time_id ON historic_retry_operations (completion_time DESC, id DESC); + +CREATE INDEX ix_licensing_endpoint_throughput_date_utc ON licensing_endpoint_throughput (date_utc); + +CREATE INDEX ix_licensing_endpoints_normalized_sanitized_name ON licensing_endpoints (normalized_sanitized_name); + +CREATE INDEX ix_retry_batches_status_retry_session_id ON retry_batches (status, retry_session_id); + +INSERT INTO "__EFMigrationsHistory" (migration_id, product_version) +VALUES ('20260902040418_Initial', ''); + +COMMIT; + +START TRANSACTION; +CREATE INDEX ix_failed_messages_full_text ON failed_messages USING GIN (to_tsvector('simple', headers_json || ' ' || COALESCE(body_text, '') || ' ' || replace(replace(COALESCE(message_type, ''), '.', ' '), '+', ' '))) + +INSERT INTO "__EFMigrationsHistory" (migration_id, product_version) +VALUES ('20260902040422_AddFullTextSearch', ''); + +COMMIT; + +START TRANSACTION; +DROP INDEX ix_failed_messages_status_last_modified; + +DROP INDEX ix_failed_message_groups_type_group_id; + +ALTER TABLE failed_messages ALTER COLUMN message_type TYPE character varying(450); + +CREATE INDEX ix_failed_messages_status_last_modified ON failed_messages (status, last_modified) INCLUDE (first_time_of_failure, last_time_of_failure); + +CREATE INDEX ix_failed_messages_status_last_time_of_failure ON failed_messages (status, last_time_of_failure); + +CREATE INDEX ix_failed_messages_status_message_type_unique_message_id ON failed_messages (status, message_type, unique_message_id); + +CREATE INDEX ix_failed_message_groups_type_group_id ON failed_message_groups (type, group_id) INCLUDE (title); + +INSERT INTO "__EFMigrationsHistory" (migration_id, product_version) +VALUES ('20260911041328_AddMissingIndexes', ''); + +COMMIT; + +START TRANSACTION; +DROP INDEX ix_failed_messages_status_last_modified; + +DROP INDEX ix_failed_message_groups_type_group_id; + +CREATE INDEX ix_failed_messages_status_last_modified_unique_message_id ON failed_messages (status, last_modified, unique_message_id) INCLUDE (first_time_of_failure, last_time_of_failure); + +CREATE INDEX ix_failed_message_groups_type_group_id ON failed_message_groups (type, group_id) INCLUDE (failed_message_unique_id, title); + +INSERT INTO "__EFMigrationsHistory" (migration_id, product_version) +VALUES ('20260915050538_AddAdditionalIndexCoverage', ''); + +COMMIT; + +START TRANSACTION; +ALTER TABLE failed_messages ALTER COLUMN message_id TYPE text; + +ALTER TABLE failed_error_imports ALTER COLUMN message_id TYPE text; + +DROP INDEX IF EXISTS ix_failed_messages_full_text + +CREATE INDEX ix_failed_messages_full_text ON failed_messages USING GIN (to_tsvector('simple', headers_json || ' ' || substring(COALESCE(body_text, ''), 1, 262144) || ' ' || replace(replace(COALESCE(message_type, ''), '.', ' '), '+', ' '))) + +INSERT INTO "__EFMigrationsHistory" (migration_id, product_version) +VALUES ('20260928064706_WidenMessageIdAndCapFullTextBody', ''); + +COMMIT; + diff --git a/src/ServiceControl.Persistence.Tests.PostgreSql/MigrationScriptApprovals.cs b/src/ServiceControl.Persistence.Tests.PostgreSql/MigrationScriptApprovals.cs new file mode 100644 index 0000000000..200f8740ce --- /dev/null +++ b/src/ServiceControl.Persistence.Tests.PostgreSql/MigrationScriptApprovals.cs @@ -0,0 +1,38 @@ +// ReSharper disable once CheckNamespace +namespace ServiceControl.Persistence.Tests; + +using EFCore.PostgreSql; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.Extensions.DependencyInjection; +using NUnit.Framework; +using Particular.Approvals; +using ServiceControl.Persistence.EFCore.Abstractions; +using ServiceControl.Persistence.EFCore.DbContexts; +using TestHelper; + +// A changed index can block writes on a large table for a long time. Record it in the upgrade guide and never edit an existing migration, see docs/testing.md. +[TestFixture] +class MigrationScriptApprovals +{ + [TestCase(null)] + [TestCase("custom_schema")] + public void Verify_migration_script_is_unchanged(string schema) + { + var settings = new PostgreSqlPersisterSettings + { + ConnectionString = "Host=unused", + Schema = schema, + BodyStorage = new FileSystemBodyStorageSettings { StoragePath = "unused" } + }; + + var services = new ServiceCollection(); + new PostgreSqlPersistenceConfiguration().Create(settings).AddInstaller(services); + + using var serviceProvider = services.BuildServiceProvider(); + using var scope = serviceProvider.CreateScope(); + var script = scope.ServiceProvider.GetRequiredService().GetService().GenerateScript(); + + Approver.Verify(script, MigrationScript.WithoutProductVersion, schema ?? "default_schema"); + } +} diff --git a/src/ServiceControl.Persistence.Tests.SqlServer/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.custom_schema.approved.txt b/src/ServiceControl.Persistence.Tests.SqlServer/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.custom_schema.approved.txt new file mode 100644 index 0000000000..1bcc0e4ee0 --- /dev/null +++ b/src/ServiceControl.Persistence.Tests.SqlServer/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.custom_schema.approved.txt @@ -0,0 +1,359 @@ +IF OBJECT_ID(N'[custom_schema].[__EFMigrationsHistory]') IS NULL +BEGIN + IF SCHEMA_ID(N'custom_schema') IS NULL EXEC(N'CREATE SCHEMA [custom_schema];'); + CREATE TABLE [custom_schema].[__EFMigrationsHistory] ( + [MigrationId] nvarchar(150) NOT NULL, + [ProductVersion] nvarchar(32) NOT NULL, + CONSTRAINT [PK___EFMigrationsHistory] PRIMARY KEY ([MigrationId]) + ); +END; +GO + +BEGIN TRANSACTION; +CREATE TABLE [custom_schema].[ArchiveOperations] ( + [RequestId] nvarchar(64) NOT NULL, + [ArchiveType] int NOT NULL, + [OperationType] int NOT NULL, + [GroupName] nvarchar(max) NOT NULL, + [TotalNumberOfMessages] int NOT NULL, + [NumberOfMessagesProcessed] int NOT NULL, + [NumberOfBatches] int NOT NULL, + [CurrentBatch] int NOT NULL, + [Started] datetime2 NOT NULL, + [InitiatedById] nvarchar(450) NULL, + [InitiatedByName] nvarchar(450) NULL, + [OperationId] nvarchar(450) NULL, + CONSTRAINT [PK_ArchiveOperations] PRIMARY KEY ([RequestId], [ArchiveType], [OperationType]) +); + +CREATE TABLE [custom_schema].[CustomChecks] ( + [Id] uniqueidentifier NOT NULL, + [CustomCheckId] nvarchar(max) NOT NULL, + [Category] nvarchar(max) NOT NULL, + [Status] int NOT NULL, + [ReportedAt] datetime2 NOT NULL, + [FailureReason] nvarchar(max) NULL, + [OriginatingEndpointName] nvarchar(max) NOT NULL, + [OriginatingEndpointHostId] uniqueidentifier NOT NULL, + [OriginatingEndpointHost] nvarchar(max) NOT NULL, + CONSTRAINT [PK_CustomChecks] PRIMARY KEY ([Id]) +); + +CREATE TABLE [custom_schema].[EndpointSettings] ( + [Name] nvarchar(450) NOT NULL, + [TrackInstances] bit NOT NULL, + CONSTRAINT [PK_EndpointSettings] PRIMARY KEY ([Name]) +); + +CREATE TABLE [custom_schema].[EventLogItems] ( + [Id] bigint NOT NULL IDENTITY, + [Description] nvarchar(max) NOT NULL, + [Severity] int NOT NULL, + [RaisedAt] datetime2 NOT NULL, + [RelatedTo] nvarchar(max) NOT NULL, + [Category] nvarchar(450) NOT NULL, + [EventType] nvarchar(450) NOT NULL, + CONSTRAINT [PK_EventLogItems] PRIMARY KEY ([Id]) +); + +CREATE TABLE [custom_schema].[ExternalIntegrationDispatchRequests] ( + [Id] bigint NOT NULL IDENTITY, + [DispatchContextTypeName] nvarchar(450) NOT NULL, + [DispatchContextJson] nvarchar(max) NOT NULL, + CONSTRAINT [PK_ExternalIntegrationDispatchRequests] PRIMARY KEY ([Id]) +); + +CREATE TABLE [custom_schema].[FailedErrorImports] ( + [UniqueMessageId] uniqueidentifier NOT NULL, + [FailedAt] datetime2 NOT NULL, + [MessageId] nvarchar(450) NOT NULL, + [HeadersJson] nvarchar(max) NOT NULL, + [Body] varbinary(max) NOT NULL, + [BodyStoredExternally] bit NOT NULL, + [ExceptionInfo] nvarchar(max) NOT NULL, + CONSTRAINT [PK_FailedErrorImports] PRIMARY KEY ([UniqueMessageId]) +); + +CREATE TABLE [custom_schema].[FailedMessageEdits] ( + [UniqueMessageId] uniqueidentifier NOT NULL, + [EditId] nvarchar(450) NOT NULL, + CONSTRAINT [PK_FailedMessageEdits] PRIMARY KEY ([UniqueMessageId]) +); + +CREATE TABLE [custom_schema].[FailedMessageRetries] ( + [UniqueMessageId] uniqueidentifier NOT NULL, + [RetryBatchId] uniqueidentifier NOT NULL, + [StageAttempts] int NOT NULL, + CONSTRAINT [PK_FailedMessageRetries] PRIMARY KEY ([UniqueMessageId]) +); + +CREATE TABLE [custom_schema].[FailedMessages] ( + [UniqueMessageId] uniqueidentifier NOT NULL, + [Status] int NOT NULL, + [StatusChangedAt] datetime2 NOT NULL, + [LastModified] datetime2 NOT NULL, + [NumberOfProcessingAttempts] int NOT NULL, + [FirstTimeOfFailure] datetime2 NOT NULL, + [LastTimeOfFailure] datetime2 NOT NULL, + [LastAttemptedAt] datetime2 NOT NULL, + [MessageId] nvarchar(450) NULL, + [MessageType] nvarchar(max) NULL, + [TimeSent] datetime2 NULL, + [ConversationId] nvarchar(450) NULL, + [SendingEndpointName] nvarchar(450) NULL, + [SendingEndpointHostId] uniqueidentifier NULL, + [SendingEndpointHost] nvarchar(450) NULL, + [ReceivingEndpointName] nvarchar(450) NULL, + [ReceivingEndpointHostId] uniqueidentifier NULL, + [ReceivingEndpointHost] nvarchar(450) NULL, + [ExceptionType] nvarchar(max) NULL, + [ExceptionMessage] nvarchar(max) NULL, + [IsSystemMessage] bit NOT NULL, + [HeadersJson] nvarchar(max) NOT NULL, + [BodyText] nvarchar(max) NULL, + [BodyStoredExternally] bit NOT NULL, + [BodySize] int NOT NULL, + [BodyContentType] nvarchar(450) NULL, + [FailingEndpointAddress] nvarchar(450) NOT NULL, + CONSTRAINT [PK_FailedMessages] PRIMARY KEY ([UniqueMessageId]) +); + +CREATE TABLE [custom_schema].[GroupComments] ( + [GroupId] nvarchar(64) NOT NULL, + [Comment] nvarchar(max) NOT NULL, + CONSTRAINT [PK_GroupComments] PRIMARY KEY ([GroupId]) +); + +CREATE TABLE [custom_schema].[HistoricRetryOperations] ( + [Id] bigint NOT NULL IDENTITY, + [RequestId] nvarchar(400) NOT NULL, + [RetryType] int NOT NULL, + [StartTime] datetime2 NOT NULL, + [CompletionTime] datetime2 NOT NULL, + [Originator] nvarchar(max) NULL, + [Failed] bit NOT NULL, + [NumberOfMessagesProcessed] int NOT NULL, + CONSTRAINT [PK_HistoricRetryOperations] PRIMARY KEY ([Id]) +); + +CREATE TABLE [custom_schema].[KnownEndpoints] ( + [Id] uniqueidentifier NOT NULL, + [Name] nvarchar(450) NOT NULL, + [HostId] uniqueidentifier NOT NULL, + [Host] nvarchar(450) NOT NULL, + [Monitored] bit NOT NULL, + CONSTRAINT [PK_KnownEndpoints] PRIMARY KEY ([Id]) +); + +CREATE TABLE [custom_schema].[LicensingEndpoints] ( + [NormalizedName] nvarchar(300) NOT NULL, + [ThroughputSource] int NOT NULL, + [Name] nvarchar(300) NOT NULL, + [SanitizedName] nvarchar(450) NOT NULL, + [NormalizedSanitizedName] nvarchar(450) NOT NULL, + [UserIndicator] nvarchar(450) NOT NULL, + [Scope] nvarchar(450) NULL, + [EndpointIndicators] nvarchar(max) NOT NULL, + CONSTRAINT [PK_LicensingEndpoints] PRIMARY KEY ([NormalizedName], [ThroughputSource]) +); + +CREATE TABLE [custom_schema].[MessageRedirects] ( + [FromPhysicalAddress] nvarchar(450) NOT NULL, + [ToPhysicalAddress] nvarchar(450) NOT NULL, + [LastModified] datetime2 NOT NULL, + CONSTRAINT [PK_MessageRedirects] PRIMARY KEY ([FromPhysicalAddress]) +); + +CREATE TABLE [custom_schema].[RetryBatches] ( + [Id] uniqueidentifier NOT NULL, + [Status] int NOT NULL, + [RetrySessionId] nvarchar(450) NOT NULL, + [RequestId] nvarchar(450) NOT NULL, + [RetryType] int NOT NULL, + [InitialBatchSize] int NOT NULL, + [StartTime] datetime2 NOT NULL, + [Last] datetime2 NULL, + [StagingId] nvarchar(450) NULL, + [Context] nvarchar(max) NULL, + [Originator] nvarchar(max) NULL, + [Classifier] nvarchar(max) NULL, + [InitiatedById] nvarchar(450) NULL, + [InitiatedByName] nvarchar(max) NULL, + [OperationId] nvarchar(450) NULL, + CONSTRAINT [PK_RetryBatches] PRIMARY KEY ([Id]) +); + +CREATE TABLE [custom_schema].[RetryBatchNowForwarding] ( + [Id] int NOT NULL, + [RetryBatchId] uniqueidentifier NOT NULL, + CONSTRAINT [PK_RetryBatchNowForwarding] PRIMARY KEY ([Id]) +); + +CREATE TABLE [custom_schema].[Settings] ( + [Key] nvarchar(450) NOT NULL, + [Value] nvarchar(max) NOT NULL, + CONSTRAINT [PK_Settings] PRIMARY KEY ([Key]) +); + +CREATE TABLE [custom_schema].[Subscriptions] ( + [MessageType] nvarchar(200) NOT NULL, + [TransportAddress] nvarchar(200) NOT NULL, + [Endpoint] nvarchar(450) NOT NULL, + CONSTRAINT [PK_Subscriptions] PRIMARY KEY ([MessageType], [TransportAddress]) +); + +CREATE TABLE [custom_schema].[UnacknowledgedRetryOperations] ( + [RequestId] nvarchar(400) NOT NULL, + [RetryType] int NOT NULL, + [StartTime] datetime2 NOT NULL, + [CompletionTime] datetime2 NOT NULL, + [Last] datetime2 NOT NULL, + [Originator] nvarchar(max) NULL, + [Classifier] nvarchar(450) NULL, + [Failed] bit NOT NULL, + [NumberOfMessagesProcessed] int NOT NULL, + CONSTRAINT [PK_UnacknowledgedRetryOperations] PRIMARY KEY ([RequestId], [RetryType]) +); + +CREATE TABLE [custom_schema].[FailedMessageGroups] ( + [FailedMessageUniqueId] uniqueidentifier NOT NULL, + [GroupId] nvarchar(64) NOT NULL, + [Title] nvarchar(max) NOT NULL, + [Type] nvarchar(255) NOT NULL, + CONSTRAINT [PK_FailedMessageGroups] PRIMARY KEY ([FailedMessageUniqueId], [GroupId]), + CONSTRAINT [FK_FailedMessageGroups_FailedMessages_FailedMessageUniqueId] FOREIGN KEY ([FailedMessageUniqueId]) REFERENCES [custom_schema].[FailedMessages] ([UniqueMessageId]) ON DELETE CASCADE +); + +CREATE TABLE [custom_schema].[LicensingEndpointThroughput] ( + [NormalizedName] nvarchar(300) NOT NULL, + [ThroughputSource] int NOT NULL, + [DateUtc] date NOT NULL, + [MessageCount] bigint NOT NULL, + CONSTRAINT [PK_LicensingEndpointThroughput] PRIMARY KEY ([NormalizedName], [ThroughputSource], [DateUtc]), + CONSTRAINT [FK_LicensingEndpointThroughput_LicensingEndpoints_NormalizedName_ThroughputSource] FOREIGN KEY ([NormalizedName], [ThroughputSource]) REFERENCES [custom_schema].[LicensingEndpoints] ([NormalizedName], [ThroughputSource]) ON DELETE CASCADE +); + +CREATE INDEX [IX_ArchiveOperations_Started] ON [custom_schema].[ArchiveOperations] ([Started]); + +CREATE INDEX [IX_CustomChecks_ReportedAt] ON [custom_schema].[CustomChecks] ([ReportedAt]); + +CREATE INDEX [IX_CustomChecks_Status_ReportedAt] ON [custom_schema].[CustomChecks] ([Status], [ReportedAt]); + +CREATE INDEX [IX_EventLogItems_RaisedAt_Id] ON [custom_schema].[EventLogItems] ([RaisedAt] DESC, [Id] DESC); + +CREATE INDEX [IX_FailedErrorImports_FailedAt] ON [custom_schema].[FailedErrorImports] ([FailedAt]); + +CREATE INDEX [IX_FailedMessageEdits_EditId] ON [custom_schema].[FailedMessageEdits] ([EditId]); + +CREATE INDEX [IX_FailedMessageGroups_GroupId] ON [custom_schema].[FailedMessageGroups] ([GroupId]); + +CREATE INDEX [IX_FailedMessageGroups_Type_GroupId] ON [custom_schema].[FailedMessageGroups] ([Type], [GroupId]); + +CREATE INDEX [IX_FailedMessageRetries_RetryBatchId] ON [custom_schema].[FailedMessageRetries] ([RetryBatchId]); + +CREATE INDEX [IX_FailedMessages_ConversationId] ON [custom_schema].[FailedMessages] ([ConversationId]); + +CREATE INDEX [IX_FailedMessages_FailingEndpointAddress] ON [custom_schema].[FailedMessages] ([FailingEndpointAddress]); + +CREATE INDEX [IX_FailedMessages_ReceivingEndpointName] ON [custom_schema].[FailedMessages] ([ReceivingEndpointName]); + +CREATE INDEX [IX_FailedMessages_Status_LastModified] ON [custom_schema].[FailedMessages] ([Status], [LastModified]); + +CREATE INDEX [IX_FailedMessages_StatusChangedAt] ON [custom_schema].[FailedMessages] ([StatusChangedAt]) WHERE [Status] IN (2, 4); + +CREATE INDEX [IX_FailedMessages_TimeSent] ON [custom_schema].[FailedMessages] ([TimeSent]); + +CREATE INDEX [IX_HistoricRetryOperations_CompletionTime_Id] ON [custom_schema].[HistoricRetryOperations] ([CompletionTime] DESC, [Id] DESC); + +CREATE INDEX [IX_LicensingEndpoints_NormalizedSanitizedName] ON [custom_schema].[LicensingEndpoints] ([NormalizedSanitizedName]); + +CREATE INDEX [IX_LicensingEndpointThroughput_DateUtc] ON [custom_schema].[LicensingEndpointThroughput] ([DateUtc]); + +CREATE INDEX [IX_RetryBatches_Status_RetrySessionId] ON [custom_schema].[RetryBatches] ([Status], [RetrySessionId]); + +INSERT INTO [custom_schema].[__EFMigrationsHistory] ([MigrationId], [ProductVersion]) +VALUES (N'20260902040428_Initial', N''); + +COMMIT; +GO + +IF SERVERPROPERTY('IsFullTextInstalled') <> 1 +BEGIN + THROW 50000, 'ServiceControl requires the SQL Server Full-Text Search feature, which is not installed on this instance. Install it and run setup again.', 1; +END +GO + +IF NOT EXISTS (SELECT 1 FROM sys.fulltext_catalogs WHERE name = 'ServiceControlFullTextCatalog') +BEGIN + BEGIN TRY + EXEC('CREATE FULLTEXT CATALOG ServiceControlFullTextCatalog'); + END TRY + BEGIN CATCH + IF NOT EXISTS (SELECT 1 FROM sys.fulltext_catalogs WHERE name = 'ServiceControlFullTextCatalog') THROW; + END CATCH +END +GO + +IF NOT EXISTS (SELECT 1 FROM sys.fulltext_indexes WHERE object_id = OBJECT_ID('[custom_schema].[FailedMessages]')) +BEGIN + EXEC('CREATE FULLTEXT INDEX ON [custom_schema].[FailedMessages](HeadersJson LANGUAGE 0, BodyText LANGUAGE 0) + KEY INDEX PK_FailedMessages + ON ServiceControlFullTextCatalog + WITH (CHANGE_TRACKING AUTO, STOPLIST = OFF)'); +END +GO + +INSERT INTO [custom_schema].[__EFMigrationsHistory] ([MigrationId], [ProductVersion]) +VALUES (N'20260902040432_AddFullTextSearch', N''); +GO + +BEGIN TRANSACTION; +DROP INDEX [IX_FailedMessages_Status_LastModified] ON [custom_schema].[FailedMessages]; + +DROP INDEX [IX_FailedMessageGroups_Type_GroupId] ON [custom_schema].[FailedMessageGroups]; + +DECLARE @var nvarchar(max); +SELECT @var = QUOTENAME([d].[name]) +FROM [sys].[default_constraints] [d] +INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] +WHERE ([d].[parent_object_id] = OBJECT_ID(N'[custom_schema].[FailedMessages]') AND [c].[name] = N'MessageType'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [custom_schema].[FailedMessages] DROP CONSTRAINT ' + @var + ';'); +ALTER TABLE [custom_schema].[FailedMessages] ALTER COLUMN [MessageType] nvarchar(450) NULL; + +CREATE INDEX [IX_FailedMessages_Status_LastModified] ON [custom_schema].[FailedMessages] ([Status], [LastModified]) INCLUDE ([FirstTimeOfFailure], [LastTimeOfFailure]); + +CREATE INDEX [IX_FailedMessages_Status_LastTimeOfFailure] ON [custom_schema].[FailedMessages] ([Status], [LastTimeOfFailure]); + +CREATE INDEX [IX_FailedMessages_Status_MessageType_UniqueMessageId] ON [custom_schema].[FailedMessages] ([Status], [MessageType], [UniqueMessageId]); + +CREATE INDEX [IX_FailedMessageGroups_Type_GroupId] ON [custom_schema].[FailedMessageGroups] ([Type], [GroupId]) INCLUDE ([Title]); + +INSERT INTO [custom_schema].[__EFMigrationsHistory] ([MigrationId], [ProductVersion]) +VALUES (N'20260911041253_AddMissingIndexes', N''); + +COMMIT; +GO + +BEGIN TRANSACTION; +DECLARE @var1 nvarchar(max); +SELECT @var1 = QUOTENAME([d].[name]) +FROM [sys].[default_constraints] [d] +INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] +WHERE ([d].[parent_object_id] = OBJECT_ID(N'[custom_schema].[FailedMessages]') AND [c].[name] = N'MessageId'); +IF @var1 IS NOT NULL EXEC(N'ALTER TABLE [custom_schema].[FailedMessages] DROP CONSTRAINT ' + @var1 + ';'); +ALTER TABLE [custom_schema].[FailedMessages] ALTER COLUMN [MessageId] nvarchar(max) NULL; + +DECLARE @var2 nvarchar(max); +SELECT @var2 = QUOTENAME([d].[name]) +FROM [sys].[default_constraints] [d] +INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] +WHERE ([d].[parent_object_id] = OBJECT_ID(N'[custom_schema].[FailedErrorImports]') AND [c].[name] = N'MessageId'); +IF @var2 IS NOT NULL EXEC(N'ALTER TABLE [custom_schema].[FailedErrorImports] DROP CONSTRAINT ' + @var2 + ';'); +ALTER TABLE [custom_schema].[FailedErrorImports] ALTER COLUMN [MessageId] nvarchar(max) NOT NULL; + +INSERT INTO [custom_schema].[__EFMigrationsHistory] ([MigrationId], [ProductVersion]) +VALUES (N'20260928064709_WidenMessageId', N''); + +COMMIT; +GO + diff --git a/src/ServiceControl.Persistence.Tests.SqlServer/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.default_schema.approved.txt b/src/ServiceControl.Persistence.Tests.SqlServer/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.default_schema.approved.txt new file mode 100644 index 0000000000..ad260eb8f2 --- /dev/null +++ b/src/ServiceControl.Persistence.Tests.SqlServer/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.default_schema.approved.txt @@ -0,0 +1,358 @@ +IF OBJECT_ID(N'[__EFMigrationsHistory]') IS NULL +BEGIN + CREATE TABLE [__EFMigrationsHistory] ( + [MigrationId] nvarchar(150) NOT NULL, + [ProductVersion] nvarchar(32) NOT NULL, + CONSTRAINT [PK___EFMigrationsHistory] PRIMARY KEY ([MigrationId]) + ); +END; +GO + +BEGIN TRANSACTION; +CREATE TABLE [ArchiveOperations] ( + [RequestId] nvarchar(64) NOT NULL, + [ArchiveType] int NOT NULL, + [OperationType] int NOT NULL, + [GroupName] nvarchar(max) NOT NULL, + [TotalNumberOfMessages] int NOT NULL, + [NumberOfMessagesProcessed] int NOT NULL, + [NumberOfBatches] int NOT NULL, + [CurrentBatch] int NOT NULL, + [Started] datetime2 NOT NULL, + [InitiatedById] nvarchar(450) NULL, + [InitiatedByName] nvarchar(450) NULL, + [OperationId] nvarchar(450) NULL, + CONSTRAINT [PK_ArchiveOperations] PRIMARY KEY ([RequestId], [ArchiveType], [OperationType]) +); + +CREATE TABLE [CustomChecks] ( + [Id] uniqueidentifier NOT NULL, + [CustomCheckId] nvarchar(max) NOT NULL, + [Category] nvarchar(max) NOT NULL, + [Status] int NOT NULL, + [ReportedAt] datetime2 NOT NULL, + [FailureReason] nvarchar(max) NULL, + [OriginatingEndpointName] nvarchar(max) NOT NULL, + [OriginatingEndpointHostId] uniqueidentifier NOT NULL, + [OriginatingEndpointHost] nvarchar(max) NOT NULL, + CONSTRAINT [PK_CustomChecks] PRIMARY KEY ([Id]) +); + +CREATE TABLE [EndpointSettings] ( + [Name] nvarchar(450) NOT NULL, + [TrackInstances] bit NOT NULL, + CONSTRAINT [PK_EndpointSettings] PRIMARY KEY ([Name]) +); + +CREATE TABLE [EventLogItems] ( + [Id] bigint NOT NULL IDENTITY, + [Description] nvarchar(max) NOT NULL, + [Severity] int NOT NULL, + [RaisedAt] datetime2 NOT NULL, + [RelatedTo] nvarchar(max) NOT NULL, + [Category] nvarchar(450) NOT NULL, + [EventType] nvarchar(450) NOT NULL, + CONSTRAINT [PK_EventLogItems] PRIMARY KEY ([Id]) +); + +CREATE TABLE [ExternalIntegrationDispatchRequests] ( + [Id] bigint NOT NULL IDENTITY, + [DispatchContextTypeName] nvarchar(450) NOT NULL, + [DispatchContextJson] nvarchar(max) NOT NULL, + CONSTRAINT [PK_ExternalIntegrationDispatchRequests] PRIMARY KEY ([Id]) +); + +CREATE TABLE [FailedErrorImports] ( + [UniqueMessageId] uniqueidentifier NOT NULL, + [FailedAt] datetime2 NOT NULL, + [MessageId] nvarchar(450) NOT NULL, + [HeadersJson] nvarchar(max) NOT NULL, + [Body] varbinary(max) NOT NULL, + [BodyStoredExternally] bit NOT NULL, + [ExceptionInfo] nvarchar(max) NOT NULL, + CONSTRAINT [PK_FailedErrorImports] PRIMARY KEY ([UniqueMessageId]) +); + +CREATE TABLE [FailedMessageEdits] ( + [UniqueMessageId] uniqueidentifier NOT NULL, + [EditId] nvarchar(450) NOT NULL, + CONSTRAINT [PK_FailedMessageEdits] PRIMARY KEY ([UniqueMessageId]) +); + +CREATE TABLE [FailedMessageRetries] ( + [UniqueMessageId] uniqueidentifier NOT NULL, + [RetryBatchId] uniqueidentifier NOT NULL, + [StageAttempts] int NOT NULL, + CONSTRAINT [PK_FailedMessageRetries] PRIMARY KEY ([UniqueMessageId]) +); + +CREATE TABLE [FailedMessages] ( + [UniqueMessageId] uniqueidentifier NOT NULL, + [Status] int NOT NULL, + [StatusChangedAt] datetime2 NOT NULL, + [LastModified] datetime2 NOT NULL, + [NumberOfProcessingAttempts] int NOT NULL, + [FirstTimeOfFailure] datetime2 NOT NULL, + [LastTimeOfFailure] datetime2 NOT NULL, + [LastAttemptedAt] datetime2 NOT NULL, + [MessageId] nvarchar(450) NULL, + [MessageType] nvarchar(max) NULL, + [TimeSent] datetime2 NULL, + [ConversationId] nvarchar(450) NULL, + [SendingEndpointName] nvarchar(450) NULL, + [SendingEndpointHostId] uniqueidentifier NULL, + [SendingEndpointHost] nvarchar(450) NULL, + [ReceivingEndpointName] nvarchar(450) NULL, + [ReceivingEndpointHostId] uniqueidentifier NULL, + [ReceivingEndpointHost] nvarchar(450) NULL, + [ExceptionType] nvarchar(max) NULL, + [ExceptionMessage] nvarchar(max) NULL, + [IsSystemMessage] bit NOT NULL, + [HeadersJson] nvarchar(max) NOT NULL, + [BodyText] nvarchar(max) NULL, + [BodyStoredExternally] bit NOT NULL, + [BodySize] int NOT NULL, + [BodyContentType] nvarchar(450) NULL, + [FailingEndpointAddress] nvarchar(450) NOT NULL, + CONSTRAINT [PK_FailedMessages] PRIMARY KEY ([UniqueMessageId]) +); + +CREATE TABLE [GroupComments] ( + [GroupId] nvarchar(64) NOT NULL, + [Comment] nvarchar(max) NOT NULL, + CONSTRAINT [PK_GroupComments] PRIMARY KEY ([GroupId]) +); + +CREATE TABLE [HistoricRetryOperations] ( + [Id] bigint NOT NULL IDENTITY, + [RequestId] nvarchar(400) NOT NULL, + [RetryType] int NOT NULL, + [StartTime] datetime2 NOT NULL, + [CompletionTime] datetime2 NOT NULL, + [Originator] nvarchar(max) NULL, + [Failed] bit NOT NULL, + [NumberOfMessagesProcessed] int NOT NULL, + CONSTRAINT [PK_HistoricRetryOperations] PRIMARY KEY ([Id]) +); + +CREATE TABLE [KnownEndpoints] ( + [Id] uniqueidentifier NOT NULL, + [Name] nvarchar(450) NOT NULL, + [HostId] uniqueidentifier NOT NULL, + [Host] nvarchar(450) NOT NULL, + [Monitored] bit NOT NULL, + CONSTRAINT [PK_KnownEndpoints] PRIMARY KEY ([Id]) +); + +CREATE TABLE [LicensingEndpoints] ( + [NormalizedName] nvarchar(300) NOT NULL, + [ThroughputSource] int NOT NULL, + [Name] nvarchar(300) NOT NULL, + [SanitizedName] nvarchar(450) NOT NULL, + [NormalizedSanitizedName] nvarchar(450) NOT NULL, + [UserIndicator] nvarchar(450) NOT NULL, + [Scope] nvarchar(450) NULL, + [EndpointIndicators] nvarchar(max) NOT NULL, + CONSTRAINT [PK_LicensingEndpoints] PRIMARY KEY ([NormalizedName], [ThroughputSource]) +); + +CREATE TABLE [MessageRedirects] ( + [FromPhysicalAddress] nvarchar(450) NOT NULL, + [ToPhysicalAddress] nvarchar(450) NOT NULL, + [LastModified] datetime2 NOT NULL, + CONSTRAINT [PK_MessageRedirects] PRIMARY KEY ([FromPhysicalAddress]) +); + +CREATE TABLE [RetryBatches] ( + [Id] uniqueidentifier NOT NULL, + [Status] int NOT NULL, + [RetrySessionId] nvarchar(450) NOT NULL, + [RequestId] nvarchar(450) NOT NULL, + [RetryType] int NOT NULL, + [InitialBatchSize] int NOT NULL, + [StartTime] datetime2 NOT NULL, + [Last] datetime2 NULL, + [StagingId] nvarchar(450) NULL, + [Context] nvarchar(max) NULL, + [Originator] nvarchar(max) NULL, + [Classifier] nvarchar(max) NULL, + [InitiatedById] nvarchar(450) NULL, + [InitiatedByName] nvarchar(max) NULL, + [OperationId] nvarchar(450) NULL, + CONSTRAINT [PK_RetryBatches] PRIMARY KEY ([Id]) +); + +CREATE TABLE [RetryBatchNowForwarding] ( + [Id] int NOT NULL, + [RetryBatchId] uniqueidentifier NOT NULL, + CONSTRAINT [PK_RetryBatchNowForwarding] PRIMARY KEY ([Id]) +); + +CREATE TABLE [Settings] ( + [Key] nvarchar(450) NOT NULL, + [Value] nvarchar(max) NOT NULL, + CONSTRAINT [PK_Settings] PRIMARY KEY ([Key]) +); + +CREATE TABLE [Subscriptions] ( + [MessageType] nvarchar(200) NOT NULL, + [TransportAddress] nvarchar(200) NOT NULL, + [Endpoint] nvarchar(450) NOT NULL, + CONSTRAINT [PK_Subscriptions] PRIMARY KEY ([MessageType], [TransportAddress]) +); + +CREATE TABLE [UnacknowledgedRetryOperations] ( + [RequestId] nvarchar(400) NOT NULL, + [RetryType] int NOT NULL, + [StartTime] datetime2 NOT NULL, + [CompletionTime] datetime2 NOT NULL, + [Last] datetime2 NOT NULL, + [Originator] nvarchar(max) NULL, + [Classifier] nvarchar(450) NULL, + [Failed] bit NOT NULL, + [NumberOfMessagesProcessed] int NOT NULL, + CONSTRAINT [PK_UnacknowledgedRetryOperations] PRIMARY KEY ([RequestId], [RetryType]) +); + +CREATE TABLE [FailedMessageGroups] ( + [FailedMessageUniqueId] uniqueidentifier NOT NULL, + [GroupId] nvarchar(64) NOT NULL, + [Title] nvarchar(max) NOT NULL, + [Type] nvarchar(255) NOT NULL, + CONSTRAINT [PK_FailedMessageGroups] PRIMARY KEY ([FailedMessageUniqueId], [GroupId]), + CONSTRAINT [FK_FailedMessageGroups_FailedMessages_FailedMessageUniqueId] FOREIGN KEY ([FailedMessageUniqueId]) REFERENCES [FailedMessages] ([UniqueMessageId]) ON DELETE CASCADE +); + +CREATE TABLE [LicensingEndpointThroughput] ( + [NormalizedName] nvarchar(300) NOT NULL, + [ThroughputSource] int NOT NULL, + [DateUtc] date NOT NULL, + [MessageCount] bigint NOT NULL, + CONSTRAINT [PK_LicensingEndpointThroughput] PRIMARY KEY ([NormalizedName], [ThroughputSource], [DateUtc]), + CONSTRAINT [FK_LicensingEndpointThroughput_LicensingEndpoints_NormalizedName_ThroughputSource] FOREIGN KEY ([NormalizedName], [ThroughputSource]) REFERENCES [LicensingEndpoints] ([NormalizedName], [ThroughputSource]) ON DELETE CASCADE +); + +CREATE INDEX [IX_ArchiveOperations_Started] ON [ArchiveOperations] ([Started]); + +CREATE INDEX [IX_CustomChecks_ReportedAt] ON [CustomChecks] ([ReportedAt]); + +CREATE INDEX [IX_CustomChecks_Status_ReportedAt] ON [CustomChecks] ([Status], [ReportedAt]); + +CREATE INDEX [IX_EventLogItems_RaisedAt_Id] ON [EventLogItems] ([RaisedAt] DESC, [Id] DESC); + +CREATE INDEX [IX_FailedErrorImports_FailedAt] ON [FailedErrorImports] ([FailedAt]); + +CREATE INDEX [IX_FailedMessageEdits_EditId] ON [FailedMessageEdits] ([EditId]); + +CREATE INDEX [IX_FailedMessageGroups_GroupId] ON [FailedMessageGroups] ([GroupId]); + +CREATE INDEX [IX_FailedMessageGroups_Type_GroupId] ON [FailedMessageGroups] ([Type], [GroupId]); + +CREATE INDEX [IX_FailedMessageRetries_RetryBatchId] ON [FailedMessageRetries] ([RetryBatchId]); + +CREATE INDEX [IX_FailedMessages_ConversationId] ON [FailedMessages] ([ConversationId]); + +CREATE INDEX [IX_FailedMessages_FailingEndpointAddress] ON [FailedMessages] ([FailingEndpointAddress]); + +CREATE INDEX [IX_FailedMessages_ReceivingEndpointName] ON [FailedMessages] ([ReceivingEndpointName]); + +CREATE INDEX [IX_FailedMessages_Status_LastModified] ON [FailedMessages] ([Status], [LastModified]); + +CREATE INDEX [IX_FailedMessages_StatusChangedAt] ON [FailedMessages] ([StatusChangedAt]) WHERE [Status] IN (2, 4); + +CREATE INDEX [IX_FailedMessages_TimeSent] ON [FailedMessages] ([TimeSent]); + +CREATE INDEX [IX_HistoricRetryOperations_CompletionTime_Id] ON [HistoricRetryOperations] ([CompletionTime] DESC, [Id] DESC); + +CREATE INDEX [IX_LicensingEndpoints_NormalizedSanitizedName] ON [LicensingEndpoints] ([NormalizedSanitizedName]); + +CREATE INDEX [IX_LicensingEndpointThroughput_DateUtc] ON [LicensingEndpointThroughput] ([DateUtc]); + +CREATE INDEX [IX_RetryBatches_Status_RetrySessionId] ON [RetryBatches] ([Status], [RetrySessionId]); + +INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion]) +VALUES (N'20260902040428_Initial', N''); + +COMMIT; +GO + +IF SERVERPROPERTY('IsFullTextInstalled') <> 1 +BEGIN + THROW 50000, 'ServiceControl requires the SQL Server Full-Text Search feature, which is not installed on this instance. Install it and run setup again.', 1; +END +GO + +IF NOT EXISTS (SELECT 1 FROM sys.fulltext_catalogs WHERE name = 'ServiceControlFullTextCatalog') +BEGIN + BEGIN TRY + EXEC('CREATE FULLTEXT CATALOG ServiceControlFullTextCatalog'); + END TRY + BEGIN CATCH + IF NOT EXISTS (SELECT 1 FROM sys.fulltext_catalogs WHERE name = 'ServiceControlFullTextCatalog') THROW; + END CATCH +END +GO + +IF NOT EXISTS (SELECT 1 FROM sys.fulltext_indexes WHERE object_id = OBJECT_ID('FailedMessages')) +BEGIN + EXEC('CREATE FULLTEXT INDEX ON FailedMessages(HeadersJson LANGUAGE 0, BodyText LANGUAGE 0) + KEY INDEX PK_FailedMessages + ON ServiceControlFullTextCatalog + WITH (CHANGE_TRACKING AUTO, STOPLIST = OFF)'); +END +GO + +INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion]) +VALUES (N'20260902040432_AddFullTextSearch', N''); +GO + +BEGIN TRANSACTION; +DROP INDEX [IX_FailedMessages_Status_LastModified] ON [FailedMessages]; + +DROP INDEX [IX_FailedMessageGroups_Type_GroupId] ON [FailedMessageGroups]; + +DECLARE @var nvarchar(max); +SELECT @var = QUOTENAME([d].[name]) +FROM [sys].[default_constraints] [d] +INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] +WHERE ([d].[parent_object_id] = OBJECT_ID(N'[FailedMessages]') AND [c].[name] = N'MessageType'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [FailedMessages] DROP CONSTRAINT ' + @var + ';'); +ALTER TABLE [FailedMessages] ALTER COLUMN [MessageType] nvarchar(450) NULL; + +CREATE INDEX [IX_FailedMessages_Status_LastModified] ON [FailedMessages] ([Status], [LastModified]) INCLUDE ([FirstTimeOfFailure], [LastTimeOfFailure]); + +CREATE INDEX [IX_FailedMessages_Status_LastTimeOfFailure] ON [FailedMessages] ([Status], [LastTimeOfFailure]); + +CREATE INDEX [IX_FailedMessages_Status_MessageType_UniqueMessageId] ON [FailedMessages] ([Status], [MessageType], [UniqueMessageId]); + +CREATE INDEX [IX_FailedMessageGroups_Type_GroupId] ON [FailedMessageGroups] ([Type], [GroupId]) INCLUDE ([Title]); + +INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion]) +VALUES (N'20260911041253_AddMissingIndexes', N''); + +COMMIT; +GO + +BEGIN TRANSACTION; +DECLARE @var1 nvarchar(max); +SELECT @var1 = QUOTENAME([d].[name]) +FROM [sys].[default_constraints] [d] +INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] +WHERE ([d].[parent_object_id] = OBJECT_ID(N'[FailedMessages]') AND [c].[name] = N'MessageId'); +IF @var1 IS NOT NULL EXEC(N'ALTER TABLE [FailedMessages] DROP CONSTRAINT ' + @var1 + ';'); +ALTER TABLE [FailedMessages] ALTER COLUMN [MessageId] nvarchar(max) NULL; + +DECLARE @var2 nvarchar(max); +SELECT @var2 = QUOTENAME([d].[name]) +FROM [sys].[default_constraints] [d] +INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] +WHERE ([d].[parent_object_id] = OBJECT_ID(N'[FailedErrorImports]') AND [c].[name] = N'MessageId'); +IF @var2 IS NOT NULL EXEC(N'ALTER TABLE [FailedErrorImports] DROP CONSTRAINT ' + @var2 + ';'); +ALTER TABLE [FailedErrorImports] ALTER COLUMN [MessageId] nvarchar(max) NOT NULL; + +INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion]) +VALUES (N'20260928064709_WidenMessageId', N''); + +COMMIT; +GO + diff --git a/src/ServiceControl.Persistence.Tests.SqlServer/MigrationScriptApprovals.cs b/src/ServiceControl.Persistence.Tests.SqlServer/MigrationScriptApprovals.cs new file mode 100644 index 0000000000..f775824079 --- /dev/null +++ b/src/ServiceControl.Persistence.Tests.SqlServer/MigrationScriptApprovals.cs @@ -0,0 +1,38 @@ +// ReSharper disable once CheckNamespace +namespace ServiceControl.Persistence.Tests; + +using EFCore.SqlServer; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.Extensions.DependencyInjection; +using NUnit.Framework; +using Particular.Approvals; +using ServiceControl.Persistence.EFCore.Abstractions; +using ServiceControl.Persistence.EFCore.DbContexts; +using TestHelper; + +// A changed index can block writes on a large table for a long time. Record it in the upgrade guide and never edit an existing migration, see docs/testing.md. +[TestFixture] +class MigrationScriptApprovals +{ + [TestCase(null)] + [TestCase("custom_schema")] + public void Verify_migration_script_is_unchanged(string schema) + { + var settings = new SqlServerPersisterSettings + { + ConnectionString = "Server=unused", + Schema = schema, + BodyStorage = new FileSystemBodyStorageSettings { StoragePath = "unused" } + }; + + var services = new ServiceCollection(); + new SqlServerPersistenceConfiguration().Create(settings).AddInstaller(services); + + using var serviceProvider = services.BuildServiceProvider(); + using var scope = serviceProvider.CreateScope(); + var script = scope.ServiceProvider.GetRequiredService().GetService().GenerateScript(); + + Approver.Verify(script, MigrationScript.WithoutProductVersion, schema ?? "default_schema"); + } +} diff --git a/src/TestHelper/MigrationScript.cs b/src/TestHelper/MigrationScript.cs new file mode 100644 index 0000000000..2adc6031fc --- /dev/null +++ b/src/TestHelper/MigrationScript.cs @@ -0,0 +1,15 @@ +namespace TestHelper; + +using System.Text.RegularExpressions; + +public static partial class MigrationScript +{ + /// + /// Replaces the EF Core product version in each migrations history row of a migration script with <ProductVersion>. + /// The version changes with every EF Core update, but the schema stays the same. + /// + public static string WithoutProductVersion(string script) => HistoryRowVersion().Replace(script, "${row}'"); + + [GeneratedRegex(@"(?VALUES \(N?'\d{14}_\w+', N?')[^']+'")] + private static partial Regex HistoryRowVersion(); +} From f7e4b16f4fe6787d7c79227ba1437975c11a04f6 Mon Sep 17 00:00:00 2001 From: Warwick Schroeder Date: Thu, 22 Oct 2026 12:11:55 +0800 Subject: [PATCH 2/3] Add tests for slow migration operations and remove obsolete migration scripts --- docs/testing.md | 2 +- ...pt_is_unchanged.custom_schema.approved.txt | 94 ----- ...t_is_unchanged.default_schema.approved.txt | 88 ----- ...tion_operations_are_unchanged.approved.txt | 3 + ....cs => SlowMigrationOperationApprovals.cs} | 18 +- ...pt_is_unchanged.custom_schema.approved.txt | 118 ------ ...t_is_unchanged.default_schema.approved.txt | 117 ------ ...tion_operations_are_unchanged.approved.txt | 24 ++ ....cs => SlowMigrationOperationApprovals.cs} | 18 +- ...pt_is_unchanged.custom_schema.approved.txt | 335 ---------------- ...t_is_unchanged.default_schema.approved.txt | 329 ---------------- ...tion_operations_are_unchanged.approved.txt | 23 ++ ....cs => SlowMigrationOperationApprovals.cs} | 15 +- ...pt_is_unchanged.custom_schema.approved.txt | 359 ------------------ ...t_is_unchanged.default_schema.approved.txt | 358 ----------------- ...tion_operations_are_unchanged.approved.txt | 35 ++ ....cs => SlowMigrationOperationApprovals.cs} | 15 +- src/TestHelper/MigrationScript.cs | 15 - src/TestHelper/SlowMigrationOperations.cs | 91 +++++ src/TestHelper/TestHelper.csproj | 2 + 20 files changed, 195 insertions(+), 1864 deletions(-) delete mode 100644 src/ServiceControl.Audit.Persistence.Tests.PostgreSql/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.custom_schema.approved.txt delete mode 100644 src/ServiceControl.Audit.Persistence.Tests.PostgreSql/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.default_schema.approved.txt create mode 100644 src/ServiceControl.Audit.Persistence.Tests.PostgreSql/ApprovalFiles/SlowMigrationOperationApprovals.Verify_slow_migration_operations_are_unchanged.approved.txt rename src/ServiceControl.Audit.Persistence.Tests.PostgreSql/{MigrationScriptApprovals.cs => SlowMigrationOperationApprovals.cs} (50%) delete mode 100644 src/ServiceControl.Audit.Persistence.Tests.SqlServer/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.custom_schema.approved.txt delete mode 100644 src/ServiceControl.Audit.Persistence.Tests.SqlServer/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.default_schema.approved.txt create mode 100644 src/ServiceControl.Audit.Persistence.Tests.SqlServer/ApprovalFiles/SlowMigrationOperationApprovals.Verify_slow_migration_operations_are_unchanged.approved.txt rename src/ServiceControl.Audit.Persistence.Tests.SqlServer/{MigrationScriptApprovals.cs => SlowMigrationOperationApprovals.cs} (50%) delete mode 100644 src/ServiceControl.Persistence.Tests.PostgreSql/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.custom_schema.approved.txt delete mode 100644 src/ServiceControl.Persistence.Tests.PostgreSql/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.default_schema.approved.txt create mode 100644 src/ServiceControl.Persistence.Tests.PostgreSql/ApprovalFiles/SlowMigrationOperationApprovals.Verify_slow_migration_operations_are_unchanged.approved.txt rename src/ServiceControl.Persistence.Tests.PostgreSql/{MigrationScriptApprovals.cs => SlowMigrationOperationApprovals.cs} (53%) delete mode 100644 src/ServiceControl.Persistence.Tests.SqlServer/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.custom_schema.approved.txt delete mode 100644 src/ServiceControl.Persistence.Tests.SqlServer/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.default_schema.approved.txt create mode 100644 src/ServiceControl.Persistence.Tests.SqlServer/ApprovalFiles/SlowMigrationOperationApprovals.Verify_slow_migration_operations_are_unchanged.approved.txt rename src/ServiceControl.Persistence.Tests.SqlServer/{MigrationScriptApprovals.cs => SlowMigrationOperationApprovals.cs} (53%) delete mode 100644 src/TestHelper/MigrationScript.cs create mode 100644 src/TestHelper/SlowMigrationOperations.cs diff --git a/docs/testing.md b/docs/testing.md index ecfb2e4f41..c2acbd0a57 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -27,7 +27,7 @@ On Windows, [PackagedRavenDBTests](../src/ServiceControlInstaller.Engine.UnitTes Persistence tests check assumptions at the persistence seam level by exercising each persister. For local setup details, see [Local testing of persistence providers](testing-persistence.md). -The SQL Server and PostgreSQL persistence test projects contain a `MigrationScriptApprovals` test. It approves the script EF Core generates from all of the persister's migrations, with and without a configured schema. A migration that creates, drops, or rebuilds an index on a large table can take a long time and block writes. When the test fails, record any index change in the upgrade guide of the next release before you approve the new script. If the script changed inside an existing migration, for example through a shared SQL constant, add a new migration instead. Databases that already ran the old migration never run it again. A package update that changes only the formatting of the script needs no upgrade guide entry. +The SQL Server and PostgreSQL persistence test projects contain a `SlowMigrationOperationApprovals` test. It approves the migration steps that can block writes on a large table: index, primary key, and unique constraint changes, altered columns, and raw SQL. The test lists these steps for each migration and leaves out steps on a table that the same migration creates, because that table is empty. When the test fails, record the change in the upgrade guide of the next release before you approve the new list. If a step changed inside an existing migration, for example through a shared SQL constant, add a new migration instead. Databases that already ran the old migration never run it again. ## Transport tests diff --git a/src/ServiceControl.Audit.Persistence.Tests.PostgreSql/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.custom_schema.approved.txt b/src/ServiceControl.Audit.Persistence.Tests.PostgreSql/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.custom_schema.approved.txt deleted file mode 100644 index 28c7abf906..0000000000 --- a/src/ServiceControl.Audit.Persistence.Tests.PostgreSql/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.custom_schema.approved.txt +++ /dev/null @@ -1,94 +0,0 @@ -DO $EF$ -BEGIN - IF NOT EXISTS(SELECT 1 FROM pg_namespace WHERE nspname = 'custom_schema') THEN - CREATE SCHEMA custom_schema; - END IF; -END $EF$; -CREATE TABLE IF NOT EXISTS custom_schema."__AuditMigrationsHistory" ( - migration_id character varying(150) NOT NULL, - product_version character varying(32) NOT NULL, - CONSTRAINT pk___audit_migrations_history PRIMARY KEY (migration_id) -); - -START TRANSACTION; -CREATE TABLE custom_schema.audit_messages ( - created_on timestamp with time zone NOT NULL, - id bigint GENERATED BY DEFAULT AS IDENTITY, - unique_message_id uuid NOT NULL, - message_id text, - message_type text, - time_sent timestamp with time zone, - processed_at timestamp with time zone NOT NULL, - conversation_id character varying(450), - is_system_message boolean NOT NULL, - status integer NOT NULL, - sending_endpoint_name text, - sending_endpoint_host_id uuid, - sending_endpoint_host text, - receiving_endpoint_name character varying(450), - receiving_endpoint_host_id uuid, - receiving_endpoint_host text, - critical_time_ticks bigint, - processing_time_ticks bigint, - delivery_time_ticks bigint, - headers_json text NOT NULL, - body_text text, - body_state integer NOT NULL, - body_size integer NOT NULL, - body_content_type text, - CONSTRAINT pk_audit_messages PRIMARY KEY (created_on, id) -) PARTITION BY RANGE (created_on); - -CREATE TABLE custom_schema.failed_audit_imports ( - unique_message_id uuid NOT NULL, - failed_at timestamp with time zone NOT NULL, - message_id text, - headers_json text NOT NULL, - body bytea NOT NULL, - exception_info text, - CONSTRAINT pk_failed_audit_imports PRIMARY KEY (unique_message_id) -); - -CREATE TABLE custom_schema.saga_snapshots ( - created_on timestamp with time zone NOT NULL, - id bigint GENERATED BY DEFAULT AS IDENTITY, - saga_id uuid NOT NULL, - saga_type text, - status integer NOT NULL, - start_time timestamp with time zone NOT NULL, - finish_time timestamp with time zone NOT NULL, - processed_at timestamp with time zone NOT NULL, - endpoint text, - state_after_change text, - initiating_message_json text, - outgoing_messages_json text, - CONSTRAINT pk_saga_snapshots PRIMARY KEY (created_on, id) -) PARTITION BY RANGE (created_on); - -CREATE INDEX ix_audit_messages_conversation_id ON custom_schema.audit_messages (conversation_id); - -CREATE INDEX ix_audit_messages_processed_at ON custom_schema.audit_messages (processed_at); - -CREATE INDEX ix_audit_messages_receiving_endpoint_name_created_on ON custom_schema.audit_messages (receiving_endpoint_name, created_on); - -CREATE INDEX ix_audit_messages_time_sent ON custom_schema.audit_messages (time_sent); - -CREATE INDEX ix_audit_messages_unique_message_id ON custom_schema.audit_messages (unique_message_id); - -CREATE INDEX ix_failed_audit_imports_failed_at_unique_message_id ON custom_schema.failed_audit_imports (failed_at, unique_message_id); - -CREATE INDEX ix_saga_snapshots_saga_id_finish_time ON custom_schema.saga_snapshots (saga_id, finish_time); - -INSERT INTO custom_schema."__AuditMigrationsHistory" (migration_id, product_version) -VALUES ('20260928032741_Initial', ''); - -COMMIT; - -START TRANSACTION; -CREATE INDEX ix_audit_messages_full_text ON "custom_schema".audit_messages USING GIN (to_tsvector('simple', headers_json || ' ' || substring(COALESCE(body_text, ''), 1, 262144) || ' ' || replace(replace(COALESCE(message_type, ''), '.', ' '), '+', ' '))) - -INSERT INTO custom_schema."__AuditMigrationsHistory" (migration_id, product_version) -VALUES ('20260928032745_AddFullTextSearch', ''); - -COMMIT; - diff --git a/src/ServiceControl.Audit.Persistence.Tests.PostgreSql/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.default_schema.approved.txt b/src/ServiceControl.Audit.Persistence.Tests.PostgreSql/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.default_schema.approved.txt deleted file mode 100644 index 2a40bc52fe..0000000000 --- a/src/ServiceControl.Audit.Persistence.Tests.PostgreSql/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.default_schema.approved.txt +++ /dev/null @@ -1,88 +0,0 @@ -CREATE TABLE IF NOT EXISTS "__AuditMigrationsHistory" ( - migration_id character varying(150) NOT NULL, - product_version character varying(32) NOT NULL, - CONSTRAINT pk___audit_migrations_history PRIMARY KEY (migration_id) -); - -START TRANSACTION; -CREATE TABLE audit_messages ( - created_on timestamp with time zone NOT NULL, - id bigint GENERATED BY DEFAULT AS IDENTITY, - unique_message_id uuid NOT NULL, - message_id text, - message_type text, - time_sent timestamp with time zone, - processed_at timestamp with time zone NOT NULL, - conversation_id character varying(450), - is_system_message boolean NOT NULL, - status integer NOT NULL, - sending_endpoint_name text, - sending_endpoint_host_id uuid, - sending_endpoint_host text, - receiving_endpoint_name character varying(450), - receiving_endpoint_host_id uuid, - receiving_endpoint_host text, - critical_time_ticks bigint, - processing_time_ticks bigint, - delivery_time_ticks bigint, - headers_json text NOT NULL, - body_text text, - body_state integer NOT NULL, - body_size integer NOT NULL, - body_content_type text, - CONSTRAINT pk_audit_messages PRIMARY KEY (created_on, id) -) PARTITION BY RANGE (created_on); - -CREATE TABLE failed_audit_imports ( - unique_message_id uuid NOT NULL, - failed_at timestamp with time zone NOT NULL, - message_id text, - headers_json text NOT NULL, - body bytea NOT NULL, - exception_info text, - CONSTRAINT pk_failed_audit_imports PRIMARY KEY (unique_message_id) -); - -CREATE TABLE saga_snapshots ( - created_on timestamp with time zone NOT NULL, - id bigint GENERATED BY DEFAULT AS IDENTITY, - saga_id uuid NOT NULL, - saga_type text, - status integer NOT NULL, - start_time timestamp with time zone NOT NULL, - finish_time timestamp with time zone NOT NULL, - processed_at timestamp with time zone NOT NULL, - endpoint text, - state_after_change text, - initiating_message_json text, - outgoing_messages_json text, - CONSTRAINT pk_saga_snapshots PRIMARY KEY (created_on, id) -) PARTITION BY RANGE (created_on); - -CREATE INDEX ix_audit_messages_conversation_id ON audit_messages (conversation_id); - -CREATE INDEX ix_audit_messages_processed_at ON audit_messages (processed_at); - -CREATE INDEX ix_audit_messages_receiving_endpoint_name_created_on ON audit_messages (receiving_endpoint_name, created_on); - -CREATE INDEX ix_audit_messages_time_sent ON audit_messages (time_sent); - -CREATE INDEX ix_audit_messages_unique_message_id ON audit_messages (unique_message_id); - -CREATE INDEX ix_failed_audit_imports_failed_at_unique_message_id ON failed_audit_imports (failed_at, unique_message_id); - -CREATE INDEX ix_saga_snapshots_saga_id_finish_time ON saga_snapshots (saga_id, finish_time); - -INSERT INTO "__AuditMigrationsHistory" (migration_id, product_version) -VALUES ('20260928032741_Initial', ''); - -COMMIT; - -START TRANSACTION; -CREATE INDEX ix_audit_messages_full_text ON audit_messages USING GIN (to_tsvector('simple', headers_json || ' ' || substring(COALESCE(body_text, ''), 1, 262144) || ' ' || replace(replace(COALESCE(message_type, ''), '.', ' '), '+', ' '))) - -INSERT INTO "__AuditMigrationsHistory" (migration_id, product_version) -VALUES ('20260928032745_AddFullTextSearch', ''); - -COMMIT; - diff --git a/src/ServiceControl.Audit.Persistence.Tests.PostgreSql/ApprovalFiles/SlowMigrationOperationApprovals.Verify_slow_migration_operations_are_unchanged.approved.txt b/src/ServiceControl.Audit.Persistence.Tests.PostgreSql/ApprovalFiles/SlowMigrationOperationApprovals.Verify_slow_migration_operations_are_unchanged.approved.txt new file mode 100644 index 0000000000..48a74dddcc --- /dev/null +++ b/src/ServiceControl.Audit.Persistence.Tests.PostgreSql/ApprovalFiles/SlowMigrationOperationApprovals.Verify_slow_migration_operations_are_unchanged.approved.txt @@ -0,0 +1,3 @@ +20260928032745_AddFullTextSearch + Sql + CREATE INDEX ix_audit_messages_full_text ON audit_messages USING GIN (to_tsvector('simple', headers_json || ' ' || substring(COALESCE(body_text, ''), 1, 262144) || ' ' || replace(replace(COALESCE(message_type, ''), '.', ' '), '+', ' '))) diff --git a/src/ServiceControl.Audit.Persistence.Tests.PostgreSql/MigrationScriptApprovals.cs b/src/ServiceControl.Audit.Persistence.Tests.PostgreSql/SlowMigrationOperationApprovals.cs similarity index 50% rename from src/ServiceControl.Audit.Persistence.Tests.PostgreSql/MigrationScriptApprovals.cs rename to src/ServiceControl.Audit.Persistence.Tests.PostgreSql/SlowMigrationOperationApprovals.cs index bc9404cea1..2119122cc9 100644 --- a/src/ServiceControl.Audit.Persistence.Tests.PostgreSql/MigrationScriptApprovals.cs +++ b/src/ServiceControl.Audit.Persistence.Tests.PostgreSql/SlowMigrationOperationApprovals.cs @@ -1,39 +1,29 @@ namespace ServiceControl.Audit.Persistence.Tests { using System; - using Microsoft.EntityFrameworkCore.Infrastructure; - using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; - using Particular.Approvals; using ServiceControl.Audit.Persistence.EFCore.Abstractions; using ServiceControl.Audit.Persistence.EFCore.DbContexts; using ServiceControl.Audit.Persistence.EFCore.PostgreSql; using TestHelper; - // A changed index can block writes on a large table for a long time. Record it in the upgrade guide and never edit an existing migration, see docs/testing.md. [TestFixture] - class MigrationScriptApprovals + class SlowMigrationOperationApprovals { - [TestCase(null)] - [TestCase("custom_schema")] - public void Verify_migration_script_is_unchanged(string schema) + [Test] + public void Verify_slow_migration_operations_are_unchanged() { var settings = new PersistenceSettings(TimeSpan.FromDays(1), true, 100000); settings.PersisterSpecificSettings[EFPersistenceConfigurationBase.ConnectionStringKey] = "Host=unused"; - if (schema is not null) - { - settings.PersisterSpecificSettings[EFPersistenceConfigurationBase.SchemaKey] = schema; - } var services = new ServiceCollection(); new PostgreSqlPersistenceConfiguration().Create(settings).AddInstaller(services); using var serviceProvider = services.BuildServiceProvider(); using var scope = serviceProvider.CreateScope(); - var script = scope.ServiceProvider.GetRequiredService().GetService().GenerateScript(); - Approver.Verify(script, MigrationScript.WithoutProductVersion, schema ?? "default_schema"); + SlowMigrationOperations.Verify(scope.ServiceProvider.GetRequiredService()); } } } diff --git a/src/ServiceControl.Audit.Persistence.Tests.SqlServer/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.custom_schema.approved.txt b/src/ServiceControl.Audit.Persistence.Tests.SqlServer/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.custom_schema.approved.txt deleted file mode 100644 index fd4c5902e6..0000000000 --- a/src/ServiceControl.Audit.Persistence.Tests.SqlServer/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.custom_schema.approved.txt +++ /dev/null @@ -1,118 +0,0 @@ -IF OBJECT_ID(N'[custom_schema].[__AuditMigrationsHistory]') IS NULL -BEGIN - IF SCHEMA_ID(N'custom_schema') IS NULL EXEC(N'CREATE SCHEMA [custom_schema];'); - CREATE TABLE [custom_schema].[__AuditMigrationsHistory] ( - [MigrationId] nvarchar(150) NOT NULL, - [ProductVersion] nvarchar(32) NOT NULL, - CONSTRAINT [PK___AuditMigrationsHistory] PRIMARY KEY ([MigrationId]) - ); -END; -GO - -BEGIN TRANSACTION; -CREATE TABLE [custom_schema].[AuditMessages] ( - [CreatedOn] datetime2 NOT NULL, - [Id] bigint NOT NULL IDENTITY, - [UniqueMessageId] uniqueidentifier NOT NULL, - [MessageId] nvarchar(max) NULL, - [MessageType] nvarchar(max) NULL, - [TimeSent] datetime2 NULL, - [ProcessedAt] datetime2 NOT NULL, - [ConversationId] nvarchar(450) NULL, - [IsSystemMessage] bit NOT NULL, - [Status] int NOT NULL, - [SendingEndpointName] nvarchar(max) NULL, - [SendingEndpointHostId] uniqueidentifier NULL, - [SendingEndpointHost] nvarchar(max) NULL, - [ReceivingEndpointName] nvarchar(450) NULL, - [ReceivingEndpointHostId] uniqueidentifier NULL, - [ReceivingEndpointHost] nvarchar(max) NULL, - [CriticalTimeTicks] bigint NULL, - [ProcessingTimeTicks] bigint NULL, - [DeliveryTimeTicks] bigint NULL, - [HeadersJson] nvarchar(max) NOT NULL, - [BodyText] nvarchar(max) NULL, - [BodyState] int NOT NULL, - [BodySize] int NOT NULL, - [BodyContentType] nvarchar(max) NULL, - CONSTRAINT [PK_AuditMessages] PRIMARY KEY ([CreatedOn], [Id]) -); - -CREATE TABLE [custom_schema].[FailedAuditImports] ( - [UniqueMessageId] uniqueidentifier NOT NULL, - [FailedAt] datetime2 NOT NULL, - [MessageId] nvarchar(max) NULL, - [HeadersJson] nvarchar(max) NOT NULL, - [Body] varbinary(max) NOT NULL, - [ExceptionInfo] nvarchar(max) NULL, - CONSTRAINT [PK_FailedAuditImports] PRIMARY KEY ([UniqueMessageId]) -); - -CREATE TABLE [custom_schema].[SagaSnapshots] ( - [CreatedOn] datetime2 NOT NULL, - [Id] bigint NOT NULL IDENTITY, - [SagaId] uniqueidentifier NOT NULL, - [SagaType] nvarchar(max) NULL, - [Status] int NOT NULL, - [StartTime] datetime2 NOT NULL, - [FinishTime] datetime2 NOT NULL, - [ProcessedAt] datetime2 NOT NULL, - [Endpoint] nvarchar(max) NULL, - [StateAfterChange] nvarchar(max) NULL, - [InitiatingMessageJson] nvarchar(max) NULL, - [OutgoingMessagesJson] nvarchar(max) NULL, - CONSTRAINT [PK_SagaSnapshots] PRIMARY KEY ([CreatedOn], [Id]) -); - -CREATE INDEX [IX_AuditMessages_ConversationId] ON [custom_schema].[AuditMessages] ([ConversationId]); - -CREATE UNIQUE INDEX [IX_AuditMessages_Id] ON [custom_schema].[AuditMessages] ([Id]); - -CREATE INDEX [IX_AuditMessages_ProcessedAt] ON [custom_schema].[AuditMessages] ([ProcessedAt]); - -CREATE INDEX [IX_AuditMessages_ReceivingEndpointName_CreatedOn] ON [custom_schema].[AuditMessages] ([ReceivingEndpointName], [CreatedOn]); - -CREATE INDEX [IX_AuditMessages_TimeSent] ON [custom_schema].[AuditMessages] ([TimeSent]); - -CREATE INDEX [IX_AuditMessages_UniqueMessageId] ON [custom_schema].[AuditMessages] ([UniqueMessageId]); - -CREATE INDEX [IX_FailedAuditImports_FailedAt_UniqueMessageId] ON [custom_schema].[FailedAuditImports] ([FailedAt], [UniqueMessageId]); - -CREATE INDEX [IX_SagaSnapshots_SagaId_FinishTime] ON [custom_schema].[SagaSnapshots] ([SagaId], [FinishTime]); - -INSERT INTO [custom_schema].[__AuditMigrationsHistory] ([MigrationId], [ProductVersion]) -VALUES (N'20260928032748_Initial', N''); - -COMMIT; -GO - -IF SERVERPROPERTY('IsFullTextInstalled') <> 1 -BEGIN - THROW 50000, 'ServiceControl requires the SQL Server Full-Text Search feature, which is not installed on this instance. Install it and run setup again.', 1; -END -GO - -IF NOT EXISTS (SELECT 1 FROM sys.fulltext_catalogs WHERE name = 'ServiceControlFullTextCatalog') -BEGIN - BEGIN TRY - EXEC('CREATE FULLTEXT CATALOG ServiceControlFullTextCatalog'); - END TRY - BEGIN CATCH - IF NOT EXISTS (SELECT 1 FROM sys.fulltext_catalogs WHERE name = 'ServiceControlFullTextCatalog') THROW; - END CATCH -END -GO - -IF NOT EXISTS (SELECT 1 FROM sys.fulltext_indexes WHERE object_id = OBJECT_ID('[custom_schema].[AuditMessages]')) -BEGIN - EXEC('CREATE FULLTEXT INDEX ON [custom_schema].[AuditMessages](HeadersJson LANGUAGE 0, BodyText LANGUAGE 0) - KEY INDEX IX_AuditMessages_Id - ON ServiceControlFullTextCatalog - WITH (CHANGE_TRACKING AUTO, STOPLIST = OFF)'); -END -GO - -INSERT INTO [custom_schema].[__AuditMigrationsHistory] ([MigrationId], [ProductVersion]) -VALUES (N'20260928032751_AddFullTextSearch', N''); -GO - diff --git a/src/ServiceControl.Audit.Persistence.Tests.SqlServer/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.default_schema.approved.txt b/src/ServiceControl.Audit.Persistence.Tests.SqlServer/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.default_schema.approved.txt deleted file mode 100644 index 7bef1a47e6..0000000000 --- a/src/ServiceControl.Audit.Persistence.Tests.SqlServer/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.default_schema.approved.txt +++ /dev/null @@ -1,117 +0,0 @@ -IF OBJECT_ID(N'[__AuditMigrationsHistory]') IS NULL -BEGIN - CREATE TABLE [__AuditMigrationsHistory] ( - [MigrationId] nvarchar(150) NOT NULL, - [ProductVersion] nvarchar(32) NOT NULL, - CONSTRAINT [PK___AuditMigrationsHistory] PRIMARY KEY ([MigrationId]) - ); -END; -GO - -BEGIN TRANSACTION; -CREATE TABLE [AuditMessages] ( - [CreatedOn] datetime2 NOT NULL, - [Id] bigint NOT NULL IDENTITY, - [UniqueMessageId] uniqueidentifier NOT NULL, - [MessageId] nvarchar(max) NULL, - [MessageType] nvarchar(max) NULL, - [TimeSent] datetime2 NULL, - [ProcessedAt] datetime2 NOT NULL, - [ConversationId] nvarchar(450) NULL, - [IsSystemMessage] bit NOT NULL, - [Status] int NOT NULL, - [SendingEndpointName] nvarchar(max) NULL, - [SendingEndpointHostId] uniqueidentifier NULL, - [SendingEndpointHost] nvarchar(max) NULL, - [ReceivingEndpointName] nvarchar(450) NULL, - [ReceivingEndpointHostId] uniqueidentifier NULL, - [ReceivingEndpointHost] nvarchar(max) NULL, - [CriticalTimeTicks] bigint NULL, - [ProcessingTimeTicks] bigint NULL, - [DeliveryTimeTicks] bigint NULL, - [HeadersJson] nvarchar(max) NOT NULL, - [BodyText] nvarchar(max) NULL, - [BodyState] int NOT NULL, - [BodySize] int NOT NULL, - [BodyContentType] nvarchar(max) NULL, - CONSTRAINT [PK_AuditMessages] PRIMARY KEY ([CreatedOn], [Id]) -); - -CREATE TABLE [FailedAuditImports] ( - [UniqueMessageId] uniqueidentifier NOT NULL, - [FailedAt] datetime2 NOT NULL, - [MessageId] nvarchar(max) NULL, - [HeadersJson] nvarchar(max) NOT NULL, - [Body] varbinary(max) NOT NULL, - [ExceptionInfo] nvarchar(max) NULL, - CONSTRAINT [PK_FailedAuditImports] PRIMARY KEY ([UniqueMessageId]) -); - -CREATE TABLE [SagaSnapshots] ( - [CreatedOn] datetime2 NOT NULL, - [Id] bigint NOT NULL IDENTITY, - [SagaId] uniqueidentifier NOT NULL, - [SagaType] nvarchar(max) NULL, - [Status] int NOT NULL, - [StartTime] datetime2 NOT NULL, - [FinishTime] datetime2 NOT NULL, - [ProcessedAt] datetime2 NOT NULL, - [Endpoint] nvarchar(max) NULL, - [StateAfterChange] nvarchar(max) NULL, - [InitiatingMessageJson] nvarchar(max) NULL, - [OutgoingMessagesJson] nvarchar(max) NULL, - CONSTRAINT [PK_SagaSnapshots] PRIMARY KEY ([CreatedOn], [Id]) -); - -CREATE INDEX [IX_AuditMessages_ConversationId] ON [AuditMessages] ([ConversationId]); - -CREATE UNIQUE INDEX [IX_AuditMessages_Id] ON [AuditMessages] ([Id]); - -CREATE INDEX [IX_AuditMessages_ProcessedAt] ON [AuditMessages] ([ProcessedAt]); - -CREATE INDEX [IX_AuditMessages_ReceivingEndpointName_CreatedOn] ON [AuditMessages] ([ReceivingEndpointName], [CreatedOn]); - -CREATE INDEX [IX_AuditMessages_TimeSent] ON [AuditMessages] ([TimeSent]); - -CREATE INDEX [IX_AuditMessages_UniqueMessageId] ON [AuditMessages] ([UniqueMessageId]); - -CREATE INDEX [IX_FailedAuditImports_FailedAt_UniqueMessageId] ON [FailedAuditImports] ([FailedAt], [UniqueMessageId]); - -CREATE INDEX [IX_SagaSnapshots_SagaId_FinishTime] ON [SagaSnapshots] ([SagaId], [FinishTime]); - -INSERT INTO [__AuditMigrationsHistory] ([MigrationId], [ProductVersion]) -VALUES (N'20260928032748_Initial', N''); - -COMMIT; -GO - -IF SERVERPROPERTY('IsFullTextInstalled') <> 1 -BEGIN - THROW 50000, 'ServiceControl requires the SQL Server Full-Text Search feature, which is not installed on this instance. Install it and run setup again.', 1; -END -GO - -IF NOT EXISTS (SELECT 1 FROM sys.fulltext_catalogs WHERE name = 'ServiceControlFullTextCatalog') -BEGIN - BEGIN TRY - EXEC('CREATE FULLTEXT CATALOG ServiceControlFullTextCatalog'); - END TRY - BEGIN CATCH - IF NOT EXISTS (SELECT 1 FROM sys.fulltext_catalogs WHERE name = 'ServiceControlFullTextCatalog') THROW; - END CATCH -END -GO - -IF NOT EXISTS (SELECT 1 FROM sys.fulltext_indexes WHERE object_id = OBJECT_ID('AuditMessages')) -BEGIN - EXEC('CREATE FULLTEXT INDEX ON AuditMessages(HeadersJson LANGUAGE 0, BodyText LANGUAGE 0) - KEY INDEX IX_AuditMessages_Id - ON ServiceControlFullTextCatalog - WITH (CHANGE_TRACKING AUTO, STOPLIST = OFF)'); -END -GO - -INSERT INTO [__AuditMigrationsHistory] ([MigrationId], [ProductVersion]) -VALUES (N'20260928032751_AddFullTextSearch', N''); -GO - diff --git a/src/ServiceControl.Audit.Persistence.Tests.SqlServer/ApprovalFiles/SlowMigrationOperationApprovals.Verify_slow_migration_operations_are_unchanged.approved.txt b/src/ServiceControl.Audit.Persistence.Tests.SqlServer/ApprovalFiles/SlowMigrationOperationApprovals.Verify_slow_migration_operations_are_unchanged.approved.txt new file mode 100644 index 0000000000..16e598b9a5 --- /dev/null +++ b/src/ServiceControl.Audit.Persistence.Tests.SqlServer/ApprovalFiles/SlowMigrationOperationApprovals.Verify_slow_migration_operations_are_unchanged.approved.txt @@ -0,0 +1,24 @@ +20260928032751_AddFullTextSearch + Sql + IF SERVERPROPERTY('IsFullTextInstalled') <> 1 + BEGIN + THROW 50000, 'ServiceControl requires the SQL Server Full-Text Search feature, which is not installed on this instance. Install it and run setup again.', 1; + END + Sql + IF NOT EXISTS (SELECT 1 FROM sys.fulltext_catalogs WHERE name = 'ServiceControlFullTextCatalog') + BEGIN + BEGIN TRY + EXEC('CREATE FULLTEXT CATALOG ServiceControlFullTextCatalog'); + END TRY + BEGIN CATCH + IF NOT EXISTS (SELECT 1 FROM sys.fulltext_catalogs WHERE name = 'ServiceControlFullTextCatalog') THROW; + END CATCH + END + Sql + IF NOT EXISTS (SELECT 1 FROM sys.fulltext_indexes WHERE object_id = OBJECT_ID('AuditMessages')) + BEGIN + EXEC('CREATE FULLTEXT INDEX ON AuditMessages(HeadersJson LANGUAGE 0, BodyText LANGUAGE 0) + KEY INDEX IX_AuditMessages_Id + ON ServiceControlFullTextCatalog + WITH (CHANGE_TRACKING AUTO, STOPLIST = OFF)'); + END diff --git a/src/ServiceControl.Audit.Persistence.Tests.SqlServer/MigrationScriptApprovals.cs b/src/ServiceControl.Audit.Persistence.Tests.SqlServer/SlowMigrationOperationApprovals.cs similarity index 50% rename from src/ServiceControl.Audit.Persistence.Tests.SqlServer/MigrationScriptApprovals.cs rename to src/ServiceControl.Audit.Persistence.Tests.SqlServer/SlowMigrationOperationApprovals.cs index 937cff594d..493c8c6a87 100644 --- a/src/ServiceControl.Audit.Persistence.Tests.SqlServer/MigrationScriptApprovals.cs +++ b/src/ServiceControl.Audit.Persistence.Tests.SqlServer/SlowMigrationOperationApprovals.cs @@ -1,39 +1,29 @@ namespace ServiceControl.Audit.Persistence.Tests { using System; - using Microsoft.EntityFrameworkCore.Infrastructure; - using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; - using Particular.Approvals; using ServiceControl.Audit.Persistence.EFCore.Abstractions; using ServiceControl.Audit.Persistence.EFCore.DbContexts; using ServiceControl.Audit.Persistence.EFCore.SqlServer; using TestHelper; - // A changed index can block writes on a large table for a long time. Record it in the upgrade guide and never edit an existing migration, see docs/testing.md. [TestFixture] - class MigrationScriptApprovals + class SlowMigrationOperationApprovals { - [TestCase(null)] - [TestCase("custom_schema")] - public void Verify_migration_script_is_unchanged(string schema) + [Test] + public void Verify_slow_migration_operations_are_unchanged() { var settings = new PersistenceSettings(TimeSpan.FromDays(1), true, 100000); settings.PersisterSpecificSettings[EFPersistenceConfigurationBase.ConnectionStringKey] = "Server=unused"; - if (schema is not null) - { - settings.PersisterSpecificSettings[EFPersistenceConfigurationBase.SchemaKey] = schema; - } var services = new ServiceCollection(); new SqlServerPersistenceConfiguration().Create(settings).AddInstaller(services); using var serviceProvider = services.BuildServiceProvider(); using var scope = serviceProvider.CreateScope(); - var script = scope.ServiceProvider.GetRequiredService().GetService().GenerateScript(); - Approver.Verify(script, MigrationScript.WithoutProductVersion, schema ?? "default_schema"); + SlowMigrationOperations.Verify(scope.ServiceProvider.GetRequiredService()); } } } diff --git a/src/ServiceControl.Persistence.Tests.PostgreSql/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.custom_schema.approved.txt b/src/ServiceControl.Persistence.Tests.PostgreSql/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.custom_schema.approved.txt deleted file mode 100644 index f432879bc8..0000000000 --- a/src/ServiceControl.Persistence.Tests.PostgreSql/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.custom_schema.approved.txt +++ /dev/null @@ -1,335 +0,0 @@ -DO $EF$ -BEGIN - IF NOT EXISTS(SELECT 1 FROM pg_namespace WHERE nspname = 'custom_schema') THEN - CREATE SCHEMA custom_schema; - END IF; -END $EF$; -CREATE TABLE IF NOT EXISTS custom_schema."__EFMigrationsHistory" ( - migration_id character varying(150) NOT NULL, - product_version character varying(32) NOT NULL, - CONSTRAINT pk___ef_migrations_history PRIMARY KEY (migration_id) -); - -START TRANSACTION; -CREATE TABLE custom_schema.archive_operations ( - request_id character varying(64) NOT NULL, - archive_type integer NOT NULL, - operation_type integer NOT NULL, - group_name text NOT NULL, - total_number_of_messages integer NOT NULL, - number_of_messages_processed integer NOT NULL, - number_of_batches integer NOT NULL, - current_batch integer NOT NULL, - started timestamp with time zone NOT NULL, - initiated_by_id character varying(450), - initiated_by_name character varying(450), - operation_id character varying(450), - CONSTRAINT pk_archive_operations PRIMARY KEY (request_id, archive_type, operation_type) -); - -CREATE TABLE custom_schema.custom_checks ( - id uuid NOT NULL, - custom_check_id text NOT NULL, - category text NOT NULL, - status integer NOT NULL, - reported_at timestamp with time zone NOT NULL, - failure_reason text, - originating_endpoint_name text NOT NULL, - originating_endpoint_host_id uuid NOT NULL, - originating_endpoint_host text NOT NULL, - CONSTRAINT pk_custom_checks PRIMARY KEY (id) -); - -CREATE TABLE custom_schema.endpoint_settings ( - name character varying(450) NOT NULL, - track_instances boolean NOT NULL, - CONSTRAINT pk_endpoint_settings PRIMARY KEY (name) -); - -CREATE TABLE custom_schema.event_log_items ( - id bigint GENERATED BY DEFAULT AS IDENTITY, - description text NOT NULL, - severity integer NOT NULL, - raised_at timestamp with time zone NOT NULL, - related_to text[] NOT NULL, - category character varying(450) NOT NULL, - event_type character varying(450) NOT NULL, - CONSTRAINT pk_event_log_items PRIMARY KEY (id) -); - -CREATE TABLE custom_schema.external_integration_dispatch_requests ( - id bigint GENERATED BY DEFAULT AS IDENTITY, - dispatch_context_type_name character varying(450) NOT NULL, - dispatch_context_json text NOT NULL, - CONSTRAINT pk_external_integration_dispatch_requests PRIMARY KEY (id) -); - -CREATE TABLE custom_schema.failed_error_imports ( - unique_message_id uuid NOT NULL, - failed_at timestamp with time zone NOT NULL, - message_id character varying(450) NOT NULL, - headers_json text NOT NULL, - body bytea NOT NULL, - body_stored_externally boolean NOT NULL, - exception_info text NOT NULL, - CONSTRAINT pk_failed_error_imports PRIMARY KEY (unique_message_id) -); - -CREATE TABLE custom_schema.failed_message_edits ( - unique_message_id uuid NOT NULL, - edit_id character varying(450) NOT NULL, - CONSTRAINT pk_failed_message_edits PRIMARY KEY (unique_message_id) -); - -CREATE TABLE custom_schema.failed_message_retries ( - unique_message_id uuid NOT NULL, - retry_batch_id uuid NOT NULL, - stage_attempts integer NOT NULL, - CONSTRAINT pk_failed_message_retries PRIMARY KEY (unique_message_id) -); - -CREATE TABLE custom_schema.failed_messages ( - unique_message_id uuid NOT NULL, - status integer NOT NULL, - status_changed_at timestamp with time zone NOT NULL, - last_modified timestamp with time zone NOT NULL, - number_of_processing_attempts integer NOT NULL, - first_time_of_failure timestamp with time zone NOT NULL, - last_time_of_failure timestamp with time zone NOT NULL, - last_attempted_at timestamp with time zone NOT NULL, - message_id character varying(450), - message_type text, - time_sent timestamp with time zone, - conversation_id character varying(450), - sending_endpoint_name character varying(450), - sending_endpoint_host_id uuid, - sending_endpoint_host character varying(450), - receiving_endpoint_name character varying(450), - receiving_endpoint_host_id uuid, - receiving_endpoint_host character varying(450), - exception_type text, - exception_message text, - is_system_message boolean NOT NULL, - headers_json text NOT NULL, - body_text text, - body_stored_externally boolean NOT NULL, - body_size integer NOT NULL, - body_content_type character varying(450), - failing_endpoint_address character varying(450) NOT NULL, - CONSTRAINT pk_failed_messages PRIMARY KEY (unique_message_id) -); - -CREATE TABLE custom_schema.group_comments ( - group_id character varying(64) NOT NULL, - comment text NOT NULL, - CONSTRAINT pk_group_comments PRIMARY KEY (group_id) -); - -CREATE TABLE custom_schema.historic_retry_operations ( - id bigint GENERATED BY DEFAULT AS IDENTITY, - request_id character varying(400) NOT NULL, - retry_type integer NOT NULL, - start_time timestamp with time zone NOT NULL, - completion_time timestamp with time zone NOT NULL, - originator text, - failed boolean NOT NULL, - number_of_messages_processed integer NOT NULL, - CONSTRAINT pk_historic_retry_operations PRIMARY KEY (id) -); - -CREATE TABLE custom_schema.known_endpoints ( - id uuid NOT NULL, - name character varying(450) NOT NULL, - host_id uuid NOT NULL, - host character varying(450) NOT NULL, - monitored boolean NOT NULL, - CONSTRAINT pk_known_endpoints PRIMARY KEY (id) -); - -CREATE TABLE custom_schema.licensing_endpoints ( - normalized_name character varying(300) NOT NULL, - throughput_source integer NOT NULL, - name character varying(300) NOT NULL, - sanitized_name character varying(450) NOT NULL, - normalized_sanitized_name character varying(450) NOT NULL, - user_indicator character varying(450) NOT NULL, - scope character varying(450), - endpoint_indicators text[] NOT NULL, - CONSTRAINT pk_licensing_endpoints PRIMARY KEY (normalized_name, throughput_source) -); - -CREATE TABLE custom_schema.message_redirects ( - from_physical_address character varying(450) NOT NULL, - to_physical_address character varying(450) NOT NULL, - last_modified timestamp with time zone NOT NULL, - CONSTRAINT pk_message_redirects PRIMARY KEY (from_physical_address) -); - -CREATE TABLE custom_schema.retry_batch_now_forwarding ( - id integer NOT NULL, - retry_batch_id uuid NOT NULL, - CONSTRAINT pk_retry_batch_now_forwarding PRIMARY KEY (id) -); - -CREATE TABLE custom_schema.retry_batches ( - id uuid NOT NULL, - status integer NOT NULL, - retry_session_id character varying(450) NOT NULL, - request_id character varying(450) NOT NULL, - retry_type integer NOT NULL, - initial_batch_size integer NOT NULL, - start_time timestamp with time zone NOT NULL, - last timestamp with time zone, - staging_id character varying(450), - context text, - originator text, - classifier text, - initiated_by_id character varying(450), - initiated_by_name text, - operation_id character varying(450), - CONSTRAINT pk_retry_batches PRIMARY KEY (id) -); - -CREATE TABLE custom_schema.settings ( - key character varying(450) NOT NULL, - value text NOT NULL, - CONSTRAINT pk_settings PRIMARY KEY (key) -); - -CREATE TABLE custom_schema.subscriptions ( - message_type character varying(200) NOT NULL, - transport_address character varying(200) NOT NULL, - endpoint character varying(450) NOT NULL, - CONSTRAINT pk_subscriptions PRIMARY KEY (message_type, transport_address) -); - -CREATE TABLE custom_schema.unacknowledged_retry_operations ( - request_id character varying(400) NOT NULL, - retry_type integer NOT NULL, - start_time timestamp with time zone NOT NULL, - completion_time timestamp with time zone NOT NULL, - last timestamp with time zone NOT NULL, - originator text, - classifier character varying(450), - failed boolean NOT NULL, - number_of_messages_processed integer NOT NULL, - CONSTRAINT pk_unacknowledged_retry_operations PRIMARY KEY (request_id, retry_type) -); - -CREATE TABLE custom_schema.failed_message_groups ( - failed_message_unique_id uuid NOT NULL, - group_id character varying(64) NOT NULL, - title text NOT NULL, - type character varying(255) NOT NULL, - CONSTRAINT pk_failed_message_groups PRIMARY KEY (failed_message_unique_id, group_id), - CONSTRAINT fk_failed_message_groups_failed_messages_failed_message_unique FOREIGN KEY (failed_message_unique_id) REFERENCES custom_schema.failed_messages (unique_message_id) ON DELETE CASCADE -); - -CREATE TABLE custom_schema.licensing_endpoint_throughput ( - normalized_name character varying(300) NOT NULL, - throughput_source integer NOT NULL, - date_utc date NOT NULL, - message_count bigint NOT NULL, - CONSTRAINT pk_licensing_endpoint_throughput PRIMARY KEY (normalized_name, throughput_source, date_utc), - CONSTRAINT fk_licensing_endpoint_throughput_licensing_endpoints_normalize FOREIGN KEY (normalized_name, throughput_source) REFERENCES custom_schema.licensing_endpoints (normalized_name, throughput_source) ON DELETE CASCADE -); - -CREATE INDEX ix_archive_operations_started ON custom_schema.archive_operations (started); - -CREATE INDEX ix_custom_checks_reported_at ON custom_schema.custom_checks (reported_at); - -CREATE INDEX ix_custom_checks_status_reported_at ON custom_schema.custom_checks (status, reported_at); - -CREATE INDEX ix_event_log_items_raised_at_id ON custom_schema.event_log_items (raised_at DESC, id DESC); - -CREATE INDEX ix_failed_error_imports_failed_at ON custom_schema.failed_error_imports (failed_at); - -CREATE INDEX ix_failed_message_edits_edit_id ON custom_schema.failed_message_edits (edit_id); - -CREATE INDEX ix_failed_message_groups_group_id ON custom_schema.failed_message_groups (group_id); - -CREATE INDEX ix_failed_message_groups_type_group_id ON custom_schema.failed_message_groups (type, group_id); - -CREATE INDEX ix_failed_message_retries_retry_batch_id ON custom_schema.failed_message_retries (retry_batch_id); - -CREATE INDEX ix_failed_messages_conversation_id ON custom_schema.failed_messages (conversation_id); - -CREATE INDEX ix_failed_messages_failing_endpoint_address ON custom_schema.failed_messages (failing_endpoint_address); - -CREATE INDEX ix_failed_messages_receiving_endpoint_name ON custom_schema.failed_messages (receiving_endpoint_name); - -CREATE INDEX ix_failed_messages_status_changed_at ON custom_schema.failed_messages (status_changed_at) WHERE status IN (2, 4); - -CREATE INDEX ix_failed_messages_status_last_modified ON custom_schema.failed_messages (status, last_modified); - -CREATE INDEX ix_failed_messages_time_sent ON custom_schema.failed_messages (time_sent); - -CREATE INDEX ix_historic_retry_operations_completion_time_id ON custom_schema.historic_retry_operations (completion_time DESC, id DESC); - -CREATE INDEX ix_licensing_endpoint_throughput_date_utc ON custom_schema.licensing_endpoint_throughput (date_utc); - -CREATE INDEX ix_licensing_endpoints_normalized_sanitized_name ON custom_schema.licensing_endpoints (normalized_sanitized_name); - -CREATE INDEX ix_retry_batches_status_retry_session_id ON custom_schema.retry_batches (status, retry_session_id); - -INSERT INTO custom_schema."__EFMigrationsHistory" (migration_id, product_version) -VALUES ('20260902040418_Initial', ''); - -COMMIT; - -START TRANSACTION; -CREATE INDEX ix_failed_messages_full_text ON "custom_schema".failed_messages USING GIN (to_tsvector('simple', headers_json || ' ' || COALESCE(body_text, '') || ' ' || replace(replace(COALESCE(message_type, ''), '.', ' '), '+', ' '))) - -INSERT INTO custom_schema."__EFMigrationsHistory" (migration_id, product_version) -VALUES ('20260902040422_AddFullTextSearch', ''); - -COMMIT; - -START TRANSACTION; -DROP INDEX custom_schema.ix_failed_messages_status_last_modified; - -DROP INDEX custom_schema.ix_failed_message_groups_type_group_id; - -ALTER TABLE custom_schema.failed_messages ALTER COLUMN message_type TYPE character varying(450); - -CREATE INDEX ix_failed_messages_status_last_modified ON custom_schema.failed_messages (status, last_modified) INCLUDE (first_time_of_failure, last_time_of_failure); - -CREATE INDEX ix_failed_messages_status_last_time_of_failure ON custom_schema.failed_messages (status, last_time_of_failure); - -CREATE INDEX ix_failed_messages_status_message_type_unique_message_id ON custom_schema.failed_messages (status, message_type, unique_message_id); - -CREATE INDEX ix_failed_message_groups_type_group_id ON custom_schema.failed_message_groups (type, group_id) INCLUDE (title); - -INSERT INTO custom_schema."__EFMigrationsHistory" (migration_id, product_version) -VALUES ('20260911041328_AddMissingIndexes', ''); - -COMMIT; - -START TRANSACTION; -DROP INDEX custom_schema.ix_failed_messages_status_last_modified; - -DROP INDEX custom_schema.ix_failed_message_groups_type_group_id; - -CREATE INDEX ix_failed_messages_status_last_modified_unique_message_id ON custom_schema.failed_messages (status, last_modified, unique_message_id) INCLUDE (first_time_of_failure, last_time_of_failure); - -CREATE INDEX ix_failed_message_groups_type_group_id ON custom_schema.failed_message_groups (type, group_id) INCLUDE (failed_message_unique_id, title); - -INSERT INTO custom_schema."__EFMigrationsHistory" (migration_id, product_version) -VALUES ('20260915050538_AddAdditionalIndexCoverage', ''); - -COMMIT; - -START TRANSACTION; -ALTER TABLE custom_schema.failed_messages ALTER COLUMN message_id TYPE text; - -ALTER TABLE custom_schema.failed_error_imports ALTER COLUMN message_id TYPE text; - -DROP INDEX IF EXISTS "custom_schema".ix_failed_messages_full_text - -CREATE INDEX ix_failed_messages_full_text ON "custom_schema".failed_messages USING GIN (to_tsvector('simple', headers_json || ' ' || substring(COALESCE(body_text, ''), 1, 262144) || ' ' || replace(replace(COALESCE(message_type, ''), '.', ' '), '+', ' '))) - -INSERT INTO custom_schema."__EFMigrationsHistory" (migration_id, product_version) -VALUES ('20260928064706_WidenMessageIdAndCapFullTextBody', ''); - -COMMIT; - diff --git a/src/ServiceControl.Persistence.Tests.PostgreSql/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.default_schema.approved.txt b/src/ServiceControl.Persistence.Tests.PostgreSql/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.default_schema.approved.txt deleted file mode 100644 index bfaa7b5f58..0000000000 --- a/src/ServiceControl.Persistence.Tests.PostgreSql/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.default_schema.approved.txt +++ /dev/null @@ -1,329 +0,0 @@ -CREATE TABLE IF NOT EXISTS "__EFMigrationsHistory" ( - migration_id character varying(150) NOT NULL, - product_version character varying(32) NOT NULL, - CONSTRAINT pk___ef_migrations_history PRIMARY KEY (migration_id) -); - -START TRANSACTION; -CREATE TABLE archive_operations ( - request_id character varying(64) NOT NULL, - archive_type integer NOT NULL, - operation_type integer NOT NULL, - group_name text NOT NULL, - total_number_of_messages integer NOT NULL, - number_of_messages_processed integer NOT NULL, - number_of_batches integer NOT NULL, - current_batch integer NOT NULL, - started timestamp with time zone NOT NULL, - initiated_by_id character varying(450), - initiated_by_name character varying(450), - operation_id character varying(450), - CONSTRAINT pk_archive_operations PRIMARY KEY (request_id, archive_type, operation_type) -); - -CREATE TABLE custom_checks ( - id uuid NOT NULL, - custom_check_id text NOT NULL, - category text NOT NULL, - status integer NOT NULL, - reported_at timestamp with time zone NOT NULL, - failure_reason text, - originating_endpoint_name text NOT NULL, - originating_endpoint_host_id uuid NOT NULL, - originating_endpoint_host text NOT NULL, - CONSTRAINT pk_custom_checks PRIMARY KEY (id) -); - -CREATE TABLE endpoint_settings ( - name character varying(450) NOT NULL, - track_instances boolean NOT NULL, - CONSTRAINT pk_endpoint_settings PRIMARY KEY (name) -); - -CREATE TABLE event_log_items ( - id bigint GENERATED BY DEFAULT AS IDENTITY, - description text NOT NULL, - severity integer NOT NULL, - raised_at timestamp with time zone NOT NULL, - related_to text[] NOT NULL, - category character varying(450) NOT NULL, - event_type character varying(450) NOT NULL, - CONSTRAINT pk_event_log_items PRIMARY KEY (id) -); - -CREATE TABLE external_integration_dispatch_requests ( - id bigint GENERATED BY DEFAULT AS IDENTITY, - dispatch_context_type_name character varying(450) NOT NULL, - dispatch_context_json text NOT NULL, - CONSTRAINT pk_external_integration_dispatch_requests PRIMARY KEY (id) -); - -CREATE TABLE failed_error_imports ( - unique_message_id uuid NOT NULL, - failed_at timestamp with time zone NOT NULL, - message_id character varying(450) NOT NULL, - headers_json text NOT NULL, - body bytea NOT NULL, - body_stored_externally boolean NOT NULL, - exception_info text NOT NULL, - CONSTRAINT pk_failed_error_imports PRIMARY KEY (unique_message_id) -); - -CREATE TABLE failed_message_edits ( - unique_message_id uuid NOT NULL, - edit_id character varying(450) NOT NULL, - CONSTRAINT pk_failed_message_edits PRIMARY KEY (unique_message_id) -); - -CREATE TABLE failed_message_retries ( - unique_message_id uuid NOT NULL, - retry_batch_id uuid NOT NULL, - stage_attempts integer NOT NULL, - CONSTRAINT pk_failed_message_retries PRIMARY KEY (unique_message_id) -); - -CREATE TABLE failed_messages ( - unique_message_id uuid NOT NULL, - status integer NOT NULL, - status_changed_at timestamp with time zone NOT NULL, - last_modified timestamp with time zone NOT NULL, - number_of_processing_attempts integer NOT NULL, - first_time_of_failure timestamp with time zone NOT NULL, - last_time_of_failure timestamp with time zone NOT NULL, - last_attempted_at timestamp with time zone NOT NULL, - message_id character varying(450), - message_type text, - time_sent timestamp with time zone, - conversation_id character varying(450), - sending_endpoint_name character varying(450), - sending_endpoint_host_id uuid, - sending_endpoint_host character varying(450), - receiving_endpoint_name character varying(450), - receiving_endpoint_host_id uuid, - receiving_endpoint_host character varying(450), - exception_type text, - exception_message text, - is_system_message boolean NOT NULL, - headers_json text NOT NULL, - body_text text, - body_stored_externally boolean NOT NULL, - body_size integer NOT NULL, - body_content_type character varying(450), - failing_endpoint_address character varying(450) NOT NULL, - CONSTRAINT pk_failed_messages PRIMARY KEY (unique_message_id) -); - -CREATE TABLE group_comments ( - group_id character varying(64) NOT NULL, - comment text NOT NULL, - CONSTRAINT pk_group_comments PRIMARY KEY (group_id) -); - -CREATE TABLE historic_retry_operations ( - id bigint GENERATED BY DEFAULT AS IDENTITY, - request_id character varying(400) NOT NULL, - retry_type integer NOT NULL, - start_time timestamp with time zone NOT NULL, - completion_time timestamp with time zone NOT NULL, - originator text, - failed boolean NOT NULL, - number_of_messages_processed integer NOT NULL, - CONSTRAINT pk_historic_retry_operations PRIMARY KEY (id) -); - -CREATE TABLE known_endpoints ( - id uuid NOT NULL, - name character varying(450) NOT NULL, - host_id uuid NOT NULL, - host character varying(450) NOT NULL, - monitored boolean NOT NULL, - CONSTRAINT pk_known_endpoints PRIMARY KEY (id) -); - -CREATE TABLE licensing_endpoints ( - normalized_name character varying(300) NOT NULL, - throughput_source integer NOT NULL, - name character varying(300) NOT NULL, - sanitized_name character varying(450) NOT NULL, - normalized_sanitized_name character varying(450) NOT NULL, - user_indicator character varying(450) NOT NULL, - scope character varying(450), - endpoint_indicators text[] NOT NULL, - CONSTRAINT pk_licensing_endpoints PRIMARY KEY (normalized_name, throughput_source) -); - -CREATE TABLE message_redirects ( - from_physical_address character varying(450) NOT NULL, - to_physical_address character varying(450) NOT NULL, - last_modified timestamp with time zone NOT NULL, - CONSTRAINT pk_message_redirects PRIMARY KEY (from_physical_address) -); - -CREATE TABLE retry_batch_now_forwarding ( - id integer NOT NULL, - retry_batch_id uuid NOT NULL, - CONSTRAINT pk_retry_batch_now_forwarding PRIMARY KEY (id) -); - -CREATE TABLE retry_batches ( - id uuid NOT NULL, - status integer NOT NULL, - retry_session_id character varying(450) NOT NULL, - request_id character varying(450) NOT NULL, - retry_type integer NOT NULL, - initial_batch_size integer NOT NULL, - start_time timestamp with time zone NOT NULL, - last timestamp with time zone, - staging_id character varying(450), - context text, - originator text, - classifier text, - initiated_by_id character varying(450), - initiated_by_name text, - operation_id character varying(450), - CONSTRAINT pk_retry_batches PRIMARY KEY (id) -); - -CREATE TABLE settings ( - key character varying(450) NOT NULL, - value text NOT NULL, - CONSTRAINT pk_settings PRIMARY KEY (key) -); - -CREATE TABLE subscriptions ( - message_type character varying(200) NOT NULL, - transport_address character varying(200) NOT NULL, - endpoint character varying(450) NOT NULL, - CONSTRAINT pk_subscriptions PRIMARY KEY (message_type, transport_address) -); - -CREATE TABLE unacknowledged_retry_operations ( - request_id character varying(400) NOT NULL, - retry_type integer NOT NULL, - start_time timestamp with time zone NOT NULL, - completion_time timestamp with time zone NOT NULL, - last timestamp with time zone NOT NULL, - originator text, - classifier character varying(450), - failed boolean NOT NULL, - number_of_messages_processed integer NOT NULL, - CONSTRAINT pk_unacknowledged_retry_operations PRIMARY KEY (request_id, retry_type) -); - -CREATE TABLE failed_message_groups ( - failed_message_unique_id uuid NOT NULL, - group_id character varying(64) NOT NULL, - title text NOT NULL, - type character varying(255) NOT NULL, - CONSTRAINT pk_failed_message_groups PRIMARY KEY (failed_message_unique_id, group_id), - CONSTRAINT fk_failed_message_groups_failed_messages_failed_message_unique FOREIGN KEY (failed_message_unique_id) REFERENCES failed_messages (unique_message_id) ON DELETE CASCADE -); - -CREATE TABLE licensing_endpoint_throughput ( - normalized_name character varying(300) NOT NULL, - throughput_source integer NOT NULL, - date_utc date NOT NULL, - message_count bigint NOT NULL, - CONSTRAINT pk_licensing_endpoint_throughput PRIMARY KEY (normalized_name, throughput_source, date_utc), - CONSTRAINT fk_licensing_endpoint_throughput_licensing_endpoints_normalize FOREIGN KEY (normalized_name, throughput_source) REFERENCES licensing_endpoints (normalized_name, throughput_source) ON DELETE CASCADE -); - -CREATE INDEX ix_archive_operations_started ON archive_operations (started); - -CREATE INDEX ix_custom_checks_reported_at ON custom_checks (reported_at); - -CREATE INDEX ix_custom_checks_status_reported_at ON custom_checks (status, reported_at); - -CREATE INDEX ix_event_log_items_raised_at_id ON event_log_items (raised_at DESC, id DESC); - -CREATE INDEX ix_failed_error_imports_failed_at ON failed_error_imports (failed_at); - -CREATE INDEX ix_failed_message_edits_edit_id ON failed_message_edits (edit_id); - -CREATE INDEX ix_failed_message_groups_group_id ON failed_message_groups (group_id); - -CREATE INDEX ix_failed_message_groups_type_group_id ON failed_message_groups (type, group_id); - -CREATE INDEX ix_failed_message_retries_retry_batch_id ON failed_message_retries (retry_batch_id); - -CREATE INDEX ix_failed_messages_conversation_id ON failed_messages (conversation_id); - -CREATE INDEX ix_failed_messages_failing_endpoint_address ON failed_messages (failing_endpoint_address); - -CREATE INDEX ix_failed_messages_receiving_endpoint_name ON failed_messages (receiving_endpoint_name); - -CREATE INDEX ix_failed_messages_status_changed_at ON failed_messages (status_changed_at) WHERE status IN (2, 4); - -CREATE INDEX ix_failed_messages_status_last_modified ON failed_messages (status, last_modified); - -CREATE INDEX ix_failed_messages_time_sent ON failed_messages (time_sent); - -CREATE INDEX ix_historic_retry_operations_completion_time_id ON historic_retry_operations (completion_time DESC, id DESC); - -CREATE INDEX ix_licensing_endpoint_throughput_date_utc ON licensing_endpoint_throughput (date_utc); - -CREATE INDEX ix_licensing_endpoints_normalized_sanitized_name ON licensing_endpoints (normalized_sanitized_name); - -CREATE INDEX ix_retry_batches_status_retry_session_id ON retry_batches (status, retry_session_id); - -INSERT INTO "__EFMigrationsHistory" (migration_id, product_version) -VALUES ('20260902040418_Initial', ''); - -COMMIT; - -START TRANSACTION; -CREATE INDEX ix_failed_messages_full_text ON failed_messages USING GIN (to_tsvector('simple', headers_json || ' ' || COALESCE(body_text, '') || ' ' || replace(replace(COALESCE(message_type, ''), '.', ' '), '+', ' '))) - -INSERT INTO "__EFMigrationsHistory" (migration_id, product_version) -VALUES ('20260902040422_AddFullTextSearch', ''); - -COMMIT; - -START TRANSACTION; -DROP INDEX ix_failed_messages_status_last_modified; - -DROP INDEX ix_failed_message_groups_type_group_id; - -ALTER TABLE failed_messages ALTER COLUMN message_type TYPE character varying(450); - -CREATE INDEX ix_failed_messages_status_last_modified ON failed_messages (status, last_modified) INCLUDE (first_time_of_failure, last_time_of_failure); - -CREATE INDEX ix_failed_messages_status_last_time_of_failure ON failed_messages (status, last_time_of_failure); - -CREATE INDEX ix_failed_messages_status_message_type_unique_message_id ON failed_messages (status, message_type, unique_message_id); - -CREATE INDEX ix_failed_message_groups_type_group_id ON failed_message_groups (type, group_id) INCLUDE (title); - -INSERT INTO "__EFMigrationsHistory" (migration_id, product_version) -VALUES ('20260911041328_AddMissingIndexes', ''); - -COMMIT; - -START TRANSACTION; -DROP INDEX ix_failed_messages_status_last_modified; - -DROP INDEX ix_failed_message_groups_type_group_id; - -CREATE INDEX ix_failed_messages_status_last_modified_unique_message_id ON failed_messages (status, last_modified, unique_message_id) INCLUDE (first_time_of_failure, last_time_of_failure); - -CREATE INDEX ix_failed_message_groups_type_group_id ON failed_message_groups (type, group_id) INCLUDE (failed_message_unique_id, title); - -INSERT INTO "__EFMigrationsHistory" (migration_id, product_version) -VALUES ('20260915050538_AddAdditionalIndexCoverage', ''); - -COMMIT; - -START TRANSACTION; -ALTER TABLE failed_messages ALTER COLUMN message_id TYPE text; - -ALTER TABLE failed_error_imports ALTER COLUMN message_id TYPE text; - -DROP INDEX IF EXISTS ix_failed_messages_full_text - -CREATE INDEX ix_failed_messages_full_text ON failed_messages USING GIN (to_tsvector('simple', headers_json || ' ' || substring(COALESCE(body_text, ''), 1, 262144) || ' ' || replace(replace(COALESCE(message_type, ''), '.', ' '), '+', ' '))) - -INSERT INTO "__EFMigrationsHistory" (migration_id, product_version) -VALUES ('20260928064706_WidenMessageIdAndCapFullTextBody', ''); - -COMMIT; - diff --git a/src/ServiceControl.Persistence.Tests.PostgreSql/ApprovalFiles/SlowMigrationOperationApprovals.Verify_slow_migration_operations_are_unchanged.approved.txt b/src/ServiceControl.Persistence.Tests.PostgreSql/ApprovalFiles/SlowMigrationOperationApprovals.Verify_slow_migration_operations_are_unchanged.approved.txt new file mode 100644 index 0000000000..55879e2ebc --- /dev/null +++ b/src/ServiceControl.Persistence.Tests.PostgreSql/ApprovalFiles/SlowMigrationOperationApprovals.Verify_slow_migration_operations_are_unchanged.approved.txt @@ -0,0 +1,23 @@ +20260902040422_AddFullTextSearch + Sql + CREATE INDEX ix_failed_messages_full_text ON failed_messages USING GIN (to_tsvector('simple', headers_json || ' ' || COALESCE(body_text, '') || ' ' || replace(replace(COALESCE(message_type, ''), '.', ' '), '+', ' '))) +20260911041328_AddMissingIndexes + DropIndex ix_failed_messages_status_last_modified on failed_messages + DropIndex ix_failed_message_groups_type_group_id on failed_message_groups + AlterColumn failed_messages.message_type text null -> character varying(450) null + CreateIndex ix_failed_messages_status_last_modified on failed_messages (status, last_modified) Npgsql:IndexInclude=[first_time_of_failure, last_time_of_failure] + CreateIndex ix_failed_messages_status_last_time_of_failure on failed_messages (status, last_time_of_failure) + CreateIndex ix_failed_messages_status_message_type_unique_message_id on failed_messages (status, message_type, unique_message_id) + CreateIndex ix_failed_message_groups_type_group_id on failed_message_groups (type, group_id) Npgsql:IndexInclude=[title] +20260915050538_AddAdditionalIndexCoverage + DropIndex ix_failed_messages_status_last_modified on failed_messages + DropIndex ix_failed_message_groups_type_group_id on failed_message_groups + CreateIndex ix_failed_messages_status_last_modified_unique_message_id on failed_messages (status, last_modified, unique_message_id) Npgsql:IndexInclude=[first_time_of_failure, last_time_of_failure] + CreateIndex ix_failed_message_groups_type_group_id on failed_message_groups (type, group_id) Npgsql:IndexInclude=[failed_message_unique_id, title] +20260928064706_WidenMessageIdAndCapFullTextBody + AlterColumn failed_messages.message_id character varying(450) null -> text null + AlterColumn failed_error_imports.message_id character varying(450) not null -> text not null + Sql + DROP INDEX IF EXISTS ix_failed_messages_full_text + Sql + CREATE INDEX ix_failed_messages_full_text ON failed_messages USING GIN (to_tsvector('simple', headers_json || ' ' || substring(COALESCE(body_text, ''), 1, 262144) || ' ' || replace(replace(COALESCE(message_type, ''), '.', ' '), '+', ' '))) diff --git a/src/ServiceControl.Persistence.Tests.PostgreSql/MigrationScriptApprovals.cs b/src/ServiceControl.Persistence.Tests.PostgreSql/SlowMigrationOperationApprovals.cs similarity index 53% rename from src/ServiceControl.Persistence.Tests.PostgreSql/MigrationScriptApprovals.cs rename to src/ServiceControl.Persistence.Tests.PostgreSql/SlowMigrationOperationApprovals.cs index 200f8740ce..535be80ef4 100644 --- a/src/ServiceControl.Persistence.Tests.PostgreSql/MigrationScriptApprovals.cs +++ b/src/ServiceControl.Persistence.Tests.PostgreSql/SlowMigrationOperationApprovals.cs @@ -2,27 +2,21 @@ namespace ServiceControl.Persistence.Tests; using EFCore.PostgreSql; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; -using Particular.Approvals; using ServiceControl.Persistence.EFCore.Abstractions; using ServiceControl.Persistence.EFCore.DbContexts; using TestHelper; -// A changed index can block writes on a large table for a long time. Record it in the upgrade guide and never edit an existing migration, see docs/testing.md. [TestFixture] -class MigrationScriptApprovals +class SlowMigrationOperationApprovals { - [TestCase(null)] - [TestCase("custom_schema")] - public void Verify_migration_script_is_unchanged(string schema) + [Test] + public void Verify_slow_migration_operations_are_unchanged() { var settings = new PostgreSqlPersisterSettings { ConnectionString = "Host=unused", - Schema = schema, BodyStorage = new FileSystemBodyStorageSettings { StoragePath = "unused" } }; @@ -31,8 +25,7 @@ public void Verify_migration_script_is_unchanged(string schema) using var serviceProvider = services.BuildServiceProvider(); using var scope = serviceProvider.CreateScope(); - var script = scope.ServiceProvider.GetRequiredService().GetService().GenerateScript(); - Approver.Verify(script, MigrationScript.WithoutProductVersion, schema ?? "default_schema"); + SlowMigrationOperations.Verify(scope.ServiceProvider.GetRequiredService()); } } diff --git a/src/ServiceControl.Persistence.Tests.SqlServer/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.custom_schema.approved.txt b/src/ServiceControl.Persistence.Tests.SqlServer/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.custom_schema.approved.txt deleted file mode 100644 index 1bcc0e4ee0..0000000000 --- a/src/ServiceControl.Persistence.Tests.SqlServer/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.custom_schema.approved.txt +++ /dev/null @@ -1,359 +0,0 @@ -IF OBJECT_ID(N'[custom_schema].[__EFMigrationsHistory]') IS NULL -BEGIN - IF SCHEMA_ID(N'custom_schema') IS NULL EXEC(N'CREATE SCHEMA [custom_schema];'); - CREATE TABLE [custom_schema].[__EFMigrationsHistory] ( - [MigrationId] nvarchar(150) NOT NULL, - [ProductVersion] nvarchar(32) NOT NULL, - CONSTRAINT [PK___EFMigrationsHistory] PRIMARY KEY ([MigrationId]) - ); -END; -GO - -BEGIN TRANSACTION; -CREATE TABLE [custom_schema].[ArchiveOperations] ( - [RequestId] nvarchar(64) NOT NULL, - [ArchiveType] int NOT NULL, - [OperationType] int NOT NULL, - [GroupName] nvarchar(max) NOT NULL, - [TotalNumberOfMessages] int NOT NULL, - [NumberOfMessagesProcessed] int NOT NULL, - [NumberOfBatches] int NOT NULL, - [CurrentBatch] int NOT NULL, - [Started] datetime2 NOT NULL, - [InitiatedById] nvarchar(450) NULL, - [InitiatedByName] nvarchar(450) NULL, - [OperationId] nvarchar(450) NULL, - CONSTRAINT [PK_ArchiveOperations] PRIMARY KEY ([RequestId], [ArchiveType], [OperationType]) -); - -CREATE TABLE [custom_schema].[CustomChecks] ( - [Id] uniqueidentifier NOT NULL, - [CustomCheckId] nvarchar(max) NOT NULL, - [Category] nvarchar(max) NOT NULL, - [Status] int NOT NULL, - [ReportedAt] datetime2 NOT NULL, - [FailureReason] nvarchar(max) NULL, - [OriginatingEndpointName] nvarchar(max) NOT NULL, - [OriginatingEndpointHostId] uniqueidentifier NOT NULL, - [OriginatingEndpointHost] nvarchar(max) NOT NULL, - CONSTRAINT [PK_CustomChecks] PRIMARY KEY ([Id]) -); - -CREATE TABLE [custom_schema].[EndpointSettings] ( - [Name] nvarchar(450) NOT NULL, - [TrackInstances] bit NOT NULL, - CONSTRAINT [PK_EndpointSettings] PRIMARY KEY ([Name]) -); - -CREATE TABLE [custom_schema].[EventLogItems] ( - [Id] bigint NOT NULL IDENTITY, - [Description] nvarchar(max) NOT NULL, - [Severity] int NOT NULL, - [RaisedAt] datetime2 NOT NULL, - [RelatedTo] nvarchar(max) NOT NULL, - [Category] nvarchar(450) NOT NULL, - [EventType] nvarchar(450) NOT NULL, - CONSTRAINT [PK_EventLogItems] PRIMARY KEY ([Id]) -); - -CREATE TABLE [custom_schema].[ExternalIntegrationDispatchRequests] ( - [Id] bigint NOT NULL IDENTITY, - [DispatchContextTypeName] nvarchar(450) NOT NULL, - [DispatchContextJson] nvarchar(max) NOT NULL, - CONSTRAINT [PK_ExternalIntegrationDispatchRequests] PRIMARY KEY ([Id]) -); - -CREATE TABLE [custom_schema].[FailedErrorImports] ( - [UniqueMessageId] uniqueidentifier NOT NULL, - [FailedAt] datetime2 NOT NULL, - [MessageId] nvarchar(450) NOT NULL, - [HeadersJson] nvarchar(max) NOT NULL, - [Body] varbinary(max) NOT NULL, - [BodyStoredExternally] bit NOT NULL, - [ExceptionInfo] nvarchar(max) NOT NULL, - CONSTRAINT [PK_FailedErrorImports] PRIMARY KEY ([UniqueMessageId]) -); - -CREATE TABLE [custom_schema].[FailedMessageEdits] ( - [UniqueMessageId] uniqueidentifier NOT NULL, - [EditId] nvarchar(450) NOT NULL, - CONSTRAINT [PK_FailedMessageEdits] PRIMARY KEY ([UniqueMessageId]) -); - -CREATE TABLE [custom_schema].[FailedMessageRetries] ( - [UniqueMessageId] uniqueidentifier NOT NULL, - [RetryBatchId] uniqueidentifier NOT NULL, - [StageAttempts] int NOT NULL, - CONSTRAINT [PK_FailedMessageRetries] PRIMARY KEY ([UniqueMessageId]) -); - -CREATE TABLE [custom_schema].[FailedMessages] ( - [UniqueMessageId] uniqueidentifier NOT NULL, - [Status] int NOT NULL, - [StatusChangedAt] datetime2 NOT NULL, - [LastModified] datetime2 NOT NULL, - [NumberOfProcessingAttempts] int NOT NULL, - [FirstTimeOfFailure] datetime2 NOT NULL, - [LastTimeOfFailure] datetime2 NOT NULL, - [LastAttemptedAt] datetime2 NOT NULL, - [MessageId] nvarchar(450) NULL, - [MessageType] nvarchar(max) NULL, - [TimeSent] datetime2 NULL, - [ConversationId] nvarchar(450) NULL, - [SendingEndpointName] nvarchar(450) NULL, - [SendingEndpointHostId] uniqueidentifier NULL, - [SendingEndpointHost] nvarchar(450) NULL, - [ReceivingEndpointName] nvarchar(450) NULL, - [ReceivingEndpointHostId] uniqueidentifier NULL, - [ReceivingEndpointHost] nvarchar(450) NULL, - [ExceptionType] nvarchar(max) NULL, - [ExceptionMessage] nvarchar(max) NULL, - [IsSystemMessage] bit NOT NULL, - [HeadersJson] nvarchar(max) NOT NULL, - [BodyText] nvarchar(max) NULL, - [BodyStoredExternally] bit NOT NULL, - [BodySize] int NOT NULL, - [BodyContentType] nvarchar(450) NULL, - [FailingEndpointAddress] nvarchar(450) NOT NULL, - CONSTRAINT [PK_FailedMessages] PRIMARY KEY ([UniqueMessageId]) -); - -CREATE TABLE [custom_schema].[GroupComments] ( - [GroupId] nvarchar(64) NOT NULL, - [Comment] nvarchar(max) NOT NULL, - CONSTRAINT [PK_GroupComments] PRIMARY KEY ([GroupId]) -); - -CREATE TABLE [custom_schema].[HistoricRetryOperations] ( - [Id] bigint NOT NULL IDENTITY, - [RequestId] nvarchar(400) NOT NULL, - [RetryType] int NOT NULL, - [StartTime] datetime2 NOT NULL, - [CompletionTime] datetime2 NOT NULL, - [Originator] nvarchar(max) NULL, - [Failed] bit NOT NULL, - [NumberOfMessagesProcessed] int NOT NULL, - CONSTRAINT [PK_HistoricRetryOperations] PRIMARY KEY ([Id]) -); - -CREATE TABLE [custom_schema].[KnownEndpoints] ( - [Id] uniqueidentifier NOT NULL, - [Name] nvarchar(450) NOT NULL, - [HostId] uniqueidentifier NOT NULL, - [Host] nvarchar(450) NOT NULL, - [Monitored] bit NOT NULL, - CONSTRAINT [PK_KnownEndpoints] PRIMARY KEY ([Id]) -); - -CREATE TABLE [custom_schema].[LicensingEndpoints] ( - [NormalizedName] nvarchar(300) NOT NULL, - [ThroughputSource] int NOT NULL, - [Name] nvarchar(300) NOT NULL, - [SanitizedName] nvarchar(450) NOT NULL, - [NormalizedSanitizedName] nvarchar(450) NOT NULL, - [UserIndicator] nvarchar(450) NOT NULL, - [Scope] nvarchar(450) NULL, - [EndpointIndicators] nvarchar(max) NOT NULL, - CONSTRAINT [PK_LicensingEndpoints] PRIMARY KEY ([NormalizedName], [ThroughputSource]) -); - -CREATE TABLE [custom_schema].[MessageRedirects] ( - [FromPhysicalAddress] nvarchar(450) NOT NULL, - [ToPhysicalAddress] nvarchar(450) NOT NULL, - [LastModified] datetime2 NOT NULL, - CONSTRAINT [PK_MessageRedirects] PRIMARY KEY ([FromPhysicalAddress]) -); - -CREATE TABLE [custom_schema].[RetryBatches] ( - [Id] uniqueidentifier NOT NULL, - [Status] int NOT NULL, - [RetrySessionId] nvarchar(450) NOT NULL, - [RequestId] nvarchar(450) NOT NULL, - [RetryType] int NOT NULL, - [InitialBatchSize] int NOT NULL, - [StartTime] datetime2 NOT NULL, - [Last] datetime2 NULL, - [StagingId] nvarchar(450) NULL, - [Context] nvarchar(max) NULL, - [Originator] nvarchar(max) NULL, - [Classifier] nvarchar(max) NULL, - [InitiatedById] nvarchar(450) NULL, - [InitiatedByName] nvarchar(max) NULL, - [OperationId] nvarchar(450) NULL, - CONSTRAINT [PK_RetryBatches] PRIMARY KEY ([Id]) -); - -CREATE TABLE [custom_schema].[RetryBatchNowForwarding] ( - [Id] int NOT NULL, - [RetryBatchId] uniqueidentifier NOT NULL, - CONSTRAINT [PK_RetryBatchNowForwarding] PRIMARY KEY ([Id]) -); - -CREATE TABLE [custom_schema].[Settings] ( - [Key] nvarchar(450) NOT NULL, - [Value] nvarchar(max) NOT NULL, - CONSTRAINT [PK_Settings] PRIMARY KEY ([Key]) -); - -CREATE TABLE [custom_schema].[Subscriptions] ( - [MessageType] nvarchar(200) NOT NULL, - [TransportAddress] nvarchar(200) NOT NULL, - [Endpoint] nvarchar(450) NOT NULL, - CONSTRAINT [PK_Subscriptions] PRIMARY KEY ([MessageType], [TransportAddress]) -); - -CREATE TABLE [custom_schema].[UnacknowledgedRetryOperations] ( - [RequestId] nvarchar(400) NOT NULL, - [RetryType] int NOT NULL, - [StartTime] datetime2 NOT NULL, - [CompletionTime] datetime2 NOT NULL, - [Last] datetime2 NOT NULL, - [Originator] nvarchar(max) NULL, - [Classifier] nvarchar(450) NULL, - [Failed] bit NOT NULL, - [NumberOfMessagesProcessed] int NOT NULL, - CONSTRAINT [PK_UnacknowledgedRetryOperations] PRIMARY KEY ([RequestId], [RetryType]) -); - -CREATE TABLE [custom_schema].[FailedMessageGroups] ( - [FailedMessageUniqueId] uniqueidentifier NOT NULL, - [GroupId] nvarchar(64) NOT NULL, - [Title] nvarchar(max) NOT NULL, - [Type] nvarchar(255) NOT NULL, - CONSTRAINT [PK_FailedMessageGroups] PRIMARY KEY ([FailedMessageUniqueId], [GroupId]), - CONSTRAINT [FK_FailedMessageGroups_FailedMessages_FailedMessageUniqueId] FOREIGN KEY ([FailedMessageUniqueId]) REFERENCES [custom_schema].[FailedMessages] ([UniqueMessageId]) ON DELETE CASCADE -); - -CREATE TABLE [custom_schema].[LicensingEndpointThroughput] ( - [NormalizedName] nvarchar(300) NOT NULL, - [ThroughputSource] int NOT NULL, - [DateUtc] date NOT NULL, - [MessageCount] bigint NOT NULL, - CONSTRAINT [PK_LicensingEndpointThroughput] PRIMARY KEY ([NormalizedName], [ThroughputSource], [DateUtc]), - CONSTRAINT [FK_LicensingEndpointThroughput_LicensingEndpoints_NormalizedName_ThroughputSource] FOREIGN KEY ([NormalizedName], [ThroughputSource]) REFERENCES [custom_schema].[LicensingEndpoints] ([NormalizedName], [ThroughputSource]) ON DELETE CASCADE -); - -CREATE INDEX [IX_ArchiveOperations_Started] ON [custom_schema].[ArchiveOperations] ([Started]); - -CREATE INDEX [IX_CustomChecks_ReportedAt] ON [custom_schema].[CustomChecks] ([ReportedAt]); - -CREATE INDEX [IX_CustomChecks_Status_ReportedAt] ON [custom_schema].[CustomChecks] ([Status], [ReportedAt]); - -CREATE INDEX [IX_EventLogItems_RaisedAt_Id] ON [custom_schema].[EventLogItems] ([RaisedAt] DESC, [Id] DESC); - -CREATE INDEX [IX_FailedErrorImports_FailedAt] ON [custom_schema].[FailedErrorImports] ([FailedAt]); - -CREATE INDEX [IX_FailedMessageEdits_EditId] ON [custom_schema].[FailedMessageEdits] ([EditId]); - -CREATE INDEX [IX_FailedMessageGroups_GroupId] ON [custom_schema].[FailedMessageGroups] ([GroupId]); - -CREATE INDEX [IX_FailedMessageGroups_Type_GroupId] ON [custom_schema].[FailedMessageGroups] ([Type], [GroupId]); - -CREATE INDEX [IX_FailedMessageRetries_RetryBatchId] ON [custom_schema].[FailedMessageRetries] ([RetryBatchId]); - -CREATE INDEX [IX_FailedMessages_ConversationId] ON [custom_schema].[FailedMessages] ([ConversationId]); - -CREATE INDEX [IX_FailedMessages_FailingEndpointAddress] ON [custom_schema].[FailedMessages] ([FailingEndpointAddress]); - -CREATE INDEX [IX_FailedMessages_ReceivingEndpointName] ON [custom_schema].[FailedMessages] ([ReceivingEndpointName]); - -CREATE INDEX [IX_FailedMessages_Status_LastModified] ON [custom_schema].[FailedMessages] ([Status], [LastModified]); - -CREATE INDEX [IX_FailedMessages_StatusChangedAt] ON [custom_schema].[FailedMessages] ([StatusChangedAt]) WHERE [Status] IN (2, 4); - -CREATE INDEX [IX_FailedMessages_TimeSent] ON [custom_schema].[FailedMessages] ([TimeSent]); - -CREATE INDEX [IX_HistoricRetryOperations_CompletionTime_Id] ON [custom_schema].[HistoricRetryOperations] ([CompletionTime] DESC, [Id] DESC); - -CREATE INDEX [IX_LicensingEndpoints_NormalizedSanitizedName] ON [custom_schema].[LicensingEndpoints] ([NormalizedSanitizedName]); - -CREATE INDEX [IX_LicensingEndpointThroughput_DateUtc] ON [custom_schema].[LicensingEndpointThroughput] ([DateUtc]); - -CREATE INDEX [IX_RetryBatches_Status_RetrySessionId] ON [custom_schema].[RetryBatches] ([Status], [RetrySessionId]); - -INSERT INTO [custom_schema].[__EFMigrationsHistory] ([MigrationId], [ProductVersion]) -VALUES (N'20260902040428_Initial', N''); - -COMMIT; -GO - -IF SERVERPROPERTY('IsFullTextInstalled') <> 1 -BEGIN - THROW 50000, 'ServiceControl requires the SQL Server Full-Text Search feature, which is not installed on this instance. Install it and run setup again.', 1; -END -GO - -IF NOT EXISTS (SELECT 1 FROM sys.fulltext_catalogs WHERE name = 'ServiceControlFullTextCatalog') -BEGIN - BEGIN TRY - EXEC('CREATE FULLTEXT CATALOG ServiceControlFullTextCatalog'); - END TRY - BEGIN CATCH - IF NOT EXISTS (SELECT 1 FROM sys.fulltext_catalogs WHERE name = 'ServiceControlFullTextCatalog') THROW; - END CATCH -END -GO - -IF NOT EXISTS (SELECT 1 FROM sys.fulltext_indexes WHERE object_id = OBJECT_ID('[custom_schema].[FailedMessages]')) -BEGIN - EXEC('CREATE FULLTEXT INDEX ON [custom_schema].[FailedMessages](HeadersJson LANGUAGE 0, BodyText LANGUAGE 0) - KEY INDEX PK_FailedMessages - ON ServiceControlFullTextCatalog - WITH (CHANGE_TRACKING AUTO, STOPLIST = OFF)'); -END -GO - -INSERT INTO [custom_schema].[__EFMigrationsHistory] ([MigrationId], [ProductVersion]) -VALUES (N'20260902040432_AddFullTextSearch', N''); -GO - -BEGIN TRANSACTION; -DROP INDEX [IX_FailedMessages_Status_LastModified] ON [custom_schema].[FailedMessages]; - -DROP INDEX [IX_FailedMessageGroups_Type_GroupId] ON [custom_schema].[FailedMessageGroups]; - -DECLARE @var nvarchar(max); -SELECT @var = QUOTENAME([d].[name]) -FROM [sys].[default_constraints] [d] -INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] -WHERE ([d].[parent_object_id] = OBJECT_ID(N'[custom_schema].[FailedMessages]') AND [c].[name] = N'MessageType'); -IF @var IS NOT NULL EXEC(N'ALTER TABLE [custom_schema].[FailedMessages] DROP CONSTRAINT ' + @var + ';'); -ALTER TABLE [custom_schema].[FailedMessages] ALTER COLUMN [MessageType] nvarchar(450) NULL; - -CREATE INDEX [IX_FailedMessages_Status_LastModified] ON [custom_schema].[FailedMessages] ([Status], [LastModified]) INCLUDE ([FirstTimeOfFailure], [LastTimeOfFailure]); - -CREATE INDEX [IX_FailedMessages_Status_LastTimeOfFailure] ON [custom_schema].[FailedMessages] ([Status], [LastTimeOfFailure]); - -CREATE INDEX [IX_FailedMessages_Status_MessageType_UniqueMessageId] ON [custom_schema].[FailedMessages] ([Status], [MessageType], [UniqueMessageId]); - -CREATE INDEX [IX_FailedMessageGroups_Type_GroupId] ON [custom_schema].[FailedMessageGroups] ([Type], [GroupId]) INCLUDE ([Title]); - -INSERT INTO [custom_schema].[__EFMigrationsHistory] ([MigrationId], [ProductVersion]) -VALUES (N'20260911041253_AddMissingIndexes', N''); - -COMMIT; -GO - -BEGIN TRANSACTION; -DECLARE @var1 nvarchar(max); -SELECT @var1 = QUOTENAME([d].[name]) -FROM [sys].[default_constraints] [d] -INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] -WHERE ([d].[parent_object_id] = OBJECT_ID(N'[custom_schema].[FailedMessages]') AND [c].[name] = N'MessageId'); -IF @var1 IS NOT NULL EXEC(N'ALTER TABLE [custom_schema].[FailedMessages] DROP CONSTRAINT ' + @var1 + ';'); -ALTER TABLE [custom_schema].[FailedMessages] ALTER COLUMN [MessageId] nvarchar(max) NULL; - -DECLARE @var2 nvarchar(max); -SELECT @var2 = QUOTENAME([d].[name]) -FROM [sys].[default_constraints] [d] -INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] -WHERE ([d].[parent_object_id] = OBJECT_ID(N'[custom_schema].[FailedErrorImports]') AND [c].[name] = N'MessageId'); -IF @var2 IS NOT NULL EXEC(N'ALTER TABLE [custom_schema].[FailedErrorImports] DROP CONSTRAINT ' + @var2 + ';'); -ALTER TABLE [custom_schema].[FailedErrorImports] ALTER COLUMN [MessageId] nvarchar(max) NOT NULL; - -INSERT INTO [custom_schema].[__EFMigrationsHistory] ([MigrationId], [ProductVersion]) -VALUES (N'20260928064709_WidenMessageId', N''); - -COMMIT; -GO - diff --git a/src/ServiceControl.Persistence.Tests.SqlServer/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.default_schema.approved.txt b/src/ServiceControl.Persistence.Tests.SqlServer/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.default_schema.approved.txt deleted file mode 100644 index ad260eb8f2..0000000000 --- a/src/ServiceControl.Persistence.Tests.SqlServer/ApprovalFiles/MigrationScriptApprovals.Verify_migration_script_is_unchanged.default_schema.approved.txt +++ /dev/null @@ -1,358 +0,0 @@ -IF OBJECT_ID(N'[__EFMigrationsHistory]') IS NULL -BEGIN - CREATE TABLE [__EFMigrationsHistory] ( - [MigrationId] nvarchar(150) NOT NULL, - [ProductVersion] nvarchar(32) NOT NULL, - CONSTRAINT [PK___EFMigrationsHistory] PRIMARY KEY ([MigrationId]) - ); -END; -GO - -BEGIN TRANSACTION; -CREATE TABLE [ArchiveOperations] ( - [RequestId] nvarchar(64) NOT NULL, - [ArchiveType] int NOT NULL, - [OperationType] int NOT NULL, - [GroupName] nvarchar(max) NOT NULL, - [TotalNumberOfMessages] int NOT NULL, - [NumberOfMessagesProcessed] int NOT NULL, - [NumberOfBatches] int NOT NULL, - [CurrentBatch] int NOT NULL, - [Started] datetime2 NOT NULL, - [InitiatedById] nvarchar(450) NULL, - [InitiatedByName] nvarchar(450) NULL, - [OperationId] nvarchar(450) NULL, - CONSTRAINT [PK_ArchiveOperations] PRIMARY KEY ([RequestId], [ArchiveType], [OperationType]) -); - -CREATE TABLE [CustomChecks] ( - [Id] uniqueidentifier NOT NULL, - [CustomCheckId] nvarchar(max) NOT NULL, - [Category] nvarchar(max) NOT NULL, - [Status] int NOT NULL, - [ReportedAt] datetime2 NOT NULL, - [FailureReason] nvarchar(max) NULL, - [OriginatingEndpointName] nvarchar(max) NOT NULL, - [OriginatingEndpointHostId] uniqueidentifier NOT NULL, - [OriginatingEndpointHost] nvarchar(max) NOT NULL, - CONSTRAINT [PK_CustomChecks] PRIMARY KEY ([Id]) -); - -CREATE TABLE [EndpointSettings] ( - [Name] nvarchar(450) NOT NULL, - [TrackInstances] bit NOT NULL, - CONSTRAINT [PK_EndpointSettings] PRIMARY KEY ([Name]) -); - -CREATE TABLE [EventLogItems] ( - [Id] bigint NOT NULL IDENTITY, - [Description] nvarchar(max) NOT NULL, - [Severity] int NOT NULL, - [RaisedAt] datetime2 NOT NULL, - [RelatedTo] nvarchar(max) NOT NULL, - [Category] nvarchar(450) NOT NULL, - [EventType] nvarchar(450) NOT NULL, - CONSTRAINT [PK_EventLogItems] PRIMARY KEY ([Id]) -); - -CREATE TABLE [ExternalIntegrationDispatchRequests] ( - [Id] bigint NOT NULL IDENTITY, - [DispatchContextTypeName] nvarchar(450) NOT NULL, - [DispatchContextJson] nvarchar(max) NOT NULL, - CONSTRAINT [PK_ExternalIntegrationDispatchRequests] PRIMARY KEY ([Id]) -); - -CREATE TABLE [FailedErrorImports] ( - [UniqueMessageId] uniqueidentifier NOT NULL, - [FailedAt] datetime2 NOT NULL, - [MessageId] nvarchar(450) NOT NULL, - [HeadersJson] nvarchar(max) NOT NULL, - [Body] varbinary(max) NOT NULL, - [BodyStoredExternally] bit NOT NULL, - [ExceptionInfo] nvarchar(max) NOT NULL, - CONSTRAINT [PK_FailedErrorImports] PRIMARY KEY ([UniqueMessageId]) -); - -CREATE TABLE [FailedMessageEdits] ( - [UniqueMessageId] uniqueidentifier NOT NULL, - [EditId] nvarchar(450) NOT NULL, - CONSTRAINT [PK_FailedMessageEdits] PRIMARY KEY ([UniqueMessageId]) -); - -CREATE TABLE [FailedMessageRetries] ( - [UniqueMessageId] uniqueidentifier NOT NULL, - [RetryBatchId] uniqueidentifier NOT NULL, - [StageAttempts] int NOT NULL, - CONSTRAINT [PK_FailedMessageRetries] PRIMARY KEY ([UniqueMessageId]) -); - -CREATE TABLE [FailedMessages] ( - [UniqueMessageId] uniqueidentifier NOT NULL, - [Status] int NOT NULL, - [StatusChangedAt] datetime2 NOT NULL, - [LastModified] datetime2 NOT NULL, - [NumberOfProcessingAttempts] int NOT NULL, - [FirstTimeOfFailure] datetime2 NOT NULL, - [LastTimeOfFailure] datetime2 NOT NULL, - [LastAttemptedAt] datetime2 NOT NULL, - [MessageId] nvarchar(450) NULL, - [MessageType] nvarchar(max) NULL, - [TimeSent] datetime2 NULL, - [ConversationId] nvarchar(450) NULL, - [SendingEndpointName] nvarchar(450) NULL, - [SendingEndpointHostId] uniqueidentifier NULL, - [SendingEndpointHost] nvarchar(450) NULL, - [ReceivingEndpointName] nvarchar(450) NULL, - [ReceivingEndpointHostId] uniqueidentifier NULL, - [ReceivingEndpointHost] nvarchar(450) NULL, - [ExceptionType] nvarchar(max) NULL, - [ExceptionMessage] nvarchar(max) NULL, - [IsSystemMessage] bit NOT NULL, - [HeadersJson] nvarchar(max) NOT NULL, - [BodyText] nvarchar(max) NULL, - [BodyStoredExternally] bit NOT NULL, - [BodySize] int NOT NULL, - [BodyContentType] nvarchar(450) NULL, - [FailingEndpointAddress] nvarchar(450) NOT NULL, - CONSTRAINT [PK_FailedMessages] PRIMARY KEY ([UniqueMessageId]) -); - -CREATE TABLE [GroupComments] ( - [GroupId] nvarchar(64) NOT NULL, - [Comment] nvarchar(max) NOT NULL, - CONSTRAINT [PK_GroupComments] PRIMARY KEY ([GroupId]) -); - -CREATE TABLE [HistoricRetryOperations] ( - [Id] bigint NOT NULL IDENTITY, - [RequestId] nvarchar(400) NOT NULL, - [RetryType] int NOT NULL, - [StartTime] datetime2 NOT NULL, - [CompletionTime] datetime2 NOT NULL, - [Originator] nvarchar(max) NULL, - [Failed] bit NOT NULL, - [NumberOfMessagesProcessed] int NOT NULL, - CONSTRAINT [PK_HistoricRetryOperations] PRIMARY KEY ([Id]) -); - -CREATE TABLE [KnownEndpoints] ( - [Id] uniqueidentifier NOT NULL, - [Name] nvarchar(450) NOT NULL, - [HostId] uniqueidentifier NOT NULL, - [Host] nvarchar(450) NOT NULL, - [Monitored] bit NOT NULL, - CONSTRAINT [PK_KnownEndpoints] PRIMARY KEY ([Id]) -); - -CREATE TABLE [LicensingEndpoints] ( - [NormalizedName] nvarchar(300) NOT NULL, - [ThroughputSource] int NOT NULL, - [Name] nvarchar(300) NOT NULL, - [SanitizedName] nvarchar(450) NOT NULL, - [NormalizedSanitizedName] nvarchar(450) NOT NULL, - [UserIndicator] nvarchar(450) NOT NULL, - [Scope] nvarchar(450) NULL, - [EndpointIndicators] nvarchar(max) NOT NULL, - CONSTRAINT [PK_LicensingEndpoints] PRIMARY KEY ([NormalizedName], [ThroughputSource]) -); - -CREATE TABLE [MessageRedirects] ( - [FromPhysicalAddress] nvarchar(450) NOT NULL, - [ToPhysicalAddress] nvarchar(450) NOT NULL, - [LastModified] datetime2 NOT NULL, - CONSTRAINT [PK_MessageRedirects] PRIMARY KEY ([FromPhysicalAddress]) -); - -CREATE TABLE [RetryBatches] ( - [Id] uniqueidentifier NOT NULL, - [Status] int NOT NULL, - [RetrySessionId] nvarchar(450) NOT NULL, - [RequestId] nvarchar(450) NOT NULL, - [RetryType] int NOT NULL, - [InitialBatchSize] int NOT NULL, - [StartTime] datetime2 NOT NULL, - [Last] datetime2 NULL, - [StagingId] nvarchar(450) NULL, - [Context] nvarchar(max) NULL, - [Originator] nvarchar(max) NULL, - [Classifier] nvarchar(max) NULL, - [InitiatedById] nvarchar(450) NULL, - [InitiatedByName] nvarchar(max) NULL, - [OperationId] nvarchar(450) NULL, - CONSTRAINT [PK_RetryBatches] PRIMARY KEY ([Id]) -); - -CREATE TABLE [RetryBatchNowForwarding] ( - [Id] int NOT NULL, - [RetryBatchId] uniqueidentifier NOT NULL, - CONSTRAINT [PK_RetryBatchNowForwarding] PRIMARY KEY ([Id]) -); - -CREATE TABLE [Settings] ( - [Key] nvarchar(450) NOT NULL, - [Value] nvarchar(max) NOT NULL, - CONSTRAINT [PK_Settings] PRIMARY KEY ([Key]) -); - -CREATE TABLE [Subscriptions] ( - [MessageType] nvarchar(200) NOT NULL, - [TransportAddress] nvarchar(200) NOT NULL, - [Endpoint] nvarchar(450) NOT NULL, - CONSTRAINT [PK_Subscriptions] PRIMARY KEY ([MessageType], [TransportAddress]) -); - -CREATE TABLE [UnacknowledgedRetryOperations] ( - [RequestId] nvarchar(400) NOT NULL, - [RetryType] int NOT NULL, - [StartTime] datetime2 NOT NULL, - [CompletionTime] datetime2 NOT NULL, - [Last] datetime2 NOT NULL, - [Originator] nvarchar(max) NULL, - [Classifier] nvarchar(450) NULL, - [Failed] bit NOT NULL, - [NumberOfMessagesProcessed] int NOT NULL, - CONSTRAINT [PK_UnacknowledgedRetryOperations] PRIMARY KEY ([RequestId], [RetryType]) -); - -CREATE TABLE [FailedMessageGroups] ( - [FailedMessageUniqueId] uniqueidentifier NOT NULL, - [GroupId] nvarchar(64) NOT NULL, - [Title] nvarchar(max) NOT NULL, - [Type] nvarchar(255) NOT NULL, - CONSTRAINT [PK_FailedMessageGroups] PRIMARY KEY ([FailedMessageUniqueId], [GroupId]), - CONSTRAINT [FK_FailedMessageGroups_FailedMessages_FailedMessageUniqueId] FOREIGN KEY ([FailedMessageUniqueId]) REFERENCES [FailedMessages] ([UniqueMessageId]) ON DELETE CASCADE -); - -CREATE TABLE [LicensingEndpointThroughput] ( - [NormalizedName] nvarchar(300) NOT NULL, - [ThroughputSource] int NOT NULL, - [DateUtc] date NOT NULL, - [MessageCount] bigint NOT NULL, - CONSTRAINT [PK_LicensingEndpointThroughput] PRIMARY KEY ([NormalizedName], [ThroughputSource], [DateUtc]), - CONSTRAINT [FK_LicensingEndpointThroughput_LicensingEndpoints_NormalizedName_ThroughputSource] FOREIGN KEY ([NormalizedName], [ThroughputSource]) REFERENCES [LicensingEndpoints] ([NormalizedName], [ThroughputSource]) ON DELETE CASCADE -); - -CREATE INDEX [IX_ArchiveOperations_Started] ON [ArchiveOperations] ([Started]); - -CREATE INDEX [IX_CustomChecks_ReportedAt] ON [CustomChecks] ([ReportedAt]); - -CREATE INDEX [IX_CustomChecks_Status_ReportedAt] ON [CustomChecks] ([Status], [ReportedAt]); - -CREATE INDEX [IX_EventLogItems_RaisedAt_Id] ON [EventLogItems] ([RaisedAt] DESC, [Id] DESC); - -CREATE INDEX [IX_FailedErrorImports_FailedAt] ON [FailedErrorImports] ([FailedAt]); - -CREATE INDEX [IX_FailedMessageEdits_EditId] ON [FailedMessageEdits] ([EditId]); - -CREATE INDEX [IX_FailedMessageGroups_GroupId] ON [FailedMessageGroups] ([GroupId]); - -CREATE INDEX [IX_FailedMessageGroups_Type_GroupId] ON [FailedMessageGroups] ([Type], [GroupId]); - -CREATE INDEX [IX_FailedMessageRetries_RetryBatchId] ON [FailedMessageRetries] ([RetryBatchId]); - -CREATE INDEX [IX_FailedMessages_ConversationId] ON [FailedMessages] ([ConversationId]); - -CREATE INDEX [IX_FailedMessages_FailingEndpointAddress] ON [FailedMessages] ([FailingEndpointAddress]); - -CREATE INDEX [IX_FailedMessages_ReceivingEndpointName] ON [FailedMessages] ([ReceivingEndpointName]); - -CREATE INDEX [IX_FailedMessages_Status_LastModified] ON [FailedMessages] ([Status], [LastModified]); - -CREATE INDEX [IX_FailedMessages_StatusChangedAt] ON [FailedMessages] ([StatusChangedAt]) WHERE [Status] IN (2, 4); - -CREATE INDEX [IX_FailedMessages_TimeSent] ON [FailedMessages] ([TimeSent]); - -CREATE INDEX [IX_HistoricRetryOperations_CompletionTime_Id] ON [HistoricRetryOperations] ([CompletionTime] DESC, [Id] DESC); - -CREATE INDEX [IX_LicensingEndpoints_NormalizedSanitizedName] ON [LicensingEndpoints] ([NormalizedSanitizedName]); - -CREATE INDEX [IX_LicensingEndpointThroughput_DateUtc] ON [LicensingEndpointThroughput] ([DateUtc]); - -CREATE INDEX [IX_RetryBatches_Status_RetrySessionId] ON [RetryBatches] ([Status], [RetrySessionId]); - -INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion]) -VALUES (N'20260902040428_Initial', N''); - -COMMIT; -GO - -IF SERVERPROPERTY('IsFullTextInstalled') <> 1 -BEGIN - THROW 50000, 'ServiceControl requires the SQL Server Full-Text Search feature, which is not installed on this instance. Install it and run setup again.', 1; -END -GO - -IF NOT EXISTS (SELECT 1 FROM sys.fulltext_catalogs WHERE name = 'ServiceControlFullTextCatalog') -BEGIN - BEGIN TRY - EXEC('CREATE FULLTEXT CATALOG ServiceControlFullTextCatalog'); - END TRY - BEGIN CATCH - IF NOT EXISTS (SELECT 1 FROM sys.fulltext_catalogs WHERE name = 'ServiceControlFullTextCatalog') THROW; - END CATCH -END -GO - -IF NOT EXISTS (SELECT 1 FROM sys.fulltext_indexes WHERE object_id = OBJECT_ID('FailedMessages')) -BEGIN - EXEC('CREATE FULLTEXT INDEX ON FailedMessages(HeadersJson LANGUAGE 0, BodyText LANGUAGE 0) - KEY INDEX PK_FailedMessages - ON ServiceControlFullTextCatalog - WITH (CHANGE_TRACKING AUTO, STOPLIST = OFF)'); -END -GO - -INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion]) -VALUES (N'20260902040432_AddFullTextSearch', N''); -GO - -BEGIN TRANSACTION; -DROP INDEX [IX_FailedMessages_Status_LastModified] ON [FailedMessages]; - -DROP INDEX [IX_FailedMessageGroups_Type_GroupId] ON [FailedMessageGroups]; - -DECLARE @var nvarchar(max); -SELECT @var = QUOTENAME([d].[name]) -FROM [sys].[default_constraints] [d] -INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] -WHERE ([d].[parent_object_id] = OBJECT_ID(N'[FailedMessages]') AND [c].[name] = N'MessageType'); -IF @var IS NOT NULL EXEC(N'ALTER TABLE [FailedMessages] DROP CONSTRAINT ' + @var + ';'); -ALTER TABLE [FailedMessages] ALTER COLUMN [MessageType] nvarchar(450) NULL; - -CREATE INDEX [IX_FailedMessages_Status_LastModified] ON [FailedMessages] ([Status], [LastModified]) INCLUDE ([FirstTimeOfFailure], [LastTimeOfFailure]); - -CREATE INDEX [IX_FailedMessages_Status_LastTimeOfFailure] ON [FailedMessages] ([Status], [LastTimeOfFailure]); - -CREATE INDEX [IX_FailedMessages_Status_MessageType_UniqueMessageId] ON [FailedMessages] ([Status], [MessageType], [UniqueMessageId]); - -CREATE INDEX [IX_FailedMessageGroups_Type_GroupId] ON [FailedMessageGroups] ([Type], [GroupId]) INCLUDE ([Title]); - -INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion]) -VALUES (N'20260911041253_AddMissingIndexes', N''); - -COMMIT; -GO - -BEGIN TRANSACTION; -DECLARE @var1 nvarchar(max); -SELECT @var1 = QUOTENAME([d].[name]) -FROM [sys].[default_constraints] [d] -INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] -WHERE ([d].[parent_object_id] = OBJECT_ID(N'[FailedMessages]') AND [c].[name] = N'MessageId'); -IF @var1 IS NOT NULL EXEC(N'ALTER TABLE [FailedMessages] DROP CONSTRAINT ' + @var1 + ';'); -ALTER TABLE [FailedMessages] ALTER COLUMN [MessageId] nvarchar(max) NULL; - -DECLARE @var2 nvarchar(max); -SELECT @var2 = QUOTENAME([d].[name]) -FROM [sys].[default_constraints] [d] -INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] -WHERE ([d].[parent_object_id] = OBJECT_ID(N'[FailedErrorImports]') AND [c].[name] = N'MessageId'); -IF @var2 IS NOT NULL EXEC(N'ALTER TABLE [FailedErrorImports] DROP CONSTRAINT ' + @var2 + ';'); -ALTER TABLE [FailedErrorImports] ALTER COLUMN [MessageId] nvarchar(max) NOT NULL; - -INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion]) -VALUES (N'20260928064709_WidenMessageId', N''); - -COMMIT; -GO - diff --git a/src/ServiceControl.Persistence.Tests.SqlServer/ApprovalFiles/SlowMigrationOperationApprovals.Verify_slow_migration_operations_are_unchanged.approved.txt b/src/ServiceControl.Persistence.Tests.SqlServer/ApprovalFiles/SlowMigrationOperationApprovals.Verify_slow_migration_operations_are_unchanged.approved.txt new file mode 100644 index 0000000000..9f6affc840 --- /dev/null +++ b/src/ServiceControl.Persistence.Tests.SqlServer/ApprovalFiles/SlowMigrationOperationApprovals.Verify_slow_migration_operations_are_unchanged.approved.txt @@ -0,0 +1,35 @@ +20260902040432_AddFullTextSearch + Sql + IF SERVERPROPERTY('IsFullTextInstalled') <> 1 + BEGIN + THROW 50000, 'ServiceControl requires the SQL Server Full-Text Search feature, which is not installed on this instance. Install it and run setup again.', 1; + END + Sql + IF NOT EXISTS (SELECT 1 FROM sys.fulltext_catalogs WHERE name = 'ServiceControlFullTextCatalog') + BEGIN + BEGIN TRY + EXEC('CREATE FULLTEXT CATALOG ServiceControlFullTextCatalog'); + END TRY + BEGIN CATCH + IF NOT EXISTS (SELECT 1 FROM sys.fulltext_catalogs WHERE name = 'ServiceControlFullTextCatalog') THROW; + END CATCH + END + Sql + IF NOT EXISTS (SELECT 1 FROM sys.fulltext_indexes WHERE object_id = OBJECT_ID('FailedMessages')) + BEGIN + EXEC('CREATE FULLTEXT INDEX ON FailedMessages(HeadersJson LANGUAGE 0, BodyText LANGUAGE 0) + KEY INDEX PK_FailedMessages + ON ServiceControlFullTextCatalog + WITH (CHANGE_TRACKING AUTO, STOPLIST = OFF)'); + END +20260911041253_AddMissingIndexes + DropIndex IX_FailedMessages_Status_LastModified on FailedMessages + DropIndex IX_FailedMessageGroups_Type_GroupId on FailedMessageGroups + AlterColumn FailedMessages.MessageType nvarchar(max) null -> nvarchar(450) null + CreateIndex IX_FailedMessages_Status_LastModified on FailedMessages (Status, LastModified) SqlServer:Include=[FirstTimeOfFailure, LastTimeOfFailure] + CreateIndex IX_FailedMessages_Status_LastTimeOfFailure on FailedMessages (Status, LastTimeOfFailure) + CreateIndex IX_FailedMessages_Status_MessageType_UniqueMessageId on FailedMessages (Status, MessageType, UniqueMessageId) + CreateIndex IX_FailedMessageGroups_Type_GroupId on FailedMessageGroups (Type, GroupId) SqlServer:Include=[Title] +20260928064709_WidenMessageId + AlterColumn FailedMessages.MessageId nvarchar(450) null -> nvarchar(max) null + AlterColumn FailedErrorImports.MessageId nvarchar(450) not null -> nvarchar(max) not null diff --git a/src/ServiceControl.Persistence.Tests.SqlServer/MigrationScriptApprovals.cs b/src/ServiceControl.Persistence.Tests.SqlServer/SlowMigrationOperationApprovals.cs similarity index 53% rename from src/ServiceControl.Persistence.Tests.SqlServer/MigrationScriptApprovals.cs rename to src/ServiceControl.Persistence.Tests.SqlServer/SlowMigrationOperationApprovals.cs index f775824079..719705d1bb 100644 --- a/src/ServiceControl.Persistence.Tests.SqlServer/MigrationScriptApprovals.cs +++ b/src/ServiceControl.Persistence.Tests.SqlServer/SlowMigrationOperationApprovals.cs @@ -2,27 +2,21 @@ namespace ServiceControl.Persistence.Tests; using EFCore.SqlServer; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; -using Particular.Approvals; using ServiceControl.Persistence.EFCore.Abstractions; using ServiceControl.Persistence.EFCore.DbContexts; using TestHelper; -// A changed index can block writes on a large table for a long time. Record it in the upgrade guide and never edit an existing migration, see docs/testing.md. [TestFixture] -class MigrationScriptApprovals +class SlowMigrationOperationApprovals { - [TestCase(null)] - [TestCase("custom_schema")] - public void Verify_migration_script_is_unchanged(string schema) + [Test] + public void Verify_slow_migration_operations_are_unchanged() { var settings = new SqlServerPersisterSettings { ConnectionString = "Server=unused", - Schema = schema, BodyStorage = new FileSystemBodyStorageSettings { StoragePath = "unused" } }; @@ -31,8 +25,7 @@ public void Verify_migration_script_is_unchanged(string schema) using var serviceProvider = services.BuildServiceProvider(); using var scope = serviceProvider.CreateScope(); - var script = scope.ServiceProvider.GetRequiredService().GetService().GenerateScript(); - Approver.Verify(script, MigrationScript.WithoutProductVersion, schema ?? "default_schema"); + SlowMigrationOperations.Verify(scope.ServiceProvider.GetRequiredService()); } } diff --git a/src/TestHelper/MigrationScript.cs b/src/TestHelper/MigrationScript.cs deleted file mode 100644 index 2adc6031fc..0000000000 --- a/src/TestHelper/MigrationScript.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace TestHelper; - -using System.Text.RegularExpressions; - -public static partial class MigrationScript -{ - /// - /// Replaces the EF Core product version in each migrations history row of a migration script with <ProductVersion>. - /// The version changes with every EF Core update, but the schema stays the same. - /// - public static string WithoutProductVersion(string script) => HistoryRowVersion().Replace(script, "${row}'"); - - [GeneratedRegex(@"(?VALUES \(N?'\d{14}_\w+', N?')[^']+'")] - private static partial Regex HistoryRowVersion(); -} diff --git a/src/TestHelper/SlowMigrationOperations.cs b/src/TestHelper/SlowMigrationOperations.cs new file mode 100644 index 0000000000..353642391d --- /dev/null +++ b/src/TestHelper/SlowMigrationOperations.cs @@ -0,0 +1,91 @@ +namespace TestHelper; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Particular.Approvals; + +public static class SlowMigrationOperations +{ + const string ChangedMessage = + "The migration steps that can block writes on a large table changed. Creating or dropping an index or key, or altering a column, can block writes for a long time during an upgrade. " + + "Record the change in the upgrade guide of the next release, and add a new migration instead of editing an existing one. " + + "Then approve the new list. See docs/testing.md."; + + /// + /// Compares the slow steps in the migrations of with the approved file for the calling test. + /// The slow steps are index, primary key and unique constraint changes, altered columns and raw SQL. + /// Steps on a table that the same migration creates are left out, because that table starts empty. + /// When the list differs, it throws an exception that says what to check before approving the new list, with the approval failure as the inner exception. + /// + public static void Verify(DbContext context, [CallerFilePath] string callerFilePath = null, [CallerMemberName] string callerMemberName = null) + { + var steps = Describe(context); + + try + { + Approver.Verify(steps, callerFilePath: callerFilePath, callerMemberName: callerMemberName); + } + catch (Exception e) + { + throw new Exception(ChangedMessage, e); + } + } + + static string Describe(DbContext context) + { + var migrationsAssembly = context.GetService(); + var text = new StringBuilder(); + + foreach (var (id, type) in migrationsAssembly.Migrations.OrderBy(migration => migration.Key, StringComparer.Ordinal)) + { + var operations = migrationsAssembly.CreateMigration(type, context.Database.ProviderName).UpOperations; + var newTables = operations.OfType().Select(table => table.Name).ToHashSet(StringComparer.Ordinal); + var steps = operations.Select(operation => Describe(operation, newTables)).OfType().ToList(); + + if (steps.Count == 0) + { + continue; + } + + text.AppendLine(id); + foreach (var step in steps) + { + text.AppendLine(Indent(step)); + } + } + + return text.ToString(); + } + + static string Describe(MigrationOperation operation, HashSet newTables) => operation switch + { + SqlOperation sql => $"Sql{Environment.NewLine}{Indent(sql.Sql)}", + ITableMigrationOperation table when newTables.Contains(table.Table) => null, + CreateIndexOperation index => $"CreateIndex {index.Name} on {index.Table} ({IndexColumns(index)}){(index.IsUnique ? " unique" : "")}{(index.Filter is null ? "" : $" where {index.Filter}")}{Annotations(index)}", + DropIndexOperation index => $"DropIndex {index.Name} on {index.Table}", + AddPrimaryKeyOperation key => $"AddPrimaryKey {key.Name} on {key.Table} ({string.Join(", ", key.Columns)}){Annotations(key)}", + DropPrimaryKeyOperation key => $"DropPrimaryKey {key.Name} on {key.Table}", + AddUniqueConstraintOperation constraint => $"AddUniqueConstraint {constraint.Name} on {constraint.Table} ({string.Join(", ", constraint.Columns)}){Annotations(constraint)}", + DropUniqueConstraintOperation constraint => $"DropUniqueConstraint {constraint.Name} on {constraint.Table}", + AlterColumnOperation column => $"AlterColumn {column.Table}.{column.Name} {ColumnType(column.OldColumn)} -> {ColumnType(column)}", + _ => null + }; + + // An empty IsDescending array means every column is descending. + static string IndexColumns(CreateIndexOperation index) => string.Join(", ", index.Columns.Select((column, i) => + index.IsDescending is { } descending && (descending.Length == 0 || descending[i]) ? $"{column} desc" : column)); + + static string ColumnType(ColumnOperation column) => $"{column.ColumnType ?? column.ClrType.Name} {(column.IsNullable ? "null" : "not null")}"; + + static string Annotations(MigrationOperation operation) => string.Concat(operation.GetAnnotations().Select(annotation => + $" {annotation.Name}={(annotation.Value is IEnumerable values ? $"[{string.Join(", ", values)}]" : annotation.Value)}")); + + static string Indent(string text) => string.Join(Environment.NewLine, text.Split('\n').Select(line => line.TrimEnd('\r')).Select(line => line.Length == 0 ? line : " " + line)); +} diff --git a/src/TestHelper/TestHelper.csproj b/src/TestHelper/TestHelper.csproj index b6077f151d..ec9704188c 100644 --- a/src/TestHelper/TestHelper.csproj +++ b/src/TestHelper/TestHelper.csproj @@ -5,8 +5,10 @@ + + From 6b4bf7ead53bbb29113eb7920bd3056970ef6dbd Mon Sep 17 00:00:00 2001 From: Warwick Schroeder Date: Thu, 22 Oct 2026 12:30:34 +0800 Subject: [PATCH 3/3] Enhance SlowMigrationOperation approvals to include new foreign keys, check constraints, and NOT NULL columns with defaults in migration tests --- docs/testing.md | 2 +- src/TestHelper/SlowMigrationOperations.cs | 22 ++++++++++++++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/docs/testing.md b/docs/testing.md index c2acbd0a57..f7a995eebc 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -27,7 +27,7 @@ On Windows, [PackagedRavenDBTests](../src/ServiceControlInstaller.Engine.UnitTes Persistence tests check assumptions at the persistence seam level by exercising each persister. For local setup details, see [Local testing of persistence providers](testing-persistence.md). -The SQL Server and PostgreSQL persistence test projects contain a `SlowMigrationOperationApprovals` test. It approves the migration steps that can block writes on a large table: index, primary key, and unique constraint changes, altered columns, and raw SQL. The test lists these steps for each migration and leaves out steps on a table that the same migration creates, because that table is empty. When the test fails, record the change in the upgrade guide of the next release before you approve the new list. If a step changed inside an existing migration, for example through a shared SQL constant, add a new migration instead. Databases that already ran the old migration never run it again. +The SQL Server and PostgreSQL persistence test projects contain a `SlowMigrationOperationApprovals` test. It approves the migration steps that can block writes on a large table: index, primary key, and unique constraint changes, new foreign keys and check constraints, altered columns, raw SQL, and new columns that are NOT NULL with a default or are stored computed columns. Other steps, such as renames and drops of columns, tables, and constraints, are not listed. The test lists these steps for each migration and leaves out steps on a table that the same migration creates, because that table is empty. Raw SQL is always listed. When the test fails because a step changed, record the change in the upgrade guide of the next release before you approve the new list. If a step changed inside an existing migration, for example through a shared SQL constant, add a new migration instead. Databases that already ran the old migration never run it again. ## Transport tests diff --git a/src/TestHelper/SlowMigrationOperations.cs b/src/TestHelper/SlowMigrationOperations.cs index 353642391d..89e21da883 100644 --- a/src/TestHelper/SlowMigrationOperations.cs +++ b/src/TestHelper/SlowMigrationOperations.cs @@ -2,6 +2,7 @@ namespace TestHelper; using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Runtime.CompilerServices; using System.Text; @@ -14,14 +15,16 @@ namespace TestHelper; public static class SlowMigrationOperations { const string ChangedMessage = - "The migration steps that can block writes on a large table changed. Creating or dropping an index or key, or altering a column, can block writes for a long time during an upgrade. " + - "Record the change in the upgrade guide of the next release, and add a new migration instead of editing an existing one. " + + "The migration steps that can block writes on a large table do not match the approved file. Index, key, foreign key, check constraint, column and raw SQL changes can block writes for a long time during an upgrade. " + + "If the diff shows a changed step, record it in the upgrade guide of the next release. If the step is in an existing migration, add a new migration instead. " + "Then approve the new list. See docs/testing.md."; /// /// Compares the slow steps in the migrations of with the approved file for the calling test. - /// The slow steps are index, primary key and unique constraint changes, altered columns and raw SQL. - /// Steps on a table that the same migration creates are left out, because that table starts empty. + /// The slow steps are index, primary key and unique constraint changes, new foreign keys and check constraints, altered columns, raw SQL, + /// and new columns that are NOT NULL with a default or are stored computed columns. + /// Other steps, such as renames and drops of columns, tables and constraints, are not listed. + /// Steps on a table that the same migration creates are left out, because that table starts empty. Raw SQL is always listed. /// When the list differs, it throws an exception that says what to check before approving the new list, with the approval failure as the inner exception. /// public static void Verify(DbContext context, [CallerFilePath] string callerFilePath = null, [CallerMemberName] string callerMemberName = null) @@ -74,10 +77,21 @@ ITableMigrationOperation table when newTables.Contains(table.Table) => null, DropPrimaryKeyOperation key => $"DropPrimaryKey {key.Name} on {key.Table}", AddUniqueConstraintOperation constraint => $"AddUniqueConstraint {constraint.Name} on {constraint.Table} ({string.Join(", ", constraint.Columns)}){Annotations(constraint)}", DropUniqueConstraintOperation constraint => $"DropUniqueConstraint {constraint.Name} on {constraint.Table}", + AddCheckConstraintOperation constraint => $"AddCheckConstraint {constraint.Name} on {constraint.Table} ({constraint.Sql})", + AddForeignKeyOperation key => $"AddForeignKey {key.Name} on {key.Table} ({string.Join(", ", key.Columns)}) references {key.PrincipalTable} ({string.Join(", ", key.PrincipalColumns ?? [])})", AlterColumnOperation column => $"AlterColumn {column.Table}.{column.Name} {ColumnType(column.OldColumn)} -> {ColumnType(column)}", + AddColumnOperation column when CanRewriteEveryRow(column) => $"AddColumn {column.Table}.{column.Name} {ColumnType(column)}{ColumnValue(column)}", _ => null }; + static bool CanRewriteEveryRow(AddColumnOperation column) => + (!column.IsNullable && (column.DefaultValue is not null || column.DefaultValueSql is not null)) || (column.ComputedColumnSql is not null && column.IsStored == true); + + static string ColumnValue(ColumnOperation column) => + column.ComputedColumnSql is not null ? $" computed {column.ComputedColumnSql}" + : column.DefaultValueSql is not null ? $" default {column.DefaultValueSql}" + : $" default {Convert.ToString(column.DefaultValue, CultureInfo.InvariantCulture)}"; + // An empty IsDescending array means every column is descending. static string IndexColumns(CreateIndexOperation index) => string.Join(", ", index.Columns.Select((column, i) => index.IsDescending is { } descending && (descending.Length == 0 || descending[i]) ? $"{column} desc" : column));