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:
2026-09-18 10:21:56 -07:00
commit 7dae9b29fd
1650 changed files with 485521 additions and 0 deletions
+206
View File
@@ -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();
}
}
}
+105
View File
@@ -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)
}
}
+108
View File
@@ -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)
})
}
}
+238
View File
@@ -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)
})
}
}
+943
View File
@@ -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)
}
}
+632
View File
@@ -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"),
}
}
}