good_ormning/sqlite/query/
utils.rs

1use {
2    super::{
3        expr::{
4            check_assignable,
5            Binding,
6            Expr,
7            ExprType,
8        },
9        select_body::{
10            Returning,
11            SelectBody,
12            SelectJunction,
13            SelectJunctionOperator,
14        },
15    },
16    crate::{
17        sqlite::{
18            schema::{
19                field::{
20                    Field,
21                    FieldType,
22                    Field_,
23                    SchemaFieldId,
24                },
25                table::{
26                    SchemaTableId,
27                    Table,
28                    Table_,
29                },
30            },
31            types::Type,
32            QueryResCount,
33        },
34        utils::{
35            Errs,
36            Tokens,
37        },
38    },
39    proc_macro2::TokenStream,
40    std::{
41        collections::{
42            HashMap,
43            HashSet,
44        },
45        rc::Rc,
46    },
47};
48
49pub struct SqliteQueryCtx {
50    pub(crate) tables: HashMap<Table, HashSet<Field>>,
51    pub errs: Errs,
52    pub(crate) rust_arg_lookup: HashMap<String, (usize, Type)>,
53    pub(crate) rust_args: Vec<TokenStream>,
54    pub(crate) query_args: Vec<TokenStream>,
55}
56
57impl<'a> SqliteQueryCtx {
58    pub(crate) fn new(errs: Errs, tables: HashMap<Table, HashSet<Field>>) -> Self {
59        Self {
60            tables: tables,
61            errs: errs,
62            rust_arg_lookup: Default::default(),
63            rust_args: Default::default(),
64            query_args: Default::default(),
65        }
66    }
67}
68
69pub trait QueryBody {
70    fn build(
71        &self,
72        ctx: &mut SqliteQueryCtx,
73        path: &rpds::Vector<String>,
74        res_count: QueryResCount,
75    ) -> (ExprType, Tokens);
76}
77
78pub fn build_set(
79    ctx: &mut SqliteQueryCtx,
80    path: &rpds::Vector<String>,
81    scope: &HashMap<Binding, Type>,
82    out: &mut Tokens,
83    values: &Vec<(Field, Expr)>,
84) {
85    out.s("set");
86    for (i, (field, val)) in values.iter().enumerate() {
87        let path = path.push_back(format!("Set field {}", i));
88        if i > 0 {
89            out.s(",");
90        }
91        out.id(&field.id).s("=");
92        let res = val.build(ctx, &path, &scope);
93        let field = match ctx.tables.get(&field.table).and_then(|t| t.get(&field)) {
94            Some(t) => t,
95            None => {
96                ctx.errs.err(&path, format!("Update destination value field {} is not known", field));
97                continue;
98            },
99        };
100        check_assignable(&mut ctx.errs, &path, &field.type_.type_, &res.0);
101        out.s(&res.1.to_string());
102    }
103}
104
105pub fn build_returning_values(
106    ctx: &mut SqliteQueryCtx,
107    path: &rpds::Vector<String>,
108    scope: &HashMap<Binding, Type>,
109    out: &mut Tokens,
110    outputs: &Vec<Returning>,
111    res_count: QueryResCount,
112) -> ExprType {
113    if outputs.is_empty() {
114        if !matches!(res_count, QueryResCount::None) {
115            ctx.errs.err(path, format!("Query has no outputs but res_count is, {:?}, not None", res_count));
116        }
117    } else {
118        if matches!(res_count, QueryResCount::None) {
119            ctx.errs.err(&path, format!("Query has outputs so res_count must be not None, but is {:?}", res_count));
120        }
121    }
122    let mut out_rec: Vec<(Binding, Type)> = vec![];
123    for (i, o) in outputs.iter().enumerate() {
124        let path = path.push_back(format!("Result {}", i));
125        if i > 0 {
126            out.s(",");
127        }
128        let res = o.e.build(ctx, &path, scope);
129        out.s(&res.1.to_string());
130        let (res_name, res_type) = match res.0.assert_scalar(&mut ctx.errs, &path) {
131            Some(x) => x,
132            None => continue,
133        };
134        if let Some(rename) = &o.rename {
135            out.s("as").id(rename);
136            out_rec.push((Binding::local(rename.clone()), res_type));
137        } else {
138            out_rec.push((res_name, res_type));
139        }
140    }
141    ExprType(out_rec)
142}
143
144pub fn build_returning(
145    ctx: &mut SqliteQueryCtx,
146    path: &rpds::Vector<String>,
147    scope: &HashMap<Binding, Type>,
148    out: &mut Tokens,
149    outputs: &Vec<Returning>,
150    res_count: QueryResCount,
151) -> ExprType {
152    if !outputs.is_empty() {
153        out.s("returning");
154    }
155    build_returning_values(ctx, path, scope, out, outputs, res_count)
156}
157
158#[derive(Clone, Debug)]
159pub struct With {
160    pub recursive: bool,
161    pub ctes: Vec<Cte>,
162}
163
164#[derive(Clone, Debug)]
165pub struct Cte {
166    pub table: Table,
167    pub columns: Vec<Field>,
168    pub body: SelectBody,
169    pub body_junctions: Vec<SelectJunction>,
170}
171
172pub struct CteBuilder {
173    table: Table,
174    cte: Cte,
175}
176
177impl CteBuilder {
178    pub fn new(id: impl AsRef<str>, body: SelectBody) -> Self {
179        let table = Table(Rc::new(Table_ {
180            schema_id: SchemaTableId("".to_string()),
181            id: id.as_ref().to_string(),
182        }));
183        return Self {
184            table: table.clone(),
185            cte: Cte {
186                table: table,
187                columns: vec![],
188                body: body,
189                body_junctions: vec![],
190            },
191        };
192    }
193
194    pub fn body_junction(&mut self, j: SelectJunction) {
195        self.cte.body_junctions.push(j);
196    }
197
198    pub fn field(&mut self, id: impl AsRef<str>, type_: Type) -> Field {
199        let f = Field(Rc::new(Field_ {
200            table: self.table.clone(),
201            schema_id: SchemaFieldId(id.as_ref().to_string()),
202            id: id.as_ref().to_string(),
203            type_: FieldType {
204                type_: type_,
205                migration_default: None,
206            },
207        }));
208        if self.cte.columns.contains(&f) {
209            panic!("Duplicate field {} in CTE definition", id.as_ref());
210        }
211        self.cte.columns.push(f.clone());
212        return f;
213    }
214
215    pub fn build(self) -> (Table, Cte) {
216        return (self.table, self.cte);
217    }
218}
219
220pub fn build_with(ctx: &mut SqliteQueryCtx, path: &rpds::Vector<String>, with: &With) -> Tokens {
221    let mut out = Tokens::new();
222    out.s("with");
223    for (i, cte) in with.ctes.iter().enumerate() {
224        if i > 0 {
225            out.s(",");
226        }
227        let path = path.push_back(format!("CTE {}", i));
228        out.s(&cte.table.id);
229        out.s("(");
230        for (i, c) in cte.columns.iter().enumerate() {
231            if i > 0 {
232                out.s(",");
233            }
234            out.s(&c.id);
235        }
236        out.s(")");
237        out.s("as");
238        out.s("(");
239        let body = cte.body.build(ctx, &HashMap::new(), &path, QueryResCount::Many);
240        for (i, ((_, got), want)) in Iterator::zip(body.0.0.iter(), cte.columns.iter()).enumerate() {
241            let path = path.push_back(format!("Select return {}", i));
242            check_assignable(
243                &mut ctx.errs,
244                &path,
245                &want.type_.type_,
246                &ExprType(vec![(Binding::empty(), got.clone())]),
247            );
248        }
249        out.s(&body.1.to_string());
250        if body.0.0.len() != cte.columns.len() {
251            ctx
252                .errs
253                .err(
254                    &path,
255                    format!(
256                        "Select returns {} columns but the CTE needs exactly {} columns",
257                        body.0.0.len(),
258                        cte.columns.len()
259                    ),
260                );
261            continue;
262        }
263        ctx.tables.insert(cte.table.clone(), cte.columns.iter().cloned().collect());
264        for (i, j) in cte.body_junctions.iter().enumerate() {
265            let path = path.push_back(format!("Junction clause {} - {:?}", i, j.op));
266            match j.op {
267                SelectJunctionOperator::Union => {
268                    out.s("union");
269                },
270                SelectJunctionOperator::UnionAll => {
271                    out.s("union all");
272                },
273                SelectJunctionOperator::Intersect => {
274                    out.s("intersect");
275                },
276                SelectJunctionOperator::Except => {
277                    out.s("except");
278                },
279            }
280            let j_body = j.body.build(ctx, &HashMap::new(), &path, QueryResCount::Many);
281            if j_body.0.0.len() != cte.columns.len() {
282                ctx
283                    .errs
284                    .err(
285                        &path,
286                        format!(
287                            "Select returns {} columns but the CTE needs exactly {} columns",
288                            j_body.0.0.len(),
289                            cte.columns.len()
290                        ),
291                    );
292                continue;
293            }
294            for (i, ((_, got), want)) in Iterator::zip(j_body.0.0.iter(), cte.columns.iter()).enumerate() {
295                let path = path.push_back(format!("Select return {}", i));
296                check_assignable(
297                    &mut ctx.errs,
298                    &path,
299                    &want.type_.type_,
300                    &ExprType(vec![(Binding::empty(), got.clone())]),
301                );
302            }
303        }
304        out.s(")");
305    }
306    return out;
307}