From 51b5fdaf7c1c6d5194f3321bd3eaaf821a857e00 Mon Sep 17 00:00:00 2001 From: Hazem Zaghloul Date: Tue, 26 May 2026 15:47:42 +0100 Subject: [PATCH 1/2] Fix mathematical, bitwise and logical operator precedence in SQL --- .../src/main/antlr/RelationalLexer.g4 | 8 +- .../src/main/antlr/RelationalParser.g4 | 43 +++---- .../functions/SqlFunctionCatalogImpl.java | 2 + .../query/visitors/BaseVisitor.java | 22 +--- .../query/visitors/DelegatingVisitor.java | 22 +--- .../query/visitors/ExpressionVisitor.java | 8 +- .../query/visitors/TypedVisitor.java | 14 +- .../src/test/java/YamlIntegrationTests.java | 5 + .../operator-precedence.metrics.binpb | 75 +++++++++++ .../operator-precedence.metrics.yaml | 40 ++++++ .../test/resources/operator-precedence.yamsql | 121 ++++++++++++++++++ 11 files changed, 278 insertions(+), 82 deletions(-) create mode 100644 yaml-tests/src/test/resources/operator-precedence.metrics.binpb create mode 100644 yaml-tests/src/test/resources/operator-precedence.metrics.yaml create mode 100644 yaml-tests/src/test/resources/operator-precedence.yamsql diff --git a/fdb-relational-core/src/main/antlr/RelationalLexer.g4 b/fdb-relational-core/src/main/antlr/RelationalLexer.g4 index 138236f861..5445d3170a 100644 --- a/fdb-relational-core/src/main/antlr/RelationalLexer.g4 +++ b/fdb-relational-core/src/main/antlr/RelationalLexer.g4 @@ -1248,7 +1248,7 @@ OR_ASSIGN: '|='; STAR: '*'; DIVIDE: '/'; -MODULE: '%'; +MODULO: '%'; PLUS: '+'; MINUS: '-'; DIV: 'DIV'; @@ -1269,7 +1269,13 @@ BIT_NOT_OP: '~'; BIT_OR_OP: '|'; BIT_AND_OP: '&'; BIT_XOR_OP: '^'; +BIT_SHIFT_LEFT_OP: '<<'; +BIT_SHIFT_RIGHT_OP: '>>'; +// Operators. Logical + +LOGICAL_AND_AND: '&&'; +LOGICAL_OR_OR: '||'; // Constructors symbols diff --git a/fdb-relational-core/src/main/antlr/RelationalParser.g4 b/fdb-relational-core/src/main/antlr/RelationalParser.g4 index 3c785b4d58..7f504ca5de 100644 --- a/fdb-relational-core/src/main/antlr/RelationalParser.g4 +++ b/fdb-relational-core/src/main/antlr/RelationalParser.g4 @@ -1221,12 +1221,16 @@ namedFunctionArg ; // Expressions, predicates +// Mathemtical and logical expressions must be listed as different alternatives to the left-recursive +// rules to represent the precedence of the underlying operators. expression - : notOperator=(NOT | '!') expression #notExpression // done + : notOperator=(NOT | EXCLAMATION_SYMBOL) expression #notExpression // done | EXISTS '(' query ')' #existsExpressionAtom // done | expressionAtom predicate? #predicatedExpression - | expression logicalOperator expression #logicalExpression // done + | left=expressionAtom comparisonOperator right=expressionAtom #binaryComparisonExpression // done + | left=expression operator=(AND | LOGICAL_AND_AND) right=expression #logicalExpression // done + | left=expression operator=(XOR | OR | LOGICAL_OR_OR) expression #logicalExpression // done ; predicate @@ -1237,16 +1241,19 @@ predicate ; expressionAtom - : constant #constantExpressionAtom // done - | fullColumnName #fullColumnNameExpressionAtom // done - | functionCall #functionCallExpressionAtom // done - | preparedStatementParameter #preparedStatementParameterAtom // done - | recordConstructor #recordConstructorExpressionAtom // done - | arrayConstructor #arrayConstructorExpressionAtom // done - | base=expressionAtom LEFT_SQUARE_BRACKET index=expressionAtom RIGHT_SQUARE_BRACKET #subscriptExpression // done - | left=expressionAtom bitOperator right=expressionAtom #bitExpressionAtom // done - | left=expressionAtom mathOperator right=expressionAtom #mathExpressionAtom // done - | left=expressionAtom comparisonOperator right=expressionAtom #binaryComparisonPredicate // done + : constant #constantExpressionAtom // done + | fullColumnName #fullColumnNameExpressionAtom // done + | functionCall #functionCallExpressionAtom // done + | preparedStatementParameter #preparedStatementParameterAtom // done + | recordConstructor #recordConstructorExpressionAtom // done + | arrayConstructor #arrayConstructorExpressionAtom // done + | base=expressionAtom LEFT_SQUARE_BRACKET index=expressionAtom RIGHT_SQUARE_BRACKET #subscriptExpression // done + | left=expressionAtom operator=(STAR|DIVIDE) right=expressionAtom #mathExpressionAtom // done + | left=expressionAtom operator=(MODULO|DIV|MOD) right=expressionAtom #mathExpressionAtom // done + | left=expressionAtom operator=(PLUS|MINUS) right=expressionAtom #mathExpressionAtom // done + | left=expressionAtom operator=(BIT_SHIFT_LEFT_OP | BIT_SHIFT_RIGHT_OP) right=expressionAtom #bitExpressionAtom // done + | left=expressionAtom operator=BIT_AND_OP right=expressionAtom #bitExpressionAtom // done + | left=expressionAtom operator=(BIT_XOR_OP | BIT_OR_OP) right=expressionAtom #bitExpressionAtom // done ; inList @@ -1270,18 +1277,6 @@ comparisonOperator | IS NOT? DISTINCT FROM ; -logicalOperator - : AND | '&' '&' | XOR | OR | '|' '|' - ; - -bitOperator - : '<' '<' | '>' '>' | '&' | '^' | '|' - ; - -mathOperator - : '*' | '/' | '%' | DIV | MOD | '+' | '-' - ; - jsonOperator : '-' '>' | '-' '>' '>' ; diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/functions/SqlFunctionCatalogImpl.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/functions/SqlFunctionCatalogImpl.java index 8bc23f0ea0..dc970054e5 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/functions/SqlFunctionCatalogImpl.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/functions/SqlFunctionCatalogImpl.java @@ -132,7 +132,9 @@ private static ImmutableMap BuiltInFunctionCatalog.resolve("dot_product_distance", argumentsCount)) .put("not", argumentsCount -> BuiltInFunctionCatalog.resolve("not", argumentsCount)) .put("and", argumentsCount -> BuiltInFunctionCatalog.resolve("and", argumentsCount)) + .put("&&", argumentsCount -> BuiltInFunctionCatalog.resolve("and", argumentsCount)) .put("or", argumentsCount -> BuiltInFunctionCatalog.resolve("or", argumentsCount)) + .put("||", argumentsCount -> BuiltInFunctionCatalog.resolve("or", argumentsCount)) .put("count", argumentsCount -> BuiltInFunctionCatalog.resolve("COUNT", argumentsCount)) .put("max", argumentsCount -> BuiltInFunctionCatalog.resolve("MAX", argumentsCount)) .put("min", argumentsCount -> BuiltInFunctionCatalog.resolve("MIN", argumentsCount)) diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/BaseVisitor.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/BaseVisitor.java index d65aa8feb9..833b5f0cdc 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/BaseVisitor.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/BaseVisitor.java @@ -1587,8 +1587,8 @@ public Expression visitPredicatedExpression(@Nonnull RelationalParser.Predicated @Nonnull @Override - public Expression visitBinaryComparisonPredicate(@Nonnull RelationalParser.BinaryComparisonPredicateContext ctx) { - return expressionVisitor.visitBinaryComparisonPredicate(ctx); + public Expression visitBinaryComparisonExpression(@Nonnull RelationalParser.BinaryComparisonExpressionContext ctx) { + return expressionVisitor.visitBinaryComparisonExpression(ctx); } @Override @@ -1674,24 +1674,6 @@ public Object visitComparisonOperator(@Nonnull RelationalParser.ComparisonOperat return visitChildren(ctx); } - @Nonnull - @Override - public Object visitLogicalOperator(@Nonnull RelationalParser.LogicalOperatorContext ctx) { - return visitChildren(ctx); - } - - @Nonnull - @Override - public Object visitBitOperator(@Nonnull RelationalParser.BitOperatorContext ctx) { - return visitChildren(ctx); - } - - @Nonnull - @Override - public Object visitMathOperator(@Nonnull RelationalParser.MathOperatorContext ctx) { - return visitChildren(ctx); - } - @Nonnull @Override public Object visitJsonOperator(@Nonnull RelationalParser.JsonOperatorContext ctx) { diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/DelegatingVisitor.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/DelegatingVisitor.java index 67c2a0d76b..64c297c51c 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/DelegatingVisitor.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/DelegatingVisitor.java @@ -1525,8 +1525,8 @@ public Expression visitPredicatedExpression(@Nonnull RelationalParser.Predicated @Nonnull @Override - public Expression visitBinaryComparisonPredicate(@Nonnull RelationalParser.BinaryComparisonPredicateContext ctx) { - return getDelegate().visitBinaryComparisonPredicate(ctx); + public Expression visitBinaryComparisonExpression(@Nonnull RelationalParser.BinaryComparisonExpressionContext ctx) { + return getDelegate().visitBinaryComparisonExpression(ctx); } @Override @@ -1632,24 +1632,6 @@ public Object visitComparisonOperator(@Nonnull RelationalParser.ComparisonOperat return getDelegate().visitComparisonOperator(ctx); } - @Nonnull - @Override - public Object visitLogicalOperator(@Nonnull RelationalParser.LogicalOperatorContext ctx) { - return getDelegate().visitLogicalOperator(ctx); - } - - @Nonnull - @Override - public Object visitBitOperator(@Nonnull RelationalParser.BitOperatorContext ctx) { - return getDelegate().visitBitOperator(ctx); - } - - @Nonnull - @Override - public Object visitMathOperator(@Nonnull RelationalParser.MathOperatorContext ctx) { - return getDelegate().visitMathOperator(ctx); - } - @Nonnull @Override public Object visitJsonOperator(@Nonnull RelationalParser.JsonOperatorContext ctx) { diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/ExpressionVisitor.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/ExpressionVisitor.java index cfc3ae9f01..9151d5788e 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/ExpressionVisitor.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/ExpressionVisitor.java @@ -503,7 +503,7 @@ public Expression visitNotExpression(@Nonnull RelationalParser.NotExpressionCont public Expression visitLogicalExpression(@Nonnull RelationalParser.LogicalExpressionContext ctx) { final var left = Assert.castUnchecked(ctx.expression(0).accept(this), Expression.class); final var right = Assert.castUnchecked(ctx.expression(1).accept(this), Expression.class); - return getDelegate().resolveFunction(ctx.logicalOperator().getText(), left, right); + return getDelegate().resolveFunction(ctx.operator.getText(), left, right); } @Nonnull @@ -683,12 +683,12 @@ public Expressions visitExpressions(@Nonnull RelationalParser.ExpressionsContext public Expression visitBitExpressionAtom(@Nonnull RelationalParser.BitExpressionAtomContext ctx) { final var left = Assert.castUnchecked(ctx.left.accept(this), Expression.class); final var right = Assert.castUnchecked(ctx.right.accept(this), Expression.class); - return getDelegate().resolveFunction(ctx.bitOperator().getText(), left, right); + return getDelegate().resolveFunction(ctx.operator.getText(), left, right); } @Nonnull @Override - public Expression visitBinaryComparisonPredicate(@Nonnull RelationalParser.BinaryComparisonPredicateContext ctx) { + public Expression visitBinaryComparisonExpression(@Nonnull RelationalParser.BinaryComparisonExpressionContext ctx) { final var left = Assert.castUnchecked(ctx.left.accept(this), Expression.class); final var right = Assert.castUnchecked(ctx.right.accept(this), Expression.class); return getDelegate().resolveFunction(ctx.comparisonOperator().getText(), left, right); @@ -723,7 +723,7 @@ private Expression visitBetweenComparisonPredicate(@Nonnull Expression operand, public Expression visitMathExpressionAtom(@Nonnull RelationalParser.MathExpressionAtomContext ctx) { final var left = Assert.castUnchecked(ctx.left.accept(this), Expression.class); final var right = Assert.castUnchecked(ctx.right.accept(this), Expression.class); - return getDelegate().resolveFunction(ctx.mathOperator().getText(), left, right); + return getDelegate().resolveFunction(ctx.operator.getText(), left, right); } @Nonnull diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/TypedVisitor.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/TypedVisitor.java index 321c6a7104..9a3965d7ab 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/TypedVisitor.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/TypedVisitor.java @@ -913,7 +913,7 @@ public interface TypedVisitor extends RelationalParserVisitor { @Nonnull @Override - Expression visitBinaryComparisonPredicate(@Nonnull RelationalParser.BinaryComparisonPredicateContext ctx); + Expression visitBinaryComparisonExpression(@Nonnull RelationalParser.BinaryComparisonExpressionContext ctx); @Override Expression visitSubscriptExpression(@Nonnull RelationalParser.SubscriptExpressionContext ctx); @@ -970,18 +970,6 @@ public interface TypedVisitor extends RelationalParserVisitor { @Override Object visitComparisonOperator(@Nonnull RelationalParser.ComparisonOperatorContext ctx); - @Nonnull - @Override - Object visitLogicalOperator(@Nonnull RelationalParser.LogicalOperatorContext ctx); - - @Nonnull - @Override - Object visitBitOperator(@Nonnull RelationalParser.BitOperatorContext ctx); - - @Nonnull - @Override - Object visitMathOperator(@Nonnull RelationalParser.MathOperatorContext ctx); - @Nonnull @Override Object visitJsonOperator(@Nonnull RelationalParser.JsonOperatorContext ctx); diff --git a/yaml-tests/src/test/java/YamlIntegrationTests.java b/yaml-tests/src/test/java/YamlIntegrationTests.java index f42b76bf15..c3180fe29b 100644 --- a/yaml-tests/src/test/java/YamlIntegrationTests.java +++ b/yaml-tests/src/test/java/YamlIntegrationTests.java @@ -493,4 +493,9 @@ public void recordTypeKeyTest(YamlTest.Runner runner) throws Exception { public void filterIndexTest(YamlTest.Runner runner) throws Exception { runner.runYamsql("filter-index.yamsql"); } + + @TestTemplate + public void operatorPrecedenceTest(YamlTest.Runner runner) throws Exception { + runner.runYamsql("operator-precedence.yamsql"); + } } diff --git a/yaml-tests/src/test/resources/operator-precedence.metrics.binpb b/yaml-tests/src/test/resources/operator-precedence.metrics.binpb new file mode 100644 index 0000000000..932ee1e91b --- /dev/null +++ b/yaml-tests/src/test/resources/operator-precedence.metrics.binpb @@ -0,0 +1,75 @@ +" +M + unnamed-2@EXPLAIN select a1 from A where a4 is true or a2 > 10 and a1 > 10! +ɏ ](n0ô 8@SCAN([IS A]) | FILTER _.A4 NOT_NULL AND _.A4 EQUALS promote('true' AS BOOLEAN) ∪ SCAN([IS A, [GREATER_THAN promote(@c11 AS LONG)]]) | FILTER _.A2 GREATER_THAN promote(@c11 AS INT) COMPARE BY (_.A1) | MAP (_.A1 AS A1)digraph G { + fontname=courier; + rankdir=BT; + splines=line; + 1 [ label=<
Value Computation
MAP (q2.A1 AS A1)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS A1)" ]; + 2 [ label=<
Union Distinct
COMPARE BY (_.A1)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS A1, INT AS A2, STRING AS A3, BOOLEAN AS A4, DOUBLE AS A5)" ]; + 3 [ label=<
Predicate Filter
WHERE q2.A4 NOT_NULL AND q2.A4 EQUALS promote('true' AS BOOLEAN)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS A1, INT AS A2, STRING AS A3, BOOLEAN AS A4, DOUBLE AS A5)" ]; + 4 [ label=<
Predicate Filter
WHERE q2.A2 GREATER_THAN promote(@c11 AS INT)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS A1, INT AS A2, STRING AS A3, BOOLEAN AS A4, DOUBLE AS A5)" ]; + 5 [ label=<
Scan
comparisons: [IS A]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS A1, INT AS A2, STRING AS A3, BOOLEAN AS A4, DOUBLE AS A5)" ]; + 6 [ label=<
Scan
comparisons: [IS A, [GREATER_THAN promote(@c11 AS LONG)]]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS A1, INT AS A2, STRING AS A3, BOOLEAN AS A4, DOUBLE AS A5)" ]; + 7 [ label=<
Primary Storage
record types: [A]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS A1, INT AS A2, STRING AS A3, BOOLEAN AS A4, DOUBLE AS A5)" ]; + 8 [ label=<
Primary Storage
record types: [A]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS A1, INT AS A2, STRING AS A3, BOOLEAN AS A4, DOUBLE AS A5)" ]; + 3 -> 2 [ label=< q210> label="q210" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 4 -> 2 [ label=< q212> label="q212" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 5 -> 3 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 6 -> 4 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 7 -> 5 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 8 -> 6 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +}- +R + unnamed-2EEXPLAIN select a1 from A where NOT a4 is false or a2 > 10 and a1 > 10, + +Q (N08@SCAN([IS A]) | FILTER NOT _.A4 NOT_NULL ∪ SCAN([IS A]) | FILTER _.A4 NOT_EQUALS 'false' ∪ SCAN([IS A, [GREATER_THAN promote(@c12 AS LONG)]]) | FILTER _.A2 GREATER_THAN @c12 COMPARE BY (_.A1) | MAP (_.A1 AS A1)*digraph G { + fontname=courier; + rankdir=BT; + splines=line; + 1 [ label=<
Value Computation
MAP (q2.A1 AS A1)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS A1)" ]; + 2 [ label=<
Union Distinct
COMPARE BY (_.A1)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS A1, INT AS A2, STRING AS A3, BOOLEAN AS A4, DOUBLE AS A5)" ]; + 3 [ label=<
Predicate Filter
WHERE q2.A2 GREATER_THAN @c12
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS A1, INT AS A2, STRING AS A3, BOOLEAN AS A4, DOUBLE AS A5)" ]; + 4 [ label=<
Predicate Filter
WHERE NOT q2.A4 NOT_NULL
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS A1, INT AS A2, STRING AS A3, BOOLEAN AS A4, DOUBLE AS A5)" ]; + 5 [ label=<
Predicate Filter
WHERE q2.A4 NOT_EQUALS 'false'
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS A1, INT AS A2, STRING AS A3, BOOLEAN AS A4, DOUBLE AS A5)" ]; + 6 [ label=<
Scan
comparisons: [IS A, [GREATER_THAN promote(@c12 AS LONG)]]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS A1, INT AS A2, STRING AS A3, BOOLEAN AS A4, DOUBLE AS A5)" ]; + 7 [ label=<
Scan
comparisons: [IS A]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS A1, INT AS A2, STRING AS A3, BOOLEAN AS A4, DOUBLE AS A5)" ]; + 8 [ label=<
Scan
comparisons: [IS A]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS A1, INT AS A2, STRING AS A3, BOOLEAN AS A4, DOUBLE AS A5)" ]; + 9 [ label=<
Primary Storage
record types: [A]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS A1, INT AS A2, STRING AS A3, BOOLEAN AS A4, DOUBLE AS A5)" ]; + 10 [ label=<
Primary Storage
record types: [A]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS A1, INT AS A2, STRING AS A3, BOOLEAN AS A4, DOUBLE AS A5)" ]; + 11 [ label=<
Primary Storage
record types: [A]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS A1, INT AS A2, STRING AS A3, BOOLEAN AS A4, DOUBLE AS A5)" ]; + 3 -> 2 [ label=< q151> label="q151" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 4 -> 2 [ label=< q147> label="q147" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 5 -> 2 [ label=< q149> label="q149" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 6 -> 3 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 7 -> 4 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 8 -> 5 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 9 -> 6 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 10 -> 7 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 11 -> 8 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +}" +L + unnamed-2?EXPLAIN select a1 from A where a4 is true || a2 > 10 && a1 > 10! +ú X(n0 +8@SCAN([IS A]) | FILTER _.A4 NOT_NULL AND _.A4 EQUALS promote('true' AS BOOLEAN) ∪ SCAN([IS A, [GREATER_THAN promote(@c11 AS LONG)]]) | FILTER _.A2 GREATER_THAN promote(@c11 AS INT) COMPARE BY (_.A1) | MAP (_.A1 AS A1)digraph G { + fontname=courier; + rankdir=BT; + splines=line; + 1 [ label=<
Value Computation
MAP (q2.A1 AS A1)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS A1)" ]; + 2 [ label=<
Union Distinct
COMPARE BY (_.A1)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS A1, INT AS A2, STRING AS A3, BOOLEAN AS A4, DOUBLE AS A5)" ]; + 3 [ label=<
Predicate Filter
WHERE q2.A4 NOT_NULL AND q2.A4 EQUALS promote('true' AS BOOLEAN)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS A1, INT AS A2, STRING AS A3, BOOLEAN AS A4, DOUBLE AS A5)" ]; + 4 [ label=<
Predicate Filter
WHERE q2.A2 GREATER_THAN promote(@c11 AS INT)
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS A1, INT AS A2, STRING AS A3, BOOLEAN AS A4, DOUBLE AS A5)" ]; + 5 [ label=<
Scan
comparisons: [IS A]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS A1, INT AS A2, STRING AS A3, BOOLEAN AS A4, DOUBLE AS A5)" ]; + 6 [ label=<
Scan
comparisons: [IS A, [GREATER_THAN promote(@c11 AS LONG)]]
> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS A1, INT AS A2, STRING AS A3, BOOLEAN AS A4, DOUBLE AS A5)" ]; + 7 [ label=<
Primary Storage
record types: [A]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS A1, INT AS A2, STRING AS A3, BOOLEAN AS A4, DOUBLE AS A5)" ]; + 8 [ label=<
Primary Storage
record types: [A]
> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(LONG AS A1, INT AS A2, STRING AS A3, BOOLEAN AS A4, DOUBLE AS A5)" ]; + 3 -> 2 [ label=< q210> label="q210" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 4 -> 2 [ label=< q212> label="q212" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 5 -> 3 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 6 -> 4 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 7 -> 5 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 8 -> 6 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; + 2 -> 1 [ label=< q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ]; +} \ No newline at end of file diff --git a/yaml-tests/src/test/resources/operator-precedence.metrics.yaml b/yaml-tests/src/test/resources/operator-precedence.metrics.yaml new file mode 100644 index 0000000000..f4fe167b31 --- /dev/null +++ b/yaml-tests/src/test/resources/operator-precedence.metrics.yaml @@ -0,0 +1,40 @@ +unnamed-2: +- query: EXPLAIN select a1 from A where a4 is true or a2 > 10 and a1 > 10 + ref: operator-precedence.yamsql:65 + explain: SCAN([IS A]) | FILTER _.A4 NOT_NULL AND _.A4 EQUALS promote('true' AS + BOOLEAN) ∪ SCAN([IS A, [GREATER_THAN promote(@c11 AS LONG)]]) | FILTER _.A2 + GREATER_THAN promote(@c11 AS INT) COMPARE BY (_.A1) | MAP (_.A1 AS A1) + task_count: 1855 + task_total_time_ms: 403 + transform_count: 498 + transform_time_ms: 195 + transform_yield_count: 110 + insert_time_ms: 23 + insert_new_count: 212 + insert_reused_count: 26 +- query: EXPLAIN select a1 from A where NOT a4 is false or a2 > 10 and a1 > 10 + ref: operator-precedence.yamsql:72 + explain: SCAN([IS A]) | FILTER NOT _.A4 NOT_NULL ∪ SCAN([IS A]) | FILTER _.A4 + NOT_EQUALS 'false' ∪ SCAN([IS A, [GREATER_THAN promote(@c12 AS LONG)]]) | + FILTER _.A2 GREATER_THAN @c12 COMPARE BY (_.A1) | MAP (_.A1 AS A1) + task_count: 1349 + task_total_time_ms: 171 + transform_count: 364 + transform_time_ms: 65 + transform_yield_count: 78 + insert_time_ms: 8 + insert_new_count: 149 + insert_reused_count: 21 +- query: EXPLAIN select a1 from A where a4 is true || a2 > 10 && a1 > 10 + ref: operator-precedence.yamsql:79 + explain: SCAN([IS A]) | FILTER _.A4 NOT_NULL AND _.A4 EQUALS promote('true' AS + BOOLEAN) ∪ SCAN([IS A, [GREATER_THAN promote(@c11 AS LONG)]]) | FILTER _.A2 + GREATER_THAN promote(@c11 AS INT) COMPARE BY (_.A1) | MAP (_.A1 AS A1) + task_count: 1855 + task_total_time_ms: 391 + transform_count: 498 + transform_time_ms: 186 + transform_yield_count: 110 + insert_time_ms: 22 + insert_new_count: 212 + insert_reused_count: 26 diff --git a/yaml-tests/src/test/resources/operator-precedence.yamsql b/yaml-tests/src/test/resources/operator-precedence.yamsql new file mode 100644 index 0000000000..c9284fd451 --- /dev/null +++ b/yaml-tests/src/test/resources/operator-precedence.yamsql @@ -0,0 +1,121 @@ +# +# operator-precedence.yamsql +# +# This source file is part of the FoundationDB open source project +# +# Copyright 2021-2026 Apple Inc. and the FoundationDB project authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- +schema_template: + create table A(a1 bigint, a2 integer, a3 string, a4 boolean, a5 double, primary key(a1)) +--- +setup: + steps: + - query: insert into A(a1, a2, a3, a4, a5) values (1, 1, '1', true, 1.0) +--- +test_block: + name: arithmetic-operators + tests: + - + - query: select 3 + 1 * 5 from range(1, 2) + - initialVersionLessThan: !current_version + - unorderedResult: [ { 20 } ] + - initialVersionAtLeast: !current_version + - unorderedResult: [ { 8 } ] + - + - query: select 3 + 1 - 5 from range(1, 2) + - unorderedResult: [ { -1 } ] + - + - query: select (3 + 1) * 5 from range(1, 2) + - unorderedResult: [ { 20 } ] + - + - query: select ((3*5)*(4+6))*(1+0) from range(1, 2) + - unorderedResult: [ { 150 } ] + - + - query: select 6 * 5 + 3 * 5 * 4 - 1 + 2 from range(1, 2) + - initialVersionLessThan: !current_version + - unorderedResult: [ { 661 } ] + - initialVersionAtLeast: !current_version + - unorderedResult: [ { 91 } ] + - + - query: select ((6 * 5) + 3) * 5 * 4 - 1 + 2 from range(1, 2) + - unorderedResult: [ { 661 } ] + - + - query: select 15 / 5 * 4 + 6 * 5 - 1 + 2 * 9 % 6 from range(1, 2) + - initialVersionLessThan: !current_version + - unorderedResult: [ { 3 } ] + - initialVersionAtLeast: !current_version + - unorderedResult: [ { 41 } ] + - + - query: select ((15 / 5) * 4) + (6 * 5) - 1 + ((2 * 9) % 6) from range(1, 2) + - unorderedResult: [ { 41 } ] +--- +test_block: + name: bitwise-operators + tests: + - + - query: select 5 | 6 & 4 from range(1, 2) + - initialVersionLessThan: !current_version + - unorderedResult: [ { 4 } ] + - initialVersionAtLeast: !current_version + - unorderedResult: [ { 5 } ] + - + - query: select 5 ^ 6 & 4 from range(1, 2) + - initialVersionLessThan: !current_version + - unorderedResult: [ { 0 } ] + - initialVersionAtLeast: !current_version + - unorderedResult: [ { 1 } ] + - + - query: select 12 & 10 | 3 & 5 from range(1, 2) + - initialVersionLessThan: !current_version + - unorderedResult: [ { 1 } ] + - initialVersionAtLeast: !current_version + - unorderedResult: [ { 9 } ] + - + - query: select (5 | 6) & 4 from range(1, 2) + - unorderedResult: [ { 4 } ] + - + - query: select (5 ^ 6) & 4 from range(1, 2) + - unorderedResult: [ { 0 } ] + - + - query: select 5 | (6 & 4) from range(1, 2) + - unorderedResult: [ { 5 } ] +--- +test_block: + name: logical-operators + tests: + - + - query: select a1 from A where a4 is true or a2 > 10 and a1 > 10 + - explain: "SCAN([IS A]) | FILTER _.A4 NOT_NULL AND _.A4 EQUALS promote('true' AS BOOLEAN) ∪ SCAN([IS A, [GREATER_THAN promote(@c11 AS LONG)]]) | FILTER _.A2 GREATER_THAN promote(@c11 AS INT) COMPARE BY (_.A1) | MAP (_.A1 AS A1)" + - initialVersionLessThan: !current_version + - unorderedResult: [] + - initialVersionAtLeast: !current_version + - unorderedResult: [{ !l 1 }] + - + - query: select a1 from A where NOT a4 is false or a2 > 10 and a1 > 10 + - explain: "SCAN([IS A]) | FILTER NOT _.A4 NOT_NULL ∪ SCAN([IS A]) | FILTER _.A4 NOT_EQUALS 'false' ∪ SCAN([IS A, [GREATER_THAN promote(@c12 AS LONG)]]) | FILTER _.A2 GREATER_THAN @c12 COMPARE BY (_.A1) | MAP (_.A1 AS A1)" + - initialVersionLessThan: !current_version + - unorderedResult: [] + - initialVersionAtLeast: !current_version + - unorderedResult: [{ !l 1 }] + - + - query: select a1 from A where a4 is true || a2 > 10 && a1 > 10 + - explain: "SCAN([IS A]) | FILTER _.A4 NOT_NULL AND _.A4 EQUALS promote('true' AS BOOLEAN) ∪ SCAN([IS A, [GREATER_THAN promote(@c11 AS LONG)]]) | FILTER _.A2 GREATER_THAN promote(@c11 AS INT) COMPARE BY (_.A1) | MAP (_.A1 AS A1)" + - initialVersionLessThan: !current_version + - error: '0AF00' + - initialVersionAtLeast: !current_version + - unorderedResult: [{ !l 1 }] +... + From d2e65d50eb56d6d92527bcb00a55a88fb53a3fe3 Mon Sep 17 00:00:00 2001 From: Hazem Zaghloul Date: Tue, 26 May 2026 19:01:47 +0100 Subject: [PATCH 2/2] Give higher precedence to bitwise operators --- fdb-relational-core/src/main/antlr/RelationalParser.g4 | 6 +++--- yaml-tests/src/test/resources/operator-precedence.yamsql | 6 ++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/fdb-relational-core/src/main/antlr/RelationalParser.g4 b/fdb-relational-core/src/main/antlr/RelationalParser.g4 index 7f504ca5de..32f8408253 100644 --- a/fdb-relational-core/src/main/antlr/RelationalParser.g4 +++ b/fdb-relational-core/src/main/antlr/RelationalParser.g4 @@ -1248,12 +1248,12 @@ expressionAtom | recordConstructor #recordConstructorExpressionAtom // done | arrayConstructor #arrayConstructorExpressionAtom // done | base=expressionAtom LEFT_SQUARE_BRACKET index=expressionAtom RIGHT_SQUARE_BRACKET #subscriptExpression // done + | left=expressionAtom operator=(BIT_SHIFT_LEFT_OP | BIT_SHIFT_RIGHT_OP) right=expressionAtom #bitExpressionAtom // done + | left=expressionAtom operator=BIT_AND_OP right=expressionAtom #bitExpressionAtom // done + | left=expressionAtom operator=(BIT_XOR_OP | BIT_OR_OP) right=expressionAtom #bitExpressionAtom // done | left=expressionAtom operator=(STAR|DIVIDE) right=expressionAtom #mathExpressionAtom // done | left=expressionAtom operator=(MODULO|DIV|MOD) right=expressionAtom #mathExpressionAtom // done | left=expressionAtom operator=(PLUS|MINUS) right=expressionAtom #mathExpressionAtom // done - | left=expressionAtom operator=(BIT_SHIFT_LEFT_OP | BIT_SHIFT_RIGHT_OP) right=expressionAtom #bitExpressionAtom // done - | left=expressionAtom operator=BIT_AND_OP right=expressionAtom #bitExpressionAtom // done - | left=expressionAtom operator=(BIT_XOR_OP | BIT_OR_OP) right=expressionAtom #bitExpressionAtom // done ; inList diff --git a/yaml-tests/src/test/resources/operator-precedence.yamsql b/yaml-tests/src/test/resources/operator-precedence.yamsql index c9284fd451..fa09298c2c 100644 --- a/yaml-tests/src/test/resources/operator-precedence.yamsql +++ b/yaml-tests/src/test/resources/operator-precedence.yamsql @@ -92,6 +92,12 @@ test_block: - - query: select 5 | (6 & 4) from range(1, 2) - unorderedResult: [ { 5 } ] + - + - query: select a1 from A where a1 & 1l = 1l AND a1 & 1l > 0 + - unorderedResult: [ { !l 1 } ] + - + - query: select 5 & 6 + 4 from range(1, 2) + - unorderedResult: [ { 8 } ] --- test_block: name: logical-operators