hamelin_translation 0.9.6

Lowering and IR for Hamelin query language
Documentation
//! Pipeline pass: DISTINCT → AGG lowering.
//!
//! Converts top-level DISTINCT (deduplicate by keys) into an AGG with no aggregates
//! and matching AST, so downstream passes and backends see ordinary GROUP BY semantics.

use std::sync::Arc;

use hamelin_lib::err::TranslationError;
use hamelin_lib::tree::ast::command::{AggCommand, Command, CommandKind};
use hamelin_lib::tree::{
    builder,
    typed_ast::{
        command::TypedCommandKind, context::StatementTranslationContext, pipeline::TypedPipeline,
    },
};

fn distinct_command_to_agg_command(cmd: &Command) -> Command {
    match &cmd.kind {
        CommandKind::Distinct(d) => Command {
            span: cmd.span,
            kind: AggCommand {
                clauses: vec![],
                group_by: d.clauses.clone(),
                sort: vec![],
            }
            .into(),
        },
        _ => cmd.clone(),
    }
}

/// Lower DISTINCT commands to AGG with empty aggregate clause.
///
/// Contract: `Arc<TypedPipeline> -> Result<Arc<TypedPipeline>, ...>`
pub fn lower_distinct(
    pipeline: Arc<TypedPipeline>,
    ctx: &mut StatementTranslationContext,
) -> Result<Arc<TypedPipeline>, Arc<TranslationError>> {
    if !pipeline
        .valid_ref()?
        .commands
        .iter()
        .any(|cmd| matches!(cmd.kind, TypedCommandKind::Distinct(_)))
    {
        return Ok(pipeline);
    }

    let valid = pipeline.valid_ref()?;

    let mut pipe_builder = builder::pipeline();
    for cmd in &valid.commands {
        let ast = match &cmd.kind {
            TypedCommandKind::Distinct(_) => {
                Arc::new(distinct_command_to_agg_command(cmd.ast.as_ref()))
            }
            _ => cmd.ast.clone(),
        };
        pipe_builder = pipe_builder.command(ast);
    }

    let new_ast = pipe_builder.build().at(pipeline.ast.span);

    Ok(Arc::new(TypedPipeline::from_ast_with_context(
        Arc::new(new_ast),
        ctx,
    )))
}