use crate::{
db::{
Db,
commit::{
CommitRowOp, PreparedRowCommitOp, prepare_commit_context_for_runtime_registration,
prepare_row_commit_with_context,
},
data::RawDataStoreKey,
},
error::InternalError,
traits::CanisterKind,
types::EntityTag,
};
use std::{collections::BTreeSet, marker::PhantomData};
pub(in crate::db) struct GeneratedEntityRoute<C: CanisterKind> {
pub(in crate::db) source_key: &'static str,
pub(in crate::db) store_path: &'static str,
_marker: PhantomData<C>,
}
impl<C: CanisterKind> GeneratedEntityRoute<C> {
const fn new(source_key: &'static str, store_path: &'static str) -> Self {
Self {
source_key,
store_path,
_marker: PhantomData,
}
}
pub(in crate::db) fn accepted_entity_tag(self, db: &Db<C>) -> Result<EntityTag, InternalError> {
let source = icydb_schema::EntitySourceKey::try_new(self.source_key)
.map_err(|_| InternalError::store_invariant())?;
let store = db.store_handle(self.store_path)?;
store.with_schema(|schema| schema.current_accepted_entity_tag_for_source(&source))
}
pub(in crate::db) fn resolve(
self,
db: &Db<C>,
) -> Result<EntityRuntimeRegistration<C>, InternalError> {
Ok(EntityRuntimeRegistration {
entity_tag: self.accepted_entity_tag(db)?,
entity_path: self.source_key,
store_path: self.store_path,
_marker: PhantomData,
})
}
}
impl<C: CanisterKind> Clone for GeneratedEntityRoute<C> {
fn clone(&self) -> Self {
*self
}
}
impl<C: CanisterKind> Copy for GeneratedEntityRoute<C> {}
pub(in crate::db) struct EntityRuntimeRegistration<C: CanisterKind> {
pub(in crate::db) entity_tag: EntityTag,
pub(in crate::db) entity_path: &'static str,
pub(in crate::db) store_path: &'static str,
_marker: PhantomData<C>,
}
impl<C: CanisterKind> Clone for EntityRuntimeRegistration<C> {
fn clone(&self) -> Self {
*self
}
}
impl<C: CanisterKind> Copy for EntityRuntimeRegistration<C> {}
impl<C: CanisterKind> EntityRuntimeRegistration<C> {
pub(in crate::db) fn prepare_commit_context(
self,
db: &Db<C>,
schema_fingerprint: crate::db::commit::CommitSchemaFingerprint,
include_candidate_relation_effects: bool,
) -> Result<crate::db::commit::CommitPrepareContext, InternalError> {
prepare_commit_context_for_runtime_registration(
db,
self.entity_path,
self.entity_tag,
self.store_path,
schema_fingerprint,
include_candidate_relation_effects,
)
}
}
pub struct EntityRegistration<C: CanisterKind> {
runtime: GeneratedEntityRoute<C>,
}
impl<C: CanisterKind> EntityRegistration<C> {
#[must_use]
pub const fn new(source_key: &'static str, store_path: &'static str) -> Self {
Self {
runtime: GeneratedEntityRoute::new(source_key, store_path),
}
}
#[must_use]
pub(in crate::db) const fn runtime(&self) -> GeneratedEntityRoute<C> {
self.runtime
}
}
pub(in crate::db) fn resolve_runtime_registration_by_tag<C: CanisterKind>(
db: &Db<C>,
registrations: &[EntityRegistration<C>],
entity_tag: EntityTag,
) -> Result<EntityRuntimeRegistration<C>, InternalError> {
let mut matched = None;
for registration in registrations {
let runtime = registration.runtime();
let store = db.store_handle(runtime.store_path)?;
if store.storage_capabilities().schema_metadata()
== crate::db::registry::StoreSchemaMetadataCapability::LiveRebuiltMetadata
&& store
.with_schema(crate::db::schema::SchemaStore::current_accepted_schema_bundle)?
.is_none()
{
continue;
}
let resolved = runtime.resolve(db)?;
if resolved.entity_tag != entity_tag {
continue;
}
if matched.is_some() {
return Err(InternalError::duplicate_entity_registrations_for_tag(
entity_tag,
));
}
matched = Some(resolved);
}
matched.ok_or_else(|| InternalError::unsupported_entity_tag_in_data_store(entity_tag))
}
pub(in crate::db) fn resolve_runtime_registration_by_path<C: CanisterKind>(
db: &Db<C>,
registrations: &[EntityRegistration<C>],
entity_path: &str,
) -> Result<EntityRuntimeRegistration<C>, InternalError> {
resolve_generated_route_by_path(registrations, entity_path)?.resolve(db)
}
pub(in crate::db) fn resolve_generated_route_by_path<C: CanisterKind>(
registrations: &[EntityRegistration<C>],
entity_path: &str,
) -> Result<GeneratedEntityRoute<C>, InternalError> {
let mut matched = None;
for registration in registrations {
let runtime = registration.runtime();
if runtime.source_key != entity_path {
continue;
}
if matched.is_some() {
return Err(InternalError::duplicate_entity_registrations_for_path(
entity_path,
));
}
matched = Some(runtime);
}
matched.ok_or_else(|| InternalError::unsupported_entity_path(entity_path))
}
pub(in crate::db) fn prepare_row_commit_with_registration<C: CanisterKind>(
db: &Db<C>,
registrations: &[EntityRegistration<C>],
op: &CommitRowOp,
) -> Result<PreparedRowCommitOp, InternalError> {
let runtime = resolve_runtime_registration_by_path(db, registrations, op.entity_path.as_ref())?;
let store = db.store_handle(runtime.store_path)?;
let context = prepare_commit_context_for_runtime_registration(
db,
runtime.entity_path,
runtime.entity_tag,
runtime.store_path,
op.schema_fingerprint,
true,
)?;
prepare_row_commit_with_context(db, op, &context, &store, &store)
}
pub(in crate::db) fn prepare_row_commit_with_registration_for_rebuild<C: CanisterKind>(
db: &Db<C>,
registrations: &[EntityRegistration<C>],
op: &CommitRowOp,
) -> Result<PreparedRowCommitOp, InternalError> {
let runtime = resolve_runtime_registration_by_path(db, registrations, op.entity_path.as_ref())?;
let store = db.store_handle(runtime.store_path)?;
let context = prepare_commit_context_for_runtime_registration(
db,
runtime.entity_path,
runtime.entity_tag,
runtime.store_path,
op.schema_fingerprint,
false,
)?;
prepare_row_commit_with_context(db, op, &context, &store, &store)
}
pub(in crate::db) fn validate_delete_relations_with_registrations<C: CanisterKind>(
db: &Db<C>,
registrations: &[EntityRegistration<C>],
target_path: &str,
deleted_target_keys: &BTreeSet<RawDataStoreKey>,
) -> Result<(), InternalError> {
if deleted_target_keys.is_empty() {
return Ok(());
}
crate::db::relation::validate_candidate_relation_target_delete_barrier(
db,
target_path,
deleted_target_keys,
)?;
for registration in registrations {
let runtime = registration.runtime().resolve(db)?;
let source_store = db.store_handle(runtime.store_path)?;
if !source_store.with_schema(|schema_store| {
schema_store.entity_has_relation_to_target(runtime.entity_tag, target_path)
})? {
continue;
}
crate::db::relation::validate_delete_relations_for_registered_source(
db,
runtime.entity_tag,
runtime.entity_path,
runtime.store_path,
target_path,
deleted_target_keys,
)?;
}
Ok(())
}