UNPKG

@cran/gql.jm

Version:

Cran/GraphQL Join Monster Utilities

121 lines (120 loc) 4.55 kB
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); }; }