use crate::sql;
use serde::{Deserialize, Serialize};
#[doc(hidden)]
#[derive(Debug, Clone, PartialEq)]
pub enum RowKind {
Primitive,
Struct,
Tuple,
Vec,
}
pub trait Row {
#[doc(hidden)]
const NAME: &'static str;
#[doc(hidden)]
const COLUMN_NAMES: &'static [&'static str];
#[doc(hidden)]
const COLUMN_COUNT: usize;
#[doc(hidden)]
const KIND: RowKind;
#[doc(hidden)]
type Value<'a>: Row;
}
pub trait RowRead: for<'a> Row<Value<'a>: Deserialize<'a>> {}
impl<R> RowRead for R where R: for<'a> Row<Value<'a>: Deserialize<'a>> {}
pub trait RowWrite: for<'a> Row<Value<'a>: Serialize> {}
impl<R> RowWrite for R where R: for<'a> Row<Value<'a>: Serialize> {}
pub trait RowOwned: 'static + for<'a> Row<Value<'a> = Self> {}
impl<R> RowOwned for R where R: 'static + for<'a> Row<Value<'a> = R> {}
#[doc(hidden)]
pub trait Primitive {}
impl<T: Primitive> Primitive for Option<T> {}
macro_rules! impl_primitive_for {
($t:ty, $($other:tt)*) => {
impl Primitive for $t {}
impl_primitive_for!($($other)*);
};
() => {};
}
impl_primitive_for![
bool,
String,
u8,
u16,
u32,
u64,
u128,
usize,
i8,
i16,
i32,
i64,
i128,
isize,
f32,
f64,
bytes::Bytes,
bytes::BytesMut,
];
macro_rules! count_tokens {
() => { 0 };
($head:tt $($tail:tt)*) => { 1 + count_tokens!($($tail)*) };
}
macro_rules! impl_row_for_tuple {
($i:ident $($other:ident)+) => {
impl<$i: Row, $($other: Primitive),+> Row for ($i, $($other),+) {
const NAME: &'static str = $i::NAME;
const COLUMN_NAMES: &'static [&'static str] = $i::COLUMN_NAMES;
const COLUMN_COUNT: usize = $i::COLUMN_COUNT + count_tokens!($($other)*);
const KIND: RowKind = RowKind::Tuple;
type Value<'a> = Self;
}
impl_row_for_tuple!($($other)+);
};
($i:ident) => {};
}
impl Primitive for () {}
impl<P: Primitive> Row for P {
const NAME: &'static str = stringify!(P);
const COLUMN_NAMES: &'static [&'static str] = &[];
const COLUMN_COUNT: usize = 1;
const KIND: RowKind = RowKind::Primitive;
type Value<'a> = Self;
}
impl_row_for_tuple!(T0 T1 T2 T3 T4 T5 T6 T7 T8);
impl<T> Row for Vec<T> {
const NAME: &'static str = "Vec";
const COLUMN_NAMES: &'static [&'static str] = &[];
const COLUMN_COUNT: usize = 1;
const KIND: RowKind = RowKind::Vec;
type Value<'a> = Self;
}
pub(crate) fn join_column_names<R: Row>() -> Option<String> {
if R::COLUMN_NAMES.is_empty() {
return None;
}
let out = R::COLUMN_NAMES
.iter()
.enumerate()
.fold(String::new(), |mut res, (idx, name)| {
if idx > 0 {
res.push(',');
}
sql::escape::identifier(name, &mut res).expect("impossible");
res
});
Some(out)
}
#[cfg(test)]
mod tests {
use crate::Row;
use super::*;
#[test]
fn it_grabs_simple_struct() {
#[derive(Row)]
#[clickhouse(crate = "crate")]
#[allow(dead_code)]
struct Simple1 {
one: u32,
}
#[derive(Row)]
#[clickhouse(crate = "crate")]
#[allow(dead_code)]
struct Simple2 {
one: u32,
two: u32,
}
assert_eq!(join_column_names::<Simple1>().unwrap(), "`one`");
assert_eq!(join_column_names::<Simple2>().unwrap(), "`one`,`two`");
}
#[test]
fn it_grabs_mix() {
#[derive(Row)]
#[clickhouse(crate = "crate")]
struct SomeRow {
_a: u32,
}
assert_eq!(join_column_names::<(SomeRow, u32)>().unwrap(), "`_a`");
}
#[test]
fn it_supports_renaming() {
use serde::Serialize;
#[derive(Row, Serialize)]
#[clickhouse(crate = "crate")]
#[allow(dead_code)]
struct TopLevel {
#[serde(rename = "two")]
one: u32,
}
assert_eq!(join_column_names::<TopLevel>().unwrap(), "`two`");
}
#[test]
fn it_skips_serializing() {
use serde::Serialize;
#[derive(Row, Serialize)]
#[clickhouse(crate = "crate")]
#[allow(dead_code)]
struct TopLevel {
one: u32,
#[serde(skip_serializing)]
two: u32,
}
assert_eq!(join_column_names::<TopLevel>().unwrap(), "`one`");
}
#[test]
fn it_skips_deserializing() {
use serde::Deserialize;
#[derive(Row, Deserialize)]
#[clickhouse(crate = "crate")]
#[allow(dead_code)]
struct TopLevel {
one: u32,
#[serde(skip_deserializing)]
two: u32,
}
assert_eq!(join_column_names::<TopLevel>().unwrap(), "`one`");
}
#[test]
fn it_rejects_other() {
#[allow(dead_code)]
#[derive(Row)]
#[clickhouse(crate = "crate")]
struct NamedTuple(u32, u32);
assert_eq!(join_column_names::<u32>(), None);
assert_eq!(join_column_names::<(u32, u64)>(), None);
assert_eq!(join_column_names::<NamedTuple>(), None);
}
#[test]
fn it_handles_raw_identifiers() {
use serde::Serialize;
#[derive(Row, Serialize)]
#[clickhouse(crate = "crate")]
#[allow(dead_code)]
struct MyRow {
r#type: u32,
#[serde(rename = "if")]
r#match: u32,
}
assert_eq!(join_column_names::<MyRow>().unwrap(), "`type`,`if`");
}
}