@mikro-orm/sql 7.1.0-dev.3 → 7.1.0-dev.31
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 +19 -1
- package/AbstractSqlDriver.js +215 -16
- package/AbstractSqlPlatform.d.ts +15 -3
- package/AbstractSqlPlatform.js +25 -7
- package/PivotCollectionPersister.js +13 -2
- package/README.md +2 -1
- package/SqlEntityManager.d.ts +5 -1
- package/SqlEntityManager.js +36 -1
- package/SqlMikroORM.d.ts +23 -0
- package/SqlMikroORM.js +23 -0
- package/dialects/mysql/BaseMySqlPlatform.d.ts +1 -0
- package/dialects/mysql/BaseMySqlPlatform.js +3 -0
- package/dialects/mysql/MySqlSchemaHelper.d.ts +13 -3
- package/dialects/mysql/MySqlSchemaHelper.js +145 -21
- package/dialects/oracledb/OracleDialect.d.ts +1 -1
- package/dialects/oracledb/OracleDialect.js +2 -1
- package/dialects/postgresql/BasePostgreSqlEntityManager.d.ts +19 -0
- package/dialects/postgresql/BasePostgreSqlEntityManager.js +24 -0
- package/dialects/postgresql/BasePostgreSqlPlatform.d.ts +9 -0
- package/dialects/postgresql/BasePostgreSqlPlatform.js +72 -6
- package/dialects/postgresql/PostgreSqlSchemaHelper.d.ts +31 -1
- package/dialects/postgresql/PostgreSqlSchemaHelper.js +230 -5
- package/dialects/postgresql/index.d.ts +2 -0
- package/dialects/postgresql/index.js +2 -0
- package/dialects/postgresql/typeOverrides.d.ts +14 -0
- package/dialects/postgresql/typeOverrides.js +12 -0
- package/dialects/sqlite/SqlitePlatform.d.ts +1 -0
- package/dialects/sqlite/SqlitePlatform.js +4 -0
- package/dialects/sqlite/SqliteSchemaHelper.d.ts +9 -2
- package/dialects/sqlite/SqliteSchemaHelper.js +148 -19
- package/index.d.ts +2 -0
- package/index.js +2 -0
- package/package.json +4 -4
- package/plugin/transformer.d.ts +11 -3
- package/plugin/transformer.js +138 -29
- package/query/CriteriaNode.d.ts +1 -1
- package/query/CriteriaNode.js +2 -2
- package/query/ObjectCriteriaNode.js +1 -1
- package/query/QueryBuilder.d.ts +36 -0
- package/query/QueryBuilder.js +63 -1
- package/schema/DatabaseSchema.js +26 -4
- package/schema/DatabaseTable.d.ts +20 -1
- package/schema/DatabaseTable.js +182 -31
- package/schema/SchemaComparator.d.ts +10 -0
- package/schema/SchemaComparator.js +104 -1
- package/schema/SchemaHelper.d.ts +63 -1
- package/schema/SchemaHelper.js +235 -6
- package/schema/SqlSchemaGenerator.d.ts +2 -2
- package/schema/SqlSchemaGenerator.js +16 -9
- package/schema/partitioning.d.ts +13 -0
- package/schema/partitioning.js +326 -0
- package/typings.d.ts +34 -2
package/SqlMikroORM.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { defineConfig, MikroORM, } from '@mikro-orm/core';
|
|
2
|
+
/**
|
|
3
|
+
* Creates a type-safe configuration object for any SQL driver. The driver class
|
|
4
|
+
* must be passed via `options.driver` (e.g. `SqliteDriver`, `MySqlDriver`, …).
|
|
5
|
+
*/
|
|
6
|
+
export function defineSqlConfig(options) {
|
|
7
|
+
return defineConfig(options);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Generic entry point for SQL drivers. Use this when consuming `@mikro-orm/sql`
|
|
11
|
+
* directly with a Kysely dialect; for the bundled driver packages prefer
|
|
12
|
+
* `@mikro-orm/sqlite`, `@mikro-orm/postgresql`, etc.
|
|
13
|
+
*
|
|
14
|
+
* @inheritDoc
|
|
15
|
+
*/
|
|
16
|
+
export class SqlMikroORM extends MikroORM {
|
|
17
|
+
/**
|
|
18
|
+
* @inheritDoc
|
|
19
|
+
*/
|
|
20
|
+
static async init(options) {
|
|
21
|
+
return super.init(options);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -17,6 +17,7 @@ export declare class BaseMySqlPlatform extends AbstractSqlPlatform {
|
|
|
17
17
|
supportsMultiColumnCountDistinct(): boolean;
|
|
18
18
|
/** @internal */
|
|
19
19
|
createNativeQueryBuilder(): MySqlNativeQueryBuilder;
|
|
20
|
+
formatIndexHint(indexNames: string[]): string;
|
|
20
21
|
getDefaultCharset(): string;
|
|
21
22
|
init(orm: MikroORM): void;
|
|
22
23
|
getBeginTransactionSQL(options?: {
|
|
@@ -25,6 +25,9 @@ export class BaseMySqlPlatform extends AbstractSqlPlatform {
|
|
|
25
25
|
createNativeQueryBuilder() {
|
|
26
26
|
return new MySqlNativeQueryBuilder(this);
|
|
27
27
|
}
|
|
28
|
+
formatIndexHint(indexNames) {
|
|
29
|
+
return `use index(${indexNames.join(', ')})`;
|
|
30
|
+
}
|
|
28
31
|
getDefaultCharset() {
|
|
29
32
|
return 'utf8mb4';
|
|
30
33
|
}
|
|
@@ -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,7 +11,12 @@ 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;
|
|
16
|
+
getSetSchemaSQL(schema: string): string;
|
|
17
|
+
getResetSchemaSQL(defaultSchema: string): string;
|
|
18
|
+
supportsMigrationSchema(): boolean;
|
|
19
|
+
tableExists(connection: AbstractSqlConnection, tableName: string, schemaName: string | undefined, ctx?: Transaction): Promise<boolean>;
|
|
15
20
|
disableForeignKeysSQL(): string;
|
|
16
21
|
enableForeignKeysSQL(): string;
|
|
17
22
|
finalizeTable(table: DatabaseTable, charset: string, collate?: string): string;
|
|
@@ -22,8 +27,10 @@ export declare class MySqlSchemaHelper extends SchemaHelper {
|
|
|
22
27
|
getAllIndexes(connection: AbstractSqlConnection, tables: Table[], ctx?: Transaction): Promise<Dictionary<IndexDef[]>>;
|
|
23
28
|
getCreateIndexSQL(tableName: string, index: IndexDef, partialExpression?: boolean): string;
|
|
24
29
|
/**
|
|
25
|
-
* Build the column list for a MySQL index
|
|
26
|
-
*
|
|
30
|
+
* Build the column list for a MySQL index. MySQL requires collation via an expression:
|
|
31
|
+
* `(column COLLATE collation_name)`. Partial indexes (`where`) are emulated via functional
|
|
32
|
+
* indexes — requires MySQL 8.0.13+. MariaDB does not support inline functional indexes
|
|
33
|
+
* and overrides to throw at a higher level.
|
|
27
34
|
*/
|
|
28
35
|
protected getIndexColumns(index: IndexDef): string;
|
|
29
36
|
/**
|
|
@@ -32,6 +39,9 @@ export declare class MySqlSchemaHelper extends SchemaHelper {
|
|
|
32
39
|
protected appendMySqlIndexSuffix(sql: string, index: IndexDef): string;
|
|
33
40
|
getAllColumns(connection: AbstractSqlConnection, tables: Table[], ctx?: Transaction): Promise<Dictionary<Column[]>>;
|
|
34
41
|
getAllChecks(connection: AbstractSqlConnection, tables: Table[], ctx?: Transaction): Promise<Dictionary<CheckDef[]>>;
|
|
42
|
+
/** Generates SQL to create MySQL triggers. MySQL requires one trigger per event. */
|
|
43
|
+
createTrigger(table: DatabaseTable, trigger: SqlTriggerDef): string;
|
|
44
|
+
getAllTriggers(connection: AbstractSqlConnection, tables: Table[]): Promise<Dictionary<SqlTriggerDef[]>>;
|
|
35
45
|
getAllForeignKeys(connection: AbstractSqlConnection, tables: Table[], ctx?: Transaction): Promise<Dictionary<Dictionary<ForeignKey>>>;
|
|
36
46
|
getPreAlterTable(tableDiff: TableDifference, safe: boolean): string[];
|
|
37
47
|
getRenameColumnSQL(tableName: string, oldColumnName: string, to: Column): string;
|
|
@@ -7,12 +7,34 @@ 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`;
|
|
13
17
|
}
|
|
14
18
|
return `set names ${charset};\n\n`;
|
|
15
19
|
}
|
|
20
|
+
getSetSchemaSQL(schema) {
|
|
21
|
+
return `use ${this.quote(schema)}`;
|
|
22
|
+
}
|
|
23
|
+
getResetSchemaSQL(defaultSchema) {
|
|
24
|
+
return `use ${this.quote(defaultSchema)}`;
|
|
25
|
+
}
|
|
26
|
+
supportsMigrationSchema() {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
async tableExists(connection, tableName, schemaName, ctx) {
|
|
30
|
+
// MySQL "schema" = database — when none is requested, probe the connection's current DB
|
|
31
|
+
// via `schema()` rather than the base impl's `getDefaultSchemaName()` (which is undefined here).
|
|
32
|
+
const schemaClause = schemaName
|
|
33
|
+
? `table_schema = ${this.platform.quoteValue(schemaName)}`
|
|
34
|
+
: `table_schema = schema()`;
|
|
35
|
+
const rows = await connection.execute(`select 1 from information_schema.tables where ${schemaClause} and table_name = ${this.platform.quoteValue(tableName)}`, [], 'all', ctx);
|
|
36
|
+
return rows.length > 0;
|
|
37
|
+
}
|
|
16
38
|
disableForeignKeysSQL() {
|
|
17
39
|
return 'set foreign_key_checks = 0;';
|
|
18
40
|
}
|
|
@@ -31,7 +53,7 @@ export class MySqlSchemaHelper extends SchemaHelper {
|
|
|
31
53
|
return sql;
|
|
32
54
|
}
|
|
33
55
|
getListTablesSQL() {
|
|
34
|
-
return `select table_name as table_name, nullif(table_schema, schema()) as schema_name, table_comment as table_comment from information_schema.tables where table_type = 'BASE TABLE' and table_schema = schema()`;
|
|
56
|
+
return `select table_name as table_name, nullif(table_schema, schema()) as schema_name, table_comment as table_comment, table_collation as table_collation from information_schema.tables where table_type = 'BASE TABLE' and table_schema = schema()`;
|
|
35
57
|
}
|
|
36
58
|
getListViewsSQL() {
|
|
37
59
|
return `select table_name as view_name, nullif(table_schema, schema()) as schema_name, view_definition from information_schema.views where table_schema = schema()`;
|
|
@@ -64,11 +86,16 @@ export class MySqlSchemaHelper extends SchemaHelper {
|
|
|
64
86
|
const checks = await this.getAllChecks(connection, tables, ctx);
|
|
65
87
|
const fks = await this.getAllForeignKeys(connection, tables, ctx);
|
|
66
88
|
const enums = await this.getAllEnumDefinitions(connection, tables, ctx);
|
|
89
|
+
const triggers = await this.getAllTriggers(connection, tables);
|
|
67
90
|
for (const t of tables) {
|
|
68
91
|
const key = this.getTableKey(t);
|
|
69
92
|
const table = schema.addTable(t.table_name, t.schema_name, t.table_comment);
|
|
93
|
+
table.collation = t.table_collation ?? undefined;
|
|
70
94
|
const pks = await this.getPrimaryKeys(connection, indexes[key], table.name, table.schema);
|
|
71
95
|
table.init(columns[key], indexes[key], checks[key], pks, fks[key], enums[key]);
|
|
96
|
+
if (triggers[key]) {
|
|
97
|
+
table.setTriggers(triggers[key]);
|
|
98
|
+
}
|
|
72
99
|
}
|
|
73
100
|
}
|
|
74
101
|
async getAllIndexes(connection, tables, ctx) {
|
|
@@ -80,8 +107,11 @@ export class MySqlSchemaHelper extends SchemaHelper {
|
|
|
80
107
|
const ret = {};
|
|
81
108
|
for (const index of allIndexes) {
|
|
82
109
|
const key = this.getTableKey(index);
|
|
110
|
+
const partialMatch = !index.column_name && typeof index.expression === 'string'
|
|
111
|
+
? MySqlSchemaHelper.PARTIAL_INDEX_RE.exec(index.expression)
|
|
112
|
+
: null;
|
|
83
113
|
const indexDef = {
|
|
84
|
-
columnNames: [index.column_name],
|
|
114
|
+
columnNames: [partialMatch ? partialMatch[2] : index.column_name],
|
|
85
115
|
keyName: index.index_name,
|
|
86
116
|
unique: !index.non_unique,
|
|
87
117
|
primary: index.index_name === 'PRIMARY',
|
|
@@ -109,7 +139,10 @@ export class MySqlSchemaHelper extends SchemaHelper {
|
|
|
109
139
|
if (index.is_visible === 'NO') {
|
|
110
140
|
indexDef.invisible = true;
|
|
111
141
|
}
|
|
112
|
-
if (
|
|
142
|
+
if (partialMatch) {
|
|
143
|
+
indexDef.where = partialMatch[1].trim();
|
|
144
|
+
}
|
|
145
|
+
else if (!index.column_name || index.expression?.match(/ where /i)) {
|
|
113
146
|
indexDef.expression = index.expression; // required for the `getCreateIndexSQL()` call
|
|
114
147
|
indexDef.expression = this.getCreateIndexSQL(index.table_name, indexDef, !!index.expression);
|
|
115
148
|
}
|
|
@@ -146,10 +179,15 @@ export class MySqlSchemaHelper extends SchemaHelper {
|
|
|
146
179
|
return this.appendMySqlIndexSuffix(sql, index);
|
|
147
180
|
}
|
|
148
181
|
/**
|
|
149
|
-
* Build the column list for a MySQL index
|
|
150
|
-
*
|
|
182
|
+
* Build the column list for a MySQL index. MySQL requires collation via an expression:
|
|
183
|
+
* `(column COLLATE collation_name)`. Partial indexes (`where`) are emulated via functional
|
|
184
|
+
* indexes — requires MySQL 8.0.13+. MariaDB does not support inline functional indexes
|
|
185
|
+
* and overrides to throw at a higher level.
|
|
151
186
|
*/
|
|
152
187
|
getIndexColumns(index) {
|
|
188
|
+
if (index.where) {
|
|
189
|
+
return this.emulatePartialIndexColumns(index);
|
|
190
|
+
}
|
|
153
191
|
if (index.columns?.length) {
|
|
154
192
|
return index.columns
|
|
155
193
|
.map(col => {
|
|
@@ -192,22 +230,25 @@ export class MySqlSchemaHelper extends SchemaHelper {
|
|
|
192
230
|
return sql;
|
|
193
231
|
}
|
|
194
232
|
async getAllColumns(connection, tables, ctx) {
|
|
195
|
-
const sql = `select table_name as table_name,
|
|
196
|
-
nullif(table_schema, schema()) as schema_name,
|
|
197
|
-
column_name as column_name,
|
|
198
|
-
column_default as column_default,
|
|
199
|
-
nullif(column_comment, '') as column_comment,
|
|
200
|
-
is_nullable as is_nullable,
|
|
201
|
-
data_type as data_type,
|
|
202
|
-
column_type as column_type,
|
|
203
|
-
column_key as column_key,
|
|
204
|
-
extra as extra,
|
|
205
|
-
generation_expression as generation_expression,
|
|
206
|
-
numeric_precision as numeric_precision,
|
|
207
|
-
numeric_scale as numeric_scale,
|
|
208
|
-
ifnull(datetime_precision, character_maximum_length) length
|
|
209
|
-
|
|
210
|
-
|
|
233
|
+
const sql = `select c.table_name as table_name,
|
|
234
|
+
nullif(c.table_schema, schema()) as schema_name,
|
|
235
|
+
c.column_name as column_name,
|
|
236
|
+
c.column_default as column_default,
|
|
237
|
+
nullif(c.column_comment, '') as column_comment,
|
|
238
|
+
c.is_nullable as is_nullable,
|
|
239
|
+
c.data_type as data_type,
|
|
240
|
+
c.column_type as column_type,
|
|
241
|
+
c.column_key as column_key,
|
|
242
|
+
c.extra as extra,
|
|
243
|
+
c.generation_expression as generation_expression,
|
|
244
|
+
c.numeric_precision as numeric_precision,
|
|
245
|
+
c.numeric_scale as numeric_scale,
|
|
246
|
+
ifnull(c.datetime_precision, c.character_maximum_length) length,
|
|
247
|
+
nullif(c.collation_name, t.table_collation) as collation_name
|
|
248
|
+
from information_schema.columns c
|
|
249
|
+
join information_schema.tables t on t.table_schema = c.table_schema and t.table_name = c.table_name
|
|
250
|
+
where c.table_schema = database() and c.table_name in (${tables.map(t => this.platform.quoteValue(t.table_name))})
|
|
251
|
+
order by c.ordinal_position`;
|
|
211
252
|
const allColumns = await connection.execute(sql, [], 'all', ctx);
|
|
212
253
|
const str = (val) => (val != null ? '' + val : val);
|
|
213
254
|
const extra = (val) => val.replace(/auto_increment|default_generated|(stored|virtual) generated/i, '').trim() || undefined;
|
|
@@ -238,6 +279,7 @@ export class MySqlSchemaHelper extends SchemaHelper {
|
|
|
238
279
|
precision: col.numeric_precision,
|
|
239
280
|
scale: col.numeric_scale,
|
|
240
281
|
comment: col.column_comment,
|
|
282
|
+
collation: col.collation_name ?? undefined,
|
|
241
283
|
extra: extra(col.extra),
|
|
242
284
|
generated,
|
|
243
285
|
});
|
|
@@ -264,6 +306,85 @@ export class MySqlSchemaHelper extends SchemaHelper {
|
|
|
264
306
|
}
|
|
265
307
|
return ret;
|
|
266
308
|
}
|
|
309
|
+
/** Generates SQL to create MySQL triggers. MySQL requires one trigger per event. */
|
|
310
|
+
createTrigger(table, trigger) {
|
|
311
|
+
if (trigger.expression) {
|
|
312
|
+
return trigger.expression;
|
|
313
|
+
}
|
|
314
|
+
/* v8 ignore next 3 */
|
|
315
|
+
if (trigger.timing === 'instead of') {
|
|
316
|
+
throw new Error(`MySQL does not support INSTEAD OF triggers. Use BEFORE or AFTER for trigger "${trigger.name}".`);
|
|
317
|
+
}
|
|
318
|
+
/* v8 ignore next 5 */
|
|
319
|
+
if (trigger.forEach === 'statement') {
|
|
320
|
+
throw new Error(`MySQL does not support FOR EACH STATEMENT triggers. Use FOR EACH ROW for trigger "${trigger.name}".`);
|
|
321
|
+
}
|
|
322
|
+
const timing = trigger.timing.toUpperCase();
|
|
323
|
+
const ret = [];
|
|
324
|
+
for (const event of trigger.events) {
|
|
325
|
+
const name = trigger.events.length > 1 ? `${trigger.name}_${event}` : trigger.name;
|
|
326
|
+
ret.push(`create trigger ${this.quote(name)} ${timing} ${event.toUpperCase()} on ${table.getQuotedName()} for each ROW begin ${trigger.body}; end`);
|
|
327
|
+
}
|
|
328
|
+
return ret.join(';\n');
|
|
329
|
+
}
|
|
330
|
+
async getAllTriggers(connection, tables) {
|
|
331
|
+
const names = tables.map(t => this.platform.quoteValue(t.table_name)).join(', ');
|
|
332
|
+
const sql = `select trigger_name as trigger_name, event_object_table as table_name, nullif(event_object_schema, schema()) as schema_name,
|
|
333
|
+
event_manipulation as event, action_timing as timing,
|
|
334
|
+
action_orientation as for_each, action_statement as body
|
|
335
|
+
from information_schema.triggers
|
|
336
|
+
where event_object_schema = database()
|
|
337
|
+
and event_object_table in (${names})
|
|
338
|
+
order by trigger_name, event_manipulation`;
|
|
339
|
+
const allTriggers = await connection.execute(sql);
|
|
340
|
+
const ret = {};
|
|
341
|
+
// First pass: collect all raw trigger names per table to detect multi-event groups.
|
|
342
|
+
// A base name is only used for grouping if multiple triggers share it (e.g. trg_multi_insert + trg_multi_update).
|
|
343
|
+
const namesByTable = new Map();
|
|
344
|
+
for (const row of allTriggers) {
|
|
345
|
+
const key = this.getTableKey(row);
|
|
346
|
+
namesByTable.set(key, [...(namesByTable.get(key) ?? []), row.trigger_name]);
|
|
347
|
+
}
|
|
348
|
+
const triggerMap = new Map();
|
|
349
|
+
for (const row of allTriggers) {
|
|
350
|
+
const key = this.getTableKey(row);
|
|
351
|
+
const eventLower = row.event.toLowerCase();
|
|
352
|
+
const tableNames = namesByTable.get(key) ?? [];
|
|
353
|
+
// Only strip event suffix when another trigger with the same base exists for this table
|
|
354
|
+
const candidateBase = row.trigger_name.endsWith(`_${eventLower}`)
|
|
355
|
+
? row.trigger_name.slice(0, -eventLower.length - 1)
|
|
356
|
+
: null;
|
|
357
|
+
const baseName = candidateBase && tableNames.some(n => n !== row.trigger_name && n.startsWith(`${candidateBase}_`))
|
|
358
|
+
? candidateBase
|
|
359
|
+
: row.trigger_name;
|
|
360
|
+
const dedupeKey = `${key}:${baseName}`;
|
|
361
|
+
if (triggerMap.has(dedupeKey)) {
|
|
362
|
+
const existing = triggerMap.get(dedupeKey);
|
|
363
|
+
const event = eventLower;
|
|
364
|
+
if (!existing.events.includes(event)) {
|
|
365
|
+
existing.events.push(event);
|
|
366
|
+
}
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
ret[key] ??= [];
|
|
370
|
+
// Strip BEGIN/END wrapper from MySQL action_statement
|
|
371
|
+
let body = row.body ?? '';
|
|
372
|
+
const beginEndMatch = /^\s*begin\s+([\s\S]*)\s*end\s*$/i.exec(body);
|
|
373
|
+
if (beginEndMatch) {
|
|
374
|
+
body = beginEndMatch[1].trim().replace(/;\s*$/, '');
|
|
375
|
+
}
|
|
376
|
+
const trigger = {
|
|
377
|
+
name: baseName,
|
|
378
|
+
timing: row.timing.toLowerCase(),
|
|
379
|
+
events: [eventLower],
|
|
380
|
+
forEach: (row.for_each ?? 'row').toLowerCase(),
|
|
381
|
+
body,
|
|
382
|
+
};
|
|
383
|
+
ret[key].push(trigger);
|
|
384
|
+
triggerMap.set(dedupeKey, trigger);
|
|
385
|
+
}
|
|
386
|
+
return ret;
|
|
387
|
+
}
|
|
267
388
|
async getAllForeignKeys(connection, tables, ctx) {
|
|
268
389
|
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
390
|
from information_schema.key_column_usage k
|
|
@@ -319,10 +440,13 @@ export class MySqlSchemaHelper extends SchemaHelper {
|
|
|
319
440
|
const col = this.createTableColumn(column, table, changedProperties);
|
|
320
441
|
return [`alter table ${table.getQuotedName()} modify ${col}`];
|
|
321
442
|
}
|
|
443
|
+
// MySQL MODIFY/CHANGE resets omitted column attributes to the table default, so collation must
|
|
444
|
+
// be re-emitted on rename and comment-only paths to preserve a non-default column collation.
|
|
322
445
|
getColumnDeclarationSQL(col) {
|
|
323
446
|
let ret = col.type;
|
|
324
447
|
ret += col.unsigned ? ' unsigned' : '';
|
|
325
448
|
ret += col.autoincrement ? ' auto_increment' : '';
|
|
449
|
+
ret += col.collation ? ` ${this.getCollateSQL(col.collation)}` : '';
|
|
326
450
|
ret += ' ';
|
|
327
451
|
ret += col.nullable ? 'null' : 'not null';
|
|
328
452
|
ret += col.default ? ' default ' + col.default : '';
|
|
@@ -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 {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type EntityName } from '@mikro-orm/core';
|
|
2
|
+
import { SqlEntityManager } from '../../SqlEntityManager.js';
|
|
3
|
+
import type { AbstractSqlDriver } from '../../AbstractSqlDriver.js';
|
|
4
|
+
/**
|
|
5
|
+
* Shared base class for PostgreSQL-flavoured entity managers (`pg`, `pglite`).
|
|
6
|
+
* Adds Postgres-only helpers on top of `SqlEntityManager`.
|
|
7
|
+
*/
|
|
8
|
+
export declare class BasePostgreSqlEntityManager<Driver extends AbstractSqlDriver = AbstractSqlDriver> extends SqlEntityManager<Driver> {
|
|
9
|
+
/**
|
|
10
|
+
* Refreshes a materialized view.
|
|
11
|
+
*
|
|
12
|
+
* @param entityName - The entity name or class of the materialized view
|
|
13
|
+
* @param options - Optional settings
|
|
14
|
+
* @param options.concurrently - If true, refreshes the view concurrently (requires a unique index on the view)
|
|
15
|
+
*/
|
|
16
|
+
refreshMaterializedView<Entity extends object>(entityName: EntityName<Entity>, options?: {
|
|
17
|
+
concurrently?: boolean;
|
|
18
|
+
}): Promise<void>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { SqlEntityManager } from '../../SqlEntityManager.js';
|
|
2
|
+
/**
|
|
3
|
+
* Shared base class for PostgreSQL-flavoured entity managers (`pg`, `pglite`).
|
|
4
|
+
* Adds Postgres-only helpers on top of `SqlEntityManager`.
|
|
5
|
+
*/
|
|
6
|
+
export class BasePostgreSqlEntityManager extends SqlEntityManager {
|
|
7
|
+
/**
|
|
8
|
+
* Refreshes a materialized view.
|
|
9
|
+
*
|
|
10
|
+
* @param entityName - The entity name or class of the materialized view
|
|
11
|
+
* @param options - Optional settings
|
|
12
|
+
* @param options.concurrently - If true, refreshes the view concurrently (requires a unique index on the view)
|
|
13
|
+
*/
|
|
14
|
+
async refreshMaterializedView(entityName, options) {
|
|
15
|
+
const meta = this.getMetadata(entityName);
|
|
16
|
+
if (!meta.view || !meta.materialized) {
|
|
17
|
+
throw new Error(`Entity ${meta.className} is not a materialized view`);
|
|
18
|
+
}
|
|
19
|
+
const helper = this.getDriver().getPlatform().getSchemaHelper();
|
|
20
|
+
const schema = meta.schema ?? this.config.get('schema');
|
|
21
|
+
const sql = helper.refreshMaterializedView(meta.tableName, schema, options?.concurrently);
|
|
22
|
+
await this.execute(sql);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -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;
|
|
@@ -69,6 +71,12 @@ export declare class BasePostgreSqlPlatform extends AbstractSqlPlatform {
|
|
|
69
71
|
}): string[];
|
|
70
72
|
marshallArray(values: string[]): string;
|
|
71
73
|
unmarshallArray(value: string): string[];
|
|
74
|
+
escape(value: any): string;
|
|
75
|
+
/**
|
|
76
|
+
* Ported from PostgreSQL 9.2.4 source code (`src/interfaces/libpq/fe-exec.c`),
|
|
77
|
+
* matching `pg.Client.prototype.escapeLiteral` so we don't need a `pg` dep here.
|
|
78
|
+
*/
|
|
79
|
+
private escapeLiteral;
|
|
72
80
|
getVarcharTypeDeclarationSQL(column: {
|
|
73
81
|
length?: number;
|
|
74
82
|
}): string;
|
|
@@ -110,4 +118,5 @@ export declare class BasePostgreSqlPlatform extends AbstractSqlPlatform {
|
|
|
110
118
|
}[]): string;
|
|
111
119
|
getJsonArrayElementPropertySQL(alias: string, property: string, type: string): string;
|
|
112
120
|
getDefaultClientUrl(): string;
|
|
121
|
+
caseInsensitiveCollationNames(): boolean;
|
|
113
122
|
}
|
|
@@ -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) {
|
|
@@ -194,6 +212,50 @@ export class BasePostgreSqlPlatform extends AbstractSqlPlatform {
|
|
|
194
212
|
return v;
|
|
195
213
|
});
|
|
196
214
|
}
|
|
215
|
+
escape(value) {
|
|
216
|
+
if (typeof value === 'bigint') {
|
|
217
|
+
value = value.toString();
|
|
218
|
+
}
|
|
219
|
+
if (typeof value === 'string') {
|
|
220
|
+
return this.escapeLiteral(value);
|
|
221
|
+
}
|
|
222
|
+
if (value instanceof Date) {
|
|
223
|
+
return `'${this.formatDate(value)}'`;
|
|
224
|
+
}
|
|
225
|
+
if (ArrayBuffer.isView(value)) {
|
|
226
|
+
return `E'\\\\x${value.toString('hex')}'`;
|
|
227
|
+
}
|
|
228
|
+
if (Array.isArray(value)) {
|
|
229
|
+
return value.map(v => this.escape(v)).join(', ');
|
|
230
|
+
}
|
|
231
|
+
return value;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Ported from PostgreSQL 9.2.4 source code (`src/interfaces/libpq/fe-exec.c`),
|
|
235
|
+
* matching `pg.Client.prototype.escapeLiteral` so we don't need a `pg` dep here.
|
|
236
|
+
*/
|
|
237
|
+
escapeLiteral(str) {
|
|
238
|
+
let hasBackslash = false;
|
|
239
|
+
let escaped = `'`;
|
|
240
|
+
for (let i = 0; i < str.length; i++) {
|
|
241
|
+
const c = str[i];
|
|
242
|
+
if (c === `'`) {
|
|
243
|
+
escaped += c + c;
|
|
244
|
+
}
|
|
245
|
+
else if (c === '\\') {
|
|
246
|
+
escaped += c + c;
|
|
247
|
+
hasBackslash = true;
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
escaped += c;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
escaped += `'`;
|
|
254
|
+
if (hasBackslash) {
|
|
255
|
+
escaped = ` E${escaped}`;
|
|
256
|
+
}
|
|
257
|
+
return escaped;
|
|
258
|
+
}
|
|
197
259
|
getVarcharTypeDeclarationSQL(column) {
|
|
198
260
|
if (column.length === -1) {
|
|
199
261
|
return 'varchar';
|
|
@@ -228,9 +290,9 @@ export class BasePostgreSqlPlatform extends AbstractSqlPlatform {
|
|
|
228
290
|
lastOperator = '->';
|
|
229
291
|
}
|
|
230
292
|
if (path.length === 0) {
|
|
231
|
-
return cast(`${root}${lastOperator}
|
|
293
|
+
return cast(`${root}${lastOperator}${this.quoteValue(last)}`);
|
|
232
294
|
}
|
|
233
|
-
return cast(`${root}->${path.map(a => this.quoteValue(a)).join('->')}${lastOperator}
|
|
295
|
+
return cast(`${root}->${path.map(a => this.quoteValue(a)).join('->')}${lastOperator}${this.quoteValue(last)}`);
|
|
234
296
|
}
|
|
235
297
|
getJsonIndexDefinition(index) {
|
|
236
298
|
return index.columnNames.map(column => {
|
|
@@ -250,7 +312,8 @@ export class BasePostgreSqlPlatform extends AbstractSqlPlatform {
|
|
|
250
312
|
if (RawQueryFragment.isKnownFragment(id)) {
|
|
251
313
|
return super.quoteIdentifier(id);
|
|
252
314
|
}
|
|
253
|
-
|
|
315
|
+
const escaped = id.toString().replaceAll(quote, quote + quote);
|
|
316
|
+
return `${quote}${escaped.replace('.', `${quote}.${quote}`)}${quote}`;
|
|
254
317
|
}
|
|
255
318
|
pad(number, digits) {
|
|
256
319
|
return String(number).padStart(digits, '0');
|
|
@@ -362,4 +425,7 @@ export class BasePostgreSqlPlatform extends AbstractSqlPlatform {
|
|
|
362
425
|
getDefaultClientUrl() {
|
|
363
426
|
return 'postgresql://postgres@127.0.0.1:5432';
|
|
364
427
|
}
|
|
428
|
+
caseInsensitiveCollationNames() {
|
|
429
|
+
return false;
|
|
430
|
+
}
|
|
365
431
|
}
|
|
@@ -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,7 +14,12 @@ 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;
|
|
20
|
+
getSetSchemaSQL(schema: string): string;
|
|
21
|
+
getResetSchemaSQL(_defaultSchema: string): string;
|
|
22
|
+
supportsMigrationSchema(): boolean;
|
|
18
23
|
getCreateDatabaseSQL(name: string): string;
|
|
19
24
|
getListTablesSQL(): string;
|
|
20
25
|
private getIgnoredViewsCondition;
|
|
@@ -23,11 +28,22 @@ export declare class PostgreSqlSchemaHelper extends SchemaHelper {
|
|
|
23
28
|
getListMaterializedViewsSQL(): string;
|
|
24
29
|
loadMaterializedViews(schema: DatabaseSchema, connection: AbstractSqlConnection, schemaName?: string, ctx?: Transaction): Promise<void>;
|
|
25
30
|
createMaterializedView(name: string, schema: string | undefined, definition: string, withData?: boolean): string;
|
|
31
|
+
createTable(table: DatabaseTable, alter?: boolean): string[];
|
|
26
32
|
dropMaterializedViewIfExists(name: string, schema?: string): string;
|
|
27
33
|
refreshMaterializedView(name: string, schema?: string, concurrently?: boolean): string;
|
|
28
34
|
getNamespaces(connection: AbstractSqlConnection, ctx?: Transaction): Promise<string[]>;
|
|
29
35
|
private getIgnoredNamespacesConditionSQL;
|
|
30
36
|
loadInformationSchema(schema: DatabaseSchema, connection: AbstractSqlConnection, tables: Table[], schemas?: string[], ctx?: Transaction): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Introspects direct partitions only: the `pg_inherits` join surfaces a parent's children but
|
|
39
|
+
* does not recurse into sub-partitioning (e.g. hash-of-range). Declarative `partitionBy`
|
|
40
|
+
* metadata does not express multi-level partitioning either, so grandchildren are intentionally
|
|
41
|
+
* invisible to schema diffing.
|
|
42
|
+
*
|
|
43
|
+
* Entries with an undefined schema bucket are resolved against `current_schema()` so they do
|
|
44
|
+
* not match same-named tables in unrelated schemas.
|
|
45
|
+
*/
|
|
46
|
+
getPartitions(connection: AbstractSqlConnection, tablesBySchemas: Map<string | undefined, Table[]>, ctx?: Transaction): Promise<Dictionary<TablePartitioning>>;
|
|
31
47
|
getAllIndexes(connection: AbstractSqlConnection, tables: Table[], ctx?: Transaction): Promise<Dictionary<IndexDef[]>>;
|
|
32
48
|
/**
|
|
33
49
|
* Parses column definitions from the full CREATE INDEX expression.
|
|
@@ -49,6 +65,19 @@ export declare class PostgreSqlSchemaHelper extends SchemaHelper {
|
|
|
49
65
|
items: string[];
|
|
50
66
|
}>, ctx?: Transaction): Promise<Dictionary<Column[]>>;
|
|
51
67
|
getAllChecks(connection: AbstractSqlConnection, tablesBySchemas: Map<string | undefined, Table[]>, ctx?: Transaction): Promise<Dictionary<CheckDef[]>>;
|
|
68
|
+
/** Generates SQL to create a PostgreSQL trigger and its associated function. */
|
|
69
|
+
createTrigger(table: DatabaseTable, trigger: SqlTriggerDef): string;
|
|
70
|
+
/** Generates SQL to drop a PostgreSQL trigger and its associated function. */
|
|
71
|
+
dropTrigger(table: DatabaseTable, trigger: SqlTriggerDef): string;
|
|
72
|
+
private getSchemaQualifiedTriggerFnName;
|
|
73
|
+
/**
|
|
74
|
+
* Resolves the real name of the implicit 'default' collation (the DB's `datcollate`),
|
|
75
|
+
* so the comparator can treat `@Property({ collation: '<datcollate>' })` as equivalent
|
|
76
|
+
* to a column that introspects as using the default.
|
|
77
|
+
*/
|
|
78
|
+
getDatabaseCollation(connection: AbstractSqlConnection, ctx?: Transaction): Promise<string | undefined>;
|
|
79
|
+
getAllTriggers(connection: AbstractSqlConnection, tablesBySchemas: Map<string | undefined, Table[]>): Promise<Dictionary<SqlTriggerDef[]>>;
|
|
80
|
+
private getTriggersSQL;
|
|
52
81
|
getAllForeignKeys(connection: AbstractSqlConnection, tablesBySchemas: Map<string | undefined, Table[]>, ctx?: Transaction): Promise<Dictionary<Dictionary<ForeignKey>>>;
|
|
53
82
|
getNativeEnumDefinitions(connection: AbstractSqlConnection, schemas: string[], ctx?: Transaction): Promise<Dictionary<{
|
|
54
83
|
name: string;
|
|
@@ -59,6 +88,7 @@ export declare class PostgreSqlSchemaHelper extends SchemaHelper {
|
|
|
59
88
|
getDropNativeEnumSQL(name: string, schema?: string): string;
|
|
60
89
|
getAlterNativeEnumSQL(name: string, schema?: string, value?: string, items?: string[], oldItems?: string[]): string;
|
|
61
90
|
private getEnumDefinitions;
|
|
91
|
+
protected getCollateSQL(collation: string): string;
|
|
62
92
|
createTableColumn(column: Column, table: DatabaseTable): string | undefined;
|
|
63
93
|
getPreAlterTable(tableDiff: TableDifference, safe: boolean): string[];
|
|
64
94
|
castColumn(name: string, type: string): string;
|