@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.
Files changed (42) hide show
  1. package/AbstractSqlConnection.d.ts +1 -1
  2. package/AbstractSqlConnection.js +2 -2
  3. package/AbstractSqlDriver.d.ts +25 -1
  4. package/AbstractSqlDriver.js +315 -15
  5. package/PivotCollectionPersister.js +13 -2
  6. package/SqlEntityManager.d.ts +5 -1
  7. package/SqlEntityManager.js +36 -1
  8. package/dialects/mssql/MsSqlNativeQueryBuilder.js +4 -0
  9. package/dialects/mysql/BaseMySqlPlatform.d.ts +1 -0
  10. package/dialects/mysql/BaseMySqlPlatform.js +3 -0
  11. package/dialects/mysql/MySqlNativeQueryBuilder.js +11 -0
  12. package/dialects/mysql/MySqlSchemaHelper.d.ts +9 -3
  13. package/dialects/mysql/MySqlSchemaHelper.js +102 -4
  14. package/dialects/oracledb/OracleDialect.d.ts +1 -1
  15. package/dialects/oracledb/OracleDialect.js +2 -1
  16. package/dialects/postgresql/BasePostgreSqlPlatform.d.ts +2 -0
  17. package/dialects/postgresql/BasePostgreSqlPlatform.js +21 -3
  18. package/dialects/postgresql/PostgreSqlSchemaHelper.d.ts +21 -1
  19. package/dialects/postgresql/PostgreSqlSchemaHelper.js +200 -4
  20. package/dialects/sqlite/SqlitePlatform.d.ts +1 -0
  21. package/dialects/sqlite/SqlitePlatform.js +4 -0
  22. package/dialects/sqlite/SqliteSchemaHelper.d.ts +8 -2
  23. package/dialects/sqlite/SqliteSchemaHelper.js +131 -19
  24. package/package.json +3 -3
  25. package/query/CriteriaNode.d.ts +1 -1
  26. package/query/CriteriaNode.js +2 -2
  27. package/query/NativeQueryBuilder.d.ts +6 -0
  28. package/query/NativeQueryBuilder.js +16 -1
  29. package/query/ObjectCriteriaNode.js +1 -1
  30. package/query/QueryBuilder.d.ts +77 -0
  31. package/query/QueryBuilder.js +170 -6
  32. package/schema/DatabaseSchema.js +26 -4
  33. package/schema/DatabaseTable.d.ts +13 -1
  34. package/schema/DatabaseTable.js +171 -31
  35. package/schema/SchemaComparator.d.ts +1 -0
  36. package/schema/SchemaComparator.js +86 -1
  37. package/schema/SchemaHelper.d.ts +51 -1
  38. package/schema/SchemaHelper.js +192 -3
  39. package/schema/SqlSchemaGenerator.js +7 -0
  40. package/schema/partitioning.d.ts +13 -0
  41. package/schema/partitioning.js +326 -0
  42. package/typings.d.ts +32 -1
@@ -24,6 +24,10 @@ export class SqlitePlatform extends AbstractSqlPlatform {
24
24
  getDateTimeTypeDeclarationSQL(column) {
25
25
  return 'datetime';
26
26
  }
27
+ // sqlite's datetime DDL drops precision and the current-ts expression hardcodes ms scaling
28
+ getDefaultVersionLength() {
29
+ return 0;
30
+ }
27
31
  getBeginTransactionSQL(options) {
28
32
  return ['begin'];
29
33
  }
@@ -1,10 +1,11 @@
1
1
  import { type Connection, type Transaction } from '@mikro-orm/core';
2
2
  import type { AbstractSqlConnection } from '../../AbstractSqlConnection.js';
3
3
  import { SchemaHelper } from '../../schema/SchemaHelper.js';
4
- import type { Column, IndexDef, Table, TableDifference } from '../../typings.js';
4
+ import type { Column, IndexDef, Table, TableDifference, SqlTriggerDef } from '../../typings.js';
5
5
  import type { DatabaseTable } from '../../schema/DatabaseTable.js';
6
6
  import type { DatabaseSchema } from '../../schema/DatabaseSchema.js';
7
7
  export declare class SqliteSchemaHelper extends SchemaHelper {
8
+ private static readonly PARTIAL_WHERE_RE;
8
9
  disableForeignKeysSQL(): string;
9
10
  enableForeignKeysSQL(): string;
10
11
  supportsSchemaConstraints(): boolean;
@@ -44,7 +45,8 @@ export declare class SqliteSchemaHelper extends SchemaHelper {
44
45
  * We need to add them back so they match what we generate in DDL.
45
46
  */
46
47
  private wrapExpressionDefault;
47
- private getEnumDefinitions;
48
+ /** Extract enum values from `IN (…)` CHECKs only — a `!= 'x'` check would otherwise be misread as a one-item enum. */
49
+ private extractEnumValuesFromChecks;
48
50
  getPrimaryKeys(connection: AbstractSqlConnection, indexes: IndexDef[], tableName: string, schemaName?: string, ctx?: Transaction): Promise<string[]>;
49
51
  private getIndexes;
50
52
  private getChecks;
@@ -64,5 +66,9 @@ export declare class SqliteSchemaHelper extends SchemaHelper {
64
66
  */
65
67
  getReferencedTableName(referencedTableName: string, schema?: string): string;
66
68
  alterTable(diff: TableDifference, safe?: boolean): string[];
69
+ /** Generates SQL to create SQLite triggers. SQLite requires one trigger per event. */
70
+ createTrigger(table: DatabaseTable, trigger: SqlTriggerDef): string;
71
+ private getTableTriggers;
72
+ private parseTriggerDDL;
67
73
  private getAlterTempTableSQL;
68
74
  }
@@ -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
  }
@@ -100,8 +101,10 @@ export class SqliteSchemaHelper extends SchemaHelper {
100
101
  const checks = await this.getChecks(connection, table.name, table.schema, ctx);
101
102
  const pks = await this.getPrimaryKeys(connection, indexes, table.name, table.schema, ctx);
102
103
  const fks = await this.getForeignKeys(connection, table.name, table.schema, ctx);
103
- const enums = await this.getEnumDefinitions(connection, table.name, table.schema, ctx);
104
+ const enums = this.extractEnumValuesFromChecks(checks);
105
+ const triggers = await this.getTableTriggers(connection, table.name);
104
106
  table.init(cols, indexes, checks, pks, fks, enums);
107
+ table.setTriggers(triggers);
105
108
  }
106
109
  }
107
110
  createTable(table, alter) {
@@ -140,6 +143,9 @@ export class SqliteSchemaHelper extends SchemaHelper {
140
143
  for (const index of table.getIndexes()) {
141
144
  this.append(ret, this.createIndex(index, table));
142
145
  }
146
+ for (const trigger of table.getTriggers()) {
147
+ this.append(ret, this.createTrigger(table, trigger));
148
+ }
143
149
  return ret;
144
150
  }
145
151
  createTableColumn(column, table, _changedProperties) {
@@ -211,10 +217,10 @@ export class SqliteSchemaHelper extends SchemaHelper {
211
217
  if (index.columnNames.some(column => column.includes('.'))) {
212
218
  // JSON columns can have unique index but not unique constraint, and we need to distinguish those, so we can properly drop them
213
219
  const columns = this.platform.getJsonIndexDefinition(index);
214
- return `${sqlPrefix} (${columns.join(', ')})`;
220
+ return `${sqlPrefix} (${columns.join(', ')})${this.getIndexWhereClause(index)}`;
215
221
  }
216
222
  // Use getIndexColumns to support advanced options like sort order and collation
217
- return `${sqlPrefix} (${this.getIndexColumns(index)})`;
223
+ return `${sqlPrefix} (${this.getIndexColumns(index)})${this.getIndexWhereClause(index)}`;
218
224
  }
219
225
  parseTableDefinition(sql, cols) {
220
226
  const columns = {};
@@ -321,23 +327,23 @@ export class SqliteSchemaHelper extends SchemaHelper {
321
327
  // everything else is an expression that had its outer parens stripped
322
328
  return `(${value})`;
323
329
  }
324
- async getEnumDefinitions(connection, tableName, schemaName, ctx) {
325
- const prefix = this.getSchemaPrefix(schemaName);
326
- const sql = `select sql from ${prefix}sqlite_master where type = ? and name = ?`;
327
- const tableDefinition = await connection.execute(sql, ['table', tableName], 'get', ctx);
328
- const checkConstraints = [...(tableDefinition.sql.match(/[`["'][^`\]"']+[`\]"'] text check \(.*?\)/gi) ?? [])];
329
- return checkConstraints.reduce((o, item) => {
330
- // check constraints are defined as (note that last closing paren is missing):
331
- // `type` text check (`type` in ('local', 'global')
332
- const match = /[`["']([^`\]"']+)[`\]"'] text check \(.* \((.*)\)/i.exec(item);
333
- /* v8 ignore next */
334
- if (match) {
335
- o[match[1]] = match[2]
336
- .split(/,(?=\s*'(?:[^']|'')*'(?:\s*\)|$))/)
337
- .map((item) => /^\(?'((?:[^']|'')*)'/.exec(item.trim())[1].replace(/''/g, "'"));
330
+ /** Extract enum values from `IN (…)` CHECKs only — a `!= 'x'` check would otherwise be misread as a one-item enum. */
331
+ extractEnumValuesFromChecks(checks) {
332
+ const result = {};
333
+ for (const check of checks) {
334
+ if (!check.columnName || typeof check.expression !== 'string') {
335
+ continue;
338
336
  }
339
- return o;
340
- }, {});
337
+ const inClause = /\bin\s*\(([^)]*)\)/i.exec(check.expression);
338
+ if (!inClause) {
339
+ continue;
340
+ }
341
+ const items = [...inClause[1].matchAll(/'((?:[^']|'')*)'/g)].map(m => m[1].replace(/''/g, "'"));
342
+ if (items.length > 0) {
343
+ result[check.columnName] = items;
344
+ }
345
+ }
346
+ return result;
341
347
  }
342
348
  async getPrimaryKeys(connection, indexes, tableName, schemaName, ctx) {
343
349
  const prefix = this.getSchemaPrefix(schemaName);
@@ -350,6 +356,16 @@ export class SqliteSchemaHelper extends SchemaHelper {
350
356
  const sql = `pragma ${prefix}table_info(\`${tableName}\`)`;
351
357
  const cols = await connection.execute(sql, [], 'all', ctx);
352
358
  const indexes = await connection.execute(`pragma ${prefix}index_list(\`${tableName}\`)`, [], 'all', ctx);
359
+ // sqlite_master.sql holds the original CREATE INDEX statement — the only place a partial
360
+ // index's WHERE predicate is preserved (PRAGMA index_* don't expose it).
361
+ const indexSqls = await connection.execute(`select name, sql from ${prefix}sqlite_master where type = 'index' and tbl_name = ?`, [tableName], 'all', ctx);
362
+ const wherePredicates = new Map();
363
+ for (const row of indexSqls) {
364
+ const match = row.sql && SqliteSchemaHelper.PARTIAL_WHERE_RE.exec(row.sql);
365
+ if (match) {
366
+ wherePredicates.set(row.name, match[1].trim());
367
+ }
368
+ }
353
369
  const ret = [];
354
370
  for (const col of cols.filter(c => c.pk)) {
355
371
  ret.push({
@@ -362,12 +378,14 @@ export class SqliteSchemaHelper extends SchemaHelper {
362
378
  }
363
379
  for (const index of indexes.filter(index => !this.isImplicitIndex(index.name))) {
364
380
  const res = await connection.execute(`pragma ${prefix}index_info(\`${index.name}\`)`, [], 'all', ctx);
381
+ const where = wherePredicates.get(index.name);
365
382
  ret.push(...res.map(row => ({
366
383
  columnNames: [row.name],
367
384
  keyName: index.name,
368
385
  unique: !!index.unique,
369
386
  constraint: !!index.unique,
370
387
  primary: false,
388
+ ...(where ? { where } : {}),
371
389
  })));
372
390
  }
373
391
  return this.mapIndexes(ret);
@@ -501,8 +519,102 @@ export class SqliteSchemaHelper extends SchemaHelper {
501
519
  this.append(ret, this.getRenameIndexSQL(diff.name, index, oldIndexName));
502
520
  }
503
521
  }
522
+ for (const trigger of Object.values(diff.removedTriggers)) {
523
+ this.append(ret, this.dropTrigger(diff.toTable, trigger));
524
+ }
525
+ for (const trigger of Object.values(diff.changedTriggers)) {
526
+ this.append(ret, this.dropTrigger(diff.toTable, trigger));
527
+ this.append(ret, this.createTrigger(diff.toTable, trigger));
528
+ }
529
+ for (const trigger of Object.values(diff.addedTriggers)) {
530
+ this.append(ret, this.createTrigger(diff.toTable, trigger));
531
+ }
504
532
  return ret;
505
533
  }
534
+ /** Generates SQL to create SQLite triggers. SQLite requires one trigger per event. */
535
+ createTrigger(table, trigger) {
536
+ if (trigger.expression) {
537
+ return trigger.expression;
538
+ }
539
+ const timing = trigger.timing.toUpperCase();
540
+ const forEach = trigger.forEach === 'statement' ? 'STATEMENT' : 'ROW';
541
+ const ret = [];
542
+ for (const event of trigger.events) {
543
+ const name = trigger.events.length > 1 ? `${trigger.name}_${event}` : trigger.name;
544
+ const when = trigger.when ? `\n when ${trigger.when}` : '';
545
+ ret.push(`create trigger ${this.quote(name)} ${timing} ${event.toUpperCase()} on ${table.getQuotedName()} for each ${forEach}${when} begin ${trigger.body}; end`);
546
+ }
547
+ return ret.join(';\n');
548
+ }
549
+ async getTableTriggers(connection, tableName) {
550
+ const rows = await connection.execute(`select name, sql from sqlite_master where type = 'trigger' and tbl_name = ?`, [tableName]);
551
+ // First pass: parse all triggers and collect names to detect multi-event groups
552
+ const parsedRows = [];
553
+ for (const row of rows) {
554
+ /* v8 ignore next 3 */
555
+ if (!row.sql) {
556
+ continue;
557
+ }
558
+ const parsed = this.parseTriggerDDL(row.sql, row.name);
559
+ if (parsed) {
560
+ parsedRows.push({ name: row.name, parsed });
561
+ }
562
+ }
563
+ const allNames = parsedRows.map(r => r.name);
564
+ const triggers = [];
565
+ const triggerMap = new Map();
566
+ for (const { name, parsed } of parsedRows) {
567
+ // Only strip event suffix when another trigger with the same base exists
568
+ const eventLower = parsed.events[0];
569
+ const candidateBase = name.endsWith(`_${eventLower}`) ? name.slice(0, -eventLower.length - 1) : null;
570
+ const baseName = candidateBase && allNames.some(n => n !== name && n.startsWith(`${candidateBase}_`)) ? candidateBase : name;
571
+ if (triggerMap.has(baseName)) {
572
+ const existing = triggerMap.get(baseName);
573
+ if (!existing.events.includes(parsed.events[0])) {
574
+ existing.events.push(parsed.events[0]);
575
+ }
576
+ continue;
577
+ }
578
+ const trigger = { ...parsed, name: baseName };
579
+ triggers.push(trigger);
580
+ triggerMap.set(baseName, trigger);
581
+ }
582
+ return triggers;
583
+ }
584
+ parseTriggerDDL(sql, name) {
585
+ // Split at the last top-level BEGIN to separate header from body,
586
+ // so that a WHEN clause containing the word "begin" in a string literal doesn't confuse parsing.
587
+ const beginIdx = sql.search(/\bbegin\b(?=[^]*$)/i);
588
+ /* v8 ignore next 3 */
589
+ if (beginIdx === -1) {
590
+ return null;
591
+ }
592
+ const header = sql.slice(0, beginIdx);
593
+ const bodyPart = sql.slice(beginIdx);
594
+ 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);
595
+ /* v8 ignore next 3 */
596
+ if (!headerMatch) {
597
+ return null;
598
+ }
599
+ const bodyMatch = /^begin\s+([\s\S]*?)\s*end/i.exec(bodyPart);
600
+ /* v8 ignore next 3 */
601
+ if (!bodyMatch) {
602
+ return null;
603
+ }
604
+ const timing = headerMatch[1].toLowerCase();
605
+ const event = headerMatch[2].toLowerCase();
606
+ const forEach = (headerMatch[3]?.toLowerCase() ?? 'row');
607
+ const when = headerMatch[4]?.trim() || undefined;
608
+ const body = bodyMatch[1].trim().replace(/;\s*$/, '');
609
+ return {
610
+ name,
611
+ timing,
612
+ events: [event],
613
+ forEach,
614
+ body,
615
+ when,
616
+ };
617
+ }
506
618
  getAlterTempTableSQL(changedTable) {
507
619
  const tempName = `${changedTable.toTable.name}__temp_alter`;
508
620
  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.1.0-dev.2",
3
+ "version": "7.1.0-dev.21",
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",
@@ -50,10 +50,10 @@
50
50
  "kysely": "0.28.16"
51
51
  },
52
52
  "devDependencies": {
53
- "@mikro-orm/core": "^7.0.11"
53
+ "@mikro-orm/core": "^7.0.12"
54
54
  },
55
55
  "peerDependencies": {
56
- "@mikro-orm/core": "7.1.0-dev.2"
56
+ "@mikro-orm/core": "7.1.0-dev.21"
57
57
  },
58
58
  "engines": {
59
59
  "node": ">= 22.17.0"
@@ -21,7 +21,7 @@ export declare class CriteriaNode<T extends object> implements ICriteriaNode<T>
21
21
  shouldInline(payload: any): boolean;
22
22
  willAutoJoin(qb: IQueryBuilder<T>, alias?: string, options?: ICriteriaNodeProcessOptions): boolean;
23
23
  shouldRename(payload: any): boolean;
24
- renameFieldToPK<T>(qb: IQueryBuilder<T>, ownerAlias?: string): string;
24
+ renameFieldToPK<T>(qb: IQueryBuilder<T>, ownerAlias?: string, options?: ICriteriaNodeProcessOptions): string;
25
25
  getPath(opts?: {
26
26
  addIndex?: boolean;
27
27
  parentPath?: string;
@@ -83,8 +83,8 @@ export class CriteriaNode {
83
83
  return false;
84
84
  }
85
85
  }
86
- renameFieldToPK(qb, ownerAlias) {
87
- const joinAlias = qb.getAliasForJoinPath(this.getPath(), { matchPopulateJoins: true });
86
+ renameFieldToPK(qb, ownerAlias, options) {
87
+ const joinAlias = qb.getAliasForJoinPath(this.getPath(), { ...options, matchPopulateJoins: true });
88
88
  if (!joinAlias &&
89
89
  this.parent &&
90
90
  [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(this.prop.kind) &&
@@ -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');
@@ -105,7 +105,7 @@ export class ObjectCriteriaNode extends CriteriaNode {
105
105
  this.inlineChildPayload(o, payload, field, a, childAlias);
106
106
  }
107
107
  else if (childNode.shouldRename(payload)) {
108
- this.inlineCondition(childNode.renameFieldToPK(qb, alias), o, payload);
108
+ this.inlineCondition(childNode.renameFieldToPK(qb, alias, options), o, payload);
109
109
  }
110
110
  else if (isRawField) {
111
111
  const rawField = RawQueryFragment.getKnownFragment(field);
@@ -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.
@@ -877,6 +933,16 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
877
933
  protected resolveNestedPath(field: string): string | string[];
878
934
  protected init(type: QueryType, data?: any, cond?: any): this;
879
935
  private getQueryBase;
936
+ /**
937
+ * Resolves the INSERT column list for `insertFrom()`.
938
+ *
939
+ * Tier 1: Explicit `insertColumns` from `options.columns` → map property names to field names
940
+ * Tier 2: Source QB has explicit select fields → derive from those
941
+ * Tier 3: Derive from target entity metadata (all cloneable columns), auto-populate source select
942
+ */
943
+ private resolveInsertFromColumns;
944
+ /** Returns properties that are safe to clone (persistable, non-PK, non-generated). */
945
+ private getCloneableProps;
880
946
  private applyDiscriminatorCondition;
881
947
  /**
882
948
  * Ensures TPT joins are applied. Can be called early before finalize() to populate
@@ -911,6 +977,17 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
911
977
  private processNestedJoins;
912
978
  private hasToManyJoins;
913
979
  protected wrapPaginateSubQuery(meta: EntityMetadata): void;
980
+ /**
981
+ * Wraps the inner query (which has ROW_NUMBER in SELECT) with an outer query
982
+ * that filters by the __rn column to apply per-parent limiting.
983
+ */
984
+ protected wrapPartitionLimitSubQuery(innerQb: NativeQueryBuilder): NativeQueryBuilder;
985
+ /**
986
+ * Adds ROW_NUMBER() OVER (PARTITION BY ...) to the SELECT list and prepares
987
+ * the query state for per-parent limiting. The actual wrapping into a subquery
988
+ * with __rn filtering happens in getNativeQuery().
989
+ */
990
+ protected preparePartitionLimit(): void;
914
991
  /**
915
992
  * Computes the set of populate paths from the _populate hints.
916
993
  */