metal-orm 1.0.115 → 1.0.116

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/dist/index.cjs CHANGED
@@ -3420,6 +3420,17 @@ var SelectQueryState = class _SelectQueryState {
3420
3420
  joins: [...this.ast.joins ?? [], join]
3421
3421
  });
3422
3422
  }
3423
+ /**
3424
+ * Replaces the JOIN list.
3425
+ * @param joins - Join nodes to set
3426
+ * @returns New SelectQueryState with updated JOINs
3427
+ */
3428
+ withJoins(joins) {
3429
+ return this.clone({
3430
+ ...this.ast,
3431
+ joins
3432
+ });
3433
+ }
3423
3434
  /**
3424
3435
  * Replaces the FROM clause.
3425
3436
  * @param from - Table source for the FROM clause
@@ -4342,9 +4353,6 @@ var buildRelationCorrelation = (root, relation, rootAlias, targetTableName) => {
4342
4353
  return baseRelationCondition(root, relation, rootAlias, targetTableName);
4343
4354
  };
4344
4355
 
4345
- // src/core/ast/join-metadata.ts
4346
- var getJoinRelationName = (join) => join.meta?.relationName;
4347
-
4348
4356
  // src/query-builder/relation-filter-utils.ts
4349
4357
  var splitFilterExpressions = (filter, allowedTables) => {
4350
4358
  const terms = flattenAnd(filter);
@@ -4482,7 +4490,241 @@ var collectFromOrderingTerm = (term, collector) => {
4482
4490
  collectFromExpression(term, collector);
4483
4491
  };
4484
4492
 
4485
- // src/query-builder/relation-join-planner.ts
4493
+ // src/query-builder/expression-table-mapper.ts
4494
+ var remapExpressionTable = (expr, fromTable, toTable) => {
4495
+ if (!expr || fromTable === toTable) return expr;
4496
+ return mapExpression(expr, fromTable, toTable);
4497
+ };
4498
+ var mapExpression = (expr, fromTable, toTable) => {
4499
+ switch (expr.type) {
4500
+ case "BinaryExpression": {
4501
+ const left2 = mapOperand(expr.left, fromTable, toTable);
4502
+ const right2 = mapOperand(expr.right, fromTable, toTable);
4503
+ if (left2 === expr.left && right2 === expr.right) return expr;
4504
+ return { ...expr, left: left2, right: right2 };
4505
+ }
4506
+ case "LogicalExpression": {
4507
+ const nextOperands = expr.operands.map((op) => mapExpression(op, fromTable, toTable));
4508
+ if (nextOperands.every((op, i) => op === expr.operands[i])) return expr;
4509
+ return { ...expr, operands: nextOperands };
4510
+ }
4511
+ case "NullExpression": {
4512
+ const left2 = mapOperand(expr.left, fromTable, toTable);
4513
+ if (left2 === expr.left) return expr;
4514
+ return { ...expr, left: left2 };
4515
+ }
4516
+ case "InExpression": {
4517
+ const left2 = mapOperand(expr.left, fromTable, toTable);
4518
+ let right2 = expr.right;
4519
+ if (Array.isArray(expr.right)) {
4520
+ const mapped = expr.right.map((val) => mapOperand(val, fromTable, toTable));
4521
+ if (!mapped.every((val, i) => val === expr.right[i])) {
4522
+ right2 = mapped;
4523
+ }
4524
+ } else if (expr.right.type === "ScalarSubquery") {
4525
+ const mapped = mapScalarSubquery(expr.right, fromTable, toTable);
4526
+ if (mapped !== expr.right) right2 = mapped;
4527
+ }
4528
+ if (left2 === expr.left && right2 === expr.right) return expr;
4529
+ return { ...expr, left: left2, right: right2 };
4530
+ }
4531
+ case "ExistsExpression": {
4532
+ const mapped = mapSelectQuery(expr.subquery, fromTable, toTable);
4533
+ if (mapped === expr.subquery) return expr;
4534
+ return { ...expr, subquery: mapped };
4535
+ }
4536
+ case "BetweenExpression": {
4537
+ const left2 = mapOperand(expr.left, fromTable, toTable);
4538
+ const lower2 = mapOperand(expr.lower, fromTable, toTable);
4539
+ const upper2 = mapOperand(expr.upper, fromTable, toTable);
4540
+ if (left2 === expr.left && lower2 === expr.lower && upper2 === expr.upper) return expr;
4541
+ return { ...expr, left: left2, lower: lower2, upper: upper2 };
4542
+ }
4543
+ case "ArithmeticExpression": {
4544
+ const left2 = mapOperand(expr.left, fromTable, toTable);
4545
+ const right2 = mapOperand(expr.right, fromTable, toTable);
4546
+ if (left2 === expr.left && right2 === expr.right) return expr;
4547
+ return { ...expr, left: left2, right: right2 };
4548
+ }
4549
+ case "BitwiseExpression": {
4550
+ const left2 = mapOperand(expr.left, fromTable, toTable);
4551
+ const right2 = mapOperand(expr.right, fromTable, toTable);
4552
+ if (left2 === expr.left && right2 === expr.right) return expr;
4553
+ return { ...expr, left: left2, right: right2 };
4554
+ }
4555
+ default:
4556
+ return expr;
4557
+ }
4558
+ };
4559
+ var mapColumn = (node, fromTable, toTable) => {
4560
+ if (node.table !== fromTable) return node;
4561
+ return { ...node, table: toTable };
4562
+ };
4563
+ var mapJsonPath = (node, fromTable, toTable) => {
4564
+ const nextColumn = mapColumn(node.column, fromTable, toTable);
4565
+ if (nextColumn === node.column) return node;
4566
+ return { ...node, column: nextColumn };
4567
+ };
4568
+ var mapWindowFunctionArg = (node, fromTable, toTable) => {
4569
+ switch (node.type) {
4570
+ case "Column":
4571
+ return mapColumn(node, fromTable, toTable);
4572
+ case "JsonPath":
4573
+ return mapJsonPath(node, fromTable, toTable);
4574
+ default:
4575
+ return node;
4576
+ }
4577
+ };
4578
+ var mapOperand = (node, fromTable, toTable) => {
4579
+ switch (node.type) {
4580
+ case "Column":
4581
+ return mapColumn(node, fromTable, toTable);
4582
+ case "Function": {
4583
+ const nextArgs = node.args.map((arg) => mapOperand(arg, fromTable, toTable));
4584
+ const nextSeparator = node.separator ? mapOperand(node.separator, fromTable, toTable) : node.separator;
4585
+ const nextOrderBy = node.orderBy?.map((order) => ({
4586
+ ...order,
4587
+ term: mapOrderingTerm(order.term, fromTable, toTable)
4588
+ }));
4589
+ const changed = nextArgs.some((arg, i) => arg !== node.args[i]) || nextSeparator !== node.separator || nextOrderBy && node.orderBy && nextOrderBy.some((ob, i) => ob.term !== node.orderBy[i].term);
4590
+ if (!changed) return node;
4591
+ return {
4592
+ ...node,
4593
+ args: nextArgs,
4594
+ separator: nextSeparator,
4595
+ orderBy: nextOrderBy
4596
+ };
4597
+ }
4598
+ case "JsonPath": {
4599
+ return mapJsonPath(node, fromTable, toTable);
4600
+ }
4601
+ case "ScalarSubquery": {
4602
+ return mapScalarSubquery(node, fromTable, toTable);
4603
+ }
4604
+ case "CaseExpression": {
4605
+ const nextConditions = node.conditions.map((cond) => ({
4606
+ when: mapExpression(cond.when, fromTable, toTable),
4607
+ then: mapOperand(cond.then, fromTable, toTable)
4608
+ }));
4609
+ const nextElse = node.else ? mapOperand(node.else, fromTable, toTable) : node.else;
4610
+ const changed = nextConditions.some(
4611
+ (cond, i) => cond.when !== node.conditions[i].when || cond.then !== node.conditions[i].then
4612
+ ) || nextElse !== node.else;
4613
+ if (!changed) return node;
4614
+ return { ...node, conditions: nextConditions, else: nextElse };
4615
+ }
4616
+ case "Cast": {
4617
+ const nextExpr = mapOperand(node.expression, fromTable, toTable);
4618
+ if (nextExpr === node.expression) return node;
4619
+ return { ...node, expression: nextExpr };
4620
+ }
4621
+ case "WindowFunction": {
4622
+ const nextArgs = node.args.map((arg) => mapWindowFunctionArg(arg, fromTable, toTable));
4623
+ const nextPartition = node.partitionBy?.map((part) => mapColumn(part, fromTable, toTable));
4624
+ const nextOrderBy = node.orderBy?.map((order) => ({
4625
+ ...order,
4626
+ term: mapOrderingTerm(order.term, fromTable, toTable)
4627
+ }));
4628
+ const changed = nextArgs.some((arg, i) => arg !== node.args[i]) || nextPartition && node.partitionBy && nextPartition.some((p, i) => p !== node.partitionBy[i]) || nextOrderBy && node.orderBy && nextOrderBy.some((ob, i) => ob.term !== node.orderBy[i].term);
4629
+ if (!changed) return node;
4630
+ return {
4631
+ ...node,
4632
+ args: nextArgs,
4633
+ partitionBy: nextPartition,
4634
+ orderBy: nextOrderBy
4635
+ };
4636
+ }
4637
+ case "Collate": {
4638
+ const nextExpr = mapOperand(node.expression, fromTable, toTable);
4639
+ if (nextExpr === node.expression) return node;
4640
+ return { ...node, expression: nextExpr };
4641
+ }
4642
+ case "ArithmeticExpression": {
4643
+ const left2 = mapOperand(node.left, fromTable, toTable);
4644
+ const right2 = mapOperand(node.right, fromTable, toTable);
4645
+ if (left2 === node.left && right2 === node.right) return node;
4646
+ return { ...node, left: left2, right: right2 };
4647
+ }
4648
+ case "BitwiseExpression": {
4649
+ const left2 = mapOperand(node.left, fromTable, toTable);
4650
+ const right2 = mapOperand(node.right, fromTable, toTable);
4651
+ if (left2 === node.left && right2 === node.right) return node;
4652
+ return { ...node, left: left2, right: right2 };
4653
+ }
4654
+ default:
4655
+ return node;
4656
+ }
4657
+ };
4658
+ var mapOrderingTerm = (term, fromTable, toTable) => {
4659
+ if (isOperandNode(term)) {
4660
+ return mapOperand(term, fromTable, toTable);
4661
+ }
4662
+ return mapExpression(term, fromTable, toTable);
4663
+ };
4664
+ var mapScalarSubquery = (node, fromTable, toTable) => {
4665
+ const mapped = mapSelectQuery(node.query, fromTable, toTable);
4666
+ if (mapped === node.query) return node;
4667
+ return { ...node, query: mapped };
4668
+ };
4669
+ var mapProjectionNode = (node, fromTable, toTable) => {
4670
+ switch (node.type) {
4671
+ case "Column":
4672
+ return mapColumn(node, fromTable, toTable);
4673
+ case "Function":
4674
+ return mapOperand(node, fromTable, toTable);
4675
+ case "CaseExpression":
4676
+ return mapOperand(node, fromTable, toTable);
4677
+ case "Cast":
4678
+ return mapOperand(node, fromTable, toTable);
4679
+ case "WindowFunction":
4680
+ return mapOperand(node, fromTable, toTable);
4681
+ case "ScalarSubquery":
4682
+ return mapScalarSubquery(node, fromTable, toTable);
4683
+ default:
4684
+ return node;
4685
+ }
4686
+ };
4687
+ var mapSelectQuery = (query, fromTable, toTable) => {
4688
+ const nextColumns = query.columns.map((col2) => mapProjectionNode(col2, fromTable, toTable));
4689
+ const nextJoins = query.joins.map((join) => ({
4690
+ ...join,
4691
+ condition: mapExpression(join.condition, fromTable, toTable)
4692
+ }));
4693
+ const nextWhere = query.where ? mapExpression(query.where, fromTable, toTable) : query.where;
4694
+ const nextHaving = query.having ? mapExpression(query.having, fromTable, toTable) : query.having;
4695
+ const nextGroupBy = query.groupBy?.map((term) => mapOrderingTerm(term, fromTable, toTable));
4696
+ const nextOrderBy = query.orderBy?.map((ob) => ({
4697
+ ...ob,
4698
+ term: mapOrderingTerm(ob.term, fromTable, toTable)
4699
+ }));
4700
+ const nextDistinct = query.distinct?.map((col2) => mapColumn(col2, fromTable, toTable));
4701
+ const nextSetOps = query.setOps?.map((op) => ({ ...op, query: mapSelectQuery(op.query, fromTable, toTable) }));
4702
+ const nextCtes = query.ctes?.map((cte) => ({ ...cte, query: mapSelectQuery(cte.query, fromTable, toTable) }));
4703
+ const changed = nextColumns.some((c, i) => c !== query.columns[i]) || nextJoins.some((j, i) => j.condition !== query.joins[i].condition) || nextWhere !== query.where || nextHaving !== query.having || nextGroupBy && query.groupBy && nextGroupBy.some((t, i) => t !== query.groupBy[i]) || nextOrderBy && query.orderBy && nextOrderBy.some((o, i) => o.term !== query.orderBy[i].term) || nextDistinct && query.distinct && nextDistinct.some((d, i) => d !== query.distinct[i]) || nextSetOps && query.setOps && nextSetOps.some((o, i) => o.query !== query.setOps[i].query) || nextCtes && query.ctes && nextCtes.some((c, i) => c.query !== query.ctes[i].query);
4704
+ if (!changed) return query;
4705
+ return {
4706
+ ...query,
4707
+ columns: nextColumns,
4708
+ joins: nextJoins,
4709
+ where: nextWhere,
4710
+ having: nextHaving,
4711
+ groupBy: nextGroupBy,
4712
+ orderBy: nextOrderBy,
4713
+ distinct: nextDistinct,
4714
+ setOps: nextSetOps,
4715
+ ctes: nextCtes
4716
+ };
4717
+ };
4718
+
4719
+ // src/core/ast/join-metadata.ts
4720
+ var getJoinRelationName = (join) => join.meta?.relationName;
4721
+
4722
+ // src/query-builder/join-utils.ts
4723
+ var findJoinIndexByRelationKey = (joins, relationKey) => joins.findIndex((j) => getJoinRelationName(j) === relationKey);
4724
+ var findJoinByRelationKey = (joins, relationKey) => joins.find((j) => getJoinRelationName(j) === relationKey);
4725
+ var hasJoinForRelationKey = (joins, relationKey) => findJoinIndexByRelationKey(joins, relationKey) !== -1;
4726
+
4727
+ // src/query-builder/table-alias-utils.ts
4486
4728
  var getExposedName = (ts) => {
4487
4729
  if (ts.type === "Table") return ts.alias ?? ts.name;
4488
4730
  if (ts.type === "DerivedTable") return ts.alias;
@@ -4514,69 +4756,118 @@ var ensureCorrelationName = (state, relationName, ts, extraUsed) => {
4514
4756
  const alias = makeUniqueAlias(relationName, used);
4515
4757
  return { ...ts, alias };
4516
4758
  };
4517
- var RelationJoinPlanner = class {
4518
- constructor(table, createQueryAstService) {
4519
- this.table = table;
4520
- this.createQueryAstService = createQueryAstService;
4759
+ var getJoinCorrelationName = (state, relationName, fallback) => {
4760
+ const join = findJoinByRelationKey(state.ast.joins, relationName);
4761
+ if (!join) return fallback;
4762
+ const t = join.table;
4763
+ if (t.type === "Table") return t.alias ?? t.name;
4764
+ if (t.type === "DerivedTable") return t.alias;
4765
+ if (t.type === "FunctionTable") return t.alias ?? fallback;
4766
+ return fallback;
4767
+ };
4768
+ var resolveTargetTableName = (target, fallback) => {
4769
+ if (target.type === "Table") return target.alias ?? target.name;
4770
+ if (target.type === "DerivedTable") return target.alias;
4771
+ if (target.type === "FunctionTable") return target.alias ?? fallback;
4772
+ return fallback;
4773
+ };
4774
+
4775
+ // src/query-builder/relation-join-strategies.ts
4776
+ var buildBelongsToManyTargetCondition = (relation, targetName, extra) => {
4777
+ const targetKey = relation.targetKey || findPrimaryKey(relation.target);
4778
+ let condition = eq(
4779
+ { type: "Column", table: targetName, name: targetKey },
4780
+ { type: "Column", table: relation.pivotTable.name, name: relation.pivotForeignKeyToTarget }
4781
+ );
4782
+ if (extra) {
4783
+ condition = and(condition, extra);
4521
4784
  }
4522
- withJoin(state, relationName, relation, joinKind, extraCondition, tableSource) {
4523
- const rootAlias = state.ast.from.type === "Table" ? state.ast.from.alias : void 0;
4524
- if (relation.type === RelationKinds.BelongsToMany) {
4525
- let targetTableSource = tableSource ?? {
4526
- type: "Table",
4527
- name: relation.target.name,
4528
- schema: relation.target.schema
4529
- };
4530
- targetTableSource = ensureCorrelationName(
4531
- state,
4532
- relationName,
4533
- targetTableSource,
4534
- [relation.pivotTable.name]
4535
- );
4536
- const targetName2 = this.resolveTargetTableName(targetTableSource, relation);
4537
- const joins = buildBelongsToManyJoins(
4538
- this.table,
4539
- relationName,
4540
- relation,
4541
- joinKind,
4542
- extraCondition,
4543
- rootAlias,
4544
- targetTableSource,
4545
- targetName2
4546
- );
4547
- return joins.reduce((current, join) => this.astService(current).withJoin(join), state);
4548
- }
4549
- let targetTable = tableSource ?? {
4785
+ return condition;
4786
+ };
4787
+ var addRelationJoin = (params) => {
4788
+ const { state, rootTable, rootAlias, relationKey, relation, joinKind, filter, tableSource } = params;
4789
+ if (relation.type === RelationKinds.BelongsToMany) {
4790
+ const many = relation;
4791
+ let targetSource2 = tableSource ?? {
4550
4792
  type: "Table",
4551
4793
  name: relation.target.name,
4552
4794
  schema: relation.target.schema
4553
4795
  };
4554
- targetTable = ensureCorrelationName(state, relationName, targetTable);
4555
- const targetName = this.resolveTargetTableName(targetTable, relation);
4556
- const condition = buildRelationJoinCondition(
4557
- this.table,
4558
- relation,
4559
- extraCondition,
4796
+ targetSource2 = ensureCorrelationName(state, relationKey, targetSource2, [many.pivotTable.name]);
4797
+ const targetName2 = resolveTargetTableName(targetSource2, relation.target.name);
4798
+ const extra2 = remapExpressionTable(filter, relation.target.name, targetName2);
4799
+ const joins = buildBelongsToManyJoins(
4800
+ rootTable,
4801
+ relationKey,
4802
+ many,
4803
+ joinKind,
4804
+ extra2,
4560
4805
  rootAlias,
4561
- targetName
4806
+ targetSource2,
4807
+ targetName2
4562
4808
  );
4563
- const joinNode = createJoinNode(joinKind, targetTable, condition, relationName);
4564
- return this.astService(state).withJoin(joinNode);
4809
+ return joins.reduce((curr, join) => curr.withJoin(join), state);
4565
4810
  }
4566
- astService(state) {
4567
- return this.createQueryAstService(this.table, state);
4568
- }
4569
- resolveTargetTableName(target, relation) {
4570
- if (target.type === "Table") {
4571
- return target.alias ?? target.name;
4572
- }
4573
- if (target.type === "DerivedTable") {
4574
- return target.alias;
4575
- }
4576
- if (target.type === "FunctionTable") {
4577
- return target.alias ?? relation.target.name;
4811
+ let targetSource = tableSource ?? {
4812
+ type: "Table",
4813
+ name: relation.target.name,
4814
+ schema: relation.target.schema
4815
+ };
4816
+ targetSource = ensureCorrelationName(state, relationKey, targetSource);
4817
+ const targetName = resolveTargetTableName(targetSource, relation.target.name);
4818
+ const extra = remapExpressionTable(filter, relation.target.name, targetName);
4819
+ const condition = buildRelationJoinCondition(rootTable, relation, extra, rootAlias, targetName);
4820
+ const joinNode = createJoinNode(joinKind, targetSource, condition, relationKey);
4821
+ return state.withJoin(joinNode);
4822
+ };
4823
+ var updateRelationJoin = (params) => {
4824
+ const { joins, joinIndex, relation, currentTable, currentAlias, options } = params;
4825
+ const join = joins[joinIndex];
4826
+ const targetName = resolveTargetTableName(join.table, relation.target.name);
4827
+ const extra = remapExpressionTable(options.filter, relation.target.name, targetName);
4828
+ if (relation.type === RelationKinds.BelongsToMany) {
4829
+ const many = relation;
4830
+ const targetCondition = buildBelongsToManyTargetCondition(many, targetName, extra);
4831
+ joins[joinIndex] = {
4832
+ ...join,
4833
+ kind: options.joinKind ?? join.kind,
4834
+ condition: targetCondition
4835
+ };
4836
+ if (options.joinKind && joinIndex > 0) {
4837
+ const pivotJoin = joins[joinIndex - 1];
4838
+ const pivotTable = pivotJoin.table.type === "Table" ? pivotJoin.table.name : void 0;
4839
+ if (pivotTable === many.pivotTable.name) {
4840
+ joins[joinIndex - 1] = { ...pivotJoin, kind: options.joinKind };
4841
+ }
4578
4842
  }
4579
- return relation.target.name;
4843
+ return joins;
4844
+ }
4845
+ const condition = buildRelationJoinCondition(currentTable, relation, extra, currentAlias, targetName);
4846
+ joins[joinIndex] = {
4847
+ ...join,
4848
+ kind: options.joinKind ?? join.kind,
4849
+ condition
4850
+ };
4851
+ return joins;
4852
+ };
4853
+
4854
+ // src/query-builder/relation-join-planner.ts
4855
+ var RelationJoinPlanner = class {
4856
+ constructor(table) {
4857
+ this.table = table;
4858
+ }
4859
+ withJoin(state, relationName, relation, joinKind, extraCondition, tableSource) {
4860
+ const rootAlias = state.ast.from.type === "Table" ? state.ast.from.alias : void 0;
4861
+ return addRelationJoin({
4862
+ state,
4863
+ rootTable: this.table,
4864
+ rootAlias,
4865
+ relationKey: relationName,
4866
+ relation,
4867
+ joinKind,
4868
+ filter: extraCondition,
4869
+ tableSource
4870
+ });
4580
4871
  }
4581
4872
  };
4582
4873
 
@@ -4627,15 +4918,6 @@ var RelationCteBuilder = class {
4627
4918
  };
4628
4919
 
4629
4920
  // src/query-builder/relation-include-strategies.ts
4630
- var getJoinCorrelationName = (state, relationName, fallback) => {
4631
- const join = state.ast.joins.find((j) => getJoinRelationName(j) === relationName);
4632
- if (!join) return fallback;
4633
- const t = join.table;
4634
- if (t.type === "Table") return t.alias ?? t.name;
4635
- if (t.type === "DerivedTable") return t.alias;
4636
- if (t.type === "FunctionTable") return t.alias ?? fallback;
4637
- return fallback;
4638
- };
4639
4921
  var buildTypedSelection = (columns, prefix, keys, missingMsg, tableOverride) => {
4640
4922
  return keys.reduce((acc, key) => {
4641
4923
  const def = columns[key];
@@ -4762,7 +5044,7 @@ var RelationService = class {
4762
5044
  table,
4763
5045
  (state2, hydration2, columns) => this.selectColumns(state2, hydration2, columns)
4764
5046
  );
4765
- this.joinPlanner = new RelationJoinPlanner(table, createQueryAstService);
5047
+ this.joinPlanner = new RelationJoinPlanner(table);
4766
5048
  this.cteBuilder = new RelationCteBuilder(table, createQueryAstService);
4767
5049
  }
4768
5050
  projectionHelper;
@@ -4812,7 +5094,7 @@ var RelationService = class {
4812
5094
  let hydration = this.hydration;
4813
5095
  const relation = this.getRelation(relationName);
4814
5096
  const aliasPrefix = options?.aliasPrefix ?? relationName;
4815
- const alreadyJoined = state.ast.joins.some((j) => getJoinRelationName(j) === relationName);
5097
+ const alreadyJoined = hasJoinForRelationKey(state.ast.joins, relationName);
4816
5098
  const { selfFilters, crossFilters } = splitFilterExpressions(
4817
5099
  options?.filter,
4818
5100
  /* @__PURE__ */ new Set([relation.target.name])
@@ -5130,6 +5412,34 @@ var cloneRelationIncludeTree = (tree) => {
5130
5412
  }
5131
5413
  return cloned;
5132
5414
  };
5415
+ var getIncludeNode = (tree, segments) => {
5416
+ let current = tree;
5417
+ let node;
5418
+ for (let i = 0; i < segments.length; i += 1) {
5419
+ if (!current) return { exists: false };
5420
+ node = current[segments[i]];
5421
+ if (!node) return { exists: false };
5422
+ if (i < segments.length - 1) {
5423
+ current = node.include;
5424
+ }
5425
+ }
5426
+ return { options: node?.options, exists: Boolean(node) };
5427
+ };
5428
+ var setIncludeOptions = (tree, segments, options) => {
5429
+ let current = tree;
5430
+ for (let i = 0; i < segments.length; i += 1) {
5431
+ const key = segments[i];
5432
+ const isLeaf = i === segments.length - 1;
5433
+ const existing = current[key] ?? {};
5434
+ if (isLeaf) {
5435
+ current[key] = { ...existing, options };
5436
+ return;
5437
+ }
5438
+ const nextInclude = existing.include ?? {};
5439
+ current[key] = { ...existing, include: nextInclude };
5440
+ current = nextInclude;
5441
+ }
5442
+ };
5133
5443
 
5134
5444
  // src/orm/hydration.ts
5135
5445
  var hydrateRows = (rows, plan) => {
@@ -7780,6 +8090,17 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
7780
8090
  getIncludeTree() {
7781
8091
  return cloneRelationIncludeTree(this.includeTree);
7782
8092
  }
8093
+ /**
8094
+ * Internal access to builder internals for advanced operations.
8095
+ * @internal
8096
+ */
8097
+ getInternals() {
8098
+ return {
8099
+ context: this.context,
8100
+ includeTree: this.includeTree,
8101
+ clone: (context, includeTree) => this.clone(context, void 0, void 0, includeTree)
8102
+ };
8103
+ }
7783
8104
  /**
7784
8105
  * Gets the table definition for this query builder
7785
8106
  * @returns Table definition
@@ -14772,6 +15093,87 @@ function createPooledExecutorFactory(opts) {
14772
15093
  };
14773
15094
  }
14774
15095
 
15096
+ // src/query-builder/relation-key.ts
15097
+ var buildRelationKey = (segments) => segments.join(RELATION_SEPARATOR);
15098
+
15099
+ // src/query-builder/update-include.ts
15100
+ var updateInclude = (qb, relationPath, updater) => {
15101
+ if (!relationPath || !relationPath.trim()) {
15102
+ return qb;
15103
+ }
15104
+ const segments = relationPath.split(".").filter(Boolean);
15105
+ if (segments.length === 0) {
15106
+ return qb;
15107
+ }
15108
+ const internals = qb.getInternals();
15109
+ const { context, includeTree } = internals;
15110
+ let state = context.state;
15111
+ const hydration = context.hydration;
15112
+ let currentTable = qb.getTable();
15113
+ let currentAlias = getExposedName(state.ast.from) ?? currentTable.name;
15114
+ const includeInfo = getIncludeNode(includeTree, segments);
15115
+ const existingOptions = includeInfo.options ?? {};
15116
+ const nextOptions = updater({ ...existingOptions });
15117
+ let nextIncludeTree = includeTree;
15118
+ const shouldCreateIncludePath = segments.length === 1 || includeInfo.exists;
15119
+ if (shouldCreateIncludePath) {
15120
+ nextIncludeTree = cloneRelationIncludeTree(includeTree);
15121
+ setIncludeOptions(nextIncludeTree, segments, nextOptions);
15122
+ }
15123
+ for (let i = 0; i < segments.length; i += 1) {
15124
+ const segment = segments[i];
15125
+ const relation = currentTable.relations[segment];
15126
+ if (!relation) {
15127
+ throw new Error(`Relation '${segment}' not found on table '${currentTable.name}'`);
15128
+ }
15129
+ const relationKey = buildRelationKey(segments.slice(0, i + 1));
15130
+ const joinIndex = findJoinIndexByRelationKey(state.ast.joins, relationKey);
15131
+ const isLeaf = i === segments.length - 1;
15132
+ if (isLeaf) {
15133
+ if (joinIndex === -1) {
15134
+ const joinKind = nextOptions.joinKind ?? JOIN_KINDS.LEFT;
15135
+ state = addRelationJoin({
15136
+ state,
15137
+ rootTable: currentTable,
15138
+ rootAlias: currentAlias,
15139
+ relationKey,
15140
+ relation,
15141
+ joinKind,
15142
+ filter: nextOptions.filter
15143
+ });
15144
+ } else {
15145
+ const joins = [...state.ast.joins];
15146
+ updateRelationJoin({
15147
+ joins,
15148
+ joinIndex,
15149
+ relation,
15150
+ currentTable,
15151
+ currentAlias,
15152
+ options: nextOptions
15153
+ });
15154
+ state = state.withJoins(joins);
15155
+ }
15156
+ } else if (joinIndex === -1) {
15157
+ const segmentOptions = getIncludeNode(includeTree, segments.slice(0, i + 1)).options;
15158
+ const joinKind = segmentOptions?.joinKind ?? JOIN_KINDS.LEFT;
15159
+ state = addRelationJoin({
15160
+ state,
15161
+ rootTable: currentTable,
15162
+ rootAlias: currentAlias,
15163
+ relationKey,
15164
+ relation,
15165
+ joinKind,
15166
+ filter: segmentOptions?.filter
15167
+ });
15168
+ }
15169
+ const joinForSegment = findJoinByRelationKey(state.ast.joins, relationKey);
15170
+ currentAlias = joinForSegment ? getExposedName(joinForSegment.table) ?? relation.target.name : relation.target.name;
15171
+ currentTable = relation.target;
15172
+ }
15173
+ const nextContext = { state, hydration };
15174
+ return internals.clone(nextContext, nextIncludeTree);
15175
+ };
15176
+
14775
15177
  // src/dto/apply-filter.ts
14776
15178
  function buildFieldExpression(column, filter) {
14777
15179
  const expressions = [];
@@ -14849,7 +15251,7 @@ function caseInsensitiveLike(column, pattern) {
14849
15251
  right: { type: "Literal", value: pattern.toLowerCase() }
14850
15252
  };
14851
15253
  }
14852
- function applyFilter(qb, tableOrEntity, where) {
15254
+ function applyFilter(qb, tableOrEntity, where, _options) {
14853
15255
  if (!where) {
14854
15256
  return qb;
14855
15257
  }
@@ -14863,12 +15265,15 @@ function applyFilter(qb, tableOrEntity, where) {
14863
15265
  continue;
14864
15266
  }
14865
15267
  const column = table.columns[fieldName];
14866
- if (!column) {
14867
- continue;
14868
- }
14869
- const expr = buildFieldExpression(column, fieldFilter);
14870
- if (expr) {
14871
- expressions.push(expr);
15268
+ if (column) {
15269
+ const expr = buildFieldExpression(column, fieldFilter);
15270
+ if (expr) {
15271
+ expressions.push(expr);
15272
+ }
15273
+ } else if (table.relations && fieldName in table.relations) {
15274
+ const relationFilter = fieldFilter;
15275
+ const relationName = fieldName;
15276
+ qb = applyRelationFilter(qb, table, relationName, relationFilter);
14872
15277
  }
14873
15278
  }
14874
15279
  if (expressions.length === 0) {
@@ -14879,6 +15284,199 @@ function applyFilter(qb, tableOrEntity, where) {
14879
15284
  }
14880
15285
  return qb.where(and(...expressions));
14881
15286
  }
15287
+ function applyRelationFilter(qb, table, relationName, filter) {
15288
+ const relation = table.relations[relationName];
15289
+ if (!relation) {
15290
+ return qb;
15291
+ }
15292
+ if (filter.some) {
15293
+ const predicate = buildFilterExpression(relation.target, filter.some);
15294
+ if (predicate) {
15295
+ qb = updateInclude(qb, relationName, (opts) => ({
15296
+ ...opts,
15297
+ joinKind: JOIN_KINDS.INNER,
15298
+ filter: opts.filter ? and(opts.filter, predicate) : predicate
15299
+ }));
15300
+ const pk = findPrimaryKey(table);
15301
+ const pkColumn = table.columns[pk];
15302
+ qb = pkColumn ? qb.distinct(pkColumn) : qb.distinct({ type: "Column", table: table.name, name: pk });
15303
+ }
15304
+ }
15305
+ if (filter.none) {
15306
+ const predicate = buildFilterExpression(relation.target, filter.none);
15307
+ if (predicate) {
15308
+ qb = qb.whereHasNot(relationName, (subQb) => subQb.where(predicate));
15309
+ }
15310
+ }
15311
+ if (filter.every) {
15312
+ const predicate = buildFilterExpression(relation.target, filter.every);
15313
+ if (predicate) {
15314
+ const pk = findPrimaryKey(table);
15315
+ qb = qb.joinRelation(relationName);
15316
+ qb = qb.groupBy({ type: "Column", table: table.name, name: pk });
15317
+ qb = qb.having(buildEveryHavingClause(relationName, predicate, relation.target));
15318
+ }
15319
+ }
15320
+ if (filter.isEmpty !== void 0) {
15321
+ if (filter.isEmpty) {
15322
+ qb = qb.whereHasNot(relationName);
15323
+ } else {
15324
+ qb = qb.whereHas(relationName);
15325
+ }
15326
+ }
15327
+ if (filter.isNotEmpty !== void 0) {
15328
+ if (filter.isNotEmpty) {
15329
+ qb = qb.whereHas(relationName);
15330
+ } else {
15331
+ qb = qb.whereHasNot(relationName);
15332
+ }
15333
+ }
15334
+ return qb;
15335
+ }
15336
+ var buildRelationSubqueryBase = (table, relation) => {
15337
+ const target = relation.target;
15338
+ if (relation.type === RelationKinds.BelongsToMany) {
15339
+ const many = relation;
15340
+ const localKey = many.localKey || findPrimaryKey(table);
15341
+ const targetKey = many.targetKey || findPrimaryKey(target);
15342
+ const pivot = many.pivotTable;
15343
+ const from2 = {
15344
+ type: "Table",
15345
+ name: pivot.name,
15346
+ schema: pivot.schema
15347
+ };
15348
+ const joins = [
15349
+ createJoinNode(
15350
+ JOIN_KINDS.INNER,
15351
+ { type: "Table", name: target.name, schema: target.schema },
15352
+ eq(
15353
+ { type: "Column", table: target.name, name: targetKey },
15354
+ { type: "Column", table: pivot.name, name: many.pivotForeignKeyToTarget }
15355
+ )
15356
+ )
15357
+ ];
15358
+ const correlation2 = eq(
15359
+ { type: "Column", table: pivot.name, name: many.pivotForeignKeyToRoot },
15360
+ { type: "Column", table: table.name, name: localKey }
15361
+ );
15362
+ const groupByColumn = {
15363
+ type: "Column",
15364
+ table: pivot.name,
15365
+ name: many.pivotForeignKeyToRoot
15366
+ };
15367
+ return {
15368
+ from: from2,
15369
+ joins,
15370
+ correlation: correlation2,
15371
+ groupByColumn,
15372
+ targetTable: target,
15373
+ targetTableName: target.name
15374
+ };
15375
+ }
15376
+ const from = {
15377
+ type: "Table",
15378
+ name: target.name,
15379
+ schema: target.schema
15380
+ };
15381
+ const correlation = buildRelationCorrelation(table, relation);
15382
+ const groupByColumnName = relation.type === RelationKinds.BelongsTo ? relation.localKey || findPrimaryKey(target) : relation.foreignKey;
15383
+ return {
15384
+ from,
15385
+ joins: [],
15386
+ correlation,
15387
+ groupByColumn: { type: "Column", table: target.name, name: groupByColumnName },
15388
+ targetTable: target,
15389
+ targetTableName: target.name
15390
+ };
15391
+ };
15392
+ function buildRelationFilterExpression(table, relationName, filter) {
15393
+ const relation = table.relations[relationName];
15394
+ if (!relation) {
15395
+ return null;
15396
+ }
15397
+ const expressions = [];
15398
+ const subqueryBase = buildRelationSubqueryBase(table, relation);
15399
+ const buildSubquery = (predicate) => {
15400
+ const where = predicate ? and(subqueryBase.correlation, predicate) : subqueryBase.correlation;
15401
+ return {
15402
+ type: "SelectQuery",
15403
+ from: subqueryBase.from,
15404
+ columns: [],
15405
+ joins: subqueryBase.joins,
15406
+ where
15407
+ };
15408
+ };
15409
+ if (filter.some) {
15410
+ const predicate = buildFilterExpression(relation.target, filter.some);
15411
+ if (predicate) {
15412
+ expressions.push(exists(buildSubquery(predicate)));
15413
+ }
15414
+ }
15415
+ if (filter.none) {
15416
+ const predicate = buildFilterExpression(relation.target, filter.none);
15417
+ if (predicate) {
15418
+ expressions.push(notExists(buildSubquery(predicate)));
15419
+ }
15420
+ }
15421
+ if (filter.every) {
15422
+ const predicate = buildFilterExpression(relation.target, filter.every);
15423
+ if (predicate) {
15424
+ const subquery = {
15425
+ type: "SelectQuery",
15426
+ from: subqueryBase.from,
15427
+ columns: [],
15428
+ joins: subqueryBase.joins,
15429
+ where: subqueryBase.correlation,
15430
+ groupBy: [subqueryBase.groupByColumn],
15431
+ having: buildEveryHavingClause(subqueryBase.targetTableName, predicate, subqueryBase.targetTable)
15432
+ };
15433
+ expressions.push(exists(subquery));
15434
+ }
15435
+ }
15436
+ if (filter.isEmpty !== void 0) {
15437
+ const subquery = buildSubquery();
15438
+ expressions.push(filter.isEmpty ? notExists(subquery) : exists(subquery));
15439
+ }
15440
+ if (filter.isNotEmpty !== void 0) {
15441
+ const subquery = buildSubquery();
15442
+ expressions.push(filter.isNotEmpty ? exists(subquery) : notExists(subquery));
15443
+ }
15444
+ if (expressions.length === 0) {
15445
+ return null;
15446
+ }
15447
+ if (expressions.length === 1) {
15448
+ return expressions[0];
15449
+ }
15450
+ return and(...expressions);
15451
+ }
15452
+ function buildEveryHavingClause(relationName, predicate, targetTable) {
15453
+ const pk = findPrimaryKey(targetTable);
15454
+ const whenClause = {
15455
+ when: predicate,
15456
+ then: { type: "Literal", value: 1 }
15457
+ };
15458
+ const caseExpr = {
15459
+ type: "CaseExpression",
15460
+ conditions: [whenClause],
15461
+ else: { type: "Literal", value: null }
15462
+ };
15463
+ const countMatching = {
15464
+ type: "Function",
15465
+ name: "COUNT",
15466
+ args: [caseExpr]
15467
+ };
15468
+ const countAll2 = {
15469
+ type: "Function",
15470
+ name: "COUNT",
15471
+ args: [{ type: "Column", table: relationName, name: pk }]
15472
+ };
15473
+ return {
15474
+ type: "BinaryExpression",
15475
+ operator: "=",
15476
+ left: countAll2,
15477
+ right: countMatching
15478
+ };
15479
+ }
14882
15480
  function isEntityConstructor(value) {
14883
15481
  return typeof value === "function" && value.prototype?.constructor === value;
14884
15482
  }
@@ -14896,12 +15494,20 @@ function buildFilterExpression(tableOrEntity, where) {
14896
15494
  continue;
14897
15495
  }
14898
15496
  const column = table.columns[fieldName];
14899
- if (!column) {
14900
- continue;
14901
- }
14902
- const expr = buildFieldExpression(column, fieldFilter);
14903
- if (expr) {
14904
- expressions.push(expr);
15497
+ if (column) {
15498
+ const expr = buildFieldExpression(column, fieldFilter);
15499
+ if (expr) {
15500
+ expressions.push(expr);
15501
+ }
15502
+ } else if (table.relations && fieldName in table.relations) {
15503
+ const relationExpr = buildRelationFilterExpression(
15504
+ table,
15505
+ fieldName,
15506
+ fieldFilter
15507
+ );
15508
+ if (relationExpr) {
15509
+ expressions.push(relationExpr);
15510
+ }
14905
15511
  }
14906
15512
  }
14907
15513
  if (expressions.length === 0) {