hamelin_lib 0.15.4

Core library for Hamelin query language
Documentation
//! Conditional functions: if, case, coalesce

use std::any::TypeId;

use anyhow::bail;

use crate::func::def::{FunctionDef, MatchTestFailure, ParameterBindingProvider, Parameters};
use crate::func::utils::{bindings_must_have_same_type, must_be_same_type};
use crate::types::array::Array;
use crate::types::matcher::{AnyMatcher, ArrayMatcher, ExactMatcher, TupleMatcher};
use crate::types::{Type, BOOLEAN};

/// If with 2 arguments: if(condition, then)
/// Returns then value if condition is true, NULL otherwise.
#[derive(Default, Clone)]
pub struct If2;

impl FunctionDef for If2 {
    fn name(&self) -> &'static str {
        "if"
    }

    fn parameters(&self) -> Parameters {
        Parameters::new()
            .with("condition", ExactMatcher::of(BOOLEAN))
            .with("then", AnyMatcher::default())
    }

    fn return_type(&self, bindings: &dyn ParameterBindingProvider) -> anyhow::Result<Type> {
        Ok(bindings.get_by_index(1)?.typ().clone())
    }

    fn type_id(&self) -> TypeId {
        TypeId::of::<Self>()
    }
}

/// If with 3 arguments: if(condition, then, else)
/// Returns then value if condition is true, else value otherwise.
#[derive(Default, Clone)]
pub struct If3;

impl FunctionDef for If3 {
    fn name(&self) -> &'static str {
        "if"
    }

    fn parameters(&self) -> Parameters {
        Parameters::new()
            .with("condition", ExactMatcher::of(BOOLEAN))
            .with("then", AnyMatcher::default())
            .with("else", AnyMatcher::default())
    }

    fn return_type(&self, bindings: &dyn ParameterBindingProvider) -> anyhow::Result<Type> {
        must_be_same_type(
            vec![
                bindings.get_by_index(1)?.typ(),
                bindings.get_by_index(2)?.typ(),
            ]
            .into_iter(),
        )
    }

    fn type_id(&self) -> TypeId {
        TypeId::of::<Self>()
    }
}

/// Case expression: case((cond1, val1), (cond2, val2), ...)
/// Returns the value corresponding to the first true condition.
#[derive(Default, Clone)]
pub struct Case;

impl FunctionDef for Case {
    fn name(&self) -> &'static str {
        "case"
    }

    fn parameters(&self) -> Parameters {
        Parameters::new().with_var_args(TupleMatcher::of(
            ExactMatcher::of(BOOLEAN),
            AnyMatcher::default(),
        ))
    }

    fn return_type(&self, bindings: &dyn ParameterBindingProvider) -> anyhow::Result<Type> {
        if bindings.len() == 0 {
            bail!(MatchTestFailure::msg("At least one argument is required"));
        }

        must_be_same_type(bindings.iter().map(|b| match b.typ() {
            Type::Tuple(t) => t.elements[1].as_ref(),
            _ => unreachable!(),
        }))
    }

    fn type_id(&self) -> TypeId {
        TypeId::of::<Self>()
    }
}

/// Coalesce: coalesce(val1, val2, ...)
/// Returns the first non-null value.
#[derive(Default, Clone)]
pub struct Coalesce;

impl FunctionDef for Coalesce {
    fn name(&self) -> &'static str {
        "coalesce"
    }

    fn parameters(&self) -> Parameters {
        Parameters::new().with_var_args(AnyMatcher::default())
    }

    fn return_type(&self, bindings: &dyn ParameterBindingProvider) -> anyhow::Result<Type> {
        bindings_must_have_same_type(bindings)
    }

    fn type_id(&self) -> TypeId {
        TypeId::of::<Self>()
    }
}

/// Element-wise coalesce over an array with a scalar default:
/// array_coalesce(array<T>, T) -> array<T>. Replaces each null element of the
/// array with the default. The outer array null/emptiness is preserved.
///
/// **Internal-only.** Users write `coalesce(arr, lit)`, which broadcasts via
/// element-type substitution into `transform(arr, x -> coalesce(x, lit))`;
/// the `lower_transform` fast-path matcher rewrites that to a direct call
/// against this function. This function is never resolved by the user-facing
/// name resolver, so it does not interfere with broadcast coalesce.
#[derive(Default, Clone)]
pub struct ArrayCoalesce;

impl FunctionDef for ArrayCoalesce {
    fn name(&self) -> &'static str {
        "array_coalesce"
    }

    fn parameters(&self) -> Parameters {
        Parameters::new()
            .with("array", ArrayMatcher::default())
            .with("default", AnyMatcher::default())
    }

    fn return_type(&self, bindings: &dyn ParameterBindingProvider) -> anyhow::Result<Type> {
        let array_type = bindings.get_by_index(0)?.typ();
        let default_type = bindings.get_by_index(1)?.typ();
        let element_type = match array_type {
            Type::Array(arr) => arr.element_type.as_ref(),
            _ => bail!(MatchTestFailure::msg(
                "array_coalesce(array, default): first argument must be array"
            )),
        };
        // Defer to merge() to allow Unknown (null literal) and decimal precision widening.
        let merged = element_type
            .clone()
            .merge(default_type.clone())
            .map_err(|e| anyhow::anyhow!(MatchTestFailure::wrap(e)))?;
        Ok(Array::new(merged).into())
    }

    fn type_id(&self) -> TypeId {
        TypeId::of::<Self>()
    }
}