supabase_client_query/
upsert.rs1use std::marker::PhantomData;
2
3use serde::de::DeserializeOwned;
4
5use supabase_client_core::SupabaseResponse;
6
7use crate::backend::QueryBackend;
8use crate::modifier::Modifiable;
9use crate::sql::{ParamStore, SqlParts};
10
11pub struct UpsertBuilder<T> {
14 pub(crate) backend: QueryBackend,
15 pub(crate) parts: SqlParts,
16 pub(crate) params: ParamStore,
17 pub(crate) _marker: PhantomData<T>,
18}
19
20impl<T> Modifiable for UpsertBuilder<T> {
21 fn parts_mut(&mut self) -> &mut SqlParts {
22 &mut self.parts
23 }
24}
25
26impl<T> UpsertBuilder<T> {
27 pub fn on_conflict(mut self, columns: &[&str]) -> Self {
29 self.parts.conflict_columns = columns.iter().map(|c| c.to_string()).collect();
30 self
31 }
32
33 pub fn on_conflict_constraint(mut self, constraint: &str) -> Self {
35 self.parts.conflict_constraint = Some(constraint.to_string());
36 self
37 }
38
39 pub fn ignore_duplicates(mut self) -> Self {
43 self.parts.ignore_duplicates = true;
44 self
45 }
46
47 pub fn schema(mut self, schema: &str) -> Self {
51 self.parts.schema_override = Some(schema.to_string());
52 self
53 }
54
55 pub fn select(mut self) -> Self {
57 self.parts.returning = Some("*".to_string());
58 self
59 }
60
61 pub fn select_columns(mut self, columns: &str) -> Self {
63 if columns == "*" || columns.is_empty() {
64 self.parts.returning = Some("*".to_string());
65 } else {
66 let quoted = columns
67 .split(',')
68 .map(|c| {
69 let c = c.trim();
70 if c.contains('(') || c.contains('*') || c.contains('"') {
71 c.to_string()
72 } else {
73 format!("\"{}\"", c)
74 }
75 })
76 .collect::<Vec<_>>()
77 .join(", ");
78 self.parts.returning = Some(quoted);
79 }
80 self
81 }
82}
83
84#[cfg(not(feature = "direct-sql"))]
86impl<T> UpsertBuilder<T>
87where
88 T: DeserializeOwned + Send,
89{
90 pub async fn execute(self) -> SupabaseResponse<T> {
92 let QueryBackend::Rest { ref http, ref base_url, ref api_key, ref schema } = self.backend;
93 let (url, headers, body) = match crate::postgrest::build_postgrest_upsert(
94 base_url, &self.parts, &self.params,
95 ) {
96 Ok(r) => r,
97 Err(e) => return SupabaseResponse::error(
98 supabase_client_core::SupabaseError::QueryBuilder(e),
99 ),
100 };
101 crate::postgrest_execute::execute_rest(
102 http, reqwest::Method::POST, &url, headers, Some(body), api_key, schema, &self.parts,
103 ).await
104 }
105}
106
107#[cfg(feature = "direct-sql")]
109impl<T> UpsertBuilder<T>
110where
111 T: DeserializeOwned + Send + Unpin + for<'r> sqlx::FromRow<'r, sqlx::postgres::PgRow>,
112{
113 pub async fn execute(self) -> SupabaseResponse<T> {
115 match &self.backend {
116 QueryBackend::Rest { http, base_url, api_key, schema } => {
117 let (url, headers, body) = match crate::postgrest::build_postgrest_upsert(
118 base_url, &self.parts, &self.params,
119 ) {
120 Ok(r) => r,
121 Err(e) => return SupabaseResponse::error(
122 supabase_client_core::SupabaseError::QueryBuilder(e),
123 ),
124 };
125 crate::postgrest_execute::execute_rest(
126 http, reqwest::Method::POST, &url, headers, Some(body), api_key, schema, &self.parts,
127 ).await
128 }
129 QueryBackend::DirectSql { pool } => {
130 crate::execute::execute_typed::<T>(pool, &self.parts, &self.params).await
131 }
132 }
133 }
134}