kosame 0.3.0

Macro-based Rust ORM focused on developer ergonomics
Documentation
use std::fmt::Write;

use kosame_repr::schema::Relation;
use kosame_sql::FmtSql;

use crate::driver::Connection;

use super::{Field, Node, Params, Query, Runner};

pub struct RecordArrayRunner {}

impl RecordArrayRunner {
    pub fn query_to_sql<D: kosame_sql::Dialect>(
        &self,
        query: &(impl Query + ?Sized),
    ) -> Result<String, kosame_sql::Error> {
        let mut sql = String::new();
        let mut formatter = kosame_sql::Formatter::<D>::new(&mut sql);
        fmt_node_sql(&mut formatter, query.repr(), None)?;
        Ok(sql)
    }
}

impl Runner for RecordArrayRunner {
    async fn run<'a, C, Q>(&self, connection: &mut C, query: &Q) -> crate::Result<Vec<Q::Row>>
    where
        C: Connection,
        Q: Query + ?Sized,
        Q::Params: Params<C::Params<'a>>,
        for<'b> Q::Row: From<&'b C::Row>,
    {
        let sql = self.query_to_sql::<C::Dialect>(query)?;
        let rows = connection
            .query(&sql, &query.params().to_driver())
            .await
            .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
        Ok(rows.iter().map(Q::Row::from).collect())
    }
}

fn fmt_node_sql<D: kosame_sql::Dialect>(
    formatter: &mut kosame_sql::Formatter<D>,
    node: &Node,
    relation: Option<&Relation>,
) -> std::fmt::Result {
    formatter.write_str("select ")?;

    if relation.is_some() {
        formatter.write_str("row(")?;
    }

    if node.star() {
        for (index, column) in node.table().columns().iter().enumerate() {
            column.name().fmt_sql(formatter)?;
            if index != node.table().columns().len() - 1 {
                formatter.write_str(", ")?;
            }
        }
        if !node.fields().is_empty() {
            formatter.write_str(", ")?;
        }
    }

    for (index, field) in node.fields().iter().enumerate() {
        match field {
            Field::Column { column, .. } => {
                column.name().fmt_sql(formatter)?;
            }
            Field::Relation { node, relation, .. } => {
                formatter.write_str("array(")?;
                fmt_node_sql::<D>(formatter, node, Some(relation))?;
                formatter.write_str(")")?;
            }
            Field::Expr { expr, .. } => {
                expr.fmt_sql(formatter)?;
            }
        }
        if index != node.fields().len() - 1 {
            formatter.write_str(", ")?;
        }
    }

    if relation.is_some() {
        formatter.write_str(")")?;
    }

    formatter.write_str(" from ")?;
    node.table().name().fmt_sql(formatter)?;

    if relation.is_some() || node.r#where().is_some() {
        formatter.write_str(" where ")?;
    }

    if relation.is_some() && node.r#where().is_some() {
        formatter.write_str("(")?;
    }

    if let Some(relation) = relation {
        for (index, (source_column, target_column)) in relation.column_pairs().enumerate() {
            relation.source_table().fmt_sql(formatter)?;
            formatter.write_str(".")?;
            source_column.name().fmt_sql(formatter)?;
            formatter.write_str(" = ")?;
            relation.target_table().fmt_sql(formatter)?;
            formatter.write_str(".")?;
            target_column.name().fmt_sql(formatter)?;
            if index != relation.source_columns().len() - 1 {
                formatter.write_str(" and ")?;
            }
        }
    }

    if relation.is_some() && node.r#where().is_some() {
        formatter.write_str(") and (")?;
    }

    if let Some(r#where) = &node.r#where() {
        r#where.expr().fmt_sql(formatter)?;
    }

    if relation.is_some() && node.r#where().is_some() {
        formatter.write_str(")")?;
    }

    if let Some(order_by) = &node.order_by() {
        order_by.fmt_sql(formatter)?;
    }

    if let Some(limit) = &node.limit() {
        limit.fmt_sql(formatter)?;
    }

    if let Some(offset) = &node.offset() {
        offset.fmt_sql(formatter)?;
    }

    Ok(())
}