toolu_orm_query/
insert.rs1use toolu_orm_core::dialect::Dialect;
4use toolu_orm_core::query_column::Column;
5use toolu_orm_core::value::Value;
6
7use crate::where_clause::cfg_single_backend;
8
9cfg_single_backend! {
10 use crate::exec_helpers::impl_execute;
11}
12
13enum ConflictMode {
16 None,
17 Replace,
18 Ignore,
19}
20
21pub struct InsertBuilder {
24 table: String,
25 columns: Vec<String>,
26 values: Vec<Value>,
27 conflict_mode: ConflictMode,
28 conflict_cols: Vec<String>,
29}
30
31impl InsertBuilder {
32 pub fn new(table: &str) -> Self {
33 Self {
34 table: table.to_owned(),
35 columns: Vec::new(),
36 values: Vec::new(),
37 conflict_mode: ConflictMode::None,
38 conflict_cols: Vec::new(),
39 }
40 }
41
42 pub fn set<T>(mut self, col: &Column<T>, val: impl Into<Value>) -> Self {
43 self.columns.push(col.name.to_owned());
44 self.values.push(val.into());
45 self
46 }
47
48 pub fn set_null<T>(mut self, col: &Column<T>) -> Self {
49 self.columns.push(col.name.to_owned());
50 self.values.push(Value::Null);
51 self
52 }
53
54 pub fn or_replace(mut self) -> Self {
55 self.conflict_mode = ConflictMode::Replace;
56 self
57 }
58
59 pub fn or_ignore(mut self) -> Self {
60 self.conflict_mode = ConflictMode::Ignore;
61 self
62 }
63
64 pub fn conflict_columns(mut self, cols: &[&str]) -> Self {
69 self.conflict_cols = cols.iter().map(|c| (*c).to_owned()).collect();
70 self
71 }
72
73 pub fn to_sql_for(&self, dialect: Dialect) -> (String, Vec<Value>) {
74 match dialect {
75 Dialect::Sqlite => self.to_sql_sqlite(),
76 Dialect::Postgres => self.to_sql_postgres(),
77 }
78 }
79
80 pub fn to_sql(&self) -> (String, Vec<Value>) {
81 self.to_sql_for(Dialect::CURRENT)
82 }
83
84 fn push_columns_and_values(&self, sql: &mut String, dialect: Dialect) {
85 let col_list: Vec<String> = self.columns.iter().map(|c| format!(r#""{c}""#)).collect();
86 sql.push_str(&format!(" ({}) VALUES (", col_list.join(", ")));
87 let placeholders: Vec<String> = (1..=self.columns.len()).map(|i| dialect.param(i)).collect();
88 sql.push_str(&placeholders.join(", "));
89 sql.push(')');
90 }
91
92 fn to_sql_sqlite(&self) -> (String, Vec<Value>) {
93 let mut sql = String::new();
94
95 let keyword = match self.conflict_mode {
96 ConflictMode::None => "INSERT INTO",
97 ConflictMode::Replace => "INSERT OR REPLACE INTO",
98 ConflictMode::Ignore => "INSERT OR IGNORE INTO",
99 };
100
101 sql.push_str(keyword);
102 sql.push_str(&format!(r#" "{}""#, self.table));
103 self.push_columns_and_values(&mut sql, Dialect::Sqlite);
104
105 (sql, self.values.clone())
106 }
107
108 fn to_sql_postgres(&self) -> (String, Vec<Value>) {
109 let mut sql = String::new();
110
111 sql.push_str(&format!(r#"INSERT INTO "{}""#, self.table));
112 self.push_columns_and_values(&mut sql, Dialect::Postgres);
113
114 match self.conflict_mode {
115 ConflictMode::None => {},
116 ConflictMode::Ignore => {
117 sql.push_str(" ON CONFLICT DO NOTHING");
118 },
119 ConflictMode::Replace => {
120 let conflict_target = if !self.conflict_cols.is_empty() {
121 self.conflict_cols.clone()
122 } else if let Some(first) = self.columns.first() {
123 vec![first.clone()]
124 } else {
125 Vec::new()
126 };
127
128 let conflict_cols_sql: Vec<String> = conflict_target
129 .iter()
130 .map(|c| format!(r#""{c}""#))
131 .collect();
132
133 let update_cols: Vec<String> = self
134 .columns
135 .iter()
136 .filter(|c| !conflict_target.iter().any(|t| t == *c))
137 .map(|c| format!(r#""{c}" = EXCLUDED."{c}""#))
138 .collect();
139
140 sql.push_str(&format!(
141 " ON CONFLICT ({}) DO ",
142 conflict_cols_sql.join(", ")
143 ));
144
145 if update_cols.is_empty() {
146 sql.push_str("NOTHING");
147 } else {
148 sql.push_str(&format!("UPDATE SET {}", update_cols.join(", ")));
149 }
150 },
151 }
152
153 (sql, self.values.clone())
154 }
155}
156
157cfg_single_backend! {
158 impl_execute!(InsertBuilder, "INSERT");
159}