tideorm 0.9.3

A developer-friendly ORM for Rust with clean, expressive syntax
Documentation
use super::*;

#[allow(missing_docs)]
impl<M: Model> QueryBuilder<M> {
    fn operator_label(operator: &Operator) -> &'static str {
        match operator {
            Operator::Eq => "=",
            Operator::NotEq => "!=",
            Operator::Gt => ">",
            Operator::Gte => ">=",
            Operator::Lt => "<",
            Operator::Lte => "<=",
            Operator::Like => "LIKE",
            Operator::LikeEscaped => "LIKE",
            Operator::NotLike => "NOT LIKE",
            Operator::In => "IN",
            Operator::NotIn => "NOT IN",
            Operator::IsNull => "IS NULL",
            Operator::IsNotNull => "IS NOT NULL",
            Operator::Between => "BETWEEN",
            Operator::JsonContains => "JSON_CONTAINS",
            Operator::JsonContainedBy => "JSON_CONTAINED_BY",
            Operator::JsonKeyExists => "JSON_KEY_EXISTS",
            Operator::JsonKeyNotExists => "JSON_KEY_NOT_EXISTS",
            Operator::JsonPathExists => "JSON_PATH_EXISTS",
            Operator::JsonPathNotExists => "JSON_PATH_NOT_EXISTS",
            Operator::ArrayContains => "ARRAY_CONTAINS",
            Operator::ArrayContainedBy => "ARRAY_CONTAINED_BY",
            Operator::ArrayOverlaps => "ARRAY_OVERLAPS",
            Operator::ArrayContainsAny => "ARRAY_CONTAINS_ANY",
            Operator::ArrayContainsAll => "ARRAY_CONTAINS_ALL",
            Operator::SubqueryIn => "IN SUBQUERY",
            Operator::SubqueryNotIn => "NOT IN SUBQUERY",
            Operator::Raw => "RAW",
            Operator::EqAny => "= ANY",
            Operator::NeAll => "<> ALL",
        }
    }

    fn describe_condition_value(value: &ConditionValue) -> String {
        match value {
            ConditionValue::Single(value) => value.to_string(),
            ConditionValue::List(values) => format!("{:?}", values),
            ConditionValue::Range(low, high) => format!("{}..{}", low, high),
            ConditionValue::None => "NULL".to_string(),
            ConditionValue::Subquery(query_sql) => query_sql.clone(),
            ConditionValue::RawExpr(raw_sql) => raw_sql.clone(),
        }
    }

    fn describe_condition(condition: &WhereCondition) -> String {
        match (&condition.operator, &condition.value) {
            (Operator::Raw, ConditionValue::RawExpr(raw_sql)) => raw_sql.clone(),
            (Operator::IsNull | Operator::IsNotNull, ConditionValue::None) => {
                format!(
                    "{} {}",
                    condition.column,
                    Self::operator_label(&condition.operator)
                )
            }
            _ => format!(
                "{} {} {}",
                condition.column,
                Self::operator_label(&condition.operator),
                Self::describe_condition_value(&condition.value)
            ),
        }
    }

    fn describe_or_group(group: &OrGroup) -> String {
        let mut parts: Vec<String> = group
            .conditions
            .iter()
            .map(Self::describe_condition)
            .collect();
        parts.extend(group.nested_groups.iter().map(Self::describe_or_group));

        if parts.is_empty() {
            String::new()
        } else if parts.len() == 1 {
            parts[0].clone()
        } else {
            format!(
                "({})",
                parts.join(&format!(" {} ", group.combine_with.as_sql()))
            )
        }
    }

    fn error_context_conditions(&self) -> Vec<String> {
        let mut conditions: Vec<String> = self
            .conditions
            .iter()
            .map(Self::describe_condition)
            .collect();
        conditions.extend(
            self.or_groups
                .iter()
                .map(Self::describe_or_group)
                .filter(|group| !group.is_empty()),
        );
        conditions.extend(
            self.having_conditions
                .iter()
                .map(|having| format!("HAVING {}", having)),
        );
        conditions
    }

    fn error_context_operator_chain(&self) -> Option<String> {
        let mut parts = Vec::new();

        if !self.conditions.is_empty() {
            parts.push(
                self.conditions
                    .iter()
                    .map(Self::describe_condition)
                    .collect::<Vec<_>>()
                    .join(" AND "),
            );
        }

        parts.extend(
            self.or_groups
                .iter()
                .map(Self::describe_or_group)
                .filter(|group| !group.is_empty()),
        );

        if !self.having_conditions.is_empty() {
            parts.push(format!("HAVING {}", self.having_conditions.join(" AND ")));
        }

        if parts.is_empty() {
            None
        } else {
            Some(parts.join(" AND "))
        }
    }

    pub(crate) fn build_query_error_context(
        &self,
        query: Option<String>,
    ) -> crate::error::ErrorContext {
        let mut context = crate::error::ErrorContext::new()
            .table(M::table_name())
            .conditions(self.error_context_conditions());

        if let Some(operator_chain) = self.error_context_operator_chain() {
            context = context.operator_chain(operator_chain);
        }

        if let Some(query) = query {
            context = context.query(query);
        }

        context
    }

    pub(super) fn log_query(&self, sql: &str) {
        if std::env::var("TIDE_LOG_QUERIES")
            .map(|value| value.eq_ignore_ascii_case("true") || value == "1")
            .unwrap_or(false)
        {
            crate::tide_debug!("Query: {}", sql);
        }

        if crate::logging::QueryLogger::is_enabled() {
            let entry = crate::logging::QueryLogEntry::new(sql).with_table(M::table_name());
            crate::logging::QueryLogger::log(entry);
        }
    }

    pub fn debug(&self) -> crate::logging::QueryDebugInfo {
        use crate::logging::QueryDebugInfo;

        let (parameterized_sql, params) = self.build_select_sql_with_params();
        let preview_sql = self.build_sql_preview();
        let mut info = QueryDebugInfo::new(M::table_name()).with_sql(preview_sql.clone());
        info.params = params
            .into_iter()
            .map(|value| format!("{:?}", value))
            .collect();

        for condition in &self.conditions {
            info.add_condition(Self::describe_condition(condition));
        }

        for (column, direction) in &self.order_by {
            info.add_order_by(format!("{} {}", column, direction.as_str()));
        }

        info.group_by = self.group_by.clone();
        info.limit = self.limit_value;
        info.offset = self.offset_value;

        if !self.raw_select_expressions.is_empty() {
            info.select = self.raw_select_expressions.clone();
        } else if let Some(columns) = &self.select_columns {
            info.select = columns.clone();
        }

        for join in &self.joins {
            info.joins.push(format!(
                "{:?} JOIN {} ON {} = {}",
                join.join_type, join.table, join.left_column, join.right_column
            ));
        }

        if !parameterized_sql.is_empty() {
            info.sql = format!(
                "{}\n-- PARAMETERIZED SQL\n{}",
                preview_sql, parameterized_sql
            );
        }

        info
    }

    pub fn build_sql_preview(&self) -> String {
        self.build_sql_preview_for_db(self.db_type_for_sql())
    }

    pub(crate) fn build_sql_preview_for_db(&self, db_type: DatabaseType) -> String {
        format!(
            "-- DEBUG PREVIEW (not executable, values are approximate)\n{}",
            self.build_select_sql_for_db(db_type)
        )
    }
}