Scope
| file |
type |
datasource-json/src/source.rs |
JsonSource |
datasource-json/src/file_format.rs |
JsonSink |
4 hooks (2 encoders, 2 decoders).
Field drop found in this group
JsonSource::newline_delimited is silently dropped. JsonScanExecNode contains only base_conf, and JsonSource::default() sets newline_delimited: true. A source configured with the public with_newline_delimited(false) -- which selects JSON-array input rather than NDJSON, and also controls whether a file may be split by byte range -- decodes as NDJSON, so the scan reads the file the wrong way.
Why
Serde hooks that read state through getters or self.field make an added field invisible to serialization: nothing breaks at compile time, the field simply stops round-tripping, and Debug-comparing round-trip tests do not notice. HashJoinExec::fetch was lost exactly this way (#24165), and #24609 is a second live instance found by applying the convention to one file.
#24164 established the fix -- exhaustive destructuring in both directions -- and applied it to the join plans. The physical-plan plan nodes are done. physical-expr and the datasource* crates were never converted.
What to do
For each hook in scope:
- In
try_to_proto, start with an exhaustive let Self {{ .. }} -- no .. rest pattern. Fields that are genuinely not serialized bind to _ with a short comment saying why (derived at construction, runtime state, recomputed on decode, carried by a parent message).
- In
try_from_proto, destructure the prost-generated node struct the same way, so adding a field to datafusion.proto is a compile error in every decoder.
- If the destructure turns up a field that should round-trip but has no wire representation, add it to the message and cover it with a test that fails without the fix.
Definition of done
Scope
datasource-json/src/source.rsJsonSourcedatasource-json/src/file_format.rsJsonSink4 hooks (2 encoders, 2 decoders).
Field drop found in this group
JsonSource::newline_delimitedis silently dropped.JsonScanExecNodecontains onlybase_conf, andJsonSource::default()setsnewline_delimited: true. A source configured with the publicwith_newline_delimited(false)-- which selects JSON-array input rather than NDJSON, and also controls whether a file may be split by byte range -- decodes as NDJSON, so the scan reads the file the wrong way.Why
Serde hooks that read state through getters or
self.fieldmake an added field invisible to serialization: nothing breaks at compile time, the field simply stops round-tripping, andDebug-comparing round-trip tests do not notice.HashJoinExec::fetchwas lost exactly this way (#24165), and #24609 is a second live instance found by applying the convention to one file.#24164 established the fix -- exhaustive destructuring in both directions -- and applied it to the join plans. The
physical-planplan nodes are done.physical-exprand thedatasource*crates were never converted.What to do
For each hook in scope:
try_to_proto, start with an exhaustivelet Self {{ .. }}-- no..rest pattern. Fields that are genuinely not serialized bind to_with a short comment saying why (derived at construction, runtime state, recomputed on decode, carried by a parent message).try_from_proto, destructure the prost-generated node struct the same way, so adding a field todatafusion.protois a compile error in every decoder.Definition of done
Every field of the plan/expression struct is either serialized or bound to
_with a reason.Every field of the prost node is destructured in the decoder.
Any newly serialized field has a round-trip test, verified to fail before the fix.
Part of EPIC: destructure proto serde hooks in the
datasourcecrates #24612.