@mikro-orm/sql 7.0.7-dev.10 → 7.0.7-dev.11
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/dialects/postgresql/PostgreSqlSchemaHelper.js +13 -4
- package/package.json +2 -2
- package/schema/DatabaseSchema.js +21 -1
- package/schema/SchemaComparator.d.ts +1 -1
- package/schema/SchemaComparator.js +29 -4
- package/schema/SchemaHelper.d.ts +2 -1
- package/schema/SchemaHelper.js +1 -0
- package/schema/SqlSchemaGenerator.d.ts +1 -0
- package/schema/SqlSchemaGenerator.js +19 -23
- package/typings.d.ts +2 -0
|
@@ -51,17 +51,26 @@ export class PostgreSqlSchemaHelper extends SchemaHelper {
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
getListMaterializedViewsSQL() {
|
|
54
|
-
return (`select matviewname as view_name, schemaname as schema_name, definition as view_definition ` +
|
|
54
|
+
return (`select matviewname as view_name, schemaname as schema_name, definition as view_definition, ispopulated as is_populated ` +
|
|
55
55
|
`from pg_matviews ` +
|
|
56
56
|
`where ${this.getIgnoredNamespacesConditionSQL('schemaname')} ` +
|
|
57
57
|
`order by matviewname`);
|
|
58
58
|
}
|
|
59
59
|
async loadMaterializedViews(schema, connection, schemaName) {
|
|
60
60
|
const views = await connection.execute(this.getListMaterializedViewsSQL());
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
if (views.length === 0) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const tables = views.map(v => ({ table_name: v.view_name, schema_name: v.schema_name }));
|
|
65
|
+
const indexes = await this.getAllIndexes(connection, tables);
|
|
66
|
+
for (let i = 0; i < views.length; i++) {
|
|
67
|
+
const definition = views[i].view_definition?.trim().replace(/;$/, '') ?? '';
|
|
63
68
|
if (definition) {
|
|
64
|
-
schema.addView(
|
|
69
|
+
const dbView = schema.addView(views[i].view_name, views[i].schema_name, definition, true, views[i].is_populated);
|
|
70
|
+
const key = this.getTableKey(tables[i]);
|
|
71
|
+
if (indexes[key]?.length) {
|
|
72
|
+
dbView.indexes = indexes[key];
|
|
73
|
+
}
|
|
65
74
|
}
|
|
66
75
|
}
|
|
67
76
|
}
|
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.11",
|
|
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",
|
|
@@ -53,7 +53,7 @@
|
|
|
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.11"
|
|
57
57
|
},
|
|
58
58
|
"engines": {
|
|
59
59
|
"node": ">= 22.17.0"
|
package/schema/DatabaseSchema.js
CHANGED
|
@@ -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
|
@@ -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;
|
package/schema/SchemaHelper.js
CHANGED
|
@@ -593,6 +593,7 @@ export class SchemaHelper {
|
|
|
593
593
|
createCheck(table, check) {
|
|
594
594
|
return `alter table ${table.getQuotedName()} add constraint ${this.quote(check.name)} check (${check.expression})`;
|
|
595
595
|
}
|
|
596
|
+
/** @internal */
|
|
596
597
|
getTableName(table, schema) {
|
|
597
598
|
if (schema && schema !== this.platform.getDefaultSchemaName()) {
|
|
598
599
|
return `${schema}.${table}`;
|
|
@@ -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 };
|
|
@@ -89,16 +89,9 @@ export class SqlSchemaGenerator extends AbstractSchemaGenerator {
|
|
|
89
89
|
this.append(ret, fks, true);
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
|
-
// Create views after tables (views may depend on tables)
|
|
93
|
-
// Sort views by dependencies (views depending on other views come later)
|
|
94
92
|
const sortedViews = this.sortViewsByDependencies(toSchema.getViews());
|
|
95
93
|
for (const view of sortedViews) {
|
|
96
|
-
|
|
97
|
-
this.append(ret, this.helper.createMaterializedView(view.name, view.schema, view.definition, view.withData ?? true));
|
|
98
|
-
}
|
|
99
|
-
else {
|
|
100
|
-
this.append(ret, this.helper.createView(view.name, view.schema, view.definition), true);
|
|
101
|
-
}
|
|
94
|
+
this.appendViewCreation(ret, view);
|
|
102
95
|
}
|
|
103
96
|
return this.wrapSchema(ret, options);
|
|
104
97
|
}
|
|
@@ -338,27 +331,14 @@ export class SqlSchemaGenerator extends AbstractSchemaGenerator {
|
|
|
338
331
|
this.append(ret, sql);
|
|
339
332
|
}
|
|
340
333
|
}
|
|
341
|
-
// Create new views after all table changes are done
|
|
342
|
-
// Sort views by dependencies (views depending on other views come later)
|
|
343
334
|
const sortedNewViews = this.sortViewsByDependencies(Object.values(schemaDiff.newViews));
|
|
344
335
|
for (const view of sortedNewViews) {
|
|
345
|
-
|
|
346
|
-
this.append(ret, this.helper.createMaterializedView(view.name, view.schema, view.definition, view.withData ?? true));
|
|
347
|
-
}
|
|
348
|
-
else {
|
|
349
|
-
this.append(ret, this.helper.createView(view.name, view.schema, view.definition), true);
|
|
350
|
-
}
|
|
336
|
+
this.appendViewCreation(ret, view);
|
|
351
337
|
}
|
|
352
|
-
// Recreate changed views (also sorted by dependencies)
|
|
353
338
|
const changedViews = Object.values(schemaDiff.changedViews).map(v => v.to);
|
|
354
339
|
const sortedChangedViews = this.sortViewsByDependencies(changedViews);
|
|
355
340
|
for (const view of sortedChangedViews) {
|
|
356
|
-
|
|
357
|
-
this.append(ret, this.helper.createMaterializedView(view.name, view.schema, view.definition, view.withData ?? true));
|
|
358
|
-
}
|
|
359
|
-
else {
|
|
360
|
-
this.append(ret, this.helper.createView(view.name, view.schema, view.definition), true);
|
|
361
|
-
}
|
|
341
|
+
this.appendViewCreation(ret, view);
|
|
362
342
|
}
|
|
363
343
|
return this.wrapSchema(ret, options);
|
|
364
344
|
}
|
|
@@ -511,6 +491,22 @@ export class SqlSchemaGenerator extends AbstractSchemaGenerator {
|
|
|
511
491
|
// Sort and map back to views
|
|
512
492
|
return calc.sort().map(index => indexToView.get(index));
|
|
513
493
|
}
|
|
494
|
+
appendViewCreation(ret, view) {
|
|
495
|
+
if (view.materialized) {
|
|
496
|
+
this.append(ret, this.helper.createMaterializedView(view.name, view.schema, view.definition, view.withData ?? true));
|
|
497
|
+
// Skip indexes for WITH NO DATA views — they have no data to index yet.
|
|
498
|
+
// Indexes will be created on the next schema:update after REFRESH populates data.
|
|
499
|
+
if (view.withData !== false) {
|
|
500
|
+
const viewName = this.helper.getTableName(view.name, view.schema);
|
|
501
|
+
for (const index of view.indexes ?? []) {
|
|
502
|
+
this.append(ret, this.helper.getCreateIndexSQL(viewName, index));
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
else {
|
|
507
|
+
this.append(ret, this.helper.createView(view.name, view.schema, view.definition), true);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
514
510
|
escapeRegExp(string) {
|
|
515
511
|
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
516
512
|
}
|
package/typings.d.ts
CHANGED
|
@@ -142,6 +142,8 @@ export interface DatabaseView {
|
|
|
142
142
|
materialized?: boolean;
|
|
143
143
|
/** For materialized views, whether data was populated on creation. */
|
|
144
144
|
withData?: boolean;
|
|
145
|
+
/** Indexes on the materialized view. Only materialized views support indexes. */
|
|
146
|
+
indexes?: IndexDef[];
|
|
145
147
|
}
|
|
146
148
|
export interface SchemaDifference {
|
|
147
149
|
newNamespaces: Set<string>;
|