[connectors] Add preprocessor API with ConnectorMetadata#6580
Conversation
| &mut self, | ||
| data: &[u8], | ||
| metadata: Option<ConnectorMetadata>, | ||
| ) -> (Vec<u8>, Vec<ParseError>); |
There was a problem hiding this comment.
Nit: showing process_with_metadata with a bare ; makes it look like a required method. Since it has a default impl that delegates to process, consider showing the body so users know they only need to override it when they actually want the metadata:
fn process_with_metadata(
&mut self,
data: &[u8],
metadata: Option<ConnectorMetadata>,
) -> (Vec<u8>, Vec<ParseError>) {
self.process(data)
}| /// | ||
| /// # Arguments | ||
| /// * `data` - Raw input data bytes | ||
| /// * `_metadata` - Connector metadata |
There was a problem hiding this comment.
Nit: the doc comment says _metadata — drop the underscore prefix in documentation (it's an implementation detail for suppressing the unused-variable warning in the default impl).
mythical-fred
left a comment
There was a problem hiding this comment.
Backwards-compatible extension done the right way — new process_with_metadata with a default that delegates to process, so no existing impl breaks. Tests exercise both the "metadata-aware" and "no metadata" paths through the trait's public conversion into Variant, which is the right seam for user-defined preprocessors.
A couple of small things worth considering:
-
metadata.clone()per chunk in the streaming path. InStreamingPreprocessedParser::parse,metadatais cloned once forprocess_with_metadataand then again inside thewhile let Some(chunk)loop for every parser call.ConnectorMetadatais aBTreeMap<Variant, Variant>, andVariantclones aren't free (some variants carryArcs, others own their bytes). For high-throughput sources with many attributes this becomes measurable. The typical fix is to hand the parser an&Option<ConnectorMetadata>(or wrap the metadata inArcat the top of the pipeline) so the leaves clone only when they actually need to keep it. Not blocking for this PR, but worth a follow-up. -
The trait in
udf.mdis now shown without the default body, which reads as "you must implementprocess_with_metadata". Since the whole point of the new signature is that it defaults toprocess, the doc snippet should either show the default ({ self.process(data) }) or add a one-liner immediately below the code block saying so. Otherwise UDF authors will wonder why the compiler doesn't complain when they omit it. -
Minor: the doc paragraph
If the connector does not provide metadata, its value is None.— recommend`None`in code font, and the sentence just above says "an optional argument" — you can drop one of them.
The preprocess → preprocessor field-name doc fix is a nice catch, thanks.
snkas
left a comment
There was a problem hiding this comment.
If I understand correctly, it's backward compatible in that there is a default implementation for the process_with_metadata variant that drops the metadata?
What's the difference between process(data) and process_with_metadata(data, None)? If a user implements both, then the former is never used?
Could you add an end-to-end integration test for it?
|
|
||
| impl Preprocessor for TopicPrefixPreprocessor { | ||
| fn process(&mut self, data: &[u8]) -> (Vec<u8>, Vec<ParseError>) { | ||
| (data.to_vec(), vec![]) |
There was a problem hiding this comment.
This seems backward, in that shouldn't process call process_with_metadata(data, None) rather than the other way around?
There was a problem hiding this comment.
I did it this way for backwards compatibility - since we have users who have already implemented process but didn't know about the other method.
There was a problem hiding this comment.
Apparently it's possible to have two default implementations which call each other, so maybe I will do that instead.
| let (pre_data, mut pre_errors) = self.preprocessor.process(data); | ||
| let (pre_data, mut pre_errors) = self | ||
| .preprocessor | ||
| .process_with_metadata(data, metadata.clone()); |
There was a problem hiding this comment.
Why is metadata passed by value instead of an immutable ref?
There was a problem hiding this comment.
I think this is a design mistake in the original parser API, I will submit a commit to change this.
|
I have force-pushed 2 commits. |
mythical-fred
left a comment
There was a problem hiding this comment.
Rebase LGTM. Arc-based sharing is correct: Arc::make_mut gives copy-on-write semantics on insert, and the two From<...> for Variant impls correctly distinguish the owned (move) and borrowed (cheap Arc::clone) paths. Two tiny nits inline; none blocking.
| the pipeline with a overflow. | ||
|
|
||
| ::: | ||
|
|
There was a problem hiding this comment.
Nit: "crash the pipeline with a overflow" -> "with a stack overflow" (or "with an infinite recursion"). The Rust source doc comment on process_with_metadata already says "stack overflow" correctly; docs should match.
| /// # Arguments | ||
| /// * `data` - Raw input data bytes | ||
| /// * `_metadata` - Connector metadata | ||
| /// |
There was a problem hiding this comment.
Nit (carried from prior review): the Arguments doc still names the param _metadata. The underscore is only there to silence the unused-var warning in the default body; readers of the rustdoc shouldn't see it. Prefer * metadata - Connector metadata. Cosmetic.
| /// to populate some of the table columns. | ||
| #[derive(Default, Clone, Debug, PartialEq, Eq)] | ||
| pub struct ConnectorMetadata(BTreeMap<Variant, Variant>); | ||
| pub struct ConnectorMetadata(Arc<BTreeMap<Variant, Variant>>); |
There was a problem hiding this comment.
I realize this is pre-existing but why are we using our sql types here in something unrelated to sql types
There was a problem hiding this comment.
I wondered the same, these should probably be just strings. @ryzhyk do you know?
There was a problem hiding this comment.
Connector metadata exists so we can put it in SQL tables. Which is why we use SQL types.
snkas
left a comment
There was a problem hiding this comment.
Is the preprocessor only used if the trait is implemented? As the default implementation would cause a stack overflow.
mythical-fred
left a comment
There was a problem hiding this comment.
The added process_with_metadata API and its tests look good — the default-forwarding pair is a clean way to keep the trait backward-compatible, and the TopicPrefixPreprocessor test exercises the metadata path end-to-end through the public Variant conversion. Small doc nit inline; my prior APPROVE stands.
|
|
||
| A preprocessor that implements neither processing method compiles | ||
| without warnings, but the two default implementations will crash | ||
| the pipeline with a overflow. |
There was a problem hiding this comment.
Typo — should be a stack overflow, matching the trait docstring in preprocess.rs (which says "panic at runtime with a stack overflow"). Consistency helps readers who bounce between docs and source.
088acd0 to
e655a1a
Compare
Signed-off-by: Mihai Budiu <mbudiu@feldera.com>
mythical-fred
left a comment
There was a problem hiding this comment.
Nice follow-up on tip 04ad9018:
ConnectorMetadatagains a proper public read API (get,get_by_name) with aninsert/getkey-encoding invariant test — much cleaner than round-tripping throughVariant::from(metadata.clone())and pattern-matching on the resultingVariant::Map.- The
topictypo I flagged ("a overflow" → "a stack overflow") is fixed in the trait doc. - Docs updated to teach
get_by_nameas the common lookup, with a copy-pasteable Kafka-topic example. - Test coverage for both getters and for the metadata-carrying preprocessor path.
APPROVE stands.
Fixes #6578
Two commits:
ConnectorMetadataby reference to parsers. I think this was an API design bugAdds a new method to the
Preprocessor::process_with_metadatatrait which receives the connector metadata as well.This change should be backwards-compatible.
Checklist