join-monster
Version:
A GraphQL to SQL query execution layer for batch data fetching.
236 lines (235 loc) • 8.91 kB
JavaScript
;
var _shared = require("../shared");
var _lodash = require("lodash");
function recursiveConcat(keys) {
if (keys.length <= 1) {
return keys[0];
}
return recursiveConcat([`CONCAT(${keys[0]}, ${keys[1]})`, ...keys.slice(2)]);
}
const q = str => `"${str}"`;
function keysetPagingSelect(table, whereCondition, order, limit, as, options = {}) {
let {
joinCondition,
joinType,
extraJoin
} = options;
whereCondition = (0, _lodash.filter)(whereCondition).join(' AND ') || '1 = 1';
if (joinCondition) {
return `\
${joinType === 'LEFT' ? 'OUTER' : 'CROSS'} APPLY (
SELECT "${as}".*
FROM ${table} "${as}"
${extraJoin ? `LEFT JOIN ${extraJoin.name} ${q(extraJoin.as)}
ON ${extraJoin.condition}` : ''}
WHERE ${whereCondition}
ORDER BY ${(0, _shared.orderingsToString)(order.columns, q, order.table)}
FETCH FIRST ${limit} ROWS ONLY
) ${q(as)}`;
}
return `\
FROM (
SELECT "${as}".*
FROM ${table} "${as}"
WHERE ${whereCondition}
ORDER BY ${(0, _shared.orderingsToString)(order.columns, q, order.table)}
FETCH FIRST ${limit} ROWS ONLY
) ${q(as)}`;
}
function offsetPagingSelect(table, pagingWhereConditions, order, limit, offset, as, options = {}) {
let {
joinCondition,
joinType,
extraJoin
} = options;
const whereCondition = (0, _lodash.filter)(pagingWhereConditions).join(' AND ') || '1 = 1';
if (joinCondition) {
return `\
${joinType === 'LEFT' ? 'OUTER' : 'CROSS'} APPLY (
SELECT "${as}".*, count(*) OVER () AS ${q('$total')}
FROM ${table} "${as}"
${extraJoin ? `LEFT JOIN ${extraJoin.name} ${q(extraJoin.as)}
ON ${extraJoin.condition}` : ''}
WHERE ${whereCondition}
ORDER BY ${(0, _shared.orderingsToString)(order.columns, q, order.table)}
OFFSET ${offset} ROWS FETCH NEXT ${limit} ROWS ONLY
) ${q(as)}`;
}
return `\
FROM (
SELECT "${as}".*, count(*) OVER () AS ${q('$total')}
FROM ${table} "${as}"
WHERE ${whereCondition}
ORDER BY ${(0, _shared.orderingsToString)(order.columns, q, order.table)}
OFFSET ${offset} ROWS FETCH NEXT ${limit} ROWS ONLY
) ${q(as)}`;
}
const dialect = module.exports = {
...require('./pg'),
name: 'oracle',
compositeKey(parent, keys) {
keys = keys.map(key => `"${parent}"."${key}"`);
return `NULLIF(${recursiveConcat(keys)}, '')`;
},
handlePaginationAtRoot: async function (parent, node, context, tables) {
const pagingWhereConditions = [];
if (node.sortKey) {
const {
limit,
order,
whereCondition: whereAddendum
} = (0, _shared.interpretForKeysetPaging)(node, dialect);
pagingWhereConditions.push(whereAddendum);
if (node.where) {
pagingWhereConditions.push(await node.where(`"${node.as}"`, node.args || {}, context, node));
}
tables.push(keysetPagingSelect(node.name, pagingWhereConditions, order, limit, node.as));
} else if (node.orderBy) {
const {
limit,
offset,
order
} = (0, _shared.interpretForOffsetPaging)(node, dialect);
if (node.where) {
pagingWhereConditions.push(await node.where(`"${node.as}"`, node.args || {}, context, node));
}
tables.push(offsetPagingSelect(node.name, pagingWhereConditions, order, limit, offset, node.as));
}
},
handleJoinedOneToManyPaginated: async function (parent, node, context, tables, joinCondition) {
const pagingWhereConditions = [await node.sqlJoin(`"${parent.as}"`, q(node.as), node.args || {}, context, node)];
if (node.where) {
pagingWhereConditions.push(await node.where(`"${node.as}"`, node.args || {}, context, node));
}
if (node.sortKey) {
const {
limit,
order,
whereCondition: whereAddendum
} = (0, _shared.interpretForKeysetPaging)(node, dialect);
pagingWhereConditions.push(whereAddendum);
tables.push(keysetPagingSelect(node.name, pagingWhereConditions, order, limit, node.as, {
joinCondition,
joinType: 'LEFT'
}));
} else if (node.orderBy) {
const {
limit,
offset,
order
} = (0, _shared.interpretForOffsetPaging)(node, dialect);
tables.push(offsetPagingSelect(node.name, pagingWhereConditions, order, limit, offset, node.as, {
joinCondition,
joinType: 'LEFT'
}));
}
},
handleJoinedManyToManyPaginated: async function (parent, node, context, tables, joinCondition1, joinCondition2) {
const pagingWhereConditions = [await node.junction.sqlJoins[0](`"${parent.as}"`, `"${node.junction.as}"`, node.args || {}, context, node)];
if (node.junction.where) {
pagingWhereConditions.push(await node.junction.where(`"${node.junction.as}"`, node.args || {}, context, node));
}
if (node.where) {
pagingWhereConditions.push(await node.where(`"${node.as}"`, node.args || {}, context, node));
}
const lateralJoinOptions = {
joinCondition: joinCondition1,
joinType: 'LEFT'
};
if (node.where || node.orderBy) {
lateralJoinOptions.extraJoin = {
name: node.name,
as: node.as,
condition: joinCondition2
};
}
if (node.sortKey || node.junction.sortKey) {
const {
limit,
order,
whereCondition: whereAddendum
} = (0, _shared.interpretForKeysetPaging)(node, dialect);
pagingWhereConditions.push(whereAddendum);
tables.push(keysetPagingSelect(node.junction.sqlTable, pagingWhereConditions, order, limit, node.junction.as, lateralJoinOptions));
} else if (node.orderBy || node.junction.orderBy) {
const {
limit,
offset,
order
} = (0, _shared.interpretForOffsetPaging)(node, dialect);
tables.push(offsetPagingSelect(node.junction.sqlTable, pagingWhereConditions, order, limit, offset, node.junction.as, lateralJoinOptions));
}
},
handleBatchedOneToManyPaginated: async function (parent, node, context, tables, batchScope) {
const pagingWhereConditions = [`"${node.as}"."${node.sqlBatch.thisKey.name}" = "temp"."value"`];
if (node.where) {
pagingWhereConditions.push(await node.where(`"${node.as}"`, node.args || {}, context, node));
}
tables.push(`FROM (${arrToTableUnion(batchScope)}) "temp"`);
const lateralJoinCondition = `"${node.as}"."${node.sqlBatch.thisKey.name}" = "temp"."value"`;
if (node.sortKey) {
const {
limit,
order,
whereCondition: whereAddendum
} = (0, _shared.interpretForKeysetPaging)(node, dialect);
pagingWhereConditions.push(whereAddendum);
tables.push(keysetPagingSelect(node.name, pagingWhereConditions, order, limit, node.as, {
joinCondition: lateralJoinCondition
}));
} else if (node.orderBy) {
const {
limit,
offset,
order
} = (0, _shared.interpretForOffsetPaging)(node, dialect);
tables.push(offsetPagingSelect(node.name, pagingWhereConditions, order, limit, offset, node.as, {
joinCondition: lateralJoinCondition
}));
}
},
handleBatchedManyToManyPaginated: async function (parent, node, context, tables, batchScope, joinCondition) {
const pagingWhereConditions = [`"${node.junction.as}"."${node.junction.sqlBatch.thisKey.name}" = "temp"."value"`];
if (node.junction.where) {
pagingWhereConditions.push(await node.junction.where(`"${node.junction.as}"`, node.args || {}, context, node));
}
if (node.where) {
pagingWhereConditions.push(await node.where(`"${node.as}"`, node.args || {}, context, node));
}
tables.push(`FROM (${arrToTableUnion(batchScope)}) "temp"`);
const lateralJoinCondition = `"${node.junction.as}"."${node.junction.sqlBatch.thisKey.name}" = "temp"."value"`;
const lateralJoinOptions = {
joinCondition: lateralJoinCondition,
joinType: 'LEFT'
};
if (node.where || node.orderBy) {
lateralJoinOptions.extraJoin = {
name: node.name,
as: node.as,
condition: joinCondition
};
}
if (node.sortKey || node.junction.sortKey) {
const {
limit,
order,
whereCondition: whereAddendum
} = (0, _shared.interpretForKeysetPaging)(node, dialect);
pagingWhereConditions.push(whereAddendum);
tables.push(keysetPagingSelect(node.junction.sqlTable, pagingWhereConditions, order, limit, node.junction.as, lateralJoinOptions));
} else if (node.orderBy || node.junction.orderBy) {
const {
limit,
offset,
order
} = (0, _shared.interpretForOffsetPaging)(node, dialect);
tables.push(offsetPagingSelect(node.junction.sqlTable, pagingWhereConditions, order, limit, offset, node.junction.as, lateralJoinOptions));
}
tables.push(`LEFT JOIN ${node.name} "${node.as}" ON ${joinCondition}`);
}
};
function arrToTableUnion(arr) {
return arr.map(val => `
SELECT ${val} AS "value" FROM DUAL
`).join(' UNION ');
}