@mikro-orm/knex 6.4.17-dev.74 → 6.4.17-dev.76

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.
@@ -837,7 +837,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
837
837
  const toPopulate = meta.relations
838
838
  .filter(prop => prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !prop.owner && !prop.lazy && !relationsToPopulate.includes(prop.name))
839
839
  .filter(prop => fields.length === 0 || fields.some(f => prop.name === f || prop.name.startsWith(`${String(f)}.`)))
840
- .map(prop => ({ field: `${prop.name}:ref`, strategy: prop.strategy ?? core_1.LoadStrategy.JOINED }));
840
+ .map(prop => ({ field: `${prop.name}:ref`, strategy: core_1.LoadStrategy.JOINED }));
841
841
  return [...populate, ...toPopulate];
842
842
  }
843
843
  /**
@@ -847,8 +847,8 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
847
847
  return populate.filter(hint => {
848
848
  const [propName] = hint.field.split(':', 2);
849
849
  const prop = meta.properties[propName] || {};
850
- const strategy = (0, core_1.getLoadingStrategy)(hint.strategy || options?.strategy || prop.strategy || this.config.get('loadStrategy'), prop.kind);
851
- if (hint.filter && (0, core_1.getLoadingStrategy)(hint.strategy, prop.kind) === core_1.LoadStrategy.JOINED) {
850
+ const strategy = (0, core_1.getLoadingStrategy)(hint.strategy || prop.strategy || options?.strategy || this.config.get('loadStrategy'), prop.kind);
851
+ if (hint.filter && [core_1.ReferenceKind.ONE_TO_ONE, core_1.ReferenceKind.MANY_TO_ONE].includes(prop.kind) && !prop.nullable) {
852
852
  return true;
853
853
  }
854
854
  // skip redundant joins for 1:1 owner population hints when using `mapToPk`
@@ -938,11 +938,12 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
938
938
  if (!parentJoinPath && populateWhereAll && !hint.filter && !path.startsWith('[populate]')) {
939
939
  path = '[populate]' + path;
940
940
  }
941
+ const mandatoryToOneProperty = [core_1.ReferenceKind.MANY_TO_ONE, core_1.ReferenceKind.ONE_TO_ONE].includes(prop.kind) && !prop.nullable;
941
942
  const joinType = pivotRefJoin
942
943
  ? query_1.JoinType.pivotJoin
943
944
  : hint.joinType
944
945
  ? hint.joinType
945
- : hint.filter && !prop.nullable
946
+ : (hint.filter && !prop.nullable) || mandatoryToOneProperty
946
947
  ? query_1.JoinType.innerJoin
947
948
  : query_1.JoinType.leftJoin;
948
949
  qb.join(field, tableAlias, {}, joinType, path);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mikro-orm/knex",
3
- "version": "6.4.17-dev.74",
3
+ "version": "6.4.17-dev.76",
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
  "main": "index.js",
6
6
  "module": "index.mjs",
@@ -66,7 +66,7 @@
66
66
  "@mikro-orm/core": "^6.4.16"
67
67
  },
68
68
  "peerDependencies": {
69
- "@mikro-orm/core": "6.4.17-dev.74",
69
+ "@mikro-orm/core": "6.4.17-dev.76",
70
70
  "better-sqlite3": "*",
71
71
  "libsql": "*",
72
72
  "mariadb": "*"
@@ -210,7 +210,23 @@ class ObjectCriteriaNode extends CriteriaNode_1.CriteriaNode {
210
210
  }
211
211
  else {
212
212
  const prev = qb._fields?.slice();
213
- qb[method](field, nestedAlias, undefined, enums_1.JoinType.leftJoin, path);
213
+ const toOneProperty = [core_1.ReferenceKind.MANY_TO_ONE, core_1.ReferenceKind.ONE_TO_ONE].includes(this.prop.kind);
214
+ const joinType = toOneProperty && !this.prop.nullable
215
+ ? enums_1.JoinType.innerJoin
216
+ : enums_1.JoinType.leftJoin;
217
+ qb[method](field, nestedAlias, undefined, joinType, path);
218
+ // if the property is nullable, we need to use left join, so we mimic the inner join behaviour
219
+ // with an exclusive condition on the join columns:
220
+ // - if the owning column is null, the row is missing, we don't apply the filter
221
+ // - if the target column is not null, the row is matched, we apply the filter
222
+ if (toOneProperty && this.prop.nullable && options?.filter) {
223
+ qb.andWhere({
224
+ $or: [
225
+ { [field]: null },
226
+ { [nestedAlias + '.' + core_1.Utils.getPrimaryKeyHash(this.prop.referencedPKs)]: { $ne: null } },
227
+ ],
228
+ });
229
+ }
214
230
  if (!qb.hasFlag(core_1.QueryFlag.INFER_POPULATE)) {
215
231
  qb._fields = prev;
216
232
  }
@@ -299,6 +299,11 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
299
299
  processPopulateHint(): void;
300
300
  private processPopulateWhere;
301
301
  private mergeOnConditions;
302
+ /**
303
+ * When adding an inner join on a left joined relation, we need to nest them,
304
+ * otherwise the inner join could discard rows of the root table.
305
+ */
306
+ private processNestedJoins;
302
307
  private hasToManyJoins;
303
308
  protected wrapPaginateSubQuery(meta: EntityMetadata): void;
304
309
  private wrapModifySubQuery;
@@ -1161,6 +1161,7 @@ class QueryBuilder {
1161
1161
  const meta = this.mainAlias.metadata;
1162
1162
  this.applyDiscriminatorCondition();
1163
1163
  this.processPopulateHint();
1164
+ this.processNestedJoins();
1164
1165
  if (meta && (this._fields?.includes('*') || this._fields?.includes(`${this.mainAlias.aliasName}.*`))) {
1165
1166
  meta.props
1166
1167
  .filter(prop => prop.formula && (!prop.lazy || this.flags.has(core_1.QueryFlag.INCLUDE_LAZY_FORMULAS)))
@@ -1240,7 +1241,7 @@ class QueryBuilder {
1240
1241
  if (typeof this[key] === 'object') {
1241
1242
  const cond = CriteriaNodeFactory_1.CriteriaNodeFactory
1242
1243
  .createNode(this.metadata, this.mainAlias.entityName, this[key])
1243
- .process(this, { matchPopulateJoins: true, ignoreBranching: true, preferNoBranch: true });
1244
+ .process(this, { matchPopulateJoins: true, ignoreBranching: true, preferNoBranch: true, filter });
1244
1245
  // there might be new joins created by processing the `populateWhere` object
1245
1246
  joins = Object.values(this._joins);
1246
1247
  this.mergeOnConditions(joins, cond, filter);
@@ -1281,6 +1282,29 @@ class QueryBuilder {
1281
1282
  }
1282
1283
  }
1283
1284
  }
1285
+ /**
1286
+ * When adding an inner join on a left joined relation, we need to nest them,
1287
+ * otherwise the inner join could discard rows of the root table.
1288
+ */
1289
+ processNestedJoins() {
1290
+ if (this.flags.has(core_1.QueryFlag.DISABLE_NESTED_INNER_JOIN)) {
1291
+ return;
1292
+ }
1293
+ const joins = Object.values(this._joins);
1294
+ for (const join of joins) {
1295
+ if (join.type === enums_1.JoinType.innerJoin) {
1296
+ const parentJoin = joins.find(j => j.alias === join.ownerAlias);
1297
+ // https://stackoverflow.com/a/56815807/3665878
1298
+ if (parentJoin?.type === enums_1.JoinType.leftJoin || parentJoin?.type === enums_1.JoinType.nestedLeftJoin) {
1299
+ const nested = (parentJoin.nested ??= new Set());
1300
+ join.type = join.type === enums_1.JoinType.innerJoin
1301
+ ? enums_1.JoinType.nestedInnerJoin
1302
+ : enums_1.JoinType.nestedLeftJoin;
1303
+ nested.add(join);
1304
+ }
1305
+ }
1306
+ }
1307
+ }
1284
1308
  hasToManyJoins() {
1285
1309
  return Object.values(this._joins).some(join => {
1286
1310
  return [core_1.ReferenceKind.ONE_TO_MANY, core_1.ReferenceKind.MANY_TO_MANY].includes(join.prop.kind);
package/typings.d.ts CHANGED
@@ -172,6 +172,7 @@ export interface ICriteriaNodeProcessOptions {
172
172
  ignoreBranching?: boolean;
173
173
  preferNoBranch?: boolean;
174
174
  type?: 'orderBy';
175
+ filter?: boolean;
175
176
  }
176
177
  export interface ICriteriaNode<T extends object> {
177
178
  readonly entityName: string;