1use toolu_orm_core::dialect::Dialect;
16use toolu_orm_core::expr::{Expr, JoinCondition, OrderBy};
17use toolu_orm_core::query_column::ColumnRef;
18use toolu_orm_core::value::Value;
19
20use crate::where_clause::{append_where_for, impl_filter};
21
22pub(super) struct JoinClause {
25 pub join_type: &'static str,
26 pub table: String,
27 pub condition: JoinCondition,
28}
29
30pub struct SelectBuilder {
33 pub(super) table: String,
34 pub(super) columns: Vec<String>,
35 pub(super) filters: Vec<Expr>,
36 pub(super) joins: Vec<JoinClause>,
37 pub(super) order_bys: Vec<OrderBy>,
38 pub(super) limit_val: Option<i64>,
39 pub(super) offset_val: Option<i64>,
40 pub(super) column_exprs: Vec<(String, String)>,
41 pub(super) is_raw: bool,
42}
43
44impl_filter!(SelectBuilder);
45
46impl SelectBuilder {
47 pub fn new(table: &str) -> Self {
48 Self {
49 table: table.to_owned(),
50 columns: Vec::new(),
51 filters: Vec::new(),
52 joins: Vec::new(),
53 order_bys: Vec::new(),
54 limit_val: None,
55 offset_val: None,
56 column_exprs: Vec::new(),
57 is_raw: false,
58 }
59 }
60
61 pub fn raw() -> Self {
62 Self {
63 table: String::new(),
64 columns: Vec::new(),
65 filters: Vec::new(),
66 joins: Vec::new(),
67 order_bys: Vec::new(),
68 limit_val: None,
69 offset_val: None,
70 column_exprs: Vec::new(),
71 is_raw: true,
72 }
73 }
74
75 pub fn columns_raw(mut self, cols: &[&str]) -> Self {
76 self.columns = cols.iter().map(|c| (*c).to_owned()).collect();
77 self
78 }
79
80 pub fn columns_typed(mut self, cols: &[&dyn ColumnRef]) -> Self {
81 self.columns = cols.iter().map(|c| c.name().to_owned()).collect();
82 self
83 }
84
85 pub fn join(mut self, table: &str, on: JoinCondition) -> Self {
86 self.joins.push(JoinClause {
87 join_type: "INNER JOIN",
88 table: table.to_owned(),
89 condition: on,
90 });
91 self
92 }
93
94 pub fn left_join(mut self, table: &str, on: JoinCondition) -> Self {
95 self.joins.push(JoinClause {
96 join_type: "LEFT JOIN",
97 table: table.to_owned(),
98 condition: on,
99 });
100 self
101 }
102
103 pub fn order_by(mut self, ob: OrderBy) -> Self {
104 self.order_bys.push(ob);
105 self
106 }
107
108 pub fn limit(mut self, n: i64) -> Self {
109 self.limit_val = Some(n);
110 self
111 }
112
113 pub fn offset(mut self, n: i64) -> Self {
114 self.offset_val = Some(n);
115 self
116 }
117
118 pub fn column_expr(mut self, expr: &str, alias: &str) -> Self {
119 self.column_exprs.push((expr.to_owned(), alias.to_owned()));
120 self
121 }
122
123 pub fn to_sql_for(&self, dialect: Dialect) -> (String, Vec<Value>) {
124 let mut sql = String::new();
125 let mut params: Vec<Value> = Vec::new();
126
127 sql.push_str("SELECT ");
128 sql.push_str(&self.build_select_list());
129
130 if !self.is_raw {
131 sql.push_str(&format!(r#" FROM "{}""#, self.table));
132 self.append_joins(&mut sql);
133 }
134
135 append_where_for(&self.filters, &mut sql, &mut params, dialect);
136 self.append_order_by(&mut sql);
137 self.append_limit_offset_for(&mut sql, &mut params, dialect);
138
139 (sql, params)
140 }
141
142 pub fn to_sql(&self) -> (String, Vec<Value>) {
143 self.to_sql_for(Dialect::CURRENT)
144 }
145
146 pub fn to_count_sql_for(&self, dialect: Dialect) -> (String, Vec<Value>) {
147 let mut sql = String::new();
148 let mut params: Vec<Value> = Vec::new();
149
150 sql.push_str(&format!(r#"SELECT COUNT(*) FROM "{}""#, self.table));
151 self.append_joins(&mut sql);
152 append_where_for(&self.filters, &mut sql, &mut params, dialect);
153
154 (sql, params)
155 }
156
157 pub fn to_count_sql(&self) -> (String, Vec<Value>) {
158 self.to_count_sql_for(Dialect::CURRENT)
159 }
160
161 pub fn to_exists_sql_for(&self, dialect: Dialect) -> (String, Vec<Value>) {
162 let mut inner = String::new();
163 let mut params: Vec<Value> = Vec::new();
164
165 inner.push_str(&format!(r#"SELECT 1 FROM "{}""#, self.table));
166 self.append_joins(&mut inner);
167 append_where_for(&self.filters, &mut inner, &mut params, dialect);
168
169 (format!("SELECT EXISTS({inner})"), params)
170 }
171
172 pub fn to_exists_sql(&self) -> (String, Vec<Value>) {
173 self.to_exists_sql_for(Dialect::CURRENT)
174 }
175
176 pub fn table_name(&self) -> &str {
177 &self.table
178 }
179
180 fn build_select_list(&self) -> String {
183 if self.is_raw && !self.column_exprs.is_empty() {
184 self
185 .column_exprs
186 .iter()
187 .map(|(expr, alias)| format!(r#"{expr} AS "{alias}""#))
188 .collect::<Vec<_>>()
189 .join(", ")
190 } else {
191 self
192 .columns
193 .iter()
194 .map(|c| format!(r#""{c}""#))
195 .collect::<Vec<_>>()
196 .join(", ")
197 }
198 }
199
200 fn append_joins(&self, sql: &mut String) {
201 for join in &self.joins {
202 sql.push_str(&format!(
203 r#" {} "{}" ON {}"#,
204 join.join_type,
205 join.table,
206 join.condition.to_sql()
207 ));
208 }
209 }
210
211 fn append_order_by(&self, sql: &mut String) {
212 if self.order_bys.is_empty() {
213 return;
214 }
215 let parts: Vec<String> = self.order_bys.iter().map(|ob| ob.to_sql()).collect();
216 sql.push_str(&format!(" ORDER BY {}", parts.join(", ")));
217 }
218
219 fn append_limit_offset_for(&self, sql: &mut String, params: &mut Vec<Value>, dialect: Dialect) {
220 if let Some(limit) = self.limit_val {
221 let idx = params.len() + 1;
222 params.push(Value::Integer(limit));
223 sql.push_str(&format!(" LIMIT {}", dialect.param(idx)));
224 }
225 if let Some(offset) = self.offset_val {
226 let idx = params.len() + 1;
227 params.push(Value::Integer(offset));
228 sql.push_str(&format!(" OFFSET {}", dialect.param(idx)));
229 }
230 }
231}