From a3645ebe6e4c489a85f72e303f021a9589283df2 Mon Sep 17 00:00:00 2001 From: Lenz Weber Date: Thu, 17 Oct 2019 17:27:44 +0200 Subject: [PATCH 1/2] try to use primary key as additional @key directive --- package.json | 3 +- src/index.ts | 112 +++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 102 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index b78c446..9d81738 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,8 @@ "typescript": "^3.4.4" }, "peerDependencies": { - "graphile-build": "^4.4.2" + "graphile-build": "^4.4.2", + "graphile-build-pg": "^4.4.5" }, "files": [ "build" diff --git a/src/index.ts b/src/index.ts index ec04f85..06ae424 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,6 +6,7 @@ import { import { Plugin } from "graphile-build"; import printFederatedSchema from "./printFederatedSchema"; import { ObjectTypeDefinition, Directive, StringValue } from "./AST"; +import { PgAttribute, PgClass } from "graphile-build-pg"; /** * This plugin installs the schema outlined in the Apollo Federation spec, and @@ -22,8 +23,10 @@ const SchemaExtensionPlugin = makeExtendSchemaPlugin(build => { $$isQuery, $$nodeType, getTypeByName, + scopeByType, inflection, nodeIdFieldName, + pgSql: sql, } = build; // Cache let Query: any; @@ -84,22 +87,67 @@ const SchemaExtensionPlugin = makeExtendSchemaPlugin(build => { const { graphile: { fieldContext }, } = resolveInfo; - return representations.map((representation: any) => { + return representations.map(async (representation: any) => { if (!representation || typeof representation !== "object") { throw new Error("Invalid representation"); } + const { __typename, [nodeIdFieldName]: nodeId } = representation; - if (!__typename || typeof nodeId !== "string") { - throw new Error("Failed to interpret representation"); + if (!__typename) { + throw new Error( + "Failed to interpret representation, no typename" + ); + } + if (nodeId) { + if (typeof nodeId !== "string") { + throw new Error( + "Failed to interpret representation, invalid nodeId" + ); + } + const x = resolveNode( + nodeId, + build, + fieldContext, + data, + context, + resolveInfo + ); + + return x; + } else { + const type = getTypeByName(__typename); + const { pgIntrospection: table } = scopeByType.get(type); + + if (!table.primaryKeyConstraint) { + throw new Error("Failed to interpret representation"); + } + const { + primaryKeyConstraint: { keyAttributes }, + } = table; + + const whereClause = sql.fragment`(${sql.join( + keyAttributes.map( + (attr: PgAttribute) => + sql.fragment`${sql.identifier(attr.name)} = ${sql.value( + representation[inflection.column(attr)] + )}` + ), + ") and (" + )})`; + + const rows = await resolveInfo.graphile.selectGraphQLResultFromTable( + sql.identifier(table.namespace, table.name), + (_alias, queryBuilder) => { + queryBuilder.where(whereClause); + } + ); + + if (rows.count !== 1) { + throw new Error("Failed to interpret representation"); + } + + return rows[0]; } - return resolveNode( - nodeId, - build, - fieldContext, - data, - context, - resolveInfo - ); }); }, @@ -131,7 +179,7 @@ const SchemaExtensionPlugin = makeExtendSchemaPlugin(build => { serialize(value: any) { return value; }, - }), + }) as any, }, }; }); @@ -147,6 +195,44 @@ const AddKeyPlugin: Plugin = builder => { return build; }); + builder.hook("GraphQLObjectType", (type, build, context) => { + const { + scope: { pgIntrospection, isPgRowType }, + } = context; + + const { inflection } = build; + + if ( + !( + isPgRowType && + pgIntrospection.isSelectable && + pgIntrospection.namespace && + pgIntrospection.primaryKeyConstraint + ) + ) { + return type; + } + + const primaryKeyNames = pgIntrospection.primaryKeyConstraint.keyAttributes.map( + (attr: PgAttribute) => inflection.column(attr) + ); + + if (!primaryKeyNames.length) { + return type; + } + + const astNode = { + ...ObjectTypeDefinition({ name: type.name }), + ...type.astNode, + }; + + (astNode.directives as any).push( + Directive("key", { fields: StringValue(primaryKeyNames.join(" ")) }) + ); + + return { ...type, astNode } as typeof type; + }); + // Find out what types implement the Node interface builder.hook("GraphQLObjectType:interfaces", (interfaces, build, context) => { const { getTypeByName, inflection, nodeIdFieldName } = build; @@ -197,6 +283,8 @@ const AddKeyPlugin: Plugin = builder => { } const { federationEntityTypes } = build; + console.log(federationEntityTypes.map((type: any) => type.name)); + // Add our types to the entity types return [...types, ...federationEntityTypes]; }); From dbae8506ef6521713994b895ca7d34372648807c Mon Sep 17 00:00:00 2001 From: Lenz Weber Date: Wed, 6 Nov 2019 15:48:00 +0100 Subject: [PATCH 2/2] use queryFromResolveData to retrieve _entity by primary key(s) --- src/index.ts | 44 +++++++++++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/src/index.ts b/src/index.ts index 06ae424..4beb662 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,7 +6,7 @@ import { import { Plugin } from "graphile-build"; import printFederatedSchema from "./printFederatedSchema"; import { ObjectTypeDefinition, Directive, StringValue } from "./AST"; -import { PgAttribute, PgClass } from "graphile-build-pg"; +import { PgAttribute, QueryBuilder } from "graphile-build-pg"; /** * This plugin installs the schema outlined in the Apollo Federation spec, and @@ -27,6 +27,9 @@ const SchemaExtensionPlugin = makeExtendSchemaPlugin(build => { inflection, nodeIdFieldName, pgSql: sql, + parseResolveInfo, + pgQueryFromResolveData: queryFromResolveData, + pgPrepareAndRun, } = build; // Cache let Query: any; @@ -84,6 +87,7 @@ const SchemaExtensionPlugin = makeExtendSchemaPlugin(build => { resolvers: { Query: { _entities(data, { representations }, context, resolveInfo) { + const { pgClient } = context; const { graphile: { fieldContext }, } = resolveInfo; @@ -104,7 +108,7 @@ const SchemaExtensionPlugin = makeExtendSchemaPlugin(build => { "Failed to interpret representation, invalid nodeId" ); } - const x = resolveNode( + return resolveNode( nodeId, build, fieldContext, @@ -112,8 +116,6 @@ const SchemaExtensionPlugin = makeExtendSchemaPlugin(build => { context, resolveInfo ); - - return x; } else { const type = getTypeByName(__typename); const { pgIntrospection: table } = scopeByType.get(type); @@ -135,18 +137,32 @@ const SchemaExtensionPlugin = makeExtendSchemaPlugin(build => { ") and (" )})`; - const rows = await resolveInfo.graphile.selectGraphQLResultFromTable( - sql.identifier(table.namespace, table.name), - (_alias, queryBuilder) => { + const resolveData = fieldContext.getDataFromParsedResolveInfoFragment( + parseResolveInfo(resolveInfo), + type + ); + + const query = queryFromResolveData( + sql.identifier(table.namespace.name, table.name), + undefined, + resolveData, + { + useAsterisk: false, // Because it's only a single relation, no need + }, + (queryBuilder: QueryBuilder) => { queryBuilder.where(whereClause); - } + }, + context, + resolveInfo.rootValue ); - if (rows.count !== 1) { - throw new Error("Failed to interpret representation"); - } + const { text, values } = sql.compile(query); + + const { + rows: [row], + } = await pgPrepareAndRun(pgClient, text, values); - return rows[0]; + return { [$$nodeType]: __typename, ...row }; } }); }, @@ -179,7 +195,7 @@ const SchemaExtensionPlugin = makeExtendSchemaPlugin(build => { serialize(value: any) { return value; }, - }) as any, + }), }, }; }); @@ -283,8 +299,6 @@ const AddKeyPlugin: Plugin = builder => { } const { federationEntityTypes } = build; - console.log(federationEntityTypes.map((type: any) => type.name)); - // Add our types to the entity types return [...types, ...federationEntityTypes]; });