Issue #6408 : Read and write Kafka record headers - #7770
Open
vbhanuchander-lang wants to merge 2 commits into
Open
Issue #6408 : Read and write Kafka record headers#7770vbhanuchander-lang wants to merge 2 commits into
vbhanuchander-lang wants to merge 2 commits into
Conversation
The Kafka Consumer exposes key, message, topic, partition, offset and timestamp,
but not the record headers, so anything carried in a header - trace context,
schema hints, routing metadata - is unreachable once a message is consumed.
Add an optional Headers output field. It ships with an empty output name, and
getRowMeta already omits fields without one, so a pipeline saved before this
change produces exactly the row it did before.
Headers are rendered as a JSON array of {"name":..,"value":..} objects rather
than a JSON object. A record carries an ordered list of header pairs and the
same name may appear more than once, so an object would silently drop repeats
and lose ordering; an array keeps both, which also lets the value round-trip
back out through the producer unchanged. Values are bytes on the wire and are
decoded as UTF-8, with a null value rendered as JSON null.
Building the row also had to change. putFieldOnRowMeta leaves out any field
whose output name is empty, so the output row is variable length, but
processMessageAsRow wrote its six values at fixed indices regardless. Clearing
the name of, say, the topic field was therefore enough to shift every later
column by one. Values are now placed only for fields that contribute a column,
in the order getRowMeta adds them, which fixes that and is what lets the new
field be optional at all.
Completes header support: the consumer can now read headers into a field and the producer can attach headers read back from one. The exchange format lives in a new shared KafkaHeaders class rather than in either transform, because it is a contract between the two: what the consumer writes into a field can be handed straight to the producer and yields the same headers, including ordering, repeated names, and the difference between a null and an empty value. Keeping both directions in one place stops them drifting apart, and the round trip is covered by a test. The producer's headers field is optional; leaving it empty sends messages without headers, so existing pipelines are unaffected. A field that is named but absent from the input stream fails when the first row arrives, matching how the key, message and topic fields already behave. A value that is not a JSON array of name/value objects fails with the offending document in the message. Also documents both transforms and makes the consumer's output field table editable. That table was already built with an editable name column and a disabled-listener limiting type changes, but the table itself was flagged read-only and the write-back left as a TODO, so field names could not actually be changed from the dialog - which would have left the new Headers field impossible to switch on outside metadata injection.
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.
Addresses #6408.
The Kafka transforms expose key, message, topic, partition, offset and timestamp, but not the record headers — so anything carried in a header (trace context, schema hints, routing or tenancy metadata) is unreachable on the way in and impossible to set on the way out. As the reporter put it, that rules Hop out of a fair number of Kafka use cases.
This adds headers to both sides.
Exchange format
A record carries an ordered list of header pairs and the same name may appear more than once, neither of which a flat row column can express. The value is therefore a JSON array of
{"name":..,"value":..}objects rather than a JSON object:[{"name":"traceparent","value":"00-abc"},{"name":"tag","value":"one"},{"name":"tag","value":"two"}]A JSON object would have been friendlier to hand-write, but it silently drops repeats and loses ordering. The array is lossless, which means the consumer's output can be fed straight into the producer's Headers field and the headers come out unchanged — including a null value, which stays distinct from an empty one.
Both directions live in one new
shared/KafkaHeadersclass rather than in either transform, because the format is a contract between them; keeping them together is what stops them drifting apart. The round trip is covered by a test.Consumer
A new optional headers output field. It ships with an empty output name, and
getRowMetaalready omits fields without one, so a pipeline saved before this change produces exactly the row it did before. Give it a name to start reading headers.Producer
A new optional Headers field. Left empty, messages are sent without headers, so existing pipelines are unaffected. A field that is named but missing from the input stream fails when the first row arrives — matching how the key, message and topic fields already behave — and a value that is not a JSON array of name/value objects fails with the offending document in the message.
Two things I had to fix to make this work
The consumer's row builder used fixed indices.
putFieldOnRowMetaomits any field whose output name is empty, so the output row is variable length, butprocessMessageAsRowwrote its six values at indices 0–5 regardless. Clearing the name of, say, the topic field was enough to shift every later column by one. Values are now placed only for fields that contribute a column, in the ordergetRowMetaadds them. That is a pre-existing defect rather than anything this feature introduced, but an optional field cannot sit on top of fixed indices, so it had to be fixed here.The consumer's output field table was read-only. It was already built with an editable name column and a
setDisabledListenerrestricting type changes to key/message, but the table itself was flaggedsetReadonly(true)and the write-back left as// meta.setField(field); TODO FIXME, so field names could not actually be changed from the dialog. That would have left the new Headers field impossible to switch on except through metadata injection. The write-back is now implemented and the table editable — which the indexing fix above makes safe, since clearing a name now correctly drops the column instead of corrupting the row.Happy to split either of those into their own PR if you would rather keep this one to the feature.
Tests
15 cases in
KafkaHeadersTestcovering rendering, parsing and the round trip: ordering, repeated names, null vs. empty values, JSON escaping, non-ASCII, and the rejection cases (not JSON, a JSON object, an entry without a name). Plus the existing suite — 32 tests pass in the module.mvn -pl plugins/transforms/kafka test,spotless:checkandapache-rat:check(Unapproved: 0) all pass.Documentation
Both
kafkaconsumer.adocandkafkaproducer.adocare updated, including a note that an empty output name keeps a field off the row.Not included
I have not added an integration test under
integration-tests/kafkafor this one yet — happy to add a produce-with-headers / consume-and-assert workflow in the same shape as0004-kafka-test-topic-from-fieldif you would like it before merge.Thank you for your contribution! Follow this checklist to help us incorporate your contribution quickly and easily:
mvn clean install apache-rat:checkto make sure basic checks pass. A more thorough check will be performed on your pull request automatically. (ran the module build,spotless:checkandapache-rat:check; fullclean installnot run locally)git rebase -i. (two commits kept deliberately: read side, then write side — happy to squash)addresses #123), if applicable.To make clear that you license your contribution under the Apache License Version 2.0, January 2004
you have to acknowledge this by using the following check-box.