Skip to main content

spate_clickhouse/
rowbinary.rs

1//! A serde `Serializer` producing ClickHouse [RowBinary].
2//!
3//! The `clickhouse` crate's own row serializer is crate-private, so
4//! `spate-clickhouse` ships its own. Wire semantics deliberately mirror the
5//! crate's (its deserializer is used to read rows back in tests, so
6//! compatibility is proven by round-trips):
7//!
8//! | Rust / serde type | RowBinary encoding |
9//! |---|---|
10//! | `bool` | 1 byte, `0`/`1` |
11//! | `i8..i128`, `u8..u128` | little-endian fixed width |
12//! | `f32`, `f64` | little-endian IEEE 754 |
13//! | `String`, `&str` | LEB128 byte length + UTF-8 bytes (`String`) |
14//! | `serde_bytes`-style `&[u8]` | LEB128 length + bytes (`String`) |
15//! | `Option<T>` (T not itself `Option`) | 1 prefix byte: `1` = NULL, `0` = value follows (`Nullable(T)`) |
16//! | `Vec<T>` / slices (seq) | LEB128 element count + elements (`Array(T)`) |
17//! | tuples, `[T; N]`, tuple structs | elements back-to-back, no length (`Tuple`, `FixedString(N)` for `[u8; N]`) |
18//! | structs | fields in **declaration order**, no header |
19//! | maps | LEB128 entry count + alternating key, value (`Map(K, V)`) |
20//! | newtype structs | transparent |
21//! | newtype enum variants | 1 discriminant byte (serde `variant_index`) + value (`Variant`) |
22//! | `char`, `()`, unit structs/variants, struct/tuple variants, `Option<Option<T>>` | **unsupported** — record-level error |
23//!
24//! ## Column-type coverage beyond the primitives
25//!
26//! | ClickHouse type | Rust field |
27//! |---|---|
28//! | `Date` / `Date32` | [`DateDays`](crate::DateDays) / [`Date32Days`](crate::Date32Days), or `chrono::NaiveDate` / `time::Date` via [`crate::serde`] (feature-gated) |
29//! | `DateTime` | [`DateTimeSeconds`], or `chrono::DateTime<Utc>` / `time::OffsetDateTime` via [`crate::serde`] |
30//! | `DateTime64(0/3/6/9)` | [`DateTime64Secs`](crate::DateTime64Secs) / [`DateTime64Millis`] / [`DateTime64Micros`](crate::DateTime64Micros) / [`DateTime64Nanos`](crate::DateTime64Nanos), or the `datetime64::*` serde modules |
31//! | `Time` / `Time64(p)` | [`TimeSeconds`](crate::TimeSeconds) / [`Time64Secs`](crate::Time64Secs)-family, or the `time`/`time64::*` serde modules (server ≥ 25.6, `enable_time_time64_type=1`) |
32//! | `UUID` | `uuid::Uuid` with `#[serde(with = "spate_clickhouse::serde::uuid")]` (feature `uuid`). ⚠ `Uuid`'s default impl writes a LEB128-prefixed 16-byte string — silently wrong |
33//! | `IPv4` | `std::net::Ipv4Addr` with `#[serde(with = "spate_clickhouse::serde::ipv4")]`. ⚠ the default impl writes big-endian octets — silently wrong |
34//! | `IPv6` | `std::net::Ipv6Addr` bare (its default impl — 16 network-order bytes — is correct) |
35//! | `Enum8` / `Enum16` | `serde_repr` enums: `#[derive(Serialize_repr)] #[repr(i8)]` (or `i16`) |
36//! | `Decimal(P, S)` | [`Decimal32<S>`](crate::Decimal32) / [`Decimal64<S>`](crate::Decimal64) / [`Decimal128<S>`](crate::Decimal128) pre-scaled wrappers (or raw ints); `rust_decimal` conversions behind the `rust_decimal` feature |
37//! | `Int256` / `UInt256` | [`Int256`](crate::Int256) / [`UInt256`](crate::UInt256) (32 raw LE bytes) |
38//! | `JSON` | a `String` of JSON text **plus** the per-insert setting `input_format_binary_read_json_as_string: "1"` (see the crate docs' wiring example) |
39//! | `LowCardinality(T)` | the plain `T` — transparent on insert; the server builds the dictionary |
40//! | Geo (`Point`, `Ring`, `LineString`, `Polygon`, ...) | the [`Point`](crate::Point)/[`Ring`](crate::Ring)/... aliases (tuples and `Vec`s of `Float64`) |
41//! | `Dynamic`, `AggregateFunction(...)`, `Decimal256` | **unsupported** — no serde shape maps to them (`Decimal256`: scale manually into an [`Int256`](crate::Int256)) |
42//!
43//! ## The wire contract is field order
44//!
45//! RowBinary carries no column names: the `INSERT INTO t (a, b, c)` column
46//! list must match the struct's field declaration order exactly. Reordering
47//! struct fields is a breaking change to the pipeline's wire format.
48//! Column-type niceties (e.g. `DateTime64(3)` is an `Int64` of milliseconds
49//! on the wire) are handled with plain integer fields, the newtype wrappers
50//! above, or the [`crate::serde`] field attributes.
51//!
52//! ## Unsupported serde attributes (they misalign columns)
53//!
54//! Because rows are positional and fixed-width, several serde attributes
55//! that make a struct emit a variable or unexpected number of fields are
56//! **not supported** on row types:
57//!
58//! - **`#[serde(skip)]` / `#[serde(skip_serializing_if = ...)]`** — a skipped
59//!   field emits *fewer* columns for some rows, shifting every following
60//!   byte into the wrong column. This serializer **cannot reliably detect
61//!   it**: serde reports the already-reduced field count to
62//!   `serialize_struct`, so a short row looks self-consistent. Never put a
63//!   skip attribute on a row struct; model absent values as `Option<T>`
64//!   (`Nullable`) instead. (A `Serialize` impl that *lies* about its field
65//!   count — declares N but writes a different number — is caught with
66//!   [`RowBinaryError::FieldCountMismatch`].)
67//! - **`#[serde(flatten)]`** — serde lowers a flattened struct to a
68//!   length-less map; rejected with [`RowBinaryError::FlattenUnsupported`].
69//!   Inline the fields into the row struct in column order instead.
70//! - **internally/adjacently-tagged enums (`#[serde(tag = ...)]`)** — emit
71//!   the tag as a phantom string column. Map enums to an explicit integer or
72//!   `String` column, or use a newtype-variant `Variant` (below).
73//!
74//! ## `Variant` discriminant ordering
75//!
76//! A newtype enum variant writes serde's `variant_index` — the enum's
77//! **declaration order** — as the `Variant` discriminant byte. ClickHouse
78//! does **not** use declaration order: it sorts a `Variant(T1, T2, ...)`'s
79//! member types **alphabetically by type name** and numbers the
80//! discriminators in that sorted order. There is no way to infer that
81//! mapping here, so **you must declare your Rust enum variants in the same
82//! order ClickHouse will sort the column's types.**
83//!
84//! For a `Variant(Int64, String)` column (ClickHouse order: `Int64` = 0,
85//! `String` = 1, sorted alphabetically), declare:
86//!
87//! ```
88//! # use serde::Serialize;
89//! #[derive(Serialize)]
90//! enum Payload {
91//!     Num(i64),     // variant_index 0 -> Int64
92//!     Text(String), // variant_index 1 -> String
93//! }
94//! # let _ = (Payload::Num(0), Payload::Text(String::new()));
95//! ```
96//!
97//! Also note: `Option<Enum>` against such a column emits the `Nullable`
98//! `0`/`1` prefix, but ClickHouse encodes a `Variant` NULL as discriminant
99//! `255` and forbids `Nullable(Variant)` — so a nullable variant column is
100//! not representable; use the `Variant`'s own NULL support instead.
101//!
102//! [RowBinary]: https://clickhouse.com/docs/en/interfaces/formats#rowbinary
103
104use bytes::{BufMut, BytesMut};
105use serde::ser::{self, Serialize};
106use std::fmt;
107
108/// A row failed to serialize. Record-level: subject to the sink stage's
109/// error policy, never fatal to the pipeline by itself.
110#[derive(Debug, thiserror::Error)]
111#[non_exhaustive]
112pub enum RowBinaryError {
113    /// The Rust type has no RowBinary representation.
114    #[error("type has no RowBinary representation: {0}")]
115    Unsupported(&'static str),
116    /// Sequences must know their length up front (LEB128 prefix).
117    #[error("sequences must have a known length ahead of time")]
118    SequenceMustHaveLength,
119    /// `#[serde(flatten)]` (serde serializes the outer struct as a
120    /// length-less map, which RowBinary cannot position into columns).
121    #[error(
122        "#[serde(flatten)] is not supported by RowBinary: it produces \
123         length-less, unordered fields; inline the flattened fields into the \
124         row struct in column order instead"
125    )]
126    FlattenUnsupported,
127    /// A nested `Option` (`Option<Option<T>>`): ClickHouse has no
128    /// `Nullable(Nullable(T))`, so the double null-prefix is unparseable.
129    #[error(
130        "nested Option (Option<Option<T>>) has no ClickHouse type: \
131         ClickHouse forbids Nullable(Nullable(T))"
132    )]
133    NestedOption,
134    /// A `Serialize` impl declared one struct field count to
135    /// `serialize_struct` but serialized a different number of fields.
136    /// RowBinary is positional, so a variable-width row misaligns every
137    /// following column. (Note: `#[serde(skip)]`/`#[serde(skip_serializing_if)]`
138    /// evade this check — serde reports the post-skip count — which is why
139    /// those attributes are documented as unsupported.)
140    #[error(
141        "struct declared {expected} field(s) but serialized {got}: RowBinary rows are positional and must be fixed-width"
142    )]
143    FieldCountMismatch {
144        /// Fields the `Serialize` impl declared.
145        expected: usize,
146        /// Fields it actually serialized.
147        got: usize,
148    },
149    /// `Variant` discriminants are single bytes.
150    #[error("variant discriminant {0} exceeds 255")]
151    VariantOutOfRange(u32),
152    /// An error raised by a `Serialize` implementation.
153    #[error("serialize error: {0}")]
154    Custom(String),
155}
156
157impl ser::Error for RowBinaryError {
158    fn custom<T: fmt::Display>(msg: T) -> Self {
159        RowBinaryError::Custom(msg.to_string())
160    }
161}
162
163/// Serialize one row, appending its RowBinary encoding to `buf`.
164///
165/// On error the buffer may contain a partial row; the caller rolls back to
166/// its pre-record length (the chain's terminal stage does this).
167pub fn serialize_row<T: Serialize + ?Sized>(
168    row: &T,
169    buf: &mut BytesMut,
170) -> Result<(), RowBinaryError> {
171    row.serialize(&mut RowBinarySer {
172        buf,
173        option_inner: false,
174        struct_depth: 0,
175        top_expected: 0,
176        top_written: 0,
177    })
178}
179
180pub use crate::types::{DateTime64Millis, DateTimeSeconds};
181
182fn put_leb128(buf: &mut BytesMut, mut value: u64) {
183    loop {
184        let byte = (value & 0x7f) as u8;
185        value >>= 7;
186        if value == 0 {
187            buf.put_u8(byte);
188            break;
189        }
190        buf.put_u8(byte | 0x80);
191    }
192}
193
194struct RowBinarySer<'a> {
195    buf: &'a mut BytesMut,
196    /// `true` while the next `serialize` call is the *direct* inner of an
197    /// `Option`. Lets `serialize_some`/`serialize_none` reject nested
198    /// `Option<Option<T>>` (unparseable double null-prefix). Composite
199    /// serializers clear it: an `Option` inside a struct/seq/tuple field is
200    /// a separate column, not a nested nullable.
201    option_inner: bool,
202    /// Depth of nested `serialize_struct` calls; the outermost struct
203    /// (depth 1) is the one whose fields map to the insert columns.
204    struct_depth: u32,
205    /// The field count the outermost struct declared to `serialize_struct`.
206    top_expected: usize,
207    /// The field count it actually serialized (compared at `end`).
208    top_written: usize,
209}
210
211impl<'a, 'b> ser::Serializer for &'a mut RowBinarySer<'b> {
212    type Ok = ();
213    type Error = RowBinaryError;
214    type SerializeSeq = Self;
215    type SerializeTuple = Self;
216    type SerializeTupleStruct = Self;
217    type SerializeTupleVariant = ser::Impossible<(), RowBinaryError>;
218    type SerializeMap = Self;
219    type SerializeStruct = Self;
220    type SerializeStructVariant = ser::Impossible<(), RowBinaryError>;
221
222    #[inline]
223    fn serialize_bool(self, v: bool) -> Result<(), RowBinaryError> {
224        self.buf.put_u8(u8::from(v));
225        Ok(())
226    }
227
228    #[inline]
229    fn serialize_i8(self, v: i8) -> Result<(), RowBinaryError> {
230        self.buf.put_i8(v);
231        Ok(())
232    }
233
234    #[inline]
235    fn serialize_i16(self, v: i16) -> Result<(), RowBinaryError> {
236        self.buf.put_i16_le(v);
237        Ok(())
238    }
239
240    #[inline]
241    fn serialize_i32(self, v: i32) -> Result<(), RowBinaryError> {
242        self.buf.put_i32_le(v);
243        Ok(())
244    }
245
246    #[inline]
247    fn serialize_i64(self, v: i64) -> Result<(), RowBinaryError> {
248        self.buf.put_i64_le(v);
249        Ok(())
250    }
251
252    #[inline]
253    fn serialize_i128(self, v: i128) -> Result<(), RowBinaryError> {
254        self.buf.put_i128_le(v);
255        Ok(())
256    }
257
258    #[inline]
259    fn serialize_u8(self, v: u8) -> Result<(), RowBinaryError> {
260        self.buf.put_u8(v);
261        Ok(())
262    }
263
264    #[inline]
265    fn serialize_u16(self, v: u16) -> Result<(), RowBinaryError> {
266        self.buf.put_u16_le(v);
267        Ok(())
268    }
269
270    #[inline]
271    fn serialize_u32(self, v: u32) -> Result<(), RowBinaryError> {
272        self.buf.put_u32_le(v);
273        Ok(())
274    }
275
276    #[inline]
277    fn serialize_u64(self, v: u64) -> Result<(), RowBinaryError> {
278        self.buf.put_u64_le(v);
279        Ok(())
280    }
281
282    #[inline]
283    fn serialize_u128(self, v: u128) -> Result<(), RowBinaryError> {
284        self.buf.put_u128_le(v);
285        Ok(())
286    }
287
288    #[inline]
289    fn serialize_f32(self, v: f32) -> Result<(), RowBinaryError> {
290        self.buf.put_f32_le(v);
291        Ok(())
292    }
293
294    #[inline]
295    fn serialize_f64(self, v: f64) -> Result<(), RowBinaryError> {
296        self.buf.put_f64_le(v);
297        Ok(())
298    }
299
300    fn serialize_char(self, _v: char) -> Result<(), RowBinaryError> {
301        Err(RowBinaryError::Unsupported(
302            "char (use a String column instead)",
303        ))
304    }
305
306    #[inline]
307    fn serialize_str(self, v: &str) -> Result<(), RowBinaryError> {
308        put_leb128(self.buf, v.len() as u64);
309        self.buf.put_slice(v.as_bytes());
310        Ok(())
311    }
312
313    #[inline]
314    fn serialize_bytes(self, v: &[u8]) -> Result<(), RowBinaryError> {
315        put_leb128(self.buf, v.len() as u64);
316        self.buf.put_slice(v);
317        Ok(())
318    }
319
320    #[inline]
321    fn serialize_none(self) -> Result<(), RowBinaryError> {
322        if self.option_inner {
323            // The null of a nested Option (`Some(None)`): unparseable.
324            return Err(RowBinaryError::NestedOption);
325        }
326        // Nullable(T): 1 = NULL.
327        self.buf.put_u8(1);
328        Ok(())
329    }
330
331    #[inline]
332    fn serialize_some<T: Serialize + ?Sized>(self, value: &T) -> Result<(), RowBinaryError> {
333        if self.option_inner {
334            // We are the direct inner of an Option: `Option<Option<T>>`.
335            return Err(RowBinaryError::NestedOption);
336        }
337        // Nullable(T): 0 then the value. Mark the inner so a directly-nested
338        // Option is rejected; composite/leaf serializers clear it.
339        self.buf.put_u8(0);
340        self.option_inner = true;
341        let result = value.serialize(&mut *self);
342        self.option_inner = false;
343        result
344    }
345
346    fn serialize_unit(self) -> Result<(), RowBinaryError> {
347        Err(RowBinaryError::Unsupported("() has no column type"))
348    }
349
350    fn serialize_unit_struct(self, name: &'static str) -> Result<(), RowBinaryError> {
351        let _ = name;
352        Err(RowBinaryError::Unsupported("unit struct"))
353    }
354
355    fn serialize_unit_variant(
356        self,
357        _name: &'static str,
358        _variant_index: u32,
359        _variant: &'static str,
360    ) -> Result<(), RowBinaryError> {
361        Err(RowBinaryError::Unsupported(
362            "unit enum variant (map enums to an integer or String column explicitly)",
363        ))
364    }
365
366    #[inline]
367    fn serialize_newtype_struct<T: Serialize + ?Sized>(
368        self,
369        _name: &'static str,
370        value: &T,
371    ) -> Result<(), RowBinaryError> {
372        value.serialize(self)
373    }
374
375    fn serialize_newtype_variant<T: Serialize + ?Sized>(
376        self,
377        _name: &'static str,
378        variant_index: u32,
379        _variant: &'static str,
380        value: &T,
381    ) -> Result<(), RowBinaryError> {
382        self.option_inner = false;
383        // ClickHouse `Variant`: one discriminant byte then the value.
384        //
385        // IMPORTANT: the discriminant written here is serde's
386        // `variant_index` — the enum's *declaration order*. ClickHouse
387        // assigns `Variant(...)` discriminators in a different order (its
388        // types sorted alphabetically by name), so declaration order must be
389        // made to match by the user. See the crate/type docs — there is no
390        // way to infer the mapping here.
391        let idx = u8::try_from(variant_index)
392            .map_err(|_| RowBinaryError::VariantOutOfRange(variant_index))?;
393        self.buf.put_u8(idx);
394        value.serialize(self)
395    }
396
397    fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq, RowBinaryError> {
398        self.option_inner = false;
399        let len = len.ok_or(RowBinaryError::SequenceMustHaveLength)?;
400        put_leb128(self.buf, len as u64);
401        Ok(self)
402    }
403
404    #[inline]
405    fn serialize_tuple(self, _len: usize) -> Result<Self::SerializeTuple, RowBinaryError> {
406        self.option_inner = false;
407        Ok(self)
408    }
409
410    #[inline]
411    fn serialize_tuple_struct(
412        self,
413        _name: &'static str,
414        _len: usize,
415    ) -> Result<Self::SerializeTupleStruct, RowBinaryError> {
416        self.option_inner = false;
417        Ok(self)
418    }
419
420    fn serialize_tuple_variant(
421        self,
422        _name: &'static str,
423        _variant_index: u32,
424        _variant: &'static str,
425        _len: usize,
426    ) -> Result<Self::SerializeTupleVariant, RowBinaryError> {
427        Err(RowBinaryError::Unsupported("tuple enum variant"))
428    }
429
430    fn serialize_map(self, len: Option<usize>) -> Result<Self::SerializeMap, RowBinaryError> {
431        self.option_inner = false;
432        // A real `Map` always reports its length; a length-less map is
433        // serde's `#[serde(flatten)]` lowering. Reject it with a message that
434        // names the actual cause instead of the misleading "sequences must
435        // have a known length" error.
436        let len = len.ok_or(RowBinaryError::FlattenUnsupported)?;
437        put_leb128(self.buf, len as u64);
438        Ok(self)
439    }
440
441    #[inline]
442    fn serialize_struct(
443        self,
444        _name: &'static str,
445        len: usize,
446    ) -> Result<Self::SerializeStruct, RowBinaryError> {
447        self.option_inner = false;
448        // Track the outermost struct's declared field count so `end` can
449        // reject a `Serialize` impl that emits a different number of fields
450        // (a positional-row misalignment).
451        if self.struct_depth == 0 {
452            self.top_expected = len;
453            self.top_written = 0;
454        }
455        self.struct_depth += 1;
456        Ok(self)
457    }
458
459    fn serialize_struct_variant(
460        self,
461        _name: &'static str,
462        _variant_index: u32,
463        _variant: &'static str,
464        _len: usize,
465    ) -> Result<Self::SerializeStructVariant, RowBinaryError> {
466        Err(RowBinaryError::Unsupported("struct enum variant"))
467    }
468
469    #[inline]
470    fn is_human_readable(&self) -> bool {
471        false
472    }
473}
474
475impl<'a, 'b> ser::SerializeSeq for &'a mut RowBinarySer<'b> {
476    type Ok = ();
477    type Error = RowBinaryError;
478
479    #[inline]
480    fn serialize_element<T: Serialize + ?Sized>(
481        &mut self,
482        value: &T,
483    ) -> Result<(), RowBinaryError> {
484        value.serialize(&mut **self)
485    }
486
487    #[inline]
488    fn end(self) -> Result<(), RowBinaryError> {
489        Ok(())
490    }
491}
492
493impl<'a, 'b> ser::SerializeTuple for &'a mut RowBinarySer<'b> {
494    type Ok = ();
495    type Error = RowBinaryError;
496
497    #[inline]
498    fn serialize_element<T: Serialize + ?Sized>(
499        &mut self,
500        value: &T,
501    ) -> Result<(), RowBinaryError> {
502        value.serialize(&mut **self)
503    }
504
505    #[inline]
506    fn end(self) -> Result<(), RowBinaryError> {
507        Ok(())
508    }
509}
510
511impl<'a, 'b> ser::SerializeTupleStruct for &'a mut RowBinarySer<'b> {
512    type Ok = ();
513    type Error = RowBinaryError;
514
515    #[inline]
516    fn serialize_field<T: Serialize + ?Sized>(&mut self, value: &T) -> Result<(), RowBinaryError> {
517        value.serialize(&mut **self)
518    }
519
520    #[inline]
521    fn end(self) -> Result<(), RowBinaryError> {
522        Ok(())
523    }
524}
525
526impl<'a, 'b> ser::SerializeMap for &'a mut RowBinarySer<'b> {
527    type Ok = ();
528    type Error = RowBinaryError;
529
530    #[inline]
531    fn serialize_key<T: Serialize + ?Sized>(&mut self, key: &T) -> Result<(), RowBinaryError> {
532        key.serialize(&mut **self)
533    }
534
535    #[inline]
536    fn serialize_value<T: Serialize + ?Sized>(&mut self, value: &T) -> Result<(), RowBinaryError> {
537        value.serialize(&mut **self)
538    }
539
540    #[inline]
541    fn end(self) -> Result<(), RowBinaryError> {
542        Ok(())
543    }
544}
545
546impl<'a, 'b> ser::SerializeStruct for &'a mut RowBinarySer<'b> {
547    type Ok = ();
548    type Error = RowBinaryError;
549
550    #[inline]
551    fn serialize_field<T: Serialize + ?Sized>(
552        &mut self,
553        _key: &'static str,
554        value: &T,
555    ) -> Result<(), RowBinaryError> {
556        // Count only the outermost struct's fields (depth 1); nested structs
557        // are Tuple columns with their own widths.
558        if self.struct_depth == 1 {
559            self.top_written += 1;
560        }
561        value.serialize(&mut **self)
562    }
563
564    #[inline]
565    fn end(self) -> Result<(), RowBinaryError> {
566        self.struct_depth -= 1;
567        if self.struct_depth == 0 && self.top_written != self.top_expected {
568            return Err(RowBinaryError::FieldCountMismatch {
569                expected: self.top_expected,
570                got: self.top_written,
571            });
572        }
573        Ok(())
574    }
575}
576
577#[cfg(test)]
578mod tests {
579    use super::*;
580    use serde::Serialize;
581
582    fn enc<T: Serialize>(v: &T) -> Vec<u8> {
583        let mut buf = BytesMut::new();
584        serialize_row(v, &mut buf).expect("serialize");
585        buf.to_vec()
586    }
587
588    #[test]
589    fn integers_are_little_endian_fixed_width() {
590        assert_eq!(enc(&0x0102_0304u32), [0x04, 0x03, 0x02, 0x01]);
591        assert_eq!(enc(&-2i16), [0xfe, 0xff]);
592        assert_eq!(enc(&1u8), [0x01]);
593        assert_eq!(
594            enc(&0x0102_0304_0506_0708u64),
595            [0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01]
596        );
597        assert_eq!(enc(&1u128).len(), 16);
598    }
599
600    #[test]
601    fn floats_and_bools() {
602        assert_eq!(enc(&1.0f32), 1.0f32.to_le_bytes());
603        assert_eq!(enc(&-2.5f64), (-2.5f64).to_le_bytes());
604        assert_eq!(enc(&true), [1]);
605        assert_eq!(enc(&false), [0]);
606    }
607
608    #[test]
609    fn strings_are_leb128_prefixed() {
610        assert_eq!(enc(&"abc"), [3, b'a', b'b', b'c']);
611        assert_eq!(enc(&""), [0]);
612        // 300 bytes: LEB128 = [0xAC, 0x02].
613        let long = "x".repeat(300);
614        let bytes = enc(&long);
615        assert_eq!(&bytes[..2], &[0xac, 0x02]);
616        assert_eq!(bytes.len(), 302);
617    }
618
619    #[test]
620    fn options_use_null_prefix_bytes() {
621        // Matches the clickhouse crate: 1 = NULL, 0 = value follows.
622        assert_eq!(enc(&Option::<u8>::None), [1]);
623        assert_eq!(enc(&Some(7u8)), [0, 7]);
624        assert_eq!(enc(&Some("hi")), [0, 2, b'h', b'i']);
625    }
626
627    #[test]
628    fn sequences_carry_a_count_tuples_do_not() {
629        assert_eq!(enc(&vec![1u8, 2, 3]), [3, 1, 2, 3]);
630        assert_eq!(enc(&Vec::<u8>::new()), [0]);
631        assert_eq!(enc(&(1u8, 2u8, 3u8)), [1, 2, 3]);
632        assert_eq!(
633            enc(&[1u8, 2, 3]),
634            [1, 2, 3],
635            "fixed arrays = FixedString/Tuple"
636        );
637    }
638
639    #[test]
640    fn maps_are_counted_key_value_pairs() {
641        let mut m = std::collections::BTreeMap::new();
642        m.insert("a".to_string(), 1u8);
643        m.insert("b".to_string(), 2u8);
644        assert_eq!(enc(&m), [2, 1, b'a', 1, 1, b'b', 2]);
645    }
646
647    #[test]
648    fn structs_encode_fields_in_declaration_order() {
649        #[derive(Serialize)]
650        struct Row {
651            id: u64,
652            name: String,
653            score: Option<f64>,
654        }
655        let bytes = enc(&Row {
656            id: 5,
657            name: "n".into(),
658            score: None,
659        });
660        assert_eq!(bytes, [5, 0, 0, 0, 0, 0, 0, 0, 1, b'n', 1]);
661    }
662
663    #[test]
664    fn spike_fixture_row_matches_hand_encoding() {
665        // The exact encoding the validation spike wrote and read back
666        // through the clickhouse crate against a live server.
667        #[derive(Serialize)]
668        struct SpikeRow {
669            id: u64,
670            name: String,
671        }
672        let bytes = enc(&SpikeRow {
673            id: 1500,
674            name: "raw-1500".into(),
675        });
676        let mut expected = 1500u64.to_le_bytes().to_vec();
677        expected.push(8);
678        expected.extend_from_slice(b"raw-1500");
679        assert_eq!(bytes, expected);
680    }
681
682    #[test]
683    fn newtypes_are_transparent() {
684        assert_eq!(enc(&DateTime64Millis(1_000)), enc(&1_000i64));
685        assert_eq!(enc(&DateTimeSeconds(42)), enc(&42u32));
686    }
687
688    #[test]
689    fn unsupported_types_error_instead_of_panicking() {
690        #[derive(Serialize)]
691        enum Unit {
692            A,
693        }
694        assert!(matches!(
695            serialize_row(&Unit::A, &mut BytesMut::new()),
696            Err(RowBinaryError::Unsupported(_))
697        ));
698        assert!(matches!(
699            serialize_row(&'x', &mut BytesMut::new()),
700            Err(RowBinaryError::Unsupported(_))
701        ));
702        assert!(matches!(
703            serialize_row(&(), &mut BytesMut::new()),
704            Err(RowBinaryError::Unsupported(_))
705        ));
706    }
707
708    #[test]
709    fn variant_newtype_gets_a_discriminant_byte() {
710        #[derive(Serialize)]
711        enum V {
712            #[allow(dead_code)]
713            A(u8),
714            B(u16),
715        }
716        // The discriminant is serde's variant_index (declaration order): B
717        // is index 1. The user must declare variants in the order ClickHouse
718        // sorts the column's Variant types (see the module docs).
719        assert_eq!(enc(&V::B(7)), [1, 7, 0]);
720    }
721
722    #[test]
723    fn nested_option_is_rejected() {
724        // Option<Option<T>> has no ClickHouse type (no Nullable(Nullable(T)));
725        // it previously emitted an unparseable double null-prefix.
726        assert!(matches!(
727            serialize_row(&Some(Option::<u8>::None), &mut BytesMut::new()),
728            Err(RowBinaryError::NestedOption)
729        ));
730        assert!(matches!(
731            serialize_row(&Some(Some(7u8)), &mut BytesMut::new()),
732            Err(RowBinaryError::NestedOption)
733        ));
734
735        // A plain Option, and Options nested inside a struct/seq (separate
736        // columns/elements, not nested nullables), still work.
737        assert_eq!(enc(&Some(7u8)), [0, 7]);
738        assert_eq!(enc(&None::<u8>), [1]);
739        #[derive(Serialize)]
740        struct Row {
741            a: Option<u8>,
742            b: Option<u8>,
743        }
744        assert_eq!(
745            enc(&Row {
746                a: Some(1),
747                b: None
748            }),
749            [0, 1, 1]
750        );
751        assert_eq!(enc(&vec![Some(1u8), None]), [2, 0, 1, 1]);
752    }
753
754    #[test]
755    fn flatten_is_rejected_with_a_clear_error() {
756        #[derive(Serialize)]
757        struct Inner {
758            x: u8,
759        }
760        #[derive(Serialize)]
761        struct Flat {
762            id: u8,
763            #[serde(flatten)]
764            inner: Inner,
765        }
766        let err = serialize_row(
767            &Flat {
768                id: 1,
769                inner: Inner { x: 2 },
770            },
771            &mut BytesMut::new(),
772        )
773        .unwrap_err();
774        assert!(matches!(err, RowBinaryError::FlattenUnsupported), "{err}");
775        // The message names flatten, not the old misleading "sequences".
776        assert!(err.to_string().contains("flatten"), "{err}");
777    }
778
779    #[test]
780    fn struct_field_count_mismatch_is_caught() {
781        // A Serialize impl that declares 3 fields but serializes 1 would
782        // misalign every following column; caught at end().
783        struct Liar;
784        impl Serialize for Liar {
785            fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
786                use serde::ser::SerializeStruct;
787                let mut st = s.serialize_struct("Liar", 3)?;
788                st.serialize_field("a", &1u8)?;
789                st.end()
790            }
791        }
792        assert!(
793            matches!(
794                serialize_row(&Liar, &mut BytesMut::new()),
795                Err(RowBinaryError::FieldCountMismatch {
796                    expected: 3,
797                    got: 1
798                })
799            ),
800            "declared/serialized field-count mismatch must error"
801        );
802    }
803
804    #[test]
805    fn serde_repr_enums_encode_as_enum8_and_enum16() {
806        // The documented mechanism for Enum8/Enum16 columns: serde_repr
807        // serializes the discriminant as its #[repr] integer, which the
808        // serializer writes fixed-width like any other i8/i16.
809        #[derive(serde_repr::Serialize_repr)]
810        #[repr(i8)]
811        enum Level8 {
812            #[allow(dead_code)]
813            Low = -1,
814            High = 2,
815        }
816        #[derive(serde_repr::Serialize_repr)]
817        #[repr(i16)]
818        enum Level16 {
819            Big = 300,
820        }
821        assert_eq!(enc(&Level8::High), [2]);
822        assert_eq!(enc(&Level8::Low), [0xff]);
823        assert_eq!(enc(&Level16::Big), 300i16.to_le_bytes());
824    }
825
826    #[test]
827    fn ipv6_default_impl_matches_the_16_byte_wire_format() {
828        // Unlike Ipv4Addr (see crate::serde::ipv4), Ipv6Addr's default
829        // serde impl — 16 network-order octets — is exactly the IPv6
830        // column's FixedString(16)-style layout.
831        use std::net::Ipv6Addr;
832        let localhost = Ipv6Addr::LOCALHOST;
833        assert_eq!(enc(&localhost), localhost.octets());
834        let addr: Ipv6Addr = "2001:db8::8a2e:370:7334".parse().unwrap();
835        assert_eq!(enc(&addr), addr.octets());
836    }
837
838    #[test]
839    fn skip_serializing_if_yields_a_short_row_the_serializer_cannot_detect() {
840        // Documents the known limitation behind the "skip attributes are
841        // unsupported" docs: serde reports the *post-skip* field count to
842        // serialize_struct, so a skipped column looks self-consistent here.
843        #[derive(Serialize)]
844        struct Row {
845            id: u8,
846            #[serde(skip_serializing_if = "Option::is_none")]
847            score: Option<u8>,
848            name: String,
849        }
850        // score present -> 3 fields.
851        assert_eq!(
852            enc(&Row {
853                id: 1,
854                score: Some(9),
855                name: "ab".into()
856            }),
857            [1, 0, 9, 2, b'a', b'b']
858        );
859        // score absent -> only 2 fields emitted (no error), which would
860        // misalign against a 3-column table. Hence: never use skip on rows.
861        assert_eq!(
862            enc(&Row {
863                id: 1,
864                score: None,
865                name: "ab".into()
866            }),
867            [1, 2, b'a', b'b']
868        );
869    }
870}