use crate::node::{validate_stable_key, validate_stable_key_segment};
use crate::prelude::*;
#[derive(Clone, Debug, Serialize)]
pub struct Store {
def: Def,
canister: &'static str,
storage: StoreStorage,
}
#[derive(Clone, Debug, Serialize)]
pub enum StoreStorage {
Heap(StoreHeapConfig),
Journaled(StoreJournaledMemoryConfig),
}
impl StoreStorage {
#[must_use]
pub const fn journaled_memory_config(&self) -> Option<&StoreJournaledMemoryConfig> {
match self {
Self::Journaled(config) => Some(config),
Self::Heap(_) => None,
}
}
#[must_use]
pub const fn storage_capabilities(&self) -> StoreStorageCapabilities {
match self {
Self::Heap(_) => StoreStorageCapabilities::heap(),
Self::Journaled(_) => StoreStorageCapabilities::journaled(),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
pub enum StoreStorageMode {
Heap,
Journaled,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
pub enum AllocationIdentityCapability {
Present,
Absent,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
pub enum StoreDurability {
Durable,
Volatile,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
pub enum StoreRecoveryCapability {
StableBasePlusJournalReplay,
None,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
pub enum CommitParticipation {
Durable,
LiveOnly,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
pub enum SchemaMetadataCapability {
LiveRebuiltMetadata,
CanonicalStableHistoryPlusJournalTail,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
pub enum RelationSourceCapability {
DurableSource,
LiveSource,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
pub enum RelationTargetCapability {
DurableTarget,
VolatileTarget,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
pub enum LiveValidationCapability {
Supported,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
pub struct StoreStorageCapabilities {
storage_mode: StoreStorageMode,
allocation_identity: AllocationIdentityCapability,
durability: StoreDurability,
recovery: StoreRecoveryCapability,
commit_participation: CommitParticipation,
schema_metadata: SchemaMetadataCapability,
relation_source: RelationSourceCapability,
relation_target: RelationTargetCapability,
live_validation: LiveValidationCapability,
}
impl StoreStorageCapabilities {
#[must_use]
pub const fn heap() -> Self {
Self {
storage_mode: StoreStorageMode::Heap,
allocation_identity: AllocationIdentityCapability::Absent,
durability: StoreDurability::Volatile,
recovery: StoreRecoveryCapability::None,
commit_participation: CommitParticipation::LiveOnly,
schema_metadata: SchemaMetadataCapability::LiveRebuiltMetadata,
relation_source: RelationSourceCapability::LiveSource,
relation_target: RelationTargetCapability::VolatileTarget,
live_validation: LiveValidationCapability::Supported,
}
}
#[must_use]
pub const fn journaled() -> Self {
Self {
storage_mode: StoreStorageMode::Journaled,
allocation_identity: AllocationIdentityCapability::Present,
durability: StoreDurability::Durable,
recovery: StoreRecoveryCapability::StableBasePlusJournalReplay,
commit_participation: CommitParticipation::Durable,
schema_metadata: SchemaMetadataCapability::CanonicalStableHistoryPlusJournalTail,
relation_source: RelationSourceCapability::DurableSource,
relation_target: RelationTargetCapability::DurableTarget,
live_validation: LiveValidationCapability::Supported,
}
}
#[must_use]
pub const fn storage_mode(self) -> StoreStorageMode {
self.storage_mode
}
#[must_use]
pub const fn allocation_identity(self) -> AllocationIdentityCapability {
self.allocation_identity
}
#[must_use]
pub const fn durability(self) -> StoreDurability {
self.durability
}
#[must_use]
pub const fn recovery(self) -> StoreRecoveryCapability {
self.recovery
}
#[must_use]
pub const fn commit_participation(self) -> CommitParticipation {
self.commit_participation
}
#[must_use]
pub const fn schema_metadata(self) -> SchemaMetadataCapability {
self.schema_metadata
}
#[must_use]
pub const fn relation_source(self) -> RelationSourceCapability {
self.relation_source
}
#[must_use]
pub const fn relation_target(self) -> RelationTargetCapability {
self.relation_target
}
#[must_use]
pub const fn live_validation(self) -> LiveValidationCapability {
self.live_validation
}
#[must_use]
pub const fn has_allocation_identity(self) -> bool {
matches!(
self.allocation_identity,
AllocationIdentityCapability::Present
)
}
#[must_use]
pub const fn participates_in_durable_commit(self) -> bool {
matches!(self.commit_participation, CommitParticipation::Durable)
}
#[must_use]
pub const fn is_volatile(self) -> bool {
matches!(self.durability, StoreDurability::Volatile)
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)]
pub struct StoreHeapConfig;
impl StoreHeapConfig {
#[must_use]
pub const fn new() -> Self {
Self
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
pub struct StoreJournaledMemoryConfig {
key: &'static str,
}
impl StoreJournaledMemoryConfig {
#[must_use]
pub const fn new(key: &'static str) -> Self {
Self { key }
}
#[must_use]
pub const fn key(self) -> &'static str {
self.key
}
}
impl Store {
#[must_use]
pub const fn new_heap(def: Def, canister: &'static str, heap: StoreHeapConfig) -> Self {
Self {
def,
canister,
storage: StoreStorage::Heap(heap),
}
}
#[must_use]
pub const fn new_journaled(
def: Def,
canister: &'static str,
journaled: StoreJournaledMemoryConfig,
) -> Self {
Self {
def,
canister,
storage: StoreStorage::Journaled(journaled),
}
}
#[must_use]
pub const fn def(&self) -> &Def {
&self.def
}
#[must_use]
pub const fn canister(&self) -> &'static str {
self.canister
}
#[must_use]
pub const fn storage(&self) -> &StoreStorage {
&self.storage
}
#[must_use]
pub const fn is_heap_storage(&self) -> bool {
matches!(self.storage, StoreStorage::Heap(_))
}
#[must_use]
pub const fn is_journaled_storage(&self) -> bool {
matches!(self.storage, StoreStorage::Journaled(_))
}
#[must_use]
pub const fn journaled_memory_config(&self) -> Option<&StoreJournaledMemoryConfig> {
self.storage.journaled_memory_config()
}
#[must_use]
pub const fn storage_capabilities(&self) -> StoreStorageCapabilities {
self.storage.storage_capabilities()
}
#[must_use]
pub const fn stable_store_key(&self) -> &'static str {
match self.storage {
StoreStorage::Journaled(config) => config.key(),
StoreStorage::Heap(_) => panic!("heap stores do not have a stable store key"),
}
}
#[must_use]
pub fn stable_data_allocation(&self, memory_namespace: &str) -> StableMemoryAllocation {
self.stable_allocation(memory_namespace, StoreMemoryRole::Data)
}
#[must_use]
pub fn stable_data_allocation_with_schema_metadata(
&self,
memory_namespace: &str,
schema_metadata: StableMemoryAllocationMetadata,
) -> StableMemoryAllocation {
self.stable_allocation_with_schema_metadata(
memory_namespace,
StoreMemoryRole::Data,
schema_metadata,
)
}
#[must_use]
pub fn stable_index_allocation(&self, memory_namespace: &str) -> StableMemoryAllocation {
self.stable_allocation(memory_namespace, StoreMemoryRole::Index)
}
#[must_use]
pub fn stable_index_allocation_with_schema_metadata(
&self,
memory_namespace: &str,
schema_metadata: StableMemoryAllocationMetadata,
) -> StableMemoryAllocation {
self.stable_allocation_with_schema_metadata(
memory_namespace,
StoreMemoryRole::Index,
schema_metadata,
)
}
#[must_use]
pub fn stable_schema_allocation(&self, memory_namespace: &str) -> StableMemoryAllocation {
self.stable_allocation(memory_namespace, StoreMemoryRole::Schema)
}
#[must_use]
pub fn journal_allocation(&self, memory_namespace: &str) -> StableMemoryAllocation {
StableMemoryAllocation::without_schema_metadata(stable_store_memory_key(
memory_namespace,
self.stable_store_key(),
"journal",
))
}
#[must_use]
pub fn stable_schema_allocation_with_schema_metadata(
&self,
memory_namespace: &str,
schema_metadata: StableMemoryAllocationMetadata,
) -> StableMemoryAllocation {
self.stable_allocation_with_schema_metadata(
memory_namespace,
StoreMemoryRole::Schema,
schema_metadata,
)
}
#[must_use]
pub fn stable_allocation(
&self,
memory_namespace: &str,
role: StoreMemoryRole,
) -> StableMemoryAllocation {
StableMemoryAllocation::without_schema_metadata(stable_store_memory_key(
memory_namespace,
self.stable_store_key(),
role.as_str(),
))
}
fn stable_allocation_with_schema_metadata(
&self,
memory_namespace: &str,
role: StoreMemoryRole,
schema_metadata: StableMemoryAllocationMetadata,
) -> StableMemoryAllocation {
StableMemoryAllocation::with_schema_metadata(
stable_store_memory_key(memory_namespace, self.stable_store_key(), role.as_str()),
schema_metadata,
)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum StoreMemoryRole {
Data,
Index,
Schema,
}
impl StoreMemoryRole {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Data => "data",
Self::Index => "index",
Self::Schema => "schema",
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct StableMemoryAllocationMetadata {
version: Option<u32>,
fingerprint_method_version: Option<u8>,
fingerprint: Option<String>,
}
impl StableMemoryAllocationMetadata {
const fn new(
schema_version: Option<u32>,
schema_fingerprint_method_version: Option<u8>,
schema_fingerprint: Option<String>,
) -> Self {
Self {
version: schema_version,
fingerprint_method_version: schema_fingerprint_method_version,
fingerprint: schema_fingerprint,
}
}
#[must_use]
pub const fn from_accepted_schema_contract(
schema_version: u32,
schema_fingerprint_method_version: u8,
schema_fingerprint: String,
) -> Self {
Self::new(
Some(schema_version),
Some(schema_fingerprint_method_version),
Some(schema_fingerprint),
)
}
#[must_use]
pub const fn absent() -> Self {
Self::new(None, None, None)
}
#[must_use]
pub const fn schema_version(&self) -> Option<u32> {
self.version
}
#[must_use]
pub const fn schema_fingerprint_method_version(&self) -> Option<u8> {
self.fingerprint_method_version
}
#[must_use]
pub const fn schema_fingerprint(&self) -> Option<&str> {
match &self.fingerprint {
Some(value) => Some(value.as_str()),
None => None,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct StableMemoryAllocation {
stable_key: String,
schema_metadata: StableMemoryAllocationMetadata,
}
impl StableMemoryAllocation {
#[must_use]
pub const fn without_schema_metadata(stable_key: String) -> Self {
Self::with_schema_metadata(stable_key, StableMemoryAllocationMetadata::absent())
}
#[must_use]
pub const fn with_schema_metadata(
stable_key: String,
schema_metadata: StableMemoryAllocationMetadata,
) -> Self {
Self {
stable_key,
schema_metadata,
}
}
#[must_use]
pub const fn stable_key(&self) -> &str {
self.stable_key.as_str()
}
#[must_use]
pub const fn schema_metadata(&self) -> &StableMemoryAllocationMetadata {
&self.schema_metadata
}
#[must_use]
pub const fn schema_version(&self) -> Option<u32> {
self.schema_metadata.schema_version()
}
#[must_use]
pub const fn schema_fingerprint_method_version(&self) -> Option<u8> {
self.schema_metadata.schema_fingerprint_method_version()
}
#[must_use]
pub const fn schema_fingerprint(&self) -> Option<&str> {
self.schema_metadata.schema_fingerprint()
}
#[must_use]
pub fn same_identity_as(&self, other: &Self) -> bool {
self.stable_key == other.stable_key
}
}
#[must_use]
pub fn stable_memory_key(memory_namespace: &str, allocation: &str, role: &str) -> String {
format!("icydb.{memory_namespace}.{allocation}.{role}.v1")
}
#[must_use]
fn stable_store_memory_key(memory_namespace: &str, store_key: &str, role: &str) -> String {
format!("icydb.{memory_namespace}.store.{store_key}.{role}.v1")
}
impl MacroNode for Store {
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
impl ValidateNode for Store {
fn validate(&self) -> Result<(), ErrorTree> {
let mut errs = ErrorTree::new();
{
let schema = schema_read();
match schema.cast_node::<Canister>(self.canister()) {
Ok(canister) => match self.storage() {
StoreStorage::Heap(_) => {}
StoreStorage::Journaled(config) => {
validate_journaled_memory_config(&mut errs, self, *config, canister);
}
},
Err(e) => errs.add(e),
}
}
errs.result()
}
}
fn validate_journaled_memory_config(
errs: &mut ErrorTree,
store: &Store,
config: StoreJournaledMemoryConfig,
canister: &Canister,
) {
validate_stable_key_segment(errs, "store key", config.key());
for allocation in [
store.stable_data_allocation(canister.memory_namespace()),
store.stable_index_allocation(canister.memory_namespace()),
store.stable_schema_allocation(canister.memory_namespace()),
store.journal_allocation(canister.memory_namespace()),
] {
validate_stable_key(errs, "store allocation key", allocation.stable_key());
}
}
impl VisitableNode for Store {
fn route_key(&self) -> String {
self.def().path()
}
fn drive<V: Visitor>(&self, v: &mut V) {
self.def().accept(v);
}
}
#[cfg(test)]
mod tests;