1use crate::rowbinary;
5use crate::schema::{self, RowSchema};
6use bytes::BytesMut;
7use serde::Serialize;
8use spate_core::deser::{Owned, RecFamily};
9use spate_core::error::{ErrorClass, SinkError};
10use spate_core::record::Record;
11use spate_core::sink::RowEncoder;
12use std::marker::PhantomData;
13use std::sync::Arc;
14
15#[derive(Debug)]
30pub struct ClickHouseEncoder<F> {
31 check: Option<CheckState>,
32 _row: PhantomData<fn(F)>,
33}
34
35#[derive(Debug)]
36struct CheckState {
37 expected: Arc<RowSchema>,
38 done: bool,
39}
40
41impl<F> ClickHouseEncoder<F> {
42 #[must_use]
44 pub fn new() -> Self {
45 ClickHouseEncoder {
46 check: None,
47 _row: PhantomData,
48 }
49 }
50
51 #[must_use]
59 pub fn with_schema(expected: Arc<RowSchema>) -> Self {
60 ClickHouseEncoder {
61 check: Some(CheckState {
62 expected,
63 done: false,
64 }),
65 _row: PhantomData,
66 }
67 }
68}
69
70impl<F> Default for ClickHouseEncoder<F> {
71 fn default() -> Self {
72 Self::new()
73 }
74}
75
76impl<F> Clone for ClickHouseEncoder<F> {
77 fn clone(&self) -> Self {
78 ClickHouseEncoder {
81 check: self.check.as_ref().map(|c| CheckState {
82 expected: Arc::clone(&c.expected),
83 done: false,
84 }),
85 _row: PhantomData,
86 }
87 }
88}
89
90impl<F> RowEncoder<F> for ClickHouseEncoder<F>
91where
92 F: RecFamily,
93 for<'b> F::Rec<'b>: Serialize,
94{
95 fn encode<'buf>(
96 &mut self,
97 rec: &Record<F::Rec<'buf>>,
98 buf: &mut BytesMut,
99 ) -> Result<(), SinkError> {
100 if let Some(check) = &mut self.check
101 && !check.done
102 {
103 let fields = schema::probe::probe_row(&rec.payload).map_err(|e| SinkError::Client {
104 class: ErrorClass::Fatal,
105 reason: format!("schema validation could not probe the row struct: {e}"),
106 })?;
107 schema::check_first_record(&check.expected, &fields).map_err(|diff| {
108 SinkError::Client {
109 class: ErrorClass::Fatal,
110 reason: diff,
111 }
112 })?;
113 check.done = true;
114 }
115 rowbinary::serialize_row(&rec.payload, buf).map_err(|e| SinkError::Client {
116 class: ErrorClass::RecordLevel,
117 reason: format!("rowbinary encoding failed: {e}"),
118 })
119 }
120}
121
122#[derive(Clone, Copy, Debug, Default)]
127pub struct PreEncodedRows;
128
129impl RowEncoder<Owned<Vec<u8>>> for PreEncodedRows {
130 fn encode<'buf>(&mut self, rec: &Record<Vec<u8>>, buf: &mut BytesMut) -> Result<(), SinkError> {
131 buf.extend_from_slice(&rec.payload);
132 Ok(())
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139 use serde::Serialize;
140 use spate_core::checkpoint::AckRef;
141 use spate_core::record::{PartitionId, RecordMeta};
142
143 #[derive(Serialize)]
144 struct Row {
145 id: u64,
146 name: String,
147 }
148
149 fn record<T>(
150 payload: T,
151 ) -> (
152 Record<T>,
153 crossbeam_channel::Receiver<spate_core::checkpoint::AckMsg>,
154 ) {
155 let (ack, rx) = AckRef::test_pair();
156 (
157 Record {
158 payload,
159 meta: RecordMeta {
160 partition: PartitionId(0),
161 offset: 0,
162 event_time_ms: 0,
163 key_hash: None,
164 },
165 ack,
166 },
167 rx,
168 )
169 }
170
171 #[test]
172 fn encodes_serializable_rows() {
173 let (rec, _rx) = record(Row {
174 id: 7,
175 name: "x".into(),
176 });
177 let mut buf = BytesMut::new();
178 ClickHouseEncoder::<Owned<Row>>::new()
179 .encode(&rec, &mut buf)
180 .unwrap();
181 assert_eq!(buf.as_ref(), &[7, 0, 0, 0, 0, 0, 0, 0, 1, b'x']);
182 }
183
184 #[test]
185 fn encodes_borrowed_rows_identically_to_owned() {
186 #[derive(Serialize)]
189 struct RowRef<'a> {
190 id: u64,
191 name: &'a str,
192 }
193 struct RowRefFam;
194 impl RecFamily for RowRefFam {
195 type Rec<'buf> = RowRef<'buf>;
196 }
197
198 let name = String::from("x");
199 let (rec, _rx) = record(RowRef { id: 7, name: &name });
200 let mut buf = BytesMut::new();
201 ClickHouseEncoder::<RowRefFam>::new()
202 .encode(&rec, &mut buf)
203 .unwrap();
204 assert_eq!(buf.as_ref(), &[7, 0, 0, 0, 0, 0, 0, 0, 1, b'x']);
205 }
206
207 #[test]
208 fn encoding_failures_are_record_level() {
209 #[derive(Serialize)]
210 struct Bad {
211 c: char,
212 }
213 let (rec, _rx) = record(Bad { c: 'x' });
214 let err = ClickHouseEncoder::<Owned<Bad>>::new()
215 .encode(&rec, &mut BytesMut::new())
216 .unwrap_err();
217 match err {
218 SinkError::Client { class, .. } => assert_eq!(class, ErrorClass::RecordLevel),
219 other => panic!("unexpected error shape: {other:?}"),
220 }
221 }
222
223 #[test]
224 fn pre_encoded_rows_pass_through() {
225 let (rec, _rx) = record(vec![1u8, 2, 3]);
226 let mut buf = BytesMut::new();
227 PreEncodedRows.encode(&rec, &mut buf).unwrap();
228 assert_eq!(buf.as_ref(), &[1, 2, 3]);
229 }
230}