siderite_orm/expr/
related.rs1use super::{Expr, Field, Ident, pk_column};
8use crate::model::Model;
9use crate::plan::SelectExpr;
10use crate::relations::ForeignKey;
11use std::marker::PhantomData;
12
13#[derive(Debug, Clone, PartialEq)]
15pub struct RelHop {
16 pub fk_column: Ident,
18 pub table: Ident,
20 pub pk_column: Ident,
22}
23
24impl RelHop {
25 pub fn alias_part(&self) -> &str {
27 self.fk_column
28 .strip_suffix("_id")
29 .filter(|s| !s.is_empty())
30 .unwrap_or(&self.fk_column)
31 }
32}
33
34#[derive(Debug, Clone, PartialEq)]
36pub struct RelatedColumn {
37 pub path: Vec<RelHop>,
39 pub column: Ident,
41}
42
43impl RelatedColumn {
44 pub fn alias(&self) -> String {
46 path_alias_of(&self.path)
47 }
48
49 pub fn output_name(&self) -> String {
51 format!("{}__{}", self.alias(), self.column)
52 }
53}
54
55pub fn path_alias_of(path: &[RelHop]) -> String {
57 path.iter()
58 .map(RelHop::alias_part)
59 .collect::<Vec<_>>()
60 .join("__")
61}
62
63pub trait FkSlot<T: Model> {
65 fn fk(&mut self) -> Option<&mut ForeignKey<T>>;
67}
68
69impl<T: Model> FkSlot<T> for ForeignKey<T> {
70 fn fk(&mut self) -> Option<&mut ForeignKey<T>> {
71 Some(self)
72 }
73}
74
75impl<T: Model> FkSlot<T> for Option<ForeignKey<T>> {
76 fn fk(&mut self) -> Option<&mut ForeignKey<T>> {
77 self.as_mut()
78 }
79}
80
81pub trait Related<T, X> {
84 fn into_parts(self) -> (Vec<RelHop>, &'static str);
86}
87
88impl<T, X> Related<T, X> for Field<T, X> {
89 fn into_parts(self) -> (Vec<RelHop>, &'static str) {
90 (Vec::new(), self.name())
91 }
92}
93
94impl<T, X> Related<T, X> for Joined<T, X> {
95 fn into_parts(self) -> (Vec<RelHop>, &'static str) {
96 (self.path, self.column)
97 }
98}
99
100pub struct Joined<M, T> {
102 path: Vec<RelHop>,
103 column: &'static str,
104 _marker: PhantomData<fn() -> (M, T)>,
105}
106
107impl<M, T> Clone for Joined<M, T> {
108 fn clone(&self) -> Self {
109 Self {
110 path: self.path.clone(),
111 column: self.column,
112 _marker: PhantomData,
113 }
114 }
115}
116
117impl<M, T> std::fmt::Debug for Joined<M, T> {
118 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119 f.debug_struct("Joined")
120 .field("path", &self.path)
121 .field("column", &self.column)
122 .finish()
123 }
124}
125
126impl<M, T> Joined<M, T> {
127 fn related_column(self) -> RelatedColumn {
128 RelatedColumn {
129 path: self.path,
130 column: self.column.into(),
131 }
132 }
133
134 pub fn select(self) -> SelectExpr {
136 let related = self.related_column();
137 SelectExpr::new(
138 Expr::Related(related.clone()),
139 Some(related.output_name().into()),
140 )
141 }
142}
143
144impl<M, T> From<Joined<M, T>> for Expr {
145 fn from(j: Joined<M, T>) -> Self {
146 Expr::Related(j.related_column())
147 }
148}
149
150impl<M, T> From<Joined<M, T>> for SelectExpr {
151 fn from(j: Joined<M, T>) -> Self {
152 j.select()
153 }
154}
155
156impl<M, S> Field<M, S> {
157 pub fn join<T: Model, X>(self, inner: impl Related<T, X>) -> Joined<M, X>
160 where
161 S: FkSlot<T>,
162 {
163 let (mut path, column) = inner.into_parts();
164 path.insert(
165 0,
166 RelHop {
167 fk_column: self.name().into(),
168 table: T::META.table.into(),
169 pk_column: pk_column::<T>(),
170 },
171 );
172 Joined {
173 path,
174 column,
175 _marker: PhantomData,
176 }
177 }
178}