udb 0.2.1

Universal Data Broker — a Rust gRPC broker over multiple databases (Postgres, MySQL, SQLite, MongoDB, ClickHouse, Cassandra, MSSQL, Redis, Qdrant, S3, Neo4j, …) with per-tenant RLS, 2PC, sagas, and CDC.
Documentation
use crate::generation::manifest::ManifestStore;

/// Wall-clock seconds since the Unix epoch, for the `generated_at` metadata
/// field embedded in non-SQL backend artifacts (#210). Single-sourced here so
/// the six backend generators don't each carry an identical copy. Note: this
/// value is intentionally NOT part of any artifact checksum/dedup decision —
/// `sync` keys on the embedded `proto_manifest_checksum` header — so a changing
/// timestamp never triggers a spurious re-migration.
pub(super) fn generated_at_unix() -> u64 {
    std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .map(|d| d.as_secs())
        .unwrap_or_default()
}

pub(super) fn store_opt_str_any<'a>(store: &'a ManifestStore, keys: &[&str]) -> Option<&'a str> {
    keys.iter().find_map(|key| {
        store
            .options
            .iter()
            .find(|option| option.key == *key)
            .map(|option| option.value.trim())
            .filter(|value| !value.is_empty())
    })
}

pub(super) fn store_opt_i64_any(store: &ManifestStore, keys: &[&str], default: i64) -> i64 {
    store_opt_str_any(store, keys)
        .and_then(|value| value.parse::<i64>().ok())
        .unwrap_or(default)
}

pub(super) fn store_opt_bool_any(store: &ManifestStore, keys: &[&str], default: bool) -> bool {
    store_opt_str_any(store, keys)
        .map(|value| matches!(value.to_ascii_lowercase().as_str(), "true" | "1" | "yes"))
        .unwrap_or(default)
}

pub(super) fn is_safe_identifier(value: &str) -> bool {
    let mut chars = value.chars();
    matches!(chars.next(), Some(ch) if ch.is_ascii_alphabetic() || ch == '_')
        && chars.all(|ch| ch.is_ascii_alphanumeric() || ch == '_')
}

pub(super) fn safe_identifier(value: &str, fallback: &str) -> String {
    let candidate = value.trim();
    if is_safe_identifier(candidate) {
        return candidate.to_string();
    }

    let mut out = String::new();
    for ch in candidate.chars() {
        if ch.is_ascii_alphanumeric() || ch == '_' {
            out.push(ch);
        } else if ch == '-' || ch == '.' || ch == ':' || ch.is_whitespace() {
            out.push('_');
        }
    }
    if out.is_empty() || !is_safe_identifier(&out) {
        fallback.to_string()
    } else {
        out
    }
}

pub(super) fn safe_resource_name(value: &str, fallback: &str) -> String {
    let mut out = String::new();
    for ch in value.trim().chars() {
        if ch.is_ascii_alphanumeric() || matches!(ch, '_' | '-' | '.') {
            out.push(ch);
        } else {
            out.push('_');
        }
    }
    if out.is_empty() {
        fallback.to_string()
    } else {
        out
    }
}

pub(super) fn safe_comment_value(value: &str) -> String {
    value
        .chars()
        .map(|ch| if ch == '\r' || ch == '\n' { ' ' } else { ch })
        .collect()
}

pub(super) fn quote_clickhouse_identifier(value: &str) -> String {
    format!("`{}`", value.replace('`', "``"))
}

pub(super) fn sanitize_filename(s: &str) -> String {
    s.chars()
        .map(|c| {
            if c.is_ascii_alphanumeric() || matches!(c, '-' | '_' | '.') {
                c
            } else {
                '_'
            }
        })
        .collect()
}