Skip to main content

schema_core/config/
join.rs

1use serde::{Deserialize, Serialize};
2
3use crate::{Field, common};
4
5use super::Filter;
6
7/// Folds rows from a related `table` into the document. The [`kind`](Self::kind)
8/// names the relationship — which side carries the key, and whether one row or
9/// many fold in; `filters`, `order_by`, and `limit` narrow and shape the rows
10/// that come back.
11#[derive(Debug, Clone, Hash, Serialize, Deserialize)]
12pub struct Join {
13    pub table: common::TableName,
14    pub kind: JoinKind,
15    pub primary_key: common::ColumnName,
16    /// Whether the folded-in object may be absent. Only meaningful for a to-one
17    /// join (`belongs_to`/`has_one`); a to-many join is always a non-null array.
18    /// A to-one join defaults to nullable unless the schema marks it `required`.
19    #[serde(default)]
20    pub nullable: bool,
21    #[serde(default, skip_serializing_if = "Option::is_none")]
22    pub filters: Option<Vec<Filter>>,
23    #[serde(default, skip_serializing_if = "Option::is_none")]
24    pub order_by: Option<Vec<OrderBy>>,
25    #[serde(default, skip_serializing_if = "Option::is_none")]
26    pub limit: Option<u64>,
27    pub fields: Vec<Field>,
28}
29
30/// The relationship a join expresses. The verb carries both the cardinality
31/// and — the part that matters — **which table holds the key**:
32///
33/// - [`BelongsTo`](Self::BelongsTo): *this* row points at the related row
34///   (`column` is on the parent table) → a single object.
35/// - [`HasOne`](Self::HasOne) / [`HasMany`](Self::HasMany): the related rows
36///   point back at this one (`foreign_key` is on the related table) → a single
37///   object / a nested array.
38/// - [`ManyToMany`](Self::ManyToMany): both sides are keyed through a junction
39///   table → a nested array.
40#[derive(Debug, Clone, Hash, Serialize, Deserialize)]
41#[serde(rename_all = "snake_case")]
42pub enum JoinKind {
43    BelongsTo { column: common::ColumnName },
44    HasOne { foreign_key: common::ColumnName },
45    HasMany { foreign_key: common::ColumnName },
46    ManyToMany { through: Through },
47}
48
49impl JoinKind {
50    /// Whether this join folds in many rows (a nested array) rather than one
51    /// (an object).
52    pub fn is_to_many(&self) -> bool {
53        matches!(self, JoinKind::HasMany { .. } | JoinKind::ManyToMany { .. })
54    }
55}
56
57/// How an aggregate's related rows tie back to the parent — a direct FK on the
58/// aggregated table, or a junction table. (Joins carry their key inside
59/// [`JoinKind`]; aggregates are inherently over-many, so `belongs_to` has no
60/// aggregate counterpart.)
61#[derive(Debug, Clone, Hash, Serialize, Deserialize)]
62#[serde(rename_all = "snake_case")]
63pub enum AggregateKey {
64    Direct(common::ColumnName),
65    Through(Through),
66}
67
68/// A junction table linking two sides of a many-to-many relation.
69#[derive(Debug, Clone, Hash, Serialize, Deserialize)]
70pub struct Through {
71    pub table: common::TableName,
72    pub left_key: common::ColumnName,
73    pub right_key: common::ColumnName,
74}
75
76#[derive(Debug, Clone, Hash, Serialize, Deserialize)]
77pub struct OrderBy {
78    pub column: common::ColumnName,
79    #[serde(default, skip_serializing_if = "Option::is_none")]
80    pub direction: Option<Direction>,
81}
82
83#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
84#[serde(rename_all = "snake_case")]
85pub enum Direction {
86    Asc,
87    Desc,
88}