Skip to content

Instantly share code, notes, and snippets.

@ttfkam
Created December 31, 2022 19:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ttfkam/1ac171945ec1b5fab36034afeb7a541a to your computer and use it in GitHub Desktop.
Save ttfkam/1ac171945ec1b5fab36034afeb7a541a to your computer and use it in GitHub Desktop.
Enhance Postgraphile compatibility with PostgREST
import type { Plugin, Build } from "graphile-build";
import type { PgClass, PgProc } from "./PgIntrospectionPlugin";
export const getComputedColumnDetails = (
build: Build,
table: PgClass,
proc: PgProc
) => {
if (proc.isVolatile) return null;
if (proc.namespaceId !== table.namespaceId) return null;
if (proc.argTypeIds.length < 1) return null;
if (proc.argTypeIds[0] !== table.type.id) return null;
const argTypes = proc.argTypeIds.reduce((prev, typeId, idx) => {
if (
proc.argModes.length === 0 || // all args are `in`
proc.argModes[idx] === "i" || // this arg is `in`
proc.argModes[idx] === "b" // this arg is `inout`
) {
prev.push(build.pgIntrospectionResultsByKind.typeById[typeId]);
}
return prev;
}, []);
if (
argTypes
.slice(1)
.some(type => type.type === "c" && type.class && type.class.isSelectable)
) {
// Accepts two input tables? Skip.
return null;
}
const pseudoColumnName = proc.name.startsWith(`${table.name}_`) ? proc.name.slice(table.name.length + 1) : proc.name;
return { argTypes, pseudoColumnName };
};
export default (function PgComputedColumnsPlugin(
builder,
{ pgSimpleCollections, pgComputedColumnNames }
) {
builder.hook(
"GraphQLObjectType:fields",
(fields, build, context) => {
const {
scope: {
isPgRowType,
isPgCompoundType,
isInputType,
pgIntrospection: table,
},
fieldWithHooks,
Self,
} = context;
if (
isInputType ||
!(isPgRowType || isPgCompoundType) ||
!table ||
table.kind !== "class" ||
!table.namespace
) {
return fields;
}
const {
extend,
pgIntrospectionResultsByKind: introspectionResultsByKind,
inflection,
pgOmit: omit,
pgMakeProcField: makeProcField,
swallowError,
describePgEntity,
sqlCommentByAddingTags,
} = build;
const tableType = table.type;
if (!tableType) {
throw new Error("Could not determine the type for this table");
}
return extend(
fields,
introspectionResultsByKind.procedure.reduce((memo, proc) => {
if (omit(proc, "execute")) return memo;
const isVerboseName = proc.name.startsWith(`${table.name}_`);
if ((isVerboseName && pgComputedColumnNames === 'simple') ||
(!isVerboseName && pgComputedColumnNames === 'verbose')) {
return memo;
}
const computedColumnDetails = getComputedColumnDetails(
build,
table,
proc
);
if (!computedColumnDetails) return memo;
const { pseudoColumnName } = computedColumnDetails;
function makeField(forceList) {
const fieldName = forceList
? inflection.computedColumnList(pseudoColumnName, proc, table)
: inflection.computedColumn(pseudoColumnName, proc, table);
try {
memo = extend(
memo,
{
[fieldName]: makeProcField(fieldName, proc, build, {
fieldWithHooks,
computed: true,
forceList,
}),
},
`Adding computed column for ${describePgEntity(
proc
)}. You can rename this field with a 'Smart Comment':\n\n ${sqlCommentByAddingTags(
proc,
{
fieldName: "newNameHere",
}
)}`
);
} catch (e) {
swallowError(e);
}
}
const simpleCollections =
proc.tags.simpleCollections || pgSimpleCollections;
const hasConnections = simpleCollections !== "only";
const hasSimpleCollections =
simpleCollections === "only" || simpleCollections === "both";
if (!proc.returnsSet || hasConnections) {
makeField(false);
}
if (proc.returnsSet && hasSimpleCollections) {
makeField(true);
}
return memo;
}, {}),
`Adding computed column to '${Self.name}'`
);
},
["PgComputedColumns"]
);
}: Plugin);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment