Import upstream v0.16.22, stripped
Upstream commit: 474dd0229cb20cf513036619781ed97bd8073c3f Enterprise-only files removed or emptied: 63 Enterprise-only snippets removed: 117 in 50 files Dangling module declarations removed: 5 Cargo edits turning enterprise off: 14 Verification: clean Enterprise feature gates left for rebuilt features: 19 in 18 files Produced by tools/fork/strip.py. The full report is in docs/fork/strip-reports/ on main.
This commit is contained in:
@@ -0,0 +1,206 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::{RegistryStore, Store, registry::RegistryObject};
|
||||
use registry::{
|
||||
schema::{
|
||||
prelude::{Object, ObjectType, Property},
|
||||
structs::ClusterRole,
|
||||
},
|
||||
types::{
|
||||
ObjectImpl,
|
||||
error::{Error, ValidationError, Warning},
|
||||
id::ObjectId,
|
||||
},
|
||||
};
|
||||
use types::id::Id;
|
||||
|
||||
pub struct Bootstrap {
|
||||
pub registry: RegistryStore,
|
||||
pub data_store: Store,
|
||||
pub errors: Vec<Error>,
|
||||
pub warnings: Vec<Warning>,
|
||||
pub has_fatal_errors: bool,
|
||||
pub role: Option<ClusterRole>,
|
||||
}
|
||||
|
||||
impl Bootstrap {
|
||||
pub async fn new(registry: RegistryStore) -> Self {
|
||||
let mut bp = Self::new_uninitialized(registry);
|
||||
|
||||
let Some(role_name) = bp.registry.cluster_role().map(|r| r.to_string()) else {
|
||||
return bp;
|
||||
};
|
||||
|
||||
for role in bp.list_infallible::<ClusterRole>().await {
|
||||
if role.object.name == role_name {
|
||||
bp.role = Some(role.object);
|
||||
return bp;
|
||||
}
|
||||
}
|
||||
|
||||
bp.build_error(
|
||||
ObjectType::ClusterRole.singleton(),
|
||||
format!("Cluster role \"{role_name}\" not found in registry"),
|
||||
);
|
||||
|
||||
bp
|
||||
}
|
||||
|
||||
pub fn new_uninitialized(registry: RegistryStore) -> Self {
|
||||
Self {
|
||||
data_store: registry.0.store.clone(),
|
||||
registry,
|
||||
errors: Vec::new(),
|
||||
warnings: Vec::new(),
|
||||
has_fatal_errors: false,
|
||||
role: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_data_store(mut self, data_store: Store) -> Self {
|
||||
self.data_store = data_store;
|
||||
self
|
||||
}
|
||||
|
||||
pub async fn setting<T: ObjectImpl + From<Object>>(&mut self) -> trc::Result<T> {
|
||||
let object_id = T::OBJECT.singleton();
|
||||
|
||||
if let Some(setting) = self.registry.object::<T>(object_id.id()).await? {
|
||||
let mut errors = Vec::new();
|
||||
if setting.validate(&mut errors) {
|
||||
return Ok(setting);
|
||||
}
|
||||
self.errors.push(Error::Validation { object_id, errors });
|
||||
}
|
||||
|
||||
Ok(T::default())
|
||||
}
|
||||
|
||||
pub async fn setting_infallible<T: ObjectImpl + From<Object>>(&mut self) -> T {
|
||||
match self.setting::<T>().await {
|
||||
Ok(setting) => setting,
|
||||
Err(err) => {
|
||||
if !self.has_fatal_errors {
|
||||
self.errors.push(Error::Internal {
|
||||
object_id: Some(T::OBJECT.singleton()),
|
||||
error: err,
|
||||
});
|
||||
self.has_fatal_errors = true;
|
||||
}
|
||||
T::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_infallible<T: ObjectImpl + From<Object>>(&mut self, id: Id) -> Option<T> {
|
||||
match self.registry.object::<T>(id).await {
|
||||
Ok(Some(setting)) => {
|
||||
let mut errors = Vec::new();
|
||||
if setting.validate(&mut errors) {
|
||||
Some(setting)
|
||||
} else {
|
||||
self.errors.push(Error::Validation {
|
||||
object_id: ObjectId::new(T::OBJECT, id),
|
||||
errors,
|
||||
});
|
||||
None
|
||||
}
|
||||
}
|
||||
Ok(None) => {
|
||||
self.errors.push(Error::NotFound {
|
||||
object_id: ObjectId::new(T::OBJECT, id),
|
||||
});
|
||||
None
|
||||
}
|
||||
Err(err) => {
|
||||
if !self.has_fatal_errors {
|
||||
self.errors.push(Error::Internal {
|
||||
object_id: Some(ObjectId::new(T::OBJECT, id)),
|
||||
error: err,
|
||||
});
|
||||
self.has_fatal_errors = true;
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn list_infallible<T: ObjectImpl + From<Object>>(
|
||||
&mut self,
|
||||
) -> Vec<RegistryObject<T>> {
|
||||
match self.registry.list::<T>().await {
|
||||
Ok(objects) => objects
|
||||
.into_iter()
|
||||
.filter(|object| self.validate(object.id, &object.object))
|
||||
.collect(),
|
||||
Err(err) => {
|
||||
if !self.has_fatal_errors {
|
||||
self.errors.push(Error::Internal {
|
||||
object_id: None,
|
||||
error: err,
|
||||
});
|
||||
self.has_fatal_errors = true;
|
||||
}
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_error(&mut self, id: ObjectId, message: impl Into<String>) {
|
||||
self.errors.push(Error::Build {
|
||||
object_id: id,
|
||||
message: message.into(),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn build_warning(&mut self, id: ObjectId, message: impl Into<String>) {
|
||||
self.warnings.push(Warning {
|
||||
object_id: id,
|
||||
property: None,
|
||||
message: message.into(),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn invalid_property(&mut self, id: ObjectId, property: Property, value: impl Into<String>) {
|
||||
self.errors.push(Error::Validation {
|
||||
object_id: id,
|
||||
errors: vec![ValidationError::Invalid {
|
||||
property,
|
||||
value: value.into(),
|
||||
}],
|
||||
});
|
||||
}
|
||||
|
||||
pub fn validate(&mut self, id: ObjectId, object: &impl ObjectImpl) -> bool {
|
||||
let mut errors = Vec::new();
|
||||
if object.validate(&mut errors) {
|
||||
true
|
||||
} else {
|
||||
self.errors.push(Error::Validation {
|
||||
object_id: id,
|
||||
errors,
|
||||
});
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn node_id(&self) -> u16 {
|
||||
self.registry.0.node_id
|
||||
}
|
||||
|
||||
pub fn log_errors(&self) {
|
||||
for error in &self.errors {
|
||||
error.log();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn log_warnings(&self) {
|
||||
for warning in &self.warnings {
|
||||
warning.log();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::{
|
||||
IterateParams, RegistryStore, SUBSPACE_REGISTRY, U16_LEN, U64_LEN, ValueKey,
|
||||
registry::{RegistryObject, local::RegistryInit},
|
||||
write::{
|
||||
AnyClass, RegistryClass, ValueClass,
|
||||
key::{DeserializeBigEndian, KeySerializer},
|
||||
},
|
||||
};
|
||||
use registry::{
|
||||
pickle::PickledStream,
|
||||
schema::prelude::{Object, ObjectType},
|
||||
types::{EnumImpl, ObjectImpl, id::ObjectId},
|
||||
};
|
||||
use trc::AddContext;
|
||||
use types::id::Id;
|
||||
|
||||
impl RegistryStore {
|
||||
pub async fn get(&self, object_id: ObjectId) -> trc::Result<Option<Object>> {
|
||||
if object_id.object() != ObjectType::DataStore {
|
||||
self.0
|
||||
.store
|
||||
.get_value::<Object>(ValueKey::from(ValueClass::Registry(RegistryClass::Item {
|
||||
object_id: object_id.object().to_id(),
|
||||
item_id: object_id.id().id(),
|
||||
})))
|
||||
.await
|
||||
} else {
|
||||
match self.0.read_data_store().await {
|
||||
RegistryInit::Ok(data_store) => Ok(Some(Object {
|
||||
inner: data_store.into(),
|
||||
revision: 0,
|
||||
})),
|
||||
RegistryInit::Err(err) => {
|
||||
Err(trc::EventType::Registry(trc::RegistryEvent::LocalReadError)
|
||||
.into_err()
|
||||
.caused_by(trc::location!())
|
||||
.reason(err))
|
||||
}
|
||||
RegistryInit::Bootstrap => Ok(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn object<T: ObjectImpl + From<Object>>(&self, id: Id) -> trc::Result<Option<T>> {
|
||||
self.get(ObjectId::new(T::OBJECT, id))
|
||||
.await
|
||||
.map(|v| v.map(T::from))
|
||||
}
|
||||
|
||||
pub async fn list<T: ObjectImpl + From<Object>>(&self) -> trc::Result<Vec<RegistryObject<T>>> {
|
||||
let object_type = T::OBJECT;
|
||||
|
||||
let mut results = Vec::new();
|
||||
self.0
|
||||
.store
|
||||
.iterate(
|
||||
IterateParams::new(
|
||||
ValueKey::from(ValueClass::Any(AnyClass {
|
||||
subspace: SUBSPACE_REGISTRY,
|
||||
key: KeySerializer::new(U16_LEN)
|
||||
.write(object_type.to_id())
|
||||
.finalize(),
|
||||
})),
|
||||
ValueKey::from(ValueClass::Any(AnyClass {
|
||||
subspace: SUBSPACE_REGISTRY,
|
||||
key: KeySerializer::new(U16_LEN + U64_LEN)
|
||||
.write(object_type.to_id())
|
||||
.write(u64::MAX)
|
||||
.finalize(),
|
||||
})),
|
||||
),
|
||||
|key, value| {
|
||||
let id = key.deserialize_be_u64(U16_LEN)?;
|
||||
let object = PickledStream::new(value)
|
||||
.and_then(|mut stream| T::unpickle(&mut stream))
|
||||
.ok_or_else(|| {
|
||||
trc::EventType::Registry(trc::RegistryEvent::DeserializationError)
|
||||
.into_err()
|
||||
.caused_by(trc::location!())
|
||||
.id(id)
|
||||
.details(object_type.as_str())
|
||||
.ctx(trc::Key::Value, value)
|
||||
})?;
|
||||
|
||||
results.push(RegistryObject {
|
||||
id: ObjectId::new(object_type, Id::new(id)),
|
||||
object,
|
||||
revision: xxhash_rust::xxh3::xxh3_64(value),
|
||||
});
|
||||
|
||||
Ok(true)
|
||||
},
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
|
||||
Ok(results)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::{RegistryStore, RegistryStoreInner, Store};
|
||||
use registry::schema::structs::DataStore;
|
||||
use std::{net::IpAddr, path::PathBuf};
|
||||
use utils::snowflake::SnowflakeIdGenerator;
|
||||
|
||||
pub(crate) enum RegistryInit {
|
||||
Ok(DataStore),
|
||||
Err(String),
|
||||
Bootstrap,
|
||||
}
|
||||
|
||||
impl RegistryStoreInner {
|
||||
pub(crate) fn new(local_path: PathBuf) -> Self {
|
||||
let env_hostname = std::env::var("STALWART_HOSTNAME")
|
||||
.ok()
|
||||
.filter(|h| !h.is_empty())
|
||||
.unwrap_or_else(|| {
|
||||
let host = gethostname::gethostname();
|
||||
let host = host.to_string_lossy();
|
||||
if host.parse::<IpAddr>().is_err() {
|
||||
host.to_lowercase()
|
||||
} else {
|
||||
"localhost".to_string()
|
||||
}
|
||||
});
|
||||
|
||||
Self {
|
||||
local_path,
|
||||
store: Store::None,
|
||||
id_generator: SnowflakeIdGenerator::new(),
|
||||
node_id: 0,
|
||||
env_recovery_mode: std::env::var("STALWART_RECOVERY_MODE")
|
||||
.ok()
|
||||
.map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
|
||||
.unwrap_or(false),
|
||||
env_recovery_admin: std::env::var("STALWART_RECOVERY_ADMIN")
|
||||
.ok()
|
||||
.and_then(|v| {
|
||||
v.split_once(':')
|
||||
.map(|(a, p)| (a.trim().to_string(), p.trim().to_string()))
|
||||
})
|
||||
.filter(|(a, p)| !a.is_empty() && !p.is_empty()),
|
||||
env_cluster_role: std::env::var("STALWART_ROLE")
|
||||
.ok()
|
||||
.filter(|r| !r.is_empty()),
|
||||
env_push_shard_id: std::env::var("STALWART_PUSH_SHARD")
|
||||
.ok()
|
||||
.and_then(|id| id.parse::<u32>().ok().and_then(|v| v.checked_sub(1)))
|
||||
.unwrap_or(0),
|
||||
env_public_url: std::env::var("STALWART_PUBLIC_URL")
|
||||
.ok()
|
||||
.map(|v| v.trim().trim_end_matches('/').to_string())
|
||||
.filter(|u| !u.is_empty())
|
||||
.or_else(|| {
|
||||
std::env::var("STALWART_HTTPS_PORT").ok().and_then(|p| {
|
||||
p.parse::<u16>()
|
||||
.ok()
|
||||
.map(|port| format!("https://{}:{}", env_hostname, port))
|
||||
})
|
||||
}),
|
||||
env_hostname,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn read_data_store(&self) -> RegistryInit {
|
||||
match tokio::fs::read_to_string(&self.local_path).await {
|
||||
Ok(contents) => match serde_json::from_str::<DataStore>(&contents) {
|
||||
Ok(data_store) => RegistryInit::Ok(data_store),
|
||||
Err(err) => RegistryInit::Err(format!(
|
||||
"Failed to parse data store settings at {}: {}",
|
||||
self.local_path.display(),
|
||||
err
|
||||
)),
|
||||
},
|
||||
Err(err) if err.kind() == std::io::ErrorKind::NotFound => RegistryInit::Bootstrap,
|
||||
Err(err) => RegistryInit::Err(format!(
|
||||
"Failed to read data store settings at {}: {}",
|
||||
self.local_path.display(),
|
||||
err
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RegistryStore {
|
||||
pub async fn write_data_store(&self, data_store: &DataStore) -> trc::Result<()> {
|
||||
let json_text = serde_json::to_string(data_store).map_err(|err| {
|
||||
trc::EventType::Registry(trc::RegistryEvent::LocalWriteError)
|
||||
.into_err()
|
||||
.caused_by(trc::location!())
|
||||
.reason(err)
|
||||
})?;
|
||||
tokio::fs::write(&self.0.local_path, json_text)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
trc::EventType::Registry(trc::RegistryEvent::LocalWriteError)
|
||||
.into_err()
|
||||
.caused_by(trc::location!())
|
||||
.reason(err)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
pub mod bootstrap;
|
||||
pub mod get;
|
||||
pub mod local;
|
||||
pub mod query;
|
||||
pub mod write;
|
||||
|
||||
use crate::{
|
||||
Deserialize, SerializeInfallible, U16_LEN, U32_LEN, U64_LEN,
|
||||
write::key::{DeserializeBigEndian, KeySerializer},
|
||||
};
|
||||
use registry::{
|
||||
pickle::{Pickle, PickledStream},
|
||||
schema::{
|
||||
prelude::{Object, ObjectInner, ObjectType, Property},
|
||||
structs::{
|
||||
ArchivedItem, DmarcInternalReport, Metric, SpamTrainingSample, Task, TlsInternalReport,
|
||||
Trace,
|
||||
},
|
||||
},
|
||||
types::{EnumImpl, ObjectImpl, id::ObjectId},
|
||||
};
|
||||
use types::id::Id;
|
||||
|
||||
pub struct RegistryObject<T: ObjectImpl> {
|
||||
pub id: ObjectId,
|
||||
pub object: T,
|
||||
pub revision: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RegistryQuery {
|
||||
pub(crate) object_type: ObjectType,
|
||||
pub filters: Vec<RegistryFilter>,
|
||||
pub(crate) start: RegistryQueryStart,
|
||||
pub(crate) limit: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct RegistryObjectCounter(pub usize);
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) enum RegistryQueryStart {
|
||||
Index(u64),
|
||||
Anchor(u64),
|
||||
None,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RegistryFilter {
|
||||
pub property: Property,
|
||||
pub op: RegistryFilterOp,
|
||||
pub value: RegistryFilterValue,
|
||||
pub is_pk: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Copy, Eq, Hash)]
|
||||
pub struct ObjectIdVersioned {
|
||||
pub object_id: ObjectId,
|
||||
pub version: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum RegistryFilterOp {
|
||||
Equal,
|
||||
GreaterThan,
|
||||
GreaterEqualThan,
|
||||
LowerThan,
|
||||
LowerEqualThan,
|
||||
TextMatch,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum RegistryFilterValue {
|
||||
String(String),
|
||||
Bytes(Vec<u8>),
|
||||
U64(u64),
|
||||
U16(u16),
|
||||
Boolean(bool),
|
||||
}
|
||||
|
||||
impl Deserialize for Object {
|
||||
fn deserialize_with_key(key: &[u8], bytes: &[u8]) -> trc::Result<Self> {
|
||||
let revision = xxhash_rust::xxh3::xxh3_64(bytes);
|
||||
ObjectType::from_id(key.deserialize_be_u16(0)?)
|
||||
.and_then(|object_id| ObjectInner::unpickle(object_id, &mut PickledStream::new(bytes)?))
|
||||
.map(|inner| Object { revision, inner })
|
||||
.ok_or_else(|| {
|
||||
trc::EventType::Registry(trc::RegistryEvent::DeserializationError)
|
||||
.into_err()
|
||||
.caused_by(trc::location!())
|
||||
.ctx(trc::Key::Value, bytes)
|
||||
})
|
||||
}
|
||||
|
||||
fn deserialize(_: &[u8]) -> trc::Result<Self> {
|
||||
unreachable!("Object deserialization requires the object type from the key")
|
||||
}
|
||||
}
|
||||
|
||||
impl Deserialize for Task {
|
||||
fn deserialize(bytes: &[u8]) -> trc::Result<Self> {
|
||||
PickledStream::new(bytes)
|
||||
.and_then(|mut stream| Self::unpickle(&mut stream))
|
||||
.ok_or_else(|| {
|
||||
trc::EventType::Registry(trc::RegistryEvent::DeserializationError)
|
||||
.into_err()
|
||||
.caused_by(trc::location!())
|
||||
.ctx(trc::Key::Value, bytes)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Deserialize for SpamTrainingSample {
|
||||
fn deserialize(bytes: &[u8]) -> trc::Result<Self> {
|
||||
PickledStream::new(bytes)
|
||||
.and_then(|mut stream| Self::unpickle(&mut stream))
|
||||
.ok_or_else(|| {
|
||||
trc::EventType::Registry(trc::RegistryEvent::DeserializationError)
|
||||
.into_err()
|
||||
.caused_by(trc::location!())
|
||||
.ctx(trc::Key::Value, bytes)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Deserialize for ArchivedItem {
|
||||
fn deserialize(bytes: &[u8]) -> trc::Result<Self> {
|
||||
PickledStream::new(bytes)
|
||||
.and_then(|mut stream| Self::unpickle(&mut stream))
|
||||
.ok_or_else(|| {
|
||||
trc::EventType::Registry(trc::RegistryEvent::DeserializationError)
|
||||
.into_err()
|
||||
.caused_by(trc::location!())
|
||||
.ctx(trc::Key::Value, bytes)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Deserialize for TlsInternalReport {
|
||||
fn deserialize(bytes: &[u8]) -> trc::Result<Self> {
|
||||
PickledStream::new(bytes)
|
||||
.and_then(|mut stream| Self::unpickle(&mut stream))
|
||||
.ok_or_else(|| {
|
||||
trc::EventType::Registry(trc::RegistryEvent::DeserializationError)
|
||||
.into_err()
|
||||
.caused_by(trc::location!())
|
||||
.ctx(trc::Key::Value, bytes)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Deserialize for DmarcInternalReport {
|
||||
fn deserialize(bytes: &[u8]) -> trc::Result<Self> {
|
||||
PickledStream::new(bytes)
|
||||
.and_then(|mut stream| Self::unpickle(&mut stream))
|
||||
.ok_or_else(|| {
|
||||
trc::EventType::Registry(trc::RegistryEvent::DeserializationError)
|
||||
.into_err()
|
||||
.caused_by(trc::location!())
|
||||
.ctx(trc::Key::Value, bytes)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl SerializeInfallible for ObjectId {
|
||||
fn serialize(&self) -> Vec<u8> {
|
||||
KeySerializer::new(U16_LEN + U64_LEN)
|
||||
.write(self.object().to_id())
|
||||
.write(self.id().id())
|
||||
.finalize()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deserialize for ObjectId {
|
||||
fn deserialize(bytes: &[u8]) -> trc::Result<Self> {
|
||||
let object_id = bytes.deserialize_be_u16(0)?;
|
||||
let item_id = bytes.deserialize_be_u64(U16_LEN)?;
|
||||
Ok(ObjectId::new(
|
||||
ObjectType::from_id(object_id).ok_or_else(|| {
|
||||
trc::EventType::Registry(trc::RegistryEvent::DeserializationError)
|
||||
.into_err()
|
||||
.caused_by(trc::location!())
|
||||
.ctx(trc::Key::Value, bytes)
|
||||
})?,
|
||||
Id::new(item_id),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl SerializeInfallible for ObjectIdVersioned {
|
||||
fn serialize(&self) -> Vec<u8> {
|
||||
KeySerializer::new(U16_LEN + U64_LEN + U32_LEN)
|
||||
.write(self.object_id.object().to_id())
|
||||
.write(self.object_id.id().id())
|
||||
.write(self.version)
|
||||
.finalize()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deserialize for ObjectIdVersioned {
|
||||
fn deserialize(bytes: &[u8]) -> trc::Result<Self> {
|
||||
let object_id = ObjectId::deserialize(bytes)?;
|
||||
let version = bytes.deserialize_be_u32(U16_LEN + U64_LEN)?;
|
||||
Ok(Self { object_id, version })
|
||||
}
|
||||
}
|
||||
|
||||
impl Deserialize for Trace {
|
||||
fn deserialize(bytes: &[u8]) -> trc::Result<Self> {
|
||||
PickledStream::new(bytes)
|
||||
.and_then(|mut stream| Self::unpickle(&mut stream))
|
||||
.ok_or_else(|| {
|
||||
trc::EventType::Registry(trc::RegistryEvent::DeserializationError)
|
||||
.into_err()
|
||||
.caused_by(trc::location!())
|
||||
.ctx(trc::Key::Value, bytes)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Deserialize for Metric {
|
||||
fn deserialize(bytes: &[u8]) -> trc::Result<Self> {
|
||||
PickledStream::new(bytes)
|
||||
.and_then(|mut stream| Self::unpickle(&mut stream))
|
||||
.ok_or_else(|| {
|
||||
trc::EventType::Registry(trc::RegistryEvent::DeserializationError)
|
||||
.into_err()
|
||||
.caused_by(trc::location!())
|
||||
.ctx(trc::Key::Value, bytes)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,943 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::{
|
||||
IterateParams, RegistryStore, SUBSPACE_REGISTRY_IDX, SUBSPACE_REGISTRY_PK, Store, U16_LEN,
|
||||
U64_LEN, ValueKey,
|
||||
registry::{
|
||||
RegistryFilter, RegistryFilterOp, RegistryFilterValue, RegistryObjectCounter,
|
||||
RegistryQuery, RegistryQueryStart,
|
||||
},
|
||||
write::{
|
||||
AnyClass, RegistryClass, ValueClass,
|
||||
key::{DeserializeBigEndian, KeySerializer},
|
||||
},
|
||||
};
|
||||
use ahash::AHashSet;
|
||||
use registry::{
|
||||
schema::prelude::{OBJ_FILTER_ACCOUNT, OBJ_FILTER_TENANT, OBJ_SINGLETON, ObjectType, Property},
|
||||
types::{EnumImpl, id::ObjectId},
|
||||
};
|
||||
use roaring::RoaringBitmap;
|
||||
use std::{borrow::Cow, ops::BitAndAssign};
|
||||
use trc::AddContext;
|
||||
use types::id::Id;
|
||||
|
||||
impl RegistryStore {
|
||||
pub async fn query<T: RegistryQueryResults>(&self, query: RegistryQuery) -> trc::Result<T> {
|
||||
if query.filters.is_empty() {
|
||||
return all_ids::<T>(&self.0.store, query).await;
|
||||
}
|
||||
|
||||
let mut u64_buffer;
|
||||
let mut u16_buffer;
|
||||
let mut bool_buffer = [0u8; 1];
|
||||
|
||||
let mut results = ResultsPagination::<T>::new(&query);
|
||||
for filter in &query.filters {
|
||||
if filter.op == RegistryFilterOp::TextMatch {
|
||||
if let RegistryFilterValue::String(text) = &filter.value {
|
||||
let mut matches = ResultsPagination::<T>::new(&query);
|
||||
|
||||
for word in text
|
||||
.split(|c: char| !c.is_alphanumeric())
|
||||
.filter(|s| s.len() > 1)
|
||||
{
|
||||
let word = if word
|
||||
.chars()
|
||||
.all(|ch| ch.is_lowercase() || !ch.is_alphabetic())
|
||||
{
|
||||
Cow::Borrowed(word)
|
||||
} else {
|
||||
Cow::Owned(word.to_lowercase())
|
||||
};
|
||||
|
||||
let mut result = ResultsPagination::<T>::new(&query);
|
||||
|
||||
index_range(
|
||||
&self.0.store,
|
||||
query.object_type,
|
||||
filter.property.to_id(),
|
||||
word.as_bytes(),
|
||||
RegistryFilterOp::Equal,
|
||||
&mut result,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if !matches.list.has_items() {
|
||||
matches = result;
|
||||
} else {
|
||||
matches.list.intersect(&result.list);
|
||||
if !matches.list.has_items() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !results.list.has_items() {
|
||||
results = matches;
|
||||
} else {
|
||||
results.list.intersect(&matches.list);
|
||||
}
|
||||
} else {
|
||||
return Err(trc::EventType::Registry(trc::RegistryEvent::NotSupported)
|
||||
.into_err()
|
||||
.details("TextMatch operator only supports string values"));
|
||||
}
|
||||
} else {
|
||||
let value = match &filter.value {
|
||||
RegistryFilterValue::String(v) => v.as_bytes(),
|
||||
RegistryFilterValue::Bytes(v) => v.as_slice(),
|
||||
RegistryFilterValue::U64(v) => {
|
||||
u64_buffer = v.to_be_bytes();
|
||||
&u64_buffer
|
||||
}
|
||||
RegistryFilterValue::U16(v) => {
|
||||
u16_buffer = v.to_be_bytes();
|
||||
&u16_buffer
|
||||
}
|
||||
RegistryFilterValue::Boolean(v) => {
|
||||
bool_buffer[0] = *v as u8;
|
||||
&bool_buffer
|
||||
}
|
||||
};
|
||||
|
||||
let mut result = ResultsPagination::<T>::new(&query);
|
||||
if !filter.is_pk {
|
||||
index_range(
|
||||
&self.0.store,
|
||||
query.object_type,
|
||||
filter.property.to_id(),
|
||||
value,
|
||||
filter.op,
|
||||
&mut result,
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
pk_range(
|
||||
&self.0.store,
|
||||
query.object_type,
|
||||
filter.property.to_id(),
|
||||
value,
|
||||
filter.op,
|
||||
&mut result,
|
||||
)
|
||||
.await?
|
||||
};
|
||||
|
||||
if !results.list.has_items() {
|
||||
results = result;
|
||||
} else {
|
||||
results.list.intersect(&result.list);
|
||||
}
|
||||
}
|
||||
|
||||
if !results.list.has_items() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(results.finalize())
|
||||
}
|
||||
|
||||
pub async fn count_object(&self, object_type: ObjectType) -> trc::Result<usize> {
|
||||
if object_type.flags() & OBJ_SINGLETON == 0 {
|
||||
self.query::<RegistryObjectCounter>(RegistryQuery::new(object_type))
|
||||
.await
|
||||
.map(|r| r.0)
|
||||
} else {
|
||||
self.store()
|
||||
.key_exists(ValueKey::from(RegistryClass::Item {
|
||||
object_id: object_type.to_id(),
|
||||
item_id: Id::singleton().id(),
|
||||
}))
|
||||
.await
|
||||
.caused_by(trc::location!())
|
||||
.map(|exists| if exists { 1 } else { 0 })
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn primary_key(
|
||||
&self,
|
||||
object_type: Option<ObjectType>,
|
||||
property: Property,
|
||||
key: Vec<u8>,
|
||||
) -> trc::Result<Option<ObjectId>> {
|
||||
self.store()
|
||||
.get_value::<ObjectId>(ValueKey::from(ValueClass::Registry(
|
||||
RegistryClass::PrimaryKey {
|
||||
object_id: object_type.map(|obj| obj.to_id()),
|
||||
index_id: property.to_id(),
|
||||
key,
|
||||
},
|
||||
)))
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn sort_by_index(
|
||||
&self,
|
||||
object: ObjectType,
|
||||
property: Property,
|
||||
ids: Option<Vec<Id>>,
|
||||
ascending: bool,
|
||||
) -> trc::Result<Vec<Id>> {
|
||||
let mut ids = ids.map(|ids| ids.into_iter().collect::<AHashSet<_>>());
|
||||
let mut ids_sorted = Vec::with_capacity(ids.as_ref().map_or(0, |ids| ids.len()));
|
||||
|
||||
let object_id = object.to_id();
|
||||
let index_id = property.to_id();
|
||||
let begin = ValueKey::from(ValueClass::Any(AnyClass {
|
||||
subspace: SUBSPACE_REGISTRY_IDX,
|
||||
key: KeySerializer::new(U16_LEN * 2)
|
||||
.write(object_id)
|
||||
.write(index_id)
|
||||
.finalize(),
|
||||
}));
|
||||
let end = ValueKey::from(ValueClass::Any(AnyClass {
|
||||
subspace: SUBSPACE_REGISTRY_IDX,
|
||||
key: KeySerializer::new((U16_LEN * 2) + U64_LEN)
|
||||
.write(object_id)
|
||||
.write(index_id)
|
||||
.write(u64::MAX)
|
||||
.finalize(),
|
||||
}));
|
||||
|
||||
self.0
|
||||
.store
|
||||
.iterate(
|
||||
IterateParams::new(begin, end)
|
||||
.no_values()
|
||||
.set_ascending(ascending),
|
||||
|key, _| {
|
||||
let id = Id::from(key.deserialize_be_u64(key.len() - U64_LEN)?);
|
||||
if let Some(ids) = ids.as_mut() {
|
||||
if ids.remove(&id) {
|
||||
ids_sorted.push(id);
|
||||
}
|
||||
Ok(!ids.is_empty())
|
||||
} else {
|
||||
ids_sorted.push(id);
|
||||
Ok(true)
|
||||
}
|
||||
},
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())
|
||||
.map(|_| {
|
||||
if let Some(mut ids) = ids
|
||||
&& !ids.is_empty()
|
||||
{
|
||||
ids_sorted.extend(ids.drain());
|
||||
}
|
||||
|
||||
ids_sorted
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn sort_by_pk(
|
||||
&self,
|
||||
object: ObjectType,
|
||||
property: Property,
|
||||
ids: Option<Vec<Id>>,
|
||||
ascending: bool,
|
||||
) -> trc::Result<Vec<Id>> {
|
||||
let mut ids = ids.map(|ids| ids.into_iter().collect::<AHashSet<_>>());
|
||||
let mut ids_sorted = Vec::with_capacity(ids.as_ref().map_or(0, |ids| ids.len()));
|
||||
|
||||
let object_id = object.to_id();
|
||||
let index_id = property.to_id();
|
||||
let begin = ValueKey::from(ValueClass::Any(AnyClass {
|
||||
subspace: SUBSPACE_REGISTRY_PK,
|
||||
key: KeySerializer::new(U16_LEN * 2)
|
||||
.write(object_id)
|
||||
.write(index_id)
|
||||
.finalize(),
|
||||
}));
|
||||
let end = ValueKey::from(ValueClass::Any(AnyClass {
|
||||
subspace: SUBSPACE_REGISTRY_PK,
|
||||
key: KeySerializer::new((U16_LEN * 2) + U64_LEN)
|
||||
.write(object_id)
|
||||
.write(index_id)
|
||||
.write(u64::MAX)
|
||||
.finalize(),
|
||||
}));
|
||||
|
||||
self.0
|
||||
.store
|
||||
.iterate(
|
||||
IterateParams::new(begin, end).set_ascending(ascending),
|
||||
|_, value| {
|
||||
let id = Id::from(value.deserialize_be_u64(U16_LEN)?);
|
||||
|
||||
if let Some(ids) = ids.as_mut() {
|
||||
if ids.remove(&id) {
|
||||
ids_sorted.push(id);
|
||||
}
|
||||
Ok(!ids.is_empty())
|
||||
} else {
|
||||
ids_sorted.push(id);
|
||||
Ok(true)
|
||||
}
|
||||
},
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())
|
||||
.map(|_| {
|
||||
if let Some(mut ids) = ids
|
||||
&& !ids.is_empty()
|
||||
{
|
||||
ids_sorted.extend(ids.drain());
|
||||
}
|
||||
|
||||
ids_sorted
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn all_ids<T: RegistryQueryResults>(store: &Store, query: RegistryQuery) -> trc::Result<T> {
|
||||
let mut bm = T::default();
|
||||
let object_id = query.object_type.to_id();
|
||||
|
||||
let (item_id, mut offset) = match query.start {
|
||||
RegistryQueryStart::Index(index) => (0, index),
|
||||
RegistryQueryStart::Anchor(anchor) => (anchor + 1, 0),
|
||||
RegistryQueryStart::None => (0, 0),
|
||||
};
|
||||
|
||||
store
|
||||
.iterate(
|
||||
IterateParams::new(
|
||||
ValueKey::from(ValueClass::Registry(RegistryClass::IndexId {
|
||||
object_id,
|
||||
item_id,
|
||||
})),
|
||||
ValueKey::from(ValueClass::Registry(RegistryClass::IndexId {
|
||||
object_id,
|
||||
item_id: u64::MAX,
|
||||
})),
|
||||
)
|
||||
.no_values()
|
||||
.ascending(),
|
||||
|key, _| {
|
||||
if offset == 0 {
|
||||
bm.push(key.deserialize_be_u64(U16_LEN * 2)?);
|
||||
Ok(query.limit.is_none_or(|limit| bm.count() < limit))
|
||||
} else {
|
||||
offset -= 1;
|
||||
Ok(true)
|
||||
}
|
||||
},
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())
|
||||
.map(|_| bm)
|
||||
}
|
||||
|
||||
async fn index_range<T: RegistryQueryResults>(
|
||||
store: &Store,
|
||||
object: ObjectType,
|
||||
index_id: u16,
|
||||
match_value: &[u8],
|
||||
op: RegistryFilterOp,
|
||||
results: &mut ResultsPagination<T>,
|
||||
) -> trc::Result<()> {
|
||||
let ((from_value, from_doc_id, from_index_id), (end_value, end_doc_id, end_index_id)) = match op
|
||||
{
|
||||
RegistryFilterOp::LowerThan => ((&[][..], 0, index_id), (match_value, 0, index_id)),
|
||||
RegistryFilterOp::LowerEqualThan => {
|
||||
((&[][..], 0, index_id), (match_value, u64::MAX, index_id))
|
||||
}
|
||||
RegistryFilterOp::GreaterThan => (
|
||||
(match_value, u64::MAX, index_id),
|
||||
(&[][..], u64::MAX, index_id + 1),
|
||||
),
|
||||
RegistryFilterOp::GreaterEqualThan => (
|
||||
(match_value, 0, index_id),
|
||||
(&[][..], u64::MAX, index_id + 1),
|
||||
),
|
||||
RegistryFilterOp::Equal | RegistryFilterOp::TextMatch => (
|
||||
(match_value, 0, index_id),
|
||||
(match_value, u64::MAX, index_id),
|
||||
),
|
||||
};
|
||||
|
||||
let object_id = object.to_id();
|
||||
let begin = ValueKey::from(ValueClass::Any(AnyClass {
|
||||
subspace: SUBSPACE_REGISTRY_IDX,
|
||||
key: KeySerializer::new((U16_LEN * 2) + U64_LEN + from_value.len())
|
||||
.write(object_id)
|
||||
.write(from_index_id)
|
||||
.write(from_value)
|
||||
.write(from_doc_id)
|
||||
.finalize(),
|
||||
}));
|
||||
let end = ValueKey::from(ValueClass::Any(AnyClass {
|
||||
subspace: SUBSPACE_REGISTRY_IDX,
|
||||
key: KeySerializer::new((U16_LEN * 2) + U64_LEN + end_value.len())
|
||||
.write(object_id)
|
||||
.write(end_index_id)
|
||||
.write(end_value)
|
||||
.write(end_doc_id)
|
||||
.finalize(),
|
||||
}));
|
||||
|
||||
let prefix = KeySerializer::new(U16_LEN * 2)
|
||||
.write(object_id)
|
||||
.write(index_id)
|
||||
.finalize();
|
||||
|
||||
store
|
||||
.iterate(
|
||||
IterateParams::new(begin, end).no_values().ascending(),
|
||||
|key, _| {
|
||||
if !key.starts_with(&prefix) {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let id_pos = key.len() - U64_LEN;
|
||||
let value = key
|
||||
.get(U16_LEN * 2..id_pos)
|
||||
.ok_or_else(|| trc::Error::corrupted_key(key, None, trc::location!()))?;
|
||||
|
||||
let matches = match op {
|
||||
RegistryFilterOp::LowerThan => value < match_value,
|
||||
RegistryFilterOp::LowerEqualThan => value <= match_value,
|
||||
RegistryFilterOp::GreaterThan => value > match_value,
|
||||
RegistryFilterOp::GreaterEqualThan => value >= match_value,
|
||||
RegistryFilterOp::Equal | RegistryFilterOp::TextMatch => value == match_value,
|
||||
};
|
||||
|
||||
if matches {
|
||||
Ok(results.push(key.deserialize_be_u64(id_pos)?))
|
||||
} else {
|
||||
Ok(true)
|
||||
}
|
||||
},
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())
|
||||
.inspect(|_| results.list.sort())
|
||||
}
|
||||
|
||||
async fn pk_range<T: RegistryQueryResults>(
|
||||
store: &Store,
|
||||
object: ObjectType,
|
||||
index_id: u16,
|
||||
match_value: &[u8],
|
||||
op: RegistryFilterOp,
|
||||
results: &mut ResultsPagination<T>,
|
||||
) -> trc::Result<()> {
|
||||
let ((from_value, from_index_id), (end_value, end_index_id)) = match op {
|
||||
RegistryFilterOp::LowerThan => ((&[][..], index_id), (match_value, index_id)),
|
||||
RegistryFilterOp::LowerEqualThan => ((&[][..], index_id), (match_value, index_id)),
|
||||
RegistryFilterOp::GreaterThan => ((match_value, index_id), (&[][..], index_id + 1)),
|
||||
RegistryFilterOp::GreaterEqualThan => ((match_value, index_id), (&[][..], index_id + 1)),
|
||||
RegistryFilterOp::Equal | RegistryFilterOp::TextMatch => {
|
||||
((match_value, index_id), (match_value, index_id))
|
||||
}
|
||||
};
|
||||
|
||||
let object_id = object.to_id();
|
||||
let begin = ValueKey::from(ValueClass::Any(AnyClass {
|
||||
subspace: SUBSPACE_REGISTRY_PK,
|
||||
key: KeySerializer::new((U16_LEN * 2) + from_value.len())
|
||||
.write(object_id)
|
||||
.write(from_index_id)
|
||||
.write(from_value)
|
||||
.finalize(),
|
||||
}));
|
||||
let end = ValueKey::from(ValueClass::Any(AnyClass {
|
||||
subspace: SUBSPACE_REGISTRY_PK,
|
||||
key: KeySerializer::new((U16_LEN * 2) + end_value.len())
|
||||
.write(object_id)
|
||||
.write(end_index_id)
|
||||
.write(end_value)
|
||||
.finalize(),
|
||||
}));
|
||||
|
||||
let prefix = KeySerializer::new(U16_LEN * 2)
|
||||
.write(object_id)
|
||||
.write(index_id)
|
||||
.finalize();
|
||||
|
||||
store
|
||||
.iterate(IterateParams::new(begin, end).ascending(), |key, value| {
|
||||
if !key.starts_with(&prefix) {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let key = key
|
||||
.get(U16_LEN * 2..)
|
||||
.ok_or_else(|| trc::Error::corrupted_key(key, None, trc::location!()))?;
|
||||
|
||||
let matches = match op {
|
||||
RegistryFilterOp::LowerThan => key < match_value,
|
||||
RegistryFilterOp::LowerEqualThan => key <= match_value,
|
||||
RegistryFilterOp::GreaterThan => key > match_value,
|
||||
RegistryFilterOp::GreaterEqualThan => key >= match_value,
|
||||
RegistryFilterOp::Equal | RegistryFilterOp::TextMatch => key == match_value,
|
||||
};
|
||||
|
||||
if matches {
|
||||
Ok(results.push(value.deserialize_be_u64(U16_LEN)?))
|
||||
} else {
|
||||
Ok(true)
|
||||
}
|
||||
})
|
||||
.await
|
||||
.caused_by(trc::location!())
|
||||
.inspect(|_| results.list.sort())
|
||||
}
|
||||
|
||||
pub trait RegistryQueryResults: Default + Sized + Sync + Send {
|
||||
fn push(&mut self, id: u64);
|
||||
fn has_items(&self) -> bool;
|
||||
fn intersect(&mut self, other: &Self);
|
||||
fn count(&self) -> usize;
|
||||
fn sort(&mut self);
|
||||
fn into_list(self) -> impl Iterator<Item = u64>;
|
||||
}
|
||||
|
||||
impl RegistryQueryResults for Vec<Id> {
|
||||
fn push(&mut self, id: u64) {
|
||||
self.push(Id::new(id));
|
||||
}
|
||||
|
||||
fn has_items(&self) -> bool {
|
||||
!self.is_empty()
|
||||
}
|
||||
|
||||
fn intersect(&mut self, other: &Self) {
|
||||
let a = self;
|
||||
let b = other;
|
||||
let mut i = 0;
|
||||
let mut j = 0;
|
||||
let mut write = 0;
|
||||
|
||||
while i < a.len() && j < b.len() {
|
||||
if a[i] < b[j] {
|
||||
let target = b[j];
|
||||
let remain = &a[i..];
|
||||
i += remain.partition_point(|&x| x < target);
|
||||
} else if a[i] > b[j] {
|
||||
let target = a[i];
|
||||
let remain = &b[j..];
|
||||
j += remain.partition_point(|&x| x < target);
|
||||
} else {
|
||||
a[write] = a[i];
|
||||
write += 1;
|
||||
i += 1;
|
||||
j += 1;
|
||||
}
|
||||
}
|
||||
a.truncate(write);
|
||||
}
|
||||
|
||||
fn count(&self) -> usize {
|
||||
self.len()
|
||||
}
|
||||
|
||||
fn sort(&mut self) {
|
||||
match self.len() {
|
||||
0 | 1 => {}
|
||||
..3000 => self.sort_unstable(),
|
||||
_ => radsort::sort_by_key(self, |id| id.id()),
|
||||
}
|
||||
}
|
||||
|
||||
fn into_list(self) -> impl Iterator<Item = u64> {
|
||||
self.into_iter().map(|id| id.id())
|
||||
}
|
||||
}
|
||||
|
||||
impl RegistryQueryResults for RoaringBitmap {
|
||||
fn push(&mut self, id: u64) {
|
||||
self.insert(id as u32);
|
||||
}
|
||||
|
||||
fn has_items(&self) -> bool {
|
||||
!self.is_empty()
|
||||
}
|
||||
|
||||
fn intersect(&mut self, other: &Self) {
|
||||
self.bitand_assign(other);
|
||||
}
|
||||
|
||||
fn count(&self) -> usize {
|
||||
self.len() as usize
|
||||
}
|
||||
|
||||
fn sort(&mut self) {}
|
||||
|
||||
fn into_list(self) -> impl Iterator<Item = u64> {
|
||||
self.into_iter().map(|id| id as u64)
|
||||
}
|
||||
}
|
||||
|
||||
impl RegistryQueryResults for RegistryObjectCounter {
|
||||
fn push(&mut self, _: u64) {
|
||||
self.0 += 1;
|
||||
}
|
||||
|
||||
fn has_items(&self) -> bool {
|
||||
self.0 > 0
|
||||
}
|
||||
|
||||
fn intersect(&mut self, _: &Self) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn count(&self) -> usize {
|
||||
self.0
|
||||
}
|
||||
|
||||
fn sort(&mut self) {}
|
||||
|
||||
fn into_list(self) -> impl Iterator<Item = u64> {
|
||||
Vec::new().into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
struct ResultsPagination<T: RegistryQueryResults> {
|
||||
list: T,
|
||||
offset: usize,
|
||||
anchor: Option<u64>,
|
||||
limit: Option<usize>,
|
||||
deferred_pagination: bool,
|
||||
}
|
||||
|
||||
impl<T: RegistryQueryResults> ResultsPagination<T> {
|
||||
fn new(query: &RegistryQuery) -> Self {
|
||||
let (anchor, offset) = match query.start {
|
||||
RegistryQueryStart::Index(index) => (None, index),
|
||||
RegistryQueryStart::Anchor(anchor) => (Some(anchor), 0),
|
||||
RegistryQueryStart::None => (None, 0),
|
||||
};
|
||||
|
||||
Self {
|
||||
list: T::default(),
|
||||
offset: offset as usize,
|
||||
anchor,
|
||||
limit: query.limit,
|
||||
deferred_pagination: query.filters.len() > 1
|
||||
|| query.filters.first().is_some_and(|f| {
|
||||
if let (RegistryFilterOp::TextMatch, RegistryFilterValue::String(value)) =
|
||||
(&f.op, &f.value)
|
||||
{
|
||||
value.chars().any(|c| !c.is_alphanumeric()) && value.len() > 1
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn push(&mut self, id: u64) -> bool {
|
||||
if !self.deferred_pagination {
|
||||
if self.offset > 0 {
|
||||
self.offset -= 1;
|
||||
true
|
||||
} else if let Some(anchor) = self.anchor {
|
||||
if id == anchor {
|
||||
self.anchor = None;
|
||||
}
|
||||
true
|
||||
} else {
|
||||
self.list.push(id);
|
||||
self.limit.is_none_or(|limit| self.list.count() < limit)
|
||||
}
|
||||
} else {
|
||||
self.list.push(id);
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
fn finalize(mut self) -> T {
|
||||
if self.deferred_pagination
|
||||
&& self.list.has_items()
|
||||
&& (self.limit.is_some() || self.anchor.is_some() || self.offset > 0)
|
||||
{
|
||||
let list = std::mem::take(&mut self.list);
|
||||
self.deferred_pagination = false;
|
||||
|
||||
for item in list.into_list() {
|
||||
if !self.push(item) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.list
|
||||
}
|
||||
}
|
||||
|
||||
impl RegistryQuery {
|
||||
pub fn new(object_type: ObjectType) -> Self {
|
||||
Self {
|
||||
object_type,
|
||||
filters: Vec::new(),
|
||||
start: RegistryQueryStart::None,
|
||||
limit: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_anchor(mut self, anchor: u64) -> Self {
|
||||
self.start = RegistryQueryStart::Anchor(anchor);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_index_start(mut self, index: u64) -> Self {
|
||||
self.start = RegistryQueryStart::Index(index);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_limit(mut self, limit: usize) -> Self {
|
||||
self.limit = Some(limit);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_account(mut self, account_id: u32) -> Self {
|
||||
if self.object_type.flags() & OBJ_FILTER_ACCOUNT != 0 {
|
||||
let filter = RegistryFilter::equal(Property::AccountId, account_id, false);
|
||||
if self.filters.is_empty() {
|
||||
self.filters.push(filter);
|
||||
} else {
|
||||
self.filters.insert(0, filter);
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_account_opt(self, account_id: Option<u32>) -> Self {
|
||||
if let Some(account_id) = account_id {
|
||||
self.with_account(account_id)
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_tenant(mut self, tenant_id: Option<u32>) -> Self {
|
||||
if let Some(tenant_id) = tenant_id
|
||||
&& self.object_type.flags() & OBJ_FILTER_TENANT != 0
|
||||
{
|
||||
let filter = RegistryFilter::equal(Property::MemberTenantId, tenant_id, false);
|
||||
if self.filters.is_empty() {
|
||||
self.filters.push(filter);
|
||||
} else {
|
||||
self.filters.insert(0, filter);
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn filter(mut self, filter: RegistryFilter) -> Self {
|
||||
self.filters.push(filter);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn equal(mut self, property: Property, value: impl Into<RegistryFilterValue>) -> Self {
|
||||
self.filters
|
||||
.push(RegistryFilter::equal(property, value, false));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn equal_pk(
|
||||
mut self,
|
||||
property: Property,
|
||||
value: impl Into<RegistryFilterValue>,
|
||||
is_pk: bool,
|
||||
) -> Self {
|
||||
self.filters
|
||||
.push(RegistryFilter::equal(property, value, is_pk));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn push_equal_pk(
|
||||
&mut self,
|
||||
property: Property,
|
||||
value: impl Into<RegistryFilterValue>,
|
||||
is_pk: bool,
|
||||
) {
|
||||
self.filters
|
||||
.push(RegistryFilter::equal(property, value, is_pk));
|
||||
}
|
||||
|
||||
pub fn equal_opt(
|
||||
mut self,
|
||||
property: Property,
|
||||
value: Option<impl Into<RegistryFilterValue>>,
|
||||
) -> Self {
|
||||
if let Some(value) = value {
|
||||
self.filters
|
||||
.push(RegistryFilter::equal(property, value, false));
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn greater_than(
|
||||
mut self,
|
||||
property: Property,
|
||||
value: impl Into<RegistryFilterValue>,
|
||||
) -> Self {
|
||||
self.filters
|
||||
.push(RegistryFilter::greater_than(property, value, false));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn less_than(mut self, property: Property, value: impl Into<RegistryFilterValue>) -> Self {
|
||||
self.filters
|
||||
.push(RegistryFilter::less_than(property, value, false));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn greater_than_or_equal(
|
||||
mut self,
|
||||
property: Property,
|
||||
value: impl Into<RegistryFilterValue>,
|
||||
) -> Self {
|
||||
self.filters.push(RegistryFilter::greater_than_or_equal(
|
||||
property, value, false,
|
||||
));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn less_than_or_equal(
|
||||
mut self,
|
||||
property: Property,
|
||||
value: impl Into<RegistryFilterValue>,
|
||||
) -> Self {
|
||||
self.filters
|
||||
.push(RegistryFilter::less_than_or_equal(property, value, false));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn text(mut self, property: Property, value: impl Into<String>) -> Self {
|
||||
self.filters.push(RegistryFilter::text(property, value));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn text_opt(mut self, property: Property, value: Option<impl Into<String>>) -> Self {
|
||||
if let Some(value) = value {
|
||||
self.filters.push(RegistryFilter::text(property, value));
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn push_text(&mut self, property: Property, value: impl Into<String>) {
|
||||
self.filters.push(RegistryFilter::text(property, value));
|
||||
}
|
||||
|
||||
pub fn has_filters(&self) -> bool {
|
||||
!self.filters.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl RegistryFilter {
|
||||
pub fn text(property: Property, value: impl Into<String>) -> Self {
|
||||
Self {
|
||||
property,
|
||||
op: RegistryFilterOp::TextMatch,
|
||||
value: RegistryFilterValue::String(value.into()),
|
||||
is_pk: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn equal(property: Property, value: impl Into<RegistryFilterValue>, is_pk: bool) -> Self {
|
||||
Self {
|
||||
property,
|
||||
op: RegistryFilterOp::Equal,
|
||||
value: value.into(),
|
||||
is_pk,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn greater_than(
|
||||
property: Property,
|
||||
value: impl Into<RegistryFilterValue>,
|
||||
is_pk: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
property,
|
||||
op: RegistryFilterOp::GreaterThan,
|
||||
value: value.into(),
|
||||
is_pk,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn less_than(
|
||||
property: Property,
|
||||
value: impl Into<RegistryFilterValue>,
|
||||
is_pk: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
property,
|
||||
op: RegistryFilterOp::LowerThan,
|
||||
value: value.into(),
|
||||
is_pk,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn greater_than_or_equal(
|
||||
property: Property,
|
||||
value: impl Into<RegistryFilterValue>,
|
||||
is_pk: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
property,
|
||||
op: RegistryFilterOp::GreaterEqualThan,
|
||||
value: value.into(),
|
||||
is_pk,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn less_than_or_equal(
|
||||
property: Property,
|
||||
value: impl Into<RegistryFilterValue>,
|
||||
is_pk: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
property,
|
||||
op: RegistryFilterOp::LowerEqualThan,
|
||||
value: value.into(),
|
||||
is_pk,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for RegistryFilterValue {
|
||||
fn from(value: String) -> Self {
|
||||
RegistryFilterValue::String(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for RegistryFilterValue {
|
||||
fn from(value: &str) -> Self {
|
||||
RegistryFilterValue::String(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for RegistryFilterValue {
|
||||
fn from(value: u64) -> Self {
|
||||
RegistryFilterValue::U64(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for RegistryFilterValue {
|
||||
fn from(value: u32) -> Self {
|
||||
RegistryFilterValue::U64(value as u64)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u16> for RegistryFilterValue {
|
||||
fn from(value: u16) -> Self {
|
||||
RegistryFilterValue::U16(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for RegistryFilterValue {
|
||||
fn from(value: bool) -> Self {
|
||||
RegistryFilterValue::Boolean(value)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,632 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::{
|
||||
IterateParams, RegistryStore, SerializeInfallible, U16_LEN, U64_LEN, ValueKey,
|
||||
write::{
|
||||
BatchBuilder, RegistryClass, ValueClass,
|
||||
assert::AssertValue,
|
||||
key::{DeserializeBigEndian, KeySerializer},
|
||||
},
|
||||
};
|
||||
use registry::{
|
||||
schema::prelude::{
|
||||
OBJ_FILTER_ACCOUNT, OBJ_FILTER_TENANT, OBJ_SEQ_ID, OBJ_SINGLETON, Object, ObjectInner,
|
||||
ObjectType, Property,
|
||||
},
|
||||
types::{
|
||||
EnumImpl,
|
||||
error::ValidationError,
|
||||
id::ObjectId,
|
||||
index::{IndexBuilder, IndexKey, IndexValue},
|
||||
},
|
||||
};
|
||||
use std::{borrow::Cow, fmt::Display};
|
||||
use trc::AddContext;
|
||||
use types::id::Id;
|
||||
|
||||
const MAX_OBJECT_PAYLOAD_SIZE: usize = 200_000;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum RegistryWriteResult {
|
||||
Success(Id),
|
||||
CannotDeleteLinked {
|
||||
object_id: ObjectId,
|
||||
linked_objects: Vec<ObjectId>,
|
||||
},
|
||||
InvalidSingletonId,
|
||||
CannotDeleteSingleton,
|
||||
NotFound {
|
||||
object_id: ObjectId,
|
||||
},
|
||||
InvalidForeignKey {
|
||||
object_id: ObjectId,
|
||||
},
|
||||
PrimaryKeyConflict {
|
||||
property: Property,
|
||||
existing_id: ObjectId,
|
||||
},
|
||||
ValidationError {
|
||||
errors: Vec<ValidationError>,
|
||||
},
|
||||
NotSupported,
|
||||
}
|
||||
|
||||
pub enum RegistryWrite<'x> {
|
||||
Insert {
|
||||
object: &'x Object,
|
||||
id: Option<Id>,
|
||||
},
|
||||
Update {
|
||||
object: &'x Object,
|
||||
id: Id,
|
||||
old_object: &'x Object,
|
||||
},
|
||||
Delete {
|
||||
object_id: ObjectId,
|
||||
object: Option<&'x Object>,
|
||||
allowed_orphan_types: &'x [ObjectType],
|
||||
},
|
||||
}
|
||||
|
||||
impl RegistryStore {
|
||||
pub async fn write(&self, write: RegistryWrite<'_>) -> trc::Result<RegistryWriteResult> {
|
||||
let mut set_index = IndexBuilder::default();
|
||||
let mut clear_index = IndexBuilder::default();
|
||||
|
||||
let object;
|
||||
let object_type;
|
||||
let object_flags;
|
||||
let object_id;
|
||||
let mut item_id;
|
||||
|
||||
let mut batch = BatchBuilder::new();
|
||||
let mut write_id = true;
|
||||
let mut generate_id = false;
|
||||
|
||||
match write {
|
||||
RegistryWrite::Insert {
|
||||
object: insert_object,
|
||||
id,
|
||||
} => {
|
||||
object = insert_object;
|
||||
object_flags = object.flags();
|
||||
object_type = object.object_type();
|
||||
object_id = object_type.to_id();
|
||||
object.index(&mut set_index);
|
||||
|
||||
item_id = if let Some(id) = id {
|
||||
id.id()
|
||||
} else if object_flags & OBJ_SINGLETON != 0 {
|
||||
write_id = false;
|
||||
Id::singleton().id()
|
||||
} else if object_flags & OBJ_SEQ_ID != 0 {
|
||||
generate_id = true;
|
||||
u64::MAX
|
||||
} else {
|
||||
self.0.id_generator.generate()
|
||||
};
|
||||
}
|
||||
RegistryWrite::Update {
|
||||
object: update_object,
|
||||
id,
|
||||
old_object,
|
||||
} => {
|
||||
object = update_object;
|
||||
object_flags = object.flags();
|
||||
object_type = object.object_type();
|
||||
object_id = object_type.to_id();
|
||||
object.index(&mut set_index);
|
||||
|
||||
// Obtain changes
|
||||
let mut old_index = IndexBuilder::default();
|
||||
old_object.index(&mut old_index);
|
||||
for key in &old_index.keys {
|
||||
if !set_index.keys.contains(key) {
|
||||
clear_index.keys.insert(key.clone());
|
||||
}
|
||||
}
|
||||
set_index.keys.retain(|key| !old_index.keys.contains(key));
|
||||
|
||||
// Validate singleton
|
||||
if object_flags & OBJ_SINGLETON != 0 && !id.is_singleton() {
|
||||
return Ok(RegistryWriteResult::InvalidSingletonId);
|
||||
}
|
||||
|
||||
// Assert value
|
||||
item_id = id.id();
|
||||
batch.assert_value(
|
||||
ValueClass::Registry(RegistryClass::Item { object_id, item_id }),
|
||||
AssertValue::Hash(old_object.revision),
|
||||
);
|
||||
}
|
||||
RegistryWrite::Delete {
|
||||
object_id,
|
||||
object,
|
||||
allowed_orphan_types,
|
||||
} => {
|
||||
return if object_id.object().flags() & OBJ_SINGLETON == 0 {
|
||||
self.delete(object_id, object, allowed_orphan_types).await
|
||||
} else {
|
||||
Ok(RegistryWriteResult::CannotDeleteSingleton)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Validate object
|
||||
let mut errors = Vec::new();
|
||||
object.validate(&mut errors);
|
||||
if !errors.is_empty() {
|
||||
return Ok(RegistryWriteResult::ValidationError { errors });
|
||||
}
|
||||
|
||||
// Write to local registry
|
||||
if let ObjectInner::DataStore(data_store) = &object.inner {
|
||||
if generate_id {
|
||||
return Ok(RegistryWriteResult::NotSupported);
|
||||
}
|
||||
|
||||
return self
|
||||
.write_data_store(data_store)
|
||||
.await
|
||||
.map(|_| RegistryWriteResult::Success(Id::singleton()));
|
||||
}
|
||||
|
||||
// Validate foreign keys
|
||||
let tenant_id = object.inner.member_tenant_id().map(|id| id.id());
|
||||
let account_id = object
|
||||
.inner
|
||||
.account_id()
|
||||
.map(|id| id.id())
|
||||
.or_else(|| (object_type == ObjectType::Account).then_some(item_id));
|
||||
|
||||
#[cfg(not(feature = "test_mode"))]
|
||||
let set_keys = &set_index.keys;
|
||||
|
||||
#[cfg(feature = "test_mode")]
|
||||
let set_keys = set_index
|
||||
.keys
|
||||
.iter()
|
||||
.collect::<std::collections::BTreeSet<_>>();
|
||||
|
||||
for key in set_keys {
|
||||
match key {
|
||||
IndexKey::ForeignKey {
|
||||
object_id: foreign_id,
|
||||
type_filter,
|
||||
} => {
|
||||
// Verify that the referenced object exists
|
||||
let item_id = foreign_id.id().id();
|
||||
let object_id = foreign_id.object().to_id();
|
||||
let object_flags = foreign_id.object().flags();
|
||||
let key = if type_filter != &IndexValue::None {
|
||||
RegistryClass::Index {
|
||||
index_id: Property::Type.to_id(),
|
||||
object_id,
|
||||
item_id,
|
||||
key: type_filter.serialize(),
|
||||
}
|
||||
} else {
|
||||
RegistryClass::IndexId { object_id, item_id }
|
||||
};
|
||||
|
||||
if !self
|
||||
.0
|
||||
.store
|
||||
.key_exists(ValueKey::from(ValueClass::Registry(key)))
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
{
|
||||
return Ok(RegistryWriteResult::InvalidForeignKey {
|
||||
object_id: *foreign_id,
|
||||
});
|
||||
} else if let Some(tenant_id) = tenant_id
|
||||
&& (object_flags & OBJ_FILTER_TENANT) != 0
|
||||
&& !self
|
||||
.0
|
||||
.store
|
||||
.key_exists(ValueKey::from(ValueClass::Registry(
|
||||
RegistryClass::Index {
|
||||
index_id: Property::MemberTenantId.to_id(),
|
||||
object_id,
|
||||
item_id,
|
||||
key: IndexValue::U64(tenant_id).serialize(),
|
||||
},
|
||||
)))
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
{
|
||||
return Ok(RegistryWriteResult::InvalidForeignKey {
|
||||
object_id: *foreign_id,
|
||||
});
|
||||
} else if (object_flags & OBJ_FILTER_ACCOUNT) != 0
|
||||
&& let Some(account_id) = account_id
|
||||
&& !self
|
||||
.0
|
||||
.store
|
||||
.key_exists(ValueKey::from(ValueClass::Registry(
|
||||
RegistryClass::Index {
|
||||
index_id: Property::AccountId.to_id(),
|
||||
object_id,
|
||||
item_id,
|
||||
key: IndexValue::U64(account_id).serialize(),
|
||||
},
|
||||
)))
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
{
|
||||
return Ok(RegistryWriteResult::InvalidForeignKey {
|
||||
object_id: *foreign_id,
|
||||
});
|
||||
}
|
||||
}
|
||||
IndexKey::Unique {
|
||||
property,
|
||||
value_1,
|
||||
value_2,
|
||||
global,
|
||||
} => {
|
||||
let key = ValueKey::from(ValueClass::Registry(RegistryClass::PrimaryKey {
|
||||
object_id: (!*global).then_some(object_id),
|
||||
index_id: property.to_id(),
|
||||
key: serialize_composite_key(value_1, value_2),
|
||||
}));
|
||||
if let Some(existing_id) = self
|
||||
.0
|
||||
.store
|
||||
.get_value::<ObjectId>(key)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
&& existing_id != ObjectId::new(object_type, Id::new(item_id))
|
||||
{
|
||||
return Ok(RegistryWriteResult::PrimaryKeyConflict {
|
||||
property: *property,
|
||||
existing_id,
|
||||
});
|
||||
}
|
||||
}
|
||||
IndexKey::Search { .. } => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Assign id
|
||||
if generate_id {
|
||||
let mut id_batch = BatchBuilder::new();
|
||||
id_batch.add_and_get(
|
||||
ValueClass::Registry(RegistryClass::IdCounter { object_id }),
|
||||
1,
|
||||
);
|
||||
item_id = self
|
||||
.0
|
||||
.store
|
||||
.write(id_batch.build_all())
|
||||
.await
|
||||
.and_then(|v| v.last_counter_id())? as u64;
|
||||
}
|
||||
|
||||
// It's pickle time!
|
||||
let out = object.inner.to_pickled_vec();
|
||||
if out.len() > MAX_OBJECT_PAYLOAD_SIZE {
|
||||
return Ok(RegistryWriteResult::ValidationError {
|
||||
errors: vec![ValidationError::Invalid {
|
||||
property: Property::Id,
|
||||
value: format!(
|
||||
"Object size {} exceeds maximum of {}",
|
||||
out.len(),
|
||||
MAX_OBJECT_PAYLOAD_SIZE
|
||||
),
|
||||
}],
|
||||
});
|
||||
}
|
||||
|
||||
// Build batch
|
||||
if write_id {
|
||||
batch.set(
|
||||
ValueClass::Registry(RegistryClass::IndexId { object_id, item_id }),
|
||||
vec![],
|
||||
);
|
||||
}
|
||||
|
||||
batch
|
||||
.registry_index(object_id, item_id, set_index.keys.iter(), true)
|
||||
.registry_index(object_id, item_id, clear_index.keys.iter(), false)
|
||||
.set(
|
||||
ValueClass::Registry(RegistryClass::Item { object_id, item_id }),
|
||||
out,
|
||||
);
|
||||
|
||||
self.store()
|
||||
.write(batch.build_all())
|
||||
.await
|
||||
.map(|_| RegistryWriteResult::Success(Id::new(item_id)))
|
||||
}
|
||||
|
||||
async fn delete(
|
||||
&self,
|
||||
object_id: ObjectId,
|
||||
object: Option<&Object>,
|
||||
allowed_orphan_types: &[ObjectType],
|
||||
) -> trc::Result<RegistryWriteResult> {
|
||||
let object_type = object_id.object();
|
||||
let object_type_id = object_type.to_id();
|
||||
let id = object_id.id();
|
||||
let item_id = id.id();
|
||||
|
||||
// Fetch object
|
||||
let object = if let Some(object) = object {
|
||||
Cow::Borrowed(object)
|
||||
} else if let Some(object) = self.get(object_id).await? {
|
||||
Cow::Owned(object)
|
||||
} else {
|
||||
return Ok(RegistryWriteResult::NotFound {
|
||||
object_id: ObjectId::new(object_type, id),
|
||||
});
|
||||
};
|
||||
|
||||
// Validate tenant and account changes
|
||||
let mut clear_index = IndexBuilder::default();
|
||||
object.index(&mut clear_index);
|
||||
|
||||
// Validate relationships
|
||||
let mut linked = self.linked_objects(object_id).await?;
|
||||
if !linked.is_empty() {
|
||||
if !allowed_orphan_types.is_empty() {
|
||||
linked.retain(|object_id| !allowed_orphan_types.contains(&object_id.object()));
|
||||
}
|
||||
|
||||
if !linked.is_empty() {
|
||||
return Ok(RegistryWriteResult::CannotDeleteLinked {
|
||||
object_id: ObjectId::new(object_type, id),
|
||||
linked_objects: linked,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Build deletion batch
|
||||
let mut batch = BatchBuilder::new();
|
||||
batch
|
||||
.assert_value(
|
||||
ValueClass::Registry(RegistryClass::Item {
|
||||
object_id: object_type_id,
|
||||
item_id,
|
||||
}),
|
||||
AssertValue::Hash(object.revision),
|
||||
)
|
||||
.clear(ValueClass::Registry(RegistryClass::Item {
|
||||
object_id: object_type_id,
|
||||
item_id,
|
||||
}))
|
||||
.clear(ValueClass::Registry(RegistryClass::IndexId {
|
||||
object_id: object_type_id,
|
||||
item_id,
|
||||
}))
|
||||
.registry_index(object_type_id, item_id, clear_index.keys.iter(), false);
|
||||
|
||||
self.0
|
||||
.store
|
||||
.write(batch.build_all())
|
||||
.await
|
||||
.map(|_| RegistryWriteResult::Success(Id::from(item_id)))
|
||||
.caused_by(trc::location!())
|
||||
}
|
||||
|
||||
pub async fn linked_objects(&self, object_id: ObjectId) -> trc::Result<Vec<ObjectId>> {
|
||||
let object_type_id = object_id.object().to_id();
|
||||
let item_id = object_id.id().id();
|
||||
let mut linked = Vec::new();
|
||||
let from_key = ValueKey::from(ValueClass::Registry(RegistryClass::Reference {
|
||||
to_object_id: object_type_id,
|
||||
to_item_id: item_id,
|
||||
from_object_id: 0,
|
||||
from_item_id: 0,
|
||||
}));
|
||||
let to_key = ValueKey::from(ValueClass::Registry(RegistryClass::Reference {
|
||||
to_object_id: object_type_id,
|
||||
to_item_id: item_id,
|
||||
from_object_id: u16::MAX,
|
||||
from_item_id: u64::MAX,
|
||||
}));
|
||||
|
||||
self.0
|
||||
.store
|
||||
.iterate(
|
||||
IterateParams::new(from_key, to_key).no_values().ascending(),
|
||||
|key, _| {
|
||||
if key.len() == (U16_LEN * 2) + (U64_LEN * 2) {
|
||||
let object =
|
||||
ObjectType::from_id(key.deserialize_be_u16(U64_LEN + U16_LEN)?)
|
||||
.ok_or_else(|| {
|
||||
trc::EventType::Registry(
|
||||
trc::RegistryEvent::DeserializationError,
|
||||
)
|
||||
.into_err()
|
||||
.caused_by(trc::location!())
|
||||
.ctx(trc::Key::Key, key)
|
||||
})?;
|
||||
let id = key.deserialize_be_u64(U64_LEN + U16_LEN + U16_LEN)?;
|
||||
linked.push(ObjectId::new(object, Id::new(id)));
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
},
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())
|
||||
.map(|_| linked)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn assign_id(&self) -> u64 {
|
||||
self.0.id_generator.generate()
|
||||
}
|
||||
}
|
||||
|
||||
impl BatchBuilder {
|
||||
pub fn registry_index<'x>(
|
||||
&mut self,
|
||||
object_id: u16,
|
||||
item_id: u64,
|
||||
index_keys: impl Iterator<Item = &'x IndexKey<'x>>,
|
||||
is_set: bool,
|
||||
) -> &mut Self {
|
||||
for key in index_keys {
|
||||
let (key, value) = match key {
|
||||
IndexKey::Search { property, value } => (
|
||||
RegistryClass::Index {
|
||||
index_id: property.to_id(),
|
||||
object_id,
|
||||
item_id,
|
||||
key: value.serialize(),
|
||||
},
|
||||
vec![],
|
||||
),
|
||||
IndexKey::Unique {
|
||||
property,
|
||||
value_1,
|
||||
value_2,
|
||||
global,
|
||||
} => (
|
||||
RegistryClass::PrimaryKey {
|
||||
object_id: (!*global).then_some(object_id),
|
||||
index_id: property.to_id(),
|
||||
key: serialize_composite_key(value_1, value_2),
|
||||
},
|
||||
KeySerializer::new(U16_LEN + U64_LEN)
|
||||
.write(object_id)
|
||||
.write(item_id)
|
||||
.finalize(),
|
||||
),
|
||||
IndexKey::ForeignKey {
|
||||
object_id: to_object_id,
|
||||
..
|
||||
} => (
|
||||
RegistryClass::Reference {
|
||||
to_object_id: to_object_id.object().to_id(),
|
||||
to_item_id: to_object_id.id().id(),
|
||||
from_item_id: item_id,
|
||||
from_object_id: object_id,
|
||||
},
|
||||
vec![],
|
||||
),
|
||||
};
|
||||
if is_set {
|
||||
if !value.is_empty() {
|
||||
self.assert_value(ValueClass::Registry(key.clone()), ());
|
||||
}
|
||||
self.set(ValueClass::Registry(key), value);
|
||||
} else {
|
||||
self.clear(ValueClass::Registry(key));
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize_composite_key(value_1: &IndexValue<'_>, value_2: &IndexValue<'_>) -> Vec<u8> {
|
||||
let mut key = value_1.serialize();
|
||||
match value_2 {
|
||||
IndexValue::Text(text) => key.extend_from_slice(text.as_bytes()),
|
||||
IndexValue::Bytes(bytes) => key.extend_from_slice(bytes),
|
||||
IndexValue::U64(num) => key.extend_from_slice(&num.to_be_bytes()),
|
||||
IndexValue::I64(num) => key.extend_from_slice(&num.to_be_bytes()),
|
||||
IndexValue::U16(num) => key.extend_from_slice(&num.to_be_bytes()),
|
||||
IndexValue::None => {}
|
||||
}
|
||||
key
|
||||
}
|
||||
|
||||
impl SerializeInfallible for IndexValue<'_> {
|
||||
fn serialize(&self) -> Vec<u8> {
|
||||
match self {
|
||||
IndexValue::Text(text) => text.as_bytes().to_vec(),
|
||||
IndexValue::Bytes(bytes) => bytes.clone(),
|
||||
IndexValue::U64(num) => num.to_be_bytes().to_vec(),
|
||||
IndexValue::I64(num) => num.to_be_bytes().to_vec(),
|
||||
IndexValue::U16(num) => num.to_be_bytes().to_vec(),
|
||||
IndexValue::None => vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> RegistryWrite<'x> {
|
||||
pub fn insert(object: &'x Object) -> Self {
|
||||
RegistryWrite::Insert { object, id: None }
|
||||
}
|
||||
|
||||
pub fn insert_with_id(id: Id, object: &'x Object) -> Self {
|
||||
RegistryWrite::Insert {
|
||||
object,
|
||||
id: Some(id),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(id: Id, object: &'x Object, old_object: &'x Object) -> Self {
|
||||
RegistryWrite::Update {
|
||||
object,
|
||||
id,
|
||||
old_object,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delete(object_id: ObjectId) -> Self {
|
||||
RegistryWrite::Delete {
|
||||
object_id,
|
||||
object: None,
|
||||
allowed_orphan_types: &[],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delete_object(object_id: ObjectId, object: &'x Object) -> Self {
|
||||
RegistryWrite::Delete {
|
||||
object_id,
|
||||
object: Some(object),
|
||||
allowed_orphan_types: &[],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for RegistryWriteResult {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
RegistryWriteResult::Success(id) => write!(f, "Success: {}", id),
|
||||
RegistryWriteResult::CannotDeleteLinked {
|
||||
object_id,
|
||||
linked_objects,
|
||||
} => {
|
||||
write!(f, "Cannot delete {} because it is linked to: ", object_id)?;
|
||||
for linked in linked_objects {
|
||||
write!(f, "{}, ", linked)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
RegistryWriteResult::InvalidSingletonId => write!(f, "Invalid singleton id"),
|
||||
RegistryWriteResult::CannotDeleteSingleton => write!(f, "Cannot delete singleton"),
|
||||
RegistryWriteResult::NotFound { object_id } => write!(f, "Not found: {}", object_id),
|
||||
RegistryWriteResult::InvalidForeignKey { object_id } => {
|
||||
write!(f, "Invalid foreign key: {}", object_id)
|
||||
}
|
||||
RegistryWriteResult::PrimaryKeyConflict {
|
||||
property,
|
||||
existing_id,
|
||||
} => {
|
||||
write!(
|
||||
f,
|
||||
"Primary key conflict on property {:?} with existing object {}",
|
||||
property.as_str(),
|
||||
existing_id
|
||||
)
|
||||
}
|
||||
RegistryWriteResult::ValidationError { errors } => {
|
||||
write!(f, "Validation error: ")?;
|
||||
for error in errors {
|
||||
write!(f, "{}, ", error)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
RegistryWriteResult::NotSupported => write!(f, "Operation not supported"),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user