Skip to main content

drizzle_core/
cte.rs

1//! Driver-neutral common table expression types.
2
3use core::{marker::PhantomData, ops::Deref};
4
5use crate::{SQL, SQLParam, ToSQL, Token};
6
7/// Something that can be listed in a `WITH` clause.
8pub trait CTEDefinition<'a, V: SQLParam> {
9    /// Renders the definition: `"name" AS (SELECT ...)`.
10    fn cte_definition(&self) -> SQL<'a, V>;
11}
12
13/// A named common table expression (CTE) that can be read like a table.
14///
15/// Created by a SELECT builder's `.into_cte::<Tag>()`. Pass it to `.with(...)`
16/// to define it, then select from it. Its typed columns are reached through
17/// `table` (or directly, through `Deref`). As a source it renders as its
18/// quoted name.
19///
20/// # Examples
21///
22/// ```
23/// use drizzle_core::{SQL, ToSQL};
24/// use drizzle_core::cte::{CTEDefinition, CTEView};
25/// # use drizzle_core::{Dialect, SQLParam, SQLiteDialect};
26/// # use std::borrow::Cow;
27/// # #[derive(Debug, Clone, PartialEq)]
28/// # struct Value(i64);
29/// # impl SQLParam for Value {
30/// #     const DIALECT: Dialect = Dialect::SQLite;
31/// #     type DialectMarker = SQLiteDialect;
32/// # }
33/// # impl From<Value> for Cow<'_, Value> {
34/// #     fn from(value: Value) -> Self { Cow::Owned(value) }
35/// # }
36///
37/// let recent = CTEView::new((), "recent", SQL::<Value>::raw("SELECT 1"));
38/// assert_eq!(recent.cte_definition().sql(), r#""recent" AS (SELECT 1)"#);
39/// assert_eq!(recent.to_sql().sql(), r#""recent""#);
40/// ```
41#[derive(Clone, Debug)]
42pub struct CTEView<'a, V: SQLParam, Table, Query> {
43    /// The aliased table that gives typed access to the CTE's columns.
44    pub table: Table,
45    name: &'static str,
46    query: Query,
47    value: PhantomData<(&'a (), V)>,
48}
49
50impl<'a, V, Table, Query> CTEView<'a, V, Table, Query>
51where
52    V: SQLParam,
53    Query: ToSQL<'a, V>,
54{
55    /// Creates a CTE named `name`, defined by `query`, with columns typed by
56    /// `table`.
57    pub const fn new(table: Table, name: &'static str, query: Query) -> Self {
58        Self {
59            table,
60            name,
61            query,
62            value: PhantomData,
63        }
64    }
65
66    /// Returns the CTE name.
67    pub const fn cte_name(&self) -> &'static str {
68        self.name
69    }
70
71    /// Returns the defining query.
72    pub const fn query(&self) -> &Query {
73        &self.query
74    }
75}
76
77impl<'a, V, Table, Query> CTEDefinition<'a, V> for CTEView<'a, V, Table, Query>
78where
79    V: SQLParam,
80    Query: ToSQL<'a, V>,
81{
82    fn cte_definition(&self) -> SQL<'a, V> {
83        SQL::ident(self.name)
84            .push(Token::AS)
85            .append(self.query.to_sql().parens())
86    }
87}
88
89impl<'a, V, Table, Query> CTEDefinition<'a, V> for &CTEView<'a, V, Table, Query>
90where
91    V: SQLParam,
92    Query: ToSQL<'a, V>,
93{
94    fn cte_definition(&self) -> SQL<'a, V> {
95        (*self).cte_definition()
96    }
97}
98
99// A CTE is looked up in scope by the key of the aliased table it exposes.
100impl<V: SQLParam, Table: crate::scope::ScopeEntry, Query> crate::scope::ScopeEntry
101    for CTEView<'_, V, Table, Query>
102{
103    type Key = Table::Key;
104    type Nullable = Table::Nullable;
105    // The CTE query is checked where it is defined.
106    type Sources = ();
107}
108
109impl<V: SQLParam, Table, Query> Deref for CTEView<'_, V, Table, Query> {
110    type Target = Table;
111
112    fn deref(&self) -> &Self::Target {
113        &self.table
114    }
115}
116
117impl<'a, V, Table, Query> ToSQL<'a, V> for CTEView<'a, V, Table, Query>
118where
119    V: SQLParam,
120    Query: ToSQL<'a, V>,
121{
122    fn to_sql(&self) -> SQL<'a, V> {
123        SQL::ident(self.name)
124    }
125}