This is an automated email from the ASF dual-hosted git repository. btellier 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 29c85f3fe2 JAMES 2586 PostgresPushSubscriptionRepository: rely on Postgres unique constraint for deviceClientId (#2094) 29c85f3fe2 is described below commit 29c85f3fe23b9d8638fab0505bc839f981b40845 Author: Trần Hồng Quân <55171818+quantranhong1...@users.noreply.github.com> AuthorDate: Mon Mar 11 03:23:14 2024 +0700 JAMES 2586 PostgresPushSubscriptionRepository: rely on Postgres unique constraint for deviceClientId (#2094) Avoid 2 round trips (checking duplicate deviceClientId + INSERT subscription) when saving a subscription. --- .../PostgresPushSubscriptionDAO.java | 17 ++++++------- .../PostgresPushSubscriptionModule.java | 8 ++++--- .../PostgresPushSubscriptionRepository.java | 28 ++++++++++++---------- 3 files changed, 29 insertions(+), 24 deletions(-) diff --git a/server/data/data-jmap-postgres/src/main/java/org/apache/james/jmap/postgres/pushsubscription/PostgresPushSubscriptionDAO.java b/server/data/data-jmap-postgres/src/main/java/org/apache/james/jmap/postgres/pushsubscription/PostgresPushSubscriptionDAO.java index 5d59e08fe2..91b06c248b 100644 --- a/server/data/data-jmap-postgres/src/main/java/org/apache/james/jmap/postgres/pushsubscription/PostgresPushSubscriptionDAO.java +++ b/server/data/data-jmap-postgres/src/main/java/org/apache/james/jmap/postgres/pushsubscription/PostgresPushSubscriptionDAO.java @@ -21,6 +21,8 @@ package org.apache.james.jmap.postgres.pushsubscription; import static org.apache.james.backends.postgres.PostgresCommons.IN_CLAUSE_MAX_SIZE; import static org.apache.james.backends.postgres.PostgresCommons.OFFSET_DATE_TIME_ZONED_DATE_TIME_FUNCTION; +import static org.apache.james.backends.postgres.utils.PostgresUtils.UNIQUE_CONSTRAINT_VIOLATION_PREDICATE; +import static org.apache.james.jmap.postgres.pushsubscription.PostgresPushSubscriptionModule.PushSubscriptionTable.PRIMARY_KEY_CONSTRAINT; import java.time.ZonedDateTime; import java.util.Arrays; @@ -28,11 +30,13 @@ import java.util.Collection; import java.util.Optional; import java.util.Set; import java.util.function.Function; +import java.util.function.Predicate; import java.util.stream.Collectors; import org.apache.james.backends.postgres.utils.PostgresExecutor; import org.apache.james.core.Username; import org.apache.james.jmap.api.change.TypeStateFactory; +import org.apache.james.jmap.api.model.DeviceClientIdInvalidException; import org.apache.james.jmap.api.model.PushSubscription; import org.apache.james.jmap.api.model.PushSubscriptionExpiredTime; import org.apache.james.jmap.api.model.PushSubscriptionId; @@ -51,6 +55,8 @@ import scala.jdk.javaapi.CollectionConverters; import scala.jdk.javaapi.OptionConverters; public class PostgresPushSubscriptionDAO { + private static final Predicate<Throwable> IS_PRIMARY_KEY_UNIQUE_CONSTRAINT = throwable -> throwable.getMessage().contains(PRIMARY_KEY_CONSTRAINT); + private final PostgresExecutor postgresExecutor; private final TypeStateFactory typeStateFactory; @@ -71,7 +77,9 @@ public class PostgresPushSubscriptionDAO { .set(PushSubscriptionTable.VERIFICATION_CODE, pushSubscription.verificationCode()) .set(PushSubscriptionTable.VALIDATED, pushSubscription.validated()) .set(PushSubscriptionTable.ENCRYPT_PUBLIC_KEY, OptionConverters.toJava(pushSubscription.keys().map(PushSubscriptionKeys::p256dh)).orElse(null)) - .set(PushSubscriptionTable.ENCRYPT_AUTH_SECRET, OptionConverters.toJava(pushSubscription.keys().map(PushSubscriptionKeys::auth)).orElse(null)))); + .set(PushSubscriptionTable.ENCRYPT_AUTH_SECRET, OptionConverters.toJava(pushSubscription.keys().map(PushSubscriptionKeys::auth)).orElse(null)))) + .onErrorMap(UNIQUE_CONSTRAINT_VIOLATION_PREDICATE.and(IS_PRIMARY_KEY_UNIQUE_CONSTRAINT), + e -> new DeviceClientIdInvalidException(pushSubscription.deviceClientId(), "deviceClientId must be unique")); } public Flux<PushSubscription> listByUsername(Username username) { @@ -134,13 +142,6 @@ public class PostgresPushSubscriptionDAO { .map(record -> OFFSET_DATE_TIME_ZONED_DATE_TIME_FUNCTION.apply(record.get(PushSubscriptionTable.EXPIRES))); } - public Mono<Boolean> existDeviceClientId(Username username, String deviceClientId) { - return postgresExecutor.executeExists(dslContext -> dslContext.selectOne() - .from(PushSubscriptionTable.TABLE_NAME) - .where(PushSubscriptionTable.USER.eq(username.asString())) - .and(PushSubscriptionTable.DEVICE_CLIENT_ID.eq(deviceClientId))); - } - private PushSubscription recordAsPushSubscription(Record record) { try { return new PushSubscription(new PushSubscriptionId(record.get(PushSubscriptionTable.ID)), diff --git a/server/data/data-jmap-postgres/src/main/java/org/apache/james/jmap/postgres/pushsubscription/PostgresPushSubscriptionModule.java b/server/data/data-jmap-postgres/src/main/java/org/apache/james/jmap/postgres/pushsubscription/PostgresPushSubscriptionModule.java index 4a16bed563..ebe3c552ee 100644 --- a/server/data/data-jmap-postgres/src/main/java/org/apache/james/jmap/postgres/pushsubscription/PostgresPushSubscriptionModule.java +++ b/server/data/data-jmap-postgres/src/main/java/org/apache/james/jmap/postgres/pushsubscription/PostgresPushSubscriptionModule.java @@ -36,13 +36,14 @@ public interface PostgresPushSubscriptionModule { interface PushSubscriptionTable { Table<Record> TABLE_NAME = DSL.table("push_subscription"); + + String PRIMARY_KEY_CONSTRAINT = "push_subscription_primary_key_constraint"; + Field<String> USER = DSL.field("username", SQLDataType.VARCHAR.notNull()); Field<String> DEVICE_CLIENT_ID = DSL.field("device_client_id", SQLDataType.VARCHAR.notNull()); - Field<UUID> ID = DSL.field("id", SQLDataType.UUID.notNull()); Field<OffsetDateTime> EXPIRES = DSL.field("expires", PostgresCommons.DataTypes.TIMESTAMP_WITH_TIMEZONE); Field<String[]> TYPES = DSL.field("types", PostgresCommons.DataTypes.STRING_ARRAY.notNull()); - Field<String> URL = DSL.field("url", SQLDataType.VARCHAR.notNull()); Field<String> VERIFICATION_CODE = DSL.field("verification_code", SQLDataType.VARCHAR); Field<String> ENCRYPT_PUBLIC_KEY = DSL.field("encrypt_public_key", SQLDataType.VARCHAR); @@ -61,7 +62,8 @@ public interface PostgresPushSubscriptionModule { .column(ENCRYPT_PUBLIC_KEY) .column(ENCRYPT_AUTH_SECRET) .column(VALIDATED) - .primaryKey(USER, DEVICE_CLIENT_ID))) + .constraint(DSL.constraint(PRIMARY_KEY_CONSTRAINT) + .primaryKey(USER, DEVICE_CLIENT_ID)))) .supportsRowLevelSecurity() .build(); diff --git a/server/data/data-jmap-postgres/src/main/java/org/apache/james/jmap/postgres/pushsubscription/PostgresPushSubscriptionRepository.java b/server/data/data-jmap-postgres/src/main/java/org/apache/james/jmap/postgres/pushsubscription/PostgresPushSubscriptionRepository.java index c2370e43ad..4f81c8237d 100644 --- a/server/data/data-jmap-postgres/src/main/java/org/apache/james/jmap/postgres/pushsubscription/PostgresPushSubscriptionRepository.java +++ b/server/data/data-jmap-postgres/src/main/java/org/apache/james/jmap/postgres/pushsubscription/PostgresPushSubscriptionRepository.java @@ -34,7 +34,6 @@ import javax.inject.Singleton; import org.apache.james.backends.postgres.utils.PostgresExecutor; import org.apache.james.core.Username; import org.apache.james.jmap.api.change.TypeStateFactory; -import org.apache.james.jmap.api.model.DeviceClientIdInvalidException; import org.apache.james.jmap.api.model.ExpireTimeInvalidException; import org.apache.james.jmap.api.model.InvalidPushSubscriptionKeys; import org.apache.james.jmap.api.model.PushSubscription; @@ -64,26 +63,29 @@ public class PostgresPushSubscriptionRepository implements PushSubscriptionRepos @Override public Mono<PushSubscription> save(Username username, PushSubscriptionCreationRequest request) { - PushSubscription pushSubscription = PushSubscription.from(request, - evaluateExpiresTime(OptionConverters.toJava(request.expires().map(PushSubscriptionExpiredTime::value)), clock)); - PostgresPushSubscriptionDAO pushSubscriptionDAO = getDAO(username); - return pushSubscriptionDAO.existDeviceClientId(username, request.deviceClientId()) - .handle((isDuplicated, sink) -> { + + return validateCreationRequest(request) + .then(Mono.defer(() -> { + PushSubscription pushSubscription = PushSubscription.from(request, + evaluateExpiresTime(OptionConverters.toJava(request.expires().map(PushSubscriptionExpiredTime::value)), clock)); + + return pushSubscriptionDAO.save(username, pushSubscription) + .thenReturn(pushSubscription); + })); + } + + private Mono<Object> validateCreationRequest(PushSubscriptionCreationRequest request) { + return Mono.just(request) + .handle((creationRequest, sink) -> { if (isInThePast(request.expires(), clock)) { sink.error(new ExpireTimeInvalidException(request.expires().get().value(), "expires must be greater than now")); return; } - if (isDuplicated) { - sink.error(new DeviceClientIdInvalidException(request.deviceClientId(), "deviceClientId must be unique")); - return; - } if (isInvalidPushSubscriptionKey(request.keys())) { sink.error(new InvalidPushSubscriptionKeys(request.keys().get())); } - }) - .then(Mono.defer(() -> pushSubscriptionDAO.save(username, pushSubscription)) - .thenReturn(pushSubscription)); + }); } @Override --------------------------------------------------------------------- To unsubscribe, e-mail: notifications-unsubscr...@james.apache.org For additional commands, e-mail: notifications-h...@james.apache.org