Skip to main content

drizzle_core/
derived.rs

1//! Typed derived-table projections.
2//!
3//! A [`Derived`] value is a complete SELECT used as a named source
4//! (`(SELECT ...) AS "name"`) in another query's FROM or JOIN. It exposes
5//! only the columns the SELECT returns, as typed [`DerivedField`]s. Dialect
6//! builders create one with `.alias(name)` after checking the query is
7//! complete.
8
9use core::marker::PhantomData;
10
11use crate::expr::{AllScalar, Expr, HasAggStatus, Scalar};
12use crate::row::{
13    ExprValueType, GroupByIdentity, HasSelectModel, IntoGroupBy, IntoSelectTarget, Scoped,
14    SelectCols, SelectStar,
15};
16use crate::{Cons, Nil, SQL, SQLColumnInfo, SQLParam, SQLSchemaType, SQLTable, Tag, ToSQL, Token};
17
18mod private {
19    pub trait Projection {}
20    pub trait Selection<'a, V: crate::SQLParam, Schema, Table> {}
21    pub trait Output {}
22}
23
24/// A complete SELECT used as a named source in another query:
25/// `(SELECT ...) AS "name"`.
26///
27/// Create one with a SELECT builder's `.alias(name)`, where `name` is a
28/// [`Tag`] value. Read its columns through [`fields`](Self::fields). Columns
29/// of a derived table resolve against its alias, so the scope check treats
30/// it like an aliased table.
31pub struct Derived<'a, V: SQLParam, Name, Projection, Query> {
32    query: Query,
33    marker: PhantomData<(&'a (), V, Name, Projection)>,
34}
35
36impl<'a, V, Name, Projection, Query> Derived<'a, V, Name, Projection, Query>
37where
38    V: SQLParam,
39    Name: Tag,
40    Projection: DerivedProjection<Name>,
41{
42    /// Creates a derived source without checking its query.
43    ///
44    /// # Safety
45    ///
46    /// `query` must select exactly the columns described by `Projection`, in
47    /// the same order, and must already satisfy the dialect builder's
48    /// aggregate rules. Its scope is checked where the derived source is
49    /// used, through `Query`'s [`ExprSources`](crate::expr::ExprSources).
50    #[doc(hidden)]
51    #[track_caller]
52    pub unsafe fn new_unchecked(query: Query) -> Self {
53        Projection::validate();
54        Self {
55            query,
56            marker: PhantomData,
57        }
58    }
59
60    /// Returns the underlying query.
61    pub const fn query(&self) -> &Query {
62        &self.query
63    }
64
65    /// Returns the underlying query by value.
66    pub fn into_query(self) -> Query {
67        self.query
68    }
69
70    /// Returns the typed columns of this source, each rendered as
71    /// `"name"."column"`.
72    pub fn fields(&self) -> Projection::Fields
73    where
74        Name: Tag,
75        Projection: DerivedProjection<Name>,
76    {
77        Projection::fields()
78    }
79}
80
81impl<'a, V: SQLParam, Name, Projection, Query: Clone> Clone
82    for Derived<'a, V, Name, Projection, Query>
83{
84    fn clone(&self) -> Self {
85        Self {
86            query: self.query.clone(),
87            marker: PhantomData,
88        }
89    }
90}
91
92impl<'a, V, Name, Projection, Query> ToSQL<'a, V> for Derived<'a, V, Name, Projection, Query>
93where
94    V: SQLParam,
95    Name: Tag,
96    Query: ToSQL<'a, V>,
97{
98    fn to_sql(&self) -> SQL<'a, V> {
99        self.query
100            .to_sql()
101            .parens()
102            .push(Token::AS)
103            .append(SQL::table(crate::TableRef::sql(Name::NAME, &[])))
104    }
105
106    fn into_sql(self) -> SQL<'a, V> {
107        self.query
108            .into_sql()
109            .parens()
110            .push(Token::AS)
111            .append(SQL::table(crate::TableRef::sql(Name::NAME, &[])))
112    }
113}
114
115// A derived source is looked up in scope by its alias name.
116impl<V: SQLParam, Name, Projection, Query: crate::expr::ExprSources> crate::scope::ScopeEntry
117    for Derived<'_, V, Name, Projection, Query>
118{
119    type Key = crate::scope::AliasKey<Name>;
120    type Nullable = crate::expr::NonNull;
121    type Sources = Query::Sources;
122}
123
124impl<'a, V, Name, Projection, Query> HasSelectModel for Derived<'a, V, Name, Projection, Query>
125where
126    V: SQLParam,
127    Name: Tag,
128    Projection: DerivedProjection<Name>,
129{
130    type SelectModel = Projection::Row;
131
132    const COLUMN_COUNT: usize = Projection::COLUMN_COUNT;
133}
134
135/// Maps a SELECT projection to the fields and row exposed by a derived source.
136#[doc(hidden)]
137pub trait DerivedProjection<Name: Tag>: private::Projection {
138    /// The typed columns returned by [`Derived::fields`].
139    type Fields;
140    /// The row type of `SELECT *` from the derived source.
141    type Row;
142
143    /// Number of columns.
144    const COLUMN_COUNT: usize;
145
146    /// Checks the projection at construction time.
147    ///
148    /// # Panics
149    ///
150    /// Implementations panic when two columns share an output name.
151    fn validate() {}
152
153    /// Builds the typed columns.
154    fn fields() -> Self::Fields;
155}
156
157/// Select markers whose query can become a derived source.
158///
159/// The single-table `SELECT *` implementation deliberately matches only an
160/// exact one-table scope. After a join, `SELECT *` contains more columns and
161/// cannot soundly expose the last joined table as its complete projection.
162#[doc(hidden)]
163pub trait DerivedSelection<'a, V: SQLParam, Schema, Table>:
164    private::Selection<'a, V, Schema, Table>
165{
166    type Projection;
167}
168
169impl<'a, V, Schema, Table, Used> DerivedSelection<'a, V, Schema, Table>
170    for Scoped<SelectStar, Cons<Table, Nil>, Used>
171where
172    V: SQLParam + 'a,
173    Schema: SQLSchemaType,
174    Table: SQLTable<'a, Schema, V>,
175{
176    type Projection = TableProjection<'a, V, Schema, Table>;
177}
178
179impl<'a, V, Schema, Table, Used> private::Selection<'a, V, Schema, Table>
180    for Scoped<SelectStar, Cons<Table, Nil>, Used>
181where
182    V: SQLParam + 'a,
183    Schema: SQLSchemaType,
184    Table: SQLTable<'a, Schema, V>,
185{
186}
187
188impl<'a, V, Schema, Name, Projection, Query, Used>
189    DerivedSelection<'a, V, Schema, Derived<'a, V, Name, Projection, Query>>
190    for Scoped<SelectStar, Cons<Derived<'a, V, Name, Projection, Query>, Nil>, Used>
191where
192    V: SQLParam,
193    Name: Tag,
194    Projection: DerivedProjection<Name>,
195{
196    type Projection = Projection;
197}
198
199impl<'a, V, Schema, Name, Projection, Query, Used>
200    private::Selection<'a, V, Schema, Derived<'a, V, Name, Projection, Query>>
201    for Scoped<SelectStar, Cons<Derived<'a, V, Name, Projection, Query>, Nil>, Used>
202where
203    V: SQLParam,
204    Name: Tag,
205    Projection: DerivedProjection<Name>,
206{
207}
208
209impl<'a, V, Schema, Table, Columns, Scope, Used> DerivedSelection<'a, V, Schema, Table>
210    for Scoped<SelectCols<Columns>, Scope, Used>
211where
212    V: SQLParam,
213{
214    type Projection = Self;
215}
216
217impl<'a, V, Schema, Table, Columns, Scope, Used> private::Selection<'a, V, Schema, Table>
218    for Scoped<SelectCols<Columns>, Scope, Used>
219where
220    V: SQLParam,
221{
222}
223
224impl<Name, Marker, Scope, Used> DerivedProjection<Name> for Scoped<Marker, Scope, Used>
225where
226    Name: Tag,
227    Marker: DerivedProjection<Name>,
228{
229    type Fields = Marker::Fields;
230    type Row = Marker::Row;
231
232    const COLUMN_COUNT: usize = Marker::COLUMN_COUNT;
233
234    fn validate() {
235        Marker::validate();
236    }
237
238    fn fields() -> Self::Fields {
239        Marker::fields()
240    }
241}
242
243impl<Marker, Scope, Used> private::Projection for Scoped<Marker, Scope, Used> where
244    Marker: private::Projection
245{
246}
247
248/// Projection marker used when a dialect has proven that `SELECT *` comes from
249/// one base table.
250#[doc(hidden)]
251pub struct TableProjection<'a, V: SQLParam, Schema, Table>(PhantomData<(&'a (), V, Schema, Table)>);
252
253impl<V: SQLParam, Schema, Table> private::Projection for TableProjection<'_, V, Schema, Table> {}
254
255impl<'a, V, Schema, Name, Table> DerivedProjection<Name> for TableProjection<'a, V, Schema, Table>
256where
257    V: SQLParam + 'a,
258    Schema: SQLSchemaType,
259    Name: Tag + 'static,
260    Table: SQLTable<'a, Schema, V> + HasSelectModel,
261    Table::Aliased<Name>: HasSelectModel<SelectModel = Table::SelectModel>,
262{
263    type Fields = Table::Aliased<Name>;
264    type Row = Table::SelectModel;
265
266    const COLUMN_COUNT: usize = Table::COLUMN_COUNT;
267
268    fn fields() -> Self::Fields {
269        Table::alias::<Name>()
270    }
271}
272
273/// A column of a derived source named `Name`, rendered as
274/// `"name"."output"`.
275///
276/// `Output` is the selected expression (a column or a named expression),
277/// which supplies the output name, SQL type and nullability.
278pub struct DerivedField<Name, Output>(PhantomData<(Name, Output)>);
279
280impl<Name, Output> Copy for DerivedField<Name, Output> {}
281
282impl<Name, Output> Clone for DerivedField<Name, Output> {
283    fn clone(&self) -> Self {
284        *self
285    }
286}
287
288impl<Name, Output> Default for DerivedField<Name, Output> {
289    fn default() -> Self {
290        Self(PhantomData)
291    }
292}
293
294impl<'a, V, Name, Output> ToSQL<'a, V> for DerivedField<Name, Output>
295where
296    V: SQLParam,
297    Name: Tag,
298    Output: ProjectionOutput,
299{
300    fn to_sql(&self) -> SQL<'a, V> {
301        SQL::ident(Name::NAME)
302            .push(Token::DOT)
303            .append(SQL::ident(Output::output_name()))
304    }
305}
306
307impl<'a, V, Name, Output> Expr<'a, V> for DerivedField<Name, Output>
308where
309    V: SQLParam + 'a,
310    Name: Tag,
311    Output: ProjectionOutput + Expr<'a, V>,
312{
313    type SQLType = Output::SQLType;
314    type Nullable = Output::Nullable;
315    type Aggregate = Scalar;
316}
317
318impl<Name, Output> ExprValueType for DerivedField<Name, Output>
319where
320    Name: Tag,
321    Output: ProjectionOutput + ExprValueType,
322{
323    type ValueType = Output::ValueType;
324}
325
326impl<Name, Output> IntoSelectTarget for DerivedField<Name, Output>
327where
328    Name: Tag,
329    Output: ProjectionOutput + ExprValueType,
330{
331    type Marker = SelectCols<(Self,)>;
332}
333
334impl<Name, Output> HasAggStatus for DerivedField<Name, Output>
335where
336    Name: Tag,
337    Output: ProjectionOutput,
338{
339    type Status = AllScalar;
340}
341
342impl<Name, Output> GroupByIdentity for DerivedField<Name, Output>
343where
344    Name: Tag,
345    Output: ProjectionOutput,
346{
347    type Identity = Self;
348}
349
350impl<'a, V, Name, Output, Projection, Query>
351    crate::traits::ColumnOf<Derived<'a, V, Name, Projection, Query>> for DerivedField<Name, Output>
352where
353    V: SQLParam,
354{
355}
356
357// A derived field reads the derived source named `Name`.
358impl<Name, Output> crate::expr::ExprSources for DerivedField<Name, Output> {
359    type Sources = crate::scope::Src<crate::scope::AliasKey<Name>>;
360}
361
362impl<'a, V, Name, Output> IntoGroupBy<'a, V> for DerivedField<Name, Output>
363where
364    V: SQLParam + 'a,
365    Name: Tag,
366    Output: ProjectionOutput,
367{
368    type Columns = Cons<Self, Nil>;
369}
370
371/// Supplies the static output name for one SELECT expression.
372#[doc(hidden)]
373pub trait ProjectionOutput: private::Output {
374    /// The column's name in the derived table.
375    fn output_name() -> &'static str;
376}
377
378impl<Column> private::Output for Column where Column: SQLColumnInfo + Default {}
379
380impl<Column> ProjectionOutput for Column
381where
382    Column: SQLColumnInfo + Default,
383{
384    fn output_name() -> &'static str {
385        Column::default().name()
386    }
387}
388
389impl<E, Name> ProjectionOutput for crate::expr::NamedExpr<E, Name>
390where
391    Name: Tag,
392{
393    fn output_name() -> &'static str {
394        Name::NAME
395    }
396}
397
398impl<E, Name> private::Output for crate::expr::NamedExpr<E, Name> where Name: Tag {}
399
400impl<Name, Output> ProjectionOutput for DerivedField<Name, Output>
401where
402    Name: Tag,
403    Output: ProjectionOutput,
404{
405    fn output_name() -> &'static str {
406        Output::output_name()
407    }
408}
409
410impl<Name, Output> private::Output for DerivedField<Name, Output>
411where
412    Name: Tag,
413    Output: ProjectionOutput,
414{
415}
416
417macro_rules! impl_derived_projection_tuple {
418    ($($output:ident),+; $($_index:tt),+) => {
419        impl<Name, $($output),+> DerivedProjection<Name> for SelectCols<($($output,)+)>
420        where
421            Name: Tag,
422            $($output: ProjectionOutput + ExprValueType,)+
423        {
424            type Fields = ($(DerivedField<Name, $output>,)+);
425            type Row = ($(<$output as ExprValueType>::ValueType,)+);
426
427            const COLUMN_COUNT: usize = impl_derived_projection_tuple!(@count $($output),+);
428
429            fn validate() {
430                let names = [$(<$output as ProjectionOutput>::output_name(),)+];
431                let mut left = 0;
432                while left < names.len() {
433                    let mut right = left + 1;
434                    while right < names.len() {
435                        assert!(
436                            names[left] != names[right],
437                            "derived projection contains duplicate output name `{}`; name one expression with `.named::<Tag>()`",
438                            names[left],
439                        );
440                        right += 1;
441                    }
442                    left += 1;
443                }
444            }
445
446            fn fields() -> Self::Fields {
447                ($(DerivedField::<Name, $output>::default(),)+)
448            }
449        }
450
451        impl<$($output),+> private::Projection for SelectCols<($($output,)+)>
452        where
453            $($output: ProjectionOutput + ExprValueType,)+
454        {
455        }
456    };
457    (@count $head:ident $(,$tail:ident)*) => {
458        1usize $(+ { let _ = stringify!($tail); 1usize })*
459    };
460}
461
462with_col_sizes_8!(impl_derived_projection_tuple);
463
464#[cfg(any(
465    feature = "col16",
466    feature = "col32",
467    feature = "col64",
468    feature = "col128",
469    feature = "col200"
470))]
471with_col_sizes_16!(impl_derived_projection_tuple);
472
473#[cfg(any(
474    feature = "col32",
475    feature = "col64",
476    feature = "col128",
477    feature = "col200"
478))]
479with_col_sizes_32!(impl_derived_projection_tuple);
480
481#[cfg(any(feature = "col64", feature = "col128", feature = "col200"))]
482with_col_sizes_64!(impl_derived_projection_tuple);
483
484#[cfg(any(feature = "col128", feature = "col200"))]
485with_col_sizes_128!(impl_derived_projection_tuple);
486
487#[cfg(feature = "col200")]
488with_col_sizes_200!(impl_derived_projection_tuple);
489
490#[cfg(test)]
491mod tests {
492    use super::*;
493    use crate::{Dialect, SQLiteDialect};
494
495    #[derive(Clone, Debug)]
496    struct TestParam;
497
498    impl SQLParam for TestParam {
499        const DIALECT: Dialect = Dialect::SQLite;
500        type DialectMarker = SQLiteDialect;
501    }
502
503    struct Alias;
504
505    impl Tag for Alias {
506        const NAME: &'static str = "alias";
507    }
508
509    struct First;
510    struct Second;
511
512    impl private::Output for First {}
513    impl private::Output for Second {}
514
515    impl ProjectionOutput for First {
516        fn output_name() -> &'static str {
517            "duplicate"
518        }
519    }
520
521    impl ProjectionOutput for Second {
522        fn output_name() -> &'static str {
523            "duplicate"
524        }
525    }
526
527    impl ExprValueType for First {
528        type ValueType = i32;
529    }
530
531    impl ExprValueType for Second {
532        type ValueType = i32;
533    }
534
535    #[test]
536    #[should_panic(expected = "duplicate output name")]
537    fn duplicate_projection_names_are_rejected() {
538        let _: Derived<'_, TestParam, Alias, SelectCols<(First, Second)>, ()> =
539            // SAFETY: This test exercises projection-name validation before
540            // the query is ever rendered or decoded.
541            unsafe { Derived::new_unchecked(()) };
542    }
543}