@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.
@@ -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 || fields.includes('*') || prop.primary || meta.root.discriminatorColumn === prop.name) {
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
- if (ref) {
1869
- // For filter :ref hints, schedule filter check for each target (no field selection)
1870
- qb.scheduleFilterCheck(targetPath);
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",
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.3"
56
+ "@mikro-orm/core": "7.3.0-dev.5"
57
57
  },
58
58
  "engines": {
59
59
  "node": ">= 22.17.0"
@@ -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 || !!prop.polymorphTargets?.some(t => t.className === 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 ? (target ?? prop.targetMeta).class : entityName;
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
- return Utils.getObjectQueryKeys(payload[k]).every(ik => Utils.isOperator(ik, false));
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.getObjectQueryKeys(payload[k]).some(ik => !Utils.isOperator(ik, false))) {
228
- // $not wraps entity conditions (from auto-join), inline at current level
229
- this.inlineCondition(k, o, payload[k]);
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
  }
@@ -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 = targetMeta.getPrimaryProps().flatMap(pk => pk.fieldNames);
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);