>From Hussain Towaileb <[email protected]>:
Hussain Towaileb has uploaded this change for review. (
https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/21391?usp=email )
Change subject: Add support to Variant type
......................................................................
Add support to Variant type
Change-Id: Iaf8cbd6657cabcf15008501b53892a1432d873eb
---
M
asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/parser/IcebergParquetDataParser.java
1 file changed, 178 insertions(+), 35 deletions(-)
git pull ssh://asterix-gerrit.ics.uci.edu:29418/asterixdb
refs/changes/91/21391/1
diff --git
a/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/parser/IcebergParquetDataParser.java
b/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/parser/IcebergParquetDataParser.java
index 7ed1b24..665e464 100644
---
a/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/parser/IcebergParquetDataParser.java
+++
b/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/parser/IcebergParquetDataParser.java
@@ -36,7 +36,6 @@
import java.time.zone.ZoneRules;
import java.util.List;
import java.util.Map;
-import java.util.UUID;
import java.util.concurrent.TimeUnit;
import org.apache.asterix.builders.IARecordBuilder;
@@ -59,6 +58,7 @@
import org.apache.hyracks.api.exceptions.Warning;
import org.apache.hyracks.data.std.api.IMutableValueStorage;
import org.apache.hyracks.data.std.api.IValueReference;
+import org.apache.hyracks.util.annotations.AiProvenance;
import org.apache.iceberg.Schema;
import org.apache.iceberg.StructLike;
import org.apache.iceberg.data.Record;
@@ -66,6 +66,11 @@
import org.apache.iceberg.types.Types;
import org.apache.iceberg.types.Types.NestedField;
import org.apache.iceberg.types.Types.StructType;
+import org.apache.iceberg.variants.PhysicalType;
+import org.apache.iceberg.variants.Variant;
+import org.apache.iceberg.variants.VariantArray;
+import org.apache.iceberg.variants.VariantObject;
+import org.apache.iceberg.variants.VariantValue;
public class IcebergParquetDataParser extends AbstractDataParser implements
IRecordDataParser<Record> {
private final IcebergConverterContext parserContext;
@@ -126,15 +131,16 @@
parserContext.exitObject(valueBuffer, null, objectBuilder);
}
+ @AiProvenance(agent = AiProvenance.Agent.CLAUDE_SONNET_4_6, tool =
AiProvenance.Tool.CLAUDE_CODE_UI, contributionKind =
AiProvenance.ContributionKind.REFACTORED, notes = "Added VARIANT case")
private void parseValue(Type fieldType, Object value, DataOutput out)
throws IOException {
if (value == null) {
- nullSerde.serialize(ANull.NULL, out);
+ serializeNull(out);
return;
}
switch (fieldType.typeId()) {
case BOOLEAN:
- booleanSerde.serialize((Boolean) value ? ABoolean.TRUE :
ABoolean.FALSE, out);
+ serializeBoolean((Boolean) value, out);
return;
case INTEGER:
serializeInteger(value, out);
@@ -152,7 +158,7 @@
serializeString(value, out);
return;
case UUID:
- serializeUuid(value, out);
+ serializeUuid(value.toString(), out);
return;
case FIXED:
serializeFixedBinary(value, out);
@@ -185,9 +191,11 @@
case TIMESTAMP_NANO:
serializeTimestamp(fieldType, value, out);
return;
+ case VARIANT:
+ parseVariant((Variant) value, out);
+ return;
case GEOMETRY:
case GEOGRAPHY:
- case VARIANT:
case UNKNOWN:
default:
throw createUnsupportedException(fieldType);
@@ -197,10 +205,9 @@
private void parseArray(Types.ListType listType, List<?> listValues,
DataOutput out) throws IOException {
if (listValues == null) {
- nullSerde.serialize(ANull.NULL, out);
+ serializeNull(out);
return;
}
-
Type elementType = listType.elementType();
final IMutableValueStorage valueBuffer =
parserContext.enterCollection();
final IAsterixListBuilder arrayBuilder =
parserContext.getCollectionBuilder(NESTED_OPEN_AORDERED_LIST_TYPE);
@@ -259,6 +266,101 @@
parserContext.exitCollection(item, listBuilder);
}
+ @AiProvenance(agent = AiProvenance.Agent.CLAUDE_SONNET_4_6, tool =
AiProvenance.Tool.CLAUDE_CODE_UI, contributionKind =
AiProvenance.ContributionKind.GENERATED, notes = "Entry point for VARIANT
column: unwraps Iceberg Variant and delegates to parseVariantValue")
+ private void parseVariant(Variant variant, DataOutput out) throws
IOException {
+ parseVariantValue(variant.value(), out);
+ }
+
+ @AiProvenance(agent = AiProvenance.Agent.CLAUDE_SONNET_4_6, tool =
AiProvenance.Tool.CLAUDE_CODE_UI, contributionKind =
AiProvenance.ContributionKind.GENERATED, notes = "Dispatches on PhysicalType
and serializes each Variant primitive/object/array to the proper type")
+ private void parseVariantValue(VariantValue variantValue, DataOutput out)
throws IOException {
+ PhysicalType physicalType = variantValue.type();
+ switch (physicalType) {
+ case NULL:
+ serializeNull(out);
+ return;
+ case BOOLEAN_TRUE:
+ serializeBoolean(true, out);
+ return;
+ case BOOLEAN_FALSE:
+ serializeBoolean(false, out);
+ return;
+ case INT8:
+ case INT16:
+ case INT32:
+ case INT64:
+ serializeLong(((Number)
variantValue.asPrimitive().get()).longValue(), out);
+ return;
+ case FLOAT:
+ serializeFloat(variantValue.asPrimitive().get(), out);
+ return;
+ case DOUBLE:
+ serializeDouble(variantValue.asPrimitive().get(), out);
+ return;
+ case DECIMAL4:
+ case DECIMAL8:
+ case DECIMAL16:
+ serializeDecimal((BigDecimal)
variantValue.asPrimitive().get(), out);
+ return;
+ case STRING:
+ serializeString(variantValue.asPrimitive().get(), out);
+ return;
+ case BINARY:
+ serializeBinary(variantValue.asPrimitive().get(), out);
+ return;
+ case DATE:
+ serializeDateEpochDay((Integer)
variantValue.asPrimitive().get(), out);
+ return;
+ case TIME:
+ serializeTimeMicros((Long) variantValue.asPrimitive().get(),
out);
+ return;
+ case TIMESTAMPTZ:
+ case TIMESTAMPNTZ:
+ serializeTimestampMicros((Long)
variantValue.asPrimitive().get(), out);
+ return;
+ case TIMESTAMPTZ_NANOS:
+ case TIMESTAMPNTZ_NANOS:
+ serializeTimestampNanos((Long)
variantValue.asPrimitive().get(), out);
+ return;
+ case UUID:
+ serializeUuid((String) variantValue.asPrimitive().get(), out);
+ return;
+ case OBJECT:
+ parseVariantObject(variantValue.asObject(), out);
+ return;
+ case ARRAY:
+ parseVariantArray(variantValue.asArray(), out);
+ return;
+ default:
+ throw createVariantUnsupportedException(physicalType);
+ }
+ }
+
+ @AiProvenance(agent = AiProvenance.Agent.CLAUDE_SONNET_4_6, tool =
AiProvenance.Tool.CLAUDE_CODE_UI, contributionKind =
AiProvenance.ContributionKind.GENERATED, notes = "Serializes a VariantObject as
an open AsterixDB record")
+ private void parseVariantObject(VariantObject variantObject, DataOutput
out) throws IOException {
+ IMutableValueStorage valueBuffer = parserContext.enterObject();
+ IARecordBuilder objectBuilder =
parserContext.getObjectBuilder(DefaultOpenFieldType.NESTED_OPEN_RECORD_TYPE);
+ for (String fieldName : variantObject.fieldNames()) {
+ valueBuffer.reset();
+ parseVariantValue(variantObject.get(fieldName),
valueBuffer.getDataOutput());
+
objectBuilder.addField(parserContext.getSerializedFieldName(fieldName),
valueBuffer);
+ }
+ objectBuilder.write(out, true);
+ parserContext.exitObject(valueBuffer, null, objectBuilder);
+ }
+
+ @AiProvenance(agent = AiProvenance.Agent.CLAUDE_SONNET_4_6, tool =
AiProvenance.Tool.CLAUDE_CODE_UI, contributionKind =
AiProvenance.ContributionKind.GENERATED, notes = "Serializes a VariantArray as
an AsterixDB ordered list")
+ private void parseVariantArray(VariantArray variantArray, DataOutput out)
throws IOException {
+ final IMutableValueStorage valueBuffer =
parserContext.enterCollection();
+ final IAsterixListBuilder arrayBuilder =
parserContext.getCollectionBuilder(NESTED_OPEN_AORDERED_LIST_TYPE);
+ for (int i = 0; i < variantArray.numElements(); i++) {
+ valueBuffer.reset();
+ parseVariantValue(variantArray.get(i),
valueBuffer.getDataOutput());
+ arrayBuilder.addItem(valueBuffer);
+ }
+ arrayBuilder.write(out, true);
+ parserContext.exitCollection(valueBuffer, arrayBuilder);
+ }
+
private void parseValueAndAddObjectField(IMutableValueStorage valueBuffer,
IARecordBuilder objectBuilder,
Type valueType, String fieldName, Object fieldValue) throws
IOException {
ATypeTag typeTag = getTypeTag(valueType, fieldValue == null,
parserContext);
@@ -277,6 +379,14 @@
}
}
+ private void serializeNull(DataOutput out) throws HyracksDataException {
+ nullSerde.serialize(ANull.NULL, out);
+ }
+
+ private void serializeBoolean(boolean value, DataOutput out) throws
HyracksDataException {
+ booleanSerde.serialize(value ? ABoolean.TRUE : ABoolean.FALSE, out);
+ }
+
private void serializeInteger(Object value, DataOutput out) throws
HyracksDataException {
int intValue = (Integer) value;
aInt64.setValue(intValue);
@@ -301,11 +411,8 @@
doubleSerde.serialize(aDouble, out);
}
- private void serializeUuid(Object value, DataOutput out) throws
HyracksDataException {
- UUID uuid = (UUID) value;
- String uuidValue = uuid.toString();
- char[] buffer = uuidValue.toCharArray();
- aUUID.parseUUIDString(buffer, 0, uuidValue.length());
+ private void serializeUuid(String uuidStr, DataOutput out) throws
HyracksDataException {
+ aUUID.parseUUIDString(uuidStr.toCharArray(), 0, uuidStr.length());
uuidSerde.serialize(aUUID, out);
}
@@ -344,26 +451,38 @@
}
private void serializeDate(Object value, DataOutput output) throws
HyracksDataException {
- LocalDate localDate = (LocalDate) value;
+ serializeDateEpochDay((int) ((LocalDate) value).toEpochDay(), output);
+ }
+
+ @AiProvenance(agent = AiProvenance.Agent.CLAUDE_SONNET_4_6, tool =
AiProvenance.Tool.CLAUDE_CODE_UI, contributionKind =
AiProvenance.ContributionKind.GENERATED, notes = "Core date serializer
accepting days-since-epoch; shared by serializeDate and Variant DATE handling")
+ private void serializeDateEpochDay(int epochDay, DataOutput out) throws
HyracksDataException {
if (parserContext.isDateAsInt()) {
- serializeInteger((int) localDate.toEpochDay(), output);
+ serializeInteger(epochDay, out);
} else {
- aDate.setValue((int) localDate.toEpochDay());
- dateSerde.serialize(aDate, output);
+ aDate.setValue(epochDay);
+ dateSerde.serialize(aDate, out);
}
}
private void serializeTime(Object value, DataOutput output) throws
HyracksDataException {
- LocalTime localTime = (LocalTime) value;
- int timeInMillis = (int)
TimeUnit.NANOSECONDS.toMillis(localTime.toNanoOfDay());
+ serializeTimeMillis((int) TimeUnit.NANOSECONDS.toMillis(((LocalTime)
value).toNanoOfDay()), output);
+ }
+
+ @AiProvenance(agent = AiProvenance.Agent.CLAUDE_SONNET_4_6, tool =
AiProvenance.Tool.CLAUDE_CODE_UI, contributionKind =
AiProvenance.ContributionKind.GENERATED, notes = "Core time serializer
accepting milliseconds-of-day; shared by serializeTime and serializeTimeMicros")
+ private void serializeTimeMillis(int timeInMillis, DataOutput out) throws
HyracksDataException {
if (parserContext.isTimeAsInt()) {
- serializeInteger(timeInMillis, output);
+ serializeInteger(timeInMillis, out);
} else {
aTime.setValue(timeInMillis);
- timeSerde.serialize(aTime, output);
+ timeSerde.serialize(aTime, out);
}
}
+ @AiProvenance(agent = AiProvenance.Agent.CLAUDE_SONNET_4_6, tool =
AiProvenance.Tool.CLAUDE_CODE_UI, contributionKind =
AiProvenance.ContributionKind.GENERATED, notes = "Converts Variant TIME
(microseconds-of-day) to milliseconds and delegates to serializeTimeMillis")
+ private void serializeTimeMicros(long timeMicros, DataOutput out) throws
HyracksDataException {
+ serializeTimeMillis((int) TimeUnit.MICROSECONDS.toMillis(timeMicros),
out);
+ }
+
private void serializeTimestamp(Type type, Object value, DataOutput
output) throws HyracksDataException {
switch (value) {
case LocalDateTime localDateTime ->
serializeWallClockTimestamp(type, localDateTime, output);
@@ -378,12 +497,10 @@
throws HyracksDataException {
long epochSecond = localDateTime.toEpochSecond(ZoneOffset.UTC);
int nano = localDateTime.getNano();
- if (parserContext.isTimestampAsLong()) {
- long value = isTimestampNano(type) ? toNanos(epochSecond, nano) :
toMicros(epochSecond, nano);
- serializeLong(value, output);
+ if (isTimestampNano(type)) {
+ serializeTimestampNanos(toNanos(epochSecond, nano), output);
} else {
- aDateTime.setValue(toMillis(epochSecond, nano));
- datetimeSerde.serialize(aDateTime, output);
+ serializeTimestampMicros(toMicros(epochSecond, nano), output);
}
}
@@ -391,19 +508,40 @@
throws HyracksDataException {
long epochSecond = offsetDateTime.toEpochSecond();
int nano = offsetDateTime.getNano();
- if (parserContext.isTimestampAsLong()) {
- TimestampUnit unit = getTimestampUnit(type);
- long value = unit == TimestampUnit.NANOS ? toNanos(epochSecond,
nano) : toMicros(epochSecond, nano);
- value = timestampZoneProjector.projectEpochValue(value, unit);
- serializeLong(value, output);
+ if (isTimestampNano(type)) {
+ serializeTimestampNanos(
+
timestampZoneProjector.projectEpochValue(toNanos(epochSecond, nano),
TimestampUnit.NANOS), output);
} else {
- long epochMillis = toMillis(epochSecond, nano);
- epochMillis =
timestampZoneProjector.projectEpochMillis(epochMillis);
- aDateTime.setValue(epochMillis);
- datetimeSerde.serialize(aDateTime, output);
+ serializeTimestampMicros(
+
timestampZoneProjector.projectEpochValue(toMicros(epochSecond, nano),
TimestampUnit.MICROS),
+ output);
}
}
+ @AiProvenance(agent = AiProvenance.Agent.CLAUDE_SONNET_4_6, tool =
AiProvenance.Tool.CLAUDE_CODE_UI, contributionKind =
AiProvenance.ContributionKind.GENERATED, notes = "Handles isTimestampAsLong
flag for microsecond-precision timestamps; shared by Iceberg TIMESTAMP and
Variant TIMESTAMPTZ/TIMESTAMPNTZ")
+ private void serializeTimestampMicros(long epochMicros, DataOutput out)
throws HyracksDataException {
+ if (parserContext.isTimestampAsLong()) {
+ serializeLong(epochMicros, out);
+ } else {
+
serializeDatetimeMillis(TimeUnit.MICROSECONDS.toMillis(epochMicros), out);
+ }
+ }
+
+ @AiProvenance(agent = AiProvenance.Agent.CLAUDE_SONNET_4_6, tool =
AiProvenance.Tool.CLAUDE_CODE_UI, contributionKind =
AiProvenance.ContributionKind.GENERATED, notes = "Handles isTimestampAsLong
flag for nanosecond-precision timestamps; shared by Iceberg TIMESTAMP_NANO and
Variant TIMESTAMPTZ_NANOS/TIMESTAMPNTZ_NANOS")
+ private void serializeTimestampNanos(long epochNanos, DataOutput out)
throws HyracksDataException {
+ if (parserContext.isTimestampAsLong()) {
+ serializeLong(epochNanos, out);
+ } else {
+ serializeDatetimeMillis(TimeUnit.NANOSECONDS.toMillis(epochNanos),
out);
+ }
+ }
+
+ @AiProvenance(agent = AiProvenance.Agent.CLAUDE_SONNET_4_6, tool =
AiProvenance.Tool.CLAUDE_CODE_UI, contributionKind =
AiProvenance.ContributionKind.GENERATED, notes = "Core datetime serializer
accepting epoch milliseconds; shared by timestamp helpers and Variant TIMESTAMP
handling")
+ private void serializeDatetimeMillis(long epochMillis, DataOutput out)
throws HyracksDataException {
+ aDateTime.setValue(epochMillis);
+ datetimeSerde.serialize(aDateTime, out);
+ }
+
private static TimestampUnit getTimestampUnit(Type type) {
return isTimestampNano(type) ? TimestampUnit.NANOS :
TimestampUnit.MICROS;
}
@@ -444,6 +582,10 @@
return new RuntimeDataException(ErrorCode.TYPE_UNSUPPORTED, "Iceberg
Parser", type.toString());
}
+ private static HyracksDataException
createVariantUnsupportedException(PhysicalType type) {
+ return new RuntimeDataException(ErrorCode.TYPE_UNSUPPORTED, "Iceberg
Variant Parser", type.toString());
+ }
+
private static void ensureDecimalToDoubleEnabled(Type type,
IcebergConverterContext context)
throws RuntimeDataException {
if (!context.isDecimalToDoubleEnabled()) {
@@ -452,6 +594,7 @@
}
}
+ @AiProvenance(agent = AiProvenance.Agent.CLAUDE_SONNET_4_6, tool =
AiProvenance.Tool.CLAUDE_CODE_UI, contributionKind =
AiProvenance.ContributionKind.REFACTORED, notes = "Added VARIANT case")
private static ATypeTag getTypeTag(Type type, boolean isNull,
IcebergConverterContext parserContext)
throws HyracksDataException {
if (isNull) {
@@ -470,7 +613,7 @@
ensureDecimalToDoubleEnabled(type, parserContext);
yield ATypeTag.DOUBLE;
}
- case STRUCT -> ATypeTag.OBJECT;
+ case STRUCT, VARIANT -> ATypeTag.OBJECT;
case LIST, MAP -> ATypeTag.ARRAY;
case DATE -> {
if (parserContext.isDateAsInt()) {
--
To view, visit https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/21391?usp=email
To unsubscribe, or for help writing mail filters, visit
https://asterix-gerrit.ics.uci.edu/settings?usp=email
Gerrit-MessageType: newchange
Gerrit-Project: asterixdb
Gerrit-Branch: master
Gerrit-Change-Id: Iaf8cbd6657cabcf15008501b53892a1432d873eb
Gerrit-Change-Number: 21391
Gerrit-PatchSet: 1
Gerrit-Owner: Hussain Towaileb <[email protected]>