Skip to content

fix(connectors): tag sink batches with the payload's schema - #4204

Merged
hubcio merged 18 commits into
apache:masterfrom
MarcusKainth:fix/connectors-payload-schema-tagging
Sep 22, 2026
Merged

hubcio merged 18 commits into
apache:masterfrom
MarcusKainth:fix/connectors-payload-schema-tagging

Conversation

@MarcusKainth

@MarcusKainth MarcusKainth commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Which issue does this PR address?

Closes #4053

Prior art: #3669.

Rationale

Sinks configured avro, proto or flatbuffer receive the wrong Payload variant. JSON-only sinks drop the batch after the offset has already been committed, so those messages are lost with no redelivery, and sinks that accept the raw variants store base64 of JSON text as though it were Avro bytes.

What changed?

The runtime tagged each batch with the decoder's schema, which names the wire format a decoder reads rather than the variant it returns. All three decoders extract to Payload::Json under the configuration the runtime gives them, and the SDK rebuilds the payload from that tag alone, so a sink was handed a Payload::Avro holding JSON.

The tag now comes from the payload itself through Payload::schema(), read after transforms run. One Schema covers a whole FFI call, so messages are grouped into contiguous runs of the same variant, and a uniform batch stays one run and one consume() call. The new integration test covers avro only, because StreamConsumerConfig has no schema configuration for flatbuffer or proto.

Compatibility

Three of the six stream schema values change tag. avro, flat_buffer and proto all extract to JSON under the runtime's default decoder settings (avro.rs L40, flatbuffer.rs L42, proto.rs L48), so a batch from any of them now arrives tagged json. S3, HTTP and SurrealDB will write a JSON document for those streams instead of base64 of JSON text labelled as the wire format. That output was wrong, so this is a correction, but anyone reading it will see the shape change. A proto stream into Doris improves outright: it failed every poll before and writes rows now. json, text and raw streams are unaffected, because the decoder's schema and the payload's variant already agree.

Payload::Proto now reaches a sink, which it never did before. A proto_convert transform hands it over whenever it has no descriptor, and also whenever it has one and the message is not a top-level JSON object, because encode_json_with_schema returns Err for any array or scalar (proto_convert.rs L285). On a json stream the same messages previously arrived as Payload::Json. Every sink that writes documents now reads a Payload::Proto that holds JSON as that document: ClickHouse (JSONEachRow and RowBinary), Doris, Delta, Iceberg, Elasticsearch, Meilisearch, Quickwit, S3, SurrealDB and HTTP. Proto text that is not JSON is handled the way each sink handles Text. RowBinary keeps failing a batch on a non-object document, as it did on master. Two smaller shifts remain: a text or raw stream with proto_convert now hands the sink Payload::Proto where it handed Text or Raw, which changes nothing now that both are treated alike; and a descriptor-backed proto_convert on a json stream, which the SDK dropped on master because the json tag sent protobuf bytes through the JSON parser, now arrives as Raw.

iggy_connector_sdk moves from 0.4.0 to 0.5.0. No FFI signature or Schema variant changed, but the proto tag changed meaning on the wire. A plugin built against 0.4.0 is better off under this runtime on avro, flat_buffer and proto streams, because it now receives a json tag it can read, and worse off on one pipeline: a json stream with proto_convert, where it rebuilds the proto-tagged run as Payload::Raw where master handed it Payload::Json. Plugins built against 0.4.0 or earlier must be rebuilt.

avro, flat_buffer and proto streams now pay a per-message JSON parse inside the plugin. They avoided it before by handing the plugin a mislabelled payload it could not use.

One poll can reach a plugin as several consume() calls, one per contiguous payload variant, all repeating the same current_offset. iggy_connector_sink_runs_total counts those calls; the ffi stage histogram stays one sample per batch, summed over its runs. SurrealDB's iggy_schema column records the variant received, so rows written before this change on avro, flat_buffer and proto streams disagree with new ones.

Local Execution

  • Passed. cargo fmt, cargo sort --no-format, Clippy with all features and all targets on every touched crate and the integration crate, taplo, markdownlint, license headers, trailing whitespace and newline, typos and cargo machete all pass. Unit tests pass for the runtime (236), the SDK (185) and the ten sinks touched, including the new tests for each. The schema_tagging, Elasticsearch, ClickHouse and Doris proto_text integration tests pass against a real server, runtime and containers.
  • The same test files were run against a master worktree, d8a8ece and this branch. The three pipeline tests pass on master, fail on d8a8ece and pass here; the schema-tagging and sink container tests fail on master and pass on both later refs; every json, text, raw and source-path control passes on all three. The full table is in the review summary comment.
  • Pre-commit hooks ran on every commit.

AI Usage

  1. Claude Opus 5.
  2. Review and rebase, not implementation. I wrote the code. Claude reviewed it afterwards and I applied its suggestions with my own reasoning. Claude also rebased the branch onto current master and resolved the conflicts against fix(connectors): report failures and correct format conversion #4152.
  3. The unit tests pin the schema tag per variant, the one-FFI-call-per-uniform-batch invariant and the Proto round-trip. The integration test sends real Avro datums through a running server and connectors runtime and asserts the sink is handed Payload::Json. The local checks listed above were run.
  4. Yes.

A sink receives the wrong Payload variant when its stream is configured
avro, proto or flatbuffer. The runtime tagged each batch with the
decoder's schema, which names the wire format a decoder reads rather
than the variant it returns, and the SDK rebuilds the payload from that
tag alone. JSON-only sinks dropped the batch after the offset had been
committed, and sinks that take the raw variants stored base64 of the
JSON as Avro bytes.

Read the tag from the payload the sink will actually receive, after
transforms run. One Schema covers a whole FFI call, so messages are
grouped into contiguous runs of the same variant and each run is sent
on its own.

Also corrects the sink documentation on where a payload's variant comes
from, and the sdk protobuf example, which could not load.

Closes apache#4053
Every new run reserved the messages still to come, so a batch that
alternated payload variants reserved O(n^2) message slots. Only the
first run is sized to the batch now; a later run is rare enough to grow
on demand.

Also pins the empty-batch fallback against a non-default schema, since
asserting Json could not tell the stream's configured schema apart from
Schema::default(), and waits for the payload log lines rather than the
batch header in the integration test, which could otherwise read the
log file between the two.
@github-actions

Copy link
Copy Markdown

Thanks for the PR. It is labeled S-waiting-on-review and queued for review.

Slash commands (own line, regular comment) move it around the queue:

  • /ready - back to S-waiting-on-review after addressing feedback
  • /author - flip to S-waiting-on-author while you finish changes
  • /request-review @user-or-team - request a reviewer
  • /pin - exempt the PR from the stale bot, /unpin to undo

See CONTRIBUTING.md for details.

@github-actions github-actions Bot added the S-waiting-on-review PR is waiting on a reviewer label Sep 16, 2026
@codecov

codecov Bot commented Sep 17, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.34146% with 58 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.63%. Comparing base (fa80704) to head (04c45ca).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
core/connectors/runtime/src/sink.rs 96.33% 14 Missing and 2 partials ⚠️
core/connectors/sdk/src/lib.rs 92.85% 5 Missing and 7 partials ⚠️
...ore/connectors/sinks/elasticsearch_sink/src/lib.rs 66.66% 10 Missing and 1 partial ⚠️
core/connectors/sdk/src/sink.rs 89.02% 8 Missing and 1 partial ⚠️
...onnectors/sdk/src/transforms/flatbuffer_convert.rs 20.00% 4 Missing ⚠️
core/connectors/sinks/clickhouse_sink/src/body.rs 94.87% 1 Missing and 1 partial ⚠️
...ore/connectors/sdk/src/transforms/proto_convert.rs 97.77% 1 Missing ⚠️
core/connectors/sinks/http_sink/src/lib.rs 90.90% 0 Missing and 1 partial ⚠️
...rs/sinks/iceberg_sink/src/router/dynamic_router.rs 95.45% 1 Missing ⚠️
core/connectors/sinks/meilisearch_sink/src/lib.rs 96.15% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@              Coverage Diff              @@
##             master    #4204       +/-   ##
=============================================
- Coverage     87.59%   68.63%   -18.97%     
  Complexity     1575     1575               
=============================================
  Files          1284     1282        -2     
  Lines        224493   185387    -39106     
  Branches     187856   148750    -39106     
=============================================
- Hits         196653   127246    -69407     
- Misses        23127    53349    +30222     
- Partials       4713     4792       +79     
Components Coverage Δ
Rust Core 64.94% <93.27%> (-23.76%) ⬇️
Java SDK 68.68% <ø> (ø)
C# SDK 77.41% <ø> (+0.06%) ⬆️
Python SDK 90.97% <ø> (ø)
PHP SDK 85.67% <ø> (ø)
Node SDK 96.45% <ø> (+0.08%) ⬆️
Go SDK 70.08% <ø> (+0.02%) ⬆️
Files with missing lines Coverage Δ
core/connectors/runtime/src/benchmark.rs 94.40% <100.00%> (+0.07%) ⬆️
core/connectors/runtime/src/metrics.rs 99.67% <100.00%> (+0.01%) ⬆️
core/connectors/sinks/delta_sink/src/sink.rs 66.10% <100.00%> (+43.02%) ⬆️
core/connectors/sinks/doris_sink/src/lib.rs 95.23% <100.00%> (+0.11%) ⬆️
...re/connectors/sinks/iceberg_sink/src/router/mod.rs 75.07% <100.00%> (+1.63%) ⬆️
core/connectors/sinks/quickwit_sink/src/lib.rs 93.90% <100.00%> (+0.09%) ⬆️
core/connectors/sinks/s3_sink/src/formatter.rs 85.13% <100.00%> (+1.20%) ⬆️
core/connectors/sinks/surrealdb_sink/src/lib.rs 85.87% <100.00%> (+0.13%) ⬆️
...ore/connectors/sdk/src/transforms/proto_convert.rs 80.86% <97.77%> (+1.21%) ⬆️
core/connectors/sinks/http_sink/src/lib.rs 87.32% <90.90%> (-0.11%) ⬇️
... and 8 more

... and 412 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment thread core/connectors/sdk/src/lib.rs
Comment thread core/connectors/runtime/src/sink.rs Outdated
Comment thread core/connectors/sdk/src/lib.rs

@rohankumardubey rohankumardubey left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MarcusKainth left couple of comments to address. Thanks for contributing.

hubcio and others added 2 commits September 17, 2026 18:18
Schema::Proto means protobuf wire bytes when a source plugin sets it and
a Payload::Proto string when the runtime tags a batch from the payload,
so the sink SDK rebuilds through Payload::try_from_schema, an exact
inverse of Payload::schema. Elasticsearch, Meilisearch and the ClickHouse
string passthrough take proto text the way they take text.

Also corrects the run-grouping comment, which claimed one FFI call per
batch when ProtoConvert can return a different variant per message.

@mlevkov mlevkov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adversarial multi-agent review of d8a8ece9: four independent reviewers, then clean-room validators who saw only the raw claims and the diff, then tiebreaks on the three contested items. 10 of 22 initial claims were corrected and 1 was removed before anything reached this comment.

First, the parts that hold. The round trip is correct for all six Payload variants, the split of Schema::try_into_payload into two inverses is the right shape, and every line number in your reply to @rohankumardubey and @jiengup checks out. The earlier regression is genuinely fixed.

Three findings look like merge blockers, all the same shape: this PR establishes a remedy and applies it to three sinks out of six.

You added | Payload::Proto(text) to the Payload::Text arm in Elasticsearch, Meilisearch and the ClickHouse passthrough. build_json_body, build_row_binary_body, Doris, Delta and Iceberg did not get it, and all of them lose committed messages as a result. The offset commits at poll time (runtime/src/sink.rs:515), there is no replay and no dead-letter queue, so a failed or skipped batch is gone. iggy_connector_messages_processed_total counts the dropped rows and iggy_connector_errors_total stays flat, so the dashboard reads the same before and after the upgrade. Details inline on body.rs and doris_sink/src/lib.rs.

The Compatibility section is right that this surfaces a misconfiguration. The part that does not follow is the surfacing: on master these pipelines delivered correct rows, and after the upgrade they deliver none, with metrics reporting success.

Two corrections to the section itself: flat_buffer and proto streams also change tag and appear in neither the affected nor the unaffected list, and the descriptor-less case is not the only trigger. encode_json_with_schema returns Err for any non-object top-level JSON (proto_convert.rs:285), so a fully configured pipeline takes the fallback on every array, string, number, boolean and null message.

Also worth a look, not blocking:

  • Four test gaps. The round-trip test compares only the variant, the per-run subtraction has no failing-run test, the new Elasticsearch test asserts the flattened shape, and the Any test does not check that type_url and value survived. Inline where they anchor.
  • surrealdb_sink/src/lib.rs:838 persists messages_metadata.schema into iggy_schema, so new rows on an avro stream now say json. Operator-visible, worth a README line.
  • http_sink/src/lib.rs:1197 says a nonzero return records no processed messages for the batch. The runtime now subtracts only the failed run.
  • .claude/skills/connector-sdk/SKILL.md:82 points item 12 at a schema list in core/connectors/README.md. That file has none; the list is in sdk/README.md.
  • Pre-existing, exposed rather than caused: avro_convert.rs:125 builds an encoder per message, proto_convert.rs:630 serializes a document it then drops, proto_convert.rs:296 can produce an empty Payload::Raw that now gets written, and elasticsearch_sink/src/lib.rs:373 clones and parses per Raw message.

Simplifications, all optional: runtime/src/sink.rs:606 can use one flat Vec with run offsets instead of a Vec per run (postcard writes a borrowed slice and an owned Vec identically, so the FFI bytes are unchanged); sdk/src/lib.rs:294 can delegate five of six arms to Payload::try_from_schema, though it needs the variants named rather than a _ arm or it removes the exhaustiveness check, and mut value then trips -D warnings; runtime/src/sink.rs:751 can accumulate instead of subtracting. At flatbuffer_convert.rs:146 I would keep the Err return rather than unreachable!(), since the error drops one message and a panic kills the consumer task.

Comment thread core/connectors/sinks/clickhouse_sink/src/body.rs Outdated
Comment thread core/connectors/sinks/doris_sink/src/lib.rs Outdated
Comment thread core/connectors/runtime/src/sink.rs
Comment thread core/connectors/sinks/README.md
Comment thread core/connectors/sdk/src/sink.rs
Comment thread core/connectors/sinks/meilisearch_sink/src/lib.rs
Comment thread core/integration/tests/connectors/elasticsearch/proto_text.rs Outdated
Comment thread core/connectors/runtime/src/sink.rs Outdated
Comment thread core/connectors/sdk/src/lib.rs
Comment thread core/connectors/runtime/src/sink.rs
@hubcio hubcio added S-waiting-on-author PR is waiting on author response and removed S-waiting-on-review PR is waiting on a reviewer labels Sep 18, 2026
The meaning of Schema::Proto on the wire changed: the runtime tags a sink
batch from the payload, so a proto tag names a Payload::Proto string
rather than protobuf wire bytes. A plugin built against 0.4.0 still runs
the old inverse and rebuilds such a run as Payload::Raw, and nothing at
load time can catch that because iggy_sink_version reports the plugin's
own crate version. The SDK is neither published nor tagged, so 0.5.0 is a
compatibility marker rather than a release. The connector-sdk skill now
treats a Schema variant that changes meaning as breaking.
A proto_convert transform with no descriptor, or one that cannot encode a
message, hands the sink Payload::Proto holding the JSON it was given. On
master that run was tagged json and reparsed, so the document sinks
loaded it. Tagging from the payload made ClickHouse skip the rows and
return success, Doris abort the poll, Delta fail the batch, Iceberg drop
the rows, and Elasticsearch and Meilisearch flatten the document to one
text field. The offset commits at poll time, so each of those was silent
loss or a lossy index.

Payload::json_document borrows a Json payload and parses a Proto one, so
every document sink takes the document when there is one and keeps its
existing handling when there is not: proto text that is not JSON still
skips, aborts or indexes as text as before. The Elasticsearch integration
test asserts the original fields rather than the flattened shape, and
ClickHouse and Doris gain the same pipeline end to end. The sink
round-trip test compares bytes as well as the variant.
Quickwit flattened Payload::Proto to a text wrapper before this branch,
the same shape Elasticsearch and Meilisearch had. It now takes the
document when the text is JSON and keeps the wrapper otherwise, so the
document sinks agree on what proto text means.
Nothing recorded that a batch was split into several consume() calls,
and processed_count started at the batch size and was debited per failed
run. iggy_connector_sink_runs counts the calls, so runs per batch is that
counter over the total-stage sample count, and the benchmark event
carries the same number. processed_count accumulates in the success
branch, which removes the underflow class outright. The ffi stage sample
stays one per batch, summed over its runs, and the README says so.
Tests cover one failing run in a split batch and every run failing.
The sink SDK's rebuild had no direct test; only an integration test
crossed it. The container is now driven in process with postcard bytes,
so a proto run arriving as Payload::Proto is asserted where the runtime
cannot influence it, with json, text and raw runs as controls. The
protobuf Any test asserts type_url and value rather than key presence.
Transforms run inside the runtime's consume task, so a panic on a
conversion pair that validate_conversion did not accept would take the
task down rather than one message. The pair cannot reach that arm today;
it now returns the error validation would have returned.
One poll can arrive as several consume() calls that repeat the same
current_offset, and an empty batch keeps the stream's configured schema.
The sdk README limits the source_format rejection to flatbuffer_convert,
the only transform with the guard, and states that chain order is
undefined. The http sink's runtime note and the SurrealDB iggy_schema
column are updated to match.
… HTTP

The same shape as the document sinks: a proto_convert transform with no
descriptor hands over the JSON it was given as proto text, and these
three wrote it as a string or base64 where a batch tagged json used to
give them the document. All three now read the document when the text is
JSON. HTTP sends proto text that is not JSON as a plain string, the way it
sends Text, since a text stream with proto_convert now delivers Proto
where it delivered Text.
@MarcusKainth

Copy link
Copy Markdown
Contributor Author

Thanks for the depth here. All twelve threads are addressed and the per-thread replies cite the commits. Three things I want on record at the top level.

First, the evidence. Rather than argue the before and after, I ran the same test files against a master worktree, d8a8ece, and this branch.

test master d8a8ece this branch
Fix proof: fails on master, passes here
given_a_proto_tagged_run_when_the_container_consumes_should_hand_the_sink_a_proto_payload FAIL PASS PASS
given_an_avro_stream_when_the_sink_consumes_should_receive_json_payloads FAIL PASS PASS
proto_payloads_that_are_not_json_are_stored_as_text FAIL PASS PASS
string_body_proto_payload_is_written_as_text FAIL PASS PASS
Blockers demonstrated: passed on master, broke on d8a8ece, pass here
given_a_proto_convert_transform_when_the_sink_consumes_should_index_the_document PASS FAIL PASS
given_a_proto_convert_transform_when_the_sink_consumes_should_store_the_rows PASS FAIL PASS
given_proto_text_messages_should_store PASS FAIL PASS
Round-two gaps closed: failed on both earlier refs, pass here
given_auto_payload_proto_holding_json_should_store_queryable_json FAIL FAIL PASS
given_proto_payload_holding_json_should_send_the_document FAIL FAIL PASS
given_proto_text_holding_json_when_extracted_should_preserve_the_document FAIL FAIL PASS
given_proto_text_payloads_holding_json_should_write_them_as_rows FAIL FAIL PASS
given_proto_text_that_is_not_json_should_send_it_as_a_string FAIL FAIL PASS
json_body_pretty_proto_json_is_written_as_one_line FAIL FAIL PASS
json_body_proto_payload_holding_json_is_written FAIL FAIL PASS
proto_payload_holding_json_is_written_as_a_document FAIL FAIL PASS
proto_payloads_holding_json_are_indexed_as_documents FAIL FAIL PASS
row_binary_body_proto_payload_holding_json_writes_bytes FAIL FAIL PASS
Unchanged behaviour: pass everywhere
given_a_json_tagged_run_when_the_container_consumes_should_hand_the_sink_a_json_payload PASS PASS PASS
given_a_raw_tagged_run_when_the_container_consumes_should_hand_the_sink_a_raw_payload PASS PASS PASS
given_a_text_tagged_run_when_the_container_consumes_should_hand_the_sink_a_text_payload PASS PASS PASS
given_auto_payload_proto_text_should_store_text PASS PASS PASS
given_bytes_that_are_not_an_any_when_rebuilt_from_a_wire_tag_should_fall_back_to_raw PASS PASS PASS
given_only_proto_text_that_is_not_json_should_fail_with_invalid_payload_type PASS PASS PASS
given_protobuf_wire_bytes_when_rebuilt_from_a_wire_tag_should_stay_an_any_document PASS PASS PASS
json_body_non_json_proto_payload_is_skipped PASS PASS PASS
proto_text_that_is_not_json_is_written_as_a_string PASS PASS PASS
row_binary_body_non_json_proto_payload_is_skipped PASS PASS PASS

The rows that fail on master and pass here are the fix. Two of them fail on master only because it had no Payload::Proto arm at all, in Meilisearch and the ClickHouse string passthrough, so they are there for completeness rather than as proof. The middle group is the three blockers demonstrated end to end: ClickHouse, Doris and Elasticsearch each pass on master, fail on d8a8ece, and pass again now. The rows that pass on all three refs are the streams and paths this PR claims not to touch.

Building that matrix surfaced two additions to the Compatibility section. A text or raw stream with proto_convert now hands the sink Payload::Proto where master handed it Text or Raw; every sink now treats proto text that is not JSON the way it treats Text, including HTTP, which used to base64-encode it. And a descriptor-backed proto_convert on a json stream was dropped by the SDK on master, because the json tag sent protobuf bytes through the JSON parser; it now arrives as Raw.

A second pass on my side after the fixes turned up the same defect in three more sinks and one metric detail, so for completeness:

  • S3, SurrealDB's auto format and HTTP wrote proto text holding JSON as a string or base64 where master wrote the document. All three now read it as the document (c1bbb4c); HTTP sends proto text that is not JSON as a plain string, the way it sends Text.
  • iggy_connector_sink_runs increments after the calls are made rather than before, so it counts calls that happened (2dbebc4).
  • RowBinary is unchanged on purpose: non-object proto text fails the batch, which is what the same array did on master when it arrived as JSON, and whole-batch failure on a bad row is the documented contract.
  • A run that holds no document now fails in Iceberg where a mixed batch used to skip those messages beside their siblings. They were dropped either way; now they are counted as an error.
  • No warn log on a split batch. A threshold log would fire on every batch of a descriptor-backed proto_convert, and the counter carries the signal.

Second, four attributions I would correct. RowBinary never had a failure signal to lose; the builder on master is byte-identical and the regression was loss rather than silence. Iceberg is not the same shape as Doris and Delta: it silently drops via filter_map and only errors when every row is non-JSON, which is worse, and it has a second site in the dynamic router. Both are fixed. The unreachable!() in flatbuffer_convert.rs is on master at L172 verbatim; this PR deleted a helper and left the panic where it was. I agree it should be an Err, so I have made it one. The SKILL.md item 12 pointer was already stale on master as item 10, and I have fixed it as a drive-by.

Third, the SDK bump. Agreed and done, 0.4.0 to 0.5.0, with the rebuild requirement in the PR body, and the skill now treats a Schema variant changing meaning as breaking. SurrealDB's iggy_schema column is covered in its README: it now records the variant received, so avro streams write json next to a real document where they previously wrote avro next to base64 of JSON text. The http sink comment is updated to per-run wording. The pre-existing items you listed (the encoder per message in avro_convert, the dropped serialisation in proto_convert, the empty Payload::Raw, and the per-Raw clone in Elasticsearch) are all real. The Elasticsearch clone is needed because simd_json mutates its input even on failure; the rest I have left for a follow-up so this PR stays a fix.

@MarcusKainth

Copy link
Copy Markdown
Contributor Author

/ready

@github-actions github-actions Bot added S-waiting-on-review PR is waiting on a reviewer and removed S-waiting-on-author PR is waiting on author response labels Sep 20, 2026
@hubcio
hubcio dismissed rohankumardubey’s stale review September 21, 2026 11:09

committer fixed all issues

@hubcio hubcio left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for history: #3669 attempted the same fix in july and closed stale without a verdict, worth a link in the description.

overall i like what you did here. just nits below, once you fix i think we can merge

Comment thread core/connectors/sdk/src/lib.rs
Comment thread core/connectors/sdk/README.md
Comment thread core/connectors/sinks/meilisearch_sink/src/lib.rs
Comment thread core/connectors/sinks/meilisearch_sink/src/lib.rs Outdated
Comment thread core/connectors/sinks/iceberg_sink/src/router/dynamic_router.rs
Comment thread core/connectors/runtime/src/sink.rs Outdated
@github-actions github-actions Bot added S-waiting-on-author PR is waiting on author response and removed S-waiting-on-review PR is waiting on a reviewer labels Sep 21, 2026
The same four lines rebuilding a Payload::Proto that holds JSON into a
Payload::Json had been copied into five sinks, each under its own wording of
the same comment. Payload::into_json_document states the rule once, next to
json_document, so it lives with the type rather than with every sink that
has to follow it.

The Iceberg dynamic router now normalizes at the top of its routing loop
rather than at each point of use, so the proto text behind a route field is
no longer parsed once for routing and again when the data files are written.
The run-splitting test rebuilt the four retagged messages that split_batch
already builds, while the two tests below it call the helper.

An empty batch still reaching the sink as exactly one call is a contract the
Sink::consume rustdoc is about to state, so it gets a test of its own rather
than resting on a comment in the runtime.
A poll reaching the plugin as several consume() calls, each repeating one
current_offset, was documented only in the sinks README, while the rustdoc on
Sink::consume said a batch arrives every time one is received. A plugin author
reading the trait was told the opposite of what the runtime does, so the
contract now sits on consume and on MessagesMetadata as well.

The SDK README listed the retry removals as the only changes that break
out-of-tree plugins; the schema tag changing meaning belongs there too. The
Meilisearch README described proto nowhere, leaving its readers to infer from
"unsupported payload schemas are skipped" something the sink stopped doing.
@MarcusKainth

Copy link
Copy Markdown
Contributor Author

The description now links #3669.

All six threads are addressed in 535214c, e587e4d and 093f5eb. origin/master is merged in at 233211c and the verification chain is green on the merged tree: fmt, sort, workspace clippy with all features and all targets, taplo, markdownlint, license headers, whitespace and newline, typos, unit tests for the SDK, the runtime and every touched sink, and the schema_tagging, Meilisearch, Elasticsearch, Quickwit, HTTP, SurrealDB and Iceberg integration suites against a real server, runtime and containers.

@MarcusKainth

Copy link
Copy Markdown
Contributor Author

/ready

@github-actions github-actions Bot added S-waiting-on-review PR is waiting on a reviewer and removed S-waiting-on-author PR is waiting on author response labels Sep 22, 2026
@hubcio
hubcio merged commit 7bae8ec into apache:master Sep 22, 2026
102 checks passed
@github-actions github-actions Bot removed the S-waiting-on-review PR is waiting on a reviewer label Sep 22, 2026
kriti-sc added a commit to kriti-sc/iggy that referenced this pull request Sep 22, 2026
master moved 192 commits since this branch was cut and two of them land
on the same code.

apache#4152 already reads the FFI status a sink returns: it logs the failure
and counts it, then still returns Ok. apache#4204 then split a batch into runs
of contiguous payload variants, so one batch is now several FFI calls,
each with its own status, and a failing run does not stop the ones after
it.

The single-call block here gives way to that loop. Halting still needs
one verdict per batch, so the loop records the first non-zero status and
the batch is committed only when the sink took every run. A partly
accepted batch therefore commits nothing and its accepted prefix is
redelivered. That keeps the semantics this branch already shipped, but
it is not the tightest watermark available: runs are contiguous and in
offset order, so the end of the last consecutively accepted run would
commit more without losing anything. Left alone here because it is a
behaviour change rather than a conflict.

The config tests keep both sides, and to_sink_config became
into_sink_config taking a ConnectorKey. Module ordering and a stray
blank line in a toml are fixed, which clears the two one-line CI
failures on this head.

Nothing here answers the review on apache#3954.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

connectors: batches are tagged with the decoder's schema, not the payload's, so avro/proto/flatbuffer reach sinks as the wrong Payload variant

6 participants