@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.cjs
CHANGED
|
@@ -1616,13 +1616,20 @@ const nullableOps = {
|
|
|
1616
1616
|
isNull: drizzle_orm.isNull,
|
|
1617
1617
|
isNotNull: drizzle_orm.isNotNull
|
|
1618
1618
|
};
|
|
1619
|
+
/** Operators whose operand is an on/off flag rather than a value: `false` adds no condition. */
|
|
1620
|
+
const FLAG_OPERATORS = /* @__PURE__ */ new Set([
|
|
1621
|
+
"isNull",
|
|
1622
|
+
"isNotNull",
|
|
1623
|
+
"isEmpty"
|
|
1624
|
+
]);
|
|
1619
1625
|
const extractFiltersColumn = (column, columnName, operators) => {
|
|
1620
1626
|
const { OR, AND, NOT, insensitive, ...restOperators } = operators;
|
|
1621
1627
|
const foldCase = insensitive === true;
|
|
1622
1628
|
const entries = Object.entries(restOperators);
|
|
1623
1629
|
const variants = [];
|
|
1624
1630
|
for (const [operatorName, operatorValue] of entries) {
|
|
1625
|
-
if (operatorValue === null || operatorValue ===
|
|
1631
|
+
if (operatorValue === null || operatorValue === void 0) continue;
|
|
1632
|
+
if (operatorValue === false && FLAG_OPERATORS.has(operatorName)) continue;
|
|
1626
1633
|
if (operatorName in singleValueOps) {
|
|
1627
1634
|
const singleValue = remapFromGraphQLCore(operatorValue, column, columnName);
|
|
1628
1635
|
variants.push(foldCase ? lowerComparison(column, operatorName, singleValue) : singleValueOps[operatorName](column, singleValue));
|
|
@@ -2411,8 +2418,8 @@ const columnEnumCache = /* @__PURE__ */ new WeakMap();
|
|
|
2411
2418
|
* input field the enum would have typed, rather than emitting an empty enum, which is
|
|
2412
2419
|
* invalid GraphQL.
|
|
2413
2420
|
*/
|
|
2414
|
-
const generateColumnEnum = (table, enumName, description, predicate = () => true) => {
|
|
2415
|
-
const cacheKey = `${enumName}\u0000${excludedColumnsKey(table)}`;
|
|
2421
|
+
const generateColumnEnum = (table, enumName, description, predicate = () => true, variant = "") => {
|
|
2422
|
+
const cacheKey = `${enumName}\u0000${excludedColumnsKey(table)}\u0000${variant}`;
|
|
2416
2423
|
let tableCache = columnEnumCache.get(table);
|
|
2417
2424
|
const cached = tableCache?.get(cacheKey);
|
|
2418
2425
|
if (cached) return cached;
|
|
@@ -2472,6 +2479,9 @@ const conflictActionEnumType = (typeName) => sharedType(typeName, {
|
|
|
2472
2479
|
* `ON DUPLICATE KEY UPDATE` fires on any unique key and takes no predicate, so offering
|
|
2473
2480
|
* either there would mean silently ignoring it.
|
|
2474
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
|
+
*
|
|
2475
2485
|
* Returns `undefined` when the table has nothing to conflict on (`withTarget` dialects
|
|
2476
2486
|
* only) — the caller then generates no upsert mutations for that table at all, rather than
|
|
2477
2487
|
* an operation whose every call is a database error.
|
|
@@ -2479,12 +2489,13 @@ const conflictActionEnumType = (typeName) => sharedType(typeName, {
|
|
|
2479
2489
|
const generateOnConflictInput = (params) => {
|
|
2480
2490
|
const { table, tableName, typeName, uniqueSets, tableFilters, withTarget, cacheCtx } = params;
|
|
2481
2491
|
const resolveName = cacheCtx.typeName;
|
|
2492
|
+
const serverOwned = Object.keys(cacheCtx.contextValuesOf?.(tableName) ?? {}).sort();
|
|
2482
2493
|
const updateEnum = generateColumnEnum(table, resolveName({
|
|
2483
2494
|
kind: "columnEnum",
|
|
2484
2495
|
defaultName: `${typeName}UpdateColumn`,
|
|
2485
2496
|
table: tableName,
|
|
2486
2497
|
operation: "conflictUpdate"
|
|
2487
|
-
}), `Columns of ${typeName} that an upsert can overwrite
|
|
2498
|
+
}), `Columns of ${typeName} that an upsert can overwrite`, (_column, columnName) => !serverOwned.includes(columnName), serverOwned.join(","));
|
|
2488
2499
|
if (!updateEnum) return;
|
|
2489
2500
|
const fields = {
|
|
2490
2501
|
action: {
|
|
@@ -2494,11 +2505,11 @@ const generateOnConflictInput = (params) => {
|
|
|
2494
2505
|
},
|
|
2495
2506
|
update: {
|
|
2496
2507
|
type: new graphql.GraphQLList(new graphql.GraphQLNonNull(updateEnum)),
|
|
2497
|
-
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.`
|
|
2498
2509
|
}
|
|
2499
2510
|
};
|
|
2500
2511
|
if (withTarget) {
|
|
2501
|
-
const uniqueColumns = new Set(uniqueSets.
|
|
2512
|
+
const uniqueColumns = new Set(uniqueSets.flatMap((set) => set.columns));
|
|
2502
2513
|
const targetEnum = generateColumnEnum(table, resolveName({
|
|
2503
2514
|
kind: "columnEnum",
|
|
2504
2515
|
defaultName: `${typeName}ConflictTarget`,
|
|
@@ -2526,55 +2537,88 @@ const generateOnConflictInput = (params) => {
|
|
|
2526
2537
|
});
|
|
2527
2538
|
};
|
|
2528
2539
|
/**
|
|
2529
|
-
* Turns the request's `onConflict` argument and the rows it is inserting into the
|
|
2530
|
-
* 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.
|
|
2531
2549
|
*
|
|
2532
2550
|
* `excludedRef` names the row that failed to insert in the dialect's own terms
|
|
2533
2551
|
* (`excluded.col` on PostgreSQL and SQLite, `values(col)` on MySQL), which is what makes a
|
|
2534
2552
|
* batch upsert update each row with its own values instead of the last row's.
|
|
2535
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
|
+
*
|
|
2536
2559
|
* An UPDATE with nothing left to write degrades to NOTHING: `DO UPDATE SET` with an empty
|
|
2537
2560
|
* body is not valid SQL, and doing nothing is what the request asked for anyway.
|
|
2538
2561
|
*/
|
|
2539
2562
|
const resolveConflictPlan = (params) => {
|
|
2540
2563
|
const { table, values, onConflict, pkNames, uniqueSets, excludedRef, withTarget, buildWhere } = params;
|
|
2541
2564
|
const columns = (0, drizzle_orm.getColumns)(table);
|
|
2565
|
+
const serverOwned = new Set(params.serverOwned ?? []);
|
|
2542
2566
|
let target;
|
|
2567
|
+
let targetWhere;
|
|
2543
2568
|
if (withTarget) {
|
|
2544
2569
|
const targetNames = onConflict?.target?.length ? onConflict.target : [...pkNames];
|
|
2545
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" });
|
|
2546
2571
|
const requested = [...targetNames].sort().join(",");
|
|
2547
|
-
|
|
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" });
|
|
2548
2574
|
target = targetNames.map((name) => columns[name]);
|
|
2575
|
+
targetWhere = matched.where;
|
|
2549
2576
|
}
|
|
2550
|
-
|
|
2577
|
+
const nothing = {
|
|
2551
2578
|
action: "NOTHING",
|
|
2552
2579
|
target,
|
|
2580
|
+
targetWhere,
|
|
2553
2581
|
set: {},
|
|
2554
2582
|
setWhere: void 0
|
|
2555
2583
|
};
|
|
2584
|
+
if ((onConflict?.action ?? "UPDATE") === "NOTHING") return [{
|
|
2585
|
+
values,
|
|
2586
|
+
plan: nothing
|
|
2587
|
+
}];
|
|
2556
2588
|
const supplied = new Set(values.flatMap((row) => Object.keys(row)));
|
|
2557
|
-
const
|
|
2558
|
-
let
|
|
2589
|
+
const neverWritten = new Set(withTarget && onConflict?.target?.length ? onConflict.target : pkNames);
|
|
2590
|
+
let candidates;
|
|
2559
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" });
|
|
2560
2594
|
const unsupplied = onConflict.update.filter((name) => !supplied.has(name));
|
|
2561
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" });
|
|
2562
|
-
|
|
2563
|
-
} else
|
|
2564
|
-
if (!updateNames.length) return {
|
|
2565
|
-
action: "NOTHING",
|
|
2566
|
-
target,
|
|
2567
|
-
set: {},
|
|
2568
|
-
setWhere: void 0
|
|
2569
|
-
};
|
|
2570
|
-
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));
|
|
2571
2598
|
const setWhere = onConflict?.where && buildWhere ? buildWhere(onConflict.where) : void 0;
|
|
2572
|
-
|
|
2599
|
+
const planFor = (updateNames) => updateNames.length ? {
|
|
2573
2600
|
action: "UPDATE",
|
|
2574
2601
|
target,
|
|
2575
|
-
|
|
2602
|
+
targetWhere,
|
|
2603
|
+
set: Object.fromEntries(updateNames.map((name) => [name, excludedRef(columns[name].name)])),
|
|
2576
2604
|
setWhere
|
|
2577
|
-
};
|
|
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
|
+
}));
|
|
2578
2622
|
};
|
|
2579
2623
|
/** `excluded.<column>` — PostgreSQL and SQLite name the rejected row this way. */
|
|
2580
2624
|
const excludedColumnRef = (columnName) => drizzle_orm.sql`excluded.${drizzle_orm.sql.identifier(columnName)}`;
|
|
@@ -2756,6 +2800,45 @@ const cursorOrderExprs = (table, entries) => {
|
|
|
2756
2800
|
return entries.flatMap(([column, direction, nulls]) => orderExpressions(cols[column], direction, nulls ?? void 0, cols[column]));
|
|
2757
2801
|
};
|
|
2758
2802
|
/**
|
|
2803
|
+
* Property a row carries an ordering column's full-precision text under, when the column holds
|
|
2804
|
+
* more than a JS Date can. Namespaced like {@link ROW_CURSOR_PROP}, and removed from the row
|
|
2805
|
+
* once its cursor is encoded.
|
|
2806
|
+
*/
|
|
2807
|
+
const preciseTextProp = (column) => `__drizzle_graphql_cursor_text_${column}`;
|
|
2808
|
+
/**
|
|
2809
|
+
* Whether a column reads into a JS Date (milliseconds) while the database keeps finer
|
|
2810
|
+
* fractional seconds: a PostgreSQL `timestamp` / `timestamptz` in `mode: 'date'` (precision 6
|
|
2811
|
+
* unless it says otherwise) or a MySQL `datetime` / `timestamp` in `mode: 'date'` with
|
|
2812
|
+
* `fsp` above 3. String-mode columns already arrive as the database's own text, and `time` is
|
|
2813
|
+
* always a string, so neither loses anything.
|
|
2814
|
+
*/
|
|
2815
|
+
const outgrowsJsDate = (column) => {
|
|
2816
|
+
if ((0, drizzle_orm.is)(column, drizzle_orm_pg_core.PgTimestamp)) return column.precision === void 0 || column.precision > 3;
|
|
2817
|
+
if ((0, drizzle_orm.is)(column, drizzle_orm_mysql_core.MySqlDateTime) || (0, drizzle_orm.is)(column, drizzle_orm_mysql_core.MySqlTimestamp)) return (column.fsp ?? 0) > 3;
|
|
2818
|
+
return false;
|
|
2819
|
+
};
|
|
2820
|
+
/**
|
|
2821
|
+
* Extra SQL expressions a cursor-computing query selects alongside the row: for each ordering
|
|
2822
|
+
* column whose values a JS Date would truncate, the value as the database's own text. The
|
|
2823
|
+
* cursor then encodes that text rather than the Date, and `after` compares the column against
|
|
2824
|
+
* it — so two rows a microsecond apart stay distinct positions. `table` may be the aliased RQB
|
|
2825
|
+
* proxy. Empty when no ordering column needs it.
|
|
2826
|
+
*/
|
|
2827
|
+
const cursorPreciseExtras = (table, entries) => {
|
|
2828
|
+
const cols = (0, drizzle_orm.getColumns)(table);
|
|
2829
|
+
const extras = {};
|
|
2830
|
+
for (const [name] of entries) {
|
|
2831
|
+
const column = cols[name];
|
|
2832
|
+
if (column && outgrowsJsDate(column)) extras[preciseTextProp(name)] = (0, drizzle_orm.is)(column, drizzle_orm_pg_core.PgTimestamp) ? drizzle_orm.sql`${column}::text` : drizzle_orm.sql`cast(${column} as char)`;
|
|
2833
|
+
}
|
|
2834
|
+
return extras;
|
|
2835
|
+
};
|
|
2836
|
+
/**
|
|
2837
|
+
* {@link cursorPreciseExtras} aliased under their property names, for a `select({...})` that
|
|
2838
|
+
* may be wrapped in a subquery (where a bare SQL expression would have no name).
|
|
2839
|
+
*/
|
|
2840
|
+
const cursorPreciseSelection = (table, entries) => Object.fromEntries(Object.entries(cursorPreciseExtras(table, entries)).map(([prop, expression]) => [prop, expression.as(prop)]));
|
|
2841
|
+
/**
|
|
2759
2842
|
* Serializes one ordering-tuple value into a JSON-safe shape. Dates and bigints don't survive
|
|
2760
2843
|
* JSON.stringify losslessly (bigint throws, Date turns into an untagged string), so they are
|
|
2761
2844
|
* tagged; decimals already arrive from the driver as strings, which are lossless as-is.
|
|
@@ -2790,7 +2873,11 @@ const cursorEncoder = (entries) => {
|
|
|
2790
2873
|
const spec = JSON.stringify(entries);
|
|
2791
2874
|
const columns = entries.map(([column]) => column);
|
|
2792
2875
|
return (row) => {
|
|
2793
|
-
const values = JSON.stringify(columns.map((column) =>
|
|
2876
|
+
const values = JSON.stringify(columns.map((column) => {
|
|
2877
|
+
const value = row[column] ?? null;
|
|
2878
|
+
const precise = row[preciseTextProp(column)];
|
|
2879
|
+
return value !== null && typeof precise === "string" ? precise : encodeCursorValue(value);
|
|
2880
|
+
}));
|
|
2794
2881
|
return Buffer.from(`{"o":${spec},"v":${values}}`, "utf8").toString("base64url");
|
|
2795
2882
|
};
|
|
2796
2883
|
};
|
|
@@ -2883,7 +2970,11 @@ const selectsCursorField = (info, table) => {
|
|
|
2883
2970
|
*/
|
|
2884
2971
|
const attachRowCursors = (rows, entries) => {
|
|
2885
2972
|
const encode = cursorEncoder(entries);
|
|
2886
|
-
|
|
2973
|
+
const preciseProps = entries.map(([column]) => preciseTextProp(column));
|
|
2974
|
+
for (const row of rows) {
|
|
2975
|
+
row[ROW_CURSOR_PROP] = encode(row);
|
|
2976
|
+
for (const prop of preciseProps) delete row[prop];
|
|
2977
|
+
}
|
|
2887
2978
|
};
|
|
2888
2979
|
/** Resolver for the `cursor` meta field: reads the value a list resolver attached, if any. */
|
|
2889
2980
|
const rowCursorResolver = (source) => source?.[ROW_CURSOR_PROP] ?? null;
|
|
@@ -2971,9 +3062,9 @@ const primaryKeyOrderExprs = (table, pkNames) => {
|
|
|
2971
3062
|
return pkNames.map((n) => cols[n]).filter(Boolean).map((col) => (0, drizzle_orm.asc)(col));
|
|
2972
3063
|
};
|
|
2973
3064
|
/**
|
|
2974
|
-
* Every set of columns that
|
|
2975
|
-
*
|
|
2976
|
-
*
|
|
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.
|
|
2977
3068
|
*
|
|
2978
3069
|
* `getTableConfig` is the dialect's own — the three dialects expose the same
|
|
2979
3070
|
* `{ primaryKeys, uniqueConstraints, indexes }` shape but from different modules, so the
|
|
@@ -2982,9 +3073,13 @@ const primaryKeyOrderExprs = (table, pkNames) => {
|
|
|
2982
3073
|
* Index entries whose columns are SQL expressions rather than plain columns are skipped:
|
|
2983
3074
|
* an expression index is a valid conflict target in the database but cannot be named by a
|
|
2984
3075
|
* column enum. Deduplicated, order-insensitive — a column that is both the primary key and
|
|
2985
|
-
* 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.
|
|
2986
3081
|
*/
|
|
2987
|
-
const
|
|
3082
|
+
const getUniqueTargets = (table, getTableConfig) => {
|
|
2988
3083
|
const cols = visibleColumns(table);
|
|
2989
3084
|
const propNameByColumnName = new Map(Object.entries(cols).map(([propName, col]) => [col.name, propName]));
|
|
2990
3085
|
const toPropNames = (columnNames) => {
|
|
@@ -2996,25 +3091,40 @@ const getUniqueColumnSets = (table, getTableConfig) => {
|
|
|
2996
3091
|
}
|
|
2997
3092
|
return propNames.length ? propNames : void 0;
|
|
2998
3093
|
};
|
|
3094
|
+
const whole = (columns) => columns ? { columns } : void 0;
|
|
2999
3095
|
const config = getTableConfig(table);
|
|
3000
3096
|
const candidates = [
|
|
3001
|
-
Object.entries(cols).filter(([, col]) => col.primary).map(([propName]) => propName),
|
|
3002
|
-
...config.primaryKeys.map((pk) => toPropNames(pk.columns.map((c) => c.name))),
|
|
3003
|
-
...(config.uniqueConstraints ?? []).map((uc) => toPropNames(uc.columns.map((c) => c.name))),
|
|
3004
|
-
...(config.indexes ?? []).filter((index) => index.config.unique).map((index) =>
|
|
3005
|
-
|
|
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] }))
|
|
3006
3108
|
];
|
|
3007
|
-
const
|
|
3008
|
-
const
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
const
|
|
3012
|
-
if (seen.
|
|
3013
|
-
seen.
|
|
3014
|
-
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 });
|
|
3015
3116
|
}
|
|
3016
|
-
return
|
|
3117
|
+
return [...byKey.values()];
|
|
3017
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);
|
|
3018
3128
|
//#endregion
|
|
3019
3129
|
//#region src/util/builders/common/distinct.ts
|
|
3020
3130
|
/**
|
|
@@ -3241,6 +3351,21 @@ const withDefaultOrderBy = (args, tableName, defaults) => {
|
|
|
3241
3351
|
} : args;
|
|
3242
3352
|
};
|
|
3243
3353
|
/**
|
|
3354
|
+
* Rejects a negative `limit` or `offset`. Neither is a page size or a position, and the
|
|
3355
|
+
* dialects disagree on what one means: SQLite reads `LIMIT -1` as "no limit" — which would walk
|
|
3356
|
+
* straight past `maxLimit` — while PostgreSQL and MySQL fail with a raw database error. Applied
|
|
3357
|
+
* whether or not the table has a limit policy.
|
|
3358
|
+
*/
|
|
3359
|
+
const assertPagingNotNegative = (paging, errorCtx) => {
|
|
3360
|
+
for (const name of ["limit", "offset"]) {
|
|
3361
|
+
const value = paging[name];
|
|
3362
|
+
if (value != null && value < 0) throw drizzleError(`'${name}' must not be negative, got ${value}.`, {
|
|
3363
|
+
code: "DRIZZLE_INVALID_INPUT_VALUE",
|
|
3364
|
+
...errorCtx
|
|
3365
|
+
});
|
|
3366
|
+
}
|
|
3367
|
+
};
|
|
3368
|
+
/**
|
|
3244
3369
|
* The `limit` a query actually runs with, given the policy for the table it reads.
|
|
3245
3370
|
*
|
|
3246
3371
|
* A request that passes no `limit` falls back to `defaultLimit`, or to `maxLimit` when only
|
|
@@ -3252,6 +3377,7 @@ const withDefaultOrderBy = (args, tableName, defaults) => {
|
|
|
3252
3377
|
* fail a client's query over.
|
|
3253
3378
|
*/
|
|
3254
3379
|
const applyLimitPolicy = (limit, policy, errorCtx) => {
|
|
3380
|
+
assertPagingNotNegative({ limit }, errorCtx);
|
|
3255
3381
|
if (!policy) return limit ?? void 0;
|
|
3256
3382
|
const { defaultLimit, maxLimit, clampToMax } = policy;
|
|
3257
3383
|
if (limit == null) {
|
|
@@ -3769,6 +3895,11 @@ const extractRelationsParams = (relationMap, tables, tableName, info, typeName,
|
|
|
3769
3895
|
thisRecord.columns = columns;
|
|
3770
3896
|
const relationArgs = (0, drizzle_orm.is)(relEntry.relation, drizzle_orm.One) ? field.args : withDefaultOrderBy(field.args ?? {}, targetTableName, defaultOrderBy);
|
|
3771
3897
|
const offset = relationArgs?.offset ?? void 0;
|
|
3898
|
+
assertPagingNotNegative({ offset }, {
|
|
3899
|
+
table: targetTableName,
|
|
3900
|
+
operation: "relation",
|
|
3901
|
+
relation: relName
|
|
3902
|
+
});
|
|
3772
3903
|
const limit = (0, drizzle_orm.is)(relEntry.relation, drizzle_orm.One) ? relationArgs?.limit ?? void 0 : applyLimitPolicy(relationArgs?.limit, limits?.(targetTableName), {
|
|
3773
3904
|
table: targetTableName,
|
|
3774
3905
|
operation: "relation",
|
|
@@ -3812,6 +3943,10 @@ const pruneNonEagerRelations = (relationMap, shouldEagerLoad) => {
|
|
|
3812
3943
|
const runRelationalSelect = async (opts) => {
|
|
3813
3944
|
const { queryBase, tables, tableName, table, relationMap, typeName, typeNameMapper, parsedInfo, offset, orderBy, where, single, filterCtx, pkNames, after, scope, deleted } = opts;
|
|
3814
3945
|
const distinct = opts.distinct?.length ? opts.distinct : void 0;
|
|
3946
|
+
assertPagingNotNegative({
|
|
3947
|
+
limit: opts.limit,
|
|
3948
|
+
offset
|
|
3949
|
+
});
|
|
3815
3950
|
const cursorSelected = !single && isCursorFieldSelected(parsedInfo.fieldsByTypeName[typeName], table);
|
|
3816
3951
|
let cursorEntries;
|
|
3817
3952
|
if (!single && (after != null || cursorSelected)) {
|
|
@@ -3863,7 +3998,11 @@ const runRelationalSelect = async (opts) => {
|
|
|
3863
3998
|
const result = await queryBase.findFirst(omitUndefinedKeys(params));
|
|
3864
3999
|
return result ? remapToGraphQLSingleOutput(result, tableName, table, relationMap) : void 0;
|
|
3865
4000
|
}
|
|
3866
|
-
if (cursorEntries && cursorSelected)
|
|
4001
|
+
if (cursorEntries && cursorSelected) {
|
|
4002
|
+
for (const [column] of cursorEntries) params.columns[column] = true;
|
|
4003
|
+
const preciseProps = Object.keys(cursorPreciseExtras(table, cursorEntries));
|
|
4004
|
+
if (preciseProps.length) params.extras = Object.fromEntries(preciseProps.map((prop) => [prop, (aliasedTable) => cursorPreciseExtras(aliasedTable, cursorEntries)[prop]]));
|
|
4005
|
+
}
|
|
3867
4006
|
params.limit = distinctKeys ? void 0 : opts.limit;
|
|
3868
4007
|
const result = await queryBase.findMany(omitUndefinedKeys(params));
|
|
3869
4008
|
if (cursorEntries && cursorSelected) attachRowCursors(result, cursorEntries);
|
|
@@ -4615,6 +4754,19 @@ const keysFromWhere = (where, primaryKey) => {
|
|
|
4615
4754
|
return none;
|
|
4616
4755
|
};
|
|
4617
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
|
+
/**
|
|
4618
4770
|
* The rows a generated field's arguments are about, as primary-key values.
|
|
4619
4771
|
*
|
|
4620
4772
|
* The row identity of a mutation lives in a different argument for every operation —
|
|
@@ -4650,7 +4802,7 @@ const identifyRows = (field, args) => {
|
|
|
4650
4802
|
table,
|
|
4651
4803
|
primaryKey,
|
|
4652
4804
|
rows: rows.filter((row) => !!row),
|
|
4653
|
-
complete: entries.length > 0 && rows.every(Boolean)
|
|
4805
|
+
complete: entries.length > 0 && rows.every(Boolean) && (meta.operation !== "upsert" || conflictsOnKeyAlone(args?.["onConflict"], primaryKey))
|
|
4654
4806
|
};
|
|
4655
4807
|
}
|
|
4656
4808
|
if (targetArg === "updates") {
|
|
@@ -4767,7 +4919,10 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
4767
4919
|
const column = targetColumns[columnName];
|
|
4768
4920
|
if (column) columns[columnName] = column;
|
|
4769
4921
|
}
|
|
4770
|
-
return
|
|
4922
|
+
return cursorEntries ? {
|
|
4923
|
+
...columns,
|
|
4924
|
+
...cursorPreciseSelection(targetTable, cursorEntries)
|
|
4925
|
+
} : columns;
|
|
4771
4926
|
};
|
|
4772
4927
|
const resolve = async (parent, args, context, info) => {
|
|
4773
4928
|
if (parent[relationName] !== void 0) return parent[relationName];
|
|
@@ -4777,6 +4932,7 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
4777
4932
|
const { orderByArg, limit, distinct, cursorEntries, cursorValues, columns, loaderKey } = getOrCreateRequestValue(context, info?.fieldNodes?.[0], `relation:${tableName}::${relationName}`, () => {
|
|
4778
4933
|
const orderByArg = isOne ? args?.orderBy : withDefaultOrderBy(args ?? {}, targetTableName, policies?.defaultOrderBy).orderBy;
|
|
4779
4934
|
const limit = applyLimitPolicy(requestedLimit, limitPolicy, errorCtx);
|
|
4935
|
+
assertPagingNotNegative({ offset }, errorCtx);
|
|
4780
4936
|
const distinct = (args ?? {}).distinct?.length ? (args ?? {}).distinct : void 0;
|
|
4781
4937
|
const cursorSelected = !isOne && !!info && selectsCursorField(info, targetTable);
|
|
4782
4938
|
let cursorEntries;
|
|
@@ -7123,7 +7279,7 @@ const createSchemaBuilder = (adapter) => {
|
|
|
7123
7279
|
uniqueKeysOf: (tableName) => uniqueKeys[tableName]
|
|
7124
7280
|
};
|
|
7125
7281
|
adapter.preflight?.(db, options);
|
|
7126
|
-
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;
|
|
7127
7283
|
if (nestedPlans) {
|
|
7128
7284
|
const omitted = gateNestedWritePlans(nestedPlans, featureOf, (tableName) => excludedTables.has(tableName));
|
|
7129
7285
|
if (omitted.length) {
|
|
@@ -7411,20 +7567,33 @@ const generateUpsert = (db, tableName, table, baseType, onConflictType, fieldNam
|
|
|
7411
7567
|
}
|
|
7412
7568
|
},
|
|
7413
7569
|
run: async ({ executor, args, context, before, after }) => {
|
|
7414
|
-
const
|
|
7570
|
+
const contextColumns = policies?.contextValues?.(tableName);
|
|
7571
|
+
const input = applyContextValuesAll(single ? [remapFromGraphQLSingleInput(args.values, table)] : remapFromGraphQLArrayInput(args.values, table), contextColumns, context);
|
|
7415
7572
|
if (!input.length) throw drizzleError("No values were provided!", { code: "DRIZZLE_NO_VALUES" });
|
|
7416
7573
|
await before();
|
|
7417
|
-
const
|
|
7574
|
+
const batches = resolveConflictPlan({
|
|
7418
7575
|
table,
|
|
7419
7576
|
values: input,
|
|
7420
7577
|
onConflict: args.onConflict,
|
|
7421
7578
|
pkNames,
|
|
7422
7579
|
uniqueSets: [],
|
|
7423
7580
|
excludedRef: mysqlValuesColumnRef,
|
|
7424
|
-
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);
|
|
7425
7596
|
});
|
|
7426
|
-
if (plan.action === "NOTHING") await executor.insert(table).ignore().values(input);
|
|
7427
|
-
else await executor.insert(table).values(input).onDuplicateKeyUpdate({ set: plan.set });
|
|
7428
7597
|
await after();
|
|
7429
7598
|
return isSuccess;
|
|
7430
7599
|
}
|
|
@@ -7870,7 +8039,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7870
8039
|
});
|
|
7871
8040
|
const returning = withHookReturning(nestedEntries ? nested.withJoinColumns(tableName, mergedOps(nestedEntries), { ...columns }, table) : columns);
|
|
7872
8041
|
const runUpsert = async (target, values) => {
|
|
7873
|
-
const
|
|
8042
|
+
const batches = resolveConflictPlan({
|
|
7874
8043
|
table,
|
|
7875
8044
|
values,
|
|
7876
8045
|
onConflict: args.onConflict,
|
|
@@ -7878,16 +8047,29 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7878
8047
|
uniqueSets,
|
|
7879
8048
|
excludedRef: excludedColumnRef,
|
|
7880
8049
|
withTarget: true,
|
|
8050
|
+
serverOwned: Object.keys(contextColumns ?? {}),
|
|
7881
8051
|
buildWhere: (where) => extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName))
|
|
7882
8052
|
});
|
|
7883
|
-
|
|
7884
|
-
|
|
7885
|
-
|
|
7886
|
-
|
|
7887
|
-
|
|
7888
|
-
|
|
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;
|
|
7889
8072
|
});
|
|
7890
|
-
return await query;
|
|
7891
8073
|
};
|
|
7892
8074
|
const written = nestedEntries ? await writeWithNestedOps({
|
|
7893
8075
|
executor,
|
|
@@ -8067,7 +8249,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
8067
8249
|
*/
|
|
8068
8250
|
const createSchemaDataGenerator = (adapter) => {
|
|
8069
8251
|
const primaryKeyPropNames = adapter.primaryKeyPropNames;
|
|
8070
|
-
const
|
|
8252
|
+
const uniqueTargets = (table) => getUniqueTargets(table, adapter.getTableConfig);
|
|
8071
8253
|
const { generateInsert, generateUpsert, generateUpdate, generateDelete } = buildWriteResolvers(primaryKeyPropNames);
|
|
8072
8254
|
const { prepareBuild, forEachTable, finalizeBuild } = createSchemaBuilder(adapter);
|
|
8073
8255
|
return (db, schema, relations, options) => {
|
|
@@ -8108,7 +8290,7 @@ const createSchemaDataGenerator = (adapter) => {
|
|
|
8108
8290
|
single: true,
|
|
8109
8291
|
conflictDoNothing
|
|
8110
8292
|
}) : void 0;
|
|
8111
|
-
const uniqueSets = tableFeatures.upsert ?
|
|
8293
|
+
const uniqueSets = tableFeatures.upsert ? uniqueTargets(schema[tableName]) : [];
|
|
8112
8294
|
const onConflictInput = tableFeatures.upsert ? generateOnConflictInput({
|
|
8113
8295
|
table: schema[tableName],
|
|
8114
8296
|
tableName,
|