use crate::typed::{TypedColumn, TypedTable};
use sz_orm_model::Dialect;
pub trait SqlType: 'static {}
pub struct Bool;
impl SqlType for Bool {}
pub struct Integer;
impl SqlType for Integer {}
pub struct SmallInt;
impl SqlType for SmallInt {}
pub struct BigInt;
impl SqlType for BigInt {}
pub struct Real;
impl SqlType for Real {}
pub struct Double;
impl SqlType for Double {}
pub struct Text;
impl SqlType for Text {}
pub struct Date;
impl SqlType for Date {}
pub struct DateTime;
impl SqlType for DateTime {}
pub struct Json;
impl SqlType for Json {}
pub struct Uuid;
impl SqlType for Uuid {}
pub struct Binary;
impl SqlType for Binary {}
pub struct Nullable<T: SqlType>(pub std::marker::PhantomData<T>);
impl<T: SqlType> SqlType for Nullable<T> {}
pub struct Untyped;
impl SqlType for Untyped {}
pub trait InferSqlType {
type SqlType: SqlType;
}
impl InferSqlType for bool {
type SqlType = Bool;
}
impl InferSqlType for i8 {
type SqlType = SmallInt;
}
impl InferSqlType for i16 {
type SqlType = SmallInt;
}
impl InferSqlType for i32 {
type SqlType = Integer;
}
impl InferSqlType for i64 {
type SqlType = BigInt;
}
impl InferSqlType for u8 {
type SqlType = SmallInt;
}
impl InferSqlType for u16 {
type SqlType = SmallInt;
}
impl InferSqlType for u32 {
type SqlType = Integer;
}
impl InferSqlType for u64 {
type SqlType = BigInt;
}
impl InferSqlType for f32 {
type SqlType = Real;
}
impl InferSqlType for f64 {
type SqlType = Double;
}
impl InferSqlType for String {
type SqlType = Text;
}
impl InferSqlType for Vec<u8> {
type SqlType = Binary;
}
impl InferSqlType for &str {
type SqlType = Text;
}
impl InferSqlType for &String {
type SqlType = Text;
}
impl InferSqlType for &[u8] {
type SqlType = Binary;
}
impl InferSqlType for &Vec<u8> {
type SqlType = Binary;
}
impl<T: InferSqlType> InferSqlType for Option<T> {
type SqlType = Nullable<T::SqlType>;
}
impl InferSqlType for () {
type SqlType = Untyped;
}
pub trait TypedExpression {
type SqlType: SqlType;
fn to_sql(&self, dialect: &dyn Dialect) -> (String, Vec<String>);
}
pub struct ColumnExpr<C: TypedColumn> {
_marker: std::marker::PhantomData<C>,
}
impl<C: TypedColumn> ColumnExpr<C> {
pub fn new() -> Self {
Self {
_marker: std::marker::PhantomData,
}
}
}
impl<C: TypedColumn> Default for ColumnExpr<C> {
fn default() -> Self {
Self::new()
}
}
impl<C: TypedColumn> TypedExpression for ColumnExpr<C> {
type SqlType = C::SqlType;
fn to_sql(&self, dialect: &dyn Dialect) -> (String, Vec<String>) {
let qualified = format!("{}.{}", C::Table::NAME, C::NAME);
(dialect.quote(&qualified), Vec::new())
}
}
pub struct Literal<T: Clone> {
value: T,
}
impl<T: Clone> Literal<T> {
pub fn new(value: T) -> Self {
Self { value }
}
}
impl TypedExpression for Literal<i64> {
type SqlType = BigInt;
fn to_sql(&self, _dialect: &dyn Dialect) -> (String, Vec<String>) {
(String::from("?"), vec![self.value.to_string()])
}
}
impl TypedExpression for Literal<i32> {
type SqlType = Integer;
fn to_sql(&self, _dialect: &dyn Dialect) -> (String, Vec<String>) {
(String::from("?"), vec![self.value.to_string()])
}
}
impl TypedExpression for Literal<i16> {
type SqlType = SmallInt;
fn to_sql(&self, _dialect: &dyn Dialect) -> (String, Vec<String>) {
(String::from("?"), vec![self.value.to_string()])
}
}
impl TypedExpression for Literal<i8> {
type SqlType = SmallInt;
fn to_sql(&self, _dialect: &dyn Dialect) -> (String, Vec<String>) {
(String::from("?"), vec![self.value.to_string()])
}
}
impl TypedExpression for Literal<f64> {
type SqlType = Double;
fn to_sql(&self, _dialect: &dyn Dialect) -> (String, Vec<String>) {
(String::from("?"), vec![self.value.to_string()])
}
}
impl TypedExpression for Literal<f32> {
type SqlType = Real;
fn to_sql(&self, _dialect: &dyn Dialect) -> (String, Vec<String>) {
(String::from("?"), vec![self.value.to_string()])
}
}
impl TypedExpression for Literal<String> {
type SqlType = Text;
fn to_sql(&self, _dialect: &dyn Dialect) -> (String, Vec<String>) {
(String::from("?"), vec![self.value.to_string()])
}
}
impl TypedExpression for Literal<bool> {
type SqlType = Bool;
fn to_sql(&self, _dialect: &dyn Dialect) -> (String, Vec<String>) {
(String::from("?"), vec![self.value.to_string()])
}
}
impl TypedExpression for Literal<Vec<u8>> {
type SqlType = Binary;
fn to_sql(&self, _dialect: &dyn Dialect) -> (String, Vec<String>) {
let hex: String = self.value.iter().map(|b| format!("{:02x}", b)).collect();
(String::from("?"), vec![hex])
}
}
pub struct Eq<C: TypedColumn, V: Clone> {
column: std::marker::PhantomData<C>,
value: V,
}
impl<C: TypedColumn, V: Clone + ToString> Eq<C, V> {
pub fn new(_col: C, value: V) -> Self {
Self {
column: std::marker::PhantomData,
value,
}
}
}
impl<C: TypedColumn, V: Clone + ToString> TypedExpression for Eq<C, V> {
type SqlType = Bool;
fn to_sql(&self, dialect: &dyn Dialect) -> (String, Vec<String>) {
let col_sql = dialect.quote(C::NAME);
(format!("{} = ?", col_sql), vec![self.value.to_string()])
}
}
pub struct Ne<C: TypedColumn, V: Clone> {
column: std::marker::PhantomData<C>,
value: V,
}
impl<C: TypedColumn, V: Clone + ToString> Ne<C, V> {
pub fn new(_col: C, value: V) -> Self {
Self {
column: std::marker::PhantomData,
value,
}
}
}
impl<C: TypedColumn, V: Clone + ToString> TypedExpression for Ne<C, V> {
type SqlType = Bool;
fn to_sql(&self, dialect: &dyn Dialect) -> (String, Vec<String>) {
let col_sql = dialect.quote(C::NAME);
(format!("{} <> ?", col_sql), vec![self.value.to_string()])
}
}
pub struct Lt<C: TypedColumn, V: Clone> {
column: std::marker::PhantomData<C>,
value: V,
}
impl<C: TypedColumn, V: Clone + ToString> Lt<C, V> {
pub fn new(_col: C, value: V) -> Self {
Self {
column: std::marker::PhantomData,
value,
}
}
}
impl<C: TypedColumn, V: Clone + ToString> TypedExpression for Lt<C, V> {
type SqlType = Bool;
fn to_sql(&self, dialect: &dyn Dialect) -> (String, Vec<String>) {
let col_sql = dialect.quote(C::NAME);
(format!("{} < ?", col_sql), vec![self.value.to_string()])
}
}
pub struct Gt<C: TypedColumn, V: Clone> {
column: std::marker::PhantomData<C>,
value: V,
}
impl<C: TypedColumn, V: Clone + ToString> Gt<C, V> {
pub fn new(_col: C, value: V) -> Self {
Self {
column: std::marker::PhantomData,
value,
}
}
}
impl<C: TypedColumn, V: Clone + ToString> TypedExpression for Gt<C, V> {
type SqlType = Bool;
fn to_sql(&self, dialect: &dyn Dialect) -> (String, Vec<String>) {
let col_sql = dialect.quote(C::NAME);
(format!("{} > ?", col_sql), vec![self.value.to_string()])
}
}
pub struct Le<C: TypedColumn, V: Clone> {
column: std::marker::PhantomData<C>,
value: V,
}
impl<C: TypedColumn, V: Clone + ToString> Le<C, V> {
pub fn new(_col: C, value: V) -> Self {
Self {
column: std::marker::PhantomData,
value,
}
}
}
impl<C: TypedColumn, V: Clone + ToString> TypedExpression for Le<C, V> {
type SqlType = Bool;
fn to_sql(&self, dialect: &dyn Dialect) -> (String, Vec<String>) {
let col_sql = dialect.quote(C::NAME);
(format!("{} <= ?", col_sql), vec![self.value.to_string()])
}
}
pub struct Ge<C: TypedColumn, V: Clone> {
column: std::marker::PhantomData<C>,
value: V,
}
impl<C: TypedColumn, V: Clone + ToString> Ge<C, V> {
pub fn new(_col: C, value: V) -> Self {
Self {
column: std::marker::PhantomData,
value,
}
}
}
impl<C: TypedColumn, V: Clone + ToString> TypedExpression for Ge<C, V> {
type SqlType = Bool;
fn to_sql(&self, dialect: &dyn Dialect) -> (String, Vec<String>) {
let col_sql = dialect.quote(C::NAME);
(format!("{} >= ?", col_sql), vec![self.value.to_string()])
}
}
pub struct And<L: TypedExpression<SqlType = Bool>, R: TypedExpression<SqlType = Bool>> {
left: L,
right: R,
}
impl<L: TypedExpression<SqlType = Bool>, R: TypedExpression<SqlType = Bool>> And<L, R> {
pub fn new(left: L, right: R) -> Self {
Self { left, right }
}
}
impl<L: TypedExpression<SqlType = Bool>, R: TypedExpression<SqlType = Bool>> TypedExpression
for And<L, R>
{
type SqlType = Bool;
fn to_sql(&self, dialect: &dyn Dialect) -> (String, Vec<String>) {
let (left_sql, mut left_params) = self.left.to_sql(dialect);
let (right_sql, right_params) = self.right.to_sql(dialect);
left_params.extend(right_params);
(format!("({} AND {})", left_sql, right_sql), left_params)
}
}
pub struct Or<L: TypedExpression<SqlType = Bool>, R: TypedExpression<SqlType = Bool>> {
left: L,
right: R,
}
impl<L: TypedExpression<SqlType = Bool>, R: TypedExpression<SqlType = Bool>> Or<L, R> {
pub fn new(left: L, right: R) -> Self {
Self { left, right }
}
}
impl<L: TypedExpression<SqlType = Bool>, R: TypedExpression<SqlType = Bool>> TypedExpression
for Or<L, R>
{
type SqlType = Bool;
fn to_sql(&self, dialect: &dyn Dialect) -> (String, Vec<String>) {
let (left_sql, mut left_params) = self.left.to_sql(dialect);
let (right_sql, right_params) = self.right.to_sql(dialect);
left_params.extend(right_params);
(format!("({} OR {})", left_sql, right_sql), left_params)
}
}
pub trait ExprTable {
type Table: TypedTable;
}
impl<C: TypedColumn> ExprTable for ColumnExpr<C> {
type Table = C::Table;
}
impl<C: TypedColumn, V: Clone> ExprTable for Eq<C, V> {
type Table = C::Table;
}
impl<C: TypedColumn, V: Clone> ExprTable for Ne<C, V> {
type Table = C::Table;
}
impl<C: TypedColumn, V: Clone> ExprTable for Lt<C, V> {
type Table = C::Table;
}
impl<C: TypedColumn, V: Clone> ExprTable for Gt<C, V> {
type Table = C::Table;
}
impl<C: TypedColumn, V: Clone> ExprTable for Le<C, V> {
type Table = C::Table;
}
impl<C: TypedColumn, V: Clone> ExprTable for Ge<C, V> {
type Table = C::Table;
}
impl<L, R> ExprTable for And<L, R>
where
L: TypedExpression<SqlType = Bool> + ExprTable,
R: TypedExpression<SqlType = Bool> + ExprTable<Table = L::Table>,
{
type Table = L::Table;
}
impl<L, R> ExprTable for Or<L, R>
where
L: TypedExpression<SqlType = Bool> + ExprTable,
R: TypedExpression<SqlType = Bool> + ExprTable<Table = L::Table>,
{
type Table = L::Table;
}
pub struct TypedSelectQuery<T: TypedTable> {
_table: std::marker::PhantomData<T>,
wheres: Vec<Box<dyn TypedExpression<SqlType = Bool>>>,
limit_n: Option<usize>,
offset_n: Option<usize>,
}
impl<T: TypedTable> TypedSelectQuery<T> {
pub fn new() -> Self {
Self {
_table: std::marker::PhantomData,
wheres: Vec::new(),
limit_n: None,
offset_n: None,
}
}
pub fn filter<E>(mut self, expr: E) -> Self
where
E: TypedExpression<SqlType = Bool> + ExprTable<Table = T> + 'static,
{
self.wheres.push(Box::new(expr));
self
}
pub const MAX_LIMIT: usize = 1_000_000;
pub const MAX_OFFSET: usize = 1_000_000_000;
pub fn limit(mut self, n: usize) -> Self {
self.limit_n = Some(n.min(Self::MAX_LIMIT));
self
}
pub fn offset(mut self, n: usize) -> Self {
self.offset_n = Some(n.min(Self::MAX_OFFSET));
self
}
pub fn build(&self, dialect: &dyn Dialect) -> (String, Vec<String>) {
let table_sql = dialect.quote(T::NAME);
let mut sql = format!("SELECT * FROM {}", table_sql);
let mut all_params = Vec::new();
if !self.wheres.is_empty() {
let mut cond_strs = Vec::new();
for w in &self.wheres {
let (s, p) = w.to_sql(dialect);
cond_strs.push(s);
all_params.extend(p);
}
sql.push_str(" WHERE ");
sql.push_str(&cond_strs.join(" AND "));
}
if let Some(limit) = self.limit_n {
let page = match self.offset_n {
Some(offset) if limit > 0 => (offset / limit) as u64 + 1,
_ => 1,
};
sql = dialect.build_pagination(&sql, page, limit as u64);
}
(sql, all_params)
}
}
impl<T: TypedTable> Default for TypedSelectQuery<T> {
fn default() -> Self {
Self::new()
}
}
pub trait TypedColumnExt: TypedColumn + Sized {
fn eq<V: Clone + ToString>(self, value: V) -> Eq<Self, V> {
Eq::new(self, value)
}
fn ne<V: Clone + ToString>(self, value: V) -> Ne<Self, V> {
Ne::new(self, value)
}
fn lt<V: Clone + ToString>(self, value: V) -> Lt<Self, V> {
Lt::new(self, value)
}
fn gt<V: Clone + ToString>(self, value: V) -> Gt<Self, V> {
Gt::new(self, value)
}
fn le<V: Clone + ToString>(self, value: V) -> Le<Self, V> {
Le::new(self, value)
}
fn ge<V: Clone + ToString>(self, value: V) -> Ge<Self, V> {
Ge::new(self, value)
}
}
impl<C: TypedColumn> TypedColumnExt for C {}
#[cfg(test)]
mod tests {
use super::*;
use crate::typed::{TypedColumn, TypedTable};
use sz_orm_model::MySqlDialect;
struct UsersTable;
impl TypedTable for UsersTable {
const NAME: &'static str = "users";
}
struct ColId;
impl TypedColumn for ColId {
const NAME: &'static str = "id";
type Table = UsersTable;
type RustType = i64;
type SqlType = BigInt;
}
struct ColName;
impl TypedColumn for ColName {
const NAME: &'static str = "name";
type Table = UsersTable;
type RustType = String;
type SqlType = Text;
}
struct ColAge;
impl TypedColumn for ColAge {
const NAME: &'static str = "age";
type Table = UsersTable;
type RustType = i64;
type SqlType = BigInt;
}
struct ColScore;
impl TypedColumn for ColScore {
const NAME: &'static str = "score";
type Table = UsersTable;
type RustType = i32;
type SqlType = Integer;
}
struct ColHeight;
impl TypedColumn for ColHeight {
const NAME: &'static str = "height";
type Table = UsersTable;
type RustType = f64;
type SqlType = Double;
}
struct PostsTable;
impl TypedTable for PostsTable {
const NAME: &'static str = "posts";
}
struct ColPostTitle;
impl TypedColumn for ColPostTitle {
const NAME: &'static str = "title";
type Table = PostsTable;
type RustType = String;
type SqlType = Text;
}
#[test]
fn test_eq_expression_sql() {
let dialect = MySqlDialect;
let expr = ColId.eq(42i64);
let (sql, params) = expr.to_sql(&dialect);
assert_eq!(sql, "`id` = ?");
assert_eq!(params, vec!["42"]);
}
#[test]
fn test_ne_expression_sql() {
let dialect = MySqlDialect;
let expr = ColId.ne(0i64);
let (sql, params) = expr.to_sql(&dialect);
assert_eq!(sql, "`id` <> ?");
assert_eq!(params, vec!["0"]);
}
#[test]
fn test_lt_expression_sql() {
let dialect = MySqlDialect;
let expr = ColAge.lt(18i64);
let (sql, params) = expr.to_sql(&dialect);
assert_eq!(sql, "`age` < ?");
assert_eq!(params, vec!["18"]);
}
#[test]
fn test_gt_expression_sql() {
let dialect = MySqlDialect;
let expr = ColAge.gt(18i64);
let (sql, params) = expr.to_sql(&dialect);
assert_eq!(sql, "`age` > ?");
assert_eq!(params, vec!["18"]);
}
#[test]
fn test_le_expression_sql() {
let dialect = MySqlDialect;
let expr = ColAge.le(65i64);
let (sql, params) = expr.to_sql(&dialect);
assert_eq!(sql, "`age` <= ?");
assert_eq!(params, vec!["65"]);
}
#[test]
fn test_ge_expression_sql() {
let dialect = MySqlDialect;
let expr = ColAge.ge(18i64);
let (sql, params) = expr.to_sql(&dialect);
assert_eq!(sql, "`age` >= ?");
assert_eq!(params, vec!["18"]);
}
#[test]
fn test_string_eq_expression() {
let dialect = MySqlDialect;
let expr = ColName.eq("Alice".to_string());
let (sql, params) = expr.to_sql(&dialect);
assert_eq!(sql, "`name` = ?");
assert_eq!(params, vec!["Alice"]);
}
#[test]
fn test_and_expression() {
let dialect = MySqlDialect;
let expr = And::new(ColId.eq(1i64), ColAge.gt(18i64));
let (sql, params) = expr.to_sql(&dialect);
assert_eq!(sql, "(`id` = ? AND `age` > ?)");
assert_eq!(params, vec!["1", "18"]);
}
#[test]
fn test_or_expression() {
let dialect = MySqlDialect;
let expr = Or::new(
ColName.eq("Alice".to_string()),
ColName.eq("Bob".to_string()),
);
let (sql, params) = expr.to_sql(&dialect);
assert_eq!(sql, "(`name` = ? OR `name` = ?)");
assert_eq!(params, vec!["Alice", "Bob"]);
}
#[test]
fn test_nested_and_or() {
let dialect = MySqlDialect;
let left = ColId.eq(1i64);
let right = Or::new(
ColName.eq("Alice".to_string()),
ColName.eq("Bob".to_string()),
);
let expr = And::new(left, right);
let (sql, params) = expr.to_sql(&dialect);
assert_eq!(sql, "(`id` = ? AND (`name` = ? OR `name` = ?))");
assert_eq!(params, vec!["1", "Alice", "Bob"]);
}
#[test]
fn test_select_query_no_filter() {
let dialect = MySqlDialect;
let q = TypedSelectQuery::<UsersTable>::new();
let (sql, params) = q.build(&dialect);
assert_eq!(sql, "SELECT * FROM `users`");
assert!(params.is_empty());
}
#[test]
fn test_select_query_single_filter() {
let dialect = MySqlDialect;
let q = TypedSelectQuery::<UsersTable>::new().filter(ColId.eq(42i64));
let (sql, params) = q.build(&dialect);
assert_eq!(sql, "SELECT * FROM `users` WHERE `id` = ?");
assert_eq!(params, vec!["42"]);
}
#[test]
fn test_select_query_multiple_filters() {
let dialect = MySqlDialect;
let q = TypedSelectQuery::<UsersTable>::new()
.filter(ColId.eq(1i64))
.filter(ColAge.gt(18i64))
.filter(ColName.ne("guest".to_string()));
let (sql, params) = q.build(&dialect);
assert_eq!(
sql,
"SELECT * FROM `users` WHERE `id` = ? AND `age` > ? AND `name` <> ?"
);
assert_eq!(params, vec!["1", "18", "guest"]);
}
#[test]
fn test_select_query_with_limit_offset() {
let dialect = MySqlDialect;
let q = TypedSelectQuery::<UsersTable>::new()
.filter(ColAge.ge(18i64))
.limit(10)
.offset(20);
let (sql, params) = q.build(&dialect);
assert_eq!(
sql,
"SELECT * FROM `users` WHERE `age` >= ? LIMIT 10 OFFSET 20"
);
assert_eq!(params, vec!["18"]);
}
#[test]
fn test_l3_limit_clamp_to_max() {
let dialect = MySqlDialect;
let q = TypedSelectQuery::<UsersTable>::new().limit(usize::MAX);
assert_eq!(q.limit_n, Some(TypedSelectQuery::<UsersTable>::MAX_LIMIT));
let (sql, _) = q.build(&dialect);
assert!(sql.contains(&format!(
"LIMIT {}",
TypedSelectQuery::<UsersTable>::MAX_LIMIT
)));
}
#[test]
fn test_l3_offset_clamp_to_max() {
let dialect = MySqlDialect;
let q = TypedSelectQuery::<UsersTable>::new()
.limit(10)
.offset(usize::MAX);
assert_eq!(q.offset_n, Some(TypedSelectQuery::<UsersTable>::MAX_OFFSET));
let (sql, _) = q.build(&dialect);
assert!(sql.contains("LIMIT 10"));
}
#[test]
fn test_l3_normal_limit_offset_not_clamped() {
let q1 = TypedSelectQuery::<UsersTable>::new().limit(100);
assert_eq!(q1.limit_n, Some(100));
let q2 = TypedSelectQuery::<UsersTable>::new().offset(1000);
assert_eq!(q2.offset_n, Some(1000));
let q3 =
TypedSelectQuery::<UsersTable>::new().limit(TypedSelectQuery::<UsersTable>::MAX_LIMIT);
assert_eq!(q3.limit_n, Some(TypedSelectQuery::<UsersTable>::MAX_LIMIT));
let q4 = TypedSelectQuery::<UsersTable>::new()
.offset(TypedSelectQuery::<UsersTable>::MAX_OFFSET);
assert_eq!(
q4.offset_n,
Some(TypedSelectQuery::<UsersTable>::MAX_OFFSET)
);
}
#[test]
fn test_select_query_with_complex_and_or() {
let dialect = MySqlDialect;
let q = TypedSelectQuery::<UsersTable>::new().filter(And::new(
ColAge.ge(18i64),
Or::new(
ColName.eq("Alice".to_string()),
ColName.eq("Bob".to_string()),
),
));
let (sql, params) = q.build(&dialect);
assert_eq!(
sql,
"SELECT * FROM `users` WHERE (`age` >= ? AND (`name` = ? OR `name` = ?))"
);
assert_eq!(params, vec!["18", "Alice", "Bob"]);
}
#[test]
fn test_compile_time_type_safety_i64_column() {
fn _assert_i64<C: TypedColumn<RustType = i64>>(_: C) {}
_assert_i64(ColId);
_assert_i64(ColAge);
}
#[test]
fn test_compile_time_type_safety_string_column() {
fn _assert_string<C: TypedColumn<RustType = String>>(_: C) {}
_assert_string(ColName);
}
#[test]
fn test_compile_time_table_association() {
fn _assert_users_table<C: TypedColumn<Table = UsersTable>>(_: C) {}
_assert_users_table(ColId);
_assert_users_table(ColName);
_assert_users_table(ColAge);
fn _assert_posts_table<C: TypedColumn<Table = PostsTable>>(_: C) {}
_assert_posts_table(ColPostTitle);
}
#[test]
fn test_compile_time_bool_expression() {
fn _assert_bool<E: TypedExpression<SqlType = Bool>>(_: E) {}
_assert_bool(ColId.eq(1i64));
_assert_bool(ColAge.lt(18i64));
_assert_bool(ColName.ne("x".to_string()));
_assert_bool(And::new(ColId.eq(1i64), ColAge.gt(18i64)));
_assert_bool(Or::new(
ColName.eq("a".to_string()),
ColName.eq("b".to_string()),
));
}
#[test]
fn test_cross_table_column_has_correct_table_association() {
fn _assert_post_table<C: TypedColumn<Table = PostsTable>>(_: C) {}
_assert_post_table(ColPostTitle);
fn _assert_user_table<C: TypedColumn<Table = UsersTable>>(_: C) {}
_assert_user_table(ColId);
}
#[test]
fn test_expr_table_for_column_expressions() {
fn _assert_expr_table<E: ExprTable<Table = UsersTable>>(_: E) {}
_assert_expr_table(ColId.eq(1i64));
_assert_expr_table(ColName.eq("Alice".to_string()));
_assert_expr_table(ColAge.gt(18i64));
_assert_expr_table(ColAge.lt(65i64));
_assert_expr_table(ColAge.le(65i64));
_assert_expr_table(ColAge.ge(18i64));
_assert_expr_table(ColId.ne(0i64));
}
#[test]
fn test_expr_table_for_logical_combinations() {
fn _assert_expr_table<E: ExprTable<Table = UsersTable>>(_: E) {}
_assert_expr_table(And::new(ColId.eq(1i64), ColAge.gt(18i64)));
_assert_expr_table(Or::new(
ColName.eq("a".to_string()),
ColName.eq("b".to_string()),
));
_assert_expr_table(And::new(
ColAge.ge(18i64),
Or::new(ColName.eq("a".to_string()), ColName.eq("b".to_string())),
));
}
#[test]
fn test_cross_table_logical_combination_rejected_at_compile_time() {
fn _assert_expr_table<E: ExprTable<Table = UsersTable>>(_: E) {}
let expr = And::new(ColId.eq(1i64), ColAge.gt(18i64));
_assert_expr_table(expr);
let expr = Or::new(ColName.eq("a".to_string()), ColName.eq("b".to_string()));
_assert_expr_table(expr);
}
#[test]
fn test_sql_type_markers() {
assert_eq!(std::mem::size_of::<Bool>(), 0);
assert_eq!(std::mem::size_of::<Integer>(), 0);
assert_eq!(std::mem::size_of::<SmallInt>(), 0);
assert_eq!(std::mem::size_of::<BigInt>(), 0);
assert_eq!(std::mem::size_of::<Real>(), 0);
assert_eq!(std::mem::size_of::<Double>(), 0);
assert_eq!(std::mem::size_of::<Text>(), 0);
assert_eq!(std::mem::size_of::<Date>(), 0);
assert_eq!(std::mem::size_of::<DateTime>(), 0);
assert_eq!(std::mem::size_of::<Json>(), 0);
assert_eq!(std::mem::size_of::<Uuid>(), 0);
assert_eq!(std::mem::size_of::<Binary>(), 0);
assert_eq!(std::mem::size_of::<Untyped>(), 0);
assert_eq!(std::mem::size_of::<Nullable<Integer>>(), 0);
assert_eq!(std::mem::size_of::<Nullable<Text>>(), 0);
}
#[test]
fn test_infer_sql_type_mapping() {
fn _assert_bool<T: InferSqlType<SqlType = Bool>>(_: T) {}
fn _assert_smallint<T: InferSqlType<SqlType = SmallInt>>(_: T) {}
fn _assert_integer<T: InferSqlType<SqlType = Integer>>(_: T) {}
fn _assert_bigint<T: InferSqlType<SqlType = BigInt>>(_: T) {}
fn _assert_real<T: InferSqlType<SqlType = Real>>(_: T) {}
fn _assert_double<T: InferSqlType<SqlType = Double>>(_: T) {}
fn _assert_text<T: InferSqlType<SqlType = Text>>(_: T) {}
fn _assert_binary<T: InferSqlType<SqlType = Binary>>(_: T) {}
fn _assert_nullable_bigint<T: InferSqlType<SqlType = Nullable<BigInt>>>(_: T) {}
fn _assert_untyped<T: InferSqlType<SqlType = Untyped>>(_: T) {}
_assert_bool(true);
_assert_smallint(0i8);
_assert_smallint(0u8);
_assert_smallint(0i16);
_assert_smallint(0u16);
_assert_integer(0i32);
_assert_integer(0u32);
_assert_bigint(0i64);
_assert_bigint(0u64);
_assert_real(0.0f32);
_assert_double(0.0f64);
_assert_text(String::new());
_assert_binary(Vec::<u8>::new());
_assert_nullable_bigint(Some(0i64));
_assert_untyped(());
let s = String::new();
let v: Vec<u8> = Vec::new();
_assert_text("hello");
_assert_text(&s);
_assert_binary(&[1u8, 2u8, 3u8][..]);
_assert_binary(&v);
}
#[test]
fn test_column_sql_type_propagation() {
fn _assert_bigint<E: TypedExpression<SqlType = BigInt>>(_: E) {}
fn _assert_integer<E: TypedExpression<SqlType = Integer>>(_: E) {}
fn _assert_double<E: TypedExpression<SqlType = Double>>(_: E) {}
fn _assert_text<E: TypedExpression<SqlType = Text>>(_: E) {}
_assert_bigint(ColumnExpr::<ColId>::new());
_assert_bigint(ColumnExpr::<ColAge>::new());
_assert_integer(ColumnExpr::<ColScore>::new());
_assert_double(ColumnExpr::<ColHeight>::new());
_assert_text(ColumnExpr::<ColName>::new());
_assert_text(ColumnExpr::<ColPostTitle>::new());
}
#[test]
fn test_literal_sql_type_specialized() {
fn _assert_bigint<E: TypedExpression<SqlType = BigInt>>(_: E) {}
fn _assert_integer<E: TypedExpression<SqlType = Integer>>(_: E) {}
fn _assert_smallint<E: TypedExpression<SqlType = SmallInt>>(_: E) {}
fn _assert_double<E: TypedExpression<SqlType = Double>>(_: E) {}
fn _assert_real<E: TypedExpression<SqlType = Real>>(_: E) {}
fn _assert_text<E: TypedExpression<SqlType = Text>>(_: E) {}
fn _assert_bool<E: TypedExpression<SqlType = Bool>>(_: E) {}
fn _assert_binary<E: TypedExpression<SqlType = Binary>>(_: E) {}
_assert_bigint(Literal::new(42i64));
_assert_integer(Literal::new(7i32));
_assert_smallint(Literal::new(3i16));
_assert_smallint(Literal::new(3i8));
_assert_double(Literal::new(1.5f64));
_assert_real(Literal::new(1.0f32));
_assert_text(Literal::new("hello".to_string()));
_assert_bool(Literal::new(true));
_assert_binary(Literal::new(vec![1u8, 2u8, 3u8]));
}
#[test]
fn test_typed_select_query_is_zero_cost() {
let q = TypedSelectQuery::<UsersTable>::new();
assert_eq!(q.wheres.len(), 0);
}
#[test]
fn test_typed_select_query_default() {
let q = TypedSelectQuery::<UsersTable>::default();
let dialect = MySqlDialect;
let (sql, _) = q.build(&dialect);
assert_eq!(sql, "SELECT * FROM `users`");
}
}