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(crate) 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)]
38mod tests;
39
40use crate::{
41 db::{
42 commit::{CommitRowOp, PreparedRowCommitOp, ensure_recovered},
43 data::RawDataStoreKey,
44 executor::Context,
45 registry::StoreHandle,
46 schema::{PersistedFieldKind, ensure_accepted_schema_snapshot},
47 },
48 error::InternalError,
49 traits::{CanisterKind, EntityKind, EntityValue},
50 types::EntityTag,
51};
52use std::{collections::BTreeSet, marker::PhantomData, thread::LocalKey};
53
54pub use catalog::{
55 EntityCatalogCounts, EntityCatalogDescription, MemoryCatalogDescription,
56 StoreCatalogDescription,
57};
58#[doc(hidden)]
59pub use codec::hex::encode_hex_lower;
60pub use cursor::{decode_cursor, encode_cursor};
61pub use runtime_hooks::EntityRuntimeHooks;
62pub use data::{DataStore, PersistedRow, SlotReader, SlotWriter, StructuralPatch};
66#[doc(hidden)]
67pub use data::{
68 PersistedScalar, ScalarSlotValueRef, ScalarValueRef,
69 decode_persisted_many_slot_payload_by_meta, decode_persisted_option_scalar_slot_payload,
70 decode_persisted_option_slot_payload_by_kind, decode_persisted_option_slot_payload_by_meta,
71 decode_persisted_scalar_slot_payload, decode_persisted_slot_payload_by_kind,
72 decode_persisted_slot_payload_by_meta, decode_persisted_structured_many_slot_payload,
73 decode_persisted_structured_slot_payload, decode_slot_into_runtime_value,
74 encode_persisted_many_slot_payload_by_meta, encode_persisted_option_scalar_slot_payload,
75 encode_persisted_option_slot_payload_by_meta, encode_persisted_scalar_slot_payload,
76 encode_persisted_slot_payload_by_kind, encode_persisted_slot_payload_by_meta,
77 encode_persisted_structured_many_slot_payload, encode_persisted_structured_slot_payload,
78 encode_runtime_value_into_slot,
79};
80#[cfg(feature = "diagnostics")]
81#[doc(hidden)]
82pub use data::{StructuralReadMetrics, with_structural_read_metrics};
83#[cfg(all(test, not(feature = "diagnostics")))]
84#[expect(unused_imports)]
85pub(crate) use data::{StructuralReadMetrics, with_structural_read_metrics};
86pub use diagnostics::execution_trace::{
87 ExecutionAccessPathVariant, ExecutionMetrics, ExecutionOptimization, ExecutionStats,
88 ExecutionTrace,
89};
90pub use diagnostics::model::{
91 DataStoreSnapshot, EntitySnapshot, IndexStoreSnapshot, IntegrityReport, IntegrityStoreSnapshot,
92 IntegrityTotals, SchemaStoreSnapshot, StorageReport, 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, SchemaStore, ValidateError,
175};
176#[cfg(not(feature = "sql"))]
177pub use session::DbSession;
178#[cfg(feature = "sql")]
179pub use session::{
180 DbSession, SqlDdlExecutionStatus, SqlDdlMutationKind, SqlDdlPreparationReport,
181 SqlStatementResult, SqlStatementSurface, sql_statement_entity_name, sql_statement_surface,
182};
183#[cfg(feature = "diagnostics")]
184pub use session::{
185 DirectDataRowAttribution, GroupedCountAttribution, GroupedExecutionAttribution,
186 QueryExecutionAttribution,
187};
188#[cfg(all(feature = "sql", feature = "diagnostics"))]
189pub use session::{
190 SqlCompileAttribution, SqlExecutionAttribution, SqlPureCoveringAttribution,
191 SqlQueryCacheAttribution, SqlQueryExecutionAttribution, SqlScalarAggregateAttribution,
192};
193#[cfg(all(feature = "sql", feature = "diagnostics"))]
194#[doc(hidden)]
195pub use session::{
196 SqlProjectionMaterializationMetrics, with_sql_projection_materialization_metrics,
197};
198#[cfg(feature = "sql")]
199pub use sql::identifier::{
200 identifier_last_segment, identifiers_tail_match, normalize_identifier_to_scope,
201 split_qualified_identifier,
202};
203#[cfg(feature = "sql")]
204pub use sql::lowering::LoweredSqlCommand;
205
206#[doc(hidden)]
208pub type GeneratedStructuralMapPayloadSlices<'a> = Vec<(&'a [u8], &'a [u8])>;
209
210#[doc(hidden)]
212pub type GeneratedStructuralEnumPayload<'a> = (String, Option<String>, Option<&'a [u8]>);
213
214#[doc(hidden)]
216#[must_use]
217pub(crate) fn encode_generated_structural_text_payload_bytes(value: &str) -> Vec<u8> {
218 data::encode_value_storage_text(value)
219}
220
221#[doc(hidden)]
223#[must_use]
224pub(crate) fn encode_generated_structural_list_payload_bytes(items: &[&[u8]]) -> Vec<u8> {
225 data::encode_value_storage_list_item_slices(items)
226}
227
228#[doc(hidden)]
230#[must_use]
231pub(crate) fn encode_generated_structural_map_payload_bytes(entries: &[(&[u8], &[u8])]) -> Vec<u8> {
232 data::encode_value_storage_map_entry_slices(entries)
233}
234
235#[doc(hidden)]
237#[must_use]
238pub(crate) fn encode_generated_structural_enum_payload_bytes(
239 variant: &str,
240 path: Option<&str>,
241 payload: Option<&[u8]>,
242) -> Vec<u8> {
243 data::encode_enum(variant, path, payload)
244}
245
246#[doc(hidden)]
248pub(crate) fn decode_generated_structural_text_payload_bytes(
249 raw_bytes: &[u8],
250) -> Result<String, InternalError> {
251 data::decode_value_storage_text(raw_bytes).map_err(InternalError::persisted_row_decode_failed)
252}
253
254#[doc(hidden)]
256pub(crate) fn decode_generated_structural_list_payload_bytes(
257 raw_bytes: &[u8],
258) -> Result<Vec<&[u8]>, InternalError> {
259 data::decode_value_storage_list_item_slices(raw_bytes)
260 .map_err(InternalError::persisted_row_decode_failed)
261}
262
263#[doc(hidden)]
265pub(crate) fn decode_generated_structural_map_payload_bytes(
266 raw_bytes: &[u8],
267) -> Result<GeneratedStructuralMapPayloadSlices<'_>, InternalError> {
268 data::decode_value_storage_map_entry_slices(raw_bytes)
269 .map_err(InternalError::persisted_row_decode_failed)
270}
271
272#[doc(hidden)]
274pub(crate) fn decode_generated_structural_enum_payload_bytes(
275 raw_bytes: &[u8],
276) -> Result<GeneratedStructuralEnumPayload<'_>, InternalError> {
277 data::decode_enum(raw_bytes).map_err(InternalError::persisted_row_decode_failed)
278}
279
280#[doc(hidden)]
282pub(crate) fn generated_persisted_structured_payload_decode_failed(
283 detail: impl std::fmt::Display,
284) -> InternalError {
285 InternalError::persisted_row_decode_failed(detail)
286}
287
288pub(crate) struct Db<C: CanisterKind> {
294 store: &'static LocalKey<StoreRegistry>,
295 entity_runtime_hooks: &'static [EntityRuntimeHooks<C>],
296 _marker: PhantomData<C>,
297}
298
299impl<C: CanisterKind> Db<C> {
300 #[must_use]
302 #[cfg(test)]
303 pub(crate) const fn new(store: &'static LocalKey<StoreRegistry>) -> Self {
304 Self::new_with_hooks(store, &[])
305 }
306
307 #[must_use]
309 pub(crate) const fn new_with_hooks(
310 store: &'static LocalKey<StoreRegistry>,
311 entity_runtime_hooks: &'static [EntityRuntimeHooks<C>],
312 ) -> Self {
313 #[cfg(debug_assertions)]
314 {
315 let _ = crate::db::runtime_hooks::debug_assert_unique_runtime_hook_tags(
316 entity_runtime_hooks,
317 );
318 }
319
320 Self {
321 store,
322 entity_runtime_hooks,
323 _marker: PhantomData,
324 }
325 }
326
327 #[must_use]
328 pub(in crate::db) const fn context<E>(&self) -> Context<'_, E>
329 where
330 E: EntityKind<Canister = C> + EntityValue,
331 {
332 Context::new(self)
333 }
334
335 pub(in crate::db) fn recovered_store(&self, path: &str) -> Result<StoreHandle, InternalError> {
337 ensure_recovered(self)?;
338
339 self.store_handle(path)
340 }
341
342 pub(in crate::db) fn store_handle(&self, path: &str) -> Result<StoreHandle, InternalError> {
348 self.with_store_registry(|registry| registry.try_get_store(path))
349 }
350
351 pub(crate) fn ensure_recovered_state(&self) -> Result<(), InternalError> {
353 ensure_recovered(self)
354 }
355
356 pub(crate) fn with_store_registry<R>(&self, f: impl FnOnce(&StoreRegistry) -> R) -> R {
358 self.store.with(|reg| f(reg))
359 }
360
361 #[must_use]
368 pub(in crate::db) fn cache_scope_id(&self) -> usize {
369 std::ptr::from_ref::<LocalKey<StoreRegistry>>(self.store) as usize
370 }
371
372 #[must_use]
374 pub(in crate::db) fn store_resolver(&self) -> executor::StoreResolver<'_> {
375 executor::StoreResolver::new(self)
376 }
377
378 pub(in crate::db) fn mark_all_registered_index_stores_ready(&self) {
383 self.with_store_registry(|registry| {
384 for (_, handle) in registry.iter() {
385 handle.mark_index_ready();
386 }
387 });
388 }
389
390 pub(crate) fn storage_report(
392 &self,
393 name_to_path: &[(&'static str, &'static str)],
394 ) -> Result<StorageReport, InternalError> {
395 diagnostics::storage_report(self, name_to_path)
396 }
397
398 pub(crate) fn storage_report_default(&self) -> Result<StorageReport, InternalError> {
400 diagnostics::storage_report_default(self)
401 }
402
403 pub(crate) fn integrity_report(&self) -> Result<IntegrityReport, InternalError> {
405 diagnostics::integrity_report(self)
406 }
407
408 pub(in crate::db) fn prepare_row_commit_op(
409 &self,
410 op: &CommitRowOp,
411 ) -> Result<PreparedRowCommitOp, InternalError> {
412 runtime_hooks::prepare_row_commit_with_hook(self, self.entity_runtime_hooks, op)
413 }
414
415 pub(crate) fn validate_delete_strong_relations(
417 &self,
418 target_path: &str,
419 deleted_target_keys: &BTreeSet<RawDataStoreKey>,
420 ) -> Result<(), InternalError> {
421 runtime_hooks::validate_delete_strong_relations_with_hooks(
422 self,
423 self.entity_runtime_hooks,
424 target_path,
425 deleted_target_keys,
426 )
427 }
428}
429
430impl<C: CanisterKind> Db<C> {
431 #[must_use]
433 pub(crate) const fn has_runtime_hooks(&self) -> bool {
434 runtime_hooks::has_runtime_hooks(self.entity_runtime_hooks)
435 }
436
437 pub(crate) fn runtime_entity_catalog(
439 &self,
440 ) -> Result<Vec<EntityCatalogDescription>, InternalError> {
441 let mut entities = Vec::with_capacity(self.entity_runtime_hooks.len());
442
443 for hooks in self.entity_runtime_hooks {
444 let store = self.recovered_store(hooks.store_path)?;
445 let storage = store
446 .storage_capabilities()
447 .storage_mode()
448 .as_str()
449 .to_string();
450 let accepted = store.with_schema_mut(|schema_store| {
451 ensure_accepted_schema_snapshot(
452 schema_store,
453 hooks.entity_tag,
454 hooks.entity_path,
455 hooks.model,
456 )
457 })?;
458 let snapshot = accepted.persisted_snapshot();
459
460 entities.push(EntityCatalogDescription::new(
461 snapshot.entity_name().to_string(),
462 snapshot.entity_path().to_string(),
463 hooks.store_path.to_string(),
464 storage,
465 EntityCatalogCounts::new(
466 u32::try_from(snapshot.fields().len()).unwrap_or(u32::MAX),
467 u32::try_from(snapshot.indexes().len()).unwrap_or(u32::MAX),
468 u32::try_from(relation_field_count(snapshot.fields())).unwrap_or(u32::MAX),
469 snapshot.version().get(),
470 ),
471 ));
472 }
473
474 Ok(entities)
475 }
476
477 #[must_use]
479 pub(crate) fn runtime_store_catalog(&self) -> Vec<StoreCatalogDescription> {
480 let mut stores = self.with_store_registry(|registry| {
481 registry
482 .iter()
483 .map(|(store_path, handle)| {
484 StoreCatalogDescription::new(
485 store_path.to_string(),
486 handle
487 .storage_capabilities()
488 .storage_mode()
489 .as_str()
490 .to_string(),
491 )
492 })
493 .collect::<Vec<_>>()
494 });
495 stores.sort_by(|left, right| left.store_path().cmp(right.store_path()));
496 stores
497 }
498
499 #[must_use]
501 pub(crate) fn runtime_memory_catalog(&self) -> Vec<MemoryCatalogDescription> {
502 let mut memory = self.with_store_registry(|registry| {
503 registry
504 .iter()
505 .flat_map(|(store_path, handle)| {
506 [
507 handle.data_allocation(),
508 handle.index_allocation(),
509 handle.schema_allocation(),
510 handle.journal_allocation(),
511 ]
512 .into_iter()
513 .flatten()
514 .map(move |allocation| {
515 MemoryCatalogDescription::new(
516 allocation.stable_key().to_string(),
517 allocation.memory_id(),
518 store_path.to_string(),
519 )
520 })
521 })
522 .collect::<Vec<_>>()
523 });
524 memory.sort_by(|left, right| {
525 left.memory_id()
526 .cmp(&right.memory_id())
527 .then_with(|| left.tag().cmp(right.tag()))
528 .then_with(|| left.store_path().cmp(right.store_path()))
529 });
530 memory
531 }
532
533 pub(crate) fn runtime_hook_for_entity_tag(
536 &self,
537 entity_tag: EntityTag,
538 ) -> Result<&EntityRuntimeHooks<C>, InternalError> {
539 runtime_hooks::resolve_runtime_hook_by_tag(self.entity_runtime_hooks, entity_tag)
540 }
541
542 pub(crate) fn runtime_hook_for_entity_path(
545 &self,
546 entity_path: &str,
547 ) -> Result<&EntityRuntimeHooks<C>, InternalError> {
548 runtime_hooks::resolve_runtime_hook_by_path(self.entity_runtime_hooks, entity_path)
549 }
550}
551
552fn relation_field_count(fields: &[crate::db::schema::PersistedFieldSnapshot]) -> usize {
553 fields
554 .iter()
555 .filter(|field| persisted_kind_is_relation_field(field.kind()))
556 .count()
557}
558
559fn persisted_kind_is_relation_field(kind: &PersistedFieldKind) -> bool {
560 match kind {
561 PersistedFieldKind::Relation { .. } => true,
562 PersistedFieldKind::List(inner) | PersistedFieldKind::Set(inner) => {
563 matches!(inner.as_ref(), PersistedFieldKind::Relation { .. })
564 }
565 _ => false,
566 }
567}
568
569impl<C: CanisterKind> Copy for Db<C> {}
570
571impl<C: CanisterKind> Clone for Db<C> {
572 fn clone(&self) -> Self {
573 *self
574 }
575}