import {merge, md5} from './utils';
import {OPERATORS} from './operators';
export function literal(value) {
return {
type: 'literal',
value,
string: String(value).toUpperCase(),
};
}
export function string(s) {
return {
type: 'string',
value: s,
string: quoteString(s, '"'),
}
}
function tokenMaker(type, defaultString = null) {
return (s = defaultString) => {
const upper = s.toUpperCase();
return {
type,
string: upper,
value: upper,
};
};
}
export const keyword = tokenMaker('keyword');
export const parOpen = tokenMaker('parOpen', '(');
export const parClose = tokenMaker('parClose', ')');
export const star = tokenMaker('star', '*');
export const comma = tokenMaker('comma', ',');
export const semicolon = tokenMaker('semicolon', ';');
export function operator(s) {
const upper = s.toUpperCase();
return {
type: 'operator',
string: upper,
value: upper,
precedence: OPERATORS[upper].precedence,
associativity: OPERATORS[upper].associativity,
}
}
export function identifier(value) {
let string = value;
const basicIdentifierRegex = /^[a-z_]\w*$/i;
if (!basicIdentifierRegex.test(value)) {
string = quoteString(value, '`');
}
return {
type: 'identifier',
string,
value,
};
}
export function number(num) {
return {
type: 'number',
string: String(num),
value: Number(num),
};
}
export function call(functionName, args=[]) {
functionName = functionName.toUpperCase();
return {
type: 'call',
functionName,
arguments: args,
string: `${functionName}(${args.map(arg => arg.string).join(', ')})`,
};
}
export function aggregate(...callArgs) {
const callNode = call(...callArgs);
return merge(callNode, {type: 'aggregate', id: md5(callNode.string)});
}
export function binaryExpression(operator, left, right) {
return {
type: 'binaryExpression',
operator,
left,
right,
string: `${parenWrap(left)} ${operator} ${parenWrap(right)}`,
}
}
export function caseIf(cases, elseExpression=null) {
const node = {
type: 'caseIf',
cases,
elseExpression,
};
node.string = caseString(node);
return node;
}
export function caseSwitch(switchExpression, cases, elseExpression=null) {
const node = {
type: 'caseSwitch',
switchExpression,
cases,
elseExpression,
};
node.string = caseString(node);
return node;
}
function caseString({switchExpression, cases, elseExpression}) {
const switchString = switchExpression ? ` ${switchExpression.string}` : '';
const elseString = elseExpression ? ` ELSE ${elseExpression.string}` : '';
return `CASE${switchString} ${cases.map(c => c.string).join(' ')}${elseString} END`;
}
export function whenThen(when, then) {
return {
type: 'whenThen',
when,
then,
string: `WHEN ${when.string} THEN ${then.string}`,
}
}
export function namedExpression(expression, name=null) {
if (!name) {
name = expression.type === 'identifier' ? expression.value : expression.string;
}
return {
type: 'namedExpression',
expression,
name,
}
}
export function orderingTerm(expression, direction='asc') {
return {
type: 'orderingTerm',
expression,
direction: direction.toLowerCase(),
};
}
export function query({select,
from,
where=null,
groupBy=null,
having=null,
orderBy=null,
limit=null,
offset=null,
aggregates=null}) {
return {
select,
from,
where,
groupBy,
having,
orderBy,
limit,
offset,
aggregates,
}
}
function parenWrap(exp) {
if (exp.type === 'binaryExpression') {
return `(${exp.string})`;
}
else return exp.string;
}
function quoteString(s, delimiter) {
// TODO properly escape delimiters in strings when quoting
return delimiter + s.split(delimiter).join('\\' + delimiter) + delimiter;
}