@mikro-orm/sql 7.1.0-dev.2 → 7.1.0-dev.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AbstractSqlConnection.d.ts +1 -1
- package/AbstractSqlConnection.js +2 -2
- package/AbstractSqlDriver.d.ts +25 -1
- package/AbstractSqlDriver.js +315 -15
- package/PivotCollectionPersister.js +13 -2
- package/SqlEntityManager.d.ts +5 -1
- package/SqlEntityManager.js +36 -1
- package/dialects/mssql/MsSqlNativeQueryBuilder.js +4 -0
- package/dialects/mysql/BaseMySqlPlatform.d.ts +1 -0
- package/dialects/mysql/BaseMySqlPlatform.js +3 -0
- package/dialects/mysql/MySqlNativeQueryBuilder.js +11 -0
- package/dialects/mysql/MySqlSchemaHelper.d.ts +9 -3
- package/dialects/mysql/MySqlSchemaHelper.js +102 -4
- package/dialects/oracledb/OracleDialect.d.ts +1 -1
- package/dialects/oracledb/OracleDialect.js +2 -1
- package/dialects/postgresql/BasePostgreSqlPlatform.d.ts +2 -0
- package/dialects/postgresql/BasePostgreSqlPlatform.js +21 -3
- package/dialects/postgresql/PostgreSqlSchemaHelper.d.ts +21 -1
- package/dialects/postgresql/PostgreSqlSchemaHelper.js +200 -4
- package/dialects/sqlite/SqlitePlatform.d.ts +1 -0
- package/dialects/sqlite/SqlitePlatform.js +4 -0
- package/dialects/sqlite/SqliteSchemaHelper.d.ts +8 -2
- package/dialects/sqlite/SqliteSchemaHelper.js +131 -19
- package/package.json +3 -3
- package/query/CriteriaNode.d.ts +1 -1
- package/query/CriteriaNode.js +2 -2
- package/query/NativeQueryBuilder.d.ts +6 -0
- package/query/NativeQueryBuilder.js +16 -1
- package/query/ObjectCriteriaNode.js +1 -1
- package/query/QueryBuilder.d.ts +77 -0
- package/query/QueryBuilder.js +170 -6
- package/schema/DatabaseSchema.js +26 -4
- package/schema/DatabaseTable.d.ts +13 -1
- package/schema/DatabaseTable.js +171 -31
- package/schema/SchemaComparator.d.ts +1 -0
- package/schema/SchemaComparator.js +86 -1
- package/schema/SchemaHelper.d.ts +51 -1
- package/schema/SchemaHelper.js +192 -3
- package/schema/SqlSchemaGenerator.js +7 -0
- package/schema/partitioning.d.ts +13 -0
- package/schema/partitioning.js +326 -0
- package/typings.d.ts +32 -1
|
@@ -3,6 +3,17 @@ import { NativeQueryBuilder } from '../../query/NativeQueryBuilder.js';
|
|
|
3
3
|
/** @internal */
|
|
4
4
|
export class MySqlNativeQueryBuilder extends NativeQueryBuilder {
|
|
5
5
|
compileInsert() {
|
|
6
|
+
if (this.options.insertSubQuery) {
|
|
7
|
+
super.compileInsert();
|
|
8
|
+
// Inject 'ignore' after 'insert' for MySQL's INSERT IGNORE ... SELECT syntax
|
|
9
|
+
if (this.options.onConflict?.ignore) {
|
|
10
|
+
const insertIdx = this.parts.indexOf('insert');
|
|
11
|
+
if (insertIdx >= 0) {
|
|
12
|
+
this.parts.splice(insertIdx + 1, 0, 'ignore');
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
6
17
|
if (!this.options.data) {
|
|
7
18
|
throw new Error('No data provided');
|
|
8
19
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Dictionary, type Transaction, type Type } from '@mikro-orm/core';
|
|
2
|
-
import type { CheckDef, Column, ForeignKey, IndexDef, Table, TableDifference } from '../../typings.js';
|
|
2
|
+
import type { CheckDef, Column, ForeignKey, IndexDef, Table, TableDifference, SqlTriggerDef } from '../../typings.js';
|
|
3
3
|
import type { AbstractSqlConnection } from '../../AbstractSqlConnection.js';
|
|
4
4
|
import { SchemaHelper } from '../../schema/SchemaHelper.js';
|
|
5
5
|
import type { DatabaseSchema } from '../../schema/DatabaseSchema.js';
|
|
@@ -11,6 +11,7 @@ export declare class MySqlSchemaHelper extends SchemaHelper {
|
|
|
11
11
|
'current_timestamp(?)': string[];
|
|
12
12
|
'0': string[];
|
|
13
13
|
};
|
|
14
|
+
private static readonly PARTIAL_INDEX_RE;
|
|
14
15
|
getSchemaBeginning(charset: string, disableForeignKeys?: boolean): string;
|
|
15
16
|
disableForeignKeysSQL(): string;
|
|
16
17
|
enableForeignKeysSQL(): string;
|
|
@@ -22,8 +23,10 @@ export declare class MySqlSchemaHelper extends SchemaHelper {
|
|
|
22
23
|
getAllIndexes(connection: AbstractSqlConnection, tables: Table[], ctx?: Transaction): Promise<Dictionary<IndexDef[]>>;
|
|
23
24
|
getCreateIndexSQL(tableName: string, index: IndexDef, partialExpression?: boolean): string;
|
|
24
25
|
/**
|
|
25
|
-
* Build the column list for a MySQL index
|
|
26
|
-
*
|
|
26
|
+
* Build the column list for a MySQL index. MySQL requires collation via an expression:
|
|
27
|
+
* `(column COLLATE collation_name)`. Partial indexes (`where`) are emulated via functional
|
|
28
|
+
* indexes — requires MySQL 8.0.13+. MariaDB does not support inline functional indexes
|
|
29
|
+
* and overrides to throw at a higher level.
|
|
27
30
|
*/
|
|
28
31
|
protected getIndexColumns(index: IndexDef): string;
|
|
29
32
|
/**
|
|
@@ -32,6 +35,9 @@ export declare class MySqlSchemaHelper extends SchemaHelper {
|
|
|
32
35
|
protected appendMySqlIndexSuffix(sql: string, index: IndexDef): string;
|
|
33
36
|
getAllColumns(connection: AbstractSqlConnection, tables: Table[], ctx?: Transaction): Promise<Dictionary<Column[]>>;
|
|
34
37
|
getAllChecks(connection: AbstractSqlConnection, tables: Table[], ctx?: Transaction): Promise<Dictionary<CheckDef[]>>;
|
|
38
|
+
/** Generates SQL to create MySQL triggers. MySQL requires one trigger per event. */
|
|
39
|
+
createTrigger(table: DatabaseTable, trigger: SqlTriggerDef): string;
|
|
40
|
+
getAllTriggers(connection: AbstractSqlConnection, tables: Table[]): Promise<Dictionary<SqlTriggerDef[]>>;
|
|
35
41
|
getAllForeignKeys(connection: AbstractSqlConnection, tables: Table[], ctx?: Transaction): Promise<Dictionary<Dictionary<ForeignKey>>>;
|
|
36
42
|
getPreAlterTable(tableDiff: TableDifference, safe: boolean): string[];
|
|
37
43
|
getRenameColumnSQL(tableName: string, oldColumnName: string, to: Column): string;
|
|
@@ -7,6 +7,10 @@ export class MySqlSchemaHelper extends SchemaHelper {
|
|
|
7
7
|
'current_timestamp(?)': ['current_timestamp(?)'],
|
|
8
8
|
'0': ['0', 'false'],
|
|
9
9
|
};
|
|
10
|
+
// Greedy `(.+)` so nested CASE expressions inside the predicate don't trip the match on
|
|
11
|
+
// an inner `then <col> end` — the trailing `$` anchor forces the regex engine to extend
|
|
12
|
+
// the capture to the outermost case-end boundary.
|
|
13
|
+
static PARTIAL_INDEX_RE = /^\s*\(\s*case\s+when\s+(.+)\s+then\s+`([^`]+)`\s+end\s*\)\s*$/is;
|
|
10
14
|
getSchemaBeginning(charset, disableForeignKeys) {
|
|
11
15
|
if (disableForeignKeys) {
|
|
12
16
|
return `set names ${charset};\n${this.disableForeignKeysSQL()}\n\n`;
|
|
@@ -64,11 +68,15 @@ export class MySqlSchemaHelper extends SchemaHelper {
|
|
|
64
68
|
const checks = await this.getAllChecks(connection, tables, ctx);
|
|
65
69
|
const fks = await this.getAllForeignKeys(connection, tables, ctx);
|
|
66
70
|
const enums = await this.getAllEnumDefinitions(connection, tables, ctx);
|
|
71
|
+
const triggers = await this.getAllTriggers(connection, tables);
|
|
67
72
|
for (const t of tables) {
|
|
68
73
|
const key = this.getTableKey(t);
|
|
69
74
|
const table = schema.addTable(t.table_name, t.schema_name, t.table_comment);
|
|
70
75
|
const pks = await this.getPrimaryKeys(connection, indexes[key], table.name, table.schema);
|
|
71
76
|
table.init(columns[key], indexes[key], checks[key], pks, fks[key], enums[key]);
|
|
77
|
+
if (triggers[key]) {
|
|
78
|
+
table.setTriggers(triggers[key]);
|
|
79
|
+
}
|
|
72
80
|
}
|
|
73
81
|
}
|
|
74
82
|
async getAllIndexes(connection, tables, ctx) {
|
|
@@ -80,8 +88,11 @@ export class MySqlSchemaHelper extends SchemaHelper {
|
|
|
80
88
|
const ret = {};
|
|
81
89
|
for (const index of allIndexes) {
|
|
82
90
|
const key = this.getTableKey(index);
|
|
91
|
+
const partialMatch = !index.column_name && typeof index.expression === 'string'
|
|
92
|
+
? MySqlSchemaHelper.PARTIAL_INDEX_RE.exec(index.expression)
|
|
93
|
+
: null;
|
|
83
94
|
const indexDef = {
|
|
84
|
-
columnNames: [index.column_name],
|
|
95
|
+
columnNames: [partialMatch ? partialMatch[2] : index.column_name],
|
|
85
96
|
keyName: index.index_name,
|
|
86
97
|
unique: !index.non_unique,
|
|
87
98
|
primary: index.index_name === 'PRIMARY',
|
|
@@ -109,7 +120,10 @@ export class MySqlSchemaHelper extends SchemaHelper {
|
|
|
109
120
|
if (index.is_visible === 'NO') {
|
|
110
121
|
indexDef.invisible = true;
|
|
111
122
|
}
|
|
112
|
-
if (
|
|
123
|
+
if (partialMatch) {
|
|
124
|
+
indexDef.where = partialMatch[1].trim();
|
|
125
|
+
}
|
|
126
|
+
else if (!index.column_name || index.expression?.match(/ where /i)) {
|
|
113
127
|
indexDef.expression = index.expression; // required for the `getCreateIndexSQL()` call
|
|
114
128
|
indexDef.expression = this.getCreateIndexSQL(index.table_name, indexDef, !!index.expression);
|
|
115
129
|
}
|
|
@@ -146,10 +160,15 @@ export class MySqlSchemaHelper extends SchemaHelper {
|
|
|
146
160
|
return this.appendMySqlIndexSuffix(sql, index);
|
|
147
161
|
}
|
|
148
162
|
/**
|
|
149
|
-
* Build the column list for a MySQL index
|
|
150
|
-
*
|
|
163
|
+
* Build the column list for a MySQL index. MySQL requires collation via an expression:
|
|
164
|
+
* `(column COLLATE collation_name)`. Partial indexes (`where`) are emulated via functional
|
|
165
|
+
* indexes — requires MySQL 8.0.13+. MariaDB does not support inline functional indexes
|
|
166
|
+
* and overrides to throw at a higher level.
|
|
151
167
|
*/
|
|
152
168
|
getIndexColumns(index) {
|
|
169
|
+
if (index.where) {
|
|
170
|
+
return this.emulatePartialIndexColumns(index);
|
|
171
|
+
}
|
|
153
172
|
if (index.columns?.length) {
|
|
154
173
|
return index.columns
|
|
155
174
|
.map(col => {
|
|
@@ -264,6 +283,85 @@ export class MySqlSchemaHelper extends SchemaHelper {
|
|
|
264
283
|
}
|
|
265
284
|
return ret;
|
|
266
285
|
}
|
|
286
|
+
/** Generates SQL to create MySQL triggers. MySQL requires one trigger per event. */
|
|
287
|
+
createTrigger(table, trigger) {
|
|
288
|
+
if (trigger.expression) {
|
|
289
|
+
return trigger.expression;
|
|
290
|
+
}
|
|
291
|
+
/* v8 ignore next 3 */
|
|
292
|
+
if (trigger.timing === 'instead of') {
|
|
293
|
+
throw new Error(`MySQL does not support INSTEAD OF triggers. Use BEFORE or AFTER for trigger "${trigger.name}".`);
|
|
294
|
+
}
|
|
295
|
+
/* v8 ignore next 5 */
|
|
296
|
+
if (trigger.forEach === 'statement') {
|
|
297
|
+
throw new Error(`MySQL does not support FOR EACH STATEMENT triggers. Use FOR EACH ROW for trigger "${trigger.name}".`);
|
|
298
|
+
}
|
|
299
|
+
const timing = trigger.timing.toUpperCase();
|
|
300
|
+
const ret = [];
|
|
301
|
+
for (const event of trigger.events) {
|
|
302
|
+
const name = trigger.events.length > 1 ? `${trigger.name}_${event}` : trigger.name;
|
|
303
|
+
ret.push(`create trigger ${this.quote(name)} ${timing} ${event.toUpperCase()} on ${table.getQuotedName()} for each ROW begin ${trigger.body}; end`);
|
|
304
|
+
}
|
|
305
|
+
return ret.join(';\n');
|
|
306
|
+
}
|
|
307
|
+
async getAllTriggers(connection, tables) {
|
|
308
|
+
const names = tables.map(t => this.platform.quoteValue(t.table_name)).join(', ');
|
|
309
|
+
const sql = `select trigger_name as trigger_name, event_object_table as table_name, nullif(event_object_schema, schema()) as schema_name,
|
|
310
|
+
event_manipulation as event, action_timing as timing,
|
|
311
|
+
action_orientation as for_each, action_statement as body
|
|
312
|
+
from information_schema.triggers
|
|
313
|
+
where event_object_schema = database()
|
|
314
|
+
and event_object_table in (${names})
|
|
315
|
+
order by trigger_name, event_manipulation`;
|
|
316
|
+
const allTriggers = await connection.execute(sql);
|
|
317
|
+
const ret = {};
|
|
318
|
+
// First pass: collect all raw trigger names per table to detect multi-event groups.
|
|
319
|
+
// A base name is only used for grouping if multiple triggers share it (e.g. trg_multi_insert + trg_multi_update).
|
|
320
|
+
const namesByTable = new Map();
|
|
321
|
+
for (const row of allTriggers) {
|
|
322
|
+
const key = this.getTableKey(row);
|
|
323
|
+
namesByTable.set(key, [...(namesByTable.get(key) ?? []), row.trigger_name]);
|
|
324
|
+
}
|
|
325
|
+
const triggerMap = new Map();
|
|
326
|
+
for (const row of allTriggers) {
|
|
327
|
+
const key = this.getTableKey(row);
|
|
328
|
+
const eventLower = row.event.toLowerCase();
|
|
329
|
+
const tableNames = namesByTable.get(key) ?? [];
|
|
330
|
+
// Only strip event suffix when another trigger with the same base exists for this table
|
|
331
|
+
const candidateBase = row.trigger_name.endsWith(`_${eventLower}`)
|
|
332
|
+
? row.trigger_name.slice(0, -eventLower.length - 1)
|
|
333
|
+
: null;
|
|
334
|
+
const baseName = candidateBase && tableNames.some(n => n !== row.trigger_name && n.startsWith(`${candidateBase}_`))
|
|
335
|
+
? candidateBase
|
|
336
|
+
: row.trigger_name;
|
|
337
|
+
const dedupeKey = `${key}:${baseName}`;
|
|
338
|
+
if (triggerMap.has(dedupeKey)) {
|
|
339
|
+
const existing = triggerMap.get(dedupeKey);
|
|
340
|
+
const event = eventLower;
|
|
341
|
+
if (!existing.events.includes(event)) {
|
|
342
|
+
existing.events.push(event);
|
|
343
|
+
}
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
ret[key] ??= [];
|
|
347
|
+
// Strip BEGIN/END wrapper from MySQL action_statement
|
|
348
|
+
let body = row.body ?? '';
|
|
349
|
+
const beginEndMatch = /^\s*begin\s+([\s\S]*)\s*end\s*$/i.exec(body);
|
|
350
|
+
if (beginEndMatch) {
|
|
351
|
+
body = beginEndMatch[1].trim().replace(/;\s*$/, '');
|
|
352
|
+
}
|
|
353
|
+
const trigger = {
|
|
354
|
+
name: baseName,
|
|
355
|
+
timing: row.timing.toLowerCase(),
|
|
356
|
+
events: [eventLower],
|
|
357
|
+
forEach: (row.for_each ?? 'row').toLowerCase(),
|
|
358
|
+
body,
|
|
359
|
+
};
|
|
360
|
+
ret[key].push(trigger);
|
|
361
|
+
triggerMap.set(dedupeKey, trigger);
|
|
362
|
+
}
|
|
363
|
+
return ret;
|
|
364
|
+
}
|
|
267
365
|
async getAllForeignKeys(connection, tables, ctx) {
|
|
268
366
|
const sql = `select k.constraint_name as constraint_name, nullif(k.table_schema, schema()) as schema_name, k.table_name as table_name, k.column_name as column_name, k.referenced_table_name as referenced_table_name, k.referenced_column_name as referenced_column_name, c.update_rule as update_rule, c.delete_rule as delete_rule
|
|
269
367
|
from information_schema.key_column_usage k
|
|
@@ -46,7 +46,7 @@ declare class OracleConnection implements DatabaseConnection {
|
|
|
46
46
|
sql: string;
|
|
47
47
|
bindParams: unknown[];
|
|
48
48
|
};
|
|
49
|
-
streamQuery<R>(compiledQuery: CompiledQuery,
|
|
49
|
+
streamQuery<R>(compiledQuery: CompiledQuery, chunkSize?: number): AsyncIterableIterator<QueryResult<R>>;
|
|
50
50
|
get connection(): OraclePoolConnection;
|
|
51
51
|
}
|
|
52
52
|
declare class OracleDriver implements Driver {
|
|
@@ -66,13 +66,14 @@ class OracleConnection {
|
|
|
66
66
|
bindParams: query.parameters,
|
|
67
67
|
};
|
|
68
68
|
}
|
|
69
|
-
async *streamQuery(compiledQuery,
|
|
69
|
+
async *streamQuery(compiledQuery, chunkSize) {
|
|
70
70
|
const { sql, bindParams } = this.formatQuery(compiledQuery);
|
|
71
71
|
const result = await this.#connection.execute(sql, bindParams, {
|
|
72
72
|
resultSet: true,
|
|
73
73
|
autoCommit: compiledQuery.autoCommit,
|
|
74
74
|
outFormat: OUT_FORMAT_OBJECT,
|
|
75
75
|
...this.#executeOptions,
|
|
76
|
+
...(chunkSize != null ? { fetchArraySize: chunkSize } : {}),
|
|
76
77
|
});
|
|
77
78
|
const rs = result.resultSet;
|
|
78
79
|
try {
|
|
@@ -15,6 +15,7 @@ export declare class BasePostgreSqlPlatform extends AbstractSqlPlatform {
|
|
|
15
15
|
usesEnumCheckConstraints(): boolean;
|
|
16
16
|
getEnumArrayCheckConstraintExpression(column: string, items: string[]): string;
|
|
17
17
|
supportsMaterializedViews(): boolean;
|
|
18
|
+
supportsPartitionedTables(): boolean;
|
|
18
19
|
supportsCustomPrimaryKeyNames(): boolean;
|
|
19
20
|
getCurrentTimestampSQL(length: number): string;
|
|
20
21
|
getDateTimeTypeDeclarationSQL(column: {
|
|
@@ -46,6 +47,7 @@ export declare class BasePostgreSqlPlatform extends AbstractSqlPlatform {
|
|
|
46
47
|
precision?: number;
|
|
47
48
|
scale?: number;
|
|
48
49
|
autoincrement?: boolean;
|
|
50
|
+
columnTypes?: string[];
|
|
49
51
|
}): string;
|
|
50
52
|
getMappedType(type: string): Type<unknown>;
|
|
51
53
|
getRegExpOperator(val?: unknown, flags?: string): string;
|
|
@@ -30,6 +30,9 @@ export class BasePostgreSqlPlatform extends AbstractSqlPlatform {
|
|
|
30
30
|
supportsMaterializedViews() {
|
|
31
31
|
return true;
|
|
32
32
|
}
|
|
33
|
+
supportsPartitionedTables() {
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
33
36
|
supportsCustomPrimaryKeyNames() {
|
|
34
37
|
return true;
|
|
35
38
|
}
|
|
@@ -90,18 +93,27 @@ export class BasePostgreSqlPlatform extends AbstractSqlPlatform {
|
|
|
90
93
|
}
|
|
91
94
|
normalizeColumnType(type, options) {
|
|
92
95
|
const simpleType = this.extractSimpleType(type);
|
|
93
|
-
if (['int', 'int4', 'integer'].includes(simpleType)) {
|
|
96
|
+
if (['int', 'int4', 'integer', 'serial'].includes(simpleType)) {
|
|
94
97
|
return this.getIntegerTypeDeclarationSQL({});
|
|
95
98
|
}
|
|
96
|
-
if (['bigint', 'int8'].includes(simpleType)) {
|
|
99
|
+
if (['bigint', 'int8', 'bigserial'].includes(simpleType)) {
|
|
97
100
|
return this.getBigIntTypeDeclarationSQL({});
|
|
98
101
|
}
|
|
99
|
-
if (['smallint', 'int2'].includes(simpleType)) {
|
|
102
|
+
if (['smallint', 'int2', 'smallserial'].includes(simpleType)) {
|
|
100
103
|
return this.getSmallIntTypeDeclarationSQL({});
|
|
101
104
|
}
|
|
102
105
|
if (['boolean', 'bool'].includes(simpleType)) {
|
|
103
106
|
return this.getBooleanTypeDeclarationSQL();
|
|
104
107
|
}
|
|
108
|
+
if (['double', 'double precision', 'float8'].includes(simpleType)) {
|
|
109
|
+
return this.getDoubleDeclarationSQL();
|
|
110
|
+
}
|
|
111
|
+
if (['real', 'float4'].includes(simpleType)) {
|
|
112
|
+
return this.getFloatDeclarationSQL();
|
|
113
|
+
}
|
|
114
|
+
if (['timestamptz', 'timestamp with time zone'].includes(simpleType)) {
|
|
115
|
+
return this.getDateTimeTypeDeclarationSQL(options);
|
|
116
|
+
}
|
|
105
117
|
if (['varchar', 'character varying'].includes(simpleType)) {
|
|
106
118
|
return this.getVarcharTypeDeclarationSQL(options);
|
|
107
119
|
}
|
|
@@ -114,6 +126,12 @@ export class BasePostgreSqlPlatform extends AbstractSqlPlatform {
|
|
|
114
126
|
if (['interval'].includes(simpleType)) {
|
|
115
127
|
return this.getIntervalTypeDeclarationSQL(options);
|
|
116
128
|
}
|
|
129
|
+
// TimeType.getColumnType drops the timezone qualifier, so detect tz aliases from the original column type.
|
|
130
|
+
const originalType = options.columnTypes?.[0]?.toLowerCase() ?? type;
|
|
131
|
+
if (/^timetz\b/.test(originalType) || /^time\s+with\s+time\s+zone\b/.test(originalType)) {
|
|
132
|
+
const length = options.length ?? this.getDefaultDateTimeLength();
|
|
133
|
+
return `timetz(${length})`;
|
|
134
|
+
}
|
|
117
135
|
return super.normalizeColumnType(type, options);
|
|
118
136
|
}
|
|
119
137
|
getMappedType(type) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type Dictionary, type Transaction } from '@mikro-orm/core';
|
|
2
2
|
import { SchemaHelper } from '../../schema/SchemaHelper.js';
|
|
3
3
|
import type { AbstractSqlConnection } from '../../AbstractSqlConnection.js';
|
|
4
|
-
import type { CheckDef, Column, ForeignKey, IndexDef, Table, TableDifference } from '../../typings.js';
|
|
4
|
+
import type { CheckDef, Column, ForeignKey, IndexDef, Table, TableDifference, TablePartitioning, SqlTriggerDef } from '../../typings.js';
|
|
5
5
|
import type { DatabaseSchema } from '../../schema/DatabaseSchema.js';
|
|
6
6
|
import type { DatabaseTable } from '../../schema/DatabaseTable.js';
|
|
7
7
|
export declare class PostgreSqlSchemaHelper extends SchemaHelper {
|
|
@@ -14,6 +14,8 @@ export declare class PostgreSqlSchemaHelper extends SchemaHelper {
|
|
|
14
14
|
'null::timestamp with time zone': string[];
|
|
15
15
|
'null::timestamp without time zone': string[];
|
|
16
16
|
};
|
|
17
|
+
private static readonly PARTIAL_WHERE_RE;
|
|
18
|
+
private static readonly FUNCTIONAL_COL_RE;
|
|
17
19
|
getSchemaBeginning(charset: string, disableForeignKeys?: boolean): string;
|
|
18
20
|
getCreateDatabaseSQL(name: string): string;
|
|
19
21
|
getListTablesSQL(): string;
|
|
@@ -23,11 +25,22 @@ export declare class PostgreSqlSchemaHelper extends SchemaHelper {
|
|
|
23
25
|
getListMaterializedViewsSQL(): string;
|
|
24
26
|
loadMaterializedViews(schema: DatabaseSchema, connection: AbstractSqlConnection, schemaName?: string, ctx?: Transaction): Promise<void>;
|
|
25
27
|
createMaterializedView(name: string, schema: string | undefined, definition: string, withData?: boolean): string;
|
|
28
|
+
createTable(table: DatabaseTable, alter?: boolean): string[];
|
|
26
29
|
dropMaterializedViewIfExists(name: string, schema?: string): string;
|
|
27
30
|
refreshMaterializedView(name: string, schema?: string, concurrently?: boolean): string;
|
|
28
31
|
getNamespaces(connection: AbstractSqlConnection, ctx?: Transaction): Promise<string[]>;
|
|
29
32
|
private getIgnoredNamespacesConditionSQL;
|
|
30
33
|
loadInformationSchema(schema: DatabaseSchema, connection: AbstractSqlConnection, tables: Table[], schemas?: string[], ctx?: Transaction): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Introspects direct partitions only: the `pg_inherits` join surfaces a parent's children but
|
|
36
|
+
* does not recurse into sub-partitioning (e.g. hash-of-range). Declarative `partitionBy`
|
|
37
|
+
* metadata does not express multi-level partitioning either, so grandchildren are intentionally
|
|
38
|
+
* invisible to schema diffing.
|
|
39
|
+
*
|
|
40
|
+
* Entries with an undefined schema bucket are resolved against `current_schema()` so they do
|
|
41
|
+
* not match same-named tables in unrelated schemas.
|
|
42
|
+
*/
|
|
43
|
+
getPartitions(connection: AbstractSqlConnection, tablesBySchemas: Map<string | undefined, Table[]>, ctx?: Transaction): Promise<Dictionary<TablePartitioning>>;
|
|
31
44
|
getAllIndexes(connection: AbstractSqlConnection, tables: Table[], ctx?: Transaction): Promise<Dictionary<IndexDef[]>>;
|
|
32
45
|
/**
|
|
33
46
|
* Parses column definitions from the full CREATE INDEX expression.
|
|
@@ -49,6 +62,13 @@ export declare class PostgreSqlSchemaHelper extends SchemaHelper {
|
|
|
49
62
|
items: string[];
|
|
50
63
|
}>, ctx?: Transaction): Promise<Dictionary<Column[]>>;
|
|
51
64
|
getAllChecks(connection: AbstractSqlConnection, tablesBySchemas: Map<string | undefined, Table[]>, ctx?: Transaction): Promise<Dictionary<CheckDef[]>>;
|
|
65
|
+
/** Generates SQL to create a PostgreSQL trigger and its associated function. */
|
|
66
|
+
createTrigger(table: DatabaseTable, trigger: SqlTriggerDef): string;
|
|
67
|
+
/** Generates SQL to drop a PostgreSQL trigger and its associated function. */
|
|
68
|
+
dropTrigger(table: DatabaseTable, trigger: SqlTriggerDef): string;
|
|
69
|
+
private getSchemaQualifiedTriggerFnName;
|
|
70
|
+
getAllTriggers(connection: AbstractSqlConnection, tablesBySchemas: Map<string | undefined, Table[]>): Promise<Dictionary<SqlTriggerDef[]>>;
|
|
71
|
+
private getTriggersSQL;
|
|
52
72
|
getAllForeignKeys(connection: AbstractSqlConnection, tablesBySchemas: Map<string | undefined, Table[]>, ctx?: Transaction): Promise<Dictionary<Dictionary<ForeignKey>>>;
|
|
53
73
|
getNativeEnumDefinitions(connection: AbstractSqlConnection, schemas: string[], ctx?: Transaction): Promise<Dictionary<{
|
|
54
74
|
name: string;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DeferMode, EnumType, Type, Utils, } from '@mikro-orm/core';
|
|
2
2
|
import { SchemaHelper } from '../../schema/SchemaHelper.js';
|
|
3
|
+
import { normalizePartitionBound, normalizePartitionDefinition } from '../../schema/partitioning.js';
|
|
3
4
|
/** PostGIS system views that should be automatically ignored */
|
|
4
5
|
const POSTGIS_VIEWS = ['geography_columns', 'geometry_columns'];
|
|
5
6
|
export class PostgreSqlSchemaHelper extends SchemaHelper {
|
|
@@ -12,6 +13,8 @@ export class PostgreSqlSchemaHelper extends SchemaHelper {
|
|
|
12
13
|
'null::timestamp with time zone': ['null'],
|
|
13
14
|
'null::timestamp without time zone': ['null'],
|
|
14
15
|
};
|
|
16
|
+
static PARTIAL_WHERE_RE = /\swhere\s+(.+)$/is;
|
|
17
|
+
static FUNCTIONAL_COL_RE = /[(): ,"'`]/;
|
|
15
18
|
getSchemaBeginning(charset, disableForeignKeys) {
|
|
16
19
|
if (disableForeignKeys) {
|
|
17
20
|
return `set names '${charset}';\n${this.disableForeignKeysSQL()}\n\n`;
|
|
@@ -22,13 +25,22 @@ export class PostgreSqlSchemaHelper extends SchemaHelper {
|
|
|
22
25
|
return `create database ${this.quote(name)}`;
|
|
23
26
|
}
|
|
24
27
|
getListTablesSQL() {
|
|
28
|
+
// The `pg_inherits` anti-join compares on (schema, table) pairs so cross-schema child
|
|
29
|
+
// partitions are excluded even when their schema is not on the session `search_path`
|
|
30
|
+
// (in which case `inhrelid::regclass::text` renders as `schema.name` rather than bare `name`,
|
|
31
|
+
// breaking a plain `table_name not in (...)` predicate).
|
|
25
32
|
return (`select table_name, table_schema as schema_name, ` +
|
|
26
33
|
`(select pg_catalog.obj_description(c.oid) from pg_catalog.pg_class c
|
|
27
34
|
where c.oid = (select ('"' || table_schema || '"."' || table_name || '"')::regclass::oid) and c.relname = table_name) as table_comment ` +
|
|
28
|
-
`from information_schema.tables ` +
|
|
35
|
+
`from information_schema.tables t ` +
|
|
29
36
|
`where ${this.getIgnoredNamespacesConditionSQL('table_schema')} ` +
|
|
30
37
|
`and table_name != 'geometry_columns' and table_name != 'spatial_ref_sys' and table_type != 'VIEW' ` +
|
|
31
|
-
`and
|
|
38
|
+
`and not exists (` +
|
|
39
|
+
`select 1 from pg_inherits i ` +
|
|
40
|
+
`join pg_class c on c.oid = i.inhrelid ` +
|
|
41
|
+
`join pg_namespace n on n.oid = c.relnamespace ` +
|
|
42
|
+
`where c.relname = t.table_name and n.nspname = t.table_schema` +
|
|
43
|
+
`) ` +
|
|
32
44
|
`order by table_name`);
|
|
33
45
|
}
|
|
34
46
|
getIgnoredViewsCondition() {
|
|
@@ -79,6 +91,23 @@ export class PostgreSqlSchemaHelper extends SchemaHelper {
|
|
|
79
91
|
const dataClause = withData ? ' with data' : ' with no data';
|
|
80
92
|
return `create materialized view ${viewName} as ${definition}${dataClause}`;
|
|
81
93
|
}
|
|
94
|
+
createTable(table, alter) {
|
|
95
|
+
const partitioning = table.getPartitioning();
|
|
96
|
+
if (!partitioning) {
|
|
97
|
+
return super.createTable(table, alter);
|
|
98
|
+
}
|
|
99
|
+
const [createTable, ...rest] = super.createTable(table, alter);
|
|
100
|
+
const partitions = partitioning.partitions.map(partition => {
|
|
101
|
+
const partitionName = this.quote(this.getTableName(partition.name, partition.schema ?? table.schema));
|
|
102
|
+
return `create table ${partitionName} partition of ${table.getQuotedName()} ${partition.bound}`;
|
|
103
|
+
});
|
|
104
|
+
// SchemaHelper.append() always terminates the CREATE TABLE with `;`; we rely on that to splice
|
|
105
|
+
// the `partition by ...` clause in before the terminator. Use slice instead of replace() so that
|
|
106
|
+
// regex replacement tokens like `$$`, `$&`, or `$1` inside user-supplied expressions (e.g., a
|
|
107
|
+
// callback that returns a dollar-quoted literal) are not interpreted as back-references.
|
|
108
|
+
const spliced = `${createTable.slice(0, -1)} partition by ${partitioning.definition};`;
|
|
109
|
+
return [spliced, ...rest, ...partitions];
|
|
110
|
+
}
|
|
82
111
|
dropMaterializedViewIfExists(name, schema) {
|
|
83
112
|
return `drop materialized view if exists ${this.quote(this.getTableName(name, schema))} cascade`;
|
|
84
113
|
}
|
|
@@ -118,6 +147,8 @@ export class PostgreSqlSchemaHelper extends SchemaHelper {
|
|
|
118
147
|
const indexes = await this.getAllIndexes(connection, tables, ctx);
|
|
119
148
|
const checks = await this.getAllChecks(connection, tablesBySchema, ctx);
|
|
120
149
|
const fks = await this.getAllForeignKeys(connection, tablesBySchema, ctx);
|
|
150
|
+
const partitionings = await this.getPartitions(connection, tablesBySchema, ctx);
|
|
151
|
+
const triggers = await this.getAllTriggers(connection, tablesBySchema);
|
|
121
152
|
for (const t of tables) {
|
|
122
153
|
const key = this.getTableKey(t);
|
|
123
154
|
const table = schema.addTable(t.table_name, t.schema_name, t.table_comment);
|
|
@@ -126,8 +157,64 @@ export class PostgreSqlSchemaHelper extends SchemaHelper {
|
|
|
126
157
|
if (columns[key]) {
|
|
127
158
|
table.init(columns[key], indexes[key], checks[key], pks, fks[key], enums);
|
|
128
159
|
}
|
|
160
|
+
if (triggers[key]) {
|
|
161
|
+
table.setTriggers(triggers[key]);
|
|
162
|
+
}
|
|
163
|
+
table.setPartitioning(partitionings[key]);
|
|
129
164
|
}
|
|
130
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* Introspects direct partitions only: the `pg_inherits` join surfaces a parent's children but
|
|
168
|
+
* does not recurse into sub-partitioning (e.g. hash-of-range). Declarative `partitionBy`
|
|
169
|
+
* metadata does not express multi-level partitioning either, so grandchildren are intentionally
|
|
170
|
+
* invisible to schema diffing.
|
|
171
|
+
*
|
|
172
|
+
* Entries with an undefined schema bucket are resolved against `current_schema()` so they do
|
|
173
|
+
* not match same-named tables in unrelated schemas.
|
|
174
|
+
*/
|
|
175
|
+
async getPartitions(connection, tablesBySchemas, ctx) {
|
|
176
|
+
// Collapse every (schema, table) pair into a single `values (...)` relation and join against
|
|
177
|
+
// the catalog, instead of building an OR-tree of per-schema `in (...)` predicates. This keeps
|
|
178
|
+
// the query size O(pairs) rather than O(schemas × predicate_overhead) and stays sargable when
|
|
179
|
+
// many schemas are in play.
|
|
180
|
+
const pairs = [...tablesBySchemas.entries()].flatMap(([schema, tables]) => tables.map(t => {
|
|
181
|
+
const schemaLiteral = schema == null ? 'null::text' : `${this.platform.quoteValue(schema)}::text`;
|
|
182
|
+
return `(${schemaLiteral}, ${this.platform.quoteValue(t.table_name)})`;
|
|
183
|
+
}));
|
|
184
|
+
if (pairs.length === 0) {
|
|
185
|
+
return {};
|
|
186
|
+
}
|
|
187
|
+
const sql = `with targets(schema_name, table_name) as (values ${pairs.join(', ')})
|
|
188
|
+
select parent_ns.nspname as schema_name,
|
|
189
|
+
parent.relname as table_name,
|
|
190
|
+
pg_get_partkeydef(parent.oid) as partition_definition,
|
|
191
|
+
child_ns.nspname as partition_schema_name,
|
|
192
|
+
child.relname as partition_name,
|
|
193
|
+
pg_get_expr(child.relpartbound, child.oid) as partition_bound
|
|
194
|
+
from targets
|
|
195
|
+
join pg_class parent on parent.relname = targets.table_name
|
|
196
|
+
join pg_namespace parent_ns on parent_ns.oid = parent.relnamespace
|
|
197
|
+
and parent_ns.nspname = coalesce(targets.schema_name, current_schema())
|
|
198
|
+
join pg_partitioned_table partitioned on partitioned.partrelid = parent.oid
|
|
199
|
+
left join pg_inherits inherits on inherits.inhparent = parent.oid
|
|
200
|
+
left join pg_class child on child.oid = inherits.inhrelid
|
|
201
|
+
left join pg_namespace child_ns on child_ns.oid = child.relnamespace
|
|
202
|
+
order by parent_ns.nspname, parent.relname, child_ns.nspname, child.relname`;
|
|
203
|
+
const rows = await connection.execute(sql, [], 'all', ctx);
|
|
204
|
+
const ret = {};
|
|
205
|
+
for (const row of rows) {
|
|
206
|
+
const key = this.getTableKey(row);
|
|
207
|
+
ret[key] ??= { definition: normalizePartitionDefinition(row.partition_definition), partitions: [] };
|
|
208
|
+
if (row.partition_name && row.partition_bound) {
|
|
209
|
+
ret[key].partitions.push({
|
|
210
|
+
name: row.partition_name,
|
|
211
|
+
schema: row.partition_schema_name,
|
|
212
|
+
bound: normalizePartitionBound(row.partition_bound),
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return ret;
|
|
217
|
+
}
|
|
131
218
|
async getAllIndexes(connection, tables, ctx) {
|
|
132
219
|
const sql = this.getIndexesSQL(tables);
|
|
133
220
|
const unquote = (str) => str.replace(/['"`]/g, '');
|
|
@@ -161,9 +248,22 @@ export class PostgreSqlSchemaHelper extends SchemaHelper {
|
|
|
161
248
|
if (index.condeferrable) {
|
|
162
249
|
indexDef.deferMode = index.condeferred ? DeferMode.INITIALLY_DEFERRED : DeferMode.INITIALLY_IMMEDIATE;
|
|
163
250
|
}
|
|
164
|
-
|
|
251
|
+
const hasFunctionalColumns = index.index_def.some((col) => PostgreSqlSchemaHelper.FUNCTIONAL_COL_RE.exec(col));
|
|
252
|
+
const whereMatch = hasFunctionalColumns
|
|
253
|
+
? null
|
|
254
|
+
: PostgreSqlSchemaHelper.PARTIAL_WHERE_RE.exec(index.expression ?? '');
|
|
255
|
+
if (hasFunctionalColumns) {
|
|
256
|
+
// Functional-column expression can't be diffed structurally — keep the whole CREATE
|
|
257
|
+
// statement (WHERE included) on `expression`; don't try to split the predicate.
|
|
165
258
|
indexDef.expression = index.expression;
|
|
166
259
|
}
|
|
260
|
+
else if (whereMatch) {
|
|
261
|
+
let where = whereMatch[1].trim();
|
|
262
|
+
if (where.startsWith('(') && where.endsWith(')') && this.isBalancedWrap(where)) {
|
|
263
|
+
where = where.slice(1, -1).trim();
|
|
264
|
+
}
|
|
265
|
+
indexDef.where = where;
|
|
266
|
+
}
|
|
167
267
|
if (index.deferrable) {
|
|
168
268
|
indexDef.deferMode = index.initially_deferred ? DeferMode.INITIALLY_DEFERRED : DeferMode.INITIALLY_IMMEDIATE;
|
|
169
269
|
}
|
|
@@ -375,6 +475,95 @@ export class PostgreSqlSchemaHelper extends SchemaHelper {
|
|
|
375
475
|
}
|
|
376
476
|
return ret;
|
|
377
477
|
}
|
|
478
|
+
/** Generates SQL to create a PostgreSQL trigger and its associated function. */
|
|
479
|
+
createTrigger(table, trigger) {
|
|
480
|
+
if (trigger.expression) {
|
|
481
|
+
return trigger.expression;
|
|
482
|
+
}
|
|
483
|
+
const timing = trigger.timing.toUpperCase();
|
|
484
|
+
const events = trigger.events.map(e => e.toUpperCase()).join(' OR ');
|
|
485
|
+
const forEach = trigger.forEach === 'statement' ? 'STATEMENT' : 'ROW';
|
|
486
|
+
const when = trigger.when ? `\n when (${trigger.when})` : '';
|
|
487
|
+
const fnName = this.getSchemaQualifiedTriggerFnName(table, trigger);
|
|
488
|
+
const triggerName = this.platform.quoteIdentifier(trigger.name);
|
|
489
|
+
const fnSql = `create or replace function ${fnName}() returns trigger as $$ begin ${trigger.body}; end; $$ language plpgsql`;
|
|
490
|
+
const triggerSql = `create trigger ${triggerName} ${timing} ${events} on ${table.getQuotedName()} for each ${forEach}${when} execute function ${fnName}()`;
|
|
491
|
+
return `${fnSql};\n${triggerSql}`;
|
|
492
|
+
}
|
|
493
|
+
/** Generates SQL to drop a PostgreSQL trigger and its associated function. */
|
|
494
|
+
dropTrigger(table, trigger) {
|
|
495
|
+
const triggerName = this.platform.quoteIdentifier(trigger.name);
|
|
496
|
+
const fnName = this.getSchemaQualifiedTriggerFnName(table, trigger);
|
|
497
|
+
return `drop trigger if exists ${triggerName} on ${table.getQuotedName()};\ndrop function if exists ${fnName}()`;
|
|
498
|
+
}
|
|
499
|
+
getSchemaQualifiedTriggerFnName(table, trigger) {
|
|
500
|
+
const rawName = `${table.name}_${trigger.name}_fn`;
|
|
501
|
+
const defaultSchema = this.platform.getDefaultSchemaName();
|
|
502
|
+
if (table.schema && table.schema !== defaultSchema) {
|
|
503
|
+
return `${this.platform.quoteIdentifier(table.schema)}.${this.platform.quoteIdentifier(rawName)}`;
|
|
504
|
+
}
|
|
505
|
+
return this.platform.quoteIdentifier(rawName);
|
|
506
|
+
}
|
|
507
|
+
async getAllTriggers(connection, tablesBySchemas) {
|
|
508
|
+
const sql = this.getTriggersSQL(tablesBySchemas);
|
|
509
|
+
const allTriggers = await connection.execute(sql);
|
|
510
|
+
const ret = {};
|
|
511
|
+
const triggerMap = new Map();
|
|
512
|
+
for (const row of allTriggers) {
|
|
513
|
+
const key = this.getTableKey(row);
|
|
514
|
+
const dedupeKey = `${key}:${row.trigger_name}`;
|
|
515
|
+
if (triggerMap.has(dedupeKey)) {
|
|
516
|
+
// Same trigger with multiple events — merge events
|
|
517
|
+
const existing = triggerMap.get(dedupeKey);
|
|
518
|
+
const event = row.event.toLowerCase();
|
|
519
|
+
if (!existing.events.includes(event)) {
|
|
520
|
+
existing.events.push(event);
|
|
521
|
+
}
|
|
522
|
+
continue;
|
|
523
|
+
}
|
|
524
|
+
ret[key] ??= [];
|
|
525
|
+
// prosrc includes the full function body between $$ delimiters (e.g. " begin RETURN NEW; end;")
|
|
526
|
+
// Strip the begin/end wrapper to get just the trigger body for round-trip comparison
|
|
527
|
+
let body = (row.function_body ?? '').trim();
|
|
528
|
+
const beginEndMatch = /^\s*begin\s+([\s\S]*?)\s*end;?\s*$/i.exec(body);
|
|
529
|
+
if (beginEndMatch) {
|
|
530
|
+
body = beginEndMatch[1].trim().replace(/;\s*$/, '');
|
|
531
|
+
}
|
|
532
|
+
const trigger = {
|
|
533
|
+
name: row.trigger_name,
|
|
534
|
+
timing: row.timing.toLowerCase(),
|
|
535
|
+
events: [row.event.toLowerCase()],
|
|
536
|
+
forEach: row.for_each.toLowerCase(),
|
|
537
|
+
body,
|
|
538
|
+
when: row.when_clause ?? undefined,
|
|
539
|
+
};
|
|
540
|
+
ret[key].push(trigger);
|
|
541
|
+
triggerMap.set(dedupeKey, trigger);
|
|
542
|
+
}
|
|
543
|
+
return ret;
|
|
544
|
+
}
|
|
545
|
+
getTriggersSQL(tablesBySchemas) {
|
|
546
|
+
const conditions = [];
|
|
547
|
+
for (const [schema, tables] of tablesBySchemas) {
|
|
548
|
+
const names = tables.map(t => this.platform.quoteValue(t.table_name)).join(', ');
|
|
549
|
+
const schemaName = this.platform.quoteValue(schema ?? this.platform.getDefaultSchemaName());
|
|
550
|
+
conditions.push(`(t.event_object_schema = ${schemaName} and t.event_object_table in (${names}))`);
|
|
551
|
+
}
|
|
552
|
+
// Function lookup uses the '{table}_{trigger}_fn' convention from createTrigger().
|
|
553
|
+
// External triggers with different function names will have NULL body;
|
|
554
|
+
// use the `expression` escape hatch for those.
|
|
555
|
+
return `select t.trigger_name, t.event_object_schema as schema_name, t.event_object_table as table_name,
|
|
556
|
+
t.event_manipulation as event, t.action_timing as timing,
|
|
557
|
+
t.action_orientation as for_each,
|
|
558
|
+
t.action_condition as when_clause,
|
|
559
|
+
pg_get_functiondef(p.oid) as function_def,
|
|
560
|
+
p.prosrc as function_body
|
|
561
|
+
from information_schema.triggers t
|
|
562
|
+
left join pg_namespace n on n.nspname = t.event_object_schema
|
|
563
|
+
left join pg_proc p on p.proname = t.event_object_table || '_' || t.trigger_name || '_fn' and p.pronamespace = n.oid
|
|
564
|
+
where (${conditions.join(' or ')})
|
|
565
|
+
order by t.trigger_name, t.event_manipulation`;
|
|
566
|
+
}
|
|
378
567
|
async getAllForeignKeys(connection, tablesBySchemas, ctx) {
|
|
379
568
|
const sql = `select nsp1.nspname schema_name, cls1.relname table_name, nsp2.nspname referenced_schema_name,
|
|
380
569
|
cls2.relname referenced_table_name, a.attname column_name, af.attname referenced_column_name, conname constraint_name,
|
|
@@ -548,6 +737,12 @@ export class PostgreSqlSchemaHelper extends SchemaHelper {
|
|
|
548
737
|
return col.join(' ');
|
|
549
738
|
}
|
|
550
739
|
getPreAlterTable(tableDiff, safe) {
|
|
740
|
+
if (tableDiff.changedPartitioning) {
|
|
741
|
+
const from = tableDiff.changedPartitioning.from?.definition;
|
|
742
|
+
const to = tableDiff.changedPartitioning.to?.definition;
|
|
743
|
+
const action = !from ? 'Adding' : !to ? 'Removing' : 'Changing';
|
|
744
|
+
throw new Error(`${action} partition definitions for existing PostgreSQL tables is not supported automatically (${tableDiff.name}: '${from ?? '<none>'}' -> '${to ?? '<none>'}'); create a manual migration instead`);
|
|
745
|
+
}
|
|
551
746
|
const ret = [];
|
|
552
747
|
const parts = tableDiff.name.split('.');
|
|
553
748
|
const tableName = parts.pop();
|
|
@@ -680,7 +875,8 @@ export class PostgreSqlSchemaHelper extends SchemaHelper {
|
|
|
680
875
|
if (index.primary || (index.unique && index.constraint)) {
|
|
681
876
|
return `alter table ${this.quote(table)} drop constraint ${this.quote(oldIndexName)}`;
|
|
682
877
|
}
|
|
683
|
-
|
|
878
|
+
const [schemaName] = this.splitTableName(table);
|
|
879
|
+
return `drop index ${this.quote(schemaName, oldIndexName)}`;
|
|
684
880
|
}
|
|
685
881
|
/**
|
|
686
882
|
* Build the column list for a PostgreSQL index.
|
|
@@ -15,6 +15,7 @@ export declare class SqlitePlatform extends AbstractSqlPlatform {
|
|
|
15
15
|
getDateTimeTypeDeclarationSQL(column: {
|
|
16
16
|
length: number;
|
|
17
17
|
}): string;
|
|
18
|
+
getDefaultVersionLength(): number;
|
|
18
19
|
getBeginTransactionSQL(options?: {
|
|
19
20
|
isolationLevel?: IsolationLevel;
|
|
20
21
|
readOnly?: boolean;
|