Add `create model` txn impl

next
Sayan Nandan 1 year ago
parent b78824d6d0
commit 138753c4ad
No known key found for this signature in database
GPG Key ID: 42EEDF4AE9D96B54

@ -36,7 +36,7 @@ use {
DictGeneric,
},
idx::{IndexBaseSpec, IndexSTSeqCns, STIndex, STIndexSeq},
storage::v1::{rw::BufferedScanner, SDSSError, SDSSResult},
storage::v1::{inf, rw::BufferedScanner, SDSSError, SDSSResult},
},
util::{copy_slice_to_array as memcpy, EndianQW},
},
@ -227,7 +227,7 @@ impl PersistMapSpec for GenericDictSpec {
}
}
unsafe fn dec_key(scanner: &mut BufferedScanner, md: &Self::EntryMD) -> Option<Self::Key> {
String::from_utf8(scanner.next_chunk_variable(md.klen).to_owned())
inf::dec::utils::decode_string(scanner, md.klen as usize)
.map(|s| s.into_boxed_str())
.ok()
}
@ -390,13 +390,9 @@ impl PersistMapSpec for FieldMapSpec {
}
}
unsafe fn dec_key(scanner: &mut BufferedScanner, md: &Self::EntryMD) -> Option<Self::Key> {
String::from_utf8(
scanner
.next_chunk_variable(md.field_id_l as usize)
.to_owned(),
)
.map(|v| v.into_boxed_str())
.ok()
inf::dec::utils::decode_string(scanner, md.field_id_l as usize)
.map(|s| s.into_boxed_str())
.ok()
}
unsafe fn dec_val(scanner: &mut BufferedScanner, md: &Self::EntryMD) -> Option<Self::Value> {
super::obj::FieldRef::obj_dec(

@ -271,4 +271,11 @@ pub mod dec {
) -> SDSSResult<PM::MapType> {
<map::PersistMapImpl<PM> as PersistObject>::default_full_dec(scanner)
}
pub mod utils {
use crate::engine::storage::v1::{BufferedScanner, SDSSError, SDSSResult};
pub unsafe fn decode_string(s: &mut BufferedScanner, len: usize) -> SDSSResult<String> {
String::from_utf8(s.next_chunk_variable(len).to_owned())
.map_err(|_| SDSSError::InternalDecodeStructureCorruptedPayload)
}
}
}

@ -39,7 +39,7 @@ use {
uuid::Uuid,
},
mem::VInline,
storage::v1::{rw::BufferedScanner, SDSSError, SDSSResult},
storage::v1::{inf, rw::BufferedScanner, SDSSError, SDSSResult},
},
util::EndianQW,
},
@ -209,6 +209,9 @@ impl ModelLayoutMD {
field_c,
}
}
pub fn p_key_len(&self) -> u64 {
self.p_key_len
}
}
#[derive(Clone, Copy)]
@ -251,12 +254,7 @@ impl<'a> PersistObject for ModelLayoutRef<'a> {
scanner: &mut BufferedScanner,
md: Self::Metadata,
) -> SDSSResult<Self::OutputType> {
let key = String::from_utf8(
scanner
.next_chunk_variable(md.p_key_len as usize)
.to_owned(),
)
.map_err(|_| SDSSError::InternalDecodeStructureCorruptedPayload)?;
let key = inf::dec::utils::decode_string(scanner, md.p_key_len as usize)?;
let fieldmap =
<super::map::PersistMapImpl<super::map::FieldMapSpec> as PersistObject>::obj_dec(
scanner,

@ -364,6 +364,7 @@ impl<TA, LF: RawFileIOInterface> JournalReader<TA, LF> {
}
}
#[derive(Debug)]
pub struct JournalWriter<LF, TA> {
/// the txn log file
log_file: SDSSFileIO<LF>,
@ -403,6 +404,15 @@ impl<LF: RawFileIOInterface, TA: JournalAdapter> JournalWriter<LF, TA> {
self.log_file.fsync_all()?;
Ok(())
}
pub fn append_event_with_recovery_plugin(&mut self, event: TA::JournalEvent) -> SDSSResult<()> {
debug_assert!(TA::RECOVERY_PLUGIN);
match self.append_event(event) {
Ok(()) => Ok(()),
Err(_) => {
return self.appendrec_journal_reverse_entry();
}
}
}
}
impl<LF: RawFileIOInterface, TA> JournalWriter<LF, TA> {

@ -24,27 +24,53 @@
*
*/
use crate::engine::storage::v1::BufferedScanner;
use {
super::{TransactionError, TransactionResult},
crate::engine::{
core::GlobalNS,
storage::v1::{
inf::{self, PersistObject},
JournalAdapter,
BufferedScanner, JournalAdapter, JournalWriter,
},
},
std::fs::File,
};
mod model;
mod space;
// re-exports
pub use {
model::CreateModelTxn,
space::{AlterSpaceTxn, CreateSpaceTxn, DropSpaceTxn},
};
#[derive(Debug)]
/// The GNS transaction driver is used to handle DDL transactions
pub struct GNSTransactionDriver {
journal: JournalWriter<File, GNSAdapter>,
}
impl GNSTransactionDriver {
/// Attempts to commit the given event into the journal, handling any possible recovery triggers and returning
/// errors (if any)
pub fn try_commit<GE: GNSEvent>(&mut self, gns_event: GE::CommitType) -> TransactionResult<()> {
let mut buf = vec![];
buf.extend(GE::OPC.to_le_bytes());
GE::encode_super_event(gns_event, &mut buf);
self.journal
.append_event_with_recovery_plugin(GNSSuperEvent(buf.into_boxed_slice()))?;
Ok(())
}
}
/*
journal implementor
*/
/// the journal adapter for DDL queries on the GNS
pub struct GNSAdapter;
#[derive(Debug)]
struct GNSAdapter;
impl JournalAdapter for GNSAdapter {
const RECOVERY_PLUGIN: bool = true;
@ -71,20 +97,27 @@ impl JournalAdapter for GNSAdapter {
pub struct GNSSuperEvent(Box<[u8]>);
/// Definition for an event in the GNS (DDL queries)
pub trait GNSEvent
where
Self: PersistObject<InputType = Self::CommitType, OutputType = Self::RestoreType> + Sized,
{
/// OPC for the event (unique)
const OPC: u16;
/// Expected type for a commit
type CommitType;
/// Expected type for a restore
type RestoreType;
fn encode_super_event(commit: Self::CommitType) -> GNSSuperEvent {
GNSSuperEvent(inf::enc::enc_full::<Self>(commit).into_boxed_slice())
/// Encodes the event into the given buffer
fn encode_super_event(commit: Self::CommitType, buf: &mut Vec<u8>) {
inf::enc::enc_full_into_buffer::<Self>(buf, commit)
}
/// Attempts to decode the event using the given scanner
fn decode_from_super_event(
scanner: &mut BufferedScanner,
) -> TransactionResult<Self::RestoreType> {
inf::dec::dec_full_from_scanner::<Self>(scanner).map_err(|e| e.into())
}
/// Update the global state from the restored event
fn update_global_state(restore: Self::RestoreType, gns: &GlobalNS) -> TransactionResult<()>;
}

@ -0,0 +1,179 @@
/*
* Created on Wed Aug 23 2023
*
* This file is a part of Skytable
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
* NoSQL database written by Sayan Nandan ("the Author") with the
* vision to provide flexibility in data modelling without compromising
* on performance, queryability or scalability.
*
* Copyright (c) 2023, Sayan Nandan <ohsayan@outlook.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
use {
super::GNSEvent,
crate::{
engine::{
core::model::{delta::IRModel, Model},
data::uuid::Uuid,
idx::STIndex,
storage::v1::{
inf::{self, obj, PersistObject},
BufferedScanner, SDSSResult,
},
txn::TransactionError,
},
util::EndianQW,
},
std::marker::PhantomData,
};
/*
create model
*/
/// Transaction for running a `create model ... (...) with {..}` query
pub struct CreateModelTxn<'a>(PhantomData<&'a ()>);
impl<'a> CreateModelTxn<'a> {
pub const fn new_commit(
space_name: &'a str,
space_uuid: Uuid,
model_name: &'a str,
model: &'a Model,
model_read: &'a IRModel<'a>,
) -> CreateModelTxnCommitPL<'a> {
CreateModelTxnCommitPL {
space_name,
space_uuid,
model_name,
model,
model_read,
}
}
}
#[derive(Clone, Copy)]
pub struct CreateModelTxnCommitPL<'a> {
space_name: &'a str,
space_uuid: Uuid,
model_name: &'a str,
model: &'a Model,
model_read: &'a IRModel<'a>,
}
pub struct CreateModelTxnRestorePL {
space_name: Box<str>,
space_uuid: Uuid,
model_name: Box<str>,
model: Model,
}
pub struct CreateModelTxnMD {
space_name_l: u64,
space_uuid: Uuid,
model_name_l: u64,
model_meta: <obj::ModelLayoutRef<'static> as PersistObject>::Metadata,
}
impl<'a> PersistObject for CreateModelTxn<'a> {
const METADATA_SIZE: usize =
sizeof!(u64, 2) + sizeof!(u128) + <obj::ModelLayoutRef<'a> as PersistObject>::METADATA_SIZE;
type InputType = CreateModelTxnCommitPL<'a>;
type OutputType = CreateModelTxnRestorePL;
type Metadata = CreateModelTxnMD;
fn pretest_can_dec_object(scanner: &BufferedScanner, md: &Self::Metadata) -> bool {
scanner.has_left((md.model_meta.p_key_len() + md.model_name_l) as usize)
}
fn meta_enc(buf: &mut Vec<u8>, data: Self::InputType) {
buf.extend(data.space_name.len().u64_bytes_le());
buf.extend(data.space_uuid.to_le_bytes());
buf.extend(data.model_name.len().u64_bytes_le());
<obj::ModelLayoutRef as PersistObject>::meta_enc(
buf,
obj::ModelLayoutRef::from((data.model, data.model_read)),
)
}
unsafe fn meta_dec(scanner: &mut BufferedScanner) -> SDSSResult<Self::Metadata> {
let space_name_l = u64::from_le_bytes(scanner.next_chunk());
let space_uuid = Uuid::from_bytes(scanner.next_chunk());
let model_name_l = u64::from_le_bytes(scanner.next_chunk());
let model_meta = <obj::ModelLayoutRef as PersistObject>::meta_dec(scanner)?;
Ok(CreateModelTxnMD {
space_name_l,
space_uuid,
model_name_l,
model_meta,
})
}
fn obj_enc(buf: &mut Vec<u8>, data: Self::InputType) {
buf.extend(data.model_name.as_bytes());
<obj::ModelLayoutRef as PersistObject>::obj_enc(
buf,
obj::ModelLayoutRef::from((data.model, data.model_read)),
)
}
unsafe fn obj_dec(s: &mut BufferedScanner, md: Self::Metadata) -> SDSSResult<Self::OutputType> {
let space_name =
inf::dec::utils::decode_string(s, md.space_name_l as usize)?.into_boxed_str();
let model_name =
inf::dec::utils::decode_string(s, md.model_name_l as usize)?.into_boxed_str();
let model = <obj::ModelLayoutRef as PersistObject>::obj_dec(s, md.model_meta)?;
Ok(CreateModelTxnRestorePL {
space_name,
space_uuid: md.space_uuid,
model_name,
model,
})
}
}
impl<'a> GNSEvent for CreateModelTxn<'a> {
const OPC: u16 = 3;
type CommitType = CreateModelTxnCommitPL<'a>;
type RestoreType = CreateModelTxnRestorePL;
fn update_global_state(
CreateModelTxnRestorePL {
space_name,
space_uuid,
model_name,
model,
}: Self::RestoreType,
gns: &crate::engine::core::GlobalNS,
) -> crate::engine::txn::TransactionResult<()> {
let rgns = gns.spaces().read();
/*
NOTE(@ohsayan):
do note that this is a little interesting situation especially because we need to be able to handle
changes in the schema *and* be able to "sync" that (for consistency) with the model's primary index.
There is no evident way about how this is going to be handled, but the ideal way would be to keep
versioned index of schemas.
*/
match rgns.st_get(&space_name) {
Some(space) if space.get_uuid() == space_uuid => {
if space._create_model(&model_name, model).is_ok() {
Ok(())
} else {
Err(TransactionError::OnRestoreDataConflictAlreadyExists)
}
}
Some(_) => return Err(TransactionError::OnRestoreDataConflictMismatch),
None => return Err(TransactionError::OnRestoreDataMissing),
}
}
}

@ -24,18 +24,18 @@
*
*/
use crate::engine::{idx::STIndex, txn::TransactionError};
use {
super::GNSEvent,
crate::{
engine::{
core::space::Space,
core::{space::Space, GlobalNS},
data::{uuid::Uuid, DictGeneric},
idx::STIndex,
storage::v1::{
inf::{map, obj, PersistObject},
SDSSError,
inf::{self, map, obj, PersistObject},
BufferedScanner, SDSSResult,
},
txn::{TransactionError, TransactionResult},
},
util::EndianQW,
},
@ -46,8 +46,23 @@ use {
create space
*/
/// A transaction to run a `create space ...` operation
pub struct CreateSpaceTxn<'a>(PhantomData<&'a ()>);
impl<'a> CreateSpaceTxn<'a> {
pub const fn new_commit(
space_meta: &'a DictGeneric,
space_name: &'a str,
space: &'a Space,
) -> CreateSpaceTxnCommitPL<'a> {
CreateSpaceTxnCommitPL {
space_meta,
space_name,
space,
}
}
}
#[derive(Clone, Copy)]
pub struct CreateSpaceTxnCommitPL<'a> {
pub(crate) space_meta: &'a DictGeneric,
@ -71,10 +86,7 @@ impl<'a> PersistObject for CreateSpaceTxn<'a> {
type InputType = CreateSpaceTxnCommitPL<'a>;
type OutputType = CreateSpaceTxnRestorePL;
type Metadata = CreateSpaceTxnMD;
fn pretest_can_dec_object(
scanner: &crate::engine::storage::v1::BufferedScanner,
md: &Self::Metadata,
) -> bool {
fn pretest_can_dec_object(scanner: &BufferedScanner, md: &Self::Metadata) -> bool {
scanner.has_left(md.space_name_l as usize)
}
fn meta_enc(buf: &mut Vec<u8>, data: Self::InputType) {
@ -84,9 +96,7 @@ impl<'a> PersistObject for CreateSpaceTxn<'a> {
obj::SpaceLayoutRef::from((data.space, data.space_meta)),
);
}
unsafe fn meta_dec(
scanner: &mut crate::engine::storage::v1::BufferedScanner,
) -> crate::engine::storage::v1::SDSSResult<Self::Metadata> {
unsafe fn meta_dec(scanner: &mut BufferedScanner) -> SDSSResult<Self::Metadata> {
let space_name_l = u64::from_le_bytes(scanner.next_chunk());
let space_meta = <obj::SpaceLayoutRef as PersistObject>::meta_dec(scanner)?;
Ok(CreateSpaceTxnMD {
@ -98,14 +108,9 @@ impl<'a> PersistObject for CreateSpaceTxn<'a> {
buf.extend(data.space_name.as_bytes());
<obj::SpaceLayoutRef as PersistObject>::meta_enc(buf, (data.space, data.space_meta).into());
}
unsafe fn obj_dec(
s: &mut crate::engine::storage::v1::BufferedScanner,
md: Self::Metadata,
) -> crate::engine::storage::v1::SDSSResult<Self::OutputType> {
unsafe fn obj_dec(s: &mut BufferedScanner, md: Self::Metadata) -> SDSSResult<Self::OutputType> {
let space_name =
String::from_utf8(s.next_chunk_variable(md.space_name_l as usize).to_owned())
.map_err(|_| SDSSError::InternalDecodeStructureCorruptedPayload)?
.into_boxed_str();
inf::dec::utils::decode_string(s, md.space_name_l as usize)?.into_boxed_str();
let space = <obj::SpaceLayoutRef as PersistObject>::obj_dec(s, md.space_meta)?;
Ok(CreateSpaceTxnRestorePL { space_name, space })
}
@ -134,18 +139,36 @@ impl<'a> GNSEvent for CreateSpaceTxn<'a> {
for now dump the entire meta
*/
/// A transaction to run `alter space ...`
pub struct AlterSpaceTxn<'a>(PhantomData<&'a ()>);
impl<'a> AlterSpaceTxn<'a> {
pub const fn new_commit(
space_uuid: Uuid,
space_name: &'a str,
space_meta: &'a DictGeneric,
) -> AlterSpaceTxnCommitPL<'a> {
AlterSpaceTxnCommitPL {
space_uuid,
space_name,
space_meta,
}
}
}
pub struct AlterSpaceTxnMD {
uuid: Uuid,
space_name_l: u64,
dict_len: u64,
}
#[derive(Clone, Copy)]
pub struct AlterSpaceTxnCommitPL<'a> {
space_uuid: Uuid,
space_name: &'a str,
space_meta: &'a DictGeneric,
}
pub struct AlterSpaceTxnRestorePL {
space_name: Box<str>,
space_meta: DictGeneric,
@ -156,10 +179,7 @@ impl<'a> PersistObject for AlterSpaceTxn<'a> {
type InputType = AlterSpaceTxnCommitPL<'a>;
type OutputType = AlterSpaceTxnRestorePL;
type Metadata = AlterSpaceTxnMD;
fn pretest_can_dec_object(
scanner: &crate::engine::storage::v1::BufferedScanner,
md: &Self::Metadata,
) -> bool {
fn pretest_can_dec_object(scanner: &BufferedScanner, md: &Self::Metadata) -> bool {
scanner.has_left(md.space_name_l as usize)
}
fn meta_enc(buf: &mut Vec<u8>, data: Self::InputType) {
@ -167,9 +187,7 @@ impl<'a> PersistObject for AlterSpaceTxn<'a> {
buf.extend(data.space_name.len().u64_bytes_le());
buf.extend(data.space_meta.len().u64_bytes_le());
}
unsafe fn meta_dec(
scanner: &mut crate::engine::storage::v1::BufferedScanner,
) -> crate::engine::storage::v1::SDSSResult<Self::Metadata> {
unsafe fn meta_dec(scanner: &mut BufferedScanner) -> SDSSResult<Self::Metadata> {
Ok(AlterSpaceTxnMD {
uuid: Uuid::from_bytes(scanner.next_chunk()),
space_name_l: u64::from_le_bytes(scanner.next_chunk()),
@ -180,14 +198,9 @@ impl<'a> PersistObject for AlterSpaceTxn<'a> {
buf.extend(data.space_name.as_bytes());
<map::PersistMapImpl<map::GenericDictSpec> as PersistObject>::obj_enc(buf, data.space_meta);
}
unsafe fn obj_dec(
s: &mut crate::engine::storage::v1::BufferedScanner,
md: Self::Metadata,
) -> crate::engine::storage::v1::SDSSResult<Self::OutputType> {
unsafe fn obj_dec(s: &mut BufferedScanner, md: Self::Metadata) -> SDSSResult<Self::OutputType> {
let space_name =
String::from_utf8(s.next_chunk_variable(md.space_name_l as usize).to_owned())
.map_err(|_| SDSSError::InternalDecodeStructureCorruptedPayload)?
.into_boxed_str();
inf::dec::utils::decode_string(s, md.space_name_l as usize)?.into_boxed_str();
let space_meta = <map::PersistMapImpl<map::GenericDictSpec> as PersistObject>::obj_dec(
s,
map::MapIndexSizeMD(md.dict_len as usize),
@ -199,11 +212,47 @@ impl<'a> PersistObject for AlterSpaceTxn<'a> {
}
}
impl<'a> GNSEvent for AlterSpaceTxn<'a> {
const OPC: u16 = 1;
type CommitType = AlterSpaceTxnCommitPL<'a>;
type RestoreType = AlterSpaceTxnRestorePL;
fn update_global_state(
AlterSpaceTxnRestorePL {
space_name,
space_meta,
}: Self::RestoreType,
gns: &crate::engine::core::GlobalNS,
) -> TransactionResult<()> {
let gns = gns.spaces().read();
match gns.st_get(&space_name) {
Some(space) => {
let mut wmeta = space.metadata().env().write();
space_meta
.into_iter()
.for_each(|(k, v)| wmeta.st_upsert(k, v));
}
None => return Err(TransactionError::OnRestoreDataMissing),
}
Ok(())
}
}
/*
drop space
*/
pub struct DropSpace<'a>(PhantomData<&'a ()>);
/// A transaction to run `drop space ...`
pub struct DropSpaceTxn<'a>(PhantomData<&'a ()>);
impl<'a> DropSpaceTxn<'a> {
pub const fn new_commit(space_name: &'a str, uuid: Uuid) -> DropSpaceTxnCommitPL<'a> {
DropSpaceTxnCommitPL { space_name, uuid }
}
}
pub struct DropSpaceTxnMD {
space_name_l: u64,
uuid: Uuid,
@ -213,29 +262,25 @@ pub struct DropSpaceTxnCommitPL<'a> {
space_name: &'a str,
uuid: Uuid,
}
pub struct DropSpaceTxnRestorePL {
uuid: Uuid,
space_name: Box<str>,
}
impl<'a> PersistObject for DropSpace<'a> {
impl<'a> PersistObject for DropSpaceTxn<'a> {
const METADATA_SIZE: usize = sizeof!(u128) + sizeof!(u64);
type InputType = DropSpaceTxnCommitPL<'a>;
type OutputType = DropSpaceTxnRestorePL;
type Metadata = DropSpaceTxnMD;
fn pretest_can_dec_object(
scanner: &crate::engine::storage::v1::BufferedScanner,
md: &Self::Metadata,
) -> bool {
fn pretest_can_dec_object(scanner: &BufferedScanner, md: &Self::Metadata) -> bool {
scanner.has_left(md.space_name_l as usize)
}
fn meta_enc(buf: &mut Vec<u8>, data: Self::InputType) {
buf.extend(data.space_name.len().u64_bytes_le());
buf.extend(data.uuid.to_le_bytes());
}
unsafe fn meta_dec(
scanner: &mut crate::engine::storage::v1::BufferedScanner,
) -> crate::engine::storage::v1::SDSSResult<Self::Metadata> {
unsafe fn meta_dec(scanner: &mut BufferedScanner) -> SDSSResult<Self::Metadata> {
Ok(DropSpaceTxnMD {
space_name_l: u64::from_le_bytes(scanner.next_chunk()),
uuid: Uuid::from_bytes(scanner.next_chunk()),
@ -244,17 +289,37 @@ impl<'a> PersistObject for DropSpace<'a> {
fn obj_enc(buf: &mut Vec<u8>, data: Self::InputType) {
buf.extend(data.space_name.as_bytes());
}
unsafe fn obj_dec(
s: &mut crate::engine::storage::v1::BufferedScanner,
md: Self::Metadata,
) -> crate::engine::storage::v1::SDSSResult<Self::OutputType> {
unsafe fn obj_dec(s: &mut BufferedScanner, md: Self::Metadata) -> SDSSResult<Self::OutputType> {
let space_name =
String::from_utf8(s.next_chunk_variable(md.space_name_l as usize).to_owned())
.map_err(|_| SDSSError::InternalDecodeStructureCorruptedPayload)?
.into_boxed_str();
inf::dec::utils::decode_string(s, md.space_name_l as usize)?.into_boxed_str();
Ok(DropSpaceTxnRestorePL {
uuid: md.uuid,
space_name,
})
}
}
impl<'a> GNSEvent for DropSpaceTxn<'a> {
const OPC: u16 = 2;
type CommitType = DropSpaceTxnCommitPL<'a>;
type RestoreType = DropSpaceTxnRestorePL;
fn update_global_state(
DropSpaceTxnRestorePL { uuid, space_name }: Self::RestoreType,
gns: &GlobalNS,
) -> TransactionResult<()> {
let mut wgns = gns.spaces().write();
match wgns.entry(space_name) {
std::collections::hash_map::Entry::Occupied(oe) => {
if oe.get().get_uuid() == uuid {
oe.remove_entry();
Ok(())
} else {
return Err(TransactionError::OnRestoreDataConflictMismatch);
}
}
std::collections::hash_map::Entry::Vacant(_) => {
return Err(TransactionError::OnRestoreDataMissing)
}
}
}
}

@ -36,6 +36,10 @@ pub enum TransactionError {
/// While restoring a certain item, a non-resolvable conflict was encountered in the global state, because the item was
/// already present (when it was expected to not be present)
OnRestoreDataConflictAlreadyExists,
/// On restore, a certain item that was expected to be present was missing in the global state
OnRestoreDataMissing,
/// On restore, a certain item that was expected to match a certain value, has a different value
OnRestoreDataConflictMismatch,
}
direct_from! {

Loading…
Cancel
Save