use sqlx::{
Arguments, ConnectOptions, Decode, Encode, Executor, Row, Statement, Type, Value, ValueRef,
};
use sqlx_core::{encode::IsNull, error::BoxDynError};
use sqlx_turso_driver::{Turso, TursoArguments, TursoConnectOptions, TursoTypeInfo, TursoValueRef};
type TestResult = Result<(), BoxDynError>;
#[tokio::test]
async fn positional_binding_reaches_engine() -> TestResult {
let mut connection = TursoConnectOptions::memory().connect().await?;
assert_eq!(
sqlx::query_scalar::<Turso, i64>("SELECT ?1 + ?1")
.bind(7_i64)
.fetch_one(&mut connection)
.await?,
14
);
assert_eq!(
sqlx::query_scalar::<Turso, i64>("SELECT ? + ?")
.bind(7_i32)
.bind(8_u8)
.fetch_one(&mut connection)
.await?,
15
);
Ok(())
}
#[tokio::test]
async fn null_and_empty_are_distinct() -> TestResult {
let mut connection = TursoConnectOptions::memory().connect().await?;
connection
.execute("CREATE TABLE items(t TEXT, b BLOB, n INTEGER)")
.await?;
for (text, blob, number) in [
(None, None, None),
(Some(""), Some(&b""[..]), Some(i64::MIN)),
(
Some("中文 '); DROP TABLE items; --"),
Some(&b"\0\xff\0abc"[..]),
Some(i64::MAX),
),
] {
sqlx::query::<Turso>("INSERT INTO items VALUES (?, ?, ?)")
.bind(text)
.bind(blob)
.bind(number)
.execute(&mut connection)
.await?;
}
let rows = sqlx::query::<Turso>("SELECT t, b, n FROM items ORDER BY rowid")
.fetch_all(&mut connection)
.await?;
assert_eq!(rows.len(), 3);
assert_eq!(rows[0].try_get::<Option<String>, _>(0)?, None);
assert_eq!(rows[0].try_get::<Option<Vec<u8>>, _>(1)?, None);
assert_eq!(rows[0].try_get::<Option<i64>, _>(2)?, None);
assert!(rows[0].try_get_raw(0)?.is_null());
assert!(rows[0].try_get::<String, _>(0).is_err());
assert_eq!(
rows[1].try_get::<Option<String>, _>(0)?,
Some(String::new())
);
assert_eq!(rows[1].try_get::<Option<Vec<u8>>, _>(1)?, Some(vec![]));
assert_eq!(rows[1].try_get::<i64, _>(2)?, i64::MIN);
assert!(!rows[1].try_get_raw(0)?.is_null());
assert!(!rows[1].try_get_raw(1)?.is_null());
assert_eq!(
rows[2].try_get::<&str, _>(0)?,
"中文 '); DROP TABLE items; --"
);
assert_eq!(rows[2].try_get::<&[u8], _>(1)?, b"\0\xff\0abc");
assert_eq!(rows[2].try_get::<i64, _>(2)?, i64::MAX);
let owned = ValueRef::to_owned(&rows[2].try_get_raw(0)?);
drop(rows);
assert_eq!(
<&str as Decode<Turso>>::decode(owned.as_ref())?,
"中文 '); DROP TABLE items; --"
);
Ok(())
}
#[tokio::test]
async fn narrow_integer_overflow_errors() -> TestResult {
let mut connection = TursoConnectOptions::memory().connect().await?;
for value in [256_i64, -1] {
let row = sqlx::query::<Turso>("SELECT ?")
.bind(value)
.fetch_one(&mut connection)
.await?;
assert!(matches!(
row.try_get::<u8, _>(0),
Err(sqlx::Error::ColumnDecode { .. })
));
}
let row = sqlx::query::<Turso>("SELECT 128, 32768, 2147483648, -1")
.fetch_one(&mut connection)
.await?;
assert!(row.try_get::<i8, _>(0).is_err());
assert!(row.try_get::<i16, _>(1).is_err());
assert!(row.try_get::<i32, _>(2).is_err());
assert!(row.try_get::<u64, _>(3).is_err());
assert!(matches!(
sqlx::query::<Turso>("SELECT ?")
.bind(u64::MAX)
.fetch_one(&mut connection)
.await,
Err(sqlx::Error::Encode(_))
));
let row = sqlx::query::<Turso>("SELECT ?, ?, ?, ?, ?, ?, ?, ?")
.bind(i8::MIN)
.bind(i16::MIN)
.bind(i32::MIN)
.bind(i64::MIN)
.bind(u8::MAX)
.bind(u16::MAX)
.bind(u32::MAX)
.bind(i64::MAX as u64)
.fetch_one(&mut connection)
.await?;
assert_eq!(row.try_get::<i8, _>(0)?, -128);
assert_eq!(row.try_get::<i16, _>(1)?, -32768);
assert_eq!(row.try_get::<i32, _>(2)?, -2147483648);
assert_eq!(row.try_get::<i64, _>(3)?, i64::MIN);
assert_eq!(row.try_get::<u8, _>(4)?, 255);
assert_eq!(row.try_get::<u16, _>(5)?, 65535);
assert_eq!(row.try_get::<u32, _>(6)?, 4294967295);
assert_eq!(row.try_get::<u64, _>(7)?, 9223372036854775807);
Ok(())
}
#[tokio::test]
async fn scalar_codecs_are_strict() -> TestResult {
let mut connection = TursoConnectOptions::memory().connect().await?;
let row = sqlx::query::<Turso>("SELECT ?, ?, ?, ?, ?, ?, ?, ?")
.bind(1.25_f64)
.bind(-2.5_f32)
.bind(true)
.bind(false)
.bind(String::from("中文"))
.bind(vec![0_u8, 255, 0])
.bind(Some(4.5_f64))
.bind(None::<bool>)
.fetch_one(&mut connection)
.await?;
assert_eq!(row.try_get::<f64, _>(0)?, 1.25);
assert_eq!(row.try_get::<f32, _>(1)?, -2.5);
assert!(row.try_get::<bool, _>(2)?);
assert!(!row.try_get::<bool, _>(3)?);
assert_eq!(row.try_get::<String, _>(4)?, "中文");
assert_eq!(row.try_get::<Vec<u8>, _>(5)?, [0, 255, 0]);
assert_eq!(row.try_get::<Option<f64>, _>(6)?, Some(4.5));
assert_eq!(row.try_get::<Option<bool>, _>(7)?, None);
assert!(row.try_get::<i64, _>(0).is_err());
assert!(row.try_get::<f64, _>(4).is_err());
assert!(row.try_get::<Vec<u8>, _>(4).is_err());
assert!(row.try_get::<String, _>(5).is_err());
assert_eq!(
row.try_get_raw(0)?.type_info().as_ref(),
&TursoTypeInfo::Real
);
Ok(())
}
#[tokio::test]
async fn prepared_statement_never_reuses_old_bindings() -> TestResult {
use sqlx::SqlSafeStr;
let mut connection = TursoConnectOptions::memory().connect().await?;
let statement = connection
.prepare("SELECT ?1, ?2, ?1".into_sql_str())
.await?;
for (first, second) in [(7_i64, Some(8_i64)), (9, None), (10, Some(11))] {
let row = statement
.query()
.bind(first)
.bind(second)
.fetch_one(&mut connection)
.await?;
assert_eq!(row.try_get::<i64, _>(0)?, first);
assert_eq!(row.try_get::<Option<i64>, _>(1)?, second);
assert_eq!(row.try_get::<i64, _>(2)?, first);
}
Ok(())
}
#[tokio::test]
async fn insufficient_and_extra_bindings_preserve_engine_errors() -> TestResult {
let mut connection = TursoConnectOptions::memory().connect().await?;
connection
.execute("CREATE TABLE items(a INTEGER, b INTEGER NOT NULL)")
.await?;
let error = sqlx::query::<Turso>("INSERT INTO items VALUES (?1, ?2)")
.bind(7_i64)
.execute(&mut connection)
.await
.unwrap_err();
let sqlx::Error::Database(error) = error else {
panic!("expected engine error")
};
assert!(matches!(
std::error::Error::source(&*error)
.unwrap()
.downcast_ref::<turso::Error>(),
Some(turso::Error::Constraint(_))
));
let missing = sqlx::query::<Turso>("SELECT ?1, ?2")
.bind(7_i64)
.fetch_one(&mut connection)
.await?;
assert_eq!(missing.try_get::<Option<i64>, _>(1)?, None);
let error = sqlx::query::<Turso>("SELECT ?1")
.bind(7_i64)
.bind(8_i64)
.fetch_one(&mut connection)
.await
.unwrap_err();
let sqlx::Error::Database(error) = error else {
panic!("expected engine error")
};
assert!(matches!(
std::error::Error::source(&*error)
.unwrap()
.downcast_ref::<turso::Error>(),
Some(turso::Error::Misuse(_))
));
Ok(())
}
struct Failing;
impl Type<Turso> for Failing {
fn type_info() -> TursoTypeInfo {
TursoTypeInfo::Integer
}
}
impl Encode<'_, Turso> for Failing {
fn encode_by_ref(&self, buffer: &mut Vec<turso::Value>) -> Result<IsNull, BoxDynError> {
buffer.push(turso::Value::Integer(999));
Err("deliberate encoder failure".into())
}
}
#[tokio::test]
async fn arguments_roll_back_failed_encoding() -> TestResult {
let mut arguments = TursoArguments::default();
arguments.reserve(3, 0);
arguments.add(7_i64)?;
assert!(arguments.add(Failing).is_err());
assert_eq!(arguments.len(), 1);
arguments.add(None::<i64>)?;
let mut connection = TursoConnectOptions::memory().connect().await?;
let row = sqlx::query_with::<Turso, _>("SELECT ?1, ?2", arguments)
.fetch_one(&mut connection)
.await?;
assert_eq!(row.try_get::<i64, _>(0)?, 7);
assert_eq!(row.try_get::<Option<i64>, _>(1)?, None);
Ok(())
}
fn _public_value_ref(value: TursoValueRef<'_>) -> Result<i64, BoxDynError> {
<i64 as Decode<Turso>>::decode(value)
}