1use crate::{Column, SqlClause, wrap, Clause};
2use tokio_postgres::types::ToSql;
3
4pub struct Insert;
5pub struct End;
6pub struct OnConflictHandle<'q>(Clause<'q, End>);
7
8pub fn insert_into<'q>(
9 table: &str,
10 map: &[(&str, &'q (dyn ToSql + Sync + 'q))],
11) -> Clause<'q, Insert> {
12 let mut sql = String::from("INSERT INTO ");
13 let mut value_list = Vec::with_capacity(map.len());
14 sql.push_str(&wrap(table));
15 sql.push_str(" (");
16 let mut not_first = false;
17 for (key, value) in map {
18 if not_first {
19 sql.push(',');
20 }
21 sql.push_str(format!("\"{}\"", key).as_str());
22 not_first = true;
23 value_list.push(*value);
24 }
25 sql.push_str(") VALUES (");
26 let mut not_first = false;
27 for _ in 0..value_list.len() {
28 if not_first {
29 sql.push(',');
30 }
31 not_first = true;
32 sql.push('$');
33 }
34 sql.push(')');
35 Clause::new(sql, value_list)
36}
37impl<'q> Clause<'q, Insert> {
38 pub fn on_conflict_do_nothing(self) -> Clause<'q, ()> {
39 let (mut sql, params) = self.unwrap();
40 sql.push_str(" ON CONFLICT DO NOTHING");
41 Clause::new(sql, params)
42 }
43 pub fn on_conflict(self, cols: Clause<'q, Column>) -> OnConflictHandle<'q> {
44 let (mut sql, params) = self.unwrap();
45 let (cols_sql, _) = cols.unwrap();
46 sql.push_str(" ON CONFLICT (");
47 sql.push_str(&cols_sql);
48 sql.push(')');
49 OnConflictHandle(Clause::new(sql, params))
50 }
51}
52
53impl<'q> OnConflictHandle<'q> {
54 pub fn do_nothing(self) -> Clause<'q, ()> {
55 let (mut sql, params) = self.0.unwrap();
56 sql.push_str(" DO NOTHING");
57 Clause::new(sql, params)
58 }
59
60 pub fn do_update_excluded(self, columns: &[&str]) -> Clause<'q, ()> {
67 let (mut sql, params) = self.0.unwrap();
68 sql.push_str(" DO UPDATE SET ");
69 let mut not_first = false;
70 for item in columns {
71 if not_first {
72 sql.push(',');
73 }
74 not_first = true;
75 sql.push_str(&format!("\"{}\" = excluded.\"{}\"", item, item));
76 }
77 Clause::new(sql, params)
78 }
79}
80
81#[cfg(test)]
82mod tests {
83 use super::*;
84 use crate::cols;
85
86 #[test]
87 fn insert_test() {
88 let title = String::from("title");
89 let content = String::from("content");
90 let image_id = 123;
91 let (sql, params) = insert_into(
92 "Post",
93 &[
94 ("Title", &title),
95 ("Content", &content),
96 ("ImageId", &image_id),
97 ],
98 )
99 .on_conflict(cols(&["Title"]))
100 .do_update_excluded(&["Content", "ImageId"])
101 .build();
102 println!("{:}", sql);
103 assert_eq!(
104 sql,
105 r##"INSERT INTO "Post" ("Title","Content","ImageId") VALUES ($1,$2,$3) ON CONFLICT ("Title") DO UPDATE SET "Content" = excluded."Content","ImageId" = excluded."ImageId""##
106 );
107 println!("params: {:?}", params);
108 }
109}