graphile-build-pg
Version:
Build a GraphQL schema by reflection over a PostgreSQL schema. Easy to customize since it's built with plugins on graphile-build
234 lines (228 loc) • 9.28 kB
Flow
// @flow
import type { Plugin } from "graphile-build";
import debugSql from "./debugSql";
export default (async function PgAllRows(
builder,
{ pgViewUniqueKey, pgSimpleCollections, subscriptions }
) {
builder.hook(
"GraphQLObjectType:fields",
(fields, build, context) => {
const {
parseResolveInfo,
extend,
getTypeByName,
pgGetGqlTypeByTypeIdAndModifier,
pgSql: sql,
pgIntrospectionResultsByKind: introspectionResultsByKind,
inflection,
graphql: { GraphQLList, GraphQLNonNull },
pgQueryFromResolveData: queryFromResolveData,
pgAddStartEndCursor: addStartEndCursor,
pgOmit: omit,
pgPrepareAndRun,
} = build;
const {
fieldWithHooks,
scope: { isRootQuery },
} = context;
if (!isRootQuery) {
return fields;
}
return extend(
fields,
introspectionResultsByKind.class.reduce((memo, table) => {
// PERFORMANCE: These used to be .filter(...) calls
if (!table.isSelectable) return memo;
if (!table.namespace) return memo;
if (omit(table, "all")) return memo;
const TableType = pgGetGqlTypeByTypeIdAndModifier(
table.type.id,
null
);
if (!TableType) {
return memo;
}
const tableTypeName = TableType.name;
const ConnectionType = getTypeByName(
inflection.connection(TableType.name)
);
if (!TableType) {
throw new Error(
`Could not find GraphQL type for table '${table.name}'`
);
}
const attributes = table.attributes;
const primaryKeyConstraint = table.primaryKeyConstraint;
const primaryKeys =
primaryKeyConstraint && primaryKeyConstraint.keyAttributes;
const isView = t => t.classKind === "v";
const viewUniqueKey = table.tags.uniqueKey || pgViewUniqueKey;
const uniqueIdAttribute = viewUniqueKey
? attributes.find(attr => attr.name === viewUniqueKey)
: undefined;
if (isView(table) && table.tags.uniqueKey && !uniqueIdAttribute) {
throw new Error(
`Could not find the named unique key '${table.tags.uniqueKey}' on view '${table.namespaceName}.${table.name}'`
);
}
if (!ConnectionType) {
throw new Error(
`Could not find GraphQL connection type for table '${table.name}'`
);
}
const schema = table.namespace;
const sqlFullTableName = sql.identifier(schema.name, table.name);
function makeField(isConnection) {
const fieldName = isConnection
? inflection.allRows(table)
: inflection.allRowsSimple(table);
memo[fieldName] = fieldWithHooks(
fieldName,
({ getDataFromParsedResolveInfoFragment }) => {
return {
description: build.wrapDescription(
isConnection
? `Reads and enables pagination through a set of \`${tableTypeName}\`.`
: `Reads a set of \`${tableTypeName}\`.`,
"field"
),
type: isConnection
? ConnectionType
: new GraphQLList(new GraphQLNonNull(TableType)),
args: {},
async resolve(parent, args, resolveContext, resolveInfo) {
const { pgClient } = resolveContext;
const parsedResolveInfoFragment =
parseResolveInfo(resolveInfo);
parsedResolveInfoFragment.args = args; // Allow overriding via makeWrapResolversPlugin
const resolveData = getDataFromParsedResolveInfoFragment(
parsedResolveInfoFragment,
resolveInfo.returnType
);
let checkerGenerator;
const query = queryFromResolveData(
sqlFullTableName,
undefined,
resolveData,
{
useAsterisk: table.canUseAsterisk,
withPaginationAsFields: isConnection,
},
queryBuilder => {
if (subscriptions) {
queryBuilder.makeLiveCollection(
table,
_checkerGenerator => {
checkerGenerator = _checkerGenerator;
}
);
}
if (primaryKeys) {
if (subscriptions && !isConnection) {
queryBuilder.selectIdentifiers(table);
}
queryBuilder.beforeLock("orderBy", () => {
if (!queryBuilder.isOrderUnique(false)) {
// Order by PK if no order specified
queryBuilder.data.cursorPrefix = [
"primary_key_asc",
];
primaryKeys.forEach(key => {
queryBuilder.orderBy(
sql.fragment`${queryBuilder.getTableAlias()}.${sql.identifier(
key.name
)}`,
true
);
});
queryBuilder.setOrderIsUnique();
}
});
} else if (isView(table) && !!uniqueIdAttribute) {
queryBuilder.beforeLock("orderBy", () => {
if (!queryBuilder.isOrderUnique(false)) {
queryBuilder.data.cursorPrefix = [
"view_unique_key_asc",
];
queryBuilder.orderBy(
sql.fragment`${queryBuilder.getTableAlias()}.${sql.identifier(
uniqueIdAttribute.name
)}`,
true
);
queryBuilder.setOrderIsUnique();
}
});
}
},
resolveContext,
resolveInfo.rootValue
);
const { text, values } = sql.compile(query);
if (debugSql.enabled) debugSql(text);
const result = await pgPrepareAndRun(
pgClient,
text,
values
);
const liveCollection =
resolveInfo.rootValue &&
resolveInfo.rootValue.liveCollection;
if (subscriptions && liveCollection && checkerGenerator) {
const checker = checkerGenerator();
liveCollection("pg", table, checker);
}
if (isConnection) {
const {
rows: [row],
} = result;
return addStartEndCursor(row);
} else {
const liveRecord =
resolveInfo.rootValue &&
resolveInfo.rootValue.liveRecord;
if (
subscriptions &&
!isConnection &&
primaryKeys &&
liveRecord
) {
result.rows.forEach(
row =>
row && liveRecord("pg", table, row.__identifiers)
);
}
return result.rows;
}
},
};
},
{
isPgFieldConnection: isConnection,
isPgFieldSimpleCollection: !isConnection,
pgFieldIntrospection: table,
}
);
}
const simpleCollections =
table.tags.simpleCollections || pgSimpleCollections;
const hasConnections = simpleCollections !== "only";
const hasSimpleCollections =
simpleCollections === "only" || simpleCollections === "both";
if (TableType && ConnectionType && hasConnections) {
makeField(true);
}
if (TableType && hasSimpleCollections) {
makeField(false);
}
return memo;
}, {}),
`Adding 'all*' relations to root Query`
);
},
["PgAllRows"],
[],
["PgTables"]
);
}: Plugin);