Skip to content

Commit

Permalink
Enable limiting polymorphic relations to only certain types (#1817)
Browse files Browse the repository at this point in the history
  • Loading branch information
benjie authored Dec 8, 2023
2 parents 839f367 + f305c32 commit 4ed8d79
Show file tree
Hide file tree
Showing 26 changed files with 3,145 additions and 2,090 deletions.
11 changes: 11 additions & 0 deletions .changeset/khaki-fans-kiss.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"graphile-build-pg": patch
"postgraphile": patch
"@dataplan/pg": patch
---

Add support for limiting polymorphic plans (only some of them, specifically
`pgUnionAll()` right now) to limit the types of their results; exposed via an
experimental 'only' argument on fields, for example
`allApplications(only: [GcpApplication, AwsApplication])` would limit the type
of applications returned to only be the two specified.
5 changes: 5 additions & 0 deletions .changeset/swift-cows-beam.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"graphile-build": patch
---

Enable detecting "empty" enums (enums with no values).
18 changes: 18 additions & 0 deletions grafast/dataplan-pg/src/steps/pgUnionAll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,7 @@ export class PgUnionAllStep<
private locker: PgLocker<this> = new PgLocker(this);

private memberDigests: MemberDigest<TTypeNames>[];
private _limitToTypes: string[] | undefined;

constructor(
cloneFrom: PgUnionAllStep<TAttributes, TTypeNames>,
Expand All @@ -534,6 +535,9 @@ export class PgUnionAllStep<
cloneFrom.mode === this.mode ? cloneFrom : null;
this.spec = cloneFrom.spec;
this.memberDigests = cloneDigests(cloneFrom.memberDigests);
this._limitToTypes = cloneFrom._limitToTypes
? [...cloneFrom._limitToTypes]
: undefined;

cloneFrom.dependencies.forEach((planId, idx) => {
const myIdx = this.addDependency(cloneFrom.getDep(idx), true);
Expand Down Expand Up @@ -1516,7 +1520,21 @@ and ${condition(i + 1)}`}
return this.orders;
}

/** @experimental */
limitToTypes(types: readonly string[]): void {
if (!this._limitToTypes) {
this._limitToTypes = [...types];
} else {
this._limitToTypes = this._limitToTypes.filter((t) => types.includes(t));
}
}

optimize() {
if (this._limitToTypes) {
this.memberDigests = this.memberDigests.filter((d) =>
this._limitToTypes!.includes(d.member.typeName),
);
}
if (this.memberDigests.length === 0) {
// We have no implementations, we'll never return anything
return constant([], false);
Expand Down
3 changes: 1 addition & 2 deletions grafast/website/docusaurus.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,7 @@ const config = {
},
pages: {
remarkPlugins: [
require("@docusaurus/remark-plugin-npm2yarn"),
{ sync: true },
[require("@docusaurus/remark-plugin-npm2yarn"), { sync: true }],
],
},
blog: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import "graphile-config";

import type {
PgCodec,
PgRegistry,
PgResource,
PgUnionAllStepConfigAttributes,
PgUnionAllStepMember,
Expand Down Expand Up @@ -78,9 +77,11 @@ export const PgInterfaceModeUnionAllRowsPlugin: GraphileConfig.Plugin = {
hooks: {
GraphQLObjectType_fields(fields, build, context) {
const {
getTypeByName,
inflection,
input,
graphql: { GraphQLList, GraphQLNonNull },
pgResourcesByPolymorphicTypeName,
pgCodecByPolymorphicUnionModeTypeName,
} = build;
const {
scope: { isRootQuery },
Expand All @@ -90,96 +91,16 @@ export const PgInterfaceModeUnionAllRowsPlugin: GraphileConfig.Plugin = {
return fields;
}

const pgRegistry = input.pgRegistry as PgRegistry;

const resourcesByPolymorphicTypeName: {
[polymorphicTypeName: string]: {
resources: PgResource[];
type: "union" | "interface";
};
} = Object.create(null);

const allResources = Object.values(pgRegistry.pgResources);
for (const resource of allResources) {
if (resource.parameters) continue;
if (typeof resource.from === "function") continue;
if (!resource.codec.extensions?.tags) continue;
const { implements: implementsTag } = resource.codec.extensions.tags;
/*
const { unionMember } = resource.codec.extensions.tags;
if (unionMember) {
const unions = Array.isArray(unionMember)
? unionMember
: [unionMember];
for (const union of unions) {
if (!resourcesByPolymorphicTypeName[union]) {
resourcesByPolymorphicTypeName[union] = {
resources: [resource as PgResource],
type: "union",
};
} else {
if (resourcesByPolymorphicTypeName[union].type !== "union") {
throw new Error(`Inconsistent polymorphism`);
}
resourcesByPolymorphicTypeName[union].resources.push(
resource as PgResource,
);
}
}
}
*/
if (implementsTag) {
const interfaces = Array.isArray(implementsTag)
? implementsTag
: [implementsTag];
for (const interfaceName of interfaces) {
if (!resourcesByPolymorphicTypeName[interfaceName]) {
resourcesByPolymorphicTypeName[interfaceName] = {
resources: [resource as PgResource],
type: "interface",
};
} else {
if (
resourcesByPolymorphicTypeName[interfaceName].type !==
"interface"
) {
throw new Error(`Inconsistent polymorphism`);
}
resourcesByPolymorphicTypeName[interfaceName].resources.push(
resource as PgResource,
);
}
}
}
}

const interfaceCodecs: { [polymorphicTypeName: string]: PgCodec } =
Object.create(null);
for (const codec of Object.values(pgRegistry.pgCodecs)) {
if (!codec.polymorphism) continue;
if (codec.polymorphism.mode !== "union") continue;

const interfaceTypeName = inflection.tableType(codec);
interfaceCodecs[interfaceTypeName] = codec;

// Explicitly allow zero implementations.
if (!resourcesByPolymorphicTypeName[interfaceTypeName]) {
resourcesByPolymorphicTypeName[interfaceTypeName] = {
resources: [],
type: "interface",
};
}
}

for (const [polymorphicTypeName, spec] of Object.entries(
resourcesByPolymorphicTypeName,
pgResourcesByPolymorphicTypeName,
)) {
if (spec.type === "union") {
// We can't add a root field for a basic union because there's
// nothing to order it by - we wouldn't be able to reliably
// paginate.
} else if (spec.type === "interface") {
const interfaceCodec = interfaceCodecs[polymorphicTypeName];
const interfaceCodec =
pgCodecByPolymorphicUnionModeTypeName[polymorphicTypeName];
if (!interfaceCodec) {
console.warn(
`A number of resources claim to implement '${polymorphicTypeName}', but we couldn't find the definition for that type so we won't add a root field for it. (Perhaps you implemented it in makeExtendSchemaPlugin?) Affected resources: ${spec.resources
Expand All @@ -196,14 +117,14 @@ export const PgInterfaceModeUnionAllRowsPlugin: GraphileConfig.Plugin = {
const makeField = (useConnection: boolean): void => {
if (!interfaceCodec.polymorphism) return;

const type = build.getTypeByName(
build.inflection.tableType(interfaceCodec),
const type = getTypeByName(
inflection.tableType(interfaceCodec),
) as GraphQLInterfaceType | undefined;
if (!type) return;

const fieldType = useConnection
? (build.getTypeByName(
build.inflection.tableConnectionType(interfaceCodec),
? (getTypeByName(
inflection.tableConnectionType(interfaceCodec),
) as GraphQLObjectType | undefined)
: // TODO: nullability.
new GraphQLList(new GraphQLNonNull(type));
Expand Down
Loading

0 comments on commit 4ed8d79

Please sign in to comment.