@mikro-orm/sql 7.3.0-dev.3 → 7.3.0-dev.5
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/AbstractSqlDriver.js
CHANGED
|
@@ -1809,7 +1809,11 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1809
1809
|
if (!this.platform.shouldHaveColumn(prop, populate, exclude)) {
|
|
1810
1810
|
return false;
|
|
1811
1811
|
}
|
|
1812
|
-
if (!fields ||
|
|
1812
|
+
if (!fields ||
|
|
1813
|
+
fields.includes('*') ||
|
|
1814
|
+
prop.primary ||
|
|
1815
|
+
meta.root.discriminatorColumn === prop.name ||
|
|
1816
|
+
meta.root.targetKeys?.includes(prop.name)) {
|
|
1813
1817
|
return true;
|
|
1814
1818
|
}
|
|
1815
1819
|
return fields.some(f => f === prop.name || f.toString().startsWith(prop.name + '.'));
|
|
@@ -1865,11 +1869,9 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1865
1869
|
const tptMeta = this.metadata.get(targetMeta.class);
|
|
1866
1870
|
this.addTPTPolymorphicJoinsForRelation(qb, tptMeta, tableAlias, fields);
|
|
1867
1871
|
}
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
}
|
|
1872
|
-
else {
|
|
1872
|
+
// target filters apply per target, both for filter :ref hints and populated relations
|
|
1873
|
+
qb.scheduleFilterCheck(targetPath);
|
|
1874
|
+
if (!ref) {
|
|
1873
1875
|
// Select fields from each target table
|
|
1874
1876
|
fields.push(...this.getFieldsForJoinedLoad(qb, targetMeta, {
|
|
1875
1877
|
...options,
|
|
@@ -2469,6 +2471,8 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
2469
2471
|
}
|
|
2470
2472
|
if (!options.fields.includes('*') && !options.fields.includes(`${qb.alias}.*`)) {
|
|
2471
2473
|
ret.unshift(...meta.primaryKeys.filter(pk => !options.fields.includes(pk)));
|
|
2474
|
+
// `targetKey` values identify the entity like its PK, references resolve it by them
|
|
2475
|
+
ret.push(...(meta.root.targetKeys ?? []).filter(key => meta.properties[key] && !options.fields.includes(key)));
|
|
2472
2476
|
}
|
|
2473
2477
|
if (meta.root.inheritanceType === 'sti' &&
|
|
2474
2478
|
!options.fields.includes(`${qb.alias}.${meta.root.discriminatorColumn}`)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikro-orm/sql",
|
|
3
|
-
"version": "7.3.0-dev.
|
|
3
|
+
"version": "7.3.0-dev.5",
|
|
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",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"@mikro-orm/core": "^7.2.1"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
|
-
"@mikro-orm/core": "7.3.0-dev.
|
|
56
|
+
"@mikro-orm/core": "7.3.0-dev.5"
|
|
57
57
|
},
|
|
58
58
|
"engines": {
|
|
59
59
|
"node": ">= 22.17.0"
|
package/query/CriteriaNode.js
CHANGED
|
@@ -29,7 +29,7 @@ export class CriteriaNode {
|
|
|
29
29
|
}
|
|
30
30
|
for (const k of pks) {
|
|
31
31
|
const [name, target] = QueryHelper.splitPolymorphicKey(k);
|
|
32
|
-
this.prop = meta.props.find(prop => (prop.name === name && (!target ||
|
|
32
|
+
this.prop = meta.props.find(prop => (prop.name === name && (!target || QueryHelper.findTargetMetas(prop, k).length > 0)) ||
|
|
33
33
|
(prop.fieldNames?.length === 1 && prop.fieldNames[0] === k && prop.persist !== false));
|
|
34
34
|
const isProp = this.prop || meta.props.find(prop => (prop.fieldNames || []).includes(k));
|
|
35
35
|
// do not validate if the key is prefixed or type casted (e.g. `k::text`)
|
|
@@ -52,7 +52,7 @@ export class CriteriaNodeFactory {
|
|
|
52
52
|
const rawField = RawQueryFragment.isKnownFragmentSymbol(key);
|
|
53
53
|
const prop = rawField ? null : meta?.properties[QueryHelper.splitPolymorphicKey(key)[0]];
|
|
54
54
|
const target = rawField ? undefined : QueryHelper.findTargetMeta(prop, key);
|
|
55
|
-
const childEntity = prop && prop.kind !== ReferenceKind.SCALAR
|
|
55
|
+
const childEntity = prop && prop.kind !== ReferenceKind.SCALAR && target ? target.class : entityName;
|
|
56
56
|
const isNotEmbedded = rawField || prop?.kind !== ReferenceKind.EMBEDDED;
|
|
57
57
|
const val = payload[key];
|
|
58
58
|
if (isNotEmbedded && prop?.customType instanceof JsonType) {
|
|
@@ -5,6 +5,8 @@ import type { ICriteriaNodeProcessOptions, IQueryBuilder } from '../typings.js';
|
|
|
5
5
|
*/
|
|
6
6
|
export declare class ObjectCriteriaNode<T extends object> extends CriteriaNode<T> {
|
|
7
7
|
process(qb: IQueryBuilder<T>, options?: ICriteriaNodeProcessOptions): any;
|
|
8
|
+
/** Orders by the first non-null value of the property across the joined polymorphic targets. */
|
|
9
|
+
private processPolymorphicOrderBy;
|
|
8
10
|
isStrict(): boolean;
|
|
9
11
|
unwrap(): any;
|
|
10
12
|
willAutoJoin(qb: IQueryBuilder<T>, alias?: string, options?: ICriteriaNodeProcessOptions): boolean;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ALIAS_REPLACEMENT, GroupOperator, QueryFlag, raw, RawQueryFragment, ReferenceKind, Utils, } from '@mikro-orm/core';
|
|
1
|
+
import { ALIAS_REPLACEMENT, GroupOperator, QueryFlag, QueryHelper, raw, RawQueryFragment, ReferenceKind, Utils, } from '@mikro-orm/core';
|
|
2
2
|
import { CriteriaNode } from './CriteriaNode.js';
|
|
3
3
|
import { JoinType, QueryType } from './enums.js';
|
|
4
4
|
const COLLECTION_OPERATORS = ['$some', '$none', '$every', '$size', '$all'];
|
|
@@ -7,6 +7,10 @@ const COLLECTION_OPERATORS = ['$some', '$none', '$every', '$size', '$all'];
|
|
|
7
7
|
*/
|
|
8
8
|
export class ObjectCriteriaNode extends CriteriaNode {
|
|
9
9
|
process(qb, options) {
|
|
10
|
+
const targets = this.isPolymorphicBranch() ? QueryHelper.findTargetMetas(this.prop, this.key) : [];
|
|
11
|
+
if (options?.type === 'orderBy' && targets.length > 1) {
|
|
12
|
+
return this.processPolymorphicOrderBy(qb, targets, options);
|
|
13
|
+
}
|
|
10
14
|
const matchPopulateJoins = options?.matchPopulateJoins ||
|
|
11
15
|
(this.prop && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(this.prop.kind));
|
|
12
16
|
const nestedAlias = qb.getAliasForJoinPath(this.getPath(options), { ...options, matchPopulateJoins });
|
|
@@ -150,12 +154,31 @@ export class ObjectCriteriaNode extends CriteriaNode {
|
|
|
150
154
|
return o;
|
|
151
155
|
}, {});
|
|
152
156
|
// the target is left joined, so its condition must only match rows that point to it
|
|
153
|
-
if (this.isPolymorphicBranch() && alias) {
|
|
157
|
+
if (this.isPolymorphicBranch() && alias && options?.type !== 'orderBy') {
|
|
154
158
|
const pk = this.metadata.find(this.entityName).primaryKeys[0];
|
|
155
159
|
this.inlineCondition(`${alias}.${pk}`, ret, { $ne: null });
|
|
156
160
|
}
|
|
157
161
|
return ret;
|
|
158
162
|
}
|
|
163
|
+
/** Orders by the first non-null value of the property across the joined polymorphic targets. */
|
|
164
|
+
processPolymorphicOrderBy(qb, targets, options) {
|
|
165
|
+
const ownerAlias = options.alias || qb.alias;
|
|
166
|
+
const aliases = targets.map(target => {
|
|
167
|
+
const path = `${this.parent.getPath()}.${this.prop.name}[${target.className}]`;
|
|
168
|
+
const alias = qb.getAliasForJoinPath(path, { ...options, matchPopulateJoins: true });
|
|
169
|
+
if (alias) {
|
|
170
|
+
return alias;
|
|
171
|
+
}
|
|
172
|
+
const newAlias = qb.getNextAlias(target.class);
|
|
173
|
+
qb.addPolymorphicJoin(this.prop, target, ownerAlias, newAlias, JoinType.leftJoin, path);
|
|
174
|
+
return newAlias;
|
|
175
|
+
});
|
|
176
|
+
return Utils.getObjectQueryKeys(this.payload).reduce((o, field) => {
|
|
177
|
+
const columns = targets.map((target, i) => `${aliases[i]}.${target.properties[field].fieldNames[0]}`);
|
|
178
|
+
o[raw(`coalesce(${columns.map(() => '??').join(', ')})`, columns)] = this.payload[field].payload;
|
|
179
|
+
return o;
|
|
180
|
+
}, {});
|
|
181
|
+
}
|
|
159
182
|
isStrict() {
|
|
160
183
|
return (this.strict ||
|
|
161
184
|
Utils.getObjectQueryKeys(this.payload).some(key => {
|
|
@@ -192,8 +215,9 @@ export class ObjectCriteriaNode extends CriteriaNode {
|
|
|
192
215
|
const operator = Utils.isObject(payload) &&
|
|
193
216
|
Utils.getObjectQueryKeys(payload).every(k => {
|
|
194
217
|
if (k === '$not' && Utils.isPlainObject(payload[k])) {
|
|
195
|
-
// $not wrapping non-operator conditions (entity props) should be inlined
|
|
196
|
-
|
|
218
|
+
// $not wrapping non-operator conditions (entity props) or an empty one should be inlined
|
|
219
|
+
const inner = Utils.getObjectQueryKeys(payload[k]);
|
|
220
|
+
return inner.length > 0 && inner.every(ik => Utils.isOperator(ik, false));
|
|
197
221
|
}
|
|
198
222
|
return Utils.isOperator(k, false);
|
|
199
223
|
});
|
|
@@ -224,9 +248,12 @@ export class ObjectCriteriaNode extends CriteriaNode {
|
|
|
224
248
|
}
|
|
225
249
|
else if (k === '$not' &&
|
|
226
250
|
Utils.isPlainObject(payload[k]) &&
|
|
227
|
-
Utils.
|
|
228
|
-
|
|
229
|
-
|
|
251
|
+
(!Utils.hasObjectKeys(payload[k]) ||
|
|
252
|
+
Utils.getObjectQueryKeys(payload[k]).some(ik => !Utils.isOperator(ik, false)))) {
|
|
253
|
+
// $not wraps entity conditions (from auto-join) or an empty one, inline at current level with the same key mapping
|
|
254
|
+
const inner = {};
|
|
255
|
+
this.inlineChildPayload(inner, payload[k], field, alias, childAlias);
|
|
256
|
+
this.inlineCondition(k, o, inner);
|
|
230
257
|
}
|
|
231
258
|
else if (Utils.isOperator(k, false)) {
|
|
232
259
|
const tmp = payload[k];
|
|
@@ -301,6 +328,13 @@ export class ObjectCriteriaNode extends CriteriaNode {
|
|
|
301
328
|
});
|
|
302
329
|
const primaryKeys = knownKey &&
|
|
303
330
|
keys.every(key => {
|
|
331
|
+
if (key === '$not') {
|
|
332
|
+
// e.g. `{ author: { $not: { id: 1 } } }` compares the FK column, relation PKs still need the join
|
|
333
|
+
const childPayload = this.payload[key].payload;
|
|
334
|
+
return (Utils.isPlainObject(childPayload) &&
|
|
335
|
+
Utils.getObjectQueryKeys(childPayload).every(k => meta.primaryKeys.includes(k) &&
|
|
336
|
+
meta.properties[k].kind === ReferenceKind.SCALAR));
|
|
337
|
+
}
|
|
304
338
|
if (typeof key !== 'string' || !meta.primaryKeys.includes(key)) {
|
|
305
339
|
return false;
|
|
306
340
|
}
|
package/query/QueryBuilder.js
CHANGED
|
@@ -1032,9 +1032,11 @@ export class QueryBuilder {
|
|
|
1032
1032
|
* @internal
|
|
1033
1033
|
*/
|
|
1034
1034
|
addPolymorphicJoin(prop, targetMeta, ownerAlias, alias, type, path, schema) {
|
|
1035
|
-
// Override referencedColumnNames to use the specific target's PK columns
|
|
1035
|
+
// Override referencedColumnNames to use the specific target's PK (or `targetKey`) columns
|
|
1036
1036
|
// (polymorphic targets may have different PK column names, e.g. org_id vs user_id)
|
|
1037
|
-
const referencedColumnNames =
|
|
1037
|
+
const referencedColumnNames = prop.targetKey
|
|
1038
|
+
? targetMeta.properties[prop.targetKey].fieldNames
|
|
1039
|
+
: targetMeta.getPrimaryProps().flatMap(pk => pk.fieldNames);
|
|
1038
1040
|
const targetProp = { ...prop, targetMeta, referencedColumnNames };
|
|
1039
1041
|
const aliasedName = `${ownerAlias}.${prop.name}[${targetMeta.className}]#${alias}`;
|
|
1040
1042
|
this.#state.joins[aliasedName] = this.helper.joinManyToOneReference(targetProp, ownerAlias, alias, type, {}, schema);
|