vbhanuchander-lang opened a new pull request, #28996:
URL: https://github.com/apache/flink/pull/28996

   ## What is the purpose of the change
   
   Bytes-backed Avro decimal logical types were encoded incompatibly with 
Flink's JVM serializer when a
   `GenericRecord` crossed the JVM/Python boundary, silently corrupting the 
record.
   
   FLINK-37192 replaced `avro-python3` with `avro>=1.12.0`. That version routes 
a bytes-backed decimal
   through `BinaryEncoder.write_decimal_bytes` and 
`BinaryDecoder.read_decimal_from_bytes`, both of
   which size the payload with `write_long` / `read_long`. `FlinkAvroEncoder` 
and `FlinkAvroDecoder`
   override the primitive reads and writes for JVM compatibility but not those 
two, and in this encoder
   `write_long` is a fixed 8-byte long — so the length prefix was written as 8 
bytes where the JVM
   frames a bytes field with a 4-byte int.
   
   For a record of `amount=Decimal("12.34"), tail=7`:
   
   ```
   expected (JVM) : 0000000204d200000007
   actual         : 000000000000000204d200000007
   ```
   
   A JVM `GenericDatumReader` consumes the oversized prefix as the whole bytes 
field, so every following
   field shifts: the decimal reads back empty and `tail` reads back as `2`, 
leaving four bytes unread.
   Nothing is raised, which is why this is silent corruption rather than a 
decode failure. Flink's own
   reader is wrong in the same direction, so a pure-Python round trip looks 
correct and the defect only
   appears across the boundary.
   
   This does not affect standard Avro interoperability — `avro`, `fastavro` and 
ordinary Kafka Avro
   payloads are unchanged. It is specific to Flink's internal JVM-compatible 
serializer.
   
   ## Brief change log
   
     - `FlinkAvroEncoder.write_decimal_bytes` frames the unscaled payload as a 
bytes field rather than
       letting avro size it with an 8-byte long
     - `FlinkAvroDecoder.read_decimal_from_bytes` reads that 4-byte size 
symmetrically
     - The payload itself is untouched: the same two's-complement big-endian 
unscaled value avro already
       produced, so only the length prefix moves
   
   ## Verifying this change
   
   This change added tests and can be verified as follows:
   
     - `pyflink/fn_execution/tests/test_avro_format.py`, four tests: the exact 
encoded bytes for a
       decimal followed by an int; that the record reads back field by field 
with JVM framing; that the
       unscaled payload is unchanged for `0`, `±12.34` and the `±1.28`/`-1.29` 
byte-boundary cases; and
       a round trip through `FlinkAvroDatumWriter`/`FlinkAvroDatumReader`
     - Reverting the two overrides fails eight assertions across those tests
     - Separately checked against avro 1.12.2 that the payload bytes stay 
identical to
       `avro.io.BinaryEncoder.write_decimal_bytes` across 23 values — positive, 
negative, zero and the
       ±128, ±256, ±32768 boundaries — so only the framing differs
   
   A note on how that was run: the module imports only `struct` and `avro`, and 
I exercised the test
   body with `PyFlinkTestCase` substituted by `unittest.TestCase` in a 
virtualenv with avro 1.12.2. I
   did not build a full PyFlink environment locally, so the assertions are 
verified but the change has
   not been through the project's own Python test harness on my machine. flake8 
is clean under
   `flink-python/tox.ini` (max-line-length 100).
   
   ## Does this pull request potentially affect one of the following parts:
   
     - Dependencies (does it add or upgrade a dependency): **no**
     - The public API, i.e., is any changed class annotated with 
`@Public(Evolving)`: **no**
     - The serializers: **yes** — this is the JVM/Python boundary encoding for 
Avro generic records.
       It is a wire format used for in-flight data exchange rather than 
persisted state, and the
       previous encoding was already unreadable by the JVM, so the only case 
that changes behaviour is
       a decimal field that was silently corrupted before.
     - The runtime per-record code paths (performance sensitive): **yes** — the 
decimal path only. The
       work is equivalent to what avro already did; one `int.to_bytes` replaces 
a manual byte loop.
     - Anything that affects deployment or recovery: JobManager (and its 
components), Checkpointing,
       Kubernetes/Yarn, ZooKeeper: **no**
     - The S3 file system connector: **no**
   
   ## Documentation
   
     - Does this pull request introduce a new feature? **no** — it corrects an 
encoding defect, so there
       is nothing to document.
   


-- 
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