use clicktype_core::{
ClickInsertable, ClickReadable, ClickTable, DateTime64, LowCardinality, Nullable,
};
use clicktype_macros::ClickTable;
use std::io::Cursor;
#[derive(ClickTable, Debug, Clone)]
#[click_table(name = "events")]
pub struct Event {
pub id: u64,
pub name: String,
pub count: i32,
pub active: bool,
}
#[derive(ClickTable, Debug, Clone)]
#[click_table(name = "users")]
pub struct User {
pub id: u64,
pub username: String,
pub email: Nullable<String>,
pub age: Nullable<i32>,
}
#[derive(ClickTable, Debug, Clone)]
#[click_table(name = "posts")]
pub struct Post {
pub id: u64,
pub title: String,
pub tags: Vec<String>,
pub scores: Vec<i32>,
}
#[derive(ClickTable, Debug, Clone)]
#[click_table(name = "metrics")]
pub struct Metric {
pub id: u64,
pub category: LowCardinality<String>,
pub value: f64,
}
#[derive(ClickTable, Debug, Clone)]
#[click_table(name = "logs")]
pub struct LogEntry {
pub id: u64,
pub timestamp: DateTime64<3>,
pub message: String,
}
#[derive(ClickTable, Debug, Clone)]
#[click_table(name = "bookings")]
pub struct Booking {
pub id: u64,
pub check_in: chrono::NaiveDate,
pub check_out: chrono::NaiveDate,
}
#[derive(ClickTable, Debug, Clone)]
#[click_table(name = "sessions")]
pub struct Session {
pub id: uuid::Uuid,
pub user_id: u64,
}
#[derive(ClickTable, Debug, Clone)]
#[click_table(name = "products", primary_key = "id")]
pub struct Product {
pub id: u64,
pub name: String,
pub price: f64,
}
#[derive(ClickTable, Debug, Clone)]
#[click_table(name = "orders", primary_key = "user_id, order_id")]
pub struct Order {
pub user_id: u64,
pub order_id: u64,
pub total: f64,
}
#[derive(ClickTable, Debug, Clone)]
#[click_table(name = "items")]
pub struct Item {
pub id: u64,
pub name: String,
}
#[derive(ClickTable, Debug, Clone)]
#[click_table(name = "numbers")]
pub struct Numbers {
pub i8_val: i8,
pub i16_val: i16,
pub i32_val: i32,
pub i64_val: i64,
pub u8_val: u8,
pub u16_val: u16,
pub u32_val: u32,
pub u64_val: u64,
}
#[derive(ClickTable, Debug, Clone)]
#[click_table(name = "simple")]
pub struct SimpleTable {
pub id: u64,
}
#[derive(ClickTable, Debug, Clone)]
#[click_table(name = "table1")]
pub struct Table1 {
pub id: u64,
}
#[derive(ClickTable, Debug, Clone)]
#[click_table(name = "table2")]
pub struct Table2 {
pub id: u64,
pub value: String,
}
#[derive(ClickTable, Debug, Clone)]
#[click_table(name = "complex")]
pub struct ComplexData {
pub id: u64,
pub nullable_array: Nullable<Vec<i32>>,
pub array_of_nullable: Vec<Nullable<String>>,
}
#[test]
fn test_basic_struct() {
assert_eq!(Event::table_name(), "events");
let cols = Event::column_names();
assert_eq!(cols.len(), 4);
assert!(cols.contains(&"id"));
assert!(cols.contains(&"name"));
assert!(cols.contains(&"count"));
assert!(cols.contains(&"active"));
let insertable = Event::insertable_columns();
assert_eq!(insertable.len(), 4);
let event = Event {
id: 123,
name: "test_event".to_string(),
count: 42,
active: true,
};
let mut buf = Vec::new();
event.write_row(&mut buf).unwrap();
let decoded = Event::read_row(&mut Cursor::new(&buf)).unwrap();
assert_eq!(decoded.id, event.id);
assert_eq!(decoded.name, event.name);
assert_eq!(decoded.count, event.count);
assert_eq!(decoded.active, event.active);
}
#[test]
fn test_struct_with_nullable() {
assert_eq!(User::table_name(), "users");
let user = User {
id: 1,
username: "alice".to_string(),
email: Nullable::null(),
age: Nullable::null(),
};
let mut buf = Vec::new();
user.write_row(&mut buf).unwrap();
let decoded = User::read_row(&mut Cursor::new(&buf)).unwrap();
assert_eq!(decoded.id, 1);
assert_eq!(decoded.username, "alice");
assert!(decoded.email.is_null());
assert!(decoded.age.is_null());
let user = User {
id: 2,
username: "bob".to_string(),
email: Nullable::some("bob@example.com".to_string()),
age: Nullable::some(30),
};
let mut buf = Vec::new();
user.write_row(&mut buf).unwrap();
let decoded = User::read_row(&mut Cursor::new(&buf)).unwrap();
assert_eq!(decoded.email.0, Some("bob@example.com".to_string()));
assert_eq!(decoded.age.0, Some(30));
}
#[test]
fn test_struct_with_arrays() {
let post = Post {
id: 1,
title: "Hello World".to_string(),
tags: vec!["rust".to_string(), "clickhouse".to_string()],
scores: vec![10, 20, 30],
};
let mut buf = Vec::new();
post.write_row(&mut buf).unwrap();
let decoded = Post::read_row(&mut Cursor::new(&buf)).unwrap();
assert_eq!(decoded.id, 1);
assert_eq!(decoded.title, "Hello World");
assert_eq!(decoded.tags, vec!["rust", "clickhouse"]);
assert_eq!(decoded.scores, vec![10, 20, 30]);
}
#[test]
fn test_struct_with_low_cardinality() {
let metric = Metric {
id: 1,
category: LowCardinality("performance".to_string()),
value: 99.5,
};
let mut buf = Vec::new();
metric.write_row(&mut buf).unwrap();
let decoded = Metric::read_row(&mut Cursor::new(&buf)).unwrap();
assert_eq!(decoded.id, 1);
assert_eq!(decoded.category.0, "performance");
assert_eq!(decoded.value, 99.5);
}
#[test]
fn test_struct_with_datetime64() {
let dt = chrono::NaiveDateTime::from_timestamp_opt(1705334400, 123_456_789).unwrap();
let entry = LogEntry {
id: 1,
timestamp: DateTime64::<3>::from_naive_datetime(dt),
message: "test log".to_string(),
};
let mut buf = Vec::new();
entry.write_row(&mut buf).unwrap();
let decoded = LogEntry::read_row(&mut Cursor::new(&buf)).unwrap();
assert_eq!(decoded.id, 1);
assert_eq!(decoded.timestamp, entry.timestamp);
assert_eq!(decoded.message, "test log");
}
#[test]
fn test_struct_with_dates() {
let booking = Booking {
id: 1,
check_in: chrono::NaiveDate::from_ymd_opt(2024, 1, 15).unwrap(),
check_out: chrono::NaiveDate::from_ymd_opt(2024, 1, 20).unwrap(),
};
let mut buf = Vec::new();
booking.write_row(&mut buf).unwrap();
let decoded = Booking::read_row(&mut Cursor::new(&buf)).unwrap();
assert_eq!(decoded.id, 1);
assert_eq!(decoded.check_in, booking.check_in);
assert_eq!(decoded.check_out, booking.check_out);
}
#[test]
fn test_struct_with_uuid() {
let session = Session {
id: uuid::Uuid::new_v4(),
user_id: 123,
};
let mut buf = Vec::new();
session.write_row(&mut buf).unwrap();
let decoded = Session::read_row(&mut Cursor::new(&buf)).unwrap();
assert_eq!(decoded.id, session.id);
assert_eq!(decoded.user_id, 123);
}
#[test]
fn test_primary_key() {
let pk_cols = Product::primary_key_columns();
assert_eq!(pk_cols.len(), 1);
assert_eq!(pk_cols[0], "id");
}
#[test]
fn test_composite_primary_key() {
let pk_cols = Order::primary_key_columns();
assert_eq!(pk_cols.len(), 2);
assert!(pk_cols.contains(&"user_id"));
assert!(pk_cols.contains(&"order_id"));
}
#[test]
fn test_typed_columns() {
let _columns = Item::columns();
let col_names = Item::column_names();
assert_eq!(col_names.len(), 2);
assert!(col_names.contains(&"id"));
assert!(col_names.contains(&"name"));
}
#[test]
fn test_all_integer_types_in_struct() {
let nums = Numbers {
i8_val: -1,
i16_val: -100,
i32_val: -10000,
i64_val: -1000000,
u8_val: 1,
u16_val: 100,
u32_val: 10000,
u64_val: 1000000,
};
let mut buf = Vec::new();
nums.write_row(&mut buf).unwrap();
let decoded = Numbers::read_row(&mut Cursor::new(&buf)).unwrap();
assert_eq!(decoded.i8_val, -1);
assert_eq!(decoded.i16_val, -100);
assert_eq!(decoded.i32_val, -10000);
assert_eq!(decoded.i64_val, -1000000);
assert_eq!(decoded.u8_val, 1);
assert_eq!(decoded.u16_val, 100);
assert_eq!(decoded.u32_val, 10000);
assert_eq!(decoded.u64_val, 1000000);
}
#[test]
fn test_single_field_struct() {
assert_eq!(SimpleTable::table_name(), "simple");
assert_eq!(SimpleTable::column_names().len(), 1);
assert_eq!(SimpleTable::column_names()[0], "id");
let row = SimpleTable { id: 42 };
let mut buf = Vec::new();
row.write_row(&mut buf).unwrap();
let decoded = SimpleTable::read_row(&mut Cursor::new(&buf)).unwrap();
assert_eq!(decoded.id, 42);
}
#[test]
fn test_multiple_structs() {
assert_eq!(Table1::table_name(), "table1");
assert_eq!(Table2::table_name(), "table2");
assert_eq!(Table1::column_names().len(), 1);
assert_eq!(Table2::column_names().len(), 2);
}
#[test]
fn test_complex_nested_types() {
let data = ComplexData {
id: 1,
nullable_array: Nullable::some(vec![1, 2, 3]),
array_of_nullable: vec![
Nullable::some("a".to_string()),
Nullable::null(),
Nullable::some("c".to_string()),
],
};
let mut buf = Vec::new();
data.write_row(&mut buf).unwrap();
let decoded = ComplexData::read_row(&mut Cursor::new(&buf)).unwrap();
assert_eq!(decoded.id, 1);
assert_eq!(decoded.nullable_array.0, Some(vec![1, 2, 3]));
assert_eq!(decoded.array_of_nullable.len(), 3);
assert_eq!(decoded.array_of_nullable[0].0, Some("a".to_string()));
assert!(decoded.array_of_nullable[1].is_null());
assert_eq!(decoded.array_of_nullable[2].0, Some("c".to_string()));
}
#[test]
fn test_create_table_ddl_simple() {
let ddl = SimpleTable::create_table_ddl();
assert!(ddl.contains("CREATE TABLE simple"));
assert!(ddl.contains("id UInt64"));
assert!(ddl.contains("ENGINE = MergeTree()"));
assert!(ddl.contains("ORDER BY tuple()"));
}
#[test]
fn test_create_table_ddl_with_primary_key() {
let ddl = Product::create_table_ddl();
assert!(ddl.contains("CREATE TABLE products"));
assert!(ddl.contains("id UInt64"));
assert!(ddl.contains("name String"));
assert!(ddl.contains("price Float64"));
assert!(ddl.contains("PRIMARY KEY (id)"));
assert!(ddl.contains("ORDER BY (id)"));
}
#[test]
fn test_create_table_ddl_composite_primary_key() {
let ddl = Order::create_table_ddl();
assert!(ddl.contains("CREATE TABLE orders"));
assert!(ddl.contains("user_id UInt64"));
assert!(ddl.contains("order_id UInt64"));
assert!(ddl.contains("PRIMARY KEY (user_id, order_id)"));
assert!(ddl.contains("ORDER BY (user_id, order_id)"));
}
#[test]
fn test_create_table_ddl_with_nullable() {
let ddl = User::create_table_ddl();
assert!(ddl.contains("CREATE TABLE users"));
assert!(ddl.contains("id UInt64"));
assert!(ddl.contains("username String"));
assert!(ddl.contains("email Nullable(String)"));
assert!(ddl.contains("age Nullable(Int32)"));
}
#[test]
fn test_create_table_ddl_with_arrays() {
let ddl = Post::create_table_ddl();
assert!(ddl.contains("CREATE TABLE posts"));
assert!(ddl.contains("tags Array(String)"));
assert!(ddl.contains("scores Array(Int32)"));
}