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 sql::{
18 lowering::{bind_lowered_sql_query, lower_sql_command_from_prepared_statement},
19 parser::{SqlStatement, parse_sql},
20 },
21 },
22 traits::{CanisterKind, EntityKind, EntityValue},
23};
24
25use crate::db::session::sql::aggregate::{
26 SqlAggregateSurface, parsed_requires_dedicated_sql_aggregate_lane,
27 unsupported_sql_aggregate_lane_message,
28};
29use crate::db::session::sql::surface::{
30 SqlSurface, session_sql_lane, sql_statement_route_from_statement, unsupported_sql_lane_message,
31};
32
33pub use crate::db::session::sql::surface::{
34 SqlDispatchResult, SqlParsedStatement, SqlStatementRoute,
35};
36
37#[derive(Clone, Copy, Debug, Eq, PartialEq)]
38enum SqlComputedProjectionSurface {
39 QueryFrom,
40 ExecuteSql,
41 ExecuteSqlGrouped,
42}
43
44const fn unsupported_sql_computed_projection_message(
45 surface: SqlComputedProjectionSurface,
46) -> &'static str {
47 match surface {
48 SqlComputedProjectionSurface::QueryFrom => {
49 "query_from_sql does not accept computed text projection; use execute_sql_dispatch(...)"
50 }
51 SqlComputedProjectionSurface::ExecuteSql => {
52 "execute_sql rejects computed text projection; use execute_sql_dispatch(...)"
53 }
54 SqlComputedProjectionSurface::ExecuteSqlGrouped => {
55 "execute_sql_grouped rejects computed text projection; use execute_sql_dispatch(...)"
56 }
57 }
58}
59
60impl<C: CanisterKind> DbSession<C> {
61 fn query_from_sql_parsed<E>(
64 parsed: &SqlParsedStatement,
65 lane_surface: SqlSurface,
66 computed_surface: SqlComputedProjectionSurface,
67 surface: SqlAggregateSurface,
68 ) -> Result<Query<E>, QueryError>
69 where
70 E: EntityKind<Canister = C>,
71 {
72 if computed_projection::computed_sql_projection_plan(&parsed.statement)?.is_some() {
73 return Err(QueryError::unsupported_query(
74 unsupported_sql_computed_projection_message(computed_surface),
75 ));
76 }
77
78 if parsed_requires_dedicated_sql_aggregate_lane(parsed) {
79 return Err(QueryError::unsupported_query(
80 unsupported_sql_aggregate_lane_message(surface),
81 ));
82 }
83
84 let lowered = lower_sql_command_from_prepared_statement(
85 parsed.prepare(E::MODEL.name())?,
86 E::MODEL.primary_key.name,
87 )
88 .map_err(QueryError::from_sql_lowering_error)?;
89 let lane = session_sql_lane(&lowered);
90 let Some(query) = lowered.query().cloned() else {
91 return Err(QueryError::unsupported_query(unsupported_sql_lane_message(
92 lane_surface,
93 lane,
94 )));
95 };
96 let query = bind_lowered_sql_query::<E>(query, MissingRowPolicy::Ignore)
97 .map_err(QueryError::from_sql_lowering_error)?;
98
99 Ok(query)
100 }
101
102 pub fn parse_sql_statement(&self, sql: &str) -> Result<SqlParsedStatement, QueryError> {
106 let statement = parse_sql(sql).map_err(QueryError::from_sql_parse_error)?;
107 let route = sql_statement_route_from_statement(&statement);
108
109 Ok(SqlParsedStatement::new(statement, route))
110 }
111
112 pub fn sql_statement_route(&self, sql: &str) -> Result<SqlStatementRoute, QueryError> {
117 let parsed = self.parse_sql_statement(sql)?;
118
119 Ok(parsed.route().clone())
120 }
121
122 pub fn query_from_sql<E>(&self, sql: &str) -> Result<Query<E>, QueryError>
127 where
128 E: EntityKind<Canister = C>,
129 {
130 let parsed = self.parse_sql_statement(sql)?;
131
132 Self::query_from_sql_parsed::<E>(
133 &parsed,
134 SqlSurface::QueryFrom,
135 SqlComputedProjectionSurface::QueryFrom,
136 SqlAggregateSurface::QueryFrom,
137 )
138 }
139
140 pub fn execute_sql<E>(&self, sql: &str) -> Result<EntityResponse<E>, QueryError>
142 where
143 E: PersistedRow<Canister = C> + EntityValue,
144 {
145 let parsed = self.parse_sql_statement(sql)?;
146 let query = Self::query_from_sql_parsed::<E>(
147 &parsed,
148 SqlSurface::ExecuteSql,
149 SqlComputedProjectionSurface::ExecuteSql,
150 SqlAggregateSurface::ExecuteSql,
151 )?;
152 Self::ensure_sql_query_grouping(&query, dispatch::SqlGroupingSurface::Scalar)?;
153
154 self.execute_query(&query)
155 }
156
157 pub fn execute_sql_grouped<E>(
159 &self,
160 sql: &str,
161 cursor_token: Option<&str>,
162 ) -> Result<PagedGroupedExecutionWithTrace, QueryError>
163 where
164 E: PersistedRow<Canister = C> + EntityValue,
165 {
166 let parsed = self.parse_sql_statement(sql)?;
167
168 if matches!(&parsed.statement, SqlStatement::Delete(_)) {
169 return Err(QueryError::unsupported_query(
170 "execute_sql_grouped rejects DELETE; use execute_sql_dispatch(...)",
171 ));
172 }
173
174 let query = Self::query_from_sql_parsed::<E>(
175 &parsed,
176 SqlSurface::ExecuteSqlGrouped,
177 SqlComputedProjectionSurface::ExecuteSqlGrouped,
178 SqlAggregateSurface::ExecuteSqlGrouped,
179 )?;
180 Self::ensure_sql_query_grouping(&query, dispatch::SqlGroupingSurface::Grouped)?;
181
182 self.execute_grouped(&query, cursor_token)
183 }
184
185 #[doc(hidden)]
187 pub fn execute_sql_grouped_text_cursor<E>(
188 &self,
189 sql: &str,
190 cursor_token: Option<&str>,
191 ) -> Result<GroupedTextCursorPageWithTrace, QueryError>
192 where
193 E: PersistedRow<Canister = C> + EntityValue,
194 {
195 let parsed = self.parse_sql_statement(sql)?;
196
197 if matches!(&parsed.statement, SqlStatement::Delete(_)) {
198 return Err(QueryError::unsupported_query(
199 "execute_sql_grouped rejects DELETE; use execute_sql_dispatch(...)",
200 ));
201 }
202
203 let query = Self::query_from_sql_parsed::<E>(
204 &parsed,
205 SqlSurface::ExecuteSqlGrouped,
206 SqlComputedProjectionSurface::ExecuteSqlGrouped,
207 SqlAggregateSurface::ExecuteSqlGrouped,
208 )?;
209 Self::ensure_sql_query_grouping(&query, dispatch::SqlGroupingSurface::Grouped)?;
210
211 self.execute_grouped_text_cursor(&query, cursor_token)
212 }
213}