rqb 0.1.7

sqlx-first typed Postgres query builder.
Documentation
use super::*;

impl Renderer {
    pub(super) fn render_value(&mut self, expr: &ValueExpr) {
        match expr {
            ValueExpr::Field { meta, qualifier } => {
                self.render_field(meta, qualifier.as_deref());
            }
            ValueExpr::Excluded(field) => {
                self.sql.push_str("EXCLUDED.");
                write_quoted_ident(&mut self.sql, field.db);
            }
            ValueExpr::Param(param) => {
                self.push_param(param.clone());
            }
            ValueExpr::Null => {
                self.sql.push_str("NULL");
            }
            ValueExpr::SqlLiteral(value) => {
                self.render_sql_literal(value);
            }
            ValueExpr::Keyword(keyword) => {
                self.sql.push_str(keyword);
            }
            ValueExpr::Function { name, args } => self.render_call(name, args),
            ValueExpr::Aggregate {
                name,
                args,
                distinct,
                order_by,
                filter,
                over,
            } => {
                self.render_aggregate(name, args, *distinct, order_by);
                if let Some(filter) = filter {
                    self.sql.push_str(" FILTER (WHERE ");
                    self.render_bool(filter);
                    self.sql.push(')');
                }
                if let Some(spec) = over {
                    self.sql.push_str(" OVER (");
                    self.render_window_spec(spec);
                    self.sql.push(')');
                }
            }
            ValueExpr::OrderedSetAggregate {
                name,
                args,
                within_group,
                filter,
            } => {
                self.render_ordered_set_aggregate(name, args, within_group);
                if let Some(filter) = filter {
                    self.sql.push_str(" FILTER (WHERE ");
                    self.render_bool(filter);
                    self.sql.push(')');
                }
            }
            ValueExpr::Case { branches, else_ } => {
                self.sql.push_str("CASE");
                for (when, then) in branches {
                    self.sql.push_str(" WHEN ");
                    self.render_bool(when);
                    self.sql.push_str(" THEN ");
                    self.render_value(then);
                }
                if let Some(else_) = else_ {
                    self.sql.push_str(" ELSE ");
                    self.render_value(else_);
                }
                self.sql.push_str(" END");
            }
            ValueExpr::Cast { expr, pg } => {
                self.sql.push_str("CAST(");
                self.render_value(expr);
                self.sql.push_str(" AS ");
                self.sql.push_str(pg);
                self.sql.push(')');
            }
            ValueExpr::Binary { left, op, right } => {
                self.sql.push('(');
                self.render_operand(left);
                self.sql.push(' ');
                self.sql.push_str(op.as_sql());
                self.sql.push(' ');
                self.render_operand(right);
                self.sql.push(')');
            }
            ValueExpr::Subscript { expr, index } => {
                self.render_subscript_base(expr);
                self.sql.push('[');
                self.render_value(index);
                self.sql.push(']');
            }
            ValueExpr::Slice { expr, start, end } => {
                self.render_subscript_base(expr);
                self.sql.push('[');
                if let Some(start) = start {
                    self.render_value(start);
                }
                self.sql.push(':');
                if let Some(end) = end {
                    self.render_value(end);
                }
                self.sql.push(']');
            }
            ValueExpr::Array(values) => {
                self.sql.push_str("ARRAY[");
                for (idx, value) in values.iter().enumerate() {
                    if idx > 0 {
                        self.sql.push_str(", ");
                    }
                    self.render_value(value);
                }
                self.sql.push(']');
            }
            ValueExpr::Row(values) => {
                self.sql.push_str("ROW(");
                for (idx, value) in values.iter().enumerate() {
                    if idx > 0 {
                        self.sql.push_str(", ");
                    }
                    self.render_value(value);
                }
                self.sql.push(')');
            }
            ValueExpr::Extract { field, expr } => {
                self.sql.push_str("extract(");
                self.sql.push_str(field);
                self.sql.push_str(" FROM ");
                self.render_value(expr);
                self.sql.push(')');
            }
            ValueExpr::Window {
                function,
                args,
                spec,
            } => {
                self.sql.push_str(function.as_sql());
                self.sql.push('(');
                for (idx, arg) in args.iter().enumerate() {
                    if idx > 0 {
                        self.sql.push_str(", ");
                    }
                    self.render_value(arg);
                }
                self.sql.push_str(") OVER (");
                self.render_window_spec(spec);
                self.sql.push(')');
            }
            ValueExpr::Raw { sql, params } => self.render_raw(sql, params),
            ValueExpr::Subquery(stmt) => {
                self.sql.push('(');
                self.render_stmt(stmt);
                self.sql.push(')');
            }
            ValueExpr::InvalidAggregateModifier { .. } => unreachable!(
                "invalid aggregate modifiers must be rejected during validation before rendering"
            ),
        }
    }

    pub(super) fn render_operand(&mut self, expr: &ValueExpr) {
        if matches!(expr, ValueExpr::Raw { .. }) {
            self.sql.push('(');
            self.render_value(expr);
            self.sql.push(')');
        } else {
            self.render_value(expr);
        }
    }

    fn render_subscript_base(&mut self, expr: &ValueExpr) {
        // A chain must remain a[i][j], not (a[i])[j]: PG arrays are multidimensional.
        if matches!(
            expr,
            ValueExpr::Field { .. }
                | ValueExpr::Param(_)
                | ValueExpr::Subscript { .. }
                | ValueExpr::Slice { .. }
        ) {
            self.render_value(expr);
        } else {
            self.sql.push('(');
            self.render_value(expr);
            self.sql.push(')');
        }
    }

    pub(super) fn render_call(&mut self, name: &str, args: &[ValueExpr]) {
        self.sql.push_str(name);
        self.sql.push('(');
        for (idx, arg) in args.iter().enumerate() {
            if idx > 0 {
                self.sql.push_str(", ");
            }
            self.render_value(arg);
        }
        self.sql.push(')');
    }

    fn render_sql_literal(&mut self, value: &str) {
        self.sql.push('\'');
        if value.contains('\'') {
            for ch in value.chars() {
                if ch == '\'' {
                    self.sql.push('\'');
                }
                self.sql.push(ch);
            }
        } else {
            self.sql.push_str(value);
        }
        self.sql.push('\'');
    }

    pub(super) fn render_aggregate(
        &mut self,
        name: &str,
        args: &[ValueExpr],
        distinct: bool,
        order_by: &[crate::OrderItem],
    ) {
        self.sql.push_str(name);
        self.sql.push('(');
        if distinct {
            self.sql.push_str("DISTINCT ");
        }
        if args.is_empty() {
            self.sql.push('*');
        } else {
            for (idx, arg) in args.iter().enumerate() {
                if idx > 0 {
                    self.sql.push_str(", ");
                }
                self.render_value(arg);
            }
        }
        self.render_order_clause(" ORDER BY ", order_by);
        self.sql.push(')');
    }

    pub(super) fn render_ordered_set_aggregate(
        &mut self,
        name: &str,
        args: &[ValueExpr],
        within_group: &[crate::OrderItem],
    ) {
        self.sql.push_str(name);
        self.sql.push('(');
        for (idx, arg) in args.iter().enumerate() {
            if idx > 0 {
                self.sql.push_str(", ");
            }
            self.render_value(arg);
        }
        self.sql.push_str(") WITHIN GROUP (ORDER BY ");
        self.render_order_items(within_group);
        self.sql.push(')');
    }

    pub(super) fn render_window_spec(&mut self, spec: &WindowSpec) {
        let mut needs_space = false;
        if !spec.partition_by.is_empty() {
            self.sql.push_str("PARTITION BY ");
            for (idx, expr) in spec.partition_by.iter().enumerate() {
                if idx > 0 {
                    self.sql.push_str(", ");
                }
                self.render_value(expr);
            }
            needs_space = true;
        }
        if !spec.order_by.is_empty() {
            if needs_space {
                self.sql.push(' ');
            }
            self.render_order_clause("ORDER BY ", &spec.order_by);
            needs_space = true;
        }
        if let Some(frame) = &spec.frame {
            if needs_space {
                self.sql.push(' ');
            }
            self.render_window_frame(frame);
        }
    }

    pub(super) fn render_window_frame(&mut self, frame: &WindowFrame) {
        self.sql.push_str(frame.kind.as_sql());
        if let Some(end) = &frame.end {
            self.sql.push_str(" BETWEEN ");
            self.render_frame_bound(&frame.start);
            self.sql.push_str(" AND ");
            self.render_frame_bound(end);
        } else {
            self.sql.push(' ');
            self.render_frame_bound(&frame.start);
        }
        if let Some(exclude) = frame.exclude {
            self.sql.push(' ');
            self.sql.push_str(exclude.as_sql());
        }
    }

    pub(super) fn render_frame_bound(&mut self, bound: &FrameBound) {
        match bound {
            FrameBound::UnboundedPreceding => self.sql.push_str("UNBOUNDED PRECEDING"),
            FrameBound::Preceding(expr) => {
                self.render_value(expr);
                self.sql.push_str(" PRECEDING");
            }
            FrameBound::CurrentRow => self.sql.push_str("CURRENT ROW"),
            FrameBound::Following(expr) => {
                self.render_value(expr);
                self.sql.push_str(" FOLLOWING");
            }
            FrameBound::UnboundedFollowing => self.sql.push_str("UNBOUNDED FOLLOWING"),
        }
    }
}