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}