supabase_client_query/
lib.rs1pub mod sql;
19pub mod table;
20pub mod filter;
21pub mod modifier;
22pub mod backend;
23pub mod postgrest;
24pub mod postgrest_execute;
25pub mod builder;
26pub mod select;
27pub mod csv_select;
28pub mod geojson_select;
29pub mod insert;
30pub mod update;
31pub mod delete;
32pub mod upsert;
33pub mod rpc;
34
35#[cfg(feature = "direct-sql")]
36pub mod generate;
37#[cfg(feature = "direct-sql")]
38pub mod execute;
39
40pub use sql::*;
41pub use table::Table;
42pub use filter::{Filterable, FilterCollector};
43pub use modifier::Modifiable;
44pub use backend::QueryBackend;
45pub use builder::{QueryBuilder, TypedQueryBuilder};
46pub use select::SelectBuilder;
47pub use insert::InsertBuilder;
48pub use update::UpdateBuilder;
49pub use delete::DeleteBuilder;
50pub use upsert::UpsertBuilder;
51pub use rpc::{RpcBuilder, TypedRpcBuilder};
52pub use csv_select::CsvSelectBuilder;
53pub use geojson_select::GeoJsonSelectBuilder;
54
55pub use sql::{ExplainOptions, ExplainFormat, CountOption};
57
58use std::sync::Arc;
59use serde::de::DeserializeOwned;
60use serde_json::Value as JsonValue;
61use supabase_client_core::SupabaseClient;
62
63pub trait SupabaseClientQueryExt {
65 fn from(&self, table: &str) -> QueryBuilder;
67
68 fn from_typed<T: Table>(&self) -> TypedQueryBuilder<T>;
70
71 fn rpc(&self, function: &str, args: JsonValue) -> Result<RpcBuilder, supabase_client_core::SupabaseError>;
73
74 fn rpc_typed<T>(&self, function: &str, args: JsonValue) -> Result<TypedRpcBuilder<T>, supabase_client_core::SupabaseError>
76 where
77 T: DeserializeOwned + Send;
78}
79
80impl SupabaseClientQueryExt for SupabaseClient {
81 fn from(&self, table: &str) -> QueryBuilder {
82 let backend = make_backend(self);
83 QueryBuilder::new(backend, self.schema().to_string(), table.to_string())
84 }
85
86 fn from_typed<T: Table>(&self) -> TypedQueryBuilder<T> {
87 let backend = make_backend(self);
88 let schema = if T::schema_name() != "public" {
89 T::schema_name().to_string()
90 } else {
91 self.schema().to_string()
92 };
93 TypedQueryBuilder::new(backend, schema)
94 }
95
96 fn rpc(&self, function: &str, args: JsonValue) -> Result<RpcBuilder, supabase_client_core::SupabaseError> {
97 let backend = make_backend(self);
98 RpcBuilder::new(backend, self.schema().to_string(), function.to_string(), args)
99 }
100
101 fn rpc_typed<T>(&self, function: &str, args: JsonValue) -> Result<TypedRpcBuilder<T>, supabase_client_core::SupabaseError>
102 where
103 T: DeserializeOwned + Send,
104 {
105 let backend = make_backend(self);
106 TypedRpcBuilder::new(backend, self.schema().to_string(), function.to_string(), args)
107 }
108}
109
110fn make_backend(client: &SupabaseClient) -> QueryBackend {
115 #[cfg(feature = "direct-sql")]
116 {
117 if let Some(pool) = client.pool_arc() {
118 return QueryBackend::DirectSql { pool };
119 }
120 }
121
122 QueryBackend::Rest {
123 http: client.http().clone(),
124 base_url: Arc::from(client.supabase_url()),
125 api_key: Arc::from(client.api_key()),
126 schema: client.schema().to_string(),
127 }
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133 use supabase_client_core::SupabaseConfig;
134
135 fn make_client() -> SupabaseClient {
136 let config = SupabaseConfig {
137 supabase_url: "http://localhost:54321".to_string(),
138 supabase_key: "test-key".to_string(),
139 schema: "public".to_string(),
140 #[cfg(feature = "direct-sql")]
141 database_url: None,
142 #[cfg(feature = "direct-sql")]
143 pool: Default::default(),
144 };
145 SupabaseClient::new(config).unwrap()
146 }
147
148 #[test]
149 fn test_from_returns_query_builder() {
150 let client = make_client();
151 let select_builder = client.from("users").select("*");
153 assert_eq!(select_builder.parts.table, "users");
154 assert_eq!(select_builder.parts.schema, "public");
155 assert!(select_builder.parts.select_columns.is_none());
156 }
157
158 #[test]
159 fn test_from_typed_returns_typed_query_builder() {
160 #[derive(Debug, Clone, serde::Deserialize)]
162 struct MyTable {
163 id: i32,
164 }
165
166 impl crate::table::Table for MyTable {
167 fn table_name() -> &'static str {
168 "my_table"
169 }
170
171 fn primary_key_columns() -> &'static [&'static str] {
172 &["id"]
173 }
174
175 fn column_names() -> &'static [&'static str] {
176 &["id"]
177 }
178
179 fn insertable_columns() -> &'static [&'static str] {
180 &[]
181 }
182
183 fn field_to_column(field: &str) -> Option<&'static str> {
184 match field {
185 "id" => Some("id"),
186 _ => None,
187 }
188 }
189
190 fn column_to_field(column: &str) -> Option<&'static str> {
191 match column {
192 "id" => Some("id"),
193 _ => None,
194 }
195 }
196
197 fn bind_insert(&self) -> Vec<SqlParam> {
198 vec![]
199 }
200
201 fn bind_update(&self) -> Vec<SqlParam> {
202 vec![]
203 }
204
205 fn bind_primary_key(&self) -> Vec<SqlParam> {
206 vec![SqlParam::I32(self.id)]
207 }
208 }
209
210 let client = make_client();
211 let select_builder = client.from_typed::<MyTable>().select();
212 assert_eq!(select_builder.parts.table, "my_table");
213 assert_eq!(select_builder.parts.schema, "public");
214 }
215
216 #[test]
217 fn test_make_backend_creates_rest() {
218 let client = make_client();
219 let backend = make_backend(&client);
220 match &backend {
221 QueryBackend::Rest { base_url, schema, .. } => {
222 assert_eq!(base_url.as_ref(), "http://localhost:54321");
223 assert_eq!(schema, "public");
224 }
225 #[cfg(feature = "direct-sql")]
226 _ => panic!("expected Rest backend"),
227 }
228 }
229}