#![cfg_attr(feature = "fail-on-warnings", deny(warnings))]
#![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]
#![allow(clippy::multiple_crate_versions)]
pub mod config;
#[cfg(feature = "duckdb")]
pub mod duckdb;
pub mod executable;
#[cfg(feature = "postgres-raw")]
pub mod postgres;
pub mod profiles;
#[cfg(feature = "sqlite-rusqlite")]
pub mod rusqlite;
#[cfg(feature = "simulator")]
pub mod simulator;
#[cfg(feature = "sqlx")]
pub mod sqlx;
#[cfg(feature = "turso")]
pub mod turso;
pub mod query;
pub mod query_transform;
pub mod sql_interval;
pub mod value_builders;
#[cfg(feature = "schema")]
pub mod schema;
use std::{num::TryFromIntError, sync::Arc};
use async_trait::async_trait;
use chrono::NaiveDateTime;
use crate::sql_interval::SqlInterval;
use query::{
DeleteStatement, InsertStatement, SelectQuery, UpdateStatement, UpsertMultiStatement,
UpsertStatement,
};
use thiserror::Error;
#[derive(Debug, Clone, PartialEq)]
pub enum DatabaseValue {
Null,
String(String),
StringOpt(Option<String>),
Bool(bool),
BoolOpt(Option<bool>),
Int8(i8),
Int8Opt(Option<i8>),
Int16(i16),
Int16Opt(Option<i16>),
Int32(i32),
Int32Opt(Option<i32>),
Int64(i64),
Int64Opt(Option<i64>),
UInt8(u8),
UInt8Opt(Option<u8>),
UInt16(u16),
UInt16Opt(Option<u16>),
UInt32(u32),
UInt32Opt(Option<u32>),
UInt64(u64),
UInt64Opt(Option<u64>),
Real64(f64),
Real64Opt(Option<f64>),
Real32(f32),
Real32Opt(Option<f32>),
#[cfg(feature = "decimal")]
Decimal(rust_decimal::Decimal),
#[cfg(feature = "decimal")]
DecimalOpt(Option<rust_decimal::Decimal>),
#[cfg(feature = "uuid")]
Uuid(uuid::Uuid),
#[cfg(feature = "uuid")]
UuidOpt(Option<uuid::Uuid>),
NowPlus(SqlInterval),
Now,
DateTime(NaiveDateTime),
}
impl DatabaseValue {
#[must_use]
#[allow(clippy::missing_const_for_fn)]
pub fn as_str(&self) -> Option<&str> {
match self {
Self::String(value) | Self::StringOpt(Some(value)) => Some(value),
_ => None,
}
}
#[must_use]
#[allow(clippy::missing_const_for_fn)]
pub fn as_i8(&self) -> Option<i8> {
match self {
Self::Int8(value) | Self::Int8Opt(Some(value)) => Some(*value),
_ => None,
}
}
#[must_use]
#[allow(clippy::missing_const_for_fn)]
pub fn as_i16(&self) -> Option<i16> {
match self {
Self::Int8(value) | Self::Int8Opt(Some(value)) => Some(i16::from(*value)),
Self::Int16(value) | Self::Int16Opt(Some(value)) => Some(*value),
_ => None,
}
}
#[must_use]
#[allow(clippy::missing_const_for_fn)]
pub fn as_i32(&self) -> Option<i32> {
match self {
Self::Int8(value) | Self::Int8Opt(Some(value)) => Some(i32::from(*value)),
Self::Int16(value) | Self::Int16Opt(Some(value)) => Some(i32::from(*value)),
Self::Int32(value) | Self::Int32Opt(Some(value)) => Some(*value),
_ => None,
}
}
#[must_use]
#[allow(clippy::missing_const_for_fn)]
pub fn as_i64(&self) -> Option<i64> {
match self {
Self::Int8(value) | Self::Int8Opt(Some(value)) => Some(i64::from(*value)),
Self::Int16(value) | Self::Int16Opt(Some(value)) => Some(i64::from(*value)),
Self::Int32(value) | Self::Int32Opt(Some(value)) => Some(i64::from(*value)),
Self::Int64(value) | Self::Int64Opt(Some(value)) => Some(*value),
_ => None,
}
}
#[must_use]
#[allow(clippy::missing_const_for_fn)]
pub fn as_u64(&self) -> Option<u64> {
match self {
Self::UInt8(value) | Self::UInt8Opt(Some(value)) => Some(u64::from(*value)),
Self::UInt16(value) | Self::UInt16Opt(Some(value)) => Some(u64::from(*value)),
Self::UInt32(value) | Self::UInt32Opt(Some(value)) => Some(u64::from(*value)),
Self::UInt64(value) | Self::UInt64Opt(Some(value)) => Some(*value),
Self::Int8(value) | Self::Int8Opt(Some(value)) => Some(
#[allow(clippy::cast_sign_loss)]
if *value >= 0 {
*value as u64
} else {
panic!("DatabaseValue::as_u64: value is negative")
},
),
Self::Int16(value) | Self::Int16Opt(Some(value)) => Some(
#[allow(clippy::cast_sign_loss)]
if *value >= 0 {
*value as u64
} else {
panic!("DatabaseValue::as_u64: value is negative")
},
),
Self::Int32(value) | Self::Int32Opt(Some(value)) => Some(
#[allow(clippy::cast_sign_loss)]
if *value >= 0 {
*value as u64
} else {
panic!("DatabaseValue::as_u64: value is negative")
},
),
Self::Int64(value) | Self::Int64Opt(Some(value)) => Some(
#[allow(clippy::cast_sign_loss)]
if *value >= 0 {
*value as u64
} else {
panic!("DatabaseValue::as_u64: value is negative")
},
),
_ => None,
}
}
#[must_use]
#[allow(clippy::missing_const_for_fn)]
pub fn as_f64(&self) -> Option<f64> {
match self {
Self::Real32(value) | Self::Real32Opt(Some(value)) => Some(f64::from(*value)),
Self::Real64(value) | Self::Real64Opt(Some(value)) => Some(*value),
_ => None,
}
}
#[must_use]
#[allow(clippy::missing_const_for_fn)]
pub fn as_f32(&self) -> Option<f32> {
match self {
Self::Real32(value) | Self::Real32Opt(Some(value)) => Some(*value),
_ => None,
}
}
#[cfg(feature = "decimal")]
#[must_use]
#[allow(clippy::missing_const_for_fn)]
pub fn as_decimal(&self) -> Option<rust_decimal::Decimal> {
match self {
Self::String(value) | Self::StringOpt(Some(value)) => {
value.parse::<rust_decimal::Decimal>().ok()
}
Self::Decimal(value) | Self::DecimalOpt(Some(value)) => Some(*value),
_ => None,
}
}
#[cfg(feature = "uuid")]
#[must_use]
#[allow(clippy::missing_const_for_fn)]
pub fn as_uuid(&self) -> Option<uuid::Uuid> {
match self {
Self::String(value) | Self::StringOpt(Some(value)) => value.parse::<uuid::Uuid>().ok(),
Self::Uuid(value) | Self::UuidOpt(Some(value)) => Some(*value),
_ => None,
}
}
#[must_use]
#[allow(clippy::missing_const_for_fn)]
pub fn as_datetime(&self) -> Option<NaiveDateTime> {
match self {
Self::DateTime(value) => Some(*value),
_ => None,
}
}
#[must_use]
#[allow(clippy::missing_const_for_fn)]
pub fn as_bool(&self) -> Option<bool> {
match self {
Self::Bool(value) | Self::BoolOpt(Some(value)) => Some(*value),
_ => None,
}
}
#[must_use]
#[allow(clippy::missing_const_for_fn)]
pub fn as_u8(&self) -> Option<u8> {
match self {
Self::UInt8(value) | Self::UInt8Opt(Some(value)) => Some(*value),
_ => None,
}
}
#[must_use]
#[allow(clippy::missing_const_for_fn)]
pub fn as_u16(&self) -> Option<u16> {
match self {
Self::UInt8(value) | Self::UInt8Opt(Some(value)) => Some(u16::from(*value)),
Self::UInt16(value) | Self::UInt16Opt(Some(value)) => Some(*value),
_ => None,
}
}
#[must_use]
#[allow(clippy::missing_const_for_fn)]
pub fn as_u32(&self) -> Option<u32> {
match self {
Self::UInt8(value) | Self::UInt8Opt(Some(value)) => Some(u32::from(*value)),
Self::UInt16(value) | Self::UInt16Opt(Some(value)) => Some(u32::from(*value)),
Self::UInt32(value) | Self::UInt32Opt(Some(value)) => Some(*value),
_ => None,
}
}
}
impl<T: Into<Self>> From<Option<T>> for DatabaseValue {
fn from(val: Option<T>) -> Self {
val.map_or(Self::Null, std::convert::Into::into)
}
}
impl From<bool> for DatabaseValue {
fn from(val: bool) -> Self {
Self::Bool(val)
}
}
impl From<&str> for DatabaseValue {
fn from(val: &str) -> Self {
Self::String(val.to_string())
}
}
impl From<&String> for DatabaseValue {
fn from(val: &String) -> Self {
Self::String(val.clone())
}
}
impl From<String> for DatabaseValue {
fn from(val: String) -> Self {
Self::String(val)
}
}
impl From<f32> for DatabaseValue {
fn from(val: f32) -> Self {
Self::Real32(val)
}
}
impl From<f64> for DatabaseValue {
fn from(val: f64) -> Self {
Self::Real64(val)
}
}
#[cfg(feature = "decimal")]
impl From<rust_decimal::Decimal> for DatabaseValue {
fn from(val: rust_decimal::Decimal) -> Self {
Self::Decimal(val)
}
}
impl From<i8> for DatabaseValue {
fn from(val: i8) -> Self {
Self::Int8(val)
}
}
#[cfg(feature = "uuid")]
impl From<uuid::Uuid> for DatabaseValue {
fn from(val: uuid::Uuid) -> Self {
Self::Uuid(val)
}
}
impl From<i16> for DatabaseValue {
fn from(val: i16) -> Self {
Self::Int16(val)
}
}
impl From<i32> for DatabaseValue {
fn from(val: i32) -> Self {
Self::Int32(val)
}
}
impl From<i64> for DatabaseValue {
fn from(val: i64) -> Self {
Self::Int64(val)
}
}
impl From<isize> for DatabaseValue {
fn from(val: isize) -> Self {
Self::Int64(val as i64)
}
}
impl From<u8> for DatabaseValue {
fn from(val: u8) -> Self {
Self::UInt8(val)
}
}
impl From<u16> for DatabaseValue {
fn from(val: u16) -> Self {
Self::UInt16(val)
}
}
impl From<u32> for DatabaseValue {
fn from(val: u32) -> Self {
Self::UInt32(val)
}
}
impl From<u64> for DatabaseValue {
fn from(val: u64) -> Self {
Self::UInt64(val)
}
}
impl From<usize> for DatabaseValue {
fn from(val: usize) -> Self {
Self::UInt64(val as u64)
}
}
pub trait AsId {
fn as_id(&self) -> DatabaseValue;
}
#[derive(Debug, Error)]
pub enum TryFromError {
#[error("Could not convert to type '{0}'")]
CouldNotConvert(String),
#[error(transparent)]
TryFromInt(#[from] TryFromIntError),
}
impl TryFrom<DatabaseValue> for u8 {
type Error = TryFromError;
fn try_from(value: DatabaseValue) -> Result<Self, Self::Error> {
match value {
DatabaseValue::UInt8(value) | DatabaseValue::UInt8Opt(Some(value)) => Ok(value),
DatabaseValue::UInt16(value) | DatabaseValue::UInt16Opt(Some(value)) => {
Ok(Self::try_from(value)?)
}
DatabaseValue::UInt32(value) | DatabaseValue::UInt32Opt(Some(value)) => {
Ok(Self::try_from(value)?)
}
DatabaseValue::UInt64(value) | DatabaseValue::UInt64Opt(Some(value)) => {
Ok(Self::try_from(value)?)
}
DatabaseValue::Int8(value) | DatabaseValue::Int8Opt(Some(value)) => {
Ok(Self::try_from(value)?)
}
DatabaseValue::Int16(value) | DatabaseValue::Int16Opt(Some(value)) => {
Ok(Self::try_from(value)?)
}
DatabaseValue::Int32(value) | DatabaseValue::Int32Opt(Some(value)) => {
Ok(Self::try_from(value)?)
}
DatabaseValue::Int64(value) | DatabaseValue::Int64Opt(Some(value)) => {
Ok(Self::try_from(value)?)
}
_ => Err(TryFromError::CouldNotConvert("u8".into())),
}
}
}
impl TryFrom<DatabaseValue> for u16 {
type Error = TryFromError;
fn try_from(value: DatabaseValue) -> Result<Self, Self::Error> {
match value {
DatabaseValue::UInt8(value) | DatabaseValue::UInt8Opt(Some(value)) => {
Ok(Self::from(value))
}
DatabaseValue::UInt16(value) | DatabaseValue::UInt16Opt(Some(value)) => Ok(value),
DatabaseValue::UInt32(value) | DatabaseValue::UInt32Opt(Some(value)) => {
Ok(Self::try_from(value)?)
}
DatabaseValue::UInt64(value) | DatabaseValue::UInt64Opt(Some(value)) => {
Ok(Self::try_from(value)?)
}
DatabaseValue::Int8(value) | DatabaseValue::Int8Opt(Some(value)) => {
Ok(Self::try_from(value)?)
}
DatabaseValue::Int16(value) | DatabaseValue::Int16Opt(Some(value)) => {
Ok(Self::try_from(value)?)
}
DatabaseValue::Int32(value) | DatabaseValue::Int32Opt(Some(value)) => {
Ok(Self::try_from(value)?)
}
DatabaseValue::Int64(value) | DatabaseValue::Int64Opt(Some(value)) => {
Ok(Self::try_from(value)?)
}
_ => Err(TryFromError::CouldNotConvert("u16".into())),
}
}
}
impl TryFrom<DatabaseValue> for u32 {
type Error = TryFromError;
fn try_from(value: DatabaseValue) -> Result<Self, Self::Error> {
match value {
DatabaseValue::UInt8(value) | DatabaseValue::UInt8Opt(Some(value)) => {
Ok(Self::from(value))
}
DatabaseValue::UInt16(value) | DatabaseValue::UInt16Opt(Some(value)) => {
Ok(Self::from(value))
}
DatabaseValue::UInt32(value) | DatabaseValue::UInt32Opt(Some(value)) => Ok(value),
DatabaseValue::UInt64(value) | DatabaseValue::UInt64Opt(Some(value)) => {
Ok(Self::try_from(value)?)
}
DatabaseValue::Int8(value) | DatabaseValue::Int8Opt(Some(value)) => {
Ok(Self::try_from(value)?)
}
DatabaseValue::Int16(value) | DatabaseValue::Int16Opt(Some(value)) => {
Ok(Self::try_from(value)?)
}
DatabaseValue::Int32(value) | DatabaseValue::Int32Opt(Some(value)) => {
Ok(Self::try_from(value)?)
}
DatabaseValue::Int64(value) | DatabaseValue::Int64Opt(Some(value)) => {
Ok(Self::try_from(value)?)
}
_ => Err(TryFromError::CouldNotConvert("u32".into())),
}
}
}
impl TryFrom<DatabaseValue> for u64 {
type Error = TryFromError;
fn try_from(value: DatabaseValue) -> Result<Self, Self::Error> {
match value {
DatabaseValue::UInt8(value) | DatabaseValue::UInt8Opt(Some(value)) => {
Ok(Self::from(value))
}
DatabaseValue::UInt16(value) | DatabaseValue::UInt16Opt(Some(value)) => {
Ok(Self::from(value))
}
DatabaseValue::UInt32(value) | DatabaseValue::UInt32Opt(Some(value)) => {
Ok(Self::from(value))
}
DatabaseValue::UInt64(value) | DatabaseValue::UInt64Opt(Some(value)) => Ok(value),
DatabaseValue::Int8(value) | DatabaseValue::Int8Opt(Some(value)) => {
Ok(Self::try_from(value)?)
}
DatabaseValue::Int16(value) | DatabaseValue::Int16Opt(Some(value)) => {
Ok(Self::try_from(value)?)
}
DatabaseValue::Int32(value) | DatabaseValue::Int32Opt(Some(value)) => {
Ok(Self::try_from(value)?)
}
DatabaseValue::Int64(value) | DatabaseValue::Int64Opt(Some(value)) => {
Ok(Self::try_from(value)?)
}
_ => Err(TryFromError::CouldNotConvert("u64".into())),
}
}
}
impl TryFrom<DatabaseValue> for i64 {
type Error = TryFromError;
fn try_from(value: DatabaseValue) -> Result<Self, Self::Error> {
match value {
DatabaseValue::Int8(value) | DatabaseValue::Int8Opt(Some(value)) => {
Ok(Self::from(value))
}
DatabaseValue::Int16(value) | DatabaseValue::Int16Opt(Some(value)) => {
Ok(Self::from(value))
}
DatabaseValue::Int32(value) | DatabaseValue::Int32Opt(Some(value)) => {
Ok(Self::from(value))
}
DatabaseValue::Int64(value) | DatabaseValue::Int64Opt(Some(value)) => Ok(value),
DatabaseValue::UInt64(value) | DatabaseValue::UInt64Opt(Some(value)) => {
Ok(Self::try_from(value)?)
}
_ => Err(TryFromError::CouldNotConvert("i64".into())),
}
}
}
impl TryFrom<DatabaseValue> for i8 {
type Error = TryFromError;
fn try_from(value: DatabaseValue) -> Result<Self, Self::Error> {
match value {
DatabaseValue::Int8(value) | DatabaseValue::Int8Opt(Some(value)) => Ok(value),
DatabaseValue::Int16(value) | DatabaseValue::Int16Opt(Some(value)) => {
Ok(Self::try_from(value)?)
}
DatabaseValue::Int32(value) | DatabaseValue::Int32Opt(Some(value)) => {
Ok(Self::try_from(value)?)
}
DatabaseValue::Int64(value) | DatabaseValue::Int64Opt(Some(value)) => {
Ok(Self::try_from(value)?)
}
DatabaseValue::UInt64(value) | DatabaseValue::UInt64Opt(Some(value)) => {
Ok(Self::try_from(value)?)
}
_ => Err(TryFromError::CouldNotConvert("i8".into())),
}
}
}
impl TryFrom<DatabaseValue> for i16 {
type Error = TryFromError;
fn try_from(value: DatabaseValue) -> Result<Self, Self::Error> {
match value {
DatabaseValue::Int8(value) | DatabaseValue::Int8Opt(Some(value)) => {
Ok(Self::from(value))
}
DatabaseValue::Int16(value) | DatabaseValue::Int16Opt(Some(value)) => Ok(value),
DatabaseValue::Int32(value) | DatabaseValue::Int32Opt(Some(value)) => {
Ok(Self::try_from(value)?)
}
DatabaseValue::Int64(value) | DatabaseValue::Int64Opt(Some(value)) => {
Ok(Self::try_from(value)?)
}
DatabaseValue::UInt64(value) | DatabaseValue::UInt64Opt(Some(value)) => {
Ok(Self::try_from(value)?)
}
_ => Err(TryFromError::CouldNotConvert("i16".into())),
}
}
}
impl TryFrom<DatabaseValue> for i32 {
type Error = TryFromError;
fn try_from(value: DatabaseValue) -> Result<Self, Self::Error> {
match value {
DatabaseValue::Int8(value) | DatabaseValue::Int8Opt(Some(value)) => {
Ok(Self::from(value))
}
DatabaseValue::Int16(value) | DatabaseValue::Int16Opt(Some(value)) => {
Ok(Self::from(value))
}
DatabaseValue::Int32(value) | DatabaseValue::Int32Opt(Some(value)) => Ok(value),
DatabaseValue::Int64(value) | DatabaseValue::Int64Opt(Some(value)) => {
Ok(Self::try_from(value)?)
}
DatabaseValue::UInt64(value) | DatabaseValue::UInt64Opt(Some(value)) => {
Ok(Self::try_from(value)?)
}
_ => Err(TryFromError::CouldNotConvert("i32".into())),
}
}
}
impl TryFrom<DatabaseValue> for f32 {
type Error = TryFromError;
fn try_from(value: DatabaseValue) -> Result<Self, Self::Error> {
match value {
DatabaseValue::Real32(value) | DatabaseValue::Real32Opt(Some(value)) => Ok(value),
#[allow(clippy::cast_possible_truncation)]
DatabaseValue::Real64(value) | DatabaseValue::Real64Opt(Some(value)) => {
Ok(value as Self)
}
_ => Err(TryFromError::CouldNotConvert("f32".into())),
}
}
}
impl TryFrom<DatabaseValue> for f64 {
type Error = TryFromError;
fn try_from(value: DatabaseValue) -> Result<Self, Self::Error> {
match value {
DatabaseValue::Real64(value) | DatabaseValue::Real64Opt(Some(value)) => Ok(value),
DatabaseValue::Real32(value) | DatabaseValue::Real32Opt(Some(value)) => {
Ok(Self::from(value))
}
_ => Err(TryFromError::CouldNotConvert("f64".into())),
}
}
}
#[cfg(feature = "decimal")]
impl TryFrom<DatabaseValue> for rust_decimal::Decimal {
type Error = TryFromError;
fn try_from(value: DatabaseValue) -> Result<Self, Self::Error> {
match value {
DatabaseValue::Decimal(value) | DatabaseValue::DecimalOpt(Some(value)) => Ok(value),
_ => Err(TryFromError::CouldNotConvert("Decimal".into())),
}
}
}
#[cfg(feature = "uuid")]
impl TryFrom<DatabaseValue> for uuid::Uuid {
type Error = TryFromError;
fn try_from(value: DatabaseValue) -> Result<Self, Self::Error> {
match value {
DatabaseValue::Uuid(value) | DatabaseValue::UuidOpt(Some(value)) => Ok(value),
_ => Err(TryFromError::CouldNotConvert("Uuid".into())),
}
}
}
#[derive(Debug, Error)]
pub enum DatabaseError {
#[cfg(feature = "duckdb")]
#[error(transparent)]
DuckDb(duckdb::DuckDbDatabaseError),
#[cfg(feature = "sqlite-rusqlite")]
#[error(transparent)]
Rusqlite(rusqlite::RusqliteDatabaseError),
#[cfg(feature = "mysql-sqlx")]
#[error(transparent)]
MysqlSqlx(sqlx::mysql::SqlxDatabaseError),
#[cfg(feature = "sqlite-sqlx")]
#[error(transparent)]
SqliteSqlx(sqlx::sqlite::SqlxDatabaseError),
#[cfg(feature = "postgres-raw")]
#[error(transparent)]
Postgres(postgres::postgres::PostgresDatabaseError),
#[cfg(feature = "postgres-sqlx")]
#[error(transparent)]
PostgresSqlx(sqlx::postgres::SqlxDatabaseError),
#[cfg(feature = "turso")]
#[error(transparent)]
Turso(#[from] turso::TursoDatabaseError),
#[error("No row")]
NoRow,
#[cfg(feature = "schema")]
#[error("Invalid schema: {0}")]
InvalidSchema(String),
#[error("Already in transaction - nested transactions not supported")]
AlreadyInTransaction,
#[error("Transaction already committed")]
TransactionCommitted,
#[error("Transaction already rolled back")]
TransactionRolledBack,
#[error("Transaction failed to start")]
TransactionFailed,
#[error("Unexpected result from operation")]
UnexpectedResult,
#[error("Unsupported data type: {0}")]
UnsupportedDataType(String),
#[error("Invalid savepoint name: {0}")]
InvalidSavepointName(String),
#[error("Savepoint already exists: {0}")]
SavepointExists(String),
#[error("Savepoint not found: {0}")]
SavepointNotFound(String),
#[error("Query failed: {0}")]
QueryFailed(String),
#[error("Foreign key violation: {0}")]
ForeignKeyViolation(String),
#[error("Invalid query: {0}")]
InvalidQuery(String),
#[error("Unsupported operation: {0}")]
UnsupportedOperation(String),
#[error("UInt8 overflow: value {0} exceeds i8::MAX (127) for this database backend")]
UInt8Overflow(u8),
#[error("UInt16 overflow: value {0} exceeds i16::MAX (32767) for this database backend")]
UInt16Overflow(u16),
#[error("UInt32 overflow: value {0} exceeds i32::MAX (2147483647) for this database backend")]
UInt32Overflow(u32),
}
impl DatabaseError {
#[must_use]
#[allow(clippy::missing_const_for_fn)]
pub fn is_connection_error(&self) -> bool {
match &self {
#[cfg(feature = "postgres-sqlx")]
Self::PostgresSqlx(sqlx::postgres::SqlxDatabaseError::Sqlx(::sqlx::Error::Io(
_io_err,
))) => true,
#[cfg(feature = "mysql-sqlx")]
Self::MysqlSqlx(sqlx::mysql::SqlxDatabaseError::Sqlx(::sqlx::Error::Io(_io_err))) => {
true
}
#[cfg(feature = "sqlite-sqlx")]
Self::SqliteSqlx(sqlx::sqlite::SqlxDatabaseError::Sqlx(::sqlx::Error::Io(_io_err))) => {
true
}
#[cfg(feature = "postgres-raw")]
Self::Postgres(postgres::postgres::PostgresDatabaseError::Postgres(pg_err)) => {
pg_err.to_string().as_str() == "connection closed"
}
#[cfg(feature = "sqlite-rusqlite")]
Self::Rusqlite(rusqlite::RusqliteDatabaseError::Rusqlite(
::rusqlite::Error::SqliteFailure(_, _),
)) => true,
_ => false,
}
}
}
#[allow(unused)]
pub(crate) fn validate_savepoint_name(name: &str) -> Result<(), DatabaseError> {
if name.is_empty() {
return Err(DatabaseError::InvalidSavepointName(
"Savepoint name cannot be empty".to_string(),
));
}
if !name.chars().all(|c| c.is_alphanumeric() || c == '_') {
return Err(DatabaseError::InvalidSavepointName(format!(
"Savepoint name '{name}' contains invalid characters"
)));
}
if name.chars().next().is_some_and(char::is_numeric) {
return Err(DatabaseError::InvalidSavepointName(format!(
"Savepoint name '{name}' cannot start with a number"
)));
}
Ok(())
}
#[derive(Debug, Clone, PartialEq)]
pub struct Row {
pub columns: Vec<(String, DatabaseValue)>,
}
impl Row {
#[must_use]
pub fn get(&self, column_name: &str) -> Option<DatabaseValue> {
self.columns
.iter()
.find(|c| c.0 == column_name)
.map(|c| c.1.clone())
}
#[must_use]
pub fn id(&self) -> Option<DatabaseValue> {
self.get("id")
}
}
#[async_trait]
pub trait Database: Send + Sync + std::fmt::Debug {
fn select<'a>(&self, table_name: &'a str) -> SelectQuery<'a> {
query::select(table_name)
}
fn update<'a>(&self, table_name: &'a str) -> UpdateStatement<'a> {
query::update(table_name)
}
fn insert<'a>(&self, table_name: &'a str) -> InsertStatement<'a> {
query::insert(table_name)
}
fn upsert<'a>(&self, table_name: &'a str) -> UpsertStatement<'a> {
query::upsert(table_name)
}
fn upsert_first<'a>(&self, table_name: &'a str) -> UpsertStatement<'a> {
query::upsert(table_name)
}
fn upsert_multi<'a>(&self, table_name: &'a str) -> UpsertMultiStatement<'a> {
query::upsert_multi(table_name)
}
fn delete<'a>(&self, table_name: &'a str) -> DeleteStatement<'a> {
query::delete(table_name)
}
#[cfg(feature = "schema")]
fn create_table<'a>(&self, table_name: &'a str) -> schema::CreateTableStatement<'a> {
schema::create_table(table_name)
}
#[cfg(feature = "schema")]
fn drop_table<'a>(&self, table_name: &'a str) -> schema::DropTableStatement<'a> {
schema::drop_table(table_name)
}
#[cfg(feature = "schema")]
fn create_index<'a>(&self, index_name: &'a str) -> schema::CreateIndexStatement<'a> {
schema::create_index(index_name)
}
#[cfg(feature = "schema")]
fn drop_index<'a>(
&self,
index_name: &'a str,
table_name: &'a str,
) -> schema::DropIndexStatement<'a> {
schema::drop_index(index_name, table_name)
}
#[cfg(feature = "schema")]
fn alter_table<'a>(&self, table_name: &'a str) -> schema::AlterTableStatement<'a> {
schema::alter_table(table_name)
}
async fn query(&self, query: &SelectQuery<'_>) -> Result<Vec<Row>, DatabaseError>;
async fn query_first(&self, query: &SelectQuery<'_>) -> Result<Option<Row>, DatabaseError>;
async fn exec_update(&self, statement: &UpdateStatement<'_>)
-> Result<Vec<Row>, DatabaseError>;
async fn exec_update_first(
&self,
statement: &UpdateStatement<'_>,
) -> Result<Option<Row>, DatabaseError>;
async fn exec_insert(&self, statement: &InsertStatement<'_>) -> Result<Row, DatabaseError>;
async fn exec_upsert(&self, statement: &UpsertStatement<'_>)
-> Result<Vec<Row>, DatabaseError>;
async fn exec_upsert_first(
&self,
statement: &UpsertStatement<'_>,
) -> Result<Row, DatabaseError>;
async fn exec_upsert_multi(
&self,
statement: &UpsertMultiStatement<'_>,
) -> Result<Vec<Row>, DatabaseError>;
async fn exec_delete(&self, statement: &DeleteStatement<'_>)
-> Result<Vec<Row>, DatabaseError>;
async fn exec_delete_first(
&self,
statement: &DeleteStatement<'_>,
) -> Result<Option<Row>, DatabaseError>;
async fn exec_raw(&self, statement: &str) -> Result<(), DatabaseError>;
fn trigger_close(&self) -> Result<(), DatabaseError> {
Ok(())
}
async fn close(&self) -> Result<(), DatabaseError> {
self.trigger_close()
}
#[cfg(feature = "schema")]
async fn exec_create_table(
&self,
statement: &schema::CreateTableStatement<'_>,
) -> Result<(), DatabaseError>;
#[cfg(feature = "schema")]
async fn exec_drop_table(
&self,
statement: &schema::DropTableStatement<'_>,
) -> Result<(), DatabaseError>;
#[cfg(feature = "schema")]
async fn exec_create_index(
&self,
statement: &schema::CreateIndexStatement<'_>,
) -> Result<(), DatabaseError>;
#[cfg(feature = "schema")]
async fn exec_drop_index(
&self,
statement: &schema::DropIndexStatement<'_>,
) -> Result<(), DatabaseError>;
#[cfg(feature = "schema")]
async fn exec_alter_table(
&self,
statement: &schema::AlterTableStatement<'_>,
) -> Result<(), DatabaseError>;
#[cfg(feature = "schema")]
async fn table_exists(&self, table_name: &str) -> Result<bool, DatabaseError>;
#[cfg(feature = "schema")]
async fn list_tables(&self) -> Result<Vec<String>, DatabaseError>;
#[cfg(feature = "schema")]
async fn get_table_info(
&self,
table_name: &str,
) -> Result<Option<schema::TableInfo>, DatabaseError>;
#[cfg(feature = "schema")]
async fn get_table_columns(
&self,
table_name: &str,
) -> Result<Vec<schema::ColumnInfo>, DatabaseError>;
#[cfg(feature = "schema")]
async fn column_exists(
&self,
table_name: &str,
column_name: &str,
) -> Result<bool, DatabaseError>;
async fn query_raw(&self, query: &str) -> Result<Vec<Row>, DatabaseError>;
async fn exec_raw_params(
&self,
_query: &str,
_params: &[DatabaseValue],
) -> Result<u64, DatabaseError> {
Err(DatabaseError::UnsupportedOperation(
"exec_raw_params not implemented for this backend".to_string(),
))
}
async fn query_raw_params(
&self,
_query: &str,
_params: &[DatabaseValue],
) -> Result<Vec<Row>, DatabaseError> {
Err(DatabaseError::UnsupportedOperation(
"query_raw_params not implemented for this backend".to_string(),
))
}
async fn begin_transaction(&self) -> Result<Box<dyn DatabaseTransaction>, DatabaseError>;
async fn clear_connection_cache(&self) {}
}
#[async_trait]
pub trait Savepoint: Send + Sync {
async fn release(self: Box<Self>) -> Result<(), DatabaseError>;
async fn rollback_to(self: Box<Self>) -> Result<(), DatabaseError>;
fn name(&self) -> &str;
}
#[async_trait]
pub trait DatabaseTransaction: Database + Send + Sync {
async fn commit(self: Box<Self>) -> Result<(), DatabaseError>;
async fn rollback(self: Box<Self>) -> Result<(), DatabaseError>;
async fn savepoint(&self, name: &str) -> Result<Box<dyn Savepoint>, DatabaseError>;
#[cfg(feature = "cascade")]
async fn find_cascade_targets(
&self,
table_name: &str,
) -> Result<crate::schema::DropPlan, DatabaseError>;
#[cfg(feature = "cascade")]
async fn has_any_dependents(&self, table_name: &str) -> Result<bool, DatabaseError>;
#[cfg(feature = "cascade")]
async fn get_direct_dependents(
&self,
table_name: &str,
) -> Result<std::collections::BTreeSet<String>, DatabaseError>;
}
#[async_trait]
pub trait TryFromDb<T>
where
Self: Sized,
{
type Error;
async fn try_from_db(value: T, db: Arc<Box<dyn Database>>) -> Result<Self, Self::Error>;
}
#[async_trait]
impl<T, U: Send + 'static> TryFromDb<Vec<U>> for Vec<T>
where
T: TryFromDb<U> + Send,
{
type Error = T::Error;
async fn try_from_db(value: Vec<U>, db: Arc<Box<dyn Database>>) -> Result<Self, T::Error> {
let mut converted = Self::with_capacity(value.len());
for x in value {
converted.push(T::try_from_db(x, db.clone()).await?);
}
Ok(converted)
}
}
#[async_trait]
impl<T, U: Send + 'static> TryFromDb<Option<U>> for Option<T>
where
T: TryFromDb<U>,
{
type Error = T::Error;
async fn try_from_db(value: Option<U>, db: Arc<Box<dyn Database>>) -> Result<Self, T::Error> {
Ok(match value {
Some(x) => Some(T::try_from_db(x, db).await?),
None => None,
})
}
}
#[async_trait]
pub trait TryIntoDb<T>
where
Self: Sized,
{
type Error;
async fn try_into_db(self, db: Arc<Box<dyn Database>>) -> Result<T, Self::Error>;
}
#[async_trait]
impl<T: Send, U> TryIntoDb<U> for T
where
U: TryFromDb<T>,
{
type Error = U::Error;
async fn try_into_db(self, db: Arc<Box<dyn Database>>) -> Result<U, U::Error> {
U::try_from_db(self, db).await
}
}
pub use executable::Executable;
#[cfg(test)]
mod tests {
use super::*;
mod validate_savepoint_name_tests {
use super::*;
#[test_log::test]
fn test_empty_name_is_rejected() {
let result = validate_savepoint_name("");
assert!(result.is_err());
match result {
Err(DatabaseError::InvalidSavepointName(msg)) => {
assert!(msg.contains("empty"));
}
_ => panic!("Expected InvalidSavepointName error"),
}
}
#[test_log::test]
fn test_valid_alphanumeric_name() {
assert!(validate_savepoint_name("sp1").is_ok());
assert!(validate_savepoint_name("savepoint").is_ok());
assert!(validate_savepoint_name("my_savepoint").is_ok());
assert!(validate_savepoint_name("SP_123").is_ok());
}
#[test_log::test]
fn test_invalid_characters_rejected() {
let result = validate_savepoint_name("save-point");
assert!(result.is_err());
match result {
Err(DatabaseError::InvalidSavepointName(msg)) => {
assert!(msg.contains("invalid characters"));
}
_ => panic!("Expected InvalidSavepointName error"),
}
assert!(validate_savepoint_name("save point").is_err());
assert!(validate_savepoint_name("save.point").is_err());
assert!(validate_savepoint_name("save@point").is_err());
}
#[test_log::test]
fn test_name_starting_with_number_rejected() {
let result = validate_savepoint_name("1savepoint");
assert!(result.is_err());
match result {
Err(DatabaseError::InvalidSavepointName(msg)) => {
assert!(msg.contains("cannot start with a number"));
}
_ => panic!("Expected InvalidSavepointName error"),
}
assert!(validate_savepoint_name("123").is_err());
}
#[test_log::test]
fn test_underscore_allowed_at_start() {
assert!(validate_savepoint_name("_savepoint").is_ok());
assert!(validate_savepoint_name("__sp").is_ok());
}
}
mod row_tests {
use super::*;
fn create_test_row() -> Row {
Row {
columns: vec![
("id".to_string(), DatabaseValue::Int64(42)),
(
"name".to_string(),
DatabaseValue::String("test".to_string()),
),
("count".to_string(), DatabaseValue::Int32Opt(Some(100))),
],
}
}
#[test_log::test]
fn test_get_existing_column() {
let row = create_test_row();
let value = row.get("name");
assert!(value.is_some());
assert_eq!(value.unwrap().as_str(), Some("test"));
}
#[test_log::test]
fn test_get_nonexistent_column() {
let row = create_test_row();
assert!(row.get("nonexistent").is_none());
}
#[test_log::test]
fn test_id_convenience_method() {
let row = create_test_row();
let id = row.id();
assert!(id.is_some());
assert_eq!(id.unwrap().as_i64(), Some(42));
}
#[test_log::test]
fn test_id_returns_none_when_no_id_column() {
let row = Row {
columns: vec![(
"other".to_string(),
DatabaseValue::String("value".to_string()),
)],
};
assert!(row.id().is_none());
}
}
mod database_value_tests {
use super::*;
use crate::query::Expression;
#[test_log::test]
fn test_is_null_for_null() {
let val = DatabaseValue::Null;
assert!(val.is_null());
}
#[test_log::test]
fn test_is_null_for_opt_none_variants() {
assert!(DatabaseValue::BoolOpt(None).is_null());
assert!(DatabaseValue::Real64Opt(None).is_null());
assert!(DatabaseValue::Real32Opt(None).is_null());
assert!(DatabaseValue::StringOpt(None).is_null());
assert!(DatabaseValue::Int64Opt(None).is_null());
assert!(DatabaseValue::UInt64Opt(None).is_null());
}
#[test_log::test]
fn test_is_null_for_opt_some_variants() {
assert!(!DatabaseValue::BoolOpt(Some(true)).is_null());
assert!(!DatabaseValue::Real64Opt(Some(1.0)).is_null());
assert!(!DatabaseValue::StringOpt(Some("test".to_string())).is_null());
assert!(!DatabaseValue::Int64Opt(Some(42)).is_null());
}
#[test_log::test]
fn test_is_null_for_non_null_values() {
assert!(!DatabaseValue::Bool(true).is_null());
assert!(!DatabaseValue::String("test".to_string()).is_null());
assert!(!DatabaseValue::Int64(42).is_null());
assert!(!DatabaseValue::Now.is_null());
}
#[test_log::test]
fn test_as_str_for_string_variants() {
assert_eq!(
DatabaseValue::String("hello".to_string()).as_str(),
Some("hello")
);
assert_eq!(
DatabaseValue::StringOpt(Some("world".to_string())).as_str(),
Some("world")
);
assert_eq!(DatabaseValue::StringOpt(None).as_str(), None);
}
#[test_log::test]
fn test_as_str_for_non_string_values() {
assert_eq!(DatabaseValue::Int64(42).as_str(), None);
assert_eq!(DatabaseValue::Bool(true).as_str(), None);
assert_eq!(DatabaseValue::Null.as_str(), None);
}
#[test_log::test]
fn test_as_i64_coercion() {
assert_eq!(DatabaseValue::Int64(100).as_i64(), Some(100));
assert_eq!(DatabaseValue::Int64Opt(Some(200)).as_i64(), Some(200));
assert_eq!(DatabaseValue::Int8(10).as_i64(), Some(10));
assert_eq!(DatabaseValue::Int16(1000).as_i64(), Some(1000));
assert_eq!(DatabaseValue::Int32(100_000).as_i64(), Some(100_000));
assert_eq!(DatabaseValue::String("test".to_string()).as_i64(), None);
assert_eq!(DatabaseValue::Real64(1.5).as_i64(), None);
}
#[test_log::test]
fn test_as_u64_coercion_from_unsigned() {
assert_eq!(DatabaseValue::UInt8(10).as_u64(), Some(10));
assert_eq!(DatabaseValue::UInt16(1000).as_u64(), Some(1000));
assert_eq!(DatabaseValue::UInt32(100_000).as_u64(), Some(100_000));
assert_eq!(DatabaseValue::UInt64(1_000_000).as_u64(), Some(1_000_000));
}
#[test_log::test]
fn test_as_u64_coercion_from_positive_signed() {
assert_eq!(DatabaseValue::Int8(10).as_u64(), Some(10));
assert_eq!(DatabaseValue::Int16(1000).as_u64(), Some(1000));
assert_eq!(DatabaseValue::Int32(100_000).as_u64(), Some(100_000));
assert_eq!(DatabaseValue::Int64(1_000_000).as_u64(), Some(1_000_000));
}
#[test_log::test]
#[should_panic(expected = "value is negative")]
fn test_as_u64_panics_on_negative_i8() {
let _ = DatabaseValue::Int8(-1).as_u64();
}
#[test_log::test]
#[should_panic(expected = "value is negative")]
fn test_as_u64_panics_on_negative_i64() {
let _ = DatabaseValue::Int64(-100).as_u64();
}
#[test_log::test]
fn test_as_f64_coercion() {
assert_eq!(DatabaseValue::Real64(1.5).as_f64(), Some(1.5));
assert_eq!(DatabaseValue::Real64Opt(Some(2.5)).as_f64(), Some(2.5));
let f32_val = DatabaseValue::Real32(1.5f32);
assert!(f32_val.as_f64().is_some());
assert!((f32_val.as_f64().unwrap() - 1.5).abs() < 0.0001);
}
#[test_log::test]
fn test_as_bool() {
assert_eq!(DatabaseValue::Bool(true).as_bool(), Some(true));
assert_eq!(DatabaseValue::Bool(false).as_bool(), Some(false));
assert_eq!(DatabaseValue::BoolOpt(Some(true)).as_bool(), Some(true));
assert_eq!(DatabaseValue::BoolOpt(None).as_bool(), None);
assert_eq!(DatabaseValue::Int64(1).as_bool(), None);
}
#[test_log::test]
fn test_as_datetime() {
use chrono::NaiveDate;
let dt = NaiveDate::from_ymd_opt(2024, 1, 15)
.unwrap()
.and_hms_opt(10, 30, 0)
.unwrap();
let val = DatabaseValue::DateTime(dt);
assert_eq!(val.as_datetime(), Some(dt));
assert_eq!(
DatabaseValue::String("2024-01-15".to_string()).as_datetime(),
None
);
}
#[test_log::test]
fn test_as_i8_extraction() {
assert_eq!(DatabaseValue::Int8(42).as_i8(), Some(42));
assert_eq!(DatabaseValue::Int8(-100).as_i8(), Some(-100));
assert_eq!(DatabaseValue::Int8Opt(Some(10)).as_i8(), Some(10));
assert_eq!(DatabaseValue::Int8Opt(None).as_i8(), None);
assert_eq!(DatabaseValue::Int16(100).as_i8(), None);
assert_eq!(DatabaseValue::Int32(100).as_i8(), None);
assert_eq!(DatabaseValue::String("test".to_string()).as_i8(), None);
}
#[test_log::test]
fn test_as_i16_coercion() {
assert_eq!(DatabaseValue::Int16(1000).as_i16(), Some(1000));
assert_eq!(DatabaseValue::Int16(-500).as_i16(), Some(-500));
assert_eq!(DatabaseValue::Int16Opt(Some(200)).as_i16(), Some(200));
assert_eq!(DatabaseValue::Int16Opt(None).as_i16(), None);
assert_eq!(DatabaseValue::Int8(50).as_i16(), Some(50));
assert_eq!(DatabaseValue::Int8(-50).as_i16(), Some(-50));
assert_eq!(DatabaseValue::Int8Opt(Some(10)).as_i16(), Some(10));
assert_eq!(DatabaseValue::Int32(100).as_i16(), None);
assert_eq!(DatabaseValue::String("test".to_string()).as_i16(), None);
}
#[test_log::test]
fn test_as_i32_coercion() {
assert_eq!(DatabaseValue::Int32(50_000).as_i32(), Some(50_000));
assert_eq!(DatabaseValue::Int32(-25_000).as_i32(), Some(-25_000));
assert_eq!(DatabaseValue::Int32Opt(Some(123)).as_i32(), Some(123));
assert_eq!(DatabaseValue::Int32Opt(None).as_i32(), None);
assert_eq!(DatabaseValue::Int8(100).as_i32(), Some(100));
assert_eq!(DatabaseValue::Int8Opt(Some(-50)).as_i32(), Some(-50));
assert_eq!(DatabaseValue::Int16(5000).as_i32(), Some(5000));
assert_eq!(DatabaseValue::Int16Opt(Some(-1000)).as_i32(), Some(-1000));
assert_eq!(DatabaseValue::Int64(100).as_i32(), None);
assert_eq!(DatabaseValue::Real64(1.5).as_i32(), None);
}
#[test_log::test]
fn test_as_u8_extraction() {
assert_eq!(DatabaseValue::UInt8(200).as_u8(), Some(200));
assert_eq!(DatabaseValue::UInt8Opt(Some(50)).as_u8(), Some(50));
assert_eq!(DatabaseValue::UInt8Opt(None).as_u8(), None);
assert_eq!(DatabaseValue::UInt16(100).as_u8(), None);
assert_eq!(DatabaseValue::UInt32(100).as_u8(), None);
assert_eq!(DatabaseValue::Int8(10).as_u8(), None);
}
#[test_log::test]
fn test_as_u16_coercion() {
assert_eq!(DatabaseValue::UInt16(50_000).as_u16(), Some(50_000));
assert_eq!(DatabaseValue::UInt16Opt(Some(1000)).as_u16(), Some(1000));
assert_eq!(DatabaseValue::UInt16Opt(None).as_u16(), None);
assert_eq!(DatabaseValue::UInt8(200).as_u16(), Some(200));
assert_eq!(DatabaseValue::UInt8Opt(Some(100)).as_u16(), Some(100));
assert_eq!(DatabaseValue::UInt32(100).as_u16(), None);
assert_eq!(DatabaseValue::Int16(100).as_u16(), None);
}
#[test_log::test]
fn test_as_u32_coercion() {
assert_eq!(DatabaseValue::UInt32(1_000_000).as_u32(), Some(1_000_000));
assert_eq!(DatabaseValue::UInt32Opt(Some(500)).as_u32(), Some(500));
assert_eq!(DatabaseValue::UInt32Opt(None).as_u32(), None);
assert_eq!(DatabaseValue::UInt8(255).as_u32(), Some(255));
assert_eq!(DatabaseValue::UInt8Opt(Some(100)).as_u32(), Some(100));
assert_eq!(DatabaseValue::UInt16(60_000).as_u32(), Some(60_000));
assert_eq!(DatabaseValue::UInt16Opt(Some(5000)).as_u32(), Some(5000));
assert_eq!(DatabaseValue::UInt64(100).as_u32(), None);
assert_eq!(DatabaseValue::Int32(100).as_u32(), None);
}
#[test_log::test]
fn test_as_f32_extraction() {
let val = DatabaseValue::Real32(12.375f32);
let result = val.as_f32().unwrap();
assert!((result - 12.375).abs() < 0.001);
assert_eq!(
DatabaseValue::Real32Opt(Some(2.5f32)).as_f32(),
Some(2.5f32)
);
assert_eq!(DatabaseValue::Real32Opt(None).as_f32(), None);
assert_eq!(DatabaseValue::Real64(1.5).as_f32(), None);
assert_eq!(DatabaseValue::Int32(100).as_f32(), None);
assert_eq!(DatabaseValue::String("12.375".to_string()).as_f32(), None);
}
#[test_log::test]
fn test_is_null_for_real32_opt_variants() {
assert!(DatabaseValue::Real32Opt(None).is_null());
assert!(!DatabaseValue::Real32Opt(Some(1.5f32)).is_null());
}
}
mod try_from_database_value_tests {
use super::*;
#[test_log::test]
fn test_try_from_u8_overflow_from_u16() {
let val = DatabaseValue::UInt16(300);
let result: Result<u8, _> = val.try_into();
assert!(matches!(result, Err(TryFromError::TryFromInt(_))));
}
#[test_log::test]
fn test_try_from_u8_overflow_from_u32() {
let val = DatabaseValue::UInt32(1000);
let result: Result<u8, _> = val.try_into();
assert!(matches!(result, Err(TryFromError::TryFromInt(_))));
}
#[test_log::test]
fn test_try_from_u8_overflow_from_u64() {
let val = DatabaseValue::UInt64(1_000_000);
let result: Result<u8, _> = val.try_into();
assert!(matches!(result, Err(TryFromError::TryFromInt(_))));
}
#[test_log::test]
fn test_try_from_u8_negative_i8_fails() {
let val = DatabaseValue::Int8(-1);
let result: Result<u8, _> = val.try_into();
assert!(matches!(result, Err(TryFromError::TryFromInt(_))));
}
#[test_log::test]
fn test_try_from_u8_negative_i64_fails() {
let val = DatabaseValue::Int64(-100);
let result: Result<u8, _> = val.try_into();
assert!(matches!(result, Err(TryFromError::TryFromInt(_))));
}
#[test_log::test]
fn test_try_from_u8_unsupported_type() {
let val = DatabaseValue::String("not a number".to_string());
let result: Result<u8, _> = val.try_into();
assert!(matches!(result, Err(TryFromError::CouldNotConvert(_))));
}
#[test_log::test]
fn test_try_from_u16_success_coercion_from_u8() {
let val = DatabaseValue::UInt8(200);
let result: u16 = val.try_into().unwrap();
assert_eq!(result, 200);
}
#[test_log::test]
fn test_try_from_u16_overflow_from_u32() {
let val = DatabaseValue::UInt32(100_000);
let result: Result<u16, _> = val.try_into();
assert!(matches!(result, Err(TryFromError::TryFromInt(_))));
}
#[test_log::test]
fn test_try_from_u16_negative_fails() {
let val = DatabaseValue::Int16(-500);
let result: Result<u16, _> = val.try_into();
assert!(matches!(result, Err(TryFromError::TryFromInt(_))));
}
#[test_log::test]
fn test_try_from_u32_overflow_from_u64() {
let val = DatabaseValue::UInt64(u64::MAX);
let result: Result<u32, _> = val.try_into();
assert!(matches!(result, Err(TryFromError::TryFromInt(_))));
}
#[test_log::test]
fn test_try_from_u32_negative_i32_fails() {
let val = DatabaseValue::Int32(-1);
let result: Result<u32, _> = val.try_into();
assert!(matches!(result, Err(TryFromError::TryFromInt(_))));
}
#[test_log::test]
fn test_try_from_u64_negative_i64_fails() {
let val = DatabaseValue::Int64(-1);
let result: Result<u64, _> = val.try_into();
assert!(matches!(result, Err(TryFromError::TryFromInt(_))));
}
#[test_log::test]
fn test_try_from_u64_success_from_positive_i64() {
let val = DatabaseValue::Int64(1_000_000);
let result: u64 = val.try_into().unwrap();
assert_eq!(result, 1_000_000);
}
#[test_log::test]
fn test_try_from_i8_overflow_from_i16() {
let val = DatabaseValue::Int16(200); let result: Result<i8, _> = val.try_into();
assert!(matches!(result, Err(TryFromError::TryFromInt(_))));
}
#[test_log::test]
fn test_try_from_i8_underflow_from_i16() {
let val = DatabaseValue::Int16(-200); let result: Result<i8, _> = val.try_into();
assert!(matches!(result, Err(TryFromError::TryFromInt(_))));
}
#[test_log::test]
fn test_try_from_i8_overflow_from_u64() {
let val = DatabaseValue::UInt64(1000);
let result: Result<i8, _> = val.try_into();
assert!(matches!(result, Err(TryFromError::TryFromInt(_))));
}
#[test_log::test]
fn test_try_from_i16_success_from_i8() {
let val = DatabaseValue::Int8(-50);
let result: i16 = val.try_into().unwrap();
assert_eq!(result, -50);
}
#[test_log::test]
fn test_try_from_i16_overflow_from_i32() {
let val = DatabaseValue::Int32(50_000); let result: Result<i16, _> = val.try_into();
assert!(matches!(result, Err(TryFromError::TryFromInt(_))));
}
#[test_log::test]
fn test_try_from_i32_overflow_from_i64() {
let val = DatabaseValue::Int64(i64::MAX);
let result: Result<i32, _> = val.try_into();
assert!(matches!(result, Err(TryFromError::TryFromInt(_))));
}
#[test_log::test]
fn test_try_from_i32_overflow_from_u64() {
let val = DatabaseValue::UInt64(u64::MAX);
let result: Result<i32, _> = val.try_into();
assert!(matches!(result, Err(TryFromError::TryFromInt(_))));
}
#[test_log::test]
fn test_try_from_i64_overflow_from_u64() {
let val = DatabaseValue::UInt64(u64::MAX); let result: Result<i64, _> = val.try_into();
assert!(matches!(result, Err(TryFromError::TryFromInt(_))));
}
#[test_log::test]
fn test_try_from_i64_success_from_i32_coercion() {
let val = DatabaseValue::Int32(-100_000);
let result: i64 = val.try_into().unwrap();
assert_eq!(result, -100_000);
}
#[test_log::test]
fn test_try_from_f32_truncation_from_f64() {
let val = DatabaseValue::Real64(1.5);
let result: f32 = val.try_into().unwrap();
assert!((result - 1.5).abs() < 0.0001);
}
#[test_log::test]
fn test_try_from_f32_unsupported_type() {
let val = DatabaseValue::Int64(42);
let result: Result<f32, _> = val.try_into();
assert!(matches!(result, Err(TryFromError::CouldNotConvert(_))));
}
#[test_log::test]
fn test_try_from_f64_coercion_from_f32() {
let val = DatabaseValue::Real32(2.5f32);
let result: f64 = val.try_into().unwrap();
assert!((result - 2.5).abs() < 0.0001);
}
#[test_log::test]
fn test_try_from_f64_unsupported_type() {
let val = DatabaseValue::String("not a float".to_string());
let result: Result<f64, _> = val.try_into();
assert!(matches!(result, Err(TryFromError::CouldNotConvert(_))));
}
#[test_log::test]
fn test_try_from_u64_opt_some() {
let val = DatabaseValue::UInt64Opt(Some(500));
let result: u64 = val.try_into().unwrap();
assert_eq!(result, 500);
}
#[test_log::test]
fn test_try_from_u64_opt_none_fails() {
let val = DatabaseValue::UInt64Opt(None);
let result: Result<u64, _> = val.try_into();
assert!(matches!(result, Err(TryFromError::CouldNotConvert(_))));
}
#[test_log::test]
fn test_try_from_i64_opt_some() {
let val = DatabaseValue::Int64Opt(Some(-999));
let result: i64 = val.try_into().unwrap();
assert_eq!(result, -999);
}
#[test_log::test]
fn test_try_from_f64_opt_some() {
let val = DatabaseValue::Real64Opt(Some(std::f64::consts::PI));
let result: f64 = val.try_into().unwrap();
assert!((result - std::f64::consts::PI).abs() < 0.00001);
}
}
}