Skip to main content

supabase_client_query/
update.rs

1use std::marker::PhantomData;
2
3use serde::de::DeserializeOwned;
4
5use supabase_client_core::SupabaseResponse;
6
7use crate::backend::QueryBackend;
8use crate::filter::Filterable;
9use crate::modifier::Modifiable;
10use crate::sql::{FilterCondition, ParamStore, SqlParts};
11
12/// Builder for UPDATE queries. Implements Filterable and Modifiable.
13/// Call `.select()` to add RETURNING clause.
14pub struct UpdateBuilder<T> {
15    pub(crate) backend: QueryBackend,
16    pub(crate) parts: SqlParts,
17    pub(crate) params: ParamStore,
18    pub(crate) _marker: PhantomData<T>,
19}
20
21impl<T> Filterable for UpdateBuilder<T> {
22    fn filters_mut(&mut self) -> &mut Vec<FilterCondition> {
23        &mut self.parts.filters
24    }
25    fn params_mut(&mut self) -> &mut ParamStore {
26        &mut self.params
27    }
28}
29
30impl<T> Modifiable for UpdateBuilder<T> {
31    fn parts_mut(&mut self) -> &mut SqlParts {
32        &mut self.parts
33    }
34}
35
36impl<T> UpdateBuilder<T> {
37    /// Override the schema for this query.
38    ///
39    /// Generates `"schema"."table"` instead of the default schema.
40    pub fn schema(mut self, schema: &str) -> Self {
41        self.parts.schema_override = Some(schema.to_string());
42        self
43    }
44
45    /// Add RETURNING * clause.
46    pub fn select(mut self) -> Self {
47        self.parts.returning = Some("*".to_string());
48        self
49    }
50
51    /// Add RETURNING with specific columns.
52    pub fn select_columns(mut self, columns: &str) -> Self {
53        if columns == "*" || columns.is_empty() {
54            self.parts.returning = Some("*".to_string());
55        } else {
56            let quoted = columns
57                .split(',')
58                .map(|c| {
59                    let c = c.trim();
60                    if c.contains('(') || c.contains('*') || c.contains('"') {
61                        c.to_string()
62                    } else {
63                        format!("\"{}\"", c)
64                    }
65                })
66                .collect::<Vec<_>>()
67                .join(", ");
68            self.parts.returning = Some(quoted);
69        }
70        self
71    }
72}
73
74// REST-only mode: only DeserializeOwned + Send needed
75#[cfg(not(feature = "direct-sql"))]
76impl<T> UpdateBuilder<T>
77where
78    T: DeserializeOwned + Send,
79{
80    /// Execute the UPDATE query.
81    pub async fn execute(self) -> SupabaseResponse<T> {
82        let QueryBackend::Rest { ref http, ref base_url, ref api_key, ref schema } = self.backend;
83        let (url, headers, body) = match crate::postgrest::build_postgrest_update(
84            base_url, &self.parts, &self.params,
85        ) {
86            Ok(r) => r,
87            Err(e) => return SupabaseResponse::error(
88                supabase_client_core::SupabaseError::QueryBuilder(e),
89            ),
90        };
91        crate::postgrest_execute::execute_rest(
92            http, reqwest::Method::PATCH, &url, headers, Some(body), api_key, schema, &self.parts,
93        ).await
94    }
95}
96
97// Direct-SQL mode: additional FromRow + Unpin bounds
98#[cfg(feature = "direct-sql")]
99impl<T> UpdateBuilder<T>
100where
101    T: DeserializeOwned + Send + Unpin + for<'r> sqlx::FromRow<'r, sqlx::postgres::PgRow>,
102{
103    /// Execute the UPDATE query.
104    pub async fn execute(self) -> SupabaseResponse<T> {
105        match &self.backend {
106            QueryBackend::Rest { http, base_url, api_key, schema } => {
107                let (url, headers, body) = match crate::postgrest::build_postgrest_update(
108                    base_url, &self.parts, &self.params,
109                ) {
110                    Ok(r) => r,
111                    Err(e) => return SupabaseResponse::error(
112                        supabase_client_core::SupabaseError::QueryBuilder(e),
113                    ),
114                };
115                crate::postgrest_execute::execute_rest(
116                    http, reqwest::Method::PATCH, &url, headers, Some(body), api_key, schema, &self.parts,
117                ).await
118            }
119            QueryBackend::DirectSql { pool } => {
120                crate::execute::execute_typed::<T>(pool, &self.parts, &self.params).await
121            }
122        }
123    }
124}