Skip to main content

dibs_sql/
stmt.rs

1//! SQL statements.
2
3use crate::expr::Expr;
4use crate::{ColumnName, PgType, TableName};
5
6/// A SQL statement.
7#[derive(Debug, Clone)]
8pub enum Stmt {
9    /// A SELECT query.
10    Select(SelectStmt),
11    /// An INSERT statement.
12    Insert(InsertStmt),
13    /// An INSERT ... SELECT statement (for bulk inserts with UNNEST).
14    InsertSelect(InsertSelectStmt),
15    /// An UPDATE statement.
16    Update(UpdateStmt),
17    /// A DELETE statement.
18    Delete(DeleteStmt),
19}
20
21/// A SELECT statement.
22#[derive(Debug, Clone, Default)]
23pub struct SelectStmt {
24    /// Whether to use DISTINCT (eliminates duplicate rows).
25    pub distinct: bool,
26    /// DISTINCT ON columns (PostgreSQL-specific, returns first row of each group).
27    pub distinct_on: Vec<Expr>,
28    /// Columns to select (empty means `SELECT *`).
29    pub columns: Vec<SelectColumn>,
30    /// The FROM clause specifying the primary table.
31    pub from: Option<FromClause>,
32    /// JOIN clauses for related tables.
33    pub joins: Vec<Join>,
34    /// The WHERE clause filter condition.
35    pub where_: Option<Expr>,
36    /// ORDER BY clauses for sorting results.
37    pub order_by: Vec<OrderBy>,
38    /// LIMIT clause to restrict number of rows.
39    pub limit: Option<Expr>,
40    /// OFFSET clause for pagination.
41    pub offset: Option<Expr>,
42}
43
44/// A column in a SELECT clause.
45#[derive(Debug, Clone)]
46pub enum SelectColumn {
47    /// An expression with optional alias: `expr AS alias`.
48    Expr {
49        /// The expression to select.
50        expr: Expr,
51        /// Optional alias for the column.
52        alias: Option<ColumnName>,
53    },
54
55    /// All columns from a table: `table.*`.
56    AllFrom(TableName),
57}
58
59impl SelectColumn {
60    pub fn expr(expr: Expr) -> Self {
61        SelectColumn::Expr { expr, alias: None }
62    }
63
64    pub fn aliased(expr: Expr, alias: ColumnName) -> Self {
65        SelectColumn::Expr {
66            expr,
67            alias: Some(alias),
68        }
69    }
70
71    pub fn all_from(table: TableName) -> Self {
72        SelectColumn::AllFrom(table)
73    }
74}
75
76/// A FROM clause specifying the primary table.
77#[derive(Debug, Clone)]
78pub struct FromClause {
79    /// The table name.
80    pub table: TableName,
81    /// Optional alias for the table (e.g., `FROM users t0`).
82    pub alias: Option<TableName>,
83}
84
85impl FromClause {
86    pub fn table(name: TableName) -> Self {
87        Self {
88            table: name,
89            alias: None,
90        }
91    }
92
93    pub fn aliased(name: TableName, alias: TableName) -> Self {
94        Self {
95            table: name,
96            alias: Some(alias),
97        }
98    }
99}
100
101/// An UNNEST clause for bulk operations.
102///
103/// Generates SQL like: `UNNEST($1::text[], $2::bigint[]) AS t(col1, col2)`
104#[derive(Debug, Clone)]
105pub struct Unnest {
106    /// Parameters with their PostgreSQL array types.
107    pub params: Vec<UnnestParam>,
108    /// Alias for the UNNEST result (e.g., "t").
109    pub alias: TableName,
110}
111
112/// A parameter in an UNNEST clause.
113#[derive(Debug, Clone)]
114pub struct UnnestParam {
115    /// The parameter name.
116    pub name: ColumnName,
117    /// The PostgreSQL array type (e.g., "text[]", "bigint[]").
118    pub pg_type: PgType,
119}
120
121impl UnnestParam {
122    pub fn new(name: ColumnName, pg_type: PgType) -> Self {
123        Self { name, pg_type }
124    }
125}
126
127impl Unnest {
128    pub fn new(alias: TableName) -> Self {
129        Self {
130            params: Vec::new(),
131            alias,
132        }
133    }
134
135    pub fn param(mut self, name: ColumnName, pg_type: PgType) -> Self {
136        self.params.push(UnnestParam::new(name, pg_type));
137        self
138    }
139
140    pub fn params(mut self, params: impl IntoIterator<Item = UnnestParam>) -> Self {
141        self.params.extend(params);
142        self
143    }
144}
145
146/// A JOIN clause.
147#[derive(Debug, Clone)]
148pub struct Join {
149    /// The type of join (INNER, LEFT, RIGHT, FULL).
150    pub kind: JoinKind,
151    /// The table to join.
152    pub table: TableName,
153    /// Optional alias for the joined table.
154    pub alias: Option<TableName>,
155    /// The ON condition for the join.
156    pub on: Expr,
157}
158
159/// Type of JOIN.
160#[derive(Debug, Clone, Copy, PartialEq, Eq)]
161pub enum JoinKind {
162    /// INNER JOIN - only matching rows from both tables.
163    Inner,
164    /// LEFT JOIN - all rows from left table, matching from right.
165    Left,
166    /// RIGHT JOIN - all rows from right table, matching from left.
167    Right,
168    /// FULL JOIN - all rows from both tables.
169    Full,
170}
171
172impl JoinKind {
173    pub fn as_str(self) -> &'static str {
174        match self {
175            JoinKind::Inner => "INNER JOIN",
176            JoinKind::Left => "LEFT JOIN",
177            JoinKind::Right => "RIGHT JOIN",
178            JoinKind::Full => "FULL JOIN",
179        }
180    }
181}
182
183/// ORDER BY clause for sorting query results.
184#[derive(Debug, Clone)]
185pub struct OrderBy {
186    /// The expression to sort by.
187    pub expr: Expr,
188    /// Whether to sort descending (true) or ascending (false).
189    pub desc: bool,
190    /// Optional NULLS FIRST / NULLS LAST specification.
191    pub nulls: Option<NullsOrder>,
192}
193
194impl OrderBy {
195    pub fn asc(expr: Expr) -> Self {
196        Self {
197            expr,
198            desc: false,
199            nulls: None,
200        }
201    }
202
203    pub fn desc(expr: Expr) -> Self {
204        Self {
205            expr,
206            desc: true,
207            nulls: None,
208        }
209    }
210}
211
212/// NULLS FIRST / NULLS LAST ordering for ORDER BY.
213#[derive(Debug, Clone, Copy, PartialEq, Eq)]
214pub enum NullsOrder {
215    /// NULL values sort before non-NULL values.
216    First,
217    /// NULL values sort after non-NULL values.
218    Last,
219}
220
221// ============================================================================
222// INSERT statement
223// ============================================================================
224
225/// An INSERT statement.
226#[derive(Debug, Clone)]
227pub struct InsertStmt {
228    /// The table to insert into.
229    pub table: TableName,
230    /// Column names for the insert.
231    pub columns: Vec<ColumnName>,
232    /// Values to insert (parallel to columns).
233    pub values: Vec<Expr>,
234    /// Optional ON CONFLICT clause for upsert behavior.
235    pub on_conflict: Option<OnConflict>,
236    /// Columns to return after insert (RETURNING clause).
237    pub returning: Vec<ColumnName>,
238}
239
240/// ON CONFLICT clause for upsert behavior.
241#[derive(Debug, Clone)]
242pub struct OnConflict {
243    /// Conflict target columns (the unique constraint columns).
244    pub columns: Vec<ColumnName>,
245    /// What to do when a conflict occurs.
246    pub action: ConflictAction,
247}
248
249/// Action to take when a conflict occurs.
250#[derive(Debug, Clone)]
251pub enum ConflictAction {
252    /// DO NOTHING - skip the conflicting row.
253    DoNothing,
254    /// DO UPDATE SET - update the existing row.
255    DoUpdate(Vec<UpdateAssignment>),
256}
257
258/// An INSERT ... SELECT statement for bulk inserts.
259///
260/// Used with UNNEST for efficient bulk operations:
261/// ```sql
262/// INSERT INTO products (handle, status, created_at)
263/// SELECT handle, status, NOW()
264/// FROM UNNEST($1::text[], $2::text[]) AS t(handle, status)
265/// RETURNING id, handle, status
266/// ```
267#[derive(Debug, Clone)]
268pub struct InsertSelectStmt {
269    /// The table to insert into.
270    pub table: TableName,
271    /// Column names for the insert.
272    pub columns: Vec<ColumnName>,
273    /// Expressions to select (parallel to columns).
274    pub select_exprs: Vec<Expr>,
275    /// The UNNEST source.
276    pub unnest: Unnest,
277    /// Optional ON CONFLICT clause for upsert behavior.
278    pub on_conflict: Option<OnConflict>,
279    /// Columns to return after insert (RETURNING clause).
280    pub returning: Vec<ColumnName>,
281}
282
283/// A column assignment for UPDATE SET or ON CONFLICT DO UPDATE SET.
284#[derive(Debug, Clone)]
285pub struct UpdateAssignment {
286    /// The column to update.
287    pub column: ColumnName,
288    /// The value to assign.
289    pub value: Expr,
290}
291
292impl UpdateAssignment {
293    pub fn new(column: ColumnName, value: Expr) -> Self {
294        Self { column, value }
295    }
296}
297
298// ============================================================================
299// UPDATE statement
300// ============================================================================
301
302/// An UPDATE statement.
303#[derive(Debug, Clone)]
304pub struct UpdateStmt {
305    /// The table to update.
306    pub table: TableName,
307    /// Column assignments (SET clause).
308    pub assignments: Vec<UpdateAssignment>,
309    /// Optional WHERE clause filter.
310    pub where_: Option<Expr>,
311    /// Columns to return after update (RETURNING clause).
312    pub returning: Vec<ColumnName>,
313}
314
315// ============================================================================
316// DELETE statement
317// ============================================================================
318
319/// A DELETE statement.
320#[derive(Debug, Clone)]
321pub struct DeleteStmt {
322    /// The table to delete from.
323    pub table: TableName,
324    /// Optional WHERE clause filter.
325    pub where_: Option<Expr>,
326    /// Columns to return after delete (RETURNING clause).
327    pub returning: Vec<ColumnName>,
328}
329
330// ============================================================================
331// Builder-style constructors
332// ============================================================================
333
334impl SelectStmt {
335    pub fn new() -> Self {
336        Self::default()
337    }
338
339    /// Set DISTINCT to eliminate duplicate rows.
340    pub fn distinct(mut self) -> Self {
341        self.distinct = true;
342        self
343    }
344
345    /// Set DISTINCT ON columns (PostgreSQL-specific).
346    /// Returns the first row of each group defined by these columns.
347    pub fn distinct_on(mut self, cols: impl IntoIterator<Item = Expr>) -> Self {
348        self.distinct_on.extend(cols);
349        self
350    }
351
352    pub fn column(mut self, col: SelectColumn) -> Self {
353        self.columns.push(col);
354        self
355    }
356
357    pub fn columns(mut self, cols: impl IntoIterator<Item = SelectColumn>) -> Self {
358        self.columns.extend(cols);
359        self
360    }
361
362    pub fn from(mut self, from: FromClause) -> Self {
363        self.from = Some(from);
364        self
365    }
366
367    pub fn join(mut self, join: Join) -> Self {
368        self.joins.push(join);
369        self
370    }
371
372    pub fn where_(mut self, expr: Expr) -> Self {
373        self.where_ = Some(expr);
374        self
375    }
376
377    pub fn and_where(mut self, expr: Expr) -> Self {
378        self.where_ = Some(match self.where_ {
379            Some(existing) => existing.and(expr),
380            None => expr,
381        });
382        self
383    }
384
385    pub fn order_by(mut self, order: OrderBy) -> Self {
386        self.order_by.push(order);
387        self
388    }
389
390    pub fn limit(mut self, expr: Expr) -> Self {
391        self.limit = Some(expr);
392        self
393    }
394
395    pub fn offset(mut self, expr: Expr) -> Self {
396        self.offset = Some(expr);
397        self
398    }
399}
400
401impl InsertStmt {
402    pub fn new(table: TableName) -> Self {
403        Self {
404            table,
405            columns: Vec::new(),
406            values: Vec::new(),
407            on_conflict: None,
408            returning: Vec::new(),
409        }
410    }
411
412    pub fn column(mut self, name: ColumnName, value: Expr) -> Self {
413        self.columns.push(name);
414        self.values.push(value);
415        self
416    }
417
418    pub fn on_conflict(mut self, conflict: OnConflict) -> Self {
419        self.on_conflict = Some(conflict);
420        self
421    }
422
423    pub fn returning(mut self, cols: impl IntoIterator<Item = ColumnName>) -> Self {
424        self.returning.extend(cols);
425        self
426    }
427}
428
429impl UpdateStmt {
430    pub fn new(table: TableName) -> Self {
431        Self {
432            table,
433            assignments: Vec::new(),
434            where_: None,
435            returning: Vec::new(),
436        }
437    }
438
439    pub fn set(mut self, column: ColumnName, value: Expr) -> Self {
440        self.assignments.push(UpdateAssignment::new(column, value));
441        self
442    }
443
444    pub fn where_(mut self, expr: Expr) -> Self {
445        self.where_ = Some(expr);
446        self
447    }
448
449    pub fn and_where(mut self, expr: Expr) -> Self {
450        self.where_ = Some(match self.where_ {
451            Some(existing) => existing.and(expr),
452            None => expr,
453        });
454        self
455    }
456
457    pub fn returning(mut self, cols: impl IntoIterator<Item = ColumnName>) -> Self {
458        self.returning.extend(cols);
459        self
460    }
461}
462
463impl DeleteStmt {
464    pub fn new(table: TableName) -> Self {
465        Self {
466            table,
467            where_: None,
468            returning: Vec::new(),
469        }
470    }
471
472    pub fn where_(mut self, expr: Expr) -> Self {
473        self.where_ = Some(expr);
474        self
475    }
476
477    pub fn and_where(mut self, expr: Expr) -> Self {
478        self.where_ = Some(match self.where_ {
479            Some(existing) => existing.and(expr),
480            None => expr,
481        });
482        self
483    }
484
485    pub fn returning(mut self, cols: impl IntoIterator<Item = ColumnName>) -> Self {
486        self.returning.extend(cols);
487        self
488    }
489}
490
491impl InsertSelectStmt {
492    pub fn new(table: TableName, unnest: Unnest) -> Self {
493        Self {
494            table,
495            columns: Vec::new(),
496            select_exprs: Vec::new(),
497            unnest,
498            on_conflict: None,
499            returning: Vec::new(),
500        }
501    }
502
503    /// Add a column with its select expression.
504    pub fn column(mut self, name: ColumnName, expr: Expr) -> Self {
505        self.columns.push(name);
506        self.select_exprs.push(expr);
507        self
508    }
509
510    pub fn on_conflict(mut self, conflict: OnConflict) -> Self {
511        self.on_conflict = Some(conflict);
512        self
513    }
514
515    pub fn returning(mut self, cols: impl IntoIterator<Item = ColumnName>) -> Self {
516        self.returning.extend(cols);
517        self
518    }
519}