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>;