datasources 0.1.5

Async datasource interfaces, middleware, registry, and test doubles
Documentation
use crate::builders::{Join, JoinKind};
use crate::i_where::IWhere;
use crate::select_statement::SelectStatement;
use crate::where_text::WhereText;
use super::order_by::OrderBy;
use super::statement_text_renderer::StatementTextRenderer;

pub struct SelectStatementTextRenderer<'a> {
    text: &'a StatementTextRenderer,
}

impl<'a> SelectStatementTextRenderer<'a> {
    pub fn new(text: &'a StatementTextRenderer) -> Self {
        Self { text }
    }

    pub fn render(&self, statement: &SelectStatement) -> String {
        let fields = if statement.fields.is_empty() {
            "*".to_string()
        } else {
            statement.fields.join(", ")
        };
        let distinct = if statement.distinct { "DISTINCT " } else { "" };
        format!(
            "SELECT {distinct}{fields} FROM {}{}{}{}{}{}{}{}",
            statement.table,
            self.join_sql(&statement.joins),
            self.where_sql(statement.where_clause()),
            self.group_sql(&statement.group_by),
            self.having_sql(statement.having()),
            self.order_sql(&statement.order_by),
            self.limit(statement.limit),
            self.offset(statement.offset),
        )
    }

    fn where_sql(&self, where_clause: Option<&dyn IWhere>) -> String {
        match where_clause {
            Some(predicate) => format!(" WHERE {}", self.text.render_where(predicate)),
            None => String::new(),
        }
    }

    fn having_sql(&self, having: Option<&dyn IWhere>) -> String {
        match having {
            Some(predicate) => format!(" HAVING {}", self.text.render_where(predicate)),
            None => String::new(),
        }
    }

    fn group_sql(&self, columns: &[String]) -> String {
        match columns {
            [] => String::new(),
            names => format!(" GROUP BY {}", names.join(", ")),
        }
    }

    fn order_sql(&self, order: &[OrderBy]) -> String {
        match order {
            [] => String::new(),
            items => format!(
                " ORDER BY {}",
                items
                    .iter()
                    .map(ToString::to_string)
                    .collect::<Vec<_>>()
                    .join(", ")
            ),
        }
    }

    fn limit(&self, limit: Option<i64>) -> String {
        self.bound("LIMIT", limit)
    }

    fn offset(&self, offset: Option<i64>) -> String {
        self.bound("OFFSET", offset)
    }

    fn bound(&self, keyword: &str, value: Option<i64>) -> String {
        match value {
            None => String::new(),
            Some(n) if n < 0 => panic!("negative {}", keyword.to_lowercase()),
            Some(n) => format!(" {keyword} {n}"),
        }
    }

    fn join_sql(&self, joins: &[Join]) -> String {
        joins
            .iter()
            .map(|join| match join.kind {
                JoinKind::Cross => format!(" CROSS JOIN {}", join.table),
                kind => format!(
                    " {} {} ON {}",
                    keyword(kind),
                    join.table,
                    self.text.render_where(&WhereText::new(
                        join.on.as_deref().expect("empty join"),
                    ))
                ),
            })
            .collect()
    }
}

fn keyword(kind: JoinKind) -> &'static str {
    match kind {
        JoinKind::Inner => "INNER JOIN",
        JoinKind::Left => "LEFT JOIN",
        JoinKind::Right => "RIGHT JOIN",
        JoinKind::Full => "FULL JOIN",
        JoinKind::Cross => "CROSS JOIN",
    }
}