Skip to main content

ngb_sqlbuilder/
insert.rs

1use crate::{Clause, Column, SqlClause, wrap};
2use tokio_postgres::types::ToSql;
3
4pub struct Insert;
5pub struct End;
6pub struct OnConflictHandle<'q>(Clause<'q, End>);
7
8
9/// Begin `INSERT` statement
10///
11/// # Arguments
12///
13/// * `table`: table name
14/// * `map`: values
15///
16/// returns: `Clause<Insert>`
17///
18/// # Examples
19///
20/// ```
21/// insert_into("Post", &[("Id",&id),("Title", &title),("Content", &content)]).on_conflict(col("Id")).do_update_excluded(&["Title","Content"]);
22/// // SQL: INSERT INTO "Post" ("Id","Title","Content") VALUES ($1,$2,$3) ON CONFLICT ("Id) DO UPDATE SET "Title" = excluded."Title", "Content" = excluded."Content"
23/// ```
24pub fn insert_into<'q>(
25    table: &str,
26    map: &[(&str, &'q (dyn ToSql + Sync + 'q))],
27) -> Clause<'q, Insert> {
28    let mut sql = String::from("INSERT INTO ");
29    let mut value_list = Vec::with_capacity(map.len());
30    sql.push_str(&wrap(table));
31    sql.push_str(" (");
32    let mut not_first = false;
33    for (key, value) in map {
34        if not_first {
35            sql.push(',');
36        }
37        sql.push_str(format!("\"{}\"", key).as_str());
38        not_first = true;
39        value_list.push(*value);
40    }
41    sql.push_str(") VALUES (");
42    let mut not_first = false;
43    for _ in 0..value_list.len() {
44        if not_first {
45            sql.push(',');
46        }
47        not_first = true;
48        sql.push('$');
49    }
50    sql.push(')');
51    Clause::new(sql, value_list)
52}
53impl<'q> Clause<'q, Insert> {
54    pub fn on_conflict_do_nothing(self) -> Clause<'q, ()> {
55        let (mut sql, params) = self.unwrap();
56        sql.push_str(" ON CONFLICT DO NOTHING");
57        Clause::new(sql, params)
58    }
59    pub fn on_conflict(self, cols: Clause<'q, Column>) -> OnConflictHandle<'q> {
60        let (mut sql, params) = self.unwrap();
61        let (cols_sql, _) = cols.unwrap();
62        sql.push_str(" ON CONFLICT (");
63        sql.push_str(&cols_sql);
64        sql.push(')');
65        OnConflictHandle(Clause::new(sql, params))
66    }
67}
68
69impl<'q> OnConflictHandle<'q> {
70    pub fn do_nothing(self) -> Clause<'q, ()> {
71        let (mut sql, params) = self.0.unwrap();
72        sql.push_str(" DO NOTHING");
73        Clause::new(sql, params)
74    }
75
76    // pub fn do_update(self, column: Clause<'q, Column>) -> Clause<'q, Final> {
77    //     let (mut sql, params) = self.0.build();
78    //     sql.push_str(" do update ");
79    //     Clause::new(sql, params)
80    // }
81
82    pub fn do_update_excluded(self, columns: &[&str]) -> Clause<'q, ()> {
83        let (mut sql, params) = self.0.unwrap();
84        sql.push_str(" DO UPDATE SET ");
85        let mut not_first = false;
86        for item in columns {
87            if not_first {
88                sql.push(',');
89            }
90            not_first = true;
91            sql.push_str(&format!("\"{}\" = excluded.\"{}\"", item, item));
92        }
93        Clause::new(sql, params)
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100    use crate::{cols, Build};
101
102    #[test]
103    fn insert_test1() {
104        let title = String::from("title");
105        let content = String::from("content");
106        let image_id = 123;
107        let (sql, params) = insert_into(
108            "Post",
109            &[
110                ("Title", &title),
111                ("Content", &content),
112                ("ImageId", &image_id),
113            ],
114        )
115        .on_conflict(cols(&["Title"]))
116        .do_update_excluded(&["Content", "ImageId"])
117        .build();
118        println!("{:}", sql);
119        assert_eq!(
120            sql,
121            r##"INSERT INTO "Post" ("Title","Content","ImageId") VALUES ($1,$2,$3) ON CONFLICT ("Title") DO UPDATE SET "Content" = excluded."Content","ImageId" = excluded."ImageId""##
122        );
123        println!("params: {:?}", params);
124    }
125    #[test]
126    fn insert_test2() {
127        let title = String::from("title");
128        let content = String::from("content");
129        let image_id = 123;
130        let (sql, params) = insert_into(
131            "Post",
132            &[
133                ("Title", &title),
134                ("Content", &content),
135                ("ImageId", &image_id),
136            ],
137        )
138        .on_conflict(cols(&["Title"]))
139        .do_update_excluded(&["Content", "ImageId"])
140        .combine_raw(r##"returning "Id""##, &[])
141        .build();
142        println!("{:}", sql);
143        println!("params: {:?}", params);
144    }
145}