icydb_core/db/session/sql/
mod.rs1mod execute;
8mod explain;
9mod projection;
10
11use crate::{
12 db::{
13 DbSession, GroupedRow, PersistedRow, QueryError,
14 executor::EntityAuthority,
15 query::{
16 intent::StructuralQuery,
17 plan::{AccessPlannedQuery, VisibleIndexes},
18 },
19 sql::parser::{SqlStatement, parse_sql},
20 },
21 traits::{CanisterKind, EntityValue},
22};
23
24#[cfg(test)]
25use crate::db::{
26 MissingRowPolicy, PagedGroupedExecutionWithTrace,
27 sql::lowering::{
28 LoweredSelectQueryShape, bind_lowered_sql_query, lower_sql_command_from_prepared_statement,
29 prepare_sql_statement,
30 },
31};
32
33#[cfg(feature = "structural-read-metrics")]
34pub use crate::db::session::sql::projection::{
35 SqlProjectionMaterializationMetrics, with_sql_projection_materialization_metrics,
36};
37#[cfg(feature = "perf-attribution")]
38pub use crate::db::{
39 session::sql::execute::LoweredSqlStatementExecutorAttribution,
40 session::sql::projection::SqlProjectionTextExecutorAttribution,
41};
42
43#[derive(Debug)]
45pub enum SqlStatementResult {
46 Count {
47 row_count: u32,
48 },
49 Projection {
50 columns: Vec<String>,
51 rows: Vec<Vec<crate::value::Value>>,
52 row_count: u32,
53 },
54 ProjectionText {
55 columns: Vec<String>,
56 rows: Vec<Vec<String>>,
57 row_count: u32,
58 },
59 Grouped {
60 columns: Vec<String>,
61 rows: Vec<GroupedRow>,
62 row_count: u32,
63 next_cursor: Option<String>,
64 },
65 Explain(String),
66 Describe(crate::db::EntitySchemaDescription),
67 ShowIndexes(Vec<String>),
68 ShowColumns(Vec<crate::db::EntityFieldDescription>),
69 ShowEntities(Vec<String>),
70}
71
72pub(in crate::db) fn parse_sql_statement(sql: &str) -> Result<SqlStatement, QueryError> {
75 parse_sql(sql).map_err(QueryError::from_sql_parse_error)
76}
77
78impl<C: CanisterKind> DbSession<C> {
79 pub(in crate::db::session::sql) fn build_structural_plan_with_visible_indexes_for_authority(
82 &self,
83 query: StructuralQuery,
84 authority: EntityAuthority,
85 ) -> Result<(VisibleIndexes<'_>, AccessPlannedQuery), QueryError> {
86 let visible_indexes =
87 self.visible_indexes_for_store_model(authority.store_path(), authority.model())?;
88 let plan = query.build_plan_with_visible_indexes(&visible_indexes)?;
89
90 Ok((visible_indexes, plan))
91 }
92
93 fn ensure_sql_query_statement_supported(statement: &SqlStatement) -> Result<(), QueryError> {
96 match statement {
97 SqlStatement::Select(_)
98 | SqlStatement::Explain(_)
99 | SqlStatement::Describe(_)
100 | SqlStatement::ShowIndexes(_)
101 | SqlStatement::ShowColumns(_)
102 | SqlStatement::ShowEntities(_) => Ok(()),
103 SqlStatement::Insert(_) => Err(QueryError::unsupported_query(
104 "execute_sql_query rejects INSERT; use execute_sql_update::<E>()",
105 )),
106 SqlStatement::Update(_) => Err(QueryError::unsupported_query(
107 "execute_sql_query rejects UPDATE; use execute_sql_update::<E>()",
108 )),
109 SqlStatement::Delete(_) => Err(QueryError::unsupported_query(
110 "execute_sql_query rejects DELETE; use execute_sql_update::<E>()",
111 )),
112 }
113 }
114
115 fn ensure_sql_update_statement_supported(statement: &SqlStatement) -> Result<(), QueryError> {
118 match statement {
119 SqlStatement::Insert(_) | SqlStatement::Update(_) | SqlStatement::Delete(_) => Ok(()),
120 SqlStatement::Select(_) => Err(QueryError::unsupported_query(
121 "execute_sql_update rejects SELECT; use execute_sql_query::<E>()",
122 )),
123 SqlStatement::Explain(_) => Err(QueryError::unsupported_query(
124 "execute_sql_update rejects EXPLAIN; use execute_sql_query::<E>()",
125 )),
126 SqlStatement::Describe(_) => Err(QueryError::unsupported_query(
127 "execute_sql_update rejects DESCRIBE; use execute_sql_query::<E>()",
128 )),
129 SqlStatement::ShowIndexes(_) => Err(QueryError::unsupported_query(
130 "execute_sql_update rejects SHOW INDEXES; use execute_sql_query::<E>()",
131 )),
132 SqlStatement::ShowColumns(_) => Err(QueryError::unsupported_query(
133 "execute_sql_update rejects SHOW COLUMNS; use execute_sql_query::<E>()",
134 )),
135 SqlStatement::ShowEntities(_) => Err(QueryError::unsupported_query(
136 "execute_sql_update rejects SHOW ENTITIES; use execute_sql_query::<E>()",
137 )),
138 }
139 }
140
141 pub fn execute_sql_query<E>(&self, sql: &str) -> Result<SqlStatementResult, QueryError>
146 where
147 E: PersistedRow<Canister = C> + EntityValue,
148 {
149 let parsed = parse_sql_statement(sql)?;
150
151 Self::ensure_sql_query_statement_supported(&parsed)?;
152
153 self.execute_sql_statement_inner::<E>(&parsed)
154 }
155
156 pub fn execute_sql_update<E>(&self, sql: &str) -> Result<SqlStatementResult, QueryError>
161 where
162 E: PersistedRow<Canister = C> + EntityValue,
163 {
164 let parsed = parse_sql_statement(sql)?;
165
166 Self::ensure_sql_update_statement_supported(&parsed)?;
167
168 self.execute_sql_statement_inner::<E>(&parsed)
169 }
170
171 #[cfg(test)]
172 pub(in crate::db) fn execute_grouped_sql_query_for_tests<E>(
173 &self,
174 sql: &str,
175 cursor_token: Option<&str>,
176 ) -> Result<PagedGroupedExecutionWithTrace, QueryError>
177 where
178 E: PersistedRow<Canister = C> + EntityValue,
179 {
180 let parsed = parse_sql_statement(sql)?;
181
182 let lowered = lower_sql_command_from_prepared_statement(
183 prepare_sql_statement(parsed, E::MODEL.name())
184 .map_err(QueryError::from_sql_lowering_error)?,
185 E::MODEL.primary_key.name,
186 )
187 .map_err(QueryError::from_sql_lowering_error)?;
188 let Some(query) = lowered.query().cloned() else {
189 return Err(QueryError::unsupported_query(
190 "grouped SELECT helper requires grouped SELECT",
191 ));
192 };
193 if query.select_shape() != Some(LoweredSelectQueryShape::Grouped) {
194 return Err(QueryError::unsupported_query(
195 "grouped SELECT helper requires grouped SELECT",
196 ));
197 }
198 let query = bind_lowered_sql_query::<E>(query, MissingRowPolicy::Ignore)
199 .map_err(QueryError::from_sql_lowering_error)?;
200
201 self.execute_grouped(&query, cursor_token)
202 }
203}