uql-orm 0.63.0 → 0.64.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/migrate/builder/expressions.js +1 -15
- package/dist/migrate/builder/migrationBuilder.d.ts +6 -28
- package/dist/migrate/builder/migrationBuilder.js +9 -83
- package/dist/migrate/builder/types.d.ts +14 -24
- package/dist/migrate/cli.d.ts +1 -6
- package/dist/migrate/cli.js +3 -10
- package/dist/migrate/codegen/migrationFile.d.ts +9 -1
- package/dist/migrate/codegen/migrationFile.js +2 -1
- package/dist/migrate/ddl/indexDdl.d.ts +4 -2
- package/dist/migrate/ddl/indexDdl.js +16 -15
- package/dist/migrate/generator/mongoCommand.d.ts +9 -9
- package/dist/migrate/generator/mongoCommand.js +1 -1
- package/dist/migrate/generator/mongoSchemaGenerator.d.ts +21 -26
- package/dist/migrate/generator/mongoSchemaGenerator.js +103 -80
- package/dist/migrate/index.d.ts +1 -1
- package/dist/migrate/index.js +1 -1
- package/dist/migrate/indexPredicate.d.ts +8 -0
- package/dist/migrate/indexPredicate.js +52 -0
- package/dist/migrate/migrationTarget.d.ts +23 -0
- package/dist/migrate/migrationTarget.js +48 -0
- package/dist/migrate/migrator.d.ts +17 -45
- package/dist/migrate/migrator.js +80 -179
- package/dist/migrate/schemaGenerator.d.ts +12 -13
- package/dist/migrate/schemaGenerator.js +43 -9
- package/dist/mongo/mongoDialect.d.ts +6 -1
- package/dist/schema/schemaASTBuilder.d.ts +2 -0
- package/dist/schema/schemaASTBuilder.js +9 -22
- package/dist/type/migration.d.ts +11 -40
- package/dist/util/ddlExpression.util.d.ts +3 -1
- package/dist/util/ddlExpression.util.js +14 -0
- package/dist/util/sqlLiteral.js +5 -3
- package/package.json +1 -1
- package/dist/migrate/schemaGeneratorAsync.d.ts +0 -7
- package/dist/migrate/schemaGeneratorAsync.js +0 -12
package/dist/migrate/migrator.js
CHANGED
|
@@ -3,16 +3,10 @@ import { basename, extname, join } from 'node:path';
|
|
|
3
3
|
import { pathToFileURL } from 'node:url';
|
|
4
4
|
import { getEntities, getMeta } from '../entity/index.js';
|
|
5
5
|
import { introspectSchema, SchemaAST } from '../schema/index.js';
|
|
6
|
-
import { isKnownMigratorDialect, isMongoQuerier, isSqlQuerier } from '../type/index.js';
|
|
7
6
|
import { LoggerWrapper } from '../util/index.js';
|
|
8
|
-
import {
|
|
9
|
-
import { MigrationBuilder } from './builder/migrationBuilder.js';
|
|
10
|
-
import { buildMigrationModule, migrationSource, } from './codegen/migrationFile.js';
|
|
11
|
-
import { runMongoCommand } from './generator/mongoCommand.js';
|
|
7
|
+
import { buildMigrationModule } from './codegen/migrationFile.js';
|
|
12
8
|
import { introspectorFor } from './introspection/registry.js';
|
|
13
|
-
import {
|
|
14
|
-
import { DatabaseMigrationStorage } from './storage/databaseStorage.js';
|
|
15
|
-
import { MongoMigrationStorage } from './storage/mongoStorage.js';
|
|
9
|
+
import { migrationBuilderFor, migrationTargetFor } from './migrationTarget.js';
|
|
16
10
|
/**
|
|
17
11
|
* Main class for managing database migrations
|
|
18
12
|
*/
|
|
@@ -32,55 +26,35 @@ export class Migrator {
|
|
|
32
26
|
return this._entities ?? getEntities();
|
|
33
27
|
}
|
|
34
28
|
dialectName;
|
|
29
|
+
/** The generator given, or this dialect's once {@link getSchemaGenerator} has loaded it. */
|
|
35
30
|
schemaGenerator;
|
|
36
31
|
schemaIntrospector;
|
|
37
|
-
|
|
38
|
-
|
|
32
|
+
defaultForeignKeyAction;
|
|
33
|
+
target;
|
|
39
34
|
constructor(pool, options = {}) {
|
|
40
35
|
this.pool = pool;
|
|
41
36
|
this.dialectName = pool.dialect.dialectName;
|
|
42
|
-
this.
|
|
43
|
-
this.
|
|
44
|
-
|
|
45
|
-
(this.dialectName === 'mongodb'
|
|
46
|
-
? new MongoMigrationStorage(pool, { tableName: options.tableName })
|
|
47
|
-
: new DatabaseMigrationStorage(pool, { tableName: options.tableName }));
|
|
37
|
+
this.target = migrationTargetFor(pool.dialect);
|
|
38
|
+
this.defaultForeignKeyAction = options.defaultForeignKeyAction;
|
|
39
|
+
this.storage = options.storage ?? this.target.storage(pool, options.tableName);
|
|
48
40
|
this.migrationsPath = options.migrationsPath ?? './migrations';
|
|
49
41
|
this._logger = new LoggerWrapper(options.logger, { logValues: options.logValues, slowQuery: options.slowQuery });
|
|
50
42
|
this._entities = options.entities;
|
|
51
43
|
this.schemaIntrospector = this.createIntrospector();
|
|
52
|
-
this.schemaGenerator =
|
|
53
|
-
options.schemaGenerator ?? (this.dialectName === 'mongodb' ? undefined : this.createGenerator());
|
|
44
|
+
this.schemaGenerator = options.schemaGenerator;
|
|
54
45
|
}
|
|
55
|
-
/**
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
if (this.schemaGenerator || this.dialectName !== 'mongodb') {
|
|
61
|
-
return;
|
|
46
|
+
/** The schema generator, loaded on first use: MongoDB's needs its optional peer. */
|
|
47
|
+
async getSchemaGenerator() {
|
|
48
|
+
this.schemaGenerator ??= await this.target.generator(this.pool.dialect, this.defaultForeignKeyAction);
|
|
49
|
+
if (!this.schemaGenerator) {
|
|
50
|
+
throw new TypeError(`No schema generator for dialect '${this.dialectName}'`);
|
|
62
51
|
}
|
|
63
|
-
this.
|
|
64
|
-
this.schemaGenerator = new MongoSchemaGenerator(this.pool.dialect.namingStrategy, this._defaultForeignKeyAction);
|
|
65
|
-
});
|
|
66
|
-
await this._mongoSchemaLoadPromise;
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Set the schema generator for DDL operations
|
|
70
|
-
*/
|
|
71
|
-
setSchemaGenerator(generator) {
|
|
72
|
-
this.schemaGenerator = generator;
|
|
52
|
+
return this.schemaGenerator;
|
|
73
53
|
}
|
|
74
54
|
/** `schema` reads one namespace instead of the connection's own; see {@link BaseSqlIntrospector.schema}. */
|
|
75
55
|
createIntrospector(schema) {
|
|
76
56
|
return introspectorFor(this.dialectName, this.pool, schema);
|
|
77
57
|
}
|
|
78
|
-
createGenerator() {
|
|
79
|
-
if (!isKnownMigratorDialect(this.dialectName)) {
|
|
80
|
-
return undefined;
|
|
81
|
-
}
|
|
82
|
-
return createSchemaGenerator(this.pool.dialect, this._defaultForeignKeyAction);
|
|
83
|
-
}
|
|
84
58
|
/**
|
|
85
59
|
* Get all discovered migrations from the migrations directory
|
|
86
60
|
*/
|
|
@@ -159,10 +133,10 @@ export class Migrator {
|
|
|
159
133
|
*/
|
|
160
134
|
async runMigration(migration, direction) {
|
|
161
135
|
const startTime = Date.now();
|
|
162
|
-
return this.
|
|
136
|
+
return this.target.withSession(this.pool, async ({ querier, transaction }) => {
|
|
163
137
|
try {
|
|
164
138
|
this.logger.logMigration(`${direction === 'up' ? 'Running' : 'Reverting'} migration: ${migration.name}`);
|
|
165
|
-
await
|
|
139
|
+
await transaction(async () => {
|
|
166
140
|
if (direction === 'up') {
|
|
167
141
|
await migration.up(querier);
|
|
168
142
|
await this.storage.logWithQuerier(querier, migration.name);
|
|
@@ -194,25 +168,11 @@ export class Migrator {
|
|
|
194
168
|
}
|
|
195
169
|
});
|
|
196
170
|
}
|
|
197
|
-
/**
|
|
198
|
-
* A migration querier, and how to run work in one transaction on it. MongoDB gets none: it creates
|
|
199
|
-
* collections and indexes outside any transaction. SQL asserts its querier before opening one, so a
|
|
200
|
-
* wrong querier reports which one the dialect needs rather than a missing `transaction`.
|
|
201
|
-
*/
|
|
202
|
-
withMigrationQuerier(task) {
|
|
203
|
-
return this.dialectName === 'mongodb'
|
|
204
|
-
? withMongoQuerierForMigrations(this.pool, 'Migrator', (querier) => task(querier, (work) => work()))
|
|
205
|
-
: withSqlQuerierForMigrations(this.pool, 'Migrator', (querier) => task(querier, (work) => querier.transaction(work)));
|
|
206
|
-
}
|
|
207
|
-
/** What this dialect's migration files are written against. */
|
|
208
|
-
get migrationQuerier() {
|
|
209
|
-
return this.dialectName === 'mongodb' ? 'MongoQuerier' : 'SqlQuerier';
|
|
210
|
-
}
|
|
211
171
|
/**
|
|
212
172
|
* Generate a new migration file
|
|
213
173
|
*/
|
|
214
174
|
async generate(name) {
|
|
215
|
-
const { emptyUp, emptyDown } =
|
|
175
|
+
const { emptyUp, emptyDown } = this.target.source;
|
|
216
176
|
const filePath = await this.writeMigration(name, { upInner: emptyUp, downInner: emptyDown });
|
|
217
177
|
this.logger.logInfo(`Created migration: ${filePath}`);
|
|
218
178
|
return filePath;
|
|
@@ -223,7 +183,7 @@ export class Migrator {
|
|
|
223
183
|
const content = buildMigrationModule({
|
|
224
184
|
migrationName: name,
|
|
225
185
|
createdAt: new Date(),
|
|
226
|
-
querier: this.
|
|
186
|
+
querier: this.target.source.querier,
|
|
227
187
|
...body,
|
|
228
188
|
});
|
|
229
189
|
await mkdir(this.migrationsPath, { recursive: true });
|
|
@@ -234,31 +194,25 @@ export class Migrator {
|
|
|
234
194
|
* Generate a migration based on entity schema differences
|
|
235
195
|
*/
|
|
236
196
|
async generateFromEntities(name) {
|
|
237
|
-
const
|
|
238
|
-
const
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
downStatements.push(this.generator.generateDropTable(diff.tableName, { ifExists: true }));
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
else if (diff.type === 'alter') {
|
|
248
|
-
altering.push(...this.generator.generateAlterTable(diff));
|
|
249
|
-
downStatements.push(...this.generator.generateAlterTableDown(diff));
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
const upStatements = [...this.createSchema(creating), ...altering];
|
|
253
|
-
if (upStatements.length === 0) {
|
|
197
|
+
const generator = await this.getSchemaGenerator();
|
|
198
|
+
const { created, altered } = await this.pendingChanges();
|
|
199
|
+
const up = [
|
|
200
|
+
...this.createSchema(generator, created),
|
|
201
|
+
...altered.flatMap((diff) => generator.generateAlterTable(diff)),
|
|
202
|
+
];
|
|
203
|
+
if (up.length === 0) {
|
|
254
204
|
this.logger.logInfo('No schema changes detected.');
|
|
255
205
|
return '';
|
|
256
206
|
}
|
|
257
|
-
const
|
|
207
|
+
const down = [
|
|
208
|
+
...created.map((tableName) => generator.generateDropTable(tableName, { ifExists: true })),
|
|
209
|
+
...altered.flatMap((diff) => generator.generateAlterTableDown(diff)),
|
|
210
|
+
];
|
|
211
|
+
const { emit } = this.target.source;
|
|
258
212
|
const filePath = await this.writeMigration(name, {
|
|
259
213
|
docExtraLines: ['Generated from entity definitions'],
|
|
260
|
-
upInner: emit(
|
|
261
|
-
downInner: emit(
|
|
214
|
+
upInner: emit(up),
|
|
215
|
+
downInner: emit(down.reverse()),
|
|
262
216
|
});
|
|
263
217
|
this.logger.logInfo(`Created migration from entities: ${filePath}`);
|
|
264
218
|
return filePath;
|
|
@@ -267,26 +221,20 @@ export class Migrator {
|
|
|
267
221
|
* Get all schema differences between entities and database
|
|
268
222
|
*/
|
|
269
223
|
async getDiffs() {
|
|
270
|
-
await this.
|
|
271
|
-
if (!this.
|
|
272
|
-
throw new TypeError(
|
|
224
|
+
const generator = await this.getSchemaGenerator();
|
|
225
|
+
if (!this.schemaIntrospector) {
|
|
226
|
+
throw new TypeError(`No introspector for dialect '${this.dialectName}'`);
|
|
273
227
|
}
|
|
274
228
|
const ast = await this.introspectClaimedSchemas();
|
|
275
229
|
// Both sides built once: the database's above, the entities' here. Left to `diffSchema`, each
|
|
276
230
|
// entity would rebuild the whole AST, which is quadratic in the number of entities. Absent on a
|
|
277
231
|
// generator that compares no schema of its own - MongoDB, which reads only indexes.
|
|
278
|
-
const desiredAst =
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
const diff = this.schemaGenerator.diffSchema(entity, currentTable, desiredAst);
|
|
285
|
-
if (diff) {
|
|
286
|
-
diffs.push(diff);
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
return diffs;
|
|
232
|
+
const desiredAst = generator.buildAST?.(this.entities);
|
|
233
|
+
return this.entities.flatMap((entity) => {
|
|
234
|
+
const table = ast.getTable(generator.resolveTableName(getMeta(entity)));
|
|
235
|
+
const diff = generator.diffSchema(entity, table, desiredAst);
|
|
236
|
+
return diff ? [diff] : [];
|
|
237
|
+
});
|
|
290
238
|
}
|
|
291
239
|
/**
|
|
292
240
|
* One AST spanning every schema the entities claim, each table stamped with the schema it was read
|
|
@@ -309,17 +257,6 @@ export class Migrator {
|
|
|
309
257
|
}
|
|
310
258
|
return merged;
|
|
311
259
|
}
|
|
312
|
-
async findEntityForTable(tableName) {
|
|
313
|
-
await this.ensureSchemaGenerator();
|
|
314
|
-
for (const entity of this.entities) {
|
|
315
|
-
const meta = getMeta(entity);
|
|
316
|
-
const name = this.generator.resolveTableName(meta);
|
|
317
|
-
if (name === tableName) {
|
|
318
|
-
return entity;
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
return undefined;
|
|
322
|
-
}
|
|
323
260
|
/**
|
|
324
261
|
* Applies the entity schema to the database: every registered entity, or the one `entity` names.
|
|
325
262
|
*
|
|
@@ -344,10 +281,10 @@ export class Migrator {
|
|
|
344
281
|
* with no referential integrity; and the old drop loop went in reverse *declaration* order, which
|
|
345
282
|
* says nothing about the relation graph and is rejected as soon as the constraints are really there.
|
|
346
283
|
*/
|
|
347
|
-
forceStatements() {
|
|
284
|
+
forceStatements(generator) {
|
|
348
285
|
return [
|
|
349
|
-
...
|
|
350
|
-
...
|
|
286
|
+
...generator.generateDropSchema(this.entities, { ifExists: true, cascade: true }),
|
|
287
|
+
...generator.generateCreateSchema(this.entities),
|
|
351
288
|
];
|
|
352
289
|
}
|
|
353
290
|
/**
|
|
@@ -355,32 +292,26 @@ export class Migrator {
|
|
|
355
292
|
* existence check and is created with `IF NOT EXISTS`, so instances racing the same admin save
|
|
356
293
|
* settle instead of colliding; an existing one still pays for introspection, as a diff needs columns.
|
|
357
294
|
*/
|
|
358
|
-
async planEntity(entity, options) {
|
|
295
|
+
async planEntity(generator, entity, options) {
|
|
359
296
|
const meta = getMeta(entity);
|
|
360
|
-
// Before anything reads `this.generator`, whose own failure names neither the entity nor the
|
|
361
|
-
// dialect that has no support.
|
|
362
297
|
const introspector = this.introspectorFor(this.pool.dialect.resolveSchema(meta));
|
|
363
298
|
if (!introspector) {
|
|
364
299
|
throw new TypeError(`No introspector for '${meta.entity.name}' on '${this.dialectName}'`);
|
|
365
300
|
}
|
|
366
|
-
const tableName =
|
|
301
|
+
const tableName = generator.resolveTableName(meta);
|
|
367
302
|
return (await introspector.tableExists(tableName))
|
|
368
|
-
? this.alterFromEntity(entity, (await introspectSchema(introspector)).getTable(tableName), options)
|
|
303
|
+
? this.alterFromEntity(generator, entity, (await introspectSchema(introspector)).getTable(tableName), options)
|
|
369
304
|
: // Spanning the whole set, so a foreign key resolves against the tables it points at, and
|
|
370
305
|
// always including this entity: pinned to an explicit `entities` list, a sync of one outside
|
|
371
306
|
// it emitted nothing at all. `only` is what keeps the statements to this table.
|
|
372
|
-
|
|
373
|
-
}
|
|
374
|
-
/** An alter diff as statements, narrowed to what the caller allows. */
|
|
375
|
-
alterFromDiff(diff, options) {
|
|
376
|
-
return this.generator.generateAlterTable(this.filterDiff(diff, options));
|
|
307
|
+
generator.generateCreateSchema(this.entitiesWith(entity), { only: [tableName], ifNotExists: true });
|
|
377
308
|
}
|
|
378
309
|
/** The same for one entity against the table it already has, and nothing where the two agree. */
|
|
379
|
-
alterFromEntity(entity, table, options) {
|
|
310
|
+
alterFromEntity(generator, entity, table, options) {
|
|
380
311
|
// Spanning the set for the reason `planEntity` spells out: a foreign key needs the table it
|
|
381
312
|
// points at, which a sync of one entity outside the configured list would not otherwise have.
|
|
382
|
-
const diff =
|
|
383
|
-
return diff?.type === 'alter' ? this.
|
|
313
|
+
const diff = generator.diffSchema(entity, table, generator.buildAST?.(this.entitiesWith(entity)));
|
|
314
|
+
return diff?.type === 'alter' ? generator.generateAlterTable(this.filterDiff(diff, options)) : [];
|
|
384
315
|
}
|
|
385
316
|
/** The configured entities, with `entity` among them however the migrator was built. */
|
|
386
317
|
entitiesWith(entity) {
|
|
@@ -396,25 +327,18 @@ export class Migrator {
|
|
|
396
327
|
* statements rather than a summary of a second, differently-computed diff.
|
|
397
328
|
*/
|
|
398
329
|
async planSync(options = {}) {
|
|
399
|
-
await this.
|
|
330
|
+
const generator = await this.getSchemaGenerator();
|
|
400
331
|
if (options.force) {
|
|
401
|
-
return this.forceStatements();
|
|
332
|
+
return this.forceStatements(generator);
|
|
402
333
|
}
|
|
403
334
|
if (options.entity) {
|
|
404
|
-
return this.planEntity(options.entity, options);
|
|
335
|
+
return this.planEntity(generator, options.entity, options);
|
|
405
336
|
}
|
|
406
|
-
const
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
creating.push(diff.tableName);
|
|
412
|
-
}
|
|
413
|
-
else if (diff.type === 'alter') {
|
|
414
|
-
altering.push(...this.alterFromDiff(diff, options));
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
return [...this.createSchema(creating), ...altering];
|
|
337
|
+
const { created, altered } = await this.pendingChanges();
|
|
338
|
+
return [
|
|
339
|
+
...this.createSchema(generator, created),
|
|
340
|
+
...altered.flatMap((diff) => generator.generateAlterTable(this.filterDiff(diff, options))),
|
|
341
|
+
];
|
|
418
342
|
}
|
|
419
343
|
/**
|
|
420
344
|
* New tables are emitted together, never one at a time: a single-entity AST has no other table for a
|
|
@@ -424,31 +348,20 @@ export class Migrator {
|
|
|
424
348
|
*
|
|
425
349
|
* Empty in, empty out, so a diff with no new tables does not build an AST for the whole graph.
|
|
426
350
|
*/
|
|
427
|
-
createSchema(tableNames) {
|
|
428
|
-
return tableNames.length ?
|
|
351
|
+
createSchema(generator, tableNames) {
|
|
352
|
+
return tableNames.length ? generator.generateCreateSchema(this.entities, { only: tableNames }) : [];
|
|
429
353
|
}
|
|
430
354
|
/**
|
|
431
|
-
*
|
|
432
|
-
*
|
|
433
|
-
*
|
|
434
|
-
* `ALTER COLUMN`), so computing it eagerly for everyone would throw there.
|
|
355
|
+
* The pending diffs a sync or a generated migration acts on: the tables to create and the tables to
|
|
356
|
+
* alter. What to emit for each stays with the caller: a sync narrows an alter to what it allows and
|
|
357
|
+
* never asks for the rollback, which on SQLite cannot even be expressed (no `ALTER COLUMN`).
|
|
435
358
|
*/
|
|
436
|
-
async
|
|
359
|
+
async pendingChanges() {
|
|
437
360
|
const diffs = await this.getDiffs();
|
|
438
|
-
return
|
|
439
|
-
diff,
|
|
440
|
-
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
/**
|
|
444
|
-
* The schema generator. A getter because MongoDB's loads lazily (see {@link ensureSchemaGenerator}),
|
|
445
|
-
* so every caller had to repeat the same assertion after awaiting it.
|
|
446
|
-
*/
|
|
447
|
-
get generator() {
|
|
448
|
-
if (!this.schemaGenerator) {
|
|
449
|
-
throw new TypeError('Schema generator not set. Call setSchemaGenerator() first.');
|
|
450
|
-
}
|
|
451
|
-
return this.schemaGenerator;
|
|
361
|
+
return {
|
|
362
|
+
created: diffs.filter((diff) => diff.type === 'create').map((diff) => diff.tableName),
|
|
363
|
+
altered: diffs.filter((diff) => diff.type === 'alter'),
|
|
364
|
+
};
|
|
452
365
|
}
|
|
453
366
|
filterDiff(diff, options) {
|
|
454
367
|
const filteredDiff = { ...diff };
|
|
@@ -484,30 +397,18 @@ export class Migrator {
|
|
|
484
397
|
}
|
|
485
398
|
return filteredDiff;
|
|
486
399
|
}
|
|
400
|
+
/** Runs the statements a generator wrote, in one transaction where the engine takes DDL in one. */
|
|
487
401
|
async executeSyncStatements(statements, options) {
|
|
488
|
-
await this.
|
|
489
|
-
|
|
490
|
-
|
|
402
|
+
await this.target.withSession(this.pool, ({ run, transaction }) => transaction(async () => {
|
|
403
|
+
for (const statement of statements) {
|
|
404
|
+
if (options.logging)
|
|
405
|
+
this.logger.logSchema(`Executing: ${statement}`);
|
|
406
|
+
await run(statement);
|
|
407
|
+
}
|
|
408
|
+
}));
|
|
491
409
|
if (options.logging)
|
|
492
410
|
this.logger.logSchema('Schema synchronization completed');
|
|
493
411
|
}
|
|
494
|
-
async executeMongoSyncStatements(statements, options, querier) {
|
|
495
|
-
for (const statement of statements) {
|
|
496
|
-
if (options.logging)
|
|
497
|
-
this.logger.logSchema(`Executing MongoDB: ${statement}`);
|
|
498
|
-
await runMongoCommand(querier.db, statement);
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
async executeSqlSyncStatements(statements, options, querier) {
|
|
502
|
-
if (!isSqlQuerier(querier)) {
|
|
503
|
-
throw new TypeError('Migrator requires a SQL-based querier for this dialect');
|
|
504
|
-
}
|
|
505
|
-
for (const sql of statements) {
|
|
506
|
-
if (options.logging)
|
|
507
|
-
this.logger.logSchema(`Executing: ${sql}`);
|
|
508
|
-
await querier.run(sql);
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
412
|
/**
|
|
512
413
|
* Get migration status
|
|
513
414
|
*/
|
|
@@ -625,7 +526,7 @@ export function defineMigration(migration) {
|
|
|
625
526
|
export function defineBuilderMigration(migration) {
|
|
626
527
|
return {
|
|
627
528
|
...migration,
|
|
628
|
-
up: (querier) => migration.up(
|
|
629
|
-
down: (querier) => migration.down(
|
|
529
|
+
up: async (querier) => migration.up(await migrationBuilderFor(querier), querier),
|
|
530
|
+
down: async (querier) => migration.down(await migrationBuilderFor(querier), querier),
|
|
630
531
|
};
|
|
631
532
|
}
|
|
@@ -2,14 +2,14 @@ import type { AbstractSqlDialect } from '../dialect/index.js';
|
|
|
2
2
|
import type { SchemaAST } from '../schema/schemaAST.js';
|
|
3
3
|
import { type DiffOptions } from '../schema/schemaASTDiffer.js';
|
|
4
4
|
import type { CanonicalType, ColumnNode, ForeignKeyAction, IndexNode, TableNode } from '../schema/types.js';
|
|
5
|
-
import type { ColumnSchema, CreateSchemaOptions, DialectFeatures, DropSchemaOptions, EntityMeta, EntityWhereMeta, FieldMeta, FieldOptions, ForeignKeySchema, IndexSchema, MigratorDialect, NamingStrategy, SchemaDiff, SchemaGenerator,
|
|
6
|
-
import type { FullColumnDefinition, IndexDefinition, TableDefinition } from './builder/types.js';
|
|
5
|
+
import type { ColumnSchema, CreateSchemaOptions, DialectFeatures, DropSchemaOptions, EntityMeta, EntityWhereMeta, FieldMeta, FieldOptions, ForeignKeySchema, IndexSchema, MigratorDialect, NamingStrategy, SchemaDiff, SchemaGenerator, Type } from '../type/index.js';
|
|
6
|
+
import type { AnyMigrationOperation, FullColumnDefinition, IndexDefinition, TableDefinition } from './builder/types.js';
|
|
7
7
|
import { type IndexDdl, type TableDdl } from './ddl/index.js';
|
|
8
8
|
/**
|
|
9
9
|
* Unified SQL schema generator.
|
|
10
10
|
* Parameterized by dialect to handle Postgres, MySQL, MariaDB, and SQLite.
|
|
11
11
|
*/
|
|
12
|
-
export declare class SqlSchemaGenerator implements
|
|
12
|
+
export declare class SqlSchemaGenerator implements SchemaGenerator {
|
|
13
13
|
protected readonly dialect: AbstractSqlDialect;
|
|
14
14
|
protected readonly defaultForeignKeyAction: ForeignKeyAction;
|
|
15
15
|
/** `CREATE INDEX` for this dialect: the migrator's, so a runtime import carries none of it. */
|
|
@@ -24,6 +24,7 @@ export declare class SqlSchemaGenerator implements SqlDdlGenerator {
|
|
|
24
24
|
resolveSchema<E>(meta: EntityMeta<E>): string | undefined;
|
|
25
25
|
resolveColumnName(key: string, field: FieldOptions): string;
|
|
26
26
|
compileDdl(sql: EntityWhereMeta<object>, entity: Type<object>): string;
|
|
27
|
+
compileIndexPredicate(where: EntityWhereMeta<object>, entity: Type<object>, indexName: string): string;
|
|
27
28
|
/** Escape an identifier (table name, column name, etc.) */
|
|
28
29
|
protected escapeId(identifier: string): string;
|
|
29
30
|
/**
|
|
@@ -168,17 +169,18 @@ export declare class SqlSchemaGenerator implements SqlDdlGenerator {
|
|
|
168
169
|
}): string[];
|
|
169
170
|
generateCreateIndexFromDefinition(tableName: string, index: IndexDefinition): string;
|
|
170
171
|
generateRenameTableSql(oldName: string, newName: string): string;
|
|
172
|
+
/** `raw` is split, being the one SQL no generator wrote. */
|
|
173
|
+
generateOperation(operation: AnyMigrationOperation): string[];
|
|
171
174
|
/**
|
|
172
175
|
* `ADD COLUMN`, plus the constraint and index the column declares.
|
|
173
176
|
*
|
|
174
177
|
* `CREATE TABLE` lifts a column's `references` and `index` onto the table it is building; this had
|
|
175
178
|
* no lift, so a hand-written `addColumn(...).references(...).index()` emitted the column alone and
|
|
176
|
-
* dropped both without a word.
|
|
177
|
-
* already returns, and `execute` splits them.
|
|
179
|
+
* dropped both without a word.
|
|
178
180
|
*/
|
|
179
|
-
generateAddColumnSql(tableName: string, column: FullColumnDefinition): string;
|
|
180
|
-
generateAlterColumnSql(tableName: string, columnName: string, column: FullColumnDefinition): string;
|
|
181
|
-
generateDropColumnSql(tableName: string, columnName: string): string;
|
|
181
|
+
generateAddColumnSql(tableName: string, column: FullColumnDefinition): string[];
|
|
182
|
+
generateAlterColumnSql(tableName: string, columnName: string, column: FullColumnDefinition): string[];
|
|
183
|
+
generateDropColumnSql(tableName: string, columnName: string): string[];
|
|
182
184
|
generateRenameColumnSql(tableName: string, oldName: string, newName: string): string;
|
|
183
185
|
/**
|
|
184
186
|
* `CONSTRAINT <name> FOREIGN KEY (...) REFERENCES ... ON DELETE ... ON UPDATE ...`.
|
|
@@ -219,9 +221,6 @@ export declare class SqlSchemaGenerator implements SqlDdlGenerator {
|
|
|
219
221
|
* other way and the table is created under one name and compared under another, which reports every
|
|
220
222
|
* table of a project using a naming strategy as both missing and unexpected.
|
|
221
223
|
*/
|
|
222
|
-
export declare function buildEntityAST(generator: Pick<SchemaGenerator, 'resolveTableAlias' | 'resolveSchema' | 'resolveColumnName' | 'compileDdl'>, entities: readonly Type<object>[], defaultForeignKeyAction?: ForeignKeyAction): SchemaAST;
|
|
223
|
-
/**
|
|
224
|
-
* Synchronous factory for SQL schema generators only.
|
|
225
|
-
* For MongoDB, use `createSchemaGeneratorAsync` from `./schemaGeneratorAsync.js` so the optional `mongodb` peer is not loaded at import time.
|
|
226
|
-
*/
|
|
224
|
+
export declare function buildEntityAST(generator: Pick<SchemaGenerator, 'resolveTableAlias' | 'resolveSchema' | 'resolveColumnName' | 'compileDdl' | 'compileIndexPredicate'>, entities: readonly Type<object>[], defaultForeignKeyAction?: ForeignKeyAction): SchemaAST;
|
|
225
|
+
/** The SQL schema generator for `dialect`, `undefined` on MongoDB, whose generator needs its optional peer. */
|
|
227
226
|
export declare function createSchemaGenerator(dialect: MigratorDialect, defaultForeignKeyAction?: ForeignKeyAction): SqlSchemaGenerator | undefined;
|
|
@@ -6,10 +6,12 @@ import { diffRelationshipNodes, diffTable } from '../schema/schemaASTDiffer.js';
|
|
|
6
6
|
import { isAutoIncrement, qualifyName } from '../util/index.js';
|
|
7
7
|
import { derivedCheckName, derivedForeignKeyName, derivedPrimaryKeyName } from '../util/sql.util.js';
|
|
8
8
|
import { formatDefaultValue, SqlExpression } from './builder/expressions.js';
|
|
9
|
+
import { splitSqlStatements } from './builder/splitSqlStatements.js';
|
|
9
10
|
import { indexDdlFor, tableDdlFor } from './ddl/index.js';
|
|
10
11
|
import { sizedType } from './ddl/tableDdl.js';
|
|
11
12
|
import { columnForeignKey, columnIndex, fullColumnDefinitionToNode, renderIndexDefinition, tableDefinitionToNode, } from './generator/definitionToNode.js';
|
|
12
13
|
import { indexNodeToSchema } from './generator/indexNodeToSchema.js';
|
|
14
|
+
import { assertIndexPredicate } from './indexPredicate.js';
|
|
13
15
|
/**
|
|
14
16
|
* Unified SQL schema generator.
|
|
15
17
|
* Parameterized by dialect to handle Postgres, MySQL, MariaDB, and SQLite.
|
|
@@ -48,6 +50,10 @@ export class SqlSchemaGenerator {
|
|
|
48
50
|
compileDdl(sql, entity) {
|
|
49
51
|
return this.dialect.compileDdl(sql, entity);
|
|
50
52
|
}
|
|
53
|
+
compileIndexPredicate(where, entity, indexName) {
|
|
54
|
+
assertIndexPredicate(where, this.dialect.dialectName, indexName);
|
|
55
|
+
return this.dialect.compileDdl(where, entity);
|
|
56
|
+
}
|
|
51
57
|
/** Escape an identifier (table name, column name, etc.) */
|
|
52
58
|
escapeId(identifier) {
|
|
53
59
|
return this.dialect.escapeId(identifier);
|
|
@@ -577,13 +583,43 @@ export class SqlSchemaGenerator {
|
|
|
577
583
|
generateRenameTableSql(oldName, newName) {
|
|
578
584
|
return this.tableDdl.renameTable(oldName, newName);
|
|
579
585
|
}
|
|
586
|
+
/** `raw` is split, being the one SQL no generator wrote. */
|
|
587
|
+
generateOperation(operation) {
|
|
588
|
+
switch (operation.type) {
|
|
589
|
+
case 'createTable':
|
|
590
|
+
return this.generateCreateTableFromDefinition(operation.table);
|
|
591
|
+
case 'dropTable':
|
|
592
|
+
return [
|
|
593
|
+
this.generateDropTable(operation.tableName, { ifExists: operation.ifExists, cascade: operation.cascade }),
|
|
594
|
+
];
|
|
595
|
+
case 'renameTable':
|
|
596
|
+
return [this.generateRenameTableSql(operation.oldName, operation.newName)];
|
|
597
|
+
case 'addColumn':
|
|
598
|
+
return this.generateAddColumnSql(operation.tableName, operation.column);
|
|
599
|
+
case 'dropColumn':
|
|
600
|
+
return this.generateDropColumnSql(operation.tableName, operation.columnName);
|
|
601
|
+
case 'renameColumn':
|
|
602
|
+
return [this.generateRenameColumnSql(operation.tableName, operation.oldName, operation.newName)];
|
|
603
|
+
case 'alterColumn':
|
|
604
|
+
return this.generateAlterColumnSql(operation.tableName, operation.columnName, operation.changes);
|
|
605
|
+
case 'createIndex':
|
|
606
|
+
return [this.generateCreateIndexFromDefinition(operation.tableName, operation.index)];
|
|
607
|
+
case 'dropIndex':
|
|
608
|
+
return [this.generateDropIndex(operation.tableName, operation.indexName)];
|
|
609
|
+
case 'addForeignKey':
|
|
610
|
+
return [this.generateAddForeignKeySql(operation.tableName, operation.foreignKey)];
|
|
611
|
+
case 'dropForeignKey':
|
|
612
|
+
return [this.generateDropForeignKeySql(operation.tableName, operation.constraintName)];
|
|
613
|
+
case 'raw':
|
|
614
|
+
return splitSqlStatements(operation.sql);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
580
617
|
/**
|
|
581
618
|
* `ADD COLUMN`, plus the constraint and index the column declares.
|
|
582
619
|
*
|
|
583
620
|
* `CREATE TABLE` lifts a column's `references` and `index` onto the table it is building; this had
|
|
584
621
|
* no lift, so a hand-written `addColumn(...).references(...).index()` emitted the column alone and
|
|
585
|
-
* dropped both without a word.
|
|
586
|
-
* already returns, and `execute` splits them.
|
|
622
|
+
* dropped both without a word.
|
|
587
623
|
*/
|
|
588
624
|
generateAddColumnSql(tableName, column) {
|
|
589
625
|
this.assertColumnAddable(tableName, column);
|
|
@@ -598,14 +634,14 @@ export class SqlSchemaGenerator {
|
|
|
598
634
|
statements.push(this.generateCreateIndex(tableName, index));
|
|
599
635
|
}
|
|
600
636
|
statements.push(...this.generateColumnCommentStatement(tableName, column));
|
|
601
|
-
return statements
|
|
637
|
+
return statements;
|
|
602
638
|
}
|
|
603
639
|
generateAlterColumnSql(tableName, columnName, column) {
|
|
604
640
|
const node = fullColumnDefinitionToNode(column, tableName);
|
|
605
|
-
return this.generateAlterColumnStatements(tableName, { ...this.columnNodeToSchema(node), name: columnName }, this.generateColumnFromNode(node))
|
|
641
|
+
return this.generateAlterColumnStatements(tableName, { ...this.columnNodeToSchema(node), name: columnName }, this.generateColumnFromNode(node));
|
|
606
642
|
}
|
|
607
643
|
generateDropColumnSql(tableName, columnName) {
|
|
608
|
-
return this.tableDdl.dropColumn(tableName, columnName)
|
|
644
|
+
return this.tableDdl.dropColumn(tableName, columnName);
|
|
609
645
|
}
|
|
610
646
|
generateRenameColumnSql(tableName, oldName, newName) {
|
|
611
647
|
return this.tableDdl.renameColumn(tableName, oldName, newName);
|
|
@@ -726,13 +762,11 @@ export function buildEntityAST(generator, entities, defaultForeignKeyAction) {
|
|
|
726
762
|
resolveSchema: (meta) => generator.resolveSchema(meta),
|
|
727
763
|
resolveColumnName: (key, field) => generator.resolveColumnName(key, field),
|
|
728
764
|
compileDdl: (sql, entity) => generator.compileDdl(sql, entity),
|
|
765
|
+
compileIndexPredicate: (where, entity, indexName) => generator.compileIndexPredicate(where, entity, indexName),
|
|
729
766
|
defaultForeignKeyAction,
|
|
730
767
|
});
|
|
731
768
|
}
|
|
732
|
-
/**
|
|
733
|
-
* Synchronous factory for SQL schema generators only.
|
|
734
|
-
* For MongoDB, use `createSchemaGeneratorAsync` from `./schemaGeneratorAsync.js` so the optional `mongodb` peer is not loaded at import time.
|
|
735
|
-
*/
|
|
769
|
+
/** The SQL schema generator for `dialect`, `undefined` on MongoDB, whose generator needs its optional peer. */
|
|
736
770
|
export function createSchemaGenerator(dialect, defaultForeignKeyAction) {
|
|
737
771
|
return dialect.dialectName === 'mongodb' ? undefined : new SqlSchemaGenerator(dialect, defaultForeignKeyAction);
|
|
738
772
|
}
|
|
@@ -10,6 +10,11 @@ type MongoReadStages = {
|
|
|
10
10
|
/** Keys merged into the query's projection, when it has one: a vector search's score. */
|
|
11
11
|
readonly project?: Record<string, 1>;
|
|
12
12
|
};
|
|
13
|
+
/** Accumulator threaded through `$where` rendering: the relation lookups it needs, and their temp fields. */
|
|
14
|
+
type RelationLookups = {
|
|
15
|
+
readonly stages: MongoAggregationPipelineEntry<Document>[];
|
|
16
|
+
readonly temps: string[];
|
|
17
|
+
};
|
|
13
18
|
/** Default {@link DialectFeatures} for MongoDB; shared by {@link MongoDialect} and its schema generator. */
|
|
14
19
|
export declare const mongoDialectFeatures: DialectFeatures;
|
|
15
20
|
export declare class MongoDialect extends AbstractDialect {
|
|
@@ -46,7 +51,7 @@ export declare class MongoDialect extends AbstractDialect {
|
|
|
46
51
|
* recursion). Relation keys need `$lookup` stages, so they are only accepted when `lookups` is
|
|
47
52
|
* given - a plain `find`/`updateMany` filter has nowhere to put them.
|
|
48
53
|
*/
|
|
49
|
-
|
|
54
|
+
protected renderFilter<E extends Document>(entity: Type<E>, where?: QueryWhere<E>, lookups?: RelationLookups): Filter<E>;
|
|
50
55
|
/**
|
|
51
56
|
* Renders `$and`/`$or`/`$not`/`$nor` into `filter`. MongoDB has no root-level `$not`, so both
|
|
52
57
|
* negating operators become its `$nor`, which is exactly `NOT (a OR b)` - and by De Morgan that
|
|
@@ -29,6 +29,8 @@ export interface BuildSchemaASTOptions {
|
|
|
29
29
|
* predicate - which only a dialect can render. `buildEntityAST` supplies it from the generator.
|
|
30
30
|
*/
|
|
31
31
|
compileDdl?: (sql: EntityWhereMeta<object>, entity: Type<object>) => string;
|
|
32
|
+
/** A partial index's predicate as the engine writes it, `compileDdl` where none is given. `buildEntityAST` supplies it. */
|
|
33
|
+
compileIndexPredicate?: (where: EntityWhereMeta<object>, entity: Type<object>, indexName: string) => string;
|
|
32
34
|
}
|
|
33
35
|
/**
|
|
34
36
|
* Build a SchemaAST from entity classes (decorated with `@Entity`, `@Field`, etc.).
|