diff --git a/api/src/main/java/org/apache/iceberg/VariantLike.java b/api/src/main/java/org/apache/iceberg/VariantLike.java index 5dd579ab6964..7e4c2a3ec81f 100644 --- a/api/src/main/java/org/apache/iceberg/VariantLike.java +++ b/api/src/main/java/org/apache/iceberg/VariantLike.java @@ -18,8 +18,6 @@ */ package org.apache.iceberg; -import java.math.BigDecimal; - /** * Interface for accessing Variant fields. * @@ -28,25 +26,11 @@ public interface VariantLike { int size(); - VariantLike getFieldByKey(String key); - - VariantLike getFieldAtIndex(int index); - - boolean getBoolean(); - - int getInt(); - - long getLong(); - - float getFloat(); - - double getDouble(); - - BigDecimal getDecimal(); - - String getString(); + // To access the value of the root element based on the type of provided javaClass. + T get(Class javaClass); - byte[] getBinary(); + // To access the sub-element for the provided path. + VariantLike get(String[] path); String toJson(); } diff --git a/api/src/test/java/org/apache/iceberg/TestAccessors.java b/api/src/test/java/org/apache/iceberg/TestAccessors.java index 25a425bbc815..177b150ed332 100644 --- a/api/src/test/java/org/apache/iceberg/TestAccessors.java +++ b/api/src/test/java/org/apache/iceberg/TestAccessors.java @@ -22,8 +22,12 @@ import static org.apache.iceberg.types.Types.NestedField.required; import static org.assertj.core.api.Assertions.assertThat; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import java.math.BigDecimal; import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.Base64; import java.util.UUID; import org.apache.iceberg.TestHelpers.JsonVariant; import org.apache.iceberg.TestHelpers.Row; @@ -250,8 +254,67 @@ public void testEmptySchema() { } @Test - public void testVariant() { - VariantLike variant = JsonVariant.of("{\"name\":\"John\",\"age\":30}"); + public void testVariant() throws JsonProcessingException { + Base64.Encoder encoder = Base64.getEncoder(); + boolean expectedTrue = true, expectedFalse = false; + int expectedInt = 2147483647; + long expectedLong = 2147483648L; + float expectedFloat = 1.2345f; + double expectedDouble = 1.23456; + BigDecimal expectedDecimal = new BigDecimal(123456); + String expectedString = "abc"; + String expectedBytes = + new String(encoder.encode(expectedString.getBytes()), StandardCharsets.UTF_8); + int nestInt = 10; + + String json = + "{\"false\":" + + expectedFalse + + ", \"true\":" + + expectedTrue + + ", \"string\": \"" + + expectedString + + "\"," + + "\"int\":" + + expectedInt + + "," + + "\"long\":" + + expectedLong + + ", \"float\":" + + expectedFloat + + "," + + "\"double\":" + + expectedDouble + + ", \"bytes\":\"" + + expectedBytes + + "\", \"decimal\":" + + expectedDecimal + + "," + + "\"nest1\": {\"nest2\":" + + nestInt + + "}" + + "}"; + + VariantLike variant = JsonVariant.of(json); assertAccessorReturns(Types.VariantType.get(), variant); + + assertThat(variant.get(new String[] {"true"}).get(Boolean.class)).isEqualTo(expectedTrue); + assertThat(variant.get(new String[] {"false"}).get(Boolean.class)).isEqualTo(expectedFalse); + assertThat(variant.get(new String[] {"string"}).get(String.class)).isEqualTo(expectedString); + assertThat(variant.get(new String[] {"int"}).get(Integer.class)).isEqualTo(expectedInt); + assertThat(variant.get(new String[] {"long"}).get(Long.class)).isEqualTo(expectedLong); + assertThat(variant.get(new String[] {"float"}).get(Float.class)).isEqualTo(expectedFloat); + assertThat(variant.get(new String[] {"double"}).get(Double.class)).isEqualTo(expectedDouble); + assertThat(variant.get(new String[] {"decimal"}).get(BigDecimal.class)) + .isEqualTo(expectedDecimal); + assertThat( + StandardCharsets.UTF_8 + .decode(variant.get(new String[] {"bytes"}).get(ByteBuffer.class)) + .toString()) + .isEqualTo(expectedString); + assertThat(variant.get(new String[] {"nest1", "nest2"}).get(Integer.class)).isEqualTo(nestInt); + assertThat(variant.get(new String[] {"nest1", "invalid"})).isNull(); + assertThat(new ObjectMapper().readTree(variant.toJson())) + .isEqualTo(new ObjectMapper().readTree(json)); } } diff --git a/api/src/test/java/org/apache/iceberg/TestHelpers.java b/api/src/test/java/org/apache/iceberg/TestHelpers.java index 7517a1552560..68cd4e4caabd 100644 --- a/api/src/test/java/org/apache/iceberg/TestHelpers.java +++ b/api/src/test/java/org/apache/iceberg/TestHelpers.java @@ -433,59 +433,43 @@ public int size() { } @Override - public VariantLike getFieldByKey(String key) { - JsonNode childNode = node.get(key); - return new JsonVariant(childNode); - } - - @Override - public VariantLike getFieldAtIndex(int index) { - JsonNode childNode = node.get(index); - return new JsonVariant(childNode); - } - - @Override - public boolean getBoolean() { - return node.asBoolean(); - } - - @Override - public int getInt() { - return node.asInt(); - } - - @Override - public long getLong() { - return node.asLong(); - } - - @Override - public float getFloat() { - return (float) node.asDouble(); - } - - @Override - public double getDouble() { - return node.asDouble(); - } - - @Override - public BigDecimal getDecimal() { - return new BigDecimal(node.asText()); - } + public T get(Class javaClass) { + if (javaClass.equals(Boolean.class)) { + return (T) (Boolean) node.asBoolean(); + } else if (javaClass.equals(Integer.class)) { + return (T) (Integer) node.asInt(); + } else if (javaClass.equals(Long.class)) { + return (T) (Long) node.asLong(); + } else if (javaClass.equals(Float.class)) { + return (T) Float.valueOf((float) node.asDouble()); + } else if (javaClass.equals(Double.class)) { + return (T) (Double) (node.asDouble()); + } else if (CharSequence.class.isAssignableFrom(javaClass)) { + return (T) node.asText(); + } else if (javaClass.equals(ByteBuffer.class)) { + try { + return (T) ByteBuffer.wrap(node.binaryValue()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } else if (javaClass.equals(BigDecimal.class)) { + return (T) node.decimalValue(); + } - @Override - public String getString() { - return node.asText(); + throw new IllegalArgumentException("Unsupported type: " + javaClass); } @Override - public byte[] getBinary() { - try { - return node.binaryValue(); - } catch (IOException e) { - return null; + public VariantLike get(String[] path) { + JsonNode childNode = node; + for (String pathElement : path) { + childNode = childNode.get(pathElement); + if (childNode == null) { + return null; + } } + + return new JsonVariant(childNode); } @Override diff --git a/api/src/test/java/org/apache/iceberg/util/RandomUtil.java b/api/src/test/java/org/apache/iceberg/util/RandomUtil.java index 973fe9eeae9c..9131e6166133 100644 --- a/api/src/test/java/org/apache/iceberg/util/RandomUtil.java +++ b/api/src/test/java/org/apache/iceberg/util/RandomUtil.java @@ -22,7 +22,6 @@ import java.math.BigInteger; import java.util.Arrays; import java.util.Random; -import org.apache.iceberg.TestHelpers.JsonVariant; import org.apache.iceberg.types.Type; import org.apache.iceberg.types.Types; @@ -145,9 +144,6 @@ public static Object generatePrimitive(Type.PrimitiveType primitive, Random rand BigDecimal bigDecimal = new BigDecimal(unscaled, type.scale()); return negate(choice) ? bigDecimal.negate() : bigDecimal; - case VARIANT: - return randomVariant(); - default: throw new IllegalArgumentException( "Cannot generate random value for unknown type: " + primitive); @@ -229,8 +225,4 @@ private static BigInteger randomUnscaled(int precision, Random random) { return new BigInteger(sb.toString()); } - - private static JsonVariant randomVariant() { - return JsonVariant.of("{\"name\": \"John\"}"); - } }