Skip to main content

qail_core/ast/cmd/
mod.rs

1use crate::ast::{
2    Action, Cage, Condition, Distance, Expr, GroupByMode, IndexDef, Join, LockMode, OverridingKind,
3    SampleMethod, SetOp, TableConstraint,
4};
5
6/// The core Qail AST node representing a single database operation.
7#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
8pub struct Qail {
9    /// SQL action to perform.
10    pub action: Action,
11    /// Target table name.
12    pub table: String,
13    /// Selected / inserted / modified columns.
14    pub columns: Vec<Expr>,
15    /// Join clauses.
16    pub joins: Vec<Join>,
17    /// Filter / sort / group / limit cages.
18    pub cages: Vec<Cage>,
19    /// SELECT DISTINCT.
20    pub distinct: bool,
21    /// Index definition for CREATE INDEX.
22    pub index_def: Option<IndexDef>,
23    /// Table-level constraints (composite UNIQUE / PK).
24    pub table_constraints: Vec<TableConstraint>,
25    /// UNION / INTERSECT / EXCEPT operations.
26    pub set_ops: Vec<(SetOp, Box<Qail>)>,
27    /// HAVING clause conditions.
28    pub having: Vec<Condition>,
29    /// GROUP BY mode (simple, rollup, cube, grouping sets).
30    pub group_by_mode: GroupByMode,
31    /// Common table expressions (WITH).
32    pub ctes: Vec<CTEDef>,
33    /// DISTINCT ON columns.
34    pub distinct_on: Vec<Expr>,
35    /// RETURNING clause.
36    pub returning: Option<Vec<Expr>>,
37    /// ON CONFLICT clause for upsert.
38    pub on_conflict: Option<OnConflict>,
39    /// PostgreSQL MERGE specification.
40    #[serde(default)]
41    pub merge: Option<Merge>,
42    /// INSERT … SELECT source query.
43    pub source_query: Option<Box<Qail>>,
44    /// LISTEN/NOTIFY channel.
45    pub channel: Option<String>,
46    /// NOTIFY payload.
47    pub payload: Option<String>,
48    /// SAVEPOINT name.
49    pub savepoint_name: Option<String>,
50    /// UPDATE … FROM additional tables.
51    pub from_tables: Vec<String>,
52    /// DELETE … USING additional tables.
53    pub using_tables: Vec<String>,
54    /// Row locking (FOR UPDATE / FOR SHARE).
55    pub lock_mode: Option<LockMode>,
56    /// SKIP LOCKED modifier for row locking (FOR UPDATE SKIP LOCKED).
57    pub skip_locked: bool,
58    /// FETCH FIRST n ROWS [ONLY|WITH TIES].
59    pub fetch: Option<(u64, bool)>,
60    /// INSERT with DEFAULT VALUES.
61    pub default_values: bool,
62    /// OVERRIDING clause for generated columns.
63    pub overriding: Option<OverridingKind>,
64    /// TABLESAMPLE method, percentage, and optional seed.
65    pub sample: Option<(SampleMethod, f64, Option<u64>)>,
66    /// SELECT FROM ONLY (exclude inheritance).
67    pub only_table: bool,
68    // Vector database fields (Qdrant)
69    /// Search vector for similarity queries.
70    pub vector: Option<Vec<f32>>,
71    /// Minimum score threshold.
72    pub score_threshold: Option<f32>,
73    /// Named vector in multi-vector collections.
74    pub vector_name: Option<String>,
75    /// Include vector data in results.
76    pub with_vector: bool,
77    /// Vector dimensionality.
78    pub vector_size: Option<u64>,
79    /// Distance metric.
80    pub distance: Option<Distance>,
81    /// Store vectors on disk.
82    pub on_disk: Option<bool>,
83    // PostgreSQL procedural objects
84    /// Function definition.
85    pub function_def: Option<crate::ast::FunctionDef>,
86    /// Trigger definition.
87    pub trigger_def: Option<crate::ast::TriggerDef>,
88    /// RLS policy definition.
89    pub policy_def: Option<crate::migrate::policy::RlsPolicy>,
90    /// `CREATE VIEW … WITH (security_invoker = true)`.
91    ///
92    /// Postgres evaluates a plain view against its base tables with the VIEW
93    /// OWNER's privileges, so row-level security on those tables is checked as
94    /// the owner rather than the caller — a view over an RLS-protected table is
95    /// an RLS bypass unless this is set. Only meaningful for [`Action::CreateView`].
96    #[serde(default)]
97    pub view_security_invoker: bool,
98}
99
100/// Common Table Expression (WITH clause) definition.
101#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
102pub struct CTEDef {
103    /// Alias name used to reference this CTE elsewhere in the query.
104    pub name: String,
105    /// Whether this is a recursive CTE.
106    pub recursive: bool,
107    /// Explicit column list.
108    pub columns: Vec<String>,
109    /// The base query.
110    pub base_query: Box<Qail>,
111    /// Recursive part (UNION ALL).
112    pub recursive_query: Option<Box<Qail>>,
113    /// Source table for data-modifying CTEs.
114    pub source_table: Option<String>,
115}
116
117/// ON CONFLICT clause for upsert.
118#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
119pub struct OnConflict {
120    /// Conflict target columns.
121    pub columns: Vec<String>,
122    /// What to do on conflict.
123    pub action: ConflictAction,
124    /// `DO UPDATE ... WHERE <conditions>` — predicates over the EXISTING row.
125    ///
126    /// This is how RLS scoping reaches the update arm of an upsert: the
127    /// insert payload is stamped with the scope, and the conflicting row
128    /// must satisfy the same scope or the update is skipped. Ignored for
129    /// `DO NOTHING`.
130    #[serde(default)]
131    pub where_conditions: Vec<Condition>,
132}
133
134/// Action to take on an INSERT conflict.
135#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
136pub enum ConflictAction {
137    /// DO NOTHING.
138    DoNothing,
139    /// DO UPDATE SET.
140    DoUpdate {
141        /// Column = expression assignments.
142        assignments: Vec<(String, Expr)>,
143    },
144}
145
146/// PostgreSQL `MERGE` specification.
147#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
148pub struct Merge {
149    /// Optional target table alias.
150    pub target_alias: Option<String>,
151    /// `USING` data source.
152    pub source: MergeSource,
153    /// `ON` join conditions.
154    pub on: Vec<Condition>,
155    /// Ordered `WHEN ... THEN ...` clauses.
156    pub clauses: Vec<MergeClause>,
157}
158
159/// PostgreSQL `MERGE USING` source.
160#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
161pub enum MergeSource {
162    /// Table or view source.
163    Table {
164        /// Source relation name.
165        name: String,
166        /// Optional source alias.
167        alias: Option<String>,
168    },
169    /// Subquery source.
170    Query {
171        /// Source query.
172        query: Box<Qail>,
173        /// Optional source alias.
174        alias: Option<String>,
175    },
176}
177
178/// One ordered PostgreSQL `MERGE WHEN` clause.
179#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
180pub struct MergeClause {
181    /// Match class for the candidate row.
182    pub match_kind: MergeMatchKind,
183    /// Optional `AND` conditions after the match class.
184    pub condition: Vec<Condition>,
185    /// Action to execute for this clause.
186    pub action: MergeAction,
187}
188
189/// PostgreSQL `MERGE WHEN` match class.
190#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
191pub enum MergeMatchKind {
192    /// `WHEN MATCHED`.
193    Matched,
194    /// `WHEN NOT MATCHED [BY TARGET]`.
195    NotMatchedByTarget,
196    /// `WHEN NOT MATCHED BY SOURCE`.
197    NotMatchedBySource,
198}
199
200/// PostgreSQL `MERGE THEN` action.
201#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
202pub enum MergeAction {
203    /// `UPDATE SET ...`.
204    Update {
205        /// Column = expression assignments.
206        assignments: Vec<(String, Expr)>,
207    },
208    /// `INSERT (...) VALUES (...)`.
209    Insert {
210        /// Optional target columns.
211        columns: Vec<String>,
212        /// Insert value expressions.
213        values: Vec<Expr>,
214    },
215    /// `DELETE`.
216    Delete,
217    /// `DO NOTHING`.
218    DoNothing,
219}
220
221impl Default for OnConflict {
222    fn default() -> Self {
223        Self {
224            columns: vec![],
225            action: ConflictAction::DoNothing,
226            where_conditions: Vec::new(),
227        }
228    }
229}
230
231impl ConflictAction {
232    pub(crate) fn update_assignments(&self) -> Option<&[(String, Expr)]> {
233        match self {
234            Self::DoNothing => None,
235            Self::DoUpdate { assignments } => Some(assignments),
236        }
237    }
238}
239
240impl Default for Qail {
241    fn default() -> Self {
242        Self {
243            action: Action::Get,
244            table: String::new(),
245            columns: vec![],
246            joins: vec![],
247            cages: vec![],
248            distinct: false,
249            index_def: None,
250            table_constraints: vec![],
251            set_ops: vec![],
252            having: vec![],
253            group_by_mode: GroupByMode::Simple,
254            ctes: vec![],
255            distinct_on: vec![],
256            returning: None,
257            on_conflict: None,
258            merge: None,
259            source_query: None,
260            channel: None,
261            payload: None,
262            savepoint_name: None,
263            from_tables: vec![],
264            using_tables: vec![],
265            lock_mode: None,
266            skip_locked: false,
267            fetch: None,
268            default_values: false,
269            overriding: None,
270            sample: None,
271            only_table: false,
272            // Vector database fields
273            vector: None,
274            score_threshold: None,
275            vector_name: None,
276            with_vector: false,
277            vector_size: None,
278            distance: None,
279            on_disk: None,
280            // Procedural objects
281            function_def: None,
282            trigger_def: None,
283            policy_def: None,
284            view_security_invoker: false,
285        }
286    }
287}
288
289// Submodules with builder methods
290mod advanced;
291mod constructors;
292mod cte;
293mod merge;
294mod query;
295mod rls;
296mod vector;
297
298impl std::fmt::Display for Qail {
299    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
300        // Use the Formatter from the fmt module for canonical output
301        use crate::fmt::Formatter;
302        match Formatter::new().format(self) {
303            Ok(s) => write!(f, "{}", s),
304            Err(_) => write!(f, "{:?}", self), // Fallback to Debug
305        }
306    }
307}