@mikro-orm/sql 7.0.0-dev.98 → 7.0.0-rc.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/AbstractSqlConnection.d.ts +6 -7
- package/AbstractSqlConnection.js +27 -24
- package/AbstractSqlDriver.d.ts +82 -23
- package/AbstractSqlDriver.js +584 -184
- package/AbstractSqlPlatform.d.ts +3 -4
- package/AbstractSqlPlatform.js +0 -4
- package/PivotCollectionPersister.d.ts +5 -0
- package/PivotCollectionPersister.js +30 -12
- package/SqlEntityManager.d.ts +2 -2
- package/dialects/mysql/{MySqlPlatform.d.ts → BaseMySqlPlatform.d.ts} +3 -2
- package/dialects/mysql/{MySqlPlatform.js → BaseMySqlPlatform.js} +5 -1
- package/dialects/mysql/MySqlSchemaHelper.d.ts +12 -1
- package/dialects/mysql/MySqlSchemaHelper.js +97 -6
- package/dialects/mysql/index.d.ts +1 -2
- package/dialects/mysql/index.js +1 -2
- package/dialects/postgresql/BasePostgreSqlPlatform.d.ts +106 -0
- package/dialects/postgresql/BasePostgreSqlPlatform.js +350 -0
- package/dialects/postgresql/FullTextType.d.ts +14 -0
- package/dialects/postgresql/FullTextType.js +59 -0
- package/dialects/postgresql/PostgreSqlExceptionConverter.d.ts +8 -0
- package/dialects/postgresql/PostgreSqlExceptionConverter.js +47 -0
- package/dialects/postgresql/PostgreSqlSchemaHelper.d.ts +90 -0
- package/dialects/postgresql/PostgreSqlSchemaHelper.js +732 -0
- package/dialects/postgresql/index.d.ts +3 -0
- package/dialects/postgresql/index.js +3 -0
- package/dialects/sqlite/BaseSqliteConnection.d.ts +1 -0
- package/dialects/sqlite/BaseSqliteConnection.js +14 -1
- package/dialects/sqlite/BaseSqlitePlatform.d.ts +6 -0
- package/dialects/sqlite/BaseSqlitePlatform.js +12 -0
- package/dialects/sqlite/SqliteSchemaHelper.d.ts +25 -0
- package/dialects/sqlite/SqliteSchemaHelper.js +145 -19
- package/dialects/sqlite/index.d.ts +0 -1
- package/dialects/sqlite/index.js +0 -1
- package/package.json +5 -6
- package/plugin/transformer.d.ts +1 -1
- package/plugin/transformer.js +1 -1
- package/query/CriteriaNode.d.ts +9 -5
- package/query/CriteriaNode.js +16 -15
- package/query/CriteriaNodeFactory.d.ts +6 -6
- package/query/CriteriaNodeFactory.js +33 -31
- package/query/NativeQueryBuilder.d.ts +3 -2
- package/query/NativeQueryBuilder.js +1 -2
- package/query/ObjectCriteriaNode.js +50 -35
- package/query/QueryBuilder.d.ts +548 -79
- package/query/QueryBuilder.js +537 -159
- package/query/QueryBuilderHelper.d.ts +22 -14
- package/query/QueryBuilderHelper.js +158 -69
- package/query/ScalarCriteriaNode.js +2 -2
- package/query/raw.d.ts +11 -3
- package/query/raw.js +1 -2
- package/schema/DatabaseSchema.d.ts +15 -2
- package/schema/DatabaseSchema.js +143 -15
- package/schema/DatabaseTable.d.ts +12 -0
- package/schema/DatabaseTable.js +91 -31
- package/schema/SchemaComparator.d.ts +8 -0
- package/schema/SchemaComparator.js +126 -3
- package/schema/SchemaHelper.d.ts +26 -3
- package/schema/SchemaHelper.js +98 -11
- package/schema/SqlSchemaGenerator.d.ts +10 -0
- package/schema/SqlSchemaGenerator.js +137 -9
- package/tsconfig.build.tsbuildinfo +1 -0
- package/typings.d.ts +74 -36
- package/dialects/postgresql/PostgreSqlTableCompiler.d.ts +0 -1
- package/dialects/postgresql/PostgreSqlTableCompiler.js +0 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type Configuration, type Dictionary, type EntityMetadata } from '@mikro-orm/core';
|
|
2
2
|
import { DatabaseTable } from './DatabaseTable.js';
|
|
3
3
|
import type { AbstractSqlConnection } from '../AbstractSqlConnection.js';
|
|
4
|
+
import type { DatabaseView } from '../typings.js';
|
|
4
5
|
import type { AbstractSqlPlatform } from '../AbstractSqlPlatform.js';
|
|
5
6
|
/**
|
|
6
7
|
* @internal
|
|
@@ -9,6 +10,7 @@ export declare class DatabaseSchema {
|
|
|
9
10
|
private readonly platform;
|
|
10
11
|
readonly name: string;
|
|
11
12
|
private tables;
|
|
13
|
+
private views;
|
|
12
14
|
private namespaces;
|
|
13
15
|
private nativeEnums;
|
|
14
16
|
constructor(platform: AbstractSqlPlatform, name: string);
|
|
@@ -16,6 +18,10 @@ export declare class DatabaseSchema {
|
|
|
16
18
|
getTables(): DatabaseTable[];
|
|
17
19
|
getTable(name: string): DatabaseTable | undefined;
|
|
18
20
|
hasTable(name: string): boolean;
|
|
21
|
+
addView(name: string, schema: string | undefined | null, definition: string, materialized?: boolean, withData?: boolean): DatabaseView;
|
|
22
|
+
getViews(): DatabaseView[];
|
|
23
|
+
getView(name: string): DatabaseView | undefined;
|
|
24
|
+
hasView(name: string): boolean;
|
|
19
25
|
setNativeEnums(nativeEnums: Dictionary<{
|
|
20
26
|
name: string;
|
|
21
27
|
schema?: string;
|
|
@@ -34,10 +40,17 @@ export declare class DatabaseSchema {
|
|
|
34
40
|
hasNamespace(namespace: string): boolean;
|
|
35
41
|
hasNativeEnum(name: string): boolean;
|
|
36
42
|
getNamespaces(): string[];
|
|
37
|
-
static create(connection: AbstractSqlConnection, platform: AbstractSqlPlatform, config: Configuration, schemaName?: string, schemas?: string[], takeTables?: (string | RegExp)[], skipTables?: (string | RegExp)[]): Promise<DatabaseSchema>;
|
|
38
|
-
static fromMetadata(metadata: EntityMetadata[], platform: AbstractSqlPlatform, config: Configuration, schemaName?: string): DatabaseSchema;
|
|
43
|
+
static create(connection: AbstractSqlConnection, platform: AbstractSqlPlatform, config: Configuration, schemaName?: string, schemas?: string[], takeTables?: (string | RegExp)[], skipTables?: (string | RegExp)[], skipViews?: (string | RegExp)[]): Promise<DatabaseSchema>;
|
|
44
|
+
static fromMetadata(metadata: EntityMetadata[], platform: AbstractSqlPlatform, config: Configuration, schemaName?: string, em?: any): DatabaseSchema;
|
|
45
|
+
private static getViewDefinition;
|
|
39
46
|
private static getSchemaName;
|
|
47
|
+
/**
|
|
48
|
+
* Add a foreign key from a TPT child entity's PK to its parent entity's PK.
|
|
49
|
+
* This FK uses ON DELETE CASCADE to ensure child rows are deleted when parent is deleted.
|
|
50
|
+
*/
|
|
51
|
+
private static addTPTForeignKey;
|
|
40
52
|
private static matchName;
|
|
53
|
+
private static isNameAllowed;
|
|
41
54
|
private static isTableNameAllowed;
|
|
42
55
|
private static shouldHaveColumn;
|
|
43
56
|
toJSON(): Dictionary;
|
package/schema/DatabaseSchema.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ReferenceKind,
|
|
1
|
+
import { ReferenceKind, isRaw, } from '@mikro-orm/core';
|
|
2
2
|
import { DatabaseTable } from './DatabaseTable.js';
|
|
3
3
|
/**
|
|
4
4
|
* @internal
|
|
@@ -7,6 +7,7 @@ export class DatabaseSchema {
|
|
|
7
7
|
platform;
|
|
8
8
|
name;
|
|
9
9
|
tables = [];
|
|
10
|
+
views = [];
|
|
10
11
|
namespaces = new Set();
|
|
11
12
|
nativeEnums = {}; // for postgres
|
|
12
13
|
constructor(platform, name) {
|
|
@@ -33,6 +34,24 @@ export class DatabaseSchema {
|
|
|
33
34
|
hasTable(name) {
|
|
34
35
|
return !!this.getTable(name);
|
|
35
36
|
}
|
|
37
|
+
addView(name, schema, definition, materialized, withData) {
|
|
38
|
+
const namespaceName = schema ?? this.name;
|
|
39
|
+
const view = { name, schema: namespaceName, definition, materialized, withData };
|
|
40
|
+
this.views.push(view);
|
|
41
|
+
if (namespaceName != null) {
|
|
42
|
+
this.namespaces.add(namespaceName);
|
|
43
|
+
}
|
|
44
|
+
return view;
|
|
45
|
+
}
|
|
46
|
+
getViews() {
|
|
47
|
+
return this.views;
|
|
48
|
+
}
|
|
49
|
+
getView(name) {
|
|
50
|
+
return this.views.find(v => v.name === name || `${v.schema}.${v.name}` === name);
|
|
51
|
+
}
|
|
52
|
+
hasView(name) {
|
|
53
|
+
return !!this.getView(name);
|
|
54
|
+
}
|
|
36
55
|
setNativeEnums(nativeEnums) {
|
|
37
56
|
this.nativeEnums = nativeEnums;
|
|
38
57
|
for (const nativeEnum of Object.values(nativeEnums)) {
|
|
@@ -56,21 +75,35 @@ export class DatabaseSchema {
|
|
|
56
75
|
getNamespaces() {
|
|
57
76
|
return [...this.namespaces];
|
|
58
77
|
}
|
|
59
|
-
static async create(connection, platform, config, schemaName, schemas, takeTables, skipTables) {
|
|
78
|
+
static async create(connection, platform, config, schemaName, schemas, takeTables, skipTables, skipViews) {
|
|
60
79
|
const schema = new DatabaseSchema(platform, schemaName ?? config.get('schema') ?? platform.getDefaultSchemaName());
|
|
61
|
-
const allTables = await
|
|
80
|
+
const allTables = await platform.getSchemaHelper().getAllTables(connection, schemas);
|
|
62
81
|
const parts = config.get('migrations').tableName.split('.');
|
|
63
82
|
const migrationsTableName = parts[1] ?? parts[0];
|
|
64
83
|
const migrationsSchemaName = parts.length > 1 ? parts[0] : config.get('schema', platform.getDefaultSchemaName());
|
|
65
84
|
const tables = allTables.filter(t => this.isTableNameAllowed(t.table_name, takeTables, skipTables) && (t.table_name !== migrationsTableName || (t.schema_name && t.schema_name !== migrationsSchemaName)));
|
|
66
85
|
await platform.getSchemaHelper().loadInformationSchema(schema, connection, tables, schemas && schemas.length > 0 ? schemas : undefined);
|
|
86
|
+
// Load views from database
|
|
87
|
+
await platform.getSchemaHelper().loadViews(schema, connection);
|
|
88
|
+
// Load materialized views (PostgreSQL only)
|
|
89
|
+
if (platform.supportsMaterializedViews()) {
|
|
90
|
+
await platform.getSchemaHelper().loadMaterializedViews(schema, connection, schemaName);
|
|
91
|
+
}
|
|
92
|
+
// Filter out skipped views
|
|
93
|
+
if (skipViews && skipViews.length > 0) {
|
|
94
|
+
schema.views = schema.views.filter(v => this.isNameAllowed(v.name, skipViews));
|
|
95
|
+
}
|
|
67
96
|
return schema;
|
|
68
97
|
}
|
|
69
|
-
static fromMetadata(metadata, platform, config, schemaName) {
|
|
98
|
+
static fromMetadata(metadata, platform, config, schemaName, em) {
|
|
70
99
|
const schema = new DatabaseSchema(platform, schemaName ?? config.get('schema'));
|
|
71
100
|
const nativeEnums = {};
|
|
72
101
|
const skipColumns = config.get('schemaGenerator').skipColumns || {};
|
|
73
102
|
for (const meta of metadata) {
|
|
103
|
+
// Skip view entities when collecting native enums
|
|
104
|
+
if (meta.view) {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
74
107
|
for (const prop of meta.props) {
|
|
75
108
|
if (prop.nativeEnumName) {
|
|
76
109
|
let key = prop.nativeEnumName;
|
|
@@ -95,45 +128,134 @@ export class DatabaseSchema {
|
|
|
95
128
|
}
|
|
96
129
|
schema.setNativeEnums(nativeEnums);
|
|
97
130
|
for (const meta of metadata) {
|
|
131
|
+
// Handle view entities separately
|
|
132
|
+
if (meta.view) {
|
|
133
|
+
const viewDefinition = this.getViewDefinition(meta, em, platform);
|
|
134
|
+
if (viewDefinition) {
|
|
135
|
+
schema.addView(meta.collection, this.getSchemaName(meta, config, schemaName), viewDefinition, meta.materialized, meta.withData);
|
|
136
|
+
}
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
98
139
|
const table = schema.addTable(meta.collection, this.getSchemaName(meta, config, schemaName));
|
|
99
140
|
table.comment = meta.comment;
|
|
100
|
-
|
|
141
|
+
// For TPT child entities, only use ownProps (properties defined in this entity only)
|
|
142
|
+
// For all other entities (including TPT root), use all props
|
|
143
|
+
const propsToProcess = meta.inheritanceType === 'tpt' && meta.tptParent && meta.ownProps
|
|
144
|
+
? meta.ownProps
|
|
145
|
+
: meta.props;
|
|
146
|
+
for (const prop of propsToProcess) {
|
|
101
147
|
if (!this.shouldHaveColumn(meta, prop, skipColumns)) {
|
|
102
148
|
continue;
|
|
103
149
|
}
|
|
104
150
|
table.addColumnFromProperty(prop, meta, config);
|
|
105
151
|
}
|
|
152
|
+
// For TPT child entities, always include the PK columns (they form the FK to parent)
|
|
153
|
+
if (meta.inheritanceType === 'tpt' && meta.tptParent) {
|
|
154
|
+
const pkProps = meta.primaryKeys.map(pk => meta.properties[pk]);
|
|
155
|
+
for (const pkProp of pkProps) {
|
|
156
|
+
// Only add if not already added (it might be in ownProps if defined in this entity)
|
|
157
|
+
if (!propsToProcess.includes(pkProp)) {
|
|
158
|
+
table.addColumnFromProperty(pkProp, meta, config);
|
|
159
|
+
}
|
|
160
|
+
// Child PK must not be autoincrement — it references the parent PK via FK
|
|
161
|
+
for (const field of pkProp.fieldNames) {
|
|
162
|
+
const col = table.getColumn(field);
|
|
163
|
+
if (col) {
|
|
164
|
+
col.autoincrement = false;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// Add FK from child PK to parent PK with ON DELETE CASCADE
|
|
169
|
+
this.addTPTForeignKey(table, meta, config, platform);
|
|
170
|
+
}
|
|
106
171
|
meta.indexes.forEach(index => table.addIndex(meta, index, 'index'));
|
|
107
172
|
meta.uniques.forEach(index => table.addIndex(meta, index, 'unique'));
|
|
108
|
-
|
|
173
|
+
// For TPT child entities, the PK is also defined here
|
|
174
|
+
const pkPropsForIndex = meta.inheritanceType === 'tpt' && meta.tptParent
|
|
175
|
+
? meta.primaryKeys.map(pk => meta.properties[pk])
|
|
176
|
+
: meta.props.filter(prop => prop.primary);
|
|
177
|
+
table.addIndex(meta, { properties: pkPropsForIndex.map(prop => prop.name) }, 'primary');
|
|
109
178
|
for (const check of meta.checks) {
|
|
110
179
|
const columnName = check.property ? meta.properties[check.property].fieldNames[0] : undefined;
|
|
111
|
-
|
|
112
|
-
const raw = RawQueryFragment.getKnownFragment(expression);
|
|
113
|
-
if (raw) {
|
|
114
|
-
expression = platform.formatQuery(raw.sql, raw.params);
|
|
115
|
-
}
|
|
180
|
+
const expression = isRaw(check.expression) ? platform.formatQuery(check.expression.sql, check.expression.params) : check.expression;
|
|
116
181
|
table.addCheck({
|
|
117
182
|
name: check.name,
|
|
118
183
|
expression,
|
|
119
|
-
definition: `check (${
|
|
184
|
+
definition: `check (${expression})`,
|
|
120
185
|
columnName,
|
|
121
186
|
});
|
|
122
187
|
}
|
|
123
188
|
}
|
|
124
189
|
return schema;
|
|
125
190
|
}
|
|
191
|
+
static getViewDefinition(meta, em, platform) {
|
|
192
|
+
if (typeof meta.expression === 'string') {
|
|
193
|
+
return meta.expression;
|
|
194
|
+
}
|
|
195
|
+
// Expression is a function, need to evaluate it
|
|
196
|
+
/* v8 ignore next */
|
|
197
|
+
if (!em) {
|
|
198
|
+
return undefined;
|
|
199
|
+
}
|
|
200
|
+
const result = meta.expression(em, {}, {});
|
|
201
|
+
// Async expressions are not supported for view entities
|
|
202
|
+
if (result && typeof result.then === 'function') {
|
|
203
|
+
throw new Error(`View entity ${meta.className} expression returned a Promise. Async expressions are not supported for view entities.`);
|
|
204
|
+
}
|
|
205
|
+
/* v8 ignore next */
|
|
206
|
+
if (typeof result === 'string') {
|
|
207
|
+
return result;
|
|
208
|
+
}
|
|
209
|
+
/* v8 ignore next */
|
|
210
|
+
if (isRaw(result)) {
|
|
211
|
+
return platform.formatQuery(result.sql, result.params);
|
|
212
|
+
}
|
|
213
|
+
// Check if it's a QueryBuilder (has getFormattedQuery method)
|
|
214
|
+
if (result && typeof result.getFormattedQuery === 'function') {
|
|
215
|
+
return result.getFormattedQuery();
|
|
216
|
+
}
|
|
217
|
+
/* v8 ignore next - fallback for unknown result types */
|
|
218
|
+
return undefined;
|
|
219
|
+
}
|
|
126
220
|
static getSchemaName(meta, config, schema) {
|
|
127
221
|
return (meta.schema === '*' ? schema : meta.schema) ?? config.get('schema');
|
|
128
222
|
}
|
|
223
|
+
/**
|
|
224
|
+
* Add a foreign key from a TPT child entity's PK to its parent entity's PK.
|
|
225
|
+
* This FK uses ON DELETE CASCADE to ensure child rows are deleted when parent is deleted.
|
|
226
|
+
*/
|
|
227
|
+
static addTPTForeignKey(table, meta, config, platform) {
|
|
228
|
+
const parent = meta.tptParent;
|
|
229
|
+
const pkColumnNames = meta.primaryKeys.flatMap(pk => meta.properties[pk].fieldNames);
|
|
230
|
+
const parentPkColumnNames = parent.primaryKeys.flatMap(pk => parent.properties[pk].fieldNames);
|
|
231
|
+
// Determine the parent table name with schema
|
|
232
|
+
const parentSchema = parent.schema === '*' ? undefined : parent.schema ?? config.get('schema', platform.getDefaultSchemaName());
|
|
233
|
+
const parentTableName = parentSchema ? `${parentSchema}.${parent.tableName}` : parent.tableName;
|
|
234
|
+
// Create FK constraint name
|
|
235
|
+
const constraintName = platform.getIndexName(table.name, pkColumnNames, 'foreign');
|
|
236
|
+
// Add the foreign key to the table
|
|
237
|
+
const fks = table.getForeignKeys();
|
|
238
|
+
fks[constraintName] = {
|
|
239
|
+
constraintName,
|
|
240
|
+
columnNames: pkColumnNames,
|
|
241
|
+
localTableName: table.getShortestName(false),
|
|
242
|
+
referencedColumnNames: parentPkColumnNames,
|
|
243
|
+
referencedTableName: parentTableName,
|
|
244
|
+
deleteRule: 'cascade', // TPT always uses cascade delete
|
|
245
|
+
updateRule: 'cascade', // TPT always uses cascade update
|
|
246
|
+
};
|
|
247
|
+
}
|
|
129
248
|
static matchName(name, nameToMatch) {
|
|
130
249
|
return typeof nameToMatch === 'string'
|
|
131
250
|
? name.toLocaleLowerCase() === nameToMatch.toLocaleLowerCase()
|
|
132
251
|
: nameToMatch.test(name);
|
|
133
252
|
}
|
|
253
|
+
static isNameAllowed(name, skipNames) {
|
|
254
|
+
return !(skipNames?.some(pattern => this.matchName(name, pattern)) ?? false);
|
|
255
|
+
}
|
|
134
256
|
static isTableNameAllowed(tableName, takeTables, skipTables) {
|
|
135
257
|
return ((takeTables?.some(tableNameToMatch => this.matchName(tableName, tableNameToMatch)) ?? true) &&
|
|
136
|
-
|
|
258
|
+
this.isNameAllowed(tableName, skipTables));
|
|
137
259
|
}
|
|
138
260
|
static shouldHaveColumn(meta, prop, skipColumns) {
|
|
139
261
|
if (prop.persist === false || (prop.columnTypes?.length ?? 0) === 0) {
|
|
@@ -175,9 +297,15 @@ export class DatabaseSchema {
|
|
|
175
297
|
|| table.schema === schema // specified schema matches the table's one
|
|
176
298
|
|| (!schema && !wildcardSchemaTables.includes(table.name)); // no schema specified and the table has fixed one provided
|
|
177
299
|
});
|
|
178
|
-
|
|
300
|
+
this.views = this.views.filter(view => {
|
|
301
|
+
/* v8 ignore next */
|
|
302
|
+
return (!schema && !hasWildcardSchema)
|
|
303
|
+
|| view.schema === schema
|
|
304
|
+
|| (!schema && !wildcardSchemaTables.includes(view.name));
|
|
305
|
+
});
|
|
306
|
+
// remove namespaces of ignored tables and views
|
|
179
307
|
for (const ns of this.namespaces) {
|
|
180
|
-
if (!this.tables.some(t => t.schema === ns) && !Object.values(this.nativeEnums).some(e => e.schema === ns)) {
|
|
308
|
+
if (!this.tables.some(t => t.schema === ns) && !this.views.some(v => v.schema === ns) && !Object.values(this.nativeEnums).some(e => e.schema === ns)) {
|
|
181
309
|
this.namespaces.delete(ns);
|
|
182
310
|
}
|
|
183
311
|
}
|
|
@@ -62,6 +62,18 @@ export declare class DatabaseTable {
|
|
|
62
62
|
expression?: string | IndexCallback<any>;
|
|
63
63
|
deferMode?: DeferMode | `${DeferMode}`;
|
|
64
64
|
options?: Dictionary;
|
|
65
|
+
columns?: {
|
|
66
|
+
name: string;
|
|
67
|
+
sort?: 'ASC' | 'DESC' | 'asc' | 'desc';
|
|
68
|
+
nulls?: 'FIRST' | 'LAST' | 'first' | 'last';
|
|
69
|
+
length?: number;
|
|
70
|
+
collation?: string;
|
|
71
|
+
}[];
|
|
72
|
+
include?: string | string[];
|
|
73
|
+
fillFactor?: number;
|
|
74
|
+
invisible?: boolean;
|
|
75
|
+
disabled?: boolean;
|
|
76
|
+
clustered?: boolean;
|
|
65
77
|
}, type: 'index' | 'unique' | 'primary'): void;
|
|
66
78
|
addCheck(check: CheckDef): void;
|
|
67
79
|
toJSON(): Dictionary;
|
package/schema/DatabaseTable.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DecimalType, EntitySchema, RawQueryFragment, ReferenceKind, t, Type, UnknownType, Utils, } from '@mikro-orm/core';
|
|
2
2
|
/**
|
|
3
3
|
* @internal
|
|
4
4
|
*/
|
|
@@ -80,7 +80,7 @@ export class DatabaseTable {
|
|
|
80
80
|
this.columns[field] = {
|
|
81
81
|
name: prop.fieldNames[idx],
|
|
82
82
|
type: prop.columnTypes[idx],
|
|
83
|
-
generated: prop.generated,
|
|
83
|
+
generated: prop.generated instanceof RawQueryFragment ? this.platform.formatQuery(prop.generated.sql, prop.generated.params) : prop.generated,
|
|
84
84
|
mappedType,
|
|
85
85
|
unsigned: prop.unsigned && this.platform.isNumericColumn(mappedType),
|
|
86
86
|
autoincrement: prop.autoincrement ?? (primary && prop.kind === ReferenceKind.SCALAR && this.platform.isNumericColumn(mappedType)),
|
|
@@ -103,7 +103,7 @@ export class DatabaseTable {
|
|
|
103
103
|
const defaultValue = this.platform.getSchemaHelper().normalizeDefaultValue(prop.defaultRaw, prop.length);
|
|
104
104
|
this.columns[field].default = defaultValue;
|
|
105
105
|
});
|
|
106
|
-
if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
|
|
106
|
+
if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && !prop.polymorphic) {
|
|
107
107
|
const constraintName = this.getIndexName(prop.foreignKeyName ?? true, prop.fieldNames, 'foreign');
|
|
108
108
|
let schema = prop.targetMeta.root.schema === '*' ? this.schema : (prop.targetMeta.root.schema ?? config.get('schema', this.platform.getDefaultSchemaName()));
|
|
109
109
|
if (prop.referencedTableName.includes('.')) {
|
|
@@ -117,23 +117,9 @@ export class DatabaseTable {
|
|
|
117
117
|
referencedColumnNames: prop.referencedColumnNames,
|
|
118
118
|
referencedTableName: schema ? `${schema}.${prop.referencedTableName}` : prop.referencedTableName,
|
|
119
119
|
};
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
if (prop.updateRule) {
|
|
125
|
-
this.foreignKeys[constraintName].updateRule = prop.updateRule || 'cascade';
|
|
126
|
-
}
|
|
127
|
-
if ((prop.cascade.includes(Cascade.PERSIST) || prop.cascade.includes(Cascade.ALL))) {
|
|
128
|
-
const hasCascadePath = Object.values(this.foreignKeys).some(fk => {
|
|
129
|
-
return fk.constraintName !== constraintName
|
|
130
|
-
&& ((fk.updateRule && fk.updateRule !== 'no action') || (fk.deleteRule && fk.deleteRule !== 'no action'))
|
|
131
|
-
&& fk.referencedTableName === this.foreignKeys[constraintName].referencedTableName;
|
|
132
|
-
});
|
|
133
|
-
if (!hasCascadePath || this.platform.supportsMultipleCascadePaths()) {
|
|
134
|
-
this.foreignKeys[constraintName].updateRule ??= 'cascade';
|
|
135
|
-
}
|
|
136
|
-
}
|
|
120
|
+
const schemaConfig = config.get('schemaGenerator');
|
|
121
|
+
this.foreignKeys[constraintName].deleteRule = prop.deleteRule ?? schemaConfig.defaultDeleteRule;
|
|
122
|
+
this.foreignKeys[constraintName].updateRule = prop.updateRule ?? schemaConfig.defaultUpdateRule;
|
|
137
123
|
if (prop.deferMode) {
|
|
138
124
|
this.foreignKeys[constraintName].deferMode = prop.deferMode;
|
|
139
125
|
}
|
|
@@ -181,13 +167,40 @@ export class DatabaseTable {
|
|
|
181
167
|
)
|
|
182
168
|
// ignore indexes that don't have all column names (this can happen in sqlite where there is no way to infer this for expressions)
|
|
183
169
|
&& !(index.columnNames.some(col => !col) && !index.expression));
|
|
170
|
+
// Helper to map column name to property name
|
|
171
|
+
const columnToPropertyName = (colName) => this.getPropertyName(namingStrategy, colName);
|
|
184
172
|
for (const index of potentiallyUnmappedIndexes) {
|
|
173
|
+
// Build the index/unique options object with advanced options
|
|
185
174
|
const ret = {
|
|
186
175
|
name: index.keyName,
|
|
187
176
|
deferMode: index.deferMode,
|
|
188
177
|
expression: index.expression,
|
|
178
|
+
// Advanced index options - convert column names to property names
|
|
179
|
+
columns: index.columns?.map(col => ({
|
|
180
|
+
...col,
|
|
181
|
+
name: columnToPropertyName(col.name),
|
|
182
|
+
})),
|
|
183
|
+
include: index.include?.map(colName => columnToPropertyName(colName)),
|
|
184
|
+
fillFactor: index.fillFactor,
|
|
185
|
+
disabled: index.disabled,
|
|
189
186
|
};
|
|
190
|
-
|
|
187
|
+
// Index-only options (not valid for Unique)
|
|
188
|
+
if (!index.unique) {
|
|
189
|
+
if (index.type) {
|
|
190
|
+
// Convert index type - IndexDef.type can be string or object, IndexOptions.type is just string
|
|
191
|
+
ret.type = typeof index.type === 'string' ? index.type : index.type.indexType;
|
|
192
|
+
}
|
|
193
|
+
if (index.invisible) {
|
|
194
|
+
ret.invisible = index.invisible;
|
|
195
|
+
}
|
|
196
|
+
if (index.clustered) {
|
|
197
|
+
ret.clustered = index.clustered;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
// An index is trivial if it has no special options that require entity-level declaration
|
|
201
|
+
const hasAdvancedOptions = index.columns?.length || index.include?.length || index.fillFactor ||
|
|
202
|
+
index.type || index.invisible || index.disabled || index.clustered;
|
|
203
|
+
const isTrivial = !index.deferMode && !index.expression && !hasAdvancedOptions;
|
|
191
204
|
if (isTrivial) {
|
|
192
205
|
// Index is for FK. Map to the FK prop and move on.
|
|
193
206
|
const fkForIndex = fkIndexes.get(index);
|
|
@@ -495,11 +508,19 @@ export class DatabaseTable {
|
|
|
495
508
|
// Use the column name as a base for the FK prop.
|
|
496
509
|
return columnName;
|
|
497
510
|
}
|
|
498
|
-
|
|
499
|
-
|
|
511
|
+
// Strip schema prefix from referenced table name (e.g., "public.fr_usuario" -> "fr_usuario")
|
|
512
|
+
const getTableName = (fullName) => {
|
|
513
|
+
const parts = fullName.split('.');
|
|
514
|
+
return parts[parts.length - 1];
|
|
515
|
+
};
|
|
516
|
+
const referencedTableName = getTableName(currentFk.referencedTableName);
|
|
517
|
+
// Check for conflicts using stripped table names (handles cross-schema FKs to same-named tables)
|
|
518
|
+
const hasConflictingFk = fks.some(fk => fk !== currentFk && getTableName(fk.referencedTableName) === referencedTableName);
|
|
519
|
+
if (!hasConflictingFk && !this.getColumn(referencedTableName)) {
|
|
520
|
+
// FK is the only one in this table that references a table with this name.
|
|
500
521
|
// The name of the referenced table is not shared with a column in this table,
|
|
501
522
|
// so it is safe to output prop name based on the referenced entity.
|
|
502
|
-
return
|
|
523
|
+
return referencedTableName;
|
|
503
524
|
}
|
|
504
525
|
// Any ambiguous FK is rendered with a name based on the FK constraint name
|
|
505
526
|
let finalPropBaseName = currentFk.constraintName;
|
|
@@ -716,22 +737,24 @@ export class DatabaseTable {
|
|
|
716
737
|
}
|
|
717
738
|
processIndexExpression(indexName, expression, meta) {
|
|
718
739
|
if (expression instanceof Function) {
|
|
740
|
+
const qualifiedName = this.schema ? `${this.schema}.${this.name}` : this.name;
|
|
719
741
|
const table = {
|
|
720
742
|
name: this.name,
|
|
721
743
|
schema: this.schema,
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
return `${this.schema}.${this.name}`;
|
|
725
|
-
}
|
|
726
|
-
return this.name;
|
|
727
|
-
},
|
|
744
|
+
qualifiedName,
|
|
745
|
+
toString: () => qualifiedName,
|
|
728
746
|
};
|
|
729
|
-
const
|
|
747
|
+
const columns = meta.createSchemaColumnMappingObject();
|
|
748
|
+
const exp = expression(columns, table, indexName);
|
|
730
749
|
return exp instanceof RawQueryFragment ? this.platform.formatQuery(exp.sql, exp.params) : exp;
|
|
731
750
|
}
|
|
732
751
|
return expression;
|
|
733
752
|
}
|
|
734
753
|
addIndex(meta, index, type) {
|
|
754
|
+
// If columns are specified but properties are not, derive properties from column names
|
|
755
|
+
if (index.columns?.length && !index.expression && (!index.properties || Utils.asArray(index.properties).length === 0)) {
|
|
756
|
+
index = { ...index, properties: index.columns.map(c => c.name) };
|
|
757
|
+
}
|
|
735
758
|
const properties = Utils.unique(Utils.flatten(Utils.asArray(index.properties).map(prop => {
|
|
736
759
|
const parts = prop.split('.');
|
|
737
760
|
const root = parts[0];
|
|
@@ -763,6 +786,37 @@ export class DatabaseTable {
|
|
|
763
786
|
return;
|
|
764
787
|
}
|
|
765
788
|
const name = this.getIndexName(index.name, properties, type);
|
|
789
|
+
// Process include columns (map property names to field names)
|
|
790
|
+
const includeColumns = index.include ? Utils.unique(Utils.flatten(Utils.asArray(index.include).map(prop => {
|
|
791
|
+
if (meta.properties[prop]) {
|
|
792
|
+
return meta.properties[prop].fieldNames;
|
|
793
|
+
}
|
|
794
|
+
/* v8 ignore next */
|
|
795
|
+
return [prop];
|
|
796
|
+
}))) : undefined;
|
|
797
|
+
// Process columns with advanced options (map property names to field names)
|
|
798
|
+
const columns = index.columns?.map(col => {
|
|
799
|
+
const fieldName = meta.properties[col.name]?.fieldNames[0] ?? col.name;
|
|
800
|
+
return {
|
|
801
|
+
name: fieldName,
|
|
802
|
+
sort: col.sort?.toUpperCase(),
|
|
803
|
+
nulls: col.nulls?.toUpperCase(),
|
|
804
|
+
length: col.length,
|
|
805
|
+
collation: col.collation,
|
|
806
|
+
};
|
|
807
|
+
});
|
|
808
|
+
// Validate that column options reference fields in the index properties
|
|
809
|
+
if (columns?.length && properties.length > 0) {
|
|
810
|
+
for (const col of columns) {
|
|
811
|
+
if (!properties.includes(col.name)) {
|
|
812
|
+
throw new Error(`Index '${name}' on entity '${meta.className}': column option references field '${col.name}' which is not in the index properties`);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
// Validate fillFactor range
|
|
817
|
+
if (index.fillFactor != null && (index.fillFactor < 0 || index.fillFactor > 100)) {
|
|
818
|
+
throw new Error(`fillFactor must be between 0 and 100, got ${index.fillFactor} for index '${name}' on entity '${meta.className}'`);
|
|
819
|
+
}
|
|
766
820
|
this.indexes.push({
|
|
767
821
|
keyName: name,
|
|
768
822
|
columnNames: properties,
|
|
@@ -775,6 +829,12 @@ export class DatabaseTable {
|
|
|
775
829
|
expression: this.processIndexExpression(name, index.expression, meta),
|
|
776
830
|
options: index.options,
|
|
777
831
|
deferMode: index.deferMode,
|
|
832
|
+
columns,
|
|
833
|
+
include: includeColumns,
|
|
834
|
+
fillFactor: index.fillFactor,
|
|
835
|
+
invisible: index.invisible,
|
|
836
|
+
disabled: index.disabled,
|
|
837
|
+
clustered: index.clustered,
|
|
778
838
|
});
|
|
779
839
|
}
|
|
780
840
|
addCheck(check) {
|
|
@@ -50,6 +50,14 @@ export declare class SchemaComparator {
|
|
|
50
50
|
* Checks if the other index already fulfills all the indexing and constraint needs of the current one.
|
|
51
51
|
*/
|
|
52
52
|
isIndexFulfilledBy(index1: IndexDef, index2: IndexDef): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Compare advanced column options between two indexes.
|
|
55
|
+
*/
|
|
56
|
+
private compareIndexColumns;
|
|
57
|
+
/**
|
|
58
|
+
* Compare two arrays for equality (order matters).
|
|
59
|
+
*/
|
|
60
|
+
private compareArrays;
|
|
53
61
|
diffExpression(expr1: string, expr2: string): boolean;
|
|
54
62
|
parseJsonDefault(defaultValue?: string | null): Dictionary | string | null;
|
|
55
63
|
hasSameDefaultValue(from: Column, to: Column): boolean;
|