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.
- package/dist/entity/db-context.d.ts +16 -0
- package/dist/entity/db-context.d.ts.map +1 -1
- package/dist/entity/db-context.js +228 -58
- package/dist/entity/db-context.js.map +1 -1
- package/dist/query/query-builder.d.ts +11 -0
- package/dist/query/query-builder.d.ts.map +1 -1
- package/dist/query/query-builder.js +269 -102
- package/dist/query/query-builder.js.map +1 -1
- package/package.json +1 -1
|
@@ -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
|
-
|
|
1880
|
-
|
|
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(', ')}
|
|
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
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
const
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2082
|
-
|
|
2083
|
-
const
|
|
2084
|
-
|
|
2085
|
-
|
|
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
|
-
//
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
const
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
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
|
-
//
|
|
2134
|
-
|
|
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:
|
|
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
|
-
//
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
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
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
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
|
-
|
|
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;
|