@mikro-orm/sql 7.0.7-dev.2 → 7.0.7-dev.20
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/AbstractSqlPlatform.d.ts +2 -0
- package/AbstractSqlPlatform.js +4 -0
- package/dialects/mssql/MsSqlNativeQueryBuilder.js +25 -14
- package/dialects/mysql/BaseMySqlPlatform.d.ts +1 -0
- package/dialects/mysql/BaseMySqlPlatform.js +3 -0
- package/dialects/mysql/MySqlSchemaHelper.d.ts +8 -8
- package/dialects/mysql/MySqlSchemaHelper.js +22 -22
- package/dialects/oracledb/OracleNativeQueryBuilder.js +20 -10
- package/dialects/postgresql/PostgreSqlSchemaHelper.d.ts +10 -10
- package/dialects/postgresql/PostgreSqlSchemaHelper.js +38 -26
- package/dialects/sqlite/SqliteSchemaHelper.d.ts +6 -6
- package/dialects/sqlite/SqliteSchemaHelper.js +38 -38
- package/package.json +3 -3
- package/query/NativeQueryBuilder.d.ts +3 -1
- package/query/NativeQueryBuilder.js +32 -11
- package/query/QueryBuilderHelper.js +1 -1
- package/schema/DatabaseSchema.d.ts +2 -2
- package/schema/DatabaseSchema.js +26 -6
- package/schema/SchemaComparator.d.ts +1 -1
- package/schema/SchemaComparator.js +29 -4
- package/schema/SchemaHelper.d.ts +8 -7
- package/schema/SchemaHelper.js +15 -7
- package/schema/SqlSchemaGenerator.d.ts +1 -0
- package/schema/SqlSchemaGenerator.js +21 -25
- package/typings.d.ts +7 -2
|
@@ -34,12 +34,12 @@ export class SqliteSchemaHelper extends SchemaHelper {
|
|
|
34
34
|
return (`select name as table_name from sqlite_master where type = 'table' and name != 'sqlite_sequence' and name != 'geometry_columns' and name != 'spatial_ref_sys' ` +
|
|
35
35
|
`union all select name as table_name from sqlite_temp_master where type = 'table' order by name`);
|
|
36
36
|
}
|
|
37
|
-
async getAllTables(connection, schemas) {
|
|
38
|
-
const databases = await this.getDatabaseList(connection);
|
|
37
|
+
async getAllTables(connection, schemas, ctx) {
|
|
38
|
+
const databases = await this.getDatabaseList(connection, ctx);
|
|
39
39
|
const hasAttachedDbs = databases.length > 1; // More than just 'main'
|
|
40
40
|
// If no attached databases, use original behavior
|
|
41
41
|
if (!hasAttachedDbs && !schemas?.length) {
|
|
42
|
-
return connection.execute(this.getListTablesSQL());
|
|
42
|
+
return connection.execute(this.getListTablesSQL(), [], 'all', ctx);
|
|
43
43
|
}
|
|
44
44
|
// With attached databases, query each one
|
|
45
45
|
const targetSchemas = schemas?.length ? schemas : databases;
|
|
@@ -47,15 +47,15 @@ export class SqliteSchemaHelper extends SchemaHelper {
|
|
|
47
47
|
for (const dbName of targetSchemas) {
|
|
48
48
|
const prefix = this.getSchemaPrefix(dbName);
|
|
49
49
|
const tables = await connection.execute(`select name from ${prefix}sqlite_master where type = 'table' ` +
|
|
50
|
-
`and name != 'sqlite_sequence' and name != 'geometry_columns' and name != 'spatial_ref_sys'
|
|
50
|
+
`and name != 'sqlite_sequence' and name != 'geometry_columns' and name != 'spatial_ref_sys'`, [], 'all', ctx);
|
|
51
51
|
for (const t of tables) {
|
|
52
52
|
allTables.push({ table_name: t.name, schema_name: dbName });
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
return allTables;
|
|
56
56
|
}
|
|
57
|
-
async getNamespaces(connection) {
|
|
58
|
-
return this.getDatabaseList(connection);
|
|
57
|
+
async getNamespaces(connection, ctx) {
|
|
58
|
+
return this.getDatabaseList(connection, ctx);
|
|
59
59
|
}
|
|
60
60
|
getIgnoredViewsCondition() {
|
|
61
61
|
return SPATIALITE_VIEWS.map(v => `name != '${v}'`).join(' and ');
|
|
@@ -63,12 +63,12 @@ export class SqliteSchemaHelper extends SchemaHelper {
|
|
|
63
63
|
getListViewsSQL() {
|
|
64
64
|
return `select name as view_name, sql as view_definition from sqlite_master where type = 'view' and ${this.getIgnoredViewsCondition()} order by name`;
|
|
65
65
|
}
|
|
66
|
-
async loadViews(schema, connection, schemaName) {
|
|
67
|
-
const databases = await this.getDatabaseList(connection);
|
|
66
|
+
async loadViews(schema, connection, schemaName, ctx) {
|
|
67
|
+
const databases = await this.getDatabaseList(connection, ctx);
|
|
68
68
|
const hasAttachedDbs = databases.length > 1; // More than just 'main'
|
|
69
69
|
// If no attached databases and no specific schema, use original behavior
|
|
70
70
|
if (!hasAttachedDbs && !schemaName) {
|
|
71
|
-
const views = await connection.execute(this.getListViewsSQL());
|
|
71
|
+
const views = await connection.execute(this.getListViewsSQL(), [], 'all', ctx);
|
|
72
72
|
for (const view of views) {
|
|
73
73
|
schema.addView(view.view_name, schemaName, this.extractViewDefinition(view.view_definition));
|
|
74
74
|
}
|
|
@@ -79,7 +79,7 @@ export class SqliteSchemaHelper extends SchemaHelper {
|
|
|
79
79
|
const targetDbs = schemaName ? [schemaName] : databases;
|
|
80
80
|
for (const dbName of targetDbs) {
|
|
81
81
|
const prefix = this.getSchemaPrefix(dbName);
|
|
82
|
-
const views = await connection.execute(`select name as view_name, sql as view_definition from ${prefix}sqlite_master where type = 'view' and ${this.getIgnoredViewsCondition()} order by name
|
|
82
|
+
const views = await connection.execute(`select name as view_name, sql as view_definition from ${prefix}sqlite_master where type = 'view' and ${this.getIgnoredViewsCondition()} order by name`, [], 'all', ctx);
|
|
83
83
|
for (const view of views) {
|
|
84
84
|
schema.addView(view.view_name, dbName, this.extractViewDefinition(view.view_definition));
|
|
85
85
|
}
|
|
@@ -92,15 +92,15 @@ export class SqliteSchemaHelper extends SchemaHelper {
|
|
|
92
92
|
/* v8 ignore next */
|
|
93
93
|
return `drop database if exists ${this.quote(name)}`;
|
|
94
94
|
}
|
|
95
|
-
async loadInformationSchema(schema, connection, tables, schemas) {
|
|
95
|
+
async loadInformationSchema(schema, connection, tables, schemas, ctx) {
|
|
96
96
|
for (const t of tables) {
|
|
97
97
|
const table = schema.addTable(t.table_name, t.schema_name, t.table_comment);
|
|
98
|
-
const cols = await this.getColumns(connection, table.name, table.schema);
|
|
99
|
-
const indexes = await this.getIndexes(connection, table.name, table.schema);
|
|
100
|
-
const checks = await this.getChecks(connection, table.name, table.schema);
|
|
101
|
-
const pks = await this.getPrimaryKeys(connection, indexes, table.name, table.schema);
|
|
102
|
-
const fks = await this.getForeignKeys(connection, table.name, table.schema);
|
|
103
|
-
const enums = await this.getEnumDefinitions(connection, table.name, table.schema);
|
|
98
|
+
const cols = await this.getColumns(connection, table.name, table.schema, ctx);
|
|
99
|
+
const indexes = await this.getIndexes(connection, table.name, table.schema, ctx);
|
|
100
|
+
const checks = await this.getChecks(connection, table.name, table.schema, ctx);
|
|
101
|
+
const pks = await this.getPrimaryKeys(connection, indexes, table.name, table.schema, ctx);
|
|
102
|
+
const fks = await this.getForeignKeys(connection, table.name, table.schema, ctx);
|
|
103
|
+
const enums = await this.getEnumDefinitions(connection, table.name, table.schema, ctx);
|
|
104
104
|
table.init(cols, indexes, checks, pks, fks, enums);
|
|
105
105
|
}
|
|
106
106
|
}
|
|
@@ -252,8 +252,8 @@ export class SqliteSchemaHelper extends SchemaHelper {
|
|
|
252
252
|
/**
|
|
253
253
|
* Returns all database names excluding 'temp'.
|
|
254
254
|
*/
|
|
255
|
-
async getDatabaseList(connection) {
|
|
256
|
-
const databases = await connection.execute('pragma database_list');
|
|
255
|
+
async getDatabaseList(connection, ctx) {
|
|
256
|
+
const databases = await connection.execute('pragma database_list', [], 'all', ctx);
|
|
257
257
|
return databases.filter(d => d.name !== 'temp').map(d => d.name);
|
|
258
258
|
}
|
|
259
259
|
/**
|
|
@@ -264,11 +264,11 @@ export class SqliteSchemaHelper extends SchemaHelper {
|
|
|
264
264
|
/* v8 ignore next - fallback for non-standard view definitions */
|
|
265
265
|
return match ? match[1] : viewDefinition;
|
|
266
266
|
}
|
|
267
|
-
async getColumns(connection, tableName, schemaName) {
|
|
267
|
+
async getColumns(connection, tableName, schemaName, ctx) {
|
|
268
268
|
const prefix = this.getSchemaPrefix(schemaName);
|
|
269
|
-
const columns = await connection.execute(`pragma ${prefix}table_xinfo('${tableName}')
|
|
269
|
+
const columns = await connection.execute(`pragma ${prefix}table_xinfo('${tableName}')`, [], 'all', ctx);
|
|
270
270
|
const sql = `select sql from ${prefix}sqlite_master where type = ? and name = ?`;
|
|
271
|
-
const tableDefinition = await connection.execute(sql, ['table', tableName], 'get');
|
|
271
|
+
const tableDefinition = await connection.execute(sql, ['table', tableName], 'get', ctx);
|
|
272
272
|
const composite = columns.reduce((count, col) => count + (col.pk ? 1 : 0), 0) > 1;
|
|
273
273
|
// there can be only one, so naive check like this should be enough
|
|
274
274
|
const hasAutoincrement = tableDefinition.sql.toLowerCase().includes('autoincrement');
|
|
@@ -321,10 +321,10 @@ export class SqliteSchemaHelper extends SchemaHelper {
|
|
|
321
321
|
// everything else is an expression that had its outer parens stripped
|
|
322
322
|
return `(${value})`;
|
|
323
323
|
}
|
|
324
|
-
async getEnumDefinitions(connection, tableName, schemaName) {
|
|
324
|
+
async getEnumDefinitions(connection, tableName, schemaName, ctx) {
|
|
325
325
|
const prefix = this.getSchemaPrefix(schemaName);
|
|
326
326
|
const sql = `select sql from ${prefix}sqlite_master where type = ? and name = ?`;
|
|
327
|
-
const tableDefinition = await connection.execute(sql, ['table', tableName], 'get');
|
|
327
|
+
const tableDefinition = await connection.execute(sql, ['table', tableName], 'get', ctx);
|
|
328
328
|
const checkConstraints = [...(tableDefinition.sql.match(/[`["'][^`\]"']+[`\]"'] text check \(.*?\)/gi) ?? [])];
|
|
329
329
|
return checkConstraints.reduce((o, item) => {
|
|
330
330
|
// check constraints are defined as (note that last closing paren is missing):
|
|
@@ -339,17 +339,17 @@ export class SqliteSchemaHelper extends SchemaHelper {
|
|
|
339
339
|
return o;
|
|
340
340
|
}, {});
|
|
341
341
|
}
|
|
342
|
-
async getPrimaryKeys(connection, indexes, tableName, schemaName) {
|
|
342
|
+
async getPrimaryKeys(connection, indexes, tableName, schemaName, ctx) {
|
|
343
343
|
const prefix = this.getSchemaPrefix(schemaName);
|
|
344
344
|
const sql = `pragma ${prefix}table_info(\`${tableName}\`)`;
|
|
345
|
-
const cols = await connection.execute(sql);
|
|
345
|
+
const cols = await connection.execute(sql, [], 'all', ctx);
|
|
346
346
|
return cols.filter(col => !!col.pk).map(col => col.name);
|
|
347
347
|
}
|
|
348
|
-
async getIndexes(connection, tableName, schemaName) {
|
|
348
|
+
async getIndexes(connection, tableName, schemaName, ctx) {
|
|
349
349
|
const prefix = this.getSchemaPrefix(schemaName);
|
|
350
350
|
const sql = `pragma ${prefix}table_info(\`${tableName}\`)`;
|
|
351
|
-
const cols = await connection.execute(sql);
|
|
352
|
-
const indexes = await connection.execute(`pragma ${prefix}index_list(\`${tableName}\`)
|
|
351
|
+
const cols = await connection.execute(sql, [], 'all', ctx);
|
|
352
|
+
const indexes = await connection.execute(`pragma ${prefix}index_list(\`${tableName}\`)`, [], 'all', ctx);
|
|
353
353
|
const ret = [];
|
|
354
354
|
for (const col of cols.filter(c => c.pk)) {
|
|
355
355
|
ret.push({
|
|
@@ -361,7 +361,7 @@ export class SqliteSchemaHelper extends SchemaHelper {
|
|
|
361
361
|
});
|
|
362
362
|
}
|
|
363
363
|
for (const index of indexes.filter(index => !this.isImplicitIndex(index.name))) {
|
|
364
|
-
const res = await connection.execute(`pragma ${prefix}index_info(\`${index.name}\`)
|
|
364
|
+
const res = await connection.execute(`pragma ${prefix}index_info(\`${index.name}\`)`, [], 'all', ctx);
|
|
365
365
|
ret.push(...res.map(row => ({
|
|
366
366
|
columnNames: [row.name],
|
|
367
367
|
keyName: index.name,
|
|
@@ -372,8 +372,8 @@ export class SqliteSchemaHelper extends SchemaHelper {
|
|
|
372
372
|
}
|
|
373
373
|
return this.mapIndexes(ret);
|
|
374
374
|
}
|
|
375
|
-
async getChecks(connection, tableName, schemaName) {
|
|
376
|
-
const { columns, constraints } = await this.getColumnDefinitions(connection, tableName, schemaName);
|
|
375
|
+
async getChecks(connection, tableName, schemaName, ctx) {
|
|
376
|
+
const { columns, constraints } = await this.getColumnDefinitions(connection, tableName, schemaName, ctx);
|
|
377
377
|
const checks = [];
|
|
378
378
|
for (const key of Object.keys(columns)) {
|
|
379
379
|
const column = columns[key];
|
|
@@ -399,17 +399,17 @@ export class SqliteSchemaHelper extends SchemaHelper {
|
|
|
399
399
|
}
|
|
400
400
|
return checks;
|
|
401
401
|
}
|
|
402
|
-
async getColumnDefinitions(connection, tableName, schemaName) {
|
|
402
|
+
async getColumnDefinitions(connection, tableName, schemaName, ctx) {
|
|
403
403
|
const prefix = this.getSchemaPrefix(schemaName);
|
|
404
|
-
const columns = await connection.execute(`pragma ${prefix}table_xinfo('${tableName}')
|
|
404
|
+
const columns = await connection.execute(`pragma ${prefix}table_xinfo('${tableName}')`, [], 'all', ctx);
|
|
405
405
|
const sql = `select sql from ${prefix}sqlite_master where type = ? and name = ?`;
|
|
406
|
-
const tableDefinition = await connection.execute(sql, ['table', tableName], 'get');
|
|
406
|
+
const tableDefinition = await connection.execute(sql, ['table', tableName], 'get', ctx);
|
|
407
407
|
return this.parseTableDefinition(tableDefinition.sql, columns);
|
|
408
408
|
}
|
|
409
|
-
async getForeignKeys(connection, tableName, schemaName) {
|
|
410
|
-
const { constraints } = await this.getColumnDefinitions(connection, tableName, schemaName);
|
|
409
|
+
async getForeignKeys(connection, tableName, schemaName, ctx) {
|
|
410
|
+
const { constraints } = await this.getColumnDefinitions(connection, tableName, schemaName, ctx);
|
|
411
411
|
const prefix = this.getSchemaPrefix(schemaName);
|
|
412
|
-
const fks = await connection.execute(`pragma ${prefix}foreign_key_list(\`${tableName}\`)
|
|
412
|
+
const fks = await connection.execute(`pragma ${prefix}foreign_key_list(\`${tableName}\`)`, [], 'all', ctx);
|
|
413
413
|
const qualifiedTableName = schemaName ? `${schemaName}.${tableName}` : tableName;
|
|
414
414
|
return fks.reduce((ret, fk) => {
|
|
415
415
|
const constraintName = this.platform.getIndexName(tableName, [fk.from], 'foreign');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikro-orm/sql",
|
|
3
|
-
"version": "7.0.7-dev.
|
|
3
|
+
"version": "7.0.7-dev.20",
|
|
4
4
|
"description": "TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, PostgreSQL and SQLite databases as well as usage with vanilla JavaScript.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"data-mapper",
|
|
@@ -47,13 +47,13 @@
|
|
|
47
47
|
"copy": "node ../../scripts/copy.mjs"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"kysely": "0.28.
|
|
50
|
+
"kysely": "0.28.15"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@mikro-orm/core": "^7.0.6"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
|
-
"@mikro-orm/core": "7.0.7-dev.
|
|
56
|
+
"@mikro-orm/core": "7.0.7-dev.20"
|
|
57
57
|
},
|
|
58
58
|
"engines": {
|
|
59
59
|
"node": ">= 22.17.0"
|
|
@@ -121,7 +121,9 @@ export declare class NativeQueryBuilder implements Subquery {
|
|
|
121
121
|
as(alias: string): this;
|
|
122
122
|
toRaw(): RawQueryFragment;
|
|
123
123
|
protected compileSelect(): void;
|
|
124
|
-
|
|
124
|
+
/** Whether this COUNT query needs a subquery wrapper for multi-column distinct. */
|
|
125
|
+
protected needsCountSubquery(): boolean;
|
|
126
|
+
protected getFields(countSubquery?: boolean): string;
|
|
125
127
|
protected compileInsert(): void;
|
|
126
128
|
protected addOutputClause(type: 'inserted' | 'deleted'): void;
|
|
127
129
|
protected processInsertData(): string[];
|
|
@@ -281,9 +281,13 @@ export class NativeQueryBuilder {
|
|
|
281
281
|
return raw(sql, params);
|
|
282
282
|
}
|
|
283
283
|
compileSelect() {
|
|
284
|
+
const wrapCountSubquery = this.needsCountSubquery();
|
|
285
|
+
if (wrapCountSubquery) {
|
|
286
|
+
this.parts.push(`select count(*) as ${this.quote('count')} from (`);
|
|
287
|
+
}
|
|
284
288
|
this.parts.push('select');
|
|
285
289
|
this.addHintComment();
|
|
286
|
-
this.parts.push(`${this.getFields()} from ${this.getTableName()}`);
|
|
290
|
+
this.parts.push(`${this.getFields(wrapCountSubquery)} from ${this.getTableName()}`);
|
|
287
291
|
if (this.options.joins) {
|
|
288
292
|
for (const join of this.options.joins) {
|
|
289
293
|
this.parts.push(join.sql);
|
|
@@ -302,23 +306,40 @@ export class NativeQueryBuilder {
|
|
|
302
306
|
this.parts.push(`having ${this.options.having.sql}`);
|
|
303
307
|
this.params.push(...this.options.having.params);
|
|
304
308
|
}
|
|
305
|
-
if (
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
this.
|
|
310
|
-
|
|
309
|
+
if (!wrapCountSubquery) {
|
|
310
|
+
if (this.options.orderBy) {
|
|
311
|
+
this.parts.push(`order by ${this.options.orderBy}`);
|
|
312
|
+
}
|
|
313
|
+
if (this.options.limit != null) {
|
|
314
|
+
this.parts.push(`limit ?`);
|
|
315
|
+
this.params.push(this.options.limit);
|
|
316
|
+
}
|
|
317
|
+
if (this.options.offset != null) {
|
|
318
|
+
this.parts.push(`offset ?`);
|
|
319
|
+
this.params.push(this.options.offset);
|
|
320
|
+
}
|
|
311
321
|
}
|
|
312
|
-
if (
|
|
313
|
-
this.
|
|
314
|
-
this.
|
|
322
|
+
if (wrapCountSubquery) {
|
|
323
|
+
const asKeyword = this.platform.usesAsKeyword() ? ' as ' : ' ';
|
|
324
|
+
this.parts.push(`)${asKeyword}${this.quote('dcnt')}`);
|
|
315
325
|
}
|
|
316
326
|
}
|
|
317
|
-
|
|
327
|
+
/** Whether this COUNT query needs a subquery wrapper for multi-column distinct. */
|
|
328
|
+
needsCountSubquery() {
|
|
329
|
+
return (this.type === QueryType.COUNT &&
|
|
330
|
+
!!this.options.distinct &&
|
|
331
|
+
this.options.select.length > 1 &&
|
|
332
|
+
!this.platform.supportsMultiColumnCountDistinct());
|
|
333
|
+
}
|
|
334
|
+
getFields(countSubquery) {
|
|
318
335
|
if (!this.options.select || this.options.select.length === 0) {
|
|
319
336
|
throw new Error('No fields selected');
|
|
320
337
|
}
|
|
321
338
|
let fields = this.options.select.map(field => this.quote(field)).join(', ');
|
|
339
|
+
// count subquery emits just `distinct col1, col2` — the outer wrapper adds count(*)
|
|
340
|
+
if (countSubquery) {
|
|
341
|
+
return `distinct ${fields}`;
|
|
342
|
+
}
|
|
322
343
|
if (this.options.distinct) {
|
|
323
344
|
fields = `distinct ${fields}`;
|
|
324
345
|
}
|
|
@@ -572,7 +572,7 @@ export class QueryBuilderHelper {
|
|
|
572
572
|
else if (value[op] instanceof Raw || typeof value[op]?.toRaw === 'function') {
|
|
573
573
|
const query = value[op] instanceof Raw ? value[op] : value[op].toRaw();
|
|
574
574
|
const mappedKey = this.mapper(key, type, query, null);
|
|
575
|
-
let sql = query.sql;
|
|
575
|
+
let sql = query.sql.replaceAll(ALIAS_REPLACEMENT, this.#alias);
|
|
576
576
|
if (['$in', '$nin'].includes(op)) {
|
|
577
577
|
sql = `(${sql})`;
|
|
578
578
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Configuration, type Dictionary, type EntityMetadata } from '@mikro-orm/core';
|
|
1
|
+
import { type Configuration, type Dictionary, type EntityMetadata, type Transaction } from '@mikro-orm/core';
|
|
2
2
|
import { DatabaseTable } from './DatabaseTable.js';
|
|
3
3
|
import type { AbstractSqlConnection } from '../AbstractSqlConnection.js';
|
|
4
4
|
import type { DatabaseView } from '../typings.js';
|
|
@@ -42,7 +42,7 @@ export declare class DatabaseSchema {
|
|
|
42
42
|
hasNamespace(namespace: string): boolean;
|
|
43
43
|
hasNativeEnum(name: string): boolean;
|
|
44
44
|
getNamespaces(): string[];
|
|
45
|
-
static create(connection: AbstractSqlConnection, platform: AbstractSqlPlatform, config: Configuration, schemaName?: string, schemas?: string[], takeTables?: (string | RegExp)[], skipTables?: (string | RegExp)[], skipViews?: (string | RegExp)[]): Promise<DatabaseSchema>;
|
|
45
|
+
static create(connection: AbstractSqlConnection, platform: AbstractSqlPlatform, config: Configuration, schemaName?: string, schemas?: string[], takeTables?: (string | RegExp)[], skipTables?: (string | RegExp)[], skipViews?: (string | RegExp)[], ctx?: Transaction): Promise<DatabaseSchema>;
|
|
46
46
|
static fromMetadata(metadata: EntityMetadata[], platform: AbstractSqlPlatform, config: Configuration, schemaName?: string, em?: any): DatabaseSchema;
|
|
47
47
|
private static getViewDefinition;
|
|
48
48
|
private static getSchemaName;
|
package/schema/DatabaseSchema.js
CHANGED
|
@@ -87,9 +87,9 @@ export class DatabaseSchema {
|
|
|
87
87
|
getNamespaces() {
|
|
88
88
|
return [...this.#namespaces];
|
|
89
89
|
}
|
|
90
|
-
static async create(connection, platform, config, schemaName, schemas, takeTables, skipTables, skipViews) {
|
|
90
|
+
static async create(connection, platform, config, schemaName, schemas, takeTables, skipTables, skipViews, ctx) {
|
|
91
91
|
const schema = new DatabaseSchema(platform, schemaName ?? config.get('schema') ?? platform.getDefaultSchemaName());
|
|
92
|
-
const allTables = await platform.getSchemaHelper().getAllTables(connection, schemas);
|
|
92
|
+
const allTables = await platform.getSchemaHelper().getAllTables(connection, schemas, ctx);
|
|
93
93
|
const parts = config.get('migrations').tableName.split('.');
|
|
94
94
|
const migrationsTableName = parts[1] ?? parts[0];
|
|
95
95
|
const migrationsSchemaName = parts.length > 1 ? parts[0] : config.get('schema', platform.getDefaultSchemaName());
|
|
@@ -97,12 +97,12 @@ export class DatabaseSchema {
|
|
|
97
97
|
(t.table_name !== migrationsTableName || (t.schema_name && t.schema_name !== migrationsSchemaName)));
|
|
98
98
|
await platform
|
|
99
99
|
.getSchemaHelper()
|
|
100
|
-
.loadInformationSchema(schema, connection, tables, schemas && schemas.length > 0 ? schemas : undefined);
|
|
100
|
+
.loadInformationSchema(schema, connection, tables, schemas && schemas.length > 0 ? schemas : undefined, ctx);
|
|
101
101
|
// Load views from database
|
|
102
|
-
await platform.getSchemaHelper().loadViews(schema, connection);
|
|
102
|
+
await platform.getSchemaHelper().loadViews(schema, connection, schemaName, ctx);
|
|
103
103
|
// Load materialized views (PostgreSQL only)
|
|
104
104
|
if (platform.supportsMaterializedViews()) {
|
|
105
|
-
await platform.getSchemaHelper().loadMaterializedViews(schema, connection, schemaName);
|
|
105
|
+
await platform.getSchemaHelper().loadMaterializedViews(schema, connection, schemaName, ctx);
|
|
106
106
|
}
|
|
107
107
|
// Filter out skipped views
|
|
108
108
|
if (skipViews && skipViews.length > 0) {
|
|
@@ -147,7 +147,27 @@ export class DatabaseSchema {
|
|
|
147
147
|
if (meta.view) {
|
|
148
148
|
const viewDefinition = this.getViewDefinition(meta, em, platform);
|
|
149
149
|
if (viewDefinition) {
|
|
150
|
-
schema.addView(meta.collection, this.getSchemaName(meta, config, schemaName), viewDefinition, meta.materialized, meta.withData);
|
|
150
|
+
const view = schema.addView(meta.collection, this.getSchemaName(meta, config, schemaName), viewDefinition, meta.materialized, meta.withData);
|
|
151
|
+
if (meta.materialized) {
|
|
152
|
+
// Use a DatabaseTable to resolve property names → field names for indexes.
|
|
153
|
+
// addIndex only needs meta + table name, not actual columns.
|
|
154
|
+
const indexTable = new DatabaseTable(platform, meta.collection, this.getSchemaName(meta, config, schemaName));
|
|
155
|
+
meta.indexes.forEach(index => indexTable.addIndex(meta, index, 'index'));
|
|
156
|
+
meta.uniques.forEach(index => indexTable.addIndex(meta, index, 'unique'));
|
|
157
|
+
const pkProps = meta.props.filter(prop => prop.primary);
|
|
158
|
+
indexTable.addIndex(meta, { properties: pkProps.map(prop => prop.name) }, 'primary');
|
|
159
|
+
// Materialized views don't have primary keys or constraints in the DB,
|
|
160
|
+
// convert to match what PostgreSQL stores.
|
|
161
|
+
view.indexes = indexTable.getIndexes().map(idx => {
|
|
162
|
+
if (idx.primary) {
|
|
163
|
+
return { ...idx, primary: false, unique: true, constraint: false };
|
|
164
|
+
}
|
|
165
|
+
if (idx.constraint) {
|
|
166
|
+
return { ...idx, constraint: false };
|
|
167
|
+
}
|
|
168
|
+
return idx;
|
|
169
|
+
});
|
|
170
|
+
}
|
|
151
171
|
}
|
|
152
172
|
continue;
|
|
153
173
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type Dictionary } from '@mikro-orm/core';
|
|
2
2
|
import type { Column, ForeignKey, IndexDef, SchemaDifference, TableDifference } from '../typings.js';
|
|
3
3
|
import type { DatabaseSchema } from './DatabaseSchema.js';
|
|
4
|
-
import
|
|
4
|
+
import { DatabaseTable } from './DatabaseTable.js';
|
|
5
5
|
import type { AbstractSqlPlatform } from '../AbstractSqlPlatform.js';
|
|
6
6
|
/**
|
|
7
7
|
* Compares two Schemas and return an instance of SchemaDifference.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ArrayType, BooleanType, DateTimeType, inspect, JsonType, parseJsonSafe, Utils, } from '@mikro-orm/core';
|
|
2
|
+
import { DatabaseTable } from './DatabaseTable.js';
|
|
2
3
|
/**
|
|
3
4
|
* Compares two Schemas and return an instance of SchemaDifference.
|
|
4
5
|
*/
|
|
@@ -114,15 +115,16 @@ export class SchemaComparator {
|
|
|
114
115
|
}
|
|
115
116
|
}
|
|
116
117
|
}
|
|
117
|
-
// Compare views
|
|
118
|
+
// Compare views — prefer schema-qualified lookup to avoid matching
|
|
119
|
+
// views with the same name in different schemas
|
|
118
120
|
for (const toView of toSchema.getViews()) {
|
|
119
121
|
const viewName = toView.schema ? `${toView.schema}.${toView.name}` : toView.name;
|
|
120
|
-
if (!fromSchema.hasView(
|
|
122
|
+
if (!fromSchema.hasView(viewName) && !fromSchema.hasView(toView.name)) {
|
|
121
123
|
diff.newViews[viewName] = toView;
|
|
122
124
|
this.log(`view ${viewName} added`);
|
|
123
125
|
}
|
|
124
126
|
else {
|
|
125
|
-
const fromView = fromSchema.getView(
|
|
127
|
+
const fromView = fromSchema.getView(viewName) ?? fromSchema.getView(toView.name);
|
|
126
128
|
if (fromView && this.diffViewExpression(fromView.definition, toView.definition)) {
|
|
127
129
|
diff.changedViews[viewName] = { from: fromView, to: toView };
|
|
128
130
|
this.log(`view ${viewName} changed`);
|
|
@@ -132,11 +134,34 @@ export class SchemaComparator {
|
|
|
132
134
|
// Check for removed views
|
|
133
135
|
for (const fromView of fromSchema.getViews()) {
|
|
134
136
|
const viewName = fromView.schema ? `${fromView.schema}.${fromView.name}` : fromView.name;
|
|
135
|
-
if (!toSchema.hasView(
|
|
137
|
+
if (!toSchema.hasView(viewName) && !toSchema.hasView(fromView.name)) {
|
|
136
138
|
diff.removedViews[viewName] = fromView;
|
|
137
139
|
this.log(`view ${viewName} removed`);
|
|
138
140
|
}
|
|
139
141
|
}
|
|
142
|
+
// Diff materialized view indexes using the existing table diff infrastructure.
|
|
143
|
+
// Build transient DatabaseTable objects from view indexes so diffTable handles
|
|
144
|
+
// added/removed/changed/renamed index detection and alterTable emits correct DDL.
|
|
145
|
+
for (const toView of toSchema.getViews()) {
|
|
146
|
+
if (!toView.materialized || toView.withData === false) {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
const viewName = toView.schema ? `${toView.schema}.${toView.name}` : toView.name;
|
|
150
|
+
// New or definition-changed views have indexes handled during create/recreate
|
|
151
|
+
if (diff.newViews[viewName] || diff.changedViews[viewName]) {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
// If we get here, the view exists in fromSchema (otherwise it would be in diff.newViews)
|
|
155
|
+
const fromView = fromSchema.getView(viewName) ?? fromSchema.getView(toView.name);
|
|
156
|
+
const fromTable = new DatabaseTable(this.#platform, fromView.name, fromView.schema);
|
|
157
|
+
fromTable.init([], fromView.indexes ?? [], [], []);
|
|
158
|
+
const toTable = new DatabaseTable(this.#platform, toView.name, toView.schema);
|
|
159
|
+
toTable.init([], toView.indexes ?? [], [], []);
|
|
160
|
+
const tableDiff = this.diffTable(fromTable, toTable);
|
|
161
|
+
if (tableDiff) {
|
|
162
|
+
diff.changedTables[viewName] = tableDiff;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
140
165
|
return diff;
|
|
141
166
|
}
|
|
142
167
|
/**
|
package/schema/SchemaHelper.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Connection, type Dictionary, type Options, RawQueryFragment } from '@mikro-orm/core';
|
|
1
|
+
import { type Connection, type Dictionary, type Options, type Transaction, RawQueryFragment } from '@mikro-orm/core';
|
|
2
2
|
import type { AbstractSqlConnection } from '../AbstractSqlConnection.js';
|
|
3
3
|
import type { AbstractSqlPlatform } from '../AbstractSqlPlatform.js';
|
|
4
4
|
import type { CheckDef, Column, ForeignKey, IndexDef, Table, TableDifference } from '../typings.js';
|
|
@@ -26,13 +26,13 @@ export declare abstract class SchemaHelper {
|
|
|
26
26
|
getDropNativeEnumSQL(name: string, schema?: string): string;
|
|
27
27
|
getAlterNativeEnumSQL(name: string, schema?: string, value?: string, items?: string[], oldItems?: string[]): string;
|
|
28
28
|
/** Loads table metadata (columns, indexes, foreign keys) from the database information schema. */
|
|
29
|
-
abstract loadInformationSchema(schema: DatabaseSchema, connection: AbstractSqlConnection, tables: Table[], schemas?: string[]): Promise<void>;
|
|
29
|
+
abstract loadInformationSchema(schema: DatabaseSchema, connection: AbstractSqlConnection, tables: Table[], schemas?: string[], ctx?: Transaction): Promise<void>;
|
|
30
30
|
/** Returns the SQL query to list all tables in the database. */
|
|
31
31
|
getListTablesSQL(): string;
|
|
32
32
|
/** Retrieves all tables from the database. */
|
|
33
|
-
getAllTables(connection: AbstractSqlConnection, schemas?: string[]): Promise<Table[]>;
|
|
33
|
+
getAllTables(connection: AbstractSqlConnection, schemas?: string[], ctx?: Transaction): Promise<Table[]>;
|
|
34
34
|
getListViewsSQL(): string;
|
|
35
|
-
loadViews(schema: DatabaseSchema, connection: AbstractSqlConnection, schemaName?: string): Promise<void>;
|
|
35
|
+
loadViews(schema: DatabaseSchema, connection: AbstractSqlConnection, schemaName?: string, ctx?: Transaction): Promise<void>;
|
|
36
36
|
/** Returns SQL to rename a column in a table. */
|
|
37
37
|
getRenameColumnSQL(tableName: string, oldColumnName: string, to: Column, schemaName?: string): string;
|
|
38
38
|
/** Returns SQL to create an index on a table. */
|
|
@@ -61,7 +61,7 @@ export declare abstract class SchemaHelper {
|
|
|
61
61
|
getPreAlterTable(tableDiff: TableDifference, safe: boolean): string[];
|
|
62
62
|
getPostAlterTable(tableDiff: TableDifference, safe: boolean): string[];
|
|
63
63
|
getChangeColumnCommentSQL(tableName: string, to: Column, schemaName?: string): string;
|
|
64
|
-
getNamespaces(connection: AbstractSqlConnection): Promise<string[]>;
|
|
64
|
+
getNamespaces(connection: AbstractSqlConnection, ctx?: Transaction): Promise<string[]>;
|
|
65
65
|
protected mapIndexes(indexes: IndexDef[]): Promise<IndexDef[]>;
|
|
66
66
|
mapForeignKeys(fks: any[], tableName: string, schemaName?: string): Dictionary;
|
|
67
67
|
normalizeDefaultValue(defaultValue: string | RawQueryFragment, length?: number, defaultValues?: Dictionary<string[]>): string | number;
|
|
@@ -84,7 +84,8 @@ export declare abstract class SchemaHelper {
|
|
|
84
84
|
getReferencedTableName(referencedTableName: string, schema?: string): string;
|
|
85
85
|
createIndex(index: IndexDef, table: DatabaseTable, createPrimary?: boolean): string;
|
|
86
86
|
createCheck(table: DatabaseTable, check: CheckDef): string;
|
|
87
|
-
|
|
87
|
+
/** @internal */
|
|
88
|
+
getTableName(table: string, schema?: string): string;
|
|
88
89
|
getTablesGroupedBySchemas(tables: Table[]): Map<string | undefined, Table[]>;
|
|
89
90
|
get options(): NonNullable<Options['schemaGenerator']>;
|
|
90
91
|
protected processComment(comment: string): string;
|
|
@@ -100,5 +101,5 @@ export declare abstract class SchemaHelper {
|
|
|
100
101
|
dropMaterializedViewIfExists(name: string, schema?: string): string;
|
|
101
102
|
refreshMaterializedView(name: string, schema?: string, concurrently?: boolean): string;
|
|
102
103
|
getListMaterializedViewsSQL(): string;
|
|
103
|
-
loadMaterializedViews(schema: DatabaseSchema, connection: AbstractSqlConnection, schemaName?: string): Promise<void>;
|
|
104
|
+
loadMaterializedViews(schema: DatabaseSchema, connection: AbstractSqlConnection, schemaName?: string, ctx?: Transaction): Promise<void>;
|
|
104
105
|
}
|
package/schema/SchemaHelper.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RawQueryFragment, Utils } from '@mikro-orm/core';
|
|
1
|
+
import { RawQueryFragment, Utils, } from '@mikro-orm/core';
|
|
2
2
|
/** Base class for database-specific schema helpers. Provides SQL generation for DDL operations. */
|
|
3
3
|
export class SchemaHelper {
|
|
4
4
|
platform;
|
|
@@ -72,13 +72,13 @@ export class SchemaHelper {
|
|
|
72
72
|
throw new Error('Not supported by given driver');
|
|
73
73
|
}
|
|
74
74
|
/** Retrieves all tables from the database. */
|
|
75
|
-
async getAllTables(connection, schemas) {
|
|
76
|
-
return connection.execute(this.getListTablesSQL());
|
|
75
|
+
async getAllTables(connection, schemas, ctx) {
|
|
76
|
+
return connection.execute(this.getListTablesSQL(), [], 'all', ctx);
|
|
77
77
|
}
|
|
78
78
|
getListViewsSQL() {
|
|
79
79
|
throw new Error('Not supported by given driver');
|
|
80
80
|
}
|
|
81
|
-
async loadViews(schema, connection, schemaName) {
|
|
81
|
+
async loadViews(schema, connection, schemaName, ctx) {
|
|
82
82
|
throw new Error('Not supported by given driver');
|
|
83
83
|
}
|
|
84
84
|
/** Returns SQL to rename a column in a table. */
|
|
@@ -302,7 +302,14 @@ export class SchemaHelper {
|
|
|
302
302
|
if (changedProperties.has('type')) {
|
|
303
303
|
let type = column.type + (column.generated ? ` generated always as ${column.generated}` : '');
|
|
304
304
|
if (column.nativeEnumName) {
|
|
305
|
-
|
|
305
|
+
const parts = type.split('.');
|
|
306
|
+
if (parts.length === 2 && parts[0] === '*' && table.schema) {
|
|
307
|
+
type = `${table.schema}.${parts[1]}`;
|
|
308
|
+
}
|
|
309
|
+
else if (parts.length === 1) {
|
|
310
|
+
type = this.getTableName(type, table.schema);
|
|
311
|
+
}
|
|
312
|
+
type = this.quote(type);
|
|
306
313
|
}
|
|
307
314
|
sql.push(`alter table ${table.getQuotedName()} alter column ${this.quote(column.name)} type ${type + this.castColumn(column.name, type)}`);
|
|
308
315
|
}
|
|
@@ -362,7 +369,7 @@ export class SchemaHelper {
|
|
|
362
369
|
getChangeColumnCommentSQL(tableName, to, schemaName) {
|
|
363
370
|
return '';
|
|
364
371
|
}
|
|
365
|
-
async getNamespaces(connection) {
|
|
372
|
+
async getNamespaces(connection, ctx) {
|
|
366
373
|
return [];
|
|
367
374
|
}
|
|
368
375
|
async mapIndexes(indexes) {
|
|
@@ -593,6 +600,7 @@ export class SchemaHelper {
|
|
|
593
600
|
createCheck(table, check) {
|
|
594
601
|
return `alter table ${table.getQuotedName()} add constraint ${this.quote(check.name)} check (${check.expression})`;
|
|
595
602
|
}
|
|
603
|
+
/** @internal */
|
|
596
604
|
getTableName(table, schema) {
|
|
597
605
|
if (schema && schema !== this.platform.getDefaultSchemaName()) {
|
|
598
606
|
return `${schema}.${table}`;
|
|
@@ -662,7 +670,7 @@ export class SchemaHelper {
|
|
|
662
670
|
getListMaterializedViewsSQL() {
|
|
663
671
|
throw new Error('Not supported by given driver');
|
|
664
672
|
}
|
|
665
|
-
async loadMaterializedViews(schema, connection, schemaName) {
|
|
673
|
+
async loadMaterializedViews(schema, connection, schemaName, ctx) {
|
|
666
674
|
throw new Error('Not supported by given driver');
|
|
667
675
|
}
|
|
668
676
|
}
|
|
@@ -62,6 +62,7 @@ export declare class SqlSchemaGenerator extends AbstractSchemaGenerator<Abstract
|
|
|
62
62
|
* Uses topological sort based on view definition string matching.
|
|
63
63
|
*/
|
|
64
64
|
private sortViewsByDependencies;
|
|
65
|
+
private appendViewCreation;
|
|
65
66
|
private escapeRegExp;
|
|
66
67
|
}
|
|
67
68
|
export { SqlSchemaGenerator as SchemaGenerator };
|