Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable limiting polymorphic relations to only certain types #1817

Merged
merged 12 commits into from
Dec 8, 2023
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