@mikro-orm/sql 7.0.7-dev.1 → 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/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/oracledb/OracleNativeQueryBuilder.js +20 -10
- package/dialects/postgresql/PostgreSqlSchemaHelper.js +13 -4
- package/package.json +2 -2
- package/query/NativeQueryBuilder.d.ts +3 -1
- package/query/NativeQueryBuilder.js +32 -11
- 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
package/AbstractSqlPlatform.d.ts
CHANGED
|
@@ -36,6 +36,8 @@ export declare abstract class AbstractSqlPlatform extends Platform {
|
|
|
36
36
|
quoteJsonKey(key: string): string;
|
|
37
37
|
getJsonIndexDefinition(index: IndexDef): string[];
|
|
38
38
|
supportsUnionWhere(): boolean;
|
|
39
|
+
/** Whether the platform supports `count(distinct col1, col2)` with multiple columns. If false, a subquery wrapper is used instead. */
|
|
40
|
+
supportsMultiColumnCountDistinct(): boolean;
|
|
39
41
|
supportsSchemas(): boolean;
|
|
40
42
|
/** @inheritDoc */
|
|
41
43
|
generateCustomOrder(escapedColumn: string, values: unknown[]): string;
|
package/AbstractSqlPlatform.js
CHANGED
|
@@ -91,6 +91,10 @@ export class AbstractSqlPlatform extends Platform {
|
|
|
91
91
|
supportsUnionWhere() {
|
|
92
92
|
return true;
|
|
93
93
|
}
|
|
94
|
+
/** Whether the platform supports `count(distinct col1, col2)` with multiple columns. If false, a subquery wrapper is used instead. */
|
|
95
|
+
supportsMultiColumnCountDistinct() {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
94
98
|
supportsSchemas() {
|
|
95
99
|
return false;
|
|
96
100
|
}
|
|
@@ -137,13 +137,18 @@ export class MsSqlNativeQueryBuilder extends NativeQueryBuilder {
|
|
|
137
137
|
this.parts[this.parts.length - 1] += ';';
|
|
138
138
|
}
|
|
139
139
|
compileSelect() {
|
|
140
|
+
const wrapCountSubquery = this.needsCountSubquery();
|
|
141
|
+
if (wrapCountSubquery) {
|
|
142
|
+
this.parts.push(`select count(*) as ${this.quote('count')} from (`);
|
|
143
|
+
}
|
|
140
144
|
this.parts.push('select');
|
|
141
|
-
|
|
145
|
+
// skip top(?) inside the count subquery — it would limit the distinct rows before counting
|
|
146
|
+
if (this.options.limit != null && this.options.offset == null && !wrapCountSubquery) {
|
|
142
147
|
this.parts.push(`top (?)`);
|
|
143
148
|
this.params.push(this.options.limit);
|
|
144
149
|
}
|
|
145
150
|
this.addHintComment();
|
|
146
|
-
this.parts.push(`${this.getFields()} from ${this.getTableName()}`);
|
|
151
|
+
this.parts.push(`${this.getFields(wrapCountSubquery)} from ${this.getTableName()}`);
|
|
147
152
|
this.addLockClause();
|
|
148
153
|
if (this.options.joins) {
|
|
149
154
|
for (const join of this.options.joins) {
|
|
@@ -163,21 +168,27 @@ export class MsSqlNativeQueryBuilder extends NativeQueryBuilder {
|
|
|
163
168
|
this.parts.push(`having ${this.options.having.sql}`);
|
|
164
169
|
this.params.push(...this.options.having.params);
|
|
165
170
|
}
|
|
166
|
-
if (
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
if (this.options.offset != null) {
|
|
170
|
-
/* v8 ignore next */
|
|
171
|
-
if (!this.options.orderBy) {
|
|
172
|
-
throw new Error('Order by clause is required for pagination');
|
|
171
|
+
if (!wrapCountSubquery) {
|
|
172
|
+
if (this.options.orderBy) {
|
|
173
|
+
this.parts.push(`order by ${this.options.orderBy}`);
|
|
173
174
|
}
|
|
174
|
-
this.
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
175
|
+
if (this.options.offset != null) {
|
|
176
|
+
/* v8 ignore next */
|
|
177
|
+
if (!this.options.orderBy) {
|
|
178
|
+
throw new Error('Order by clause is required for pagination');
|
|
179
|
+
}
|
|
180
|
+
this.parts.push(`offset ? rows`);
|
|
181
|
+
this.params.push(this.options.offset);
|
|
182
|
+
if (this.options.limit != null) {
|
|
183
|
+
this.parts.push(`fetch next ? rows only`);
|
|
184
|
+
this.params.push(this.options.limit);
|
|
185
|
+
}
|
|
179
186
|
}
|
|
180
187
|
}
|
|
188
|
+
if (wrapCountSubquery) {
|
|
189
|
+
const asKeyword = this.platform.usesAsKeyword() ? ' as ' : ' ';
|
|
190
|
+
this.parts.push(`)${asKeyword}${this.quote('dcnt')}`);
|
|
191
|
+
}
|
|
181
192
|
}
|
|
182
193
|
addLockClause() {
|
|
183
194
|
if (!this.options.lockMode ||
|
|
@@ -14,6 +14,7 @@ export declare class BaseMySqlPlatform extends AbstractSqlPlatform {
|
|
|
14
14
|
readonly "desc nulls first": "is not null";
|
|
15
15
|
readonly "desc nulls last": "is null";
|
|
16
16
|
};
|
|
17
|
+
supportsMultiColumnCountDistinct(): boolean;
|
|
17
18
|
/** @internal */
|
|
18
19
|
createNativeQueryBuilder(): MySqlNativeQueryBuilder;
|
|
19
20
|
getDefaultCharset(): string;
|
|
@@ -18,6 +18,9 @@ export class BaseMySqlPlatform extends AbstractSqlPlatform {
|
|
|
18
18
|
[QueryOrder.desc_nulls_first]: 'is not null',
|
|
19
19
|
[QueryOrder.desc_nulls_last]: 'is null',
|
|
20
20
|
};
|
|
21
|
+
supportsMultiColumnCountDistinct() {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
21
24
|
/** @internal */
|
|
22
25
|
createNativeQueryBuilder() {
|
|
23
26
|
return new MySqlNativeQueryBuilder(this);
|
|
@@ -213,9 +213,13 @@ export class OracleNativeQueryBuilder extends NativeQueryBuilder {
|
|
|
213
213
|
}
|
|
214
214
|
}
|
|
215
215
|
compileSelect() {
|
|
216
|
+
const wrapCountSubquery = this.needsCountSubquery();
|
|
217
|
+
if (wrapCountSubquery) {
|
|
218
|
+
this.parts.push(`select count(*) as ${this.quote('count')} from (`);
|
|
219
|
+
}
|
|
216
220
|
this.parts.push('select');
|
|
217
221
|
this.addHintComment();
|
|
218
|
-
this.parts.push(`${this.getFields()} from ${this.getTableName()}`);
|
|
222
|
+
this.parts.push(`${this.getFields(wrapCountSubquery)} from ${this.getTableName()}`);
|
|
219
223
|
if (this.options.joins) {
|
|
220
224
|
for (const join of this.options.joins) {
|
|
221
225
|
this.parts.push(join.sql);
|
|
@@ -234,16 +238,22 @@ export class OracleNativeQueryBuilder extends NativeQueryBuilder {
|
|
|
234
238
|
this.parts.push(`having ${this.options.having.sql}`);
|
|
235
239
|
this.params.push(...this.options.having.params);
|
|
236
240
|
}
|
|
237
|
-
if (
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
this.
|
|
242
|
-
|
|
241
|
+
if (!wrapCountSubquery) {
|
|
242
|
+
if (this.options.orderBy) {
|
|
243
|
+
this.parts.push(`order by ${this.options.orderBy}`);
|
|
244
|
+
}
|
|
245
|
+
if (this.options.offset != null) {
|
|
246
|
+
this.parts.push(`offset ? rows`);
|
|
247
|
+
this.params.push(this.options.offset);
|
|
248
|
+
}
|
|
249
|
+
if (this.options.limit != null) {
|
|
250
|
+
this.parts.push(`fetch next ? rows only`);
|
|
251
|
+
this.params.push(this.options.limit);
|
|
252
|
+
}
|
|
243
253
|
}
|
|
244
|
-
if (
|
|
245
|
-
this.
|
|
246
|
-
this.
|
|
254
|
+
if (wrapCountSubquery) {
|
|
255
|
+
const asKeyword = this.platform.usesAsKeyword() ? ' as ' : ' ';
|
|
256
|
+
this.parts.push(`)${asKeyword}${this.quote('dcnt')}`);
|
|
247
257
|
}
|
|
248
258
|
}
|
|
249
259
|
}
|
|
@@ -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"
|
|
@@ -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
|
}
|
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>;
|