icydb_core/db/session/sql/
mod.rs1mod aggregate;
7mod computed_projection;
8mod dispatch;
9mod explain;
10mod projection;
11mod surface;
12
13use crate::{
14 db::{
15 DbSession, EntityResponse, GroupedTextCursorPageWithTrace, MissingRowPolicy,
16 PagedGroupedExecutionWithTrace, PersistedRow, Query, QueryError,
17 executor::EntityAuthority,
18 query::{
19 intent::StructuralQuery,
20 plan::{AccessPlannedQuery, VisibleIndexes},
21 },
22 sql::{
23 lowering::{bind_lowered_sql_query, lower_sql_command_from_prepared_statement},
24 parser::{SqlStatement, parse_sql},
25 },
26 },
27 traits::{CanisterKind, EntityKind, EntityValue},
28};
29
30use crate::db::session::sql::aggregate::{
31 SqlAggregateSurface, parsed_requires_dedicated_sql_aggregate_lane,
32 unsupported_sql_aggregate_lane_message,
33};
34use crate::db::session::sql::surface::{
35 SqlSurface, session_sql_lane, sql_statement_route_from_statement, unsupported_sql_lane_message,
36};
37
38pub use crate::db::session::sql::surface::{
39 SqlDispatchResult, SqlParsedStatement, SqlStatementRoute,
40};
41#[cfg(feature = "perf-attribution")]
42pub use crate::db::{
43 executor::SqlProjectionTextExecutorAttribution,
44 session::sql::dispatch::LoweredSqlDispatchExecutorAttribution,
45};
46
47#[derive(Clone, Copy, Debug, Eq, PartialEq)]
48enum SqlComputedProjectionSurface {
49 QueryFrom,
50 ExecuteSql,
51 ExecuteSqlGrouped,
52}
53
54const fn unsupported_sql_computed_projection_message(
55 surface: SqlComputedProjectionSurface,
56) -> &'static str {
57 match surface {
58 SqlComputedProjectionSurface::QueryFrom => {
59 "query_from_sql does not accept computed text projection; use execute_sql_dispatch(...)"
60 }
61 SqlComputedProjectionSurface::ExecuteSql => {
62 "execute_sql rejects computed text projection; use execute_sql_dispatch(...)"
63 }
64 SqlComputedProjectionSurface::ExecuteSqlGrouped => {
65 "execute_sql_grouped rejects computed text projection; use execute_sql_dispatch(...)"
66 }
67 }
68}
69
70impl<C: CanisterKind> DbSession<C> {
71 pub(in crate::db::session::sql) fn build_structural_plan_with_visible_indexes_for_authority(
74 &self,
75 query: StructuralQuery,
76 authority: EntityAuthority,
77 ) -> Result<(VisibleIndexes<'_>, AccessPlannedQuery), QueryError> {
78 let visible_indexes =
79 self.visible_indexes_for_store_model(authority.store_path(), authority.model())?;
80 let plan = query.build_plan_with_visible_indexes(&visible_indexes)?;
81
82 Ok((visible_indexes, plan))
83 }
84
85 fn query_from_sql_parsed<E>(
88 parsed: &SqlParsedStatement,
89 lane_surface: SqlSurface,
90 computed_surface: SqlComputedProjectionSurface,
91 surface: SqlAggregateSurface,
92 ) -> Result<Query<E>, QueryError>
93 where
94 E: EntityKind<Canister = C>,
95 {
96 if computed_projection::computed_sql_projection_plan(&parsed.statement)?.is_some() {
97 return Err(QueryError::unsupported_query(
98 unsupported_sql_computed_projection_message(computed_surface),
99 ));
100 }
101
102 if parsed_requires_dedicated_sql_aggregate_lane(parsed) {
103 return Err(QueryError::unsupported_query(
104 unsupported_sql_aggregate_lane_message(surface),
105 ));
106 }
107
108 let lowered = lower_sql_command_from_prepared_statement(
109 parsed.prepare(E::MODEL.name())?,
110 E::MODEL.primary_key.name,
111 )
112 .map_err(QueryError::from_sql_lowering_error)?;
113 let lane = session_sql_lane(&lowered);
114 let Some(query) = lowered.query().cloned() else {
115 return Err(QueryError::unsupported_query(unsupported_sql_lane_message(
116 lane_surface,
117 lane,
118 )));
119 };
120 let query = bind_lowered_sql_query::<E>(query, MissingRowPolicy::Ignore)
121 .map_err(QueryError::from_sql_lowering_error)?;
122
123 Ok(query)
124 }
125
126 pub fn parse_sql_statement(&self, sql: &str) -> Result<SqlParsedStatement, QueryError> {
130 let statement = parse_sql(sql).map_err(QueryError::from_sql_parse_error)?;
131 let route = sql_statement_route_from_statement(&statement);
132
133 Ok(SqlParsedStatement::new(statement, route))
134 }
135
136 pub fn sql_statement_route(&self, sql: &str) -> Result<SqlStatementRoute, QueryError> {
141 let parsed = self.parse_sql_statement(sql)?;
142
143 Ok(parsed.route().clone())
144 }
145
146 pub fn query_from_sql<E>(&self, sql: &str) -> Result<Query<E>, QueryError>
151 where
152 E: EntityKind<Canister = C>,
153 {
154 let parsed = self.parse_sql_statement(sql)?;
155
156 Self::query_from_sql_parsed::<E>(
157 &parsed,
158 SqlSurface::QueryFrom,
159 SqlComputedProjectionSurface::QueryFrom,
160 SqlAggregateSurface::QueryFrom,
161 )
162 }
163
164 pub fn execute_sql<E>(&self, sql: &str) -> Result<EntityResponse<E>, QueryError>
166 where
167 E: PersistedRow<Canister = C> + EntityValue,
168 {
169 let parsed = self.parse_sql_statement(sql)?;
170 let query = Self::query_from_sql_parsed::<E>(
171 &parsed,
172 SqlSurface::ExecuteSql,
173 SqlComputedProjectionSurface::ExecuteSql,
174 SqlAggregateSurface::ExecuteSql,
175 )?;
176 Self::ensure_sql_query_grouping(&query, dispatch::SqlGroupingSurface::Scalar)?;
177
178 self.execute_query(&query)
179 }
180
181 pub fn execute_sql_grouped<E>(
183 &self,
184 sql: &str,
185 cursor_token: Option<&str>,
186 ) -> Result<PagedGroupedExecutionWithTrace, QueryError>
187 where
188 E: PersistedRow<Canister = C> + EntityValue,
189 {
190 let parsed = self.parse_sql_statement(sql)?;
191
192 if matches!(&parsed.statement, SqlStatement::Delete(_)) {
193 return Err(QueryError::unsupported_query(
194 "execute_sql_grouped rejects DELETE; use execute_sql_dispatch(...)",
195 ));
196 }
197
198 let query = Self::query_from_sql_parsed::<E>(
199 &parsed,
200 SqlSurface::ExecuteSqlGrouped,
201 SqlComputedProjectionSurface::ExecuteSqlGrouped,
202 SqlAggregateSurface::ExecuteSqlGrouped,
203 )?;
204 Self::ensure_sql_query_grouping(&query, dispatch::SqlGroupingSurface::Grouped)?;
205
206 self.execute_grouped(&query, cursor_token)
207 }
208
209 #[doc(hidden)]
211 pub fn execute_sql_grouped_text_cursor<E>(
212 &self,
213 sql: &str,
214 cursor_token: Option<&str>,
215 ) -> Result<GroupedTextCursorPageWithTrace, QueryError>
216 where
217 E: PersistedRow<Canister = C> + EntityValue,
218 {
219 let parsed = self.parse_sql_statement(sql)?;
220
221 if matches!(&parsed.statement, SqlStatement::Delete(_)) {
222 return Err(QueryError::unsupported_query(
223 "execute_sql_grouped rejects DELETE; use execute_sql_dispatch(...)",
224 ));
225 }
226
227 let query = Self::query_from_sql_parsed::<E>(
228 &parsed,
229 SqlSurface::ExecuteSqlGrouped,
230 SqlComputedProjectionSurface::ExecuteSqlGrouped,
231 SqlAggregateSurface::ExecuteSqlGrouped,
232 )?;
233 Self::ensure_sql_query_grouping(&query, dispatch::SqlGroupingSurface::Grouped)?;
234
235 self.execute_grouped_text_cursor(&query, cursor_token)
236 }
237}