deuterium 0.2.0

Deuterium is a fancy SQL builder for Rust. It's designed to provide a DSL to easily build SQL queries in safe and *typed* way.

use sql::{SqlContext};
use predicate::{
    RcPredicate, 
    RawPredicate,
    IsPredicate, 
    OrPredicate, 
    AndPredicate,
    InPredicate,
    InRangePredicate, InRangeBounds,
    InequalityPredicate, Inequality,
    ExcludePredicate,
    LikePredicate,
    IsNullPredicate
};

use sql::value::{ToPredicateValue};

pub trait PredicateToSql {
    fn to_sql(&self, bool, &mut SqlContext) -> String;
}

impl<F: ToPredicateValue, T: ToPredicateValue> PredicateToSql for IsPredicate<F, T> {
    fn to_sql(&self, negation: bool, ctx: &mut SqlContext) -> String {
        let op = if negation { "!=" } else { "=" };
        format!("{} {} {}", self.field.to_predicate_value(ctx), op, self.value.to_predicate_value(ctx))
    }
}

impl<F: ToPredicateValue> PredicateToSql for IsNullPredicate<F> {
    fn to_sql(&self, negation: bool, ctx: &mut SqlContext) -> String {
        let op = if !negation && self.null { "IS NULL" } else { "IS NOT NULL" };
        format!("{} {}", self.field.to_predicate_value(ctx), op)
    }
}

impl PredicateToSql for RcPredicate {
    fn to_sql(&self, negation: bool, ctx: &mut SqlContext) -> String {
        (**self).to_sql(negation, ctx)
    }
}

impl PredicateToSql for OrPredicate {
    fn to_sql(&self, negation: bool, ctx: &mut SqlContext) -> String {
        let left = self.left.to_sql(negation, ctx);
        let right = self.right.to_sql(negation, ctx);
        if !negation {
            format!("({}) OR ({})", left, right)
        } else {
            format!("({}) AND ({})", left, right)
        }
    }
}

impl PredicateToSql for RawPredicate {
    fn to_sql(&self, negation: bool, _ctx: &mut SqlContext) -> String {
        let maybe_not = if negation { "NOT " } else { "" };
        format!("{}{}", maybe_not, self.content.to_string())
    }
}

impl PredicateToSql for ExcludePredicate {
    fn to_sql(&self, negation: bool, ctx: &mut SqlContext) -> String {
        self.predicate.to_sql(!negation, ctx)
    }
}

impl PredicateToSql for AndPredicate {
    fn to_sql(&self, negation: bool, ctx: &mut SqlContext) -> String {
        let left = self.left.to_sql(negation, ctx);
        let right = self.right.to_sql(negation, ctx);
        format!("({}) AND ({})", left, right)
    }
}

impl<F: ToPredicateValue, T: ToPredicateValue> PredicateToSql for InPredicate<F, T> {
    fn to_sql(&self, negation: bool, ctx: &mut SqlContext) -> String {
        let maybe_not = if negation { "NOT " } else { "" };
        let values = self.values.to_predicate_value(ctx);
        format!("{} {}IN ({})", self.field.to_predicate_value(ctx), maybe_not, values)
    }
}

impl<F: ToPredicateValue, T: ToPredicateValue> PredicateToSql for LikePredicate<F, T> {
    fn to_sql(&self, negation: bool, ctx: &mut SqlContext) -> String {
        let maybe_not = if negation { "NOT " } else { "" };
        let like = if self.case_sensitive { "LIKE" } else { "ILIKE" };
        let values = self.value.to_predicate_value(ctx);
        format!("{} {}{} {}", self.field.to_predicate_value(ctx), maybe_not, like, values)
    }
}

impl<F: ToPredicateValue, T: ToPredicateValue> PredicateToSql for InRangePredicate<F, T> {
    fn to_sql(&self, negation: bool, ctx: &mut SqlContext) -> String {
        let ref name = self.field.to_predicate_value(ctx);
        let from = self.from.to_predicate_value(ctx); 
        let to = self.to.to_predicate_value(ctx);
        match self.bounds {
            InRangeBounds::IncludeBoth => {
                if !negation {
                    format!("{} >= {} AND {} <= {}", name, from, name, to)
                } else {
                    format!("{} < {} OR {} > {}", name, from, name, to)
                }
            },
            InRangeBounds::ExcludeBoth => {
                if !negation {
                    format!("{} > {} AND {} < {}", name, from, name, to)
                } else {
                    format!("{} <= {} OR {} >= {}", name, from, name, to)
                }
            },
            InRangeBounds::ExcludeLeft => {
                if !negation {
                    format!("{} > {} AND {} <= {}", name, from, name, to)
                } else {
                    format!("{} <= {} OR {} > {}", name, from, name, to)
                }
            },
            InRangeBounds::ExcludeRight => {
                if !negation {
                    format!("{} >= {} AND {} < {}", name, from, name, to)
                } else {
                    format!("{} < {} OR {} >= {}", name, from, name, to)
                }
            }
        }
    }
}

impl<F: ToPredicateValue, T: ToPredicateValue> PredicateToSql for InequalityPredicate<F, T> {
    fn to_sql(&self, negation: bool, ctx: &mut SqlContext) -> String {
        let ref name = self.field.to_predicate_value(ctx);
        let value = self.value.to_predicate_value(ctx);
        match self.inequality {
            Inequality::LessThan => {
                if !negation {
                    format!("{} < {}", name, value)
                } else {
                    format!("{} >= {}", name, value)
                }
            },
            Inequality::LessThanEqual => {
                if !negation {
                    format!("{} <= {}", name, value)
                } else {
                    format!("{} > {}", name, value)
                }
            },
            Inequality::GreaterThan => {
                if !negation {
                    format!("{} > {}", name, value)
                } else {
                    format!("{} <= {}", name, value)
                }
            },
            Inequality::GreaterThanEqual => {
                if !negation {
                    format!("{} >= {}", name, value)
                } else {
                    format!("{} < {}", name, value)
                }
            }
        }
    }
}