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}
391
392#[cfg(test)]
393mod tests {
394 use super::*;
395 use crate::backend::QueryBackend;
396 use crate::sql::*;
397 use serde_json::json;
398 use std::sync::Arc;
399 use supabase_client_core::Row;
400
401 fn make_backend() -> QueryBackend {
402 QueryBackend::Rest {
403 http: reqwest::Client::new(),
404 base_url: Arc::from("http://localhost"),
405 api_key: Arc::from("key"),
406 schema: "public".to_string(),
407 }
408 }
409
410 fn make_query_builder() -> QueryBuilder {
411 QueryBuilder::new(make_backend(), "public".to_string(), "test".to_string())
412 }
413
414 #[test]
417 fn test_select_star() {
418 let builder = make_query_builder().select("*");
419 assert!(builder.parts.select_columns.is_none());
420 }
421
422 #[test]
423 fn test_select_empty() {
424 let builder = make_query_builder().select("");
425 assert!(builder.parts.select_columns.is_none());
426 }
427
428 #[test]
429 fn test_select_named_columns() {
430 let builder = make_query_builder().select("name, age");
431 let cols = builder.parts.select_columns.unwrap();
432 assert_eq!(cols, "\"name\", \"age\"");
433 }
434
435 #[test]
436 fn test_select_count_expression_passes_through() {
437 let builder = make_query_builder().select("count(*)");
438 let cols = builder.parts.select_columns.unwrap();
439 assert_eq!(cols, "count(*)");
440 }
441
442 #[test]
443 fn test_select_quoted_column_passes_through() {
444 let builder = make_query_builder().select("\"my_col\"");
445 let cols = builder.parts.select_columns.unwrap();
446 assert_eq!(cols, "\"my_col\"");
448 }
449
450 #[test]
453 fn test_insert_sets_up_clauses() {
454 let mut row = Row::new();
455 row.set("name", json!("Alice"));
456 row.set("age", json!(30));
457
458 let builder = make_query_builder().insert(row);
459 assert_eq!(builder.parts.operation, SqlOperation::Insert);
460 assert_eq!(builder.parts.set_clauses.len(), 2);
461 assert_eq!(builder.parts.set_clauses[0].0, "age");
463 assert_eq!(builder.parts.set_clauses[1].0, "name");
464 }
465
466 #[test]
469 fn test_insert_many_sets_up_many_rows() {
470 let mut row1 = Row::new();
471 row1.set("name", json!("Alice"));
472 row1.set("age", json!(30));
473
474 let mut row2 = Row::new();
475 row2.set("name", json!("Bob"));
476 row2.set("age", json!(25));
477
478 let builder = make_query_builder().insert_many(vec![row1, row2]);
479 assert_eq!(builder.parts.operation, SqlOperation::Insert);
480 assert_eq!(builder.parts.many_rows.len(), 2);
481 assert_eq!(builder.parts.many_rows[0].len(), 2);
483 assert_eq!(builder.parts.many_rows[1].len(), 2);
484 }
485
486 #[test]
487 fn test_insert_many_empty() {
488 let builder = make_query_builder().insert_many(vec![]);
489 assert!(builder.parts.many_rows.is_empty());
490 }
491
492 #[test]
495 fn test_delete_creates_builder() {
496 let builder = make_query_builder().delete();
497 assert_eq!(builder.parts.operation, SqlOperation::Delete);
498 assert!(builder.params.is_empty());
499 assert!(builder.parts.filters.is_empty());
500 }
501
502 #[test]
505 fn test_update_sets_up_clauses() {
506 let mut row = Row::new();
507 row.set("name", json!("Updated"));
508
509 let builder = make_query_builder().update(row);
510 assert_eq!(builder.parts.operation, SqlOperation::Update);
511 assert_eq!(builder.parts.set_clauses.len(), 1);
512 assert_eq!(builder.parts.set_clauses[0].0, "name");
513 }
514
515 #[test]
518 fn test_upsert_sets_up_clauses() {
519 let mut row = Row::new();
520 row.set("id", json!(1));
521 row.set("name", json!("Alice"));
522
523 let builder = make_query_builder().upsert(row);
524 assert_eq!(builder.parts.operation, SqlOperation::Upsert);
525 assert_eq!(builder.parts.set_clauses.len(), 2);
526 }
527
528 #[test]
531 fn test_upsert_many_sets_up_many_rows() {
532 let mut row1 = Row::new();
533 row1.set("id", json!(1));
534 row1.set("name", json!("Alice"));
535
536 let mut row2 = Row::new();
537 row2.set("id", json!(2));
538 row2.set("name", json!("Bob"));
539
540 let builder = make_query_builder().upsert_many(vec![row1, row2]);
541 assert_eq!(builder.parts.operation, SqlOperation::Upsert);
542 assert_eq!(builder.parts.many_rows.len(), 2);
543 }
544
545 #[test]
548 fn test_json_to_sql_param_null() {
549 let param = json_to_sql_param(json!(null));
550 assert!(matches!(param, SqlParam::Null));
551 }
552
553 #[test]
554 fn test_json_to_sql_param_bool() {
555 let param = json_to_sql_param(json!(true));
556 assert!(matches!(param, SqlParam::Bool(true)));
557 let param = json_to_sql_param(json!(false));
558 assert!(matches!(param, SqlParam::Bool(false)));
559 }
560
561 #[test]
562 fn test_json_to_sql_param_int_small() {
563 let param = json_to_sql_param(json!(42));
565 assert!(matches!(param, SqlParam::I32(42)));
566 }
567
568 #[test]
569 fn test_json_to_sql_param_int_large() {
570 let big = i64::MAX;
572 let param = json_to_sql_param(json!(big));
573 assert!(matches!(param, SqlParam::I64(_)));
574 }
575
576 #[test]
577 fn test_json_to_sql_param_float() {
578 let param = json_to_sql_param(json!(3.14));
579 match param {
580 SqlParam::F64(v) => assert!((v - 3.14).abs() < 0.001),
581 _ => panic!("expected F64"),
582 }
583 }
584
585 #[test]
586 fn test_json_to_sql_param_string() {
587 let param = json_to_sql_param(json!("hello world"));
588 match param {
589 SqlParam::Text(s) => assert_eq!(s, "hello world"),
590 _ => panic!("expected Text"),
591 }
592 }
593
594 #[test]
595 fn test_json_to_sql_param_uuid_string() {
596 let param = json_to_sql_param(json!("550e8400-e29b-41d4-a716-446655440000"));
597 match param {
598 SqlParam::Uuid(u) => assert_eq!(u.to_string(), "550e8400-e29b-41d4-a716-446655440000"),
599 _ => panic!("expected Uuid, got {:?}", param),
600 }
601 }
602
603 #[test]
604 fn test_json_to_sql_param_json_object() {
605 let param = json_to_sql_param(json!({"key": "value"}));
606 assert!(matches!(param, SqlParam::Json(_)));
607 }
608
609 #[test]
610 fn test_json_to_sql_param_json_array() {
611 let param = json_to_sql_param(json!([1, 2, 3]));
612 assert!(matches!(param, SqlParam::Json(_)));
613 }
614
615 #[derive(Debug, Clone, serde::Deserialize)]
619 struct TestTable {
620 id: i32,
621 name: String,
622 }
623
624 impl crate::table::Table for TestTable {
625 fn table_name() -> &'static str {
626 "test_table"
627 }
628
629 fn primary_key_columns() -> &'static [&'static str] {
630 &["id"]
631 }
632
633 fn column_names() -> &'static [&'static str] {
634 &["id", "name"]
635 }
636
637 fn insertable_columns() -> &'static [&'static str] {
638 &["name"]
639 }
640
641 fn field_to_column(field: &str) -> Option<&'static str> {
642 match field {
643 "id" => Some("id"),
644 "name" => Some("name"),
645 _ => None,
646 }
647 }
648
649 fn column_to_field(column: &str) -> Option<&'static str> {
650 match column {
651 "id" => Some("id"),
652 "name" => Some("name"),
653 _ => None,
654 }
655 }
656
657 fn bind_insert(&self) -> Vec<SqlParam> {
658 vec![SqlParam::Text(self.name.clone())]
659 }
660
661 fn bind_update(&self) -> Vec<SqlParam> {
662 vec![SqlParam::Text(self.name.clone())]
663 }
664
665 fn bind_primary_key(&self) -> Vec<SqlParam> {
666 vec![SqlParam::I32(self.id)]
667 }
668 }
669
670 #[test]
671 fn test_typed_query_builder_select() {
672 let typed_builder: TypedQueryBuilder<TestTable> =
673 TypedQueryBuilder::new(make_backend(), "public".to_string());
674 let select_builder = typed_builder.select();
675 assert_eq!(select_builder.parts.table, "test_table");
676 assert!(select_builder.parts.select_columns.is_none());
677 }
678
679 #[test]
680 fn test_typed_query_builder_select_columns() {
681 let typed_builder: TypedQueryBuilder<TestTable> =
682 TypedQueryBuilder::new(make_backend(), "public".to_string());
683 let select_builder = typed_builder.select_columns("id, name");
684 let cols = select_builder.parts.select_columns.unwrap();
685 assert_eq!(cols, "\"id\", \"name\"");
686 }
687
688 #[test]
689 fn test_typed_query_builder_delete() {
690 let typed_builder: TypedQueryBuilder<TestTable> =
691 TypedQueryBuilder::new(make_backend(), "public".to_string());
692 let delete_builder = typed_builder.delete();
693 assert_eq!(delete_builder.parts.operation, SqlOperation::Delete);
694 assert_eq!(delete_builder.parts.table, "test_table");
695 }
696
697 #[test]
698 fn test_typed_query_builder_insert() {
699 let typed_builder: TypedQueryBuilder<TestTable> =
700 TypedQueryBuilder::new(make_backend(), "public".to_string());
701 let value = TestTable {
702 id: 1,
703 name: "Alice".to_string(),
704 };
705 let insert_builder = typed_builder.insert(&value);
706 assert_eq!(insert_builder.parts.operation, SqlOperation::Insert);
707 assert_eq!(insert_builder.parts.set_clauses.len(), 1);
708 assert_eq!(insert_builder.parts.set_clauses[0].0, "name");
709 }
710
711 #[test]
712 fn test_typed_query_builder_update() {
713 let typed_builder: TypedQueryBuilder<TestTable> =
714 TypedQueryBuilder::new(make_backend(), "public".to_string());
715 let value = TestTable {
716 id: 1,
717 name: "Updated".to_string(),
718 };
719 let update_builder = typed_builder.update(&value);
720 assert_eq!(update_builder.parts.operation, SqlOperation::Update);
721 assert_eq!(update_builder.parts.set_clauses.len(), 1);
723 assert_eq!(update_builder.parts.filters.len(), 1);
725 }
726
727 #[test]
728 fn test_typed_query_builder_upsert() {
729 let typed_builder: TypedQueryBuilder<TestTable> =
730 TypedQueryBuilder::new(make_backend(), "public".to_string());
731 let value = TestTable {
732 id: 1,
733 name: "Alice".to_string(),
734 };
735 let upsert_builder = typed_builder.upsert(&value);
736 assert_eq!(upsert_builder.parts.operation, SqlOperation::Upsert);
737 assert_eq!(upsert_builder.parts.set_clauses.len(), 2);
739 assert_eq!(upsert_builder.parts.conflict_columns, vec!["id"]);
741 }
742}