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