@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.cjs
CHANGED
|
@@ -1802,6 +1802,11 @@ const warnUnresolvedJoin = (relEntry, tableName, relationName, omitted) => {
|
|
|
1802
1802
|
* Returns the JS property name of the local column on the parent table and the
|
|
1803
1803
|
* Column object for the foreign column on the target table, or undefined if the
|
|
1804
1804
|
* relation internals are not accessible.
|
|
1805
|
+
*
|
|
1806
|
+
* Only the relation's *first* column pair is reported. A relation joined on several columns
|
|
1807
|
+
* (`from: [a, b], to: [x, y]`) is not described fully by it, so anything that reads rows by
|
|
1808
|
+
* this pair alone mixes up parents that share the first column. Read paths use
|
|
1809
|
+
* {@link extractRelationJoinColumnPairs}, which returns every pair.
|
|
1805
1810
|
*/
|
|
1806
1811
|
const extractRelationJoinColumns = (relEntry, parentTable, targetTable) => {
|
|
1807
1812
|
const rel = relEntry.relation ?? relEntry;
|
|
@@ -1820,6 +1825,48 @@ const extractRelationJoinColumns = (relEntry, parentTable, targetTable) => {
|
|
|
1820
1825
|
};
|
|
1821
1826
|
};
|
|
1822
1827
|
/**
|
|
1828
|
+
* Every column pair the relation joins on, in declaration order — one for an ordinary relation,
|
|
1829
|
+
* several for one declared `from: [a, b], to: [x, y]`. A row of the target belongs to a parent
|
|
1830
|
+
* only when *all* pairs match, so a read path has to correlate on every one of them.
|
|
1831
|
+
*
|
|
1832
|
+
* `undefined` when the relation's columns cannot be matched to the two tables, or when its two
|
|
1833
|
+
* sides list a different number of columns.
|
|
1834
|
+
*/
|
|
1835
|
+
const extractRelationJoinColumnPairs = (relEntry, parentTable, targetTable) => {
|
|
1836
|
+
const rel = relEntry.relation ?? relEntry;
|
|
1837
|
+
const sourceCols = relationColumns(rel, "source");
|
|
1838
|
+
const targetCols = relationColumns(rel, "target");
|
|
1839
|
+
if (!sourceCols?.length || sourceCols.length !== targetCols?.length) return;
|
|
1840
|
+
const parentEntries = Object.entries((0, drizzle_orm.getColumns)(parentTable));
|
|
1841
|
+
const targetEntries = Object.entries((0, drizzle_orm.getColumns)(targetTable));
|
|
1842
|
+
const pairs = [];
|
|
1843
|
+
for (let i = 0; i < sourceCols.length; i++) {
|
|
1844
|
+
const localCol = sourceCols[i];
|
|
1845
|
+
const foreignCol = targetCols[i];
|
|
1846
|
+
const localColPropName = parentEntries.find(([, c]) => c === localCol)?.[0];
|
|
1847
|
+
const foreignColPropName = targetEntries.find(([, c]) => c === foreignCol)?.[0];
|
|
1848
|
+
if (!localColPropName || !foreignColPropName) return;
|
|
1849
|
+
pairs.push({
|
|
1850
|
+
localColPropName,
|
|
1851
|
+
localCol,
|
|
1852
|
+
foreignColPropName,
|
|
1853
|
+
foreignCol
|
|
1854
|
+
});
|
|
1855
|
+
}
|
|
1856
|
+
return pairs;
|
|
1857
|
+
};
|
|
1858
|
+
/**
|
|
1859
|
+
* A string identifying one combination of join-column values, for grouping a batch's rows by
|
|
1860
|
+
* parent. Values are compared by their string form, as a single-column key always has been.
|
|
1861
|
+
*/
|
|
1862
|
+
const joinKeyOf = (values) => values.length === 1 ? String(values[0]) : JSON.stringify(values.map((value) => String(value)));
|
|
1863
|
+
/**
|
|
1864
|
+
* The rows whose `cols` hold one of `keys`: `col IN (…)` for a single join column, and an `OR`
|
|
1865
|
+
* of per-parent equalities for several — `(a = 1 AND b = 1) OR (a = 1 AND b = 2)` — which
|
|
1866
|
+
* every dialect accepts, unlike a row-value `IN` list.
|
|
1867
|
+
*/
|
|
1868
|
+
const parentKeyCondition = (cols, keys) => cols.length === 1 ? (0, drizzle_orm.inArray)(cols[0], keys.map((key) => key[0])) : (0, drizzle_orm.or)(...keys.map((key) => (0, drizzle_orm.and)(...cols.map((col, i) => (0, drizzle_orm.eq)(col, key[i])))));
|
|
1869
|
+
/**
|
|
1823
1870
|
* The junction table and its two columns for a `.through()` (many-to-many) relation:
|
|
1824
1871
|
* `sourceCol` holds the parent's key, `targetCol` the target's. `undefined` for a relation
|
|
1825
1872
|
* with no junction; `null` for one whose junction cannot be used here — more than one column
|
|
@@ -1850,16 +1897,18 @@ const extractJunctionColumns = (relEntry) => {
|
|
|
1850
1897
|
*
|
|
1851
1898
|
* A reversed filter (inherited from the relation on the other side) is written against the
|
|
1852
1899
|
* parent table, so it is applied as "the row's parent key belongs to a parent that passes it".
|
|
1853
|
-
* `
|
|
1854
|
-
* the parent key for each row — the target's foreign key, or the junction's source
|
|
1900
|
+
* `parentKeyCols` are the parent's join columns and `groupCols` the columns in the query that
|
|
1901
|
+
* hold the parent key for each row — the target's foreign key, or the junction's source
|
|
1902
|
+
* column — in the same order. A key of several columns is compared as a row value.
|
|
1855
1903
|
*/
|
|
1856
|
-
const relationOwnCondition = (relEntry, parentTable, targetTable,
|
|
1904
|
+
const relationOwnCondition = (relEntry, parentTable, targetTable, parentKeyCols, groupCols) => {
|
|
1857
1905
|
const rel = relEntry.relation ?? relEntry;
|
|
1858
1906
|
const ownWhere = relationOwnWhere(rel);
|
|
1859
1907
|
if (!ownWhere) return;
|
|
1860
1908
|
if (!isRelationFilterReversed(rel)) return (0, drizzle_orm.relationsFilterToSQL)(targetTable, ownWhere);
|
|
1861
1909
|
const parentFilter = (0, drizzle_orm.relationsFilterToSQL)(parentTable, ownWhere);
|
|
1862
|
-
return drizzle_orm.sql`${
|
|
1910
|
+
if (groupCols.length === 1) return drizzle_orm.sql`${groupCols[0]} in (select ${parentKeyCols[0]} from ${parentTable} where ${parentFilter})`;
|
|
1911
|
+
return drizzle_orm.sql`(${drizzle_orm.sql.join([...groupCols], drizzle_orm.sql`, `)}) in (select ${drizzle_orm.sql.join([...parentKeyCols], drizzle_orm.sql`, `)} from ${parentTable} where ${parentFilter})`;
|
|
1863
1912
|
};
|
|
1864
1913
|
/**
|
|
1865
1914
|
* Whether a to-one relation can back a relation orderBy hop. `.through()` (junction)
|
|
@@ -3845,8 +3894,7 @@ const relationJoinColumns = (tree, table, selectionCtx) => {
|
|
|
3845
3894
|
const relEntry = fieldData.name.endsWith(AGGREGATE_FIELD_SUFFIX) ? relations[fieldData.name.slice(0, -9)] : relations[fieldData.name];
|
|
3846
3895
|
const targetTable = relEntry ? selectionCtx.tables[relEntry.targetTableName] : void 0;
|
|
3847
3896
|
if (!relEntry || !targetTable) continue;
|
|
3848
|
-
const
|
|
3849
|
-
if (joinCols) needed.push(joinCols.localColPropName);
|
|
3897
|
+
for (const pair of extractRelationJoinColumnPairs(relEntry, table, targetTable) ?? []) needed.push(pair.localColPropName);
|
|
3850
3898
|
}
|
|
3851
3899
|
return needed;
|
|
3852
3900
|
};
|
|
@@ -4867,13 +4915,12 @@ const identifyRows = (field, args) => {
|
|
|
4867
4915
|
* Window functions require PostgreSQL, MySQL >= 8.0, or SQLite >= 3.25.
|
|
4868
4916
|
* Returns raw rows (NOT remapped); the caller groups + remaps them.
|
|
4869
4917
|
*/
|
|
4870
|
-
const batchedPaginatedRelationQuery = async (db, targetTable, foreignCol, whereCondition, orderByArg, limit, offset, pkNames, columns, orderCtx, whereArgs, junction) => {
|
|
4918
|
+
const batchedPaginatedRelationQuery = async (db, targetTable, foreignCol, partitionCols, whereCondition, orderByArg, limit, offset, pkNames, columns, orderCtx, whereArgs, junction) => {
|
|
4871
4919
|
const cols = columns ?? (0, drizzle_orm.getColumns)(targetTable);
|
|
4872
4920
|
const orderExprs = [...orderByArg ? extractOrderBy(targetTable, orderByArg, orderCtx, whereArgs) : [], ...primaryKeyOrderExprs(targetTable, pkNames)];
|
|
4873
4921
|
const orderClause = orderExprs.length ? drizzle_orm.sql` order by ${drizzle_orm.sql.join(orderExprs, drizzle_orm.sql`, `)}` : drizzle_orm.sql``;
|
|
4874
4922
|
const RN = "__drizzle_graphql_rn";
|
|
4875
|
-
const
|
|
4876
|
-
const rowNumber = drizzle_orm.sql`row_number() over (partition by ${partitionCol}${orderClause})`.as(RN);
|
|
4923
|
+
const rowNumber = drizzle_orm.sql`row_number() over (partition by ${drizzle_orm.sql.join([...partitionCols], drizzle_orm.sql`, `)}${orderClause})`.as(RN);
|
|
4877
4924
|
const sub = withJunction(db.select({
|
|
4878
4925
|
...cols,
|
|
4879
4926
|
[RN]: rowNumber
|
|
@@ -4912,13 +4959,14 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
4912
4959
|
const targetTableName = relEntry.targetTableName;
|
|
4913
4960
|
const targetTable = tables[targetTableName];
|
|
4914
4961
|
if (!parentTable || !targetTable) return;
|
|
4915
|
-
const
|
|
4916
|
-
if (!
|
|
4917
|
-
const
|
|
4962
|
+
const joinPairs = extractRelationJoinColumnPairs(relEntry, parentTable, targetTable);
|
|
4963
|
+
if (!joinPairs) return;
|
|
4964
|
+
const localColPropNames = joinPairs.map((pair) => pair.localColPropName);
|
|
4965
|
+
const foreignCol = joinPairs[0].foreignCol;
|
|
4918
4966
|
const junction = extractJunctionColumns(relEntry);
|
|
4919
|
-
const
|
|
4920
|
-
const
|
|
4921
|
-
const ownCondition = relationOwnCondition(relEntry, parentTable, targetTable, (
|
|
4967
|
+
const groupCols = junction ? [junction.sourceCol] : joinPairs.map((pair) => pair.foreignCol);
|
|
4968
|
+
const groupKeys = junction ? [PARENT_KEY] : joinPairs.map((pair) => pair.foreignColPropName);
|
|
4969
|
+
const ownCondition = relationOwnCondition(relEntry, parentTable, targetTable, joinPairs.map((pair) => pair.localCol), groupCols);
|
|
4922
4970
|
const defaultDeleted = relationDeletedDefault(policies?.softDelete, targetTableName, isOne && relEntry.relation?.optional === false);
|
|
4923
4971
|
const limitPolicy = isOne ? void 0 : limits?.(targetTableName);
|
|
4924
4972
|
const targetPkNames = relEntry.targetPkNames ?? [];
|
|
@@ -4947,7 +4995,7 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
4947
4995
|
allRelations: filterCtx?.relationMap
|
|
4948
4996
|
});
|
|
4949
4997
|
const targetColumns = (0, drizzle_orm.getColumns)(targetTable);
|
|
4950
|
-
columns[foreignColPropName] = foreignCol;
|
|
4998
|
+
for (const pair of joinPairs) columns[pair.foreignColPropName] = pair.foreignCol;
|
|
4951
4999
|
for (const [columnName] of cursorEntries ?? []) {
|
|
4952
5000
|
const column = targetColumns[columnName];
|
|
4953
5001
|
if (column) columns[columnName] = column;
|
|
@@ -4959,8 +5007,8 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
4959
5007
|
};
|
|
4960
5008
|
const resolve = async (parent, args, context, info) => {
|
|
4961
5009
|
if (parent[relationName] !== void 0) return parent[relationName];
|
|
4962
|
-
const
|
|
4963
|
-
if (
|
|
5010
|
+
const localValues = localColPropNames.map((name) => parent[name]);
|
|
5011
|
+
if (localValues.some((value) => value == null)) return isOne ? null : [];
|
|
4964
5012
|
const { where: whereArg, limit: requestedLimit, offset, after, deleted } = args ?? {};
|
|
4965
5013
|
const { orderByArg, limit, distinct, cursorEntries, cursorValues, columns, loaderKey } = getOrCreateRequestValue(context, info?.fieldNodes?.[0], `relation:${tableName}::${relationName}`, () => {
|
|
4966
5014
|
const orderByArg = isOne ? args?.orderBy : withDefaultOrderBy(args ?? {}, targetTableName, policies?.defaultOrderBy).orderBy;
|
|
@@ -5001,11 +5049,11 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
5001
5049
|
};
|
|
5002
5050
|
});
|
|
5003
5051
|
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" });
|
|
5004
|
-
return getOrCreateLoader(context, loaderKey, async (
|
|
5052
|
+
return getOrCreateLoader(context, loaderKey, async (parentKeys) => {
|
|
5005
5053
|
const executor = resolveExecutor(db, context);
|
|
5006
|
-
const
|
|
5054
|
+
const uniqueKeys = [...new Map(parentKeys.map((key) => [joinKeyOf(key), key])).values()];
|
|
5007
5055
|
const scope = resolveScope(policies, context, filterCtx);
|
|
5008
|
-
let whereCondition = withScope(scope, targetTableName, targetTable, (0, drizzle_orm.and)((
|
|
5056
|
+
let whereCondition = withScope(scope, targetTableName, targetTable, (0, drizzle_orm.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);
|
|
5009
5057
|
if (distinct) {
|
|
5010
5058
|
const keys = await selectDistinctKeys({
|
|
5011
5059
|
db: executor,
|
|
@@ -5015,22 +5063,22 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
5015
5063
|
pkNames: targetPkNames,
|
|
5016
5064
|
where: whereCondition,
|
|
5017
5065
|
orderBy: orderByArg,
|
|
5018
|
-
partitionBy:
|
|
5066
|
+
partitionBy: groupCols,
|
|
5019
5067
|
junction: junction ? {
|
|
5020
5068
|
table: junction.table,
|
|
5021
5069
|
on: (0, drizzle_orm.eq)(junction.targetCol, foreignCol),
|
|
5022
|
-
parentKey:
|
|
5070
|
+
parentKey: junction.sourceCol
|
|
5023
5071
|
} : void 0
|
|
5024
5072
|
});
|
|
5025
|
-
if (!keys.length) return
|
|
5026
|
-
whereCondition = (0, drizzle_orm.and)(whereCondition, junction ? (0, drizzle_orm.or)(...keys.map((key) => (0, drizzle_orm.and)((0, drizzle_orm.eq)(
|
|
5073
|
+
if (!keys.length) return parentKeys.map(() => isOne ? null : []);
|
|
5074
|
+
whereCondition = (0, drizzle_orm.and)(whereCondition, junction ? (0, drizzle_orm.or)(...keys.map((key) => (0, drizzle_orm.and)((0, drizzle_orm.eq)(junction.sourceCol, key[DISTINCT_PARENT]), primaryKeyRestriction(targetTable, targetPkNames, [key])))) : primaryKeyRestriction(targetTable, targetPkNames, keys));
|
|
5027
5075
|
}
|
|
5028
5076
|
const selection = junction ? {
|
|
5029
5077
|
...columns ?? (0, drizzle_orm.getColumns)(targetTable),
|
|
5030
|
-
[PARENT_KEY]:
|
|
5078
|
+
[PARENT_KEY]: junction.sourceCol
|
|
5031
5079
|
} : columns;
|
|
5032
5080
|
let rows;
|
|
5033
|
-
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);
|
|
5081
|
+
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);
|
|
5034
5082
|
else {
|
|
5035
5083
|
let q = withJunction((selection ? executor.select(selection) : executor.select()).from(targetTable), junction, foreignCol).where(whereCondition);
|
|
5036
5084
|
const orderExprs = cursorEntries ? cursorOrderExprs(targetTable, cursorEntries) : orderByArg ? extractOrderBy(targetTable, orderByArg, relationFilterCtx(filterCtx, targetTableName, scope), whereArg) : [];
|
|
@@ -5039,20 +5087,20 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
5039
5087
|
}
|
|
5040
5088
|
if (cursorEntries) attachRowCursors(rows, cursorEntries);
|
|
5041
5089
|
const keyOf = (row) => {
|
|
5042
|
-
const key =
|
|
5090
|
+
const key = joinKeyOf(groupKeys.map((name) => row[name]));
|
|
5043
5091
|
if (junction) delete row[PARENT_KEY];
|
|
5044
5092
|
return key;
|
|
5045
5093
|
};
|
|
5046
5094
|
if (isOne) {
|
|
5047
5095
|
const byKey = new Map(rows.map((row) => [keyOf(row), row]));
|
|
5048
5096
|
remapToGraphQLArrayOutput(rows, targetTableName, targetTable);
|
|
5049
|
-
return
|
|
5097
|
+
return parentKeys.map((key) => byKey.get(joinKeyOf(key)) ?? null);
|
|
5050
5098
|
}
|
|
5051
|
-
const grouped = new Map(
|
|
5099
|
+
const grouped = new Map(uniqueKeys.map((key) => [joinKeyOf(key), []]));
|
|
5052
5100
|
for (const row of rows) grouped.get(keyOf(row))?.push(row);
|
|
5053
5101
|
remapToGraphQLArrayOutput(rows, targetTableName, targetTable);
|
|
5054
|
-
return
|
|
5055
|
-
}).load(
|
|
5102
|
+
return parentKeys.map((key) => grouped.get(joinKeyOf(key)) ?? []);
|
|
5103
|
+
}).load(localValues);
|
|
5056
5104
|
};
|
|
5057
5105
|
return async (parent, args, context, info) => {
|
|
5058
5106
|
try {
|
|
@@ -6128,8 +6176,8 @@ const createRelationAggregateFactory = (db, tables, cacheCtx, typeNameMapper, fi
|
|
|
6128
6176
|
const targetTableName = relEntry.targetTableName;
|
|
6129
6177
|
const targetTable = tables[targetTableName];
|
|
6130
6178
|
if (!parentTable || !targetTable) return;
|
|
6131
|
-
const
|
|
6132
|
-
if (!
|
|
6179
|
+
const joinPairs = extractRelationJoinColumnPairs(relEntry, parentTable, targetTable);
|
|
6180
|
+
if (!joinPairs) {
|
|
6133
6181
|
const key = `${tableName}.${relationName}`;
|
|
6134
6182
|
if (!warned.has(key)) {
|
|
6135
6183
|
warned.add(key);
|
|
@@ -6137,11 +6185,13 @@ const createRelationAggregateFactory = (db, tables, cacheCtx, typeNameMapper, fi
|
|
|
6137
6185
|
}
|
|
6138
6186
|
return;
|
|
6139
6187
|
}
|
|
6140
|
-
const
|
|
6188
|
+
const localColPropNames = joinPairs.map((pair) => pair.localColPropName);
|
|
6189
|
+
const foreignCol = joinPairs[0].foreignCol;
|
|
6141
6190
|
const junction = extractJunctionColumns(relEntry);
|
|
6142
6191
|
if (junction === null) return;
|
|
6143
|
-
const
|
|
6144
|
-
const
|
|
6192
|
+
const groupCols = junction ? [junction.sourceCol] : joinPairs.map((pair) => pair.foreignCol);
|
|
6193
|
+
const groupAliases = groupCols.map((_, i) => `${GROUP_KEY}${i}`);
|
|
6194
|
+
const ownCondition = relationOwnCondition(relEntry, parentTable, targetTable, joinPairs.map((pair) => pair.localCol), groupCols);
|
|
6145
6195
|
const defaultDeleted = relationDeletedDefault(policies?.softDelete, targetTableName, false);
|
|
6146
6196
|
const targetTypeName = resolveTypeName(targetTableName, typeNameMapper);
|
|
6147
6197
|
const type = generateAggregateTypes(targetTable, targetTableName, targetTypeName, cacheCtx);
|
|
@@ -6165,24 +6215,25 @@ const createRelationAggregateFactory = (db, tables, cacheCtx, typeNameMapper, fi
|
|
|
6165
6215
|
};
|
|
6166
6216
|
});
|
|
6167
6217
|
if (!Object.keys(request.selection).length) return {};
|
|
6168
|
-
const
|
|
6169
|
-
if (
|
|
6218
|
+
const localValues = localColPropNames.map((name) => parent[name]);
|
|
6219
|
+
if (localValues.some((value) => value == null)) return assembleAggregateRow({}, request, target);
|
|
6170
6220
|
const whereArg = args?.where;
|
|
6171
6221
|
const deleted = args?.deleted;
|
|
6172
|
-
const loader = getOrCreateLoader(context, loaderKey, async (
|
|
6222
|
+
const loader = getOrCreateLoader(context, loaderKey, async (parentKeys) => {
|
|
6173
6223
|
const executor = resolveExecutor(db, context);
|
|
6174
|
-
const
|
|
6224
|
+
const uniqueKeys = [...new Map(parentKeys.map((key) => [joinKeyOf(key), key])).values()];
|
|
6175
6225
|
const scope = resolveScope(policies, context, filterCtx);
|
|
6176
|
-
const whereCondition = withScope(scope, targetTableName, targetTable, (0, drizzle_orm.and)((
|
|
6226
|
+
const whereCondition = withScope(scope, targetTableName, targetTable, (0, drizzle_orm.and)(parentKeyCondition(groupCols, uniqueKeys), ownCondition, whereArg ? extractFilters(targetTable, targetTableName, whereArg, relationFilterCtx(filterCtx, targetTableName, scope)) : void 0), deleted ?? defaultDeleted);
|
|
6227
|
+
const groupSelection = Object.fromEntries(groupAliases.map((alias, i) => [alias, groupCols[i]]));
|
|
6177
6228
|
const selected = executor.select({
|
|
6178
|
-
|
|
6229
|
+
...groupSelection,
|
|
6179
6230
|
...request.selection
|
|
6180
6231
|
}).from(targetTable);
|
|
6181
|
-
const rows = await (junction ? selected.innerJoin(junction.table, (0, drizzle_orm.eq)(junction.targetCol, foreignCol)) : selected).where(whereCondition).groupBy(
|
|
6182
|
-
const byKey = new Map(rows.map((row) => [row[
|
|
6183
|
-
return
|
|
6232
|
+
const rows = await (junction ? selected.innerJoin(junction.table, (0, drizzle_orm.eq)(junction.targetCol, foreignCol)) : selected).where(whereCondition).groupBy(...groupCols);
|
|
6233
|
+
const byKey = new Map(rows.map((row) => [joinKeyOf(groupAliases.map((alias) => row[alias])), row]));
|
|
6234
|
+
return parentKeys.map((key) => byKey.get(joinKeyOf(key)) ?? {});
|
|
6184
6235
|
});
|
|
6185
|
-
return assembleAggregateRow(await loader.load(
|
|
6236
|
+
return assembleAggregateRow(await loader.load(localValues), request, target);
|
|
6186
6237
|
} catch (e) {
|
|
6187
6238
|
throw withErrorContext(toGraphQLError(e), errorCtx);
|
|
6188
6239
|
}
|
|
@@ -6489,6 +6540,20 @@ const ALL_OPS = [
|
|
|
6489
6540
|
* accepts NULL.
|
|
6490
6541
|
*/
|
|
6491
6542
|
const isOptionalOnInsert = (column) => !column.notNull || !!column.hasDefault || !!column.defaultFn;
|
|
6543
|
+
/**
|
|
6544
|
+
* The plan fields that carry a relation's own `where` (see {@link relationOwnCondition}).
|
|
6545
|
+
*
|
|
6546
|
+
* A `where` on the target narrows every row selection the write makes, and takes `create`
|
|
6547
|
+
* away: the row it inserts is attached whether or not it satisfies the `where`, so the
|
|
6548
|
+
* relation could come back without it. A `where` inherited from the reverse relation filters
|
|
6549
|
+
* the parent instead, and becomes a per-parent check.
|
|
6550
|
+
*/
|
|
6551
|
+
const ownWherePlan = (relEntry, parentTable, targetTable, localCol) => {
|
|
6552
|
+
const condition = () => relationOwnCondition(relEntry, parentTable, targetTable, [localCol], [localCol]);
|
|
6553
|
+
if (!condition()) return {};
|
|
6554
|
+
if (!isRelationFilterReversed(relEntry.relation ?? relEntry)) return { ownCondition: () => condition() };
|
|
6555
|
+
return { parentShows: async (executor, key) => (await executor.select({ key: localCol }).from(parentTable).where((0, drizzle_orm.and)((0, drizzle_orm.eq)(localCol, key), condition())).limit(1)).length > 0 };
|
|
6556
|
+
};
|
|
6492
6557
|
/** The property name a column is stored under on its table, matched by identity. */
|
|
6493
6558
|
const propNameOf = (table, column) => {
|
|
6494
6559
|
if (!column) return;
|
|
@@ -6509,7 +6574,7 @@ const propNameOf = (table, column) => {
|
|
|
6509
6574
|
* `onUnresolved` is called when the relation's columns are of a supported shape but cannot be
|
|
6510
6575
|
* matched to the three tables — a drizzle-orm mismatch rather than an unsupported relation.
|
|
6511
6576
|
*/
|
|
6512
|
-
const buildThroughPlan = (relationName, relation, table, targetTableName, targetTable, tableNameOf, onUnresolved) => {
|
|
6577
|
+
const buildThroughPlan = (relationName, relEntry, relation, table, targetTableName, targetTable, tableNameOf, onUnresolved) => {
|
|
6513
6578
|
if ((0, drizzle_orm.is)(relation, drizzle_orm.One)) return;
|
|
6514
6579
|
const junction = relation.throughTable;
|
|
6515
6580
|
const sourceEntries = relation.through?.source;
|
|
@@ -6550,6 +6615,7 @@ const buildThroughPlan = (relationName, relation, table, targetTableName, target
|
|
|
6550
6615
|
targetCol
|
|
6551
6616
|
},
|
|
6552
6617
|
targetKeyColumns: { [foreignColPropName]: foreignCol },
|
|
6618
|
+
...ownWherePlan(relEntry, table, targetTable, localCol),
|
|
6553
6619
|
allowed: /* @__PURE__ */ new Set([
|
|
6554
6620
|
"connect",
|
|
6555
6621
|
"disconnect",
|
|
@@ -6564,8 +6630,9 @@ const buildThroughPlan = (relationName, relation, table, targetTableName, target
|
|
|
6564
6630
|
* rather than a foreign-key one — see {@link buildThroughPlan}.
|
|
6565
6631
|
*
|
|
6566
6632
|
* Skipped, and so absent from the generated inputs rather than failing at runtime:
|
|
6567
|
-
* relations whose join columns cannot be read off the drizzle relation;
|
|
6568
|
-
*
|
|
6633
|
+
* relations whose join columns cannot be read off the drizzle relation; relations joining on
|
|
6634
|
+
* more than one column per side; and relations where neither join column is unique, which
|
|
6635
|
+
* leaves no way to tell which side stores the key.
|
|
6569
6636
|
*/
|
|
6570
6637
|
const buildNestedWritePlans = (tables, relationMap, resolveUniqueSets, resolvePrimaryKeys, extractJoinColumns, tableNameOf = () => void 0) => {
|
|
6571
6638
|
const uniqueCache = /* @__PURE__ */ new Map();
|
|
@@ -6597,10 +6664,11 @@ const buildNestedWritePlans = (tables, relationMap, resolveUniqueSets, resolvePr
|
|
|
6597
6664
|
if (!targetTable) continue;
|
|
6598
6665
|
const omitted = `the '${relationName}' field of its nested-write create and update inputs`;
|
|
6599
6666
|
if (relation.through) {
|
|
6600
|
-
const throughPlan = buildThroughPlan(relationName, relation, table, targetTableName, targetTable, tableNameOf, () => warnUnresolvedJoin(relEntry, tableName, relationName, omitted));
|
|
6667
|
+
const throughPlan = buildThroughPlan(relationName, relEntry, relation, table, targetTableName, targetTable, tableNameOf, () => warnUnresolvedJoin(relEntry, tableName, relationName, omitted));
|
|
6601
6668
|
if (throughPlan) tablePlans[relationName] = throughPlan;
|
|
6602
6669
|
continue;
|
|
6603
6670
|
}
|
|
6671
|
+
if ((relationColumns(relation, "source")?.length ?? 0) > 1 || (relationColumns(relation, "target")?.length ?? 0) > 1) continue;
|
|
6604
6672
|
const joinColumns = extractJoinColumns(relEntry, table, targetTable);
|
|
6605
6673
|
if (!joinColumns) {
|
|
6606
6674
|
warnUnresolvedJoin(relEntry, tableName, relationName, omitted);
|
|
@@ -6623,8 +6691,15 @@ const buildNestedWritePlans = (tables, relationMap, resolveUniqueSets, resolvePr
|
|
|
6623
6691
|
else continue;
|
|
6624
6692
|
const fkColumn = fkSide === "child" ? foreignCol : localCol;
|
|
6625
6693
|
const targetColumns = (0, drizzle_orm.getColumns)(targetTable);
|
|
6626
|
-
const
|
|
6694
|
+
const targetPrimaryKey = resolvePrimaryKeys(targetTable).filter((name) => targetColumns[name]);
|
|
6695
|
+
const targetKeyColumns = Object.fromEntries(targetPrimaryKey.map((name) => [name, targetColumns[name]]));
|
|
6627
6696
|
targetKeyColumns[foreignColPropName] = foreignCol;
|
|
6697
|
+
const ownWhere = ownWherePlan(relEntry, table, targetTable, localCol);
|
|
6698
|
+
const ops = !fkColumn.notNull ? isOne ? [
|
|
6699
|
+
"create",
|
|
6700
|
+
"connect",
|
|
6701
|
+
"disconnect"
|
|
6702
|
+
] : ALL_OPS : ["create", "connect"];
|
|
6628
6703
|
tablePlans[relationName] = {
|
|
6629
6704
|
relationName,
|
|
6630
6705
|
targetTableName,
|
|
@@ -6637,11 +6712,9 @@ const buildNestedWritePlans = (tables, relationMap, resolveUniqueSets, resolvePr
|
|
|
6637
6712
|
fkSide,
|
|
6638
6713
|
canDetach: !fkColumn.notNull,
|
|
6639
6714
|
targetKeyColumns,
|
|
6640
|
-
|
|
6641
|
-
|
|
6642
|
-
|
|
6643
|
-
"disconnect"
|
|
6644
|
-
] : ALL_OPS : ["create", "connect"])
|
|
6715
|
+
targetPrimaryKey,
|
|
6716
|
+
...ownWhere,
|
|
6717
|
+
allowed: new Set(ownWhere.ownCondition ? ops.filter((op) => op !== "create") : ops)
|
|
6645
6718
|
};
|
|
6646
6719
|
}
|
|
6647
6720
|
if (Object.keys(tablePlans).length) plans[tableName] = tablePlans;
|
|
@@ -6716,6 +6789,35 @@ const gateNestedWritePlans = (plans, featureOf, isExcluded) => {
|
|
|
6716
6789
|
}
|
|
6717
6790
|
return omitted;
|
|
6718
6791
|
};
|
|
6792
|
+
/**
|
|
6793
|
+
* Takes out every relation whose foreign key is a `contextValues` column of the table storing
|
|
6794
|
+
* it — the parent's own column for a parent-side relation, the target's for a child-side one,
|
|
6795
|
+
* either junction column for a many-to-many.
|
|
6796
|
+
*
|
|
6797
|
+
* Every operation on such a relation writes that column: `connect`, `disconnect` and `set`
|
|
6798
|
+
* by definition, and a child-side `create` too, since it fills the key in from the parent row
|
|
6799
|
+
* rather than from context. That column is the server's to set — an insert stamps it and an
|
|
6800
|
+
* update never touches it — so writing it from a nested input would let a client pick, or
|
|
6801
|
+
* change, the owner the server assigns. Whole relations are dropped, not single inputs: no
|
|
6802
|
+
* operation on them is left that does not write the key.
|
|
6803
|
+
*
|
|
6804
|
+
* Returns one line per relation taken out, for the build note.
|
|
6805
|
+
*/
|
|
6806
|
+
const dropContextValueRelations = (plans, contextValuesOf) => {
|
|
6807
|
+
const omitted = [];
|
|
6808
|
+
if (!contextValuesOf) return omitted;
|
|
6809
|
+
for (const [tableName, tablePlans] of Object.entries(plans)) {
|
|
6810
|
+
for (const [relationName, plan] of Object.entries(tablePlans)) {
|
|
6811
|
+
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]);
|
|
6812
|
+
if (stamped) {
|
|
6813
|
+
delete tablePlans[relationName];
|
|
6814
|
+
omitted.push(`${tableName}.${relationName} (${stamped[0]}.${stamped[1]} is a contextValues column)`);
|
|
6815
|
+
}
|
|
6816
|
+
}
|
|
6817
|
+
if (!Object.keys(tablePlans).length) delete plans[tableName];
|
|
6818
|
+
}
|
|
6819
|
+
return omitted;
|
|
6820
|
+
};
|
|
6719
6821
|
const NO_COLUMNS = /* @__PURE__ */ new Set();
|
|
6720
6822
|
/**
|
|
6721
6823
|
* Builds the nested-write input types for a schema.
|
|
@@ -6746,7 +6848,8 @@ const createNestedWriteTypes = (params) => {
|
|
|
6746
6848
|
if (cached) return cached;
|
|
6747
6849
|
const omitted = plan.fkSide === "child" ? plan.foreignColPropName : void 0;
|
|
6748
6850
|
const contextColumns = cacheCtx.contextValuesOf?.(plan.targetTableName);
|
|
6749
|
-
const
|
|
6851
|
+
const markerColumn = cacheCtx.softDeleteOf?.(plan.targetTableName)?.columnName;
|
|
6852
|
+
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)]));
|
|
6750
6853
|
const type = new graphql.GraphQLInputObjectType({
|
|
6751
6854
|
name: cacheCtx.typeName({
|
|
6752
6855
|
kind: "nestedWriteInput",
|
|
@@ -6870,6 +6973,8 @@ const createNestedWriteTypes = (params) => {
|
|
|
6870
6973
|
* join columns the whole batch's RETURNING clause needs, without inspecting each row's write.
|
|
6871
6974
|
*/
|
|
6872
6975
|
const mergedOps = (entries) => Object.assign({}, ...entries.map((entry) => entry.ops));
|
|
6976
|
+
/** A to-one `connect` whose filter did not match exactly one row. */
|
|
6977
|
+
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" });
|
|
6873
6978
|
const asArray = (value) => Array.isArray(value) ? value : value === void 0 ? [] : [value];
|
|
6874
6979
|
const supplied = (value) => value !== void 0 && value !== null;
|
|
6875
6980
|
/**
|
|
@@ -6961,6 +7066,8 @@ const registerChildSteps = (steps, plan, op) => {
|
|
|
6961
7066
|
};
|
|
6962
7067
|
/** The input that attaches a row to a to-one, which detaches whatever was attached before. */
|
|
6963
7068
|
const attachingOp = (op) => op["connect"] !== void 0 ? "connect" : op["create"] !== void 0 ? "create" : void 0;
|
|
7069
|
+
/** The child-side relation in `ops` whose target is the parent's own table, if there is one. */
|
|
7070
|
+
const selfTargetingPlan = (tablePlans, tableName, ops) => Object.keys(ops).map((relationName) => tablePlans?.[relationName]).find((plan) => plan?.fkSide === "child" && !plan.through && plan.targetTableName === tableName);
|
|
6964
7071
|
const createNestedWriteRuntime = (params) => {
|
|
6965
7072
|
const { plans, filterCtx, policies, contextValues } = params;
|
|
6966
7073
|
const onWrite = policies?.onWrite;
|
|
@@ -6971,7 +7078,7 @@ const createNestedWriteRuntime = (params) => {
|
|
|
6971
7078
|
const conditionOf = (plan, filter, operation, scope) => {
|
|
6972
7079
|
const condition = extractFilters(plan.targetTable, plan.targetTableName, filter, relationFilterCtx(filterCtx, plan.targetTableName, scope));
|
|
6973
7080
|
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" });
|
|
6974
|
-
return withScope(scope, plan.targetTableName, plan.targetTable, condition);
|
|
7081
|
+
return withScope(scope, plan.targetTableName, plan.targetTable, (0, drizzle_orm.and)(condition, plan.ownCondition?.()));
|
|
6975
7082
|
};
|
|
6976
7083
|
const assertSingleOperation = (plan, op) => {
|
|
6977
7084
|
const given = [
|
|
@@ -6994,22 +7101,24 @@ const createNestedWriteRuntime = (params) => {
|
|
|
6994
7101
|
* error on some of them — the read and the write share the surrounding transaction, so no
|
|
6995
7102
|
* other writer can slip a duplicate in between them.
|
|
6996
7103
|
*/
|
|
6997
|
-
const applyThroughOps = async (executor, plan, key, op, scope, steps) => {
|
|
7104
|
+
const applyThroughOps = async (executor, plan, key, op, scope, steps, context) => {
|
|
6998
7105
|
const through = plan.through;
|
|
6999
7106
|
const link = async (operands, operation) => {
|
|
7000
7107
|
const existing = new Set((await executor.select({ [through.targetColPropName]: through.targetCol }).from(through.table).where((0, drizzle_orm.eq)(through.sourceCol, key))).map((row) => row[through.targetColPropName]));
|
|
7001
7108
|
const rows = [];
|
|
7002
7109
|
for (const filter of operands) for (const targetKey of await selectedTargetKeys(executor, plan, filter, operation, scope)) if (!existing.has(targetKey)) {
|
|
7003
7110
|
existing.add(targetKey);
|
|
7004
|
-
rows.push({
|
|
7111
|
+
rows.push(applyContextValues({
|
|
7005
7112
|
[through.sourceColPropName]: key,
|
|
7006
7113
|
[through.targetColPropName]: targetKey
|
|
7007
|
-
});
|
|
7114
|
+
}, through.tableName ? contextValues?.(through.tableName) : void 0, context));
|
|
7008
7115
|
}
|
|
7009
7116
|
if (rows.length) await steps.run(executor.insert(through.table).values(rows), operation, "insert");
|
|
7010
7117
|
};
|
|
7011
7118
|
if (supplied(op["set"])) {
|
|
7012
|
-
|
|
7119
|
+
const reachable = withScope(scope, plan.targetTableName, plan.targetTable, plan.ownCondition?.());
|
|
7120
|
+
const unlink = reachable ? (0, drizzle_orm.and)((0, drizzle_orm.eq)(through.sourceCol, key), (0, drizzle_orm.inArray)(through.targetCol, executor.select({ [plan.foreignColPropName]: plan.foreignCol }).from(plan.targetTable).where(reachable))) : (0, drizzle_orm.eq)(through.sourceCol, key);
|
|
7121
|
+
await steps.run(executor.delete(through.table).where(unlink), "set", "delete");
|
|
7013
7122
|
await link(asArray(op["set"]), "set");
|
|
7014
7123
|
}
|
|
7015
7124
|
if (supplied(op["disconnect"])) for (const filter of asArray(op["disconnect"])) {
|
|
@@ -7018,7 +7127,7 @@ const createNestedWriteRuntime = (params) => {
|
|
|
7018
7127
|
}
|
|
7019
7128
|
if (supplied(op["connect"])) await link(asArray(op["connect"]), "connect");
|
|
7020
7129
|
};
|
|
7021
|
-
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, (0, drizzle_orm.eq)(plan.foreignCol, key))), op, "update");
|
|
7130
|
+
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, (0, drizzle_orm.and)((0, drizzle_orm.eq)(plan.foreignCol, key), plan.ownCondition?.()))), op, "update");
|
|
7022
7131
|
return {
|
|
7023
7132
|
enabled: (tableName) => !!plans[tableName],
|
|
7024
7133
|
split: (tableName, values) => {
|
|
@@ -7045,8 +7154,24 @@ const createNestedWriteRuntime = (params) => {
|
|
|
7045
7154
|
const plan = tablePlans[relationName];
|
|
7046
7155
|
if (plan?.fkSide === "child" && !(plan.localColPropName in columns) && allColumns[plan.localColPropName]) columns[plan.localColPropName] = allColumns[plan.localColPropName];
|
|
7047
7156
|
}
|
|
7157
|
+
for (const name of selfTargetingPlan(tablePlans, tableName, ops)?.targetPrimaryKey ?? []) if (!(name in columns) && allColumns[name]) columns[name] = allColumns[name];
|
|
7048
7158
|
return columns;
|
|
7049
7159
|
},
|
|
7160
|
+
createsParentSide: (tableName, ops) => Object.entries(ops).some(([relationName, op]) => plans[tableName]?.[relationName]?.fkSide === "parent" && supplied(op?.["create"])),
|
|
7161
|
+
refreshed: async (executor, tableName, ops, rows) => {
|
|
7162
|
+
const plan = selfTargetingPlan(plans[tableName], tableName, ops);
|
|
7163
|
+
const pk = plan?.targetPrimaryKey ?? [];
|
|
7164
|
+
if (!plan || !pk.length || !rows.length || !pk.every((name) => name in rows[0])) return rows;
|
|
7165
|
+
const columns = (0, drizzle_orm.getColumns)(plan.targetTable);
|
|
7166
|
+
const selection = Object.fromEntries(Object.keys(rows[0]).filter((name) => columns[name]).map((name) => [name, columns[name]]));
|
|
7167
|
+
const keyOf = (row) => pk.map((name) => String(row[name])).join("\0");
|
|
7168
|
+
const match = pk.length === 1 ? (0, drizzle_orm.inArray)(columns[pk[0]], rows.map((row) => row[pk[0]])) : (0, drizzle_orm.or)(...rows.map((row) => (0, drizzle_orm.and)(...pk.map((name) => (0, drizzle_orm.eq)(columns[name], row[name])))));
|
|
7169
|
+
const fresh = new Map((await executor.select(selection).from(plan.targetTable).where(match)).map((row) => [keyOf(row), row]));
|
|
7170
|
+
return rows.map((row) => ({
|
|
7171
|
+
...row,
|
|
7172
|
+
...fresh.get(keyOf(row))
|
|
7173
|
+
}));
|
|
7174
|
+
},
|
|
7050
7175
|
applyParentSide: async (executor, tableName, ops, request) => {
|
|
7051
7176
|
const tablePlans = plans[tableName];
|
|
7052
7177
|
const patch = {};
|
|
@@ -7071,10 +7196,11 @@ const createNestedWriteRuntime = (params) => {
|
|
|
7071
7196
|
patch[plan.localColPropName] = created[plan.foreignColPropName];
|
|
7072
7197
|
} else if (op["connect"] !== void 0) {
|
|
7073
7198
|
const rows = await executor.select({ [plan.foreignColPropName]: plan.foreignCol }).from(plan.targetTable).where(conditionOf(plan, op["connect"], "connect", scope));
|
|
7074
|
-
if (rows.length !== 1) throw
|
|
7199
|
+
if (rows.length !== 1) throw connectMatchError(relationName, rows.length);
|
|
7075
7200
|
patch[plan.localColPropName] = rows[0][plan.foreignColPropName];
|
|
7076
7201
|
} else if (op["disconnect"] === true) patch[plan.localColPropName] = null;
|
|
7077
7202
|
}
|
|
7203
|
+
for (const columnName of Object.keys(contextValues?.(tableName) ?? {})) delete patch[columnName];
|
|
7078
7204
|
return patch;
|
|
7079
7205
|
},
|
|
7080
7206
|
applyChildSide: async (executor, tableName, ops, parentRows, request) => {
|
|
@@ -7092,8 +7218,9 @@ const createNestedWriteRuntime = (params) => {
|
|
|
7092
7218
|
for (const parentRow of parentRows) {
|
|
7093
7219
|
const key = parentRow?.[plan.localColPropName];
|
|
7094
7220
|
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" });
|
|
7221
|
+
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" });
|
|
7095
7222
|
if (plan.through) {
|
|
7096
|
-
await applyThroughOps(executor, plan, key, op, scope, steps);
|
|
7223
|
+
await applyThroughOps(executor, plan, key, op, scope, steps, context);
|
|
7097
7224
|
continue;
|
|
7098
7225
|
}
|
|
7099
7226
|
if (op["set"] !== void 0) {
|
|
@@ -7105,14 +7232,18 @@ const createNestedWriteRuntime = (params) => {
|
|
|
7105
7232
|
if (op["disconnect"] === true) await detachAll(executor, plan, key, scope, steps, "disconnect");
|
|
7106
7233
|
} else for (const filter of asArray(op["disconnect"])) await steps.run(executor.update(plan.targetTable).set({ [plan.foreignColPropName]: null }).where((0, drizzle_orm.and)((0, drizzle_orm.eq)(plan.foreignCol, key), conditionOf(plan, filter, "disconnect", scope))), "disconnect", "update");
|
|
7107
7234
|
}
|
|
7235
|
+
if (plan.isOne && op["connect"] !== void 0) {
|
|
7236
|
+
const matched = await executor.select({ [plan.foreignColPropName]: plan.foreignCol }).from(plan.targetTable).where(conditionOf(plan, op["connect"], "connect", scope));
|
|
7237
|
+
if (matched.length !== 1) throw connectMatchError(relationName, matched.length);
|
|
7238
|
+
}
|
|
7108
7239
|
const attachedBy = attachingOp(op);
|
|
7109
7240
|
if (plan.isOne && attachedBy && plan.canDetach && op["set"] === void 0) await detachAll(executor, plan, key, scope, steps, attachedBy);
|
|
7110
7241
|
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");
|
|
7111
7242
|
if (op["create"] !== void 0) {
|
|
7112
|
-
const rows = asArray(op["create"]).map((row) => ({
|
|
7113
|
-
...
|
|
7243
|
+
const rows = asArray(op["create"]).map((row) => applyContextValues({
|
|
7244
|
+
...remapFromGraphQLSingleInput({ ...row }, plan.targetTable),
|
|
7114
7245
|
[plan.foreignColPropName]: key
|
|
7115
|
-
}));
|
|
7246
|
+
}, contextValues?.(plan.targetTableName), context));
|
|
7116
7247
|
if (rows.length) await steps.run(executor.insert(plan.targetTable).values(rows), "create", "insert");
|
|
7117
7248
|
}
|
|
7118
7249
|
}
|
|
@@ -7121,6 +7252,39 @@ const createNestedWriteRuntime = (params) => {
|
|
|
7121
7252
|
}
|
|
7122
7253
|
};
|
|
7123
7254
|
};
|
|
7255
|
+
/** Thrown inside an entry's savepoint to undo a parent-side `create` whose parent was never written. */
|
|
7256
|
+
var NoParentRow = class extends Error {};
|
|
7257
|
+
/**
|
|
7258
|
+
* One parent write with its nested operations around it: the parent-side operations, whose
|
|
7259
|
+
* values `write` receives as a patch, then the write, then the child-side operations — and
|
|
7260
|
+
* the rows it returns read back when a child-side statement may have changed them.
|
|
7261
|
+
*
|
|
7262
|
+
* A parent write can come back empty: an update whose `where` matched nothing, an insert or
|
|
7263
|
+
* upsert that a conflict turned into a no-op. Anything a parent-side `create` inserted would
|
|
7264
|
+
* then be left with nothing pointing at it, so such an entry runs in a savepoint of its own
|
|
7265
|
+
* that is rolled back when the write produced no row, and the entry returns no rows — as it
|
|
7266
|
+
* would have without the nested operation. The rest of the batch is unaffected.
|
|
7267
|
+
*
|
|
7268
|
+
* `executor` must already be a transaction.
|
|
7269
|
+
*/
|
|
7270
|
+
const writeNestedEntry = async (params) => {
|
|
7271
|
+
const { executor, runtime, tableName, ops, write, request } = params;
|
|
7272
|
+
const guarded = runtime.createsParentSide(tableName, ops);
|
|
7273
|
+
const run = async (tx) => {
|
|
7274
|
+
const patch = await runtime.applyParentSide(tx, tableName, ops, request);
|
|
7275
|
+
const rows = await write(tx, patch);
|
|
7276
|
+
if (guarded && !rows.length) throw new NoParentRow();
|
|
7277
|
+
await runtime.applyChildSide(tx, tableName, ops, rows, request);
|
|
7278
|
+
return runtime.refreshed(tx, tableName, ops, rows);
|
|
7279
|
+
};
|
|
7280
|
+
if (!guarded) return run(executor);
|
|
7281
|
+
try {
|
|
7282
|
+
return await executor.transaction(run);
|
|
7283
|
+
} catch (error) {
|
|
7284
|
+
if (error instanceof NoParentRow) return [];
|
|
7285
|
+
throw error;
|
|
7286
|
+
}
|
|
7287
|
+
};
|
|
7124
7288
|
/**
|
|
7125
7289
|
* Runs one parent write per input entry, with that entry's nested operations around it, all
|
|
7126
7290
|
* inside a single transaction — a savepoint when the caller already runs on one, so a failing
|
|
@@ -7139,12 +7303,17 @@ const writeWithNestedOps = async (params) => {
|
|
|
7139
7303
|
return executor.transaction(async (tx) => {
|
|
7140
7304
|
const rows = [];
|
|
7141
7305
|
for (const entry of entries) {
|
|
7142
|
-
const
|
|
7143
|
-
|
|
7144
|
-
|
|
7145
|
-
|
|
7306
|
+
const written = await writeNestedEntry({
|
|
7307
|
+
executor: tx,
|
|
7308
|
+
runtime,
|
|
7309
|
+
tableName,
|
|
7310
|
+
ops: entry.ops,
|
|
7311
|
+
write: (target, patch) => write(target, {
|
|
7312
|
+
...remapValues(entry.columns),
|
|
7313
|
+
...patch
|
|
7314
|
+
}),
|
|
7315
|
+
request
|
|
7146
7316
|
});
|
|
7147
|
-
await runtime.applyChildSide(tx, tableName, entry.ops, written, request);
|
|
7148
7317
|
rows.push(...written);
|
|
7149
7318
|
}
|
|
7150
7319
|
return rows;
|
|
@@ -7157,15 +7326,17 @@ const writeWithNestedOps = async (params) => {
|
|
|
7157
7326
|
*/
|
|
7158
7327
|
const updateWithNestedOps = async (params) => {
|
|
7159
7328
|
const { executor, runtime, tableName, columns, ops, remapValues, write, request } = params;
|
|
7160
|
-
return executor.transaction(
|
|
7161
|
-
|
|
7162
|
-
|
|
7329
|
+
return executor.transaction((tx) => writeNestedEntry({
|
|
7330
|
+
executor: tx,
|
|
7331
|
+
runtime,
|
|
7332
|
+
tableName,
|
|
7333
|
+
ops,
|
|
7334
|
+
write: (target, patch) => write(target, {
|
|
7163
7335
|
...remapValues(columns),
|
|
7164
7336
|
...patch
|
|
7165
|
-
})
|
|
7166
|
-
|
|
7167
|
-
|
|
7168
|
-
});
|
|
7337
|
+
}),
|
|
7338
|
+
request
|
|
7339
|
+
}));
|
|
7169
7340
|
};
|
|
7170
7341
|
//#endregion
|
|
7171
7342
|
//#region src/util/builders/errors.ts
|
|
@@ -7372,6 +7543,10 @@ const createSchemaBuilder = (adapter) => {
|
|
|
7372
7543
|
console.warn(`Drizzle-GraphQL Warning: config.features — nestedWrites leaves out the nested inputs whose table has that write turned off: ${listed}.`);
|
|
7373
7544
|
}
|
|
7374
7545
|
}
|
|
7546
|
+
if (nestedPlans) {
|
|
7547
|
+
const omitted = dropContextValueRelations(nestedPlans, contextValuesOf);
|
|
7548
|
+
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(", ")}.`);
|
|
7549
|
+
}
|
|
7375
7550
|
const nestedTypes = nestedPlans ? createNestedWriteTypes({
|
|
7376
7551
|
plans: nestedPlans,
|
|
7377
7552
|
cacheCtx,
|
|
@@ -8685,16 +8860,24 @@ const createSchemaDataGenerator = (adapter) => {
|
|
|
8685
8860
|
const runUpdateManyBatch = ({ executor, entries, table, returning, nested, tableName, request }) => executor.transaction(async (tx) => {
|
|
8686
8861
|
const results = [];
|
|
8687
8862
|
for (const entry of entries) {
|
|
8688
|
-
const
|
|
8689
|
-
|
|
8690
|
-
|
|
8691
|
-
|
|
8692
|
-
|
|
8693
|
-
|
|
8694
|
-
|
|
8695
|
-
|
|
8696
|
-
|
|
8697
|
-
|
|
8863
|
+
const write = async (target, patch) => {
|
|
8864
|
+
const values = {
|
|
8865
|
+
...entry.set,
|
|
8866
|
+
...patch
|
|
8867
|
+
};
|
|
8868
|
+
const writes = Object.keys(values).length > 0;
|
|
8869
|
+
let query = writes ? target.update(table).set(values) : target.select(returning).from(table);
|
|
8870
|
+
if (entry.filters) query = query.where(entry.filters);
|
|
8871
|
+
return await (writes ? query.returning(returning) : query);
|
|
8872
|
+
};
|
|
8873
|
+
results.push(entry.ops ? await writeNestedEntry({
|
|
8874
|
+
executor: tx,
|
|
8875
|
+
runtime: nested,
|
|
8876
|
+
tableName,
|
|
8877
|
+
ops: entry.ops,
|
|
8878
|
+
write,
|
|
8879
|
+
request
|
|
8880
|
+
}) : await write(tx, {}));
|
|
8698
8881
|
}
|
|
8699
8882
|
return results;
|
|
8700
8883
|
});
|
|
@@ -8826,16 +9009,24 @@ const runSqliteUpdateManyBatch = ({ executor, entries, table, returning, anyNest
|
|
|
8826
9009
|
return query.returning(returning).all();
|
|
8827
9010
|
};
|
|
8828
9011
|
const runNestedEntry = async (tx, entry) => {
|
|
8829
|
-
const
|
|
8830
|
-
|
|
8831
|
-
|
|
8832
|
-
|
|
8833
|
-
|
|
8834
|
-
|
|
8835
|
-
|
|
8836
|
-
|
|
8837
|
-
|
|
8838
|
-
|
|
9012
|
+
const write = async (target, patch) => {
|
|
9013
|
+
const values = {
|
|
9014
|
+
...entry.set,
|
|
9015
|
+
...patch
|
|
9016
|
+
};
|
|
9017
|
+
const writes = Object.keys(values).length > 0;
|
|
9018
|
+
let query = writes ? target.update(table).set(values) : target.select(returning).from(table);
|
|
9019
|
+
if (entry.filters) query = query.where(entry.filters);
|
|
9020
|
+
return await (writes ? query.returning(returning) : query);
|
|
9021
|
+
};
|
|
9022
|
+
return entry.ops ? writeNestedEntry({
|
|
9023
|
+
executor: tx,
|
|
9024
|
+
runtime: nested,
|
|
9025
|
+
tableName,
|
|
9026
|
+
ops: entry.ops,
|
|
9027
|
+
write,
|
|
9028
|
+
request
|
|
9029
|
+
}) : write(tx, {});
|
|
8839
9030
|
};
|
|
8840
9031
|
return executor.transaction((tx) => {
|
|
8841
9032
|
if (anyNested) return (async () => {
|