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}