1mod query;
7mod sql;
8#[cfg(test)]
12mod tests;
13mod write;
14
15use crate::{
16 db::{
17 Db, EntityFieldDescription, EntitySchemaDescription, FluentDeleteQuery, FluentLoadQuery,
18 IntegrityReport, MigrationPlan, MigrationRunOutcome, MissingRowPolicy, PlanError, Query,
19 QueryError, StorageReport, StoreRegistry, WriteBatchResponse,
20 commit::EntityRuntimeHooks,
21 cursor::decode_optional_cursor_token,
22 executor::{DeleteExecutor, ExecutorPlanError, LoadExecutor, SaveExecutor},
23 schema::{describe_entity_model, show_indexes_for_model},
24 },
25 error::InternalError,
26 metrics::sink::{MetricsSink, with_metrics_sink},
27 traits::{CanisterKind, EntityKind, EntityValue},
28 value::Value,
29};
30use std::thread::LocalKey;
31
32pub use sql::SqlStatementRoute;
33
34fn map_executor_plan_error(err: ExecutorPlanError) -> QueryError {
36 match err {
37 ExecutorPlanError::Cursor(err) => QueryError::from(PlanError::from(*err)),
38 }
39}
40
41fn decode_optional_cursor_bytes(cursor_token: Option<&str>) -> Result<Option<Vec<u8>>, QueryError> {
44 decode_optional_cursor_token(cursor_token).map_err(|err| QueryError::from(PlanError::from(err)))
45}
46
47pub struct DbSession<C: CanisterKind> {
54 db: Db<C>,
55 debug: bool,
56 metrics: Option<&'static dyn MetricsSink>,
57}
58
59impl<C: CanisterKind> DbSession<C> {
60 #[must_use]
62 pub(crate) const fn new(db: Db<C>) -> Self {
63 Self {
64 db,
65 debug: false,
66 metrics: None,
67 }
68 }
69
70 #[must_use]
72 pub const fn new_with_hooks(
73 store: &'static LocalKey<StoreRegistry>,
74 entity_runtime_hooks: &'static [EntityRuntimeHooks<C>],
75 ) -> Self {
76 Self::new(Db::new_with_hooks(store, entity_runtime_hooks))
77 }
78
79 #[must_use]
81 pub const fn debug(mut self) -> Self {
82 self.debug = true;
83 self
84 }
85
86 #[must_use]
88 pub const fn metrics_sink(mut self, sink: &'static dyn MetricsSink) -> Self {
89 self.metrics = Some(sink);
90 self
91 }
92
93 fn with_metrics<T>(&self, f: impl FnOnce() -> T) -> T {
94 if let Some(sink) = self.metrics {
95 with_metrics_sink(sink, f)
96 } else {
97 f()
98 }
99 }
100
101 fn execute_save_with<E, T, R>(
103 &self,
104 op: impl FnOnce(SaveExecutor<E>) -> Result<T, InternalError>,
105 map: impl FnOnce(T) -> R,
106 ) -> Result<R, InternalError>
107 where
108 E: EntityKind<Canister = C> + EntityValue,
109 {
110 let value = self.with_metrics(|| op(self.save_executor::<E>()))?;
111
112 Ok(map(value))
113 }
114
115 fn execute_save_entity<E>(
117 &self,
118 op: impl FnOnce(SaveExecutor<E>) -> Result<E, InternalError>,
119 ) -> Result<E, InternalError>
120 where
121 E: EntityKind<Canister = C> + EntityValue,
122 {
123 self.execute_save_with(op, std::convert::identity)
124 }
125
126 fn execute_save_batch<E>(
127 &self,
128 op: impl FnOnce(SaveExecutor<E>) -> Result<Vec<E>, InternalError>,
129 ) -> Result<WriteBatchResponse<E>, InternalError>
130 where
131 E: EntityKind<Canister = C> + EntityValue,
132 {
133 self.execute_save_with(op, WriteBatchResponse::new)
134 }
135
136 fn execute_save_view<E>(
137 &self,
138 op: impl FnOnce(SaveExecutor<E>) -> Result<E::ViewType, InternalError>,
139 ) -> Result<E::ViewType, InternalError>
140 where
141 E: EntityKind<Canister = C> + EntityValue,
142 {
143 self.execute_save_with(op, std::convert::identity)
144 }
145
146 #[must_use]
152 pub const fn load<E>(&self) -> FluentLoadQuery<'_, E>
153 where
154 E: EntityKind<Canister = C>,
155 {
156 FluentLoadQuery::new(self, Query::new(MissingRowPolicy::Ignore))
157 }
158
159 #[must_use]
161 pub const fn load_with_consistency<E>(
162 &self,
163 consistency: MissingRowPolicy,
164 ) -> FluentLoadQuery<'_, E>
165 where
166 E: EntityKind<Canister = C>,
167 {
168 FluentLoadQuery::new(self, Query::new(consistency))
169 }
170
171 #[must_use]
173 pub fn delete<E>(&self) -> FluentDeleteQuery<'_, E>
174 where
175 E: EntityKind<Canister = C>,
176 {
177 FluentDeleteQuery::new(self, Query::new(MissingRowPolicy::Ignore).delete())
178 }
179
180 #[must_use]
182 pub fn delete_with_consistency<E>(
183 &self,
184 consistency: MissingRowPolicy,
185 ) -> FluentDeleteQuery<'_, E>
186 where
187 E: EntityKind<Canister = C>,
188 {
189 FluentDeleteQuery::new(self, Query::new(consistency).delete())
190 }
191
192 #[must_use]
196 pub const fn select_one(&self) -> Value {
197 Value::Int(1)
198 }
199
200 #[must_use]
207 pub fn show_indexes<E>(&self) -> Vec<String>
208 where
209 E: EntityKind<Canister = C>,
210 {
211 show_indexes_for_model(E::MODEL)
212 }
213
214 #[must_use]
216 pub fn show_columns<E>(&self) -> Vec<EntityFieldDescription>
217 where
218 E: EntityKind<Canister = C>,
219 {
220 describe_entity_model(E::MODEL).fields().to_vec()
221 }
222
223 #[must_use]
225 pub fn show_entities(&self) -> Vec<String> {
226 self.db.runtime_entity_names()
227 }
228
229 #[must_use]
234 pub fn describe_entity<E>(&self) -> EntitySchemaDescription
235 where
236 E: EntityKind<Canister = C>,
237 {
238 describe_entity_model(E::MODEL)
239 }
240
241 pub fn storage_report(
243 &self,
244 name_to_path: &[(&'static str, &'static str)],
245 ) -> Result<StorageReport, InternalError> {
246 self.db.storage_report(name_to_path)
247 }
248
249 pub fn integrity_report(&self) -> Result<IntegrityReport, InternalError> {
251 self.db.integrity_report()
252 }
253
254 pub fn execute_migration_plan(
259 &self,
260 plan: &MigrationPlan,
261 max_steps: usize,
262 ) -> Result<MigrationRunOutcome, InternalError> {
263 self.with_metrics(|| self.db.execute_migration_plan(plan, max_steps))
264 }
265
266 #[must_use]
271 pub(in crate::db) const fn load_executor<E>(&self) -> LoadExecutor<E>
272 where
273 E: EntityKind<Canister = C> + EntityValue,
274 {
275 LoadExecutor::new(self.db, self.debug)
276 }
277
278 #[must_use]
279 pub(in crate::db) const fn delete_executor<E>(&self) -> DeleteExecutor<E>
280 where
281 E: EntityKind<Canister = C> + EntityValue,
282 {
283 DeleteExecutor::new(self.db, self.debug)
284 }
285
286 #[must_use]
287 pub(in crate::db) const fn save_executor<E>(&self) -> SaveExecutor<E>
288 where
289 E: EntityKind<Canister = C> + EntityValue,
290 {
291 SaveExecutor::new(self.db, self.debug)
292 }
293}