1mod accepted_schema;
7mod bounded_cache;
8mod catalog;
9mod query;
10mod response;
11#[cfg(feature = "sql")]
12mod sql;
13#[cfg(all(test, feature = "sql"))]
17mod tests;
18mod write;
19
20use crate::{
21 db::{
22 Db, EntityRuntimeHooks, FluentDeleteQuery, FluentLoadQuery, MissingRowPolicy, PersistedRow,
23 Query, StoreRegistry, WriteBatchResponse,
24 commit::CommitSchemaFingerprint,
25 executor::{DeleteExecutor, LoadExecutor, SaveExecutor},
26 schema::{AcceptedRowDecodeContract, SchemaInfo},
27 },
28 error::InternalError,
29 metrics::sink::{ExecKind, MetricsSink, record_exec_error_for_path, with_metrics_sink},
30 traits::{CanisterKind, EntityKind, EntityValue},
31 value::Value,
32};
33use std::thread::LocalKey;
34
35#[cfg(all(test, feature = "sql"))]
36pub(in crate::db) use accepted_schema::AcceptedCatalogRuntimeCounterSnapshot;
37pub(in crate::db) use accepted_schema::AcceptedSchemaCatalogContext;
38#[cfg(feature = "diagnostics")]
39pub use query::{
40 DirectDataRowAttribution, FluentTerminalExecutionAttribution, GroupedCountAttribution,
41 GroupedExecutionAttribution, KernelRowAttribution, QueryExecutionAttribution,
42 ScalarAggregateAttribution,
43};
44pub(in crate::db) use response::finalize_scalar_paged_execution;
45pub(in crate::db) use response::finalize_structural_grouped_projection_result;
46#[cfg(feature = "sql")]
47pub(in crate::db) use response::sql_grouped_cursor_from_bytes;
48#[cfg(feature = "sql")]
49pub use sql::{
50 SqlAdminBulkDeletePlan, SqlAdminBulkUpdatePlan, SqlDdlExecutionStatus, SqlDdlMutationKind,
51 SqlDdlPreparationReport, SqlDeleteExposurePolicy, SqlDeletePolicyContext,
52 SqlDeletePolicyRejection, SqlDeletePolicyReport, SqlDeleteStatementClassification,
53 SqlPublicBoundedDeletePlan, SqlPublicBoundedUpdatePlan, SqlPublicPrimaryKeyDeletePlan,
54 SqlPublicPrimaryKeyUpdatePlan, SqlSessionCurrentDeletePlan, SqlSessionCurrentUpdatePlan,
55 SqlStatementDispatch, SqlStatementResult, SqlStatementShellSurface, SqlStatementSurface,
56 SqlUpdateAssignmentPolicy, SqlUpdateExposurePolicy, SqlUpdatePolicyContext,
57 SqlUpdatePolicyRejection, SqlUpdatePolicyReport, SqlUpdateStatementClassification,
58 SqlValidatedDeletePlan, SqlValidatedUpdatePlan, SqlWriteExecutionBounds, SqlWriteOrderProof,
59 SqlWriteReturningBounds, SqlWriteReturningShape, SqlWriteStatementShape, SqlWriteWhereProof,
60 classify_sql_delete_policy, classify_sql_update_policy, sql_statement_dispatch,
61 sql_statement_entity_name, sql_statement_shell_surface, sql_statement_surface,
62};
63#[cfg(all(feature = "sql", feature = "diagnostics"))]
64pub use sql::{
65 SqlCompileAttribution, SqlExecutionAttribution, SqlHybridCoveringAttribution,
66 SqlOutputBlobAttribution, SqlPureCoveringAttribution, SqlQueryCacheAttribution,
67 SqlQueryExecutionAttribution, SqlScalarAggregateAttribution,
68};
69#[cfg(all(feature = "sql", feature = "diagnostics"))]
70pub use sql::{SqlProjectionMaterializationMetrics, with_sql_projection_materialization_metrics};
71
72pub struct DbSession<C: CanisterKind> {
79 db: Db<C>,
80 debug: bool,
81 metrics: Option<&'static dyn MetricsSink>,
82}
83
84impl<C: CanisterKind> DbSession<C> {
85 #[must_use]
87 pub(crate) const fn new(db: Db<C>) -> Self {
88 Self {
89 db,
90 debug: false,
91 metrics: None,
92 }
93 }
94
95 #[must_use]
97 pub const fn new_with_hooks(
98 store: &'static LocalKey<StoreRegistry>,
99 entity_runtime_hooks: &'static [EntityRuntimeHooks<C>],
100 ) -> Self {
101 Self::new(Db::new_with_hooks(store, entity_runtime_hooks))
102 }
103
104 #[must_use]
106 pub const fn debug(mut self) -> Self {
107 self.debug = true;
108 self
109 }
110
111 #[must_use]
113 pub const fn metrics_sink(mut self, sink: &'static dyn MetricsSink) -> Self {
114 self.metrics = Some(sink);
115 self
116 }
117
118 const fn fluent_load_query<E>(&self, consistency: MissingRowPolicy) -> FluentLoadQuery<'_, E>
121 where
122 E: EntityKind<Canister = C>,
123 {
124 FluentLoadQuery::new(self, Query::new(consistency))
125 }
126
127 fn fluent_delete_query<E>(&self, consistency: MissingRowPolicy) -> FluentDeleteQuery<'_, E>
131 where
132 E: PersistedRow<Canister = C>,
133 {
134 FluentDeleteQuery::new(self, Query::new(consistency).delete())
135 }
136
137 fn with_metrics<T>(&self, f: impl FnOnce() -> T) -> T {
138 if let Some(sink) = self.metrics {
139 with_metrics_sink(sink, f)
140 } else {
141 f()
142 }
143 }
144
145 fn execute_save_with<E, T, R>(
147 &self,
148 op: impl FnOnce(SaveExecutor<E>) -> Result<T, InternalError>,
149 map: impl FnOnce(T) -> R,
150 ) -> Result<R, InternalError>
151 where
152 E: PersistedRow<Canister = C> + EntityValue,
153 {
154 let (contract, schema_info, schema_fingerprint) = match self
155 .with_metrics(|| self.ensure_generated_compatible_accepted_save_schema::<E>())
156 {
157 Ok(authority) => authority,
158 Err(error) => {
159 self.with_metrics(|| record_exec_error_for_path(ExecKind::Save, E::PATH, &error));
160
161 return Err(error);
162 }
163 };
164 let value = self.with_metrics(|| {
165 op(self.save_executor::<E>(contract, schema_info, schema_fingerprint))
166 })?;
167
168 Ok(map(value))
169 }
170
171 fn execute_save_with_checked_accepted_row_contract<E, T, R>(
176 &self,
177 accepted_row_decode_contract: AcceptedRowDecodeContract,
178 accepted_schema_info: SchemaInfo,
179 accepted_schema_fingerprint: CommitSchemaFingerprint,
180 op: impl FnOnce(SaveExecutor<E>) -> Result<T, InternalError>,
181 map: impl FnOnce(T) -> R,
182 ) -> Result<R, InternalError>
183 where
184 E: PersistedRow<Canister = C> + EntityValue,
185 {
186 let value = self.with_metrics(|| {
187 op(self.save_executor::<E>(
188 accepted_row_decode_contract,
189 accepted_schema_info,
190 accepted_schema_fingerprint,
191 ))
192 })?;
193
194 Ok(map(value))
195 }
196
197 fn execute_save_entity<E>(
199 &self,
200 op: impl FnOnce(SaveExecutor<E>) -> Result<E, InternalError>,
201 ) -> Result<E, InternalError>
202 where
203 E: PersistedRow<Canister = C> + EntityValue,
204 {
205 self.execute_save_with(op, std::convert::identity)
206 }
207
208 fn execute_save_batch<E>(
209 &self,
210 op: impl FnOnce(SaveExecutor<E>) -> Result<Vec<E>, InternalError>,
211 ) -> Result<WriteBatchResponse<E>, InternalError>
212 where
213 E: PersistedRow<Canister = C> + EntityValue,
214 {
215 self.execute_save_with(op, WriteBatchResponse::new)
216 }
217
218 #[must_use]
224 pub const fn load<E>(&self) -> FluentLoadQuery<'_, E>
225 where
226 E: EntityKind<Canister = C>,
227 {
228 self.fluent_load_query(MissingRowPolicy::Ignore)
229 }
230
231 #[must_use]
233 pub const fn load_with_consistency<E>(
234 &self,
235 consistency: MissingRowPolicy,
236 ) -> FluentLoadQuery<'_, E>
237 where
238 E: EntityKind<Canister = C>,
239 {
240 self.fluent_load_query(consistency)
241 }
242
243 #[must_use]
245 pub fn delete<E>(&self) -> FluentDeleteQuery<'_, E>
246 where
247 E: PersistedRow<Canister = C>,
248 {
249 self.fluent_delete_query(MissingRowPolicy::Ignore)
250 }
251
252 #[must_use]
254 pub fn delete_with_consistency<E>(
255 &self,
256 consistency: MissingRowPolicy,
257 ) -> FluentDeleteQuery<'_, E>
258 where
259 E: PersistedRow<Canister = C>,
260 {
261 self.fluent_delete_query(consistency)
262 }
263
264 #[must_use]
268 pub const fn select_one(&self) -> Value {
269 Value::Int64(1)
270 }
271
272 #[must_use]
277 pub(in crate::db) const fn load_executor<E>(&self) -> LoadExecutor<E>
278 where
279 E: EntityKind<Canister = C> + EntityValue,
280 {
281 LoadExecutor::new(self.db, self.debug)
282 }
283
284 #[must_use]
285 pub(in crate::db) const fn delete_executor<E>(&self) -> DeleteExecutor<E>
286 where
287 E: PersistedRow<Canister = C> + EntityValue,
288 {
289 DeleteExecutor::new(self.db)
290 }
291
292 #[must_use]
293 pub(in crate::db) const fn save_executor<E>(
294 &self,
295 accepted_row_decode_contract: AcceptedRowDecodeContract,
296 accepted_schema_info: SchemaInfo,
297 accepted_schema_fingerprint: CommitSchemaFingerprint,
298 ) -> SaveExecutor<E>
299 where
300 E: PersistedRow<Canister = C> + EntityValue,
301 {
302 SaveExecutor::new_with_accepted_contract(
303 self.db,
304 self.debug,
305 accepted_row_decode_contract,
306 accepted_schema_info,
307 accepted_schema_fingerprint,
308 )
309 }
310}