uql-orm 0.74.0 → 0.74.1
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/drift/driftDetector.js +2 -2
- package/dist/migrate/generator/mongoSchemaGenerator.d.ts +1 -0
- package/dist/migrate/generator/mongoSchemaGenerator.js +20 -8
- package/dist/migrate/migrator.js +11 -4
- package/dist/migrate/schemaGenerator.d.ts +6 -9
- package/dist/migrate/schemaGenerator.js +46 -74
- package/dist/schema/indexDifferences.d.ts +11 -1
- package/dist/schema/indexDifferences.js +34 -2
- package/dist/type/migration.d.ts +2 -1
- package/package.json +1 -1
|
@@ -199,7 +199,7 @@ function detectIndexDrifts(diff) {
|
|
|
199
199
|
table: idxDiff.table,
|
|
200
200
|
index: idxDiff.name,
|
|
201
201
|
details: `Index "${idxDiff.name}" exists in database but not defined in entity`,
|
|
202
|
-
suggestion: '
|
|
202
|
+
suggestion: 'Declare it, or drop it via migration: generate:entities drops one uql named',
|
|
203
203
|
});
|
|
204
204
|
}
|
|
205
205
|
else {
|
|
@@ -211,7 +211,7 @@ function detectIndexDrifts(diff) {
|
|
|
211
211
|
table: idxDiff.table,
|
|
212
212
|
index: idxDiff.name,
|
|
213
213
|
details: `Index "${idxDiff.name}" differs from the entity (${idxDiff.description})`,
|
|
214
|
-
suggestion: '
|
|
214
|
+
suggestion: 'Recreate it via migration, which generate:entities writes',
|
|
215
215
|
});
|
|
216
216
|
}
|
|
217
217
|
}
|
|
@@ -33,6 +33,7 @@ export declare class MongoSchemaGenerator extends MongoDialect implements Schema
|
|
|
33
33
|
generateDropTable(tableName: string): string;
|
|
34
34
|
generateAlterTable(diff: SchemaDiff): string[];
|
|
35
35
|
generateAlterTableDown(diff: SchemaDiff): string[];
|
|
36
|
+
private dropIndexCommand;
|
|
36
37
|
/** An index as MongoDB's key spec (`-1` descending, `'text'` full-text), refusing the SQL-only options. */
|
|
37
38
|
generateCreateIndex(tableName: string, index: IndexSchema): string;
|
|
38
39
|
/** An Atlas vector search index: its vector field first, then each field a `$vectorSearch` pre-filters on. */
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getMeta } from '../../entity/index.js';
|
|
2
2
|
import { MongoDialect } from '../../mongo/mongoDialect.js';
|
|
3
3
|
import { textLanguage } from '../../mongo/textLanguage.js';
|
|
4
|
+
import { indexChanges } from '../../schema/indexDifferences.js';
|
|
4
5
|
import { QueryRaw, } from '../../type/index.js';
|
|
5
6
|
import { indexDistance, unsupportedVectorMetric } from '../../type/vector.js';
|
|
6
7
|
import { declaredIndexes, declaredIndexName, renderIndexColumn } from '../../util/ddlExpression.util.js';
|
|
@@ -8,6 +9,7 @@ import { fulltextConfig, fulltextWeights } from '../../util/dialect.util.js';
|
|
|
8
9
|
import { assertIndexFeatures, assertIndexType } from '../ddl/indexDdl.js';
|
|
9
10
|
import { assertIndexPredicate, refusedIndexPredicate } from '../indexPredicate.js';
|
|
10
11
|
import { renderIndexDefinition } from './definitionToNode.js';
|
|
12
|
+
import { indexNodeToSchema } from './indexNodeToSchema.js';
|
|
11
13
|
import { serializeMongoCommand } from './mongoCommand.js';
|
|
12
14
|
/** The index types a key spec can say, a plain key or `'text'`, and Atlas's vector search index. */
|
|
13
15
|
const MONGO_INDEX_TYPES = new Set(['btree', 'fulltext', 'vectorSearch']);
|
|
@@ -105,12 +107,21 @@ export class MongoSchemaGenerator extends MongoDialect {
|
|
|
105
107
|
return serializeMongoCommand({ action: 'dropCollection', name: tableName });
|
|
106
108
|
}
|
|
107
109
|
generateAlterTable(diff) {
|
|
108
|
-
return
|
|
110
|
+
return [
|
|
111
|
+
...(diff.indexesToDrop ?? []).map((index) => this.dropIndexCommand(diff.tableName, index)),
|
|
112
|
+
...(diff.indexesToAdd ?? []).map((index) => this.generateCreateIndex(diff.tableName, index)),
|
|
113
|
+
];
|
|
109
114
|
}
|
|
110
115
|
generateAlterTableDown(diff) {
|
|
111
|
-
return
|
|
112
|
-
|
|
113
|
-
|
|
116
|
+
return [
|
|
117
|
+
...(diff.indexesToAdd ?? []).map((index) => this.dropIndexCommand(diff.tableName, index)),
|
|
118
|
+
...(diff.indexesToDrop ?? []).map((index) => this.generateCreateIndex(diff.tableName, index)),
|
|
119
|
+
];
|
|
120
|
+
}
|
|
121
|
+
dropIndexCommand(tableName, index) {
|
|
122
|
+
return index.type === 'vectorSearch'
|
|
123
|
+
? serializeMongoCommand({ action: 'dropSearchIndex', collection: tableName, name: index.name })
|
|
124
|
+
: this.generateDropIndex(tableName, index.name);
|
|
114
125
|
}
|
|
115
126
|
/** An index as MongoDB's key spec (`-1` descending, `'text'` full-text), refusing the SQL-only options. */
|
|
116
127
|
generateCreateIndex(tableName, index) {
|
|
@@ -200,15 +211,16 @@ export class MongoSchemaGenerator extends MongoDialect {
|
|
|
200
211
|
if (!currentTable) {
|
|
201
212
|
return { tableName: collectionName, type: 'create' };
|
|
202
213
|
}
|
|
203
|
-
|
|
204
|
-
const
|
|
205
|
-
if (
|
|
214
|
+
// By name: MongoDB lists a text index's fields alphabetically, so a shape would not match its own.
|
|
215
|
+
const { toAdd, toDrop } = indexChanges(collectionName, this.indexesOf(meta, collectionName), currentTable.indexes, (index) => index.name);
|
|
216
|
+
if (!toAdd.length && !toDrop.length) {
|
|
206
217
|
return undefined;
|
|
207
218
|
}
|
|
208
219
|
return {
|
|
209
220
|
tableName: collectionName,
|
|
210
221
|
type: 'alter',
|
|
211
|
-
indexesToAdd,
|
|
222
|
+
indexesToAdd: toAdd.length ? toAdd : undefined,
|
|
223
|
+
indexesToDrop: toDrop.length ? toDrop.map(indexNodeToSchema) : undefined,
|
|
212
224
|
};
|
|
213
225
|
}
|
|
214
226
|
}
|
package/dist/migrate/migrator.js
CHANGED
|
@@ -189,15 +189,16 @@ export class Migrator {
|
|
|
189
189
|
this.logger.logInfo('No schema changes detected.');
|
|
190
190
|
return '';
|
|
191
191
|
}
|
|
192
|
+
// Diff by diff in reverse, each rolled back in the order its generator wrote it.
|
|
192
193
|
const down = [
|
|
193
|
-
...
|
|
194
|
-
...
|
|
194
|
+
...altered.toReversed().flatMap((diff) => generator.generateAlterTableDown(diff)),
|
|
195
|
+
...created.toReversed().map((tableName) => generator.generateDropTable(tableName, { ifExists: true })),
|
|
195
196
|
];
|
|
196
197
|
const { emit } = this.target.source;
|
|
197
198
|
const filePath = await this.writeMigration(name, {
|
|
198
199
|
docExtraLines: ['Generated from entity definitions'],
|
|
199
200
|
upInner: emit(up),
|
|
200
|
-
downInner: emit(down
|
|
201
|
+
downInner: emit(down),
|
|
201
202
|
});
|
|
202
203
|
this.logger.logInfo(`Created migration from entities: ${filePath}`);
|
|
203
204
|
return filePath;
|
|
@@ -345,7 +346,13 @@ export class Migrator {
|
|
|
345
346
|
this.logger.logSkippedMigration(`[AutoSync] Skipped altering ${filteredDiff.foreignKeysToAlter.length} foreign keys in table '${diff.tableName}': ${filteredDiff.foreignKeysToAlter.map((fk) => fk.to.name).join(', ')} (safe mode active). Use a migration or { safe: false } to apply.`);
|
|
346
347
|
delete filteredDiff.foreignKeysToAlter;
|
|
347
348
|
}
|
|
348
|
-
|
|
349
|
+
if (filteredDiff.indexesToDrop?.length) {
|
|
350
|
+
// An index recreated under its old name is a drop and an add, held back together.
|
|
351
|
+
const dropped = new Set(filteredDiff.indexesToDrop.map((index) => index.name));
|
|
352
|
+
this.logger.logSkippedMigration(`[AutoSync] Skipped dropping ${dropped.size} indexes in table '${diff.tableName}': ${[...dropped].join(', ')} (safe mode active). Use a migration or { safe: false } to apply.`);
|
|
353
|
+
filteredDiff.indexesToAdd = filteredDiff.indexesToAdd?.filter((index) => !dropped.has(index.name));
|
|
354
|
+
delete filteredDiff.indexesToDrop;
|
|
355
|
+
}
|
|
349
356
|
delete filteredDiff.foreignKeysToDrop;
|
|
350
357
|
}
|
|
351
358
|
if (!options.drop && filteredDiff.columnsToDrop?.length) {
|
|
@@ -62,12 +62,16 @@ export declare class SqlSchemaGenerator implements SchemaGenerator {
|
|
|
62
62
|
private addForeignKeyStatements;
|
|
63
63
|
/** `DROP CONSTRAINT` for each of `constraintNames`, the mirror of {@link addForeignKeyStatements}. */
|
|
64
64
|
private dropForeignKeyStatements;
|
|
65
|
+
/** The `ALTER COLUMN` restating each of `columns`. */
|
|
66
|
+
private alterColumnStatements;
|
|
67
|
+
/** An index added to a table that may already have rows: its `CREATE`, then what the engine needs after. */
|
|
68
|
+
private addIndexStatements;
|
|
69
|
+
/** `DROP INDEX` for each of `indexes`, the mirror of {@link addIndexStatements}. */
|
|
70
|
+
private dropIndexStatements;
|
|
65
71
|
generateAlterTableDown(diff: SchemaDiff): string[];
|
|
66
72
|
generateCreateIndex(tableName: string, index: IndexSchema, options?: {
|
|
67
73
|
ifNotExists?: boolean;
|
|
68
74
|
}): string;
|
|
69
|
-
/** An index added to a table that may already have rows: its `CREATE`, then what the engine needs after. */
|
|
70
|
-
private addIndexStatements;
|
|
71
75
|
/**
|
|
72
76
|
* `schema` is the table's, because that is where its indexes live. MySQL takes it from the table
|
|
73
77
|
* operand instead, which is already qualified.
|
|
@@ -106,13 +110,6 @@ export declare class SqlSchemaGenerator implements SchemaGenerator {
|
|
|
106
110
|
* drift detection runs, with types normalized as the engine stores them.
|
|
107
111
|
*/
|
|
108
112
|
diffSchema(entity: Type<object>, currentTable: TableNode | undefined, desiredAst?: SchemaAST): SchemaDiff | undefined;
|
|
109
|
-
/**
|
|
110
|
-
* Indexes the entity declares that the table does not already have, in any shape.
|
|
111
|
-
*
|
|
112
|
-
* Additive only: an index the entity does not name may well have been created deliberately outside
|
|
113
|
-
* the ORM, and dropping it is a decision for a reviewed migration.
|
|
114
|
-
*/
|
|
115
|
-
private missingIndexes;
|
|
116
113
|
protected diffOptions(): DiffOptions;
|
|
117
114
|
/** Spread, not copied field by field, so a field the node gains cannot go missing here. */
|
|
118
115
|
private columnNodeToSchema;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getMeta } from '../entity/index.js';
|
|
2
2
|
import { canonicalToSql, engineType, isVectorCategory } from '../schema/canonicalType.js';
|
|
3
|
-
import {
|
|
3
|
+
import { indexChanges } from '../schema/indexDifferences.js';
|
|
4
4
|
import { buildSchemaAST, resolveColumnCanonicalType } from '../schema/schemaASTBuilder.js';
|
|
5
5
|
import { diffRelationshipNodes, diffTable } from '../schema/schemaASTDiffer.js';
|
|
6
6
|
import { isAutoIncrement, qualifyName } from '../util/index.js';
|
|
@@ -144,40 +144,19 @@ export class SqlSchemaGenerator {
|
|
|
144
144
|
...(diff.foreignKeysToDrop ?? []),
|
|
145
145
|
...(diff.foreignKeysToAlter ?? []).map((it) => constraintNameOf(diff.tableName, it.from)),
|
|
146
146
|
]));
|
|
147
|
-
//
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
statements.push(...colStatements);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
// Drop columns
|
|
164
|
-
if (diff.columnsToDrop?.length) {
|
|
165
|
-
for (const columnName of diff.columnsToDrop) {
|
|
166
|
-
statements.push(...this.tableDdl.dropColumn(diff.tableName, columnName));
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
// Add indexes
|
|
170
|
-
if (diff.indexesToAdd?.length) {
|
|
171
|
-
for (const index of diff.indexesToAdd) {
|
|
172
|
-
statements.push(...this.addIndexStatements(diff.tableName, index));
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
// Drop indexes
|
|
176
|
-
if (diff.indexesToDrop?.length) {
|
|
177
|
-
for (const indexName of diff.indexesToDrop) {
|
|
178
|
-
statements.push(this.generateDropIndex(diff.tableName, indexName, diff.schema));
|
|
179
|
-
}
|
|
180
|
-
}
|
|
147
|
+
// Before the adds, which may reuse a dropped index's name, and before the columns: some engines
|
|
148
|
+
// drop an index along with its column, which would leave nothing here to name.
|
|
149
|
+
statements.push(...this.dropIndexStatements(diff.tableName, diff.indexesToDrop, diff.schema));
|
|
150
|
+
for (const column of diff.columnsToAdd ?? []) {
|
|
151
|
+
this.assertColumnAddable(diff.tableName, column);
|
|
152
|
+
statements.push(this.tableDdl.addColumn(diff.tableName, this.generateColumnDefinitionFromSchema(column)));
|
|
153
|
+
statements.push(...this.generateColumnCommentStatement(diff.tableName, column, diff.schema));
|
|
154
|
+
}
|
|
155
|
+
statements.push(...this.alterColumnStatements(diff.tableName, (diff.columnsToAlter ?? []).map((it) => it.to)));
|
|
156
|
+
for (const columnName of diff.columnsToDrop ?? []) {
|
|
157
|
+
statements.push(...this.tableDdl.dropColumn(diff.tableName, columnName));
|
|
158
|
+
}
|
|
159
|
+
statements.push(...this.addIndexStatements(diff.tableName, diff.indexesToAdd));
|
|
181
160
|
// Last, so every column it names exists by now.
|
|
182
161
|
if (diff.primaryKey?.to.length) {
|
|
183
162
|
statements.push(this.generateAddPrimaryKeySql(diff.tableName, diff.primaryKey.to));
|
|
@@ -198,6 +177,21 @@ export class SqlSchemaGenerator {
|
|
|
198
177
|
dropForeignKeyStatements(tableName, constraintNames) {
|
|
199
178
|
return constraintNames.map((name) => this.generateDropForeignKeySql(tableName, name));
|
|
200
179
|
}
|
|
180
|
+
/** The `ALTER COLUMN` restating each of `columns`. */
|
|
181
|
+
alterColumnStatements(tableName, columns) {
|
|
182
|
+
return columns.flatMap((column) => this.generateAlterColumnStatements(tableName, column, this.generateColumnDefinitionFromSchema(column)));
|
|
183
|
+
}
|
|
184
|
+
/** An index added to a table that may already have rows: its `CREATE`, then what the engine needs after. */
|
|
185
|
+
addIndexStatements(tableName, indexes = []) {
|
|
186
|
+
return indexes.flatMap((index) => [
|
|
187
|
+
this.generateCreateIndex(tableName, index),
|
|
188
|
+
...this.indexDdl.settleStatements(tableName, index),
|
|
189
|
+
]);
|
|
190
|
+
}
|
|
191
|
+
/** `DROP INDEX` for each of `indexes`, the mirror of {@link addIndexStatements}. */
|
|
192
|
+
dropIndexStatements(tableName, indexes = [], schema) {
|
|
193
|
+
return indexes.map((index) => this.generateDropIndex(tableName, index.name, schema));
|
|
194
|
+
}
|
|
201
195
|
generateAlterTableDown(diff) {
|
|
202
196
|
const statements = [];
|
|
203
197
|
// Constraints first, mirroring the up direction: the up added them last, so the down drops them
|
|
@@ -212,44 +206,26 @@ export class SqlSchemaGenerator {
|
|
|
212
206
|
if (diff.primaryKey?.to.length) {
|
|
213
207
|
statements.push(this.generateDropPrimaryKeySql(diff.tableName, derivedPrimaryKeyName(diff.tableName, diff.primaryKey.to)));
|
|
214
208
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
for (const column of diff.columnsToAdd) {
|
|
218
|
-
statements.push(...this.tableDdl.dropColumn(diff.tableName, column.name));
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
// Reverse column alterations by restoring original schema
|
|
222
|
-
if (diff.columnsToAlter?.length) {
|
|
223
|
-
for (const { from } of diff.columnsToAlter) {
|
|
224
|
-
const colDef = this.generateColumnDefinitionFromSchema(from);
|
|
225
|
-
const colStatements = this.generateAlterColumnStatements(diff.tableName, from, colDef);
|
|
226
|
-
statements.push(...colStatements);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
// Reverse index additions by dropping them
|
|
230
|
-
if (diff.indexesToAdd?.length) {
|
|
231
|
-
for (const index of diff.indexesToAdd) {
|
|
232
|
-
statements.push(this.generateDropIndex(diff.tableName, index.name, diff.schema));
|
|
233
|
-
}
|
|
209
|
+
for (const column of diff.columnsToAdd ?? []) {
|
|
210
|
+
statements.push(...this.tableDdl.dropColumn(diff.tableName, column.name));
|
|
234
211
|
}
|
|
212
|
+
statements.push(...this.alterColumnStatements(diff.tableName, (diff.columnsToAlter ?? []).map((it) => it.from)));
|
|
213
|
+
statements.push(...this.dropIndexStatements(diff.tableName, diff.indexesToAdd, diff.schema));
|
|
214
|
+
statements.push(...this.addIndexStatements(diff.tableName, diff.indexesToDrop));
|
|
235
215
|
if (diff.primaryKey?.from.length) {
|
|
236
216
|
statements.push(this.generateAddPrimaryKeySql(diff.tableName, diff.primaryKey.from, diff.primaryKey.fromName));
|
|
237
217
|
}
|
|
238
218
|
// The constraint the up replaced, back under the name the database had for it. A foreign key the
|
|
239
219
|
// up *dropped* is not restored: only its name survived the diff, never what it pointed at.
|
|
240
220
|
statements.push(...this.addForeignKeyStatements(diff.tableName, (diff.foreignKeysToAlter ?? []).map((it) => it.from)));
|
|
241
|
-
if (diff.columnsToDrop?.length || diff.
|
|
242
|
-
statements.push(`-- TODO: Manual reversal needed for dropped columns/
|
|
221
|
+
if (diff.columnsToDrop?.length || diff.foreignKeysToDrop?.length) {
|
|
222
|
+
statements.push(`-- TODO: Manual reversal needed for dropped columns/foreign keys`);
|
|
243
223
|
}
|
|
244
224
|
return statements;
|
|
245
225
|
}
|
|
246
226
|
generateCreateIndex(tableName, index, options = {}) {
|
|
247
227
|
return this.indexDdl.getCreateIndexStatement(tableName, index, options);
|
|
248
228
|
}
|
|
249
|
-
/** An index added to a table that may already have rows: its `CREATE`, then what the engine needs after. */
|
|
250
|
-
addIndexStatements(tableName, index) {
|
|
251
|
-
return [this.generateCreateIndex(tableName, index), ...this.indexDdl.settleStatements(tableName, index)];
|
|
252
|
-
}
|
|
253
229
|
/**
|
|
254
230
|
* `schema` is the table's, because that is where its indexes live. MySQL takes it from the table
|
|
255
231
|
* operand instead, which is already qualified.
|
|
@@ -351,10 +327,12 @@ export class SqlSchemaGenerator {
|
|
|
351
327
|
}
|
|
352
328
|
// Indexes are matched here rather than by the differ, which pairs them by name so that a changed
|
|
353
329
|
// one reads as one index that altered. A migration needs the opposite: an index already in the
|
|
354
|
-
// table, under whatever name, must not be created again, and one whose shape differs is
|
|
355
|
-
//
|
|
330
|
+
// table, under whatever name, must not be created again, and one whose shape differs is dropped
|
|
331
|
+
// and created anew - no engine alters an index's columns or uniqueness.
|
|
356
332
|
const tableDiff = diffTable(desired, currentTable, { ...this.diffOptions(), compareIndexes: false });
|
|
357
|
-
const
|
|
333
|
+
const indexes = indexChanges(currentTable.name, desired.indexes, currentTable.indexes);
|
|
334
|
+
const indexesToAdd = indexes.toAdd.map(indexNodeToSchema);
|
|
335
|
+
const indexesToDrop = indexes.toDrop.map(indexNodeToSchema);
|
|
358
336
|
const columnDiffs = tableDiff?.columnDiffs ?? [];
|
|
359
337
|
const columnsToAdd = columnDiffs.flatMap((it) => (it.type === 'add' ? [this.columnNodeToSchema(it.expected)] : []));
|
|
360
338
|
const columnsToDrop = columnDiffs.flatMap((it) => (it.type === 'drop' ? [it.column] : []));
|
|
@@ -387,6 +365,7 @@ export class SqlSchemaGenerator {
|
|
|
387
365
|
!columnsToAlter.length &&
|
|
388
366
|
!columnsToDrop.length &&
|
|
389
367
|
!indexesToAdd.length &&
|
|
368
|
+
!indexesToDrop.length &&
|
|
390
369
|
!foreignKeysToAdd.length &&
|
|
391
370
|
!foreignKeysToDrop.length &&
|
|
392
371
|
!foreignKeysToAlter.length &&
|
|
@@ -402,21 +381,12 @@ export class SqlSchemaGenerator {
|
|
|
402
381
|
columnsToAlter: columnsToAlter.length ? columnsToAlter : undefined,
|
|
403
382
|
columnsToDrop: columnsToDrop.length ? columnsToDrop : undefined,
|
|
404
383
|
indexesToAdd: indexesToAdd.length ? indexesToAdd : undefined,
|
|
384
|
+
indexesToDrop: indexesToDrop.length ? indexesToDrop : undefined,
|
|
405
385
|
foreignKeysToAdd: foreignKeysToAdd.length ? foreignKeysToAdd : undefined,
|
|
406
386
|
foreignKeysToDrop: foreignKeysToDrop.length ? foreignKeysToDrop : undefined,
|
|
407
387
|
foreignKeysToAlter: foreignKeysToAlter.length ? foreignKeysToAlter : undefined,
|
|
408
388
|
};
|
|
409
389
|
}
|
|
410
|
-
/**
|
|
411
|
-
* Indexes the entity declares that the table does not already have, in any shape.
|
|
412
|
-
*
|
|
413
|
-
* Additive only: an index the entity does not name may well have been created deliberately outside
|
|
414
|
-
* the ORM, and dropping it is a decision for a reviewed migration.
|
|
415
|
-
*/
|
|
416
|
-
missingIndexes(desired, currentTable) {
|
|
417
|
-
const present = new Set(currentTable.indexes.map(indexSignature));
|
|
418
|
-
return desired.indexes.filter((index) => !present.has(indexSignature(index))).map(indexNodeToSchema);
|
|
419
|
-
}
|
|
420
390
|
diffOptions() {
|
|
421
391
|
return {
|
|
422
392
|
normalizeType: engineType(this.dialect),
|
|
@@ -563,7 +533,9 @@ export class SqlSchemaGenerator {
|
|
|
563
533
|
case 'alterColumn':
|
|
564
534
|
return this.generateAlterColumnSql(operation.tableName, operation.columnName, operation.changes);
|
|
565
535
|
case 'createIndex':
|
|
566
|
-
return this.addIndexStatements(operation.tableName,
|
|
536
|
+
return this.addIndexStatements(operation.tableName, [
|
|
537
|
+
renderIndexDefinition(operation.index, (sql) => this.dialect.compileDdl(sql)),
|
|
538
|
+
]);
|
|
567
539
|
case 'dropIndex':
|
|
568
540
|
return [this.generateDropIndex(operation.tableName, operation.indexName)];
|
|
569
541
|
case 'addForeignKey':
|
|
@@ -5,18 +5,28 @@ import type { IndexNode } from './types.js';
|
|
|
5
5
|
* `textIndex` is a text index's weights and language, kept by an engine that lists its fields in no declared order.
|
|
6
6
|
*/
|
|
7
7
|
export type IndexFacet = 'order' | 'nulls' | 'opsClass' | 'accessMethod' | 'include' | 'vector' | 'textIndex';
|
|
8
|
+
type ComparableIndex = Pick<IndexNode, 'name' | 'entries' | 'unique'>;
|
|
8
9
|
/**
|
|
9
10
|
* Whether the table has this index already, by shape rather than name, uniqueness included. An index
|
|
10
11
|
* over an expression, whose text the engine reprints, falls back to its name.
|
|
11
12
|
*/
|
|
12
|
-
export declare function indexSignature(index:
|
|
13
|
+
export declare function indexSignature(index: ComparableIndex): string;
|
|
13
14
|
/**
|
|
14
15
|
* A constraint name without its kind marker, pairing an index with its older spelling
|
|
15
16
|
* (`idx_User_email` with `User__email_idx`). Only one marker, the trailing one first.
|
|
16
17
|
*/
|
|
17
18
|
export declare function indexNameStem(name: string): string;
|
|
19
|
+
/**
|
|
20
|
+
* The indexes a table lacks and the ones it no longer needs, matched by `keyOf`. Only an index uql
|
|
21
|
+
* named, or whose name the entity claims, is dropped: any other may have been made outside the ORM.
|
|
22
|
+
*/
|
|
23
|
+
export declare function indexChanges<I extends ComparableIndex>(table: string, declared: readonly I[], current: readonly IndexNode[], keyOf?: (index: ComparableIndex) => string): {
|
|
24
|
+
toAdd: I[];
|
|
25
|
+
toDrop: IndexNode[];
|
|
26
|
+
};
|
|
18
27
|
/**
|
|
19
28
|
* What two indexes differ by, comparing only what both sides state structurally: an expression, a JSON
|
|
20
29
|
* path or a predicate is reprinted by the engine, so never compared.
|
|
21
30
|
*/
|
|
22
31
|
export declare function describeIndexDifferences(source: IndexNode, target: IndexNode, facets: ReadonlySet<IndexFacet>): string[];
|
|
32
|
+
export {};
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { isVectorIndexType } from '../type/vector.js';
|
|
2
2
|
import { fulltextConfig } from '../util/dialect.util.js';
|
|
3
|
+
import { derivedIndexName } from '../util/sql.util.js';
|
|
4
|
+
/** An entry the engine reprints in its own words, so never compared as written. */
|
|
5
|
+
function isReprinted(entry) {
|
|
6
|
+
return Boolean(entry.expression || entry.jsonPath || entry.jsonArray);
|
|
7
|
+
}
|
|
3
8
|
/**
|
|
4
9
|
* Whether the table has this index already, by shape rather than name, uniqueness included. An index
|
|
5
10
|
* over an expression, whose text the engine reprints, falls back to its name.
|
|
6
11
|
*/
|
|
7
12
|
export function indexSignature(index) {
|
|
8
|
-
const comparable = !index.entries.some(
|
|
13
|
+
const comparable = !index.entries.some(isReprinted);
|
|
9
14
|
const identity = comparable
|
|
10
15
|
? index.entries.map((entry) => entry.column).join(',')
|
|
11
16
|
: `name:${indexNameStem(index.name)}`;
|
|
@@ -20,6 +25,33 @@ export function indexNameStem(name) {
|
|
|
20
25
|
const bare = withoutSuffix === name ? name.replace(KIND_PREFIX, '') : withoutSuffix;
|
|
21
26
|
return bare.replace(/__/g, '_');
|
|
22
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* The indexes a table lacks and the ones it no longer needs, matched by `keyOf`. Only an index uql
|
|
30
|
+
* named, or whose name the entity claims, is dropped: any other may have been made outside the ORM.
|
|
31
|
+
*/
|
|
32
|
+
export function indexChanges(table, declared, current, keyOf = indexSignature) {
|
|
33
|
+
const present = new Set(current.map(keyOf));
|
|
34
|
+
const wanted = new Set(declared.map(keyOf));
|
|
35
|
+
const claimed = new Set(declared.map((index) => index.name));
|
|
36
|
+
const owned = (index) => claimed.has(index.name) || hasDerivedName(table, index);
|
|
37
|
+
return {
|
|
38
|
+
toAdd: declared.filter((index) => !present.has(keyOf(index))),
|
|
39
|
+
toDrop: current.filter((index) => !wanted.has(keyOf(index)) && owned(index)),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Whether uql named the index itself, from its own columns: `Order__total_idx`, its unique `_uk`, or
|
|
44
|
+
* the `idx_Order_total` it wrote until 0.42.1.
|
|
45
|
+
*/
|
|
46
|
+
function hasDerivedName(table, index) {
|
|
47
|
+
const parts = index.entries.map((entry, at) => (isReprinted(entry) ? `expr${at}` : entry.column));
|
|
48
|
+
const derived = [
|
|
49
|
+
derivedIndexName(table, parts),
|
|
50
|
+
derivedIndexName(table, parts, true),
|
|
51
|
+
`idx_${table}_${parts.join('_')}`,
|
|
52
|
+
];
|
|
53
|
+
return derived.includes(index.name);
|
|
54
|
+
}
|
|
23
55
|
/** What this version emits. */
|
|
24
56
|
const KIND_SUFFIX = /_(?:idx|fk|ck|pk|uk|uq)$/i;
|
|
25
57
|
/**
|
|
@@ -34,7 +66,7 @@ const KIND_PREFIX = /^(?:idx|fk|ck|pk|uk|uq)_/i;
|
|
|
34
66
|
*/
|
|
35
67
|
export function describeIndexDifferences(source, target, facets) {
|
|
36
68
|
const differences = [];
|
|
37
|
-
const comparableEntries = ![...source.entries, ...target.entries].some(
|
|
69
|
+
const comparableEntries = ![...source.entries, ...target.entries].some(isReprinted);
|
|
38
70
|
if (comparableEntries) {
|
|
39
71
|
const [sourceColumns, targetColumns] = [source, target].map((index) => textFieldOrder(index, facets, index.entries.map((entry) => entrySignature(entry, facets))).join(', '));
|
|
40
72
|
if (sourceColumns !== targetColumns) {
|
package/dist/type/migration.d.ts
CHANGED
|
@@ -203,7 +203,8 @@ export interface SchemaDiff {
|
|
|
203
203
|
}[];
|
|
204
204
|
readonly columnsToDrop?: string[];
|
|
205
205
|
readonly indexesToAdd?: IndexSchema[];
|
|
206
|
-
|
|
206
|
+
/** Whole rather than by name, so the rollback can create each again. */
|
|
207
|
+
readonly indexesToDrop?: IndexSchema[];
|
|
207
208
|
readonly foreignKeysToAdd?: ForeignKeySchema[];
|
|
208
209
|
/** Dropped under the name the *database* reported, which is the only name a `DROP` can use. */
|
|
209
210
|
readonly foreignKeysToDrop?: string[];
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"homepage": "https://uql-orm.dev",
|
|
4
4
|
"description": "The JSON-native TypeScript ORM for Bun, Browsers, Edge, Deno, Node, Workers. Supports PostgreSQL, PGlite, MySQL, MariaDB, SQLite, CockroachDB, SQL Server, Turso, Neon, Cloudflare D1 and MongoDB. Queries are plain JSON, typed to the leaf.",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"version": "0.74.
|
|
6
|
+
"version": "0.74.1",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"engines": {
|
|
9
9
|
"node": ">=24"
|