#![cfg(feature = "sqlite")]
pub mod _test_common;
use ormer::query::builder::Select;
use ormer::{DbType, TimePart};
define_test_user_for_join!(FixP0User, "fix_p0_users");
define_test_role!(FixP0Role, "fix_p0_roles");
define_test_role_for_join!(FixP0JoinRole, "fix_p0_join_roles");
#[derive(Debug, ormer::Model, Clone)]
#[table = "fix_p0_grades"]
struct FixP0Grade {
#[primary]
id: i32,
uid: i32,
level: i32,
}
#[derive(Debug, ormer::Model, Clone)]
#[table = "fix_p0_events"]
struct FixP0Event {
#[primary(auto)]
id: i32,
occurred_at: chrono::NaiveDateTime,
}
#[test]
fn related_select_value_filter_uses_right_table_alias() {
let sql = Select::<FixP0User>::new()
.from::<FixP0Role>()
.filter(|p, q| p.id.eq(q.uid))
.filter(|_, q| q.name.eq("admin".to_string()))
.to_sql_with_params(DbType::Sqlite)
.0;
println!("RelatedSelect SQL: {sql}");
assert!(sql.contains("FROM fix_p0_users AS t0, fix_p0_roles AS t1"), "{sql}");
assert!(sql.contains("t0.id = t1.uid"), "{sql}");
assert!(sql.contains("t1.name = ?"), "{sql}");
assert!(!sql.contains("t0.name = ?"), "{sql}");
}
#[test]
fn related_select_main_table_filter_keeps_t0() {
let sql = Select::<FixP0User>::new()
.from::<FixP0Role>()
.filter(|p, q| p.id.eq(q.uid))
.filter(|p, _| p.age.ge(18))
.to_sql_with_params(DbType::Sqlite)
.0;
println!("RelatedSelect main-table SQL: {sql}");
assert!(sql.contains("t0.age >= ?"), "{sql}");
assert!(!sql.contains("t1.age"), "{sql}");
}
#[test]
fn related_select_value_filter_variants_use_right_table_alias() {
let sql = Select::<FixP0User>::new()
.from::<FixP0Role>()
.filter(|p, q| p.id.eq(q.uid))
.filter(|_, q| q.uid.is_in(vec![1, 2]))
.filter(|_, q| q.name.is_null())
.filter(|_, q| q.id.between(1, 10))
.to_sql_with_params(DbType::Sqlite)
.0;
println!("RelatedSelect variants SQL: {sql}");
assert!(sql.contains("t1.uid IN (?, ?)"), "{sql}");
assert!(sql.contains("t1.name IS NULL"), "{sql}");
assert!(sql.contains("t1.id BETWEEN ? AND ?"), "{sql}");
}
#[test]
fn single_table_filter_stays_unqualified() {
let sql = Select::<FixP0User>::new()
.filter(|p| p.name.eq("bob".to_string()))
.filter(|p| p.age.ge(18))
.to_sql_with_params(DbType::Sqlite)
.0;
println!("Single-table SQL: {sql}");
assert!(sql.contains("WHERE name = ? AND age >= ?"), "{sql}");
assert!(!sql.contains("t0."), "{sql}");
}
#[test]
fn multi_table_filter_uses_each_related_alias() {
let sql = Select::<FixP0User>::new()
.from3::<FixP0Role, FixP0Grade>()
.filter(|p, q1, _q2| p.id.eq(q1.uid))
.filter(|_, q1, _q2| q1.name.eq("admin".to_string()))
.filter(|_, _, q2| q2.level.ge(3))
.to_sql_with_params(DbType::Sqlite)
.0;
println!("MultiTableSelect SQL: {sql}");
assert!(sql.contains("FROM fix_p0_users AS t0, fix_p0_roles AS t1, fix_p0_grades AS t2"), "{sql}");
assert!(sql.contains("t1.name = ?"), "{sql}");
assert!(sql.contains("t2.level >= ?"), "{sql}");
}
#[test]
fn four_table_filter_uses_third_related_alias() {
let sql = Select::<FixP0User>::new()
.from4::<FixP0Role, FixP0Grade, FixP0JoinRole>()
.filter(|p, q1, _q2, _q3| p.id.eq(q1.uid))
.filter(|_, _, _, q3| q3.role_name.eq("admin".to_string()))
.to_sql_with_params(DbType::Sqlite)
.0;
println!("FourTableSelect SQL: {sql}");
assert!(sql.contains("t3.role_name = ?"), "{sql}");
}
#[test]
fn join_on_condition_qualifies_right_table_value_filter() {
let sql = Select::<FixP0User>::new()
.left_join::<FixP0JoinRole>(|p, q| p.id.eq(q.uid).and(q.role_name.eq("admin".to_string())))
.to_sql_with_params(DbType::Sqlite)
.0;
println!("JOIN ON SQL: {sql}");
assert!(sql.contains("ON (t0.id = t1.uid AND t1.role_name = ?)"), "{sql}");
assert!(!sql.contains("t0.role_name"), "{sql}");
}
#[test]
fn join_on_condition_column_comparison_unchanged() {
let sql = Select::<FixP0User>::new()
.left_join::<FixP0JoinRole>(|p, q| p.id.eq(q.uid))
.to_sql_with_params(DbType::Sqlite)
.0;
println!("Plain JOIN SQL: {sql}");
assert!(sql.contains("ON t0.id = t1.uid"), "{sql}");
}
#[test]
fn grouped_select_aggregate_sql_stable_across_renders() {
let build_sql = || {
Select::<FixP0User>::new()
.select_column(|u| u.id.count())
.group_by(|u| u.age)
.to_sql()
};
let first = build_sql();
let second = build_sql();
println!("GroupedSelect SQL: {first}");
assert_eq!(first, second);
assert!(first.contains("SELECT COUNT(id)"), "{first}");
assert!(first.contains("GROUP BY age"), "{first}");
}
fn date_diff_projection_sql(part: TimePart) -> String {
Select::<FixP0Event>::new()
.map_to(|e| {
(
e.id,
e.occurred_at.until(ormer::now(), part).alias("diff"),
)
})
.to_sql_with_params(DbType::Sqlite)
.0
}
#[test]
fn sqlite_date_diff_formula_per_unit() {
let cases = [
(TimePart::Second, "86400 / 1"),
(TimePart::Minute, "86400 / 60"),
(TimePart::Hour, "86400 / 3600"),
(TimePart::Day, "86400 / 86400"),
(TimePart::Week, "86400 / 604800"),
(TimePart::Month, "86400 / 2629746"),
(TimePart::Year, "86400 / 31556952"),
];
for (part, conversion) in cases {
let sql = date_diff_projection_sql(part);
let expected = format!(
"CAST((julianday(occurred_at) - julianday(datetime('now'))) * {conversion} AS INTEGER)"
);
assert!(sql.contains(&expected), "part {part:?}: {sql}");
}
}