mod column;
mod dispatch;
mod leaf;
mod lowcard;
use crate::schema::RowSchema;
use crate::schema::typeparse::ChType;
use bytes::BytesMut;
use column::ColumnWriter;
use dispatch::RowDispatchSer;
use leaf::{put_leb128, put_string};
use serde::Serialize;
use spate_core::deser::RecFamily;
use spate_core::error::{ErrorClass, SinkError};
use spate_core::record::Record;
use spate_core::sink::RowEncoder;
use std::fmt;
use std::marker::PhantomData;
use std::sync::Arc;
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum NativeError {
#[error("column `{column}`: type `{ch_type}` is not supported by the Native encoder")]
UnsupportedColumn {
column: String,
ch_type: String,
},
#[error("value does not match column type (expected {expected})")]
TypeMismatch {
expected: &'static str,
},
#[error("fixed-width column expected {expected} bytes, got {got}")]
RawWidth {
expected: usize,
got: usize,
},
#[error("FixedString({width}) value is {got} bytes (too long)")]
FixedTooLong {
width: usize,
got: usize,
},
#[error("row has {got} field(s) but the table has {expected} column(s)")]
RowArity {
expected: usize,
got: usize,
},
#[error("{0}")]
FirstRecord(String),
#[error("tuple value has more elements than the column type")]
TupleArity,
#[error("row must serialize as a struct or tuple of columns")]
NotAStruct,
#[error("internal Native encoder error: {0}")]
Internal(&'static str),
#[error("serialize error: {0}")]
Custom(String),
}
impl serde::ser::Error for NativeError {
fn custom<T: fmt::Display>(msg: T) -> Self {
NativeError::Custom(msg.to_string())
}
}
impl NativeError {
fn into_sink_error(self) -> SinkError {
SinkError::Client {
class: ErrorClass::Fatal,
reason: format!("native encoding failed: {self}"),
}
}
}
#[derive(Debug)]
pub struct NativeSchema {
expected: RowSchema,
}
impl NativeSchema {
pub fn from_row_schema(schema: &RowSchema) -> Result<Arc<NativeSchema>, NativeError> {
Self::from_expected(RowSchema {
mode: schema.mode,
table: schema.table.clone(),
columns: schema.columns.clone(),
})
}
fn from_expected(expected: RowSchema) -> Result<Arc<NativeSchema>, NativeError> {
for (name, ty, _) in &expected.columns {
ColumnWriter::build(ty).map_err(|ch_type| NativeError::UnsupportedColumn {
column: name.clone(),
ch_type,
})?;
}
Ok(Arc::new(NativeSchema { expected }))
}
pub fn from_columns(specs: &[(&str, &str)]) -> Result<Arc<NativeSchema>, NativeError> {
Self::from_expected(RowSchema {
mode: crate::config::SchemaValidation::Names,
table: "<static schema>".into(),
columns: specs
.iter()
.map(|(n, t)| {
(
(*n).to_string(),
crate::schema::typeparse::parse(t),
(*t).to_string(),
)
})
.collect(),
})
}
fn columns(&self) -> &[(String, ChType, String)] {
&self.expected.columns
}
fn fresh_columns(&self) -> Vec<ColumnWriter> {
self.columns()
.iter()
.map(|(_, ty, _)| {
ColumnWriter::build(ty).expect("column type validated at construction")
})
.collect()
}
}
pub struct NativeEncoder<F> {
schema: Arc<NativeSchema>,
columns: Vec<ColumnWriter>,
rows: u32,
poisoned: bool,
checked: bool,
approx_bytes: usize,
_row: PhantomData<fn(F)>,
}
impl<F> NativeEncoder<F> {
#[must_use]
pub fn new(schema: Arc<NativeSchema>) -> Self {
let columns = schema.fresh_columns();
NativeEncoder {
schema,
columns,
rows: 0,
poisoned: false,
checked: false,
approx_bytes: 0,
_row: PhantomData,
}
}
fn compute_buffered(&self) -> usize {
self.columns
.iter()
.map(ColumnWriter::byte_len)
.sum::<usize>()
+ self.columns.len() * 16
}
pub fn from_row_schema(schema: &RowSchema) -> Result<Self, NativeError> {
Ok(Self::new(NativeSchema::from_row_schema(schema)?))
}
fn finalize_block(&mut self, buf: &mut BytesMut) {
let reserve = self.compute_buffered()
+ self
.schema
.columns()
.iter()
.map(|(name, _, type_name)| name.len() + type_name.len())
.sum::<usize>()
+ 20;
buf.reserve(reserve);
put_leb128(buf, self.columns.len() as u64);
put_leb128(buf, u64::from(self.rows));
for (col, (name, _, type_name)) in self.columns.iter().zip(self.schema.columns()) {
put_string(buf, name.as_bytes());
put_string(buf, type_name.as_bytes());
col.write_prefix(buf);
col.write_data(buf);
}
for col in &mut self.columns {
col.reset();
}
self.rows = 0;
self.approx_bytes = 0;
}
}
impl<F> Clone for NativeEncoder<F> {
fn clone(&self) -> Self {
NativeEncoder::new(Arc::clone(&self.schema))
}
}
impl<F> fmt::Debug for NativeEncoder<F> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("NativeEncoder")
.field("columns", &self.schema.columns().len())
.field("rows", &self.rows)
.finish()
}
}
impl<F> RowEncoder<F> for NativeEncoder<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 !self.checked {
if let Ok(fields) = crate::schema::probe::probe_row(&rec.payload) {
crate::schema::check_first_record(&self.schema.expected, &fields)
.map_err(|diff| NativeError::FirstRecord(diff).into_sink_error())?;
}
self.checked = true;
}
match rec.payload.serialize(RowDispatchSer {
columns: &mut self.columns,
}) {
Ok(()) => {
self.rows += 1;
if self.rows <= 16 || self.rows.is_multiple_of(16) {
self.approx_bytes = self.compute_buffered();
}
Ok(())
}
Err(e) => {
self.poisoned = true;
Err(e.into_sink_error())
}
}
}
#[inline]
fn buffered_bytes(&self) -> usize {
self.approx_bytes
}
fn finish_chunk(&mut self, buf: &mut BytesMut) -> Result<(), SinkError> {
if self.poisoned {
return Err(SinkError::Client {
class: ErrorClass::Fatal,
reason: "native block abandoned after a failed row".into(),
});
}
if self.rows > 0 {
self.finalize_block(buf);
}
Ok(())
}
}
#[cfg(test)]
impl<T> NativeEncoder<T> {
pub(crate) fn block_of<R: Serialize>(
schema: Arc<NativeSchema>,
rows: &[R],
) -> Result<Vec<u8>, NativeError> {
let mut enc = NativeEncoder::<T>::new(schema);
for r in rows {
r.serialize(RowDispatchSer {
columns: &mut enc.columns,
})?;
enc.rows += 1;
}
let mut buf = BytesMut::new();
if enc.rows > 0 {
enc.finalize_block(&mut buf);
}
Ok(buf.to_vec())
}
}
#[cfg(test)]
mod tests;