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>, }
#[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,
) -> 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_column() -> &'static str;
fn primary_key_value(&self) -> Value;
}
pub trait Insertable {
type Model: crate::model::Model;
fn as_refs(&self) -> Vec<&Self::Model>;
}
impl<T: crate::model::Model> Insertable for &T {
type Model = T;
fn as_refs(&self) -> Vec<&T> {
vec![*self]
}
}
impl<T: crate::model::Model> Insertable for Vec<T> {
type Model = T;
fn as_refs(&self) -> Vec<&T> {
self.iter().collect()
}
}
impl<T: crate::model::Model> Insertable for &Vec<T> {
type Model = T;
fn as_refs(&self) -> Vec<&T> {
self.iter().collect()
}
}
impl<T: crate::model::Model> Insertable for &[T] {
type Model = T;
fn as_refs(&self) -> Vec<&T> {
self.iter().collect()
}
}
impl<T: crate::model::Model, const N: usize> Insertable for &[T; N] {
type Model = T;
fn as_refs(&self) -> Vec<&T> {
self.iter().collect()
}
}
#[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 {
let mut sql = format!("CREATE TABLE IF NOT EXISTS {} (", T::TABLE_NAME);
for (i, column) in T::COLUMN_SCHEMA.iter().enumerate() {
if i > 0 {
sql.push_str(", ");
}
let sql_type = db_type.sql_type(
column.rust_type,
column.is_primary,
column.is_auto_increment,
column.is_nullable,
);
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 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::<T>(db_type);
if !index_sql.is_empty() {
sql.push_str(";");
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
}
fn generate_indexes<T: Model>(_db_type: crate::abstract_layer::DbType) -> String {
let mut sqls = Vec::new();
for column in T::COLUMN_SCHEMA.iter() {
if column.is_indexed {
let index_name = format!("idx_{}_{}", T::TABLE_NAME, column.name);
sqls.push(format!(
"CREATE INDEX IF NOT EXISTS {} ON {} ({})",
index_name,
T::TABLE_NAME,
column.name
));
}
}
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
}
#[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 },
}
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,
}
}
}