use sqlx_template::{SqliteTemplate, sqlite_query};
use sqlx::{FromRow, SqlitePool};
#[derive(SqliteTemplate, FromRow, Debug, Clone)]
#[table("users")]
#[tp_select_one(by = "id", where = "name = :name and age > :min_age$i32")]
#[tp_select_all(where = "status = :status and score > :min_score$f64", fn_name = "find_by_status_and_score")]
#[tp_select_all(by = "active", where = "age BETWEEN :min_age$i32 AND :max_age$i32", fn_name = "find_active_by_age_range")]
#[tp_select_one(where = "email = :email and active = :active", fn_name = "find_by_email_and_status")]
#[tp_select_count(where = "score >= :threshold$f64 and status = :status", fn_name = "count_high_scorers")]
#[tp_update(by = "id", on = "name", where = "active = :active")]
#[tp_update(by = "email", on = "score, status", where = "updated_at < :cutoff$String", fn_name = "update_stale_users")]
#[tp_update(where = "age < :min_age$i32", on = "status", fn_name = "update_young_users_status")]
#[tp_delete(where = "name = :name and created_at < :cutoff_date$String", fn_name = "delete")]
#[tp_delete(by = "id", where = "active = :active and score < :min_score$f64", fn_name = "delete_inactive_low_scorers")]
#[tp_delete(where = "status = :status and updated_at < :stale_date$String", fn_name = "cleanup_stale_records")]
#[tp_upsert(by = "email", where = "updated_at > :min_update_time$String")]
#[tp_upsert(by = "id", where = "score < :max_score$f64", fn_name = "upsert_if_score_below")]
pub struct User {
#[auto]
pub id: i32,
pub name: String,
pub email: String,
pub age: i32,
pub status: String,
pub score: f64,
pub active: bool,
pub created_at: String,
pub updated_at: String,
}
#[sqlite_query(
r#"
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
age INTEGER NOT NULL,
status TEXT NOT NULL,
score REAL NOT NULL,
active BOOLEAN NOT NULL,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
)
"#
)]
async fn create_users_table() {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let pool = SqlitePool::connect(":memory:").await?;
create_users_table(&pool).await?;
let test_users = vec![
User {
id: 0, name: "Alice".to_string(),
email: "alice@example.com".to_string(),
age: 25,
status: "active".to_string(),
score: 85.5,
active: true,
created_at: "2023-01-01".to_string(),
updated_at: "2023-06-01".to_string(),
},
User {
id: 0, name: "Bob".to_string(),
email: "bob@example.com".to_string(),
age: 30,
status: "inactive".to_string(),
score: 92.0,
active: false,
created_at: "2023-02-01".to_string(),
updated_at: "2023-05-01".to_string(),
},
User {
id: 0, name: "Charlie".to_string(),
email: "charlie@example.com".to_string(),
age: 35,
status: "active".to_string(),
score: 78.0,
active: true,
created_at: "2023-03-01".to_string(),
updated_at: "2023-07-01".to_string(),
},
];
for user in test_users {
User::insert(&user, &pool).await?;
}
println!("=== Testing Placeholder Operations with SQLite ===\n");
println!("1. SELECT ONE with mixed placeholders:");
println!(" - :name mapped to name column (String type from struct)");
println!(" - :min_age$i32 custom type (not mapped to column)");
let user = User::find_one_by_id(&1, "Alice", &20, &pool).await?;
println!(" Found user: {:?}\n", user);
println!("2. SELECT ALL with custom placeholders:");
println!(" - :status mapped to status column");
println!(" - :min_score$f64 custom type");
let users = User::find_by_status_and_score("active", &80.0, &pool).await?;
println!(" Found {} active users with score > 80.0", users.len());
for user in &users {
println!(" - {}: {} (score: {})", user.name, user.status, user.score);
}
println!();
println!("3. SELECT with BY + WHERE combination:");
println!(" - by: active column");
println!(" - where: age BETWEEN :min_age$i32 AND :max_age$i32");
let users = User::find_active_by_age_range(&true, &20, &40, &pool).await?;
println!(" Found {} active users aged 20-40", users.len());
for user in &users {
println!(" - {}: age {} (active: {})", user.name, user.age, user.active);
}
println!();
println!("4. SELECT with email and status placeholders:");
println!(" - :email mapped to email column");
println!(" - :active mapped to active column");
let user = User::find_by_email_and_status("alice@example.com", &true, &pool).await?;
println!(" Found user: {:?}\n", user);
println!("5. COUNT with custom threshold:");
println!(" - :threshold$f64 custom type");
println!(" - :status mapped to status column");
let count = User::count_high_scorers(&85.0, "active", &pool).await?;
println!(" Found {} active users with score >= 85.0\n", count);
println!("6. UPDATE with BY + WHERE combination:");
println!(" - by: id column");
println!(" - on: name column");
println!(" - where: :active mapped to active column");
let rows_affected = User::update_by_id_on_name(&1, "Alice Updated", &true, &pool).await?;
println!(" Updated {} rows", rows_affected);
println!("7. UPDATE stale users:");
println!(" - by: email column");
println!(" - on: score, status columns");
println!(" - where: :cutoff$String custom type");
let rows_affected = User::update_stale_users("alice@example.com", &90.0, "premium", &"2023-01-01".to_string(), &pool).await?;
println!(" Updated {} stale users", rows_affected);
println!("8. UPDATE young users (WHERE only):");
println!(" - where: :min_age$i32 custom type");
println!(" - on: status column");
let rows_affected = User::update_young_users_status("junior", &30, &pool).await?;
println!(" Updated {} young users\n", rows_affected);
println!("9. DELETE with WHERE only:");
println!(" - :name mapped to name column");
println!(" - :cutoff_date$String custom type");
let rows_affected = User::delete("Bob", &"2023-06-01".to_string(), &pool).await?;
println!(" Deleted {} rows", rows_affected);
println!("10. DELETE with BY + WHERE combination:");
println!(" - by: id column");
println!(" - where: :active mapped to active column, :min_score$f64 custom type");
let rows_affected = User::delete_inactive_low_scorers(&2, &false, &70.0, &pool).await?;
println!(" Deleted {} inactive low scorers", rows_affected);
println!("11. DELETE stale records:");
println!(" - where: :status mapped to status column, :stale_date$String custom type");
let rows_affected = User::cleanup_stale_records("inactive", &"2023-01-01".to_string(), &pool).await?;
println!(" Cleaned up {} stale records\n", rows_affected);
println!("12. UPSERT with WHERE placeholder:");
println!(" - by: email column (conflict resolution)");
println!(" - where: :min_update_time$String custom type in WHERE clause");
let new_user = User {
id: 0, name: "David".to_string(),
email: "david@example.com".to_string(),
age: 28,
status: "active".to_string(),
score: 88.5,
active: true,
created_at: "2023-08-01".to_string(),
updated_at: "2023-08-01".to_string(),
};
let rows_affected = User::upsert_by_email(&new_user, &"2023-01-01".to_string(), &pool).await?;
println!(" Upserted {} rows", rows_affected);
println!("13. UPSERT with score condition:");
println!(" - by: id column (conflict resolution)");
println!(" - where: :max_score$f64 custom type");
let update_user = User {
id: 1,
name: "Alice Premium".to_string(),
email: "alice@example.com".to_string(),
age: 26,
status: "premium".to_string(),
score: 95.0,
active: true,
created_at: "2023-01-01".to_string(),
updated_at: "2023-08-15".to_string(),
};
let rows_affected = User::upsert_if_score_below(&update_user, &100.0, &pool).await?;
println!(" Upserted {} rows (score condition)\n", rows_affected);
println!("14. Final state of users table:");
let all_users = User::find_all(&pool).await?;
for user in all_users {
println!(" ID: {}, Name: {}, Email: {}, Age: {}, Status: {}, Score: {}, Active: {}",
user.id, user.name, user.email, user.age, user.status, user.score, user.active);
}
println!("\n=== All 13 placeholder operation tests completed successfully! ===");
println!("✅ SELECT: Column mapping, custom types, BY+WHERE, COUNT");
println!("✅ UPDATE: BY+WHERE, WHERE only, multiple columns");
println!("✅ DELETE: WHERE only, BY+WHERE combinations");
println!("✅ UPSERT: WHERE conditions with custom types");
Ok(())
}