Browse Source

[ESQL] move optimizer rules tests (#110654)

Just moving some code around. This pulls the Optimizer Rule unit tests out of the OptimizerRulesTests file and into individual class test files. As usual, this will reduce the risk of git conflicts when editing tests. It'll also help add visibility to which rules have unit tests and which still need them written.
Mark Tozzi 1 year ago
parent
commit
6d5b26871a

+ 0 - 857
x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/OptimizerRulesTests.java

@@ -1,857 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-package org.elasticsearch.xpack.esql.optimizer;
-
-import org.elasticsearch.test.ESTestCase;
-import org.elasticsearch.xpack.esql.core.expression.Alias;
-import org.elasticsearch.xpack.esql.core.expression.Expression;
-import org.elasticsearch.xpack.esql.core.expression.Expressions;
-import org.elasticsearch.xpack.esql.core.expression.FieldAttribute;
-import org.elasticsearch.xpack.esql.core.expression.Literal;
-import org.elasticsearch.xpack.esql.core.expression.Nullability;
-import org.elasticsearch.xpack.esql.core.expression.predicate.BinaryOperator;
-import org.elasticsearch.xpack.esql.core.expression.predicate.Predicates;
-import org.elasticsearch.xpack.esql.core.expression.predicate.Range;
-import org.elasticsearch.xpack.esql.core.expression.predicate.logical.And;
-import org.elasticsearch.xpack.esql.core.expression.predicate.logical.Not;
-import org.elasticsearch.xpack.esql.core.expression.predicate.logical.Or;
-import org.elasticsearch.xpack.esql.core.expression.predicate.nulls.IsNotNull;
-import org.elasticsearch.xpack.esql.core.expression.predicate.nulls.IsNull;
-import org.elasticsearch.xpack.esql.core.expression.predicate.operator.comparison.BinaryComparison;
-import org.elasticsearch.xpack.esql.core.expression.predicate.regex.Like;
-import org.elasticsearch.xpack.esql.core.expression.predicate.regex.LikePattern;
-import org.elasticsearch.xpack.esql.core.expression.predicate.regex.RLike;
-import org.elasticsearch.xpack.esql.core.expression.predicate.regex.RLikePattern;
-import org.elasticsearch.xpack.esql.core.expression.predicate.regex.WildcardLike;
-import org.elasticsearch.xpack.esql.core.expression.predicate.regex.WildcardPattern;
-import org.elasticsearch.xpack.esql.core.tree.Source;
-import org.elasticsearch.xpack.esql.core.type.DataType;
-import org.elasticsearch.xpack.esql.core.util.StringUtils;
-import org.elasticsearch.xpack.esql.expression.predicate.operator.arithmetic.Add;
-import org.elasticsearch.xpack.esql.expression.predicate.operator.arithmetic.Div;
-import org.elasticsearch.xpack.esql.expression.predicate.operator.arithmetic.Mod;
-import org.elasticsearch.xpack.esql.expression.predicate.operator.arithmetic.Mul;
-import org.elasticsearch.xpack.esql.expression.predicate.operator.arithmetic.Sub;
-import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.Equals;
-import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.GreaterThan;
-import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.GreaterThanOrEqual;
-import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.In;
-import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.LessThan;
-import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.LessThanOrEqual;
-import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.NotEquals;
-import org.elasticsearch.xpack.esql.optimizer.rules.BooleanFunctionEqualsElimination;
-import org.elasticsearch.xpack.esql.optimizer.rules.CombineDisjunctionsToIn;
-import org.elasticsearch.xpack.esql.optimizer.rules.ConstantFolding;
-import org.elasticsearch.xpack.esql.optimizer.rules.LiteralsOnTheRight;
-import org.elasticsearch.xpack.esql.optimizer.rules.OptimizerRules;
-import org.elasticsearch.xpack.esql.optimizer.rules.OptimizerRules.FoldNull;
-import org.elasticsearch.xpack.esql.optimizer.rules.OptimizerRules.PropagateNullable;
-import org.elasticsearch.xpack.esql.optimizer.rules.PropagateEquals;
-import org.elasticsearch.xpack.esql.optimizer.rules.ReplaceRegexMatch;
-import org.elasticsearch.xpack.esql.plan.logical.Filter;
-import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan;
-
-import java.util.List;
-
-import static java.util.Arrays.asList;
-import static org.elasticsearch.xpack.esql.EsqlTestUtils.FIVE;
-import static org.elasticsearch.xpack.esql.EsqlTestUtils.FOUR;
-import static org.elasticsearch.xpack.esql.EsqlTestUtils.ONE;
-import static org.elasticsearch.xpack.esql.EsqlTestUtils.THREE;
-import static org.elasticsearch.xpack.esql.EsqlTestUtils.TWO;
-import static org.elasticsearch.xpack.esql.EsqlTestUtils.equalsOf;
-import static org.elasticsearch.xpack.esql.EsqlTestUtils.getFieldAttribute;
-import static org.elasticsearch.xpack.esql.EsqlTestUtils.greaterThanOf;
-import static org.elasticsearch.xpack.esql.EsqlTestUtils.greaterThanOrEqualOf;
-import static org.elasticsearch.xpack.esql.EsqlTestUtils.lessThanOf;
-import static org.elasticsearch.xpack.esql.EsqlTestUtils.lessThanOrEqualOf;
-import static org.elasticsearch.xpack.esql.EsqlTestUtils.notEqualsOf;
-import static org.elasticsearch.xpack.esql.EsqlTestUtils.of;
-import static org.elasticsearch.xpack.esql.EsqlTestUtils.rangeOf;
-import static org.elasticsearch.xpack.esql.EsqlTestUtils.relation;
-import static org.elasticsearch.xpack.esql.core.expression.Literal.FALSE;
-import static org.elasticsearch.xpack.esql.core.expression.Literal.NULL;
-import static org.elasticsearch.xpack.esql.core.expression.Literal.TRUE;
-import static org.elasticsearch.xpack.esql.core.tree.Source.EMPTY;
-import static org.elasticsearch.xpack.esql.core.type.DataType.BOOLEAN;
-import static org.elasticsearch.xpack.esql.core.type.DataType.INTEGER;
-import static org.hamcrest.Matchers.contains;
-
-public class OptimizerRulesTests extends ESTestCase {
-    private static final Expression DUMMY_EXPRESSION =
-        new org.elasticsearch.xpack.esql.core.optimizer.OptimizerRulesTests.DummyBooleanExpression(EMPTY, 0);
-
-    //
-    // Constant folding
-    //
-
-    public void testConstantFolding() {
-        Expression exp = new Add(EMPTY, TWO, THREE);
-
-        assertTrue(exp.foldable());
-        Expression result = new ConstantFolding().rule(exp);
-        assertTrue(result instanceof Literal);
-        assertEquals(5, ((Literal) result).value());
-
-        // check now with an alias
-        result = new ConstantFolding().rule(new Alias(EMPTY, "a", exp));
-        assertEquals("a", Expressions.name(result));
-        assertEquals(Alias.class, result.getClass());
-    }
-
-    public void testConstantFoldingBinaryComparison() {
-        assertEquals(FALSE, new ConstantFolding().rule(greaterThanOf(TWO, THREE)).canonical());
-        assertEquals(FALSE, new ConstantFolding().rule(greaterThanOrEqualOf(TWO, THREE)).canonical());
-        assertEquals(FALSE, new ConstantFolding().rule(equalsOf(TWO, THREE)).canonical());
-        assertEquals(TRUE, new ConstantFolding().rule(notEqualsOf(TWO, THREE)).canonical());
-        assertEquals(TRUE, new ConstantFolding().rule(lessThanOrEqualOf(TWO, THREE)).canonical());
-        assertEquals(TRUE, new ConstantFolding().rule(lessThanOf(TWO, THREE)).canonical());
-    }
-
-    public void testConstantFoldingBinaryLogic() {
-        assertEquals(FALSE, new ConstantFolding().rule(new And(EMPTY, greaterThanOf(TWO, THREE), TRUE)).canonical());
-        assertEquals(TRUE, new ConstantFolding().rule(new Or(EMPTY, greaterThanOrEqualOf(TWO, THREE), TRUE)).canonical());
-    }
-
-    public void testConstantFoldingBinaryLogic_WithNullHandling() {
-        assertEquals(Nullability.TRUE, new ConstantFolding().rule(new And(EMPTY, NULL, TRUE)).canonical().nullable());
-        assertEquals(Nullability.TRUE, new ConstantFolding().rule(new And(EMPTY, TRUE, NULL)).canonical().nullable());
-        assertEquals(FALSE, new ConstantFolding().rule(new And(EMPTY, NULL, FALSE)).canonical());
-        assertEquals(FALSE, new ConstantFolding().rule(new And(EMPTY, FALSE, NULL)).canonical());
-        assertEquals(Nullability.TRUE, new ConstantFolding().rule(new And(EMPTY, NULL, NULL)).canonical().nullable());
-
-        assertEquals(TRUE, new ConstantFolding().rule(new Or(EMPTY, NULL, TRUE)).canonical());
-        assertEquals(TRUE, new ConstantFolding().rule(new Or(EMPTY, TRUE, NULL)).canonical());
-        assertEquals(Nullability.TRUE, new ConstantFolding().rule(new Or(EMPTY, NULL, FALSE)).canonical().nullable());
-        assertEquals(Nullability.TRUE, new ConstantFolding().rule(new Or(EMPTY, FALSE, NULL)).canonical().nullable());
-        assertEquals(Nullability.TRUE, new ConstantFolding().rule(new Or(EMPTY, NULL, NULL)).canonical().nullable());
-    }
-
-    public void testConstantFoldingRange() {
-        assertEquals(true, new ConstantFolding().rule(rangeOf(FIVE, FIVE, true, new Literal(EMPTY, 10, DataType.INTEGER), false)).fold());
-        assertEquals(false, new ConstantFolding().rule(rangeOf(FIVE, FIVE, false, new Literal(EMPTY, 10, DataType.INTEGER), false)).fold());
-    }
-
-    public void testConstantNot() {
-        assertEquals(FALSE, new ConstantFolding().rule(new Not(EMPTY, TRUE)));
-        assertEquals(TRUE, new ConstantFolding().rule(new Not(EMPTY, FALSE)));
-    }
-
-    public void testConstantFoldingLikes() {
-        assertEquals(TRUE, new ConstantFolding().rule(new Like(EMPTY, of("test_emp"), new LikePattern("test%", (char) 0))).canonical());
-        assertEquals(TRUE, new ConstantFolding().rule(new WildcardLike(EMPTY, of("test_emp"), new WildcardPattern("test*"))).canonical());
-        assertEquals(TRUE, new ConstantFolding().rule(new RLike(EMPTY, of("test_emp"), new RLikePattern("test.emp"))).canonical());
-    }
-
-    public void testArithmeticFolding() {
-        assertEquals(10, foldOperator(new Add(EMPTY, new Literal(EMPTY, 7, DataType.INTEGER), THREE)));
-        assertEquals(4, foldOperator(new Sub(EMPTY, new Literal(EMPTY, 7, DataType.INTEGER), THREE)));
-        assertEquals(21, foldOperator(new Mul(EMPTY, new Literal(EMPTY, 7, DataType.INTEGER), THREE)));
-        assertEquals(2, foldOperator(new Div(EMPTY, new Literal(EMPTY, 7, DataType.INTEGER), THREE)));
-        assertEquals(1, foldOperator(new Mod(EMPTY, new Literal(EMPTY, 7, DataType.INTEGER), THREE)));
-    }
-
-    private static Object foldOperator(BinaryOperator<?, ?, ?, ?> b) {
-        return ((Literal) new ConstantFolding().rule(b)).value();
-    }
-
-    //
-    // CombineDisjunction in Equals
-    //
-    public void testTwoEqualsWithOr() {
-        FieldAttribute fa = getFieldAttribute();
-
-        Or or = new Or(EMPTY, equalsOf(fa, ONE), equalsOf(fa, TWO));
-        Expression e = new CombineDisjunctionsToIn().rule(or);
-        assertEquals(In.class, e.getClass());
-        In in = (In) e;
-        assertEquals(fa, in.value());
-        assertThat(in.list(), contains(ONE, TWO));
-    }
-
-    public void testTwoEqualsWithSameValue() {
-        FieldAttribute fa = getFieldAttribute();
-
-        Or or = new Or(EMPTY, equalsOf(fa, ONE), equalsOf(fa, ONE));
-        Expression e = new CombineDisjunctionsToIn().rule(or);
-        assertEquals(Equals.class, e.getClass());
-        Equals eq = (Equals) e;
-        assertEquals(fa, eq.left());
-        assertEquals(ONE, eq.right());
-    }
-
-    public void testOneEqualsOneIn() {
-        FieldAttribute fa = getFieldAttribute();
-
-        Or or = new Or(EMPTY, equalsOf(fa, ONE), new In(EMPTY, fa, List.of(TWO)));
-        Expression e = new CombineDisjunctionsToIn().rule(or);
-        assertEquals(In.class, e.getClass());
-        In in = (In) e;
-        assertEquals(fa, in.value());
-        assertThat(in.list(), contains(ONE, TWO));
-    }
-
-    public void testOneEqualsOneInWithSameValue() {
-        FieldAttribute fa = getFieldAttribute();
-
-        Or or = new Or(EMPTY, equalsOf(fa, ONE), new In(EMPTY, fa, asList(ONE, TWO)));
-        Expression e = new CombineDisjunctionsToIn().rule(or);
-        assertEquals(In.class, e.getClass());
-        In in = (In) e;
-        assertEquals(fa, in.value());
-        assertThat(in.list(), contains(ONE, TWO));
-    }
-
-    public void testSingleValueInToEquals() {
-        FieldAttribute fa = getFieldAttribute();
-
-        Equals equals = equalsOf(fa, ONE);
-        Or or = new Or(EMPTY, equals, new In(EMPTY, fa, List.of(ONE)));
-        Expression e = new CombineDisjunctionsToIn().rule(or);
-        assertEquals(equals, e);
-    }
-
-    public void testEqualsBehindAnd() {
-        FieldAttribute fa = getFieldAttribute();
-
-        And and = new And(EMPTY, equalsOf(fa, ONE), equalsOf(fa, TWO));
-        Filter dummy = new Filter(EMPTY, relation(), and);
-        LogicalPlan transformed = new CombineDisjunctionsToIn().apply(dummy);
-        assertSame(dummy, transformed);
-        assertEquals(and, ((Filter) transformed).condition());
-    }
-
-    public void testTwoEqualsDifferentFields() {
-        FieldAttribute fieldOne = getFieldAttribute("ONE");
-        FieldAttribute fieldTwo = getFieldAttribute("TWO");
-
-        Or or = new Or(EMPTY, equalsOf(fieldOne, ONE), equalsOf(fieldTwo, TWO));
-        Expression e = new CombineDisjunctionsToIn().rule(or);
-        assertEquals(or, e);
-    }
-
-    public void testMultipleIn() {
-        FieldAttribute fa = getFieldAttribute();
-
-        Or firstOr = new Or(EMPTY, new In(EMPTY, fa, List.of(ONE)), new In(EMPTY, fa, List.of(TWO)));
-        Or secondOr = new Or(EMPTY, firstOr, new In(EMPTY, fa, List.of(THREE)));
-        Expression e = new CombineDisjunctionsToIn().rule(secondOr);
-        assertEquals(In.class, e.getClass());
-        In in = (In) e;
-        assertEquals(fa, in.value());
-        assertThat(in.list(), contains(ONE, TWO, THREE));
-    }
-
-    public void testOrWithNonCombinableExpressions() {
-        FieldAttribute fa = getFieldAttribute();
-
-        Or firstOr = new Or(EMPTY, new In(EMPTY, fa, List.of(ONE)), lessThanOf(fa, TWO));
-        Or secondOr = new Or(EMPTY, firstOr, new In(EMPTY, fa, List.of(THREE)));
-        Expression e = new CombineDisjunctionsToIn().rule(secondOr);
-        assertEquals(Or.class, e.getClass());
-        Or or = (Or) e;
-        assertEquals(or.left(), firstOr.right());
-        assertEquals(In.class, or.right().getClass());
-        In in = (In) or.right();
-        assertEquals(fa, in.value());
-        assertThat(in.list(), contains(ONE, THREE));
-    }
-
-    // Test BooleanFunctionEqualsElimination
-    public void testBoolEqualsSimplificationOnExpressions() {
-        BooleanFunctionEqualsElimination s = new BooleanFunctionEqualsElimination();
-        Expression exp = new GreaterThan(EMPTY, getFieldAttribute(), new Literal(EMPTY, 0, DataType.INTEGER), null);
-
-        assertEquals(exp, s.rule(new Equals(EMPTY, exp, TRUE)));
-        // TODO: Replace use of QL Not with ESQL Not
-        assertEquals(new Not(EMPTY, exp), s.rule(new Equals(EMPTY, exp, FALSE)));
-    }
-
-    public void testBoolEqualsSimplificationOnFields() {
-        BooleanFunctionEqualsElimination s = new BooleanFunctionEqualsElimination();
-
-        FieldAttribute field = getFieldAttribute();
-
-        List<? extends BinaryComparison> comparisons = asList(
-            new Equals(EMPTY, field, TRUE),
-            new Equals(EMPTY, field, FALSE),
-            notEqualsOf(field, TRUE),
-            notEqualsOf(field, FALSE),
-            new Equals(EMPTY, NULL, TRUE),
-            new Equals(EMPTY, NULL, FALSE),
-            notEqualsOf(NULL, TRUE),
-            notEqualsOf(NULL, FALSE)
-        );
-
-        for (BinaryComparison comparison : comparisons) {
-            assertEquals(comparison, s.rule(comparison));
-        }
-    }
-
-    // Test Propagate Equals
-
-    // a == 1 AND a == 2 -> FALSE
-    public void testDualEqualsConjunction() {
-        FieldAttribute fa = getFieldAttribute();
-        Equals eq1 = equalsOf(fa, ONE);
-        Equals eq2 = equalsOf(fa, TWO);
-
-        PropagateEquals rule = new PropagateEquals();
-        Expression exp = rule.rule(new And(EMPTY, eq1, eq2));
-        assertEquals(FALSE, exp);
-    }
-
-    // 1 < a < 10 AND a == 10 -> FALSE
-    public void testEliminateRangeByEqualsOutsideInterval() {
-        FieldAttribute fa = getFieldAttribute();
-        Equals eq1 = equalsOf(fa, new Literal(EMPTY, 10, DataType.INTEGER));
-        Range r = rangeOf(fa, ONE, false, new Literal(EMPTY, 10, DataType.INTEGER), false);
-
-        PropagateEquals rule = new PropagateEquals();
-        Expression exp = rule.rule(new And(EMPTY, eq1, r));
-        assertEquals(FALSE, exp);
-    }
-
-    // a != 3 AND a = 3 -> FALSE
-    public void testPropagateEquals_VarNeq3AndVarEq3() {
-        FieldAttribute fa = getFieldAttribute();
-        NotEquals neq = notEqualsOf(fa, THREE);
-        Equals eq = equalsOf(fa, THREE);
-
-        PropagateEquals rule = new PropagateEquals();
-        Expression exp = rule.rule(new And(EMPTY, neq, eq));
-        assertEquals(FALSE, exp);
-    }
-
-    // a != 4 AND a = 3 -> a = 3
-    public void testPropagateEquals_VarNeq4AndVarEq3() {
-        FieldAttribute fa = getFieldAttribute();
-        NotEquals neq = notEqualsOf(fa, FOUR);
-        Equals eq = equalsOf(fa, THREE);
-
-        PropagateEquals rule = new PropagateEquals();
-        Expression exp = rule.rule(new And(EMPTY, neq, eq));
-        assertEquals(Equals.class, exp.getClass());
-        assertEquals(eq, exp);
-    }
-
-    // a = 2 AND a < 2 -> FALSE
-    public void testPropagateEquals_VarEq2AndVarLt2() {
-        FieldAttribute fa = getFieldAttribute();
-        Equals eq = equalsOf(fa, TWO);
-        LessThan lt = lessThanOf(fa, TWO);
-
-        PropagateEquals rule = new PropagateEquals();
-        Expression exp = rule.rule(new And(EMPTY, eq, lt));
-        assertEquals(FALSE, exp);
-    }
-
-    // a = 2 AND a <= 2 -> a = 2
-    public void testPropagateEquals_VarEq2AndVarLte2() {
-        FieldAttribute fa = getFieldAttribute();
-        Equals eq = equalsOf(fa, TWO);
-        LessThanOrEqual lt = lessThanOrEqualOf(fa, TWO);
-
-        PropagateEquals rule = new PropagateEquals();
-        Expression exp = rule.rule(new And(EMPTY, eq, lt));
-        assertEquals(eq, exp);
-    }
-
-    // a = 2 AND a <= 1 -> FALSE
-    public void testPropagateEquals_VarEq2AndVarLte1() {
-        FieldAttribute fa = getFieldAttribute();
-        Equals eq = equalsOf(fa, TWO);
-        LessThanOrEqual lt = lessThanOrEqualOf(fa, ONE);
-
-        PropagateEquals rule = new PropagateEquals();
-        Expression exp = rule.rule(new And(EMPTY, eq, lt));
-        assertEquals(FALSE, exp);
-    }
-
-    // a = 2 AND a > 2 -> FALSE
-    public void testPropagateEquals_VarEq2AndVarGt2() {
-        FieldAttribute fa = getFieldAttribute();
-        Equals eq = equalsOf(fa, TWO);
-        GreaterThan gt = greaterThanOf(fa, TWO);
-
-        PropagateEquals rule = new PropagateEquals();
-        Expression exp = rule.rule(new And(EMPTY, eq, gt));
-        assertEquals(FALSE, exp);
-    }
-
-    // a = 2 AND a >= 2 -> a = 2
-    public void testPropagateEquals_VarEq2AndVarGte2() {
-        FieldAttribute fa = getFieldAttribute();
-        Equals eq = equalsOf(fa, TWO);
-        GreaterThanOrEqual gte = greaterThanOrEqualOf(fa, TWO);
-
-        PropagateEquals rule = new PropagateEquals();
-        Expression exp = rule.rule(new And(EMPTY, eq, gte));
-        assertEquals(eq, exp);
-    }
-
-    // a = 2 AND a > 3 -> FALSE
-    public void testPropagateEquals_VarEq2AndVarLt3() {
-        FieldAttribute fa = getFieldAttribute();
-        Equals eq = equalsOf(fa, TWO);
-        GreaterThan gt = greaterThanOf(fa, THREE);
-
-        PropagateEquals rule = new PropagateEquals();
-        Expression exp = rule.rule(new And(EMPTY, eq, gt));
-        assertEquals(FALSE, exp);
-    }
-
-    // a = 2 AND a < 3 AND a > 1 AND a != 4 -> a = 2
-    public void testPropagateEquals_VarEq2AndVarLt3AndVarGt1AndVarNeq4() {
-        FieldAttribute fa = getFieldAttribute();
-        Equals eq = equalsOf(fa, TWO);
-        LessThan lt = lessThanOf(fa, THREE);
-        GreaterThan gt = greaterThanOf(fa, ONE);
-        NotEquals neq = notEqualsOf(fa, FOUR);
-
-        PropagateEquals rule = new PropagateEquals();
-        Expression and = Predicates.combineAnd(asList(eq, lt, gt, neq));
-        Expression exp = rule.rule((And) and);
-        assertEquals(eq, exp);
-    }
-
-    // a = 2 AND 1 < a < 3 AND a > 0 AND a != 4 -> a = 2
-    public void testPropagateEquals_VarEq2AndVarRangeGt1Lt3AndVarGt0AndVarNeq4() {
-        FieldAttribute fa = getFieldAttribute();
-        Equals eq = equalsOf(fa, TWO);
-        Range range = rangeOf(fa, ONE, false, THREE, false);
-        GreaterThan gt = greaterThanOf(fa, new Literal(EMPTY, 0, DataType.INTEGER));
-        NotEquals neq = notEqualsOf(fa, FOUR);
-
-        PropagateEquals rule = new PropagateEquals();
-        Expression and = Predicates.combineAnd(asList(eq, range, gt, neq));
-        Expression exp = rule.rule((And) and);
-        assertEquals(eq, exp);
-    }
-
-    // a = 2 OR a > 1 -> a > 1
-    public void testPropagateEquals_VarEq2OrVarGt1() {
-        FieldAttribute fa = getFieldAttribute();
-        Equals eq = equalsOf(fa, TWO);
-        GreaterThan gt = greaterThanOf(fa, ONE);
-
-        PropagateEquals rule = new PropagateEquals();
-        Expression exp = rule.rule(new Or(EMPTY, eq, gt));
-        assertEquals(gt, exp);
-    }
-
-    // a = 2 OR a > 2 -> a >= 2
-    public void testPropagateEquals_VarEq2OrVarGte2() {
-        FieldAttribute fa = getFieldAttribute();
-        Equals eq = equalsOf(fa, TWO);
-        GreaterThan gt = greaterThanOf(fa, TWO);
-
-        PropagateEquals rule = new PropagateEquals();
-        Expression exp = rule.rule(new Or(EMPTY, eq, gt));
-        assertEquals(GreaterThanOrEqual.class, exp.getClass());
-        GreaterThanOrEqual gte = (GreaterThanOrEqual) exp;
-        assertEquals(TWO, gte.right());
-    }
-
-    // a = 2 OR a < 3 -> a < 3
-    public void testPropagateEquals_VarEq2OrVarLt3() {
-        FieldAttribute fa = getFieldAttribute();
-        Equals eq = equalsOf(fa, TWO);
-        LessThan lt = lessThanOf(fa, THREE);
-
-        PropagateEquals rule = new PropagateEquals();
-        Expression exp = rule.rule(new Or(EMPTY, eq, lt));
-        assertEquals(lt, exp);
-    }
-
-    // a = 3 OR a < 3 -> a <= 3
-    public void testPropagateEquals_VarEq3OrVarLt3() {
-        FieldAttribute fa = getFieldAttribute();
-        Equals eq = equalsOf(fa, THREE);
-        LessThan lt = lessThanOf(fa, THREE);
-
-        PropagateEquals rule = new PropagateEquals();
-        Expression exp = rule.rule(new Or(EMPTY, eq, lt));
-        assertEquals(LessThanOrEqual.class, exp.getClass());
-        LessThanOrEqual lte = (LessThanOrEqual) exp;
-        assertEquals(THREE, lte.right());
-    }
-
-    // a = 2 OR 1 < a < 3 -> 1 < a < 3
-    public void testPropagateEquals_VarEq2OrVarRangeGt1Lt3() {
-        FieldAttribute fa = getFieldAttribute();
-        Equals eq = equalsOf(fa, TWO);
-        Range range = rangeOf(fa, ONE, false, THREE, false);
-
-        PropagateEquals rule = new PropagateEquals();
-        Expression exp = rule.rule(new Or(EMPTY, eq, range));
-        assertEquals(range, exp);
-    }
-
-    // a = 2 OR 2 < a < 3 -> 2 <= a < 3
-    public void testPropagateEquals_VarEq2OrVarRangeGt2Lt3() {
-        FieldAttribute fa = getFieldAttribute();
-        Equals eq = equalsOf(fa, TWO);
-        Range range = rangeOf(fa, TWO, false, THREE, false);
-
-        PropagateEquals rule = new PropagateEquals();
-        Expression exp = rule.rule(new Or(EMPTY, eq, range));
-        assertEquals(Range.class, exp.getClass());
-        Range r = (Range) exp;
-        assertEquals(TWO, r.lower());
-        assertTrue(r.includeLower());
-        assertEquals(THREE, r.upper());
-        assertFalse(r.includeUpper());
-    }
-
-    // a = 3 OR 2 < a < 3 -> 2 < a <= 3
-    public void testPropagateEquals_VarEq3OrVarRangeGt2Lt3() {
-        FieldAttribute fa = getFieldAttribute();
-        Equals eq = equalsOf(fa, THREE);
-        Range range = rangeOf(fa, TWO, false, THREE, false);
-
-        PropagateEquals rule = new PropagateEquals();
-        Expression exp = rule.rule(new Or(EMPTY, eq, range));
-        assertEquals(Range.class, exp.getClass());
-        Range r = (Range) exp;
-        assertEquals(TWO, r.lower());
-        assertFalse(r.includeLower());
-        assertEquals(THREE, r.upper());
-        assertTrue(r.includeUpper());
-    }
-
-    // a = 2 OR a != 2 -> TRUE
-    public void testPropagateEquals_VarEq2OrVarNeq2() {
-        FieldAttribute fa = getFieldAttribute();
-        Equals eq = equalsOf(fa, TWO);
-        NotEquals neq = notEqualsOf(fa, TWO);
-
-        PropagateEquals rule = new PropagateEquals();
-        Expression exp = rule.rule(new Or(EMPTY, eq, neq));
-        assertEquals(TRUE, exp);
-    }
-
-    // a = 2 OR a != 5 -> a != 5
-    public void testPropagateEquals_VarEq2OrVarNeq5() {
-        FieldAttribute fa = getFieldAttribute();
-        Equals eq = equalsOf(fa, TWO);
-        NotEquals neq = notEqualsOf(fa, FIVE);
-
-        PropagateEquals rule = new PropagateEquals();
-        Expression exp = rule.rule(new Or(EMPTY, eq, neq));
-        assertEquals(NotEquals.class, exp.getClass());
-        NotEquals ne = (NotEquals) exp;
-        assertEquals(FIVE, ne.right());
-    }
-
-    // a = 2 OR 3 < a < 4 OR a > 2 OR a!= 2 -> TRUE
-    public void testPropagateEquals_VarEq2OrVarRangeGt3Lt4OrVarGt2OrVarNe2() {
-        FieldAttribute fa = getFieldAttribute();
-        Equals eq = equalsOf(fa, TWO);
-        Range range = rangeOf(fa, THREE, false, FOUR, false);
-        GreaterThan gt = greaterThanOf(fa, TWO);
-        NotEquals neq = notEqualsOf(fa, TWO);
-
-        PropagateEquals rule = new PropagateEquals();
-        Expression exp = rule.rule((Or) Predicates.combineOr(asList(eq, range, neq, gt)));
-        assertEquals(TRUE, exp);
-    }
-
-    // a == 1 AND a == 2 -> nop for date/time fields
-    public void testPropagateEquals_ignoreDateTimeFields() {
-        FieldAttribute fa = getFieldAttribute("a", DataType.DATETIME);
-        Equals eq1 = equalsOf(fa, ONE);
-        Equals eq2 = equalsOf(fa, TWO);
-        And and = new And(EMPTY, eq1, eq2);
-
-        PropagateEquals rule = new PropagateEquals();
-        Expression exp = rule.rule(and);
-        assertEquals(and, exp);
-    }
-
-    // 1 <= a < 10 AND a == 1 -> a == 1
-    public void testEliminateRangeByEqualsInInterval() {
-        FieldAttribute fa = getFieldAttribute();
-        Equals eq1 = equalsOf(fa, ONE);
-        Range r = rangeOf(fa, ONE, true, new Literal(EMPTY, 10, DataType.INTEGER), false);
-
-        PropagateEquals rule = new PropagateEquals();
-        Expression exp = rule.rule(new And(EMPTY, eq1, r));
-        assertEquals(eq1, exp);
-    }
-    //
-    // Null folding
-
-    public void testNullFoldingIsNull() {
-        FoldNull foldNull = new FoldNull();
-        assertEquals(true, foldNull.rule(new IsNull(EMPTY, NULL)).fold());
-        assertEquals(false, foldNull.rule(new IsNull(EMPTY, TRUE)).fold());
-    }
-
-    public void testGenericNullableExpression() {
-        FoldNull rule = new FoldNull();
-        // arithmetic
-        assertNullLiteral(rule.rule(new Add(EMPTY, getFieldAttribute(), NULL)));
-        // comparison
-        assertNullLiteral(rule.rule(greaterThanOf(getFieldAttribute(), NULL)));
-        // regex
-        assertNullLiteral(rule.rule(new RLike(EMPTY, NULL, new RLikePattern("123"))));
-    }
-
-    public void testNullFoldingDoesNotApplyOnLogicalExpressions() {
-        OptimizerRules.FoldNull rule = new OptimizerRules.FoldNull();
-
-        Or or = new Or(EMPTY, NULL, TRUE);
-        assertEquals(or, rule.rule(or));
-        or = new Or(EMPTY, NULL, NULL);
-        assertEquals(or, rule.rule(or));
-
-        And and = new And(EMPTY, NULL, TRUE);
-        assertEquals(and, rule.rule(and));
-        and = new And(EMPTY, NULL, NULL);
-        assertEquals(and, rule.rule(and));
-    }
-
-    //
-    // Propagate nullability (IS NULL / IS NOT NULL)
-    //
-
-    // a IS NULL AND a IS NOT NULL => false
-    public void testIsNullAndNotNull() {
-        FieldAttribute fa = getFieldAttribute();
-
-        And and = new And(EMPTY, new IsNull(EMPTY, fa), new IsNotNull(EMPTY, fa));
-        assertEquals(FALSE, new OptimizerRules.PropagateNullable().rule(and));
-    }
-
-    // a IS NULL AND b IS NOT NULL AND c IS NULL AND d IS NOT NULL AND e IS NULL AND a IS NOT NULL => false
-    public void testIsNullAndNotNullMultiField() {
-        FieldAttribute fa = getFieldAttribute();
-
-        And andOne = new And(EMPTY, new IsNull(EMPTY, fa), new IsNotNull(EMPTY, getFieldAttribute()));
-        And andTwo = new And(EMPTY, new IsNull(EMPTY, getFieldAttribute()), new IsNotNull(EMPTY, getFieldAttribute()));
-        And andThree = new And(EMPTY, new IsNull(EMPTY, getFieldAttribute()), new IsNotNull(EMPTY, fa));
-
-        And and = new And(EMPTY, andOne, new And(EMPTY, andThree, andTwo));
-
-        assertEquals(FALSE, new OptimizerRules.PropagateNullable().rule(and));
-    }
-
-    // a IS NULL AND a > 1 => a IS NULL AND false
-    public void testIsNullAndComparison() {
-        FieldAttribute fa = getFieldAttribute();
-        IsNull isNull = new IsNull(EMPTY, fa);
-
-        And and = new And(EMPTY, isNull, greaterThanOf(fa, ONE));
-        assertEquals(new And(EMPTY, isNull, nullOf(BOOLEAN)), new PropagateNullable().rule(and));
-    }
-
-    // a IS NULL AND b < 1 AND c < 1 AND a < 1 => a IS NULL AND b < 1 AND c < 1 => a IS NULL AND b < 1 AND c < 1
-    public void testIsNullAndMultipleComparison() {
-        FieldAttribute fa = getFieldAttribute();
-        IsNull isNull = new IsNull(EMPTY, fa);
-
-        And nestedAnd = new And(EMPTY, lessThanOf(getFieldAttribute("b"), ONE), lessThanOf(getFieldAttribute("c"), ONE));
-        And and = new And(EMPTY, isNull, nestedAnd);
-        And top = new And(EMPTY, and, lessThanOf(fa, ONE));
-
-        Expression optimized = new PropagateNullable().rule(top);
-        Expression expected = new And(EMPTY, and, nullOf(BOOLEAN));
-        assertEquals(Predicates.splitAnd(expected), Predicates.splitAnd(optimized));
-    }
-
-    // ((a+1)/2) > 1 AND a + 2 AND a IS NULL AND b < 3 => NULL AND NULL AND a IS NULL AND b < 3
-    public void testIsNullAndDeeplyNestedExpression() {
-        FieldAttribute fa = getFieldAttribute();
-        IsNull isNull = new IsNull(EMPTY, fa);
-
-        Expression nullified = new And(
-            EMPTY,
-            greaterThanOf(new Div(EMPTY, new Add(EMPTY, fa, ONE), TWO), ONE),
-            greaterThanOf(new Add(EMPTY, fa, TWO), ONE)
-        );
-        Expression kept = new And(EMPTY, isNull, lessThanOf(getFieldAttribute("b"), THREE));
-        And and = new And(EMPTY, nullified, kept);
-
-        Expression optimized = new PropagateNullable().rule(and);
-        Expression expected = new And(EMPTY, new And(EMPTY, nullOf(BOOLEAN), nullOf(BOOLEAN)), kept);
-
-        assertEquals(Predicates.splitAnd(expected), Predicates.splitAnd(optimized));
-    }
-
-    // a IS NULL OR a IS NOT NULL => no change
-    // a IS NULL OR a > 1 => no change
-    public void testIsNullInDisjunction() {
-        FieldAttribute fa = getFieldAttribute();
-
-        Or or = new Or(EMPTY, new IsNull(EMPTY, fa), new IsNotNull(EMPTY, fa));
-        Filter dummy = new Filter(EMPTY, relation(), or);
-        LogicalPlan transformed = new PropagateNullable().apply(dummy);
-        assertSame(dummy, transformed);
-        assertEquals(or, ((Filter) transformed).condition());
-
-        or = new Or(EMPTY, new IsNull(EMPTY, fa), greaterThanOf(fa, ONE));
-        dummy = new Filter(EMPTY, relation(), or);
-        transformed = new PropagateNullable().apply(dummy);
-        assertSame(dummy, transformed);
-        assertEquals(or, ((Filter) transformed).condition());
-    }
-
-    // a + 1 AND (a IS NULL OR a > 3) => no change
-    public void testIsNullDisjunction() {
-        FieldAttribute fa = getFieldAttribute();
-        IsNull isNull = new IsNull(EMPTY, fa);
-
-        Or or = new Or(EMPTY, isNull, greaterThanOf(fa, THREE));
-        And and = new And(EMPTY, new Add(EMPTY, fa, ONE), or);
-
-        assertEquals(and, new PropagateNullable().rule(and));
-    }
-
-    //
-    // Like / Regex
-    //
-    public void testMatchAllLikeToExist() {
-        for (String s : asList("%", "%%", "%%%")) {
-            LikePattern pattern = new LikePattern(s, (char) 0);
-            FieldAttribute fa = getFieldAttribute();
-            Like l = new Like(EMPTY, fa, pattern);
-            Expression e = new ReplaceRegexMatch().rule(l);
-            assertEquals(IsNotNull.class, e.getClass());
-            IsNotNull inn = (IsNotNull) e;
-            assertEquals(fa, inn.field());
-        }
-    }
-
-    public void testMatchAllWildcardLikeToExist() {
-        for (String s : asList("*", "**", "***")) {
-            WildcardPattern pattern = new WildcardPattern(s);
-            FieldAttribute fa = getFieldAttribute();
-            WildcardLike l = new WildcardLike(EMPTY, fa, pattern);
-            Expression e = new ReplaceRegexMatch().rule(l);
-            assertEquals(IsNotNull.class, e.getClass());
-            IsNotNull inn = (IsNotNull) e;
-            assertEquals(fa, inn.field());
-        }
-    }
-
-    public void testMatchAllRLikeToExist() {
-        RLikePattern pattern = new RLikePattern(".*");
-        FieldAttribute fa = getFieldAttribute();
-        RLike l = new RLike(EMPTY, fa, pattern);
-        Expression e = new ReplaceRegexMatch().rule(l);
-        assertEquals(IsNotNull.class, e.getClass());
-        IsNotNull inn = (IsNotNull) e;
-        assertEquals(fa, inn.field());
-    }
-
-    public void testExactMatchLike() {
-        for (String s : asList("ab", "ab0%", "ab0_c")) {
-            LikePattern pattern = new LikePattern(s, '0');
-            FieldAttribute fa = getFieldAttribute();
-            Like l = new Like(EMPTY, fa, pattern);
-            Expression e = new ReplaceRegexMatch().rule(l);
-            assertEquals(Equals.class, e.getClass());
-            Equals eq = (Equals) e;
-            assertEquals(fa, eq.left());
-            assertEquals(s.replace("0", StringUtils.EMPTY), eq.right().fold());
-        }
-    }
-
-    public void testExactMatchWildcardLike() {
-        String s = "ab";
-        WildcardPattern pattern = new WildcardPattern(s);
-        FieldAttribute fa = getFieldAttribute();
-        WildcardLike l = new WildcardLike(EMPTY, fa, pattern);
-        Expression e = new ReplaceRegexMatch().rule(l);
-        assertEquals(Equals.class, e.getClass());
-        Equals eq = (Equals) e;
-        assertEquals(fa, eq.left());
-        assertEquals(s, eq.right().fold());
-    }
-
-    public void testExactMatchRLike() {
-        RLikePattern pattern = new RLikePattern("abc");
-        FieldAttribute fa = getFieldAttribute();
-        RLike l = new RLike(EMPTY, fa, pattern);
-        Expression e = new ReplaceRegexMatch().rule(l);
-        assertEquals(Equals.class, e.getClass());
-        Equals eq = (Equals) e;
-        assertEquals(fa, eq.left());
-        assertEquals("abc", eq.right().fold());
-    }
-
-    private void assertNullLiteral(Expression expression) {
-        assertEquals(Literal.class, expression.getClass());
-        assertNull(expression.fold());
-    }
-
-    private IsNotNull isNotNull(Expression field) {
-        return new IsNotNull(EMPTY, field);
-    }
-
-    private IsNull isNull(Expression field) {
-        return new IsNull(EMPTY, field);
-    }
-
-    private Literal nullOf(DataType dataType) {
-        return new Literal(Source.EMPTY, null, dataType);
-    }
-    //
-    // Logical simplifications
-    //
-
-    public void testLiteralsOnTheRight() {
-        Alias a = new Alias(EMPTY, "a", new Literal(EMPTY, 10, INTEGER));
-        Expression result = new LiteralsOnTheRight().rule(equalsOf(FIVE, a));
-        assertTrue(result instanceof Equals);
-        Equals eq = (Equals) result;
-        assertEquals(a, eq.left());
-        assertEquals(FIVE, eq.right());
-
-        // Note: Null Equals test removed here
-    }
-
-    public void testBoolSimplifyOr() {
-        OptimizerRules.BooleanSimplification simplification = new OptimizerRules.BooleanSimplification();
-
-        assertEquals(TRUE, simplification.rule(new Or(EMPTY, TRUE, TRUE)));
-        assertEquals(TRUE, simplification.rule(new Or(EMPTY, TRUE, DUMMY_EXPRESSION)));
-        assertEquals(TRUE, simplification.rule(new Or(EMPTY, DUMMY_EXPRESSION, TRUE)));
-
-        assertEquals(FALSE, simplification.rule(new Or(EMPTY, FALSE, FALSE)));
-        assertEquals(DUMMY_EXPRESSION, simplification.rule(new Or(EMPTY, FALSE, DUMMY_EXPRESSION)));
-        assertEquals(DUMMY_EXPRESSION, simplification.rule(new Or(EMPTY, DUMMY_EXPRESSION, FALSE)));
-    }
-
-    public void testBoolSimplifyAnd() {
-        OptimizerRules.BooleanSimplification simplification = new OptimizerRules.BooleanSimplification();
-
-        assertEquals(TRUE, simplification.rule(new And(EMPTY, TRUE, TRUE)));
-        assertEquals(DUMMY_EXPRESSION, simplification.rule(new And(EMPTY, TRUE, DUMMY_EXPRESSION)));
-        assertEquals(DUMMY_EXPRESSION, simplification.rule(new And(EMPTY, DUMMY_EXPRESSION, TRUE)));
-
-        assertEquals(FALSE, simplification.rule(new And(EMPTY, FALSE, FALSE)));
-        assertEquals(FALSE, simplification.rule(new And(EMPTY, FALSE, DUMMY_EXPRESSION)));
-        assertEquals(FALSE, simplification.rule(new And(EMPTY, DUMMY_EXPRESSION, FALSE)));
-    }
-
-    public void testBoolCommonFactorExtraction() {
-        OptimizerRules.BooleanSimplification simplification = new OptimizerRules.BooleanSimplification();
-
-        Expression a1 = new org.elasticsearch.xpack.esql.core.optimizer.OptimizerRulesTests.DummyBooleanExpression(EMPTY, 1);
-        Expression a2 = new org.elasticsearch.xpack.esql.core.optimizer.OptimizerRulesTests.DummyBooleanExpression(EMPTY, 1);
-        Expression b = new org.elasticsearch.xpack.esql.core.optimizer.OptimizerRulesTests.DummyBooleanExpression(EMPTY, 2);
-        Expression c = new org.elasticsearch.xpack.esql.core.optimizer.OptimizerRulesTests.DummyBooleanExpression(EMPTY, 3);
-
-        Or actual = new Or(EMPTY, new And(EMPTY, a1, b), new And(EMPTY, a2, c));
-        And expected = new And(EMPTY, a1, new Or(EMPTY, b, c));
-
-        assertEquals(expected, simplification.rule(actual));
-    }
-}

+ 62 - 0
x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/BooleanFunctionEqualsEliminationTests.java

@@ -0,0 +1,62 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+package org.elasticsearch.xpack.esql.optimizer.rules;
+
+import org.elasticsearch.test.ESTestCase;
+import org.elasticsearch.xpack.esql.core.expression.Expression;
+import org.elasticsearch.xpack.esql.core.expression.FieldAttribute;
+import org.elasticsearch.xpack.esql.core.expression.Literal;
+import org.elasticsearch.xpack.esql.core.expression.predicate.logical.Not;
+import org.elasticsearch.xpack.esql.core.expression.predicate.operator.comparison.BinaryComparison;
+import org.elasticsearch.xpack.esql.core.type.DataType;
+import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.Equals;
+import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.GreaterThan;
+
+import java.util.List;
+
+import static java.util.Arrays.asList;
+import static org.elasticsearch.xpack.esql.EsqlTestUtils.getFieldAttribute;
+import static org.elasticsearch.xpack.esql.EsqlTestUtils.notEqualsOf;
+import static org.elasticsearch.xpack.esql.core.expression.Literal.FALSE;
+import static org.elasticsearch.xpack.esql.core.expression.Literal.NULL;
+import static org.elasticsearch.xpack.esql.core.expression.Literal.TRUE;
+import static org.elasticsearch.xpack.esql.core.tree.Source.EMPTY;
+
+public class BooleanFunctionEqualsEliminationTests extends ESTestCase {
+
+    public void testBoolEqualsSimplificationOnExpressions() {
+        BooleanFunctionEqualsElimination s = new BooleanFunctionEqualsElimination();
+        Expression exp = new GreaterThan(EMPTY, getFieldAttribute(), new Literal(EMPTY, 0, DataType.INTEGER), null);
+
+        assertEquals(exp, s.rule(new Equals(EMPTY, exp, TRUE)));
+        // TODO: Replace use of QL Not with ESQL Not
+        assertEquals(new Not(EMPTY, exp), s.rule(new Equals(EMPTY, exp, FALSE)));
+    }
+
+    public void testBoolEqualsSimplificationOnFields() {
+        BooleanFunctionEqualsElimination s = new BooleanFunctionEqualsElimination();
+
+        FieldAttribute field = getFieldAttribute();
+
+        List<? extends BinaryComparison> comparisons = asList(
+            new Equals(EMPTY, field, TRUE),
+            new Equals(EMPTY, field, FALSE),
+            notEqualsOf(field, TRUE),
+            notEqualsOf(field, FALSE),
+            new Equals(EMPTY, NULL, TRUE),
+            new Equals(EMPTY, NULL, FALSE),
+            notEqualsOf(NULL, TRUE),
+            notEqualsOf(NULL, FALSE)
+        );
+
+        for (BinaryComparison comparison : comparisons) {
+            assertEquals(comparison, s.rule(comparison));
+        }
+    }
+
+}

+ 61 - 0
x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/BooleanSimplificationTests.java

@@ -0,0 +1,61 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+package org.elasticsearch.xpack.esql.optimizer.rules;
+
+import org.elasticsearch.test.ESTestCase;
+import org.elasticsearch.xpack.esql.core.expression.Expression;
+import org.elasticsearch.xpack.esql.core.expression.predicate.logical.And;
+import org.elasticsearch.xpack.esql.core.expression.predicate.logical.Or;
+
+import static org.elasticsearch.xpack.esql.core.expression.Literal.FALSE;
+import static org.elasticsearch.xpack.esql.core.expression.Literal.TRUE;
+import static org.elasticsearch.xpack.esql.core.tree.Source.EMPTY;
+
+public class BooleanSimplificationTests extends ESTestCase {
+    private static final Expression DUMMY_EXPRESSION =
+        new org.elasticsearch.xpack.esql.core.optimizer.OptimizerRulesTests.DummyBooleanExpression(EMPTY, 0);
+
+    public void testBoolSimplifyOr() {
+        OptimizerRules.BooleanSimplification simplification = new OptimizerRules.BooleanSimplification();
+
+        assertEquals(TRUE, simplification.rule(new Or(EMPTY, TRUE, TRUE)));
+        assertEquals(TRUE, simplification.rule(new Or(EMPTY, TRUE, DUMMY_EXPRESSION)));
+        assertEquals(TRUE, simplification.rule(new Or(EMPTY, DUMMY_EXPRESSION, TRUE)));
+
+        assertEquals(FALSE, simplification.rule(new Or(EMPTY, FALSE, FALSE)));
+        assertEquals(DUMMY_EXPRESSION, simplification.rule(new Or(EMPTY, FALSE, DUMMY_EXPRESSION)));
+        assertEquals(DUMMY_EXPRESSION, simplification.rule(new Or(EMPTY, DUMMY_EXPRESSION, FALSE)));
+    }
+
+    public void testBoolSimplifyAnd() {
+        OptimizerRules.BooleanSimplification simplification = new OptimizerRules.BooleanSimplification();
+
+        assertEquals(TRUE, simplification.rule(new And(EMPTY, TRUE, TRUE)));
+        assertEquals(DUMMY_EXPRESSION, simplification.rule(new And(EMPTY, TRUE, DUMMY_EXPRESSION)));
+        assertEquals(DUMMY_EXPRESSION, simplification.rule(new And(EMPTY, DUMMY_EXPRESSION, TRUE)));
+
+        assertEquals(FALSE, simplification.rule(new And(EMPTY, FALSE, FALSE)));
+        assertEquals(FALSE, simplification.rule(new And(EMPTY, FALSE, DUMMY_EXPRESSION)));
+        assertEquals(FALSE, simplification.rule(new And(EMPTY, DUMMY_EXPRESSION, FALSE)));
+    }
+
+    public void testBoolCommonFactorExtraction() {
+        OptimizerRules.BooleanSimplification simplification = new OptimizerRules.BooleanSimplification();
+
+        Expression a1 = new org.elasticsearch.xpack.esql.core.optimizer.OptimizerRulesTests.DummyBooleanExpression(EMPTY, 1);
+        Expression a2 = new org.elasticsearch.xpack.esql.core.optimizer.OptimizerRulesTests.DummyBooleanExpression(EMPTY, 1);
+        Expression b = new org.elasticsearch.xpack.esql.core.optimizer.OptimizerRulesTests.DummyBooleanExpression(EMPTY, 2);
+        Expression c = new org.elasticsearch.xpack.esql.core.optimizer.OptimizerRulesTests.DummyBooleanExpression(EMPTY, 3);
+
+        Or actual = new Or(EMPTY, new And(EMPTY, a1, b), new And(EMPTY, a2, c));
+        And expected = new And(EMPTY, a1, new Or(EMPTY, b, c));
+
+        assertEquals(expected, simplification.rule(actual));
+    }
+
+}

+ 132 - 0
x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/CombineDisjunctionsToInTests.java

@@ -0,0 +1,132 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+package org.elasticsearch.xpack.esql.optimizer.rules;
+
+import org.elasticsearch.test.ESTestCase;
+import org.elasticsearch.xpack.esql.core.expression.Expression;
+import org.elasticsearch.xpack.esql.core.expression.FieldAttribute;
+import org.elasticsearch.xpack.esql.core.expression.predicate.logical.And;
+import org.elasticsearch.xpack.esql.core.expression.predicate.logical.Or;
+import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.Equals;
+import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.In;
+import org.elasticsearch.xpack.esql.plan.logical.Filter;
+import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan;
+
+import java.util.List;
+
+import static java.util.Arrays.asList;
+import static org.elasticsearch.xpack.esql.EsqlTestUtils.ONE;
+import static org.elasticsearch.xpack.esql.EsqlTestUtils.THREE;
+import static org.elasticsearch.xpack.esql.EsqlTestUtils.TWO;
+import static org.elasticsearch.xpack.esql.EsqlTestUtils.equalsOf;
+import static org.elasticsearch.xpack.esql.EsqlTestUtils.getFieldAttribute;
+import static org.elasticsearch.xpack.esql.EsqlTestUtils.lessThanOf;
+import static org.elasticsearch.xpack.esql.EsqlTestUtils.relation;
+import static org.elasticsearch.xpack.esql.core.tree.Source.EMPTY;
+import static org.hamcrest.Matchers.contains;
+
+public class CombineDisjunctionsToInTests extends ESTestCase {
+    public void testTwoEqualsWithOr() {
+        FieldAttribute fa = getFieldAttribute();
+
+        Or or = new Or(EMPTY, equalsOf(fa, ONE), equalsOf(fa, TWO));
+        Expression e = new CombineDisjunctionsToIn().rule(or);
+        assertEquals(In.class, e.getClass());
+        In in = (In) e;
+        assertEquals(fa, in.value());
+        assertThat(in.list(), contains(ONE, TWO));
+    }
+
+    public void testTwoEqualsWithSameValue() {
+        FieldAttribute fa = getFieldAttribute();
+
+        Or or = new Or(EMPTY, equalsOf(fa, ONE), equalsOf(fa, ONE));
+        Expression e = new CombineDisjunctionsToIn().rule(or);
+        assertEquals(Equals.class, e.getClass());
+        Equals eq = (Equals) e;
+        assertEquals(fa, eq.left());
+        assertEquals(ONE, eq.right());
+    }
+
+    public void testOneEqualsOneIn() {
+        FieldAttribute fa = getFieldAttribute();
+
+        Or or = new Or(EMPTY, equalsOf(fa, ONE), new In(EMPTY, fa, List.of(TWO)));
+        Expression e = new CombineDisjunctionsToIn().rule(or);
+        assertEquals(In.class, e.getClass());
+        In in = (In) e;
+        assertEquals(fa, in.value());
+        assertThat(in.list(), contains(ONE, TWO));
+    }
+
+    public void testOneEqualsOneInWithSameValue() {
+        FieldAttribute fa = getFieldAttribute();
+
+        Or or = new Or(EMPTY, equalsOf(fa, ONE), new In(EMPTY, fa, asList(ONE, TWO)));
+        Expression e = new CombineDisjunctionsToIn().rule(or);
+        assertEquals(In.class, e.getClass());
+        In in = (In) e;
+        assertEquals(fa, in.value());
+        assertThat(in.list(), contains(ONE, TWO));
+    }
+
+    public void testSingleValueInToEquals() {
+        FieldAttribute fa = getFieldAttribute();
+
+        Equals equals = equalsOf(fa, ONE);
+        Or or = new Or(EMPTY, equals, new In(EMPTY, fa, List.of(ONE)));
+        Expression e = new CombineDisjunctionsToIn().rule(or);
+        assertEquals(equals, e);
+    }
+
+    public void testEqualsBehindAnd() {
+        FieldAttribute fa = getFieldAttribute();
+
+        And and = new And(EMPTY, equalsOf(fa, ONE), equalsOf(fa, TWO));
+        Filter dummy = new Filter(EMPTY, relation(), and);
+        LogicalPlan transformed = new CombineDisjunctionsToIn().apply(dummy);
+        assertSame(dummy, transformed);
+        assertEquals(and, ((Filter) transformed).condition());
+    }
+
+    public void testTwoEqualsDifferentFields() {
+        FieldAttribute fieldOne = getFieldAttribute("ONE");
+        FieldAttribute fieldTwo = getFieldAttribute("TWO");
+
+        Or or = new Or(EMPTY, equalsOf(fieldOne, ONE), equalsOf(fieldTwo, TWO));
+        Expression e = new CombineDisjunctionsToIn().rule(or);
+        assertEquals(or, e);
+    }
+
+    public void testMultipleIn() {
+        FieldAttribute fa = getFieldAttribute();
+
+        Or firstOr = new Or(EMPTY, new In(EMPTY, fa, List.of(ONE)), new In(EMPTY, fa, List.of(TWO)));
+        Or secondOr = new Or(EMPTY, firstOr, new In(EMPTY, fa, List.of(THREE)));
+        Expression e = new CombineDisjunctionsToIn().rule(secondOr);
+        assertEquals(In.class, e.getClass());
+        In in = (In) e;
+        assertEquals(fa, in.value());
+        assertThat(in.list(), contains(ONE, TWO, THREE));
+    }
+
+    public void testOrWithNonCombinableExpressions() {
+        FieldAttribute fa = getFieldAttribute();
+
+        Or firstOr = new Or(EMPTY, new In(EMPTY, fa, List.of(ONE)), lessThanOf(fa, TWO));
+        Or secondOr = new Or(EMPTY, firstOr, new In(EMPTY, fa, List.of(THREE)));
+        Expression e = new CombineDisjunctionsToIn().rule(secondOr);
+        assertEquals(Or.class, e.getClass());
+        Or or = (Or) e;
+        assertEquals(or.left(), firstOr.right());
+        assertEquals(In.class, or.right().getClass());
+        In in = (In) or.right();
+        assertEquals(fa, in.value());
+        assertThat(in.list(), contains(ONE, THREE));
+    }
+}

+ 121 - 0
x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/ConstantFoldingTests.java

@@ -0,0 +1,121 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+package org.elasticsearch.xpack.esql.optimizer.rules;
+
+import org.elasticsearch.test.ESTestCase;
+import org.elasticsearch.xpack.esql.core.expression.Alias;
+import org.elasticsearch.xpack.esql.core.expression.Expression;
+import org.elasticsearch.xpack.esql.core.expression.Expressions;
+import org.elasticsearch.xpack.esql.core.expression.Literal;
+import org.elasticsearch.xpack.esql.core.expression.Nullability;
+import org.elasticsearch.xpack.esql.core.expression.predicate.BinaryOperator;
+import org.elasticsearch.xpack.esql.core.expression.predicate.logical.And;
+import org.elasticsearch.xpack.esql.core.expression.predicate.logical.Not;
+import org.elasticsearch.xpack.esql.core.expression.predicate.logical.Or;
+import org.elasticsearch.xpack.esql.core.expression.predicate.regex.Like;
+import org.elasticsearch.xpack.esql.core.expression.predicate.regex.LikePattern;
+import org.elasticsearch.xpack.esql.core.expression.predicate.regex.RLike;
+import org.elasticsearch.xpack.esql.core.expression.predicate.regex.RLikePattern;
+import org.elasticsearch.xpack.esql.core.expression.predicate.regex.WildcardLike;
+import org.elasticsearch.xpack.esql.core.expression.predicate.regex.WildcardPattern;
+import org.elasticsearch.xpack.esql.core.type.DataType;
+import org.elasticsearch.xpack.esql.expression.predicate.operator.arithmetic.Add;
+import org.elasticsearch.xpack.esql.expression.predicate.operator.arithmetic.Div;
+import org.elasticsearch.xpack.esql.expression.predicate.operator.arithmetic.Mod;
+import org.elasticsearch.xpack.esql.expression.predicate.operator.arithmetic.Mul;
+import org.elasticsearch.xpack.esql.expression.predicate.operator.arithmetic.Sub;
+
+import static org.elasticsearch.xpack.esql.EsqlTestUtils.FIVE;
+import static org.elasticsearch.xpack.esql.EsqlTestUtils.THREE;
+import static org.elasticsearch.xpack.esql.EsqlTestUtils.TWO;
+import static org.elasticsearch.xpack.esql.EsqlTestUtils.equalsOf;
+import static org.elasticsearch.xpack.esql.EsqlTestUtils.greaterThanOf;
+import static org.elasticsearch.xpack.esql.EsqlTestUtils.greaterThanOrEqualOf;
+import static org.elasticsearch.xpack.esql.EsqlTestUtils.lessThanOf;
+import static org.elasticsearch.xpack.esql.EsqlTestUtils.lessThanOrEqualOf;
+import static org.elasticsearch.xpack.esql.EsqlTestUtils.notEqualsOf;
+import static org.elasticsearch.xpack.esql.EsqlTestUtils.of;
+import static org.elasticsearch.xpack.esql.EsqlTestUtils.rangeOf;
+import static org.elasticsearch.xpack.esql.core.expression.Literal.FALSE;
+import static org.elasticsearch.xpack.esql.core.expression.Literal.NULL;
+import static org.elasticsearch.xpack.esql.core.expression.Literal.TRUE;
+import static org.elasticsearch.xpack.esql.core.tree.Source.EMPTY;
+
+public class ConstantFoldingTests extends ESTestCase {
+
+    public void testConstantFolding() {
+        Expression exp = new Add(EMPTY, TWO, THREE);
+
+        assertTrue(exp.foldable());
+        Expression result = new ConstantFolding().rule(exp);
+        assertTrue(result instanceof Literal);
+        assertEquals(5, ((Literal) result).value());
+
+        // check now with an alias
+        result = new ConstantFolding().rule(new Alias(EMPTY, "a", exp));
+        assertEquals("a", Expressions.name(result));
+        assertEquals(Alias.class, result.getClass());
+    }
+
+    public void testConstantFoldingBinaryComparison() {
+        assertEquals(FALSE, new ConstantFolding().rule(greaterThanOf(TWO, THREE)).canonical());
+        assertEquals(FALSE, new ConstantFolding().rule(greaterThanOrEqualOf(TWO, THREE)).canonical());
+        assertEquals(FALSE, new ConstantFolding().rule(equalsOf(TWO, THREE)).canonical());
+        assertEquals(TRUE, new ConstantFolding().rule(notEqualsOf(TWO, THREE)).canonical());
+        assertEquals(TRUE, new ConstantFolding().rule(lessThanOrEqualOf(TWO, THREE)).canonical());
+        assertEquals(TRUE, new ConstantFolding().rule(lessThanOf(TWO, THREE)).canonical());
+    }
+
+    public void testConstantFoldingBinaryLogic() {
+        assertEquals(FALSE, new ConstantFolding().rule(new And(EMPTY, greaterThanOf(TWO, THREE), TRUE)).canonical());
+        assertEquals(TRUE, new ConstantFolding().rule(new Or(EMPTY, greaterThanOrEqualOf(TWO, THREE), TRUE)).canonical());
+    }
+
+    public void testConstantFoldingBinaryLogic_WithNullHandling() {
+        assertEquals(Nullability.TRUE, new ConstantFolding().rule(new And(EMPTY, NULL, TRUE)).canonical().nullable());
+        assertEquals(Nullability.TRUE, new ConstantFolding().rule(new And(EMPTY, TRUE, NULL)).canonical().nullable());
+        assertEquals(FALSE, new ConstantFolding().rule(new And(EMPTY, NULL, FALSE)).canonical());
+        assertEquals(FALSE, new ConstantFolding().rule(new And(EMPTY, FALSE, NULL)).canonical());
+        assertEquals(Nullability.TRUE, new ConstantFolding().rule(new And(EMPTY, NULL, NULL)).canonical().nullable());
+
+        assertEquals(TRUE, new ConstantFolding().rule(new Or(EMPTY, NULL, TRUE)).canonical());
+        assertEquals(TRUE, new ConstantFolding().rule(new Or(EMPTY, TRUE, NULL)).canonical());
+        assertEquals(Nullability.TRUE, new ConstantFolding().rule(new Or(EMPTY, NULL, FALSE)).canonical().nullable());
+        assertEquals(Nullability.TRUE, new ConstantFolding().rule(new Or(EMPTY, FALSE, NULL)).canonical().nullable());
+        assertEquals(Nullability.TRUE, new ConstantFolding().rule(new Or(EMPTY, NULL, NULL)).canonical().nullable());
+    }
+
+    public void testConstantFoldingRange() {
+        assertEquals(true, new ConstantFolding().rule(rangeOf(FIVE, FIVE, true, new Literal(EMPTY, 10, DataType.INTEGER), false)).fold());
+        assertEquals(false, new ConstantFolding().rule(rangeOf(FIVE, FIVE, false, new Literal(EMPTY, 10, DataType.INTEGER), false)).fold());
+    }
+
+    public void testConstantNot() {
+        assertEquals(FALSE, new ConstantFolding().rule(new Not(EMPTY, TRUE)));
+        assertEquals(TRUE, new ConstantFolding().rule(new Not(EMPTY, FALSE)));
+    }
+
+    public void testConstantFoldingLikes() {
+        assertEquals(TRUE, new ConstantFolding().rule(new Like(EMPTY, of("test_emp"), new LikePattern("test%", (char) 0))).canonical());
+        assertEquals(TRUE, new ConstantFolding().rule(new WildcardLike(EMPTY, of("test_emp"), new WildcardPattern("test*"))).canonical());
+        assertEquals(TRUE, new ConstantFolding().rule(new RLike(EMPTY, of("test_emp"), new RLikePattern("test.emp"))).canonical());
+    }
+
+    public void testArithmeticFolding() {
+        assertEquals(10, foldOperator(new Add(EMPTY, new Literal(EMPTY, 7, DataType.INTEGER), THREE)));
+        assertEquals(4, foldOperator(new Sub(EMPTY, new Literal(EMPTY, 7, DataType.INTEGER), THREE)));
+        assertEquals(21, foldOperator(new Mul(EMPTY, new Literal(EMPTY, 7, DataType.INTEGER), THREE)));
+        assertEquals(2, foldOperator(new Div(EMPTY, new Literal(EMPTY, 7, DataType.INTEGER), THREE)));
+        assertEquals(1, foldOperator(new Mod(EMPTY, new Literal(EMPTY, 7, DataType.INTEGER), THREE)));
+    }
+
+    private static Object foldOperator(BinaryOperator<?, ?, ?, ?> b) {
+        return ((Literal) new ConstantFolding().rule(b)).value();
+    }
+
+}

+ 63 - 0
x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/FoldNullTests.java

@@ -0,0 +1,63 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+package org.elasticsearch.xpack.esql.optimizer.rules;
+
+import org.elasticsearch.test.ESTestCase;
+import org.elasticsearch.xpack.esql.core.expression.Expression;
+import org.elasticsearch.xpack.esql.core.expression.Literal;
+import org.elasticsearch.xpack.esql.core.expression.predicate.logical.And;
+import org.elasticsearch.xpack.esql.core.expression.predicate.logical.Or;
+import org.elasticsearch.xpack.esql.core.expression.predicate.nulls.IsNull;
+import org.elasticsearch.xpack.esql.core.expression.predicate.regex.RLike;
+import org.elasticsearch.xpack.esql.core.expression.predicate.regex.RLikePattern;
+import org.elasticsearch.xpack.esql.expression.predicate.operator.arithmetic.Add;
+
+import static org.elasticsearch.xpack.esql.EsqlTestUtils.getFieldAttribute;
+import static org.elasticsearch.xpack.esql.EsqlTestUtils.greaterThanOf;
+import static org.elasticsearch.xpack.esql.core.expression.Literal.NULL;
+import static org.elasticsearch.xpack.esql.core.expression.Literal.TRUE;
+import static org.elasticsearch.xpack.esql.core.tree.Source.EMPTY;
+
+public class FoldNullTests extends ESTestCase {
+
+    public void testNullFoldingIsNull() {
+        OptimizerRules.FoldNull foldNull = new OptimizerRules.FoldNull();
+        assertEquals(true, foldNull.rule(new IsNull(EMPTY, NULL)).fold());
+        assertEquals(false, foldNull.rule(new IsNull(EMPTY, TRUE)).fold());
+    }
+
+    public void testGenericNullableExpression() {
+        OptimizerRules.FoldNull rule = new OptimizerRules.FoldNull();
+        // arithmetic
+        assertNullLiteral(rule.rule(new Add(EMPTY, getFieldAttribute(), NULL)));
+        // comparison
+        assertNullLiteral(rule.rule(greaterThanOf(getFieldAttribute(), NULL)));
+        // regex
+        assertNullLiteral(rule.rule(new RLike(EMPTY, NULL, new RLikePattern("123"))));
+    }
+
+    public void testNullFoldingDoesNotApplyOnLogicalExpressions() {
+        OptimizerRules.FoldNull rule = new OptimizerRules.FoldNull();
+
+        Or or = new Or(EMPTY, NULL, TRUE);
+        assertEquals(or, rule.rule(or));
+        or = new Or(EMPTY, NULL, NULL);
+        assertEquals(or, rule.rule(or));
+
+        And and = new And(EMPTY, NULL, TRUE);
+        assertEquals(and, rule.rule(and));
+        and = new And(EMPTY, NULL, NULL);
+        assertEquals(and, rule.rule(and));
+    }
+
+    private void assertNullLiteral(Expression expression) {
+        assertEquals(Literal.class, expression.getClass());
+        assertNull(expression.fold());
+    }
+
+}

+ 34 - 0
x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/LiteralsOnTheRightTests.java

@@ -0,0 +1,34 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+package org.elasticsearch.xpack.esql.optimizer.rules;
+
+import org.elasticsearch.test.ESTestCase;
+import org.elasticsearch.xpack.esql.core.expression.Alias;
+import org.elasticsearch.xpack.esql.core.expression.Expression;
+import org.elasticsearch.xpack.esql.core.expression.Literal;
+import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.Equals;
+
+import static org.elasticsearch.xpack.esql.EsqlTestUtils.FIVE;
+import static org.elasticsearch.xpack.esql.EsqlTestUtils.equalsOf;
+import static org.elasticsearch.xpack.esql.core.tree.Source.EMPTY;
+import static org.elasticsearch.xpack.esql.core.type.DataType.INTEGER;
+
+public class LiteralsOnTheRightTests extends ESTestCase {
+
+    public void testLiteralsOnTheRight() {
+        Alias a = new Alias(EMPTY, "a", new Literal(EMPTY, 10, INTEGER));
+        Expression result = new LiteralsOnTheRight().rule(equalsOf(FIVE, a));
+        assertTrue(result instanceof Equals);
+        Equals eq = (Equals) result;
+        assertEquals(a, eq.left());
+        assertEquals(FIVE, eq.right());
+
+        // Note: Null Equals test removed here
+    }
+
+}

+ 335 - 0
x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/PropagateEqualsTests.java

@@ -0,0 +1,335 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+package org.elasticsearch.xpack.esql.optimizer.rules;
+
+import org.elasticsearch.test.ESTestCase;
+import org.elasticsearch.xpack.esql.core.expression.Expression;
+import org.elasticsearch.xpack.esql.core.expression.FieldAttribute;
+import org.elasticsearch.xpack.esql.core.expression.Literal;
+import org.elasticsearch.xpack.esql.core.expression.predicate.Predicates;
+import org.elasticsearch.xpack.esql.core.expression.predicate.Range;
+import org.elasticsearch.xpack.esql.core.expression.predicate.logical.And;
+import org.elasticsearch.xpack.esql.core.expression.predicate.logical.Or;
+import org.elasticsearch.xpack.esql.core.type.DataType;
+import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.Equals;
+import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.GreaterThan;
+import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.GreaterThanOrEqual;
+import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.LessThan;
+import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.LessThanOrEqual;
+import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.NotEquals;
+
+import static java.util.Arrays.asList;
+import static org.elasticsearch.xpack.esql.EsqlTestUtils.FIVE;
+import static org.elasticsearch.xpack.esql.EsqlTestUtils.FOUR;
+import static org.elasticsearch.xpack.esql.EsqlTestUtils.ONE;
+import static org.elasticsearch.xpack.esql.EsqlTestUtils.THREE;
+import static org.elasticsearch.xpack.esql.EsqlTestUtils.TWO;
+import static org.elasticsearch.xpack.esql.EsqlTestUtils.equalsOf;
+import static org.elasticsearch.xpack.esql.EsqlTestUtils.getFieldAttribute;
+import static org.elasticsearch.xpack.esql.EsqlTestUtils.greaterThanOf;
+import static org.elasticsearch.xpack.esql.EsqlTestUtils.greaterThanOrEqualOf;
+import static org.elasticsearch.xpack.esql.EsqlTestUtils.lessThanOf;
+import static org.elasticsearch.xpack.esql.EsqlTestUtils.lessThanOrEqualOf;
+import static org.elasticsearch.xpack.esql.EsqlTestUtils.notEqualsOf;
+import static org.elasticsearch.xpack.esql.EsqlTestUtils.rangeOf;
+import static org.elasticsearch.xpack.esql.core.expression.Literal.FALSE;
+import static org.elasticsearch.xpack.esql.core.expression.Literal.TRUE;
+import static org.elasticsearch.xpack.esql.core.tree.Source.EMPTY;
+
+public class PropagateEqualsTests extends ESTestCase {
+
+    // a == 1 AND a == 2 -> FALSE
+    public void testDualEqualsConjunction() {
+        FieldAttribute fa = getFieldAttribute();
+        Equals eq1 = equalsOf(fa, ONE);
+        Equals eq2 = equalsOf(fa, TWO);
+
+        PropagateEquals rule = new PropagateEquals();
+        Expression exp = rule.rule(new And(EMPTY, eq1, eq2));
+        assertEquals(FALSE, exp);
+    }
+
+    // 1 < a < 10 AND a == 10 -> FALSE
+    public void testEliminateRangeByEqualsOutsideInterval() {
+        FieldAttribute fa = getFieldAttribute();
+        Equals eq1 = equalsOf(fa, new Literal(EMPTY, 10, DataType.INTEGER));
+        Range r = rangeOf(fa, ONE, false, new Literal(EMPTY, 10, DataType.INTEGER), false);
+
+        PropagateEquals rule = new PropagateEquals();
+        Expression exp = rule.rule(new And(EMPTY, eq1, r));
+        assertEquals(FALSE, exp);
+    }
+
+    // a != 3 AND a = 3 -> FALSE
+    public void testPropagateEquals_VarNeq3AndVarEq3() {
+        FieldAttribute fa = getFieldAttribute();
+        NotEquals neq = notEqualsOf(fa, THREE);
+        Equals eq = equalsOf(fa, THREE);
+
+        PropagateEquals rule = new PropagateEquals();
+        Expression exp = rule.rule(new And(EMPTY, neq, eq));
+        assertEquals(FALSE, exp);
+    }
+
+    // a != 4 AND a = 3 -> a = 3
+    public void testPropagateEquals_VarNeq4AndVarEq3() {
+        FieldAttribute fa = getFieldAttribute();
+        NotEquals neq = notEqualsOf(fa, FOUR);
+        Equals eq = equalsOf(fa, THREE);
+
+        PropagateEquals rule = new PropagateEquals();
+        Expression exp = rule.rule(new And(EMPTY, neq, eq));
+        assertEquals(Equals.class, exp.getClass());
+        assertEquals(eq, exp);
+    }
+
+    // a = 2 AND a < 2 -> FALSE
+    public void testPropagateEquals_VarEq2AndVarLt2() {
+        FieldAttribute fa = getFieldAttribute();
+        Equals eq = equalsOf(fa, TWO);
+        LessThan lt = lessThanOf(fa, TWO);
+
+        PropagateEquals rule = new PropagateEquals();
+        Expression exp = rule.rule(new And(EMPTY, eq, lt));
+        assertEquals(FALSE, exp);
+    }
+
+    // a = 2 AND a <= 2 -> a = 2
+    public void testPropagateEquals_VarEq2AndVarLte2() {
+        FieldAttribute fa = getFieldAttribute();
+        Equals eq = equalsOf(fa, TWO);
+        LessThanOrEqual lt = lessThanOrEqualOf(fa, TWO);
+
+        PropagateEquals rule = new PropagateEquals();
+        Expression exp = rule.rule(new And(EMPTY, eq, lt));
+        assertEquals(eq, exp);
+    }
+
+    // a = 2 AND a <= 1 -> FALSE
+    public void testPropagateEquals_VarEq2AndVarLte1() {
+        FieldAttribute fa = getFieldAttribute();
+        Equals eq = equalsOf(fa, TWO);
+        LessThanOrEqual lt = lessThanOrEqualOf(fa, ONE);
+
+        PropagateEquals rule = new PropagateEquals();
+        Expression exp = rule.rule(new And(EMPTY, eq, lt));
+        assertEquals(FALSE, exp);
+    }
+
+    // a = 2 AND a > 2 -> FALSE
+    public void testPropagateEquals_VarEq2AndVarGt2() {
+        FieldAttribute fa = getFieldAttribute();
+        Equals eq = equalsOf(fa, TWO);
+        GreaterThan gt = greaterThanOf(fa, TWO);
+
+        PropagateEquals rule = new PropagateEquals();
+        Expression exp = rule.rule(new And(EMPTY, eq, gt));
+        assertEquals(FALSE, exp);
+    }
+
+    // a = 2 AND a >= 2 -> a = 2
+    public void testPropagateEquals_VarEq2AndVarGte2() {
+        FieldAttribute fa = getFieldAttribute();
+        Equals eq = equalsOf(fa, TWO);
+        GreaterThanOrEqual gte = greaterThanOrEqualOf(fa, TWO);
+
+        PropagateEquals rule = new PropagateEquals();
+        Expression exp = rule.rule(new And(EMPTY, eq, gte));
+        assertEquals(eq, exp);
+    }
+
+    // a = 2 AND a > 3 -> FALSE
+    public void testPropagateEquals_VarEq2AndVarLt3() {
+        FieldAttribute fa = getFieldAttribute();
+        Equals eq = equalsOf(fa, TWO);
+        GreaterThan gt = greaterThanOf(fa, THREE);
+
+        PropagateEquals rule = new PropagateEquals();
+        Expression exp = rule.rule(new And(EMPTY, eq, gt));
+        assertEquals(FALSE, exp);
+    }
+
+    // a = 2 AND a < 3 AND a > 1 AND a != 4 -> a = 2
+    public void testPropagateEquals_VarEq2AndVarLt3AndVarGt1AndVarNeq4() {
+        FieldAttribute fa = getFieldAttribute();
+        Equals eq = equalsOf(fa, TWO);
+        LessThan lt = lessThanOf(fa, THREE);
+        GreaterThan gt = greaterThanOf(fa, ONE);
+        NotEquals neq = notEqualsOf(fa, FOUR);
+
+        PropagateEquals rule = new PropagateEquals();
+        Expression and = Predicates.combineAnd(asList(eq, lt, gt, neq));
+        Expression exp = rule.rule((And) and);
+        assertEquals(eq, exp);
+    }
+
+    // a = 2 AND 1 < a < 3 AND a > 0 AND a != 4 -> a = 2
+    public void testPropagateEquals_VarEq2AndVarRangeGt1Lt3AndVarGt0AndVarNeq4() {
+        FieldAttribute fa = getFieldAttribute();
+        Equals eq = equalsOf(fa, TWO);
+        Range range = rangeOf(fa, ONE, false, THREE, false);
+        GreaterThan gt = greaterThanOf(fa, new Literal(EMPTY, 0, DataType.INTEGER));
+        NotEquals neq = notEqualsOf(fa, FOUR);
+
+        PropagateEquals rule = new PropagateEquals();
+        Expression and = Predicates.combineAnd(asList(eq, range, gt, neq));
+        Expression exp = rule.rule((And) and);
+        assertEquals(eq, exp);
+    }
+
+    // a = 2 OR a > 1 -> a > 1
+    public void testPropagateEquals_VarEq2OrVarGt1() {
+        FieldAttribute fa = getFieldAttribute();
+        Equals eq = equalsOf(fa, TWO);
+        GreaterThan gt = greaterThanOf(fa, ONE);
+
+        PropagateEquals rule = new PropagateEquals();
+        Expression exp = rule.rule(new Or(EMPTY, eq, gt));
+        assertEquals(gt, exp);
+    }
+
+    // a = 2 OR a > 2 -> a >= 2
+    public void testPropagateEquals_VarEq2OrVarGte2() {
+        FieldAttribute fa = getFieldAttribute();
+        Equals eq = equalsOf(fa, TWO);
+        GreaterThan gt = greaterThanOf(fa, TWO);
+
+        PropagateEquals rule = new PropagateEquals();
+        Expression exp = rule.rule(new Or(EMPTY, eq, gt));
+        assertEquals(GreaterThanOrEqual.class, exp.getClass());
+        GreaterThanOrEqual gte = (GreaterThanOrEqual) exp;
+        assertEquals(TWO, gte.right());
+    }
+
+    // a = 2 OR a < 3 -> a < 3
+    public void testPropagateEquals_VarEq2OrVarLt3() {
+        FieldAttribute fa = getFieldAttribute();
+        Equals eq = equalsOf(fa, TWO);
+        LessThan lt = lessThanOf(fa, THREE);
+
+        PropagateEquals rule = new PropagateEquals();
+        Expression exp = rule.rule(new Or(EMPTY, eq, lt));
+        assertEquals(lt, exp);
+    }
+
+    // a = 3 OR a < 3 -> a <= 3
+    public void testPropagateEquals_VarEq3OrVarLt3() {
+        FieldAttribute fa = getFieldAttribute();
+        Equals eq = equalsOf(fa, THREE);
+        LessThan lt = lessThanOf(fa, THREE);
+
+        PropagateEquals rule = new PropagateEquals();
+        Expression exp = rule.rule(new Or(EMPTY, eq, lt));
+        assertEquals(LessThanOrEqual.class, exp.getClass());
+        LessThanOrEqual lte = (LessThanOrEqual) exp;
+        assertEquals(THREE, lte.right());
+    }
+
+    // a = 2 OR 1 < a < 3 -> 1 < a < 3
+    public void testPropagateEquals_VarEq2OrVarRangeGt1Lt3() {
+        FieldAttribute fa = getFieldAttribute();
+        Equals eq = equalsOf(fa, TWO);
+        Range range = rangeOf(fa, ONE, false, THREE, false);
+
+        PropagateEquals rule = new PropagateEquals();
+        Expression exp = rule.rule(new Or(EMPTY, eq, range));
+        assertEquals(range, exp);
+    }
+
+    // a = 2 OR 2 < a < 3 -> 2 <= a < 3
+    public void testPropagateEquals_VarEq2OrVarRangeGt2Lt3() {
+        FieldAttribute fa = getFieldAttribute();
+        Equals eq = equalsOf(fa, TWO);
+        Range range = rangeOf(fa, TWO, false, THREE, false);
+
+        PropagateEquals rule = new PropagateEquals();
+        Expression exp = rule.rule(new Or(EMPTY, eq, range));
+        assertEquals(Range.class, exp.getClass());
+        Range r = (Range) exp;
+        assertEquals(TWO, r.lower());
+        assertTrue(r.includeLower());
+        assertEquals(THREE, r.upper());
+        assertFalse(r.includeUpper());
+    }
+
+    // a = 3 OR 2 < a < 3 -> 2 < a <= 3
+    public void testPropagateEquals_VarEq3OrVarRangeGt2Lt3() {
+        FieldAttribute fa = getFieldAttribute();
+        Equals eq = equalsOf(fa, THREE);
+        Range range = rangeOf(fa, TWO, false, THREE, false);
+
+        PropagateEquals rule = new PropagateEquals();
+        Expression exp = rule.rule(new Or(EMPTY, eq, range));
+        assertEquals(Range.class, exp.getClass());
+        Range r = (Range) exp;
+        assertEquals(TWO, r.lower());
+        assertFalse(r.includeLower());
+        assertEquals(THREE, r.upper());
+        assertTrue(r.includeUpper());
+    }
+
+    // a = 2 OR a != 2 -> TRUE
+    public void testPropagateEquals_VarEq2OrVarNeq2() {
+        FieldAttribute fa = getFieldAttribute();
+        Equals eq = equalsOf(fa, TWO);
+        NotEquals neq = notEqualsOf(fa, TWO);
+
+        PropagateEquals rule = new PropagateEquals();
+        Expression exp = rule.rule(new Or(EMPTY, eq, neq));
+        assertEquals(TRUE, exp);
+    }
+
+    // a = 2 OR a != 5 -> a != 5
+    public void testPropagateEquals_VarEq2OrVarNeq5() {
+        FieldAttribute fa = getFieldAttribute();
+        Equals eq = equalsOf(fa, TWO);
+        NotEquals neq = notEqualsOf(fa, FIVE);
+
+        PropagateEquals rule = new PropagateEquals();
+        Expression exp = rule.rule(new Or(EMPTY, eq, neq));
+        assertEquals(NotEquals.class, exp.getClass());
+        NotEquals ne = (NotEquals) exp;
+        assertEquals(FIVE, ne.right());
+    }
+
+    // a = 2 OR 3 < a < 4 OR a > 2 OR a!= 2 -> TRUE
+    public void testPropagateEquals_VarEq2OrVarRangeGt3Lt4OrVarGt2OrVarNe2() {
+        FieldAttribute fa = getFieldAttribute();
+        Equals eq = equalsOf(fa, TWO);
+        Range range = rangeOf(fa, THREE, false, FOUR, false);
+        GreaterThan gt = greaterThanOf(fa, TWO);
+        NotEquals neq = notEqualsOf(fa, TWO);
+
+        PropagateEquals rule = new PropagateEquals();
+        Expression exp = rule.rule((Or) Predicates.combineOr(asList(eq, range, neq, gt)));
+        assertEquals(TRUE, exp);
+    }
+
+    // a == 1 AND a == 2 -> nop for date/time fields
+    public void testPropagateEquals_ignoreDateTimeFields() {
+        FieldAttribute fa = getFieldAttribute("a", DataType.DATETIME);
+        Equals eq1 = equalsOf(fa, ONE);
+        Equals eq2 = equalsOf(fa, TWO);
+        And and = new And(EMPTY, eq1, eq2);
+
+        PropagateEquals rule = new PropagateEquals();
+        Expression exp = rule.rule(and);
+        assertEquals(and, exp);
+    }
+
+    // 1 <= a < 10 AND a == 1 -> a == 1
+    public void testEliminateRangeByEqualsInInterval() {
+        FieldAttribute fa = getFieldAttribute();
+        Equals eq1 = equalsOf(fa, ONE);
+        Range r = rangeOf(fa, ONE, true, new Literal(EMPTY, 10, DataType.INTEGER), false);
+
+        PropagateEquals rule = new PropagateEquals();
+        Expression exp = rule.rule(new And(EMPTY, eq1, r));
+        assertEquals(eq1, exp);
+    }
+}

+ 134 - 0
x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/PropagateNullableTests.java

@@ -0,0 +1,134 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+package org.elasticsearch.xpack.esql.optimizer.rules;
+
+import org.elasticsearch.test.ESTestCase;
+import org.elasticsearch.xpack.esql.core.expression.Expression;
+import org.elasticsearch.xpack.esql.core.expression.FieldAttribute;
+import org.elasticsearch.xpack.esql.core.expression.Literal;
+import org.elasticsearch.xpack.esql.core.expression.predicate.Predicates;
+import org.elasticsearch.xpack.esql.core.expression.predicate.logical.And;
+import org.elasticsearch.xpack.esql.core.expression.predicate.logical.Or;
+import org.elasticsearch.xpack.esql.core.expression.predicate.nulls.IsNotNull;
+import org.elasticsearch.xpack.esql.core.expression.predicate.nulls.IsNull;
+import org.elasticsearch.xpack.esql.core.tree.Source;
+import org.elasticsearch.xpack.esql.core.type.DataType;
+import org.elasticsearch.xpack.esql.expression.predicate.operator.arithmetic.Add;
+import org.elasticsearch.xpack.esql.expression.predicate.operator.arithmetic.Div;
+import org.elasticsearch.xpack.esql.plan.logical.Filter;
+import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan;
+
+import static org.elasticsearch.xpack.esql.EsqlTestUtils.ONE;
+import static org.elasticsearch.xpack.esql.EsqlTestUtils.THREE;
+import static org.elasticsearch.xpack.esql.EsqlTestUtils.TWO;
+import static org.elasticsearch.xpack.esql.EsqlTestUtils.getFieldAttribute;
+import static org.elasticsearch.xpack.esql.EsqlTestUtils.greaterThanOf;
+import static org.elasticsearch.xpack.esql.EsqlTestUtils.lessThanOf;
+import static org.elasticsearch.xpack.esql.EsqlTestUtils.relation;
+import static org.elasticsearch.xpack.esql.core.expression.Literal.FALSE;
+import static org.elasticsearch.xpack.esql.core.tree.Source.EMPTY;
+import static org.elasticsearch.xpack.esql.core.type.DataType.BOOLEAN;
+
+public class PropagateNullableTests extends ESTestCase {
+    private Literal nullOf(DataType dataType) {
+        return new Literal(Source.EMPTY, null, dataType);
+    }
+
+    // a IS NULL AND a IS NOT NULL => false
+    public void testIsNullAndNotNull() {
+        FieldAttribute fa = getFieldAttribute();
+
+        And and = new And(EMPTY, new IsNull(EMPTY, fa), new IsNotNull(EMPTY, fa));
+        assertEquals(FALSE, new OptimizerRules.PropagateNullable().rule(and));
+    }
+
+    // a IS NULL AND b IS NOT NULL AND c IS NULL AND d IS NOT NULL AND e IS NULL AND a IS NOT NULL => false
+    public void testIsNullAndNotNullMultiField() {
+        FieldAttribute fa = getFieldAttribute();
+
+        And andOne = new And(EMPTY, new IsNull(EMPTY, fa), new IsNotNull(EMPTY, getFieldAttribute()));
+        And andTwo = new And(EMPTY, new IsNull(EMPTY, getFieldAttribute()), new IsNotNull(EMPTY, getFieldAttribute()));
+        And andThree = new And(EMPTY, new IsNull(EMPTY, getFieldAttribute()), new IsNotNull(EMPTY, fa));
+
+        And and = new And(EMPTY, andOne, new And(EMPTY, andThree, andTwo));
+
+        assertEquals(FALSE, new OptimizerRules.PropagateNullable().rule(and));
+    }
+
+    // a IS NULL AND a > 1 => a IS NULL AND false
+    public void testIsNullAndComparison() {
+        FieldAttribute fa = getFieldAttribute();
+        IsNull isNull = new IsNull(EMPTY, fa);
+
+        And and = new And(EMPTY, isNull, greaterThanOf(fa, ONE));
+        assertEquals(new And(EMPTY, isNull, nullOf(BOOLEAN)), new OptimizerRules.PropagateNullable().rule(and));
+    }
+
+    // a IS NULL AND b < 1 AND c < 1 AND a < 1 => a IS NULL AND b < 1 AND c < 1 => a IS NULL AND b < 1 AND c < 1
+    public void testIsNullAndMultipleComparison() {
+        FieldAttribute fa = getFieldAttribute();
+        IsNull isNull = new IsNull(EMPTY, fa);
+
+        And nestedAnd = new And(EMPTY, lessThanOf(getFieldAttribute("b"), ONE), lessThanOf(getFieldAttribute("c"), ONE));
+        And and = new And(EMPTY, isNull, nestedAnd);
+        And top = new And(EMPTY, and, lessThanOf(fa, ONE));
+
+        Expression optimized = new OptimizerRules.PropagateNullable().rule(top);
+        Expression expected = new And(EMPTY, and, nullOf(BOOLEAN));
+        assertEquals(Predicates.splitAnd(expected), Predicates.splitAnd(optimized));
+    }
+
+    // ((a+1)/2) > 1 AND a + 2 AND a IS NULL AND b < 3 => NULL AND NULL AND a IS NULL AND b < 3
+    public void testIsNullAndDeeplyNestedExpression() {
+        FieldAttribute fa = getFieldAttribute();
+        IsNull isNull = new IsNull(EMPTY, fa);
+
+        Expression nullified = new And(
+            EMPTY,
+            greaterThanOf(new Div(EMPTY, new Add(EMPTY, fa, ONE), TWO), ONE),
+            greaterThanOf(new Add(EMPTY, fa, TWO), ONE)
+        );
+        Expression kept = new And(EMPTY, isNull, lessThanOf(getFieldAttribute("b"), THREE));
+        And and = new And(EMPTY, nullified, kept);
+
+        Expression optimized = new OptimizerRules.PropagateNullable().rule(and);
+        Expression expected = new And(EMPTY, new And(EMPTY, nullOf(BOOLEAN), nullOf(BOOLEAN)), kept);
+
+        assertEquals(Predicates.splitAnd(expected), Predicates.splitAnd(optimized));
+    }
+
+    // a IS NULL OR a IS NOT NULL => no change
+    // a IS NULL OR a > 1 => no change
+    public void testIsNullInDisjunction() {
+        FieldAttribute fa = getFieldAttribute();
+
+        Or or = new Or(EMPTY, new IsNull(EMPTY, fa), new IsNotNull(EMPTY, fa));
+        Filter dummy = new Filter(EMPTY, relation(), or);
+        LogicalPlan transformed = new OptimizerRules.PropagateNullable().apply(dummy);
+        assertSame(dummy, transformed);
+        assertEquals(or, ((Filter) transformed).condition());
+
+        or = new Or(EMPTY, new IsNull(EMPTY, fa), greaterThanOf(fa, ONE));
+        dummy = new Filter(EMPTY, relation(), or);
+        transformed = new OptimizerRules.PropagateNullable().apply(dummy);
+        assertSame(dummy, transformed);
+        assertEquals(or, ((Filter) transformed).condition());
+    }
+
+    // a + 1 AND (a IS NULL OR a > 3) => no change
+    public void testIsNullDisjunction() {
+        FieldAttribute fa = getFieldAttribute();
+        IsNull isNull = new IsNull(EMPTY, fa);
+
+        Or or = new Or(EMPTY, isNull, greaterThanOf(fa, THREE));
+        And and = new And(EMPTY, new Add(EMPTY, fa, ONE), or);
+
+        assertEquals(and, new OptimizerRules.PropagateNullable().rule(and));
+    }
+
+}

+ 99 - 0
x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/ReplaceRegexMatchTests.java

@@ -0,0 +1,99 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+package org.elasticsearch.xpack.esql.optimizer.rules;
+
+import org.elasticsearch.test.ESTestCase;
+import org.elasticsearch.xpack.esql.core.expression.Expression;
+import org.elasticsearch.xpack.esql.core.expression.FieldAttribute;
+import org.elasticsearch.xpack.esql.core.expression.predicate.nulls.IsNotNull;
+import org.elasticsearch.xpack.esql.core.expression.predicate.regex.Like;
+import org.elasticsearch.xpack.esql.core.expression.predicate.regex.LikePattern;
+import org.elasticsearch.xpack.esql.core.expression.predicate.regex.RLike;
+import org.elasticsearch.xpack.esql.core.expression.predicate.regex.RLikePattern;
+import org.elasticsearch.xpack.esql.core.expression.predicate.regex.WildcardLike;
+import org.elasticsearch.xpack.esql.core.expression.predicate.regex.WildcardPattern;
+import org.elasticsearch.xpack.esql.core.util.StringUtils;
+import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.Equals;
+
+import static java.util.Arrays.asList;
+import static org.elasticsearch.xpack.esql.EsqlTestUtils.getFieldAttribute;
+import static org.elasticsearch.xpack.esql.core.tree.Source.EMPTY;
+
+public class ReplaceRegexMatchTests extends ESTestCase {
+
+    public void testMatchAllLikeToExist() {
+        for (String s : asList("%", "%%", "%%%")) {
+            LikePattern pattern = new LikePattern(s, (char) 0);
+            FieldAttribute fa = getFieldAttribute();
+            Like l = new Like(EMPTY, fa, pattern);
+            Expression e = new ReplaceRegexMatch().rule(l);
+            assertEquals(IsNotNull.class, e.getClass());
+            IsNotNull inn = (IsNotNull) e;
+            assertEquals(fa, inn.field());
+        }
+    }
+
+    public void testMatchAllWildcardLikeToExist() {
+        for (String s : asList("*", "**", "***")) {
+            WildcardPattern pattern = new WildcardPattern(s);
+            FieldAttribute fa = getFieldAttribute();
+            WildcardLike l = new WildcardLike(EMPTY, fa, pattern);
+            Expression e = new ReplaceRegexMatch().rule(l);
+            assertEquals(IsNotNull.class, e.getClass());
+            IsNotNull inn = (IsNotNull) e;
+            assertEquals(fa, inn.field());
+        }
+    }
+
+    public void testMatchAllRLikeToExist() {
+        RLikePattern pattern = new RLikePattern(".*");
+        FieldAttribute fa = getFieldAttribute();
+        RLike l = new RLike(EMPTY, fa, pattern);
+        Expression e = new ReplaceRegexMatch().rule(l);
+        assertEquals(IsNotNull.class, e.getClass());
+        IsNotNull inn = (IsNotNull) e;
+        assertEquals(fa, inn.field());
+    }
+
+    public void testExactMatchLike() {
+        for (String s : asList("ab", "ab0%", "ab0_c")) {
+            LikePattern pattern = new LikePattern(s, '0');
+            FieldAttribute fa = getFieldAttribute();
+            Like l = new Like(EMPTY, fa, pattern);
+            Expression e = new ReplaceRegexMatch().rule(l);
+            assertEquals(Equals.class, e.getClass());
+            Equals eq = (Equals) e;
+            assertEquals(fa, eq.left());
+            assertEquals(s.replace("0", StringUtils.EMPTY), eq.right().fold());
+        }
+    }
+
+    public void testExactMatchWildcardLike() {
+        String s = "ab";
+        WildcardPattern pattern = new WildcardPattern(s);
+        FieldAttribute fa = getFieldAttribute();
+        WildcardLike l = new WildcardLike(EMPTY, fa, pattern);
+        Expression e = new ReplaceRegexMatch().rule(l);
+        assertEquals(Equals.class, e.getClass());
+        Equals eq = (Equals) e;
+        assertEquals(fa, eq.left());
+        assertEquals(s, eq.right().fold());
+    }
+
+    public void testExactMatchRLike() {
+        RLikePattern pattern = new RLikePattern("abc");
+        FieldAttribute fa = getFieldAttribute();
+        RLike l = new RLike(EMPTY, fa, pattern);
+        Expression e = new ReplaceRegexMatch().rule(l);
+        assertEquals(Equals.class, e.getClass());
+        Equals eq = (Equals) e;
+        assertEquals(fa, eq.left());
+        assertEquals("abc", eq.right().fold());
+    }
+
+}