1use std::marker::PhantomData;
2
3use serde_json::Value as JsonValue;
4
5use supabase_client_core::Row;
6
7use crate::backend::QueryBackend;
8use crate::delete::DeleteBuilder;
9use crate::insert::InsertBuilder;
10use crate::select::SelectBuilder;
11use crate::sql::{ParamStore, SqlOperation, SqlParts};
12use crate::table::Table;
13use crate::update::UpdateBuilder;
14use crate::upsert::UpsertBuilder;
15
16pub struct QueryBuilder {
21 backend: QueryBackend,
22 schema: String,
23 table: String,
24}
25
26impl QueryBuilder {
27 pub fn new(backend: QueryBackend, schema: String, table: String) -> Self {
28 Self {
29 backend,
30 schema,
31 table,
32 }
33 }
34
35 pub fn select(self, columns: &str) -> SelectBuilder<Row> {
38 let mut parts = SqlParts::new(SqlOperation::Select, &self.schema, &self.table);
39
40 if columns == "*" || columns.is_empty() {
42 parts.select_columns = None; } else {
44 let quoted = columns
45 .split(',')
46 .map(|c| {
47 let c = c.trim();
48 if c.contains('(') || c.contains('*') || c.contains('"') || c.contains(' ') {
49 c.to_string()
51 } else {
52 format!("\"{}\"", c)
53 }
54 })
55 .collect::<Vec<_>>()
56 .join(", ");
57 parts.select_columns = Some(quoted);
58 }
59
60 SelectBuilder {
61 backend: self.backend,
62 parts,
63 params: ParamStore::new(),
64 _marker: PhantomData,
65 }
66 }
67
68 pub fn insert(self, row: Row) -> InsertBuilder<Row> {
70 let mut parts = SqlParts::new(SqlOperation::Insert, &self.schema, &self.table);
71 let mut params = ParamStore::new();
72
73 let mut entries: Vec<_> = row.into_inner().into_iter().collect();
74 entries.sort_by(|a, b| a.0.cmp(&b.0));
75 for (col, val) in entries {
76 let idx = params.push(json_to_sql_param(val));
77 parts.set_clauses.push((col, idx));
78 }
79
80 InsertBuilder {
81 backend: self.backend,
82 parts,
83 params,
84 _marker: PhantomData,
85 }
86 }
87
88 pub fn insert_many(self, rows: Vec<Row>) -> InsertBuilder<Row> {
90 let mut parts = SqlParts::new(SqlOperation::Insert, &self.schema, &self.table);
91 let mut params = ParamStore::new();
92
93 let column_order: Vec<String> = if let Some(first) = rows.first() {
95 let mut cols: Vec<String> = first.columns().iter().map(|c| c.to_string()).collect();
96 cols.sort();
97 cols
98 } else {
99 Vec::new()
100 };
101
102 for row in rows {
103 let inner = row.into_inner();
104 let mut row_pairs = Vec::new();
105 for col in &column_order {
106 let val = inner.get(col).cloned().unwrap_or(serde_json::Value::Null);
107 let idx = params.push(json_to_sql_param(val));
108 row_pairs.push((col.clone(), idx));
109 }
110 parts.many_rows.push(row_pairs);
111 }
112
113 InsertBuilder {
114 backend: self.backend,
115 parts,
116 params,
117 _marker: PhantomData,
118 }
119 }
120
121 pub fn update(self, row: Row) -> UpdateBuilder<Row> {
123 let mut parts = SqlParts::new(SqlOperation::Update, &self.schema, &self.table);
124 let mut params = ParamStore::new();
125
126 let mut entries: Vec<_> = row.into_inner().into_iter().collect();
127 entries.sort_by(|a, b| a.0.cmp(&b.0));
128 for (col, val) in entries {
129 let idx = params.push(json_to_sql_param(val));
130 parts.set_clauses.push((col, idx));
131 }
132
133 UpdateBuilder {
134 backend: self.backend,
135 parts,
136 params,
137 _marker: PhantomData,
138 }
139 }
140
141 pub fn delete(self) -> DeleteBuilder<Row> {
143 let parts = SqlParts::new(SqlOperation::Delete, &self.schema, &self.table);
144 DeleteBuilder {
145 backend: self.backend,
146 parts,
147 params: ParamStore::new(),
148 _marker: PhantomData,
149 }
150 }
151
152 pub fn upsert(self, row: Row) -> UpsertBuilder<Row> {
154 let mut parts = SqlParts::new(SqlOperation::Upsert, &self.schema, &self.table);
155 let mut params = ParamStore::new();
156
157 let mut entries: Vec<_> = row.into_inner().into_iter().collect();
158 entries.sort_by(|a, b| a.0.cmp(&b.0));
159 for (col, val) in entries {
160 let idx = params.push(json_to_sql_param(val));
161 parts.set_clauses.push((col, idx));
162 }
163
164 UpsertBuilder {
165 backend: self.backend,
166 parts,
167 params,
168 _marker: PhantomData,
169 }
170 }
171
172 pub fn upsert_many(self, rows: Vec<Row>) -> UpsertBuilder<Row> {
174 let mut parts = SqlParts::new(SqlOperation::Upsert, &self.schema, &self.table);
175 let mut params = ParamStore::new();
176
177 let column_order: Vec<String> = if let Some(first) = rows.first() {
178 let mut cols: Vec<String> = first.columns().iter().map(|c| c.to_string()).collect();
179 cols.sort();
180 cols
181 } else {
182 Vec::new()
183 };
184
185 for row in rows {
186 let inner = row.into_inner();
187 let mut row_pairs = Vec::new();
188 for col in &column_order {
189 let val = inner.get(col).cloned().unwrap_or(serde_json::Value::Null);
190 let idx = params.push(json_to_sql_param(val));
191 row_pairs.push((col.clone(), idx));
192 }
193 parts.many_rows.push(row_pairs);
194 }
195
196 UpsertBuilder {
197 backend: self.backend,
198 parts,
199 params,
200 _marker: PhantomData,
201 }
202 }
203}
204
205pub struct TypedQueryBuilder<T: Table> {
207 backend: QueryBackend,
208 schema: String,
209 _marker: PhantomData<T>,
210}
211
212impl<T: Table> TypedQueryBuilder<T> {
213 pub fn new(backend: QueryBackend, schema: String) -> Self {
214 Self {
215 backend,
216 schema,
217 _marker: PhantomData,
218 }
219 }
220
221 pub fn select(self) -> SelectBuilder<T> {
223 let parts = SqlParts::new(SqlOperation::Select, &self.schema, T::table_name());
224 SelectBuilder {
225 backend: self.backend,
226 parts,
227 params: ParamStore::new(),
228 _marker: PhantomData,
229 }
230 }
231
232 pub fn select_columns(self, columns: &str) -> SelectBuilder<T> {
234 let mut parts = SqlParts::new(SqlOperation::Select, &self.schema, T::table_name());
235 if columns != "*" && !columns.is_empty() {
236 let quoted = columns
237 .split(',')
238 .map(|c| {
239 let c = c.trim();
240 if c.contains('(') || c.contains('*') || c.contains('"') || c.contains(' ') {
241 c.to_string()
242 } else {
243 format!("\"{}\"", c)
244 }
245 })
246 .collect::<Vec<_>>()
247 .join(", ");
248 parts.select_columns = Some(quoted);
249 }
250 SelectBuilder {
251 backend: self.backend,
252 parts,
253 params: ParamStore::new(),
254 _marker: PhantomData,
255 }
256 }
257
258 pub fn insert(self, value: &T) -> InsertBuilder<T> {
260 let mut parts = SqlParts::new(SqlOperation::Insert, &self.schema, T::table_name());
261 let mut params = ParamStore::new();
262
263 let columns = T::insertable_columns();
264 let values = value.bind_insert();
265
266 for (col, val) in columns.iter().zip(values.into_iter()) {
267 let idx = params.push(val);
268 parts.set_clauses.push((col.to_string(), idx));
269 }
270
271 InsertBuilder {
272 backend: self.backend,
273 parts,
274 params,
275 _marker: PhantomData,
276 }
277 }
278
279 pub fn update(self, value: &T) -> UpdateBuilder<T> {
281 let mut parts = SqlParts::new(SqlOperation::Update, &self.schema, T::table_name());
282 let mut params = ParamStore::new();
283
284 let pk_cols = T::primary_key_columns();
286 let all_cols = T::column_names();
287 let update_vals = value.bind_update();
288
289 let update_cols: Vec<&&str> = all_cols
290 .iter()
291 .filter(|c| !pk_cols.contains(c))
292 .collect();
293
294 for (col, val) in update_cols.iter().zip(update_vals.into_iter()) {
295 let idx = params.push(val);
296 parts.set_clauses.push((col.to_string(), idx));
297 }
298
299 let pk_vals = value.bind_primary_key();
301 for (col, val) in pk_cols.iter().zip(pk_vals.into_iter()) {
302 let idx = params.push(val);
303 parts.filters.push(crate::sql::FilterCondition::Comparison {
304 column: col.to_string(),
305 operator: crate::sql::FilterOperator::Eq,
306 param_index: idx,
307 });
308 }
309
310 UpdateBuilder {
311 backend: self.backend,
312 parts,
313 params,
314 _marker: PhantomData,
315 }
316 }
317
318 pub fn delete(self) -> DeleteBuilder<T> {
320 let parts = SqlParts::new(SqlOperation::Delete, &self.schema, T::table_name());
321 DeleteBuilder {
322 backend: self.backend,
323 parts,
324 params: ParamStore::new(),
325 _marker: PhantomData,
326 }
327 }
328
329 pub fn upsert(self, value: &T) -> UpsertBuilder<T> {
331 let mut parts = SqlParts::new(SqlOperation::Upsert, &self.schema, T::table_name());
332 let mut params = ParamStore::new();
333
334 let pk_cols = T::primary_key_columns();
335 let insertable_cols = T::insertable_columns();
336
337 let pk_vals = value.bind_primary_key();
339 for (col, val) in pk_cols.iter().zip(pk_vals.into_iter()) {
340 let idx = params.push(val);
341 parts.set_clauses.push((col.to_string(), idx));
342 }
343
344 let insert_vals = value.bind_insert();
346 for (col, val) in insertable_cols.iter().zip(insert_vals.into_iter()) {
347 let idx = params.push(val);
348 parts.set_clauses.push((col.to_string(), idx));
349 }
350
351 parts.conflict_columns = pk_cols.iter().map(|c| c.to_string()).collect();
352
353 UpsertBuilder {
354 backend: self.backend,
355 parts,
356 params,
357 _marker: PhantomData,
358 }
359 }
360}
361
362fn json_to_sql_param(value: JsonValue) -> crate::sql::SqlParam {
364 match value {
365 JsonValue::Null => crate::sql::SqlParam::Null,
366 JsonValue::Bool(b) => crate::sql::SqlParam::Bool(b),
367 JsonValue::Number(n) => {
368 if let Some(i) = n.as_i64() {
369 if i >= i32::MIN as i64 && i <= i32::MAX as i64 {
370 crate::sql::SqlParam::I32(i as i32)
371 } else {
372 crate::sql::SqlParam::I64(i)
373 }
374 } else if let Some(f) = n.as_f64() {
375 crate::sql::SqlParam::F64(f)
376 } else {
377 crate::sql::SqlParam::Text(n.to_string())
378 }
379 }
380 JsonValue::String(s) => {
381 if let Ok(uuid) = uuid::Uuid::parse_str(&s) {
383 crate::sql::SqlParam::Uuid(uuid)
384 } else {
385 crate::sql::SqlParam::Text(s)
386 }
387 }
388 other => crate::sql::SqlParam::Json(other),
389 }
390}