Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save singingwolfboy/7ad74f479f710d9ca26585413b2a1fde to your computer and use it in GitHub Desktop.
Save singingwolfboy/7ad74f479f710d9ca26585413b2a1fde to your computer and use it in GitHub Desktop.
I'm trying to build a PostGraphile plugin that works with the PgManyToManyRelationPlugin (https://github.com/graphile-contrib/pg-many-to-many) but adds a `condition` argument, inspired by the built-in PgConnectionArgCondition plugin, that allows filtering the junction table. I think I'm almost there, but the structure of the query in PgManyToMan…
const flatten = require("lodash/flatten");
module.exports = (function PgManyToManyRelationArgCondition(builder) {
builder.hook(
"init",
(_, build) => {
const {
newWithHooks,
pgIntrospectionResultsByKind: introspectionResultsByKind,
pgGetGqlInputTypeByTypeIdAndModifier,
graphql: { GraphQLInputObjectType, GraphQLString },
pgColumnFilter,
inflection,
pgOmit: omit,
describePgEntity,
sqlCommentByAddingTags,
} = build;
introspectionResultsByKind.class.forEach(table => {
// PERFORMANCE: These used to be .filter(...) calls
if (!table.isSelectable || omit(table, "filter")) return;
if (!table.namespace) return;
const fkeyConstraints = table.constraints.filter(c => c.type === "f");
if (fkeyConstraints.length < 2) return;
const fkeyAttributes = flatten(fkeyConstraints.map(c => c.keyAttributes.map(a => a.name)));
const tableTypeName = inflection.manyToManyRelationByKeys(table);
newWithHooks(
GraphQLInputObjectType,
{
description: `A condition to be used against \`${tableTypeName}\` object types. All fields are tested for equality and combined with a logical ‘and.’`,
name: inflection.conditionType(tableTypeName),
fields: context => {
const { fieldWithHooks } = context;
return table.attributes.reduce((memo, attr) => {
// PERFORMANCE: These used to be .filter(...) calls
if (!pgColumnFilter(attr, build, context)) return memo;
if (omit(attr, "filter")) return memo;
if (fkeyAttributes.includes(attr.name)) return memo;
const fieldName = inflection.column(attr);
memo = build.extend(
memo,
{
[fieldName]: fieldWithHooks(
fieldName,
{
description: `Checks for equality with the object’s \`${fieldName}\` field.`,
type:
pgGetGqlInputTypeByTypeIdAndModifier(
attr.typeId,
attr.typeModifier
) || GraphQLString,
},
{
isPgConnectionConditionInputField: true,
}
),
},
`Adding condition argument for ${describePgEntity(attr)}`
);
return memo;
}, {});
},
},
{
__origin: `Adding condition type for ${describePgEntity(
table
)}. You can rename the table's GraphQL type via:\n\n ${sqlCommentByAddingTags(
table,
{
name: "newNameHere",
}
)}`,
pgIntrospection: table,
isPgCondition: true,
},
true // Conditions might all be filtered
);
});
return _;
},
["PgManyToManyRelationArgCondition"],
[],
["PgTypes"]
);
builder.hook(
"GraphQLObjectType:fields:field:args",
(args, build, context) => {
const {
pgSql: sql,
gql2pg,
extend,
getTypeByName,
pgGetGqlTypeByTypeIdAndModifier,
pgColumnFilter,
inflection,
pgOmit: omit,
} = build;
const {
scope: {
fieldName,
isPgManyToManyRelationField,
isPgFieldConnection,
isPgFieldSimpleCollection,
pgManyToManyJunctionTable: junctionTable,
pgManyToManyJunctionLeftConstraint: junctionLeftConstraint,
pgManyToManyJunctionRightConstraint: junctionRightConstraint,
},
addArgDataGenerator,
Self,
field,
} = context;
if (!isPgManyToManyRelationField) return args;
const shouldAddCondition =
isPgFieldConnection || isPgFieldSimpleCollection;
if (!shouldAddCondition) return args;
const TableConditionType = getTypeByName(
inflection.conditionType(
inflection.manyToManyRelationByKeys(junctionTable)
)
);
if (!TableConditionType) {
return args;
}
const junctionAttributeNames = [
...junctionLeftConstraint.keyAttributes.map(a => a.name),
...junctionRightConstraint.keyAttributes.map(a => a.name),
];
const relevantAttributes = junctionTable.attributes.filter(
attr => (
pgColumnFilter(attr, build, context) &&
!omit(attr, "filter") &&
!junctionAttributeNames.includes(attr.name)
)
);
addArgDataGenerator(function connectionCondition({ condition }) {
return {
pgQuery: queryBuilder => {
if (condition != null) {
relevantAttributes.forEach(attr => {
const fieldName = inflection.column(attr);
const val = condition[fieldName];
// How do I apply this condition to the subquery?
// I can't just do `queryBuilder.where()`, since that will not
// impact the subquery.
});
}
},
};
});
// The `PgConnectionArgCondition` plugin adds a `condition` argument,
// but it doesn't do what we want. So we'll delete that argument,
// and set our own instead.
delete args.condition;
return extend(
args,
{
condition: {
description:
"A condition to be used in determining which values should be returned by the collection.",
type: TableConditionType,
},
},
`Adding condition to connection field '${fieldName || field.type}' of '${Self.name}'`
);
},
["PgManyToManyRelationArgCondition"]
);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment