@vantreeseba/drizzle-graphql 15.1.1 → 15.1.3
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 +70 -7
- package/dist/index.cjs +334 -96
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +68 -7
- package/dist/index.d.ts +68 -7
- package/dist/index.js +335 -97
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -2007,6 +2007,13 @@ const extractUniqueKeyFilter = (table, tableName, fieldName, members, value) =>
|
|
|
2007
2007
|
*/
|
|
2008
2008
|
const narrowedCtxCache = /* @__PURE__ */ new WeakMap();
|
|
2009
2009
|
/**
|
|
2010
|
+
* One alias counter per build, shared by every table's narrowed context. A statement can
|
|
2011
|
+
* combine subqueries compiled from different contexts — a relation filter on one table whose
|
|
2012
|
+
* target's scope is a filter object reaching through another relation, say — and nested
|
|
2013
|
+
* subqueries that drew the same alias from two separate counters would shadow each other.
|
|
2014
|
+
*/
|
|
2015
|
+
const aliasCounters = /* @__PURE__ */ new WeakMap();
|
|
2016
|
+
/**
|
|
2010
2017
|
* Narrows the build-scoped relation filter context to the table a resolver is filtering.
|
|
2011
2018
|
*
|
|
2012
2019
|
* Cached rather than rebuilt: the base carries the build's whole `tables` and `relationMap`,
|
|
@@ -2020,7 +2027,7 @@ const narrowedCtxCache = /* @__PURE__ */ new WeakMap();
|
|
|
2020
2027
|
* aliases only ever have to be unique *within* one statement, which a number that never
|
|
2021
2028
|
* repeats gives more surely than one that restarts.
|
|
2022
2029
|
*/
|
|
2023
|
-
const relationFilterCtx = (base, tableKey) => {
|
|
2030
|
+
const relationFilterCtx = (base, tableKey, scope) => {
|
|
2024
2031
|
if (!base) return;
|
|
2025
2032
|
let byTable = narrowedCtxCache.get(base);
|
|
2026
2033
|
if (!byTable) {
|
|
@@ -2029,13 +2036,33 @@ const relationFilterCtx = (base, tableKey) => {
|
|
|
2029
2036
|
}
|
|
2030
2037
|
let ctx = byTable.get(tableKey);
|
|
2031
2038
|
if (!ctx) {
|
|
2039
|
+
let aliases = aliasCounters.get(base);
|
|
2040
|
+
if (!aliases) {
|
|
2041
|
+
aliases = { n: 0 };
|
|
2042
|
+
aliasCounters.set(base, aliases);
|
|
2043
|
+
}
|
|
2032
2044
|
ctx = {
|
|
2033
2045
|
...base,
|
|
2034
|
-
tableKey
|
|
2046
|
+
tableKey,
|
|
2047
|
+
aliases
|
|
2035
2048
|
};
|
|
2036
2049
|
byTable.set(tableKey, ctx);
|
|
2037
2050
|
}
|
|
2038
|
-
return
|
|
2051
|
+
return scope ? {
|
|
2052
|
+
...ctx,
|
|
2053
|
+
scope
|
|
2054
|
+
} : ctx;
|
|
2055
|
+
};
|
|
2056
|
+
/**
|
|
2057
|
+
* The row-policy predicate a subquery over a relation's target applies: the target's scope
|
|
2058
|
+
* and soft-delete condition, in the `deleted` mode the relation field defaults to — so a
|
|
2059
|
+
* relation filter or a relation ordering sees exactly the rows the relation field returns.
|
|
2060
|
+
*/
|
|
2061
|
+
const relationTargetPolicy = (ctx, relation, targetTableName, aliasedTarget) => {
|
|
2062
|
+
const scope = ctx.scope;
|
|
2063
|
+
if (!scope) return;
|
|
2064
|
+
const mode = scope.relationDefault(targetTableName, (0, drizzle_orm.is)(relation, drizzle_orm.One) && relation.optional === false);
|
|
2065
|
+
return scope.has(targetTableName, mode) ? scope.on(targetTableName, aliasedTarget, mode) : void 0;
|
|
2039
2066
|
};
|
|
2040
2067
|
/**
|
|
2041
2068
|
* Correlates the parent row with the aliased target table using the relation's own join
|
|
@@ -2126,8 +2153,11 @@ const buildRelationExists = (parentTable, relationName, relEntry, innerFilters,
|
|
|
2126
2153
|
const throughTable = relation.throughTable;
|
|
2127
2154
|
let fromClause;
|
|
2128
2155
|
let joinCondition;
|
|
2156
|
+
let junctionPolicy;
|
|
2129
2157
|
if (throughTable) {
|
|
2130
2158
|
const aliasedThrough = (0, drizzle_orm.aliasedTable)(throughTable, `dgql_rel_${aliases.n++}`);
|
|
2159
|
+
const throughTableName = ctx.scope ? Object.keys(ctx.tables).find((name) => ctx.tables[name] === throughTable) : void 0;
|
|
2160
|
+
if (throughTableName && ctx.scope.has(throughTableName)) junctionPolicy = ctx.scope.on(throughTableName, aliasedThrough);
|
|
2131
2161
|
const { correlation, junctionJoin } = buildThroughJoinConditions(parentTable, relation, aliasedThrough, aliasedTarget, relationName);
|
|
2132
2162
|
fromClause = drizzle_orm.sql`${(0, drizzle_orm.getTableAsAliasSQL)(aliasedTarget)} inner join ${(0, drizzle_orm.getTableAsAliasSQL)(aliasedThrough)} on ${junctionJoin}`;
|
|
2133
2163
|
joinCondition = correlation;
|
|
@@ -2137,6 +2167,7 @@ const buildRelationExists = (parentTable, relationName, relEntry, innerFilters,
|
|
|
2137
2167
|
}
|
|
2138
2168
|
const ownWhere = relationOwnWhere(relation);
|
|
2139
2169
|
const relationWhere = ownWhere ? (0, drizzle_orm.relationsFilterToSQL)(isRelationFilterReversed(relation) ? parentTable : aliasedTarget, ownWhere) : void 0;
|
|
2170
|
+
const policy = (0, drizzle_orm.and)(relationTargetPolicy(ctx, relation, targetTableName, aliasedTarget), junctionPolicy);
|
|
2140
2171
|
const inner = innerFilters ? extractFilters(aliasedTarget, targetTableName, innerFilters, {
|
|
2141
2172
|
...ctx,
|
|
2142
2173
|
tableKey: targetTableName,
|
|
@@ -2144,9 +2175,9 @@ const buildRelationExists = (parentTable, relationName, relEntry, innerFilters,
|
|
|
2144
2175
|
}) : void 0;
|
|
2145
2176
|
if (mode === "every") {
|
|
2146
2177
|
if (!inner) return;
|
|
2147
|
-
return drizzle_orm.sql`not exists (select 1 from ${fromClause} where ${(0, drizzle_orm.and)(joinCondition, relationWhere, (0, drizzle_orm.not)(inner))})`;
|
|
2178
|
+
return drizzle_orm.sql`not exists (select 1 from ${fromClause} where ${(0, drizzle_orm.and)(joinCondition, relationWhere, policy, (0, drizzle_orm.not)(inner))})`;
|
|
2148
2179
|
}
|
|
2149
|
-
const condition = (0, drizzle_orm.and)(joinCondition, relationWhere, inner);
|
|
2180
|
+
const condition = (0, drizzle_orm.and)(joinCondition, relationWhere, policy, inner);
|
|
2150
2181
|
return mode === "none" ? drizzle_orm.sql`not exists (select 1 from ${fromClause} where ${condition})` : drizzle_orm.sql`exists (select 1 from ${fromClause} where ${condition})`;
|
|
2151
2182
|
};
|
|
2152
2183
|
/**
|
|
@@ -2418,8 +2449,8 @@ const columnEnumCache = /* @__PURE__ */ new WeakMap();
|
|
|
2418
2449
|
* input field the enum would have typed, rather than emitting an empty enum, which is
|
|
2419
2450
|
* invalid GraphQL.
|
|
2420
2451
|
*/
|
|
2421
|
-
const generateColumnEnum = (table, enumName, description, predicate = () => true) => {
|
|
2422
|
-
const cacheKey = `${enumName}\u0000${excludedColumnsKey(table)}`;
|
|
2452
|
+
const generateColumnEnum = (table, enumName, description, predicate = () => true, variant = "") => {
|
|
2453
|
+
const cacheKey = `${enumName}\u0000${excludedColumnsKey(table)}\u0000${variant}`;
|
|
2423
2454
|
let tableCache = columnEnumCache.get(table);
|
|
2424
2455
|
const cached = tableCache?.get(cacheKey);
|
|
2425
2456
|
if (cached) return cached;
|
|
@@ -2479,6 +2510,9 @@ const conflictActionEnumType = (typeName) => sharedType(typeName, {
|
|
|
2479
2510
|
* `ON DUPLICATE KEY UPDATE` fires on any unique key and takes no predicate, so offering
|
|
2480
2511
|
* either there would mean silently ignoring it.
|
|
2481
2512
|
*
|
|
2513
|
+
* `update` never offers a `contextValues` column: the server owns it, and the conflict
|
|
2514
|
+
* branch is an update, which must not hand the row to another owner.
|
|
2515
|
+
*
|
|
2482
2516
|
* Returns `undefined` when the table has nothing to conflict on (`withTarget` dialects
|
|
2483
2517
|
* only) — the caller then generates no upsert mutations for that table at all, rather than
|
|
2484
2518
|
* an operation whose every call is a database error.
|
|
@@ -2486,12 +2520,13 @@ const conflictActionEnumType = (typeName) => sharedType(typeName, {
|
|
|
2486
2520
|
const generateOnConflictInput = (params) => {
|
|
2487
2521
|
const { table, tableName, typeName, uniqueSets, tableFilters, withTarget, cacheCtx } = params;
|
|
2488
2522
|
const resolveName = cacheCtx.typeName;
|
|
2523
|
+
const serverOwned = Object.keys(cacheCtx.contextValuesOf?.(tableName) ?? {}).sort();
|
|
2489
2524
|
const updateEnum = generateColumnEnum(table, resolveName({
|
|
2490
2525
|
kind: "columnEnum",
|
|
2491
2526
|
defaultName: `${typeName}UpdateColumn`,
|
|
2492
2527
|
table: tableName,
|
|
2493
2528
|
operation: "conflictUpdate"
|
|
2494
|
-
}), `Columns of ${typeName} that an upsert can overwrite
|
|
2529
|
+
}), `Columns of ${typeName} that an upsert can overwrite`, (_column, columnName) => !serverOwned.includes(columnName), serverOwned.join(","));
|
|
2495
2530
|
if (!updateEnum) return;
|
|
2496
2531
|
const fields = {
|
|
2497
2532
|
action: {
|
|
@@ -2501,11 +2536,11 @@ const generateOnConflictInput = (params) => {
|
|
|
2501
2536
|
},
|
|
2502
2537
|
update: {
|
|
2503
2538
|
type: new graphql.GraphQLList(new graphql.GraphQLNonNull(updateEnum)),
|
|
2504
|
-
description:
|
|
2539
|
+
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
2540
|
}
|
|
2506
2541
|
};
|
|
2507
2542
|
if (withTarget) {
|
|
2508
|
-
const uniqueColumns = new Set(uniqueSets.
|
|
2543
|
+
const uniqueColumns = new Set(uniqueSets.flatMap((set) => set.columns));
|
|
2509
2544
|
const targetEnum = generateColumnEnum(table, resolveName({
|
|
2510
2545
|
kind: "columnEnum",
|
|
2511
2546
|
defaultName: `${typeName}ConflictTarget`,
|
|
@@ -2533,55 +2568,88 @@ const generateOnConflictInput = (params) => {
|
|
|
2533
2568
|
});
|
|
2534
2569
|
};
|
|
2535
2570
|
/**
|
|
2536
|
-
* Turns the request's `onConflict` argument and the rows it is inserting into the
|
|
2537
|
-
* dialect should
|
|
2571
|
+
* Turns the request's `onConflict` argument and the rows it is inserting into the
|
|
2572
|
+
* statements a dialect should run — one per run of consecutive rows that write the same
|
|
2573
|
+
* columns on conflict.
|
|
2574
|
+
*
|
|
2575
|
+
* The split is what keeps a partial row partial. A multi-row insert fills a key one row
|
|
2576
|
+
* left out with DEFAULT, and `excluded.<column>` would carry that default onto the existing
|
|
2577
|
+
* row, so rows that supply different columns cannot share a `SET` list. Splitting into
|
|
2578
|
+
* *consecutive* runs, rather than grouping, keeps the rows a caller gets back in the order
|
|
2579
|
+
* it sent them. A batch whose rows agree — the usual case — is still one statement.
|
|
2538
2580
|
*
|
|
2539
2581
|
* `excludedRef` names the row that failed to insert in the dialect's own terms
|
|
2540
2582
|
* (`excluded.col` on PostgreSQL and SQLite, `values(col)` on MySQL), which is what makes a
|
|
2541
2583
|
* batch upsert update each row with its own values instead of the last row's.
|
|
2542
2584
|
*
|
|
2585
|
+
* Never written on conflict, even when supplied: the conflict target (it already matches);
|
|
2586
|
+
* the primary key on a dialect with no target (MySQL's conflict can be on another unique
|
|
2587
|
+
* key, and copying a generated id over the existing row's would move it); and `serverOwned`
|
|
2588
|
+
* — the `contextValues` columns, which an update never reassigns.
|
|
2589
|
+
*
|
|
2543
2590
|
* An UPDATE with nothing left to write degrades to NOTHING: `DO UPDATE SET` with an empty
|
|
2544
2591
|
* body is not valid SQL, and doing nothing is what the request asked for anyway.
|
|
2545
2592
|
*/
|
|
2546
2593
|
const resolveConflictPlan = (params) => {
|
|
2547
2594
|
const { table, values, onConflict, pkNames, uniqueSets, excludedRef, withTarget, buildWhere } = params;
|
|
2548
2595
|
const columns = (0, drizzle_orm.getColumns)(table);
|
|
2596
|
+
const serverOwned = new Set(params.serverOwned ?? []);
|
|
2549
2597
|
let target;
|
|
2598
|
+
let targetWhere;
|
|
2550
2599
|
if (withTarget) {
|
|
2551
2600
|
const targetNames = onConflict?.target?.length ? onConflict.target : [...pkNames];
|
|
2552
2601
|
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
2602
|
const requested = [...targetNames].sort().join(",");
|
|
2554
|
-
|
|
2603
|
+
const matched = uniqueSets.find((set) => [...set.columns].sort().join(",") === requested);
|
|
2604
|
+
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
2605
|
target = targetNames.map((name) => columns[name]);
|
|
2606
|
+
targetWhere = matched.where;
|
|
2556
2607
|
}
|
|
2557
|
-
|
|
2608
|
+
const nothing = {
|
|
2558
2609
|
action: "NOTHING",
|
|
2559
2610
|
target,
|
|
2611
|
+
targetWhere,
|
|
2560
2612
|
set: {},
|
|
2561
2613
|
setWhere: void 0
|
|
2562
2614
|
};
|
|
2615
|
+
if ((onConflict?.action ?? "UPDATE") === "NOTHING") return [{
|
|
2616
|
+
values,
|
|
2617
|
+
plan: nothing
|
|
2618
|
+
}];
|
|
2563
2619
|
const supplied = new Set(values.flatMap((row) => Object.keys(row)));
|
|
2564
|
-
const
|
|
2565
|
-
let
|
|
2620
|
+
const neverWritten = new Set(withTarget && onConflict?.target?.length ? onConflict.target : pkNames);
|
|
2621
|
+
let candidates;
|
|
2566
2622
|
if (onConflict?.update?.length) {
|
|
2623
|
+
const owned = onConflict.update.filter((name) => serverOwned.has(name));
|
|
2624
|
+
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
2625
|
const unsupplied = onConflict.update.filter((name) => !supplied.has(name));
|
|
2568
2626
|
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)]));
|
|
2627
|
+
candidates = onConflict.update;
|
|
2628
|
+
} else candidates = [...supplied].filter((name) => !neverWritten.has(name) && !serverOwned.has(name));
|
|
2578
2629
|
const setWhere = onConflict?.where && buildWhere ? buildWhere(onConflict.where) : void 0;
|
|
2579
|
-
|
|
2630
|
+
const planFor = (updateNames) => updateNames.length ? {
|
|
2580
2631
|
action: "UPDATE",
|
|
2581
2632
|
target,
|
|
2582
|
-
|
|
2633
|
+
targetWhere,
|
|
2634
|
+
set: Object.fromEntries(updateNames.map((name) => [name, excludedRef(columns[name].name)])),
|
|
2583
2635
|
setWhere
|
|
2584
|
-
};
|
|
2636
|
+
} : nothing;
|
|
2637
|
+
const batches = [];
|
|
2638
|
+
for (const row of values) {
|
|
2639
|
+
const updateNames = candidates.filter((name) => name in row);
|
|
2640
|
+
const key = updateNames.join(",");
|
|
2641
|
+
const last = batches[batches.length - 1];
|
|
2642
|
+
if (last?.key === key) last.values.push(row);
|
|
2643
|
+
else batches.push({
|
|
2644
|
+
key,
|
|
2645
|
+
values: [row],
|
|
2646
|
+
plan: planFor(updateNames)
|
|
2647
|
+
});
|
|
2648
|
+
}
|
|
2649
|
+
return batches.map(({ values: rows, plan }) => ({
|
|
2650
|
+
values: rows,
|
|
2651
|
+
plan
|
|
2652
|
+
}));
|
|
2585
2653
|
};
|
|
2586
2654
|
/** `excluded.<column>` — PostgreSQL and SQLite name the rejected row this way. */
|
|
2587
2655
|
const excludedColumnRef = (columnName) => drizzle_orm.sql`excluded.${drizzle_orm.sql.identifier(columnName)}`;
|
|
@@ -2635,12 +2703,14 @@ const extendRelationOrderChain = (parentTable, relationName, relEntry, ctx, chai
|
|
|
2635
2703
|
const joinCondition = buildRelationJoinCondition(parentTable, relation, aliasedTarget, relationName);
|
|
2636
2704
|
const ownWhere = relationOwnWhere(relation);
|
|
2637
2705
|
const relationWhere = ownWhere ? (0, drizzle_orm.relationsFilterToSQL)(isRelationFilterReversed(relation) ? parentTable : aliasedTarget, ownWhere) : void 0;
|
|
2706
|
+
const policy = relationTargetPolicy(ctx, relation, relEntry.targetTableName, aliasedTarget);
|
|
2638
2707
|
return {
|
|
2639
2708
|
fromParts: [...chain?.fromParts ?? [], (0, drizzle_orm.getTableAsAliasSQL)(aliasedTarget)],
|
|
2640
2709
|
conditions: [
|
|
2641
2710
|
...chain?.conditions ?? [],
|
|
2642
2711
|
...joinCondition ? [joinCondition] : [],
|
|
2643
|
-
...relationWhere ? [relationWhere] : []
|
|
2712
|
+
...relationWhere ? [relationWhere] : [],
|
|
2713
|
+
...policy ? [policy] : []
|
|
2644
2714
|
],
|
|
2645
2715
|
table: aliasedTarget
|
|
2646
2716
|
};
|
|
@@ -3025,9 +3095,9 @@ const primaryKeyOrderExprs = (table, pkNames) => {
|
|
|
3025
3095
|
return pkNames.map((n) => cols[n]).filter(Boolean).map((col) => (0, drizzle_orm.asc)(col));
|
|
3026
3096
|
};
|
|
3027
3097
|
/**
|
|
3028
|
-
* Every set of columns that
|
|
3029
|
-
*
|
|
3030
|
-
*
|
|
3098
|
+
* Every set of columns that is unique on `table`: the primary key first, then each unique
|
|
3099
|
+
* constraint and unique index, then each column declared `.unique()` inline. Sets are
|
|
3100
|
+
* property names (what GraphQL inputs use), not database column names.
|
|
3031
3101
|
*
|
|
3032
3102
|
* `getTableConfig` is the dialect's own — the three dialects expose the same
|
|
3033
3103
|
* `{ primaryKeys, uniqueConstraints, indexes }` shape but from different modules, so the
|
|
@@ -3036,9 +3106,13 @@ const primaryKeyOrderExprs = (table, pkNames) => {
|
|
|
3036
3106
|
* Index entries whose columns are SQL expressions rather than plain columns are skipped:
|
|
3037
3107
|
* an expression index is a valid conflict target in the database but cannot be named by a
|
|
3038
3108
|
* column enum. Deduplicated, order-insensitive — a column that is both the primary key and
|
|
3039
|
-
* a unique constraint yields one set
|
|
3109
|
+
* a unique constraint yields one set, and a set that is unique both outright and under a
|
|
3110
|
+
* partial index keeps the outright one.
|
|
3111
|
+
*
|
|
3112
|
+
* A partial unique index carries its predicate as `where`: it is a conflict target only
|
|
3113
|
+
* together with that predicate, and it does not make the set unique across the whole table.
|
|
3040
3114
|
*/
|
|
3041
|
-
const
|
|
3115
|
+
const getUniqueTargets = (table, getTableConfig) => {
|
|
3042
3116
|
const cols = visibleColumns(table);
|
|
3043
3117
|
const propNameByColumnName = new Map(Object.entries(cols).map(([propName, col]) => [col.name, propName]));
|
|
3044
3118
|
const toPropNames = (columnNames) => {
|
|
@@ -3050,25 +3124,40 @@ const getUniqueColumnSets = (table, getTableConfig) => {
|
|
|
3050
3124
|
}
|
|
3051
3125
|
return propNames.length ? propNames : void 0;
|
|
3052
3126
|
};
|
|
3127
|
+
const whole = (columns) => columns ? { columns } : void 0;
|
|
3053
3128
|
const config = getTableConfig(table);
|
|
3054
3129
|
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
|
-
|
|
3130
|
+
whole(Object.entries(cols).filter(([, col]) => col.primary).map(([propName]) => propName)),
|
|
3131
|
+
...config.primaryKeys.map((pk) => whole(toPropNames(pk.columns.map((c) => c.name)))),
|
|
3132
|
+
...(config.uniqueConstraints ?? []).map((uc) => whole(toPropNames(uc.columns.map((c) => c.name)))),
|
|
3133
|
+
...(config.indexes ?? []).filter((index) => index.config.unique).map((index) => {
|
|
3134
|
+
const columns = toPropNames(index.config.columns.map((c) => c?.name));
|
|
3135
|
+
return columns ? {
|
|
3136
|
+
columns,
|
|
3137
|
+
...index.config.where ? { where: index.config.where } : {}
|
|
3138
|
+
} : void 0;
|
|
3139
|
+
}),
|
|
3140
|
+
...Object.entries(cols).filter(([, col]) => col.isUnique).map(([propName]) => ({ columns: [propName] }))
|
|
3060
3141
|
];
|
|
3061
|
-
const
|
|
3062
|
-
const
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
const
|
|
3066
|
-
if (seen.
|
|
3067
|
-
seen.
|
|
3068
|
-
sets.push(set);
|
|
3142
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
3143
|
+
for (const candidate of candidates) {
|
|
3144
|
+
if (!candidate?.columns.length) continue;
|
|
3145
|
+
const key = [...candidate.columns].sort().join(",");
|
|
3146
|
+
const seen = byKey.get(key);
|
|
3147
|
+
if (!seen) byKey.set(key, candidate);
|
|
3148
|
+
else if (seen.where && !candidate.where) byKey.set(key, { columns: seen.columns });
|
|
3069
3149
|
}
|
|
3070
|
-
return
|
|
3150
|
+
return [...byKey.values()];
|
|
3071
3151
|
};
|
|
3152
|
+
/**
|
|
3153
|
+
* The column sets that identify at most one row of `table` — {@link getUniqueTargets}
|
|
3154
|
+
* without the partial unique indexes, which only constrain the rows their predicate
|
|
3155
|
+
* matches (a soft-deleted duplicate is allowed), and so do not.
|
|
3156
|
+
*
|
|
3157
|
+
* Pass `{ partial: true }` to keep them, for callers that only need to know a set is
|
|
3158
|
+
* unique among the rows that matter.
|
|
3159
|
+
*/
|
|
3160
|
+
const getUniqueColumnSets = (table, getTableConfig, options = {}) => getUniqueTargets(table, getTableConfig).filter((target) => options.partial || !target.where).map((target) => target.columns);
|
|
3072
3161
|
//#endregion
|
|
3073
3162
|
//#region src/util/builders/common/distinct.ts
|
|
3074
3163
|
/**
|
|
@@ -3851,10 +3940,10 @@ const extractRelationsParams = (relationMap, tables, tableName, info, typeName,
|
|
|
3851
3940
|
});
|
|
3852
3941
|
const relWhere = relationArgs?.where;
|
|
3853
3942
|
const relDeleted = relationArgs?.deleted ?? scope?.relationDefault(targetTableName, (0, drizzle_orm.is)(relEntry.relation, drizzle_orm.One) && relEntry.relation.optional === false);
|
|
3854
|
-
thisRecord.where = relWhere || scope?.has(targetTableName, relDeleted) ? { RAW: (aliasedTable) => withScope(scope, targetTableName, aliasedTable, relWhere ? extractFilters(aliasedTable, relName, relWhere, relationFilterCtx(filterCtx, targetTableName)) : void 0, relDeleted) } : void 0;
|
|
3943
|
+
thisRecord.where = relWhere || scope?.has(targetTableName, relDeleted) ? { RAW: (aliasedTable) => withScope(scope, targetTableName, aliasedTable, relWhere ? extractFilters(aliasedTable, relName, relWhere, relationFilterCtx(filterCtx, targetTableName, scope)) : void 0, relDeleted) } : void 0;
|
|
3855
3944
|
const hasPagination = offset != null || limit != null;
|
|
3856
3945
|
const pkNames = targetPkNames ?? [];
|
|
3857
|
-
thisRecord.orderBy = relationArgs?.orderBy ? (aliasedTable) => extractOrderBy(aliasedTable, relationArgs.orderBy, relationFilterCtx(filterCtx, targetTableName), relWhere) : hasPagination && pkNames.length ? (aliasedTable) => primaryKeyOrderExprs(aliasedTable, pkNames) : void 0;
|
|
3946
|
+
thisRecord.orderBy = relationArgs?.orderBy ? (aliasedTable) => extractOrderBy(aliasedTable, relationArgs.orderBy, relationFilterCtx(filterCtx, targetTableName, scope), relWhere) : hasPagination && pkNames.length ? (aliasedTable) => primaryKeyOrderExprs(aliasedTable, pkNames) : void 0;
|
|
3858
3947
|
thisRecord.offset = offset;
|
|
3859
3948
|
thisRecord.limit = limit;
|
|
3860
3949
|
thisRecord.with = extractRelationsParams(relationMap, tables, targetTableName, field, relTypeName, options);
|
|
@@ -3912,7 +4001,7 @@ const runRelationalSelect = async (opts) => {
|
|
|
3912
4001
|
tableName,
|
|
3913
4002
|
distinct,
|
|
3914
4003
|
pkNames: pkNames ?? [],
|
|
3915
|
-
where: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName)) : void 0, deleted),
|
|
4004
|
+
where: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName, scope)) : void 0, deleted),
|
|
3916
4005
|
orderBy,
|
|
3917
4006
|
limit: single ? 1 : opts.limit,
|
|
3918
4007
|
offset
|
|
@@ -3927,8 +4016,8 @@ const runRelationalSelect = async (opts) => {
|
|
|
3927
4016
|
allRelations: filterCtx?.relationMap
|
|
3928
4017
|
}),
|
|
3929
4018
|
offset: distinctKeys ? void 0 : offset,
|
|
3930
|
-
orderBy: distinctKeys ? (aliasedTable) => [...orderBy ? extractOrderBy(aliasedTable, orderBy, relationFilterCtx(filterCtx, tableName), where) : [], ...primaryKeyOrderExprs(aliasedTable, pkNames)] : cursorEntries ? (aliasedTable) => cursorOrderExprs(aliasedTable, cursorEntries) : orderBy ? (aliasedTable) => extractOrderBy(aliasedTable, orderBy, relationFilterCtx(filterCtx, tableName), where) : needsDefaultOrder && pkNames?.length ? (aliasedTable) => primaryKeyOrderExprs(aliasedTable, pkNames) : void 0,
|
|
3931
|
-
where: distinctKeys ? { RAW: (aliasedTable) => primaryKeyRestriction(aliasedTable, pkNames, distinctKeys) } : where || cursorValues || scope?.has(tableName, deleted) ? { RAW: (aliasedTable) => withScope(scope, tableName, aliasedTable, (0, drizzle_orm.and)(where ? extractFilters(aliasedTable, tableName, where, relationFilterCtx(filterCtx, tableName)) : void 0, cursorValues ? buildCursorCondition(aliasedTable, cursorEntries, cursorValues, opts.nullOrdering ?? "nulls-smallest") : void 0), deleted) } : void 0,
|
|
4019
|
+
orderBy: distinctKeys ? (aliasedTable) => [...orderBy ? extractOrderBy(aliasedTable, orderBy, relationFilterCtx(filterCtx, tableName, scope), where) : [], ...primaryKeyOrderExprs(aliasedTable, pkNames)] : cursorEntries ? (aliasedTable) => cursorOrderExprs(aliasedTable, cursorEntries) : orderBy ? (aliasedTable) => extractOrderBy(aliasedTable, orderBy, relationFilterCtx(filterCtx, tableName, scope), where) : needsDefaultOrder && pkNames?.length ? (aliasedTable) => primaryKeyOrderExprs(aliasedTable, pkNames) : void 0,
|
|
4020
|
+
where: distinctKeys ? { RAW: (aliasedTable) => primaryKeyRestriction(aliasedTable, pkNames, distinctKeys) } : where || cursorValues || scope?.has(tableName, deleted) ? { RAW: (aliasedTable) => withScope(scope, tableName, aliasedTable, (0, drizzle_orm.and)(where ? extractFilters(aliasedTable, tableName, where, relationFilterCtx(filterCtx, tableName, scope)) : void 0, cursorValues ? buildCursorCondition(aliasedTable, cursorEntries, cursorValues, opts.nullOrdering ?? "nulls-smallest") : void 0), deleted) } : void 0,
|
|
3932
4021
|
with: relationMap[tableName] ? extractRelationsParams(relationMap, tables, tableName, parsedInfo, typeName, {
|
|
3933
4022
|
typeNameMapper,
|
|
3934
4023
|
filterCtx,
|
|
@@ -4510,7 +4599,7 @@ const generateWriteCount = ({ db, tableName, table, kind, setArgs, filterArgs, f
|
|
|
4510
4599
|
tableName,
|
|
4511
4600
|
table,
|
|
4512
4601
|
where,
|
|
4513
|
-
relationCtx: relationFilterCtx(filterCtx, tableName),
|
|
4602
|
+
relationCtx: relationFilterCtx(filterCtx, tableName, scope),
|
|
4514
4603
|
required: requireWhere,
|
|
4515
4604
|
deleted: hard ? "INCLUDE" : void 0
|
|
4516
4605
|
});
|
|
@@ -4698,6 +4787,19 @@ const keysFromWhere = (where, primaryKey) => {
|
|
|
4698
4787
|
return none;
|
|
4699
4788
|
};
|
|
4700
4789
|
/**
|
|
4790
|
+
* Whether an upsert's `onConflict` leaves the primary-key values in `values` naming exactly
|
|
4791
|
+
* the rows it writes. It does not when the conflict is detected on another unique key — the
|
|
4792
|
+
* row updated is whichever one holds that key, not the one the supplied primary key names —
|
|
4793
|
+
* nor when conflicting rows can be skipped: `action: NOTHING`, or a `where` guard.
|
|
4794
|
+
*/
|
|
4795
|
+
const conflictsOnKeyAlone = (onConflict, primaryKey) => {
|
|
4796
|
+
if (!onConflict || typeof onConflict !== "object") return true;
|
|
4797
|
+
const { target, action, where } = onConflict;
|
|
4798
|
+
if (action === "NOTHING" || where !== void 0 && where !== null) return false;
|
|
4799
|
+
if (!Array.isArray(target) || !target.length) return true;
|
|
4800
|
+
return [...target].sort().join(",") === [...primaryKey].sort().join(",");
|
|
4801
|
+
};
|
|
4802
|
+
/**
|
|
4701
4803
|
* The rows a generated field's arguments are about, as primary-key values.
|
|
4702
4804
|
*
|
|
4703
4805
|
* The row identity of a mutation lives in a different argument for every operation —
|
|
@@ -4733,7 +4835,7 @@ const identifyRows = (field, args) => {
|
|
|
4733
4835
|
table,
|
|
4734
4836
|
primaryKey,
|
|
4735
4837
|
rows: rows.filter((row) => !!row),
|
|
4736
|
-
complete: entries.length > 0 && rows.every(Boolean)
|
|
4838
|
+
complete: entries.length > 0 && rows.every(Boolean) && (meta.operation !== "upsert" || conflictsOnKeyAlone(args?.["onConflict"], primaryKey))
|
|
4737
4839
|
};
|
|
4738
4840
|
}
|
|
4739
4841
|
if (targetArg === "updates") {
|
|
@@ -4903,7 +5005,7 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
4903
5005
|
const executor = resolveExecutor(db, context);
|
|
4904
5006
|
const uniqueIds = [...new Set(parentIds)];
|
|
4905
5007
|
const scope = resolveScope(policies, context, filterCtx);
|
|
4906
|
-
let whereCondition = withScope(scope, targetTableName, targetTable, (0, drizzle_orm.and)((0, drizzle_orm.inArray)(groupCol, uniqueIds), ownCondition, whereArg ? extractFilters(targetTable, targetTableName, whereArg, relationFilterCtx(filterCtx, targetTableName)) : void 0, cursorValues ? buildCursorCondition(targetTable, cursorEntries, cursorValues, nullOrdering) : void 0), deleted ?? defaultDeleted);
|
|
5008
|
+
let whereCondition = withScope(scope, targetTableName, targetTable, (0, drizzle_orm.and)((0, drizzle_orm.inArray)(groupCol, uniqueIds), ownCondition, whereArg ? extractFilters(targetTable, targetTableName, whereArg, relationFilterCtx(filterCtx, targetTableName, scope)) : void 0, cursorValues ? buildCursorCondition(targetTable, cursorEntries, cursorValues, nullOrdering) : void 0), deleted ?? defaultDeleted);
|
|
4907
5009
|
if (distinct) {
|
|
4908
5010
|
const keys = await selectDistinctKeys({
|
|
4909
5011
|
db: executor,
|
|
@@ -4928,10 +5030,10 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
4928
5030
|
[PARENT_KEY]: groupCol
|
|
4929
5031
|
} : columns;
|
|
4930
5032
|
let rows;
|
|
4931
|
-
if (limit != null || offset != null) rows = await batchedPaginatedRelationQuery(executor, targetTable, foreignCol, whereCondition, orderByArg, limit ?? null, offset ?? null, targetPkNames, selection, relationFilterCtx(filterCtx, targetTableName), whereArg, junction);
|
|
5033
|
+
if (limit != null || offset != null) rows = await batchedPaginatedRelationQuery(executor, targetTable, foreignCol, whereCondition, orderByArg, limit ?? null, offset ?? null, targetPkNames, selection, relationFilterCtx(filterCtx, targetTableName, scope), whereArg, junction);
|
|
4932
5034
|
else {
|
|
4933
5035
|
let q = withJunction((selection ? executor.select(selection) : executor.select()).from(targetTable), junction, foreignCol).where(whereCondition);
|
|
4934
|
-
const orderExprs = cursorEntries ? cursorOrderExprs(targetTable, cursorEntries) : orderByArg ? extractOrderBy(targetTable, orderByArg, relationFilterCtx(filterCtx, targetTableName), whereArg) : [];
|
|
5036
|
+
const orderExprs = cursorEntries ? cursorOrderExprs(targetTable, cursorEntries) : orderByArg ? extractOrderBy(targetTable, orderByArg, relationFilterCtx(filterCtx, targetTableName, scope), whereArg) : [];
|
|
4935
5037
|
if (orderExprs.length) q = q.orderBy(...orderExprs);
|
|
4936
5038
|
rows = await q;
|
|
4937
5039
|
}
|
|
@@ -5611,6 +5713,7 @@ const resolveBuildConfig = (config, schema) => {
|
|
|
5611
5713
|
const normalized = {};
|
|
5612
5714
|
for (const [columnName, entry] of Object.entries(orderBy)) {
|
|
5613
5715
|
if (!columns[columnName]) throw new Error(`Drizzle-GraphQL Error: config.defaults.${tableName}.orderBy names '${columnName}', which is not a column of '${tableName}'.`);
|
|
5716
|
+
if (config?.exclude?.columns?.[tableName]?.includes(columnName)) throw new Error(`Drizzle-GraphQL Error: config.defaults.${tableName}.orderBy names '${columnName}', which config.exclude.columns hides. A default ordering is encoded into every row's cursor, so it can only use columns the schema exposes.`);
|
|
5614
5717
|
const direction = typeof entry === "string" ? entry : entry?.direction;
|
|
5615
5718
|
if (direction !== "asc" && direction !== "desc") throw new Error(`Drizzle-GraphQL Error: config.defaults.${tableName}.orderBy.${columnName} must be 'asc', 'desc', or { direction, priority? }.`);
|
|
5616
5719
|
const priority = typeof entry === "string" ? void 0 : entry?.priority;
|
|
@@ -5991,7 +6094,8 @@ const generateAggregate = (db, tableName, table, typeName, fieldName, filterArgs
|
|
|
5991
6094
|
const request = parseAggregateRequest(info, target);
|
|
5992
6095
|
if (!Object.keys(request.selection).length) return {};
|
|
5993
6096
|
let query = resolveExecutor(db, context).select(request.selection).from(table);
|
|
5994
|
-
const
|
|
6097
|
+
const scope = resolveScope(policies, context, filterCtx);
|
|
6098
|
+
const where = withScope(scope, tableName, table, args.where ? extractFilters(table, tableName, args.where, relationFilterCtx(filterCtx, tableName, scope)) : void 0, args.deleted);
|
|
5995
6099
|
if (where) query = query.where(where);
|
|
5996
6100
|
const rows = await query;
|
|
5997
6101
|
return assembleAggregateRow(rows[0] ?? {}, request, target);
|
|
@@ -6068,7 +6172,8 @@ const createRelationAggregateFactory = (db, tables, cacheCtx, typeNameMapper, fi
|
|
|
6068
6172
|
const loader = getOrCreateLoader(context, loaderKey, async (parentIds) => {
|
|
6069
6173
|
const executor = resolveExecutor(db, context);
|
|
6070
6174
|
const uniqueIds = [...new Set(parentIds)];
|
|
6071
|
-
const
|
|
6175
|
+
const scope = resolveScope(policies, context, filterCtx);
|
|
6176
|
+
const whereCondition = withScope(scope, targetTableName, targetTable, (0, drizzle_orm.and)((0, drizzle_orm.inArray)(groupCol, uniqueIds), ownCondition, whereArg ? extractFilters(targetTable, targetTableName, whereArg, relationFilterCtx(filterCtx, targetTableName, scope)) : void 0), deleted ?? defaultDeleted);
|
|
6072
6177
|
const selected = executor.select({
|
|
6073
6178
|
[GROUP_KEY]: groupCol,
|
|
6074
6179
|
...request.selection
|
|
@@ -6284,7 +6389,8 @@ const generateGroupBy = (db, tableName, table, typeName, fieldName, filterArgs,
|
|
|
6284
6389
|
...request.selection
|
|
6285
6390
|
};
|
|
6286
6391
|
let query = resolveExecutor(db, context).select(selection).from(table);
|
|
6287
|
-
const
|
|
6392
|
+
const scope = resolveScope(policies, context, filterCtx);
|
|
6393
|
+
const where = withScope(scope, tableName, table, args.where ? extractFilters(table, tableName, args.where, relationFilterCtx(filterCtx, tableName, scope)) : void 0, args.deleted);
|
|
6288
6394
|
if (where) query = query.where(where);
|
|
6289
6395
|
query = query.groupBy(...keyColumns.map(([, column]) => column));
|
|
6290
6396
|
const havingCondition = buildHavingCondition(args.having, columns);
|
|
@@ -6323,6 +6429,54 @@ const generateGroupBy = (db, tableName, table, typeName, fieldName, filterArgs,
|
|
|
6323
6429
|
};
|
|
6324
6430
|
};
|
|
6325
6431
|
//#endregion
|
|
6432
|
+
//#region src/util/builders/common/build-registry.ts
|
|
6433
|
+
/**
|
|
6434
|
+
* Keyed by the `extensions.drizzle` object of every object type the build generated, rather
|
|
6435
|
+
* than by the type: a schema composed from `entities.types` keeps the types, and one rebuilt
|
|
6436
|
+
* by a schema-transforming tool (`mapSchema`, stitching) keeps their extensions.
|
|
6437
|
+
*/
|
|
6438
|
+
const runtimeByTypeMeta = /* @__PURE__ */ new WeakMap();
|
|
6439
|
+
/**
|
|
6440
|
+
* What is known about the builds of one drizzle instance, for a caller with no resolve info to
|
|
6441
|
+
* look its build up by. Only a lone build is kept — with two or more there is no telling which
|
|
6442
|
+
* one a selection belongs to — so rebuilding a schema repeatedly holds on to nothing extra.
|
|
6443
|
+
*/
|
|
6444
|
+
const buildsByDb = /* @__PURE__ */ new WeakMap();
|
|
6445
|
+
/** Whether a build changes what a read returns at all: a row policy, a limit or a default ordering. */
|
|
6446
|
+
const hasPolicies = (runtime) => !!(runtime.policies?.scope || runtime.policies?.defaultOrderBy || runtime.limits);
|
|
6447
|
+
/** Records a finished build against the object types it generated and the instance it read. */
|
|
6448
|
+
const registerBuildRuntime = (runtime, types) => {
|
|
6449
|
+
for (const type of types) {
|
|
6450
|
+
const meta = drizzleExtension(type);
|
|
6451
|
+
if (meta) runtimeByTypeMeta.set(meta, runtime);
|
|
6452
|
+
}
|
|
6453
|
+
const previous = buildsByDb.get(runtime.db);
|
|
6454
|
+
buildsByDb.set(runtime.db, previous ? {
|
|
6455
|
+
count: previous.count + 1,
|
|
6456
|
+
anyPolicies: previous.anyPolicies || hasPolicies(runtime)
|
|
6457
|
+
} : {
|
|
6458
|
+
count: 1,
|
|
6459
|
+
only: runtime,
|
|
6460
|
+
anyPolicies: hasPolicies(runtime)
|
|
6461
|
+
});
|
|
6462
|
+
};
|
|
6463
|
+
/** The build that generated `type`, when one did. */
|
|
6464
|
+
const runtimeForType = (type) => {
|
|
6465
|
+
const meta = type && typeof type === "object" ? drizzleExtension(type) : void 0;
|
|
6466
|
+
return meta ? runtimeByTypeMeta.get(meta) : void 0;
|
|
6467
|
+
};
|
|
6468
|
+
/**
|
|
6469
|
+
* The build of `db` to use when no generated type identifies one: the only build, or none
|
|
6470
|
+
* when no build applies any policy. Throws when several builds exist and one of them would
|
|
6471
|
+
* change the read, since picking either could apply the wrong policies — or none.
|
|
6472
|
+
*/
|
|
6473
|
+
const runtimeForDb = (db, helper) => {
|
|
6474
|
+
const builds = buildsByDb.get(db);
|
|
6475
|
+
if (!builds) return;
|
|
6476
|
+
if (builds.count === 1) return builds.only;
|
|
6477
|
+
if (builds.anyPolicies) throw new Error(`Drizzle-GraphQL Error: ${helper} cannot tell which of the ${builds.count} schemas built over this database the selection belongs to, and at least one applies row policies (scope, softDelete, limits or defaults). Pass the resolver's \`info\` rather than a parsed selection tree, from a field whose type the build generated.`);
|
|
6478
|
+
};
|
|
6479
|
+
//#endregion
|
|
6326
6480
|
//#region src/util/builders/nested-writes.ts
|
|
6327
6481
|
const ALL_OPS = [
|
|
6328
6482
|
"create",
|
|
@@ -6815,7 +6969,7 @@ const createNestedWriteRuntime = (params) => {
|
|
|
6815
6969
|
* nothing is rejected: `connect: {}` would otherwise attach every row in the table.
|
|
6816
6970
|
*/
|
|
6817
6971
|
const conditionOf = (plan, filter, operation, scope) => {
|
|
6818
|
-
const condition = extractFilters(plan.targetTable, plan.targetTableName, filter, relationFilterCtx(filterCtx, plan.targetTableName));
|
|
6972
|
+
const condition = extractFilters(plan.targetTable, plan.targetTableName, filter, relationFilterCtx(filterCtx, plan.targetTableName, scope));
|
|
6819
6973
|
if (!condition) throw drizzleError(`Drizzle-GraphQL Error: '${operation}' on '${plan.relationName}' needs a filter that selects rows — it was given one that matches everything.`, { code: "DRIZZLE_NESTED_WRITE_INVALID" });
|
|
6820
6974
|
return withScope(scope, plan.targetTableName, plan.targetTable, condition);
|
|
6821
6975
|
};
|
|
@@ -7210,7 +7364,7 @@ const createSchemaBuilder = (adapter) => {
|
|
|
7210
7364
|
uniqueKeysOf: (tableName) => uniqueKeys[tableName]
|
|
7211
7365
|
};
|
|
7212
7366
|
adapter.preflight?.(db, options);
|
|
7213
|
-
const nestedPlans = features.nestedWrites ? buildNestedWritePlans(tables, namedRelations, (target) =>
|
|
7367
|
+
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
7368
|
if (nestedPlans) {
|
|
7215
7369
|
const omitted = gateNestedWritePlans(nestedPlans, featureOf, (tableName) => excludedTables.has(tableName));
|
|
7216
7370
|
if (omitted.length) {
|
|
@@ -7426,6 +7580,14 @@ const createSchemaBuilder = (adapter) => {
|
|
|
7426
7580
|
}
|
|
7427
7581
|
if (Object.keys(relResolvers).length > 0) fieldResolvers[tableName] = relResolvers;
|
|
7428
7582
|
}
|
|
7583
|
+
registerBuildRuntime({
|
|
7584
|
+
db: ctx.db,
|
|
7585
|
+
filterCtx: ctx.filterCtx,
|
|
7586
|
+
policies: ctx.policies,
|
|
7587
|
+
scoped: !!ctx.tablePolicies?.scope,
|
|
7588
|
+
limits: ctx.limits,
|
|
7589
|
+
nullOrdering: adapter.nullOrdering
|
|
7590
|
+
}, Object.values(ctx.outputs));
|
|
7429
7591
|
return {
|
|
7430
7592
|
queries: ctx.queries,
|
|
7431
7593
|
mutations: ctx.mutations,
|
|
@@ -7498,20 +7660,33 @@ const generateUpsert = (db, tableName, table, baseType, onConflictType, fieldNam
|
|
|
7498
7660
|
}
|
|
7499
7661
|
},
|
|
7500
7662
|
run: async ({ executor, args, context, before, after }) => {
|
|
7501
|
-
const
|
|
7663
|
+
const contextColumns = policies?.contextValues?.(tableName);
|
|
7664
|
+
const input = applyContextValuesAll(single ? [remapFromGraphQLSingleInput(args.values, table)] : remapFromGraphQLArrayInput(args.values, table), contextColumns, context);
|
|
7502
7665
|
if (!input.length) throw drizzleError("No values were provided!", { code: "DRIZZLE_NO_VALUES" });
|
|
7503
7666
|
await before();
|
|
7504
|
-
const
|
|
7667
|
+
const batches = resolveConflictPlan({
|
|
7505
7668
|
table,
|
|
7506
7669
|
values: input,
|
|
7507
7670
|
onConflict: args.onConflict,
|
|
7508
7671
|
pkNames,
|
|
7509
7672
|
uniqueSets: [],
|
|
7510
7673
|
excludedRef: mysqlValuesColumnRef,
|
|
7511
|
-
withTarget: false
|
|
7674
|
+
withTarget: false,
|
|
7675
|
+
serverOwned: Object.keys(contextColumns ?? {})
|
|
7676
|
+
});
|
|
7677
|
+
const requestedNothing = args.onConflict?.action === "NOTHING";
|
|
7678
|
+
const runBatch = async (target, { values, plan }) => {
|
|
7679
|
+
if (plan.action === "NOTHING" && !requestedNothing) {
|
|
7680
|
+
const columns = (0, drizzle_orm.getColumns)(table);
|
|
7681
|
+
const keyName = pkNames[0] ?? Object.keys(columns)[0];
|
|
7682
|
+
await target.insert(table).values(values).onDuplicateKeyUpdate({ set: { [keyName]: drizzle_orm.sql`${drizzle_orm.sql.identifier(columns[keyName].name)}` } });
|
|
7683
|
+
} else if (plan.action === "NOTHING") await target.insert(table).ignore().values(values);
|
|
7684
|
+
else await target.insert(table).values(values).onDuplicateKeyUpdate({ set: plan.set });
|
|
7685
|
+
};
|
|
7686
|
+
if (batches.length === 1) await runBatch(executor, batches[0]);
|
|
7687
|
+
else await executor.transaction(async (tx) => {
|
|
7688
|
+
for (const batch of batches) await runBatch(tx, batch);
|
|
7512
7689
|
});
|
|
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
7690
|
await after();
|
|
7516
7691
|
return isSuccess;
|
|
7517
7692
|
}
|
|
@@ -7535,7 +7710,7 @@ const generateUpdate = (db, tableName, table, setArgs, filterArgs, fieldName, si
|
|
|
7535
7710
|
const input = stripContextValues(remapUpdateInput(set, table, tableName), policies?.contextValues?.(tableName));
|
|
7536
7711
|
if (!Object.keys(input).length) throw drizzleError("Unable to update with no values specified!", { code: "DRIZZLE_NO_VALUES" });
|
|
7537
7712
|
await before();
|
|
7538
|
-
const relationCtx = relationFilterCtx(filterCtx, tableName);
|
|
7713
|
+
const relationCtx = relationFilterCtx(filterCtx, tableName, scope);
|
|
7539
7714
|
const filters = scopedWhere({
|
|
7540
7715
|
scope,
|
|
7541
7716
|
tableName,
|
|
@@ -7581,7 +7756,7 @@ const generateUpdateMany$2 = (db, tableName, table, updateManyInput, fieldName,
|
|
|
7581
7756
|
if (!Object.keys(input).length) throw drizzleError("Unable to update with no values specified!", { code: "DRIZZLE_NO_VALUES" });
|
|
7582
7757
|
return {
|
|
7583
7758
|
set: input,
|
|
7584
|
-
filters: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName)) : void 0)
|
|
7759
|
+
filters: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName, scope)) : void 0)
|
|
7585
7760
|
};
|
|
7586
7761
|
});
|
|
7587
7762
|
await executor.transaction(async (tx) => {
|
|
@@ -7624,7 +7799,7 @@ const generateDelete = (db, tableName, table, filterArgs, fieldName, single, req
|
|
|
7624
7799
|
const { where } = args;
|
|
7625
7800
|
const hard = canHardDelete && args.hard === true;
|
|
7626
7801
|
const scope = policies?.scope?.(context);
|
|
7627
|
-
const relationCtx = relationFilterCtx(filterCtx, tableName);
|
|
7802
|
+
const relationCtx = relationFilterCtx(filterCtx, tableName, scope);
|
|
7628
7803
|
const filters = scopedWhere({
|
|
7629
7804
|
scope,
|
|
7630
7805
|
tableName,
|
|
@@ -7957,7 +8132,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7957
8132
|
});
|
|
7958
8133
|
const returning = withHookReturning(nestedEntries ? nested.withJoinColumns(tableName, mergedOps(nestedEntries), { ...columns }, table) : columns);
|
|
7959
8134
|
const runUpsert = async (target, values) => {
|
|
7960
|
-
const
|
|
8135
|
+
const batches = resolveConflictPlan({
|
|
7961
8136
|
table,
|
|
7962
8137
|
values,
|
|
7963
8138
|
onConflict: args.onConflict,
|
|
@@ -7965,16 +8140,29 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7965
8140
|
uniqueSets,
|
|
7966
8141
|
excludedRef: excludedColumnRef,
|
|
7967
8142
|
withTarget: true,
|
|
7968
|
-
|
|
8143
|
+
serverOwned: Object.keys(contextColumns ?? {}),
|
|
8144
|
+
buildWhere: (where) => extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName, scope))
|
|
7969
8145
|
});
|
|
7970
|
-
|
|
7971
|
-
|
|
7972
|
-
|
|
7973
|
-
|
|
7974
|
-
|
|
7975
|
-
|
|
8146
|
+
const runBatch = async (executor, { values: rows, plan }) => {
|
|
8147
|
+
let query = executor.insert(table).values(rows).returning(returning);
|
|
8148
|
+
const setWhere = withScope(scope, tableName, table, plan.setWhere);
|
|
8149
|
+
query = plan.action === "NOTHING" ? query.onConflictDoNothing(plan.target ? {
|
|
8150
|
+
target: plan.target,
|
|
8151
|
+
where: plan.targetWhere
|
|
8152
|
+
} : void 0) : query.onConflictDoUpdate({
|
|
8153
|
+
target: plan.target,
|
|
8154
|
+
targetWhere: plan.targetWhere,
|
|
8155
|
+
set: plan.set,
|
|
8156
|
+
setWhere
|
|
8157
|
+
});
|
|
8158
|
+
return await query;
|
|
8159
|
+
};
|
|
8160
|
+
if (batches.length === 1) return runBatch(target, batches[0]);
|
|
8161
|
+
return target.transaction(async (tx) => {
|
|
8162
|
+
const rows = [];
|
|
8163
|
+
for (const batch of batches) rows.push(...await runBatch(tx, batch));
|
|
8164
|
+
return rows;
|
|
7976
8165
|
});
|
|
7977
|
-
return await query;
|
|
7978
8166
|
};
|
|
7979
8167
|
const written = nestedEntries ? await writeWithNestedOps({
|
|
7980
8168
|
executor,
|
|
@@ -8035,7 +8223,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
8035
8223
|
const nestedOps = entry && nested.hasOps(entry.ops) ? entry.ops : void 0;
|
|
8036
8224
|
const input = stripContextValues(remapUpdateInput(entry ? entry.columns : set, table, tableName), policies?.contextValues?.(tableName));
|
|
8037
8225
|
if (!Object.keys(input).length && !nestedOps) throw drizzleError("Unable to update with no values specified!", { code: "DRIZZLE_NO_VALUES" });
|
|
8038
|
-
const relationCtx = relationFilterCtx(filterCtx, tableName);
|
|
8226
|
+
const relationCtx = relationFilterCtx(filterCtx, tableName, scope);
|
|
8039
8227
|
const filters = scopedWhere({
|
|
8040
8228
|
scope,
|
|
8041
8229
|
tableName,
|
|
@@ -8114,7 +8302,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
8114
8302
|
await before();
|
|
8115
8303
|
const parsedInfo = parseResolveInfo(info, { deep: true });
|
|
8116
8304
|
const columns = extractSelectedColumnsFromTreeSQLFormat(parsedInfo.fieldsByTypeName[applyObjectTypeName(typeName, tableName, resolveName)], table, selectionCtx);
|
|
8117
|
-
const relationCtx = relationFilterCtx(filterCtx, tableName);
|
|
8305
|
+
const relationCtx = relationFilterCtx(filterCtx, tableName, scope);
|
|
8118
8306
|
const filters = scopedWhere({
|
|
8119
8307
|
scope,
|
|
8120
8308
|
tableName,
|
|
@@ -8154,7 +8342,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
8154
8342
|
*/
|
|
8155
8343
|
const createSchemaDataGenerator = (adapter) => {
|
|
8156
8344
|
const primaryKeyPropNames = adapter.primaryKeyPropNames;
|
|
8157
|
-
const
|
|
8345
|
+
const uniqueTargets = (table) => getUniqueTargets(table, adapter.getTableConfig);
|
|
8158
8346
|
const { generateInsert, generateUpsert, generateUpdate, generateDelete } = buildWriteResolvers(primaryKeyPropNames);
|
|
8159
8347
|
const { prepareBuild, forEachTable, finalizeBuild } = createSchemaBuilder(adapter);
|
|
8160
8348
|
return (db, schema, relations, options) => {
|
|
@@ -8195,7 +8383,7 @@ const createSchemaDataGenerator = (adapter) => {
|
|
|
8195
8383
|
single: true,
|
|
8196
8384
|
conflictDoNothing
|
|
8197
8385
|
}) : void 0;
|
|
8198
|
-
const uniqueSets = tableFeatures.upsert ?
|
|
8386
|
+
const uniqueSets = tableFeatures.upsert ? uniqueTargets(schema[tableName]) : [];
|
|
8199
8387
|
const onConflictInput = tableFeatures.upsert ? generateOnConflictInput({
|
|
8200
8388
|
table: schema[tableName],
|
|
8201
8389
|
tableName,
|
|
@@ -8565,7 +8753,7 @@ const createUpdateManyGenerator = (primaryKeyPropNames, runBatch = runUpdateMany
|
|
|
8565
8753
|
return {
|
|
8566
8754
|
set: input,
|
|
8567
8755
|
ops,
|
|
8568
|
-
filters: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName)) : void 0)
|
|
8756
|
+
filters: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName, scope)) : void 0)
|
|
8569
8757
|
};
|
|
8570
8758
|
});
|
|
8571
8759
|
const anyNested = entries.some((entry) => entry.ops);
|
|
@@ -8735,6 +8923,36 @@ const namingRuleOf = (options) => resolveGeneratedTypeNames({
|
|
|
8735
8923
|
typeNameSuffix: options.typeNameSuffix
|
|
8736
8924
|
});
|
|
8737
8925
|
/**
|
|
8926
|
+
* The build whose policies the read applies. Looked up through the generated types the
|
|
8927
|
+
* resolve info reaches — the field's return type, the root table's type in the executing
|
|
8928
|
+
* schema, the parent type — so a resolver over a generated type finds its own build. Without
|
|
8929
|
+
* resolve info (a parsed tree was passed), the instance's only build is used.
|
|
8930
|
+
*/
|
|
8931
|
+
const runtimeFor = (info, db, typeName, helper) => {
|
|
8932
|
+
if (!("fieldsByTypeName" in info)) {
|
|
8933
|
+
const candidates = [
|
|
8934
|
+
info.returnType ? (0, graphql.getNamedType)(info.returnType) : void 0,
|
|
8935
|
+
info.schema?.getType(typeName),
|
|
8936
|
+
info.parentType
|
|
8937
|
+
];
|
|
8938
|
+
for (const candidate of candidates) {
|
|
8939
|
+
const runtime = runtimeForType(candidate);
|
|
8940
|
+
if (runtime && runtime.db === db) return runtime;
|
|
8941
|
+
}
|
|
8942
|
+
}
|
|
8943
|
+
return runtimeForDb(db, helper);
|
|
8944
|
+
};
|
|
8945
|
+
/**
|
|
8946
|
+
* The request's row policies under `runtime`, bound to the context the caller passed. A build
|
|
8947
|
+
* with `scope` hooks cannot be read without that context: the hooks would be called with
|
|
8948
|
+
* nothing, and running the read unscoped is the leak the option exists to prevent.
|
|
8949
|
+
*/
|
|
8950
|
+
const scopeFor = (runtime, options, helper) => {
|
|
8951
|
+
if (!runtime?.policies?.scope) return;
|
|
8952
|
+
if (runtime.scoped && !("context" in options)) throw new Error(`Drizzle-GraphQL Error: the schema this selection belongs to configures \`scope\`, so ${helper} needs the request context to apply it. Pass the resolver's context as the \`context\` option.`);
|
|
8953
|
+
return runtime.policies.scope(options.context);
|
|
8954
|
+
};
|
|
8955
|
+
/**
|
|
8738
8956
|
* Reads the parsed selection tree out of a resolver's `info`, resolving fragments and
|
|
8739
8957
|
* inline fragments. Accepts an already-parsed tree so a caller that has one (or is chaining
|
|
8740
8958
|
* these helpers) does not pay for the walk twice.
|
|
@@ -8761,6 +8979,10 @@ const parseInfo = (info) => {
|
|
|
8761
8979
|
* const row = await tx.query.Users.findFirst({ where: { id: { eq } }, with: withTree });
|
|
8762
8980
|
* ```
|
|
8763
8981
|
*
|
|
8982
|
+
* Every nested entry carries the build's row policies — `scope`, `softDelete`, `limits` and
|
|
8983
|
+
* `defaults.orderBy` — exactly as the generated `with:` clause does. The build is found
|
|
8984
|
+
* through the resolve info; pass `context` when it configures `scope`.
|
|
8985
|
+
*
|
|
8764
8986
|
* Returns `undefined` when the selection asks for no relations, which is the value the
|
|
8765
8987
|
* query builder wants for "no `with` clause".
|
|
8766
8988
|
*/
|
|
@@ -8770,11 +8992,16 @@ const selectionToWith = (info, options) => {
|
|
|
8770
8992
|
const { tables, relationMap, filterCtx } = contextFor(db);
|
|
8771
8993
|
if (!tables[table]) throw new Error(`Drizzle-GraphQL Error: '${table}' is not a table in the Drizzle schema.`);
|
|
8772
8994
|
if (!relationMap[table]) return;
|
|
8995
|
+
const typeName = resolveObjectTypeName(table, typeNameMapper, resolveName);
|
|
8996
|
+
const runtime = runtimeFor(info, db, typeName, "selectionToWith");
|
|
8773
8997
|
const parsed = parseInfo(info);
|
|
8774
|
-
const params = extractRelationsParams(relationMap, tables, table, parsed,
|
|
8998
|
+
const params = extractRelationsParams(relationMap, tables, table, parsed, typeName, {
|
|
8775
8999
|
typeNameMapper,
|
|
8776
|
-
filterCtx,
|
|
8777
|
-
resolveName
|
|
9000
|
+
filterCtx: runtime?.filterCtx ?? filterCtx,
|
|
9001
|
+
resolveName,
|
|
9002
|
+
scope: scopeFor(runtime, options, "selectionToWith"),
|
|
9003
|
+
limits: runtime?.limits,
|
|
9004
|
+
defaultOrderBy: runtime?.policies?.defaultOrderBy
|
|
8778
9005
|
});
|
|
8779
9006
|
return params && Object.keys(params).length ? params : void 0;
|
|
8780
9007
|
};
|
|
@@ -8786,14 +9013,16 @@ const selectionToWith = (info, options) => {
|
|
|
8786
9013
|
* This is what an override usually wants after it has done its own work:
|
|
8787
9014
|
*
|
|
8788
9015
|
* ```ts
|
|
8789
|
-
* const rows = await resolveSelection(info, { db, table: 'Users', where, executor: tx });
|
|
9016
|
+
* const rows = await resolveSelection(info, { db, table: 'Users', context, where, executor: tx });
|
|
8790
9017
|
* ```
|
|
8791
9018
|
*
|
|
8792
9019
|
* Relation arguments and aggregate fields keep behaving as they do in the generated schema,
|
|
8793
|
-
* because it is the generated schema's own code path.
|
|
9020
|
+
* because it is the generated schema's own code path. So do the build's row policies: the
|
|
9021
|
+
* root table and every nested relation are scoped and soft-delete-filtered, and the limit
|
|
9022
|
+
* policy and default ordering apply. Pass `context` when the build configures `scope`.
|
|
8794
9023
|
*/
|
|
8795
9024
|
const resolveSelection = async (info, options) => {
|
|
8796
|
-
const { db, table: tableName, typeNameMapper, executor, single = false, where, orderBy, limit, offset, after } = options;
|
|
9025
|
+
const { db, table: tableName, typeNameMapper, executor, single = false, where, orderBy, limit, offset, after, deleted } = options;
|
|
8797
9026
|
const resolveName = namingRuleOf(options);
|
|
8798
9027
|
const { tables, relationMap, filterCtx, primaryKeyOf } = contextFor(db);
|
|
8799
9028
|
const table = tables[tableName];
|
|
@@ -8803,25 +9032,34 @@ const resolveSelection = async (info, options) => {
|
|
|
8803
9032
|
const source = executor ?? db;
|
|
8804
9033
|
const queryBase = source.query?.[tableName];
|
|
8805
9034
|
if (!queryBase) throw new Error(`Drizzle-GraphQL Error: the executor passed to resolveSelection has no relational query builder for '${tableName}'.`);
|
|
9035
|
+
const typeName = resolveObjectTypeName(tableName, typeNameMapper, resolveName);
|
|
9036
|
+
const runtime = runtimeFor(info, db, typeName, "resolveSelection");
|
|
9037
|
+
const scope = scopeFor(runtime, options, "resolveSelection");
|
|
9038
|
+
const defaultOrderBy = runtime?.policies?.defaultOrderBy;
|
|
8806
9039
|
return runRelationalSelect({
|
|
8807
9040
|
queryBase,
|
|
8808
9041
|
tables,
|
|
8809
9042
|
tableName,
|
|
8810
9043
|
table,
|
|
8811
9044
|
relationMap,
|
|
8812
|
-
typeName
|
|
9045
|
+
typeName,
|
|
8813
9046
|
typeNameMapper,
|
|
8814
9047
|
resolveName,
|
|
8815
9048
|
parsedInfo,
|
|
8816
9049
|
single,
|
|
8817
9050
|
where,
|
|
8818
|
-
orderBy,
|
|
8819
|
-
limit,
|
|
9051
|
+
orderBy: withDefaultOrderBy({ orderBy }, tableName, defaultOrderBy).orderBy,
|
|
9052
|
+
limit: single ? limit : applyLimitPolicy(limit, runtime?.limits?.(tableName)),
|
|
8820
9053
|
offset,
|
|
8821
9054
|
after,
|
|
8822
|
-
filterCtx,
|
|
9055
|
+
filterCtx: runtime?.filterCtx ?? filterCtx,
|
|
8823
9056
|
pkNames: primaryKeyOf(tableName),
|
|
8824
|
-
db: source
|
|
9057
|
+
db: source,
|
|
9058
|
+
scope,
|
|
9059
|
+
deleted,
|
|
9060
|
+
limits: runtime?.limits,
|
|
9061
|
+
defaultOrderBy,
|
|
9062
|
+
nullOrdering: runtime?.nullOrdering
|
|
8825
9063
|
});
|
|
8826
9064
|
};
|
|
8827
9065
|
//#endregion
|