#![allow(clippy::indexing_slicing)]
#![allow(clippy::redundant_closure_for_method_calls)]
use mik_sql::Value;
use mik_sql_macros::{sql_create, sql_delete, sql_read, sql_update};
use rusqlite::{Connection, Result, params_from_iter, types::ToSql};
fn value_to_sql(value: &Value) -> Box<dyn ToSql> {
match value {
Value::String(s) => Box::new(s.clone()),
Value::Int(i) => Box::new(*i),
Value::Float(f) => Box::new(*f),
Value::Bool(b) => Box::new(*b),
Value::Array(arr) => {
let json: Vec<String> = arr
.iter()
.map(|v| match v {
Value::String(s) => format!("\"{s}\""),
Value::Int(i) => i.to_string(),
Value::Float(f) => f.to_string(),
Value::Bool(b) => b.to_string(),
_ => "null".to_string(),
})
.collect();
Box::new(format!("[{}]", json.join(",")))
},
_ => Box::new(Option::<String>::None),
}
}
fn execute_with_values(conn: &Connection, sql: &str, params: &[Value]) -> Result<usize> {
let sql_params: Vec<Box<dyn ToSql>> = params.iter().map(value_to_sql).collect();
let param_refs: Vec<&dyn ToSql> = sql_params.iter().map(|b| b.as_ref()).collect();
conn.execute(sql, params_from_iter(param_refs))
}
fn query_with_values<T, F>(
conn: &Connection,
sql: &str,
params: &[Value],
row_mapper: F,
) -> Result<Vec<T>>
where
F: FnMut(&rusqlite::Row<'_>) -> Result<T>,
{
let sql_params: Vec<Box<dyn ToSql>> = params.iter().map(value_to_sql).collect();
let param_refs: Vec<&dyn ToSql> = sql_params.iter().map(|b| b.as_ref()).collect();
let mut stmt = conn.prepare(sql)?;
let rows = stmt.query_map(params_from_iter(param_refs), row_mapper)?;
rows.collect()
}
fn create_test_db() -> Result<Connection> {
let conn = Connection::open_in_memory()?;
conn.execute_batch(
"
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT,
age INTEGER,
active INTEGER DEFAULT 1,
role TEXT DEFAULT 'user',
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
deleted_at TEXT
);
CREATE TABLE orders (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
total REAL NOT NULL,
status TEXT DEFAULT 'pending',
created_at TEXT DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE products (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
price REAL NOT NULL,
quantity INTEGER DEFAULT 0,
category TEXT
);
CREATE TABLE logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
level TEXT NOT NULL,
message TEXT,
created_at TEXT DEFAULT CURRENT_TIMESTAMP
);
",
)?;
Ok(conn)
}
fn seed_test_data(conn: &Connection) -> Result<()> {
conn.execute_batch(
"
INSERT INTO users (name, email, age, active, role) VALUES
('Alice', 'alice@example.com', 30, 1, 'admin'),
('Bob', 'bob@example.com', 25, 1, 'user'),
('Charlie', 'charlie@example.com', 35, 0, 'user'),
('Diana', 'diana@example.com', 28, 1, 'moderator'),
('Eve', 'eve@example.com', 22, 1, 'user');
INSERT INTO orders (user_id, total, status) VALUES
(1, 100.50, 'completed'),
(1, 200.00, 'pending'),
(2, 50.25, 'completed'),
(3, 75.00, 'cancelled'),
(4, 150.00, 'pending');
INSERT INTO products (name, price, quantity, category) VALUES
('Widget', 9.99, 100, 'electronics'),
('Gadget', 19.99, 50, 'electronics'),
('Gizmo', 29.99, 25, 'electronics'),
('Book', 14.99, 200, 'books'),
('Pen', 1.99, 500, 'office');
INSERT INTO logs (level, message) VALUES
('info', 'Application started'),
('debug', 'Debug message'),
('error', 'Something went wrong'),
('info', 'User logged in'),
('debug', 'Another debug');
",
)?;
Ok(())
}
#[test]
fn test_sqlite_select_all() -> Result<()> {
let conn = create_test_db()?;
seed_test_data(&conn)?;
let (sql, params) = sql_read!(sqlite, users {});
let users: Vec<(i64, String)> =
query_with_values(&conn, &sql, ¶ms, |row| Ok((row.get(0)?, row.get(1)?)))?;
assert_eq!(users.len(), 5);
Ok(())
}
#[test]
fn test_sqlite_select_with_columns() -> Result<()> {
let conn = create_test_db()?;
seed_test_data(&conn)?;
let (sql, params) = sql_read!(
sqlite,
users {
select: [id, name, email],
}
);
let users: Vec<(i64, String, String)> = query_with_values(&conn, &sql, ¶ms, |row| {
Ok((row.get(0)?, row.get(1)?, row.get(2)?))
})?;
assert_eq!(users.len(), 5);
assert_eq!(users[0].1, "Alice");
Ok(())
}
#[test]
fn test_sqlite_select_with_filter_bool() -> Result<()> {
let conn = create_test_db()?;
seed_test_data(&conn)?;
let (sql, params) = sql_read!(sqlite, users {
select: [id, name],
filter: { active: true },
});
let users: Vec<(i64, String)> =
query_with_values(&conn, &sql, ¶ms, |row| Ok((row.get(0)?, row.get(1)?)))?;
assert_eq!(users.len(), 4); Ok(())
}
#[test]
fn test_sqlite_select_with_filter_string() -> Result<()> {
let conn = create_test_db()?;
seed_test_data(&conn)?;
let (sql, params) = sql_read!(sqlite, users {
select: [id, name],
filter: { role: "admin" },
});
let users: Vec<(i64, String)> =
query_with_values(&conn, &sql, ¶ms, |row| Ok((row.get(0)?, row.get(1)?)))?;
assert_eq!(users.len(), 1);
assert_eq!(users[0].1, "Alice");
Ok(())
}
#[test]
fn test_sqlite_select_with_filter_gte() -> Result<()> {
let conn = create_test_db()?;
seed_test_data(&conn)?;
let (sql, params) = sql_read!(sqlite, users {
select: [id, name, age],
filter: { age: { $gte: 30 } },
});
let users: Vec<(i64, String, i64)> = query_with_values(&conn, &sql, ¶ms, |row| {
Ok((row.get(0)?, row.get(1)?, row.get(2)?))
})?;
assert_eq!(users.len(), 2); Ok(())
}
#[test]
fn test_sqlite_select_with_filter_lt() -> Result<()> {
let conn = create_test_db()?;
seed_test_data(&conn)?;
let (sql, params) = sql_read!(sqlite, users {
select: [id, name, age],
filter: { age: { $lt: 28 } },
});
let users: Vec<(i64, String, i64)> = query_with_values(&conn, &sql, ¶ms, |row| {
Ok((row.get(0)?, row.get(1)?, row.get(2)?))
})?;
assert_eq!(users.len(), 2); Ok(())
}
#[test]
fn test_sqlite_select_with_filter_ne() -> Result<()> {
let conn = create_test_db()?;
seed_test_data(&conn)?;
let (sql, params) = sql_read!(sqlite, users {
select: [id, name],
filter: { role: { $ne: "user" } },
});
let users: Vec<(i64, String)> =
query_with_values(&conn, &sql, ¶ms, |row| Ok((row.get(0)?, row.get(1)?)))?;
assert_eq!(users.len(), 2); Ok(())
}
#[test]
fn test_sqlite_select_with_multiple_filters() -> Result<()> {
let conn = create_test_db()?;
seed_test_data(&conn)?;
let (sql, params) = sql_read!(sqlite, users {
select: [id, name],
filter: {
active: true,
role: "user",
},
});
let users: Vec<(i64, String)> =
query_with_values(&conn, &sql, ¶ms, |row| Ok((row.get(0)?, row.get(1)?)))?;
assert_eq!(users.len(), 2); Ok(())
}
#[test]
fn test_sqlite_select_with_like() -> Result<()> {
let conn = create_test_db()?;
seed_test_data(&conn)?;
let (sql, params) = sql_read!(sqlite, users {
select: [id, name],
filter: { email: { $like: "%example.com" } },
});
let users: Vec<(i64, String)> =
query_with_values(&conn, &sql, ¶ms, |row| Ok((row.get(0)?, row.get(1)?)))?;
assert_eq!(users.len(), 5); Ok(())
}
#[test]
fn test_sqlite_select_with_order() -> Result<()> {
let conn = create_test_db()?;
seed_test_data(&conn)?;
let (sql, params) = sql_read!(
sqlite,
users {
select: [id, name],
order: name,
}
);
let users: Vec<(i64, String)> =
query_with_values(&conn, &sql, ¶ms, |row| Ok((row.get(0)?, row.get(1)?)))?;
assert_eq!(users[0].1, "Alice");
assert_eq!(users[1].1, "Bob");
assert_eq!(users[2].1, "Charlie");
Ok(())
}
#[test]
fn test_sqlite_select_with_order_desc() -> Result<()> {
let conn = create_test_db()?;
seed_test_data(&conn)?;
let (sql, params) = sql_read!(
sqlite,
users {
select: [id, name],
order: -name,
}
);
let users: Vec<(i64, String)> =
query_with_values(&conn, &sql, ¶ms, |row| Ok((row.get(0)?, row.get(1)?)))?;
assert_eq!(users[0].1, "Eve");
assert_eq!(users[1].1, "Diana");
Ok(())
}
#[test]
fn test_sqlite_select_with_limit() -> Result<()> {
let conn = create_test_db()?;
seed_test_data(&conn)?;
let (sql, params) = sql_read!(
sqlite,
users {
select: [id, name],
limit: 2,
}
);
let users: Vec<(i64, String)> =
query_with_values(&conn, &sql, ¶ms, |row| Ok((row.get(0)?, row.get(1)?)))?;
assert_eq!(users.len(), 2);
Ok(())
}
#[test]
fn test_sqlite_select_with_limit_offset() -> Result<()> {
let conn = create_test_db()?;
seed_test_data(&conn)?;
let (sql, params) = sql_read!(
sqlite,
users {
select: [id, name],
order: id,
limit: 2,
offset: 2,
}
);
let users: Vec<(i64, String)> =
query_with_values(&conn, &sql, ¶ms, |row| Ok((row.get(0)?, row.get(1)?)))?;
assert_eq!(users.len(), 2);
assert_eq!(users[0].1, "Charlie"); assert_eq!(users[1].1, "Diana"); Ok(())
}
#[test]
fn test_sqlite_select_with_or() -> Result<()> {
let conn = create_test_db()?;
seed_test_data(&conn)?;
let (sql, params) = sql_read!(sqlite, users {
select: [id, name],
filter: {
$or: [
{ role: "admin" },
{ role: "moderator" },
]
},
});
let users: Vec<(i64, String)> =
query_with_values(&conn, &sql, ¶ms, |row| Ok((row.get(0)?, row.get(1)?)))?;
assert_eq!(users.len(), 2); Ok(())
}
#[test]
fn test_sqlite_select_with_and() -> Result<()> {
let conn = create_test_db()?;
seed_test_data(&conn)?;
let (sql, params) = sql_read!(sqlite, users {
select: [id, name],
filter: {
$and: [
{ active: true },
{ age: { $gte: 25 } },
]
},
});
let users: Vec<(i64, String)> =
query_with_values(&conn, &sql, ¶ms, |row| Ok((row.get(0)?, row.get(1)?)))?;
assert_eq!(users.len(), 3); Ok(())
}
#[test]
fn test_sqlite_select_with_between() -> Result<()> {
let conn = create_test_db()?;
seed_test_data(&conn)?;
let (sql, params) = sql_read!(sqlite, users {
select: [id, name, age],
filter: { age: { $between: [25, 30] } },
});
let users: Vec<(i64, String, i64)> = query_with_values(&conn, &sql, ¶ms, |row| {
Ok((row.get(0)?, row.get(1)?, row.get(2)?))
})?;
assert_eq!(users.len(), 3); Ok(())
}
#[test]
fn test_sqlite_count_star() -> Result<()> {
let conn = create_test_db()?;
seed_test_data(&conn)?;
let (sql, params) = sql_read!(sqlite, users {
aggregate: { count: * },
});
let count: i64 = query_with_values(&conn, &sql, ¶ms, |row| row.get(0))?[0];
assert_eq!(count, 5);
Ok(())
}
#[test]
fn test_sqlite_count_with_filter() -> Result<()> {
let conn = create_test_db()?;
seed_test_data(&conn)?;
let (sql, params) = sql_read!(sqlite, users {
aggregate: { count: * },
filter: { active: true },
});
let count: i64 = query_with_values(&conn, &sql, ¶ms, |row| row.get(0))?[0];
assert_eq!(count, 4);
Ok(())
}
#[test]
fn test_sqlite_sum() -> Result<()> {
let conn = create_test_db()?;
seed_test_data(&conn)?;
let (sql, params) = sql_read!(sqlite, orders {
aggregate: { sum: total },
});
let sum: f64 = query_with_values(&conn, &sql, ¶ms, |row| row.get(0))?[0];
assert!((sum - 575.75).abs() < 0.01); Ok(())
}
#[test]
fn test_sqlite_avg() -> Result<()> {
let conn = create_test_db()?;
seed_test_data(&conn)?;
let (sql, params) = sql_read!(sqlite, users {
aggregate: { avg: age },
});
let avg: f64 = query_with_values(&conn, &sql, ¶ms, |row| row.get(0))?[0];
assert!((avg - 28.0).abs() < 0.01); Ok(())
}
#[test]
fn test_sqlite_min_max() -> Result<()> {
let conn = create_test_db()?;
seed_test_data(&conn)?;
let (sql, params) = sql_read!(sqlite, products {
aggregate: { min: price, max: price },
});
let result: (f64, f64) =
query_with_values(&conn, &sql, ¶ms, |row| Ok((row.get(0)?, row.get(1)?)))?[0];
assert!((result.0 - 1.99).abs() < 0.01); assert!((result.1 - 29.99).abs() < 0.01); Ok(())
}
#[test]
fn test_sqlite_group_by() -> Result<()> {
let conn = create_test_db()?;
seed_test_data(&conn)?;
let (sql, params) = sql_read!(sqlite, orders {
select: [status],
aggregate: { count: * },
group_by: [status],
order: status,
});
let results: Vec<(String, i64)> =
query_with_values(&conn, &sql, ¶ms, |row| Ok((row.get(0)?, row.get(1)?)))?;
assert_eq!(results.len(), 3); Ok(())
}
#[test]
fn test_sqlite_insert_basic() -> Result<()> {
let conn = create_test_db()?;
let (sql, params) = sql_create!(
sqlite,
users {
name: "Frank",
email: "frank@example.com",
age: 40,
active: true,
}
);
let rows_affected = execute_with_values(&conn, &sql, ¶ms)?;
assert_eq!(rows_affected, 1);
let count: i64 =
conn.query_row("SELECT COUNT(*) FROM users WHERE name = 'Frank'", [], |r| {
r.get(0)
})?;
assert_eq!(count, 1);
Ok(())
}
#[test]
fn test_sqlite_insert_with_variables() -> Result<()> {
let conn = create_test_db()?;
let name = "Grace";
let email = "grace@example.com";
let age: i64 = 33;
let (sql, params) = sql_create!(
sqlite,
users {
name: str(name),
email: str(email),
age: int(age),
}
);
execute_with_values(&conn, &sql, ¶ms)?;
let result: (String, i64) = conn.query_row(
"SELECT name, age FROM users WHERE email = 'grace@example.com'",
[],
|r| Ok((r.get(0)?, r.get(1)?)),
)?;
assert_eq!(result.0, "Grace");
assert_eq!(result.1, 33);
Ok(())
}
#[test]
fn test_sqlite_update_basic() -> Result<()> {
let conn = create_test_db()?;
seed_test_data(&conn)?;
let (sql, params) = sql_update!(sqlite, users {
set: { role: "superadmin" },
filter: { name: "Alice" },
});
let rows_affected = execute_with_values(&conn, &sql, ¶ms)?;
assert_eq!(rows_affected, 1);
let role: String = conn.query_row("SELECT role FROM users WHERE name = 'Alice'", [], |r| {
r.get(0)
})?;
assert_eq!(role, "superadmin");
Ok(())
}
#[test]
fn test_sqlite_update_multiple_rows() -> Result<()> {
let conn = create_test_db()?;
seed_test_data(&conn)?;
let (sql, params) = sql_update!(sqlite, users {
set: { active: false },
filter: { role: "user" },
});
let rows_affected = execute_with_values(&conn, &sql, ¶ms)?;
assert_eq!(rows_affected, 3);
let active_users: i64 =
conn.query_row("SELECT COUNT(*) FROM users WHERE active = 1", [], |r| {
r.get(0)
})?;
assert_eq!(active_users, 2);
Ok(())
}
#[test]
fn test_sqlite_update_with_operators() -> Result<()> {
let conn = create_test_db()?;
seed_test_data(&conn)?;
let (sql, params) = sql_update!(sqlite, users {
set: { role: "senior" },
filter: { age: { $gte: 30 } },
});
let rows_affected = execute_with_values(&conn, &sql, ¶ms)?;
assert_eq!(rows_affected, 2);
Ok(())
}
#[test]
fn test_sqlite_delete_basic() -> Result<()> {
let conn = create_test_db()?;
seed_test_data(&conn)?;
let (sql, params) = sql_delete!(sqlite, users {
filter: { name: "Charlie" },
});
let rows_affected = execute_with_values(&conn, &sql, ¶ms)?;
assert_eq!(rows_affected, 1);
let count: i64 = conn.query_row("SELECT COUNT(*) FROM users", [], |r| r.get(0))?;
assert_eq!(count, 4);
Ok(())
}
#[test]
fn test_sqlite_delete_multiple() -> Result<()> {
let conn = create_test_db()?;
seed_test_data(&conn)?;
let (sql, params) = sql_delete!(sqlite, logs {
filter: { level: "debug" },
});
let rows_affected = execute_with_values(&conn, &sql, ¶ms)?;
assert_eq!(rows_affected, 2);
Ok(())
}
#[test]
fn test_sqlite_delete_with_or() -> Result<()> {
let conn = create_test_db()?;
seed_test_data(&conn)?;
let (sql, params) = sql_delete!(sqlite, orders {
filter: {
$or: [
{ status: "cancelled" },
{ status: "pending" },
]
},
});
let rows_affected = execute_with_values(&conn, &sql, ¶ms)?;
assert_eq!(rows_affected, 3);
Ok(())
}
#[test]
fn test_sqlite_empty_result() -> Result<()> {
let conn = create_test_db()?;
seed_test_data(&conn)?;
let (sql, params) = sql_read!(sqlite, users {
select: [id, name],
filter: { name: "NonExistent" },
});
let users: Vec<(i64, String)> =
query_with_values(&conn, &sql, ¶ms, |row| Ok((row.get(0)?, row.get(1)?)))?;
assert!(users.is_empty());
Ok(())
}
#[test]
fn test_sqlite_null_filter() -> Result<()> {
let conn = create_test_db()?;
seed_test_data(&conn)?;
let (sql, params) = sql_read!(sqlite, users {
select: [id, name],
filter: { deleted_at: null },
});
let users: Vec<(i64, String)> =
query_with_values(&conn, &sql, ¶ms, |row| Ok((row.get(0)?, row.get(1)?)))?;
assert_eq!(users.len(), 5); Ok(())
}
#[test]
fn test_sqlite_type_coercion() -> Result<()> {
let conn = create_test_db()?;
seed_test_data(&conn)?;
let age_filter: i64 = 25;
let (sql, params) = sql_read!(sqlite, users {
select: [id, name],
filter: { age: int(age_filter) },
});
let users: Vec<(i64, String)> =
query_with_values(&conn, &sql, ¶ms, |row| Ok((row.get(0)?, row.get(1)?)))?;
assert_eq!(users.len(), 1);
assert_eq!(users[0].1, "Bob");
Ok(())
}
#[test]
fn test_sqlite_float_values() -> Result<()> {
let conn = create_test_db()?;
seed_test_data(&conn)?;
let (sql, params) = sql_read!(sqlite, products {
select: [id, name, price],
filter: { price: { $lt: 10.0 } },
});
let products: Vec<(i64, String, f64)> = query_with_values(&conn, &sql, ¶ms, |row| {
Ok((row.get(0)?, row.get(1)?, row.get(2)?))
})?;
assert_eq!(products.len(), 2); Ok(())
}
#[test]
fn test_sqlite_complex_query() -> Result<()> {
let conn = create_test_db()?;
seed_test_data(&conn)?;
let (sql, params) = sql_read!(sqlite, users {
select: [id, name, age],
filter: {
$and: [
{ active: true },
{ $or: [
{ role: "admin" },
{ age: { $gte: 28 } },
]},
]
},
order: -age,
limit: 3,
});
let users: Vec<(i64, String, i64)> = query_with_values(&conn, &sql, ¶ms, |row| {
Ok((row.get(0)?, row.get(1)?, row.get(2)?))
})?;
assert_eq!(users.len(), 2);
assert_eq!(users[0].1, "Alice"); Ok(())
}
#[test]
fn test_sqlite_special_characters_in_string() -> Result<()> {
let conn = create_test_db()?;
conn.execute(
"INSERT INTO users (name, email, age, active) VALUES (?, ?, ?, ?)",
["O'Brien", "obrien@example.com", "45", "1"],
)?;
let (sql, params) = sql_read!(sqlite, users {
select: [id, name],
filter: { name: "O'Brien" },
});
let users: Vec<(i64, String)> =
query_with_values(&conn, &sql, ¶ms, |row| Ok((row.get(0)?, row.get(1)?)))?;
assert_eq!(users.len(), 1);
assert_eq!(users[0].1, "O'Brien");
Ok(())
}