@vantreeseba/drizzle-graphql 15.1.3 → 15.1.5

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.js CHANGED
@@ -1778,6 +1778,11 @@ const warnUnresolvedJoin = (relEntry, tableName, relationName, omitted) => {
1778
1778
  * Returns the JS property name of the local column on the parent table and the
1779
1779
  * Column object for the foreign column on the target table, or undefined if the
1780
1780
  * relation internals are not accessible.
1781
+ *
1782
+ * Only the relation's *first* column pair is reported. A relation joined on several columns
1783
+ * (`from: [a, b], to: [x, y]`) is not described fully by it, so anything that reads rows by
1784
+ * this pair alone mixes up parents that share the first column. Read paths use
1785
+ * {@link extractRelationJoinColumnPairs}, which returns every pair.
1781
1786
  */
1782
1787
  const extractRelationJoinColumns = (relEntry, parentTable, targetTable) => {
1783
1788
  const rel = relEntry.relation ?? relEntry;
@@ -1796,6 +1801,48 @@ const extractRelationJoinColumns = (relEntry, parentTable, targetTable) => {
1796
1801
  };
1797
1802
  };
1798
1803
  /**
1804
+ * Every column pair the relation joins on, in declaration order — one for an ordinary relation,
1805
+ * several for one declared `from: [a, b], to: [x, y]`. A row of the target belongs to a parent
1806
+ * only when *all* pairs match, so a read path has to correlate on every one of them.
1807
+ *
1808
+ * `undefined` when the relation's columns cannot be matched to the two tables, or when its two
1809
+ * sides list a different number of columns.
1810
+ */
1811
+ const extractRelationJoinColumnPairs = (relEntry, parentTable, targetTable) => {
1812
+ const rel = relEntry.relation ?? relEntry;
1813
+ const sourceCols = relationColumns(rel, "source");
1814
+ const targetCols = relationColumns(rel, "target");
1815
+ if (!sourceCols?.length || sourceCols.length !== targetCols?.length) return;
1816
+ const parentEntries = Object.entries(getColumns(parentTable));
1817
+ const targetEntries = Object.entries(getColumns(targetTable));
1818
+ const pairs = [];
1819
+ for (let i = 0; i < sourceCols.length; i++) {
1820
+ const localCol = sourceCols[i];
1821
+ const foreignCol = targetCols[i];
1822
+ const localColPropName = parentEntries.find(([, c]) => c === localCol)?.[0];
1823
+ const foreignColPropName = targetEntries.find(([, c]) => c === foreignCol)?.[0];
1824
+ if (!localColPropName || !foreignColPropName) return;
1825
+ pairs.push({
1826
+ localColPropName,
1827
+ localCol,
1828
+ foreignColPropName,
1829
+ foreignCol
1830
+ });
1831
+ }
1832
+ return pairs;
1833
+ };
1834
+ /**
1835
+ * A string identifying one combination of join-column values, for grouping a batch's rows by
1836
+ * parent. Values are compared by their string form, as a single-column key always has been.
1837
+ */
1838
+ const joinKeyOf = (values) => values.length === 1 ? String(values[0]) : JSON.stringify(values.map((value) => String(value)));
1839
+ /**
1840
+ * The rows whose `cols` hold one of `keys`: `col IN (…)` for a single join column, and an `OR`
1841
+ * of per-parent equalities for several — `(a = 1 AND b = 1) OR (a = 1 AND b = 2)` — which
1842
+ * every dialect accepts, unlike a row-value `IN` list.
1843
+ */
1844
+ const parentKeyCondition = (cols, keys) => cols.length === 1 ? inArray(cols[0], keys.map((key) => key[0])) : or(...keys.map((key) => and(...cols.map((col, i) => eq(col, key[i])))));
1845
+ /**
1799
1846
  * The junction table and its two columns for a `.through()` (many-to-many) relation:
1800
1847
  * `sourceCol` holds the parent's key, `targetCol` the target's. `undefined` for a relation
1801
1848
  * with no junction; `null` for one whose junction cannot be used here — more than one column
@@ -1826,16 +1873,18 @@ const extractJunctionColumns = (relEntry) => {
1826
1873
  *
1827
1874
  * A reversed filter (inherited from the relation on the other side) is written against the
1828
1875
  * parent table, so it is applied as "the row's parent key belongs to a parent that passes it".
1829
- * `parentKeyCol` is the parent's join column and `groupCol` the column in the query that holds
1830
- * the parent key for each row — the target's foreign key, or the junction's source column.
1876
+ * `parentKeyCols` are the parent's join columns and `groupCols` the columns in the query that
1877
+ * hold the parent key for each row — the target's foreign key, or the junction's source
1878
+ * column — in the same order. A key of several columns is compared as a row value.
1831
1879
  */
1832
- const relationOwnCondition = (relEntry, parentTable, targetTable, parentKeyCol, groupCol) => {
1880
+ const relationOwnCondition = (relEntry, parentTable, targetTable, parentKeyCols, groupCols) => {
1833
1881
  const rel = relEntry.relation ?? relEntry;
1834
1882
  const ownWhere = relationOwnWhere(rel);
1835
1883
  if (!ownWhere) return;
1836
1884
  if (!isRelationFilterReversed(rel)) return relationsFilterToSQL(targetTable, ownWhere);
1837
1885
  const parentFilter = relationsFilterToSQL(parentTable, ownWhere);
1838
- return sql`${groupCol} in (select ${parentKeyCol} from ${parentTable} where ${parentFilter})`;
1886
+ if (groupCols.length === 1) return sql`${groupCols[0]} in (select ${parentKeyCols[0]} from ${parentTable} where ${parentFilter})`;
1887
+ return sql`(${sql.join([...groupCols], sql`, `)}) in (select ${sql.join([...parentKeyCols], sql`, `)} from ${parentTable} where ${parentFilter})`;
1839
1888
  };
1840
1889
  /**
1841
1890
  * Whether a to-one relation can back a relation orderBy hop. `.through()` (junction)
@@ -3821,8 +3870,7 @@ const relationJoinColumns = (tree, table, selectionCtx) => {
3821
3870
  const relEntry = fieldData.name.endsWith(AGGREGATE_FIELD_SUFFIX) ? relations[fieldData.name.slice(0, -9)] : relations[fieldData.name];
3822
3871
  const targetTable = relEntry ? selectionCtx.tables[relEntry.targetTableName] : void 0;
3823
3872
  if (!relEntry || !targetTable) continue;
3824
- const joinCols = extractRelationJoinColumns(relEntry, table, targetTable);
3825
- if (joinCols) needed.push(joinCols.localColPropName);
3873
+ for (const pair of extractRelationJoinColumnPairs(relEntry, table, targetTable) ?? []) needed.push(pair.localColPropName);
3826
3874
  }
3827
3875
  return needed;
3828
3876
  };
@@ -4843,13 +4891,12 @@ const identifyRows = (field, args) => {
4843
4891
  * Window functions require PostgreSQL, MySQL >= 8.0, or SQLite >= 3.25.
4844
4892
  * Returns raw rows (NOT remapped); the caller groups + remaps them.
4845
4893
  */
4846
- const batchedPaginatedRelationQuery = async (db, targetTable, foreignCol, whereCondition, orderByArg, limit, offset, pkNames, columns, orderCtx, whereArgs, junction) => {
4894
+ const batchedPaginatedRelationQuery = async (db, targetTable, foreignCol, partitionCols, whereCondition, orderByArg, limit, offset, pkNames, columns, orderCtx, whereArgs, junction) => {
4847
4895
  const cols = columns ?? getColumns(targetTable);
4848
4896
  const orderExprs = [...orderByArg ? extractOrderBy(targetTable, orderByArg, orderCtx, whereArgs) : [], ...primaryKeyOrderExprs(targetTable, pkNames)];
4849
4897
  const orderClause = orderExprs.length ? sql` order by ${sql.join(orderExprs, sql`, `)}` : sql``;
4850
4898
  const RN = "__drizzle_graphql_rn";
4851
- const partitionCol = junction ? junction.sourceCol : foreignCol;
4852
- const rowNumber = sql`row_number() over (partition by ${partitionCol}${orderClause})`.as(RN);
4899
+ const rowNumber = sql`row_number() over (partition by ${sql.join([...partitionCols], sql`, `)}${orderClause})`.as(RN);
4853
4900
  const sub = withJunction(db.select({
4854
4901
  ...cols,
4855
4902
  [RN]: rowNumber
@@ -4888,13 +4935,14 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
4888
4935
  const targetTableName = relEntry.targetTableName;
4889
4936
  const targetTable = tables[targetTableName];
4890
4937
  if (!parentTable || !targetTable) return;
4891
- const joinCols = extractRelationJoinColumns(relEntry, parentTable, targetTable);
4892
- if (!joinCols) return;
4893
- const { localColPropName, foreignCol, foreignColPropName } = joinCols;
4938
+ const joinPairs = extractRelationJoinColumnPairs(relEntry, parentTable, targetTable);
4939
+ if (!joinPairs) return;
4940
+ const localColPropNames = joinPairs.map((pair) => pair.localColPropName);
4941
+ const foreignCol = joinPairs[0].foreignCol;
4894
4942
  const junction = extractJunctionColumns(relEntry);
4895
- const groupCol = junction ? junction.sourceCol : foreignCol;
4896
- const groupKey = junction ? PARENT_KEY : foreignColPropName;
4897
- const ownCondition = relationOwnCondition(relEntry, parentTable, targetTable, getColumns(parentTable)[localColPropName], groupCol);
4943
+ const groupCols = junction ? [junction.sourceCol] : joinPairs.map((pair) => pair.foreignCol);
4944
+ const groupKeys = junction ? [PARENT_KEY] : joinPairs.map((pair) => pair.foreignColPropName);
4945
+ const ownCondition = relationOwnCondition(relEntry, parentTable, targetTable, joinPairs.map((pair) => pair.localCol), groupCols);
4898
4946
  const defaultDeleted = relationDeletedDefault(policies?.softDelete, targetTableName, isOne && relEntry.relation?.optional === false);
4899
4947
  const limitPolicy = isOne ? void 0 : limits?.(targetTableName);
4900
4948
  const targetPkNames = relEntry.targetPkNames ?? [];
@@ -4923,7 +4971,7 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
4923
4971
  allRelations: filterCtx?.relationMap
4924
4972
  });
4925
4973
  const targetColumns = getColumns(targetTable);
4926
- columns[foreignColPropName] = foreignCol;
4974
+ for (const pair of joinPairs) columns[pair.foreignColPropName] = pair.foreignCol;
4927
4975
  for (const [columnName] of cursorEntries ?? []) {
4928
4976
  const column = targetColumns[columnName];
4929
4977
  if (column) columns[columnName] = column;
@@ -4935,8 +4983,8 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
4935
4983
  };
4936
4984
  const resolve = async (parent, args, context, info) => {
4937
4985
  if (parent[relationName] !== void 0) return parent[relationName];
4938
- const localValue = parent[localColPropName];
4939
- if (localValue == null) return isOne ? null : [];
4986
+ const localValues = localColPropNames.map((name) => parent[name]);
4987
+ if (localValues.some((value) => value == null)) return isOne ? null : [];
4940
4988
  const { where: whereArg, limit: requestedLimit, offset, after, deleted } = args ?? {};
4941
4989
  const { orderByArg, limit, distinct, cursorEntries, cursorValues, columns, loaderKey } = getOrCreateRequestValue(context, info?.fieldNodes?.[0], `relation:${tableName}::${relationName}`, () => {
4942
4990
  const orderByArg = isOne ? args?.orderBy : withDefaultOrderBy(args ?? {}, targetTableName, policies?.defaultOrderBy).orderBy;
@@ -4977,11 +5025,11 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
4977
5025
  };
4978
5026
  });
4979
5027
  if (junction === null) throw drizzleError(`Relation '${tableName}.${relationName}' goes through a junction with a composite key, which can only be loaded together with its parent. Leave it to eager loading (eagerLoadRelations).`, { code: "DRIZZLE_UNSUPPORTED_RELATION" });
4980
- return getOrCreateLoader(context, loaderKey, async (parentIds) => {
5028
+ return getOrCreateLoader(context, loaderKey, async (parentKeys) => {
4981
5029
  const executor = resolveExecutor(db, context);
4982
- const uniqueIds = [...new Set(parentIds)];
5030
+ const uniqueKeys = [...new Map(parentKeys.map((key) => [joinKeyOf(key), key])).values()];
4983
5031
  const scope = resolveScope(policies, context, filterCtx);
4984
- let whereCondition = withScope(scope, targetTableName, targetTable, and(inArray(groupCol, uniqueIds), ownCondition, whereArg ? extractFilters(targetTable, targetTableName, whereArg, relationFilterCtx(filterCtx, targetTableName, scope)) : void 0, cursorValues ? buildCursorCondition(targetTable, cursorEntries, cursorValues, nullOrdering) : void 0), deleted ?? defaultDeleted);
5032
+ let whereCondition = withScope(scope, targetTableName, targetTable, and(parentKeyCondition(groupCols, uniqueKeys), ownCondition, whereArg ? extractFilters(targetTable, targetTableName, whereArg, relationFilterCtx(filterCtx, targetTableName, scope)) : void 0, cursorValues ? buildCursorCondition(targetTable, cursorEntries, cursorValues, nullOrdering) : void 0), deleted ?? defaultDeleted);
4985
5033
  if (distinct) {
4986
5034
  const keys = await selectDistinctKeys({
4987
5035
  db: executor,
@@ -4991,22 +5039,22 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
4991
5039
  pkNames: targetPkNames,
4992
5040
  where: whereCondition,
4993
5041
  orderBy: orderByArg,
4994
- partitionBy: [groupCol],
5042
+ partitionBy: groupCols,
4995
5043
  junction: junction ? {
4996
5044
  table: junction.table,
4997
5045
  on: eq(junction.targetCol, foreignCol),
4998
- parentKey: groupCol
5046
+ parentKey: junction.sourceCol
4999
5047
  } : void 0
5000
5048
  });
5001
- if (!keys.length) return parentIds.map(() => isOne ? null : []);
5002
- whereCondition = and(whereCondition, junction ? or(...keys.map((key) => and(eq(groupCol, key[DISTINCT_PARENT]), primaryKeyRestriction(targetTable, targetPkNames, [key])))) : primaryKeyRestriction(targetTable, targetPkNames, keys));
5049
+ if (!keys.length) return parentKeys.map(() => isOne ? null : []);
5050
+ whereCondition = and(whereCondition, junction ? or(...keys.map((key) => and(eq(junction.sourceCol, key[DISTINCT_PARENT]), primaryKeyRestriction(targetTable, targetPkNames, [key])))) : primaryKeyRestriction(targetTable, targetPkNames, keys));
5003
5051
  }
5004
5052
  const selection = junction ? {
5005
5053
  ...columns ?? getColumns(targetTable),
5006
- [PARENT_KEY]: groupCol
5054
+ [PARENT_KEY]: junction.sourceCol
5007
5055
  } : columns;
5008
5056
  let rows;
5009
- if (limit != null || offset != null) rows = await batchedPaginatedRelationQuery(executor, targetTable, foreignCol, whereCondition, orderByArg, limit ?? null, offset ?? null, targetPkNames, selection, relationFilterCtx(filterCtx, targetTableName, scope), whereArg, junction);
5057
+ if (limit != null || offset != null) rows = await batchedPaginatedRelationQuery(executor, targetTable, foreignCol, groupCols, whereCondition, orderByArg, limit ?? null, offset ?? null, targetPkNames, selection, relationFilterCtx(filterCtx, targetTableName, scope), whereArg, junction);
5010
5058
  else {
5011
5059
  let q = withJunction((selection ? executor.select(selection) : executor.select()).from(targetTable), junction, foreignCol).where(whereCondition);
5012
5060
  const orderExprs = cursorEntries ? cursorOrderExprs(targetTable, cursorEntries) : orderByArg ? extractOrderBy(targetTable, orderByArg, relationFilterCtx(filterCtx, targetTableName, scope), whereArg) : [];
@@ -5015,20 +5063,20 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
5015
5063
  }
5016
5064
  if (cursorEntries) attachRowCursors(rows, cursorEntries);
5017
5065
  const keyOf = (row) => {
5018
- const key = String(row[groupKey]);
5066
+ const key = joinKeyOf(groupKeys.map((name) => row[name]));
5019
5067
  if (junction) delete row[PARENT_KEY];
5020
5068
  return key;
5021
5069
  };
5022
5070
  if (isOne) {
5023
5071
  const byKey = new Map(rows.map((row) => [keyOf(row), row]));
5024
5072
  remapToGraphQLArrayOutput(rows, targetTableName, targetTable);
5025
- return parentIds.map((id) => byKey.get(String(id)) ?? null);
5073
+ return parentKeys.map((key) => byKey.get(joinKeyOf(key)) ?? null);
5026
5074
  }
5027
- const grouped = new Map(uniqueIds.map((id) => [String(id), []]));
5075
+ const grouped = new Map(uniqueKeys.map((key) => [joinKeyOf(key), []]));
5028
5076
  for (const row of rows) grouped.get(keyOf(row))?.push(row);
5029
5077
  remapToGraphQLArrayOutput(rows, targetTableName, targetTable);
5030
- return parentIds.map((id) => grouped.get(String(id)) ?? []);
5031
- }).load(localValue);
5078
+ return parentKeys.map((key) => grouped.get(joinKeyOf(key)) ?? []);
5079
+ }).load(localValues);
5032
5080
  };
5033
5081
  return async (parent, args, context, info) => {
5034
5082
  try {
@@ -6104,8 +6152,8 @@ const createRelationAggregateFactory = (db, tables, cacheCtx, typeNameMapper, fi
6104
6152
  const targetTableName = relEntry.targetTableName;
6105
6153
  const targetTable = tables[targetTableName];
6106
6154
  if (!parentTable || !targetTable) return;
6107
- const joinCols = extractRelationJoinColumns(relEntry, parentTable, targetTable);
6108
- if (!joinCols) {
6155
+ const joinPairs = extractRelationJoinColumnPairs(relEntry, parentTable, targetTable);
6156
+ if (!joinPairs) {
6109
6157
  const key = `${tableName}.${relationName}`;
6110
6158
  if (!warned.has(key)) {
6111
6159
  warned.add(key);
@@ -6113,11 +6161,13 @@ const createRelationAggregateFactory = (db, tables, cacheCtx, typeNameMapper, fi
6113
6161
  }
6114
6162
  return;
6115
6163
  }
6116
- const { localColPropName, foreignCol } = joinCols;
6164
+ const localColPropNames = joinPairs.map((pair) => pair.localColPropName);
6165
+ const foreignCol = joinPairs[0].foreignCol;
6117
6166
  const junction = extractJunctionColumns(relEntry);
6118
6167
  if (junction === null) return;
6119
- const groupCol = junction ? junction.sourceCol : foreignCol;
6120
- const ownCondition = relationOwnCondition(relEntry, parentTable, targetTable, getColumns(parentTable)[localColPropName], groupCol);
6168
+ const groupCols = junction ? [junction.sourceCol] : joinPairs.map((pair) => pair.foreignCol);
6169
+ const groupAliases = groupCols.map((_, i) => `${GROUP_KEY}${i}`);
6170
+ const ownCondition = relationOwnCondition(relEntry, parentTable, targetTable, joinPairs.map((pair) => pair.localCol), groupCols);
6121
6171
  const defaultDeleted = relationDeletedDefault(policies?.softDelete, targetTableName, false);
6122
6172
  const targetTypeName = resolveTypeName(targetTableName, typeNameMapper);
6123
6173
  const type = generateAggregateTypes(targetTable, targetTableName, targetTypeName, cacheCtx);
@@ -6141,24 +6191,25 @@ const createRelationAggregateFactory = (db, tables, cacheCtx, typeNameMapper, fi
6141
6191
  };
6142
6192
  });
6143
6193
  if (!Object.keys(request.selection).length) return {};
6144
- const localValue = parent[localColPropName];
6145
- if (localValue == null) return assembleAggregateRow({}, request, target);
6194
+ const localValues = localColPropNames.map((name) => parent[name]);
6195
+ if (localValues.some((value) => value == null)) return assembleAggregateRow({}, request, target);
6146
6196
  const whereArg = args?.where;
6147
6197
  const deleted = args?.deleted;
6148
- const loader = getOrCreateLoader(context, loaderKey, async (parentIds) => {
6198
+ const loader = getOrCreateLoader(context, loaderKey, async (parentKeys) => {
6149
6199
  const executor = resolveExecutor(db, context);
6150
- const uniqueIds = [...new Set(parentIds)];
6200
+ const uniqueKeys = [...new Map(parentKeys.map((key) => [joinKeyOf(key), key])).values()];
6151
6201
  const scope = resolveScope(policies, context, filterCtx);
6152
- const whereCondition = withScope(scope, targetTableName, targetTable, and(inArray(groupCol, uniqueIds), ownCondition, whereArg ? extractFilters(targetTable, targetTableName, whereArg, relationFilterCtx(filterCtx, targetTableName, scope)) : void 0), deleted ?? defaultDeleted);
6202
+ const whereCondition = withScope(scope, targetTableName, targetTable, and(parentKeyCondition(groupCols, uniqueKeys), ownCondition, whereArg ? extractFilters(targetTable, targetTableName, whereArg, relationFilterCtx(filterCtx, targetTableName, scope)) : void 0), deleted ?? defaultDeleted);
6203
+ const groupSelection = Object.fromEntries(groupAliases.map((alias, i) => [alias, groupCols[i]]));
6153
6204
  const selected = executor.select({
6154
- [GROUP_KEY]: groupCol,
6205
+ ...groupSelection,
6155
6206
  ...request.selection
6156
6207
  }).from(targetTable);
6157
- const rows = await (junction ? selected.innerJoin(junction.table, eq(junction.targetCol, foreignCol)) : selected).where(whereCondition).groupBy(groupCol);
6158
- const byKey = new Map(rows.map((row) => [row[GROUP_KEY], row]));
6159
- return parentIds.map((id) => byKey.get(id) ?? {});
6208
+ const rows = await (junction ? selected.innerJoin(junction.table, eq(junction.targetCol, foreignCol)) : selected).where(whereCondition).groupBy(...groupCols);
6209
+ const byKey = new Map(rows.map((row) => [joinKeyOf(groupAliases.map((alias) => row[alias])), row]));
6210
+ return parentKeys.map((key) => byKey.get(joinKeyOf(key)) ?? {});
6160
6211
  });
6161
- return assembleAggregateRow(await loader.load(localValue), request, target);
6212
+ return assembleAggregateRow(await loader.load(localValues), request, target);
6162
6213
  } catch (e) {
6163
6214
  throw withErrorContext(toGraphQLError(e), errorCtx);
6164
6215
  }
@@ -6465,6 +6516,20 @@ const ALL_OPS = [
6465
6516
  * accepts NULL.
6466
6517
  */
6467
6518
  const isOptionalOnInsert = (column) => !column.notNull || !!column.hasDefault || !!column.defaultFn;
6519
+ /**
6520
+ * The plan fields that carry a relation's own `where` (see {@link relationOwnCondition}).
6521
+ *
6522
+ * A `where` on the target narrows every row selection the write makes, and takes `create`
6523
+ * away: the row it inserts is attached whether or not it satisfies the `where`, so the
6524
+ * relation could come back without it. A `where` inherited from the reverse relation filters
6525
+ * the parent instead, and becomes a per-parent check.
6526
+ */
6527
+ const ownWherePlan = (relEntry, parentTable, targetTable, localCol) => {
6528
+ const condition = () => relationOwnCondition(relEntry, parentTable, targetTable, [localCol], [localCol]);
6529
+ if (!condition()) return {};
6530
+ if (!isRelationFilterReversed(relEntry.relation ?? relEntry)) return { ownCondition: () => condition() };
6531
+ return { parentShows: async (executor, key) => (await executor.select({ key: localCol }).from(parentTable).where(and(eq(localCol, key), condition())).limit(1)).length > 0 };
6532
+ };
6468
6533
  /** The property name a column is stored under on its table, matched by identity. */
6469
6534
  const propNameOf = (table, column) => {
6470
6535
  if (!column) return;
@@ -6485,7 +6550,7 @@ const propNameOf = (table, column) => {
6485
6550
  * `onUnresolved` is called when the relation's columns are of a supported shape but cannot be
6486
6551
  * matched to the three tables — a drizzle-orm mismatch rather than an unsupported relation.
6487
6552
  */
6488
- const buildThroughPlan = (relationName, relation, table, targetTableName, targetTable, tableNameOf, onUnresolved) => {
6553
+ const buildThroughPlan = (relationName, relEntry, relation, table, targetTableName, targetTable, tableNameOf, onUnresolved) => {
6489
6554
  if (is(relation, One)) return;
6490
6555
  const junction = relation.throughTable;
6491
6556
  const sourceEntries = relation.through?.source;
@@ -6526,6 +6591,7 @@ const buildThroughPlan = (relationName, relation, table, targetTableName, target
6526
6591
  targetCol
6527
6592
  },
6528
6593
  targetKeyColumns: { [foreignColPropName]: foreignCol },
6594
+ ...ownWherePlan(relEntry, table, targetTable, localCol),
6529
6595
  allowed: /* @__PURE__ */ new Set([
6530
6596
  "connect",
6531
6597
  "disconnect",
@@ -6540,8 +6606,9 @@ const buildThroughPlan = (relationName, relation, table, targetTableName, target
6540
6606
  * rather than a foreign-key one — see {@link buildThroughPlan}.
6541
6607
  *
6542
6608
  * Skipped, and so absent from the generated inputs rather than failing at runtime:
6543
- * relations whose join columns cannot be read off the drizzle relation; and relations where
6544
- * neither join column is unique, which leaves no way to tell which side stores the key.
6609
+ * relations whose join columns cannot be read off the drizzle relation; relations joining on
6610
+ * more than one column per side; and relations where neither join column is unique, which
6611
+ * leaves no way to tell which side stores the key.
6545
6612
  */
6546
6613
  const buildNestedWritePlans = (tables, relationMap, resolveUniqueSets, resolvePrimaryKeys, extractJoinColumns, tableNameOf = () => void 0) => {
6547
6614
  const uniqueCache = /* @__PURE__ */ new Map();
@@ -6573,10 +6640,11 @@ const buildNestedWritePlans = (tables, relationMap, resolveUniqueSets, resolvePr
6573
6640
  if (!targetTable) continue;
6574
6641
  const omitted = `the '${relationName}' field of its nested-write create and update inputs`;
6575
6642
  if (relation.through) {
6576
- const throughPlan = buildThroughPlan(relationName, relation, table, targetTableName, targetTable, tableNameOf, () => warnUnresolvedJoin(relEntry, tableName, relationName, omitted));
6643
+ const throughPlan = buildThroughPlan(relationName, relEntry, relation, table, targetTableName, targetTable, tableNameOf, () => warnUnresolvedJoin(relEntry, tableName, relationName, omitted));
6577
6644
  if (throughPlan) tablePlans[relationName] = throughPlan;
6578
6645
  continue;
6579
6646
  }
6647
+ if ((relationColumns(relation, "source")?.length ?? 0) > 1 || (relationColumns(relation, "target")?.length ?? 0) > 1) continue;
6580
6648
  const joinColumns = extractJoinColumns(relEntry, table, targetTable);
6581
6649
  if (!joinColumns) {
6582
6650
  warnUnresolvedJoin(relEntry, tableName, relationName, omitted);
@@ -6599,8 +6667,15 @@ const buildNestedWritePlans = (tables, relationMap, resolveUniqueSets, resolvePr
6599
6667
  else continue;
6600
6668
  const fkColumn = fkSide === "child" ? foreignCol : localCol;
6601
6669
  const targetColumns = getColumns(targetTable);
6602
- const targetKeyColumns = Object.fromEntries(resolvePrimaryKeys(targetTable).filter((name) => targetColumns[name]).map((name) => [name, targetColumns[name]]));
6670
+ const targetPrimaryKey = resolvePrimaryKeys(targetTable).filter((name) => targetColumns[name]);
6671
+ const targetKeyColumns = Object.fromEntries(targetPrimaryKey.map((name) => [name, targetColumns[name]]));
6603
6672
  targetKeyColumns[foreignColPropName] = foreignCol;
6673
+ const ownWhere = ownWherePlan(relEntry, table, targetTable, localCol);
6674
+ const ops = !fkColumn.notNull ? isOne ? [
6675
+ "create",
6676
+ "connect",
6677
+ "disconnect"
6678
+ ] : ALL_OPS : ["create", "connect"];
6604
6679
  tablePlans[relationName] = {
6605
6680
  relationName,
6606
6681
  targetTableName,
@@ -6613,11 +6688,9 @@ const buildNestedWritePlans = (tables, relationMap, resolveUniqueSets, resolvePr
6613
6688
  fkSide,
6614
6689
  canDetach: !fkColumn.notNull,
6615
6690
  targetKeyColumns,
6616
- allowed: new Set(!fkColumn.notNull ? isOne ? [
6617
- "create",
6618
- "connect",
6619
- "disconnect"
6620
- ] : ALL_OPS : ["create", "connect"])
6691
+ targetPrimaryKey,
6692
+ ...ownWhere,
6693
+ allowed: new Set(ownWhere.ownCondition ? ops.filter((op) => op !== "create") : ops)
6621
6694
  };
6622
6695
  }
6623
6696
  if (Object.keys(tablePlans).length) plans[tableName] = tablePlans;
@@ -6692,6 +6765,35 @@ const gateNestedWritePlans = (plans, featureOf, isExcluded) => {
6692
6765
  }
6693
6766
  return omitted;
6694
6767
  };
6768
+ /**
6769
+ * Takes out every relation whose foreign key is a `contextValues` column of the table storing
6770
+ * it — the parent's own column for a parent-side relation, the target's for a child-side one,
6771
+ * either junction column for a many-to-many.
6772
+ *
6773
+ * Every operation on such a relation writes that column: `connect`, `disconnect` and `set`
6774
+ * by definition, and a child-side `create` too, since it fills the key in from the parent row
6775
+ * rather than from context. That column is the server's to set — an insert stamps it and an
6776
+ * update never touches it — so writing it from a nested input would let a client pick, or
6777
+ * change, the owner the server assigns. Whole relations are dropped, not single inputs: no
6778
+ * operation on them is left that does not write the key.
6779
+ *
6780
+ * Returns one line per relation taken out, for the build note.
6781
+ */
6782
+ const dropContextValueRelations = (plans, contextValuesOf) => {
6783
+ const omitted = [];
6784
+ if (!contextValuesOf) return omitted;
6785
+ for (const [tableName, tablePlans] of Object.entries(plans)) {
6786
+ for (const [relationName, plan] of Object.entries(tablePlans)) {
6787
+ const stamped = (plan.through ? [[plan.through.tableName, plan.through.sourceColPropName], [plan.through.tableName, plan.through.targetColPropName]] : plan.fkSide === "parent" ? [[tableName, plan.localColPropName]] : [[plan.targetTableName, plan.foreignColPropName]]).find(([owner, column]) => owner !== void 0 && contextValuesOf(owner)?.[column]);
6788
+ if (stamped) {
6789
+ delete tablePlans[relationName];
6790
+ omitted.push(`${tableName}.${relationName} (${stamped[0]}.${stamped[1]} is a contextValues column)`);
6791
+ }
6792
+ }
6793
+ if (!Object.keys(tablePlans).length) delete plans[tableName];
6794
+ }
6795
+ return omitted;
6796
+ };
6695
6797
  const NO_COLUMNS = /* @__PURE__ */ new Set();
6696
6798
  /**
6697
6799
  * Builds the nested-write input types for a schema.
@@ -6722,7 +6824,8 @@ const createNestedWriteTypes = (params) => {
6722
6824
  if (cached) return cached;
6723
6825
  const omitted = plan.fkSide === "child" ? plan.foreignColPropName : void 0;
6724
6826
  const contextColumns = cacheCtx.contextValuesOf?.(plan.targetTableName);
6725
- const fields = Object.fromEntries(Object.entries(visibleColumns(plan.targetTable)).filter(([columnName]) => columnName !== omitted && !contextColumns?.[columnName]).map(([columnName, column]) => [columnName, drizzleColumnToGraphQLType(column, columnName, plan.targetTableName, false, true, true)]));
6827
+ const markerColumn = cacheCtx.softDeleteOf?.(plan.targetTableName)?.columnName;
6828
+ const fields = Object.fromEntries(Object.entries(visibleColumns(plan.targetTable)).filter(([columnName]) => columnName !== omitted && !contextColumns?.[columnName] && columnName !== markerColumn).map(([columnName, column]) => [columnName, drizzleColumnToGraphQLType(column, columnName, plan.targetTableName, false, true, true)]));
6726
6829
  const type = new GraphQLInputObjectType({
6727
6830
  name: cacheCtx.typeName({
6728
6831
  kind: "nestedWriteInput",
@@ -6846,6 +6949,8 @@ const createNestedWriteTypes = (params) => {
6846
6949
  * join columns the whole batch's RETURNING clause needs, without inspecting each row's write.
6847
6950
  */
6848
6951
  const mergedOps = (entries) => Object.assign({}, ...entries.map((entry) => entry.ops));
6952
+ /** A to-one `connect` whose filter did not match exactly one row. */
6953
+ const connectMatchError = (relationName, matched) => drizzleError(`Drizzle-GraphQL Error: 'connect' on '${relationName}' matched ${matched} rows — it attaches a single row, so its filter must match exactly one.`, { code: "DRIZZLE_NESTED_WRITE_INVALID" });
6849
6954
  const asArray = (value) => Array.isArray(value) ? value : value === void 0 ? [] : [value];
6850
6955
  const supplied = (value) => value !== void 0 && value !== null;
6851
6956
  /**
@@ -6937,6 +7042,8 @@ const registerChildSteps = (steps, plan, op) => {
6937
7042
  };
6938
7043
  /** The input that attaches a row to a to-one, which detaches whatever was attached before. */
6939
7044
  const attachingOp = (op) => op["connect"] !== void 0 ? "connect" : op["create"] !== void 0 ? "create" : void 0;
7045
+ /** The child-side relation in `ops` whose target is the parent's own table, if there is one. */
7046
+ const selfTargetingPlan = (tablePlans, tableName, ops) => Object.keys(ops).map((relationName) => tablePlans?.[relationName]).find((plan) => plan?.fkSide === "child" && !plan.through && plan.targetTableName === tableName);
6940
7047
  const createNestedWriteRuntime = (params) => {
6941
7048
  const { plans, filterCtx, policies, contextValues } = params;
6942
7049
  const onWrite = policies?.onWrite;
@@ -6947,7 +7054,7 @@ const createNestedWriteRuntime = (params) => {
6947
7054
  const conditionOf = (plan, filter, operation, scope) => {
6948
7055
  const condition = extractFilters(plan.targetTable, plan.targetTableName, filter, relationFilterCtx(filterCtx, plan.targetTableName, scope));
6949
7056
  if (!condition) throw drizzleError(`Drizzle-GraphQL Error: '${operation}' on '${plan.relationName}' needs a filter that selects rows — it was given one that matches everything.`, { code: "DRIZZLE_NESTED_WRITE_INVALID" });
6950
- return withScope(scope, plan.targetTableName, plan.targetTable, condition);
7057
+ return withScope(scope, plan.targetTableName, plan.targetTable, and(condition, plan.ownCondition?.()));
6951
7058
  };
6952
7059
  const assertSingleOperation = (plan, op) => {
6953
7060
  const given = [
@@ -6970,22 +7077,24 @@ const createNestedWriteRuntime = (params) => {
6970
7077
  * error on some of them — the read and the write share the surrounding transaction, so no
6971
7078
  * other writer can slip a duplicate in between them.
6972
7079
  */
6973
- const applyThroughOps = async (executor, plan, key, op, scope, steps) => {
7080
+ const applyThroughOps = async (executor, plan, key, op, scope, steps, context) => {
6974
7081
  const through = plan.through;
6975
7082
  const link = async (operands, operation) => {
6976
7083
  const existing = new Set((await executor.select({ [through.targetColPropName]: through.targetCol }).from(through.table).where(eq(through.sourceCol, key))).map((row) => row[through.targetColPropName]));
6977
7084
  const rows = [];
6978
7085
  for (const filter of operands) for (const targetKey of await selectedTargetKeys(executor, plan, filter, operation, scope)) if (!existing.has(targetKey)) {
6979
7086
  existing.add(targetKey);
6980
- rows.push({
7087
+ rows.push(applyContextValues({
6981
7088
  [through.sourceColPropName]: key,
6982
7089
  [through.targetColPropName]: targetKey
6983
- });
7090
+ }, through.tableName ? contextValues?.(through.tableName) : void 0, context));
6984
7091
  }
6985
7092
  if (rows.length) await steps.run(executor.insert(through.table).values(rows), operation, "insert");
6986
7093
  };
6987
7094
  if (supplied(op["set"])) {
6988
- await steps.run(executor.delete(through.table).where(eq(through.sourceCol, key)), "set", "delete");
7095
+ const reachable = withScope(scope, plan.targetTableName, plan.targetTable, plan.ownCondition?.());
7096
+ const unlink = reachable ? and(eq(through.sourceCol, key), inArray(through.targetCol, executor.select({ [plan.foreignColPropName]: plan.foreignCol }).from(plan.targetTable).where(reachable))) : eq(through.sourceCol, key);
7097
+ await steps.run(executor.delete(through.table).where(unlink), "set", "delete");
6989
7098
  await link(asArray(op["set"]), "set");
6990
7099
  }
6991
7100
  if (supplied(op["disconnect"])) for (const filter of asArray(op["disconnect"])) {
@@ -6994,7 +7103,7 @@ const createNestedWriteRuntime = (params) => {
6994
7103
  }
6995
7104
  if (supplied(op["connect"])) await link(asArray(op["connect"]), "connect");
6996
7105
  };
6997
- const detachAll = (executor, plan, key, scope, steps, op) => steps.run(executor.update(plan.targetTable).set({ [plan.foreignColPropName]: null }).where(withScope(scope, plan.targetTableName, plan.targetTable, eq(plan.foreignCol, key))), op, "update");
7106
+ const detachAll = (executor, plan, key, scope, steps, op) => steps.run(executor.update(plan.targetTable).set({ [plan.foreignColPropName]: null }).where(withScope(scope, plan.targetTableName, plan.targetTable, and(eq(plan.foreignCol, key), plan.ownCondition?.()))), op, "update");
6998
7107
  return {
6999
7108
  enabled: (tableName) => !!plans[tableName],
7000
7109
  split: (tableName, values) => {
@@ -7021,8 +7130,24 @@ const createNestedWriteRuntime = (params) => {
7021
7130
  const plan = tablePlans[relationName];
7022
7131
  if (plan?.fkSide === "child" && !(plan.localColPropName in columns) && allColumns[plan.localColPropName]) columns[plan.localColPropName] = allColumns[plan.localColPropName];
7023
7132
  }
7133
+ for (const name of selfTargetingPlan(tablePlans, tableName, ops)?.targetPrimaryKey ?? []) if (!(name in columns) && allColumns[name]) columns[name] = allColumns[name];
7024
7134
  return columns;
7025
7135
  },
7136
+ createsParentSide: (tableName, ops) => Object.entries(ops).some(([relationName, op]) => plans[tableName]?.[relationName]?.fkSide === "parent" && supplied(op?.["create"])),
7137
+ refreshed: async (executor, tableName, ops, rows) => {
7138
+ const plan = selfTargetingPlan(plans[tableName], tableName, ops);
7139
+ const pk = plan?.targetPrimaryKey ?? [];
7140
+ if (!plan || !pk.length || !rows.length || !pk.every((name) => name in rows[0])) return rows;
7141
+ const columns = getColumns(plan.targetTable);
7142
+ const selection = Object.fromEntries(Object.keys(rows[0]).filter((name) => columns[name]).map((name) => [name, columns[name]]));
7143
+ const keyOf = (row) => pk.map((name) => String(row[name])).join("\0");
7144
+ const match = pk.length === 1 ? inArray(columns[pk[0]], rows.map((row) => row[pk[0]])) : or(...rows.map((row) => and(...pk.map((name) => eq(columns[name], row[name])))));
7145
+ const fresh = new Map((await executor.select(selection).from(plan.targetTable).where(match)).map((row) => [keyOf(row), row]));
7146
+ return rows.map((row) => ({
7147
+ ...row,
7148
+ ...fresh.get(keyOf(row))
7149
+ }));
7150
+ },
7026
7151
  applyParentSide: async (executor, tableName, ops, request) => {
7027
7152
  const tablePlans = plans[tableName];
7028
7153
  const patch = {};
@@ -7047,10 +7172,11 @@ const createNestedWriteRuntime = (params) => {
7047
7172
  patch[plan.localColPropName] = created[plan.foreignColPropName];
7048
7173
  } else if (op["connect"] !== void 0) {
7049
7174
  const rows = await executor.select({ [plan.foreignColPropName]: plan.foreignCol }).from(plan.targetTable).where(conditionOf(plan, op["connect"], "connect", scope));
7050
- if (rows.length !== 1) throw drizzleError(`Drizzle-GraphQL Error: 'connect' on '${relationName}' matched ${rows.length} rows — it attaches a single row, so its filter must match exactly one.`, { code: "DRIZZLE_NESTED_WRITE_INVALID" });
7175
+ if (rows.length !== 1) throw connectMatchError(relationName, rows.length);
7051
7176
  patch[plan.localColPropName] = rows[0][plan.foreignColPropName];
7052
7177
  } else if (op["disconnect"] === true) patch[plan.localColPropName] = null;
7053
7178
  }
7179
+ for (const columnName of Object.keys(contextValues?.(tableName) ?? {})) delete patch[columnName];
7054
7180
  return patch;
7055
7181
  },
7056
7182
  applyChildSide: async (executor, tableName, ops, parentRows, request) => {
@@ -7068,8 +7194,9 @@ const createNestedWriteRuntime = (params) => {
7068
7194
  for (const parentRow of parentRows) {
7069
7195
  const key = parentRow?.[plan.localColPropName];
7070
7196
  if (key === void 0 || key === null) throw drizzleError(`Drizzle-GraphQL Error: cannot write through '${relationName}': the ${tableName} row has no '${plan.localColPropName}' value to attach to.`, { code: "DRIZZLE_NESTED_WRITE_INVALID" });
7197
+ if (plan.parentShows && !await plan.parentShows(executor, key)) throw drizzleError(`Drizzle-GraphQL Error: cannot write through '${relationName}': its \`where\` hides the relation on this ${tableName} row.`, { code: "DRIZZLE_NESTED_WRITE_INVALID" });
7071
7198
  if (plan.through) {
7072
- await applyThroughOps(executor, plan, key, op, scope, steps);
7199
+ await applyThroughOps(executor, plan, key, op, scope, steps, context);
7073
7200
  continue;
7074
7201
  }
7075
7202
  if (op["set"] !== void 0) {
@@ -7081,14 +7208,18 @@ const createNestedWriteRuntime = (params) => {
7081
7208
  if (op["disconnect"] === true) await detachAll(executor, plan, key, scope, steps, "disconnect");
7082
7209
  } else for (const filter of asArray(op["disconnect"])) await steps.run(executor.update(plan.targetTable).set({ [plan.foreignColPropName]: null }).where(and(eq(plan.foreignCol, key), conditionOf(plan, filter, "disconnect", scope))), "disconnect", "update");
7083
7210
  }
7211
+ if (plan.isOne && op["connect"] !== void 0) {
7212
+ const matched = await executor.select({ [plan.foreignColPropName]: plan.foreignCol }).from(plan.targetTable).where(conditionOf(plan, op["connect"], "connect", scope));
7213
+ if (matched.length !== 1) throw connectMatchError(relationName, matched.length);
7214
+ }
7084
7215
  const attachedBy = attachingOp(op);
7085
7216
  if (plan.isOne && attachedBy && plan.canDetach && op["set"] === void 0) await detachAll(executor, plan, key, scope, steps, attachedBy);
7086
7217
  if (op["connect"] !== void 0) for (const filter of asArray(op["connect"])) await steps.run(executor.update(plan.targetTable).set({ [plan.foreignColPropName]: key }).where(conditionOf(plan, filter, "connect", scope)), "connect", "update");
7087
7218
  if (op["create"] !== void 0) {
7088
- const rows = asArray(op["create"]).map((row) => ({
7089
- ...applyContextValues(remapFromGraphQLSingleInput({ ...row }, plan.targetTable), contextValues?.(plan.targetTableName), context),
7219
+ const rows = asArray(op["create"]).map((row) => applyContextValues({
7220
+ ...remapFromGraphQLSingleInput({ ...row }, plan.targetTable),
7090
7221
  [plan.foreignColPropName]: key
7091
- }));
7222
+ }, contextValues?.(plan.targetTableName), context));
7092
7223
  if (rows.length) await steps.run(executor.insert(plan.targetTable).values(rows), "create", "insert");
7093
7224
  }
7094
7225
  }
@@ -7097,6 +7228,39 @@ const createNestedWriteRuntime = (params) => {
7097
7228
  }
7098
7229
  };
7099
7230
  };
7231
+ /** Thrown inside an entry's savepoint to undo a parent-side `create` whose parent was never written. */
7232
+ var NoParentRow = class extends Error {};
7233
+ /**
7234
+ * One parent write with its nested operations around it: the parent-side operations, whose
7235
+ * values `write` receives as a patch, then the write, then the child-side operations — and
7236
+ * the rows it returns read back when a child-side statement may have changed them.
7237
+ *
7238
+ * A parent write can come back empty: an update whose `where` matched nothing, an insert or
7239
+ * upsert that a conflict turned into a no-op. Anything a parent-side `create` inserted would
7240
+ * then be left with nothing pointing at it, so such an entry runs in a savepoint of its own
7241
+ * that is rolled back when the write produced no row, and the entry returns no rows — as it
7242
+ * would have without the nested operation. The rest of the batch is unaffected.
7243
+ *
7244
+ * `executor` must already be a transaction.
7245
+ */
7246
+ const writeNestedEntry = async (params) => {
7247
+ const { executor, runtime, tableName, ops, write, request } = params;
7248
+ const guarded = runtime.createsParentSide(tableName, ops);
7249
+ const run = async (tx) => {
7250
+ const patch = await runtime.applyParentSide(tx, tableName, ops, request);
7251
+ const rows = await write(tx, patch);
7252
+ if (guarded && !rows.length) throw new NoParentRow();
7253
+ await runtime.applyChildSide(tx, tableName, ops, rows, request);
7254
+ return runtime.refreshed(tx, tableName, ops, rows);
7255
+ };
7256
+ if (!guarded) return run(executor);
7257
+ try {
7258
+ return await executor.transaction(run);
7259
+ } catch (error) {
7260
+ if (error instanceof NoParentRow) return [];
7261
+ throw error;
7262
+ }
7263
+ };
7100
7264
  /**
7101
7265
  * Runs one parent write per input entry, with that entry's nested operations around it, all
7102
7266
  * inside a single transaction — a savepoint when the caller already runs on one, so a failing
@@ -7115,12 +7279,17 @@ const writeWithNestedOps = async (params) => {
7115
7279
  return executor.transaction(async (tx) => {
7116
7280
  const rows = [];
7117
7281
  for (const entry of entries) {
7118
- const patch = await runtime.applyParentSide(tx, tableName, entry.ops, request);
7119
- const written = await write(tx, {
7120
- ...remapValues(entry.columns),
7121
- ...patch
7282
+ const written = await writeNestedEntry({
7283
+ executor: tx,
7284
+ runtime,
7285
+ tableName,
7286
+ ops: entry.ops,
7287
+ write: (target, patch) => write(target, {
7288
+ ...remapValues(entry.columns),
7289
+ ...patch
7290
+ }),
7291
+ request
7122
7292
  });
7123
- await runtime.applyChildSide(tx, tableName, entry.ops, written, request);
7124
7293
  rows.push(...written);
7125
7294
  }
7126
7295
  return rows;
@@ -7133,15 +7302,17 @@ const writeWithNestedOps = async (params) => {
7133
7302
  */
7134
7303
  const updateWithNestedOps = async (params) => {
7135
7304
  const { executor, runtime, tableName, columns, ops, remapValues, write, request } = params;
7136
- return executor.transaction(async (tx) => {
7137
- const patch = await runtime.applyParentSide(tx, tableName, ops, request);
7138
- const rows = await write(tx, {
7305
+ return executor.transaction((tx) => writeNestedEntry({
7306
+ executor: tx,
7307
+ runtime,
7308
+ tableName,
7309
+ ops,
7310
+ write: (target, patch) => write(target, {
7139
7311
  ...remapValues(columns),
7140
7312
  ...patch
7141
- });
7142
- await runtime.applyChildSide(tx, tableName, ops, rows, request);
7143
- return rows;
7144
- });
7313
+ }),
7314
+ request
7315
+ }));
7145
7316
  };
7146
7317
  //#endregion
7147
7318
  //#region src/util/builders/errors.ts
@@ -7348,6 +7519,10 @@ const createSchemaBuilder = (adapter) => {
7348
7519
  console.warn(`Drizzle-GraphQL Warning: config.features — nestedWrites leaves out the nested inputs whose table has that write turned off: ${listed}.`);
7349
7520
  }
7350
7521
  }
7522
+ if (nestedPlans) {
7523
+ const omitted = dropContextValueRelations(nestedPlans, contextValuesOf);
7524
+ if (omitted.length) console.warn(`Drizzle-GraphQL Warning: config.contextValues — nestedWrites leaves out the relations whose foreign key the server stamps from context: ${omitted.join(", ")}.`);
7525
+ }
7351
7526
  const nestedTypes = nestedPlans ? createNestedWriteTypes({
7352
7527
  plans: nestedPlans,
7353
7528
  cacheCtx,
@@ -8661,16 +8836,24 @@ const createSchemaDataGenerator = (adapter) => {
8661
8836
  const runUpdateManyBatch = ({ executor, entries, table, returning, nested, tableName, request }) => executor.transaction(async (tx) => {
8662
8837
  const results = [];
8663
8838
  for (const entry of entries) {
8664
- const values = entry.ops ? {
8665
- ...entry.set,
8666
- ...await nested.applyParentSide(tx, tableName, entry.ops, request)
8667
- } : entry.set;
8668
- const writes = Object.keys(values).length > 0;
8669
- let query = writes ? tx.update(table).set(values) : tx.select(returning).from(table);
8670
- if (entry.filters) query = query.where(entry.filters);
8671
- const rows = await (writes ? query.returning(returning) : query);
8672
- if (entry.ops) await nested.applyChildSide(tx, tableName, entry.ops, rows, request);
8673
- results.push(rows);
8839
+ const write = async (target, patch) => {
8840
+ const values = {
8841
+ ...entry.set,
8842
+ ...patch
8843
+ };
8844
+ const writes = Object.keys(values).length > 0;
8845
+ let query = writes ? target.update(table).set(values) : target.select(returning).from(table);
8846
+ if (entry.filters) query = query.where(entry.filters);
8847
+ return await (writes ? query.returning(returning) : query);
8848
+ };
8849
+ results.push(entry.ops ? await writeNestedEntry({
8850
+ executor: tx,
8851
+ runtime: nested,
8852
+ tableName,
8853
+ ops: entry.ops,
8854
+ write,
8855
+ request
8856
+ }) : await write(tx, {}));
8674
8857
  }
8675
8858
  return results;
8676
8859
  });
@@ -8802,16 +8985,24 @@ const runSqliteUpdateManyBatch = ({ executor, entries, table, returning, anyNest
8802
8985
  return query.returning(returning).all();
8803
8986
  };
8804
8987
  const runNestedEntry = async (tx, entry) => {
8805
- const values = entry.ops ? {
8806
- ...entry.set,
8807
- ...await nested.applyParentSide(tx, tableName, entry.ops, request)
8808
- } : entry.set;
8809
- const writes = Object.keys(values).length > 0;
8810
- let query = writes ? tx.update(table).set(values) : tx.select(returning).from(table);
8811
- if (entry.filters) query = query.where(entry.filters);
8812
- const rows = await (writes ? query.returning(returning) : query);
8813
- if (entry.ops) await nested.applyChildSide(tx, tableName, entry.ops, rows, request);
8814
- return rows;
8988
+ const write = async (target, patch) => {
8989
+ const values = {
8990
+ ...entry.set,
8991
+ ...patch
8992
+ };
8993
+ const writes = Object.keys(values).length > 0;
8994
+ let query = writes ? target.update(table).set(values) : target.select(returning).from(table);
8995
+ if (entry.filters) query = query.where(entry.filters);
8996
+ return await (writes ? query.returning(returning) : query);
8997
+ };
8998
+ return entry.ops ? writeNestedEntry({
8999
+ executor: tx,
9000
+ runtime: nested,
9001
+ tableName,
9002
+ ops: entry.ops,
9003
+ write,
9004
+ request
9005
+ }) : write(tx, {});
8815
9006
  };
8816
9007
  return executor.transaction((tx) => {
8817
9008
  if (anyNested) return (async () => {