uql-orm 0.34.0 → 0.34.1
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/dialect/abstractDialect.js +2 -3
- package/dist/dialect/abstractSqlDialect.d.ts +6 -0
- package/dist/dialect/abstractSqlDialect.js +10 -3
- package/dist/migrate/builder/migrationBuilder.js +3 -1
- package/dist/migrate/builder/tableBuilder.js +2 -1
- package/dist/migrate/generator/definitionToNode.js +3 -3
- package/dist/migrate/generator/mongoSchemaGenerator.js +3 -2
- package/dist/migrate/introspection/abstractSqlSchemaIntrospector.d.ts +12 -1
- package/dist/migrate/introspection/abstractSqlSchemaIntrospector.js +16 -2
- package/dist/migrate/introspection/baseSqlIntrospector.d.ts +7 -1
- package/dist/migrate/introspection/baseSqlIntrospector.js +11 -12
- package/dist/migrate/introspection/mongoIntrospector.js +0 -1
- package/dist/migrate/introspection/mysqlIntrospector.d.ts +1 -0
- package/dist/migrate/introspection/mysqlIntrospector.js +8 -6
- package/dist/migrate/introspection/postgresIntrospector.d.ts +1 -0
- package/dist/migrate/introspection/postgresIntrospector.js +6 -5
- package/dist/migrate/migrator.d.ts +10 -1
- package/dist/migrate/migrator.js +28 -6
- package/dist/migrate/schemaGenerator.d.ts +12 -8
- package/dist/migrate/schemaGenerator.js +44 -29
- package/dist/schema/schemaAST.d.ts +8 -1
- package/dist/schema/schemaAST.js +22 -13
- package/dist/schema/schemaASTBuilder.d.ts +4 -2
- package/dist/schema/schemaASTBuilder.js +17 -25
- package/dist/schema/types.d.ts +10 -3
- package/dist/type/migration.d.ts +15 -1
- package/dist/util/sql.util.d.ts +17 -0
- package/dist/util/sql.util.js +23 -0
- package/package.json +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { applyFilters, buildQueryWhereAsMap } from '../util/dialect.util.js';
|
|
2
|
+
import { qualifyName } from '../util/sql.util.js';
|
|
2
3
|
/**
|
|
3
4
|
* The dialect's share of a pool's {@link ExtraOptions}: what changes the SQL rather than the
|
|
4
5
|
* connection. Every pool builds its dialect through this, so a new option lands here instead of in
|
|
@@ -52,9 +53,7 @@ export class AbstractDialect {
|
|
|
52
53
|
* would point at one that does not exist.
|
|
53
54
|
*/
|
|
54
55
|
resolveTableName(meta) {
|
|
55
|
-
|
|
56
|
-
const schema = this.resolveSchema(meta);
|
|
57
|
-
return schema ? `${schema}.${name}` : name;
|
|
56
|
+
return qualifyName(this.resolveTableAlias(meta), this.resolveSchema(meta));
|
|
58
57
|
}
|
|
59
58
|
/**
|
|
60
59
|
* Which schema an entity is read from: its own wins over the pool's default, the way its `name`
|
|
@@ -301,6 +301,12 @@ export declare abstract class AbstractSqlDialect extends IndexSqlDialect impleme
|
|
|
301
301
|
protected getUpsertConflictPathsStr<E>(meta: EntityMeta<E>, conflictPaths: QueryConflictPaths<E>): string;
|
|
302
302
|
delete<E>(ctx: QueryContext, entity: Type<E>, q: QuerySearch<E>, opts?: QueryOptions): void;
|
|
303
303
|
escapeId(val: string | undefined, forbidQualified?: boolean, addDot?: boolean): string;
|
|
304
|
+
/**
|
|
305
|
+
* A name behind its schema, each part escaped on its own rather than as one dotted string taken
|
|
306
|
+
* apart again by {@link escapeId}. Tables and their indexes both use it, since an index lives in
|
|
307
|
+
* the schema of the table it is on.
|
|
308
|
+
*/
|
|
309
|
+
escapeQualifiedId(name: string, schema: string | undefined): string;
|
|
304
310
|
/**
|
|
305
311
|
* Bind one persisted value, classifying its column on the spot. Dialects override
|
|
306
312
|
* {@link appendJsonValue} and {@link appendVectorValue} rather than this, so the chain runs once per
|
|
@@ -195,9 +195,7 @@ export class AbstractSqlDialect extends IndexSqlDialect {
|
|
|
195
195
|
* string taken apart again by {@link escapeId}.
|
|
196
196
|
*/
|
|
197
197
|
escapedTableName(meta) {
|
|
198
|
-
|
|
199
|
-
const schema = this.resolveSchema(meta);
|
|
200
|
-
return schema ? `${this.escapeId(schema, true)}.${alias}` : alias;
|
|
198
|
+
return this.escapeQualifiedId(this.resolveTableAlias(meta), this.resolveSchema(meta));
|
|
201
199
|
}
|
|
202
200
|
/**
|
|
203
201
|
* A FROM or JOIN operand plus the alias to prefix its columns by, aliased only once a schema puts
|
|
@@ -1065,6 +1063,15 @@ export class AbstractSqlDialect extends IndexSqlDialect {
|
|
|
1065
1063
|
escapeId(val, forbidQualified, addDot) {
|
|
1066
1064
|
return escapeSqlId(val, this.escapeIdChar, forbidQualified, addDot);
|
|
1067
1065
|
}
|
|
1066
|
+
/**
|
|
1067
|
+
* A name behind its schema, each part escaped on its own rather than as one dotted string taken
|
|
1068
|
+
* apart again by {@link escapeId}. Tables and their indexes both use it, since an index lives in
|
|
1069
|
+
* the schema of the table it is on.
|
|
1070
|
+
*/
|
|
1071
|
+
escapeQualifiedId(name, schema) {
|
|
1072
|
+
const escaped = this.escapeId(name, true);
|
|
1073
|
+
return schema ? `${this.escapeId(schema, true)}.${escaped}` : escaped;
|
|
1074
|
+
}
|
|
1068
1075
|
/**
|
|
1069
1076
|
* Bind one persisted value, classifying its column on the spot. Dialects override
|
|
1070
1077
|
* {@link appendJsonValue} and {@link appendVectorValue} rather than this, so the chain runs once per
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* - MigrationBuilder: Execute DDL operations (for integration tests/runtime)
|
|
7
7
|
*/
|
|
8
8
|
import { normalizeIndexColumn } from '../../util/index.js';
|
|
9
|
+
import { derivedIndexName } from '../../util/sql.util.js';
|
|
9
10
|
import { createSchemaGenerator } from '../schemaGenerator.js';
|
|
10
11
|
import { splitSqlStatements } from './splitSqlStatements.js';
|
|
11
12
|
import { TableBuilder } from './tableBuilder.js';
|
|
@@ -26,7 +27,8 @@ function createIndexOperation(tableName, columns, options = {}) {
|
|
|
26
27
|
tableName,
|
|
27
28
|
index: {
|
|
28
29
|
...index,
|
|
29
|
-
name: name ??
|
|
30
|
+
name: name ??
|
|
31
|
+
derivedIndexName(tableName, entries.map((entry) => entry.column)),
|
|
30
32
|
entries,
|
|
31
33
|
unique: unique ?? false,
|
|
32
34
|
},
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Fluent API for defining tables in migrations.
|
|
5
5
|
*/
|
|
6
6
|
import { normalizeIndexColumn } from '../../util/index.js';
|
|
7
|
+
import { derivedIndexName } from '../../util/sql.util.js';
|
|
7
8
|
import { ColumnBuilder } from './columnBuilder.js';
|
|
8
9
|
import { t } from './expressions.js';
|
|
9
10
|
/**
|
|
@@ -190,7 +191,7 @@ export class TableBuilder {
|
|
|
190
191
|
// Collect column-level indexes
|
|
191
192
|
for (const col of columns) {
|
|
192
193
|
if (col.index) {
|
|
193
|
-
const indexName = typeof col.index === 'string' ? col.index :
|
|
194
|
+
const indexName = typeof col.index === 'string' ? col.index : derivedIndexName(this._name, [col.name]);
|
|
194
195
|
// Only add if not already in table-level indexes
|
|
195
196
|
if (!this._indexes.some((idx) => idx.name === indexName)) {
|
|
196
197
|
this._indexes.push({
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { derivedForeignKeyName } from '../../util/sql.util.js';
|
|
1
2
|
/**
|
|
2
3
|
* A migration builder's table definition as the AST nodes the generators render from, so a hand-written
|
|
3
4
|
* `createTable` and an entity reach `generateCreateTableFromNode` in the same shape. Free functions and
|
|
@@ -11,7 +12,6 @@ export function tableDefinitionToNode(def) {
|
|
|
11
12
|
columns,
|
|
12
13
|
primaryKey: [], // placeholder
|
|
13
14
|
indexes: [],
|
|
14
|
-
schema: { tables: new Map(), relationships: [], indexes: [] },
|
|
15
15
|
incomingRelations: [],
|
|
16
16
|
outgoingRelations: [],
|
|
17
17
|
comment: def.comment,
|
|
@@ -33,7 +33,7 @@ export function tableDefinitionToNode(def) {
|
|
|
33
33
|
}
|
|
34
34
|
for (const fkDef of def.foreignKeys) {
|
|
35
35
|
const relNode = {
|
|
36
|
-
name: fkDef.name ??
|
|
36
|
+
name: fkDef.name ?? derivedForeignKeyName(def.name, fkDef.columns),
|
|
37
37
|
type: 'ManyToOne', // Builder default
|
|
38
38
|
from: {
|
|
39
39
|
table,
|
|
@@ -64,7 +64,7 @@ export function fullColumnDefinitionToNode(col, tableName) {
|
|
|
64
64
|
referencedBy: [],
|
|
65
65
|
references: col.foreignKey
|
|
66
66
|
? {
|
|
67
|
-
name:
|
|
67
|
+
name: derivedForeignKeyName(tableName, [col.name]),
|
|
68
68
|
type: 'ManyToOne',
|
|
69
69
|
from: { table: { name: tableName }, columns: [] },
|
|
70
70
|
to: {
|
|
@@ -2,6 +2,7 @@ import { AbstractDialect } from '../../dialect/abstractDialect.js';
|
|
|
2
2
|
import { getMeta } from '../../entity/index.js';
|
|
3
3
|
import { mongoDialectFeatures } from '../../mongo/mongoDialect.js';
|
|
4
4
|
import { getKeys } from '../../util/index.js';
|
|
5
|
+
import { derivedIndexName } from '../../util/sql.util.js';
|
|
5
6
|
import { indexNodeToSchema } from './indexNodeToSchema.js';
|
|
6
7
|
import { serializeMongoCommand } from './mongoCommand.js';
|
|
7
8
|
export class MongoSchemaGenerator extends AbstractDialect {
|
|
@@ -39,7 +40,7 @@ export class MongoSchemaGenerator extends AbstractDialect {
|
|
|
39
40
|
const field = meta.fields[key];
|
|
40
41
|
if (field?.index) {
|
|
41
42
|
const columnName = this.resolveColumnName(key, field);
|
|
42
|
-
const indexName = typeof field.index === 'string' ? field.index :
|
|
43
|
+
const indexName = typeof field.index === 'string' ? field.index : derivedIndexName(collectionName, [columnName]);
|
|
43
44
|
indexes.push({
|
|
44
45
|
name: indexName,
|
|
45
46
|
entries: [{ column: columnName }],
|
|
@@ -142,7 +143,7 @@ export class MongoSchemaGenerator extends AbstractDialect {
|
|
|
142
143
|
const field = meta.fields[key];
|
|
143
144
|
if (field?.index) {
|
|
144
145
|
const columnName = this.resolveColumnName(key, field);
|
|
145
|
-
const indexName = typeof field.index === 'string' ? field.index :
|
|
146
|
+
const indexName = typeof field.index === 'string' ? field.index : derivedIndexName(collectionName, [columnName]);
|
|
146
147
|
if (!existingIndexes.has(indexName)) {
|
|
147
148
|
indexesToAdd.push({
|
|
148
149
|
name: indexName,
|
|
@@ -36,7 +36,18 @@ export type TableRowReader = <T extends RawRow>(sql: string, params?: unknown[])
|
|
|
36
36
|
*/
|
|
37
37
|
export declare abstract class AbstractSqlSchemaIntrospector extends BaseSqlIntrospector implements SchemaIntrospector {
|
|
38
38
|
protected readonly pool: QuerierPool;
|
|
39
|
-
constructor(pool: QuerierPool);
|
|
39
|
+
constructor(pool: QuerierPool, schema?: string);
|
|
40
|
+
/**
|
|
41
|
+
* The schema every catalogue query filters on, as SQL: the one that was asked for, or the engine's
|
|
42
|
+
* expression for the connection's default. A literal rather than a bind parameter because these
|
|
43
|
+
* queries are assembled as text and several use it more than once.
|
|
44
|
+
*/
|
|
45
|
+
protected get schemaExpr(): string;
|
|
46
|
+
/**
|
|
47
|
+
* How this engine names the connection's current schema (Postgres) or database (MySQL). Empty on
|
|
48
|
+
* an engine with no schemas, whose catalogue queries never reference one.
|
|
49
|
+
*/
|
|
50
|
+
protected readonly defaultSchemaExpr: string;
|
|
40
51
|
getTableSchema(tableName: string): Promise<TableSchema | undefined>;
|
|
41
52
|
getTableNames(): Promise<string[]>;
|
|
42
53
|
tableExists(tableName: string): Promise<boolean>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isSqlQuerier } from '../../type/index.js';
|
|
2
|
+
import { escapeAnsiSqlLiteral } from '../../util/sqlLiteral.js';
|
|
2
3
|
import { BaseSqlIntrospector } from './baseSqlIntrospector.js';
|
|
3
4
|
/**
|
|
4
5
|
* Abstract base class for SQL schema introspectors.
|
|
@@ -21,10 +22,23 @@ import { BaseSqlIntrospector } from './baseSqlIntrospector.js';
|
|
|
21
22
|
*/
|
|
22
23
|
export class AbstractSqlSchemaIntrospector extends BaseSqlIntrospector {
|
|
23
24
|
pool;
|
|
24
|
-
constructor(pool) {
|
|
25
|
-
super(pool.dialect);
|
|
25
|
+
constructor(pool, schema) {
|
|
26
|
+
super(pool.dialect, schema);
|
|
26
27
|
this.pool = pool;
|
|
27
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* The schema every catalogue query filters on, as SQL: the one that was asked for, or the engine's
|
|
31
|
+
* expression for the connection's default. A literal rather than a bind parameter because these
|
|
32
|
+
* queries are assembled as text and several use it more than once.
|
|
33
|
+
*/
|
|
34
|
+
get schemaExpr() {
|
|
35
|
+
return this.schema === undefined ? this.defaultSchemaExpr : escapeAnsiSqlLiteral(this.schema);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* How this engine names the connection's current schema (Postgres) or database (MySQL). Empty on
|
|
39
|
+
* an engine with no schemas, whose catalogue queries never reference one.
|
|
40
|
+
*/
|
|
41
|
+
defaultSchemaExpr = '';
|
|
28
42
|
async getTableSchema(tableName) {
|
|
29
43
|
return this.withSqlQuerier(async (querier) => {
|
|
30
44
|
const read = createTableRowReader(querier);
|
|
@@ -7,9 +7,15 @@ import type { TableSchema } from '../../type/migration.js';
|
|
|
7
7
|
*/
|
|
8
8
|
export declare abstract class BaseSqlIntrospector {
|
|
9
9
|
protected readonly dialect: AbstractSqlDialect;
|
|
10
|
+
readonly schema?: string | undefined;
|
|
10
11
|
/** Columns and uniqueness only; each introspector opts in to what its catalogue queries report. */
|
|
11
12
|
readonly indexFacets: ReadonlySet<IndexFacet>;
|
|
12
|
-
|
|
13
|
+
/**
|
|
14
|
+
* The one schema these queries read, `undefined` for the connection's own default. Every table
|
|
15
|
+
* reported is stamped with it, so a diff compares like with like: entity and database both say
|
|
16
|
+
* `undefined` for "wherever the connection points", and name a schema only when one was asked for.
|
|
17
|
+
*/
|
|
18
|
+
constructor(dialect: AbstractSqlDialect, schema?: string | undefined);
|
|
13
19
|
protected escapeId(identifier: string): string;
|
|
14
20
|
/**
|
|
15
21
|
* Introspect entire database schema and return SchemaAST.
|
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
import { canonicalColumnType } from '../../schema/canonicalType.js';
|
|
2
|
-
import { SchemaAST } from '../../schema/schemaAST.js';
|
|
2
|
+
import { createTableNode, SchemaAST } from '../../schema/schemaAST.js';
|
|
3
3
|
import { escapeSqlId } from '../../util/index.js';
|
|
4
4
|
/**
|
|
5
5
|
* Base class for SQL introspectors with shared AST building logic.
|
|
6
6
|
*/
|
|
7
7
|
export class BaseSqlIntrospector {
|
|
8
8
|
dialect;
|
|
9
|
+
schema;
|
|
9
10
|
/** Columns and uniqueness only; each introspector opts in to what its catalogue queries report. */
|
|
10
11
|
indexFacets = new Set();
|
|
11
|
-
|
|
12
|
+
/**
|
|
13
|
+
* The one schema these queries read, `undefined` for the connection's own default. Every table
|
|
14
|
+
* reported is stamped with it, so a diff compares like with like: entity and database both say
|
|
15
|
+
* `undefined` for "wherever the connection points", and name a schema only when one was asked for.
|
|
16
|
+
*/
|
|
17
|
+
constructor(dialect, schema) {
|
|
12
18
|
this.dialect = dialect;
|
|
19
|
+
this.schema = schema;
|
|
13
20
|
}
|
|
14
21
|
escapeId(identifier) {
|
|
15
22
|
return escapeSqlId(identifier, this.dialect.escapeIdChar);
|
|
@@ -41,16 +48,8 @@ export class BaseSqlIntrospector {
|
|
|
41
48
|
}
|
|
42
49
|
buildTables(ast, tableNodes, tableSchemas) {
|
|
43
50
|
for (const schema of tableSchemas) {
|
|
44
|
-
const
|
|
45
|
-
const
|
|
46
|
-
name: schema.name,
|
|
47
|
-
columns,
|
|
48
|
-
primaryKey: [],
|
|
49
|
-
indexes: [],
|
|
50
|
-
schema: ast,
|
|
51
|
-
incomingRelations: [],
|
|
52
|
-
outgoingRelations: [],
|
|
53
|
-
};
|
|
51
|
+
const table = createTableNode(schema.name, this.schema);
|
|
52
|
+
const { columns } = table;
|
|
54
53
|
for (const col of schema.columns) {
|
|
55
54
|
const column = {
|
|
56
55
|
name: col.name,
|
|
@@ -5,6 +5,7 @@ import { AbstractSqlSchemaIntrospector, type TableRowReader } from './abstractSq
|
|
|
5
5
|
* Works with both MySQL and MariaDB as they share the same information_schema structure.
|
|
6
6
|
*/
|
|
7
7
|
export declare class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
8
|
+
protected readonly defaultSchemaExpr = "DATABASE()";
|
|
8
9
|
protected getTableNamesQuery(): string;
|
|
9
10
|
protected tableExistsQuery(): string;
|
|
10
11
|
protected parseTableExistsResult(results: {
|
|
@@ -4,11 +4,13 @@ import { AbstractSqlSchemaIntrospector } from './abstractSqlSchemaIntrospector.j
|
|
|
4
4
|
* Works with both MySQL and MariaDB as they share the same information_schema structure.
|
|
5
5
|
*/
|
|
6
6
|
export class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
7
|
+
// A MySQL "schema" is a database, so the connection's own is what `DATABASE()` reports.
|
|
8
|
+
defaultSchemaExpr = 'DATABASE()';
|
|
7
9
|
getTableNamesQuery() {
|
|
8
10
|
return /*sql*/ `
|
|
9
11
|
SELECT TABLE_NAME as table_name
|
|
10
12
|
FROM information_schema.TABLES
|
|
11
|
-
WHERE TABLE_SCHEMA =
|
|
13
|
+
WHERE TABLE_SCHEMA = ${this.schemaExpr}
|
|
12
14
|
AND TABLE_TYPE = 'BASE TABLE'
|
|
13
15
|
ORDER BY TABLE_NAME
|
|
14
16
|
`;
|
|
@@ -17,7 +19,7 @@ export class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
17
19
|
return /*sql*/ `
|
|
18
20
|
SELECT COUNT(*) as count
|
|
19
21
|
FROM information_schema.TABLES
|
|
20
|
-
WHERE TABLE_SCHEMA =
|
|
22
|
+
WHERE TABLE_SCHEMA = ${this.schemaExpr}
|
|
21
23
|
AND TABLE_NAME = ?
|
|
22
24
|
`;
|
|
23
25
|
}
|
|
@@ -43,7 +45,7 @@ export class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
43
45
|
EXTRA as extra,
|
|
44
46
|
COLUMN_COMMENT as column_comment
|
|
45
47
|
FROM information_schema.COLUMNS
|
|
46
|
-
WHERE TABLE_SCHEMA =
|
|
48
|
+
WHERE TABLE_SCHEMA = ${this.schemaExpr}
|
|
47
49
|
AND TABLE_NAME = ?
|
|
48
50
|
ORDER BY ORDINAL_POSITION
|
|
49
51
|
`;
|
|
@@ -55,7 +57,7 @@ export class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
55
57
|
GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX) as columns,
|
|
56
58
|
NOT NON_UNIQUE as is_unique
|
|
57
59
|
FROM information_schema.STATISTICS
|
|
58
|
-
WHERE TABLE_SCHEMA =
|
|
60
|
+
WHERE TABLE_SCHEMA = ${this.schemaExpr}
|
|
59
61
|
AND TABLE_NAME = ?
|
|
60
62
|
AND INDEX_NAME != 'PRIMARY'
|
|
61
63
|
GROUP BY INDEX_NAME, NON_UNIQUE
|
|
@@ -75,7 +77,7 @@ export class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
75
77
|
JOIN information_schema.REFERENTIAL_CONSTRAINTS rc
|
|
76
78
|
ON kcu.CONSTRAINT_NAME = rc.CONSTRAINT_NAME
|
|
77
79
|
AND kcu.TABLE_SCHEMA = rc.CONSTRAINT_SCHEMA
|
|
78
|
-
WHERE kcu.TABLE_SCHEMA =
|
|
80
|
+
WHERE kcu.TABLE_SCHEMA = ${this.schemaExpr}
|
|
79
81
|
AND kcu.TABLE_NAME = ?
|
|
80
82
|
AND kcu.REFERENCED_TABLE_NAME IS NOT NULL
|
|
81
83
|
GROUP BY kcu.CONSTRAINT_NAME, kcu.REFERENCED_TABLE_NAME, rc.DELETE_RULE, rc.UPDATE_RULE
|
|
@@ -86,7 +88,7 @@ export class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
86
88
|
return /*sql*/ `
|
|
87
89
|
SELECT COLUMN_NAME as column_name
|
|
88
90
|
FROM information_schema.KEY_COLUMN_USAGE
|
|
89
|
-
WHERE TABLE_SCHEMA =
|
|
91
|
+
WHERE TABLE_SCHEMA = ${this.schemaExpr}
|
|
90
92
|
AND TABLE_NAME = ?
|
|
91
93
|
AND CONSTRAINT_NAME = 'PRIMARY'
|
|
92
94
|
ORDER BY ORDINAL_POSITION
|
|
@@ -5,6 +5,7 @@ import { AbstractSqlSchemaIntrospector, type TableRowReader } from './abstractSq
|
|
|
5
5
|
* PostgreSQL schema introspector
|
|
6
6
|
*/
|
|
7
7
|
export declare class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
8
|
+
protected readonly defaultSchemaExpr = "current_schema()";
|
|
8
9
|
/**
|
|
9
10
|
* Expressions and predicates are read back too, for `generate:from-db`, but they are text the
|
|
10
11
|
* database reprints in its own words, so they are not comparable and are not claimed here.
|
|
@@ -4,6 +4,7 @@ import { AbstractSqlSchemaIntrospector } from './abstractSqlSchemaIntrospector.j
|
|
|
4
4
|
* PostgreSQL schema introspector
|
|
5
5
|
*/
|
|
6
6
|
export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
7
|
+
defaultSchemaExpr = 'current_schema()';
|
|
7
8
|
/**
|
|
8
9
|
* Expressions and predicates are read back too, for `generate:from-db`, but they are text the
|
|
9
10
|
* database reprints in its own words, so they are not comparable and are not claimed here.
|
|
@@ -19,7 +20,7 @@ export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
19
20
|
return /*sql*/ `
|
|
20
21
|
SELECT table_name
|
|
21
22
|
FROM information_schema.tables
|
|
22
|
-
WHERE table_schema =
|
|
23
|
+
WHERE table_schema = ${this.schemaExpr}
|
|
23
24
|
AND table_type = 'BASE TABLE'
|
|
24
25
|
ORDER BY table_name
|
|
25
26
|
`;
|
|
@@ -28,7 +29,7 @@ export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
28
29
|
return /*sql*/ `
|
|
29
30
|
SELECT EXISTS (
|
|
30
31
|
SELECT FROM information_schema.tables
|
|
31
|
-
WHERE table_schema =
|
|
32
|
+
WHERE table_schema = ${this.schemaExpr}
|
|
32
33
|
AND table_name = $1
|
|
33
34
|
) AS exists
|
|
34
35
|
`;
|
|
@@ -74,7 +75,7 @@ export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
74
75
|
c.ordinal_position
|
|
75
76
|
) AS column_comment
|
|
76
77
|
FROM information_schema.columns c
|
|
77
|
-
WHERE c.table_schema =
|
|
78
|
+
WHERE c.table_schema = ${this.schemaExpr}
|
|
78
79
|
AND c.table_name = $1
|
|
79
80
|
ORDER BY c.ordinal_position
|
|
80
81
|
`;
|
|
@@ -152,7 +153,7 @@ export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
152
153
|
AND rc.constraint_schema = tc.table_schema
|
|
153
154
|
WHERE tc.constraint_type = 'FOREIGN KEY'
|
|
154
155
|
AND tc.table_name = $1
|
|
155
|
-
AND tc.table_schema =
|
|
156
|
+
AND tc.table_schema = ${this.schemaExpr}
|
|
156
157
|
GROUP BY tc.constraint_name, ccu.table_name, rc.delete_rule, rc.update_rule
|
|
157
158
|
ORDER BY tc.constraint_name
|
|
158
159
|
`;
|
|
@@ -166,7 +167,7 @@ export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
166
167
|
AND tc.table_schema = kcu.table_schema
|
|
167
168
|
WHERE tc.constraint_type = 'PRIMARY KEY'
|
|
168
169
|
AND tc.table_name = $1
|
|
169
|
-
AND tc.table_schema =
|
|
170
|
+
AND tc.table_schema = ${this.schemaExpr}
|
|
170
171
|
ORDER BY kcu.ordinal_position
|
|
171
172
|
`;
|
|
172
173
|
}
|
|
@@ -25,7 +25,8 @@ export declare class Migrator {
|
|
|
25
25
|
* Set the schema generator for DDL operations
|
|
26
26
|
*/
|
|
27
27
|
setSchemaGenerator(generator: SchemaGenerator): void;
|
|
28
|
-
|
|
28
|
+
/** `schema` reads one namespace instead of the connection's own; see {@link BaseSqlIntrospector.schema}. */
|
|
29
|
+
protected createIntrospector(schema?: string): SchemaIntrospector | undefined;
|
|
29
30
|
protected createGenerator(): SchemaGenerator | undefined;
|
|
30
31
|
/**
|
|
31
32
|
* Get all discovered migrations from the migrations directory
|
|
@@ -77,6 +78,14 @@ export declare class Migrator {
|
|
|
77
78
|
* Get all schema differences between entities and database
|
|
78
79
|
*/
|
|
79
80
|
getDiffs(): Promise<SchemaDiff[]>;
|
|
81
|
+
/**
|
|
82
|
+
* One AST spanning every schema the entities claim, each table stamped with the schema it was read
|
|
83
|
+
* from. Read per schema rather than all at once, so a table comes back keyed exactly as the entity
|
|
84
|
+
* that wants it spells the key: `undefined` on both sides for the connection's default, a name on
|
|
85
|
+
* both sides otherwise. Ordinarily that is one schema and one pass, as before, and that pass keeps
|
|
86
|
+
* {@link schemaIntrospector} so a caller that replaced it still wins.
|
|
87
|
+
*/
|
|
88
|
+
private introspectClaimedSchemas;
|
|
80
89
|
findEntityForTable(tableName: string): Promise<Type<unknown> | undefined>;
|
|
81
90
|
/**
|
|
82
91
|
* Sync schema directly (for development only - not for production!)
|
package/dist/migrate/migrator.js
CHANGED
|
@@ -2,7 +2,7 @@ import { mkdir, readdir, writeFile } from 'node:fs/promises';
|
|
|
2
2
|
import { basename, extname, join } from 'node:path';
|
|
3
3
|
import { pathToFileURL } from 'node:url';
|
|
4
4
|
import { getEntities, getMeta } from '../entity/index.js';
|
|
5
|
-
import { introspectSchema } from '../schema/index.js';
|
|
5
|
+
import { introspectSchema, SchemaAST } from '../schema/index.js';
|
|
6
6
|
import { isKnownMigratorDialect, isSqlQuerier } from '../type/index.js';
|
|
7
7
|
import { LoggerWrapper } from '../util/index.js';
|
|
8
8
|
import { withQuerierForMigrations, withSqlQuerierForMigrations } from './acquireQuerierForMigrations.js';
|
|
@@ -71,19 +71,20 @@ export class Migrator {
|
|
|
71
71
|
setSchemaGenerator(generator) {
|
|
72
72
|
this.schemaGenerator = generator;
|
|
73
73
|
}
|
|
74
|
-
|
|
74
|
+
/** `schema` reads one namespace instead of the connection's own; see {@link BaseSqlIntrospector.schema}. */
|
|
75
|
+
createIntrospector(schema) {
|
|
75
76
|
const d = this.dialectName;
|
|
76
77
|
if (!isKnownMigratorDialect(d)) {
|
|
77
78
|
return undefined;
|
|
78
79
|
}
|
|
79
80
|
switch (d) {
|
|
80
81
|
case 'postgres':
|
|
81
|
-
return new PostgresSchemaIntrospector(this.pool);
|
|
82
|
+
return new PostgresSchemaIntrospector(this.pool, schema);
|
|
82
83
|
case 'cockroachdb':
|
|
83
|
-
return new CockroachSchemaIntrospector(this.pool);
|
|
84
|
+
return new CockroachSchemaIntrospector(this.pool, schema);
|
|
84
85
|
case 'mysql':
|
|
85
86
|
case 'mariadb':
|
|
86
|
-
return new MysqlSchemaIntrospector(this.pool);
|
|
87
|
+
return new MysqlSchemaIntrospector(this.pool, schema);
|
|
87
88
|
case 'sqlite':
|
|
88
89
|
return new SqliteSchemaIntrospector(this.pool);
|
|
89
90
|
case 'mongodb':
|
|
@@ -279,7 +280,7 @@ export class Migrator {
|
|
|
279
280
|
if (!this.schemaGenerator || !this.schemaIntrospector) {
|
|
280
281
|
throw new TypeError('Schema generator and introspector must be set');
|
|
281
282
|
}
|
|
282
|
-
const ast = await
|
|
283
|
+
const ast = await this.introspectClaimedSchemas();
|
|
283
284
|
const diffs = [];
|
|
284
285
|
for (const entity of this.entities) {
|
|
285
286
|
const meta = getMeta(entity);
|
|
@@ -292,6 +293,27 @@ export class Migrator {
|
|
|
292
293
|
}
|
|
293
294
|
return diffs;
|
|
294
295
|
}
|
|
296
|
+
/**
|
|
297
|
+
* One AST spanning every schema the entities claim, each table stamped with the schema it was read
|
|
298
|
+
* from. Read per schema rather than all at once, so a table comes back keyed exactly as the entity
|
|
299
|
+
* that wants it spells the key: `undefined` on both sides for the connection's default, a name on
|
|
300
|
+
* both sides otherwise. Ordinarily that is one schema and one pass, as before, and that pass keeps
|
|
301
|
+
* {@link schemaIntrospector} so a caller that replaced it still wins.
|
|
302
|
+
*/
|
|
303
|
+
async introspectClaimedSchemas() {
|
|
304
|
+
const claimed = new Set(this.entities.map((entity) => this.pool.dialect.resolveSchema(getMeta(entity))));
|
|
305
|
+
const merged = new SchemaAST();
|
|
306
|
+
for (const schema of claimed) {
|
|
307
|
+
const introspector = schema === undefined ? this.schemaIntrospector : this.createIntrospector(schema);
|
|
308
|
+
if (!introspector) {
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
for (const table of (await introspectSchema(introspector)).getTables()) {
|
|
312
|
+
merged.addTable(table);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return merged;
|
|
316
|
+
}
|
|
295
317
|
async findEntityForTable(tableName) {
|
|
296
318
|
await this.ensureSchemaGenerator();
|
|
297
319
|
if (!this.schemaGenerator) {
|
|
@@ -14,10 +14,10 @@ export declare class SqlSchemaGenerator implements SqlDdlGenerator {
|
|
|
14
14
|
get namingStrategy(): NamingStrategy | undefined;
|
|
15
15
|
get features(): DialectFeatures;
|
|
16
16
|
resolveTableName<E>(meta: EntityMeta<E>): string;
|
|
17
|
+
resolveTableAlias<E>(meta: EntityMeta<E>): string;
|
|
18
|
+
resolveSchema<E>(meta: EntityMeta<E>): string | undefined;
|
|
17
19
|
resolveColumnName(key: string, field: FieldOptions): string;
|
|
18
|
-
/**
|
|
19
|
-
* Escape an identifier (table name, column name, etc.)
|
|
20
|
-
*/
|
|
20
|
+
/** Escape an identifier (table name, column name, etc.) */
|
|
21
21
|
protected escapeId(identifier: string): string;
|
|
22
22
|
/**
|
|
23
23
|
* Primary key type for auto-increment integer IDs
|
|
@@ -39,9 +39,9 @@ export declare class SqlSchemaGenerator implements SqlDdlGenerator {
|
|
|
39
39
|
*/
|
|
40
40
|
generateCreateSchema(entities: readonly Type<unknown>[], options?: CreateSchemaOptions): string[];
|
|
41
41
|
/**
|
|
42
|
-
* One statement per distinct schema the
|
|
43
|
-
*
|
|
44
|
-
*
|
|
42
|
+
* One statement per distinct schema the tables being created live in, in first-seen order. Only
|
|
43
|
+
* the tables actually being created, so a narrowed `only` does not declare namespaces it is not
|
|
44
|
+
* about to fill. Empty on an engine without schemas, whose tables are never qualified.
|
|
45
45
|
*/
|
|
46
46
|
private generateCreateSchemas;
|
|
47
47
|
generateDropSchema(entities: readonly Type<unknown>[], options?: DropSchemaOptions): string[];
|
|
@@ -57,7 +57,11 @@ export declare class SqlSchemaGenerator implements SqlDdlGenerator {
|
|
|
57
57
|
generateCreateIndex(tableName: string, index: IndexSchema, options?: {
|
|
58
58
|
ifNotExists?: boolean;
|
|
59
59
|
}): string;
|
|
60
|
-
|
|
60
|
+
/**
|
|
61
|
+
* `schema` is the table's, because that is where its indexes live. MySQL takes it from the table
|
|
62
|
+
* operand instead, which is already qualified.
|
|
63
|
+
*/
|
|
64
|
+
generateDropIndex(tableName: string, indexName: string, schema?: string): string;
|
|
61
65
|
/**
|
|
62
66
|
* Generate a column definition from a {@link ColumnSchema}, whose type is the engine's own spelling
|
|
63
67
|
* and may already carry its precision (or even `PRIMARY KEY`, for a serial).
|
|
@@ -162,7 +166,7 @@ export declare class SqlSchemaGenerator implements SqlDdlGenerator {
|
|
|
162
166
|
* other way and the table is created under one name and compared under another, which reports every
|
|
163
167
|
* table of a project using a naming strategy as both missing and unexpected.
|
|
164
168
|
*/
|
|
165
|
-
export declare function buildEntityAST(generator: Pick<SchemaGenerator, '
|
|
169
|
+
export declare function buildEntityAST(generator: Pick<SchemaGenerator, 'resolveTableAlias' | 'resolveSchema' | 'resolveColumnName'>, entities: readonly Type<unknown>[], defaultForeignKeyAction?: ForeignKeyAction): SchemaAST;
|
|
166
170
|
/**
|
|
167
171
|
* Synchronous factory for SQL schema generators only.
|
|
168
172
|
* For MongoDB, use `createSchemaGeneratorAsync` from `./schemaGeneratorAsync.js` so the optional `mongodb` peer is not loaded at import time.
|
|
@@ -2,7 +2,8 @@ import { AbstractSqlDialect } from '../dialect/index.js';
|
|
|
2
2
|
import { getMeta } from '../entity/index.js';
|
|
3
3
|
import { areTypesEqual, canonicalToSql, fieldOptionsToCanonical, isVectorCategory, sqlToCanonical, } from '../schema/canonicalType.js';
|
|
4
4
|
import { buildSchemaAST } from '../schema/schemaASTBuilder.js';
|
|
5
|
-
import {
|
|
5
|
+
import { getKeys, isAutoIncrement, qualifyName } from '../util/index.js';
|
|
6
|
+
import { derivedForeignKeyName } from '../util/sql.util.js';
|
|
6
7
|
import { formatDefaultValue } from './builder/expressions.js';
|
|
7
8
|
import { fullColumnDefinitionToNode, tableDefinitionToNode } from './generator/definitionToNode.js';
|
|
8
9
|
import { indexNodeToSchema } from './generator/indexNodeToSchema.js';
|
|
@@ -26,14 +27,18 @@ export class SqlSchemaGenerator {
|
|
|
26
27
|
resolveTableName(meta) {
|
|
27
28
|
return this.dialect.resolveTableName(meta);
|
|
28
29
|
}
|
|
30
|
+
resolveTableAlias(meta) {
|
|
31
|
+
return this.dialect.resolveTableAlias(meta);
|
|
32
|
+
}
|
|
33
|
+
resolveSchema(meta) {
|
|
34
|
+
return this.dialect.resolveSchema(meta);
|
|
35
|
+
}
|
|
29
36
|
resolveColumnName(key, field) {
|
|
30
37
|
return this.dialect.resolveColumnName(key, field);
|
|
31
38
|
}
|
|
32
|
-
/**
|
|
33
|
-
* Escape an identifier (table name, column name, etc.)
|
|
34
|
-
*/
|
|
39
|
+
/** Escape an identifier (table name, column name, etc.) */
|
|
35
40
|
escapeId(identifier) {
|
|
36
|
-
return
|
|
41
|
+
return this.dialect.escapeId(identifier);
|
|
37
42
|
}
|
|
38
43
|
/**
|
|
39
44
|
* Primary key type for auto-increment integer IDs
|
|
@@ -67,15 +72,15 @@ export class SqlSchemaGenerator {
|
|
|
67
72
|
const inline = withForeignKeys && !this.features.foreignKeyAlter;
|
|
68
73
|
// Namespaces first: a qualified `CREATE TABLE` fails against a schema nobody created, and the
|
|
69
74
|
// schema is the one part of the layout a migration cannot infer from the table it is making.
|
|
70
|
-
const statements = this.generateCreateSchemas(
|
|
75
|
+
const statements = this.generateCreateSchemas(tables);
|
|
71
76
|
statements.push(...tables.flatMap((table) => this.generateCreateTableFromNode(inline ? table : { ...table, outgoingRelations: [] }, options)));
|
|
72
77
|
if (withForeignKeys && !inline) {
|
|
73
78
|
for (const table of tables) {
|
|
74
79
|
for (const rel of table.outgoingRelations) {
|
|
75
|
-
statements.push(this.generateAddForeignKeySql(table.name, {
|
|
80
|
+
statements.push(this.generateAddForeignKeySql(qualifyName(table.name, table.schema), {
|
|
76
81
|
name: rel.name,
|
|
77
82
|
columns: rel.from.columns.map((c) => c.name),
|
|
78
|
-
referencesTable: rel.to.table.name,
|
|
83
|
+
referencesTable: qualifyName(rel.to.table.name, rel.to.table.schema),
|
|
79
84
|
referencesColumns: rel.to.columns.map((c) => c.name),
|
|
80
85
|
onDelete: rel.onDelete ?? this.defaultForeignKeyAction,
|
|
81
86
|
onUpdate: rel.onUpdate ?? this.defaultForeignKeyAction,
|
|
@@ -86,18 +91,16 @@ export class SqlSchemaGenerator {
|
|
|
86
91
|
return statements;
|
|
87
92
|
}
|
|
88
93
|
/**
|
|
89
|
-
* One statement per distinct schema the
|
|
90
|
-
*
|
|
91
|
-
*
|
|
94
|
+
* One statement per distinct schema the tables being created live in, in first-seen order. Only
|
|
95
|
+
* the tables actually being created, so a narrowed `only` does not declare namespaces it is not
|
|
96
|
+
* about to fill. Empty on an engine without schemas, whose tables are never qualified.
|
|
92
97
|
*/
|
|
93
|
-
generateCreateSchemas(
|
|
94
|
-
const named =
|
|
95
|
-
.map((entity) => this.dialect.resolveSchema(getMeta(entity)))
|
|
96
|
-
.filter((it) => it !== undefined);
|
|
98
|
+
generateCreateSchemas(tables) {
|
|
99
|
+
const named = tables.map((table) => table.schema).filter((it) => it !== undefined);
|
|
97
100
|
return [...new Set(named)].map((schema) => this.dialect.createSchemaSql(schema));
|
|
98
101
|
}
|
|
99
102
|
generateDropSchema(entities, options = {}) {
|
|
100
|
-
return this.orderedTables(entities, 'drop').map((table) => this.generateDropTable(table.name, options));
|
|
103
|
+
return this.orderedTables(entities, 'drop').map((table) => this.generateDropTable(qualifyName(table.name, table.schema), options));
|
|
101
104
|
}
|
|
102
105
|
/**
|
|
103
106
|
* The tables of `entities` in dependency order, optionally narrowed to `only`. The AST always spans
|
|
@@ -111,7 +114,7 @@ export class SqlSchemaGenerator {
|
|
|
111
114
|
return tables;
|
|
112
115
|
}
|
|
113
116
|
const wanted = new Set(only);
|
|
114
|
-
return tables.filter((table) => wanted.has(table.name));
|
|
117
|
+
return tables.filter((table) => wanted.has(qualifyName(table.name, table.schema)));
|
|
115
118
|
}
|
|
116
119
|
generateDropTable(tableName, options = {}) {
|
|
117
120
|
const ifExists = options.ifExists ? 'IF EXISTS ' : '';
|
|
@@ -151,7 +154,7 @@ export class SqlSchemaGenerator {
|
|
|
151
154
|
// Drop indexes
|
|
152
155
|
if (diff.indexesToDrop?.length) {
|
|
153
156
|
for (const indexName of diff.indexesToDrop) {
|
|
154
|
-
statements.push(this.generateDropIndex(diff.tableName, indexName));
|
|
157
|
+
statements.push(this.generateDropIndex(diff.tableName, indexName, diff.schema));
|
|
155
158
|
}
|
|
156
159
|
}
|
|
157
160
|
return statements;
|
|
@@ -176,7 +179,7 @@ export class SqlSchemaGenerator {
|
|
|
176
179
|
// Reverse index additions by dropping them
|
|
177
180
|
if (diff.indexesToAdd?.length) {
|
|
178
181
|
for (const index of diff.indexesToAdd) {
|
|
179
|
-
statements.push(this.generateDropIndex(diff.tableName, index.name));
|
|
182
|
+
statements.push(this.generateDropIndex(diff.tableName, index.name, diff.schema));
|
|
180
183
|
}
|
|
181
184
|
}
|
|
182
185
|
if (diff.columnsToDrop?.length || diff.indexesToDrop?.length) {
|
|
@@ -187,11 +190,15 @@ export class SqlSchemaGenerator {
|
|
|
187
190
|
generateCreateIndex(tableName, index, options = {}) {
|
|
188
191
|
return this.dialect.getCreateIndexStatement(tableName, index, options);
|
|
189
192
|
}
|
|
190
|
-
|
|
193
|
+
/**
|
|
194
|
+
* `schema` is the table's, because that is where its indexes live. MySQL takes it from the table
|
|
195
|
+
* operand instead, which is already qualified.
|
|
196
|
+
*/
|
|
197
|
+
generateDropIndex(tableName, indexName, schema) {
|
|
191
198
|
if (this.dialect.dropIndexSyntax === 'on-table') {
|
|
192
199
|
return `DROP INDEX ${this.escapeId(indexName)} ON ${this.escapeId(tableName)};`;
|
|
193
200
|
}
|
|
194
|
-
return `DROP INDEX IF EXISTS ${this.
|
|
201
|
+
return `DROP INDEX IF EXISTS ${this.dialect.escapeQualifiedId(indexName, schema)};`;
|
|
195
202
|
}
|
|
196
203
|
/**
|
|
197
204
|
* Generate a column definition from a {@link ColumnSchema}, whose type is the engine's own spelling
|
|
@@ -316,7 +323,8 @@ export class SqlSchemaGenerator {
|
|
|
316
323
|
const meta = getMeta(entity);
|
|
317
324
|
if (!currentTable) {
|
|
318
325
|
return {
|
|
319
|
-
tableName: this.
|
|
326
|
+
tableName: this.resolveTableName(meta),
|
|
327
|
+
schema: this.resolveSchema(meta),
|
|
320
328
|
type: 'create',
|
|
321
329
|
};
|
|
322
330
|
}
|
|
@@ -354,7 +362,8 @@ export class SqlSchemaGenerator {
|
|
|
354
362
|
return undefined;
|
|
355
363
|
}
|
|
356
364
|
return {
|
|
357
|
-
tableName: this.
|
|
365
|
+
tableName: this.resolveTableName(meta),
|
|
366
|
+
schema: this.resolveSchema(meta),
|
|
358
367
|
type: 'alter',
|
|
359
368
|
columnsToAdd: columnsToAdd.length > 0 ? columnsToAdd : undefined,
|
|
360
369
|
columnsToAlter: columnsToAlter.length > 0 ? columnsToAlter : undefined,
|
|
@@ -371,7 +380,9 @@ export class SqlSchemaGenerator {
|
|
|
371
380
|
* created deliberately outside the ORM, and dropping it is a decision for a reviewed migration.
|
|
372
381
|
*/
|
|
373
382
|
missingIndexes(entity, currentTable) {
|
|
374
|
-
|
|
383
|
+
// Keyed by the qualified name, so a table in a schema finds itself rather than reporting that
|
|
384
|
+
// the entity declares no indexes at all.
|
|
385
|
+
const desired = buildEntityAST(this, [entity]).getTable(qualifyName(currentTable.name, currentTable.schema))?.indexes ?? [];
|
|
375
386
|
const present = new Set(currentTable.indexes.map((index) => index.name));
|
|
376
387
|
return desired
|
|
377
388
|
.filter((index) => !present.has(index.name) && !this.isInlineVectorIndex(index))
|
|
@@ -491,12 +502,12 @@ export class SqlSchemaGenerator {
|
|
|
491
502
|
const fromCols = rel.from.columns.map((c) => this.escapeId(c.name)).join(', ');
|
|
492
503
|
const toCols = rel.to.columns.map((c) => this.escapeId(c.name)).join(', ');
|
|
493
504
|
const constraintName = rel.name ? `CONSTRAINT ${this.escapeId(rel.name)} ` : '';
|
|
494
|
-
constraints.push(`${constraintName}FOREIGN KEY (${fromCols}) REFERENCES ${this.
|
|
505
|
+
constraints.push(`${constraintName}FOREIGN KEY (${fromCols}) REFERENCES ${this.dialect.escapeQualifiedId(rel.to.table.name, rel.to.table.schema)} (${toCols})` +
|
|
495
506
|
` ON DELETE ${rel.onDelete ?? this.defaultForeignKeyAction} ON UPDATE ${rel.onUpdate ?? this.defaultForeignKeyAction}`);
|
|
496
507
|
}
|
|
497
508
|
}
|
|
498
509
|
const ifNotExists = options.ifNotExists && this.features.ifNotExists ? 'IF NOT EXISTS ' : '';
|
|
499
|
-
let createSql = `CREATE TABLE ${ifNotExists}${this.
|
|
510
|
+
let createSql = `CREATE TABLE ${ifNotExists}${this.dialect.escapeQualifiedId(table.name, table.schema)} (\n`;
|
|
500
511
|
createSql += columns.map((col) => ` ${col}`).join(',\n');
|
|
501
512
|
if (constraints.length > 0) {
|
|
502
513
|
createSql += ',\n';
|
|
@@ -542,7 +553,8 @@ export class SqlSchemaGenerator {
|
|
|
542
553
|
* Delegates to `generateCreateIndex` for unified SQL assembly.
|
|
543
554
|
*/
|
|
544
555
|
generateCreateIndexFromNode(index, options = { ifNotExists: false }) {
|
|
545
|
-
|
|
556
|
+
// The index's own name stays unqualified: it is created in the schema of the table it is on.
|
|
557
|
+
return this.generateCreateIndex(qualifyName(index.table.name, index.table.schema), indexNodeToSchema(index), options);
|
|
546
558
|
}
|
|
547
559
|
generateCreateTableFromDefinition(table, options = {}) {
|
|
548
560
|
const tableNode = tableDefinitionToNode(table);
|
|
@@ -573,7 +585,7 @@ export class SqlSchemaGenerator {
|
|
|
573
585
|
const refCols = foreignKey.referencesColumns.map((c) => this.escapeId(c)).join(', ');
|
|
574
586
|
const constraintName = foreignKey.name
|
|
575
587
|
? this.escapeId(foreignKey.name)
|
|
576
|
-
: this.escapeId(
|
|
588
|
+
: this.escapeId(derivedForeignKeyName(tableName, foreignKey.columns));
|
|
577
589
|
if (!this.features.foreignKeyAlter) {
|
|
578
590
|
throw new TypeError(`Dialect ${this.dialect} does not support adding foreign keys to existing tables`);
|
|
579
591
|
}
|
|
@@ -595,7 +607,10 @@ export class SqlSchemaGenerator {
|
|
|
595
607
|
*/
|
|
596
608
|
export function buildEntityAST(generator, entities, defaultForeignKeyAction) {
|
|
597
609
|
return buildSchemaAST(entities, {
|
|
598
|
-
resolveTableName
|
|
610
|
+
// The alias, not `resolveTableName`: a node holds its schema separately, so that a name derived
|
|
611
|
+
// from it stays a single identifier.
|
|
612
|
+
resolveTableName: (meta) => generator.resolveTableAlias(meta),
|
|
613
|
+
resolveSchema: (meta) => generator.resolveSchema(meta),
|
|
599
614
|
resolveColumnName: (key, field) => generator.resolveColumnName(key, field),
|
|
600
615
|
defaultForeignKeyAction,
|
|
601
616
|
});
|
|
@@ -5,6 +5,12 @@
|
|
|
5
5
|
* Provides graph operations like navigation, validation, and topological sorting.
|
|
6
6
|
*/
|
|
7
7
|
import type { ColumnNode, IndexNode, SchemaAST as ISchemaAST, RelationshipNode, RelationshipType, TableNode, ValidationError } from './types.js';
|
|
8
|
+
/**
|
|
9
|
+
* A table node with its collections empty, ready to be filled. Six places build one, and the fields
|
|
10
|
+
* that are pure boilerplate are exactly the ones a new field gets forgotten in: {@link TableNode.schema}
|
|
11
|
+
* was added and `SchemaAST.clone` kept rebuilding nodes without it.
|
|
12
|
+
*/
|
|
13
|
+
export declare function createTableNode(name: string, schema?: string, comment?: string): TableNode;
|
|
8
14
|
/**
|
|
9
15
|
* Schema AST - A graph representation of a database schema.
|
|
10
16
|
*
|
|
@@ -20,7 +26,8 @@ export declare class SchemaAST implements ISchemaAST {
|
|
|
20
26
|
readonly relationships: RelationshipNode[];
|
|
21
27
|
readonly indexes: IndexNode[];
|
|
22
28
|
/**
|
|
23
|
-
* Get a table by name
|
|
29
|
+
* Get a table by the name it is keyed under: schema-qualified where it has one, so two tables of
|
|
30
|
+
* the same name in different schemas stay distinct. Build the key with `qualifyName`.
|
|
24
31
|
*/
|
|
25
32
|
getTable(name: string): TableNode | undefined;
|
|
26
33
|
/**
|
package/dist/schema/schemaAST.js
CHANGED
|
@@ -4,6 +4,24 @@
|
|
|
4
4
|
* The main class for working with schema graphs.
|
|
5
5
|
* Provides graph operations like navigation, validation, and topological sorting.
|
|
6
6
|
*/
|
|
7
|
+
import { qualifyName } from '../util/sql.util.js';
|
|
8
|
+
/**
|
|
9
|
+
* A table node with its collections empty, ready to be filled. Six places build one, and the fields
|
|
10
|
+
* that are pure boilerplate are exactly the ones a new field gets forgotten in: {@link TableNode.schema}
|
|
11
|
+
* was added and `SchemaAST.clone` kept rebuilding nodes without it.
|
|
12
|
+
*/
|
|
13
|
+
export function createTableNode(name, schema, comment) {
|
|
14
|
+
return {
|
|
15
|
+
name,
|
|
16
|
+
schema,
|
|
17
|
+
comment,
|
|
18
|
+
columns: new Map(),
|
|
19
|
+
primaryKey: [],
|
|
20
|
+
indexes: [],
|
|
21
|
+
incomingRelations: [],
|
|
22
|
+
outgoingRelations: [],
|
|
23
|
+
};
|
|
24
|
+
}
|
|
7
25
|
/**
|
|
8
26
|
* Schema AST - A graph representation of a database schema.
|
|
9
27
|
*
|
|
@@ -19,7 +37,8 @@ export class SchemaAST {
|
|
|
19
37
|
relationships = [];
|
|
20
38
|
indexes = [];
|
|
21
39
|
/**
|
|
22
|
-
* Get a table by name
|
|
40
|
+
* Get a table by the name it is keyed under: schema-qualified where it has one, so two tables of
|
|
41
|
+
* the same name in different schemas stay distinct. Build the key with `qualifyName`.
|
|
23
42
|
*/
|
|
24
43
|
getTable(name) {
|
|
25
44
|
return this.tables.get(name);
|
|
@@ -28,8 +47,7 @@ export class SchemaAST {
|
|
|
28
47
|
* Add a table to the schema.
|
|
29
48
|
*/
|
|
30
49
|
addTable(table) {
|
|
31
|
-
table.schema
|
|
32
|
-
this.tables.set(table.name, table);
|
|
50
|
+
this.tables.set(qualifyName(table.name, table.schema), table);
|
|
33
51
|
}
|
|
34
52
|
/**
|
|
35
53
|
* Remove a table from the schema.
|
|
@@ -353,16 +371,7 @@ export class SchemaAST {
|
|
|
353
371
|
// back to it on creation - `columns` and `primaryKey` are readonly properties holding mutable
|
|
354
372
|
// containers, so they are filled in afterwards without reassigning anything.
|
|
355
373
|
for (const [name, table] of this.tables) {
|
|
356
|
-
const clonedTable =
|
|
357
|
-
name,
|
|
358
|
-
columns: new Map(),
|
|
359
|
-
primaryKey: [],
|
|
360
|
-
indexes: [],
|
|
361
|
-
comment: table.comment,
|
|
362
|
-
schema: clone,
|
|
363
|
-
incomingRelations: [],
|
|
364
|
-
outgoingRelations: [],
|
|
365
|
-
};
|
|
374
|
+
const clonedTable = createTableNode(table.name, table.schema, table.comment);
|
|
366
375
|
for (const [colName, col] of table.columns) {
|
|
367
376
|
clonedTable.columns.set(colName, { ...col, table: clonedTable, referencedBy: [], references: undefined });
|
|
368
377
|
}
|
|
@@ -8,13 +8,15 @@
|
|
|
8
8
|
import type { EntityMeta, FieldOptions, Type } from '../type/index.js';
|
|
9
9
|
import type { NamingStrategy } from '../type/namingStrategy.js';
|
|
10
10
|
import { SchemaAST } from './schemaAST.js';
|
|
11
|
-
import type
|
|
11
|
+
import { type ForeignKeyAction } from './types.js';
|
|
12
12
|
/**
|
|
13
13
|
* Options for building SchemaAST from entities.
|
|
14
14
|
*/
|
|
15
15
|
export interface BuildSchemaASTOptions {
|
|
16
|
-
/** Custom table name
|
|
16
|
+
/** Custom resolver for a table's own name, unqualified. */
|
|
17
17
|
resolveTableName?: (meta: EntityMeta<unknown>) => string;
|
|
18
|
+
/** Custom resolver for the schema a table lives in; `undefined` leaves it unqualified. */
|
|
19
|
+
resolveSchema?: (meta: EntityMeta<unknown>) => string | undefined;
|
|
18
20
|
/** Custom column name resolver */
|
|
19
21
|
resolveColumnName?: (key: string, field: FieldOptions) => string;
|
|
20
22
|
/** Naming strategy to use */
|
|
@@ -6,9 +6,10 @@
|
|
|
6
6
|
* - Database introspection results (TableSchema[])
|
|
7
7
|
*/
|
|
8
8
|
import { getMeta } from '../entity/metadata/definition.js';
|
|
9
|
+
import { derivedForeignKeyName, derivedIndexName, qualifyName } from '../util/sql.util.js';
|
|
9
10
|
import { fieldOptionsToCanonical } from './canonicalType.js';
|
|
10
|
-
import { SchemaAST } from './schemaAST.js';
|
|
11
|
-
import { DEFAULT_FOREIGN_KEY_ACTION } from './types.js';
|
|
11
|
+
import { createTableNode, SchemaAST } from './schemaAST.js';
|
|
12
|
+
import { DEFAULT_FOREIGN_KEY_ACTION, } from './types.js';
|
|
12
13
|
/**
|
|
13
14
|
* Build a SchemaAST from entity classes (decorated with `@Entity`, `@Field`, etc.).
|
|
14
15
|
*
|
|
@@ -21,6 +22,7 @@ export function buildSchemaAST(entities, options = {}) {
|
|
|
21
22
|
ast: new SchemaAST(),
|
|
22
23
|
resolveTableName: options.resolveTableName ??
|
|
23
24
|
((m) => namingStrategy?.tableName(m.name ?? m.entity.name) ?? m.name ?? m.entity.name),
|
|
25
|
+
resolveSchema: options.resolveSchema ?? ((m) => m.schema),
|
|
24
26
|
resolveColumnName: options.resolveColumnName ?? ((k, f) => namingStrategy?.columnName(f.name ?? k) ?? f.name ?? k),
|
|
25
27
|
defaultForeignKeyAction: options.defaultForeignKeyAction ?? DEFAULT_FOREIGN_KEY_ACTION,
|
|
26
28
|
};
|
|
@@ -67,18 +69,8 @@ function resolveColumnCanonicalType(field, seen = new Set()) {
|
|
|
67
69
|
*/
|
|
68
70
|
function addTableFromEntity(ctx, meta) {
|
|
69
71
|
const tableName = ctx.resolveTableName(meta);
|
|
70
|
-
const
|
|
71
|
-
const primaryKey =
|
|
72
|
-
// Create placeholder table (will be fully initialized below)
|
|
73
|
-
const table = {
|
|
74
|
-
name: tableName,
|
|
75
|
-
columns,
|
|
76
|
-
primaryKey,
|
|
77
|
-
indexes: [],
|
|
78
|
-
schema: ctx.ast,
|
|
79
|
-
incomingRelations: [],
|
|
80
|
-
outgoingRelations: [],
|
|
81
|
-
};
|
|
72
|
+
const table = createTableNode(tableName, ctx.resolveSchema(meta));
|
|
73
|
+
const { columns, primaryKey } = table;
|
|
82
74
|
// Add columns from fields
|
|
83
75
|
const fields = meta.fields;
|
|
84
76
|
for (const key of Object.keys(fields)) {
|
|
@@ -113,12 +105,15 @@ function addTableFromEntity(ctx, meta) {
|
|
|
113
105
|
}
|
|
114
106
|
ctx.ast.addTable(table);
|
|
115
107
|
}
|
|
108
|
+
/** The node an entity maps to, found under the key {@link SchemaAST} stores it by. */
|
|
109
|
+
function tableOf(ctx, meta) {
|
|
110
|
+
return ctx.ast.getTable(qualifyName(ctx.resolveTableName(meta), ctx.resolveSchema(meta)));
|
|
111
|
+
}
|
|
116
112
|
/**
|
|
117
113
|
* Add relationships from entity relation decorators.
|
|
118
114
|
*/
|
|
119
115
|
function addRelationshipsFromEntity(ctx, meta) {
|
|
120
|
-
const
|
|
121
|
-
const table = ctx.ast.getTable(tableName);
|
|
116
|
+
const table = tableOf(ctx, meta);
|
|
122
117
|
if (!table)
|
|
123
118
|
return;
|
|
124
119
|
const relations = meta.relations;
|
|
@@ -126,10 +121,8 @@ function addRelationshipsFromEntity(ctx, meta) {
|
|
|
126
121
|
const relation = relations[key];
|
|
127
122
|
if (!relation)
|
|
128
123
|
continue;
|
|
129
|
-
const
|
|
130
|
-
const
|
|
131
|
-
const relatedTableName = ctx.resolveTableName(relatedMeta);
|
|
132
|
-
const relatedTable = ctx.ast.getTable(relatedTableName);
|
|
124
|
+
const relatedMeta = getMeta(relation.entity());
|
|
125
|
+
const relatedTable = tableOf(ctx, relatedMeta);
|
|
133
126
|
if (!relatedTable)
|
|
134
127
|
continue;
|
|
135
128
|
// Only the owning side gets the FK. `mappedBy` marks the inverse side of a one-to-one, whose
|
|
@@ -152,7 +145,7 @@ function addRelationshipsFromEntity(ctx, meta) {
|
|
|
152
145
|
const foreignColumn = relatedTable.columns.get(foreignColName);
|
|
153
146
|
if (localColumn && foreignColumn) {
|
|
154
147
|
const relNode = {
|
|
155
|
-
name:
|
|
148
|
+
name: derivedForeignKeyName(table.name, [localColName]),
|
|
156
149
|
type: relation.cardinality === 'm1' ? 'ManyToOne' : 'OneToOne',
|
|
157
150
|
from: { table, columns: [localColumn] },
|
|
158
151
|
to: { table: relatedTable, columns: [foreignColumn] },
|
|
@@ -173,8 +166,7 @@ function addRelationshipsFromEntity(ctx, meta) {
|
|
|
173
166
|
* in common beyond their target table.
|
|
174
167
|
*/
|
|
175
168
|
function addIndexesFromEntity(ctx, meta) {
|
|
176
|
-
const
|
|
177
|
-
const table = ctx.ast.getTable(tableName);
|
|
169
|
+
const table = tableOf(ctx, meta);
|
|
178
170
|
if (!table)
|
|
179
171
|
return;
|
|
180
172
|
for (const key of Object.keys(meta.fields)) {
|
|
@@ -185,7 +177,7 @@ function addIndexesFromEntity(ctx, meta) {
|
|
|
185
177
|
if (!column)
|
|
186
178
|
continue;
|
|
187
179
|
ctx.ast.addIndex({
|
|
188
|
-
name: typeof field.index === 'string' ? field.index :
|
|
180
|
+
name: typeof field.index === 'string' ? field.index : derivedIndexName(table.name, [column.name]),
|
|
189
181
|
table,
|
|
190
182
|
entries: [{ column: column.name }],
|
|
191
183
|
unique: field.unique ?? false,
|
|
@@ -224,7 +216,7 @@ function addCompositeIndex(ctx, table, meta, idxMeta) {
|
|
|
224
216
|
// An index over expressions alone has no column names to build a default name from.
|
|
225
217
|
const named = entries.map((entry, at) => (entry.expression ? `expr${at}` : entry.column));
|
|
226
218
|
ctx.ast.addIndex({
|
|
227
|
-
name: idxMeta.name ??
|
|
219
|
+
name: idxMeta.name ?? derivedIndexName(table.name, named),
|
|
228
220
|
table,
|
|
229
221
|
entries,
|
|
230
222
|
include: idxMeta.include?.map((column) => resolveIncludeColumn(ctx, meta, column)),
|
package/dist/schema/types.d.ts
CHANGED
|
@@ -98,8 +98,17 @@ export interface ColumnNode {
|
|
|
98
98
|
* Represents a database table with all its columns, indexes, and relationships.
|
|
99
99
|
*/
|
|
100
100
|
export interface TableNode {
|
|
101
|
-
/**
|
|
101
|
+
/**
|
|
102
|
+
* The table's own name, never qualified. Everything derived from a table reads this: an index or
|
|
103
|
+
* constraint name is a single identifier, and `idx_sales.Order_total` is a syntax error.
|
|
104
|
+
*/
|
|
102
105
|
readonly name: string;
|
|
106
|
+
/**
|
|
107
|
+
* The namespace the table lives in, absent where nothing named one and it resolves through the
|
|
108
|
+
* connection's own default. Joined onto {@link name} by `qualifyName`, which is the key a
|
|
109
|
+
* `SchemaAST` stores the table under and the operand a statement names it by.
|
|
110
|
+
*/
|
|
111
|
+
readonly schema?: string;
|
|
103
112
|
/** Map of column name to column node */
|
|
104
113
|
readonly columns: Map<string, ColumnNode>;
|
|
105
114
|
/** Primary key columns (supports composite keys) */
|
|
@@ -108,8 +117,6 @@ export interface TableNode {
|
|
|
108
117
|
readonly indexes: IndexNode[];
|
|
109
118
|
/** Optional table comment */
|
|
110
119
|
readonly comment?: string;
|
|
111
|
-
/** Reference to the parent schema */
|
|
112
|
-
schema: SchemaAST;
|
|
113
120
|
/** Relationships pointing TO this table (other tables referencing this one) */
|
|
114
121
|
incomingRelations: RelationshipNode[];
|
|
115
122
|
/** Relationships pointing FROM this table (this table referencing others) */
|
package/dist/type/migration.d.ts
CHANGED
|
@@ -168,7 +168,13 @@ export interface ForeignKeySchema {
|
|
|
168
168
|
* Represents a difference between current and desired schema
|
|
169
169
|
*/
|
|
170
170
|
export interface SchemaDiff {
|
|
171
|
+
/** Qualified where the table has a schema, since it is also the key the table is found under. */
|
|
171
172
|
readonly tableName: string;
|
|
173
|
+
/**
|
|
174
|
+
* The schema {@link tableName} is in, carried separately for the identifiers that live in it
|
|
175
|
+
* rather than name it: a Postgres index is dropped as `schema.index`, never `schema.table`.
|
|
176
|
+
*/
|
|
177
|
+
readonly schema?: string;
|
|
172
178
|
readonly type: 'create' | 'alter' | 'drop';
|
|
173
179
|
readonly columnsToAdd?: ColumnSchema[];
|
|
174
180
|
readonly columnsToAlter?: {
|
|
@@ -243,9 +249,17 @@ export interface SchemaGenerator {
|
|
|
243
249
|
*/
|
|
244
250
|
diffSchema<E>(entity: Type<E>, currentTable: TableNode | undefined): SchemaDiff | undefined;
|
|
245
251
|
/**
|
|
246
|
-
*
|
|
252
|
+
* The table's key: {@link resolveTableAlias} behind {@link resolveSchema}, which is how a
|
|
253
|
+
* `SchemaAST` stores it and how a diff finds it again.
|
|
247
254
|
*/
|
|
248
255
|
resolveTableName<E>(meta: EntityMeta<E>): string;
|
|
256
|
+
/**
|
|
257
|
+
* The table's own name, unqualified. What a derived index or constraint name is built from, since
|
|
258
|
+
* those are single identifiers.
|
|
259
|
+
*/
|
|
260
|
+
resolveTableAlias<E>(meta: EntityMeta<E>): string;
|
|
261
|
+
/** The schema the table lives in, `undefined` where nothing named one. */
|
|
262
|
+
resolveSchema<E>(meta: EntityMeta<E>): string | undefined;
|
|
249
263
|
/**
|
|
250
264
|
* Resolve column name using field options and naming strategy
|
|
251
265
|
*/
|
package/dist/util/sql.util.d.ts
CHANGED
|
@@ -9,6 +9,23 @@ export declare function unflatObject<T extends object>(row: RawRow, attrsPaths:
|
|
|
9
9
|
export declare function obtainAttrsPaths<T extends object>(row: T): {
|
|
10
10
|
[k: string]: string[];
|
|
11
11
|
};
|
|
12
|
+
/**
|
|
13
|
+
* A name behind its namespace, or bare where there is none: the one place the two are joined, so a
|
|
14
|
+
* table's key, its statement operand and its escaped form cannot spell it differently. Never the
|
|
15
|
+
* seed for a derived identifier - an index or constraint name is a single identifier, and
|
|
16
|
+
* `idx_sales.Order_total` is a syntax error.
|
|
17
|
+
*/
|
|
18
|
+
export declare function qualifyName(name: string, schema?: string): string;
|
|
19
|
+
/**
|
|
20
|
+
* The name a derived index or constraint gets when nothing named it: `idx_Order_total`.
|
|
21
|
+
*
|
|
22
|
+
* One owner, because it is a rule two layers apply and a third has to match: the entity AST derives
|
|
23
|
+
* it, the DDL generator falls back to it, and a diff compares what the database reports against it.
|
|
24
|
+
* `table` is the table's own name, never qualified - the result is a single identifier.
|
|
25
|
+
*/
|
|
26
|
+
export declare function derivedIndexName(table: string, columns: readonly string[]): string;
|
|
27
|
+
/** The constraint name a foreign key gets when nothing named it: `fk_Order_customerId`. */
|
|
28
|
+
export declare function derivedForeignKeyName(table: string, columns: readonly string[]): string;
|
|
12
29
|
/**
|
|
13
30
|
* Escape a SQL identifier (table name, column name, etc.)
|
|
14
31
|
* @param val the identifier to escape
|
package/dist/util/sql.util.js
CHANGED
|
@@ -48,6 +48,29 @@ export function obtainAttrsPaths(row) {
|
|
|
48
48
|
}
|
|
49
49
|
return paths;
|
|
50
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* A name behind its namespace, or bare where there is none: the one place the two are joined, so a
|
|
53
|
+
* table's key, its statement operand and its escaped form cannot spell it differently. Never the
|
|
54
|
+
* seed for a derived identifier - an index or constraint name is a single identifier, and
|
|
55
|
+
* `idx_sales.Order_total` is a syntax error.
|
|
56
|
+
*/
|
|
57
|
+
export function qualifyName(name, schema) {
|
|
58
|
+
return schema ? `${schema}.${name}` : name;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The name a derived index or constraint gets when nothing named it: `idx_Order_total`.
|
|
62
|
+
*
|
|
63
|
+
* One owner, because it is a rule two layers apply and a third has to match: the entity AST derives
|
|
64
|
+
* it, the DDL generator falls back to it, and a diff compares what the database reports against it.
|
|
65
|
+
* `table` is the table's own name, never qualified - the result is a single identifier.
|
|
66
|
+
*/
|
|
67
|
+
export function derivedIndexName(table, columns) {
|
|
68
|
+
return `idx_${table}_${columns.join('_')}`;
|
|
69
|
+
}
|
|
70
|
+
/** The constraint name a foreign key gets when nothing named it: `fk_Order_customerId`. */
|
|
71
|
+
export function derivedForeignKeyName(table, columns) {
|
|
72
|
+
return `fk_${table}_${columns.join('_')}`;
|
|
73
|
+
}
|
|
51
74
|
/**
|
|
52
75
|
* Escape a SQL identifier (table name, column name, etc.)
|
|
53
76
|
* @param val the identifier to escape
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"homepage": "https://uql-orm.dev",
|
|
4
4
|
"description": "JSON-native TypeScript ORM for Node.js, Bun and Deno. Supports PostgreSQL, PGlite, MySQL, MariaDB, SQLite, CockroachDB, Turso, Neon, Cloudflare D1 and MongoDB. Queries are plain JSON, typed to the leaf.",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"version": "0.34.
|
|
6
|
+
"version": "0.34.1",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"engines": {
|
|
9
9
|
"node": ">=24"
|