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