Skip to main content

rorm_sql/
select.rs

1use std::fmt::Write;
2
3use crate::conditional::{BuildCondition, Condition};
4use crate::join_table::{JoinTable, JoinTableImpl};
5use crate::limit_clause::LimitClause;
6use crate::ordering::{OrderByEntry, Ordering};
7use crate::select_column::{SelectColumn, SelectColumnImpl};
8use crate::{DBImpl, Value};
9
10/// Select builder
11///
12/// Can be constructed via [`DBImpl::select`]
13#[derive(Debug)]
14pub struct Select<'until_build, 'post_query> {
15    // Set on construction
16    pub(crate) db_impl: DBImpl,
17    pub(crate) resulting_columns: &'until_build [SelectColumnImpl<'until_build>],
18    pub(crate) from_clause: &'until_build str,
19    pub(crate) join_tables: &'until_build [JoinTableImpl<'until_build, 'post_query>],
20    pub(crate) order_by_clause: &'until_build [OrderByEntry<'until_build>],
21
22    // Set by builder
23    pub(crate) where_clause: Option<&'until_build Condition<'post_query>>,
24    pub(crate) distinct: bool,
25    pub(crate) limit: Option<u64>,
26    pub(crate) offset: Option<u64>,
27
28    #[cfg(feature = "postgres-only")]
29    pub(crate) locking_clause: Option<LockingClause>,
30}
31
32/// A `SELECT` statement's locking clause
33#[cfg(feature = "postgres-only")]
34#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
35pub struct LockingClause {
36    /// Strength of the row lock
37    pub strength: LockStrength,
38
39    /// How to acquire the row lock
40    pub acquire: LockAcquire,
41}
42
43#[cfg(feature = "postgres-only")]
44/// Strength of a `SELECT` statement's row lock
45#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
46pub enum LockStrength {
47    /// `SELECT ... FOR UPDATE;`
48    Update,
49    /// `SELECT ... FOR NO KEY UPDATE;`
50    NoKeyUpdate,
51    /// `SELECT ... FOR SHARE;`
52    Share,
53    /// `SELECT ... FOR KEY SHARE;`
54    KeyShare,
55}
56
57#[cfg(feature = "postgres-only")]
58/// How to acquire a `SELECT` statement's row lock
59#[derive(Default, Copy, Clone, Debug, Eq, PartialEq, Hash)]
60pub enum LockAcquire {
61    /// `SELECT ... FOR <strength>;`
62    #[default]
63    Wait,
64    /// `SELECT ... FOR <strength> NOWAIT;`
65    NoWait,
66    /// `SELECT ... FOR <strength> SKIP LOCKED;`
67    SkipLocked,
68}
69
70impl<'until_build, 'post_build> Select<'until_build, 'post_build> {
71    /// Set a limit to the resulting rows.
72    pub fn limit_clause(mut self, limit: LimitClause) -> Self {
73        self.limit = Some(limit.limit);
74        self.offset = limit.offset;
75        self
76    }
77
78    /// Only retrieve distinct rows.
79    pub fn distinct(mut self) -> Self {
80        self.distinct = true;
81        self
82    }
83
84    /// Set a where clause to the query.
85    pub fn where_clause(mut self, where_clause: &'until_build Condition<'post_build>) -> Self {
86        self.where_clause = Some(where_clause);
87        self
88    }
89
90    /// Set a lock on the resulting rows
91    #[cfg(feature = "postgres-only")]
92    pub fn locking_clause(mut self, locking: LockingClause) -> Self {
93        self.locking_clause = Some(locking);
94        self
95    }
96
97    /// Build the select query
98    pub fn build(self) -> (String, Vec<Value<'post_build>>) {
99        let mut sql;
100        let mut values = Vec::new();
101
102        match self.db_impl {
103            #[cfg(feature = "sqlite")]
104            DBImpl::SQLite => {
105                sql = format!("SELECT{} ", if self.distinct { " DISTINCT" } else { "" });
106
107                let column_len = self.resulting_columns.len();
108                for (idx, column) in self.resulting_columns.iter().enumerate() {
109                    column.build(&mut sql);
110
111                    if idx != column_len - 1 {
112                        write!(sql, ", ").unwrap();
113                    }
114                }
115
116                write!(sql, " FROM \"{}\"", self.from_clause).unwrap();
117
118                for x in self.join_tables {
119                    write!(sql, " ").unwrap();
120                    x.build(&mut sql, &mut values);
121                }
122
123                if let Some(c) = self.where_clause {
124                    write!(sql, " WHERE {}", c.build(DBImpl::SQLite, &mut values)).unwrap()
125                };
126
127                if !self.order_by_clause.is_empty() {
128                    write!(sql, " ORDER BY ").unwrap();
129
130                    let order_by_len = self.order_by_clause.len();
131                    for (idx, entry) in self.order_by_clause.iter().enumerate() {
132                        if let Some(table_name) = entry.table_name {
133                            write!(sql, "{table_name}.").unwrap();
134                        };
135                        write!(
136                            sql,
137                            "{}{}",
138                            entry.column_name,
139                            match entry.ordering {
140                                Ordering::Asc => "",
141                                Ordering::Desc => " DESC",
142                            }
143                        )
144                        .unwrap();
145
146                        if idx != order_by_len - 1 {
147                            write!(sql, ", ").unwrap();
148                        }
149                    }
150                };
151
152                if let Some(limit) = self.limit {
153                    write!(sql, " LIMIT {limit}").unwrap();
154                    if let Some(offset) = self.offset {
155                        write!(sql, " OFFSET {offset}").unwrap();
156                    }
157                };
158
159                write!(sql, ";").unwrap();
160            }
161            #[cfg(feature = "postgres")]
162            DBImpl::Postgres => {
163                sql = format!("SELECT{} ", if self.distinct { " DISTINCT" } else { "" });
164
165                let column_len = self.resulting_columns.len();
166                for (idx, column) in self.resulting_columns.iter().enumerate() {
167                    column.build(&mut sql);
168
169                    if idx != column_len - 1 {
170                        write!(sql, ", ").unwrap();
171                    }
172                }
173
174                write!(sql, " FROM \"{}\"", self.from_clause).unwrap();
175
176                for x in self.join_tables {
177                    write!(sql, " ").unwrap();
178                    x.build(&mut sql, &mut values);
179                }
180
181                if let Some(c) = self.where_clause {
182                    write!(sql, " WHERE {}", c.build(DBImpl::Postgres, &mut values)).unwrap()
183                };
184
185                if !self.order_by_clause.is_empty() {
186                    write!(sql, " ORDER BY ").unwrap();
187
188                    let order_by_len = self.order_by_clause.len();
189                    for (idx, entry) in self.order_by_clause.iter().enumerate() {
190                        if let Some(table_name) = entry.table_name {
191                            write!(sql, "\"{table_name}\".").unwrap();
192                        };
193                        write!(
194                            sql,
195                            "\"{}\"{}",
196                            entry.column_name,
197                            match entry.ordering {
198                                Ordering::Asc => "",
199                                Ordering::Desc => " DESC",
200                            }
201                        )
202                        .unwrap();
203
204                        if idx != order_by_len - 1 {
205                            write!(sql, ", ").unwrap();
206                        }
207                    }
208                };
209
210                if let Some(limit) = self.limit {
211                    write!(sql, " LIMIT {limit}").unwrap();
212                    if let Some(offset) = self.offset {
213                        write!(sql, " OFFSET {offset}").unwrap();
214                    }
215                };
216
217                #[cfg(feature = "postgres-only")]
218                if let Some(locking) = self.locking_clause {
219                    write!(
220                        sql,
221                        " FOR {}{}",
222                        match locking.strength {
223                            LockStrength::Update => "UPDATE",
224                            LockStrength::NoKeyUpdate => "NO KEY UPDATE",
225                            LockStrength::Share => "SHARE",
226                            LockStrength::KeyShare => "KEY SHARE",
227                        },
228                        match locking.acquire {
229                            LockAcquire::Wait => "",
230                            LockAcquire::NoWait => " NOWAIT",
231                            LockAcquire::SkipLocked => " SKIP LOCKED",
232                        }
233                    )
234                    .unwrap();
235                }
236
237                write!(sql, ";").unwrap();
238            }
239        }
240
241        (sql, values)
242    }
243}