uql-orm 0.52.0 → 0.54.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser/uql-browser.min.js +2 -2
- package/dist/browser/uql-browser.min.js.map +5 -5
- package/dist/cockroachdb/cockroachDialect.d.ts +2 -5
- package/dist/cockroachdb/cockroachDialect.js +2 -5
- package/dist/dialect/abstractDialect.d.ts +5 -5
- package/dist/dialect/abstractDialect.js +7 -6
- package/dist/dialect/abstractSqlDialect.d.ts +8 -3
- package/dist/dialect/abstractSqlDialect.js +14 -11
- package/dist/dialect/mysqlLikeSqlDialect.d.ts +0 -4
- package/dist/dialect/mysqlLikeSqlDialect.js +0 -6
- package/dist/dialect/pgLikeSqlDialect.js +0 -2
- package/dist/dialect/vectorSqlDialect.js +2 -1
- package/dist/entity/decorator/members.d.ts +3 -10
- package/dist/entity/metadata/definition.js +0 -4
- package/dist/http/handler.js +3 -12
- package/dist/http/query.js +4 -1
- package/dist/migrate/builder/migrationBuilder.js +0 -4
- package/dist/migrate/ddl/index.d.ts +8 -0
- package/dist/migrate/ddl/index.js +11 -0
- package/dist/migrate/ddl/mssqlTableDdl.d.ts +23 -0
- package/dist/migrate/ddl/mssqlTableDdl.js +61 -0
- package/dist/migrate/ddl/tableDdl.d.ts +34 -0
- package/dist/migrate/ddl/tableDdl.js +71 -0
- package/dist/migrate/introspection/mssqlIntrospector.d.ts +6 -9
- package/dist/migrate/introspection/mssqlIntrospector.js +40 -33
- package/dist/migrate/migrator.d.ts +3 -7
- package/dist/migrate/migrator.js +3 -7
- package/dist/migrate/schemaGenerator.d.ts +8 -23
- package/dist/migrate/schemaGenerator.js +29 -87
- package/dist/migrate/storage/databaseStorage.d.ts +2 -2
- package/dist/migrate/storage/databaseStorage.js +2 -2
- package/dist/mongo/mongoDialect.js +9 -14
- package/dist/mongo/mongodbQuerier.d.ts +3 -5
- package/dist/mongo/mongodbQuerier.js +18 -19
- package/dist/mssql/mssqlDialect.d.ts +14 -1
- package/dist/mssql/mssqlDialect.js +22 -7
- package/dist/querier/abstractQuerier.d.ts +17 -22
- package/dist/querier/abstractQuerier.js +92 -67
- package/dist/querier/abstractSqlQuerier.d.ts +9 -9
- package/dist/querier/abstractSqlQuerier.js +71 -87
- package/dist/schema/canonicalType.js +2 -2
- package/dist/schema/schemaASTBuilder.js +7 -7
- package/dist/sqlite/sqliteDialect.js +0 -2
- package/dist/type/dialect.d.ts +0 -8
- package/dist/type/entity.d.ts +6 -11
- package/dist/type/migration.d.ts +0 -3
- package/dist/type/query.d.ts +13 -13
- package/dist/type/query.js +0 -6
- package/dist/type/queryWhere.d.ts +7 -19
- package/dist/type/universalQuerier.d.ts +3 -3
- package/dist/type/vector.d.ts +3 -1
- package/dist/util/dialect.util.d.ts +12 -8
- package/dist/util/dialect.util.js +17 -29
- package/dist/util/field.util.d.ts +4 -16
- package/dist/util/field.util.js +6 -19
- package/dist/util/fieldOption.util.d.ts +1 -4
- package/dist/util/fieldOption.util.js +0 -2
- package/dist/util/logger.d.ts +3 -3
- package/dist/util/logger.js +3 -0
- package/dist/util/object.util.d.ts +2 -0
- package/dist/util/object.util.js +4 -0
- package/dist/util/raw.d.ts +3 -10
- package/dist/util/sql.util.js +2 -2
- package/package.json +2 -2
package/dist/http/handler.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { withContext } from '../context/context.js';
|
|
2
2
|
import { getEntities, getMeta, soleIdOf } from '../entity/index.js';
|
|
3
|
+
import { whereIds } from '../util/dialect.util.js';
|
|
3
4
|
import { entityPath, matchRoute } from './contract.js';
|
|
4
5
|
import { parseQueryParams } from './query.js';
|
|
5
6
|
/** `Company (crm.Company)`: the class, and the table it maps, which is what tells two apart. */
|
|
@@ -145,7 +146,7 @@ export function createRequestHandler(opts) {
|
|
|
145
146
|
if (founds.length) {
|
|
146
147
|
const idKey = soleIdOf(meta, 'the HTTP handler');
|
|
147
148
|
ids = founds.map((found) => found[idKey]);
|
|
148
|
-
count = await querier.deleteMany(entity, { $where: ids }, { hardDelete });
|
|
149
|
+
count = await querier.deleteMany(entity, { $where: whereIds(meta, ids) }, { hardDelete });
|
|
149
150
|
}
|
|
150
151
|
return ok({ data: ids, count });
|
|
151
152
|
});
|
|
@@ -157,16 +158,6 @@ function ok(body) {
|
|
|
157
158
|
return { status: 200, body };
|
|
158
159
|
}
|
|
159
160
|
function buildIdQuery(meta, id, query) {
|
|
160
|
-
|
|
161
|
-
const where = query.$where;
|
|
162
|
-
if (Array.isArray(where)) {
|
|
163
|
-
query.$where = { $and: [{ [idKey]: { $in: where } }, { [idKey]: id }] };
|
|
164
|
-
}
|
|
165
|
-
else if (typeof where === 'object' && where !== null) {
|
|
166
|
-
query.$where = { ...where, [idKey]: id };
|
|
167
|
-
}
|
|
168
|
-
else {
|
|
169
|
-
query.$where = { [idKey]: id };
|
|
170
|
-
}
|
|
161
|
+
query.$where = { ...query.$where, [soleIdOf(meta, 'the HTTP handler')]: id };
|
|
171
162
|
return query;
|
|
172
163
|
}
|
package/dist/http/query.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// the clause lists themselves, not the barrel: this module is in the browser bundle's graph
|
|
2
2
|
import { QUERY_BOOLEAN_CLAUSES, QUERY_NUMBER_CLAUSES, QUERY_OBJECT_CLAUSES, QUERY_ROOT_NUMBER_CLAUSES, QUERY_ROOT_OBJECT_CLAUSES, } from '../type/query.js';
|
|
3
3
|
// the specific util module, not the barrel, so the browser bundle does not pull in entity metadata
|
|
4
|
-
import { getKeys } from '../util/object.util.js';
|
|
4
|
+
import { getKeys, isWhereMap } from '../util/object.util.js';
|
|
5
5
|
/**
|
|
6
6
|
* Keys accepted from the wire - query structure ({@link Query}) plus the `hardDelete`/`count` scalar
|
|
7
7
|
* flags. Anything else (e.g. `filters`, `context`, `$entity`) is dropped so a remote client can't
|
|
@@ -50,6 +50,9 @@ export function parseQueryParams(params = {}) {
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
query['$where'] ??= {};
|
|
53
|
+
if (!isWhereMap(query['$where'])) {
|
|
54
|
+
throw Object.assign(new TypeError("'$where' must be a JSON object"), { status: 400 });
|
|
55
|
+
}
|
|
53
56
|
// A query string carries every value as text, so what decodes a clause is the shape its group
|
|
54
57
|
// declares. `'false'` is the reason the boolean pass exists rather than the raw value being taken:
|
|
55
58
|
// it is a non-empty string, so a `$distinct=false` would otherwise read as asking for one.
|
|
@@ -10,10 +10,6 @@ import { derivedIndexName } from '../../util/sql.util.js';
|
|
|
10
10
|
import { createSchemaGenerator } from '../schemaGenerator.js';
|
|
11
11
|
import { splitSqlStatements } from './splitSqlStatements.js';
|
|
12
12
|
import { TableBuilder } from './tableBuilder.js';
|
|
13
|
-
/**
|
|
14
|
-
* Builder for altering a table.
|
|
15
|
-
* Delegates to parent builder for operation recording.
|
|
16
|
-
*/
|
|
17
13
|
/**
|
|
18
14
|
* One `createIndex` operation. Shared because the alter-table builder, the recorder and the
|
|
19
15
|
* executing builder all record the same thing, and an entry left unnormalized reaches the generator
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import type { AbstractSqlDialect } from '../../dialect/abstractSqlDialect.js';
|
|
2
2
|
import { IndexDdl } from './indexDdl.js';
|
|
3
|
+
import { TableDdl } from './tableDdl.js';
|
|
3
4
|
export { IndexDdl } from './indexDdl.js';
|
|
5
|
+
export { MsSqlTableDdl } from './mssqlTableDdl.js';
|
|
4
6
|
export { MariaIndexDdl, MySqlIndexDdl, MysqlLikeIndexDdl } from './mysqlIndexDdl.js';
|
|
5
7
|
export { CockroachIndexDdl, PgIndexDdl } from './pgIndexDdl.js';
|
|
8
|
+
export { TableDdl } from './tableDdl.js';
|
|
6
9
|
/**
|
|
7
10
|
* The index DDL a dialect gets, most specific first. `instanceof` rather than `dialectName` so a
|
|
8
11
|
* dialect subclassed by a user keeps its family's DDL, which is what overriding gave it while this
|
|
9
12
|
* lived on the dialect itself. Anything else gets the portable form, which is SQLite's.
|
|
10
13
|
*/
|
|
11
14
|
export declare function indexDdlFor(dialect: AbstractSqlDialect): IndexDdl;
|
|
15
|
+
/**
|
|
16
|
+
* The table DDL a dialect gets: SQL Server's, or the portable form every other engine takes. By
|
|
17
|
+
* `dialectName`, which a subclass inherits too, so this entry need not carry the dialect itself.
|
|
18
|
+
*/
|
|
19
|
+
export declare function tableDdlFor(dialect: AbstractSqlDialect): TableDdl;
|
|
@@ -4,11 +4,15 @@ import { PgLikeSqlDialect } from '../../dialect/pgLikeSqlDialect.js';
|
|
|
4
4
|
import { MariaDialect } from '../../maria/mariaDialect.js';
|
|
5
5
|
import { MySqlDialect } from '../../mysql/mysqlDialect.js';
|
|
6
6
|
import { IndexDdl } from './indexDdl.js';
|
|
7
|
+
import { MsSqlTableDdl } from './mssqlTableDdl.js';
|
|
7
8
|
import { MariaIndexDdl, MySqlIndexDdl, MysqlLikeIndexDdl } from './mysqlIndexDdl.js';
|
|
8
9
|
import { CockroachIndexDdl, PgIndexDdl } from './pgIndexDdl.js';
|
|
10
|
+
import { TableDdl } from './tableDdl.js';
|
|
9
11
|
export { IndexDdl } from './indexDdl.js';
|
|
12
|
+
export { MsSqlTableDdl } from './mssqlTableDdl.js';
|
|
10
13
|
export { MariaIndexDdl, MySqlIndexDdl, MysqlLikeIndexDdl } from './mysqlIndexDdl.js';
|
|
11
14
|
export { CockroachIndexDdl, PgIndexDdl } from './pgIndexDdl.js';
|
|
15
|
+
export { TableDdl } from './tableDdl.js';
|
|
12
16
|
/**
|
|
13
17
|
* The index DDL a dialect gets, most specific first. `instanceof` rather than `dialectName` so a
|
|
14
18
|
* dialect subclassed by a user keeps its family's DDL, which is what overriding gave it while this
|
|
@@ -32,3 +36,10 @@ export function indexDdlFor(dialect) {
|
|
|
32
36
|
}
|
|
33
37
|
return new IndexDdl(dialect);
|
|
34
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* The table DDL a dialect gets: SQL Server's, or the portable form every other engine takes. By
|
|
41
|
+
* `dialectName`, which a subclass inherits too, so this entry need not carry the dialect itself.
|
|
42
|
+
*/
|
|
43
|
+
export function tableDdlFor(dialect) {
|
|
44
|
+
return dialect.dialectName === 'mssql' ? new MsSqlTableDdl(dialect) : new TableDdl(dialect);
|
|
45
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ColumnSchema } from '../../type/index.js';
|
|
2
|
+
import { TableDdl } from './tableDdl.js';
|
|
3
|
+
/**
|
|
4
|
+
* SQL Server keeps a column's `DEFAULT`, `CHECK` and `UNIQUE` as constraints under names it picks, and
|
|
5
|
+
* refuses to drop or retype the column past one, so they go first - looked up by column, as no two
|
|
6
|
+
* databases name them alike. Renames are `sp_rename`, T-SQL having no `RENAME` clause.
|
|
7
|
+
*/
|
|
8
|
+
export declare class MsSqlTableDdl extends TableDdl {
|
|
9
|
+
/** T-SQL rejects the optional `COLUMN` keyword after `ADD`. */
|
|
10
|
+
addColumn(table: string, definition: string): string;
|
|
11
|
+
/** Its constraints go with the column, as they do on every other engine. */
|
|
12
|
+
dropColumn(table: string, column: string): string[];
|
|
13
|
+
/**
|
|
14
|
+
* `ALTER COLUMN` takes the type and nullability alone, so the default is dropped and added back as a
|
|
15
|
+
* constraint of its own. A `CHECK` or `UNIQUE` stays, and the server refuses a retype it blocks.
|
|
16
|
+
*/
|
|
17
|
+
alterColumn(table: string, column: ColumnSchema): string[];
|
|
18
|
+
renameColumn(table: string, oldName: string, newName: string): string;
|
|
19
|
+
renameTable(oldName: string, newName: string): string;
|
|
20
|
+
storedGeneratedColumn(_type: string, expression: string): string;
|
|
21
|
+
/** One statement, so a split on `;` cannot part the lookup from the `EXEC` it feeds. */
|
|
22
|
+
private dropConstraints;
|
|
23
|
+
}
|
|
@@ -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
|
-
* `
|
|
20
|
-
*
|
|
21
|
-
*
|
|
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
|
-
* `
|
|
34
|
-
*
|
|
35
|
-
*
|
|
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:
|
|
144
|
-
isUnique:
|
|
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
|
+
}
|
|
@@ -105,14 +105,10 @@ export declare class Migrator {
|
|
|
105
105
|
*/
|
|
106
106
|
private forceStatements;
|
|
107
107
|
/**
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
* The new-table case costs one existence check and creates with `IF NOT EXISTS`, so instances racing
|
|
112
|
-
* the same admin save settle instead of colliding. An existing table still pays for introspection,
|
|
113
|
-
* since a column diff needs the columns.
|
|
108
|
+
* The DDL for one entity: {@link planSync} narrowed to the table it names. A new table costs one
|
|
109
|
+
* existence check and is created with `IF NOT EXISTS`, so instances racing the same admin save
|
|
110
|
+
* settle instead of colliding; an existing one still pays for introspection, as a diff needs columns.
|
|
114
111
|
*/
|
|
115
|
-
/** The DDL for one entity: {@link planSync} narrowed to the table it names. */
|
|
116
112
|
private planEntity;
|
|
117
113
|
/** An alter diff as statements, narrowed to what the caller allows. */
|
|
118
114
|
private alterFromDiff;
|
package/dist/migrate/migrator.js
CHANGED
|
@@ -345,14 +345,10 @@ export class Migrator {
|
|
|
345
345
|
];
|
|
346
346
|
}
|
|
347
347
|
/**
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
* The new-table case costs one existence check and creates with `IF NOT EXISTS`, so instances racing
|
|
352
|
-
* the same admin save settle instead of colliding. An existing table still pays for introspection,
|
|
353
|
-
* since a column diff needs the columns.
|
|
348
|
+
* The DDL for one entity: {@link planSync} narrowed to the table it names. A new table costs one
|
|
349
|
+
* existence check and is created with `IF NOT EXISTS`, so instances racing the same admin save
|
|
350
|
+
* settle instead of colliding; an existing one still pays for introspection, as a diff needs columns.
|
|
354
351
|
*/
|
|
355
|
-
/** The DDL for one entity: {@link planSync} narrowed to the table it names. */
|
|
356
352
|
async planEntity(entity, options) {
|
|
357
353
|
const meta = getMeta(entity);
|
|
358
354
|
// Before anything reads `this.generator`, whose own failure names neither the entity nor the
|
|
@@ -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;
|
|
@@ -23,9 +25,6 @@ export declare class SqlSchemaGenerator implements SqlDdlGenerator {
|
|
|
23
25
|
resolveColumnName(key: string, field: FieldOptions): string;
|
|
24
26
|
/** Escape an identifier (table name, column name, etc.) */
|
|
25
27
|
protected escapeId(identifier: string): string;
|
|
26
|
-
/**
|
|
27
|
-
* Primary key type for auto-increment integer IDs
|
|
28
|
-
*/
|
|
29
28
|
/**
|
|
30
29
|
* How an auto-increment key of `type` is spelled: the type as any other column renders it, plus what
|
|
31
30
|
* the engine appends to make it generated.
|
|
@@ -97,18 +96,14 @@ export declare class SqlSchemaGenerator implements SqlDdlGenerator {
|
|
|
97
96
|
*/
|
|
98
97
|
generateColumnDefinitionFromSchema(column: ColumnSchema): string;
|
|
99
98
|
/**
|
|
100
|
-
* The one place a column definition is spelled
|
|
101
|
-
*
|
|
102
|
-
*
|
|
99
|
+
* The one place a column definition is spelled, so the `ColumnSchema` and `ColumnNode` paths cannot
|
|
100
|
+
* drift. A key column states `NOT NULL` rather than leave it to the key: SQLite lets a key column hold
|
|
101
|
+
* NULL otherwise, and SQL Server adds no key over a nullable column. `UNIQUE` is left to the key. An
|
|
102
|
+
* enum's `CHECK` comes last, the only place MariaDB takes it.
|
|
103
103
|
*/
|
|
104
104
|
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
105
|
getSqlType(field: FieldMeta): string;
|
|
109
|
-
/**
|
|
110
|
-
* Generate ALTER COLUMN statements (database-specific)
|
|
111
|
-
*/
|
|
106
|
+
/** The statements that alter `column` in place, as this dialect spells them. */
|
|
112
107
|
generateAlterColumnStatements(tableName: string, column: ColumnSchema, newDefinition: string): string[];
|
|
113
108
|
/** The inline ` COMMENT '...'` a column declaration carries, where the engine takes one there. */
|
|
114
109
|
generateColumnComment(comment: string): string;
|
|
@@ -130,9 +125,6 @@ export declare class SqlSchemaGenerator implements SqlDdlGenerator {
|
|
|
130
125
|
name: string;
|
|
131
126
|
comment?: string;
|
|
132
127
|
}, schema?: string): string[];
|
|
133
|
-
/**
|
|
134
|
-
* Compare an entity with a database table node and return the differences.
|
|
135
|
-
*/
|
|
136
128
|
/**
|
|
137
129
|
* How this entity differs from the table the database reported, as the migrator's `SchemaDiff`.
|
|
138
130
|
*
|
|
@@ -141,13 +133,6 @@ export declare class SqlSchemaGenerator implements SqlDdlGenerator {
|
|
|
141
133
|
* table node first, and types are compared as the *engine* would store them - see `normalizeType`.
|
|
142
134
|
*/
|
|
143
135
|
diffSchema<E>(entity: Type<E>, currentTable: TableNode | undefined, desiredAst?: SchemaAST): SchemaDiff | undefined;
|
|
144
|
-
/**
|
|
145
|
-
* What the shared differ needs from a dialect: a type as this engine would actually store it.
|
|
146
|
-
*
|
|
147
|
-
* `boolean` is `TINYINT(1)` on MySQL and `INTEGER` on SQLite, so two canonical types that differ on
|
|
148
|
-
* paper can be one column in the database. Round-tripping through the engine's own spelling is what
|
|
149
|
-
* stops every such column reporting an alteration on every sync.
|
|
150
|
-
*/
|
|
151
136
|
/**
|
|
152
137
|
* Indexes the entity declares that the table does not already have, in any shape.
|
|
153
138
|
*
|