uql-orm 0.80.0 → 0.82.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.
Files changed (123) hide show
  1. package/README.md +3 -3
  2. package/dist/browser/querier/httpQuerier.d.ts +2 -2
  3. package/dist/browser/querier/httpQuerier.js +2 -1
  4. package/dist/browser/type/clientQuerier.d.ts +2 -2
  5. package/dist/browser/uql-browser.min.js +2 -2
  6. package/dist/browser/uql-browser.min.js.map +9 -8
  7. package/dist/bunSql/bunSql.util.js +2 -1
  8. package/dist/dialect/abstractSqlDialect.d.ts +15 -5
  9. package/dist/dialect/abstractSqlDialect.js +109 -40
  10. package/dist/dialect/operators.d.ts +7 -1
  11. package/dist/dialect/operators.js +13 -1
  12. package/dist/dialect/vectorSqlDialect.d.ts +2 -0
  13. package/dist/dialect/vectorSqlDialect.js +4 -0
  14. package/dist/entity/metadata/definition.d.ts +1 -2
  15. package/dist/entity/metadata/definition.js +37 -39
  16. package/dist/http/handler.js +5 -4
  17. package/dist/http/query.d.ts +1 -1
  18. package/dist/http/query.js +2 -2
  19. package/dist/index.d.ts +1 -0
  20. package/dist/index.js +1 -0
  21. package/dist/migrate/acquireQuerierForMigrations.js +2 -1
  22. package/dist/migrate/assertCliConfig.js +7 -6
  23. package/dist/migrate/bin.js +0 -0
  24. package/dist/migrate/builder/expressions.d.ts +2 -0
  25. package/dist/migrate/builder/expressions.js +26 -1
  26. package/dist/migrate/cli-config.js +5 -4
  27. package/dist/migrate/cli.js +1 -1
  28. package/dist/migrate/codegen/entityCodeGenerator.js +2 -2
  29. package/dist/migrate/codegen/indexDecoratorSource.d.ts +3 -2
  30. package/dist/migrate/codegen/indexDecoratorSource.js +5 -23
  31. package/dist/migrate/ddl/indexDdl.js +4 -3
  32. package/dist/migrate/ddl/mssqlTableDdl.d.ts +4 -4
  33. package/dist/migrate/ddl/mssqlTableDdl.js +20 -14
  34. package/dist/migrate/ddl/mysqlIndexDdl.d.ts +2 -2
  35. package/dist/migrate/ddl/mysqlIndexDdl.js +12 -10
  36. package/dist/migrate/ddl/pgIndexDdl.d.ts +2 -1
  37. package/dist/migrate/ddl/pgIndexDdl.js +10 -7
  38. package/dist/migrate/ddl/sqliteIndexDdl.js +2 -1
  39. package/dist/migrate/ddl/tableDdl.d.ts +3 -2
  40. package/dist/migrate/ddl/tableDdl.js +11 -8
  41. package/dist/migrate/drift/driftDetector.d.ts +4 -5
  42. package/dist/migrate/drift/driftDetector.js +21 -21
  43. package/dist/migrate/generator/definitionToNode.d.ts +1 -1
  44. package/dist/migrate/generator/definitionToNode.js +9 -20
  45. package/dist/migrate/generator/mongoCommand.js +2 -1
  46. package/dist/migrate/generator/mongoSchemaGenerator.d.ts +1 -1
  47. package/dist/migrate/generator/mongoSchemaGenerator.js +17 -24
  48. package/dist/migrate/index.d.ts +2 -1
  49. package/dist/migrate/index.js +1 -0
  50. package/dist/migrate/indexPredicate.js +2 -1
  51. package/dist/migrate/introspection/abstractSqlSchemaIntrospector.d.ts +5 -7
  52. package/dist/migrate/introspection/abstractSqlSchemaIntrospector.js +13 -19
  53. package/dist/migrate/introspection/baseSqlIntrospector.js +7 -18
  54. package/dist/migrate/introspection/mongoIntrospector.d.ts +1 -1
  55. package/dist/migrate/introspection/mongoIntrospector.js +5 -4
  56. package/dist/migrate/introspection/mssqlIntrospector.js +2 -1
  57. package/dist/migrate/introspection/mysqlIntrospector.d.ts +15 -5
  58. package/dist/migrate/introspection/mysqlIntrospector.js +32 -4
  59. package/dist/migrate/introspection/postgresIntrospector.d.ts +29 -21
  60. package/dist/migrate/introspection/postgresIntrospector.js +63 -46
  61. package/dist/migrate/introspection/sqliteIntrospector.js +11 -9
  62. package/dist/migrate/migrationTarget.js +2 -1
  63. package/dist/migrate/migrator.d.ts +5 -0
  64. package/dist/migrate/migrator.js +35 -45
  65. package/dist/migrate/schemaChange.d.ts +18 -0
  66. package/dist/migrate/schemaChange.js +37 -0
  67. package/dist/migrate/schemaGenerator.d.ts +13 -14
  68. package/dist/migrate/schemaGenerator.js +88 -181
  69. package/dist/migrate/triggerSql.d.ts +1 -1
  70. package/dist/migrate/triggerSql.js +77 -61
  71. package/dist/mongo/mongoDialect.d.ts +1 -3
  72. package/dist/mongo/mongoDialect.js +9 -14
  73. package/dist/mongo/mongodbQuerier.js +3 -7
  74. package/dist/querier/abstractQuerier.d.ts +9 -4
  75. package/dist/querier/abstractQuerier.js +26 -19
  76. package/dist/querier/abstractQuerierPool.d.ts +3 -3
  77. package/dist/querier/abstractSqlQuerier.d.ts +2 -2
  78. package/dist/querier/abstractSqlQuerier.js +1 -1
  79. package/dist/querier/abstractSqlQuerierPool.d.ts +2 -2
  80. package/dist/querier/queryError.d.ts +2 -2
  81. package/dist/schema/indexDifferences.d.ts +22 -6
  82. package/dist/schema/indexDifferences.js +23 -8
  83. package/dist/schema/matchByKey.d.ts +10 -0
  84. package/dist/schema/matchByKey.js +18 -0
  85. package/dist/schema/schemaAST.d.ts +6 -2
  86. package/dist/schema/schemaAST.js +7 -3
  87. package/dist/schema/schemaASTBuilder.d.ts +2 -0
  88. package/dist/schema/schemaASTBuilder.js +17 -11
  89. package/dist/schema/schemaASTDiffer.d.ts +2 -3
  90. package/dist/schema/schemaASTDiffer.js +15 -36
  91. package/dist/schema/types.d.ts +14 -15
  92. package/dist/type/dialect.d.ts +23 -1
  93. package/dist/type/entity.d.ts +15 -11
  94. package/dist/type/logger.d.ts +2 -2
  95. package/dist/type/migration.d.ts +32 -50
  96. package/dist/type/querier.d.ts +3 -3
  97. package/dist/type/query.d.ts +3 -13
  98. package/dist/type/queryAggregate.d.ts +4 -10
  99. package/dist/type/queryRaw.d.ts +17 -3
  100. package/dist/type/queryRaw.js +2 -1
  101. package/dist/type/queryWhere.d.ts +7 -7
  102. package/dist/type/universalQuerier.d.ts +3 -3
  103. package/dist/type/vector.d.ts +2 -1
  104. package/dist/type/vector.js +2 -1
  105. package/dist/util/ddlExpression.util.d.ts +5 -1
  106. package/dist/util/ddlExpression.util.js +6 -2
  107. package/dist/util/dialect.util.d.ts +13 -5
  108. package/dist/util/dialect.util.js +28 -20
  109. package/dist/util/field.util.d.ts +4 -4
  110. package/dist/util/field.util.js +10 -2
  111. package/dist/util/hook.util.d.ts +1 -1
  112. package/dist/util/hook.util.js +8 -1
  113. package/dist/util/index.d.ts +1 -0
  114. package/dist/util/index.js +1 -0
  115. package/dist/util/logger.d.ts +3 -3
  116. package/dist/util/object.util.js +3 -2
  117. package/dist/util/raw.d.ts +6 -7
  118. package/dist/util/raw.js +10 -12
  119. package/dist/util/sqlLiteral.js +3 -2
  120. package/dist/util/triggerWrite.d.ts +15 -0
  121. package/dist/util/triggerWrite.js +20 -0
  122. package/package.json +1 -1
  123. package/skills/uql-orm/SKILL.md +3 -3
@@ -0,0 +1,37 @@
1
+ /** Each change's end on `side`, where it has one: what a drop half removes (`from`), or an add half creates (`to`). */
2
+ export function sides(changes, side) {
3
+ return (changes ?? []).flatMap((change) => {
4
+ const end = change[side];
5
+ return end === undefined ? [] : [end];
6
+ });
7
+ }
8
+ /** What the changes add: each `to` with no `from`. */
9
+ export function added(changes = []) {
10
+ return changes.flatMap(({ from, to }) => (from === undefined && to !== undefined ? [to] : []));
11
+ }
12
+ /** What the changes drop: each `from` with no `to`. */
13
+ export function dropped(changes = []) {
14
+ return changes.flatMap(({ from, to }) => (to === undefined && from !== undefined ? [from] : []));
15
+ }
16
+ /** The changes that alter an object in place, which only a column can. */
17
+ export function alterations(changes = []) {
18
+ return changes.flatMap(({ from, to }) => (from === undefined || to === undefined ? [] : [{ from, to }]));
19
+ }
20
+ /** `items`, or nothing where it has none, so an empty change list is left off a diff. */
21
+ export function nonEmpty(items) {
22
+ return items.length ? items : undefined;
23
+ }
24
+ /** `change` undone: an add becomes a drop, a drop an add, and an alter runs the other way. */
25
+ export function swap({ from, to }) {
26
+ return { from: to, to: from };
27
+ }
28
+ /** `diff` undone: every change swapped, which is what a migration's `down` runs. */
29
+ export function reverseDiff(diff) {
30
+ return {
31
+ ...diff,
32
+ primaryKey: diff.primaryKey && swap(diff.primaryKey),
33
+ columns: diff.columns?.map(swap),
34
+ indexes: diff.indexes?.map(swap),
35
+ foreignKeys: diff.foreignKeys?.map(swap),
36
+ };
37
+ }
@@ -68,18 +68,19 @@ export declare class SqlSchemaGenerator implements SchemaGenerator {
68
68
  */
69
69
  private orderedTables;
70
70
  generateDropTable(tableName: string, options?: DropSchemaOptions): string;
71
+ /**
72
+ * The statements taking a table through `diff`, in the one order both directions need: whatever holds
73
+ * something down goes before it and comes back after it. A foreign key holds its columns and the key it
74
+ * points at, so it goes first and comes back last; the key holds its columns; and some engines drop an
75
+ * index along with its column, which would leave nothing to name. An alter is its drop, then its add.
76
+ */
71
77
  generateAlterTable(diff: SchemaDiff): string[];
72
78
  /** `ADD CONSTRAINT` for each of `foreignKeys`. */
73
79
  private addForeignKeyStatements;
74
- /** `DROP CONSTRAINT` for each of `constraintNames`, the mirror of {@link addForeignKeyStatements}. */
75
- private dropForeignKeyStatements;
76
- /** The `ALTER COLUMN` restating each of `columns`. */
77
- private alterColumnStatements;
78
80
  /** An index added to a table that may already have rows: its `CREATE`, then what the engine needs after. */
79
81
  private addIndexStatements;
80
- /** `DROP INDEX` for each of `indexes`, the mirror of {@link addIndexStatements}. */
81
- private dropIndexStatements;
82
- generateAlterTableDown(diff: SchemaDiff): string[];
82
+ /** A column added to a table that exists, and its comment where the engine keeps one apart. */
83
+ private addColumnStatements;
83
84
  generateCreateIndex(tableName: string, index: IndexSchema, options?: {
84
85
  ifNotExists?: boolean;
85
86
  }): string;
@@ -96,8 +97,8 @@ export declare class SqlSchemaGenerator implements SchemaGenerator {
96
97
  /**
97
98
  * The one place a column definition is spelled, so the `ColumnSchema` and `ColumnNode` paths cannot
98
99
  * drift. A key column states `NOT NULL` rather than leave it to the key: SQLite lets a key column hold
99
- * NULL otherwise, and SQL Server adds no key over a nullable column. `UNIQUE` is left to the key. An
100
- * enum's `CHECK` comes last, the only place MariaDB takes it.
100
+ * NULL otherwise, and SQL Server adds no key over a nullable column. Never `UNIQUE`: a unique column is
101
+ * a unique index, which the table creates beside it. An enum's `CHECK` comes last, the only place MariaDB takes it.
101
102
  */
102
103
  private renderColumn;
103
104
  /**
@@ -124,10 +125,8 @@ export declare class SqlSchemaGenerator implements SchemaGenerator {
124
125
  protected diffOptions(): DiffOptions;
125
126
  /** Spread, not copied field by field, so a field the node gains cannot go missing here. */
126
127
  private columnNodeToSchema;
127
- /**
128
- * Compare two default values for equality
129
- */
130
- protected isDefaultValueEqual(current: unknown, desired: unknown): boolean;
128
+ /** Whether a column's stored default is the one the entity declares, as this engine reprints it. */
129
+ readonly defaultsEqual: (desired: unknown, current: unknown) => boolean;
131
130
  generateCreateTableFromNode(table: TableNode, options?: {
132
131
  ifNotExists?: boolean;
133
132
  }): string[];
@@ -186,4 +185,4 @@ export declare class SqlSchemaGenerator implements SchemaGenerator {
186
185
  * The entities as an AST, named by `generator`'s resolvers rather than a naming strategy, which would
187
186
  * also rename an explicit `@Entity({ name })` and so compare each table under another name.
188
187
  */
189
- export declare function buildEntityAST(generator: Pick<SchemaGenerator, 'resolveTableAlias' | 'resolveSchema' | 'resolveColumnName' | 'compileDdl' | 'compileIndexPredicate'>, entities: readonly Type<object>[], options?: Pick<BuildSchemaASTOptions, 'defaultForeignKeyAction' | 'textScoreIndexes'>): SchemaAST;
188
+ export declare function buildEntityAST(generator: Pick<SchemaGenerator, 'resolveTableAlias' | 'resolveSchema' | 'resolveColumnName' | 'compileDdl' | 'compileIndexPredicate'>, entities: readonly Type<object>[], options?: Pick<BuildSchemaASTOptions, 'defaultForeignKeyAction' | 'textScoreIndexes' | 'vectorIndexRequiresNotNull'>): SchemaAST;
@@ -5,13 +5,15 @@ import { buildSchemaAST } from '../schema/schemaASTBuilder.js';
5
5
  import { diffRelationshipNodes, diffTable } from '../schema/schemaASTDiffer.js';
6
6
  import { isAutoIncrement, qualifyName } from '../util/index.js';
7
7
  import { derivedCheckName, derivedForeignKeyName, derivedPrimaryKeyName, isOwnedName } from '../util/sql.util.js';
8
- import { formatDefaultValue, SqlExpression } from './builder/expressions.js';
8
+ import { UqlUsageError } from '../util/uqlError.js';
9
+ import { sameDefault } from './builder/expressions.js';
9
10
  import { splitSqlStatements } from './builder/splitSqlStatements.js';
10
11
  import { indexDdlFor, tableDdlFor } from './ddl/index.js';
11
12
  import { sizedType } from './ddl/tableDdl.js';
12
13
  import { columnForeignKey, columnIndex, fullColumnDefinitionToNode, renderIndexDefinition, tableDefinitionToNode, } from './generator/definitionToNode.js';
13
14
  import { indexNodeToSchema } from './generator/indexNodeToSchema.js';
14
15
  import { assertIndexPredicate } from './indexPredicate.js';
16
+ import { added, alterations, dropped, nonEmpty, sides } from './schemaChange.js';
15
17
  import { dropTrigger, renderTrigger, stampTriggers } from './triggerSql.js';
16
18
  /**
17
19
  * Unified SQL schema generator.
@@ -78,6 +80,7 @@ export class SqlSchemaGenerator {
78
80
  return buildEntityAST(this, entities, {
79
81
  defaultForeignKeyAction: this.defaultForeignKeyAction,
80
82
  textScoreIndexes: this.dialect.features.textScoreIndexes,
83
+ vectorIndexRequiresNotNull: this.features.vectorIndexRequiresNotNull,
81
84
  });
82
85
  }
83
86
  /**
@@ -173,97 +176,45 @@ export class SqlSchemaGenerator {
173
176
  const cascade = options.cascade && this.features.dropTableCascade ? ' CASCADE' : '';
174
177
  return `DROP TABLE ${ifExists}${this.escapeId(tableName)}${cascade};`;
175
178
  }
179
+ /**
180
+ * The statements taking a table through `diff`, in the one order both directions need: whatever holds
181
+ * something down goes before it and comes back after it. A foreign key holds its columns and the key it
182
+ * points at, so it goes first and comes back last; the key holds its columns; and some engines drop an
183
+ * index along with its column, which would leave nothing to name. An alter is its drop, then its add.
184
+ */
176
185
  generateAlterTable(diff) {
177
- const statements = [];
178
- // Before the columns, because a key column being added cannot be part of the old key, and after
179
- // it is dropped the table is free to take the new one below.
180
- if (diff.primaryKey?.from.length) {
181
- statements.push(this.generateDropPrimaryKeySql(diff.tableName, diff.primaryKey.fromName));
182
- }
183
- // Before the columns: a constraint holds its columns down, so one the entity dropped cannot go
184
- // while a foreign key still names it. An alter is a drop and an add, and this is its drop half.
185
- statements.push(...this.dropForeignKeyStatements(diff.tableName, [
186
- ...(diff.foreignKeysToDrop ?? []),
187
- ...(diff.foreignKeysToAlter ?? []).map((it) => constraintNameOf(diff.tableName, it.from)),
188
- ]));
189
- // Before the adds, which may reuse a dropped index's name, and before the columns: some engines
190
- // drop an index along with its column, which would leave nothing here to name.
191
- statements.push(...this.dropIndexStatements(diff.tableName, diff.indexesToDrop, diff.schema));
192
- for (const column of diff.columnsToAdd ?? []) {
193
- this.assertColumnAddable(diff.tableName, column);
194
- statements.push(this.tableDdl.addColumn(diff.tableName, this.generateColumnDefinitionFromSchema(column)));
195
- statements.push(...this.generateColumnCommentStatement(diff.tableName, column, diff.schema));
196
- }
197
- statements.push(...this.alterColumnStatements(diff.tableName, (diff.columnsToAlter ?? []).map((it) => it.to)));
198
- for (const columnName of diff.columnsToDrop ?? []) {
199
- statements.push(...this.tableDdl.dropColumn(diff.tableName, columnName));
200
- }
201
- statements.push(...this.addIndexStatements(diff.tableName, diff.indexesToAdd));
202
- // Last, so every column it names exists by now.
203
- if (diff.primaryKey?.to.length) {
204
- statements.push(this.generateAddPrimaryKeySql(diff.tableName, diff.primaryKey.to));
205
- }
206
- // After the columns, for the same reason the key is: a constraint cannot name one that is not
207
- // there yet. The add half of an alter rides along, its drop having gone out above.
208
- statements.push(...this.addForeignKeyStatements(diff.tableName, [
209
- ...(diff.foreignKeysToAdd ?? []),
210
- ...(diff.foreignKeysToAlter ?? []).map((it) => it.to),
211
- ]));
212
- return statements;
186
+ const { tableName, schema, primaryKey } = diff;
187
+ const { columns } = diff;
188
+ return [
189
+ ...sides(diff.foreignKeys, 'from').map((foreignKey) => this.generateDropForeignKeySql(tableName, constraintNameOf(tableName, foreignKey))),
190
+ ...(primaryKey?.from ? [this.generateDropPrimaryKeySql(tableName, primaryKey.from.name)] : []),
191
+ ...sides(diff.indexes, 'from').map((index) => this.generateDropIndex(tableName, index.name, schema)),
192
+ ...added(columns).flatMap((column) => this.addColumnStatements(tableName, column, schema)),
193
+ ...alterations(columns).flatMap(({ from, to }) => this.tableDdl.alterColumn(tableName, to, this.generateColumnDefinitionFromSchema(to), from)),
194
+ ...dropped(columns).flatMap((column) => this.tableDdl.dropColumn(tableName, column.name)),
195
+ ...this.addIndexStatements(tableName, sides(diff.indexes, 'to')),
196
+ ...(primaryKey?.to ? [this.generateAddPrimaryKeySql(tableName, primaryKey.to.columns, primaryKey.to.name)] : []),
197
+ ...this.addForeignKeyStatements(tableName, sides(diff.foreignKeys, 'to')),
198
+ ];
213
199
  }
214
200
  /** `ADD CONSTRAINT` for each of `foreignKeys`. */
215
201
  addForeignKeyStatements(tableName, foreignKeys) {
216
202
  return foreignKeys.map((foreignKey) => this.generateAddForeignKeySql(tableName, foreignKey));
217
203
  }
218
- /** `DROP CONSTRAINT` for each of `constraintNames`, the mirror of {@link addForeignKeyStatements}. */
219
- dropForeignKeyStatements(tableName, constraintNames) {
220
- return constraintNames.map((name) => this.generateDropForeignKeySql(tableName, name));
221
- }
222
- /** The `ALTER COLUMN` restating each of `columns`. */
223
- alterColumnStatements(tableName, columns) {
224
- return columns.flatMap((column) => this.generateAlterColumnStatements(tableName, column, this.generateColumnDefinitionFromSchema(column)));
225
- }
226
204
  /** An index added to a table that may already have rows: its `CREATE`, then what the engine needs after. */
227
- addIndexStatements(tableName, indexes = []) {
205
+ addIndexStatements(tableName, indexes) {
228
206
  return indexes.flatMap((index) => [
229
207
  this.generateCreateIndex(tableName, index),
230
208
  ...this.indexDdl.settleStatements(tableName, index),
231
209
  ]);
232
210
  }
233
- /** `DROP INDEX` for each of `indexes`, the mirror of {@link addIndexStatements}. */
234
- dropIndexStatements(tableName, indexes = [], schema) {
235
- return indexes.map((index) => this.generateDropIndex(tableName, index.name, schema));
236
- }
237
- generateAlterTableDown(diff) {
238
- const statements = [];
239
- // Constraints first, mirroring the up direction: the up added them last, so the down drops them
240
- // first, and a column it is about to drop is then free of anything naming it.
241
- statements.push(...this.dropForeignKeyStatements(diff.tableName, [
242
- ...(diff.foreignKeysToAdd ?? []).map((it) => constraintNameOf(diff.tableName, it)),
243
- ...(diff.foreignKeysToAlter ?? []).map((it) => constraintNameOf(diff.tableName, it.to)),
244
- ]));
245
- // The key next, for the same reason: a column the up added cannot be dropped below while the new
246
- // key still names it. Restored under the name the database gave it, which is what the table had
247
- // before, rather than a derived one that was never on it.
248
- if (diff.primaryKey?.to.length) {
249
- statements.push(this.generateDropPrimaryKeySql(diff.tableName, derivedPrimaryKeyName(diff.tableName, diff.primaryKey.to)));
250
- }
251
- for (const column of diff.columnsToAdd ?? []) {
252
- statements.push(...this.tableDdl.dropColumn(diff.tableName, column.name));
253
- }
254
- statements.push(...this.alterColumnStatements(diff.tableName, (diff.columnsToAlter ?? []).map((it) => it.from)));
255
- statements.push(...this.dropIndexStatements(diff.tableName, diff.indexesToAdd, diff.schema));
256
- statements.push(...this.addIndexStatements(diff.tableName, diff.indexesToDrop));
257
- if (diff.primaryKey?.from.length) {
258
- statements.push(this.generateAddPrimaryKeySql(diff.tableName, diff.primaryKey.from, diff.primaryKey.fromName));
259
- }
260
- // The constraint the up replaced, back under the name the database had for it. A foreign key the
261
- // up *dropped* is not restored: only its name survived the diff, never what it pointed at.
262
- statements.push(...this.addForeignKeyStatements(diff.tableName, (diff.foreignKeysToAlter ?? []).map((it) => it.from)));
263
- if (diff.columnsToDrop?.length || diff.foreignKeysToDrop?.length) {
264
- statements.push(`-- TODO: Manual reversal needed for dropped columns/foreign keys`);
265
- }
266
- return statements;
211
+ /** A column added to a table that exists, and its comment where the engine keeps one apart. */
212
+ addColumnStatements(tableName, column, schema) {
213
+ this.assertColumnAddable(tableName, column);
214
+ return [
215
+ this.tableDdl.addColumn(tableName, this.generateColumnDefinitionFromSchema(column)),
216
+ ...this.generateColumnCommentStatement(tableName, column, schema),
217
+ ];
267
218
  }
268
219
  generateCreateIndex(tableName, index, options = {}) {
269
220
  return this.indexDdl.getCreateIndexStatement(tableName, index, options);
@@ -288,8 +239,8 @@ export class SqlSchemaGenerator {
288
239
  /**
289
240
  * The one place a column definition is spelled, so the `ColumnSchema` and `ColumnNode` paths cannot
290
241
  * drift. A key column states `NOT NULL` rather than leave it to the key: SQLite lets a key column hold
291
- * NULL otherwise, and SQL Server adds no key over a nullable column. `UNIQUE` is left to the key. An
292
- * enum's `CHECK` comes last, the only place MariaDB takes it.
242
+ * NULL otherwise, and SQL Server adds no key over a nullable column. Never `UNIQUE`: a unique column is
243
+ * a unique index, which the table creates beside it. An enum's `CHECK` comes last, the only place MariaDB takes it.
293
244
  */
294
245
  renderColumn(column) {
295
246
  const type = column.generatedAs
@@ -299,9 +250,6 @@ export class SqlSchemaGenerator {
299
250
  if (!column.nullable) {
300
251
  def += ' NOT NULL';
301
252
  }
302
- if (column.isUnique && !column.isPrimaryKey) {
303
- def += ' UNIQUE';
304
- }
305
253
  def += this.tableDdl.defaultClause(column);
306
254
  if (column.comment) {
307
255
  def += this.generateColumnComment(column.comment);
@@ -367,72 +315,61 @@ export class SqlSchemaGenerator {
367
315
  if (!desired) {
368
316
  return undefined;
369
317
  }
370
- // Indexes are matched here rather than by the differ, which pairs them by name so that a changed
371
- // one reads as one index that altered. A migration needs the opposite: an index already in the
372
- // table, under whatever name, must not be created again, and one whose shape differs is dropped
373
- // and created anew - no engine alters an index's columns or uniqueness.
374
318
  const tableDiff = diffTable(desired, currentTable, { ...this.diffOptions(), compareIndexes: false });
375
- const indexes = indexChanges(currentTable.name, desired.indexes, currentTable.indexes);
376
- const indexesToAdd = indexes.toAdd.map(indexNodeToSchema);
377
- const indexesToDrop = indexes.toDrop.map(indexNodeToSchema);
378
- const columnDiffs = tableDiff?.columnDiffs ?? [];
379
- const columnsToAdd = columnDiffs.flatMap((it) => (it.type === 'add' ? [this.columnNodeToSchema(it.expected)] : []));
380
- const columnsToDrop = columnDiffs.flatMap((it) => (it.type === 'drop' ? [it.column] : []));
381
- // Without its values: an alter restates the whole column, and MySQL answers a restated `CHECK` by
382
- // adding a *second* constraint rather than replacing the first, so the column would accumulate one
383
- // per alter. An enum's values reach the database with the column and are never restated - which is
384
- // also why changing them is a hand-written migration. See architecture/roadmap.md.
385
- const columnsToAlter = columnDiffs.flatMap((it) => it.type === 'alter'
386
- ? [
387
- {
388
- from: this.columnNodeToSchema(it.actual),
389
- to: { ...this.columnNodeToSchema(it.expected), enum: undefined },
390
- },
391
- ]
392
- : []);
393
- const primaryKey = tableDiff?.primaryKeyDiff && {
394
- from: tableDiff.primaryKeyDiff.actual,
395
- to: tableDiff.primaryKeyDiff.expected,
396
- fromName: tableDiff.primaryKeyDiff.actualName,
319
+ const indexes = indexChanges(currentTable.name, desired.indexes, currentTable.indexes, currentTable.indexFacets);
320
+ // An alter's `to` without its values: an alter restates the whole column, and MySQL answers a
321
+ // restated `CHECK` by adding a *second* constraint rather than replacing the first, so the column
322
+ // would accumulate one per alter. An enum's values reach the database with the column and are never
323
+ // restated - which is also why changing them is a hand-written migration. See architecture/roadmap.md.
324
+ const columns = (tableDiff?.columnDiffs ?? []).map((it) => {
325
+ if (it.type === 'add') {
326
+ return { to: this.columnNodeToSchema(it.expected) };
327
+ }
328
+ if (it.type === 'drop') {
329
+ return { from: this.columnNodeToSchema(it.actual) };
330
+ }
331
+ return {
332
+ from: this.columnNodeToSchema(it.actual),
333
+ to: { ...this.columnNodeToSchema(it.expected), enum: undefined },
334
+ };
335
+ });
336
+ const keyDiff = tableDiff?.primaryKeyDiff;
337
+ // The key added named as this generator names it, so the rollback can drop it by that name.
338
+ const primaryKey = keyDiff && {
339
+ from: keyDiff.actual,
340
+ to: keyDiff.expected && {
341
+ columns: keyDiff.expected.columns,
342
+ name: derivedPrimaryKeyName(tableName, keyDiff.expected.columns),
343
+ },
397
344
  };
398
345
  // This table's own foreign keys. None where the engine cannot alter one (SQLite, short of rebuilding
399
346
  // the table), since a difference nothing can apply would throw on every sync; `drift:check` names it.
400
347
  const relationDiffs = this.features.foreignKeyAlter
401
348
  ? diffRelationshipNodes(desired.outgoingRelations, currentTable.outgoingRelations, this.diffOptions())
402
349
  : [];
403
- const foreignKeysToAdd = relationDiffs.flatMap((it) => (it.type === 'create' ? [foreignKeyOf(it.expected)] : []));
404
- const foreignKeysToDrop = relationDiffs.flatMap((it) => (it.type === 'drop' ? [it.name] : []));
405
- const foreignKeysToAlter = relationDiffs.flatMap((it) => it.type === 'alter' ? [{ from: foreignKeyOf(it.actual), to: foreignKeyOf(it.expected) }] : []);
406
- if (!columnsToAdd.length &&
407
- !columnsToAlter.length &&
408
- !columnsToDrop.length &&
409
- !indexesToAdd.length &&
410
- !indexesToDrop.length &&
411
- !foreignKeysToAdd.length &&
412
- !foreignKeysToDrop.length &&
413
- !foreignKeysToAlter.length &&
414
- !primaryKey) {
415
- return undefined;
416
- }
417
- return {
350
+ const foreignKeys = relationDiffs.map(({ actual, expected }) => ({
351
+ from: actual && foreignKeyOf(actual),
352
+ to: expected && foreignKeyOf(expected),
353
+ }));
354
+ const alter = {
418
355
  tableName,
419
356
  schema,
420
357
  type: 'alter',
421
358
  primaryKey,
422
- columnsToAdd: columnsToAdd.length ? columnsToAdd : undefined,
423
- columnsToAlter: columnsToAlter.length ? columnsToAlter : undefined,
424
- columnsToDrop: columnsToDrop.length ? columnsToDrop : undefined,
425
- indexesToAdd: indexesToAdd.length ? indexesToAdd : undefined,
426
- indexesToDrop: indexesToDrop.length ? indexesToDrop : undefined,
427
- foreignKeysToAdd: foreignKeysToAdd.length ? foreignKeysToAdd : undefined,
428
- foreignKeysToDrop: foreignKeysToDrop.length ? foreignKeysToDrop : undefined,
429
- foreignKeysToAlter: foreignKeysToAlter.length ? foreignKeysToAlter : undefined,
359
+ columns: nonEmpty(columns),
360
+ indexes: nonEmpty([
361
+ ...indexes.toAdd.map((to) => ({ to: indexNodeToSchema(to) })),
362
+ ...indexes.toDrop.map((from) => ({ from: indexNodeToSchema(from) })),
363
+ ...indexes.toAlter.map(({ from, to }) => ({ from: indexNodeToSchema(from), to: indexNodeToSchema(to) })),
364
+ ]),
365
+ foreignKeys: nonEmpty(foreignKeys),
430
366
  };
367
+ return alter.primaryKey || alter.columns || alter.indexes || alter.foreignKeys ? alter : undefined;
431
368
  }
432
369
  diffOptions() {
433
370
  return {
434
371
  normalizeType: engineType(this.dialect),
435
- defaultsEqual: (expected, actual) => this.isDefaultValueEqual(actual, expected),
372
+ defaultsEqual: this.defaultsEqual,
436
373
  };
437
374
  }
438
375
  /** Spread, not copied field by field, so a field the node gains cannot go missing here. */
@@ -440,58 +377,28 @@ export class SqlSchemaGenerator {
440
377
  const { table: _table, referencedBy: _referencedBy, references: _references, ...column } = col;
441
378
  return { ...column, type: this.columnSqlType(col) };
442
379
  }
443
- /**
444
- * Compare two default values for equality
445
- */
446
- isDefaultValueEqual(current, desired) {
447
- if (current === desired)
448
- return true;
449
- // Both spellings of "no default" are the same fact, and engines disagree on which they report:
450
- // MariaDB says `null` where MySQL says nothing at all. Reading them as different values asked to
451
- // `MODIFY` every nullable column, on every sync, forever.
452
- if (current == null || desired == null)
453
- return current == null && desired == null;
454
- const normalize = (value) => {
455
- // Render first: the desired side may be a symbolic expression, the current side is always the
456
- // engine's own text, and `{"kind":"now"}` matches no spelling of `CURRENT_TIMESTAMP`.
457
- const val = SqlExpression.isExpression(value) ? formatDefaultValue(value, this.dialect) : value;
458
- if (typeof val === 'string') {
459
- let s = val.replace(/::[a-z_]+(\s+[a-z_]+)*(\[\])?$/i, '');
460
- s = s.replace(/^'(.*)'$/, '$1');
461
- if (s.toLowerCase() === 'null')
462
- return 'null';
463
- return s;
464
- }
465
- return typeof val === 'object' ? JSON.stringify(val) : String(val);
466
- };
467
- return normalize(current) === normalize(desired);
468
- }
380
+ /** Whether a column's stored default is the one the entity declares, as this engine reprints it. */
381
+ defaultsEqual = (desired, current) => sameDefault(desired, current, this.dialect);
469
382
  generateCreateTableFromNode(table, options = {}) {
470
383
  const columns = [];
471
384
  const constraints = [];
472
- // MariaDB rejects a `VECTOR INDEX` whose column is nullable ("All parts of a VECTOR index must
473
- // be NOT NULL"), so being indexed decides it rather than the entity's own nullability.
474
- const indexedVectorColumns = new Set(this.features.vectorIndexRequiresNotNull
475
- ? table.indexes.filter((index) => index.type === 'vector').flatMap((idx) => idx.entries.map((e) => e.column))
476
- : []);
477
385
  for (const col of table.columns.values()) {
478
- const colDef = this.generateColumnFromNode(indexedVectorColumns.has(col.name) ? { ...col, nullable: false } : col);
479
- columns.push(colDef);
386
+ columns.push(this.generateColumnFromNode(col));
480
387
  }
481
388
  // Every key, of any width, as one named constraint beside the checks and foreign keys - so a
482
389
  // later `DROP` has something to name. The exception is a dialect whose serial type states the key
483
390
  // itself (SQLite's `INTEGER PRIMARY KEY AUTOINCREMENT`, which cannot be split): there the column
484
391
  // has already declared it, and saying it again is a second primary key.
392
+ const key = table.primaryKey;
485
393
  const declaredByColumn = this.dialect.features.serialDeclaresPrimaryKey &&
486
- table.primaryKey.length === 1 &&
487
- table.primaryKey[0].isAutoIncrement;
488
- if (table.primaryKey.length && !declaredByColumn) {
489
- const pkColumns = table.primaryKey.map((c) => c.name);
490
- const pkName = table.primaryKeyName ?? derivedPrimaryKeyName(table.name, pkColumns);
491
- const pkCols = pkColumns.map((c) => this.escapeId(c)).join(', ');
492
- constraints.push(`CONSTRAINT ${this.escapeId(pkName)} PRIMARY KEY (${pkCols})`);
493
- }
494
- (table.checks ?? []).forEach((check, i) => {
394
+ key?.columns.length === 1 &&
395
+ table.columns.get(key.columns[0])?.isAutoIncrement;
396
+ if (key && !declaredByColumn) {
397
+ const name = key.name ?? derivedPrimaryKeyName(table.name, key.columns);
398
+ const columns = key.columns.map((column) => this.escapeId(column)).join(', ');
399
+ constraints.push(`CONSTRAINT ${this.escapeId(name)} PRIMARY KEY (${columns})`);
400
+ }
401
+ table.checks.forEach((check, i) => {
495
402
  const name = check.name ?? derivedCheckName(table.name, i + 1);
496
403
  constraints.push(`CONSTRAINT ${this.escapeId(name)} CHECK (${check.expression})`);
497
404
  });
@@ -630,7 +537,7 @@ export class SqlSchemaGenerator {
630
537
  }
631
538
  generateAddForeignKeySql(tableName, foreignKey) {
632
539
  if (!this.features.foreignKeyAlter) {
633
- throw new TypeError(`Dialect ${this.dialect} does not support adding foreign keys to existing tables`);
540
+ throw new UqlUsageError(`Dialect ${this.dialect} does not support adding foreign keys to existing tables`);
634
541
  }
635
542
  const constraint = this.foreignKeyConstraint(tableName, foreignKey, this.escapeId(foreignKey.references.table));
636
543
  return `ALTER TABLE ${this.escapeId(tableName)} ADD ${constraint};`;
@@ -660,7 +567,7 @@ export class SqlSchemaGenerator {
660
567
  return `ALTER TABLE ${table} DROP PRIMARY KEY;`;
661
568
  }
662
569
  if (!constraintName) {
663
- throw new TypeError(`Cannot drop the primary key of "${tableName}": ${this.dialect} names the constraint, and ` +
570
+ throw new UqlUsageError(`Cannot drop the primary key of "${tableName}": ${this.dialect} names the constraint, and ` +
664
571
  'introspection did not report a name for it.');
665
572
  }
666
573
  return `ALTER TABLE ${table} DROP CONSTRAINT ${this.escapeId(constraintName)};`;
@@ -673,7 +580,7 @@ export class SqlSchemaGenerator {
673
580
  if (!column.generatedAs || this.features.generatedColumnAdd) {
674
581
  return;
675
582
  }
676
- throw new TypeError(`${this.dialect}: Cannot add the computed column "${column.name}" to the existing table ` +
583
+ throw new UqlUsageError(`${this.dialect}: Cannot add the computed column "${column.name}" to the existing table ` +
677
584
  `"${tableName}" - this database only accepts one in a CREATE TABLE. Drop \`stored\` to have the ` +
678
585
  'expression spliced into each statement instead, or recreate the table in a written migration.');
679
586
  }
@@ -681,7 +588,7 @@ export class SqlSchemaGenerator {
681
588
  if (this.features.primaryKeyAlter) {
682
589
  return;
683
590
  }
684
- throw new TypeError(`${this.dialect}: Cannot change the primary key of "${tableName}" - this database has no ALTER ` +
591
+ throw new UqlUsageError(`${this.dialect}: Cannot change the primary key of "${tableName}" - this database has no ALTER ` +
685
592
  'for it. Recreate the table in a written migration.');
686
593
  }
687
594
  }
@@ -21,4 +21,4 @@ export declare function dropTrigger<E>(dialect: AbstractSqlDialect, meta: Entity
21
21
  * column. Generated rather than authored, so unlike an authored body it renders on every engine from
22
22
  * one declaration - and not at all on the MySQL family, whose columns stamp themselves.
23
23
  */
24
- export declare function stampTriggers<E>(dialect: AbstractSqlDialect, meta: EntityMeta<E>): EntityTriggerMeta<E>[];
24
+ export declare function stampTriggers<E extends object>(dialect: AbstractSqlDialect, meta: EntityMeta<E>): EntityTriggerMeta<E>[];