UNPKG

jql2sql

Version:

Transpiling JQL to SQL

156 lines (133 loc) 4.33 kB
const { has } = require("lodash"); const { KINDS, isNumeric, clearAST } = require("./utils"); /** * b or "b" -> 'b' * @param {*} v b or "b" * @returns 'b' */ function valueForIn(v) { v = v.trim(); if (v[0] == '\"') v = v.substr(1, v.length - (1*2)); // v is not number, we surround with ' if (!isNumeric(v)) { v = '\'' + v+ '\'' } return v; } function transpileFOV(ast, UIField2Column) { const JQLOps2JSQLOps = { /** We may use these operators without transfiling */ '=': '=', '!=': '!=', '>': '>', '<': '<', '>=': '>=', '<=': '<=', 'in': 'in', 'not in': 'not in', /** Operators should be transpiled */ '~': 'LIKE', '!~': 'NOT LIKE', 'is': '=', 'is not': '!=' } // console.log(ast); const UIfield = ast.cleaned.field; const column = (UIField2Column.has(UIfield))? UIField2Column.get(UIfield): UIfield; const JQLOperator = ast.cleaned.operator; const SQLOperator = JQLOps2JSQLOps[JQLOperator]; let value = ast.cleaned.value; // console.log('transpileFOV() ' + `${field} ${operator} ${value}`); if (JQLOperator === 'in' || JQLOperator === 'not in') { value = value.substr(1, value.length - (1*2)); if (value.indexOf(',') >= 0) { let vs = value.split(','); vs = vs.map(v => valueForIn(v)); value = vs.join(', '); } else { value = valueForIn(value); } value = '(' + value + ')'; } if (JQLOperator === '~' || JQLOperator === '!~') { if (has(ast, 'valueHint') && has(ast.valueHint, 'text')) { const hintText = ast.valueHint.text; if (hintText == 'noQuoteValueNoSpace') { value = `'${value}'`; } else if (hintText == 'doubleQuoteValueNoSpace') { // Escape " i.e) "blah" -> blah value = value.substr(1, value.length - (1*2)); value = `'${value}'`; } else if (hintText == 'doubleQuoteValueWithSpace') { // Escape " i.e) "blah" -> blah value = value.substr(1, value.length - (1*2)); // value contains ' '(space) if (value.indexOf(' ') >= 0) { const vs = value.split(' '); const fovs = vs.map(v => `${column} ${SQLOperator} '${v}'`); value = '(' + fovs.join(' or ') + ')'; } return value; } else if (hintText == 'doubleQuoteValueWithLeftAsterisk' || hintText == 'doubleQuoteValueWithRightAsterisk' || hintText == 'doubleQuoteValueWithBothAsterisk') { // Escape "*blah" i.e) "*blah" -> *blah // Escape "blah*" i.e) "blah*" -> blah* // Escape "*blah*" i.e) "*blah*" -> *blah* value = value.substr(1, value.length - 2); value = value.replace('*', '%'); value = value.replace('*', '%'); value = `'${value}'`; } else if (hintText == 'nestedDoubleQuoteValue') { // Escape "\"blah\"" i.e) "\"blah\"" -> blah value = value.substr(3, value.length - (3*2)); value = `'%${value}%'`; } } } return `${column} ${SQLOperator} ${value}`; } /** * Transpile AST to SQL where clause * * @param {*} ast Abstrat syntax tree * @param {*} UIField2Column Replacing UI field name(in AST) with table's column name(in DB) * @returns SQL where clause */ function transpileAST(ast, UIField2Column) { let where = ''; if (ast.kinds == KINDS.AST_LTRT) { let lt = transpileAST(clearAST(ast.expLt), UIField2Column); let rt = transpileAST(clearAST(ast.expRt), UIField2Column); where = `${lt} ${ast.andOr} ${rt}`; } else if (ast.kinds == KINDS.AST_FOV) { where = transpileFOV(clearAST(ast), UIField2Column); } else if (ast.kinds == KINDS.AST_BRACKET) { where = '(' + transpileAST(clearAST(ast.exp), UIField2Column) + ')'; } return where; } /** * ASTs are trasnfiled to SQL where clause * * @param {*} ast 'parser.results' should be given * @param {*} UIField2Column Replacing UI field name(in AST) with table's column name(in DB) * @returns SQL where clause */ function transpile2SQL(ast, UIField2Column) { if (UIField2Column === undefined || UIField2Column === null) { UIField2Column = new Map(); } let where = transpileAST(clearAST(ast), UIField2Column); return where.trim(); } module.exports = { transpile2SQL };