#[macro_export]
macro_rules! table {
(
$(#[$meta:meta])*
$vis:vis struct $name:ident {
$( $(#[$fmeta:meta])* $fvis:vis $field:ident : $ty:ty ),+ $(,)?
}
key = [ $( $key:ident ),* $(,)? ];
) => {
$(#[$meta])*
$vis struct $name {
$( $(#[$fmeta])* $fvis $field : $ty ),+
}
impl $crate::table::Table for $name {
fn schema() -> ::core::result::Result<$crate::table::Schema, $crate::table::SchemaError> {
$( let _ = |row: &$name| $crate::__private::is_key(&row.$key); )*
$crate::table::Schema::new(
[ $(
$crate::table::Column::new(
$crate::core::ColumnId::new($crate::core::Identifier::new(
::core::stringify!($field).trim_start_matches("r#"),
)),
<$ty as $crate::cell::FieldType>::cell_type(),
<$ty as $crate::cell::FieldType>::nullable(),
)
),+ ]
.into_iter()
.collect(),
[ $(
$crate::core::ColumnId::new($crate::core::Identifier::new(
::core::stringify!($key).trim_start_matches("r#"),
))
),* ]
.into_iter()
.collect(),
)
}
fn values(self) -> $crate::__private::Vec<$crate::cell::RawValue> {
[ $( $crate::cell::IntoValue::into_value(self.$field) ),+ ]
.into_iter()
.collect()
}
}
};
}
mod key_checks {}
#[cfg(test)]
mod tests {
use alloc::boxed::Box;
use alloc::string::String;
use alloc::vec;
use alloc::vec::Vec;
use crate::cell::{CellType, Decimal, Numeric, RawValue};
use crate::core::{ColumnId, Identifier};
use crate::table::{Column, Schema, Table};
crate::table! {
struct Expense {
id: i64,
description: Option<String>,
amount: Numeric<14, 2>,
tags: Vec<String>,
}
key = [id];
}
crate::table! {
struct Kind {
r#type: i64,
}
key = [r#type];
}
crate::table! {
struct Event {
payload: serde_json::Value,
}
key = [];
}
fn id(name: &str) -> ColumnId {
ColumnId::new(Identifier::new(name))
}
#[test]
fn the_schema_comes_from_the_fields() {
let expected = Schema::new(
vec![
Column::new(id("id"), CellType::Integer, false),
Column::new(id("description"), CellType::Text, true),
Column::new(
id("amount"),
CellType::Decimal {
precision: 14,
scale: 2,
},
false,
),
Column::new(
id("tags"),
CellType::Array {
element: Box::new(CellType::Text),
nullable_elements: false,
},
false,
),
],
vec![id("id")],
);
assert_eq!(Expense::schema(), expected);
}
#[test]
fn the_values_follow_the_schema_order() {
let values = Numeric::new(12_345).map(|amount| {
Expense {
id: 7,
description: None,
amount,
tags: vec![String::from("travel")],
}
.values()
});
let expected = Decimal::new(12_345, 2).map(|amount| {
vec![
RawValue::Integer(7),
RawValue::Null,
RawValue::Decimal(amount),
RawValue::Array(vec![RawValue::Text(String::from("travel"))]),
]
});
assert_eq!(values, expected);
}
#[test]
fn raw_identifiers_name_their_column_without_the_prefix() {
assert_eq!(
Kind::schema().map(|s| s.key().to_vec()),
Ok(vec![id("type")])
);
}
#[test]
fn a_table_may_be_declared_with_no_key() {
assert_eq!(Event::schema().map(|s| s.key().is_empty()), Ok(true));
}
}