@mikro-orm/sql 7.1.13-dev.1 → 7.1.13-dev.10

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mikro-orm/sql",
3
- "version": "7.1.13-dev.1",
3
+ "version": "7.1.13-dev.10",
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.1.12"
54
54
  },
55
55
  "peerDependencies": {
56
- "@mikro-orm/core": "7.1.13-dev.1"
56
+ "@mikro-orm/core": "7.1.13-dev.10"
57
57
  },
58
58
  "engines": {
59
59
  "node": ">= 22.17.0"
@@ -994,6 +994,20 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
994
994
  processPopulateHint(): void;
995
995
  private processPopulateWhere;
996
996
  private mergeOnConditions;
997
+ /**
998
+ * `$or` branches can be moved to a join's `on` clause only when they all target that same join
999
+ * and stay flat — a partial `$or` in the `on` clause would drop rows matching a sibling branch,
1000
+ * and nested operators cannot be preserved inside a distributed disjunction, as `mergeOnConditions`
1001
+ * would flatten them into `and` conjuncts.
1002
+ */
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;
997
1011
  /**
998
1012
  * When adding an inner join on a left joined relation, we need to nest them,
999
1013
  * otherwise the inner join could discard rows of the root table.
@@ -2038,6 +2038,13 @@ 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
+ 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
+ }
2046
+ continue;
2047
+ }
2041
2048
  cond[k].forEach((c) => this.mergeOnConditions(joins, c, filter, k));
2042
2049
  }
2043
2050
  /* v8 ignore next */
@@ -2071,6 +2078,63 @@ export class QueryBuilder {
2071
2078
  }
2072
2079
  }
2073
2080
  }
2081
+ /**
2082
+ * `$or` branches can be moved to a join's `on` clause only when they all target that same join
2083
+ * and stay flat — a partial `$or` in the `on` clause would drop rows matching a sibling branch,
2084
+ * and nested operators cannot be preserved inside a distributed disjunction, as `mergeOnConditions`
2085
+ * would flatten them into `and` conjuncts.
2086
+ */
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) {
2094
+ const aliases = new Set();
2095
+ const collectAliases = (cond) => {
2096
+ for (const k of Object.keys(cond)) {
2097
+ if (Utils.isOperator(k)) {
2098
+ Utils.asArray(cond[k]).forEach((c) => collectAliases(c));
2099
+ }
2100
+ else {
2101
+ aliases.add(this.helper.splitField(k)[0]);
2102
+ }
2103
+ }
2104
+ };
2105
+ branches.forEach(collectAliases);
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 };
2137
+ }
2074
2138
  /**
2075
2139
  * When adding an inner join on a left joined relation, we need to nest them,
2076
2140
  * otherwise the inner join could discard rows of the root table.