reddb-io-server 1.2.0

RedDB server-side engine: storage, runtime, replication, MCP, AI, and the gRPC/HTTP/RedWire/PG-wire dispatchers. Re-exported by the umbrella `reddb` crate.
Documentation
use std::collections::HashSet;

use super::CreateTableQuery;
use crate::storage::schema::{DataType, SqlTypeName};

#[derive(Debug, Clone)]
pub enum AnalysisError {
    DuplicateColumn(String),
    UnsupportedType(String),
}

impl std::fmt::Display for AnalysisError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::DuplicateColumn(name) => write!(f, "duplicate column name: {name}"),
            Self::UnsupportedType(name) => write!(f, "unsupported SQL type: {name}"),
        }
    }
}

impl std::error::Error for AnalysisError {}

#[derive(Debug, Clone)]
pub struct AnalyzedCreateTableQuery {
    pub name: String,
    pub columns: Vec<AnalyzedColumnDef>,
    pub if_not_exists: bool,
    pub default_ttl_ms: Option<u64>,
    pub context_index_fields: Vec<String>,
    pub timestamps: bool,
}

#[derive(Debug, Clone)]
pub struct AnalyzedColumnDef {
    pub name: String,
    pub declared_type: SqlTypeName,
    pub storage_type: DataType,
    pub not_null: bool,
    pub default: Option<String>,
    pub primary_key: bool,
    pub unique: bool,
}

pub fn analyze_create_table(
    query: &CreateTableQuery,
) -> Result<AnalyzedCreateTableQuery, AnalysisError> {
    let mut seen = HashSet::new();
    let mut columns = Vec::with_capacity(query.columns.len());

    for column in &query.columns {
        if !seen.insert(column.name.to_ascii_lowercase()) {
            return Err(AnalysisError::DuplicateColumn(column.name.clone()));
        }

        columns.push(AnalyzedColumnDef {
            name: column.name.clone(),
            declared_type: column.sql_type.clone(),
            storage_type: resolve_sql_type_name(&column.sql_type)?,
            not_null: column.not_null,
            default: column.default.clone(),
            primary_key: column.primary_key,
            unique: column.unique,
        });
    }

    Ok(AnalyzedCreateTableQuery {
        name: query.name.clone(),
        columns,
        if_not_exists: query.if_not_exists,
        default_ttl_ms: query.default_ttl_ms,
        context_index_fields: query.context_index_fields.clone(),
        timestamps: query.timestamps,
    })
}

pub fn resolve_declared_data_type(declared: &str) -> Result<DataType, AnalysisError> {
    resolve_sql_type_name(&SqlTypeName::parse_declared(declared))
}

pub fn resolve_sql_type_name(sql_type: &SqlTypeName) -> Result<DataType, AnalysisError> {
    DataType::from_sql_type_name(sql_type)
        .ok_or_else(|| AnalysisError::UnsupportedType(sql_type.base_name()))
}