Skip to main content

icydb_core/db/session/sql/surface/
route.rs

1//! Module: db::session::sql::surface::route
2//! Responsibility: module-local ownership and contracts for db::session::sql::surface::route.
3//! Does not own: cross-module orchestration outside this module.
4//! Boundary: exposes this module API while keeping implementation details internal.
5
6use crate::db::{
7    GroupedRow, QueryError,
8    sql::lowering::{
9        LoweredSqlCommand, LoweredSqlLaneKind, PreparedSqlStatement as CorePreparedSqlStatement,
10        lower_sql_command_from_prepared_statement, lowered_sql_command_lane, prepare_sql_statement,
11    },
12    sql::parser::{SqlExplainTarget, SqlStatement},
13};
14
15/// Canonical SQL statement routing metadata derived from reduced SQL parser output.
16///
17/// Carries surface kind (`Query` / `Explain` / `Describe` / `ShowIndexes` /
18/// `ShowColumns` / `ShowEntities`) and canonical parsed entity identifier.
19#[derive(Clone, Debug, Eq, PartialEq)]
20pub enum SqlStatementRoute {
21    Query { entity: String },
22    Explain { entity: String },
23    Describe { entity: String },
24    ShowIndexes { entity: String },
25    ShowColumns { entity: String },
26    ShowEntities,
27}
28
29/// Unified SQL dispatch payload returned by shared SQL lane execution.
30#[derive(Debug)]
31pub enum SqlDispatchResult {
32    Projection {
33        columns: Vec<String>,
34        rows: Vec<Vec<crate::value::Value>>,
35        row_count: u32,
36    },
37    ProjectionText {
38        columns: Vec<String>,
39        rows: Vec<Vec<String>>,
40        row_count: u32,
41    },
42    Grouped {
43        columns: Vec<String>,
44        rows: Vec<GroupedRow>,
45        row_count: u32,
46        next_cursor: Option<String>,
47    },
48    Explain(String),
49    Describe(crate::db::EntitySchemaDescription),
50    ShowIndexes(Vec<String>),
51    ShowColumns(Vec<crate::db::EntityFieldDescription>),
52    ShowEntities(Vec<String>),
53}
54
55///
56/// SqlParsedStatement
57///
58/// Opaque parsed SQL statement envelope with stable route metadata.
59/// This allows callers to parse once and reuse parsed authority across route
60/// classification and typed dispatch lowering.
61///
62
63#[derive(Clone, Debug)]
64pub struct SqlParsedStatement {
65    pub(in crate::db::session::sql) statement: SqlStatement,
66    route: SqlStatementRoute,
67}
68
69impl SqlParsedStatement {
70    // Construct one parsed SQL statement envelope inside the session SQL boundary.
71    pub(in crate::db::session::sql) const fn new(
72        statement: SqlStatement,
73        route: SqlStatementRoute,
74    ) -> Self {
75        Self { statement, route }
76    }
77
78    /// Borrow canonical route metadata for this parsed statement.
79    #[must_use]
80    pub const fn route(&self) -> &SqlStatementRoute {
81        &self.route
82    }
83
84    // Prepare this parsed statement for one concrete entity route.
85    pub(in crate::db::session::sql) fn prepare(
86        &self,
87        expected_entity: &'static str,
88    ) -> Result<CorePreparedSqlStatement, QueryError> {
89        prepare_sql_statement(self.statement.clone(), expected_entity)
90            .map_err(QueryError::from_sql_lowering_error)
91    }
92
93    /// Lower this parsed statement into one shared query-lane shape.
94    #[inline(never)]
95    pub fn lower_query_lane_for_entity(
96        &self,
97        expected_entity: &'static str,
98        primary_key_field: &str,
99    ) -> Result<LoweredSqlCommand, QueryError> {
100        let lowered = lower_sql_command_from_prepared_statement(
101            self.prepare(expected_entity)?,
102            primary_key_field,
103        )
104        .map_err(QueryError::from_sql_lowering_error)?;
105        let lane = lowered_sql_command_lane(&lowered);
106
107        match lane {
108            LoweredSqlLaneKind::Query | LoweredSqlLaneKind::Explain => Ok(lowered),
109            LoweredSqlLaneKind::Describe
110            | LoweredSqlLaneKind::ShowIndexes
111            | LoweredSqlLaneKind::ShowColumns
112            | LoweredSqlLaneKind::ShowEntities => {
113                Err(QueryError::unsupported_query_lane_dispatch())
114            }
115        }
116    }
117}
118
119impl SqlStatementRoute {
120    /// Borrow the parsed SQL entity identifier for this statement.
121    ///
122    /// `SHOW ENTITIES` does not carry an entity identifier and returns an
123    /// empty string for this accessor.
124    #[must_use]
125    pub const fn entity(&self) -> &str {
126        match self {
127            Self::Query { entity }
128            | Self::Explain { entity }
129            | Self::Describe { entity }
130            | Self::ShowIndexes { entity }
131            | Self::ShowColumns { entity } => entity.as_str(),
132            Self::ShowEntities => "",
133        }
134    }
135
136    /// Return whether this route targets the EXPLAIN surface.
137    #[must_use]
138    pub const fn is_explain(&self) -> bool {
139        matches!(self, Self::Explain { .. })
140    }
141
142    /// Return whether this route targets the DESCRIBE surface.
143    #[must_use]
144    pub const fn is_describe(&self) -> bool {
145        matches!(self, Self::Describe { .. })
146    }
147
148    /// Return whether this route targets the `SHOW INDEXES` surface.
149    #[must_use]
150    pub const fn is_show_indexes(&self) -> bool {
151        matches!(self, Self::ShowIndexes { .. })
152    }
153
154    /// Return whether this route targets the `SHOW COLUMNS` surface.
155    #[must_use]
156    pub const fn is_show_columns(&self) -> bool {
157        matches!(self, Self::ShowColumns { .. })
158    }
159
160    /// Return whether this route targets the `SHOW ENTITIES` surface.
161    #[must_use]
162    pub const fn is_show_entities(&self) -> bool {
163        matches!(self, Self::ShowEntities)
164    }
165}
166
167// Resolve one parsed reduced SQL statement to canonical surface route metadata.
168pub(in crate::db::session::sql) fn sql_statement_route_from_statement(
169    statement: &SqlStatement,
170) -> SqlStatementRoute {
171    match statement {
172        SqlStatement::Select(select) => SqlStatementRoute::Query {
173            entity: select.entity.clone(),
174        },
175        SqlStatement::Delete(delete) => SqlStatementRoute::Query {
176            entity: delete.entity.clone(),
177        },
178        SqlStatement::Explain(explain) => match &explain.statement {
179            SqlExplainTarget::Select(select) => SqlStatementRoute::Explain {
180                entity: select.entity.clone(),
181            },
182            SqlExplainTarget::Delete(delete) => SqlStatementRoute::Explain {
183                entity: delete.entity.clone(),
184            },
185        },
186        SqlStatement::Describe(describe) => SqlStatementRoute::Describe {
187            entity: describe.entity.clone(),
188        },
189        SqlStatement::ShowIndexes(show_indexes) => SqlStatementRoute::ShowIndexes {
190            entity: show_indexes.entity.clone(),
191        },
192        SqlStatement::ShowColumns(show_columns) => SqlStatementRoute::ShowColumns {
193            entity: show_columns.entity.clone(),
194        },
195        SqlStatement::ShowEntities(_) => SqlStatementRoute::ShowEntities,
196    }
197}