@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/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, or a table with no primary key. `rows` is then
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. The fields live
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, or a table with no primary key. `rows` is then
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. The fields live
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: "Columns to overwrite on conflict. Defaults to every column the request supplied, minus the conflict target. Columns the request did not supply cannot be listed here — there would be no value to write."
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.flat());
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 clause a
2513
- * dialect should attach.
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
- if (!uniqueSets.some((set) => [...set].sort().join(",") === requested)) throw drizzleError(`Unable to upsert: [${targetNames.join(", ")}] is not a unique constraint on this table. Valid conflict targets: ${uniqueSets.map((set) => `[${set.join(", ")}]`).join(", ")}.`, { code: "DRIZZLE_INVALID_ON_CONFLICT" });
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
- if ((onConflict?.action ?? "UPDATE") === "NOTHING") return {
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 targetNames = new Set(withTarget ? onConflict?.target?.length ? onConflict.target : pkNames : []);
2541
- let updateNames;
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
- updateNames = onConflict.update;
2546
- } else updateNames = [...supplied].filter((name) => !targetNames.has(name));
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
- return {
2575
+ const planFor = (updateNames) => updateNames.length ? {
2556
2576
  action: "UPDATE",
2557
2577
  target,
2558
- set,
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 uniquely identifies a row of `table`: the primary key first,
3005
- * then each unique constraint and unique index, then each column declared `.unique()`
3006
- * inline. Sets are property names (what GraphQL inputs use), not database column names.
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 getUniqueColumnSets = (table, getTableConfig) => {
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) => toPropNames(index.config.columns.map((c) => c?.name))),
3035
- ...Object.entries(cols).filter(([, col]) => col.isUnique).map(([propName]) => [propName])
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 seen = /* @__PURE__ */ new Set();
3038
- const sets = [];
3039
- for (const set of candidates) {
3040
- if (!set?.length) continue;
3041
- const key = [...set].sort().join(",");
3042
- if (seen.has(key)) continue;
3043
- seen.add(key);
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 sets;
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) => uniqueColumnSets(target), (target) => primaryKeyPropNames(target), extractRelationJoinColumns, (junction) => schemaEntries.find(([, value]) => value === junction)?.[0]) : void 0;
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 input = applyContextValuesAll(single ? [remapFromGraphQLSingleInput(args.values, table)] : remapFromGraphQLArrayInput(args.values, table), policies?.contextValues?.(tableName), context);
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 plan = resolveConflictPlan({
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 plan = resolveConflictPlan({
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
- let query = target.insert(table).values(values).returning(returning);
7947
- const setWhere = withScope(scope, tableName, table, plan.setWhere);
7948
- query = plan.action === "NOTHING" ? query.onConflictDoNothing(plan.target ? { target: plan.target } : void 0) : query.onConflictDoUpdate({
7949
- target: plan.target,
7950
- set: plan.set,
7951
- setWhere
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 uniqueColumnSets = (table) => getUniqueColumnSets(table, adapter.getTableConfig);
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 ? uniqueColumnSets(schema[tableName]) : [];
8269
+ const uniqueSets = tableFeatures.upsert ? uniqueTargets(schema[tableName]) : [];
8175
8270
  const onConflictInput = tableFeatures.upsert ? generateOnConflictInput({
8176
8271
  table: schema[tableName],
8177
8272
  tableName,