@Override public Expression negate() { return new BoundPredicate<>(op().negate(), ref(), literal()); } }
public <T> R predicate(BoundPredicate<T> pred) { switch (pred.op()) { case IS_NULL: return isNull(pred.ref()); case NOT_NULL: return notNull(pred.ref()); case LT: return lt(pred.ref(), pred.literal()); case LT_EQ: return ltEq(pred.ref(), pred.literal()); case GT: return gt(pred.ref(), pred.literal()); case GT_EQ: return gtEq(pred.ref(), pred.literal()); case EQ: return eq(pred.ref(), pred.literal()); case NOT_EQ: return notEq(pred.ref(), pred.literal()); case IN: return in(pred.ref(), pred.literal()); case NOT_IN: return notIn(pred.ref(), pred.literal()); default: throw new UnsupportedOperationException( "Unknown operation for predicate: " + pred.op()); } }
@Override public UnboundPredicate<Integer> projectStrict(String name, BoundPredicate<T> predicate) { switch (predicate.op()) { case NOT_EQ: // TODO: need to translate not(eq(...)) into notEq in expressions return Expressions.predicate(predicate.op(), name, apply(predicate.literal().value())); // case NOT_IN: // return null; default: // no strict projection for comparison or equality return null; } }
static <T> UnboundPredicate<T> truncateDecimal( String name, BoundPredicate<BigDecimal> pred, Transform<BigDecimal, T> transform) { BigDecimal boundary = pred.literal().value(); switch (pred.op()) { case LT: // adjust closed and then transform ltEq BigDecimal minusOne = new BigDecimal( boundary.unscaledValue().subtract(BigInteger.ONE), boundary.scale()); return predicate(Expression.Operation.LT_EQ, name, transform.apply(minusOne)); case LT_EQ: return predicate(Expression.Operation.LT_EQ, name, transform.apply(boundary)); case GT: // adjust closed and then transform gtEq BigDecimal plusOne = new BigDecimal( boundary.unscaledValue().add(BigInteger.ONE), boundary.scale()); return predicate(Expression.Operation.GT_EQ, name, transform.apply(plusOne)); case GT_EQ: return predicate(Expression.Operation.GT_EQ, name, transform.apply(boundary)); case EQ: return predicate(pred.op(), name, transform.apply(boundary)); default: return null; } }
@Override public UnboundPredicate<Integer> project(String name, BoundPredicate<T> predicate) { switch (predicate.op()) { case EQ: return Expressions.predicate( predicate.op(), name, apply(predicate.literal().value())); // case IN: // return Expressions.predicate(); default: // comparison predicates can't be projected, notEq can't be projected // TODO: small ranges can be projected. // for example, (x > 0) and (x < 3) can be turned into in({1, 2}) and projected. return null; } }
static <S, T> UnboundPredicate<T> truncateArray( String name, BoundPredicate<S> pred, Transform<S, T> transform) { S boundary = pred.literal().value(); switch (pred.op()) { case LT: case LT_EQ: return predicate(Expression.Operation.LT_EQ, name, transform.apply(boundary)); case GT: case GT_EQ: return predicate(Expression.Operation.GT_EQ, name, transform.apply(boundary)); case EQ: return predicate(Expression.Operation.EQ, name, transform.apply(boundary)); // case IN: // TODO // return Expressions.predicate(Operation.IN, name, transform.apply(boundary)); default: return null; } } }
static <T> UnboundPredicate<T> truncateInteger( String name, BoundPredicate<Integer> pred, Transform<Integer, T> transform) { int boundary = pred.literal().value(); switch (pred.op()) { case LT: // adjust closed and then transform ltEq return predicate(Expression.Operation.LT_EQ, name, transform.apply(boundary - 1)); case LT_EQ: return predicate(Expression.Operation.LT_EQ, name, transform.apply(boundary)); case GT: // adjust closed and then transform gtEq return predicate(Expression.Operation.GT_EQ, name, transform.apply(boundary + 1)); case GT_EQ: return predicate(Expression.Operation.GT_EQ, name, transform.apply(boundary)); case EQ: return predicate(pred.op(), name, transform.apply(boundary)); default: return null; } }
static <T> UnboundPredicate<T> truncateLong( String name, BoundPredicate<Long> pred, Transform<Long, T> transform) { long boundary = pred.literal().value(); switch (pred.op()) { case LT: // adjust closed and then transform ltEq return predicate(Expression.Operation.LT_EQ, name, transform.apply(boundary - 1L)); case LT_EQ: return predicate(Expression.Operation.LT_EQ, name, transform.apply(boundary)); case GT: // adjust closed and then transform gtEq return predicate(Expression.Operation.GT_EQ, name, transform.apply(boundary + 1L)); case GT_EQ: return predicate(Expression.Operation.GT_EQ, name, transform.apply(boundary)); case EQ: return predicate(pred.op(), name, transform.apply(boundary)); default: return null; } }
@Test @SuppressWarnings("unchecked") public void testComparisonPredicateBinding() { StructType struct = StructType.of(required(14, "x", Types.IntegerType.get())); for (Expression.Operation op : COMPARISONS) { UnboundPredicate<Integer> unbound = new UnboundPredicate<>(op, ref("x"), 5); Expression expr = unbound.bind(struct); BoundPredicate<Integer> bound = assertAndUnwrap(expr); Assert.assertEquals("Should not alter literal value", Integer.valueOf(5), bound.literal().value()); Assert.assertEquals("Should reference correct field ID", 14, bound.ref().fieldId()); Assert.assertEquals("Should not change the comparison operation", op, bound.op()); } }
@Test @SuppressWarnings("unchecked") public void testLiteralConversion() { StructType struct = StructType.of(required(15, "d", Types.DecimalType.of(9, 2))); for (Expression.Operation op : COMPARISONS) { UnboundPredicate<String> unbound = new UnboundPredicate<>(op, ref("d"), "12.40"); Expression expr = unbound.bind(struct); BoundPredicate<BigDecimal> bound = assertAndUnwrap(expr); Assert.assertEquals("Should convert literal value to decimal", new BigDecimal("12.40"), bound.literal().value()); Assert.assertEquals("Should reference correct field ID", 15, bound.ref().fieldId()); Assert.assertEquals("Should not change the comparison operation", op, bound.op()); } }
Operation op = pred.op(); BoundReference<T> ref = pred.ref(); Literal<T> lit = pred.literal(); String path = schema.idToAlias(ref.fieldId());
@Test @SuppressWarnings("unchecked") public void testMultipleFields() { StructType struct = StructType.of( required(10, "x", Types.IntegerType.get()), required(11, "y", Types.IntegerType.get()), required(12, "z", Types.IntegerType.get()) ); UnboundPredicate<Integer> unbound = new UnboundPredicate<>(LT, ref("y"), 6); Expression expr = unbound.bind(struct); BoundPredicate<Integer> bound = assertAndUnwrap(expr); Assert.assertEquals("Should reference correct field ID", 11, bound.ref().fieldId()); Assert.assertEquals("Should not change the comparison operation", LT, bound.op()); Assert.assertEquals("Should not alter literal value", Integer.valueOf(6), bound.literal().value()); }
@Test public void testNotNull() { StructType optional = StructType.of(optional(21, "s", Types.StringType.get())); UnboundPredicate<?> unbound = new UnboundPredicate<>(NOT_NULL, ref("s")); Expression expr = unbound.bind(optional); BoundPredicate<?> bound = assertAndUnwrap(expr); Assert.assertEquals("Should use the same operation", NOT_NULL, bound.op()); Assert.assertEquals("Should use the correct field", 21, bound.ref().fieldId()); Assert.assertNull("Should not have a literal value", bound.literal()); StructType required = StructType.of(required(22, "s", Types.StringType.get())); Assert.assertEquals("NotNull inclusive a required field should be alwaysTrue", Expressions.alwaysTrue(), unbound.bind(required)); } }
@Test @SuppressWarnings("unchecked") public void testIsNull() { StructType optional = StructType.of(optional(19, "s", Types.StringType.get())); UnboundPredicate<?> unbound = new UnboundPredicate<>(IS_NULL, ref("s")); Expression expr = unbound.bind(optional); BoundPredicate<?> bound = assertAndUnwrap(expr); Assert.assertEquals("Should use the same operation", IS_NULL, bound.op()); Assert.assertEquals("Should use the correct field", 19, bound.ref().fieldId()); Assert.assertNull("Should not have a literal value", bound.literal()); StructType required = StructType.of(required(20, "s", Types.StringType.get())); Assert.assertEquals("IsNull inclusive a required field should be alwaysFalse", Expressions.alwaysFalse(), unbound.bind(required)); }
BoundPredicate<Float> ltMax = assertAndUnwrap(ltExpr); Assert.assertEquals("Should translate bound to Float", (Float) Float.MAX_VALUE, ltMax.literal().value()); BoundPredicate<Float> lteqMax = assertAndUnwrap(lteqExpr); Assert.assertEquals("Should translate bound to Float", (Float) Float.MAX_VALUE, lteqMax.literal().value()); Float.valueOf(-Float.MAX_VALUE), gtMin.literal().value()); BoundPredicate<Float> gteqMin = assertAndUnwrap(gteqExpr); Assert.assertEquals("Should translate bound to Float", Float.valueOf(-Float.MAX_VALUE), gteqMin.literal().value());
BoundPredicate<Integer> ltMax = assertAndUnwrap(ltExpr); Assert.assertEquals("Should translate bound to Integer", (Integer) Integer.MAX_VALUE, ltMax.literal().value()); BoundPredicate<Integer> lteqMax = assertAndUnwrap(lteqExpr); Assert.assertEquals("Should translate bound to Integer", (Integer) Integer.MAX_VALUE, lteqMax.literal().value()); (Integer) Integer.MIN_VALUE, gtMin.literal().value()); BoundPredicate<Integer> gteqMin = assertAndUnwrap(gteqExpr); Assert.assertEquals("Should translate bound to Integer", (Integer) Integer.MIN_VALUE, gteqMin.literal().value());