linkgress-orm 0.2.6 → 0.2.7

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.
@@ -1847,6 +1847,9 @@ class SelectQueryBuilder {
1847
1847
  if (!queryBuilder.whereCond) {
1848
1848
  throw new Error('Update requires a WHERE condition. Use where() before update().');
1849
1849
  }
1850
+ // Detect navigation property joins from WHERE condition
1851
+ const whereJoins = [];
1852
+ queryBuilder.detectAndAddJoinsFromCondition(queryBuilder.whereCond, whereJoins);
1850
1853
  // Build SET clause
1851
1854
  const setClauses = [];
1852
1855
  const values = [];
@@ -1870,13 +1873,40 @@ class SelectQueryBuilder {
1870
1873
  const { sql: whereSql, params: whereParams } = condBuilder.build(queryBuilder.whereCond, paramIndex);
1871
1874
  values.push(...whereParams);
1872
1875
  const qualifiedTableName = queryBuilder.getQualifiedTableName(queryBuilder.schema.name, queryBuilder.schema.schema);
1876
+ // Build FROM clause for navigation properties (PostgreSQL syntax for UPDATE with JOINs)
1877
+ let fromClause = '';
1878
+ let joinConditions = [];
1879
+ for (const join of whereJoins) {
1880
+ const sourceTable = join.sourceAlias || queryBuilder.schema.name;
1881
+ const joinTableName = queryBuilder.getQualifiedTableName(join.targetTable, join.targetSchema);
1882
+ if (fromClause) {
1883
+ fromClause += `, ${joinTableName} AS "${join.alias}"`;
1884
+ }
1885
+ else {
1886
+ fromClause = `FROM ${joinTableName} AS "${join.alias}"`;
1887
+ }
1888
+ // Build ON conditions as part of WHERE clause
1889
+ for (let i = 0; i < join.foreignKeys.length; i++) {
1890
+ const fk = join.foreignKeys[i];
1891
+ const match = join.matches[i];
1892
+ joinConditions.push(`"${sourceTable}"."${fk}" = "${join.alias}"."${match}"`);
1893
+ }
1894
+ }
1895
+ // Combine join conditions with the original WHERE clause
1896
+ const fullWhereClause = joinConditions.length > 0
1897
+ ? `${joinConditions.join(' AND ')} AND ${whereSql}`
1898
+ : whereSql;
1873
1899
  // Check if RETURNING uses navigation properties
1874
1900
  const navigationInfo = returning && returning !== 'count' && returning !== true
1875
1901
  ? queryBuilder.detectNavigationInReturning(returning)
1876
1902
  : null;
1877
1903
  if (navigationInfo) {
1878
1904
  // Use CTE-based approach for navigation properties in RETURNING
1879
- const updateSql = `UPDATE ${qualifiedTableName} SET ${setClauses.join(', ')} WHERE ${whereSql}`;
1905
+ let updateSql = `UPDATE ${qualifiedTableName} SET ${setClauses.join(', ')}`;
1906
+ if (fromClause) {
1907
+ updateSql += ` ${fromClause}`;
1908
+ }
1909
+ updateSql += ` WHERE ${fullWhereClause}`;
1880
1910
  const { sql, params } = queryBuilder.buildReturningWithNavigation(updateSql, values, returning, navigationInfo);
1881
1911
  const result = queryBuilder.executor
1882
1912
  ? await queryBuilder.executor.query(sql, params)
@@ -1884,10 +1914,16 @@ class SelectQueryBuilder {
1884
1914
  return queryBuilder.mapReturningResultsWithNavigation(result.rows, returning, navigationInfo.navigationFields);
1885
1915
  }
1886
1916
  // Standard RETURNING (no navigation properties)
1917
+ // Qualify columns with table name when using FROM clause to avoid ambiguity
1918
+ const hasJoins = whereJoins.length > 0;
1887
1919
  const returningClause = returning !== 'count'
1888
- ? queryBuilder.buildUpdateDeleteReturningClause(returning)
1920
+ ? queryBuilder.buildUpdateDeleteReturningClause(returning, hasJoins)
1889
1921
  : undefined;
1890
- let sql = `UPDATE ${qualifiedTableName} SET ${setClauses.join(', ')} WHERE ${whereSql}`;
1922
+ let sql = `UPDATE ${qualifiedTableName} SET ${setClauses.join(', ')}`;
1923
+ if (fromClause) {
1924
+ sql += ` ${fromClause}`;
1925
+ }
1926
+ sql += ` WHERE ${fullWhereClause}`;
1891
1927
  if (returningClause) {
1892
1928
  sql += ` RETURNING ${returningClause.sql}`;
1893
1929
  }
@@ -2060,6 +2096,21 @@ class SelectQueryBuilder {
2060
2096
  const mockRow = this._createMockRow();
2061
2097
  const selectedMock = this.selector(mockRow);
2062
2098
  const selection = returning(selectedMock);
2099
+ // Helper to get FK db column name from a schema
2100
+ const getFkDbColumnName = (sourceSchema, fkPropName) => {
2101
+ const colEntry = Object.entries(sourceSchema.columns).find(([propName, _]) => propName === fkPropName);
2102
+ if (colEntry) {
2103
+ const config = colEntry[1].build();
2104
+ return config.name;
2105
+ }
2106
+ return fkPropName; // Fallback to property name
2107
+ };
2108
+ // Build a map of alias -> source table name for FK lookups
2109
+ const aliasToSourceTable = new Map();
2110
+ aliasToSourceTable.set(this.schema.name, this.schema.name);
2111
+ for (const join of navigationInfo.joins) {
2112
+ aliasToSourceTable.set(join.alias, join.targetTable);
2113
+ }
2063
2114
  // For each selected field, determine if it's from main table or navigation
2064
2115
  for (const [alias, field] of Object.entries(selection)) {
2065
2116
  if (field && typeof field === 'object' && '__dbColumnName' in field) {
@@ -2076,31 +2127,13 @@ class SelectQueryBuilder {
2076
2127
  }
2077
2128
  }
2078
2129
  }
2079
- // We also need to include foreign keys for joins that aren't already in the selection
2130
+ // Include foreign keys needed for joins - only for joins from main table
2080
2131
  for (const join of navigationInfo.joins) {
2081
- for (const fk of join.foreignKeys) {
2082
- // Find the db column name for this foreign key
2083
- const colEntry = Object.entries(this.schema.columns).find(([propName, _]) => propName === fk);
2084
- if (colEntry) {
2085
- const config = colEntry[1].build();
2086
- const dbColName = config.name;
2087
- mainTableColumns.add(dbColName);
2088
- }
2089
- else {
2090
- // FK might be the db column name directly
2091
- mainTableColumns.add(fk);
2092
- }
2093
- }
2094
- }
2095
- // Also include foreign keys from intermediate joins (join to another join)
2096
- for (const join of navigationInfo.joins) {
2097
- if (join.sourceAlias && join.sourceAlias !== this.schema.name) {
2098
- // This join comes from another join, we need to include those FK columns too
2099
- // Find the source join
2100
- const sourceJoin = navigationInfo.joins.find(j => j.alias === join.sourceAlias);
2101
- if (sourceJoin) {
2102
- // We need the source table's FK columns in our RETURNING if the source is the main table
2103
- // But if source is itself a navigation, we handle it in the outer SELECT
2132
+ // Only add FK to mainTableColumns if the join source is the main table
2133
+ if (join.sourceAlias === this.schema.name || !join.sourceAlias) {
2134
+ for (const fk of join.foreignKeys) {
2135
+ const fkDbCol = getFkDbColumnName(this.schema, fk);
2136
+ mainTableColumns.add(fkDbCol);
2104
2137
  }
2105
2138
  }
2106
2139
  }
@@ -2111,27 +2144,23 @@ class SelectQueryBuilder {
2111
2144
  // Build the JOINs for the outer SELECT
2112
2145
  const joinClauses = [];
2113
2146
  for (const join of navigationInfo.joins) {
2114
- const sourceAlias = join.sourceAlias === this.schema.name ? '__mutation__' : `"${join.sourceAlias}"`;
2115
2147
  const qualifiedJoinTable = this.getQualifiedTableName(join.targetTable, join.targetSchema);
2116
2148
  // Find the db column names for the foreign keys
2117
2149
  const joinConditions = [];
2118
2150
  for (let i = 0; i < join.foreignKeys.length; i++) {
2119
2151
  const fk = join.foreignKeys[i];
2120
2152
  const match = join.matches[i] || 'id';
2121
- // Convert property name to db column name for FK
2122
- let fkDbCol = fk;
2123
- const colEntry = Object.entries(this.schema.columns).find(([propName, _]) => propName === fk);
2124
- if (colEntry) {
2125
- const config = colEntry[1].build();
2126
- fkDbCol = config.name;
2127
- }
2128
- // For source that's the mutation CTE
2129
2153
  if (join.sourceAlias === this.schema.name || !join.sourceAlias) {
2154
+ // FK is on main table - look up db column name from main schema
2155
+ const fkDbCol = getFkDbColumnName(this.schema, fk);
2130
2156
  joinConditions.push(`"__mutation__"."${fkDbCol}" = "${join.alias}"."${match}"`);
2131
2157
  }
2132
2158
  else {
2133
- // Source is another joined table
2134
- joinConditions.push(`"${join.sourceAlias}"."${fk}" = "${join.alias}"."${match}"`);
2159
+ // FK is on an intermediate joined table - look up from its schema
2160
+ const sourceTableName = aliasToSourceTable.get(join.sourceAlias);
2161
+ const sourceSchema = sourceTableName && this.schemaRegistry ? this.schemaRegistry.get(sourceTableName) : undefined;
2162
+ const fkDbCol = sourceSchema ? getFkDbColumnName(sourceSchema, fk) : fk;
2163
+ joinConditions.push(`"${join.sourceAlias}"."${fkDbCol}" = "${join.alias}"."${match}"`);
2135
2164
  }
2136
2165
  }
2137
2166
  const joinType = join.isMandatory ? 'INNER JOIN' : 'LEFT JOIN';