|
1 | 1 | using System.Collections.Concurrent; |
| 2 | +using JasperFx; |
2 | 3 | using Microsoft.Data.SqlClient; |
3 | | -using Polecat.Metadata; |
| 4 | +using Polecat.Schema.Identity.Sequences; |
4 | 5 | using Polecat.Storage; |
| 6 | +using Weasel.Core; |
| 7 | +using Weasel.SqlServer; |
5 | 8 |
|
6 | 9 | namespace Polecat.Internal; |
7 | 10 |
|
8 | 11 | /// <summary> |
9 | | -/// Ensures document tables exist on demand. Uses Weasel to create tables |
10 | | -/// if they don't exist, and tracks which types have been ensured. |
| 12 | +/// Ensures document tables exist on demand. Uses Weasel SchemaMigration to create |
| 13 | +/// or update tables, and tracks which types have been ensured. |
11 | 14 | /// </summary> |
12 | 15 | internal class DocumentTableEnsurer |
13 | 16 | { |
@@ -49,35 +52,29 @@ public async Task EnsureTableAsync(DocumentProvider provider, CancellationToken |
49 | 52 | return; |
50 | 53 | } |
51 | 54 |
|
52 | | - var table = new DocumentTable(provider.Mapping); |
53 | | - |
54 | 55 | await using var conn = _connectionFactory.Create(); |
55 | 56 | await conn.OpenAsync(token); |
56 | 57 |
|
| 58 | + var migrator = new SqlServerMigrator(); |
| 59 | + |
57 | 60 | // Ensure pc_hilo table for numeric ID types |
58 | 61 | if (provider.Mapping.IsNumericId && !_hiloTableEnsured) |
59 | 62 | { |
60 | | - var hiloDdl = BuildHiloTableDdl(provider.Mapping.DatabaseSchemaName); |
61 | | - await using var hiloCmd = conn.CreateCommand(); |
62 | | - hiloCmd.CommandText = hiloDdl; |
63 | | - await hiloCmd.ExecuteNonQueryAsync(token); |
| 63 | + var hiloTable = new HiloTable(provider.Mapping.DatabaseSchemaName); |
| 64 | + var hiloMigration = await SchemaMigration.DetermineAsync(conn, token, hiloTable); |
| 65 | + await migrator.ApplyAllAsync(conn, hiloMigration, AutoCreate.CreateOrUpdate, ct: token); |
64 | 66 | _hiloTableEnsured = true; |
65 | 67 | } |
66 | 68 |
|
67 | | - // Use raw DDL with IF NOT EXISTS for safety |
68 | | - var ddl = BuildCreateTableDdl(provider.Mapping); |
69 | | - await using var cmd = conn.CreateCommand(); |
70 | | - cmd.CommandText = ddl; |
71 | | - await cmd.ExecuteNonQueryAsync(token); |
72 | | - |
73 | | - // Ensure created_at column exists (migration for tables created before this column was added) |
74 | | - await using var migrateCmd = conn.CreateCommand(); |
75 | | - migrateCmd.CommandText = BuildAddMissingColumnsDdl(provider.Mapping); |
76 | | - await migrateCmd.ExecuteNonQueryAsync(token); |
| 69 | + // Use Weasel SchemaMigration to create or update the document table |
| 70 | + var table = new DocumentTable(provider.Mapping); |
| 71 | + var migration = await SchemaMigration.DetermineAsync(conn, token, table); |
| 72 | + await migrator.ApplyAllAsync(conn, migration, AutoCreate.CreateOrUpdate, ct: token); |
77 | 73 |
|
78 | 74 | // Create custom indexes (computed columns + index) |
| 75 | + // Computed columns are not modeled in Weasel, so they remain as supplementary DDL. |
79 | 76 | // Each statement executed separately so computed columns are visible |
80 | | - // before filtered indexes reference them |
| 77 | + // before filtered indexes reference them. |
81 | 78 | foreach (var index in provider.Mapping.Indexes) |
82 | 79 | { |
83 | 80 | foreach (var statement in index.ToDdlStatements(provider.Mapping)) |
@@ -147,106 +144,4 @@ public async Task EnsureTablesAsync(IEnumerable<DocumentProvider> providers, Can |
147 | 144 | await EnsureTableAsync(provider, token); |
148 | 145 | } |
149 | 146 | } |
150 | | - |
151 | | - private static string BuildHiloTableDdl(string schema) |
152 | | - { |
153 | | - return $""" |
154 | | - IF NOT EXISTS (SELECT 1 FROM sys.tables t |
155 | | - JOIN sys.schemas s ON t.schema_id = s.schema_id |
156 | | - WHERE s.name = '{schema}' AND t.name = 'pc_hilo') |
157 | | - BEGIN |
158 | | - IF SCHEMA_ID('{schema}') IS NULL |
159 | | - EXEC('CREATE SCHEMA [{schema}]'); |
160 | | -
|
161 | | - CREATE TABLE [{schema}].[pc_hilo] ( |
162 | | - entity_name varchar(250) NOT NULL PRIMARY KEY, |
163 | | - hi_value bigint NOT NULL DEFAULT 0 |
164 | | - ); |
165 | | - END |
166 | | - """; |
167 | | - } |
168 | | - |
169 | | - private static string BuildCreateTableDdl(DocumentMapping mapping) |
170 | | - { |
171 | | - var schema = mapping.DatabaseSchemaName; |
172 | | - var table = mapping.TableName; |
173 | | - var innerIdType = mapping.InnerIdType; |
174 | | - var idType = innerIdType == typeof(Guid) ? "uniqueidentifier" |
175 | | - : innerIdType == typeof(int) ? "int" |
176 | | - : innerIdType == typeof(long) ? "bigint" |
177 | | - : "varchar(250)"; |
178 | | - var isConjoined = mapping.TenancyStyle == TenancyStyle.Conjoined; |
179 | | - var isSoftDelete = mapping.DeleteStyle == DeleteStyle.SoftDelete; |
180 | | - var softDeleteCols = isSoftDelete |
181 | | - ? @" |
182 | | - is_deleted bit NOT NULL DEFAULT 0, |
183 | | - deleted_at datetimeoffset NULL," |
184 | | - : ""; |
185 | | - var guidVersionCol = mapping.UseOptimisticConcurrency |
186 | | - ? @" |
187 | | - guid_version uniqueidentifier NOT NULL DEFAULT NEWID()," |
188 | | - : ""; |
189 | | - var docTypeCol = mapping.IsHierarchy() |
190 | | - ? @" |
191 | | - doc_type varchar(250) NOT NULL DEFAULT 'base'," |
192 | | - : ""; |
193 | | - |
194 | | - if (isConjoined) |
195 | | - { |
196 | | - return $@" |
197 | | - IF NOT EXISTS (SELECT 1 FROM sys.tables t |
198 | | - JOIN sys.schemas s ON t.schema_id = s.schema_id |
199 | | - WHERE s.name = '{schema}' AND t.name = '{table}') |
200 | | - BEGIN |
201 | | - IF SCHEMA_ID('{schema}') IS NULL |
202 | | - EXEC('CREATE SCHEMA [{schema}]'); |
203 | | -
|
204 | | - CREATE TABLE [{schema}].[{table}] ( |
205 | | - tenant_id varchar(250) NOT NULL, |
206 | | - id {idType} NOT NULL, |
207 | | - data nvarchar(max) NOT NULL, |
208 | | - version int NOT NULL DEFAULT 1, |
209 | | - last_modified datetimeoffset NOT NULL DEFAULT SYSDATETIMEOFFSET(), |
210 | | - created_at datetimeoffset NOT NULL DEFAULT SYSDATETIMEOFFSET(), |
211 | | - dotnet_type varchar(500) NULL,{docTypeCol}{softDeleteCols}{guidVersionCol} |
212 | | - CONSTRAINT pk_{table} PRIMARY KEY (tenant_id, id) |
213 | | - ); |
214 | | - END"; |
215 | | - } |
216 | | - |
217 | | - return $@" |
218 | | - IF NOT EXISTS (SELECT 1 FROM sys.tables t |
219 | | - JOIN sys.schemas s ON t.schema_id = s.schema_id |
220 | | - WHERE s.name = '{schema}' AND t.name = '{table}') |
221 | | - BEGIN |
222 | | - IF SCHEMA_ID('{schema}') IS NULL |
223 | | - EXEC('CREATE SCHEMA [{schema}]'); |
224 | | -
|
225 | | - CREATE TABLE [{schema}].[{table}] ( |
226 | | - id {idType} NOT NULL PRIMARY KEY, |
227 | | - data nvarchar(max) NOT NULL, |
228 | | - version int NOT NULL DEFAULT 1, |
229 | | - last_modified datetimeoffset NOT NULL DEFAULT SYSDATETIMEOFFSET(), |
230 | | - created_at datetimeoffset NOT NULL DEFAULT SYSDATETIMEOFFSET(), |
231 | | - dotnet_type varchar(500) NULL,{docTypeCol}{softDeleteCols}{guidVersionCol} |
232 | | - tenant_id varchar(250) NOT NULL DEFAULT '*DEFAULT*' |
233 | | - ); |
234 | | - END"; |
235 | | - } |
236 | | - |
237 | | - private static string BuildAddMissingColumnsDdl(DocumentMapping mapping) |
238 | | - { |
239 | | - var schema = mapping.DatabaseSchemaName; |
240 | | - var table = mapping.TableName; |
241 | | - return $""" |
242 | | - IF NOT EXISTS (SELECT 1 FROM sys.columns |
243 | | - WHERE object_id = OBJECT_ID('[{schema}].[{table}]') |
244 | | - AND name = 'created_at') |
245 | | - BEGIN |
246 | | - ALTER TABLE [{schema}].[{table}] |
247 | | - ADD created_at datetimeoffset NOT NULL DEFAULT SYSDATETIMEOFFSET(); |
248 | | - END |
249 | | - """; |
250 | | - } |
251 | | - |
252 | 147 | } |
0 commit comments