[FLINK-40262][format/avro] Support matching Avro record fields by name - #28845
Open
kumarpritam863 wants to merge 1 commit into
Open
[FLINK-40262][format/avro] Support matching Avro record fields by name#28845kumarpritam863 wants to merge 1 commit into
kumarpritam863 wants to merge 1 commit into
Conversation
Collaborator
RowDataToAvroConverters and AvroToRowDataConverters pair a RowType field with an
Avro record field by ordinal position. That is correct when the Avro schema is
derived from the row type by AvroSchemaConverter, since both sides then agree on
field order, but not when the schema is supplied independently - a schema
registry subject, 'avro-confluent.schema', or the AvroRowData{,De}serialization
Schema constructors that take a nested schema. There, column i and Avro field i
need not be the same field, and the converters silently write and read the wrong
one.
Introduce an explicit strategy, FieldMatching, and thread it through both
converters:
- INDEX keeps today's behaviour and stays the default, byte for byte.
- NAME pairs fields by name. Names are compared exactly first, then against
Avro field aliases, and finally ignoring case, each stage running to
completion before the next so a fuzzy match can never claim a field that
some other column matches exactly.
AvroFieldMatcher does the resolution and refuses anything that is ambiguous or
that would quietly lose data: a column matching several Avro fields, two columns
matching the same one, a column with no Avro counterpart when writing, a NOT NULL
column with no counterpart when reading, or an Avro field that nothing writes to
and that is neither nullable nor defaulted. Avro fields that no column maps to
but that declare a default are written with that default; Avro fields no column
reads are ignored, which is ordinary projection.
Resolution is done once per (row type, schema) pair and memoized, so NAME costs
an array lookup per field per record - no hashing, no lowercasing, no allocation
on the hot path. The memo is transient and rebuilt after the converter is shipped
to a task.
This commit only makes the converters capable of it. Exposing the strategy to SQL
as an 'avro-confluent.field-matching' option, and relaxing the order-sensitive
schema check in RegistryAvroFormatFactory that rejects such a schema outright
today, is left to a follow-up so the two can be reviewed independently.
Restructuring createRowConverter into createRowConverterInternal necessarily
threads legacyTimestampMapping into nested converters, which incidentally fixes
FLINK-40264. That is submitted separately together with its regression test;
whichever of the two lands second needs a trivial rebase.
kumarpritam863
force-pushed
the
avro-field-matching-by-name
branch
from
July 30, 2026 10:00
2db08cc to
a81e3dc
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What is the purpose of the change
RowDataToAvroConvertersandAvroToRowDataConverterspair a FlinkRowTypefield with an Avro record field by ordinal position. That is correct whenever the Avro schema is derived from the row type byAvroSchemaConverter, becauseboth sides then agree on field order by construction. It is not correct when the schema is supplied independently of the table:
There, column i and Avro field i need not be the same field, so values are written into and read from the wrong one. Where the types at a position differ the job fails with a
ClassCastException; where they happen to agree, the data issilently corrupted. A table
a STRING, b STRINGagainst a schema that declaresbbeforeaswaps the two values with no error at all.This change lets the converters pair fields by name instead, behind an explicit opt-in that leaves the existing positional behaviour as the default.
Brief change log
column matches exactly.
-
AvroFieldMatcherrejects anything ambiguous or lossy: a column matching several Avro fields, two columns matching the same one, a column with no counterpart when writing, aNOT NULLcolumn with no counterpart when reading, or anunwritten Avro field that is neither nullable nor defaulted — which Avro would otherwise surface as a bare
NullPointerException.- Unwritten Avro fields that declare a default are written with that default; Avro fields that nothing reads are ignored, which is ordinary projection.
- Resolution runs once per (row type, schema) pair and is memoized, so
NAMEcosts one array lookup per field per record: no hashing, no lowercasing and no allocation on the hot path. The memo istransientand rebuilt after the converteris shipped to a task.
No SQL-facing option is added here. The converters are
@Internal, so this change makes name matching reachable from Java only. Exposing it as anavro-confluent.field-matchingoption also requires relaxing the order-sensitive schema checkin
RegistryAvroFormatFactory#getAvroSchema, which compares the convertedDataTypewithequals()and therefore rejects a reordered schema during planning with "Schema provided for 'avro-confluent' format does not match the tableschema". That part is written and tested, but held back so the two can be reviewed independently — happy to add it to this PR if reviewers would rather have it in one go. The plain
avroformat needs no option, since it always derives itsschema from the table.
Verifying this change
This change added tests and can be verified as follows:
bound to the schema it was resolved for.
- Added
AvroRowDataFieldMatchingTest: 12 tests over both Avro encodings, end to end throughAvroRowDataSerializationSchemaandAvroRowDataDeserializationSchema— reordered top-level fields (asserted byte for byte against what aGenericDatumWriterproduces for the same schema), reordering inside nested rows, array elements and map values, case-insensitive and alias matching, enum combined with reordering, an unmatched Avro field falling back to its default, a columnabsent from the schema read as
NULL, a column absent from the schema refused on write, and a Java-serialization round trip of both converters, which exercises rebuilding the transient memo in a task.- Added
AvroRowDataFieldMatchingTest#testIndexMatchingIgnoresFieldNames, which pins down the behaviourNAMEexists to avoid: withINDEX, a reordered schema of same-typed fields silently swaps the values.- Ran
mvn clean verifyforflink-formats/flink-avroandflink-formats/flink-avro-confluent-registry: 403 and 27 tests respectively, no failures, 0 checkstyle violations, spotless and ArchUnit clean, and japicmp reports noincompatibility.
Does this pull request potentially affect one of the following parts:
Documentation
Was generative AI tooling used to co-author this PR?
Generated-by: Claude Code (Anthropic Claude Opus 5)