JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 3349
  • Score
    100M100P100Q124333F
  • License MIT

Parses an SQL-like WHERE string into various forms.

Package Exports

  • sql-where-parser

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (sql-where-parser) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

SqlWhereParser

What is it?

SqlWhereParser parses the WHERE portion of an SQL string into many forms.

const sql = 'job = "developer" AND hasJob = true';
const parser = new SqlWhereParser();
const parsed = parser.parse(sql);
// "parsed" is an object that looks like:
// {
//     "tokens": [ "(", "job", "=", "developer", "AND", "hasJob", "=", true, ")" ],
//     "expression": [
//         [
//             "job",
//             "=",
//             "developer"
//         ],
//         "AND",
//         [
//             "hasJob",
//             "=",
//             true
//         ]
//     ],
//     "expressionDisplay": [
//         "job",
//         "=",
//         "developer",
//         "AND",
//         "hasJob",
//         "=",
//         true
//     ],
//     "expressionTree": [
//         "AND",
//         [
//             [
//                 "=",
//                 [
//                     "job",
//                     "developer"
//                 ]
//             ],
//             [
//                 "=",
//                 [
//                     "hasJob",
//                     true
//                 ]
//             ]
//         ]
//     ]
// };

The "expression" result is an array where each sub-expression is explicitly grouped based on order of operations.

//     "expression": [
//         [
//             "job",
//             "=",
//             "developer"
//         ],
//         "AND",
//         [
//             "hasJob",
//             "=",
//             true
//         ]
//     ],

The "expressionDisplay" result uses only the original groupings, except for unnecessary groups.

//     "expressionDisplay": [
//         "job",
//         "=",
//         "developer",
//         "AND",
//         "hasJob",
//         "=",
//         true
//     ],

"expressionDisplay" is useful for mapping to the front-end, e.g. as HTML.

const sql = 'job = "developer" AND (hasJob = true OR age > null)';
const parser = new SqlWhereParser();
const parsed = parser.parse(sql);
const toHtml = (expression) => {
    if (!expression || !(expression.constructor === Array)) {
        const isOperator = parser.operatorType(expression);
        if (isOperator) {
            return `<strong class="operator">${expression}</strong>`;
        }
        return `<span class="operand">${expression}</span>`;
    }
    const html = expression.map((subExpression) => {
        return toHtml(subExpression);
    });
    return `<div class="expression">${html.join('')}</div>`;
};
const html = toHtml(parsed.expressionDisplay);
// "html" is now a string that looks like:
// <div class="expression">
//   <span class="operand">job</span><strong class="operator">=</strong><span class="operand">developer</span>
//   <strong class="operator">AND</strong>
//     <div class="expression">
//       <span class="operand">hasJob</span><strong class="operator">=</strong><span class="operand">true</span>
//       <strong class="operator">OR</strong>
//       <span class="operand">age</span><strong class="operator">></strong><span class="operand">null</span>
//     </div>
// </div>

The "expressionTree" result is an array where the first element is the operation, and the second element is an array of that operation's operands..

//     "expressionTree": [
//         "AND",
//         [
//             [
//                 "=",
//                 [
//                     "job",
//                     "developer"
//                 ]
//             ],
//             [
//                 "=",
//                 [
//                     "hasJob",
//                     true
//                 ]
//             ]
//         ]
//     ]

"expressionTree" can be used to convert to other query languages, such as MongoDB or Elasticsearch.

const sql = 'job = "developer" AND (hasJob = true OR age > 17)';
const parser = new SqlWhereParser();
const parsed = parser.parse(sql);
const toMongo = (expression) => {
    if (!expression || !(expression.constructor === Array)) {
        return expression;
    }
    const operator = expression[0];
    const operands = expression[1];
    return map[operator](operands);
};
const map = {
    '=': (operands) => {
        return {
            [operands[0]]: toMongo(operands[1])
        };
    },
    '>': (operands) => {
        return {
            [operands[0]]: {
                $gt: toMongo(operands[1])
            }
        };
    },
    'AND': (operands) => {
        return {
            $and : operands.map(toMongo)
        };
    },
    'OR': (operands) => {
        return {
            $or: operands.map(toMongo)
        };
    }
};
const mongo = toMongo(parsed.expressionTree);
// "mongo" is now an object that looks like:
// {
//   "$and": [
//     {
//       "job": "developer"
//     },
//     {
//       "$or": [
//         {
//           "hasJob": true
//         },
//         {
//           "age": {
//             "$gt": 17
//           }
//         }
//       ]
//     }
//   ]
// }

If you wish to combine the tokens yourself, the "tokens" result is a flat array of the tokens as found in the original SQL string.

//     "tokens": [ "(", "job", "=", "developer", "AND", "hasJob", "=", true, ")" ],

Installation

npm install sql-where-parser.

// or if in the browser: <script src="sql-where-parser/sql-where-parser.min.js"></script>

API

#parse(String: sql):Object

returns a results object with tokens, expression, expressionDisplay, and expressionTree properties.

const parsed = parser.parse('');
parsed.should.have.property('tokens').which.is.an.Array;
parsed.should.have.property('expression').which.is.an.Array;
parsed.should.have.property('expressionDisplay').which.is.an.Array;
parsed.should.have.property('expressionTree').which.is.an.Array;

results.tokens

is an array containing the tokens of the SQL string (wrapped in parentheses).

const results = parser.parse('name = shaun');
results.tokens.should.be.an.Array;
equals(results.tokens, ['(', 'name', '=', 'shaun', ')']);

treats anything wrapped in single-quotes, double-quotes, and ticks as a single token.

const results = parser.parse(`(name = shaun) and "a" = 'b(' or (\`c\` OR "d e, f")`);
results.tokens.should.be.an.Array;
equals(results.tokens, ['(', '(', 'name', '=', 'shaun', ')', 'and', 'a', '=', 'b(', 'or', '(', 'c', 'OR', 'd e, f', ')', ')']);

results.expression

is the parsed SQL as an array.

const parsed = parser.parse('name = shaun');
equals(parsed.expression, ['name', '=', 'shaun']);

does not care about spaces.

const parsed = parser.parse('  name  =  shaun     ');
equals(parsed.expression, ['name', '=', 'shaun']);

strips out unnecessary parentheses.

const parsed = parser.parse('(((name) = ((shaun))))');
equals(parsed.expression, ['name', '=', 'shaun']);

adds explicit groupings defined by the order of operations.

const parsed = parser.parse('name = shaun AND job = developer AND (gender = male OR type = person AND location IN (NY, America) AND hobby = coding)');
/**
 * Original.
 */
'name = shaun AND job = developer AND (gender = male OR type = person AND location IN (NY, America) AND hobby = coding)';
/**
 * Perform equals.
 */
'(name = shaun) AND (job = developer) AND ((gender = male) OR (type = person) AND location IN (NY, America) AND (hobby = coding))';
/**
 * Perform IN
 */
'(name = shaun) AND (job = developer) AND ((gender = male) OR (type = person) AND (location IN (NY, America)) AND (hobby = coding))';
/**
 * Perform AND
 */
'(((name = shaun) AND (job = developer)) AND ((gender = male) OR (((type = person) AND (location IN (NY, America))) AND (hobby = coding))))';
equals(parsed.expression, [
    [
        [
            "name",
            "=",
            "shaun"
        ],
        "AND",
        [
            "job",
            "=",
            "developer"
        ]
    ],
    "AND",
    [
        [
            "gender",
            "=",
            "male"
        ],
        "OR",
        [
            [
                [
                    "type",
                    "=",
                    "person"
                ],
                "AND",
                [
                    "location",
                    "IN",
                    [
                        "NY",
                        "America"
                    ]
                ]
            ],
            "AND",
            [
                "hobby",
                "=",
                "coding"
            ]
        ]
    ]
]);

results.expressionDisplay

uses only the original groupings, except for unnecessary groups.

const parsed = parser.parse('(name = shaun AND job = developer AND ((gender = male OR type = person AND location IN (NY, America) AND hobby = coding)))');
equals(parsed.expressionDisplay, [
    "name",
    "=",
    "shaun",
    "AND",
    "job",
    "=",
    "developer",
    "AND",
    [
        "gender",
        "=",
        "male",
        "OR",
        "type",
        "=",
        "person",
        "AND",
        "location",
        "IN",
        [
            "NY",
            "America"
        ],
        "AND",
        "hobby",
        "=",
        "coding"
    ]
]);

results.expressionTree

converts the expression into a tree.

const parsed = parser.parse('name = shaun AND job = developer AND (gender = male OR type = person AND location IN (NY, America) AND hobby = coding)');
/**
 * Original.
 */
'name = shaun AND job = developer AND (gender = male OR type = person AND location IN (NY, America) AND hobby = coding)';
/**
 * Perform equals.
 */
'(name = shaun) AND (job = developer) AND ((gender = male) OR (type = person) AND location IN (NY, America) AND (hobby = coding))';
/**
 * Perform IN
 */
'(name = shaun) AND (job = developer) AND ((gender = male) OR (type = person) AND (location IN (NY, America)) AND (hobby = coding))';
/**
 * Perform AND
 */
'(((name = shaun) AND (job = developer)) AND ((gender = male) OR (((type = person) AND (location IN (NY, America))) AND (hobby = coding))))';
equals(parsed.expressionTree, [
    'AND',
    [
        [
            'AND',
            [
                [
                    '=',
                    [
                        'name',
                        'shaun'
                    ]
                ],
                [
                    '=',
                    [
                        'job',
                        'developer'
                    ]
                ]
            ]
        ],
        [
            'OR',
            [
                [
                    '=',
                    [
                        'gender',
                        'male'
                    ]
                ],
                [
                    'AND',
                    [
                        [
                            'AND',
                            [
                                [
                                    '=',
                                    [
                                        'type',
                                        'person'
                                    ]
                                ],
                                [
                                    'IN',
                                    [
                                        'location',
                                        [
                                            'NY',
                                            'America'
                                        ]
                                    ]
                                ]
                            ]
                        ],
                        [
                            '=',
                            [
                                'hobby',
                                'coding'
                            ]
                        ]
                    ]
                ]
            ]
        ]
    ]
]);

#expressionTreeFromExpression(Array|*: expression):Array

returns a tree representation of the supplied expression array.

const syntaxTree = parser.expressionTreeFromExpression([]);
syntaxTree.should.be.an.Array;

#setPrecedenceInExpression(Array|*: expression):Array

takes an array of tokens and groups them explicitly, based on the order of operations.

const orderedTokens = parser.setPrecedenceInExpression(['a1', '=', 'a2', 'OR', 'b', 'AND', 'c', 'OR', 'd', 'LIKE', 'e', 'AND', 'f', '<', 'g']);
equals(orderedTokens, [[["a1","=","a2"],"OR",["b","AND","c"]],"OR",[["d","LIKE","e"],"AND",["f","<","g"]]]);

#operatorType(String: operator):Function|null

returns the operator's type (unary, binary, etc.)..

parser.operators.forEach((operators) => {
    Object.keys(operators).forEach((operator) => {
        const type = operators[operator];
        parser.operatorType(operator).should.equal(type);
        parser.operatorType(operator).should.be.a.Function;
    });
});

.tokenize(String: sql[, Function: iteratee]):Array

returns an array containing the tokens of the SQL string.

const tokens = SqlWhereParser.tokenize('name = shaun');
tokens.should.be.an.Array;
equals(tokens, ['name', '=', 'shaun']);

treats anything wrapped in single-quotes, double-quotes, and ticks as a single token.

const tokens = SqlWhereParser.tokenize(`(name = shaun) and "a" = 'b(' or (\`c\` OR "d e, f")`);
tokens.should.be.an.Array;
equals(tokens, ['(', 'name', '=', 'shaun', ')', 'and', 'a', '=', 'b(', 'or', '(', 'c', 'OR', 'd e, f', ')']);

can be supplied with an optional iteratee function, which is called when each token is ready.

const collectedTokens = [];
const tokens = SqlWhereParser.tokenize('name = shaun', (token) => {
    collectedTokens.push(token);
});
collectedTokens.should.be.an.Array;
equals(tokens, collectedTokens);

.reduceArray(Array: arr):Array

reduces unnecessarily nested arrays.

const arr = [[['hey', 'hi']]];
const reducedArr = SqlWhereParser.reduceArray(arr);
let passed = true;
try {
    equals(arr, reducedArr);
    passed = false;
} catch(e) {
    equals(reducedArr, ['hey', 'hi']);
}
if (!passed) {
    throw new Error('Did not reduce the array');
}

.OPERATOR_TYPE_UNARY

is a function(indexOfOperatorInExpression, expression) that returns an array containing the index of where the operand is in the expression.

const operand = ['a', 'AND', 'b'];
const expression = ['NOT', operand];
const getIndexes = SqlWhereParser.OPERATOR_TYPE_UNARY;
const indexes = getIndexes(0, expression);
const operandIndex = indexes[0];
expression[operandIndex].should.equal(operand);

throws a syntax error if the operand is not found in the expression.

const expression = ['NOT'];
const getIndexes = SqlWhereParser.OPERATOR_TYPE_UNARY;
let passed = true;
try {
    const indexes = getIndexes(0, expression);
    passed = false;
} catch(e) {
    e.should.be.instanceOf(SyntaxError);
}
if (!passed) {
    throw new Error('Did not throw a syntax error');
}

.OPERATOR_TYPE_BINARY

is a function(indexOfOperatorInExpression, expression) that returns an array of indexes of where the operands are in the expression.

const operand1 = ['a', 'AND', 'b'];
const operand2 = ['c', 'OR', 'd'];
const expression = [operand1, 'AND', operand2];
const getIndexes = SqlWhereParser.OPERATOR_TYPE_BINARY;
const indexes = getIndexes(1, expression);
const operand1Index = indexes[0];
const operand2Index = indexes[1];
expression[operand1Index].should.equal(operand1);
expression[operand2Index].should.equal(operand2);

throws a syntax error if any of the operands are not found in the expression.

let expression = ['AND'];
let getIndexes = SqlWhereParser.OPERATOR_TYPE_BINARY;
let passed = true;
try {
    const indexes = getIndexes(0, expression);
    passed = false;
} catch(e) {
    e.should.be.instanceOf(SyntaxError);
}
if (!passed) {
    throw new Error('Did not throw a syntax error');
}
expression = ['a', 'AND'];
try {
    const indexes = getIndexes(1, expression);
    passed = false;
} catch(e) {
    e.should.be.instanceOf(SyntaxError);
}
if (!passed) {
    throw new Error('Did not throw a syntax error');
}
expression = ['AND', 'b'];
try {
    const indexes = getIndexes(0, expression);
    passed = false;
} catch(e) {
    e.should.be.instanceOf(SyntaxError);
}
if (!passed) {
    throw new Error('Did not throw a syntax error');
}

.OPERATOR_TYPE_BINARY_IN

is a function(indexOfOperatorInExpression, expression) that returns an array of indexes of where the operands are in the expression.

const operand1 = 'field';
const operand2 = [1, 2, 3];
const expression = [operand1, 'IN', operand2];
const getIndexes = SqlWhereParser.OPERATOR_TYPE_BINARY_IN;
const indexes = getIndexes(1, expression);
const operand1Index = indexes[0];
const operand2Index = indexes[1];
expression[operand1Index].should.equal(operand1);
expression[operand2Index].should.equal(operand2);

throws a syntax error if any of the operands are not found in the expression.

let expression = ['IN'];
let getIndexes = SqlWhereParser.OPERATOR_TYPE_BINARY_IN;
let passed = true;
try {
    const indexes = getIndexes(0, expression);
    passed = false;
} catch(e) {
    e.should.be.instanceOf(SyntaxError);
}
if (!passed) {
    throw new Error('Did not throw a syntax error');
}
expression = ['a', 'IN'];
try {
    const indexes = getIndexes(1, expression);
    passed = false;
} catch(e) {
    e.should.be.instanceOf(SyntaxError);
}
if (!passed) {
    throw new Error('Did not throw a syntax error');
}
expression = ['IN', [1, 2]];
try {
    const indexes = getIndexes(0, expression);
    passed = false;
} catch(e) {
    e.should.be.instanceOf(SyntaxError);
}
if (!passed) {
    throw new Error('Did not throw a syntax error');
}

throws a syntax error if the second operand is not an array.

const expression = ['field', 'IN', 'value'];
const getIndexes = SqlWhereParser.OPERATOR_TYPE_BINARY_IN;
let passed = true;
try {
    const indexes = getIndexes(0, expression);
    passed = false;
} catch(e) {
    e.should.be.instanceOf(SyntaxError);
}
if (!passed) {
    throw new Error('Did not throw a syntax error');
}

provides a LiteralIndex as the array operand's index, to alert the parser that this operand is a literal and requires no further parsing..

const operand1 = 'field';
const operand2 = [1, 2, 3];
const expression = [operand1, 'IN', operand2];
const getIndexes = SqlWhereParser.OPERATOR_TYPE_BINARY_IN;
const indexes = getIndexes(1, expression);
const operand1Index = indexes[0];
const operand2Index = indexes[1];
expression[operand1Index].should.equal(operand1);
expression[operand2Index].should.equal(operand2);
operand2Index.constructor.should.equal(SqlWhereParser.LiteralIndex);

.OPERATOR_TYPE_TERNARY_BETWEEN

is a function(indexOfOperatorInExpression, expression) that returns an array of indexes of where the operands are in the expression.

const operand1 = 'field';
const operand2 = 1;
const operand3 = 5;
const expression = [operand1, 'BETWEEN', operand2, 'AND', operand3];
const getIndexes = SqlWhereParser.OPERATOR_TYPE_TERNARY_BETWEEN;
const indexes = getIndexes(1, expression);
const operand1Index = indexes[0];
const operand2Index = indexes[1];
const operand3Index = indexes[2];
expression[operand1Index].should.equal(operand1);
expression[operand2Index].should.equal(operand2);
expression[operand3Index].should.equal(operand3);

throws a syntax error if any of the operands are not found in the expression.

let expression = ['BETWEEN'];
let getIndexes = SqlWhereParser.OPERATOR_TYPE_TERNARY_BETWEEN;
let passed = true;
try {
    const indexes = getIndexes(0, expression);
    passed = false;
} catch(e) {
    e.should.be.instanceOf(SyntaxError);
}
if (!passed) {
    throw new Error('Did not throw a syntax error');
}
expression = ['a', 'BETWEEN'];
try {
    const indexes = getIndexes(1, expression);
    passed = false;
} catch(e) {
    e.should.be.instanceOf(SyntaxError);
}
if (!passed) {
    throw new Error('Did not throw a syntax error');
}
expression = ['a', 'BETWEEN', 1, 'AND'];
try {
    const indexes = getIndexes(1, expression);
    passed = false;
} catch(e) {
    e.should.be.instanceOf(SyntaxError);
}
if (!passed) {
    throw new Error('Did not throw a syntax error');
}

throws a syntax error if the BETWEEN does not include an AND in between the {min} and {max}.

let expression = ['a', 'BETWEEN', 1, 'OR', 2];
let getIndexes = SqlWhereParser.OPERATOR_TYPE_TERNARY_BETWEEN;
let passed = true;
try {
    const indexes = getIndexes(0, expression);
    passed = false;
} catch(e) {
    e.should.be.instanceOf(SyntaxError);
}
if (!passed) {
    throw new Error('Did not throw a syntax error');
}