This is an automated email from the ASF dual-hosted git repository. raminqaf pushed a commit to branch release-2.4 in repository https://gitbox.apache.org/repos/asf/flink.git
commit 85d807e130ff06d04ceee76625682df8beb826d8 Author: Ramin Gharib <[email protected]> AuthorDate: Tue Oct 27 13:20:44 2026 +0100 [FLINK-41241][table] Use VariantFieldGetter for v['key'] and CAST to ROW The generated code for v['literal'] keeps one VariantFieldGetter per call site as a reusable member, and the VARIANT to ROW and STRUCTURED cast keeps one per field. The key now reaches the generated code inside the serialized getter, so it no longer needs escaping. A key that is not a literal passes the UTF-8 bytes of its StringData to BinaryVariant#getFieldByUtf8Key instead of decoding them into a String. The getter assumes a BinaryVariant, like the row writer and the serializer. A UDF that returns another Variant implementation now fails with a ClassCastException on v['k'] in the same projection. Generated-by: Claude Code 2.1.311 (Claude Opus 5.5) (cherry picked from commit 43a9d4bc31931e3808cbf9d879d1af503815c1e4) --- .../apache/flink/table/data/utils/FromVariantConverter.java | 5 ++++- .../table/planner/codegen/calls/ScalarOperatorGens.scala | 13 ++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/flink-table/flink-table-common/src/main/java/org/apache/flink/table/data/utils/FromVariantConverter.java b/flink-table/flink-table-common/src/main/java/org/apache/flink/table/data/utils/FromVariantConverter.java index f612be4bf34..e2909990a75 100644 --- a/flink-table/flink-table-common/src/main/java/org/apache/flink/table/data/utils/FromVariantConverter.java +++ b/flink-table/flink-table-common/src/main/java/org/apache/flink/table/data/utils/FromVariantConverter.java @@ -33,6 +33,7 @@ import org.apache.flink.table.types.logical.utils.LogicalTypeChecks; import org.apache.flink.table.types.logical.utils.LogicalTypeUtils; import org.apache.flink.types.variant.BinaryVariant; import org.apache.flink.types.variant.Variant; +import org.apache.flink.types.variant.VariantFieldGetter; import org.apache.flink.types.variant.VariantTypeException; import java.io.Serializable; @@ -254,8 +255,10 @@ public final class FromVariantConverter implements Serializable { final String[] fieldNames = LogicalTypeChecks.getFieldNames(type).toArray(new String[0]); final int fieldCount = fieldTypes.size(); final String rowType = type.asSummaryString(); + final VariantFieldGetter[] fieldGetters = new VariantFieldGetter[fieldCount]; final ValueReader[] fieldReaders = new ValueReader[fieldCount]; for (int i = 0; i < fieldCount; i++) { + fieldGetters[i] = new VariantFieldGetter(fieldNames[i]); fieldReaders[i] = createChildReader( fieldTypes.get(i), @@ -268,7 +271,7 @@ public final class FromVariantConverter implements Serializable { final GenericRowData row = new GenericRowData(fieldCount); for (int i = 0; i < fieldCount; i++) { // getField returns null for a field the object does not have. - final Variant field = variant.getField(fieldNames[i]); + final Variant field = fieldGetters[i].getField(variant); if (field == null) { throw new TableRuntimeException(missingFieldError(rowType, fieldNames[i])); } diff --git a/flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/codegen/calls/ScalarOperatorGens.scala b/flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/codegen/calls/ScalarOperatorGens.scala index b3dcd9b00bf..891f9c4b19a 100644 --- a/flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/codegen/calls/ScalarOperatorGens.scala +++ b/flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/codegen/calls/ScalarOperatorGens.scala @@ -40,8 +40,8 @@ import org.apache.flink.table.types.logical.utils.LogicalTypeChecks import org.apache.flink.table.types.logical.utils.LogicalTypeChecks.{getFieldTypes, getPrecision, getScale} import org.apache.flink.table.types.logical.utils.LogicalTypeMerging.findCommonType import org.apache.flink.table.utils.DateTimeUtils.MILLIS_PER_DAY -import org.apache.flink.table.utils.EncodingUtils import org.apache.flink.types.ColumnList +import org.apache.flink.types.variant.{BinaryVariant, VariantFieldGetter} import org.apache.flink.util.Preconditions.checkArgument import java.time.ZoneId @@ -1749,10 +1749,13 @@ object ScalarOperatorGens { } else if (isCharacterString(keyType)) { val fieldAccess = key.literalValue match { case Some(fieldName) if fieldName != null => - s"""$variantTerm.getField("${EncodingUtils.escapeJava(fieldName.toString)}")""" - // A key that is not a literal, or a NULL literal, whose access code never runs. The key is - // a StringData, and getField takes a String. - case _ => s"$variantTerm.getField($keyTerm.toString())" + val getterTerm = + ctx.addReusableObject(new VariantFieldGetter(fieldName.toString), "variantFieldGetter") + s"$getterTerm.getField($variantTerm)" + // A key that is not a literal, or a NULL literal, whose access code never runs. The + // lookup compares the UTF-8 bytes of the StringData key without decoding them. + case _ => + s"((${className[BinaryVariant]}) $variantTerm).getFieldByUtf8Key($keyTerm.toBytes())" } generateCharacterStringKeyAccess( variantTerm,
