@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.
@@ -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.2",
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.14"
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.2"
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
- protected getFields(): string;
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 (this.options.orderBy) {
306
- this.parts.push(`order by ${this.options.orderBy}`);
307
- }
308
- if (this.options.limit != null) {
309
- this.parts.push(`limit ?`);
310
- this.params.push(this.options.limit);
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 (this.options.offset != null) {
313
- this.parts.push(`offset ?`);
314
- this.params.push(this.options.offset);
322
+ if (wrapCountSubquery) {
323
+ const asKeyword = this.platform.usesAsKeyword() ? ' as ' : ' ';
324
+ this.parts.push(`)${asKeyword}${this.quote('dcnt')}`);
315
325
  }
316
326
  }
317
- getFields() {
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;
@@ -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 type { DatabaseTable } from './DatabaseTable.js';
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(toView.name) && !fromSchema.hasView(viewName)) {
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(toView.name) ?? fromSchema.getView(viewName);
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(fromView.name) && !toSchema.hasView(viewName)) {
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
  /**
@@ -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
- protected getTableName(table: string, schema?: string): string;
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
  }
@@ -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
- type = this.quote(this.getTableName(type, table.schema));
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 };