vantage-sql 0.10.0

Vantage extension for SQL databases (Postgres, MySQL, SQLite)
Documentation
//! The two probes behind set-confined writes, shared by the SQL dialects:
//! whether an id exists anywhere in the table, and whether a row would
//! satisfy the table's conditions. A dialect supplies only how it binds ids
//! and NULL, and how it quotes identifiers — see [`SetWrites`].

use ciborium::Value as CborValue;
use vantage_core::Result;
use vantage_expressions::traits::datasource::ExprDataSource;
use vantage_expressions::{Expression, expr_any};
use vantage_table::table::Table;
use vantage_table::traits::column_like::ColumnLike;
use vantage_table::traits::table_source::TableSource;
use vantage_types::{Entity, Record};

/// The dialect-specific pieces of the set-confined write paths.
pub(crate) trait SetWrites:
    TableSource<Id = String> + ExprDataSource<<Self as TableSource>::Value>
{
    /// The id as bound in a `WHERE <id> = …` lookup.
    fn id_param<E: Entity<Self::Value>>(table: &Table<Self, E>, id: &str) -> Self::Value;

    /// The id as the table stores it — the cell the fits-the-set probe puts
    /// on the row, so `id = 5` conditions compare like for like.
    fn id_cell<E: Entity<Self::Value>>(table: &Table<Self, E>, id: &str) -> Self::Value;

    /// The cell a column the record leaves out reads as in the probe.
    fn null() -> Self::Value;

    /// The probe cell for a column the record leaves out. `column_type` is
    /// the column's declared Rust type name, when the table knows it. The
    /// default is a plain [`SetWrites::null`]; a dialect whose NULL would
    /// otherwise take the wrong type overrides this to type it.
    fn absent(_column_type: Option<&str>) -> Expression<Self::Value> {
        let null = Self::null();
        expr_any!("{}", null)
    }

    /// `name` quoted as an identifier.
    fn quoted(name: &str) -> Expression<Self::Value>;

    fn condition_expr(condition: &Self::Condition) -> Expression<Self::Value>;

    fn into_cbor(value: Self::Value) -> CborValue;
}

/// Whether any row of the table has `id`, ignoring its conditions.
pub(crate) async fn id_exists_anywhere<T, E>(db: &T, table: &Table<T, E>, id: &str) -> Result<bool>
where
    T: SetWrites,
    E: Entity<T::Value>,
{
    let id_field = T::quoted(&table.id_field_name());
    let id_val = T::id_param(table, id);
    let probe: Expression<T::Value> = expr_any!(
        "SELECT 1 AS {} FROM {} WHERE {} = {} LIMIT 1",
        (id_field),
        (T::quoted(table.table_name())),
        (id_field),
        id_val
    );
    Ok(has_rows::<T>(db.execute(&probe).await?))
}

/// Whether `row` — the record as it would be stored, id included —
/// satisfies every condition of `table`, evaluated by the database over a
/// one-row [`set_probe`].
pub(crate) async fn row_in_set<T, E>(
    db: &T,
    table: &Table<T, E>,
    row: &Record<T::Value>,
) -> Result<bool>
where
    T: SetWrites,
    E: Entity<T::Value>,
{
    if table.conditions().next().is_none() {
        return Ok(true);
    }
    let one: Expression<T::Value> = expr_any!("1 AS {}", (T::quoted(&table.id_field_name())));
    let probe = set_probe(table, &[row], one);
    Ok(has_rows::<T>(db.execute(&probe).await?))
}

/// The columns a set probe over `rows` carries: every stored column of the
/// table, then any other key a row sets. [`set_probe`] binds one parameter
/// per column per row.
pub(crate) fn probe_columns<T, E>(table: &Table<T, E>, rows: &[&Record<T::Value>]) -> Vec<String>
where
    T: SetWrites,
    E: Entity<T::Value>,
{
    let mut names: Vec<String> = table
        .columns()
        .keys()
        .filter(|n| !table.is_calculated_column(n))
        .cloned()
        .collect();
    for row in rows {
        for key in row.keys() {
            if !names.contains(key) {
                names.push(key.clone());
            }
        }
    }
    names
}

/// `SELECT <select> FROM (<rows>) AS <table> WHERE <conditions>`: the rows
/// as a derived table aliased as the table itself (one `SELECT` per row,
/// joined with `UNION ALL`), so plain, table-qualified and subquery
/// conditions read as they do on the real rows. A column a row leaves out
/// reads as [`SetWrites::absent`]. The table must have conditions.
pub(crate) fn set_probe<T, E>(
    table: &Table<T, E>,
    rows: &[&Record<T::Value>],
    select: Expression<T::Value>,
) -> Expression<T::Value>
where
    T: SetWrites,
    E: Entity<T::Value>,
{
    let names = probe_columns(table, rows);
    let selects: Vec<Expression<T::Value>> = rows
        .iter()
        .map(|row| {
            let fields: Vec<Expression<T::Value>> = names
                .iter()
                .map(|name| {
                    let cell: Expression<T::Value> = match row.get(name) {
                        Some(value) => {
                            let value = value.clone();
                            expr_any!("{}", value)
                        }
                        None => T::absent(table.columns().get(name).map(|c| c.get_type())),
                    };
                    expr_any!("{} AS {}", (cell), (T::quoted(name)))
                })
                .collect();
            expr_any!("SELECT {}", (Expression::from_vec(fields, ", ")))
        })
        .collect();
    let conditions: Vec<Expression<T::Value>> = table
        .conditions()
        .map(|c| expr_any!("({})", (T::condition_expr(c))))
        .collect();
    expr_any!(
        "SELECT {} FROM ({}) AS {} WHERE {}",
        (select),
        (Expression::from_vec(selects, " UNION ALL ")),
        (T::quoted(table.table_name())),
        (Expression::from_vec(conditions, " AND "))
    )
}

/// Whether a query result holds at least one row.
fn has_rows<T: SetWrites>(result: T::Value) -> bool {
    matches!(T::into_cbor(result), CborValue::Array(rows) if !rows.is_empty())
}