good_ormning/sqlite/query/
select_body.rs

1use {
2    super::{
3        expr::{
4            check_assignable,
5            check_bool,
6            check_general_same,
7            Expr,
8            ExprType,
9            Binding,
10        },
11        utils::{
12            build_returning_values,
13            SqliteQueryCtx,
14        },
15    },
16    crate::{
17        sqlite::{
18            schema::table::Table,
19            types::{
20                type_i64,
21                Type,
22            },
23            QueryResCount,
24        },
25        utils::Tokens,
26    },
27    std::collections::HashMap,
28};
29
30#[derive(Clone, Debug)]
31pub enum Order {
32    Asc,
33    Desc,
34}
35
36#[derive(Clone, Debug)]
37pub enum JoinSource {
38    Subsel(Box<SelectBody>),
39    Table(Table),
40}
41
42#[derive(Clone, Debug)]
43pub struct NamedSelectSource {
44    pub source: JoinSource,
45    pub alias: Option<String>,
46}
47
48impl NamedSelectSource {
49    fn build(&self, ctx: &mut SqliteQueryCtx, path: &rpds::Vector<String>) -> (Vec<(Binding, Type)>, Tokens) {
50        let mut out = Tokens::new();
51        let mut new_fields: Vec<(Binding, Type)> = match &self.source {
52            JoinSource::Subsel(s) => {
53                let res =
54                    s.build(ctx, &HashMap::new(), &path.push_back(format!("From subselect")), QueryResCount::Many);
55                out.s("(").s(&res.1.to_string()).s(")");
56                res.0.0.clone()
57            },
58            JoinSource::Table(s) => {
59                let new_fields = match ctx.tables.get(&s) {
60                    Some(f) => f,
61                    None => {
62                        ctx.errs.err(&path.push_back(format!("From")), format!("No known table with id {}", s));
63                        return (vec![], Tokens::new());
64                    },
65                };
66                out.id(&s.id);
67                new_fields.iter().map(|e| (Binding::field(e), e.type_.type_.clone())).collect()
68            },
69        };
70        if let Some(s) = &self.alias {
71            out.s("as").id(s);
72            let mut new_fields2 = vec![];
73            for (k, v) in new_fields {
74                new_fields2.push((k.with_alias(s), v));
75            }
76            new_fields = new_fields2;
77        }
78        (new_fields, out)
79    }
80}
81
82#[derive(Clone, Debug)]
83pub enum JoinType {
84    Left,
85    Inner,
86}
87
88#[derive(Clone, Debug)]
89pub struct Join {
90    pub source: Box<NamedSelectSource>,
91    pub type_: JoinType,
92    pub on: Expr,
93}
94
95#[derive(Clone, Debug)]
96pub struct Returning {
97    pub e: Expr,
98    pub rename: Option<String>,
99}
100
101#[derive(Clone, Debug)]
102pub struct SelectBody {
103    pub table: NamedSelectSource,
104    pub distinct: bool,
105    pub returning: Vec<Returning>,
106    pub join: Vec<Join>,
107    pub where_: Option<Expr>,
108    pub group: Vec<Expr>,
109    pub order: Vec<(Expr, Order)>,
110    pub limit: Option<Expr>,
111}
112
113impl SelectBody {
114    pub fn build(
115        &self,
116        ctx: &mut super::utils::SqliteQueryCtx,
117        inject_scope: &HashMap<Binding, Type>,
118        path: &rpds::Vector<String>,
119        res_count: QueryResCount,
120    ) -> (ExprType, Tokens) {
121        // Prep
122        let source = self.table.build(ctx, path);
123        let mut scope = inject_scope.clone();
124        for (k, v) in source.0 {
125            scope.insert(k, v);
126        }
127        let mut joins = vec![];
128        for (i, je) in self.join.iter().enumerate() {
129            let path = path.push_back(format!("Join {}", i));
130            let mut out = Tokens::new();
131            match je.type_ {
132                JoinType::Left => out.s("left"),
133                JoinType::Inner => out.s("inner"),
134            };
135            out.s("join");
136            let source = je.source.build(ctx, &path);
137            out.s(&source.1.to_string());
138            match je.type_ {
139                JoinType::Left => {
140                    for (k, mut v) in source.0 {
141                        if !v.opt {
142                            v = Type {
143                                opt: true,
144                                type_: v.type_,
145                                array: false,
146                            };
147                        }
148                        scope.insert(k, v);
149                    }
150                },
151                JoinType::Inner => {
152                    for (k, v) in source.0 {
153                        scope.insert(k, v);
154                    }
155                },
156            }
157            out.s("on").s(&je.on.build(ctx, &path, &scope).1.to_string());
158            joins.push(out.to_string());
159        }
160
161        // Build query
162        let mut out = Tokens::new();
163        out.s("select");
164        if self.distinct {
165            out.s("distinct");
166        }
167        if self.returning.is_empty() {
168            ctx.errs.err(path, format!("Select must have at least one output, but outputs are empty"));
169        }
170        let out_type = build_returning_values(ctx, path, &scope, &mut out, &self.returning, res_count);
171        out.s("from");
172        out.s(&source.1.to_string());
173        for join in joins {
174            out.s(&join);
175        }
176        if let Some(where_) = &self.where_ {
177            out.s("where");
178            let path = path.push_back("Where".into());
179            let (where_t, where_tokens) = where_.build(ctx, &path, &scope);
180            check_bool(ctx, &path, &where_t);
181            out.s(&where_tokens.to_string());
182        }
183        if self.group.len() > 0 {
184            out.s("group by");
185            for (i, g) in self.group.iter().enumerate() {
186                let path = path.push_back(format!("Group by clause {}", i));
187                if i > 0 {
188                    out.s(",");
189                }
190                let (_, g_tokens) = g.build(ctx, &path, &scope);
191                out.s(&g_tokens.to_string());
192            }
193        }
194        if !self.order.is_empty() {
195            out.s("order by");
196            for (i, o) in self.order.iter().enumerate() {
197                let path = path.push_back(format!("Order by clause {}", i));
198                if i > 0 {
199                    out.s(",");
200                }
201                let (_, o_tokens) = o.0.build(ctx, &path, &scope);
202                out.s(&o_tokens.to_string());
203                out.s(match o.1 {
204                    Order::Asc => "asc",
205                    Order::Desc => "desc",
206                });
207            }
208        }
209        if let Some(l) = &self.limit {
210            out.s("limit");
211            let path = path.push_back("Limit".into());
212            let (limit_t, limit_tokens) = l.build(ctx, &path, &scope);
213            check_general_same(ctx, &path, &limit_t, &ExprType(vec![(Binding::empty(), type_i64().build())]));
214            out.s(&limit_tokens.to_string());
215        }
216        (out_type, out)
217    }
218}
219
220#[derive(Clone, Debug, Copy)]
221pub enum SelectJunctionOperator {
222    Union,
223    UnionAll,
224    Intersect,
225    Except,
226}
227
228#[derive(Clone, Debug)]
229pub struct SelectJunction {
230    pub op: SelectJunctionOperator,
231    pub body: SelectBody,
232}
233
234pub fn build_select_junction(
235    ctx: &mut super::utils::SqliteQueryCtx,
236    path: &rpds::Vector<String>,
237    base_type: &ExprType,
238    body_junctions: &[SelectJunction],
239) -> Tokens {
240    let mut out = Tokens::new();
241    for (i, j) in body_junctions.iter().enumerate() {
242        let path = path.push_back(format!("Junction clause {} - {:?}", i, j.op));
243        match j.op {
244            SelectJunctionOperator::Union => {
245                out.s("union");
246            },
247            SelectJunctionOperator::UnionAll => {
248                out.s("union all");
249            },
250            SelectJunctionOperator::Intersect => {
251                out.s("intersect");
252            },
253            SelectJunctionOperator::Except => {
254                out.s("except");
255            },
256        }
257        let j_body = j.body.build(ctx, &HashMap::new(), &path, QueryResCount::Many);
258        if j_body.0.0.len() != base_type.0.len() {
259            ctx
260                .errs
261                .err(
262                    &path,
263                    format!(
264                        "Select returns {} columns but the base select has {} columns and these must match exactly",
265                        j_body.0.0.len(),
266                        base_type.0.len()
267                    ),
268                );
269            continue;
270        }
271        for (i, ((_, got), (_, want))) in Iterator::zip(j_body.0.0.iter(), base_type.0.iter()).enumerate() {
272            let path = path.push_back(format!("Select return {}", i));
273            check_assignable(&mut ctx.errs, &path, want, &ExprType(vec![(Binding::empty(), got.clone())]));
274        }
275        out.s(&j_body.1.to_string());
276    }
277    return out;
278}