ormer 0.2.7

A minimalist ORM framework that supports SQLite, PostgreSQL, MySQL, and SqlServer
use crate::model::{Model, Value};
use crate::query::builder::{TypedColumn, WhereExpr};
use crate::query::filter::FilterExpr;
use crate::query::update::UpdateAssignment;

#[derive(Debug, Clone)]
pub enum InsertConflictTarget {
    Columns(Vec<&'static str>),
    Constraint(String),
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InsertConflictAction {
    DoNothing,
    DoUpdate,
}

#[derive(Debug, Clone, Default)]
pub struct InsertConflict {
    pub target: Option<InsertConflictTarget>,
    pub target_filter: Option<FilterExpr>,
    pub action: Option<InsertConflictAction>,
    pub update_filter: Option<FilterExpr>,
    pub assignments: Vec<UpdateAssignment>,
}

#[derive(Debug, Clone)]
pub enum InsertValue {
    Literal(Value),
    Default,
}

#[derive(Debug, Clone)]
pub struct InsertAssignment {
    pub column: String,
    pub value: InsertValue,
}

impl InsertAssignment {
    pub fn value(column: impl Into<String>, value: impl Into<Value>) -> Self {
        Self {
            column: column.into(),
            value: InsertValue::Literal(value.into()),
        }
    }

    pub fn default(column: impl Into<String>) -> Self {
        Self {
            column: column.into(),
            value: InsertValue::Default,
        }
    }
}

pub trait IntoInsertAssignment<M: Model> {
    fn into_insert_assignment(self) -> InsertAssignment;
}

impl<M: Model> IntoInsertAssignment<M> for InsertAssignment {
    fn into_insert_assignment(self) -> InsertAssignment {
        self
    }
}

impl<M, T, S, V> IntoInsertAssignment<M> for (TypedColumn<T, S>, V)
where
    M: Model,
    V: Into<Value>,
{
    fn into_insert_assignment(self) -> InsertAssignment {
        InsertAssignment::value(self.0.column_name(), self.1)
    }
}

pub trait IntoInsertDefaultColumn<M: Model> {
    fn into_insert_default_column(self) -> String;
}

impl<M, T, S> IntoInsertDefaultColumn<M> for TypedColumn<T, S>
where
    M: Model,
{
    fn into_insert_default_column(self) -> String {
        self.column_name().to_string()
    }
}

impl<M: Model> IntoInsertDefaultColumn<M> for &'static str {
    fn into_insert_default_column(self) -> String {
        self.to_string()
    }
}

impl<M: Model> IntoInsertDefaultColumn<M> for String {
    fn into_insert_default_column(self) -> String {
        self
    }
}

impl<T, S> TypedColumn<T, S> {
    pub fn set<V>(self, value: V) -> InsertAssignment
    where
        V: Into<Value>,
    {
        InsertAssignment::value(self.column_name(), value)
    }
}

impl InsertConflict {
    pub fn is_configured(&self) -> bool {
        self.target.is_some()
            || self.target_filter.is_some()
            || self.action.is_some()
            || self.update_filter.is_some()
            || !self.assignments.is_empty()
    }
}

pub trait ConflictColumns {
    fn conflict_columns(self) -> Vec<&'static str>;
}

impl<T, S> ConflictColumns for TypedColumn<T, S> {
    fn conflict_columns(self) -> Vec<&'static str> {
        vec![self.column_name()]
    }
}

macro_rules! impl_conflict_columns_for_tuple {
    ($($type:ident => $value:ident),+) => {
        impl<$($type),+> ConflictColumns for ($($type,)+)
        where
            $($type: ConflictColumns),+
        {
            fn conflict_columns(self) -> Vec<&'static str> {
                let ($($value,)+) = self;
                let mut columns = Vec::new();
                $(columns.extend($value.conflict_columns());)+
                columns
            }
        }
    };
}

impl_conflict_columns_for_tuple!(A => a, B => b);
impl_conflict_columns_for_tuple!(A => a, B => b, C => c);
impl_conflict_columns_for_tuple!(A => a, B => b, C => c, D => d);

pub trait IntoInsertConflictTarget<M: Model> {
    fn into_insert_conflict_target(self) -> InsertConflictTarget;
}

impl<M, F, C> IntoInsertConflictTarget<M> for F
where
    M: Model,
    F: FnOnce(M::Where) -> C,
    C: ConflictColumns,
{
    fn into_insert_conflict_target(self) -> InsertConflictTarget {
        InsertConflictTarget::Columns(self(M::Where::default()).conflict_columns())
    }
}

impl<M: Model> IntoInsertConflictTarget<M> for &str {
    fn into_insert_conflict_target(self) -> InsertConflictTarget {
        InsertConflictTarget::Constraint(self.to_string())
    }
}

impl<M: Model> IntoInsertConflictTarget<M> for String {
    fn into_insert_conflict_target(self) -> InsertConflictTarget {
        InsertConflictTarget::Constraint(self)
    }
}

pub fn where_expr_to_filter<W>(expr: W) -> FilterExpr
where
    W: Into<WhereExpr>,
{
    expr.into().into()
}