pub mod backend;
pub mod connection;
pub mod db_schema;
pub mod eager;
pub mod migration;
pub mod pool;
pub mod query;
pub mod relation;
pub mod repository;
pub mod seed;
pub mod timestamp;
mod decode;
pub mod uuid;
#[cfg(test)]
mod tests;
#[cfg(test)]
mod backends;
#[cfg(test)]
mod convention_tests;
#[cfg(test)]
mod part2_tests;
#[cfg(test)]
mod tls_tests;
pub use backend::Backend;
pub use connection::{DbConfig, DbSslMode, DbTls};
pub use migration::MigrationStatus;
pub use db_schema::{ColumnSchema, DbSchema, ForeignKeySchema, IndexSchema, TableSchema};
pub use eager::{EagerQuery, Loaded, Relation, RelationKind};
pub use pool::{DbPool, DbTransaction, Replicas};
pub use query::{Order, QueryBuilder};
pub use relation::{BelongsTo, BelongsToMany, HasMany, HasOne, SyncChanges};
pub use seed::{Seeder, Seeders};
pub use repository::{ModelRepository, PrimaryKey, Repository};
pub use timestamp::Timestamp;
pub use uuid::Uuid;
pub use crate::pagination::{CursorPage, Page};
#[derive(Debug, Clone, PartialEq)]
pub enum Value {
Null,
Bool(bool),
Int(i64),
Float(f64),
Text(String),
Bytes(Vec<u8>),
}
#[derive(Debug)]
pub struct DbError(pub String);
impl std::fmt::Display for DbError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "DbError: {}", self.0)
}
}
impl std::error::Error for DbError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum DbErrorKind {
StaleVersion,
NotFound,
Other,
}
const STALE_VERSION: &str = "stale version: ";
const NOT_FOUND: &str = "not found: ";
impl DbError {
pub fn new(msg: impl Into<String>) -> Self {
DbError(msg.into())
}
pub fn stale_version(detail: impl std::fmt::Display) -> Self {
DbError(format!("{STALE_VERSION}{detail}"))
}
pub fn not_found(detail: impl std::fmt::Display) -> Self {
DbError(format!("{NOT_FOUND}{detail}"))
}
pub fn kind(&self) -> DbErrorKind {
if self.0.starts_with(STALE_VERSION) {
DbErrorKind::StaleVersion
} else if self.0.starts_with(NOT_FOUND) {
DbErrorKind::NotFound
} else {
DbErrorKind::Other
}
}
pub fn is_stale_version(&self) -> bool {
self.kind() == DbErrorKind::StaleVersion
}
pub fn is_not_found(&self) -> bool {
self.kind() == DbErrorKind::NotFound
}
}
#[derive(Debug, Clone)]
pub struct ModelRow {
#[doc(hidden)]
pub columns: Vec<(String, Value)>,
}
impl ModelRow {
pub fn new(columns: Vec<(String, Value)>) -> Self {
ModelRow { columns }
}
pub fn get<T: FromColumn>(&self, col: &str) -> Result<T, DbError> {
for (name, val) in &self.columns {
if name.eq_ignore_ascii_case(col) {
return T::from_column(val.clone());
}
}
Err(DbError::new(format!("column '{}' not found in row", col)))
}
}
pub trait FromColumn: Sized {
fn from_column(v: Value) -> Result<Self, DbError>;
}
pub trait ToColumn {
fn to_column(&self) -> Value;
}
impl FromColumn for i16 {
fn from_column(v: Value) -> Result<Self, DbError> {
match v {
Value::Int(n) => Ok(n as i16),
Value::Null => Err(DbError::new("unexpected NULL for i16")),
other => Err(DbError::new(format!("cannot convert {:?} to i16", other))),
}
}
}
impl FromColumn for i32 {
fn from_column(v: Value) -> Result<Self, DbError> {
match v {
Value::Int(n) => Ok(n as i32),
Value::Null => Err(DbError::new("unexpected NULL for i32")),
other => Err(DbError::new(format!("cannot convert {:?} to i32", other))),
}
}
}
impl FromColumn for i64 {
fn from_column(v: Value) -> Result<Self, DbError> {
match v {
Value::Int(n) => Ok(n),
Value::Null => Err(DbError::new("unexpected NULL for i64")),
other => Err(DbError::new(format!("cannot convert {:?} to i64", other))),
}
}
}
impl FromColumn for u32 {
fn from_column(v: Value) -> Result<Self, DbError> {
match v {
Value::Int(n) => Ok(n as u32),
Value::Null => Err(DbError::new("unexpected NULL for u32")),
other => Err(DbError::new(format!("cannot convert {:?} to u32", other))),
}
}
}
impl FromColumn for u64 {
fn from_column(v: Value) -> Result<Self, DbError> {
match v {
Value::Int(n) => Ok(n as u64),
Value::Null => Err(DbError::new("unexpected NULL for u64")),
other => Err(DbError::new(format!("cannot convert {:?} to u64", other))),
}
}
}
impl FromColumn for f32 {
fn from_column(v: Value) -> Result<Self, DbError> {
match v {
Value::Float(f) => Ok(f as f32),
Value::Int(n) => Ok(n as f32),
Value::Null => Err(DbError::new("unexpected NULL for f32")),
other => Err(DbError::new(format!("cannot convert {:?} to f32", other))),
}
}
}
impl FromColumn for f64 {
fn from_column(v: Value) -> Result<Self, DbError> {
match v {
Value::Float(f) => Ok(f),
Value::Int(n) => Ok(n as f64),
Value::Null => Err(DbError::new("unexpected NULL for f64")),
other => Err(DbError::new(format!("cannot convert {:?} to f64", other))),
}
}
}
impl FromColumn for bool {
fn from_column(v: Value) -> Result<Self, DbError> {
match v {
Value::Bool(b) => Ok(b),
Value::Int(n) => Ok(n != 0),
Value::Null => Err(DbError::new("unexpected NULL for bool")),
other => Err(DbError::new(format!("cannot convert {:?} to bool", other))),
}
}
}
impl FromColumn for String {
fn from_column(v: Value) -> Result<Self, DbError> {
match v {
Value::Text(s) => Ok(s),
Value::Null => Err(DbError::new("unexpected NULL for String")),
other => Err(DbError::new(format!("cannot convert {:?} to String", other))),
}
}
}
impl FromColumn for Value {
fn from_column(v: Value) -> Result<Self, DbError> {
Ok(v)
}
}
impl<T: FromColumn> FromColumn for Option<T> {
fn from_column(v: Value) -> Result<Self, DbError> {
match v {
Value::Null => Ok(None),
other => Ok(Some(T::from_column(other)?)),
}
}
}
impl ToColumn for i16 {
fn to_column(&self) -> Value { Value::Int(*self as i64) }
}
impl ToColumn for i32 {
fn to_column(&self) -> Value { Value::Int(*self as i64) }
}
impl ToColumn for i64 {
fn to_column(&self) -> Value { Value::Int(*self) }
}
impl ToColumn for u32 {
fn to_column(&self) -> Value { Value::Int(*self as i64) }
}
impl ToColumn for u64 {
fn to_column(&self) -> Value { Value::Int(*self as i64) }
}
impl ToColumn for f32 {
fn to_column(&self) -> Value { Value::Float(*self as f64) }
}
impl ToColumn for f64 {
fn to_column(&self) -> Value { Value::Float(*self) }
}
impl ToColumn for bool {
fn to_column(&self) -> Value { Value::Bool(*self) }
}
impl ToColumn for String {
fn to_column(&self) -> Value { Value::Text(self.clone()) }
}
impl ToColumn for str {
fn to_column(&self) -> Value { Value::Text(self.to_owned()) }
}
impl ToColumn for &str {
fn to_column(&self) -> Value { Value::Text((*self).to_owned()) }
}
impl<T: ToColumn> ToColumn for Option<T> {
fn to_column(&self) -> Value {
match self {
Some(v) => v.to_column(),
None => Value::Null,
}
}
}
pub trait Model: Sized + Send + Sync {
fn table_name() -> &'static str;
fn column_names() -> &'static [&'static str];
fn primary_key_name() -> &'static str;
fn primary_key_value(&self) -> Value;
fn primary_key_auto_increment() -> bool {
false
}
fn from_row(row: &ModelRow) -> Result<Self, DbError>;
fn to_values(&self) -> Vec<(&'static str, Value)>;
fn created_at_column() -> Option<&'static str> {
None
}
fn updated_at_column() -> Option<&'static str> {
None
}
fn soft_delete_column() -> Option<&'static str> {
None
}
fn version_column() -> Option<&'static str> {
None
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ModelInfo {
pub type_name: &'static str,
pub table: &'static str,
pub columns: &'static [&'static str],
pub primary_key: &'static str,
pub auto_increment: bool,
}
impl ModelInfo {
pub fn of<T: Model>() -> Self {
ModelInfo {
type_name: std::any::type_name::<T>(),
table: T::table_name(),
columns: T::column_names(),
primary_key: T::primary_key_name(),
auto_increment: T::primary_key_auto_increment(),
}
}
pub fn short_name(&self) -> &'static str {
let base = self.type_name.split('<').next().unwrap_or(self.type_name);
base.rsplit("::").next().unwrap_or(base)
}
}