@mikro-orm/sql 7.1.13-dev.9 → 7.1.14-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/SqlEntityManager.js +3 -1
- package/package.json +3 -3
- package/plugin/transformer.d.ts +7 -1
- package/plugin/transformer.js +60 -1
- package/query/QueryBuilder.d.ts +7 -0
- package/query/QueryBuilder.js +42 -5
package/SqlEntityManager.js
CHANGED
|
@@ -81,7 +81,9 @@ export class SqlEntityManager extends EntityManager {
|
|
|
81
81
|
const { where: rawWhere, ...countOptions } = options;
|
|
82
82
|
await em.tryFlush(entityName, options);
|
|
83
83
|
const where = await em.processWhere(entityName, rawWhere ?? {}, options, 'read');
|
|
84
|
-
|
|
84
|
+
// match `em.count()` semantics: an active transaction always wins over the requested connection type
|
|
85
|
+
const connectionType = em.getTransactionContext() ? 'write' : options.connectionType;
|
|
86
|
+
const qb = em.createQueryBuilder(meta.class, undefined, connectionType);
|
|
85
87
|
qb
|
|
86
88
|
.select([...fields, raw('count(*) as cnt')])
|
|
87
89
|
.where(where)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikro-orm/sql",
|
|
3
|
-
"version": "7.1.
|
|
3
|
+
"version": "7.1.14-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",
|
|
@@ -50,10 +50,10 @@
|
|
|
50
50
|
"kysely": "0.29.5"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"@mikro-orm/core": "^7.1.
|
|
53
|
+
"@mikro-orm/core": "^7.1.13"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
|
-
"@mikro-orm/core": "7.1.
|
|
56
|
+
"@mikro-orm/core": "7.1.14-dev.0"
|
|
57
57
|
},
|
|
58
58
|
"engines": {
|
|
59
59
|
"node": ">= 22.17.0"
|
package/plugin/transformer.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
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';
|
|
2
|
+
import { type BinaryOperationNode, 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,6 +28,12 @@ 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
|
+
transformBinaryOperation(node: BinaryOperationNode, queryId: QueryId): BinaryOperationNode;
|
|
32
|
+
/** Resolve the entity property a comparison's left operand refers to, so its value operand can be converted. */
|
|
33
|
+
resolveOperandProperty(operand: OperationNode): {
|
|
34
|
+
prop: EntityProperty;
|
|
35
|
+
fieldName: string;
|
|
36
|
+
} | undefined;
|
|
31
37
|
processInputValueNode(prop: EntityProperty | undefined, fieldName: string | undefined, valueNode: ValueNode): OperationNode;
|
|
32
38
|
expandSelections(selections: readonly SelectionNode[]): readonly SelectionNode[];
|
|
33
39
|
expandSelection(sel: SelectionNode): SelectionNode[] | null;
|
package/plugin/transformer.js
CHANGED
|
@@ -455,6 +455,60 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
455
455
|
updates,
|
|
456
456
|
};
|
|
457
457
|
}
|
|
458
|
+
transformBinaryOperation(node, queryId) {
|
|
459
|
+
const transformed = super.transformBinaryOperation(node, queryId);
|
|
460
|
+
if (!this.#options.convertValues) {
|
|
461
|
+
return transformed;
|
|
462
|
+
}
|
|
463
|
+
const resolved = this.resolveOperandProperty(transformed.leftOperand);
|
|
464
|
+
if (!resolved) {
|
|
465
|
+
return transformed;
|
|
466
|
+
}
|
|
467
|
+
const { prop, fieldName } = resolved;
|
|
468
|
+
const right = transformed.rightOperand;
|
|
469
|
+
if (ValueNode.is(right)) {
|
|
470
|
+
const converted = this.processInputValueNode(prop, fieldName, right);
|
|
471
|
+
return converted === right ? transformed : { ...transformed, rightOperand: converted };
|
|
472
|
+
}
|
|
473
|
+
if (PrimitiveValueListNode.is(right)) {
|
|
474
|
+
// upgrade to ValueListNode when the type needs SQL-side wrapping, since
|
|
475
|
+
// PrimitiveValueListNode can only hold primitives
|
|
476
|
+
if (prop.hasConvertToDatabaseValueSQL) {
|
|
477
|
+
const values = right.values.map(value => this.processInputValueNode(prop, fieldName, ValueNode.create(value)));
|
|
478
|
+
return { ...transformed, rightOperand: ValueListNode.create(values) };
|
|
479
|
+
}
|
|
480
|
+
const values = right.values.map(value => this.prepareInputValue(prop, value, true));
|
|
481
|
+
return values.every((value, idx) => value === right.values[idx])
|
|
482
|
+
? transformed
|
|
483
|
+
: { ...transformed, rightOperand: PrimitiveValueListNode.create(values) };
|
|
484
|
+
}
|
|
485
|
+
if (ValueListNode.is(right)) {
|
|
486
|
+
let changed = false;
|
|
487
|
+
const values = right.values.map(valueNode => {
|
|
488
|
+
if (!ValueNode.is(valueNode)) {
|
|
489
|
+
return valueNode;
|
|
490
|
+
}
|
|
491
|
+
const converted = this.processInputValueNode(prop, fieldName, valueNode);
|
|
492
|
+
if (converted !== valueNode) {
|
|
493
|
+
changed = true;
|
|
494
|
+
}
|
|
495
|
+
return converted;
|
|
496
|
+
});
|
|
497
|
+
return changed ? { ...transformed, rightOperand: ValueListNode.create(values) } : transformed;
|
|
498
|
+
}
|
|
499
|
+
return transformed;
|
|
500
|
+
}
|
|
501
|
+
/** Resolve the entity property a comparison's left operand refers to, so its value operand can be converted. */
|
|
502
|
+
resolveOperandProperty(operand) {
|
|
503
|
+
if (!ReferenceNode.is(operand) || !ColumnNode.is(operand.column)) {
|
|
504
|
+
return undefined;
|
|
505
|
+
}
|
|
506
|
+
const tableName = operand.table ? this.getTableName(operand.table) : undefined;
|
|
507
|
+
const meta = this.findOwnerMeta(tableName);
|
|
508
|
+
const fieldName = this.normalizeColumnName(operand.column.column);
|
|
509
|
+
const prop = this.findProperty(meta, fieldName);
|
|
510
|
+
return prop ? { prop, fieldName } : undefined;
|
|
511
|
+
}
|
|
458
512
|
processInputValueNode(prop, fieldName, valueNode) {
|
|
459
513
|
const converted = this.prepareInputValue(prop, valueNode.value, true);
|
|
460
514
|
const newValueNode = converted === valueNode.value
|
|
@@ -543,8 +597,13 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
543
597
|
if (name) {
|
|
544
598
|
return this.lookupInContextStack(name) ?? this.#subqueryAliasMap.get(name) ?? this.findEntityMetadata(name);
|
|
545
599
|
}
|
|
600
|
+
// the stack can be empty when transforming a raw root node with embedded expressions
|
|
601
|
+
const context = this.#contextStack[this.#contextStack.length - 1];
|
|
602
|
+
if (!context) {
|
|
603
|
+
return undefined;
|
|
604
|
+
}
|
|
546
605
|
let single;
|
|
547
|
-
for (const meta of
|
|
606
|
+
for (const meta of context.values()) {
|
|
548
607
|
if (!meta) {
|
|
549
608
|
continue;
|
|
550
609
|
}
|
package/query/QueryBuilder.d.ts
CHANGED
|
@@ -1001,6 +1001,13 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
|
|
|
1001
1001
|
* would flatten them into `and` conjuncts.
|
|
1002
1002
|
*/
|
|
1003
1003
|
private canDistributeOrBranches;
|
|
1004
|
+
private getOrBranchAliases;
|
|
1005
|
+
/**
|
|
1006
|
+
* An entity filter's `$or` that cannot be distributed is applied intact to the `on` clause of the
|
|
1007
|
+
* outermost join common to all targeted joins, nesting the targeted joins under it so the clause
|
|
1008
|
+
* can reference their aliases.
|
|
1009
|
+
*/
|
|
1010
|
+
private mergeFilterOrCondition;
|
|
1004
1011
|
/**
|
|
1005
1012
|
* When adding an inner join on a left joined relation, we need to nest them,
|
|
1006
1013
|
* otherwise the inner join could discard rows of the root table.
|
package/query/QueryBuilder.js
CHANGED
|
@@ -2038,8 +2038,11 @@ export class QueryBuilder {
|
|
|
2038
2038
|
for (const k of Object.keys(cond)) {
|
|
2039
2039
|
if (Utils.isOperator(k)) {
|
|
2040
2040
|
if (Array.isArray(cond[k])) {
|
|
2041
|
-
|
|
2042
|
-
|
|
2041
|
+
if (k === '$or' && !this.canDistributeOrBranches(cond[k], joins)) {
|
|
2042
|
+
// entity filters have no other sink, so their `$or` is kept intact on the outermost targeted join instead
|
|
2043
|
+
if (filter) {
|
|
2044
|
+
this.mergeFilterOrCondition(cond[k], joins);
|
|
2045
|
+
}
|
|
2043
2046
|
continue;
|
|
2044
2047
|
}
|
|
2045
2048
|
cond[k].forEach((c) => this.mergeOnConditions(joins, c, filter, k));
|
|
@@ -2082,6 +2085,12 @@ export class QueryBuilder {
|
|
|
2082
2085
|
* would flatten them into `and` conjuncts.
|
|
2083
2086
|
*/
|
|
2084
2087
|
canDistributeOrBranches(branches, joins) {
|
|
2088
|
+
const aliases = this.getOrBranchAliases(branches);
|
|
2089
|
+
const targeted = joins.filter(j => aliases.has(j.alias));
|
|
2090
|
+
const flat = branches.every(branch => Object.keys(branch).every(k => !Utils.isOperator(k)));
|
|
2091
|
+
return aliases.size === 1 && targeted.length === 1 && flat;
|
|
2092
|
+
}
|
|
2093
|
+
getOrBranchAliases(branches) {
|
|
2085
2094
|
const aliases = new Set();
|
|
2086
2095
|
const collectAliases = (cond) => {
|
|
2087
2096
|
for (const k of Object.keys(cond)) {
|
|
@@ -2094,9 +2103,37 @@ export class QueryBuilder {
|
|
|
2094
2103
|
}
|
|
2095
2104
|
};
|
|
2096
2105
|
branches.forEach(collectAliases);
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2106
|
+
return aliases;
|
|
2107
|
+
}
|
|
2108
|
+
/**
|
|
2109
|
+
* An entity filter's `$or` that cannot be distributed is applied intact to the `on` clause of the
|
|
2110
|
+
* outermost join common to all targeted joins, nesting the targeted joins under it so the clause
|
|
2111
|
+
* can reference their aliases.
|
|
2112
|
+
*/
|
|
2113
|
+
mergeFilterOrCondition(branches, joins) {
|
|
2114
|
+
const aliases = this.getOrBranchAliases(branches);
|
|
2115
|
+
const chainOf = (join) => {
|
|
2116
|
+
const parent = joins.find(j => j.alias === join.ownerAlias);
|
|
2117
|
+
return parent ? [join, ...chainOf(parent)] : [join];
|
|
2118
|
+
};
|
|
2119
|
+
// chains go from each targeted join up to its root, so the first join present in all of them is the outermost common one
|
|
2120
|
+
const chains = joins.filter(j => aliases.has(j.alias)).map(chainOf);
|
|
2121
|
+
const anchor = chains[0]?.find(a => chains.every(chain => chain.includes(a)));
|
|
2122
|
+
/* v8 ignore next 3 */
|
|
2123
|
+
if (!anchor) {
|
|
2124
|
+
return;
|
|
2125
|
+
}
|
|
2126
|
+
for (const chain of chains) {
|
|
2127
|
+
// nest the chain below the anchor, so the anchor's `on` clause can reference the nested aliases
|
|
2128
|
+
for (let i = 0; chain[i] !== anchor; i++) {
|
|
2129
|
+
const nested = (chain[i + 1].nested ??= new Set());
|
|
2130
|
+
if (!nested.has(chain[i])) {
|
|
2131
|
+
chain[i].type = chain[i].type === JoinType.innerJoin ? JoinType.nestedInnerJoin : JoinType.nestedLeftJoin;
|
|
2132
|
+
nested.add(chain[i]);
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
anchor.cond = anchor.cond.$or ? { $and: [anchor.cond, { $or: branches }] } : { ...anchor.cond, $or: branches };
|
|
2100
2137
|
}
|
|
2101
2138
|
/**
|
|
2102
2139
|
* When adding an inner join on a left joined relation, we need to nest them,
|