use std::rc::Rc;
use rusqlite::ToSql;
use rusqlite::types::{ToSqlOutput, Value, ValueRef};
use crate::DatabaseError;
#[derive(Debug, Clone)]
pub enum DbValue {
Single(Value),
Array(Rc<Vec<Value>>),
}
impl DbValue {
pub fn integer(value: i64) -> Self {
Self::Single(Value::Integer(value))
}
pub fn real(value: f64) -> Self {
Self::Single(Value::Real(value))
}
pub fn text(value: String) -> Self {
Self::Single(Value::Text(value))
}
pub fn blob(value: Vec<u8>) -> Self {
Self::Single(Value::Blob(value))
}
pub fn null() -> Self {
Self::Single(Value::Null)
}
pub(crate) fn array(values: Vec<Value>) -> Self {
Self::Array(Rc::new(values))
}
}
impl ToSql for DbValue {
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
match self {
Self::Single(value) => value.to_sql(),
Self::Array(values) => values.to_sql(),
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct DbValueRef<'a>(ValueRef<'a>);
impl<'a> DbValueRef<'a> {
pub(crate) fn new(value: ValueRef<'a>) -> Self {
Self(value)
}
pub fn as_i64(self) -> Result<i64, DatabaseError> {
self.0.as_i64().map_err(|err| DatabaseError::deserialization("i64", err))
}
pub fn as_blob(self) -> Result<&'a [u8], DatabaseError> {
self.0.as_blob().map_err(|err| DatabaseError::deserialization("blob", err))
}
pub fn as_str(self) -> Result<&'a str, DatabaseError> {
self.0.as_str().map_err(|err| DatabaseError::deserialization("str", err))
}
pub fn is_null(self) -> bool {
matches!(self.0, ValueRef::Null)
}
}
pub trait ToSqlValue {
fn to_sql_value(&self) -> DbValue;
}
pub trait FromSqlValue: Sized {
fn from_sql_value(value: DbValueRef<'_>) -> Result<Self, DatabaseError>;
}
impl<T: ToSqlValue + ?Sized> ToSqlValue for &T {
fn to_sql_value(&self) -> DbValue {
(**self).to_sql_value()
}
}
impl ToSqlValue for i64 {
fn to_sql_value(&self) -> DbValue {
DbValue::integer(*self)
}
}
impl FromSqlValue for i64 {
fn from_sql_value(value: DbValueRef<'_>) -> Result<Self, DatabaseError> {
value.as_i64()
}
}
impl ToSqlValue for bool {
fn to_sql_value(&self) -> DbValue {
DbValue::integer(i64::from(*self))
}
}
impl FromSqlValue for bool {
fn from_sql_value(value: DbValueRef<'_>) -> Result<Self, DatabaseError> {
Ok(value.as_i64()? != 0)
}
}
impl ToSqlValue for Vec<u8> {
fn to_sql_value(&self) -> DbValue {
DbValue::blob(self.clone())
}
}
impl FromSqlValue for Vec<u8> {
fn from_sql_value(value: DbValueRef<'_>) -> Result<Self, DatabaseError> {
Ok(value.as_blob()?.to_vec())
}
}
impl ToSqlValue for str {
fn to_sql_value(&self) -> DbValue {
DbValue::text(self.to_owned())
}
}
impl ToSqlValue for String {
fn to_sql_value(&self) -> DbValue {
DbValue::text(self.clone())
}
}
impl FromSqlValue for String {
fn from_sql_value(value: DbValueRef<'_>) -> Result<Self, DatabaseError> {
Ok(value.as_str()?.to_owned())
}
}
impl<T: ToSqlValue> ToSqlValue for Option<T> {
fn to_sql_value(&self) -> DbValue {
match self {
Some(value) => value.to_sql_value(),
None => DbValue::null(),
}
}
}
impl<T: FromSqlValue> FromSqlValue for Option<T> {
fn from_sql_value(value: DbValueRef<'_>) -> Result<Self, DatabaseError> {
if value.is_null() {
Ok(None)
} else {
Ok(Some(T::from_sql_value(value)?))
}
}
}
#[macro_export]
macro_rules! impl_blob_codec {
($($t:ty),+ $(,)?) => {
$(
impl $crate::sqlite::ToSqlValue for $t {
fn to_sql_value(&self) -> $crate::sqlite::DbValue {
$crate::sqlite::DbValue::blob(
::miden_protocol::utils::serde::Serializable::to_bytes(self),
)
}
}
impl $crate::sqlite::FromSqlValue for $t {
fn from_sql_value(
value: $crate::sqlite::DbValueRef<'_>,
) -> ::core::result::Result<Self, $crate::DatabaseError> {
let bytes = value.as_blob()?;
<$t as ::miden_protocol::utils::serde::Deserializable>::read_from_bytes(bytes)
.map_err(|err| {
$crate::DatabaseError::deserialization(::core::stringify!($t), err)
})
}
}
)+
};
}
impl_blob_codec!(
miden_protocol::block::BlockHeader,
miden_protocol::account::AccountId,
miden_protocol::transaction::TransactionId,
miden_protocol::note::Nullifier,
miden_protocol::Word,
);