twalthr commented on code in PR #26655:
URL: https://github.com/apache/flink/pull/26655#discussion_r2158342521


##########
flink-core/src/main/java/org/apache/flink/types/variant/Variant.java:
##########
@@ -0,0 +1,216 @@
+/*
+ * 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.flink.types.variant;
+
+import org.apache.flink.annotation.PublicEvolving;
+
+import java.math.BigDecimal;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
+/** Variant represent a semi-structured data. */
+@PublicEvolving
+public interface Variant {
+
+    /** Returns true if the variant is a primitive typed value, such as INT, 
DOUBLE, STRING, etc. */
+    boolean isPrimitive();
+
+    /** Returns true if this variant is an Array, false otherwise. */
+    boolean isArray();
+
+    /** Returns true if this variant is an Object, false otherwise. */
+    boolean isObject();
+
+    /** Check If this variant is null. */
+    boolean isNull();
+
+    /** Get the type of variant. */
+    Type getType();
+
+    /**
+     * Get the scalar value of variant as boolean, if the variant type is 
{@link Type#BOOLEAN}.
+     *
+     * @throws VariantTypeException If this variant is not a scalar value or 
is not {@link
+     *     Type#BOOLEAN}.
+     */
+    boolean getBoolean() throws VariantTypeException;
+
+    /**
+     * Get the scalar value of variant as byte, if the variant type is {@link 
Type#TINYINT}.
+     *
+     * @throws VariantTypeException If this variant is not a scalar value or 
is not {@link
+     *     Type#TINYINT}.
+     */
+    byte getByte() throws VariantTypeException;
+
+    /**
+     * Get the scalar value of variant as short, if the variant type is {@link 
Type#SMALLINT}.
+     *
+     * @throws VariantTypeException If this variant is not a scalar value or 
is not {@link
+     *     Type#SMALLINT}.
+     */
+    short getShort() throws VariantTypeException;
+
+    /**
+     * Get the scalar value of variant as int, if the variant type is {@link 
Type#INT}.
+     *
+     * @throws VariantTypeException if this variant is not a scalar value or 
is not {@link
+     *     Type#INT}.
+     */
+    int getInt() throws VariantTypeException;
+
+    /**
+     * Get the scalar value of variant as long, if the variant type is {@link 
Type#BIGINT}.
+     *
+     * @throws VariantTypeException If this variant is not a scalar value or 
is not {@link
+     *     Type#BIGINT}.
+     */
+    long getLong() throws VariantTypeException;
+
+    /**
+     * Get the scalar value of variant as float, if the variant type is {@link 
Type#FLOAT}.
+     *
+     * @throws VariantTypeException If this variant is not a scalar value or 
is not {@link
+     *     Type#FLOAT}.
+     */
+    float getFloat() throws VariantTypeException;
+
+    /**
+     * Get the scalar value of variant as BigDecimal, if the variant type is 
{@link Type#DECIMAL}.
+     *
+     * @throws VariantTypeException If this variant is not a scalar value or 
is not {@link
+     *     Type#DECIMAL}.
+     */
+    BigDecimal getDecimal() throws VariantTypeException;
+
+    /**
+     * Get the scalar value of variant as double, if the variant type is 
{@link Type#DOUBLE}.
+     *
+     * @throws VariantTypeException If this variant is not a scalar value or 
is not {@link
+     *     Type#DOUBLE}.
+     */
+    double getDouble() throws VariantTypeException;
+
+    /**
+     * Get the scalar value of variant as string, if the variant type is 
{@link Type#STRING}.
+     *
+     * @throws VariantTypeException If this variant is not a scalar value or 
is not {@link
+     *     Type#STRING}.
+     */
+    String getString() throws VariantTypeException;
+
+    /**
+     * Get the scalar value of variant as LocalDate, if the variant type is 
{@link Type#DATE}.
+     *
+     * @throws VariantTypeException If this variant is not a scalar value or 
is not {@link
+     *     Type#DATE}.
+     */
+    LocalDate getDate() throws VariantTypeException;
+
+    /**
+     * Get the scalar value of variant as LocalDateTime, if the variant type 
is {@link
+     * Type#TIMESTAMP}.
+     *
+     * @throws VariantTypeException If this variant is not a scalar value or 
is not {@link
+     *     Type#TIMESTAMP}.
+     */
+    LocalDateTime getDateTime() throws VariantTypeException;
+
+    /**
+     * Get the scalar value of variant as Instant, if the variant type is 
{@link Type#TIMESTAMP}.
+     *
+     * @throws VariantTypeException If this variant is not a scalar value or 
is not {@link
+     *     Type#TIMESTAMP}.
+     */
+    Instant getInstant() throws VariantTypeException;
+
+    /**
+     * Get the scalar value of variant as byte array, if the variant type is 
{@link Type#BINARY}.
+     *
+     * @throws VariantTypeException If this variant is not a scalar value or 
is not {@link
+     *     Type#BINARY}.
+     */
+    byte[] getBytes() throws VariantTypeException;
+
+    /**
+     * Get the scalar value of variant.
+     *
+     * @throws VariantTypeException If this variant is not a scalar value.
+     */
+    Object get() throws VariantTypeException;
+
+    /**
+     * Get the scalar value of variant.
+     *
+     * @throws VariantTypeException If this variant is not a scalar value.
+     */
+    <T> T getAs() throws VariantTypeException;
+
+    /**
+     * Access value of the specified element of an array variant. If index is 
out of range, null is
+     * returned.
+     *
+     * <p>NOTE: if the element value has been explicitly set as 
<code>null</code> (which is
+     * different from removal!), a variant that @{@link Variant#isNull()} 
returns true will be
+     * returned, not null.
+     *
+     * @throws VariantTypeException If this variant is not an array.
+     */
+    Variant getElement(int index) throws VariantTypeException;
+
+    /**
+     * Access value of the specified field of an object variant. If there is 
no field with the
+     * specified name, null is returned.
+     *
+     * <p>NOTE: if the property value has been explicitly set as 
<code>null</code>, a variant
+     * that @{@link Variant#isNull()} returns true will be returned, not null.
+     *
+     * @throws VariantTypeException If this variant is not an object.
+     */
+    Variant getField(String fieldName) throws VariantTypeException;
+
+    /** Parses the variant to json. */
+    String toJson();
+
+    /** The type of variant. */
+    @PublicEvolving
+    enum Type {
+        OBJECT,
+        ARRAY,
+        NULL,
+        BOOLEAN,
+        TINYINT,
+        SMALLINT,
+        INT,
+        BIGINT,
+        FLOAT,
+        DOUBLE,
+        DECIMAL,
+        STRING,

Review Comment:
   It seems we want to be consistent with SQL in naming here. So we should use 
`VARCHAR`. Otherwise, I would suggest to rename `BINARY` to `BYTES`.
   
   So either `STRING, BYTES` (closer to Java).
   So either `VARCHAR, VARBINARY` (closer to SQL standard).
   
   I would vote for `STRING, BYTES`. As we also use those synonyms in SQL 
examples.



##########
flink-core/src/test/java/org/apache/flink/api/common/typeutils/base/VariantSerializerTest.java:
##########
@@ -0,0 +1,60 @@
+/*
+ * 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.flink.api.common.typeutils.base;
+
+import org.apache.flink.api.common.typeutils.SerializerTestBase;
+import org.apache.flink.api.common.typeutils.TypeSerializer;
+import org.apache.flink.types.variant.Variant;
+import org.apache.flink.types.variant.VariantBuilder;
+
+class VariantSerializerTest extends SerializerTestBase<Variant> {
+
+    @Override
+    protected TypeSerializer<Variant> createSerializer() {
+        return VariantSerializer.INSTANCE;
+    }
+
+    @Override
+    protected int getLength() {
+        return -1;
+    }
+
+    @Override
+    protected Class<Variant> getTypeClass() {
+        return Variant.class;
+    }
+
+    @Override
+    protected Variant[] getTestData() {
+        VariantBuilder builder = Variant.builder();
+        return new Variant[] {

Review Comment:
   add one or two more different variant into the array to test reusing 
behavior. we often had cases where serializing the first value left some state 
that influenced the following serialized value.



##########
flink-core/src/main/java/org/apache/flink/types/variant/BinaryVariantUtil.java:
##########
@@ -0,0 +1,631 @@
+/*
+ * 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.flink.types.variant;
+
+import org.apache.flink.annotation.Internal;
+import org.apache.flink.types.variant.Variant.Type;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeFormatterBuilder;
+import java.util.Arrays;
+import java.util.Locale;
+
+/* This file is based on source code from the Spark Project 
(http://spark.apache.org/), licensed by the Apache
+ * Software Foundation (ASF) under the Apache License, Version 2.0. See the 
NOTICE file distributed with this work for
+ * additional information regarding copyright ownership. */
+
+/**
+ * This class defines constants related to the variant format and provides 
functions for
+ * manipulating variant binaries.
+ *
+ * <p>A variant is made up of 2 binaries: value and metadata. A variant value 
consists of a one-byte
+ * header and a number of content bytes (can be zero). The header byte is 
divided into upper 6 bits
+ * (called "type info") and lower 2 bits (called "basic type"). The content 
format is explained in
+ * the below constants for all possible basic type and type info values.
+ *
+ * <p>The variant metadata includes a version id and a dictionary of distinct 
strings
+ * (case-sensitive). Its binary format is: - Version: 1-byte unsigned integer. 
The only acceptable
+ * value is 1 currently. - Dictionary size: 4-byte little-endian unsigned 
integer. The number of
+ * keys in the dictionary. - Offsets: (size + 1) * 4-byte little-endian 
unsigned integers.
+ * `offsets[i]` represents the starting position of string i, counting 
starting from the address of
+ * `offsets[0]`. Strings must be stored contiguously, so we don’t need to 
store the string size,
+ * instead, we compute it with `offset[i + 1] - offset[i]`. - UTF-8 string 
data.
+ */
+@Internal
+public class BinaryVariantUtil {
+    public static final int BASIC_TYPE_BITS = 2;
+    public static final int BASIC_TYPE_MASK = 0x3;
+    public static final int TYPE_INFO_MASK = 0x3F;
+    // The inclusive maximum value of the type info value. It is the size 
limit of `SHORT_STR`.
+    public static final int MAX_SHORT_STR_SIZE = 0x3F;
+
+    // Below is all possible basic type values.
+    // Primitive value. The type info value must be one of the values in the 
below section.
+    public static final int PRIMITIVE = 0;
+    // Short string value. The type info value is the string size, which must 
be in `[0,
+    // kMaxShortStrSize]`.
+    // The string content bytes directly follow the header byte.
+    public static final int SHORT_STR = 1;
+    // Object value. The content contains a size, a list of field ids, a list 
of field offsets, and
+    // the actual field data. The length of the id list is `size`, while the 
length of the offset
+    // list is `size + 1`, where the last offset represent the total size of 
the field data. The
+    // fields in an object must be sorted by the field name in alphabetical 
order. Duplicate field
+    // names in one object are not allowed.
+    // We use 5 bits in the type info to specify the integer type of the 
object header: it should
+    // be 0_b4_b3b2_b1b0 (MSB is 0), where:
+    // - b4 specifies the type of size. When it is 0/1, `size` is a 
little-endian 1/4-byte
+    // unsigned integer.
+    // - b3b2/b1b0 specifies the integer type of id and offset. When the 2 
bits are  0/1/2, the
+    // list contains 1/2/3-byte little-endian unsigned integers.
+    public static final int OBJECT = 2;
+    // Array value. The content contains a size, a list of field offsets, and 
the actual element
+    // data. It is similar to an object without the id list. The length of the 
offset list
+    // is `size + 1`, where the last offset represent the total size of the 
element data.
+    // Its type info should be: 000_b2_b1b0:
+    // - b2 specifies the type of size.
+    // - b1b0 specifies the integer type of offset.
+    public static final int ARRAY = 3;
+
+    // Below is all possible type info values for `PRIMITIVE`.
+    // JSON Null value. Empty content.
+    public static final int NULL = 0;
+    // True value. Empty content.
+    public static final int TRUE = 1;
+    // False value. Empty content.
+    public static final int FALSE = 2;
+    // 1-byte little-endian signed integer.
+    public static final int INT1 = 3;
+    // 2-byte little-endian signed integer.
+    public static final int INT2 = 4;
+    // 4-byte little-endian signed integer.
+    public static final int INT4 = 5;
+    // 4-byte little-endian signed integer.
+    public static final int INT8 = 6;
+    // 8-byte IEEE double.
+    public static final int DOUBLE = 7;
+    // 4-byte decimal. Content is 1-byte scale + 4-byte little-endian signed 
integer.
+    public static final int DECIMAL4 = 8;
+    // 8-byte decimal. Content is 1-byte scale + 8-byte little-endian signed 
integer.
+    public static final int DECIMAL8 = 9;
+    // 16-byte decimal. Content is 1-byte scale + 16-byte little-endian signed 
integer.
+    public static final int DECIMAL16 = 10;
+    // Date value. Content is 4-byte little-endian signed integer that 
represents the number of days
+    // from the Unix epoch.
+    public static final int DATE = 11;
+    // Timestamp value. Content is 8-byte little-endian signed integer that 
represents the number of
+    // microseconds elapsed since the Unix epoch, 1970-01-01 00:00:00 UTC. It 
is displayed to users
+    // in
+    // their local time zones and may be displayed differently depending on 
the execution
+    // environment.
+    public static final int TIMESTAMP = 12;
+    // Timestamp_ntz value. It has the same content as `TIMESTAMP` but should 
always be interpreted
+    // as if the local time zone is UTC.
+    public static final int TIMESTAMP_NTZ = 13;
+    // 4-byte IEEE float.
+    public static final int FLOAT = 14;
+    // Binary value. The content is (4-byte little-endian unsigned integer 
representing the binary
+    // size) + (size bytes of binary content).
+    public static final int BINARY = 15;
+    // Long string value. The content is (4-byte little-endian unsigned 
integer representing the
+    // string size) + (size bytes of string content).
+    public static final int LONG_STR = 16;
+
+    public static final byte VERSION = 1;
+    // The lower 4 bits of the first metadata byte contain the version.
+    public static final byte VERSION_MASK = 0x0F;
+
+    public static final int U8_MAX = 0xFF;
+    public static final int U16_MAX = 0xFFFF;
+    public static final int U24_MAX = 0xFFFFFF;
+    public static final int U24_SIZE = 3;
+    public static final int U32_SIZE = 4;
+
+    // Both variant value and variant metadata need to be no longer than 16MiB.
+    public static final int SIZE_LIMIT = U24_MAX + 1;
+
+    public static final int MAX_DECIMAL4_PRECISION = 9;
+    public static final int MAX_DECIMAL8_PRECISION = 18;
+    public static final int MAX_DECIMAL16_PRECISION = 38;
+
+    public static final int BINARY_SEARCH_THRESHOLD = 32;
+
+    public static final DateTimeFormatter TIMESTAMP_FORMATTER =
+            new DateTimeFormatterBuilder()
+                    .append(DateTimeFormatter.ISO_LOCAL_DATE)
+                    .appendLiteral(' ')

Review Comment:
   I just double checked ISO-8601. I wasn't aware that LTZ can be represented 
both as `Z` or `+00:00`.



##########
flink-table/flink-table-common/src/main/java/org/apache/flink/table/api/DataTypes.java:
##########
@@ -976,6 +977,10 @@ public static <T> DataType STRUCTURED(Class<T> 
implementationClass, Field... fie
         return new FieldsDataType(builder.build(), implementationClass, 
fieldDataTypes);
     }
 
+    public static DataType VARIANT() {

Review Comment:
   I still think that more doc is needed. Esp. helping people to distinguish 
between ROW, STRUCTURED, and VARIANT. But this can be a follow up. Happy to 
help here as well in shaping the docs.



##########
flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/plan/nodes/exec/stream/VariantSemanticTest.java:
##########
@@ -0,0 +1,304 @@
+/*
+ * 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.flink.table.planner.plan.nodes.exec.stream;
+
+import org.apache.flink.table.api.DataTypes;
+import org.apache.flink.table.api.TableRuntimeException;
+import org.apache.flink.table.functions.AggregateFunction;
+import org.apache.flink.table.functions.ScalarFunction;
+import 
org.apache.flink.table.planner.plan.nodes.exec.testutils.SemanticTestBase;
+import org.apache.flink.table.test.program.SinkTestStep;
+import org.apache.flink.table.test.program.SourceTestStep;
+import org.apache.flink.table.test.program.TableTestProgram;
+import org.apache.flink.types.Row;
+import org.apache.flink.types.RowKind;
+import org.apache.flink.types.variant.Variant;
+import org.apache.flink.types.variant.VariantBuilder;
+
+import java.util.List;
+
+/** Semantic tests for {@link DataTypes#VARIANT()} type. */
+public class VariantSemanticTest extends SemanticTestBase {

Review Comment:
   Thank you for reimplementing the tests using this test base. Very clean and 
readable!



##########
flink-core/src/main/java/org/apache/flink/types/variant/Variant.java:
##########
@@ -0,0 +1,216 @@
+/*
+ * 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.flink.types.variant;
+
+import org.apache.flink.annotation.PublicEvolving;
+
+import java.math.BigDecimal;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
+/** Variant represent a semi-structured data. */
+@PublicEvolving
+public interface Variant {
+
+    /** Returns true if the variant is a primitive typed value, such as INT, 
DOUBLE, STRING, etc. */
+    boolean isPrimitive();
+
+    /** Returns true if this variant is an Array, false otherwise. */
+    boolean isArray();
+
+    /** Returns true if this variant is an Object, false otherwise. */
+    boolean isObject();
+
+    /** Check If this variant is null. */
+    boolean isNull();
+
+    /** Get the type of variant. */
+    Type getType();
+
+    /**
+     * Get the scalar value of variant as boolean, if the variant type is 
{@link Type#BOOLEAN}.
+     *
+     * @throws VariantTypeException If this variant is not a scalar value or 
is not {@link
+     *     Type#BOOLEAN}.
+     */
+    boolean getBoolean() throws VariantTypeException;
+
+    /**
+     * Get the scalar value of variant as byte, if the variant type is {@link 
Type#TINYINT}.
+     *
+     * @throws VariantTypeException If this variant is not a scalar value or 
is not {@link
+     *     Type#TINYINT}.
+     */
+    byte getByte() throws VariantTypeException;
+
+    /**
+     * Get the scalar value of variant as short, if the variant type is {@link 
Type#SMALLINT}.
+     *
+     * @throws VariantTypeException If this variant is not a scalar value or 
is not {@link
+     *     Type#SMALLINT}.
+     */
+    short getShort() throws VariantTypeException;
+
+    /**
+     * Get the scalar value of variant as int, if the variant type is {@link 
Type#INT}.
+     *
+     * @throws VariantTypeException if this variant is not a scalar value or 
is not {@link
+     *     Type#INT}.
+     */
+    int getInt() throws VariantTypeException;
+
+    /**
+     * Get the scalar value of variant as long, if the variant type is {@link 
Type#BIGINT}.
+     *
+     * @throws VariantTypeException If this variant is not a scalar value or 
is not {@link
+     *     Type#BIGINT}.
+     */
+    long getLong() throws VariantTypeException;
+
+    /**
+     * Get the scalar value of variant as float, if the variant type is {@link 
Type#FLOAT}.
+     *
+     * @throws VariantTypeException If this variant is not a scalar value or 
is not {@link
+     *     Type#FLOAT}.
+     */
+    float getFloat() throws VariantTypeException;
+
+    /**
+     * Get the scalar value of variant as BigDecimal, if the variant type is 
{@link Type#DECIMAL}.
+     *
+     * @throws VariantTypeException If this variant is not a scalar value or 
is not {@link
+     *     Type#DECIMAL}.
+     */
+    BigDecimal getDecimal() throws VariantTypeException;
+
+    /**
+     * Get the scalar value of variant as double, if the variant type is 
{@link Type#DOUBLE}.
+     *
+     * @throws VariantTypeException If this variant is not a scalar value or 
is not {@link
+     *     Type#DOUBLE}.
+     */
+    double getDouble() throws VariantTypeException;
+
+    /**
+     * Get the scalar value of variant as string, if the variant type is 
{@link Type#STRING}.
+     *
+     * @throws VariantTypeException If this variant is not a scalar value or 
is not {@link
+     *     Type#STRING}.
+     */
+    String getString() throws VariantTypeException;
+
+    /**
+     * Get the scalar value of variant as LocalDate, if the variant type is 
{@link Type#DATE}.
+     *
+     * @throws VariantTypeException If this variant is not a scalar value or 
is not {@link
+     *     Type#DATE}.
+     */
+    LocalDate getDate() throws VariantTypeException;
+
+    /**
+     * Get the scalar value of variant as LocalDateTime, if the variant type 
is {@link
+     * Type#TIMESTAMP}.
+     *
+     * @throws VariantTypeException If this variant is not a scalar value or 
is not {@link
+     *     Type#TIMESTAMP}.
+     */
+    LocalDateTime getDateTime() throws VariantTypeException;
+
+    /**
+     * Get the scalar value of variant as Instant, if the variant type is 
{@link Type#TIMESTAMP}.
+     *
+     * @throws VariantTypeException If this variant is not a scalar value or 
is not {@link
+     *     Type#TIMESTAMP}.
+     */
+    Instant getInstant() throws VariantTypeException;
+
+    /**
+     * Get the scalar value of variant as byte array, if the variant type is 
{@link Type#BINARY}.
+     *
+     * @throws VariantTypeException If this variant is not a scalar value or 
is not {@link
+     *     Type#BINARY}.
+     */
+    byte[] getBytes() throws VariantTypeException;
+
+    /**
+     * Get the scalar value of variant.
+     *
+     * @throws VariantTypeException If this variant is not a scalar value.
+     */
+    Object get() throws VariantTypeException;
+
+    /**
+     * Get the scalar value of variant.
+     *
+     * @throws VariantTypeException If this variant is not a scalar value.
+     */
+    <T> T getAs() throws VariantTypeException;
+
+    /**
+     * Access value of the specified element of an array variant. If index is 
out of range, null is
+     * returned.
+     *
+     * <p>NOTE: if the element value has been explicitly set as 
<code>null</code> (which is
+     * different from removal!), a variant that @{@link Variant#isNull()} 
returns true will be
+     * returned, not null.
+     *
+     * @throws VariantTypeException If this variant is not an array.
+     */
+    Variant getElement(int index) throws VariantTypeException;
+
+    /**
+     * Access value of the specified field of an object variant. If there is 
no field with the
+     * specified name, null is returned.
+     *
+     * <p>NOTE: if the property value has been explicitly set as 
<code>null</code>, a variant
+     * that @{@link Variant#isNull()} returns true will be returned, not null.
+     *
+     * @throws VariantTypeException If this variant is not an object.
+     */
+    Variant getField(String fieldName) throws VariantTypeException;
+
+    /** Parses the variant to json. */
+    String toJson();
+
+    /** The type of variant. */
+    @PublicEvolving
+    enum Type {
+        OBJECT,
+        ARRAY,
+        NULL,
+        BOOLEAN,
+        TINYINT,
+        SMALLINT,
+        INT,
+        BIGINT,
+        FLOAT,
+        DOUBLE,
+        DECIMAL,
+        STRING,
+        DATE,
+        TIMESTAMP,
+        TIMESTAMP_LTZ,
+        BINARY
+    }
+
+    static VariantBuilder builder() {

Review Comment:
   ```suggestion
       static VariantBuilder newBuilder() {
   ```
   For consistency with other classes such as `TypeInference`, `Schema`, and 
other API methods. I would suggest to go with `newBuilder()`. It also leaves 
room for a future `toBuilder()`.



##########
flink-core/src/main/java/org/apache/flink/types/variant/Variant.java:
##########
@@ -0,0 +1,213 @@
+/*
+ * 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.flink.types.variant;
+
+import org.apache.flink.annotation.PublicEvolving;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
+/** Variant represent a semi-structured data. */
+@PublicEvolving
+public interface Variant extends Serializable {

Review Comment:
   I could imagine that users will request it to more easily pass it to 
constructors of ProcessFunction or having it in member variables of 
ProcessFunction as well. This is also the reason why `Row` is `Serializable`. 
But this can be a followup. Given all the utilities we have, ensuring 
serializability should not be too difficult to implement with a custom 
`readObject`/`writeObject`.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to