linkgress-orm 0.4.58 → 0.4.60

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.
@@ -6006,12 +6006,22 @@ class CollectionQueryBuilder {
6006
6006
  const sourceTable = this.sourceTable;
6007
6007
  // Build JOINs needed inside the EXISTS subquery
6008
6008
  const allJoins = [];
6009
+ // Inside a lateral, the collection's λ-root table is visible only under the
6010
+ // lateral's generated alias (e.g. `FROM "cart_discount_code" "lateral_3_cartDiscountCodes"`).
6011
+ // The first bridge hop / root correlation must anchor on that alias, not the raw
6012
+ // table name — mirrors lateral-collection-strategy's own correlation handling.
6013
+ // Only the λ-root is translated: bridge-internal aliases are emitted by this very
6014
+ // subquery and must never be remapped, even if they collide with a lateral's
6015
+ // target table name.
6016
+ const aliasMap = context.lateralTableAliasMap;
6017
+ const rootAnchor = (alias) => aliasMap?.get(alias) || alias;
6009
6018
  // Navigation path joins (for reference navigation like pc.order → orders)
6010
- for (const nav of this.navigationPath) {
6019
+ for (const [index, nav] of this.navigationPath.entries()) {
6011
6020
  const joinType = nav.isMandatory ? 'JOIN' : 'LEFT JOIN';
6012
6021
  const fk = nav.foreignKeys[0];
6013
6022
  const pk = (nav.matches && nav.matches.length > 0) ? nav.matches[0] : 'id';
6014
- allJoins.push(`${joinType} "${nav.targetTable}" "${nav.alias}" ON "${nav.sourceAlias}"."${fk}" = "${nav.alias}"."${pk}"`);
6023
+ const src = index === 0 ? rootAnchor(nav.sourceAlias) : nav.sourceAlias;
6024
+ allJoins.push(`${joinType} "${nav.targetTable}" "${nav.alias}" ON "${src}"."${fk}" = "${nav.alias}"."${pk}"`);
6015
6025
  }
6016
6026
  // SelectMany joins (for selectMany navigation through intermediate tables)
6017
6027
  for (const nav of this.selectManyJoins) {
@@ -6020,18 +6030,62 @@ class CollectionQueryBuilder {
6020
6030
  const pk = (nav.matches && nav.matches.length > 0) ? nav.matches[0] : 'id';
6021
6031
  allJoins.push(`${joinType} "${nav.targetTable}" "${nav.alias}" ON "${nav.sourceAlias}"."${fk}" = "${nav.alias}"."${pk}"`);
6022
6032
  }
6033
+ // Joins required by REFERENCE navigations inside the collection's own
6034
+ // where-condition, e.g. `exists(links.where(l => eq(l.campaign.isPublic, true)))`.
6035
+ // Navigation joins were historically derived from selectors only; an EXISTS
6036
+ // aggregation has no selector, so a where-navigated alias rendered unjoined and
6037
+ // the statement failed with `missing FROM-clause entry for table "<alias>"`.
6038
+ // Reuses the selector path's machinery: seed aliases + chains from the
6039
+ // condition's FieldRefs, then resolve multi-hop paths against the target schema.
6040
+ if (this.whereCond) {
6041
+ const joinedAliases = new Set([
6042
+ ...this.navigationPath.map(nav => nav.alias),
6043
+ ...this.selectManyJoins.map(nav => nav.alias),
6044
+ ]);
6045
+ const targetSchema = this.schemaRegistry?.get(targetTable);
6046
+ if (targetSchema) {
6047
+ const whereJoins = [];
6048
+ const whereAliases = new Set();
6049
+ for (const ref of this.whereCond.getFieldRefs()) {
6050
+ const refAlias = ref?.__tableAlias;
6051
+ // Direct columns of the target arrive under the `__collection_<table>__`
6052
+ // marker (rewritten below) or the bare table name — neither needs a join.
6053
+ if (!refAlias || refAlias === targetTable || refAlias.startsWith('__collection_') || joinedAliases.has(refAlias)) {
6054
+ continue;
6055
+ }
6056
+ this.addNavigationJoinForFieldRef(ref, whereJoins, targetTable, targetSchema, whereAliases);
6057
+ }
6058
+ if (whereAliases.size > 0) {
6059
+ this.resolveNavigationJoins(whereAliases, whereJoins, targetSchema);
6060
+ }
6061
+ for (const nav of whereJoins) {
6062
+ if (joinedAliases.has(nav.alias)) {
6063
+ continue;
6064
+ }
6065
+ joinedAliases.add(nav.alias);
6066
+ const joinType = nav.isMandatory ? 'JOIN' : 'LEFT JOIN';
6067
+ const fk = nav.foreignKeys[0];
6068
+ const pk = (nav.matches && nav.matches.length > 0) ? nav.matches[0] : 'id';
6069
+ allJoins.push(`${joinType} "${nav.targetTable}" "${nav.alias}" ON "${nav.sourceAlias}"."${fk}" = "${nav.alias}"."${pk}"`);
6070
+ }
6071
+ }
6072
+ }
6023
6073
  const navJoinsSQL = allJoins.join('\n');
6024
6074
  // Build WHERE clause: correlation + additional conditions
6025
6075
  // Use full composite-FK / literal-predicate form when navigation declared
6026
6076
  // multiple key pairs (e.g. SCD2 `[productId, isCurrent] / [id, true]`).
6027
6077
  const fkTableAlias = this.foreignKeyTableAlias || targetTable;
6078
+ // Root-attached collections correlate on the λ-root itself, which a lateral
6079
+ // exposes only under its generated alias; nav-derived collections correlate on
6080
+ // the bridge's terminal alias, which this subquery emits itself — never remap it.
6081
+ const correlationSource = this.navigationPath.length === 0 ? rootAnchor(sourceTable) : sourceTable;
6028
6082
  let whereSQL;
6029
6083
  if (this.foreignKeys && this.foreignKeys.length > 0) {
6030
6084
  const matches = this.matches && this.matches.length > 0 ? this.matches : ['id'];
6031
- whereSQL = (0, join_utils_1.buildCollectionCorrelationWhere)(fkTableAlias, sourceTable, this.foreignKeys, matches);
6085
+ whereSQL = (0, join_utils_1.buildCollectionCorrelationWhere)(fkTableAlias, correlationSource, this.foreignKeys, matches);
6032
6086
  }
6033
6087
  else {
6034
- whereSQL = `"${fkTableAlias}"."${foreignKey}" = "${sourceTable}"."id"`;
6088
+ whereSQL = `"${fkTableAlias}"."${foreignKey}" = "${correlationSource}"."id"`;
6035
6089
  }
6036
6090
  if (this.whereCond) {
6037
6091
  const condBuilder = new conditions_1.ConditionBuilder();
@@ -6369,6 +6423,10 @@ class CollectionQueryBuilder {
6369
6423
  paramCounter: context.paramCounter,
6370
6424
  params: context.allParams,
6371
6425
  placeholders: context.placeholders, // Pass placeholders for prepared statements
6426
+ // Thread the lateral alias scope into fragments: an EXISTS collection
6427
+ // rendered inside this lateral must anchor its λ-root on the lateral's
6428
+ // generated alias (see CollectionQueryBuilder.buildSql rootAnchor).
6429
+ lateralTableAliasMap: context.lateralTableAliasMap,
6372
6430
  };
6373
6431
  const fragmentSql = field.buildSql(sqlBuildContext);
6374
6432
  context.paramCounter = sqlBuildContext.paramCounter;