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