From da90d7c054b81ff8d7fe267a1327d5ef3dbf2d07 Mon Sep 17 00:00:00 2001 From: Nicolas Adment <39568358+nadment@users.noreply.github.com> Date: Fri, 14 Jun 2024 12:00:48 +0200 Subject: [PATCH] Simplify arithmetic and bitwise operators MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NULL / x → NULL x / NULL → NULL DIV0(x,NULL) → NULL DIV0(NULL,x) → NULL DIV0(x,0) → 0 if x is not nullable x % NULL → NULL NULL % x → NULL A % 1 → A NULL ^ A → NULL 0 ^ A → A (even if A is null) NULL | A → NULL 0 | A → A (even if A is null) NULL & A → NULL 0 & A -> 0 (if A not nullable) NULL * x → NULL Simplify EQUAL operator with TRUE/FALSE when operands is of boolean type Fix simplify NOT_EQUAL operator with TRUE/FALSE when operands is of boolean type Add tests EQUAL and BOT_EQUAL operators with NULL Fix simplify 0 * A → 0 (if A is not nullable) --- .../expression/operator/BitAndFunction.java | 39 +++++ .../expression/operator/BitOrFunction.java | 17 ++- .../expression/operator/BitXorFunction.java | 29 ++++ .../expression/operator/BoolOrOperator.java | 1 + .../hop/expression/operator/Div0Function.java | 15 ++ .../hop/expression/operator/DivOperator.java | 14 +- .../expression/operator/EqualOperator.java | 15 +- .../hop/expression/operator/ModFunction.java | 26 ++++ .../expression/operator/MultiplyOperator.java | 17 ++- .../expression/operator/NotEqualOperator.java | 19 ++- .../hop/core/expression/OperatorTest.java | 133 +++++++++++++++--- 11 files changed, 286 insertions(+), 39 deletions(-) diff --git a/plugins/src/main/java/org/apache/hop/expression/operator/BitAndFunction.java b/plugins/src/main/java/org/apache/hop/expression/operator/BitAndFunction.java index e6c84976..49d9058e 100644 --- a/plugins/src/main/java/org/apache/hop/expression/operator/BitAndFunction.java +++ b/plugins/src/main/java/org/apache/hop/expression/operator/BitAndFunction.java @@ -18,9 +18,15 @@ package org.apache.hop.expression.operator; import java.io.StringWriter; +import java.util.PriorityQueue; +import org.apache.hop.expression.Call; +import org.apache.hop.expression.ExpressionComparator; +import org.apache.hop.expression.ExpressionException; import org.apache.hop.expression.Function; import org.apache.hop.expression.FunctionPlugin; import org.apache.hop.expression.IExpression; +import org.apache.hop.expression.IExpressionContext; +import org.apache.hop.expression.Literal; import org.apache.hop.expression.OperatorCategory; import org.apache.hop.expression.type.OperandTypes; import org.apache.hop.expression.type.ReturnTypes; @@ -53,6 +59,39 @@ public BitAndFunction(String name) { "/docs/bit_and.html"); } + @Override + public boolean isSymmetrical() { + return true; + } + + @Override + public IExpression compile(IExpressionContext context, Call call) throws ExpressionException { + // Reorder chained symmetric operator and simplify A & (..A..) --> (..A..) + PriorityQueue operands = new PriorityQueue<>(new ExpressionComparator()); + operands.addAll(this.getChainedOperands(call, true)); + IExpression operand = operands.poll(); + while (!operands.isEmpty()) { + call = new Call(this, operand, operands.poll()); + call.inferReturnType(); + operand = call; + } + call = operand.asCall(); + IExpression left = call.getOperand(0); + IExpression right = call.getOperand(1); + + // Simplify NULL & A → NULL + if (left.isNull()) { + return Literal.NULL; + } + + // Simplify 0 & A -> 0 (if A not nullable) + if (Literal.ZERO.equals(left) && !right.getType().isNullable()) { + return Literal.ZERO; + } + + return call; + } + @Override public Object eval(final IExpression[] operands) { Long left = operands[0].getValue(Long.class); diff --git a/plugins/src/main/java/org/apache/hop/expression/operator/BitOrFunction.java b/plugins/src/main/java/org/apache/hop/expression/operator/BitOrFunction.java index ab5f5de0..42b9f027 100644 --- a/plugins/src/main/java/org/apache/hop/expression/operator/BitOrFunction.java +++ b/plugins/src/main/java/org/apache/hop/expression/operator/BitOrFunction.java @@ -65,7 +65,7 @@ public boolean isSymmetrical() { @Override public IExpression compile(IExpressionContext context, Call call) throws ExpressionException { - // Reorder chained symmetric operator + // Reorder chained symmetric operator and simplify A | (..A..) --> (..A..) PriorityQueue operands = new PriorityQueue<>(new ExpressionComparator()); operands.addAll(this.getChainedOperands(call, true)); IExpression operand = operands.poll(); @@ -75,12 +75,21 @@ public IExpression compile(IExpressionContext context, Call call) throws Express operand = call; } call = operand.asCall(); + IExpression left = call.getOperand(0); + IExpression right = call.getOperand(1); - // Simplify 0|A → A - if (Literal.ZERO.equals(call.getOperand(0))) { - return call.getOperand(1); + // Simplify NULL | A → NULL + if (left.isNull()) { + return Literal.NULL; } + // Simplify 0 | A → A (even if A is null) + if (Literal.ZERO.equals(left)) { + return right; + } + + // TODO: Simplify A | !A → -1 (if A is not nullable) + return call; } diff --git a/plugins/src/main/java/org/apache/hop/expression/operator/BitXorFunction.java b/plugins/src/main/java/org/apache/hop/expression/operator/BitXorFunction.java index ff0b0f22..1648579a 100644 --- a/plugins/src/main/java/org/apache/hop/expression/operator/BitXorFunction.java +++ b/plugins/src/main/java/org/apache/hop/expression/operator/BitXorFunction.java @@ -17,9 +17,13 @@ package org.apache.hop.expression.operator; import java.io.StringWriter; +import org.apache.hop.expression.Call; +import org.apache.hop.expression.ExpressionException; import org.apache.hop.expression.Function; import org.apache.hop.expression.FunctionPlugin; import org.apache.hop.expression.IExpression; +import org.apache.hop.expression.IExpressionContext; +import org.apache.hop.expression.Literal; import org.apache.hop.expression.OperatorCategory; import org.apache.hop.expression.type.OperandTypes; import org.apache.hop.expression.type.ReturnTypes; @@ -52,6 +56,31 @@ public BitXorFunction(String name) { "/docs/bit_xor.html"); } + @Override + public IExpression compile(IExpressionContext context, Call call) throws ExpressionException { + IExpression left = call.getOperand(0); + IExpression right = call.getOperand(1); + + // Simplify A ^ NULL → NULL + // Simplify NULL ^ A → NULL + if (left.isNull() || right.isNull()) { + return Literal.NULL; + } + + // Simplify 0 ^ A → A (even if A is not nullable) + if (Literal.ZERO.equals(left)) { + return right; + } + if (Literal.ZERO.equals(right)) { + return left; + } + + // TODO: Simplify A ^ (..A..) → (the expression without A, if number of A is odd, otherwise + // one A) + + return call; + } + @Override public Object eval(final IExpression[] operands) { Long left = operands[0].getValue(Long.class); diff --git a/plugins/src/main/java/org/apache/hop/expression/operator/BoolOrOperator.java b/plugins/src/main/java/org/apache/hop/expression/operator/BoolOrOperator.java index 17e35d3c..79b6bd6c 100644 --- a/plugins/src/main/java/org/apache/hop/expression/operator/BoolOrOperator.java +++ b/plugins/src/main/java/org/apache/hop/expression/operator/BoolOrOperator.java @@ -187,6 +187,7 @@ public IExpression compile(IExpressionContext context, Call call) throws Express // Simplify x OR NOT x → TRUE (if x is not nullable) // Simplify x OR NOT x → x IS NOT NULL OR NULL (if x is nullable) + // Simplify x OR (x AND y) → x (if x not nullable) // Simplify x OR x IS NOT NULL → x IS NOT NULL for (IExpression identifier : identifiers) { diff --git a/plugins/src/main/java/org/apache/hop/expression/operator/Div0Function.java b/plugins/src/main/java/org/apache/hop/expression/operator/Div0Function.java index 53a63ad2..877ef7de 100644 --- a/plugins/src/main/java/org/apache/hop/expression/operator/Div0Function.java +++ b/plugins/src/main/java/org/apache/hop/expression/operator/Div0Function.java @@ -53,6 +53,21 @@ public IExpression compile(IExpressionContext context, Call call) throws Express IExpression left = call.getOperand(0); IExpression right = call.getOperand(1); + // Simplify DIV0(x,NULL) → NULL + if (right.isNull()) { + return Literal.NULL; + } + + // Simplify DIV0(NULL,x) → NULL + if (left.isNull()) { + return Literal.NULL; + } + + // Simplify arithmetic DIV0(A,0) → 0 if x is not nullable + if (Literal.ZERO.equals(right) && !left.getType().isNullable()) { + return Literal.ZERO; + } + // Simplify arithmetic DIV0(A,1) → A if (Literal.ONE.equals(right)) { return call.getOperand(0); diff --git a/plugins/src/main/java/org/apache/hop/expression/operator/DivOperator.java b/plugins/src/main/java/org/apache/hop/expression/operator/DivOperator.java index 0dc65cdb..29145580 100644 --- a/plugins/src/main/java/org/apache/hop/expression/operator/DivOperator.java +++ b/plugins/src/main/java/org/apache/hop/expression/operator/DivOperator.java @@ -56,12 +56,22 @@ public IExpression compile(IExpressionContext context, Call call) throws Express IExpression left = call.getOperand(0); IExpression right = call.getOperand(1); - // Simplify arithmetic A/1 → A + // Simplify x / NULL → NULL + if (right.isNull()) { + return Literal.NULL; + } + + // Simplify NULL / x → NULL + if (left.isNull()) { + return Literal.NULL; + } + + // Simplify arithmetic A / 1 → A if (Literal.ONE.equals(right)) { return call.getOperand(0); } - // Simplify arithmetic (-A)/(-B) → A/B + // Simplify arithmetic (-A) / (-B) → A / B if (left.is(Operators.NEGATE) && right.is(Operators.NEGATE)) { return new Call(Operators.DIVIDE, left.asCall().getOperand(0), right.asCall().getOperand(0)); } diff --git a/plugins/src/main/java/org/apache/hop/expression/operator/EqualOperator.java b/plugins/src/main/java/org/apache/hop/expression/operator/EqualOperator.java index e4a42761..360c07f6 100644 --- a/plugins/src/main/java/org/apache/hop/expression/operator/EqualOperator.java +++ b/plugins/src/main/java/org/apache/hop/expression/operator/EqualOperator.java @@ -29,6 +29,7 @@ import org.apache.hop.expression.type.Comparison; import org.apache.hop.expression.type.OperandTypes; import org.apache.hop.expression.type.ReturnTypes; +import org.apache.hop.expression.type.TypeId; import org.apache.hop.expression.type.Types; /** @@ -92,7 +93,19 @@ public IExpression compile(IExpressionContext context, Call call) throws Express return new Call(this, right, left); } - // Simplify if not nullable x=x → TRUE + // Simplify comparison when operands is of boolean type + // TRUE=x → x + // FALSE=x → NOT x + if (right.getType().is(TypeId.BOOLEAN)) { + if (left == Literal.TRUE) { + return right; + } + if (left == Literal.FALSE) { + return new Call(Operators.BOOLNOT, right); + } + } + + // Simplify x=x → TRUE (if x is not nullable) if (left.equals(right) && !left.getType().isNullable()) { return Literal.TRUE; } diff --git a/plugins/src/main/java/org/apache/hop/expression/operator/ModFunction.java b/plugins/src/main/java/org/apache/hop/expression/operator/ModFunction.java index 758326e0..c0a2085a 100644 --- a/plugins/src/main/java/org/apache/hop/expression/operator/ModFunction.java +++ b/plugins/src/main/java/org/apache/hop/expression/operator/ModFunction.java @@ -20,9 +20,12 @@ import java.math.BigDecimal; import org.apache.hop.expression.Call; import org.apache.hop.expression.ErrorCode; +import org.apache.hop.expression.ExpressionException; import org.apache.hop.expression.Function; import org.apache.hop.expression.FunctionPlugin; import org.apache.hop.expression.IExpression; +import org.apache.hop.expression.IExpressionContext; +import org.apache.hop.expression.Literal; import org.apache.hop.expression.OperatorCategory; import org.apache.hop.expression.type.OperandTypes; import org.apache.hop.expression.type.ReturnTypes; @@ -56,6 +59,29 @@ public ModFunction(String name) { "/docs/mod.html"); } + @Override + public IExpression compile(IExpressionContext context, Call call) throws ExpressionException { + IExpression left = call.getOperand(0); + IExpression right = call.getOperand(1); + + // Simplify A % NULL → NULL + if (right.isNull()) { + return Literal.NULL; + } + + // Simplify NULL % A → NULL + if (left.isNull()) { + return Literal.NULL; + } + + // Simplify arithmetic A % 1 → A + if (Literal.ONE.equals(right)) { + return call.getOperand(0); + } + + return call; + } + @Override public Object eval(final IExpression[] operands) { BigDecimal value = operands[0].getValue(BigDecimal.class); diff --git a/plugins/src/main/java/org/apache/hop/expression/operator/MultiplyOperator.java b/plugins/src/main/java/org/apache/hop/expression/operator/MultiplyOperator.java index 7c5b2a22..bf9a384c 100644 --- a/plugins/src/main/java/org/apache/hop/expression/operator/MultiplyOperator.java +++ b/plugins/src/main/java/org/apache/hop/expression/operator/MultiplyOperator.java @@ -71,28 +71,33 @@ public IExpression compile(IExpressionContext context, Call call) throws Express IExpression left = call.getOperand(0); IExpression right = call.getOperand(1); - // Simplify arithmetic 0*A → 0 - if (Literal.ZERO.equals(left)) { + // Simplify NULL * A → NULL + if (left.isNull()) { + return Literal.NULL; + } + + // Simplify arithmetic 0 * A → 0 (if A is not nullable) + if (Literal.ZERO.equals(left) && !right.getType().isNullable()) { return Literal.ZERO; } - // Simplify arithmetic 1*A → A + // Simplify arithmetic 1 * A → A if (Literal.ONE.equals(left)) { return right; } - // Simplify arithmetic (-A)*(-B) → A*B + // Simplify arithmetic (-A) * (-B) → A * B if (left.is(Operators.NEGATE) && right.is(Operators.NEGATE)) { return new Call( Operators.MULTIPLY, left.asCall().getOperand(0), right.asCall().getOperand(0)); } - // Simplify arithmetic A*(1/B) → A/B + // Simplify arithmetic A * (1 / B) → A / B if (right.is(Operators.DIVIDE) && Literal.ONE.equals(right.asCall().getOperand(0))) { return new Call(Operators.DIVIDE, left, right.asCall().getOperand(1)); } - // Simplify arithmetic A*A → SQUARE(A) + // Simplify arithmetic A * A → SQUARE(A) if (left.equals(right)) { return new Call(SquareFunction.INSTANCE, left); } diff --git a/plugins/src/main/java/org/apache/hop/expression/operator/NotEqualOperator.java b/plugins/src/main/java/org/apache/hop/expression/operator/NotEqualOperator.java index 42ff4a88..121e9de1 100644 --- a/plugins/src/main/java/org/apache/hop/expression/operator/NotEqualOperator.java +++ b/plugins/src/main/java/org/apache/hop/expression/operator/NotEqualOperator.java @@ -87,14 +87,21 @@ public IExpression compile(IExpressionContext context, Call call) throws Express return new Call(this, right, left); } - // Simplify only if x is data type boolean TRUE<>x → X IS NOT TRUE - if (left.equals(Literal.TRUE) && right.getType().is(TypeId.BOOLEAN)) { - return new Call(Operators.IS_NOT_TRUE, right); + // Simplify comparison when operands is of boolean type + // TRUE<>x → x + // FALSE<>x → NOT x + if (right.getType().is(TypeId.BOOLEAN)) { + if (left == Literal.TRUE) { + return new Call(Operators.BOOLNOT, right); + } + if (left == Literal.FALSE) { + return right; + } } - // Simplify only if x is data type boolean FALSE<>x → X IS NOT FALSE - if (left.equals(Literal.FALSE) && right.getType().is(TypeId.BOOLEAN)) { - return new Call(Operators.IS_NOT_FALSE, right); + // Simplify x!=NULL → NULL + if (left.isNull() || right.isNull()) { + return Literal.NULL; } // Simplify x!=x → NULL AND x IS NULL diff --git a/plugins/src/test/java/org/apache/hop/core/expression/OperatorTest.java b/plugins/src/test/java/org/apache/hop/core/expression/OperatorTest.java index d755b372..cfecd6a1 100644 --- a/plugins/src/test/java/org/apache/hop/core/expression/OperatorTest.java +++ b/plugins/src/test/java/org/apache/hop/core/expression/OperatorTest.java @@ -129,15 +129,27 @@ public void EqualTo() throws Exception { evalFails("FIELD_INTEGER="); evalFails(" = FIELD_INTEGER "); + // Normalize + optimize("10=FIELD_INTEGER"); + optimize("FIELD_INTEGER=40", "40=FIELD_INTEGER"); + + // Simplify comparison with literals optimizeTrue("'a' = 'a'"); optimizeFalse("'a' = 'b'"); optimizeFalse("10151082135029368 = 10151082135029369"); - optimize("FIELD_INTEGER=40", "40=FIELD_INTEGER"); + optimizeNull("NULL::STRING=FIELD_STRING"); + optimizeNull("FIELD_STRING=NULL::STRING"); - // Simplify arithmetic comparisons + // Simplify comparison when operands is of boolean type + optimize("FIELD_BOOLEAN_TRUE=TRUE", "FIELD_BOOLEAN_TRUE"); + optimize("TRUE=FIELD_BOOLEAN_TRUE", "FIELD_BOOLEAN_TRUE"); + optimize("FIELD_BOOLEAN_TRUE=FALSE", "NOT FIELD_BOOLEAN_TRUE"); + optimize("FALSE=FIELD_BOOLEAN_TRUE", "NOT FIELD_BOOLEAN_TRUE"); + + // Simplify comparison with arithmetic optimize("FIELD_INTEGER+1=3", "2=FIELD_INTEGER"); - // Simplify comparison with same term if not nullable + // Simplify comparison with the same term when it is not nullable optimize("FIELD_STRING=FIELD_STRING", "FIELD_STRING=FIELD_STRING"); optimize("PI()=PI()", "TRUE"); } @@ -205,14 +217,28 @@ public void NotEqualTo() throws Exception { evalFails("FIELD_INTEGER ! "); evalFails("<>FIELD_INTEGER"); - optimize("FIELD_BOOLEAN_TRUE<>TRUE", "FIELD_BOOLEAN_TRUE IS NOT TRUE"); - optimize("TRUE<>FIELD_BOOLEAN_TRUE", "FIELD_BOOLEAN_TRUE IS NOT TRUE"); - optimize("FIELD_BOOLEAN_TRUE<>FALSE", "FIELD_BOOLEAN_TRUE IS NOT FALSE"); - optimize("FALSE<>FIELD_BOOLEAN_TRUE", "FIELD_BOOLEAN_TRUE IS NOT FALSE"); + // Normalize optimize("10!=FIELD_INTEGER"); + optimize("FIELD_INTEGER=40", "40=FIELD_INTEGER"); + + // Simplify comparison with literals + optimizeFalse("'a' <> 'a'"); + optimizeTrue("'a' <> 'b'"); + optimizeFalse("10151082135029368 <> 10151082135029368"); + optimizeTrue("10151082135029368 <> 10151082135029369"); + optimizeNull("NULL::STRING<>FIELD_STRING"); + optimizeNull("FIELD_STRING<>NULL::STRING"); + + // Simplify comparison with the same term optimize("FIELD_STRING!=FIELD_STRING", "NULL AND FIELD_STRING IS NULL"); - // Simplify arithmetic comparisons + // Simplify comparison when operands is of boolean type + optimize("FIELD_BOOLEAN_TRUE<>FALSE", "FIELD_BOOLEAN_TRUE"); + optimize("FALSE<>FIELD_BOOLEAN_TRUE", "FIELD_BOOLEAN_TRUE"); + optimize("FIELD_BOOLEAN_TRUE<>TRUE", "NOT FIELD_BOOLEAN_TRUE"); + optimize("TRUE<>FIELD_BOOLEAN_TRUE", "NOT FIELD_BOOLEAN_TRUE"); + + // Simplify comparison with arithmetic optimize("FIELD_INTEGER+1!=3", "2!=FIELD_INTEGER"); } @@ -1479,14 +1505,25 @@ public void Mod() throws Exception { evalFails("'TEST'%5"); evalFails("Mod()"); evalFails("Mod(3)"); + // Division by 0 evalFails("Mod(9,0)"); + // Normalize + optimize("0%0"); optimize("FIELD_INTEGER%4"); + + // Simplify arithmetic with NULL + optimizeNull("NULL::INTEGER%FIELD_INTEGER"); + optimizeNull("FIELD_INTEGER%NULL::INTEGER"); + + // Simplify arithmetic A%1 → A + optimize("FIELD_INTEGER%1", "FIELD_INTEGER"); + optimize("FIELD_INTEGER%1.0", "FIELD_INTEGER"); } @Test - public void Multiplication() throws Exception { + public void Multiply() throws Exception { evalEquals("2.55*10", 25.50D).returnType(NumberType.of(5, 2)); evalEquals("4*10", 40D).returnType(IntegerType.of(3)); evalEquals("-4*-1", 4D).returnType(IntegerType.of(2)); @@ -1516,21 +1553,30 @@ public void Multiplication() throws Exception { optimize("3*(FIELD_INTEGER*1)*1*(2*5)", "30*FIELD_INTEGER"); optimize("4*FIELD_INTEGER*0.5", "2*FIELD_INTEGER"); - // Simplify arithmetic 0*A → 0 - optimize("FIELD_INTEGER*0", "0"); - optimize("0*FIELD_INTEGER", "0"); + // Normalize + optimize("0*FIELD_INTEGER"); + optimize("10*FIELD_INTEGER"); + optimize("FIELD_INTEGER*0", "0*FIELD_INTEGER"); - // Simplify arithmetic 1*A → A + // Simplify arithmetic with NULL + optimizeNull("NULL::INTEGER*FIELD_INTEGER"); + optimizeNull("FIELD_INTEGER*NULL::INTEGER"); + + // Simplify arithmetic 0 * A → 0 + optimize("PI()*0", "0"); + optimize("0*PI()", "0"); + + // Simplify arithmetic 1 * A → A optimize("FIELD_INTEGER*1", "FIELD_INTEGER"); optimize("1.0*FIELD_INTEGER", "FIELD_INTEGER"); - // Simplify arithmetic (-A)*(-B) → A*B + // Simplify arithmetic (-A) * (-B) → A*B optimize("-FIELD_INTEGER*(-FIELD_NUMBER)", "FIELD_INTEGER*FIELD_NUMBER"); - // Simplify arithmetic A*A → SQUARE(A) + // Simplify arithmetic A * A → SQUARE(A) optimize("FIELD_INTEGER*FIELD_INTEGER", "SQUARE(FIELD_INTEGER)"); - // Simplify arithmetic 1/A*B → B/A + // Simplify arithmetic 1 / A * B → B / A optimize("1/FIELD_INTEGER*4", "4/FIELD_INTEGER"); } @@ -1556,14 +1602,19 @@ public void Div() throws Exception { evalEquals("'8'/2", 4L).returnType(NumberType.of(38, 37)); evalEquals("5/'2'", 2.5).returnType(NumberType.of(38, 37)); - optimize("0/0", "0/0"); + // Normalize + optimize("0/0"); optimize("FIELD_INTEGER/4"); - // Simplify arithmetic A/1 → A + // Simplify arithmetic with NULL + optimizeNull("NULL::INTEGER/FIELD_INTEGER"); + optimizeNull("FIELD_INTEGER/NULL::INTEGER"); + + // Simplify arithmetic A / 1 → A optimize("FIELD_INTEGER/1", "FIELD_INTEGER"); optimize("FIELD_INTEGER/1.0", "FIELD_INTEGER"); - // Simplify arithmetic (-A)/(-B) → A/B + // Simplify arithmetic (-A) / (-B) → A / B optimize("-FIELD_NUMBER/-FIELD_INTEGER", "FIELD_NUMBER/FIELD_INTEGER"); } @@ -1581,7 +1632,21 @@ public void Div0() throws Exception { evalFails("Div0(40)"); evalFails("Div0(40,1,2)"); + // Normalize + optimize("DIV0(FIELD_INTEGER,4)"); + + // Simplify arithmetic DIV0(A,0) → 0 when A is not nullable + optimize("DIV0(0,0)", "0"); + optimize("DIV0(PI(),0)", "0"); + + // Simplify arithmetic with NULL + optimizeNull("DIV0(NULL::INTEGER,FIELD_INTEGER)"); + optimizeNull("DIV0(FIELD_INTEGER,NULL::INTEGER)"); + + // Simplify arithmetic DIV0(A,1) → A optimize("DIV0(FIELD_INTEGER,1)", "FIELD_INTEGER"); + + // Simplify arithmetic DIV0(-A,-B) → DIV0(A,B) optimize("DIV0(-FIELD_NUMBER,-FIELD_INTEGER)", "DIV0(FIELD_NUMBER,FIELD_INTEGER)"); } @@ -1616,7 +1681,19 @@ public void BitAnd() throws Exception { // Alias function evalEquals("BIT_AND(3,2)", 2L).returnType(Types.INTEGER); - optimize("FIELD_INTEGER&4"); + // Nothing to simplify + optimize("4&FIELD_INTEGER"); + optimize("FIELD_INTEGER&4", "4&FIELD_INTEGER"); + + // Simplify NULL & A → NULL + optimizeNull("NULL&FIELD_INTEGER"); + optimizeNull("FIELD_INTEGER&NULL"); + + // Simplify 0 & A -> 0 (if A not nullable) + optimize("FIELD_INTEGER&0", "0&FIELD_INTEGER"); + optimize("0&FIELD_INTEGER"); + optimize("123&0", "0"); + optimize("0&123", "0"); } @Test @@ -1631,8 +1708,15 @@ public void BitOr() throws Exception { // Alias function evalEquals("BIT_OR(100,2)", 102L).returnType(Types.INTEGER); + // Nothing to simplify optimize("FIELD_INTEGER|4", "4|FIELD_INTEGER"); optimize("1|FIELD_INTEGER|4", "5|FIELD_INTEGER"); + + // Simplify NULL | A → NULL + optimizeNull("NULL|FIELD_INTEGER"); + optimizeNull("FIELD_INTEGER|NULL"); + + // Simplify 0 | A → A (even if A is null) optimize("FIELD_INTEGER|0", "FIELD_INTEGER"); optimize("0|FIELD_INTEGER", "FIELD_INTEGER"); } @@ -1648,7 +1732,16 @@ public void BitXor() throws Exception { evalFails("100^"); evalFails("100 ^ "); + // Nothing to simplify optimize("FIELD_INTEGER^4"); + + // Simplify NULL ^ A → NULL + optimizeNull("NULL^FIELD_INTEGER"); + optimizeNull("FIELD_INTEGER^NULL"); + + // Simplify 0 ^ A → A (even if A is null) + optimize("0^FIELD_INTEGER", "FIELD_INTEGER"); + optimize("FIELD_INTEGER^0", "FIELD_INTEGER"); } @Test