@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.cjs
CHANGED
|
@@ -1259,6 +1259,39 @@ const remapToGraphQLCore = (key, value, tableName, column, relationMap) => {
|
|
|
1259
1259
|
}
|
|
1260
1260
|
return value;
|
|
1261
1261
|
};
|
|
1262
|
+
/**
|
|
1263
|
+
* Where a remapped row keeps the values its columns held before remapping, by property name —
|
|
1264
|
+
* only for the columns remapping changed. The output form is not always reversible: a
|
|
1265
|
+
* microsecond timestamp read in `'string'` mode leaves as an ISO string the `DateTime` input
|
|
1266
|
+
* side would cut to milliseconds. A relation field correlating on the row's join columns reads
|
|
1267
|
+
* them from here (see `parentJoinValues`). Non-enumerable, so the row's own keys, JSON form and
|
|
1268
|
+
* equality are unchanged.
|
|
1269
|
+
*/
|
|
1270
|
+
const RAW_COLUMN_VALUES = Symbol("drizzle-graphql.rawColumnValues");
|
|
1271
|
+
/**
|
|
1272
|
+
* The value column `key` held in `row` before remapping: `{ found: true, value }` when remapping
|
|
1273
|
+
* changed it, `{ found: false }` when it did not (or the row was never remapped), in which case
|
|
1274
|
+
* `row[key]` is still the column's own value.
|
|
1275
|
+
*/
|
|
1276
|
+
const rawColumnValue = (row, key) => {
|
|
1277
|
+
const raw = row[RAW_COLUMN_VALUES];
|
|
1278
|
+
return raw && key in raw ? {
|
|
1279
|
+
found: true,
|
|
1280
|
+
value: raw[key]
|
|
1281
|
+
} : { found: false };
|
|
1282
|
+
};
|
|
1283
|
+
const keepRawColumnValue = (row, key, value) => {
|
|
1284
|
+
let raw = row[RAW_COLUMN_VALUES];
|
|
1285
|
+
if (!raw) {
|
|
1286
|
+
raw = Object.create(null);
|
|
1287
|
+
Object.defineProperty(row, RAW_COLUMN_VALUES, {
|
|
1288
|
+
value: raw,
|
|
1289
|
+
enumerable: false,
|
|
1290
|
+
configurable: true
|
|
1291
|
+
});
|
|
1292
|
+
}
|
|
1293
|
+
if (!(key in raw)) raw[key] = value;
|
|
1294
|
+
};
|
|
1262
1295
|
const remapToGraphQLSingleOutput = (queryOutput, tableName, table, relationMap) => {
|
|
1263
1296
|
const columns = (0, drizzle_orm.getColumns)(table);
|
|
1264
1297
|
for (const [key, value] of Object.entries(queryOutput)) {
|
|
@@ -1276,7 +1309,9 @@ const remapToGraphQLSingleOutput = (queryOutput, tableName, table, relationMap)
|
|
|
1276
1309
|
delete queryOutput[key];
|
|
1277
1310
|
continue;
|
|
1278
1311
|
}
|
|
1279
|
-
|
|
1312
|
+
const remapped = remapToGraphQLCore(key, value, tableName, column, relationMap);
|
|
1313
|
+
if (column && remapped !== value) keepRawColumnValue(queryOutput, key, value);
|
|
1314
|
+
queryOutput[key] = remapped;
|
|
1280
1315
|
}
|
|
1281
1316
|
return queryOutput;
|
|
1282
1317
|
};
|
|
@@ -1855,17 +1890,104 @@ const extractRelationJoinColumnPairs = (relEntry, parentTable, targetTable) => {
|
|
|
1855
1890
|
}
|
|
1856
1891
|
return pairs;
|
|
1857
1892
|
};
|
|
1893
|
+
/** A decimal written as text, in a form `canonicalDecimal` can normalise. */
|
|
1894
|
+
const DECIMAL_TEXT = /^([+-]?)(\d*)(?:\.(\d*))?$/;
|
|
1895
|
+
/**
|
|
1896
|
+
* Decimal text without the zeros that do not change its value: `1.500` and `01.5` are both
|
|
1897
|
+
* `1.5`, as SQL compares them. Anything else (an exponent, `NaN`) is returned unchanged.
|
|
1898
|
+
*/
|
|
1899
|
+
const canonicalDecimal = (value) => {
|
|
1900
|
+
const match = DECIMAL_TEXT.exec(value);
|
|
1901
|
+
if (!match || !match[2] && !match[3]) return value;
|
|
1902
|
+
const whole = match[2].replace(/^0+(?=\d)/, "") || "0";
|
|
1903
|
+
const fraction = (match[3] ?? "").replace(/0+$/, "");
|
|
1904
|
+
const digits = fraction ? `${whole}.${fraction}` : whole;
|
|
1905
|
+
return match[1] === "-" && digits !== "0" ? `-${digits}` : digits;
|
|
1906
|
+
};
|
|
1907
|
+
/** An ISO instant without trailing zeros in its fraction: `…:00.100Z` and `…:00.1Z` agree. */
|
|
1908
|
+
const trimIsoFraction = (iso) => iso.replace(/(\.\d*?)0+Z$/, "$1Z").replace(/\.Z$/, "Z");
|
|
1909
|
+
/**
|
|
1910
|
+
* One join value in a form both sides of a relation produce for the same SQL value. The parent's
|
|
1911
|
+
* key and a related row's can arrive in different JS forms — a `Date` and the text of the same
|
|
1912
|
+
* instant, `1.50` and `1.500` from decimals of different scale — and `String()` would tell them
|
|
1913
|
+
* apart (it even drops a `Date`'s milliseconds).
|
|
1914
|
+
*/
|
|
1915
|
+
const joinValueKey = (value, column) => {
|
|
1916
|
+
if (value instanceof Date) {
|
|
1917
|
+
if (Number.isNaN(value.getTime())) return String(value);
|
|
1918
|
+
if (column && isDateOnlyColumn(column)) return dateToCalendarDate(value) ?? value.toISOString();
|
|
1919
|
+
return trimIsoFraction(value.toISOString());
|
|
1920
|
+
}
|
|
1921
|
+
if (typeof value === "bigint") return value.toString();
|
|
1922
|
+
if (value instanceof Uint8Array) return Buffer.from(value.buffer, value.byteOffset, value.byteLength).toString("hex");
|
|
1923
|
+
if (typeof value === "string" && column) {
|
|
1924
|
+
if (isMySqlDateTimeStringColumn(column)) return trimIsoFraction(mySqlDateTimeTextToIso(value));
|
|
1925
|
+
if (isPgTimestampStringColumn(column)) return trimIsoFraction(pgTimestampTextToIso(value));
|
|
1926
|
+
if ((column.dataType ?? "") === "string numeric") return canonicalDecimal(value);
|
|
1927
|
+
}
|
|
1928
|
+
return String(value);
|
|
1929
|
+
};
|
|
1858
1930
|
/**
|
|
1859
1931
|
* A string identifying one combination of join-column values, for grouping a batch's rows by
|
|
1860
|
-
* parent.
|
|
1932
|
+
* parent. `columns` are the columns the values were read from, in the same order: the parent's
|
|
1933
|
+
* join columns for a parent key, the target's (or the junction's) for a related row. Values the
|
|
1934
|
+
* database compares equal give the same key — see {@link joinValueKey}.
|
|
1935
|
+
*/
|
|
1936
|
+
const joinKeyOf = (values, columns) => values.length === 1 ? joinValueKey(values[0], columns?.[0]) : JSON.stringify(values.map((value, i) => joinValueKey(value, columns?.[i])));
|
|
1937
|
+
/**
|
|
1938
|
+
* The parent row's values for the relation's join columns, as the database holds them. The row
|
|
1939
|
+
* a relation field resolves under has been remapped to its GraphQL form — a `Date` is an ISO
|
|
1940
|
+
* string by then, a `bigint` a decimal string — which can neither be bound back through every
|
|
1941
|
+
* column's encoder (SQLite's integer timestamp calls `getTime()` on it) nor be compared with the
|
|
1942
|
+
* related rows' decoded values. The values from before remapping are used when the row kept
|
|
1943
|
+
* them; otherwise the output form is mapped back the way an input value would be.
|
|
1944
|
+
*/
|
|
1945
|
+
const parentJoinValues = (parent, pairs) => pairs.map(({ localColPropName, localCol }) => {
|
|
1946
|
+
const raw = rawColumnValue(parent, localColPropName);
|
|
1947
|
+
if (raw.found) return raw.value;
|
|
1948
|
+
const value = parent[localColPropName];
|
|
1949
|
+
if (value == null) return value;
|
|
1950
|
+
try {
|
|
1951
|
+
return remapFromGraphQLCore(value, localCol, localColPropName);
|
|
1952
|
+
} catch {
|
|
1953
|
+
return value;
|
|
1954
|
+
}
|
|
1955
|
+
});
|
|
1956
|
+
/**
|
|
1957
|
+
* At most this many bound parameters for the parent keys of one batched relation query. SQLite
|
|
1958
|
+
* accepts 32,766 per statement, Postgres and MySQL 65,535; the rest is left to the request's own
|
|
1959
|
+
* filters. A batch with more parents runs as several queries.
|
|
1960
|
+
*/
|
|
1961
|
+
const MAX_PARENT_KEY_PARAMS = 1e4;
|
|
1962
|
+
/**
|
|
1963
|
+
* The parent keys of one batch split into the groups each query reads, so that no query binds
|
|
1964
|
+
* more than {@link MAX_PARENT_KEY_PARAMS} key values. Every parent lands in exactly one group, so
|
|
1965
|
+
* a per-parent result — its rows, its window, its count — is the same whichever query made it.
|
|
1966
|
+
*/
|
|
1967
|
+
const parentKeyChunks = (keys, width) => {
|
|
1968
|
+
const size = Math.max(1, Math.floor(MAX_PARENT_KEY_PARAMS / Math.max(1, width)));
|
|
1969
|
+
const chunks = [];
|
|
1970
|
+
for (let i = 0; i < keys.length; i += size) chunks.push(keys.slice(i, i + size));
|
|
1971
|
+
return chunks;
|
|
1972
|
+
};
|
|
1973
|
+
/**
|
|
1974
|
+
* `or` over `conditions` as a balanced tree. A flat `a OR b OR c …` is parsed left-deep, and
|
|
1975
|
+
* SQLite refuses an expression more than 1,000 levels deep; balanced, a batch of thousands of
|
|
1976
|
+
* parents stays a few levels deep.
|
|
1861
1977
|
*/
|
|
1862
|
-
const
|
|
1978
|
+
const balancedOr = (conditions) => {
|
|
1979
|
+
if (conditions.length <= 2) return (0, drizzle_orm.or)(...conditions);
|
|
1980
|
+
const middle = Math.ceil(conditions.length / 2);
|
|
1981
|
+
return (0, drizzle_orm.or)(balancedOr(conditions.slice(0, middle)), balancedOr(conditions.slice(middle)));
|
|
1982
|
+
};
|
|
1863
1983
|
/**
|
|
1864
1984
|
* The rows whose `cols` hold one of `keys`: `col IN (…)` for a single join column, and an `OR`
|
|
1865
1985
|
* 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.
|
|
1986
|
+
* every dialect accepts, unlike a row-value `IN` list. The `OR` is nested as a balanced tree.
|
|
1987
|
+
* Callers split a large batch with {@link parentKeyChunks} first, which keeps either form within
|
|
1988
|
+
* the dialects' parameter limits.
|
|
1867
1989
|
*/
|
|
1868
|
-
const parentKeyCondition = (cols, keys) => cols.length === 1 ? (0, drizzle_orm.inArray)(cols[0], keys.map((key) => key[0])) : (
|
|
1990
|
+
const parentKeyCondition = (cols, keys) => cols.length === 1 ? (0, drizzle_orm.inArray)(cols[0], keys.map((key) => key[0])) : balancedOr(keys.map((key) => (0, drizzle_orm.and)(...cols.map((col, i) => (0, drizzle_orm.eq)(col, key[i])))));
|
|
1869
1991
|
/**
|
|
1870
1992
|
* The junction table and its two columns for a `.through()` (many-to-many) relation:
|
|
1871
1993
|
* `sourceCol` holds the parent's key, `targetCol` the target's. `undefined` for a relation
|
|
@@ -4961,7 +5083,7 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
4961
5083
|
if (!parentTable || !targetTable) return;
|
|
4962
5084
|
const joinPairs = extractRelationJoinColumnPairs(relEntry, parentTable, targetTable);
|
|
4963
5085
|
if (!joinPairs) return;
|
|
4964
|
-
const
|
|
5086
|
+
const localCols = joinPairs.map((pair) => pair.localCol);
|
|
4965
5087
|
const foreignCol = joinPairs[0].foreignCol;
|
|
4966
5088
|
const junction = extractJunctionColumns(relEntry);
|
|
4967
5089
|
const groupCols = junction ? [junction.sourceCol] : joinPairs.map((pair) => pair.foreignCol);
|
|
@@ -5007,7 +5129,7 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
5007
5129
|
};
|
|
5008
5130
|
const resolve = async (parent, args, context, info) => {
|
|
5009
5131
|
if (parent[relationName] !== void 0) return parent[relationName];
|
|
5010
|
-
const localValues =
|
|
5132
|
+
const localValues = parentJoinValues(parent, joinPairs);
|
|
5011
5133
|
if (localValues.some((value) => value == null)) return isOne ? null : [];
|
|
5012
5134
|
const { where: whereArg, limit: requestedLimit, offset, after, deleted } = args ?? {};
|
|
5013
5135
|
const { orderByArg, limit, distinct, cursorEntries, cursorValues, columns, loaderKey } = getOrCreateRequestValue(context, info?.fieldNodes?.[0], `relation:${tableName}::${relationName}`, () => {
|
|
@@ -5051,55 +5173,60 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
5051
5173
|
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" });
|
|
5052
5174
|
return getOrCreateLoader(context, loaderKey, async (parentKeys) => {
|
|
5053
5175
|
const executor = resolveExecutor(db, context);
|
|
5054
|
-
const uniqueKeys = [...new Map(parentKeys.map((key) => [joinKeyOf(key), key])).values()];
|
|
5176
|
+
const uniqueKeys = [...new Map(parentKeys.map((key) => [joinKeyOf(key, localCols), key])).values()];
|
|
5055
5177
|
const scope = resolveScope(policies, context, filterCtx);
|
|
5056
|
-
|
|
5057
|
-
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
|
|
5061
|
-
|
|
5062
|
-
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
const
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5178
|
+
const loadChunk = async (chunkKeys) => {
|
|
5179
|
+
let whereCondition = withScope(scope, targetTableName, targetTable, (0, drizzle_orm.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);
|
|
5180
|
+
if (distinct) {
|
|
5181
|
+
const keys = await selectDistinctKeys({
|
|
5182
|
+
db: executor,
|
|
5183
|
+
table: targetTable,
|
|
5184
|
+
tableName: targetTableName,
|
|
5185
|
+
distinct,
|
|
5186
|
+
pkNames: targetPkNames,
|
|
5187
|
+
where: whereCondition,
|
|
5188
|
+
orderBy: orderByArg,
|
|
5189
|
+
partitionBy: groupCols,
|
|
5190
|
+
junction: junction ? {
|
|
5191
|
+
table: junction.table,
|
|
5192
|
+
on: (0, drizzle_orm.eq)(junction.targetCol, foreignCol),
|
|
5193
|
+
parentKey: junction.sourceCol
|
|
5194
|
+
} : void 0
|
|
5195
|
+
});
|
|
5196
|
+
if (!keys.length) return /* @__PURE__ */ new Map();
|
|
5197
|
+
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));
|
|
5198
|
+
}
|
|
5199
|
+
const selection = junction ? {
|
|
5200
|
+
...columns ?? (0, drizzle_orm.getColumns)(targetTable),
|
|
5201
|
+
[PARENT_KEY]: junction.sourceCol
|
|
5202
|
+
} : columns;
|
|
5203
|
+
let rows;
|
|
5204
|
+
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);
|
|
5205
|
+
else {
|
|
5206
|
+
let q = withJunction((selection ? executor.select(selection) : executor.select()).from(targetTable), junction, foreignCol).where(whereCondition);
|
|
5207
|
+
const orderExprs = cursorEntries ? cursorOrderExprs(targetTable, cursorEntries) : orderByArg ? extractOrderBy(targetTable, orderByArg, relationFilterCtx(filterCtx, targetTableName, scope), whereArg) : [];
|
|
5208
|
+
if (orderExprs.length) q = q.orderBy(...orderExprs);
|
|
5209
|
+
rows = await q;
|
|
5210
|
+
}
|
|
5211
|
+
if (cursorEntries) attachRowCursors(rows, cursorEntries);
|
|
5212
|
+
const keyOf = (row) => {
|
|
5213
|
+
const key = joinKeyOf(groupKeys.map((name) => row[name]), groupCols);
|
|
5214
|
+
if (junction) delete row[PARENT_KEY];
|
|
5215
|
+
return key;
|
|
5216
|
+
};
|
|
5217
|
+
if (isOne) {
|
|
5218
|
+
const byKey = new Map(rows.map((row) => [keyOf(row), row]));
|
|
5219
|
+
remapToGraphQLArrayOutput(rows, targetTableName, targetTable);
|
|
5220
|
+
return byKey;
|
|
5221
|
+
}
|
|
5222
|
+
const grouped = new Map(chunkKeys.map((key) => [joinKeyOf(key, localCols), []]));
|
|
5223
|
+
for (const row of rows) grouped.get(keyOf(row))?.push(row);
|
|
5096
5224
|
remapToGraphQLArrayOutput(rows, targetTableName, targetTable);
|
|
5097
|
-
return
|
|
5098
|
-
}
|
|
5099
|
-
const
|
|
5100
|
-
for (const
|
|
5101
|
-
|
|
5102
|
-
return parentKeys.map((key) => grouped.get(joinKeyOf(key)) ?? []);
|
|
5225
|
+
return grouped;
|
|
5226
|
+
};
|
|
5227
|
+
const results = /* @__PURE__ */ new Map();
|
|
5228
|
+
for (const chunk of parentKeyChunks(uniqueKeys, groupCols.length)) for (const [key, value] of await loadChunk(chunk)) results.set(key, value);
|
|
5229
|
+
return parentKeys.map((key) => results.get(joinKeyOf(key, localCols)) ?? (isOne ? null : []));
|
|
5103
5230
|
}).load(localValues);
|
|
5104
5231
|
};
|
|
5105
5232
|
return async (parent, args, context, info) => {
|
|
@@ -6185,7 +6312,7 @@ const createRelationAggregateFactory = (db, tables, cacheCtx, typeNameMapper, fi
|
|
|
6185
6312
|
}
|
|
6186
6313
|
return;
|
|
6187
6314
|
}
|
|
6188
|
-
const
|
|
6315
|
+
const localCols = joinPairs.map((pair) => pair.localCol);
|
|
6189
6316
|
const foreignCol = joinPairs[0].foreignCol;
|
|
6190
6317
|
const junction = extractJunctionColumns(relEntry);
|
|
6191
6318
|
if (junction === null) return;
|
|
@@ -6215,23 +6342,26 @@ const createRelationAggregateFactory = (db, tables, cacheCtx, typeNameMapper, fi
|
|
|
6215
6342
|
};
|
|
6216
6343
|
});
|
|
6217
6344
|
if (!Object.keys(request.selection).length) return {};
|
|
6218
|
-
const localValues =
|
|
6345
|
+
const localValues = parentJoinValues(parent, joinPairs);
|
|
6219
6346
|
if (localValues.some((value) => value == null)) return assembleAggregateRow({}, request, target);
|
|
6220
6347
|
const whereArg = args?.where;
|
|
6221
6348
|
const deleted = args?.deleted;
|
|
6222
6349
|
const loader = getOrCreateLoader(context, loaderKey, async (parentKeys) => {
|
|
6223
6350
|
const executor = resolveExecutor(db, context);
|
|
6224
|
-
const uniqueKeys = [...new Map(parentKeys.map((key) => [joinKeyOf(key), key])).values()];
|
|
6351
|
+
const uniqueKeys = [...new Map(parentKeys.map((key) => [joinKeyOf(key, localCols), key])).values()];
|
|
6225
6352
|
const scope = resolveScope(policies, context, filterCtx);
|
|
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
6353
|
const groupSelection = Object.fromEntries(groupAliases.map((alias, i) => [alias, groupCols[i]]));
|
|
6228
|
-
const
|
|
6229
|
-
|
|
6230
|
-
|
|
6231
|
-
|
|
6232
|
-
|
|
6233
|
-
|
|
6234
|
-
|
|
6354
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
6355
|
+
for (const chunk of parentKeyChunks(uniqueKeys, groupCols.length)) {
|
|
6356
|
+
const whereCondition = withScope(scope, targetTableName, targetTable, (0, drizzle_orm.and)(parentKeyCondition(groupCols, chunk), ownCondition, whereArg ? extractFilters(targetTable, targetTableName, whereArg, relationFilterCtx(filterCtx, targetTableName, scope)) : void 0), deleted ?? defaultDeleted);
|
|
6357
|
+
const selected = executor.select({
|
|
6358
|
+
...groupSelection,
|
|
6359
|
+
...request.selection
|
|
6360
|
+
}).from(targetTable);
|
|
6361
|
+
const rows = await (junction ? selected.innerJoin(junction.table, (0, drizzle_orm.eq)(junction.targetCol, foreignCol)) : selected).where(whereCondition).groupBy(...groupCols);
|
|
6362
|
+
for (const row of rows) byKey.set(joinKeyOf(groupAliases.map((alias) => row[alias]), groupCols), row);
|
|
6363
|
+
}
|
|
6364
|
+
return parentKeys.map((key) => byKey.get(joinKeyOf(key, localCols)) ?? {});
|
|
6235
6365
|
});
|
|
6236
6366
|
return assembleAggregateRow(await loader.load(localValues), request, target);
|
|
6237
6367
|
} catch (e) {
|
|
@@ -6540,6 +6670,20 @@ const ALL_OPS = [
|
|
|
6540
6670
|
* accepts NULL.
|
|
6541
6671
|
*/
|
|
6542
6672
|
const isOptionalOnInsert = (column) => !column.notNull || !!column.hasDefault || !!column.defaultFn;
|
|
6673
|
+
/**
|
|
6674
|
+
* The plan fields that carry a relation's own `where` (see {@link relationOwnCondition}).
|
|
6675
|
+
*
|
|
6676
|
+
* A `where` on the target narrows every row selection the write makes, and takes `create`
|
|
6677
|
+
* away: the row it inserts is attached whether or not it satisfies the `where`, so the
|
|
6678
|
+
* relation could come back without it. A `where` inherited from the reverse relation filters
|
|
6679
|
+
* the parent instead, and becomes a per-parent check.
|
|
6680
|
+
*/
|
|
6681
|
+
const ownWherePlan = (relEntry, parentTable, targetTable, localCol) => {
|
|
6682
|
+
const condition = () => relationOwnCondition(relEntry, parentTable, targetTable, [localCol], [localCol]);
|
|
6683
|
+
if (!condition()) return {};
|
|
6684
|
+
if (!isRelationFilterReversed(relEntry.relation ?? relEntry)) return { ownCondition: () => condition() };
|
|
6685
|
+
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 };
|
|
6686
|
+
};
|
|
6543
6687
|
/** The property name a column is stored under on its table, matched by identity. */
|
|
6544
6688
|
const propNameOf = (table, column) => {
|
|
6545
6689
|
if (!column) return;
|
|
@@ -6560,7 +6704,7 @@ const propNameOf = (table, column) => {
|
|
|
6560
6704
|
* `onUnresolved` is called when the relation's columns are of a supported shape but cannot be
|
|
6561
6705
|
* matched to the three tables — a drizzle-orm mismatch rather than an unsupported relation.
|
|
6562
6706
|
*/
|
|
6563
|
-
const buildThroughPlan = (relationName, relation, table, targetTableName, targetTable, tableNameOf, onUnresolved) => {
|
|
6707
|
+
const buildThroughPlan = (relationName, relEntry, relation, table, targetTableName, targetTable, tableNameOf, onUnresolved) => {
|
|
6564
6708
|
if ((0, drizzle_orm.is)(relation, drizzle_orm.One)) return;
|
|
6565
6709
|
const junction = relation.throughTable;
|
|
6566
6710
|
const sourceEntries = relation.through?.source;
|
|
@@ -6601,6 +6745,7 @@ const buildThroughPlan = (relationName, relation, table, targetTableName, target
|
|
|
6601
6745
|
targetCol
|
|
6602
6746
|
},
|
|
6603
6747
|
targetKeyColumns: { [foreignColPropName]: foreignCol },
|
|
6748
|
+
...ownWherePlan(relEntry, table, targetTable, localCol),
|
|
6604
6749
|
allowed: /* @__PURE__ */ new Set([
|
|
6605
6750
|
"connect",
|
|
6606
6751
|
"disconnect",
|
|
@@ -6615,8 +6760,9 @@ const buildThroughPlan = (relationName, relation, table, targetTableName, target
|
|
|
6615
6760
|
* rather than a foreign-key one — see {@link buildThroughPlan}.
|
|
6616
6761
|
*
|
|
6617
6762
|
* Skipped, and so absent from the generated inputs rather than failing at runtime:
|
|
6618
|
-
* relations whose join columns cannot be read off the drizzle relation;
|
|
6619
|
-
*
|
|
6763
|
+
* relations whose join columns cannot be read off the drizzle relation; relations joining on
|
|
6764
|
+
* more than one column per side; and relations where neither join column is unique, which
|
|
6765
|
+
* leaves no way to tell which side stores the key.
|
|
6620
6766
|
*/
|
|
6621
6767
|
const buildNestedWritePlans = (tables, relationMap, resolveUniqueSets, resolvePrimaryKeys, extractJoinColumns, tableNameOf = () => void 0) => {
|
|
6622
6768
|
const uniqueCache = /* @__PURE__ */ new Map();
|
|
@@ -6648,10 +6794,11 @@ const buildNestedWritePlans = (tables, relationMap, resolveUniqueSets, resolvePr
|
|
|
6648
6794
|
if (!targetTable) continue;
|
|
6649
6795
|
const omitted = `the '${relationName}' field of its nested-write create and update inputs`;
|
|
6650
6796
|
if (relation.through) {
|
|
6651
|
-
const throughPlan = buildThroughPlan(relationName, relation, table, targetTableName, targetTable, tableNameOf, () => warnUnresolvedJoin(relEntry, tableName, relationName, omitted));
|
|
6797
|
+
const throughPlan = buildThroughPlan(relationName, relEntry, relation, table, targetTableName, targetTable, tableNameOf, () => warnUnresolvedJoin(relEntry, tableName, relationName, omitted));
|
|
6652
6798
|
if (throughPlan) tablePlans[relationName] = throughPlan;
|
|
6653
6799
|
continue;
|
|
6654
6800
|
}
|
|
6801
|
+
if ((relationColumns(relation, "source")?.length ?? 0) > 1 || (relationColumns(relation, "target")?.length ?? 0) > 1) continue;
|
|
6655
6802
|
const joinColumns = extractJoinColumns(relEntry, table, targetTable);
|
|
6656
6803
|
if (!joinColumns) {
|
|
6657
6804
|
warnUnresolvedJoin(relEntry, tableName, relationName, omitted);
|
|
@@ -6674,8 +6821,15 @@ const buildNestedWritePlans = (tables, relationMap, resolveUniqueSets, resolvePr
|
|
|
6674
6821
|
else continue;
|
|
6675
6822
|
const fkColumn = fkSide === "child" ? foreignCol : localCol;
|
|
6676
6823
|
const targetColumns = (0, drizzle_orm.getColumns)(targetTable);
|
|
6677
|
-
const
|
|
6824
|
+
const targetPrimaryKey = resolvePrimaryKeys(targetTable).filter((name) => targetColumns[name]);
|
|
6825
|
+
const targetKeyColumns = Object.fromEntries(targetPrimaryKey.map((name) => [name, targetColumns[name]]));
|
|
6678
6826
|
targetKeyColumns[foreignColPropName] = foreignCol;
|
|
6827
|
+
const ownWhere = ownWherePlan(relEntry, table, targetTable, localCol);
|
|
6828
|
+
const ops = !fkColumn.notNull ? isOne ? [
|
|
6829
|
+
"create",
|
|
6830
|
+
"connect",
|
|
6831
|
+
"disconnect"
|
|
6832
|
+
] : ALL_OPS : ["create", "connect"];
|
|
6679
6833
|
tablePlans[relationName] = {
|
|
6680
6834
|
relationName,
|
|
6681
6835
|
targetTableName,
|
|
@@ -6688,11 +6842,9 @@ const buildNestedWritePlans = (tables, relationMap, resolveUniqueSets, resolvePr
|
|
|
6688
6842
|
fkSide,
|
|
6689
6843
|
canDetach: !fkColumn.notNull,
|
|
6690
6844
|
targetKeyColumns,
|
|
6691
|
-
|
|
6692
|
-
|
|
6693
|
-
|
|
6694
|
-
"disconnect"
|
|
6695
|
-
] : ALL_OPS : ["create", "connect"])
|
|
6845
|
+
targetPrimaryKey,
|
|
6846
|
+
...ownWhere,
|
|
6847
|
+
allowed: new Set(ownWhere.ownCondition ? ops.filter((op) => op !== "create") : ops)
|
|
6696
6848
|
};
|
|
6697
6849
|
}
|
|
6698
6850
|
if (Object.keys(tablePlans).length) plans[tableName] = tablePlans;
|
|
@@ -6767,6 +6919,35 @@ const gateNestedWritePlans = (plans, featureOf, isExcluded) => {
|
|
|
6767
6919
|
}
|
|
6768
6920
|
return omitted;
|
|
6769
6921
|
};
|
|
6922
|
+
/**
|
|
6923
|
+
* Takes out every relation whose foreign key is a `contextValues` column of the table storing
|
|
6924
|
+
* it — the parent's own column for a parent-side relation, the target's for a child-side one,
|
|
6925
|
+
* either junction column for a many-to-many.
|
|
6926
|
+
*
|
|
6927
|
+
* Every operation on such a relation writes that column: `connect`, `disconnect` and `set`
|
|
6928
|
+
* by definition, and a child-side `create` too, since it fills the key in from the parent row
|
|
6929
|
+
* rather than from context. That column is the server's to set — an insert stamps it and an
|
|
6930
|
+
* update never touches it — so writing it from a nested input would let a client pick, or
|
|
6931
|
+
* change, the owner the server assigns. Whole relations are dropped, not single inputs: no
|
|
6932
|
+
* operation on them is left that does not write the key.
|
|
6933
|
+
*
|
|
6934
|
+
* Returns one line per relation taken out, for the build note.
|
|
6935
|
+
*/
|
|
6936
|
+
const dropContextValueRelations = (plans, contextValuesOf) => {
|
|
6937
|
+
const omitted = [];
|
|
6938
|
+
if (!contextValuesOf) return omitted;
|
|
6939
|
+
for (const [tableName, tablePlans] of Object.entries(plans)) {
|
|
6940
|
+
for (const [relationName, plan] of Object.entries(tablePlans)) {
|
|
6941
|
+
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]);
|
|
6942
|
+
if (stamped) {
|
|
6943
|
+
delete tablePlans[relationName];
|
|
6944
|
+
omitted.push(`${tableName}.${relationName} (${stamped[0]}.${stamped[1]} is a contextValues column)`);
|
|
6945
|
+
}
|
|
6946
|
+
}
|
|
6947
|
+
if (!Object.keys(tablePlans).length) delete plans[tableName];
|
|
6948
|
+
}
|
|
6949
|
+
return omitted;
|
|
6950
|
+
};
|
|
6770
6951
|
const NO_COLUMNS = /* @__PURE__ */ new Set();
|
|
6771
6952
|
/**
|
|
6772
6953
|
* Builds the nested-write input types for a schema.
|
|
@@ -6797,7 +6978,8 @@ const createNestedWriteTypes = (params) => {
|
|
|
6797
6978
|
if (cached) return cached;
|
|
6798
6979
|
const omitted = plan.fkSide === "child" ? plan.foreignColPropName : void 0;
|
|
6799
6980
|
const contextColumns = cacheCtx.contextValuesOf?.(plan.targetTableName);
|
|
6800
|
-
const
|
|
6981
|
+
const markerColumn = cacheCtx.softDeleteOf?.(plan.targetTableName)?.columnName;
|
|
6982
|
+
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)]));
|
|
6801
6983
|
const type = new graphql.GraphQLInputObjectType({
|
|
6802
6984
|
name: cacheCtx.typeName({
|
|
6803
6985
|
kind: "nestedWriteInput",
|
|
@@ -6921,6 +7103,8 @@ const createNestedWriteTypes = (params) => {
|
|
|
6921
7103
|
* join columns the whole batch's RETURNING clause needs, without inspecting each row's write.
|
|
6922
7104
|
*/
|
|
6923
7105
|
const mergedOps = (entries) => Object.assign({}, ...entries.map((entry) => entry.ops));
|
|
7106
|
+
/** A to-one `connect` whose filter did not match exactly one row. */
|
|
7107
|
+
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" });
|
|
6924
7108
|
const asArray = (value) => Array.isArray(value) ? value : value === void 0 ? [] : [value];
|
|
6925
7109
|
const supplied = (value) => value !== void 0 && value !== null;
|
|
6926
7110
|
/**
|
|
@@ -7012,6 +7196,8 @@ const registerChildSteps = (steps, plan, op) => {
|
|
|
7012
7196
|
};
|
|
7013
7197
|
/** The input that attaches a row to a to-one, which detaches whatever was attached before. */
|
|
7014
7198
|
const attachingOp = (op) => op["connect"] !== void 0 ? "connect" : op["create"] !== void 0 ? "create" : void 0;
|
|
7199
|
+
/** The child-side relation in `ops` whose target is the parent's own table, if there is one. */
|
|
7200
|
+
const selfTargetingPlan = (tablePlans, tableName, ops) => Object.keys(ops).map((relationName) => tablePlans?.[relationName]).find((plan) => plan?.fkSide === "child" && !plan.through && plan.targetTableName === tableName);
|
|
7015
7201
|
const createNestedWriteRuntime = (params) => {
|
|
7016
7202
|
const { plans, filterCtx, policies, contextValues } = params;
|
|
7017
7203
|
const onWrite = policies?.onWrite;
|
|
@@ -7022,7 +7208,7 @@ const createNestedWriteRuntime = (params) => {
|
|
|
7022
7208
|
const conditionOf = (plan, filter, operation, scope) => {
|
|
7023
7209
|
const condition = extractFilters(plan.targetTable, plan.targetTableName, filter, relationFilterCtx(filterCtx, plan.targetTableName, scope));
|
|
7024
7210
|
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" });
|
|
7025
|
-
return withScope(scope, plan.targetTableName, plan.targetTable, condition);
|
|
7211
|
+
return withScope(scope, plan.targetTableName, plan.targetTable, (0, drizzle_orm.and)(condition, plan.ownCondition?.()));
|
|
7026
7212
|
};
|
|
7027
7213
|
const assertSingleOperation = (plan, op) => {
|
|
7028
7214
|
const given = [
|
|
@@ -7045,22 +7231,24 @@ const createNestedWriteRuntime = (params) => {
|
|
|
7045
7231
|
* error on some of them — the read and the write share the surrounding transaction, so no
|
|
7046
7232
|
* other writer can slip a duplicate in between them.
|
|
7047
7233
|
*/
|
|
7048
|
-
const applyThroughOps = async (executor, plan, key, op, scope, steps) => {
|
|
7234
|
+
const applyThroughOps = async (executor, plan, key, op, scope, steps, context) => {
|
|
7049
7235
|
const through = plan.through;
|
|
7050
7236
|
const link = async (operands, operation) => {
|
|
7051
7237
|
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]));
|
|
7052
7238
|
const rows = [];
|
|
7053
7239
|
for (const filter of operands) for (const targetKey of await selectedTargetKeys(executor, plan, filter, operation, scope)) if (!existing.has(targetKey)) {
|
|
7054
7240
|
existing.add(targetKey);
|
|
7055
|
-
rows.push({
|
|
7241
|
+
rows.push(applyContextValues({
|
|
7056
7242
|
[through.sourceColPropName]: key,
|
|
7057
7243
|
[through.targetColPropName]: targetKey
|
|
7058
|
-
});
|
|
7244
|
+
}, through.tableName ? contextValues?.(through.tableName) : void 0, context));
|
|
7059
7245
|
}
|
|
7060
7246
|
if (rows.length) await steps.run(executor.insert(through.table).values(rows), operation, "insert");
|
|
7061
7247
|
};
|
|
7062
7248
|
if (supplied(op["set"])) {
|
|
7063
|
-
|
|
7249
|
+
const reachable = withScope(scope, plan.targetTableName, plan.targetTable, plan.ownCondition?.());
|
|
7250
|
+
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);
|
|
7251
|
+
await steps.run(executor.delete(through.table).where(unlink), "set", "delete");
|
|
7064
7252
|
await link(asArray(op["set"]), "set");
|
|
7065
7253
|
}
|
|
7066
7254
|
if (supplied(op["disconnect"])) for (const filter of asArray(op["disconnect"])) {
|
|
@@ -7069,7 +7257,7 @@ const createNestedWriteRuntime = (params) => {
|
|
|
7069
7257
|
}
|
|
7070
7258
|
if (supplied(op["connect"])) await link(asArray(op["connect"]), "connect");
|
|
7071
7259
|
};
|
|
7072
|
-
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");
|
|
7260
|
+
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");
|
|
7073
7261
|
return {
|
|
7074
7262
|
enabled: (tableName) => !!plans[tableName],
|
|
7075
7263
|
split: (tableName, values) => {
|
|
@@ -7096,8 +7284,24 @@ const createNestedWriteRuntime = (params) => {
|
|
|
7096
7284
|
const plan = tablePlans[relationName];
|
|
7097
7285
|
if (plan?.fkSide === "child" && !(plan.localColPropName in columns) && allColumns[plan.localColPropName]) columns[plan.localColPropName] = allColumns[plan.localColPropName];
|
|
7098
7286
|
}
|
|
7287
|
+
for (const name of selfTargetingPlan(tablePlans, tableName, ops)?.targetPrimaryKey ?? []) if (!(name in columns) && allColumns[name]) columns[name] = allColumns[name];
|
|
7099
7288
|
return columns;
|
|
7100
7289
|
},
|
|
7290
|
+
createsParentSide: (tableName, ops) => Object.entries(ops).some(([relationName, op]) => plans[tableName]?.[relationName]?.fkSide === "parent" && supplied(op?.["create"])),
|
|
7291
|
+
refreshed: async (executor, tableName, ops, rows) => {
|
|
7292
|
+
const plan = selfTargetingPlan(plans[tableName], tableName, ops);
|
|
7293
|
+
const pk = plan?.targetPrimaryKey ?? [];
|
|
7294
|
+
if (!plan || !pk.length || !rows.length || !pk.every((name) => name in rows[0])) return rows;
|
|
7295
|
+
const columns = (0, drizzle_orm.getColumns)(plan.targetTable);
|
|
7296
|
+
const selection = Object.fromEntries(Object.keys(rows[0]).filter((name) => columns[name]).map((name) => [name, columns[name]]));
|
|
7297
|
+
const keyOf = (row) => pk.map((name) => String(row[name])).join("\0");
|
|
7298
|
+
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])))));
|
|
7299
|
+
const fresh = new Map((await executor.select(selection).from(plan.targetTable).where(match)).map((row) => [keyOf(row), row]));
|
|
7300
|
+
return rows.map((row) => ({
|
|
7301
|
+
...row,
|
|
7302
|
+
...fresh.get(keyOf(row))
|
|
7303
|
+
}));
|
|
7304
|
+
},
|
|
7101
7305
|
applyParentSide: async (executor, tableName, ops, request) => {
|
|
7102
7306
|
const tablePlans = plans[tableName];
|
|
7103
7307
|
const patch = {};
|
|
@@ -7122,10 +7326,11 @@ const createNestedWriteRuntime = (params) => {
|
|
|
7122
7326
|
patch[plan.localColPropName] = created[plan.foreignColPropName];
|
|
7123
7327
|
} else if (op["connect"] !== void 0) {
|
|
7124
7328
|
const rows = await executor.select({ [plan.foreignColPropName]: plan.foreignCol }).from(plan.targetTable).where(conditionOf(plan, op["connect"], "connect", scope));
|
|
7125
|
-
if (rows.length !== 1) throw
|
|
7329
|
+
if (rows.length !== 1) throw connectMatchError(relationName, rows.length);
|
|
7126
7330
|
patch[plan.localColPropName] = rows[0][plan.foreignColPropName];
|
|
7127
7331
|
} else if (op["disconnect"] === true) patch[plan.localColPropName] = null;
|
|
7128
7332
|
}
|
|
7333
|
+
for (const columnName of Object.keys(contextValues?.(tableName) ?? {})) delete patch[columnName];
|
|
7129
7334
|
return patch;
|
|
7130
7335
|
},
|
|
7131
7336
|
applyChildSide: async (executor, tableName, ops, parentRows, request) => {
|
|
@@ -7143,8 +7348,9 @@ const createNestedWriteRuntime = (params) => {
|
|
|
7143
7348
|
for (const parentRow of parentRows) {
|
|
7144
7349
|
const key = parentRow?.[plan.localColPropName];
|
|
7145
7350
|
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" });
|
|
7351
|
+
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" });
|
|
7146
7352
|
if (plan.through) {
|
|
7147
|
-
await applyThroughOps(executor, plan, key, op, scope, steps);
|
|
7353
|
+
await applyThroughOps(executor, plan, key, op, scope, steps, context);
|
|
7148
7354
|
continue;
|
|
7149
7355
|
}
|
|
7150
7356
|
if (op["set"] !== void 0) {
|
|
@@ -7156,14 +7362,18 @@ const createNestedWriteRuntime = (params) => {
|
|
|
7156
7362
|
if (op["disconnect"] === true) await detachAll(executor, plan, key, scope, steps, "disconnect");
|
|
7157
7363
|
} 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");
|
|
7158
7364
|
}
|
|
7365
|
+
if (plan.isOne && op["connect"] !== void 0) {
|
|
7366
|
+
const matched = await executor.select({ [plan.foreignColPropName]: plan.foreignCol }).from(plan.targetTable).where(conditionOf(plan, op["connect"], "connect", scope));
|
|
7367
|
+
if (matched.length !== 1) throw connectMatchError(relationName, matched.length);
|
|
7368
|
+
}
|
|
7159
7369
|
const attachedBy = attachingOp(op);
|
|
7160
7370
|
if (plan.isOne && attachedBy && plan.canDetach && op["set"] === void 0) await detachAll(executor, plan, key, scope, steps, attachedBy);
|
|
7161
7371
|
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");
|
|
7162
7372
|
if (op["create"] !== void 0) {
|
|
7163
|
-
const rows = asArray(op["create"]).map((row) => ({
|
|
7164
|
-
...
|
|
7373
|
+
const rows = asArray(op["create"]).map((row) => applyContextValues({
|
|
7374
|
+
...remapFromGraphQLSingleInput({ ...row }, plan.targetTable),
|
|
7165
7375
|
[plan.foreignColPropName]: key
|
|
7166
|
-
}));
|
|
7376
|
+
}, contextValues?.(plan.targetTableName), context));
|
|
7167
7377
|
if (rows.length) await steps.run(executor.insert(plan.targetTable).values(rows), "create", "insert");
|
|
7168
7378
|
}
|
|
7169
7379
|
}
|
|
@@ -7172,6 +7382,39 @@ const createNestedWriteRuntime = (params) => {
|
|
|
7172
7382
|
}
|
|
7173
7383
|
};
|
|
7174
7384
|
};
|
|
7385
|
+
/** Thrown inside an entry's savepoint to undo a parent-side `create` whose parent was never written. */
|
|
7386
|
+
var NoParentRow = class extends Error {};
|
|
7387
|
+
/**
|
|
7388
|
+
* One parent write with its nested operations around it: the parent-side operations, whose
|
|
7389
|
+
* values `write` receives as a patch, then the write, then the child-side operations — and
|
|
7390
|
+
* the rows it returns read back when a child-side statement may have changed them.
|
|
7391
|
+
*
|
|
7392
|
+
* A parent write can come back empty: an update whose `where` matched nothing, an insert or
|
|
7393
|
+
* upsert that a conflict turned into a no-op. Anything a parent-side `create` inserted would
|
|
7394
|
+
* then be left with nothing pointing at it, so such an entry runs in a savepoint of its own
|
|
7395
|
+
* that is rolled back when the write produced no row, and the entry returns no rows — as it
|
|
7396
|
+
* would have without the nested operation. The rest of the batch is unaffected.
|
|
7397
|
+
*
|
|
7398
|
+
* `executor` must already be a transaction.
|
|
7399
|
+
*/
|
|
7400
|
+
const writeNestedEntry = async (params) => {
|
|
7401
|
+
const { executor, runtime, tableName, ops, write, request } = params;
|
|
7402
|
+
const guarded = runtime.createsParentSide(tableName, ops);
|
|
7403
|
+
const run = async (tx) => {
|
|
7404
|
+
const patch = await runtime.applyParentSide(tx, tableName, ops, request);
|
|
7405
|
+
const rows = await write(tx, patch);
|
|
7406
|
+
if (guarded && !rows.length) throw new NoParentRow();
|
|
7407
|
+
await runtime.applyChildSide(tx, tableName, ops, rows, request);
|
|
7408
|
+
return runtime.refreshed(tx, tableName, ops, rows);
|
|
7409
|
+
};
|
|
7410
|
+
if (!guarded) return run(executor);
|
|
7411
|
+
try {
|
|
7412
|
+
return await executor.transaction(run);
|
|
7413
|
+
} catch (error) {
|
|
7414
|
+
if (error instanceof NoParentRow) return [];
|
|
7415
|
+
throw error;
|
|
7416
|
+
}
|
|
7417
|
+
};
|
|
7175
7418
|
/**
|
|
7176
7419
|
* Runs one parent write per input entry, with that entry's nested operations around it, all
|
|
7177
7420
|
* inside a single transaction — a savepoint when the caller already runs on one, so a failing
|
|
@@ -7190,12 +7433,17 @@ const writeWithNestedOps = async (params) => {
|
|
|
7190
7433
|
return executor.transaction(async (tx) => {
|
|
7191
7434
|
const rows = [];
|
|
7192
7435
|
for (const entry of entries) {
|
|
7193
|
-
const
|
|
7194
|
-
|
|
7195
|
-
|
|
7196
|
-
|
|
7436
|
+
const written = await writeNestedEntry({
|
|
7437
|
+
executor: tx,
|
|
7438
|
+
runtime,
|
|
7439
|
+
tableName,
|
|
7440
|
+
ops: entry.ops,
|
|
7441
|
+
write: (target, patch) => write(target, {
|
|
7442
|
+
...remapValues(entry.columns),
|
|
7443
|
+
...patch
|
|
7444
|
+
}),
|
|
7445
|
+
request
|
|
7197
7446
|
});
|
|
7198
|
-
await runtime.applyChildSide(tx, tableName, entry.ops, written, request);
|
|
7199
7447
|
rows.push(...written);
|
|
7200
7448
|
}
|
|
7201
7449
|
return rows;
|
|
@@ -7208,15 +7456,17 @@ const writeWithNestedOps = async (params) => {
|
|
|
7208
7456
|
*/
|
|
7209
7457
|
const updateWithNestedOps = async (params) => {
|
|
7210
7458
|
const { executor, runtime, tableName, columns, ops, remapValues, write, request } = params;
|
|
7211
|
-
return executor.transaction(
|
|
7212
|
-
|
|
7213
|
-
|
|
7459
|
+
return executor.transaction((tx) => writeNestedEntry({
|
|
7460
|
+
executor: tx,
|
|
7461
|
+
runtime,
|
|
7462
|
+
tableName,
|
|
7463
|
+
ops,
|
|
7464
|
+
write: (target, patch) => write(target, {
|
|
7214
7465
|
...remapValues(columns),
|
|
7215
7466
|
...patch
|
|
7216
|
-
})
|
|
7217
|
-
|
|
7218
|
-
|
|
7219
|
-
});
|
|
7467
|
+
}),
|
|
7468
|
+
request
|
|
7469
|
+
}));
|
|
7220
7470
|
};
|
|
7221
7471
|
//#endregion
|
|
7222
7472
|
//#region src/util/builders/errors.ts
|
|
@@ -7423,6 +7673,10 @@ const createSchemaBuilder = (adapter) => {
|
|
|
7423
7673
|
console.warn(`Drizzle-GraphQL Warning: config.features — nestedWrites leaves out the nested inputs whose table has that write turned off: ${listed}.`);
|
|
7424
7674
|
}
|
|
7425
7675
|
}
|
|
7676
|
+
if (nestedPlans) {
|
|
7677
|
+
const omitted = dropContextValueRelations(nestedPlans, contextValuesOf);
|
|
7678
|
+
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(", ")}.`);
|
|
7679
|
+
}
|
|
7426
7680
|
const nestedTypes = nestedPlans ? createNestedWriteTypes({
|
|
7427
7681
|
plans: nestedPlans,
|
|
7428
7682
|
cacheCtx,
|
|
@@ -8194,7 +8448,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
8194
8448
|
serverOwned: Object.keys(contextColumns ?? {}),
|
|
8195
8449
|
buildWhere: (where) => extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName, scope))
|
|
8196
8450
|
});
|
|
8197
|
-
const
|
|
8451
|
+
const batchQuery = (executor, { values: rows, plan }) => {
|
|
8198
8452
|
let query = executor.insert(table).values(rows).returning(returning);
|
|
8199
8453
|
const setWhere = withScope(scope, tableName, table, plan.setWhere);
|
|
8200
8454
|
query = plan.action === "NOTHING" ? query.onConflictDoNothing(plan.target ? {
|
|
@@ -8206,12 +8460,22 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
8206
8460
|
set: plan.set,
|
|
8207
8461
|
setWhere
|
|
8208
8462
|
});
|
|
8209
|
-
return
|
|
8463
|
+
return query;
|
|
8210
8464
|
};
|
|
8211
|
-
if (batches.length === 1) return
|
|
8212
|
-
return target.transaction(
|
|
8213
|
-
const
|
|
8214
|
-
|
|
8465
|
+
if (batches.length === 1) return await batchQuery(target, batches[0]);
|
|
8466
|
+
return target.transaction((tx) => {
|
|
8467
|
+
const execute = (batch) => {
|
|
8468
|
+
const query = batchQuery(tx, batch);
|
|
8469
|
+
return typeof query.all === "function" ? query.all() : query;
|
|
8470
|
+
};
|
|
8471
|
+
const first = execute(batches[0]);
|
|
8472
|
+
if (typeof first?.then === "function") return (async () => {
|
|
8473
|
+
const rows = [...await first];
|
|
8474
|
+
for (const batch of batches.slice(1)) rows.push(...await execute(batch));
|
|
8475
|
+
return rows;
|
|
8476
|
+
})();
|
|
8477
|
+
const rows = [...first];
|
|
8478
|
+
for (const batch of batches.slice(1)) rows.push(...execute(batch));
|
|
8215
8479
|
return rows;
|
|
8216
8480
|
});
|
|
8217
8481
|
};
|
|
@@ -8736,16 +9000,24 @@ const createSchemaDataGenerator = (adapter) => {
|
|
|
8736
9000
|
const runUpdateManyBatch = ({ executor, entries, table, returning, nested, tableName, request }) => executor.transaction(async (tx) => {
|
|
8737
9001
|
const results = [];
|
|
8738
9002
|
for (const entry of entries) {
|
|
8739
|
-
const
|
|
8740
|
-
|
|
8741
|
-
|
|
8742
|
-
|
|
8743
|
-
|
|
8744
|
-
|
|
8745
|
-
|
|
8746
|
-
|
|
8747
|
-
|
|
8748
|
-
|
|
9003
|
+
const write = async (target, patch) => {
|
|
9004
|
+
const values = {
|
|
9005
|
+
...entry.set,
|
|
9006
|
+
...patch
|
|
9007
|
+
};
|
|
9008
|
+
const writes = Object.keys(values).length > 0;
|
|
9009
|
+
let query = writes ? target.update(table).set(values) : target.select(returning).from(table);
|
|
9010
|
+
if (entry.filters) query = query.where(entry.filters);
|
|
9011
|
+
return await (writes ? query.returning(returning) : query);
|
|
9012
|
+
};
|
|
9013
|
+
results.push(entry.ops ? await writeNestedEntry({
|
|
9014
|
+
executor: tx,
|
|
9015
|
+
runtime: nested,
|
|
9016
|
+
tableName,
|
|
9017
|
+
ops: entry.ops,
|
|
9018
|
+
write,
|
|
9019
|
+
request
|
|
9020
|
+
}) : await write(tx, {}));
|
|
8749
9021
|
}
|
|
8750
9022
|
return results;
|
|
8751
9023
|
});
|
|
@@ -8877,16 +9149,24 @@ const runSqliteUpdateManyBatch = ({ executor, entries, table, returning, anyNest
|
|
|
8877
9149
|
return query.returning(returning).all();
|
|
8878
9150
|
};
|
|
8879
9151
|
const runNestedEntry = async (tx, entry) => {
|
|
8880
|
-
const
|
|
8881
|
-
|
|
8882
|
-
|
|
8883
|
-
|
|
8884
|
-
|
|
8885
|
-
|
|
8886
|
-
|
|
8887
|
-
|
|
8888
|
-
|
|
8889
|
-
|
|
9152
|
+
const write = async (target, patch) => {
|
|
9153
|
+
const values = {
|
|
9154
|
+
...entry.set,
|
|
9155
|
+
...patch
|
|
9156
|
+
};
|
|
9157
|
+
const writes = Object.keys(values).length > 0;
|
|
9158
|
+
let query = writes ? target.update(table).set(values) : target.select(returning).from(table);
|
|
9159
|
+
if (entry.filters) query = query.where(entry.filters);
|
|
9160
|
+
return await (writes ? query.returning(returning) : query);
|
|
9161
|
+
};
|
|
9162
|
+
return entry.ops ? writeNestedEntry({
|
|
9163
|
+
executor: tx,
|
|
9164
|
+
runtime: nested,
|
|
9165
|
+
tableName,
|
|
9166
|
+
ops: entry.ops,
|
|
9167
|
+
write,
|
|
9168
|
+
request
|
|
9169
|
+
}) : write(tx, {});
|
|
8890
9170
|
};
|
|
8891
9171
|
return executor.transaction((tx) => {
|
|
8892
9172
|
if (anyNested) return (async () => {
|