qraft-core 0.1.2

Core type system, query model, decoding, and SQL lowering primitives for qraft.
Documentation
//! Select builders and clause traits.

mod from;
pub use from::*;
mod group;
pub use group::*;
mod order;
pub use order::*;
mod project;
pub use project::*;
mod query;
pub use query::*;
mod join;
pub use join::*;
mod having;
pub use having::*;
mod filter;
pub use filter::*;

use crate::{
    HasDialect, TypeMeta,
    alias::Aliased,
    cte::{Cte, IntoCtes},
    emitter::Emitter,
    expression::Scalar,
    lower::LowerCtx,
};

/// A select statement before a `from` source has been attached.
#[derive(Debug, Clone, Copy)]
pub struct Select<P> {
    project: P,
}

/// A select statement that already carries one or more CTEs.
#[derive(Debug, Clone)]
pub struct WithSelect<P> {
    project: P,
    ctes: Vec<Cte>,
}

impl<P: LowerProject> Select<P> {
    /// Attaches a `from` source and returns a full query.
    pub fn from<F>(self, v: F) -> Query
    where
        F: LowerFrom,
    {
        Query::from(v).select(self.project)
    }

    /// Materializes the select builder into a query value.
    pub fn into_query(self) -> Query {
        let mut query = Query::default();
        let mut ctx = LowerCtx {
            instrs: &mut query.project,
            params: &mut query.params,
            data: &mut query.data,
        };
        self.project.lower_project(&mut ctx);
        query
    }

    /// Treats the select as a scalar subquery of the requested type.
    pub fn scalar<T: TypeMeta>(self) -> Scalar<T> {
        Scalar {
            inner: self.into_query(),
            marker: std::marker::PhantomData,
        }
    }

    /// Adds a common table expression to the statement.
    pub fn with<C>(self, ctes: C) -> WithSelect<P>
    where
        C: IntoCtes,
    {
        let ctes = ctes.into_ctes(false);
        WithSelect {
            project: self.project,
            ctes,
        }
    }

    /// Adds a recursive common table expression to the statement.
    pub fn with_recursive<C>(self, ctes: C) -> WithSelect<P>
    where
        C: IntoCtes,
    {
        let ctes = ctes.into_ctes(true);
        WithSelect {
            project: self.project,
            ctes,
        }
    }

    /// Compiles the select to SQL without parameter debug output.
    pub fn into_sql<D: HasDialect>(self) -> String {
        let mut buf = String::new();
        let mut directives = Vec::new();
        let mut params = Vec::new();
        let query = self.into_query();
        let mut emitter = Emitter::new(
            &mut buf,
            &query.data,
            D::DIALECT,
            &mut directives,
            &mut params,
        );
        emitter.emit_query(&query).unwrap();
        buf
    }
}

impl<P: LowerProject> WithSelect<P> {
    /// Adds another CTE to the statement.
    pub fn with<C>(mut self, ctes: C) -> Self
    where
        C: IntoCtes,
    {
        self.ctes.extend(ctes.into_ctes(false));
        self
    }

    /// Adds another recursive CTE to the statement.
    pub fn with_recursive<C>(mut self, ctes: C) -> Self
    where
        C: IntoCtes,
    {
        self.ctes.extend(ctes.into_ctes(true));
        self
    }

    /// Attaches a `from` source and returns a full query.
    pub fn from<F>(self, v: F) -> Query
    where
        F: LowerFrom,
    {
        Query::from(v).select(self.project).with_many(self.ctes)
    }

    /// Materializes the select builder into a query value.
    pub fn into_query(self) -> Query {
        let mut query = Query::default();
        let mut ctx = LowerCtx {
            instrs: &mut query.project,
            params: &mut query.params,
            data: &mut query.data,
        };
        self.project.lower_project(&mut ctx);
        query.with_many(self.ctes)
    }

    /// Treats the select as a scalar subquery of the requested type.
    pub fn scalar<T: TypeMeta>(self) -> Scalar<T> {
        Scalar {
            inner: self.into_query(),
            marker: std::marker::PhantomData,
        }
    }

    /// Compiles the select to SQL without parameter debug output.
    pub fn into_sql<D: HasDialect>(self) -> String {
        self.into_query().to_sql::<D>()
    }
}

/// Starts a `select` builder from a projection value.
///
/// # Examples
///
/// ```rust
/// use qraft_core::{BigInt, Sqlite, expression::col, query::select};
///
/// let sql = select(col::<BigInt>("id"))
///     .from("users")
///     .filter(col::<BigInt>("id").eq(1_i64))
///     .to_debug_sql::<Sqlite>();
///
/// assert_eq!(
///     sql,
///     r#"select "id" from "users" where "id" = ?; params=[1]"#
/// );
/// ```
pub fn select<P>(project: P) -> Select<P>
where
    P: LowerProject,
{
    Select { project }
}

/// Starts a `select *` builder.
///
/// # Examples
///
/// ```rust
/// use qraft_core::{Sqlite, query::select_all};
///
/// let sql = select_all().from("users").to_sql::<Sqlite>();
///
/// assert_eq!(sql, r#"select * from "users""#);
/// ```
pub fn select_all() -> Select<Star> {
    Select { project: star() }
}

// helper to prevent having to type the scalar
impl<P: LowerProject> LowerProject for Select<P> {
    fn lower_project(self, ctx: &mut LowerCtx) {
        let query = self.into_query();
        let _ = ctx.lower_subquery_ref(&query);
    }
}

impl<P: LowerProject> LowerProject for Aliased<Select<P>> {
    fn lower_project(self, ctx: &mut LowerCtx) {
        let query = self.inner.into_query();
        let inner = ctx.lower_subquery_ref(&query);
        let _ = ctx.lower_alias(self.alias, inner);
    }
}