iridium_core 0.1.0

SQL Server-compatible Rust engine core for Iridium SQL
Documentation
use crate::ast::Expr;
use crate::catalog::Catalog;
use crate::error::DbError;
use crate::storage::Storage;
use crate::types::Value;

use super::super::super::clock::Clock;
use super::super::super::context::ExecutionContext;
use super::super::super::evaluator::eval_expr;
use super::super::super::model::ContextTable;

pub(crate) fn eval_charindex(
    args: &[Expr],
    row: &[ContextTable],
    ctx: &mut ExecutionContext,
    catalog: &dyn Catalog,
    storage: &dyn Storage,
    clock: &dyn Clock,
) -> Result<Value, DbError> {
    if args.len() < 2 || args.len() > 3 {
        return Err(DbError::Execution(
            "CHARINDEX expects 2 or 3 arguments".into(),
        ));
    }
    let search = eval_expr(&args[0], row, ctx, catalog, storage, clock)?;
    let target = eval_expr(&args[1], row, ctx, catalog, storage, clock)?;

    if search.is_null() || target.is_null() {
        return Ok(Value::Null);
    }

    let search_str = search.to_string_value();
    let target_str = target.to_string_value();

    let start_pos = if args.len() == 3 {
        let sp = eval_expr(&args[2], row, ctx, catalog, storage, clock)?;
        sp.to_integer_i64().unwrap_or(1) as usize
    } else {
        1
    };

    let start_idx = if start_pos > 0 { start_pos - 1 } else { 0 };
    let result = if start_idx < target_str.len() {
        target_str[start_idx..]
            .find(&search_str)
            .map(|pos| (start_idx + pos + 1) as i64)
            .unwrap_or(0)
    } else {
        0
    };

    Ok(Value::Int(result as i32))
}

pub(crate) fn eval_patindex(
    args: &[Expr],
    row: &[ContextTable],
    ctx: &mut ExecutionContext,
    catalog: &dyn Catalog,
    storage: &dyn Storage,
    clock: &dyn Clock,
) -> Result<Value, DbError> {
    if args.len() != 2 {
        return Err(DbError::Execution("PATINDEX expects 2 arguments".into()));
    }
    let pattern_val = eval_expr(&args[0], row, ctx, catalog, storage, clock)?;
    let target_val = eval_expr(&args[1], row, ctx, catalog, storage, clock)?;

    if pattern_val.is_null() || target_val.is_null() {
        return Ok(Value::Null);
    }

    let pattern = pattern_val.to_string_value();
    let target = target_val.to_string_value();

    let pat = pattern.trim_start_matches('%').trim_end_matches('%');
    let starts_with_wild = pattern.starts_with('%');
    let ends_with_wild = pattern.ends_with('%');

    let result = if starts_with_wild && ends_with_wild {
        target.find(pat).map(|pos| (pos + 1) as i64).unwrap_or(0)
    } else if starts_with_wild {
        target.rfind(pat).map(|pos| (pos + 1) as i64).unwrap_or(0)
    } else if ends_with_wild {
        target.find(pat).map(|pos| (pos + 1) as i64).unwrap_or(0)
    } else if target == pat {
        1
    } else {
        0
    };

    Ok(Value::Int(result as i32))
}

pub(crate) fn eval_soundex(
    args: &[Expr],
    row: &[ContextTable],
    ctx: &mut ExecutionContext,
    catalog: &dyn Catalog,
    storage: &dyn Storage,
    clock: &dyn Clock,
) -> Result<Value, DbError> {
    if args.len() != 1 {
        return Err(DbError::Execution("SOUNDEX expects 1 argument".into()));
    }
    let val = eval_expr(&args[0], row, ctx, catalog, storage, clock)?;
    if val.is_null() {
        return Ok(Value::Null);
    }
    let s = val.to_string_value();
    Ok(Value::VarChar(soundex(&s)))
}

pub(crate) fn eval_difference(
    args: &[Expr],
    row: &[ContextTable],
    ctx: &mut ExecutionContext,
    catalog: &dyn Catalog,
    storage: &dyn Storage,
    clock: &dyn Clock,
) -> Result<Value, DbError> {
    if args.len() != 2 {
        return Err(DbError::Execution("DIFFERENCE expects 2 arguments".into()));
    }
    let s1 = eval_expr(&args[0], row, ctx, catalog, storage, clock)?;
    let s2 = eval_expr(&args[1], row, ctx, catalog, storage, clock)?;

    if s1.is_null() || s2.is_null() {
        return Ok(Value::Null);
    }

    let snd1 = soundex(&s1.to_string_value());
    let snd2 = soundex(&s2.to_string_value());

    let mut diff = 0;
    let chars1: Vec<char> = snd1.chars().collect();
    let chars2: Vec<char> = snd2.chars().collect();

    for i in 0..4 {
        if chars1[i] == chars2[i] {
            diff += 1;
        }
    }
    Ok(Value::Int(diff))
}

fn soundex(s: &str) -> String {
    let chars: Vec<char> = s
        .to_uppercase()
        .chars()
        .filter(|c| c.is_ascii_alphabetic())
        .collect();
    if chars.is_empty() {
        return "0000".to_string();
    }
    let mut result = String::with_capacity(4);
    result.push(chars[0]);

    let mut prev_code = soundex_code(chars[0]);
    for &c in &chars[1..] {
        let code = soundex_code(c);
        if code != '0' && code != prev_code {
            result.push(code);
            if result.len() == 4 {
                break;
            }
        }
        prev_code = code;
    }
    while result.len() < 4 {
        result.push('0');
    }
    result
}

fn soundex_code(c: char) -> char {
    match c {
        'B' | 'F' | 'P' | 'V' => '1',
        'C' | 'G' | 'J' | 'K' | 'Q' | 'S' | 'X' | 'Z' => '2',
        'D' | 'T' => '3',
        'L' => '4',
        'M' | 'N' => '5',
        'R' => '6',
        _ => '0',
    }
}