@mikro-orm/core 7.3.0-dev.1 → 7.3.0-dev.2
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/metadata/MetadataDiscovery.js +15 -3
- package/package.json +1 -1
- package/utils/QueryHelper.d.ts +12 -0
- package/utils/QueryHelper.js +66 -5
- package/utils/Utils.js +1 -1
|
@@ -547,7 +547,7 @@ export class MetadataDiscovery {
|
|
|
547
547
|
const fieldNames1 = prop.targetMeta.getPrimaryProps().flatMap(pk => pk.fieldNames);
|
|
548
548
|
const idColumns = fieldNames1.map(fieldName => this.#namingStrategy.joinKeyColumnName(prop.discriminator, fieldName, fieldNames1.length > 1));
|
|
549
549
|
prop.fieldNames ??= [prop.discriminatorColumn, ...idColumns];
|
|
550
|
-
prop.joinColumns ??=
|
|
550
|
+
prop.joinColumns ??= prop.fieldNames.slice(1);
|
|
551
551
|
prop.referencedColumnNames ??= fieldNames1;
|
|
552
552
|
prop.referencedTableName ??= prop.targetMeta.tableName;
|
|
553
553
|
return;
|
|
@@ -1175,12 +1175,24 @@ export class MetadataDiscovery {
|
|
|
1175
1175
|
if (!prop.discriminator && !prop.discriminatorColumn && !prop.discriminatorMap && !Array.isArray(prop.target)) {
|
|
1176
1176
|
return;
|
|
1177
1177
|
}
|
|
1178
|
+
const isToOne = [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind);
|
|
1179
|
+
const isUnionTargetMN = prop.kind === ReferenceKind.MANY_TO_MANY && Array.isArray(prop.target);
|
|
1178
1180
|
prop.polymorphic = true;
|
|
1179
1181
|
prop.discriminator ??= prop.name;
|
|
1182
|
+
// explicit `fieldNames` use the `[discriminatorColumn, ...fkIdColumns]` layout
|
|
1183
|
+
if (isToOne && prop.fieldNames && prop.fieldNames.length > 1) {
|
|
1184
|
+
prop.discriminatorColumn ??= prop.fieldNames[0];
|
|
1185
|
+
// `EntitySchema` mirrors `fieldNames` into `joinColumns`, let `initManyToOneFields` derive them after embedded prefixing
|
|
1186
|
+
if (Utils.equals(prop.joinColumns, prop.fieldNames)) {
|
|
1187
|
+
delete prop.joinColumns;
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1180
1190
|
prop.discriminatorColumn ??= this.#namingStrategy.discriminatorColumnName(prop.discriminator);
|
|
1181
1191
|
prop.createForeignKeyConstraint = false;
|
|
1182
|
-
|
|
1183
|
-
|
|
1192
|
+
// a single field name can only be the FK column, e.g. mirrored from `joinColumn`
|
|
1193
|
+
if (isToOne && prop.fieldNames?.length === 1) {
|
|
1194
|
+
prop.fieldNames = [prop.discriminatorColumn, ...prop.fieldNames];
|
|
1195
|
+
}
|
|
1184
1196
|
if (isToOne || isUnionTargetMN) {
|
|
1185
1197
|
const types = prop.type.split(/ ?\| ?/);
|
|
1186
1198
|
prop.polymorphTargets = discovered.filter(m => types.includes(m.className) && !m.embeddable);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikro-orm/core",
|
|
3
|
-
"version": "7.3.0-dev.
|
|
3
|
+
"version": "7.3.0-dev.2",
|
|
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",
|
package/utils/QueryHelper.d.ts
CHANGED
|
@@ -38,6 +38,18 @@ export declare class QueryHelper {
|
|
|
38
38
|
/** Copies the plain object and array structure of the condition, keeping the leaf values (e.g. entities) by reference. */
|
|
39
39
|
private static cloneWhere;
|
|
40
40
|
static processWhere<T extends object>(options: ProcessWhereOptions<T>): FilterQuery<T>;
|
|
41
|
+
/** @internal Splits a polymorphic target key like `imageable[Article]` into the property and target class names. */
|
|
42
|
+
static splitPolymorphicKey(key: string): [string, string?];
|
|
43
|
+
/** @internal Resolves the target entity of a condition key, including polymorphic target keys like `imageable[Article]`. */
|
|
44
|
+
static findTargetMeta(prop: EntityProperty | null | undefined, key: string): EntityMetadata | undefined;
|
|
45
|
+
/**
|
|
46
|
+
* Routes conditions on polymorphic to-one relations to the targets that define all the queried properties,
|
|
47
|
+
* e.g. `{ imageable: { title } }` becomes `{ 'imageable[Article]': { title } }`, or an `$or` of such keys
|
|
48
|
+
* when more targets match. The first target alone keeps the regular relation join.
|
|
49
|
+
*/
|
|
50
|
+
private static splitPolymorphicConditions;
|
|
51
|
+
/** Property names used in a condition, including those nested in group operators. */
|
|
52
|
+
private static collectConditionKeys;
|
|
41
53
|
static getActiveFilters<T>(meta: EntityMetadata<T>, options: FilterOptions | undefined, filters: Dictionary<FilterDef>): FilterDef[];
|
|
42
54
|
/** @internal Sentinel wrapping for arguments accessed while statically resolving an `rls` filter condition. */
|
|
43
55
|
static readonly RLS_SENTINEL_PREFIX = "__mikro_rls_arg__";
|
package/utils/QueryHelper.js
CHANGED
|
@@ -158,17 +158,18 @@ export class QueryHelper {
|
|
|
158
158
|
})));
|
|
159
159
|
}
|
|
160
160
|
Object.keys(where).forEach(k => {
|
|
161
|
-
const prop = meta.properties[k];
|
|
162
|
-
const meta2 = metadata.find(prop?.
|
|
161
|
+
const prop = meta.properties[QueryHelper.splitPolymorphicKey(k)[0]];
|
|
162
|
+
const meta2 = metadata.find(QueryHelper.findTargetMeta(prop, k)?.class) || meta;
|
|
163
163
|
if (this.inlinePrimaryKeyObjects(where[k], meta2, metadata, k)) {
|
|
164
164
|
// Skip the PK collapse when an owning M:1/1:1 relation's FK column count does not match
|
|
165
165
|
// the target's PK column count (e.g. FK references target PK + an extra unique column).
|
|
166
166
|
// The criteria layer would otherwise emit a malformed predicate such as
|
|
167
167
|
// `(joinCol1, joinCol2) = scalar`. Limited to owning relations — `joinColumns` on the
|
|
168
168
|
// inverse side (1:m) describes the owning entity's FK columns, not the LHS tuple.
|
|
169
|
+
// Polymorphic relations are skipped too, a scalar would be compared to the discriminator column.
|
|
169
170
|
if (prop?.owner &&
|
|
170
171
|
[ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) &&
|
|
171
|
-
prop.joinColumns.length !== meta2.primaryKeys.length) {
|
|
172
|
+
(prop.polymorphic || prop.joinColumns.length !== meta2.primaryKeys.length)) {
|
|
172
173
|
return;
|
|
173
174
|
}
|
|
174
175
|
where[k] = Utils.getPrimaryKeyValues(where[k], meta2, true);
|
|
@@ -230,6 +231,9 @@ export class QueryHelper {
|
|
|
230
231
|
if (!Utils.isPlainObject(where)) {
|
|
231
232
|
return where;
|
|
232
233
|
}
|
|
234
|
+
if (meta && options.type !== 'orderBy') {
|
|
235
|
+
where = QueryHelper.splitPolymorphicConditions(meta, where);
|
|
236
|
+
}
|
|
233
237
|
return Utils.getObjectQueryKeys(where).reduce((o, key) => {
|
|
234
238
|
let value = where[key];
|
|
235
239
|
const customExpression = Raw.isKnownFragmentSymbol(key);
|
|
@@ -277,10 +281,11 @@ export class QueryHelper {
|
|
|
277
281
|
return o;
|
|
278
282
|
}
|
|
279
283
|
if (Utils.isPlainObject(value)) {
|
|
284
|
+
const targetMeta = customExpression ? undefined : QueryHelper.findTargetMeta(prop, key);
|
|
280
285
|
o[key] = QueryHelper.processWhere({
|
|
281
286
|
...options,
|
|
282
287
|
where: value,
|
|
283
|
-
entityName:
|
|
288
|
+
entityName: targetMeta?.class ?? entityName,
|
|
284
289
|
root: false,
|
|
285
290
|
});
|
|
286
291
|
}
|
|
@@ -290,6 +295,62 @@ export class QueryHelper {
|
|
|
290
295
|
return o;
|
|
291
296
|
}, {});
|
|
292
297
|
}
|
|
298
|
+
/** @internal Splits a polymorphic target key like `imageable[Article]` into the property and target class names. */
|
|
299
|
+
static splitPolymorphicKey(key) {
|
|
300
|
+
const match = /^(\w+)\[(\w+)]$/.exec(key);
|
|
301
|
+
return match ? [match[1], match[2]] : [key];
|
|
302
|
+
}
|
|
303
|
+
/** @internal Resolves the target entity of a condition key, including polymorphic target keys like `imageable[Article]`. */
|
|
304
|
+
static findTargetMeta(prop, key) {
|
|
305
|
+
const [, targetName] = QueryHelper.splitPolymorphicKey(key);
|
|
306
|
+
return targetName ? prop?.polymorphTargets?.find(t => t.className === targetName) : prop?.targetMeta;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Routes conditions on polymorphic to-one relations to the targets that define all the queried properties,
|
|
310
|
+
* e.g. `{ imageable: { title } }` becomes `{ 'imageable[Article]': { title } }`, or an `$or` of such keys
|
|
311
|
+
* when more targets match. The first target alone keeps the regular relation join.
|
|
312
|
+
*/
|
|
313
|
+
static splitPolymorphicConditions(meta, payload) {
|
|
314
|
+
let ret = payload;
|
|
315
|
+
for (const key of Object.keys(payload)) {
|
|
316
|
+
const prop = meta.properties[key];
|
|
317
|
+
const value = payload[key];
|
|
318
|
+
if (!prop?.polymorphTargets ||
|
|
319
|
+
![ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) ||
|
|
320
|
+
!Utils.isPlainObject(value)) {
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
const keys = this.collectConditionKeys(value);
|
|
324
|
+
const targets = prop.polymorphTargets.filter(target => keys.every(k => k in target.properties));
|
|
325
|
+
const primaryKeys = keys.every(k => targets.every(target => target.primaryKeys.includes(k)));
|
|
326
|
+
// PK conditions compare the FK columns without checking the type, so only when every target defines them
|
|
327
|
+
if (keys.length === 0 ||
|
|
328
|
+
targets.length === 0 ||
|
|
329
|
+
(primaryKeys && targets.length === prop.polymorphTargets.length) ||
|
|
330
|
+
(!primaryKeys && targets.length === 1 && targets[0] === prop.targetMeta)) {
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
const branches = targets.map(target => ({ [`${key}[${target.className}]`]: value }));
|
|
334
|
+
ret = { ...ret };
|
|
335
|
+
delete ret[key];
|
|
336
|
+
if (branches.length === 1) {
|
|
337
|
+
Object.assign(ret, branches[0]);
|
|
338
|
+
}
|
|
339
|
+
else {
|
|
340
|
+
ret.$and = [...(ret.$and ?? []), { $or: branches }];
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return ret;
|
|
344
|
+
}
|
|
345
|
+
/** Property names used in a condition, including those nested in group operators. */
|
|
346
|
+
static collectConditionKeys(payload) {
|
|
347
|
+
return Object.keys(payload).flatMap(k => {
|
|
348
|
+
if (k in GroupOperator || k === '$not') {
|
|
349
|
+
return Utils.asArray(payload[k]).flatMap(item => Utils.isPlainObject(item) ? this.collectConditionKeys(item) : []);
|
|
350
|
+
}
|
|
351
|
+
return Utils.isOperator(k) ? [] : [k];
|
|
352
|
+
});
|
|
353
|
+
}
|
|
293
354
|
static getActiveFilters(meta, options, filters) {
|
|
294
355
|
if (options === false) {
|
|
295
356
|
return [];
|
|
@@ -450,7 +511,7 @@ export class QueryHelper {
|
|
|
450
511
|
}
|
|
451
512
|
static findProperty(fieldName, options) {
|
|
452
513
|
const parts = fieldName.split('.');
|
|
453
|
-
const propName = parts.pop();
|
|
514
|
+
const propName = QueryHelper.splitPolymorphicKey(parts.pop())[0];
|
|
454
515
|
const alias = parts.length > 0 ? parts.join('.') : undefined;
|
|
455
516
|
const entityName = alias ? options.aliasMap?.[alias] : options.entityName;
|
|
456
517
|
const meta = entityName ? options.metadata.find(entityName) : undefined;
|
package/utils/Utils.js
CHANGED
|
@@ -153,7 +153,7 @@ export function parseJsonSafe(value) {
|
|
|
153
153
|
/** Collection of general-purpose utility methods used throughout the ORM. */
|
|
154
154
|
export class Utils {
|
|
155
155
|
static PK_SEPARATOR = '~~~';
|
|
156
|
-
static #ORM_VERSION = '7.3.0-dev.
|
|
156
|
+
static #ORM_VERSION = '7.3.0-dev.2';
|
|
157
157
|
/** Default session variable name backing an RLS filter argument (`current_setting('mikro.<filter>.<arg>')`). */
|
|
158
158
|
static getRlsSettingName(filterName, argName) {
|
|
159
159
|
return `mikro.${filterName}.${argName}`;
|