This is an automated email from the ASF dual-hosted git repository.

rcordier pushed a commit to branch postgresql
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit b367787dae294d519cd588250882e3219405dd9c
Author: Rene Cordier <rcord...@linagora.com>
AuthorDate: Fri Dec 22 16:13:43 2023 +0700

    JAMES-2586 Implement UnseenSearchOverrideTest for Postgresql
---
 .../mail/dao/PostgresMailboxMessageDAO.java        |  40 ++++
 .../postgres/search/UnseenSearchOverride.java      |  93 +++++++++
 .../postgres/search/UnseenSearchOverrideTest.java  | 216 +++++++++++++++++++++
 3 files changed, 349 insertions(+)

diff --git 
a/mailbox/postgres/src/main/java/org/apache/james/mailbox/postgres/mail/dao/PostgresMailboxMessageDAO.java
 
b/mailbox/postgres/src/main/java/org/apache/james/mailbox/postgres/mail/dao/PostgresMailboxMessageDAO.java
index 1c8f032c41..61ff87a764 100644
--- 
a/mailbox/postgres/src/main/java/org/apache/james/mailbox/postgres/mail/dao/PostgresMailboxMessageDAO.java
+++ 
b/mailbox/postgres/src/main/java/org/apache/james/mailbox/postgres/mail/dao/PostgresMailboxMessageDAO.java
@@ -118,6 +118,46 @@ public class PostgresMailboxMessageDAO {
             .map(RECORD_TO_MESSAGE_UID_FUNCTION);
     }
 
+    public Flux<MessageUid> listUnseen(PostgresMailboxId mailboxId) {
+        return postgresExecutor.executeRows(dslContext -> 
Flux.from(selectMessageUidByMailboxIdAndExtraConditionQuery(mailboxId,
+                IS_SEEN.eq(false), Limit.unlimited(), dslContext)))
+            .map(RECORD_TO_MESSAGE_UID_FUNCTION);
+    }
+
+    public Flux<MessageUid> listUnseen(PostgresMailboxId mailboxId, 
MessageRange range) {
+        switch (range.getType()) {
+            case ALL:
+                return listUnseen(mailboxId);
+            case FROM:
+                return postgresExecutor.executeRows(dslContext -> 
Flux.from(dslContext.select(MESSAGE_UID)
+                        .from(TABLE_NAME)
+                        .where(MAILBOX_ID.eq(mailboxId.asUuid()))
+                        .and(IS_SEEN.eq(false))
+                        
.and(MESSAGE_UID.greaterOrEqual(range.getUidFrom().asLong()))
+                        .orderBy(DEFAULT_SORT_ORDER_BY)))
+                    .map(RECORD_TO_MESSAGE_UID_FUNCTION);
+            case RANGE:
+                return postgresExecutor.executeRows(dslContext -> 
Flux.from(dslContext.select(MESSAGE_UID)
+                        .from(TABLE_NAME)
+                        .where(MAILBOX_ID.eq(mailboxId.asUuid()))
+                        .and(IS_SEEN.eq(false))
+                        
.and(MESSAGE_UID.greaterOrEqual(range.getUidFrom().asLong()))
+                        
.and(MESSAGE_UID.lessOrEqual(range.getUidTo().asLong()))
+                        .orderBy(DEFAULT_SORT_ORDER_BY)))
+                    .map(RECORD_TO_MESSAGE_UID_FUNCTION);
+            case ONE:
+                return postgresExecutor.executeRows(dslContext -> 
Flux.from(dslContext.select(MESSAGE_UID)
+                        .from(TABLE_NAME)
+                        .where(MAILBOX_ID.eq(mailboxId.asUuid()))
+                        .and(IS_SEEN.eq(false))
+                        .and(MESSAGE_UID.eq(range.getUidFrom().asLong()))
+                        .orderBy(DEFAULT_SORT_ORDER_BY)))
+                    .map(RECORD_TO_MESSAGE_UID_FUNCTION);
+            default:
+                throw new RuntimeException("Unsupported range type " + 
range.getType());
+        }
+    }
+
     public Flux<MessageUid> findAllRecentMessageUid(PostgresMailboxId 
mailboxId) {
         return postgresExecutor.executeRows(dslContext -> 
Flux.from(selectMessageUidByMailboxIdAndExtraConditionQuery(mailboxId,
                 IS_RECENT.eq(true), Limit.unlimited(), dslContext)))
diff --git 
a/mailbox/postgres/src/main/java/org/apache/james/mailbox/postgres/search/UnseenSearchOverride.java
 
b/mailbox/postgres/src/main/java/org/apache/james/mailbox/postgres/search/UnseenSearchOverride.java
new file mode 100644
index 0000000000..2bef17a9f9
--- /dev/null
+++ 
b/mailbox/postgres/src/main/java/org/apache/james/mailbox/postgres/search/UnseenSearchOverride.java
@@ -0,0 +1,93 @@
+/****************************************************************
+ * 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.postgres.search;
+
+import java.util.Optional;
+
+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.model.Mailbox;
+import org.apache.james.mailbox.model.MessageRange;
+import org.apache.james.mailbox.model.SearchQuery;
+import org.apache.james.mailbox.postgres.PostgresMailboxId;
+import org.apache.james.mailbox.postgres.mail.dao.PostgresMailboxMessageDAO;
+import org.apache.james.mailbox.store.search.ListeningMessageSearchIndex;
+
+import com.google.common.collect.ImmutableList;
+
+import reactor.core.publisher.Flux;
+
+public class UnseenSearchOverride implements 
ListeningMessageSearchIndex.SearchOverride {
+    private final PostgresMailboxMessageDAO dao;
+
+    @Inject
+    public UnseenSearchOverride(PostgresMailboxMessageDAO 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()
+                .anyMatch(criterion -> criterion instanceof 
SearchQuery.UidCriterion) ||
+                searchQuery.getCriteria().stream()
+                    .anyMatch(criterion -> criterion instanceof 
SearchQuery.AllCriterion);
+        }
+        return false;
+    }
+
+    @Override
+    public Flux<MessageUid> search(MailboxSession session, Mailbox mailbox, 
SearchQuery searchQuery) {
+        final Optional<SearchQuery.UidCriterion> maybeUidCriterion = 
searchQuery.getCriteria().stream()
+            .filter(criterion -> criterion instanceof SearchQuery.UidCriterion)
+            .map(SearchQuery.UidCriterion.class::cast)
+            .findFirst();
+
+        return maybeUidCriterion
+            .map(uidCriterion -> 
Flux.fromIterable(ImmutableList.copyOf(uidCriterion.getOperator().getRange()))
+                .concatMap(range -> dao.listUnseen((PostgresMailboxId) 
mailbox.getMailboxId(),
+                    MessageRange.range(range.getLowValue(), 
range.getHighValue()))))
+            .orElseGet(() -> dao.listUnseen((PostgresMailboxId) 
mailbox.getMailboxId()));
+    }
+}
diff --git 
a/mailbox/postgres/src/test/java/org/apache/james/mailbox/postgres/search/UnseenSearchOverrideTest.java
 
b/mailbox/postgres/src/test/java/org/apache/james/mailbox/postgres/search/UnseenSearchOverrideTest.java
new file mode 100644
index 0000000000..51d8825096
--- /dev/null
+++ 
b/mailbox/postgres/src/test/java/org/apache/james/mailbox/postgres/search/UnseenSearchOverrideTest.java
@@ -0,0 +1,216 @@
+/****************************************************************
+ * 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.postgres.search;
+
+import static javax.mail.Flags.Flag.SEEN;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Date;
+
+import javax.mail.Flags;
+
+import org.apache.james.backends.postgres.PostgresExtension;
+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.model.ByteContent;
+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.apache.james.mailbox.postgres.PostgresMailboxAggregateModule;
+import org.apache.james.mailbox.postgres.PostgresMailboxId;
+import org.apache.james.mailbox.postgres.PostgresMessageId;
+import org.apache.james.mailbox.postgres.mail.dao.PostgresMailboxMessageDAO;
+import org.apache.james.mailbox.postgres.mail.dao.PostgresMessageDAO;
+import org.apache.james.mailbox.store.mail.model.MailboxMessage;
+import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder;
+import org.apache.james.mailbox.store.mail.model.impl.SimpleMailboxMessage;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+public 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), 
PostgresMailboxId.generate());
+    private static final String BLOB_ID = "abc";
+    private static final Charset MESSAGE_CHARSET = StandardCharsets.UTF_8;
+    private static final String MESSAGE_CONTENT = "Simple message content";
+    private static final byte[] MESSAGE_CONTENT_BYTES = 
MESSAGE_CONTENT.getBytes(MESSAGE_CHARSET);
+    private static final ByteContent CONTENT_STREAM = new 
ByteContent(MESSAGE_CONTENT_BYTES);
+    private final static long SIZE = MESSAGE_CONTENT_BYTES.length;
+
+    @RegisterExtension
+    static PostgresExtension postgresExtension = 
PostgresExtension.withoutRowLevelSecurity(PostgresMailboxAggregateModule.MODULE);
+
+    private PostgresMailboxMessageDAO postgresMailboxMessageDAO;
+    private PostgresMessageDAO postgresMessageDAO;
+    private UnseenSearchOverride testee;
+
+    @BeforeEach
+    void setUp() {
+        postgresMessageDAO = new 
PostgresMessageDAO(postgresExtension.getPostgresExecutor());
+        postgresMailboxMessageDAO = new 
PostgresMailboxMessageDAO(postgresExtension.getPostgresExecutor());
+        testee = new UnseenSearchOverride(postgresMailboxMessageDAO);
+    }
+
+    @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);
+        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(SEEN));
+
+        assertThat(testee.search(MAILBOX_SESSION, MAILBOX,
+            SearchQuery.builder()
+                .andCriteria(SearchQuery.flagIsUnSet(SEEN))
+                .build()).collectList().block())
+            .containsOnly(messageUid, messageUid2);
+    }
+
+    @Test
+    void searchShouldSupportRanges() {
+        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(SEEN));
+        MessageUid messageUid4 = MessageUid.of(4);
+        insert(messageUid4, MAILBOX.getMailboxId(), new Flags());
+
+        assertThat(testee.search(MAILBOX_SESSION, MAILBOX,
+            SearchQuery.builder()
+                .andCriteria(SearchQuery.flagIsUnSet(SEEN))
+                .andCriterion(SearchQuery.uid(new 
SearchQuery.UidRange(MessageUid.of(2), MessageUid.of(4))))
+                .build()).collectList().block())
+            .containsOnly(messageUid2, messageUid4);
+    }
+
+    private void insert(MessageUid messageUid, MailboxId mailboxId, Flags 
flags) {
+        PostgresMessageId messageId = new 
PostgresMessageId.Factory().generate();
+        MailboxMessage message = SimpleMailboxMessage.builder()
+            .messageId(messageId)
+            .threadId(ThreadId.fromBaseMessageId(messageId))
+            .uid(messageUid)
+            .content(CONTENT_STREAM)
+            .size(SIZE)
+            .internalDate(new Date())
+            .bodyStartOctet(18)
+            .flags(flags)
+            .properties(new PropertyBuilder())
+            .mailboxId(mailboxId)
+            .modseq(ModSeq.of(1))
+            .build();
+        postgresMessageDAO.insert(message, BLOB_ID).block();
+        postgresMailboxMessageDAO.insert(message).block();
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscr...@james.apache.org
For additional commands, e-mail: notifications-h...@james.apache.org

Reply via email to