use crate::Error;
use chrono::{DateTime, FixedOffset, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
use serde::{Deserialize, Serialize};
use tracing::error;
#[derive(Debug)]
pub enum Row<'a> {
Borrowed(&'a rusqlite::Row<'a>),
Owned(RowOwned),
}
impl Row<'_> {
pub fn get<T>(&mut self, idx: &str) -> T
where
T: TryFrom<ValueOwned, Error = crate::Error> + rusqlite::types::FromSql,
{
match self {
Row::Borrowed(b) => {
let res = b.get(idx);
if res.is_err() {
error!("Cannot convert column index '{idx}' to requested type");
}
res.unwrap()
}
Row::Owned(o) => o.try_get(idx).unwrap_or_else(|err| {
panic!("Cannot convert column index '{idx}' to requested type: {err:?}")
}),
}
}
pub fn try_get<T>(&mut self, idx: &str) -> Result<T, Error>
where
T: TryFrom<ValueOwned, Error = crate::Error> + rusqlite::types::FromSql,
{
match self {
Self::Borrowed(r) => r.get(idx).map_err(Error::from),
Self::Owned(o) => o.try_get(idx),
}
}
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct RowOwned {
pub(crate) columns: Vec<ColumnOwned>,
}
impl RowOwned {
#[inline(always)]
pub(crate) fn from_row_column(row: &rusqlite::Row, columns: &[ColumnInfo]) -> Self {
let mut cols = Vec::with_capacity(columns.len());
for (i, info) in columns.iter().enumerate() {
let value = match info.typ {
ColumnType::Expr => {
if let Ok(text) = row.get::<_, String>(i) {
ValueOwned::Text(text)
} else if let Ok(i) = row.get::<_, i64>(i) {
ValueOwned::Integer(i)
} else if let Ok(r) = row.get::<_, f64>(i) {
ValueOwned::Real(r)
} else if let Ok(b) = row.get::<_, Vec<u8>>(i) {
ValueOwned::Blob(b)
} else {
ValueOwned::Null
}
}
ColumnType::Integer => row
.get(i)
.map(ValueOwned::Integer)
.unwrap_or(ValueOwned::Null),
ColumnType::Real => row.get(i).map(ValueOwned::Real).unwrap_or(ValueOwned::Null),
ColumnType::Text => row.get(i).map(ValueOwned::Text).unwrap_or(ValueOwned::Null),
ColumnType::Blob => row.get(i).map(ValueOwned::Blob).unwrap_or(ValueOwned::Null),
};
cols.push(ColumnOwned {
name: info.name.clone(),
value,
})
}
Self { columns: cols }
}
}
impl RowOwned {
pub fn get<T: TryFrom<ValueOwned, Error = crate::Error>>(&mut self, idx: &str) -> T {
self.try_get(idx).unwrap()
}
pub fn try_get<T: TryFrom<ValueOwned, Error = crate::Error>>(
&mut self,
idx: &str,
) -> Result<T, Error> {
for i in 0..self.columns.len() {
if self.columns[i].name == idx {
return T::try_from(self.columns.swap_remove(i).value);
}
}
Err(Error::QueryParams(
format!("column '{idx}' not found").into(),
))
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ColumnInfo {
pub name: String,
pub typ: ColumnType,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ColumnType {
Expr,
Integer,
Real,
Text,
Blob,
}
impl ColumnType {
#[inline(always)]
fn from_decl_type(typ: Option<&str>) -> Self {
if let Some(t) = typ {
if t.starts_with("INT") || t.starts_with("int") {
return Self::Integer;
}
match t {
"TEXT" | "text" => Self::Text,
"BLOB" | "blob" => Self::Blob,
"REAL" | "real" => Self::Real,
_ => {
if t.is_empty() {
return Self::Blob;
}
let ty = t.to_uppercase();
if ty.contains("INT") {
Self::Integer
} else if ty.contains("CHAR") || ty.contains("CLOB") {
Self::Text
} else if ty.contains("FLOA") || ty.contains("DOUB") {
Self::Real
} else {
Self::Integer
}
}
}
} else {
Self::Expr
}
}
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct ColumnOwned {
pub(crate) name: String,
pub(crate) value: ValueOwned,
}
impl ColumnOwned {
#[inline(always)]
pub(crate) fn mapping_cols_from_stmt(
columns: Vec<rusqlite::Column>,
) -> Result<Vec<ColumnInfo>, Error> {
let mut cols = Vec::with_capacity(columns.len());
for col in columns {
cols.push(ColumnInfo {
name: col.name().to_string(),
typ: ColumnType::from_decl_type(col.decl_type()),
});
}
Ok(cols)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ValueOwned {
Null,
Integer(i64),
Real(f64),
Text(String),
Blob(Vec<u8>),
}
impl ValueOwned {
#[inline]
fn try_as_str(&self) -> Result<&str, Error> {
match self {
ValueOwned::Text(s) => Ok(s.as_str()),
_ => Err(Error::Sqlite("Cannot convert ValueOwned to &str".into())),
}
}
}
impl TryFrom<ValueOwned> for i64 {
type Error = crate::Error;
#[inline]
fn try_from(value: ValueOwned) -> Result<Self, Self::Error> {
match value {
ValueOwned::Integer(i) => Ok(i),
_ => Err(Error::Sqlite("Cannot convert into i64".into())),
}
}
}
impl TryFrom<ValueOwned> for Option<i64> {
type Error = crate::Error;
#[inline]
fn try_from(value: ValueOwned) -> Result<Self, Self::Error> {
match value {
ValueOwned::Null => Ok(None),
v => i64::try_from(v).map(Some),
}
}
}
#[cfg(any(feature = "cast_ints", feature = "cast_ints_unchecked"))]
mod cast_ints {
impl TryFrom<super::ValueOwned> for u64 {
type Error = crate::Error;
#[inline]
fn try_from(value: super::ValueOwned) -> Result<Self, Self::Error> {
let i = i64::try_from(value)?;
if i < 0 { Ok(0) } else { Ok(i as u64) }
}
}
impl TryFrom<super::ValueOwned> for Option<u64> {
type Error = crate::Error;
#[inline]
fn try_from(value: super::ValueOwned) -> Result<Option<u64>, Self::Error> {
let i: Option<i64> = value.try_into()?;
Ok(i.map(|i| i as u64))
}
}
macro_rules! downcast_int(
($t:ty) => (
impl TryFrom<super::ValueOwned> for $t {
type Error = crate::Error;
#[inline]
fn try_from(value: super::ValueOwned) -> Result<Self, Self::Error> {
let i = i64::try_from(value)?;
#[cfg(feature = "cast_ints_unchecked")]
return Ok(i as $t);
#[cfg(feature = "cast_ints")]
if i < 0 {
Ok(::core::cmp::max(<$t>::MIN as i64, i) as $t)
} else {
Ok(::core::cmp::min(<$t>::MAX as i64, i) as $t)
}
}
}
)
);
downcast_int!(i32);
downcast_int!(i16);
downcast_int!(i8);
downcast_int!(u32);
downcast_int!(u16);
downcast_int!(u8);
macro_rules! downcast_int_opt(
($t:ty) => (
impl TryFrom<super::ValueOwned> for Option<$t> {
type Error = crate::Error;
#[inline]
fn try_from(value: super::ValueOwned) -> Result<Option<$t>, Self::Error> {
let i: Option<i64> = value.try_into()?;
Ok(i.map(|i| i as $t))
}
}
)
);
downcast_int_opt!(i32);
downcast_int_opt!(i16);
downcast_int_opt!(i8);
downcast_int_opt!(u32);
downcast_int_opt!(u16);
downcast_int_opt!(u8);
}
impl TryFrom<ValueOwned> for f64 {
type Error = Error;
#[inline]
fn try_from(value: ValueOwned) -> Result<Self, Self::Error> {
match value {
ValueOwned::Real(r) => Ok(r),
_ => Err(Error::Sqlite("Cannot convert into f64".into())),
}
}
}
impl TryFrom<ValueOwned> for Option<f64> {
type Error = crate::Error;
#[inline]
fn try_from(value: ValueOwned) -> Result<Self, Self::Error> {
match value {
ValueOwned::Null => Ok(None),
v => f64::try_from(v).map(Some),
}
}
}
impl TryFrom<ValueOwned> for bool {
type Error = crate::Error;
#[inline]
fn try_from(value: ValueOwned) -> Result<Self, Self::Error> {
match value {
ValueOwned::Integer(i) => Ok(i == 1),
ValueOwned::Real(i) => Ok(i == 1.0),
ValueOwned::Text(s) => Ok(s.as_str() == "true"),
_ => Err(Error::Sqlite("Cannot convert into bool".into())),
}
}
}
impl TryFrom<ValueOwned> for Option<bool> {
type Error = crate::Error;
#[inline]
fn try_from(value: ValueOwned) -> Result<Self, Self::Error> {
match value {
ValueOwned::Null => Ok(None),
v => v.try_into().map(Some),
}
}
}
impl TryFrom<ValueOwned> for String {
type Error = crate::Error;
#[inline]
fn try_from(value: ValueOwned) -> Result<Self, Self::Error> {
match value {
ValueOwned::Text(s) => Ok(s),
_ => Err(Error::Sqlite("Cannot convert into String".into())),
}
}
}
impl TryFrom<ValueOwned> for Option<String> {
type Error = crate::Error;
#[inline]
fn try_from(value: ValueOwned) -> Result<Self, Self::Error> {
match value {
ValueOwned::Null => Ok(None),
v => String::try_from(v).map(Some),
}
}
}
impl TryFrom<ValueOwned> for Vec<u8> {
type Error = Error;
#[inline]
fn try_from(value: ValueOwned) -> Result<Self, Self::Error> {
match value {
ValueOwned::Blob(b) => Ok(b),
_ => Err(Error::Sqlite("Cannot convert into Vec<u8>".into())),
}
}
}
impl TryFrom<ValueOwned> for Option<Vec<u8>> {
type Error = crate::Error;
#[inline]
fn try_from(value: ValueOwned) -> Result<Self, Self::Error> {
match value {
ValueOwned::Null => Ok(None),
v => Vec::try_from(v).map(Some),
}
}
}
impl<const N: usize> TryFrom<ValueOwned> for [u8; N] {
type Error = crate::Error;
#[inline]
fn try_from(value: ValueOwned) -> Result<Self, Self::Error> {
match value {
ValueOwned::Blob(b) => Ok(b
.try_into()
.map_err(|_| Error::Sqlite("Cannot convert into [u8]: incorrect length".into()))?),
_ => Err(Error::Sqlite("Cannot convert into [u8]".into())),
}
}
}
impl<const N: usize> TryFrom<ValueOwned> for Option<[u8; N]> {
type Error = crate::Error;
#[inline]
fn try_from(value: ValueOwned) -> Result<Self, Self::Error> {
match value {
ValueOwned::Null => Ok(None),
v => <[u8; N]>::try_from(v).map(Some),
}
}
}
impl TryFrom<ValueOwned> for NaiveDate {
type Error = crate::Error;
#[inline]
fn try_from(value: ValueOwned) -> Result<Self, Self::Error> {
let s = value.try_as_str()?;
match Self::parse_from_str(s, "%F") {
Ok(slf) => Ok(slf),
Err(err) => Err(Error::Sqlite(err.to_string().into())),
}
}
}
impl TryFrom<ValueOwned> for NaiveTime {
type Error = crate::Error;
#[inline]
fn try_from(value: ValueOwned) -> Result<Self, Self::Error> {
let s = value.try_as_str()?;
let fmt = match s.len() {
5 => "%H:%M",
8 => "%T",
_ => "%T%.f",
};
match Self::parse_from_str(s, fmt) {
Ok(slf) => Ok(slf),
Err(err) => Err(Error::Sqlite(err.to_string().into())),
}
}
}
impl TryFrom<ValueOwned> for NaiveDateTime {
type Error = crate::Error;
#[inline]
fn try_from(value: ValueOwned) -> Result<Self, Self::Error> {
let s = value.try_as_str()?;
let fmt = if s.len() >= 11 && s.as_bytes()[10] == b'T' {
"%FT%T%.f"
} else {
"%F %T%.f"
};
match Self::parse_from_str(s, fmt) {
Ok(slf) => Ok(slf),
Err(err) => Err(Error::Sqlite(err.to_string().into())),
}
}
}
impl TryFrom<ValueOwned> for DateTime<Utc> {
type Error = crate::Error;
#[inline]
fn try_from(value: ValueOwned) -> Result<Self, Self::Error> {
{
let s = value.try_as_str()?;
let fmt = if s.len() >= 11 && s.as_bytes()[10] == b'T' {
"%FT%T%.f%#z"
} else {
"%F %T%.f%#z"
};
if let Ok(dt) = DateTime::parse_from_str(s, fmt) {
return Ok(dt.with_timezone(&Utc));
}
}
NaiveDateTime::try_from(value).map(|dt| Utc.from_utc_datetime(&dt))
}
}
impl TryFrom<ValueOwned> for DateTime<Local> {
type Error = crate::Error;
#[inline]
fn try_from(value: ValueOwned) -> Result<Self, Self::Error> {
let utc_dt = DateTime::<Utc>::try_from(value)?;
Ok(utc_dt.with_timezone(&Local))
}
}
impl TryFrom<ValueOwned> for DateTime<FixedOffset> {
type Error = crate::Error;
#[inline]
fn try_from(value: ValueOwned) -> Result<Self, Self::Error> {
let s = value.try_as_str()?;
Self::parse_from_rfc3339(s)
.or_else(|_| Self::parse_from_str(s, "%F %T%.f%:z"))
.map_err(|err| Error::Sqlite(err.to_string().into()))
}
}
impl TryFrom<ValueOwned> for serde_json::Value {
type Error = crate::Error;
#[inline]
fn try_from(value: ValueOwned) -> Result<Self, Self::Error> {
let slf = match value {
ValueOwned::Null => serde_json::Value::Null,
ValueOwned::Integer(i) => serde_json::Value::from(i),
ValueOwned::Real(r) => serde_json::Value::from(r),
ValueOwned::Text(s) => serde_json::from_str(s.as_str())
.map_err(|err| Error::Sqlite(err.to_string().into()))?,
ValueOwned::Blob(b) => {
serde_json::from_slice(&b).map_err(|err| Error::Sqlite(err.to_string().into()))?
}
};
Ok(slf)
}
}
impl TryFrom<ValueOwned> for Option<url::Url> {
type Error = crate::Error;
#[inline]
fn try_from(value: ValueOwned) -> Result<Self, Self::Error> {
match value {
ValueOwned::Text(s) => {
if s.is_empty() {
return Ok(None);
}
match url::Url::parse(&s) {
Ok(url) => Ok(Some(url)),
Err(_) => Err(Error::Sqlite(
"Cannot parse URL from given input: {s}".into(),
)),
}
}
_ => Err(Error::Sqlite(
"Cannot only parse URL from TEXT column".into(),
)),
}
}
}
impl TryFrom<ValueOwned> for url::Url {
type Error = crate::Error;
#[inline]
fn try_from(value: ValueOwned) -> Result<Self, Self::Error> {
match value {
ValueOwned::Text(s) => match url::Url::parse(&s) {
Ok(url) => Ok(url),
Err(_) => Err(Error::Sqlite(
"Cannot parse URL from given input: {s}".into(),
)),
},
_ => Err(Error::Sqlite(
"Cannot only parse URL from TEXT column".into(),
)),
}
}
}
impl TryFrom<ValueOwned> for Option<uuid::Uuid> {
type Error = crate::Error;
#[inline]
fn try_from(value: ValueOwned) -> Result<Self, Self::Error> {
match value {
ValueOwned::Blob(b) => {
if b.is_empty() {
return Ok(None);
}
let bytes = <[u8; 16]>::try_from(b.as_slice()).map_err(|_| {
Error::Sqlite("Invalid data in BLOB to be able to convert to UUID".into())
})?;
Ok(Some(uuid::Uuid::from_u128(u128::from_be_bytes(bytes))))
}
_ => Err(Error::Sqlite(
"Cannot only parse UUID from BLOB column".into(),
)),
}
}
}
impl TryFrom<ValueOwned> for uuid::Uuid {
type Error = crate::Error;
#[inline]
fn try_from(value: ValueOwned) -> Result<Self, Self::Error> {
match value {
ValueOwned::Blob(b) => {
let bytes = <[u8; 16]>::try_from(b.as_slice()).map_err(|_| {
Error::Sqlite("Invalid data in BLOB to be able to convert to UUID".into())
})?;
Ok(uuid::Uuid::from_u128(u128::from_be_bytes(bytes)))
}
_ => Err(Error::Sqlite(
"Cannot only parse UUID from BLOB column".into(),
)),
}
}
}