uql-orm 0.52.0 → 0.53.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 (38) hide show
  1. package/dist/browser/uql-browser.min.js +2 -2
  2. package/dist/browser/uql-browser.min.js.map +5 -5
  3. package/dist/dialect/abstractDialect.d.ts +5 -5
  4. package/dist/dialect/abstractDialect.js +7 -6
  5. package/dist/dialect/abstractSqlDialect.d.ts +2 -3
  6. package/dist/dialect/abstractSqlDialect.js +5 -8
  7. package/dist/dialect/mysqlLikeSqlDialect.d.ts +0 -1
  8. package/dist/dialect/mysqlLikeSqlDialect.js +0 -3
  9. package/dist/dialect/pgLikeSqlDialect.js +0 -2
  10. package/dist/dialect/vectorSqlDialect.js +2 -1
  11. package/dist/http/handler.js +3 -12
  12. package/dist/http/query.js +4 -1
  13. package/dist/migrate/ddl/index.d.ts +8 -0
  14. package/dist/migrate/ddl/index.js +11 -0
  15. package/dist/migrate/ddl/mssqlTableDdl.d.ts +23 -0
  16. package/dist/migrate/ddl/mssqlTableDdl.js +61 -0
  17. package/dist/migrate/ddl/tableDdl.d.ts +34 -0
  18. package/dist/migrate/ddl/tableDdl.js +71 -0
  19. package/dist/migrate/introspection/mssqlIntrospector.d.ts +6 -9
  20. package/dist/migrate/introspection/mssqlIntrospector.js +40 -33
  21. package/dist/migrate/schemaGenerator.d.ts +8 -10
  22. package/dist/migrate/schemaGenerator.js +29 -74
  23. package/dist/mongo/mongoDialect.js +9 -14
  24. package/dist/mssql/mssqlDialect.d.ts +12 -1
  25. package/dist/mssql/mssqlDialect.js +20 -7
  26. package/dist/querier/abstractQuerier.js +13 -11
  27. package/dist/querier/abstractSqlQuerier.js +3 -3
  28. package/dist/schema/canonicalType.js +2 -2
  29. package/dist/sqlite/sqliteDialect.js +0 -2
  30. package/dist/type/dialect.d.ts +0 -8
  31. package/dist/type/query.d.ts +1 -1
  32. package/dist/type/queryWhere.d.ts +7 -19
  33. package/dist/type/vector.d.ts +3 -1
  34. package/dist/util/dialect.util.d.ts +10 -7
  35. package/dist/util/dialect.util.js +16 -29
  36. package/dist/util/object.util.d.ts +2 -0
  37. package/dist/util/object.util.js +4 -0
  38. package/package.json +2 -2
@@ -0,0 +1,61 @@
1
+ import { escapeSingleQuotes } from '../../util/sqlLiteral.js';
2
+ import { sizedType, TableDdl } from './tableDdl.js';
3
+ /** The constraints of each kind on column `c`, the `sys.columns` row {@link MsSqlTableDdl} reads. */
4
+ const CONSTRAINTS_ON = {
5
+ default: /*sql*/ `SELECT d.name FROM sys.default_constraints d
6
+ WHERE d.parent_object_id = c.object_id AND d.parent_column_id = c.column_id`,
7
+ check: /*sql*/ `SELECT k.name FROM sys.check_constraints k
8
+ WHERE k.parent_object_id = c.object_id AND k.parent_column_id = c.column_id`,
9
+ unique: /*sql*/ `SELECT u.name FROM sys.key_constraints u
10
+ JOIN sys.index_columns ic ON ic.object_id = u.parent_object_id AND ic.index_id = u.unique_index_id
11
+ WHERE u.parent_object_id = c.object_id AND u.type = 'UQ' AND ic.column_id = c.column_id`,
12
+ };
13
+ /**
14
+ * SQL Server keeps a column's `DEFAULT`, `CHECK` and `UNIQUE` as constraints under names it picks, and
15
+ * refuses to drop or retype the column past one, so they go first - looked up by column, as no two
16
+ * databases name them alike. Renames are `sp_rename`, T-SQL having no `RENAME` clause.
17
+ */
18
+ export class MsSqlTableDdl extends TableDdl {
19
+ /** T-SQL rejects the optional `COLUMN` keyword after `ADD`. */
20
+ addColumn(table, definition) {
21
+ return /*sql*/ `ALTER TABLE ${this.dialect.escapeId(table)} ADD ${definition};`;
22
+ }
23
+ /** Its constraints go with the column, as they do on every other engine. */
24
+ dropColumn(table, column) {
25
+ return [this.dropConstraints(table, column, Object.values(CONSTRAINTS_ON)), ...super.dropColumn(table, column)];
26
+ }
27
+ /**
28
+ * `ALTER COLUMN` takes the type and nullability alone, so the default is dropped and added back as a
29
+ * constraint of its own. A `CHECK` or `UNIQUE` stays, and the server refuses a retype it blocks.
30
+ */
31
+ alterColumn(table, column) {
32
+ const target = this.dialect.escapeId(table);
33
+ const name = this.dialect.escapeId(column.name);
34
+ const statements = [
35
+ this.dropConstraints(table, column.name, [CONSTRAINTS_ON.default]),
36
+ /*sql*/ `ALTER TABLE ${target} ALTER COLUMN ${name} ${sizedType(column)} ${column.nullable ? 'NULL' : 'NOT NULL'};`,
37
+ ];
38
+ if (column.defaultValue !== undefined) {
39
+ statements.push(/*sql*/ `ALTER TABLE ${target} ADD${this.defaultClause(column)} FOR ${name};`);
40
+ }
41
+ return statements;
42
+ }
43
+ renameColumn(table, oldName, newName) {
44
+ const qualified = `${this.dialect.escapeId(table)}.${this.dialect.escapeId(oldName)}`;
45
+ return /*sql*/ `EXEC sp_rename ${this.dialect.escape(qualified)}, ${this.dialect.escape(newName)}, 'COLUMN';`;
46
+ }
47
+ renameTable(oldName, newName) {
48
+ return /*sql*/ `EXEC sp_rename ${this.dialect.escape(this.dialect.escapeId(oldName))}, ${this.dialect.escape(newName)};`;
49
+ }
50
+ storedGeneratedColumn(_type, expression) {
51
+ return /*sql*/ `AS (${expression}) PERSISTED`;
52
+ }
53
+ /** One statement, so a split on `;` cannot part the lookup from the `EXEC` it feeds. */
54
+ dropConstraints(table, column, kinds) {
55
+ const target = this.dialect.escapeId(table);
56
+ return (`DECLARE @drop nvarchar(max) = (SELECT STRING_AGG(N'ALTER TABLE ${escapeSingleQuotes(target)} DROP CONSTRAINT ' ` +
57
+ `+ QUOTENAME(pinned.name), N'; ') FROM sys.columns c CROSS APPLY (${kinds.join(' UNION ALL ')}) pinned ` +
58
+ `WHERE c.object_id = OBJECT_ID(${this.dialect.escape(target)}) AND c.name = ${this.dialect.escape(column)}) ` +
59
+ 'EXEC (@drop);');
60
+ }
61
+ }
@@ -0,0 +1,34 @@
1
+ import type { AbstractSqlDialect } from '../../dialect/abstractSqlDialect.js';
2
+ import type { ColumnSchema } from '../../type/index.js';
3
+ /**
4
+ * A column's type with the size it was read back with, unless its spelling already carries one:
5
+ * introspection reports `VARCHAR` and `255` apart, where a type from an entity is already whole.
6
+ */
7
+ export declare function sizedType(column: Pick<ColumnSchema, 'type' | 'length' | 'precision' | 'scale'>): string;
8
+ /**
9
+ * The `ALTER TABLE` statements an engine spells its own way: the migrator's, for the reason
10
+ * {@link IndexDdl} is. The form here is the portable one; SQL Server's is {@link MsSqlTableDdl}.
11
+ */
12
+ export declare class TableDdl {
13
+ protected readonly dialect: AbstractSqlDialect;
14
+ constructor(dialect: AbstractSqlDialect);
15
+ addColumn(table: string, definition: string): string;
16
+ dropColumn(table: string, column: string): string[];
17
+ /**
18
+ * What changes `column` to what it now declares. `definition` is the whole column, which MySQL's
19
+ * `MODIFY COLUMN` restates; Postgres takes each change as a clause of its own.
20
+ */
21
+ alterColumn(table: string, column: ColumnSchema, definition: string): string[];
22
+ renameColumn(table: string, oldName: string, newName: string): string;
23
+ renameTable(oldName: string, newName: string): string;
24
+ /** A stored generated column's type, with the clause computing it. */
25
+ storedGeneratedColumn(type: string, expression: string): string;
26
+ /**
27
+ * ` DEFAULT <sql>`, or nothing where the column declares none. Empty rather than `DEFAULT NULL`, so
28
+ * an absent default stays absent - `defaultValue: null` is the way to ask for one.
29
+ */
30
+ defaultClause(column: {
31
+ readonly defaultValue?: unknown;
32
+ readonly type: string;
33
+ }): string;
34
+ }
@@ -0,0 +1,71 @@
1
+ import { formatDefaultValue } from '../builder/expressions.js';
2
+ /**
3
+ * A column's type with the size it was read back with, unless its spelling already carries one:
4
+ * introspection reports `VARCHAR` and `255` apart, where a type from an entity is already whole.
5
+ */
6
+ export function sizedType(column) {
7
+ if (column.type.includes('(')) {
8
+ return column.type;
9
+ }
10
+ if (column.precision !== undefined) {
11
+ const size = column.scale === undefined ? column.precision : `${column.precision}, ${column.scale}`;
12
+ return `${column.type}(${size})`;
13
+ }
14
+ return column.length === undefined ? column.type : `${column.type}(${column.length})`;
15
+ }
16
+ /**
17
+ * The `ALTER TABLE` statements an engine spells its own way: the migrator's, for the reason
18
+ * {@link IndexDdl} is. The form here is the portable one; SQL Server's is {@link MsSqlTableDdl}.
19
+ */
20
+ export class TableDdl {
21
+ dialect;
22
+ constructor(dialect) {
23
+ this.dialect = dialect;
24
+ }
25
+ addColumn(table, definition) {
26
+ return `ALTER TABLE ${this.dialect.escapeId(table)} ADD COLUMN ${definition};`;
27
+ }
28
+ dropColumn(table, column) {
29
+ return [`ALTER TABLE ${this.dialect.escapeId(table)} DROP COLUMN ${this.dialect.escapeId(column)};`];
30
+ }
31
+ /**
32
+ * What changes `column` to what it now declares. `definition` is the whole column, which MySQL's
33
+ * `MODIFY COLUMN` restates; Postgres takes each change as a clause of its own.
34
+ */
35
+ alterColumn(table, column, definition) {
36
+ if (this.dialect.alterColumnSyntax === 'none') {
37
+ throw new TypeError(`${this.dialect}: Cannot alter column "${column.name}" - you must recreate the table. ` +
38
+ `This database does not support ALTER COLUMN.`);
39
+ }
40
+ const target = this.dialect.escapeId(table);
41
+ if (this.dialect.alterColumnStrategy !== 'separate-clauses') {
42
+ return [`ALTER TABLE ${target} ${this.dialect.alterColumnSyntax} ${definition};`];
43
+ }
44
+ const alter = `ALTER TABLE ${target} ALTER COLUMN ${this.dialect.escapeId(column.name)}`;
45
+ return [
46
+ `${alter} TYPE ${column.type};`,
47
+ `${alter} ${column.nullable ? 'DROP NOT NULL' : 'SET NOT NULL'};`,
48
+ column.defaultValue === undefined ? `${alter} DROP DEFAULT;` : `${alter} SET${this.defaultClause(column)};`,
49
+ ];
50
+ }
51
+ renameColumn(table, oldName, newName) {
52
+ const [target, from, to] = [table, oldName, newName].map((name) => this.dialect.escapeId(name));
53
+ return `ALTER TABLE ${target} RENAME COLUMN ${from} TO ${to};`;
54
+ }
55
+ renameTable(oldName, newName) {
56
+ return `ALTER TABLE ${this.dialect.escapeId(oldName)} RENAME TO ${this.dialect.escapeId(newName)};`;
57
+ }
58
+ /** A stored generated column's type, with the clause computing it. */
59
+ storedGeneratedColumn(type, expression) {
60
+ return `${type} GENERATED ALWAYS AS (${expression}) STORED`;
61
+ }
62
+ /**
63
+ * ` DEFAULT <sql>`, or nothing where the column declares none. Empty rather than `DEFAULT NULL`, so
64
+ * an absent default stays absent - `defaultValue: null` is the way to ask for one.
65
+ */
66
+ defaultClause(column) {
67
+ return column.defaultValue === undefined
68
+ ? ''
69
+ : ` DEFAULT ${formatDefaultValue(column.defaultValue, this.dialect, column.type)}`;
70
+ }
71
+ }
@@ -16,21 +16,16 @@ export declare class MsSqlSchemaIntrospector extends AbstractSqlSchemaIntrospect
16
16
  count?: number;
17
17
  }[]): boolean;
18
18
  /**
19
- * `is_identity` and the default come from `sys`, not `INFORMATION_SCHEMA`: the latter has no
20
- * identity flag, and reports a default already wrapped in the parentheses the engine reprints it
21
- * with, which is the same text either way but only nameable as a constraint through `sys`.
19
+ * From `sys` rather than `INFORMATION_SCHEMA`, which has no identity flag and no per-column view of
20
+ * the key or of a unique index. A key column is not also `isUnique`, as on MySQL: only a unique index
21
+ * of its own, other than the key's, makes it so.
22
22
  */
23
23
  protected getColumnsQuery(_tableName: string): string;
24
24
  /** `is_primary_key` is excluded: the key is read separately, the way every other engine reads it. */
25
25
  protected getIndexesQuery(_tableName: string): string;
26
26
  protected getForeignKeysQuery(_tableName: string): string;
27
27
  protected getPrimaryKeyQuery(_tableName: string): string;
28
- /**
29
- * `max_length` is in bytes, so an `NVARCHAR` column reports twice its declared length and `MAX`
30
- * reports `-1`. Read as characters, every Unicode column would drift to double its width the first
31
- * time it was diffed against the entity that declared it.
32
- */
33
- protected mapColumnsResult(read: TableRowReader, tableName: string, results: MsSqlColumnRow[]): Promise<ColumnSchema[]>;
28
+ protected mapColumnsResult(_read: TableRowReader, _tableName: string, results: MsSqlColumnRow[]): Promise<ColumnSchema[]>;
34
29
  protected mapIndexesResult(_read: TableRowReader, _tableName: string, results: {
35
30
  index_name: string;
36
31
  columns: string;
@@ -59,5 +54,7 @@ type MsSqlColumnRow = {
59
54
  is_nullable: boolean;
60
55
  is_identity: boolean;
61
56
  column_default: string | null;
57
+ is_primary_key: number | null;
58
+ is_unique: number | null;
62
59
  };
63
60
  export {};
@@ -30,9 +30,9 @@ export class MsSqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
30
30
  return (this.toNumber(results[0]?.count) ?? 0) > 0;
31
31
  }
32
32
  /**
33
- * `is_identity` and the default come from `sys`, not `INFORMATION_SCHEMA`: the latter has no
34
- * identity flag, and reports a default already wrapped in the parentheses the engine reprints it
35
- * with, which is the same text either way but only nameable as a constraint through `sys`.
33
+ * From `sys` rather than `INFORMATION_SCHEMA`, which has no identity flag and no per-column view of
34
+ * the key or of a unique index. A key column is not also `isUnique`, as on MySQL: only a unique index
35
+ * of its own, other than the key's, makes it so.
36
36
  */
37
37
  getColumnsQuery(_tableName) {
38
38
  return /*sql*/ `
@@ -44,12 +44,27 @@ export class MsSqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
44
44
  c.scale as numeric_scale,
45
45
  c.is_nullable as is_nullable,
46
46
  c.is_identity as is_identity,
47
- d.definition as column_default
47
+ d.definition as column_default,
48
+ f.is_primary_key,
49
+ f.is_unique
48
50
  FROM sys.columns c
49
51
  JOIN sys.objects o ON o.object_id = c.object_id
50
52
  JOIN sys.schemas s ON s.schema_id = o.schema_id
51
53
  JOIN sys.types t ON t.user_type_id = c.user_type_id
52
54
  LEFT JOIN sys.default_constraints d ON d.object_id = c.default_object_id
55
+ OUTER APPLY (
56
+ SELECT
57
+ MAX(CAST(i.is_primary_key AS INT)) as is_primary_key,
58
+ MAX(CASE WHEN i.is_primary_key = 0 AND n.key_columns = 1 THEN 1 ELSE 0 END) as is_unique
59
+ FROM sys.index_columns ic
60
+ JOIN sys.indexes i ON i.object_id = ic.object_id AND i.index_id = ic.index_id
61
+ CROSS APPLY (
62
+ SELECT COUNT(*) as key_columns FROM sys.index_columns k
63
+ WHERE k.object_id = i.object_id AND k.index_id = i.index_id AND k.is_included_column = 0
64
+ ) n
65
+ WHERE ic.object_id = c.object_id AND ic.column_id = c.column_id
66
+ AND ic.is_included_column = 0 AND i.is_unique = 1
67
+ ) f
53
68
  WHERE s.name = ${this.schemaExpr} AND o.name = @p1
54
69
  ORDER BY c.column_id
55
70
  `;
@@ -105,44 +120,19 @@ export class MsSqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
105
120
  ORDER BY ic.key_ordinal
106
121
  `;
107
122
  }
108
- /**
109
- * `max_length` is in bytes, so an `NVARCHAR` column reports twice its declared length and `MAX`
110
- * reports `-1`. Read as characters, every Unicode column would drift to double its width the first
111
- * time it was diffed against the entity that declared it.
112
- */
113
- async mapColumnsResult(read, tableName, results) {
114
- // One extra statement for both flags, and the reader sends it once: a column is in the key or
115
- // uniquely indexed on its own, and `sys` is the only place either is readable per column.
116
- const flags = await read(
117
- /*sql*/ `
118
- SELECT c.name as column_name, MAX(CAST(i.is_primary_key AS INT)) as is_primary_key
119
- FROM sys.indexes i
120
- JOIN sys.objects o ON o.object_id = i.object_id
121
- JOIN sys.schemas s ON s.schema_id = o.schema_id
122
- JOIN sys.index_columns ic ON ic.object_id = i.object_id AND ic.index_id = i.index_id
123
- JOIN sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id
124
- WHERE s.name = ${this.schemaExpr} AND o.name = @p1
125
- AND i.is_unique = 1 AND ic.is_included_column = 0
126
- AND (SELECT COUNT(*) FROM sys.index_columns k
127
- WHERE k.object_id = i.object_id AND k.index_id = i.index_id AND k.is_included_column = 0) = 1
128
- GROUP BY c.name
129
- `, [tableName]);
130
- const unique = new Set(flags.map((row) => row.column_name));
131
- const keys = new Set(flags.filter((row) => Boolean(row.is_primary_key)).map((row) => row.column_name));
123
+ async mapColumnsResult(_read, _tableName, results) {
132
124
  return results.map((row) => {
133
125
  const type = row.data_type.toUpperCase();
134
- const wide = type.startsWith('N');
135
126
  const bytes = this.toNumber(row.max_length);
136
- const length = bytes === undefined || bytes < 0 || !CHARACTER_TYPES.has(type) ? undefined : wide ? bytes / 2 : bytes;
137
127
  return {
138
128
  name: row.column_name,
139
129
  type: bytes === -1 && CHARACTER_TYPES.has(type) ? `${type}(MAX)` : type,
140
130
  nullable: Boolean(row.is_nullable),
141
131
  defaultValue: this.parseDefaultValue(row.column_default),
142
132
  isAutoIncrement: Boolean(row.is_identity),
143
- isPrimaryKey: keys.has(row.column_name),
144
- isUnique: unique.has(row.column_name),
145
- length,
133
+ isPrimaryKey: Boolean(row.is_primary_key),
134
+ isUnique: Boolean(row.is_unique),
135
+ length: widthOf(type, bytes),
146
136
  precision: NUMERIC_TYPES.has(type) ? this.toNumber(row.numeric_precision) : undefined,
147
137
  scale: NUMERIC_TYPES.has(type) ? this.toNumber(row.numeric_scale) : undefined,
148
138
  };
@@ -196,3 +186,20 @@ export class MsSqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
196
186
  /** The types whose `max_length` is a width rather than a fixed storage size. */
197
187
  const CHARACTER_TYPES = new Set(['CHAR', 'NCHAR', 'VARCHAR', 'NVARCHAR', 'BINARY', 'VARBINARY']);
198
188
  const NUMERIC_TYPES = new Set(['DECIMAL', 'NUMERIC']);
189
+ /**
190
+ * A column's declared width from `max_length`, which is bytes: an `N` type holds two a character, a
191
+ * `VECTOR` is an 8-byte header and four a dimension, and `MAX` is `-1`. Read as bytes, every Unicode
192
+ * column would drift to double its width against the entity that declared it.
193
+ */
194
+ function widthOf(type, bytes) {
195
+ if (bytes === undefined || bytes < 0) {
196
+ return undefined;
197
+ }
198
+ if (type === 'VECTOR') {
199
+ return (bytes - 8) / 4;
200
+ }
201
+ if (!CHARACTER_TYPES.has(type)) {
202
+ return undefined;
203
+ }
204
+ return type.startsWith('N') ? bytes / 2 : bytes;
205
+ }
@@ -4,7 +4,7 @@ import { type DiffOptions } from '../schema/schemaASTDiffer.js';
4
4
  import type { CanonicalType, ColumnNode, ForeignKeyAction, IndexNode, TableNode } from '../schema/types.js';
5
5
  import type { ColumnSchema, CreateSchemaOptions, DialectFeatures, DropSchemaOptions, EntityMeta, FieldMeta, FieldOptions, ForeignKeySchema, IndexSchema, NamingStrategy, SchemaDiff, SchemaGenerator, SqlDdlGenerator, Type } from '../type/index.js';
6
6
  import type { FullColumnDefinition, TableDefinition } from './builder/types.js';
7
- import { type IndexDdl } from './ddl/index.js';
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.
@@ -14,6 +14,8 @@ export declare class SqlSchemaGenerator implements SqlDdlGenerator {
14
14
  protected readonly defaultForeignKeyAction: ForeignKeyAction;
15
15
  /** `CREATE INDEX` for this dialect: the migrator's, so a runtime import carries none of it. */
16
16
  protected readonly indexDdl: IndexDdl;
17
+ /** The `ALTER TABLE` statements this dialect spells its own way, the migrator's for the same reason. */
18
+ protected readonly tableDdl: TableDdl;
17
19
  constructor(dialect: AbstractSqlDialect, defaultForeignKeyAction?: ForeignKeyAction);
18
20
  get namingStrategy(): NamingStrategy | undefined;
19
21
  get features(): DialectFeatures;
@@ -97,18 +99,14 @@ export declare class SqlSchemaGenerator implements SqlDdlGenerator {
97
99
  */
98
100
  generateColumnDefinitionFromSchema(column: ColumnSchema): string;
99
101
  /**
100
- * The one place a column definition is spelled. Both callers reach it - the `ColumnSchema` path above
101
- * and the `ColumnNode` path below - because the clause order and the "no NOT NULL on a primary key"
102
- * rules are the same everywhere, and having them written twice is how the two paths drifted.
102
+ * The one place a column definition is spelled, so the `ColumnSchema` and `ColumnNode` paths cannot
103
+ * drift. A key column states `NOT NULL` rather than leave it to the key: SQLite lets a key column hold
104
+ * NULL otherwise, and SQL Server adds no key over a nullable column. `UNIQUE` is left to the key. An
105
+ * enum's `CHECK` comes last, the only place MariaDB takes it.
103
106
  */
104
107
  private renderColumn;
105
- /** ` DEFAULT <sql>`, or nothing where the column declares none. Empty rather than `DEFAULT NULL`
106
- * so an absent default stays absent - `defaultValue: null` is the way to ask for one. */
107
- private defaultClause;
108
108
  getSqlType(field: FieldMeta): string;
109
- /**
110
- * Generate ALTER COLUMN statements (database-specific)
111
- */
109
+ /** The statements that alter `column` in place, as this dialect spells them. */
112
110
  generateAlterColumnStatements(tableName: string, column: ColumnSchema, newDefinition: string): string[];
113
111
  /** The inline ` COMMENT '...'` a column declaration carries, where the engine takes one there. */
114
112
  generateColumnComment(comment: string): string;
@@ -4,10 +4,11 @@ import { areTypesEqual, canonicalToSql, engineType, fieldOptionsToCanonical, isV
4
4
  import { indexSignature } from '../schema/indexDifferences.js';
5
5
  import { buildSchemaAST } from '../schema/schemaASTBuilder.js';
6
6
  import { diffRelationshipNodes, diffTable } from '../schema/schemaASTDiffer.js';
7
- import { getKeys, isAutoIncrement, isSoleIdField, qualifyName } from '../util/index.js';
7
+ import { isAutoIncrement, qualifyName } from '../util/index.js';
8
8
  import { derivedCheckName, derivedForeignKeyName, derivedPrimaryKeyName } from '../util/sql.util.js';
9
9
  import { formatDefaultValue, SqlExpression } from './builder/expressions.js';
10
- import { indexDdlFor } from './ddl/index.js';
10
+ import { indexDdlFor, tableDdlFor } from './ddl/index.js';
11
+ import { sizedType } from './ddl/tableDdl.js';
11
12
  import { columnForeignKey, columnIndex, fullColumnDefinitionToNode, tableDefinitionToNode, } from './generator/definitionToNode.js';
12
13
  import { indexNodeToSchema } from './generator/indexNodeToSchema.js';
13
14
  /**
@@ -19,10 +20,13 @@ export class SqlSchemaGenerator {
19
20
  defaultForeignKeyAction;
20
21
  /** `CREATE INDEX` for this dialect: the migrator's, so a runtime import carries none of it. */
21
22
  indexDdl;
23
+ /** The `ALTER TABLE` statements this dialect spells its own way, the migrator's for the same reason. */
24
+ tableDdl;
22
25
  constructor(dialect, defaultForeignKeyAction = 'NO ACTION') {
23
26
  this.dialect = dialect;
24
27
  this.defaultForeignKeyAction = defaultForeignKeyAction;
25
28
  this.indexDdl = indexDdlFor(dialect);
29
+ this.tableDdl = tableDdlFor(dialect);
26
30
  }
27
31
  get namingStrategy() {
28
32
  return this.dialect.namingStrategy;
@@ -138,7 +142,6 @@ export class SqlSchemaGenerator {
138
142
  }
139
143
  generateAlterTable(diff) {
140
144
  const statements = [];
141
- const tableName = this.escapeId(diff.tableName);
142
145
  // Before the columns, because a key column being added cannot be part of the old key, and after
143
146
  // it is dropped the table is free to take the new one below.
144
147
  if (diff.primaryKey?.from.length) {
@@ -154,7 +157,7 @@ export class SqlSchemaGenerator {
154
157
  if (diff.columnsToAdd?.length) {
155
158
  for (const column of diff.columnsToAdd) {
156
159
  this.assertColumnAddable(diff.tableName, column);
157
- statements.push(`ALTER TABLE ${tableName} ADD COLUMN ${this.generateColumnDefinitionFromSchema(column)};`);
160
+ statements.push(this.tableDdl.addColumn(diff.tableName, this.generateColumnDefinitionFromSchema(column)));
158
161
  statements.push(...this.generateColumnCommentStatement(diff.tableName, column, diff.schema));
159
162
  }
160
163
  }
@@ -169,7 +172,7 @@ export class SqlSchemaGenerator {
169
172
  // Drop columns
170
173
  if (diff.columnsToDrop?.length) {
171
174
  for (const columnName of diff.columnsToDrop) {
172
- statements.push(`ALTER TABLE ${tableName} DROP COLUMN ${this.escapeId(columnName)};`);
175
+ statements.push(...this.tableDdl.dropColumn(diff.tableName, columnName));
173
176
  }
174
177
  }
175
178
  // Add indexes
@@ -206,7 +209,6 @@ export class SqlSchemaGenerator {
206
209
  }
207
210
  generateAlterTableDown(diff) {
208
211
  const statements = [];
209
- const tableName = this.escapeId(diff.tableName);
210
212
  // Constraints first, mirroring the up direction: the up added them last, so the down drops them
211
213
  // first, and a column it is about to drop is then free of anything naming it.
212
214
  statements.push(...this.dropForeignKeyStatements(diff.tableName, [
@@ -222,7 +224,7 @@ export class SqlSchemaGenerator {
222
224
  // Reverse column additions by dropping them
223
225
  if (diff.columnsToAdd?.length) {
224
226
  for (const column of diff.columnsToAdd) {
225
- statements.push(`ALTER TABLE ${tableName} DROP COLUMN ${this.escapeId(column.name)};`);
227
+ statements.push(...this.tableDdl.dropColumn(diff.tableName, column.name));
226
228
  }
227
229
  }
228
230
  // Reverse column alterations by restoring original schema
@@ -273,53 +275,35 @@ export class SqlSchemaGenerator {
273
275
  * {@link renderColumn}, which is the part that must not be written twice.
274
276
  */
275
277
  generateColumnDefinitionFromSchema(column) {
276
- let type = column.type;
277
- if (!type.includes('(')) {
278
- if (column.precision !== undefined) {
279
- type += column.scale === undefined ? `(${column.precision})` : `(${column.precision}, ${column.scale})`;
280
- }
281
- else if (column.length !== undefined) {
282
- type += `(${column.length})`;
283
- }
284
- }
285
- return this.renderColumn({ ...column, type });
278
+ return this.renderColumn({ ...column, type: sizedType(column) });
286
279
  }
287
280
  /**
288
- * The one place a column definition is spelled. Both callers reach it - the `ColumnSchema` path above
289
- * and the `ColumnNode` path below - because the clause order and the "no NOT NULL on a primary key"
290
- * rules are the same everywhere, and having them written twice is how the two paths drifted.
281
+ * The one place a column definition is spelled, so the `ColumnSchema` and `ColumnNode` paths cannot
282
+ * drift. A key column states `NOT NULL` rather than leave it to the key: SQLite lets a key column hold
283
+ * NULL otherwise, and SQL Server adds no key over a nullable column. `UNIQUE` is left to the key. An
284
+ * enum's `CHECK` comes last, the only place MariaDB takes it.
291
285
  */
292
286
  renderColumn(column) {
293
- let def = `${this.escapeId(column.name)} ${column.type}`;
294
- // Before the constraints, which every engine here accepts and is where each documents it. The
295
- // clause takes the place of a `DEFAULT`, which is mutually exclusive with it; everything else -
296
- // `NOT NULL`, `UNIQUE`, an enum `CHECK`, a comment - a generated column carries like any other.
297
- if (column.generatedAs) {
298
- def += ` GENERATED ALWAYS AS (${column.generatedAs}) STORED`;
299
- }
300
- if (!column.nullable && !column.isPrimaryKey) {
287
+ const type = column.generatedAs
288
+ ? this.tableDdl.storedGeneratedColumn(column.type, column.generatedAs)
289
+ : column.type;
290
+ let def = `${this.escapeId(column.name)} ${type}`;
291
+ if (!column.nullable) {
301
292
  def += ' NOT NULL';
302
293
  }
303
294
  if (column.isUnique && !column.isPrimaryKey) {
304
295
  def += ' UNIQUE';
305
296
  }
297
+ def += this.tableDdl.defaultClause(column);
298
+ if (column.comment) {
299
+ def += this.generateColumnComment(column.comment);
300
+ }
306
301
  if (column.enum?.length) {
307
302
  const values = column.enum.map((value) => this.dialect.escape(value)).join(', ');
308
303
  def += ` CHECK (${this.escapeId(column.name)} IN (${values}))`;
309
304
  }
310
- def += this.defaultClause(column);
311
- if (column.comment) {
312
- def += this.generateColumnComment(column.comment);
313
- }
314
305
  return def;
315
306
  }
316
- /** ` DEFAULT <sql>`, or nothing where the column declares none. Empty rather than `DEFAULT NULL`
317
- * so an absent default stays absent - `defaultValue: null` is the way to ask for one. */
318
- defaultClause(column) {
319
- return column.defaultValue === undefined
320
- ? ''
321
- : ` DEFAULT ${formatDefaultValue(column.defaultValue, this.dialect, column.type)}`;
322
- }
323
307
  getSqlType(field) {
324
308
  // A foreign key takes the type of the key it points at. A `referencedKey` the target does not
325
309
  // have falls through to this column's own options, as the AST builder does with the same case.
@@ -338,35 +322,9 @@ export class SqlSchemaGenerator {
338
322
  }
339
323
  return this.canonicalTypeToSql(canonical);
340
324
  }
341
- /**
342
- * Generate ALTER COLUMN statements (database-specific)
343
- */
325
+ /** The statements that alter `column` in place, as this dialect spells them. */
344
326
  generateAlterColumnStatements(tableName, column, newDefinition) {
345
- const table = this.escapeId(tableName);
346
- const colName = this.escapeId(column.name);
347
- if (this.dialect.alterColumnSyntax === 'none') {
348
- throw new TypeError(`${this.dialect}: Cannot alter column "${column.name}" - you must recreate the table. ` +
349
- `This database does not support ALTER COLUMN.`);
350
- }
351
- if (this.dialect.alterColumnStrategy === 'separate-clauses') {
352
- const statements = [];
353
- // Separate ALTER COLUMN clauses for different changes (Postgres)
354
- statements.push(`ALTER TABLE ${table} ALTER COLUMN ${colName} TYPE ${column.type};`);
355
- if (column.nullable) {
356
- statements.push(`ALTER TABLE ${table} ALTER COLUMN ${colName} DROP NOT NULL;`);
357
- }
358
- else {
359
- statements.push(`ALTER TABLE ${table} ALTER COLUMN ${colName} SET NOT NULL;`);
360
- }
361
- if (column.defaultValue !== undefined) {
362
- statements.push(`ALTER TABLE ${table} ALTER COLUMN ${colName} SET${this.defaultClause(column)};`);
363
- }
364
- else {
365
- statements.push(`ALTER TABLE ${table} ALTER COLUMN ${colName} DROP DEFAULT;`);
366
- }
367
- return statements;
368
- }
369
- return [`ALTER TABLE ${table} ${this.dialect.alterColumnSyntax} ${newDefinition};`];
327
+ return this.tableDdl.alterColumn(tableName, column, newDefinition);
370
328
  }
371
329
  /** The inline ` COMMENT '...'` a column declaration carries, where the engine takes one there. */
372
330
  generateColumnComment(comment) {
@@ -627,10 +585,7 @@ export class SqlSchemaGenerator {
627
585
  return this.generateCreateTableFromNode(tableNode, options);
628
586
  }
629
587
  generateRenameTableSql(oldName, newName) {
630
- if (this.dialect.renameTableSyntax === 'rename-table') {
631
- return `RENAME TABLE ${this.escapeId(oldName)} TO ${this.escapeId(newName)};`;
632
- }
633
- return `ALTER TABLE ${this.escapeId(oldName)} RENAME TO ${this.escapeId(newName)};`;
588
+ return this.tableDdl.renameTable(oldName, newName);
634
589
  }
635
590
  /**
636
591
  * `ADD COLUMN`, plus the constraint and index the column declares.
@@ -643,7 +598,7 @@ export class SqlSchemaGenerator {
643
598
  generateAddColumnSql(tableName, column) {
644
599
  this.assertColumnAddable(tableName, column);
645
600
  const colSql = this.generateColumnFromNode(fullColumnDefinitionToNode(column, tableName));
646
- const statements = [`ALTER TABLE ${this.escapeId(tableName)} ADD COLUMN ${colSql};`];
601
+ const statements = [this.tableDdl.addColumn(tableName, colSql)];
647
602
  const foreignKey = columnForeignKey(column);
648
603
  if (foreignKey) {
649
604
  statements.push(...this.addForeignKeyStatements(tableName, [foreignKey]));
@@ -660,10 +615,10 @@ export class SqlSchemaGenerator {
660
615
  return this.generateAlterColumnStatements(tableName, { ...this.columnNodeToSchema(node), name: columnName }, this.generateColumnFromNode(node)).join('\n');
661
616
  }
662
617
  generateDropColumnSql(tableName, columnName) {
663
- return `ALTER TABLE ${this.escapeId(tableName)} DROP COLUMN ${this.escapeId(columnName)};`;
618
+ return this.tableDdl.dropColumn(tableName, columnName).join('\n');
664
619
  }
665
620
  generateRenameColumnSql(tableName, oldName, newName) {
666
- return `ALTER TABLE ${this.escapeId(tableName)} RENAME COLUMN ${this.escapeId(oldName)} TO ${this.escapeId(newName)};`;
621
+ return this.tableDdl.renameColumn(tableName, oldName, newName);
667
622
  }
668
623
  /**
669
624
  * `CONSTRAINT <name> FOREIGN KEY (...) REFERENCES ... ON DELETE ... ON UPDATE ...`.