From 7ac984e1c060c2428d48f6995b394a5ad37e13fc Mon Sep 17 00:00:00 2001 From: mdecimus Date: Tue, 28 Oct 2025 09:54:31 +0100 Subject: [PATCH] Clarify breaking changes (closes #2332) --- CHANGELOG.md | 8 +- crates/common/src/storage/index.rs | 36 +------ crates/common/src/storage/tag.rs | 151 ----------------------------- crates/store/src/write/hash.rs | 19 +--- 4 files changed, 9 insertions(+), 205 deletions(-) delete mode 100644 crates/common/src/storage/tag.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index bb32bbfd..344308a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/crates/common/src/storage/index.rs b/crates/common/src/storage/index.rs index 6b495949..c6757b95 100644 --- a/crates/common/src/storage/index.rs +++ b/crates/common/src/storage/index.rs @@ -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>, }, - Tag { - field: Field, - value: Vec, - }, 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![]); diff --git a/crates/common/src/storage/tag.rs b/crates/common/src/storage/tag.rs deleted file mode 100644 index 2c655b5e..00000000 --- a/crates/common/src/storage/tag.rs +++ /dev/null @@ -1,151 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC - * - * 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 - + 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>, - added: Vec, - removed: Vec, - last: LastTag, -} - -enum LastTag { - Set, - Update, - None, -} - -impl< - T: Into - + 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 -{ - pub fn new(current: Archive>) -> Self { - Self { - current, - added: Vec::new(), - removed: Vec::new(), - last: LastTag::None, - } - } - - pub fn set(&mut self, tags: Vec) { - 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 { - 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(()) - } -} diff --git a/crates/store/src/write/hash.rs b/crates/store/src/write/hash.rs index e10e4b26..b10f8407 100644 --- a/crates/store/src/write/hash.rs +++ b/crates/store/src/write/hash.rs @@ -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) -> Self { - BitmapClass::Text { - field: field.into(), - token: BitmapHash::new(token), - } - } - - pub fn stemmed(token: impl AsRef<[u8]>, field: impl Into) -> Self { - BitmapClass::Text { - field: field.into() | (1 << 7), - token: BitmapHash::new(token), - } - } -} - impl BitmapHash { pub fn new(item: impl AsRef<[u8]>) -> Self { Self {