use trailbase_sqlvalue::{Blob, DecodeError, SqlValue};
use wstd::http::body::IntoBody;
use wstd::http::{Client, Request};
use crate::wit::trailbase::database::sqlite::{
tx_begin, tx_commit, tx_execute, tx_query, tx_rollback,
};
pub use crate::wit::trailbase::database::sqlite::{TxError, Value};
pub use trailbase_wasm_common::{SqliteRequest, SqliteResponse};
pub struct Transaction {
committed: bool,
}
impl Transaction {
pub fn begin() -> Result<Self, TxError> {
tx_begin()?;
return Ok(Self { committed: false });
}
pub fn query(&mut self, query: &str, params: &[Value]) -> Result<Vec<Vec<Value>>, TxError> {
return tx_query(query, params);
}
pub fn execute(&mut self, query: &str, params: &[Value]) -> Result<u64, TxError> {
return tx_execute(query, params);
}
pub fn commit(&mut self) -> Result<(), TxError> {
if !self.committed {
self.committed = true;
tx_commit()?;
}
return Ok(());
}
}
impl Drop for Transaction {
fn drop(&mut self) {
if !self.committed
&& let Err(err) = tx_rollback()
{
log::warn!("TX rollback failed: {err}");
}
}
}
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("Unexpected Type")]
UnexpectedType,
#[error("Not a Number")]
NotANumber,
#[error("Decoding")]
Decoding(#[from] DecodeError),
#[error("Other: {0}")]
Other(String),
}
pub async fn query(
query: impl std::string::ToString,
params: impl Into<Vec<Value>>,
) -> Result<Vec<Vec<Value>>, Error> {
let r = SqliteRequest {
query: query.to_string(),
params: params.into().into_iter().map(to_sql_value).collect(),
};
let request = Request::builder()
.uri("http://__sqlite/query")
.method("POST")
.body(
serde_json::to_vec(&r)
.map_err(|_| Error::UnexpectedType)?
.into_body(),
)
.map_err(|err| Error::Other(err.to_string()))?;
let client = Client::new();
let (_parts, mut body) = client
.send(request)
.await
.map_err(|err| Error::Other(err.to_string()))?
.into_parts();
let bytes = body
.bytes()
.await
.map_err(|err| Error::Other(err.to_string()))?;
return match serde_json::from_slice(&bytes) {
Ok(SqliteResponse::Query { rows }) => Ok(
rows
.into_iter()
.map(|row| {
row
.into_iter()
.map(from_sql_value)
.collect::<Result<Vec<_>, _>>()
})
.collect::<Result<Vec<_>, _>>()?,
),
Ok(_) => Err(Error::UnexpectedType),
Err(err) => Err(Error::Other(err.to_string())),
};
}
pub async fn execute(
query: impl std::string::ToString,
params: impl Into<Vec<Value>>,
) -> Result<usize, Error> {
let r = SqliteRequest {
query: query.to_string(),
params: params.into().into_iter().map(to_sql_value).collect(),
};
let request = Request::builder()
.uri("http://__sqlite/execute")
.method("POST")
.body(
serde_json::to_vec(&r)
.map_err(|_| Error::UnexpectedType)?
.into_body(),
)
.map_err(|err| Error::Other(err.to_string()))?;
let client = Client::new();
let (_parts, mut body) = client
.send(request)
.await
.map_err(|err| Error::Other(err.to_string()))?
.into_parts();
let bytes = body
.bytes()
.await
.map_err(|err| Error::Other(err.to_string()))?;
return match serde_json::from_slice(&bytes) {
Ok(SqliteResponse::Execute { rows_affected }) => Ok(rows_affected),
Ok(_) => Err(Error::UnexpectedType),
Err(err) => Err(Error::Other(err.to_string())),
};
}
fn from_sql_value(value: SqlValue) -> Result<Value, DecodeError> {
return match value {
SqlValue::Null => Ok(Value::Null),
SqlValue::Integer(v) => Ok(Value::Integer(v)),
SqlValue::Real(v) => Ok(Value::Real(v)),
SqlValue::Text(v) => Ok(Value::Text(v)),
SqlValue::Blob(v) => Ok(Value::Blob(v.into_bytes()?)),
};
}
pub fn to_sql_value(value: Value) -> SqlValue {
return match value {
Value::Null => SqlValue::Null,
Value::Text(s) => SqlValue::Text(s),
Value::Integer(i) => SqlValue::Integer(i),
Value::Real(f) => SqlValue::Real(f),
Value::Blob(b) => SqlValue::Blob(Blob::Array(b)),
};
}