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


The following commit(s) were added to refs/heads/postgresql by this push:
     new 490163ab9b JAMES-2586 Implement PostgresQuotaCurrentValueDAO (#1813)
490163ab9b is described below

commit 490163ab9be5624420dc5c099763179639db2265
Author: Trần Hồng Quân <[email protected]>
AuthorDate: Mon Nov 27 11:30:30 2023 +0700

    JAMES-2586 Implement PostgresQuotaCurrentValueDAO (#1813)
---
 .../components/CassandraQuotaCurrentValueDao.java  |  64 +--------
 .../quota/CassandraQuotaCurrentValueDaoTest.java   |   8 +-
 .../quota/PostgresQuotaCurrentValueDAO.java        | 120 +++++++++++++++++
 .../postgres/quota/PostgresQuotaModule.java        |  59 +++++++++
 .../quota/PostgresQuotaCurrentValueDAOTest.java    | 147 +++++++++++++++++++++
 .../apache/james/core/quota/QuotaCurrentValue.java |  53 ++++++++
 .../quota/CassandraCurrentQuotaManagerV2.java      |   9 +-
 .../sieve/cassandra/CassandraSieveQuotaDAOV2.java  |   8 +-
 .../upload/CassandraUploadUsageRepository.java     |   7 +-
 9 files changed, 398 insertions(+), 77 deletions(-)

diff --git 
a/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/components/CassandraQuotaCurrentValueDao.java
 
b/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/components/CassandraQuotaCurrentValueDao.java
index 2c77929750..c968a0c4d5 100644
--- 
a/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/components/CassandraQuotaCurrentValueDao.java
+++ 
b/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/components/CassandraQuotaCurrentValueDao.java
@@ -30,8 +30,6 @@ import static 
org.apache.james.backends.cassandra.components.CassandraQuotaCurre
 import static 
org.apache.james.backends.cassandra.components.CassandraQuotaCurrentValueTable.QUOTA_TYPE;
 import static 
org.apache.james.backends.cassandra.components.CassandraQuotaCurrentValueTable.TABLE_NAME;
 
-import java.util.Objects;
-
 import javax.inject.Inject;
 
 import org.apache.james.backends.cassandra.utils.CassandraAsyncExecutor;
@@ -47,66 +45,12 @@ import com.datastax.oss.driver.api.core.cql.Row;
 import com.datastax.oss.driver.api.querybuilder.delete.Delete;
 import com.datastax.oss.driver.api.querybuilder.select.Select;
 import com.datastax.oss.driver.api.querybuilder.update.Update;
-import com.google.common.base.MoreObjects;
 
 import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
 
 public class CassandraQuotaCurrentValueDao {
 
-    public static class QuotaKey {
-
-        public static QuotaKey of(QuotaComponent component, String identifier, 
QuotaType quotaType) {
-            return new QuotaKey(component, identifier, quotaType);
-        }
-
-        private final QuotaComponent quotaComponent;
-        private final String identifier;
-        private final QuotaType quotaType;
-
-        public QuotaComponent getQuotaComponent() {
-            return quotaComponent;
-        }
-
-        public String getIdentifier() {
-            return identifier;
-        }
-
-        public QuotaType getQuotaType() {
-            return quotaType;
-        }
-
-        private QuotaKey(QuotaComponent quotaComponent, String identifier, 
QuotaType quotaType) {
-            this.quotaComponent = quotaComponent;
-            this.identifier = identifier;
-            this.quotaType = quotaType;
-        }
-
-        @Override
-        public final int hashCode() {
-            return Objects.hash(quotaComponent, identifier, quotaType);
-        }
-
-        @Override
-        public final boolean equals(Object o) {
-            if (o instanceof QuotaKey) {
-                QuotaKey other = (QuotaKey) o;
-                return Objects.equals(quotaComponent, other.quotaComponent)
-                    && Objects.equals(identifier, other.identifier)
-                    && Objects.equals(quotaType, other.quotaType);
-            }
-            return false;
-        }
-
-        public String toString() {
-            return MoreObjects.toStringHelper(this)
-                .add("quotaComponent", quotaComponent)
-                .add("identifier", identifier)
-                .add("quotaType", quotaType)
-                .toString();
-        }
-    }
-
     private static final Logger LOGGER = 
LoggerFactory.getLogger(CassandraQuotaCurrentValueDao.class);
 
     private final CassandraAsyncExecutor queryExecutor;
@@ -126,7 +70,7 @@ public class CassandraQuotaCurrentValueDao {
         this.deleteQuotaCurrentValueStatement = 
session.prepare(deleteQuotaCurrentValueStatement().build());
     }
 
-    public Mono<Void> increase(QuotaKey quotaKey, long amount) {
+    public Mono<Void> increase(QuotaCurrentValue.Key quotaKey, long amount) {
         return queryExecutor.executeVoid(increaseStatement.bind()
             .setString(QUOTA_COMPONENT, 
quotaKey.getQuotaComponent().getValue())
             .setString(IDENTIFIER, quotaKey.getIdentifier())
@@ -139,7 +83,7 @@ public class CassandraQuotaCurrentValueDao {
             });
     }
 
-    public Mono<Void> decrease(QuotaKey quotaKey, long amount) {
+    public Mono<Void> decrease(QuotaCurrentValue.Key quotaKey, long amount) {
         return queryExecutor.executeVoid(decreaseStatement.bind()
             .setString(QUOTA_COMPONENT, 
quotaKey.getQuotaComponent().getValue())
             .setString(IDENTIFIER, quotaKey.getIdentifier())
@@ -152,7 +96,7 @@ public class CassandraQuotaCurrentValueDao {
             });
     }
 
-    public Mono<QuotaCurrentValue> getQuotaCurrentValue(QuotaKey quotaKey) {
+    public Mono<QuotaCurrentValue> getQuotaCurrentValue(QuotaCurrentValue.Key 
quotaKey) {
         return 
queryExecutor.executeSingleRow(getQuotaCurrentValueStatement.bind()
             .setString(QUOTA_COMPONENT, 
quotaKey.getQuotaComponent().getValue())
             .setString(IDENTIFIER, quotaKey.getIdentifier())
@@ -160,7 +104,7 @@ public class CassandraQuotaCurrentValueDao {
             .map(row -> convertRowToModel(row));
     }
 
-    public Mono<Void> deleteQuotaCurrentValue(QuotaKey quotaKey) {
+    public Mono<Void> deleteQuotaCurrentValue(QuotaCurrentValue.Key quotaKey) {
         return 
queryExecutor.executeVoid(deleteQuotaCurrentValueStatement.bind()
             .setString(QUOTA_COMPONENT, 
quotaKey.getQuotaComponent().getValue())
             .setString(IDENTIFIER, quotaKey.getIdentifier())
diff --git 
a/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/quota/CassandraQuotaCurrentValueDaoTest.java
 
b/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/quota/CassandraQuotaCurrentValueDaoTest.java
index baa6eea80b..344e272acb 100644
--- 
a/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/quota/CassandraQuotaCurrentValueDaoTest.java
+++ 
b/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/quota/CassandraQuotaCurrentValueDaoTest.java
@@ -24,10 +24,8 @@ import static org.assertj.core.api.Assertions.assertThat;
 import java.util.List;
 
 import org.apache.james.backends.cassandra.CassandraClusterExtension;
-import org.apache.james.backends.cassandra.components.CassandraModule;
 import 
org.apache.james.backends.cassandra.components.CassandraMutualizedQuotaModule;
 import 
org.apache.james.backends.cassandra.components.CassandraQuotaCurrentValueDao;
-import 
org.apache.james.backends.cassandra.components.CassandraQuotaCurrentValueDao.QuotaKey;
 import org.apache.james.core.quota.QuotaComponent;
 import org.apache.james.core.quota.QuotaCurrentValue;
 import org.apache.james.core.quota.QuotaType;
@@ -37,7 +35,7 @@ import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.RegisterExtension;
 
 public class CassandraQuotaCurrentValueDaoTest {
-    private static final QuotaKey QUOTA_KEY = 
QuotaKey.of(QuotaComponent.MAILBOX, "[email protected]", QuotaType.SIZE);
+    private static final QuotaCurrentValue.Key QUOTA_KEY = 
QuotaCurrentValue.Key.of(QuotaComponent.MAILBOX, "[email protected]", 
QuotaType.SIZE);
 
     private CassandraQuotaCurrentValueDao cassandraQuotaCurrentValueDao;
 
@@ -93,7 +91,7 @@ public class CassandraQuotaCurrentValueDaoTest {
 
     @Test
     void deleteQuotaCurrentValueShouldDeleteSuccessfully() {
-        QuotaKey quotaKey = QuotaKey.of(QuotaComponent.MAILBOX, 
"[email protected]", QuotaType.SIZE);
+        QuotaCurrentValue.Key quotaKey = 
QuotaCurrentValue.Key.of(QuotaComponent.MAILBOX, "[email protected]", 
QuotaType.SIZE);
         cassandraQuotaCurrentValueDao.increase(quotaKey, 100L).block();
         
cassandraQuotaCurrentValueDao.deleteQuotaCurrentValue(quotaKey).block();
 
@@ -126,7 +124,7 @@ public class CassandraQuotaCurrentValueDaoTest {
 
     @Test
     void getQuotasByComponentShouldGetAllQuotaTypesSuccessfully() {
-        QuotaKey countQuotaKey = QuotaKey.of(QuotaComponent.MAILBOX, 
"[email protected]", QuotaType.COUNT);
+        QuotaCurrentValue.Key countQuotaKey = 
QuotaCurrentValue.Key.of(QuotaComponent.MAILBOX, "[email protected]", 
QuotaType.COUNT);
 
         QuotaCurrentValue expectedQuotaSize = 
QuotaCurrentValue.builder().quotaComponent(QUOTA_KEY.getQuotaComponent())
             
.identifier(QUOTA_KEY.getIdentifier()).quotaType(QUOTA_KEY.getQuotaType()).currentValue(100l).build();
diff --git 
a/backends-common/postgres/src/main/java/org/apache/james/backends/postgres/quota/PostgresQuotaCurrentValueDAO.java
 
b/backends-common/postgres/src/main/java/org/apache/james/backends/postgres/quota/PostgresQuotaCurrentValueDAO.java
new file mode 100644
index 0000000000..8f5c7eea6c
--- /dev/null
+++ 
b/backends-common/postgres/src/main/java/org/apache/james/backends/postgres/quota/PostgresQuotaCurrentValueDAO.java
@@ -0,0 +1,120 @@
+/****************************************************************
+ * 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.backends.postgres.quota;
+
+import static 
org.apache.james.backends.postgres.quota.PostgresQuotaModule.PostgresQuotaCurrentValueTable.COMPONENT;
+import static 
org.apache.james.backends.postgres.quota.PostgresQuotaModule.PostgresQuotaCurrentValueTable.CURRENT_VALUE;
+import static 
org.apache.james.backends.postgres.quota.PostgresQuotaModule.PostgresQuotaCurrentValueTable.IDENTIFIER;
+import static 
org.apache.james.backends.postgres.quota.PostgresQuotaModule.PostgresQuotaCurrentValueTable.PRIMARY_KEY_CONSTRAINT_NAME;
+import static 
org.apache.james.backends.postgres.quota.PostgresQuotaModule.PostgresQuotaCurrentValueTable.TABLE_NAME;
+import static 
org.apache.james.backends.postgres.quota.PostgresQuotaModule.PostgresQuotaCurrentValueTable.TYPE;
+
+import java.util.function.Function;
+
+import org.apache.james.backends.postgres.utils.PostgresExecutor;
+import org.apache.james.core.quota.QuotaComponent;
+import org.apache.james.core.quota.QuotaCurrentValue;
+import org.apache.james.core.quota.QuotaType;
+import org.jooq.Record;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+public class PostgresQuotaCurrentValueDAO {
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(PostgresQuotaCurrentValueDAO.class);
+
+    private final PostgresExecutor postgresExecutor;
+
+    public PostgresQuotaCurrentValueDAO(PostgresExecutor postgresExecutor) {
+        this.postgresExecutor = postgresExecutor;
+    }
+
+    public Mono<Void> increase(QuotaCurrentValue.Key quotaKey, long amount) {
+        return postgresExecutor.executeVoid(dslContext -> 
Mono.from(dslContext.insertInto(TABLE_NAME)
+            .set(IDENTIFIER, quotaKey.getIdentifier())
+            .set(COMPONENT, quotaKey.getQuotaComponent().getValue())
+            .set(TYPE, quotaKey.getQuotaType().getValue())
+            .set(CURRENT_VALUE, amount)
+            .onConflictOnConstraint(PRIMARY_KEY_CONSTRAINT_NAME)
+            .doUpdate()
+            .set(CURRENT_VALUE, CURRENT_VALUE.plus(amount))))
+            .onErrorResume(ex -> {
+                LOGGER.warn("Failure when increasing {} {} quota for {}. Quota 
current value is thus not updated and needs re-computation",
+                    quotaKey.getQuotaComponent().getValue(), 
quotaKey.getQuotaType().getValue(), quotaKey.getIdentifier(), ex);
+                return Mono.empty();
+            });
+    }
+
+    public Mono<Void> decrease(QuotaCurrentValue.Key quotaKey, long amount) {
+        return postgresExecutor.executeVoid(dslContext -> 
Mono.from(dslContext.insertInto(TABLE_NAME)
+            .set(IDENTIFIER, quotaKey.getIdentifier())
+            .set(COMPONENT, quotaKey.getQuotaComponent().getValue())
+            .set(TYPE, quotaKey.getQuotaType().getValue())
+            .set(CURRENT_VALUE, 0L)
+            .onConflictOnConstraint(PRIMARY_KEY_CONSTRAINT_NAME)
+            .doUpdate()
+            .set(CURRENT_VALUE, CURRENT_VALUE.minus(amount))))
+            .onErrorResume(ex -> {
+                LOGGER.warn("Failure when decreasing {} {} quota for {}. Quota 
current value is thus not updated and needs re-computation",
+                    quotaKey.getQuotaComponent().getValue(), 
quotaKey.getQuotaType().getValue(), quotaKey.getIdentifier(), ex);
+                return Mono.empty();
+            });
+    }
+
+    public Mono<QuotaCurrentValue> getQuotaCurrentValue(QuotaCurrentValue.Key 
quotaKey) {
+        return postgresExecutor.executeRow(dslContext -> 
Mono.from(dslContext.select(CURRENT_VALUE)
+            .from(TABLE_NAME)
+            .where(IDENTIFIER.eq(quotaKey.getIdentifier()),
+                COMPONENT.eq(quotaKey.getQuotaComponent().getValue()),
+                TYPE.eq(quotaKey.getQuotaType().getValue()))))
+            .map(toQuotaCurrentValue(quotaKey));
+    }
+
+    public Mono<Void> deleteQuotaCurrentValue(QuotaCurrentValue.Key quotaKey) {
+        return postgresExecutor.executeVoid(dslContext -> 
Mono.from(dslContext.deleteFrom(TABLE_NAME)
+                .where(IDENTIFIER.eq(quotaKey.getIdentifier()),
+                    COMPONENT.eq(quotaKey.getQuotaComponent().getValue()),
+                    TYPE.eq(quotaKey.getQuotaType().getValue()))));
+    }
+
+    public Flux<QuotaCurrentValue> getQuotaCurrentValues(QuotaComponent 
quotaComponent, String identifier) {
+        return postgresExecutor.executeRows(dslContext -> 
Flux.from(dslContext.select(TYPE, CURRENT_VALUE)
+                .from(TABLE_NAME)
+                .where(IDENTIFIER.eq(identifier),
+                    COMPONENT.eq(quotaComponent.getValue()))))
+            .map(toQuotaCurrentValue(quotaComponent, identifier));
+    }
+
+    private Function<Record, QuotaCurrentValue> 
toQuotaCurrentValue(QuotaCurrentValue.Key quotaKey) {
+        return record -> 
QuotaCurrentValue.builder().quotaComponent(quotaKey.getQuotaComponent())
+            .identifier(quotaKey.getIdentifier())
+            .quotaType(quotaKey.getQuotaType())
+            .currentValue(record.get(CURRENT_VALUE)).build();
+    }
+
+    private static Function<Record, QuotaCurrentValue> 
toQuotaCurrentValue(QuotaComponent quotaComponent, String identifier) {
+        return record -> 
QuotaCurrentValue.builder().quotaComponent(quotaComponent)
+                .identifier(identifier)
+                .quotaType(QuotaType.of(record.get(TYPE)))
+                .currentValue(record.get(CURRENT_VALUE)).build();
+    }
+}
diff --git 
a/backends-common/postgres/src/main/java/org/apache/james/backends/postgres/quota/PostgresQuotaModule.java
 
b/backends-common/postgres/src/main/java/org/apache/james/backends/postgres/quota/PostgresQuotaModule.java
new file mode 100644
index 0000000000..dad84108d0
--- /dev/null
+++ 
b/backends-common/postgres/src/main/java/org/apache/james/backends/postgres/quota/PostgresQuotaModule.java
@@ -0,0 +1,59 @@
+/****************************************************************
+ * 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.backends.postgres.quota;
+
+import static org.jooq.impl.DSL.name;
+import static org.jooq.impl.SQLDataType.BIGINT;
+
+import org.apache.james.backends.postgres.PostgresModule;
+import org.apache.james.backends.postgres.PostgresTable;
+import org.jooq.Field;
+import org.jooq.Name;
+import org.jooq.Record;
+import org.jooq.Table;
+import org.jooq.impl.DSL;
+import org.jooq.impl.SQLDataType;
+
+public interface PostgresQuotaModule {
+    interface PostgresQuotaCurrentValueTable {
+        Table<Record> TABLE_NAME = DSL.table("quota_current_value");
+
+        Field<String> IDENTIFIER = DSL.field("identifier", 
SQLDataType.VARCHAR.notNull());
+        Field<String> COMPONENT = DSL.field("component", 
SQLDataType.VARCHAR.notNull());
+        Field<String> TYPE = DSL.field("type", SQLDataType.VARCHAR.notNull());
+        Field<Long> CURRENT_VALUE = DSL.field(name(TABLE_NAME.getName(), 
"current_value"), BIGINT.notNull());
+
+        Name PRIMARY_KEY_CONSTRAINT_NAME = 
DSL.name("quota_current_value_primary_key");
+
+        PostgresTable TABLE = PostgresTable.name(TABLE_NAME.getName())
+            .createTableStep(((dsl, tableName) -> 
dsl.createTableIfNotExists(tableName)
+                .column(IDENTIFIER)
+                .column(COMPONENT)
+                .column(TYPE)
+                .column(CURRENT_VALUE)
+                .constraint(DSL.constraint(PRIMARY_KEY_CONSTRAINT_NAME)
+                    .primaryKey(IDENTIFIER, COMPONENT, TYPE))))
+            .disableRowLevelSecurity();
+    }
+
+    PostgresModule MODULE = PostgresModule.builder()
+        .addTable(PostgresQuotaCurrentValueTable.TABLE)
+        .build();
+}
diff --git 
a/backends-common/postgres/src/test/java/org/apache/james/backends/postgres/quota/PostgresQuotaCurrentValueDAOTest.java
 
b/backends-common/postgres/src/test/java/org/apache/james/backends/postgres/quota/PostgresQuotaCurrentValueDAOTest.java
new file mode 100644
index 0000000000..0164d3bab6
--- /dev/null
+++ 
b/backends-common/postgres/src/test/java/org/apache/james/backends/postgres/quota/PostgresQuotaCurrentValueDAOTest.java
@@ -0,0 +1,147 @@
+/****************************************************************
+ * 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.backends.postgres.quota;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.List;
+
+import org.apache.james.backends.postgres.PostgresExtension;
+import org.apache.james.core.quota.QuotaComponent;
+import org.apache.james.core.quota.QuotaCurrentValue;
+import org.apache.james.core.quota.QuotaType;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+class PostgresQuotaCurrentValueDAOTest {
+    @RegisterExtension
+    static PostgresExtension postgresExtension = 
PostgresExtension.withoutRowLevelSecurity(PostgresQuotaModule.MODULE);
+
+    private static final QuotaCurrentValue.Key QUOTA_KEY = 
QuotaCurrentValue.Key.of(QuotaComponent.MAILBOX, "[email protected]", 
QuotaType.SIZE);
+
+    private PostgresQuotaCurrentValueDAO postgresQuotaCurrentValueDAO;
+
+    @BeforeEach
+    void setup() {
+        postgresQuotaCurrentValueDAO = new 
PostgresQuotaCurrentValueDAO(postgresExtension.getPostgresExecutor());
+    }
+
+    @Test
+    void increaseQuotaCurrentValueShouldCreateNewRowSuccessfully() {
+        postgresQuotaCurrentValueDAO.increase(QUOTA_KEY, 100L).block();
+
+        
assertThat(postgresQuotaCurrentValueDAO.getQuotaCurrentValue(QUOTA_KEY).block().getCurrentValue())
+            .isEqualTo(100L);
+    }
+
+    @Test
+    void 
increaseQuotaCurrentValueShouldCreateNewRowSuccessfullyWhenIncreaseAmountIsZero()
 {
+        postgresQuotaCurrentValueDAO.increase(QUOTA_KEY, 0L).block();
+
+        
assertThat(postgresQuotaCurrentValueDAO.getQuotaCurrentValue(QUOTA_KEY).block().getCurrentValue())
+            .isZero();
+    }
+
+    @Test
+    void increaseQuotaCurrentValueShouldIncreaseValueSuccessfully() {
+        
assertThat(postgresQuotaCurrentValueDAO.getQuotaCurrentValue(QUOTA_KEY).block()).isNull();
+
+        postgresQuotaCurrentValueDAO.increase(QUOTA_KEY, 100L).block();
+        postgresQuotaCurrentValueDAO.increase(QUOTA_KEY, 100L).block();
+
+        
assertThat(postgresQuotaCurrentValueDAO.getQuotaCurrentValue(QUOTA_KEY).block().getCurrentValue())
+            .isEqualTo(200L);
+    }
+
+    @Test
+    void 
increaseQuotaCurrentValueShouldDecreaseValueSuccessfullyWhenIncreaseAmountIsNegative()
 {
+        postgresQuotaCurrentValueDAO.increase(QUOTA_KEY, 200L).block();
+        postgresQuotaCurrentValueDAO.increase(QUOTA_KEY, -100L).block();
+
+        
assertThat(postgresQuotaCurrentValueDAO.getQuotaCurrentValue(QUOTA_KEY).block().getCurrentValue())
+            .isEqualTo(100L);
+    }
+
+    @Test
+    void decreaseQuotaCurrentValueShouldDecreaseValueSuccessfully() {
+        postgresQuotaCurrentValueDAO.increase(QUOTA_KEY, 200L).block();
+        postgresQuotaCurrentValueDAO.decrease(QUOTA_KEY, 100L).block();
+
+        
assertThat(postgresQuotaCurrentValueDAO.getQuotaCurrentValue(QUOTA_KEY).block().getCurrentValue())
+            .isEqualTo(100L);
+    }
+
+    @Test
+    void decreaseQuotaCurrentValueDownToNegativeShouldAllowNegativeValue() {
+        postgresQuotaCurrentValueDAO.increase(QUOTA_KEY, 100L).block();
+        postgresQuotaCurrentValueDAO.decrease(QUOTA_KEY, 1000L).block();
+
+        
assertThat(postgresQuotaCurrentValueDAO.getQuotaCurrentValue(QUOTA_KEY).block().getCurrentValue())
+            .isEqualTo(-900L);
+    }
+
+    @Test
+    void 
decreaseQuotaCurrentValueWhenNoRecordYetShouldNotFailAndSetValueToZero() {
+        postgresQuotaCurrentValueDAO.decrease(QUOTA_KEY, 1000L).block();
+
+        
assertThat(postgresQuotaCurrentValueDAO.getQuotaCurrentValue(QUOTA_KEY).block().getCurrentValue())
+            .isZero();
+    }
+
+    @Test
+    void deleteQuotaCurrentValueShouldDeleteSuccessfully() {
+        QuotaCurrentValue.Key quotaKey = 
QuotaCurrentValue.Key.of(QuotaComponent.MAILBOX, "[email protected]", 
QuotaType.SIZE);
+        postgresQuotaCurrentValueDAO.increase(quotaKey, 100L).block();
+        postgresQuotaCurrentValueDAO.deleteQuotaCurrentValue(quotaKey).block();
+
+        
assertThat(postgresQuotaCurrentValueDAO.getQuotaCurrentValue(quotaKey).block())
+            .isNull();
+    }
+
+    @Test
+    void deleteQuotaCurrentValueShouldResetCounterForever() {
+        postgresQuotaCurrentValueDAO.increase(QUOTA_KEY, 100L).block();
+        
postgresQuotaCurrentValueDAO.deleteQuotaCurrentValue(QUOTA_KEY).block();
+        postgresQuotaCurrentValueDAO.increase(QUOTA_KEY, 100L).block();
+
+        
assertThat(postgresQuotaCurrentValueDAO.getQuotaCurrentValue(QUOTA_KEY).block().getCurrentValue())
+            .isEqualTo(100L);
+    }
+
+    @Test
+    void getQuotasByComponentShouldGetAllQuotaTypesSuccessfully() {
+        QuotaCurrentValue.Key countQuotaKey = 
QuotaCurrentValue.Key.of(QuotaComponent.MAILBOX, "[email protected]", 
QuotaType.COUNT);
+
+        QuotaCurrentValue expectedQuotaSize = 
QuotaCurrentValue.builder().quotaComponent(QUOTA_KEY.getQuotaComponent())
+            
.identifier(QUOTA_KEY.getIdentifier()).quotaType(QUOTA_KEY.getQuotaType()).currentValue(100L).build();
+        QuotaCurrentValue expectedQuotaCount = 
QuotaCurrentValue.builder().quotaComponent(countQuotaKey.getQuotaComponent())
+            
.identifier(countQuotaKey.getIdentifier()).quotaType(countQuotaKey.getQuotaType()).currentValue(56L).build();
+
+        postgresQuotaCurrentValueDAO.increase(QUOTA_KEY, 100L).block();
+        postgresQuotaCurrentValueDAO.increase(countQuotaKey, 56L).block();
+
+        List<QuotaCurrentValue> actual = 
postgresQuotaCurrentValueDAO.getQuotaCurrentValues(QUOTA_KEY.getQuotaComponent(),
 QUOTA_KEY.getIdentifier())
+            .collectList()
+            .block();
+
+        assertThat(actual).containsExactlyInAnyOrder(expectedQuotaSize, 
expectedQuotaCount);
+    }
+}
diff --git 
a/core/src/main/java/org/apache/james/core/quota/QuotaCurrentValue.java 
b/core/src/main/java/org/apache/james/core/quota/QuotaCurrentValue.java
index 682f10c7bc..c1b38bb819 100644
--- a/core/src/main/java/org/apache/james/core/quota/QuotaCurrentValue.java
+++ b/core/src/main/java/org/apache/james/core/quota/QuotaCurrentValue.java
@@ -26,6 +26,59 @@ import com.google.common.base.Preconditions;
 
 public class QuotaCurrentValue {
 
+    public static class Key {
+
+        public static Key of(QuotaComponent component, String identifier, 
QuotaType quotaType) {
+            return new Key(component, identifier, quotaType);
+        }
+
+        private final QuotaComponent quotaComponent;
+        private final String identifier;
+        private final QuotaType quotaType;
+
+        public QuotaComponent getQuotaComponent() {
+            return quotaComponent;
+        }
+
+        public String getIdentifier() {
+            return identifier;
+        }
+
+        public QuotaType getQuotaType() {
+            return quotaType;
+        }
+
+        private Key(QuotaComponent quotaComponent, String identifier, 
QuotaType quotaType) {
+            this.quotaComponent = quotaComponent;
+            this.identifier = identifier;
+            this.quotaType = quotaType;
+        }
+
+        @Override
+        public final int hashCode() {
+            return Objects.hash(quotaComponent, identifier, quotaType);
+        }
+
+        @Override
+        public final boolean equals(Object o) {
+            if (o instanceof Key) {
+                Key other = (Key) o;
+                return Objects.equals(quotaComponent, other.quotaComponent)
+                    && Objects.equals(identifier, other.identifier)
+                    && Objects.equals(quotaType, other.quotaType);
+            }
+            return false;
+        }
+
+        public String toString() {
+            return MoreObjects.toStringHelper(this)
+                .add("quotaComponent", quotaComponent)
+                .add("identifier", identifier)
+                .add("quotaType", quotaType)
+                .toString();
+        }
+    }
+
     public static class Builder {
         private QuotaComponent quotaComponent;
         private String identifier;
diff --git 
a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/quota/CassandraCurrentQuotaManagerV2.java
 
b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/quota/CassandraCurrentQuotaManagerV2.java
index 15746cd2e6..934eb4cde6 100644
--- 
a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/quota/CassandraCurrentQuotaManagerV2.java
+++ 
b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/quota/CassandraCurrentQuotaManagerV2.java
@@ -26,7 +26,6 @@ import java.util.function.Predicate;
 import javax.inject.Inject;
 
 import 
org.apache.james.backends.cassandra.components.CassandraQuotaCurrentValueDao;
-import 
org.apache.james.backends.cassandra.components.CassandraQuotaCurrentValueDao.QuotaKey;
 import org.apache.james.core.quota.QuotaComponent;
 import org.apache.james.core.quota.QuotaCountUsage;
 import org.apache.james.core.quota.QuotaCurrentValue;
@@ -117,16 +116,16 @@ public class CassandraCurrentQuotaManagerV2 implements 
CurrentQuotaManager {
             });
     }
 
-    private QuotaKey asQuotaKeyCount(QuotaRoot quotaRoot) {
+    private QuotaCurrentValue.Key asQuotaKeyCount(QuotaRoot quotaRoot) {
         return asQuotaKey(quotaRoot, QuotaType.COUNT);
     }
 
-    private QuotaKey asQuotaKeySize(QuotaRoot quotaRoot) {
+    private QuotaCurrentValue.Key asQuotaKeySize(QuotaRoot quotaRoot) {
         return asQuotaKey(quotaRoot, QuotaType.SIZE);
     }
 
-    private QuotaKey asQuotaKey(QuotaRoot quotaRoot, QuotaType quotaType) {
-        return QuotaKey.of(
+    private QuotaCurrentValue.Key asQuotaKey(QuotaRoot quotaRoot, QuotaType 
quotaType) {
+        return QuotaCurrentValue.Key.of(
             QuotaComponent.MAILBOX,
             quotaRoot.asString(),
             quotaType);
diff --git 
a/server/data/data-cassandra/src/main/java/org/apache/james/sieve/cassandra/CassandraSieveQuotaDAOV2.java
 
b/server/data/data-cassandra/src/main/java/org/apache/james/sieve/cassandra/CassandraSieveQuotaDAOV2.java
index 98168db81e..d68165ef24 100644
--- 
a/server/data/data-cassandra/src/main/java/org/apache/james/sieve/cassandra/CassandraSieveQuotaDAOV2.java
+++ 
b/server/data/data-cassandra/src/main/java/org/apache/james/sieve/cassandra/CassandraSieveQuotaDAOV2.java
@@ -51,14 +51,14 @@ public class CassandraSieveQuotaDAOV2 implements 
CassandraSieveQuotaDAO {
 
     @Override
     public Mono<Long> spaceUsedBy(Username username) {
-        CassandraQuotaCurrentValueDao.QuotaKey quotaKey = asQuotaKey(username);
+        QuotaCurrentValue.Key quotaKey = asQuotaKey(username);
 
         return 
currentValueDao.getQuotaCurrentValue(quotaKey).map(QuotaCurrentValue::getCurrentValue)
             .switchIfEmpty(Mono.just(0L));
     }
 
-    private CassandraQuotaCurrentValueDao.QuotaKey asQuotaKey(Username 
username) {
-        return CassandraQuotaCurrentValueDao.QuotaKey.of(
+    private QuotaCurrentValue.Key asQuotaKey(Username username) {
+        return QuotaCurrentValue.Key.of(
             QUOTA_COMPONENT,
             username.asString(),
             QuotaType.SIZE);
@@ -66,7 +66,7 @@ public class CassandraSieveQuotaDAOV2 implements 
CassandraSieveQuotaDAO {
 
     @Override
     public Mono<Void> updateSpaceUsed(Username username, long spaceUsed) {
-        CassandraQuotaCurrentValueDao.QuotaKey quotaKey = asQuotaKey(username);
+        QuotaCurrentValue.Key quotaKey = asQuotaKey(username);
 
         return currentValueDao.deleteQuotaCurrentValue(quotaKey)
             .then(currentValueDao.increase(quotaKey, spaceUsed));
diff --git 
a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/upload/CassandraUploadUsageRepository.java
 
b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/upload/CassandraUploadUsageRepository.java
index ebf61da7c5..6a4520615c 100644
--- 
a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/upload/CassandraUploadUsageRepository.java
+++ 
b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/upload/CassandraUploadUsageRepository.java
@@ -24,6 +24,7 @@ import javax.inject.Inject;
 import 
org.apache.james.backends.cassandra.components.CassandraQuotaCurrentValueDao;
 import org.apache.james.core.Username;
 import org.apache.james.core.quota.QuotaComponent;
+import org.apache.james.core.quota.QuotaCurrentValue;
 import org.apache.james.core.quota.QuotaSizeUsage;
 import org.apache.james.core.quota.QuotaType;
 import org.apache.james.jmap.api.upload.UploadUsageRepository;
@@ -43,19 +44,19 @@ public class CassandraUploadUsageRepository implements 
UploadUsageRepository {
 
     @Override
     public Mono<Void> increaseSpace(Username username, QuotaSizeUsage usage) {
-        return 
cassandraQuotaCurrentValueDao.increase(CassandraQuotaCurrentValueDao.QuotaKey.of(QuotaComponent.JMAP_UPLOADS,
 username.asString(), QuotaType.SIZE),
+        return 
cassandraQuotaCurrentValueDao.increase(QuotaCurrentValue.Key.of(QuotaComponent.JMAP_UPLOADS,
 username.asString(), QuotaType.SIZE),
             usage.asLong());
     }
 
     @Override
     public Mono<Void> decreaseSpace(Username username, QuotaSizeUsage usage) {
-        return 
cassandraQuotaCurrentValueDao.decrease(CassandraQuotaCurrentValueDao.QuotaKey.of(QuotaComponent.JMAP_UPLOADS,
 username.asString(), QuotaType.SIZE),
+        return 
cassandraQuotaCurrentValueDao.decrease(QuotaCurrentValue.Key.of(QuotaComponent.JMAP_UPLOADS,
 username.asString(), QuotaType.SIZE),
             usage.asLong());
     }
 
     @Override
     public Mono<QuotaSizeUsage> getSpaceUsage(Username username) {
-        return 
cassandraQuotaCurrentValueDao.getQuotaCurrentValue(CassandraQuotaCurrentValueDao.QuotaKey.of(QuotaComponent.JMAP_UPLOADS,
 username.asString(), QuotaType.SIZE))
+        return 
cassandraQuotaCurrentValueDao.getQuotaCurrentValue(QuotaCurrentValue.Key.of(QuotaComponent.JMAP_UPLOADS,
 username.asString(), QuotaType.SIZE))
             .map(quotaCurrentValue -> 
QuotaSizeUsage.size(quotaCurrentValue.getCurrentValue())).defaultIfEmpty(DEFAULT_QUOTA_SIZE_USAGE);
     }
 


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


Reply via email to