aedb 0.3.1

Embedded Rust storage engine with transactional commits, WAL durability, and snapshot-consistent reads
Documentation
use crate::catalog::schema::TableSchema;
use crate::catalog::types::{Row, Value};
use crate::commit::validation::TableUpdateExpr;
use crate::error::AedbError;

pub(crate) fn extract_primary_key_values(
    schema: &TableSchema,
    row: &Row,
) -> Result<Vec<Value>, AedbError> {
    let mut primary_key = Vec::with_capacity(schema.primary_key.len());
    for pk_name in &schema.primary_key {
        let column_index = schema
            .columns
            .iter()
            .position(|c| c.name == *pk_name)
            .ok_or_else(|| {
                AedbError::Validation(format!("primary key column missing: {pk_name}"))
            })?;
        let value = row.values.get(column_index).ok_or_else(|| {
            AedbError::Validation(format!(
                "row missing primary key value for column: {pk_name}"
            ))
        })?;
        primary_key.push(value.clone());
    }
    Ok(primary_key)
}

pub(crate) fn apply_table_update_exprs(
    schema: &TableSchema,
    row: &Row,
    updates: &[(String, TableUpdateExpr)],
) -> Result<Row, AedbError> {
    let mut next = row.clone();
    for (target_column, expr) in updates {
        let target_idx = schema
            .columns
            .iter()
            .position(|c| c.name == *target_column)
            .ok_or_else(|| AedbError::UnknownColumn {
                table: schema.table_name.clone(),
                column: target_column.clone(),
            })?;
        let current = next.values.get(target_idx).cloned().ok_or_else(|| {
            AedbError::Validation(format!(
                "target column index out of bounds: {target_column}"
            ))
        })?;
        let value = match expr {
            TableUpdateExpr::Value(value) => value.clone(),
            TableUpdateExpr::CopyColumn(source_column) => {
                let source_idx = schema
                    .columns
                    .iter()
                    .position(|c| c.name == *source_column)
                    .ok_or_else(|| AedbError::UnknownColumn {
                        table: schema.table_name.clone(),
                        column: source_column.clone(),
                    })?;
                next.values.get(source_idx).cloned().ok_or_else(|| {
                    AedbError::Validation(format!(
                        "source column index out of bounds: {source_column}"
                    ))
                })?
            }
            TableUpdateExpr::AddI64(delta) => match current {
                Value::Integer(current) => current
                    .checked_add(*delta)
                    .map(Value::Integer)
                    .ok_or(AedbError::Overflow)?,
                _ => {
                    return Err(AedbError::Validation(format!(
                        "AddI64 requires Integer current value for column {target_column}"
                    )));
                }
            },
            TableUpdateExpr::Coalesce(fallback) => {
                if matches!(current, Value::Null) {
                    fallback.clone()
                } else {
                    current
                }
            }
        };
        next.values[target_idx] = value;
    }
    Ok(next)
}