You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by bt...@apache.org on 2022/05/23 02:47:32 UTC
[james-project] 03/10: JAMES-3769 Common search overrides on top of existing cassandra projections
This is an automated email from the ASF dual-hosted git repository.
btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git
commit 30b0a956f9912792f43604115dafa487bc4c53e9
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Mon May 9 12:06:04 2022 +0700
JAMES-3769 Common search overrides on top of existing cassandra projections
- Deleted / not deleted mails
- Unseen
- Ranges support
---
.../apache/james/mailbox/model/SearchQuery.java | 11 +-
.../cassandra/mail/CassandraFirstUnseenDAO.java | 17 ++
.../cassandra/mail/CassandraMessageIdDAO.java | 67 ++++++++
.../cassandra/mail/CassandraMessageMapper.java | 3 +-
.../cassandra/search/AllSearchOverride.java | 70 ++++++++
.../cassandra/search/DeletedSearchOverride.java | 55 +++++++
.../search/DeletedWithRangeSearchOverride.java | 69 ++++++++
.../search/NotDeletedWithRangeSearchOverride.java | 83 ++++++++++
.../cassandra/search/UidSearchOverride.java | 66 ++++++++
.../cassandra/search/UnseenSearchOverride.java | 81 ++++++++++
.../cassandra/search/AllSearchOverrideTest.java | 177 +++++++++++++++++++++
.../search/DeletedSearchOverrideTest.java | 109 +++++++++++++
.../search/DeletedWithRangeSearchOverrideTest.java | 126 +++++++++++++++
.../NotDeletedWithRangeSearchOverrideTest.java | 158 ++++++++++++++++++
.../cassandra/search/UidSearchOverrideTest.java | 143 +++++++++++++++++
.../cassandra/search/UnseenSearchOverrideTest.java | 162 +++++++++++++++++++
16 files changed, 1394 insertions(+), 3 deletions(-)
diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/model/SearchQuery.java b/mailbox/api/src/main/java/org/apache/james/mailbox/model/SearchQuery.java
index f26f78f67d..da0082c8dc 100644
--- a/mailbox/api/src/main/java/org/apache/james/mailbox/model/SearchQuery.java
+++ b/mailbox/api/src/main/java/org/apache/james/mailbox/model/SearchQuery.java
@@ -50,6 +50,8 @@ import com.google.common.collect.ImmutableSet;
*/
public class SearchQuery {
private static final String DATE_HEADER_NAME = "Date";
+ public static final ImmutableList<Sort> DEFAULT_SORTS = ImmutableList.of(new Sort(Sort.SortClause.Uid, Sort.Order.NATURAL));
+
/**
* The Resolution which should get used for {@link Date} searches
@@ -769,7 +771,7 @@ public class SearchQuery {
public SearchQuery build() {
return new SearchQuery(criterias.build(),
- sorts.orElse(ImmutableList.of(new Sort(Sort.SortClause.Uid, Sort.Order.NATURAL))),
+ sorts.orElse(DEFAULT_SORTS),
recentMessageUids.build());
}
}
@@ -1898,6 +1900,13 @@ public class SearchQuery {
return ranges;
}
+ public boolean isAll() {
+ return ranges.length == 0
+ || (ranges.length == 1
+ && ranges[0].getLowValue() == MessageUid.MIN_VALUE
+ && ranges[0].getHighValue() == MessageUid.MAX_VALUE);
+ }
+
@Override
public int hashCode() {
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraFirstUnseenDAO.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraFirstUnseenDAO.java
index d3fffc208a..e42ee8bc9d 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraFirstUnseenDAO.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraFirstUnseenDAO.java
@@ -38,6 +38,7 @@ import org.apache.james.mailbox.cassandra.ids.CassandraId;
import com.datastax.driver.core.PreparedStatement;
import com.datastax.driver.core.Session;
+import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public class CassandraFirstUnseenDAO {
@@ -46,6 +47,7 @@ public class CassandraFirstUnseenDAO {
private final PreparedStatement deleteStatement;
private final PreparedStatement deleteAllStatement;
private final PreparedStatement readStatement;
+ private final PreparedStatement listStatement;
@Inject
public CassandraFirstUnseenDAO(Session session) {
@@ -54,6 +56,7 @@ public class CassandraFirstUnseenDAO {
this.deleteStatement = prepareDeleteStatement(session);
this.deleteAllStatement = prepareDeleteAllStatement(session);
this.readStatement = prepareReadStatement(session);
+ this.listStatement = prepareReadStatement(session);
}
private PreparedStatement prepareReadStatement(Session session) {
@@ -64,6 +67,13 @@ public class CassandraFirstUnseenDAO {
.limit(1));
}
+ private PreparedStatement prepareListStatement(Session session) {
+ return session.prepare(select(UID)
+ .from(TABLE_NAME)
+ .where(eq(MAILBOX_ID, bindMarker(MAILBOX_ID)))
+ .orderBy(asc(UID)));
+ }
+
private PreparedStatement prepareDeleteStatement(Session session) {
return session.prepare(delete()
.from(TABLE_NAME)
@@ -108,4 +118,11 @@ public class CassandraFirstUnseenDAO {
.map(row -> MessageUid.of(row.getLong(UID)));
}
+ public Flux<MessageUid> listUnseen(CassandraId cassandraId) {
+ return cassandraAsyncExecutor.executeRows(
+ listStatement.bind()
+ .setUUID(MAILBOX_ID, cassandraId.asUuid()))
+ .map(row -> MessageUid.of(row.getLong(UID)));
+ }
+
}
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdDAO.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdDAO.java
index 654412a82d..b647ab08ea 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdDAO.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdDAO.java
@@ -51,12 +51,14 @@ import static org.apache.james.mailbox.cassandra.table.Flag.RECENT;
import static org.apache.james.mailbox.cassandra.table.Flag.SEEN;
import static org.apache.james.mailbox.cassandra.table.Flag.USER;
import static org.apache.james.mailbox.cassandra.table.Flag.USER_FLAGS;
+import static org.apache.james.mailbox.cassandra.table.Flag.USER_FLAGS_LOWERCASE;
import static org.apache.james.mailbox.cassandra.table.MessageIdToImapUid.MOD_SEQ;
import static org.apache.james.mailbox.cassandra.table.MessageIdToImapUid.MOD_SEQ_LOWERCASE;
import static org.apache.james.mailbox.cassandra.table.MessageIdToImapUid.THREAD_ID_LOWERCASE;
import static org.apache.james.util.ReactorUtils.publishIfPresent;
import java.time.Duration;
+import java.util.Locale;
import java.util.Optional;
import java.util.UUID;
@@ -109,6 +111,8 @@ public class CassandraMessageIdDAO {
private final PreparedStatement selectUidGte;
private final PreparedStatement selectUidGteLimited;
private final PreparedStatement selectUidRange;
+ private final PreparedStatement selectUidOnlyRange;
+ private final PreparedStatement selectMetadataRange;
private final PreparedStatement selectUidRangeLimited;
private final PreparedStatement update;
private final PreparedStatement listStatement;
@@ -127,8 +131,10 @@ public class CassandraMessageIdDAO {
this.selectUidGte = prepareSelectUidGte(session);
this.selectUidGteLimited = prepareSelectUidGteLimited(session);
this.selectUidRange = prepareSelectUidRange(session);
+ this.selectUidOnlyRange = prepareSelectUidOnlyRange(session);
this.selectUidRangeLimited = prepareSelectUidRangeLimited(session);
this.listStatement = prepareList(session);
+ this.selectMetadataRange = prepareSelectMetadataRange(session);
}
private PreparedStatement prepareDelete(Session session) {
@@ -229,6 +235,34 @@ public class CassandraMessageIdDAO {
.and(lte(IMAP_UID, bindMarker(IMAP_UID_LTE))));
}
+ private PreparedStatement prepareSelectUidOnlyRange(Session session) {
+ return session.prepare(select(IMAP_UID)
+ .from(TABLE_NAME)
+ .where(eq(MAILBOX_ID, bindMarker(MAILBOX_ID)))
+ .and(gte(IMAP_UID, bindMarker(IMAP_UID_GTE)))
+ .and(lte(IMAP_UID, bindMarker(IMAP_UID_LTE))));
+ }
+
+ private PreparedStatement prepareSelectMetadataRange(Session session) {
+ return session.prepare(
+ select(
+ IMAP_UID,
+ MESSAGE_ID,
+ ANSWERED.toLowerCase(Locale.US),
+ DELETED.toLowerCase(Locale.US),
+ DRAFT.toLowerCase(Locale.US),
+ RECENT.toLowerCase(Locale.US),
+ SEEN.toLowerCase(Locale.US),
+ FLAGGED.toLowerCase(Locale.US),
+ USER.toLowerCase(Locale.US),
+ USER_FLAGS_LOWERCASE,
+ MOD_SEQ_LOWERCASE)
+ .from(TABLE_NAME)
+ .where(eq(MAILBOX_ID, bindMarker(MAILBOX_ID)))
+ .and(gte(IMAP_UID, bindMarker(IMAP_UID_GTE)))
+ .and(lte(IMAP_UID, bindMarker(IMAP_UID_LTE))));
+ }
+
private PreparedStatement prepareSelectUidRangeLimited(Session session) {
return session.prepare(select()
.from(TABLE_NAME)
@@ -369,6 +403,39 @@ public class CassandraMessageIdDAO {
.map(row -> MessageUid.of(row.getLong(IMAP_UID)));
}
+ public Flux<ComposedMessageIdWithMetaData> listMessagesMetadata(CassandraId mailboxId, MessageRange range) {
+ return cassandraAsyncExecutor.executeRows(selectMetadataRange.bind()
+ .setUUID(MAILBOX_ID, mailboxId.asUuid())
+ .setLong(IMAP_UID_GTE, range.getUidFrom().asLong())
+ .setLong(IMAP_UID_LTE, range.getUidTo().asLong()))
+ .map(row -> {
+ CassandraMessageId messageId = CassandraMessageId.Factory.of(row.getUUID(MESSAGE_ID_LOWERCASE));
+ return ComposedMessageIdWithMetaData.builder()
+ .modSeq(ModSeq.of(row.getLong(MOD_SEQ_LOWERCASE)))
+ .threadId(getThreadIdFromRow(row, messageId))
+ .flags(FlagsExtractor.getFlags(row))
+ .composedMessageId(new ComposedMessageId(mailboxId,
+ messageId,
+ MessageUid.of(row.getLong(IMAP_UID))))
+ .build();
+ });
+ }
+
+ public Flux<MessageUid> doListUids(CassandraId mailboxId, MessageRange range) {
+ return cassandraAsyncExecutor.executeRows(selectUidOnlyRange.bind()
+ .setUUID(MAILBOX_ID, mailboxId.asUuid())
+ .setLong(IMAP_UID_GTE, range.getUidFrom().asLong())
+ .setLong(IMAP_UID_LTE, range.getUidTo().asLong()))
+ .map(row -> MessageUid.of(row.getLong(IMAP_UID)));
+ }
+
+ public Flux<MessageUid> listUids(CassandraId mailboxId, MessageRange range) {
+ if (range.getType() == MessageRange.Type.ALL) {
+ return listUids(mailboxId);
+ }
+ return doListUids(mailboxId, range);
+ }
+
public Flux<CassandraMessageMetadata> retrieveAllMessages() {
return cassandraAsyncExecutor.executeRows(listStatement.bind()
.setReadTimeoutMillis(Duration.ofDays(1).toMillisPart()))
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapper.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapper.java
index a511db6f6b..baaece7491 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapper.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapper.java
@@ -243,8 +243,7 @@ public class CassandraMessageMapper implements MessageMapper {
@Override
public Flux<ComposedMessageIdWithMetaData> listMessagesMetadata(Mailbox mailbox, MessageRange set) {
CassandraId mailboxId = (CassandraId) mailbox.getMailboxId();
- return messageIdDAO.retrieveMessages(mailboxId, set, Limit.unlimited())
- .map(CassandraMessageMetadata::getComposedMessageId);
+ return messageIdDAO.listMessagesMetadata(mailboxId, set);
}
@Override
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/search/AllSearchOverride.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/search/AllSearchOverride.java
new file mode 100644
index 0000000000..fd3122d794
--- /dev/null
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/search/AllSearchOverride.java
@@ -0,0 +1,70 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one *
+ * or more contributor license agreements. See the NOTICE file *
+ * distributed with this work for additional information *
+ * regarding copyright ownership. The ASF licenses this file *
+ * to you under the Apache License, Version 2.0 (the *
+ * "License"); you may not use this file except in compliance *
+ * with the License. You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, *
+ * software distributed under the License is distributed on an *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
+ * KIND, either express or implied. See the License for the *
+ * specific language governing permissions and limitations *
+ * under the License. *
+ ****************************************************************/
+
+package org.apache.james.mailbox.cassandra.search;
+
+import javax.inject.Inject;
+
+import org.apache.james.mailbox.MailboxSession;
+import org.apache.james.mailbox.MessageUid;
+import org.apache.james.mailbox.cassandra.ids.CassandraId;
+import org.apache.james.mailbox.cassandra.mail.CassandraMessageIdDAO;
+import org.apache.james.mailbox.model.Mailbox;
+import org.apache.james.mailbox.model.SearchQuery;
+import org.apache.james.mailbox.store.search.ListeningMessageSearchIndex;
+
+import reactor.core.publisher.Flux;
+
+public class AllSearchOverride implements ListeningMessageSearchIndex.SearchOverride {
+ private final CassandraMessageIdDAO dao;
+
+ @Inject
+ public AllSearchOverride(CassandraMessageIdDAO dao) {
+ this.dao = dao;
+ }
+
+ @Override
+ public boolean applicable(SearchQuery searchQuery, MailboxSession session) {
+ return isAll(searchQuery)
+ || isFromOne(searchQuery)
+ || isEmpty(searchQuery);
+ }
+
+ private boolean isAll(SearchQuery searchQuery) {
+ return searchQuery.getCriteria().size() == 1
+ && searchQuery.getCriteria().get(0).equals(SearchQuery.uid(new SearchQuery.UidRange(MessageUid.MIN_VALUE, MessageUid.MAX_VALUE)))
+ && searchQuery.getSorts().equals(SearchQuery.DEFAULT_SORTS);
+ }
+
+ private boolean isFromOne(SearchQuery searchQuery) {
+ return searchQuery.getCriteria().size() == 1
+ && searchQuery.getCriteria().get(0).equals(SearchQuery.all())
+ && searchQuery.getSorts().equals(SearchQuery.DEFAULT_SORTS);
+ }
+
+ private boolean isEmpty(SearchQuery searchQuery) {
+ return searchQuery.getCriteria().isEmpty()
+ && searchQuery.getSorts().equals(SearchQuery.DEFAULT_SORTS);
+ }
+
+ @Override
+ public Flux<MessageUid> search(MailboxSession session, Mailbox mailbox, SearchQuery searchQuery) {
+ return dao.listUids((CassandraId) mailbox.getMailboxId());
+ }
+}
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/search/DeletedSearchOverride.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/search/DeletedSearchOverride.java
new file mode 100644
index 0000000000..82e13dc19c
--- /dev/null
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/search/DeletedSearchOverride.java
@@ -0,0 +1,55 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one *
+ * or more contributor license agreements. See the NOTICE file *
+ * distributed with this work for additional information *
+ * regarding copyright ownership. The ASF licenses this file *
+ * to you under the Apache License, Version 2.0 (the *
+ * "License"); you may not use this file except in compliance *
+ * with the License. You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, *
+ * software distributed under the License is distributed on an *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
+ * KIND, either express or implied. See the License for the *
+ * specific language governing permissions and limitations *
+ * under the License. *
+ ****************************************************************/
+
+package org.apache.james.mailbox.cassandra.search;
+
+import javax.inject.Inject;
+import javax.mail.Flags;
+
+import org.apache.james.mailbox.MailboxSession;
+import org.apache.james.mailbox.MessageUid;
+import org.apache.james.mailbox.cassandra.ids.CassandraId;
+import org.apache.james.mailbox.cassandra.mail.CassandraDeletedMessageDAO;
+import org.apache.james.mailbox.model.Mailbox;
+import org.apache.james.mailbox.model.MessageRange;
+import org.apache.james.mailbox.model.SearchQuery;
+import org.apache.james.mailbox.store.search.ListeningMessageSearchIndex;
+
+import reactor.core.publisher.Flux;
+
+public class DeletedSearchOverride implements ListeningMessageSearchIndex.SearchOverride {
+ private final CassandraDeletedMessageDAO dao;
+
+ @Inject
+ public DeletedSearchOverride(CassandraDeletedMessageDAO dao) {
+ this.dao = dao;
+ }
+
+ @Override
+ public boolean applicable(SearchQuery searchQuery, MailboxSession session) {
+ return searchQuery.getCriteria().size() == 1
+ && searchQuery.getCriteria().get(0).equals(SearchQuery.flagIsSet(Flags.Flag.DELETED))
+ && searchQuery.getSorts().equals(SearchQuery.DEFAULT_SORTS);
+ }
+
+ @Override
+ public Flux<MessageUid> search(MailboxSession session, Mailbox mailbox, SearchQuery searchQuery) {
+ return dao.retrieveDeletedMessage((CassandraId) mailbox.getMailboxId(), MessageRange.all());
+ }
+}
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/search/DeletedWithRangeSearchOverride.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/search/DeletedWithRangeSearchOverride.java
new file mode 100644
index 0000000000..14f792efcb
--- /dev/null
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/search/DeletedWithRangeSearchOverride.java
@@ -0,0 +1,69 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one *
+ * or more contributor license agreements. See the NOTICE file *
+ * distributed with this work for additional information *
+ * regarding copyright ownership. The ASF licenses this file *
+ * to you under the Apache License, Version 2.0 (the *
+ * "License"); you may not use this file except in compliance *
+ * with the License. You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, *
+ * software distributed under the License is distributed on an *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
+ * KIND, either express or implied. See the License for the *
+ * specific language governing permissions and limitations *
+ * under the License. *
+ ****************************************************************/
+
+package org.apache.james.mailbox.cassandra.search;
+
+import javax.inject.Inject;
+import javax.mail.Flags;
+
+import org.apache.james.mailbox.MailboxSession;
+import org.apache.james.mailbox.MessageUid;
+import org.apache.james.mailbox.cassandra.ids.CassandraId;
+import org.apache.james.mailbox.cassandra.mail.CassandraDeletedMessageDAO;
+import org.apache.james.mailbox.model.Mailbox;
+import org.apache.james.mailbox.model.MessageRange;
+import org.apache.james.mailbox.model.SearchQuery;
+import org.apache.james.mailbox.store.search.ListeningMessageSearchIndex;
+
+import com.google.common.collect.ImmutableList;
+
+import reactor.core.publisher.Flux;
+
+public class DeletedWithRangeSearchOverride implements ListeningMessageSearchIndex.SearchOverride {
+ private final CassandraDeletedMessageDAO dao;
+
+ @Inject
+ public DeletedWithRangeSearchOverride(CassandraDeletedMessageDAO dao) {
+ this.dao = dao;
+ }
+
+ @Override
+ public boolean applicable(SearchQuery searchQuery, MailboxSession session) {
+ return searchQuery.getCriteria().size() == 2
+ && searchQuery.getCriteria().contains(SearchQuery.flagIsSet(Flags.Flag.DELETED))
+ && searchQuery.getCriteria().stream()
+ .anyMatch(criterion -> criterion instanceof SearchQuery.UidCriterion)
+ && searchQuery.getSorts().equals(SearchQuery.DEFAULT_SORTS);
+ }
+
+ @Override
+ public Flux<MessageUid> search(MailboxSession session, Mailbox mailbox, SearchQuery searchQuery) {
+ SearchQuery.UidCriterion uidArgument = searchQuery.getCriteria().stream()
+ .filter(criterion -> criterion instanceof SearchQuery.UidCriterion)
+ .map(SearchQuery.UidCriterion.class::cast)
+ .findAny()
+ .orElseThrow(() -> new RuntimeException("Missing Uid argument"));
+
+ SearchQuery.UidRange[] uidRanges = uidArgument.getOperator().getRange();
+
+ return Flux.fromIterable(ImmutableList.copyOf(uidRanges))
+ .concatMap(range -> dao.retrieveDeletedMessage((CassandraId) mailbox.getMailboxId(),
+ MessageRange.range(range.getLowValue(), range.getHighValue())));
+ }
+}
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/search/NotDeletedWithRangeSearchOverride.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/search/NotDeletedWithRangeSearchOverride.java
new file mode 100644
index 0000000000..0f0f81c3a7
--- /dev/null
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/search/NotDeletedWithRangeSearchOverride.java
@@ -0,0 +1,83 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one *
+ * or more contributor license agreements. See the NOTICE file *
+ * distributed with this work for additional information *
+ * regarding copyright ownership. The ASF licenses this file *
+ * to you under the Apache License, Version 2.0 (the *
+ * "License"); you may not use this file except in compliance *
+ * with the License. You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, *
+ * software distributed under the License is distributed on an *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
+ * KIND, either express or implied. See the License for the *
+ * specific language governing permissions and limitations *
+ * under the License. *
+ ****************************************************************/
+
+package org.apache.james.mailbox.cassandra.search;
+
+import javax.inject.Inject;
+import javax.mail.Flags;
+
+import org.apache.james.mailbox.MailboxSession;
+import org.apache.james.mailbox.MessageUid;
+import org.apache.james.mailbox.cassandra.ids.CassandraId;
+import org.apache.james.mailbox.cassandra.mail.CassandraMessageIdDAO;
+import org.apache.james.mailbox.model.Mailbox;
+import org.apache.james.mailbox.model.MessageRange;
+import org.apache.james.mailbox.model.SearchQuery;
+import org.apache.james.mailbox.store.search.ListeningMessageSearchIndex;
+
+import com.google.common.collect.ImmutableList;
+
+import reactor.core.publisher.Flux;
+
+public class NotDeletedWithRangeSearchOverride implements ListeningMessageSearchIndex.SearchOverride {
+ private final CassandraMessageIdDAO dao;
+
+ @Inject
+ public NotDeletedWithRangeSearchOverride(CassandraMessageIdDAO dao) {
+ this.dao = dao;
+ }
+
+ @Override
+ public boolean applicable(SearchQuery searchQuery, MailboxSession session) {
+ return isDeletedUnset(searchQuery) || isDeletedNotSet(searchQuery);
+ }
+
+ private boolean isDeletedUnset(SearchQuery searchQuery) {
+ return searchQuery.getCriteria().size() == 2
+ && searchQuery.getCriteria().contains(SearchQuery.flagIsUnSet(Flags.Flag.DELETED))
+ && searchQuery.getCriteria().stream()
+ .anyMatch(criterion -> criterion instanceof SearchQuery.UidCriterion)
+ && searchQuery.getSorts().equals(SearchQuery.DEFAULT_SORTS);
+ }
+
+ private boolean isDeletedNotSet(SearchQuery searchQuery) {
+ return searchQuery.getCriteria().size() == 2
+ && searchQuery.getCriteria().contains(SearchQuery.not(SearchQuery.flagIsSet(Flags.Flag.DELETED)))
+ && searchQuery.getCriteria().stream()
+ .anyMatch(criterion -> criterion instanceof SearchQuery.UidCriterion)
+ && searchQuery.getSorts().equals(SearchQuery.DEFAULT_SORTS);
+ }
+
+ @Override
+ public Flux<MessageUid> search(MailboxSession session, Mailbox mailbox, SearchQuery searchQuery) {
+ SearchQuery.UidCriterion uidArgument = searchQuery.getCriteria().stream()
+ .filter(criterion -> criterion instanceof SearchQuery.UidCriterion)
+ .map(SearchQuery.UidCriterion.class::cast)
+ .findAny()
+ .orElseThrow(() -> new RuntimeException("Missing Uid argument"));
+
+ SearchQuery.UidRange[] uidRanges = uidArgument.getOperator().getRange();
+
+ return Flux.fromIterable(ImmutableList.copyOf(uidRanges))
+ .concatMap(range -> dao.listMessagesMetadata((CassandraId) mailbox.getMailboxId(),
+ MessageRange.range(range.getLowValue(), range.getHighValue())))
+ .filter(message -> !message.getFlags().contains(Flags.Flag.DELETED))
+ .map(message -> message.getComposedMessageId().getUid());
+ }
+}
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/search/UidSearchOverride.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/search/UidSearchOverride.java
new file mode 100644
index 0000000000..a427c537b8
--- /dev/null
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/search/UidSearchOverride.java
@@ -0,0 +1,66 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one *
+ * or more contributor license agreements. See the NOTICE file *
+ * distributed with this work for additional information *
+ * regarding copyright ownership. The ASF licenses this file *
+ * to you under the Apache License, Version 2.0 (the *
+ * "License"); you may not use this file except in compliance *
+ * with the License. You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, *
+ * software distributed under the License is distributed on an *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
+ * KIND, either express or implied. See the License for the *
+ * specific language governing permissions and limitations *
+ * under the License. *
+ ****************************************************************/
+
+package org.apache.james.mailbox.cassandra.search;
+
+import javax.inject.Inject;
+
+import org.apache.james.mailbox.MailboxSession;
+import org.apache.james.mailbox.MessageUid;
+import org.apache.james.mailbox.cassandra.ids.CassandraId;
+import org.apache.james.mailbox.cassandra.mail.CassandraMessageIdDAO;
+import org.apache.james.mailbox.model.Mailbox;
+import org.apache.james.mailbox.model.MessageRange;
+import org.apache.james.mailbox.model.SearchQuery;
+import org.apache.james.mailbox.store.search.ListeningMessageSearchIndex;
+
+import com.google.common.collect.ImmutableList;
+
+import reactor.core.publisher.Flux;
+
+public class UidSearchOverride implements ListeningMessageSearchIndex.SearchOverride {
+ private final CassandraMessageIdDAO dao;
+
+ @Inject
+ public UidSearchOverride(CassandraMessageIdDAO dao) {
+ this.dao = dao;
+ }
+
+ @Override
+ public boolean applicable(SearchQuery searchQuery, MailboxSession session) {
+ return searchQuery.getCriteria().size() == 1
+ && searchQuery.getCriteria().get(0) instanceof SearchQuery.UidCriterion
+ && searchQuery.getSorts().equals(SearchQuery.DEFAULT_SORTS);
+ }
+
+ @Override
+ public Flux<MessageUid> search(MailboxSession session, Mailbox mailbox, SearchQuery searchQuery) {
+ SearchQuery.UidCriterion uidArgument = searchQuery.getCriteria().stream()
+ .filter(criterion -> criterion instanceof SearchQuery.UidCriterion)
+ .map(SearchQuery.UidCriterion.class::cast)
+ .findAny()
+ .orElseThrow(() -> new RuntimeException("Missing Uid argument"));
+
+ SearchQuery.UidRange[] uidRanges = uidArgument.getOperator().getRange();
+
+ return Flux.fromIterable(ImmutableList.copyOf(uidRanges))
+ .concatMap(range -> dao.listUids((CassandraId) mailbox.getMailboxId(),
+ MessageRange.range(range.getLowValue(), range.getHighValue())));
+ }
+}
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/search/UnseenSearchOverride.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/search/UnseenSearchOverride.java
new file mode 100644
index 0000000000..b928cc4a8e
--- /dev/null
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/search/UnseenSearchOverride.java
@@ -0,0 +1,81 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one *
+ * or more contributor license agreements. See the NOTICE file *
+ * distributed with this work for additional information *
+ * regarding copyright ownership. The ASF licenses this file *
+ * to you under the Apache License, Version 2.0 (the *
+ * "License"); you may not use this file except in compliance *
+ * with the License. You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, *
+ * software distributed under the License is distributed on an *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
+ * KIND, either express or implied. See the License for the *
+ * specific language governing permissions and limitations *
+ * under the License. *
+ ****************************************************************/
+
+package org.apache.james.mailbox.cassandra.search;
+
+import javax.inject.Inject;
+import javax.mail.Flags;
+
+import org.apache.james.mailbox.MailboxSession;
+import org.apache.james.mailbox.MessageUid;
+import org.apache.james.mailbox.cassandra.ids.CassandraId;
+import org.apache.james.mailbox.cassandra.mail.CassandraFirstUnseenDAO;
+import org.apache.james.mailbox.model.Mailbox;
+import org.apache.james.mailbox.model.SearchQuery;
+import org.apache.james.mailbox.store.search.ListeningMessageSearchIndex;
+
+import reactor.core.publisher.Flux;
+
+public class UnseenSearchOverride implements ListeningMessageSearchIndex.SearchOverride {
+ private final CassandraFirstUnseenDAO dao;
+
+ @Inject
+ public UnseenSearchOverride(CassandraFirstUnseenDAO dao) {
+ this.dao = dao;
+ }
+
+ @Override
+ public boolean applicable(SearchQuery searchQuery, MailboxSession session) {
+ return isUnseenWithAll(searchQuery)
+ || isNotSeenWithAll(searchQuery);
+ }
+
+ private boolean isUnseenWithAll(SearchQuery searchQuery) {
+ return searchQuery.getCriteria().contains(SearchQuery.flagIsUnSet(Flags.Flag.SEEN))
+ && allMessages(searchQuery)
+ && searchQuery.getSorts().equals(SearchQuery.DEFAULT_SORTS);
+ }
+
+ private boolean isNotSeenWithAll(SearchQuery searchQuery) {
+ return searchQuery.getCriteria().contains(SearchQuery.not(SearchQuery.flagIsSet(Flags.Flag.SEEN)))
+ && allMessages(searchQuery)
+ && searchQuery.getSorts().equals(SearchQuery.DEFAULT_SORTS);
+ }
+
+ private boolean allMessages(SearchQuery searchQuery) {
+ if (searchQuery.getCriteria().size() == 1) {
+ // Only the unseen critrion
+ return true;
+ }
+ if (searchQuery.getCriteria().size() == 2) {
+ return searchQuery.getCriteria().stream()
+ .filter(criterion -> criterion instanceof SearchQuery.UidCriterion)
+ .map(SearchQuery.UidCriterion.class::cast)
+ .anyMatch(uidCriterion -> uidCriterion.getOperator().isAll()) ||
+ searchQuery.getCriteria().stream()
+ .anyMatch(criterion -> criterion instanceof SearchQuery.AllCriterion);
+ }
+ return false;
+ }
+
+ @Override
+ public Flux<MessageUid> search(MailboxSession session, Mailbox mailbox, SearchQuery searchQuery) {
+ return dao.listUnseen((CassandraId) mailbox.getMailboxId());
+ }
+}
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/search/AllSearchOverrideTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/search/AllSearchOverrideTest.java
new file mode 100644
index 0000000000..19a6a83684
--- /dev/null
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/search/AllSearchOverrideTest.java
@@ -0,0 +1,177 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one *
+ * or more contributor license agreements. See the NOTICE file *
+ * distributed with this work for additional information *
+ * regarding copyright ownership. The ASF licenses this file *
+ * to you under the Apache License, Version 2.0 (the *
+ * "License"); you may not use this file except in compliance *
+ * with the License. You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, *
+ * software distributed under the License is distributed on an *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
+ * KIND, either express or implied. See the License for the *
+ * specific language governing permissions and limitations *
+ * under the License. *
+ ****************************************************************/
+
+package org.apache.james.mailbox.cassandra.search;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.Date;
+import java.util.Optional;
+
+import javax.mail.Flags;
+
+import org.apache.james.backends.cassandra.CassandraCluster;
+import org.apache.james.backends.cassandra.CassandraClusterExtension;
+import org.apache.james.backends.cassandra.components.CassandraModule;
+import org.apache.james.backends.cassandra.versions.CassandraSchemaVersionModule;
+import org.apache.james.blob.api.HashBlobId;
+import org.apache.james.core.Username;
+import org.apache.james.mailbox.MailboxSession;
+import org.apache.james.mailbox.MailboxSessionUtil;
+import org.apache.james.mailbox.MessageUid;
+import org.apache.james.mailbox.ModSeq;
+import org.apache.james.mailbox.cassandra.ids.CassandraId;
+import org.apache.james.mailbox.cassandra.ids.CassandraMessageId;
+import org.apache.james.mailbox.cassandra.mail.CassandraMessageIdDAO;
+import org.apache.james.mailbox.cassandra.mail.CassandraMessageMetadata;
+import org.apache.james.mailbox.cassandra.modules.CassandraMessageModule;
+import org.apache.james.mailbox.model.ComposedMessageId;
+import org.apache.james.mailbox.model.ComposedMessageIdWithMetaData;
+import org.apache.james.mailbox.model.Mailbox;
+import org.apache.james.mailbox.model.MailboxPath;
+import org.apache.james.mailbox.model.SearchQuery;
+import org.apache.james.mailbox.model.ThreadId;
+import org.apache.james.mailbox.model.UidValidity;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+class AllSearchOverrideTest {
+ private static final MailboxSession MAILBOX_SESSION = MailboxSessionUtil.create(Username.of("benwa"));
+ private static final Mailbox MAILBOX = new Mailbox(MailboxPath.inbox(MAILBOX_SESSION), UidValidity.of(12), CassandraId.timeBased());
+ private static final HashBlobId HEADER_BLOB_ID_1 = new HashBlobId.Factory().forPayload("abc".getBytes());
+ private static final CassandraModule MODULE = CassandraModule.aggregateModules(
+ CassandraMessageModule.MODULE,
+ CassandraSchemaVersionModule.MODULE);
+
+ @RegisterExtension
+ static CassandraClusterExtension cassandraCluster = new CassandraClusterExtension(MODULE);
+
+ private CassandraMessageIdDAO dao;
+ private AllSearchOverride testee;
+
+ @BeforeEach
+ void setUp(CassandraCluster cassandra) {
+ dao = new CassandraMessageIdDAO(cassandra.getConf(), new HashBlobId.Factory());
+ testee = new AllSearchOverride(dao);
+ }
+
+ @Test
+ void emptyQueryShouldBeApplicable() {
+ assertThat(testee.applicable(
+ SearchQuery.builder()
+ .build(),
+ MAILBOX_SESSION))
+ .isTrue();
+ }
+
+ @Test
+ void allQueryShouldBeApplicable() {
+ assertThat(testee.applicable(
+ SearchQuery.builder()
+ .andCriteria(SearchQuery.all())
+ .build(),
+ MAILBOX_SESSION))
+ .isTrue();
+ }
+
+ @Test
+ void fromOneQueryShouldBeApplicable() {
+ assertThat(testee.applicable(
+ SearchQuery.builder()
+ .andCriteria(SearchQuery.uid(new SearchQuery.UidRange(MessageUid.MIN_VALUE, MessageUid.MAX_VALUE)))
+ .build(),
+ MAILBOX_SESSION))
+ .isTrue();
+ }
+
+ @Test
+ void sizeQueryShouldNotBeApplicable() {
+ assertThat(testee.applicable(
+ SearchQuery.builder()
+ .andCriteria(SearchQuery.sizeEquals(12))
+ .build(),
+ MAILBOX_SESSION))
+ .isFalse();
+ }
+
+ @Test
+ void searchShouldReturnEmptyByDefault() {
+ assertThat(testee.search(MAILBOX_SESSION, MAILBOX,
+ SearchQuery.builder()
+ .andCriteria(SearchQuery.all())
+ .build()).collectList().block())
+ .isEmpty();
+ }
+
+ @Test
+ void searchShouldReturnMailboxEntries() {
+ MessageUid messageUid = MessageUid.of(1);
+ CassandraMessageId messageId = new CassandraMessageId.Factory().generate();
+ dao.insert(CassandraMessageMetadata.builder()
+ .ids(ComposedMessageIdWithMetaData.builder()
+ .composedMessageId(new ComposedMessageId(MAILBOX.getMailboxId(), messageId, messageUid))
+ .flags(new Flags())
+ .modSeq(ModSeq.of(1))
+ .threadId(ThreadId.fromBaseMessageId(messageId))
+ .build())
+ .internalDate(new Date())
+ .bodyStartOctet(18L)
+ .size(36L)
+ .headerContent(Optional.of(HEADER_BLOB_ID_1))
+ .build())
+ .block();
+ MessageUid messageUid2 = MessageUid.of(2);
+ CassandraMessageId messageId2 = new CassandraMessageId.Factory().generate();
+ dao.insert(CassandraMessageMetadata.builder()
+ .ids(ComposedMessageIdWithMetaData.builder()
+ .composedMessageId(new ComposedMessageId(MAILBOX.getMailboxId(), messageId2, messageUid2))
+ .flags(new Flags())
+ .modSeq(ModSeq.of(1))
+ .threadId(ThreadId.fromBaseMessageId(messageId2))
+ .build())
+ .internalDate(new Date())
+ .bodyStartOctet(18L)
+ .size(36L)
+ .headerContent(Optional.of(HEADER_BLOB_ID_1))
+ .build())
+ .block();
+ MessageUid messageUid3 = MessageUid.of(3);
+ CassandraMessageId messageId3 = new CassandraMessageId.Factory().generate();
+ dao.insert(CassandraMessageMetadata.builder()
+ .ids(ComposedMessageIdWithMetaData.builder()
+ .composedMessageId(new ComposedMessageId(CassandraId.timeBased(), messageId3, messageUid3))
+ .flags(new Flags())
+ .modSeq(ModSeq.of(1))
+ .threadId(ThreadId.fromBaseMessageId(messageId3))
+ .build())
+ .internalDate(new Date())
+ .bodyStartOctet(18L)
+ .size(36L)
+ .headerContent(Optional.of(HEADER_BLOB_ID_1))
+ .build())
+ .block();
+
+ assertThat(testee.search(MAILBOX_SESSION, MAILBOX,
+ SearchQuery.builder()
+ .andCriteria(SearchQuery.all())
+ .build()).collectList().block())
+ .containsOnly(messageUid, messageUid2);
+ }
+}
\ No newline at end of file
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/search/DeletedSearchOverrideTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/search/DeletedSearchOverrideTest.java
new file mode 100644
index 0000000000..46a1b7a655
--- /dev/null
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/search/DeletedSearchOverrideTest.java
@@ -0,0 +1,109 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one *
+ * or more contributor license agreements. See the NOTICE file *
+ * distributed with this work for additional information *
+ * regarding copyright ownership. The ASF licenses this file *
+ * to you under the Apache License, Version 2.0 (the *
+ * "License"); you may not use this file except in compliance *
+ * with the License. You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, *
+ * software distributed under the License is distributed on an *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
+ * KIND, either express or implied. See the License for the *
+ * specific language governing permissions and limitations *
+ * under the License. *
+ ****************************************************************/
+
+package org.apache.james.mailbox.cassandra.search;
+
+import static javax.mail.Flags.Flag.DELETED;
+import static javax.mail.Flags.Flag.SEEN;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.apache.james.backends.cassandra.CassandraCluster;
+import org.apache.james.backends.cassandra.CassandraClusterExtension;
+import org.apache.james.backends.cassandra.components.CassandraModule;
+import org.apache.james.backends.cassandra.versions.CassandraSchemaVersionModule;
+import org.apache.james.core.Username;
+import org.apache.james.mailbox.MailboxSession;
+import org.apache.james.mailbox.MailboxSessionUtil;
+import org.apache.james.mailbox.MessageUid;
+import org.apache.james.mailbox.cassandra.ids.CassandraId;
+import org.apache.james.mailbox.cassandra.mail.CassandraDeletedMessageDAO;
+import org.apache.james.mailbox.cassandra.modules.CassandraDeletedMessageModule;
+import org.apache.james.mailbox.model.Mailbox;
+import org.apache.james.mailbox.model.MailboxPath;
+import org.apache.james.mailbox.model.SearchQuery;
+import org.apache.james.mailbox.model.UidValidity;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+class DeletedSearchOverrideTest {
+ private static final MailboxSession MAILBOX_SESSION = MailboxSessionUtil.create(Username.of("benwa"));
+ private static final Mailbox MAILBOX = new Mailbox(MailboxPath.inbox(MAILBOX_SESSION), UidValidity.of(12), CassandraId.timeBased());
+ private static final CassandraModule MODULE = CassandraModule.aggregateModules(
+ CassandraDeletedMessageModule.MODULE,
+ CassandraSchemaVersionModule.MODULE);
+
+ @RegisterExtension
+ static CassandraClusterExtension cassandraCluster = new CassandraClusterExtension(MODULE);
+
+ private CassandraDeletedMessageDAO dao;
+ private DeletedSearchOverride testee;
+
+ @BeforeEach
+ void setUp(CassandraCluster cassandra) {
+ dao = new CassandraDeletedMessageDAO(cassandra.getConf());
+ testee = new DeletedSearchOverride(dao);
+ }
+
+ @Test
+ void deletedQueryShouldBeApplicable() {
+ assertThat(testee.applicable(
+ SearchQuery.builder()
+ .andCriteria(SearchQuery.flagIsSet(DELETED))
+ .build(),
+ MAILBOX_SESSION))
+ .isTrue();
+ }
+
+ @Test
+ void sizeQueryShouldNotBeApplicable() {
+ assertThat(testee.applicable(
+ SearchQuery.builder()
+ .andCriteria(SearchQuery.sizeEquals(12))
+ .build(),
+ MAILBOX_SESSION))
+ .isFalse();
+ }
+
+ @Test
+ void searchShouldReturnEmptyByDefault() {
+ assertThat(testee.search(MAILBOX_SESSION, MAILBOX,
+ SearchQuery.builder()
+ .andCriteria(SearchQuery.flagIsUnSet(SEEN))
+ .build()).collectList().block())
+ .isEmpty();
+ }
+
+ @Test
+ void searchShouldReturnMailboxEntries() {
+ MessageUid messageUid = MessageUid.of(1);
+ MessageUid messageUid2 = MessageUid.of(2);
+ MessageUid messageUid3 = MessageUid.of(3);
+
+ dao.addDeleted((CassandraId) MAILBOX.getMailboxId(), messageUid).block();
+ dao.addDeleted((CassandraId) MAILBOX.getMailboxId(), messageUid2).block();
+ dao.addDeleted((CassandraId) MAILBOX.getMailboxId(), messageUid3).block();
+
+ assertThat(testee.search(MAILBOX_SESSION, MAILBOX,
+ SearchQuery.builder()
+ .andCriteria(SearchQuery.flagIsUnSet(DELETED))
+ .build()).collectList().block())
+ .containsOnly(messageUid, messageUid2, messageUid3);
+ }
+}
\ No newline at end of file
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/search/DeletedWithRangeSearchOverrideTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/search/DeletedWithRangeSearchOverrideTest.java
new file mode 100644
index 0000000000..dda44e6f4a
--- /dev/null
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/search/DeletedWithRangeSearchOverrideTest.java
@@ -0,0 +1,126 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one *
+ * or more contributor license agreements. See the NOTICE file *
+ * distributed with this work for additional information *
+ * regarding copyright ownership. The ASF licenses this file *
+ * to you under the Apache License, Version 2.0 (the *
+ * "License"); you may not use this file except in compliance *
+ * with the License. You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, *
+ * software distributed under the License is distributed on an *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
+ * KIND, either express or implied. See the License for the *
+ * specific language governing permissions and limitations *
+ * under the License. *
+ ****************************************************************/
+
+package org.apache.james.mailbox.cassandra.search;
+
+import static javax.mail.Flags.Flag.DELETED;
+import static javax.mail.Flags.Flag.SEEN;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.apache.james.backends.cassandra.CassandraCluster;
+import org.apache.james.backends.cassandra.CassandraClusterExtension;
+import org.apache.james.backends.cassandra.components.CassandraModule;
+import org.apache.james.backends.cassandra.versions.CassandraSchemaVersionModule;
+import org.apache.james.core.Username;
+import org.apache.james.mailbox.MailboxSession;
+import org.apache.james.mailbox.MailboxSessionUtil;
+import org.apache.james.mailbox.MessageUid;
+import org.apache.james.mailbox.cassandra.ids.CassandraId;
+import org.apache.james.mailbox.cassandra.mail.CassandraDeletedMessageDAO;
+import org.apache.james.mailbox.cassandra.modules.CassandraDeletedMessageModule;
+import org.apache.james.mailbox.model.Mailbox;
+import org.apache.james.mailbox.model.MailboxPath;
+import org.apache.james.mailbox.model.SearchQuery;
+import org.apache.james.mailbox.model.UidValidity;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+class DeletedWithRangeSearchOverrideTest {
+ private static final MailboxSession MAILBOX_SESSION = MailboxSessionUtil.create(Username.of("benwa"));
+ private static final Mailbox MAILBOX = new Mailbox(MailboxPath.inbox(MAILBOX_SESSION), UidValidity.of(12), CassandraId.timeBased());
+ private static final CassandraModule MODULE = CassandraModule.aggregateModules(
+ CassandraDeletedMessageModule.MODULE,
+ CassandraSchemaVersionModule.MODULE);
+
+ @RegisterExtension
+ static CassandraClusterExtension cassandraCluster = new CassandraClusterExtension(MODULE);
+
+ private CassandraDeletedMessageDAO dao;
+ private DeletedWithRangeSearchOverride testee;
+
+ @BeforeEach
+ void setUp(CassandraCluster cassandra) {
+ dao = new CassandraDeletedMessageDAO(cassandra.getConf());
+ testee = new DeletedWithRangeSearchOverride(dao);
+ }
+
+ @Test
+ void deletedWithRangeQueryShouldBeApplicable() {
+ assertThat(testee.applicable(
+ SearchQuery.builder()
+ .andCriteria(SearchQuery.flagIsSet(DELETED))
+ .andCriteria(SearchQuery.uid(new SearchQuery.UidRange(MessageUid.of(4), MessageUid.of(45))))
+ .build(),
+ MAILBOX_SESSION))
+ .isTrue();
+ }
+
+ @Test
+ void deletedQueryShouldNotBeApplicable() {
+ assertThat(testee.applicable(
+ SearchQuery.builder()
+ .andCriteria(SearchQuery.flagIsSet(DELETED))
+ .build(),
+ MAILBOX_SESSION))
+ .isFalse();
+ }
+
+ @Test
+ void sizeQueryShouldNotBeApplicable() {
+ assertThat(testee.applicable(
+ SearchQuery.builder()
+ .andCriteria(SearchQuery.sizeEquals(12))
+ .build(),
+ MAILBOX_SESSION))
+ .isFalse();
+ }
+
+ @Test
+ void searchShouldReturnEmptyByDefault() {
+ assertThat(testee.search(MAILBOX_SESSION, MAILBOX,
+ SearchQuery.builder()
+ .andCriteria(SearchQuery.flagIsUnSet(SEEN))
+ .andCriteria(SearchQuery.uid(new SearchQuery.UidRange(MessageUid.MIN_VALUE, MessageUid.of(45))))
+ .build()).collectList().block())
+ .isEmpty();
+ }
+
+ @Test
+ void searchShouldReturnMailboxEntries() {
+ MessageUid messageUid = MessageUid.of(1);
+ MessageUid messageUid2 = MessageUid.of(2);
+ MessageUid messageUid3 = MessageUid.of(3);
+ MessageUid messageUid4 = MessageUid.of(4);
+ MessageUid messageUid5 = MessageUid.of(5);
+
+ dao.addDeleted((CassandraId) MAILBOX.getMailboxId(), messageUid).block();
+ dao.addDeleted((CassandraId) MAILBOX.getMailboxId(), messageUid2).block();
+ dao.addDeleted((CassandraId) MAILBOX.getMailboxId(), messageUid3).block();
+ dao.addDeleted((CassandraId) MAILBOX.getMailboxId(), messageUid4).block();
+ dao.addDeleted((CassandraId) MAILBOX.getMailboxId(), messageUid5).block();
+
+ assertThat(testee.search(MAILBOX_SESSION, MAILBOX,
+ SearchQuery.builder()
+ .andCriteria(SearchQuery.flagIsUnSet(DELETED))
+ .andCriteria(SearchQuery.uid(new SearchQuery.UidRange(messageUid2, messageUid4)))
+ .build()).collectList().block())
+ .containsOnly(messageUid2, messageUid3, messageUid4);
+ }
+}
\ No newline at end of file
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/search/NotDeletedWithRangeSearchOverrideTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/search/NotDeletedWithRangeSearchOverrideTest.java
new file mode 100644
index 0000000000..42b01cd348
--- /dev/null
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/search/NotDeletedWithRangeSearchOverrideTest.java
@@ -0,0 +1,158 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one *
+ * or more contributor license agreements. See the NOTICE file *
+ * distributed with this work for additional information *
+ * regarding copyright ownership. The ASF licenses this file *
+ * to you under the Apache License, Version 2.0 (the *
+ * "License"); you may not use this file except in compliance *
+ * with the License. You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, *
+ * software distributed under the License is distributed on an *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
+ * KIND, either express or implied. See the License for the *
+ * specific language governing permissions and limitations *
+ * under the License. *
+ ****************************************************************/
+
+package org.apache.james.mailbox.cassandra.search;
+
+import static javax.mail.Flags.Flag.DELETED;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.Date;
+import java.util.Optional;
+
+import javax.mail.Flags;
+
+import org.apache.james.backends.cassandra.CassandraCluster;
+import org.apache.james.backends.cassandra.CassandraClusterExtension;
+import org.apache.james.backends.cassandra.components.CassandraModule;
+import org.apache.james.backends.cassandra.versions.CassandraSchemaVersionModule;
+import org.apache.james.blob.api.HashBlobId;
+import org.apache.james.core.Username;
+import org.apache.james.mailbox.MailboxSession;
+import org.apache.james.mailbox.MailboxSessionUtil;
+import org.apache.james.mailbox.MessageUid;
+import org.apache.james.mailbox.ModSeq;
+import org.apache.james.mailbox.cassandra.ids.CassandraId;
+import org.apache.james.mailbox.cassandra.ids.CassandraMessageId;
+import org.apache.james.mailbox.cassandra.mail.CassandraMessageIdDAO;
+import org.apache.james.mailbox.cassandra.mail.CassandraMessageMetadata;
+import org.apache.james.mailbox.cassandra.modules.CassandraMessageModule;
+import org.apache.james.mailbox.model.ComposedMessageId;
+import org.apache.james.mailbox.model.ComposedMessageIdWithMetaData;
+import org.apache.james.mailbox.model.Mailbox;
+import org.apache.james.mailbox.model.MailboxId;
+import org.apache.james.mailbox.model.MailboxPath;
+import org.apache.james.mailbox.model.SearchQuery;
+import org.apache.james.mailbox.model.ThreadId;
+import org.apache.james.mailbox.model.UidValidity;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+class NotDeletedWithRangeSearchOverrideTest {
+ private static final MailboxSession MAILBOX_SESSION = MailboxSessionUtil.create(Username.of("benwa"));
+ private static final Mailbox MAILBOX = new Mailbox(MailboxPath.inbox(MAILBOX_SESSION), UidValidity.of(12), CassandraId.timeBased());
+ private static final HashBlobId HEADER_BLOB_ID_1 = new HashBlobId.Factory().forPayload("abc".getBytes());
+ private static final CassandraModule MODULE = CassandraModule.aggregateModules(
+ CassandraMessageModule.MODULE,
+ CassandraSchemaVersionModule.MODULE);
+
+ @RegisterExtension
+ static CassandraClusterExtension cassandraCluster = new CassandraClusterExtension(MODULE);
+
+ private CassandraMessageIdDAO dao;
+ private NotDeletedWithRangeSearchOverride testee;
+
+ @BeforeEach
+ void setUp(CassandraCluster cassandra) {
+ dao = new CassandraMessageIdDAO(cassandra.getConf(), new HashBlobId.Factory());
+ testee = new NotDeletedWithRangeSearchOverride(dao);
+ }
+
+ @Test
+ void undeletedRangeQueryShouldBeApplicable() {
+ assertThat(testee.applicable(
+ SearchQuery.builder()
+ .andCriteria(SearchQuery.flagIsUnSet(DELETED))
+ .andCriteria(SearchQuery.uid(new SearchQuery.UidRange(MessageUid.of(4), MessageUid.of(45))))
+ .build(),
+ MAILBOX_SESSION))
+ .isTrue();
+ }
+
+ @Test
+ void notDeletedRangeQueryShouldBeApplicable() {
+ assertThat(testee.applicable(
+ SearchQuery.builder()
+ .andCriteria(SearchQuery.not(SearchQuery.flagIsSet(DELETED)))
+ .andCriteria(SearchQuery.uid(new SearchQuery.UidRange(MessageUid.of(4), MessageUid.of(45))))
+ .build(),
+ MAILBOX_SESSION))
+ .isTrue();
+ }
+
+ @Test
+ void sizeQueryShouldNotBeApplicable() {
+ assertThat(testee.applicable(
+ SearchQuery.builder()
+ .andCriteria(SearchQuery.sizeEquals(12))
+ .build(),
+ MAILBOX_SESSION))
+ .isFalse();
+ }
+
+ @Test
+ void searchShouldReturnEmptyByDefault() {
+ assertThat(testee.search(MAILBOX_SESSION, MAILBOX,
+ SearchQuery.builder()
+ .andCriteria(SearchQuery.not(SearchQuery.flagIsSet(DELETED)))
+ .andCriteria(SearchQuery.uid(new SearchQuery.UidRange(MessageUid.of(34), MessageUid.of(345))))
+ .build()).collectList().block())
+ .isEmpty();
+ }
+
+ @Test
+ void searchShouldReturnMailboxEntries() {
+ MessageUid messageUid = MessageUid.of(1);
+ insert(messageUid, MAILBOX.getMailboxId(), new Flags());
+ MessageUid messageUid2 = MessageUid.of(2);
+ insert(messageUid2, MAILBOX.getMailboxId(), new Flags());
+ MessageUid messageUid3 = MessageUid.of(3);
+ insert(messageUid3, MAILBOX.getMailboxId(), new Flags(DELETED));
+ MessageUid messageUid4 = MessageUid.of(5);
+ insert(messageUid4, MAILBOX.getMailboxId(), new Flags());
+ MessageUid messageUid5 = MessageUid.of(5);
+ insert(messageUid5, MAILBOX.getMailboxId(), new Flags());
+ MessageUid messageUid6 = MessageUid.of(4);
+ insert(messageUid6, CassandraId.timeBased(), new Flags(DELETED));
+
+ assertThat(testee.search(MAILBOX_SESSION, MAILBOX,
+ SearchQuery.builder()
+ .andCriteria(SearchQuery.not(SearchQuery.flagIsSet(DELETED)))
+ .andCriteria(SearchQuery.uid(new SearchQuery.UidRange(messageUid2, messageUid4)))
+ .build()).collectList().block())
+ .containsOnly(messageUid2, messageUid4);
+ }
+
+ private void insert(MessageUid messageUid5, MailboxId cassandraId, Flags flags) {
+ CassandraMessageId messageId5 = new CassandraMessageId.Factory().generate();
+ dao.insert(CassandraMessageMetadata.builder()
+ .ids(ComposedMessageIdWithMetaData.builder()
+ .composedMessageId(new ComposedMessageId(cassandraId, messageId5, messageUid5))
+ .flags(flags)
+ .modSeq(ModSeq.of(1))
+ .threadId(ThreadId.fromBaseMessageId(messageId5))
+ .build())
+ .internalDate(new Date())
+ .bodyStartOctet(18L)
+ .size(36L)
+ .headerContent(Optional.of(HEADER_BLOB_ID_1))
+ .build())
+ .block();
+ }
+}
\ No newline at end of file
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/search/UidSearchOverrideTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/search/UidSearchOverrideTest.java
new file mode 100644
index 0000000000..e45de172ea
--- /dev/null
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/search/UidSearchOverrideTest.java
@@ -0,0 +1,143 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one *
+ * or more contributor license agreements. See the NOTICE file *
+ * distributed with this work for additional information *
+ * regarding copyright ownership. The ASF licenses this file *
+ * to you under the Apache License, Version 2.0 (the *
+ * "License"); you may not use this file except in compliance *
+ * with the License. You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, *
+ * software distributed under the License is distributed on an *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
+ * KIND, either express or implied. See the License for the *
+ * specific language governing permissions and limitations *
+ * under the License. *
+ ****************************************************************/
+
+package org.apache.james.mailbox.cassandra.search;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.Date;
+import java.util.Optional;
+
+import javax.mail.Flags;
+
+import org.apache.james.backends.cassandra.CassandraCluster;
+import org.apache.james.backends.cassandra.CassandraClusterExtension;
+import org.apache.james.backends.cassandra.components.CassandraModule;
+import org.apache.james.backends.cassandra.versions.CassandraSchemaVersionModule;
+import org.apache.james.blob.api.HashBlobId;
+import org.apache.james.core.Username;
+import org.apache.james.mailbox.MailboxSession;
+import org.apache.james.mailbox.MailboxSessionUtil;
+import org.apache.james.mailbox.MessageUid;
+import org.apache.james.mailbox.ModSeq;
+import org.apache.james.mailbox.cassandra.ids.CassandraId;
+import org.apache.james.mailbox.cassandra.ids.CassandraMessageId;
+import org.apache.james.mailbox.cassandra.mail.CassandraMessageIdDAO;
+import org.apache.james.mailbox.cassandra.mail.CassandraMessageMetadata;
+import org.apache.james.mailbox.cassandra.modules.CassandraMessageModule;
+import org.apache.james.mailbox.model.ComposedMessageId;
+import org.apache.james.mailbox.model.ComposedMessageIdWithMetaData;
+import org.apache.james.mailbox.model.Mailbox;
+import org.apache.james.mailbox.model.MailboxId;
+import org.apache.james.mailbox.model.MailboxPath;
+import org.apache.james.mailbox.model.SearchQuery;
+import org.apache.james.mailbox.model.ThreadId;
+import org.apache.james.mailbox.model.UidValidity;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+class UidSearchOverrideTest {
+ private static final MailboxSession MAILBOX_SESSION = MailboxSessionUtil.create(Username.of("benwa"));
+ private static final Mailbox MAILBOX = new Mailbox(MailboxPath.inbox(MAILBOX_SESSION), UidValidity.of(12), CassandraId.timeBased());
+ private static final HashBlobId HEADER_BLOB_ID_1 = new HashBlobId.Factory().forPayload("abc".getBytes());
+ private static final CassandraModule MODULE = CassandraModule.aggregateModules(
+ CassandraMessageModule.MODULE,
+ CassandraSchemaVersionModule.MODULE);
+
+ @RegisterExtension
+ static CassandraClusterExtension cassandraCluster = new CassandraClusterExtension(MODULE);
+
+ private CassandraMessageIdDAO dao;
+ private UidSearchOverride testee;
+
+ @BeforeEach
+ void setUp(CassandraCluster cassandra) {
+ dao = new CassandraMessageIdDAO(cassandra.getConf(), new HashBlobId.Factory());
+ testee = new UidSearchOverride(dao);
+ }
+
+ @Test
+ void rangeQueryShouldBeApplicable() {
+ assertThat(testee.applicable(
+ SearchQuery.builder()
+ .andCriteria(SearchQuery.uid(new SearchQuery.UidRange(MessageUid.of(4), MessageUid.of(45))))
+ .build(),
+ MAILBOX_SESSION))
+ .isTrue();
+ }
+
+ @Test
+ void sizeQueryShouldNotBeApplicable() {
+ assertThat(testee.applicable(
+ SearchQuery.builder()
+ .andCriteria(SearchQuery.sizeEquals(12))
+ .build(),
+ MAILBOX_SESSION))
+ .isFalse();
+ }
+
+ @Test
+ void searchShouldReturnEmptyByDefault() {
+ assertThat(testee.search(MAILBOX_SESSION, MAILBOX,
+ SearchQuery.builder()
+ .andCriteria(SearchQuery.uid(new SearchQuery.UidRange(MessageUid.of(34), MessageUid.of(345))))
+ .build()).collectList().block())
+ .isEmpty();
+ }
+
+ @Test
+ void searchShouldReturnMailboxEntries() {
+ MessageUid messageUid = MessageUid.of(1);
+ insert(messageUid, MAILBOX.getMailboxId());
+ MessageUid messageUid2 = MessageUid.of(2);
+ insert(messageUid2, MAILBOX.getMailboxId());
+ MessageUid messageUid3 = MessageUid.of(3);
+ insert(messageUid3, MAILBOX.getMailboxId());
+ MessageUid messageUid4 = MessageUid.of(5);
+ insert(messageUid4, MAILBOX.getMailboxId());
+ MessageUid messageUid5 = MessageUid.of(5);
+ insert(messageUid5, MAILBOX.getMailboxId());
+ MessageUid messageUid6 = MessageUid.of(6);
+ insert(messageUid6, CassandraId.timeBased());
+
+ assertThat(testee.search(MAILBOX_SESSION, MAILBOX,
+ SearchQuery.builder()
+ .andCriteria(SearchQuery.uid(new SearchQuery.UidRange(messageUid2, messageUid4)))
+ .build()).collectList().block())
+ .containsOnly(messageUid2, messageUid3, messageUid4);
+ }
+
+ private void insert(MessageUid messageUid5, MailboxId cassandraId) {
+ CassandraMessageId messageId5 = new CassandraMessageId.Factory().generate();
+ dao.insert(CassandraMessageMetadata.builder()
+ .ids(ComposedMessageIdWithMetaData.builder()
+ .composedMessageId(new ComposedMessageId(cassandraId, messageId5, messageUid5))
+ .flags(new Flags())
+ .modSeq(ModSeq.of(1))
+ .threadId(ThreadId.fromBaseMessageId(messageId5))
+ .build())
+ .internalDate(new Date())
+ .bodyStartOctet(18L)
+ .size(36L)
+ .headerContent(Optional.of(HEADER_BLOB_ID_1))
+ .build())
+ .block();
+ }
+}
\ No newline at end of file
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/search/UnseenSearchOverrideTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/search/UnseenSearchOverrideTest.java
new file mode 100644
index 0000000000..433843ff16
--- /dev/null
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/search/UnseenSearchOverrideTest.java
@@ -0,0 +1,162 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one *
+ * or more contributor license agreements. See the NOTICE file *
+ * distributed with this work for additional information *
+ * regarding copyright ownership. The ASF licenses this file *
+ * to you under the Apache License, Version 2.0 (the *
+ * "License"); you may not use this file except in compliance *
+ * with the License. You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, *
+ * software distributed under the License is distributed on an *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
+ * KIND, either express or implied. See the License for the *
+ * specific language governing permissions and limitations *
+ * under the License. *
+ ****************************************************************/
+
+package org.apache.james.mailbox.cassandra.search;
+
+import static javax.mail.Flags.Flag.SEEN;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.apache.james.backends.cassandra.CassandraCluster;
+import org.apache.james.backends.cassandra.CassandraClusterExtension;
+import org.apache.james.backends.cassandra.components.CassandraModule;
+import org.apache.james.backends.cassandra.versions.CassandraSchemaVersionModule;
+import org.apache.james.core.Username;
+import org.apache.james.mailbox.MailboxSession;
+import org.apache.james.mailbox.MailboxSessionUtil;
+import org.apache.james.mailbox.MessageUid;
+import org.apache.james.mailbox.cassandra.ids.CassandraId;
+import org.apache.james.mailbox.cassandra.mail.CassandraFirstUnseenDAO;
+import org.apache.james.mailbox.cassandra.modules.CassandraFirstUnseenModule;
+import org.apache.james.mailbox.model.Mailbox;
+import org.apache.james.mailbox.model.MailboxPath;
+import org.apache.james.mailbox.model.SearchQuery;
+import org.apache.james.mailbox.model.UidValidity;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+class UnseenSearchOverrideTest {
+ private static final MailboxSession MAILBOX_SESSION = MailboxSessionUtil.create(Username.of("benwa"));
+ private static final Mailbox MAILBOX = new Mailbox(MailboxPath.inbox(MAILBOX_SESSION), UidValidity.of(12), CassandraId.timeBased());
+ private static final CassandraModule MODULE = CassandraModule.aggregateModules(
+ CassandraFirstUnseenModule.MODULE,
+ CassandraSchemaVersionModule.MODULE);
+
+ @RegisterExtension
+ static CassandraClusterExtension cassandraCluster = new CassandraClusterExtension(MODULE);
+
+ private CassandraFirstUnseenDAO dao;
+ private UnseenSearchOverride testee;
+
+ @BeforeEach
+ void setUp(CassandraCluster cassandra) {
+ dao = new CassandraFirstUnseenDAO(cassandra.getConf());
+ testee = new UnseenSearchOverride(dao);
+ }
+
+ @Test
+ void unseenQueryShouldBeApplicable() {
+ assertThat(testee.applicable(
+ SearchQuery.builder()
+ .andCriteria(SearchQuery.flagIsUnSet(SEEN))
+ .build(),
+ MAILBOX_SESSION))
+ .isTrue();
+ }
+
+ @Test
+ void notSeenQueryShouldBeApplicable() {
+ assertThat(testee.applicable(
+ SearchQuery.builder()
+ .andCriteria(SearchQuery.not(SearchQuery.flagIsSet(SEEN)))
+ .build(),
+ MAILBOX_SESSION))
+ .isTrue();
+ }
+
+ @Test
+ void unseenAndAllQueryShouldBeApplicable() {
+ assertThat(testee.applicable(
+ SearchQuery.builder()
+ .andCriteria(SearchQuery.flagIsUnSet(SEEN))
+ .andCriteria(SearchQuery.all())
+ .build(),
+ MAILBOX_SESSION))
+ .isTrue();
+ }
+
+ @Test
+ void notSeenAndAllQueryShouldBeApplicable() {
+ assertThat(testee.applicable(
+ SearchQuery.builder()
+ .andCriteria(SearchQuery.not(SearchQuery.flagIsSet(SEEN)))
+ .andCriteria(SearchQuery.all())
+ .build(),
+ MAILBOX_SESSION))
+ .isTrue();
+ }
+
+ @Test
+ void unseenAndFromOneQueryShouldBeApplicable() {
+ assertThat(testee.applicable(
+ SearchQuery.builder()
+ .andCriteria(SearchQuery.flagIsUnSet(SEEN))
+ .andCriteria(SearchQuery.uid(new SearchQuery.UidRange(MessageUid.MIN_VALUE, MessageUid.MAX_VALUE)))
+ .build(),
+ MAILBOX_SESSION))
+ .isTrue();
+ }
+
+ @Test
+ void notSeenFromOneQueryShouldBeApplicable() {
+ assertThat(testee.applicable(
+ SearchQuery.builder()
+ .andCriteria(SearchQuery.not(SearchQuery.flagIsSet(SEEN)))
+ .andCriteria(SearchQuery.uid(new SearchQuery.UidRange(MessageUid.MIN_VALUE, MessageUid.MAX_VALUE)))
+ .build(),
+ MAILBOX_SESSION))
+ .isTrue();
+ }
+
+ @Test
+ void sizeQueryShouldNotBeApplicable() {
+ assertThat(testee.applicable(
+ SearchQuery.builder()
+ .andCriteria(SearchQuery.sizeEquals(12))
+ .build(),
+ MAILBOX_SESSION))
+ .isFalse();
+ }
+
+ @Test
+ void searchShouldReturnEmptyByDefault() {
+ assertThat(testee.search(MAILBOX_SESSION, MAILBOX,
+ SearchQuery.builder()
+ .andCriteria(SearchQuery.flagIsUnSet(SEEN))
+ .build()).collectList().block())
+ .isEmpty();
+ }
+
+ @Test
+ void searchShouldReturnMailboxEntries() {
+ MessageUid messageUid = MessageUid.of(1);
+ MessageUid messageUid2 = MessageUid.of(2);
+ MessageUid messageUid3 = MessageUid.of(3);
+
+ dao.addUnread((CassandraId) MAILBOX.getMailboxId(), messageUid).block();
+ dao.addUnread((CassandraId) MAILBOX.getMailboxId(), messageUid2).block();
+ dao.addUnread((CassandraId) MAILBOX.getMailboxId(), messageUid3).block();
+
+ assertThat(testee.search(MAILBOX_SESSION, MAILBOX,
+ SearchQuery.builder()
+ .andCriteria(SearchQuery.flagIsUnSet(SEEN))
+ .build()).collectList().block())
+ .containsOnly(messageUid, messageUid2, messageUid3);
+ }
+}
\ No newline at end of file
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org