UNPKG

join-monster

Version:

A GraphQL to SQL query execution layer for batch data fetching.

238 lines (237 loc) 7.88 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.flipOrderings = flipOrderings; exports.generateCastExpressionFromValueType = generateCastExpressionFromValueType; exports.interpretForKeysetPaging = interpretForKeysetPaging; exports.interpretForOffsetPaging = interpretForOffsetPaging; exports.joinPrefix = joinPrefix; exports.keysetPagingSelect = keysetPagingSelect; exports.offsetPagingSelect = offsetPagingSelect; exports.orderingsToString = orderingsToString; exports.thisIsNotTheEndOfThisBatch = thisIsNotTheEndOfThisBatch; exports.validateCursor = validateCursor; exports.whereConditionIsntSupposedToGoInsideSubqueryOrOnNextBatch = whereConditionIsntSupposedToGoInsideSubqueryOrOnNextBatch; var _lodash = require("lodash"); var _graphqlRelay = require("graphql-relay"); var _util = require("../util"); function joinPrefix(prefix) { return prefix.slice(1).map(name => name + '__').join(''); } function generateCastExpressionFromValueType(key, val) { const castTypes = { string: 'TEXT' }; const type = castTypes[typeof val] || null; if (type) { return `CAST(${key} AS ${type})`; } return key; } function doubleQuote(str) { return `"${str}"`; } function thisIsNotTheEndOfThisBatch(node, parent) { var _ref8; return !node.sqlBatch && !((_ref8 = node) != null ? (_ref8 = _ref8.junction) != null ? _ref8.sqlBatch : _ref8 : _ref8) || !parent; } function whereConditionIsntSupposedToGoInsideSubqueryOrOnNextBatch(node, parent) { var _ref7; return !node.paginate && (!(node.sqlBatch || ((_ref7 = node) != null ? (_ref7 = _ref7.junction) != null ? _ref7.sqlBatch : _ref7 : _ref7)) || !parent); } function flipOrderings(orderings, args) { const flip = args?.last; return orderings.map(({ direction, ...rest }) => { let descending = direction.toUpperCase() === 'DESC'; if (flip) descending = !descending; return { direction: descending ? 'DESC' : 'ASC', ...rest }; }); } function keysetPagingSelect(table, whereCondition, order, limit, as, options = {}) { let { joinCondition, joinType, extraJoin, q } = options; q = q || doubleQuote; whereCondition = (0, _lodash.filter)(whereCondition).join(' AND ') || 'TRUE'; if (joinCondition) { return `\ ${joinType || ''} JOIN LATERAL ( SELECT ${q(as)}.* FROM ${table} ${q(as)} ${extraJoin ? `LEFT JOIN ${extraJoin.name} ${q(extraJoin.as)} ON ${extraJoin.condition}` : ''} WHERE ${whereCondition} ORDER BY ${orderingsToString(order.columns, q, order.table)} LIMIT ${limit} ) ${q(as)} ON ${joinCondition}`; } return `\ FROM ( SELECT ${q(as)}.* FROM ${table} ${q(as)} WHERE ${whereCondition} ORDER BY ${orderingsToString(order.columns, q, order.table)} LIMIT ${limit} ) ${q(as)}`; } function offsetPagingSelect(table, pagingWhereConditions, order, limit, offset, as, options = {}) { let { joinCondition, joinType, extraJoin, q } = options; q = q || doubleQuote; const whereCondition = (0, _lodash.filter)(pagingWhereConditions).join(' AND ') || 'TRUE'; if (joinCondition) { return `\ ${joinType || ''} JOIN LATERAL ( SELECT ${q(as)}.*, count(*) OVER () AS ${q('$total')} FROM ${table} ${q(as)} ${extraJoin ? `LEFT JOIN ${extraJoin.name} ${q(extraJoin.as)} ON ${extraJoin.condition}` : ''} WHERE ${whereCondition} ORDER BY ${orderingsToString(order.columns, q, order.table)} LIMIT ${limit} OFFSET ${offset} ) ${q(as)} ON ${joinCondition}`; } return `\ FROM ( SELECT ${q(as)}.*, count(*) OVER () AS ${q('$total')} FROM ${table} ${q(as)} WHERE ${whereCondition} ORDER BY ${orderingsToString(order.columns, q, order.table)} LIMIT ${limit} OFFSET ${offset} ) ${q(as)}`; } function orderingsToString(orderings, q, as) { const orderByClauses = []; for (const ordering of orderings) { orderByClauses.push(`${as ? q(as) + '.' : ''}${q(ordering.column)} ${ordering.direction}`); } return orderByClauses.join(', '); } function interpretForOffsetPaging(node, dialect) { var _ref4, _ref5, _ref6; const { name } = dialect; if ((_ref6 = node) != null ? (_ref6 = _ref6.args) != null ? _ref6.last : _ref6 : _ref6) { throw new Error('Backward pagination not supported with offsets. Consider using keyset pagination instead'); } const order = {}; if (node.orderBy) { order.table = node.as; order.columns = node.orderBy; } else { order.table = node.junction.as; order.columns = node.junction.orderBy; } let limit = ['mariadb', 'mysql', 'oracle'].includes(name) ? '18446744073709551615' : 'ALL'; if ((_ref5 = node) != null ? _ref5.defaultPageSize : _ref5) { limit = node.defaultPageSize + 1; } let offset = node.offset ?? 0; if ((_ref4 = node) != null ? (_ref4 = _ref4.args) != null ? _ref4.first : _ref4 : _ref4) { limit = parseInt(node.args.first, 10); if (node.paginate) { limit++; } if (node.args.after) { offset = (0, _graphqlRelay.cursorToOffset)(node.args.after) + 1; } } return { limit, offset, order }; } function interpretForKeysetPaging(node, dialect) { var _ref, _ref2, _ref3; const { name } = dialect; let sortTable; let sortKey; if (node.sortKey) { sortKey = node.sortKey; sortTable = node.as; } else { sortKey = node.junction.sortKey; sortTable = node.junction.as; } const order = { table: sortTable, columns: flipOrderings(sortKey, node.args) }; const cursorKeys = order.columns.map(ordering => ordering.column); let limit = ['mariadb', 'mysql', 'oracle'].includes(name) ? '18446744073709551615' : 'ALL'; let whereCondition = ''; if ((_ref3 = node) != null ? _ref3.defaultPageSize : _ref3) { limit = node.defaultPageSize + 1; } if ((_ref2 = node) != null ? (_ref2 = _ref2.args) != null ? _ref2.first : _ref2 : _ref2) { limit = parseInt(node.args.first, 10) + 1; if (node.args.after) { const cursorObj = (0, _util.cursorToObj)(node.args.after); validateCursor(cursorObj, cursorKeys); whereCondition = sortKeyToWhereCondition(cursorObj, order.columns, sortTable, dialect); } if (node.args.before) { throw new Error('Using "before" with "first" is nonsensical.'); } } else if ((_ref = node) != null ? (_ref = _ref.args) != null ? _ref.last : _ref : _ref) { limit = parseInt(node.args.last, 10) + 1; if (node.args.before) { const cursorObj = (0, _util.cursorToObj)(node.args.before); validateCursor(cursorObj, cursorKeys); whereCondition = sortKeyToWhereCondition(cursorObj, order.columns, sortTable, dialect); } if (node.args.after) { throw new Error('Using "after" with "last" is nonsensical.'); } } return { limit, order, whereCondition }; } function validateCursor(cursorObj, expectedKeys) { const actualKeys = Object.keys(cursorObj); const expectedKeySet = new Set(expectedKeys); const actualKeySet = new Set(actualKeys); for (let key of actualKeys) { if (!expectedKeySet.has(key)) { throw new Error(`Invalid cursor. The column "${key}" is not in the sort key.`); } } for (let key of expectedKeys) { if (!actualKeySet.has(key)) { throw new Error(`Invalid cursor. The column "${key}" is not in the cursor.`); } } } function sortKeyToWhereCondition(keyObj, orderings, sortTable, dialect) { const condition = (ordering, operator) => { operator = operator || (ordering.direction === 'DESC' ? '<' : '>'); return `${dialect.quote(sortTable)}.${dialect.quote(ordering.column)} ${operator} ${(0, _util.maybeQuote)(keyObj[ordering.column], dialect.name)}`; }; orderings = [...orderings]; return '(' + orderings.reduceRight((agg, ordering) => { return ` ${condition(ordering)} OR (${condition(ordering, '=')} AND ${agg})`; }, condition(orderings.pop())) + ')'; }