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, EntityKind, EntityValue},
22};
23
24#[cfg(test)]
25use crate::db::{
26 MissingRowPolicy, PagedGroupedExecutionWithTrace,
27 sql::lowering::{
28 bind_lowered_sql_query, lower_sql_command_from_prepared_statement, prepare_sql_statement,
29 },
30};
31
32#[cfg(feature = "structural-read-metrics")]
33pub use crate::db::session::sql::projection::{
34 SqlProjectionMaterializationMetrics, with_sql_projection_materialization_metrics,
35};
36#[cfg(feature = "perf-attribution")]
37pub use crate::db::{
38 session::sql::execute::LoweredSqlStatementExecutorAttribution,
39 session::sql::projection::SqlProjectionTextExecutorAttribution,
40};
41
42#[derive(Debug)]
44pub enum SqlStatementResult {
45 Count {
46 row_count: u32,
47 },
48 Projection {
49 columns: Vec<String>,
50 rows: Vec<Vec<crate::value::Value>>,
51 row_count: u32,
52 },
53 ProjectionText {
54 columns: Vec<String>,
55 rows: Vec<Vec<String>>,
56 row_count: u32,
57 },
58 Grouped {
59 columns: Vec<String>,
60 rows: Vec<GroupedRow>,
61 row_count: u32,
62 next_cursor: Option<String>,
63 },
64 Explain(String),
65 Describe(crate::db::EntitySchemaDescription),
66 ShowIndexes(Vec<String>),
67 ShowColumns(Vec<crate::db::EntityFieldDescription>),
68 ShowEntities(Vec<String>),
69}
70
71pub(in crate::db) fn parse_sql_statement(sql: &str) -> Result<SqlStatement, QueryError> {
74 parse_sql(sql).map_err(QueryError::from_sql_parse_error)
75}
76
77impl<C: CanisterKind> DbSession<C> {
78 pub(in crate::db::session::sql) fn build_structural_plan_with_visible_indexes_for_authority(
81 &self,
82 query: StructuralQuery,
83 authority: EntityAuthority,
84 ) -> Result<(VisibleIndexes<'_>, AccessPlannedQuery), QueryError> {
85 let visible_indexes =
86 self.visible_indexes_for_store_model(authority.store_path(), authority.model())?;
87 let plan = query.build_plan_with_visible_indexes(&visible_indexes)?;
88
89 Ok((visible_indexes, plan))
90 }
91
92 fn ensure_typed_sql_statement_matches<E>(statement: &SqlStatement) -> Result<(), QueryError>
95 where
96 E: EntityKind<Canister = C>,
97 {
98 let Some(sql_entity) = (match statement {
99 SqlStatement::Select(select) => Some(select.entity.as_str()),
100 SqlStatement::Delete(delete) => Some(delete.entity.as_str()),
101 SqlStatement::Insert(insert) => Some(insert.entity.as_str()),
102 SqlStatement::Update(update) => Some(update.entity.as_str()),
103 SqlStatement::Explain(explain) => Some(match &explain.statement {
104 crate::db::sql::parser::SqlExplainTarget::Select(select) => select.entity.as_str(),
105 crate::db::sql::parser::SqlExplainTarget::Delete(delete) => delete.entity.as_str(),
106 }),
107 SqlStatement::Describe(describe) => Some(describe.entity.as_str()),
108 SqlStatement::ShowIndexes(show_indexes) => Some(show_indexes.entity.as_str()),
109 SqlStatement::ShowColumns(show_columns) => Some(show_columns.entity.as_str()),
110 SqlStatement::ShowEntities(_) => None,
111 }) else {
112 return Ok(());
113 };
114
115 if crate::db::identifiers_tail_match(sql_entity, E::MODEL.name()) {
116 return Ok(());
117 }
118
119 Err(QueryError::unsupported_query(format!(
120 "typed SQL only supports entity '{}', but received '{sql_entity}'",
121 E::MODEL.name()
122 )))
123 }
124
125 fn ensure_sql_query_statement_supported(statement: &SqlStatement) -> Result<(), QueryError> {
128 match statement {
129 SqlStatement::Select(_)
130 | SqlStatement::Explain(_)
131 | SqlStatement::Describe(_)
132 | SqlStatement::ShowIndexes(_)
133 | SqlStatement::ShowColumns(_)
134 | SqlStatement::ShowEntities(_) => Ok(()),
135 SqlStatement::Insert(_) => Err(QueryError::unsupported_query(
136 "execute_sql_query rejects INSERT; use execute_sql_update::<E>()",
137 )),
138 SqlStatement::Update(_) => Err(QueryError::unsupported_query(
139 "execute_sql_query rejects UPDATE; use execute_sql_update::<E>()",
140 )),
141 SqlStatement::Delete(_) => Err(QueryError::unsupported_query(
142 "execute_sql_query rejects DELETE; use execute_sql_update::<E>()",
143 )),
144 }
145 }
146
147 fn ensure_sql_update_statement_supported(statement: &SqlStatement) -> Result<(), QueryError> {
150 match statement {
151 SqlStatement::Insert(_) | SqlStatement::Update(_) | SqlStatement::Delete(_) => Ok(()),
152 SqlStatement::Select(_) => Err(QueryError::unsupported_query(
153 "execute_sql_update rejects SELECT; use execute_sql_query::<E>()",
154 )),
155 SqlStatement::Explain(_) => Err(QueryError::unsupported_query(
156 "execute_sql_update rejects EXPLAIN; use execute_sql_query::<E>()",
157 )),
158 SqlStatement::Describe(_) => Err(QueryError::unsupported_query(
159 "execute_sql_update rejects DESCRIBE; use execute_sql_query::<E>()",
160 )),
161 SqlStatement::ShowIndexes(_) => Err(QueryError::unsupported_query(
162 "execute_sql_update rejects SHOW INDEXES; use execute_sql_query::<E>()",
163 )),
164 SqlStatement::ShowColumns(_) => Err(QueryError::unsupported_query(
165 "execute_sql_update rejects SHOW COLUMNS; use execute_sql_query::<E>()",
166 )),
167 SqlStatement::ShowEntities(_) => Err(QueryError::unsupported_query(
168 "execute_sql_update rejects SHOW ENTITIES; use execute_sql_query::<E>()",
169 )),
170 }
171 }
172
173 pub fn execute_sql_query<E>(&self, sql: &str) -> Result<SqlStatementResult, QueryError>
178 where
179 E: PersistedRow<Canister = C> + EntityValue,
180 {
181 let parsed = parse_sql_statement(sql)?;
182
183 Self::ensure_typed_sql_statement_matches::<E>(&parsed)?;
184 Self::ensure_sql_query_statement_supported(&parsed)?;
185
186 self.execute_sql_statement_inner::<E>(&parsed)
187 }
188
189 pub fn execute_sql_update<E>(&self, sql: &str) -> Result<SqlStatementResult, QueryError>
194 where
195 E: PersistedRow<Canister = C> + EntityValue,
196 {
197 let parsed = parse_sql_statement(sql)?;
198
199 Self::ensure_typed_sql_statement_matches::<E>(&parsed)?;
200 Self::ensure_sql_update_statement_supported(&parsed)?;
201
202 self.execute_sql_statement_inner::<E>(&parsed)
203 }
204
205 #[cfg(test)]
206 pub(in crate::db) fn execute_grouped_sql_query_for_tests<E>(
207 &self,
208 sql: &str,
209 cursor_token: Option<&str>,
210 ) -> Result<PagedGroupedExecutionWithTrace, QueryError>
211 where
212 E: PersistedRow<Canister = C> + EntityValue,
213 {
214 let parsed = parse_sql_statement(sql)?;
215
216 let lowered = lower_sql_command_from_prepared_statement(
217 prepare_sql_statement(parsed, E::MODEL.name())
218 .map_err(QueryError::from_sql_lowering_error)?,
219 E::MODEL.primary_key.name,
220 )
221 .map_err(QueryError::from_sql_lowering_error)?;
222 let Some(query) = lowered.query().cloned() else {
223 return Err(QueryError::unsupported_query(
224 "grouped SELECT helper requires grouped SELECT",
225 ));
226 };
227 let query = bind_lowered_sql_query::<E>(query, MissingRowPolicy::Ignore)
228 .map_err(QueryError::from_sql_lowering_error)?;
229
230 if !query.has_grouping() {
231 return Err(QueryError::unsupported_query(
232 "grouped SELECT helper requires grouped SELECT",
233 ));
234 }
235
236 self.execute_grouped(&query, cursor_token)
237 }
238}