use std::cell::RefCell;
use std::collections::HashMap;
use std::error::Error;
use std::fmt;
use std::path::{Path, PathBuf};
use std::rc::Rc;
use rusqlite::types::{ToSqlOutput, Value as RusqliteValue, ValueRef};
use rusqlite::{Connection, ToSql, params_from_iter};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SqliteBackend {
NodeSqlite,
}
impl SqliteBackend {
pub fn as_str(self) -> &'static str {
match self {
Self::NodeSqlite => "node-sqlite",
}
}
}
#[derive(Debug, Clone)]
pub struct SqliteError {
pub message: String,
}
impl SqliteError {
pub fn new(message: impl Into<String>) -> Self {
Self {
message: message.into(),
}
}
pub fn backend_unavailable(db_path: &Path) -> Self {
Self::new(format!(
"SQLite backend is not wired yet for {}. Task 02 only translates the facade; a later Cargo/runtime task chooses the concrete backend.",
db_path.display()
))
}
}
impl fmt::Display for SqliteError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.message)
}
}
impl Error for SqliteError {}
pub type SqliteResult<T> = Result<T, SqliteError>;
#[derive(Debug, Clone, PartialEq)]
pub enum SqliteValue {
Null,
Integer(i64),
Real(f64),
Text(String),
Blob(Vec<u8>),
Bool(bool),
Json(serde_json::Value),
}
impl SqliteValue {
pub fn as_i64(&self) -> Option<i64> {
match self {
Self::Integer(value) => Some(*value),
Self::Bool(value) => Some(if *value { 1 } else { 0 }),
Self::Real(value) => Some(*value as i64),
Self::Text(value) => value.parse::<i64>().ok(),
Self::Null | Self::Blob(_) | Self::Json(_) => None,
}
}
pub fn as_f64(&self) -> Option<f64> {
match self {
Self::Real(value) => Some(*value),
Self::Integer(value) => Some(*value as f64),
Self::Bool(value) => Some(if *value { 1.0 } else { 0.0 }),
Self::Text(value) => value.parse::<f64>().ok(),
Self::Null | Self::Blob(_) | Self::Json(_) => None,
}
}
pub fn as_bool(&self) -> Option<bool> {
match self {
Self::Bool(value) => Some(*value),
Self::Integer(value) => Some(*value != 0),
Self::Text(value) => match value.as_str() {
"1" | "true" | "TRUE" => Some(true),
"0" | "false" | "FALSE" => Some(false),
_ => None,
},
Self::Null | Self::Real(_) | Self::Blob(_) | Self::Json(_) => None,
}
}
pub fn as_str(&self) -> Option<&str> {
match self {
Self::Text(value) => Some(value),
_ => None,
}
}
pub fn into_string_lossy(self) -> Option<String> {
match self {
Self::Text(value) => Some(value),
Self::Integer(value) => Some(value.to_string()),
Self::Real(value) => Some(value.to_string()),
Self::Bool(value) => Some(if value { "1" } else { "0" }.to_string()),
Self::Json(value) => Some(value.to_string()),
Self::Null | Self::Blob(_) => None,
}
}
}
impl From<&str> for SqliteValue {
fn from(value: &str) -> Self {
Self::Text(value.to_string())
}
}
impl From<String> for SqliteValue {
fn from(value: String) -> Self {
Self::Text(value)
}
}
impl From<&String> for SqliteValue {
fn from(value: &String) -> Self {
Self::Text(value.clone())
}
}
impl From<i64> for SqliteValue {
fn from(value: i64) -> Self {
Self::Integer(value)
}
}
impl From<i32> for SqliteValue {
fn from(value: i32) -> Self {
Self::Integer(value as i64)
}
}
impl From<usize> for SqliteValue {
fn from(value: usize) -> Self {
Self::Integer(value as i64)
}
}
impl From<u64> for SqliteValue {
fn from(value: u64) -> Self {
Self::Integer(value as i64)
}
}
impl From<bool> for SqliteValue {
fn from(value: bool) -> Self {
Self::Integer(if value { 1 } else { 0 })
}
}
impl From<serde_json::Value> for SqliteValue {
fn from(value: serde_json::Value) -> Self {
Self::Json(value)
}
}
impl<T> From<Option<T>> for SqliteValue
where
T: Into<SqliteValue>,
{
fn from(value: Option<T>) -> Self {
value.map(Into::into).unwrap_or(Self::Null)
}
}
pub type SqliteRow = HashMap<String, SqliteValue>;
#[derive(Debug, Clone, PartialEq, Default)]
pub enum SqliteParams {
#[default]
None,
Positional(Vec<SqliteValue>),
Named(HashMap<String, SqliteValue>),
}
impl SqliteParams {
pub fn none() -> Self {
Self::None
}
pub fn positional(values: Vec<SqliteValue>) -> Self {
Self::Positional(values)
}
pub fn named(values: HashMap<String, SqliteValue>) -> Self {
Self::Named(values)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RunResult {
pub changes: u64,
pub last_insert_rowid: i64,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct PragmaOptions {
pub simple: bool,
}
pub trait SqliteStatement {
fn run(&mut self, params: SqliteParams) -> SqliteResult<RunResult>;
fn get(&mut self, params: SqliteParams) -> SqliteResult<Option<SqliteRow>>;
fn all(&mut self, params: SqliteParams) -> SqliteResult<Vec<SqliteRow>>;
fn iterate<'stmt>(
&'stmt mut self,
params: SqliteParams,
) -> SqliteResult<Box<dyn Iterator<Item = SqliteResult<SqliteRow>> + 'stmt>>;
}
pub trait SqliteDatabase {
fn prepare(&mut self, sql: &str) -> SqliteResult<Box<dyn SqliteStatement>>;
fn exec(&mut self, sql: &str) -> SqliteResult<()>;
fn pragma(
&mut self,
pragma: &str,
options: Option<PragmaOptions>,
) -> SqliteResult<Option<SqliteValue>>;
fn transaction(
&mut self,
f: &mut dyn FnMut(&mut dyn SqliteDatabase) -> SqliteResult<()>,
) -> SqliteResult<()>;
fn close(&mut self) -> SqliteResult<()>;
fn is_open(&self) -> bool;
}
pub struct CreatedDatabase {
pub db: Box<dyn SqliteDatabase>,
pub backend: SqliteBackend,
}
pub fn create_database(db_path: impl AsRef<Path>) -> SqliteResult<CreatedDatabase> {
let connection = Connection::open(db_path.as_ref()).map_err(sqlite_error)?;
Ok(CreatedDatabase {
db: Box::new(RusqliteDatabase::new(connection)),
backend: SqliteBackend::NodeSqlite,
})
}
type SharedConnection = Rc<RefCell<Option<Connection>>>;
struct RusqliteDatabase {
connection: SharedConnection,
}
impl RusqliteDatabase {
fn new(connection: Connection) -> Self {
Self {
connection: Rc::new(RefCell::new(Some(connection))),
}
}
}
struct RusqliteStatement {
connection: SharedConnection,
sql: String,
}
impl RusqliteStatement {
fn new(connection: SharedConnection, sql: &str) -> Self {
Self {
connection,
sql: sql.to_string(),
}
}
}
impl SqliteDatabase for RusqliteDatabase {
fn prepare(&mut self, sql: &str) -> SqliteResult<Box<dyn SqliteStatement>> {
with_connection(&self.connection, |connection| {
connection.prepare(sql).map(|_| ()).map_err(sqlite_error)
})?;
Ok(Box::new(RusqliteStatement::new(
Rc::clone(&self.connection),
sql,
)))
}
fn exec(&mut self, sql: &str) -> SqliteResult<()> {
with_connection(&self.connection, |connection| {
connection.execute_batch(sql).map_err(sqlite_error)
})
}
fn pragma(
&mut self,
pragma: &str,
_options: Option<PragmaOptions>,
) -> SqliteResult<Option<SqliteValue>> {
with_connection(&self.connection, |connection| {
let sql = format!("PRAGMA {pragma}");
let mut statement = connection.prepare(&sql).map_err(sqlite_error)?;
let mut rows = statement.query([]).map_err(sqlite_error)?;
let Some(row) = rows.next().map_err(sqlite_error)? else {
return Ok(None);
};
let value = row.get_ref(0).map_err(sqlite_error)?;
Ok(Some(value_ref_to_sqlite(value)))
})
}
fn transaction(
&mut self,
f: &mut dyn FnMut(&mut dyn SqliteDatabase) -> SqliteResult<()>,
) -> SqliteResult<()> {
self.exec("BEGIN IMMEDIATE TRANSACTION")?;
match f(self) {
Ok(()) => {
self.exec("COMMIT")?;
Ok(())
}
Err(error) => {
let _ = self.exec("ROLLBACK");
Err(error)
}
}
}
fn close(&mut self) -> SqliteResult<()> {
let Some(connection) = self.connection.borrow_mut().take() else {
return Ok(());
};
connection
.close()
.map_err(|(_connection, error)| sqlite_error(error))
}
fn is_open(&self) -> bool {
self.connection.borrow().is_some()
}
}
impl SqliteStatement for RusqliteStatement {
fn run(&mut self, params: SqliteParams) -> SqliteResult<RunResult> {
with_connection(&self.connection, |connection| {
let mut statement = connection.prepare(&self.sql).map_err(sqlite_error)?;
let values = bind_values(&statement, params)?;
let changes = statement
.execute(params_from_iter(values.iter()))
.map_err(sqlite_error)?;
Ok(RunResult {
changes: changes as u64,
last_insert_rowid: connection.last_insert_rowid(),
})
})
}
fn get(&mut self, params: SqliteParams) -> SqliteResult<Option<SqliteRow>> {
with_connection(&self.connection, |connection| {
let mut statement = connection.prepare(&self.sql).map_err(sqlite_error)?;
let values = bind_values(&statement, params)?;
let mut rows = statement
.query(params_from_iter(values.iter()))
.map_err(sqlite_error)?;
let Some(row) = rows.next().map_err(sqlite_error)? else {
return Ok(None);
};
row_to_map(row).map(Some)
})
}
fn all(&mut self, params: SqliteParams) -> SqliteResult<Vec<SqliteRow>> {
with_connection(&self.connection, |connection| {
let mut statement = connection.prepare(&self.sql).map_err(sqlite_error)?;
let values = bind_values(&statement, params)?;
let mut rows = statement
.query(params_from_iter(values.iter()))
.map_err(sqlite_error)?;
let mut output = Vec::new();
while let Some(row) = rows.next().map_err(sqlite_error)? {
output.push(row_to_map(row)?);
}
Ok(output)
})
}
fn iterate<'stmt>(
&'stmt mut self,
params: SqliteParams,
) -> SqliteResult<Box<dyn Iterator<Item = SqliteResult<SqliteRow>> + 'stmt>> {
let rows = self.all(params)?;
Ok(Box::new(rows.into_iter().map(Ok)))
}
}
impl ToSql for SqliteValue {
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
Ok(ToSqlOutput::Owned(sqlite_value_to_rusqlite(self.clone())))
}
}
fn with_connection<T>(
connection: &SharedConnection,
f: impl FnOnce(&Connection) -> SqliteResult<T>,
) -> SqliteResult<T> {
let borrowed = connection.borrow();
let connection = borrowed
.as_ref()
.ok_or_else(|| SqliteError::new("SQLite connection is closed"))?;
f(connection)
}
fn sqlite_error(error: rusqlite::Error) -> SqliteError {
SqliteError::new(error.to_string())
}
fn bind_values(
statement: &rusqlite::Statement<'_>,
params: SqliteParams,
) -> SqliteResult<Vec<SqliteValue>> {
let count = statement.parameter_count();
match params {
SqliteParams::None => Ok(Vec::new()),
SqliteParams::Positional(values) => {
if values.len() != count {
return Err(SqliteError::new(format!(
"expected {count} SQL parameters, got {}",
values.len()
)));
}
Ok(values)
}
SqliteParams::Named(values) => {
let mut output = Vec::with_capacity(count);
for index in 1..=count {
let Some(name) = statement.parameter_name(index) else {
return Err(SqliteError::new(format!(
"SQL parameter {index} is positional but named parameters were provided"
)));
};
let key = name.trim_start_matches(['@', ':', '$']);
let value = values
.get(key)
.or_else(|| values.get(name))
.ok_or_else(|| SqliteError::new(format!("missing SQL parameter {name}")))?;
output.push(value.clone());
}
Ok(output)
}
}
}
fn row_to_map(row: &rusqlite::Row<'_>) -> SqliteResult<SqliteRow> {
let statement = row.as_ref();
let mut output = HashMap::new();
for index in 0..statement.column_count() {
let name = statement
.column_name(index)
.map_err(sqlite_error)?
.to_string();
let value = row.get_ref(index).map_err(sqlite_error)?;
output.insert(name, value_ref_to_sqlite(value));
}
Ok(output)
}
fn sqlite_value_to_rusqlite(value: SqliteValue) -> RusqliteValue {
match value {
SqliteValue::Null => RusqliteValue::Null,
SqliteValue::Integer(value) => RusqliteValue::Integer(value),
SqliteValue::Real(value) => RusqliteValue::Real(value),
SqliteValue::Text(value) => RusqliteValue::Text(value),
SqliteValue::Blob(value) => RusqliteValue::Blob(value),
SqliteValue::Bool(value) => RusqliteValue::Integer(if value { 1 } else { 0 }),
SqliteValue::Json(value) => RusqliteValue::Text(value.to_string()),
}
}
fn value_ref_to_sqlite(value: ValueRef<'_>) -> SqliteValue {
match value {
ValueRef::Null => SqliteValue::Null,
ValueRef::Integer(value) => SqliteValue::Integer(value),
ValueRef::Real(value) => SqliteValue::Real(value),
ValueRef::Text(value) => SqliteValue::Text(String::from_utf8_lossy(value).into_owned()),
ValueRef::Blob(value) => SqliteValue::Blob(value.to_vec()),
}
}
pub struct DeferredSqliteDatabase {
path: PathBuf,
open: bool,
}
impl DeferredSqliteDatabase {
pub fn new(path: impl Into<PathBuf>) -> Self {
Self {
path: path.into(),
open: true,
}
}
}
impl SqliteDatabase for DeferredSqliteDatabase {
fn prepare(&mut self, _sql: &str) -> SqliteResult<Box<dyn SqliteStatement>> {
Err(SqliteError::backend_unavailable(&self.path))
}
fn exec(&mut self, _sql: &str) -> SqliteResult<()> {
Err(SqliteError::backend_unavailable(&self.path))
}
fn pragma(
&mut self,
_pragma: &str,
_options: Option<PragmaOptions>,
) -> SqliteResult<Option<SqliteValue>> {
Err(SqliteError::backend_unavailable(&self.path))
}
fn transaction(
&mut self,
_f: &mut dyn FnMut(&mut dyn SqliteDatabase) -> SqliteResult<()>,
) -> SqliteResult<()> {
Err(SqliteError::backend_unavailable(&self.path))
}
fn close(&mut self) -> SqliteResult<()> {
self.open = false;
Ok(())
}
fn is_open(&self) -> bool {
self.open
}
}