linkgress-orm 0.2.6 → 0.2.8

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.
@@ -1768,11 +1768,11 @@ class SelectQueryBuilder {
1768
1768
  deleteSql += ` ${usingClause}`;
1769
1769
  }
1770
1770
  deleteSql += ` WHERE ${fullWhereClause}`;
1771
- const { sql, params } = queryBuilder.buildReturningWithNavigation(deleteSql, whereParams, returning, navigationInfo);
1771
+ const { sql, params, nestedPaths } = queryBuilder.buildReturningWithNavigation(deleteSql, whereParams, returning, navigationInfo);
1772
1772
  const result = queryBuilder.executor
1773
1773
  ? await queryBuilder.executor.query(sql, params)
1774
1774
  : await queryBuilder.client.query(sql, params);
1775
- return queryBuilder.mapReturningResultsWithNavigation(result.rows, returning, navigationInfo.navigationFields);
1775
+ return queryBuilder.mapReturningResultsWithNavigation(result.rows, returning, navigationInfo.navigationFields, nestedPaths);
1776
1776
  }
1777
1777
  // Standard RETURNING (no navigation properties)
1778
1778
  // Qualify columns with table name when using USING clause to avoid ambiguity
@@ -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,24 +1873,57 @@ 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}`;
1880
- const { sql, params } = queryBuilder.buildReturningWithNavigation(updateSql, values, returning, navigationInfo);
1905
+ let updateSql = `UPDATE ${qualifiedTableName} SET ${setClauses.join(', ')}`;
1906
+ if (fromClause) {
1907
+ updateSql += ` ${fromClause}`;
1908
+ }
1909
+ updateSql += ` WHERE ${fullWhereClause}`;
1910
+ const { sql, params, nestedPaths } = queryBuilder.buildReturningWithNavigation(updateSql, values, returning, navigationInfo);
1881
1911
  const result = queryBuilder.executor
1882
1912
  ? await queryBuilder.executor.query(sql, params)
1883
1913
  : await queryBuilder.client.query(sql, params);
1884
- return queryBuilder.mapReturningResultsWithNavigation(result.rows, returning, navigationInfo.navigationFields);
1914
+ return queryBuilder.mapReturningResultsWithNavigation(result.rows, returning, navigationInfo.navigationFields, nestedPaths);
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
  }
@@ -2018,35 +2054,54 @@ class SelectQueryBuilder {
2018
2054
  }
2019
2055
  const navigationFields = new Map();
2020
2056
  const allTableAliases = new Set();
2021
- // Check each field in the selection for navigation properties
2022
- for (const [alias, field] of Object.entries(selection)) {
2023
- if (field && typeof field === 'object' && '__dbColumnName' in field && '__tableAlias' in field) {
2024
- const tableAlias = field.__tableAlias;
2025
- if (tableAlias && tableAlias !== this.schema.name) {
2026
- allTableAliases.add(tableAlias);
2027
- navigationFields.set(alias, {
2028
- tableAlias,
2029
- dbColumnName: field.__dbColumnName,
2030
- schemaTable: field.__sourceTable,
2031
- });
2032
- }
2033
- // Also collect intermediate navigation aliases for multi-level navigation
2034
- if ('__navigationAliases' in field && Array.isArray(field.__navigationAliases)) {
2035
- for (const navAlias of field.__navigationAliases) {
2036
- if (navAlias && navAlias !== this.schema.name) {
2037
- allTableAliases.add(navAlias);
2057
+ const nestedObjects = new Map();
2058
+ const collectionFields = new Map();
2059
+ // Recursively collect field refs and table aliases from selection
2060
+ const collectFieldRefs = (obj, path = '') => {
2061
+ for (const [key, field] of Object.entries(obj)) {
2062
+ const fieldPath = path ? `${path}.${key}` : key;
2063
+ if (field && typeof field === 'object') {
2064
+ if ('__dbColumnName' in field) {
2065
+ // Direct field reference (either main table or navigation)
2066
+ const tableAlias = field.__tableAlias;
2067
+ if (tableAlias && tableAlias !== this.schema.name) {
2068
+ // Navigation field
2069
+ allTableAliases.add(tableAlias);
2070
+ navigationFields.set(fieldPath, {
2071
+ tableAlias,
2072
+ dbColumnName: field.__dbColumnName,
2073
+ schemaTable: field.__sourceTable,
2074
+ });
2075
+ }
2076
+ // Also collect intermediate navigation aliases for multi-level navigation
2077
+ if ('__navigationAliases' in field && Array.isArray(field.__navigationAliases)) {
2078
+ for (const navAlias of field.__navigationAliases) {
2079
+ if (navAlias && navAlias !== this.schema.name) {
2080
+ allTableAliases.add(navAlias);
2081
+ }
2082
+ }
2038
2083
  }
2039
2084
  }
2085
+ else if (field instanceof CollectionQueryBuilder) {
2086
+ // CollectionQueryBuilder (.toList(), .firstOrDefault())
2087
+ collectionFields.set(fieldPath, field);
2088
+ }
2089
+ else if (!Array.isArray(field)) {
2090
+ // Nested plain object - recurse into it
2091
+ nestedObjects.set(fieldPath, field);
2092
+ collectFieldRefs(field, fieldPath);
2093
+ }
2040
2094
  }
2041
2095
  }
2042
- }
2043
- if (navigationFields.size === 0) {
2096
+ };
2097
+ collectFieldRefs(selection);
2098
+ if (navigationFields.size === 0 && nestedObjects.size === 0 && collectionFields.size === 0) {
2044
2099
  return null;
2045
2100
  }
2046
2101
  // Resolve navigation joins
2047
2102
  const joins = [];
2048
2103
  this.resolveJoinsForTableAliases(allTableAliases, joins);
2049
- return { hasNavigation: true, selection, joins, navigationFields };
2104
+ return { hasNavigation: true, selection, joins, navigationFields, nestedObjects, collectionFields };
2050
2105
  }
2051
2106
  /**
2052
2107
  * Build RETURNING clause with navigation property support using CTE
@@ -2057,51 +2112,115 @@ class SelectQueryBuilder {
2057
2112
  // First, collect all columns needed from the main table in the mutation's RETURNING
2058
2113
  const mainTableColumns = new Set();
2059
2114
  const selectParts = [];
2115
+ const nestedPaths = new Set();
2060
2116
  const mockRow = this._createMockRow();
2061
2117
  const selectedMock = this.selector(mockRow);
2062
2118
  const selection = returning(selectedMock);
2063
- // For each selected field, determine if it's from main table or navigation
2064
- for (const [alias, field] of Object.entries(selection)) {
2065
- if (field && typeof field === 'object' && '__dbColumnName' in field) {
2066
- const tableAlias = field.__tableAlias;
2067
- const dbColumnName = field.__dbColumnName;
2068
- if (!tableAlias || tableAlias === this.schema.name) {
2069
- // Main table column - add to CTE RETURNING and final SELECT
2070
- mainTableColumns.add(dbColumnName);
2071
- selectParts.push(`"__mutation__"."${dbColumnName}" AS "${alias}"`);
2072
- }
2073
- else {
2074
- // Navigation column - will be joined in outer query
2075
- selectParts.push(`"${tableAlias}"."${dbColumnName}" AS "${alias}"`);
2119
+ // Helper to get FK db column name from a schema
2120
+ const getFkDbColumnName = (sourceSchema, fkPropName) => {
2121
+ const colEntry = Object.entries(sourceSchema.columns).find(([propName, _]) => propName === fkPropName);
2122
+ if (colEntry) {
2123
+ const config = colEntry[1].build();
2124
+ return config.name;
2125
+ }
2126
+ return fkPropName; // Fallback to property name
2127
+ };
2128
+ // Build a map of alias -> source table name for FK lookups
2129
+ const aliasToSourceTable = new Map();
2130
+ aliasToSourceTable.set(this.schema.name, this.schema.name);
2131
+ for (const join of navigationInfo.joins) {
2132
+ aliasToSourceTable.set(join.alias, join.targetTable);
2133
+ }
2134
+ // Track collection subqueries for LATERAL JOINs
2135
+ const collectionSubqueries = [];
2136
+ let lateralCounter = 0;
2137
+ // Track all params including those from collection subqueries
2138
+ const allParams = [...mutationParams];
2139
+ let currentParamCounter = mutationParams.length + 1;
2140
+ // Build a QueryContext for collection subquery building
2141
+ const buildCollectionContext = () => ({
2142
+ ctes: new Map(),
2143
+ cteCounter: lateralCounter,
2144
+ paramCounter: currentParamCounter,
2145
+ allParams: allParams,
2146
+ collectionStrategy: 'lateral',
2147
+ });
2148
+ // Recursively process selection to build SELECT parts
2149
+ const processSelection = (obj, path = '') => {
2150
+ for (const [key, field] of Object.entries(obj)) {
2151
+ const fieldPath = path ? `${path}.${key}` : key;
2152
+ if (field && typeof field === 'object') {
2153
+ if ('__dbColumnName' in field) {
2154
+ // Direct field reference
2155
+ const tableAlias = field.__tableAlias;
2156
+ const dbColumnName = field.__dbColumnName;
2157
+ if (!tableAlias || tableAlias === this.schema.name) {
2158
+ mainTableColumns.add(dbColumnName);
2159
+ selectParts.push(`"__mutation__"."${dbColumnName}" AS "${fieldPath}"`);
2160
+ }
2161
+ else {
2162
+ selectParts.push(`"${tableAlias}"."${dbColumnName}" AS "${fieldPath}"`);
2163
+ }
2164
+ }
2165
+ else if (field instanceof CollectionQueryBuilder) {
2166
+ // CollectionQueryBuilder (.toList(), .firstOrDefault())
2167
+ // Build a correlated subquery that references __mutation__ instead of the source table
2168
+ const collectionBuilder = field;
2169
+ const context = buildCollectionContext();
2170
+ // Build the CTE/subquery using lateral strategy
2171
+ const cteResult = collectionBuilder.buildCTE(context);
2172
+ lateralCounter = context.cteCounter;
2173
+ currentParamCounter = context.paramCounter; // Track new param index after collection subquery
2174
+ // The lateral strategy returns either:
2175
+ // 1. A correlated subquery in selectExpression (no join needed)
2176
+ // 2. A LATERAL JOIN with joinClause and selectExpression
2177
+ if (cteResult.joinClause && cteResult.joinClause.trim()) {
2178
+ // LATERAL JOIN needed - rewrite to reference __mutation__ instead of source table
2179
+ const rewrittenJoinClause = this.rewriteCollectionJoinForMutation(cteResult.joinClause, this.schema.name, '__mutation__');
2180
+ collectionSubqueries.push({
2181
+ fieldPath,
2182
+ lateralAlias: cteResult.tableName || `lateral_${lateralCounter - 1}`,
2183
+ joinClause: rewrittenJoinClause,
2184
+ selectExpression: cteResult.selectExpression || `"${cteResult.tableName}".data`,
2185
+ });
2186
+ selectParts.push(`${cteResult.selectExpression || `"${cteResult.tableName}".data`} AS "${fieldPath}"`);
2187
+ }
2188
+ else if (cteResult.selectExpression) {
2189
+ // Correlated subquery in SELECT - rewrite source table references
2190
+ const rewrittenExpr = this.rewriteCollectionExprForMutation(cteResult.selectExpression, this.schema.name, '__mutation__');
2191
+ selectParts.push(`${rewrittenExpr} AS "${fieldPath}"`);
2192
+ }
2193
+ }
2194
+ else if (!Array.isArray(field)) {
2195
+ // Nested plain object - recurse into it and mark as nested path
2196
+ nestedPaths.add(fieldPath);
2197
+ processSelection(field, fieldPath);
2198
+ }
2076
2199
  }
2077
2200
  }
2078
- }
2079
- // We also need to include foreign keys for joins that aren't already in the selection
2201
+ };
2202
+ processSelection(selection);
2203
+ // Include foreign keys needed for joins - only for joins from main table
2080
2204
  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);
2205
+ // Only add FK to mainTableColumns if the join source is the main table
2206
+ if (join.sourceAlias === this.schema.name || !join.sourceAlias) {
2207
+ for (const fk of join.foreignKeys) {
2208
+ const fkDbCol = getFkDbColumnName(this.schema, fk);
2209
+ mainTableColumns.add(fkDbCol);
2092
2210
  }
2093
2211
  }
2094
2212
  }
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
2104
- }
2213
+ // Include 'id' column if there are collection subqueries (needed for correlation)
2214
+ if (collectionSubqueries.length > 0 || (navigationInfo.collectionFields && navigationInfo.collectionFields.size > 0)) {
2215
+ // Find the 'id' column db name
2216
+ const idColEntry = Object.entries(this.schema.columns).find(([propName, _]) => propName === 'id');
2217
+ if (idColEntry) {
2218
+ const idDbCol = idColEntry[1].build().name;
2219
+ mainTableColumns.add(idDbCol);
2220
+ }
2221
+ else {
2222
+ // Fallback to 'id' if not found in schema
2223
+ mainTableColumns.add('id');
2105
2224
  }
2106
2225
  }
2107
2226
  // Build RETURNING clause for CTE with all needed columns from main table
@@ -2111,32 +2230,32 @@ class SelectQueryBuilder {
2111
2230
  // Build the JOINs for the outer SELECT
2112
2231
  const joinClauses = [];
2113
2232
  for (const join of navigationInfo.joins) {
2114
- const sourceAlias = join.sourceAlias === this.schema.name ? '__mutation__' : `"${join.sourceAlias}"`;
2115
2233
  const qualifiedJoinTable = this.getQualifiedTableName(join.targetTable, join.targetSchema);
2116
2234
  // Find the db column names for the foreign keys
2117
2235
  const joinConditions = [];
2118
2236
  for (let i = 0; i < join.foreignKeys.length; i++) {
2119
2237
  const fk = join.foreignKeys[i];
2120
2238
  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
2239
  if (join.sourceAlias === this.schema.name || !join.sourceAlias) {
2240
+ // FK is on main table - look up db column name from main schema
2241
+ const fkDbCol = getFkDbColumnName(this.schema, fk);
2130
2242
  joinConditions.push(`"__mutation__"."${fkDbCol}" = "${join.alias}"."${match}"`);
2131
2243
  }
2132
2244
  else {
2133
- // Source is another joined table
2134
- joinConditions.push(`"${join.sourceAlias}"."${fk}" = "${join.alias}"."${match}"`);
2245
+ // FK is on an intermediate joined table - look up from its schema
2246
+ const sourceTableName = aliasToSourceTable.get(join.sourceAlias);
2247
+ const sourceSchema = sourceTableName && this.schemaRegistry ? this.schemaRegistry.get(sourceTableName) : undefined;
2248
+ const fkDbCol = sourceSchema ? getFkDbColumnName(sourceSchema, fk) : fk;
2249
+ joinConditions.push(`"${join.sourceAlias}"."${fkDbCol}" = "${join.alias}"."${match}"`);
2135
2250
  }
2136
2251
  }
2137
2252
  const joinType = join.isMandatory ? 'INNER JOIN' : 'LEFT JOIN';
2138
2253
  joinClauses.push(`${joinType} ${qualifiedJoinTable} AS "${join.alias}" ON ${joinConditions.join(' AND ')}`);
2139
2254
  }
2255
+ // Add LATERAL JOINs for collections
2256
+ for (const collection of collectionSubqueries) {
2257
+ joinClauses.push(collection.joinClause);
2258
+ }
2140
2259
  // Build the final CTE query
2141
2260
  const sql = `WITH "__mutation__" AS (
2142
2261
  ${mutationWithReturning}
@@ -2144,47 +2263,95 @@ class SelectQueryBuilder {
2144
2263
  SELECT ${selectParts.join(', ')}
2145
2264
  FROM "__mutation__"
2146
2265
  ${joinClauses.join('\n')}`;
2147
- return { sql, params: mutationParams };
2266
+ return { sql, params: allParams, nestedPaths };
2267
+ }
2268
+ /**
2269
+ * Rewrite a LATERAL JOIN clause to reference __mutation__ instead of the source table
2270
+ * @internal
2271
+ */
2272
+ rewriteCollectionJoinForMutation(joinClause, sourceTable, mutationAlias) {
2273
+ const sourcePattern = new RegExp(`"${sourceTable}"\\."`, 'g');
2274
+ return joinClause.replace(sourcePattern, `"${mutationAlias}"."`);
2275
+ }
2276
+ /**
2277
+ * Rewrite a correlated subquery expression to reference __mutation__ instead of the source table
2278
+ * @internal
2279
+ */
2280
+ rewriteCollectionExprForMutation(expression, sourceTable, mutationAlias) {
2281
+ const sourcePattern = new RegExp(`"${sourceTable}"\\."`, 'g');
2282
+ return expression.replace(sourcePattern, `"${mutationAlias}"."`);
2148
2283
  }
2149
2284
  /**
2150
2285
  * Map RETURNING results with navigation properties
2151
2286
  * Applies type mappers based on source table schemas
2287
+ * Reconstructs nested objects from flat column paths
2152
2288
  * @internal
2153
2289
  */
2154
- mapReturningResultsWithNavigation(rows, returning, navigationFields) {
2290
+ mapReturningResultsWithNavigation(rows, returning, navigationFields, nestedPaths) {
2155
2291
  return rows.map(row => {
2156
2292
  const mapped = {};
2157
2293
  for (const [key, value] of Object.entries(row)) {
2158
- // Check if this is a navigation field
2159
- const navInfo = navigationFields.get(key);
2160
- if (navInfo && navInfo.schemaTable && this.schemaRegistry) {
2161
- // Try to get mapper from the navigation target's schema
2162
- const targetSchema = this.schemaRegistry.get(navInfo.schemaTable);
2163
- if (targetSchema) {
2164
- // Find the column and its mapper
2165
- const colEntry = Object.entries(targetSchema.columns).find(([_, col]) => {
2166
- const config = col.build();
2167
- return config.name === navInfo.dbColumnName;
2168
- });
2169
- if (colEntry) {
2170
- const config = colEntry[1].build();
2171
- mapped[key] = config.mapper ? config.mapper.fromDriver(value) : value;
2172
- continue;
2294
+ // Handle nested paths (e.g., "invoicingPartner.id" -> { invoicingPartner: { id: value } })
2295
+ if (key.includes('.')) {
2296
+ const parts = key.split('.');
2297
+ let current = mapped;
2298
+ for (let i = 0; i < parts.length - 1; i++) {
2299
+ if (!current[parts[i]]) {
2300
+ current[parts[i]] = {};
2173
2301
  }
2302
+ current = current[parts[i]];
2174
2303
  }
2175
- }
2176
- // Try to find column by alias or name in main schema
2177
- const colEntry = Object.entries(this.schema.columns).find(([propName, col]) => {
2178
- const config = col.build();
2179
- return propName === key || config.name === key;
2180
- });
2181
- if (colEntry) {
2182
- const [, col] = colEntry;
2183
- const config = col.build();
2184
- mapped[key] = config.mapper ? config.mapper.fromDriver(value) : value;
2304
+ const finalKey = parts[parts.length - 1];
2305
+ // Check if this is a navigation field
2306
+ const navInfo = navigationFields.get(key);
2307
+ if (navInfo && navInfo.schemaTable && this.schemaRegistry) {
2308
+ const targetSchema = this.schemaRegistry.get(navInfo.schemaTable);
2309
+ if (targetSchema) {
2310
+ const colEntry = Object.entries(targetSchema.columns).find(([_, col]) => {
2311
+ const config = col.build();
2312
+ return config.name === navInfo.dbColumnName;
2313
+ });
2314
+ if (colEntry) {
2315
+ const config = colEntry[1].build();
2316
+ current[finalKey] = config.mapper ? config.mapper.fromDriver(value) : value;
2317
+ continue;
2318
+ }
2319
+ }
2320
+ }
2321
+ current[finalKey] = value;
2185
2322
  }
2186
2323
  else {
2187
- mapped[key] = value;
2324
+ // Check if this is a navigation field
2325
+ const navInfo = navigationFields.get(key);
2326
+ if (navInfo && navInfo.schemaTable && this.schemaRegistry) {
2327
+ // Try to get mapper from the navigation target's schema
2328
+ const targetSchema = this.schemaRegistry.get(navInfo.schemaTable);
2329
+ if (targetSchema) {
2330
+ // Find the column and its mapper
2331
+ const colEntry = Object.entries(targetSchema.columns).find(([_, col]) => {
2332
+ const config = col.build();
2333
+ return config.name === navInfo.dbColumnName;
2334
+ });
2335
+ if (colEntry) {
2336
+ const config = colEntry[1].build();
2337
+ mapped[key] = config.mapper ? config.mapper.fromDriver(value) : value;
2338
+ continue;
2339
+ }
2340
+ }
2341
+ }
2342
+ // Try to find column by alias or name in main schema
2343
+ const colEntry = Object.entries(this.schema.columns).find(([propName, col]) => {
2344
+ const config = col.build();
2345
+ return propName === key || config.name === key;
2346
+ });
2347
+ if (colEntry) {
2348
+ const [, col] = colEntry;
2349
+ const config = col.build();
2350
+ mapped[key] = config.mapper ? config.mapper.fromDriver(value) : value;
2351
+ }
2352
+ else {
2353
+ mapped[key] = value;
2354
+ }
2188
2355
  }
2189
2356
  }
2190
2357
  return mapped;