uql-orm 0.61.0 → 0.62.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.map +1 -1
- package/dist/dialect/abstractSqlDialect.d.ts +12 -7
- package/dist/dialect/abstractSqlDialect.js +51 -50
- package/dist/dialect/mysqlLikeSqlDialect.js +1 -1
- package/dist/dialect/pgLikeSqlDialect.d.ts +1 -0
- package/dist/dialect/pgLikeSqlDialect.js +8 -7
- package/dist/dialect/queryContext.d.ts +5 -6
- package/dist/dialect/queryContext.js +8 -8
- package/dist/entity/decorator/entity.d.ts +3 -3
- package/dist/entity/decorator/entity.js +2 -2
- package/dist/entity/decorator/members.d.ts +3 -2
- package/dist/entity/decorator/members.js +1 -0
- package/dist/entity/metadata/definition.js +12 -6
- package/dist/migrate/builder/migrationBuilder.js +3 -5
- package/dist/migrate/builder/tableBuilder.js +2 -4
- package/dist/migrate/builder/types.d.ts +11 -3
- package/dist/migrate/codegen/indexDecoratorSource.js +3 -1
- package/dist/migrate/generator/definitionToNode.d.ts +8 -5
- package/dist/migrate/generator/definitionToNode.js +13 -4
- package/dist/migrate/generator/mongoSchemaGenerator.d.ts +2 -0
- package/dist/migrate/generator/mongoSchemaGenerator.js +6 -1
- package/dist/migrate/schemaGenerator.d.ts +5 -3
- package/dist/migrate/schemaGenerator.js +9 -2
- package/dist/mongo/mongoDialect.js +1 -1
- package/dist/mssql/mssqlDialect.js +3 -5
- package/dist/schema/schemaASTBuilder.d.ts +6 -1
- package/dist/schema/schemaASTBuilder.js +17 -16
- package/dist/type/dialect.d.ts +15 -6
- package/dist/type/entity.d.ts +94 -34
- package/dist/type/migration.d.ts +9 -2
- package/dist/type/query.d.ts +3 -3
- package/dist/type/queryLock.d.ts +7 -7
- package/dist/type/queryLock.js +8 -6
- package/dist/type/queryRaw.d.ts +22 -30
- package/dist/type/queryRaw.js +3 -9
- package/dist/util/ddlExpression.util.d.ts +8 -13
- package/dist/util/ddlExpression.util.js +17 -19
- package/dist/util/dialect.util.d.ts +1 -1
- package/dist/util/dialect.util.js +3 -4
- package/dist/util/field.util.d.ts +1 -1
- package/dist/util/raw.d.ts +19 -17
- package/dist/util/raw.js +43 -17
- package/package.json +1 -1
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
export class SqlQueryContext {
|
|
9
9
|
dialect;
|
|
10
10
|
statement;
|
|
11
|
+
inlineValues;
|
|
11
12
|
sqlChunks = [];
|
|
12
13
|
params;
|
|
13
14
|
tableAliases = new Set();
|
|
@@ -19,14 +20,16 @@ export class SqlQueryContext {
|
|
|
19
20
|
* than needing to be reconciled after the fact.
|
|
20
21
|
* @param statement The context this one renders a fragment of, which owns the claimed aliases: a
|
|
21
22
|
* fragment is part of one statement, so its aliases have to be unique across the whole of it.
|
|
23
|
+
* @param inlineValues See {@link QueryContext.inlineValues}; a fragment takes its statement's.
|
|
22
24
|
*/
|
|
23
|
-
constructor(dialect, params = [], statement) {
|
|
25
|
+
constructor(dialect, params = [], statement, inlineValues = false) {
|
|
24
26
|
this.dialect = dialect;
|
|
25
27
|
this.statement = statement;
|
|
28
|
+
this.inlineValues = inlineValues;
|
|
26
29
|
this.params = params;
|
|
27
30
|
}
|
|
28
31
|
createFragment() {
|
|
29
|
-
return new SqlQueryContext(this.dialect, this.params, this.statement ?? this);
|
|
32
|
+
return new SqlQueryContext(this.dialect, this.params, this.statement ?? this, this.inlineValues);
|
|
30
33
|
}
|
|
31
34
|
/**
|
|
32
35
|
* Appends raw SQL string fragments to the query.
|
|
@@ -41,14 +44,11 @@ export class SqlQueryContext {
|
|
|
41
44
|
return this;
|
|
42
45
|
}
|
|
43
46
|
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* @param value The value to be parameterized.
|
|
48
|
-
* @returns The current context instance for method chaining.
|
|
47
|
+
* Appends the SQL the dialect writes for `value`: a placeholder for the value bound, or its literal
|
|
48
|
+
* where this context inlines values.
|
|
49
49
|
*/
|
|
50
50
|
addValue(value) {
|
|
51
|
-
this.sqlChunks.push(this.dialect.addValue(this
|
|
51
|
+
this.sqlChunks.push(this.dialect.addValue(this, value));
|
|
52
52
|
return this;
|
|
53
53
|
}
|
|
54
54
|
/**
|
|
@@ -12,7 +12,7 @@ export declare function Entity<E>(opts?: EntityOptions<E>): (entity: Type<E>, co
|
|
|
12
12
|
/**
|
|
13
13
|
* Registers a named `$where` filter, applied to every query unless bypassed via `QueryOptions.filters`.
|
|
14
14
|
*
|
|
15
|
-
* @example `@Filter('active', {
|
|
15
|
+
* @example `@Filter('active', { where: { status: 'active' }, default: false })`
|
|
16
16
|
*/
|
|
17
17
|
export declare function Filter<E>(name: string, opts: FilterOptions<E>): (entity: Type<E>) => void;
|
|
18
18
|
/**
|
|
@@ -22,6 +22,6 @@ export declare function Filter<E>(name: string, opts: FilterOptions<E>): (entity
|
|
|
22
22
|
*
|
|
23
23
|
* @example `@Index((user) => [user.lastName, user.firstName], { name: 'users_fullname_idx' })`
|
|
24
24
|
* @example `@Index((user) => [user.email], { unique: true })`
|
|
25
|
-
* @example `@Index((user) => [user.
|
|
25
|
+
* @example `@Index((user) => [user.email], { unique: true, where: { deletedAt: null } })`
|
|
26
26
|
*/
|
|
27
|
-
export declare function Index<E>(columns: (keys: KeyMap<E>) => readonly IndexColumnInput<FieldKey<
|
|
27
|
+
export declare function Index<E>(columns: (keys: KeyMap<E>) => readonly IndexColumnInput<FieldKey<E>, E>[], options?: EntityIndexOptions<E>): (entity: Type<E>) => void;
|
|
@@ -20,7 +20,7 @@ export function Entity(opts) {
|
|
|
20
20
|
/**
|
|
21
21
|
* Registers a named `$where` filter, applied to every query unless bypassed via `QueryOptions.filters`.
|
|
22
22
|
*
|
|
23
|
-
* @example `@Filter('active', {
|
|
23
|
+
* @example `@Filter('active', { where: { status: 'active' }, default: false })`
|
|
24
24
|
*/
|
|
25
25
|
export function Filter(name, opts) {
|
|
26
26
|
return (entity) => {
|
|
@@ -34,7 +34,7 @@ export function Filter(name, opts) {
|
|
|
34
34
|
*
|
|
35
35
|
* @example `@Index((user) => [user.lastName, user.firstName], { name: 'users_fullname_idx' })`
|
|
36
36
|
* @example `@Index((user) => [user.email], { unique: true })`
|
|
37
|
-
* @example `@Index((user) => [user.
|
|
37
|
+
* @example `@Index((user) => [user.email], { unique: true, where: { deletedAt: null } })`
|
|
38
38
|
*/
|
|
39
39
|
export function Index(columns, options = {}) {
|
|
40
40
|
return (entity) => {
|
|
@@ -40,12 +40,13 @@ type EnumValue<Members, Declared> = Declared extends Members ? {
|
|
|
40
40
|
*
|
|
41
41
|
* @example `@Field({ type: String }) name?: string;`
|
|
42
42
|
* @example `@Field({ references: () => User }) userId?: string;` (where `User.id` is a `uuid`)
|
|
43
|
+
* @example `@Field({ type: Number, computed: (line) => raw`${line.qty} * ${line.price}` }) total?: number;`
|
|
43
44
|
*/
|
|
44
|
-
export declare function Field<O extends FieldOptions<DeclaredValue<O
|
|
45
|
+
export declare function Field<This, O extends FieldOptions<DeclaredValue<O>, This> & ({
|
|
45
46
|
type: FieldType;
|
|
46
47
|
} | {
|
|
47
48
|
references: EntityGetter;
|
|
48
|
-
}) & RejectUnknown<O, FieldOptions> & RejectIncompatible<O>>(opts: O): MemberDecorator<DeclaredValue<O> | undefined>;
|
|
49
|
+
}) & RejectUnknown<O, FieldOptions> & RejectIncompatible<O>>(opts: O): MemberDecorator<DeclaredValue<O> | undefined, This>;
|
|
49
50
|
/**
|
|
50
51
|
* A key the type level cannot name, reported on each `@Id` that leaves it unnamed. Where no `idKey`
|
|
51
52
|
* brand and no conventional name applies, `IdKey` falls back to every field, and `IdValue`,
|
|
@@ -8,6 +8,7 @@ import { memberRegistrations } from './bag.js';
|
|
|
8
8
|
*
|
|
9
9
|
* @example `@Field({ type: String }) name?: string;`
|
|
10
10
|
* @example `@Field({ references: () => User }) userId?: string;` (where `User.id` is a `uuid`)
|
|
11
|
+
* @example `@Field({ type: Number, computed: (line) => raw`${line.qty} * ${line.price}` }) total?: number;`
|
|
11
12
|
*/
|
|
12
13
|
export function Field(opts) {
|
|
13
14
|
return (_value, context) => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SOFT_DELETE_FILTER } from '../../type/index.js';
|
|
2
2
|
import { isInlinedExpression } from '../../util/field.util.js';
|
|
3
|
-
import { entityName, fieldOptionConflict, getKeys,
|
|
3
|
+
import { entityName, entitySql, entityWhere, fieldOptionConflict, getKeys, hasKeys, isToManyRelation, lowerFirst, normalizeIndexColumn, upperFirst, definedEntries, } from '../../util/index.js';
|
|
4
4
|
import { ownRegistrations } from '../decorator/bag.js';
|
|
5
5
|
/**
|
|
6
6
|
* A map held on `globalThis` through the global symbol registry, so a single one survives multiple
|
|
@@ -31,8 +31,14 @@ export function defineField(entity, key, opts = {}) {
|
|
|
31
31
|
// Flagged when the author gave `references` but no `type`, so schema generation knows to resolve the
|
|
32
32
|
// column from the referenced primary key (picking up its `columnType`, length and chained keys)
|
|
33
33
|
// instead of treating whatever ends up in `type` as deliberate.
|
|
34
|
-
const
|
|
35
|
-
|
|
34
|
+
const { computed, ...rest } = opts;
|
|
35
|
+
const resolved = rest.type ? rest : { ...rest, typeFromReference: true };
|
|
36
|
+
meta.fields[fieldKey] = {
|
|
37
|
+
...meta.fields[fieldKey],
|
|
38
|
+
name: key,
|
|
39
|
+
...resolved,
|
|
40
|
+
...(computed && { computed: entitySql(computed) }),
|
|
41
|
+
};
|
|
36
42
|
return meta;
|
|
37
43
|
}
|
|
38
44
|
export function defineId(entity, key, opts) {
|
|
@@ -94,7 +100,7 @@ export function defineIndex(entity, index) {
|
|
|
94
100
|
(meta.indexes ??= []).push({
|
|
95
101
|
...index,
|
|
96
102
|
unique: index.unique ?? false,
|
|
97
|
-
where:
|
|
103
|
+
where: index.where && entityWhere(index.where),
|
|
98
104
|
columns: index.columns(keys).map(normalizeIndexColumn),
|
|
99
105
|
include: index.include?.(keys),
|
|
100
106
|
});
|
|
@@ -161,7 +167,7 @@ export function defineEntity(entity, opts = {}) {
|
|
|
161
167
|
});
|
|
162
168
|
// Unnamed checks are named by the generator, as unnamed indexes are.
|
|
163
169
|
for (const check of opts.checks ?? []) {
|
|
164
|
-
(meta.checks ??= []).push({ name: check.name,
|
|
170
|
+
(meta.checks ??= []).push({ name: check.name, where: entityWhere(check.where) });
|
|
165
171
|
}
|
|
166
172
|
for (const index of opts.indexes ?? []) {
|
|
167
173
|
defineIndex(entity, index);
|
|
@@ -204,7 +210,7 @@ export function defineEntity(entity, opts = {}) {
|
|
|
204
210
|
meta.softDelete = softDeleteKeys[0];
|
|
205
211
|
if (!meta.filters)
|
|
206
212
|
meta.filters = {};
|
|
207
|
-
meta.filters[SOFT_DELETE_FILTER] = {
|
|
213
|
+
meta.filters[SOFT_DELETE_FILTER] = { where: { [meta.softDelete]: null }, default: true };
|
|
208
214
|
}
|
|
209
215
|
const ids = getIdKeys(meta);
|
|
210
216
|
if (!ids.length) {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* - OperationRecorder: Record operations only (for code generation)
|
|
6
6
|
* - MigrationBuilder: Execute DDL operations (for integration tests/runtime)
|
|
7
7
|
*/
|
|
8
|
-
import {
|
|
8
|
+
import { indexNameParts, normalizeIndexColumn } from '../../util/index.js';
|
|
9
9
|
import { derivedIndexName } from '../../util/sql.util.js';
|
|
10
10
|
import { createSchemaGenerator } from '../schemaGenerator.js';
|
|
11
11
|
import { splitSqlStatements } from './splitSqlStatements.js';
|
|
@@ -23,9 +23,7 @@ function createIndexOperation(tableName, columns, options = {}) {
|
|
|
23
23
|
tableName,
|
|
24
24
|
index: {
|
|
25
25
|
...index,
|
|
26
|
-
name: name ??
|
|
27
|
-
derivedIndexName(tableName, entries.map((entry) => entry.column)),
|
|
28
|
-
where: ddlText(index.where, 'a partial-index predicate'),
|
|
26
|
+
name: name ?? derivedIndexName(tableName, indexNameParts(entries)),
|
|
29
27
|
entries,
|
|
30
28
|
unique: unique ?? false,
|
|
31
29
|
},
|
|
@@ -310,7 +308,7 @@ export class MigrationBuilder extends OperationRecorder {
|
|
|
310
308
|
case 'alterColumn':
|
|
311
309
|
return this.sqlGenerator.generateAlterColumnSql(operation.tableName, operation.columnName, operation.changes);
|
|
312
310
|
case 'createIndex':
|
|
313
|
-
return this.sqlGenerator.
|
|
311
|
+
return this.sqlGenerator.generateCreateIndexFromDefinition(operation.tableName, operation.index);
|
|
314
312
|
case 'dropIndex':
|
|
315
313
|
return this.sqlGenerator.generateDropIndex(operation.tableName, operation.indexName);
|
|
316
314
|
case 'addForeignKey':
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Fluent API for defining tables in migrations.
|
|
5
5
|
*/
|
|
6
|
-
import {
|
|
6
|
+
import { indexNameParts, normalizeIndexColumn } from '../../util/index.js';
|
|
7
7
|
import { derivedIndexName } from '../../util/sql.util.js';
|
|
8
8
|
import { columnForeignKey, columnIndex } from '../generator/definitionToNode.js';
|
|
9
9
|
import { ColumnBuilder } from './columnBuilder.js';
|
|
@@ -171,9 +171,7 @@ export class TableBuilder {
|
|
|
171
171
|
const entries = columns.map(normalizeIndexColumn);
|
|
172
172
|
this._indexes.push({
|
|
173
173
|
...rest,
|
|
174
|
-
name: name ??
|
|
175
|
-
derivedIndexName(this._name, entries.map((entry) => entry.column), unique),
|
|
176
|
-
where: ddlText(rest.where, 'a partial-index predicate'),
|
|
174
|
+
name: name ?? derivedIndexName(this._name, indexNameParts(entries), unique),
|
|
177
175
|
entries,
|
|
178
176
|
unique,
|
|
179
177
|
});
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Enables type-safe migrations without raw SQL.
|
|
6
6
|
*/
|
|
7
7
|
import type { ColumnNode, EnumValues, ForeignKeyAction } from '../../schema/types.js';
|
|
8
|
-
import type { IndexColumnInput, IndexOptions, IndexSchema } from '../../type/index.js';
|
|
8
|
+
import type { EntityIndexColumn, Except, IndexColumnInput, IndexOptions, IndexSchema, QueryRaw } from '../../type/index.js';
|
|
9
9
|
import type { ForeignKeySchema } from '../../type/migration.js';
|
|
10
10
|
/**
|
|
11
11
|
* Foreign key reference options.
|
|
@@ -91,6 +91,14 @@ export interface FullColumnDefinition extends ColumnDefinition {
|
|
|
91
91
|
/** Index name (if indexed) */
|
|
92
92
|
index?: string | boolean;
|
|
93
93
|
}
|
|
94
|
+
/**
|
|
95
|
+
* An index as the builder records it, with the entries and options `@Index` takes: its SQL kept as `raw`
|
|
96
|
+
* until a generator renders it for the engine the migration runs on.
|
|
97
|
+
*/
|
|
98
|
+
export type IndexDefinition = Except<IndexSchema, 'entries' | 'where'> & {
|
|
99
|
+
readonly entries: readonly EntityIndexColumn[];
|
|
100
|
+
readonly where?: QueryRaw;
|
|
101
|
+
};
|
|
94
102
|
/**
|
|
95
103
|
* Complete table definition.
|
|
96
104
|
*/
|
|
@@ -102,7 +110,7 @@ export interface TableDefinition {
|
|
|
102
110
|
/** Primary key columns (for composite keys) */
|
|
103
111
|
primaryKey?: string[];
|
|
104
112
|
/** Index definitions */
|
|
105
|
-
indexes:
|
|
113
|
+
indexes: IndexDefinition[];
|
|
106
114
|
/** Foreign key definitions at table level */
|
|
107
115
|
foreignKeys: ForeignKeySchema[];
|
|
108
116
|
/** Table comment */
|
|
@@ -183,7 +191,7 @@ export interface RenameColumnOperation extends MigrationOperation {
|
|
|
183
191
|
export interface CreateIndexOperation extends MigrationOperation {
|
|
184
192
|
type: 'createIndex';
|
|
185
193
|
tableName: string;
|
|
186
|
-
index:
|
|
194
|
+
index: IndexDefinition;
|
|
187
195
|
ifNotExists?: boolean;
|
|
188
196
|
}
|
|
189
197
|
/**
|
|
@@ -89,7 +89,9 @@ export function indexNeedsRaw(index) {
|
|
|
89
89
|
}
|
|
90
90
|
function indexEntrySource(entry, propertyName, param) {
|
|
91
91
|
if (entry.expression) {
|
|
92
|
-
|
|
92
|
+
const expression = `() => ${rawTag(entry.column)}`;
|
|
93
|
+
const modifiers = significantModifiers(entry);
|
|
94
|
+
return modifiers.length === 0 ? expression : `{ column: ${expression}, ${modifiers.join(', ')} }`;
|
|
93
95
|
}
|
|
94
96
|
const column = memberSource(param, propertyName(entry.column));
|
|
95
97
|
const modifiers = significantModifiers(entry);
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import type { ColumnNode, TableNode } from '../../schema/types.js';
|
|
2
2
|
import type { ForeignKeySchema, IndexSchema } from '../../type/migration.js';
|
|
3
|
-
import type {
|
|
3
|
+
import type { QueryRaw } from '../../type/queryRaw.js';
|
|
4
|
+
import type { FullColumnDefinition, IndexDefinition, TableDefinition } from '../builder/types.js';
|
|
4
5
|
/**
|
|
5
6
|
* A migration builder's table definition as the AST nodes the generators render from, so a hand-written
|
|
6
|
-
* `createTable` and an entity reach `generateCreateTableFromNode` in the same shape
|
|
7
|
-
* not generator methods:
|
|
7
|
+
* `createTable` and an entity reach `generateCreateTableFromNode` in the same shape, its SQL rendered by
|
|
8
|
+
* `render`. Free functions and not generator methods: the dialect reaches them only through `render`.
|
|
8
9
|
*/
|
|
9
|
-
export declare function tableDefinitionToNode(def: TableDefinition): TableNode;
|
|
10
|
+
export declare function tableDefinitionToNode(def: TableDefinition, render: (sql: QueryRaw) => string): TableNode;
|
|
10
11
|
/**
|
|
11
12
|
* A builder's column as the AST node the generators render from.
|
|
12
13
|
*
|
|
@@ -25,6 +26,8 @@ export declare function fullColumnDefinitionToNode(col: FullColumnDefinition, ta
|
|
|
25
26
|
* Shared with `TableBuilder.build`, which lifts these into the table it is creating: written twice,
|
|
26
27
|
* `addColumn` had no lift at all and silently emitted a column with no index.
|
|
27
28
|
*/
|
|
28
|
-
export declare function columnIndex(tableName: string, col: FullColumnDefinition): IndexSchema | undefined;
|
|
29
|
+
export declare function columnIndex(tableName: string, col: FullColumnDefinition): Pick<IndexSchema, 'name' | 'entries' | 'unique'> | undefined;
|
|
30
|
+
/** An index the builder recorded, its SQL rendered by `render` into the text the schema holds. */
|
|
31
|
+
export declare function renderIndexDefinition(index: IndexDefinition, render: (sql: QueryRaw) => string): IndexSchema;
|
|
29
32
|
/** The foreign key a column-level `references` declares, or nothing. Shared for the same reason. */
|
|
30
33
|
export declare function columnForeignKey(col: FullColumnDefinition): ForeignKeySchema | undefined;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { renderIndexColumn } from '../../util/ddlExpression.util.js';
|
|
1
2
|
import { derivedForeignKeyName, derivedIndexName } from '../../util/sql.util.js';
|
|
2
3
|
/**
|
|
3
4
|
* A table the builder names but has not seen.
|
|
@@ -12,10 +13,10 @@ function unresolvedTable(name) {
|
|
|
12
13
|
}
|
|
13
14
|
/**
|
|
14
15
|
* A migration builder's table definition as the AST nodes the generators render from, so a hand-written
|
|
15
|
-
* `createTable` and an entity reach `generateCreateTableFromNode` in the same shape
|
|
16
|
-
* not generator methods:
|
|
16
|
+
* `createTable` and an entity reach `generateCreateTableFromNode` in the same shape, its SQL rendered by
|
|
17
|
+
* `render`. Free functions and not generator methods: the dialect reaches them only through `render`.
|
|
17
18
|
*/
|
|
18
|
-
export function tableDefinitionToNode(def) {
|
|
19
|
+
export function tableDefinitionToNode(def, render) {
|
|
19
20
|
const columns = new Map();
|
|
20
21
|
const pkNodes = [];
|
|
21
22
|
const table = {
|
|
@@ -40,7 +41,7 @@ export function tableDefinitionToNode(def) {
|
|
|
40
41
|
: pkNodes;
|
|
41
42
|
table.primaryKey = finalPrimaryKey;
|
|
42
43
|
for (const idxDef of def.indexes) {
|
|
43
|
-
table.indexes.push({ ...idxDef, table });
|
|
44
|
+
table.indexes.push({ ...renderIndexDefinition(idxDef, render), table });
|
|
44
45
|
}
|
|
45
46
|
for (const fkDef of def.foreignKeys) {
|
|
46
47
|
const relNode = {
|
|
@@ -92,6 +93,14 @@ export function columnIndex(tableName, col) {
|
|
|
92
93
|
unique: col.isUnique,
|
|
93
94
|
};
|
|
94
95
|
}
|
|
96
|
+
/** An index the builder recorded, its SQL rendered by `render` into the text the schema holds. */
|
|
97
|
+
export function renderIndexDefinition(index, render) {
|
|
98
|
+
return {
|
|
99
|
+
...index,
|
|
100
|
+
entries: index.entries.map((entry) => renderIndexColumn(entry, render)),
|
|
101
|
+
where: index.where && render(index.where),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
95
104
|
/** The foreign key a column-level `references` declares, or nothing. Shared for the same reason. */
|
|
96
105
|
export function columnForeignKey(col) {
|
|
97
106
|
if (!col.foreignKey) {
|
|
@@ -8,6 +8,8 @@ export declare class MongoSchemaGenerator extends AbstractDialect implements Sch
|
|
|
8
8
|
protected readonly featureDefaults: import("../../type/dialect.js").DialectFeatures;
|
|
9
9
|
readonly insertIdSource: InsertIdSource;
|
|
10
10
|
constructor(namingStrategy?: NamingStrategy, defaultForeignKeyAction?: ForeignKeyAction | undefined);
|
|
11
|
+
/** A collection has no SQL to render a check, a computed column or an index expression into. */
|
|
12
|
+
compileDdl(): string;
|
|
11
13
|
/**
|
|
12
14
|
* A document store has no cross-collection constraint, so unlike the SQL generator there is nothing to
|
|
13
15
|
* defer and no order to respect: this is each collection and nothing more. `foreignKeys` is accepted
|
|
@@ -3,6 +3,7 @@ import { getMeta } from '../../entity/index.js';
|
|
|
3
3
|
import { mongoDialectFeatures } from '../../mongo/mongoDialect.js';
|
|
4
4
|
import { getKeys } from '../../util/index.js';
|
|
5
5
|
import { derivedIndexName } from '../../util/sql.util.js';
|
|
6
|
+
import { renderIndexDefinition } from './definitionToNode.js';
|
|
6
7
|
import { indexNodeToSchema } from './indexNodeToSchema.js';
|
|
7
8
|
import { serializeMongoCommand } from './mongoCommand.js';
|
|
8
9
|
export class MongoSchemaGenerator extends AbstractDialect {
|
|
@@ -14,6 +15,10 @@ export class MongoSchemaGenerator extends AbstractDialect {
|
|
|
14
15
|
super({ namingStrategy });
|
|
15
16
|
this.defaultForeignKeyAction = defaultForeignKeyAction;
|
|
16
17
|
}
|
|
18
|
+
/** A collection has no SQL to render a check, a computed column or an index expression into. */
|
|
19
|
+
compileDdl() {
|
|
20
|
+
throw new TypeError('mongodb has no SQL to render a check, a computed column or an index expression into');
|
|
21
|
+
}
|
|
17
22
|
/**
|
|
18
23
|
* A document store has no cross-collection constraint, so unlike the SQL generator there is nothing to
|
|
19
24
|
* defer and no order to respect: this is each collection and nothing more. `foreignKeys` is accepted
|
|
@@ -135,7 +140,7 @@ export class MongoSchemaGenerator extends AbstractDialect {
|
|
|
135
140
|
generateCreateTableFromDefinition(table, _options) {
|
|
136
141
|
return [
|
|
137
142
|
serializeMongoCommand({ action: 'createCollection', name: table.name }),
|
|
138
|
-
...table.indexes.map((index) => this.generateCreateIndex(table.name, index)),
|
|
143
|
+
...table.indexes.map((index) => this.generateCreateIndex(table.name, renderIndexDefinition(index, () => this.compileDdl()))),
|
|
139
144
|
];
|
|
140
145
|
}
|
|
141
146
|
generateRenameTableSql(oldName, newName) {
|
|
@@ -2,8 +2,8 @@ import type { AbstractSqlDialect } from '../dialect/index.js';
|
|
|
2
2
|
import type { SchemaAST } from '../schema/schemaAST.js';
|
|
3
3
|
import { type DiffOptions } from '../schema/schemaASTDiffer.js';
|
|
4
4
|
import type { CanonicalType, ColumnNode, ForeignKeyAction, IndexNode, TableNode } from '../schema/types.js';
|
|
5
|
-
import type { ColumnSchema, CreateSchemaOptions, DialectFeatures, DropSchemaOptions, EntityMeta, FieldMeta, FieldOptions, ForeignKeySchema, IndexSchema, MigratorDialect, NamingStrategy, SchemaDiff, SchemaGenerator, SqlDdlGenerator, Type } from '../type/index.js';
|
|
6
|
-
import type { FullColumnDefinition, TableDefinition } from './builder/types.js';
|
|
5
|
+
import type { ColumnSchema, CreateSchemaOptions, DialectFeatures, DropSchemaOptions, EntityMeta, EntityWhereMeta, FieldMeta, FieldOptions, ForeignKeySchema, IndexSchema, MigratorDialect, NamingStrategy, SchemaDiff, SchemaGenerator, SqlDdlGenerator, Type } from '../type/index.js';
|
|
6
|
+
import type { FullColumnDefinition, IndexDefinition, TableDefinition } from './builder/types.js';
|
|
7
7
|
import { type IndexDdl, type TableDdl } from './ddl/index.js';
|
|
8
8
|
/**
|
|
9
9
|
* Unified SQL schema generator.
|
|
@@ -23,6 +23,7 @@ export declare class SqlSchemaGenerator implements SqlDdlGenerator {
|
|
|
23
23
|
resolveTableAlias<E>(meta: EntityMeta<E>): string;
|
|
24
24
|
resolveSchema<E>(meta: EntityMeta<E>): string | undefined;
|
|
25
25
|
resolveColumnName(key: string, field: FieldOptions): string;
|
|
26
|
+
compileDdl(sql: EntityWhereMeta<object>, entity: Type<object>): string;
|
|
26
27
|
/** Escape an identifier (table name, column name, etc.) */
|
|
27
28
|
protected escapeId(identifier: string): string;
|
|
28
29
|
/**
|
|
@@ -165,6 +166,7 @@ export declare class SqlSchemaGenerator implements SqlDdlGenerator {
|
|
|
165
166
|
generateCreateTableFromDefinition(table: TableDefinition, options?: {
|
|
166
167
|
ifNotExists?: boolean;
|
|
167
168
|
}): string[];
|
|
169
|
+
generateCreateIndexFromDefinition(tableName: string, index: IndexDefinition): string;
|
|
168
170
|
generateRenameTableSql(oldName: string, newName: string): string;
|
|
169
171
|
/**
|
|
170
172
|
* `ADD COLUMN`, plus the constraint and index the column declares.
|
|
@@ -217,7 +219,7 @@ export declare class SqlSchemaGenerator implements SqlDdlGenerator {
|
|
|
217
219
|
* other way and the table is created under one name and compared under another, which reports every
|
|
218
220
|
* table of a project using a naming strategy as both missing and unexpected.
|
|
219
221
|
*/
|
|
220
|
-
export declare function buildEntityAST(generator: Pick<SchemaGenerator, 'resolveTableAlias' | 'resolveSchema' | 'resolveColumnName'>, entities: readonly Type<object>[], defaultForeignKeyAction?: ForeignKeyAction): SchemaAST;
|
|
222
|
+
export declare function buildEntityAST(generator: Pick<SchemaGenerator, 'resolveTableAlias' | 'resolveSchema' | 'resolveColumnName' | 'compileDdl'>, entities: readonly Type<object>[], defaultForeignKeyAction?: ForeignKeyAction): SchemaAST;
|
|
221
223
|
/**
|
|
222
224
|
* Synchronous factory for SQL schema generators only.
|
|
223
225
|
* For MongoDB, use `createSchemaGeneratorAsync` from `./schemaGeneratorAsync.js` so the optional `mongodb` peer is not loaded at import time.
|
|
@@ -8,7 +8,7 @@ import { derivedCheckName, derivedForeignKeyName, derivedPrimaryKeyName } from '
|
|
|
8
8
|
import { formatDefaultValue, SqlExpression } from './builder/expressions.js';
|
|
9
9
|
import { indexDdlFor, tableDdlFor } from './ddl/index.js';
|
|
10
10
|
import { sizedType } from './ddl/tableDdl.js';
|
|
11
|
-
import { columnForeignKey, columnIndex, fullColumnDefinitionToNode, tableDefinitionToNode, } from './generator/definitionToNode.js';
|
|
11
|
+
import { columnForeignKey, columnIndex, fullColumnDefinitionToNode, renderIndexDefinition, tableDefinitionToNode, } from './generator/definitionToNode.js';
|
|
12
12
|
import { indexNodeToSchema } from './generator/indexNodeToSchema.js';
|
|
13
13
|
/**
|
|
14
14
|
* Unified SQL schema generator.
|
|
@@ -45,6 +45,9 @@ export class SqlSchemaGenerator {
|
|
|
45
45
|
resolveColumnName(key, field) {
|
|
46
46
|
return this.dialect.resolveColumnName(key, field);
|
|
47
47
|
}
|
|
48
|
+
compileDdl(sql, entity) {
|
|
49
|
+
return this.dialect.compileDdl(sql, entity);
|
|
50
|
+
}
|
|
48
51
|
/** Escape an identifier (table name, column name, etc.) */
|
|
49
52
|
escapeId(identifier) {
|
|
50
53
|
return this.dialect.escapeId(identifier);
|
|
@@ -565,9 +568,12 @@ export class SqlSchemaGenerator {
|
|
|
565
568
|
return this.generateCreateIndex(qualifyName(index.table.name, index.table.schema), indexNodeToSchema(index), options);
|
|
566
569
|
}
|
|
567
570
|
generateCreateTableFromDefinition(table, options = {}) {
|
|
568
|
-
const tableNode = tableDefinitionToNode(table);
|
|
571
|
+
const tableNode = tableDefinitionToNode(table, (sql) => this.dialect.compileDdl(sql));
|
|
569
572
|
return this.generateCreateTableFromNode(tableNode, options);
|
|
570
573
|
}
|
|
574
|
+
generateCreateIndexFromDefinition(tableName, index) {
|
|
575
|
+
return this.generateCreateIndex(tableName, renderIndexDefinition(index, (sql) => this.dialect.compileDdl(sql)));
|
|
576
|
+
}
|
|
571
577
|
generateRenameTableSql(oldName, newName) {
|
|
572
578
|
return this.tableDdl.renameTable(oldName, newName);
|
|
573
579
|
}
|
|
@@ -719,6 +725,7 @@ export function buildEntityAST(generator, entities, defaultForeignKeyAction) {
|
|
|
719
725
|
resolveTableName: (meta) => generator.resolveTableAlias(meta),
|
|
720
726
|
resolveSchema: (meta) => generator.resolveSchema(meta),
|
|
721
727
|
resolveColumnName: (key, field) => generator.resolveColumnName(key, field),
|
|
728
|
+
compileDdl: (sql, entity) => generator.compileDdl(sql, entity),
|
|
722
729
|
defaultForeignKeyAction,
|
|
723
730
|
});
|
|
724
731
|
}
|
|
@@ -263,7 +263,7 @@ export class MongoDialect extends AbstractDialect {
|
|
|
263
263
|
* lock silently removes the mutual exclusion the caller asked for.
|
|
264
264
|
*/
|
|
265
265
|
assertNoLock(q) {
|
|
266
|
-
if (q.$lock
|
|
266
|
+
if (q.$lock) {
|
|
267
267
|
throw new TypeError('$lock (row-level locking) is not supported on MongoDB');
|
|
268
268
|
}
|
|
269
269
|
}
|
|
@@ -273,8 +273,7 @@ export class MsSqlDialect extends MergeSqlDialect {
|
|
|
273
273
|
* scalar - which is why reading and writing need the two different binders here.
|
|
274
274
|
*/
|
|
275
275
|
jsonScalarParam(ctx, value) {
|
|
276
|
-
return (this.#jsonCompound(ctx, value) ??
|
|
277
|
-
this.addValue(ctx.values, typeof value === 'boolean' ? JSON.stringify(value) : value));
|
|
276
|
+
return (this.#jsonCompound(ctx, value) ?? this.addValue(ctx, typeof value === 'boolean' ? JSON.stringify(value) : value));
|
|
278
277
|
}
|
|
279
278
|
/**
|
|
280
279
|
* A value being *written* to a JSON path, which takes the type the driver sent: a `BIT` becomes a
|
|
@@ -286,7 +285,7 @@ export class MsSqlDialect extends MergeSqlDialect {
|
|
|
286
285
|
if (compound) {
|
|
287
286
|
return compound;
|
|
288
287
|
}
|
|
289
|
-
const placeholder = this.addValue(ctx
|
|
288
|
+
const placeholder = this.addValue(ctx, value);
|
|
290
289
|
return typeof value === 'boolean' ? `CAST(${placeholder} AS BIT)` : placeholder;
|
|
291
290
|
}
|
|
292
291
|
/**
|
|
@@ -300,8 +299,7 @@ export class MsSqlDialect extends MergeSqlDialect {
|
|
|
300
299
|
if (value === null || typeof value !== 'object') {
|
|
301
300
|
return undefined;
|
|
302
301
|
}
|
|
303
|
-
|
|
304
|
-
return `JSON_QUERY(${this.placeholder(ctx.values.length)})`;
|
|
302
|
+
return `JSON_QUERY(${this.addValue(ctx, JSON.stringify(value))})`;
|
|
305
303
|
}
|
|
306
304
|
/** An exploded element compares as text here, so `$elemMatch` always expands per field. */
|
|
307
305
|
jsonContainmentIsPartial = false;
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* - Database introspection results (TableSchema[])
|
|
7
7
|
*/
|
|
8
8
|
import type { EntityGetter } from '../type/entity.js';
|
|
9
|
-
import type { EntityMeta, FieldMeta, FieldOptions, Type } from '../type/index.js';
|
|
9
|
+
import type { EntityMeta, EntityWhereMeta, FieldMeta, FieldOptions, Type } from '../type/index.js';
|
|
10
10
|
import type { NamingStrategy } from '../type/namingStrategy.js';
|
|
11
11
|
import { SchemaAST } from './schemaAST.js';
|
|
12
12
|
import { type CanonicalType, type ForeignKeyAction } from './types.js';
|
|
@@ -24,6 +24,11 @@ export interface BuildSchemaASTOptions {
|
|
|
24
24
|
namingStrategy?: NamingStrategy;
|
|
25
25
|
/** Default action for foreign key ON DELETE and ON UPDATE clauses */
|
|
26
26
|
defaultForeignKeyAction?: ForeignKeyAction;
|
|
27
|
+
/**
|
|
28
|
+
* The text of SQL an entity declares - a check, a stored computed column, an index expression or
|
|
29
|
+
* predicate - which only a dialect can render. `buildEntityAST` supplies it from the generator.
|
|
30
|
+
*/
|
|
31
|
+
compileDdl?: (sql: EntityWhereMeta<object>, entity: Type<object>) => string;
|
|
27
32
|
}
|
|
28
33
|
/**
|
|
29
34
|
* Build a SchemaAST from entity classes (decorated with `@Entity`, `@Field`, etc.).
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* - Database introspection results (TableSchema[])
|
|
7
7
|
*/
|
|
8
8
|
import { getMeta, soleIdOf } from '../entity/metadata/definition.js';
|
|
9
|
-
import {
|
|
9
|
+
import { indexNameParts, renderIndexColumn } from '../util/ddlExpression.util.js';
|
|
10
10
|
import { isInlinedExpression } from '../util/field.util.js';
|
|
11
11
|
import { isSoleIdField } from '../util/field.util.js';
|
|
12
12
|
import { isAutoIncrement } from '../util/field.util.js';
|
|
@@ -30,6 +30,7 @@ export function buildSchemaAST(entities, options = {}) {
|
|
|
30
30
|
resolveSchema: options.resolveSchema ?? ((m) => m.schema),
|
|
31
31
|
resolveColumnName: options.resolveColumnName ?? ((k, f) => namingStrategy?.columnName(f.name ?? k) ?? f.name ?? k),
|
|
32
32
|
defaultForeignKeyAction: options.defaultForeignKeyAction ?? DEFAULT_FOREIGN_KEY_ACTION,
|
|
33
|
+
compileDdl: options.compileDdl ?? refuseDdl,
|
|
33
34
|
};
|
|
34
35
|
for (const pass of [addTableFromEntity, addRelationshipsFromEntity, addIndexesFromEntity]) {
|
|
35
36
|
for (const entity of entities) {
|
|
@@ -38,6 +39,10 @@ export function buildSchemaAST(entities, options = {}) {
|
|
|
38
39
|
}
|
|
39
40
|
return ctx.ast;
|
|
40
41
|
}
|
|
42
|
+
/** The `compileDdl` of a build given no dialect, which has nothing to render an entity's SQL with. */
|
|
43
|
+
function refuseDdl() {
|
|
44
|
+
throw new TypeError('building the schema of an entity that declares SQL (a check, a stored computed column, an index expression or predicate) needs a dialect to render it: pass `compileDdl`, as `buildEntityAST` does');
|
|
45
|
+
}
|
|
41
46
|
/**
|
|
42
47
|
* Resolve the canonical type for a field, inheriting from the referenced
|
|
43
48
|
* entity's primary key when the field is a foreign-key reference
|
|
@@ -78,7 +83,7 @@ function addTableFromEntity(ctx, meta) {
|
|
|
78
83
|
const tableName = ctx.resolveTableName(meta);
|
|
79
84
|
const table = createTableNode(tableName, ctx.resolveSchema(meta));
|
|
80
85
|
const { columns, primaryKey } = table;
|
|
81
|
-
table.checks?.push(...(meta.checks ?? []));
|
|
86
|
+
table.checks?.push(...(meta.checks ?? []).map(({ name, where }) => ({ name, expression: ctx.compileDdl(where, meta.entity) })));
|
|
82
87
|
// Add columns from fields
|
|
83
88
|
for (const [key, field] of definedEntries(meta.fields)) {
|
|
84
89
|
// An inlined expression has no column; a stored one is a column like any other.
|
|
@@ -98,7 +103,7 @@ function addTableFromEntity(ctx, meta) {
|
|
|
98
103
|
isPrimaryKey,
|
|
99
104
|
isAutoIncrement: isAutoIncrement(field, isSoleKey),
|
|
100
105
|
isUnique: field.unique ?? false,
|
|
101
|
-
generatedAs:
|
|
106
|
+
generatedAs: field.computed && ctx.compileDdl(field.computed, meta.entity),
|
|
102
107
|
comment: field.comment,
|
|
103
108
|
enum: field.enum,
|
|
104
109
|
table,
|
|
@@ -237,27 +242,23 @@ function resolveIncludeColumn(ctx, meta, column) {
|
|
|
237
242
|
function addCompositeIndex(ctx, table, meta, idxMeta) {
|
|
238
243
|
// An entry survives if it is an expression (nothing to resolve) or names a column that exists;
|
|
239
244
|
// an index left with none is dropped, the same as one naming only unknown columns always was.
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
return entry;
|
|
245
|
+
const resolved = idxMeta.columns.flatMap((entry) => {
|
|
246
|
+
if (typeof entry.column !== 'string')
|
|
247
|
+
return [entry];
|
|
244
248
|
const field = meta.fields[entry.column];
|
|
245
249
|
const column = field && ctx.resolveColumnName(entry.column, field);
|
|
246
|
-
return column && table.columns.has(column) ? { ...entry, column } :
|
|
247
|
-
})
|
|
248
|
-
|
|
249
|
-
if (!entries.length)
|
|
250
|
+
return column && table.columns.has(column) ? [{ ...entry, column }] : [];
|
|
251
|
+
});
|
|
252
|
+
if (!resolved.length)
|
|
250
253
|
return;
|
|
251
|
-
// An index over expressions alone has no column names to build a default name from.
|
|
252
|
-
const named = entries.map((entry, at) => (entry.expression ? `expr${at}` : entry.column));
|
|
253
254
|
ctx.ast.addIndex({
|
|
254
|
-
name: idxMeta.name ?? derivedIndexName(table.name,
|
|
255
|
+
name: idxMeta.name ?? derivedIndexName(table.name, indexNameParts(resolved)),
|
|
255
256
|
table,
|
|
256
|
-
entries,
|
|
257
|
+
entries: resolved.map((entry) => renderIndexColumn(entry, (sql) => ctx.compileDdl(sql, meta.entity))),
|
|
257
258
|
include: idxMeta.include?.map((column) => resolveIncludeColumn(ctx, meta, column)),
|
|
258
259
|
unique: idxMeta.unique ?? false,
|
|
259
260
|
type: idxMeta.type,
|
|
260
|
-
where: idxMeta.where,
|
|
261
|
+
where: idxMeta.where && ctx.compileDdl(idxMeta.where, meta.entity),
|
|
261
262
|
distance: idxMeta.distance,
|
|
262
263
|
m: idxMeta.m,
|
|
263
264
|
efConstruction: idxMeta.efConstruction,
|