/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use rkv::{
backend::{BackendDatabase, BackendEnvironment, BackendRwCursorTransaction},
Readable, Rkv, SingleStore, StoreOptions, Value, Writer,
}; use xpcom::interfaces::nsIKeyValueImporter;
usecrate::skv::{
connection::{ConnectionIncident, ToConnectionIncident},
key::Key as SkvKey,
store::{Store as SkvStore, StoreError as SkvStoreError},
value::Value as SkvValue,
};
/// Specifies the databases to import. #[derive(Clone, Debug)] pubenum SourceDatabases { /// Import all key-value pairs from one or more named databases.
Named(Vec<NamedSourceDatabase>),
/// Import all key-value pairs from all databases.
All {
conflict_policy: ConflictPolicy,
cleanup_policy: CleanupPolicy,
},
}
/// Specifies the name and settings for a single database. #[derive(Clone, Debug)] pubstruct NamedSourceDatabase { pub name: DatabaseName, pub conflict_policy: ConflictPolicy, pub cleanup_policy: CleanupPolicy,
}
impl<'env, 'a, T, D> RkvImporter<'a, Writer<T>, SingleStore<D>> where
T: BackendRwCursorTransaction<'env, Database = D>,
D: BackendDatabase,
{ /// Creates an importer for one or more Rkv databases in the same /// Rkv environment. pubfn new<'rkv: 'env, E>(
env: &'rkv Rkv<E>,
store: &'a SkvStore,
dbs: SourceDatabases,
) -> Result<Self, ImporterError> where
E: BackendEnvironment<'env, Database = D, RwTransaction = T>,
{ let dbs = match dbs {
SourceDatabases::Named(dbs) => dbs,
SourceDatabases::All {
conflict_policy,
cleanup_policy,
} => env
.get_dbs()?
.into_iter()
.map(|name| {
Ok(NamedSourceDatabase {
name: name.try_into()?,
conflict_policy,
cleanup_policy,
})
})
.collect::<Result<_, ImporterError>>()?,
}; let sources = dbs
.into_iter()
.map(|db| { let store = env.open_single(Some(db.name.as_str()), StoreOptions::default())?;
Ok((db, store))
})
.collect::<Result<_, ImporterError>>()?;
Ok(Self {
writer: env.write()?,
sources,
store,
})
}
/// Copies all key-value pairs from the specified Rkv databases into /// the corresponding Skv databases. /// /// [`RkvImporter::import`] and [`RkvImporter::cleanup`] are exposed as /// separate methods because of lifetime requirements on the /// Rkv environment. pubfn import(&'env self) -> Result<(), ImporterError> { let writer = self.store.writer()?;
writer.write(|tx| { for (db, store) in &self.sources { let importer = RkvSingleStoreImporter {
name: &db.name,
reader: &self.writer,
store,
tx,
conflict_policy: db.conflict_policy,
};
importer.import()?;
}
Ok(())
})
}
/// Cleans up imported key-value pairs from Rkv. pubfn cleanup(mutself) { for (db, store) inself.sources { match db.cleanup_policy {
CleanupPolicy::Keep => continue,
CleanupPolicy::Delete => { let _ = store.clear(&mutself.writer);
}
}
} let _ = self.writer.commit();
}
}
/// Copies all key-value pairs from a single Rkv database into /// the corresponding Skv database. struct RkvSingleStoreImporter<'a, 'rkv, 'conn, R, D> {
name: &'a DatabaseName,
reader: &'rkv R,
store: &'a SingleStore<D>,
tx: &'a rusqlite::Transaction<'conn>,
conflict_policy: ConflictPolicy,
}
fn import_pair(&self, key: &[u8], value: rkv::Value) -> Result<(), ImporterError> { let key = SkvKey::from(
std::str::from_utf8(key).map_err(|err| ImporterError::RkvKey(err.into()))?,
); let value = <SkvValue as From<serde_json::Value>>::from(match value {
Value::Bool(b) => b.into(),
Value::I64(n) => n.into(),
Value::F64(n) => n.into_inner().into(),
Value::Str(s) => s.into(),
_ => Err(ImporterError::UnsupportedRkvValueType)?,
});
letmut statement = self.tx.prepare_cached(&format!( "INSERT INTO data(
db_id,
key,
value
)
VALUES(
(SELECT id FROM dbs WHERE name = :name),
:key,
jsonb(:value)
)
ON CONFLICT {}", matchself.conflict_policy {
ConflictPolicy::Error => { // Throw an error if the values are different; // do nothing if they're equal. "DO UPDATE SET
value = throw(printf('conflict: %Q', key)) WHERE value <> excluded.value"
}
ConflictPolicy::Ignore => "DO NOTHING",
ConflictPolicy::Replace => { "DO UPDATE SET
value = excluded.value"
}
},
))?;
statement.execute(rusqlite::named_params! { ":name": self.name.as_str(), ":key": key, ":value": value,
})?;
/// The action to take if a key in a source database already exists /// and has a different value in the corresponding destination database. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[repr(u8)] pubenum ConflictPolicy {
Error = nsIKeyValueImporter::ERROR_ON_CONFLICT,
Ignore = nsIKeyValueImporter::IGNORE_ON_CONFLICT,
Replace = nsIKeyValueImporter::REPLACE_ON_CONFLICT,
}
impl TryFrom<u8> for ConflictPolicy { type Error = ImporterError;
fn try_from(value: u8) -> Result<Self, <Selfas TryFrom<u8>>::Error> {
Ok(match value { // Rust doesn't allow `as` expressions directly in // match arms.
_ if value == Self::Error as u8 => Self::Error,
_ if value == Self::Ignore as u8 => Self::Ignore,
_ if value == Self::Replace as u8 => Self::Replace,
_ => Err(ImporterError::UnknownConflictPolicy(value))?,
})
}
}
/// The action to take after a successful import. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[repr(u8)] pubenum CleanupPolicy {
Keep = nsIKeyValueImporter::KEEP_AFTER_IMPORT,
Delete = nsIKeyValueImporter::DELETE_AFTER_IMPORT,
}
impl TryFrom<u8> for CleanupPolicy { type Error = ImporterError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
Ok(match value {
_ if value == Self::Keep as u8 => Self::Keep,
_ if value == Self::Delete as u8 => Self::Delete,
_ => Err(ImporterError::UnknownCleanupPolicy(value))?,
})
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.