laminar_connectors/serde/debezium/
mod.rs1use std::sync::Arc;
22
23use arrow_array::builder::{Int64Builder, StringBuilder};
24use arrow_array::{ArrayRef, RecordBatch};
25use arrow_schema::{DataType, Field, Schema, SchemaRef};
26use serde_json::Value;
27
28use super::json::JsonDeserializer;
29use super::{Format, RecordDeserializer};
30use crate::error::SerdeError;
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum DebeziumOp {
35 Create,
37 Update,
39 Delete,
41 Read,
43}
44
45impl DebeziumOp {
46 fn from_str(s: &str) -> Result<Self, SerdeError> {
48 match s {
49 "c" => Ok(DebeziumOp::Create),
50 "u" => Ok(DebeziumOp::Update),
51 "d" => Ok(DebeziumOp::Delete),
52 "r" => Ok(DebeziumOp::Read),
53 other => Err(SerdeError::MalformedInput(format!(
54 "unknown Debezium op: {other}"
55 ))),
56 }
57 }
58
59 #[must_use]
61 pub fn as_str(&self) -> &'static str {
62 match self {
63 DebeziumOp::Create => "c",
64 DebeziumOp::Update => "u",
65 DebeziumOp::Delete => "d",
66 DebeziumOp::Read => "r",
67 }
68 }
69}
70
71#[derive(Debug, Clone)]
81pub struct DebeziumDeserializer {
82 json_deser: JsonDeserializer,
83}
84
85impl DebeziumDeserializer {
86 #[must_use]
88 pub fn new() -> Self {
89 Self {
90 json_deser: JsonDeserializer::new(),
91 }
92 }
93}
94
95impl Default for DebeziumDeserializer {
96 fn default() -> Self {
97 Self::new()
98 }
99}
100
101fn output_schema(schema: &SchemaRef) -> SchemaRef {
102 let mut fields: Vec<Arc<Field>> = schema.fields().iter().cloned().collect();
103 fields.push(Arc::new(Field::new("__op", DataType::Utf8, false)));
104 fields.push(Arc::new(Field::new("__ts_ms", DataType::Int64, false)));
105 Arc::new(Schema::new(fields))
106}
107
108impl RecordDeserializer for DebeziumDeserializer {
109 fn deserialize(&self, data: &[u8], schema: &SchemaRef) -> Result<RecordBatch, SerdeError> {
110 let envelope: Value = serde_json::from_slice(data)?;
111 let obj = envelope
112 .as_object()
113 .ok_or_else(|| SerdeError::MalformedInput("expected JSON object".into()))?;
114
115 let op_str = obj
117 .get("op")
118 .and_then(Value::as_str)
119 .ok_or_else(|| SerdeError::MissingField("op".into()))?;
120 let op = DebeziumOp::from_str(op_str)?;
121
122 let ts_ms = obj.get("ts_ms").and_then(Value::as_i64).unwrap_or(0);
124
125 let payload = match op {
127 DebeziumOp::Create | DebeziumOp::Update | DebeziumOp::Read => obj
128 .get("after")
129 .ok_or_else(|| SerdeError::MissingField("after".into()))?,
130 DebeziumOp::Delete => obj
131 .get("before")
132 .ok_or_else(|| SerdeError::MissingField("before".into()))?,
133 };
134
135 if payload.is_null() {
136 return Err(SerdeError::MalformedInput(format!(
137 "payload is null for op={op_str}"
138 )));
139 }
140
141 let data_batch = self.json_deser.deserialize_value(payload, schema)?;
143
144 let mut columns: Vec<ArrayRef> = data_batch.columns().to_vec();
146
147 let mut op_builder = StringBuilder::with_capacity(1, 1);
148 op_builder.append_value(op.as_str());
149 columns.push(Arc::new(op_builder.finish()));
150
151 let mut ts_builder = Int64Builder::with_capacity(1);
152 ts_builder.append_value(ts_ms);
153 columns.push(Arc::new(ts_builder.finish()));
154
155 RecordBatch::try_new(output_schema(schema), columns)
156 .map_err(|e| SerdeError::MalformedInput(format!("failed to create RecordBatch: {e}")))
157 }
158
159 fn deserialize_batch(
160 &self,
161 records: &[&[u8]],
162 schema: &SchemaRef,
163 ) -> Result<RecordBatch, SerdeError> {
164 if records.is_empty() {
165 return Ok(RecordBatch::new_empty(output_schema(schema)));
166 }
167 let batches = records
168 .iter()
169 .map(|record| self.deserialize(record, schema))
170 .collect::<Result<Vec<_>, _>>()?;
171 let schema = batches[0].schema();
172 arrow_select::concat::concat_batches(&schema, &batches)
173 .map_err(|error| SerdeError::MalformedInput(format!("failed to concat batch: {error}")))
174 }
175
176 fn format(&self) -> Format {
177 Format::Debezium
178 }
179}
180
181#[cfg(test)]
182mod tests;