Skip to main content

supabase_client_query/
select.rs

1use std::marker::PhantomData;
2
3use serde::de::DeserializeOwned;
4
5use supabase_client_core::SupabaseResponse;
6
7use crate::backend::QueryBackend;
8use crate::csv_select::CsvSelectBuilder;
9use crate::filter::Filterable;
10use crate::geojson_select::GeoJsonSelectBuilder;
11use crate::modifier::Modifiable;
12use crate::sql::{ExplainOptions, FilterCondition, ParamStore, SqlParts};
13
14/// Builder for SELECT queries. Implements both Filterable and Modifiable.
15pub struct SelectBuilder<T> {
16    pub(crate) backend: QueryBackend,
17    pub(crate) parts: SqlParts,
18    pub(crate) params: ParamStore,
19    pub(crate) _marker: PhantomData<T>,
20}
21
22impl<T> Filterable for SelectBuilder<T> {
23    fn filters_mut(&mut self) -> &mut Vec<FilterCondition> {
24        &mut self.parts.filters
25    }
26    fn params_mut(&mut self) -> &mut ParamStore {
27        &mut self.params
28    }
29}
30
31impl<T> Modifiable for SelectBuilder<T> {
32    fn parts_mut(&mut self) -> &mut SqlParts {
33        &mut self.parts
34    }
35}
36
37impl<T> SelectBuilder<T> {
38    /// Override the schema for this query.
39    pub fn schema(mut self, schema: &str) -> Self {
40        self.parts.schema_override = Some(schema.to_string());
41        self
42    }
43
44    /// Wrap the SELECT in `EXPLAIN (ANALYZE, FORMAT JSON)`.
45    pub fn explain(mut self) -> Self {
46        self.parts.explain = Some(ExplainOptions::default());
47        self
48    }
49
50    /// Wrap the SELECT in EXPLAIN with custom options.
51    pub fn explain_with(mut self, options: ExplainOptions) -> Self {
52        self.parts.explain = Some(options);
53        self
54    }
55
56    /// Switch to head/count-only mode.
57    pub fn head(mut self) -> Self {
58        self.parts.head = true;
59        self
60    }
61
62    /// Switch to CSV output mode. Returns a `CsvSelectBuilder` that
63    /// executes the query and returns the raw CSV text.
64    pub fn csv(self) -> CsvSelectBuilder {
65        CsvSelectBuilder {
66            backend: self.backend,
67            parts: self.parts,
68            params: self.params,
69        }
70    }
71
72    /// Switch to GeoJSON output mode. Returns a `GeoJsonSelectBuilder` that
73    /// executes the query and returns a `serde_json::Value` (GeoJSON FeatureCollection).
74    pub fn geojson(self) -> GeoJsonSelectBuilder {
75        GeoJsonSelectBuilder {
76            backend: self.backend,
77            parts: self.parts,
78            params: self.params,
79        }
80    }
81}
82
83// REST-only mode: only DeserializeOwned + Send needed
84#[cfg(not(feature = "direct-sql"))]
85impl<T> SelectBuilder<T>
86where
87    T: DeserializeOwned + Send,
88{
89    /// Execute the SELECT query and return results.
90    pub async fn execute(self) -> SupabaseResponse<T> {
91        let QueryBackend::Rest { ref http, ref base_url, ref api_key, ref schema } = self.backend;
92        let method = if self.parts.head {
93            reqwest::Method::HEAD
94        } else {
95            reqwest::Method::GET
96        };
97        let (url, headers) = match crate::postgrest::build_postgrest_select(
98            base_url, &self.parts, &self.params,
99        ) {
100            Ok(r) => r,
101            Err(e) => return SupabaseResponse::error(
102                supabase_client_core::SupabaseError::QueryBuilder(e),
103            ),
104        };
105        crate::postgrest_execute::execute_rest(
106            http, method, &url, headers, None, api_key, schema, &self.parts,
107        ).await
108    }
109}
110
111// Direct-SQL mode: additional FromRow + Unpin bounds
112#[cfg(feature = "direct-sql")]
113impl<T> SelectBuilder<T>
114where
115    T: DeserializeOwned + Send + Unpin + for<'r> sqlx::FromRow<'r, sqlx::postgres::PgRow>,
116{
117    /// Execute the SELECT query and return results.
118    pub async fn execute(self) -> SupabaseResponse<T> {
119        match &self.backend {
120            QueryBackend::Rest { http, base_url, api_key, schema } => {
121                let method = if self.parts.head {
122                    reqwest::Method::HEAD
123                } else {
124                    reqwest::Method::GET
125                };
126                let (url, headers) = match crate::postgrest::build_postgrest_select(
127                    base_url, &self.parts, &self.params,
128                ) {
129                    Ok(r) => r,
130                    Err(e) => return SupabaseResponse::error(
131                        supabase_client_core::SupabaseError::QueryBuilder(e),
132                    ),
133                };
134                crate::postgrest_execute::execute_rest(
135                    http, method, &url, headers, None, api_key, schema, &self.parts,
136                ).await
137            }
138            QueryBackend::DirectSql { pool } => {
139                crate::execute::execute_typed::<T>(pool, &self.parts, &self.params).await
140            }
141        }
142    }
143}