Skip to main content

qcraft_core/ast/
query.rs

1use super::common::{FieldRef, OrderByDef, SchemaRef};
2use super::conditions::Conditions;
3use super::custom::CustomTableSource;
4use super::expr::Expr;
5
6// ---------------------------------------------------------------------------
7// SELECT statement
8// ---------------------------------------------------------------------------
9
10/// A SELECT query statement.
11#[derive(Debug, Clone, Default)]
12pub struct QueryStmt {
13    pub ctes: Option<Vec<CteDef>>,
14    pub columns: Vec<SelectColumn>,
15    pub distinct: Option<DistinctDef>,
16    /// FROM items. None for `SELECT 1` (no FROM clause).
17    pub from: Option<Vec<FromItem>>,
18    pub joins: Option<Vec<JoinDef>>,
19    pub where_clause: Option<Conditions>,
20    pub group_by: Option<Vec<GroupByItem>>,
21    pub having: Option<Conditions>,
22    pub window: Option<Vec<WindowNameDef>>,
23    pub order_by: Option<Vec<OrderByDef>>,
24    pub limit: Option<LimitDef>,
25    /// Multiple lock clauses: PG supports `FOR UPDATE OF t1 FOR SHARE OF t2`.
26    pub lock: Option<Vec<SelectLockDef>>,
27    /// Set operation (UNION / INTERSECT / EXCEPT). When set, this query
28    /// represents the set operation directly instead of a SELECT statement.
29    pub set_op: Option<Box<SetOpDef>>,
30}
31
32// ---------------------------------------------------------------------------
33// SELECT columns
34// ---------------------------------------------------------------------------
35
36/// A column in SELECT clause.
37#[derive(Debug, Clone)]
38pub enum SelectColumn {
39    /// All columns: `*` or `table.*`.
40    Star(Option<String>),
41
42    /// An expression, optionally aliased.
43    Expr { expr: Expr, alias: Option<String> },
44
45    /// A field reference, optionally aliased.
46    Field {
47        field: FieldRef,
48        alias: Option<String>,
49    },
50}
51
52impl SelectColumn {
53    /// `*`
54    pub fn all() -> Self {
55        SelectColumn::Star(None)
56    }
57
58    /// `table.*`
59    pub fn all_from(table: impl Into<String>) -> Self {
60        SelectColumn::Star(Some(table.into()))
61    }
62
63    /// `table.field`
64    pub fn field(table: &str, name: &str) -> Self {
65        SelectColumn::Field {
66            field: FieldRef::new(table, name),
67            alias: None,
68        }
69    }
70
71    /// Expression without alias.
72    pub fn expr(expr: Expr) -> Self {
73        SelectColumn::Expr { expr, alias: None }
74    }
75
76    /// Expression with alias: `expr AS alias`.
77    pub fn aliased(expr: Expr, alias: impl Into<String>) -> Self {
78        SelectColumn::Expr {
79            expr,
80            alias: Some(alias.into()),
81        }
82    }
83
84    /// `table.field AS alias`
85    pub fn field_aliased(table: &str, name: &str, alias: impl Into<String>) -> Self {
86        SelectColumn::Field {
87            field: FieldRef::new(table, name),
88            alias: Some(alias.into()),
89        }
90    }
91}
92
93// ---------------------------------------------------------------------------
94// DISTINCT
95// ---------------------------------------------------------------------------
96
97/// DISTINCT clause.
98#[derive(Debug, Clone)]
99pub enum DistinctDef {
100    /// Plain DISTINCT (all databases).
101    Distinct,
102    /// DISTINCT ON (expr, ...) — PostgreSQL only.
103    DistinctOn(Vec<Expr>),
104}
105
106// ---------------------------------------------------------------------------
107// FROM item
108// ---------------------------------------------------------------------------
109
110/// A single item in the FROM clause, wrapping a table source with decorations.
111#[derive(Debug, Clone)]
112pub struct FromItem {
113    pub source: TableSource,
114    /// PG: ONLY (exclude inherited/child tables).
115    pub only: bool,
116    /// TABLESAMPLE / SAMPLE clause.
117    pub sample: Option<TableSampleDef>,
118    /// SQLite: INDEXED BY / NOT INDEXED.
119    pub index_hint: Option<SqliteIndexHint>,
120}
121
122impl FromItem {
123    pub fn table(schema_ref: SchemaRef) -> Self {
124        Self {
125            source: TableSource::Table(schema_ref),
126            only: false,
127            sample: None,
128            index_hint: None,
129        }
130    }
131
132    pub fn lateral(inner: FromItem) -> Self {
133        Self {
134            source: TableSource::Lateral(Box::new(inner)),
135            only: false,
136            sample: None,
137            index_hint: None,
138        }
139    }
140
141    pub fn function(name: impl Into<String>, args: Vec<Expr>, alias: impl Into<String>) -> Self {
142        Self {
143            source: TableSource::Function {
144                name: name.into(),
145                args,
146                alias: Some(alias.into()),
147            },
148            only: false,
149            sample: None,
150            index_hint: None,
151        }
152    }
153
154    pub fn values(rows: Vec<Vec<Expr>>, alias: impl Into<String>) -> Self {
155        Self {
156            source: TableSource::Values {
157                rows,
158                alias: alias.into(),
159                column_aliases: None,
160            },
161            only: false,
162            sample: None,
163            index_hint: None,
164        }
165    }
166
167    pub fn subquery(query: QueryStmt, alias: String) -> Self {
168        Self {
169            source: TableSource::SubQuery(SubQueryDef {
170                query: Box::new(query),
171                alias,
172            }),
173            only: false,
174            sample: None,
175            index_hint: None,
176        }
177    }
178}
179
180/// Source of data in FROM clause.
181#[derive(Debug, Clone)]
182pub enum TableSource {
183    /// A table or view.
184    Table(SchemaRef),
185    /// A subquery with alias.
186    SubQuery(SubQueryDef),
187    /// Set operation (UNION/INTERSECT/EXCEPT).
188    SetOp(Box<SetOpDef>),
189    /// LATERAL (subquery).
190    Lateral(Box<FromItem>),
191    /// Table-valued function: `generate_series(1, 10)`, `json_each(col)`.
192    Function {
193        name: String,
194        args: Vec<Expr>,
195        alias: Option<String>,
196    },
197    /// VALUES as a table source: `(VALUES (1,'a'), (2,'b')) AS t(id, name)`.
198    Values {
199        rows: Vec<Vec<Expr>>,
200        alias: String,
201        column_aliases: Option<Vec<String>>,
202    },
203    /// User-defined table source (extension point).
204    Custom(Box<dyn CustomTableSource>),
205}
206
207// ---------------------------------------------------------------------------
208// TABLESAMPLE
209// ---------------------------------------------------------------------------
210
211/// TABLESAMPLE / SAMPLE clause.
212#[derive(Debug, Clone)]
213pub struct TableSampleDef {
214    /// Sampling method: BERNOULLI, SYSTEM, BLOCK (Oracle).
215    pub method: SampleMethod,
216    /// Sample percentage (0.0 - 100.0).
217    pub percentage: f64,
218    /// REPEATABLE / SEED value for reproducible sampling.
219    pub seed: Option<i64>,
220}
221
222/// Sampling method.
223#[derive(Debug, Clone, Copy, PartialEq, Eq)]
224pub enum SampleMethod {
225    /// Row-level random sampling (PG, SQL Server).
226    Bernoulli,
227    /// Page/block-level random sampling (PG, SQL Server).
228    System,
229    /// Block-level sampling (Oracle: SAMPLE BLOCK).
230    Block,
231}
232
233// ---------------------------------------------------------------------------
234// SQLite index hints
235// ---------------------------------------------------------------------------
236
237/// SQLite-specific index hints in FROM.
238#[derive(Debug, Clone)]
239pub enum SqliteIndexHint {
240    /// INDEXED BY index_name.
241    IndexedBy(String),
242    /// NOT INDEXED.
243    NotIndexed,
244}
245
246// ---------------------------------------------------------------------------
247// JOIN
248// ---------------------------------------------------------------------------
249
250/// JOIN definition.
251#[derive(Debug, Clone)]
252pub struct JoinDef {
253    pub source: FromItem,
254    pub condition: Option<JoinCondition>,
255    pub join_type: JoinType,
256    pub natural: bool,
257}
258
259impl JoinDef {
260    pub fn inner(source: FromItem, on: Conditions) -> Self {
261        Self {
262            source,
263            condition: Some(JoinCondition::On(on)),
264            join_type: JoinType::Inner,
265            natural: false,
266        }
267    }
268
269    pub fn left(source: FromItem, on: Conditions) -> Self {
270        Self {
271            source,
272            condition: Some(JoinCondition::On(on)),
273            join_type: JoinType::Left,
274            natural: false,
275        }
276    }
277
278    pub fn right(source: FromItem, on: Conditions) -> Self {
279        Self {
280            source,
281            condition: Some(JoinCondition::On(on)),
282            join_type: JoinType::Right,
283            natural: false,
284        }
285    }
286
287    pub fn full(source: FromItem, on: Conditions) -> Self {
288        Self {
289            source,
290            condition: Some(JoinCondition::On(on)),
291            join_type: JoinType::Full,
292            natural: false,
293        }
294    }
295
296    pub fn cross(source: FromItem) -> Self {
297        Self {
298            source,
299            condition: None,
300            join_type: JoinType::Cross,
301            natural: false,
302        }
303    }
304
305    pub fn using(join_type: JoinType, source: FromItem, columns: Vec<String>) -> Self {
306        Self {
307            source,
308            condition: Some(JoinCondition::Using(columns)),
309            join_type,
310            natural: false,
311        }
312    }
313
314    pub fn natural(mut self) -> Self {
315        self.natural = true;
316        self
317    }
318}
319
320/// JOIN condition.
321#[derive(Debug, Clone)]
322pub enum JoinCondition {
323    /// ON condition.
324    On(Conditions),
325    /// USING (col1, col2, ...).
326    Using(Vec<String>),
327}
328
329/// Types of JOIN.
330#[derive(Debug, Clone, Copy, PartialEq, Eq)]
331pub enum JoinType {
332    Inner,
333    Left,
334    Right,
335    Full,
336    Cross,
337    /// SQL Server / Oracle: CROSS APPLY (equivalent to INNER LATERAL JOIN).
338    CrossApply,
339    /// SQL Server / Oracle: OUTER APPLY (equivalent to LEFT LATERAL JOIN).
340    OuterApply,
341}
342
343// ---------------------------------------------------------------------------
344// Subquery
345// ---------------------------------------------------------------------------
346
347/// Subquery with alias.
348#[derive(Debug, Clone)]
349pub struct SubQueryDef {
350    pub query: Box<QueryStmt>,
351    pub alias: String,
352}
353
354// ---------------------------------------------------------------------------
355// Set operations (UNION / INTERSECT / EXCEPT)
356// ---------------------------------------------------------------------------
357
358/// Set operation (UNION, INTERSECT, EXCEPT).
359#[derive(Debug, Clone)]
360pub struct SetOpDef {
361    pub left: Box<QueryStmt>,
362    pub right: Box<QueryStmt>,
363    pub operation: SetOperationType,
364}
365
366impl SetOpDef {
367    pub fn union(left: QueryStmt, right: QueryStmt) -> Self {
368        Self {
369            left: Box::new(left),
370            right: Box::new(right),
371            operation: SetOperationType::Union,
372        }
373    }
374
375    pub fn union_all(left: QueryStmt, right: QueryStmt) -> Self {
376        Self {
377            left: Box::new(left),
378            right: Box::new(right),
379            operation: SetOperationType::UnionAll,
380        }
381    }
382
383    pub fn intersect(left: QueryStmt, right: QueryStmt) -> Self {
384        Self {
385            left: Box::new(left),
386            right: Box::new(right),
387            operation: SetOperationType::Intersect,
388        }
389    }
390
391    pub fn except(left: QueryStmt, right: QueryStmt) -> Self {
392        Self {
393            left: Box::new(left),
394            right: Box::new(right),
395            operation: SetOperationType::Except,
396        }
397    }
398}
399
400/// Set operation types.
401#[derive(Debug, Clone, Copy, PartialEq, Eq)]
402pub enum SetOperationType {
403    Union,
404    UnionAll,
405    Intersect,
406    IntersectAll,
407    Except,
408    ExceptAll,
409}
410
411// ---------------------------------------------------------------------------
412// GROUP BY
413// ---------------------------------------------------------------------------
414
415/// GROUP BY element.
416#[derive(Debug, Clone)]
417pub enum GroupByItem {
418    /// Simple expression: `GROUP BY col1, col2`.
419    Expr(Expr),
420    /// ROLLUP(a, b) or MySQL `WITH ROLLUP`.
421    Rollup(Vec<Expr>),
422    /// CUBE(a, b).
423    Cube(Vec<Expr>),
424    /// GROUPING SETS ((a, b), (a), ()).
425    GroupingSets(Vec<Vec<Expr>>),
426}
427
428// ---------------------------------------------------------------------------
429// WINDOW clause (named windows)
430// ---------------------------------------------------------------------------
431
432/// Named window definition in the WINDOW clause.
433#[derive(Debug, Clone)]
434pub struct WindowNameDef {
435    pub name: String,
436    /// Optional base window name for inheritance: `WINDOW w2 AS (w1 ORDER BY y)`.
437    pub base_window: Option<String>,
438    pub partition_by: Option<Vec<Expr>>,
439    pub order_by: Option<Vec<OrderByDef>>,
440    pub frame: Option<super::expr::WindowFrameDef>,
441}
442
443// ---------------------------------------------------------------------------
444// LIMIT / OFFSET / FETCH / TOP
445// ---------------------------------------------------------------------------
446
447/// Pagination definition.
448#[derive(Debug, Clone)]
449pub struct LimitDef {
450    pub kind: LimitKind,
451    pub offset: Option<u64>,
452}
453
454impl LimitDef {
455    pub fn limit(count: u64) -> Self {
456        Self {
457            kind: LimitKind::Limit(count),
458            offset: None,
459        }
460    }
461
462    pub fn limit_offset(count: u64, offset: u64) -> Self {
463        Self {
464            kind: LimitKind::Limit(count),
465            offset: Some(offset),
466        }
467    }
468
469    pub fn fetch_first(count: u64) -> Self {
470        Self {
471            kind: LimitKind::FetchFirst {
472                count,
473                with_ties: false,
474                percent: false,
475            },
476            offset: None,
477        }
478    }
479
480    pub fn fetch_first_with_ties(count: u64) -> Self {
481        Self {
482            kind: LimitKind::FetchFirst {
483                count,
484                with_ties: true,
485                percent: false,
486            },
487            offset: None,
488        }
489    }
490
491    pub fn top(count: u64) -> Self {
492        Self {
493            kind: LimitKind::Top {
494                count,
495                with_ties: false,
496                percent: false,
497            },
498            offset: None,
499        }
500    }
501
502    pub fn offset(mut self, offset: u64) -> Self {
503        self.offset = Some(offset);
504        self
505    }
506}
507
508/// The type of row limiting.
509#[derive(Debug, Clone)]
510pub enum LimitKind {
511    /// LIMIT n (PG, SQLite, MySQL).
512    Limit(u64),
513    /// FETCH FIRST n ROWS { ONLY | WITH TIES } (PG, Oracle, SQL Server).
514    FetchFirst {
515        count: u64,
516        with_ties: bool,
517        /// Oracle: FETCH FIRST n PERCENT ROWS.
518        percent: bool,
519    },
520    /// SQL Server: TOP(n) [PERCENT] [WITH TIES].
521    Top {
522        count: u64,
523        with_ties: bool,
524        percent: bool,
525    },
526}
527
528// ---------------------------------------------------------------------------
529// Common Table Expressions (WITH clause)
530// ---------------------------------------------------------------------------
531
532/// Common Table Expression (WITH clause).
533#[derive(Debug, Clone)]
534pub struct CteDef {
535    pub name: String,
536    pub query: Box<QueryStmt>,
537    pub recursive: bool,
538    /// Explicit column names: `WITH cte(a, b) AS (...)`.
539    pub column_names: Option<Vec<String>>,
540    /// PG: MATERIALIZED / NOT MATERIALIZED hint.
541    pub materialized: Option<CteMaterialized>,
542}
543
544impl CteDef {
545    pub fn new(name: impl Into<String>, query: QueryStmt) -> Self {
546        Self {
547            name: name.into(),
548            query: Box::new(query),
549            recursive: false,
550            column_names: None,
551            materialized: None,
552        }
553    }
554
555    pub fn recursive(name: impl Into<String>, query: QueryStmt) -> Self {
556        Self {
557            name: name.into(),
558            query: Box::new(query),
559            recursive: true,
560            column_names: None,
561            materialized: None,
562        }
563    }
564
565    pub fn columns(mut self, cols: Vec<&str>) -> Self {
566        self.column_names = Some(cols.into_iter().map(String::from).collect());
567        self
568    }
569
570    pub fn materialized(mut self) -> Self {
571        self.materialized = Some(CteMaterialized::Materialized);
572        self
573    }
574
575    pub fn not_materialized(mut self) -> Self {
576        self.materialized = Some(CteMaterialized::NotMaterialized);
577        self
578    }
579}
580
581/// CTE materialization hint (PostgreSQL).
582#[derive(Debug, Clone, Copy, PartialEq, Eq)]
583pub enum CteMaterialized {
584    Materialized,
585    NotMaterialized,
586}
587
588// ---------------------------------------------------------------------------
589// SELECT ... FOR UPDATE / SHARE (row locking)
590// ---------------------------------------------------------------------------
591
592/// SELECT ... FOR UPDATE / SHARE.
593#[derive(Debug, Clone)]
594pub struct SelectLockDef {
595    pub strength: LockStrength,
596    pub of: Option<Vec<SchemaRef>>,
597    pub nowait: bool,
598    pub skip_locked: bool,
599    /// Oracle: FOR UPDATE WAIT N seconds.
600    pub wait: Option<u64>,
601}
602
603/// Lock strength.
604#[derive(Debug, Clone, Copy, PartialEq, Eq)]
605pub enum LockStrength {
606    Update,
607    /// PG: FOR NO KEY UPDATE.
608    NoKeyUpdate,
609    Share,
610    /// PG: FOR KEY SHARE.
611    KeyShare,
612}