mod support;
#[cfg(test)]
mod tests {
use std::{f32::consts::PI, f64::consts::E};
use musq::{
query::Map,
types::time::{Date, OffsetDateTime, PrimitiveDateTime, Time},
*,
};
use time::macros::{date, datetime, time};
use crate::support::connection;
#[derive(FromRow, Debug, PartialEq)]
struct User {
id: i32,
name: String,
status: String,
}
macro_rules! bind_check {
($name:ident: $ty:ty = $value:expr) => {
#[tokio::test]
async fn $name() -> anyhow::Result<()> {
let conn = connection().await?;
let val: $ty = $value;
let row = sql!("SELECT {}", val.clone())?.fetch_one(&conn).await?;
let out: $ty = row.get_value_idx(0)?;
assert_eq!(out, val);
let row = sql!("SELECT {v}", v = val.clone())?
.fetch_one(&conn)
.await?;
let out: $ty = row.get_value_idx(0)?;
assert_eq!(out, val);
let list = vec![val.clone(), val.clone()];
let row = sql!("SELECT {values:list}")?.fetch_one(&conn).await?;
let out0: $ty = row.get_value_idx(0)?;
let out1: $ty = row.get_value_idx(1)?;
assert_eq!(out0, val);
assert_eq!(out1, val);
Ok(())
}
};
}
#[tokio::test]
async fn test_placeholders() -> anyhow::Result<()> {
let conn = connection().await?;
sql!("CREATE TABLE users (id INTEGER, name TEXT, status TEXT)")?
.execute(&conn)
.await?;
let id = 1;
let name = "Alice";
let insert = sql!(
"INSERT INTO users (id, name, status) VALUES ({}, {}, 'active')",
id,
name
)?;
println!("insert sql: {}", insert.sql());
insert.execute(&conn).await?;
let row = sql!("SELECT name FROM users WHERE id = {id}")?
.fetch_one(&conn)
.await?;
assert_eq!(row.get_value_idx::<String>(0)?, "Alice");
Ok(())
}
#[tokio::test]
async fn test_ident_and_lists() -> anyhow::Result<()> {
let conn = connection().await?;
let table = "user-data";
sql!("CREATE TABLE {ident:table} (id INTEGER, name TEXT)")?
.execute(&conn)
.await?;
sql!("INSERT INTO {ident:table} (id, name) VALUES (1, 'a'), (2, 'b')")?
.execute(&conn)
.await?;
let ids = [1, 2];
let cols = ["id", "name"];
let rows = sql!("SELECT {idents:cols} FROM {ident:table} WHERE id IN ({values:ids})")?
.fetch_all(&conn)
.await?;
assert_eq!(rows.len(), 2);
Ok(())
}
#[tokio::test]
async fn test_raw_and_taint() -> anyhow::Result<()> {
let conn = connection().await?;
sql!("CREATE TABLE t (id INTEGER)")?.execute(&conn).await?;
sql!("INSERT INTO t (id) VALUES (1), (2)")?
.execute(&conn)
.await?;
let q = sql!("SELECT * FROM t {raw:\"ORDER BY id DESC\"}")?;
assert!(q.is_tainted());
let rows = q.fetch_all(&conn).await?;
assert_eq!(rows.len(), 2);
Ok(())
}
#[tokio::test]
async fn test_sql_as_and_builder() -> anyhow::Result<()> {
let conn = connection().await?;
sql!("CREATE TABLE users (id INTEGER, name TEXT, status TEXT)")?
.execute(&conn)
.await?;
sql!("INSERT INTO users (id, name, status) VALUES (1, 'Alice', 'active')")?
.execute(&conn)
.await?;
let user: User = sql_as!("SELECT id, name, status FROM users WHERE id = {id}", id = 1)?
.fetch_one(&conn)
.await?;
assert_eq!(
user,
User {
id: 1,
name: "Alice".into(),
status: "active".into()
}
);
let tuple: (i32, String) = sql_as!("SELECT id, name FROM users WHERE id = {id}", id = 1)?
.fetch_one(&conn)
.await?;
assert_eq!(tuple, (1, "Alice".into()));
let base = sql!(
"SELECT id FROM users WHERE status = {status}",
status = "active"
)?;
let final_q = {
let mut b = base.into_builder();
b.push_sql(" ORDER BY id");
b.build()
};
let ids: Vec<i32> = final_q
.try_map(|row| row.get_value_idx::<i32>(0))
.fetch_all(&conn)
.await?;
assert_eq!(ids, vec![1]);
Ok(())
}
bind_check!(bind_bool: bool = true);
bind_check!(bind_i8: i8 = -5);
bind_check!(bind_i16: i16 = -1234);
bind_check!(bind_i32: i32 = 42);
bind_check!(bind_i64: i64 = 9001);
bind_check!(bind_u8: u8 = 5);
bind_check!(bind_u16: u16 = 55);
bind_check!(bind_u32: u32 = 999);
bind_check!(bind_f32: f32 = PI);
bind_check!(bind_f64: f64 = E);
bind_check!(bind_string: String = "hello".to_string());
bind_check!(bind_bytes: Vec<u8> = vec![1u8, 2, 3]);
bind_check!(bind_offset_datetime: OffsetDateTime = datetime!(2025 - 7 - 22 6:20:47 UTC));
bind_check!(bind_primitive_datetime: PrimitiveDateTime = datetime!(2025 - 1 - 15 12:30:45));
bind_check!(bind_date: Date = date!(2025 - 1 - 1));
bind_check!(bind_time: Time = time!(23:59:59));
#[tokio::test]
async fn test_sql_non_result_context() -> anyhow::Result<()> {
let conn = connection().await?;
let query = sql!("SELECT 1 as value").unwrap();
let row = query.fetch_one(&conn).await?;
let value: i32 = row.get_value_idx(0)?;
assert_eq!(value, 1);
let test_value = 42;
let query = sql!("SELECT {} as value", test_value).unwrap();
let row = query.fetch_one(&conn).await?;
let value: i32 = row.get_value_idx(0)?;
assert_eq!(value, 42);
let query = sql!("SELECT {val} as value", val = 123).unwrap();
let row = query.fetch_one(&conn).await?;
let value: i32 = row.get_value_idx(0)?;
assert_eq!(value, 123);
Ok(())
}
#[tokio::test]
async fn test_sql_as_non_result_context() -> anyhow::Result<()> {
let conn = connection().await?;
sql!("CREATE TABLE test_table (id INTEGER, name TEXT)")
.unwrap()
.execute(&conn)
.await?;
sql!("INSERT INTO test_table (id, name) VALUES (1, 'test')")
.unwrap()
.execute(&conn)
.await?;
let query = sql_as!("SELECT id, name FROM test_table WHERE id = {}", 1).unwrap();
let row: (i32, String) = query.fetch_one(&conn).await?;
assert_eq!(row, (1, "test".to_string()));
let query = sql_as!(
"SELECT id, name FROM test_table WHERE id = {test_id}",
test_id = 1
)
.unwrap();
let row: (i32, String) = query.fetch_one(&conn).await?;
assert_eq!(row, (1, "test".to_string()));
Ok(())
}
#[tokio::test]
async fn test_sql_result_methods() -> anyhow::Result<()> {
let conn = connection().await?;
let query = sql!("SELECT 'default' as value")
.unwrap_or_else(|_| panic!("Should not error for valid SQL"));
let row = query.fetch_one(&conn).await?;
let value: String = row.get_value_idx(0)?;
assert_eq!(value, "default");
Ok(())
}
fn create_simple_query() {
let _query = sql!("SELECT 42 as answer").unwrap();
}
fn create_parameterized_query() {
let value = 42;
let _query = sql!("SELECT {} as value", value).unwrap();
}
fn create_named_query() {
let name = "test";
let _query = sql!("SELECT {name} as greeting", name = name).unwrap();
}
fn create_sql_as_simple() -> Map<impl FnMut(Row) -> Result<(i32, String)>> {
sql_as!("SELECT 1 as id, 'hello' as name").unwrap()
}
fn create_sql_as_with_params() -> Map<impl FnMut(Row) -> Result<(i32, String)>> {
let id = 42;
let name = "world";
sql_as!("SELECT {id} as id, {name} as name", id = id, name = name).unwrap()
}
#[derive(musq::FromRow, Debug, PartialEq)]
struct TestRecord {
id: i32,
message: String,
}
fn create_sql_as_with_struct() -> Map<impl FnMut(Row) -> Result<TestRecord>> {
sql_as!("SELECT 999 as id, 'struct test' as message").unwrap()
}
fn create_sql_as_with_complex_params() -> Map<impl FnMut(Row) -> Result<(i32, String, bool)>> {
let user_id = 123;
let status = "active";
let is_admin = true;
sql_as!(
"SELECT {user_id} as id, {status} as status, {is_admin} as is_admin",
user_id = user_id,
status = status,
is_admin = is_admin
)
.unwrap()
}
#[tokio::test]
async fn test_sql_macros_compilation_in_non_result_context() -> anyhow::Result<()> {
create_simple_query();
create_parameterized_query();
create_named_query();
let query1 = create_sql_as_simple();
let query2 = create_sql_as_with_params();
let query3 = create_sql_as_with_struct();
let query4 = create_sql_as_with_complex_params();
let conn = connection().await?;
let query = sql!("SELECT 123 as test_value").unwrap();
let row = query.fetch_one(&conn).await?;
let value: i32 = row.get_value_idx(0)?;
assert_eq!(value, 123);
let rows: Vec<(i32, String)> = query1.fetch_all(&conn).await?;
assert_eq!(rows.len(), 1);
assert_eq!(rows[0], (1, "hello".to_string()));
let rows: Vec<(i32, String)> = query2.fetch_all(&conn).await?;
assert_eq!(rows.len(), 1);
assert_eq!(rows[0], (42, "world".to_string()));
let rows: Vec<TestRecord> = query3.fetch_all(&conn).await?;
assert_eq!(rows.len(), 1);
assert_eq!(
rows[0],
TestRecord {
id: 999,
message: "struct test".to_string()
}
);
let rows: Vec<(i32, String, bool)> = query4.fetch_all(&conn).await?;
assert_eq!(rows.len(), 1);
assert_eq!(rows[0], (123, "active".to_string(), true));
let rows: Vec<(i32, String)> = sql_as!("SELECT 1 as id, 'direct' as name")
.unwrap()
.fetch_all(&conn)
.await?;
assert_eq!(rows.len(), 1);
assert_eq!(rows[0], (1, "direct".to_string()));
Ok(())
}
}