use d1_orm_query::{build_conditions, build_tail, Order, Query, Set};
fn sql(q: &Query) -> String {
build_conditions(q, 1).0
}
fn params_len(q: &Query) -> usize {
build_conditions(q, 1).1.len()
}
#[test]
fn empty_query_produces_empty_string() {
let q = Query::new();
assert_eq!(sql(&q), "");
assert_eq!(params_len(&q), 0);
}
#[test]
fn single_eq() {
let q = Query::new().eq("id", "abc");
assert_eq!(sql(&q), "id = ?1");
assert_eq!(params_len(&q), 1);
}
#[test]
fn multiple_conditions_joined_with_and() {
let q = Query::new().eq("status", "active").eq("role", "admin");
assert_eq!(sql(&q), "status = ?1 AND role = ?2");
assert_eq!(params_len(&q), 2);
}
#[test]
fn ne_gt_gte_lt_lte() {
assert_eq!(sql(&Query::new().ne("x", "v")), "x != ?1");
assert_eq!(sql(&Query::new().gt("x", "v")), "x > ?1");
assert_eq!(sql(&Query::new().gte("x", "v")), "x >= ?1");
assert_eq!(sql(&Query::new().lt("x", "v")), "x < ?1");
assert_eq!(sql(&Query::new().lte("x", "v")), "x <= ?1");
}
#[test]
fn is_null_and_is_not_null_produce_no_params() {
let q = Query::new().is_null("bio").is_not_null("email");
assert_eq!(sql(&q), "bio IS NULL AND email IS NOT NULL");
assert_eq!(params_len(&q), 0);
}
#[test]
fn in_operator() {
let q = Query::new().in_("role", vec!["admin", "mod"]);
assert_eq!(sql(&q), "role IN (?1, ?2)");
assert_eq!(params_len(&q), 2);
}
#[test]
fn not_in_operator() {
let q = Query::new().not_in("status", vec!["banned", "deleted"]);
assert_eq!(sql(&q), "status NOT IN (?1, ?2)");
assert_eq!(params_len(&q), 2);
}
#[test]
fn like_and_not_like() {
assert_eq!(sql(&Query::new().like("email", "%@example.com")), "email LIKE ?1");
assert_eq!(sql(&Query::new().not_like("name", "test%")), "name NOT LIKE ?1");
assert_eq!(params_len(&Query::new().like("x", "pat")), 1);
}
#[test]
fn between_uses_two_params() {
let q = Query::new().between("score", "10", "100");
assert_eq!(sql(&q), "score BETWEEN ?1 AND ?2");
assert_eq!(params_len(&q), 2);
}
#[test]
fn or_group() {
let q = Query::new().or(|q| q.eq("vip", "yes").eq("beta", "yes"));
assert_eq!(sql(&q), "(vip = ?1 OR beta = ?2)");
assert_eq!(params_len(&q), 2);
}
#[test]
fn and_group() {
let q = Query::new().and(|q| q.eq("a", "x").eq("b", "y"));
assert_eq!(sql(&q), "(a = ?1 AND b = ?2)");
assert_eq!(params_len(&q), 2);
}
#[test]
fn nested_or_in_and_chain() {
let q = Query::new()
.eq("active", "yes")
.or(|q| q.eq("role", "admin").eq("role", "mod"));
assert_eq!(sql(&q), "active = ?1 AND (role = ?2 OR role = ?3)");
assert_eq!(params_len(&q), 3);
}
#[test]
fn param_numbering_continuous_across_nodes() {
let q = Query::new()
.eq("a", "v")
.in_("b", vec!["p", "q"])
.between("c", "lo", "hi");
assert_eq!(sql(&q), "a = ?1 AND b IN (?2, ?3) AND c BETWEEN ?4 AND ?5");
assert_eq!(params_len(&q), 5);
}
#[test]
fn filter_optional_generates_null_check() {
let q = Query::new().filter_optional("tag", None::<&str>);
assert_eq!(sql(&q), "(?1 IS NULL OR tag = ?1)");
assert_eq!(params_len(&q), 1);
}
#[test]
fn filter_optional_with_some() {
let q = Query::new().filter_optional("tag", Some("rust"));
assert_eq!(sql(&q), "(?1 IS NULL OR tag = ?1)");
assert_eq!(params_len(&q), 1);
}
#[test]
fn filter_optional_gte_and_lte() {
assert_eq!(
sql(&Query::new().filter_optional_gte("score", Some("10"))),
"(?1 IS NULL OR score >= ?1)"
);
assert_eq!(
sql(&Query::new().filter_optional_lte("score", Some("100"))),
"(?1 IS NULL OR score <= ?1)"
);
}
#[test]
fn or_inside_and_chain_with_outer_condition() {
let q = Query::new()
.eq("tenant", "acme")
.or(|q| q.eq("status", "open").eq("status", "pending"))
.eq("deleted", "no");
assert_eq!(
sql(&q),
"tenant = ?1 AND (status = ?2 OR status = ?3) AND deleted = ?4"
);
assert_eq!(params_len(&q), 4);
}
#[test]
fn build_tail_empty() {
assert_eq!(build_tail(&Query::new()), "");
}
#[test]
fn build_tail_order_asc() {
let q = Query::new().order_by("created_at", Order::Asc);
assert_eq!(build_tail(&q), " ORDER BY created_at ASC");
}
#[test]
fn build_tail_order_desc() {
let q = Query::new().order_by("score", Order::Desc);
assert_eq!(build_tail(&q), " ORDER BY score DESC");
}
#[test]
fn build_tail_limit_offset() {
let q = Query::new().limit(10).offset(20);
assert_eq!(build_tail(&q), " LIMIT 10 OFFSET 20");
}
#[test]
fn build_tail_full() {
let q = Query::new().order_by("id", Order::Asc).limit(5).offset(10);
assert_eq!(build_tail(&q), " ORDER BY id ASC LIMIT 5 OFFSET 10");
}
#[test]
fn set_single_field() {
let (sql, vals, next) = Set::new().field("name", "Alice").build(1);
assert_eq!(sql, "name = ?1");
assert_eq!(vals.len(), 1);
assert_eq!(next, 2);
}
#[test]
fn set_multiple_fields() {
let (sql, vals, next) = Set::new().field("a", "x").field("b", "y").build(1);
assert_eq!(sql, "a = ?1, b = ?2");
assert_eq!(vals.len(), 2);
assert_eq!(next, 3);
}
#[test]
fn set_nullable_field_with_none() {
let (sql, vals, _) = Set::new().nullable_field("bio", None::<&str>).build(1);
assert_eq!(sql, "bio = ?1");
assert_eq!(vals.len(), 1);
}
#[test]
fn set_raw_expr_does_not_add_param() {
let (sql, vals, next) = Set::new().raw_expr("score", "score + 1").build(1);
assert_eq!(sql, "score = score + 1");
assert_eq!(vals.len(), 0);
assert_eq!(next, 1);
}
#[test]
fn set_mixed_fields_and_raw() {
let (sql, vals, next) = Set::new()
.field("name", "Bob")
.raw_expr("hits", "hits + 1")
.field("active", "yes")
.build(1);
assert_eq!(sql, "name = ?1, hits = hits + 1, active = ?2");
assert_eq!(vals.len(), 2);
assert_eq!(next, 3);
}
#[test]
fn set_param_start_respected() {
let (sql, vals, next) = Set::new().field("x", "v").build(5);
assert_eq!(sql, "x = ?5");
assert_eq!(vals.len(), 1);
assert_eq!(next, 6);
}
#[test]
fn set_is_empty() {
assert!(Set::new().is_empty());
assert!(!Set::new().field("x", "v").is_empty());
}
#[test]
fn in_single_value() {
let q = Query::new().in_("id", vec!["abc"]);
assert_eq!(sql(&q), "id IN (?1)");
assert_eq!(params_len(&q), 1);
}
#[test]
fn not_in_single_value() {
let q = Query::new().not_in("id", vec!["abc"]);
assert_eq!(sql(&q), "id NOT IN (?1)");
assert_eq!(params_len(&q), 1);
}