Created
July 23, 2019 14:52
-
-
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…
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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