good_ormning/pg/query/
utils.rs

1use std::{
2    collections::HashMap,
3};
4use proc_macro2::TokenStream;
5use crate::{
6    pg::{
7        types::Type,
8        QueryResCount,
9        schema::{
10            field::Field,
11            table::Table,
12        },
13    },
14    utils::{
15        Tokens,
16        Errs,
17    },
18};
19use super::{
20    expr::{
21        ExprType,
22        ExprValName,
23        Expr,
24        check_assignable,
25    },
26    select::Returning,
27};
28
29pub struct PgQueryCtx<'a> {
30    pub(crate) tables: &'a HashMap<Table, HashMap<Field, Type>>,
31    pub errs: Errs,
32    pub(crate) rust_arg_lookup: HashMap<String, (usize, Type)>,
33    pub(crate) rust_args: Vec<TokenStream>,
34    pub(crate) query_args: Vec<TokenStream>,
35}
36
37impl<'a> PgQueryCtx<'a> {
38    pub(crate) fn new(errs: Errs, tables: &'a HashMap<Table, HashMap<Field, Type>>) -> Self {
39        Self {
40            tables: tables,
41            errs: errs,
42            rust_arg_lookup: Default::default(),
43            rust_args: Default::default(),
44            query_args: Default::default(),
45        }
46    }
47}
48
49pub trait QueryBody {
50    fn build(
51        &self,
52        ctx: &mut PgQueryCtx,
53        path: &rpds::Vector<String>,
54        res_count: QueryResCount,
55    ) -> (ExprType, Tokens);
56}
57
58pub fn build_set(
59    ctx: &mut PgQueryCtx,
60    path: &rpds::Vector<String>,
61    scope: &HashMap<ExprValName, Type>,
62    out: &mut Tokens,
63    values: &Vec<(Field, Expr)>,
64) {
65    out.s("set");
66    for (i, (field, val)) in values.iter().enumerate() {
67        let path = path.push_back(format!("Set field {}", i));
68        if i > 0 {
69            out.s(",");
70        }
71        out.id(&field.id).s("=");
72        let res = val.build(ctx, &path, &scope);
73        let field_type = match ctx.tables.get(&field.table).and_then(|t| t.get(&field)) {
74            Some(t) => t,
75            None => {
76                ctx.errs.err(&path, format!("Update destination value field {} is not known", field));
77                continue;
78            },
79        };
80        check_assignable(&mut ctx.errs, &path, field_type, &res.0);
81        out.s(&res.1.to_string());
82    }
83}
84
85pub fn build_returning_values(
86    ctx: &mut PgQueryCtx,
87    path: &rpds::Vector<String>,
88    scope: &HashMap<ExprValName, Type>,
89    out: &mut Tokens,
90    outputs: &Vec<Returning>,
91    res_count: QueryResCount,
92) -> ExprType {
93    if outputs.is_empty() {
94        if !matches!(res_count, QueryResCount::None) {
95            ctx.errs.err(path, format!("Query has no outputs but res_count is, {:?}, not None", res_count));
96        }
97    } else {
98        if matches!(res_count, QueryResCount::None) {
99            ctx.errs.err(&path, format!("Query has outputs so res_count must be not None, but is {:?}", res_count));
100        }
101    }
102    let mut out_rec: Vec<(ExprValName, Type)> = vec![];
103    for (i, o) in outputs.iter().enumerate() {
104        let path = path.push_back(format!("Result {}", i));
105        if i > 0 {
106            out.s(",");
107        }
108        let res = o.e.build(ctx, &path, scope);
109        out.s(&res.1.to_string());
110        let (res_name, res_type) = match res.0.assert_scalar(&mut ctx.errs, &path) {
111            Some(x) => x,
112            None => continue,
113        };
114        if let Some(rename) = &o.rename {
115            out.s("as").id(rename);
116            out_rec.push((ExprValName::local(rename.clone()), res_type));
117        } else {
118            out_rec.push((res_name, res_type));
119        }
120    }
121    ExprType(out_rec)
122}
123
124pub fn build_returning(
125    ctx: &mut PgQueryCtx,
126    path: &rpds::Vector<String>,
127    scope: &HashMap<ExprValName, Type>,
128    out: &mut Tokens,
129    outputs: &Vec<Returning>,
130    res_count: QueryResCount,
131) -> ExprType {
132    if !outputs.is_empty() {
133        out.s("returning");
134    }
135    build_returning_values(ctx, path, scope, out, outputs, res_count)
136}