@vantreeseba/drizzle-graphql 15.1.4 → 15.1.6
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 +60 -14
- package/dist/index.cjs +405 -125
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -2
- package/dist/index.d.ts +11 -2
- package/dist/index.js +405 -125
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1235,6 +1235,39 @@ const remapToGraphQLCore = (key, value, tableName, column, relationMap) => {
|
|
|
1235
1235
|
}
|
|
1236
1236
|
return value;
|
|
1237
1237
|
};
|
|
1238
|
+
/**
|
|
1239
|
+
* Where a remapped row keeps the values its columns held before remapping, by property name —
|
|
1240
|
+
* only for the columns remapping changed. The output form is not always reversible: a
|
|
1241
|
+
* microsecond timestamp read in `'string'` mode leaves as an ISO string the `DateTime` input
|
|
1242
|
+
* side would cut to milliseconds. A relation field correlating on the row's join columns reads
|
|
1243
|
+
* them from here (see `parentJoinValues`). Non-enumerable, so the row's own keys, JSON form and
|
|
1244
|
+
* equality are unchanged.
|
|
1245
|
+
*/
|
|
1246
|
+
const RAW_COLUMN_VALUES = Symbol("drizzle-graphql.rawColumnValues");
|
|
1247
|
+
/**
|
|
1248
|
+
* The value column `key` held in `row` before remapping: `{ found: true, value }` when remapping
|
|
1249
|
+
* changed it, `{ found: false }` when it did not (or the row was never remapped), in which case
|
|
1250
|
+
* `row[key]` is still the column's own value.
|
|
1251
|
+
*/
|
|
1252
|
+
const rawColumnValue = (row, key) => {
|
|
1253
|
+
const raw = row[RAW_COLUMN_VALUES];
|
|
1254
|
+
return raw && key in raw ? {
|
|
1255
|
+
found: true,
|
|
1256
|
+
value: raw[key]
|
|
1257
|
+
} : { found: false };
|
|
1258
|
+
};
|
|
1259
|
+
const keepRawColumnValue = (row, key, value) => {
|
|
1260
|
+
let raw = row[RAW_COLUMN_VALUES];
|
|
1261
|
+
if (!raw) {
|
|
1262
|
+
raw = Object.create(null);
|
|
1263
|
+
Object.defineProperty(row, RAW_COLUMN_VALUES, {
|
|
1264
|
+
value: raw,
|
|
1265
|
+
enumerable: false,
|
|
1266
|
+
configurable: true
|
|
1267
|
+
});
|
|
1268
|
+
}
|
|
1269
|
+
if (!(key in raw)) raw[key] = value;
|
|
1270
|
+
};
|
|
1238
1271
|
const remapToGraphQLSingleOutput = (queryOutput, tableName, table, relationMap) => {
|
|
1239
1272
|
const columns = getColumns(table);
|
|
1240
1273
|
for (const [key, value] of Object.entries(queryOutput)) {
|
|
@@ -1252,7 +1285,9 @@ const remapToGraphQLSingleOutput = (queryOutput, tableName, table, relationMap)
|
|
|
1252
1285
|
delete queryOutput[key];
|
|
1253
1286
|
continue;
|
|
1254
1287
|
}
|
|
1255
|
-
|
|
1288
|
+
const remapped = remapToGraphQLCore(key, value, tableName, column, relationMap);
|
|
1289
|
+
if (column && remapped !== value) keepRawColumnValue(queryOutput, key, value);
|
|
1290
|
+
queryOutput[key] = remapped;
|
|
1256
1291
|
}
|
|
1257
1292
|
return queryOutput;
|
|
1258
1293
|
};
|
|
@@ -1831,17 +1866,104 @@ const extractRelationJoinColumnPairs = (relEntry, parentTable, targetTable) => {
|
|
|
1831
1866
|
}
|
|
1832
1867
|
return pairs;
|
|
1833
1868
|
};
|
|
1869
|
+
/** A decimal written as text, in a form `canonicalDecimal` can normalise. */
|
|
1870
|
+
const DECIMAL_TEXT = /^([+-]?)(\d*)(?:\.(\d*))?$/;
|
|
1871
|
+
/**
|
|
1872
|
+
* Decimal text without the zeros that do not change its value: `1.500` and `01.5` are both
|
|
1873
|
+
* `1.5`, as SQL compares them. Anything else (an exponent, `NaN`) is returned unchanged.
|
|
1874
|
+
*/
|
|
1875
|
+
const canonicalDecimal = (value) => {
|
|
1876
|
+
const match = DECIMAL_TEXT.exec(value);
|
|
1877
|
+
if (!match || !match[2] && !match[3]) return value;
|
|
1878
|
+
const whole = match[2].replace(/^0+(?=\d)/, "") || "0";
|
|
1879
|
+
const fraction = (match[3] ?? "").replace(/0+$/, "");
|
|
1880
|
+
const digits = fraction ? `${whole}.${fraction}` : whole;
|
|
1881
|
+
return match[1] === "-" && digits !== "0" ? `-${digits}` : digits;
|
|
1882
|
+
};
|
|
1883
|
+
/** An ISO instant without trailing zeros in its fraction: `…:00.100Z` and `…:00.1Z` agree. */
|
|
1884
|
+
const trimIsoFraction = (iso) => iso.replace(/(\.\d*?)0+Z$/, "$1Z").replace(/\.Z$/, "Z");
|
|
1885
|
+
/**
|
|
1886
|
+
* One join value in a form both sides of a relation produce for the same SQL value. The parent's
|
|
1887
|
+
* key and a related row's can arrive in different JS forms — a `Date` and the text of the same
|
|
1888
|
+
* instant, `1.50` and `1.500` from decimals of different scale — and `String()` would tell them
|
|
1889
|
+
* apart (it even drops a `Date`'s milliseconds).
|
|
1890
|
+
*/
|
|
1891
|
+
const joinValueKey = (value, column) => {
|
|
1892
|
+
if (value instanceof Date) {
|
|
1893
|
+
if (Number.isNaN(value.getTime())) return String(value);
|
|
1894
|
+
if (column && isDateOnlyColumn(column)) return dateToCalendarDate(value) ?? value.toISOString();
|
|
1895
|
+
return trimIsoFraction(value.toISOString());
|
|
1896
|
+
}
|
|
1897
|
+
if (typeof value === "bigint") return value.toString();
|
|
1898
|
+
if (value instanceof Uint8Array) return Buffer.from(value.buffer, value.byteOffset, value.byteLength).toString("hex");
|
|
1899
|
+
if (typeof value === "string" && column) {
|
|
1900
|
+
if (isMySqlDateTimeStringColumn(column)) return trimIsoFraction(mySqlDateTimeTextToIso(value));
|
|
1901
|
+
if (isPgTimestampStringColumn(column)) return trimIsoFraction(pgTimestampTextToIso(value));
|
|
1902
|
+
if ((column.dataType ?? "") === "string numeric") return canonicalDecimal(value);
|
|
1903
|
+
}
|
|
1904
|
+
return String(value);
|
|
1905
|
+
};
|
|
1834
1906
|
/**
|
|
1835
1907
|
* A string identifying one combination of join-column values, for grouping a batch's rows by
|
|
1836
|
-
* parent.
|
|
1908
|
+
* parent. `columns` are the columns the values were read from, in the same order: the parent's
|
|
1909
|
+
* join columns for a parent key, the target's (or the junction's) for a related row. Values the
|
|
1910
|
+
* database compares equal give the same key — see {@link joinValueKey}.
|
|
1911
|
+
*/
|
|
1912
|
+
const joinKeyOf = (values, columns) => values.length === 1 ? joinValueKey(values[0], columns?.[0]) : JSON.stringify(values.map((value, i) => joinValueKey(value, columns?.[i])));
|
|
1913
|
+
/**
|
|
1914
|
+
* The parent row's values for the relation's join columns, as the database holds them. The row
|
|
1915
|
+
* a relation field resolves under has been remapped to its GraphQL form — a `Date` is an ISO
|
|
1916
|
+
* string by then, a `bigint` a decimal string — which can neither be bound back through every
|
|
1917
|
+
* column's encoder (SQLite's integer timestamp calls `getTime()` on it) nor be compared with the
|
|
1918
|
+
* related rows' decoded values. The values from before remapping are used when the row kept
|
|
1919
|
+
* them; otherwise the output form is mapped back the way an input value would be.
|
|
1920
|
+
*/
|
|
1921
|
+
const parentJoinValues = (parent, pairs) => pairs.map(({ localColPropName, localCol }) => {
|
|
1922
|
+
const raw = rawColumnValue(parent, localColPropName);
|
|
1923
|
+
if (raw.found) return raw.value;
|
|
1924
|
+
const value = parent[localColPropName];
|
|
1925
|
+
if (value == null) return value;
|
|
1926
|
+
try {
|
|
1927
|
+
return remapFromGraphQLCore(value, localCol, localColPropName);
|
|
1928
|
+
} catch {
|
|
1929
|
+
return value;
|
|
1930
|
+
}
|
|
1931
|
+
});
|
|
1932
|
+
/**
|
|
1933
|
+
* At most this many bound parameters for the parent keys of one batched relation query. SQLite
|
|
1934
|
+
* accepts 32,766 per statement, Postgres and MySQL 65,535; the rest is left to the request's own
|
|
1935
|
+
* filters. A batch with more parents runs as several queries.
|
|
1936
|
+
*/
|
|
1937
|
+
const MAX_PARENT_KEY_PARAMS = 1e4;
|
|
1938
|
+
/**
|
|
1939
|
+
* The parent keys of one batch split into the groups each query reads, so that no query binds
|
|
1940
|
+
* more than {@link MAX_PARENT_KEY_PARAMS} key values. Every parent lands in exactly one group, so
|
|
1941
|
+
* a per-parent result — its rows, its window, its count — is the same whichever query made it.
|
|
1942
|
+
*/
|
|
1943
|
+
const parentKeyChunks = (keys, width) => {
|
|
1944
|
+
const size = Math.max(1, Math.floor(MAX_PARENT_KEY_PARAMS / Math.max(1, width)));
|
|
1945
|
+
const chunks = [];
|
|
1946
|
+
for (let i = 0; i < keys.length; i += size) chunks.push(keys.slice(i, i + size));
|
|
1947
|
+
return chunks;
|
|
1948
|
+
};
|
|
1949
|
+
/**
|
|
1950
|
+
* `or` over `conditions` as a balanced tree. A flat `a OR b OR c …` is parsed left-deep, and
|
|
1951
|
+
* SQLite refuses an expression more than 1,000 levels deep; balanced, a batch of thousands of
|
|
1952
|
+
* parents stays a few levels deep.
|
|
1837
1953
|
*/
|
|
1838
|
-
const
|
|
1954
|
+
const balancedOr = (conditions) => {
|
|
1955
|
+
if (conditions.length <= 2) return or(...conditions);
|
|
1956
|
+
const middle = Math.ceil(conditions.length / 2);
|
|
1957
|
+
return or(balancedOr(conditions.slice(0, middle)), balancedOr(conditions.slice(middle)));
|
|
1958
|
+
};
|
|
1839
1959
|
/**
|
|
1840
1960
|
* The rows whose `cols` hold one of `keys`: `col IN (…)` for a single join column, and an `OR`
|
|
1841
1961
|
* 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.
|
|
1962
|
+
* every dialect accepts, unlike a row-value `IN` list. The `OR` is nested as a balanced tree.
|
|
1963
|
+
* Callers split a large batch with {@link parentKeyChunks} first, which keeps either form within
|
|
1964
|
+
* the dialects' parameter limits.
|
|
1843
1965
|
*/
|
|
1844
|
-
const parentKeyCondition = (cols, keys) => cols.length === 1 ? inArray(cols[0], keys.map((key) => key[0])) :
|
|
1966
|
+
const parentKeyCondition = (cols, keys) => cols.length === 1 ? inArray(cols[0], keys.map((key) => key[0])) : balancedOr(keys.map((key) => and(...cols.map((col, i) => eq(col, key[i])))));
|
|
1845
1967
|
/**
|
|
1846
1968
|
* The junction table and its two columns for a `.through()` (many-to-many) relation:
|
|
1847
1969
|
* `sourceCol` holds the parent's key, `targetCol` the target's. `undefined` for a relation
|
|
@@ -4937,7 +5059,7 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
4937
5059
|
if (!parentTable || !targetTable) return;
|
|
4938
5060
|
const joinPairs = extractRelationJoinColumnPairs(relEntry, parentTable, targetTable);
|
|
4939
5061
|
if (!joinPairs) return;
|
|
4940
|
-
const
|
|
5062
|
+
const localCols = joinPairs.map((pair) => pair.localCol);
|
|
4941
5063
|
const foreignCol = joinPairs[0].foreignCol;
|
|
4942
5064
|
const junction = extractJunctionColumns(relEntry);
|
|
4943
5065
|
const groupCols = junction ? [junction.sourceCol] : joinPairs.map((pair) => pair.foreignCol);
|
|
@@ -4983,7 +5105,7 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
4983
5105
|
};
|
|
4984
5106
|
const resolve = async (parent, args, context, info) => {
|
|
4985
5107
|
if (parent[relationName] !== void 0) return parent[relationName];
|
|
4986
|
-
const localValues =
|
|
5108
|
+
const localValues = parentJoinValues(parent, joinPairs);
|
|
4987
5109
|
if (localValues.some((value) => value == null)) return isOne ? null : [];
|
|
4988
5110
|
const { where: whereArg, limit: requestedLimit, offset, after, deleted } = args ?? {};
|
|
4989
5111
|
const { orderByArg, limit, distinct, cursorEntries, cursorValues, columns, loaderKey } = getOrCreateRequestValue(context, info?.fieldNodes?.[0], `relation:${tableName}::${relationName}`, () => {
|
|
@@ -5027,55 +5149,60 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
5027
5149
|
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" });
|
|
5028
5150
|
return getOrCreateLoader(context, loaderKey, async (parentKeys) => {
|
|
5029
5151
|
const executor = resolveExecutor(db, context);
|
|
5030
|
-
const uniqueKeys = [...new Map(parentKeys.map((key) => [joinKeyOf(key), key])).values()];
|
|
5152
|
+
const uniqueKeys = [...new Map(parentKeys.map((key) => [joinKeyOf(key, localCols), key])).values()];
|
|
5031
5153
|
const scope = resolveScope(policies, context, filterCtx);
|
|
5032
|
-
|
|
5033
|
-
|
|
5034
|
-
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
|
|
5056
|
-
|
|
5057
|
-
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
|
|
5061
|
-
|
|
5062
|
-
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
const
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
|
|
5154
|
+
const loadChunk = async (chunkKeys) => {
|
|
5155
|
+
let whereCondition = withScope(scope, targetTableName, targetTable, and(parentKeyCondition(groupCols, chunkKeys), ownCondition, whereArg ? extractFilters(targetTable, targetTableName, whereArg, relationFilterCtx(filterCtx, targetTableName, scope)) : void 0, cursorValues ? buildCursorCondition(targetTable, cursorEntries, cursorValues, nullOrdering) : void 0), deleted ?? defaultDeleted);
|
|
5156
|
+
if (distinct) {
|
|
5157
|
+
const keys = await selectDistinctKeys({
|
|
5158
|
+
db: executor,
|
|
5159
|
+
table: targetTable,
|
|
5160
|
+
tableName: targetTableName,
|
|
5161
|
+
distinct,
|
|
5162
|
+
pkNames: targetPkNames,
|
|
5163
|
+
where: whereCondition,
|
|
5164
|
+
orderBy: orderByArg,
|
|
5165
|
+
partitionBy: groupCols,
|
|
5166
|
+
junction: junction ? {
|
|
5167
|
+
table: junction.table,
|
|
5168
|
+
on: eq(junction.targetCol, foreignCol),
|
|
5169
|
+
parentKey: junction.sourceCol
|
|
5170
|
+
} : void 0
|
|
5171
|
+
});
|
|
5172
|
+
if (!keys.length) return /* @__PURE__ */ new Map();
|
|
5173
|
+
whereCondition = and(whereCondition, junction ? or(...keys.map((key) => and(eq(junction.sourceCol, key[DISTINCT_PARENT]), primaryKeyRestriction(targetTable, targetPkNames, [key])))) : primaryKeyRestriction(targetTable, targetPkNames, keys));
|
|
5174
|
+
}
|
|
5175
|
+
const selection = junction ? {
|
|
5176
|
+
...columns ?? getColumns(targetTable),
|
|
5177
|
+
[PARENT_KEY]: junction.sourceCol
|
|
5178
|
+
} : columns;
|
|
5179
|
+
let rows;
|
|
5180
|
+
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);
|
|
5181
|
+
else {
|
|
5182
|
+
let q = withJunction((selection ? executor.select(selection) : executor.select()).from(targetTable), junction, foreignCol).where(whereCondition);
|
|
5183
|
+
const orderExprs = cursorEntries ? cursorOrderExprs(targetTable, cursorEntries) : orderByArg ? extractOrderBy(targetTable, orderByArg, relationFilterCtx(filterCtx, targetTableName, scope), whereArg) : [];
|
|
5184
|
+
if (orderExprs.length) q = q.orderBy(...orderExprs);
|
|
5185
|
+
rows = await q;
|
|
5186
|
+
}
|
|
5187
|
+
if (cursorEntries) attachRowCursors(rows, cursorEntries);
|
|
5188
|
+
const keyOf = (row) => {
|
|
5189
|
+
const key = joinKeyOf(groupKeys.map((name) => row[name]), groupCols);
|
|
5190
|
+
if (junction) delete row[PARENT_KEY];
|
|
5191
|
+
return key;
|
|
5192
|
+
};
|
|
5193
|
+
if (isOne) {
|
|
5194
|
+
const byKey = new Map(rows.map((row) => [keyOf(row), row]));
|
|
5195
|
+
remapToGraphQLArrayOutput(rows, targetTableName, targetTable);
|
|
5196
|
+
return byKey;
|
|
5197
|
+
}
|
|
5198
|
+
const grouped = new Map(chunkKeys.map((key) => [joinKeyOf(key, localCols), []]));
|
|
5199
|
+
for (const row of rows) grouped.get(keyOf(row))?.push(row);
|
|
5072
5200
|
remapToGraphQLArrayOutput(rows, targetTableName, targetTable);
|
|
5073
|
-
return
|
|
5074
|
-
}
|
|
5075
|
-
const
|
|
5076
|
-
for (const
|
|
5077
|
-
|
|
5078
|
-
return parentKeys.map((key) => grouped.get(joinKeyOf(key)) ?? []);
|
|
5201
|
+
return grouped;
|
|
5202
|
+
};
|
|
5203
|
+
const results = /* @__PURE__ */ new Map();
|
|
5204
|
+
for (const chunk of parentKeyChunks(uniqueKeys, groupCols.length)) for (const [key, value] of await loadChunk(chunk)) results.set(key, value);
|
|
5205
|
+
return parentKeys.map((key) => results.get(joinKeyOf(key, localCols)) ?? (isOne ? null : []));
|
|
5079
5206
|
}).load(localValues);
|
|
5080
5207
|
};
|
|
5081
5208
|
return async (parent, args, context, info) => {
|
|
@@ -6161,7 +6288,7 @@ const createRelationAggregateFactory = (db, tables, cacheCtx, typeNameMapper, fi
|
|
|
6161
6288
|
}
|
|
6162
6289
|
return;
|
|
6163
6290
|
}
|
|
6164
|
-
const
|
|
6291
|
+
const localCols = joinPairs.map((pair) => pair.localCol);
|
|
6165
6292
|
const foreignCol = joinPairs[0].foreignCol;
|
|
6166
6293
|
const junction = extractJunctionColumns(relEntry);
|
|
6167
6294
|
if (junction === null) return;
|
|
@@ -6191,23 +6318,26 @@ const createRelationAggregateFactory = (db, tables, cacheCtx, typeNameMapper, fi
|
|
|
6191
6318
|
};
|
|
6192
6319
|
});
|
|
6193
6320
|
if (!Object.keys(request.selection).length) return {};
|
|
6194
|
-
const localValues =
|
|
6321
|
+
const localValues = parentJoinValues(parent, joinPairs);
|
|
6195
6322
|
if (localValues.some((value) => value == null)) return assembleAggregateRow({}, request, target);
|
|
6196
6323
|
const whereArg = args?.where;
|
|
6197
6324
|
const deleted = args?.deleted;
|
|
6198
6325
|
const loader = getOrCreateLoader(context, loaderKey, async (parentKeys) => {
|
|
6199
6326
|
const executor = resolveExecutor(db, context);
|
|
6200
|
-
const uniqueKeys = [...new Map(parentKeys.map((key) => [joinKeyOf(key), key])).values()];
|
|
6327
|
+
const uniqueKeys = [...new Map(parentKeys.map((key) => [joinKeyOf(key, localCols), key])).values()];
|
|
6201
6328
|
const scope = resolveScope(policies, context, filterCtx);
|
|
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
6329
|
const groupSelection = Object.fromEntries(groupAliases.map((alias, i) => [alias, groupCols[i]]));
|
|
6204
|
-
const
|
|
6205
|
-
|
|
6206
|
-
|
|
6207
|
-
|
|
6208
|
-
|
|
6209
|
-
|
|
6210
|
-
|
|
6330
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
6331
|
+
for (const chunk of parentKeyChunks(uniqueKeys, groupCols.length)) {
|
|
6332
|
+
const whereCondition = withScope(scope, targetTableName, targetTable, and(parentKeyCondition(groupCols, chunk), ownCondition, whereArg ? extractFilters(targetTable, targetTableName, whereArg, relationFilterCtx(filterCtx, targetTableName, scope)) : void 0), deleted ?? defaultDeleted);
|
|
6333
|
+
const selected = executor.select({
|
|
6334
|
+
...groupSelection,
|
|
6335
|
+
...request.selection
|
|
6336
|
+
}).from(targetTable);
|
|
6337
|
+
const rows = await (junction ? selected.innerJoin(junction.table, eq(junction.targetCol, foreignCol)) : selected).where(whereCondition).groupBy(...groupCols);
|
|
6338
|
+
for (const row of rows) byKey.set(joinKeyOf(groupAliases.map((alias) => row[alias]), groupCols), row);
|
|
6339
|
+
}
|
|
6340
|
+
return parentKeys.map((key) => byKey.get(joinKeyOf(key, localCols)) ?? {});
|
|
6211
6341
|
});
|
|
6212
6342
|
return assembleAggregateRow(await loader.load(localValues), request, target);
|
|
6213
6343
|
} catch (e) {
|
|
@@ -6516,6 +6646,20 @@ const ALL_OPS = [
|
|
|
6516
6646
|
* accepts NULL.
|
|
6517
6647
|
*/
|
|
6518
6648
|
const isOptionalOnInsert = (column) => !column.notNull || !!column.hasDefault || !!column.defaultFn;
|
|
6649
|
+
/**
|
|
6650
|
+
* The plan fields that carry a relation's own `where` (see {@link relationOwnCondition}).
|
|
6651
|
+
*
|
|
6652
|
+
* A `where` on the target narrows every row selection the write makes, and takes `create`
|
|
6653
|
+
* away: the row it inserts is attached whether or not it satisfies the `where`, so the
|
|
6654
|
+
* relation could come back without it. A `where` inherited from the reverse relation filters
|
|
6655
|
+
* the parent instead, and becomes a per-parent check.
|
|
6656
|
+
*/
|
|
6657
|
+
const ownWherePlan = (relEntry, parentTable, targetTable, localCol) => {
|
|
6658
|
+
const condition = () => relationOwnCondition(relEntry, parentTable, targetTable, [localCol], [localCol]);
|
|
6659
|
+
if (!condition()) return {};
|
|
6660
|
+
if (!isRelationFilterReversed(relEntry.relation ?? relEntry)) return { ownCondition: () => condition() };
|
|
6661
|
+
return { parentShows: async (executor, key) => (await executor.select({ key: localCol }).from(parentTable).where(and(eq(localCol, key), condition())).limit(1)).length > 0 };
|
|
6662
|
+
};
|
|
6519
6663
|
/** The property name a column is stored under on its table, matched by identity. */
|
|
6520
6664
|
const propNameOf = (table, column) => {
|
|
6521
6665
|
if (!column) return;
|
|
@@ -6536,7 +6680,7 @@ const propNameOf = (table, column) => {
|
|
|
6536
6680
|
* `onUnresolved` is called when the relation's columns are of a supported shape but cannot be
|
|
6537
6681
|
* matched to the three tables — a drizzle-orm mismatch rather than an unsupported relation.
|
|
6538
6682
|
*/
|
|
6539
|
-
const buildThroughPlan = (relationName, relation, table, targetTableName, targetTable, tableNameOf, onUnresolved) => {
|
|
6683
|
+
const buildThroughPlan = (relationName, relEntry, relation, table, targetTableName, targetTable, tableNameOf, onUnresolved) => {
|
|
6540
6684
|
if (is(relation, One)) return;
|
|
6541
6685
|
const junction = relation.throughTable;
|
|
6542
6686
|
const sourceEntries = relation.through?.source;
|
|
@@ -6577,6 +6721,7 @@ const buildThroughPlan = (relationName, relation, table, targetTableName, target
|
|
|
6577
6721
|
targetCol
|
|
6578
6722
|
},
|
|
6579
6723
|
targetKeyColumns: { [foreignColPropName]: foreignCol },
|
|
6724
|
+
...ownWherePlan(relEntry, table, targetTable, localCol),
|
|
6580
6725
|
allowed: /* @__PURE__ */ new Set([
|
|
6581
6726
|
"connect",
|
|
6582
6727
|
"disconnect",
|
|
@@ -6591,8 +6736,9 @@ const buildThroughPlan = (relationName, relation, table, targetTableName, target
|
|
|
6591
6736
|
* rather than a foreign-key one — see {@link buildThroughPlan}.
|
|
6592
6737
|
*
|
|
6593
6738
|
* Skipped, and so absent from the generated inputs rather than failing at runtime:
|
|
6594
|
-
* relations whose join columns cannot be read off the drizzle relation;
|
|
6595
|
-
*
|
|
6739
|
+
* relations whose join columns cannot be read off the drizzle relation; relations joining on
|
|
6740
|
+
* more than one column per side; and relations where neither join column is unique, which
|
|
6741
|
+
* leaves no way to tell which side stores the key.
|
|
6596
6742
|
*/
|
|
6597
6743
|
const buildNestedWritePlans = (tables, relationMap, resolveUniqueSets, resolvePrimaryKeys, extractJoinColumns, tableNameOf = () => void 0) => {
|
|
6598
6744
|
const uniqueCache = /* @__PURE__ */ new Map();
|
|
@@ -6624,10 +6770,11 @@ const buildNestedWritePlans = (tables, relationMap, resolveUniqueSets, resolvePr
|
|
|
6624
6770
|
if (!targetTable) continue;
|
|
6625
6771
|
const omitted = `the '${relationName}' field of its nested-write create and update inputs`;
|
|
6626
6772
|
if (relation.through) {
|
|
6627
|
-
const throughPlan = buildThroughPlan(relationName, relation, table, targetTableName, targetTable, tableNameOf, () => warnUnresolvedJoin(relEntry, tableName, relationName, omitted));
|
|
6773
|
+
const throughPlan = buildThroughPlan(relationName, relEntry, relation, table, targetTableName, targetTable, tableNameOf, () => warnUnresolvedJoin(relEntry, tableName, relationName, omitted));
|
|
6628
6774
|
if (throughPlan) tablePlans[relationName] = throughPlan;
|
|
6629
6775
|
continue;
|
|
6630
6776
|
}
|
|
6777
|
+
if ((relationColumns(relation, "source")?.length ?? 0) > 1 || (relationColumns(relation, "target")?.length ?? 0) > 1) continue;
|
|
6631
6778
|
const joinColumns = extractJoinColumns(relEntry, table, targetTable);
|
|
6632
6779
|
if (!joinColumns) {
|
|
6633
6780
|
warnUnresolvedJoin(relEntry, tableName, relationName, omitted);
|
|
@@ -6650,8 +6797,15 @@ const buildNestedWritePlans = (tables, relationMap, resolveUniqueSets, resolvePr
|
|
|
6650
6797
|
else continue;
|
|
6651
6798
|
const fkColumn = fkSide === "child" ? foreignCol : localCol;
|
|
6652
6799
|
const targetColumns = getColumns(targetTable);
|
|
6653
|
-
const
|
|
6800
|
+
const targetPrimaryKey = resolvePrimaryKeys(targetTable).filter((name) => targetColumns[name]);
|
|
6801
|
+
const targetKeyColumns = Object.fromEntries(targetPrimaryKey.map((name) => [name, targetColumns[name]]));
|
|
6654
6802
|
targetKeyColumns[foreignColPropName] = foreignCol;
|
|
6803
|
+
const ownWhere = ownWherePlan(relEntry, table, targetTable, localCol);
|
|
6804
|
+
const ops = !fkColumn.notNull ? isOne ? [
|
|
6805
|
+
"create",
|
|
6806
|
+
"connect",
|
|
6807
|
+
"disconnect"
|
|
6808
|
+
] : ALL_OPS : ["create", "connect"];
|
|
6655
6809
|
tablePlans[relationName] = {
|
|
6656
6810
|
relationName,
|
|
6657
6811
|
targetTableName,
|
|
@@ -6664,11 +6818,9 @@ const buildNestedWritePlans = (tables, relationMap, resolveUniqueSets, resolvePr
|
|
|
6664
6818
|
fkSide,
|
|
6665
6819
|
canDetach: !fkColumn.notNull,
|
|
6666
6820
|
targetKeyColumns,
|
|
6667
|
-
|
|
6668
|
-
|
|
6669
|
-
|
|
6670
|
-
"disconnect"
|
|
6671
|
-
] : ALL_OPS : ["create", "connect"])
|
|
6821
|
+
targetPrimaryKey,
|
|
6822
|
+
...ownWhere,
|
|
6823
|
+
allowed: new Set(ownWhere.ownCondition ? ops.filter((op) => op !== "create") : ops)
|
|
6672
6824
|
};
|
|
6673
6825
|
}
|
|
6674
6826
|
if (Object.keys(tablePlans).length) plans[tableName] = tablePlans;
|
|
@@ -6743,6 +6895,35 @@ const gateNestedWritePlans = (plans, featureOf, isExcluded) => {
|
|
|
6743
6895
|
}
|
|
6744
6896
|
return omitted;
|
|
6745
6897
|
};
|
|
6898
|
+
/**
|
|
6899
|
+
* Takes out every relation whose foreign key is a `contextValues` column of the table storing
|
|
6900
|
+
* it — the parent's own column for a parent-side relation, the target's for a child-side one,
|
|
6901
|
+
* either junction column for a many-to-many.
|
|
6902
|
+
*
|
|
6903
|
+
* Every operation on such a relation writes that column: `connect`, `disconnect` and `set`
|
|
6904
|
+
* by definition, and a child-side `create` too, since it fills the key in from the parent row
|
|
6905
|
+
* rather than from context. That column is the server's to set — an insert stamps it and an
|
|
6906
|
+
* update never touches it — so writing it from a nested input would let a client pick, or
|
|
6907
|
+
* change, the owner the server assigns. Whole relations are dropped, not single inputs: no
|
|
6908
|
+
* operation on them is left that does not write the key.
|
|
6909
|
+
*
|
|
6910
|
+
* Returns one line per relation taken out, for the build note.
|
|
6911
|
+
*/
|
|
6912
|
+
const dropContextValueRelations = (plans, contextValuesOf) => {
|
|
6913
|
+
const omitted = [];
|
|
6914
|
+
if (!contextValuesOf) return omitted;
|
|
6915
|
+
for (const [tableName, tablePlans] of Object.entries(plans)) {
|
|
6916
|
+
for (const [relationName, plan] of Object.entries(tablePlans)) {
|
|
6917
|
+
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]);
|
|
6918
|
+
if (stamped) {
|
|
6919
|
+
delete tablePlans[relationName];
|
|
6920
|
+
omitted.push(`${tableName}.${relationName} (${stamped[0]}.${stamped[1]} is a contextValues column)`);
|
|
6921
|
+
}
|
|
6922
|
+
}
|
|
6923
|
+
if (!Object.keys(tablePlans).length) delete plans[tableName];
|
|
6924
|
+
}
|
|
6925
|
+
return omitted;
|
|
6926
|
+
};
|
|
6746
6927
|
const NO_COLUMNS = /* @__PURE__ */ new Set();
|
|
6747
6928
|
/**
|
|
6748
6929
|
* Builds the nested-write input types for a schema.
|
|
@@ -6773,7 +6954,8 @@ const createNestedWriteTypes = (params) => {
|
|
|
6773
6954
|
if (cached) return cached;
|
|
6774
6955
|
const omitted = plan.fkSide === "child" ? plan.foreignColPropName : void 0;
|
|
6775
6956
|
const contextColumns = cacheCtx.contextValuesOf?.(plan.targetTableName);
|
|
6776
|
-
const
|
|
6957
|
+
const markerColumn = cacheCtx.softDeleteOf?.(plan.targetTableName)?.columnName;
|
|
6958
|
+
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)]));
|
|
6777
6959
|
const type = new GraphQLInputObjectType({
|
|
6778
6960
|
name: cacheCtx.typeName({
|
|
6779
6961
|
kind: "nestedWriteInput",
|
|
@@ -6897,6 +7079,8 @@ const createNestedWriteTypes = (params) => {
|
|
|
6897
7079
|
* join columns the whole batch's RETURNING clause needs, without inspecting each row's write.
|
|
6898
7080
|
*/
|
|
6899
7081
|
const mergedOps = (entries) => Object.assign({}, ...entries.map((entry) => entry.ops));
|
|
7082
|
+
/** A to-one `connect` whose filter did not match exactly one row. */
|
|
7083
|
+
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" });
|
|
6900
7084
|
const asArray = (value) => Array.isArray(value) ? value : value === void 0 ? [] : [value];
|
|
6901
7085
|
const supplied = (value) => value !== void 0 && value !== null;
|
|
6902
7086
|
/**
|
|
@@ -6988,6 +7172,8 @@ const registerChildSteps = (steps, plan, op) => {
|
|
|
6988
7172
|
};
|
|
6989
7173
|
/** The input that attaches a row to a to-one, which detaches whatever was attached before. */
|
|
6990
7174
|
const attachingOp = (op) => op["connect"] !== void 0 ? "connect" : op["create"] !== void 0 ? "create" : void 0;
|
|
7175
|
+
/** The child-side relation in `ops` whose target is the parent's own table, if there is one. */
|
|
7176
|
+
const selfTargetingPlan = (tablePlans, tableName, ops) => Object.keys(ops).map((relationName) => tablePlans?.[relationName]).find((plan) => plan?.fkSide === "child" && !plan.through && plan.targetTableName === tableName);
|
|
6991
7177
|
const createNestedWriteRuntime = (params) => {
|
|
6992
7178
|
const { plans, filterCtx, policies, contextValues } = params;
|
|
6993
7179
|
const onWrite = policies?.onWrite;
|
|
@@ -6998,7 +7184,7 @@ const createNestedWriteRuntime = (params) => {
|
|
|
6998
7184
|
const conditionOf = (plan, filter, operation, scope) => {
|
|
6999
7185
|
const condition = extractFilters(plan.targetTable, plan.targetTableName, filter, relationFilterCtx(filterCtx, plan.targetTableName, scope));
|
|
7000
7186
|
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" });
|
|
7001
|
-
return withScope(scope, plan.targetTableName, plan.targetTable, condition);
|
|
7187
|
+
return withScope(scope, plan.targetTableName, plan.targetTable, and(condition, plan.ownCondition?.()));
|
|
7002
7188
|
};
|
|
7003
7189
|
const assertSingleOperation = (plan, op) => {
|
|
7004
7190
|
const given = [
|
|
@@ -7021,22 +7207,24 @@ const createNestedWriteRuntime = (params) => {
|
|
|
7021
7207
|
* error on some of them — the read and the write share the surrounding transaction, so no
|
|
7022
7208
|
* other writer can slip a duplicate in between them.
|
|
7023
7209
|
*/
|
|
7024
|
-
const applyThroughOps = async (executor, plan, key, op, scope, steps) => {
|
|
7210
|
+
const applyThroughOps = async (executor, plan, key, op, scope, steps, context) => {
|
|
7025
7211
|
const through = plan.through;
|
|
7026
7212
|
const link = async (operands, operation) => {
|
|
7027
7213
|
const existing = new Set((await executor.select({ [through.targetColPropName]: through.targetCol }).from(through.table).where(eq(through.sourceCol, key))).map((row) => row[through.targetColPropName]));
|
|
7028
7214
|
const rows = [];
|
|
7029
7215
|
for (const filter of operands) for (const targetKey of await selectedTargetKeys(executor, plan, filter, operation, scope)) if (!existing.has(targetKey)) {
|
|
7030
7216
|
existing.add(targetKey);
|
|
7031
|
-
rows.push({
|
|
7217
|
+
rows.push(applyContextValues({
|
|
7032
7218
|
[through.sourceColPropName]: key,
|
|
7033
7219
|
[through.targetColPropName]: targetKey
|
|
7034
|
-
});
|
|
7220
|
+
}, through.tableName ? contextValues?.(through.tableName) : void 0, context));
|
|
7035
7221
|
}
|
|
7036
7222
|
if (rows.length) await steps.run(executor.insert(through.table).values(rows), operation, "insert");
|
|
7037
7223
|
};
|
|
7038
7224
|
if (supplied(op["set"])) {
|
|
7039
|
-
|
|
7225
|
+
const reachable = withScope(scope, plan.targetTableName, plan.targetTable, plan.ownCondition?.());
|
|
7226
|
+
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);
|
|
7227
|
+
await steps.run(executor.delete(through.table).where(unlink), "set", "delete");
|
|
7040
7228
|
await link(asArray(op["set"]), "set");
|
|
7041
7229
|
}
|
|
7042
7230
|
if (supplied(op["disconnect"])) for (const filter of asArray(op["disconnect"])) {
|
|
@@ -7045,7 +7233,7 @@ const createNestedWriteRuntime = (params) => {
|
|
|
7045
7233
|
}
|
|
7046
7234
|
if (supplied(op["connect"])) await link(asArray(op["connect"]), "connect");
|
|
7047
7235
|
};
|
|
7048
|
-
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");
|
|
7236
|
+
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");
|
|
7049
7237
|
return {
|
|
7050
7238
|
enabled: (tableName) => !!plans[tableName],
|
|
7051
7239
|
split: (tableName, values) => {
|
|
@@ -7072,8 +7260,24 @@ const createNestedWriteRuntime = (params) => {
|
|
|
7072
7260
|
const plan = tablePlans[relationName];
|
|
7073
7261
|
if (plan?.fkSide === "child" && !(plan.localColPropName in columns) && allColumns[plan.localColPropName]) columns[plan.localColPropName] = allColumns[plan.localColPropName];
|
|
7074
7262
|
}
|
|
7263
|
+
for (const name of selfTargetingPlan(tablePlans, tableName, ops)?.targetPrimaryKey ?? []) if (!(name in columns) && allColumns[name]) columns[name] = allColumns[name];
|
|
7075
7264
|
return columns;
|
|
7076
7265
|
},
|
|
7266
|
+
createsParentSide: (tableName, ops) => Object.entries(ops).some(([relationName, op]) => plans[tableName]?.[relationName]?.fkSide === "parent" && supplied(op?.["create"])),
|
|
7267
|
+
refreshed: async (executor, tableName, ops, rows) => {
|
|
7268
|
+
const plan = selfTargetingPlan(plans[tableName], tableName, ops);
|
|
7269
|
+
const pk = plan?.targetPrimaryKey ?? [];
|
|
7270
|
+
if (!plan || !pk.length || !rows.length || !pk.every((name) => name in rows[0])) return rows;
|
|
7271
|
+
const columns = getColumns(plan.targetTable);
|
|
7272
|
+
const selection = Object.fromEntries(Object.keys(rows[0]).filter((name) => columns[name]).map((name) => [name, columns[name]]));
|
|
7273
|
+
const keyOf = (row) => pk.map((name) => String(row[name])).join("\0");
|
|
7274
|
+
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])))));
|
|
7275
|
+
const fresh = new Map((await executor.select(selection).from(plan.targetTable).where(match)).map((row) => [keyOf(row), row]));
|
|
7276
|
+
return rows.map((row) => ({
|
|
7277
|
+
...row,
|
|
7278
|
+
...fresh.get(keyOf(row))
|
|
7279
|
+
}));
|
|
7280
|
+
},
|
|
7077
7281
|
applyParentSide: async (executor, tableName, ops, request) => {
|
|
7078
7282
|
const tablePlans = plans[tableName];
|
|
7079
7283
|
const patch = {};
|
|
@@ -7098,10 +7302,11 @@ const createNestedWriteRuntime = (params) => {
|
|
|
7098
7302
|
patch[plan.localColPropName] = created[plan.foreignColPropName];
|
|
7099
7303
|
} else if (op["connect"] !== void 0) {
|
|
7100
7304
|
const rows = await executor.select({ [plan.foreignColPropName]: plan.foreignCol }).from(plan.targetTable).where(conditionOf(plan, op["connect"], "connect", scope));
|
|
7101
|
-
if (rows.length !== 1) throw
|
|
7305
|
+
if (rows.length !== 1) throw connectMatchError(relationName, rows.length);
|
|
7102
7306
|
patch[plan.localColPropName] = rows[0][plan.foreignColPropName];
|
|
7103
7307
|
} else if (op["disconnect"] === true) patch[plan.localColPropName] = null;
|
|
7104
7308
|
}
|
|
7309
|
+
for (const columnName of Object.keys(contextValues?.(tableName) ?? {})) delete patch[columnName];
|
|
7105
7310
|
return patch;
|
|
7106
7311
|
},
|
|
7107
7312
|
applyChildSide: async (executor, tableName, ops, parentRows, request) => {
|
|
@@ -7119,8 +7324,9 @@ const createNestedWriteRuntime = (params) => {
|
|
|
7119
7324
|
for (const parentRow of parentRows) {
|
|
7120
7325
|
const key = parentRow?.[plan.localColPropName];
|
|
7121
7326
|
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" });
|
|
7327
|
+
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" });
|
|
7122
7328
|
if (plan.through) {
|
|
7123
|
-
await applyThroughOps(executor, plan, key, op, scope, steps);
|
|
7329
|
+
await applyThroughOps(executor, plan, key, op, scope, steps, context);
|
|
7124
7330
|
continue;
|
|
7125
7331
|
}
|
|
7126
7332
|
if (op["set"] !== void 0) {
|
|
@@ -7132,14 +7338,18 @@ const createNestedWriteRuntime = (params) => {
|
|
|
7132
7338
|
if (op["disconnect"] === true) await detachAll(executor, plan, key, scope, steps, "disconnect");
|
|
7133
7339
|
} 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");
|
|
7134
7340
|
}
|
|
7341
|
+
if (plan.isOne && op["connect"] !== void 0) {
|
|
7342
|
+
const matched = await executor.select({ [plan.foreignColPropName]: plan.foreignCol }).from(plan.targetTable).where(conditionOf(plan, op["connect"], "connect", scope));
|
|
7343
|
+
if (matched.length !== 1) throw connectMatchError(relationName, matched.length);
|
|
7344
|
+
}
|
|
7135
7345
|
const attachedBy = attachingOp(op);
|
|
7136
7346
|
if (plan.isOne && attachedBy && plan.canDetach && op["set"] === void 0) await detachAll(executor, plan, key, scope, steps, attachedBy);
|
|
7137
7347
|
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");
|
|
7138
7348
|
if (op["create"] !== void 0) {
|
|
7139
|
-
const rows = asArray(op["create"]).map((row) => ({
|
|
7140
|
-
...
|
|
7349
|
+
const rows = asArray(op["create"]).map((row) => applyContextValues({
|
|
7350
|
+
...remapFromGraphQLSingleInput({ ...row }, plan.targetTable),
|
|
7141
7351
|
[plan.foreignColPropName]: key
|
|
7142
|
-
}));
|
|
7352
|
+
}, contextValues?.(plan.targetTableName), context));
|
|
7143
7353
|
if (rows.length) await steps.run(executor.insert(plan.targetTable).values(rows), "create", "insert");
|
|
7144
7354
|
}
|
|
7145
7355
|
}
|
|
@@ -7148,6 +7358,39 @@ const createNestedWriteRuntime = (params) => {
|
|
|
7148
7358
|
}
|
|
7149
7359
|
};
|
|
7150
7360
|
};
|
|
7361
|
+
/** Thrown inside an entry's savepoint to undo a parent-side `create` whose parent was never written. */
|
|
7362
|
+
var NoParentRow = class extends Error {};
|
|
7363
|
+
/**
|
|
7364
|
+
* One parent write with its nested operations around it: the parent-side operations, whose
|
|
7365
|
+
* values `write` receives as a patch, then the write, then the child-side operations — and
|
|
7366
|
+
* the rows it returns read back when a child-side statement may have changed them.
|
|
7367
|
+
*
|
|
7368
|
+
* A parent write can come back empty: an update whose `where` matched nothing, an insert or
|
|
7369
|
+
* upsert that a conflict turned into a no-op. Anything a parent-side `create` inserted would
|
|
7370
|
+
* then be left with nothing pointing at it, so such an entry runs in a savepoint of its own
|
|
7371
|
+
* that is rolled back when the write produced no row, and the entry returns no rows — as it
|
|
7372
|
+
* would have without the nested operation. The rest of the batch is unaffected.
|
|
7373
|
+
*
|
|
7374
|
+
* `executor` must already be a transaction.
|
|
7375
|
+
*/
|
|
7376
|
+
const writeNestedEntry = async (params) => {
|
|
7377
|
+
const { executor, runtime, tableName, ops, write, request } = params;
|
|
7378
|
+
const guarded = runtime.createsParentSide(tableName, ops);
|
|
7379
|
+
const run = async (tx) => {
|
|
7380
|
+
const patch = await runtime.applyParentSide(tx, tableName, ops, request);
|
|
7381
|
+
const rows = await write(tx, patch);
|
|
7382
|
+
if (guarded && !rows.length) throw new NoParentRow();
|
|
7383
|
+
await runtime.applyChildSide(tx, tableName, ops, rows, request);
|
|
7384
|
+
return runtime.refreshed(tx, tableName, ops, rows);
|
|
7385
|
+
};
|
|
7386
|
+
if (!guarded) return run(executor);
|
|
7387
|
+
try {
|
|
7388
|
+
return await executor.transaction(run);
|
|
7389
|
+
} catch (error) {
|
|
7390
|
+
if (error instanceof NoParentRow) return [];
|
|
7391
|
+
throw error;
|
|
7392
|
+
}
|
|
7393
|
+
};
|
|
7151
7394
|
/**
|
|
7152
7395
|
* Runs one parent write per input entry, with that entry's nested operations around it, all
|
|
7153
7396
|
* inside a single transaction — a savepoint when the caller already runs on one, so a failing
|
|
@@ -7166,12 +7409,17 @@ const writeWithNestedOps = async (params) => {
|
|
|
7166
7409
|
return executor.transaction(async (tx) => {
|
|
7167
7410
|
const rows = [];
|
|
7168
7411
|
for (const entry of entries) {
|
|
7169
|
-
const
|
|
7170
|
-
|
|
7171
|
-
|
|
7172
|
-
|
|
7412
|
+
const written = await writeNestedEntry({
|
|
7413
|
+
executor: tx,
|
|
7414
|
+
runtime,
|
|
7415
|
+
tableName,
|
|
7416
|
+
ops: entry.ops,
|
|
7417
|
+
write: (target, patch) => write(target, {
|
|
7418
|
+
...remapValues(entry.columns),
|
|
7419
|
+
...patch
|
|
7420
|
+
}),
|
|
7421
|
+
request
|
|
7173
7422
|
});
|
|
7174
|
-
await runtime.applyChildSide(tx, tableName, entry.ops, written, request);
|
|
7175
7423
|
rows.push(...written);
|
|
7176
7424
|
}
|
|
7177
7425
|
return rows;
|
|
@@ -7184,15 +7432,17 @@ const writeWithNestedOps = async (params) => {
|
|
|
7184
7432
|
*/
|
|
7185
7433
|
const updateWithNestedOps = async (params) => {
|
|
7186
7434
|
const { executor, runtime, tableName, columns, ops, remapValues, write, request } = params;
|
|
7187
|
-
return executor.transaction(
|
|
7188
|
-
|
|
7189
|
-
|
|
7435
|
+
return executor.transaction((tx) => writeNestedEntry({
|
|
7436
|
+
executor: tx,
|
|
7437
|
+
runtime,
|
|
7438
|
+
tableName,
|
|
7439
|
+
ops,
|
|
7440
|
+
write: (target, patch) => write(target, {
|
|
7190
7441
|
...remapValues(columns),
|
|
7191
7442
|
...patch
|
|
7192
|
-
})
|
|
7193
|
-
|
|
7194
|
-
|
|
7195
|
-
});
|
|
7443
|
+
}),
|
|
7444
|
+
request
|
|
7445
|
+
}));
|
|
7196
7446
|
};
|
|
7197
7447
|
//#endregion
|
|
7198
7448
|
//#region src/util/builders/errors.ts
|
|
@@ -7399,6 +7649,10 @@ const createSchemaBuilder = (adapter) => {
|
|
|
7399
7649
|
console.warn(`Drizzle-GraphQL Warning: config.features — nestedWrites leaves out the nested inputs whose table has that write turned off: ${listed}.`);
|
|
7400
7650
|
}
|
|
7401
7651
|
}
|
|
7652
|
+
if (nestedPlans) {
|
|
7653
|
+
const omitted = dropContextValueRelations(nestedPlans, contextValuesOf);
|
|
7654
|
+
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(", ")}.`);
|
|
7655
|
+
}
|
|
7402
7656
|
const nestedTypes = nestedPlans ? createNestedWriteTypes({
|
|
7403
7657
|
plans: nestedPlans,
|
|
7404
7658
|
cacheCtx,
|
|
@@ -8170,7 +8424,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
8170
8424
|
serverOwned: Object.keys(contextColumns ?? {}),
|
|
8171
8425
|
buildWhere: (where) => extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName, scope))
|
|
8172
8426
|
});
|
|
8173
|
-
const
|
|
8427
|
+
const batchQuery = (executor, { values: rows, plan }) => {
|
|
8174
8428
|
let query = executor.insert(table).values(rows).returning(returning);
|
|
8175
8429
|
const setWhere = withScope(scope, tableName, table, plan.setWhere);
|
|
8176
8430
|
query = plan.action === "NOTHING" ? query.onConflictDoNothing(plan.target ? {
|
|
@@ -8182,12 +8436,22 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
8182
8436
|
set: plan.set,
|
|
8183
8437
|
setWhere
|
|
8184
8438
|
});
|
|
8185
|
-
return
|
|
8439
|
+
return query;
|
|
8186
8440
|
};
|
|
8187
|
-
if (batches.length === 1) return
|
|
8188
|
-
return target.transaction(
|
|
8189
|
-
const
|
|
8190
|
-
|
|
8441
|
+
if (batches.length === 1) return await batchQuery(target, batches[0]);
|
|
8442
|
+
return target.transaction((tx) => {
|
|
8443
|
+
const execute = (batch) => {
|
|
8444
|
+
const query = batchQuery(tx, batch);
|
|
8445
|
+
return typeof query.all === "function" ? query.all() : query;
|
|
8446
|
+
};
|
|
8447
|
+
const first = execute(batches[0]);
|
|
8448
|
+
if (typeof first?.then === "function") return (async () => {
|
|
8449
|
+
const rows = [...await first];
|
|
8450
|
+
for (const batch of batches.slice(1)) rows.push(...await execute(batch));
|
|
8451
|
+
return rows;
|
|
8452
|
+
})();
|
|
8453
|
+
const rows = [...first];
|
|
8454
|
+
for (const batch of batches.slice(1)) rows.push(...execute(batch));
|
|
8191
8455
|
return rows;
|
|
8192
8456
|
});
|
|
8193
8457
|
};
|
|
@@ -8712,16 +8976,24 @@ const createSchemaDataGenerator = (adapter) => {
|
|
|
8712
8976
|
const runUpdateManyBatch = ({ executor, entries, table, returning, nested, tableName, request }) => executor.transaction(async (tx) => {
|
|
8713
8977
|
const results = [];
|
|
8714
8978
|
for (const entry of entries) {
|
|
8715
|
-
const
|
|
8716
|
-
|
|
8717
|
-
|
|
8718
|
-
|
|
8719
|
-
|
|
8720
|
-
|
|
8721
|
-
|
|
8722
|
-
|
|
8723
|
-
|
|
8724
|
-
|
|
8979
|
+
const write = async (target, patch) => {
|
|
8980
|
+
const values = {
|
|
8981
|
+
...entry.set,
|
|
8982
|
+
...patch
|
|
8983
|
+
};
|
|
8984
|
+
const writes = Object.keys(values).length > 0;
|
|
8985
|
+
let query = writes ? target.update(table).set(values) : target.select(returning).from(table);
|
|
8986
|
+
if (entry.filters) query = query.where(entry.filters);
|
|
8987
|
+
return await (writes ? query.returning(returning) : query);
|
|
8988
|
+
};
|
|
8989
|
+
results.push(entry.ops ? await writeNestedEntry({
|
|
8990
|
+
executor: tx,
|
|
8991
|
+
runtime: nested,
|
|
8992
|
+
tableName,
|
|
8993
|
+
ops: entry.ops,
|
|
8994
|
+
write,
|
|
8995
|
+
request
|
|
8996
|
+
}) : await write(tx, {}));
|
|
8725
8997
|
}
|
|
8726
8998
|
return results;
|
|
8727
8999
|
});
|
|
@@ -8853,16 +9125,24 @@ const runSqliteUpdateManyBatch = ({ executor, entries, table, returning, anyNest
|
|
|
8853
9125
|
return query.returning(returning).all();
|
|
8854
9126
|
};
|
|
8855
9127
|
const runNestedEntry = async (tx, entry) => {
|
|
8856
|
-
const
|
|
8857
|
-
|
|
8858
|
-
|
|
8859
|
-
|
|
8860
|
-
|
|
8861
|
-
|
|
8862
|
-
|
|
8863
|
-
|
|
8864
|
-
|
|
8865
|
-
|
|
9128
|
+
const write = async (target, patch) => {
|
|
9129
|
+
const values = {
|
|
9130
|
+
...entry.set,
|
|
9131
|
+
...patch
|
|
9132
|
+
};
|
|
9133
|
+
const writes = Object.keys(values).length > 0;
|
|
9134
|
+
let query = writes ? target.update(table).set(values) : target.select(returning).from(table);
|
|
9135
|
+
if (entry.filters) query = query.where(entry.filters);
|
|
9136
|
+
return await (writes ? query.returning(returning) : query);
|
|
9137
|
+
};
|
|
9138
|
+
return entry.ops ? writeNestedEntry({
|
|
9139
|
+
executor: tx,
|
|
9140
|
+
runtime: nested,
|
|
9141
|
+
tableName,
|
|
9142
|
+
ops: entry.ops,
|
|
9143
|
+
write,
|
|
9144
|
+
request
|
|
9145
|
+
}) : write(tx, {});
|
|
8866
9146
|
};
|
|
8867
9147
|
return executor.transaction((tx) => {
|
|
8868
9148
|
if (anyNested) return (async () => {
|