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 d7bd1be50dc857340d79068cd24ed6c07cfb22c9
Author: Quan Tran <[email protected]>
AuthorDate: Mon Mar 9 15:17:20 2026 +0700

    JAMES-4187 Drop Cassandra email query view look up table
    
    By refactoring EmailQueryView::delete(MailboxId mailboxId, MessageId 
messageId) to take into account receivedAt also.
---
 .../projections/CassandraEmailQueryView.java       | 65 ++++------------------
 .../CassandraEmailQueryViewDataDefinition.java     | 11 ----
 .../table/CassandraEmailQueryViewTable.java        |  1 -
 .../projections/PostgresEmailQueryView.java        |  2 +-
 .../james/jmap/api/projections/EmailQueryView.java |  2 +-
 .../memory/projections/MemoryEmailQueryView.java   |  2 +-
 .../api/projections/EmailQueryViewContract.java    | 12 +++-
 .../jmap/event/PopulateEmailQueryViewListener.java | 31 +++++++++--
 8 files changed, 49 insertions(+), 77 deletions(-)

diff --git 
a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/projections/CassandraEmailQueryView.java
 
b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/projections/CassandraEmailQueryView.java
index 614ccc2748..145dfec8c5 100644
--- 
a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/projections/CassandraEmailQueryView.java
+++ 
b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/projections/CassandraEmailQueryView.java
@@ -24,7 +24,6 @@ import static 
com.datastax.oss.driver.api.querybuilder.QueryBuilder.bindMarker;
 import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.deleteFrom;
 import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.insertInto;
 import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.selectFrom;
-import static 
org.apache.james.jmap.cassandra.projections.table.CassandraEmailQueryViewTable.DATE_LOOKUP_TABLE;
 import static 
org.apache.james.jmap.cassandra.projections.table.CassandraEmailQueryViewTable.MAILBOX_ID;
 import static 
org.apache.james.jmap.cassandra.projections.table.CassandraEmailQueryViewTable.MESSAGE_ID;
 import static 
org.apache.james.jmap.cassandra.projections.table.CassandraEmailQueryViewTable.RECEIVED_AT;
@@ -55,7 +54,6 @@ import com.datastax.oss.driver.api.core.CqlSession;
 import com.datastax.oss.driver.api.core.cql.PreparedStatement;
 import com.datastax.oss.driver.api.core.cql.Row;
 import com.datastax.oss.driver.api.core.type.codec.TypeCodecs;
-import com.datastax.oss.driver.api.querybuilder.QueryBuilder;
 
 import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
@@ -67,13 +65,9 @@ public class CassandraEmailQueryView implements 
EmailQueryView {
     private final PreparedStatement listMailboxContentByReceivedAt;
     private final PreparedStatement listMailboxContentSinceReceivedAt;
     private final PreparedStatement listMailboxContentBeforeReceivedAt;
-    private final PreparedStatement insertInLookupTable;
     private final PreparedStatement insertReceivedAt;
-    private final PreparedStatement deleteLookupRecord;
     private final PreparedStatement deleteReceivedAt;
-    private final PreparedStatement deleteAllLookupRecords;
     private final PreparedStatement deleteAllReceivedAt;
-    private final PreparedStatement lookupDate;
 
     @Inject
     public CassandraEmailQueryView(CqlSession session) {
@@ -102,12 +96,6 @@ public class CassandraEmailQueryView implements 
EmailQueryView {
             .limit(bindMarker(LIMIT_MARKER))
             .build());
 
-        insertInLookupTable = session.prepare(insertInto(DATE_LOOKUP_TABLE)
-            .value(MAILBOX_ID, bindMarker(MAILBOX_ID))
-            .value(MESSAGE_ID, bindMarker(MESSAGE_ID))
-            .value(RECEIVED_AT, bindMarker(RECEIVED_AT))
-            .build());
-
         insertReceivedAt = session.prepare(insertInto(TABLE_NAME_RECEIVED_AT)
             .value(MAILBOX_ID, bindMarker(MAILBOX_ID))
             .value(MESSAGE_ID, bindMarker(MESSAGE_ID))
@@ -115,29 +103,14 @@ public class CassandraEmailQueryView implements 
EmailQueryView {
             .value(THREAD_ID, bindMarker(THREAD_ID))
             .build());
 
-        deleteLookupRecord = session.prepare(deleteFrom(DATE_LOOKUP_TABLE)
-            .whereColumn(MAILBOX_ID).isEqualTo(bindMarker(MAILBOX_ID))
-            .whereColumn(MESSAGE_ID).isEqualTo(bindMarker(MESSAGE_ID))
-            .build());
-
-        deleteReceivedAt = 
session.prepare(QueryBuilder.deleteFrom(TABLE_NAME_RECEIVED_AT)
+        deleteReceivedAt = session.prepare(deleteFrom(TABLE_NAME_RECEIVED_AT)
             .whereColumn(MAILBOX_ID).isEqualTo(bindMarker(MAILBOX_ID))
             .whereColumn(MESSAGE_ID).isEqualTo(bindMarker(MESSAGE_ID))
             .whereColumn(RECEIVED_AT).isEqualTo(bindMarker(RECEIVED_AT))
             .build());
 
-        deleteAllLookupRecords = 
session.prepare(QueryBuilder.deleteFrom(DATE_LOOKUP_TABLE)
-            .whereColumn(MAILBOX_ID).isEqualTo(bindMarker(MAILBOX_ID))
-            .build());
-
-        deleteAllReceivedAt = 
session.prepare(QueryBuilder.deleteFrom(TABLE_NAME_RECEIVED_AT)
-            .whereColumn(MAILBOX_ID).isEqualTo(bindMarker(MAILBOX_ID))
-            .build());
-
-        lookupDate = session.prepare(selectFrom(DATE_LOOKUP_TABLE)
-            .all()
+        deleteAllReceivedAt = 
session.prepare(deleteFrom(TABLE_NAME_RECEIVED_AT)
             .whereColumn(MAILBOX_ID).isEqualTo(bindMarker(MAILBOX_ID))
-            .whereColumn(MESSAGE_ID).isEqualTo(bindMarker(MESSAGE_ID))
             .build());
     }
 
@@ -188,26 +161,14 @@ public class CassandraEmailQueryView implements 
EmailQueryView {
     }
 
     @Override
-    public Mono<Void> delete(MailboxId mailboxId, MessageId messageId) {
+    public Mono<Void> delete(MailboxId mailboxId, ZonedDateTime receivedAt, 
MessageId messageId) {
         CassandraMessageId cassandraMessageId = (CassandraMessageId) messageId;
         CassandraId cassandraId = (CassandraId) mailboxId;
 
-        return executor.executeSingleRow(lookupDate.bind()
-            .set(MAILBOX_ID, cassandraId.asUuid(), TypeCodecs.UUID)
-            .setUuid(MESSAGE_ID, cassandraMessageId.get()))
-            .flatMap(row -> doDelete(cassandraMessageId, cassandraId, row));
-    }
-
-    public Mono<? extends Void> doDelete(CassandraMessageId 
cassandraMessageId, CassandraId cassandraId, Row row) {
-        Instant receivedAt = row.getInstant(RECEIVED_AT);
-
         return executor.executeVoid(deleteReceivedAt.bind()
-                .set(MAILBOX_ID, cassandraId.asUuid(), TypeCodecs.UUID)
-                .setUuid(MESSAGE_ID, cassandraMessageId.get())
-                .setInstant(RECEIVED_AT, receivedAt))
-            .then(executor.executeVoid(deleteLookupRecord.bind()
-                .set(MAILBOX_ID, cassandraId.asUuid(), TypeCodecs.UUID)
-                .setUuid(MESSAGE_ID, cassandraMessageId.get())));
+            .set(MAILBOX_ID, cassandraId.asUuid(), TypeCodecs.UUID)
+            .setUuid(MESSAGE_ID, cassandraMessageId.get())
+            .setInstant(RECEIVED_AT, receivedAt.toInstant()));
     }
 
     @Override
@@ -215,9 +176,7 @@ public class CassandraEmailQueryView implements 
EmailQueryView {
         CassandraId cassandraId = (CassandraId) mailboxId;
 
         return executor.executeVoid(deleteAllReceivedAt.bind()
-                .set(MAILBOX_ID, cassandraId.asUuid(), TypeCodecs.UUID))
-            .then(executor.executeVoid(deleteAllLookupRecords.bind()
-                .setUuid(MAILBOX_ID, ((CassandraId) mailboxId).asUuid())));
+            .set(MAILBOX_ID, cassandraId.asUuid(), TypeCodecs.UUID));
     }
 
     @Override
@@ -225,14 +184,10 @@ public class CassandraEmailQueryView implements 
EmailQueryView {
         CassandraMessageId cassandraMessageId = (CassandraMessageId) messageId;
         CassandraId cassandraId = (CassandraId) mailboxId;
 
-        return executor.executeVoid(insertInLookupTable.bind()
+        return executor.executeVoid(insertReceivedAt.bind()
             .setUuid(MESSAGE_ID, cassandraMessageId.get())
             .set(MAILBOX_ID, cassandraId.asUuid(), TypeCodecs.UUID)
-            .setInstant(RECEIVED_AT, receivedAt.toInstant()))
-            .then(executor.executeVoid(insertReceivedAt.bind()
-                .setUuid(MESSAGE_ID, cassandraMessageId.get())
-                .set(MAILBOX_ID, cassandraId.asUuid(), TypeCodecs.UUID)
-                .setInstant(RECEIVED_AT, receivedAt.toInstant())
-                .setUuid(THREAD_ID, ((CassandraMessageId) 
threadId.getBaseMessageId()).get())));
+            .setInstant(RECEIVED_AT, receivedAt.toInstant())
+            .setUuid(THREAD_ID, ((CassandraMessageId) 
threadId.getBaseMessageId()).get()));
     }
 }
diff --git 
a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/projections/CassandraEmailQueryViewDataDefinition.java
 
b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/projections/CassandraEmailQueryViewDataDefinition.java
index 56fed840fd..9d7c31f8f8 100644
--- 
a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/projections/CassandraEmailQueryViewDataDefinition.java
+++ 
b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/projections/CassandraEmailQueryViewDataDefinition.java
@@ -22,7 +22,6 @@ package org.apache.james.jmap.cassandra.projections;
 import static 
com.datastax.oss.driver.api.core.metadata.schema.ClusteringOrder.DESC;
 import static 
com.datastax.oss.driver.api.querybuilder.SchemaBuilder.RowsPerPartition.rows;
 import static 
org.apache.james.backends.cassandra.utils.CassandraConstants.DEFAULT_CACHED_ROW_PER_PARTITION;
-import static 
org.apache.james.jmap.cassandra.projections.table.CassandraEmailQueryViewTable.DATE_LOOKUP_TABLE;
 import static 
org.apache.james.jmap.cassandra.projections.table.CassandraEmailQueryViewTable.MAILBOX_ID;
 import static 
org.apache.james.jmap.cassandra.projections.table.CassandraEmailQueryViewTable.MESSAGE_ID;
 import static 
org.apache.james.jmap.cassandra.projections.table.CassandraEmailQueryViewTable.RECEIVED_AT;
@@ -44,15 +43,5 @@ public interface CassandraEmailQueryViewDataDefinition {
             .withClusteringColumn(RECEIVED_AT, DataTypes.TIMESTAMP)
             .withClusteringColumn(MESSAGE_ID, DataTypes.UUID)
             .withColumn(THREAD_ID, DataTypes.UUID))
-
-        .table(DATE_LOOKUP_TABLE)
-        .comment("Given a MailboxId+MessageId lookup the dates of a message to 
delete it.")
-        .options(options -> options
-            .withCaching(true, rows(DEFAULT_CACHED_ROW_PER_PARTITION)))
-        .statement(statement -> types -> statement
-            .withPartitionKey(MAILBOX_ID, DataTypes.UUID)
-            .withClusteringColumn(MESSAGE_ID, DataTypes.UUID)
-            .withColumn(RECEIVED_AT, DataTypes.TIMESTAMP))
-
         .build();
 }
diff --git 
a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/projections/table/CassandraEmailQueryViewTable.java
 
b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/projections/table/CassandraEmailQueryViewTable.java
index 3629752f2d..c1ec984ef0 100644
--- 
a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/projections/table/CassandraEmailQueryViewTable.java
+++ 
b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/projections/table/CassandraEmailQueryViewTable.java
@@ -23,7 +23,6 @@ import com.datastax.oss.driver.api.core.CqlIdentifier;
 
 public interface CassandraEmailQueryViewTable {
     String TABLE_NAME_RECEIVED_AT = "email_query_view_received_at";
-    String DATE_LOOKUP_TABLE = "email_query_view_date_lookup";
 
     CqlIdentifier MAILBOX_ID = CqlIdentifier.fromCql("mailboxId");
     CqlIdentifier MESSAGE_ID = CqlIdentifier.fromCql("messageId");
diff --git 
a/server/data/data-jmap-postgres/src/main/java/org/apache/james/jmap/postgres/projections/PostgresEmailQueryView.java
 
b/server/data/data-jmap-postgres/src/main/java/org/apache/james/jmap/postgres/projections/PostgresEmailQueryView.java
index 585a4baba8..2701ab72e2 100644
--- 
a/server/data/data-jmap-postgres/src/main/java/org/apache/james/jmap/postgres/projections/PostgresEmailQueryView.java
+++ 
b/server/data/data-jmap-postgres/src/main/java/org/apache/james/jmap/postgres/projections/PostgresEmailQueryView.java
@@ -58,7 +58,7 @@ public class PostgresEmailQueryView implements EmailQueryView 
{
     }
 
     @Override
-    public Mono<Void> delete(MailboxId mailboxId, MessageId messageId) {
+    public Mono<Void> delete(MailboxId mailboxId, ZonedDateTime receivedAt, 
MessageId messageId) {
         return 
emailQueryViewDAO.delete(PostgresMailboxId.class.cast(mailboxId), 
PostgresMessageId.class.cast(messageId));
     }
 
diff --git 
a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/projections/EmailQueryView.java
 
b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/projections/EmailQueryView.java
index d0a1720181..148d5fe480 100644
--- 
a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/projections/EmailQueryView.java
+++ 
b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/projections/EmailQueryView.java
@@ -148,7 +148,7 @@ public interface EmailQueryView {
      */
     Flux<MessageId> listMailboxContentBeforeSortedByReceivedAt(MailboxId 
mailboxId, ZonedDateTime since, Limit limit, boolean collapseThreads);
 
-    Mono<Void> delete(MailboxId mailboxId, MessageId messageId);
+    Mono<Void> delete(MailboxId mailboxId, ZonedDateTime receivedAt, MessageId 
messageId);
 
     Mono<Void> delete(MailboxId mailboxId);
 
diff --git 
a/server/data/data-jmap/src/main/java/org/apache/james/jmap/memory/projections/MemoryEmailQueryView.java
 
b/server/data/data-jmap/src/main/java/org/apache/james/jmap/memory/projections/MemoryEmailQueryView.java
index dd649a4345..3571776dcc 100644
--- 
a/server/data/data-jmap/src/main/java/org/apache/james/jmap/memory/projections/MemoryEmailQueryView.java
+++ 
b/server/data/data-jmap/src/main/java/org/apache/james/jmap/memory/projections/MemoryEmailQueryView.java
@@ -93,7 +93,7 @@ public class MemoryEmailQueryView implements EmailQueryView {
     }
 
     @Override
-    public Mono<Void> delete(MailboxId mailboxId, MessageId messageId) {
+    public Mono<Void> delete(MailboxId mailboxId, ZonedDateTime receivedAt, 
MessageId messageId) {
         return Mono.fromRunnable(() -> entries.remove(mailboxId, messageId));
     }
 
diff --git 
a/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/projections/EmailQueryViewContract.java
 
b/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/projections/EmailQueryViewContract.java
index d7d799daaa..1396f8c446 100644
--- 
a/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/projections/EmailQueryViewContract.java
+++ 
b/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/projections/EmailQueryViewContract.java
@@ -93,7 +93,17 @@ public interface EmailQueryViewContract {
 
     @Test
     default void deleteShouldNotFailWhenEmpty() {
-        assertThatCode(() -> testee().delete(mailboxId1(), 
messageId4()).block()).doesNotThrowAnyException();
+        assertThatCode(() -> testee().delete(mailboxId1(), DATE_4, 
messageId4()).block()).doesNotThrowAnyException();
+    }
+
+    @Test
+    default void deleteShouldRemoveSavedEntry() {
+        testee().save(mailboxId1(), DATE_4, messageId1(), threadId1()).block();
+
+        testee().delete(mailboxId1(), DATE_4, messageId1()).block();
+
+        assertThat(testee().listMailboxContentSortedByReceivedAt(mailboxId1(), 
Limit.limit(12), !COLLAPSE_THREAD).collectList().block())
+            .isEmpty();
     }
 
     @Test
diff --git 
a/server/protocols/jmap-rfc-8621/src/main/java/org/apache/james/jmap/event/PopulateEmailQueryViewListener.java
 
b/server/protocols/jmap-rfc-8621/src/main/java/org/apache/james/jmap/event/PopulateEmailQueryViewListener.java
index 31fa0629a6..3408f57dac 100644
--- 
a/server/protocols/jmap-rfc-8621/src/main/java/org/apache/james/jmap/event/PopulateEmailQueryViewListener.java
+++ 
b/server/protocols/jmap-rfc-8621/src/main/java/org/apache/james/jmap/event/PopulateEmailQueryViewListener.java
@@ -22,6 +22,7 @@ package org.apache.james.jmap.event;
 import static jakarta.mail.Flags.Flag.DELETED;
 import static org.apache.james.util.ReactorUtils.publishIfPresent;
 
+import java.time.Instant;
 import java.time.ZoneOffset;
 import java.time.ZonedDateTime;
 import java.util.Optional;
@@ -110,8 +111,9 @@ public class PopulateEmailQueryViewListener implements 
ReactiveGroupEventListene
 
     private Publisher<Void> handleExpunged(Expunged expunged) {
         return Flux.fromStream(expunged.getUids().stream()
-            .map(uid -> expunged.getMetaData(uid).getMessageId()))
-            .concatMap(messageId -> 
viewManager.getEmailQueryView(expunged.getUsername()).delete(expunged.getMailboxId(),
 messageId))
+            .map(expunged::getMetaData))
+            .concatMap(metaData -> 
viewManager.getEmailQueryView(expunged.getUsername())
+                .delete(expunged.getMailboxId(), 
toReceivedAt(metaData.getInternalDate().toInstant()), metaData.getMessageId()))
             .then();
     }
 
@@ -121,9 +123,7 @@ public class PopulateEmailQueryViewListener implements 
ReactiveGroupEventListene
 
         Mono<Void> removeMessagesMarkedAsDeleted = 
Flux.fromIterable(flagsUpdated.getUpdatedFlags())
             .filter(updatedFlags -> updatedFlags.isModifiedToSet(DELETED))
-            .map(UpdatedFlags::getMessageId)
-            .handle(publishIfPresent())
-            .concatMap(messageId -> 
viewManager.getEmailQueryView(flagsUpdated.getUsername()).delete(flagsUpdated.getMailboxId(),
 messageId))
+            .concatMap(updatedFlags -> 
deleteViewForMessageMarkedAsDeleted(flagsUpdated, session, updatedFlags))
             .then();
 
         Mono<Void> addMessagesNoLongerMarkedAsDeleted = 
Flux.fromIterable(flagsUpdated.getUpdatedFlags())
@@ -171,9 +171,28 @@ public class PopulateEmailQueryViewListener implements 
ReactiveGroupEventListene
     }
 
     public Mono<Void> handleAdded(MailboxId mailboxId, MessageResult 
messageResult, Username username) {
-        ZonedDateTime receivedAt = 
ZonedDateTime.ofInstant(messageResult.getInternalDate().toInstant(), 
ZoneOffset.UTC);
+        ZonedDateTime receivedAt = 
toReceivedAt(messageResult.getInternalDate().toInstant());
         return viewManager.getEmailQueryView(username)
             .save(mailboxId, receivedAt, messageResult.getMessageId(), 
messageResult.getThreadId())
             .then();
     }
+
+    private Mono<Void> deleteViewForMessageMarkedAsDeleted(FlagsUpdated 
flagsUpdated, MailboxSession session, UpdatedFlags updatedFlags) {
+        return Mono.justOrEmpty(updatedFlags.getMessageId())
+            .flatMap(messageId -> resolveReceivedAt(updatedFlags, messageId, 
session)
+                .flatMap(receivedAt -> 
viewManager.getEmailQueryView(flagsUpdated.getUsername())
+                    .delete(flagsUpdated.getMailboxId(), receivedAt, 
messageId)))
+            .then();
+    }
+
+    private Mono<ZonedDateTime> resolveReceivedAt(UpdatedFlags updatedFlags, 
MessageId messageId, MailboxSession session) {
+        return Mono.justOrEmpty(updatedFlags.getInternalDate().map(date -> 
toReceivedAt(date.toInstant())))
+            
.switchIfEmpty(Flux.from(messageIdManager.getMessagesReactive(ImmutableList.of(messageId),
 FetchGroup.HEADERS, session))
+                .next()
+                .map(messageResult -> 
toReceivedAt(messageResult.getInternalDate().toInstant())));
+    }
+
+    private ZonedDateTime toReceivedAt(Instant internalDate) {
+        return ZonedDateTime.ofInstant(internalDate, ZoneOffset.UTC);
+    }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to