uql-orm 0.51.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.
- package/README.md +1 -1
- package/dist/browser/uql-browser.min.js +2 -2
- package/dist/browser/uql-browser.min.js.map +5 -5
- package/dist/bunSql/bunSql.util.d.ts +33 -10
- package/dist/bunSql/bunSql.util.js +57 -42
- package/dist/bunSql/bunSqlQuerier.d.ts +13 -8
- package/dist/bunSql/bunSqlQuerier.js +17 -8
- package/dist/bunSql/bunSqlQuerierPool.d.ts +10 -2
- package/dist/bunSql/bunSqlQuerierPool.js +38 -25
- package/dist/bunSql/index.d.ts +1 -3
- package/dist/bunSql/index.js +0 -3
- package/dist/dialect/abstractDialect.d.ts +5 -5
- package/dist/dialect/abstractDialect.js +7 -6
- package/dist/dialect/abstractSqlDialect.d.ts +59 -6
- package/dist/dialect/abstractSqlDialect.js +90 -32
- package/dist/dialect/aliases.d.ts +2 -0
- package/dist/dialect/aliases.js +2 -0
- package/dist/dialect/mergeSqlDialect.d.ts +45 -0
- package/dist/dialect/mergeSqlDialect.js +89 -0
- package/dist/dialect/mysqlLikeSqlDialect.d.ts +0 -1
- package/dist/dialect/mysqlLikeSqlDialect.js +3 -3
- package/dist/dialect/pgLikeSqlDialect.js +3 -2
- package/dist/dialect/vectorSqlDialect.js +2 -1
- package/dist/http/handler.js +3 -12
- package/dist/http/query.js +4 -1
- package/dist/migrate/builder/expressions.js +5 -0
- 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/index.d.ts +2 -0
- package/dist/migrate/introspection/index.js +2 -0
- package/dist/migrate/introspection/mssqlIntrospector.d.ts +60 -0
- package/dist/migrate/introspection/mssqlIntrospector.js +205 -0
- package/dist/migrate/introspection/registry.d.ts +3 -0
- package/dist/migrate/introspection/registry.js +28 -0
- package/dist/migrate/migrator.js +2 -21
- package/dist/migrate/schemaGenerator.d.ts +8 -10
- package/dist/migrate/schemaGenerator.js +29 -74
- package/dist/mongo/mongoDialect.js +12 -14
- package/dist/mssql/index.d.ts +3 -0
- package/dist/mssql/index.js +3 -0
- package/dist/mssql/mssqlDialect.d.ts +155 -0
- package/dist/mssql/mssqlDialect.js +341 -0
- package/dist/mssql/mssqlQuerier.d.ts +23 -0
- package/dist/mssql/mssqlQuerier.js +137 -0
- package/dist/mssql/mssqlQuerierPool.d.ts +17 -0
- package/dist/mssql/mssqlQuerierPool.js +32 -0
- package/dist/mssql/mssqlWireTypes.d.ts +23 -0
- package/dist/mssql/mssqlWireTypes.js +44 -0
- package/dist/pglite/pgliteQuerier.d.ts +4 -2
- package/dist/pglite/pgliteQuerier.js +7 -2
- package/dist/postgres/pgCursorStream.d.ts +20 -0
- package/dist/postgres/pgCursorStream.js +49 -0
- package/dist/postgres/pgDialect.d.ts +1 -1
- package/dist/postgres/pgDialect.js +1 -1
- package/dist/postgres/postgresWireDriverCapabilities.d.ts +12 -10
- package/dist/postgres/postgresWireDriverCapabilities.js +12 -10
- package/dist/querier/abstractQuerier.js +13 -11
- package/dist/querier/abstractSqlQuerier.js +3 -3
- package/dist/schema/canonicalType.js +96 -113
- package/dist/sqlite/sqliteDialect.js +3 -2
- package/dist/type/dialect.d.ts +24 -5
- package/dist/type/migratorDialect.d.ts +1 -1
- package/dist/type/migratorDialect.js +1 -0
- package/dist/type/query.d.ts +1 -1
- package/dist/type/queryWhere.d.ts +7 -19
- package/dist/type/vector.d.ts +3 -1
- package/dist/util/dialect.util.d.ts +10 -7
- package/dist/util/dialect.util.js +16 -29
- package/dist/util/object.util.d.ts +2 -0
- package/dist/util/object.util.js +4 -0
- package/package.json +13 -3
- package/dist/bunSql/bunSqlCockroachDialect.d.ts +0 -12
- package/dist/bunSql/bunSqlCockroachDialect.js +0 -15
- package/dist/bunSql/bunSqlPostgresDialect.d.ts +0 -11
- package/dist/bunSql/bunSqlPostgresDialect.js +0 -14
- package/dist/bunSql/bunSqliteDialect.d.ts +0 -6
- package/dist/bunSql/bunSqliteDialect.js +0 -6
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { QueryConflictPaths, QueryContext, QueryPager, Type } from '../type/index.js';
|
|
2
|
+
import { AbstractSqlDialect } from './abstractSqlDialect.js';
|
|
3
|
+
/**
|
|
4
|
+
* Shared SQL between SQL Server and Oracle: the two engines that spell paging and upsert the way the
|
|
5
|
+
* standard does, where the Postgres and MySQL families each predate it.
|
|
6
|
+
*
|
|
7
|
+
* A family base rather than a pair of knobs, the way {@link PgLikeSqlDialect} and
|
|
8
|
+
* {@link MysqlLikeSqlDialect} already are - `pager` and `upsert` are both plain overrides, so nothing
|
|
9
|
+
* in the core has to learn that a second spelling exists.
|
|
10
|
+
*/
|
|
11
|
+
export declare abstract class MergeSqlDialect extends AbstractSqlDialect {
|
|
12
|
+
readonly escapeIdChar = "\"";
|
|
13
|
+
/**
|
|
14
|
+
* `OFFSET ... ROWS FETCH NEXT ... ROWS ONLY`, and an `ORDER BY` where the statement has none.
|
|
15
|
+
*
|
|
16
|
+
* SQL Server refuses to page an unordered statement. A constant `ORDER BY` costs one clause the
|
|
17
|
+
* optimizer discards and keeps `$limit` and `$skip` meaning the same thing here as everywhere
|
|
18
|
+
* else; the alternative, `TOP (n)` in the select list, needs a second hook and still leaves a
|
|
19
|
+
* `$skip` with no `$sort` unanswerable.
|
|
20
|
+
*/
|
|
21
|
+
pager(ctx: QueryContext, opts: QueryPager & {
|
|
22
|
+
$distinct?: boolean;
|
|
23
|
+
}, sorted?: boolean): void;
|
|
24
|
+
/**
|
|
25
|
+
* `MERGE`, which both engines take in place of the `ON CONFLICT`/`ON DUPLICATE KEY` the other
|
|
26
|
+
* families have. The rows go in as a `VALUES` row source rather than an `INSERT`, built by
|
|
27
|
+
* {@link AbstractSqlDialect.insertShape} so both shapes apply `onInsert` defaults identically.
|
|
28
|
+
*
|
|
29
|
+
* Every value binds before the assignments are rendered, so a `?`-placeholder engine needs none of
|
|
30
|
+
* the scratch-context reordering `ON CONFLICT` does - the source is read positionally, in order.
|
|
31
|
+
*/
|
|
32
|
+
upsert<E>(ctx: QueryContext, entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: E | E[], extraReturning?: string): void;
|
|
33
|
+
/** `<target>.<col> = <source>.<col>` for every conflict key, which is what makes a row "the same". */
|
|
34
|
+
private mergeOn;
|
|
35
|
+
/**
|
|
36
|
+
* A lock hint on the merge target. `MERGE` takes an update key lock but releases it before the
|
|
37
|
+
* insert, so two concurrent upserts of the same key race into a duplicate-key error; `HOLDLOCK`
|
|
38
|
+
* holds it across both. Empty on Oracle, which does not have the hint and does not need it.
|
|
39
|
+
*/
|
|
40
|
+
protected readonly mergeTargetHint: string;
|
|
41
|
+
/** How the merge reports the row it wrote. Oracle has no such clause on a `MERGE` and omits it. */
|
|
42
|
+
protected mergeReturning(expression: string): string;
|
|
43
|
+
/** `MERGE` must be terminated on SQL Server; nothing else here cares. */
|
|
44
|
+
protected readonly statementTerminator: string;
|
|
45
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { getMeta } from '../entity/index.js';
|
|
2
|
+
import { assertNonNegativeInteger, getKeys } from '../util/index.js';
|
|
3
|
+
import { AbstractSqlDialect } from './abstractSqlDialect.js';
|
|
4
|
+
import { UPSERT_SOURCE_ALIAS } from './aliases.js';
|
|
5
|
+
/**
|
|
6
|
+
* Shared SQL between SQL Server and Oracle: the two engines that spell paging and upsert the way the
|
|
7
|
+
* standard does, where the Postgres and MySQL families each predate it.
|
|
8
|
+
*
|
|
9
|
+
* A family base rather than a pair of knobs, the way {@link PgLikeSqlDialect} and
|
|
10
|
+
* {@link MysqlLikeSqlDialect} already are - `pager` and `upsert` are both plain overrides, so nothing
|
|
11
|
+
* in the core has to learn that a second spelling exists.
|
|
12
|
+
*/
|
|
13
|
+
export class MergeSqlDialect extends AbstractSqlDialect {
|
|
14
|
+
escapeIdChar = '"';
|
|
15
|
+
/**
|
|
16
|
+
* `OFFSET ... ROWS FETCH NEXT ... ROWS ONLY`, and an `ORDER BY` where the statement has none.
|
|
17
|
+
*
|
|
18
|
+
* SQL Server refuses to page an unordered statement. A constant `ORDER BY` costs one clause the
|
|
19
|
+
* optimizer discards and keeps `$limit` and `$skip` meaning the same thing here as everywhere
|
|
20
|
+
* else; the alternative, `TOP (n)` in the select list, needs a second hook and still leaves a
|
|
21
|
+
* `$skip` with no `$sort` unanswerable.
|
|
22
|
+
*/
|
|
23
|
+
pager(ctx, opts, sorted = false) {
|
|
24
|
+
if (opts.$limit === undefined && opts.$skip === undefined) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (!sorted) {
|
|
28
|
+
// A `SELECT DISTINCT` may only order by something it projects, so the constant cannot be used
|
|
29
|
+
// there; the first projected column is the one term always available.
|
|
30
|
+
ctx.append(` ORDER BY ${opts.$distinct ? '1' : '(SELECT NULL)'}`);
|
|
31
|
+
}
|
|
32
|
+
ctx.append(` OFFSET ${assertNonNegativeInteger(opts.$skip ?? 0, '$skip')} ROWS`);
|
|
33
|
+
if (opts.$limit !== undefined) {
|
|
34
|
+
ctx.append(` FETCH NEXT ${assertNonNegativeInteger(opts.$limit, '$limit')} ROWS ONLY`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* `MERGE`, which both engines take in place of the `ON CONFLICT`/`ON DUPLICATE KEY` the other
|
|
39
|
+
* families have. The rows go in as a `VALUES` row source rather than an `INSERT`, built by
|
|
40
|
+
* {@link AbstractSqlDialect.insertShape} so both shapes apply `onInsert` defaults identically.
|
|
41
|
+
*
|
|
42
|
+
* Every value binds before the assignments are rendered, so a `?`-placeholder engine needs none of
|
|
43
|
+
* the scratch-context reordering `ON CONFLICT` does - the source is read positionally, in order.
|
|
44
|
+
*/
|
|
45
|
+
upsert(ctx, entity, conflictPaths, payload, extraReturning = '') {
|
|
46
|
+
const meta = getMeta(entity);
|
|
47
|
+
const table = this.escapedTableName(meta);
|
|
48
|
+
const source = this.escapeId(UPSERT_SOURCE_ALIAS, true);
|
|
49
|
+
// Before the row source, which is what fills the payload's `onInsert` fields: a column that
|
|
50
|
+
// exists only there - the generated key, `createdAt` - must not join the update set, or a row
|
|
51
|
+
// that already existed has both rewritten.
|
|
52
|
+
const update = this.getUpsertUpdateAssignments(ctx, meta, conflictPaths, payload, (col) => `${source}.${col}`);
|
|
53
|
+
const shape = this.insertShape(entity, payload);
|
|
54
|
+
const columns = shape.columns.join(', ');
|
|
55
|
+
ctx.append(`MERGE INTO ${table}${this.mergeTargetHint} USING (VALUES `);
|
|
56
|
+
this.appendValueRows(ctx, shape);
|
|
57
|
+
ctx.append(`) AS ${source} (${columns}) ON ${this.mergeOn(meta, conflictPaths, table, source)}`);
|
|
58
|
+
if (update) {
|
|
59
|
+
ctx.append(` WHEN MATCHED THEN UPDATE SET ${update}`);
|
|
60
|
+
}
|
|
61
|
+
ctx.append(` WHEN NOT MATCHED THEN INSERT (${columns}) VALUES (${shape.columns.map((col) => `${source}.${col}`).join(', ')})`);
|
|
62
|
+
const returning = [this.returningIdExpression(meta), extraReturning].filter(Boolean).join(', ');
|
|
63
|
+
if (returning) {
|
|
64
|
+
ctx.append(` ${this.mergeReturning(returning)}`);
|
|
65
|
+
}
|
|
66
|
+
ctx.append(this.statementTerminator);
|
|
67
|
+
}
|
|
68
|
+
/** `<target>.<col> = <source>.<col>` for every conflict key, which is what makes a row "the same". */
|
|
69
|
+
mergeOn(meta, conflictPaths, table, source) {
|
|
70
|
+
return getKeys(conflictPaths)
|
|
71
|
+
.map((key) => {
|
|
72
|
+
const column = this.escapeId(this.resolveColumnName(key, meta.fields[key]));
|
|
73
|
+
return `${table}.${column} = ${source}.${column}`;
|
|
74
|
+
})
|
|
75
|
+
.join(' AND ');
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* A lock hint on the merge target. `MERGE` takes an update key lock but releases it before the
|
|
79
|
+
* insert, so two concurrent upserts of the same key race into a duplicate-key error; `HOLDLOCK`
|
|
80
|
+
* holds it across both. Empty on Oracle, which does not have the hint and does not need it.
|
|
81
|
+
*/
|
|
82
|
+
mergeTargetHint = '';
|
|
83
|
+
/** How the merge reports the row it wrote. Oracle has no such clause on a `MERGE` and omits it. */
|
|
84
|
+
mergeReturning(expression) {
|
|
85
|
+
return `RETURNING ${expression}`;
|
|
86
|
+
}
|
|
87
|
+
/** `MERGE` must be terminated on SQL Server; nothing else here cares. */
|
|
88
|
+
statementTerminator = '';
|
|
89
|
+
}
|
|
@@ -41,7 +41,6 @@ export declare abstract class MysqlLikeSqlDialect extends AbstractSqlDialect {
|
|
|
41
41
|
readonly dropForeignKeySyntax = "DROP FOREIGN KEY";
|
|
42
42
|
readonly dropPrimaryKeySyntax = "DROP PRIMARY KEY";
|
|
43
43
|
readonly dropIndexSyntax = "on-table";
|
|
44
|
-
readonly renameTableSyntax = "rename-table";
|
|
45
44
|
readonly alterColumnSyntax = "MODIFY COLUMN";
|
|
46
45
|
readonly booleanLiteral = "integer";
|
|
47
46
|
readonly insertIdSource: InsertIdSource;
|
|
@@ -29,7 +29,6 @@ export class MysqlLikeSqlDialect extends AbstractSqlDialect {
|
|
|
29
29
|
indexIfNotExists: false,
|
|
30
30
|
schemas: true,
|
|
31
31
|
dropTableCascade: false,
|
|
32
|
-
renameColumn: true,
|
|
33
32
|
foreignKeyAlter: true,
|
|
34
33
|
primaryKeyAlter: true,
|
|
35
34
|
generatedColumnAdd: true,
|
|
@@ -37,7 +36,9 @@ export class MysqlLikeSqlDialect extends AbstractSqlDialect {
|
|
|
37
36
|
vectorIndexRequiresNotNull: false,
|
|
38
37
|
vectorSupportsLength: false,
|
|
39
38
|
supportsTimestamptz: false,
|
|
40
|
-
|
|
39
|
+
stringSizing: 'varchar',
|
|
40
|
+
supportsUnsigned: true,
|
|
41
|
+
serverSideCursors: false,
|
|
41
42
|
};
|
|
42
43
|
/**
|
|
43
44
|
* `information_schema` keeps InnoDB's own row estimate, which is live enough to answer before
|
|
@@ -81,7 +82,6 @@ export class MysqlLikeSqlDialect extends AbstractSqlDialect {
|
|
|
81
82
|
dropForeignKeySyntax = 'DROP FOREIGN KEY';
|
|
82
83
|
dropPrimaryKeySyntax = 'DROP PRIMARY KEY';
|
|
83
84
|
dropIndexSyntax = 'on-table';
|
|
84
|
-
renameTableSyntax = 'rename-table';
|
|
85
85
|
alterColumnSyntax = 'MODIFY COLUMN';
|
|
86
86
|
booleanLiteral = 'integer';
|
|
87
87
|
// No `RETURNING` support, so multi-row insert IDs are inferred from the header - see the
|
|
@@ -29,7 +29,6 @@ export class PgLikeSqlDialect extends AbstractSqlDialect {
|
|
|
29
29
|
indexIfNotExists: true,
|
|
30
30
|
schemas: true,
|
|
31
31
|
dropTableCascade: true,
|
|
32
|
-
renameColumn: true,
|
|
33
32
|
foreignKeyAlter: true,
|
|
34
33
|
primaryKeyAlter: true,
|
|
35
34
|
generatedColumnAdd: true,
|
|
@@ -37,7 +36,9 @@ export class PgLikeSqlDialect extends AbstractSqlDialect {
|
|
|
37
36
|
vectorIndexRequiresNotNull: false,
|
|
38
37
|
vectorSupportsLength: true,
|
|
39
38
|
supportsTimestamptz: true,
|
|
40
|
-
|
|
39
|
+
stringSizing: 'bounded-text',
|
|
40
|
+
supportsUnsigned: false,
|
|
41
|
+
serverSideCursors: true,
|
|
41
42
|
};
|
|
42
43
|
escapeIdChar = '"';
|
|
43
44
|
// Shared default for both dialects. CockroachDB docs flag sequential PKs as a hotspotting risk
|
|
@@ -124,7 +124,8 @@ export class VectorSqlDialect extends AbstractDialect {
|
|
|
124
124
|
throw unsupportedVectorMetric(this.dialectName, distance);
|
|
125
125
|
}
|
|
126
126
|
if ('fn' in metric) {
|
|
127
|
-
|
|
127
|
+
const leading = metric.metricArg === undefined ? '' : `'${metric.metricArg}', `;
|
|
128
|
+
ctx.append(`${metric.fn}(${leading}${this.escapeId(colName)}, `);
|
|
128
129
|
this.appendVectorValue(ctx, search.$vector, field);
|
|
129
130
|
ctx.append(')');
|
|
130
131
|
return;
|
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.
|
|
@@ -26,6 +26,11 @@ export const DIALECT_DEFAULTS = {
|
|
|
26
26
|
mysql: { expressions: MYSQL, wrapTypes: MYSQL_LARGE_TYPES },
|
|
27
27
|
mariadb: { expressions: { ...MYSQL, uuidv7: 'UUID_v7()' }, wrapTypes: MYSQL_LARGE_TYPES },
|
|
28
28
|
sqlite: { expressions: ANSI },
|
|
29
|
+
// `SYSUTCDATETIME()` over `CURRENT_TIMESTAMP`, which is local time in the server's zone. No
|
|
30
|
+
// `uuidv7`: `NEWSEQUENTIALID()` is an ordered v4 GUID, so it carries no readable timestamp and
|
|
31
|
+
// does not sort the way a v7 does elsewhere - a `uuidv7()` default is refused rather than served
|
|
32
|
+
// something that only looks like one.
|
|
33
|
+
mssql: { expressions: { ...ANSI, now: 'SYSUTCDATETIME()', uuid: 'NEWID()' } },
|
|
29
34
|
};
|
|
30
35
|
/**
|
|
31
36
|
* A DDL default that is SQL rather than a literal. A class, not a plain object, so a JSON default
|
|
@@ -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
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export * from './abstractSqlSchemaIntrospector.js';
|
|
2
2
|
export * from './mongoIntrospector.js';
|
|
3
|
+
export * from './mssqlIntrospector.js';
|
|
3
4
|
export { MariadbSchemaIntrospector, MysqlSchemaIntrospector } from './mysqlIntrospector.js';
|
|
4
5
|
export * from './postgresIntrospector.js';
|
|
5
6
|
export * from './sqliteIntrospector.js';
|
|
7
|
+
export { introspectorFor } from './registry.js';
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export * from './abstractSqlSchemaIntrospector.js';
|
|
2
2
|
export * from './mongoIntrospector.js';
|
|
3
|
+
export * from './mssqlIntrospector.js';
|
|
3
4
|
export { MariadbSchemaIntrospector, MysqlSchemaIntrospector } from './mysqlIntrospector.js';
|
|
4
5
|
export * from './postgresIntrospector.js';
|
|
5
6
|
export * from './sqliteIntrospector.js';
|
|
7
|
+
export { introspectorFor } from './registry.js';
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { ColumnSchema, ForeignKeySchema, IndexSchema } from '../../type/index.js';
|
|
2
|
+
import { AbstractSqlSchemaIntrospector, type TableRowReader } from './abstractSqlSchemaIntrospector.js';
|
|
3
|
+
/**
|
|
4
|
+
* SQL Server schema introspector.
|
|
5
|
+
*
|
|
6
|
+
* `INFORMATION_SCHEMA` answers columns and foreign keys, but not indexes: it has no view for them at
|
|
7
|
+
* all, and its `CONSTRAINT_COLUMN_USAGE` conflates a unique index with a unique constraint. Those
|
|
8
|
+
* come from `sys.indexes` instead, which is also the only place the filtered-index predicate and the
|
|
9
|
+
* included columns are readable.
|
|
10
|
+
*/
|
|
11
|
+
export declare class MsSqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
12
|
+
protected readonly defaultSchemaExpr = "SCHEMA_NAME()";
|
|
13
|
+
protected getTableNamesQuery(): string;
|
|
14
|
+
protected tableExistsQuery(): string;
|
|
15
|
+
protected parseTableExistsResult(results: {
|
|
16
|
+
count?: number;
|
|
17
|
+
}[]): boolean;
|
|
18
|
+
/**
|
|
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
|
+
*/
|
|
23
|
+
protected getColumnsQuery(_tableName: string): string;
|
|
24
|
+
/** `is_primary_key` is excluded: the key is read separately, the way every other engine reads it. */
|
|
25
|
+
protected getIndexesQuery(_tableName: string): string;
|
|
26
|
+
protected getForeignKeysQuery(_tableName: string): string;
|
|
27
|
+
protected getPrimaryKeyQuery(_tableName: string): string;
|
|
28
|
+
protected mapColumnsResult(_read: TableRowReader, _tableName: string, results: MsSqlColumnRow[]): Promise<ColumnSchema[]>;
|
|
29
|
+
protected mapIndexesResult(_read: TableRowReader, _tableName: string, results: {
|
|
30
|
+
index_name: string;
|
|
31
|
+
columns: string;
|
|
32
|
+
is_unique: boolean;
|
|
33
|
+
}[]): Promise<IndexSchema[]>;
|
|
34
|
+
protected mapForeignKeysResult(_read: TableRowReader, _tableName: string, results: {
|
|
35
|
+
constraint_name: string;
|
|
36
|
+
columns: string;
|
|
37
|
+
referenced_table: string;
|
|
38
|
+
referenced_columns: string;
|
|
39
|
+
delete_rule: string;
|
|
40
|
+
update_rule: string;
|
|
41
|
+
}[]): Promise<ForeignKeySchema[]>;
|
|
42
|
+
/**
|
|
43
|
+
* A default is stored wrapped in at least one layer of parentheses - `((0))` for a number,
|
|
44
|
+
* `(N'x')` for a string - because the engine reprints it from its own parse tree.
|
|
45
|
+
*/
|
|
46
|
+
protected parseDefaultValue(defaultValue: string | null): unknown;
|
|
47
|
+
}
|
|
48
|
+
type MsSqlColumnRow = {
|
|
49
|
+
column_name: string;
|
|
50
|
+
data_type: string;
|
|
51
|
+
max_length: number | null;
|
|
52
|
+
numeric_precision: number | null;
|
|
53
|
+
numeric_scale: number | null;
|
|
54
|
+
is_nullable: boolean;
|
|
55
|
+
is_identity: boolean;
|
|
56
|
+
column_default: string | null;
|
|
57
|
+
is_primary_key: number | null;
|
|
58
|
+
is_unique: number | null;
|
|
59
|
+
};
|
|
60
|
+
export {};
|