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,88 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use super::{Archive, ArchiveVersion};
|
||||
use crate::{U32_LEN, U64_LEN};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum AssertValue {
|
||||
U32(u32),
|
||||
U64(u64),
|
||||
Hash(u64),
|
||||
Archive(ArchiveVersion),
|
||||
Some,
|
||||
None,
|
||||
}
|
||||
|
||||
pub trait ToAssertValue {
|
||||
fn to_assert_value(&self) -> AssertValue;
|
||||
}
|
||||
|
||||
impl ToAssertValue for AssertValue {
|
||||
fn to_assert_value(&self) -> AssertValue {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl ToAssertValue for () {
|
||||
fn to_assert_value(&self) -> AssertValue {
|
||||
AssertValue::None
|
||||
}
|
||||
}
|
||||
|
||||
impl ToAssertValue for u64 {
|
||||
fn to_assert_value(&self) -> AssertValue {
|
||||
AssertValue::U64(*self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToAssertValue for u32 {
|
||||
fn to_assert_value(&self) -> AssertValue {
|
||||
AssertValue::U32(*self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ToAssertValue for Archive<T> {
|
||||
fn to_assert_value(&self) -> AssertValue {
|
||||
AssertValue::Archive(self.version)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ToAssertValue for &Archive<T> {
|
||||
fn to_assert_value(&self) -> AssertValue {
|
||||
AssertValue::Archive(self.version)
|
||||
}
|
||||
}
|
||||
|
||||
impl AssertValue {
|
||||
pub fn matches(&self, bytes: &[u8]) -> bool {
|
||||
match self {
|
||||
AssertValue::U32(v) => bytes
|
||||
.get(bytes.len() - U32_LEN..)
|
||||
.is_some_and(|b| b == v.to_be_bytes()),
|
||||
|
||||
AssertValue::U64(v) => bytes
|
||||
.get(bytes.len() - U64_LEN..)
|
||||
.is_some_and(|b| b == v.to_be_bytes()),
|
||||
AssertValue::Hash(v) => xxhash_rust::xxh3::xxh3_64(bytes) == *v,
|
||||
AssertValue::Archive(v) => match v {
|
||||
ArchiveVersion::Versioned { hash, .. } => bytes
|
||||
.get(bytes.len() - U32_LEN - U64_LEN - 1..bytes.len() - U64_LEN - 1)
|
||||
.is_some_and(|b| b == hash.to_be_bytes()),
|
||||
ArchiveVersion::Hashed { hash } => bytes
|
||||
.get(bytes.len() - U32_LEN - 1..bytes.len() - 1)
|
||||
.is_some_and(|b| b == hash.to_be_bytes()),
|
||||
ArchiveVersion::Unversioned => false,
|
||||
},
|
||||
AssertValue::None => false,
|
||||
AssertValue::Some => true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_none(&self) -> bool {
|
||||
matches!(self, AssertValue::None)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,556 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use super::{
|
||||
Batch, BatchBuilder, ChangedCollection, IntoOperations, Operation, ValueClass, ValueOp,
|
||||
assert::ToAssertValue, log::VanishedItem,
|
||||
};
|
||||
use crate::{
|
||||
SerializeInfallible, U32_LEN,
|
||||
write::{
|
||||
LogCollection, MergeFnc, MergeOperation, Params, SetFnc, SetOperation, TaskQueueClass,
|
||||
},
|
||||
};
|
||||
use registry::{
|
||||
schema::structs::Task,
|
||||
types::{EnumImpl, ObjectImpl},
|
||||
};
|
||||
use types::{
|
||||
collection::{Collection, SyncCollection, VanishedCollection},
|
||||
field::FieldType,
|
||||
};
|
||||
use utils::{map::vec_map::VecMap, snowflake::SnowflakeIdGenerator};
|
||||
|
||||
impl BatchBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
ops: Vec::with_capacity(32),
|
||||
current_account_id: None,
|
||||
current_collection: None,
|
||||
current_document_id: None,
|
||||
changes: Default::default(),
|
||||
changed_collections: Default::default(),
|
||||
batch_size: 0,
|
||||
batch_ops: 0,
|
||||
has_assertions: false,
|
||||
commit_points: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_account_id(&mut self, account_id: u32) -> &mut Self {
|
||||
if self
|
||||
.current_account_id
|
||||
.is_none_or(|current_account_id| current_account_id != account_id)
|
||||
{
|
||||
self.current_account_id = account_id.into();
|
||||
self.ops.push(Operation::AccountId { account_id });
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_collection(&mut self, collection: Collection) -> &mut Self {
|
||||
let collection_ = Some(collection);
|
||||
if collection_ != self.current_collection {
|
||||
self.current_collection = collection_;
|
||||
self.ops.push(Operation::Collection { collection });
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_document(&mut self, document_id: u32) -> &mut Self {
|
||||
self.ops.push(Operation::DocumentId { document_id });
|
||||
self.current_document_id = Some(document_id);
|
||||
self.has_assertions = false;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn assert_value(
|
||||
&mut self,
|
||||
class: impl Into<ValueClass>,
|
||||
value: impl ToAssertValue,
|
||||
) -> &mut Self {
|
||||
self.ops.push(Operation::AssertValue {
|
||||
class: class.into(),
|
||||
assert_value: value.to_assert_value(),
|
||||
});
|
||||
self.batch_ops += 1;
|
||||
self.has_assertions = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn index(&mut self, field: impl FieldType, value: impl Into<Vec<u8>>) -> &mut Self {
|
||||
let field = field.into();
|
||||
let value = value.into();
|
||||
let value_len = value.len();
|
||||
|
||||
self.ops.push(Operation::Index {
|
||||
field,
|
||||
key: value,
|
||||
set: true,
|
||||
});
|
||||
self.batch_size += (U32_LEN * 3) + value_len;
|
||||
self.batch_ops += 1;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn unindex(&mut self, field: impl FieldType, value: impl Into<Vec<u8>>) -> &mut Self {
|
||||
let field = field.into();
|
||||
let value = value.into();
|
||||
let value_len = value.len();
|
||||
|
||||
self.ops.push(Operation::Index {
|
||||
field,
|
||||
key: value,
|
||||
set: false,
|
||||
});
|
||||
self.batch_size += (U32_LEN * 3) + value_len;
|
||||
self.batch_ops += 1;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn tag(&mut self, field: impl FieldType) -> &mut Self {
|
||||
self.index(field, vec![])
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn untag(&mut self, field: impl FieldType) -> &mut Self {
|
||||
self.unindex(field, vec![])
|
||||
}
|
||||
|
||||
pub fn add(&mut self, class: impl Into<ValueClass>, value: i64) -> &mut Self {
|
||||
let class = class.into();
|
||||
self.batch_size += class.serialized_size() + std::mem::size_of::<i64>();
|
||||
self.ops.push(Operation::Value {
|
||||
class,
|
||||
op: ValueOp::AtomicAdd(value),
|
||||
});
|
||||
self.batch_ops += 1;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_and_get(&mut self, class: impl Into<ValueClass>, value: i64) -> &mut Self {
|
||||
let class = class.into();
|
||||
self.batch_size += class.serialized_size() + (std::mem::size_of::<i64>() * 2);
|
||||
self.ops.push(Operation::Value {
|
||||
class,
|
||||
op: ValueOp::AddAndGet(value),
|
||||
});
|
||||
self.batch_ops += 1;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set(&mut self, class: impl Into<ValueClass>, value: impl Into<Vec<u8>>) -> &mut Self {
|
||||
let class = class.into();
|
||||
let value = value.into();
|
||||
self.batch_size += class.serialized_size() + value.len();
|
||||
self.ops.push(Operation::Value {
|
||||
class,
|
||||
op: ValueOp::Set(value),
|
||||
});
|
||||
self.batch_ops += 1;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_fnc(
|
||||
&mut self,
|
||||
class: impl Into<ValueClass>,
|
||||
params: Params,
|
||||
fnc: SetFnc,
|
||||
) -> &mut Self {
|
||||
self.ops.push(Operation::Value {
|
||||
class: class.into(),
|
||||
op: ValueOp::SetFnc(SetOperation { fnc, params }),
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
pub fn merge_fnc(
|
||||
&mut self,
|
||||
class: impl Into<ValueClass>,
|
||||
params: Params,
|
||||
fnc: MergeFnc,
|
||||
) -> &mut Self {
|
||||
self.ops.push(Operation::Value {
|
||||
class: class.into(),
|
||||
op: ValueOp::MergeFnc(MergeOperation { fnc, params }),
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
pub fn clear(&mut self, class: impl Into<ValueClass>) -> &mut Self {
|
||||
let class = class.into();
|
||||
self.batch_size += class.serialized_size();
|
||||
self.ops.push(Operation::Value {
|
||||
class,
|
||||
op: ValueOp::Clear,
|
||||
});
|
||||
self.batch_ops += 1;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn acl_grant(&mut self, grant_account_id: u32, op: Vec<u8>) -> &mut Self {
|
||||
self.batch_size += (U32_LEN * 3) + op.len();
|
||||
self.ops.push(Operation::Value {
|
||||
class: ValueClass::Acl(grant_account_id),
|
||||
op: ValueOp::Set(op),
|
||||
});
|
||||
self.batch_ops += 1;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn acl_revoke(&mut self, grant_account_id: u32) -> &mut Self {
|
||||
self.batch_size += U32_LEN * 3;
|
||||
self.ops.push(Operation::Value {
|
||||
class: ValueClass::Acl(grant_account_id),
|
||||
op: ValueOp::Clear,
|
||||
});
|
||||
self.batch_ops += 1;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn log_item_insert(
|
||||
&mut self,
|
||||
collection: SyncCollection,
|
||||
prefix: Option<u32>,
|
||||
) -> &mut Self {
|
||||
if let (Some(account_id), Some(document_id)) =
|
||||
(self.current_account_id, self.current_document_id)
|
||||
{
|
||||
self.changes.get_mut_or_insert(account_id).log_item_insert(
|
||||
collection,
|
||||
prefix,
|
||||
document_id,
|
||||
);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn log_item_update(
|
||||
&mut self,
|
||||
collection: SyncCollection,
|
||||
prefix: Option<u32>,
|
||||
) -> &mut Self {
|
||||
if let (Some(account_id), Some(document_id)) =
|
||||
(self.current_account_id, self.current_document_id)
|
||||
{
|
||||
self.changes.get_mut_or_insert(account_id).log_item_update(
|
||||
collection,
|
||||
prefix,
|
||||
document_id,
|
||||
);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn log_item_delete(
|
||||
&mut self,
|
||||
collection: SyncCollection,
|
||||
prefix: Option<u32>,
|
||||
) -> &mut Self {
|
||||
if let (Some(account_id), Some(document_id)) =
|
||||
(self.current_account_id, self.current_document_id)
|
||||
{
|
||||
self.changes.get_mut_or_insert(account_id).log_item_delete(
|
||||
collection,
|
||||
prefix,
|
||||
document_id,
|
||||
);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn log_container_insert(&mut self, collection: SyncCollection) -> &mut Self {
|
||||
if let (Some(account_id), Some(document_id)) =
|
||||
(self.current_account_id, self.current_document_id)
|
||||
{
|
||||
self.changes
|
||||
.get_mut_or_insert(account_id)
|
||||
.log_container_insert(collection, document_id);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn log_container_update(&mut self, collection: SyncCollection) -> &mut Self {
|
||||
if let (Some(account_id), Some(document_id)) =
|
||||
(self.current_account_id, self.current_document_id)
|
||||
{
|
||||
self.changes
|
||||
.get_mut_or_insert(account_id)
|
||||
.log_container_update(collection, document_id);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn log_container_delete(&mut self, collection: SyncCollection) -> &mut Self {
|
||||
if let (Some(account_id), Some(document_id)) =
|
||||
(self.current_account_id, self.current_document_id)
|
||||
{
|
||||
self.changes
|
||||
.get_mut_or_insert(account_id)
|
||||
.log_container_delete(collection, document_id);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn log_container_property_change(
|
||||
&mut self,
|
||||
collection: SyncCollection,
|
||||
document_id: u32,
|
||||
) -> &mut Self {
|
||||
if let Some(account_id) = self.current_account_id {
|
||||
self.changes
|
||||
.get_mut_or_insert(account_id)
|
||||
.log_container_property_update(collection, document_id);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn log_vanished_item(
|
||||
&mut self,
|
||||
collection: VanishedCollection,
|
||||
item: impl Into<VanishedItem>,
|
||||
) -> &mut Self {
|
||||
if let Some(account_id) = self.current_account_id {
|
||||
let item = item.into();
|
||||
self.batch_size += item.serialized_size();
|
||||
self.changes
|
||||
.get_mut_or_insert(account_id)
|
||||
.log_vanished_item(collection, item);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn log_share_notification(
|
||||
&mut self,
|
||||
notification_id: u64,
|
||||
notify_account_id: u32,
|
||||
value: impl SerializeInfallible,
|
||||
) -> &mut Self {
|
||||
self.changed_collections
|
||||
.get_mut_or_insert(notify_account_id)
|
||||
.share_notification_id = Some(notification_id);
|
||||
self.set(
|
||||
ValueClass::ShareNotification {
|
||||
notification_id,
|
||||
notify_account_id,
|
||||
},
|
||||
value.serialize(),
|
||||
)
|
||||
}
|
||||
|
||||
fn serialize_changes(&mut self) {
|
||||
if !self.changes.is_empty() {
|
||||
for (account_id, changelog) in std::mem::take(&mut self.changes) {
|
||||
self.with_account_id(account_id);
|
||||
|
||||
// Serialize changes
|
||||
for (collection, changes) in changelog.changes.into_iter() {
|
||||
let cc = self.changed_collections.get_mut_or_insert(account_id);
|
||||
if changes.has_container_changes() {
|
||||
cc.changed_containers.insert(collection);
|
||||
}
|
||||
if changes.has_item_changes() {
|
||||
cc.changed_items.insert(collection);
|
||||
}
|
||||
|
||||
self.ops.push(Operation::Log {
|
||||
collection: LogCollection::Sync(collection),
|
||||
set: changes.serialize(),
|
||||
});
|
||||
}
|
||||
|
||||
// Serialize vanished items
|
||||
for (collection, vanished) in changelog.vanished.into_iter() {
|
||||
self.ops.push(Operation::Log {
|
||||
collection: LogCollection::Vanished(collection),
|
||||
set: vanished.serialize(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn commit_point(&mut self) -> &mut Self {
|
||||
if self.is_large_batch() {
|
||||
self.serialize_changes();
|
||||
self.commit_points.push(self.ops.len());
|
||||
self.batch_ops = 0;
|
||||
self.batch_size = 0;
|
||||
if let Some(account_id) = self.current_account_id {
|
||||
self.ops.push(Operation::AccountId { account_id });
|
||||
}
|
||||
if let Some(collection) = self.current_collection {
|
||||
self.ops.push(Operation::Collection { collection });
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_large_batch(&self) -> bool {
|
||||
self.batch_size > 5_000_000 || self.batch_ops > 1000
|
||||
}
|
||||
|
||||
pub fn any_op(&mut self, op: Operation) -> &mut Self {
|
||||
if let Operation::Value { class, op } = &op {
|
||||
self.batch_size += class.serialized_size();
|
||||
if let ValueOp::Set(value) = op {
|
||||
self.batch_size += value.len();
|
||||
}
|
||||
}
|
||||
|
||||
self.ops.push(op);
|
||||
self.batch_ops += 1;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn custom(&mut self, value: impl IntoOperations) -> trc::Result<&mut Self> {
|
||||
value.build(self)?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn last_account_id(&self) -> Option<u32> {
|
||||
self.current_account_id
|
||||
}
|
||||
|
||||
pub fn last_collection(&self) -> Option<Collection> {
|
||||
self.current_collection
|
||||
}
|
||||
|
||||
pub fn last_document_id(&self) -> Option<u32> {
|
||||
self.current_document_id
|
||||
}
|
||||
|
||||
pub fn commit_points(&mut self) -> CommitPointIterator {
|
||||
self.serialize_changes();
|
||||
CommitPointIterator {
|
||||
commit_points: std::mem::take(&mut self.commit_points),
|
||||
commit_point_last: self.ops.len(),
|
||||
offset_start: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_one(&mut self, commit_point: CommitPoint) -> Batch<'_> {
|
||||
Batch {
|
||||
changes: &self.changed_collections,
|
||||
ops: &mut self.ops[commit_point.offset_start..commit_point.offset_end],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_all(&mut self) -> Batch<'_> {
|
||||
self.serialize_changes();
|
||||
Batch {
|
||||
changes: &self.changed_collections,
|
||||
ops: self.ops.as_mut_slice(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn changes(self) -> Option<VecMap<u32, ChangedCollection>> {
|
||||
if self.has_changes() {
|
||||
Some(self.changed_collections)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_changes(&self) -> bool {
|
||||
!self.changed_collections.is_empty()
|
||||
}
|
||||
|
||||
pub fn ops(&self) -> &[Operation] {
|
||||
self.ops.as_slice()
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.batch_size
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.batch_ops == 0
|
||||
}
|
||||
|
||||
pub fn schedule_task(&mut self, task: Task) -> &mut Self {
|
||||
let due = task.due_timestamp();
|
||||
let class = task.object_type().to_id();
|
||||
let task = task.to_pickled_vec();
|
||||
let id = SnowflakeIdGenerator::global_id().unwrap_or_default();
|
||||
|
||||
self.set(ValueClass::TaskQueue(TaskQueueClass::Task { id }), task)
|
||||
.set(
|
||||
ValueClass::TaskQueue(TaskQueueClass::Due { id, due }),
|
||||
class.serialize(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn schedule_task_with_id(&mut self, id: u64, task: Task) -> &mut Self {
|
||||
let due = task.due_timestamp();
|
||||
let class = task.object_type().to_id();
|
||||
let task = task.to_pickled_vec();
|
||||
|
||||
self.set(ValueClass::TaskQueue(TaskQueueClass::Task { id }), task)
|
||||
.set(
|
||||
ValueClass::TaskQueue(TaskQueueClass::Due { id, due }),
|
||||
class.serialize(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CommitPointIterator {
|
||||
commit_points: Vec<usize>,
|
||||
commit_point_last: usize,
|
||||
offset_start: usize,
|
||||
}
|
||||
|
||||
pub struct CommitPoint {
|
||||
pub offset_start: usize,
|
||||
pub offset_end: usize,
|
||||
}
|
||||
|
||||
impl CommitPointIterator {
|
||||
pub fn iter(&mut self) -> impl Iterator<Item = CommitPoint> {
|
||||
self.commit_points
|
||||
.iter()
|
||||
.copied()
|
||||
.chain([self.commit_point_last])
|
||||
.map(|offset_end| {
|
||||
let point = CommitPoint {
|
||||
offset_start: self.offset_start,
|
||||
offset_end,
|
||||
};
|
||||
self.offset_start = offset_end;
|
||||
point
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Batch<'_> {
|
||||
pub fn is_atomic(&self) -> bool {
|
||||
!self.ops.iter().any(|op| {
|
||||
matches!(
|
||||
op,
|
||||
Operation::AssertValue { .. }
|
||||
| Operation::Value {
|
||||
op: ValueOp::AddAndGet(_),
|
||||
..
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn first_account_id(&self) -> Option<u32> {
|
||||
self.ops.iter().find_map(|op| match op {
|
||||
Operation::AccountId { account_id } => Some(*account_id),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for BatchBuilder {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,382 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use bitpacking::{BitPacker, BitPacker1x, BitPacker4x, BitPacker8x};
|
||||
use utils::codec::leb128::Leb128Reader;
|
||||
|
||||
use super::key::KeySerializer;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct BitpackIterator<'x> {
|
||||
pub(crate) bytes: &'x [u8],
|
||||
pub(crate) bytes_offset: usize,
|
||||
pub(crate) chunk: Vec<u32>,
|
||||
pub(crate) chunk_offset: usize,
|
||||
pub items_left: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) struct BitBlockPacker {
|
||||
bitpacker_1: BitPacker1x,
|
||||
bitpacker_4: BitPacker4x,
|
||||
bitpacker_8: BitPacker8x,
|
||||
block_len: usize,
|
||||
}
|
||||
|
||||
impl KeySerializer {
|
||||
pub fn bitpack_sorted(self, items: &[u32]) -> Self {
|
||||
let mut serializer = self;
|
||||
let mut bitpacker = BitBlockPacker::new();
|
||||
let mut compressed = vec![0u8; 4 * BitPacker8x::BLOCK_LEN];
|
||||
|
||||
let mut pos = 0;
|
||||
let len = items.len();
|
||||
let mut initial_value = None;
|
||||
|
||||
serializer = serializer.write_leb128(len as u32);
|
||||
|
||||
while pos < len {
|
||||
let block_len = match len - pos {
|
||||
0..=31 => {
|
||||
for val in &items[pos..] {
|
||||
serializer = serializer.write_leb128(*val);
|
||||
}
|
||||
break;
|
||||
}
|
||||
32..=127 => BitPacker1x::BLOCK_LEN,
|
||||
128..=255 => BitPacker4x::BLOCK_LEN,
|
||||
_ => BitPacker8x::BLOCK_LEN,
|
||||
};
|
||||
|
||||
let chunk = &items[pos..pos + block_len];
|
||||
bitpacker.block_len(block_len);
|
||||
let num_bits: u8 = bitpacker.num_bits_strictly_sorted(initial_value, chunk);
|
||||
let compressed_len = bitpacker.compress_strictly_sorted(
|
||||
initial_value,
|
||||
chunk,
|
||||
&mut compressed[..],
|
||||
num_bits,
|
||||
);
|
||||
serializer = serializer
|
||||
.write(num_bits)
|
||||
.write(&compressed[..compressed_len]);
|
||||
initial_value = chunk[chunk.len() - 1].into();
|
||||
|
||||
pos += block_len;
|
||||
}
|
||||
serializer
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> BitpackIterator<'x> {
|
||||
pub fn from_bytes_and_offset(bytes: &'x [u8], bytes_offset: usize, items_left: u32) -> Self {
|
||||
BitpackIterator {
|
||||
bytes,
|
||||
bytes_offset,
|
||||
items_left,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(bytes: &'x [u8]) -> Option<Self> {
|
||||
bytes
|
||||
.read_leb128::<u32>()
|
||||
.map(|(items_left, bytes_offset)| BitpackIterator {
|
||||
bytes,
|
||||
bytes_offset,
|
||||
items_left,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for BitpackIterator<'_> {
|
||||
type Item = u32;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if let Some(item) = self.chunk.get(self.chunk_offset) {
|
||||
self.chunk_offset += 1;
|
||||
return Some(*item);
|
||||
}
|
||||
let block_len = match self.items_left {
|
||||
0 => return None,
|
||||
1..=31 => {
|
||||
self.items_left -= 1;
|
||||
let (item, bytes_read) = self.bytes.get(self.bytes_offset..)?.read_leb128()?;
|
||||
self.bytes_offset += bytes_read;
|
||||
return Some(item);
|
||||
}
|
||||
32..=127 => BitPacker1x::BLOCK_LEN,
|
||||
128..=255 => BitPacker4x::BLOCK_LEN,
|
||||
_ => BitPacker8x::BLOCK_LEN,
|
||||
};
|
||||
|
||||
let bitpacker = BitBlockPacker::with_block_len(block_len);
|
||||
let num_bits = *self.bytes.get(self.bytes_offset)?;
|
||||
let bytes_read = ((num_bits as usize) * block_len / 8) + 1;
|
||||
let initial_value = self.chunk.last().copied();
|
||||
|
||||
self.chunk = vec![0u32; block_len];
|
||||
self.chunk_offset = 1;
|
||||
|
||||
bitpacker.decompress_strictly_sorted(
|
||||
initial_value,
|
||||
self.bytes
|
||||
.get(self.bytes_offset + 1..self.bytes_offset + bytes_read)?,
|
||||
&mut self.chunk[..],
|
||||
num_bits,
|
||||
);
|
||||
|
||||
self.bytes_offset += bytes_read;
|
||||
self.items_left -= block_len as u32;
|
||||
self.chunk.first().copied()
|
||||
}
|
||||
}
|
||||
|
||||
impl BitBlockPacker {
|
||||
pub fn with_block_len(block_len: usize) -> Self {
|
||||
BitBlockPacker {
|
||||
bitpacker_1: BitPacker1x::new(),
|
||||
bitpacker_4: BitPacker4x::new(),
|
||||
bitpacker_8: BitPacker8x::new(),
|
||||
block_len,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn block_len(&mut self, num: usize) {
|
||||
self.block_len = num;
|
||||
}
|
||||
}
|
||||
|
||||
impl BitPacker for BitBlockPacker {
|
||||
const BLOCK_LEN: usize = 0;
|
||||
|
||||
fn new() -> Self {
|
||||
BitBlockPacker {
|
||||
bitpacker_1: BitPacker1x::new(),
|
||||
bitpacker_4: BitPacker4x::new(),
|
||||
bitpacker_8: BitPacker8x::new(),
|
||||
block_len: 1,
|
||||
}
|
||||
}
|
||||
|
||||
fn compress(&self, decompressed: &[u32], compressed: &mut [u8], num_bits: u8) -> usize {
|
||||
match self.block_len {
|
||||
BitPacker8x::BLOCK_LEN => self
|
||||
.bitpacker_8
|
||||
.compress(decompressed, compressed, num_bits),
|
||||
BitPacker4x::BLOCK_LEN => self
|
||||
.bitpacker_4
|
||||
.compress(decompressed, compressed, num_bits),
|
||||
_ => self
|
||||
.bitpacker_1
|
||||
.compress(decompressed, compressed, num_bits),
|
||||
}
|
||||
}
|
||||
|
||||
fn compress_sorted(
|
||||
&self,
|
||||
initial: u32,
|
||||
decompressed: &[u32],
|
||||
compressed: &mut [u8],
|
||||
num_bits: u8,
|
||||
) -> usize {
|
||||
match self.block_len {
|
||||
BitPacker8x::BLOCK_LEN => {
|
||||
self.bitpacker_8
|
||||
.compress_sorted(initial, decompressed, compressed, num_bits)
|
||||
}
|
||||
BitPacker4x::BLOCK_LEN => {
|
||||
self.bitpacker_4
|
||||
.compress_sorted(initial, decompressed, compressed, num_bits)
|
||||
}
|
||||
_ => self
|
||||
.bitpacker_1
|
||||
.compress_sorted(initial, decompressed, compressed, num_bits),
|
||||
}
|
||||
}
|
||||
|
||||
fn decompress(&self, compressed: &[u8], decompressed: &mut [u32], num_bits: u8) -> usize {
|
||||
match self.block_len {
|
||||
BitPacker8x::BLOCK_LEN => {
|
||||
self.bitpacker_8
|
||||
.decompress(compressed, decompressed, num_bits)
|
||||
}
|
||||
BitPacker4x::BLOCK_LEN => {
|
||||
self.bitpacker_4
|
||||
.decompress(compressed, decompressed, num_bits)
|
||||
}
|
||||
_ => self
|
||||
.bitpacker_1
|
||||
.decompress(compressed, decompressed, num_bits),
|
||||
}
|
||||
}
|
||||
|
||||
fn decompress_sorted(
|
||||
&self,
|
||||
initial: u32,
|
||||
compressed: &[u8],
|
||||
decompressed: &mut [u32],
|
||||
num_bits: u8,
|
||||
) -> usize {
|
||||
match self.block_len {
|
||||
BitPacker8x::BLOCK_LEN => {
|
||||
self.bitpacker_8
|
||||
.decompress_sorted(initial, compressed, decompressed, num_bits)
|
||||
}
|
||||
BitPacker4x::BLOCK_LEN => {
|
||||
self.bitpacker_4
|
||||
.decompress_sorted(initial, compressed, decompressed, num_bits)
|
||||
}
|
||||
_ => self
|
||||
.bitpacker_1
|
||||
.decompress_sorted(initial, compressed, decompressed, num_bits),
|
||||
}
|
||||
}
|
||||
|
||||
fn num_bits(&self, decompressed: &[u32]) -> u8 {
|
||||
match self.block_len {
|
||||
BitPacker8x::BLOCK_LEN => self.bitpacker_8.num_bits(decompressed),
|
||||
BitPacker4x::BLOCK_LEN => self.bitpacker_4.num_bits(decompressed),
|
||||
_ => self.bitpacker_1.num_bits(decompressed),
|
||||
}
|
||||
}
|
||||
|
||||
fn num_bits_sorted(&self, initial: u32, decompressed: &[u32]) -> u8 {
|
||||
match self.block_len {
|
||||
BitPacker8x::BLOCK_LEN => self.bitpacker_8.num_bits_sorted(initial, decompressed),
|
||||
BitPacker4x::BLOCK_LEN => self.bitpacker_4.num_bits_sorted(initial, decompressed),
|
||||
_ => self.bitpacker_1.num_bits_sorted(initial, decompressed),
|
||||
}
|
||||
}
|
||||
|
||||
fn compress_strictly_sorted(
|
||||
&self,
|
||||
initial: Option<u32>,
|
||||
decompressed: &[u32],
|
||||
compressed: &mut [u8],
|
||||
num_bits: u8,
|
||||
) -> usize {
|
||||
match self.block_len {
|
||||
BitPacker8x::BLOCK_LEN => self.bitpacker_8.compress_strictly_sorted(
|
||||
initial,
|
||||
decompressed,
|
||||
compressed,
|
||||
num_bits,
|
||||
),
|
||||
BitPacker4x::BLOCK_LEN => self.bitpacker_4.compress_strictly_sorted(
|
||||
initial,
|
||||
decompressed,
|
||||
compressed,
|
||||
num_bits,
|
||||
),
|
||||
_ => self.bitpacker_1.compress_strictly_sorted(
|
||||
initial,
|
||||
decompressed,
|
||||
compressed,
|
||||
num_bits,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn decompress_strictly_sorted(
|
||||
&self,
|
||||
initial: Option<u32>,
|
||||
compressed: &[u8],
|
||||
decompressed: &mut [u32],
|
||||
num_bits: u8,
|
||||
) -> usize {
|
||||
match self.block_len {
|
||||
BitPacker8x::BLOCK_LEN => self.bitpacker_8.decompress_strictly_sorted(
|
||||
initial,
|
||||
compressed,
|
||||
decompressed,
|
||||
num_bits,
|
||||
),
|
||||
BitPacker4x::BLOCK_LEN => self.bitpacker_4.decompress_strictly_sorted(
|
||||
initial,
|
||||
compressed,
|
||||
decompressed,
|
||||
num_bits,
|
||||
),
|
||||
_ => self.bitpacker_1.decompress_strictly_sorted(
|
||||
initial,
|
||||
compressed,
|
||||
decompressed,
|
||||
num_bits,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn num_bits_strictly_sorted(&self, initial: Option<u32>, decompressed: &[u32]) -> u8 {
|
||||
match self.block_len {
|
||||
BitPacker8x::BLOCK_LEN => self
|
||||
.bitpacker_8
|
||||
.num_bits_strictly_sorted(initial, decompressed),
|
||||
BitPacker4x::BLOCK_LEN => self
|
||||
.bitpacker_4
|
||||
.num_bits_strictly_sorted(initial, decompressed),
|
||||
_ => self
|
||||
.bitpacker_1
|
||||
.num_bits_strictly_sorted(initial, decompressed),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn bitpack_roundtrip() {
|
||||
for num_positions in [
|
||||
1,
|
||||
10,
|
||||
BitPacker1x::BLOCK_LEN,
|
||||
BitPacker4x::BLOCK_LEN,
|
||||
BitPacker8x::BLOCK_LEN,
|
||||
BitPacker8x::BLOCK_LEN + BitPacker4x::BLOCK_LEN + BitPacker1x::BLOCK_LEN,
|
||||
BitPacker8x::BLOCK_LEN + BitPacker4x::BLOCK_LEN + BitPacker1x::BLOCK_LEN + 1,
|
||||
(BitPacker8x::BLOCK_LEN * 3)
|
||||
+ (BitPacker4x::BLOCK_LEN * 3)
|
||||
+ (BitPacker1x::BLOCK_LEN * 3)
|
||||
+ 1,
|
||||
(BitPacker8x::BLOCK_LEN * 32) + 1,
|
||||
] {
|
||||
let serialized = KeySerializer::new(num_positions * std::mem::size_of::<u32>())
|
||||
.bitpack_sorted(
|
||||
&(0..num_positions)
|
||||
.map(|i| (i * i) as u32)
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.finalize();
|
||||
|
||||
println!(
|
||||
"Testing block {num_positions} with {} size...",
|
||||
serialized.len()
|
||||
);
|
||||
|
||||
let mut iter = BitpackIterator::new(&serialized).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
iter.items_left, num_positions as u32,
|
||||
"failed for num_positions: {}",
|
||||
num_positions
|
||||
);
|
||||
|
||||
for i in 0..num_positions {
|
||||
assert_eq!(
|
||||
iter.next(),
|
||||
Some((i * i) as u32),
|
||||
"failed for position: {}",
|
||||
i
|
||||
);
|
||||
}
|
||||
assert_eq!(iter.next(), None, "expected end of iterator");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use super::{BlobOp, Operation, ValueClass, ValueOp, key::DeserializeBigEndian, now};
|
||||
use crate::{
|
||||
BlobStore, Deserialize, IterateParams, SerializeInfallible, Store, U16_LEN, U32_LEN, U64_LEN,
|
||||
ValueKey,
|
||||
write::{BatchBuilder, BlobLink, RegistryClass},
|
||||
};
|
||||
use registry::{
|
||||
schema::prelude::Property,
|
||||
types::{EnumImpl, id::ObjectId},
|
||||
};
|
||||
use std::time::Instant;
|
||||
use trc::{AddContext, StoreEvent};
|
||||
use types::{
|
||||
blob::BlobClass,
|
||||
blob_hash::{BLOB_HASH_LEN, BlobHash},
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct BlobQuota {
|
||||
pub bytes: usize,
|
||||
pub count: usize,
|
||||
}
|
||||
|
||||
impl Store {
|
||||
pub async fn blob_exists(&self, hash: impl AsRef<BlobHash> + Sync + Send) -> trc::Result<bool> {
|
||||
self.key_exists(ValueKey {
|
||||
account_id: 0,
|
||||
collection: 0,
|
||||
document_id: 0,
|
||||
class: ValueClass::Blob(BlobOp::Commit {
|
||||
hash: hash.as_ref().clone(),
|
||||
}),
|
||||
})
|
||||
.await
|
||||
.caused_by(trc::location!())
|
||||
}
|
||||
|
||||
pub async fn blob_has_access(
|
||||
&self,
|
||||
hash: impl AsRef<BlobHash> + Sync + Send,
|
||||
class: impl AsRef<BlobClass> + Sync + Send,
|
||||
) -> trc::Result<bool> {
|
||||
let key = match class.as_ref() {
|
||||
BlobClass::Reserved {
|
||||
account_id,
|
||||
expires,
|
||||
} if *expires > now() => ValueKey {
|
||||
account_id: *account_id,
|
||||
collection: 0,
|
||||
document_id: 0,
|
||||
class: ValueClass::Blob(BlobOp::Link {
|
||||
hash: hash.as_ref().clone(),
|
||||
to: BlobLink::Temporary { until: *expires },
|
||||
}),
|
||||
},
|
||||
BlobClass::Linked {
|
||||
account_id,
|
||||
collection,
|
||||
document_id,
|
||||
} => ValueKey {
|
||||
account_id: *account_id,
|
||||
collection: *collection,
|
||||
document_id: *document_id,
|
||||
class: ValueClass::Blob(BlobOp::Link {
|
||||
hash: hash.as_ref().clone(),
|
||||
to: BlobLink::Document,
|
||||
}),
|
||||
},
|
||||
_ => return Ok(false),
|
||||
};
|
||||
|
||||
self.key_exists(key).await
|
||||
}
|
||||
|
||||
pub async fn purge_blobs_all_shards(&self, blob_store: BlobStore) -> trc::Result<()> {
|
||||
for shard_index in 0u8..=255 {
|
||||
self.purge_blobs(blob_store.clone(), shard_index).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn purge_blobs(&self, blob_store: BlobStore, shard_index: u8) -> trc::Result<()> {
|
||||
let mut total_active = 0;
|
||||
let mut total_deleted = 0;
|
||||
let started = Instant::now();
|
||||
|
||||
// Validate linked blobs
|
||||
let mut from_hash = BlobHash::default();
|
||||
let mut to_hash = BlobHash::new_max();
|
||||
from_hash.0[0] = shard_index;
|
||||
to_hash.0[0] = shard_index;
|
||||
let from_key = ValueKey {
|
||||
account_id: 0,
|
||||
collection: 0,
|
||||
document_id: 0,
|
||||
class: ValueClass::Blob(BlobOp::Commit { hash: from_hash }),
|
||||
};
|
||||
let to_key = ValueKey {
|
||||
account_id: u32::MAX,
|
||||
collection: u8::MAX,
|
||||
document_id: u32::MAX,
|
||||
class: ValueClass::Blob(BlobOp::Link {
|
||||
hash: to_hash,
|
||||
to: BlobLink::Document,
|
||||
}),
|
||||
};
|
||||
|
||||
let mut state = BlobPurgeState::new();
|
||||
self.iterate(
|
||||
IterateParams::new(from_key, to_key).ascending(),
|
||||
|key, value| {
|
||||
let hash =
|
||||
BlobHash::try_from_hash_slice(key.get(0..BLOB_HASH_LEN).ok_or_else(|| {
|
||||
trc::Error::corrupted_key(key, value.into(), trc::location!())
|
||||
})?)
|
||||
.unwrap();
|
||||
|
||||
state.update_hash(hash);
|
||||
state.process_key(key, value)?;
|
||||
|
||||
Ok(true)
|
||||
},
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
|
||||
state.finalize(BlobHash::default());
|
||||
|
||||
// Delete expired or unlinked blobs
|
||||
for (_, op) in &state.delete_keys {
|
||||
if let BlobOp::Commit { hash } = op {
|
||||
blob_store
|
||||
.delete_blob(hash.as_ref())
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
}
|
||||
}
|
||||
|
||||
// Delete hashes
|
||||
let mut batch = BatchBuilder::new();
|
||||
for (account_id, op) in state.delete_keys {
|
||||
if batch.is_large_batch() {
|
||||
self.write(batch.build_all())
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
batch = BatchBuilder::new();
|
||||
}
|
||||
|
||||
if let Some(account_id) = account_id {
|
||||
batch.with_account_id(account_id);
|
||||
}
|
||||
|
||||
batch.any_op(Operation::Value {
|
||||
class: ValueClass::Blob(op),
|
||||
op: ValueOp::Clear,
|
||||
});
|
||||
}
|
||||
for (account_id, object_id) in state.delete_registry {
|
||||
if batch.is_large_batch() {
|
||||
self.write(batch.build_all())
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
batch = BatchBuilder::new();
|
||||
}
|
||||
|
||||
let item_id = object_id.id().id();
|
||||
let object_id = object_id.object().to_id();
|
||||
|
||||
batch
|
||||
.clear(ValueClass::Registry(RegistryClass::Index {
|
||||
index_id: Property::AccountId.to_id(),
|
||||
object_id,
|
||||
item_id,
|
||||
key: (account_id as u64).serialize(),
|
||||
}))
|
||||
.clear(ValueClass::Registry(RegistryClass::Item {
|
||||
object_id,
|
||||
item_id,
|
||||
}));
|
||||
}
|
||||
if !batch.is_empty() {
|
||||
self.write(batch.build_all())
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
}
|
||||
|
||||
total_active += state.total_active - 1; // Exclude default hash
|
||||
total_deleted += state.total_deleted;
|
||||
|
||||
trc::event!(
|
||||
Store(StoreEvent::BlobStorePurged),
|
||||
Id = shard_index as u16,
|
||||
Expires = total_deleted,
|
||||
Total = total_active,
|
||||
Elapsed = started.elapsed()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct BlobPurgeState {
|
||||
last_hash: BlobHash,
|
||||
last_hash_is_linked: bool,
|
||||
delete_keys: Vec<(Option<u32>, BlobOp)>,
|
||||
delete_registry: Vec<(u32, ObjectId)>,
|
||||
now: u64,
|
||||
total_deleted: u64,
|
||||
total_active: u64,
|
||||
}
|
||||
|
||||
impl BlobPurgeState {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
last_hash: BlobHash::default(),
|
||||
last_hash_is_linked: true, // Avoid deleting non-existing last_hash on first iteration
|
||||
delete_keys: Vec::new(),
|
||||
delete_registry: Vec::new(),
|
||||
now: now(),
|
||||
total_deleted: 0,
|
||||
total_active: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_hash(&mut self, hash: BlobHash) {
|
||||
if self.last_hash != hash {
|
||||
self.finalize(hash);
|
||||
self.last_hash_is_linked = false;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn finalize(&mut self, new_hash: BlobHash) {
|
||||
if !self.last_hash_is_linked {
|
||||
self.total_deleted += 1;
|
||||
self.delete_keys.push((
|
||||
None,
|
||||
BlobOp::Commit {
|
||||
hash: std::mem::replace(&mut self.last_hash, new_hash),
|
||||
},
|
||||
));
|
||||
} else {
|
||||
self.total_active += 1;
|
||||
self.last_hash = new_hash;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_key(&mut self, key: &[u8], value: &[u8]) -> trc::Result<()> {
|
||||
const TEMP_LINK: usize = BLOB_HASH_LEN + U32_LEN + U64_LEN;
|
||||
const DOC_LINK: usize = BLOB_HASH_LEN + U64_LEN + 1;
|
||||
const ID_LINK: usize = BLOB_HASH_LEN + U64_LEN;
|
||||
|
||||
match key.len() {
|
||||
BLOB_HASH_LEN => {
|
||||
// Main blob entry
|
||||
Ok(())
|
||||
}
|
||||
TEMP_LINK => {
|
||||
// Temporary link
|
||||
let until = key.deserialize_be_u64(BLOB_HASH_LEN + U32_LEN)?;
|
||||
if until <= self.now {
|
||||
let account_id = key.deserialize_be_u32(BLOB_HASH_LEN)?;
|
||||
self.delete_keys.push((
|
||||
Some(account_id),
|
||||
BlobOp::Link {
|
||||
hash: self.last_hash.clone(),
|
||||
to: BlobLink::Temporary { until },
|
||||
},
|
||||
));
|
||||
if value.len() == U16_LEN + U64_LEN {
|
||||
self.delete_registry
|
||||
.push((account_id, ObjectId::deserialize(value)?));
|
||||
}
|
||||
} else {
|
||||
self.last_hash_is_linked = true;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
DOC_LINK | ID_LINK => {
|
||||
// Document/Id link
|
||||
self.last_hash_is_linked = true;
|
||||
Ok(())
|
||||
}
|
||||
_ => Err(trc::Error::corrupted_key(
|
||||
key,
|
||||
value.into(),
|
||||
trc::location!(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,685 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use super::{
|
||||
AnyKey, BlobOp, InMemoryClass, QueueClass, TaskQueueClass, TelemetryClass, ValueClass,
|
||||
};
|
||||
use crate::{
|
||||
IndexKey, IndexKeyPrefix, Key, LogKey, SUBSPACE_ACL, SUBSPACE_BLOB_LINK, SUBSPACE_COUNTER,
|
||||
SUBSPACE_DELETED_ITEMS, SUBSPACE_DIRECTORY, SUBSPACE_IN_MEMORY_COUNTER,
|
||||
SUBSPACE_IN_MEMORY_VALUE, SUBSPACE_INDEXES, SUBSPACE_LOGS, SUBSPACE_PROPERTY,
|
||||
SUBSPACE_QUEUE_EVENT, SUBSPACE_QUEUE_MESSAGE, SUBSPACE_QUOTA, SUBSPACE_REGISTRY,
|
||||
SUBSPACE_REGISTRY_IDX, SUBSPACE_REGISTRY_PK, SUBSPACE_REPORT_IN, SUBSPACE_REPORT_OUT,
|
||||
SUBSPACE_SEARCH_INDEX, SUBSPACE_SPAM_SAMPLES, SUBSPACE_TASK_QUEUE, SUBSPACE_TELEMETRY_METRIC,
|
||||
SUBSPACE_TELEMETRY_SPAN, U16_LEN, U32_LEN, U64_LEN, ValueKey, WITH_SUBSPACE,
|
||||
write::{
|
||||
BlobLink, IndexPropertyClass, RegistryClass, SearchIndex, SearchIndexId, SearchIndexType,
|
||||
},
|
||||
};
|
||||
use registry::schema::prelude::ObjectType;
|
||||
use std::convert::TryInto;
|
||||
use types::{
|
||||
blob_hash::BLOB_HASH_LEN,
|
||||
collection::{Collection, SyncCollection},
|
||||
field::{Field, MailboxField},
|
||||
};
|
||||
use utils::codec::leb128::Leb128_;
|
||||
|
||||
pub struct KeySerializer {
|
||||
pub buf: Vec<u8>,
|
||||
}
|
||||
|
||||
pub trait KeySerialize {
|
||||
fn serialize(&self, buf: &mut Vec<u8>);
|
||||
}
|
||||
|
||||
pub trait DeserializeBigEndian {
|
||||
fn deserialize_be_u16(&self, index: usize) -> trc::Result<u16>;
|
||||
fn deserialize_be_u32(&self, index: usize) -> trc::Result<u32>;
|
||||
fn deserialize_be_u64(&self, index: usize) -> trc::Result<u64>;
|
||||
}
|
||||
|
||||
impl KeySerializer {
|
||||
pub fn new(capacity: usize) -> Self {
|
||||
Self {
|
||||
buf: Vec::with_capacity(capacity),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write<T: KeySerialize>(mut self, value: T) -> Self {
|
||||
value.serialize(&mut self.buf);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn write_leb128<T: Leb128_>(mut self, value: T) -> Self {
|
||||
T::to_leb128_bytes(value, &mut self.buf);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn finalize(self) -> Vec<u8> {
|
||||
self.buf
|
||||
}
|
||||
}
|
||||
|
||||
impl KeySerialize for u8 {
|
||||
fn serialize(&self, buf: &mut Vec<u8>) {
|
||||
buf.push(*self);
|
||||
}
|
||||
}
|
||||
|
||||
impl KeySerialize for &str {
|
||||
fn serialize(&self, buf: &mut Vec<u8>) {
|
||||
buf.extend_from_slice(self.as_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
impl KeySerialize for &String {
|
||||
fn serialize(&self, buf: &mut Vec<u8>) {
|
||||
buf.extend_from_slice(self.as_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
impl KeySerialize for &[u8] {
|
||||
fn serialize(&self, buf: &mut Vec<u8>) {
|
||||
buf.extend_from_slice(self);
|
||||
}
|
||||
}
|
||||
|
||||
impl KeySerialize for u32 {
|
||||
fn serialize(&self, buf: &mut Vec<u8>) {
|
||||
buf.extend_from_slice(&self.to_be_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
impl KeySerialize for u16 {
|
||||
fn serialize(&self, buf: &mut Vec<u8>) {
|
||||
buf.extend_from_slice(&self.to_be_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
impl KeySerialize for u64 {
|
||||
fn serialize(&self, buf: &mut Vec<u8>) {
|
||||
buf.extend_from_slice(&self.to_be_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
impl DeserializeBigEndian for &[u8] {
|
||||
fn deserialize_be_u16(&self, index: usize) -> trc::Result<u16> {
|
||||
self.get(index..index + U16_LEN)
|
||||
.and_then(|bytes| bytes.try_into().ok())
|
||||
.ok_or_else(|| {
|
||||
trc::StoreEvent::DataCorruption
|
||||
.caused_by(trc::location!())
|
||||
.ctx(trc::Key::Value, *self)
|
||||
})
|
||||
.map(u16::from_be_bytes)
|
||||
}
|
||||
|
||||
fn deserialize_be_u32(&self, index: usize) -> trc::Result<u32> {
|
||||
self.get(index..index + U32_LEN)
|
||||
.and_then(|bytes| bytes.try_into().ok())
|
||||
.ok_or_else(|| {
|
||||
trc::StoreEvent::DataCorruption
|
||||
.caused_by(trc::location!())
|
||||
.ctx(trc::Key::Value, *self)
|
||||
})
|
||||
.map(u32::from_be_bytes)
|
||||
}
|
||||
|
||||
fn deserialize_be_u64(&self, index: usize) -> trc::Result<u64> {
|
||||
self.get(index..index + U64_LEN)
|
||||
.and_then(|bytes| bytes.try_into().ok())
|
||||
.ok_or_else(|| {
|
||||
trc::StoreEvent::DataCorruption
|
||||
.caused_by(trc::location!())
|
||||
.ctx(trc::Key::Value, *self)
|
||||
})
|
||||
.map(u64::from_be_bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<ValueClass>> ValueKey<T> {
|
||||
pub fn with_document_id(self, document_id: u32) -> Self {
|
||||
Self {
|
||||
document_id,
|
||||
..self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ValueKey<ValueClass> {
|
||||
pub fn property(
|
||||
account_id: u32,
|
||||
collection: impl Into<u8>,
|
||||
document_id: u32,
|
||||
field: impl Into<u8>,
|
||||
) -> ValueKey<ValueClass> {
|
||||
ValueKey {
|
||||
account_id,
|
||||
collection: collection.into(),
|
||||
document_id,
|
||||
class: ValueClass::Property(field.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn archive(
|
||||
account_id: u32,
|
||||
collection: impl Into<u8>,
|
||||
document_id: u32,
|
||||
) -> ValueKey<ValueClass> {
|
||||
ValueKey {
|
||||
account_id,
|
||||
collection: collection.into(),
|
||||
document_id,
|
||||
class: ValueClass::Property(Field::ARCHIVE.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Key for IndexKeyPrefix {
|
||||
fn serialize(&self, flags: u32) -> Vec<u8> {
|
||||
{
|
||||
if (flags & WITH_SUBSPACE) != 0 {
|
||||
KeySerializer::new(std::mem::size_of::<IndexKeyPrefix>() + 1)
|
||||
.write(crate::SUBSPACE_INDEXES)
|
||||
} else {
|
||||
KeySerializer::new(std::mem::size_of::<IndexKeyPrefix>())
|
||||
}
|
||||
}
|
||||
.write(self.account_id)
|
||||
.write(self.collection)
|
||||
.write(self.field)
|
||||
.finalize()
|
||||
}
|
||||
|
||||
fn subspace(&self) -> u8 {
|
||||
SUBSPACE_INDEXES
|
||||
}
|
||||
}
|
||||
|
||||
impl IndexKeyPrefix {
|
||||
pub fn len() -> usize {
|
||||
U32_LEN + 2
|
||||
}
|
||||
}
|
||||
|
||||
impl Key for LogKey {
|
||||
fn subspace(&self) -> u8 {
|
||||
SUBSPACE_LOGS
|
||||
}
|
||||
|
||||
fn serialize(&self, flags: u32) -> Vec<u8> {
|
||||
{
|
||||
if (flags & WITH_SUBSPACE) != 0 {
|
||||
KeySerializer::new(std::mem::size_of::<LogKey>() + 1).write(crate::SUBSPACE_LOGS)
|
||||
} else {
|
||||
KeySerializer::new(std::mem::size_of::<LogKey>())
|
||||
}
|
||||
}
|
||||
.write(self.account_id)
|
||||
.write(self.collection)
|
||||
.write(self.change_id)
|
||||
.finalize()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<ValueClass> + Sync + Send + Clone> Key for ValueKey<T> {
|
||||
fn subspace(&self) -> u8 {
|
||||
self.class.as_ref().subspace(self.collection)
|
||||
}
|
||||
|
||||
fn serialize(&self, flags: u32) -> Vec<u8> {
|
||||
self.class
|
||||
.as_ref()
|
||||
.serialize(self.account_id, self.collection, self.document_id, flags)
|
||||
}
|
||||
}
|
||||
|
||||
impl ValueClass {
|
||||
pub fn serialize(
|
||||
&self,
|
||||
account_id: u32,
|
||||
collection: u8,
|
||||
document_id: u32,
|
||||
flags: u32,
|
||||
) -> Vec<u8> {
|
||||
let serializer = if (flags & WITH_SUBSPACE) != 0 {
|
||||
KeySerializer::new(self.serialized_size() + 2).write(self.subspace(collection))
|
||||
} else {
|
||||
KeySerializer::new(self.serialized_size() + 1)
|
||||
};
|
||||
|
||||
match self {
|
||||
ValueClass::Property(property) => serializer
|
||||
.write(account_id)
|
||||
.write(collection)
|
||||
.write(*property)
|
||||
.write(document_id),
|
||||
ValueClass::IndexProperty(property) => match property {
|
||||
IndexPropertyClass::Hash { property, hash } => serializer
|
||||
.write(account_id)
|
||||
.write(collection)
|
||||
.write(*property)
|
||||
.write(hash.as_bytes())
|
||||
.write(document_id),
|
||||
IndexPropertyClass::Integer { property, value } => serializer
|
||||
.write(account_id)
|
||||
.write(collection)
|
||||
.write(*property)
|
||||
.write(*value)
|
||||
.write(document_id),
|
||||
},
|
||||
ValueClass::Acl(grant_account_id) => serializer
|
||||
.write(*grant_account_id)
|
||||
.write(account_id)
|
||||
.write(collection)
|
||||
.write(document_id),
|
||||
ValueClass::TaskQueue(task) => match task {
|
||||
TaskQueueClass::Task { id } => serializer.write(0u64).write(*id),
|
||||
TaskQueueClass::Due { id, due } => serializer.write(*due).write(*id),
|
||||
},
|
||||
ValueClass::Blob(op) => match op {
|
||||
BlobOp::Commit { hash } => serializer.write::<&[u8]>(hash.as_ref()),
|
||||
BlobOp::Link { hash, to } => match to {
|
||||
BlobLink::Id { id } => serializer.write::<&[u8]>(hash.as_ref()).write(*id),
|
||||
BlobLink::Document => serializer
|
||||
.write::<&[u8]>(hash.as_ref())
|
||||
.write(account_id)
|
||||
.write(collection)
|
||||
.write(document_id),
|
||||
BlobLink::Temporary { until } => serializer
|
||||
.write::<&[u8]>(hash.as_ref())
|
||||
.write(account_id)
|
||||
.write(*until),
|
||||
},
|
||||
},
|
||||
ValueClass::InMemory(lookup) => match lookup {
|
||||
InMemoryClass::Key(key) => serializer.write(key.as_slice()),
|
||||
InMemoryClass::Counter(key) => serializer.write(key.as_slice()),
|
||||
},
|
||||
ValueClass::Registry(registry) => match registry {
|
||||
RegistryClass::Item { object_id, item_id } => {
|
||||
serializer.write(*object_id).write(*item_id)
|
||||
}
|
||||
RegistryClass::IndexId { object_id, item_id } => {
|
||||
serializer.write(u16::MAX).write(*object_id).write(*item_id)
|
||||
}
|
||||
RegistryClass::Index {
|
||||
index_id,
|
||||
object_id,
|
||||
item_id,
|
||||
key,
|
||||
} => serializer
|
||||
.write(*object_id)
|
||||
.write(*index_id)
|
||||
.write(key.as_slice())
|
||||
.write(*item_id),
|
||||
RegistryClass::Reference {
|
||||
to_object_id,
|
||||
to_item_id,
|
||||
from_object_id,
|
||||
from_item_id,
|
||||
} => serializer
|
||||
.write(*to_object_id)
|
||||
.write(*to_item_id)
|
||||
.write(*from_object_id)
|
||||
.write(*from_item_id),
|
||||
RegistryClass::PrimaryKey {
|
||||
object_id,
|
||||
index_id,
|
||||
key,
|
||||
} => serializer
|
||||
.write((*object_id).unwrap_or(u16::MAX))
|
||||
.write(*index_id)
|
||||
.write(key.as_slice()),
|
||||
RegistryClass::IdCounter { object_id } => serializer.write(*object_id),
|
||||
},
|
||||
ValueClass::Queue(queue) => match queue {
|
||||
QueueClass::Message(queue_id) => serializer.write(*queue_id),
|
||||
QueueClass::MessageEvent(event) => serializer
|
||||
.write(event.due)
|
||||
.write(event.queue_id)
|
||||
.write(event.queue_name.as_slice()),
|
||||
QueueClass::QuotaCount(key) => serializer.write(0u8).write(key.as_slice()),
|
||||
QueueClass::QuotaSize(key) => serializer.write(1u8).write(key.as_slice()),
|
||||
},
|
||||
ValueClass::Telemetry(telemetry) => match telemetry {
|
||||
TelemetryClass::Span(span_id) => serializer.write(*span_id),
|
||||
TelemetryClass::Metric(metric_id) => serializer.write(*metric_id),
|
||||
},
|
||||
ValueClass::DocumentId => serializer.write(account_id).write(collection),
|
||||
ValueClass::ChangeId => serializer.write(account_id),
|
||||
ValueClass::Quota => serializer.write(account_id).write(u8::MAX),
|
||||
ValueClass::TenantQuota(tenant_id) => serializer.write(*tenant_id).write(u8::MAX - 1),
|
||||
ValueClass::NodeId(node_id) => serializer.write(u32::MAX).write(*node_id),
|
||||
ValueClass::ShareNotification {
|
||||
notification_id,
|
||||
notify_account_id,
|
||||
} => serializer
|
||||
.write(*notify_account_id)
|
||||
.write(u8::from(SyncCollection::ShareNotification))
|
||||
.write(*notification_id),
|
||||
ValueClass::SearchIndex(index) => match &index.typ {
|
||||
SearchIndexType::Term { field, hash } => {
|
||||
let class = index.index.as_u8();
|
||||
match &index.id {
|
||||
SearchIndexId::Account {
|
||||
account_id,
|
||||
document_id,
|
||||
} => serializer
|
||||
.write(class)
|
||||
.write(*account_id)
|
||||
.write(hash.payload())
|
||||
.write(hash.payload_len())
|
||||
.write(*field)
|
||||
.write(*document_id),
|
||||
SearchIndexId::Global { id } => serializer
|
||||
.write(class)
|
||||
.write(hash.payload())
|
||||
.write(hash.payload_len())
|
||||
.write(*field)
|
||||
.write(*id),
|
||||
}
|
||||
}
|
||||
SearchIndexType::Index { field } => {
|
||||
let class = index.index.as_u8() | 1 << 6;
|
||||
match &index.id {
|
||||
SearchIndexId::Account {
|
||||
account_id,
|
||||
document_id,
|
||||
} => serializer
|
||||
.write(class)
|
||||
.write(*account_id)
|
||||
.write(field.field_id)
|
||||
.write(field.data.as_slice())
|
||||
.write(*document_id),
|
||||
SearchIndexId::Global { id } => serializer
|
||||
.write(class)
|
||||
.write(field.field_id)
|
||||
.write(field.data.as_slice())
|
||||
.write(*id),
|
||||
}
|
||||
}
|
||||
SearchIndexType::Document => {
|
||||
let class = index.index.as_u8() | 2 << 6;
|
||||
match &index.id {
|
||||
SearchIndexId::Account {
|
||||
account_id,
|
||||
document_id,
|
||||
} => serializer
|
||||
.write(class)
|
||||
.write(*account_id)
|
||||
.write(*document_id),
|
||||
SearchIndexId::Global { id } => serializer.write(class).write(*id),
|
||||
}
|
||||
}
|
||||
},
|
||||
ValueClass::Any(any) => serializer.write(any.key.as_slice()),
|
||||
}
|
||||
.finalize()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<[u8]> + Sync + Send + Clone> Key for IndexKey<T> {
|
||||
fn subspace(&self) -> u8 {
|
||||
SUBSPACE_INDEXES
|
||||
}
|
||||
|
||||
fn serialize(&self, flags: u32) -> Vec<u8> {
|
||||
let key = self.key.as_ref();
|
||||
{
|
||||
if (flags & WITH_SUBSPACE) != 0 {
|
||||
KeySerializer::new(std::mem::size_of::<IndexKey<T>>() + key.len() + 1)
|
||||
.write(crate::SUBSPACE_INDEXES)
|
||||
} else {
|
||||
KeySerializer::new(std::mem::size_of::<IndexKey<T>>() + key.len())
|
||||
}
|
||||
}
|
||||
.write(self.account_id)
|
||||
.write(self.collection)
|
||||
.write(self.field)
|
||||
.write(key)
|
||||
.write(self.document_id)
|
||||
.finalize()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<[u8]> + Sync + Send + Clone> Key for AnyKey<T> {
|
||||
fn serialize(&self, flags: u32) -> Vec<u8> {
|
||||
let key = self.key.as_ref();
|
||||
if (flags & WITH_SUBSPACE) != 0 {
|
||||
KeySerializer::new(key.len() + 1).write(self.subspace)
|
||||
} else {
|
||||
KeySerializer::new(key.len())
|
||||
}
|
||||
.write(key)
|
||||
.finalize()
|
||||
}
|
||||
|
||||
fn subspace(&self) -> u8 {
|
||||
self.subspace
|
||||
}
|
||||
}
|
||||
|
||||
const MAILBOX_COLLECTION: u8 = Collection::Mailbox as u8;
|
||||
const MAILBOX_COUNTER_FIELD: u8 = MailboxField::UidCounter as u8;
|
||||
const REG_ARCHIVED_ITEM: u16 = ObjectType::ArchivedItem as u16;
|
||||
const REG_SPAM_SAMPLE: u16 = ObjectType::SpamTrainingSample as u16;
|
||||
const REG_ACCOUNT: u16 = ObjectType::Account as u16;
|
||||
const REG_DOMAIN: u16 = ObjectType::Domain as u16;
|
||||
const REG_TENANT: u16 = ObjectType::Tenant as u16;
|
||||
const REG_ROLE: u16 = ObjectType::Role as u16;
|
||||
const REG_OAUTH_CLIENT: u16 = ObjectType::OAuthClient as u16;
|
||||
const REG_MAILING_LIST: u16 = ObjectType::MailingList as u16;
|
||||
const REG_MASKED_EMAIL: u16 = ObjectType::MaskedEmail as u16;
|
||||
const REG_PUBLIC_KEY: u16 = ObjectType::PublicKey as u16;
|
||||
const REG_TRACE: u16 = ObjectType::Trace as u16;
|
||||
const REG_METRIC: u16 = ObjectType::Metric as u16;
|
||||
const REPORT_EXTERNAL_ARF: u16 = ObjectType::ArfExternalReport as u16;
|
||||
const REPORT_EXTERNAL_DMARC: u16 = ObjectType::DmarcExternalReport as u16;
|
||||
const REPORT_EXTERNAL_TLS: u16 = ObjectType::TlsExternalReport as u16;
|
||||
const REPORT_INTERNAL_DMARC: u16 = ObjectType::DmarcInternalReport as u16;
|
||||
const REPORT_INTERNAL_TLS: u16 = ObjectType::TlsInternalReport as u16;
|
||||
|
||||
impl ValueClass {
|
||||
pub fn serialized_size(&self) -> usize {
|
||||
match self {
|
||||
ValueClass::Property(_) => U32_LEN * 2 + 3,
|
||||
ValueClass::IndexProperty(p) => match p {
|
||||
IndexPropertyClass::Hash { hash, .. } => U32_LEN * 2 + 3 + hash.len(),
|
||||
IndexPropertyClass::Integer { .. } => U32_LEN * 2 + 3 + U64_LEN,
|
||||
},
|
||||
ValueClass::Acl(_) => U32_LEN * 3 + 2,
|
||||
ValueClass::InMemory(InMemoryClass::Counter(v) | InMemoryClass::Key(v)) => v.len(),
|
||||
ValueClass::Registry(registry) => match registry {
|
||||
RegistryClass::Item { .. } => U16_LEN + U64_LEN + 1,
|
||||
RegistryClass::Reference { .. } => ((U16_LEN + U64_LEN) * 2) + 1,
|
||||
RegistryClass::Index { key, .. } => (U16_LEN * 2) + U64_LEN + key.len() + 1,
|
||||
RegistryClass::PrimaryKey { key, .. } => (U16_LEN * 2) + key.len() + 1,
|
||||
RegistryClass::IndexId { .. } => U16_LEN + U64_LEN + 1,
|
||||
RegistryClass::IdCounter { .. } => U16_LEN + 1,
|
||||
},
|
||||
ValueClass::Blob(op) => match op {
|
||||
BlobOp::Commit { .. } => BLOB_HASH_LEN,
|
||||
BlobOp::Link { to, .. } => {
|
||||
BLOB_HASH_LEN
|
||||
+ match to {
|
||||
BlobLink::Id { .. } => U64_LEN,
|
||||
BlobLink::Document => U32_LEN * 2 + 1,
|
||||
BlobLink::Temporary { .. } => U32_LEN + U64_LEN,
|
||||
}
|
||||
}
|
||||
},
|
||||
ValueClass::TaskQueue(_) => (U64_LEN * 2) + 1,
|
||||
ValueClass::Queue(q) => match q {
|
||||
QueueClass::Message(_) => U64_LEN,
|
||||
QueueClass::MessageEvent(_) => U64_LEN * 3,
|
||||
QueueClass::QuotaCount(v) | QueueClass::QuotaSize(v) => v.len(),
|
||||
},
|
||||
ValueClass::Telemetry(telemetry) => match telemetry {
|
||||
TelemetryClass::Span(_) | TelemetryClass::Metric(_) => U64_LEN + 1,
|
||||
},
|
||||
ValueClass::DocumentId | ValueClass::Quota | ValueClass::TenantQuota(_) => U32_LEN + 1,
|
||||
ValueClass::ChangeId => U32_LEN,
|
||||
ValueClass::ShareNotification { .. } => U32_LEN + U64_LEN + 1,
|
||||
ValueClass::NodeId(_) => (U16_LEN * 3) + 1,
|
||||
ValueClass::SearchIndex(v) => match &v.typ {
|
||||
SearchIndexType::Term { hash, .. } => U64_LEN + hash.len() + 2,
|
||||
SearchIndexType::Index { field, .. } => 1 + field.data.len() + U64_LEN,
|
||||
SearchIndexType::Document => match &v.id {
|
||||
SearchIndexId::Account { .. } => 1 + U32_LEN * 2,
|
||||
SearchIndexId::Global { .. } => 1 + U64_LEN,
|
||||
},
|
||||
},
|
||||
ValueClass::Any(v) => v.key.len(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn subspace(&self, collection: u8) -> u8 {
|
||||
match self {
|
||||
ValueClass::Property(field) => {
|
||||
if collection == MAILBOX_COLLECTION && *field == MAILBOX_COUNTER_FIELD {
|
||||
SUBSPACE_COUNTER
|
||||
} else {
|
||||
SUBSPACE_PROPERTY
|
||||
}
|
||||
}
|
||||
ValueClass::IndexProperty { .. } => SUBSPACE_PROPERTY,
|
||||
ValueClass::Acl(_) => SUBSPACE_ACL,
|
||||
ValueClass::TaskQueue { .. } => SUBSPACE_TASK_QUEUE,
|
||||
ValueClass::Blob(op) => match op {
|
||||
BlobOp::Commit { .. } | BlobOp::Link { .. } => SUBSPACE_BLOB_LINK,
|
||||
},
|
||||
ValueClass::Registry(registry) => match registry {
|
||||
RegistryClass::Item { object_id, .. } => match *object_id {
|
||||
REG_ACCOUNT | REG_DOMAIN | REG_TENANT | REG_ROLE | REG_OAUTH_CLIENT
|
||||
| REG_MAILING_LIST | REG_MASKED_EMAIL | REG_PUBLIC_KEY => SUBSPACE_DIRECTORY,
|
||||
REG_ARCHIVED_ITEM => SUBSPACE_DELETED_ITEMS,
|
||||
REG_SPAM_SAMPLE => SUBSPACE_SPAM_SAMPLES,
|
||||
REG_TRACE => SUBSPACE_TELEMETRY_SPAN,
|
||||
REG_METRIC => SUBSPACE_TELEMETRY_METRIC,
|
||||
REPORT_EXTERNAL_ARF | REPORT_EXTERNAL_DMARC | REPORT_EXTERNAL_TLS => {
|
||||
SUBSPACE_REPORT_IN
|
||||
}
|
||||
REPORT_INTERNAL_DMARC | REPORT_INTERNAL_TLS => SUBSPACE_REPORT_OUT,
|
||||
_ => SUBSPACE_REGISTRY,
|
||||
},
|
||||
RegistryClass::IndexId { .. } | RegistryClass::Index { .. } => {
|
||||
SUBSPACE_REGISTRY_IDX
|
||||
}
|
||||
RegistryClass::Reference { .. } | RegistryClass::PrimaryKey { .. } => {
|
||||
SUBSPACE_REGISTRY_PK
|
||||
}
|
||||
RegistryClass::IdCounter { .. } => SUBSPACE_COUNTER,
|
||||
},
|
||||
ValueClass::NodeId(_) => SUBSPACE_REGISTRY_PK,
|
||||
ValueClass::InMemory(lookup) => match lookup {
|
||||
InMemoryClass::Key(_) => SUBSPACE_IN_MEMORY_VALUE,
|
||||
InMemoryClass::Counter(_) => SUBSPACE_IN_MEMORY_COUNTER,
|
||||
},
|
||||
ValueClass::Queue(queue) => match queue {
|
||||
QueueClass::Message(_) => SUBSPACE_QUEUE_MESSAGE,
|
||||
QueueClass::MessageEvent(_) => SUBSPACE_QUEUE_EVENT,
|
||||
QueueClass::QuotaCount(_) | QueueClass::QuotaSize(_) => SUBSPACE_QUOTA,
|
||||
},
|
||||
ValueClass::Telemetry(telemetry) => match telemetry {
|
||||
TelemetryClass::Span { .. } => SUBSPACE_TELEMETRY_SPAN,
|
||||
TelemetryClass::Metric { .. } => SUBSPACE_TELEMETRY_METRIC,
|
||||
},
|
||||
ValueClass::DocumentId
|
||||
| ValueClass::ChangeId
|
||||
| ValueClass::Quota
|
||||
| ValueClass::TenantQuota(_) => SUBSPACE_COUNTER,
|
||||
ValueClass::ShareNotification { .. } => SUBSPACE_LOGS,
|
||||
ValueClass::SearchIndex(_) => SUBSPACE_SEARCH_INDEX,
|
||||
ValueClass::Any(any) => any.subspace,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_node_id_key(key: &[u8]) -> bool {
|
||||
key.len() == U32_LEN + U16_LEN && key.starts_with(&u32::MAX.to_be_bytes())
|
||||
}
|
||||
|
||||
impl From<ValueClass> for ValueKey<ValueClass> {
|
||||
fn from(class: ValueClass) -> Self {
|
||||
ValueKey {
|
||||
account_id: 0,
|
||||
collection: 0,
|
||||
document_id: 0,
|
||||
class,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RegistryClass> for ValueKey<ValueClass> {
|
||||
fn from(value: RegistryClass) -> Self {
|
||||
ValueKey {
|
||||
account_id: 0,
|
||||
collection: 0,
|
||||
document_id: 0,
|
||||
class: ValueClass::Registry(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RegistryClass> for ValueClass {
|
||||
fn from(value: RegistryClass) -> Self {
|
||||
ValueClass::Registry(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BlobOp> for ValueClass {
|
||||
fn from(value: BlobOp) -> Self {
|
||||
ValueClass::Blob(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchIndex {
|
||||
pub fn to_u8(&self) -> u8 {
|
||||
match self {
|
||||
SearchIndex::Email => 0,
|
||||
SearchIndex::Calendar => 1,
|
||||
SearchIndex::Contacts => 2,
|
||||
SearchIndex::File => 3,
|
||||
SearchIndex::Tracing => 4,
|
||||
SearchIndex::InMemory => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_from_u8(value: u8) -> Option<Self> {
|
||||
match value {
|
||||
0 => Some(SearchIndex::Email),
|
||||
1 => Some(SearchIndex::Calendar),
|
||||
2 => Some(SearchIndex::Contacts),
|
||||
3 => Some(SearchIndex::File),
|
||||
4 => Some(SearchIndex::Tracing),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &'static str {
|
||||
match self {
|
||||
SearchIndex::Email => "email",
|
||||
SearchIndex::Calendar => "calendar",
|
||||
SearchIndex::Contacts => "contacts",
|
||||
SearchIndex::File => "file",
|
||||
SearchIndex::Tracing => "tracing",
|
||||
SearchIndex::InMemory => "in_memory",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_from_str(value: &str) -> Option<Self> {
|
||||
match value {
|
||||
"email" => Some(SearchIndex::Email),
|
||||
"calendar" => Some(SearchIndex::Calendar),
|
||||
"contacts" => Some(SearchIndex::Contacts),
|
||||
"file" => Some(SearchIndex::File),
|
||||
"tracing" => Some(SearchIndex::Tracing),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::{SerializeInfallible, U64_LEN};
|
||||
use ahash::AHashSet;
|
||||
use types::collection::{SyncCollection, VanishedCollection};
|
||||
use utils::{codec::leb128::Leb128Vec, map::vec_map::VecMap};
|
||||
|
||||
use super::key::KeySerializer;
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub(crate) struct ChangeLogBuilder {
|
||||
pub changes: VecMap<SyncCollection, Changes>,
|
||||
pub vanished: VecMap<VanishedCollection, VanishedItems>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum VanishedItem {
|
||||
Name(String),
|
||||
Id(u64),
|
||||
IdPair(u32, u32),
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub(crate) struct VanishedItems(Vec<VanishedItem>);
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Changes {
|
||||
pub item_inserts: AHashSet<u64>,
|
||||
pub item_updates: AHashSet<u64>,
|
||||
pub item_deletes: AHashSet<u64>,
|
||||
|
||||
pub container_inserts: AHashSet<u32>,
|
||||
pub container_updates: AHashSet<u32>,
|
||||
pub container_deletes: AHashSet<u32>,
|
||||
pub container_property_changes: AHashSet<u32>,
|
||||
}
|
||||
|
||||
impl ChangeLogBuilder {
|
||||
pub fn log_container_insert(&mut self, collection: SyncCollection, document_id: u32) {
|
||||
let changes = self.changes.get_mut_or_insert(collection);
|
||||
if changes.container_deletes.remove(&document_id) {
|
||||
changes.container_updates.insert(document_id);
|
||||
} else {
|
||||
changes.container_inserts.insert(document_id);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn log_item_insert(
|
||||
&mut self,
|
||||
collection: SyncCollection,
|
||||
prefix: Option<u32>,
|
||||
document_id: u32,
|
||||
) {
|
||||
let id = build_id(prefix, document_id);
|
||||
let changes = self.changes.get_mut_or_insert(collection);
|
||||
if changes.item_deletes.remove(&id) {
|
||||
changes.item_updates.insert(id);
|
||||
} else {
|
||||
changes.item_inserts.insert(id);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn log_container_update(&mut self, collection: SyncCollection, document_id: u32) {
|
||||
self.changes
|
||||
.get_mut_or_insert(collection)
|
||||
.container_updates
|
||||
.insert(document_id);
|
||||
}
|
||||
|
||||
pub fn log_container_property_update(&mut self, collection: SyncCollection, document_id: u32) {
|
||||
self.changes
|
||||
.get_mut_or_insert(collection)
|
||||
.container_property_changes
|
||||
.insert(document_id);
|
||||
}
|
||||
|
||||
pub fn log_item_update(
|
||||
&mut self,
|
||||
collection: SyncCollection,
|
||||
prefix: Option<u32>,
|
||||
document_id: u32,
|
||||
) {
|
||||
self.changes
|
||||
.get_mut_or_insert(collection)
|
||||
.item_updates
|
||||
.insert(build_id(prefix, document_id));
|
||||
}
|
||||
|
||||
pub fn log_container_delete(&mut self, collection: SyncCollection, document_id: u32) {
|
||||
let changes = self.changes.get_mut_or_insert(collection);
|
||||
let id = document_id;
|
||||
changes.container_updates.remove(&id);
|
||||
changes.container_property_changes.remove(&id);
|
||||
changes.container_deletes.insert(id);
|
||||
}
|
||||
|
||||
pub fn log_item_delete(
|
||||
&mut self,
|
||||
collection: SyncCollection,
|
||||
prefix: Option<u32>,
|
||||
document_id: u32,
|
||||
) {
|
||||
let changes = self.changes.get_mut_or_insert(collection);
|
||||
let id = build_id(prefix, document_id);
|
||||
changes.item_updates.remove(&id);
|
||||
changes.item_deletes.insert(id);
|
||||
}
|
||||
|
||||
pub fn log_vanished_item(
|
||||
&mut self,
|
||||
collection: VanishedCollection,
|
||||
item: impl Into<VanishedItem>,
|
||||
) {
|
||||
self.vanished
|
||||
.get_mut_or_insert(collection)
|
||||
.0
|
||||
.push(item.into());
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn build_id(prefix: Option<u32>, document_id: u32) -> u64 {
|
||||
if let Some(prefix) = prefix {
|
||||
((prefix as u64) << 32) | document_id as u64
|
||||
} else {
|
||||
document_id as u64
|
||||
}
|
||||
}
|
||||
|
||||
impl Changes {
|
||||
pub fn has_container_changes(&self) -> bool {
|
||||
!self.container_inserts.is_empty()
|
||||
|| !self.container_updates.is_empty()
|
||||
|| !self.container_property_changes.is_empty()
|
||||
|| !self.container_deletes.is_empty()
|
||||
}
|
||||
|
||||
pub fn has_item_changes(&self) -> bool {
|
||||
!self.item_inserts.is_empty()
|
||||
|| !self.item_updates.is_empty()
|
||||
|| !self.item_deletes.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl SerializeInfallible for Changes {
|
||||
fn serialize(&self) -> Vec<u8> {
|
||||
let mut buf = Vec::with_capacity(
|
||||
1 + (self.item_inserts.len()
|
||||
+ self.item_updates.len()
|
||||
+ self.item_deletes.len()
|
||||
+ self.container_inserts.len()
|
||||
+ self.container_updates.len()
|
||||
+ self.container_property_changes.len()
|
||||
+ self.container_deletes.len()
|
||||
+ 4)
|
||||
* std::mem::size_of::<usize>(),
|
||||
);
|
||||
|
||||
buf.push_leb128(self.container_inserts.len());
|
||||
buf.push_leb128(self.container_updates.len());
|
||||
buf.push_leb128(self.container_property_changes.len());
|
||||
buf.push_leb128(self.container_deletes.len());
|
||||
buf.push_leb128(self.item_inserts.len());
|
||||
buf.push_leb128(self.item_updates.len());
|
||||
buf.push_leb128(self.item_deletes.len());
|
||||
|
||||
for list in [
|
||||
&self.container_inserts,
|
||||
&self.container_updates,
|
||||
&self.container_property_changes,
|
||||
&self.container_deletes,
|
||||
] {
|
||||
for id in list {
|
||||
buf.push_leb128(*id);
|
||||
}
|
||||
}
|
||||
for list in [&self.item_inserts, &self.item_updates, &self.item_deletes] {
|
||||
for id in list {
|
||||
buf.push_leb128(*id);
|
||||
}
|
||||
}
|
||||
|
||||
buf
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for VanishedItem {
|
||||
fn from(value: String) -> Self {
|
||||
VanishedItem::Name(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for VanishedItem {
|
||||
fn from(value: u64) -> Self {
|
||||
VanishedItem::Id(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(u32, u32)> for VanishedItem {
|
||||
fn from(value: (u32, u32)) -> Self {
|
||||
VanishedItem::Id((value.0 as u64) << 32 | value.1 as u64)
|
||||
}
|
||||
}
|
||||
|
||||
impl VanishedItem {
|
||||
pub fn serialized_size(&self) -> usize {
|
||||
match self {
|
||||
VanishedItem::Name(name) => name.len() + 1,
|
||||
VanishedItem::Id(_) | VanishedItem::IdPair(..) => U64_LEN,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SerializeInfallible for VanishedItems {
|
||||
fn serialize(&self) -> Vec<u8> {
|
||||
let mut buf = KeySerializer::new(64);
|
||||
|
||||
for item in &self.0 {
|
||||
buf = match item {
|
||||
VanishedItem::Name(name) => buf.write(name.as_bytes()).write(0u8),
|
||||
VanishedItem::Id(id) => buf.write(id.to_be_bytes().as_slice()),
|
||||
VanishedItem::IdPair(a, b) => buf
|
||||
.write(a.to_be_bytes().as_slice())
|
||||
.write(b.to_be_bytes().as_slice()),
|
||||
};
|
||||
}
|
||||
|
||||
buf.finalize()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,700 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use self::assert::AssertValue;
|
||||
use crate::backend::MAX_TOKEN_LENGTH;
|
||||
use log::ChangeLogBuilder;
|
||||
use nlp::tokenizers::word::WordTokenizer;
|
||||
use rkyv::util::AlignedVec;
|
||||
use std::{collections::HashSet, hash::Hash, time::SystemTime};
|
||||
use types::{
|
||||
blob_hash::BlobHash,
|
||||
collection::{Collection, SyncCollection, VanishedCollection},
|
||||
field::{
|
||||
CalendarEventField, CalendarNotificationField, ContactField, EmailField,
|
||||
EmailSubmissionField, Field, MailboxField, PrincipalField, SieveField,
|
||||
},
|
||||
};
|
||||
use utils::{
|
||||
cheeky_hash::CheekyHash,
|
||||
map::{bitmap::Bitmap, vec_map::VecMap},
|
||||
};
|
||||
|
||||
pub mod assert;
|
||||
pub mod batch;
|
||||
pub mod bitpack;
|
||||
pub mod blob;
|
||||
pub mod key;
|
||||
pub mod log;
|
||||
pub mod serialize;
|
||||
|
||||
pub(crate) const ARCHIVE_ALIGNMENT: usize = 16;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Archive<T> {
|
||||
pub inner: T,
|
||||
pub version: ArchiveVersion,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum ArchiveVersion {
|
||||
Versioned { change_id: u64, hash: u32 },
|
||||
Hashed { hash: u32 },
|
||||
Unversioned,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum AlignedBytes {
|
||||
Aligned(AlignedVec<ARCHIVE_ALIGNMENT>),
|
||||
Vec(Vec<u8>),
|
||||
}
|
||||
|
||||
pub struct Archiver<T>
|
||||
where
|
||||
T: rkyv::Archive
|
||||
+ for<'a> rkyv::Serialize<
|
||||
rkyv::api::high::HighSerializer<
|
||||
rkyv::util::AlignedVec,
|
||||
rkyv::ser::allocator::ArenaHandle<'a>,
|
||||
rkyv::rancor::Error,
|
||||
>,
|
||||
>,
|
||||
{
|
||||
pub inner: T,
|
||||
pub flags: u8,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct AssignedIds {
|
||||
pub ids: Vec<AssignedId>,
|
||||
current_change_id: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AssignedId {
|
||||
Counter(i64),
|
||||
ChangeId(ChangeId),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct ChangeId {
|
||||
pub account_id: u32,
|
||||
pub change_id: u64,
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
feature = "rocks",
|
||||
feature = "postgres",
|
||||
feature = "mysql",
|
||||
feature = "foundation"
|
||||
))]
|
||||
pub(crate) use commit_limits::{MAX_COMMIT_ATTEMPTS, MAX_COMMIT_TIME};
|
||||
|
||||
#[cfg(any(
|
||||
feature = "rocks",
|
||||
feature = "postgres",
|
||||
feature = "mysql",
|
||||
feature = "foundation"
|
||||
))]
|
||||
mod commit_limits {
|
||||
use std::time::Duration;
|
||||
|
||||
#[cfg(not(feature = "test_mode"))]
|
||||
pub(crate) const MAX_COMMIT_ATTEMPTS: u32 = 10;
|
||||
#[cfg(not(feature = "test_mode"))]
|
||||
pub(crate) const MAX_COMMIT_TIME: Duration = Duration::from_secs(10);
|
||||
|
||||
#[cfg(feature = "test_mode")]
|
||||
pub(crate) const MAX_COMMIT_ATTEMPTS: u32 = 1000;
|
||||
#[cfg(feature = "test_mode")]
|
||||
pub(crate) const MAX_COMMIT_TIME: Duration = Duration::from_secs(3600);
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Batch<'x> {
|
||||
pub(crate) changes: &'x VecMap<u32, ChangedCollection>,
|
||||
pub(crate) ops: &'x mut [Operation],
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BatchBuilder {
|
||||
current_account_id: Option<u32>,
|
||||
current_collection: Option<Collection>,
|
||||
current_document_id: Option<u32>,
|
||||
changes: VecMap<u32, ChangeLogBuilder>,
|
||||
changed_collections: VecMap<u32, ChangedCollection>,
|
||||
has_assertions: bool,
|
||||
batch_size: usize,
|
||||
batch_ops: usize,
|
||||
commit_points: Vec<usize>,
|
||||
ops: Vec<Operation>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ChangedCollection {
|
||||
pub changed_containers: Bitmap<SyncCollection>,
|
||||
pub changed_items: Bitmap<SyncCollection>,
|
||||
pub share_notification_id: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub enum Operation {
|
||||
AccountId {
|
||||
account_id: u32,
|
||||
},
|
||||
Collection {
|
||||
collection: Collection,
|
||||
},
|
||||
DocumentId {
|
||||
document_id: u32,
|
||||
},
|
||||
AssertValue {
|
||||
class: ValueClass,
|
||||
assert_value: AssertValue,
|
||||
},
|
||||
Value {
|
||||
class: ValueClass,
|
||||
op: ValueOp,
|
||||
},
|
||||
Index {
|
||||
field: u8,
|
||||
key: Vec<u8>,
|
||||
set: bool,
|
||||
},
|
||||
Log {
|
||||
collection: LogCollection,
|
||||
set: Vec<u8>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
|
||||
pub enum LogCollection {
|
||||
Sync(SyncCollection),
|
||||
Vanished(VanishedCollection),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
|
||||
pub enum ValueClass {
|
||||
Property(u8),
|
||||
IndexProperty(IndexPropertyClass),
|
||||
Acl(u32),
|
||||
InMemory(InMemoryClass),
|
||||
TaskQueue(TaskQueueClass),
|
||||
Blob(BlobOp),
|
||||
Registry(RegistryClass),
|
||||
Queue(QueueClass),
|
||||
Telemetry(TelemetryClass),
|
||||
SearchIndex(SearchIndexClass),
|
||||
Any(AnyClass),
|
||||
ShareNotification {
|
||||
notification_id: u64,
|
||||
notify_account_id: u32,
|
||||
},
|
||||
DocumentId,
|
||||
ChangeId,
|
||||
Quota,
|
||||
TenantQuota(u32),
|
||||
NodeId(u16),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
|
||||
pub enum IndexPropertyClass {
|
||||
Hash { property: u8, hash: CheekyHash },
|
||||
Integer { property: u8, value: u64 },
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
|
||||
pub struct SearchIndexClass {
|
||||
pub index: SearchIndex,
|
||||
pub id: SearchIndexId,
|
||||
pub typ: SearchIndexType,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
|
||||
pub enum SearchIndexType {
|
||||
Term { field: u8, hash: CheekyHash },
|
||||
Index { field: SearchIndexField },
|
||||
Document,
|
||||
}
|
||||
|
||||
pub(crate) const SEARCH_INDEX_MAX_FIELD_LEN: usize = 128;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Hash, rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)]
|
||||
pub struct SearchIndexField {
|
||||
pub(crate) field_id: u8,
|
||||
pub(crate) data: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Copy, Eq, Hash)]
|
||||
pub enum SearchIndexId {
|
||||
Account { account_id: u32, document_id: u32 },
|
||||
Global { id: u64 },
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
|
||||
pub enum TaskQueueClass {
|
||||
Task { id: u64 },
|
||||
Due { id: u64, due: u64 },
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Copy, Eq, Hash)]
|
||||
pub enum SearchIndex {
|
||||
Email,
|
||||
Calendar,
|
||||
Contacts,
|
||||
File,
|
||||
Tracing,
|
||||
InMemory,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
|
||||
pub struct AnyClass {
|
||||
pub subspace: u8,
|
||||
pub key: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
|
||||
pub enum InMemoryClass {
|
||||
Key(Vec<u8>),
|
||||
Counter(Vec<u8>),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
|
||||
pub enum RegistryClass {
|
||||
Item {
|
||||
object_id: u16,
|
||||
item_id: u64,
|
||||
},
|
||||
Reference {
|
||||
to_object_id: u16,
|
||||
to_item_id: u64,
|
||||
from_object_id: u16,
|
||||
from_item_id: u64,
|
||||
},
|
||||
Index {
|
||||
index_id: u16,
|
||||
object_id: u16,
|
||||
item_id: u64,
|
||||
key: Vec<u8>,
|
||||
},
|
||||
IndexId {
|
||||
object_id: u16,
|
||||
item_id: u64,
|
||||
},
|
||||
PrimaryKey {
|
||||
object_id: Option<u16>,
|
||||
index_id: u16,
|
||||
key: Vec<u8>,
|
||||
},
|
||||
IdCounter {
|
||||
object_id: u16,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
|
||||
pub enum QueueClass {
|
||||
Message(u64),
|
||||
MessageEvent(QueueEvent),
|
||||
QuotaCount(Vec<u8>),
|
||||
QuotaSize(Vec<u8>),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
|
||||
pub enum TelemetryClass {
|
||||
Span(u64),
|
||||
Metric(u64),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
|
||||
pub struct QueueEvent {
|
||||
pub due: u64,
|
||||
pub queue_id: u64,
|
||||
pub queue_name: [u8; 8],
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Default)]
|
||||
pub enum ValueOp {
|
||||
Set(Vec<u8>),
|
||||
SetFnc(SetOperation),
|
||||
MergeFnc(MergeOperation),
|
||||
AtomicAdd(i64),
|
||||
AddAndGet(i64),
|
||||
#[default]
|
||||
Clear,
|
||||
}
|
||||
|
||||
pub enum MergeResult {
|
||||
Update(Vec<u8>),
|
||||
Skip,
|
||||
Delete,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum Param {
|
||||
I64(i64),
|
||||
U64(u64),
|
||||
String(String),
|
||||
Bytes(Vec<u8>),
|
||||
Bool(bool),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
#[repr(transparent)]
|
||||
pub struct Params(Vec<Param>);
|
||||
|
||||
pub type SetFnc = fn(&Params, &AssignedIds) -> trc::Result<Vec<u8>>;
|
||||
pub type MergeFnc = fn(&Params, &AssignedIds, Option<&[u8]>) -> trc::Result<MergeResult>;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MergeOperation {
|
||||
pub(crate) fnc: MergeFnc,
|
||||
pub(crate) params: Params,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SetOperation {
|
||||
pub(crate) fnc: SetFnc,
|
||||
pub(crate) params: Params,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
|
||||
pub enum BlobOp {
|
||||
Commit { hash: BlobHash },
|
||||
Link { hash: BlobHash, to: BlobLink },
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
|
||||
pub enum BlobLink {
|
||||
Id { id: u64 },
|
||||
Document,
|
||||
Temporary { until: u64 },
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
|
||||
pub struct AnyKey<T: AsRef<[u8]>> {
|
||||
pub subspace: u8,
|
||||
pub key: T,
|
||||
}
|
||||
|
||||
pub trait TokenizeText {
|
||||
fn tokenize_into(&self, tokens: &mut HashSet<String>);
|
||||
fn to_tokens(&self) -> HashSet<String>;
|
||||
}
|
||||
|
||||
impl TokenizeText for &str {
|
||||
fn tokenize_into(&self, tokens: &mut HashSet<String>) {
|
||||
for token in WordTokenizer::new(self, MAX_TOKEN_LENGTH) {
|
||||
tokens.insert(token.word.into_owned());
|
||||
}
|
||||
}
|
||||
|
||||
fn to_tokens(&self) -> HashSet<String> {
|
||||
let mut tokens = HashSet::new();
|
||||
self.tokenize_into(&mut tokens);
|
||||
tokens
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IntoOperations {
|
||||
fn build(self, batch: &mut BatchBuilder) -> trc::Result<()>;
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn now() -> u64 {
|
||||
SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.map_or(0, |d| d.as_secs())
|
||||
}
|
||||
|
||||
impl AsRef<ValueClass> for ValueClass {
|
||||
fn as_ref(&self) -> &ValueClass {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl AssignedIds {
|
||||
pub fn push_counter_id(&mut self, id: i64) {
|
||||
self.ids.push(AssignedId::Counter(id));
|
||||
}
|
||||
|
||||
pub fn push_change_id(&mut self, account_id: u32, change_id: u64) {
|
||||
self.ids.push(AssignedId::ChangeId(ChangeId {
|
||||
account_id,
|
||||
change_id,
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn last_change_id(&self, account_id: u32) -> trc::Result<u64> {
|
||||
self.ids
|
||||
.iter()
|
||||
.filter_map(|id| match id {
|
||||
AssignedId::ChangeId(change_id) if change_id.account_id == account_id => {
|
||||
Some(change_id.change_id)
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.next_back()
|
||||
.ok_or_else(|| {
|
||||
trc::StoreEvent::UnexpectedError
|
||||
.caused_by(trc::location!())
|
||||
.ctx(trc::Key::Reason, "No change ids were created")
|
||||
})
|
||||
}
|
||||
|
||||
pub fn current_change_id(&self) -> trc::Result<u64> {
|
||||
self.current_change_id.ok_or_else(|| {
|
||||
trc::StoreEvent::UnexpectedError
|
||||
.caused_by(trc::location!())
|
||||
.ctx(trc::Key::Reason, "No current change id is set")
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn set_current_change_id(&mut self, account_id: u32) -> trc::Result<u64> {
|
||||
let change_id = self.last_change_id(account_id)?;
|
||||
self.current_change_id = Some(change_id);
|
||||
Ok(change_id)
|
||||
}
|
||||
|
||||
pub fn last_counter_id(&self) -> trc::Result<i64> {
|
||||
self.ids
|
||||
.iter()
|
||||
.filter_map(|id| match id {
|
||||
AssignedId::Counter(counter_id) => Some(*counter_id),
|
||||
_ => None,
|
||||
})
|
||||
.next_back()
|
||||
.ok_or_else(|| {
|
||||
trc::StoreEvent::UnexpectedError
|
||||
.caused_by(trc::location!())
|
||||
.ctx(trc::Key::Reason, "No counter ids were created")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<[u8]>> AsRef<[u8]> for Archive<T> {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.inner.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl ArchiveVersion {
|
||||
pub fn hash(&self) -> Option<u32> {
|
||||
match self {
|
||||
ArchiveVersion::Versioned { hash, .. } => Some(*hash),
|
||||
ArchiveVersion::Hashed { hash } => Some(*hash),
|
||||
ArchiveVersion::Unversioned => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn change_id(&self) -> Option<u64> {
|
||||
match self {
|
||||
ArchiveVersion::Versioned { change_id, .. } => Some(*change_id),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LogCollection> for u8 {
|
||||
fn from(value: LogCollection) -> Self {
|
||||
match value {
|
||||
LogCollection::Sync(col) => col as u8,
|
||||
LogCollection::Vanished(col) => col as u8,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ContactField> for ValueClass {
|
||||
fn from(value: ContactField) -> Self {
|
||||
ValueClass::Property(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CalendarEventField> for ValueClass {
|
||||
fn from(value: CalendarEventField) -> Self {
|
||||
ValueClass::Property(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CalendarNotificationField> for ValueClass {
|
||||
fn from(value: CalendarNotificationField) -> Self {
|
||||
ValueClass::Property(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EmailField> for ValueClass {
|
||||
fn from(value: EmailField) -> Self {
|
||||
ValueClass::Property(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MailboxField> for ValueClass {
|
||||
fn from(value: MailboxField) -> Self {
|
||||
ValueClass::Property(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PrincipalField> for ValueClass {
|
||||
fn from(value: PrincipalField) -> Self {
|
||||
ValueClass::Property(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SieveField> for ValueClass {
|
||||
fn from(value: SieveField) -> Self {
|
||||
ValueClass::Property(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EmailSubmissionField> for ValueClass {
|
||||
fn from(value: EmailSubmissionField) -> Self {
|
||||
ValueClass::Property(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Field> for ValueClass {
|
||||
fn from(value: Field) -> Self {
|
||||
ValueClass::Property(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for MergeOperation {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.params == other.params
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for MergeOperation {}
|
||||
|
||||
impl PartialEq for SetOperation {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.params == other.params
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for SetOperation {}
|
||||
|
||||
impl Hash for MergeOperation {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.params.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for SetOperation {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.params.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl SetOperation {
|
||||
pub fn params(&self) -> &Params {
|
||||
&self.params
|
||||
}
|
||||
}
|
||||
|
||||
impl MergeOperation {
|
||||
pub fn params(&self) -> &Params {
|
||||
&self.params
|
||||
}
|
||||
}
|
||||
|
||||
impl Params {
|
||||
pub fn with_capacity(capacity: usize) -> Self {
|
||||
Self(Vec::with_capacity(capacity))
|
||||
}
|
||||
|
||||
pub fn new() -> Self {
|
||||
Self(Vec::new())
|
||||
}
|
||||
|
||||
pub fn with_i64(mut self, value: i64) -> Self {
|
||||
self.0.push(Param::I64(value));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_u64(mut self, value: u64) -> Self {
|
||||
self.0.push(Param::U64(value));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_string(mut self, value: String) -> Self {
|
||||
self.0.push(Param::String(value));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_str(mut self, value: &str) -> Self {
|
||||
self.0.push(Param::String(value.to_string()));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_bytes(mut self, value: Vec<u8>) -> Self {
|
||||
self.0.push(Param::Bytes(value));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_bool(mut self, value: bool) -> Self {
|
||||
self.0.push(Param::Bool(value));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn i64(&self, idx: usize) -> i64 {
|
||||
match &self.0[idx] {
|
||||
Param::I64(v) => *v,
|
||||
_ => panic!("Param at index {} is not an i64", idx),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn u64(&self, idx: usize) -> u64 {
|
||||
match &self.0[idx] {
|
||||
Param::U64(v) => *v,
|
||||
_ => panic!("Param at index {} is not a u64", idx),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn string(&self, idx: usize) -> &str {
|
||||
match &self.0[idx] {
|
||||
Param::String(v) => v.as_str(),
|
||||
_ => panic!("Param at index {} is not a String", idx),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bytes(&self, idx: usize) -> &[u8] {
|
||||
match &self.0[idx] {
|
||||
Param::Bytes(v) => v.as_slice(),
|
||||
_ => panic!("Param at index {} is not Bytes", idx),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bool(&self, idx: usize) -> bool {
|
||||
match &self.0[idx] {
|
||||
Param::Bool(v) => *v,
|
||||
_ => panic!("Param at index {} is not a bool", idx),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
|
||||
pub fn as_slice(&self) -> &[Param] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Params {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[Param]> for Params {
|
||||
fn as_ref(&self) -> &[Param] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,625 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use super::{ARCHIVE_ALIGNMENT, AlignedBytes, Archive, ArchiveVersion, Archiver};
|
||||
use crate::{Deserialize, Serialize, SerializeInfallible, U32_LEN, U64_LEN, Value};
|
||||
use compact_str::format_compact;
|
||||
use rkyv::util::AlignedVec;
|
||||
use roaring::{RoaringBitmap, RoaringTreemap};
|
||||
|
||||
const MAGIC_MARKER: u8 = 1 << 7;
|
||||
const VERSIONED: u8 = 1 << 6;
|
||||
const HASHED: u8 = 1 << 5;
|
||||
const LZ4_COMPRESSED: u8 = 1 << 4;
|
||||
|
||||
const COMPRESS_WATERMARK: usize = 8192;
|
||||
|
||||
fn validate_marker_and_contents(bytes: &[u8]) -> Option<(bool, &[u8], ArchiveVersion)> {
|
||||
let (marker, contents) = bytes
|
||||
.split_last()
|
||||
.filter(|(marker, _)| (**marker & MAGIC_MARKER) != 0)?;
|
||||
let is_uncompressed = (marker & LZ4_COMPRESSED) == 0;
|
||||
if marker & VERSIONED != 0 {
|
||||
let (contents, change_id) = contents
|
||||
.split_at_checked(contents.len() - U64_LEN)
|
||||
.and_then(|(contents, change_id)| {
|
||||
change_id
|
||||
.try_into()
|
||||
.ok()
|
||||
.map(|change_id| (contents, u64::from_be_bytes(change_id)))
|
||||
})?;
|
||||
contents
|
||||
.split_at_checked(contents.len() - U32_LEN)
|
||||
.and_then(|(contents, archive_hash)| {
|
||||
let hash = xxhash_rust::xxh3::xxh3_64(contents) as u32;
|
||||
if hash.to_be_bytes().as_slice() == archive_hash {
|
||||
Some((
|
||||
is_uncompressed,
|
||||
contents,
|
||||
ArchiveVersion::Versioned { change_id, hash },
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
} else if marker & HASHED != 0 {
|
||||
contents
|
||||
.split_at_checked(contents.len() - U32_LEN)
|
||||
.and_then(|(contents, archive_hash)| {
|
||||
let hash = xxhash_rust::xxh3::xxh3_64(contents) as u32;
|
||||
if hash.to_be_bytes().as_slice() == archive_hash {
|
||||
Some((is_uncompressed, contents, ArchiveVersion::Hashed { hash }))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
} else {
|
||||
Some((is_uncompressed, contents, ArchiveVersion::Unversioned))
|
||||
}
|
||||
}
|
||||
|
||||
impl Deserialize for Archive<AlignedBytes> {
|
||||
fn deserialize(bytes: &[u8]) -> trc::Result<Self> {
|
||||
let (is_uncompressed, contents, version) =
|
||||
validate_marker_and_contents(bytes).ok_or_else(|| {
|
||||
trc::StoreEvent::DataCorruption
|
||||
.into_err()
|
||||
.details("Archive integrity compromised")
|
||||
.ctx(trc::Key::Value, bytes)
|
||||
.caused_by(trc::location!())
|
||||
})?;
|
||||
|
||||
if is_uncompressed {
|
||||
let mut bytes = AlignedVec::with_capacity(contents.len());
|
||||
bytes.extend_from_slice(contents);
|
||||
Ok(Archive {
|
||||
version,
|
||||
inner: AlignedBytes::Aligned(bytes),
|
||||
})
|
||||
} else {
|
||||
aligned_lz4_deflate(contents).map(|inner| Archive { version, inner })
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_owned(mut bytes: Vec<u8>) -> trc::Result<Self> {
|
||||
let (is_uncompressed, contents, version) = validate_marker_and_contents(&bytes)
|
||||
.ok_or_else(|| {
|
||||
trc::StoreEvent::DataCorruption
|
||||
.into_err()
|
||||
.details("Archive integrity compromised")
|
||||
.ctx(trc::Key::Value, bytes.as_slice())
|
||||
.caused_by(trc::location!())
|
||||
})?;
|
||||
|
||||
if is_uncompressed {
|
||||
bytes.truncate(contents.len());
|
||||
if bytes.as_ptr().addr() & (ARCHIVE_ALIGNMENT - 1) == 0 {
|
||||
Ok(Archive {
|
||||
version,
|
||||
inner: AlignedBytes::Vec(bytes),
|
||||
})
|
||||
} else {
|
||||
let mut aligned = AlignedVec::with_capacity(bytes.len());
|
||||
aligned.extend_from_slice(&bytes);
|
||||
Ok(Archive {
|
||||
version,
|
||||
inner: AlignedBytes::Aligned(aligned),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
aligned_lz4_deflate(contents).map(|inner| Archive { version, inner })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn aligned_lz4_deflate(archive: &[u8]) -> trc::Result<AlignedBytes> {
|
||||
lz4_flex::block::uncompressed_size(archive)
|
||||
.and_then(|(uncompressed_size, archive)| {
|
||||
let mut bytes = AlignedVec::with_capacity(uncompressed_size);
|
||||
unsafe {
|
||||
// SAFETY: `new_len` is equal to `capacity` and vector is initialized by lz4_flex.
|
||||
bytes.set_len(uncompressed_size);
|
||||
}
|
||||
lz4_flex::decompress_into(archive, &mut bytes)?;
|
||||
Ok(AlignedBytes::Aligned(bytes))
|
||||
})
|
||||
.map_err(|err| {
|
||||
trc::StoreEvent::DecompressError
|
||||
.ctx(trc::Key::Value, archive)
|
||||
.caused_by(trc::location!())
|
||||
.reason(err)
|
||||
})
|
||||
}
|
||||
|
||||
impl<T> Serialize for Archiver<T>
|
||||
where
|
||||
T: rkyv::Archive
|
||||
+ for<'a> rkyv::Serialize<
|
||||
rkyv::api::high::HighSerializer<
|
||||
rkyv::util::AlignedVec,
|
||||
rkyv::ser::allocator::ArenaHandle<'a>,
|
||||
rkyv::rancor::Error,
|
||||
>,
|
||||
>,
|
||||
{
|
||||
fn serialize(&self) -> trc::Result<Vec<u8>> {
|
||||
rkyv::to_bytes::<rkyv::rancor::Error>(&self.inner)
|
||||
.map_err(|err| {
|
||||
trc::StoreEvent::DeserializeError
|
||||
.caused_by(trc::location!())
|
||||
.reason(err)
|
||||
})
|
||||
.map(|input| {
|
||||
let input = input.as_ref();
|
||||
let input_len = input.len();
|
||||
let version_offset = ((self.flags & VERSIONED != 0) as usize) * U64_LEN;
|
||||
let mut bytes = if input_len > COMPRESS_WATERMARK {
|
||||
let mut bytes = vec![
|
||||
self.flags | LZ4_COMPRESSED;
|
||||
lz4_flex::block::get_maximum_output_size(input_len)
|
||||
+ (U32_LEN * 2)
|
||||
+ version_offset
|
||||
+ 1
|
||||
];
|
||||
|
||||
// Compress the data
|
||||
let compressed_len =
|
||||
lz4_flex::compress_into(input, &mut bytes[U32_LEN..]).unwrap();
|
||||
|
||||
if compressed_len < input_len {
|
||||
// Prepend the length of the uncompressed data
|
||||
bytes[..U32_LEN].copy_from_slice(&(input_len as u32).to_le_bytes());
|
||||
|
||||
if self.flags & HASHED != 0 {
|
||||
// Hash the compressed data including the length
|
||||
let hash =
|
||||
xxhash_rust::xxh3::xxh3_64(&bytes[..compressed_len + U32_LEN])
|
||||
as u32;
|
||||
|
||||
// Add the hash
|
||||
bytes[compressed_len + U32_LEN..compressed_len + (U32_LEN * 2)]
|
||||
.copy_from_slice(&hash.to_be_bytes());
|
||||
|
||||
// Truncate to the actual size
|
||||
bytes.truncate(compressed_len + (U32_LEN * 2) + version_offset + 1);
|
||||
} else {
|
||||
// Truncate to the actual size
|
||||
bytes.truncate(compressed_len + U32_LEN + 1);
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
bytes.clear();
|
||||
bytes
|
||||
} else {
|
||||
Vec::with_capacity(input_len + U32_LEN + version_offset + 1)
|
||||
};
|
||||
|
||||
bytes.extend_from_slice(input);
|
||||
if self.flags & HASHED != 0 {
|
||||
bytes.extend_from_slice(
|
||||
&(xxhash_rust::xxh3::xxh3_64(input) as u32).to_be_bytes(),
|
||||
);
|
||||
}
|
||||
if version_offset != 0 {
|
||||
bytes.extend_from_slice(0u64.to_be_bytes().as_slice());
|
||||
}
|
||||
bytes.push(self.flags);
|
||||
bytes
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Archive<AlignedBytes> {
|
||||
#[inline]
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
match &self.inner {
|
||||
AlignedBytes::Vec(bytes) => bytes.as_slice(),
|
||||
AlignedBytes::Aligned(bytes) => bytes.as_slice(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unarchive<T>(&self) -> trc::Result<&<T as rkyv::Archive>::Archived>
|
||||
where
|
||||
T: rkyv::Archive,
|
||||
T::Archived: for<'a> rkyv::bytecheck::CheckBytes<
|
||||
rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>,
|
||||
> + rkyv::Deserialize<T, rkyv::api::high::HighDeserializer<rkyv::rancor::Error>>,
|
||||
{
|
||||
let bytes = self.as_bytes();
|
||||
if self.version != ArchiveVersion::Unversioned {
|
||||
if bytes.len() >= std::mem::size_of::<T::Archived>() {
|
||||
// SAFETY: Trusted input with integrity hash
|
||||
Ok(unsafe { rkyv::access_unchecked::<T::Archived>(bytes) })
|
||||
} else {
|
||||
Err(trc::StoreEvent::DataCorruption
|
||||
.into_err()
|
||||
.details(format_compact!(
|
||||
"Archive size mismatch, expected {} bytes but got {} bytes.",
|
||||
std::mem::size_of::<T::Archived>(),
|
||||
bytes.len()
|
||||
))
|
||||
.ctx(trc::Key::Value, bytes)
|
||||
.caused_by(trc::location!()))
|
||||
}
|
||||
} else {
|
||||
rkyv::access::<T::Archived, rkyv::rancor::Error>(bytes).map_err(|err| {
|
||||
trc::StoreEvent::DeserializeError
|
||||
.ctx(trc::Key::Value, self.as_bytes())
|
||||
.details("Archive access failed")
|
||||
.caused_by(trc::location!())
|
||||
.reason(err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unarchive_untrusted<T>(&self) -> trc::Result<&<T as rkyv::Archive>::Archived>
|
||||
where
|
||||
T: rkyv::Archive,
|
||||
T::Archived: for<'a> rkyv::bytecheck::CheckBytes<
|
||||
rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>,
|
||||
> + rkyv::Deserialize<T, rkyv::api::high::HighDeserializer<rkyv::rancor::Error>>,
|
||||
{
|
||||
let bytes = self.as_bytes();
|
||||
if bytes.len() >= std::mem::size_of::<T::Archived>() {
|
||||
rkyv::access::<T::Archived, rkyv::rancor::Error>(bytes).map_err(|err| {
|
||||
trc::StoreEvent::DeserializeError
|
||||
.ctx(trc::Key::Value, self.as_bytes())
|
||||
.details("Archive access failed")
|
||||
.caused_by(trc::location!())
|
||||
.reason(err)
|
||||
})
|
||||
} else {
|
||||
Err(trc::StoreEvent::DataCorruption
|
||||
.into_err()
|
||||
.details(format_compact!(
|
||||
"Archive size mismatch, expected {} bytes but got {} bytes.",
|
||||
std::mem::size_of::<T::Archived>(),
|
||||
bytes.len()
|
||||
))
|
||||
.ctx(trc::Key::Value, bytes)
|
||||
.caused_by(trc::location!()))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deserialize<T>(&self) -> trc::Result<T>
|
||||
where
|
||||
T: rkyv::Archive,
|
||||
T::Archived: for<'a> rkyv::bytecheck::CheckBytes<
|
||||
rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>,
|
||||
> + rkyv::Deserialize<T, rkyv::api::high::HighDeserializer<rkyv::rancor::Error>>,
|
||||
{
|
||||
self.unarchive::<T>().and_then(|input| {
|
||||
rkyv::deserialize(input).map_err(|err| {
|
||||
trc::StoreEvent::DeserializeError
|
||||
.ctx(trc::Key::Value, self.as_bytes())
|
||||
.caused_by(trc::location!())
|
||||
.reason(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn deserialize_untrusted<T>(&self) -> trc::Result<T>
|
||||
where
|
||||
T: rkyv::Archive,
|
||||
T::Archived: for<'a> rkyv::bytecheck::CheckBytes<
|
||||
rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>,
|
||||
> + rkyv::Deserialize<T, rkyv::api::high::HighDeserializer<rkyv::rancor::Error>>,
|
||||
{
|
||||
self.unarchive_untrusted::<T>().and_then(|input| {
|
||||
rkyv::deserialize(input).map_err(|err| {
|
||||
trc::StoreEvent::DeserializeError
|
||||
.ctx(trc::Key::Value, self.as_bytes())
|
||||
.caused_by(trc::location!())
|
||||
.reason(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn to_unarchived<T>(&self) -> trc::Result<Archive<&<T as rkyv::Archive>::Archived>>
|
||||
where
|
||||
T: rkyv::Archive,
|
||||
T::Archived: for<'a> rkyv::bytecheck::CheckBytes<
|
||||
rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>,
|
||||
> + rkyv::Deserialize<T, rkyv::api::high::HighDeserializer<rkyv::rancor::Error>>,
|
||||
{
|
||||
self.unarchive::<T>().map(|inner| Archive {
|
||||
version: self.version,
|
||||
inner,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn into_deserialized<T>(&self) -> trc::Result<Archive<T>>
|
||||
where
|
||||
T: rkyv::Archive,
|
||||
T::Archived: for<'a> rkyv::bytecheck::CheckBytes<
|
||||
rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>,
|
||||
> + rkyv::Deserialize<T, rkyv::api::high::HighDeserializer<rkyv::rancor::Error>>,
|
||||
{
|
||||
self.deserialize::<T>().map(|inner| Archive {
|
||||
version: self.version,
|
||||
inner,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn into_inner(self) -> Vec<u8> {
|
||||
let mut bytes = match self.inner {
|
||||
AlignedBytes::Vec(bytes) => bytes,
|
||||
AlignedBytes::Aligned(bytes) => bytes.to_vec(),
|
||||
};
|
||||
match self.version {
|
||||
ArchiveVersion::Versioned { change_id, hash } => {
|
||||
bytes.extend_from_slice(&hash.to_be_bytes());
|
||||
bytes.extend_from_slice(&change_id.to_be_bytes());
|
||||
bytes.push(MAGIC_MARKER | VERSIONED | HASHED);
|
||||
}
|
||||
ArchiveVersion::Hashed { hash } => {
|
||||
bytes.extend_from_slice(&hash.to_be_bytes());
|
||||
bytes.push(MAGIC_MARKER | HASHED);
|
||||
}
|
||||
ArchiveVersion::Unversioned => {
|
||||
bytes.push(MAGIC_MARKER);
|
||||
}
|
||||
}
|
||||
bytes
|
||||
}
|
||||
|
||||
pub fn extract_hash(bytes: &[u8]) -> Option<u32> {
|
||||
let marker = *bytes.last()?;
|
||||
if marker & VERSIONED != 0 {
|
||||
bytes
|
||||
.get(bytes.len() - U32_LEN - U64_LEN - 1..bytes.len() - U64_LEN - 1)
|
||||
.and_then(|slice| slice.try_into().ok().map(u32::from_be_bytes))
|
||||
} else if marker & HASHED != 0 {
|
||||
bytes
|
||||
.get(bytes.len() - U32_LEN - 1..bytes.len() - 1)
|
||||
.and_then(|slice| slice.try_into().ok().map(u32::from_be_bytes))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Archiver<T>
|
||||
where
|
||||
T: rkyv::Archive
|
||||
+ for<'a> rkyv::Serialize<
|
||||
rkyv::api::high::HighSerializer<
|
||||
rkyv::util::AlignedVec,
|
||||
rkyv::ser::allocator::ArenaHandle<'a>,
|
||||
rkyv::rancor::Error,
|
||||
>,
|
||||
>,
|
||||
{
|
||||
pub fn new(inner: T) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
flags: MAGIC_MARKER | HASHED,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_inner(self) -> T {
|
||||
self.inner
|
||||
}
|
||||
|
||||
pub fn with_version(self) -> Self {
|
||||
Self {
|
||||
inner: self.inner,
|
||||
flags: self.flags | VERSIONED,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn untrusted(self) -> Self {
|
||||
Self {
|
||||
inner: self.inner,
|
||||
flags: MAGIC_MARKER,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn serialize_versioned(self) -> trc::Result<(u64, Vec<u8>)> {
|
||||
self.with_version()
|
||||
.serialize()
|
||||
.map(|bytes| ((bytes.len() - U64_LEN - 1) as u64, bytes))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Archive<&T>
|
||||
where
|
||||
T: rkyv::Portable
|
||||
+ for<'a> rkyv::bytecheck::CheckBytes<rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>>
|
||||
+ Sync
|
||||
+ Send,
|
||||
{
|
||||
pub fn to_deserialized<V>(&self) -> trc::Result<Archive<V>>
|
||||
where
|
||||
T: rkyv::Deserialize<V, rkyv::api::high::HighDeserializer<rkyv::rancor::Error>>,
|
||||
{
|
||||
rkyv::deserialize::<V, rkyv::rancor::Error>(self.inner)
|
||||
.map_err(|err| {
|
||||
trc::StoreEvent::DeserializeError
|
||||
.caused_by(trc::location!())
|
||||
.reason(err)
|
||||
})
|
||||
.map(|inner| Archive {
|
||||
version: self.version,
|
||||
inner,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn deserialize<V>(&self) -> trc::Result<V>
|
||||
where
|
||||
T: rkyv::Deserialize<V, rkyv::api::high::HighDeserializer<rkyv::rancor::Error>>,
|
||||
{
|
||||
rkyv::deserialize::<V, rkyv::rancor::Error>(self.inner).map_err(|err| {
|
||||
trc::StoreEvent::DeserializeError
|
||||
.caused_by(trc::location!())
|
||||
.reason(err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn rkyv_deserialize<T, V>(input: &T) -> trc::Result<V>
|
||||
where
|
||||
T: rkyv::Portable
|
||||
+ for<'a> rkyv::bytecheck::CheckBytes<rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>>
|
||||
+ Sync
|
||||
+ Send
|
||||
+ rkyv::Deserialize<V, rkyv::api::high::HighDeserializer<rkyv::rancor::Error>>,
|
||||
{
|
||||
rkyv::deserialize::<V, rkyv::rancor::Error>(input).map_err(|err| {
|
||||
trc::StoreEvent::DeserializeError
|
||||
.caused_by(trc::location!())
|
||||
.reason(err)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn rkyv_unarchive<T>(input: &[u8]) -> trc::Result<&<T as rkyv::Archive>::Archived>
|
||||
where
|
||||
T: rkyv::Archive,
|
||||
T::Archived: for<'a> rkyv::bytecheck::CheckBytes<rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>>
|
||||
+ rkyv::Deserialize<T, rkyv::api::high::HighDeserializer<rkyv::rancor::Error>>,
|
||||
{
|
||||
rkyv::access::<T::Archived, rkyv::rancor::Error>(input).map_err(|err| {
|
||||
trc::StoreEvent::DataCorruption
|
||||
.caused_by(trc::location!())
|
||||
.ctx(trc::Key::Value, input)
|
||||
.reason(err)
|
||||
})
|
||||
}
|
||||
|
||||
impl SerializeInfallible for u32 {
|
||||
fn serialize(&self) -> Vec<u8> {
|
||||
self.to_be_bytes().to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
impl SerializeInfallible for u64 {
|
||||
fn serialize(&self) -> Vec<u8> {
|
||||
self.to_be_bytes().to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
impl SerializeInfallible for i64 {
|
||||
fn serialize(&self) -> Vec<u8> {
|
||||
self.to_be_bytes().to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
impl SerializeInfallible for u16 {
|
||||
fn serialize(&self) -> Vec<u8> {
|
||||
self.to_be_bytes().to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
impl SerializeInfallible for f64 {
|
||||
fn serialize(&self) -> Vec<u8> {
|
||||
self.to_be_bytes().to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
impl SerializeInfallible for &str {
|
||||
fn serialize(&self) -> Vec<u8> {
|
||||
self.as_bytes().to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deserialize for String {
|
||||
fn deserialize(bytes: &[u8]) -> trc::Result<Self> {
|
||||
Ok(String::from_utf8_lossy(bytes).into_owned())
|
||||
}
|
||||
|
||||
fn deserialize_owned(bytes: Vec<u8>) -> trc::Result<Self> {
|
||||
Ok(String::from_utf8(bytes)
|
||||
.unwrap_or_else(|err| String::from_utf8_lossy(err.as_bytes()).into_owned()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Deserialize for u64 {
|
||||
fn deserialize(bytes: &[u8]) -> trc::Result<Self> {
|
||||
Ok(u64::from_be_bytes(bytes.try_into().map_err(|_| {
|
||||
trc::StoreEvent::DataCorruption.caused_by(trc::location!())
|
||||
})?))
|
||||
}
|
||||
}
|
||||
|
||||
impl Deserialize for i64 {
|
||||
fn deserialize(bytes: &[u8]) -> trc::Result<Self> {
|
||||
Ok(i64::from_be_bytes(bytes.try_into().map_err(|_| {
|
||||
trc::StoreEvent::DataCorruption.caused_by(trc::location!())
|
||||
})?))
|
||||
}
|
||||
}
|
||||
|
||||
impl Deserialize for u32 {
|
||||
fn deserialize(bytes: &[u8]) -> trc::Result<Self> {
|
||||
Ok(u32::from_be_bytes(bytes.try_into().map_err(|_| {
|
||||
trc::StoreEvent::DataCorruption.caused_by(trc::location!())
|
||||
})?))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<Value<'static>> for Archive<T> {
|
||||
fn from(_: Value<'static>) -> Self {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Archive<AlignedBytes> {
|
||||
fn default() -> Self {
|
||||
Archive {
|
||||
version: ArchiveVersion::Unversioned,
|
||||
inner: AlignedBytes::Aligned(AlignedVec::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for RoaringBitmap {
|
||||
fn serialize(&self) -> trc::Result<Vec<u8>> {
|
||||
let mut bytes = Vec::with_capacity(self.serialized_size());
|
||||
self.serialize_into(&mut bytes)
|
||||
.map_err(|err| {
|
||||
trc::StoreEvent::UnexpectedError
|
||||
.caused_by(trc::location!())
|
||||
.reason(err)
|
||||
})
|
||||
.map(|_| bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deserialize for RoaringBitmap {
|
||||
fn deserialize(bytes: &[u8]) -> trc::Result<Self> {
|
||||
RoaringBitmap::deserialize_from(bytes).map_err(|err| {
|
||||
trc::StoreEvent::DeserializeError
|
||||
.caused_by(trc::location!())
|
||||
.reason(err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for RoaringTreemap {
|
||||
fn serialize(&self) -> trc::Result<Vec<u8>> {
|
||||
let mut bytes = Vec::with_capacity(self.serialized_size());
|
||||
self.serialize_into(&mut bytes)
|
||||
.map_err(|err| {
|
||||
trc::StoreEvent::UnexpectedError
|
||||
.caused_by(trc::location!())
|
||||
.reason(err)
|
||||
})
|
||||
.map(|_| bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deserialize for RoaringTreemap {
|
||||
fn deserialize(bytes: &[u8]) -> trc::Result<Self> {
|
||||
RoaringTreemap::deserialize_from(bytes).map_err(|err| {
|
||||
trc::StoreEvent::DeserializeError
|
||||
.caused_by(trc::location!())
|
||||
.reason(err)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user