iridium_core 0.1.12

SQL Server-compatible Rust engine core for Iridium SQL
Documentation
use crate::error::DbError;
use crate::types::Value;

use super::context::ExecutionContext;
use super::model::ContextTable;

pub(crate) fn resolve_identifier(
    row: &[ContextTable],
    name: &str,
    ctx: &ExecutionContext,
) -> Result<Value, DbError> {
    if name.starts_with("@@") {
        if let Some(val) = super::metadata::system_vars::resolve_system_variable(name, ctx)? {
            return Ok(val);
        }
    }
    if name.starts_with('@') {
        match ctx.session.variables.get(name) {
            Some((_, val)) => return Ok(val.clone()),
            None => {
                return Err(DbError::invalid_identifier(name));
            }
        }
    }

    let is_identity_col = name.eq_ignore_ascii_case("IDENTITYCOL");

    let mut matches: Vec<(usize, Value)> = Vec::new();
    for (binding_idx, binding) in row.iter().enumerate() {
        let col_idx = if is_identity_col {
            binding.table.columns.iter().position(|c| c.identity.is_some())
        } else {
            binding
                .table
                .columns
                .iter()
                .position(|c| c.name.eq_ignore_ascii_case(name))
        };

        if let Some(col_idx) = col_idx {
            let value = binding
                .row
                .as_ref()
                .map(|r| r.values[col_idx].clone())
                .unwrap_or(Value::Null);
            matches.push((binding_idx, value));
        }
    }

    if matches.is_empty() {
        for apply_row in ctx.row.apply_stack.iter().rev() {
            for binding in apply_row.iter() {
                let col_idx = if is_identity_col {
                    binding.table.columns.iter().position(|c| c.identity.is_some())
                } else {
                    binding
                        .table
                        .columns
                        .iter()
                        .position(|c| c.name.eq_ignore_ascii_case(name))
                };
                if let Some(col_idx) = col_idx {
                    let value = binding
                        .row
                        .as_ref()
                        .map(|r| r.values[col_idx].clone())
                        .unwrap_or(Value::Null);
                    matches.push((0, value));
                }
            }
            if !matches.is_empty() {
                break;
            }
        }
    }

    if matches.is_empty() {
        for outer_row in ctx.row.outer_stack.iter().rev() {
            for binding in outer_row.iter() {
                let col_idx = if is_identity_col {
                    binding.table.columns.iter().position(|c| c.identity.is_some())
                } else {
                    binding
                        .table
                        .columns
                        .iter()
                        .position(|c| c.name.eq_ignore_ascii_case(name))
                };
                if let Some(col_idx) = col_idx {
                    let value = binding
                        .row
                        .as_ref()
                        .map(|r| r.values[col_idx].clone())
                        .unwrap_or(Value::Null);
                    matches.push((0, value));
                }
            }
            if !matches.is_empty() {
                break;
            }
        }
    }

    match matches.len() {
        0 => Err(DbError::column_not_found(name)),
        1 => Ok(matches[0].1.clone()),
        _ => Err(DbError::invalid_identifier(format!(
            "ambiguous column '{}'",
            name
        ))),
    }
}

pub(crate) fn resolve_qualified_identifier(
    row: &[ContextTable],
    parts: &[String],
    ctx: &ExecutionContext,
) -> Result<Value, DbError> {
    if parts.len() != 2 {
        return Err(DbError::invalid_identifier(
            "only two-part identifiers are supported in this build",
        ));
    }

    let table_name = &parts[0];
    let column_name = &parts[1];
    let is_identity_col = column_name.eq_ignore_ascii_case("IDENTITYCOL");

    let search_row = |row: &[ContextTable]| -> Result<Option<Value>, DbError> {
        for binding in row {
            if binding.alias.eq_ignore_ascii_case(table_name)
                || binding.table.name.eq_ignore_ascii_case(table_name)
                || binding
                    .source_aliases
                    .iter()
                    .any(|a| a.eq_ignore_ascii_case(table_name))
            {
                let idx = if is_identity_col {
                    binding.table.columns.iter().position(|c| c.identity.is_some())
                } else {
                    binding
                        .table
                        .columns
                        .iter()
                        .position(|c| c.name.eq_ignore_ascii_case(column_name))
                };

                if let Some(idx) = idx {
                    return Ok(Some(
                        binding
                            .row
                            .as_ref()
                            .map(|r| r.values[idx].clone())
                            .unwrap_or(Value::Null),
                    ));
                } else {
                    return Err(DbError::column_not_found_qualified(table_name, column_name));
                }
            }
        }
        Ok(None)
    };

    if let Some(val) = search_row(row)? {
        return Ok(val);
    }

    for apply_row in ctx.row.apply_stack.iter().rev() {
        if let Some(val) = search_row(apply_row)? {
            return Ok(val);
        }
    }

    for outer_row in ctx.row.outer_stack.iter().rev() {
        if let Some(val) = search_row(outer_row)? {
            return Ok(val);
        }
    }

    Err(DbError::object_not_found(table_name))
}