hamelin_legacy 0.4.2

Legacy AST translation code for Hamelin (to be deprecated)
Documentation
use std::mem::swap;

use hamelin_lib::operator::Operator;
use hamelin_lib::sql::expression::apply::BinaryOperatorApply;
use hamelin_lib::sql::expression::identifier::Identifier;
use hamelin_lib::sql::expression::{OrderByExpression, SQLExpression};
use hamelin_lib::sql::query::projection::{Binding, Projection};
use hamelin_lib::sql::query::SQLQuery;

use crate::env::Environment;

/// Prepend projections onto the front of a query.
///
/// Two things to keep in mind: (1) If these new projections have expressions that make
/// references to columns that are defined in the current list of projections, those are pushed
/// to a subquery. (2) If these new projections simply shadow columns that are defined in
/// the current list of projections, those projections are just removed. The new one wins.
///
/// # Arguments
/// - `query` The query to modify.
/// - `projections` The new set of projections to prepend.
/// - `env` The environment that the query's select should maintain through this transformation.
pub fn prepend_projections(
    query: &SQLQuery,
    mut projections: Vec<Projection>,
    env: &Environment,
) -> SQLQuery {
    let mut ret = query.clone();

    if ret.references_column_in_projections(&projections) {
        ret = ret.push_down();
    }

    let new_names: Vec<Identifier> = projections
        .iter()
        .map(|p| match p {
            Projection::Binding(Binding { name, .. }) => name.clone().into(),
            Projection::ColumnProjection(cp) => cp.identifier.clone(),
        })
        .collect();

    ret = ret.remove_projections(&new_names[..]);
    swap(&mut ret.projections, &mut projections);
    ret.projections.extend(projections.iter().cloned());
    for projection in env.get_column_projections() {
        if ret.projections.iter().all(|p| match p {
            Projection::ColumnProjection(cp) => {
                cp.identifier.last() != projection.identifier.last()
            }
            Projection::Binding(Binding { name, .. }) => {
                let id: Identifier = name.clone().into();
                id != projection.identifier
            }
        }) {
            ret.projections.push(projection.into());
        }
    }

    ret
}

pub fn add_filter_condition(
    query: &SQLQuery,
    condition: SQLExpression,
    env: &Environment,
) -> SQLQuery {
    let mut ret = query.clone();
    if ret.references_columns_in_column_refs(&condition.get_column_references()[..]) {
        ret = ret.push_down().select(
            env.get_column_projections()
                .into_iter()
                .map(|cp| cp.into())
                .collect(),
        );
    }
    let new_where = ret
        .where_
        .map(|current| {
            BinaryOperatorApply::new(
                Operator::And.try_into().unwrap(),
                condition.clone(),
                current.clone(),
            )
            .into()
        })
        .unwrap_or(condition);
    ret.where_ = Some(new_where);
    ret
}

pub fn add_order_expression(
    query: SQLQuery,
    order_by: Vec<OrderByExpression>,
    env: &Environment,
) -> SQLQuery {
    let push_down_needed = order_by
        .iter()
        .map(|e| SQLExpression::from(e.clone()).get_column_references())
        .any(|c| query.references_columns_in_column_refs(&c[..]));
    let ret_query = if push_down_needed {
        query.push_down().select(
            env.get_column_projections()
                .into_iter()
                .map(|cp| Projection::from(cp))
                .collect(),
        )
    } else {
        query.clone()
    };
    ret_query.order_by(order_by)
}

pub fn apply_limit(query: SQLQuery, limit: SQLExpression, env: &Environment) -> SQLQuery {
    if query.limit.is_some() {
        query
            .push_down()
            .select(
                env.get_column_projections()
                    .into_iter()
                    .map(|cp| Projection::from(cp))
                    .collect(),
            )
            .limit(limit)
    } else {
        query.limit(limit)
    }
}