@mikro-orm/sql 7.0.17-dev.9 → 7.0.18-dev.0
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 +27 -6
- package/AbstractSqlDriver.d.ts +25 -1
- package/AbstractSqlDriver.js +356 -20
- package/AbstractSqlPlatform.d.ts +13 -2
- package/AbstractSqlPlatform.js +16 -3
- package/PivotCollectionPersister.d.ts +2 -2
- package/PivotCollectionPersister.js +19 -3
- package/README.md +2 -1
- package/SqlEntityManager.d.ts +46 -3
- package/SqlEntityManager.js +77 -7
- package/SqlMikroORM.d.ts +4 -4
- 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 +19 -3
- package/dialects/mysql/MySqlSchemaHelper.js +254 -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 +8 -0
- package/dialects/postgresql/BasePostgreSqlPlatform.js +50 -0
- package/dialects/postgresql/PostgreSqlSchemaHelper.d.ts +38 -1
- package/dialects/postgresql/PostgreSqlSchemaHelper.js +341 -6
- 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/SqliteSchemaHelper.d.ts +7 -1
- package/dialects/sqlite/SqliteSchemaHelper.js +131 -2
- package/package.json +4 -4
- package/query/NativeQueryBuilder.d.ts +6 -0
- package/query/NativeQueryBuilder.js +16 -1
- package/query/QueryBuilder.d.ts +83 -1
- package/query/QueryBuilder.js +181 -8
- package/schema/DatabaseSchema.d.ts +29 -2
- package/schema/DatabaseSchema.js +137 -0
- package/schema/DatabaseTable.d.ts +20 -1
- package/schema/DatabaseTable.js +62 -3
- package/schema/SchemaComparator.d.ts +19 -0
- package/schema/SchemaComparator.js +250 -1
- package/schema/SchemaHelper.d.ts +77 -1
- package/schema/SchemaHelper.js +279 -5
- package/schema/SqlSchemaGenerator.d.ts +2 -2
- package/schema/SqlSchemaGenerator.js +47 -10
- package/schema/partitioning.d.ts +13 -0
- package/schema/partitioning.js +326 -0
- package/typings.d.ts +69 -2
|
@@ -15,6 +15,7 @@ const SPATIALITE_VIEWS = [
|
|
|
15
15
|
'ElementaryGeometries',
|
|
16
16
|
];
|
|
17
17
|
export class SqliteSchemaHelper extends SchemaHelper {
|
|
18
|
+
static PARTIAL_WHERE_RE = /\swhere\s+(.+?)\s*$/is;
|
|
18
19
|
disableForeignKeysSQL() {
|
|
19
20
|
return 'pragma foreign_keys = off;';
|
|
20
21
|
}
|
|
@@ -30,6 +31,10 @@ export class SqliteSchemaHelper extends SchemaHelper {
|
|
|
30
31
|
getDropNamespaceSQL(name) {
|
|
31
32
|
return '';
|
|
32
33
|
}
|
|
34
|
+
async tableExists(connection, tableName, _schemaName, ctx) {
|
|
35
|
+
const rows = await connection.execute(`select name from sqlite_master where type = 'table' and name = ${this.platform.quoteValue(tableName)}`, [], 'all', ctx);
|
|
36
|
+
return rows.length > 0;
|
|
37
|
+
}
|
|
33
38
|
getListTablesSQL() {
|
|
34
39
|
return (`select name as table_name from sqlite_master where type = 'table' and name != 'sqlite_sequence' and name != 'geometry_columns' and name != 'spatial_ref_sys' ` +
|
|
35
40
|
`union all select name as table_name from sqlite_temp_master where type = 'table' order by name`);
|
|
@@ -101,7 +106,9 @@ export class SqliteSchemaHelper extends SchemaHelper {
|
|
|
101
106
|
const pks = await this.getPrimaryKeys(connection, indexes, table.name, table.schema, ctx);
|
|
102
107
|
const fks = await this.getForeignKeys(connection, table.name, table.schema, ctx);
|
|
103
108
|
const enums = this.extractEnumValuesFromChecks(checks);
|
|
109
|
+
const triggers = await this.getTableTriggers(connection, table.name);
|
|
104
110
|
table.init(cols, indexes, checks, pks, fks, enums);
|
|
111
|
+
table.setTriggers(triggers);
|
|
105
112
|
}
|
|
106
113
|
}
|
|
107
114
|
createTable(table, alter) {
|
|
@@ -140,6 +147,9 @@ export class SqliteSchemaHelper extends SchemaHelper {
|
|
|
140
147
|
for (const index of table.getIndexes()) {
|
|
141
148
|
this.append(ret, this.createIndex(index, table));
|
|
142
149
|
}
|
|
150
|
+
for (const trigger of table.getTriggers()) {
|
|
151
|
+
this.append(ret, this.createTrigger(table, trigger));
|
|
152
|
+
}
|
|
143
153
|
return ret;
|
|
144
154
|
}
|
|
145
155
|
createTableColumn(column, table, _changedProperties) {
|
|
@@ -159,6 +169,7 @@ export class SqliteSchemaHelper extends SchemaHelper {
|
|
|
159
169
|
col.push(`check (${checks[check].expression})`);
|
|
160
170
|
checks.splice(check, 1);
|
|
161
171
|
}
|
|
172
|
+
Utils.runIfNotEmpty(() => col.push(this.getCollateSQL(column.collation)), column.collation);
|
|
162
173
|
Utils.runIfNotEmpty(() => col.push('null'), column.nullable);
|
|
163
174
|
Utils.runIfNotEmpty(() => col.push('not null'), !column.nullable && !column.generated);
|
|
164
175
|
Utils.runIfNotEmpty(() => col.push('primary key'), column.primary);
|
|
@@ -211,10 +222,10 @@ export class SqliteSchemaHelper extends SchemaHelper {
|
|
|
211
222
|
if (index.columnNames.some(column => column.includes('.'))) {
|
|
212
223
|
// JSON columns can have unique index but not unique constraint, and we need to distinguish those, so we can properly drop them
|
|
213
224
|
const columns = this.platform.getJsonIndexDefinition(index);
|
|
214
|
-
return `${sqlPrefix} (${columns.join(', ')})`;
|
|
225
|
+
return `${sqlPrefix} (${columns.join(', ')})${this.getIndexWhereClause(index)}`;
|
|
215
226
|
}
|
|
216
227
|
// Use getIndexColumns to support advanced options like sort order and collation
|
|
217
|
-
return `${sqlPrefix} (${this.getIndexColumns(index)})`;
|
|
228
|
+
return `${sqlPrefix} (${this.getIndexColumns(index)})${this.getIndexWhereClause(index)}`;
|
|
218
229
|
}
|
|
219
230
|
parseTableDefinition(sql, cols) {
|
|
220
231
|
const columns = {};
|
|
@@ -285,6 +296,17 @@ export class SqliteSchemaHelper extends SchemaHelper {
|
|
|
285
296
|
generated = `${match[2]} ${storage}`;
|
|
286
297
|
}
|
|
287
298
|
}
|
|
299
|
+
// Strip string literals first (their contents could contain unbalanced parens), then
|
|
300
|
+
// repeatedly strip the innermost balanced `(...)` until none remain — a single pass would
|
|
301
|
+
// only remove the innermost level, leaving `collate` tokens inside nested CHECK/default
|
|
302
|
+
// expressions exposed to the column-collation regex.
|
|
303
|
+
let cleanDef = (columnDefinitions[col.name]?.definition ?? '').replace(/'[^']*'/g, '').replace(/"[^"]*"/g, '');
|
|
304
|
+
let prev;
|
|
305
|
+
do {
|
|
306
|
+
prev = cleanDef;
|
|
307
|
+
cleanDef = cleanDef.replace(/\([^()]*\)/g, '');
|
|
308
|
+
} while (cleanDef !== prev);
|
|
309
|
+
const collationMatch = /\bcollate\s+([`"']?)([\w\-.]+)\1/i.exec(cleanDef);
|
|
288
310
|
return {
|
|
289
311
|
name: col.name,
|
|
290
312
|
type: col.type,
|
|
@@ -295,6 +317,7 @@ export class SqliteSchemaHelper extends SchemaHelper {
|
|
|
295
317
|
unsigned: false,
|
|
296
318
|
autoincrement: !composite && col.pk && this.platform.isNumericColumn(mappedType) && hasAutoincrement,
|
|
297
319
|
generated,
|
|
320
|
+
collation: collationMatch ? collationMatch[2] : undefined,
|
|
298
321
|
};
|
|
299
322
|
});
|
|
300
323
|
}
|
|
@@ -350,6 +373,16 @@ export class SqliteSchemaHelper extends SchemaHelper {
|
|
|
350
373
|
const sql = `pragma ${prefix}table_info(\`${tableName}\`)`;
|
|
351
374
|
const cols = await connection.execute(sql, [], 'all', ctx);
|
|
352
375
|
const indexes = await connection.execute(`pragma ${prefix}index_list(\`${tableName}\`)`, [], 'all', ctx);
|
|
376
|
+
// sqlite_master.sql holds the original CREATE INDEX statement — the only place a partial
|
|
377
|
+
// index's WHERE predicate is preserved (PRAGMA index_* don't expose it).
|
|
378
|
+
const indexSqls = await connection.execute(`select name, sql from ${prefix}sqlite_master where type = 'index' and tbl_name = ?`, [tableName], 'all', ctx);
|
|
379
|
+
const wherePredicates = new Map();
|
|
380
|
+
for (const row of indexSqls) {
|
|
381
|
+
const match = row.sql && SqliteSchemaHelper.PARTIAL_WHERE_RE.exec(row.sql);
|
|
382
|
+
if (match) {
|
|
383
|
+
wherePredicates.set(row.name, match[1].trim());
|
|
384
|
+
}
|
|
385
|
+
}
|
|
353
386
|
const ret = [];
|
|
354
387
|
for (const col of cols.filter(c => c.pk)) {
|
|
355
388
|
ret.push({
|
|
@@ -362,12 +395,14 @@ export class SqliteSchemaHelper extends SchemaHelper {
|
|
|
362
395
|
}
|
|
363
396
|
for (const index of indexes.filter(index => !this.isImplicitIndex(index.name))) {
|
|
364
397
|
const res = await connection.execute(`pragma ${prefix}index_info(\`${index.name}\`)`, [], 'all', ctx);
|
|
398
|
+
const where = wherePredicates.get(index.name);
|
|
365
399
|
ret.push(...res.map(row => ({
|
|
366
400
|
columnNames: [row.name],
|
|
367
401
|
keyName: index.name,
|
|
368
402
|
unique: !!index.unique,
|
|
369
403
|
constraint: !!index.unique,
|
|
370
404
|
primary: false,
|
|
405
|
+
...(where ? { where } : {}),
|
|
371
406
|
})));
|
|
372
407
|
}
|
|
373
408
|
return this.mapIndexes(ret);
|
|
@@ -501,8 +536,102 @@ export class SqliteSchemaHelper extends SchemaHelper {
|
|
|
501
536
|
this.append(ret, this.getRenameIndexSQL(diff.name, index, oldIndexName));
|
|
502
537
|
}
|
|
503
538
|
}
|
|
539
|
+
for (const trigger of Object.values(diff.removedTriggers)) {
|
|
540
|
+
this.append(ret, this.dropTrigger(diff.toTable, trigger));
|
|
541
|
+
}
|
|
542
|
+
for (const trigger of Object.values(diff.changedTriggers)) {
|
|
543
|
+
this.append(ret, this.dropTrigger(diff.toTable, trigger));
|
|
544
|
+
this.append(ret, this.createTrigger(diff.toTable, trigger));
|
|
545
|
+
}
|
|
546
|
+
for (const trigger of Object.values(diff.addedTriggers)) {
|
|
547
|
+
this.append(ret, this.createTrigger(diff.toTable, trigger));
|
|
548
|
+
}
|
|
504
549
|
return ret;
|
|
505
550
|
}
|
|
551
|
+
/** Generates SQL to create SQLite triggers. SQLite requires one trigger per event. */
|
|
552
|
+
createTrigger(table, trigger) {
|
|
553
|
+
if (trigger.expression) {
|
|
554
|
+
return trigger.expression;
|
|
555
|
+
}
|
|
556
|
+
const timing = trigger.timing.toUpperCase();
|
|
557
|
+
const forEach = trigger.forEach === 'statement' ? 'STATEMENT' : 'ROW';
|
|
558
|
+
const ret = [];
|
|
559
|
+
for (const event of trigger.events) {
|
|
560
|
+
const name = trigger.events.length > 1 ? `${trigger.name}_${event}` : trigger.name;
|
|
561
|
+
const when = trigger.when ? `\n when ${trigger.when}` : '';
|
|
562
|
+
ret.push(`create trigger ${this.quote(name)} ${timing} ${event.toUpperCase()} on ${table.getQuotedName()} for each ${forEach}${when} begin ${trigger.body}; end`);
|
|
563
|
+
}
|
|
564
|
+
return ret.join(';\n');
|
|
565
|
+
}
|
|
566
|
+
async getTableTriggers(connection, tableName) {
|
|
567
|
+
const rows = await connection.execute(`select name, sql from sqlite_master where type = 'trigger' and tbl_name = ?`, [tableName]);
|
|
568
|
+
// First pass: parse all triggers and collect names to detect multi-event groups
|
|
569
|
+
const parsedRows = [];
|
|
570
|
+
for (const row of rows) {
|
|
571
|
+
/* v8 ignore next 3 */
|
|
572
|
+
if (!row.sql) {
|
|
573
|
+
continue;
|
|
574
|
+
}
|
|
575
|
+
const parsed = this.parseTriggerDDL(row.sql, row.name);
|
|
576
|
+
if (parsed) {
|
|
577
|
+
parsedRows.push({ name: row.name, parsed });
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
const allNames = parsedRows.map(r => r.name);
|
|
581
|
+
const triggers = [];
|
|
582
|
+
const triggerMap = new Map();
|
|
583
|
+
for (const { name, parsed } of parsedRows) {
|
|
584
|
+
// Only strip event suffix when another trigger with the same base exists
|
|
585
|
+
const eventLower = parsed.events[0];
|
|
586
|
+
const candidateBase = name.endsWith(`_${eventLower}`) ? name.slice(0, -eventLower.length - 1) : null;
|
|
587
|
+
const baseName = candidateBase && allNames.some(n => n !== name && n.startsWith(`${candidateBase}_`)) ? candidateBase : name;
|
|
588
|
+
if (triggerMap.has(baseName)) {
|
|
589
|
+
const existing = triggerMap.get(baseName);
|
|
590
|
+
if (!existing.events.includes(parsed.events[0])) {
|
|
591
|
+
existing.events.push(parsed.events[0]);
|
|
592
|
+
}
|
|
593
|
+
continue;
|
|
594
|
+
}
|
|
595
|
+
const trigger = { ...parsed, name: baseName };
|
|
596
|
+
triggers.push(trigger);
|
|
597
|
+
triggerMap.set(baseName, trigger);
|
|
598
|
+
}
|
|
599
|
+
return triggers;
|
|
600
|
+
}
|
|
601
|
+
parseTriggerDDL(sql, name) {
|
|
602
|
+
// Split at the last top-level BEGIN to separate header from body,
|
|
603
|
+
// so that a WHEN clause containing the word "begin" in a string literal doesn't confuse parsing.
|
|
604
|
+
const beginIdx = sql.search(/\bbegin\b(?=[^]*$)/i);
|
|
605
|
+
/* v8 ignore next 3 */
|
|
606
|
+
if (beginIdx === -1) {
|
|
607
|
+
return null;
|
|
608
|
+
}
|
|
609
|
+
const header = sql.slice(0, beginIdx);
|
|
610
|
+
const bodyPart = sql.slice(beginIdx);
|
|
611
|
+
const headerMatch = /create\s+trigger\s+["`]?\w+["`]?\s+(before|after|instead\s+of)\s+(insert|update|delete)\s+on\s+["`]?\w+["`]?\s*(?:for\s+each\s+(row|statement))?\s*(?:when\s+([\s\S]*?))?\s*$/i.exec(header);
|
|
612
|
+
/* v8 ignore next 3 */
|
|
613
|
+
if (!headerMatch) {
|
|
614
|
+
return null;
|
|
615
|
+
}
|
|
616
|
+
const bodyMatch = /^begin\s+([\s\S]*?)\s*end/i.exec(bodyPart);
|
|
617
|
+
/* v8 ignore next 3 */
|
|
618
|
+
if (!bodyMatch) {
|
|
619
|
+
return null;
|
|
620
|
+
}
|
|
621
|
+
const timing = headerMatch[1].toLowerCase();
|
|
622
|
+
const event = headerMatch[2].toLowerCase();
|
|
623
|
+
const forEach = (headerMatch[3]?.toLowerCase() ?? 'row');
|
|
624
|
+
const when = headerMatch[4]?.trim() || undefined;
|
|
625
|
+
const body = bodyMatch[1].trim().replace(/;\s*$/, '');
|
|
626
|
+
return {
|
|
627
|
+
name,
|
|
628
|
+
timing,
|
|
629
|
+
events: [event],
|
|
630
|
+
forEach,
|
|
631
|
+
body,
|
|
632
|
+
when,
|
|
633
|
+
};
|
|
634
|
+
}
|
|
506
635
|
getAlterTempTableSQL(changedTable) {
|
|
507
636
|
const tempName = `${changedTable.toTable.name}__temp_alter`;
|
|
508
637
|
const quotedName = this.quote(changedTable.toTable.name);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikro-orm/sql",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.18-dev.0",
|
|
4
4
|
"description": "TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, PostgreSQL and SQLite databases as well as usage with vanilla JavaScript.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"data-mapper",
|
|
@@ -47,13 +47,13 @@
|
|
|
47
47
|
"copy": "node ../../scripts/copy.mjs"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"kysely": "0.29.
|
|
50
|
+
"kysely": "0.29.2"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"@mikro-orm/core": "^7.0.
|
|
53
|
+
"@mikro-orm/core": "^7.0.17"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
|
-
"@mikro-orm/core": "7.0.
|
|
56
|
+
"@mikro-orm/core": "7.0.18-dev.0"
|
|
57
57
|
},
|
|
58
58
|
"engines": {
|
|
59
59
|
"node": ">= 22.17.0"
|
|
@@ -37,6 +37,11 @@ interface Options {
|
|
|
37
37
|
limit?: number;
|
|
38
38
|
offset?: number;
|
|
39
39
|
data?: Dictionary;
|
|
40
|
+
insertSubQuery?: {
|
|
41
|
+
sql: string;
|
|
42
|
+
params: unknown[];
|
|
43
|
+
columns: string[];
|
|
44
|
+
};
|
|
40
45
|
onConflict?: OnConflictClause;
|
|
41
46
|
lockMode?: LockMode;
|
|
42
47
|
lockTables?: string[];
|
|
@@ -105,6 +110,7 @@ export declare class NativeQueryBuilder implements Subquery {
|
|
|
105
110
|
limit(limit: number): this;
|
|
106
111
|
offset(offset: number): this;
|
|
107
112
|
insert(data: Dictionary): this;
|
|
113
|
+
insertSelect(columns: string[], subQuery: NativeQueryBuilder | RawQueryFragment): this;
|
|
108
114
|
update(data: Dictionary): this;
|
|
109
115
|
delete(): this;
|
|
110
116
|
truncate(): this;
|
|
@@ -215,6 +215,12 @@ export class NativeQueryBuilder {
|
|
|
215
215
|
this.options.data = data;
|
|
216
216
|
return this;
|
|
217
217
|
}
|
|
218
|
+
insertSelect(columns, subQuery) {
|
|
219
|
+
this.type = QueryType.INSERT;
|
|
220
|
+
const { sql, params } = subQuery instanceof NativeQueryBuilder ? subQuery.compile() : { sql: subQuery.sql, params: [...subQuery.params] };
|
|
221
|
+
this.options.insertSubQuery = { sql, params, columns: columns.map(c => this.quote(c)) };
|
|
222
|
+
return this;
|
|
223
|
+
}
|
|
218
224
|
update(data) {
|
|
219
225
|
this.type = QueryType.UPDATE;
|
|
220
226
|
this.options.data ??= {};
|
|
@@ -352,12 +358,21 @@ export class NativeQueryBuilder {
|
|
|
352
358
|
return fields;
|
|
353
359
|
}
|
|
354
360
|
compileInsert() {
|
|
355
|
-
if (!this.options.data) {
|
|
361
|
+
if (!this.options.data && !this.options.insertSubQuery) {
|
|
356
362
|
throw new Error('No data provided');
|
|
357
363
|
}
|
|
358
364
|
this.parts.push('insert');
|
|
359
365
|
this.addHintComment();
|
|
360
366
|
this.parts.push(`into ${this.getTableName()}`);
|
|
367
|
+
if (this.options.insertSubQuery) {
|
|
368
|
+
if (this.options.insertSubQuery.columns.length) {
|
|
369
|
+
this.parts.push(`(${this.options.insertSubQuery.columns.join(', ')})`);
|
|
370
|
+
}
|
|
371
|
+
this.addOutputClause('inserted');
|
|
372
|
+
this.parts.push(this.options.insertSubQuery.sql);
|
|
373
|
+
this.params.push(...this.options.insertSubQuery.params);
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
361
376
|
if (Object.keys(this.options.data).length === 0) {
|
|
362
377
|
this.addOutputClause('inserted');
|
|
363
378
|
this.parts.push('default values');
|
package/query/QueryBuilder.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AnyEntity, type AutoPath, type Collection, type ConnectionType, type Dictionary, type EntityData, type EntityDTOFlat, type EntityDTOProp, type EntityKey, type EntityManager, EntityMetadata, type EntityName, type EntityProperty, type ExpandProperty, type FilterObject, type FilterOptions, type FilterValue, type FlushMode, type GroupOperator, type Loaded, LockMode, type LoggingOptions, type MetadataStorage, type PrimaryProperty, type ObjectQuery, PopulateHint, type PopulateOptions, type PopulatePath, QueryFlag, type QueryOrderKeysFlat, type QueryOrderMap, type QueryResult, RawQueryFragment, type Raw, type RequiredEntityData, type Scalar, type SerializeDTO, type Subquery, type Transaction } from '@mikro-orm/core';
|
|
1
|
+
import { type AbortQueryOptions, type AnyEntity, type AutoPath, type Collection, type ConnectionType, type Dictionary, type EntityData, type EntityDTOFlat, type EntityDTOProp, type EntityKey, type EntityManager, EntityMetadata, type EntityName, type EntityProperty, type ExpandProperty, type FilterObject, type FilterOptions, type FilterValue, type FlushMode, type GroupOperator, type Loaded, LockMode, type LoggingOptions, type MetadataStorage, type PrimaryProperty, type ObjectQuery, PopulateHint, type PopulateOptions, type PopulatePath, QueryFlag, type QueryOrderKeysFlat, type QueryOrderMap, type QueryResult, RawQueryFragment, type Raw, type RequiredEntityData, type Scalar, type SerializeDTO, type Subquery, type Transaction } from '@mikro-orm/core';
|
|
2
2
|
import { JoinType, QueryType } from './enums.js';
|
|
3
3
|
import type { AbstractSqlDriver } from '../AbstractSqlDriver.js';
|
|
4
4
|
import { type Alias, type OnConflictClause, QueryBuilderHelper } from './QueryBuilderHelper.js';
|
|
@@ -11,6 +11,20 @@ export interface ExecuteOptions {
|
|
|
11
11
|
mergeResults?: boolean;
|
|
12
12
|
}
|
|
13
13
|
export interface QBStreamOptions {
|
|
14
|
+
/**
|
|
15
|
+
* How many rows to fetch in one round-trip.
|
|
16
|
+
* Lower values will result in more queries and network bandwidth, but less memory usage.
|
|
17
|
+
* Higher values will result in fewer queries and network bandwidth, but higher memory usage.
|
|
18
|
+
* Note that the results are iterated one row at a time regardless of this value.
|
|
19
|
+
*
|
|
20
|
+
* Honored on PostgreSQL (cursor-based fetch), MSSQL (tedious stream chunk size)
|
|
21
|
+
* and Oracle (mapped to `fetchArraySize`). Ignored on MySQL, MariaDB, SQLite and
|
|
22
|
+
* libSQL, where the underlying driver already streams row-by-row with no batching
|
|
23
|
+
* knob.
|
|
24
|
+
*
|
|
25
|
+
* @default 100 on dialects that honor it.
|
|
26
|
+
*/
|
|
27
|
+
chunkSize?: number;
|
|
14
28
|
/**
|
|
15
29
|
* Results are mapped to entities, if you set `mapResults: false` you will get POJOs instead.
|
|
16
30
|
*
|
|
@@ -158,6 +172,8 @@ export interface QBState<Entity extends object> {
|
|
|
158
172
|
schema?: string;
|
|
159
173
|
cond: Dictionary;
|
|
160
174
|
data?: Dictionary;
|
|
175
|
+
insertSubQuery?: QueryBuilder<any>;
|
|
176
|
+
insertColumns?: string[];
|
|
161
177
|
orderBy: QueryOrderMap<Entity>[];
|
|
162
178
|
groupBy: InternalField<Entity>[];
|
|
163
179
|
having: Dictionary;
|
|
@@ -191,6 +207,11 @@ export interface QBState<Entity extends object> {
|
|
|
191
207
|
})[];
|
|
192
208
|
tptJoinsApplied: boolean;
|
|
193
209
|
autoJoinedPaths: string[];
|
|
210
|
+
partitionLimit?: {
|
|
211
|
+
partitionBy: string;
|
|
212
|
+
limit: number;
|
|
213
|
+
offset?: number;
|
|
214
|
+
};
|
|
194
215
|
}
|
|
195
216
|
/**
|
|
196
217
|
* SQL query builder with fluent interface.
|
|
@@ -324,6 +345,35 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
|
|
|
324
345
|
* ```
|
|
325
346
|
*/
|
|
326
347
|
insert(data: RequiredEntityData<Entity> | RequiredEntityData<Entity>[]): InsertQueryBuilder<Entity, RootAlias, Context>;
|
|
348
|
+
/**
|
|
349
|
+
* Creates an INSERT ... SELECT query that copies rows from the source query.
|
|
350
|
+
*
|
|
351
|
+
* Column resolution (3 tiers):
|
|
352
|
+
* 1. No explicit select on source, no explicit columns → all cloneable columns derived from entity metadata
|
|
353
|
+
* 2. Explicit select on source, no explicit columns → columns derived from selected field names
|
|
354
|
+
* 3. Explicit `columns` option → user-provided column list
|
|
355
|
+
*
|
|
356
|
+
* @example
|
|
357
|
+
* ```ts
|
|
358
|
+
* // Clone all fields (columns auto-derived from metadata)
|
|
359
|
+
* const source = em.createQueryBuilder(User).where({ id: 1 });
|
|
360
|
+
* await em.createQueryBuilder(User).insertFrom(source).execute();
|
|
361
|
+
*
|
|
362
|
+
* // Clone with overrides via raw() aliases
|
|
363
|
+
* const source = em.createQueryBuilder(User)
|
|
364
|
+
* .select(['name', raw("'new@email.com'").as('email')])
|
|
365
|
+
* .where({ id: 1 });
|
|
366
|
+
* await em.createQueryBuilder(User).insertFrom(source).execute();
|
|
367
|
+
*
|
|
368
|
+
* // Explicit columns for full control
|
|
369
|
+
* await em.createQueryBuilder(User)
|
|
370
|
+
* .insertFrom(source, { columns: ['name', 'email'] })
|
|
371
|
+
* .execute();
|
|
372
|
+
* ```
|
|
373
|
+
*/
|
|
374
|
+
insertFrom(subQuery: QueryBuilder<any>, options?: {
|
|
375
|
+
columns?: Field<Entity, RootAlias, Context>[];
|
|
376
|
+
}): InsertQueryBuilder<Entity, RootAlias, Context>;
|
|
327
377
|
/**
|
|
328
378
|
* Creates an UPDATE query with the given data.
|
|
329
379
|
* Use `where()` to specify which rows to update.
|
|
@@ -644,6 +694,12 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
|
|
|
644
694
|
setFlag(flag: QueryFlag): this;
|
|
645
695
|
unsetFlag(flag: QueryFlag): this;
|
|
646
696
|
hasFlag(flag: QueryFlag): boolean;
|
|
697
|
+
/** @internal */
|
|
698
|
+
setPartitionLimit(opts: {
|
|
699
|
+
partitionBy: string;
|
|
700
|
+
limit: number;
|
|
701
|
+
offset?: number;
|
|
702
|
+
}): this;
|
|
647
703
|
cache(config?: boolean | number | [string, number]): this;
|
|
648
704
|
/**
|
|
649
705
|
* Adds index hint to the FROM clause.
|
|
@@ -859,6 +915,11 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
|
|
|
859
915
|
* Gets logger context for this query builder.
|
|
860
916
|
*/
|
|
861
917
|
getLoggerContext<T extends Dictionary & LoggingOptions = Dictionary>(): T;
|
|
918
|
+
/**
|
|
919
|
+
* Configures cancellation behavior for this query builder. The signal is forwarded to the
|
|
920
|
+
* underlying database client; see {@apilink AbortQueryOptions} for the available strategies.
|
|
921
|
+
*/
|
|
922
|
+
setAbortOptions(options: AbortQueryOptions | undefined): void;
|
|
862
923
|
private fromVirtual;
|
|
863
924
|
/**
|
|
864
925
|
* Adds a join from a property object. Used internally for TPT joins where the property
|
|
@@ -877,6 +938,16 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
|
|
|
877
938
|
protected resolveNestedPath(field: string): string | string[];
|
|
878
939
|
protected init(type: QueryType, data?: any, cond?: any): this;
|
|
879
940
|
private getQueryBase;
|
|
941
|
+
/**
|
|
942
|
+
* Resolves the INSERT column list for `insertFrom()`.
|
|
943
|
+
*
|
|
944
|
+
* Tier 1: Explicit `insertColumns` from `options.columns` → map property names to field names
|
|
945
|
+
* Tier 2: Source QB has explicit select fields → derive from those
|
|
946
|
+
* Tier 3: Derive from target entity metadata (all cloneable columns), auto-populate source select
|
|
947
|
+
*/
|
|
948
|
+
private resolveInsertFromColumns;
|
|
949
|
+
/** Returns properties that are safe to clone (persistable, non-PK, non-generated). */
|
|
950
|
+
private getCloneableProps;
|
|
880
951
|
private applyDiscriminatorCondition;
|
|
881
952
|
/**
|
|
882
953
|
* Ensures TPT joins are applied. Can be called early before finalize() to populate
|
|
@@ -911,6 +982,17 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
|
|
|
911
982
|
private processNestedJoins;
|
|
912
983
|
private hasToManyJoins;
|
|
913
984
|
protected wrapPaginateSubQuery(meta: EntityMetadata): void;
|
|
985
|
+
/**
|
|
986
|
+
* Wraps the inner query (which has ROW_NUMBER in SELECT) with an outer query
|
|
987
|
+
* that filters by the __rn column to apply per-parent limiting.
|
|
988
|
+
*/
|
|
989
|
+
protected wrapPartitionLimitSubQuery(innerQb: NativeQueryBuilder): NativeQueryBuilder;
|
|
990
|
+
/**
|
|
991
|
+
* Adds ROW_NUMBER() OVER (PARTITION BY ...) to the SELECT list and prepares
|
|
992
|
+
* the query state for per-parent limiting. The actual wrapping into a subquery
|
|
993
|
+
* with __rn filtering happens in getNativeQuery().
|
|
994
|
+
*/
|
|
995
|
+
protected preparePartitionLimit(): void;
|
|
914
996
|
/**
|
|
915
997
|
* Computes the set of populate paths from the _populate hints.
|
|
916
998
|
*/
|