1pub(crate) mod access;
8pub(crate) mod catalog;
9pub(crate) mod cursor;
10pub(crate) mod diagnostics;
11pub(crate) mod identity;
12#[cfg(feature = "diagnostics")]
13pub(in crate::db) mod physical_access;
14pub(crate) mod predicate;
15pub(crate) mod query;
16pub(crate) mod registry;
17pub(crate) mod response;
18pub(crate) mod runtime_hooks;
19pub(crate) mod scalar_expr;
20pub(crate) mod schema;
21pub(crate) mod session;
22#[cfg(feature = "sql")]
23pub(crate) mod sql;
24
25pub(in crate::db) mod codec;
26pub(in crate::db) mod commit;
27pub(in crate::db) mod data;
28pub(in crate::db) mod direction;
29pub(in crate::db) mod executor;
30pub(in crate::db) mod index;
31pub(in crate::db) mod journal;
32pub(in crate::db) mod key_taxonomy;
33pub(in crate::db) mod numeric;
34pub(in crate::db) mod ordered_overlay;
35pub(in crate::db) mod relation;
36pub(in crate::db) mod sql_shared;
37#[cfg(test)]
38pub(in crate::db) mod test_support;
39#[cfg(test)]
40mod tests;
41
42use crate::{
43 db::{
44 commit::{CommitRowOp, PreparedRowCommitOp, ensure_recovered},
45 data::RawDataStoreKey,
46 executor::Context,
47 registry::StoreHandle,
48 schema::{AcceptedSchemaSnapshot, PersistedFieldKind, ensure_accepted_schema_snapshot},
49 },
50 error::InternalError,
51 traits::{CanisterKind, EntityKind, EntityValue},
52 types::EntityTag,
53};
54use std::{collections::BTreeSet, marker::PhantomData, thread::LocalKey};
55
56pub use catalog::{
57 EntityCatalogCounts, EntityCatalogDescription, MemoryCatalogDescription,
58 StoreCatalogDescription,
59};
60#[doc(hidden)]
61pub use codec::hex::encode_hex_lower;
62pub use cursor::{decode_cursor, encode_cursor};
63pub use runtime_hooks::EntityRuntimeHooks;
64pub use data::{DataStore, PersistedRow, SlotReader, SlotWriter, StructuralPatch};
68#[doc(hidden)]
69pub use data::{
70 PersistedScalar, ScalarSlotValueRef, ScalarValueRef,
71 decode_persisted_many_slot_payload_by_meta, decode_persisted_option_scalar_slot_payload,
72 decode_persisted_option_slot_payload_by_kind, decode_persisted_option_slot_payload_by_meta,
73 decode_persisted_scalar_slot_payload, decode_persisted_slot_payload_by_kind,
74 decode_persisted_slot_payload_by_meta, decode_persisted_structured_many_slot_payload,
75 decode_persisted_structured_slot_payload, decode_slot_into_runtime_value,
76 encode_persisted_many_slot_payload_by_meta, encode_persisted_option_scalar_slot_payload,
77 encode_persisted_option_slot_payload_by_meta, encode_persisted_scalar_slot_payload,
78 encode_persisted_slot_payload_by_kind, encode_persisted_slot_payload_by_meta,
79 encode_persisted_structured_many_slot_payload, encode_persisted_structured_slot_payload,
80 encode_runtime_value_into_slot,
81};
82#[cfg(feature = "diagnostics")]
83#[doc(hidden)]
84pub use data::{StructuralReadMetrics, with_structural_read_metrics};
85#[cfg(all(test, not(feature = "diagnostics")))]
86#[expect(unused_imports)]
87pub(crate) use data::{StructuralReadMetrics, with_structural_read_metrics};
88pub use diagnostics::{
89 DataStoreSnapshot, EntitySnapshot, ExecutionAccessPathVariant, ExecutionMetrics,
90 ExecutionOptimization, ExecutionStats, ExecutionTrace, IndexStoreSnapshot, IntegrityReport,
91 IntegrityStoreSnapshot, IntegrityTotals, SchemaStoreSnapshot, StorageReport,
92 StoreSnapshotStorageMode,
93};
94#[doc(hidden)]
95pub use executor::EntityAuthority;
96pub use executor::MutationMode;
97pub use executor::{ExecutionFamily, RouteExecutionMode};
98#[cfg(feature = "diagnostics")]
99#[doc(hidden)]
100pub use executor::{RowCheckMetrics, with_row_check_metrics};
101#[cfg(all(test, not(feature = "diagnostics")))]
102#[expect(unused_imports)]
103pub(crate) use executor::{RowCheckMetrics, with_row_check_metrics};
104#[cfg(feature = "diagnostics")]
105#[doc(hidden)]
106pub use executor::{ScalarMaterializationLaneMetrics, with_scalar_materialization_lane_metrics};
107#[cfg(all(test, not(feature = "diagnostics")))]
108#[expect(unused_imports)]
109pub(crate) use executor::{
110 ScalarMaterializationLaneMetrics, with_scalar_materialization_lane_metrics,
111};
112pub use identity::{EntityName, IndexName};
113pub use index::{IndexState, IndexStore};
114#[doc(hidden)]
115pub use journal::JournalTailStore;
116#[doc(hidden)]
117pub use key_taxonomy::{
118 CompositePrimaryKeyValue, CompositePrimaryKeyValueError, PrimaryKeyComponent, PrimaryKeyValue,
119};
120pub use predicate::{
121 CoercionId, CompareFieldsPredicate, CompareOp, ComparePredicate, MissingRowPolicy, Predicate,
122 UnsupportedQueryFeature,
123};
124#[doc(hidden)]
125pub use predicate::{
126 parse_generated_index_predicate_sql, validate_generated_index_predicate_fields,
127};
128pub use query::builder::numeric_projection::{
129 NumericProjectionExpr, RoundProjectionExpr, add, div, mul, round, round_expr, sub,
130};
131pub use query::plan::validate::PlanError;
132pub use query::{
133 api::ResponseCardinalityExt,
134 builder::{
135 AggregateExpr, FieldRef, TextProjectionExpr, ValueProjectionExpr, avg, contains, count,
136 count_by, ends_with, exists, first, last, left, length, lower, ltrim, max, max_by, min,
137 min_by, position, replace, right, rtrim, starts_with, substring, substring_with_length,
138 sum, trim, upper,
139 },
140 explain::{
141 ExplainAccessCandidateV1, ExplainAccessDecisionKind, ExplainAccessDecisionV1,
142 ExplainAggregateTerminalPlan, ExplainEligibleAlternativeV1, ExplainExecutionDescriptor,
143 ExplainExecutionMode, ExplainExecutionNodeDescriptor, ExplainExecutionNodeType,
144 ExplainExecutionOrderingSource, ExplainPlan, ExplainRejectedIndexV1,
145 ExplainResidualSummaryV1, ExplainSelectedAccessV1,
146 },
147 expr::{FilterExpr, FilterValue, OrderExpr, OrderTerm, asc, desc, field},
148 fluent::{
149 delete::FluentDeleteQuery,
150 load::{FluentLoadQuery, LoadQueryResult, PagedLoadQuery},
151 },
152 intent::{
153 AccessRequirementError, AccessRequirementViolation, CompiledQuery, IntentError,
154 PlannedQuery, Query, QueryError, QueryExecutionError, RequiredAccessPath,
155 },
156 plan::{DeleteSpec, LoadSpec, OrderDirection, QueryMode},
157 trace::{QueryTracePlan, TraceExecutionFamily, TraceReuseArtifactClass, TraceReuseEvent},
158};
159pub use registry::{
160 StoreAllocationIdentities, StoreAllocationIdentity, StoreAllocationIdentityCapability,
161 StoreCommitParticipation, StoreDurability, StoreLiveValidationCapability,
162 StoreRecoveryCapability, StoreRegistry, StoreRelationSourceCapability,
163 StoreRelationTargetCapability, StoreRuntimeStorageCapabilities, StoreRuntimeStorageMode,
164 StoreSchemaMetadataCapability,
165};
166pub use response::{
167 EntityResponse, GroupedRow, PagedGroupedExecution, PagedGroupedExecutionWithTrace,
168 PagedLoadExecution, PagedLoadExecutionWithTrace, ProjectedRow, ProjectionResponse,
169 Response as RowResponse, ResponseError, ResponseRow, Row, WriteBatchResponse,
170};
171pub use schema::{
172 EntityFieldDescription, EntityIndexDescription, EntityRelationCardinality,
173 EntityRelationDescription, EntityRelationStrength, EntitySchemaCheckDescription,
174 EntitySchemaDescription, SchemaLiteralValidationReason, SchemaStore, SchemaValidationOperator,
175 ValidateError,
176};
177#[cfg(not(feature = "sql"))]
178pub use session::DbSession;
179#[cfg(feature = "sql")]
180pub use session::{
181 DbSession, SqlAdminBulkDeletePlan, SqlAdminBulkUpdatePlan, SqlDdlExecutionStatus,
182 SqlDdlMutationKind, SqlDdlPreparationReport, SqlDeleteExecutionBounds, SqlDeleteExposurePolicy,
183 SqlDeleteOrderPolicy, SqlDeletePolicyContext, SqlDeletePolicyRejection, SqlDeletePolicyReport,
184 SqlDeleteReturningBounds, SqlDeleteReturningPolicy, SqlDeleteStatementClassification,
185 SqlDeleteWherePolicy, SqlPublicBoundedDeletePlan, SqlPublicBoundedUpdatePlan,
186 SqlPublicPrimaryKeyDeletePlan, SqlPublicPrimaryKeyUpdatePlan, SqlSessionCurrentDeletePlan,
187 SqlSessionCurrentUpdatePlan, SqlStatementDispatch, SqlStatementResult,
188 SqlStatementShellSurface, SqlStatementSurface, SqlUpdateAssignmentPolicy,
189 SqlUpdateExposurePolicy, SqlUpdateOrderPolicy, SqlUpdatePolicyContext,
190 SqlUpdatePolicyRejection, SqlUpdatePolicyReport, SqlUpdateReturningBounds,
191 SqlUpdateReturningPolicy, SqlUpdateStatementClassification, SqlUpdateWherePolicy,
192 SqlValidatedDeletePlan, SqlValidatedUpdatePlan, classify_sql_delete_policy,
193 classify_sql_update_policy, sql_statement_dispatch, sql_statement_entity_name,
194 sql_statement_shell_surface, sql_statement_surface,
195};
196#[cfg(feature = "diagnostics")]
197pub use session::{
198 DirectDataRowAttribution, FluentTerminalExecutionAttribution, GroupedCountAttribution,
199 GroupedExecutionAttribution, KernelRowAttribution, QueryExecutionAttribution,
200 ScalarAggregateAttribution,
201};
202#[cfg(all(feature = "sql", feature = "diagnostics"))]
203pub use session::{
204 SqlCompileAttribution, SqlExecutionAttribution, SqlHybridCoveringAttribution,
205 SqlOutputBlobAttribution, SqlPureCoveringAttribution, SqlQueryCacheAttribution,
206 SqlQueryExecutionAttribution, SqlScalarAggregateAttribution,
207};
208#[cfg(all(feature = "sql", feature = "diagnostics"))]
209#[doc(hidden)]
210pub use session::{
211 SqlProjectionMaterializationMetrics, with_sql_projection_materialization_metrics,
212};
213#[cfg(feature = "sql")]
214pub use sql::identifier::{
215 identifier_last_segment, identifiers_tail_match, normalize_identifier_to_scope,
216 split_qualified_identifier,
217};
218#[cfg(feature = "sql")]
219pub use sql::lowering::LoweredSqlCommand;
220
221#[doc(hidden)]
223pub type GeneratedStructuralMapPayloadSlices<'a> = Vec<(&'a [u8], &'a [u8])>;
224
225#[doc(hidden)]
227pub type GeneratedStructuralEnumPayload<'a> = (String, Option<String>, Option<&'a [u8]>);
228
229#[doc(hidden)]
231#[must_use]
232pub(crate) fn encode_generated_structural_text_payload_bytes(value: &str) -> Vec<u8> {
233 data::encode_value_storage_text(value)
234}
235
236#[doc(hidden)]
238#[must_use]
239pub(crate) fn encode_generated_structural_list_payload_bytes(items: &[&[u8]]) -> Vec<u8> {
240 data::encode_value_storage_list_item_slices(items)
241}
242
243#[doc(hidden)]
245#[must_use]
246pub(crate) fn encode_generated_structural_map_payload_bytes(entries: &[(&[u8], &[u8])]) -> Vec<u8> {
247 data::encode_value_storage_map_entry_slices(entries)
248}
249
250#[doc(hidden)]
252#[must_use]
253pub(crate) fn encode_generated_structural_enum_payload_bytes(
254 variant: &str,
255 path: Option<&str>,
256 payload: Option<&[u8]>,
257) -> Vec<u8> {
258 data::encode_enum(variant, path, payload)
259}
260
261#[doc(hidden)]
263pub(crate) fn decode_generated_structural_text_payload_bytes(
264 raw_bytes: &[u8],
265) -> Result<String, InternalError> {
266 data::decode_value_storage_text(raw_bytes).map_err(InternalError::persisted_row_decode_failed)
267}
268
269#[doc(hidden)]
271pub(crate) fn decode_generated_structural_list_payload_bytes(
272 raw_bytes: &[u8],
273) -> Result<Vec<&[u8]>, InternalError> {
274 data::decode_value_storage_list_item_slices(raw_bytes)
275 .map_err(InternalError::persisted_row_decode_failed)
276}
277
278#[doc(hidden)]
280pub(crate) fn decode_generated_structural_map_payload_bytes(
281 raw_bytes: &[u8],
282) -> Result<GeneratedStructuralMapPayloadSlices<'_>, InternalError> {
283 data::decode_value_storage_map_entry_slices(raw_bytes)
284 .map_err(InternalError::persisted_row_decode_failed)
285}
286
287#[doc(hidden)]
289pub(crate) fn decode_generated_structural_enum_payload_bytes(
290 raw_bytes: &[u8],
291) -> Result<GeneratedStructuralEnumPayload<'_>, InternalError> {
292 data::decode_enum(raw_bytes).map_err(InternalError::persisted_row_decode_failed)
293}
294
295#[doc(hidden)]
297pub(crate) fn generated_persisted_structured_payload_decode_failed(
298 detail: impl Sized,
299) -> InternalError {
300 InternalError::persisted_row_decode_failed(detail)
301}
302
303pub(crate) struct Db<C: CanisterKind> {
309 store: &'static LocalKey<StoreRegistry>,
310 entity_runtime_hooks: &'static [EntityRuntimeHooks<C>],
311 _marker: PhantomData<C>,
312}
313
314impl<C: CanisterKind> Db<C> {
315 #[must_use]
317 #[cfg(test)]
318 pub(crate) const fn new(store: &'static LocalKey<StoreRegistry>) -> Self {
319 Self::new_with_hooks(store, &[])
320 }
321
322 #[must_use]
324 pub(crate) const fn new_with_hooks(
325 store: &'static LocalKey<StoreRegistry>,
326 entity_runtime_hooks: &'static [EntityRuntimeHooks<C>],
327 ) -> Self {
328 #[cfg(debug_assertions)]
329 {
330 let _ = crate::db::runtime_hooks::debug_assert_unique_runtime_hook_tags(
331 entity_runtime_hooks,
332 );
333 }
334
335 Self {
336 store,
337 entity_runtime_hooks,
338 _marker: PhantomData,
339 }
340 }
341
342 #[must_use]
343 pub(in crate::db) const fn context<E>(&self) -> Context<'_, E>
344 where
345 E: EntityKind<Canister = C> + EntityValue,
346 {
347 Context::new(self)
348 }
349
350 pub(in crate::db) fn recovered_store(&self, path: &str) -> Result<StoreHandle, InternalError> {
352 ensure_recovered(self)?;
353
354 self.store_handle(path)
355 }
356
357 pub(in crate::db) fn store_handle(&self, path: &str) -> Result<StoreHandle, InternalError> {
363 self.with_store_registry(|registry| registry.try_get_store(path))
364 }
365
366 pub(crate) fn ensure_recovered_state(&self) -> Result<(), InternalError> {
368 ensure_recovered(self)
369 }
370
371 pub(crate) fn with_store_registry<R>(&self, f: impl FnOnce(&StoreRegistry) -> R) -> R {
373 self.store.with(|reg| f(reg))
374 }
375
376 #[must_use]
383 pub(in crate::db) fn cache_scope_id(&self) -> usize {
384 std::ptr::from_ref::<LocalKey<StoreRegistry>>(self.store) as usize
385 }
386
387 #[must_use]
389 pub(in crate::db) fn store_resolver(&self) -> executor::StoreResolver<'_> {
390 executor::StoreResolver::new(self)
391 }
392
393 pub(in crate::db) fn mark_all_registered_index_stores_ready(&self) {
398 self.with_store_registry(|registry| {
399 for (_, handle) in registry.iter() {
400 handle.mark_index_ready();
401 }
402 });
403 }
404
405 pub(crate) fn storage_report(
407 &self,
408 name_to_path: &[(&'static str, &'static str)],
409 ) -> Result<StorageReport, InternalError> {
410 diagnostics::storage_report(self, name_to_path)
411 }
412
413 pub(crate) fn storage_report_default(&self) -> Result<StorageReport, InternalError> {
415 diagnostics::storage_report_default(self)
416 }
417
418 pub(crate) fn integrity_report(&self) -> Result<IntegrityReport, InternalError> {
420 diagnostics::integrity_report(self)
421 }
422
423 pub(in crate::db) fn prepare_row_commit_op(
424 &self,
425 op: &CommitRowOp,
426 ) -> Result<PreparedRowCommitOp, InternalError> {
427 runtime_hooks::prepare_row_commit_with_hook(self, self.entity_runtime_hooks, op)
428 }
429
430 pub(crate) fn validate_delete_strong_relations(
432 &self,
433 target_path: &str,
434 deleted_target_keys: &BTreeSet<RawDataStoreKey>,
435 ) -> Result<(), InternalError> {
436 runtime_hooks::validate_delete_strong_relations_with_hooks(
437 self,
438 self.entity_runtime_hooks,
439 target_path,
440 deleted_target_keys,
441 )
442 }
443}
444
445impl<C: CanisterKind> Db<C> {
446 #[must_use]
448 pub(crate) const fn has_runtime_hooks(&self) -> bool {
449 runtime_hooks::has_runtime_hooks(self.entity_runtime_hooks)
450 }
451
452 pub(crate) fn runtime_entity_catalog(
454 &self,
455 ) -> Result<Vec<EntityCatalogDescription>, InternalError> {
456 let mut entities = Vec::with_capacity(self.entity_runtime_hooks.len());
457
458 for hooks in self.entity_runtime_hooks {
459 let store = self.recovered_store(hooks.store_path)?;
460 let storage = store
461 .storage_capabilities()
462 .storage_mode()
463 .as_str()
464 .to_string();
465 let accepted = store.with_schema_mut(|schema_store| {
466 if let Some(snapshot) = schema_store.latest_persisted_snapshot(hooks.entity_tag)? {
467 let accepted = AcceptedSchemaSnapshot::try_new(snapshot)?;
468 if accepted.entity_path() == hooks.entity_path {
469 return Ok(accepted);
470 }
471 }
472
473 ensure_accepted_schema_snapshot(
474 schema_store,
475 hooks.entity_tag,
476 hooks.entity_path,
477 hooks.model,
478 )
479 })?;
480 let snapshot = accepted.persisted_snapshot();
481
482 entities.push(EntityCatalogDescription::new(
483 snapshot.entity_name().to_string(),
484 snapshot.entity_path().to_string(),
485 hooks.store_path.to_string(),
486 storage,
487 EntityCatalogCounts::new(
488 u32::try_from(snapshot.fields().len()).unwrap_or(u32::MAX),
489 u32::try_from(snapshot.indexes().len()).unwrap_or(u32::MAX),
490 u32::try_from(relation_field_count(snapshot.fields())).unwrap_or(u32::MAX),
491 snapshot.version().get(),
492 ),
493 ));
494 }
495
496 Ok(entities)
497 }
498
499 #[must_use]
501 pub(crate) fn runtime_store_catalog(&self) -> Vec<StoreCatalogDescription> {
502 let mut stores = self.with_store_registry(|registry| {
503 registry
504 .iter()
505 .map(|(store_path, handle)| {
506 StoreCatalogDescription::new(
507 store_path.to_string(),
508 handle
509 .storage_capabilities()
510 .storage_mode()
511 .as_str()
512 .to_string(),
513 )
514 })
515 .collect::<Vec<_>>()
516 });
517 stores.sort_by(|left, right| left.store_path().cmp(right.store_path()));
518 stores
519 }
520
521 #[must_use]
523 pub(crate) fn runtime_memory_catalog(&self) -> Vec<MemoryCatalogDescription> {
524 let mut memory = self.with_store_registry(|registry| {
525 registry
526 .iter()
527 .flat_map(|(store_path, handle)| {
528 [
529 handle.data_allocation(),
530 handle.index_allocation(),
531 handle.schema_allocation(),
532 handle.journal_allocation(),
533 ]
534 .into_iter()
535 .flatten()
536 .map(move |allocation| {
537 MemoryCatalogDescription::new(
538 allocation.stable_key().to_string(),
539 allocation.memory_id(),
540 store_path.to_string(),
541 )
542 })
543 })
544 .collect::<Vec<_>>()
545 });
546 memory.sort_by(|left, right| {
547 left.memory_id()
548 .cmp(&right.memory_id())
549 .then_with(|| left.tag().cmp(right.tag()))
550 .then_with(|| left.store_path().cmp(right.store_path()))
551 });
552 memory
553 }
554
555 pub(crate) fn runtime_hook_for_entity_tag(
558 &self,
559 entity_tag: EntityTag,
560 ) -> Result<&EntityRuntimeHooks<C>, InternalError> {
561 runtime_hooks::resolve_runtime_hook_by_tag(self.entity_runtime_hooks, entity_tag)
562 }
563
564 pub(crate) fn runtime_hook_for_entity_path(
567 &self,
568 entity_path: &str,
569 ) -> Result<&EntityRuntimeHooks<C>, InternalError> {
570 runtime_hooks::resolve_runtime_hook_by_path(self.entity_runtime_hooks, entity_path)
571 }
572}
573
574fn relation_field_count(fields: &[crate::db::schema::PersistedFieldSnapshot]) -> usize {
575 fields
576 .iter()
577 .filter(|field| persisted_kind_is_relation_field(field.kind()))
578 .count()
579}
580
581fn persisted_kind_is_relation_field(kind: &PersistedFieldKind) -> bool {
582 match kind {
583 PersistedFieldKind::Relation { .. } => true,
584 PersistedFieldKind::List(inner) | PersistedFieldKind::Set(inner) => {
585 matches!(inner.as_ref(), PersistedFieldKind::Relation { .. })
586 }
587 _ => false,
588 }
589}
590
591impl<C: CanisterKind> Copy for Db<C> {}
592
593impl<C: CanisterKind> Clone for Db<C> {
594 fn clone(&self) -> Self {
595 *self
596 }
597}