Skip to content

Commit

Permalink
feat(utils): add @requires(columns: [...]) directive (#286)
Browse files Browse the repository at this point in the history
  • Loading branch information
benjie committed Aug 22, 2018
1 parent 8e68052 commit 75cd16b
Show file tree
Hide file tree
Showing 6 changed files with 445 additions and 74 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@
"prettier": "^1.4.4"
},
"scripts": {
"check": "yarn clean && yarn lint && yarn prepack:all && yarn test",
"lint": "eslint packages && lerna run tslint",
"flow": "flow",
"flow:check": "flow check",
"test": "lerna run test",
"test": "lerna run --concurrency 1 test",
"prepack:all": "scripts/prepack-all",
"watch": "for I in packages/*/; do echo \"cd $I && npm run watch\" | perl -p -e 's/\\n/\\0/;'; done | xargs -0 node_modules/.bin/concurrently --kill-others",
"clean": "rm -Rf packages/*/node8plus/"
Expand Down
120 changes: 70 additions & 50 deletions packages/graphile-build-pg/src/plugins/PgColumnsPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,70 @@ const nullableIf = (GraphQLNonNull, condition, Type) =>
condition ? Type : new GraphQLNonNull(Type);

export default (function PgColumnsPlugin(builder) {
builder.hook("build", build => {
const {
pgSql: sql,
pgTweakFragmentForTypeAndModifier,
pgQueryFromResolveData: queryFromResolveData,
} = build;
const getSelectValueForFieldAndTypeAndModifier = (
ReturnType,
fieldScope,
parsedResolveInfoFragment,
sqlFullName,
type,
typeModifier
) => {
const { getDataFromParsedResolveInfoFragment } = fieldScope;
if (type.isPgArray) {
const ident = sql.identifier(Symbol());
return sql.fragment`
(
case
when ${sqlFullName} is null then null
when coalesce(array_length(${sqlFullName}, 1), 0) = 0 then '[]'::json
else
(
select json_agg(${getSelectValueForFieldAndTypeAndModifier(
ReturnType,
fieldScope,
parsedResolveInfoFragment,
ident,
type.arrayItemType,
typeModifier
)})
from unnest(${sqlFullName}) as ${ident}
)
end
)
`;
} else {
const resolveData = getDataFromParsedResolveInfoFragment(
parsedResolveInfoFragment,
ReturnType
);
if (type.type === "c") {
const jsonBuildObject = queryFromResolveData(
sql.identifier(Symbol()), // Ignore!
sqlFullName,
resolveData,
{ onlyJsonField: true, addNullCase: true }
);
return jsonBuildObject;
} else {
return pgTweakFragmentForTypeAndModifier(
sqlFullName,
type,
typeModifier,
resolveData
);
}
}
};
return build.extend(build, {
pgGetSelectValueForFieldAndTypeAndModifier: getSelectValueForFieldAndTypeAndModifier,
});
});
builder.hook("GraphQLObjectType:fields", (fields, build, context) => {
const {
extend,
Expand All @@ -13,11 +77,10 @@ export default (function PgColumnsPlugin(builder) {
pgSql: sql,
pg2gql,
graphql: { GraphQLString, GraphQLNonNull },
pgTweakFragmentForTypeAndModifier,
pgColumnFilter,
inflection,
pgQueryFromResolveData: queryFromResolveData,
pgOmit: omit,
pgGetSelectValueForFieldAndTypeAndModifier: getSelectValueForFieldAndTypeAndModifier,
} = build;
const {
scope: { isPgRowType, isPgCompoundType, pgIntrospection: table },
Expand Down Expand Up @@ -59,7 +122,8 @@ export default (function PgColumnsPlugin(builder) {
}
memo[fieldName] = fieldWithHooks(
fieldName,
({ getDataFromParsedResolveInfoFragment, addDataGenerator }) => {
fieldContext => {
const { addDataGenerator } = fieldContext;
const ReturnType =
pgGetGqlTypeByTypeIdAndModifier(
attr.typeId,
Expand All @@ -68,55 +132,11 @@ export default (function PgColumnsPlugin(builder) {
addDataGenerator(parsedResolveInfoFragment => {
return {
pgQuery: queryBuilder => {
const getSelectValueForFieldAndTypeAndModifier = (
sqlFullName,
type,
typeModifier
) => {
if (type.isPgArray) {
const ident = sql.identifier(Symbol());
return sql.fragment`
(
case
when ${sqlFullName} is null then null
when coalesce(array_length(${sqlFullName}, 1), 0) = 0 then '[]'::json
else
(
select json_agg(${getSelectValueForFieldAndTypeAndModifier(
ident,
type.arrayItemType,
typeModifier
)})
from unnest(${sqlFullName}) as ${ident}
)
end
)
`;
} else {
const resolveData = getDataFromParsedResolveInfoFragment(
parsedResolveInfoFragment,
ReturnType
);
if (type.type === "c") {
const jsonBuildObject = queryFromResolveData(
sql.identifier(Symbol()), // Ignore!
sqlFullName,
resolveData,
{ onlyJsonField: true, addNullCase: true }
);
return jsonBuildObject;
} else {
return pgTweakFragmentForTypeAndModifier(
sqlFullName,
type,
typeModifier,
resolveData
);
}
}
};
queryBuilder.select(
getSelectValueForFieldAndTypeAndModifier(
ReturnType,
fieldContext,
parsedResolveInfoFragment,
sql.fragment`(${queryBuilder.getTableAlias()}.${sql.identifier(
attr.name
)})`, // The brackets are necessary to stop the parser getting confused, ref: https://www.postgresql.org/docs/9.6/static/rowtypes.html#ROWTYPES-ACCESSING
Expand Down
50 changes: 50 additions & 0 deletions packages/graphile-utils/__tests__/ExtendSchemaPlugin-pg.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -301,3 +301,53 @@ it("allows adding a simple mutation field to PG schema", async () => {
pgClient.release();
}
});

it("allows adding a field to an existing table, and requesting necessary data along with it", async () => {
const schema = await createPostGraphileSchema(pgPool, ["a"], {
disableDefaultMutations: true,
appendPlugins: [
makeExtendSchemaPlugin(() => ({
typeDefs: gql`
extend type User {
customField: String
@requires(columns: ["id", "name", "slightly_more_complex_column"])
}
`,
resolvers: {
User: {
customField: user =>
`User ${user.id} fetched (name: ${user.name}) ${JSON.stringify(
user.renamedComplexColumn
)}`,
},
},
})),
],
});
const printedSchema = printSchema(schema);
expect(printedSchema).toMatchSnapshot();
const pgClient = await pgPool.connect();
try {
const { data, errors } = await graphql(
schema,
`
query {
userById(id: 1) {
customField
}
}
`,
null,
{ pgClient },
{}
);
expect(errors).toBeFalsy();
expect(data).toBeTruthy();
expect(data.userById).toBeTruthy();
expect(data.userById.customField).toEqual(
`User 1 fetched (name: Alice) [{"number_int":1,"string_text":"hi"},{"number_int":2,"string_text":"bye"}]`
);
} finally {
pgClient.release();
}
});
Loading

0 comments on commit 75cd16b

Please sign in to comment.