good_ormning/sqlite/query/
insert.rs1use 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 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 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 },
135 (QueryResCount::One, Some(InsertConflict::DoUpdate(_))) |
136 (QueryResCount::MaybeOne, Some(InsertConflict::DoNothing)) => {
137 },
139 }
140 let out_type = build_returning(ctx, path, &scope, &mut out, &self.returning, res_count);
141 (out_type, out)
142 }
143}