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