use sql_traits::conn::{Dialect, SqlConn, SqlError, SqlRow, SqlValue};
pub fn sql_error(e: rusqlite::Error) -> SqlError {
let sqlite_code = match &e {
rusqlite::Error::SqliteFailure(f, _) => Some(f.extended_code),
_ => None,
};
SqlError {
message: e.to_string(),
sqlite_code,
}
}
pub fn from_rusqlite(v: rusqlite::types::Value) -> SqlValue {
use rusqlite::types::Value as V;
match v {
V::Null => SqlValue::Null,
V::Integer(i) => SqlValue::Integer(i),
V::Real(r) => SqlValue::Real(r),
V::Text(s) => SqlValue::Text(s),
V::Blob(b) => SqlValue::Blob(b),
}
}
pub fn to_rusqlite(v: SqlValue) -> rusqlite::types::Value {
use rusqlite::types::Value as V;
match v {
SqlValue::Null => V::Null,
SqlValue::Integer(i) => V::Integer(i),
SqlValue::Real(r) => V::Real(r),
SqlValue::Text(s) => V::Text(s),
SqlValue::Blob(b) => V::Blob(b),
}
}
pub struct Param<'a>(pub &'a SqlValue);
impl rusqlite::types::ToSql for Param<'_> {
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
use rusqlite::types::{ToSqlOutput, ValueRef};
Ok(ToSqlOutput::Borrowed(match self.0 {
SqlValue::Null => ValueRef::Null,
SqlValue::Integer(i) => ValueRef::Integer(*i),
SqlValue::Real(r) => ValueRef::Real(*r),
SqlValue::Text(s) => ValueRef::Text(s.as_bytes()),
SqlValue::Blob(b) => ValueRef::Blob(b),
}))
}
}
pub struct RusqliteRow<'a, 'stmt>(pub &'a rusqlite::Row<'stmt>);
impl SqlRow for RusqliteRow<'_, '_> {
fn get_value(&self, idx: usize) -> Result<SqlValue, SqlError> {
let v: rusqlite::types::Value = self.0.get(idx).map_err(sql_error)?;
Ok(from_rusqlite(v))
}
fn column_count(&self) -> usize {
self.0.as_ref().column_count()
}
}
fn each_row(
rows: &mut rusqlite::Rows<'_>,
f: &mut dyn FnMut(&dyn SqlRow) -> Result<(), SqlError>,
) -> Result<(), SqlError> {
while let Some(row) = rows.next().map_err(sql_error)? {
f(&RusqliteRow(row))?;
}
Ok(())
}
pub struct SqliteConn<'c>(pub &'c rusqlite::Connection);
pub fn sqlite(conn: &rusqlite::Connection) -> SqliteConn<'_> {
SqliteConn(conn)
}
impl SqlConn for SqliteConn<'_> {
fn dialect(&self) -> Dialect {
Dialect::Sqlite
}
fn execute_batch(&self, sql: &str) -> Result<(), SqlError> {
self.0.execute_batch(sql).map_err(sql_error)
}
fn execute(&self, sql: &str, params: &[SqlValue]) -> Result<usize, SqlError> {
let mut stmt = self.0.prepare_cached(sql).map_err(sql_error)?;
stmt.execute(rusqlite::params_from_iter(params.iter().map(Param)))
.map_err(sql_error)
}
fn query(
&self,
sql: &str,
params: &[SqlValue],
f: &mut dyn FnMut(&dyn SqlRow) -> Result<(), SqlError>,
) -> Result<(), SqlError> {
let mut stmt = self.0.prepare_cached(sql).map_err(sql_error)?;
let mut rows = stmt
.query(rusqlite::params_from_iter(params.iter().map(Param)))
.map_err(sql_error)?;
each_row(&mut rows, f)
}
fn query_named(
&self,
sql: &str,
params: &[(&str, SqlValue)],
f: &mut dyn FnMut(&dyn SqlRow) -> Result<(), SqlError>,
) -> Result<(), SqlError> {
let mut stmt = self.0.prepare_cached(sql).map_err(sql_error)?;
let owned: Vec<Param> = params.iter().map(|(_, v)| Param(v)).collect();
let named: Vec<(&str, &dyn rusqlite::types::ToSql)> = params
.iter()
.zip(owned.iter())
.map(|((k, _), p)| (*k, p as &dyn rusqlite::types::ToSql))
.collect();
let mut rows = stmt.query(named.as_slice()).map_err(sql_error)?;
each_row(&mut rows, f)
}
fn max_variables(&self) -> usize {
self.0
.limit(rusqlite::limits::Limit::SQLITE_LIMIT_VARIABLE_NUMBER)
.map(|n| n as usize)
.unwrap_or(999)
}
fn column_count(&self, sql: &str) -> Result<usize, SqlError> {
Ok(self
.0
.prepare_cached(sql)
.map_err(sql_error)?
.column_count())
}
}
#[cfg(test)]
mod tests {
use super::*;
use sql_traits::conn::SqlConnExt;
#[test]
fn rusqlite_conn_round_trips_through_the_seam() {
let c = rusqlite::Connection::open_in_memory().unwrap();
let w = SqliteConn(&c);
let conn: &dyn SqlConn = &w;
conn.execute_batch("CREATE TABLE t (a INTEGER, b)").unwrap();
for v in [
SqlValue::Integer(7),
SqlValue::Real(1.5),
SqlValue::Text("x".into()),
SqlValue::Blob(vec![1, 2]),
SqlValue::Null,
] {
assert_eq!(
conn.execute("INSERT INTO t VALUES (?, ?)", &[1.into(), v])
.unwrap(),
1
);
}
let got = conn
.query_rows("SELECT b FROM t ORDER BY rowid", &[], |r| r.get_value(0))
.unwrap();
assert_eq!(
got,
vec![
SqlValue::Integer(7),
SqlValue::Real(1.5),
SqlValue::Text("x".into()),
SqlValue::Blob(vec![1, 2]),
SqlValue::Null
]
);
let named = {
let mut n = 0;
conn.query_named(
"SELECT count(*) FROM t WHERE a = $one",
&[("$one", 1.into())],
&mut |r| {
n = r.get_i64(0)?;
Ok(())
},
)
.unwrap();
n
};
assert_eq!(named, 5);
assert_eq!(conn.column_count("SELECT a, b FROM t").unwrap(), 2);
assert!(conn.max_variables() >= 999);
let e = conn
.execute("INSERT INTO nope VALUES (1)", &[])
.unwrap_err();
assert!(e.message.contains("nope"), "{e}");
}
}