Clarify breaking changes (closes #2332)
Some checks failed
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled

This commit is contained in:
mdecimus 2025-10-28 09:54:31 +01:00
parent de7f0e8e37
commit 7ac984e1c0
4 changed files with 9 additions and 205 deletions

View file

@ -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/).
## [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.
@ -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)
## 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.
- Cluster node roles now allow sharding.
## Fixed
- Push Subscription: Clean-up of expired subscriptions and cluster notification of changes (#1248)
- CalDAV: Per-user CalDAV properties (#2058)

View file

@ -14,7 +14,7 @@ use rkyv::{
use std::{borrow::Cow, fmt::Debug};
use store::{
Serialize, SerializeInfallible,
write::{Archive, Archiver, BatchBuilder, BlobOp, DirectoryClass, IntoOperations, TagValue},
write::{Archive, Archiver, BatchBuilder, BlobOp, DirectoryClass, IntoOperations},
};
use types::{
acl::AclGrant,
@ -34,10 +34,6 @@ pub enum IndexValue<'x> {
field: Field,
value: Vec<IndexItem<'x>>,
},
Tag {
field: Field,
value: Vec<TagValue>,
},
Blob {
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 } => {
if set {
batch.set(BlobOp::Link { hash: value }, vec![]);
@ -524,27 +511,6 @@ fn merge_index(
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 }) => {
batch.clear(BlobOp::Link { hash: old_hash });
batch.set(BlobOp::Link { hash: new_hash }, vec![]);

View file

@ -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(())
}
}

View file

@ -4,26 +4,9 @@
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use super::BitmapHash;
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 {
pub fn new(item: impl AsRef<[u8]>) -> Self {
Self {