This is an automated email from the ASF dual-hosted git repository.

blue pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iceberg.git


The following commit(s) were added to refs/heads/master by this push:
     new c801a2c  Hive: Add filter pushdown support (#1326)
c801a2c is described below

commit c801a2c15715f6d33c3b26eca1c2f495c3cb73b5
Author: cmathiesen <[email protected]>
AuthorDate: Tue Sep 1 02:27:30 2020 +0100

    Hive: Add filter pushdown support (#1326)
---
 .../java/org/apache/iceberg/util/DateTimeUtil.java |   8 +
 .../iceberg/mr/hive/HiveIcebergFilterFactory.java  | 179 +++++++++++++++
 .../iceberg/mr/hive/HiveIcebergInputFormat.java    |  25 +++
 .../mr/hive/TestHiveIcebergFilterFactory.java      | 250 +++++++++++++++++++++
 4 files changed, 462 insertions(+)

diff --git a/core/src/main/java/org/apache/iceberg/util/DateTimeUtil.java 
b/core/src/main/java/org/apache/iceberg/util/DateTimeUtil.java
index 9f1e26e..13a75db 100644
--- a/core/src/main/java/org/apache/iceberg/util/DateTimeUtil.java
+++ b/core/src/main/java/org/apache/iceberg/util/DateTimeUtil.java
@@ -42,6 +42,10 @@ public class DateTimeUtil {
     return (int) ChronoUnit.DAYS.between(EPOCH_DAY, date);
   }
 
+  public static int daysFromInstant(Instant instant) {
+    return (int) ChronoUnit.DAYS.between(EPOCH, 
instant.atOffset(ZoneOffset.UTC));
+  }
+
   public static LocalTime timeFromMicros(long microFromMidnight) {
     return LocalTime.ofNanoOfDay(microFromMidnight * 1000);
   }
@@ -54,6 +58,10 @@ public class DateTimeUtil {
     return ChronoUnit.MICROS.addTo(EPOCH, microsFromEpoch).toLocalDateTime();
   }
 
+  public static long microsFromInstant(Instant instant) {
+    return ChronoUnit.MICROS.between(EPOCH, instant.atOffset(ZoneOffset.UTC));
+  }
+
   public static long microsFromTimestamp(LocalDateTime dateTime) {
     return ChronoUnit.MICROS.between(EPOCH, dateTime.atOffset(ZoneOffset.UTC));
   }
diff --git 
a/mr/src/main/java/org/apache/iceberg/mr/hive/HiveIcebergFilterFactory.java 
b/mr/src/main/java/org/apache/iceberg/mr/hive/HiveIcebergFilterFactory.java
new file mode 100644
index 0000000..63e823c
--- /dev/null
+++ b/mr/src/main/java/org/apache/iceberg/mr/hive/HiveIcebergFilterFactory.java
@@ -0,0 +1,179 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iceberg.mr.hive;
+
+import java.math.BigDecimal;
+import java.sql.Date;
+import java.sql.Timestamp;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.apache.hadoop.hive.ql.io.sarg.ExpressionTree;
+import org.apache.hadoop.hive.ql.io.sarg.PredicateLeaf;
+import org.apache.hadoop.hive.ql.io.sarg.SearchArgument;
+import org.apache.hadoop.hive.ql.io.sarg.SearchArgumentImpl;
+import org.apache.hadoop.hive.serde2.io.HiveDecimalWritable;
+import org.apache.iceberg.common.DynFields;
+import org.apache.iceberg.expressions.Expression;
+import org.apache.iceberg.expressions.Expressions;
+import org.apache.iceberg.util.DateTimeUtil;
+
+import static org.apache.iceberg.expressions.Expressions.and;
+import static org.apache.iceberg.expressions.Expressions.equal;
+import static org.apache.iceberg.expressions.Expressions.greaterThanOrEqual;
+import static org.apache.iceberg.expressions.Expressions.in;
+import static org.apache.iceberg.expressions.Expressions.isNull;
+import static org.apache.iceberg.expressions.Expressions.lessThan;
+import static org.apache.iceberg.expressions.Expressions.lessThanOrEqual;
+import static org.apache.iceberg.expressions.Expressions.not;
+import static org.apache.iceberg.expressions.Expressions.or;
+
+
+public class HiveIcebergFilterFactory {
+
+  private HiveIcebergFilterFactory() {
+  }
+
+  public static Expression generateFilterExpression(SearchArgument sarg) {
+    return translate(sarg.getExpression(), sarg.getLeaves());
+  }
+
+  /**
+   * Recursive method to traverse down the ExpressionTree to evaluate each 
expression and its leaf nodes.
+   * @param tree Current ExpressionTree where the 'top' node is being 
evaluated.
+   * @param leaves List of all leaf nodes within the tree.
+   * @return Expression that is translated from the Hive SearchArgument.
+   */
+  private static Expression translate(ExpressionTree tree, List<PredicateLeaf> 
leaves) {
+    List<ExpressionTree> childNodes = tree.getChildren();
+    switch (tree.getOperator()) {
+      case OR:
+        Expression orResult = Expressions.alwaysFalse();
+        for (ExpressionTree child : childNodes) {
+          orResult = or(orResult, translate(child, leaves));
+        }
+        return orResult;
+      case AND:
+        Expression result = Expressions.alwaysTrue();
+        for (ExpressionTree child : childNodes) {
+          result = and(result, translate(child, leaves));
+        }
+        return result;
+      case NOT:
+        return not(translate(childNodes.get(0), leaves));
+      case LEAF:
+        return translateLeaf(leaves.get(tree.getLeaf()));
+      case CONSTANT:
+        throw new UnsupportedOperationException("CONSTANT operator is not 
supported");
+      default:
+        throw new UnsupportedOperationException("Unknown operator: " + 
tree.getOperator());
+    }
+  }
+
+  /**
+   * Translate leaf nodes from Hive operator to Iceberg operator.
+   * @param leaf Leaf node
+   * @return Expression fully translated from Hive PredicateLeaf
+   */
+  private static Expression translateLeaf(PredicateLeaf leaf) {
+    String column = leaf.getColumnName();
+    switch (leaf.getOperator()) {
+      case EQUALS:
+        return equal(column, leafToLiteral(leaf));
+      case LESS_THAN:
+        return lessThan(column, leafToLiteral(leaf));
+      case LESS_THAN_EQUALS:
+        return lessThanOrEqual(column, leafToLiteral(leaf));
+      case IN:
+        return in(column, leafToLiteralList(leaf));
+      case BETWEEN:
+        List<Object> icebergLiterals = leafToLiteralList(leaf);
+        return and(greaterThanOrEqual(column, icebergLiterals.get(0)),
+                lessThanOrEqual(column, icebergLiterals.get(1)));
+      case IS_NULL:
+        return isNull(column);
+      default:
+        throw new UnsupportedOperationException("Unknown operator: " + 
leaf.getOperator());
+    }
+  }
+
+  // PredicateLeafImpl has a work-around for Kryo serialization with 
java.util.Date objects where it converts values to
+  // Timestamp using Date#getTime. This conversion discards microseconds, so 
this is a necessary to avoid it.
+  private static final DynFields.UnboundField<?> LITERAL_FIELD = 
DynFields.builder()
+      .hiddenImpl(SearchArgumentImpl.PredicateLeafImpl.class, "literal")
+      .build();
+
+  private static Object leafToLiteral(PredicateLeaf leaf) {
+    switch (leaf.getType()) {
+      case LONG:
+      case BOOLEAN:
+      case STRING:
+      case FLOAT:
+        return leaf.getLiteral();
+      case DATE:
+        return daysFromTimestamp((Timestamp) leaf.getLiteral());
+      case TIMESTAMP:
+        return microsFromTimestamp((Timestamp) LITERAL_FIELD.get(leaf));
+      case DECIMAL:
+        return hiveDecimalToBigDecimal((HiveDecimalWritable) 
leaf.getLiteral());
+
+      default:
+        throw new UnsupportedOperationException("Unknown type: " + 
leaf.getType());
+    }
+  }
+
+  private static List<Object> leafToLiteralList(PredicateLeaf leaf) {
+    switch (leaf.getType()) {
+      case LONG:
+      case BOOLEAN:
+      case FLOAT:
+      case STRING:
+        return leaf.getLiteralList();
+      case DATE:
+        return leaf.getLiteralList().stream().map(value -> daysFromDate((Date) 
value))
+                .collect(Collectors.toList());
+      case DECIMAL:
+        return leaf.getLiteralList().stream()
+                .map(value -> hiveDecimalToBigDecimal((HiveDecimalWritable) 
value))
+                .collect(Collectors.toList());
+      case TIMESTAMP:
+        return leaf.getLiteralList().stream()
+                .map(value -> microsFromTimestamp((Timestamp) value))
+                .collect(Collectors.toList());
+      default:
+        throw new UnsupportedOperationException("Unknown type: " + 
leaf.getType());
+    }
+  }
+
+  private static BigDecimal hiveDecimalToBigDecimal(HiveDecimalWritable 
hiveDecimalWritable) {
+    return 
hiveDecimalWritable.getHiveDecimal().bigDecimalValue().setScale(hiveDecimalWritable.scale());
+  }
+
+  private static int daysFromDate(Date date) {
+    return DateTimeUtil.daysFromDate(date.toLocalDate());
+  }
+
+  private static int daysFromTimestamp(Timestamp timestamp) {
+    return DateTimeUtil.daysFromInstant(timestamp.toInstant());
+  }
+
+  private static long microsFromTimestamp(Timestamp timestamp) {
+    return DateTimeUtil.microsFromInstant(timestamp.toInstant());
+  }
+}
diff --git 
a/mr/src/main/java/org/apache/iceberg/mr/hive/HiveIcebergInputFormat.java 
b/mr/src/main/java/org/apache/iceberg/mr/hive/HiveIcebergInputFormat.java
index a7da368..a8a31d2 100644
--- a/mr/src/main/java/org/apache/iceberg/mr/hive/HiveIcebergInputFormat.java
+++ b/mr/src/main/java/org/apache/iceberg/mr/hive/HiveIcebergInputFormat.java
@@ -23,19 +23,44 @@ import java.io.IOException;
 import java.util.Arrays;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.hive.ql.exec.SerializationUtilities;
 import org.apache.hadoop.hive.ql.io.CombineHiveInputFormat;
+import org.apache.hadoop.hive.ql.io.sarg.ConvertAstToSearchArg;
+import org.apache.hadoop.hive.ql.io.sarg.SearchArgument;
+import org.apache.hadoop.hive.ql.plan.ExprNodeGenericFuncDesc;
+import org.apache.hadoop.hive.ql.plan.TableScanDesc;
 import org.apache.hadoop.mapred.InputSplit;
 import org.apache.hadoop.mapred.JobConf;
 import org.apache.iceberg.data.Record;
+import org.apache.iceberg.expressions.Expression;
 import org.apache.iceberg.mr.InputFormatConfig;
+import org.apache.iceberg.mr.SerializationUtil;
 import org.apache.iceberg.mr.mapred.MapredIcebergInputFormat;
 import org.apache.iceberg.mr.mapreduce.IcebergSplit;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public class HiveIcebergInputFormat extends MapredIcebergInputFormat<Record>
                                     implements 
CombineHiveInputFormat.AvoidSplitCombination {
 
+  private static final Logger LOG = 
LoggerFactory.getLogger(HiveIcebergInputFormat.class);
+
   @Override
   public InputSplit[] getSplits(JobConf job, int numSplits) throws IOException 
{
+    // Convert Hive filter to Iceberg filter
+    String hiveFilter = job.get(TableScanDesc.FILTER_EXPR_CONF_STR);
+    if (hiveFilter != null) {
+      ExprNodeGenericFuncDesc exprNodeDesc = SerializationUtilities
+              .deserializeObject(hiveFilter, ExprNodeGenericFuncDesc.class);
+      SearchArgument sarg = ConvertAstToSearchArg.create(job, exprNodeDesc);
+      try {
+        Expression filter = 
HiveIcebergFilterFactory.generateFilterExpression(sarg);
+        job.set(InputFormatConfig.FILTER_EXPRESSION, 
SerializationUtil.serializeToBase64(filter));
+      } catch (UnsupportedOperationException e) {
+        LOG.warn("Unable to create Iceberg filter, continuing without filter 
(will be applied by Hive later): ", e);
+      }
+    }
+
     String location = job.get(InputFormatConfig.TABLE_LOCATION);
     return Arrays.stream(super.getSplits(job, numSplits))
                  .map(split -> new HiveIcebergSplit((IcebergSplit) split, 
location))
diff --git 
a/mr/src/test/java/org/apache/iceberg/mr/hive/TestHiveIcebergFilterFactory.java 
b/mr/src/test/java/org/apache/iceberg/mr/hive/TestHiveIcebergFilterFactory.java
new file mode 100644
index 0000000..5dc3273
--- /dev/null
+++ 
b/mr/src/test/java/org/apache/iceberg/mr/hive/TestHiveIcebergFilterFactory.java
@@ -0,0 +1,250 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iceberg.mr.hive;
+
+import java.math.BigDecimal;
+import java.sql.Date;
+import java.sql.Timestamp;
+import java.time.ZoneOffset;
+import org.apache.hadoop.hive.ql.io.sarg.PredicateLeaf;
+import org.apache.hadoop.hive.ql.io.sarg.SearchArgument;
+import org.apache.hadoop.hive.ql.io.sarg.SearchArgumentFactory;
+import org.apache.hadoop.hive.serde2.io.HiveDecimalWritable;
+import org.apache.iceberg.expressions.And;
+import org.apache.iceberg.expressions.Expressions;
+import org.apache.iceberg.expressions.Literal;
+import org.apache.iceberg.expressions.Not;
+import org.apache.iceberg.expressions.Or;
+import org.apache.iceberg.expressions.UnboundPredicate;
+import org.apache.iceberg.types.Types;
+import org.apache.iceberg.util.DateTimeUtil;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class TestHiveIcebergFilterFactory {
+
+  @Test
+  public void testEqualsOperand() {
+    SearchArgument.Builder builder = SearchArgumentFactory.newBuilder();
+    SearchArgument arg = builder.startAnd().equals("salary", 
PredicateLeaf.Type.LONG, 3000L).end().build();
+
+    UnboundPredicate expected = Expressions.equal("salary", 3000L);
+    UnboundPredicate actual = (UnboundPredicate) 
HiveIcebergFilterFactory.generateFilterExpression(arg);
+
+    assertPredicatesMatch(expected, actual);
+  }
+
+  @Test
+  public void testNotEqualsOperand() {
+    SearchArgument.Builder builder = SearchArgumentFactory.newBuilder();
+    SearchArgument arg = builder.startNot().equals("salary", 
PredicateLeaf.Type.LONG, 3000L).end().build();
+
+    Not expected = (Not) Expressions.not(Expressions.equal("salary", 3000L));
+    Not actual = (Not) HiveIcebergFilterFactory.generateFilterExpression(arg);
+
+    UnboundPredicate childExpressionActual = (UnboundPredicate) actual.child();
+    UnboundPredicate childExpressionExpected = Expressions.equal("salary", 
3000L);
+
+    assertEquals(actual.op(), expected.op());
+    assertEquals(actual.child().op(), expected.child().op());
+    assertEquals(childExpressionActual.ref().name(), 
childExpressionExpected.ref().name());
+    assertEquals(childExpressionActual.literal(), 
childExpressionExpected.literal());
+  }
+
+  @Test
+  public void testLessThanOperand() {
+    SearchArgument.Builder builder = SearchArgumentFactory.newBuilder();
+    SearchArgument arg = builder.startAnd().lessThan("salary", 
PredicateLeaf.Type.LONG, 3000L).end().build();
+
+    UnboundPredicate expected = Expressions.lessThan("salary", 3000L);
+    UnboundPredicate actual = (UnboundPredicate) 
HiveIcebergFilterFactory.generateFilterExpression(arg);
+
+    assertEquals(actual.op(), expected.op());
+    assertEquals(actual.literal(), expected.literal());
+    assertEquals(actual.ref().name(), expected.ref().name());
+  }
+
+  @Test
+  public void testLessThanEqualsOperand() {
+    SearchArgument.Builder builder = SearchArgumentFactory.newBuilder();
+    SearchArgument arg = builder.startAnd().lessThanEquals("salary", 
PredicateLeaf.Type.LONG, 3000L).end().build();
+
+    UnboundPredicate expected = Expressions.lessThanOrEqual("salary", 3000L);
+    UnboundPredicate actual = (UnboundPredicate) 
HiveIcebergFilterFactory.generateFilterExpression(arg);
+
+    assertPredicatesMatch(expected, actual);
+  }
+
+  @Test
+  public void testInOperand() {
+    SearchArgument.Builder builder = SearchArgumentFactory.newBuilder();
+    SearchArgument arg = builder.startAnd().in("salary", 
PredicateLeaf.Type.LONG, 3000L, 4000L).end().build();
+
+    UnboundPredicate expected = Expressions.in("salary", 3000L, 4000L);
+    UnboundPredicate actual = (UnboundPredicate) 
HiveIcebergFilterFactory.generateFilterExpression(arg);
+
+    assertEquals(actual.op(), expected.op());
+    assertEquals(actual.literals(), expected.literals());
+    assertEquals(actual.ref().name(), expected.ref().name());
+  }
+
+  @Test
+  public void testBetweenOperand() {
+    SearchArgument.Builder builder = SearchArgumentFactory.newBuilder();
+    SearchArgument arg = builder
+        .startAnd()
+        .between("salary", PredicateLeaf.Type.LONG, 3000L, 
4000L).end().build();
+
+    And expected = (And) 
Expressions.and(Expressions.greaterThanOrEqual("salary", 3000L),
+        Expressions.lessThanOrEqual("salary", 3000L));
+    And actual = (And) HiveIcebergFilterFactory.generateFilterExpression(arg);
+
+    assertEquals(actual.op(), expected.op());
+    assertEquals(actual.left().op(), expected.left().op());
+    assertEquals(actual.right().op(), expected.right().op());
+  }
+
+  @Test
+  public void testIsNullOperand() {
+    SearchArgument.Builder builder = SearchArgumentFactory.newBuilder();
+    SearchArgument arg = builder.startAnd().isNull("salary", 
PredicateLeaf.Type.LONG).end().build();
+
+    UnboundPredicate expected = Expressions.isNull("salary");
+    UnboundPredicate actual = (UnboundPredicate) 
HiveIcebergFilterFactory.generateFilterExpression(arg);
+
+    assertEquals(actual.op(), expected.op());
+    assertEquals(actual.ref().name(), expected.ref().name());
+  }
+
+  @Test
+  public void testAndOperand() {
+    SearchArgument.Builder builder = SearchArgumentFactory.newBuilder();
+    SearchArgument arg = builder
+        .startAnd()
+        .equals("salary", PredicateLeaf.Type.LONG, 3000L)
+        .equals("salary", PredicateLeaf.Type.LONG, 4000L)
+        .end().build();
+
+    And expected = (And) Expressions
+        .and(Expressions.equal("salary", 3000L), Expressions.equal("salary", 
4000L));
+    And actual = (And) HiveIcebergFilterFactory.generateFilterExpression(arg);
+
+    assertEquals(actual.op(), expected.op());
+    assertEquals(actual.left().op(), expected.left().op());
+    assertEquals(actual.right().op(), expected.right().op());
+  }
+
+  @Test
+  public void testOrOperand() {
+    SearchArgument.Builder builder = SearchArgumentFactory.newBuilder();
+    SearchArgument arg = builder
+        .startOr()
+        .equals("salary", PredicateLeaf.Type.LONG, 3000L)
+        .equals("salary", PredicateLeaf.Type.LONG, 4000L)
+        .end().build();
+
+    Or expected = (Or) Expressions
+        .or(Expressions.equal("salary", 3000L), Expressions.equal("salary", 
4000L));
+    Or actual = (Or) HiveIcebergFilterFactory.generateFilterExpression(arg);
+
+    assertEquals(actual.op(), expected.op());
+    assertEquals(actual.left().op(), expected.left().op());
+    assertEquals(actual.right().op(), expected.right().op());
+  }
+
+  @Test
+  public void testStringType() {
+    SearchArgument.Builder builder = SearchArgumentFactory.newBuilder();
+    SearchArgument arg = builder.startAnd().equals("string", 
PredicateLeaf.Type.STRING, "Joe").end().build();
+
+    UnboundPredicate expected = Expressions.equal("string", "Joe");
+    UnboundPredicate actual = (UnboundPredicate) 
HiveIcebergFilterFactory.generateFilterExpression(arg);
+
+    assertPredicatesMatch(expected, actual);
+  }
+
+  @Test
+  public void testFloatType() {
+    SearchArgument.Builder builder = SearchArgumentFactory.newBuilder();
+    SearchArgument arg = builder.startAnd().equals("float", 
PredicateLeaf.Type.FLOAT, 1200D).end().build();
+
+    UnboundPredicate expected = Expressions.equal("float", 1200D);
+    UnboundPredicate actual = (UnboundPredicate) 
HiveIcebergFilterFactory.generateFilterExpression(arg);
+
+    assertPredicatesMatch(expected, actual);
+  }
+
+  @Test
+  public void testBooleanType() {
+    SearchArgument.Builder builder = SearchArgumentFactory.newBuilder();
+    SearchArgument arg = builder.startAnd().equals("boolean", 
PredicateLeaf.Type.BOOLEAN, true).end().build();
+
+    UnboundPredicate expected = Expressions.equal("boolean", true);
+    UnboundPredicate actual = (UnboundPredicate) 
HiveIcebergFilterFactory.generateFilterExpression(arg);
+
+    assertPredicatesMatch(expected, actual);
+  }
+
+  @Test
+  public void testDateType() {
+    SearchArgument.Builder builder = SearchArgumentFactory.newBuilder();
+    SearchArgument arg = builder.startAnd().equals("date", 
PredicateLeaf.Type.DATE,
+            Date.valueOf("2015-11-12")).end().build();
+
+    UnboundPredicate expected = Expressions.equal("date", 
Literal.of("2015-11-12").to(Types.DateType.get()).value());
+    UnboundPredicate actual = (UnboundPredicate) 
HiveIcebergFilterFactory.generateFilterExpression(arg);
+
+    assertPredicatesMatch(expected, actual);
+  }
+
+  @Test
+  public void testTimestampType() {
+    Literal<Long> timestampLiteral = 
Literal.of("2012-10-02T05:16:17.123456").to(Types.TimestampType.withoutZone());
+    long timestampMicros = timestampLiteral.value();
+    Timestamp ts = 
Timestamp.from(DateTimeUtil.timestampFromMicros(timestampMicros).toInstant(ZoneOffset.UTC));
+
+    SearchArgument.Builder builder = SearchArgumentFactory.newBuilder();
+    SearchArgument arg = builder.startAnd().equals("timestamp", 
PredicateLeaf.Type.TIMESTAMP, ts).end().build();
+
+    UnboundPredicate expected = Expressions.equal("timestamp", 
timestampMicros);
+    UnboundPredicate actual = (UnboundPredicate) 
HiveIcebergFilterFactory.generateFilterExpression(arg);
+
+    assertPredicatesMatch(expected, actual);
+  }
+
+  @Test
+  public void testDecimalType() {
+    SearchArgument.Builder builder = SearchArgumentFactory.newBuilder();
+    SearchArgument arg = builder.startAnd().equals("decimal", 
PredicateLeaf.Type.DECIMAL,
+            new HiveDecimalWritable("20.12")).end().build();
+
+    UnboundPredicate expected = Expressions.equal("decimal", new 
BigDecimal("20.12"));
+    UnboundPredicate actual = (UnboundPredicate) 
HiveIcebergFilterFactory.generateFilterExpression(arg);
+
+    assertPredicatesMatch(expected, actual);
+  }
+
+  private void assertPredicatesMatch(UnboundPredicate expected, 
UnboundPredicate actual) {
+    assertEquals(expected.op(), actual.op());
+    assertEquals(expected.literal(), actual.literal());
+    assertEquals(expected.ref().name(), actual.ref().name());
+  }
+}

Reply via email to