qail-core 1.3.1

AST-native query builder - type-safe expressions, zero SQL strings
Documentation
use crate::transpiler::traits::SqlGenerator;

pub(crate) fn split_table_reference(reference: &str) -> Option<(&str, Option<&str>)> {
    let parts = reference.split_whitespace().collect::<Vec<_>>();
    match parts.as_slice() {
        [] => None,
        [table] => Some((table, None)),
        [table, alias] => Some((table, Some(alias))),
        [table, as_keyword, alias] if as_keyword.eq_ignore_ascii_case("as") => {
            Some((table, Some(alias)))
        }
        _ => None,
    }
}

pub(crate) fn render_table_reference(reference: &str, generator: &dyn SqlGenerator) -> String {
    match split_table_reference(reference) {
        Some((table, Some(alias))) => format!(
            "{} {}",
            generator.quote_identifier(table),
            generator.quote_identifier(alias)
        ),
        Some((table, None)) => generator.quote_identifier(table),
        None => generator.quote_identifier(reference),
    }
}

pub(crate) fn table_reference_base(reference: &str) -> &str {
    split_table_reference(reference)
        .map(|(table, _)| table)
        .unwrap_or(reference)
}

pub(crate) fn table_reference_sql_qualifier(reference: &str) -> Option<&str> {
    split_table_reference(reference).map(|(table, alias)| alias.unwrap_or(table))
}

pub(crate) fn qualifier_for_column_reference<'a>(
    reference: &'a str,
    qualifier: &str,
) -> Option<&'a str> {
    let (table, alias) = split_table_reference(reference)?;
    if alias.is_some_and(|alias| alias == qualifier) || table == qualifier {
        Some(alias.unwrap_or(table))
    } else {
        None
    }
}

pub(crate) fn qualifier_for_column_path<'a>(
    reference: &'a str,
    parts: &[&str],
) -> Option<(&'a str, usize)> {
    let (table, alias) = split_table_reference(reference)?;
    if parts.is_empty() {
        return None;
    }

    if let Some(alias) = alias
        && ident_eq(alias, parts[0])
    {
        return Some((alias, 1));
    }

    let table_parts = table.split('.').collect::<Vec<_>>();
    if parts.len() > table_parts.len()
        && table_parts
            .iter()
            .zip(parts)
            .all(|(expected, actual)| ident_eq(expected, actual))
    {
        return Some((alias.unwrap_or(table), table_parts.len()));
    }

    if let Some(bare_table) = table_parts.last()
        && parts.len() > 1
        && ident_eq(bare_table, parts[0])
    {
        return Some((alias.unwrap_or(bare_table), 1));
    }

    None
}

fn ident_eq(left: &str, right: &str) -> bool {
    left.trim_matches('"')
        .eq_ignore_ascii_case(right.trim_matches('"'))
}