diff --git a/.changeset/tame-jokes-drum.md b/.changeset/tame-jokes-drum.md new file mode 100644 index 0000000000..b07463cc28 --- /dev/null +++ b/.changeset/tame-jokes-drum.md @@ -0,0 +1,13 @@ +--- +"@dataplan/pg": minor +"graphile-build-pg": minor +"postgraphile": minor +--- + +Add support for PostgreSQL PROCEDUREs (introduced in Postgres 11). Procedures +are invoked via `call proc(...)` rather than being embedded in a `select`, so +they're now exposed as mutation fields backed by a new `PgCallStep`/`pgCall()` +step in `@dataplan/pg`, rather than reusing the `select`-based mechanism used +for functions. Procedures with `OUT`/`INOUT` parameters return their values as a +nested record on the mutation payload, matching the existing behaviour for +functions that return multiple columns. diff --git a/.github/styles/config/vocabularies/Graphile/accept.txt b/.github/styles/config/vocabularies/Graphile/accept.txt index 53d252ec13..1d7c5f8383 100644 --- a/.github/styles/config/vocabularies/Graphile/accept.txt +++ b/.github/styles/config/vocabularies/Graphile/accept.txt @@ -1,5 +1,6 @@ tamedevil performant +[Vv]ariadic substring autofix ESLint diff --git a/grafast/dataplan-pg/src/adaptors/pg.ts b/grafast/dataplan-pg/src/adaptors/pg.ts index 12e08d346b..70c53ad440 100644 --- a/grafast/dataplan-pg/src/adaptors/pg.ts +++ b/grafast/dataplan-pg/src/adaptors/pg.ts @@ -164,10 +164,17 @@ function newNodePostgresPgClient( return doIt(); } function doIt() { - const { text, name, values, arrayMode } = opts; + const { text, name, values, arrayMode, rawText } = opts; const queryObj: QueryConfig | QueryArrayConfig = arrayMode ? { text, values, rowMode: "array" } : { text, values }; + if (rawText) { + // Some statements (e.g. `CALL`) return columns whose types we + // cannot wrap in an explicit `::text` cast; force the driver to + // hand back raw strings for every column instead of applying its + // own (potentially codec-incompatible) type parsing. + queryObj.types = { getTypeParser: () => (raw: string) => raw }; + } if (PREPARED_STATEMENT_CACHE_SIZE > 0 && name != null) { // Hacking into pgClient internals - this is dangerous, but it's the only way I know to get a prepared statement LRU diff --git a/grafast/dataplan-pg/src/datasource.ts b/grafast/dataplan-pg/src/datasource.ts index 56709a5f7b..3769b524e8 100644 --- a/grafast/dataplan-pg/src/datasource.ts +++ b/grafast/dataplan-pg/src/datasource.ts @@ -41,6 +41,7 @@ import type { PgRegistryConfig, PlanByUniques, } from "./interfaces.ts"; +import { pgCall } from "./steps/pgCall.ts"; import type { PgClassExpressionStep } from "./steps/pgClassExpression.ts"; import type { PgSelectArgumentDigest, @@ -221,6 +222,25 @@ export interface PgResourceOptions< isUnique?: boolean; sqlPartitionByIndex?: SQL; isMutation?: boolean; + /** + * If true, this resource represents a PostgreSQL PROCEDURE (as opposed to + * a FUNCTION) and must be invoked via a `CALL` statement rather than being + * embedded in a `SELECT ... FROM`. Implies `isMutation`. + */ + isProcedure?: boolean; + /** + * For procedure resources only: describes *every* positional argument the + * underlying PostgreSQL procedure accepts, including OUT-only arguments - + * in declaration order. PostgreSQL's `CALL` statement (unlike a function + * call) requires a value to be supplied for every positional argument, + * including those that are OUT-only (where the value is ignored), so we + * need this to build a valid `CALL` statement even though OUT-only + * arguments aren't exposed to GraphQL via `parameters`. + */ + procedureArguments?: ReadonlyArray<{ + mode: "i" | "o" | "b"; + codec: PgCodec; + }>; hasImplicitOrder?: boolean; /** * If true, this indicates that this was originally a list (array) and thus @@ -255,6 +275,11 @@ export interface PgFunctionResourceOptions< uniques?: TUniques; extensions?: DataplanPg.PgResourceExtensions; isMutation?: boolean; + isProcedure?: boolean; + procedureArguments?: ReadonlyArray<{ + mode: "i" | "o" | "b"; + codec: PgCodec; + }>; hasImplicitOrder?: boolean; selectAuth?: | (($step: PgSelectStep>) => void) @@ -307,6 +332,10 @@ export class PgResource< public readonly description: string | undefined; public readonly isUnique: boolean; public readonly isMutation: boolean; + public readonly isProcedure: boolean; + public readonly procedureArguments: + | ReadonlyArray<{ mode: "i" | "o" | "b"; codec: PgCodec }> + | undefined; public readonly hasImplicitOrder: boolean; /** * If true, this indicates that this was originally a list (array) and thus @@ -345,6 +374,8 @@ export class PgResource< isUnique, sqlPartitionByIndex, isMutation, + isProcedure, + procedureArguments, hasImplicitOrder, selectAuth, isList, @@ -362,7 +393,9 @@ export class PgResource< this.description = description; this.isUnique = !!isUnique; this.sqlPartitionByIndex = sqlPartitionByIndex ?? null; - this.isMutation = !!isMutation; + this.isProcedure = !!isProcedure; + this.isMutation = !!isMutation || this.isProcedure; + this.procedureArguments = procedureArguments; this.hasImplicitOrder = hasImplicitOrder ?? false; this.isList = !!isList; this.isVirtual = isVirtual ?? false; @@ -466,6 +499,8 @@ export class PgResource< uniques, extensions, isMutation, + isProcedure, + procedureArguments, hasImplicitOrder, selectAuth: overrideSelectAuth, description, @@ -487,6 +522,8 @@ export class PgResource< extensions, isUnique: !returnsSetof, isMutation: Boolean(isMutation), + isProcedure: Boolean(isProcedure), + procedureArguments, hasImplicitOrder, selectAuth, description, @@ -756,6 +793,9 @@ export class PgResource< args: ReadonlyArray = [], mode: PgSelectMode = this.isMutation ? "mutation" : "normal", ): ExecutableStep { + if (this.isProcedure) { + return pgCall({ resource: this, args }); + } const $select = pgSelect({ resource: this, identifiers: [], diff --git a/grafast/dataplan-pg/src/executor.ts b/grafast/dataplan-pg/src/executor.ts index ef5e3c4621..1edea9eeb9 100644 --- a/grafast/dataplan-pg/src/executor.ts +++ b/grafast/dataplan-pg/src/executor.ts @@ -60,6 +60,13 @@ export interface PgClientQuery { arrayMode?: boolean; /** For prepared statements */ name?: string; + /** + * Forces every column of the result to be returned as a raw (undecoded) + * string, bypassing the client library's usual type-aware parsing. + * Required for statements (e.g. `CALL`) whose result columns cannot be + * wrapped in an explicit `::text` cast. + */ + rawText?: boolean; } export type PgRaiseSeverity = "DEBUG" | "LOG" | "INFO" | "NOTICE" | "WARNING"; @@ -164,6 +171,14 @@ export type PgExecutorMutationOptions = { context: PgExecutorContext; text: string; values: ReadonlyArray; + /** + * Statements such as `CALL` return their columns using Postgres' native + * wire-protocol type parsing rather than the `::text` casts we normally + * use to get consistent string representations for our codecs to decode. + * Setting this forces every column of the result to be returned as a raw + * string so it can be fed through the relevant `PgCodec`'s `fromPg`. + */ + rawText?: boolean; }; export type PgExecutorSubscribeOptions = { @@ -209,6 +224,7 @@ export class PgExecutor { name?: string, publish?: PublishFunction, isMutation = false, + rawText = false, ): Promise> { let queryResult: PgClientResult | null = null, error: any = null; @@ -219,6 +235,7 @@ export class PgExecutor { values: values as SQLRawValue[], arrayMode: true, name, + ...(rawText ? { rawText: true } : null), }); } catch (e) { error = e; @@ -938,7 +955,7 @@ ${duration} public async executeMutation( options: PgExecutorMutationOptions, ): Promise> { - const { context, text, values } = options; + const { context, text, values, rawText } = options; const { withPgClient, pgSettings } = context; // We don't explicitly need a transaction for mutations @@ -950,6 +967,7 @@ ${duration} undefined, undefined, true, + rawText, ), ); // PERF: we could probably make this more efficient rather than blowing away the entire cache! diff --git a/grafast/dataplan-pg/src/index.ts b/grafast/dataplan-pg/src/index.ts index c72b2445ad..08cd18d44b 100644 --- a/grafast/dataplan-pg/src/index.ts +++ b/grafast/dataplan-pg/src/index.ts @@ -125,6 +125,8 @@ import { withSuperuserPgClientFromPgService, } from "./pgServices.ts"; import { PgContextPlugin } from "./plugins/PgContextPlugin.ts"; +import type { PgCallQueryBuilder } from "./steps/pgCall.ts"; +import { pgCall, PgCallStep } from "./steps/pgCall.ts"; import { pgClassExpression, PgClassExpressionStep, @@ -220,6 +222,7 @@ export type { ObjectFromPgCodecAttributes, PgAdaptor, PgBox, + PgCallQueryBuilder, PgCircle, PgClassSingleStep, PgClient, @@ -332,6 +335,8 @@ export { makeRegistry, makeRegistryBuilder, PgBooleanFilter, + pgCall, + PgCallStep, pgClassExpression, PgClassExpressionStep, PgClassFilter, @@ -406,6 +411,8 @@ exportAsMany("@dataplan/pg", { PgOrFilter, pgClassExpression, PgClassExpressionStep, + pgCall, + PgCallStep, PgCondition, pgWhereConditionSpecListToSQL, PgCursorStep, diff --git a/grafast/dataplan-pg/src/interfaces.ts b/grafast/dataplan-pg/src/interfaces.ts index b0281b5ac5..361d11f18f 100644 --- a/grafast/dataplan-pg/src/interfaces.ts +++ b/grafast/dataplan-pg/src/interfaces.ts @@ -11,6 +11,7 @@ import type { PgResourceUnique, } from "./datasource.ts"; import type { PgExecutor } from "./executor.ts"; +import type { PgCallStep } from "./steps/pgCall.ts"; import type { PgDeleteSingleStep } from "./steps/pgDeleteSingle.ts"; import type { PgInsertSingleStep } from "./steps/pgInsertSingle.ts"; import type { PgSelectQueryBuilder } from "./steps/pgSelect.ts"; @@ -29,7 +30,8 @@ export type PgClassSingleStep< | PgSelectSingleStep | PgInsertSingleStep | PgUpdateSingleStep - | PgDeleteSingleStep; + | PgDeleteSingleStep + | PgCallStep; /** * Given a value of type TInput, returns an `SQL` value to insert into an SQL diff --git a/grafast/dataplan-pg/src/steps/pgCall.ts b/grafast/dataplan-pg/src/steps/pgCall.ts new file mode 100644 index 0000000000..82cb7a1130 --- /dev/null +++ b/grafast/dataplan-pg/src/steps/pgCall.ts @@ -0,0 +1,266 @@ +import type { + ExecutionDetails, + GrafastResultsList, + PromiseOrDirect, +} from "grafast"; +import { access, exportAs, Step } from "grafast"; +import type { SQL } from "pg-sql2"; +import sql from "pg-sql2"; + +import type { PgCodecAttribute } from "../codecs.ts"; +import { sqlValueWithCodec } from "../codecs.ts"; +import type { PgResource } from "../datasource.ts"; +import type { PgCodec } from "../interfaces.ts"; +import type { PgClassExpressionStep } from "./pgClassExpression.ts"; +import { pgClassExpression } from "./pgClassExpression.ts"; +import type { + PgSelectArgumentDigest, + PgSelectArgumentSpec, +} from "./pgSelect.ts"; + +/** + * Invokes a PostgreSQL PROCEDURE via a `call` statement. + * + * A function is embedded in a `select ... from func(...)`, so it can take + * part in the wider query: joins, filters, inlining, and so on. A procedure + * has none of that. It can only be invoked as a standalone `call proc(...)` + * statement, and PostgreSQL requires every one of its positional arguments + * to be supplied, including OUT-only arguments (whose value is then + * discarded). The `call` statement returns, at most, a single row + * containing the procedure's OUT/INOUT parameters, named accordingly. + */ +export class PgCallStep< + TResource extends PgResource = PgResource, +> extends Step { + static $$export = { + moduleName: "@dataplan/pg", + exportName: "PgCallStep", + }; + + isSyncAndSafe = false; + + public readonly resource: TResource; + + /** + * Used as an alias within `get()`-built expressions. It never appears in + * any SQL we actually execute (see `selectAndReturnIndex` below); it only + * needs to be unique so expressions for different `PgCallStep`s can't be + * confused with one another. + */ + public readonly alias: SQL; + private readonly symbol: symbol; + + private readonly contextId: number; + + /** One entry per argument exposed to us (i.e. `i`/`b` mode only). */ + private readonly argDeps: ReadonlyArray<{ + depId: number; + pgCodec: PgCodec; + }>; + + /** + * The fixed position each output attribute occupies within the row `call` + * returns, alongside the `::text`-cast expression `PgClassExpressionStep` + * would build for it. This is what lets us resolve `selectAndReturnIndex()` + * without ever compiling or running that expression as real SQL (`call` + * can't be wrapped in a `select`, so there's nothing to select it *from*). + */ + private readonly outputsByFragmentText: ReadonlyArray<{ + fragment: SQL; + index: number; + }>; + + private applyDepIds: number[] = []; + + constructor(resource: TResource, args: ReadonlyArray) { + super(); + this.resource = resource; + this.symbol = Symbol(resource.name); + this.alias = sql.identifier(this.symbol); + this.contextId = this.addDependency(resource.executor.context()); + this.argDeps = args.map((spec) => ({ + depId: this.addDependency(spec.step), + pgCodec: (spec.pgCodec ?? (spec.step as any).pgCodec) as PgCodec, + })); + + const attributes = resource.codec.attributes as + | Record + | undefined; + this.outputsByFragmentText = attributes + ? Object.entries(attributes).map(([name, attribute], index) => { + const expression = sql`${this.alias}.${sql.identifier(name)}`; + // Mirrors `pgClassExpression()` + `PgClassExpressionStep.optimize()` + // exactly, since we must produce a fragment that's equivalent to + // whatever they'll ask `selectAndReturnIndex()` to resolve. + const guaranteedNotNull = + attribute.codec.notNull || attribute.notNull; + const fragment = attribute.codec.castFromPg + ? attribute.codec.castFromPg(expression, guaranteedNotNull) + : sql`${sql.parens(expression)}::text`; + return { fragment, index }; + }) + : []; + + // This must happen last + this.hasSideEffects = true; + } + + public toStringMeta(): string | null { + return this.resource.name; + } + + /** + * Used to allow other plugins (e.g. `clientMutationId` handling) to stash + * metadata against this call. This matches the `ApplyableStep` protocol, + * so we can be used anywhere a `PgSelectStep` would be for this purpose. + */ + apply($step: Step<(qb: PgCallQueryBuilder) => void>) { + this.applyDepIds.push(this.addUnaryDependency($step)); + } + + public getMeta(key: string) { + return access(this, ["m", key]); + } + + public getNotices() { + return access(this, "n"); + } + + __inferGet?: { + [TAttr in keyof NonNullable< + TResource["codec"]["attributes"] + >]: PgClassExpressionStep< + NonNullable[TAttr]["codec"], + TResource + >; + }; + /** + * Returns a plan representing a named OUT/INOUT parameter from the + * procedure's result row. + */ + get( + attr: TAttr, + ): PgClassExpressionStep { + const attribute = this.resource.codec.attributes?.[attr]; + if (!attribute) { + throw new Error( + `${this.resource} does not define an output attribute named '${attr}'`, + ); + } + const sqlExpr = pgClassExpression( + this, + attribute.codec, + attribute.notNull, + ); + return sqlExpr`${this.alias}.${sql.identifier(attr)}`; + } + + /** + * `call` cannot appear in a `select`, so compiling and running `fragment` + * directly isn't an option. Instead we match it against the fixed, + * precomputed expression for each output attribute and return that + * attribute's (equally fixed) position in the row `call` returns. + */ + public selectAndReturnIndex(fragment: SQL): number { + const found = this.outputsByFragmentText.find((entry) => + sql.isEquivalent(entry.fragment, fragment), + ); + if (!found) { + throw new Error( + `${this}: could not resolve output position for expression ${sql.compile(fragment).text}`, + ); + } + return found.index; + } + + async execute({ + indexMap, + values, + }: ExecutionDetails): Promise> { + const { resource, contextId } = this; + const contextDep = values[contextId]; + const procedureArguments = resource.procedureArguments ?? []; + + return indexMap>(async (i) => { + const context = contextDep.at(i); + + const argValues = this.argDeps.map(({ depId, pgCodec }) => { + const raw = values[depId].at(i); + return { pgCodec, value: raw }; + }); + + let nextArgIndex = 0; + const digests: PgSelectArgumentDigest[] = procedureArguments.map( + (arg) => { + if (arg.mode === "o") { + return { placeholder: sql`null::${arg.codec.sqlType}` }; + } + const argValue = argValues[nextArgIndex++]; + return { + placeholder: sqlValueWithCodec(argValue.value, argValue.pgCodec), + }; + }, + ); + + const meta = Object.create(null); + const queryBuilder: PgCallQueryBuilder = { + setMeta(key, value) { + meta[key] = value; + }, + getMetaRaw(key) { + return meta[key]; + }, + }; + for (const applyDepId of this.applyDepIds) { + const callback = values[applyDepId].unaryValue() as + | ((qb: PgCallQueryBuilder) => void) + | null + | undefined; + callback?.(queryBuilder); + } + + const from = resource.from as (...args: PgSelectArgumentDigest[]) => SQL; + const query = sql`call ${from(...digests)};`; + const { text, values: stmtValues } = sql.compile(query); + + const { rows, notices } = await this.resource.executeMutation< + ReadonlyArray + >({ + context, + text, + values: stmtValues, + rawText: true, + }); + return { + __proto__: null, + m: meta, + t: rows[0] ?? [], + n: notices, + }; + }); + } +} + +interface PgCallStepResult { + m: Record; + t: ReadonlyArray; + n: readonly unknown[] | undefined; +} + +export interface PgCallQueryBuilder { + setMeta(key: string, value: unknown): void; + getMetaRaw(key: string): unknown; +} + +/** + * Invokes a PostgreSQL PROCEDURE via `call proc(...)`. + */ +export function pgCall< + TResource extends PgResource, +>(options: { + resource: TResource; + args?: ReadonlyArray; +}): PgCallStep { + return new PgCallStep(options.resource, options.args ?? []); +} +exportAs("@dataplan/pg", pgCall, "pgCall"); diff --git a/grafast/dataplan-pg/src/steps/pgClassExpression.ts b/grafast/dataplan-pg/src/steps/pgClassExpression.ts index 1c148da77e..3dcf051b37 100644 --- a/grafast/dataplan-pg/src/steps/pgClassExpression.ts +++ b/grafast/dataplan-pg/src/steps/pgClassExpression.ts @@ -10,6 +10,7 @@ import type { PgCodec, PgTypedStep, } from "../interfaces.ts"; +import { PgCallStep } from "./pgCall.ts"; import { PgDeleteSingleStep } from "./pgDeleteSingle.ts"; import { PgInsertSingleStep } from "./pgInsertSingle.ts"; import { PgSelectSingleStep } from "./pgSelectSingle.ts"; @@ -78,7 +79,8 @@ export class PgClassExpressionStep< this.needsTupleAccess = $table instanceof PgInsertSingleStep || $table instanceof PgUpdateSingleStep || - $table instanceof PgDeleteSingleStep; + $table instanceof PgDeleteSingleStep || + $table instanceof PgCallStep; const $row = this.needsTupleAccess ? access($table, "t") : $table; this.rowDependencyId = this.addDependency($row); if (strings.length !== dependencies.length + 1) { @@ -244,10 +246,11 @@ export class PgClassExpressionStep< !(step instanceof PgInsertSingleStep) && !(step instanceof PgUpdateSingleStep) && !(step instanceof PgDeleteSingleStep) && - !(step instanceof PgUnionAllSingleStep) + !(step instanceof PgUnionAllSingleStep) && + !(step instanceof PgCallStep) ) { throw new Error( - `Expected ${step} to be a PgSelectSingleStep | PgInsertSingleStep | PgUpdateSingleStep | PgDeleteSingleStep | PgUnionAllSingleStep`, + `Expected ${step} to be a PgSelectSingleStep | PgInsertSingleStep | PgUpdateSingleStep | PgDeleteSingleStep | PgUnionAllSingleStep | PgCallStep`, ); } return step; diff --git a/grafast/dataplan-pg/src/utils.ts b/grafast/dataplan-pg/src/utils.ts index 9edf85577f..d178529354 100644 --- a/grafast/dataplan-pg/src/utils.ts +++ b/grafast/dataplan-pg/src/utils.ts @@ -13,6 +13,7 @@ import type { PgSQLCallbackOrDirect, PgTypedStep, } from "./interfaces.ts"; +import { PgCallStep } from "./steps/pgCall.ts"; import { PgDeleteSingleStep } from "./steps/pgDeleteSingle.ts"; import { PgInsertSingleStep } from "./steps/pgInsertSingle.ts"; import { PgSelectSingleStep } from "./steps/pgSelectSingle.ts"; @@ -28,11 +29,12 @@ export function assertPgClassSingleStep< step instanceof PgSelectSingleStep || step instanceof PgInsertSingleStep || step instanceof PgUpdateSingleStep || - step instanceof PgDeleteSingleStep + step instanceof PgDeleteSingleStep || + step instanceof PgCallStep ) ) { throw new Error( - `Expected a PgSelectSingleStep, PgInsertSingleStep, PgUpdateSingleStep or PgDeleteSingleStep, however we received '${step}'.`, + `Expected a PgSelectSingleStep, PgInsertSingleStep, PgUpdateSingleStep, PgDeleteSingleStep or PgCallStep, however we received '${step}'.`, ); } } diff --git a/graphile-build/graphile-build-pg/src/plugins/PgCustomTypeFieldPlugin.ts b/graphile-build/graphile-build-pg/src/plugins/PgCustomTypeFieldPlugin.ts index 4e95b73af8..1755bf3632 100644 --- a/graphile-build/graphile-build-pg/src/plugins/PgCustomTypeFieldPlugin.ts +++ b/graphile-build/graphile-build-pg/src/plugins/PgCustomTypeFieldPlugin.ts @@ -18,6 +18,7 @@ import type { } from "@dataplan/pg"; import { generatePgParameterAnalysis, + PgCallStep, pgClassExpression, pgFromExpression, pgSelectSingleFromRecord, @@ -309,16 +310,24 @@ declare global { } const pgSelectFromPayload = EXPORTABLE( - (PgSelectStep) => + (PgCallStep, PgSelectStep) => function pgSelectFromPayload( $payload: ObjectStep<{ result: | PgSelectStep | PgSelectSingleStep - | PgClassExpressionStep; + | PgClassExpressionStep + | PgCallStep; }>, ) { const $result = $payload.getStepForKey("result"); + if ($result instanceof PgCallStep) { + // Procedures are invoked via `call`, not `select`, so there's no + // `PgSelectStep` to find. The call step itself is the target that + // nested `apply`-capable input fields (e.g. `clientMutationId`) + // should be applied to. + return $result; + } const $parent = "getParentStep" in $result ? ($result.getParentStep() as PgSelectSingleStep) @@ -331,7 +340,7 @@ const pgSelectFromPayload = EXPORTABLE( throw new Error(`Could not determine PgSelectStep for ${$result}`); } }, - [PgSelectStep], + [PgCallStep, PgSelectStep], "pgSelectFromPayload", ); @@ -343,12 +352,17 @@ const applyInputArgViaPgSelect = EXPORTABLE( result: | PgSelectStep | PgSelectSingleStep - | PgClassExpressionStep; + | PgClassExpressionStep + | PgCallStep; }>, arg: FieldArg, ) { const $pgSelect = pgSelectFromPayload($payload); - arg.apply($pgSelect); + // `PgCallStep`'s `apply()` takes a `PgCallQueryBuilder` rather than a + // `PgSelectQueryBuilder`, but both satisfy the same structural `apply` + // protocol at runtime (only `clientMutationId` uses it here), so the + // precise `qb` type is immaterial to the caller. + arg.apply($pgSelect as any); }, [pgSelectFromPayload], "applyInputArgViaPgSelect", diff --git a/graphile-build/graphile-build-pg/src/plugins/PgProceduresPlugin.ts b/graphile-build/graphile-build-pg/src/plugins/PgProceduresPlugin.ts index c19e473a34..351dc93c43 100644 --- a/graphile-build/graphile-build-pg/src/plugins/PgProceduresPlugin.ts +++ b/graphile-build/graphile-build-pg/src/plugins/PgProceduresPlugin.ts @@ -191,9 +191,21 @@ export const PgProceduresPlugin: GraphileConfig.Plugin = { */ const allArgTypes = pgProc.proallargtypes ?? pgProc.proargtypes ?? []; + /** + * PostgreSQL PROCEDUREs (as opposed to FUNCTIONs) can only be + * invoked via a `call` statement, never embedded in a `select`. + */ + const isProcedure = pgProc.prokind === "p"; + /** * If there's two or more OUT or inout arguments INOUT, or any TABLE * arguments then we'll need to generate a codec for the payload. + * + * PROCEDUREs are a special case. PostgreSQL always reports their + * return type as `record` as soon as they have *any* OUT/INOUT + * argument, even just one. FUNCTIONs only do this once there's two + * or more, so for procedures we must generate the payload codec + * starting from a single OUT/INOUT argument. */ const outOrInoutOrTableArgModes = pgProc.proargmodes?.filter( @@ -201,8 +213,9 @@ export const PgProceduresPlugin: GraphileConfig.Plugin = { ) ?? []; const isRecordReturnType = pgProc.prorettype === "2249"; /* OID of the 'record' type */ - const needsPayloadCodecToBeGenerated = - outOrInoutOrTableArgModes.length > 1; + const needsPayloadCodecToBeGenerated = isProcedure + ? outOrInoutOrTableArgModes.length >= 1 + : outOrInoutOrTableArgModes.length > 1; const debugProcName = `${namespace.nspname}.${pgProc.proname}`; @@ -211,6 +224,12 @@ export const PgProceduresPlugin: GraphileConfig.Plugin = { return null; } + if (isProcedure && outOrInoutOrTableArgModes.includes("t")) { + // PostgreSQL doesn't support `RETURNS TABLE` for procedures, but + // just in case, we don't support it either. + return null; + } + const executor = info.helpers.pgIntrospection.getExecutorForService(serviceName); @@ -318,6 +337,17 @@ export const PgProceduresPlugin: GraphileConfig.Plugin = { const rawParameters: PgResourceParameter[] = []; + /** + * For procedures only: *every* positional argument (including + * OUT-only ones), in declaration order. PostgreSQL's `call` + * statement requires a value for every positional argument, even + * OUT-only ones (whose value is discarded), unlike a function call. + */ + const procedureArguments: Array<{ + mode: "i" | "o" | "b"; + codec: PgCodec; + }> = []; + // const processedFirstInputArg = false; // "v" is for "volatile"; but let's just say anything that's not @@ -404,6 +434,27 @@ export const PgProceduresPlugin: GraphileConfig.Plugin = { ...(variant ? { extensions: { variant } } : null), }), ); + if (isProcedure) { + procedureArguments.push({ mode: argMode, codec: argCodec }); + } + } else if (isProcedure && argMode === "o") { + const argCodec = await info.helpers.pgCodecs.getCodecFromType( + serviceName, + argType, + typeModifier, + ); + if (!argCodec) { + console.warn( + `Could not make codec for '${debugProcName}' argument '${argName}' which has type ${argType} (${ + (await info.helpers.pgIntrospection.getType( + serviceName, + argType, + ))!.typname + }); skipping procedure`, + ); + return null; + } + procedureArguments.push({ mode: "o", codec: argCodec }); } } @@ -565,7 +616,8 @@ export const PgProceduresPlugin: GraphileConfig.Plugin = { hasImplicitOrder, extensions, ...(!returnsSetof ? { isUnique: true } : null), - ...(isMutation ? { isMutation } : null), + ...(isMutation || isProcedure ? { isMutation: true } : null), + ...(isProcedure ? { isProcedure, procedureArguments } : null), ...(description ? { description } : null), }; diff --git a/postgraphile/postgraphile/__tests__/helpers.ts b/postgraphile/postgraphile/__tests__/helpers.ts index 5dc8835dbf..b3a2f6b683 100644 --- a/postgraphile/postgraphile/__tests__/helpers.ts +++ b/postgraphile/postgraphile/__tests__/helpers.ts @@ -267,6 +267,8 @@ export async function runTestQuery( search_path?: string; muteWarnings?: boolean; dontLogErrors?: boolean; + /** Skip this test (and its snapshots) if the server's server_version_num is lower than this. */ + requiresPg?: number; }, options: { callback?: ( @@ -286,6 +288,7 @@ export async function runTestQuery( errors?: readonly GraphQLError[]; queries: PgClientQuery[]; extensions?: any; + skipped?: boolean; }> { const { variableValues, @@ -296,6 +299,7 @@ export async function runTestQuery( search_path, muteWarnings = true, dontLogErrors = false, + requiresPg, } = config; const { path } = options; @@ -395,6 +399,9 @@ export async function runTestQuery( // Load test data await pgPool.query(await kitchenSinkData()); const serverVersionNum = await getServerVersionNum(pgPool); + if (requiresPg != null && serverVersionNum < requiresPg) { + return { data: undefined, errors: undefined, queries: [], skipped: true }; + } if (serverVersionNum >= 110000) { await pgPool.query(await pg11Data()); } @@ -776,7 +783,10 @@ export const assertSnapshotsMatch = async ( throw new Error(`Failed to trim .test.graphql from '${path}'`); } - const { data, payloads, queries, errors, extensions } = await result; + const { data, payloads, queries, errors, extensions, skipped } = await result; + if (skipped) { + return; + } const replacements = { uuid: new Map(), uuidCounter: 1 }; @@ -855,8 +865,11 @@ export const assertResultsMatch = async ( result2: ReturnType, { config }: { config: any }, ): Promise => { - const { data: data1 } = await result1; + const { data: data1, skipped } = await result1; const { data: data2 } = await result2; + if (skipped) { + return; + } const data1a = makeResultSnapshotSafe(data1, { uuid: new Map(), uuidCounter: 1, @@ -876,8 +889,11 @@ export const assertErrorsMatch = async ( result2: ReturnType, { config }: { config: any }, ): Promise => { - const { errors: errors1 } = await result1; + const { errors: errors1, skipped } = await result1; const { errors: errors2 } = await result2; + if (skipped) { + return; + } expect(errors2).toEqual(errors1); }; diff --git a/postgraphile/postgraphile/__tests__/mutations/v4/procedures-out-params.json5 b/postgraphile/postgraphile/__tests__/mutations/v4/procedures-out-params.json5 new file mode 100644 index 0000000000..907cee5e09 --- /dev/null +++ b/postgraphile/postgraphile/__tests__/mutations/v4/procedures-out-params.json5 @@ -0,0 +1,13 @@ +{ + singleOutput: { + doubled: { + doubled: 10, + }, + }, + multipleOutputs: { + result: { + total: 10, + product: 25, + }, + }, +} diff --git a/postgraphile/postgraphile/__tests__/mutations/v4/procedures-out-params.mermaid b/postgraphile/postgraphile/__tests__/mutations/v4/procedures-out-params.mermaid new file mode 100644 index 0000000000..0d259d7aa2 --- /dev/null +++ b/postgraphile/postgraphile/__tests__/mutations/v4/procedures-out-params.mermaid @@ -0,0 +1,75 @@ +%%{init: {'themeVariables': { 'fontSize': '12px'}}}%% +graph TD + classDef path fill:#eee,stroke:#000,color:#000 + classDef plan fill:#fff,stroke-width:1px,color:#000 + classDef itemplan fill:#fff,stroke-width:2px,color:#000 + classDef unbatchedplan fill:#dff,stroke-width:1px,color:#000 + classDef sideeffectplan fill:#fcc,stroke-width:2px,color:#000 + classDef bucket fill:#f6f6f6,color:#000,stroke-width:2px,text-align:left + + subgraph "Buckets for mutations/v4/procedures-out-params" + Bucket0("Bucket 0 (root)"):::bucket + Bucket1("Bucket 1 (mutationField)
Deps: 12, 27, 14

1: PgCall[9]
2:
ᐳ: Object[13]"):::bucket + Bucket2("Bucket 2 (mutationField)
Deps: 27, 21, 2

1: Access[17]
2: Access[18]
3: Object[19]
4: PgCall[16]
5:
ᐳ: Object[20]"):::bucket + Bucket3("Bucket 3 (nullableBoundary)
Deps: 13, 9

ROOT Object{1}ᐸ{result}ᐳ[13]"):::bucket + Bucket4("Bucket 4 (nullableBoundary)
Deps: 20, 16

ROOT Object{2}ᐸ{result}ᐳ[20]"):::bucket + Bucket5("Bucket 5 (nullableBoundary)
Deps: 9

ROOT PgCall{1}ᐸsingle_outputᐳ[9]"):::bucket + Bucket6("Bucket 6 (nullableBoundary)
Deps: 16

ROOT PgCall{2}ᐸmultiple_outputsᐳ[16]"):::bucket + end + Bucket0 --> Bucket1 & Bucket2 + Bucket1 --> Bucket3 + Bucket2 --> Bucket4 + Bucket3 --> Bucket5 + Bucket4 --> Bucket6 + + %% plan dependencies + __InputObject6{{"__InputObject[6∈0] ➊
More deps:
- Constantᐸundefinedᐳ[7]
- Constantᐸ5ᐳ[27]"}}:::plan + Object12{{"Object[12∈0] ➊
ᐸ{pgSettings,withPgClient}ᐳ"}}:::plan + Access10{{"Access[10∈0] ➊
ᐸ2.pgSettingsᐳ
More deps:
- __Value[2]"}}:::plan + Access11{{"Access[11∈0] ➊
ᐸ2.withPgClientᐳ
More deps:
- __Value[2]"}}:::plan + Access10 & Access11 --> Object12 + __InputObject15{{"__InputObject[15∈0] ➊
More deps:
- Constantᐸundefinedᐳ[7]
- Constantᐸ5ᐳ[27]"}}:::plan + ApplyInput14{{"ApplyInput[14∈0] ➊"}}:::plan + __InputObject6 --> ApplyInput14 + ApplyInput21{{"ApplyInput[21∈0] ➊"}}:::plan + __InputObject15 --> ApplyInput21 + __Value2["__Value[2∈0] ➊
ᐸcontextᐳ
Dependents: 4"]:::plan + PgCall9[["PgCall[9∈1] ➊
ᐸsingle_outputᐳ
More deps:
- Constantᐸ5ᐳ[27]"]]:::sideeffectplan + Object12 & ApplyInput14 --> PgCall9 + Object13{{"Object[13∈1] ➊
ᐸ{result}ᐳ"}}:::plan + PgCall9 --> Object13 + PgCall16[["PgCall[16∈2] ➊
ᐸmultiple_outputsᐳ
More deps:
- Constantᐸ5ᐳ[27]"]]:::sideeffectplan + Object19{{"Object[19∈2] ➊
ᐸ{pgSettings,withPgClient}ᐳ"}}:::plan + Object19 & ApplyInput21 --> PgCall16 + Access17{{"Access[17∈2] ➊
ᐸ2.pgSettingsᐳ
More deps:
- __Value[2]"}}:::plan + Access18{{"Access[18∈2] ➊
ᐸ2.withPgClientᐳ
More deps:
- __Value[2]"}}:::plan + Access17 & Access18 --> Object19 + Object20{{"Object[20∈2] ➊
ᐸ{result}ᐳ"}}:::plan + PgCall16 --> Object20 + PgClassExpression22{{"PgClassExpression[22∈5] ➊
ᐸ__single_o....”doubled”ᐳ"}}:::plan + Access23{{"Access[23∈5] ➊
ᐸ9.tᐳ"}}:::plan + Access23 --> PgClassExpression22 + PgCall9 --> Access23 + PgClassExpression24{{"PgClassExpression[24∈6] ➊
ᐸ__multiple...__.”total”ᐳ"}}:::plan + Access25{{"Access[25∈6] ➊
ᐸ16.tᐳ"}}:::plan + Access25 --> PgClassExpression24 + PgCall16 --> Access25 + PgClassExpression26{{"PgClassExpression[26∈6] ➊
ᐸ__multiple....”product”ᐳ"}}:::plan + PgClassExpression24 o--o PgClassExpression26 + + %% define steps + classDef bucket0 stroke:#696969 + class Bucket0,__Value2,__InputObject6,Access10,Access11,Object12,ApplyInput14,__InputObject15,ApplyInput21 bucket0 + classDef bucket1 stroke:#00bfff + class Bucket1,PgCall9,Object13 bucket1 + classDef bucket2 stroke:#7f007f + class Bucket2,PgCall16,Access17,Access18,Object19,Object20 bucket2 + classDef bucket3 stroke:#ffa500 + class Bucket3 bucket3 + classDef bucket4 stroke:#0000ff + class Bucket4 bucket4 + classDef bucket5 stroke:#7fff00 + class Bucket5,PgClassExpression22,Access23 bucket5 + classDef bucket6 stroke:#ff1493 + class Bucket6,PgClassExpression24,Access25,PgClassExpression26 bucket6 + diff --git a/postgraphile/postgraphile/__tests__/mutations/v4/procedures-out-params.sql b/postgraphile/postgraphile/__tests__/mutations/v4/procedures-out-params.sql new file mode 100644 index 0000000000..3450d93306 --- /dev/null +++ b/postgraphile/postgraphile/__tests__/mutations/v4/procedures-out-params.sql @@ -0,0 +1,10 @@ +call "procedures"."single_output"( + $1::"int4", + null::"int4" +); + +call "procedures"."multiple_outputs"( + $1::"int4", + null::"int4", + null::"int4" +); \ No newline at end of file diff --git a/postgraphile/postgraphile/__tests__/mutations/v4/procedures-out-params.test.graphql b/postgraphile/postgraphile/__tests__/mutations/v4/procedures-out-params.test.graphql new file mode 100644 index 0000000000..356597797a --- /dev/null +++ b/postgraphile/postgraphile/__tests__/mutations/v4/procedures-out-params.test.graphql @@ -0,0 +1,16 @@ +## expect(errors).toBeFalsy(); +#> schema: ["procedures"] +#> requiresPg: 140000 +mutation { + singleOutput(input: { a: 5 }) { + doubled { + doubled + } + } + multipleOutputs(input: { a: 5 }) { + result { + total + product + } + } +} diff --git a/postgraphile/postgraphile/__tests__/mutations/v4/procedures.json5 b/postgraphile/postgraphile/__tests__/mutations/v4/procedures.json5 new file mode 100644 index 0000000000..5d14aacdef --- /dev/null +++ b/postgraphile/postgraphile/__tests__/mutations/v4/procedures.json5 @@ -0,0 +1,17 @@ +{ + noArgsNoOutput: { + __typename: "NoArgsNoOutputPayload", + }, + noArgsNoOutputWithId: { + clientMutationId: "abc", + }, + inArgsNoOutput: { + __typename: "InArgsNoOutputPayload", + }, + inoutArg: { + clientMutationId: "xyz", + counter: { + counter: 11, + }, + }, +} diff --git a/postgraphile/postgraphile/__tests__/mutations/v4/procedures.mermaid b/postgraphile/postgraphile/__tests__/mutations/v4/procedures.mermaid new file mode 100644 index 0000000000..2ee50cbb86 --- /dev/null +++ b/postgraphile/postgraphile/__tests__/mutations/v4/procedures.mermaid @@ -0,0 +1,105 @@ +%%{init: {'themeVariables': { 'fontSize': '12px'}}}%% +graph TD + classDef path fill:#eee,stroke:#000,color:#000 + classDef plan fill:#fff,stroke-width:1px,color:#000 + classDef itemplan fill:#fff,stroke-width:2px,color:#000 + classDef unbatchedplan fill:#dff,stroke-width:1px,color:#000 + classDef sideeffectplan fill:#fcc,stroke-width:2px,color:#000 + classDef bucket fill:#f6f6f6,color:#000,stroke-width:2px,text-align:left + + subgraph "Buckets for mutations/v4/procedures" + Bucket0("Bucket 0 (root)"):::bucket + Bucket1("Bucket 1 (mutationField)
Deps: 11, 13

1: PgCall[8]
2:
ᐳ: Object[12]"):::bucket + Bucket2("Bucket 2 (mutationField)
Deps: 21, 2

1: Access[17]
2: Access[18]
3: Object[19]
4: PgCall[16]
5:
ᐳ: Object[20]"):::bucket + Bucket3("Bucket 3 (mutationField)
Deps: 45, 46, 30, 2

1: Access[26]
2: Access[27]
3: Object[28]
4: PgCall[25]
5:
ᐳ: Object[29]"):::bucket + Bucket4("Bucket 4 (mutationField)
Deps: 48, 39, 2

1: Access[35]
2: Access[36]
3: Object[37]
4: PgCall[34]
5:
ᐳ: Object[38]"):::bucket + Bucket5("Bucket 5 (nullableBoundary)
Deps: 12

ROOT Object{1}ᐸ{result}ᐳ[12]"):::bucket + Bucket6("Bucket 6 (nullableBoundary)
Deps: 16, 20

ROOT Object{2}ᐸ{result}ᐳ[20]"):::bucket + Bucket7("Bucket 7 (nullableBoundary)
Deps: 29

ROOT Object{3}ᐸ{result}ᐳ[29]"):::bucket + Bucket8("Bucket 8 (nullableBoundary)
Deps: 34, 38

ROOT Object{4}ᐸ{result}ᐳ[38]"):::bucket + Bucket9("Bucket 9 (nullableBoundary)
Deps: 34

ROOT PgCall{4}ᐸinout_argᐳ[34]"):::bucket + end + Bucket0 --> Bucket1 & Bucket2 & Bucket3 & Bucket4 + Bucket1 --> Bucket5 + Bucket2 --> Bucket6 + Bucket3 --> Bucket7 + Bucket4 --> Bucket8 + Bucket8 --> Bucket9 + + %% plan dependencies + __InputObject22{{"__InputObject[22∈0] ➊
More deps:
- Constantᐸundefinedᐳ[7]
- Constantᐸ1ᐳ[45]
- Constantᐸ2ᐳ[46]"}}:::plan + Object11{{"Object[11∈0] ➊
ᐸ{pgSettings,withPgClient}ᐳ"}}:::plan + Access9{{"Access[9∈0] ➊
ᐸ2.pgSettingsᐳ
More deps:
- __Value[2]"}}:::plan + Access10{{"Access[10∈0] ➊
ᐸ2.withPgClientᐳ
More deps:
- __Value[2]"}}:::plan + Access9 & Access10 --> Object11 + __InputObject31{{"__InputObject[31∈0] ➊
More deps:
- Constantᐸ'xyz'ᐳ[47]
- Constantᐸ10ᐳ[48]"}}:::plan + __InputObject6{{"__InputObject[6∈0] ➊
More deps:
- Constantᐸundefinedᐳ[7]"}}:::plan + ApplyInput13{{"ApplyInput[13∈0] ➊"}}:::plan + __InputObject6 --> ApplyInput13 + __InputObject14{{"__InputObject[14∈0] ➊
More deps:
- Constantᐸ'abc'ᐳ[44]"}}:::plan + ApplyInput21{{"ApplyInput[21∈0] ➊"}}:::plan + __InputObject14 --> ApplyInput21 + ApplyInput30{{"ApplyInput[30∈0] ➊"}}:::plan + __InputObject22 --> ApplyInput30 + ApplyInput39{{"ApplyInput[39∈0] ➊"}}:::plan + __InputObject31 --> ApplyInput39 + __Value2["__Value[2∈0] ➊
ᐸcontextᐳ
Dependents: 8"]:::plan + PgCall8[["PgCall[8∈1] ➊
ᐸno_args_no_outputᐳ"]]:::sideeffectplan + Object11 & ApplyInput13 --> PgCall8 + Object12{{"Object[12∈1] ➊
ᐸ{result}ᐳ"}}:::plan + PgCall8 --> Object12 + PgCall16[["PgCall[16∈2] ➊
ᐸno_args_no_outputᐳ"]]:::sideeffectplan + Object19{{"Object[19∈2] ➊
ᐸ{pgSettings,withPgClient}ᐳ"}}:::plan + Object19 & ApplyInput21 --> PgCall16 + Access17{{"Access[17∈2] ➊
ᐸ2.pgSettingsᐳ
More deps:
- __Value[2]"}}:::plan + Access18{{"Access[18∈2] ➊
ᐸ2.withPgClientᐳ
More deps:
- __Value[2]"}}:::plan + Access17 & Access18 --> Object19 + Object20{{"Object[20∈2] ➊
ᐸ{result}ᐳ"}}:::plan + PgCall16 --> Object20 + PgCall25[["PgCall[25∈3] ➊
ᐸin_args_no_outputᐳ
More deps:
- Constantᐸ1ᐳ[45]
- Constantᐸ2ᐳ[46]"]]:::sideeffectplan + Object28{{"Object[28∈3] ➊
ᐸ{pgSettings,withPgClient}ᐳ"}}:::plan + Object28 & ApplyInput30 --> PgCall25 + Access26{{"Access[26∈3] ➊
ᐸ2.pgSettingsᐳ
More deps:
- __Value[2]"}}:::plan + Access27{{"Access[27∈3] ➊
ᐸ2.withPgClientᐳ
More deps:
- __Value[2]"}}:::plan + Access26 & Access27 --> Object28 + Object29{{"Object[29∈3] ➊
ᐸ{result}ᐳ"}}:::plan + PgCall25 --> Object29 + PgCall34[["PgCall[34∈4] ➊
ᐸinout_argᐳ
More deps:
- Constantᐸ10ᐳ[48]"]]:::sideeffectplan + Object37{{"Object[37∈4] ➊
ᐸ{pgSettings,withPgClient}ᐳ"}}:::plan + Object37 & ApplyInput39 --> PgCall34 + Access35{{"Access[35∈4] ➊
ᐸ2.pgSettingsᐳ
More deps:
- __Value[2]"}}:::plan + Access36{{"Access[36∈4] ➊
ᐸ2.withPgClientᐳ
More deps:
- __Value[2]"}}:::plan + Access35 & Access36 --> Object37 + Object38{{"Object[38∈4] ➊
ᐸ{result}ᐳ"}}:::plan + PgCall34 --> Object38 + Access40{{"Access[40∈6] ➊
ᐸ16.m.clientMutationIdᐳ"}}:::plan + Object20 o--o Access40 + Access41{{"Access[41∈8] ➊
ᐸ34.m.clientMutationIdᐳ"}}:::plan + Object38 o--o Access41 + PgClassExpression42{{"PgClassExpression[42∈9] ➊
ᐸ__inout_arg__.”counter”ᐳ"}}:::plan + Access43{{"Access[43∈9] ➊
ᐸ34.tᐳ"}}:::plan + Access43 --> PgClassExpression42 + PgCall34 --> Access43 + + %% define steps + classDef bucket0 stroke:#696969 + class Bucket0,__Value2,__InputObject6,Access9,Access10,Object11,ApplyInput13,__InputObject14,ApplyInput21,__InputObject22,ApplyInput30,__InputObject31,ApplyInput39 bucket0 + classDef bucket1 stroke:#00bfff + class Bucket1,PgCall8,Object12 bucket1 + classDef bucket2 stroke:#7f007f + class Bucket2,PgCall16,Access17,Access18,Object19,Object20 bucket2 + classDef bucket3 stroke:#ffa500 + class Bucket3,PgCall25,Access26,Access27,Object28,Object29 bucket3 + classDef bucket4 stroke:#0000ff + class Bucket4,PgCall34,Access35,Access36,Object37,Object38 bucket4 + classDef bucket5 stroke:#7fff00 + class Bucket5 bucket5 + classDef bucket6 stroke:#ff1493 + class Bucket6,Access40 bucket6 + classDef bucket7 stroke:#808000 + class Bucket7 bucket7 + classDef bucket8 stroke:#dda0dd + class Bucket8,Access41 bucket8 + classDef bucket9 stroke:#ff0000 + class Bucket9,PgClassExpression42,Access43 bucket9 + diff --git a/postgraphile/postgraphile/__tests__/mutations/v4/procedures.sql b/postgraphile/postgraphile/__tests__/mutations/v4/procedures.sql new file mode 100644 index 0000000000..fbe70859ad --- /dev/null +++ b/postgraphile/postgraphile/__tests__/mutations/v4/procedures.sql @@ -0,0 +1,10 @@ +call "procedures"."no_args_no_output"(); + +call "procedures"."no_args_no_output"(); + +call "procedures"."in_args_no_output"( + $1::"int4", + $2::"int4" +); + +call "procedures"."inout_arg"($1::"int4"); \ No newline at end of file diff --git a/postgraphile/postgraphile/__tests__/mutations/v4/procedures.test.graphql b/postgraphile/postgraphile/__tests__/mutations/v4/procedures.test.graphql new file mode 100644 index 0000000000..ac7078d90c --- /dev/null +++ b/postgraphile/postgraphile/__tests__/mutations/v4/procedures.test.graphql @@ -0,0 +1,20 @@ +## expect(errors).toBeFalsy(); +#> schema: ["procedures"] +#> requiresPg: 110000 +mutation { + noArgsNoOutput(input: {}) { + __typename + } + noArgsNoOutputWithId: noArgsNoOutput(input: { clientMutationId: "abc" }) { + clientMutationId + } + inArgsNoOutput(input: { a: 1, b: 2 }) { + __typename + } + inoutArg(input: { counter: 10, clientMutationId: "xyz" }) { + clientMutationId + counter { + counter + } + } +} diff --git a/postgraphile/postgraphile/__tests__/pg11-schema.sql b/postgraphile/postgraphile/__tests__/pg11-schema.sql index 793459b339..5bd6d1dc3a 100644 --- a/postgraphile/postgraphile/__tests__/pg11-schema.sql +++ b/postgraphile/postgraphile/__tests__/pg11-schema.sql @@ -28,8 +28,58 @@ create domain pg11.domain_constrained_compound_type as create table pg11.types ( id serial primary key, - "regrole" regrole, + "regrole" regrole, "regnamespace" regnamespace, "bigint_domain_array_domain" c.bigint_domain_array_domain, "domain_constrained_compound_type" pg11.domain_constrained_compound_type ); + +drop schema if exists procedures cascade; +create schema procedures; + +create procedure procedures.no_args_no_output() +language plpgsql as $$ +begin +end; +$$; + +create procedure procedures.in_args_no_output(a int, b int) +language plpgsql as $$ +begin +end; +$$; + +-- PostgreSQL only allowed `inout` parameters for procedures until PG14, +-- which added support for `out` parameters too - so these two are guarded +-- to only run on PG14+. +do $guard$ +begin + if current_setting('server_version_num')::int >= 140000 then + execute $ddl$ + create procedure procedures.single_output(a int, out doubled int) + language plpgsql as $body$ + begin + doubled := a * 2; + end; + $body$; + $ddl$; + + execute $ddl$ + create procedure procedures.multiple_outputs(a int, out total int, out product int) + language plpgsql as $body$ + begin + total := a + a; + product := a * a; + end; + $body$; + $ddl$; + end if; +end; +$guard$; + +create procedure procedures.inout_arg(inout counter int) +language plpgsql as $$ +begin + counter := counter + 1; +end; +$$; diff --git a/postgraphile/postgraphile/__tests__/schema/v4/defaultOptions-minified.1.export.mjs b/postgraphile/postgraphile/__tests__/schema/v4/defaultOptions-minified.1.export.mjs index 1a5b9b430a..3e28a6ba3d 100644 --- a/postgraphile/postgraphile/__tests__/schema/v4/defaultOptions-minified.1.export.mjs +++ b/postgraphile/postgraphile/__tests__/schema/v4/defaultOptions-minified.1.export.mjs @@ -1,4 +1,4 @@ -import { LIST_TYPES, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, domainOfCodec, enumCodec, listOfCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectFromRecords, pgSelectSingleFromRecord, pgUpdateSingle, rangeOfCodec, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; +import { LIST_TYPES, PgCallStep, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, domainOfCodec, enumCodec, listOfCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectFromRecords, pgSelectSingleFromRecord, pgUpdateSingle, rangeOfCodec, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; import { ConnectionStep, EdgeStep, ObjectStep, __ValueStep, access, assertStep, bakedInput, bakedInputRuntime, connection, constant, context, createObjectAndApplyChildren, first, get as get2, inhibitOnNull, inspect, lambda, list, makeDecodeNodeId, makeGrafastSchema, markSyncAndSafe, object, operationPlan, specFromNodeId, stepAMayDependOnStepB, trap } from "grafast"; import { GraphQLError, GraphQLInt, GraphQLString, Kind, valueFromASTUntyped } from "graphql"; import { sql } from "pg-sql2"; @@ -6163,6 +6163,13 @@ const resource_edge_case_computedPgResource = registry.pgResources["edge_case_co const resource_mutation_outPgResource = registry.pgResources["mutation_out"]; function pgSelectFromPayload($payload) { const $result = $payload.getStepForKey("result"); + if ($result instanceof PgCallStep) { + // Procedures are invoked via `call`, not `select`, so there's no + // `PgSelectStep` to find. The call step itself is the target that + // nested `apply`-capable input fields (e.g. `clientMutationId`) + // should be applied to. + return $result; + } const $parent = "getParentStep" in $result ? $result.getParentStep() : $result; const $pgSelect = "getClassStep" in $parent ? $parent.getClassStep() : $parent; if ($pgSelect instanceof PgSelectStep) { @@ -6173,6 +6180,10 @@ function pgSelectFromPayload($payload) { } function applyInputArgViaPgSelect(_, $payload, arg) { const $pgSelect = pgSelectFromPayload($payload); + // `PgCallStep`'s `apply()` takes a `PgCallQueryBuilder` rather than a + // `PgSelectQueryBuilder`, but both satisfy the same structural `apply` + // protocol at runtime (only `clientMutationId` uses it here), so the + // precise `qb` type is immaterial to the caller. arg.apply($pgSelect); } const resource_mutation_out_setofPgResource = registry.pgResources["mutation_out_setof"]; diff --git a/postgraphile/postgraphile/__tests__/schema/v4/defaultOptions.1.export.mjs b/postgraphile/postgraphile/__tests__/schema/v4/defaultOptions.1.export.mjs index 639f0d2d13..e5e2f52969 100644 --- a/postgraphile/postgraphile/__tests__/schema/v4/defaultOptions.1.export.mjs +++ b/postgraphile/postgraphile/__tests__/schema/v4/defaultOptions.1.export.mjs @@ -1,4 +1,4 @@ -import { LIST_TYPES, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, domainOfCodec, enumCodec, listOfCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectFromRecords, pgSelectSingleFromRecord, pgUpdateSingle, rangeOfCodec, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; +import { LIST_TYPES, PgCallStep, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, domainOfCodec, enumCodec, listOfCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectFromRecords, pgSelectSingleFromRecord, pgUpdateSingle, rangeOfCodec, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; import { ConnectionStep, EdgeStep, ObjectStep, __ValueStep, access, assertStep, bakedInput, bakedInputRuntime, connection, constant, context, createObjectAndApplyChildren, first, get as get2, inhibitOnNull, inspect, lambda, list, makeDecodeNodeId, makeGrafastSchema, markSyncAndSafe, object, operationPlan, specFromNodeId, stepAMayDependOnStepB, trap } from "grafast"; import { GraphQLError, GraphQLInt, GraphQLString, Kind, valueFromASTUntyped } from "graphql"; import { sql } from "pg-sql2"; @@ -8124,6 +8124,13 @@ const resource_edge_case_computedPgResource = registry.pgResources["edge_case_co const resource_mutation_outPgResource = registry.pgResources["mutation_out"]; function pgSelectFromPayload($payload) { const $result = $payload.getStepForKey("result"); + if ($result instanceof PgCallStep) { + // Procedures are invoked via `call`, not `select`, so there's no + // `PgSelectStep` to find. The call step itself is the target that + // nested `apply`-capable input fields (e.g. `clientMutationId`) + // should be applied to. + return $result; + } const $parent = "getParentStep" in $result ? $result.getParentStep() : $result; const $pgSelect = "getClassStep" in $parent ? $parent.getClassStep() : $parent; if ($pgSelect instanceof PgSelectStep) { @@ -8134,6 +8141,10 @@ function pgSelectFromPayload($payload) { } function applyInputArgViaPgSelect(_, $payload, arg) { const $pgSelect = pgSelectFromPayload($payload); + // `PgCallStep`'s `apply()` takes a `PgCallQueryBuilder` rather than a + // `PgSelectQueryBuilder`, but both satisfy the same structural `apply` + // protocol at runtime (only `clientMutationId` uses it here), so the + // precise `qb` type is immaterial to the caller. arg.apply($pgSelect); } const resource_mutation_out_setofPgResource = registry.pgResources["mutation_out_setof"]; diff --git a/postgraphile/postgraphile/__tests__/schema/v4/defaultOptions.subscriptions.1.export.mjs b/postgraphile/postgraphile/__tests__/schema/v4/defaultOptions.subscriptions.1.export.mjs index 639f0d2d13..e5e2f52969 100644 --- a/postgraphile/postgraphile/__tests__/schema/v4/defaultOptions.subscriptions.1.export.mjs +++ b/postgraphile/postgraphile/__tests__/schema/v4/defaultOptions.subscriptions.1.export.mjs @@ -1,4 +1,4 @@ -import { LIST_TYPES, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, domainOfCodec, enumCodec, listOfCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectFromRecords, pgSelectSingleFromRecord, pgUpdateSingle, rangeOfCodec, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; +import { LIST_TYPES, PgCallStep, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, domainOfCodec, enumCodec, listOfCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectFromRecords, pgSelectSingleFromRecord, pgUpdateSingle, rangeOfCodec, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; import { ConnectionStep, EdgeStep, ObjectStep, __ValueStep, access, assertStep, bakedInput, bakedInputRuntime, connection, constant, context, createObjectAndApplyChildren, first, get as get2, inhibitOnNull, inspect, lambda, list, makeDecodeNodeId, makeGrafastSchema, markSyncAndSafe, object, operationPlan, specFromNodeId, stepAMayDependOnStepB, trap } from "grafast"; import { GraphQLError, GraphQLInt, GraphQLString, Kind, valueFromASTUntyped } from "graphql"; import { sql } from "pg-sql2"; @@ -8124,6 +8124,13 @@ const resource_edge_case_computedPgResource = registry.pgResources["edge_case_co const resource_mutation_outPgResource = registry.pgResources["mutation_out"]; function pgSelectFromPayload($payload) { const $result = $payload.getStepForKey("result"); + if ($result instanceof PgCallStep) { + // Procedures are invoked via `call`, not `select`, so there's no + // `PgSelectStep` to find. The call step itself is the target that + // nested `apply`-capable input fields (e.g. `clientMutationId`) + // should be applied to. + return $result; + } const $parent = "getParentStep" in $result ? $result.getParentStep() : $result; const $pgSelect = "getClassStep" in $parent ? $parent.getClassStep() : $parent; if ($pgSelect instanceof PgSelectStep) { @@ -8134,6 +8141,10 @@ function pgSelectFromPayload($payload) { } function applyInputArgViaPgSelect(_, $payload, arg) { const $pgSelect = pgSelectFromPayload($payload); + // `PgCallStep`'s `apply()` takes a `PgCallQueryBuilder` rather than a + // `PgSelectQueryBuilder`, but both satisfy the same structural `apply` + // protocol at runtime (only `clientMutationId` uses it here), so the + // precise `qb` type is immaterial to the caller. arg.apply($pgSelect); } const resource_mutation_out_setofPgResource = registry.pgResources["mutation_out_setof"]; diff --git a/postgraphile/postgraphile/__tests__/schema/v4/enum_tables.1.export.mjs b/postgraphile/postgraphile/__tests__/schema/v4/enum_tables.1.export.mjs index b25ac53bf6..521f31dece 100644 --- a/postgraphile/postgraphile/__tests__/schema/v4/enum_tables.1.export.mjs +++ b/postgraphile/postgraphile/__tests__/schema/v4/enum_tables.1.export.mjs @@ -1,4 +1,4 @@ -import { PgDeleteSingleStep, PgExecutor, PgResource, PgSelectStep, TYPES, assertPgClassSingleStep, enumCodec, makeRegistry, pgDeleteSingle, pgInsertSingle, pgSelectFromRecord, pgUpdateSingle, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; +import { PgCallStep, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectStep, TYPES, assertPgClassSingleStep, enumCodec, makeRegistry, pgDeleteSingle, pgInsertSingle, pgSelectFromRecord, pgUpdateSingle, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; import { ConnectionStep, EdgeStep, ObjectStep, __ValueStep, access, assertStep, bakedInput, bakedInputRuntime, connection, constant, context, createObjectAndApplyChildren, first, get as get2, inhibitOnNull, inspect, lambda, list, makeDecodeNodeId, makeGrafastSchema, markSyncAndSafe, object, operationPlan, specFromNodeId, trap } from "grafast"; import { GraphQLError, Kind } from "graphql"; import { sql } from "pg-sql2"; @@ -1183,6 +1183,13 @@ const makeArgs_referencing_table_mutation = (args, path = []) => argDetailsSimpl const resource_referencing_table_mutationPgResource = registry.pgResources["referencing_table_mutation"]; function pgSelectFromPayload($payload) { const $result = $payload.getStepForKey("result"); + if ($result instanceof PgCallStep) { + // Procedures are invoked via `call`, not `select`, so there's no + // `PgSelectStep` to find. The call step itself is the target that + // nested `apply`-capable input fields (e.g. `clientMutationId`) + // should be applied to. + return $result; + } const $parent = "getParentStep" in $result ? $result.getParentStep() : $result; const $pgSelect = "getClassStep" in $parent ? $parent.getClassStep() : $parent; if ($pgSelect instanceof PgSelectStep) { @@ -2356,6 +2363,10 @@ export const objects = { args: { input(_, $payload, arg) { const $pgSelect = pgSelectFromPayload($payload); + // `PgCallStep`'s `apply()` takes a `PgCallQueryBuilder` rather than a + // `PgSelectQueryBuilder`, but both satisfy the same structural `apply` + // protocol at runtime (only `clientMutationId` uses it here), so the + // precise `qb` type is immaterial to the caller. arg.apply($pgSelect); } } diff --git a/postgraphile/postgraphile/__tests__/schema/v4/foreignKey-smart-tag-autofix.1.export.mjs b/postgraphile/postgraphile/__tests__/schema/v4/foreignKey-smart-tag-autofix.1.export.mjs index c1e7f6df01..4088caca56 100644 --- a/postgraphile/postgraphile/__tests__/schema/v4/foreignKey-smart-tag-autofix.1.export.mjs +++ b/postgraphile/postgraphile/__tests__/schema/v4/foreignKey-smart-tag-autofix.1.export.mjs @@ -1,4 +1,4 @@ -import { LIST_TYPES, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, domainOfCodec, enumCodec, listOfCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectFromRecords, pgSelectSingleFromRecord, pgUpdateSingle, rangeOfCodec, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; +import { LIST_TYPES, PgCallStep, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, domainOfCodec, enumCodec, listOfCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectFromRecords, pgSelectSingleFromRecord, pgUpdateSingle, rangeOfCodec, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; import { ConnectionStep, EdgeStep, ObjectStep, __ValueStep, access, assertStep, bakedInput, bakedInputRuntime, connection, constant, context, createObjectAndApplyChildren, first, get as get2, inhibitOnNull, inspect, lambda, list, makeDecodeNodeId, makeGrafastSchema, markSyncAndSafe, object, operationPlan, specFromNodeId, stepAMayDependOnStepB, trap } from "grafast"; import { GraphQLError, GraphQLInt, GraphQLString, Kind, valueFromASTUntyped } from "graphql"; import { sql } from "pg-sql2"; @@ -5059,6 +5059,13 @@ const resource_edge_case_computedPgResource = registry.pgResources["edge_case_co const resource_mutation_outPgResource = registry.pgResources["mutation_out"]; function pgSelectFromPayload($payload) { const $result = $payload.getStepForKey("result"); + if ($result instanceof PgCallStep) { + // Procedures are invoked via `call`, not `select`, so there's no + // `PgSelectStep` to find. The call step itself is the target that + // nested `apply`-capable input fields (e.g. `clientMutationId`) + // should be applied to. + return $result; + } const $parent = "getParentStep" in $result ? $result.getParentStep() : $result; const $pgSelect = "getClassStep" in $parent ? $parent.getClassStep() : $parent; if ($pgSelect instanceof PgSelectStep) { @@ -5069,6 +5076,10 @@ function pgSelectFromPayload($payload) { } function applyInputArgViaPgSelect(_, $payload, arg) { const $pgSelect = pgSelectFromPayload($payload); + // `PgCallStep`'s `apply()` takes a `PgCallQueryBuilder` rather than a + // `PgSelectQueryBuilder`, but both satisfy the same structural `apply` + // protocol at runtime (only `clientMutationId` uses it here), so the + // precise `qb` type is immaterial to the caller. arg.apply($pgSelect); } const resource_mutation_out_setofPgResource = registry.pgResources["mutation_out_setof"]; diff --git a/postgraphile/postgraphile/__tests__/schema/v4/foreignKey-smart-tag-good.1.export.mjs b/postgraphile/postgraphile/__tests__/schema/v4/foreignKey-smart-tag-good.1.export.mjs index 04d51eb0ec..1e12ccc8fa 100644 --- a/postgraphile/postgraphile/__tests__/schema/v4/foreignKey-smart-tag-good.1.export.mjs +++ b/postgraphile/postgraphile/__tests__/schema/v4/foreignKey-smart-tag-good.1.export.mjs @@ -1,4 +1,4 @@ -import { LIST_TYPES, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, domainOfCodec, enumCodec, listOfCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectFromRecords, pgSelectSingleFromRecord, pgUpdateSingle, rangeOfCodec, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; +import { LIST_TYPES, PgCallStep, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, domainOfCodec, enumCodec, listOfCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectFromRecords, pgSelectSingleFromRecord, pgUpdateSingle, rangeOfCodec, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; import { ConnectionStep, EdgeStep, ObjectStep, __ValueStep, access, assertStep, bakedInput, bakedInputRuntime, connection, constant, context, createObjectAndApplyChildren, first, get as get2, inhibitOnNull, inspect, lambda, list, makeDecodeNodeId, makeGrafastSchema, markSyncAndSafe, object, operationPlan, specFromNodeId, stepAMayDependOnStepB, trap } from "grafast"; import { GraphQLError, GraphQLInt, GraphQLString, Kind, valueFromASTUntyped } from "graphql"; import { sql } from "pg-sql2"; @@ -5053,6 +5053,13 @@ const resource_edge_case_computedPgResource = registry.pgResources["edge_case_co const resource_mutation_outPgResource = registry.pgResources["mutation_out"]; function pgSelectFromPayload($payload) { const $result = $payload.getStepForKey("result"); + if ($result instanceof PgCallStep) { + // Procedures are invoked via `call`, not `select`, so there's no + // `PgSelectStep` to find. The call step itself is the target that + // nested `apply`-capable input fields (e.g. `clientMutationId`) + // should be applied to. + return $result; + } const $parent = "getParentStep" in $result ? $result.getParentStep() : $result; const $pgSelect = "getClassStep" in $parent ? $parent.getClassStep() : $parent; if ($pgSelect instanceof PgSelectStep) { @@ -5063,6 +5070,10 @@ function pgSelectFromPayload($payload) { } function applyInputArgViaPgSelect(_, $payload, arg) { const $pgSelect = pgSelectFromPayload($payload); + // `PgCallStep`'s `apply()` takes a `PgCallQueryBuilder` rather than a + // `PgSelectQueryBuilder`, but both satisfy the same structural `apply` + // protocol at runtime (only `clientMutationId` uses it here), so the + // precise `qb` type is immaterial to the caller. arg.apply($pgSelect); } const resource_mutation_out_setofPgResource = registry.pgResources["mutation_out_setof"]; diff --git a/postgraphile/postgraphile/__tests__/schema/v4/function-clash-with-tags-file-workaround.1.export.mjs b/postgraphile/postgraphile/__tests__/schema/v4/function-clash-with-tags-file-workaround.1.export.mjs index 6cfff429c5..e462e85b2d 100644 --- a/postgraphile/postgraphile/__tests__/schema/v4/function-clash-with-tags-file-workaround.1.export.mjs +++ b/postgraphile/postgraphile/__tests__/schema/v4/function-clash-with-tags-file-workaround.1.export.mjs @@ -1,4 +1,4 @@ -import { LIST_TYPES, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, domainOfCodec, enumCodec, listOfCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectFromRecords, pgSelectSingleFromRecord, pgUpdateSingle, rangeOfCodec, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; +import { LIST_TYPES, PgCallStep, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, domainOfCodec, enumCodec, listOfCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectFromRecords, pgSelectSingleFromRecord, pgUpdateSingle, rangeOfCodec, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; import { ConnectionStep, EdgeStep, ObjectStep, __ValueStep, access, assertStep, bakedInput, bakedInputRuntime, connection, constant, context, createObjectAndApplyChildren, first, get as get2, inhibitOnNull, inspect, lambda, list, makeDecodeNodeId, makeGrafastSchema, markSyncAndSafe, object, operationPlan, specFromNodeId, stepAMayDependOnStepB, trap } from "grafast"; import { GraphQLError, GraphQLInt, GraphQLString, Kind, valueFromASTUntyped } from "graphql"; import { sql } from "pg-sql2"; @@ -8155,6 +8155,13 @@ const resource_edge_case_computedPgResource = registry.pgResources["edge_case_co const resource_mutation_outPgResource = registry.pgResources["mutation_out"]; function pgSelectFromPayload($payload) { const $result = $payload.getStepForKey("result"); + if ($result instanceof PgCallStep) { + // Procedures are invoked via `call`, not `select`, so there's no + // `PgSelectStep` to find. The call step itself is the target that + // nested `apply`-capable input fields (e.g. `clientMutationId`) + // should be applied to. + return $result; + } const $parent = "getParentStep" in $result ? $result.getParentStep() : $result; const $pgSelect = "getClassStep" in $parent ? $parent.getClassStep() : $parent; if ($pgSelect instanceof PgSelectStep) { @@ -8165,6 +8172,10 @@ function pgSelectFromPayload($payload) { } function applyInputArgViaPgSelect(_, $payload, arg) { const $pgSelect = pgSelectFromPayload($payload); + // `PgCallStep`'s `apply()` takes a `PgCallQueryBuilder` rather than a + // `PgSelectQueryBuilder`, but both satisfy the same structural `apply` + // protocol at runtime (only `clientMutationId` uses it here), so the + // precise `qb` type is immaterial to the caller. arg.apply($pgSelect); } const resource_mutation_out_setofPgResource = registry.pgResources["mutation_out_setof"]; diff --git a/postgraphile/postgraphile/__tests__/schema/v4/function-clash.1.export.mjs b/postgraphile/postgraphile/__tests__/schema/v4/function-clash.1.export.mjs index ead239a87b..d10f5a99f5 100644 --- a/postgraphile/postgraphile/__tests__/schema/v4/function-clash.1.export.mjs +++ b/postgraphile/postgraphile/__tests__/schema/v4/function-clash.1.export.mjs @@ -1,4 +1,4 @@ -import { LIST_TYPES, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, domainOfCodec, enumCodec, listOfCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectFromRecords, pgSelectSingleFromRecord, pgUpdateSingle, rangeOfCodec, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; +import { LIST_TYPES, PgCallStep, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, domainOfCodec, enumCodec, listOfCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectFromRecords, pgSelectSingleFromRecord, pgUpdateSingle, rangeOfCodec, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; import { ConnectionStep, EdgeStep, ObjectStep, __ValueStep, access, assertStep, bakedInput, bakedInputRuntime, connection, constant, context, createObjectAndApplyChildren, first, get as get2, inhibitOnNull, inspect, lambda, list, makeDecodeNodeId, makeGrafastSchema, markSyncAndSafe, object, operationPlan, specFromNodeId, stepAMayDependOnStepB, trap } from "grafast"; import { GraphQLError, GraphQLInt, GraphQLString, Kind, valueFromASTUntyped } from "graphql"; import { sql } from "pg-sql2"; @@ -8146,6 +8146,13 @@ const resource_edge_case_computedPgResource = registry.pgResources["edge_case_co const resource_mutation_outPgResource = registry.pgResources["mutation_out"]; function pgSelectFromPayload($payload) { const $result = $payload.getStepForKey("result"); + if ($result instanceof PgCallStep) { + // Procedures are invoked via `call`, not `select`, so there's no + // `PgSelectStep` to find. The call step itself is the target that + // nested `apply`-capable input fields (e.g. `clientMutationId`) + // should be applied to. + return $result; + } const $parent = "getParentStep" in $result ? $result.getParentStep() : $result; const $pgSelect = "getClassStep" in $parent ? $parent.getClassStep() : $parent; if ($pgSelect instanceof PgSelectStep) { @@ -8156,6 +8163,10 @@ function pgSelectFromPayload($payload) { } function applyInputArgViaPgSelect(_, $payload, arg) { const $pgSelect = pgSelectFromPayload($payload); + // `PgCallStep`'s `apply()` takes a `PgCallQueryBuilder` rather than a + // `PgSelectQueryBuilder`, but both satisfy the same structural `apply` + // protocol at runtime (only `clientMutationId` uses it here), so the + // precise `qb` type is immaterial to the caller. arg.apply($pgSelect); } const resource_mutation_out_setofPgResource = registry.pgResources["mutation_out_setof"]; diff --git a/postgraphile/postgraphile/__tests__/schema/v4/indexes.1.export.mjs b/postgraphile/postgraphile/__tests__/schema/v4/indexes.1.export.mjs index 0108c77c13..c8b7c96ee9 100644 --- a/postgraphile/postgraphile/__tests__/schema/v4/indexes.1.export.mjs +++ b/postgraphile/postgraphile/__tests__/schema/v4/indexes.1.export.mjs @@ -1,4 +1,4 @@ -import { LIST_TYPES, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, domainOfCodec, enumCodec, listOfCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectFromRecords, pgSelectSingleFromRecord, pgUpdateSingle, rangeOfCodec, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; +import { LIST_TYPES, PgCallStep, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, domainOfCodec, enumCodec, listOfCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectFromRecords, pgSelectSingleFromRecord, pgUpdateSingle, rangeOfCodec, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; import { ConnectionStep, EdgeStep, ObjectStep, __ValueStep, access, assertStep, bakedInput, bakedInputRuntime, connection, constant, context, createObjectAndApplyChildren, first, get as get2, inhibitOnNull, inspect, lambda, list, makeDecodeNodeId, makeGrafastSchema, markSyncAndSafe, object, operationPlan, specFromNodeId, stepAMayDependOnStepB, trap } from "grafast"; import { GraphQLError, GraphQLInt, GraphQLString, Kind, valueFromASTUntyped } from "graphql"; import { sql } from "pg-sql2"; @@ -8519,6 +8519,13 @@ const resource_edge_case_computedPgResource = registry.pgResources["edge_case_co const resource_mutation_outPgResource = registry.pgResources["mutation_out"]; function pgSelectFromPayload($payload) { const $result = $payload.getStepForKey("result"); + if ($result instanceof PgCallStep) { + // Procedures are invoked via `call`, not `select`, so there's no + // `PgSelectStep` to find. The call step itself is the target that + // nested `apply`-capable input fields (e.g. `clientMutationId`) + // should be applied to. + return $result; + } const $parent = "getParentStep" in $result ? $result.getParentStep() : $result; const $pgSelect = "getClassStep" in $parent ? $parent.getClassStep() : $parent; if ($pgSelect instanceof PgSelectStep) { @@ -8529,6 +8536,10 @@ function pgSelectFromPayload($payload) { } function applyInputArgViaPgSelect(_, $payload, arg) { const $pgSelect = pgSelectFromPayload($payload); + // `PgCallStep`'s `apply()` takes a `PgCallQueryBuilder` rather than a + // `PgSelectQueryBuilder`, but both satisfy the same structural `apply` + // protocol at runtime (only `clientMutationId` uses it here), so the + // precise `qb` type is immaterial to the caller. arg.apply($pgSelect); } const resource_mutation_out_setofPgResource = registry.pgResources["mutation_out_setof"]; diff --git a/postgraphile/postgraphile/__tests__/schema/v4/inflect-builtin-lowercase.1.export.mjs b/postgraphile/postgraphile/__tests__/schema/v4/inflect-builtin-lowercase.1.export.mjs index 33ea4a00a7..53a513f8ab 100644 --- a/postgraphile/postgraphile/__tests__/schema/v4/inflect-builtin-lowercase.1.export.mjs +++ b/postgraphile/postgraphile/__tests__/schema/v4/inflect-builtin-lowercase.1.export.mjs @@ -1,4 +1,4 @@ -import { LIST_TYPES, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, domainOfCodec, enumCodec, listOfCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectFromRecords, pgSelectSingleFromRecord, pgUpdateSingle, rangeOfCodec, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; +import { LIST_TYPES, PgCallStep, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, domainOfCodec, enumCodec, listOfCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectFromRecords, pgSelectSingleFromRecord, pgUpdateSingle, rangeOfCodec, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; import { ConnectionStep, EdgeStep, ObjectStep, __ValueStep, access, assertStep, bakedInput, bakedInputRuntime, connection, constant, context, createObjectAndApplyChildren, first, get as get2, inhibitOnNull, inspect, lambda, list, makeDecodeNodeId, makeGrafastSchema, markSyncAndSafe, object, operationPlan, specFromNodeId, stepAMayDependOnStepB, trap } from "grafast"; import { GraphQLError, GraphQLInt, GraphQLString, Kind, valueFromASTUntyped } from "graphql"; import { sql } from "pg-sql2"; @@ -8124,6 +8124,13 @@ const resource_edge_case_computedPgResource = registry.pgResources["edge_case_co const resource_mutation_outPgResource = registry.pgResources["mutation_out"]; function pgSelectFromPayload($payload) { const $result = $payload.getStepForKey("result"); + if ($result instanceof PgCallStep) { + // Procedures are invoked via `call`, not `select`, so there's no + // `PgSelectStep` to find. The call step itself is the target that + // nested `apply`-capable input fields (e.g. `clientMutationId`) + // should be applied to. + return $result; + } const $parent = "getParentStep" in $result ? $result.getParentStep() : $result; const $pgSelect = "getClassStep" in $parent ? $parent.getClassStep() : $parent; if ($pgSelect instanceof PgSelectStep) { @@ -8134,6 +8141,10 @@ function pgSelectFromPayload($payload) { } function applyInputArgViaPgSelect(_, $payload, arg) { const $pgSelect = pgSelectFromPayload($payload); + // `PgCallStep`'s `apply()` takes a `PgCallQueryBuilder` rather than a + // `PgSelectQueryBuilder`, but both satisfy the same structural `apply` + // protocol at runtime (only `clientMutationId` uses it here), so the + // precise `qb` type is immaterial to the caller. arg.apply($pgSelect); } const resource_mutation_out_setofPgResource = registry.pgResources["mutation_out_setof"]; diff --git a/postgraphile/postgraphile/__tests__/schema/v4/inflect-core.1.export.mjs b/postgraphile/postgraphile/__tests__/schema/v4/inflect-core.1.export.mjs index a5c5bba33f..8780941b66 100644 --- a/postgraphile/postgraphile/__tests__/schema/v4/inflect-core.1.export.mjs +++ b/postgraphile/postgraphile/__tests__/schema/v4/inflect-core.1.export.mjs @@ -1,4 +1,4 @@ -import { LIST_TYPES, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, domainOfCodec, enumCodec, listOfCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectFromRecords, pgSelectSingleFromRecord, pgUpdateSingle, rangeOfCodec, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; +import { LIST_TYPES, PgCallStep, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, domainOfCodec, enumCodec, listOfCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectFromRecords, pgSelectSingleFromRecord, pgUpdateSingle, rangeOfCodec, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; import { ConnectionStep, EdgeStep, ObjectStep, __ValueStep, access, assertStep, bakedInput, bakedInputRuntime, connection, constant, context, createObjectAndApplyChildren, first, get as get2, inhibitOnNull, inspect, lambda, list, makeDecodeNodeId, makeGrafastSchema, markSyncAndSafe, object, operationPlan, specFromNodeId, stepAMayDependOnStepB, trap } from "grafast"; import { GraphQLError, GraphQLInt, GraphQLString, Kind, valueFromASTUntyped } from "graphql"; import { sql } from "pg-sql2"; @@ -8124,6 +8124,13 @@ const resource_edge_case_computedPgResource = registry.pgResources["edge_case_co const resource_mutation_outPgResource = registry.pgResources["mutation_out"]; function pgSelectFromPayload($payload) { const $result = $payload.getStepForKey("result"); + if ($result instanceof PgCallStep) { + // Procedures are invoked via `call`, not `select`, so there's no + // `PgSelectStep` to find. The call step itself is the target that + // nested `apply`-capable input fields (e.g. `clientMutationId`) + // should be applied to. + return $result; + } const $parent = "getParentStep" in $result ? $result.getParentStep() : $result; const $pgSelect = "getClassStep" in $parent ? $parent.getClassStep() : $parent; if ($pgSelect instanceof PgSelectStep) { @@ -8134,6 +8141,10 @@ function pgSelectFromPayload($payload) { } function applyInputArgViaPgSelect(_, $payload, arg) { const $pgSelect = pgSelectFromPayload($payload); + // `PgCallStep`'s `apply()` takes a `PgCallQueryBuilder` rather than a + // `PgSelectQueryBuilder`, but both satisfy the same structural `apply` + // protocol at runtime (only `clientMutationId` uses it here), so the + // precise `qb` type is immaterial to the caller. arg.apply($pgSelect); } const resource_mutation_out_setofPgResource = registry.pgResources["mutation_out_setof"]; diff --git a/postgraphile/postgraphile/__tests__/schema/v4/jwt.1.export.mjs b/postgraphile/postgraphile/__tests__/schema/v4/jwt.1.export.mjs index 91e2a6fbfb..4c6fc181e3 100644 --- a/postgraphile/postgraphile/__tests__/schema/v4/jwt.1.export.mjs +++ b/postgraphile/postgraphile/__tests__/schema/v4/jwt.1.export.mjs @@ -1,4 +1,4 @@ -import { LIST_TYPES, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectStep, TYPES, assertPgClassSingleStep, domainOfCodec, enumCodec, listOfCodec, makeRegistry, pgDeleteSingle, pgInsertSingle, pgSelectFromRecord, pgSelectFromRecords, pgSelectSingleFromRecord, pgUpdateSingle, rangeOfCodec, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; +import { LIST_TYPES, PgCallStep, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectStep, TYPES, assertPgClassSingleStep, domainOfCodec, enumCodec, listOfCodec, makeRegistry, pgDeleteSingle, pgInsertSingle, pgSelectFromRecord, pgSelectFromRecords, pgSelectSingleFromRecord, pgUpdateSingle, rangeOfCodec, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; import { ConnectionStep, EdgeStep, ObjectStep, __ValueStep, access, assertStep, bakedInput, bakedInputRuntime, connection, constant, context, createObjectAndApplyChildren, first, get as get2, inhibitOnNull, inspect, lambda, list, makeDecodeNodeId, makeGrafastSchema, markSyncAndSafe, object, operationPlan, specFromNodeId, trap } from "grafast"; import { GraphQLError, GraphQLInt, GraphQLString, Kind, valueFromASTUntyped } from "graphql"; import jsonwebtoken from "jsonwebtoken"; @@ -1997,6 +1997,13 @@ const makeArgs_mult_1 = (args, path = []) => argDetailsSimple_mult_1.map(details const resource_mult_1PgResource = registry.pgResources["mult_1"]; function pgSelectFromPayload($payload) { const $result = $payload.getStepForKey("result"); + if ($result instanceof PgCallStep) { + // Procedures are invoked via `call`, not `select`, so there's no + // `PgSelectStep` to find. The call step itself is the target that + // nested `apply`-capable input fields (e.g. `clientMutationId`) + // should be applied to. + return $result; + } const $parent = "getParentStep" in $result ? $result.getParentStep() : $result; const $pgSelect = "getClassStep" in $parent ? $parent.getClassStep() : $parent; if ($pgSelect instanceof PgSelectStep) { @@ -2007,6 +2014,10 @@ function pgSelectFromPayload($payload) { } function applyInputArgViaPgSelect(_, $payload, arg) { const $pgSelect = pgSelectFromPayload($payload); + // `PgCallStep`'s `apply()` takes a `PgCallQueryBuilder` rather than a + // `PgSelectQueryBuilder`, but both satisfy the same structural `apply` + // protocol at runtime (only `clientMutationId` uses it here), so the + // precise `qb` type is immaterial to the caller. arg.apply($pgSelect); } const argDetailsSimple_mult_2 = [{ diff --git a/postgraphile/postgraphile/__tests__/schema/v4/noDefaultMutations.1.export.mjs b/postgraphile/postgraphile/__tests__/schema/v4/noDefaultMutations.1.export.mjs index a5de27d215..8ff7b5d708 100644 --- a/postgraphile/postgraphile/__tests__/schema/v4/noDefaultMutations.1.export.mjs +++ b/postgraphile/postgraphile/__tests__/schema/v4/noDefaultMutations.1.export.mjs @@ -1,4 +1,4 @@ -import { LIST_TYPES, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, domainOfCodec, enumCodec, listOfCodec, makeRegistry, pgClassExpression, pgFromExpression, pgSelectFromRecord, pgSelectFromRecords, pgSelectSingleFromRecord, rangeOfCodec, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; +import { LIST_TYPES, PgCallStep, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, domainOfCodec, enumCodec, listOfCodec, makeRegistry, pgClassExpression, pgFromExpression, pgSelectFromRecord, pgSelectFromRecords, pgSelectSingleFromRecord, rangeOfCodec, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; import { ConnectionStep, EdgeStep, ObjectStep, __ValueStep, access, assertStep, bakedInput, bakedInputRuntime, connection, constant, context, createObjectAndApplyChildren, first, get as get2, inhibitOnNull, inspect, lambda, list, makeDecodeNodeId, makeGrafastSchema, markSyncAndSafe, object, operationPlan, stepAMayDependOnStepB, trap } from "grafast"; import { GraphQLError, GraphQLInt, GraphQLString, Kind, valueFromASTUntyped } from "graphql"; import { sql } from "pg-sql2"; @@ -5035,6 +5035,13 @@ const resource_edge_case_computedPgResource = registry.pgResources["edge_case_co const resource_mutation_outPgResource = registry.pgResources["mutation_out"]; function pgSelectFromPayload($payload) { const $result = $payload.getStepForKey("result"); + if ($result instanceof PgCallStep) { + // Procedures are invoked via `call`, not `select`, so there's no + // `PgSelectStep` to find. The call step itself is the target that + // nested `apply`-capable input fields (e.g. `clientMutationId`) + // should be applied to. + return $result; + } const $parent = "getParentStep" in $result ? $result.getParentStep() : $result; const $pgSelect = "getClassStep" in $parent ? $parent.getClassStep() : $parent; if ($pgSelect instanceof PgSelectStep) { @@ -5045,6 +5052,10 @@ function pgSelectFromPayload($payload) { } function applyInputArgViaPgSelect(_, $payload, arg) { const $pgSelect = pgSelectFromPayload($payload); + // `PgCallStep`'s `apply()` takes a `PgCallQueryBuilder` rather than a + // `PgSelectQueryBuilder`, but both satisfy the same structural `apply` + // protocol at runtime (only `clientMutationId` uses it here), so the + // precise `qb` type is immaterial to the caller. arg.apply($pgSelect); } const resource_mutation_out_setofPgResource = registry.pgResources["mutation_out_setof"]; diff --git a/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.1.export.mjs b/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.1.export.mjs index c006cc5b60..50fdfc22b3 100644 --- a/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.1.export.mjs +++ b/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.1.export.mjs @@ -1,4 +1,4 @@ -import { PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, enumCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectSingleFromRecord, pgUpdateSingle, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; +import { PgCallStep, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, enumCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectSingleFromRecord, pgUpdateSingle, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; import { ConnectionStep, EdgeStep, ObjectStep, __ValueStep, access, assertStep, bakedInput, bakedInputRuntime, connection, constant, context, createObjectAndApplyChildren, first, get as get2, inhibitOnNull, inspect, lambda, list, makeDecodeNodeId, makeGrafastSchema, markSyncAndSafe, object, operationPlan, specFromNodeId, stepAMayDependOnStepB, trap } from "grafast"; import { GraphQLError, Kind } from "graphql"; import { sql } from "pg-sql2"; @@ -1443,6 +1443,13 @@ const PostsOrderBy_ID_DESCApply = queryBuilder => { const resource_getflamblePgResource = registry.pgResources["getflamble"]; function pgSelectFromPayload($payload) { const $result = $payload.getStepForKey("result"); + if ($result instanceof PgCallStep) { + // Procedures are invoked via `call`, not `select`, so there's no + // `PgSelectStep` to find. The call step itself is the target that + // nested `apply`-capable input fields (e.g. `clientMutationId`) + // should be applied to. + return $result; + } const $parent = "getParentStep" in $result ? $result.getParentStep() : $result; const $pgSelect = "getClassStep" in $parent ? $parent.getClassStep() : $parent; if ($pgSelect instanceof PgSelectStep) { @@ -1453,6 +1460,10 @@ function pgSelectFromPayload($payload) { } function applyInputArgViaPgSelect(_, $payload, arg) { const $pgSelect = pgSelectFromPayload($payload); + // `PgCallStep`'s `apply()` takes a `PgCallQueryBuilder` rather than a + // `PgSelectQueryBuilder`, but both satisfy the same structural `apply` + // protocol at runtime (only `clientMutationId` uses it here), so the + // precise `qb` type is immaterial to the caller. arg.apply($pgSelect); } const argDetailsSimple_login = [{ diff --git a/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitcolumns.1.export.mjs b/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitcolumns.1.export.mjs index d5a9669e14..4169ce5cf9 100644 --- a/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitcolumns.1.export.mjs +++ b/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitcolumns.1.export.mjs @@ -1,4 +1,4 @@ -import { PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, enumCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectSingleFromRecord, pgUpdateSingle, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; +import { PgCallStep, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, enumCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectSingleFromRecord, pgUpdateSingle, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; import { ConnectionStep, EdgeStep, ObjectStep, __ValueStep, access, assertStep, bakedInput, bakedInputRuntime, connection, constant, context, createObjectAndApplyChildren, first, get as get2, inhibitOnNull, inspect, lambda, list, makeDecodeNodeId, makeGrafastSchema, markSyncAndSafe, object, operationPlan, specFromNodeId, stepAMayDependOnStepB, trap } from "grafast"; import { GraphQLError, Kind } from "graphql"; import { sql } from "pg-sql2"; @@ -1449,6 +1449,13 @@ const PostsOrderBy_ID_DESCApply = queryBuilder => { const resource_getflamblePgResource = registry.pgResources["getflamble"]; function pgSelectFromPayload($payload) { const $result = $payload.getStepForKey("result"); + if ($result instanceof PgCallStep) { + // Procedures are invoked via `call`, not `select`, so there's no + // `PgSelectStep` to find. The call step itself is the target that + // nested `apply`-capable input fields (e.g. `clientMutationId`) + // should be applied to. + return $result; + } const $parent = "getParentStep" in $result ? $result.getParentStep() : $result; const $pgSelect = "getClassStep" in $parent ? $parent.getClassStep() : $parent; if ($pgSelect instanceof PgSelectStep) { @@ -1459,6 +1466,10 @@ function pgSelectFromPayload($payload) { } function applyInputArgViaPgSelect(_, $payload, arg) { const $pgSelect = pgSelectFromPayload($payload); + // `PgCallStep`'s `apply()` takes a `PgCallQueryBuilder` rather than a + // `PgSelectQueryBuilder`, but both satisfy the same structural `apply` + // protocol at runtime (only `clientMutationId` uses it here), so the + // precise `qb` type is immaterial to the caller. arg.apply($pgSelect); } const argDetailsSimple_login = [{ diff --git a/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitcolumns.execute.1.export.mjs b/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitcolumns.execute.1.export.mjs index e45c2bc62c..e39ac8cd32 100644 --- a/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitcolumns.execute.1.export.mjs +++ b/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitcolumns.execute.1.export.mjs @@ -1,4 +1,4 @@ -import { PgDeleteSingleStep, PgExecutor, PgResource, PgSelectStep, TYPES, assertPgClassSingleStep, enumCodec, makeRegistry, pgDeleteSingle, pgInsertSingle, pgSelectFromRecord, pgUpdateSingle, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; +import { PgCallStep, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectStep, TYPES, assertPgClassSingleStep, enumCodec, makeRegistry, pgDeleteSingle, pgInsertSingle, pgSelectFromRecord, pgUpdateSingle, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; import { ConnectionStep, EdgeStep, ObjectStep, __ValueStep, access, assertStep, bakedInput, bakedInputRuntime, connection, constant, context, createObjectAndApplyChildren, first, get as get2, inhibitOnNull, inspect, lambda, list, makeDecodeNodeId, makeGrafastSchema, markSyncAndSafe, object, operationPlan, specFromNodeId, trap } from "grafast"; import { GraphQLError, Kind } from "graphql"; import { sql } from "pg-sql2"; @@ -1367,6 +1367,13 @@ const PostsOrderBy_ID_DESCApply = queryBuilder => { const resource_getflamblePgResource = registry.pgResources["getflamble"]; function pgSelectFromPayload($payload) { const $result = $payload.getStepForKey("result"); + if ($result instanceof PgCallStep) { + // Procedures are invoked via `call`, not `select`, so there's no + // `PgSelectStep` to find. The call step itself is the target that + // nested `apply`-capable input fields (e.g. `clientMutationId`) + // should be applied to. + return $result; + } const $parent = "getParentStep" in $result ? $result.getParentStep() : $result; const $pgSelect = "getClassStep" in $parent ? $parent.getClassStep() : $parent; if ($pgSelect instanceof PgSelectStep) { @@ -1377,6 +1384,10 @@ function pgSelectFromPayload($payload) { } function applyInputArgViaPgSelect(_, $payload, arg) { const $pgSelect = pgSelectFromPayload($payload); + // `PgCallStep`'s `apply()` takes a `PgCallQueryBuilder` rather than a + // `PgSelectQueryBuilder`, but both satisfy the same structural `apply` + // protocol at runtime (only `clientMutationId` uses it here), so the + // precise `qb` type is immaterial to the caller. arg.apply($pgSelect); } const argDetailsSimple_login = [{ diff --git a/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitcolumns.loads-title.1.export.mjs b/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitcolumns.loads-title.1.export.mjs index 2dc93b4a2f..5c8e6cc18e 100644 --- a/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitcolumns.loads-title.1.export.mjs +++ b/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitcolumns.loads-title.1.export.mjs @@ -1,4 +1,4 @@ -import { PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, enumCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectSingleFromRecord, pgUpdateSingle, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; +import { PgCallStep, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, enumCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectSingleFromRecord, pgUpdateSingle, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; import { ConnectionStep, EdgeStep, ObjectStep, __ValueStep, access, assertStep, bakedInput, bakedInputRuntime, connection, constant, context, createObjectAndApplyChildren, first, get as get2, inhibitOnNull, inspect, lambda, list, makeDecodeNodeId, makeGrafastSchema, markSyncAndSafe, object, operationPlan, specFromNodeId, stepAMayDependOnStepB, trap } from "grafast"; import { GraphQLError, Kind } from "graphql"; import { sql } from "pg-sql2"; @@ -1449,6 +1449,13 @@ const PostsOrderBy_ID_DESCApply = queryBuilder => { const resource_getflamblePgResource = registry.pgResources["getflamble"]; function pgSelectFromPayload($payload) { const $result = $payload.getStepForKey("result"); + if ($result instanceof PgCallStep) { + // Procedures are invoked via `call`, not `select`, so there's no + // `PgSelectStep` to find. The call step itself is the target that + // nested `apply`-capable input fields (e.g. `clientMutationId`) + // should be applied to. + return $result; + } const $parent = "getParentStep" in $result ? $result.getParentStep() : $result; const $pgSelect = "getClassStep" in $parent ? $parent.getClassStep() : $parent; if ($pgSelect instanceof PgSelectStep) { @@ -1459,6 +1466,10 @@ function pgSelectFromPayload($payload) { } function applyInputArgViaPgSelect(_, $payload, arg) { const $pgSelect = pgSelectFromPayload($payload); + // `PgCallStep`'s `apply()` takes a `PgCallQueryBuilder` rather than a + // `PgSelectQueryBuilder`, but both satisfy the same structural `apply` + // protocol at runtime (only `clientMutationId` uses it here), so the + // precise `qb` type is immaterial to the caller. arg.apply($pgSelect); } const argDetailsSimple_login = [{ diff --git a/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitcolumns.shows-title-asterisk.1.export.mjs b/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitcolumns.shows-title-asterisk.1.export.mjs index dcdc165518..16aa21336c 100644 --- a/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitcolumns.shows-title-asterisk.1.export.mjs +++ b/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitcolumns.shows-title-asterisk.1.export.mjs @@ -1,4 +1,4 @@ -import { PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, enumCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectSingleFromRecord, pgUpdateSingle, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; +import { PgCallStep, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, enumCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectSingleFromRecord, pgUpdateSingle, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; import { ConnectionStep, EdgeStep, ObjectStep, __ValueStep, access, assertStep, bakedInput, bakedInputRuntime, connection, constant, context, createObjectAndApplyChildren, first, get as get2, inhibitOnNull, inspect, lambda, list, makeDecodeNodeId, makeGrafastSchema, markSyncAndSafe, object, operationPlan, specFromNodeId, stepAMayDependOnStepB, trap } from "grafast"; import { GraphQLError, Kind } from "graphql"; import { sql } from "pg-sql2"; @@ -1442,6 +1442,13 @@ const PostsOrderBy_ID_DESCApply = queryBuilder => { const resource_getflamblePgResource = registry.pgResources["getflamble"]; function pgSelectFromPayload($payload) { const $result = $payload.getStepForKey("result"); + if ($result instanceof PgCallStep) { + // Procedures are invoked via `call`, not `select`, so there's no + // `PgSelectStep` to find. The call step itself is the target that + // nested `apply`-capable input fields (e.g. `clientMutationId`) + // should be applied to. + return $result; + } const $parent = "getParentStep" in $result ? $result.getParentStep() : $result; const $pgSelect = "getClassStep" in $parent ? $parent.getClassStep() : $parent; if ($pgSelect instanceof PgSelectStep) { @@ -1452,6 +1459,10 @@ function pgSelectFromPayload($payload) { } function applyInputArgViaPgSelect(_, $payload, arg) { const $pgSelect = pgSelectFromPayload($payload); + // `PgCallStep`'s `apply()` takes a `PgCallQueryBuilder` rather than a + // `PgSelectQueryBuilder`, but both satisfy the same structural `apply` + // protocol at runtime (only `clientMutationId` uses it here), so the + // precise `qb` type is immaterial to the caller. arg.apply($pgSelect); } const argDetailsSimple_login = [{ diff --git a/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitcolumns.title-order.1.export.mjs b/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitcolumns.title-order.1.export.mjs index ef7cc4ef67..3fa8789ea5 100644 --- a/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitcolumns.title-order.1.export.mjs +++ b/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitcolumns.title-order.1.export.mjs @@ -1,4 +1,4 @@ -import { PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, enumCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectSingleFromRecord, pgUpdateSingle, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; +import { PgCallStep, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, enumCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectSingleFromRecord, pgUpdateSingle, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; import { ConnectionStep, EdgeStep, ObjectStep, __ValueStep, access, assertStep, bakedInput, bakedInputRuntime, connection, constant, context, createObjectAndApplyChildren, first, get as get2, inhibitOnNull, inspect, lambda, list, makeDecodeNodeId, makeGrafastSchema, markSyncAndSafe, object, operationPlan, specFromNodeId, stepAMayDependOnStepB, trap } from "grafast"; import { GraphQLError, Kind } from "graphql"; import { sql } from "pg-sql2"; @@ -1449,6 +1449,13 @@ const PostsOrderBy_ID_DESCApply = queryBuilder => { const resource_getflamblePgResource = registry.pgResources["getflamble"]; function pgSelectFromPayload($payload) { const $result = $payload.getStepForKey("result"); + if ($result instanceof PgCallStep) { + // Procedures are invoked via `call`, not `select`, so there's no + // `PgSelectStep` to find. The call step itself is the target that + // nested `apply`-capable input fields (e.g. `clientMutationId`) + // should be applied to. + return $result; + } const $parent = "getParentStep" in $result ? $result.getParentStep() : $result; const $pgSelect = "getClassStep" in $parent ? $parent.getClassStep() : $parent; if ($pgSelect instanceof PgSelectStep) { @@ -1459,6 +1466,10 @@ function pgSelectFromPayload($payload) { } function applyInputArgViaPgSelect(_, $payload, arg) { const $pgSelect = pgSelectFromPayload($payload); + // `PgCallStep`'s `apply()` takes a `PgCallQueryBuilder` rather than a + // `PgSelectQueryBuilder`, but both satisfy the same structural `apply` + // protocol at runtime (only `clientMutationId` uses it here), so the + // precise `qb` type is immaterial to the caller. arg.apply($pgSelect); } const argDetailsSimple_login = [{ diff --git a/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitcolumns.update-title.1.export.mjs b/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitcolumns.update-title.1.export.mjs index b6a0b1011c..ce2f690dd2 100644 --- a/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitcolumns.update-title.1.export.mjs +++ b/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitcolumns.update-title.1.export.mjs @@ -1,4 +1,4 @@ -import { PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, enumCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectSingleFromRecord, pgUpdateSingle, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; +import { PgCallStep, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, enumCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectSingleFromRecord, pgUpdateSingle, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; import { ConnectionStep, EdgeStep, ObjectStep, __ValueStep, access, assertStep, bakedInput, bakedInputRuntime, connection, constant, context, createObjectAndApplyChildren, first, get as get2, inhibitOnNull, inspect, lambda, list, makeDecodeNodeId, makeGrafastSchema, markSyncAndSafe, object, operationPlan, specFromNodeId, stepAMayDependOnStepB, trap } from "grafast"; import { GraphQLError, Kind } from "graphql"; import { sql } from "pg-sql2"; @@ -1449,6 +1449,13 @@ const PostsOrderBy_ID_DESCApply = queryBuilder => { const resource_getflamblePgResource = registry.pgResources["getflamble"]; function pgSelectFromPayload($payload) { const $result = $payload.getStepForKey("result"); + if ($result instanceof PgCallStep) { + // Procedures are invoked via `call`, not `select`, so there's no + // `PgSelectStep` to find. The call step itself is the target that + // nested `apply`-capable input fields (e.g. `clientMutationId`) + // should be applied to. + return $result; + } const $parent = "getParentStep" in $result ? $result.getParentStep() : $result; const $pgSelect = "getClassStep" in $parent ? $parent.getClassStep() : $parent; if ($pgSelect instanceof PgSelectStep) { @@ -1459,6 +1466,10 @@ function pgSelectFromPayload($payload) { } function applyInputArgViaPgSelect(_, $payload, arg) { const $pgSelect = pgSelectFromPayload($payload); + // `PgCallStep`'s `apply()` takes a `PgCallQueryBuilder` rather than a + // `PgSelectQueryBuilder`, but both satisfy the same structural `apply` + // protocol at runtime (only `clientMutationId` uses it here), so the + // precise `qb` type is immaterial to the caller. arg.apply($pgSelect); } const argDetailsSimple_login = [{ diff --git a/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitstuff.1.export.mjs b/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitstuff.1.export.mjs index 29f72cf469..4e044edcca 100644 --- a/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitstuff.1.export.mjs +++ b/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitstuff.1.export.mjs @@ -1,4 +1,4 @@ -import { PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, enumCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectSingleFromRecord, pgUpdateSingle, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; +import { PgCallStep, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, enumCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectSingleFromRecord, pgUpdateSingle, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; import { ConnectionStep, EdgeStep, ObjectStep, __ValueStep, access, assertStep, bakedInput, bakedInputRuntime, connection, constant, context, createObjectAndApplyChildren, first, get as get2, inhibitOnNull, inspect, lambda, list, makeDecodeNodeId, makeGrafastSchema, markSyncAndSafe, object, operationPlan, specFromNodeId, stepAMayDependOnStepB, trap } from "grafast"; import { GraphQLError, Kind } from "graphql"; import { sql } from "pg-sql2"; @@ -1452,6 +1452,13 @@ const PostsOrderBy_ID_DESCApply = queryBuilder => { const resource_getflamblePgResource = registry.pgResources["getflamble"]; function pgSelectFromPayload($payload) { const $result = $payload.getStepForKey("result"); + if ($result instanceof PgCallStep) { + // Procedures are invoked via `call`, not `select`, so there's no + // `PgSelectStep` to find. The call step itself is the target that + // nested `apply`-capable input fields (e.g. `clientMutationId`) + // should be applied to. + return $result; + } const $parent = "getParentStep" in $result ? $result.getParentStep() : $result; const $pgSelect = "getClassStep" in $parent ? $parent.getClassStep() : $parent; if ($pgSelect instanceof PgSelectStep) { @@ -1462,6 +1469,10 @@ function pgSelectFromPayload($payload) { } function applyInputArgViaPgSelect(_, $payload, arg) { const $pgSelect = pgSelectFromPayload($payload); + // `PgCallStep`'s `apply()` takes a `PgCallQueryBuilder` rather than a + // `PgSelectQueryBuilder`, but both satisfy the same structural `apply` + // protocol at runtime (only `clientMutationId` uses it here), so the + // precise `qb` type is immaterial to the caller. arg.apply($pgSelect); } const argDetailsSimple_login = [{ diff --git a/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitstuff.constraints.1.export.mjs b/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitstuff.constraints.1.export.mjs index d6bf6a7ed3..6f83eefcd3 100644 --- a/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitstuff.constraints.1.export.mjs +++ b/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitstuff.constraints.1.export.mjs @@ -1,4 +1,4 @@ -import { PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, enumCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectSingleFromRecord, pgUpdateSingle, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; +import { PgCallStep, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, enumCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectSingleFromRecord, pgUpdateSingle, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; import { ConnectionStep, EdgeStep, ObjectStep, __ValueStep, access, assertStep, bakedInput, bakedInputRuntime, connection, constant, context, createObjectAndApplyChildren, first, get as get2, inhibitOnNull, inspect, lambda, list, makeDecodeNodeId, makeGrafastSchema, markSyncAndSafe, object, operationPlan, specFromNodeId, stepAMayDependOnStepB, trap } from "grafast"; import { GraphQLError, Kind } from "graphql"; import { sql } from "pg-sql2"; @@ -1466,6 +1466,13 @@ const StudiosOrderBy_ID_DESCApply = queryBuilder => { const resource_getflamblePgResource = registry.pgResources["getflamble"]; function pgSelectFromPayload($payload) { const $result = $payload.getStepForKey("result"); + if ($result instanceof PgCallStep) { + // Procedures are invoked via `call`, not `select`, so there's no + // `PgSelectStep` to find. The call step itself is the target that + // nested `apply`-capable input fields (e.g. `clientMutationId`) + // should be applied to. + return $result; + } const $parent = "getParentStep" in $result ? $result.getParentStep() : $result; const $pgSelect = "getClassStep" in $parent ? $parent.getClassStep() : $parent; if ($pgSelect instanceof PgSelectStep) { @@ -1476,6 +1483,10 @@ function pgSelectFromPayload($payload) { } function applyInputArgViaPgSelect(_, $payload, arg) { const $pgSelect = pgSelectFromPayload($payload); + // `PgCallStep`'s `apply()` takes a `PgCallQueryBuilder` rather than a + // `PgSelectQueryBuilder`, but both satisfy the same structural `apply` + // protocol at runtime (only `clientMutationId` uses it here), so the + // precise `qb` type is immaterial to the caller. arg.apply($pgSelect); } const argDetailsSimple_login = [{ diff --git a/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitstuff.films-asterisk.1.export.mjs b/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitstuff.films-asterisk.1.export.mjs index 6748b8cd1d..f68fc099e3 100644 --- a/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitstuff.films-asterisk.1.export.mjs +++ b/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitstuff.films-asterisk.1.export.mjs @@ -1,4 +1,4 @@ -import { PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, enumCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectSingleFromRecord, pgUpdateSingle, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; +import { PgCallStep, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, enumCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectSingleFromRecord, pgUpdateSingle, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; import { ConnectionStep, EdgeStep, ObjectStep, __ValueStep, access, assertStep, bakedInput, bakedInputRuntime, connection, constant, context, createObjectAndApplyChildren, first, get as get2, inhibitOnNull, inspect, lambda, list, makeDecodeNodeId, makeGrafastSchema, markSyncAndSafe, object, operationPlan, specFromNodeId, stepAMayDependOnStepB, trap } from "grafast"; import { GraphQLError, Kind } from "graphql"; import { sql } from "pg-sql2"; @@ -1405,6 +1405,13 @@ const PostsOrderBy_ID_DESCApply = queryBuilder => { const resource_getflamblePgResource = registry.pgResources["getflamble"]; function pgSelectFromPayload($payload) { const $result = $payload.getStepForKey("result"); + if ($result instanceof PgCallStep) { + // Procedures are invoked via `call`, not `select`, so there's no + // `PgSelectStep` to find. The call step itself is the target that + // nested `apply`-capable input fields (e.g. `clientMutationId`) + // should be applied to. + return $result; + } const $parent = "getParentStep" in $result ? $result.getParentStep() : $result; const $pgSelect = "getClassStep" in $parent ? $parent.getClassStep() : $parent; if ($pgSelect instanceof PgSelectStep) { @@ -1415,6 +1422,10 @@ function pgSelectFromPayload($payload) { } function applyInputArgViaPgSelect(_, $payload, arg) { const $pgSelect = pgSelectFromPayload($payload); + // `PgCallStep`'s `apply()` takes a `PgCallQueryBuilder` rather than a + // `PgSelectQueryBuilder`, but both satisfy the same structural `apply` + // protocol at runtime (only `clientMutationId` uses it here), so the + // precise `qb` type is immaterial to the caller. arg.apply($pgSelect); } const argDetailsSimple_login = [{ diff --git a/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitstuff.films-create.1.export.mjs b/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitstuff.films-create.1.export.mjs index 3dbcd9274a..8561dcbed9 100644 --- a/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitstuff.films-create.1.export.mjs +++ b/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitstuff.films-create.1.export.mjs @@ -1,4 +1,4 @@ -import { PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, enumCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectSingleFromRecord, pgUpdateSingle, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; +import { PgCallStep, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, enumCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectSingleFromRecord, pgUpdateSingle, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; import { ConnectionStep, EdgeStep, ObjectStep, __ValueStep, access, assertStep, bakedInput, bakedInputRuntime, connection, constant, context, createObjectAndApplyChildren, first, get as get2, inhibitOnNull, inspect, lambda, list, makeDecodeNodeId, makeGrafastSchema, markSyncAndSafe, object, operationPlan, specFromNodeId, stepAMayDependOnStepB, trap } from "grafast"; import { GraphQLError, Kind } from "graphql"; import { sql } from "pg-sql2"; @@ -1452,6 +1452,13 @@ const PostsOrderBy_ID_DESCApply = queryBuilder => { const resource_getflamblePgResource = registry.pgResources["getflamble"]; function pgSelectFromPayload($payload) { const $result = $payload.getStepForKey("result"); + if ($result instanceof PgCallStep) { + // Procedures are invoked via `call`, not `select`, so there's no + // `PgSelectStep` to find. The call step itself is the target that + // nested `apply`-capable input fields (e.g. `clientMutationId`) + // should be applied to. + return $result; + } const $parent = "getParentStep" in $result ? $result.getParentStep() : $result; const $pgSelect = "getClassStep" in $parent ? $parent.getClassStep() : $parent; if ($pgSelect instanceof PgSelectStep) { @@ -1462,6 +1469,10 @@ function pgSelectFromPayload($payload) { } function applyInputArgViaPgSelect(_, $payload, arg) { const $pgSelect = pgSelectFromPayload($payload); + // `PgCallStep`'s `apply()` takes a `PgCallQueryBuilder` rather than a + // `PgSelectQueryBuilder`, but both satisfy the same structural `apply` + // protocol at runtime (only `clientMutationId` uses it here), so the + // precise `qb` type is immaterial to the caller. arg.apply($pgSelect); } const argDetailsSimple_login = [{ diff --git a/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitstuff.films-delete.1.export.mjs b/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitstuff.films-delete.1.export.mjs index 0e9450871a..6784a467b4 100644 --- a/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitstuff.films-delete.1.export.mjs +++ b/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitstuff.films-delete.1.export.mjs @@ -1,4 +1,4 @@ -import { PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, enumCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectSingleFromRecord, pgUpdateSingle, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; +import { PgCallStep, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, enumCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectSingleFromRecord, pgUpdateSingle, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; import { ConnectionStep, EdgeStep, ObjectStep, __ValueStep, access, assertStep, bakedInput, bakedInputRuntime, connection, constant, context, createObjectAndApplyChildren, first, get as get2, inhibitOnNull, inspect, lambda, list, makeDecodeNodeId, makeGrafastSchema, markSyncAndSafe, object, operationPlan, specFromNodeId, stepAMayDependOnStepB, trap } from "grafast"; import { GraphQLError, Kind } from "graphql"; import { sql } from "pg-sql2"; @@ -1452,6 +1452,13 @@ const PostsOrderBy_ID_DESCApply = queryBuilder => { const resource_getflamblePgResource = registry.pgResources["getflamble"]; function pgSelectFromPayload($payload) { const $result = $payload.getStepForKey("result"); + if ($result instanceof PgCallStep) { + // Procedures are invoked via `call`, not `select`, so there's no + // `PgSelectStep` to find. The call step itself is the target that + // nested `apply`-capable input fields (e.g. `clientMutationId`) + // should be applied to. + return $result; + } const $parent = "getParentStep" in $result ? $result.getParentStep() : $result; const $pgSelect = "getClassStep" in $parent ? $parent.getClassStep() : $parent; if ($pgSelect instanceof PgSelectStep) { @@ -1462,6 +1469,10 @@ function pgSelectFromPayload($payload) { } function applyInputArgViaPgSelect(_, $payload, arg) { const $pgSelect = pgSelectFromPayload($payload); + // `PgCallStep`'s `apply()` takes a `PgCallQueryBuilder` rather than a + // `PgSelectQueryBuilder`, but both satisfy the same structural `apply` + // protocol at runtime (only `clientMutationId` uses it here), so the + // precise `qb` type is immaterial to the caller. arg.apply($pgSelect); } const argDetailsSimple_login = [{ diff --git a/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitstuff.films-loads.1.export.mjs b/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitstuff.films-loads.1.export.mjs index 8f9bf0d9db..c6389e6e5e 100644 --- a/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitstuff.films-loads.1.export.mjs +++ b/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitstuff.films-loads.1.export.mjs @@ -1,4 +1,4 @@ -import { PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, enumCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectSingleFromRecord, pgUpdateSingle, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; +import { PgCallStep, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, enumCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectSingleFromRecord, pgUpdateSingle, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; import { ConnectionStep, EdgeStep, ObjectStep, __ValueStep, access, assertStep, bakedInput, bakedInputRuntime, connection, constant, context, createObjectAndApplyChildren, first, get as get2, inhibitOnNull, inspect, lambda, list, makeDecodeNodeId, makeGrafastSchema, markSyncAndSafe, object, operationPlan, specFromNodeId, stepAMayDependOnStepB, trap } from "grafast"; import { GraphQLError, Kind } from "graphql"; import { sql } from "pg-sql2"; @@ -1438,6 +1438,13 @@ const PostsOrderBy_ID_DESCApply = queryBuilder => { const resource_getflamblePgResource = registry.pgResources["getflamble"]; function pgSelectFromPayload($payload) { const $result = $payload.getStepForKey("result"); + if ($result instanceof PgCallStep) { + // Procedures are invoked via `call`, not `select`, so there's no + // `PgSelectStep` to find. The call step itself is the target that + // nested `apply`-capable input fields (e.g. `clientMutationId`) + // should be applied to. + return $result; + } const $parent = "getParentStep" in $result ? $result.getParentStep() : $result; const $pgSelect = "getClassStep" in $parent ? $parent.getClassStep() : $parent; if ($pgSelect instanceof PgSelectStep) { @@ -1448,6 +1455,10 @@ function pgSelectFromPayload($payload) { } function applyInputArgViaPgSelect(_, $payload, arg) { const $pgSelect = pgSelectFromPayload($payload); + // `PgCallStep`'s `apply()` takes a `PgCallQueryBuilder` rather than a + // `PgSelectQueryBuilder`, but both satisfy the same structural `apply` + // protocol at runtime (only `clientMutationId` uses it here), so the + // precise `qb` type is immaterial to the caller. arg.apply($pgSelect); } const argDetailsSimple_login = [{ diff --git a/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitstuff.films-update.1.export.mjs b/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitstuff.films-update.1.export.mjs index 43507443ad..e2f5fa47df 100644 --- a/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitstuff.films-update.1.export.mjs +++ b/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitstuff.films-update.1.export.mjs @@ -1,4 +1,4 @@ -import { PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, enumCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectSingleFromRecord, pgUpdateSingle, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; +import { PgCallStep, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, enumCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectSingleFromRecord, pgUpdateSingle, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; import { ConnectionStep, EdgeStep, ObjectStep, __ValueStep, access, assertStep, bakedInput, bakedInputRuntime, connection, constant, context, createObjectAndApplyChildren, first, get as get2, inhibitOnNull, inspect, lambda, list, makeDecodeNodeId, makeGrafastSchema, markSyncAndSafe, object, operationPlan, specFromNodeId, stepAMayDependOnStepB, trap } from "grafast"; import { GraphQLError, Kind } from "graphql"; import { sql } from "pg-sql2"; @@ -1452,6 +1452,13 @@ const PostsOrderBy_ID_DESCApply = queryBuilder => { const resource_getflamblePgResource = registry.pgResources["getflamble"]; function pgSelectFromPayload($payload) { const $result = $payload.getStepForKey("result"); + if ($result instanceof PgCallStep) { + // Procedures are invoked via `call`, not `select`, so there's no + // `PgSelectStep` to find. The call step itself is the target that + // nested `apply`-capable input fields (e.g. `clientMutationId`) + // should be applied to. + return $result; + } const $parent = "getParentStep" in $result ? $result.getParentStep() : $result; const $pgSelect = "getClassStep" in $parent ? $parent.getClassStep() : $parent; if ($pgSelect instanceof PgSelectStep) { @@ -1462,6 +1469,10 @@ function pgSelectFromPayload($payload) { } function applyInputArgViaPgSelect(_, $payload, arg) { const $pgSelect = pgSelectFromPayload($payload); + // `PgCallStep`'s `apply()` takes a `PgCallQueryBuilder` rather than a + // `PgSelectQueryBuilder`, but both satisfy the same structural `apply` + // protocol at runtime (only `clientMutationId` uses it here), so the + // precise `qb` type is immaterial to the caller. arg.apply($pgSelect); } const argDetailsSimple_login = [{ diff --git a/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitstuff.shows-order.1.export.mjs b/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitstuff.shows-order.1.export.mjs index 65d76f9e2d..e80d4c49f6 100644 --- a/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitstuff.shows-order.1.export.mjs +++ b/postgraphile/postgraphile/__tests__/schema/v4/omit-rename.omitstuff.shows-order.1.export.mjs @@ -1,4 +1,4 @@ -import { PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, enumCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectSingleFromRecord, pgUpdateSingle, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; +import { PgCallStep, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, enumCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectSingleFromRecord, pgUpdateSingle, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; import { ConnectionStep, EdgeStep, ObjectStep, __ValueStep, access, assertStep, bakedInput, bakedInputRuntime, connection, constant, context, createObjectAndApplyChildren, first, get as get2, inhibitOnNull, inspect, lambda, list, makeDecodeNodeId, makeGrafastSchema, markSyncAndSafe, object, operationPlan, specFromNodeId, stepAMayDependOnStepB, trap } from "grafast"; import { GraphQLError, Kind } from "graphql"; import { sql } from "pg-sql2"; @@ -1452,6 +1452,13 @@ const PostsOrderBy_ID_DESCApply = queryBuilder => { const resource_getflamblePgResource = registry.pgResources["getflamble"]; function pgSelectFromPayload($payload) { const $result = $payload.getStepForKey("result"); + if ($result instanceof PgCallStep) { + // Procedures are invoked via `call`, not `select`, so there's no + // `PgSelectStep` to find. The call step itself is the target that + // nested `apply`-capable input fields (e.g. `clientMutationId`) + // should be applied to. + return $result; + } const $parent = "getParentStep" in $result ? $result.getParentStep() : $result; const $pgSelect = "getClassStep" in $parent ? $parent.getClassStep() : $parent; if ($pgSelect instanceof PgSelectStep) { @@ -1462,6 +1469,10 @@ function pgSelectFromPayload($payload) { } function applyInputArgViaPgSelect(_, $payload, arg) { const $pgSelect = pgSelectFromPayload($payload); + // `PgCallStep`'s `apply()` takes a `PgCallQueryBuilder` rather than a + // `PgSelectQueryBuilder`, but both satisfy the same structural `apply` + // protocol at runtime (only `clientMutationId` uses it here), so the + // precise `qb` type is immaterial to the caller. arg.apply($pgSelect); } const argDetailsSimple_login = [{ diff --git a/postgraphile/postgraphile/__tests__/schema/v4/pgStrictFunctions.1.export.mjs b/postgraphile/postgraphile/__tests__/schema/v4/pgStrictFunctions.1.export.mjs index e9edbba033..da32fd6ad9 100644 --- a/postgraphile/postgraphile/__tests__/schema/v4/pgStrictFunctions.1.export.mjs +++ b/postgraphile/postgraphile/__tests__/schema/v4/pgStrictFunctions.1.export.mjs @@ -1,4 +1,4 @@ -import { LIST_TYPES, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, domainOfCodec, enumCodec, listOfCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectFromRecords, pgSelectSingleFromRecord, pgUpdateSingle, rangeOfCodec, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; +import { LIST_TYPES, PgCallStep, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, domainOfCodec, enumCodec, listOfCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectFromRecords, pgSelectSingleFromRecord, pgUpdateSingle, rangeOfCodec, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; import { ConnectionStep, EdgeStep, ObjectStep, __ValueStep, access, assertStep, bakedInput, bakedInputRuntime, connection, constant, context, createObjectAndApplyChildren, first, get as get2, inhibitOnNull, inspect, lambda, list, makeDecodeNodeId, makeGrafastSchema, markSyncAndSafe, object, operationPlan, specFromNodeId, stepAMayDependOnStepB, trap } from "grafast"; import { GraphQLError, GraphQLInt, GraphQLString, Kind, valueFromASTUntyped } from "graphql"; import { sql } from "pg-sql2"; @@ -8230,6 +8230,13 @@ const resource_edge_case_computedPgResource = registry.pgResources["edge_case_co const resource_mutation_outPgResource = registry.pgResources["mutation_out"]; function pgSelectFromPayload($payload) { const $result = $payload.getStepForKey("result"); + if ($result instanceof PgCallStep) { + // Procedures are invoked via `call`, not `select`, so there's no + // `PgSelectStep` to find. The call step itself is the target that + // nested `apply`-capable input fields (e.g. `clientMutationId`) + // should be applied to. + return $result; + } const $parent = "getParentStep" in $result ? $result.getParentStep() : $result; const $pgSelect = "getClassStep" in $parent ? $parent.getClassStep() : $parent; if ($pgSelect instanceof PgSelectStep) { @@ -8240,6 +8247,10 @@ function pgSelectFromPayload($payload) { } function applyInputArgViaPgSelect(_, $payload, arg) { const $pgSelect = pgSelectFromPayload($payload); + // `PgCallStep`'s `apply()` takes a `PgCallQueryBuilder` rather than a + // `PgSelectQueryBuilder`, but both satisfy the same structural `apply` + // protocol at runtime (only `clientMutationId` uses it here), so the + // precise `qb` type is immaterial to the caller. arg.apply($pgSelect); } const resource_mutation_out_setofPgResource = registry.pgResources["mutation_out_setof"]; diff --git a/postgraphile/postgraphile/__tests__/schema/v4/polymorphic.1.export.mjs b/postgraphile/postgraphile/__tests__/schema/v4/polymorphic.1.export.mjs index 1b3bd4627a..41d153e680 100644 --- a/postgraphile/postgraphile/__tests__/schema/v4/polymorphic.1.export.mjs +++ b/postgraphile/postgraphile/__tests__/schema/v4/polymorphic.1.export.mjs @@ -1,4 +1,4 @@ -import { PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, PgUnionAllSingleStep, TYPES, assertPgClassSingleStep, enumCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectSingleFromRecord, pgUnionAll, pgUpdateSingle, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; +import { PgCallStep, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, PgUnionAllSingleStep, TYPES, assertPgClassSingleStep, enumCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectSingleFromRecord, pgUnionAll, pgUpdateSingle, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; import { ConnectionStep, ConstantStep, EdgeStep, ObjectStep, __ValueStep, access, assertStep, bakedInput, bakedInputRuntime, connection, constant, context, createObjectAndApplyChildren, first, get as get2, inhibitOnNull, inspect, lambda, list, makeDecodeNodeId, makeGrafastSchema, markSyncAndSafe, object, operationPlan, specFromNodeId, stepAMayDependOnStepB, trap } from "grafast"; import { GraphQLError, Kind } from "graphql"; import { sql } from "pg-sql2"; @@ -6061,6 +6061,13 @@ const makeArgs_custom_delete_relational_item = (args, path = []) => argDetailsSi const resource_custom_delete_relational_itemPgResource = registry.pgResources["custom_delete_relational_item"]; function pgSelectFromPayload($payload) { const $result = $payload.getStepForKey("result"); + if ($result instanceof PgCallStep) { + // Procedures are invoked via `call`, not `select`, so there's no + // `PgSelectStep` to find. The call step itself is the target that + // nested `apply`-capable input fields (e.g. `clientMutationId`) + // should be applied to. + return $result; + } const $parent = "getParentStep" in $result ? $result.getParentStep() : $result; const $pgSelect = "getClassStep" in $parent ? $parent.getClassStep() : $parent; if ($pgSelect instanceof PgSelectStep) { @@ -16075,6 +16082,10 @@ export const objects = { args: { input(_, $payload, arg) { const $pgSelect = pgSelectFromPayload($payload); + // `PgCallStep`'s `apply()` takes a `PgCallQueryBuilder` rather than a + // `PgSelectQueryBuilder`, but both satisfy the same structural `apply` + // protocol at runtime (only `clientMutationId` uses it here), so the + // precise `qb` type is immaterial to the caller. arg.apply($pgSelect); } } diff --git a/postgraphile/postgraphile/__tests__/schema/v4/rbac.1.export.mjs b/postgraphile/postgraphile/__tests__/schema/v4/rbac.1.export.mjs index 3fbe7944e3..66d53650b8 100644 --- a/postgraphile/postgraphile/__tests__/schema/v4/rbac.1.export.mjs +++ b/postgraphile/postgraphile/__tests__/schema/v4/rbac.1.export.mjs @@ -1,4 +1,4 @@ -import { LIST_TYPES, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectStep, TYPES, assertPgClassSingleStep, domainOfCodec, enumCodec, listOfCodec, makeRegistry, pgDeleteSingle, pgInsertSingle, pgSelectFromRecord, pgSelectSingleFromRecord, pgUpdateSingle, rangeOfCodec, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; +import { LIST_TYPES, PgCallStep, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectStep, TYPES, assertPgClassSingleStep, domainOfCodec, enumCodec, listOfCodec, makeRegistry, pgDeleteSingle, pgInsertSingle, pgSelectFromRecord, pgSelectSingleFromRecord, pgUpdateSingle, rangeOfCodec, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; import { ConnectionStep, EdgeStep, ObjectStep, __ValueStep, access, assertStep, bakedInput, bakedInputRuntime, connection, constant, context, createObjectAndApplyChildren, first, get as get2, inhibitOnNull, inspect, lambda, list, makeDecodeNodeId, makeGrafastSchema, markSyncAndSafe, object, operationPlan, specFromNodeId, trap } from "grafast"; import { GraphQLError, GraphQLString, Kind } from "graphql"; import { sql } from "pg-sql2"; @@ -8263,6 +8263,13 @@ const makeArgs_left_arm_identity = (args, path = []) => argDetailsSimple_left_ar const resource_left_arm_identityPgResource = registry.pgResources["left_arm_identity"]; function pgSelectFromPayload($payload) { const $result = $payload.getStepForKey("result"); + if ($result instanceof PgCallStep) { + // Procedures are invoked via `call`, not `select`, so there's no + // `PgSelectStep` to find. The call step itself is the target that + // nested `apply`-capable input fields (e.g. `clientMutationId`) + // should be applied to. + return $result; + } const $parent = "getParentStep" in $result ? $result.getParentStep() : $result; const $pgSelect = "getClassStep" in $parent ? $parent.getClassStep() : $parent; if ($pgSelect instanceof PgSelectStep) { @@ -9998,6 +10005,10 @@ export const objects = { args: { input(_, $payload, arg) { const $pgSelect = pgSelectFromPayload($payload); + // `PgCallStep`'s `apply()` takes a `PgCallQueryBuilder` rather than a + // `PgSelectQueryBuilder`, but both satisfy the same structural `apply` + // protocol at runtime (only `clientMutationId` uses it here), so the + // precise `qb` type is immaterial to the caller. arg.apply($pgSelect); } } diff --git a/postgraphile/postgraphile/__tests__/schema/v4/rbac.ignore.1.export.mjs b/postgraphile/postgraphile/__tests__/schema/v4/rbac.ignore.1.export.mjs index 639f0d2d13..e5e2f52969 100644 --- a/postgraphile/postgraphile/__tests__/schema/v4/rbac.ignore.1.export.mjs +++ b/postgraphile/postgraphile/__tests__/schema/v4/rbac.ignore.1.export.mjs @@ -1,4 +1,4 @@ -import { LIST_TYPES, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, domainOfCodec, enumCodec, listOfCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectFromRecords, pgSelectSingleFromRecord, pgUpdateSingle, rangeOfCodec, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; +import { LIST_TYPES, PgCallStep, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, domainOfCodec, enumCodec, listOfCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectFromRecords, pgSelectSingleFromRecord, pgUpdateSingle, rangeOfCodec, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; import { ConnectionStep, EdgeStep, ObjectStep, __ValueStep, access, assertStep, bakedInput, bakedInputRuntime, connection, constant, context, createObjectAndApplyChildren, first, get as get2, inhibitOnNull, inspect, lambda, list, makeDecodeNodeId, makeGrafastSchema, markSyncAndSafe, object, operationPlan, specFromNodeId, stepAMayDependOnStepB, trap } from "grafast"; import { GraphQLError, GraphQLInt, GraphQLString, Kind, valueFromASTUntyped } from "graphql"; import { sql } from "pg-sql2"; @@ -8124,6 +8124,13 @@ const resource_edge_case_computedPgResource = registry.pgResources["edge_case_co const resource_mutation_outPgResource = registry.pgResources["mutation_out"]; function pgSelectFromPayload($payload) { const $result = $payload.getStepForKey("result"); + if ($result instanceof PgCallStep) { + // Procedures are invoked via `call`, not `select`, so there's no + // `PgSelectStep` to find. The call step itself is the target that + // nested `apply`-capable input fields (e.g. `clientMutationId`) + // should be applied to. + return $result; + } const $parent = "getParentStep" in $result ? $result.getParentStep() : $result; const $pgSelect = "getClassStep" in $parent ? $parent.getClassStep() : $parent; if ($pgSelect instanceof PgSelectStep) { @@ -8134,6 +8141,10 @@ function pgSelectFromPayload($payload) { } function applyInputArgViaPgSelect(_, $payload, arg) { const $pgSelect = pgSelectFromPayload($payload); + // `PgCallStep`'s `apply()` takes a `PgCallQueryBuilder` rather than a + // `PgSelectQueryBuilder`, but both satisfy the same structural `apply` + // protocol at runtime (only `clientMutationId` uses it here), so the + // precise `qb` type is immaterial to the caller. arg.apply($pgSelect); } const resource_mutation_out_setofPgResource = registry.pgResources["mutation_out_setof"]; diff --git a/postgraphile/postgraphile/__tests__/schema/v4/relay.1.export.mjs b/postgraphile/postgraphile/__tests__/schema/v4/relay.1.export.mjs index bbc995db2f..af50a9b295 100644 --- a/postgraphile/postgraphile/__tests__/schema/v4/relay.1.export.mjs +++ b/postgraphile/postgraphile/__tests__/schema/v4/relay.1.export.mjs @@ -1,4 +1,4 @@ -import { PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, enumCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectSingleFromRecord, pgUpdateSingle, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; +import { PgCallStep, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, enumCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectSingleFromRecord, pgUpdateSingle, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; import { ConnectionStep, EdgeStep, ObjectStep, __ValueStep, access, assertStep, bakedInput, bakedInputRuntime, connection, constant, context, createObjectAndApplyChildren, first, get as get2, inhibitOnNull, inspect, lambda, list, makeDecodeNodeId, makeDecodeNodeIdRuntime, makeGrafastSchema, markSyncAndSafe, object, operationPlan, specFromNodeId, stepAMayDependOnStepB, trap } from "grafast"; import { GraphQLError, Kind } from "graphql"; import { sql } from "pg-sql2"; @@ -1478,6 +1478,13 @@ const localAttributeCodecs_tvShows_studiosByMyStudioId = [TYPES.int]; const resource_getflamblePgResource = registry.pgResources["getflamble"]; function pgSelectFromPayload($payload) { const $result = $payload.getStepForKey("result"); + if ($result instanceof PgCallStep) { + // Procedures are invoked via `call`, not `select`, so there's no + // `PgSelectStep` to find. The call step itself is the target that + // nested `apply`-capable input fields (e.g. `clientMutationId`) + // should be applied to. + return $result; + } const $parent = "getParentStep" in $result ? $result.getParentStep() : $result; const $pgSelect = "getClassStep" in $parent ? $parent.getClassStep() : $parent; if ($pgSelect instanceof PgSelectStep) { @@ -1488,6 +1495,10 @@ function pgSelectFromPayload($payload) { } function applyInputArgViaPgSelect(_, $payload, arg) { const $pgSelect = pgSelectFromPayload($payload); + // `PgCallStep`'s `apply()` takes a `PgCallQueryBuilder` rather than a + // `PgSelectQueryBuilder`, but both satisfy the same structural `apply` + // protocol at runtime (only `clientMutationId` uses it here), so the + // precise `qb` type is immaterial to the caller. arg.apply($pgSelect); } const argDetailsSimple_login = [{ diff --git a/postgraphile/postgraphile/__tests__/schema/v4/relay.defaultNodeIdCodec.1.export.mjs b/postgraphile/postgraphile/__tests__/schema/v4/relay.defaultNodeIdCodec.1.export.mjs index 03ef86a473..31b6e329dd 100644 --- a/postgraphile/postgraphile/__tests__/schema/v4/relay.defaultNodeIdCodec.1.export.mjs +++ b/postgraphile/postgraphile/__tests__/schema/v4/relay.defaultNodeIdCodec.1.export.mjs @@ -1,4 +1,4 @@ -import { PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, enumCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectSingleFromRecord, pgUpdateSingle, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; +import { PgCallStep, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, enumCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectSingleFromRecord, pgUpdateSingle, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; import { ConnectionStep, EdgeStep, ObjectStep, __ValueStep, access, assertStep, bakedInput, bakedInputRuntime, connection, constant, context, createObjectAndApplyChildren, first, get as get2, inhibitOnNull, inspect, lambda, list, makeDecodeNodeId, makeDecodeNodeIdRuntime, makeGrafastSchema, markSyncAndSafe, object, operationPlan, specFromNodeId, stepAMayDependOnStepB, trap } from "grafast"; import { GraphQLError, Kind } from "graphql"; import { sql } from "pg-sql2"; @@ -1478,6 +1478,13 @@ const localAttributeCodecs_tvShows_studiosByMyStudioId = [TYPES.int]; const resource_getflamblePgResource = registry.pgResources["getflamble"]; function pgSelectFromPayload($payload) { const $result = $payload.getStepForKey("result"); + if ($result instanceof PgCallStep) { + // Procedures are invoked via `call`, not `select`, so there's no + // `PgSelectStep` to find. The call step itself is the target that + // nested `apply`-capable input fields (e.g. `clientMutationId`) + // should be applied to. + return $result; + } const $parent = "getParentStep" in $result ? $result.getParentStep() : $result; const $pgSelect = "getClassStep" in $parent ? $parent.getClassStep() : $parent; if ($pgSelect instanceof PgSelectStep) { @@ -1488,6 +1495,10 @@ function pgSelectFromPayload($payload) { } function applyInputArgViaPgSelect(_, $payload, arg) { const $pgSelect = pgSelectFromPayload($payload); + // `PgCallStep`'s `apply()` takes a `PgCallQueryBuilder` rather than a + // `PgSelectQueryBuilder`, but both satisfy the same structural `apply` + // protocol at runtime (only `clientMutationId` uses it here), so the + // precise `qb` type is immaterial to the caller. arg.apply($pgSelect); } const argDetailsSimple_login = [{ diff --git a/postgraphile/postgraphile/__tests__/schema/v4/relay1.1.export.mjs b/postgraphile/postgraphile/__tests__/schema/v4/relay1.1.export.mjs index 4bb554c522..6ed88da4d6 100644 --- a/postgraphile/postgraphile/__tests__/schema/v4/relay1.1.export.mjs +++ b/postgraphile/postgraphile/__tests__/schema/v4/relay1.1.export.mjs @@ -1,4 +1,4 @@ -import { LIST_TYPES, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, domainOfCodec, enumCodec, listOfCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectFromRecords, pgSelectSingleFromRecord, pgUpdateSingle, rangeOfCodec, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; +import { LIST_TYPES, PgCallStep, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, domainOfCodec, enumCodec, listOfCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectFromRecords, pgSelectSingleFromRecord, pgUpdateSingle, rangeOfCodec, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; import { ConnectionStep, EdgeStep, ObjectStep, __ValueStep, access, assertStep, bakedInput, bakedInputRuntime, connection, constant, context, createObjectAndApplyChildren, first, get as get2, inhibitOnNull, inspect, lambda, list, makeDecodeNodeId, makeGrafastSchema, markSyncAndSafe, object, operationPlan, specFromNodeId, stepAMayDependOnStepB, trap } from "grafast"; import { GraphQLError, GraphQLInt, GraphQLString, Kind, valueFromASTUntyped } from "graphql"; import { sql } from "pg-sql2"; @@ -5038,6 +5038,13 @@ const resource_edge_case_computedPgResource = registry.pgResources["edge_case_co const resource_mutation_outPgResource = registry.pgResources["mutation_out"]; function pgSelectFromPayload($payload) { const $result = $payload.getStepForKey("result"); + if ($result instanceof PgCallStep) { + // Procedures are invoked via `call`, not `select`, so there's no + // `PgSelectStep` to find. The call step itself is the target that + // nested `apply`-capable input fields (e.g. `clientMutationId`) + // should be applied to. + return $result; + } const $parent = "getParentStep" in $result ? $result.getParentStep() : $result; const $pgSelect = "getClassStep" in $parent ? $parent.getClassStep() : $parent; if ($pgSelect instanceof PgSelectStep) { @@ -5048,6 +5055,10 @@ function pgSelectFromPayload($payload) { } function applyInputArgViaPgSelect(_, $payload, arg) { const $pgSelect = pgSelectFromPayload($payload); + // `PgCallStep`'s `apply()` takes a `PgCallQueryBuilder` rather than a + // `PgSelectQueryBuilder`, but both satisfy the same structural `apply` + // protocol at runtime (only `clientMutationId` uses it here), so the + // precise `qb` type is immaterial to the caller. arg.apply($pgSelect); } const resource_mutation_out_setofPgResource = registry.pgResources["mutation_out_setof"]; diff --git a/postgraphile/postgraphile/__tests__/schema/v4/simple-collections.1.export.mjs b/postgraphile/postgraphile/__tests__/schema/v4/simple-collections.1.export.mjs index d60f018bb5..ad0146d2f9 100644 --- a/postgraphile/postgraphile/__tests__/schema/v4/simple-collections.1.export.mjs +++ b/postgraphile/postgraphile/__tests__/schema/v4/simple-collections.1.export.mjs @@ -1,4 +1,4 @@ -import { LIST_TYPES, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, domainOfCodec, enumCodec, listOfCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectFromRecords, pgSelectSingleFromRecord, pgUpdateSingle, rangeOfCodec, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; +import { LIST_TYPES, PgCallStep, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, domainOfCodec, enumCodec, listOfCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectFromRecords, pgSelectSingleFromRecord, pgUpdateSingle, rangeOfCodec, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; import { ConnectionStep, EdgeStep, ObjectStep, __ValueStep, access, assertStep, bakedInput, bakedInputRuntime, connection, constant, context, createObjectAndApplyChildren, first, get as get2, inhibitOnNull, inspect, lambda, list, makeDecodeNodeId, makeGrafastSchema, markSyncAndSafe, object, operationPlan, specFromNodeId, stepAMayDependOnStepB, trap } from "grafast"; import { GraphQLError, GraphQLInt, GraphQLString, Kind, valueFromASTUntyped } from "graphql"; import { sql } from "pg-sql2"; @@ -5041,6 +5041,13 @@ const resource_edge_case_computedPgResource = registry.pgResources["edge_case_co const resource_mutation_outPgResource = registry.pgResources["mutation_out"]; function pgSelectFromPayload($payload) { const $result = $payload.getStepForKey("result"); + if ($result instanceof PgCallStep) { + // Procedures are invoked via `call`, not `select`, so there's no + // `PgSelectStep` to find. The call step itself is the target that + // nested `apply`-capable input fields (e.g. `clientMutationId`) + // should be applied to. + return $result; + } const $parent = "getParentStep" in $result ? $result.getParentStep() : $result; const $pgSelect = "getClassStep" in $parent ? $parent.getClassStep() : $parent; if ($pgSelect instanceof PgSelectStep) { @@ -5051,6 +5058,10 @@ function pgSelectFromPayload($payload) { } function applyInputArgViaPgSelect(_, $payload, arg) { const $pgSelect = pgSelectFromPayload($payload); + // `PgCallStep`'s `apply()` takes a `PgCallQueryBuilder` rather than a + // `PgSelectQueryBuilder`, but both satisfy the same structural `apply` + // protocol at runtime (only `clientMutationId` uses it here), so the + // precise `qb` type is immaterial to the caller. arg.apply($pgSelect); } const resource_mutation_out_setofPgResource = registry.pgResources["mutation_out_setof"]; diff --git a/postgraphile/postgraphile/__tests__/schema/v4/simple-collections.only.1.export.mjs b/postgraphile/postgraphile/__tests__/schema/v4/simple-collections.only.1.export.mjs index 018c9b0256..e2f566e9e8 100644 --- a/postgraphile/postgraphile/__tests__/schema/v4/simple-collections.only.1.export.mjs +++ b/postgraphile/postgraphile/__tests__/schema/v4/simple-collections.only.1.export.mjs @@ -1,4 +1,4 @@ -import { LIST_TYPES, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, domainOfCodec, enumCodec, listOfCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecords, pgSelectSingleFromRecord, pgUpdateSingle, rangeOfCodec, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; +import { LIST_TYPES, PgCallStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, domainOfCodec, enumCodec, listOfCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecords, pgSelectSingleFromRecord, pgUpdateSingle, rangeOfCodec, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; import { ConnectionStep, ObjectStep, __ValueStep, access, assertStep, bakedInput, bakedInputRuntime, connection, constant, context, createObjectAndApplyChildren, get as get2, inhibitOnNull, inspect, lambda, list, makeDecodeNodeId, makeGrafastSchema, markSyncAndSafe, object, operationPlan, specFromNodeId, stepAMayDependOnStepB, trap } from "grafast"; import { GraphQLError, GraphQLInt, GraphQLString, Kind, valueFromASTUntyped } from "graphql"; import { sql } from "pg-sql2"; @@ -4971,6 +4971,13 @@ const resource_edge_case_computedPgResource = registry.pgResources["edge_case_co const resource_mutation_outPgResource = registry.pgResources["mutation_out"]; function pgSelectFromPayload($payload) { const $result = $payload.getStepForKey("result"); + if ($result instanceof PgCallStep) { + // Procedures are invoked via `call`, not `select`, so there's no + // `PgSelectStep` to find. The call step itself is the target that + // nested `apply`-capable input fields (e.g. `clientMutationId`) + // should be applied to. + return $result; + } const $parent = "getParentStep" in $result ? $result.getParentStep() : $result; const $pgSelect = "getClassStep" in $parent ? $parent.getClassStep() : $parent; if ($pgSelect instanceof PgSelectStep) { @@ -4981,6 +4988,10 @@ function pgSelectFromPayload($payload) { } function applyInputArgViaPgSelect(_, $payload, arg) { const $pgSelect = pgSelectFromPayload($payload); + // `PgCallStep`'s `apply()` takes a `PgCallQueryBuilder` rather than a + // `PgSelectQueryBuilder`, but both satisfy the same structural `apply` + // protocol at runtime (only `clientMutationId` uses it here), so the + // precise `qb` type is immaterial to the caller. arg.apply($pgSelect); } const resource_mutation_out_setofPgResource = registry.pgResources["mutation_out_setof"]; diff --git a/postgraphile/postgraphile/__tests__/schema/v4/simplePrint.export.mjs b/postgraphile/postgraphile/__tests__/schema/v4/simplePrint.export.mjs index 639f0d2d13..e5e2f52969 100644 --- a/postgraphile/postgraphile/__tests__/schema/v4/simplePrint.export.mjs +++ b/postgraphile/postgraphile/__tests__/schema/v4/simplePrint.export.mjs @@ -1,4 +1,4 @@ -import { LIST_TYPES, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, domainOfCodec, enumCodec, listOfCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectFromRecords, pgSelectSingleFromRecord, pgUpdateSingle, rangeOfCodec, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; +import { LIST_TYPES, PgCallStep, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, domainOfCodec, enumCodec, listOfCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectFromRecords, pgSelectSingleFromRecord, pgUpdateSingle, rangeOfCodec, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; import { ConnectionStep, EdgeStep, ObjectStep, __ValueStep, access, assertStep, bakedInput, bakedInputRuntime, connection, constant, context, createObjectAndApplyChildren, first, get as get2, inhibitOnNull, inspect, lambda, list, makeDecodeNodeId, makeGrafastSchema, markSyncAndSafe, object, operationPlan, specFromNodeId, stepAMayDependOnStepB, trap } from "grafast"; import { GraphQLError, GraphQLInt, GraphQLString, Kind, valueFromASTUntyped } from "graphql"; import { sql } from "pg-sql2"; @@ -8124,6 +8124,13 @@ const resource_edge_case_computedPgResource = registry.pgResources["edge_case_co const resource_mutation_outPgResource = registry.pgResources["mutation_out"]; function pgSelectFromPayload($payload) { const $result = $payload.getStepForKey("result"); + if ($result instanceof PgCallStep) { + // Procedures are invoked via `call`, not `select`, so there's no + // `PgSelectStep` to find. The call step itself is the target that + // nested `apply`-capable input fields (e.g. `clientMutationId`) + // should be applied to. + return $result; + } const $parent = "getParentStep" in $result ? $result.getParentStep() : $result; const $pgSelect = "getClassStep" in $parent ? $parent.getClassStep() : $parent; if ($pgSelect instanceof PgSelectStep) { @@ -8134,6 +8141,10 @@ function pgSelectFromPayload($payload) { } function applyInputArgViaPgSelect(_, $payload, arg) { const $pgSelect = pgSelectFromPayload($payload); + // `PgCallStep`'s `apply()` takes a `PgCallQueryBuilder` rather than a + // `PgSelectQueryBuilder`, but both satisfy the same structural `apply` + // protocol at runtime (only `clientMutationId` uses it here), so the + // precise `qb` type is immaterial to the caller. arg.apply($pgSelect); } const resource_mutation_out_setofPgResource = registry.pgResources["mutation_out_setof"]; diff --git a/postgraphile/postgraphile/__tests__/schema/v4/skipNodePlugin.1.export.mjs b/postgraphile/postgraphile/__tests__/schema/v4/skipNodePlugin.1.export.mjs index b10a60ba0a..09c734ff68 100644 --- a/postgraphile/postgraphile/__tests__/schema/v4/skipNodePlugin.1.export.mjs +++ b/postgraphile/postgraphile/__tests__/schema/v4/skipNodePlugin.1.export.mjs @@ -1,4 +1,4 @@ -import { LIST_TYPES, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, domainOfCodec, enumCodec, listOfCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectFromRecords, pgSelectSingleFromRecord, pgUpdateSingle, rangeOfCodec, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; +import { LIST_TYPES, PgCallStep, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, domainOfCodec, enumCodec, listOfCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectFromRecords, pgSelectSingleFromRecord, pgUpdateSingle, rangeOfCodec, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; import { ConnectionStep, EdgeStep, ObjectStep, __ValueStep, assertStep, bakedInput, bakedInputRuntime, connection, constant, context, createObjectAndApplyChildren, first, get as get2, makeGrafastSchema, object, operationPlan, stepAMayDependOnStepB, trap } from "grafast"; import { GraphQLError, GraphQLInt, GraphQLString, Kind, valueFromASTUntyped } from "graphql"; import { sql } from "pg-sql2"; @@ -7772,6 +7772,13 @@ const resource_edge_case_computedPgResource = registry.pgResources["edge_case_co const resource_mutation_outPgResource = registry.pgResources["mutation_out"]; function pgSelectFromPayload($payload) { const $result = $payload.getStepForKey("result"); + if ($result instanceof PgCallStep) { + // Procedures are invoked via `call`, not `select`, so there's no + // `PgSelectStep` to find. The call step itself is the target that + // nested `apply`-capable input fields (e.g. `clientMutationId`) + // should be applied to. + return $result; + } const $parent = "getParentStep" in $result ? $result.getParentStep() : $result; const $pgSelect = "getClassStep" in $parent ? $parent.getClassStep() : $parent; if ($pgSelect instanceof PgSelectStep) { @@ -7782,6 +7789,10 @@ function pgSelectFromPayload($payload) { } function applyInputArgViaPgSelect(_, $payload, arg) { const $pgSelect = pgSelectFromPayload($payload); + // `PgCallStep`'s `apply()` takes a `PgCallQueryBuilder` rather than a + // `PgSelectQueryBuilder`, but both satisfy the same structural `apply` + // protocol at runtime (only `clientMutationId` uses it here), so the + // precise `qb` type is immaterial to the caller. arg.apply($pgSelect); } const resource_mutation_out_setofPgResource = registry.pgResources["mutation_out_setof"]; diff --git a/postgraphile/postgraphile/__tests__/schema/v5/skipNodePlugin.1.export.mjs b/postgraphile/postgraphile/__tests__/schema/v5/skipNodePlugin.1.export.mjs index 3af755a4db..cd212f7a0d 100644 --- a/postgraphile/postgraphile/__tests__/schema/v5/skipNodePlugin.1.export.mjs +++ b/postgraphile/postgraphile/__tests__/schema/v5/skipNodePlugin.1.export.mjs @@ -1,4 +1,4 @@ -import { LIST_TYPES, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, domainOfCodec, enumCodec, listOfCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectFromRecords, pgSelectSingleFromRecord, pgUpdateSingle, rangeOfCodec, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; +import { LIST_TYPES, PgCallStep, PgDeleteSingleStep, PgExecutor, PgResource, PgSelectSingleStep, PgSelectStep, TYPES, assertPgClassSingleStep, domainOfCodec, enumCodec, listOfCodec, makeRegistry, pgClassExpression, pgDeleteSingle, pgFromExpression, pgInsertSingle, pgSelectFromRecord, pgSelectFromRecords, pgSelectSingleFromRecord, pgUpdateSingle, rangeOfCodec, recordCodec, sqlFromArgDigests, sqlValueWithCodec } from "@dataplan/pg"; import { ConnectionStep, EdgeStep, ObjectStep, __ValueStep, assertStep, bakedInput, bakedInputRuntime, connection, constant, context, createObjectAndApplyChildren, first, get as get2, makeGrafastSchema, object, operationPlan, stepAMayDependOnStepB, trap } from "grafast"; import { GraphQLError, GraphQLInt, GraphQLString, Kind, valueFromASTUntyped } from "graphql"; import { sql } from "pg-sql2"; @@ -7754,6 +7754,13 @@ const resource_c_edge_case_computedPgResource = registry.pgResources["c_edge_cas const resource_c_mutation_outPgResource = registry.pgResources["c_mutation_out"]; function pgSelectFromPayload($payload) { const $result = $payload.getStepForKey("result"); + if ($result instanceof PgCallStep) { + // Procedures are invoked via `call`, not `select`, so there's no + // `PgSelectStep` to find. The call step itself is the target that + // nested `apply`-capable input fields (e.g. `clientMutationId`) + // should be applied to. + return $result; + } const $parent = "getParentStep" in $result ? $result.getParentStep() : $result; const $pgSelect = "getClassStep" in $parent ? $parent.getClassStep() : $parent; if ($pgSelect instanceof PgSelectStep) { @@ -7764,6 +7771,10 @@ function pgSelectFromPayload($payload) { } function applyInputArgViaPgSelect(_, $payload, arg) { const $pgSelect = pgSelectFromPayload($payload); + // `PgCallStep`'s `apply()` takes a `PgCallQueryBuilder` rather than a + // `PgSelectQueryBuilder`, but both satisfy the same structural `apply` + // protocol at runtime (only `clientMutationId` uses it here), so the + // precise `qb` type is immaterial to the caller. arg.apply($pgSelect); } const resource_c_mutation_out_setofPgResource = registry.pgResources["c_mutation_out_setof"]; diff --git a/postgraphile/website/postgraphile/procedures.md b/postgraphile/website/postgraphile/procedures.md index 91844e4e25..61423ac94d 100644 --- a/postgraphile/website/postgraphile/procedures.md +++ b/postgraphile/website/postgraphile/procedures.md @@ -2,6 +2,55 @@ title: Procedures --- -PostGraphile does not currently have support for procedures (introduced in -PostgreSQL 11); however we have solid support for functions, which you can read -more about [here](./functions). +PostgreSQL 11 introduced `CREATE PROCEDURE`, invoked via `call proc(...)` +rather than being embedded in a `select` like a function. PostGraphile exposes +these procedures as GraphQL mutation fields. + +Since a procedure can only be invoked with `call`, not `select`, it can't be +used as a computed column, a custom query, or return a connection. It's +always a root-level mutation field, similar to a [custom +mutation](./custom-mutations) function. + +```sql +create procedure app_public.raise_widget_price(widget_id int, out new_price numeric) +language plpgsql as $$ +begin + update app_public.widgets + set price = price * 1.1 + where id = widget_id + returning price into new_price; +end; +$$; +``` + +This procedure would be exposed as a `raiseWidgetPrice` mutation field; since +it has an `OUT` parameter, the payload includes a `result` field (renamed +according to the parameter's name in some configurations) containing the +procedure's output: + +```graphql +mutation { + raiseWidgetPrice(input: { widgetId: 1 }) { + result { + newPrice + } + } +} +``` + +Procedures with no `OUT`/`INOUT` parameters expose no result field (comparable +to a function that `returns void`). Procedures with `INOUT` parameters accept +the parameter as input _and_ include it on the result, since the same +parameter serves both roles. + +PostGraphile also has solid support for functions, which are more flexible; +see [Functions](./functions) for more details. + +## Limitations + +- Procedures can't be used as computed columns, custom queries, or return + connections. They're always mutations. +- `SETOF`/`RETURNS TABLE` aren't available for procedures in PostgreSQL, so + neither is supported here. +- Variadic parameters aren't currently supported (same restriction as + functions).