Skip to main content

drizzle_core/
scope.rs

1//! Compile-time checks that a query only reads tables it has joined.
2//!
3//! This module is internal machinery. Users never name these types; they see
4//! the result as a compile error when a query reads a table it never added
5//! with `.from(...)` or `.join(...)`:
6//!
7//! ```text
8//! error[E0277]: `Posts` is not in this query's FROM/JOIN scope
9//!    |
10//! 47 |     db.select(users.id).from(users).r#where(eq(posts.views, 1)).all();
11//!    |                                                                  ^^^ this expression reads a table that the query never joins
12//! ```
13//!
14//! The error points at the terminal method (`.all()`, `.get()`, `.rows()`),
15//! because that is where the whole query is checked.
16//!
17//! # How it works
18//!
19//! - **Scope.** A SELECT builder carries its FROM/JOIN sources in its marker
20//!   type ([`Scoped`]) as a type-level list, newest first:
21//!   `Cons<Posts, Cons<Users, Nil>>`. Each element is a [`ScopeEntry`].
22//! - **Sources.** Every expression carries the sources it reads as a type
23//!   (its [`Sources`](crate::expr::ExprSources::Sources)): `users.id` reads
24//!   `Src<Users>`, and `eq(users.id, posts.author_id)` reads both.
25//! - **Recording.** Each clause (JOIN ON, WHERE, GROUP BY, HAVING, ORDER BY)
26//!   adds its expression's sources to the marker ([`HasScope::With`]), paired
27//!   with the scope at that point ([`At`]). Joins update the scope through
28//!   [`JoinStep`].
29//! - **Checking.** The terminal method requires
30//!   [`MarkerScopeValidFor`](crate::row::MarkerScopeValidFor), which asks
31//!   [`SourcesIn`]: is every recorded source in the scope?
32//!
33//! The same walk also works out nullability. A source on the nullable side
34//! of an outer join behaves like a table whose columns are all nullable, so
35//! after `.left_join(posts)` a selected `posts.title` must be decoded as
36//! `Option<String>`.
37//!
38//! # Sources tree
39//!
40//! | Node | Meaning | NULL when |
41//! |------|---------|-----------|
42//! | `()` | reads no source | never (from outer joins) |
43//! | [`Src<T>`] | a column of source `T` | `T` is outer-joined |
44//! | `(A, B)` | both, NULL-propagating | `A` or `B` is NULL |
45//! | [`Coalesce<A, B>`] | both, NULL-absorbing | `A` and `B` are NULL |
46//! | [`NonNull`] / [`Null`] | an operand's declared nullability | `Null` |
47//! | [`At<Scope, S>`] | sources of another query or an earlier clause | never |
48//!
49//! Operators whose result is never NULL (`IS NULL`, `COUNT`, `EXISTS`) wrap
50//! their operands in [`ScopeOnly`], so the operands are still scope-checked
51//! but cannot make the result NULL.
52//!
53//! # Examples
54//!
55//! A source in scope passes the check:
56//!
57//! ```
58//! use drizzle_core::{Cons, Nil};
59//! use drizzle_core::expr::NonNull;
60//! use drizzle_core::scope::{ScopeEntry, SourcesIn, Src, TableKey, name::{H1, H2}};
61//!
62//! struct Users;
63//! impl ScopeEntry for Users {
64//!     type Key = TableKey<Cons<H1, Nil>, Users>;
65//!     type Nullable = NonNull;
66//!     type Sources = ();
67//! }
68//!
69//! fn in_scope<S: SourcesIn<Scope, P>, Scope, P>() {}
70//!
71//! in_scope::<Src<Users>, Cons<Users, Nil>, _>();
72//! ```
73//!
74//! A source that was never joined does not:
75//!
76//! ```compile_fail
77//! use drizzle_core::{Cons, Nil};
78//! use drizzle_core::expr::NonNull;
79//! use drizzle_core::scope::{ScopeEntry, SourcesIn, Src, TableKey, name::{H1, H2}};
80//!
81//! struct Users;
82//! struct Posts;
83//! impl ScopeEntry for Users {
84//!     type Key = TableKey<Cons<H1, Nil>, Users>;
85//!     type Nullable = NonNull;
86//!     type Sources = ();
87//! }
88//! impl ScopeEntry for Posts {
89//!     type Key = TableKey<Cons<H2, Nil>, Posts>;
90//!     type Nullable = NonNull;
91//!     type Sources = ();
92//! }
93//!
94//! fn in_scope<S: SourcesIn<Scope, P>, Scope, P>() {}
95//!
96//! // error: `Posts` is not in this query's FROM/JOIN scope
97//! in_scope::<Src<Posts>, Cons<Users, Nil>, _>();
98//! ```
99
100use core::marker::PhantomData;
101
102use crate::expr::{NonNull, Null, Nullability};
103use crate::{Cons, Nil};
104
105// Bound-free `Clone`/`Copy`/`Default`/`Debug` for type-level markers, so user
106// tag and table types need not implement them.
107macro_rules! marker_impls {
108    ($($name:ident<$($p:ident),+>),+ $(,)?) => {$(
109        impl<$($p),+> Clone for $name<$($p),+> {
110            fn clone(&self) -> Self {
111                *self
112            }
113        }
114        impl<$($p),+> Copy for $name<$($p),+> {}
115        impl<$($p),+> Default for $name<$($p),+> {
116            fn default() -> Self {
117                Self(PhantomData)
118            }
119        }
120        impl<$($p),+> core::fmt::Debug for $name<$($p),+> {
121            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
122                f.write_str(stringify!($name))
123            }
124        }
125    )+};
126}
127
128marker_impls!(
129    AliasKey<Tag>,
130    OuterJoined<T>,
131    ScopeThere<Prev>,
132    TableKey<Name, Table>,
133    Scoped<Marker, Scope, Used>,
134    Src<T>,
135    Coalesce<A, B>,
136    At<Scope, S>,
137    Lateral<Kind>,
138);
139
140// =============================================================================
141// Scope entries
142// =============================================================================
143
144/// Something that can be listed in FROM or JOIN: a table, view, alias,
145/// derived table, CTE, or raw SQL.
146///
147/// Table, view, and alias macros implement this for you. A column's
148/// [`Sources`](crate::expr::ExprSources::Sources) names its source as
149/// [`Src<T>`], and the scope check looks up `T::Key` in the query's scope.
150///
151/// Tables and views are keyed by their SQL name ([`TableKey`]). Aliased
152/// sources (`Table::alias::<Tag>()`, derived tables, CTEs) are keyed by
153/// [`AliasKey<Tag>`], because SQL resolves their columns by alias name.
154pub trait ScopeEntry {
155    /// How columns refer to this source: a [`TableKey`] or an [`AliasKey`].
156    type Key;
157    /// [`Null`] when an outer join can leave this source NULL.
158    type Nullable: Nullability;
159    /// Sources this source reads itself, such as the tables a derived
160    /// table's subquery reads from an outer query. They resolve against the
161    /// enclosing query (or, for a `LATERAL` join, the sources joined before
162    /// it). Tables and views read nothing (`()`).
163    type Sources;
164}
165
166impl<T: ScopeEntry + ?Sized> ScopeEntry for &T {
167    type Key = T::Key;
168    type Nullable = T::Nullable;
169    type Sources = T::Sources;
170}
171
172/// Key of a raw SQL source (`.from(sql)`). No typed column can refer to it.
173#[derive(Debug, Clone, Copy, Default)]
174pub struct RawSource;
175
176impl<V: crate::SQLParam> ScopeEntry for crate::SQL<'_, V> {
177    type Key = RawSource;
178    type Nullable = NonNull;
179    type Sources = ();
180}
181
182/// Key of a source that SQL refers to by an alias name. `Tag` is the alias's
183/// type-level name.
184pub struct AliasKey<Tag>(PhantomData<Tag>);
185
186impl<Tag> ScopeEntry for AliasKey<Tag> {
187    type Key = Self;
188    type Nullable = NonNull;
189    type Sources = ();
190}
191
192/// Scope entry for a source on the nullable side of an outer join.
193///
194/// `LEFT JOIN` wraps the joined source, `RIGHT JOIN` wraps every source
195/// already in scope, and `FULL JOIN` wraps both.
196pub struct OuterJoined<T>(PhantomData<T>);
197
198impl<T: ScopeEntry> ScopeEntry for OuterJoined<T> {
199    type Key = T::Key;
200    type Nullable = Null;
201    // Recorded when the source was joined.
202    type Sources = ();
203}
204
205/// Proof that an item is the first element of a type-level list.
206///
207/// The compiler infers these proof types; users never write them.
208#[derive(Debug, Clone, Copy, Default)]
209pub struct ScopeHere;
210
211/// Proof that an item is further down a type-level list, at the position
212/// `Prev` proves for the tail.
213pub struct ScopeThere<Prev>(PhantomData<Prev>);
214
215/// Proof that a table or view was found in a scope by name comparison.
216#[derive(Debug, Clone, Copy, Default)]
217pub struct ScopeFound;
218
219/// The scope `Self` contains a source whose key is `Key`.
220///
221/// Tables and views are looked up by SQL name, so the first (innermost)
222/// matching source wins: a correlated subquery that reads the same table as
223/// its outer query resolves to its own copy, as in SQL. Aliased sources are
224/// looked up by alias type.
225///
226/// When this fails, the compiler reports "`X` is not in this query's
227/// FROM/JOIN scope". `Witness` is a proof type the compiler infers.
228#[diagnostic::on_unimplemented(
229    message = "`{Key}` is not in this query's FROM/JOIN scope",
230    label = "this expression reads a source that the query never joins",
231    note = "add the source with .from(...) or a .join(...) before using its columns"
232)]
233pub trait ScopeContains<Key, Witness> {
234    /// [`Null`] when the found source is on the nullable side of an outer join.
235    type Nullable: Nullability;
236}
237
238impl<Name, Table, Scope> ScopeContains<TableKey<Name, Table>, ScopeFound> for Scope
239where
240    Scope: FindTable<Table>,
241{
242    type Nullable = Scope::Nullable;
243}
244
245impl<Tag, Head, Tail> ScopeContains<AliasKey<Tag>, ScopeHere> for Cons<Head, Tail>
246where
247    Head: ScopeEntry<Key = AliasKey<Tag>>,
248{
249    type Nullable = Head::Nullable;
250}
251
252impl<Tag, Head, Tail, Witness> ScopeContains<AliasKey<Tag>, ScopeThere<Witness>>
253    for Cons<Head, Tail>
254where
255    Tail: ScopeContains<AliasKey<Tag>, Witness>,
256{
257    type Nullable = Tail::Nullable;
258}
259
260// =============================================================================
261// Table keys: decidable comparison by SQL name
262// =============================================================================
263
264/// Key of a table or view: its SQL name spelled as a type-level list of
265/// nibbles (see [`name`]), plus the Rust type for error messages.
266///
267/// Rust's trait system cannot tell that two different types are *not*
268/// equal, but it can compare two nibble lists digit by digit. Comparing by
269/// name is what lets the lookup skip non-matching tables.
270pub struct TableKey<Name, Table>(PhantomData<(Name, Table)>);
271
272/// Type-level boolean `true`.
273#[derive(Debug, Clone, Copy, Default)]
274pub struct True;
275/// Type-level boolean `false`.
276#[derive(Debug, Clone, Copy, Default)]
277pub struct False;
278
279/// Type-level hex digits that spell a table's SQL name, two per byte (high
280/// nibble first). `"posts"` is `Cons<H7, Cons<H0, Cons<H6, Cons<HF, ...>>>>`.
281///
282/// The table macros generate these lists; users never write them.
283pub mod name {
284    use super::{False, True};
285
286    macro_rules! nibbles {
287        ($($n:ident),+) => {
288            $(
289                #[doc(hidden)]
290                #[derive(Debug, Clone, Copy, Default)]
291                pub struct $n;
292            )+
293        };
294    }
295
296    nibbles!(
297        H0, H1, H2, H3, H4, H5, H6, H7, H8, H9, HA, HB, HC, HD, HE, HF
298    );
299
300    /// Type-level equality of two nibbles: `Out` is [`True`] or [`False`].
301    pub trait NibEq<Other> {
302        type Out;
303    }
304
305    macro_rules! nib_eq {
306        () => {};
307        ($head:ident $(, $rest:ident)*) => {
308            impl NibEq<$head> for $head {
309                type Out = True;
310            }
311            $(
312                impl NibEq<$rest> for $head {
313                    type Out = False;
314                }
315                impl NibEq<$head> for $rest {
316                    type Out = False;
317                }
318            )*
319            nib_eq!($($rest),*);
320        };
321    }
322
323    nib_eq!(
324        H0, H1, H2, H3, H4, H5, H6, H7, H8, H9, HA, HB, HC, HD, HE, HF
325    );
326}
327
328/// Type-level equality of two nibble lists: `Out` is [`True`] or [`False`].
329#[doc(hidden)]
330pub trait NameEq<Other> {
331    type Out;
332}
333
334impl NameEq<Nil> for Nil {
335    type Out = True;
336}
337
338impl<H, T> NameEq<Cons<H, T>> for Nil {
339    type Out = False;
340}
341
342impl<H, T> NameEq<Nil> for Cons<H, T> {
343    type Out = False;
344}
345
346impl<HA, TA, HB, TB> NameEq<Cons<HB, TB>> for Cons<HA, TA>
347where
348    HA: name::NibEq<HB>,
349    HA::Out: NameEqRest<TA, TB>,
350{
351    type Out = <HA::Out as NameEqRest<TA, TB>>::Out;
352}
353
354/// Compares the rest of two names only while the prefixes match.
355#[doc(hidden)]
356pub trait NameEqRest<A, B> {
357    type Out;
358}
359
360impl<A: NameEq<B>, B> NameEqRest<A, B> for True {
361    type Out = A::Out;
362}
363
364impl<A, B> NameEqRest<A, B> for False {
365    type Out = False;
366}
367
368/// The SQL name of a table key.
369#[doc(hidden)]
370pub trait TableName {
371    type Name;
372}
373
374impl<Name, Table> TableName for TableKey<Name, Table> {
375    type Name = Name;
376}
377
378/// Whether a scope entry's key names the same table as `Table`.
379#[doc(hidden)]
380pub trait IsTable<Table> {
381    type Out;
382}
383
384impl<Table, Other, T> IsTable<Table> for TableKey<Other, T>
385where
386    Table: ScopeEntry,
387    Table::Key: TableName,
388    Other: NameEq<<Table::Key as TableName>::Name>,
389{
390    type Out = Other::Out;
391}
392
393impl<Table, Tag> IsTable<Table> for AliasKey<Tag> {
394    type Out = False;
395}
396
397impl<Table> IsTable<Table> for RawSource {
398    type Out = False;
399}
400
401/// Finds the first source in a scope list that is the table `Table`,
402/// compared by SQL name.
403#[doc(hidden)]
404#[diagnostic::on_unimplemented(
405    message = "`{Table}` is not in this query's FROM/JOIN scope",
406    label = "this expression reads a table that the query never joins",
407    note = "add the table with .from(...) or a .join(...) before using its columns"
408)]
409pub trait FindTable<Table> {
410    type Nullable: Nullability;
411}
412
413impl<Table, Head, Tail> FindTable<Table> for Cons<Head, Tail>
414where
415    Head: ScopeEntry,
416    Head::Key: IsTable<Table>,
417    <Head::Key as IsTable<Table>>::Out: FoundOr<Head::Nullable, Tail, Table>,
418{
419    type Nullable =
420        <<Head::Key as IsTable<Table>>::Out as FoundOr<Head::Nullable, Tail, Table>>::Nullable;
421}
422
423/// Continues a [`FindTable`] search: [`True`] stops at the head, [`False`]
424/// searches the tail.
425#[doc(hidden)]
426pub trait FoundOr<Nullable, Rest, Table> {
427    type Nullable: Nullability;
428}
429
430impl<N: Nullability, Rest, Table> FoundOr<N, Rest, Table> for True {
431    type Nullable = N;
432}
433
434impl<N, Rest, Table> FoundOr<N, Rest, Table> for False
435where
436    Rest: FindTable<Table>,
437{
438    type Nullable = Rest::Nullable;
439}
440
441/// SELECT marker that also carries the query's scope and the sources its
442/// clauses read.
443///
444/// `.from(...)` wraps the select marker (`SelectStar`, `SelectCols`, ...) in
445/// this type, and each join and clause updates it.
446///
447/// - `Marker`: how rows decode (see [`crate::row`]).
448/// - `Scope`: the FROM/JOIN sources, newest first.
449/// - `Used`: a sources tree of every clause added so far (JOIN ON, WHERE,
450///   GROUP BY, HAVING, ORDER BY), each wrapped in [`At`] with the scope it
451///   was written against.
452///
453/// `Used` is checked by `.all()`, `.get()` and `.rows()` (through
454/// [`MarkerScopeValidFor`](crate::row::MarkerScopeValidFor)). When the query
455/// is used as a subquery, `Used` is checked against the outer query instead,
456/// so a correlated subquery can read its outer query's tables.
457pub struct Scoped<Marker, Scope, Used = ()>(PhantomData<(Marker, Scope, Used)>);
458
459/// Reads the scope of a SELECT marker and records clause sources on it.
460///
461/// Builder methods such as `.r#where(expr)` use `M::With<E::Sources>` as the
462/// new marker type, so the clause is checked later with the rest of the query.
463pub trait HasScope {
464    /// The FROM/JOIN sources.
465    type Scope;
466    /// Sources of every clause added so far.
467    type Used;
468    /// This marker after adding a clause that reads `Sources`, recorded
469    /// against the current scope.
470    type With<Sources>;
471}
472
473impl<Marker, Scope, Used> HasScope for Scoped<Marker, Scope, Used> {
474    type Scope = Scope;
475    type Used = Used;
476    type With<Sources> = Scoped<Marker, Scope, (Used, At<Scope, Sources>)>;
477}
478
479// =============================================================================
480// Sources tree
481// =============================================================================
482
483/// Sources-tree leaf: the expression reads a column of the source `T` (a
484/// [`ScopeEntry`]).
485pub struct Src<T>(PhantomData<T>);
486
487/// Sources-tree node that is NULL only when both sides are NULL, as in
488/// `COALESCE(a, b)`.
489pub struct Coalesce<A, B>(PhantomData<(A, B)>);
490
491/// Sources that are scope-checked but never make the result NULL (used by
492/// `IS NULL`, `COUNT`, `EXISTS`).
493pub type ScopeOnly<S> = Coalesce<S, NonNull>;
494
495/// Every source in the sources tree `Self` is in `Scope`.
496///
497/// This is the core scope check. It fails, with "`X` is not in this query's
498/// FROM/JOIN scope", when a [`Src<T>`] in the tree names a source that
499/// `Scope` does not contain. `Proof` is a witness type the compiler infers.
500pub trait SourcesIn<Scope, Proof> {
501    /// [`Null`] when an outer join can make the expression NULL even though
502    /// its declared nullability says otherwise.
503    type Nullable: Nullability;
504}
505
506impl<Scope> SourcesIn<Scope, ()> for () {
507    type Nullable = NonNull;
508}
509
510impl<Scope> SourcesIn<Scope, ()> for NonNull {
511    type Nullable = NonNull;
512}
513
514impl<Scope> SourcesIn<Scope, ()> for Null {
515    type Nullable = Null;
516}
517
518impl<Scope, T, Witness> SourcesIn<Scope, Witness> for Src<T>
519where
520    T: ScopeEntry,
521    Scope: ScopeContains<T::Key, Witness>,
522{
523    type Nullable = Scope::Nullable;
524}
525
526impl<Scope, A, B, ProofA, ProofB> SourcesIn<Scope, (ProofA, ProofB)> for (A, B)
527where
528    A: SourcesIn<Scope, ProofA>,
529    B: SourcesIn<Scope, ProofB>,
530{
531    type Nullable = <A::Nullable as Nullability>::Or<B::Nullable>;
532}
533
534impl<Scope, A, B, ProofA, ProofB> SourcesIn<Scope, (ProofA, ProofB)> for Coalesce<A, B>
535where
536    A: SourcesIn<Scope, ProofA>,
537    B: SourcesIn<Scope, ProofB>,
538{
539    type Nullable = <A::Nullable as Nullability>::And<B::Nullable>;
540}
541
542/// Sources `S` read by a clause written against `Scope`.
543///
544/// They resolve against `Scope` first and then against the enclosing scope.
545/// This is how a subquery's clauses can read the outer query's tables.
546///
547/// Never NULL by itself: a subquery's inner sources do not make the outer
548/// expression NULL (the subquery operator decides that).
549pub struct At<Scope, S>(PhantomData<(Scope, S)>);
550
551impl<Outer, Scope, S, Proof> SourcesIn<Outer, Proof> for At<Scope, S>
552where
553    Scope: crate::Concat<Outer>,
554    S: SourcesIn<<Scope as crate::Concat<Outer>>::Output, Proof>,
555{
556    type Nullable = NonNull;
557}
558
559/// Sources a SELECT reads when it is used as a subquery expression.
560///
561/// A scoped query resolves its clauses and projection against its own
562/// FROM/JOIN scope first ([`At`]); whatever does not resolve there must be a
563/// source of the enclosing query (a correlated reference).
564pub trait SelectSources {
565    /// The sources tree the outer query must contain.
566    type Sources;
567}
568
569impl SelectSources for crate::row::SelectStar {
570    type Sources = ();
571}
572
573impl SelectSources for crate::row::SelectExpr {
574    type Sources = ();
575}
576
577impl<R> SelectSources for crate::row::SelectAs<R> {
578    type Sources = ();
579}
580
581impl<Cols> SelectSources for crate::row::SelectCols<Cols>
582where
583    Cols: crate::row::SelectedExpressionList,
584    Cols::Expressions: crate::expr::ExprSources,
585{
586    type Sources = <Cols::Expressions as crate::expr::ExprSources>::Sources;
587}
588
589impl<M: SelectSources, Scope, Used> SelectSources for Scoped<M, Scope, Used> {
590    type Sources = (Used, At<Scope, M::Sources>);
591}
592
593/// The marker of a compound query (`UNION`, `INTERSECT`, `EXCEPT`) built
594/// from a query with marker `Self` and an operand with marker `Other`.
595///
596/// The compound decodes like the left query. The operand's sources are kept
597/// so the compound is scope-checked as a whole.
598pub trait SetOperand<Other> {
599    /// The marker of the compound query.
600    type Combined;
601}
602
603impl<M, Scope, Used, Other: SelectSources> SetOperand<Other> for Scoped<M, Scope, Used> {
604    type Combined = Scoped<M, Scope, (Used, Other::Sources)>;
605}
606
607impl<Other> SetOperand<Other> for crate::row::SelectStar {
608    type Combined = Self;
609}
610
611impl<Other> SetOperand<Other> for crate::row::SelectExpr {
612    type Combined = Self;
613}
614
615impl<Cols, Other> SetOperand<Other> for crate::row::SelectCols<Cols> {
616    type Combined = Self;
617}
618
619impl<R, Other> SetOperand<Other> for crate::row::SelectAs<R> {
620    type Combined = Self;
621}
622
623/// Sources of a COALESCE-style operand: its declared nullability `N` plus
624/// its sources `S`, which may add nullability from outer joins.
625pub type Arg<N, S> = (N, S);
626
627// =============================================================================
628// Generic type lists
629// =============================================================================
630
631/// The `Cons` list `Self` contains the type `T`.
632///
633/// Used for column lists (GROUP BY keys, INSERT target columns), where the
634/// elements are compared by type rather than by scope key. `Witness` is a
635/// proof type the compiler infers ([`ScopeHere`] / [`ScopeThere`]).
636pub trait ListContains<T, Witness> {}
637
638impl<Head, Tail> ListContains<Head, ScopeHere> for Cons<Head, Tail> {}
639
640impl<Head, Tail, T, Witness> ListContains<T, ScopeThere<Witness>> for Cons<Head, Tail> where
641    Tail: ListContains<T, Witness>
642{
643}
644
645/// Every element of the `Cons` list `Required` is in the list `Self`.
646pub trait ListIncludes<Required, Proof> {}
647
648impl<List> ListIncludes<Nil, ()> for List {}
649
650impl<List, Head, Tail, HeadProof, TailProof> ListIncludes<Cons<Head, Tail>, (HeadProof, TailProof)>
651    for List
652where
653    List: ListContains<Head, HeadProof> + ListIncludes<Tail, TailProof>,
654{
655}
656
657// =============================================================================
658// Joins
659// =============================================================================
660
661/// Join kind for [`JoinStep`]: `JOIN`, `INNER JOIN` or `CROSS JOIN`. No
662/// source becomes nullable.
663#[derive(Debug, Clone, Copy, Default)]
664pub struct InnerJoin;
665/// Join kind for [`JoinStep`]: `LEFT [OUTER] JOIN`. The joined source can
666/// be NULL.
667#[derive(Debug, Clone, Copy, Default)]
668pub struct LeftJoin;
669/// Join kind for [`JoinStep`]: `RIGHT [OUTER] JOIN`. Every source already
670/// in scope can be NULL.
671#[derive(Debug, Clone, Copy, Default)]
672pub struct RightJoin;
673/// Join kind for [`JoinStep`]: `FULL [OUTER] JOIN`. Every source can be
674/// NULL.
675#[derive(Debug, Clone, Copy, Default)]
676pub struct FullJoin;
677
678/// Join kind for [`JoinStep`]: `[INNER|LEFT|CROSS] JOIN LATERAL` (`Kind` is
679/// [`InnerJoin`] or [`LeftJoin`]). The joined subquery may read the sources
680/// joined before it.
681pub struct Lateral<Kind>(PhantomData<Kind>);
682
683/// Wraps every entry of a scope list in [`OuterJoined`].
684#[doc(hidden)]
685pub trait OuterJoinScope {
686    type Out;
687}
688
689impl OuterJoinScope for Nil {
690    type Out = Self;
691}
692
693impl<Head, Tail: OuterJoinScope> OuterJoinScope for Cons<Head, Tail> {
694    type Out = Cons<OuterJoined<Head>, Tail::Out>;
695}
696
697/// How a select marker's row type changes when `Joined` is joined.
698///
699/// `SELECT *` grows the row by the joined model (wrapped in `Option` on the
700/// nullable side). Every other marker keeps its row.
701#[doc(hidden)]
702pub trait JoinRow<Row, Joined, Kind> {
703    type Row;
704}
705
706/// The marker and row type after joining `Joined` with join kind `Kind`
707/// ([`InnerJoin`], [`LeftJoin`], [`RightJoin`], [`FullJoin`] or
708/// [`Lateral`]).
709///
710/// Join builder methods use this to compute their return type. It pushes
711/// `Joined` onto the scope, wrapping the sources an outer join can leave
712/// NULL in [`OuterJoined`].
713///
714/// `On` is the sources tree of the join's `ON` condition. It is recorded
715/// against the scope that includes `Joined`, which is exactly what the
716/// condition may reference (plus the enclosing query, for a correlated
717/// subquery). The joined source's own [`ScopeEntry::Sources`] resolve against
718/// the enclosing query only, or against the new scope for a [`Lateral`] join.
719/// Nothing is checked here; the check happens at the terminal method.
720pub trait JoinStep<Row, Joined, Kind, On = ()> {
721    /// The new marker, with `Joined` pushed into its scope.
722    type Marker;
723    /// The new inferred row type.
724    type Row;
725}
726
727/// Records a join's sources on a marker whose scope became `NewScope`.
728type Joined<M, NewScope, Used, Free, On> = Scoped<M, NewScope, ((Used, Free), At<NewScope, On>)>;
729
730impl<M, Scope, Used, Row, J, On> JoinStep<Row, J, InnerJoin, On> for Scoped<M, Scope, Used>
731where
732    M: JoinRow<Row, J, InnerJoin>,
733    J: ScopeEntry,
734{
735    type Marker = Joined<M, Cons<J, Scope>, Used, J::Sources, On>;
736    type Row = M::Row;
737}
738
739impl<M, Scope, Used, Row, J, On> JoinStep<Row, J, LeftJoin, On> for Scoped<M, Scope, Used>
740where
741    M: JoinRow<Row, J, LeftJoin>,
742    J: ScopeEntry,
743{
744    type Marker = Joined<M, Cons<OuterJoined<J>, Scope>, Used, J::Sources, On>;
745    type Row = M::Row;
746}
747
748impl<M, Scope, Used, Row, J, On> JoinStep<Row, J, RightJoin, On> for Scoped<M, Scope, Used>
749where
750    M: JoinRow<Row, J, RightJoin>,
751    J: ScopeEntry,
752    Scope: OuterJoinScope,
753{
754    type Marker = Joined<M, Cons<J, Scope::Out>, Used, J::Sources, On>;
755    type Row = M::Row;
756}
757
758impl<M, Scope, Used, Row, J, On> JoinStep<Row, J, FullJoin, On> for Scoped<M, Scope, Used>
759where
760    M: JoinRow<Row, J, FullJoin>,
761    J: ScopeEntry,
762    Scope: OuterJoinScope,
763{
764    type Marker = Joined<M, Cons<OuterJoined<J>, Scope::Out>, Used, J::Sources, On>;
765    type Row = M::Row;
766}
767
768impl<M, Scope, Used, Row, J, On> JoinStep<Row, J, Lateral<InnerJoin>, On> for Scoped<M, Scope, Used>
769where
770    M: JoinRow<Row, J, InnerJoin>,
771    J: ScopeEntry,
772{
773    type Marker = Joined<M, Cons<J, Scope>, Used, (), (On, J::Sources)>;
774    type Row = M::Row;
775}
776
777impl<M, Scope, Used, Row, J, On> JoinStep<Row, J, Lateral<LeftJoin>, On> for Scoped<M, Scope, Used>
778where
779    M: JoinRow<Row, J, LeftJoin>,
780    J: ScopeEntry,
781{
782    type Marker = Joined<M, Cons<OuterJoined<J>, Scope>, Used, (), (On, J::Sources)>;
783    type Row = M::Row;
784}
785
786/// The marker after `.from(source)`: `M` wrapped in [`Scoped`] with `Source`
787/// as the only scope entry.
788pub type FromMarker<M, Source> = Scoped<M, Cons<Source, Nil>, <Source as ScopeEntry>::Sources>;