athena_rs 0.76.0

WIP Database API gateway
Documentation
use anyhow::Result;
use serde_json::Value;
use std::str::Chars;

/// Determines the placeholder and bind values for inserts, treating JSON null as SQL NULL.
pub fn build_insert_placeholders<'a>(values: &[&'a Value]) -> (Vec<String>, Vec<&'a Value>) {
    let mut placeholders: Vec<String> = Vec::with_capacity(values.len());
    let mut bind_values: Vec<&Value> = Vec::new();
    let mut next_param_index = 1;

    for value in values {
        if value.is_null() {
            placeholders.push("NULL".to_string());
        } else {
            placeholders.push(format!("${}", next_param_index));
            bind_values.push(value);
            next_param_index += 1;
        }
    }

    (placeholders, bind_values)
}

/// Simple condition used when building SQL queries.
#[derive(Debug)]
pub struct Condition {
    pub column: String,
    pub value: Value,
}

impl Condition {
    /// Builds an equality condition for the given column and value.
    pub fn eq(column: impl Into<String>, value: impl Into<Value>) -> Self {
        Self {
            column: column.into(),
            value: value.into(),
        }
    }
}

/// Quotes identifiers to keep SQL generation safe.
pub fn sanitize_identifier(identifier: &str) -> Option<String> {
    let mut chars: Chars<'_> = identifier.chars();
    let first: char = chars.next()?;
    if !(first.is_ascii_alphabetic() || first == '_') {
        return None;
    }
    if !chars.all(|c| c.is_ascii_alphanumeric() || c == '_') {
        return None;
    }
    // Quote the identifier to handle reserved keywords and mixed case
    Some(format!("\"{}\"", identifier))
}

/// Builds a WHERE clause for a list of equality conditions.
pub fn build_where_clause(
    conditions: &[Condition],
    start_index: usize,
) -> Result<(String, Vec<Value>)> {
    let mut clause: String = String::new();
    let mut values: Vec<Value> = Vec::new();
    let mut idx: usize = start_index;

    for condition in conditions {
        if let Some(column) = sanitize_identifier(&condition.column) {
            if clause.is_empty() {
                clause.push_str(" WHERE ");
            } else {
                clause.push_str(" AND ");
            }
            clause.push_str(&format!("t.{} = ${}", column, idx));
            idx += 1;
            values.push(condition.value.clone());
        }
    }

    Ok((clause, values))
}