[ 
https://issues.apache.org/jira/browse/FLINK-41335?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Mika Naylor reassigned FLINK-41335:
-----------------------------------

    Assignee: Mika Naylor

> Release Testing: Verify FLIP-604: Complete VARIANT Primitive Coverage with 
> UUID and Timestamps
> ----------------------------------------------------------------------------------------------
>
>                 Key: FLINK-41335
>                 URL: https://issues.apache.org/jira/browse/FLINK-41335
>             Project: Flink
>          Issue Type: Sub-task
>          Components: Table SQL / API
>            Reporter: Ramin Gharib
>            Assignee: Mika Naylor
>            Priority: Blocker
>              Labels: release-testing
>             Fix For: 2.4.0
>
>
> [FLIP-604|https://cwiki.apache.org/confluence/spaces/FLINK/pages/446070993] 
> adds a native {{UUID}} type to Flink SQL and the Table API. It also lets a 
> {{VARIANT}} hold {{TIME}}, nanosecond {{TIMESTAMP}}, nanosecond 
> {{TIMESTAMP_LTZ}} and {{UUID}} values. These are type ids 17 to 20 of the 
> Parquet Variant spec.
> h3. What is new
> * {{UUID}} as a column type, {{DataTypes.UUID()}} in the Table API and 
> {{Types.UUID}} in the DataStream API.
> * The literal {{UUID 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'}} and the 
> functions {{UUID_V4()}} and {{UUID_V7()}}. The old {{UUID()}} still returns 
> {{CHAR(36)}}.
> * Explicit {{CAST}} between {{UUID}} and {{STRING}}, {{BINARY(16)}}, 
> {{BYTES}} and {{VARIANT}}. There is no implicit cast.
> * Comparison, {{ORDER BY}}, {{GROUP BY}}, {{DISTINCT}}, joins and set 
> operations. The order is unsigned big-endian bytes, as in PostgreSQL and 
> Iceberg.
> * {{CAST}} to and from {{VARIANT}} for {{TIME}}, {{TIMESTAMP(p)}}, 
> {{TIMESTAMP_LTZ(p)}} and {{UUID}}. A precision above 6 is stored in 
> nanoseconds.
> Docs: 
> [VARIANT|https://nightlies.apache.org/flink/flink-docs-release-2.4/docs/sql/reference/data-types/#variant],
>  [built-in 
> functions|https://nightlies.apache.org/flink/flink-docs-release-2.4/docs/sql/functions/built-in-functions/],
>  UUID type section from FLINK-40494.
> h3. Setup
> Build the {{release-2.4}} branch, or use the 2.4.0 RC once it is out.
> {code:bash}
> git checkout release-2.4
> ./mvnw clean install -DskipTests -Dfast -Pskip-webui-build -T1C
> cd build-target
> ./bin/start-cluster.sh
> ./bin/sql-client.sh
> {code}
> No connector or format reads or writes a {{UUID}} column in 2.4. This 
> includes datagen. Derive the UUIDs inside the query instead, and write them 
> to the {{print}} or {{blackhole}} connector.
> {code:sql}
> SET 'table.local-time-zone' = 'UTC';
> SET 'sql-client.execution.result-mode' = 'tableau';
> CREATE TABLE src (
>   k INT,
>   pt AS PROCTIME()
> ) WITH (
>   'connector' = 'datagen',
>   'rows-per-second' = '5',
>   'fields.k.min' = '1',
>   'fields.k.max' = '5'
> );
> -- id repeats 5 keys: 00000000-0000-0000-0000-000000000001 .. 
> 00000000-0000-0000-0000-000000000005
> -- event_id is a fresh time-ordered UUID per row
> CREATE VIEW events AS
> SELECT
>   CAST(LPAD(CAST(k AS STRING), 32, '0') AS UUID) AS id,
>   UUID_V7() AS event_id,
>   k,
>   pt
> FROM src;
> {code}
> h3. 1. Literals, generators and casts
> {code:sql}
> SELECT UUID 'A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11';
> -- a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11, always canonical lower case
> SELECT UUID 'abcd';
> -- validation error: Invalid UUID string: abcd
> SELECT TYPEOF(UUID_V4()), TYPEOF(UUID_V7()), TYPEOF(UUID());
> -- UUID NOT NULL | UUID NOT NULL | CHAR(36) NOT NULL
> -- The version digit is the 15th character
> SELECT SUBSTR(CAST(UUID_V4() AS STRING), 15, 1), SUBSTR(CAST(UUID_V7() AS 
> STRING), 15, 1);
> -- 4 | 7
> -- String parsing is lenient, like PostgreSQL. All three return 
> a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11
> SELECT
>   CAST('{a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11}' AS UUID),
>   CAST('a0eebc999c0b4ef8bb6d6bb9bd380a11' AS UUID),
>   CAST('a0ee-bc99-9c0b-4ef8-bb6d-6bb9-bd38-0a11' AS UUID);
> SELECT CAST(UUID 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11' AS BYTES);
> -- the 16 bytes a0 ee bc 99 9c 0b 4e f8 bb 6d 6b b9 bd 38 0a 11, big-endian
> SELECT CAST(x'a0eebc999c0b4ef8bb6d6bb9bd380a11' AS UUID);
> -- a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11
> -- Malformed input read from a table, so the runtime path runs and not 
> constant folding
> SELECT TRY_CAST(CAST(k AS STRING) AS UUID), TRY_CAST(CAST(CAST(k AS STRING) 
> AS BYTES) AS UUID) FROM events;
> -- NULL | NULL. Without TRY_ the job fails: a string needs 32 hex digits, 
> bytes need exactly 16.
> SELECT CAST(UUID 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11' AS INT);
> -- validation error: Cast function cannot convert value of type UUID to type 
> INTEGER
> SELECT id = '00000000-0000-0000-0000-000000000001' FROM events;
> -- validation error: Cannot apply '=' to arguments of type '<UUID> = 
> <CHAR(36)>'
> {code}
> h3. 2. Comparison, ordering, grouping and joins
> Order is unsigned. A signed byte comparison would sort {{80000000-...}} first.
> {code:sql}
> SET 'execution.runtime-mode' = 'batch';
> SELECT u FROM (VALUES
>     (UUID 'ffffffff-ffff-ffff-ffff-ffffffffffff'),
>     (UUID '00000000-0000-0000-0000-000000000000'),
>     (UUID '80000000-0000-0000-0000-000000000000'),
>     (UUID '550e8400-e29b-41d4-a716-446655440000')
>   ) AS T(u)
> ORDER BY u;
> -- 00000000-... , 550e8400-... , 80000000-... , ffffffff-...
> {code}
> In streaming mode, check that each query keys on {{id}} correctly. Repeat the 
> ones that make sense in batch mode with a bounded source, for example 
> {{'number-of-rows' = '100'}}.
> {code:sql}
> SET 'execution.runtime-mode' = 'streaming';
> SELECT a.id, COUNT(*) FROM events a JOIN (SELECT id FROM events WHERE k = 1) 
> b ON a.id = b.id GROUP BY a.id;
> SELECT id FROM events WHERE id IN (SELECT id FROM events WHERE k <= 2);
> SELECT window_start, id, COUNT(*)
> FROM TABLE(TUMBLE(TABLE events, DESCRIPTOR(pt), INTERVAL '10' SECOND))
> GROUP BY window_start, window_end, id;
> SELECT id FROM events UNION SELECT id FROM events;                            
> -- 5 rows
> SELECT ARRAY_CONTAINS(ARRAY[id], UUID '00000000-0000-0000-0000-000000000001'),
>        MAP[id, k][UUID '00000000-0000-0000-0000-000000000001']
> FROM events;                                                                  
> -- TRUE and k only for key ...001
> {code}
> h3. 3. State, savepoints and compiled plans
> This is the most important part. The job below keeps {{UUID}} keys, {{UUID}} 
> values and a {{VARIANT}} holding the new kinds in keyed state.
> {code:sql}
> SET 'execution.runtime-mode' = 'streaming';
> SET 'execution.checkpointing.interval' = '10 s';
> SET 'execution.checkpointing.savepoint-dir' = 
> 'file:///tmp/flip604-savepoints';
> SET 'state.backend.type' = 'rocksdb';   -- repeat the section with 'hashmap'
> SET 'parallelism.default' = '2';
> CREATE TABLE agg_sink (id UUID, cnt BIGINT, last_event UUID, last_doc STRING)
> WITH ('connector' = 'print');
> INSERT INTO agg_sink
> SELECT
>   id,
>   COUNT(*),
>   LAST_VALUE(event_id),
>   JSON_STRING(LAST_VALUE(CAST(CAST(ROW(event_id, TIME '01:02:03.456', 
> CAST(CURRENT_TIMESTAMP AS TIMESTAMP(9)))
>                                    AS ROW<event UUID, t TIME(3), ts 
> TIMESTAMP(9)>) AS VARIANT)))
> FROM events
> GROUP BY id;
> {code}
> # Let it run for a minute. The TaskManager {{.out}} file shows changelog rows 
> per key.
> # Run {{SHOW JOBS;}} and {{STOP JOB '<job id>' WITH SAVEPOINT;}}.
> # Run {{SET 'execution.state-recovery.path' = '<savepoint path>';}} and {{SET 
> 'parallelism.default' = '3';}}, then submit the same {{INSERT}} again.
> # Expected: the counts continue from the values before the stop. No 
> serializer or restore errors appear in the logs.
> # Compile the same statement into a plan, run it, stop it with a savepoint 
> and restore it with {{EXECUTE PLAN}}:
> {code:sql}
> COMPILE PLAN 'file:///tmp/flip604-plan.json' FOR INSERT INTO agg_sink SELECT 
> ... ;  -- the INSERT above
> EXECUTE PLAN 'file:///tmp/flip604-plan.json';
> {code}
> # Expected: the plan file contains {{"UUID"}} as a type, and the job restores 
> like the plain {{INSERT}}.
> h3. 4. VARIANT with the new kinds
> {code:sql}
> SELECT
>   JSON_STRING(CAST(UUID 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11' AS VARIANT)),
>   JSON_STRING(CAST(TIME '12:34:56.789' AS VARIANT)),
>   JSON_STRING(CAST(CAST('2026-11-04 10:00:00.123456789' AS TIMESTAMP(9)) AS 
> VARIANT)),
>   JSON_STRING(CAST(TO_TIMESTAMP_LTZ('2026-11-04 10:00:00.123456789', 
> 'yyyy-MM-dd HH:mm:ss.SSSSSSSSS') AS VARIANT));
> -- "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11" | "12:34:56.789" | 
> "2026-11-04T10:00:00.123456789" | "2026-11-04T10:00:00.123456789Z"
> CREATE VIEW docs AS
> SELECT CAST(CAST(ROW(id, TIME '01:02:03.456', CAST('2026-11-04 
> 10:00:00.123456789' AS TIMESTAMP(9)))
>             AS ROW<id UUID, t TIME(3), ts TIMESTAMP(9)>) AS VARIANT) AS doc
> FROM events;
> SELECT
>   JSON_STRING(doc),                         -- 
> {"id":"00000000-...-00000000000k","t":"01:02:03.456","ts":"2026-11-04T10:00:00.123456789"}
>   CAST(doc['id'] AS UUID),                  -- 00000000-...-00000000000k
>   CAST(doc['t'] AS TIME(3)),                -- 01:02:03.456
>   CAST(doc['ts'] AS TIMESTAMP(9)),          -- 2026-11-04 10:00:00.123456789
>   CAST(doc['ts'] AS TIMESTAMP(3)),          -- 2026-11-04 10:00:00.123, extra 
> digits truncated
>   CAST(doc AS ROW<id UUID, ts TIMESTAMP(9)>)
> FROM docs;
> -- Nanoseconds only cover 1677-09-21 to 2262-04-11
> SELECT JSON_STRING(CAST(CAST('2300-01-01 00:00:00' AS TIMESTAMP(6)) AS 
> VARIANT));  -- "2300-01-01T00:00:00"
> SELECT TRY_CAST(CAST('2300-01-01 00:00:00' AS TIMESTAMP(9)) AS VARIANT);      
>      -- NULL, CAST fails
> -- A VARIANT cast never reinterprets the stored kind
> SELECT TRY_CAST(PARSE_JSON('"a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11"') AS 
> UUID);              -- NULL, the kind is STRING
> SELECT CAST(CAST(PARSE_JSON('"a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11"') AS 
> STRING) AS UUID);  -- works
> SELECT TRY_CAST(CAST(TIME '12:34:56.789' AS VARIANT) AS TIMESTAMP(3));        
>               -- NULL
> {code}
> Also write {{docs}} through the {{json}} and {{raw}} formats with the 
> filesystem connector. Files are committed on checkpoints. Expected: each line 
> is JSON, with the UUID, time and timestamp values as strings.
> h3. 5. Table API and DataStream (optional, Java)
> {code:java}
> DataStream<UUID> ds = env.fromData(Types.UUID, UUID.randomUUID());     // 
> pass Types.UUID explicitly
> Table t = tEnv.fromDataStream(ds).as("u");                             // 
> schema: u UUID
> Table out = t.select($("u"), $("u").isEqual(lit(someUuid)), 
> $("u").cast(DataTypes.STRING()), uuidV7());
> tEnv.toDataStream(out);                                                // 
> fields come back as java.util.UUID
> {code}
> In a UDF, build and read variants with 
> {{Variant.newBuilder().of(LocalTime)}}, {{of(java.util.UUID)}}, 
> {{getTime()}}, {{getUuid()}} and {{getType()}}, which returns {{TIME}}, 
> {{TIMESTAMP_NS}}, {{TIMESTAMP_LTZ_NS}} or {{UUID}}.
> h3. Known issues. Do not file these again
> ||Behavior||Ticket||
> |{{COMPILE PLAN}} fails when the query contains a {{UUID}} literal: {{Literal 
> type 'UUID' is not supported for serializing value ...}}. {{CAST('...' AS 
> UUID)}} works as a workaround.|FLINK-41332|
> |{{id IN (UUID 'a', UUID 'b')}} and {{id = UUID 'a' OR id = UUID 'b'}} return 
> no rows. {{NOT IN}} returns all rows. Same root cause as for 
> {{BINARY}}.|FLINK-41327|
> |{{MIN}} and {{MAX}} on {{UUID}} fail in both modes. {{COUNT(DISTINCT id)}} 
> fails in streaming mode. Aggregate over {{CAST(id AS STRING)}} instead, which 
> sorts the same.|FLINK-41333|
> |Two {{UUID}} literals compared at planning time use signed order, so {{UUID 
> '80000000-...' > UUID '00000000-...'}} returns FALSE. Column comparisons are 
> correct.|CALCITE-7716, needs a Calcite upgrade|
> |The JDBC driver and the SQL Gateway REST API with the default 
> {{rowFormat=JSON}} cannot return a {{UUID}} column. The SQL Client is not 
> affected.|FLINK-41334|
> |No connector or format reads or writes a {{UUID}} column. Cast it to 
> {{STRING}} or {{BYTES}} before writing.|FLINK-40544 for raw, others are 
> future work in the FLIP|
> |PyFlink has no {{UUID}} type.|none|
> |The DataStream API extracts {{java.util.UUID}} as a generic type unless 
> {{Types.UUID}} is given.|FLINK-40546, planned for 3.0|
> h3. Reporting
> Comment with the commit or RC, the runtime mode, the state backend and the 
> parallelism, and give pass or fail per section. File new bugs as linked 
> issues with Fix Version 2.4.0.



--
This message was sent by Atlassian Jira
(v10.3.26#10030026)

Reply via email to