Skip to main content

supabase_client_query/
modifier.rs

1use crate::sql::{CountOption, NullsPosition, OrderClause, OrderDirection, SqlParts, validate_column_name};
2
3/// Trait providing modifier methods (order, limit, range, single, count).
4pub trait Modifiable: Sized {
5    /// Get a mutable reference to the SQL parts.
6    fn parts_mut(&mut self) -> &mut SqlParts;
7
8    /// Order by a column.
9    fn order(mut self, column: &str, direction: OrderDirection) -> Self {
10        if let Err(e) = validate_column_name(column) {
11            tracing::error!("Invalid column name in order: {e}");
12            return self;
13        }
14        self.parts_mut().orders.push(OrderClause {
15            column: column.to_string(),
16            direction,
17            nulls: None,
18        });
19        self
20    }
21
22    /// Order by a column with explicit nulls positioning.
23    fn order_with_nulls(
24        mut self,
25        column: &str,
26        direction: OrderDirection,
27        nulls: NullsPosition,
28    ) -> Self {
29        if let Err(e) = validate_column_name(column) {
30            tracing::error!("Invalid column name in order_with_nulls: {e}");
31            return self;
32        }
33        self.parts_mut().orders.push(OrderClause {
34            column: column.to_string(),
35            direction,
36            nulls: Some(nulls),
37        });
38        self
39    }
40
41    /// Limit the number of rows returned.
42    fn limit(mut self, count: i64) -> Self {
43        self.parts_mut().limit = Some(count);
44        self
45    }
46
47    /// Set the range of rows to return (offset..offset+limit).
48    fn range(mut self, from: i64, to: i64) -> Self {
49        self.parts_mut().offset = Some(from);
50        self.parts_mut().limit = Some(to - from + 1);
51        self
52    }
53
54    /// Expect exactly one row. Returns error if 0 or >1 rows.
55    fn single(mut self) -> Self {
56        self.parts_mut().single = true;
57        self.parts_mut().limit = Some(2); // Fetch 2 to detect >1
58        self
59    }
60
61    /// Expect zero or one row. Returns error if >1 rows.
62    fn maybe_single(mut self) -> Self {
63        self.parts_mut().maybe_single = true;
64        self.parts_mut().limit = Some(2);
65        self
66    }
67
68    /// Request an exact row count.
69    fn count(mut self) -> Self {
70        self.parts_mut().count = CountOption::Exact;
71        self
72    }
73
74    /// Request a row count with a specific counting strategy.
75    fn count_option(mut self, option: CountOption) -> Self {
76        self.parts_mut().count = option;
77        self
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84    use crate::backend::QueryBackend;
85    use crate::select::SelectBuilder;
86    use crate::sql::*;
87    use std::marker::PhantomData;
88    use std::sync::Arc;
89
90    fn make_select() -> SelectBuilder<supabase_client_core::Row> {
91        SelectBuilder {
92            backend: QueryBackend::Rest {
93                http: reqwest::Client::new(),
94                base_url: Arc::from("http://localhost"),
95                api_key: Arc::from("key"),
96                schema: "public".to_string(),
97            },
98            parts: SqlParts::new(SqlOperation::Select, "public", "test"),
99            params: ParamStore::new(),
100            _marker: PhantomData,
101        }
102    }
103
104    #[test]
105    fn test_order_ascending() {
106        let builder = make_select().order("name", OrderDirection::Ascending);
107        assert_eq!(builder.parts.orders.len(), 1);
108        assert_eq!(builder.parts.orders[0].column, "name");
109        assert_eq!(builder.parts.orders[0].direction, OrderDirection::Ascending);
110        assert!(builder.parts.orders[0].nulls.is_none());
111    }
112
113    #[test]
114    fn test_order_descending() {
115        let builder = make_select().order("created_at", OrderDirection::Descending);
116        assert_eq!(builder.parts.orders.len(), 1);
117        assert_eq!(builder.parts.orders[0].column, "created_at");
118        assert_eq!(builder.parts.orders[0].direction, OrderDirection::Descending);
119    }
120
121    #[test]
122    fn test_order_with_nulls_first() {
123        let builder = make_select().order_with_nulls(
124            "score",
125            OrderDirection::Descending,
126            NullsPosition::First,
127        );
128        assert_eq!(builder.parts.orders.len(), 1);
129        assert_eq!(builder.parts.orders[0].column, "score");
130        assert_eq!(builder.parts.orders[0].direction, OrderDirection::Descending);
131        assert_eq!(builder.parts.orders[0].nulls, Some(NullsPosition::First));
132    }
133
134    #[test]
135    fn test_order_with_nulls_last() {
136        let builder = make_select().order_with_nulls(
137            "score",
138            OrderDirection::Ascending,
139            NullsPosition::Last,
140        );
141        assert_eq!(builder.parts.orders[0].nulls, Some(NullsPosition::Last));
142    }
143
144    #[test]
145    fn test_order_invalid_column_ignored() {
146        let builder = make_select().order("bad;col", OrderDirection::Ascending);
147        assert!(builder.parts.orders.is_empty());
148    }
149
150    #[test]
151    fn test_order_with_nulls_invalid_column_ignored() {
152        let builder = make_select().order_with_nulls(
153            "bad\"col",
154            OrderDirection::Ascending,
155            NullsPosition::First,
156        );
157        assert!(builder.parts.orders.is_empty());
158    }
159
160    #[test]
161    fn test_limit() {
162        let builder = make_select().limit(10);
163        assert_eq!(builder.parts.limit, Some(10));
164    }
165
166    #[test]
167    fn test_range() {
168        let builder = make_select().range(5, 14);
169        assert_eq!(builder.parts.offset, Some(5));
170        assert_eq!(builder.parts.limit, Some(10)); // 14 - 5 + 1 = 10
171    }
172
173    #[test]
174    fn test_range_single_row() {
175        let builder = make_select().range(0, 0);
176        assert_eq!(builder.parts.offset, Some(0));
177        assert_eq!(builder.parts.limit, Some(1)); // 0 - 0 + 1 = 1
178    }
179
180    #[test]
181    fn test_single() {
182        let builder = make_select().single();
183        assert!(builder.parts.single);
184        assert_eq!(builder.parts.limit, Some(2));
185    }
186
187    #[test]
188    fn test_maybe_single() {
189        let builder = make_select().maybe_single();
190        assert!(builder.parts.maybe_single);
191        assert_eq!(builder.parts.limit, Some(2));
192    }
193
194    #[test]
195    fn test_count() {
196        let builder = make_select().count();
197        assert_eq!(builder.parts.count, CountOption::Exact);
198    }
199
200    #[test]
201    fn test_count_option_exact() {
202        let builder = make_select().count_option(CountOption::Exact);
203        assert_eq!(builder.parts.count, CountOption::Exact);
204    }
205
206    #[test]
207    fn test_count_option_planned() {
208        let builder = make_select().count_option(CountOption::Planned);
209        assert_eq!(builder.parts.count, CountOption::Planned);
210    }
211
212    #[test]
213    fn test_count_option_estimated() {
214        let builder = make_select().count_option(CountOption::Estimated);
215        assert_eq!(builder.parts.count, CountOption::Estimated);
216    }
217
218    #[test]
219    fn test_count_option_none() {
220        let builder = make_select().count_option(CountOption::None);
221        assert_eq!(builder.parts.count, CountOption::None);
222    }
223}