use super::error::{Error, Result};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum DbValue {
Null,
Bool(bool),
Int(i64),
Float(f64),
String(String),
Bytes(Vec<u8>),
Json(serde_json::Value),
}
impl From<()> for DbValue {
fn from(_: ()) -> Self {
Self::Null
}
}
impl From<bool> for DbValue {
fn from(v: bool) -> Self {
Self::Bool(v)
}
}
impl From<i32> for DbValue {
fn from(v: i32) -> Self {
Self::Int(i64::from(v))
}
}
impl From<i64> for DbValue {
fn from(v: i64) -> Self {
Self::Int(v)
}
}
impl From<f64> for DbValue {
fn from(v: f64) -> Self {
Self::Float(v)
}
}
impl From<&str> for DbValue {
fn from(v: &str) -> Self {
Self::String(v.to_string())
}
}
impl From<String> for DbValue {
fn from(v: String) -> Self {
Self::String(v)
}
}
impl From<Vec<u8>> for DbValue {
fn from(v: Vec<u8>) -> Self {
Self::Bytes(v)
}
}
impl From<serde_json::Value> for DbValue {
fn from(v: serde_json::Value) -> Self {
Self::Json(v)
}
}
pub trait ToDbParams {
fn to_db_params(&self) -> Vec<DbValue>;
}
impl ToDbParams for () {
fn to_db_params(&self) -> Vec<DbValue> {
vec![]
}
}
impl<T: Into<DbValue> + Clone> ToDbParams for &[T] {
fn to_db_params(&self) -> Vec<DbValue> {
self.iter().map(|v| v.clone().into()).collect()
}
}
impl<T: Into<DbValue> + Clone, const N: usize> ToDbParams for [T; N] {
fn to_db_params(&self) -> Vec<DbValue> {
self.iter().map(|v| v.clone().into()).collect()
}
}
impl ToDbParams for Vec<DbValue> {
fn to_db_params(&self) -> Vec<DbValue> {
self.clone()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DbRow {
#[serde(flatten)]
pub columns: std::collections::HashMap<String, serde_json::Value>,
}
impl DbRow {
#[inline]
pub fn get(&self, name: &str) -> Option<&serde_json::Value> {
self.columns.get(name)
}
pub fn get_as<T: DeserializeOwned>(&self, name: &str) -> Result<Option<T>> {
match self.columns.get(name) {
Some(v) => serde_json::from_value(v.clone()).map(Some).map_err(Error::from),
None => Ok(None),
}
}
pub fn get_required<T: DeserializeOwned>(&self, name: &str) -> Result<T> {
self.get_as(name)?
.ok_or_else(|| Error::database(format!("Missing column: {}", name)))
}
pub fn into_typed<T: DeserializeOwned>(self) -> Result<T> {
let value = serde_json::to_value(self.columns)?;
serde_json::from_value(value).map_err(Error::from)
}
}
#[derive(Debug, Serialize)]
#[allow(dead_code)]
struct QueryRequest<'a> {
sql: &'a str,
params: Vec<DbValue>,
}
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
struct QueryResponse {
rows: Vec<DbRow>,
#[serde(default)]
error: Option<String>,
}
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
struct ExecuteResponse {
rows_affected: i64,
#[serde(default)]
last_insert_id: Option<i64>,
#[serde(default)]
error: Option<String>,
}
#[cfg(target_arch = "wasm32")]
pub fn query<T: DeserializeOwned>(sql: &str, params: impl ToDbParams) -> Result<Vec<T>> {
let request = QueryRequest {
sql,
params: params.to_db_params(),
};
let request_json = serde_json::to_vec(&request)?;
let result_ptr = unsafe {
super::ffi::db_query(
sql.as_ptr() as i32,
sql.len() as i32,
request_json.as_ptr() as i32,
request_json.len() as i32,
)
};
if result_ptr == 0 {
return Err(Error::database("Database query failed"));
}
let result_bytes = unsafe { super::ffi::read_length_prefixed(result_ptr) };
let response: QueryResponse = serde_json::from_slice(&result_bytes)?;
if let Some(err) = response.error {
return Err(Error::database(err));
}
response
.rows
.into_iter()
.map(|row| row.into_typed())
.collect()
}
#[cfg(not(target_arch = "wasm32"))]
pub fn query<T: DeserializeOwned>(_sql: &str, _params: impl ToDbParams) -> Result<Vec<T>> {
Ok(vec![])
}
#[cfg(target_arch = "wasm32")]
pub fn query_raw(sql: &str, params: impl ToDbParams) -> Result<Vec<DbRow>> {
let request = QueryRequest {
sql,
params: params.to_db_params(),
};
let request_json = serde_json::to_vec(&request)?;
let result_ptr = unsafe {
super::ffi::db_query(
sql.as_ptr() as i32,
sql.len() as i32,
request_json.as_ptr() as i32,
request_json.len() as i32,
)
};
if result_ptr == 0 {
return Err(Error::database("Database query failed"));
}
let result_bytes = unsafe { super::ffi::read_length_prefixed(result_ptr) };
let response: QueryResponse = serde_json::from_slice(&result_bytes)?;
if let Some(err) = response.error {
return Err(Error::database(err));
}
Ok(response.rows)
}
#[cfg(not(target_arch = "wasm32"))]
pub fn query_raw(_sql: &str, _params: impl ToDbParams) -> Result<Vec<DbRow>> {
Ok(vec![])
}
pub fn query_one<T: DeserializeOwned>(sql: &str, params: impl ToDbParams) -> Result<Option<T>> {
let results = query::<T>(sql, params)?;
Ok(results.into_iter().next())
}
pub fn query_one_required<T: DeserializeOwned>(sql: &str, params: impl ToDbParams) -> Result<T> {
query_one::<T>(sql, params)?.ok_or_else(|| Error::not_found("No rows found"))
}
pub fn query_scalar<T: DeserializeOwned>(sql: &str, params: impl ToDbParams) -> Result<Option<T>> {
let rows = query_raw(sql, params)?;
if let Some(row) = rows.into_iter().next() {
if let Some((_, value)) = row.columns.into_iter().next() {
return Ok(Some(serde_json::from_value(value)?));
}
}
Ok(None)
}
#[cfg(target_arch = "wasm32")]
pub fn execute(sql: &str, params: impl ToDbParams) -> Result<i64> {
let params_json = serde_json::to_vec(¶ms.to_db_params())?;
let result = unsafe {
super::ffi::db_execute(
sql.as_ptr() as i32,
sql.len() as i32,
params_json.as_ptr() as i32,
params_json.len() as i32,
)
};
if result < 0 {
return Err(Error::database("Database execute failed"));
}
Ok(i64::from(result))
}
#[cfg(not(target_arch = "wasm32"))]
pub fn execute(_sql: &str, _params: impl ToDbParams) -> Result<i64> {
Ok(0)
}
#[cfg(target_arch = "wasm32")]
pub fn insert_returning_id(sql: &str, params: impl ToDbParams) -> Result<i64> {
let returning_sql = if sql.to_uppercase().contains("RETURNING") {
sql.to_string()
} else {
format!("{} RETURNING id", sql)
};
query_scalar::<i64>(&returning_sql, params)?
.ok_or_else(|| Error::database("Insert did not return an ID"))
}
#[cfg(not(target_arch = "wasm32"))]
pub fn insert_returning_id(_sql: &str, _params: impl ToDbParams) -> Result<i64> {
Ok(0)
}
pub struct Transaction {
operations: Vec<(String, Vec<DbValue>)>,
}
impl Transaction {
#[must_use]
pub const fn new() -> Self {
Self {
operations: Vec::new(),
}
}
pub fn add(&mut self, sql: impl Into<String>, params: impl ToDbParams) -> &mut Self {
self.operations.push((sql.into(), params.to_db_params()));
self
}
pub fn commit(self) -> Result<()> {
for (sql, params) in self.operations {
execute(&sql, params.as_slice())?;
}
Ok(())
}
}
impl Default for Transaction {
fn default() -> Self {
Self::new()
}
}
#[inline]
#[must_use]
pub const fn transaction() -> Transaction {
Transaction::new()
}