Skip to main content

vantage_table/source/
mod.rs

1//! Source representation for tables whose FROM clause may be a sub-`SELECT`.
2//!
3//! A backend's [`TableSource::Source`](crate::traits::table_source::TableSource::Source)
4//! is either a plain `String` (most backends) or [`SelectSource<S>`] — the
5//! shared enum used by every backend whose `Select` can express
6//! `FROM (subquery)` (SQLite, PostgreSQL, MySQL, SurrealDB).
7
8use vantage_expressions::{Expressive, expr_any, traits::selectable::Selectable};
9
10use crate::traits::table_source_spec::TableSourceSpec;
11
12/// The source of a table: a named table, or an arbitrary query used as a
13/// derived (sub-`SELECT`) source.
14///
15/// `S` is the backend's `Select` type. The same enum serves all four
16/// subquery-capable backends; the only per-backend code is the one-line
17/// `type Source = SelectSource<Self::Select>;` on their `TableSource` impl.
18#[derive(Clone, Debug)]
19pub enum SelectSource<S> {
20    /// A physical table/collection name.
21    Name(String),
22    /// A query used as the FROM source, rendered `FROM (<select>) AS <alias>`.
23    Query { select: Box<S>, alias: String },
24}
25
26impl<S> SelectSource<S> {
27    /// Build a query source from a select and the alias to expose it under.
28    pub fn query(select: S, alias: impl Into<String>) -> Self {
29        SelectSource::Query {
30            select: Box::new(select),
31            alias: alias.into(),
32        }
33    }
34}
35
36impl<S: Clone + Send + Sync + 'static> TableSourceSpec for SelectSource<S> {
37    fn name(&self) -> &str {
38        match self {
39            SelectSource::Name(name) => name.as_str(),
40            SelectSource::Query { alias, .. } => alias.as_str(),
41        }
42    }
43
44    fn from_name(name: String) -> Self {
45        SelectSource::Name(name)
46    }
47}
48
49impl<S> From<&str> for SelectSource<S> {
50    fn from(value: &str) -> Self {
51        SelectSource::Name(value.to_string())
52    }
53}
54
55impl<S> From<String> for SelectSource<S> {
56    fn from(value: String) -> Self {
57        SelectSource::Name(value)
58    }
59}
60
61/// Applies a source to a freshly-created `Select`.
62///
63/// `Table::select_empty` is generic over *every* `SelectableDataSource`,
64/// including backends whose `Source` is `String` and whose `Select` is not
65/// `Expressive`. So the source can't be matched against `SelectSource`
66/// directly there — this trait dispatches on the concrete source type, keeping
67/// the `Expressive` requirement confined to the query-capable backends.
68pub trait SelectSeed<S, V, C> {
69    /// Add this source to `select` as its FROM clause.
70    fn seed(&self, select: &mut S)
71    where
72        S: Selectable<V, C>,
73        V: From<String>;
74}
75
76impl<S, V, C> SelectSeed<S, V, C> for String {
77    fn seed(&self, select: &mut S)
78    where
79        S: Selectable<V, C>,
80        V: From<String>,
81    {
82        select.add_source(self.as_str(), None);
83    }
84}
85
86impl<S, V, C> SelectSeed<S, V, C> for SelectSource<S>
87where
88    S: Expressive<V> + Clone,
89    V: Clone,
90{
91    fn seed(&self, select: &mut S)
92    where
93        S: Selectable<V, C>,
94        V: From<String>,
95    {
96        match self {
97            SelectSource::Name(name) => select.add_source(name.as_str(), None),
98            SelectSource::Query {
99                select: query,
100                alias,
101            } => {
102                // Parenthesize the subquery ourselves: `expr()` renders a bare
103                // statement, and the SQL dialects' `add_source` does not wrap a
104                // nested source (only SurrealDB does). One wrap here is correct
105                // for every backend.
106                let subquery = expr_any!("({})", (query.expr()));
107                select.add_source(subquery, Some(alias.clone()));
108            }
109        }
110    }
111}