Skip to main content
Version: Next

makeAddPgTableConditionPlugin

The (simplified) signature for makeAddPgTableConditionPlugin in V4 was:

// V4 signature
function makeAddPgTableConditionPlugin(
schemaName: string,
tableName: string,
conditionFieldName: string,
fieldSpecGenerator: (build: Build) => GraphQLInputFieldConfig,
conditionGenerator: (
value: unknown,
helpers: { queryBuilder: QueryBuilder; sql: PgSQL; sqlTableAlias: SQL },
build: Build,
) => SQL,
): Plugin;

In V5, the signature has changed a little.

The first change is trivial: we've combined the first two arguments into a "match" object which also optionally accepts the serviceName.

The second change, however, is much more significant - condition generation now operates based on the Grafast plan system (which operates based on "steps" which represent all possible values) rather than V4's lookahead engine (which deals in concrete runtime values).

The (simplified) new signature is:

// V5 signature
function makeAddPgTableConditionPlugin(
match: { serviceName?: string; schemaName: string; tableName: string },
conditionFieldName: string,
fieldSpecGenerator: (build: GraphileBuild.Build) => GraphileInputFieldConfig,

// OPTIONAL:
conditionGenerator?: (
value: FieldArgs,
helpers: {
$condition: PgConditionStep<PgSelectStep>;
sql: typeof sql;
sqlTableAlias: SQL;
build: GraphileBuild.Build;
},
) => SQL | null | undefined,
): GraphileConfig.Plugin;

Note that the conditionGenerator is now optional because you can choose to instead include an applyPlan entry in the result of fieldSpecGenerator - these input field and argument plans are now inherent to the schema rather than floating in some unknowable space as they did in V4.

Here's an example:

Example 1

V4:

import { makeAddPgTableConditionPlugin } from "graphile-utils";

const PetsCountPlugin = makeAddPgTableConditionPlugin(
"graphile_utils",
"users",
"petCountAtLeast",
(build) => ({
description: "Filters users to those that have at least this many pets",
type: build.graphql.GraphQLInt,
}),
(value, helpers, build) => {
const { sqlTableAlias, sql } = helpers;
return sql.fragment`(select count(*) from graphile_utils.pets where pets.user_id = ${sqlTableAlias}.id) >= ${sql.value(
value,
)}`;
},
);

Whereas in V5 the condition callback is called on every single GraphQL request, in V5 it is only called each time a new operation is planned - operations that reuse the plan do not call the condition callback again. value.get() gives us a step ($val) that represents all potential values for that input; we then feed this into the SQL statement via a placeholder (since it is not a concrete value) that will be substituted with the concrete runtime value each time a request executes. We also need to declare the type of the data so that it can be cast correctly for the database.

import { makeAddPgTableConditionPlugin } from "postgraphile/utils";
import { TYPES } from "postgraphile/@dataplan/pg";

const PetsCountPlugin = makeAddPgTableConditionPlugin(
{ schemaName: "graphile_utils", tableName: "users" },
"petCountAtLeast",
(build) => ({
description: "Filters users to those that have at least this many pets",
type: build.graphql.GraphQLInt,
}),
(value, helpers) => {
const { sqlTableAlias, sql, $condition } = helpers;
const $val = value.get();
return sql.fragment`(select count(*) from graphile_utils.pets where pets.user_id = ${sqlTableAlias}.id) >= ${$condition.placeholder(
$val,
TYPES.int,
)}`;
},
);