laminar_connectors/kafka/avro_serializer/
mod.rs1use std::sync::atomic::{AtomicU32, Ordering};
9use std::sync::Arc;
10
11use arrow_array::RecordBatch;
12use arrow_avro::schema::FingerprintStrategy;
13use arrow_avro::writer::format::AvroSoeFormat;
14use arrow_avro::writer::{Encoder, WriterBuilder};
15use arrow_schema::SchemaRef;
16use parking_lot::Mutex;
17
18use crate::error::SerdeError;
19use crate::kafka::schema_registry::SchemaRegistryClient;
20use crate::serde::{Format, RecordSerializer};
21
22pub struct AvroSerializer {
27 schema_id: Arc<AtomicU32>,
29 schema: SchemaRef,
31 schema_registry: Option<Arc<SchemaRegistryClient>>,
33 encoder: Mutex<Option<(u32, Encoder)>>,
34}
35
36impl AvroSerializer {
37 #[must_use]
41 pub fn new(schema: SchemaRef, schema_id: u32) -> Self {
42 Self {
43 schema_id: Arc::new(AtomicU32::new(schema_id)),
44 schema,
45 schema_registry: None,
46 encoder: Mutex::new(None),
47 }
48 }
49
50 #[must_use]
55 pub fn with_shared_schema_id(
56 schema: SchemaRef,
57 schema_id: Arc<AtomicU32>,
58 registry: Option<Arc<SchemaRegistryClient>>,
59 ) -> Self {
60 Self {
61 schema_id,
62 schema,
63 schema_registry: registry,
64 encoder: Mutex::new(None),
65 }
66 }
67
68 #[must_use]
70 pub fn with_schema_registry(
71 schema: SchemaRef,
72 schema_id: u32,
73 registry: Arc<SchemaRegistryClient>,
74 ) -> Self {
75 Self {
76 schema_id: Arc::new(AtomicU32::new(schema_id)),
77 schema,
78 schema_registry: Some(registry),
79 encoder: Mutex::new(None),
80 }
81 }
82
83 #[must_use]
85 pub fn schema_id_handle(&self) -> Arc<AtomicU32> {
86 Arc::clone(&self.schema_id)
87 }
88
89 #[must_use]
91 pub fn schema_id(&self) -> u32 {
92 self.schema_id.load(Ordering::Relaxed)
93 }
94
95 #[must_use]
97 pub fn has_schema_registry(&self) -> bool {
98 self.schema_registry.is_some()
99 }
100
101 pub fn prepare(&self) -> Result<(), SerdeError> {
106 let id = self.schema_id();
107 let mut prepared = self.encoder.lock();
108 if prepared
109 .as_ref()
110 .is_some_and(|(existing, _)| *existing == id)
111 {
112 return Ok(());
113 }
114 if id == 0 || id > i32::MAX as u32 {
115 return Err(SerdeError::MalformedInput(
116 "Avro writer requires a positive signed schema ID".into(),
117 ));
118 }
119 let encoder = WriterBuilder::new(self.schema.as_ref().clone())
120 .with_fingerprint_strategy(FingerprintStrategy::Id(id))
121 .build_encoder::<AvroSoeFormat>()
122 .map_err(|error| {
123 SerdeError::MalformedInput(format!("unsupported Avro writer: {error}"))
124 })?;
125 *prepared = Some((id, encoder));
126 Ok(())
127 }
128
129 fn encode(&self, batch: &RecordBatch) -> Result<arrow_avro::writer::EncodedRows, SerdeError> {
130 self.prepare()?;
131 let mut prepared = self.encoder.lock();
132 let (_, encoder) = prepared
133 .as_mut()
134 .ok_or_else(|| SerdeError::MalformedInput("Avro writer was not prepared".into()))?;
135 let result = encoder.encode(batch);
136 let rows = encoder.flush();
138 result
139 .map_err(|error| SerdeError::MalformedInput(format!("Avro encode error: {error}")))?;
140 Ok(rows)
141 }
142}
143
144impl RecordSerializer for AvroSerializer {
145 fn serialize(&self, batch: &RecordBatch) -> Result<Vec<Vec<u8>>, SerdeError> {
146 if batch.num_rows() == 0 {
147 return Ok(Vec::new());
148 }
149 Ok(self.encode(batch)?.iter().map(|row| row.to_vec()).collect())
150 }
151
152 fn serialize_batch(&self, batch: &RecordBatch) -> Result<Vec<u8>, SerdeError> {
153 if batch.num_rows() == 0 {
154 return Ok(Vec::new());
155 }
156 let rows = self.encode(batch)?;
157 let mut output = Vec::new();
158 for row in rows.iter() {
159 output.extend_from_slice(&row);
160 }
161 Ok(output)
162 }
163
164 fn format(&self) -> Format {
165 Format::Avro
166 }
167}
168
169impl std::fmt::Debug for AvroSerializer {
170 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
171 f.debug_struct("AvroSerializer")
172 .field("schema_id", &self.schema_id.load(Ordering::Relaxed))
173 .field("has_registry", &self.schema_registry.is_some())
174 .finish_non_exhaustive()
175 }
176}
177
178#[cfg(test)]
179mod tests;