Protobuf¶
Status: Native encode and decode, with the fallbacks below.
Row fields map to protobuf message fields by name in both directions. See Kafka for
how the protobuf value format combines with the rest of the connector's key/value pipeline.
Encode¶
Native encode admits protobuf bool, float, double, string, the signed 32-bit integer
families mapped to Flink INT, and the signed 64-bit integer families mapped to BIGINT. It supports
those recursively through messages/ROW, repeated fields/ARRAY, and maps. Every table field must
name a descriptor field of the matching shape; extra descriptor fields remain unset.
- A
NULLcolumn leaves the corresponding proto field unset. - A
NULLnested inside a container (message/repeated/map field) is written as that field's type default;protobuf.write-null-string-literalcontrols what a null string becomes. - The wire bytes match protobuf-java's exact serialization shape — including the map-entry fields protobuf-java always writes even at their default values, which prost would omit.
protobuf.read-default-valuesis decode-only in Flink and is ignored on write, matching Flink.
Encode stays on Flink for protobuf bytes, enums, unsigned integers (uint32/uint64), unsigned
fixed integers (fixed32/fixed64), any other row↔descriptor mapping outside the admitted set, a
recursive message or deprecated proto2 group, or a protobuf.write-null-string-literal containing
a line break that the native plan cannot represent. Flink then performs its normal validation and
serialization.
Decode¶
The native reader handles the protobuf shapes Flink exposes to SQL: every scalar wire type,
bytes, enums, nested and well-known messages, repeated fields, maps, proto2 presence, proto3
optional, and oneofs. It decodes directly into Arrow arrays and reconciles protobuf-specific
representations with Flink's table types:
- protobuf's unsigned 32- and 64-bit values retain their bits in Flink's signed
INTandBIGINT; - enum names become
STRING, while numeric enum columns use Flink's requested integer width; bytesbecomeVARBINARY; well-known messages remain ordinary nestedROWvalues, as in Flink;- with the default
protobuf.read-default-values = 'false', absent proto2 values and empty containers preserve Flink's null behavior; proto3 scalar getters still produce protobuf defaults; protobuf.read-default-values = 'true'is native for proto3, including default nested messages and empty containers.
The generated message class is needed only while Flink plans the job. StreamFusion reads it through Flink's user-code classloader, resolves the descriptor immediately, and serializes only portable decoder-plan data into the Kafka source. Each task recursively compiles that descriptor into a reusable native field/tag plan once when its decoder opens; batch decoding performs no descriptor traversal. TaskManagers therefore do not need a separately generated Rust library or a second job artifact, and every admitted message uses the same production decoder.
The decoder falls back to Flink's own deserializer, with each case caught at plan time, when:
protobuf.read-default-values = 'true'is used with proto2, whose schema may declare an arbitrary default value for each field;ignore-parse-errorsis set on a protobuf table — Flink skips a malformed message whole, but the native decoder fails on it instead, so the table falls back to Flink's own decode path (the JSON-decoded formats honor the per-message skip natively; CSV reproduces Flink's finer per-field granularity — see each format's own page);- the schema contains a deprecated proto2 group, or a recursive message that cannot be represented by a finite Arrow schema without choosing an arbitrary maximum depth.