Skip to main content

reddb_server/storage/query/
core.rs

1use std::fmt;
2
3use super::builders::{GraphQueryBuilder, PathQueryBuilder, TableQueryBuilder};
4use crate::catalog::CollectionModel;
5pub use crate::storage::engine::distance::DistanceMetric;
6pub use crate::storage::engine::vector_metadata::MetadataFilter;
7pub use crate::storage::queue::QueueMode;
8use crate::storage::schema::{SqlTypeName, Value};
9
10/// Root query expression
11#[derive(Debug, Clone)]
12#[allow(clippy::large_enum_variant)]
13pub enum QueryExpr {
14    /// Pure table query: SELECT ... FROM ...
15    Table(TableQuery),
16    /// Pure graph query: MATCH ... RETURN ...
17    Graph(GraphQuery),
18    /// Join between table and graph
19    Join(JoinQuery),
20    /// Path query: PATH FROM ... TO ...
21    Path(PathQuery),
22    /// Vector similarity search
23    Vector(VectorQuery),
24    /// Hybrid query combining structured and vector search
25    Hybrid(HybridQuery),
26    /// INSERT INTO table (cols) VALUES (vals)
27    Insert(InsertQuery),
28    /// UPDATE table SET col=val WHERE filter
29    Update(UpdateQuery),
30    /// DELETE FROM table WHERE filter
31    Delete(DeleteQuery),
32    /// CREATE TABLE name (columns)
33    CreateTable(CreateTableQuery),
34    /// DROP TABLE name
35    DropTable(DropTableQuery),
36    /// DROP GRAPH name
37    DropGraph(DropGraphQuery),
38    /// DROP VECTOR name
39    DropVector(DropVectorQuery),
40    /// DROP DOCUMENT name
41    DropDocument(DropDocumentQuery),
42    /// DROP KV name
43    DropKv(DropKvQuery),
44    /// DROP COLLECTION name
45    DropCollection(DropCollectionQuery),
46    /// TRUNCATE [model] name
47    Truncate(TruncateQuery),
48    /// ALTER TABLE name ADD/DROP/RENAME COLUMN
49    AlterTable(AlterTableQuery),
50    /// GRAPH subcommand (NEIGHBORHOOD, SHORTEST_PATH, etc.)
51    GraphCommand(GraphCommand),
52    /// SEARCH subcommand (SIMILAR, TEXT, HYBRID)
53    SearchCommand(SearchCommand),
54    /// ASK 'question' — RAG query with LLM synthesis
55    Ask(AskQuery),
56    /// CREATE INDEX name ON table (columns) USING type
57    CreateIndex(CreateIndexQuery),
58    /// DROP INDEX name ON table
59    DropIndex(DropIndexQuery),
60    /// Probabilistic data structure commands (HLL, SKETCH, FILTER)
61    ProbabilisticCommand(ProbabilisticCommand),
62    /// CREATE TIMESERIES name [RETENTION duration] [CHUNK_SIZE n]
63    CreateTimeSeries(CreateTimeSeriesQuery),
64    /// DROP TIMESERIES name
65    DropTimeSeries(DropTimeSeriesQuery),
66    /// CREATE QUEUE name [MAX_SIZE n] [PRIORITY] [WITH TTL duration]
67    CreateQueue(CreateQueueQuery),
68    /// ALTER QUEUE name SET MODE [FANOUT|WORK]
69    AlterQueue(AlterQueueQuery),
70    /// DROP QUEUE name
71    DropQueue(DropQueueQuery),
72    /// Read-only queue projection: SELECT ... FROM QUEUE name
73    QueueSelect(QueueSelectQuery),
74    /// QUEUE subcommand (PUSH, POP, PEEK, LEN, PURGE, GROUP, READ, ACK, NACK)
75    QueueCommand(QueueCommand),
76    /// KV subcommand (PUT, GET, DELETE)
77    KvCommand(KvCommand),
78    /// CONFIG keyed command (PUT, GET, ROTATE, DELETE, HISTORY)
79    ConfigCommand(ConfigCommand),
80    /// CREATE TREE name IN collection ROOT ... MAX_CHILDREN n
81    CreateTree(CreateTreeQuery),
82    /// DROP TREE name IN collection
83    DropTree(DropTreeQuery),
84    /// TREE subcommand (INSERT, MOVE, DELETE, VALIDATE, REBALANCE)
85    TreeCommand(TreeCommand),
86    /// SET CONFIG key = value
87    SetConfig { key: String, value: Value },
88    /// SHOW CONFIG [prefix]
89    ShowConfig { prefix: Option<String> },
90    /// SET SECRET key = value
91    SetSecret { key: String, value: Value },
92    /// DELETE SECRET key
93    DeleteSecret { key: String },
94    /// SHOW SECRET[S] [prefix]
95    ShowSecrets { prefix: Option<String> },
96    /// `SET TENANT 'id'` / `SET TENANT = 'id'` / `RESET TENANT`
97    ///
98    /// Session-scoped multi-tenancy handle. Populates a per-connection
99    /// thread-local that `CURRENT_TENANT()` reads and that RLS
100    /// policies combine with via `USING (tenant_id = CURRENT_TENANT())`.
101    /// `None` clears the current tenant (RESET TENANT or SET TENANT
102    /// NULL). Unlike `SetConfig` this is *not* persisted to red_config —
103    /// it lives for the connection's lifetime only.
104    SetTenant(Option<String>),
105    /// `SHOW TENANT` — returns the thread-local tenant id (or NULL).
106    ShowTenant,
107    /// EXPLAIN ALTER FOR CREATE TABLE name (...) [FORMAT JSON]
108    ///
109    /// Pure read command that diffs the embedded `CREATE TABLE`
110    /// statement against the live `CollectionContract` of the
111    /// table with the same name and returns the `ALTER TABLE`
112    /// operations that would close the gap. Never executes
113    /// anything — output is text (default) or JSON depending on
114    /// the optional `FORMAT JSON` suffix. Powers the Purple
115    /// framework's migration generator and any other client that
116    /// wants reddb to own the schema-diff rules.
117    ExplainAlter(ExplainAlterQuery),
118    /// CREATE MIGRATION name [DEPENDS ON dep1, dep2] [BATCH n ROWS] [NO ROLLBACK] body
119    CreateMigration(CreateMigrationQuery),
120    /// APPLY MIGRATION name | APPLY MIGRATION *  [FOR TENANT id]
121    ApplyMigration(ApplyMigrationQuery),
122    /// ROLLBACK MIGRATION name
123    RollbackMigration(RollbackMigrationQuery),
124    /// EXPLAIN MIGRATION name
125    ExplainMigration(ExplainMigrationQuery),
126    /// `EVENTS BACKFILL collection [WHERE pred] TO queue [LIMIT n]`.
127    EventsBackfill(EventsBackfillQuery),
128    /// `EVENTS BACKFILL STATUS collection` placeholder for the status slice.
129    EventsBackfillStatus { collection: String },
130    /// Transaction control: BEGIN, COMMIT, ROLLBACK, SAVEPOINT, RELEASE, ROLLBACK TO.
131    ///
132    /// Phase 1.1 (PG parity): parser + dispatch are wired so clients (psql, JDBC, etc.)
133    /// can issue these statements without errors. Real isolation/atomicity semantics
134    /// arrive with Phase 2.3 MVCC. Until then statements behave as autocommit (each
135    /// DML is its own transaction); BEGIN/COMMIT/ROLLBACK return success but do NOT
136    /// provide rollback-on-failure guarantees across multiple statements.
137    TransactionControl(TxnControl),
138    /// Maintenance commands: VACUUM [FULL] [table], ANALYZE [table].
139    ///
140    /// Phase 1.2 (PG parity): `VACUUM` triggers segment/page flush + planner stats
141    /// refresh. `ANALYZE` refreshes planner statistics (histograms, null counts,
142    /// distinct estimates). Both accept an optional table target; omitting the
143    /// target iterates every collection.
144    MaintenanceCommand(MaintenanceCommand),
145    /// `CREATE SCHEMA [IF NOT EXISTS] name`
146    ///
147    /// Phase 1.3 (PG parity): schemas are logical namespaces stored in
148    /// `red_config` under the key `schema.{name}`. Tables created inside a
149    /// schema use `schema.table` qualified names (collection name = "schema.table").
150    CreateSchema(CreateSchemaQuery),
151    /// `DROP SCHEMA [IF EXISTS] name [CASCADE]`
152    DropSchema(DropSchemaQuery),
153    /// `CREATE SEQUENCE [IF NOT EXISTS] name [START [WITH] n] [INCREMENT [BY] n]`
154    ///
155    /// Phase 1.3 (PG parity): sequences are 64-bit monotonic counters persisted
156    /// in `red_config` under the key `sequence.{name}`. Values are produced by
157    /// the scalar functions `nextval('name')` and `currval('name')`.
158    CreateSequence(CreateSequenceQuery),
159    /// `DROP SEQUENCE [IF EXISTS] name`
160    DropSequence(DropSequenceQuery),
161    /// `COPY table FROM 'path' [WITH ...]` — CSV import (Phase 1.5 PG parity).
162    ///
163    /// Supported options: `DELIMITER c`, `HEADER [true|false]`. Rows stream
164    /// into the named collection via the `CsvImporter`.
165    CopyFrom(CopyFromQuery),
166    /// `CREATE [OR REPLACE] [MATERIALIZED] VIEW [IF NOT EXISTS] name AS SELECT ...`
167    ///
168    /// Phase 2.1 (PG parity): views are stored as `view.{name}` entries in
169    /// `red_config`. Materialized views additionally allocate a slot in the
170    /// shared `MaterializedViewCache`; `REFRESH MATERIALIZED VIEW` re-runs
171    /// the underlying query and repopulates the cache.
172    CreateView(CreateViewQuery),
173    /// `DROP [MATERIALIZED] VIEW [IF EXISTS] name`
174    DropView(DropViewQuery),
175    /// `REFRESH MATERIALIZED VIEW name`
176    ///
177    /// Re-executes the view's query and writes the result into the cache.
178    RefreshMaterializedView(RefreshMaterializedViewQuery),
179    /// `CREATE POLICY name ON table [FOR action] [TO role] USING (filter)`
180    ///
181    /// Phase 2.5 (PG parity): row-level security policy definition.
182    /// Evaluated at read time — when the table has RLS enabled, all
183    /// matching policies for the current role are combined with OR and
184    /// AND-ed into the query's WHERE clause.
185    CreatePolicy(CreatePolicyQuery),
186    /// `DROP POLICY [IF EXISTS] name ON table`
187    DropPolicy(DropPolicyQuery),
188    /// `CREATE SERVER name FOREIGN DATA WRAPPER kind OPTIONS (...)`
189    /// (Phase 3.2 PG parity). Registers a named foreign-data-wrapper
190    /// instance in the runtime's `ForeignTableRegistry`.
191    CreateServer(CreateServerQuery),
192    /// `DROP SERVER [IF EXISTS] name [CASCADE]`
193    DropServer(DropServerQuery),
194    /// `CREATE FOREIGN TABLE name (cols) SERVER srv OPTIONS (...)`
195    /// (Phase 3.2 PG parity). Makes `name` resolvable as a foreign table
196    /// via the parent server's `ForeignDataWrapper`.
197    CreateForeignTable(CreateForeignTableQuery),
198    /// `DROP FOREIGN TABLE [IF EXISTS] name`
199    DropForeignTable(DropForeignTableQuery),
200    /// `GRANT { actions | ALL [PRIVILEGES] }
201    ///   ON { TABLE | SCHEMA | DATABASE | FUNCTION } object_list
202    ///   TO grant_principal_list
203    ///   [WITH GRANT OPTION]`
204    ///
205    /// Granular RBAC primitive layered on top of the legacy 3-role model.
206    /// See `crate::auth::privileges` for the resolution algorithm.
207    Grant(GrantStmt),
208    /// `REVOKE [GRANT OPTION FOR] { actions | ALL } ON … FROM …`
209    Revoke(RevokeStmt),
210    /// `ALTER USER name [VALID UNTIL 'ts'] [CONNECTION LIMIT n]
211    ///   [ENABLE | DISABLE] [SET search_path = ...]`
212    AlterUser(AlterUserStmt),
213    // ----- IAM policy DDL (Agent #28 / IAM kernel integration) -----
214    /// `CREATE POLICY '<id>' AS '<json>'` — installs an IAM policy
215    /// document in the AuthStore. Distinct from the RLS-flavoured
216    /// `CreatePolicy(CreatePolicyQuery)` above (which uses
217    /// `CREATE POLICY name ON table ...`); the parser disambiguates
218    /// at parse time by inspecting the token after the policy name.
219    CreateIamPolicy { id: String, json: String },
220    /// `DROP POLICY '<id>'` — removes an IAM policy and its
221    /// attachments.
222    DropIamPolicy { id: String },
223    /// `ATTACH POLICY '<id>' TO USER <name>` /
224    /// `ATTACH POLICY '<id>' TO GROUP <name>`.
225    AttachPolicy {
226        policy_id: String,
227        principal: PolicyPrincipalRef,
228    },
229    /// `DETACH POLICY '<id>' FROM USER <name>` /
230    /// `DETACH POLICY '<id>' FROM GROUP <name>`.
231    DetachPolicy {
232        policy_id: String,
233        principal: PolicyPrincipalRef,
234    },
235    /// `SHOW POLICIES [FOR USER <name> | FOR GROUP <name>]`.
236    ShowPolicies { filter: Option<PolicyPrincipalRef> },
237    /// `SHOW EFFECTIVE PERMISSIONS FOR <name> [ON <kind>:<name>]`.
238    ShowEffectivePermissions {
239        user: PolicyUserRef,
240        resource: Option<PolicyResourceRef>,
241    },
242    /// `SIMULATE <name> ACTION <verb> ON <kind>:<name>`.
243    SimulatePolicy {
244        user: PolicyUserRef,
245        action: String,
246        resource: PolicyResourceRef,
247    },
248}
249
250/// Tenant-qualified user reference for IAM policy SQL DDL.
251#[derive(Debug, Clone, PartialEq, Eq)]
252pub struct PolicyUserRef {
253    pub tenant: Option<String>,
254    pub username: String,
255}
256
257/// Resource reference (`<kind>:<name>`) used in `SIMULATE` /
258/// `SHOW EFFECTIVE PERMISSIONS`.
259#[derive(Debug, Clone, PartialEq, Eq)]
260pub struct PolicyResourceRef {
261    pub kind: String,
262    pub name: String,
263}
264
265/// Principal target for ATTACH / DETACH / SHOW POLICIES filter.
266#[derive(Debug, Clone, PartialEq, Eq)]
267pub enum PolicyPrincipalRef {
268    User(PolicyUserRef),
269    Group(String),
270}
271
272// ---------------------------------------------------------------------------
273// GRANT / REVOKE / ALTER USER AST
274// ---------------------------------------------------------------------------
275
276/// Object class targeted by a GRANT/REVOKE.
277#[derive(Debug, Clone, PartialEq, Eq)]
278pub enum GrantObjectKind {
279    Table,
280    Schema,
281    Database,
282    Function,
283}
284
285/// One target object in a `GRANT ... ON ... <object_list>` clause.
286///
287/// `name` follows the parser's standard `[schema.]object` shape; the
288/// optional `schema` is only populated for `Table` / `Function` and
289/// stays `None` when the user wrote a bare identifier.
290#[derive(Debug, Clone)]
291pub struct GrantObject {
292    pub schema: Option<String>,
293    pub name: String,
294}
295
296/// Principal target of a GRANT (i.e. the recipient).
297#[derive(Debug, Clone)]
298pub enum GrantPrincipalRef {
299    /// `TO username` — username may include an `@tenant` suffix.
300    User {
301        tenant: Option<String>,
302        name: String,
303    },
304    /// `TO PUBLIC`.
305    Public,
306    /// `TO GROUP groupname` (parsed today, enforcement deferred).
307    Group(String),
308}
309
310/// `GRANT` statement AST.
311#[derive(Debug, Clone)]
312pub struct GrantStmt {
313    /// Privilege keywords as the user typed them, normalised to upper
314    /// case. Matches the `Action` set in `crate::auth::privileges`. An
315    /// empty list together with `all = true` represents `ALL [PRIVILEGES]`.
316    pub actions: Vec<String>,
317    /// Optional column list — populates the AST for column-level
318    /// grants but enforcement is deferred (stretch goal).
319    pub columns: Option<Vec<String>>,
320    pub object_kind: GrantObjectKind,
321    pub objects: Vec<GrantObject>,
322    pub principals: Vec<GrantPrincipalRef>,
323    pub with_grant_option: bool,
324    /// `true` when the privilege list was `ALL [PRIVILEGES]`.
325    pub all: bool,
326}
327
328/// `REVOKE` statement AST.
329#[derive(Debug, Clone)]
330pub struct RevokeStmt {
331    pub actions: Vec<String>,
332    pub columns: Option<Vec<String>>,
333    pub object_kind: GrantObjectKind,
334    pub objects: Vec<GrantObject>,
335    pub principals: Vec<GrantPrincipalRef>,
336    /// `REVOKE GRANT OPTION FOR ...` — strips just the grant option,
337    /// keeping the underlying privilege.
338    pub grant_option_for: bool,
339    pub all: bool,
340}
341
342/// One attribute setting under `ALTER USER`.
343#[derive(Debug, Clone)]
344pub enum AlterUserAttribute {
345    ValidUntil(String),
346    ConnectionLimit(i64),
347    Enable,
348    Disable,
349    SetSearchPath(String),
350    AddGroup(String),
351    DropGroup(String),
352    /// Reset password (carry the new plaintext until the runtime
353    /// hands it to AuthStore::change_password). Out of scope for the
354    /// initial milestone — present so the parser can accept the
355    /// keyword without a follow-up grammar change.
356    Password(String),
357}
358
359/// `ALTER USER` statement AST.
360#[derive(Debug, Clone)]
361pub struct AlterUserStmt {
362    pub tenant: Option<String>,
363    pub username: String,
364    pub attributes: Vec<AlterUserAttribute>,
365}
366
367#[derive(Debug, Clone)]
368pub struct CreateServerQuery {
369    pub name: String,
370    /// Wrapper kind declared in `FOREIGN DATA WRAPPER <kind>`.
371    pub wrapper: String,
372    /// Generic `(key 'value', ...)` option bag.
373    pub options: Vec<(String, String)>,
374    pub if_not_exists: bool,
375}
376
377#[derive(Debug, Clone)]
378pub struct DropServerQuery {
379    pub name: String,
380    pub if_exists: bool,
381    pub cascade: bool,
382}
383
384#[derive(Debug, Clone)]
385pub struct CreateForeignTableQuery {
386    pub name: String,
387    pub server: String,
388    pub columns: Vec<ForeignColumnDef>,
389    pub options: Vec<(String, String)>,
390    pub if_not_exists: bool,
391}
392
393#[derive(Debug, Clone)]
394pub struct ForeignColumnDef {
395    pub name: String,
396    pub data_type: String,
397    pub not_null: bool,
398}
399
400#[derive(Debug, Clone)]
401pub struct DropForeignTableQuery {
402    pub name: String,
403    pub if_exists: bool,
404}
405
406/// Row-level security policy definition.
407#[derive(Debug, Clone)]
408pub struct CreatePolicyQuery {
409    pub name: String,
410    pub table: String,
411    /// Which action this policy gates. `None` = `ALL` (applies to all four).
412    pub action: Option<PolicyAction>,
413    /// Role the policy applies to. `None` = all roles.
414    pub role: Option<String>,
415    /// Boolean predicate the row must satisfy.
416    pub using: Box<Filter>,
417    /// Entity kind this policy targets (Phase 2.5.5 RLS universal).
418    /// `CREATE POLICY p ON t ...` defaults to `Table`; writing
419    /// `ON NODES OF g` / `ON VECTORS OF v` / `ON MESSAGES OF q` /
420    /// `ON POINTS OF ts` / `ON EDGES OF g` targets the matching
421    /// non-tabular kind. The evaluator filters polices by kind so
422    /// a graph policy only gates graph reads, vector policy only
423    /// gates vector reads, etc.
424    pub target_kind: PolicyTargetKind,
425}
426
427/// Which flavour of entity a policy governs (Phase 2.5.5).
428#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
429pub enum PolicyTargetKind {
430    Table,
431    Nodes,
432    Edges,
433    Vectors,
434    Messages,
435    Points,
436    Documents,
437}
438
439impl PolicyTargetKind {
440    /// Lowercase identifier for UX — used in messages and the
441    /// `red_config.rls.policies.*` persistence key.
442    pub fn as_ident(&self) -> &'static str {
443        match self {
444            Self::Table => "table",
445            Self::Nodes => "nodes",
446            Self::Edges => "edges",
447            Self::Vectors => "vectors",
448            Self::Messages => "messages",
449            Self::Points => "points",
450            Self::Documents => "documents",
451        }
452    }
453}
454
455#[derive(Debug, Clone, Copy, PartialEq, Eq)]
456pub enum PolicyAction {
457    Select,
458    Insert,
459    Update,
460    Delete,
461}
462
463#[derive(Debug, Clone, PartialEq, Eq)]
464pub struct DropPolicyQuery {
465    pub name: String,
466    pub table: String,
467    pub if_exists: bool,
468}
469
470#[derive(Debug, Clone)]
471pub struct CreateViewQuery {
472    pub name: String,
473    /// Parsed `SELECT ...` body. Stored as a boxed `QueryExpr` so the
474    /// runtime can substitute the tree directly when a query references
475    /// this view (no re-parsing per read).
476    pub query: Box<QueryExpr>,
477    pub materialized: bool,
478    pub if_not_exists: bool,
479    /// `CREATE OR REPLACE VIEW` — overwrites any existing definition.
480    pub or_replace: bool,
481}
482
483#[derive(Debug, Clone, PartialEq, Eq)]
484pub struct DropViewQuery {
485    pub name: String,
486    pub materialized: bool,
487    pub if_exists: bool,
488}
489
490#[derive(Debug, Clone, PartialEq, Eq)]
491pub struct RefreshMaterializedViewQuery {
492    pub name: String,
493}
494
495#[derive(Debug, Clone, PartialEq, Eq)]
496pub struct CopyFromQuery {
497    pub table: String,
498    pub path: String,
499    pub format: CopyFormat,
500    pub delimiter: Option<char>,
501    pub has_header: bool,
502}
503
504#[derive(Debug, Clone, Copy, PartialEq, Eq)]
505pub enum CopyFormat {
506    Csv,
507}
508
509#[derive(Debug, Clone, PartialEq, Eq)]
510pub struct CreateSchemaQuery {
511    pub name: String,
512    pub if_not_exists: bool,
513}
514
515#[derive(Debug, Clone, PartialEq, Eq)]
516pub struct DropSchemaQuery {
517    pub name: String,
518    pub if_exists: bool,
519    pub cascade: bool,
520}
521
522#[derive(Debug, Clone, PartialEq, Eq)]
523pub struct CreateSequenceQuery {
524    pub name: String,
525    pub if_not_exists: bool,
526    /// First value produced by `nextval`. Default 1.
527    pub start: i64,
528    /// Added to the current value on each `nextval`. Default 1.
529    pub increment: i64,
530}
531
532#[derive(Debug, Clone, PartialEq, Eq)]
533pub struct DropSequenceQuery {
534    pub name: String,
535    pub if_exists: bool,
536}
537
538/// Transaction-control statement variants. See [`QueryExpr::TransactionControl`].
539#[derive(Debug, Clone, PartialEq, Eq)]
540pub enum TxnControl {
541    /// `BEGIN [WORK | TRANSACTION]`, `START TRANSACTION`
542    Begin,
543    /// `COMMIT [WORK | TRANSACTION]`, `END`
544    Commit,
545    /// `ROLLBACK [WORK | TRANSACTION]`
546    Rollback,
547    /// `SAVEPOINT name`
548    Savepoint(String),
549    /// `RELEASE [SAVEPOINT] name`
550    ReleaseSavepoint(String),
551    /// `ROLLBACK TO [SAVEPOINT] name`
552    RollbackToSavepoint(String),
553}
554
555/// Maintenance command variants. See [`QueryExpr::MaintenanceCommand`].
556#[derive(Debug, Clone, PartialEq, Eq)]
557pub enum MaintenanceCommand {
558    /// `VACUUM [FULL] [table]`
559    ///
560    /// Triggers segment compaction and planner stats refresh. `FULL` additionally
561    /// forces a full pager sync. Target `None` applies to every collection.
562    Vacuum { target: Option<String>, full: bool },
563    /// `ANALYZE [table]`
564    ///
565    /// Refreshes planner statistics (histogram, distinct estimates, null counts).
566    /// Target `None` re-analyzes every collection.
567    Analyze { target: Option<String> },
568}
569
570/// AST node for `EXPLAIN ALTER FOR <CreateTableStmt> [FORMAT JSON]`.
571///
572/// `target` carries the CREATE TABLE structure exactly as the
573/// parser produces it for a regular CREATE — full reuse of
574/// `parse_create_table_body`. `format` determines whether the
575/// executor emits a `ALTER TABLE …;`-flavored text payload
576/// (the default — copy-paste friendly into the REPL) or a
577/// structured JSON object (machine-friendly).
578#[derive(Debug, Clone)]
579pub struct ExplainAlterQuery {
580    pub target: CreateTableQuery,
581    pub format: ExplainFormat,
582}
583
584/// Output format requested for an `EXPLAIN ALTER` command.
585#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
586pub enum ExplainFormat {
587    /// Plain SQL text — `ALTER TABLE …;` lines plus header
588    /// comments and rename hints. Default; copy-paste friendly.
589    #[default]
590    Sql,
591    /// Structured JSON object with `operations`,
592    /// `rename_candidates`, `summary`. Machine-friendly for
593    /// driver code (Purple migration generator, dashboards,
594    /// CLI tools).
595    Json,
596}
597
598#[derive(Debug, Clone, PartialEq)]
599pub struct CreateMigrationQuery {
600    pub name: String,
601    pub body: String,
602    pub depends_on: Vec<String>,
603    pub batch_size: Option<u64>,
604    pub no_rollback: bool,
605}
606
607#[derive(Debug, Clone, PartialEq)]
608pub struct ApplyMigrationQuery {
609    pub target: ApplyMigrationTarget,
610    pub for_tenant: Option<String>,
611}
612
613#[derive(Debug, Clone, PartialEq)]
614pub enum ApplyMigrationTarget {
615    Named(String),
616    All,
617}
618
619#[derive(Debug, Clone, PartialEq)]
620pub struct RollbackMigrationQuery {
621    pub name: String,
622}
623
624#[derive(Debug, Clone, PartialEq)]
625pub struct ExplainMigrationQuery {
626    pub name: String,
627}
628
629/// Probabilistic data structure commands
630#[derive(Debug, Clone)]
631pub enum ProbabilisticCommand {
632    // HyperLogLog
633    CreateHll {
634        name: String,
635        if_not_exists: bool,
636    },
637    HllAdd {
638        name: String,
639        elements: Vec<String>,
640    },
641    HllCount {
642        names: Vec<String>,
643    },
644    HllMerge {
645        dest: String,
646        sources: Vec<String>,
647    },
648    HllInfo {
649        name: String,
650    },
651    DropHll {
652        name: String,
653        if_exists: bool,
654    },
655
656    // Count-Min Sketch (Fase 7)
657    CreateSketch {
658        name: String,
659        width: usize,
660        depth: usize,
661        if_not_exists: bool,
662    },
663    SketchAdd {
664        name: String,
665        element: String,
666        count: u64,
667    },
668    SketchCount {
669        name: String,
670        element: String,
671    },
672    SketchMerge {
673        dest: String,
674        sources: Vec<String>,
675    },
676    SketchInfo {
677        name: String,
678    },
679    DropSketch {
680        name: String,
681        if_exists: bool,
682    },
683
684    // Cuckoo Filter (Fase 8)
685    CreateFilter {
686        name: String,
687        capacity: usize,
688        if_not_exists: bool,
689    },
690    FilterAdd {
691        name: String,
692        element: String,
693    },
694    FilterCheck {
695        name: String,
696        element: String,
697    },
698    FilterDelete {
699        name: String,
700        element: String,
701    },
702    FilterCount {
703        name: String,
704    },
705    FilterInfo {
706        name: String,
707    },
708    DropFilter {
709        name: String,
710        if_exists: bool,
711    },
712}
713
714/// Index type for CREATE INDEX ... USING <type>
715#[derive(Debug, Clone, PartialEq, Eq)]
716pub enum IndexMethod {
717    BTree,
718    Hash,
719    Bitmap,
720    RTree,
721}
722
723impl fmt::Display for IndexMethod {
724    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
725        match self {
726            Self::BTree => write!(f, "BTREE"),
727            Self::Hash => write!(f, "HASH"),
728            Self::Bitmap => write!(f, "BITMAP"),
729            Self::RTree => write!(f, "RTREE"),
730        }
731    }
732}
733
734/// CREATE INDEX [UNIQUE] [IF NOT EXISTS] name ON table (col1, col2, ...) [USING method]
735#[derive(Debug, Clone)]
736pub struct CreateIndexQuery {
737    pub name: String,
738    pub table: String,
739    pub columns: Vec<String>,
740    pub method: IndexMethod,
741    pub unique: bool,
742    pub if_not_exists: bool,
743}
744
745/// DROP INDEX [IF EXISTS] name ON table
746#[derive(Debug, Clone)]
747pub struct DropIndexQuery {
748    pub name: String,
749    pub table: String,
750    pub if_exists: bool,
751}
752
753/// ASK 'question' [USING provider] [MODEL 'model'] [DEPTH n] [LIMIT n] [COLLECTION col]
754#[derive(Debug, Clone)]
755pub struct AskQuery {
756    pub question: String,
757    pub provider: Option<String>,
758    pub model: Option<String>,
759    pub depth: Option<usize>,
760    pub limit: Option<usize>,
761    pub collection: Option<String>,
762}
763
764impl QueryExpr {
765    /// Create a table query
766    pub fn table(name: &str) -> TableQueryBuilder {
767        TableQueryBuilder::new(name)
768    }
769
770    /// Create a graph query
771    pub fn graph() -> GraphQueryBuilder {
772        GraphQueryBuilder::new()
773    }
774
775    /// Create a path query
776    pub fn path(from: NodeSelector, to: NodeSelector) -> PathQueryBuilder {
777        PathQueryBuilder::new(from, to)
778    }
779}
780
781// ============================================================================
782// Table Query
783// ============================================================================
784
785/// Table query: SELECT columns FROM table WHERE filter ORDER BY ... LIMIT ...
786#[derive(Debug, Clone)]
787pub struct TableQuery {
788    /// Table name. Legacy slot — still populated even when `source`
789    /// is set to a subquery so existing call sites that read
790    /// `query.table.as_str()` keep compiling. When `source` is
791    /// `Some(TableSource::Subquery(…))`, this field holds a synthetic
792    /// sentinel name (`"__subq_NNNN"`) that runtime code must never
793    /// resolve against the real schema registry.
794    pub table: String,
795    /// Fase 2 Week 3: structured table source. `None` means the
796    /// legacy `table` field is authoritative. `Some(Name)` is the
797    /// same information as `table` but in typed form. `Some(Subquery)`
798    /// wires a `(SELECT …) AS alias` in a FROM position — the Fase
799    /// 1.7 unlock.
800    pub source: Option<TableSource>,
801    /// Optional table alias
802    pub alias: Option<String>,
803    /// Canonical SQL select list.
804    pub select_items: Vec<SelectItem>,
805    /// Columns to select (empty = all)
806    pub columns: Vec<Projection>,
807    /// Canonical SQL WHERE clause.
808    pub where_expr: Option<super::Expr>,
809    /// Filter condition
810    pub filter: Option<Filter>,
811    /// Canonical SQL GROUP BY items.
812    pub group_by_exprs: Vec<super::Expr>,
813    /// GROUP BY fields
814    pub group_by: Vec<String>,
815    /// Canonical SQL HAVING clause.
816    pub having_expr: Option<super::Expr>,
817    /// HAVING filter (applied after grouping)
818    pub having: Option<Filter>,
819    /// Order by clauses
820    pub order_by: Vec<OrderByClause>,
821    /// Limit
822    pub limit: Option<u64>,
823    /// Offset
824    pub offset: Option<u64>,
825    /// WITH EXPAND options (graph traversal, cross-ref following)
826    pub expand: Option<ExpandOptions>,
827    /// Time-travel anchor. When present the executor resolves this
828    /// to an MVCC xid and evaluates the query against that snapshot
829    /// instead of the current one. Mirrors git's `AS OF` semantics.
830    pub as_of: Option<AsOfClause>,
831}
832
833/// Source spec for `AS OF` — parsed form sits in `TableQuery`, then
834/// `vcs_resolve_as_of` turns it into an MVCC xid at execute time.
835#[derive(Debug, Clone)]
836pub enum AsOfClause {
837    /// Explicit commit hash literal: `AS OF COMMIT '<hex>'`.
838    Commit(String),
839    /// Branch or ref: `AS OF BRANCH 'main'` or `AS OF 'refs/heads/main'`.
840    Branch(String),
841    /// Tag: `AS OF TAG 'v1.0'`.
842    Tag(String),
843    /// Unix epoch milliseconds: `AS OF TIMESTAMP 1710000000000`.
844    TimestampMs(i64),
845    /// Raw MVCC snapshot xid: `AS OF SNAPSHOT 12345`.
846    Snapshot(u64),
847}
848
849/// Structured FROM source for a `TableQuery`. Additive alongside the
850/// legacy `TableQuery.table: String` slot — callers that understand
851/// this type can branch on subqueries; callers that only read `table`
852/// fall back to the synthetic sentinel name and, for subqueries,
853/// produce an "unknown table" error until they migrate.
854#[derive(Debug, Clone)]
855pub enum TableSource {
856    /// Plain table reference — equivalent to the legacy `String` form.
857    Name(String),
858    /// A subquery in FROM position: `FROM (SELECT …) AS alias`.
859    Subquery(Box<QueryExpr>),
860}
861
862/// Options for WITH EXPAND clause on SELECT queries.
863#[derive(Debug, Clone, Default)]
864pub struct ExpandOptions {
865    /// Expand via graph edges (WITH EXPAND GRAPH)
866    pub graph: bool,
867    /// Graph expansion depth (DEPTH n)
868    pub graph_depth: usize,
869    /// Expand via cross-references (WITH EXPAND CROSS_REFS)
870    pub cross_refs: bool,
871    /// Index hint from the optimizer (which index to prefer for this query)
872    pub index_hint: Option<crate::storage::query::planner::optimizer::IndexHint>,
873}
874
875impl TableQuery {
876    /// Create a new table query
877    pub fn new(table: &str) -> Self {
878        Self {
879            table: table.to_string(),
880            source: None,
881            alias: None,
882            select_items: Vec::new(),
883            columns: Vec::new(),
884            where_expr: None,
885            filter: None,
886            group_by_exprs: Vec::new(),
887            group_by: Vec::new(),
888            having_expr: None,
889            having: None,
890            order_by: Vec::new(),
891            limit: None,
892            offset: None,
893            expand: None,
894            as_of: None,
895        }
896    }
897
898    /// Create a TableQuery that wraps a subquery in FROM position.
899    /// The legacy `table` slot holds a synthetic sentinel so code that
900    /// only reads `table.as_str()` errors loudly with a
901    /// recognisable marker instead of silently treating it as a
902    /// real collection.
903    pub fn from_subquery(subquery: QueryExpr, alias: Option<String>) -> Self {
904        let sentinel = match &alias {
905            Some(a) => format!("__subq_{a}"),
906            None => "__subq_anon".to_string(),
907        };
908        Self {
909            table: sentinel,
910            source: Some(TableSource::Subquery(Box::new(subquery))),
911            alias,
912            select_items: Vec::new(),
913            columns: Vec::new(),
914            where_expr: None,
915            filter: None,
916            group_by_exprs: Vec::new(),
917            group_by: Vec::new(),
918            having_expr: None,
919            having: None,
920            order_by: Vec::new(),
921            limit: None,
922            offset: None,
923            expand: None,
924            as_of: None,
925        }
926    }
927}
928
929/// Canonical SQL select item for table queries.
930#[derive(Debug, Clone, PartialEq)]
931pub enum SelectItem {
932    Wildcard,
933    Expr {
934        expr: super::Expr,
935        alias: Option<String>,
936    },
937}
938
939// ============================================================================
940// Graph Query
941// ============================================================================
942
943/// Graph query: MATCH pattern WHERE filter RETURN projection
944#[derive(Debug, Clone)]
945pub struct GraphQuery {
946    /// Optional outer alias when used as a join source
947    pub alias: Option<String>,
948    /// Graph pattern to match
949    pub pattern: GraphPattern,
950    /// Filter condition
951    pub filter: Option<Filter>,
952    /// Return projections
953    pub return_: Vec<Projection>,
954}
955
956impl GraphQuery {
957    /// Create a new graph query
958    pub fn new(pattern: GraphPattern) -> Self {
959        Self {
960            alias: None,
961            pattern,
962            filter: None,
963            return_: Vec::new(),
964        }
965    }
966
967    /// Set outer alias
968    pub fn alias(mut self, alias: &str) -> Self {
969        self.alias = Some(alias.to_string());
970        self
971    }
972}
973
974/// Graph pattern: collection of node and edge patterns
975#[derive(Debug, Clone, Default)]
976pub struct GraphPattern {
977    /// Node patterns
978    pub nodes: Vec<NodePattern>,
979    /// Edge patterns connecting nodes
980    pub edges: Vec<EdgePattern>,
981}
982
983impl GraphPattern {
984    /// Create an empty pattern
985    pub fn new() -> Self {
986        Self::default()
987    }
988
989    /// Add a node pattern
990    pub fn node(mut self, pattern: NodePattern) -> Self {
991        self.nodes.push(pattern);
992        self
993    }
994
995    /// Add an edge pattern
996    pub fn edge(mut self, pattern: EdgePattern) -> Self {
997        self.edges.push(pattern);
998        self
999    }
1000}
1001
1002/// Node pattern: (alias:Type {properties})
1003#[derive(Debug, Clone)]
1004pub struct NodePattern {
1005    /// Variable alias for this node
1006    pub alias: String,
1007    /// Optional label filter. Stored as the user-supplied label string so
1008    /// the parser is registry-free; executors resolve it against the live
1009    /// [`crate::storage::engine::graph_store::LabelRegistry`].
1010    pub node_label: Option<String>,
1011    /// Property filters
1012    pub properties: Vec<PropertyFilter>,
1013}
1014
1015impl NodePattern {
1016    /// Create a new node pattern
1017    pub fn new(alias: &str) -> Self {
1018        Self {
1019            alias: alias.to_string(),
1020            node_label: None,
1021            properties: Vec::new(),
1022        }
1023    }
1024
1025    /// Set the label filter (string form — preferred).
1026    pub fn of_label(mut self, label: impl Into<String>) -> Self {
1027        self.node_label = Some(label.into());
1028        self
1029    }
1030
1031    /// Add property filter
1032    pub fn with_property(mut self, name: &str, op: CompareOp, value: Value) -> Self {
1033        self.properties.push(PropertyFilter {
1034            name: name.to_string(),
1035            op,
1036            value,
1037        });
1038        self
1039    }
1040}
1041
1042/// Edge pattern: -[alias:Type*min..max]->
1043#[derive(Debug, Clone)]
1044pub struct EdgePattern {
1045    /// Optional alias for this edge
1046    pub alias: Option<String>,
1047    /// Source node alias
1048    pub from: String,
1049    /// Target node alias
1050    pub to: String,
1051    /// Optional label filter (user-supplied string).
1052    pub edge_label: Option<String>,
1053    /// Edge direction
1054    pub direction: EdgeDirection,
1055    /// Minimum hops (for variable-length patterns)
1056    pub min_hops: u32,
1057    /// Maximum hops (for variable-length patterns)
1058    pub max_hops: u32,
1059}
1060
1061impl EdgePattern {
1062    /// Create a new edge pattern
1063    pub fn new(from: &str, to: &str) -> Self {
1064        Self {
1065            alias: None,
1066            from: from.to_string(),
1067            to: to.to_string(),
1068            edge_label: None,
1069            direction: EdgeDirection::Outgoing,
1070            min_hops: 1,
1071            max_hops: 1,
1072        }
1073    }
1074
1075    /// Set label filter (string form — preferred).
1076    pub fn of_label(mut self, label: impl Into<String>) -> Self {
1077        self.edge_label = Some(label.into());
1078        self
1079    }
1080
1081    /// Set direction
1082    pub fn direction(mut self, dir: EdgeDirection) -> Self {
1083        self.direction = dir;
1084        self
1085    }
1086
1087    /// Set hop range for variable-length patterns
1088    pub fn hops(mut self, min: u32, max: u32) -> Self {
1089        self.min_hops = min;
1090        self.max_hops = max;
1091        self
1092    }
1093
1094    /// Set alias
1095    pub fn alias(mut self, alias: &str) -> Self {
1096        self.alias = Some(alias.to_string());
1097        self
1098    }
1099}
1100
1101/// Edge direction
1102#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1103pub enum EdgeDirection {
1104    /// Outgoing: (a)-[r]->(b)
1105    Outgoing,
1106    /// Incoming: (a)<-[r]-(b)
1107    Incoming,
1108    /// Both: (a)-[r]-(b)
1109    Both,
1110}
1111
1112/// Property filter: name op value
1113#[derive(Debug, Clone)]
1114pub struct PropertyFilter {
1115    pub name: String,
1116    pub op: CompareOp,
1117    pub value: Value,
1118}
1119
1120// ============================================================================
1121// Join Query
1122// ============================================================================
1123
1124/// Join query: combines table and graph queries
1125#[derive(Debug, Clone)]
1126pub struct JoinQuery {
1127    /// Left side (typically table)
1128    pub left: Box<QueryExpr>,
1129    /// Right side (typically graph)
1130    pub right: Box<QueryExpr>,
1131    /// Join type
1132    pub join_type: JoinType,
1133    /// Join condition
1134    pub on: JoinCondition,
1135    /// Post-join filter condition
1136    pub filter: Option<Filter>,
1137    /// Post-join ordering
1138    pub order_by: Vec<OrderByClause>,
1139    /// Post-join limit
1140    pub limit: Option<u64>,
1141    /// Post-join offset
1142    pub offset: Option<u64>,
1143    /// Canonical SQL RETURN projection.
1144    pub return_items: Vec<SelectItem>,
1145    /// Post-join projection
1146    pub return_: Vec<Projection>,
1147}
1148
1149impl JoinQuery {
1150    /// Create a new join query
1151    pub fn new(left: QueryExpr, right: QueryExpr, on: JoinCondition) -> Self {
1152        Self {
1153            left: Box::new(left),
1154            right: Box::new(right),
1155            join_type: JoinType::Inner,
1156            on,
1157            filter: None,
1158            order_by: Vec::new(),
1159            limit: None,
1160            offset: None,
1161            return_items: Vec::new(),
1162            return_: Vec::new(),
1163        }
1164    }
1165
1166    /// Set join type
1167    pub fn join_type(mut self, jt: JoinType) -> Self {
1168        self.join_type = jt;
1169        self
1170    }
1171}
1172
1173/// Join type
1174#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1175pub enum JoinType {
1176    /// Inner join — only matching pairs emitted
1177    Inner,
1178    /// Left outer join — every left row, matched or padded with nulls on the right
1179    LeftOuter,
1180    /// Right outer join — every right row, matched or padded with nulls on the left
1181    RightOuter,
1182    /// Full outer join — LeftOuter ∪ RightOuter, each unmatched side padded
1183    FullOuter,
1184    /// Cross join — Cartesian product, no predicate
1185    Cross,
1186}
1187
1188/// Join condition: how to match rows with nodes
1189#[derive(Debug, Clone)]
1190pub struct JoinCondition {
1191    /// Left field (table side)
1192    pub left_field: FieldRef,
1193    /// Right field (graph side)
1194    pub right_field: FieldRef,
1195}
1196
1197impl JoinCondition {
1198    /// Create a new join condition
1199    pub fn new(left: FieldRef, right: FieldRef) -> Self {
1200        Self {
1201            left_field: left,
1202            right_field: right,
1203        }
1204    }
1205}
1206
1207/// Reference to a field (table column, node property, or edge property)
1208#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1209pub enum FieldRef {
1210    /// Table column: table.column
1211    TableColumn { table: String, column: String },
1212    /// Node property: alias.property
1213    NodeProperty { alias: String, property: String },
1214    /// Edge property: alias.property
1215    EdgeProperty { alias: String, property: String },
1216    /// Node ID: alias.id
1217    NodeId { alias: String },
1218}
1219
1220impl FieldRef {
1221    /// Create a table column reference
1222    pub fn column(table: &str, column: &str) -> Self {
1223        Self::TableColumn {
1224            table: table.to_string(),
1225            column: column.to_string(),
1226        }
1227    }
1228
1229    /// Create a node property reference
1230    pub fn node_prop(alias: &str, property: &str) -> Self {
1231        Self::NodeProperty {
1232            alias: alias.to_string(),
1233            property: property.to_string(),
1234        }
1235    }
1236
1237    /// Create a node ID reference
1238    pub fn node_id(alias: &str) -> Self {
1239        Self::NodeId {
1240            alias: alias.to_string(),
1241        }
1242    }
1243
1244    /// Create an edge property reference
1245    pub fn edge_prop(alias: &str, property: &str) -> Self {
1246        Self::EdgeProperty {
1247            alias: alias.to_string(),
1248            property: property.to_string(),
1249        }
1250    }
1251}
1252
1253// ============================================================================
1254// Path Query
1255// ============================================================================
1256
1257/// Path query: find paths between nodes
1258#[derive(Debug, Clone)]
1259pub struct PathQuery {
1260    /// Optional outer alias when used as a join source
1261    pub alias: Option<String>,
1262    /// Source node selector
1263    pub from: NodeSelector,
1264    /// Target node selector
1265    pub to: NodeSelector,
1266    /// Edge labels to traverse (empty = any). Strings are resolved against
1267    /// the runtime registry by the executor.
1268    pub via: Vec<String>,
1269    /// Maximum path length
1270    pub max_length: u32,
1271    /// Filter on paths
1272    pub filter: Option<Filter>,
1273    /// Return projections
1274    pub return_: Vec<Projection>,
1275}
1276
1277impl PathQuery {
1278    /// Create a new path query
1279    pub fn new(from: NodeSelector, to: NodeSelector) -> Self {
1280        Self {
1281            alias: None,
1282            from,
1283            to,
1284            via: Vec::new(),
1285            max_length: 10,
1286            filter: None,
1287            return_: Vec::new(),
1288        }
1289    }
1290
1291    /// Set outer alias
1292    pub fn alias(mut self, alias: &str) -> Self {
1293        self.alias = Some(alias.to_string());
1294        self
1295    }
1296
1297    /// Add an edge label constraint to traverse (string form).
1298    pub fn via_label(mut self, label: impl Into<String>) -> Self {
1299        self.via.push(label.into());
1300        self
1301    }
1302}
1303
1304/// Node selector for path queries
1305#[derive(Debug, Clone)]
1306pub enum NodeSelector {
1307    /// By node ID
1308    ById(String),
1309    /// By node label and property
1310    ByType {
1311        node_label: String,
1312        filter: Option<PropertyFilter>,
1313    },
1314    /// By table row (linked node)
1315    ByRow { table: String, row_id: u64 },
1316}
1317
1318impl NodeSelector {
1319    /// Select by node ID
1320    pub fn by_id(id: &str) -> Self {
1321        Self::ById(id.to_string())
1322    }
1323
1324    /// Select by label string (preferred).
1325    pub fn by_label(label: impl Into<String>) -> Self {
1326        Self::ByType {
1327            node_label: label.into(),
1328            filter: None,
1329        }
1330    }
1331
1332    /// Select by table row
1333    pub fn by_row(table: &str, row_id: u64) -> Self {
1334        Self::ByRow {
1335            table: table.to_string(),
1336            row_id,
1337        }
1338    }
1339}
1340
1341// ============================================================================
1342// Vector Query
1343// ============================================================================
1344
1345/// Vector similarity search query
1346///
1347/// ```text
1348/// VECTOR SEARCH embeddings
1349/// SIMILAR TO [0.1, 0.2, ..., 0.5]
1350/// WHERE metadata.source = 'nmap'
1351/// LIMIT 10
1352/// ```
1353#[derive(Debug, Clone)]
1354pub struct VectorQuery {
1355    /// Optional outer alias when used as a join source
1356    pub alias: Option<String>,
1357    /// Collection name to search
1358    pub collection: String,
1359    /// Query vector (or reference to get vector from)
1360    pub query_vector: VectorSource,
1361    /// Number of results to return
1362    pub k: usize,
1363    /// Metadata filter
1364    pub filter: Option<MetadataFilter>,
1365    /// Distance metric to use (defaults to collection's metric)
1366    pub metric: Option<DistanceMetric>,
1367    /// Include vectors in results
1368    pub include_vectors: bool,
1369    /// Include metadata in results
1370    pub include_metadata: bool,
1371    /// Minimum similarity threshold (optional)
1372    pub threshold: Option<f32>,
1373}
1374
1375impl VectorQuery {
1376    /// Create a new vector query
1377    pub fn new(collection: &str, query: VectorSource) -> Self {
1378        Self {
1379            alias: None,
1380            collection: collection.to_string(),
1381            query_vector: query,
1382            k: 10,
1383            filter: None,
1384            metric: None,
1385            include_vectors: false,
1386            include_metadata: true,
1387            threshold: None,
1388        }
1389    }
1390
1391    /// Set the number of results
1392    pub fn limit(mut self, k: usize) -> Self {
1393        self.k = k;
1394        self
1395    }
1396
1397    /// Set metadata filter
1398    pub fn with_filter(mut self, filter: MetadataFilter) -> Self {
1399        self.filter = Some(filter);
1400        self
1401    }
1402
1403    /// Include vectors in results
1404    pub fn with_vectors(mut self) -> Self {
1405        self.include_vectors = true;
1406        self
1407    }
1408
1409    /// Set similarity threshold
1410    pub fn min_similarity(mut self, threshold: f32) -> Self {
1411        self.threshold = Some(threshold);
1412        self
1413    }
1414
1415    /// Set outer alias
1416    pub fn alias(mut self, alias: &str) -> Self {
1417        self.alias = Some(alias.to_string());
1418        self
1419    }
1420}
1421
1422/// Source of query vector
1423#[derive(Debug, Clone)]
1424pub enum VectorSource {
1425    /// Literal vector values
1426    Literal(Vec<f32>),
1427    /// Text to embed (requires embedding function)
1428    Text(String),
1429    /// Reference to another vector by ID
1430    Reference { collection: String, vector_id: u64 },
1431    /// From a subquery result
1432    Subquery(Box<QueryExpr>),
1433}
1434
1435impl VectorSource {
1436    /// Create from literal vector
1437    pub fn literal(values: Vec<f32>) -> Self {
1438        Self::Literal(values)
1439    }
1440
1441    /// Create from text (to be embedded)
1442    pub fn text(s: &str) -> Self {
1443        Self::Text(s.to_string())
1444    }
1445
1446    /// Reference another vector
1447    pub fn reference(collection: &str, vector_id: u64) -> Self {
1448        Self::Reference {
1449            collection: collection.to_string(),
1450            vector_id,
1451        }
1452    }
1453}
1454
1455// ============================================================================
1456// Hybrid Query
1457// ============================================================================
1458
1459/// Hybrid query combining structured (table/graph) and vector search
1460///
1461/// ```text
1462/// FROM hosts h
1463/// JOIN VECTOR embeddings e ON h.id = e.metadata.host_id
1464/// SIMILAR TO 'ssh vulnerability'
1465/// WHERE h.os = 'Linux'
1466/// RETURN h.*, e.distance
1467/// ```
1468#[derive(Debug, Clone)]
1469pub struct HybridQuery {
1470    /// Optional outer alias when used as a join source
1471    pub alias: Option<String>,
1472    /// Structured query part (table/graph)
1473    pub structured: Box<QueryExpr>,
1474    /// Vector search part
1475    pub vector: VectorQuery,
1476    /// How to combine results
1477    pub fusion: FusionStrategy,
1478    /// Final result limit
1479    pub limit: Option<usize>,
1480}
1481
1482impl HybridQuery {
1483    /// Create a new hybrid query
1484    pub fn new(structured: QueryExpr, vector: VectorQuery) -> Self {
1485        Self {
1486            alias: None,
1487            structured: Box::new(structured),
1488            vector,
1489            fusion: FusionStrategy::Rerank { weight: 0.5 },
1490            limit: None,
1491        }
1492    }
1493
1494    /// Set fusion strategy
1495    pub fn with_fusion(mut self, fusion: FusionStrategy) -> Self {
1496        self.fusion = fusion;
1497        self
1498    }
1499
1500    /// Set result limit
1501    pub fn limit(mut self, limit: usize) -> Self {
1502        self.limit = Some(limit);
1503        self
1504    }
1505
1506    /// Set outer alias
1507    pub fn alias(mut self, alias: &str) -> Self {
1508        self.alias = Some(alias.to_string());
1509        self
1510    }
1511}
1512
1513/// Strategy for combining structured and vector search results
1514#[derive(Debug, Clone)]
1515pub enum FusionStrategy {
1516    /// Vector similarity re-ranks structured results
1517    /// weight: 0.0 = pure structured, 1.0 = pure vector
1518    Rerank { weight: f32 },
1519    /// Filter with structured query, then search vectors among filtered
1520    FilterThenSearch,
1521    /// Search vectors first, then filter with structured query
1522    SearchThenFilter,
1523    /// Reciprocal Rank Fusion
1524    /// k: RRF constant (typically 60)
1525    RRF { k: u32 },
1526    /// Intersection: only return results that match both
1527    Intersection,
1528    /// Union: return results from either (with combined scores)
1529    Union {
1530        structured_weight: f32,
1531        vector_weight: f32,
1532    },
1533}
1534
1535impl Default for FusionStrategy {
1536    fn default() -> Self {
1537        Self::Rerank { weight: 0.5 }
1538    }
1539}
1540
1541// ============================================================================
1542// DML/DDL Query Types
1543// ============================================================================
1544
1545/// Entity type qualifier for INSERT statements
1546#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
1547pub enum InsertEntityType {
1548    /// Default: plain row
1549    #[default]
1550    Row,
1551    /// INSERT INTO t NODE (...)
1552    Node,
1553    /// INSERT INTO t EDGE (...)
1554    Edge,
1555    /// INSERT INTO t VECTOR (...)
1556    Vector,
1557    /// INSERT INTO t DOCUMENT (...)
1558    Document,
1559    /// INSERT INTO t KV (...)
1560    Kv,
1561}
1562
1563/// An item in a RETURNING clause: either `*` (all columns) or a named column.
1564#[derive(Debug, Clone, PartialEq)]
1565pub enum ReturningItem {
1566    /// RETURNING *
1567    All,
1568    /// RETURNING col
1569    Column(String),
1570}
1571
1572/// INSERT INTO table (columns) VALUES (row1), (row2), ... [WITH TTL duration] [WITH METADATA (k=v)]
1573#[derive(Debug, Clone)]
1574pub struct InsertQuery {
1575    /// Target table name
1576    pub table: String,
1577    /// Entity type qualifier
1578    pub entity_type: InsertEntityType,
1579    /// Column names
1580    pub columns: Vec<String>,
1581    /// Canonical SQL rows of expressions.
1582    pub value_exprs: Vec<Vec<super::Expr>>,
1583    /// Rows of values (each inner Vec is one row)
1584    pub values: Vec<Vec<Value>>,
1585    /// Optional RETURNING clause items.
1586    pub returning: Option<Vec<ReturningItem>>,
1587    /// Optional TTL in milliseconds (from WITH TTL clause)
1588    pub ttl_ms: Option<u64>,
1589    /// Optional absolute expiration (from WITH EXPIRES AT clause)
1590    pub expires_at_ms: Option<u64>,
1591    /// Optional metadata key-value pairs (from WITH METADATA clause)
1592    pub with_metadata: Vec<(String, Value)>,
1593    /// Auto-embed fields on insert (from WITH AUTO EMBED clause)
1594    pub auto_embed: Option<AutoEmbedConfig>,
1595    /// Skip event subscription emission for this statement (SUPPRESS EVENTS).
1596    pub suppress_events: bool,
1597}
1598
1599/// Configuration for automatic embedding generation on INSERT.
1600#[derive(Debug, Clone)]
1601pub struct AutoEmbedConfig {
1602    /// Fields to extract text from for embedding
1603    pub fields: Vec<String>,
1604    /// AI provider (e.g. "openai")
1605    pub provider: String,
1606    /// Optional model override
1607    pub model: Option<String>,
1608}
1609
1610/// EVENTS BACKFILL collection [WHERE pred] TO queue [LIMIT n]
1611#[derive(Debug, Clone)]
1612pub struct EventsBackfillQuery {
1613    pub collection: String,
1614    pub where_filter: Option<String>,
1615    pub target_queue: String,
1616    pub limit: Option<u64>,
1617}
1618
1619/// UPDATE table SET col=val, ... WHERE filter [WITH TTL duration] [WITH METADATA (...)]
1620#[derive(Debug, Clone)]
1621pub struct UpdateQuery {
1622    /// Target table name
1623    pub table: String,
1624    /// Canonical SQL assignments.
1625    pub assignment_exprs: Vec<(String, super::Expr)>,
1626    /// Best-effort literal-only cache of assignments. Non-foldable expressions
1627    /// are preserved exclusively in `assignment_exprs` and evaluated later
1628    /// against the row pre-image by the runtime.
1629    pub assignments: Vec<(String, Value)>,
1630    /// Canonical SQL WHERE clause.
1631    pub where_expr: Option<super::Expr>,
1632    /// Optional WHERE filter
1633    pub filter: Option<Filter>,
1634    /// Optional TTL in milliseconds (from WITH TTL clause)
1635    pub ttl_ms: Option<u64>,
1636    /// Optional absolute expiration (from WITH EXPIRES AT clause)
1637    pub expires_at_ms: Option<u64>,
1638    /// Optional metadata key-value pairs (from WITH METADATA clause)
1639    pub with_metadata: Vec<(String, Value)>,
1640    /// Optional RETURNING clause items.
1641    pub returning: Option<Vec<ReturningItem>>,
1642    /// Optional `LIMIT N` cap. Caps the number of rows the executor
1643    /// will mutate in a single statement. Required by `BATCH N ROWS`
1644    /// data migrations (#37) which run the same UPDATE body in a
1645    /// loop, advancing a checkpoint between batches.
1646    pub limit: Option<u64>,
1647    /// Skip event subscription emission for this statement (SUPPRESS EVENTS).
1648    pub suppress_events: bool,
1649}
1650
1651/// DELETE FROM table WHERE filter
1652#[derive(Debug, Clone)]
1653pub struct DeleteQuery {
1654    /// Target table name
1655    pub table: String,
1656    /// Canonical SQL WHERE clause.
1657    pub where_expr: Option<super::Expr>,
1658    /// Optional WHERE filter
1659    pub filter: Option<Filter>,
1660    /// Optional RETURNING clause items.
1661    pub returning: Option<Vec<ReturningItem>>,
1662    /// Skip event subscription emission for this statement (SUPPRESS EVENTS).
1663    pub suppress_events: bool,
1664}
1665
1666/// CREATE TABLE name (columns) or CREATE {KV|CONFIG|VAULT} name
1667#[derive(Debug, Clone)]
1668pub struct CreateTableQuery {
1669    /// Declared collection model. Defaults to Table for CREATE TABLE.
1670    pub collection_model: CollectionModel,
1671    /// Table name
1672    pub name: String,
1673    /// Column definitions
1674    pub columns: Vec<CreateColumnDef>,
1675    /// IF NOT EXISTS flag
1676    pub if_not_exists: bool,
1677    /// Optional default TTL applied to newly inserted items in this collection.
1678    pub default_ttl_ms: Option<u64>,
1679    /// Fields to prioritize in the context index (WITH CONTEXT INDEX ON (f1, f2))
1680    pub context_index_fields: Vec<String>,
1681    /// Enables the global context index for this table
1682    /// (`WITH context_index = true`). Default false — pure OLTP tables
1683    /// skip the tokenisation / 3-way RwLock write storm on every insert.
1684    /// Having `context_index_fields` non-empty also enables it implicitly.
1685    pub context_index_enabled: bool,
1686    /// When true, CREATE TABLE implicitly adds two user-visible columns
1687    /// `created_at` and `updated_at` (BIGINT unix-ms). The runtime
1688    /// populates them from `UnifiedEntity::created_at/updated_at` on
1689    /// every write; `created_at` is immutable after insert.
1690    /// Enabled via `WITH timestamps = true` in the DDL.
1691    pub timestamps: bool,
1692    /// Partitioning spec (Phase 2.2 PG parity).
1693    ///
1694    /// When present the table is the *parent* of a partition tree — every
1695    /// child partition is registered via `ALTER TABLE ... ATTACH PARTITION`.
1696    /// Phase 2.2 stops at registry-only: queries against a partitioned
1697    /// parent don't auto-rewrite as UNION yet (Phase 4 adds pruning).
1698    pub partition_by: Option<PartitionSpec>,
1699    /// Table-scoped multi-tenancy declaration (Phase 2.5.4).
1700    ///
1701    /// Syntax: `CREATE TABLE t (...) WITH (tenant_by = 'col_name')` or
1702    /// the shorthand `CREATE TABLE t (...) TENANT BY (col_name)`. The
1703    /// runtime treats the named column as the tenant discriminator and
1704    /// automatically:
1705    ///
1706    /// 1. Registers the table → column mapping so INSERTs that omit the
1707    ///    column get `CURRENT_TENANT()` auto-filled.
1708    /// 2. Installs an implicit RLS policy equivalent to
1709    ///    `USING (col = CURRENT_TENANT())` for SELECT/UPDATE/DELETE/INSERT.
1710    /// 3. Flips `rls_enabled_tables` on so the policy actually applies.
1711    ///
1712    /// None leaves the table non-tenant-scoped — callers manage tenancy
1713    /// manually via explicit CREATE POLICY if they want it.
1714    pub tenant_by: Option<String>,
1715    /// When true, UPDATE and DELETE on this table are rejected at
1716    /// parse time. Corresponds to `CREATE TABLE ... APPEND ONLY` or
1717    /// `WITH (append_only = true)`. Default false (mutable).
1718    pub append_only: bool,
1719    /// Declarative event subscriptions for this table. #291 stores
1720    /// metadata only; event emission is intentionally out of scope.
1721    pub subscriptions: Vec<crate::catalog::SubscriptionDescriptor>,
1722    /// `CREATE VAULT ... WITH OWN MASTER KEY`: provision per-vault
1723    /// key material instead of using the cluster vault key.
1724    pub vault_own_master_key: bool,
1725}
1726
1727/// `PARTITION BY RANGE|LIST|HASH (column)` clause.
1728#[derive(Debug, Clone, PartialEq, Eq)]
1729pub struct PartitionSpec {
1730    pub kind: PartitionKind,
1731    /// Partition key column(s). Simple single-column for Phase 2.2.
1732    pub column: String,
1733}
1734
1735#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1736pub enum PartitionKind {
1737    /// `PARTITION BY RANGE(col)` — children bind `FOR VALUES FROM (a) TO (b)`.
1738    Range,
1739    /// `PARTITION BY LIST(col)` — children bind `FOR VALUES IN (v1, v2, ...)`.
1740    List,
1741    /// `PARTITION BY HASH(col)` — children bind `FOR VALUES WITH (MODULUS m, REMAINDER r)`.
1742    Hash,
1743}
1744
1745/// Column definition for CREATE TABLE
1746#[derive(Debug, Clone)]
1747pub struct CreateColumnDef {
1748    /// Column name
1749    pub name: String,
1750    /// Legacy declared type string preserved for the runtime/storage pipeline.
1751    pub data_type: String,
1752    /// Structured SQL type used by the semantic layer.
1753    pub sql_type: SqlTypeName,
1754    /// NOT NULL constraint
1755    pub not_null: bool,
1756    /// DEFAULT value expression
1757    pub default: Option<String>,
1758    /// Compression level (COMPRESS:N)
1759    pub compress: Option<u8>,
1760    /// UNIQUE constraint
1761    pub unique: bool,
1762    /// PRIMARY KEY constraint
1763    pub primary_key: bool,
1764    /// Enum variant names (for ENUM type)
1765    pub enum_variants: Vec<String>,
1766    /// Array element type (for ARRAY type)
1767    pub array_element: Option<String>,
1768    /// Decimal precision (for DECIMAL type)
1769    pub decimal_precision: Option<u8>,
1770}
1771
1772/// DROP TABLE name
1773#[derive(Debug, Clone)]
1774pub struct DropTableQuery {
1775    /// Table name
1776    pub name: String,
1777    /// IF EXISTS flag
1778    pub if_exists: bool,
1779}
1780
1781/// DROP GRAPH [IF EXISTS] name
1782#[derive(Debug, Clone)]
1783pub struct DropGraphQuery {
1784    pub name: String,
1785    pub if_exists: bool,
1786}
1787
1788/// DROP VECTOR [IF EXISTS] name
1789#[derive(Debug, Clone)]
1790pub struct DropVectorQuery {
1791    pub name: String,
1792    pub if_exists: bool,
1793}
1794
1795/// DROP DOCUMENT [IF EXISTS] name
1796#[derive(Debug, Clone)]
1797pub struct DropDocumentQuery {
1798    pub name: String,
1799    pub if_exists: bool,
1800}
1801
1802/// DROP {KV|CONFIG|VAULT} [IF EXISTS] name
1803#[derive(Debug, Clone)]
1804pub struct DropKvQuery {
1805    pub name: String,
1806    pub if_exists: bool,
1807    pub model: CollectionModel,
1808}
1809
1810/// DROP COLLECTION [IF EXISTS] name
1811#[derive(Debug, Clone)]
1812pub struct DropCollectionQuery {
1813    pub name: String,
1814    pub if_exists: bool,
1815}
1816
1817/// TRUNCATE {TABLE|GRAPH|VECTOR|DOCUMENT|TIMESERIES|KV|QUEUE|COLLECTION} [IF EXISTS] name
1818#[derive(Debug, Clone)]
1819pub struct TruncateQuery {
1820    pub name: String,
1821    pub model: Option<CollectionModel>,
1822    pub if_exists: bool,
1823}
1824
1825/// ALTER TABLE name operations
1826#[derive(Debug, Clone)]
1827pub struct AlterTableQuery {
1828    /// Table name
1829    pub name: String,
1830    /// Alter operations
1831    pub operations: Vec<AlterOperation>,
1832}
1833
1834/// Single ALTER TABLE operation
1835#[derive(Debug, Clone)]
1836pub enum AlterOperation {
1837    /// ADD COLUMN definition
1838    AddColumn(CreateColumnDef),
1839    /// DROP COLUMN name
1840    DropColumn(String),
1841    /// RENAME COLUMN from TO to
1842    RenameColumn { from: String, to: String },
1843    /// `ATTACH PARTITION child FOR VALUES ...` (Phase 2.2 PG parity).
1844    ///
1845    /// Binds an existing child table to the parent partitioned table.
1846    /// The `bound` string captures the raw bound expression so the
1847    /// runtime can round-trip it back into `red_config` without a
1848    /// dedicated per-kind AST.
1849    AttachPartition {
1850        child: String,
1851        /// Human-readable bound string, e.g. `FROM (2024-01-01) TO (2025-01-01)`
1852        /// or `IN (1, 2, 3)` or `WITH (MODULUS 4, REMAINDER 0)`.
1853        bound: String,
1854    },
1855    /// `DETACH PARTITION child`
1856    DetachPartition { child: String },
1857    /// `ENABLE ROW LEVEL SECURITY` (Phase 2.5 PG parity).
1858    ///
1859    /// Flips the table into RLS-enforced mode. Reads against the table
1860    /// will be filtered by every matching `CREATE POLICY` (for the
1861    /// current role) combined with `AND`.
1862    EnableRowLevelSecurity,
1863    /// `DISABLE ROW LEVEL SECURITY` — disables enforcement; policies
1864    /// remain defined but are ignored until re-enabled.
1865    DisableRowLevelSecurity,
1866    /// `ENABLE TENANCY ON (col)` (Phase 2.5.4 PG parity-ish).
1867    ///
1868    /// Retrofit a tenant-scoped declaration onto an existing table —
1869    /// registers the column, installs the auto `__tenant_iso` RLS
1870    /// policy, and flips RLS on. Equivalent to re-running
1871    /// `CREATE TABLE ... TENANT BY (col)` minus the schema creation.
1872    EnableTenancy { column: String },
1873    /// `DISABLE TENANCY` — tears down the auto-policy and clears the
1874    /// tenancy registration. User-defined policies on the table are
1875    /// untouched; RLS stays enabled if any survive.
1876    DisableTenancy,
1877    /// `SET APPEND_ONLY = true|false` — flips the catalog flag.
1878    /// Setting `true` rejects all future UPDATE/DELETE at parse-time
1879    /// guard; setting `false` re-enables them. Existing rows are
1880    /// untouched either way — this is a purely declarative switch.
1881    SetAppendOnly(bool),
1882    /// `SET VERSIONED = true|false` — opt the table into (or out of)
1883    /// Git-for-Data. Enables merge / diff / AS OF semantics against
1884    /// this collection. Works retroactively: previously-created
1885    /// rows become part of the history accessible via AS OF as long
1886    /// as their xmin is still pinned by an existing commit.
1887    SetVersioned(bool),
1888    /// `ENABLE EVENTS ...` — install or re-enable table event subscription metadata.
1889    EnableEvents(crate::catalog::SubscriptionDescriptor),
1890    /// `DISABLE EVENTS` — mark all table event subscriptions disabled.
1891    DisableEvents,
1892    /// `ADD SUBSCRIPTION name TO queue [REDACT (...)] [WHERE ...]` — add a named subscription.
1893    AddSubscription {
1894        name: String,
1895        descriptor: crate::catalog::SubscriptionDescriptor,
1896    },
1897    /// `DROP SUBSCRIPTION name` — remove a named subscription by name.
1898    DropSubscription { name: String },
1899}
1900
1901// ============================================================================
1902// Shared Types
1903// ============================================================================
1904
1905/// Column/field projection
1906#[derive(Debug, Clone, PartialEq)]
1907pub enum Projection {
1908    /// Select all columns (*)
1909    All,
1910    /// Single column by name
1911    Column(String),
1912    /// Column with alias
1913    Alias(String, String),
1914    /// Function call (name, args)
1915    Function(String, Vec<Projection>),
1916    /// Expression with optional alias
1917    Expression(Box<Filter>, Option<String>),
1918    /// Field reference (for graph properties)
1919    Field(FieldRef, Option<String>),
1920}
1921
1922impl Projection {
1923    /// Create a projection from a field reference
1924    pub fn from_field(field: FieldRef) -> Self {
1925        Projection::Field(field, None)
1926    }
1927
1928    /// Create a column projection
1929    pub fn column(name: &str) -> Self {
1930        Projection::Column(name.to_string())
1931    }
1932
1933    /// Create an aliased projection
1934    pub fn with_alias(column: &str, alias: &str) -> Self {
1935        Projection::Alias(column.to_string(), alias.to_string())
1936    }
1937}
1938
1939/// Filter condition
1940#[derive(Debug, Clone, PartialEq)]
1941pub enum Filter {
1942    /// Comparison: field op value
1943    Compare {
1944        field: FieldRef,
1945        op: CompareOp,
1946        value: Value,
1947    },
1948    /// Field-to-field comparison: left.field op right.field. Used when
1949    /// WHERE / BETWEEN operands reference another column instead of a
1950    /// literal — the pre-Fase-2-parser-v2 shim for column-to-column
1951    /// predicates. Once the Expr-rewrite lands, this collapses into
1952    /// `Compare { left: Expr, op, right: Expr }`.
1953    CompareFields {
1954        left: FieldRef,
1955        op: CompareOp,
1956        right: FieldRef,
1957    },
1958    /// Expression-to-expression comparison: `lhs op rhs` where either
1959    /// side may be an arbitrary `Expr` tree (function call, CAST,
1960    /// arithmetic, nested CASE). This is the most general compare
1961    /// variant — `Compare` and `CompareFields` stay as fast-path
1962    /// specialisations because the planner / cost model / index
1963    /// selector all pattern-match on the simpler shapes. The parser
1964    /// only emits this variant when a simpler one cannot express the
1965    /// predicate.
1966    CompareExpr {
1967        lhs: super::Expr,
1968        op: CompareOp,
1969        rhs: super::Expr,
1970    },
1971    /// Logical AND
1972    And(Box<Filter>, Box<Filter>),
1973    /// Logical OR
1974    Or(Box<Filter>, Box<Filter>),
1975    /// Logical NOT
1976    Not(Box<Filter>),
1977    /// IS NULL
1978    IsNull(FieldRef),
1979    /// IS NOT NULL
1980    IsNotNull(FieldRef),
1981    /// IN (value1, value2, ...)
1982    In { field: FieldRef, values: Vec<Value> },
1983    /// BETWEEN low AND high
1984    Between {
1985        field: FieldRef,
1986        low: Value,
1987        high: Value,
1988    },
1989    /// LIKE pattern
1990    Like { field: FieldRef, pattern: String },
1991    /// STARTS WITH prefix
1992    StartsWith { field: FieldRef, prefix: String },
1993    /// ENDS WITH suffix
1994    EndsWith { field: FieldRef, suffix: String },
1995    /// CONTAINS substring
1996    Contains { field: FieldRef, substring: String },
1997}
1998
1999impl Filter {
2000    /// Create a comparison filter
2001    pub fn compare(field: FieldRef, op: CompareOp, value: Value) -> Self {
2002        Self::Compare { field, op, value }
2003    }
2004
2005    /// Combine with AND
2006    pub fn and(self, other: Filter) -> Self {
2007        Self::And(Box::new(self), Box::new(other))
2008    }
2009
2010    /// Combine with OR
2011    pub fn or(self, other: Filter) -> Self {
2012        Self::Or(Box::new(self), Box::new(other))
2013    }
2014
2015    /// Negate
2016    pub fn not(self) -> Self {
2017        Self::Not(Box::new(self))
2018    }
2019
2020    /// Bottom-up AST rewrites: OR-of-equalities → IN, AND/OR flatten.
2021    /// Inspired by MongoDB's `MatchExpression::optimize()`.
2022    /// Call on the result of `effective_table_filter()` before evaluation.
2023    pub fn optimize(self) -> Self {
2024        crate::storage::query::filter_optimizer::optimize(self)
2025    }
2026}
2027
2028/// Comparison operator
2029#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2030pub enum CompareOp {
2031    /// Equal (=)
2032    Eq,
2033    /// Not equal (<> or !=)
2034    Ne,
2035    /// Less than (<)
2036    Lt,
2037    /// Less than or equal (<=)
2038    Le,
2039    /// Greater than (>)
2040    Gt,
2041    /// Greater than or equal (>=)
2042    Ge,
2043}
2044
2045impl fmt::Display for CompareOp {
2046    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2047        match self {
2048            CompareOp::Eq => write!(f, "="),
2049            CompareOp::Ne => write!(f, "<>"),
2050            CompareOp::Lt => write!(f, "<"),
2051            CompareOp::Le => write!(f, "<="),
2052            CompareOp::Gt => write!(f, ">"),
2053            CompareOp::Ge => write!(f, ">="),
2054        }
2055    }
2056}
2057
2058/// Order by clause.
2059///
2060/// Fase 2 migration: `field` is the legacy bare column reference and
2061/// remains populated for back-compat with existing callers (SPARQL /
2062/// Gremlin / Cypher translators, the planner cost model, etc.). The
2063/// new `expr` slot carries an arbitrary `Expr` tree — when present,
2064/// runtime comparators prefer it over `field`, so the parser can
2065/// emit `ORDER BY CAST(a AS INT)`, `ORDER BY a + b * 2`, etc. without
2066/// breaking the rest of the codebase.
2067///
2068/// When `expr` is `None`, the clause behaves exactly like before.
2069/// When `expr` is `Some(Expr::Column(f))`, runtime code may still use
2070/// the legacy path — it's equivalent. Constructors default `expr` to
2071/// `None` so all existing call sites stay source-compatible.
2072#[derive(Debug, Clone)]
2073pub struct OrderByClause {
2074    /// Field to order by. Left populated even when `expr` is set so
2075    /// legacy consumers (planner cardinality estimate, cost model,
2076    /// mode translators) that still pattern-match on `field` keep
2077    /// working during the Fase 2 migration.
2078    pub field: FieldRef,
2079    /// Fase 2 expression-aware sort key. When `Some`, runtime order
2080    /// comparators evaluate this expression per row and sort on the
2081    /// resulting values — unlocks `ORDER BY expr` (Fase 1.6).
2082    pub expr: Option<super::Expr>,
2083    /// Ascending or descending
2084    pub ascending: bool,
2085    /// Nulls first or last
2086    pub nulls_first: bool,
2087}
2088
2089impl OrderByClause {
2090    /// Create ascending order
2091    pub fn asc(field: FieldRef) -> Self {
2092        Self {
2093            field,
2094            expr: None,
2095            ascending: true,
2096            nulls_first: false,
2097        }
2098    }
2099
2100    /// Create descending order
2101    pub fn desc(field: FieldRef) -> Self {
2102        Self {
2103            field,
2104            expr: None,
2105            ascending: false,
2106            nulls_first: true,
2107        }
2108    }
2109
2110    /// Attach an `Expr` sort key to an existing clause. Leaves `field`
2111    /// untouched so back-compat match sites keep their pattern.
2112    pub fn with_expr(mut self, expr: super::Expr) -> Self {
2113        self.expr = Some(expr);
2114        self
2115    }
2116}
2117
2118// ============================================================================
2119// Graph Commands
2120// ============================================================================
2121
2122/// Graph analytics command issued via SQL-like syntax
2123#[derive(Debug, Clone)]
2124pub enum GraphCommand {
2125    /// GRAPH NEIGHBORHOOD 'source' [DEPTH n] [DIRECTION dir]
2126    Neighborhood {
2127        source: String,
2128        depth: u32,
2129        direction: String,
2130    },
2131    /// GRAPH SHORTEST_PATH 'source' TO 'target' [ALGORITHM alg] [DIRECTION dir]
2132    ShortestPath {
2133        source: String,
2134        target: String,
2135        algorithm: String,
2136        direction: String,
2137    },
2138    /// GRAPH TRAVERSE 'source' [STRATEGY bfs|dfs] [DEPTH n] [DIRECTION dir]
2139    Traverse {
2140        source: String,
2141        strategy: String,
2142        depth: u32,
2143        direction: String,
2144    },
2145    /// GRAPH CENTRALITY [ALGORITHM alg]
2146    Centrality { algorithm: String },
2147    /// GRAPH COMMUNITY [ALGORITHM alg] [MAX_ITERATIONS n]
2148    Community {
2149        algorithm: String,
2150        max_iterations: u32,
2151    },
2152    /// GRAPH COMPONENTS [MODE connected|weak|strong]
2153    Components { mode: String },
2154    /// GRAPH CYCLES [MAX_LENGTH n]
2155    Cycles { max_length: u32 },
2156    /// GRAPH CLUSTERING
2157    Clustering,
2158    /// GRAPH TOPOLOGICAL_SORT
2159    TopologicalSort,
2160    /// GRAPH PROPERTIES
2161    Properties,
2162}
2163
2164// ============================================================================
2165// Search Commands
2166// ============================================================================
2167
2168/// Search command issued via SQL-like syntax
2169#[derive(Debug, Clone)]
2170pub enum SearchCommand {
2171    /// SEARCH SIMILAR [v1, v2, ...] | $N | TEXT 'query' [COLLECTION col] [LIMIT n] [MIN_SCORE f] [USING provider]
2172    Similar {
2173        vector: Vec<f32>,
2174        text: Option<String>,
2175        provider: Option<String>,
2176        collection: String,
2177        limit: usize,
2178        min_score: f32,
2179        /// `$N` placeholder for the vector slot. `Some(idx)` when the SQL
2180        /// used `SEARCH SIMILAR $N ...`; the binder substitutes the
2181        /// user-supplied `Value::Vector` and clears this back to `None`.
2182        /// Runtime executors assert this is `None` post-bind.
2183        vector_param: Option<usize>,
2184        /// `$N` placeholder for the `LIMIT` slot (issue #361). The binder
2185        /// substitutes the user-supplied positive integer into `limit`
2186        /// and clears this back to `None`.
2187        limit_param: Option<usize>,
2188        /// `$N` placeholder for the `MIN_SCORE` slot (issue #361). The
2189        /// binder substitutes the user-supplied float into `min_score`
2190        /// and clears this back to `None`.
2191        min_score_param: Option<usize>,
2192        /// `$N` placeholder for `SEARCH SIMILAR TEXT $N` (issue #361).
2193        /// Binder substitutes the user-supplied text into `text` and
2194        /// clears this back to `None`.
2195        text_param: Option<usize>,
2196    },
2197    /// SEARCH TEXT 'query' [COLLECTION col] [LIMIT n] [FUZZY]
2198    Text {
2199        query: String,
2200        collection: Option<String>,
2201        limit: usize,
2202        fuzzy: bool,
2203        /// `$N` placeholder for the `LIMIT` slot (issue #361). Same
2204        /// shape as `SearchCommand::Hybrid::limit_param`; the binder
2205        /// substitutes the user-supplied positive integer into `limit`
2206        /// and clears this back to `None`.
2207        limit_param: Option<usize>,
2208    },
2209    /// SEARCH HYBRID [vector] [TEXT 'query'] COLLECTION col [LIMIT n]
2210    Hybrid {
2211        vector: Option<Vec<f32>>,
2212        query: Option<String>,
2213        collection: String,
2214        limit: usize,
2215        /// `$N` placeholder for the `LIMIT` / `K` slot (issue #361).
2216        /// Same shape as `SearchCommand::Similar::limit_param`; the
2217        /// binder substitutes the user-supplied positive integer and
2218        /// clears this back to `None`.
2219        limit_param: Option<usize>,
2220    },
2221    /// SEARCH MULTIMODAL 'key_or_query' [COLLECTION col] [LIMIT n]
2222    Multimodal {
2223        query: String,
2224        collection: Option<String>,
2225        limit: usize,
2226        /// `$N` placeholder for the `LIMIT` slot (issue #361). Same
2227        /// shape as `SearchCommand::Hybrid::limit_param`; the binder
2228        /// substitutes the user-supplied positive integer into `limit`
2229        /// and clears this back to `None`.
2230        limit_param: Option<usize>,
2231    },
2232    /// SEARCH INDEX index VALUE 'value' [COLLECTION col] [LIMIT n] [EXACT]
2233    Index {
2234        index: String,
2235        value: String,
2236        collection: Option<String>,
2237        limit: usize,
2238        exact: bool,
2239        /// `$N` placeholder for the `LIMIT` slot (issue #361). Same
2240        /// shape as `SearchCommand::Hybrid::limit_param`; the binder
2241        /// substitutes the user-supplied positive integer into `limit`
2242        /// and clears this back to `None`.
2243        limit_param: Option<usize>,
2244    },
2245    /// SEARCH CONTEXT 'query' [FIELD field] [COLLECTION col] [LIMIT n] [DEPTH n]
2246    Context {
2247        query: String,
2248        field: Option<String>,
2249        collection: Option<String>,
2250        limit: usize,
2251        depth: usize,
2252        /// `$N` placeholder for the `LIMIT` slot (issue #361). Same
2253        /// shape as `SearchCommand::Hybrid::limit_param`; the binder
2254        /// substitutes the user-supplied positive integer into `limit`
2255        /// and clears this back to `None`.
2256        limit_param: Option<usize>,
2257    },
2258    /// SEARCH SPATIAL RADIUS lat lon radius_km COLLECTION col COLUMN col [LIMIT n]
2259    SpatialRadius {
2260        center_lat: f64,
2261        center_lon: f64,
2262        radius_km: f64,
2263        collection: String,
2264        column: String,
2265        limit: usize,
2266        /// `$N` placeholder for the `LIMIT` slot (issue #361). Same
2267        /// shape as `SearchCommand::Hybrid::limit_param`; the binder
2268        /// substitutes the user-supplied positive integer into `limit`
2269        /// and clears this back to `None`.
2270        limit_param: Option<usize>,
2271    },
2272    /// SEARCH SPATIAL BBOX min_lat min_lon max_lat max_lon COLLECTION col COLUMN col [LIMIT n]
2273    SpatialBbox {
2274        min_lat: f64,
2275        min_lon: f64,
2276        max_lat: f64,
2277        max_lon: f64,
2278        collection: String,
2279        column: String,
2280        limit: usize,
2281        /// `$N` placeholder for the `LIMIT` slot (issue #361). Same
2282        /// shape as `SearchCommand::Hybrid::limit_param`; the binder
2283        /// substitutes the user-supplied positive integer into `limit`
2284        /// and clears this back to `None`.
2285        limit_param: Option<usize>,
2286    },
2287    /// SEARCH SPATIAL NEAREST lat lon K n COLLECTION col COLUMN col
2288    SpatialNearest {
2289        lat: f64,
2290        lon: f64,
2291        k: usize,
2292        collection: String,
2293        column: String,
2294        /// `$N` placeholder for the `K` slot (issue #361). Same shape
2295        /// as `SearchCommand::Hybrid::limit_param`; the binder
2296        /// substitutes the user-supplied positive integer into `k`
2297        /// and clears this back to `None`.
2298        k_param: Option<usize>,
2299    },
2300}
2301
2302// ============================================================================
2303// Time-Series DDL
2304// ============================================================================
2305
2306/// CREATE TIMESERIES name [RETENTION duration] [CHUNK_SIZE n] [DOWNSAMPLE spec[, spec...]]
2307///
2308/// `CREATE HYPERTABLE` lands on the same AST with `hypertable` populated.
2309/// The TimescaleDB-style syntax (time column + chunk_interval) gives the
2310/// runtime enough to register a `HypertableSpec` alongside the
2311/// underlying collection contract, so chunk routing and TTL sweeps can
2312/// address the table without a separate DDL.
2313#[derive(Debug, Clone)]
2314pub struct CreateTimeSeriesQuery {
2315    pub name: String,
2316    pub retention_ms: Option<u64>,
2317    pub chunk_size: Option<usize>,
2318    pub downsample_policies: Vec<String>,
2319    pub if_not_exists: bool,
2320    /// When `Some`, the DDL was spelled `CREATE HYPERTABLE` and the
2321    /// runtime must register the spec with the hypertable registry.
2322    pub hypertable: Option<HypertableDdl>,
2323}
2324
2325/// Hypertable-specific DDL fields — set only when the caller used
2326/// `CREATE HYPERTABLE`.
2327#[derive(Debug, Clone)]
2328pub struct HypertableDdl {
2329    /// Column that carries the nanosecond timestamp axis.
2330    pub time_column: String,
2331    /// Chunk width in nanoseconds.
2332    pub chunk_interval_ns: u64,
2333    /// Per-chunk default TTL in nanoseconds (`None` = no TTL).
2334    pub default_ttl_ns: Option<u64>,
2335}
2336
2337/// DROP TIMESERIES [IF EXISTS] name
2338#[derive(Debug, Clone)]
2339pub struct DropTimeSeriesQuery {
2340    pub name: String,
2341    pub if_exists: bool,
2342}
2343
2344// ============================================================================
2345// Queue DDL & Commands
2346// ============================================================================
2347
2348/// CREATE QUEUE name [MAX_SIZE n] [PRIORITY] [WITH TTL duration] [WITH DLQ name] [MAX_ATTEMPTS n]
2349#[derive(Debug, Clone)]
2350pub struct CreateQueueQuery {
2351    pub name: String,
2352    pub mode: QueueMode,
2353    pub priority: bool,
2354    pub max_size: Option<usize>,
2355    pub ttl_ms: Option<u64>,
2356    pub dlq: Option<String>,
2357    pub max_attempts: u32,
2358    pub if_not_exists: bool,
2359}
2360
2361/// ALTER QUEUE name SET MODE [FANOUT|WORK]
2362#[derive(Debug, Clone)]
2363pub struct AlterQueueQuery {
2364    pub name: String,
2365    pub mode: QueueMode,
2366}
2367
2368/// DROP QUEUE [IF EXISTS] name
2369#[derive(Debug, Clone)]
2370pub struct DropQueueQuery {
2371    pub name: String,
2372    pub if_exists: bool,
2373}
2374
2375/// SELECT <columns> FROM QUEUE name [WHERE filter] [LIMIT n]
2376#[derive(Debug, Clone)]
2377pub struct QueueSelectQuery {
2378    pub queue: String,
2379    pub columns: Vec<String>,
2380    pub filter: Option<Filter>,
2381    pub limit: Option<u64>,
2382}
2383
2384/// Which end of the queue
2385#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2386pub enum QueueSide {
2387    Left,
2388    Right,
2389}
2390
2391/// Queue operation commands
2392#[derive(Debug, Clone)]
2393pub enum QueueCommand {
2394    Push {
2395        queue: String,
2396        value: Value,
2397        side: QueueSide,
2398        priority: Option<i32>,
2399    },
2400    Pop {
2401        queue: String,
2402        side: QueueSide,
2403        count: usize,
2404    },
2405    Peek {
2406        queue: String,
2407        count: usize,
2408    },
2409    Len {
2410        queue: String,
2411    },
2412    Purge {
2413        queue: String,
2414    },
2415    GroupCreate {
2416        queue: String,
2417        group: String,
2418    },
2419    GroupRead {
2420        queue: String,
2421        group: Option<String>,
2422        consumer: String,
2423        count: usize,
2424    },
2425    Pending {
2426        queue: String,
2427        group: String,
2428    },
2429    Claim {
2430        queue: String,
2431        group: String,
2432        consumer: String,
2433        min_idle_ms: u64,
2434    },
2435    Ack {
2436        queue: String,
2437        group: String,
2438        message_id: String,
2439    },
2440    Nack {
2441        queue: String,
2442        group: String,
2443        message_id: String,
2444    },
2445    Move {
2446        source: String,
2447        destination: String,
2448        filter: Option<Filter>,
2449        limit: usize,
2450    },
2451}
2452
2453// ============================================================================
2454// Tree DDL & Commands
2455// ============================================================================
2456
2457#[derive(Debug, Clone)]
2458pub struct TreeNodeSpec {
2459    pub label: String,
2460    pub node_type: Option<String>,
2461    pub properties: Vec<(String, Value)>,
2462    pub metadata: Vec<(String, Value)>,
2463    pub max_children: Option<usize>,
2464}
2465
2466#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2467pub enum TreePosition {
2468    First,
2469    Last,
2470    Index(usize),
2471}
2472
2473#[derive(Debug, Clone)]
2474pub struct CreateTreeQuery {
2475    pub collection: String,
2476    pub name: String,
2477    pub root: TreeNodeSpec,
2478    pub default_max_children: usize,
2479    pub if_not_exists: bool,
2480}
2481
2482#[derive(Debug, Clone)]
2483pub struct DropTreeQuery {
2484    pub collection: String,
2485    pub name: String,
2486    pub if_exists: bool,
2487}
2488
2489#[derive(Debug, Clone)]
2490pub enum TreeCommand {
2491    Insert {
2492        collection: String,
2493        tree_name: String,
2494        parent_id: u64,
2495        node: TreeNodeSpec,
2496        position: TreePosition,
2497    },
2498    Move {
2499        collection: String,
2500        tree_name: String,
2501        node_id: u64,
2502        parent_id: u64,
2503        position: TreePosition,
2504    },
2505    Delete {
2506        collection: String,
2507        tree_name: String,
2508        node_id: u64,
2509    },
2510    Validate {
2511        collection: String,
2512        tree_name: String,
2513    },
2514    Rebalance {
2515        collection: String,
2516        tree_name: String,
2517        dry_run: bool,
2518    },
2519}
2520
2521// ============================================================================
2522// KV DSL Commands
2523// ============================================================================
2524
2525/// KV verb commands: `KV PUT key = value [EXPIRE n] [IF NOT EXISTS]`, `KV GET key`, `KV DELETE key`
2526#[derive(Debug, Clone)]
2527pub enum KvCommand {
2528    Put {
2529        model: CollectionModel,
2530        collection: String,
2531        key: String,
2532        value: Value,
2533        /// TTL in milliseconds (from EXPIRE clause)
2534        ttl_ms: Option<u64>,
2535        tags: Vec<String>,
2536        if_not_exists: bool,
2537    },
2538    InvalidateTags {
2539        collection: String,
2540        tags: Vec<String>,
2541    },
2542    Get {
2543        model: CollectionModel,
2544        collection: String,
2545        key: String,
2546    },
2547    Unseal {
2548        collection: String,
2549        key: String,
2550        version: Option<i64>,
2551    },
2552    Rotate {
2553        collection: String,
2554        key: String,
2555        value: Value,
2556        tags: Vec<String>,
2557    },
2558    History {
2559        collection: String,
2560        key: String,
2561    },
2562    List {
2563        model: CollectionModel,
2564        collection: String,
2565        prefix: Option<String>,
2566        limit: Option<usize>,
2567        offset: usize,
2568    },
2569    Purge {
2570        collection: String,
2571        key: String,
2572    },
2573    Watch {
2574        model: CollectionModel,
2575        collection: String,
2576        key: String,
2577        prefix: bool,
2578        from_lsn: Option<u64>,
2579    },
2580    Delete {
2581        model: CollectionModel,
2582        collection: String,
2583        key: String,
2584    },
2585    /// `KV INCR key [BY n] [EXPIRE dur]` — atomic increment; negative `by` = decrement.
2586    Incr {
2587        model: CollectionModel,
2588        collection: String,
2589        key: String,
2590        /// Step value; negative for DECR. Defaults to 1.
2591        by: i64,
2592        ttl_ms: Option<u64>,
2593    },
2594    /// `KV CAS key EXPECT <expected|NULL> SET <new> [EXPIRE dur]` — compare-and-set.
2595    ///
2596    /// `expected = None` means `EXPECT NULL` (key must be absent).
2597    Cas {
2598        model: CollectionModel,
2599        collection: String,
2600        key: String,
2601        /// The value the caller expects to be current; `None` = key must be absent.
2602        expected: Option<Value>,
2603        new_value: Value,
2604        ttl_ms: Option<u64>,
2605    },
2606}
2607
2608#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2609pub enum ConfigValueType {
2610    Bool,
2611    Int,
2612    String,
2613    Url,
2614    Object,
2615    Array,
2616}
2617
2618impl ConfigValueType {
2619    pub fn as_str(self) -> &'static str {
2620        match self {
2621            Self::Bool => "bool",
2622            Self::Int => "int",
2623            Self::String => "string",
2624            Self::Url => "url",
2625            Self::Object => "object",
2626            Self::Array => "array",
2627        }
2628    }
2629
2630    pub fn parse(input: &str) -> Option<Self> {
2631        match input.to_ascii_lowercase().as_str() {
2632            "bool" | "boolean" => Some(Self::Bool),
2633            "int" | "integer" => Some(Self::Int),
2634            "string" | "str" | "text" => Some(Self::String),
2635            "url" => Some(Self::Url),
2636            "object" | "json_object" => Some(Self::Object),
2637            "array" | "list" => Some(Self::Array),
2638            _ => None,
2639        }
2640    }
2641}
2642
2643#[derive(Debug, Clone)]
2644pub enum ConfigCommand {
2645    Put {
2646        collection: String,
2647        key: String,
2648        value: Value,
2649        value_type: Option<ConfigValueType>,
2650        tags: Vec<String>,
2651    },
2652    Get {
2653        collection: String,
2654        key: String,
2655    },
2656    Resolve {
2657        collection: String,
2658        key: String,
2659    },
2660    Rotate {
2661        collection: String,
2662        key: String,
2663        value: Value,
2664        value_type: Option<ConfigValueType>,
2665        tags: Vec<String>,
2666    },
2667    Delete {
2668        collection: String,
2669        key: String,
2670    },
2671    History {
2672        collection: String,
2673        key: String,
2674    },
2675    List {
2676        collection: String,
2677        prefix: Option<String>,
2678        limit: Option<usize>,
2679        offset: usize,
2680    },
2681    Watch {
2682        collection: String,
2683        key: String,
2684        prefix: bool,
2685        from_lsn: Option<u64>,
2686    },
2687    InvalidVolatileOperation {
2688        operation: String,
2689        collection: String,
2690        key: Option<String>,
2691    },
2692}
2693
2694// ============================================================================
2695// Builders (Fluent API)
2696// ============================================================================