use bottle_orm::{Database, Model, Op, FromAnyRow};
#[derive(Debug, Clone, Model, PartialEq)]
struct Product {
#[orm(primary_key)]
id: i32,
name: String,
category: String,
price: f64,
stock: i32,
}
#[derive(Debug, Clone, FromAnyRow, PartialEq)]
struct CategoryDTO {
category: String,
}
#[derive(Debug, Clone, FromAnyRow, PartialEq)]
struct CategorySummary {
category: String,
total_stock: i64,
}
#[tokio::test]
async fn test_query_builder_extended_features() -> Result<(), Box<dyn std::error::Error>> {
let db = Database::builder().max_connections(1).connect("sqlite::memory:").await?;
db.migrator().register::<Product>().run().await?;
let products = vec![
Product { id: 1, name: "Laptop".to_string(), category: "Electronics".to_string(), price: 1200.0, stock: 10 },
Product { id: 2, name: "Smartphone".to_string(), category: "Electronics".to_string(), price: 800.0, stock: 20 },
Product { id: 3, name: "Coffee Maker".to_string(), category: "Appliances".to_string(), price: 150.0, stock: 5 },
Product { id: 4, name: "Toaster".to_string(), category: "Appliances".to_string(), price: 50.0, stock: 15 },
Product { id: 5, name: "Desk Lamp".to_string(), category: "Home".to_string(), price: 30.0, stock: 50 },
];
for p in &products {
db.model::<Product>().insert(p).await?;
}
let categories: Vec<CategoryDTO> = db.model::<Product>()
.debug()
.select("category")
.distinct()
.scan_as()
.await?;
assert_eq!(categories.len(), 3);
assert!(categories.iter().any(|c| c.category == "Electronics"));
assert!(categories.iter().any(|c| c.category == "Appliances"));
assert!(categories.iter().any(|c| c.category == "Home"));
let q1 = db.model::<Product>().debug().filter("price", Op::Gt, 1000.0);
let q2 = db.model::<Product>().debug().filter("category", Op::Eq, "Home".to_string());
let union_results: Vec<Product> = q1.union(q2).scan().await?;
assert_eq!(union_results.len(), 2);
assert!(union_results.iter().any(|p| p.name == "Laptop"));
assert!(union_results.iter().any(|p| p.name == "Desk Lamp"));
let subquery = db.model::<Product>().debug().select("category").filter("price", Op::Lt, 100.0);
let subquery_results: Vec<Product> = db.model::<Product>()
.debug()
.filter_subquery("category", Op::In, subquery)
.scan()
.await?;
assert_eq!(subquery_results.len(), 3);
assert!(subquery_results.iter().any(|p| p.name == "Coffee Maker"));
assert!(subquery_results.iter().any(|p| p.name == "Toaster"));
assert!(subquery_results.iter().any(|p| p.name == "Desk Lamp"));
let summary: Vec<CategorySummary> = db.model::<Product>()
.debug()
.select("category, SUM(stock) as total_stock")
.group_by("category")
.having("SUM(stock)", Op::Gt, 15)
.scan_as()
.await?;
assert_eq!(summary.len(), 3);
let summary_high: Vec<CategorySummary> = db.model::<Product>()
.select("category, SUM(stock) as total_stock")
.group_by("category")
.having("SUM(stock)", Op::Gt, 25)
.scan_as()
.await?;
assert_eq!(summary_high.len(), 2);
let total_count = db.model::<Product>().count().await?;
assert_eq!(total_count, 5);
let max_price: f64 = db.model::<Product>().max("price").await?;
assert_eq!(max_price, 1200.0);
let min_price: f64 = db.model::<Product>().min("price").await?;
assert_eq!(min_price, 30.0);
let avg_price: f64 = db.model::<Product>().avg("price").await?;
assert_eq!(avg_price, (1200.0 + 800.0 + 150.0 + 50.0 + 30.0) / 5.0);
let total_stock: i32 = db.model::<Product>().sum("stock").await?;
assert_eq!(total_stock, 10 + 20 + 5 + 15 + 50);
db.model::<Product>().truncate().await?;
let count_after_truncate = db.model::<Product>().count().await?;
assert_eq!(count_after_truncate, 0);
println!("Extended QueryBuilder features test passed!");
Ok(())
}