@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/README.md +50 -11
- package/dist/index.cjs +292 -101
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -2
- package/dist/index.d.ts +16 -2
- package/dist/index.js +292 -101
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
* `
|
|
1830
|
-
* the parent key for each row — the target's foreign key, or the junction's source
|
|
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,
|
|
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`${
|
|
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
|
|
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
|
|
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
|
|
4892
|
-
if (!
|
|
4893
|
-
const
|
|
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
|
|
4896
|
-
const
|
|
4897
|
-
const ownCondition = relationOwnCondition(relEntry, parentTable, targetTable,
|
|
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
|
|
4939
|
-
if (
|
|
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 (
|
|
5028
|
+
return getOrCreateLoader(context, loaderKey, async (parentKeys) => {
|
|
4981
5029
|
const executor = resolveExecutor(db, context);
|
|
4982
|
-
const
|
|
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(
|
|
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:
|
|
5042
|
+
partitionBy: groupCols,
|
|
4995
5043
|
junction: junction ? {
|
|
4996
5044
|
table: junction.table,
|
|
4997
5045
|
on: eq(junction.targetCol, foreignCol),
|
|
4998
|
-
parentKey:
|
|
5046
|
+
parentKey: junction.sourceCol
|
|
4999
5047
|
} : void 0
|
|
5000
5048
|
});
|
|
5001
|
-
if (!keys.length) return
|
|
5002
|
-
whereCondition = and(whereCondition, junction ? or(...keys.map((key) => and(eq(
|
|
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]:
|
|
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 =
|
|
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
|
|
5073
|
+
return parentKeys.map((key) => byKey.get(joinKeyOf(key)) ?? null);
|
|
5026
5074
|
}
|
|
5027
|
-
const grouped = new Map(
|
|
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
|
|
5031
|
-
}).load(
|
|
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
|
|
6108
|
-
if (!
|
|
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
|
|
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
|
|
6120
|
-
const
|
|
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
|
|
6145
|
-
if (
|
|
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 (
|
|
6198
|
+
const loader = getOrCreateLoader(context, loaderKey, async (parentKeys) => {
|
|
6149
6199
|
const executor = resolveExecutor(db, context);
|
|
6150
|
-
const
|
|
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(
|
|
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
|
-
|
|
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(
|
|
6158
|
-
const byKey = new Map(rows.map((row) => [row[
|
|
6159
|
-
return
|
|
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(
|
|
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;
|
|
6544
|
-
*
|
|
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
|
|
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
|
-
|
|
6617
|
-
|
|
6618
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
...
|
|
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
|
|
7119
|
-
|
|
7120
|
-
|
|
7121
|
-
|
|
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(
|
|
7137
|
-
|
|
7138
|
-
|
|
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
|
-
|
|
7143
|
-
|
|
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
|
|
8665
|
-
|
|
8666
|
-
|
|
8667
|
-
|
|
8668
|
-
|
|
8669
|
-
|
|
8670
|
-
|
|
8671
|
-
|
|
8672
|
-
|
|
8673
|
-
|
|
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
|
|
8806
|
-
|
|
8807
|
-
|
|
8808
|
-
|
|
8809
|
-
|
|
8810
|
-
|
|
8811
|
-
|
|
8812
|
-
|
|
8813
|
-
|
|
8814
|
-
|
|
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 () => {
|