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    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    Explain(String),
43    Describe(crate::db::EntitySchemaDescription),
44    ShowIndexes(Vec<String>),
45    ShowColumns(Vec<crate::db::EntityFieldDescription>),
46    ShowEntities(Vec<String>),
47}
48
49///
50/// SqlParsedStatement
51///
52/// Opaque parsed SQL statement envelope with stable route metadata.
53/// This allows callers to parse once and reuse parsed authority across route
54/// classification and typed dispatch lowering.
55///
56
57#[derive(Clone, Debug)]
58pub struct SqlParsedStatement {
59    pub(in crate::db::session::sql) statement: SqlStatement,
60    route: SqlStatementRoute,
61}
62
63impl SqlParsedStatement {
64    // Construct one parsed SQL statement envelope inside the session SQL boundary.
65    pub(in crate::db::session::sql) const fn new(
66        statement: SqlStatement,
67        route: SqlStatementRoute,
68    ) -> Self {
69        Self { statement, route }
70    }
71
72    /// Borrow canonical route metadata for this parsed statement.
73    #[must_use]
74    pub const fn route(&self) -> &SqlStatementRoute {
75        &self.route
76    }
77
78    // Prepare this parsed statement for one concrete entity route.
79    pub(in crate::db::session::sql) fn prepare(
80        &self,
81        expected_entity: &'static str,
82    ) -> Result<CorePreparedSqlStatement, QueryError> {
83        prepare_sql_statement(self.statement.clone(), expected_entity)
84            .map_err(QueryError::from_sql_lowering_error)
85    }
86
87    /// Lower this parsed statement into one shared query-lane shape.
88    #[inline(never)]
89    pub fn lower_query_lane_for_entity(
90        &self,
91        expected_entity: &'static str,
92        primary_key_field: &str,
93    ) -> Result<LoweredSqlCommand, QueryError> {
94        let lowered = lower_sql_command_from_prepared_statement(
95            self.prepare(expected_entity)?,
96            primary_key_field,
97        )
98        .map_err(QueryError::from_sql_lowering_error)?;
99        let lane = lowered_sql_command_lane(&lowered);
100
101        match lane {
102            LoweredSqlLaneKind::Query | LoweredSqlLaneKind::Explain => Ok(lowered),
103            LoweredSqlLaneKind::Describe
104            | LoweredSqlLaneKind::ShowIndexes
105            | LoweredSqlLaneKind::ShowColumns
106            | LoweredSqlLaneKind::ShowEntities => {
107                Err(QueryError::unsupported_query_lane_dispatch())
108            }
109        }
110    }
111}
112
113impl SqlStatementRoute {
114    /// Borrow the parsed SQL entity identifier for this statement.
115    ///
116    /// `SHOW ENTITIES` does not carry an entity identifier and returns an
117    /// empty string for this accessor.
118    #[must_use]
119    pub const fn entity(&self) -> &str {
120        match self {
121            Self::Query { entity }
122            | Self::Explain { entity }
123            | Self::Describe { entity }
124            | Self::ShowIndexes { entity }
125            | Self::ShowColumns { entity } => entity.as_str(),
126            Self::ShowEntities => "",
127        }
128    }
129
130    /// Return whether this route targets the EXPLAIN surface.
131    #[must_use]
132    pub const fn is_explain(&self) -> bool {
133        matches!(self, Self::Explain { .. })
134    }
135
136    /// Return whether this route targets the DESCRIBE surface.
137    #[must_use]
138    pub const fn is_describe(&self) -> bool {
139        matches!(self, Self::Describe { .. })
140    }
141
142    /// Return whether this route targets the `SHOW INDEXES` surface.
143    #[must_use]
144    pub const fn is_show_indexes(&self) -> bool {
145        matches!(self, Self::ShowIndexes { .. })
146    }
147
148    /// Return whether this route targets the `SHOW COLUMNS` surface.
149    #[must_use]
150    pub const fn is_show_columns(&self) -> bool {
151        matches!(self, Self::ShowColumns { .. })
152    }
153
154    /// Return whether this route targets the `SHOW ENTITIES` surface.
155    #[must_use]
156    pub const fn is_show_entities(&self) -> bool {
157        matches!(self, Self::ShowEntities)
158    }
159}
160
161// Resolve one parsed reduced SQL statement to canonical surface route metadata.
162pub(in crate::db::session::sql) fn sql_statement_route_from_statement(
163    statement: &SqlStatement,
164) -> SqlStatementRoute {
165    match statement {
166        SqlStatement::Select(select) => SqlStatementRoute::Query {
167            entity: select.entity.clone(),
168        },
169        SqlStatement::Delete(delete) => SqlStatementRoute::Query {
170            entity: delete.entity.clone(),
171        },
172        SqlStatement::Explain(explain) => match &explain.statement {
173            SqlExplainTarget::Select(select) => SqlStatementRoute::Explain {
174                entity: select.entity.clone(),
175            },
176            SqlExplainTarget::Delete(delete) => SqlStatementRoute::Explain {
177                entity: delete.entity.clone(),
178            },
179        },
180        SqlStatement::Describe(describe) => SqlStatementRoute::Describe {
181            entity: describe.entity.clone(),
182        },
183        SqlStatement::ShowIndexes(show_indexes) => SqlStatementRoute::ShowIndexes {
184            entity: show_indexes.entity.clone(),
185        },
186        SqlStatement::ShowColumns(show_columns) => SqlStatementRoute::ShowColumns {
187            entity: show_columns.entity.clone(),
188        },
189        SqlStatement::ShowEntities(_) => SqlStatementRoute::ShowEntities,
190    }
191}