diff --git a/api/src/main/java/org/apache/iceberg/expressions/Literals.java b/api/src/main/java/org/apache/iceberg/expressions/Literals.java index 44c8b8be86d0..483c2a1a7a79 100644 --- a/api/src/main/java/org/apache/iceberg/expressions/Literals.java +++ b/api/src/main/java/org/apache/iceberg/expressions/Literals.java @@ -35,10 +35,12 @@ import java.util.Objects; import java.util.UUID; import org.apache.iceberg.relocated.com.google.common.base.Preconditions; +import org.apache.iceberg.relocated.com.google.common.io.BaseEncoding; import org.apache.iceberg.types.Comparators; import org.apache.iceberg.types.Conversions; import org.apache.iceberg.types.Type; import org.apache.iceberg.types.Types; +import org.apache.iceberg.util.ByteBuffers; import org.apache.iceberg.util.NaNUtil; class Literals { @@ -599,6 +601,12 @@ protected Type.TypeID typeId() { Object writeReplace() throws ObjectStreamException { return new SerializationProxies.FixedLiteralProxy(value()); } + + @Override + public String toString() { + byte[] bytes = ByteBuffers.toByteArray(value()); + return "X'" + BaseEncoding.base16().encode(bytes) + "'"; + } } static class BinaryLiteral extends BaseLiteral { @@ -639,5 +647,11 @@ Object writeReplace() throws ObjectStreamException { protected Type.TypeID typeId() { return Type.TypeID.BINARY; } + + @Override + public String toString() { + byte[] bytes = ByteBuffers.toByteArray(value()); + return "X'" + BaseEncoding.base16().encode(bytes) + "'"; + } } } diff --git a/spark/v3.2/spark/src/main/java/org/apache/iceberg/spark/Spark3Util.java b/spark/v3.2/spark/src/main/java/org/apache/iceberg/spark/Spark3Util.java index 04ac3384ee6e..56dec3eaf79e 100644 --- a/spark/v3.2/spark/src/main/java/org/apache/iceberg/spark/Spark3Util.java +++ b/spark/v3.2/spark/src/main/java/org/apache/iceberg/spark/Spark3Util.java @@ -49,6 +49,7 @@ import org.apache.iceberg.relocated.com.google.common.collect.ImmutableMap; import org.apache.iceberg.relocated.com.google.common.collect.ImmutableSet; import org.apache.iceberg.relocated.com.google.common.collect.Maps; +import org.apache.iceberg.relocated.com.google.common.io.BaseEncoding; import org.apache.iceberg.spark.SparkTableUtil.SparkPartition; import org.apache.iceberg.spark.source.SparkTable; import org.apache.iceberg.transforms.PartitionSpecVisitor; @@ -56,6 +57,7 @@ import org.apache.iceberg.types.Type; import org.apache.iceberg.types.TypeUtil; import org.apache.iceberg.types.Types; +import org.apache.iceberg.util.ByteBuffers; import org.apache.iceberg.util.Pair; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Row; @@ -598,7 +600,8 @@ private static String sqlString(org.apache.iceberg.expressions.Literal lit) { if (lit.value() instanceof String) { return "'" + lit.value() + "'"; } else if (lit.value() instanceof ByteBuffer) { - throw new IllegalArgumentException("Cannot convert bytes to SQL literal: " + lit); + byte[] bytes = ByteBuffers.toByteArray((ByteBuffer) lit.value()); + return "X'" + BaseEncoding.base16().encode(bytes) + "'"; } else { return lit.value().toString(); } diff --git a/spark/v3.2/spark/src/test/java/org/apache/iceberg/spark/SparkTestBase.java b/spark/v3.2/spark/src/test/java/org/apache/iceberg/spark/SparkTestBase.java index 93ceee3a27ee..1a2fa6d296e4 100644 --- a/spark/v3.2/spark/src/test/java/org/apache/iceberg/spark/SparkTestBase.java +++ b/spark/v3.2/spark/src/test/java/org/apache/iceberg/spark/SparkTestBase.java @@ -157,7 +157,11 @@ private void assertEquals(String context, Object[] expectedRow, Object[] actualR Object actualValue = actualRow[col]; if (expectedValue != null && expectedValue.getClass().isArray()) { String newContext = String.format("%s (nested col %d)", context, col + 1); - assertEquals(newContext, (Object[]) expectedValue, (Object[]) actualValue); + if (expectedValue instanceof byte[]) { + Assert.assertArrayEquals(newContext, (byte[]) expectedValue, (byte[]) actualValue); + } else { + assertEquals(newContext, (Object[]) expectedValue, (Object[]) actualValue); + } } else if (expectedValue != ANY) { Assert.assertEquals(context + " contents should match", expectedValue, actualValue); } diff --git a/spark/v3.2/spark/src/test/java/org/apache/iceberg/spark/sql/TestSelect.java b/spark/v3.2/spark/src/test/java/org/apache/iceberg/spark/sql/TestSelect.java index 38dfe0e5afda..e9ac21d2df39 100644 --- a/spark/v3.2/spark/src/test/java/org/apache/iceberg/spark/sql/TestSelect.java +++ b/spark/v3.2/spark/src/test/java/org/apache/iceberg/spark/sql/TestSelect.java @@ -40,6 +40,7 @@ public class TestSelect extends SparkCatalogTestBase { private int scanEventCount = 0; private ScanEvent lastScanEvent = null; + private String binaryTableName = tableName("binary_table"); public TestSelect(String catalogName, String implementation, Map config) { super(catalogName, implementation, config); @@ -63,6 +64,7 @@ public void createTables() { @After public void removeTables() { sql("DROP TABLE IF EXISTS %s", tableName); + sql("DROP TABLE IF EXISTS %s", binaryTableName); } @Test @@ -203,4 +205,14 @@ public void testSpecifySnapshotAndTimestamp() { .collectAsList(); }); } + + @Test + public void testBinaryInFilter() { + sql("CREATE TABLE %s (id bigint, binary binary) USING iceberg", binaryTableName); + sql("INSERT INTO %s VALUES (1, X''), (2, X'1111'), (3, X'11')", binaryTableName); + List expected = ImmutableList.of(row(2L, new byte[]{0x11, 0x11})); + + assertEquals("Should return all expected rows", expected, + sql("SELECT id, binary FROM %s where binary > X'11'", binaryTableName)); + } }