use crate::rowbinary;
use crate::schema::{self, RowSchema};
use bytes::BytesMut;
use serde::Serialize;
use spate_core::deser::{Owned, RecFamily};
use spate_core::error::{ErrorClass, SinkError};
use spate_core::record::Record;
use spate_core::sink::RowEncoder;
use std::marker::PhantomData;
use std::sync::Arc;
#[derive(Debug)]
pub struct ClickHouseEncoder<F> {
check: Option<CheckState>,
_row: PhantomData<fn(F)>,
}
#[derive(Debug)]
struct CheckState {
expected: Arc<RowSchema>,
done: bool,
}
impl<F> ClickHouseEncoder<F> {
#[must_use]
pub fn new() -> Self {
ClickHouseEncoder {
check: None,
_row: PhantomData,
}
}
#[must_use]
pub fn with_schema(expected: Arc<RowSchema>) -> Self {
ClickHouseEncoder {
check: Some(CheckState {
expected,
done: false,
}),
_row: PhantomData,
}
}
}
impl<F> Default for ClickHouseEncoder<F> {
fn default() -> Self {
Self::new()
}
}
impl<F> Clone for ClickHouseEncoder<F> {
fn clone(&self) -> Self {
ClickHouseEncoder {
check: self.check.as_ref().map(|c| CheckState {
expected: Arc::clone(&c.expected),
done: false,
}),
_row: PhantomData,
}
}
}
impl<F> RowEncoder<F> for ClickHouseEncoder<F>
where
F: RecFamily,
for<'b> F::Rec<'b>: Serialize,
{
fn encode<'buf>(
&mut self,
rec: &Record<F::Rec<'buf>>,
buf: &mut BytesMut,
) -> Result<(), SinkError> {
if let Some(check) = &mut self.check
&& !check.done
{
let fields = schema::probe::probe_row(&rec.payload).map_err(|e| SinkError::Client {
class: ErrorClass::Fatal,
reason: format!("schema validation could not probe the row struct: {e}"),
})?;
schema::check_first_record(&check.expected, &fields).map_err(|diff| {
SinkError::Client {
class: ErrorClass::Fatal,
reason: diff,
}
})?;
check.done = true;
}
rowbinary::serialize_row(&rec.payload, buf).map_err(|e| SinkError::Client {
class: ErrorClass::RecordLevel,
reason: format!("rowbinary encoding failed: {e}"),
})
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct PreEncodedRows;
impl RowEncoder<Owned<Vec<u8>>> for PreEncodedRows {
fn encode<'buf>(&mut self, rec: &Record<Vec<u8>>, buf: &mut BytesMut) -> Result<(), SinkError> {
buf.extend_from_slice(&rec.payload);
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde::Serialize;
use spate_core::checkpoint::AckRef;
use spate_core::record::{PartitionId, RecordMeta};
#[derive(Serialize)]
struct Row {
id: u64,
name: String,
}
fn record<T>(
payload: T,
) -> (
Record<T>,
crossbeam_channel::Receiver<spate_core::checkpoint::AckMsg>,
) {
let (ack, rx) = AckRef::test_pair();
(
Record {
payload,
meta: RecordMeta {
partition: PartitionId(0),
offset: 0,
event_time_ms: 0,
key_hash: None,
},
ack,
},
rx,
)
}
#[test]
fn encodes_serializable_rows() {
let (rec, _rx) = record(Row {
id: 7,
name: "x".into(),
});
let mut buf = BytesMut::new();
ClickHouseEncoder::<Owned<Row>>::new()
.encode(&rec, &mut buf)
.unwrap();
assert_eq!(buf.as_ref(), &[7, 0, 0, 0, 0, 0, 0, 0, 1, b'x']);
}
#[test]
fn encodes_borrowed_rows_identically_to_owned() {
#[derive(Serialize)]
struct RowRef<'a> {
id: u64,
name: &'a str,
}
struct RowRefFam;
impl RecFamily for RowRefFam {
type Rec<'buf> = RowRef<'buf>;
}
let name = String::from("x");
let (rec, _rx) = record(RowRef { id: 7, name: &name });
let mut buf = BytesMut::new();
ClickHouseEncoder::<RowRefFam>::new()
.encode(&rec, &mut buf)
.unwrap();
assert_eq!(buf.as_ref(), &[7, 0, 0, 0, 0, 0, 0, 0, 1, b'x']);
}
#[test]
fn encoding_failures_are_record_level() {
#[derive(Serialize)]
struct Bad {
c: char,
}
let (rec, _rx) = record(Bad { c: 'x' });
let err = ClickHouseEncoder::<Owned<Bad>>::new()
.encode(&rec, &mut BytesMut::new())
.unwrap_err();
match err {
SinkError::Client { class, .. } => assert_eq!(class, ErrorClass::RecordLevel),
other => panic!("unexpected error shape: {other:?}"),
}
}
#[test]
fn pre_encoded_rows_pass_through() {
let (rec, _rx) = record(vec![1u8, 2, 3]);
let mut buf = BytesMut::new();
PreEncodedRows.encode(&rec, &mut buf).unwrap();
assert_eq!(buf.as_ref(), &[1, 2, 3]);
}
}