real-rs 0.1.0

Universal query engine with relational algebra - compile the same query to PostgreSQL, SQLite, MongoDB, and YottaDB
Documentation
//! Schema definitions with compile-time type checking

use std::collections::HashMap;

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DataType {
    Integer,
    String,
    Boolean,
    Float,
    Bytes,
    /// For hierarchical backends - represents a path/key
    Hierarchical,
    /// Timestamp/DateTime types
    Timestamp,
    /// High-precision decimal for financial calculations
    Decimal,
    /// JSON/JSONB for nested documents
    Json,
    /// Array of values
    Array(Box<DataType>),
    /// Vector for semantic search (dimension count)
    Vector(usize),
}

#[derive(Debug, Clone)]
pub struct Column {
    pub name: String,
    pub data_type: DataType,
    pub nullable: bool,
}

#[derive(Debug, Clone)]
pub struct Schema {
    pub name: String,
    pub columns: Vec<Column>,
}

impl Schema {
    pub fn new(name: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            columns: Vec::new(),
        }
    }

    pub fn with_column(mut self, name: impl Into<String>, data_type: DataType) -> Self {
        self.columns.push(Column {
            name: name.into(),
            data_type,
            nullable: false,
        });
        self
    }

    pub fn with_nullable_column(mut self, name: impl Into<String>, data_type: DataType) -> Self {
        self.columns.push(Column {
            name: name.into(),
            data_type,
            nullable: true,
        });
        self
    }

    pub fn find_column(&self, name: &str) -> Option<&Column> {
        self.columns.iter().find(|c| c.name == name)
    }

    pub fn column_index(&self, name: &str) -> Option<usize> {
        self.columns.iter().position(|c| c.name == name)
    }
}

#[derive(Debug, Clone, PartialEq)]
pub enum Value {
    Integer(i64),
    String(String),
    Boolean(bool),
    Float(f64),
    Bytes(Vec<u8>),
    Null,
    /// Unix timestamp in milliseconds
    Timestamp(i64),
    /// Decimal as string to preserve precision
    Decimal(String),
    /// JSON as string
    Json(String),
    /// Array of values
    Array(Vec<Value>),
    /// Vector for embeddings
    Vector(Vec<f32>),
}

impl Value {
    pub fn type_of(&self) -> DataType {
        match self {
            Value::Integer(_) => DataType::Integer,
            Value::String(_) => DataType::String,
            Value::Boolean(_) => DataType::Boolean,
            Value::Float(_) => DataType::Float,
            Value::Bytes(_) => DataType::Bytes,
            Value::Null => DataType::String, // Default
            Value::Timestamp(_) => DataType::Timestamp,
            Value::Decimal(_) => DataType::Decimal,
            Value::Json(_) => DataType::Json,
            Value::Array(arr) => {
                if let Some(first) = arr.first() {
                    DataType::Array(Box::new(first.type_of()))
                } else {
                    DataType::Array(Box::new(DataType::String))
                }
            }
            Value::Vector(v) => DataType::Vector(v.len()),
        }
    }
}

pub type Row = Vec<Value>;
pub type ResultSet = Vec<Row>;

// SQLite integration
#[cfg(feature = "backend-sqlite")]
impl rusqlite::ToSql for Value {
    fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
        use rusqlite::types::{ToSqlOutput, ValueRef};
        match self {
            Value::Integer(i) => Ok(ToSqlOutput::from(*i)),
            Value::String(s) => Ok(ToSqlOutput::from(s.as_str())),
            Value::Boolean(b) => Ok(ToSqlOutput::from(*b as i64)),
            Value::Float(f) => Ok(ToSqlOutput::from(*f)),
            Value::Bytes(b) => Ok(ToSqlOutput::from(b.as_slice())),
            Value::Null => Ok(ToSqlOutput::Borrowed(ValueRef::Null)),
            Value::Timestamp(ts) => Ok(ToSqlOutput::from(*ts)),
            Value::Decimal(d) => Ok(ToSqlOutput::from(d.as_str())),
            Value::Json(j) => Ok(ToSqlOutput::from(j.as_str())),
            Value::Array(_) => Ok(ToSqlOutput::from(format!("{:?}", self))),
            Value::Vector(v) => {
                // Serialize vector as bytes or JSON
                let json = format!("[{}]", v.iter().map(|f| f.to_string()).collect::<Vec<_>>().join(","));
                Ok(ToSqlOutput::from(json))
            }
        }
    }
}

#[cfg(feature = "backend-postgres")]
impl postgres::types::ToSql for Value {
    fn to_sql(&self, _ty: &postgres::types::Type, out: &mut bytes::BytesMut) -> Result<postgres::types::IsNull, Box<dyn std::error::Error + Sync + Send>> {
        use postgres::types::{IsNull, ToSql};
        match self {
            Value::Integer(i) => i.to_sql(_ty, out),
            Value::String(s) => s.to_sql(_ty, out),
            Value::Boolean(b) => b.to_sql(_ty, out),
            Value::Float(f) => f.to_sql(_ty, out),
            Value::Bytes(b) => b.to_sql(_ty, out),
            Value::Null => Ok(IsNull::Yes),
            Value::Timestamp(ts) => ts.to_sql(_ty, out),
            Value::Decimal(d) => d.to_sql(_ty, out),
            Value::Json(j) => j.to_sql(_ty, out),
            Value::Array(_) => {
                // Serialize as JSON for now
                format!("{:?}", self).to_sql(_ty, out)
            }
            Value::Vector(v) => {
                // Serialize as JSON array
                let json = format!("[{}]", v.iter().map(|f| f.to_string()).collect::<Vec<_>>().join(","));
                json.to_sql(_ty, out)
            }
        }
    }

    fn accepts(_ty: &postgres::types::Type) -> bool {
        true
    }

    postgres::types::to_sql_checked!();
}