use crate::rusqlite::{
Error as RusqliteError, ErrorCode,
types::{FromSql, ToSql, Type as RusqliteType},
};
use cfg_if::cfg_if;
use std::fmt::Display;
pub type Blob = Vec<u8>;
trait Sealed {}
macro_rules! impl_sealed {
($($t:ty),*) => {
$(
impl Sealed for $t {}
)*
};
}
impl_sealed!(i32, u32, i64, String, Blob);
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
pub struct SqlType(pub RusqliteType);
impl SqlType {
#[inline(always)]
pub const fn new(value: RusqliteType) -> Self {
Self(value)
}
}
impl Ord for SqlType {
#[inline]
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
(self.0 as usize).cmp(&(other.0 as usize))
}
}
impl PartialOrd for SqlType {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Display for SqlType {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0.to_string().to_ascii_uppercase())
}
}
pub const SQL_TYPE_TEXT: SqlType = SqlType::new(RusqliteType::Text);
pub static SQL_TYPES: [SqlType; 3usize] = [SqlType::new(RusqliteType::Integer), SqlType::new(RusqliteType::Text), SqlType::new(RusqliteType::Blob)];
#[allow(private_bounds)]
pub trait Type: FromSql + ToSql + Sealed {
const SQL_TYPE: SqlType;
}
macro_rules! impl_type {
($type:ty, $sql_type:ident) => {
impl Type for $type {
const SQL_TYPE: SqlType = SqlType::new(RusqliteType::$sql_type);
}
};
}
impl_type!(i32, Integer);
impl_type!(u32, Integer);
impl_type!(i64, Integer);
impl_type!(Blob, Blob);
impl_type!(String, Text);
pub trait CharType {}
macro_rules! impl_chartype {
($($t:ty),*) => {
$(
impl CharType for $t {}
)*
};
}
impl_chartype!(String);
cfg_if! {
if #[cfg(target_pointer_width = "64")] {
pub type Size = usize;
} else {
pub type Size = u64;
}
}
pub trait SizeExt {
fn from_i64(value: i64) -> Self;
fn from_u64(value: u64) -> Self;
fn to_u64(self) -> u64;
fn to_usize(self) -> Option<usize>;
}
impl SizeExt for Size {
#[inline(always)]
fn from_i64(value: i64) -> Self {
value.try_into().unwrap_or_default()
}
#[inline(always)]
fn from_u64(value: u64) -> Self {
value as Self
}
#[allow(clippy::unnecessary_cast)]
#[inline(always)]
fn to_u64(self) -> u64 {
self as u64
}
#[inline(always)]
fn to_usize(self) -> Option<usize> {
cfg_if! {
if #[cfg(target_pointer_width = "64")] {
Some(self)
} else {
self.try_into().ok()
}
}
}
}
#[allow(dead_code)]
#[derive(Debug)]
pub struct Error(RusqliteError);
impl Error {
pub fn into_inner(self) -> RusqliteError {
self.0
}
}
impl From<RusqliteError> for Error {
#[inline]
fn from(error: RusqliteError) -> Self {
Self(error)
}
}
#[inline]
pub fn check_constraint_violation(error: RusqliteError) -> Result<bool, Error> {
match error {
RusqliteError::SqliteFailure(sql_error, _) => match sql_error.code {
ErrorCode::ConstraintViolation => Ok(false),
_ => Err(error.into()),
},
_ => Err(error.into()),
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::rusqlite::ffi::{Error as FfiError, ErrorCode as FfiErrorCode, SQLITE_CONSTRAINT, SQLITE_NOMEM};
use std::path::PathBuf;
#[cfg(target_pointer_width = "64")]
#[test]
fn test_size_p64() {
assert_eq!(Size::from_i64(i64::MAX), i64::MAX as usize);
assert_eq!(Size::from_i64(1i64), 1usize);
assert_eq!(Size::from_i64(0i64), usize::MIN);
assert_eq!(Size::from_i64(-1i64), usize::MIN);
assert_eq!(Size::from_i64(i64::MIN), usize::MIN);
assert_eq!(Size::from_u64(u64::MAX), usize::MAX);
assert_eq!(Size::from_u64(1u64), 1usize);
assert_eq!(Size::from_u64(u64::MIN), usize::MIN);
assert_eq!(Size::from_i64(i64::MAX).to_u64(), i64::MAX as u64);
assert_eq!(Size::from_i64(i64::MAX).to_usize(), Some(i64::MAX as usize));
}
#[cfg(not(target_pointer_width = "64"))]
#[test]
fn test_size_p32() {
assert_eq!(Size::from_i64(i64::MAX), i64::MAX as u64);
assert_eq!(Size::from_i64(1i64), 1u64);
assert_eq!(Size::from_i64(0i64), u64::MIN);
assert_eq!(Size::from_i64(-1i64), u64::MIN);
assert_eq!(Size::from_i64(i64::MIN), u64::MIN);
assert_eq!(Size::from_u64(u64::MAX), u64::MAX);
assert_eq!(Size::from_u64(1u64), 1u64);
assert_eq!(Size::from_u64(u64::MIN), u64::MIN);
assert_eq!(Size::from_i64(i64::MAX).to_u64(), i64::MAX as u64);
assert_eq!(Size::from_i64(usize::MAX as i64).to_usize(), Some(usize::MAX));
assert!(Size::from_i64(i64::MAX).to_usize().is_none());
}
#[test]
fn test_sql_type() {
const ALL_TYPES: [RusqliteType; 3usize] = [RusqliteType::Integer, RusqliteType::Blob, RusqliteType::Text];
for native_type in ALL_TYPES {
let sql_type = SqlType::new(native_type);
assert_eq!(sql_type.0, native_type);
for other_type in ALL_TYPES {
let other_type = SqlType::new(other_type);
assert_eq!(sql_type.partial_cmp(&other_type).unwrap(), sql_type.cmp(&other_type));
}
}
}
#[test]
fn test_error() {
let error_1 = Error::from(RusqliteError::InvalidQuery);
assert_eq!(error_1.into_inner(), RusqliteError::InvalidQuery);
let error_2 = Error::from(RusqliteError::InvalidPath(PathBuf::from("filename.txt")));
assert!(matches!(error_2.into_inner(), RusqliteError::InvalidPath(_)));
let error_3 = Error::from(RusqliteError::SqliteFailure(FfiError::new(SQLITE_NOMEM), Some("Memory allocation fail!".to_owned())));
assert!(matches!(error_3.into_inner(), RusqliteError::SqliteFailure(FfiError { code: FfiErrorCode::OutOfMemory, .. }, Some(_))));
}
#[test]
fn test_check_constraint_violation() {
let error_1 = check_constraint_violation(RusqliteError::InvalidQuery);
assert!(matches!(error_1, Err(Error(..))));
let error_2 = check_constraint_violation(RusqliteError::SqliteFailure(FfiError::new(SQLITE_CONSTRAINT), None));
assert!(matches!(error_2, Ok(false)));
let error_3 = check_constraint_violation(RusqliteError::SqliteFailure(FfiError::new(SQLITE_NOMEM), None));
assert!(matches!(error_3, Err(Error(..))));
}
}