Skip to content

Commit dcb79f7

Browse files
committed
WL#15133, BUG#29178528: narrow expression parser responsability
The X DevAPI expression parser is performing two different duties when it should only have a single responsability. In this case, besides parsing an expression string, it also creates the corresponding protobuf message instance using the available protocol stubs. From an architectural standpoint, the parser should be an isolated component and should not have to know about the existence of an underlying protocol. Additionally the parser output should not be tied to a format defined by a 3rd-party library. This patch narrows the job of the expression parser component to the single responsability of parsing expression strings and returning a basic structured output and moves, to the protocol stub adapters, the responsability of actually encoding the resulting protobuf message instances. As a result, it also decouples the parser from the protocol itself and from the custom restricted format specified by the google-protobuf library. As a consequence of the structural changes in the expression parser, some of its known limitations have also been addressed. In particular, the one described by BUG#29178528. Change-Id: I1550ac72ee288864eaada69e6630d2441274b3a0
1 parent 0ccd580 commit dcb79f7

File tree

108 files changed

+8148
-4295
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

108 files changed

+8148
-4295
lines changed

lib/ExprParser/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2017, 2021, Oracle and/or its affiliates.
2+
* Copyright (c) 2017, 2022, Oracle and/or its affiliates.
33
*
44
* This program is free software; you can redistribute it and/or modify
55
* it under the terms of the GNU General Public License, version 2.0, as

lib/ExprParser/lib/grammar/booleanExpressions/addSubExpr.js

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2017, 2021, Oracle and/or its affiliates.
2+
* Copyright (c) 2017, 2022, Oracle and/or its affiliates.
33
*
44
* This program is free software; you can redistribute it and/or modify
55
* it under the terms of the GNU General Public License, version 2.0, as
@@ -30,22 +30,32 @@
3030

3131
'use strict';
3232

33-
const binaryOpMapper = require('../../mappers/binaryOperator');
33+
const binaryOperatorMapper = require('../../mappers/binaryOperator');
3434
const Pa = require('parsimmon');
3535

36-
const parser = options => r => Pa
37-
.seq(
38-
r.mulDivExpr,
39-
Pa
40-
.seq(
41-
Pa.optWhitespace,
42-
Pa.alt(Pa.string('+'), Pa.string('-')),
43-
Pa.optWhitespace,
44-
r.mulDivExpr
45-
)
46-
.map(data => ({ operand: data[3], operator: data[1] }))
47-
.many()
48-
)
49-
.map(binaryOpMapper);
36+
const PARSER_OPTIONS = {
37+
ID: 'addSubExpr',
38+
NAME: 'ADD_SUB_EXPR'
39+
};
5040

51-
module.exports = { name: 'ADD_SUB_EXPR', parser };
41+
const arithmeticOperator = Pa.alt(
42+
Pa.string('+'),
43+
Pa.string('-')
44+
);
45+
46+
/**
47+
* Sub-parser that matches addition and subtraction operator expressions.
48+
* addSubExpr ::= mulDivExpr ( ( '+' | '-' ) mulDivExpr )*
49+
* @private
50+
* @param {Object} [_] - Optional object containing parser options.
51+
* @returns The generated grammar for the parser.
52+
* @see https://dev.mysql.com/doc/x-devapi-userguide/en/mysql-x-expressions-ebnf-definitions.html#expression-ebnf-addsubexpr
53+
* @example
54+
* 1 + 1
55+
* 3 - 2
56+
*/
57+
const parser = _ => r => Pa
58+
.seq(r.mulDivExpr, Pa.seq(arithmeticOperator.trim(Pa.optWhitespace), r.mulDivExpr).map(([name, param]) => ({ name, params: [param] })).many())
59+
.map(([one, more]) => !more.length ? one : binaryOperatorMapper({ type: PARSER_OPTIONS.ID, one, more }));
60+
61+
module.exports = { name: PARSER_OPTIONS.NAME, parser };

lib/ExprParser/lib/grammar/booleanExpressions/andExpr.js

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2017, 2021, Oracle and/or its affiliates.
2+
* Copyright (c) 2017, 2022, Oracle and/or its affiliates.
33
*
44
* This program is free software; you can redistribute it and/or modify
55
* it under the terms of the GNU General Public License, version 2.0, as
@@ -30,17 +30,26 @@
3030

3131
'use strict';
3232

33-
const binaryOpMapper = require('../../mappers/binaryOperator');
33+
const binaryOperatorMapper = require('../../mappers/binaryOperator');
3434
const Pa = require('parsimmon');
3535

36-
const parser = options => r => Pa
37-
.seq(
38-
r.ilriExpr,
39-
Pa
40-
.seq(Pa.whitespace, r.AND, Pa.whitespace, r.ilriExpr)
41-
.map(data => ({ operand: data[3], operator: data[1] }))
42-
.many()
43-
)
44-
.map(binaryOpMapper);
36+
const PARSER_OPTIONS = {
37+
ID: 'andExpr',
38+
NAME: 'AND_EXPR'
39+
};
4540

46-
module.exports = { name: 'AND_EXPR', parser };
41+
/**
42+
* Sub-parser that matches composable expressions with the AND (or "&&") operator.
43+
* andExpr ::= ilriExpr ( ( '&&' | 'AND' ) ilriExpr )*
44+
* @private
45+
* @param {Object} [_] - Optional object containing parser options.
46+
* @returns The generated grammar for the parser.
47+
* @see https://dev.mysql.com/doc/x-devapi-userguide/en/mysql-x-expressions-ebnf-definitions.html#expression-ebnf-andexpr
48+
* @example
49+
* x = :x AND y + y = 5
50+
*/
51+
const parser = _ => r => Pa
52+
.seq(r.ilriExpr, Pa.seq(r.AND.trim(Pa.whitespace), r.ilriExpr).map(([name, param]) => ({ name, params: [param] })).many())
53+
.map(([one, more]) => !more.length ? one : binaryOperatorMapper({ type: PARSER_OPTIONS.ID, one, more }));
54+
55+
module.exports = { name: PARSER_OPTIONS.NAME, parser };

lib/ExprParser/lib/grammar/booleanExpressions/argsList.js

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2017, 2021, Oracle and/or its affiliates.
2+
* Copyright (c) 2017, 2022, Oracle and/or its affiliates.
33
*
44
* This program is free software; you can redistribute it and/or modify
55
* it under the terms of the GNU General Public License, version 2.0, as
@@ -32,14 +32,25 @@
3232

3333
const Pa = require('parsimmon');
3434

35-
const parser = options => r => Pa
36-
.seq(
37-
r.expr.map(data => data.output),
38-
Pa
39-
.seq(Pa.optWhitespace, Pa.string(','), Pa.optWhitespace, r.expr.map(data => data.output))
40-
.map(data => data[3])
41-
.many()
42-
)
43-
.map(data => [data[0]].concat(data[1]));
35+
const PARSER_OPTIONS = {
36+
ID: 'argsList',
37+
NAME: 'ARGS_LIST'
38+
};
4439

45-
module.exports = { name: 'ARG_LIST', parser };
40+
/**
41+
* Sub-parser that matches a comma-separated list of expressions.
42+
* argsList ::= expr ( ',' expr )*
43+
* @private
44+
* @param {Object} [_] - Optional object containing parser options.
45+
* @returns The generated grammar for the parser.
46+
* @see https://dev.mysql.com/doc/x-devapi-userguide/en/mysql-x-expressions-ebnf-definitions.html#expression-ebnf-argslist
47+
* @example
48+
* 'foo', TRUE, :x, 1.234
49+
*/
50+
const parser = _ => r => r.expr
51+
.sepBy(Pa.seq(Pa.optWhitespace, Pa.string(','), Pa.optWhitespace))
52+
// TODO(Rui): for now we will be extracting the value from the
53+
// corresponding expressions but the parser output should be standardized.
54+
.map(exprs => ({ type: PARSER_OPTIONS.ID, value: exprs.map(e => e.value) }));
55+
56+
module.exports = { name: PARSER_OPTIONS.NAME, parser };

lib/ExprParser/lib/grammar/booleanExpressions/atomicExpr.js

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2017, 2021, Oracle and/or its affiliates.
2+
* Copyright (c) 2017, 2022, Oracle and/or its affiliates.
33
*
44
* This program is free software; you can redistribute it and/or modify
55
* it under the terms of the GNU General Public License, version 2.0, as
@@ -30,28 +30,37 @@
3030

3131
'use strict';
3232

33-
const Expr = require('../../../../Protocol/Stubs/mysqlx_expr_pb').Expr;
3433
const Pa = require('parsimmon');
3534

36-
const parser = options => r => Pa
37-
.alt(
38-
r.literal
39-
.map(data => data.output)
40-
.map(data => {
41-
const expr = new Expr();
42-
expr.setType(Expr.Type.LITERAL);
43-
expr.setLiteral(data);
35+
const PARSER_OPTIONS = {
36+
ID: 'atomicExpr',
37+
NAME: 'ATOMIC_EXPR'
38+
};
4439

45-
return expr;
46-
}),
47-
r.jsonArray,
48-
r.jsonDoc,
49-
r.groupedExpr,
50-
r.placeholder,
51-
r.unaryOp,
52-
r.castOp,
53-
r.functionCall,
54-
r.columnOrPath
55-
);
40+
/**
41+
* Sub-parser that matches an atomic expression.
42+
* atomicExpr ::= placeholder
43+
* | columnOrPath
44+
* | functionCall
45+
* | groupedExpr
46+
* | unaryOp
47+
* | castOp
48+
* @private
49+
* @param {Object} [_] - Optional object containing parser options.
50+
* @returns The generated grammar for the parser.
51+
* @see https://dev.mysql.com/doc/x-devapi-userguide/en/mysql-x-expressions-ebnf-definitions.html#expression-ebnf-atomicexpr
52+
* @example
53+
* literals: 'foo', 1, 1.234, TRUE, NULL
54+
* jsonArray: ['foo', 1, 1.234, TRUE, NULL, { "bar": "baz" }, ["qux", "quux"]]
55+
* jsonDoc: { 'foo': 1, 'baz': { 'baz': 1.234 }, 'qux': ['quux'] }
56+
* groupedExpr: (x = :v1 OR y = :v2) AND z = :v3
57+
* placeholder: :x
58+
* unaryOp: !TRUE
59+
* castOp: CAST(foo AS CHAR(3))
60+
* functionCall: foo(bar("baz"))
61+
* columnOrPath: foo.bar.*, foo.bar->>'$.baz.qux'
62+
*/
63+
const parser = _ => r => Pa
64+
.alt(r.literal, r.jsonArray, r.jsonDoc, r.groupedExpr, r.placeholder, r.unaryOp, r.castOp, r.functionCall, r.columnOrPath);
5665

57-
module.exports = { name: 'ATOMIC_EXPR', parser };
66+
module.exports = { name: PARSER_OPTIONS.NAME, parser };

lib/ExprParser/lib/grammar/booleanExpressions/bitExpr.js

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2017, 2021, Oracle and/or its affiliates.
2+
* Copyright (c) 2017, 2022, Oracle and/or its affiliates.
33
*
44
* This program is free software; you can redistribute it and/or modify
55
* it under the terms of the GNU General Public License, version 2.0, as
@@ -30,22 +30,28 @@
3030

3131
'use strict';
3232

33-
const binaryOpMapper = require('../../mappers/binaryOperator');
33+
const binaryOperatorMapper = require('../../mappers/binaryOperator');
3434
const Pa = require('parsimmon');
3535

36-
const parser = options => r => Pa
37-
.seq(
38-
r.shiftExpr,
39-
Pa
40-
.seq(
41-
Pa.optWhitespace,
42-
Pa.alt(Pa.string('&'), Pa.string('|'), Pa.string('^')),
43-
Pa.optWhitespace,
44-
r.shiftExpr
45-
)
46-
.map(data => ({ operand: data[3], operator: data[1] }))
47-
.many()
48-
)
49-
.map(binaryOpMapper);
36+
const PARSER_OPTIONS = {
37+
ID: 'bitExpr',
38+
NAME: 'BIT_EXPR'
39+
};
5040

51-
module.exports = { name: 'BIT_EXPR', parser };
41+
/**
42+
* Sub-parser that matches a bitwise operator expression.
43+
* bitExpr ::= shiftExpr ( ( '&' | '|' | '^' ) shiftExpr )*
44+
* @private
45+
* @param {Object} [_] - Optional object containing parser options.
46+
* @returns The generated grammar for the parser.
47+
* @see https://dev.mysql.com/doc/x-devapi-userguide/en/mysql-x-expressions-ebnf-definitions.html#expression-ebnf-bitexpr
48+
* @example
49+
* 1 & 1
50+
* 1 | 0
51+
* 0 ^ 1
52+
*/
53+
const parser = _ => r => Pa
54+
.seq(r.xorBitExprPart, Pa.seq(Pa.string('|').trim(Pa.optWhitespace), r.xorBitExprPart).map(([name, param]) => ({ name, params: [param] })).many())
55+
.map(([one, more]) => !more.length ? one : binaryOperatorMapper({ type: PARSER_OPTIONS.ID, one, more }));
56+
57+
module.exports = { name: PARSER_OPTIONS.NAME, parser };
Lines changed: 19 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2017, 2021, Oracle and/or its affiliates.
2+
* Copyright (c) 2017, 2022, Oracle and/or its affiliates.
33
*
44
* This program is free software; you can redistribute it and/or modify
55
* it under the terms of the GNU General Public License, version 2.0, as
@@ -30,34 +30,25 @@
3030

3131
'use strict';
3232

33-
const Expr = require('../../../../Protocol/Stubs/mysqlx_expr_pb').Expr;
34-
const Operator = require('../../../../Protocol/Stubs/mysqlx_expr_pb').Operator;
3533
const Pa = require('parsimmon');
3634

37-
const parser = options => r => Pa
38-
.seq(
39-
r.CAST,
40-
Pa.string('('),
41-
Pa.optWhitespace,
42-
r.expr.map(data => data.output),
43-
Pa.whitespace,
44-
r.AS,
45-
Pa.whitespace,
46-
r.castType,
47-
Pa.optWhitespace,
48-
Pa.string(')')
49-
)
50-
.map(data => {
51-
const operator = new Operator();
52-
operator.setName(data[0]);
53-
operator.addParam(data[3]);
54-
operator.addParam(data[7]);
35+
const PARSER_OPTIONS = {
36+
ID: 'castOp',
37+
NAME: 'CAST_OP'
38+
};
5539

56-
const expr = new Expr();
57-
expr.setType(Expr.Type.OPERATOR);
58-
expr.setOperator(operator);
59-
60-
return expr;
61-
});
40+
/**
41+
* Sub-parser that matches a cast function.
42+
* castOp ::= 'CAST' '(' expr 'AS' castType ')'
43+
* @private
44+
* @param {Object} [_] - Optional object containing parser options.
45+
* @returns The generated grammar for the parser.
46+
* @see https://dev.mysql.com/doc/x-devapi-userguide/en/mysql-x-expressions-ebnf-definitions.html#expression-ebnf-castop
47+
* @example
48+
* CAST(foo AS CHAR(3))
49+
*/
50+
const parser = _ => r => Pa.seqMap(r.CAST, Pa.seq(r.expr.map(({ value }) => value).skip(Pa.seq(Pa.whitespace, r.AS, Pa.whitespace)), r.castType).trim(Pa.optWhitespace).wrap(Pa.string('('), Pa.string(')')), (name, params) => {
51+
return { type: PARSER_OPTIONS.ID, value: { name, params } };
52+
});
6253

63-
module.exports = { name: 'CAST_OP', parser };
54+
module.exports = { name: PARSER_OPTIONS.NAME, parser };

0 commit comments

Comments
 (0)