@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/README.md
CHANGED
|
@@ -335,7 +335,9 @@ const { schema } = buildSchema(db, {
|
|
|
335
335
|
send it at all — the SDL itself says the column is server-owned. It stays readable on the
|
|
336
336
|
object type.
|
|
337
337
|
- Every insert stamps it, including rows created by a nested `create`.
|
|
338
|
-
- Updates never write it, so an update cannot hand a row to another owner.
|
|
338
|
+
- Updates never write it, so an update cannot hand a row to another owner. Neither does the
|
|
339
|
+
conflict branch of an upsert: it is left out of the default overwrite and of the columns
|
|
340
|
+
`onConflict.update` can name.
|
|
339
341
|
|
|
340
342
|
Together the two close the loop: `contextValues` decides which tenant a new row belongs to, and
|
|
341
343
|
`scope` decides which rows a request can see and change afterwards.
|
|
@@ -984,6 +986,9 @@ input StockItemIdLocationIdKey {
|
|
|
984
986
|
composes with column filters, relation filters, `OR` / `AND` / `NOT`
|
|
985
987
|
- Composite primary keys get a field like any other unique constraint
|
|
986
988
|
- Single-column constraints do not: `eq` on the column already names the row
|
|
989
|
+
- Partial unique indexes (`uniqueIndex().on(…).where(…)`) do not either: they are unique
|
|
990
|
+
only among the rows the predicate matches, so with soft-deleted rows included
|
|
991
|
+
(`deleted: INCLUDE`) the key can match more than one
|
|
987
992
|
- A field name a column or relation already holds is skipped, the same way a relation
|
|
988
993
|
yields to a same-named column
|
|
989
994
|
- It compiles to exactly the equalities you would have written by hand; the guarantee is in
|
|
@@ -1566,9 +1571,17 @@ mutation {
|
|
|
1566
1571
|
- Columns the request did not supply are never overwritten — a partial upsert does not null
|
|
1567
1572
|
out the rest of the row. Listing an unsupplied column in `update` is an error rather than
|
|
1568
1573
|
a silent no-op
|
|
1574
|
+
- That holds per row: a batch whose rows supply different columns is split into one
|
|
1575
|
+
statement per run of consecutive rows that write the same columns, run in one transaction,
|
|
1576
|
+
so a row that left a column out keeps it. The rows come back in the order they were sent
|
|
1569
1577
|
- `target` must match one of the table's unique constraints exactly; anything else is
|
|
1570
1578
|
rejected with the list of valid targets, instead of the database's opaque "no unique or
|
|
1571
1579
|
exclusion constraint matching" error
|
|
1580
|
+
- `target` can be a partial unique index — the `WHERE deleted_at IS NULL` companion of
|
|
1581
|
+
[soft delete](#soft-delete), say. Its predicate goes into `ON CONFLICT (…) WHERE …`, so a
|
|
1582
|
+
conflict is detected only against the rows the index covers
|
|
1583
|
+
- `contextValues` columns are never overwritten on conflict — see
|
|
1584
|
+
[columns the server owns](#columns-the-server-owns)
|
|
1572
1585
|
- `action: NOTHING` inserts nothing and returns nothing for the conflicting row. An `UPDATE`
|
|
1573
1586
|
with no columns left to write degrades to the same thing
|
|
1574
1587
|
- `values` is the same `Create<Type>Input` the insert mutations take, so turning `insert`
|
|
@@ -1603,7 +1616,9 @@ Dialect differences:
|
|
|
1603
1616
|
- **PostgreSQL** and **SQLite** — the full surface above. A table with no primary key and no
|
|
1604
1617
|
unique constraint has nothing to conflict on, so it gets no upsert mutations at all
|
|
1605
1618
|
- **MySQL** — `ON DUPLICATE KEY UPDATE` fires on whichever unique key was violated and takes
|
|
1606
|
-
no predicate, so `<Type>OnConflict` there has only `action` and `update`.
|
|
1619
|
+
no predicate, so `<Type>OnConflict` there has only `action` and `update`. Because the
|
|
1620
|
+
conflict can be on any unique key, the default overwrite leaves out the primary key as well
|
|
1621
|
+
— copying a new row's key over the conflicting row's would move it. `action: NOTHING`
|
|
1607
1622
|
becomes `INSERT IGNORE`, and the mutations return `MutationReturn` like every other MySQL
|
|
1608
1623
|
mutation
|
|
1609
1624
|
|
|
@@ -2580,8 +2595,11 @@ It understands `values` (one object or a list), `where` (`eq` for one row, `inAr
|
|
|
2580
2595
|
many, an equality on every column of a composite key), and the per-entry `where` inside a
|
|
2581
2596
|
batch update's `updates`. Check `complete` before treating `rows` as the affected set: it
|
|
2582
2597
|
is `false` when the arguments do not pin the rows down — an insert whose key the database
|
|
2583
|
-
generates, a filter on something other than the primary key, an unbounded update,
|
|
2584
|
-
table with no primary key at all
|
|
2598
|
+
generates, a filter on something other than the primary key, an unbounded update, a
|
|
2599
|
+
table with no primary key at all, or an upsert whose `onConflict` targets another unique key
|
|
2600
|
+
or can skip a conflicting row (`action: NOTHING`, a `where`). On MySQL, where an upsert has
|
|
2601
|
+
no `target` and conflicts on whichever unique key it hits, `complete` assumes the primary
|
|
2602
|
+
key: do not rely on it for a table with other unique keys.
|
|
2585
2603
|
|
|
2586
2604
|
> [!NOTE]
|
|
2587
2605
|
> `extensions.drizzle` is stable API. The point of publishing it is that consumers stop
|
package/dist/index.cjs
CHANGED
|
@@ -2418,8 +2418,8 @@ const columnEnumCache = /* @__PURE__ */ new WeakMap();
|
|
|
2418
2418
|
* input field the enum would have typed, rather than emitting an empty enum, which is
|
|
2419
2419
|
* invalid GraphQL.
|
|
2420
2420
|
*/
|
|
2421
|
-
const generateColumnEnum = (table, enumName, description, predicate = () => true) => {
|
|
2422
|
-
const cacheKey = `${enumName}\u0000${excludedColumnsKey(table)}`;
|
|
2421
|
+
const generateColumnEnum = (table, enumName, description, predicate = () => true, variant = "") => {
|
|
2422
|
+
const cacheKey = `${enumName}\u0000${excludedColumnsKey(table)}\u0000${variant}`;
|
|
2423
2423
|
let tableCache = columnEnumCache.get(table);
|
|
2424
2424
|
const cached = tableCache?.get(cacheKey);
|
|
2425
2425
|
if (cached) return cached;
|
|
@@ -2479,6 +2479,9 @@ const conflictActionEnumType = (typeName) => sharedType(typeName, {
|
|
|
2479
2479
|
* `ON DUPLICATE KEY UPDATE` fires on any unique key and takes no predicate, so offering
|
|
2480
2480
|
* either there would mean silently ignoring it.
|
|
2481
2481
|
*
|
|
2482
|
+
* `update` never offers a `contextValues` column: the server owns it, and the conflict
|
|
2483
|
+
* branch is an update, which must not hand the row to another owner.
|
|
2484
|
+
*
|
|
2482
2485
|
* Returns `undefined` when the table has nothing to conflict on (`withTarget` dialects
|
|
2483
2486
|
* only) — the caller then generates no upsert mutations for that table at all, rather than
|
|
2484
2487
|
* an operation whose every call is a database error.
|
|
@@ -2486,12 +2489,13 @@ const conflictActionEnumType = (typeName) => sharedType(typeName, {
|
|
|
2486
2489
|
const generateOnConflictInput = (params) => {
|
|
2487
2490
|
const { table, tableName, typeName, uniqueSets, tableFilters, withTarget, cacheCtx } = params;
|
|
2488
2491
|
const resolveName = cacheCtx.typeName;
|
|
2492
|
+
const serverOwned = Object.keys(cacheCtx.contextValuesOf?.(tableName) ?? {}).sort();
|
|
2489
2493
|
const updateEnum = generateColumnEnum(table, resolveName({
|
|
2490
2494
|
kind: "columnEnum",
|
|
2491
2495
|
defaultName: `${typeName}UpdateColumn`,
|
|
2492
2496
|
table: tableName,
|
|
2493
2497
|
operation: "conflictUpdate"
|
|
2494
|
-
}), `Columns of ${typeName} that an upsert can overwrite
|
|
2498
|
+
}), `Columns of ${typeName} that an upsert can overwrite`, (_column, columnName) => !serverOwned.includes(columnName), serverOwned.join(","));
|
|
2495
2499
|
if (!updateEnum) return;
|
|
2496
2500
|
const fields = {
|
|
2497
2501
|
action: {
|
|
@@ -2501,11 +2505,11 @@ const generateOnConflictInput = (params) => {
|
|
|
2501
2505
|
},
|
|
2502
2506
|
update: {
|
|
2503
2507
|
type: new graphql.GraphQLList(new graphql.GraphQLNonNull(updateEnum)),
|
|
2504
|
-
description:
|
|
2508
|
+
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.`
|
|
2505
2509
|
}
|
|
2506
2510
|
};
|
|
2507
2511
|
if (withTarget) {
|
|
2508
|
-
const uniqueColumns = new Set(uniqueSets.
|
|
2512
|
+
const uniqueColumns = new Set(uniqueSets.flatMap((set) => set.columns));
|
|
2509
2513
|
const targetEnum = generateColumnEnum(table, resolveName({
|
|
2510
2514
|
kind: "columnEnum",
|
|
2511
2515
|
defaultName: `${typeName}ConflictTarget`,
|
|
@@ -2533,55 +2537,88 @@ const generateOnConflictInput = (params) => {
|
|
|
2533
2537
|
});
|
|
2534
2538
|
};
|
|
2535
2539
|
/**
|
|
2536
|
-
* Turns the request's `onConflict` argument and the rows it is inserting into the
|
|
2537
|
-
* dialect should
|
|
2540
|
+
* Turns the request's `onConflict` argument and the rows it is inserting into the
|
|
2541
|
+
* statements a dialect should run — one per run of consecutive rows that write the same
|
|
2542
|
+
* columns on conflict.
|
|
2543
|
+
*
|
|
2544
|
+
* The split is what keeps a partial row partial. A multi-row insert fills a key one row
|
|
2545
|
+
* left out with DEFAULT, and `excluded.<column>` would carry that default onto the existing
|
|
2546
|
+
* row, so rows that supply different columns cannot share a `SET` list. Splitting into
|
|
2547
|
+
* *consecutive* runs, rather than grouping, keeps the rows a caller gets back in the order
|
|
2548
|
+
* it sent them. A batch whose rows agree — the usual case — is still one statement.
|
|
2538
2549
|
*
|
|
2539
2550
|
* `excludedRef` names the row that failed to insert in the dialect's own terms
|
|
2540
2551
|
* (`excluded.col` on PostgreSQL and SQLite, `values(col)` on MySQL), which is what makes a
|
|
2541
2552
|
* batch upsert update each row with its own values instead of the last row's.
|
|
2542
2553
|
*
|
|
2554
|
+
* Never written on conflict, even when supplied: the conflict target (it already matches);
|
|
2555
|
+
* the primary key on a dialect with no target (MySQL's conflict can be on another unique
|
|
2556
|
+
* key, and copying a generated id over the existing row's would move it); and `serverOwned`
|
|
2557
|
+
* — the `contextValues` columns, which an update never reassigns.
|
|
2558
|
+
*
|
|
2543
2559
|
* An UPDATE with nothing left to write degrades to NOTHING: `DO UPDATE SET` with an empty
|
|
2544
2560
|
* body is not valid SQL, and doing nothing is what the request asked for anyway.
|
|
2545
2561
|
*/
|
|
2546
2562
|
const resolveConflictPlan = (params) => {
|
|
2547
2563
|
const { table, values, onConflict, pkNames, uniqueSets, excludedRef, withTarget, buildWhere } = params;
|
|
2548
2564
|
const columns = (0, drizzle_orm.getColumns)(table);
|
|
2565
|
+
const serverOwned = new Set(params.serverOwned ?? []);
|
|
2549
2566
|
let target;
|
|
2567
|
+
let targetWhere;
|
|
2550
2568
|
if (withTarget) {
|
|
2551
2569
|
const targetNames = onConflict?.target?.length ? onConflict.target : [...pkNames];
|
|
2552
2570
|
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" });
|
|
2553
2571
|
const requested = [...targetNames].sort().join(",");
|
|
2554
|
-
|
|
2572
|
+
const matched = uniqueSets.find((set) => [...set.columns].sort().join(",") === requested);
|
|
2573
|
+
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" });
|
|
2555
2574
|
target = targetNames.map((name) => columns[name]);
|
|
2575
|
+
targetWhere = matched.where;
|
|
2556
2576
|
}
|
|
2557
|
-
|
|
2577
|
+
const nothing = {
|
|
2558
2578
|
action: "NOTHING",
|
|
2559
2579
|
target,
|
|
2580
|
+
targetWhere,
|
|
2560
2581
|
set: {},
|
|
2561
2582
|
setWhere: void 0
|
|
2562
2583
|
};
|
|
2584
|
+
if ((onConflict?.action ?? "UPDATE") === "NOTHING") return [{
|
|
2585
|
+
values,
|
|
2586
|
+
plan: nothing
|
|
2587
|
+
}];
|
|
2563
2588
|
const supplied = new Set(values.flatMap((row) => Object.keys(row)));
|
|
2564
|
-
const
|
|
2565
|
-
let
|
|
2589
|
+
const neverWritten = new Set(withTarget && onConflict?.target?.length ? onConflict.target : pkNames);
|
|
2590
|
+
let candidates;
|
|
2566
2591
|
if (onConflict?.update?.length) {
|
|
2592
|
+
const owned = onConflict.update.filter((name) => serverOwned.has(name));
|
|
2593
|
+
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" });
|
|
2567
2594
|
const unsupplied = onConflict.update.filter((name) => !supplied.has(name));
|
|
2568
2595
|
if (unsupplied.length) throw drizzleError(`Unable to upsert: onConflict.update lists ${unsupplied.join(", ")}, which the values do not supply.`, { code: "DRIZZLE_INVALID_ON_CONFLICT" });
|
|
2569
|
-
|
|
2570
|
-
} else
|
|
2571
|
-
if (!updateNames.length) return {
|
|
2572
|
-
action: "NOTHING",
|
|
2573
|
-
target,
|
|
2574
|
-
set: {},
|
|
2575
|
-
setWhere: void 0
|
|
2576
|
-
};
|
|
2577
|
-
const set = Object.fromEntries(updateNames.map((name) => [name, excludedRef(columns[name].name)]));
|
|
2596
|
+
candidates = onConflict.update;
|
|
2597
|
+
} else candidates = [...supplied].filter((name) => !neverWritten.has(name) && !serverOwned.has(name));
|
|
2578
2598
|
const setWhere = onConflict?.where && buildWhere ? buildWhere(onConflict.where) : void 0;
|
|
2579
|
-
|
|
2599
|
+
const planFor = (updateNames) => updateNames.length ? {
|
|
2580
2600
|
action: "UPDATE",
|
|
2581
2601
|
target,
|
|
2582
|
-
|
|
2602
|
+
targetWhere,
|
|
2603
|
+
set: Object.fromEntries(updateNames.map((name) => [name, excludedRef(columns[name].name)])),
|
|
2583
2604
|
setWhere
|
|
2584
|
-
};
|
|
2605
|
+
} : nothing;
|
|
2606
|
+
const batches = [];
|
|
2607
|
+
for (const row of values) {
|
|
2608
|
+
const updateNames = candidates.filter((name) => name in row);
|
|
2609
|
+
const key = updateNames.join(",");
|
|
2610
|
+
const last = batches[batches.length - 1];
|
|
2611
|
+
if (last?.key === key) last.values.push(row);
|
|
2612
|
+
else batches.push({
|
|
2613
|
+
key,
|
|
2614
|
+
values: [row],
|
|
2615
|
+
plan: planFor(updateNames)
|
|
2616
|
+
});
|
|
2617
|
+
}
|
|
2618
|
+
return batches.map(({ values: rows, plan }) => ({
|
|
2619
|
+
values: rows,
|
|
2620
|
+
plan
|
|
2621
|
+
}));
|
|
2585
2622
|
};
|
|
2586
2623
|
/** `excluded.<column>` — PostgreSQL and SQLite name the rejected row this way. */
|
|
2587
2624
|
const excludedColumnRef = (columnName) => drizzle_orm.sql`excluded.${drizzle_orm.sql.identifier(columnName)}`;
|
|
@@ -3025,9 +3062,9 @@ const primaryKeyOrderExprs = (table, pkNames) => {
|
|
|
3025
3062
|
return pkNames.map((n) => cols[n]).filter(Boolean).map((col) => (0, drizzle_orm.asc)(col));
|
|
3026
3063
|
};
|
|
3027
3064
|
/**
|
|
3028
|
-
* Every set of columns that
|
|
3029
|
-
*
|
|
3030
|
-
*
|
|
3065
|
+
* Every set of columns that is unique on `table`: the primary key first, then each unique
|
|
3066
|
+
* constraint and unique index, then each column declared `.unique()` inline. Sets are
|
|
3067
|
+
* property names (what GraphQL inputs use), not database column names.
|
|
3031
3068
|
*
|
|
3032
3069
|
* `getTableConfig` is the dialect's own — the three dialects expose the same
|
|
3033
3070
|
* `{ primaryKeys, uniqueConstraints, indexes }` shape but from different modules, so the
|
|
@@ -3036,9 +3073,13 @@ const primaryKeyOrderExprs = (table, pkNames) => {
|
|
|
3036
3073
|
* Index entries whose columns are SQL expressions rather than plain columns are skipped:
|
|
3037
3074
|
* an expression index is a valid conflict target in the database but cannot be named by a
|
|
3038
3075
|
* column enum. Deduplicated, order-insensitive — a column that is both the primary key and
|
|
3039
|
-
* a unique constraint yields one set
|
|
3076
|
+
* a unique constraint yields one set, and a set that is unique both outright and under a
|
|
3077
|
+
* partial index keeps the outright one.
|
|
3078
|
+
*
|
|
3079
|
+
* A partial unique index carries its predicate as `where`: it is a conflict target only
|
|
3080
|
+
* together with that predicate, and it does not make the set unique across the whole table.
|
|
3040
3081
|
*/
|
|
3041
|
-
const
|
|
3082
|
+
const getUniqueTargets = (table, getTableConfig) => {
|
|
3042
3083
|
const cols = visibleColumns(table);
|
|
3043
3084
|
const propNameByColumnName = new Map(Object.entries(cols).map(([propName, col]) => [col.name, propName]));
|
|
3044
3085
|
const toPropNames = (columnNames) => {
|
|
@@ -3050,25 +3091,40 @@ const getUniqueColumnSets = (table, getTableConfig) => {
|
|
|
3050
3091
|
}
|
|
3051
3092
|
return propNames.length ? propNames : void 0;
|
|
3052
3093
|
};
|
|
3094
|
+
const whole = (columns) => columns ? { columns } : void 0;
|
|
3053
3095
|
const config = getTableConfig(table);
|
|
3054
3096
|
const candidates = [
|
|
3055
|
-
Object.entries(cols).filter(([, col]) => col.primary).map(([propName]) => propName),
|
|
3056
|
-
...config.primaryKeys.map((pk) => toPropNames(pk.columns.map((c) => c.name))),
|
|
3057
|
-
...(config.uniqueConstraints ?? []).map((uc) => toPropNames(uc.columns.map((c) => c.name))),
|
|
3058
|
-
...(config.indexes ?? []).filter((index) => index.config.unique).map((index) =>
|
|
3059
|
-
|
|
3097
|
+
whole(Object.entries(cols).filter(([, col]) => col.primary).map(([propName]) => propName)),
|
|
3098
|
+
...config.primaryKeys.map((pk) => whole(toPropNames(pk.columns.map((c) => c.name)))),
|
|
3099
|
+
...(config.uniqueConstraints ?? []).map((uc) => whole(toPropNames(uc.columns.map((c) => c.name)))),
|
|
3100
|
+
...(config.indexes ?? []).filter((index) => index.config.unique).map((index) => {
|
|
3101
|
+
const columns = toPropNames(index.config.columns.map((c) => c?.name));
|
|
3102
|
+
return columns ? {
|
|
3103
|
+
columns,
|
|
3104
|
+
...index.config.where ? { where: index.config.where } : {}
|
|
3105
|
+
} : void 0;
|
|
3106
|
+
}),
|
|
3107
|
+
...Object.entries(cols).filter(([, col]) => col.isUnique).map(([propName]) => ({ columns: [propName] }))
|
|
3060
3108
|
];
|
|
3061
|
-
const
|
|
3062
|
-
const
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
const
|
|
3066
|
-
if (seen.
|
|
3067
|
-
seen.
|
|
3068
|
-
sets.push(set);
|
|
3109
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
3110
|
+
for (const candidate of candidates) {
|
|
3111
|
+
if (!candidate?.columns.length) continue;
|
|
3112
|
+
const key = [...candidate.columns].sort().join(",");
|
|
3113
|
+
const seen = byKey.get(key);
|
|
3114
|
+
if (!seen) byKey.set(key, candidate);
|
|
3115
|
+
else if (seen.where && !candidate.where) byKey.set(key, { columns: seen.columns });
|
|
3069
3116
|
}
|
|
3070
|
-
return
|
|
3117
|
+
return [...byKey.values()];
|
|
3071
3118
|
};
|
|
3119
|
+
/**
|
|
3120
|
+
* The column sets that identify at most one row of `table` — {@link getUniqueTargets}
|
|
3121
|
+
* without the partial unique indexes, which only constrain the rows their predicate
|
|
3122
|
+
* matches (a soft-deleted duplicate is allowed), and so do not.
|
|
3123
|
+
*
|
|
3124
|
+
* Pass `{ partial: true }` to keep them, for callers that only need to know a set is
|
|
3125
|
+
* unique among the rows that matter.
|
|
3126
|
+
*/
|
|
3127
|
+
const getUniqueColumnSets = (table, getTableConfig, options = {}) => getUniqueTargets(table, getTableConfig).filter((target) => options.partial || !target.where).map((target) => target.columns);
|
|
3072
3128
|
//#endregion
|
|
3073
3129
|
//#region src/util/builders/common/distinct.ts
|
|
3074
3130
|
/**
|
|
@@ -4698,6 +4754,19 @@ const keysFromWhere = (where, primaryKey) => {
|
|
|
4698
4754
|
return none;
|
|
4699
4755
|
};
|
|
4700
4756
|
/**
|
|
4757
|
+
* Whether an upsert's `onConflict` leaves the primary-key values in `values` naming exactly
|
|
4758
|
+
* the rows it writes. It does not when the conflict is detected on another unique key — the
|
|
4759
|
+
* row updated is whichever one holds that key, not the one the supplied primary key names —
|
|
4760
|
+
* nor when conflicting rows can be skipped: `action: NOTHING`, or a `where` guard.
|
|
4761
|
+
*/
|
|
4762
|
+
const conflictsOnKeyAlone = (onConflict, primaryKey) => {
|
|
4763
|
+
if (!onConflict || typeof onConflict !== "object") return true;
|
|
4764
|
+
const { target, action, where } = onConflict;
|
|
4765
|
+
if (action === "NOTHING" || where !== void 0 && where !== null) return false;
|
|
4766
|
+
if (!Array.isArray(target) || !target.length) return true;
|
|
4767
|
+
return [...target].sort().join(",") === [...primaryKey].sort().join(",");
|
|
4768
|
+
};
|
|
4769
|
+
/**
|
|
4701
4770
|
* The rows a generated field's arguments are about, as primary-key values.
|
|
4702
4771
|
*
|
|
4703
4772
|
* The row identity of a mutation lives in a different argument for every operation —
|
|
@@ -4733,7 +4802,7 @@ const identifyRows = (field, args) => {
|
|
|
4733
4802
|
table,
|
|
4734
4803
|
primaryKey,
|
|
4735
4804
|
rows: rows.filter((row) => !!row),
|
|
4736
|
-
complete: entries.length > 0 && rows.every(Boolean)
|
|
4805
|
+
complete: entries.length > 0 && rows.every(Boolean) && (meta.operation !== "upsert" || conflictsOnKeyAlone(args?.["onConflict"], primaryKey))
|
|
4737
4806
|
};
|
|
4738
4807
|
}
|
|
4739
4808
|
if (targetArg === "updates") {
|
|
@@ -7210,7 +7279,7 @@ const createSchemaBuilder = (adapter) => {
|
|
|
7210
7279
|
uniqueKeysOf: (tableName) => uniqueKeys[tableName]
|
|
7211
7280
|
};
|
|
7212
7281
|
adapter.preflight?.(db, options);
|
|
7213
|
-
const nestedPlans = features.nestedWrites ? buildNestedWritePlans(tables, namedRelations, (target) =>
|
|
7282
|
+
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;
|
|
7214
7283
|
if (nestedPlans) {
|
|
7215
7284
|
const omitted = gateNestedWritePlans(nestedPlans, featureOf, (tableName) => excludedTables.has(tableName));
|
|
7216
7285
|
if (omitted.length) {
|
|
@@ -7498,20 +7567,33 @@ const generateUpsert = (db, tableName, table, baseType, onConflictType, fieldNam
|
|
|
7498
7567
|
}
|
|
7499
7568
|
},
|
|
7500
7569
|
run: async ({ executor, args, context, before, after }) => {
|
|
7501
|
-
const
|
|
7570
|
+
const contextColumns = policies?.contextValues?.(tableName);
|
|
7571
|
+
const input = applyContextValuesAll(single ? [remapFromGraphQLSingleInput(args.values, table)] : remapFromGraphQLArrayInput(args.values, table), contextColumns, context);
|
|
7502
7572
|
if (!input.length) throw drizzleError("No values were provided!", { code: "DRIZZLE_NO_VALUES" });
|
|
7503
7573
|
await before();
|
|
7504
|
-
const
|
|
7574
|
+
const batches = resolveConflictPlan({
|
|
7505
7575
|
table,
|
|
7506
7576
|
values: input,
|
|
7507
7577
|
onConflict: args.onConflict,
|
|
7508
7578
|
pkNames,
|
|
7509
7579
|
uniqueSets: [],
|
|
7510
7580
|
excludedRef: mysqlValuesColumnRef,
|
|
7511
|
-
withTarget: false
|
|
7581
|
+
withTarget: false,
|
|
7582
|
+
serverOwned: Object.keys(contextColumns ?? {})
|
|
7583
|
+
});
|
|
7584
|
+
const requestedNothing = args.onConflict?.action === "NOTHING";
|
|
7585
|
+
const runBatch = async (target, { values, plan }) => {
|
|
7586
|
+
if (plan.action === "NOTHING" && !requestedNothing) {
|
|
7587
|
+
const columns = (0, drizzle_orm.getColumns)(table);
|
|
7588
|
+
const keyName = pkNames[0] ?? Object.keys(columns)[0];
|
|
7589
|
+
await target.insert(table).values(values).onDuplicateKeyUpdate({ set: { [keyName]: drizzle_orm.sql`${drizzle_orm.sql.identifier(columns[keyName].name)}` } });
|
|
7590
|
+
} else if (plan.action === "NOTHING") await target.insert(table).ignore().values(values);
|
|
7591
|
+
else await target.insert(table).values(values).onDuplicateKeyUpdate({ set: plan.set });
|
|
7592
|
+
};
|
|
7593
|
+
if (batches.length === 1) await runBatch(executor, batches[0]);
|
|
7594
|
+
else await executor.transaction(async (tx) => {
|
|
7595
|
+
for (const batch of batches) await runBatch(tx, batch);
|
|
7512
7596
|
});
|
|
7513
|
-
if (plan.action === "NOTHING") await executor.insert(table).ignore().values(input);
|
|
7514
|
-
else await executor.insert(table).values(input).onDuplicateKeyUpdate({ set: plan.set });
|
|
7515
7597
|
await after();
|
|
7516
7598
|
return isSuccess;
|
|
7517
7599
|
}
|
|
@@ -7957,7 +8039,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7957
8039
|
});
|
|
7958
8040
|
const returning = withHookReturning(nestedEntries ? nested.withJoinColumns(tableName, mergedOps(nestedEntries), { ...columns }, table) : columns);
|
|
7959
8041
|
const runUpsert = async (target, values) => {
|
|
7960
|
-
const
|
|
8042
|
+
const batches = resolveConflictPlan({
|
|
7961
8043
|
table,
|
|
7962
8044
|
values,
|
|
7963
8045
|
onConflict: args.onConflict,
|
|
@@ -7965,16 +8047,29 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7965
8047
|
uniqueSets,
|
|
7966
8048
|
excludedRef: excludedColumnRef,
|
|
7967
8049
|
withTarget: true,
|
|
8050
|
+
serverOwned: Object.keys(contextColumns ?? {}),
|
|
7968
8051
|
buildWhere: (where) => extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName))
|
|
7969
8052
|
});
|
|
7970
|
-
|
|
7971
|
-
|
|
7972
|
-
|
|
7973
|
-
|
|
7974
|
-
|
|
7975
|
-
|
|
8053
|
+
const runBatch = async (executor, { values: rows, plan }) => {
|
|
8054
|
+
let query = executor.insert(table).values(rows).returning(returning);
|
|
8055
|
+
const setWhere = withScope(scope, tableName, table, plan.setWhere);
|
|
8056
|
+
query = plan.action === "NOTHING" ? query.onConflictDoNothing(plan.target ? {
|
|
8057
|
+
target: plan.target,
|
|
8058
|
+
where: plan.targetWhere
|
|
8059
|
+
} : void 0) : query.onConflictDoUpdate({
|
|
8060
|
+
target: plan.target,
|
|
8061
|
+
targetWhere: plan.targetWhere,
|
|
8062
|
+
set: plan.set,
|
|
8063
|
+
setWhere
|
|
8064
|
+
});
|
|
8065
|
+
return await query;
|
|
8066
|
+
};
|
|
8067
|
+
if (batches.length === 1) return runBatch(target, batches[0]);
|
|
8068
|
+
return target.transaction(async (tx) => {
|
|
8069
|
+
const rows = [];
|
|
8070
|
+
for (const batch of batches) rows.push(...await runBatch(tx, batch));
|
|
8071
|
+
return rows;
|
|
7976
8072
|
});
|
|
7977
|
-
return await query;
|
|
7978
8073
|
};
|
|
7979
8074
|
const written = nestedEntries ? await writeWithNestedOps({
|
|
7980
8075
|
executor,
|
|
@@ -8154,7 +8249,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
8154
8249
|
*/
|
|
8155
8250
|
const createSchemaDataGenerator = (adapter) => {
|
|
8156
8251
|
const primaryKeyPropNames = adapter.primaryKeyPropNames;
|
|
8157
|
-
const
|
|
8252
|
+
const uniqueTargets = (table) => getUniqueTargets(table, adapter.getTableConfig);
|
|
8158
8253
|
const { generateInsert, generateUpsert, generateUpdate, generateDelete } = buildWriteResolvers(primaryKeyPropNames);
|
|
8159
8254
|
const { prepareBuild, forEachTable, finalizeBuild } = createSchemaBuilder(adapter);
|
|
8160
8255
|
return (db, schema, relations, options) => {
|
|
@@ -8195,7 +8290,7 @@ const createSchemaDataGenerator = (adapter) => {
|
|
|
8195
8290
|
single: true,
|
|
8196
8291
|
conflictDoNothing
|
|
8197
8292
|
}) : void 0;
|
|
8198
|
-
const uniqueSets = tableFeatures.upsert ?
|
|
8293
|
+
const uniqueSets = tableFeatures.upsert ? uniqueTargets(schema[tableName]) : [];
|
|
8199
8294
|
const onConflictInput = tableFeatures.upsert ? generateOnConflictInput({
|
|
8200
8295
|
table: schema[tableName],
|
|
8201
8296
|
tableName,
|