use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct ColumnSchema {
pub name: &'static str,
pub rust_type: &'static str,
pub is_primary: bool,
pub is_auto_increment: bool,
pub is_nullable: bool,
pub unique_group: Option<i32>, pub is_indexed: bool,
pub foreign_key: Option<ForeignKeyInfo>, pub enum_variants: Option<&'static [&'static str]>, }
#[derive(Debug, Clone)]
pub struct ForeignKeyInfo {
pub ref_table: &'static str, pub ref_column: &'static str, pub ref_column_fn: Option<fn() -> &'static str>, }
impl ForeignKeyInfo {
pub fn get_ref_column(&self) -> &'static str {
if let Some(fn_get) = self.ref_column_fn {
fn_get()
} else {
self.ref_column
}
}
}
pub trait DbBackendTypeMapper {
fn sql_type(
rust_type: &str,
is_primary: bool,
is_auto_increment: bool,
is_nullable: bool,
enum_variants: Option<&[&str]>,
) -> String;
}
pub trait Model: Sized {
const TABLE_NAME: &'static str;
const COLUMNS: &'static [&'static str];
const COLUMN_SCHEMA: &'static [ColumnSchema];
type QueryBuilder;
type Where: Default;
fn query() -> Self::QueryBuilder;
fn select() -> Self::QueryBuilder;
fn from_row(row: &Row) -> Result<Self, Error>;
fn from_row_values(values: &[Value]) -> Result<Self, Error>;
fn field_values(&self) -> Vec<Value>;
fn primary_key_columns() -> &'static [&'static str] {
const PRIMARY_KEY: &[&str] = &[""];
PRIMARY_KEY
}
fn primary_key_values(&self) -> Vec<Value> {
vec![self.primary_key_value()]
}
fn primary_key_column() -> &'static str {
""
}
fn primary_key_value(&self) -> Value {
Value::Null
}
fn insert_columns() -> Vec<&'static str> {
Self::COLUMN_SCHEMA
.iter()
.filter(|col| !col.is_auto_increment)
.map(|col| col.name)
.collect()
}
fn insert_values(&self) -> Vec<Value> {
let all_values = self.field_values();
Self::COLUMN_SCHEMA
.iter()
.filter(|col| !col.is_auto_increment)
.filter_map(|col| {
let original_idx = Self::COLUMNS
.iter()
.position(|&c| c == col.name)
.expect("Column name in COLUMN_SCHEMA must exist in COLUMNS");
if original_idx < all_values.len() {
Some(all_values[original_idx].clone())
} else {
None
}
})
.collect()
}
}
pub trait ModelEnumProvider {
fn enum_variants() -> Option<&'static [&'static str]>;
}
pub trait ModelEnum: ModelEnumProvider {
const VARIANTS: &'static [&'static str];
fn name(&self) -> &'static str;
fn from_name(name: &str) -> Result<Self, Error>
where
Self: Sized;
}
impl<T: ModelEnum> ModelEnumProvider for Option<T> {
fn enum_variants() -> Option<&'static [&'static str]> {
Some(T::VARIANTS)
}
}
impl<T: ModelEnum> From<Option<T>> for Value {
fn from(v: Option<T>) -> Self {
match v {
Some(enum_val) => Value::Text(enum_val.name().to_string()),
None => Value::Null,
}
}
}
impl<T: ModelEnum> FromValue for Option<T> {
fn from_value(value: &Value) -> Result<Self, Error> {
match value {
Value::Null => Ok(None),
Value::Text(s) => {
match T::from_name(s) {
Ok(enum_val) => Ok(Some(enum_val)),
Err(_) => Err(Error::TypeMismatch(format!("Unknown enum variant: {}", s))),
}
}
_ => Err(Error::TypeMismatch(format!(
"Expected Text value for Option<{}>",
std::any::type_name::<T>()
))),
}
}
}
macro_rules! impl_enum_provider_for_non_enum {
($($t:ty),* $(,)?) => {
$(
impl ModelEnumProvider for $t {
fn enum_variants() -> Option<&'static [&'static str]> {
None
}
}
)*
};
}
impl_enum_provider_for_non_enum!(
i8, i16, i32, i64, u8, u16, u32, u64, isize, usize, f32, f64, bool, String, &str,
);
pub trait Insertable {
type Model: crate::model::Model;
fn as_refs(&self) -> Vec<&Self::Model>;
fn as_refs_mut(&mut self) -> Vec<&mut Self::Model>;
}
impl<T: crate::model::Model> Insertable for &T {
type Model = T;
fn as_refs(&self) -> Vec<&T> {
vec![*self]
}
fn as_refs_mut(&mut self) -> Vec<&mut T> {
vec![]
}
}
impl<T: crate::model::Model> Insertable for Vec<T> {
type Model = T;
fn as_refs(&self) -> Vec<&T> {
self.iter().collect()
}
fn as_refs_mut(&mut self) -> Vec<&mut T> {
self.iter_mut().collect()
}
}
impl<T: crate::model::Model> Insertable for &Vec<T> {
type Model = T;
fn as_refs(&self) -> Vec<&T> {
self.iter().collect()
}
fn as_refs_mut(&mut self) -> Vec<&mut T> {
vec![]
}
}
impl<T: crate::model::Model> Insertable for &[T] {
type Model = T;
fn as_refs(&self) -> Vec<&T> {
self.iter().collect()
}
fn as_refs_mut(&mut self) -> Vec<&mut T> {
vec![]
}
}
impl<T: crate::model::Model, const N: usize> Insertable for &[T; N] {
type Model = T;
fn as_refs(&self) -> Vec<&T> {
self.iter().collect()
}
fn as_refs_mut(&mut self) -> Vec<&mut T> {
vec![]
}
}
#[macro_export]
macro_rules! impl_insertable_for_ref_collections {
($model_type:ty) => {
impl Insertable for Vec<&$model_type> {
type Model = $model_type;
fn as_refs(&self) -> Vec<&$model_type> {
self.as_slice().to_vec()
}
}
impl Insertable for &Vec<&$model_type> {
type Model = $model_type;
fn as_refs(&self) -> Vec<&$model_type> {
self.as_slice().to_vec()
}
}
impl<const N: usize> Insertable for &[&$model_type; N] {
type Model = $model_type;
fn as_refs(&self) -> Vec<&$model_type> {
self.to_vec()
}
}
impl Insertable for &[&$model_type] {
type Model = $model_type;
fn as_refs(&self) -> Vec<&$model_type> {
self.to_vec()
}
}
};
}
pub fn generate_create_table_sql<T: Model>(db_type: crate::abstract_layer::DbType) -> String {
generate_create_table_sql_with_name::<T>(db_type, None)
}
pub fn generate_create_table_sql_with_name<T: Model>(
db_type: crate::abstract_layer::DbType,
table_name: Option<&str>,
) -> String {
let table_name = table_name.unwrap_or(T::TABLE_NAME);
let mut sql = format!("CREATE TABLE IF NOT EXISTS {} (", table_name);
for (i, column) in T::COLUMN_SCHEMA.iter().enumerate() {
if i > 0 {
sql.push_str(", ");
}
let primary_key_count = T::COLUMN_SCHEMA.iter().filter(|c| c.is_primary).count();
let is_composite_primary = primary_key_count > 1;
let sql_type = if is_composite_primary && column.is_primary {
db_type.sql_type(
column.rust_type,
false, column.is_auto_increment,
column.is_nullable,
column.enum_variants,
)
} else {
db_type.sql_type(
column.rust_type,
column.is_primary,
column.is_auto_increment,
column.is_nullable,
column.enum_variants,
)
};
sql.push_str(&format!("{} {sql_type}", column.name));
if column.unique_group.is_some() {
let group_count = T::COLUMN_SCHEMA
.iter()
.filter(|c| c.unique_group == column.unique_group)
.count();
if group_count == 1 {
sql.push_str(" UNIQUE");
}
}
}
let foreign_key_constraints = generate_foreign_key_constraints::<T>();
if !foreign_key_constraints.is_empty() {
sql.push_str(", ");
sql.push_str(&foreign_key_constraints.join(", "));
}
let composite_primary_constraint = generate_composite_primary_key_constraint::<T>();
if !composite_primary_constraint.is_empty() {
sql.push_str(", ");
sql.push_str(&composite_primary_constraint);
}
let unique_constraints = generate_unique_constraints::<T>();
if !unique_constraints.is_empty() {
sql.push_str(", ");
sql.push_str(&unique_constraints.join(", "));
}
sql.push(')');
let index_sql = generate_indexes_with_name::<T>(db_type, table_name);
if !index_sql.is_empty() {
sql.push(';');
sql.push_str(&index_sql);
}
sql
}
fn generate_unique_constraints<T: Model>() -> Vec<String> {
let mut constraints = Vec::new();
let mut group_map: std::collections::BTreeMap<i32, Vec<&str>> =
std::collections::BTreeMap::new();
for column in T::COLUMN_SCHEMA.iter() {
if let Some(group_id) = column.unique_group {
group_map.entry(group_id).or_default().push(column.name);
}
}
for (_group_id, columns) in group_map {
if columns.len() == 1 {
} else {
let cols = columns.join(", ");
constraints.push(format!("UNIQUE ({cols})"));
}
}
constraints
}
#[allow(dead_code)]
fn generate_indexes<T: Model>(db_type: crate::abstract_layer::DbType) -> String {
generate_indexes_with_name::<T>(db_type, T::TABLE_NAME)
}
fn generate_indexes_with_name<T: Model>(
db_type: crate::abstract_layer::DbType,
table_name: &str,
) -> String {
let mut sqls = Vec::new();
let is_mysql = format!("{:?}", db_type).contains("MySQL");
for column in T::COLUMN_SCHEMA.iter() {
if column.is_indexed {
let index_name = format!("idx_{}_{}", table_name, column.name);
let sql = if is_mysql {
format!(
"CREATE INDEX {} ON {} ({})",
index_name, table_name, column.name
)
} else {
format!(
"CREATE INDEX IF NOT EXISTS {} ON {} ({})",
index_name, table_name, column.name
)
};
sqls.push(sql);
}
}
sqls.join(";")
}
fn generate_foreign_key_constraints<T: Model>() -> Vec<String> {
let mut constraints = Vec::new();
for column in T::COLUMN_SCHEMA.iter() {
if let Some(fk) = &column.foreign_key {
let ref_column = fk.get_ref_column();
constraints.push(format!(
"FOREIGN KEY ({}) REFERENCES {} ({})",
column.name, fk.ref_table, ref_column
));
}
}
constraints
}
fn generate_composite_primary_key_constraint<T: Model>() -> String {
let primary_keys: Vec<&str> = T::COLUMN_SCHEMA
.iter()
.filter(|c| c.is_primary)
.map(|c| c.name)
.collect();
if primary_keys.len() > 1 {
format!("PRIMARY KEY ({})", primary_keys.join(", "))
} else {
String::new()
}
}
#[derive(Debug)]
pub struct Row {
data: HashMap<String, Value>,
}
impl Row {
pub fn new(data: HashMap<String, Value>) -> Self {
Self { data }
}
pub fn get<T: FromValue>(&self, column: &str) -> Result<T, Error> {
self.data
.get(column)
.ok_or_else(|| Error::ColumnNotFound(column.to_string()))
.and_then(|v| T::from_value(v))
}
}
#[derive(Debug, Clone)]
pub enum Value {
Integer(i64),
Text(String),
Real(f64),
Null,
}
pub trait FromValue: Sized {
fn from_value(value: &Value) -> Result<Self, Error>;
}
pub trait FromRowValues: Sized {
fn from_row_values(values: &[Value]) -> Result<Self, Error>;
}
pub trait FromSingleValue<V>: Sized {
fn from_single_value(value: V, column_name: &str) -> Result<Self, Error>;
}
impl<T, V> FromSingleValue<V> for T
where
T: Model,
V: Into<Value>,
T: FromValue,
{
fn from_single_value(value: V, _column_name: &str) -> Result<Self, Error> {
let ormer_value: Value = value.into();
Self::from_value(&ormer_value)
}
}
macro_rules! impl_from_value_for {
($($type:ty => $variant:ident),* $(,)?) => {
$(
impl FromValue for $type {
fn from_value(value: &Value) -> Result<Self, Error> {
match value {
Value::$variant(v) => Ok(*v as $type),
_ => Err(Error::TypeMismatch(stringify!($type).to_string())),
}
}
}
)*
};
}
impl_from_value_for!(
i32 => Integer,
i64 => Integer,
usize => Integer,
);
impl FromRowValues for i32 {
fn from_row_values(values: &[Value]) -> Result<Self, Error> {
if values.is_empty() {
return Err(Error::TypeMismatch("i32".to_string()));
}
Self::from_value(&values[0])
}
}
impl FromRowValues for i64 {
fn from_row_values(values: &[Value]) -> Result<Self, Error> {
if values.is_empty() {
return Err(Error::TypeMismatch("i64".to_string()));
}
Self::from_value(&values[0])
}
}
impl FromRowValues for usize {
fn from_row_values(values: &[Value]) -> Result<Self, Error> {
if values.is_empty() {
return Err(Error::TypeMismatch("usize".to_string()));
}
Self::from_value(&values[0])
}
}
impl FromValue for f64 {
fn from_value(value: &Value) -> Result<Self, Error> {
match value {
Value::Real(v) => Ok(*v),
Value::Integer(v) => Ok(*v as f64),
_ => Err(Error::TypeMismatch("f64".to_string())),
}
}
}
impl FromRowValues for f64 {
fn from_row_values(values: &[Value]) -> Result<Self, Error> {
if values.is_empty() {
return Err(Error::TypeMismatch("f64".to_string()));
}
Self::from_value(&values[0])
}
}
impl FromValue for String {
fn from_value(value: &Value) -> Result<Self, Error> {
match value {
Value::Text(v) => Ok(v.clone()),
_ => Err(Error::TypeMismatch("String".to_string())),
}
}
}
impl FromRowValues for String {
fn from_row_values(values: &[Value]) -> Result<Self, Error> {
if values.is_empty() {
return Err(Error::TypeMismatch("String".to_string()));
}
Self::from_value(&values[0])
}
}
impl FromValue for bool {
fn from_value(value: &Value) -> Result<Self, Error> {
match value {
Value::Integer(v) => Ok(*v != 0),
_ => Err(Error::TypeMismatch("bool".to_string())),
}
}
}
impl FromRowValues for bool {
fn from_row_values(values: &[Value]) -> Result<Self, Error> {
if values.is_empty() {
return Err(Error::TypeMismatch("bool".to_string()));
}
Self::from_value(&values[0])
}
}
impl<T1: FromValue, T2: FromValue> FromValue for (T1, T2) {
fn from_value(_value: &Value) -> Result<Self, Error> {
Err(Error::TypeMismatch("tuple".to_string()))
}
}
impl<T1: FromRowValues, T2: FromRowValues> FromRowValues for (T1, T2) {
fn from_row_values(values: &[Value]) -> Result<Self, Error> {
if values.len() < 2 {
return Err(Error::TypeMismatch("tuple (T1, T2)".to_string()));
}
let v1 = T1::from_row_values(&values[0..1])?;
let v2 = T2::from_row_values(&values[1..2])?;
Ok((v1, v2))
}
}
impl<T1: FromValue, T2: FromValue, T3: FromValue> FromValue for (T1, T2, T3) {
fn from_value(_value: &Value) -> Result<Self, Error> {
Err(Error::TypeMismatch("tuple".to_string()))
}
}
impl<T1: FromRowValues, T2: FromRowValues, T3: FromRowValues> FromRowValues for (T1, T2, T3) {
fn from_row_values(values: &[Value]) -> Result<Self, Error> {
if values.len() < 3 {
return Err(Error::TypeMismatch("tuple (T1, T2, T3)".to_string()));
}
let v1 = T1::from_row_values(&values[0..1])?;
let v2 = T2::from_row_values(&values[1..2])?;
let v3 = T3::from_row_values(&values[2..3])?;
Ok((v1, v2, v3))
}
}
macro_rules! impl_from_value_for_option {
($($type:ty => $variant:ident),* $(,)?) => {
$(
impl FromValue for Option<$type> {
fn from_value(value: &Value) -> Result<Self, Error> {
match value {
Value::Null => Ok(None),
Value::$variant(v) => Ok(Some(*v as $type)),
_ => Err(Error::TypeMismatch(concat!("Option<", stringify!($type), ">").to_string())),
}
}
}
)*
};
}
impl_from_value_for_option!(
i32 => Integer,
i64 => Integer,
);
impl FromValue for Option<String> {
fn from_value(value: &Value) -> Result<Self, Error> {
match value {
Value::Null => Ok(None),
Value::Text(v) => Ok(Some(v.clone())),
_ => Err(Error::TypeMismatch("Option<String>".to_string())),
}
}
}
impl FromValue for Option<bool> {
fn from_value(value: &Value) -> Result<Self, Error> {
match value {
Value::Null => Ok(None),
Value::Integer(v) => Ok(Some(*v != 0)),
_ => Err(Error::TypeMismatch("Option<bool>".to_string())),
}
}
}
impl FromValue for Option<f64> {
fn from_value(value: &Value) -> Result<Self, Error> {
match value {
Value::Null => Ok(None),
Value::Real(v) => Ok(Some(*v)),
Value::Integer(v) => Ok(Some(*v as f64)),
_ => Err(Error::TypeMismatch("Option<f64>".to_string())),
}
}
}
impl<T: FromValue> FromRowValues for Option<T> {
fn from_row_values(values: &[Value]) -> Result<Self, Error> {
if values.is_empty() {
return Err(Error::TypeMismatch(format!(
"Option<{}>",
std::any::type_name::<T>()
)));
}
match &values[0] {
Value::Null => Ok(None),
_ => {
let inner = T::from_value(&values[0])?;
Ok(Some(inner))
}
}
}
}
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("Column not found: {0}")]
ColumnNotFound(String),
#[error("Type mismatch: expected {0}")]
TypeMismatch(String),
#[error("Database error: {0}")]
Database(String),
#[error("Table schema mismatch for table '{table}': {reason}")]
SchemaMismatch { table: String, reason: String },
#[error("Validation error: {0}")]
ValidationError(String),
#[error("Connection error: {0}")]
ConnectionError(String),
#[error("Transaction error: {0}")]
TransactionError(String),
#[error("Hook error: {0}")]
HookError(String),
}
macro_rules! impl_from_for_value {
($($type:ty => $variant:ident),* $(,)?) => {
$(
impl From<$type> for Value {
fn from(v: $type) -> Self {
Value::$variant(v as i64)
}
}
)*
};
}
impl_from_for_value!(
i32 => Integer,
i64 => Integer,
);
impl From<f64> for Value {
fn from(v: f64) -> Self {
Value::Real(v)
}
}
impl From<String> for Value {
fn from(v: String) -> Self {
Value::Text(v)
}
}
impl From<bool> for Value {
fn from(v: bool) -> Self {
if v {
Value::Integer(1)
} else {
Value::Integer(0)
}
}
}
macro_rules! impl_from_option_for_value {
($($type:ty => { Some($variant:ident), None => Null }),* $(,)?) => {
$(
impl From<Option<$type>> for Value {
fn from(v: Option<$type>) -> Self {
match v {
Some(val) => Value::$variant(val as i64),
None => Value::Null,
}
}
}
)*
};
}
impl_from_option_for_value!(
i32 => { Some(Integer), None => Null },
i64 => { Some(Integer), None => Null },
);
impl From<Option<String>> for Value {
fn from(v: Option<String>) -> Self {
match v {
Some(s) => Value::Text(s),
None => Value::Null,
}
}
}
impl From<Option<bool>> for Value {
fn from(v: Option<bool>) -> Self {
match v {
Some(true) => Value::Integer(1),
Some(false) => Value::Integer(0),
None => Value::Null,
}
}
}
impl From<crate::query::filter::Value> for Value {
fn from(value: crate::query::filter::Value) -> Self {
match value {
crate::query::filter::Value::Integer(v) => Value::Integer(v),
crate::query::filter::Value::Text(v) => Value::Text(v),
crate::query::filter::Value::Real(v) => Value::Real(v),
crate::query::filter::Value::Null => Value::Null,
}
}
}
pub use crate::hooks::{
AfterDelete, AfterInsert, AfterUpdate, BeforeDelete, BeforeInsert, BeforeUpdate,
};