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, SqlDeleteExposurePolicy, SqlDeletePolicyContext,
183 SqlDeletePolicyRejection, SqlDeletePolicyReport, SqlDeleteStatementClassification,
184 SqlPublicBoundedDeletePlan, SqlPublicBoundedUpdatePlan, SqlPublicPrimaryKeyDeletePlan,
185 SqlPublicPrimaryKeyUpdatePlan, SqlSessionCurrentDeletePlan, SqlSessionCurrentUpdatePlan,
186 SqlStatementDispatch, SqlStatementResult, SqlStatementShellSurface, SqlStatementSurface,
187 SqlUpdateAssignmentPolicy, SqlUpdateExposurePolicy, SqlUpdatePolicyContext,
188 SqlUpdatePolicyRejection, SqlUpdatePolicyReport, SqlUpdateStatementClassification,
189 SqlValidatedDeletePlan, SqlValidatedUpdatePlan, SqlWriteExecutionBounds, SqlWriteOrderProof,
190 SqlWriteReturningBounds, SqlWriteReturningShape, SqlWriteStatementShape, SqlWriteWhereProof,
191 classify_sql_delete_policy, classify_sql_update_policy, sql_statement_dispatch,
192 sql_statement_entity_name, sql_statement_shell_surface, sql_statement_surface,
193};
194#[cfg(feature = "diagnostics")]
195pub use session::{
196 DirectDataRowAttribution, FluentTerminalExecutionAttribution, GroupedCountAttribution,
197 GroupedExecutionAttribution, KernelRowAttribution, QueryExecutionAttribution,
198 ScalarAggregateAttribution,
199};
200#[cfg(all(feature = "sql", feature = "diagnostics"))]
201pub use session::{
202 SqlCompileAttribution, SqlExecutionAttribution, SqlHybridCoveringAttribution,
203 SqlOutputBlobAttribution, SqlPureCoveringAttribution, SqlQueryCacheAttribution,
204 SqlQueryExecutionAttribution, SqlScalarAggregateAttribution,
205};
206#[cfg(all(feature = "sql", feature = "diagnostics"))]
207#[doc(hidden)]
208pub use session::{
209 SqlProjectionMaterializationMetrics, with_sql_projection_materialization_metrics,
210};
211#[cfg(feature = "sql")]
212pub use sql::identifier::{
213 identifier_last_segment, identifiers_tail_match, normalize_identifier_to_scope,
214 split_qualified_identifier,
215};
216#[cfg(feature = "sql")]
217pub use sql::lowering::LoweredSqlCommand;
218
219#[doc(hidden)]
221pub type GeneratedStructuralMapPayloadSlices<'a> = Vec<(&'a [u8], &'a [u8])>;
222
223#[doc(hidden)]
225pub type GeneratedStructuralEnumPayload<'a> = (String, Option<String>, Option<&'a [u8]>);
226
227#[doc(hidden)]
229#[must_use]
230pub(crate) fn encode_generated_structural_text_payload_bytes(value: &str) -> Vec<u8> {
231 data::encode_value_storage_text(value)
232}
233
234#[doc(hidden)]
236#[must_use]
237pub(crate) fn encode_generated_structural_list_payload_bytes(items: &[&[u8]]) -> Vec<u8> {
238 data::encode_value_storage_list_item_slices(items)
239}
240
241#[doc(hidden)]
243#[must_use]
244pub(crate) fn encode_generated_structural_map_payload_bytes(entries: &[(&[u8], &[u8])]) -> Vec<u8> {
245 data::encode_value_storage_map_entry_slices(entries)
246}
247
248#[doc(hidden)]
250#[must_use]
251pub(crate) fn encode_generated_structural_enum_payload_bytes(
252 variant: &str,
253 path: Option<&str>,
254 payload: Option<&[u8]>,
255) -> Vec<u8> {
256 data::encode_enum(variant, path, payload)
257}
258
259#[doc(hidden)]
261pub(crate) fn decode_generated_structural_text_payload_bytes(
262 raw_bytes: &[u8],
263) -> Result<String, InternalError> {
264 data::decode_value_storage_text(raw_bytes).map_err(InternalError::persisted_row_decode_failed)
265}
266
267#[doc(hidden)]
269pub(crate) fn decode_generated_structural_list_payload_bytes(
270 raw_bytes: &[u8],
271) -> Result<Vec<&[u8]>, InternalError> {
272 data::decode_value_storage_list_item_slices(raw_bytes)
273 .map_err(InternalError::persisted_row_decode_failed)
274}
275
276#[doc(hidden)]
278pub(crate) fn decode_generated_structural_map_payload_bytes(
279 raw_bytes: &[u8],
280) -> Result<GeneratedStructuralMapPayloadSlices<'_>, InternalError> {
281 data::decode_value_storage_map_entry_slices(raw_bytes)
282 .map_err(InternalError::persisted_row_decode_failed)
283}
284
285#[doc(hidden)]
287pub(crate) fn decode_generated_structural_enum_payload_bytes(
288 raw_bytes: &[u8],
289) -> Result<GeneratedStructuralEnumPayload<'_>, InternalError> {
290 data::decode_enum(raw_bytes).map_err(InternalError::persisted_row_decode_failed)
291}
292
293#[doc(hidden)]
295pub(crate) fn generated_persisted_structured_payload_decode_failed(
296 detail: impl Sized,
297) -> InternalError {
298 InternalError::persisted_row_decode_failed(detail)
299}
300
301pub(crate) struct Db<C: CanisterKind> {
307 store: &'static LocalKey<StoreRegistry>,
308 entity_runtime_hooks: &'static [EntityRuntimeHooks<C>],
309 _marker: PhantomData<C>,
310}
311
312impl<C: CanisterKind> Db<C> {
313 #[must_use]
315 #[cfg(test)]
316 pub(crate) const fn new(store: &'static LocalKey<StoreRegistry>) -> Self {
317 Self::new_with_hooks(store, &[])
318 }
319
320 #[must_use]
322 pub(crate) const fn new_with_hooks(
323 store: &'static LocalKey<StoreRegistry>,
324 entity_runtime_hooks: &'static [EntityRuntimeHooks<C>],
325 ) -> Self {
326 #[cfg(debug_assertions)]
327 {
328 let _ = crate::db::runtime_hooks::debug_assert_unique_runtime_hook_tags(
329 entity_runtime_hooks,
330 );
331 }
332
333 Self {
334 store,
335 entity_runtime_hooks,
336 _marker: PhantomData,
337 }
338 }
339
340 #[must_use]
341 pub(in crate::db) const fn context<E>(&self) -> Context<'_, E>
342 where
343 E: EntityKind<Canister = C> + EntityValue,
344 {
345 Context::new(self)
346 }
347
348 pub(in crate::db) fn recovered_store(&self, path: &str) -> Result<StoreHandle, InternalError> {
350 ensure_recovered(self)?;
351
352 self.store_handle(path)
353 }
354
355 pub(in crate::db) fn store_handle(&self, path: &str) -> Result<StoreHandle, InternalError> {
361 self.with_store_registry(|registry| registry.try_get_store(path))
362 }
363
364 pub(crate) fn ensure_recovered_state(&self) -> Result<(), InternalError> {
366 ensure_recovered(self)
367 }
368
369 pub(crate) fn with_store_registry<R>(&self, f: impl FnOnce(&StoreRegistry) -> R) -> R {
371 self.store.with(|reg| f(reg))
372 }
373
374 #[must_use]
381 pub(in crate::db) fn cache_scope_id(&self) -> usize {
382 std::ptr::from_ref::<LocalKey<StoreRegistry>>(self.store) as usize
383 }
384
385 #[must_use]
387 pub(in crate::db) fn store_resolver(&self) -> executor::StoreResolver<'_> {
388 executor::StoreResolver::new(self)
389 }
390
391 pub(in crate::db) fn mark_all_registered_index_stores_ready(&self) {
396 self.with_store_registry(|registry| {
397 for (_, handle) in registry.iter() {
398 handle.mark_index_ready();
399 }
400 });
401 }
402
403 pub(crate) fn storage_report(
405 &self,
406 name_to_path: &[(&'static str, &'static str)],
407 ) -> Result<StorageReport, InternalError> {
408 diagnostics::storage_report(self, name_to_path)
409 }
410
411 pub(crate) fn storage_report_default(&self) -> Result<StorageReport, InternalError> {
413 diagnostics::storage_report_default(self)
414 }
415
416 pub(crate) fn integrity_report(&self) -> Result<IntegrityReport, InternalError> {
418 diagnostics::integrity_report(self)
419 }
420
421 pub(in crate::db) fn prepare_row_commit_op(
422 &self,
423 op: &CommitRowOp,
424 ) -> Result<PreparedRowCommitOp, InternalError> {
425 runtime_hooks::prepare_row_commit_with_hook(self, self.entity_runtime_hooks, op)
426 }
427
428 pub(crate) fn validate_delete_strong_relations(
430 &self,
431 target_path: &str,
432 deleted_target_keys: &BTreeSet<RawDataStoreKey>,
433 ) -> Result<(), InternalError> {
434 runtime_hooks::validate_delete_strong_relations_with_hooks(
435 self,
436 self.entity_runtime_hooks,
437 target_path,
438 deleted_target_keys,
439 )
440 }
441}
442
443impl<C: CanisterKind> Db<C> {
444 #[must_use]
446 pub(crate) const fn has_runtime_hooks(&self) -> bool {
447 runtime_hooks::has_runtime_hooks(self.entity_runtime_hooks)
448 }
449
450 pub(crate) fn runtime_entity_catalog(
452 &self,
453 ) -> Result<Vec<EntityCatalogDescription>, InternalError> {
454 let mut entities = Vec::with_capacity(self.entity_runtime_hooks.len());
455
456 for hooks in self.entity_runtime_hooks {
457 let store = self.recovered_store(hooks.store_path)?;
458 let storage = store
459 .storage_capabilities()
460 .storage_mode()
461 .as_str()
462 .to_string();
463 let accepted = store.with_schema_mut(|schema_store| {
464 if let Some(snapshot) = schema_store.latest_persisted_snapshot(hooks.entity_tag)? {
465 let accepted = AcceptedSchemaSnapshot::try_new(snapshot)?;
466 if accepted.entity_path() == hooks.entity_path {
467 return Ok(accepted);
468 }
469 }
470
471 ensure_accepted_schema_snapshot(
472 schema_store,
473 hooks.entity_tag,
474 hooks.entity_path,
475 hooks.model,
476 )
477 })?;
478 let snapshot = accepted.persisted_snapshot();
479
480 entities.push(EntityCatalogDescription::new(
481 snapshot.entity_name().to_string(),
482 snapshot.entity_path().to_string(),
483 hooks.store_path.to_string(),
484 storage,
485 EntityCatalogCounts::new(
486 u32::try_from(snapshot.fields().len()).unwrap_or(u32::MAX),
487 u32::try_from(snapshot.indexes().len()).unwrap_or(u32::MAX),
488 u32::try_from(relation_field_count(snapshot.fields())).unwrap_or(u32::MAX),
489 snapshot.version().get(),
490 ),
491 ));
492 }
493
494 Ok(entities)
495 }
496
497 #[must_use]
499 pub(crate) fn runtime_store_catalog(&self) -> Vec<StoreCatalogDescription> {
500 let mut stores = self.with_store_registry(|registry| {
501 registry
502 .iter()
503 .map(|(store_path, handle)| {
504 StoreCatalogDescription::new(
505 store_path.to_string(),
506 handle
507 .storage_capabilities()
508 .storage_mode()
509 .as_str()
510 .to_string(),
511 )
512 })
513 .collect::<Vec<_>>()
514 });
515 stores.sort_by(|left, right| left.store_path().cmp(right.store_path()));
516 stores
517 }
518
519 #[must_use]
521 pub(crate) fn runtime_memory_catalog(&self) -> Vec<MemoryCatalogDescription> {
522 let mut memory = self.with_store_registry(|registry| {
523 registry
524 .iter()
525 .flat_map(|(store_path, handle)| {
526 [
527 handle.data_allocation(),
528 handle.index_allocation(),
529 handle.schema_allocation(),
530 handle.journal_allocation(),
531 ]
532 .into_iter()
533 .flatten()
534 .map(move |allocation| {
535 MemoryCatalogDescription::new(
536 allocation.stable_key().to_string(),
537 allocation.memory_id(),
538 store_path.to_string(),
539 )
540 })
541 })
542 .collect::<Vec<_>>()
543 });
544 memory.sort_by(|left, right| {
545 left.memory_id()
546 .cmp(&right.memory_id())
547 .then_with(|| left.tag().cmp(right.tag()))
548 .then_with(|| left.store_path().cmp(right.store_path()))
549 });
550 memory
551 }
552
553 pub(crate) fn runtime_hook_for_entity_tag(
556 &self,
557 entity_tag: EntityTag,
558 ) -> Result<&EntityRuntimeHooks<C>, InternalError> {
559 runtime_hooks::resolve_runtime_hook_by_tag(self.entity_runtime_hooks, entity_tag)
560 }
561
562 pub(crate) fn runtime_hook_for_entity_path(
565 &self,
566 entity_path: &str,
567 ) -> Result<&EntityRuntimeHooks<C>, InternalError> {
568 runtime_hooks::resolve_runtime_hook_by_path(self.entity_runtime_hooks, entity_path)
569 }
570}
571
572fn relation_field_count(fields: &[crate::db::schema::PersistedFieldSnapshot]) -> usize {
573 fields
574 .iter()
575 .filter(|field| persisted_kind_is_relation_field(field.kind()))
576 .count()
577}
578
579fn persisted_kind_is_relation_field(kind: &PersistedFieldKind) -> bool {
580 match kind {
581 PersistedFieldKind::Relation { .. } => true,
582 PersistedFieldKind::List(inner) | PersistedFieldKind::Set(inner) => {
583 matches!(inner.as_ref(), PersistedFieldKind::Relation { .. })
584 }
585 _ => false,
586 }
587}
588
589impl<C: CanisterKind> Copy for Db<C> {}
590
591impl<C: CanisterKind> Clone for Db<C> {
592 fn clone(&self) -> Self {
593 *self
594 }
595}