@vantreeseba/drizzle-graphql 15.1.1 → 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 +22 -4
- package/dist/index.cjs +154 -59
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -3
- package/dist/index.d.ts +11 -3
- package/dist/index.js +154 -59
- 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
|
*/
|
|
@@ -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
|
*/
|
|
@@ -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
|
@@ -2394,8 +2394,8 @@ const columnEnumCache = /* @__PURE__ */ new WeakMap();
|
|
|
2394
2394
|
* input field the enum would have typed, rather than emitting an empty enum, which is
|
|
2395
2395
|
* invalid GraphQL.
|
|
2396
2396
|
*/
|
|
2397
|
-
const generateColumnEnum = (table, enumName, description, predicate = () => true) => {
|
|
2398
|
-
const cacheKey = `${enumName}\u0000${excludedColumnsKey(table)}`;
|
|
2397
|
+
const generateColumnEnum = (table, enumName, description, predicate = () => true, variant = "") => {
|
|
2398
|
+
const cacheKey = `${enumName}\u0000${excludedColumnsKey(table)}\u0000${variant}`;
|
|
2399
2399
|
let tableCache = columnEnumCache.get(table);
|
|
2400
2400
|
const cached = tableCache?.get(cacheKey);
|
|
2401
2401
|
if (cached) return cached;
|
|
@@ -2455,6 +2455,9 @@ const conflictActionEnumType = (typeName) => sharedType(typeName, {
|
|
|
2455
2455
|
* `ON DUPLICATE KEY UPDATE` fires on any unique key and takes no predicate, so offering
|
|
2456
2456
|
* either there would mean silently ignoring it.
|
|
2457
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
|
+
*
|
|
2458
2461
|
* Returns `undefined` when the table has nothing to conflict on (`withTarget` dialects
|
|
2459
2462
|
* only) — the caller then generates no upsert mutations for that table at all, rather than
|
|
2460
2463
|
* an operation whose every call is a database error.
|
|
@@ -2462,12 +2465,13 @@ const conflictActionEnumType = (typeName) => sharedType(typeName, {
|
|
|
2462
2465
|
const generateOnConflictInput = (params) => {
|
|
2463
2466
|
const { table, tableName, typeName, uniqueSets, tableFilters, withTarget, cacheCtx } = params;
|
|
2464
2467
|
const resolveName = cacheCtx.typeName;
|
|
2468
|
+
const serverOwned = Object.keys(cacheCtx.contextValuesOf?.(tableName) ?? {}).sort();
|
|
2465
2469
|
const updateEnum = generateColumnEnum(table, resolveName({
|
|
2466
2470
|
kind: "columnEnum",
|
|
2467
2471
|
defaultName: `${typeName}UpdateColumn`,
|
|
2468
2472
|
table: tableName,
|
|
2469
2473
|
operation: "conflictUpdate"
|
|
2470
|
-
}), `Columns of ${typeName} that an upsert can overwrite
|
|
2474
|
+
}), `Columns of ${typeName} that an upsert can overwrite`, (_column, columnName) => !serverOwned.includes(columnName), serverOwned.join(","));
|
|
2471
2475
|
if (!updateEnum) return;
|
|
2472
2476
|
const fields = {
|
|
2473
2477
|
action: {
|
|
@@ -2477,11 +2481,11 @@ const generateOnConflictInput = (params) => {
|
|
|
2477
2481
|
},
|
|
2478
2482
|
update: {
|
|
2479
2483
|
type: new GraphQLList(new GraphQLNonNull(updateEnum)),
|
|
2480
|
-
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.`
|
|
2481
2485
|
}
|
|
2482
2486
|
};
|
|
2483
2487
|
if (withTarget) {
|
|
2484
|
-
const uniqueColumns = new Set(uniqueSets.
|
|
2488
|
+
const uniqueColumns = new Set(uniqueSets.flatMap((set) => set.columns));
|
|
2485
2489
|
const targetEnum = generateColumnEnum(table, resolveName({
|
|
2486
2490
|
kind: "columnEnum",
|
|
2487
2491
|
defaultName: `${typeName}ConflictTarget`,
|
|
@@ -2509,55 +2513,88 @@ const generateOnConflictInput = (params) => {
|
|
|
2509
2513
|
});
|
|
2510
2514
|
};
|
|
2511
2515
|
/**
|
|
2512
|
-
* Turns the request's `onConflict` argument and the rows it is inserting into the
|
|
2513
|
-
* 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.
|
|
2514
2525
|
*
|
|
2515
2526
|
* `excludedRef` names the row that failed to insert in the dialect's own terms
|
|
2516
2527
|
* (`excluded.col` on PostgreSQL and SQLite, `values(col)` on MySQL), which is what makes a
|
|
2517
2528
|
* batch upsert update each row with its own values instead of the last row's.
|
|
2518
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
|
+
*
|
|
2519
2535
|
* An UPDATE with nothing left to write degrades to NOTHING: `DO UPDATE SET` with an empty
|
|
2520
2536
|
* body is not valid SQL, and doing nothing is what the request asked for anyway.
|
|
2521
2537
|
*/
|
|
2522
2538
|
const resolveConflictPlan = (params) => {
|
|
2523
2539
|
const { table, values, onConflict, pkNames, uniqueSets, excludedRef, withTarget, buildWhere } = params;
|
|
2524
2540
|
const columns = getColumns(table);
|
|
2541
|
+
const serverOwned = new Set(params.serverOwned ?? []);
|
|
2525
2542
|
let target;
|
|
2543
|
+
let targetWhere;
|
|
2526
2544
|
if (withTarget) {
|
|
2527
2545
|
const targetNames = onConflict?.target?.length ? onConflict.target : [...pkNames];
|
|
2528
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" });
|
|
2529
2547
|
const requested = [...targetNames].sort().join(",");
|
|
2530
|
-
|
|
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" });
|
|
2531
2550
|
target = targetNames.map((name) => columns[name]);
|
|
2551
|
+
targetWhere = matched.where;
|
|
2532
2552
|
}
|
|
2533
|
-
|
|
2553
|
+
const nothing = {
|
|
2534
2554
|
action: "NOTHING",
|
|
2535
2555
|
target,
|
|
2556
|
+
targetWhere,
|
|
2536
2557
|
set: {},
|
|
2537
2558
|
setWhere: void 0
|
|
2538
2559
|
};
|
|
2560
|
+
if ((onConflict?.action ?? "UPDATE") === "NOTHING") return [{
|
|
2561
|
+
values,
|
|
2562
|
+
plan: nothing
|
|
2563
|
+
}];
|
|
2539
2564
|
const supplied = new Set(values.flatMap((row) => Object.keys(row)));
|
|
2540
|
-
const
|
|
2541
|
-
let
|
|
2565
|
+
const neverWritten = new Set(withTarget && onConflict?.target?.length ? onConflict.target : pkNames);
|
|
2566
|
+
let candidates;
|
|
2542
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" });
|
|
2543
2570
|
const unsupplied = onConflict.update.filter((name) => !supplied.has(name));
|
|
2544
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" });
|
|
2545
|
-
|
|
2546
|
-
} else
|
|
2547
|
-
if (!updateNames.length) return {
|
|
2548
|
-
action: "NOTHING",
|
|
2549
|
-
target,
|
|
2550
|
-
set: {},
|
|
2551
|
-
setWhere: void 0
|
|
2552
|
-
};
|
|
2553
|
-
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));
|
|
2554
2574
|
const setWhere = onConflict?.where && buildWhere ? buildWhere(onConflict.where) : void 0;
|
|
2555
|
-
|
|
2575
|
+
const planFor = (updateNames) => updateNames.length ? {
|
|
2556
2576
|
action: "UPDATE",
|
|
2557
2577
|
target,
|
|
2558
|
-
|
|
2578
|
+
targetWhere,
|
|
2579
|
+
set: Object.fromEntries(updateNames.map((name) => [name, excludedRef(columns[name].name)])),
|
|
2559
2580
|
setWhere
|
|
2560
|
-
};
|
|
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
|
+
}));
|
|
2561
2598
|
};
|
|
2562
2599
|
/** `excluded.<column>` — PostgreSQL and SQLite name the rejected row this way. */
|
|
2563
2600
|
const excludedColumnRef = (columnName) => sql`excluded.${sql.identifier(columnName)}`;
|
|
@@ -3001,9 +3038,9 @@ const primaryKeyOrderExprs = (table, pkNames) => {
|
|
|
3001
3038
|
return pkNames.map((n) => cols[n]).filter(Boolean).map((col) => asc(col));
|
|
3002
3039
|
};
|
|
3003
3040
|
/**
|
|
3004
|
-
* Every set of columns that
|
|
3005
|
-
*
|
|
3006
|
-
*
|
|
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.
|
|
3007
3044
|
*
|
|
3008
3045
|
* `getTableConfig` is the dialect's own — the three dialects expose the same
|
|
3009
3046
|
* `{ primaryKeys, uniqueConstraints, indexes }` shape but from different modules, so the
|
|
@@ -3012,9 +3049,13 @@ const primaryKeyOrderExprs = (table, pkNames) => {
|
|
|
3012
3049
|
* Index entries whose columns are SQL expressions rather than plain columns are skipped:
|
|
3013
3050
|
* an expression index is a valid conflict target in the database but cannot be named by a
|
|
3014
3051
|
* column enum. Deduplicated, order-insensitive — a column that is both the primary key and
|
|
3015
|
-
* 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.
|
|
3016
3057
|
*/
|
|
3017
|
-
const
|
|
3058
|
+
const getUniqueTargets = (table, getTableConfig) => {
|
|
3018
3059
|
const cols = visibleColumns(table);
|
|
3019
3060
|
const propNameByColumnName = new Map(Object.entries(cols).map(([propName, col]) => [col.name, propName]));
|
|
3020
3061
|
const toPropNames = (columnNames) => {
|
|
@@ -3026,25 +3067,40 @@ const getUniqueColumnSets = (table, getTableConfig) => {
|
|
|
3026
3067
|
}
|
|
3027
3068
|
return propNames.length ? propNames : void 0;
|
|
3028
3069
|
};
|
|
3070
|
+
const whole = (columns) => columns ? { columns } : void 0;
|
|
3029
3071
|
const config = getTableConfig(table);
|
|
3030
3072
|
const candidates = [
|
|
3031
|
-
Object.entries(cols).filter(([, col]) => col.primary).map(([propName]) => propName),
|
|
3032
|
-
...config.primaryKeys.map((pk) => toPropNames(pk.columns.map((c) => c.name))),
|
|
3033
|
-
...(config.uniqueConstraints ?? []).map((uc) => toPropNames(uc.columns.map((c) => c.name))),
|
|
3034
|
-
...(config.indexes ?? []).filter((index) => index.config.unique).map((index) =>
|
|
3035
|
-
|
|
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] }))
|
|
3036
3084
|
];
|
|
3037
|
-
const
|
|
3038
|
-
const
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
const
|
|
3042
|
-
if (seen.
|
|
3043
|
-
seen.
|
|
3044
|
-
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 });
|
|
3045
3092
|
}
|
|
3046
|
-
return
|
|
3093
|
+
return [...byKey.values()];
|
|
3047
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);
|
|
3048
3104
|
//#endregion
|
|
3049
3105
|
//#region src/util/builders/common/distinct.ts
|
|
3050
3106
|
/**
|
|
@@ -4674,6 +4730,19 @@ const keysFromWhere = (where, primaryKey) => {
|
|
|
4674
4730
|
return none;
|
|
4675
4731
|
};
|
|
4676
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
|
+
/**
|
|
4677
4746
|
* The rows a generated field's arguments are about, as primary-key values.
|
|
4678
4747
|
*
|
|
4679
4748
|
* The row identity of a mutation lives in a different argument for every operation —
|
|
@@ -4709,7 +4778,7 @@ const identifyRows = (field, args) => {
|
|
|
4709
4778
|
table,
|
|
4710
4779
|
primaryKey,
|
|
4711
4780
|
rows: rows.filter((row) => !!row),
|
|
4712
|
-
complete: entries.length > 0 && rows.every(Boolean)
|
|
4781
|
+
complete: entries.length > 0 && rows.every(Boolean) && (meta.operation !== "upsert" || conflictsOnKeyAlone(args?.["onConflict"], primaryKey))
|
|
4713
4782
|
};
|
|
4714
4783
|
}
|
|
4715
4784
|
if (targetArg === "updates") {
|
|
@@ -7186,7 +7255,7 @@ const createSchemaBuilder = (adapter) => {
|
|
|
7186
7255
|
uniqueKeysOf: (tableName) => uniqueKeys[tableName]
|
|
7187
7256
|
};
|
|
7188
7257
|
adapter.preflight?.(db, options);
|
|
7189
|
-
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;
|
|
7190
7259
|
if (nestedPlans) {
|
|
7191
7260
|
const omitted = gateNestedWritePlans(nestedPlans, featureOf, (tableName) => excludedTables.has(tableName));
|
|
7192
7261
|
if (omitted.length) {
|
|
@@ -7474,20 +7543,33 @@ const generateUpsert = (db, tableName, table, baseType, onConflictType, fieldNam
|
|
|
7474
7543
|
}
|
|
7475
7544
|
},
|
|
7476
7545
|
run: async ({ executor, args, context, before, after }) => {
|
|
7477
|
-
const
|
|
7546
|
+
const contextColumns = policies?.contextValues?.(tableName);
|
|
7547
|
+
const input = applyContextValuesAll(single ? [remapFromGraphQLSingleInput(args.values, table)] : remapFromGraphQLArrayInput(args.values, table), contextColumns, context);
|
|
7478
7548
|
if (!input.length) throw drizzleError("No values were provided!", { code: "DRIZZLE_NO_VALUES" });
|
|
7479
7549
|
await before();
|
|
7480
|
-
const
|
|
7550
|
+
const batches = resolveConflictPlan({
|
|
7481
7551
|
table,
|
|
7482
7552
|
values: input,
|
|
7483
7553
|
onConflict: args.onConflict,
|
|
7484
7554
|
pkNames,
|
|
7485
7555
|
uniqueSets: [],
|
|
7486
7556
|
excludedRef: mysqlValuesColumnRef,
|
|
7487
|
-
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);
|
|
7488
7572
|
});
|
|
7489
|
-
if (plan.action === "NOTHING") await executor.insert(table).ignore().values(input);
|
|
7490
|
-
else await executor.insert(table).values(input).onDuplicateKeyUpdate({ set: plan.set });
|
|
7491
7573
|
await after();
|
|
7492
7574
|
return isSuccess;
|
|
7493
7575
|
}
|
|
@@ -7933,7 +8015,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7933
8015
|
});
|
|
7934
8016
|
const returning = withHookReturning(nestedEntries ? nested.withJoinColumns(tableName, mergedOps(nestedEntries), { ...columns }, table) : columns);
|
|
7935
8017
|
const runUpsert = async (target, values) => {
|
|
7936
|
-
const
|
|
8018
|
+
const batches = resolveConflictPlan({
|
|
7937
8019
|
table,
|
|
7938
8020
|
values,
|
|
7939
8021
|
onConflict: args.onConflict,
|
|
@@ -7941,16 +8023,29 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7941
8023
|
uniqueSets,
|
|
7942
8024
|
excludedRef: excludedColumnRef,
|
|
7943
8025
|
withTarget: true,
|
|
8026
|
+
serverOwned: Object.keys(contextColumns ?? {}),
|
|
7944
8027
|
buildWhere: (where) => extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName))
|
|
7945
8028
|
});
|
|
7946
|
-
|
|
7947
|
-
|
|
7948
|
-
|
|
7949
|
-
|
|
7950
|
-
|
|
7951
|
-
|
|
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;
|
|
7952
8048
|
});
|
|
7953
|
-
return await query;
|
|
7954
8049
|
};
|
|
7955
8050
|
const written = nestedEntries ? await writeWithNestedOps({
|
|
7956
8051
|
executor,
|
|
@@ -8130,7 +8225,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
8130
8225
|
*/
|
|
8131
8226
|
const createSchemaDataGenerator = (adapter) => {
|
|
8132
8227
|
const primaryKeyPropNames = adapter.primaryKeyPropNames;
|
|
8133
|
-
const
|
|
8228
|
+
const uniqueTargets = (table) => getUniqueTargets(table, adapter.getTableConfig);
|
|
8134
8229
|
const { generateInsert, generateUpsert, generateUpdate, generateDelete } = buildWriteResolvers(primaryKeyPropNames);
|
|
8135
8230
|
const { prepareBuild, forEachTable, finalizeBuild } = createSchemaBuilder(adapter);
|
|
8136
8231
|
return (db, schema, relations, options) => {
|
|
@@ -8171,7 +8266,7 @@ const createSchemaDataGenerator = (adapter) => {
|
|
|
8171
8266
|
single: true,
|
|
8172
8267
|
conflictDoNothing
|
|
8173
8268
|
}) : void 0;
|
|
8174
|
-
const uniqueSets = tableFeatures.upsert ?
|
|
8269
|
+
const uniqueSets = tableFeatures.upsert ? uniqueTargets(schema[tableName]) : [];
|
|
8175
8270
|
const onConflictInput = tableFeatures.upsert ? generateOnConflictInput({
|
|
8176
8271
|
table: schema[tableName],
|
|
8177
8272
|
tableName,
|