@vantreeseba/drizzle-graphql 11.0.2 → 12.0.1
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 +45 -1
- package/dist/index.cjs +942 -792
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +942 -792
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -520,11 +520,12 @@ const isJsonColumn = (column) => (column.dataType ?? "").includes("json") && col
|
|
|
520
520
|
* StringArray, …) so arrays with different element types never share
|
|
521
521
|
* one filter input; membership operators instead of scalar comparisons
|
|
522
522
|
* - "Id" → uuid-typed columns (no string pattern operators)
|
|
523
|
-
* - "Boolean" → boolean columns
|
|
523
|
+
* - "Boolean" → boolean columns (equality and membership only — no ordering, no patterns)
|
|
524
524
|
* - "BigInt" → bigint columns (BigInt-scalar-typed operators, no string pattern operators)
|
|
525
525
|
* - "Decimal" → numeric/decimal columns (Decimal-scalar-typed operators, no string pattern operators)
|
|
526
|
-
* - the enum GraphQL type name → enum columns (still unique per enum
|
|
527
|
-
*
|
|
526
|
+
* - the enum GraphQL type name → enum columns (still unique per enum; equality and
|
|
527
|
+
* membership only, like Boolean)
|
|
528
|
+
* - "DateTime" → timestamp and date columns (ordering kept, no string pattern operators)
|
|
528
529
|
* - "Int" → integer/serial columns (no string pattern operators)
|
|
529
530
|
* - "Float" → real/double columns (no string pattern operators)
|
|
530
531
|
* - "String" → all other text/varchar columns
|
|
@@ -552,7 +553,8 @@ const resolveGenericFilterDescriptor = (column, columnGraphQLType) => {
|
|
|
552
553
|
};
|
|
553
554
|
if (columnGraphQLType.type instanceof GraphQLEnumType) return {
|
|
554
555
|
name: columnGraphQLType.type.name,
|
|
555
|
-
kind: "scalar"
|
|
556
|
+
kind: "scalar",
|
|
557
|
+
isEnum: true
|
|
556
558
|
};
|
|
557
559
|
if (columnGraphQLType.type === GraphQLBigIntString) return {
|
|
558
560
|
name: "BigInt",
|
|
@@ -776,18 +778,32 @@ const arrayFilterFields = (colType, colArr) => {
|
|
|
776
778
|
};
|
|
777
779
|
/**
|
|
778
780
|
* Filters whose fields omit the string pattern operators (like/notLike/ilike/notIlike/
|
|
779
|
-
* startsWith/contains/…
|
|
780
|
-
*
|
|
781
|
-
*
|
|
781
|
+
* startsWith/contains/… and the `insensitive` flag that modifies them): they are nonsensical
|
|
782
|
+
* on opaque uuids, booleans and enum members, invalid SQL on numeric columns, and on a
|
|
783
|
+
* timestamp they match whatever the session happens to render the value as rather than
|
|
784
|
+
* anything in the query. Keyed on the generic filter name (i.e. on column type) — a
|
|
785
|
+
* string-typed column keeps the string operators whatever it is called. Enum filters are
|
|
786
|
+
* named after their enum and are recognised by `isEnum` instead.
|
|
782
787
|
*/
|
|
783
788
|
const FILTERS_WITHOUT_STRING_OPS = /* @__PURE__ */ new Set([
|
|
784
789
|
"Id",
|
|
785
790
|
"Int",
|
|
786
791
|
"Float",
|
|
787
792
|
"BigInt",
|
|
788
|
-
"Decimal"
|
|
793
|
+
"Decimal",
|
|
794
|
+
"Boolean",
|
|
795
|
+
"Date",
|
|
796
|
+
"DateTime"
|
|
789
797
|
]);
|
|
790
798
|
/**
|
|
799
|
+
* Filters that additionally omit the ordering operators (lt/lte/gt/gte). Ordering two
|
|
800
|
+
* booleans is `eq` spelled as a puzzle (`gt: false` is `eq: true`), and ordering enum members
|
|
801
|
+
* compares their declaration order — an artefact of how the column was written rather than a
|
|
802
|
+
* fact about the domain. Timestamps and the numerics keep them: ranges are the point of
|
|
803
|
+
* having them. Enum filters are recognised by `isEnum`, as above.
|
|
804
|
+
*/
|
|
805
|
+
const FILTERS_WITHOUT_ORDERING_OPS = /* @__PURE__ */ new Set(["Boolean"]);
|
|
806
|
+
/**
|
|
791
807
|
* Scalars the built-in detection itself can produce. A scalar override to one of these
|
|
792
808
|
* shares the built-in filter type for that scalar, so its shape must stay exactly what a
|
|
793
809
|
* natural column of the scalar gets — whichever column builds the cached type first.
|
|
@@ -830,14 +846,15 @@ const resolveOverrideFilterDescriptor = (scalar) => {
|
|
|
830
846
|
const generateColumnFilterValues = (column, tableName, columnName, cacheCtx) => {
|
|
831
847
|
const columnGraphQLType = drizzleColumnToGraphQLType(column, columnName, tableName, true, false, true);
|
|
832
848
|
const inputOverride = getColumnScalarOverride(column, true);
|
|
833
|
-
const { name: genericName, kind } = inputOverride ? resolveOverrideFilterDescriptor(inputOverride) : resolveGenericFilterDescriptor(column, columnGraphQLType);
|
|
849
|
+
const { name: genericName, kind, isEnum } = inputOverride ? resolveOverrideFilterDescriptor(inputOverride) : resolveGenericFilterDescriptor(column, columnGraphQLType);
|
|
834
850
|
const cached = cacheCtx.genericFilterCache.get(genericName);
|
|
835
851
|
if (cached) return cached;
|
|
836
852
|
const colType = columnGraphQLType.type;
|
|
837
853
|
const colArr = new GraphQLList(new GraphQLNonNull(colType));
|
|
838
854
|
const customOverrideFilter = inputOverride !== void 0 && !BUILTIN_FILTER_SCALARS.has(inputOverride);
|
|
839
855
|
const underlying = extractExtendedColumnType(column);
|
|
840
|
-
const omitStringOps = customOverrideFilter ? underlying.type !== "string" || underlying.constraint === "numeric" : FILTERS_WITHOUT_STRING_OPS.has(genericName);
|
|
856
|
+
const omitStringOps = customOverrideFilter ? underlying.type !== "string" || underlying.constraint === "numeric" : isEnum || FILTERS_WITHOUT_STRING_OPS.has(genericName);
|
|
857
|
+
const omitOrderingOps = !customOverrideFilter && (isEnum || FILTERS_WITHOUT_ORDERING_OPS.has(genericName));
|
|
841
858
|
const baseFields = kind === "json" ? jsonFilterFields(column, colType, cacheCtx.typeName) : kind === "array" ? arrayFilterFields(colType, colArr) : {
|
|
842
859
|
eq: {
|
|
843
860
|
type: colType,
|
|
@@ -847,21 +864,23 @@ const generateColumnFilterValues = (column, tableName, columnName, cacheCtx) =>
|
|
|
847
864
|
type: colType,
|
|
848
865
|
description: "Not equal to"
|
|
849
866
|
},
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
867
|
+
...omitOrderingOps ? {} : {
|
|
868
|
+
lt: {
|
|
869
|
+
type: colType,
|
|
870
|
+
description: "Less than"
|
|
871
|
+
},
|
|
872
|
+
lte: {
|
|
873
|
+
type: colType,
|
|
874
|
+
description: "Less than or equal to"
|
|
875
|
+
},
|
|
876
|
+
gt: {
|
|
877
|
+
type: colType,
|
|
878
|
+
description: "Greater than"
|
|
879
|
+
},
|
|
880
|
+
gte: {
|
|
881
|
+
type: colType,
|
|
882
|
+
description: "Greater than or equal to"
|
|
883
|
+
}
|
|
865
884
|
},
|
|
866
885
|
...omitStringOps ? {} : {
|
|
867
886
|
like: { type: GraphQLString },
|
|
@@ -2883,6 +2902,154 @@ const listFieldComplexity = (options, policy) => ({ args, childComplexity }) =>
|
|
|
2883
2902
|
*/
|
|
2884
2903
|
const aggregateFieldComplexity = (options) => ({ childComplexity }) => options.aggregateCost + childComplexity;
|
|
2885
2904
|
//#endregion
|
|
2905
|
+
//#region src/util/parse-resolve-info.ts
|
|
2906
|
+
/** Unwraps `[T]!`-style wrappers down to the named type. `getNamedType` without the realm tie. */
|
|
2907
|
+
const namedTypeOf = (type) => {
|
|
2908
|
+
let current = type;
|
|
2909
|
+
while (current?.ofType) current = current.ofType;
|
|
2910
|
+
return current;
|
|
2911
|
+
};
|
|
2912
|
+
/**
|
|
2913
|
+
* Whether a field's named output type has sub-selections. Object types and interfaces expose
|
|
2914
|
+
* `getFields`, unions expose `getTypes`; scalars and enums expose neither. Input object types
|
|
2915
|
+
* also expose `getFields`, but an input type can never be a field's output type, so there is
|
|
2916
|
+
* nothing here for that to collide with.
|
|
2917
|
+
*/
|
|
2918
|
+
const isCompositeLike = (type) => !!type && (typeof type.getFields === "function" || typeof type.getTypes === "function");
|
|
2919
|
+
/**
|
|
2920
|
+
* The fields declared on a type, or `undefined` when it has none to declare. A union is the
|
|
2921
|
+
* case that matters: it has no fields of its own, so a selection directly against one resolves
|
|
2922
|
+
* to nothing and is dropped — only the inline fragments underneath contribute.
|
|
2923
|
+
*/
|
|
2924
|
+
const fieldsOf = (type) => typeof type?.getFields === "function" ? type.getFields() : void 0;
|
|
2925
|
+
/**
|
|
2926
|
+
* The runtime variable values, in the plain-map form the directive check wants. graphql 17
|
|
2927
|
+
* wraps them in `{ sources, coerced }`; graphql 16 hands over the map itself.
|
|
2928
|
+
*/
|
|
2929
|
+
const coercedVariables = (info) => {
|
|
2930
|
+
const values = info.variableValues;
|
|
2931
|
+
if (values && typeof values === "object" && "coerced" in values) return values.coerced ?? {};
|
|
2932
|
+
return values ?? {};
|
|
2933
|
+
};
|
|
2934
|
+
/** The value of a `@skip` / `@include` `if:` argument — a literal or a variable reference. */
|
|
2935
|
+
const directiveIfValue = (info, value) => {
|
|
2936
|
+
if (value.kind === Kind.VARIABLE) return coercedVariables(info)[value.name.value];
|
|
2937
|
+
if (value.kind === Kind.BOOLEAN) return value.value;
|
|
2938
|
+
};
|
|
2939
|
+
/**
|
|
2940
|
+
* Whether `@skip` / `@include` on this selection exclude it. Applies to fields, fragment
|
|
2941
|
+
* spreads and inline fragments alike, which is where graphql puts them.
|
|
2942
|
+
*/
|
|
2943
|
+
const isExcluded = (info, node) => {
|
|
2944
|
+
for (const directive of node.directives ?? []) {
|
|
2945
|
+
const directiveName = directive.name.value;
|
|
2946
|
+
if (directiveName !== "skip" && directiveName !== "include") continue;
|
|
2947
|
+
const ifArgument = directive.arguments?.find((argument) => argument.name.value === "if");
|
|
2948
|
+
if (!ifArgument) continue;
|
|
2949
|
+
const value = directiveIfValue(info, ifArgument.value);
|
|
2950
|
+
if (directiveName === "skip" ? !!value : !value) return true;
|
|
2951
|
+
}
|
|
2952
|
+
return false;
|
|
2953
|
+
};
|
|
2954
|
+
/** The type a fragment's `on Type` condition names, looked up in the executing schema. */
|
|
2955
|
+
const typeFromCondition = (info, condition) => info.schema.getType(condition.name.value);
|
|
2956
|
+
/**
|
|
2957
|
+
* Introspection meta-fields are not schema fields and never reach the database. `__typename` is
|
|
2958
|
+
* the one that turns up in practice; `__id` is excluded from the rule because a schema is free
|
|
2959
|
+
* to declare a field by that name.
|
|
2960
|
+
*/
|
|
2961
|
+
const isMetaField = (name) => name.startsWith("__") && name !== "__id";
|
|
2962
|
+
/**
|
|
2963
|
+
* Merges one selection set into `tree`, which is a `fieldsByTypeName` map — the caller owns the
|
|
2964
|
+
* level, this owns the entries under `parentType`'s name.
|
|
2965
|
+
*
|
|
2966
|
+
* `activeFragments` is the set of fragment names on the current spread path. A document that
|
|
2967
|
+
* passed validation cannot contain a fragment cycle, but this parser also runs on trees a
|
|
2968
|
+
* caller assembled by hand, and an unbounded walk would hang rather than fail.
|
|
2969
|
+
*/
|
|
2970
|
+
const collectSelections = (selections, info, tree, parentType, deep, activeFragments) => {
|
|
2971
|
+
const parentTypeName = parentType?.name;
|
|
2972
|
+
if (!parentTypeName) return tree;
|
|
2973
|
+
let fields = tree[parentTypeName];
|
|
2974
|
+
if (!fields) {
|
|
2975
|
+
fields = {};
|
|
2976
|
+
tree[parentTypeName] = fields;
|
|
2977
|
+
}
|
|
2978
|
+
const parentFields = fieldsOf(parentType);
|
|
2979
|
+
for (const selection of selections) {
|
|
2980
|
+
if (isExcluded(info, selection)) continue;
|
|
2981
|
+
if (selection.kind === Kind.FIELD) {
|
|
2982
|
+
const name = selection.name.value;
|
|
2983
|
+
if (isMetaField(name)) continue;
|
|
2984
|
+
const fieldDef = parentFields?.[name];
|
|
2985
|
+
if (!fieldDef) continue;
|
|
2986
|
+
const fieldType = namedTypeOf(fieldDef.type);
|
|
2987
|
+
if (!fieldType) continue;
|
|
2988
|
+
const composite = isCompositeLike(fieldType);
|
|
2989
|
+
const alias = selection.alias?.value || name;
|
|
2990
|
+
let node = fields[alias];
|
|
2991
|
+
if (!node) {
|
|
2992
|
+
node = {
|
|
2993
|
+
name,
|
|
2994
|
+
alias,
|
|
2995
|
+
args: { ...getArgumentValues(fieldDef, selection, info.variableValues) },
|
|
2996
|
+
fieldsByTypeName: composite && fieldType.name ? { [fieldType.name]: {} } : {}
|
|
2997
|
+
};
|
|
2998
|
+
fields[alias] = node;
|
|
2999
|
+
}
|
|
3000
|
+
if (selection.selectionSet && deep && composite) collectSelections(selection.selectionSet.selections, info, node.fieldsByTypeName, fieldType, deep, activeFragments);
|
|
3001
|
+
continue;
|
|
3002
|
+
}
|
|
3003
|
+
if (!deep) continue;
|
|
3004
|
+
if (selection.kind === Kind.FRAGMENT_SPREAD) {
|
|
3005
|
+
const fragmentName = selection.name.value;
|
|
3006
|
+
const fragment = info.fragments?.[fragmentName];
|
|
3007
|
+
if (!fragment) throw new Error(`Drizzle-GraphQL Error: unknown fragment '${fragmentName}' — it is spread in the query but not defined in the document.`);
|
|
3008
|
+
if (activeFragments.has(fragmentName)) continue;
|
|
3009
|
+
const fragmentType = fragment.typeCondition ? typeFromCondition(info, fragment.typeCondition) : parentType;
|
|
3010
|
+
if (!isCompositeLike(fragmentType)) continue;
|
|
3011
|
+
activeFragments.add(fragmentName);
|
|
3012
|
+
try {
|
|
3013
|
+
collectSelections(fragment.selectionSet.selections, info, tree, fragmentType, deep, activeFragments);
|
|
3014
|
+
} finally {
|
|
3015
|
+
activeFragments.delete(fragmentName);
|
|
3016
|
+
}
|
|
3017
|
+
continue;
|
|
3018
|
+
}
|
|
3019
|
+
if (selection.kind === Kind.INLINE_FRAGMENT) {
|
|
3020
|
+
const fragmentType = selection.typeCondition ? typeFromCondition(info, selection.typeCondition) : parentType;
|
|
3021
|
+
if (!isCompositeLike(fragmentType)) continue;
|
|
3022
|
+
collectSelections(selection.selectionSet.selections, info, tree, fragmentType, deep, activeFragments);
|
|
3023
|
+
}
|
|
3024
|
+
}
|
|
3025
|
+
return tree;
|
|
3026
|
+
};
|
|
3027
|
+
/** The first key of an object, without materializing the whole key array to discard it. */
|
|
3028
|
+
const firstKey = (object) => {
|
|
3029
|
+
for (const key in object) return key;
|
|
3030
|
+
};
|
|
3031
|
+
/**
|
|
3032
|
+
* Reads the selection under a resolver's own field out of its `info`, as a tree of
|
|
3033
|
+
* {@link ResolveTree} nodes.
|
|
3034
|
+
*
|
|
3035
|
+
* ```ts
|
|
3036
|
+
* const parsed = parseResolveInfo(info, { deep: true })!;
|
|
3037
|
+
* const selected = parsed.fieldsByTypeName['Users']; // keyed by response key
|
|
3038
|
+
* ```
|
|
3039
|
+
*
|
|
3040
|
+
* Returns `undefined` when the resolver's field produced no node at all, which in practice
|
|
3041
|
+
* means `info` carried no field nodes.
|
|
3042
|
+
*/
|
|
3043
|
+
const parseResolveInfo = (info, options = {}) => {
|
|
3044
|
+
const fieldNodes = info.fieldNodes;
|
|
3045
|
+
if (!fieldNodes) throw new Error("Drizzle-GraphQL Error: resolve info carries no fieldNodes, so no selection could be read from it.");
|
|
3046
|
+
const tree = collectSelections(fieldNodes, info, {}, info.parentType, options.deep ?? true, /* @__PURE__ */ new Set());
|
|
3047
|
+
const typeKey = firstKey(tree);
|
|
3048
|
+
const fields = typeKey === void 0 ? void 0 : tree[typeKey];
|
|
3049
|
+
const fieldKey = fields && firstKey(fields);
|
|
3050
|
+
return fieldKey === void 0 ? void 0 : fields?.[fieldKey];
|
|
3051
|
+
};
|
|
3052
|
+
//#endregion
|
|
2886
3053
|
//#region src/util/builders/field-updates.ts
|
|
2887
3054
|
/**
|
|
2888
3055
|
* The scalars arithmetic is offered on. Decimal and BigInt transport as strings but are
|
|
@@ -3215,75 +3382,196 @@ const pruneNonEagerRelations = (relationMap, shouldEagerLoad) => {
|
|
|
3215
3382
|
for (const [tableName, rels] of Object.entries(relationMap)) out[tableName] = Object.fromEntries(Object.entries(rels).filter(([relationName]) => shouldEagerLoad(tableName, relationName)));
|
|
3216
3383
|
return out;
|
|
3217
3384
|
};
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
fieldNames: /* @__PURE__ */ new Set(),
|
|
3221
|
-
states: /* @__PURE__ */ new WeakMap()
|
|
3222
|
-
} : void 0;
|
|
3223
|
-
const includedByDirectives = (node, variables) => {
|
|
3224
|
-
for (const directive of node.directives ?? []) {
|
|
3225
|
-
const name = directive.name.value;
|
|
3226
|
-
if (name !== "skip" && name !== "include") continue;
|
|
3227
|
-
const ifArg = directive.arguments?.find((arg) => arg.name.value === "if");
|
|
3228
|
-
let value;
|
|
3229
|
-
if (ifArg?.value.kind === "BooleanValue") value = ifArg.value.value;
|
|
3230
|
-
else if (ifArg?.value.kind === "Variable") value = variables?.[ifArg.value.name.value];
|
|
3231
|
-
if (name === "skip" && value === true) return false;
|
|
3232
|
-
if (name === "include" && value !== true) return false;
|
|
3233
|
-
}
|
|
3234
|
-
return true;
|
|
3235
|
-
};
|
|
3385
|
+
//#endregion
|
|
3386
|
+
//#region src/util/builders/common/select-runtime.ts
|
|
3236
3387
|
/**
|
|
3237
|
-
*
|
|
3238
|
-
*
|
|
3239
|
-
*
|
|
3240
|
-
*
|
|
3241
|
-
*
|
|
3242
|
-
* open a transaction it might never be able to close.
|
|
3388
|
+
* Runs the relational-query-builder select shared by every dialect's `generateSelect*`
|
|
3389
|
+
* resolver: selected columns + offset/limit + aliased orderBy/where callbacks + the eager
|
|
3390
|
+
* `with:` relation params, then remaps the result. `single` switches between
|
|
3391
|
+
* findFirst/findMany (and the single path omits `limit`). The PG fallback for tables
|
|
3392
|
+
* without RQB support stays in pg.ts; this covers the common RQB path for all three.
|
|
3243
3393
|
*/
|
|
3244
|
-
const
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
if (
|
|
3254
|
-
} else if (
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
visitedFragments.add(fragmentName);
|
|
3258
|
-
const fragment = fragments?.[fragmentName];
|
|
3259
|
-
if (!fragment) return false;
|
|
3260
|
-
if (!collectRootMutationKeys(fragment.selectionSet, fragments, variables, known, keys, visitedFragments)) return false;
|
|
3261
|
-
}
|
|
3394
|
+
const runRelationalSelect = async (opts) => {
|
|
3395
|
+
const { queryBase, tables, tableName, table, relationMap, typeName, typeNameMapper, parsedInfo, offset, orderBy, where, single, filterCtx, pkNames, after, scope, deleted } = opts;
|
|
3396
|
+
const distinct = opts.distinct?.length ? opts.distinct : void 0;
|
|
3397
|
+
const cursorSelected = !single && isCursorFieldSelected(parsedInfo.fieldsByTypeName[typeName], table);
|
|
3398
|
+
let cursorEntries;
|
|
3399
|
+
if (!single && (after != null || cursorSelected)) {
|
|
3400
|
+
if (after != null && distinct) throw drizzleError("'after' cannot be combined with 'distinct'.", { code: "DRIZZLE_INVALID_CURSOR" });
|
|
3401
|
+
const cursorObstacle = orderByCursorObstacle(orderBy);
|
|
3402
|
+
if (cursorObstacle) {
|
|
3403
|
+
if (after != null) throw drizzleError(cursorObstacle, { code: "DRIZZLE_INVALID_CURSOR" });
|
|
3404
|
+
} else if (!pkNames?.length) {
|
|
3405
|
+
if (after != null) throw drizzleError(`Table ${tableName} has no primary key, so cursor pagination cannot be used on it.`, { code: "DRIZZLE_INVALID_CURSOR" });
|
|
3406
|
+
} else cursorEntries = cursorOrderingEntries(orderBy, pkNames);
|
|
3262
3407
|
}
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
}
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3408
|
+
const cursorValues = after != null && cursorEntries ? decodeCursor(after, cursorEntries) : void 0;
|
|
3409
|
+
const needsDefaultOrder = single || offset != null || opts.limit != null;
|
|
3410
|
+
let distinctKeys;
|
|
3411
|
+
if (distinct) {
|
|
3412
|
+
distinctKeys = await selectDistinctKeys({
|
|
3413
|
+
db: opts.db,
|
|
3414
|
+
table,
|
|
3415
|
+
tableName,
|
|
3416
|
+
distinct,
|
|
3417
|
+
pkNames: pkNames ?? [],
|
|
3418
|
+
where: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName)) : void 0, deleted),
|
|
3419
|
+
orderBy,
|
|
3420
|
+
limit: single ? 1 : opts.limit,
|
|
3421
|
+
offset
|
|
3422
|
+
});
|
|
3423
|
+
if (!distinctKeys.length) return single ? void 0 : [];
|
|
3424
|
+
}
|
|
3425
|
+
const params = {
|
|
3426
|
+
columns: extractSelectedColumnsFromTree(parsedInfo.fieldsByTypeName[typeName], table, {
|
|
3427
|
+
tableName,
|
|
3428
|
+
relationMap,
|
|
3429
|
+
tables,
|
|
3430
|
+
allRelations: filterCtx?.relationMap
|
|
3431
|
+
}),
|
|
3432
|
+
offset: distinctKeys ? void 0 : offset,
|
|
3433
|
+
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,
|
|
3434
|
+
where: distinctKeys ? { RAW: (aliasedTable) => primaryKeyRestriction(aliasedTable, pkNames, distinctKeys) } : where || cursorValues || scope?.has(tableName, deleted) ? { RAW: (aliasedTable) => withScope(scope, tableName, aliasedTable, 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,
|
|
3435
|
+
with: relationMap[tableName] ? extractRelationsParams(relationMap, tables, tableName, parsedInfo, typeName, {
|
|
3436
|
+
typeNameMapper,
|
|
3437
|
+
filterCtx,
|
|
3438
|
+
limits: opts.limits,
|
|
3439
|
+
scope,
|
|
3440
|
+
defaultOrderBy: opts.defaultOrderBy,
|
|
3441
|
+
resolveName: opts.resolveName
|
|
3442
|
+
}) : void 0
|
|
3443
|
+
};
|
|
3444
|
+
if (single) {
|
|
3445
|
+
const result = await queryBase.findFirst(params);
|
|
3446
|
+
return result ? remapToGraphQLSingleOutput(result, tableName, table, relationMap) : void 0;
|
|
3447
|
+
}
|
|
3448
|
+
if (cursorEntries && cursorSelected) for (const [column] of cursorEntries) params.columns[column] = true;
|
|
3449
|
+
params.limit = distinctKeys ? void 0 : opts.limit;
|
|
3450
|
+
const result = await queryBase.findMany(params);
|
|
3451
|
+
if (cursorEntries && cursorSelected) attachRowCursors(result, cursorEntries);
|
|
3452
|
+
return remapToGraphQLArrayOutput(result, tableName, table, relationMap);
|
|
3453
|
+
};
|
|
3454
|
+
/**
|
|
3455
|
+
* After a mutation, re-fetch the mutated rows through the relational query builder so the
|
|
3456
|
+
* selected relations are eagerly loaded in a single query, then merge those relations onto
|
|
3457
|
+
* the `.returning()` rows — making the per-field BatchLoader fallback unnecessary.
|
|
3458
|
+
*
|
|
3459
|
+
* `withParams` is the pre-computed relation selection (from extractRelationsParams); the
|
|
3460
|
+
* caller only invokes this when relations are actually selected, so it also gates whether
|
|
3461
|
+
* the PK was forced into RETURNING.
|
|
3462
|
+
*
|
|
3463
|
+
* Falls back to the original `.returning()` rows (relations then resolve via the
|
|
3464
|
+
* field-level BatchLoader) when the table has no RQB support, no primary key columns can be
|
|
3465
|
+
* determined, or the re-fetch fails. Supports single- and multi-column primary keys.
|
|
3466
|
+
*/
|
|
3467
|
+
const eagerLoadMutationRelations = async (db, tableName, rows, pkNames, withParams) => {
|
|
3468
|
+
if (!rows.length || !pkNames.length || !withParams || !Object.keys(withParams).length) return rows;
|
|
3469
|
+
const queryBase = db.query?.[tableName];
|
|
3470
|
+
if (!queryBase) return rows;
|
|
3471
|
+
const keyableRows = rows.filter((row) => pkNames.every((n) => row[n] != null));
|
|
3472
|
+
if (!keyableRows.length) return rows;
|
|
3473
|
+
const pkColumns = {};
|
|
3474
|
+
for (const pk of pkNames) pkColumns[pk] = true;
|
|
3475
|
+
const relationNames = Object.keys(withParams);
|
|
3476
|
+
const keyOf = (row) => JSON.stringify(pkNames.map((n) => typeof row[n] === "bigint" ? row[n].toString() : row[n]));
|
|
3477
|
+
let whereRaw;
|
|
3478
|
+
if (pkNames.length === 1) {
|
|
3479
|
+
const pkName = pkNames[0];
|
|
3480
|
+
const ids = keyableRows.map((r) => r[pkName]);
|
|
3481
|
+
whereRaw = (aliased) => inArray(aliased[pkName], ids);
|
|
3482
|
+
} else whereRaw = (aliased) => {
|
|
3483
|
+
const lhs = sql.join(pkNames.map((n) => sql`${aliased[n]}`), sql`, `);
|
|
3484
|
+
const tuples = sql.join(keyableRows.map((row) => sql`(${sql.join(pkNames.map((n) => sql`${row[n]}`), sql`, `)})`), sql`, `);
|
|
3485
|
+
return sql`(${lhs}) in (${tuples})`;
|
|
3486
|
+
};
|
|
3487
|
+
let enriched;
|
|
3488
|
+
try {
|
|
3489
|
+
enriched = await queryBase.findMany({
|
|
3490
|
+
columns: pkColumns,
|
|
3491
|
+
where: { RAW: whereRaw },
|
|
3492
|
+
with: withParams
|
|
3493
|
+
});
|
|
3494
|
+
} catch (err) {
|
|
3495
|
+
console.warn(`[drizzle-graphql] eager-loading relations for a "${tableName}" mutation failed; falling back to lazy resolution.`, err);
|
|
3496
|
+
return rows;
|
|
3497
|
+
}
|
|
3498
|
+
const byKey = new Map(enriched.map((e) => [keyOf(e), e]));
|
|
3499
|
+
for (const row of rows) {
|
|
3500
|
+
const match = byKey.get(keyOf(row));
|
|
3501
|
+
if (!match) continue;
|
|
3502
|
+
for (const rel of relationNames) row[rel] = match[rel];
|
|
3503
|
+
}
|
|
3504
|
+
return rows;
|
|
3505
|
+
};
|
|
3506
|
+
const createMutationTxCtx = (options) => options ? {
|
|
3507
|
+
timeoutMs: options.timeoutMs,
|
|
3508
|
+
fieldNames: /* @__PURE__ */ new Set(),
|
|
3509
|
+
states: /* @__PURE__ */ new WeakMap()
|
|
3510
|
+
} : void 0;
|
|
3511
|
+
const includedByDirectives = (node, variables) => {
|
|
3512
|
+
for (const directive of node.directives ?? []) {
|
|
3513
|
+
const name = directive.name.value;
|
|
3514
|
+
if (name !== "skip" && name !== "include") continue;
|
|
3515
|
+
const ifArg = directive.arguments?.find((arg) => arg.name.value === "if");
|
|
3516
|
+
let value;
|
|
3517
|
+
if (ifArg?.value.kind === "BooleanValue") value = ifArg.value.value;
|
|
3518
|
+
else if (ifArg?.value.kind === "Variable") value = variables?.[ifArg.value.name.value];
|
|
3519
|
+
if (name === "skip" && value === true) return false;
|
|
3520
|
+
if (name === "include" && value !== true) return false;
|
|
3521
|
+
}
|
|
3522
|
+
return true;
|
|
3523
|
+
};
|
|
3524
|
+
/**
|
|
3525
|
+
* Collects the response keys of every root mutation field the executor will run, expanding
|
|
3526
|
+
* fragments and honoring `@skip` / `@include`. Distinct response keys (alias ?? name) are
|
|
3527
|
+
* counted, matching graphql-js field collection, so a field selected twice under one key is
|
|
3528
|
+
* still one resolver call. Returns `false` when the document selects a root mutation field
|
|
3529
|
+
* this build did not generate — its completion cannot be tracked, so the caller must not
|
|
3530
|
+
* open a transaction it might never be able to close.
|
|
3531
|
+
*/
|
|
3532
|
+
const collectRootMutationKeys = (selectionSet, fragments, variables, known, keys, visitedFragments) => {
|
|
3533
|
+
for (const selection of selectionSet.selections) {
|
|
3534
|
+
if (!includedByDirectives(selection, variables)) continue;
|
|
3535
|
+
if (selection.kind === "Field") {
|
|
3536
|
+
const name = selection.name.value;
|
|
3537
|
+
if (name === "__typename") continue;
|
|
3538
|
+
if (!known.has(name)) return false;
|
|
3539
|
+
keys.add(selection.alias?.value ?? name);
|
|
3540
|
+
} else if (selection.kind === "InlineFragment") {
|
|
3541
|
+
if (!collectRootMutationKeys(selection.selectionSet, fragments, variables, known, keys, visitedFragments)) return false;
|
|
3542
|
+
} else if (selection.kind === "FragmentSpread") {
|
|
3543
|
+
const fragmentName = selection.name.value;
|
|
3544
|
+
if (visitedFragments.has(fragmentName)) continue;
|
|
3545
|
+
visitedFragments.add(fragmentName);
|
|
3546
|
+
const fragment = fragments?.[fragmentName];
|
|
3547
|
+
if (!fragment) return false;
|
|
3548
|
+
if (!collectRootMutationKeys(fragment.selectionSet, fragments, variables, known, keys, visitedFragments)) return false;
|
|
3549
|
+
}
|
|
3550
|
+
}
|
|
3551
|
+
return true;
|
|
3552
|
+
};
|
|
3553
|
+
const abortedBatchError = () => drizzleError("Drizzle-GraphQL Error: Mutation was not executed because an earlier mutation in the same request failed and the shared transaction was rolled back.", { code: "DRIZZLE_TRANSACTION_ABORTED" });
|
|
3554
|
+
/** Opens the request-wide transaction and wires up latch, inactivity timer and cleanup. */
|
|
3555
|
+
const openSharedTx = (db, context, txCtx, stateKey, expected) => {
|
|
3556
|
+
let release;
|
|
3557
|
+
let rejectLatch;
|
|
3558
|
+
const latch = new Promise((resolve, reject) => {
|
|
3559
|
+
release = resolve;
|
|
3560
|
+
rejectLatch = reject;
|
|
3561
|
+
});
|
|
3562
|
+
let readyResolve;
|
|
3563
|
+
let readyReject;
|
|
3564
|
+
const ready = new Promise((resolve, reject) => {
|
|
3565
|
+
readyResolve = resolve;
|
|
3566
|
+
readyReject = reject;
|
|
3567
|
+
});
|
|
3568
|
+
ready.catch(() => {});
|
|
3569
|
+
const canStash = context !== null && typeof context === "object" && !Object.isFrozen(context);
|
|
3570
|
+
const settled = db.transaction(async (tx) => {
|
|
3571
|
+
if (canStash) context[drizzleExecutorKey] = tx;
|
|
3572
|
+
readyResolve(tx);
|
|
3573
|
+
await latch;
|
|
3574
|
+
});
|
|
3287
3575
|
let timer;
|
|
3288
3576
|
const clearTimer = () => {
|
|
3289
3577
|
if (timer !== void 0) {
|
|
@@ -3393,12 +3681,6 @@ const hardDeleteArg = {
|
|
|
3393
3681
|
defaultValue: false,
|
|
3394
3682
|
description: "Remove the matched rows instead of marking them deleted. Reads at `INCLUDE`, so it reaches rows that are already marked; a `scope` still confines what it can reach."
|
|
3395
3683
|
};
|
|
3396
|
-
/**
|
|
3397
|
-
* Computes the RETURNING columns and relation selection for a mutation resolver: extracts
|
|
3398
|
-
* the selected scalar columns, determines whether any relations were selected, and only
|
|
3399
|
-
* then forces the primary key into the column set (so the post-mutation eager-load can
|
|
3400
|
-
* re-key rows). Returns everything the resolver needs to decide whether to eager-load.
|
|
3401
|
-
*/
|
|
3402
3684
|
const prepareMutationRelationColumns = (params) => {
|
|
3403
3685
|
const { relationMap, tables, tableName, typeNameMapper, table, pkNames, parsedInfo, resolveName } = params;
|
|
3404
3686
|
const typeName = resolveObjectTypeName(tableName, typeNameMapper, resolveName);
|
|
@@ -3422,6 +3704,38 @@ const prepareMutationRelationColumns = (params) => {
|
|
|
3422
3704
|
};
|
|
3423
3705
|
};
|
|
3424
3706
|
/**
|
|
3707
|
+
* The prologue every write resolver runs: parse the resolve info, then work out the columns
|
|
3708
|
+
* to return and the relations to eager-load from it. `parsedInfo` comes back too — the delete
|
|
3709
|
+
* resolver reads its own columns off the tree rather than taking the prepared set.
|
|
3710
|
+
*/
|
|
3711
|
+
const mutationSelection = (info, params) => {
|
|
3712
|
+
const parsedInfo = parseResolveInfo(info, { deep: true });
|
|
3713
|
+
return {
|
|
3714
|
+
parsedInfo,
|
|
3715
|
+
...prepareMutationRelationColumns({
|
|
3716
|
+
...params,
|
|
3717
|
+
parsedInfo
|
|
3718
|
+
})
|
|
3719
|
+
};
|
|
3720
|
+
};
|
|
3721
|
+
/**
|
|
3722
|
+
* The `where` a write runs under: the caller's filters — required when the mutation refuses to
|
|
3723
|
+
* run unbounded — with the row scope ANDed on last, so a supplied filter can only narrow the
|
|
3724
|
+
* scope and never widen it. An out-of-scope row is simply not matched.
|
|
3725
|
+
*
|
|
3726
|
+
* `deleted` states which soft-delete mode the match reads at, for the writes that need one:
|
|
3727
|
+
* `restore` sees only marked rows, a hard delete has to see them too.
|
|
3728
|
+
*/
|
|
3729
|
+
const scopedWhere = (params) => {
|
|
3730
|
+
const { scope, tableName, table, where, relationCtx, required, deleted } = params;
|
|
3731
|
+
return withScope(scope, tableName, table, required ? extractRequiredFilters(table, tableName, where, relationCtx) : where ? extractFilters(table, tableName, where, relationCtx) : void 0, deleted);
|
|
3732
|
+
};
|
|
3733
|
+
/**
|
|
3734
|
+
* The epilogue: the written rows with their selected relations attached, or the rows as they
|
|
3735
|
+
* came back when the selection asked for no relation at all.
|
|
3736
|
+
*/
|
|
3737
|
+
const withEagerRelations = (executor, tableName, rows, pkNames, withParams, hasRelations) => hasRelations ? eagerLoadMutationRelations(executor, tableName, rows, pkNames, withParams) : rows;
|
|
3738
|
+
/**
|
|
3425
3739
|
* The per-entry input of `update<Table>Many`: `{ where, set }`, reusing the table's
|
|
3426
3740
|
* update `set` input and filter input. Shared by all three dialect builders.
|
|
3427
3741
|
*/
|
|
@@ -3521,8 +3835,14 @@ const generateWriteCount = ({ db, tableName, table, kind, setArgs, filterArgs, f
|
|
|
3521
3835
|
try {
|
|
3522
3836
|
return await runMutation(db, context, info, txCtx, async (executor) => {
|
|
3523
3837
|
const { where, set } = args;
|
|
3524
|
-
const
|
|
3525
|
-
|
|
3838
|
+
const filters = scopedWhere({
|
|
3839
|
+
scope: void 0,
|
|
3840
|
+
tableName,
|
|
3841
|
+
table,
|
|
3842
|
+
where,
|
|
3843
|
+
relationCtx: relationFilterCtx(filterCtx, tableName),
|
|
3844
|
+
required: requireWhere
|
|
3845
|
+
});
|
|
3526
3846
|
let query;
|
|
3527
3847
|
if (kind === "delete") query = executor.delete(table);
|
|
3528
3848
|
else {
|
|
@@ -3768,196 +4088,48 @@ const identifyRows = (field, args) => {
|
|
|
3768
4088
|
};
|
|
3769
4089
|
};
|
|
3770
4090
|
//#endregion
|
|
3771
|
-
//#region src/util/
|
|
3772
|
-
/** Unwraps `[T]!`-style wrappers down to the named type. `getNamedType` without the realm tie. */
|
|
3773
|
-
const namedTypeOf = (type) => {
|
|
3774
|
-
let current = type;
|
|
3775
|
-
while (current?.ofType) current = current.ofType;
|
|
3776
|
-
return current;
|
|
3777
|
-
};
|
|
3778
|
-
/**
|
|
3779
|
-
* Whether a field's named output type has sub-selections. Object types and interfaces expose
|
|
3780
|
-
* `getFields`, unions expose `getTypes`; scalars and enums expose neither. Input object types
|
|
3781
|
-
* also expose `getFields`, but an input type can never be a field's output type, so there is
|
|
3782
|
-
* nothing here for that to collide with.
|
|
3783
|
-
*/
|
|
3784
|
-
const isCompositeLike = (type) => !!type && (typeof type.getFields === "function" || typeof type.getTypes === "function");
|
|
3785
|
-
/**
|
|
3786
|
-
* The fields declared on a type, or `undefined` when it has none to declare. A union is the
|
|
3787
|
-
* case that matters: it has no fields of its own, so a selection directly against one resolves
|
|
3788
|
-
* to nothing and is dropped — only the inline fragments underneath contribute.
|
|
3789
|
-
*/
|
|
3790
|
-
const fieldsOf = (type) => typeof type?.getFields === "function" ? type.getFields() : void 0;
|
|
4091
|
+
//#region src/util/builders/common/relation-resolvers.ts
|
|
3791
4092
|
/**
|
|
3792
|
-
*
|
|
3793
|
-
*
|
|
4093
|
+
* Fetches a to-many relation with per-parent limit/offset for ALL parents in a
|
|
4094
|
+
* single query, using a window function (ROW_NUMBER() OVER (PARTITION BY fk ...)).
|
|
4095
|
+
*
|
|
4096
|
+
* This replaces the previous per-parent fallback that issued one query per parent
|
|
4097
|
+
* (true N+1) whenever pagination args were present. Each parent gets its own
|
|
4098
|
+
* limit/offset window while the database is hit exactly once for the whole batch.
|
|
4099
|
+
*
|
|
4100
|
+
* Window functions require PostgreSQL, MySQL >= 8.0, or SQLite >= 3.25.
|
|
4101
|
+
* Returns raw rows (NOT remapped); the caller groups + remaps them.
|
|
3794
4102
|
*/
|
|
3795
|
-
const
|
|
3796
|
-
const
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
const
|
|
3802
|
-
|
|
3803
|
-
|
|
4103
|
+
const batchedPaginatedRelationQuery = async (db, targetTable, foreignCol, whereCondition, orderByArg, limit, offset, pkNames, columns, orderCtx, whereArgs) => {
|
|
4104
|
+
const cols = columns ?? getColumns(targetTable);
|
|
4105
|
+
const orderExprs = [...orderByArg ? extractOrderBy(targetTable, orderByArg, orderCtx, whereArgs) : [], ...primaryKeyOrderExprs(targetTable, pkNames)];
|
|
4106
|
+
const orderClause = orderExprs.length ? sql` order by ${sql.join(orderExprs, sql`, `)}` : sql``;
|
|
4107
|
+
const RN = "__drizzle_graphql_rn";
|
|
4108
|
+
const rowNumber = sql`row_number() over (partition by ${foreignCol}${orderClause})`.as(RN);
|
|
4109
|
+
const sub = db.select({
|
|
4110
|
+
...cols,
|
|
4111
|
+
[RN]: rowNumber
|
|
4112
|
+
}).from(targetTable).where(whereCondition).as("__paginated");
|
|
4113
|
+
const lower = offset ?? 0;
|
|
4114
|
+
const windowConds = [gt(sub[RN], lower)];
|
|
4115
|
+
if (limit != null) windowConds.push(lte(sub[RN], lower + limit));
|
|
4116
|
+
const rows = await db.select().from(sub).where(and(...windowConds)).orderBy(sub[RN]);
|
|
4117
|
+
for (const row of rows) delete row[RN];
|
|
4118
|
+
return rows;
|
|
3804
4119
|
};
|
|
3805
4120
|
/**
|
|
3806
|
-
*
|
|
3807
|
-
*
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
return false;
|
|
3819
|
-
};
|
|
3820
|
-
/** The type a fragment's `on Type` condition names, looked up in the executing schema. */
|
|
3821
|
-
const typeFromCondition = (info, condition) => info.schema.getType(condition.name.value);
|
|
3822
|
-
/**
|
|
3823
|
-
* Introspection meta-fields are not schema fields and never reach the database. `__typename` is
|
|
3824
|
-
* the one that turns up in practice; `__id` is excluded from the rule because a schema is free
|
|
3825
|
-
* to declare a field by that name.
|
|
3826
|
-
*/
|
|
3827
|
-
const isMetaField = (name) => name.startsWith("__") && name !== "__id";
|
|
3828
|
-
/**
|
|
3829
|
-
* Merges one selection set into `tree`, which is a `fieldsByTypeName` map — the caller owns the
|
|
3830
|
-
* level, this owns the entries under `parentType`'s name.
|
|
3831
|
-
*
|
|
3832
|
-
* `activeFragments` is the set of fragment names on the current spread path. A document that
|
|
3833
|
-
* passed validation cannot contain a fragment cycle, but this parser also runs on trees a
|
|
3834
|
-
* caller assembled by hand, and an unbounded walk would hang rather than fail.
|
|
3835
|
-
*/
|
|
3836
|
-
const collectSelections = (selections, info, tree, parentType, deep, activeFragments) => {
|
|
3837
|
-
const parentTypeName = parentType?.name;
|
|
3838
|
-
if (!parentTypeName) return tree;
|
|
3839
|
-
let fields = tree[parentTypeName];
|
|
3840
|
-
if (!fields) {
|
|
3841
|
-
fields = {};
|
|
3842
|
-
tree[parentTypeName] = fields;
|
|
3843
|
-
}
|
|
3844
|
-
const parentFields = fieldsOf(parentType);
|
|
3845
|
-
for (const selection of selections) {
|
|
3846
|
-
if (isExcluded(info, selection)) continue;
|
|
3847
|
-
if (selection.kind === Kind.FIELD) {
|
|
3848
|
-
const name = selection.name.value;
|
|
3849
|
-
if (isMetaField(name)) continue;
|
|
3850
|
-
const fieldDef = parentFields?.[name];
|
|
3851
|
-
if (!fieldDef) continue;
|
|
3852
|
-
const fieldType = namedTypeOf(fieldDef.type);
|
|
3853
|
-
if (!fieldType) continue;
|
|
3854
|
-
const composite = isCompositeLike(fieldType);
|
|
3855
|
-
const alias = selection.alias?.value || name;
|
|
3856
|
-
let node = fields[alias];
|
|
3857
|
-
if (!node) {
|
|
3858
|
-
node = {
|
|
3859
|
-
name,
|
|
3860
|
-
alias,
|
|
3861
|
-
args: { ...getArgumentValues(fieldDef, selection, info.variableValues) },
|
|
3862
|
-
fieldsByTypeName: composite && fieldType.name ? { [fieldType.name]: {} } : {}
|
|
3863
|
-
};
|
|
3864
|
-
fields[alias] = node;
|
|
3865
|
-
}
|
|
3866
|
-
if (selection.selectionSet && deep && composite) collectSelections(selection.selectionSet.selections, info, node.fieldsByTypeName, fieldType, deep, activeFragments);
|
|
3867
|
-
continue;
|
|
3868
|
-
}
|
|
3869
|
-
if (!deep) continue;
|
|
3870
|
-
if (selection.kind === Kind.FRAGMENT_SPREAD) {
|
|
3871
|
-
const fragmentName = selection.name.value;
|
|
3872
|
-
const fragment = info.fragments?.[fragmentName];
|
|
3873
|
-
if (!fragment) throw new Error(`Drizzle-GraphQL Error: unknown fragment '${fragmentName}' — it is spread in the query but not defined in the document.`);
|
|
3874
|
-
if (activeFragments.has(fragmentName)) continue;
|
|
3875
|
-
const fragmentType = fragment.typeCondition ? typeFromCondition(info, fragment.typeCondition) : parentType;
|
|
3876
|
-
if (!isCompositeLike(fragmentType)) continue;
|
|
3877
|
-
activeFragments.add(fragmentName);
|
|
3878
|
-
try {
|
|
3879
|
-
collectSelections(fragment.selectionSet.selections, info, tree, fragmentType, deep, activeFragments);
|
|
3880
|
-
} finally {
|
|
3881
|
-
activeFragments.delete(fragmentName);
|
|
3882
|
-
}
|
|
3883
|
-
continue;
|
|
3884
|
-
}
|
|
3885
|
-
if (selection.kind === Kind.INLINE_FRAGMENT) {
|
|
3886
|
-
const fragmentType = selection.typeCondition ? typeFromCondition(info, selection.typeCondition) : parentType;
|
|
3887
|
-
if (!isCompositeLike(fragmentType)) continue;
|
|
3888
|
-
collectSelections(selection.selectionSet.selections, info, tree, fragmentType, deep, activeFragments);
|
|
3889
|
-
}
|
|
3890
|
-
}
|
|
3891
|
-
return tree;
|
|
3892
|
-
};
|
|
3893
|
-
/** The first key of an object, without materializing the whole key array to discard it. */
|
|
3894
|
-
const firstKey = (object) => {
|
|
3895
|
-
for (const key in object) return key;
|
|
3896
|
-
};
|
|
3897
|
-
/**
|
|
3898
|
-
* Reads the selection under a resolver's own field out of its `info`, as a tree of
|
|
3899
|
-
* {@link ResolveTree} nodes.
|
|
3900
|
-
*
|
|
3901
|
-
* ```ts
|
|
3902
|
-
* const parsed = parseResolveInfo(info, { deep: true })!;
|
|
3903
|
-
* const selected = parsed.fieldsByTypeName['Users']; // keyed by response key
|
|
3904
|
-
* ```
|
|
3905
|
-
*
|
|
3906
|
-
* Returns `undefined` when the resolver's field produced no node at all, which in practice
|
|
3907
|
-
* means `info` carried no field nodes.
|
|
3908
|
-
*/
|
|
3909
|
-
const parseResolveInfo = (info, options = {}) => {
|
|
3910
|
-
const fieldNodes = info.fieldNodes;
|
|
3911
|
-
if (!fieldNodes) throw new Error("Drizzle-GraphQL Error: resolve info carries no fieldNodes, so no selection could be read from it.");
|
|
3912
|
-
const tree = collectSelections(fieldNodes, info, {}, info.parentType, options.deep ?? true, /* @__PURE__ */ new Set());
|
|
3913
|
-
const typeKey = firstKey(tree);
|
|
3914
|
-
const fields = typeKey === void 0 ? void 0 : tree[typeKey];
|
|
3915
|
-
const fieldKey = fields && firstKey(fields);
|
|
3916
|
-
return fieldKey === void 0 ? void 0 : fields?.[fieldKey];
|
|
3917
|
-
};
|
|
3918
|
-
//#endregion
|
|
3919
|
-
//#region src/util/builders/common/relation-resolvers.ts
|
|
3920
|
-
/**
|
|
3921
|
-
* Fetches a to-many relation with per-parent limit/offset for ALL parents in a
|
|
3922
|
-
* single query, using a window function (ROW_NUMBER() OVER (PARTITION BY fk ...)).
|
|
3923
|
-
*
|
|
3924
|
-
* This replaces the previous per-parent fallback that issued one query per parent
|
|
3925
|
-
* (true N+1) whenever pagination args were present. Each parent gets its own
|
|
3926
|
-
* limit/offset window while the database is hit exactly once for the whole batch.
|
|
3927
|
-
*
|
|
3928
|
-
* Window functions require PostgreSQL, MySQL >= 8.0, or SQLite >= 3.25.
|
|
3929
|
-
* Returns raw rows (NOT remapped); the caller groups + remaps them.
|
|
3930
|
-
*/
|
|
3931
|
-
const batchedPaginatedRelationQuery = async (db, targetTable, foreignCol, whereCondition, orderByArg, limit, offset, pkNames, columns, orderCtx, whereArgs) => {
|
|
3932
|
-
const cols = columns ?? getColumns(targetTable);
|
|
3933
|
-
const orderExprs = [...orderByArg ? extractOrderBy(targetTable, orderByArg, orderCtx, whereArgs) : [], ...primaryKeyOrderExprs(targetTable, pkNames)];
|
|
3934
|
-
const orderClause = orderExprs.length ? sql` order by ${sql.join(orderExprs, sql`, `)}` : sql``;
|
|
3935
|
-
const RN = "__drizzle_graphql_rn";
|
|
3936
|
-
const rowNumber = sql`row_number() over (partition by ${foreignCol}${orderClause})`.as(RN);
|
|
3937
|
-
const sub = db.select({
|
|
3938
|
-
...cols,
|
|
3939
|
-
[RN]: rowNumber
|
|
3940
|
-
}).from(targetTable).where(whereCondition).as("__paginated");
|
|
3941
|
-
const lower = offset ?? 0;
|
|
3942
|
-
const windowConds = [gt(sub[RN], lower)];
|
|
3943
|
-
if (limit != null) windowConds.push(lte(sub[RN], lower + limit));
|
|
3944
|
-
const rows = await db.select().from(sub).where(and(...windowConds)).orderBy(sub[RN]);
|
|
3945
|
-
for (const row of rows) delete row[RN];
|
|
3946
|
-
return rows;
|
|
3947
|
-
};
|
|
3948
|
-
/**
|
|
3949
|
-
* Creates a RelationResolverFactory that generates field-level resolvers for each relation.
|
|
3950
|
-
* Each resolver:
|
|
3951
|
-
* 1. Returns pre-fetched data if the parent resolver already included it (eager path, zero cost).
|
|
3952
|
-
* 2. When limit/offset args are present, falls back to a direct per-item query.
|
|
3953
|
-
* 3. Otherwise batches all sibling resolver calls within the same GraphQL execution tick
|
|
3954
|
-
* into a single IN-clause query, eliminating N+1 database round-trips.
|
|
3955
|
-
*
|
|
3956
|
-
* `nullOrdering` is the dialect's native `ORDER BY` placement for `NULL` — the keyset
|
|
3957
|
-
* predicate behind a relation's `after` argument has to match it, or rows with a `NULL` in an
|
|
3958
|
-
* ordered column would be skipped instead of paged through. It has no sensible default, so
|
|
3959
|
-
* each dialect builder states it: `'nulls-largest'` for PostgreSQL, `'nulls-smallest'` for
|
|
3960
|
-
* MySQL and SQLite.
|
|
4121
|
+
* Creates a RelationResolverFactory that generates field-level resolvers for each relation.
|
|
4122
|
+
* Each resolver:
|
|
4123
|
+
* 1. Returns pre-fetched data if the parent resolver already included it (eager path, zero cost).
|
|
4124
|
+
* 2. When limit/offset args are present, falls back to a direct per-item query.
|
|
4125
|
+
* 3. Otherwise batches all sibling resolver calls within the same GraphQL execution tick
|
|
4126
|
+
* into a single IN-clause query, eliminating N+1 database round-trips.
|
|
4127
|
+
*
|
|
4128
|
+
* `nullOrdering` is the dialect's native `ORDER BY` placement for `NULL` — the keyset
|
|
4129
|
+
* predicate behind a relation's `after` argument has to match it, or rows with a `NULL` in an
|
|
4130
|
+
* ordered column would be skipped instead of paged through. It has no sensible default, so
|
|
4131
|
+
* each dialect builder states it: `'nulls-largest'` for PostgreSQL, `'nulls-smallest'` for
|
|
4132
|
+
* MySQL and SQLite.
|
|
3961
4133
|
*/
|
|
3962
4134
|
const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limits, policies) => ({ tableName, relationName, relEntry, isOne }) => {
|
|
3963
4135
|
const parentTable = tables[tableName];
|
|
@@ -4177,127 +4349,6 @@ const defineRootField = (fields, kind, name, config) => {
|
|
|
4177
4349
|
fields[name] = config;
|
|
4178
4350
|
};
|
|
4179
4351
|
//#endregion
|
|
4180
|
-
//#region src/util/builders/common/select-runtime.ts
|
|
4181
|
-
/**
|
|
4182
|
-
* Runs the relational-query-builder select shared by every dialect's `generateSelect*`
|
|
4183
|
-
* resolver: selected columns + offset/limit + aliased orderBy/where callbacks + the eager
|
|
4184
|
-
* `with:` relation params, then remaps the result. `single` switches between
|
|
4185
|
-
* findFirst/findMany (and the single path omits `limit`). The PG fallback for tables
|
|
4186
|
-
* without RQB support stays in pg.ts; this covers the common RQB path for all three.
|
|
4187
|
-
*/
|
|
4188
|
-
const runRelationalSelect = async (opts) => {
|
|
4189
|
-
const { queryBase, tables, tableName, table, relationMap, typeName, typeNameMapper, parsedInfo, offset, orderBy, where, single, filterCtx, pkNames, after, scope, deleted } = opts;
|
|
4190
|
-
const distinct = opts.distinct?.length ? opts.distinct : void 0;
|
|
4191
|
-
const cursorSelected = !single && isCursorFieldSelected(parsedInfo.fieldsByTypeName[typeName], table);
|
|
4192
|
-
let cursorEntries;
|
|
4193
|
-
if (!single && (after != null || cursorSelected)) {
|
|
4194
|
-
if (after != null && distinct) throw drizzleError("'after' cannot be combined with 'distinct'.", { code: "DRIZZLE_INVALID_CURSOR" });
|
|
4195
|
-
const cursorObstacle = orderByCursorObstacle(orderBy);
|
|
4196
|
-
if (cursorObstacle) {
|
|
4197
|
-
if (after != null) throw drizzleError(cursorObstacle, { code: "DRIZZLE_INVALID_CURSOR" });
|
|
4198
|
-
} else if (!pkNames?.length) {
|
|
4199
|
-
if (after != null) throw drizzleError(`Table ${tableName} has no primary key, so cursor pagination cannot be used on it.`, { code: "DRIZZLE_INVALID_CURSOR" });
|
|
4200
|
-
} else cursorEntries = cursorOrderingEntries(orderBy, pkNames);
|
|
4201
|
-
}
|
|
4202
|
-
const cursorValues = after != null && cursorEntries ? decodeCursor(after, cursorEntries) : void 0;
|
|
4203
|
-
const needsDefaultOrder = single || offset != null || opts.limit != null;
|
|
4204
|
-
let distinctKeys;
|
|
4205
|
-
if (distinct) {
|
|
4206
|
-
distinctKeys = await selectDistinctKeys({
|
|
4207
|
-
db: opts.db,
|
|
4208
|
-
table,
|
|
4209
|
-
tableName,
|
|
4210
|
-
distinct,
|
|
4211
|
-
pkNames: pkNames ?? [],
|
|
4212
|
-
where: withScope(scope, tableName, table, where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName)) : void 0, deleted),
|
|
4213
|
-
orderBy,
|
|
4214
|
-
limit: single ? 1 : opts.limit,
|
|
4215
|
-
offset
|
|
4216
|
-
});
|
|
4217
|
-
if (!distinctKeys.length) return single ? void 0 : [];
|
|
4218
|
-
}
|
|
4219
|
-
const params = {
|
|
4220
|
-
columns: extractSelectedColumnsFromTree(parsedInfo.fieldsByTypeName[typeName], table, {
|
|
4221
|
-
tableName,
|
|
4222
|
-
relationMap,
|
|
4223
|
-
tables,
|
|
4224
|
-
allRelations: filterCtx?.relationMap
|
|
4225
|
-
}),
|
|
4226
|
-
offset: distinctKeys ? void 0 : offset,
|
|
4227
|
-
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,
|
|
4228
|
-
where: distinctKeys ? { RAW: (aliasedTable) => primaryKeyRestriction(aliasedTable, pkNames, distinctKeys) } : where || cursorValues || scope?.has(tableName, deleted) ? { RAW: (aliasedTable) => withScope(scope, tableName, aliasedTable, 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,
|
|
4229
|
-
with: relationMap[tableName] ? extractRelationsParams(relationMap, tables, tableName, parsedInfo, typeName, {
|
|
4230
|
-
typeNameMapper,
|
|
4231
|
-
filterCtx,
|
|
4232
|
-
limits: opts.limits,
|
|
4233
|
-
scope,
|
|
4234
|
-
defaultOrderBy: opts.defaultOrderBy,
|
|
4235
|
-
resolveName: opts.resolveName
|
|
4236
|
-
}) : void 0
|
|
4237
|
-
};
|
|
4238
|
-
if (single) {
|
|
4239
|
-
const result = await queryBase.findFirst(params);
|
|
4240
|
-
return result ? remapToGraphQLSingleOutput(result, tableName, table, relationMap) : void 0;
|
|
4241
|
-
}
|
|
4242
|
-
if (cursorEntries && cursorSelected) for (const [column] of cursorEntries) params.columns[column] = true;
|
|
4243
|
-
params.limit = distinctKeys ? void 0 : opts.limit;
|
|
4244
|
-
const result = await queryBase.findMany(params);
|
|
4245
|
-
if (cursorEntries && cursorSelected) attachRowCursors(result, cursorEntries);
|
|
4246
|
-
return remapToGraphQLArrayOutput(result, tableName, table, relationMap);
|
|
4247
|
-
};
|
|
4248
|
-
/**
|
|
4249
|
-
* After a mutation, re-fetch the mutated rows through the relational query builder so the
|
|
4250
|
-
* selected relations are eagerly loaded in a single query, then merge those relations onto
|
|
4251
|
-
* the `.returning()` rows — making the per-field BatchLoader fallback unnecessary.
|
|
4252
|
-
*
|
|
4253
|
-
* `withParams` is the pre-computed relation selection (from extractRelationsParams); the
|
|
4254
|
-
* caller only invokes this when relations are actually selected, so it also gates whether
|
|
4255
|
-
* the PK was forced into RETURNING.
|
|
4256
|
-
*
|
|
4257
|
-
* Falls back to the original `.returning()` rows (relations then resolve via the
|
|
4258
|
-
* field-level BatchLoader) when the table has no RQB support, no primary key columns can be
|
|
4259
|
-
* determined, or the re-fetch fails. Supports single- and multi-column primary keys.
|
|
4260
|
-
*/
|
|
4261
|
-
const eagerLoadMutationRelations = async (db, tableName, rows, pkNames, withParams) => {
|
|
4262
|
-
if (!rows.length || !pkNames.length || !withParams || !Object.keys(withParams).length) return rows;
|
|
4263
|
-
const queryBase = db.query?.[tableName];
|
|
4264
|
-
if (!queryBase) return rows;
|
|
4265
|
-
const keyableRows = rows.filter((row) => pkNames.every((n) => row[n] != null));
|
|
4266
|
-
if (!keyableRows.length) return rows;
|
|
4267
|
-
const pkColumns = {};
|
|
4268
|
-
for (const pk of pkNames) pkColumns[pk] = true;
|
|
4269
|
-
const relationNames = Object.keys(withParams);
|
|
4270
|
-
const keyOf = (row) => JSON.stringify(pkNames.map((n) => typeof row[n] === "bigint" ? row[n].toString() : row[n]));
|
|
4271
|
-
let whereRaw;
|
|
4272
|
-
if (pkNames.length === 1) {
|
|
4273
|
-
const pkName = pkNames[0];
|
|
4274
|
-
const ids = keyableRows.map((r) => r[pkName]);
|
|
4275
|
-
whereRaw = (aliased) => inArray(aliased[pkName], ids);
|
|
4276
|
-
} else whereRaw = (aliased) => {
|
|
4277
|
-
const lhs = sql.join(pkNames.map((n) => sql`${aliased[n]}`), sql`, `);
|
|
4278
|
-
const tuples = sql.join(keyableRows.map((row) => sql`(${sql.join(pkNames.map((n) => sql`${row[n]}`), sql`, `)})`), sql`, `);
|
|
4279
|
-
return sql`(${lhs}) in (${tuples})`;
|
|
4280
|
-
};
|
|
4281
|
-
let enriched;
|
|
4282
|
-
try {
|
|
4283
|
-
enriched = await queryBase.findMany({
|
|
4284
|
-
columns: pkColumns,
|
|
4285
|
-
where: { RAW: whereRaw },
|
|
4286
|
-
with: withParams
|
|
4287
|
-
});
|
|
4288
|
-
} catch (err) {
|
|
4289
|
-
console.warn(`[drizzle-graphql] eager-loading relations for a "${tableName}" mutation failed; falling back to lazy resolution.`, err);
|
|
4290
|
-
return rows;
|
|
4291
|
-
}
|
|
4292
|
-
const byKey = new Map(enriched.map((e) => [keyOf(e), e]));
|
|
4293
|
-
for (const row of rows) {
|
|
4294
|
-
const match = byKey.get(keyOf(row));
|
|
4295
|
-
if (!match) continue;
|
|
4296
|
-
for (const rel of relationNames) row[rel] = match[rel];
|
|
4297
|
-
}
|
|
4298
|
-
return rows;
|
|
4299
|
-
};
|
|
4300
|
-
//#endregion
|
|
4301
4352
|
//#region src/util/builders/common/table-inputs.ts
|
|
4302
4353
|
const orderMap = /* @__PURE__ */ new WeakMap();
|
|
4303
4354
|
const generateTableOrderCached = (table, innerOrder) => {
|
|
@@ -6455,6 +6506,72 @@ const createSchemaBuilder = (adapter) => {
|
|
|
6455
6506
|
havingInput
|
|
6456
6507
|
};
|
|
6457
6508
|
};
|
|
6509
|
+
/**
|
|
6510
|
+
* The per-table loop, run once for every table the build covers.
|
|
6511
|
+
*
|
|
6512
|
+
* The dialect's callback receives the values the loop has already worked out — the feature
|
|
6513
|
+
* flags, the extension factory, the field names, the table's generated types and its
|
|
6514
|
+
* soft-delete config — and answers with the mutations it generated. Registering them, and
|
|
6515
|
+
* registering the inputs and outputs those mutations reference, happens here: both sides
|
|
6516
|
+
* did it identically, down to the `description` strings on the count mutations, so a gate
|
|
6517
|
+
* changed on one side and forgotten on the other used to be a silent dialect divergence.
|
|
6518
|
+
*
|
|
6519
|
+
* What stays in the dialect is what genuinely differs — which generators run, what each
|
|
6520
|
+
* mutation returns, and (on MySQL) that a write cannot report the rows it touched.
|
|
6521
|
+
*/
|
|
6522
|
+
const forEachTable = (ctx, perTable) => {
|
|
6523
|
+
const { schema, featureOf, softDeleteOf, typeNameMapper, gqlSchemaTypes, mutations, inputs, outputs } = ctx;
|
|
6524
|
+
const { prefixes, suffixes } = ctx.options;
|
|
6525
|
+
for (const [tableName, tableTypes] of Object.entries(gqlSchemaTypes)) {
|
|
6526
|
+
const tableFeatures = featureOf(tableName);
|
|
6527
|
+
const drizzleMeta = tableFieldExtensions(tableName, primaryKeyPropNames(schema[tableName]));
|
|
6528
|
+
const names = computeResolverFieldNames(tableName, typeNameMapper, prefixes, suffixes);
|
|
6529
|
+
const softDeleteInfo = softDeleteOf?.(tableName);
|
|
6530
|
+
const readTypes = addReadFields(ctx, tableName, names, tableTypes, drizzleMeta);
|
|
6531
|
+
const built = perTable({
|
|
6532
|
+
tableName,
|
|
6533
|
+
table: schema[tableName],
|
|
6534
|
+
tableFeatures,
|
|
6535
|
+
drizzleMeta,
|
|
6536
|
+
names,
|
|
6537
|
+
types: tableTypes,
|
|
6538
|
+
softDeleteInfo
|
|
6539
|
+
});
|
|
6540
|
+
for (const { generated, type, meta, description } of built.mutations) if (generated) defineRootField(mutations, "mutation", generated.name, {
|
|
6541
|
+
type,
|
|
6542
|
+
args: generated.args,
|
|
6543
|
+
resolve: generated.resolver,
|
|
6544
|
+
...meta ? { extensions: { drizzle: drizzleMeta({
|
|
6545
|
+
kind: "mutation",
|
|
6546
|
+
...meta
|
|
6547
|
+
}) } } : {},
|
|
6548
|
+
...description ? { description } : {}
|
|
6549
|
+
});
|
|
6550
|
+
const { insertInput, updateInput, tableFilters, tableOrder } = tableTypes.inputs;
|
|
6551
|
+
[
|
|
6552
|
+
...tableFeatures.insert || built.onConflictInput ? [insertInput] : [],
|
|
6553
|
+
...built.onConflictInput ? [built.onConflictInput] : [],
|
|
6554
|
+
...tableFeatures.update ? [updateInput] : [],
|
|
6555
|
+
...built.updateManyInput ? [built.updateManyInput] : [],
|
|
6556
|
+
tableFilters,
|
|
6557
|
+
tableOrder
|
|
6558
|
+
].forEach((e) => {
|
|
6559
|
+
inputs[e.name] = e;
|
|
6560
|
+
});
|
|
6561
|
+
const { selectSingleOutput } = tableTypes.outputs;
|
|
6562
|
+
outputs[selectSingleOutput.name] = selectSingleOutput;
|
|
6563
|
+
if (adapter.returnsRows) {
|
|
6564
|
+
const { singleTableItemOutput } = tableTypes.outputs;
|
|
6565
|
+
outputs[singleTableItemOutput.name] = singleTableItemOutput;
|
|
6566
|
+
}
|
|
6567
|
+
const { aggregateType, groupByType, havingInput } = readTypes;
|
|
6568
|
+
if (aggregateType) outputs[aggregateType.name] = aggregateType;
|
|
6569
|
+
if (groupByType && havingInput) {
|
|
6570
|
+
outputs[groupByType.name] = groupByType;
|
|
6571
|
+
inputs[havingInput.name] = havingInput;
|
|
6572
|
+
}
|
|
6573
|
+
}
|
|
6574
|
+
};
|
|
6458
6575
|
/** The relation field resolvers and the transaction roster, once every root field exists. */
|
|
6459
6576
|
const finalizeBuild = (ctx) => {
|
|
6460
6577
|
if (ctx.mutationTxCtx) for (const name of Object.keys(ctx.mutations)) ctx.mutationTxCtx.fieldNames.add(name);
|
|
@@ -6485,7 +6602,7 @@ const createSchemaBuilder = (adapter) => {
|
|
|
6485
6602
|
primaryKeyPropNames,
|
|
6486
6603
|
uniqueColumnSets,
|
|
6487
6604
|
prepareBuild,
|
|
6488
|
-
|
|
6605
|
+
forEachTable,
|
|
6489
6606
|
finalizeBuild
|
|
6490
6607
|
};
|
|
6491
6608
|
};
|
|
@@ -6583,7 +6700,14 @@ const generateUpdate = (db, tableName, table, setArgs, filterArgs, fieldName, si
|
|
|
6583
6700
|
if (!Object.keys(input).length) throw drizzleError("Unable to update with no values specified!", { code: "DRIZZLE_NO_VALUES" });
|
|
6584
6701
|
await before();
|
|
6585
6702
|
const relationCtx = relationFilterCtx(filterCtx, tableName);
|
|
6586
|
-
const filters =
|
|
6703
|
+
const filters = scopedWhere({
|
|
6704
|
+
scope,
|
|
6705
|
+
tableName,
|
|
6706
|
+
table,
|
|
6707
|
+
where,
|
|
6708
|
+
relationCtx,
|
|
6709
|
+
required: single || requireWhere
|
|
6710
|
+
});
|
|
6587
6711
|
if (single) await assertSingleMatch(executor, table, filters);
|
|
6588
6712
|
let query = executor.update(table).set(input);
|
|
6589
6713
|
if (filters) query = query.where(filters);
|
|
@@ -6665,7 +6789,15 @@ const generateDelete = (db, tableName, table, filterArgs, fieldName, single, req
|
|
|
6665
6789
|
const hard = canHardDelete && args.hard === true;
|
|
6666
6790
|
const scope = policies?.scope?.(context);
|
|
6667
6791
|
const relationCtx = relationFilterCtx(filterCtx, tableName);
|
|
6668
|
-
const filters =
|
|
6792
|
+
const filters = scopedWhere({
|
|
6793
|
+
scope,
|
|
6794
|
+
tableName,
|
|
6795
|
+
table,
|
|
6796
|
+
where,
|
|
6797
|
+
relationCtx,
|
|
6798
|
+
required: single || requireWhere,
|
|
6799
|
+
deleted: restore ? "ONLY" : hard ? "INCLUDE" : void 0
|
|
6800
|
+
});
|
|
6669
6801
|
if (single) await assertSingleMatch(executor, table, filters);
|
|
6670
6802
|
let query = softDelete && !hard ? executor.update(table).set({ [softDelete.columnName]: restore ? softDelete.writeRestored : softDelete.writeDeleted() }) : executor.delete(table);
|
|
6671
6803
|
if (filters) query = query.where(filters);
|
|
@@ -6679,7 +6811,7 @@ const generateDelete = (db, tableName, table, filterArgs, fieldName, single, req
|
|
|
6679
6811
|
const getTableConfig$5 = memoizeTableConfig(getTableConfig);
|
|
6680
6812
|
/** Primary-key property names for a MySQL table, including table-level composite keys. */
|
|
6681
6813
|
const mysqlPrimaryKeyPropNames = (table) => getPrimaryKeyPropNamesFromConfig(table, getTableConfig$5);
|
|
6682
|
-
const { prepareBuild,
|
|
6814
|
+
const { prepareBuild, forEachTable, finalizeBuild } = createSchemaBuilder({
|
|
6683
6815
|
tableClass: MySqlTable,
|
|
6684
6816
|
getTableConfig: getTableConfig$5,
|
|
6685
6817
|
primaryKeyPropNames: mysqlPrimaryKeyPropNames,
|
|
@@ -6690,9 +6822,9 @@ const { prepareBuild, addReadFields, finalizeBuild } = createSchemaBuilder({
|
|
|
6690
6822
|
}
|
|
6691
6823
|
});
|
|
6692
6824
|
const generateSchemaData$2 = (db, schema, relations, options) => {
|
|
6693
|
-
const { prefixes
|
|
6825
|
+
const { prefixes } = options;
|
|
6694
6826
|
const ctx = prepareBuild(db, schema, relations, options);
|
|
6695
|
-
const {
|
|
6827
|
+
const { anyTable, filterCtx, policies, cacheCtx, mutationTxCtx, outputs } = ctx;
|
|
6696
6828
|
const mutationReturnType = new GraphQLObjectType({
|
|
6697
6829
|
name: cacheCtx.typeName({
|
|
6698
6830
|
kind: "shared",
|
|
@@ -6706,15 +6838,9 @@ const generateSchemaData$2 = (db, schema, relations, options) => {
|
|
|
6706
6838
|
"update",
|
|
6707
6839
|
"delete"
|
|
6708
6840
|
].some((feature) => anyTable(feature))) outputs["MutationReturn"] = mutationReturnType;
|
|
6709
|
-
|
|
6710
|
-
const
|
|
6711
|
-
const drizzleMeta = tableFieldExtensions(tableName, mysqlPrimaryKeyPropNames(schema[tableName]));
|
|
6712
|
-
const { insertInput, updateInput, tableFilters, tableOrder } = tableTypes.inputs;
|
|
6713
|
-
const { selectSingleOutput } = tableTypes.outputs;
|
|
6714
|
-
const names = computeResolverFieldNames(tableName, typeNameMapper, prefixes, suffixes);
|
|
6841
|
+
forEachTable(ctx, ({ tableName, tableFeatures, names, types, softDeleteInfo }) => {
|
|
6842
|
+
const { insertInput, updateInput, tableFilters } = types.inputs;
|
|
6715
6843
|
const { typeName, createArrayFieldName, createSingleFieldName, upsertArrayFieldName, upsertSingleFieldName, updateFieldName, updateManyFieldName, updateSingleFieldName, updateCountFieldName, deleteFieldName, deleteSingleFieldName, restoreFieldName, restoreSingleFieldName, deleteCountFieldName } = names;
|
|
6716
|
-
const softDeleteInfo = softDeleteOf?.(tableName);
|
|
6717
|
-
const { aggregateType, groupByType, havingInput } = addReadFields(ctx, tableName, names, tableTypes, drizzleMeta);
|
|
6718
6844
|
const insertArrGenerated = tableFeatures.insert ? generateInsertArray(db, tableName, schema[tableName], insertInput, createArrayFieldName, mutationTxCtx, policies) : void 0;
|
|
6719
6845
|
const insertSingleGenerated = tableFeatures.insert ? generateInsertSingle(db, tableName, schema[tableName], insertInput, createSingleFieldName, mutationTxCtx, policies) : void 0;
|
|
6720
6846
|
const onConflictInput = tableFeatures.upsert ? generateOnConflictInput({
|
|
@@ -6767,103 +6893,85 @@ const generateSchemaData$2 = (db, schema, relations, options) => {
|
|
|
6767
6893
|
txCtx: mutationTxCtx
|
|
6768
6894
|
}) : void 0;
|
|
6769
6895
|
const hardDeleteMeta = softDeleteInfo?.hardDelete ? { hardDelete: true } : {};
|
|
6770
|
-
const
|
|
6771
|
-
|
|
6772
|
-
operation: "insert",
|
|
6773
|
-
single: false,
|
|
6774
|
-
targetArg: "values"
|
|
6775
|
-
}],
|
|
6776
|
-
[insertSingleGenerated, {
|
|
6777
|
-
operation: "insert",
|
|
6778
|
-
single: true,
|
|
6779
|
-
targetArg: "values"
|
|
6780
|
-
}],
|
|
6781
|
-
[upsertArrGenerated, {
|
|
6782
|
-
operation: "upsert",
|
|
6783
|
-
single: false,
|
|
6784
|
-
targetArg: "values"
|
|
6785
|
-
}],
|
|
6786
|
-
[upsertSingleGenerated, {
|
|
6787
|
-
operation: "upsert",
|
|
6788
|
-
single: true,
|
|
6789
|
-
targetArg: "values"
|
|
6790
|
-
}],
|
|
6791
|
-
[updateGenerated, {
|
|
6792
|
-
operation: "update",
|
|
6793
|
-
single: false,
|
|
6794
|
-
targetArg: "where"
|
|
6795
|
-
}],
|
|
6796
|
-
[updateManyGenerated, {
|
|
6797
|
-
operation: "updateMany",
|
|
6798
|
-
single: false,
|
|
6799
|
-
targetArg: "updates"
|
|
6800
|
-
}],
|
|
6801
|
-
[updateSingleGenerated, {
|
|
6802
|
-
operation: "update",
|
|
6803
|
-
single: true,
|
|
6804
|
-
targetArg: "where"
|
|
6805
|
-
}],
|
|
6806
|
-
[deleteGenerated, {
|
|
6807
|
-
operation: "delete",
|
|
6808
|
-
single: false,
|
|
6809
|
-
targetArg: "where",
|
|
6810
|
-
...hardDeleteMeta
|
|
6811
|
-
}],
|
|
6812
|
-
[deleteSingleGenerated, {
|
|
6813
|
-
operation: "delete",
|
|
6814
|
-
single: true,
|
|
6815
|
-
targetArg: "where",
|
|
6816
|
-
...hardDeleteMeta
|
|
6817
|
-
}],
|
|
6818
|
-
[restoreGenerated, {
|
|
6819
|
-
operation: "restore",
|
|
6820
|
-
single: false,
|
|
6821
|
-
targetArg: "where"
|
|
6822
|
-
}],
|
|
6823
|
-
[restoreSingleGenerated, {
|
|
6824
|
-
operation: "restore",
|
|
6825
|
-
single: true,
|
|
6826
|
-
targetArg: "where"
|
|
6827
|
-
}]
|
|
6828
|
-
];
|
|
6829
|
-
for (const [generated, meta] of generatedMutations) if (generated) defineRootField(mutations, "mutation", generated.name, {
|
|
6896
|
+
const withMeta = (generated, meta) => ({
|
|
6897
|
+
generated,
|
|
6830
6898
|
type: mutationReturnType,
|
|
6831
|
-
|
|
6832
|
-
resolve: generated.resolver,
|
|
6833
|
-
extensions: { drizzle: drizzleMeta({
|
|
6834
|
-
kind: "mutation",
|
|
6835
|
-
...meta
|
|
6836
|
-
}) }
|
|
6837
|
-
});
|
|
6838
|
-
if (updateCountGenerated) defineRootField(mutations, "mutation", updateCountGenerated.name, {
|
|
6839
|
-
type: new GraphQLNonNull(GraphQLInt),
|
|
6840
|
-
args: updateCountGenerated.args,
|
|
6841
|
-
resolve: updateCountGenerated.resolver,
|
|
6842
|
-
description: "How many rows the update touched. The rows themselves are not read back, which is the point of this mutation."
|
|
6899
|
+
meta
|
|
6843
6900
|
});
|
|
6844
|
-
|
|
6845
|
-
|
|
6846
|
-
|
|
6847
|
-
|
|
6848
|
-
|
|
6849
|
-
|
|
6850
|
-
|
|
6851
|
-
|
|
6852
|
-
|
|
6853
|
-
|
|
6854
|
-
|
|
6855
|
-
|
|
6856
|
-
|
|
6857
|
-
|
|
6858
|
-
|
|
6859
|
-
|
|
6860
|
-
|
|
6861
|
-
|
|
6862
|
-
|
|
6863
|
-
|
|
6864
|
-
|
|
6865
|
-
|
|
6866
|
-
|
|
6901
|
+
return {
|
|
6902
|
+
mutations: [
|
|
6903
|
+
withMeta(insertArrGenerated, {
|
|
6904
|
+
operation: "insert",
|
|
6905
|
+
single: false,
|
|
6906
|
+
targetArg: "values"
|
|
6907
|
+
}),
|
|
6908
|
+
withMeta(insertSingleGenerated, {
|
|
6909
|
+
operation: "insert",
|
|
6910
|
+
single: true,
|
|
6911
|
+
targetArg: "values"
|
|
6912
|
+
}),
|
|
6913
|
+
withMeta(upsertArrGenerated, {
|
|
6914
|
+
operation: "upsert",
|
|
6915
|
+
single: false,
|
|
6916
|
+
targetArg: "values"
|
|
6917
|
+
}),
|
|
6918
|
+
withMeta(upsertSingleGenerated, {
|
|
6919
|
+
operation: "upsert",
|
|
6920
|
+
single: true,
|
|
6921
|
+
targetArg: "values"
|
|
6922
|
+
}),
|
|
6923
|
+
withMeta(updateGenerated, {
|
|
6924
|
+
operation: "update",
|
|
6925
|
+
single: false,
|
|
6926
|
+
targetArg: "where"
|
|
6927
|
+
}),
|
|
6928
|
+
withMeta(updateManyGenerated, {
|
|
6929
|
+
operation: "updateMany",
|
|
6930
|
+
single: false,
|
|
6931
|
+
targetArg: "updates"
|
|
6932
|
+
}),
|
|
6933
|
+
withMeta(updateSingleGenerated, {
|
|
6934
|
+
operation: "update",
|
|
6935
|
+
single: true,
|
|
6936
|
+
targetArg: "where"
|
|
6937
|
+
}),
|
|
6938
|
+
withMeta(deleteGenerated, {
|
|
6939
|
+
operation: "delete",
|
|
6940
|
+
single: false,
|
|
6941
|
+
targetArg: "where",
|
|
6942
|
+
...hardDeleteMeta
|
|
6943
|
+
}),
|
|
6944
|
+
withMeta(deleteSingleGenerated, {
|
|
6945
|
+
operation: "delete",
|
|
6946
|
+
single: true,
|
|
6947
|
+
targetArg: "where",
|
|
6948
|
+
...hardDeleteMeta
|
|
6949
|
+
}),
|
|
6950
|
+
withMeta(restoreGenerated, {
|
|
6951
|
+
operation: "restore",
|
|
6952
|
+
single: false,
|
|
6953
|
+
targetArg: "where"
|
|
6954
|
+
}),
|
|
6955
|
+
withMeta(restoreSingleGenerated, {
|
|
6956
|
+
operation: "restore",
|
|
6957
|
+
single: true,
|
|
6958
|
+
targetArg: "where"
|
|
6959
|
+
}),
|
|
6960
|
+
{
|
|
6961
|
+
generated: updateCountGenerated,
|
|
6962
|
+
type: new GraphQLNonNull(GraphQLInt),
|
|
6963
|
+
description: "How many rows the update touched. The rows themselves are not read back, which is the point of this mutation."
|
|
6964
|
+
},
|
|
6965
|
+
{
|
|
6966
|
+
generated: deleteCountGenerated,
|
|
6967
|
+
type: new GraphQLNonNull(GraphQLInt),
|
|
6968
|
+
description: "How many rows the delete removed. The rows themselves are not read back, which is the point of this mutation."
|
|
6969
|
+
}
|
|
6970
|
+
],
|
|
6971
|
+
onConflictInput,
|
|
6972
|
+
updateManyInput
|
|
6973
|
+
};
|
|
6974
|
+
});
|
|
6867
6975
|
return finalizeBuild(ctx);
|
|
6868
6976
|
};
|
|
6869
6977
|
//#endregion
|
|
@@ -6873,28 +6981,37 @@ const generateSchemaData$2 = (db, schema, relations, options) => {
|
|
|
6873
6981
|
* result, so the generator call sites read exactly as they did when the functions were local.
|
|
6874
6982
|
*/
|
|
6875
6983
|
const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
6876
|
-
|
|
6877
|
-
|
|
6984
|
+
/**
|
|
6985
|
+
* `insert<Table>` / `insert<Table>Single` — one generator for both shapes, the way
|
|
6986
|
+
* {@link generateUpsert} handles its pair. `single` decides the argument type, whether the
|
|
6987
|
+
* result is remapped as a row or a list, and whether an empty return is an error; the write
|
|
6988
|
+
* itself is the same multi-row statement either way, with the single variant supplying a
|
|
6989
|
+
* one-element list.
|
|
6990
|
+
*/
|
|
6991
|
+
const generateInsert = (build, opts) => {
|
|
6992
|
+
const { db, tables, relationMap, typeNameMapper, txCtx, nested, limits, policies, resolveName } = build;
|
|
6993
|
+
const { tableName, table, baseType, fieldName, typeName, single, conflictDoNothing = false } = opts;
|
|
6994
|
+
const queryArgs = { values: { type: single ? new GraphQLNonNull(baseType) : new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(baseType))) } };
|
|
6878
6995
|
const pkNames = primaryKeyPropNames(table);
|
|
6879
6996
|
return writeResolver({
|
|
6880
6997
|
db,
|
|
6881
6998
|
tableName,
|
|
6882
6999
|
operation: "insert",
|
|
6883
|
-
single
|
|
7000
|
+
single,
|
|
6884
7001
|
fieldName,
|
|
6885
7002
|
txCtx,
|
|
6886
7003
|
policies,
|
|
6887
7004
|
args: queryArgs,
|
|
6888
7005
|
run: async ({ executor, args, context, info, before, after }) => {
|
|
6889
|
-
|
|
7006
|
+
const supplied = single ? [args.values] : args.values;
|
|
7007
|
+
if (!supplied.length) throw drizzleError("No values were provided!", { code: "DRIZZLE_NO_VALUES" });
|
|
6890
7008
|
await before();
|
|
6891
|
-
const entries = nested?.enabled(tableName) ?
|
|
7009
|
+
const entries = nested?.enabled(tableName) ? supplied.map((values) => nested.split(tableName, values)) : void 0;
|
|
6892
7010
|
const nestedEntries = entries?.some((entry) => nested.hasOps(entry.ops)) ? entries : void 0;
|
|
6893
7011
|
const contextColumns = policies?.contextValues?.(tableName);
|
|
6894
7012
|
const scope = policies?.scope?.(context);
|
|
6895
|
-
const input = nestedEntries ? [] : applyContextValuesAll(remapFromGraphQLArrayInput(entries ? entries.map((entry) => entry.columns) :
|
|
6896
|
-
const
|
|
6897
|
-
const { columns, hasRelations, withParams } = prepareMutationRelationColumns({
|
|
7013
|
+
const input = nestedEntries ? [] : applyContextValuesAll(remapFromGraphQLArrayInput(entries ? entries.map((entry) => entry.columns) : supplied, table), contextColumns, context);
|
|
7014
|
+
const { columns, hasRelations, withParams } = mutationSelection(info, {
|
|
6898
7015
|
relationMap,
|
|
6899
7016
|
tables,
|
|
6900
7017
|
tableName,
|
|
@@ -6903,7 +7020,6 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
6903
7020
|
resolveName,
|
|
6904
7021
|
table,
|
|
6905
7022
|
pkNames,
|
|
6906
|
-
parsedInfo,
|
|
6907
7023
|
limits,
|
|
6908
7024
|
scope,
|
|
6909
7025
|
defaultOrderBy: policies?.defaultOrderBy
|
|
@@ -6924,67 +7040,12 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
6924
7040
|
context
|
|
6925
7041
|
}) : await runInsert(executor, input);
|
|
6926
7042
|
await after(result);
|
|
6927
|
-
|
|
6928
|
-
return remapToGraphQLArrayOutput(enriched, tableName, table, relationMap);
|
|
6929
|
-
}
|
|
6930
|
-
});
|
|
6931
|
-
};
|
|
6932
|
-
const generateInsertSingle = (db, tableName, table, tables, relationMap, baseType, fieldName, typeName, typeNameMapper, conflictDoNothing = false, txCtx, nested, limits, policies, resolveName) => {
|
|
6933
|
-
const queryArgs = { values: { type: new GraphQLNonNull(baseType) } };
|
|
6934
|
-
const pkNames = primaryKeyPropNames(table);
|
|
6935
|
-
return writeResolver({
|
|
6936
|
-
db,
|
|
6937
|
-
tableName,
|
|
6938
|
-
operation: "insert",
|
|
6939
|
-
single: true,
|
|
6940
|
-
fieldName,
|
|
6941
|
-
txCtx,
|
|
6942
|
-
policies,
|
|
6943
|
-
args: queryArgs,
|
|
6944
|
-
run: async ({ executor, args, context, info, before, after }) => {
|
|
6945
|
-
await before();
|
|
6946
|
-
const entry = nested?.enabled(tableName) ? nested.split(tableName, args.values) : void 0;
|
|
6947
|
-
const nestedEntry = entry && nested.hasOps(entry.ops) ? entry : void 0;
|
|
6948
|
-
const contextColumns = policies?.contextValues?.(tableName);
|
|
6949
|
-
const scope = policies?.scope?.(context);
|
|
6950
|
-
const input = nestedEntry ? {} : applyContextValues(remapFromGraphQLSingleInput(entry ? entry.columns : args.values, table), contextColumns, context);
|
|
6951
|
-
const parsedInfo = parseResolveInfo(info, { deep: true });
|
|
6952
|
-
const { columns, hasRelations, withParams } = prepareMutationRelationColumns({
|
|
6953
|
-
relationMap,
|
|
6954
|
-
tables,
|
|
6955
|
-
tableName,
|
|
6956
|
-
typeName,
|
|
6957
|
-
typeNameMapper,
|
|
6958
|
-
resolveName,
|
|
6959
|
-
table,
|
|
6960
|
-
pkNames,
|
|
6961
|
-
parsedInfo,
|
|
6962
|
-
limits,
|
|
6963
|
-
scope,
|
|
6964
|
-
defaultOrderBy: policies?.defaultOrderBy
|
|
6965
|
-
});
|
|
6966
|
-
const returning = nestedEntry ? nested.withJoinColumns(tableName, nestedEntry.ops, { ...columns }, table) : columns;
|
|
6967
|
-
const runInsert = async (target, values) => {
|
|
6968
|
-
let query = target.insert(table).values(values).returning(returning);
|
|
6969
|
-
if (conflictDoNothing) query = query.onConflictDoNothing();
|
|
6970
|
-
return await query;
|
|
6971
|
-
};
|
|
6972
|
-
const result = nestedEntry ? await writeWithNestedOps({
|
|
6973
|
-
executor,
|
|
6974
|
-
runtime: nested,
|
|
6975
|
-
tableName,
|
|
6976
|
-
entries: [nestedEntry],
|
|
6977
|
-
remapValues: (values) => applyContextValues(remapFromGraphQLSingleInput(values, table), contextColumns, context),
|
|
6978
|
-
write: runInsert,
|
|
6979
|
-
context
|
|
6980
|
-
}) : await runInsert(executor, input);
|
|
6981
|
-
await after(result);
|
|
6982
|
-
if (!result[0]) {
|
|
7043
|
+
if (single && !result[0]) {
|
|
6983
7044
|
if (!conflictDoNothing) throw drizzleError("The insert returned no row.", { code: "DRIZZLE_NO_ROW_RETURNED" });
|
|
6984
7045
|
return;
|
|
6985
7046
|
}
|
|
6986
|
-
const enriched =
|
|
6987
|
-
return remapToGraphQLSingleOutput(enriched[0], tableName, table, relationMap);
|
|
7047
|
+
const enriched = await withEagerRelations(executor, tableName, result, pkNames, withParams, hasRelations);
|
|
7048
|
+
return single ? remapToGraphQLSingleOutput(enriched[0], tableName, table, relationMap) : remapToGraphQLArrayOutput(enriched, tableName, table, relationMap);
|
|
6988
7049
|
}
|
|
6989
7050
|
});
|
|
6990
7051
|
};
|
|
@@ -6994,7 +7055,9 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
6994
7055
|
*
|
|
6995
7056
|
* Shares the insert input: an upsert supplies a whole row, same as a create.
|
|
6996
7057
|
*/
|
|
6997
|
-
const generateUpsert = (
|
|
7058
|
+
const generateUpsert = (build, opts) => {
|
|
7059
|
+
const { db, tables, relationMap, typeNameMapper, filterCtx, txCtx, nested, limits, policies, resolveName } = build;
|
|
7060
|
+
const { tableName, table, baseType, onConflictType, uniqueSets, fieldName, typeName, single } = opts;
|
|
6998
7061
|
const queryArgs = {
|
|
6999
7062
|
values: { type: single ? new GraphQLNonNull(baseType) : new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(baseType))) },
|
|
7000
7063
|
onConflict: {
|
|
@@ -7021,8 +7084,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7021
7084
|
const contextColumns = policies?.contextValues?.(tableName);
|
|
7022
7085
|
const scope = policies?.scope?.(context);
|
|
7023
7086
|
const input = nestedEntries ? [] : applyContextValuesAll(remapFromGraphQLArrayInput(entries ? entries.map((entry) => entry.columns) : supplied, table), contextColumns, context);
|
|
7024
|
-
const
|
|
7025
|
-
const { columns, hasRelations, withParams } = prepareMutationRelationColumns({
|
|
7087
|
+
const { columns, hasRelations, withParams } = mutationSelection(info, {
|
|
7026
7088
|
relationMap,
|
|
7027
7089
|
tables,
|
|
7028
7090
|
tableName,
|
|
@@ -7031,7 +7093,6 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7031
7093
|
resolveName,
|
|
7032
7094
|
table,
|
|
7033
7095
|
pkNames,
|
|
7034
|
-
parsedInfo,
|
|
7035
7096
|
limits,
|
|
7036
7097
|
scope,
|
|
7037
7098
|
defaultOrderBy: policies?.defaultOrderBy
|
|
@@ -7068,12 +7129,14 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7068
7129
|
}) : await runUpsert(executor, input);
|
|
7069
7130
|
await after(result);
|
|
7070
7131
|
if (single && !result[0]) return;
|
|
7071
|
-
const enriched =
|
|
7132
|
+
const enriched = await withEagerRelations(executor, tableName, result, pkNames, withParams, hasRelations);
|
|
7072
7133
|
return single ? remapToGraphQLSingleOutput(enriched[0], tableName, table, relationMap) : remapToGraphQLArrayOutput(enriched, tableName, table, relationMap);
|
|
7073
7134
|
}
|
|
7074
7135
|
});
|
|
7075
7136
|
};
|
|
7076
|
-
const generateUpdate = (
|
|
7137
|
+
const generateUpdate = (build, opts) => {
|
|
7138
|
+
const { db, tables, relationMap, typeNameMapper, filterCtx, txCtx, nested, limits, policies, resolveName } = build;
|
|
7139
|
+
const { tableName, table, setArgs, filterArgs, fieldName, typeName, single, requireWhere } = opts;
|
|
7077
7140
|
const queryArgs = {
|
|
7078
7141
|
set: { type: new GraphQLNonNull(setArgs) },
|
|
7079
7142
|
where: { type: single || requireWhere ? new GraphQLNonNull(filterArgs) : filterArgs }
|
|
@@ -7092,8 +7155,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7092
7155
|
const { where, set } = args;
|
|
7093
7156
|
const scope = policies?.scope?.(context);
|
|
7094
7157
|
await before();
|
|
7095
|
-
const
|
|
7096
|
-
const { columns, hasRelations, withParams } = prepareMutationRelationColumns({
|
|
7158
|
+
const { columns, hasRelations, withParams } = mutationSelection(info, {
|
|
7097
7159
|
relationMap,
|
|
7098
7160
|
tables,
|
|
7099
7161
|
tableName,
|
|
@@ -7102,7 +7164,6 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7102
7164
|
resolveName,
|
|
7103
7165
|
table,
|
|
7104
7166
|
pkNames,
|
|
7105
|
-
parsedInfo,
|
|
7106
7167
|
limits,
|
|
7107
7168
|
scope,
|
|
7108
7169
|
defaultOrderBy: policies?.defaultOrderBy
|
|
@@ -7112,7 +7173,14 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7112
7173
|
const input = stripContextValues(remapUpdateInput(entry ? entry.columns : set, table, tableName), policies?.contextValues?.(tableName));
|
|
7113
7174
|
if (!Object.keys(input).length && !nestedOps) throw drizzleError("Unable to update with no values specified!", { code: "DRIZZLE_NO_VALUES" });
|
|
7114
7175
|
const relationCtx = relationFilterCtx(filterCtx, tableName);
|
|
7115
|
-
const filters =
|
|
7176
|
+
const filters = scopedWhere({
|
|
7177
|
+
scope,
|
|
7178
|
+
tableName,
|
|
7179
|
+
table,
|
|
7180
|
+
where,
|
|
7181
|
+
relationCtx,
|
|
7182
|
+
required: single || requireWhere
|
|
7183
|
+
});
|
|
7116
7184
|
if (single) await assertSingleMatch(executor, table, filters);
|
|
7117
7185
|
const returning = nestedOps ? nested.withJoinColumns(tableName, nestedOps, { ...columns }, table) : columns;
|
|
7118
7186
|
const runUpdate = async (target, values) => {
|
|
@@ -7134,7 +7202,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7134
7202
|
await after(result);
|
|
7135
7203
|
if (single && result.length > 1) throw drizzleError("'where' matched more than one row!", { code: "DRIZZLE_MULTI_ROW_MATCH" });
|
|
7136
7204
|
if (single && !result[0]) return;
|
|
7137
|
-
const enriched =
|
|
7205
|
+
const enriched = await withEagerRelations(executor, tableName, result, pkNames, withParams, hasRelations);
|
|
7138
7206
|
return single ? remapToGraphQLSingleOutput(enriched[0], tableName, table, relationMap) : remapToGraphQLArrayOutput(enriched, tableName, table, relationMap);
|
|
7139
7207
|
}
|
|
7140
7208
|
});
|
|
@@ -7151,7 +7219,10 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7151
7219
|
* `DELETE` — emptying the trash, reclaiming a unique key. It reads at `INCLUDE`, since the
|
|
7152
7220
|
* rows it mostly exists to remove are the ones already marked; a `scope` still confines it.
|
|
7153
7221
|
*/
|
|
7154
|
-
const generateDelete = (
|
|
7222
|
+
const generateDelete = (build, opts) => {
|
|
7223
|
+
const { db, filterCtx, txCtx, policies, resolveName } = build;
|
|
7224
|
+
const { tableName, table, filterArgs, fieldName, typeName, single, requireWhere, selectionCtx } = opts;
|
|
7225
|
+
const restore = opts.restore ?? false;
|
|
7155
7226
|
const softDelete = policies?.softDelete?.(tableName);
|
|
7156
7227
|
const operation = restore ? "restore" : "delete";
|
|
7157
7228
|
const canHardDelete = !restore && softDelete?.hardDelete === true;
|
|
@@ -7176,7 +7247,15 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7176
7247
|
const parsedInfo = parseResolveInfo(info, { deep: true });
|
|
7177
7248
|
const columns = extractSelectedColumnsFromTreeSQLFormat(parsedInfo.fieldsByTypeName[applyObjectTypeName(typeName, tableName, resolveName)], table, selectionCtx);
|
|
7178
7249
|
const relationCtx = relationFilterCtx(filterCtx, tableName);
|
|
7179
|
-
const filters =
|
|
7250
|
+
const filters = scopedWhere({
|
|
7251
|
+
scope,
|
|
7252
|
+
tableName,
|
|
7253
|
+
table,
|
|
7254
|
+
where,
|
|
7255
|
+
relationCtx,
|
|
7256
|
+
required: single || requireWhere,
|
|
7257
|
+
deleted: restore ? "ONLY" : hard ? "INCLUDE" : void 0
|
|
7258
|
+
});
|
|
7180
7259
|
if (single) await assertSingleMatch(executor, table, filters);
|
|
7181
7260
|
let query = softDelete && !hard ? executor.update(table).set({ [softDelete.columnName]: restore ? softDelete.writeRestored : softDelete.writeDeleted() }) : executor.delete(table);
|
|
7182
7261
|
if (filters) query = query.where(filters);
|
|
@@ -7190,8 +7269,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7190
7269
|
});
|
|
7191
7270
|
};
|
|
7192
7271
|
return {
|
|
7193
|
-
|
|
7194
|
-
generateInsertSingle,
|
|
7272
|
+
generateInsert,
|
|
7195
7273
|
generateUpsert,
|
|
7196
7274
|
generateUpdate,
|
|
7197
7275
|
generateDelete
|
|
@@ -7208,23 +7286,46 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
|
|
|
7208
7286
|
const createSchemaDataGenerator = (adapter) => {
|
|
7209
7287
|
const primaryKeyPropNames = adapter.primaryKeyPropNames;
|
|
7210
7288
|
const uniqueColumnSets = (table) => getUniqueColumnSets(table, adapter.getTableConfig);
|
|
7211
|
-
const {
|
|
7212
|
-
const { prepareBuild,
|
|
7289
|
+
const { generateInsert, generateUpsert, generateUpdate, generateDelete } = buildWriteResolvers(primaryKeyPropNames);
|
|
7290
|
+
const { prepareBuild, forEachTable, finalizeBuild } = createSchemaBuilder(adapter);
|
|
7213
7291
|
return (db, schema, relations, options) => {
|
|
7214
|
-
const { prefixes,
|
|
7292
|
+
const { prefixes, conflictDoNothing, typeNameMapper, limits } = options;
|
|
7215
7293
|
const ctx = prepareBuild(db, schema, relations, options);
|
|
7216
|
-
const { tables,
|
|
7217
|
-
|
|
7218
|
-
|
|
7219
|
-
|
|
7220
|
-
|
|
7221
|
-
|
|
7222
|
-
|
|
7294
|
+
const { tables, eagerRelations, filterCtx, policies, namedRelations, cacheCtx, nestedRuntime, mutationTxCtx } = ctx;
|
|
7295
|
+
const writeBuild = {
|
|
7296
|
+
db,
|
|
7297
|
+
tables,
|
|
7298
|
+
relationMap: eagerRelations,
|
|
7299
|
+
typeNameMapper,
|
|
7300
|
+
filterCtx,
|
|
7301
|
+
txCtx: mutationTxCtx,
|
|
7302
|
+
nested: nestedRuntime,
|
|
7303
|
+
limits,
|
|
7304
|
+
policies,
|
|
7305
|
+
resolveName: cacheCtx.typeName
|
|
7306
|
+
};
|
|
7307
|
+
forEachTable(ctx, ({ tableName, tableFeatures, names, types, softDeleteInfo }) => {
|
|
7308
|
+
const { insertInput, updateInput, tableFilters } = types.inputs;
|
|
7309
|
+
const { singleTableItemOutput, arrTableItemOutput } = types.outputs;
|
|
7223
7310
|
const { typeName, createArrayFieldName, createSingleFieldName, upsertArrayFieldName, upsertSingleFieldName, updateFieldName, updateManyFieldName, updateSingleFieldName, updateCountFieldName, deleteFieldName, deleteSingleFieldName, restoreFieldName, restoreSingleFieldName, deleteCountFieldName } = names;
|
|
7224
|
-
const
|
|
7225
|
-
|
|
7226
|
-
|
|
7227
|
-
|
|
7311
|
+
const insertArrGenerated = tableFeatures.insert ? generateInsert(writeBuild, {
|
|
7312
|
+
tableName,
|
|
7313
|
+
table: schema[tableName],
|
|
7314
|
+
baseType: insertInput,
|
|
7315
|
+
fieldName: createArrayFieldName,
|
|
7316
|
+
typeName,
|
|
7317
|
+
single: false,
|
|
7318
|
+
conflictDoNothing
|
|
7319
|
+
}) : void 0;
|
|
7320
|
+
const insertSingleGenerated = tableFeatures.insert ? generateInsert(writeBuild, {
|
|
7321
|
+
tableName,
|
|
7322
|
+
table: schema[tableName],
|
|
7323
|
+
baseType: insertInput,
|
|
7324
|
+
fieldName: createSingleFieldName,
|
|
7325
|
+
typeName,
|
|
7326
|
+
single: true,
|
|
7327
|
+
conflictDoNothing
|
|
7328
|
+
}) : void 0;
|
|
7228
7329
|
const uniqueSets = tableFeatures.upsert ? uniqueColumnSets(schema[tableName]) : [];
|
|
7229
7330
|
const onConflictInput = tableFeatures.upsert ? generateOnConflictInput({
|
|
7230
7331
|
table: schema[tableName],
|
|
@@ -7235,10 +7336,46 @@ const createSchemaDataGenerator = (adapter) => {
|
|
|
7235
7336
|
withTarget: true,
|
|
7236
7337
|
cacheCtx
|
|
7237
7338
|
}) : void 0;
|
|
7238
|
-
const upsertArrGenerated = onConflictInput ? generateUpsert(
|
|
7239
|
-
|
|
7240
|
-
|
|
7241
|
-
|
|
7339
|
+
const upsertArrGenerated = onConflictInput ? generateUpsert(writeBuild, {
|
|
7340
|
+
tableName,
|
|
7341
|
+
table: schema[tableName],
|
|
7342
|
+
baseType: insertInput,
|
|
7343
|
+
onConflictType: onConflictInput,
|
|
7344
|
+
uniqueSets,
|
|
7345
|
+
fieldName: upsertArrayFieldName,
|
|
7346
|
+
typeName,
|
|
7347
|
+
single: false
|
|
7348
|
+
}) : void 0;
|
|
7349
|
+
const upsertSingleGenerated = onConflictInput ? generateUpsert(writeBuild, {
|
|
7350
|
+
tableName,
|
|
7351
|
+
table: schema[tableName],
|
|
7352
|
+
baseType: insertInput,
|
|
7353
|
+
onConflictType: onConflictInput,
|
|
7354
|
+
uniqueSets,
|
|
7355
|
+
fieldName: upsertSingleFieldName,
|
|
7356
|
+
typeName,
|
|
7357
|
+
single: true
|
|
7358
|
+
}) : void 0;
|
|
7359
|
+
const updateGenerated = tableFeatures.update ? generateUpdate(writeBuild, {
|
|
7360
|
+
tableName,
|
|
7361
|
+
table: schema[tableName],
|
|
7362
|
+
setArgs: updateInput,
|
|
7363
|
+
filterArgs: tableFilters,
|
|
7364
|
+
fieldName: updateFieldName,
|
|
7365
|
+
typeName,
|
|
7366
|
+
single: false,
|
|
7367
|
+
requireWhere: tableFeatures.requireWhere
|
|
7368
|
+
}) : void 0;
|
|
7369
|
+
const updateSingleGenerated = tableFeatures.update ? generateUpdate(writeBuild, {
|
|
7370
|
+
tableName,
|
|
7371
|
+
table: schema[tableName],
|
|
7372
|
+
setArgs: updateInput,
|
|
7373
|
+
filterArgs: tableFilters,
|
|
7374
|
+
fieldName: updateSingleFieldName,
|
|
7375
|
+
typeName,
|
|
7376
|
+
single: true,
|
|
7377
|
+
requireWhere: tableFeatures.requireWhere
|
|
7378
|
+
}) : void 0;
|
|
7242
7379
|
const updateManyInput = tableFeatures.update && tableFeatures.updateMany ? generateUpdateManyInput({
|
|
7243
7380
|
tableName,
|
|
7244
7381
|
typeName,
|
|
@@ -7247,27 +7384,73 @@ const createSchemaDataGenerator = (adapter) => {
|
|
|
7247
7384
|
tableFilters,
|
|
7248
7385
|
cacheCtx
|
|
7249
7386
|
}) : void 0;
|
|
7250
|
-
const updateManyGenerated = updateManyInput ? adapter.generateUpdateMany(
|
|
7251
|
-
|
|
7387
|
+
const updateManyGenerated = updateManyInput ? adapter.generateUpdateMany(writeBuild, {
|
|
7388
|
+
tableName,
|
|
7389
|
+
table: schema[tableName],
|
|
7390
|
+
updateManyInput,
|
|
7391
|
+
fieldName: updateManyFieldName,
|
|
7392
|
+
typeName
|
|
7393
|
+
}) : void 0;
|
|
7394
|
+
const deleteGenerated = tableFeatures.delete ? generateDelete(writeBuild, {
|
|
7252
7395
|
tableName,
|
|
7253
|
-
|
|
7254
|
-
|
|
7255
|
-
|
|
7256
|
-
|
|
7396
|
+
table: schema[tableName],
|
|
7397
|
+
filterArgs: tableFilters,
|
|
7398
|
+
fieldName: deleteFieldName,
|
|
7399
|
+
typeName,
|
|
7400
|
+
single: false,
|
|
7401
|
+
requireWhere: tableFeatures.requireWhere,
|
|
7402
|
+
restore: false,
|
|
7403
|
+
selectionCtx: {
|
|
7404
|
+
tableName,
|
|
7405
|
+
relationMap: namedRelations,
|
|
7406
|
+
tables
|
|
7407
|
+
}
|
|
7408
|
+
}) : void 0;
|
|
7409
|
+
const deleteSingleGenerated = tableFeatures.delete ? generateDelete(writeBuild, {
|
|
7257
7410
|
tableName,
|
|
7258
|
-
|
|
7259
|
-
|
|
7260
|
-
|
|
7261
|
-
|
|
7411
|
+
table: schema[tableName],
|
|
7412
|
+
filterArgs: tableFilters,
|
|
7413
|
+
fieldName: deleteSingleFieldName,
|
|
7414
|
+
typeName,
|
|
7415
|
+
single: true,
|
|
7416
|
+
requireWhere: tableFeatures.requireWhere,
|
|
7417
|
+
restore: false,
|
|
7418
|
+
selectionCtx: {
|
|
7419
|
+
tableName,
|
|
7420
|
+
relationMap: namedRelations,
|
|
7421
|
+
tables
|
|
7422
|
+
}
|
|
7423
|
+
}) : void 0;
|
|
7424
|
+
const restoreGenerated = softDeleteInfo ? generateDelete(writeBuild, {
|
|
7262
7425
|
tableName,
|
|
7263
|
-
|
|
7264
|
-
|
|
7265
|
-
|
|
7266
|
-
|
|
7426
|
+
table: schema[tableName],
|
|
7427
|
+
filterArgs: tableFilters,
|
|
7428
|
+
fieldName: restoreFieldName,
|
|
7429
|
+
typeName,
|
|
7430
|
+
single: false,
|
|
7431
|
+
requireWhere: tableFeatures.requireWhere,
|
|
7432
|
+
restore: true,
|
|
7433
|
+
selectionCtx: {
|
|
7434
|
+
tableName,
|
|
7435
|
+
relationMap: namedRelations,
|
|
7436
|
+
tables
|
|
7437
|
+
}
|
|
7438
|
+
}) : void 0;
|
|
7439
|
+
const restoreSingleGenerated = softDeleteInfo ? generateDelete(writeBuild, {
|
|
7267
7440
|
tableName,
|
|
7268
|
-
|
|
7269
|
-
|
|
7270
|
-
|
|
7441
|
+
table: schema[tableName],
|
|
7442
|
+
filterArgs: tableFilters,
|
|
7443
|
+
fieldName: restoreSingleFieldName,
|
|
7444
|
+
typeName,
|
|
7445
|
+
single: true,
|
|
7446
|
+
requireWhere: tableFeatures.requireWhere,
|
|
7447
|
+
restore: true,
|
|
7448
|
+
selectionCtx: {
|
|
7449
|
+
tableName,
|
|
7450
|
+
relationMap: namedRelations,
|
|
7451
|
+
tables
|
|
7452
|
+
}
|
|
7453
|
+
}) : void 0;
|
|
7271
7454
|
const updateCountGenerated = tableFeatures.update && tableFeatures.countMutations ? generateWriteCount({
|
|
7272
7455
|
db,
|
|
7273
7456
|
tableName,
|
|
@@ -7292,160 +7475,126 @@ const createSchemaDataGenerator = (adapter) => {
|
|
|
7292
7475
|
filterCtx,
|
|
7293
7476
|
txCtx: mutationTxCtx
|
|
7294
7477
|
}) : void 0;
|
|
7295
|
-
|
|
7296
|
-
|
|
7297
|
-
|
|
7298
|
-
|
|
7299
|
-
|
|
7300
|
-
|
|
7301
|
-
|
|
7302
|
-
|
|
7303
|
-
|
|
7304
|
-
|
|
7305
|
-
|
|
7306
|
-
|
|
7307
|
-
|
|
7308
|
-
|
|
7309
|
-
|
|
7310
|
-
|
|
7311
|
-
|
|
7312
|
-
|
|
7313
|
-
|
|
7314
|
-
|
|
7315
|
-
|
|
7316
|
-
|
|
7317
|
-
|
|
7318
|
-
|
|
7319
|
-
|
|
7320
|
-
|
|
7321
|
-
|
|
7322
|
-
|
|
7323
|
-
|
|
7324
|
-
|
|
7325
|
-
|
|
7326
|
-
|
|
7327
|
-
|
|
7328
|
-
|
|
7329
|
-
|
|
7330
|
-
|
|
7331
|
-
|
|
7332
|
-
|
|
7333
|
-
|
|
7334
|
-
|
|
7335
|
-
|
|
7336
|
-
|
|
7337
|
-
|
|
7338
|
-
|
|
7339
|
-
|
|
7340
|
-
|
|
7341
|
-
|
|
7342
|
-
|
|
7343
|
-
|
|
7344
|
-
|
|
7345
|
-
|
|
7346
|
-
|
|
7347
|
-
|
|
7348
|
-
|
|
7349
|
-
|
|
7350
|
-
|
|
7351
|
-
|
|
7352
|
-
|
|
7353
|
-
|
|
7354
|
-
|
|
7355
|
-
|
|
7356
|
-
|
|
7357
|
-
|
|
7358
|
-
|
|
7359
|
-
|
|
7360
|
-
|
|
7361
|
-
|
|
7362
|
-
|
|
7363
|
-
|
|
7364
|
-
|
|
7365
|
-
|
|
7366
|
-
|
|
7367
|
-
|
|
7368
|
-
|
|
7369
|
-
|
|
7370
|
-
|
|
7371
|
-
|
|
7372
|
-
|
|
7373
|
-
|
|
7374
|
-
|
|
7375
|
-
|
|
7376
|
-
|
|
7377
|
-
|
|
7378
|
-
|
|
7379
|
-
|
|
7380
|
-
|
|
7381
|
-
|
|
7382
|
-
|
|
7383
|
-
|
|
7384
|
-
|
|
7385
|
-
|
|
7386
|
-
|
|
7387
|
-
|
|
7388
|
-
|
|
7389
|
-
|
|
7390
|
-
|
|
7391
|
-
|
|
7392
|
-
|
|
7393
|
-
|
|
7394
|
-
|
|
7395
|
-
|
|
7396
|
-
|
|
7397
|
-
|
|
7398
|
-
|
|
7399
|
-
|
|
7400
|
-
|
|
7401
|
-
|
|
7402
|
-
|
|
7403
|
-
|
|
7404
|
-
|
|
7405
|
-
|
|
7406
|
-
|
|
7407
|
-
|
|
7408
|
-
|
|
7409
|
-
|
|
7410
|
-
|
|
7411
|
-
|
|
7412
|
-
|
|
7413
|
-
|
|
7414
|
-
|
|
7415
|
-
single: true,
|
|
7416
|
-
targetArg: "where"
|
|
7417
|
-
}) }
|
|
7418
|
-
});
|
|
7419
|
-
if (updateCountGenerated) defineRootField(mutations, "mutation", updateCountGenerated.name, {
|
|
7420
|
-
type: new GraphQLNonNull(GraphQLInt),
|
|
7421
|
-
args: updateCountGenerated.args,
|
|
7422
|
-
resolve: updateCountGenerated.resolver,
|
|
7423
|
-
description: "How many rows the update touched. The rows themselves are not read back, which is the point of this mutation."
|
|
7424
|
-
});
|
|
7425
|
-
if (deleteCountGenerated) defineRootField(mutations, "mutation", deleteCountGenerated.name, {
|
|
7426
|
-
type: new GraphQLNonNull(GraphQLInt),
|
|
7427
|
-
args: deleteCountGenerated.args,
|
|
7428
|
-
resolve: deleteCountGenerated.resolver,
|
|
7429
|
-
description: "How many rows the delete removed. The rows themselves are not read back, which is the point of this mutation."
|
|
7430
|
-
});
|
|
7431
|
-
[
|
|
7432
|
-
...tableFeatures.insert || onConflictInput ? [insertInput] : [],
|
|
7433
|
-
...onConflictInput ? [onConflictInput] : [],
|
|
7434
|
-
...tableFeatures.update ? [updateInput] : [],
|
|
7435
|
-
...updateManyInput ? [updateManyInput] : [],
|
|
7436
|
-
tableFilters,
|
|
7437
|
-
tableOrder
|
|
7438
|
-
].forEach((e) => {
|
|
7439
|
-
inputs[e.name] = e;
|
|
7440
|
-
});
|
|
7441
|
-
outputs[selectSingleOutput.name] = selectSingleOutput;
|
|
7442
|
-
outputs[singleTableItemOutput.name] = singleTableItemOutput;
|
|
7443
|
-
if (aggregateType) outputs[aggregateType.name] = aggregateType;
|
|
7444
|
-
if (groupByType && havingInput) {
|
|
7445
|
-
outputs[groupByType.name] = groupByType;
|
|
7446
|
-
inputs[havingInput.name] = havingInput;
|
|
7447
|
-
}
|
|
7448
|
-
}
|
|
7478
|
+
const hardDeleteMeta = softDeleteInfo?.hardDelete ? { hardDelete: true } : {};
|
|
7479
|
+
return {
|
|
7480
|
+
mutations: [
|
|
7481
|
+
{
|
|
7482
|
+
generated: insertArrGenerated,
|
|
7483
|
+
type: arrTableItemOutput,
|
|
7484
|
+
meta: {
|
|
7485
|
+
operation: "insert",
|
|
7486
|
+
single: false,
|
|
7487
|
+
targetArg: "values"
|
|
7488
|
+
}
|
|
7489
|
+
},
|
|
7490
|
+
{
|
|
7491
|
+
generated: insertSingleGenerated,
|
|
7492
|
+
type: conflictDoNothing ? singleTableItemOutput : new GraphQLNonNull(singleTableItemOutput),
|
|
7493
|
+
meta: {
|
|
7494
|
+
operation: "insert",
|
|
7495
|
+
single: true,
|
|
7496
|
+
targetArg: "values"
|
|
7497
|
+
}
|
|
7498
|
+
},
|
|
7499
|
+
{
|
|
7500
|
+
generated: upsertArrGenerated,
|
|
7501
|
+
type: arrTableItemOutput,
|
|
7502
|
+
meta: {
|
|
7503
|
+
operation: "upsert",
|
|
7504
|
+
single: false,
|
|
7505
|
+
targetArg: "values"
|
|
7506
|
+
}
|
|
7507
|
+
},
|
|
7508
|
+
{
|
|
7509
|
+
generated: upsertSingleGenerated,
|
|
7510
|
+
type: singleTableItemOutput,
|
|
7511
|
+
meta: {
|
|
7512
|
+
operation: "upsert",
|
|
7513
|
+
single: true,
|
|
7514
|
+
targetArg: "values"
|
|
7515
|
+
}
|
|
7516
|
+
},
|
|
7517
|
+
{
|
|
7518
|
+
generated: updateGenerated,
|
|
7519
|
+
type: arrTableItemOutput,
|
|
7520
|
+
meta: {
|
|
7521
|
+
operation: "update",
|
|
7522
|
+
single: false,
|
|
7523
|
+
targetArg: "where"
|
|
7524
|
+
}
|
|
7525
|
+
},
|
|
7526
|
+
{
|
|
7527
|
+
generated: updateManyGenerated,
|
|
7528
|
+
type: new GraphQLNonNull(new GraphQLList(singleTableItemOutput)),
|
|
7529
|
+
meta: {
|
|
7530
|
+
operation: "updateMany",
|
|
7531
|
+
single: false,
|
|
7532
|
+
targetArg: "updates"
|
|
7533
|
+
},
|
|
7534
|
+
description: "Each entry's updated rows, in entry order. An entry whose `where` matched no rows contributes `null` in its slot; an entry that matched several contributes each of its rows."
|
|
7535
|
+
},
|
|
7536
|
+
{
|
|
7537
|
+
generated: updateSingleGenerated,
|
|
7538
|
+
type: singleTableItemOutput,
|
|
7539
|
+
meta: {
|
|
7540
|
+
operation: "update",
|
|
7541
|
+
single: true,
|
|
7542
|
+
targetArg: "where"
|
|
7543
|
+
}
|
|
7544
|
+
},
|
|
7545
|
+
{
|
|
7546
|
+
generated: deleteGenerated,
|
|
7547
|
+
type: arrTableItemOutput,
|
|
7548
|
+
meta: {
|
|
7549
|
+
operation: "delete",
|
|
7550
|
+
single: false,
|
|
7551
|
+
targetArg: "where",
|
|
7552
|
+
...hardDeleteMeta
|
|
7553
|
+
}
|
|
7554
|
+
},
|
|
7555
|
+
{
|
|
7556
|
+
generated: deleteSingleGenerated,
|
|
7557
|
+
type: singleTableItemOutput,
|
|
7558
|
+
meta: {
|
|
7559
|
+
operation: "delete",
|
|
7560
|
+
single: true,
|
|
7561
|
+
targetArg: "where",
|
|
7562
|
+
...hardDeleteMeta
|
|
7563
|
+
}
|
|
7564
|
+
},
|
|
7565
|
+
{
|
|
7566
|
+
generated: restoreGenerated,
|
|
7567
|
+
type: arrTableItemOutput,
|
|
7568
|
+
meta: {
|
|
7569
|
+
operation: "restore",
|
|
7570
|
+
single: false,
|
|
7571
|
+
targetArg: "where"
|
|
7572
|
+
}
|
|
7573
|
+
},
|
|
7574
|
+
{
|
|
7575
|
+
generated: restoreSingleGenerated,
|
|
7576
|
+
type: singleTableItemOutput,
|
|
7577
|
+
meta: {
|
|
7578
|
+
operation: "restore",
|
|
7579
|
+
single: true,
|
|
7580
|
+
targetArg: "where"
|
|
7581
|
+
}
|
|
7582
|
+
},
|
|
7583
|
+
{
|
|
7584
|
+
generated: updateCountGenerated,
|
|
7585
|
+
type: new GraphQLNonNull(GraphQLInt),
|
|
7586
|
+
description: "How many rows the update touched. The rows themselves are not read back, which is the point of this mutation."
|
|
7587
|
+
},
|
|
7588
|
+
{
|
|
7589
|
+
generated: deleteCountGenerated,
|
|
7590
|
+
type: new GraphQLNonNull(GraphQLInt),
|
|
7591
|
+
description: "How many rows the delete removed. The rows themselves are not read back, which is the point of this mutation."
|
|
7592
|
+
}
|
|
7593
|
+
],
|
|
7594
|
+
onConflictInput,
|
|
7595
|
+
updateManyInput
|
|
7596
|
+
};
|
|
7597
|
+
});
|
|
7449
7598
|
return finalizeBuild(ctx);
|
|
7450
7599
|
};
|
|
7451
7600
|
};
|
|
@@ -7487,7 +7636,9 @@ const runUpdateManyBatch = ({ executor, entries, table, returning, nested, table
|
|
|
7487
7636
|
* @param runBatch how the entries execute; defaults to {@link runUpdateManyBatch}
|
|
7488
7637
|
*/
|
|
7489
7638
|
const createUpdateManyGenerator = (primaryKeyPropNames, runBatch = runUpdateManyBatch) => {
|
|
7490
|
-
return (
|
|
7639
|
+
return (build, opts) => {
|
|
7640
|
+
const { db, tables, relationMap, typeNameMapper, filterCtx, txCtx, nested, limits, policies, resolveName } = build;
|
|
7641
|
+
const { tableName, table, updateManyInput, fieldName, typeName } = opts;
|
|
7491
7642
|
const queryArgs = { updates: { type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(updateManyInput))) } };
|
|
7492
7643
|
const pkNames = primaryKeyPropNames(table);
|
|
7493
7644
|
return writeResolver({
|
|
@@ -7505,8 +7656,7 @@ const createUpdateManyGenerator = (primaryKeyPropNames, runBatch = runUpdateMany
|
|
|
7505
7656
|
await before();
|
|
7506
7657
|
const scope = policies?.scope?.(context);
|
|
7507
7658
|
const contextColumns = policies?.contextValues?.(tableName);
|
|
7508
|
-
const
|
|
7509
|
-
const { columns, hasRelations, withParams } = prepareMutationRelationColumns({
|
|
7659
|
+
const { columns, hasRelations, withParams } = mutationSelection(info, {
|
|
7510
7660
|
relationMap,
|
|
7511
7661
|
tables,
|
|
7512
7662
|
tableName,
|
|
@@ -7515,7 +7665,6 @@ const createUpdateManyGenerator = (primaryKeyPropNames, runBatch = runUpdateMany
|
|
|
7515
7665
|
resolveName,
|
|
7516
7666
|
table,
|
|
7517
7667
|
pkNames,
|
|
7518
|
-
parsedInfo,
|
|
7519
7668
|
limits,
|
|
7520
7669
|
scope,
|
|
7521
7670
|
defaultOrderBy: policies?.defaultOrderBy
|
|
@@ -7532,11 +7681,12 @@ const createUpdateManyGenerator = (primaryKeyPropNames, runBatch = runUpdateMany
|
|
|
7532
7681
|
};
|
|
7533
7682
|
});
|
|
7534
7683
|
const anyNested = entries.some((entry) => entry.ops);
|
|
7684
|
+
const returning = anyNested ? nested.withJoinColumns(tableName, mergedOps(entries.map((entry) => ({ ops: entry.ops ?? {} }))), { ...columns }, table) : columns;
|
|
7535
7685
|
const perEntry = await runBatch({
|
|
7536
7686
|
executor,
|
|
7537
7687
|
entries,
|
|
7538
7688
|
table,
|
|
7539
|
-
returning
|
|
7689
|
+
returning,
|
|
7540
7690
|
anyNested,
|
|
7541
7691
|
nested,
|
|
7542
7692
|
tableName,
|
|
@@ -7544,7 +7694,7 @@ const createUpdateManyGenerator = (primaryKeyPropNames, runBatch = runUpdateMany
|
|
|
7544
7694
|
});
|
|
7545
7695
|
const flatRows = perEntry.flat();
|
|
7546
7696
|
await after(flatRows);
|
|
7547
|
-
const enriched =
|
|
7697
|
+
const enriched = await withEagerRelations(executor, tableName, flatRows, pkNames, withParams, hasRelations);
|
|
7548
7698
|
const output = [];
|
|
7549
7699
|
let offset = 0;
|
|
7550
7700
|
for (const rows of perEntry) {
|