@cran/gql.jm
Version:
Cran/GraphQL Join Monster Utilities
121 lines (120 loc) • 4.55 kB
JavaScript
import format from "pg-format";
import joinMonster from "join-monster";
import { GraphQLList, GraphQLNonNull } from "graphql";
import { MapperKind, composePlugins, createDirective } from "@cran/gql.core";
function isListType(type) {
if (type instanceof GraphQLList) {
return true;
}
else if (type instanceof GraphQLNonNull) {
return isListType(type.ofType);
}
return false;
}
const RE_OP = /^([+*/<>=~!@#%^&|`?-]{1,63}|i?like|in|null)$/u;
const RE_NOP = /^(--|\/\*|=>|([^~!@#%^&|`?]+)[+-])$/u;
function validOp(op) {
return RE_OP.test(op) && !RE_NOP.test(op);
}
// eslint-disable-next-line max-lines-per-function
export function withSqlResolver(name = "sqlResolver", options = {}) {
const resolver = resolverFactory(options);
return composePlugins([
{
typeDefs: ([
"enum sql_direction{asc,desc}",
"enum sql_nulls{first,last}",
"input sql_order{column:text!,direction:sql_direction,nulls:sql_nulls}",
"input sql_where{column:text,op:text,value:json,and:[sql_where!],or:[sql_where!],not:sql_where}",
]).join(""),
}, createDirective(name, {}, {
[MapperKind.COMPOSITE_FIELD](_directives, field, _fieldName, _typeName, schema) {
field.resolve = resolver;
if (isListType(field.type)) {
Object.assign(field.args || (field.args = {}), {
limit: { type: schema.getType("int4"), },
offset: { type: schema.getType("int4"), },
order: { type: schema.getType("[sql_order!]"), },
where: { type: schema.getType("sql_where"), },
});
}
return field;
},
}),
]);
}
function resolveWhere(where, ast) {
if ("or" in where) {
return `(${where.or.map(recurse).join(")or(")})`;
}
else if ("and" in where) {
return `(${where.and.map(recurse).join(")and(")})`;
}
else if ("not" in where) {
return `not(${resolveWhere(where.not, ast)})`;
}
if (!validOp(where.op)) {
throw new Error(`unknown operation ${where.op}`);
}
const column = resolveColumn(where.column, ast);
return formatWhere(column, where);
function recurse(inner) {
return resolveWhere(inner, ast);
}
}
function formatWhere(column, where) {
switch (where.op) {
case "null": return `"${column}" is${false === where.value ? " not" : ""} null`;
default: return format(`"${column}" ${where.op} %1$L`, where.value);
}
}
function resolveColumn(path, ast) {
let node = ast;
const collector = [];
for (const item of path.split(".")) {
if (!node || "table" !== node.type) {
throw new Error(`unknown column ${path}`);
}
if (node !== ast) {
collector.push(node && "as" in node ? node.as : null);
}
node = node.children.find(function findByFieldName(child) {
return "name" in child && child.fieldName === item;
});
}
if (node && "column" === node.type) {
collector.push(node.as);
return collector.join("__");
}
throw new Error(`unknown column ${path}`);
}
// eslint-disable-next-line max-lines-per-function
function resolverFactory(options) {
// eslint-disable-next-line max-lines-per-function
return function resolver(_source, args, context, info) {
return joinMonster(info, context, function query(sql, ast) {
const list = [`select * from (${sql})x`,];
if (args.where) {
list.push(`where ${resolveWhere(args.where, ast)}`);
}
if (args.order) {
// eslint-disable-next-line no-console
const orders = args.order.map(function resolveEachColumn({ column, direction, nulls, }) {
return `"${resolveColumn(column, ast)}" ${direction || "asc"} ${nulls ? `nulls ${nulls}` : ""}`;
});
list.push(`order by ${orders.join(",")}`);
}
if (args.limit) {
list.push(`limit ${args.limit}`);
}
if (args.offset) {
list.push(`offset ${args.offset}`);
}
const final = list.join("\n");
if (context.ctx?.queries) {
context.ctx.queries.push(final);
}
return (context.query(final));
}, options);
};
}