@mikro-orm/sql 7.1.0-dev.3 → 7.1.0-dev.30
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/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/BasePostgreSqlPlatform.d.ts +3 -0
- package/dialects/postgresql/BasePostgreSqlPlatform.js +28 -6
- package/dialects/postgresql/PostgreSqlSchemaHelper.d.ts +31 -1
- package/dialects/postgresql/PostgreSqlSchemaHelper.js +230 -5
- 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/index.d.ts
CHANGED
|
@@ -17,3 +17,5 @@ export type * from './typings.js';
|
|
|
17
17
|
export * from './plugin/index.js';
|
|
18
18
|
export { SqlEntityManager as EntityManager } from './SqlEntityManager.js';
|
|
19
19
|
export { SqlEntityRepository as EntityRepository } from './SqlEntityRepository.js';
|
|
20
|
+
export * from './SqlMikroORM.js';
|
|
21
|
+
export { SqlMikroORM as MikroORM, type SqlOptions as Options, defineSqlConfig as defineConfig } from './SqlMikroORM.js';
|
package/index.js
CHANGED
|
@@ -16,3 +16,5 @@ export * from './dialects/index.js';
|
|
|
16
16
|
export * from './plugin/index.js';
|
|
17
17
|
export { SqlEntityManager as EntityManager } from './SqlEntityManager.js';
|
|
18
18
|
export { SqlEntityRepository as EntityRepository } from './SqlEntityRepository.js';
|
|
19
|
+
export * from './SqlMikroORM.js';
|
|
20
|
+
export { SqlMikroORM as MikroORM, defineSqlConfig as defineConfig } from './SqlMikroORM.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikro-orm/sql",
|
|
3
|
-
"version": "7.1.0-dev.
|
|
3
|
+
"version": "7.1.0-dev.30",
|
|
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.28.
|
|
50
|
+
"kysely": "0.28.17"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"@mikro-orm/core": "^7.0.
|
|
53
|
+
"@mikro-orm/core": "^7.0.15"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
|
-
"@mikro-orm/core": "7.1.0-dev.
|
|
56
|
+
"@mikro-orm/core": "7.1.0-dev.30"
|
|
57
57
|
},
|
|
58
58
|
"engines": {
|
|
59
59
|
"node": ">= 22.17.0"
|
package/plugin/transformer.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type EntityMetadata, type EntityProperty } from '@mikro-orm/core';
|
|
2
|
-
import { type CommonTableExpressionNameNode, type DeleteQueryNode, type
|
|
1
|
+
import { type EntityMetadata, type EntityProperty, type Type } from '@mikro-orm/core';
|
|
2
|
+
import { type CommonTableExpressionNameNode, type DeleteQueryNode, type InsertQueryNode, type JoinNode, type MergeQueryNode, type OperationNode, type QueryId, type SelectQueryNode, type UpdateQueryNode, type WithNode, ColumnNode, IdentifierNode, OperationNodeTransformer, SelectionNode, TableNode, ValueNode } from 'kysely';
|
|
3
3
|
import type { MikroKyselyPluginOptions } from './index.js';
|
|
4
4
|
import type { SqlEntityManager } from '../SqlEntityManager.js';
|
|
5
5
|
export declare class MikroTransformer extends OperationNodeTransformer {
|
|
@@ -28,10 +28,18 @@ export declare class MikroTransformer extends OperationNodeTransformer {
|
|
|
28
28
|
processOnUpdateHooks(node: UpdateQueryNode, meta: EntityMetadata): UpdateQueryNode;
|
|
29
29
|
processInsertValues(node: InsertQueryNode, meta: EntityMetadata): InsertQueryNode;
|
|
30
30
|
processUpdateValues(node: UpdateQueryNode, meta: EntityMetadata): UpdateQueryNode;
|
|
31
|
+
processInputValueNode(prop: EntityProperty | undefined, fieldName: string | undefined, valueNode: ValueNode): OperationNode;
|
|
32
|
+
expandSelections(selections: readonly SelectionNode[]): readonly SelectionNode[];
|
|
33
|
+
expandSelection(sel: SelectionNode): SelectionNode[] | null;
|
|
34
|
+
expandStar(meta: EntityMetadata | undefined, table: TableNode | undefined, tableName: string | undefined): SelectionNode[] | null;
|
|
35
|
+
wrapRead(customType: Type<any>, fieldName: string, tableName: string | undefined): SelectionNode;
|
|
36
|
+
wrapWrite(prop: EntityProperty | undefined, fieldName: string | undefined, valueNode: ValueNode): OperationNode;
|
|
37
|
+
/** Resolve the customType for a specific field name (handles composite-PK FK fan-out via prop.customTypes[]). */
|
|
38
|
+
fieldType(prop: EntityProperty, fieldName: string): Type<any> | undefined;
|
|
39
|
+
findOwnerMeta(name: string | undefined): EntityMetadata | undefined;
|
|
31
40
|
mapColumnsToProperties(columns: readonly ColumnNode[], meta: EntityMetadata): (EntityProperty | undefined)[];
|
|
32
41
|
normalizeColumnName(identifier: IdentifierNode): string;
|
|
33
42
|
findProperty(meta: EntityMetadata | undefined, columnName?: string): EntityProperty | undefined;
|
|
34
|
-
shouldConvertValues(): boolean;
|
|
35
43
|
prepareInputValue(prop: EntityProperty | undefined, value: unknown, enabled: boolean): unknown;
|
|
36
44
|
/**
|
|
37
45
|
* Look up a table name/alias in the context stack.
|
package/plugin/transformer.js
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { ReferenceKind, isRaw, } from '@mikro-orm/core';
|
|
2
|
-
import { AliasNode, ColumnNode, ColumnUpdateNode, OperationNodeTransformer, PrimitiveValueListNode, ReferenceNode, SchemableIdentifierNode, TableNode, ValueListNode, ValueNode, ValuesNode, } from 'kysely';
|
|
2
|
+
import { AliasNode, ColumnNode, ColumnUpdateNode, IdentifierNode, OperationNodeTransformer, PrimitiveValueListNode, RawNode, ReferenceNode, SchemableIdentifierNode, SelectAllNode, SelectionNode, TableNode, ValueListNode, ValueNode, ValuesNode, } from 'kysely';
|
|
3
|
+
const EXPANDABLE_KINDS = new Set([
|
|
4
|
+
ReferenceKind.SCALAR,
|
|
5
|
+
ReferenceKind.EMBEDDED,
|
|
6
|
+
ReferenceKind.MANY_TO_ONE,
|
|
7
|
+
ReferenceKind.ONE_TO_ONE,
|
|
8
|
+
]);
|
|
3
9
|
export class MikroTransformer extends OperationNodeTransformer {
|
|
4
10
|
/**
|
|
5
11
|
* Context stack to support nested queries (subqueries, CTEs)
|
|
@@ -64,7 +70,14 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
64
70
|
this.processJoinNode(join, currentContext);
|
|
65
71
|
}
|
|
66
72
|
}
|
|
67
|
-
|
|
73
|
+
const transformed = super.transformSelectQuery(node, queryId);
|
|
74
|
+
if (this.#options.convertValues && transformed.selections?.length) {
|
|
75
|
+
const selections = this.expandSelections(transformed.selections);
|
|
76
|
+
if (selections !== transformed.selections) {
|
|
77
|
+
return { ...transformed, selections };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return transformed;
|
|
68
81
|
}
|
|
69
82
|
finally {
|
|
70
83
|
// Pop the context when exiting this query scope
|
|
@@ -365,32 +378,38 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
365
378
|
return node;
|
|
366
379
|
}
|
|
367
380
|
const columnProps = this.mapColumnsToProperties(node.columns, meta);
|
|
368
|
-
const
|
|
381
|
+
const fieldNames = node.columns.map(c => this.normalizeColumnName(c.column));
|
|
382
|
+
// hasConvertToDatabaseValueSQL is set by MetadataDiscovery only when the SQL is non-trivial
|
|
383
|
+
// (i.e. it actually wraps `?`), so a no-op cast on sqlite won't force a row upgrade.
|
|
384
|
+
const needsSqlWrap = columnProps.some(p => p?.hasConvertToDatabaseValueSQL);
|
|
369
385
|
let changed = false;
|
|
370
386
|
const convertedRows = node.values.values.map(row => {
|
|
371
|
-
if (ValueListNode.is(row)) {
|
|
372
|
-
if (row.values.length !== columnProps.length) {
|
|
373
|
-
return row;
|
|
374
|
-
}
|
|
387
|
+
if (ValueListNode.is(row) && row.values.length === columnProps.length) {
|
|
375
388
|
const values = row.values.map((valueNode, idx) => {
|
|
376
389
|
if (!ValueNode.is(valueNode)) {
|
|
377
390
|
return valueNode;
|
|
378
391
|
}
|
|
379
|
-
const
|
|
380
|
-
if (
|
|
381
|
-
|
|
392
|
+
const newNode = this.processInputValueNode(columnProps[idx], fieldNames[idx], valueNode);
|
|
393
|
+
if (newNode !== valueNode) {
|
|
394
|
+
changed = true;
|
|
382
395
|
}
|
|
383
|
-
|
|
384
|
-
return valueNode.immediate ? ValueNode.createImmediate(converted) : ValueNode.create(converted);
|
|
396
|
+
return newNode;
|
|
385
397
|
});
|
|
386
398
|
return ValueListNode.create(values);
|
|
387
399
|
}
|
|
388
|
-
if (PrimitiveValueListNode.is(row)) {
|
|
389
|
-
|
|
390
|
-
|
|
400
|
+
if (PrimitiveValueListNode.is(row) && row.values.length === columnProps.length) {
|
|
401
|
+
// upgrade to ValueListNode when any column needs SQL-side wrapping, since
|
|
402
|
+
// PrimitiveValueListNode can only hold primitives
|
|
403
|
+
if (needsSqlWrap) {
|
|
404
|
+
changed = true;
|
|
405
|
+
return ValueListNode.create(row.values.map((value, idx) => {
|
|
406
|
+
const prop = columnProps[idx];
|
|
407
|
+
const converted = this.prepareInputValue(prop, value, true);
|
|
408
|
+
return this.wrapWrite(prop, fieldNames[idx], ValueNode.create(converted));
|
|
409
|
+
}));
|
|
391
410
|
}
|
|
392
411
|
const values = row.values.map((value, idx) => {
|
|
393
|
-
const converted = this.prepareInputValue(columnProps[idx], value,
|
|
412
|
+
const converted = this.prepareInputValue(columnProps[idx], value, true);
|
|
394
413
|
if (converted !== value) {
|
|
395
414
|
changed = true;
|
|
396
415
|
}
|
|
@@ -412,7 +431,6 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
412
431
|
if (!node.updates?.length) {
|
|
413
432
|
return node;
|
|
414
433
|
}
|
|
415
|
-
const shouldConvert = this.shouldConvertValues();
|
|
416
434
|
let changed = false;
|
|
417
435
|
const updates = node.updates.map(updateNode => {
|
|
418
436
|
if (!ValueNode.is(updateNode.value)) {
|
|
@@ -422,18 +440,12 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
422
440
|
? this.normalizeColumnName(updateNode.column.column)
|
|
423
441
|
: undefined;
|
|
424
442
|
const property = this.findProperty(meta, columnName);
|
|
425
|
-
const
|
|
426
|
-
if (
|
|
443
|
+
const newValue = this.processInputValueNode(property, columnName, updateNode.value);
|
|
444
|
+
if (newValue === updateNode.value) {
|
|
427
445
|
return updateNode;
|
|
428
446
|
}
|
|
429
447
|
changed = true;
|
|
430
|
-
|
|
431
|
-
? ValueNode.createImmediate(converted)
|
|
432
|
-
: ValueNode.create(converted);
|
|
433
|
-
return {
|
|
434
|
-
...updateNode,
|
|
435
|
-
value: newValueNode,
|
|
436
|
-
};
|
|
448
|
+
return { ...updateNode, value: newValue };
|
|
437
449
|
});
|
|
438
450
|
if (!changed) {
|
|
439
451
|
return node;
|
|
@@ -443,6 +455,106 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
443
455
|
updates,
|
|
444
456
|
};
|
|
445
457
|
}
|
|
458
|
+
processInputValueNode(prop, fieldName, valueNode) {
|
|
459
|
+
const converted = this.prepareInputValue(prop, valueNode.value, true);
|
|
460
|
+
const newValueNode = converted === valueNode.value
|
|
461
|
+
? valueNode
|
|
462
|
+
: valueNode.immediate
|
|
463
|
+
? ValueNode.createImmediate(converted)
|
|
464
|
+
: ValueNode.create(converted);
|
|
465
|
+
return this.wrapWrite(prop, fieldName, newValueNode);
|
|
466
|
+
}
|
|
467
|
+
expandSelections(selections) {
|
|
468
|
+
const out = [];
|
|
469
|
+
let changed = false;
|
|
470
|
+
for (const sel of selections) {
|
|
471
|
+
const replaced = this.expandSelection(sel);
|
|
472
|
+
if (replaced) {
|
|
473
|
+
out.push(...replaced);
|
|
474
|
+
changed = true;
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
out.push(sel);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
return changed ? out : selections;
|
|
481
|
+
}
|
|
482
|
+
expandSelection(sel) {
|
|
483
|
+
const inner = sel.selection;
|
|
484
|
+
if (SelectAllNode.is(inner)) {
|
|
485
|
+
return this.expandStar(this.findOwnerMeta(undefined), undefined, undefined);
|
|
486
|
+
}
|
|
487
|
+
if (!ReferenceNode.is(inner)) {
|
|
488
|
+
return null;
|
|
489
|
+
}
|
|
490
|
+
const table = inner.table;
|
|
491
|
+
const tableName = table ? this.getTableName(table) : undefined;
|
|
492
|
+
const meta = this.findOwnerMeta(tableName);
|
|
493
|
+
if (!meta) {
|
|
494
|
+
return null;
|
|
495
|
+
}
|
|
496
|
+
if (SelectAllNode.is(inner.column)) {
|
|
497
|
+
return this.expandStar(meta, table, tableName);
|
|
498
|
+
}
|
|
499
|
+
const fieldName = inner.column.column.name;
|
|
500
|
+
const prop = this.findProperty(meta, fieldName);
|
|
501
|
+
const ct = prop && this.fieldType(prop, fieldName);
|
|
502
|
+
return ct?.convertToJSValueSQL ? [this.wrapRead(ct, fieldName, tableName)] : null;
|
|
503
|
+
}
|
|
504
|
+
expandStar(meta, table, tableName) {
|
|
505
|
+
if (!meta || !meta.props.some(p => p.hasConvertToJSValueSQL)) {
|
|
506
|
+
return null;
|
|
507
|
+
}
|
|
508
|
+
const out = [];
|
|
509
|
+
for (const prop of meta.props) {
|
|
510
|
+
if (prop.persist === false || !prop.fieldNames?.length || !EXPANDABLE_KINDS.has(prop.kind)) {
|
|
511
|
+
continue;
|
|
512
|
+
}
|
|
513
|
+
for (const fieldName of prop.fieldNames) {
|
|
514
|
+
const ct = this.fieldType(prop, fieldName);
|
|
515
|
+
out.push(ct?.convertToJSValueSQL
|
|
516
|
+
? this.wrapRead(ct, fieldName, tableName)
|
|
517
|
+
: SelectionNode.create(table ? ReferenceNode.create(ColumnNode.create(fieldName), table) : ColumnNode.create(fieldName)));
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
return out;
|
|
521
|
+
}
|
|
522
|
+
wrapRead(customType, fieldName, tableName) {
|
|
523
|
+
const key = this.#platform.quoteIdentifier(tableName ? `${tableName}.${fieldName}` : fieldName);
|
|
524
|
+
const sql = customType.convertToJSValueSQL(key, this.#platform);
|
|
525
|
+
return SelectionNode.create(AliasNode.create(RawNode.createWithSql(sql), IdentifierNode.create(fieldName)));
|
|
526
|
+
}
|
|
527
|
+
wrapWrite(prop, fieldName, valueNode) {
|
|
528
|
+
if (!prop?.hasConvertToDatabaseValueSQL || !fieldName || valueNode.value == null || isRaw(valueNode.value)) {
|
|
529
|
+
return valueNode;
|
|
530
|
+
}
|
|
531
|
+
const customType = this.fieldType(prop, fieldName);
|
|
532
|
+
if (!customType?.convertToDatabaseValueSQL) {
|
|
533
|
+
return valueNode;
|
|
534
|
+
}
|
|
535
|
+
const fragments = customType.convertToDatabaseValueSQL('?', this.#platform).split('?');
|
|
536
|
+
return RawNode.create(fragments, fragments.slice(0, -1).map(() => valueNode));
|
|
537
|
+
}
|
|
538
|
+
/** Resolve the customType for a specific field name (handles composite-PK FK fan-out via prop.customTypes[]). */
|
|
539
|
+
fieldType(prop, fieldName) {
|
|
540
|
+
return prop.customType ?? prop.customTypes?.[prop.fieldNames.indexOf(fieldName)];
|
|
541
|
+
}
|
|
542
|
+
findOwnerMeta(name) {
|
|
543
|
+
if (name) {
|
|
544
|
+
return this.lookupInContextStack(name) ?? this.#subqueryAliasMap.get(name) ?? this.findEntityMetadata(name);
|
|
545
|
+
}
|
|
546
|
+
let single;
|
|
547
|
+
for (const meta of this.#contextStack[this.#contextStack.length - 1].values()) {
|
|
548
|
+
if (!meta) {
|
|
549
|
+
continue;
|
|
550
|
+
}
|
|
551
|
+
if (single && single !== meta) {
|
|
552
|
+
return undefined;
|
|
553
|
+
}
|
|
554
|
+
single = meta;
|
|
555
|
+
}
|
|
556
|
+
return single;
|
|
557
|
+
}
|
|
446
558
|
mapColumnsToProperties(columns, meta) {
|
|
447
559
|
return columns.map(column => {
|
|
448
560
|
const columnName = this.normalizeColumnName(column.column);
|
|
@@ -466,9 +578,6 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
466
578
|
}
|
|
467
579
|
return meta.props.find(prop => prop.fieldNames?.includes(columnName));
|
|
468
580
|
}
|
|
469
|
-
shouldConvertValues() {
|
|
470
|
-
return !!this.#options.convertValues;
|
|
471
|
-
}
|
|
472
581
|
prepareInputValue(prop, value, enabled) {
|
|
473
582
|
if (!enabled || !prop || value == null) {
|
|
474
583
|
return value;
|
package/query/CriteriaNode.d.ts
CHANGED
|
@@ -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;
|
package/query/CriteriaNode.js
CHANGED
|
@@ -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) &&
|
|
@@ -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);
|
package/query/QueryBuilder.d.ts
CHANGED
|
@@ -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
|
*
|
|
@@ -193,6 +207,11 @@ export interface QBState<Entity extends object> {
|
|
|
193
207
|
})[];
|
|
194
208
|
tptJoinsApplied: boolean;
|
|
195
209
|
autoJoinedPaths: string[];
|
|
210
|
+
partitionLimit?: {
|
|
211
|
+
partitionBy: string;
|
|
212
|
+
limit: number;
|
|
213
|
+
offset?: number;
|
|
214
|
+
};
|
|
196
215
|
}
|
|
197
216
|
/**
|
|
198
217
|
* SQL query builder with fluent interface.
|
|
@@ -675,6 +694,12 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
|
|
|
675
694
|
setFlag(flag: QueryFlag): this;
|
|
676
695
|
unsetFlag(flag: QueryFlag): this;
|
|
677
696
|
hasFlag(flag: QueryFlag): boolean;
|
|
697
|
+
/** @internal */
|
|
698
|
+
setPartitionLimit(opts: {
|
|
699
|
+
partitionBy: string;
|
|
700
|
+
limit: number;
|
|
701
|
+
offset?: number;
|
|
702
|
+
}): this;
|
|
678
703
|
cache(config?: boolean | number | [string, number]): this;
|
|
679
704
|
/**
|
|
680
705
|
* Adds index hint to the FROM clause.
|
|
@@ -952,6 +977,17 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
|
|
|
952
977
|
private processNestedJoins;
|
|
953
978
|
private hasToManyJoins;
|
|
954
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;
|
|
955
991
|
/**
|
|
956
992
|
* Computes the set of populate paths from the _populate hints.
|
|
957
993
|
*/
|
package/query/QueryBuilder.js
CHANGED
|
@@ -721,6 +721,12 @@ export class QueryBuilder {
|
|
|
721
721
|
hasFlag(flag) {
|
|
722
722
|
return this.#state.flags.has(flag);
|
|
723
723
|
}
|
|
724
|
+
/** @internal */
|
|
725
|
+
setPartitionLimit(opts) {
|
|
726
|
+
this.ensureNotFinalized();
|
|
727
|
+
this.#state.partitionLimit = opts;
|
|
728
|
+
return this;
|
|
729
|
+
}
|
|
724
730
|
cache(config = true) {
|
|
725
731
|
this.ensureNotFinalized();
|
|
726
732
|
this.#state.cache = config;
|
|
@@ -825,6 +831,9 @@ export class QueryBuilder {
|
|
|
825
831
|
this.helper.getLockSQL(qb, this.#state.lockMode, this.#state.lockTables, this.#state.joins);
|
|
826
832
|
}
|
|
827
833
|
this.processReturningStatement(qb, this.mainAlias.meta, this.#state.insertSubQuery ? undefined : this.#state.data, this.#state.returning);
|
|
834
|
+
if (this.#state.partitionLimit) {
|
|
835
|
+
return (this.#query.qb = this.wrapPartitionLimitSubQuery(qb));
|
|
836
|
+
}
|
|
828
837
|
return (this.#query.qb = qb);
|
|
829
838
|
}
|
|
830
839
|
processReturningStatement(qb, meta, data, returning) {
|
|
@@ -1055,9 +1064,10 @@ export class QueryBuilder {
|
|
|
1055
1064
|
options ??= {};
|
|
1056
1065
|
options.mergeResults ??= true;
|
|
1057
1066
|
options.mapResults ??= true;
|
|
1067
|
+
const chunkSize = options.chunkSize ?? 100;
|
|
1058
1068
|
const query = this.toQuery();
|
|
1059
1069
|
const loggerContext = { id: this.em?.id, ...this.loggerContext };
|
|
1060
|
-
const res = this.getConnection().stream(query.sql, query.params, this.context, loggerContext);
|
|
1070
|
+
const res = this.getConnection().stream(query.sql, query.params, this.context, loggerContext, chunkSize);
|
|
1061
1071
|
const meta = this.mainAlias.meta;
|
|
1062
1072
|
if (options.rawResults || !meta) {
|
|
1063
1073
|
yield* res;
|
|
@@ -1385,6 +1395,7 @@ export class QueryBuilder {
|
|
|
1385
1395
|
aliased: [QueryType.SELECT, QueryType.COUNT].includes(this.type),
|
|
1386
1396
|
});
|
|
1387
1397
|
const criteriaNode = CriteriaNodeFactory.createNode(this.metadata, prop.targetMeta.class, cond);
|
|
1398
|
+
const joinCountBefore = Object.keys(this.#state.joins).length;
|
|
1388
1399
|
cond = criteriaNode.process(this, { ignoreBranching: true, alias });
|
|
1389
1400
|
let aliasedName = `${fromAlias}.${prop.name}#${alias}`;
|
|
1390
1401
|
path ??= `${Object.values(this.#state.joins).find(j => j.alias === fromAlias)?.path ?? Utils.className(entityName)}.${prop.name}`;
|
|
@@ -1414,6 +1425,20 @@ export class QueryBuilder {
|
|
|
1414
1425
|
this.#state.joins[aliasedName] = this.helper.joinManyToOneReference(prop, ownerAlias, alias, type, cond, schema);
|
|
1415
1426
|
this.#state.joins[aliasedName].path ??= path;
|
|
1416
1427
|
}
|
|
1428
|
+
// auto-joins added by cond processing that depend on the new alias would otherwise produce a
|
|
1429
|
+
// forward reference (the auto-join's ON refers to alias, while alias's ON refers back to it);
|
|
1430
|
+
// fold them into the new join so both aliases share scope in the outer ON clause (issue #7681)
|
|
1431
|
+
const condJoin = this.#state.joins[aliasedName];
|
|
1432
|
+
const joinKeys = Object.keys(this.#state.joins);
|
|
1433
|
+
for (let i = joinCountBefore; i < joinKeys.length; i++) {
|
|
1434
|
+
const j = this.#state.joins[joinKeys[i]];
|
|
1435
|
+
if (j === condJoin || j.ownerAlias !== alias) {
|
|
1436
|
+
continue;
|
|
1437
|
+
}
|
|
1438
|
+
const nested = (condJoin.nested ??= new Set());
|
|
1439
|
+
j.type = j.type === JoinType.innerJoin ? JoinType.nestedInnerJoin : JoinType.nestedLeftJoin;
|
|
1440
|
+
nested.add(j);
|
|
1441
|
+
}
|
|
1417
1442
|
return { prop, key: aliasedName };
|
|
1418
1443
|
}
|
|
1419
1444
|
prepareFields(fields, type = 'where', schema) {
|
|
@@ -1899,6 +1924,9 @@ export class QueryBuilder {
|
|
|
1899
1924
|
(this.#state.limit > 0 || this.#state.offset > 0)) {
|
|
1900
1925
|
this.wrapPaginateSubQuery(meta);
|
|
1901
1926
|
}
|
|
1927
|
+
if (this.#state.partitionLimit) {
|
|
1928
|
+
this.preparePartitionLimit();
|
|
1929
|
+
}
|
|
1902
1930
|
if (meta &&
|
|
1903
1931
|
(this.#state.flags.has(QueryFlag.UPDATE_SUB_QUERY) || this.#state.flags.has(QueryFlag.DELETE_SUB_QUERY))) {
|
|
1904
1932
|
this.wrapModifySubQuery(meta);
|
|
@@ -2118,6 +2146,40 @@ export class QueryBuilder {
|
|
|
2118
2146
|
[Utils.getPrimaryKeyHash(meta.primaryKeys)]: { $in: raw(sql, params) },
|
|
2119
2147
|
});
|
|
2120
2148
|
}
|
|
2149
|
+
/**
|
|
2150
|
+
* Wraps the inner query (which has ROW_NUMBER in SELECT) with an outer query
|
|
2151
|
+
* that filters by the __rn column to apply per-parent limiting.
|
|
2152
|
+
*/
|
|
2153
|
+
wrapPartitionLimitSubQuery(innerQb) {
|
|
2154
|
+
const { limit, offset = 0 } = this.#state.partitionLimit;
|
|
2155
|
+
const rnCol = this.platform.quoteIdentifier('__rn');
|
|
2156
|
+
innerQb.as(this.mainAlias.aliasName);
|
|
2157
|
+
const outerQb = this.platform.createNativeQueryBuilder();
|
|
2158
|
+
outerQb.select('*').from(innerQb);
|
|
2159
|
+
outerQb.where(`${rnCol} > ? and ${rnCol} <= ?`, [offset, offset + limit]);
|
|
2160
|
+
outerQb.orderBy(rnCol);
|
|
2161
|
+
return outerQb;
|
|
2162
|
+
}
|
|
2163
|
+
/**
|
|
2164
|
+
* Adds ROW_NUMBER() OVER (PARTITION BY ...) to the SELECT list and prepares
|
|
2165
|
+
* the query state for per-parent limiting. The actual wrapping into a subquery
|
|
2166
|
+
* with __rn filtering happens in getNativeQuery().
|
|
2167
|
+
*/
|
|
2168
|
+
preparePartitionLimit() {
|
|
2169
|
+
const { partitionBy } = this.#state.partitionLimit;
|
|
2170
|
+
// `partitionBy` is always a declared property name, so mapper returns a string here.
|
|
2171
|
+
const partitionCol = this.helper.mapper(partitionBy, this.type, undefined, null);
|
|
2172
|
+
const quotedPartition = partitionCol
|
|
2173
|
+
.split('.')
|
|
2174
|
+
.map(e => this.platform.quoteIdentifier(e))
|
|
2175
|
+
.join('.');
|
|
2176
|
+
const queryOrder = this.helper.getQueryOrder(this.type, this.#state.orderBy, this.#state.populateMap, this.#state.collation);
|
|
2177
|
+
const orderBySql = queryOrder.length > 0 ? Utils.unique(queryOrder).join(', ') : quotedPartition;
|
|
2178
|
+
const rnAlias = this.platform.quoteIdentifier('__rn');
|
|
2179
|
+
this.#state.fields.push(raw(`row_number() over (partition by ${quotedPartition} order by ${orderBySql}) as ${rnAlias}`));
|
|
2180
|
+
// Moved into the OVER clause; outer query re-applies via wrapPartitionLimitSubQuery
|
|
2181
|
+
this.#state.orderBy = [];
|
|
2182
|
+
}
|
|
2121
2183
|
/**
|
|
2122
2184
|
* Computes the set of populate paths from the _populate hints.
|
|
2123
2185
|
*/
|
package/schema/DatabaseSchema.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ReferenceKind, isRaw, } from '@mikro-orm/core';
|
|
2
2
|
import { DatabaseTable } from './DatabaseTable.js';
|
|
3
|
+
import { getTablePartitioning } from './partitioning.js';
|
|
3
4
|
/**
|
|
4
5
|
* @internal
|
|
5
6
|
*/
|
|
@@ -173,6 +174,9 @@ export class DatabaseSchema {
|
|
|
173
174
|
}
|
|
174
175
|
const table = schema.addTable(meta.collection, this.getSchemaName(meta, config, schemaName));
|
|
175
176
|
table.comment = meta.comment;
|
|
177
|
+
if (meta.partitionBy) {
|
|
178
|
+
table.setPartitioning(getTablePartitioning(meta, this.getSchemaName(meta, config, schemaName), id => platform.quoteIdentifier(id)));
|
|
179
|
+
}
|
|
176
180
|
// For TPT child entities, only use ownProps (properties defined in this entity only)
|
|
177
181
|
// For all other entities (including TPT root), use all props
|
|
178
182
|
const propsToProcess = meta.inheritanceType === 'tpt' && meta.tptParent && meta.ownProps ? meta.ownProps : meta.props;
|
|
@@ -220,6 +224,20 @@ export class DatabaseSchema {
|
|
|
220
224
|
columnName,
|
|
221
225
|
});
|
|
222
226
|
}
|
|
227
|
+
for (const trigger of meta.triggers) {
|
|
228
|
+
const body = isRaw(trigger.body)
|
|
229
|
+
? platform.formatQuery(trigger.body.sql, trigger.body.params)
|
|
230
|
+
: trigger.body;
|
|
231
|
+
table.addTrigger({
|
|
232
|
+
name: trigger.name,
|
|
233
|
+
timing: trigger.timing,
|
|
234
|
+
events: trigger.events,
|
|
235
|
+
forEach: trigger.forEach ?? 'row',
|
|
236
|
+
body: body ?? '',
|
|
237
|
+
when: trigger.when,
|
|
238
|
+
expression: trigger.expression,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
223
241
|
}
|
|
224
242
|
return schema;
|
|
225
243
|
}
|
|
@@ -323,12 +341,16 @@ export class DatabaseSchema {
|
|
|
323
341
|
(prop.kind === ReferenceKind.ONE_TO_ONE && prop.owner));
|
|
324
342
|
}
|
|
325
343
|
toJSON() {
|
|
344
|
+
// locale-independent comparison so the snapshot is stable across machines
|
|
345
|
+
const byString = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
|
|
346
|
+
const tableKey = (t) => `${t.schema ?? ''}.${t.name}`;
|
|
347
|
+
const byTable = (a, b) => byString(tableKey(a), tableKey(b));
|
|
326
348
|
return {
|
|
327
349
|
name: this.name,
|
|
328
|
-
namespaces: [...this.#namespaces],
|
|
329
|
-
tables: this.#tables,
|
|
330
|
-
views: this.#views,
|
|
331
|
-
nativeEnums: this.#nativeEnums,
|
|
350
|
+
namespaces: [...this.#namespaces].sort(),
|
|
351
|
+
tables: [...this.#tables].sort(byTable),
|
|
352
|
+
views: [...this.#views].sort(byTable),
|
|
353
|
+
nativeEnums: Object.fromEntries(Object.entries(this.#nativeEnums).sort(([a], [b]) => byString(a, b))),
|
|
332
354
|
};
|
|
333
355
|
}
|
|
334
356
|
prune(schema, wildcardSchemaTables) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type Configuration, type DeferMode, type Dictionary, type EntityMetadata, type EntityProperty, type IndexCallback, type NamingStrategy } from '@mikro-orm/core';
|
|
2
2
|
import type { SchemaHelper } from './SchemaHelper.js';
|
|
3
|
-
import type { CheckDef, Column, ForeignKey, IndexDef } from '../typings.js';
|
|
3
|
+
import type { CheckDef, Column, ForeignKey, IndexDef, TablePartitioning, SqlTriggerDef } from '../typings.js';
|
|
4
4
|
import type { AbstractSqlPlatform } from '../AbstractSqlPlatform.js';
|
|
5
5
|
/**
|
|
6
6
|
* @internal
|
|
@@ -15,6 +15,14 @@ export declare class DatabaseTable {
|
|
|
15
15
|
items: string[];
|
|
16
16
|
}>;
|
|
17
17
|
comment?: string;
|
|
18
|
+
partitioning?: TablePartitioning;
|
|
19
|
+
/**
|
|
20
|
+
* Effective collation the column defaults to when no explicit `COLLATE` is set on a column.
|
|
21
|
+
* For MySQL/MariaDB this is the table collation; for PostgreSQL and MSSQL this is the database default;
|
|
22
|
+
* SQLite has no configurable default. Used by `SchemaComparator.diffCollation` to avoid flapping
|
|
23
|
+
* when a property explicitly names the default collation.
|
|
24
|
+
*/
|
|
25
|
+
collation?: string;
|
|
18
26
|
constructor(platform: AbstractSqlPlatform, name: string, schema?: string | undefined);
|
|
19
27
|
getQuotedName(): string;
|
|
20
28
|
getColumns(): Column[];
|
|
@@ -22,11 +30,17 @@ export declare class DatabaseTable {
|
|
|
22
30
|
removeColumn(name: string): void;
|
|
23
31
|
getIndexes(): IndexDef[];
|
|
24
32
|
getChecks(): CheckDef[];
|
|
33
|
+
getPartitioning(): TablePartitioning | undefined;
|
|
34
|
+
/** @internal */
|
|
35
|
+
setPartitioning(partitioning?: TablePartitioning): void;
|
|
36
|
+
getTriggers(): SqlTriggerDef[];
|
|
25
37
|
/** @internal */
|
|
26
38
|
setIndexes(indexes: IndexDef[]): void;
|
|
27
39
|
/** @internal */
|
|
28
40
|
setChecks(checks: CheckDef[]): void;
|
|
29
41
|
/** @internal */
|
|
42
|
+
setTriggers(triggers: SqlTriggerDef[]): void;
|
|
43
|
+
/** @internal */
|
|
30
44
|
setForeignKeys(fks: Dictionary<ForeignKey>): void;
|
|
31
45
|
init(cols: Column[], indexes: IndexDef[] | undefined, checks: CheckDef[] | undefined, pks: string[], fks?: Dictionary<ForeignKey>, enums?: Dictionary<string[]>): void;
|
|
32
46
|
addColumn(column: Column): void;
|
|
@@ -47,6 +61,8 @@ export declare class DatabaseTable {
|
|
|
47
61
|
hasIndex(indexName: string): boolean;
|
|
48
62
|
getCheck(checkName: string): CheckDef | undefined;
|
|
49
63
|
hasCheck(checkName: string): boolean;
|
|
64
|
+
getTrigger(triggerName: string): SqlTriggerDef | undefined;
|
|
65
|
+
hasTrigger(triggerName: string): boolean;
|
|
50
66
|
getPrimaryKey(): IndexDef | undefined;
|
|
51
67
|
hasPrimaryKey(): boolean;
|
|
52
68
|
private getForeignKeyDeclaration;
|
|
@@ -62,6 +78,7 @@ export declare class DatabaseTable {
|
|
|
62
78
|
name?: string;
|
|
63
79
|
type?: string;
|
|
64
80
|
expression?: string | IndexCallback<any>;
|
|
81
|
+
where?: string | Dictionary;
|
|
65
82
|
deferMode?: DeferMode | `${DeferMode}`;
|
|
66
83
|
options?: Dictionary;
|
|
67
84
|
columns?: {
|
|
@@ -77,6 +94,8 @@ export declare class DatabaseTable {
|
|
|
77
94
|
disabled?: boolean;
|
|
78
95
|
clustered?: boolean;
|
|
79
96
|
}, type: 'index' | 'unique' | 'primary'): void;
|
|
97
|
+
private processIndexWhere;
|
|
80
98
|
addCheck(check: CheckDef): void;
|
|
99
|
+
addTrigger(trigger: SqlTriggerDef): void;
|
|
81
100
|
toJSON(): Dictionary;
|
|
82
101
|
}
|