rusticx-core 1.0.0

Core traits, types, and abstractions for the Rusticx multi-database ORM
Documentation
use crate::value::Value;

/// A compiled, backend-specific query with its parameter bindings.
///
/// Produced by each backend's SQL compiler after transforming a [`QueryBuilder`].
/// You typically do not construct this directly.
#[derive(Debug, Clone, Default)]
pub struct Query {
    pub sql: String,
    pub bindings: Vec<Value>,
}

impl Query {
    pub fn new(sql: impl Into<String>) -> Self {
        Self { sql: sql.into(), bindings: vec![] }
    }

    pub fn bind(mut self, val: impl Into<Value>) -> Self {
        self.bindings.push(val.into());
        self
    }
}

/// Composable, database-agnostic query builder.
///
/// `QueryBuilder` is the primary way to express SELECT / UPDATE / DELETE
/// conditions in Rusticx without writing raw SQL or MQL. Each backend's
/// compiler transforms it into parameterized SQL or a BSON filter document.
///
/// Obtain one from [`Repository::query`] so the table name is pre-filled,
/// or construct one directly with [`QueryBuilder::table`].
///
/// # Example
///
/// ```rust,ignore
/// let adults = repo.find(
///     repo.query()
///         .r#where("age", CondOp::Gte, 18)
///         .r#where("active", CondOp::Eq, true)
///         .order_by("name", Direction::Asc)
///         .limit(20)
///         .offset(0)
/// ).await?;
/// ```
#[derive(Debug, Clone, Default)]
pub struct QueryBuilder {
    pub table: String,
    pub operation: Operation,
    pub conditions: Vec<Condition>,
    pub order_by: Vec<OrderBy>,
    pub limit: Option<u64>,
    pub offset: Option<u64>,
    pub columns: Vec<String>,
    pub values: Vec<(String, Value)>,
}

/// The DML operation this [`QueryBuilder`] represents.
///
/// Set automatically by methods like [`Repository::update`] and
/// [`Repository::delete`]; you only need this when building raw queries.
#[derive(Debug, Clone, Default)]
pub enum Operation {
    #[default]
    Select,
    Insert,
    Update,
    Delete,
    Count,
}

#[derive(Debug, Clone)]
pub struct Condition {
    pub column: String,
    pub op: CondOp,
    pub value: Value,
    pub conjunction: Conjunction,
}

#[derive(Debug, Clone, Default)]
pub enum Conjunction {
    #[default]
    And,
    Or,
}

/// Comparison operator for a WHERE condition.
///
/// Used in [`QueryBuilder::r#where`] and [`QueryBuilder::or_where`].
///
/// | Variant | SQL | MongoDB |
/// |---|---|---|
/// | `Eq` | `= $1` | `{ field: value }` |
/// | `Ne` | `!= $1` | `{ field: { $ne: value } }` |
/// | `Gt` / `Gte` / `Lt` / `Lte` | `>` / `>=` / `<` / `<=` | `$gt` / `$gte` / `$lt` / `$lte` |
/// | `Like` | `LIKE $1` | regex (`.*` for `%`) |
/// | `ILike` | `ILIKE $1` (Postgres) | regex with `i` flag |
/// | `In` | `IN (...)` | `{ $in: [...] }` |
/// | `IsNull` | `IS NULL` | `{ $type: 10 }` |
/// | `IsNotNull` | `IS NOT NULL` | `{ $not: { $type: 10 } }` |
#[derive(Debug, Clone)]
pub enum CondOp {
    Eq,
    Ne,
    Gt,
    Gte,
    Lt,
    Lte,
    Like,
    ILike,
    In,
    NotIn,
    IsNull,
    IsNotNull,
}

#[derive(Debug, Clone)]
pub struct OrderBy {
    pub column: String,
    pub direction: Direction,
}

#[derive(Debug, Clone, Default)]
pub enum Direction {
    #[default]
    Asc,
    Desc,
}

impl QueryBuilder {
    pub fn table(name: impl Into<String>) -> Self {
        Self { table: name.into(), ..Default::default() }
    }

    pub fn select(mut self, cols: Vec<impl Into<String>>) -> Self {
        self.columns = cols.into_iter().map(|c| c.into()).collect();
        self
    }

    pub fn r#where(mut self, col: impl Into<String>, op: CondOp, val: impl Into<Value>) -> Self {
        self.conditions.push(Condition {
            column: col.into(),
            op,
            value: val.into(),
            conjunction: Conjunction::And,
        });
        self
    }

    pub fn or_where(mut self, col: impl Into<String>, op: CondOp, val: impl Into<Value>) -> Self {
        self.conditions.push(Condition {
            column: col.into(),
            op,
            value: val.into(),
            conjunction: Conjunction::Or,
        });
        self
    }

    pub fn order_by(mut self, col: impl Into<String>, dir: Direction) -> Self {
        self.order_by.push(OrderBy { column: col.into(), direction: dir });
        self
    }

    pub fn limit(mut self, n: u64) -> Self {
        self.limit = Some(n);
        self
    }

    pub fn offset(mut self, n: u64) -> Self {
        self.offset = Some(n);
        self
    }

    pub fn set(mut self, col: impl Into<String>, val: impl Into<Value>) -> Self {
        self.values.push((col.into(), val.into()));
        self
    }

    pub fn operation(mut self, op: Operation) -> Self {
        self.operation = op;
        self
    }
}