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,165 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use ahash::AHashSet;
|
||||
use trc::AddContext;
|
||||
use types::collection::Collection;
|
||||
|
||||
use crate::{
|
||||
Deserialize, IterateParams, Store, U32_LEN, ValueKey,
|
||||
write::{BatchBuilder, ValueClass, key::DeserializeBigEndian},
|
||||
};
|
||||
|
||||
pub enum AclQuery {
|
||||
SharedWith {
|
||||
grant_account_id: u32,
|
||||
to_account_id: u32,
|
||||
to_collection: u8,
|
||||
},
|
||||
HasAccess {
|
||||
grant_account_id: u32,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AclItem {
|
||||
pub to_account_id: u32,
|
||||
pub to_collection: Collection,
|
||||
pub to_document_id: u32,
|
||||
pub permissions: u64,
|
||||
}
|
||||
|
||||
impl Store {
|
||||
pub async fn acl_query(&self, query: AclQuery) -> trc::Result<Vec<AclItem>> {
|
||||
let mut results = Vec::new();
|
||||
let (from_key, to_key) = match query {
|
||||
AclQuery::SharedWith {
|
||||
grant_account_id,
|
||||
to_account_id,
|
||||
to_collection,
|
||||
} => {
|
||||
let from_key = ValueKey {
|
||||
account_id: to_account_id,
|
||||
collection: to_collection,
|
||||
document_id: 0,
|
||||
class: ValueClass::Acl(grant_account_id),
|
||||
};
|
||||
let mut to_key = from_key.clone();
|
||||
to_key.document_id = u32::MAX;
|
||||
|
||||
(from_key, to_key)
|
||||
}
|
||||
AclQuery::HasAccess { grant_account_id } => (
|
||||
ValueKey {
|
||||
account_id: 0,
|
||||
collection: 0,
|
||||
document_id: 0,
|
||||
class: ValueClass::Acl(grant_account_id),
|
||||
},
|
||||
ValueKey {
|
||||
account_id: u32::MAX,
|
||||
collection: u8::MAX,
|
||||
document_id: u32::MAX,
|
||||
class: ValueClass::Acl(grant_account_id),
|
||||
},
|
||||
),
|
||||
};
|
||||
|
||||
self.iterate(
|
||||
IterateParams::new(from_key, to_key).ascending(),
|
||||
|key, value| {
|
||||
results.push(AclItem::deserialize(key)?.with_permissions(u64::deserialize(value)?));
|
||||
|
||||
Ok(true)
|
||||
},
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())
|
||||
.map(|_| results)
|
||||
}
|
||||
|
||||
pub async fn acl_revoke_all(&self, account_id: u32) -> trc::Result<AHashSet<u32>> {
|
||||
let from_key = ValueKey {
|
||||
account_id: 0,
|
||||
collection: 0,
|
||||
document_id: 0,
|
||||
class: ValueClass::Acl(0),
|
||||
};
|
||||
let to_key = ValueKey {
|
||||
account_id: u32::MAX,
|
||||
collection: u8::MAX,
|
||||
document_id: u32::MAX,
|
||||
class: ValueClass::Acl(u32::MAX),
|
||||
};
|
||||
|
||||
let mut delete_keys = Vec::new();
|
||||
let mut revoked_accounts = AHashSet::new();
|
||||
self.iterate(
|
||||
IterateParams::new(from_key, to_key).ascending().no_values(),
|
||||
|key, _| {
|
||||
if account_id == key.deserialize_be_u32(U32_LEN)? {
|
||||
let owner_account_id = key.deserialize_be_u32(0)?;
|
||||
revoked_accounts.insert(owner_account_id);
|
||||
delete_keys.push((owner_account_id, AclItem::deserialize(key)?));
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
},
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
|
||||
// Remove permissions
|
||||
let mut batch = BatchBuilder::new();
|
||||
batch.with_account_id(account_id);
|
||||
let mut last_collection = Collection::None;
|
||||
for (revoke_account_id, acl_item) in delete_keys.into_iter() {
|
||||
if batch.is_large_batch() {
|
||||
self.write(batch.build_all())
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
batch = BatchBuilder::new();
|
||||
batch.with_account_id(account_id);
|
||||
last_collection = Collection::None;
|
||||
}
|
||||
if acl_item.to_collection != last_collection {
|
||||
batch.with_collection(acl_item.to_collection);
|
||||
last_collection = acl_item.to_collection;
|
||||
}
|
||||
batch
|
||||
.with_document(acl_item.to_document_id)
|
||||
.acl_revoke(revoke_account_id);
|
||||
}
|
||||
if !batch.is_empty() {
|
||||
self.write(batch.build_all())
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
}
|
||||
|
||||
Ok(revoked_accounts)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deserialize for AclItem {
|
||||
fn deserialize(bytes: &[u8]) -> trc::Result<Self> {
|
||||
Ok(AclItem {
|
||||
to_account_id: bytes.deserialize_be_u32(U32_LEN)?,
|
||||
to_collection: bytes
|
||||
.get(U32_LEN * 2)
|
||||
.map(|b| Collection::from(*b))
|
||||
.ok_or_else(|| trc::StoreEvent::DataCorruption.caused_by(trc::location!()))?,
|
||||
to_document_id: bytes.deserialize_be_u32((U32_LEN * 2) + 1)?,
|
||||
permissions: 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl AclItem {
|
||||
fn with_permissions(mut self, permissions: u64) -> Self {
|
||||
self.permissions = permissions;
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,487 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use trc::AddContext;
|
||||
use types::collection::{SyncCollection, VanishedCollection};
|
||||
use utils::codec::leb128::Leb128Iterator;
|
||||
|
||||
use crate::{
|
||||
IterateParams, LogKey, Store, U32_LEN, U64_LEN,
|
||||
write::{LogCollection, key::DeserializeBigEndian},
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum Change {
|
||||
InsertContainer(u64),
|
||||
UpdateContainer(u64),
|
||||
UpdateContainerProperty(u64),
|
||||
DeleteContainer(u64),
|
||||
InsertItem(u64),
|
||||
UpdateItem(u64),
|
||||
DeleteItem(u64),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Changes {
|
||||
pub changes: Vec<Change>,
|
||||
pub from_change_id: u64,
|
||||
pub to_change_id: u64,
|
||||
pub container_change_id: Option<u64>,
|
||||
pub item_change_id: Option<u64>,
|
||||
pub is_truncated: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Query {
|
||||
All,
|
||||
Since(u64),
|
||||
SinceInclusive(u64),
|
||||
RangeInclusive(u64, u64),
|
||||
}
|
||||
|
||||
pub trait DeserializeVanished: Sized + Sync + Send {
|
||||
fn deserialize_vanished<'x>(bytes: &mut impl Iterator<Item = &'x u8>) -> Option<Self>;
|
||||
}
|
||||
|
||||
impl Default for Changes {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
changes: Vec::with_capacity(10),
|
||||
from_change_id: 0,
|
||||
to_change_id: 0,
|
||||
container_change_id: None,
|
||||
item_change_id: None,
|
||||
is_truncated: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Store {
|
||||
pub async fn changes(
|
||||
&self,
|
||||
account_id: u32,
|
||||
collection_: LogCollection,
|
||||
query: Query,
|
||||
) -> trc::Result<Changes> {
|
||||
let is_share_log = matches!(
|
||||
collection_,
|
||||
LogCollection::Sync(SyncCollection::ShareNotification)
|
||||
);
|
||||
let collection = u8::from(collection_);
|
||||
|
||||
let (is_inclusive, from_change_id, to_change_id) = match query {
|
||||
Query::All => (true, 0, u64::MAX),
|
||||
Query::Since(change_id) => (false, change_id, u64::MAX),
|
||||
Query::SinceInclusive(change_id) => (true, change_id, u64::MAX),
|
||||
Query::RangeInclusive(from_change_id, to_change_id) => {
|
||||
(true, from_change_id, to_change_id)
|
||||
}
|
||||
};
|
||||
let from_key = LogKey {
|
||||
account_id,
|
||||
collection,
|
||||
change_id: from_change_id,
|
||||
};
|
||||
let to_key = LogKey {
|
||||
account_id,
|
||||
collection,
|
||||
change_id: to_change_id,
|
||||
};
|
||||
|
||||
let mut changelog = Changes::default();
|
||||
|
||||
self.iterate(
|
||||
IterateParams::new(from_key, to_key).ascending(),
|
||||
|key, value| {
|
||||
let change_id = key.deserialize_be_u64(key.len() - U64_LEN)?;
|
||||
if is_inclusive || change_id != from_change_id {
|
||||
if value.is_empty() {
|
||||
changelog.is_truncated = true;
|
||||
return Ok(true);
|
||||
}
|
||||
if changelog.changes.is_empty() {
|
||||
changelog.from_change_id = change_id;
|
||||
}
|
||||
changelog.to_change_id = change_id;
|
||||
if !is_share_log {
|
||||
let (has_container_changes, has_item_changes) =
|
||||
changelog.deserialize(value).ok_or_else(|| {
|
||||
trc::Error::corrupted_key(key, value.into(), trc::location!())
|
||||
})?;
|
||||
if has_container_changes {
|
||||
changelog.container_change_id = Some(change_id);
|
||||
}
|
||||
if has_item_changes {
|
||||
changelog.item_change_id = Some(change_id);
|
||||
}
|
||||
} else {
|
||||
changelog.changes.push(Change::InsertItem(change_id));
|
||||
}
|
||||
} else {
|
||||
changelog.from_change_id = change_id;
|
||||
changelog.to_change_id = change_id;
|
||||
}
|
||||
Ok(true)
|
||||
},
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
|
||||
Ok(changelog)
|
||||
}
|
||||
|
||||
pub async fn vanished<T: DeserializeVanished>(
|
||||
&self,
|
||||
account_id: u32,
|
||||
collection: LogCollection,
|
||||
query: Query,
|
||||
) -> trc::Result<Vec<T>> {
|
||||
let collection = u8::from(collection);
|
||||
let (is_inclusive, from_change_id, to_change_id) = match query {
|
||||
Query::All => (true, 0, u64::MAX),
|
||||
Query::Since(change_id) => (false, change_id, u64::MAX),
|
||||
Query::SinceInclusive(change_id) => (true, change_id, u64::MAX),
|
||||
Query::RangeInclusive(from_change_id, to_change_id) => {
|
||||
(true, from_change_id, to_change_id)
|
||||
}
|
||||
};
|
||||
let from_key = LogKey {
|
||||
account_id,
|
||||
collection,
|
||||
change_id: from_change_id,
|
||||
};
|
||||
let to_key = LogKey {
|
||||
account_id,
|
||||
collection,
|
||||
change_id: to_change_id,
|
||||
};
|
||||
|
||||
let mut vanished = Vec::default();
|
||||
|
||||
self.iterate(
|
||||
IterateParams::new(from_key, to_key).ascending(),
|
||||
|key, value| {
|
||||
let change_id = key.deserialize_be_u64(key.len() - U64_LEN)?;
|
||||
if is_inclusive || change_id != from_change_id {
|
||||
let mut iter = value.iter().peekable();
|
||||
|
||||
while iter.peek().is_some() {
|
||||
if let Some(item) = T::deserialize_vanished(&mut iter) {
|
||||
vanished.push(item);
|
||||
} else {
|
||||
return Err(trc::Error::corrupted_key(
|
||||
key,
|
||||
value.into(),
|
||||
trc::location!(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(true)
|
||||
},
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
|
||||
Ok(vanished)
|
||||
}
|
||||
|
||||
pub async fn get_last_change_id(
|
||||
&self,
|
||||
account_id: u32,
|
||||
collection: LogCollection,
|
||||
) -> trc::Result<Option<u64>> {
|
||||
let collection = u8::from(collection);
|
||||
let from_key = LogKey {
|
||||
account_id,
|
||||
collection,
|
||||
change_id: 0,
|
||||
};
|
||||
let to_key = LogKey {
|
||||
account_id,
|
||||
collection,
|
||||
change_id: u64::MAX,
|
||||
};
|
||||
|
||||
let mut last_change_id = None;
|
||||
|
||||
self.iterate(
|
||||
IterateParams::new(from_key, to_key)
|
||||
.descending()
|
||||
.no_values()
|
||||
.only_first(),
|
||||
|key, _| {
|
||||
last_change_id = key.deserialize_be_u64(key.len() - U64_LEN)?.into();
|
||||
Ok(false)
|
||||
},
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
|
||||
Ok(last_change_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<VanishedCollection> for LogCollection {
|
||||
fn from(value: VanishedCollection) -> Self {
|
||||
LogCollection::Vanished(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SyncCollection> for LogCollection {
|
||||
fn from(value: SyncCollection) -> Self {
|
||||
LogCollection::Sync(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Changes {
|
||||
pub fn deserialize(&mut self, bytes: &[u8]) -> Option<(bool, bool)> {
|
||||
let mut bytes_it = bytes.iter();
|
||||
|
||||
let container_inserts: usize = bytes_it.next_leb128()?;
|
||||
let container_updates: usize = bytes_it.next_leb128()?;
|
||||
let container_property_changes: usize = bytes_it.next_leb128()?;
|
||||
let container_deletes: usize = bytes_it.next_leb128()?;
|
||||
|
||||
let item_inserts: usize = bytes_it.next_leb128()?;
|
||||
let item_updates: usize = bytes_it.next_leb128()?;
|
||||
let item_deletes: usize = bytes_it.next_leb128()?;
|
||||
|
||||
let has_container_changes =
|
||||
container_inserts + container_updates + container_property_changes + container_deletes
|
||||
> 0;
|
||||
let has_item_changes = item_inserts + item_updates + item_deletes > 0;
|
||||
|
||||
if container_inserts > 0 {
|
||||
for _ in 0..container_inserts {
|
||||
self.changes
|
||||
.push(Change::InsertContainer(bytes_it.next_leb128()?));
|
||||
}
|
||||
}
|
||||
|
||||
if container_updates > 0 || container_property_changes > 0 {
|
||||
'update_outer: for change_pos in 0..(container_updates + container_property_changes) {
|
||||
let id = bytes_it.next_leb128()?;
|
||||
let mut is_property_change = change_pos >= container_updates;
|
||||
|
||||
for (idx, change) in self.changes.iter().enumerate() {
|
||||
match change {
|
||||
Change::InsertContainer(insert_id) if *insert_id == id => {
|
||||
// Item updated after inserted, no need to count this change.
|
||||
continue 'update_outer;
|
||||
}
|
||||
Change::UpdateContainer(update_id) if *update_id == id => {
|
||||
// Move update to the front
|
||||
is_property_change = false;
|
||||
self.changes.remove(idx);
|
||||
break;
|
||||
}
|
||||
Change::UpdateContainerProperty(update_id) if *update_id == id => {
|
||||
// Move update to the front
|
||||
self.changes.remove(idx);
|
||||
break;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
self.changes.push(if !is_property_change {
|
||||
Change::UpdateContainer(id)
|
||||
} else {
|
||||
Change::UpdateContainerProperty(id)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if container_deletes > 0 {
|
||||
'delete_outer: for _ in 0..container_deletes {
|
||||
let id = bytes_it.next_leb128()?;
|
||||
|
||||
'delete_inner: for (idx, change) in self.changes.iter().enumerate() {
|
||||
match change {
|
||||
Change::InsertContainer(insert_id) if *insert_id == id => {
|
||||
self.changes.remove(idx);
|
||||
continue 'delete_outer;
|
||||
}
|
||||
Change::UpdateContainer(update_id) if *update_id == id => {
|
||||
self.changes.remove(idx);
|
||||
break 'delete_inner;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
self.changes.push(Change::DeleteContainer(id));
|
||||
}
|
||||
}
|
||||
|
||||
// Item changes
|
||||
if item_inserts > 0 {
|
||||
for _ in 0..item_inserts {
|
||||
self.changes
|
||||
.push(Change::InsertItem(bytes_it.next_leb128()?));
|
||||
}
|
||||
}
|
||||
|
||||
if item_updates > 0 {
|
||||
'update_outer: for _ in 0..item_updates {
|
||||
let id = bytes_it.next_leb128()?;
|
||||
|
||||
for (idx, change) in self.changes.iter().enumerate() {
|
||||
match change {
|
||||
Change::InsertItem(insert_id) if *insert_id == id => {
|
||||
// Item updated after inserted, no need to count this change.
|
||||
continue 'update_outer;
|
||||
}
|
||||
Change::UpdateItem(update_id) if *update_id == id => {
|
||||
// Move update to the front
|
||||
self.changes.remove(idx);
|
||||
break;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
self.changes.push(Change::UpdateItem(id));
|
||||
}
|
||||
}
|
||||
|
||||
if item_deletes > 0 {
|
||||
'delete_outer: for _ in 0..item_deletes {
|
||||
let id = bytes_it.next_leb128()?;
|
||||
|
||||
'delete_inner: for (idx, change) in self.changes.iter().enumerate() {
|
||||
match change {
|
||||
Change::InsertItem(insert_id) if *insert_id == id => {
|
||||
self.changes.remove(idx);
|
||||
continue 'delete_outer;
|
||||
}
|
||||
Change::UpdateItem(update_id) if *update_id == id => {
|
||||
self.changes.remove(idx);
|
||||
break 'delete_inner;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
self.changes.push(Change::DeleteItem(id));
|
||||
}
|
||||
}
|
||||
|
||||
Some((has_container_changes, has_item_changes))
|
||||
}
|
||||
}
|
||||
|
||||
impl Changes {
|
||||
pub fn total_container_changes(&self) -> usize {
|
||||
self.changes
|
||||
.iter()
|
||||
.filter(|change| change.is_container_change())
|
||||
.count()
|
||||
}
|
||||
|
||||
pub fn total_item_changes(&self) -> usize {
|
||||
self.changes
|
||||
.iter()
|
||||
.filter(|change| change.is_item_change())
|
||||
.count()
|
||||
}
|
||||
}
|
||||
|
||||
impl Change {
|
||||
pub fn item_id(&self) -> Option<u64> {
|
||||
match self {
|
||||
Change::InsertItem(id) => Some(*id),
|
||||
Change::UpdateItem(id) => Some(*id),
|
||||
Change::DeleteItem(id) => Some(*id),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn container_id(&self) -> Option<u64> {
|
||||
match self {
|
||||
Change::InsertContainer(id) => Some(*id),
|
||||
Change::UpdateContainer(id) => Some(*id),
|
||||
Change::UpdateContainerProperty(id) => Some(*id),
|
||||
Change::DeleteContainer(id) => Some(*id),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_unwrap_item_id(self) -> Option<u64> {
|
||||
match self {
|
||||
Change::InsertItem(id) => Some(id),
|
||||
Change::UpdateItem(id) => Some(id),
|
||||
Change::DeleteItem(id) => Some(id),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_unwrap_container_id(self) -> Option<u64> {
|
||||
match self {
|
||||
Change::InsertContainer(id) => Some(id),
|
||||
Change::UpdateContainer(id) => Some(id),
|
||||
Change::UpdateContainerProperty(id) => Some(id),
|
||||
Change::DeleteContainer(id) => Some(id),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_container_change(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Change::InsertContainer(_)
|
||||
| Change::UpdateContainer(_)
|
||||
| Change::UpdateContainerProperty(_)
|
||||
| Change::DeleteContainer(_)
|
||||
)
|
||||
}
|
||||
|
||||
pub fn is_item_change(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Change::InsertItem(_) | Change::UpdateItem(_) | Change::DeleteItem(_)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl DeserializeVanished for u64 {
|
||||
fn deserialize_vanished<'x>(bytes: &mut impl Iterator<Item = &'x u8>) -> Option<Self> {
|
||||
let mut num = [0u8; U64_LEN];
|
||||
for i in num.iter_mut() {
|
||||
*i = *bytes.next()?;
|
||||
}
|
||||
Some(u64::from_be_bytes(num))
|
||||
}
|
||||
}
|
||||
|
||||
impl DeserializeVanished for (u32, u32) {
|
||||
fn deserialize_vanished<'x>(bytes: &mut impl Iterator<Item = &'x u8>) -> Option<Self> {
|
||||
let mut num1 = [0u8; U32_LEN];
|
||||
let mut num2 = [0u8; U32_LEN];
|
||||
for i in num1.iter_mut().chain(num2.iter_mut()) {
|
||||
*i = *bytes.next()?;
|
||||
}
|
||||
Some((u32::from_be_bytes(num1), u32::from_be_bytes(num2)))
|
||||
}
|
||||
}
|
||||
|
||||
impl DeserializeVanished for String {
|
||||
fn deserialize_vanished<'x>(bytes: &mut impl Iterator<Item = &'x u8>) -> Option<Self> {
|
||||
let mut name = Vec::with_capacity(16);
|
||||
|
||||
loop {
|
||||
let byte = bytes.next()?;
|
||||
if *byte != 0 {
|
||||
name.push(*byte);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
String::from_utf8(name).ok()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
pub mod acl;
|
||||
pub mod log;
|
||||
|
||||
use crate::{IterateParams, Key};
|
||||
|
||||
impl<T: Key> IterateParams<T> {
|
||||
pub fn new(begin: T, end: T) -> Self {
|
||||
IterateParams {
|
||||
begin,
|
||||
end,
|
||||
first: false,
|
||||
ascending: true,
|
||||
values: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_ascending(mut self, ascending: bool) -> Self {
|
||||
self.ascending = ascending;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_values(mut self, values: bool) -> Self {
|
||||
self.values = values;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn ascending(mut self) -> Self {
|
||||
self.ascending = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn descending(mut self) -> Self {
|
||||
self.ascending = false;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn only_first(mut self) -> Self {
|
||||
self.first = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn no_values(mut self) -> Self {
|
||||
self.values = false;
|
||||
self
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user