querydown 0.0.1

An expressive DSL for writing relational database queries that compile to SQL
Documentation
use chumsky::Parser;

use crate::{
    compiling::sorting::SortingStack,
    converters::{convert_condition_set, convert_join_tree},
    dialects::dialect::Dialect,
    parsing::query::query,
    rendering::{Render, RenderingContext},
    schema::{primitive_schema::PrimitiveSchema, schema::Schema},
    sql_tree::{Column, Select, Simplify},
};

pub struct Compiler<D: Dialect> {
    dialect: D,
    schema: Schema,
}

impl<D: Dialect> Compiler<D> {
    pub fn new(schema_json: &str, dialect: D) -> Result<Self, String> {
        let primitive_schema = serde_json::from_str::<PrimitiveSchema>(schema_json)
            .map_err(|_| "Schema input is not valid JSON.")?;
        let schema = Schema::try_from(primitive_schema)?;
        Ok(Self { dialect, schema })
    }

    pub fn compile(&self, input: String) -> Result<String, String> {
        let mut query = query()
            .parse(input)
            // TODO_ERR improve error handling
            .map_err(|_| "Invalid querydown code".to_string())?;
        let base_table_name = std::mem::take(&mut query.base_table);
        let mut select = Select::from(base_table_name.clone());
        let mut cx = RenderingContext::build(&self.dialect, &self.schema, &base_table_name)?;
        let mut transformations_iter = query.transformations.into_iter();
        let first_transformation = transformations_iter.next().unwrap_or_default();
        let second_transformation = transformations_iter.next();
        if second_transformation.is_some() {
            return Err("Pipelines not yet supported".to_string());
        }

        select.condition_set = convert_condition_set(&first_transformation.condition_set, &mut cx);

        let mut sorting_stack = SortingStack::new();

        for column_spec in first_transformation.column_layout.column_specs {
            let expression = column_spec.expression.render(&mut cx);
            let alias = column_spec.alias;
            if let Some(sort_spec) = column_spec.column_control.sort {
                let expr = alias
                    .as_ref()
                    .map(|a| cx.dialect.quote_identifier(a))
                    .unwrap_or_else(|| expression.clone());
                sorting_stack.push(expr, sort_spec);
            }
            select.columns.push(Column { expression, alias });
        }

        select.sorting = sorting_stack.into();

        (select.joins, select.ctes) = convert_join_tree(cx.take_join_tree(), &cx);

        select.simplify();
        let mut result = select.render(&mut cx);
        result.push(';');
        Ok(result)
    }
}