#![allow(dead_code)]
use chrono::{DateTime, Utc};
use sqlx::SqlitePool;
use umbral::orm::column::{DateTimeColExt, StrColExt};
use umbral_core::db;
#[derive(
Debug, Clone, PartialEq, sqlx::FromRow, serde::Serialize, serde::Deserialize, umbral::orm::Model,
)]
#[umbral(table = "fn_post")]
pub struct Post {
pub id: i64,
pub title: String,
pub created_at: DateTime<Utc>,
}
async fn fresh_pool() -> SqlitePool {
let pool = db::connect_sqlite("sqlite::memory:")
.await
.expect("in-memory SQLite");
sqlx::query(
"CREATE TABLE fn_post (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
created_at TEXT NOT NULL
)",
)
.execute(&pool)
.await
.expect("CREATE TABLE");
for (title, ts) in &[
("Hello", "2024-06-04T12:00:00+00:00"),
("WORLD", "2025-01-15T08:30:00+00:00"),
("rust", "2026-06-04T10:00:00+00:00"),
("Mixed Case", "2026-06-20T15:00:00+00:00"),
] {
sqlx::query("INSERT INTO fn_post (title, created_at) VALUES (?, ?)")
.bind(*title)
.bind(*ts)
.execute(&pool)
.await
.expect("seed");
}
pool
}
#[tokio::test]
async fn lower_eq_finds_case_insensitive_match() {
let pool = fresh_pool().await;
let rows = Post::objects()
.filter(post::TITLE.lower().eq("hello"))
.on(&pool)
.fetch()
.await
.expect("filter LOWER");
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].title, "Hello");
}
#[tokio::test]
async fn upper_eq_finds_case_insensitive_match() {
let pool = fresh_pool().await;
let rows = Post::objects()
.filter(post::TITLE.upper().eq("WORLD"))
.on(&pool)
.fetch()
.await
.expect("filter UPPER");
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].title, "WORLD");
}
#[tokio::test]
async fn length_lt_filters_by_string_length() {
let pool = fresh_pool().await;
let rows = Post::objects()
.filter(post::TITLE.length().lt(6))
.on(&pool)
.fetch()
.await
.expect("filter LENGTH");
let mut titles: Vec<&str> = rows.iter().map(|r| r.title.as_str()).collect();
titles.sort();
assert_eq!(titles, vec!["Hello", "WORLD", "rust"]);
}
#[tokio::test]
async fn year_eq_filters_by_year() {
let pool = fresh_pool().await;
let rows = Post::objects()
.filter(post::CREATED_AT.year().eq(2026))
.on(&pool)
.fetch()
.await
.expect("filter YEAR");
assert_eq!(rows.len(), 2);
assert!(
rows.iter()
.all(|r| r.created_at.format("%Y").to_string() == "2026")
);
}
#[tokio::test]
async fn month_eq_filters_by_month() {
let pool = fresh_pool().await;
let rows = Post::objects()
.filter(post::CREATED_AT.month().eq(6))
.on(&pool)
.fetch()
.await
.expect("filter MONTH");
assert_eq!(rows.len(), 3);
}
#[tokio::test]
async fn day_eq_filters_by_day_of_month() {
let pool = fresh_pool().await;
let rows = Post::objects()
.filter(post::CREATED_AT.day().eq(4))
.on(&pool)
.fetch()
.await
.expect("filter DAY");
assert_eq!(rows.len(), 2);
}
#[tokio::test]
async fn year_and_month_compose() {
let pool = fresh_pool().await;
let rows = Post::objects()
.filter(post::CREATED_AT.year().eq(2026))
.filter(post::CREATED_AT.month().eq(6))
.on(&pool)
.fetch()
.await
.expect("filter YEAR+MONTH");
assert_eq!(rows.len(), 2);
}
#[tokio::test]
async fn hour_eq_filters_by_hour_of_day() {
let pool = fresh_pool().await;
let rows = Post::objects()
.filter(post::CREATED_AT.hour().eq(12))
.on(&pool)
.fetch()
.await
.expect("filter HOUR");
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].title, "Hello");
}
#[tokio::test]
async fn minute_eq_filters_by_minute_of_hour() {
let pool = fresh_pool().await;
let rows = Post::objects()
.filter(post::CREATED_AT.minute().eq(30))
.on(&pool)
.fetch()
.await
.expect("filter MINUTE");
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].title, "WORLD");
}
#[tokio::test]
async fn second_eq_filters_by_whole_seconds() {
let pool = fresh_pool().await;
let rows = Post::objects()
.filter(post::CREATED_AT.second().eq(0))
.on(&pool)
.fetch()
.await
.expect("filter SECOND");
assert_eq!(rows.len(), 4);
}
#[tokio::test]
async fn week_day_filters_match_calendar_days() {
let pool = fresh_pool().await;
let rows = Post::objects()
.filter(post::CREATED_AT.week_day().eq(2))
.on(&pool)
.fetch()
.await
.expect("filter DOW");
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].title, "Hello");
}
#[tokio::test]
async fn hour_and_minute_compose() {
let pool = fresh_pool().await;
let rows = Post::objects()
.filter(post::CREATED_AT.hour().eq(15))
.filter(post::CREATED_AT.minute().eq(0))
.on(&pool)
.fetch()
.await
.expect("filter HOUR+MINUTE");
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].title, "Mixed Case");
}
#[derive(
Debug, Clone, PartialEq, sqlx::FromRow, serde::Serialize, serde::Deserialize, umbral::orm::Model,
)]
#[umbral(table = "fn_tagged")]
pub struct Tagged {
pub id: i64,
pub label: String,
pub note: Option<String>,
}
async fn tagged_pool() -> SqlitePool {
let pool = db::connect_sqlite("sqlite::memory:")
.await
.expect("in-memory SQLite");
sqlx::query(
"CREATE TABLE fn_tagged (
id INTEGER PRIMARY KEY AUTOINCREMENT,
label TEXT NOT NULL,
note TEXT
)",
)
.execute(&pool)
.await
.expect("CREATE TABLE");
for (label, note) in &[
(" spaced ", Some("x")), ("plain", None), ("alpha", Some("beta")), ] {
sqlx::query("INSERT INTO fn_tagged (label, note) VALUES (?, ?)")
.bind(*label)
.bind(*note)
.execute(&pool)
.await
.expect("seed");
}
pool
}
#[tokio::test]
async fn trim_eq_matches_after_stripping_whitespace() {
let pool = tagged_pool().await;
let rows = Tagged::objects()
.filter(tagged::LABEL.trim().eq("spaced"))
.on(&pool)
.fetch()
.await
.expect("filter TRIM");
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].label, " spaced "); }
#[tokio::test]
async fn coalesce_substitutes_for_null() {
let pool = tagged_pool().await;
let rows = Tagged::objects()
.filter(tagged::NOTE.coalesce("zzz").eq("zzz"))
.on(&pool)
.fetch()
.await
.expect("filter COALESCE null");
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].label, "plain");
assert_eq!(rows[0].note, None);
let rows = Tagged::objects()
.filter(tagged::NOTE.coalesce("zzz").eq("beta"))
.on(&pool)
.fetch()
.await
.expect("filter COALESCE passthrough");
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].label, "alpha");
}
#[tokio::test]
async fn concat_appends_before_comparison() {
let pool = tagged_pool().await;
let rows = Tagged::objects()
.filter(tagged::LABEL.concat("!").eq("alpha!"))
.on(&pool)
.fetch()
.await
.expect("filter CONCAT");
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].label, "alpha");
}