mirror of
https://github.com/stalwartlabs/mail-server.git
synced 2025-12-11 22:06:31 +08:00
Clarify breaking changes (closes #2332)
Some checks failed
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
Some checks failed
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
This commit is contained in:
parent
de7f0e8e37
commit
7ac984e1c0
4 changed files with 9 additions and 205 deletions
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/).
|
All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/).
|
||||||
|
|
||||||
## [0.14.1] - 2025-10-27
|
## [0.14.1] - 2025-10-28
|
||||||
|
|
||||||
If you are upgrading from v0.13.4 and below, this version includes **breaking changes** to the internal directory, calendar and contacts. Please read the [upgrading documentation](https://stalw.art/docs/install/upgrade) for more information on how to upgrade from previous versions.
|
If you are upgrading from v0.13.4 and below, this version includes **breaking changes** to the internal directory, calendar and contacts. Please read the [upgrading documentation](https://stalw.art/docs/install/upgrade) for more information on how to upgrade from previous versions.
|
||||||
|
|
||||||
|
|
@ -30,9 +30,15 @@ If you are upgrading from v0.13.4 and below, this version includes **breaking ch
|
||||||
- i18n: Swedish language support (contributed by @purung)
|
- i18n: Swedish language support (contributed by @purung)
|
||||||
|
|
||||||
## Changed
|
## Changed
|
||||||
|
- **Breaking Database Changes** (migrated automatically on first start):
|
||||||
|
- Internal directory schema changed.
|
||||||
|
- Calendar and Contacts storage schema changed.
|
||||||
|
- Sieve scripts storage schema changed.
|
||||||
|
- Push Subscriptions storage schema changed.
|
||||||
- Replaced `sieve.untrusted.limits.max-scripts` and `jmap.push.max-total` with `object-quota.*` settings.
|
- Replaced `sieve.untrusted.limits.max-scripts` and `jmap.push.max-total` with `object-quota.*` settings.
|
||||||
- Cluster node roles now allow sharding.
|
- Cluster node roles now allow sharding.
|
||||||
|
|
||||||
|
|
||||||
## Fixed
|
## Fixed
|
||||||
- Push Subscription: Clean-up of expired subscriptions and cluster notification of changes (#1248)
|
- Push Subscription: Clean-up of expired subscriptions and cluster notification of changes (#1248)
|
||||||
- CalDAV: Per-user CalDAV properties (#2058)
|
- CalDAV: Per-user CalDAV properties (#2058)
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ use rkyv::{
|
||||||
use std::{borrow::Cow, fmt::Debug};
|
use std::{borrow::Cow, fmt::Debug};
|
||||||
use store::{
|
use store::{
|
||||||
Serialize, SerializeInfallible,
|
Serialize, SerializeInfallible,
|
||||||
write::{Archive, Archiver, BatchBuilder, BlobOp, DirectoryClass, IntoOperations, TagValue},
|
write::{Archive, Archiver, BatchBuilder, BlobOp, DirectoryClass, IntoOperations},
|
||||||
};
|
};
|
||||||
use types::{
|
use types::{
|
||||||
acl::AclGrant,
|
acl::AclGrant,
|
||||||
|
|
@ -34,10 +34,6 @@ pub enum IndexValue<'x> {
|
||||||
field: Field,
|
field: Field,
|
||||||
value: Vec<IndexItem<'x>>,
|
value: Vec<IndexItem<'x>>,
|
||||||
},
|
},
|
||||||
Tag {
|
|
||||||
field: Field,
|
|
||||||
value: Vec<TagValue>,
|
|
||||||
},
|
|
||||||
Blob {
|
Blob {
|
||||||
value: BlobHash,
|
value: BlobHash,
|
||||||
},
|
},
|
||||||
|
|
@ -379,15 +375,6 @@ fn build_index(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
IndexValue::Tag { field, value } => {
|
|
||||||
for item in value {
|
|
||||||
if set {
|
|
||||||
batch.tag(field, item);
|
|
||||||
} else {
|
|
||||||
batch.untag(field, item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
IndexValue::Blob { value } => {
|
IndexValue::Blob { value } => {
|
||||||
if set {
|
if set {
|
||||||
batch.set(BlobOp::Link { hash: value }, vec![]);
|
batch.set(BlobOp::Link { hash: value }, vec![]);
|
||||||
|
|
@ -524,27 +511,6 @@ fn merge_index(
|
||||||
batch.unindex(field, value.into_owned());
|
batch.unindex(field, value.into_owned());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(
|
|
||||||
IndexValue::Tag {
|
|
||||||
field,
|
|
||||||
value: old_value,
|
|
||||||
},
|
|
||||||
IndexValue::Tag {
|
|
||||||
value: new_value, ..
|
|
||||||
},
|
|
||||||
) => {
|
|
||||||
for old_tag in &old_value {
|
|
||||||
if !new_value.contains(old_tag) {
|
|
||||||
batch.untag(field, old_tag.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for new_tag in new_value {
|
|
||||||
if !old_value.contains(&new_tag) {
|
|
||||||
batch.tag(field, new_tag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(IndexValue::Blob { value: old_hash }, IndexValue::Blob { value: new_hash }) => {
|
(IndexValue::Blob { value: old_hash }, IndexValue::Blob { value: new_hash }) => {
|
||||||
batch.clear(BlobOp::Link { hash: old_hash });
|
batch.clear(BlobOp::Link { hash: old_hash });
|
||||||
batch.set(BlobOp::Link { hash: new_hash }, vec![]);
|
batch.set(BlobOp::Link { hash: new_hash }, vec![]);
|
||||||
|
|
|
||||||
|
|
@ -1,151 +0,0 @@
|
||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
|
||||||
*/
|
|
||||||
|
|
||||||
use std::slice::IterMut;
|
|
||||||
|
|
||||||
use jmap_proto::types::property::Property;
|
|
||||||
use store::{
|
|
||||||
Serialize, SerializedVersion,
|
|
||||||
write::{Archive, Archiver, BatchBuilder, TagValue, ValueClass},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct TagManager<
|
|
||||||
T: Into<TagValue>
|
|
||||||
+ PartialEq
|
|
||||||
+ Clone
|
|
||||||
+ Sync
|
|
||||||
+ Send
|
|
||||||
+ rkyv::Archive
|
|
||||||
+ for<'a> rkyv::Serialize<
|
|
||||||
rkyv::api::high::HighSerializer<
|
|
||||||
rkyv::util::AlignedVec,
|
|
||||||
rkyv::ser::allocator::ArenaHandle<'a>,
|
|
||||||
rkyv::rancor::Error,
|
|
||||||
>,
|
|
||||||
>,
|
|
||||||
> {
|
|
||||||
current: Archive<Vec<T>>,
|
|
||||||
added: Vec<T>,
|
|
||||||
removed: Vec<T>,
|
|
||||||
last: LastTag,
|
|
||||||
}
|
|
||||||
|
|
||||||
enum LastTag {
|
|
||||||
Set,
|
|
||||||
Update,
|
|
||||||
None,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<
|
|
||||||
T: Into<TagValue>
|
|
||||||
+ PartialEq
|
|
||||||
+ Clone
|
|
||||||
+ Sync
|
|
||||||
+ Send
|
|
||||||
+ SerializedVersion
|
|
||||||
+ rkyv::Archive
|
|
||||||
+ for<'a> rkyv::Serialize<
|
|
||||||
rkyv::api::high::HighSerializer<
|
|
||||||
rkyv::util::AlignedVec,
|
|
||||||
rkyv::ser::allocator::ArenaHandle<'a>,
|
|
||||||
rkyv::rancor::Error,
|
|
||||||
>,
|
|
||||||
>,
|
|
||||||
> TagManager<T>
|
|
||||||
{
|
|
||||||
pub fn new(current: Archive<Vec<T>>) -> Self {
|
|
||||||
Self {
|
|
||||||
current,
|
|
||||||
added: Vec::new(),
|
|
||||||
removed: Vec::new(),
|
|
||||||
last: LastTag::None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set(&mut self, tags: Vec<T>) {
|
|
||||||
if matches!(self.last, LastTag::None) {
|
|
||||||
self.added.clear();
|
|
||||||
self.removed.clear();
|
|
||||||
|
|
||||||
for tag in &tags {
|
|
||||||
if !self.current.inner.contains(tag) {
|
|
||||||
self.added.push(tag.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for tag in &self.current.inner {
|
|
||||||
if !tags.contains(tag) {
|
|
||||||
self.removed.push(tag.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.current.inner = tags;
|
|
||||||
self.last = LastTag::Set;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update(&mut self, tag: T, add: bool) {
|
|
||||||
if matches!(self.last, LastTag::None | LastTag::Update) {
|
|
||||||
if add {
|
|
||||||
if !self.current.inner.contains(&tag) {
|
|
||||||
self.added.push(tag.clone());
|
|
||||||
self.current.inner.push(tag);
|
|
||||||
}
|
|
||||||
} else if let Some(index) = self.current.inner.iter().position(|t| t == &tag) {
|
|
||||||
self.current.inner.swap_remove(index);
|
|
||||||
self.removed.push(tag);
|
|
||||||
}
|
|
||||||
self.last = LastTag::Update;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn added(&self) -> &[T] {
|
|
||||||
&self.added
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn removed(&self) -> &[T] {
|
|
||||||
&self.removed
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn current(&self) -> &[T] {
|
|
||||||
&self.current.inner
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn changed_tags(&self) -> impl Iterator<Item = &T> {
|
|
||||||
self.added.iter().chain(self.removed.iter())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn inner_tags_mut(&mut self) -> IterMut<'_, T> {
|
|
||||||
self.current.inner.iter_mut()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn has_tags(&self) -> bool {
|
|
||||||
!self.current.inner.is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn has_changes(&self) -> bool {
|
|
||||||
!self.added.is_empty() || !self.removed.is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_batch(self, batch: &mut BatchBuilder, property: Property) -> trc::Result<()> {
|
|
||||||
let property = u8::from(property);
|
|
||||||
|
|
||||||
batch
|
|
||||||
.assert_value(ValueClass::Property(property), &self.current)
|
|
||||||
.set(
|
|
||||||
ValueClass::Property(property),
|
|
||||||
Archiver::new(self.current.inner).serialize()?,
|
|
||||||
);
|
|
||||||
for added in self.added {
|
|
||||||
batch.tag(property, added);
|
|
||||||
}
|
|
||||||
for removed in self.removed {
|
|
||||||
batch.untag(property, removed);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -4,26 +4,9 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
use super::BitmapHash;
|
||||||
use crate::backend::MAX_TOKEN_LENGTH;
|
use crate::backend::MAX_TOKEN_LENGTH;
|
||||||
|
|
||||||
use super::{BitmapClass, BitmapHash};
|
|
||||||
|
|
||||||
impl BitmapClass {
|
|
||||||
pub fn word(token: impl AsRef<[u8]>, field: impl Into<u8>) -> Self {
|
|
||||||
BitmapClass::Text {
|
|
||||||
field: field.into(),
|
|
||||||
token: BitmapHash::new(token),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn stemmed(token: impl AsRef<[u8]>, field: impl Into<u8>) -> Self {
|
|
||||||
BitmapClass::Text {
|
|
||||||
field: field.into() | (1 << 7),
|
|
||||||
token: BitmapHash::new(token),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BitmapHash {
|
impl BitmapHash {
|
||||||
pub fn new(item: impl AsRef<[u8]>) -> Self {
|
pub fn new(item: impl AsRef<[u8]>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue