use sqlx::{ConnectOptions, Connection, Executor, Statement};
use sqlx_turso_driver::{Turso, TursoConnectOptions};
#[tokio::test]
async fn unicode_file_reopens() -> Result<(), Box<dyn std::error::Error>> {
let directory = tempfile::tempdir()?;
let path = directory.path().join("中文 数据库.db");
let options = TursoConnectOptions::file(&path)?;
let mut connection = options.connect().await?;
connection
.execute("CREATE TABLE items(value INTEGER NOT NULL)")
.await?;
connection
.execute("INSERT INTO items(value) VALUES (7)")
.await?;
connection.close().await?;
assert!(path.is_file(), "the exact requested path must be used");
let mut reopened = options.connect().await?;
let value = sqlx::query_scalar::<Turso, i64>("SELECT value FROM items")
.fetch_one(&mut reopened)
.await?;
assert_eq!(value, 7);
reopened.close().await?;
Ok(())
}
#[tokio::test]
async fn memory_connections_are_independent() -> Result<(), Box<dyn std::error::Error>> {
let options = TursoConnectOptions::memory();
let mut first = options.connect().await?;
let mut second = options.connect().await?;
first
.execute("CREATE TABLE items(value INTEGER NOT NULL)")
.await?;
first.execute("INSERT INTO items(value) VALUES (7)").await?;
let value = sqlx::query_scalar::<Turso, i64>("SELECT value FROM items")
.fetch_one(&mut first)
.await?;
assert_eq!(value, 7);
assert!(
sqlx::query_scalar::<Turso, i64>("SELECT value FROM items")
.fetch_one(&mut second)
.await
.is_err()
);
first.close().await?;
second.close().await?;
Ok(())
}
#[tokio::test]
async fn local_url_roundtrips_exact_file() -> Result<(), Box<dyn std::error::Error>> {
let directory = tempfile::tempdir()?;
let path = directory.path().join("中文 空格 %25 #?.db");
let unrelated = directory.path().join("unrelated.txt");
std::fs::write(&unrelated, b"do not replace")?;
let options = TursoConnectOptions::file(&path)?;
let url = options.to_url_lossy();
let parsed: TursoConnectOptions = url.as_str().parse()?;
let mut connection = parsed.connect().await?;
connection
.execute("CREATE TABLE items(value INTEGER NOT NULL)")
.await?;
connection.execute("INSERT INTO items VALUES (7)").await?;
connection.close().await?;
assert!(path.is_file());
assert_eq!(std::fs::read(&unrelated)?, b"do not replace");
let mut reopened = options.connect().await?;
assert_eq!(
sqlx::query_scalar::<Turso, i64>("SELECT value FROM items")
.fetch_one(&mut reopened)
.await?,
7
);
reopened.close().await?;
Ok(())
}
#[tokio::test]
async fn memory_url_connects() -> Result<(), Box<dyn std::error::Error>> {
let options: TursoConnectOptions = "turso-memory:".parse()?;
let mut connection = options.connect().await?;
assert_eq!(
sqlx::query_scalar::<Turso, i64>("SELECT 7")
.fetch_one(&mut connection)
.await?,
7
);
connection.close().await?;
Ok(())
}
#[tokio::test]
async fn batches_are_rejected_without_partial_execution() -> Result<(), Box<dyn std::error::Error>>
{
let mut connection = TursoConnectOptions::memory().connect().await?;
assert!(
connection
.execute("CREATE TABLE ignored(value INTEGER); INSERT INTO ignored VALUES (7)")
.await
.is_err()
);
assert!(
sqlx::query_scalar::<Turso, i64>("SELECT value FROM ignored")
.fetch_one(&mut connection)
.await
.is_err()
);
assert_eq!(
sqlx::query_scalar::<Turso, i64>("SELECT length('a;b'); -- trailing ; comment\n")
.fetch_one(&mut connection)
.await?,
3
);
connection.close().await?;
Ok(())
}
#[tokio::test]
async fn query_failures_are_not_success() -> Result<(), Box<dyn std::error::Error>> {
let mut connection = TursoConnectOptions::memory().connect().await?;
let error = sqlx::query_scalar::<Turso, i64>("SELECT value FROM nonexistent")
.fetch_one(&mut connection)
.await
.unwrap_err();
let sqlx::Error::Database(error) = error else {
panic!("expected a real database error");
};
assert!(std::error::Error::source(&*error).is_some());
assert_eq!(error.kind(), sqlx::error::ErrorKind::Other);
assert!(matches!(
sqlx::query_scalar::<Turso, i64>("SELECT 7 WHERE 0")
.fetch_one(&mut connection)
.await,
Err(sqlx::Error::RowNotFound)
));
assert!(matches!(
sqlx::query_scalar::<Turso, i64>("SELECT '7'")
.fetch_one(&mut connection)
.await,
Err(sqlx::Error::ColumnDecode { .. })
));
assert!(matches!(
sqlx::query_scalar::<Turso, i64>("SELECT NULL")
.fetch_one(&mut connection)
.await,
Err(sqlx::Error::ColumnDecode { .. })
));
connection.begin().await?.rollback().await?;
connection.begin_with("BEGIN").await?.rollback().await?;
let prepared = connection
.prepare(sqlx::SqlSafeStr::into_sql_str("SELECT 7"))
.await?;
assert_eq!(
prepared
.query_scalar::<i64>()
.fetch_one(&mut connection)
.await?,
7
);
assert_eq!(
sqlx::query_scalar::<Turso, i64>("SELECT 7")
.fetch_one(&mut connection)
.await?,
7
);
connection.close().await?;
Ok(())
}
#[test]
fn file_url_remote_authorities_are_rejected() {
for value in [
"file://example.invalid/share/db",
"file://EXAMPLE.INVALID/share/db",
"file://localhost.invalid/share/db",
"file://localhost./share/db",
"file://127.0.0.1/share/db",
"file://[::1]/share/db",
] {
let url = url::Url::parse(value).unwrap();
assert!(
matches!(
TursoConnectOptions::from_url(&url),
Err(sqlx::Error::Configuration(_))
),
"accepted remote authority through from_url: {value}"
);
assert!(
matches!(
value.parse::<TursoConnectOptions>(),
Err(sqlx::Error::Configuration(_))
),
"accepted remote authority through FromStr: {value}"
);
}
}
#[test]
fn file_url_local_authorities_are_accepted() -> Result<(), Box<dyn std::error::Error>> {
let directory = tempfile::tempdir()?;
let path = directory.path().join("local.db");
let expected = url::Url::from_file_path(&path).unwrap();
for authority in ["", "localhost", "LOCALHOST"] {
let value = expected
.as_str()
.replacen("file://", &format!("file://{authority}"), 1);
let url = url::Url::parse(&value)?;
assert_eq!(
TursoConnectOptions::from_url(&url)?.to_url_lossy(),
expected
);
assert_eq!(
value.parse::<TursoConnectOptions>()?.to_url_lossy(),
expected
);
}
Ok(())
}
#[test]
fn local_url_flags_are_rejected() {
for url in [
"file://example.invalid/db",
"file:///tmp/db?mode=ro",
"file:///tmp/db#fragment",
"turso-memory:shared",
"turso-memory:?cache=shared",
] {
assert!(
url.parse::<TursoConnectOptions>().is_err(),
"accepted {url}"
);
}
}
#[test]
fn file_constructor_rejects_empty_path() {
assert!(matches!(
TursoConnectOptions::file(""),
Err(sqlx::Error::Configuration(_))
));
}
#[cfg(unix)]
#[test]
fn file_constructor_rejects_non_utf8_path() {
use std::os::unix::ffi::OsStringExt;
let path = std::path::PathBuf::from(std::ffi::OsString::from_vec(vec![0xff]));
assert!(matches!(
TursoConnectOptions::file(path),
Err(sqlx::Error::Configuration(_))
));
}
#[tokio::test]
async fn reserved_memory_file_name_is_absolute_and_persistent()
-> Result<(), Box<dyn std::error::Error>> {
let literal = TursoConnectOptions::file(":memory:")?;
assert_eq!(
literal.to_url_lossy().to_file_path().unwrap(),
std::env::current_dir()?.join(":memory:")
);
let directory = tempfile::tempdir()?;
let path = directory.path().join(":memory:");
let options = TursoConnectOptions::file(&path)?;
let mut connection = options.connect().await?;
connection
.execute("CREATE TABLE items(value INTEGER)")
.await?;
connection.execute("INSERT INTO items VALUES (7)").await?;
connection.close().await?;
assert!(path.is_file());
let mut reopened = options.connect().await?;
assert_eq!(
sqlx::query_scalar::<Turso, i64>("SELECT value FROM items")
.fetch_one(&mut reopened)
.await?,
7
);
reopened.close().await?;
Ok(())
}
#[test]
fn remote_and_unknown_urls_are_rejected() {
for url in [
"https://example.invalid/db",
"libsql://example.invalid/db",
"turso://example.invalid/db",
"unknown://db",
] {
assert!(
url.parse::<TursoConnectOptions>().is_err(),
"accepted {url}"
);
}
}