good_ormning/sqlite/query/
insert.rs

1use std::{
2    collections::{
3        HashMap,
4        HashSet,
5    },
6};
7use crate::{
8    sqlite::{
9        schema::{
10            field::Field,
11            table::Table,
12        },
13        QueryResCount,
14    },
15    utils::Tokens,
16};
17use super::{
18    expr::{
19        check_assignable,
20        Expr,
21        ExprType,
22        Binding,
23    },
24    select_body::Returning,
25    utils::{
26        build_returning,
27        build_set,
28        build_with,
29        QueryBody,
30        With,
31    },
32};
33
34pub enum InsertConflict {
35    DoNothing,
36    DoUpdate(Vec<(Field, Expr)>),
37}
38
39pub struct Insert {
40    pub with: Option<With>,
41    pub table: Table,
42    pub values: Vec<(Field, Expr)>,
43    pub on_conflict: Option<InsertConflict>,
44    pub returning: Vec<Returning>,
45}
46
47impl QueryBody for Insert {
48    fn build(
49        &self,
50        ctx: &mut super::utils::SqliteQueryCtx,
51        path: &rpds::Vector<String>,
52        res_count: QueryResCount,
53    ) -> (ExprType, Tokens) {
54        let mut out = Tokens::new();
55
56        // Prep
57        if let Some(w) = &self.with {
58            out.s(&build_with(ctx, path, w).to_string());
59        }
60        let mut check_inserting_fields = HashSet::new();
61        for p in &self.values {
62            if p.0.type_.type_.opt {
63                continue;
64            }
65            if !check_inserting_fields.insert(p.0.clone()) {
66                ctx.errs.err(path, format!("Duplicate field {} in insert", p.0));
67            }
68        }
69        let mut scope = HashMap::new();
70        for field in match ctx.tables.get(&self.table) {
71            Some(t) => t,
72            None => {
73                ctx.errs.err(path, format!("Unknown table {} for insert", self.table));
74                return (ExprType(vec![]), Tokens::new());
75            },
76        } {
77            scope.insert(Binding::field(field), field.type_.type_.clone());
78            if !field.type_.type_.opt && field.schema_id.0 != "rowid" && !check_inserting_fields.remove(field) {
79                ctx.errs.err(path, format!("{} is a non-optional field but is missing in insert", field));
80            }
81        }
82        drop(check_inserting_fields);
83
84        // Build query
85        out.s("insert into").id(&self.table.id).s("(");
86        for (i, (field, _)) in self.values.iter().enumerate() {
87            if i > 0 {
88                out.s(",");
89            }
90            out.id(&field.id);
91        }
92        out.s(") values (");
93        for (i, (field, val)) in self.values.iter().enumerate() {
94            if i > 0 {
95                out.s(",");
96            }
97            let field = match ctx.tables.get(&field.table).and_then(|t| t.get(&field)) {
98                Some(t) => t,
99                None => {
100                    ctx.errs.err(path, format!("Insert destination value field {} is not known", field));
101                    continue;
102                },
103            }.clone();
104            let path = path.push_back(format!("Insert value {} ({})", i, field));
105            let res = val.build(ctx, &path, &scope);
106            check_assignable(&mut ctx.errs, &path, &field.type_.type_, &res.0);
107            out.s(&res.1.to_string());
108        }
109        out.s(")");
110        if let Some(c) = &self.on_conflict {
111            out.s("on conflict do");
112            match c {
113                InsertConflict::DoNothing => {
114                    out.s("nothing");
115                },
116                InsertConflict::DoUpdate(values) => {
117                    out.s("update");
118                    build_set(ctx, path, &scope, &mut out, values);
119                },
120            }
121        }
122        match (&res_count, &self.on_conflict) {
123            (QueryResCount::MaybeOne, Some(InsertConflict::DoUpdate(_))) => {
124                ctx.errs.err(path, format!("Insert with [on conflict update] will always return a row"));
125            },
126            (QueryResCount::One, Some(InsertConflict::DoNothing)) => {
127                ctx.errs.err(path, format!("Insert with [on conflict do nothing] may not return a row"));
128            },
129            (QueryResCount::Many, _) => {
130                ctx.errs.err(path, format!("Insert can at most return one row, but res count is many"));
131            },
132            (QueryResCount::None, _) | (QueryResCount::One, None) | (QueryResCount::MaybeOne, None) => {
133                // handled elsewhere, nop
134            },
135            (QueryResCount::One, Some(InsertConflict::DoUpdate(_))) |
136            (QueryResCount::MaybeOne, Some(InsertConflict::DoNothing)) => {
137                // ok
138            },
139        }
140        let out_type = build_returning(ctx, path, &scope, &mut out, &self.returning, res_count);
141        (out_type, out)
142    }
143}