dinoco_engine 0.0.7

Database adapters, query execution, and migration engine components for Dinoco.
Documentation
use std::fmt::Write;

use crate::{AdapterDialect, BinaryOperator, DinocoValue, Expression};

pub fn push_joined<I, T, F>(buf: &mut String, iter: I, sep: &str, mut f: F)
where
    I: IntoIterator<Item = T>,
    F: FnMut(&mut String, T),
{
    let mut first = true;

    for item in iter {
        if !first {
            buf.push_str(sep);
        }

        f(buf, item);

        first = false;
    }
}

pub fn append_limit_skip<D: AdapterDialect + ?Sized>(
    dialect: &D,
    sql: &mut String,
    params: &mut Vec<DinocoValue>,
    limit: Option<usize>,
    skip: Option<usize>,
) {
    match (limit, skip) {
        (Some(limit), Some(skip)) => {
            params.push(DinocoValue::Integer(limit as i64));
            let _ = write!(sql, " LIMIT {}", dialect.bind_param(params.len()));

            params.push(DinocoValue::Integer(skip as i64));
            let _ = write!(sql, " OFFSET {}", dialect.bind_param(params.len()));
        }
        (Some(limit), None) => {
            params.push(DinocoValue::Integer(limit as i64));
            let _ = write!(sql, " LIMIT {}", dialect.bind_param(params.len()));
        }
        (None, Some(skip)) => {
            let _ = write!(sql, " LIMIT {}", dialect.offset_without_limit());
            params.push(DinocoValue::Integer(skip as i64));

            let _ = write!(sql, " OFFSET {}", dialect.bind_param(params.len()));
        }
        (None, None) => {}
    }
}

pub fn render_condition_group_into<D: AdapterDialect + ?Sized>(
    dialect: &D,
    conditions: &[Expression],
    params: &mut Vec<DinocoValue>,
    joiner: &str,
    buf: &mut String,
) {
    push_joined(buf, conditions, joiner, |b, condition| {
        render_expression_into(dialect, condition, params, b);
    });
}

pub fn render_expression_into<D: AdapterDialect + ?Sized>(
    dialect: &D,
    expression: &Expression,
    params: &mut Vec<DinocoValue>,
    buf: &mut String,
) {
    match expression {
        Expression::Column(name) => render_query_identifier_into(dialect, name, buf),
        Expression::Value(value) => {
            params.push(value.clone());

            buf.push_str(&dialect.bind_value(params.len(), value));
        }
        Expression::Raw(value) => buf.push_str(value),
        Expression::IsNull(inner) => {
            buf.push('(');

            render_expression_into(dialect, inner, params, buf);

            buf.push_str(" IS NULL)");
        }
        Expression::IsNotNull(inner) => {
            buf.push('(');

            render_expression_into(dialect, inner, params, buf);

            buf.push_str(" IS NOT NULL)");
        }
        Expression::In { expr, values } => {
            if values.is_empty() {
                buf.push_str("(1 = 0)");

                return;
            }

            params.reserve(values.len());

            buf.push('(');

            render_expression_into(dialect, expr, params, buf);

            buf.push_str(" IN (");

            push_joined(buf, values, ", ", |b, value| {
                params.push(value.clone());

                b.push_str(&dialect.bind_value(params.len(), value));
            });

            buf.push_str("))");
        }
        Expression::NotIn { expr, values } => {
            if values.is_empty() {
                buf.push_str("(1 = 1)");

                return;
            }

            params.reserve(values.len());

            buf.push('(');

            render_expression_into(dialect, expr, params, buf);

            buf.push_str(" NOT IN (");

            push_joined(buf, values, ", ", |b, value| {
                params.push(value.clone());

                b.push_str(&dialect.bind_value(params.len(), value));
            });

            buf.push_str("))");
        }
        Expression::And(expressions) => {
            if expressions.is_empty() {
                buf.push_str("(1 = 1)");

                return;
            }

            buf.push('(');

            render_condition_group_into(dialect, expressions, params, " AND ", buf);

            buf.push(')');
        }
        Expression::Or(expressions) => {
            if expressions.is_empty() {
                buf.push_str("(1 = 0)");

                return;
            }

            buf.push('(');

            render_condition_group_into(dialect, expressions, params, " OR ", buf);

            buf.push(')');
        }
        Expression::BinaryOp { left, op, right } => {
            buf.push('(');

            render_expression_into(dialect, left, params, buf);

            let op_str = match op {
                BinaryOperator::Eq => " = ",
                BinaryOperator::Neq => " <> ",
                BinaryOperator::Gt => " > ",
                BinaryOperator::Lt => " < ",
                BinaryOperator::Gte => " >= ",
                BinaryOperator::Lte => " <= ",
                BinaryOperator::Like => " LIKE ",
            };

            buf.push_str(op_str);

            render_expression_into(dialect, right, params, buf);

            buf.push(')');
        }
    }
}

pub fn render_query_identifier_into<D: AdapterDialect + ?Sized>(dialect: &D, value: &str, buf: &mut String) {
    if value == "*" || value.contains(' ') || value.contains('(') || value.contains(')') || value.contains(',') {
        buf.push_str(value);

        return;
    }

    let mut first = true;

    for part in value.split('.') {
        if !first {
            buf.push('.');
        }

        if part == "*" {
            buf.push('*');
        } else {
            buf.push_str(&dialect.identifier(part));
        }

        first = false;
    }
}