@vantreeseba/drizzle-graphql 15.1.0 → 15.1.2
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 +39 -6
- package/dist/index.cjs +246 -64
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -4
- package/dist/index.d.ts +12 -4
- package/dist/index.js +246 -64
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -385,7 +385,9 @@ type IdentifiedRows = {
|
|
|
385
385
|
*
|
|
386
386
|
* `false` means the arguments do not pin the rows down — an insert whose key the
|
|
387
387
|
* database generates, a `where` that filters on something other than the primary key,
|
|
388
|
-
* a plural update with no filter at all,
|
|
388
|
+
* a plural update with no filter at all, a table with no primary key, or an upsert whose
|
|
389
|
+
* `onConflict` conflicts on another unique key (the row updated is the one holding that
|
|
390
|
+
* key) or can skip a conflicting row (`action: NOTHING`, a `where` guard). `rows` is then
|
|
389
391
|
* a best-effort subset-free superset: every entry in it is a row the key values name,
|
|
390
392
|
* but other filters may narrow the real set further, and it may be empty.
|
|
391
393
|
*/
|
|
@@ -449,7 +451,7 @@ type DrizzleErrorCode =
|
|
|
449
451
|
'DRIZZLE_INVALID_DISTINCT' |
|
|
450
452
|
/** An `onConflict` named a target or update column the upsert cannot use. */
|
|
451
453
|
'DRIZZLE_INVALID_ON_CONFLICT' |
|
|
452
|
-
/** A written value could not be converted to what its column stores. */
|
|
454
|
+
/** A written value could not be converted to what its column stores, or a `limit` / `offset` was negative. */
|
|
453
455
|
'DRIZZLE_INVALID_INPUT_VALUE' |
|
|
454
456
|
/** An update's `set` gave a column that cannot hold null an explicit null. */
|
|
455
457
|
'DRIZZLE_NOT_NULL' |
|
|
@@ -1178,6 +1180,9 @@ type UpdateManyArgs<TTable extends Table> = {
|
|
|
1178
1180
|
* `target` and `where` exist on PostgreSQL and SQLite only: MySQL's
|
|
1179
1181
|
* `ON DUPLICATE KEY UPDATE` fires on whichever unique key was violated and takes no
|
|
1180
1182
|
* predicate, so neither field is generated there.
|
|
1183
|
+
*
|
|
1184
|
+
* `target` may name a partial unique index; its predicate is repeated in the statement's
|
|
1185
|
+
* `ON CONFLICT (…) WHERE …`, which is what lets the database match it.
|
|
1181
1186
|
*/
|
|
1182
1187
|
type UpsertConflictArgs<TTable extends Table> = {
|
|
1183
1188
|
action?: 'UPDATE' | 'NOTHING';
|
|
@@ -1604,7 +1609,9 @@ type SchemaFeatures = {
|
|
|
1604
1609
|
* have — the guarantee is in the input type.
|
|
1605
1610
|
*
|
|
1606
1611
|
* Single-column constraints get no field (`eq` on the column already says it), and a
|
|
1607
|
-
* constraint whose field name is taken by a column or a relation is skipped.
|
|
1612
|
+
* constraint whose field name is taken by a column or a relation is skipped. So is a partial
|
|
1613
|
+
* unique index (`uniqueIndex().on(…).where(…)`): it is unique only among the rows its
|
|
1614
|
+
* predicate matches, so its columns do not name one row of the table. The fields live
|
|
1608
1615
|
* on `<Type>Filters`, so they are accepted wherever a filter is: the `Single` reads and
|
|
1609
1616
|
* writes they are meant for, but also list queries, relation filters and `onConflict.where`.
|
|
1610
1617
|
*
|
|
@@ -2091,7 +2098,8 @@ type BuildSchemaConfig = {
|
|
|
2091
2098
|
* - The column is removed from the generated insert and update inputs, so a client cannot
|
|
2092
2099
|
* send it at all — the schema itself documents that it is server-owned.
|
|
2093
2100
|
* - Every insert stamps it, including the rows created by a nested `create`.
|
|
2094
|
-
* - Updates never write it: an update cannot hand a row to another owner.
|
|
2101
|
+
* - Updates never write it: an update cannot hand a row to another owner. That includes
|
|
2102
|
+
* the conflict branch of an upsert, whose `onConflict.update` does not offer it.
|
|
2095
2103
|
* - Keyed by the Drizzle schema key, then by the column's property name (not its database
|
|
2096
2104
|
* name), the same keying {@link BuildSchemaConfig.exclude} uses.
|
|
2097
2105
|
*/
|
package/dist/index.d.ts
CHANGED
|
@@ -385,7 +385,9 @@ type IdentifiedRows = {
|
|
|
385
385
|
*
|
|
386
386
|
* `false` means the arguments do not pin the rows down — an insert whose key the
|
|
387
387
|
* database generates, a `where` that filters on something other than the primary key,
|
|
388
|
-
* a plural update with no filter at all,
|
|
388
|
+
* a plural update with no filter at all, a table with no primary key, or an upsert whose
|
|
389
|
+
* `onConflict` conflicts on another unique key (the row updated is the one holding that
|
|
390
|
+
* key) or can skip a conflicting row (`action: NOTHING`, a `where` guard). `rows` is then
|
|
389
391
|
* a best-effort subset-free superset: every entry in it is a row the key values name,
|
|
390
392
|
* but other filters may narrow the real set further, and it may be empty.
|
|
391
393
|
*/
|
|
@@ -449,7 +451,7 @@ type DrizzleErrorCode =
|
|
|
449
451
|
'DRIZZLE_INVALID_DISTINCT' |
|
|
450
452
|
/** An `onConflict` named a target or update column the upsert cannot use. */
|
|
451
453
|
'DRIZZLE_INVALID_ON_CONFLICT' |
|
|
452
|
-
/** A written value could not be converted to what its column stores. */
|
|
454
|
+
/** A written value could not be converted to what its column stores, or a `limit` / `offset` was negative. */
|
|
453
455
|
'DRIZZLE_INVALID_INPUT_VALUE' |
|
|
454
456
|
/** An update's `set` gave a column that cannot hold null an explicit null. */
|
|
455
457
|
'DRIZZLE_NOT_NULL' |
|
|
@@ -1178,6 +1180,9 @@ type UpdateManyArgs<TTable extends Table> = {
|
|
|
1178
1180
|
* `target` and `where` exist on PostgreSQL and SQLite only: MySQL's
|
|
1179
1181
|
* `ON DUPLICATE KEY UPDATE` fires on whichever unique key was violated and takes no
|
|
1180
1182
|
* predicate, so neither field is generated there.
|
|
1183
|
+
*
|
|
1184
|
+
* `target` may name a partial unique index; its predicate is repeated in the statement's
|
|
1185
|
+
* `ON CONFLICT (…) WHERE …`, which is what lets the database match it.
|
|
1181
1186
|
*/
|
|
1182
1187
|
type UpsertConflictArgs<TTable extends Table> = {
|
|
1183
1188
|
action?: 'UPDATE' | 'NOTHING';
|
|
@@ -1604,7 +1609,9 @@ type SchemaFeatures = {
|
|
|
1604
1609
|
* have — the guarantee is in the input type.
|
|
1605
1610
|
*
|
|
1606
1611
|
* Single-column constraints get no field (`eq` on the column already says it), and a
|
|
1607
|
-
* constraint whose field name is taken by a column or a relation is skipped.
|
|
1612
|
+
* constraint whose field name is taken by a column or a relation is skipped. So is a partial
|
|
1613
|
+
* unique index (`uniqueIndex().on(…).where(…)`): it is unique only among the rows its
|
|
1614
|
+
* predicate matches, so its columns do not name one row of the table. The fields live
|
|
1608
1615
|
* on `<Type>Filters`, so they are accepted wherever a filter is: the `Single` reads and
|
|
1609
1616
|
* writes they are meant for, but also list queries, relation filters and `onConflict.where`.
|
|
1610
1617
|
*
|
|
@@ -2091,7 +2098,8 @@ type BuildSchemaConfig = {
|
|
|
2091
2098
|
* - The column is removed from the generated insert and update inputs, so a client cannot
|
|
2092
2099
|
* send it at all — the schema itself documents that it is server-owned.
|
|
2093
2100
|
* - Every insert stamps it, including the rows created by a nested `create`.
|
|
2094
|
-
* - Updates never write it: an update cannot hand a row to another owner.
|
|
2101
|
+
* - Updates never write it: an update cannot hand a row to another owner. That includes
|
|
2102
|
+
* the conflict branch of an upsert, whose `onConflict.update` does not offer it.
|
|
2095
2103
|
* - Keyed by the Drizzle schema key, then by the column's property name (not its database
|
|
2096
2104
|
* name), the same keying {@link BuildSchemaConfig.exclude} uses.
|
|
2097
2105
|
*/
|
package/dist/index.js
CHANGED
|
@@ -1592,13 +1592,20 @@ const nullableOps = {
|
|
|
1592
1592
|
isNull,
|
|
1593
1593
|
isNotNull
|
|
1594
1594
|
};
|
|
1595
|
+
/** Operators whose operand is an on/off flag rather than a value: `false` adds no condition. */
|
|
1596
|
+
const FLAG_OPERATORS = /* @__PURE__ */ new Set([
|
|
1597
|
+
"isNull",
|
|
1598
|
+
"isNotNull",
|
|
1599
|
+
"isEmpty"
|
|
1600
|
+
]);
|
|
1595
1601
|
const extractFiltersColumn = (column, columnName, operators) => {
|
|
1596
1602
|
const { OR, AND, NOT, insensitive, ...restOperators } = operators;
|
|
1597
1603
|
const foldCase = insensitive === true;
|
|
1598
1604
|
const entries = Object.entries(restOperators);
|
|
1599
1605
|
const variants = [];
|
|
1600
1606
|
for (const [operatorName, operatorValue] of entries) {
|
|
1601
|
-
if (operatorValue === null || operatorValue ===
|
|
1607
|
+
if (operatorValue === null || operatorValue === void 0) continue;
|
|
1608
|
+
if (operatorValue === false && FLAG_OPERATORS.has(operatorName)) continue;
|
|
1602
1609
|
if (operatorName in singleValueOps) {
|
|
1603
1610
|
const singleValue = remapFromGraphQLCore(operatorValue, column, columnName);
|
|
1604
1611
|
variants.push(foldCase ? lowerComparison(column, operatorName, singleValue) : singleValueOps[operatorName](column, singleValue));
|
|
@@ -2387,8 +2394,8 @@ const columnEnumCache = /* @__PURE__ */ new WeakMap();
|
|
|
2387
2394
|
* input field the enum would have typed, rather than emitting an empty enum, which is
|
|
2388
2395
|
* invalid GraphQL.
|
|
2389
2396
|
*/
|
|
2390
|
-
const generateColumnEnum = (table, enumName, description, predicate = () => true) => {
|
|
2391
|
-
const cacheKey = `${enumName}\u0000${excludedColumnsKey(table)}`;
|
|
2397
|
+
const generateColumnEnum = (table, enumName, description, predicate = () => true, variant = "") => {
|
|
2398
|
+
const cacheKey = `${enumName}\u0000${excludedColumnsKey(table)}\u0000${variant}`;
|
|
2392
2399
|
let tableCache = columnEnumCache.get(table);
|
|
2393
2400
|
const cached = tableCache?.get(cacheKey);
|
|
2394
2401
|
if (cached) return cached;
|
|
@@ -2448,6 +2455,9 @@ const conflictActionEnumType = (typeName) => sharedType(typeName, {
|
|
|
2448
2455
|
* `ON DUPLICATE KEY UPDATE` fires on any unique key and takes no predicate, so offering
|
|
2449
2456
|
* either there would mean silently ignoring it.
|
|
2450
2457
|
*
|
|
2458
|
+
* `update` never offers a `contextValues` column: the server owns it, and the conflict
|
|
2459
|
+
* branch is an update, which must not hand the row to another owner.
|
|
2460
|
+
*
|
|
2451
2461
|
* Returns `undefined` when the table has nothing to conflict on (`withTarget` dialects
|
|
2452
2462
|
* only) — the caller then generates no upsert mutations for that table at all, rather than
|
|
2453
2463
|
* an operation whose every call is a database error.
|
|
@@ -2455,12 +2465,13 @@ const conflictActionEnumType = (typeName) => sharedType(typeName, {
|
|
|
2455
2465
|
const generateOnConflictInput = (params) => {
|
|
2456
2466
|
const { table, tableName, typeName, uniqueSets, tableFilters, withTarget, cacheCtx } = params;
|
|
2457
2467
|
const resolveName = cacheCtx.typeName;
|
|
2468
|
+
const serverOwned = Object.keys(cacheCtx.contextValuesOf?.(tableName) ?? {}).sort();
|
|
2458
2469
|
const updateEnum = generateColumnEnum(table, resolveName({
|
|
2459
2470
|
kind: "columnEnum",
|
|
2460
2471
|
defaultName: `${typeName}UpdateColumn`,
|
|
2461
2472
|
table: tableName,
|
|
2462
2473
|
operation: "conflictUpdate"
|
|
2463
|
-
}), `Columns of ${typeName} that an upsert can overwrite
|
|
2474
|
+
}), `Columns of ${typeName} that an upsert can overwrite`, (_column, columnName) => !serverOwned.includes(columnName), serverOwned.join(","));
|
|
2464
2475
|
if (!updateEnum) return;
|
|
2465
2476
|
const fields = {
|
|
2466
2477
|
action: {
|
|
@@ -2470,11 +2481,11 @@ const generateOnConflictInput = (params) => {
|
|
|
2470
2481
|
},
|
|
2471
2482
|
update: {
|
|
2472
2483
|
type: new GraphQLList(new GraphQLNonNull(updateEnum)),
|
|
2473
|
-
description:
|
|
2484
|
+
description: `Columns to overwrite on conflict. Defaults to every column the request supplied, minus the ${withTarget ? "conflict target" : "primary key"}. Columns the request did not supply cannot be listed here — there would be no value to write.`
|
|
2474
2485
|
}
|
|
2475
2486
|
};
|
|
2476
2487
|
if (withTarget) {
|
|
2477
|
-
const uniqueColumns = new Set(uniqueSets.
|
|
2488
|
+
const uniqueColumns = new Set(uniqueSets.flatMap((set) => set.columns));
|
|
2478
2489
|
const targetEnum = generateColumnEnum(table, resolveName({
|
|
2479
2490
|
kind: "columnEnum",
|
|
2480
2491
|
defaultName: `${typeName}ConflictTarget`,
|
|
@@ -2502,55 +2513,88 @@ const generateOnConflictInput = (params) => {
|
|
|
2502
2513
|
});
|
|
2503
2514
|
};
|
|
2504
2515
|
/**
|
|
2505
|
-
* Turns the request's `onConflict` argument and the rows it is inserting into the
|
|
2506
|
-
* dialect should
|
|
2516
|
+
* Turns the request's `onConflict` argument and the rows it is inserting into the
|
|
2517
|
+
* statements a dialect should run — one per run of consecutive rows that write the same
|
|
2518
|
+
* columns on conflict.
|
|
2519
|
+
*
|
|
2520
|
+
* The split is what keeps a partial row partial. A multi-row insert fills a key one row
|
|
2521
|
+
* left out with DEFAULT, and `excluded.<column>` would carry that default onto the existing
|
|
2522
|
+
* row, so rows that supply different columns cannot share a `SET` list. Splitting into
|
|
2523
|
+
* *consecutive* runs, rather than grouping, keeps the rows a caller gets back in the order
|
|
2524
|
+
* it sent them. A batch whose rows agree — the usual case — is still one statement.
|
|
2507
2525
|
*
|
|
2508
2526
|
* `excludedRef` names the row that failed to insert in the dialect's own terms
|
|
2509
2527
|
* (`excluded.col` on PostgreSQL and SQLite, `values(col)` on MySQL), which is what makes a
|
|
2510
2528
|
* batch upsert update each row with its own values instead of the last row's.
|
|
2511
2529
|
*
|
|
2530
|
+
* Never written on conflict, even when supplied: the conflict target (it already matches);
|
|
2531
|
+
* the primary key on a dialect with no target (MySQL's conflict can be on another unique
|
|
2532
|
+
* key, and copying a generated id over the existing row's would move it); and `serverOwned`
|
|
2533
|
+
* — the `contextValues` columns, which an update never reassigns.
|
|
2534
|
+
*
|
|
2512
2535
|
* An UPDATE with nothing left to write degrades to NOTHING: `DO UPDATE SET` with an empty
|
|
2513
2536
|
* body is not valid SQL, and doing nothing is what the request asked for anyway.
|
|
2514
2537
|
*/
|
|
2515
2538
|
const resolveConflictPlan = (params) => {
|
|
2516
2539
|
const { table, values, onConflict, pkNames, uniqueSets, excludedRef, withTarget, buildWhere } = params;
|
|
2517
2540
|
const columns = getColumns(table);
|
|
2541
|
+
const serverOwned = new Set(params.serverOwned ?? []);
|
|
2518
2542
|
let target;
|
|
2543
|
+
let targetWhere;
|
|
2519
2544
|
if (withTarget) {
|
|
2520
2545
|
const targetNames = onConflict?.target?.length ? onConflict.target : [...pkNames];
|
|
2521
2546
|
if (!targetNames.length) throw drizzleError("Unable to upsert: no conflict target was given and this table has no primary key. Pass onConflict.target.", { code: "DRIZZLE_INVALID_ON_CONFLICT" });
|
|
2522
2547
|
const requested = [...targetNames].sort().join(",");
|
|
2523
|
-
|
|
2548
|
+
const matched = uniqueSets.find((set) => [...set.columns].sort().join(",") === requested);
|
|
2549
|
+
if (!matched) throw drizzleError(`Unable to upsert: [${targetNames.join(", ")}] is not a unique constraint on this table. Valid conflict targets: ${uniqueSets.map((set) => `[${set.columns.join(", ")}]`).join(", ")}.`, { code: "DRIZZLE_INVALID_ON_CONFLICT" });
|
|
2524
2550
|
target = targetNames.map((name) => columns[name]);
|
|
2551
|
+
targetWhere = matched.where;
|
|
2525
2552
|
}
|
|
2526
|
-
|
|
2553
|
+
const nothing = {
|
|
2527
2554
|
action: "NOTHING",
|
|
2528
2555
|
target,
|
|
2556
|
+
targetWhere,
|
|
2529
2557
|
set: {},
|
|
2530
2558
|
setWhere: void 0
|
|
2531
2559
|
};
|
|
2560
|
+
if ((onConflict?.action ?? "UPDATE") === "NOTHING") return [{
|
|
2561
|
+
values,
|
|
2562
|
+
plan: nothing
|
|
2563
|
+
}];
|
|
2532
2564
|
const supplied = new Set(values.flatMap((row) => Object.keys(row)));
|
|
2533
|
-
const
|
|
2534
|
-
let
|
|
2565
|
+
const neverWritten = new Set(withTarget && onConflict?.target?.length ? onConflict.target : pkNames);
|
|
2566
|
+
let candidates;
|
|
2535
2567
|
if (onConflict?.update?.length) {
|
|
2568
|
+
const owned = onConflict.update.filter((name) => serverOwned.has(name));
|
|
2569
|
+
if (owned.length) throw drizzleError(`Unable to upsert: onConflict.update lists ${owned.join(", ")}, which the server sets and an update never overwrites.`, { code: "DRIZZLE_INVALID_ON_CONFLICT" });
|
|
2536
2570
|
const unsupplied = onConflict.update.filter((name) => !supplied.has(name));
|
|
2537
2571
|
if (unsupplied.length) throw drizzleError(`Unable to upsert: onConflict.update lists ${unsupplied.join(", ")}, which the values do not supply.`, { code: "DRIZZLE_INVALID_ON_CONFLICT" });
|
|
2538
|
-
|
|
2539
|
-
} else
|
|
2540
|
-
if (!updateNames.length) return {
|
|
2541
|
-
action: "NOTHING",
|
|
2542
|
-
target,
|
|
2543
|
-
set: {},
|
|
2544
|
-
setWhere: void 0
|
|
2545
|
-
};
|
|
2546
|
-
const set = Object.fromEntries(updateNames.map((name) => [name, excludedRef(columns[name].name)]));
|
|
2572
|
+
candidates = onConflict.update;
|
|
2573
|
+
} else candidates = [...supplied].filter((name) => !neverWritten.has(name) && !serverOwned.has(name));
|
|
2547
2574
|
const setWhere = onConflict?.where && buildWhere ? buildWhere(onConflict.where) : void 0;
|
|
2548
|
-
|
|
2575
|
+
const planFor = (updateNames) => updateNames.length ? {
|
|
2549
2576
|
action: "UPDATE",
|
|
2550
2577
|
target,
|
|
2551
|
-
|
|
2578
|
+
targetWhere,
|
|
2579
|
+
set: Object.fromEntries(updateNames.map((name) => [name, excludedRef(columns[name].name)])),
|
|
2552
2580
|
setWhere
|
|
2553
|
-
};
|
|
2581
|
+
} : nothing;
|
|
2582
|
+
const batches = [];
|
|
2583
|
+
for (const row of values) {
|
|
2584
|
+
const updateNames = candidates.filter((name) => name in row);
|
|
2585
|
+
const key = updateNames.join(",");
|
|
2586
|
+
const last = batches[batches.length - 1];
|
|
2587
|
+
if (last?.key === key) last.values.push(row);
|
|
2588
|
+
else batches.push({
|
|
2589
|
+
key,
|
|
2590
|
+
values: [row],
|
|
2591
|
+
plan: planFor(updateNames)
|
|
2592
|
+
});
|
|
2593
|
+
}
|
|
2594
|
+
return batches.map(({ values: rows, plan }) => ({
|
|
2595
|
+
values: rows,
|
|
2596
|
+
plan
|
|
2597
|
+
}));
|
|
2554
2598
|
};
|
|
2555
2599
|
/** `excluded.<column>` — PostgreSQL and SQLite name the rejected row this way. */
|
|
2556
2600
|
const excludedColumnRef = (columnName) => sql`excluded.${sql.identifier(columnName)}`;
|
|
@@ -2732,6 +2776,45 @@ const cursorOrderExprs = (table, entries) => {
|
|
|
2732
2776
|
return entries.flatMap(([column, direction, nulls]) => orderExpressions(cols[column], direction, nulls ?? void 0, cols[column]));
|
|
2733
2777
|
};
|
|
2734
2778
|
/**
|
|
2779
|
+
* Property a row carries an ordering column's full-precision text under, when the column holds
|
|
2780
|
+
* more than a JS Date can. Namespaced like {@link ROW_CURSOR_PROP}, and removed from the row
|
|
2781
|
+
* once its cursor is encoded.
|
|
2782
|
+
*/
|
|
2783
|
+
const preciseTextProp = (column) => `__drizzle_graphql_cursor_text_${column}`;
|
|
2784
|
+
/**
|
|
2785
|
+
* Whether a column reads into a JS Date (milliseconds) while the database keeps finer
|
|
2786
|
+
* fractional seconds: a PostgreSQL `timestamp` / `timestamptz` in `mode: 'date'` (precision 6
|
|
2787
|
+
* unless it says otherwise) or a MySQL `datetime` / `timestamp` in `mode: 'date'` with
|
|
2788
|
+
* `fsp` above 3. String-mode columns already arrive as the database's own text, and `time` is
|
|
2789
|
+
* always a string, so neither loses anything.
|
|
2790
|
+
*/
|
|
2791
|
+
const outgrowsJsDate = (column) => {
|
|
2792
|
+
if (is(column, PgTimestamp)) return column.precision === void 0 || column.precision > 3;
|
|
2793
|
+
if (is(column, MySqlDateTime) || is(column, MySqlTimestamp)) return (column.fsp ?? 0) > 3;
|
|
2794
|
+
return false;
|
|
2795
|
+
};
|
|
2796
|
+
/**
|
|
2797
|
+
* Extra SQL expressions a cursor-computing query selects alongside the row: for each ordering
|
|
2798
|
+
* column whose values a JS Date would truncate, the value as the database's own text. The
|
|
2799
|
+
* cursor then encodes that text rather than the Date, and `after` compares the column against
|
|
2800
|
+
* it — so two rows a microsecond apart stay distinct positions. `table` may be the aliased RQB
|
|
2801
|
+
* proxy. Empty when no ordering column needs it.
|
|
2802
|
+
*/
|
|
2803
|
+
const cursorPreciseExtras = (table, entries) => {
|
|
2804
|
+
const cols = getColumns(table);
|
|
2805
|
+
const extras = {};
|
|
2806
|
+
for (const [name] of entries) {
|
|
2807
|
+
const column = cols[name];
|
|
2808
|
+
if (column && outgrowsJsDate(column)) extras[preciseTextProp(name)] = is(column, PgTimestamp) ? sql`${column}::text` : sql`cast(${column} as char)`;
|
|
2809
|
+
}
|
|
2810
|
+
return extras;
|
|
2811
|
+
};
|
|
2812
|
+
/**
|
|
2813
|
+
* {@link cursorPreciseExtras} aliased under their property names, for a `select({...})` that
|
|
2814
|
+
* may be wrapped in a subquery (where a bare SQL expression would have no name).
|
|
2815
|
+
*/
|
|
2816
|
+
const cursorPreciseSelection = (table, entries) => Object.fromEntries(Object.entries(cursorPreciseExtras(table, entries)).map(([prop, expression]) => [prop, expression.as(prop)]));
|
|
2817
|
+
/**
|
|
2735
2818
|
* Serializes one ordering-tuple value into a JSON-safe shape. Dates and bigints don't survive
|
|
2736
2819
|
* JSON.stringify losslessly (bigint throws, Date turns into an untagged string), so they are
|
|
2737
2820
|
* tagged; decimals already arrive from the driver as strings, which are lossless as-is.
|
|
@@ -2766,7 +2849,11 @@ const cursorEncoder = (entries) => {
|
|
|
2766
2849
|
const spec = JSON.stringify(entries);
|
|
2767
2850
|
const columns = entries.map(([column]) => column);
|
|
2768
2851
|
return (row) => {
|
|
2769
|
-
const values = JSON.stringify(columns.map((column) =>
|
|
2852
|
+
const values = JSON.stringify(columns.map((column) => {
|
|
2853
|
+
const value = row[column] ?? null;
|
|
2854
|
+
const precise = row[preciseTextProp(column)];
|
|
2855
|
+
return value !== null && typeof precise === "string" ? precise : encodeCursorValue(value);
|
|
2856
|
+
}));
|
|
2770
2857
|
return Buffer.from(`{"o":${spec},"v":${values}}`, "utf8").toString("base64url");
|
|
2771
2858
|
};
|
|
2772
2859
|
};
|
|
@@ -2859,7 +2946,11 @@ const selectsCursorField = (info, table) => {
|
|
|
2859
2946
|
*/
|
|
2860
2947
|
const attachRowCursors = (rows, entries) => {
|
|
2861
2948
|
const encode = cursorEncoder(entries);
|
|
2862
|
-
|
|
2949
|
+
const preciseProps = entries.map(([column]) => preciseTextProp(column));
|
|
2950
|
+
for (const row of rows) {
|
|
2951
|
+
row[ROW_CURSOR_PROP] = encode(row);
|
|
2952
|
+
for (const prop of preciseProps) delete row[prop];
|
|
2953
|
+
}
|
|
2863
2954
|
};
|
|
2864
2955
|
/** Resolver for the `cursor` meta field: reads the value a list resolver attached, if any. */
|
|
2865
2956
|
const rowCursorResolver = (source) => source?.[ROW_CURSOR_PROP] ?? null;
|
|
@@ -2947,9 +3038,9 @@ const primaryKeyOrderExprs = (table, pkNames) => {
|
|
|
2947
3038
|
return pkNames.map((n) => cols[n]).filter(Boolean).map((col) => asc(col));
|
|
2948
3039
|
};
|
|
2949
3040
|
/**
|
|
2950
|
-
* Every set of columns that
|
|
2951
|
-
*
|
|
2952
|
-
*
|
|
3041
|
+
* Every set of columns that is unique on `table`: the primary key first, then each unique
|
|
3042
|
+
* constraint and unique index, then each column declared `.unique()` inline. Sets are
|
|
3043
|
+
* property names (what GraphQL inputs use), not database column names.
|
|
2953
3044
|
*
|
|
2954
3045
|
* `getTableConfig` is the dialect's own — the three dialects expose the same
|
|
2955
3046
|
* `{ primaryKeys, uniqueConstraints, indexes }` shape but from different modules, so the
|
|
@@ -2958,9 +3049,13 @@ const primaryKeyOrderExprs = (table, pkNames) => {
|
|
|
2958
3049
|
* Index entries whose columns are SQL expressions rather than plain columns are skipped:
|
|
2959
3050
|
* an expression index is a valid conflict target in the database but cannot be named by a
|
|
2960
3051
|
* column enum. Deduplicated, order-insensitive — a column that is both the primary key and
|
|
2961
|
-
* a unique constraint yields one set
|
|
3052
|
+
* a unique constraint yields one set, and a set that is unique both outright and under a
|
|
3053
|
+
* partial index keeps the outright one.
|
|
3054
|
+
*
|
|
3055
|
+
* A partial unique index carries its predicate as `where`: it is a conflict target only
|
|
3056
|
+
* together with that predicate, and it does not make the set unique across the whole table.
|
|
2962
3057
|
*/
|
|
2963
|
-
const
|
|
3058
|
+
const getUniqueTargets = (table, getTableConfig) => {
|
|
2964
3059
|
const cols = visibleColumns(table);
|
|
2965
3060
|
const propNameByColumnName = new Map(Object.entries(cols).map(([propName, col]) => [col.name, propName]));
|
|
2966
3061
|
const toPropNames = (columnNames) => {
|
|
@@ -2972,25 +3067,40 @@ const getUniqueColumnSets = (table, getTableConfig) => {
|
|
|
2972
3067
|
}
|
|
2973
3068
|
return propNames.length ? propNames : void 0;
|
|
2974
3069
|
};
|
|
3070
|
+
const whole = (columns) => columns ? { columns } : void 0;
|
|
2975
3071
|
const config = getTableConfig(table);
|
|
2976
3072
|
const candidates = [
|
|
2977
|
-
Object.entries(cols).filter(([, col]) => col.primary).map(([propName]) => propName),
|
|
2978
|
-
...config.primaryKeys.map((pk) => toPropNames(pk.columns.map((c) => c.name))),
|
|
2979
|
-
...(config.uniqueConstraints ?? []).map((uc) => toPropNames(uc.columns.map((c) => c.name))),
|
|
2980
|
-
...(config.indexes ?? []).filter((index) => index.config.unique).map((index) =>
|
|
2981
|
-
|
|
3073
|
+
whole(Object.entries(cols).filter(([, col]) => col.primary).map(([propName]) => propName)),
|
|
3074
|
+
...config.primaryKeys.map((pk) => whole(toPropNames(pk.columns.map((c) => c.name)))),
|
|
3075
|
+
...(config.uniqueConstraints ?? []).map((uc) => whole(toPropNames(uc.columns.map((c) => c.name)))),
|
|
3076
|
+
...(config.indexes ?? []).filter((index) => index.config.unique).map((index) => {
|
|
3077
|
+
const columns = toPropNames(index.config.columns.map((c) => c?.name));
|
|
3078
|
+
return columns ? {
|
|
3079
|
+
columns,
|
|
3080
|
+
...index.config.where ? { where: index.config.where } : {}
|
|
3081
|
+
} : void 0;
|
|
3082
|
+
}),
|
|
3083
|
+
...Object.entries(cols).filter(([, col]) => col.isUnique).map(([propName]) => ({ columns: [propName] }))
|
|
2982
3084
|
];
|
|
2983
|
-
const
|
|
2984
|
-
const
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
const
|
|
2988
|
-
if (seen.
|
|
2989
|
-
seen.
|
|
2990
|
-
sets.push(set);
|
|
3085
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
3086
|
+
for (const candidate of candidates) {
|
|
3087
|
+
if (!candidate?.columns.length) continue;
|
|
3088
|
+
const key = [...candidate.columns].sort().join(",");
|
|
3089
|
+
const seen = byKey.get(key);
|
|
3090
|
+
if (!seen) byKey.set(key, candidate);
|
|
3091
|
+
else if (seen.where && !candidate.where) byKey.set(key, { columns: seen.columns });
|
|
2991
3092
|
}
|
|
2992
|
-
return
|
|
3093
|
+
return [...byKey.values()];
|
|
2993
3094
|
};
|
|
3095
|
+
/**
|
|
3096
|
+
* The column sets that identify at most one row of `table` — {@link getUniqueTargets}
|
|
3097
|
+
* without the partial unique indexes, which only constrain the rows their predicate
|
|
3098
|
+
* matches (a soft-deleted duplicate is allowed), and so do not.
|
|
3099
|
+
*
|
|
3100
|
+
* Pass `{ partial: true }` to keep them, for callers that only need to know a set is
|
|
3101
|
+
* unique among the rows that matter.
|
|
3102
|
+
*/
|
|
3103
|
+
const getUniqueColumnSets = (table, getTableConfig, options = {}) => getUniqueTargets(table, getTableConfig).filter((target) => options.partial || !target.where).map((target) => target.columns);
|
|
2994
3104
|
//#endregion
|
|
2995
3105
|
//#region src/util/builders/common/distinct.ts
|
|
2996
3106
|
/**
|
|
@@ -3217,6 +3327,21 @@ const withDefaultOrderBy = (args, tableName, defaults) => {
|
|
|
3217
3327
|
} : args;
|
|
3218
3328
|
};
|
|
3219
3329
|
/**
|
|
3330
|
+
* Rejects a negative `limit` or `offset`. Neither is a page size or a position, and the
|
|
3331
|
+
* dialects disagree on what one means: SQLite reads `LIMIT -1` as "no limit" — which would walk
|
|
3332
|
+
* straight past `maxLimit` — while PostgreSQL and MySQL fail with a raw database error. Applied
|
|
3333
|
+
* whether or not the table has a limit policy.
|
|
3334
|
+
*/
|
|
3335
|
+
const assertPagingNotNegative = (paging, errorCtx) => {
|
|
3336
|
+
for (const name of ["limit", "offset"]) {
|
|
3337
|
+
const value = paging[name];
|
|
3338
|
+
if (value != null && value < 0) throw drizzleError(`'${name}' must not be negative, got ${value}.`, {
|
|
3339
|
+
code: "DRIZZLE_INVALID_INPUT_VALUE",
|
|
3340
|
+
...errorCtx
|
|
3341
|
+
});
|
|
3342
|
+
}
|
|
3343
|
+
};
|
|
3344
|
+
/**
|
|
3220
3345
|
* The `limit` a query actually runs with, given the policy for the table it reads.
|
|
3221
3346
|
*
|
|
3222
3347
|
* A request that passes no `limit` falls back to `defaultLimit`, or to `maxLimit` when only
|
|
@@ -3228,6 +3353,7 @@ const withDefaultOrderBy = (args, tableName, defaults) => {
|
|
|
3228
3353
|
* fail a client's query over.
|
|
3229
3354
|
*/
|
|
3230
3355
|
const applyLimitPolicy = (limit, policy, errorCtx) => {
|
|
3356
|
+
assertPagingNotNegative({ limit }, errorCtx);
|
|
3231
3357
|
if (!policy) return limit ?? void 0;
|
|
3232
3358
|
const { defaultLimit, maxLimit, clampToMax } = policy;
|
|
3233
3359
|
if (limit == null) {
|
|
@@ -3745,6 +3871,11 @@ const extractRelationsParams = (relationMap, tables, tableName, info, typeName,
|
|
|
3745
3871
|
thisRecord.columns = columns;
|
|
3746
3872
|
const relationArgs = is(relEntry.relation, One) ? field.args : withDefaultOrderBy(field.args ?? {}, targetTableName, defaultOrderBy);
|
|
3747
3873
|
const offset = relationArgs?.offset ?? void 0;
|
|
3874
|
+
assertPagingNotNegative({ offset }, {
|
|
3875
|
+
table: targetTableName,
|
|
3876
|
+
operation: "relation",
|
|
3877
|
+
relation: relName
|
|
3878
|
+
});
|
|
3748
3879
|
const limit = is(relEntry.relation, One) ? relationArgs?.limit ?? void 0 : applyLimitPolicy(relationArgs?.limit, limits?.(targetTableName), {
|
|
3749
3880
|
table: targetTableName,
|
|
3750
3881
|
operation: "relation",
|
|
@@ -3788,6 +3919,10 @@ const pruneNonEagerRelations = (relationMap, shouldEagerLoad) => {
|
|
|
3788
3919
|
const runRelationalSelect = async (opts) => {
|
|
3789
3920
|
const { queryBase, tables, tableName, table, relationMap, typeName, typeNameMapper, parsedInfo, offset, orderBy, where, single, filterCtx, pkNames, after, scope, deleted } = opts;
|
|
3790
3921
|
const distinct = opts.distinct?.length ? opts.distinct : void 0;
|
|
3922
|
+
assertPagingNotNegative({
|
|
3923
|
+
limit: opts.limit,
|
|
3924
|
+
offset
|
|
3925
|
+
});
|
|
3791
3926
|
const cursorSelected = !single && isCursorFieldSelected(parsedInfo.fieldsByTypeName[typeName], table);
|
|
3792
3927
|
let cursorEntries;
|
|
3793
3928
|
if (!single && (after != null || cursorSelected)) {
|
|
@@ -3839,7 +3974,11 @@ const runRelationalSelect = async (opts) => {
|
|
|
3839
3974
|
const result = await queryBase.findFirst(omitUndefinedKeys(params));
|
|
3840
3975
|
return result ? remapToGraphQLSingleOutput(result, tableName, table, relationMap) : void 0;
|
|
3841
3976
|
}
|
|
3842
|
-
if (cursorEntries && cursorSelected)
|
|
3977
|
+
if (cursorEntries && cursorSelected) {
|
|
3978
|
+
for (const [column] of cursorEntries) params.columns[column] = true;
|
|
3979
|
+
const preciseProps = Object.keys(cursorPreciseExtras(table, cursorEntries));
|
|
3980
|
+
if (preciseProps.length) params.extras = Object.fromEntries(preciseProps.map((prop) => [prop, (aliasedTable) => cursorPreciseExtras(aliasedTable, cursorEntries)[prop]]));
|
|
3981
|
+
}
|
|
3843
3982
|
params.limit = distinctKeys ? void 0 : opts.limit;
|
|
3844
3983
|
const result = await queryBase.findMany(omitUndefinedKeys(params));
|
|
3845
3984
|
if (cursorEntries && cursorSelected) attachRowCursors(result, cursorEntries);
|
|
@@ -4591,6 +4730,19 @@ const keysFromWhere = (where, primaryKey) => {
|
|
|
4591
4730
|
return none;
|
|
4592
4731
|
};
|
|
4593
4732
|
/**
|
|
4733
|
+
* Whether an upsert's `onConflict` leaves the primary-key values in `values` naming exactly
|
|
4734
|
+
* the rows it writes. It does not when the conflict is detected on another unique key — the
|
|
4735
|
+
* row updated is whichever one holds that key, not the one the supplied primary key names —
|
|
4736
|
+
* nor when conflicting rows can be skipped: `action: NOTHING`, or a `where` guard.
|
|
4737
|
+
*/
|
|
4738
|
+
const conflictsOnKeyAlone = (onConflict, primaryKey) => {
|
|
4739
|
+
if (!onConflict || typeof onConflict !== "object") return true;
|
|
4740
|
+
const { target, action, where } = onConflict;
|
|
4741
|
+
if (action === "NOTHING" || where !== void 0 && where !== null) return false;
|
|
4742
|
+
if (!Array.isArray(target) || !target.length) return true;
|
|
4743
|
+
return [...target].sort().join(",") === [...primaryKey].sort().join(",");
|
|
4744
|
+
};
|
|
4745
|
+
/**
|
|
4594
4746
|
* The rows a generated field's arguments are about, as primary-key values.
|
|
4595
4747
|
*
|
|
4596
4748
|
* The row identity of a mutation lives in a different argument for every operation —
|
|
@@ -4626,7 +4778,7 @@ const identifyRows = (field, args) => {
|
|
|
4626
4778
|
table,
|
|
4627
4779
|
primaryKey,
|
|
4628
4780
|
rows: rows.filter((row) => !!row),
|
|
4629
|
-
complete: entries.length > 0 && rows.every(Boolean)
|
|
4781
|
+
complete: entries.length > 0 && rows.every(Boolean) && (meta.operation !== "upsert" || conflictsOnKeyAlone(args?.["onConflict"], primaryKey))
|
|
4630
4782
|
};
|
|
4631
4783
|
}
|
|
4632
4784
|
if (targetArg === "updates") {
|
|
@@ -4743,7 +4895,10 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
4743
4895
|
const column = targetColumns[columnName];
|
|
4744
4896
|
if (column) columns[columnName] = column;
|
|
4745
4897
|
}
|
|
4746
|
-
return
|
|
4898
|
+
return cursorEntries ? {
|
|
4899
|
+
...columns,
|
|
4900
|
+
...cursorPreciseSelection(targetTable, cursorEntries)
|
|
4901
|
+
} : columns;
|
|
4747
4902
|
};
|
|
4748
4903
|
const resolve = async (parent, args, context, info) => {
|
|
4749
4904
|
if (parent[relationName] !== void 0) return parent[relationName];
|
|
@@ -4753,6 +4908,7 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
4753
4908
|
const { orderByArg, limit, distinct, cursorEntries, cursorValues, columns, loaderKey } = getOrCreateRequestValue(context, info?.fieldNodes?.[0], `relation:${tableName}::${relationName}`, () => {
|
|
4754
4909
|
const orderByArg = isOne ? args?.orderBy : withDefaultOrderBy(args ?? {}, targetTableName, policies?.defaultOrderBy).orderBy;
|
|
4755
4910
|
const limit = applyLimitPolicy(requestedLimit, limitPolicy, errorCtx);
|
|
4911
|
+
assertPagingNotNegative({ offset }, errorCtx);
|
|
4756
4912
|
const distinct = (args ?? {}).distinct?.length ? (args ?? {}).distinct : void 0;
|
|
4757
4913
|
const cursorSelected = !isOne && !!info && selectsCursorField(info, targetTable);
|
|
4758
4914
|
let cursorEntries;
|
|
@@ -7099,7 +7255,7 @@ const createSchemaBuilder = (adapter) => {
|
|
|
7099
7255
|
uniqueKeysOf: (tableName) => uniqueKeys[tableName]
|
|
7100
7256
|
};
|
|
7101
7257
|
adapter.preflight?.(db, options);
|
|
7102
|
-
const nestedPlans = features.nestedWrites ? buildNestedWritePlans(tables, namedRelations, (target) =>
|
|
7258
|
+
const nestedPlans = features.nestedWrites ? buildNestedWritePlans(tables, namedRelations, (target) => getUniqueColumnSets(target, adapter.getTableConfig, { partial: true }), (target) => primaryKeyPropNames(target), extractRelationJoinColumns, (junction) => schemaEntries.find(([, value]) => value === junction)?.[0]) : void 0;
|
|
7103
7259
|
if (nestedPlans) {
|
|
7104
7260
|
const omitted = gateNestedWritePlans(nestedPlans, featureOf, (tableName) => excludedTables.has(tableName));
|
|
7105
7261
|
if (omitted.length) {
|
|
@@ -7387,20 +7543,33 @@ const generateUpsert = (db, tableName, table, baseType, onConflictType, fieldNam
|
|
|
7387
7543
|
}
|
|
7388
7544
|
},
|
|
7389
7545
|
run: async ({ executor, args, context, before, after }) => {
|
|
7390
|
-
const
|
|
7546
|
+
const contextColumns = policies?.contextValues?.(tableName);
|
|
7547
|
+
const input = applyContextValuesAll(single ? [remapFromGraphQLSingleInput(args.values, table)] : remapFromGraphQLArrayInput(args.values, table), contextColumns, context);
|
|
7391
7548
|
if (!input.length) throw drizzleError("No values were provided!", { code: "DRIZZLE_NO_VALUES" });
|
|
7392
7549
|
await before();
|
|
7393
|
-
const
|
|
7550
|
+
const batches = resolveConflictPlan({
|
|
7394
7551
|
table,
|
|
7395
7552
|
values: input,
|
|
7396
7553
|
onConflict: args.onConflict,
|
|
7397
7554
|
pkNames,
|
|
7398
7555
|
uniqueSets: [],
|
|
7399
7556
|
excludedRef: mysqlValuesColumnRef,
|
|
7400
|
-
withTarget: false
|
|
7557
|
+
withTarget: false,
|
|
7558
|
+
serverOwned: Object.keys(contextColumns ?? {})
|
|
7559
|
+
});
|
|
7560
|
+
const requestedNothing = args.onConflict?.action === "NOTHING";
|
|
7561
|
+
const runBatch = async (target, { values, plan }) => {
|
|
7562
|
+
if (plan.action === "NOTHING" && !requestedNothing) {
|
|
7563
|
+
const columns = getColumns(table);
|
|
7564
|
+
const keyName = pkNames[0] ?? Object.keys(columns)[0];
|
|
7565
|
+
await target.insert(table).values(values).onDuplicateKeyUpdate({ set: { [keyName]: sql`${sql.identifier(columns[keyName].name)}` } });
|
|
7566
|
+
} else if (plan.action === "NOTHING") await target.insert(table).ignore().values(values);
|
|
7567
|
+
else await target.insert(table).values(values).onDuplicateKeyUpdate({ set: plan.set });
|
|
7568
|
+
};
|
|
7569
|
+
if (batches.length === 1) await runBatch(executor, batches[0]);
|
|
7570
|
+
else await executor.transaction(async (tx) => {
|
|
7571
|
+
for (const batch of batches) await runBatch(tx, batch);
|
|
7401
7572
|
});
|
|
7402
|
-
if (plan.action === "NOTHING") await executor.insert(table).ignore().values(input);
|
|
7403
|
-
else await executor.insert(table).values(input).onDuplicateKeyUpdate({ set: plan.set });
|
|
7404
7573
|
await after();
|
|
7405
7574
|
return isSuccess;
|
|
7406
7575
|
}
|
|
@@ -7846,7 +8015,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7846
8015
|
});
|
|
7847
8016
|
const returning = withHookReturning(nestedEntries ? nested.withJoinColumns(tableName, mergedOps(nestedEntries), { ...columns }, table) : columns);
|
|
7848
8017
|
const runUpsert = async (target, values) => {
|
|
7849
|
-
const
|
|
8018
|
+
const batches = resolveConflictPlan({
|
|
7850
8019
|
table,
|
|
7851
8020
|
values,
|
|
7852
8021
|
onConflict: args.onConflict,
|
|
@@ -7854,16 +8023,29 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7854
8023
|
uniqueSets,
|
|
7855
8024
|
excludedRef: excludedColumnRef,
|
|
7856
8025
|
withTarget: true,
|
|
8026
|
+
serverOwned: Object.keys(contextColumns ?? {}),
|
|
7857
8027
|
buildWhere: (where) => extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName))
|
|
7858
8028
|
});
|
|
7859
|
-
|
|
7860
|
-
|
|
7861
|
-
|
|
7862
|
-
|
|
7863
|
-
|
|
7864
|
-
|
|
8029
|
+
const runBatch = async (executor, { values: rows, plan }) => {
|
|
8030
|
+
let query = executor.insert(table).values(rows).returning(returning);
|
|
8031
|
+
const setWhere = withScope(scope, tableName, table, plan.setWhere);
|
|
8032
|
+
query = plan.action === "NOTHING" ? query.onConflictDoNothing(plan.target ? {
|
|
8033
|
+
target: plan.target,
|
|
8034
|
+
where: plan.targetWhere
|
|
8035
|
+
} : void 0) : query.onConflictDoUpdate({
|
|
8036
|
+
target: plan.target,
|
|
8037
|
+
targetWhere: plan.targetWhere,
|
|
8038
|
+
set: plan.set,
|
|
8039
|
+
setWhere
|
|
8040
|
+
});
|
|
8041
|
+
return await query;
|
|
8042
|
+
};
|
|
8043
|
+
if (batches.length === 1) return runBatch(target, batches[0]);
|
|
8044
|
+
return target.transaction(async (tx) => {
|
|
8045
|
+
const rows = [];
|
|
8046
|
+
for (const batch of batches) rows.push(...await runBatch(tx, batch));
|
|
8047
|
+
return rows;
|
|
7865
8048
|
});
|
|
7866
|
-
return await query;
|
|
7867
8049
|
};
|
|
7868
8050
|
const written = nestedEntries ? await writeWithNestedOps({
|
|
7869
8051
|
executor,
|
|
@@ -8043,7 +8225,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
8043
8225
|
*/
|
|
8044
8226
|
const createSchemaDataGenerator = (adapter) => {
|
|
8045
8227
|
const primaryKeyPropNames = adapter.primaryKeyPropNames;
|
|
8046
|
-
const
|
|
8228
|
+
const uniqueTargets = (table) => getUniqueTargets(table, adapter.getTableConfig);
|
|
8047
8229
|
const { generateInsert, generateUpsert, generateUpdate, generateDelete } = buildWriteResolvers(primaryKeyPropNames);
|
|
8048
8230
|
const { prepareBuild, forEachTable, finalizeBuild } = createSchemaBuilder(adapter);
|
|
8049
8231
|
return (db, schema, relations, options) => {
|
|
@@ -8084,7 +8266,7 @@ const createSchemaDataGenerator = (adapter) => {
|
|
|
8084
8266
|
single: true,
|
|
8085
8267
|
conflictDoNothing
|
|
8086
8268
|
}) : void 0;
|
|
8087
|
-
const uniqueSets = tableFeatures.upsert ?
|
|
8269
|
+
const uniqueSets = tableFeatures.upsert ? uniqueTargets(schema[tableName]) : [];
|
|
8088
8270
|
const onConflictInput = tableFeatures.upsert ? generateOnConflictInput({
|
|
8089
8271
|
table: schema[tableName],
|
|
8090
8272
|
tableName,
|