prax_query/
types.rs

1//! Common types used in query building.
2
3use serde::{Deserialize, Serialize};
4use std::borrow::Cow;
5use std::fmt;
6
7/// Sort order for query results.
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
9pub enum SortOrder {
10    /// Ascending order (A-Z, 0-9, oldest first).
11    #[default]
12    Asc,
13    /// Descending order (Z-A, 9-0, newest first).
14    Desc,
15}
16
17impl SortOrder {
18    /// Get the SQL keyword for this sort order.
19    pub fn as_sql(&self) -> &'static str {
20        match self {
21            Self::Asc => "ASC",
22            Self::Desc => "DESC",
23        }
24    }
25}
26
27impl fmt::Display for SortOrder {
28    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29        write!(f, "{}", self.as_sql())
30    }
31}
32
33/// Null handling in sorting.
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
35pub enum NullsOrder {
36    /// Nulls appear first in the results.
37    First,
38    /// Nulls appear last in the results.
39    Last,
40}
41
42impl NullsOrder {
43    /// Get the SQL clause for this null order.
44    pub fn as_sql(&self) -> &'static str {
45        match self {
46            Self::First => "NULLS FIRST",
47            Self::Last => "NULLS LAST",
48        }
49    }
50}
51
52/// Order by specification for a single field.
53#[derive(Debug, Clone, PartialEq, Eq)]
54pub struct OrderByField {
55    /// The column name to order by.
56    pub column: Cow<'static, str>,
57    /// The sort order.
58    pub order: SortOrder,
59    /// Null handling (optional).
60    pub nulls: Option<NullsOrder>,
61}
62
63impl OrderByField {
64    /// Create a new order by field.
65    pub fn new(column: impl Into<Cow<'static, str>>, order: SortOrder) -> Self {
66        Self {
67            column: column.into(),
68            order,
69            nulls: None,
70        }
71    }
72
73    /// Create a new order by field with a static column name (zero allocation).
74    #[inline]
75    pub const fn new_static(column: &'static str, order: SortOrder) -> Self {
76        Self {
77            column: Cow::Borrowed(column),
78            order,
79            nulls: None,
80        }
81    }
82
83    /// Set null handling.
84    pub fn nulls(mut self, nulls: NullsOrder) -> Self {
85        self.nulls = Some(nulls);
86        self
87    }
88
89    /// Create an ascending order.
90    pub fn asc(column: impl Into<Cow<'static, str>>) -> Self {
91        Self::new(column, SortOrder::Asc)
92    }
93
94    /// Create a descending order.
95    pub fn desc(column: impl Into<Cow<'static, str>>) -> Self {
96        Self::new(column, SortOrder::Desc)
97    }
98
99    /// Create an ascending order with a static column name (zero allocation).
100    #[inline]
101    pub const fn asc_static(column: &'static str) -> Self {
102        Self::new_static(column, SortOrder::Asc)
103    }
104
105    /// Create a descending order with a static column name (zero allocation).
106    #[inline]
107    pub const fn desc_static(column: &'static str) -> Self {
108        Self::new_static(column, SortOrder::Desc)
109    }
110
111    /// Generate the SQL for this order by field.
112    ///
113    /// Optimized to write directly to a pre-sized buffer.
114    pub fn to_sql(&self) -> String {
115        // column + " " + ASC/DESC (4) + optional " NULLS FIRST/LAST" (12)
116        let cap = self.column.len() + 5 + if self.nulls.is_some() { 12 } else { 0 };
117        let mut sql = String::with_capacity(cap);
118        self.write_sql(&mut sql);
119        sql
120    }
121
122    /// Write the SQL directly to a buffer (zero allocation).
123    ///
124    /// # Examples
125    ///
126    /// ```rust
127    /// use prax_query::types::OrderByField;
128    ///
129    /// let field = OrderByField::desc("created_at");
130    /// let mut buffer = String::with_capacity(64);
131    /// buffer.push_str("ORDER BY ");
132    /// field.write_sql(&mut buffer);
133    /// assert_eq!(buffer, "ORDER BY created_at DESC");
134    /// ```
135    #[inline]
136    pub fn write_sql(&self, buffer: &mut String) {
137        buffer.push_str(&self.column);
138        buffer.push(' ');
139        buffer.push_str(self.order.as_sql());
140        if let Some(nulls) = self.nulls {
141            buffer.push(' ');
142            buffer.push_str(nulls.as_sql());
143        }
144    }
145
146    /// Get the estimated SQL length for capacity planning.
147    #[inline]
148    pub fn estimated_len(&self) -> usize {
149        self.column.len() + 5 + if self.nulls.is_some() { 12 } else { 0 }
150    }
151}
152
153/// Order by specification that can be a single field or multiple fields.
154#[derive(Debug, Clone, PartialEq, Eq)]
155pub enum OrderBy {
156    /// Order by a single field.
157    Field(OrderByField),
158    /// Order by multiple fields (frozen slice for memory efficiency).
159    Fields(Box<[OrderByField]>),
160}
161
162impl OrderBy {
163    /// Create an empty order by (no ordering).
164    pub fn none() -> Self {
165        Self::Fields(Box::new([]))
166    }
167
168    /// Check if the order by is empty.
169    pub fn is_empty(&self) -> bool {
170        match self {
171            Self::Field(_) => false,
172            Self::Fields(fields) => fields.is_empty(),
173        }
174    }
175
176    /// Add a field to the order by.
177    pub fn then(self, field: OrderByField) -> Self {
178        match self {
179            Self::Field(existing) => Self::Fields(vec![existing, field].into_boxed_slice()),
180            Self::Fields(existing) => {
181                let mut fields: Vec<_> = existing.into_vec();
182                fields.push(field);
183                Self::Fields(fields.into_boxed_slice())
184            }
185        }
186    }
187
188    /// Create an OrderBy from multiple fields (optimized).
189    pub fn from_fields(fields: impl IntoIterator<Item = OrderByField>) -> Self {
190        let fields: Vec<_> = fields.into_iter().collect();
191        match fields.len() {
192            0 => Self::none(),
193            1 => Self::Field(fields.into_iter().next().unwrap()),
194            _ => Self::Fields(fields.into_boxed_slice()),
195        }
196    }
197
198    /// Generate the SQL ORDER BY clause (without the "ORDER BY" keyword).
199    ///
200    /// Optimized to write directly to a pre-sized buffer.
201    pub fn to_sql(&self) -> String {
202        match self {
203            Self::Field(field) => field.to_sql(),
204            Self::Fields(fields) if fields.is_empty() => String::new(),
205            Self::Fields(fields) => {
206                // Estimate capacity
207                let cap: usize = fields.iter().map(|f| f.estimated_len() + 2).sum();
208                let mut sql = String::with_capacity(cap);
209                self.write_sql(&mut sql);
210                sql
211            }
212        }
213    }
214
215    /// Write the SQL ORDER BY clause directly to a buffer (zero allocation).
216    ///
217    /// # Examples
218    ///
219    /// ```rust
220    /// use prax_query::types::{OrderBy, OrderByField};
221    ///
222    /// let order = OrderBy::from_fields([
223    ///     OrderByField::desc("created_at"),
224    ///     OrderByField::asc("id"),
225    /// ]);
226    /// let mut buffer = String::with_capacity(64);
227    /// buffer.push_str("ORDER BY ");
228    /// order.write_sql(&mut buffer);
229    /// assert_eq!(buffer, "ORDER BY created_at DESC, id ASC");
230    /// ```
231    #[inline]
232    pub fn write_sql(&self, buffer: &mut String) {
233        match self {
234            Self::Field(field) => field.write_sql(buffer),
235            Self::Fields(fields) => {
236                for (i, field) in fields.iter().enumerate() {
237                    if i > 0 {
238                        buffer.push_str(", ");
239                    }
240                    field.write_sql(buffer);
241                }
242            }
243        }
244    }
245
246    /// Get the number of fields in this OrderBy.
247    #[inline]
248    pub fn field_count(&self) -> usize {
249        match self {
250            Self::Field(_) => 1,
251            Self::Fields(fields) => fields.len(),
252        }
253    }
254}
255
256impl From<OrderByField> for OrderBy {
257    fn from(field: OrderByField) -> Self {
258        Self::Field(field)
259    }
260}
261
262impl From<Vec<OrderByField>> for OrderBy {
263    fn from(fields: Vec<OrderByField>) -> Self {
264        match fields.len() {
265            0 => Self::none(),
266            1 => Self::Field(fields.into_iter().next().unwrap()),
267            _ => Self::Fields(fields.into_boxed_slice()),
268        }
269    }
270}
271
272/// Builder for constructing OrderBy with pre-allocated capacity.
273#[derive(Debug)]
274pub struct OrderByBuilder {
275    fields: Vec<OrderByField>,
276}
277
278impl OrderByBuilder {
279    /// Create a new builder with the specified capacity.
280    #[inline]
281    pub fn with_capacity(capacity: usize) -> Self {
282        Self {
283            fields: Vec::with_capacity(capacity),
284        }
285    }
286
287    /// Add a field to the order by.
288    #[inline]
289    pub fn push(mut self, field: OrderByField) -> Self {
290        self.fields.push(field);
291        self
292    }
293
294    /// Add an ascending field.
295    #[inline]
296    pub fn asc(self, column: impl Into<Cow<'static, str>>) -> Self {
297        self.push(OrderByField::asc(column))
298    }
299
300    /// Add a descending field.
301    #[inline]
302    pub fn desc(self, column: impl Into<Cow<'static, str>>) -> Self {
303        self.push(OrderByField::desc(column))
304    }
305
306    /// Build the OrderBy.
307    #[inline]
308    pub fn build(self) -> OrderBy {
309        OrderBy::from(self.fields)
310    }
311}
312
313/// Pre-defined common ordering patterns (zero allocation).
314pub mod order_patterns {
315    use super::*;
316
317    /// Order by `created_at DESC` (most recent first).
318    pub const CREATED_AT_DESC: OrderByField = OrderByField::desc_static("created_at");
319
320    /// Order by `created_at ASC` (oldest first).
321    pub const CREATED_AT_ASC: OrderByField = OrderByField::asc_static("created_at");
322
323    /// Order by `updated_at DESC`.
324    pub const UPDATED_AT_DESC: OrderByField = OrderByField::desc_static("updated_at");
325
326    /// Order by `updated_at ASC`.
327    pub const UPDATED_AT_ASC: OrderByField = OrderByField::asc_static("updated_at");
328
329    /// Order by `id ASC`.
330    pub const ID_ASC: OrderByField = OrderByField::asc_static("id");
331
332    /// Order by `id DESC`.
333    pub const ID_DESC: OrderByField = OrderByField::desc_static("id");
334
335    /// Order by `name ASC`.
336    pub const NAME_ASC: OrderByField = OrderByField::asc_static("name");
337
338    /// Order by `name DESC`.
339    pub const NAME_DESC: OrderByField = OrderByField::desc_static("name");
340
341    /// Order by `price ASC`.
342    pub const PRICE_ASC: OrderByField = OrderByField::asc_static("price");
343
344    /// Order by `price DESC`.
345    pub const PRICE_DESC: OrderByField = OrderByField::desc_static("price");
346}
347
348/// Field selection for queries.
349#[derive(Debug, Clone, PartialEq, Eq, Default)]
350pub enum Select {
351    /// Select all fields.
352    #[default]
353    All,
354    /// Select specific fields.
355    Fields(Vec<String>),
356    /// Select a single field.
357    Field(String),
358}
359
360impl Select {
361    /// Create a selection for all fields.
362    pub fn all() -> Self {
363        Self::All
364    }
365
366    /// Create a selection for specific fields.
367    pub fn fields(fields: impl IntoIterator<Item = impl Into<String>>) -> Self {
368        Self::Fields(fields.into_iter().map(Into::into).collect())
369    }
370
371    /// Create a selection for a single field.
372    pub fn field(field: impl Into<String>) -> Self {
373        Self::Field(field.into())
374    }
375
376    /// Check if this selects all fields.
377    pub fn is_all(&self) -> bool {
378        matches!(self, Self::All)
379    }
380
381    /// Get the list of field names.
382    pub fn field_names(&self) -> Vec<&str> {
383        match self {
384            Self::All => vec!["*"],
385            Self::Fields(fields) => fields.iter().map(String::as_str).collect(),
386            Self::Field(field) => vec![field.as_str()],
387        }
388    }
389
390    /// Generate the SQL column list.
391    pub fn to_sql(&self) -> String {
392        match self {
393            Self::All => "*".to_string(),
394            Self::Fields(fields) => {
395                // Estimate capacity
396                let cap: usize = fields.iter().map(|f| f.len() + 2).sum();
397                let mut sql = String::with_capacity(cap);
398                self.write_sql(&mut sql);
399                sql
400            }
401            Self::Field(field) => field.clone(),
402        }
403    }
404
405    /// Write the SQL column list directly to a buffer (zero allocation).
406    #[inline]
407    pub fn write_sql(&self, buffer: &mut String) {
408        match self {
409            Self::All => buffer.push('*'),
410            Self::Fields(fields) => {
411                for (i, field) in fields.iter().enumerate() {
412                    if i > 0 {
413                        buffer.push_str(", ");
414                    }
415                    buffer.push_str(field);
416                }
417            }
418            Self::Field(field) => buffer.push_str(field),
419        }
420    }
421}
422
423/// Set parameter for updates - either set a value or leave unchanged.
424#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
425pub enum SetParam<T> {
426    /// Set the field to this value.
427    Set(T),
428    /// Leave the field unchanged.
429    #[default]
430    Unset,
431}
432
433impl<T> SetParam<T> {
434    /// Check if a value is set.
435    pub fn is_set(&self) -> bool {
436        matches!(self, Self::Set(_))
437    }
438
439    /// Get the inner value if set.
440    pub fn get(&self) -> Option<&T> {
441        match self {
442            Self::Set(v) => Some(v),
443            Self::Unset => None,
444        }
445    }
446
447    /// Take the inner value if set.
448    pub fn take(self) -> Option<T> {
449        match self {
450            Self::Set(v) => Some(v),
451            Self::Unset => None,
452        }
453    }
454
455    /// Map the inner value.
456    pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> SetParam<U> {
457        match self {
458            Self::Set(v) => SetParam::Set(f(v)),
459            Self::Unset => SetParam::Unset,
460        }
461    }
462}
463
464impl<T> From<T> for SetParam<T> {
465    fn from(value: T) -> Self {
466        Self::Set(value)
467    }
468}
469
470impl<T> From<Option<T>> for SetParam<T> {
471    fn from(opt: Option<T>) -> Self {
472        match opt {
473            Some(v) => Self::Set(v),
474            None => Self::Unset,
475        }
476    }
477}
478
479#[cfg(test)]
480mod tests {
481    use super::*;
482
483    #[test]
484    fn test_sort_order() {
485        assert_eq!(SortOrder::Asc.as_sql(), "ASC");
486        assert_eq!(SortOrder::Desc.as_sql(), "DESC");
487    }
488
489    #[test]
490    fn test_order_by_field() {
491        let field = OrderByField::desc("created_at");
492        assert_eq!(field.to_sql(), "created_at DESC");
493
494        let field_with_nulls = OrderByField::asc("name").nulls(NullsOrder::Last);
495        assert_eq!(field_with_nulls.to_sql(), "name ASC NULLS LAST");
496    }
497
498    #[test]
499    fn test_order_by_field_static() {
500        let field = OrderByField::desc_static("created_at");
501        assert_eq!(field.to_sql(), "created_at DESC");
502
503        let field = OrderByField::asc_static("id");
504        assert_eq!(field.to_sql(), "id ASC");
505    }
506
507    #[test]
508    fn test_order_by_field_write_sql() {
509        let field = OrderByField::desc("created_at");
510        let mut buffer = String::with_capacity(32);
511        field.write_sql(&mut buffer);
512        assert_eq!(buffer, "created_at DESC");
513
514        let field = OrderByField::asc("name").nulls(NullsOrder::First);
515        let mut buffer = String::with_capacity(32);
516        field.write_sql(&mut buffer);
517        assert_eq!(buffer, "name ASC NULLS FIRST");
518    }
519
520    #[test]
521    fn test_order_by_multiple() {
522        let order =
523            OrderBy::Field(OrderByField::desc("created_at")).then(OrderByField::asc("name"));
524        assert_eq!(order.to_sql(), "created_at DESC, name ASC");
525    }
526
527    #[test]
528    fn test_order_by_from_fields() {
529        let order =
530            OrderBy::from_fields([OrderByField::desc("created_at"), OrderByField::asc("id")]);
531        assert_eq!(order.to_sql(), "created_at DESC, id ASC");
532        assert_eq!(order.field_count(), 2);
533    }
534
535    #[test]
536    fn test_order_by_write_sql() {
537        let order =
538            OrderBy::from_fields([OrderByField::desc("created_at"), OrderByField::asc("id")]);
539        let mut buffer = String::with_capacity(64);
540        buffer.push_str("ORDER BY ");
541        order.write_sql(&mut buffer);
542        assert_eq!(buffer, "ORDER BY created_at DESC, id ASC");
543    }
544
545    #[test]
546    fn test_order_by_builder() {
547        let order = OrderByBuilder::with_capacity(3)
548            .desc("created_at")
549            .asc("name")
550            .asc("id")
551            .build();
552        assert_eq!(order.to_sql(), "created_at DESC, name ASC, id ASC");
553        assert_eq!(order.field_count(), 3);
554    }
555
556    #[test]
557    fn test_order_patterns() {
558        assert_eq!(order_patterns::CREATED_AT_DESC.to_sql(), "created_at DESC");
559        assert_eq!(order_patterns::ID_ASC.to_sql(), "id ASC");
560        assert_eq!(order_patterns::NAME_ASC.to_sql(), "name ASC");
561    }
562
563    #[test]
564    fn test_select() {
565        assert_eq!(Select::all().to_sql(), "*");
566        assert_eq!(Select::field("id").to_sql(), "id");
567        assert_eq!(
568            Select::fields(["id", "name", "email"]).to_sql(),
569            "id, name, email"
570        );
571    }
572
573    #[test]
574    fn test_select_write_sql() {
575        let select = Select::fields(["id", "name", "email"]);
576        let mut buffer = String::with_capacity(32);
577        buffer.push_str("SELECT ");
578        select.write_sql(&mut buffer);
579        assert_eq!(buffer, "SELECT id, name, email");
580    }
581
582    #[test]
583    fn test_set_param() {
584        let set: SetParam<i32> = SetParam::Set(42);
585        assert!(set.is_set());
586        assert_eq!(set.get(), Some(&42));
587
588        let unset: SetParam<i32> = SetParam::Unset;
589        assert!(!unset.is_set());
590        assert_eq!(unset.get(), None);
591    }
592}