From c7c27efa548fb750d8ac78ece9edca076b7261a3 Mon Sep 17 00:00:00 2001 From: mikereiche Date: Mon, 23 Sep 2024 11:42:52 -0700 Subject: [PATCH 01/73] Prepare for selene-sr4 development. Change-Id: I1acd67d040254e9ff77b65587e50843b82f2108f Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/216606 Tested-by: Build Bot Reviewed-by: Michael Reiche --- columnar-fit-performer-shared/pom.xml | 2 +- columnar-java-client/pom.xml | 2 +- columnar-java-fit-performer/pom.xml | 2 +- core-fit-performer/pom.xml | 2 +- core-io-deps/pom.xml | 2 +- core-io/pom.xml | 4 ++-- java-client/pom.xml | 4 ++-- java-examples/pom.xml | 4 ++-- java-fit-performer/pom.xml | 2 +- kotlin-client/pom.xml | 4 ++-- kotlin-fit-performer/pom.xml | 2 +- metrics-micrometer/pom.xml | 4 ++-- metrics-opentelemetry/pom.xml | 4 ++-- osgi-feature/pom.xml | 4 ++-- pom.xml | 22 +++++++++++----------- scala-client/pom.xml | 4 ++-- scala-fit-performer/pom.xml | 2 +- scala-implicits/pom.xml | 4 ++-- test-utils/pom.xml | 4 ++-- tracing-micrometer-observation/pom.xml | 4 ++-- tracing-opentelemetry-deps/pom.xml | 2 +- tracing-opentelemetry/pom.xml | 6 +++--- tracing-opentracing/pom.xml | 4 ++-- 23 files changed, 47 insertions(+), 47 deletions(-) diff --git a/columnar-fit-performer-shared/pom.xml b/columnar-fit-performer-shared/pom.xml index 3c7a9280d..b8540709d 100644 --- a/columnar-fit-performer-shared/pom.xml +++ b/columnar-fit-performer-shared/pom.xml @@ -7,7 +7,7 @@ com.couchbase.client couchbase-jvm-clients - 1.16.3 + 1.16.4-SNAPSHOT columnar-fit-performer-shared diff --git a/columnar-java-client/pom.xml b/columnar-java-client/pom.xml index a9a9f45fc..267017a94 100644 --- a/columnar-java-client/pom.xml +++ b/columnar-java-client/pom.xml @@ -6,7 +6,7 @@ com.couchbase.client couchbase-jvm-clients - 1.16.3 + 1.16.4-SNAPSHOT couchbase-columnar-java-client diff --git a/columnar-java-fit-performer/pom.xml b/columnar-java-fit-performer/pom.xml index 987e796a4..7e956e77d 100644 --- a/columnar-java-fit-performer/pom.xml +++ b/columnar-java-fit-performer/pom.xml @@ -7,7 +7,7 @@ com.couchbase.client couchbase-jvm-clients - 1.16.3 + 1.16.4-SNAPSHOT columnar-java-fit-performer diff --git a/core-fit-performer/pom.xml b/core-fit-performer/pom.xml index bab1922e9..a2ed40ccf 100644 --- a/core-fit-performer/pom.xml +++ b/core-fit-performer/pom.xml @@ -7,7 +7,7 @@ com.couchbase.client couchbase-jvm-clients - 1.16.3 + 1.16.4-SNAPSHOT fit-performer-core diff --git a/core-io-deps/pom.xml b/core-io-deps/pom.xml index a0607e84a..9dd8bd190 100644 --- a/core-io-deps/pom.xml +++ b/core-io-deps/pom.xml @@ -6,7 +6,7 @@ com.couchbase.client core-io-deps - 1.7.3 + 1.7.4-SNAPSHOT jar Couchbase JVM Core IO Dependencies diff --git a/core-io/pom.xml b/core-io/pom.xml index d5ca3814a..8f2b1bf59 100644 --- a/core-io/pom.xml +++ b/core-io/pom.xml @@ -7,11 +7,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.3 + 1.16.4-SNAPSHOT core-io - 3.7.3 + 3.7.4-SNAPSHOT diff --git a/java-client/pom.xml b/java-client/pom.xml index fecef8846..76197f8b1 100644 --- a/java-client/pom.xml +++ b/java-client/pom.xml @@ -6,11 +6,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.3 + 1.16.4-SNAPSHOT java-client - 3.7.3 + 3.7.4-SNAPSHOT Couchbase Java SDK diff --git a/java-examples/pom.xml b/java-examples/pom.xml index a61446ec7..639ad7829 100644 --- a/java-examples/pom.xml +++ b/java-examples/pom.xml @@ -6,11 +6,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.3 + 1.16.4-SNAPSHOT java-examples - 1.7.3 + 1.7.4-SNAPSHOT Couchbase Java SDK Examples diff --git a/java-fit-performer/pom.xml b/java-fit-performer/pom.xml index 38f418894..eb0103912 100644 --- a/java-fit-performer/pom.xml +++ b/java-fit-performer/pom.xml @@ -7,7 +7,7 @@ com.couchbase.client couchbase-jvm-clients - 1.16.3 + 1.16.4-SNAPSHOT fit-performer-java-sdk diff --git a/kotlin-client/pom.xml b/kotlin-client/pom.xml index c59243718..9b4e13035 100644 --- a/kotlin-client/pom.xml +++ b/kotlin-client/pom.xml @@ -7,11 +7,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.3 + 1.16.4-SNAPSHOT kotlin-client - 1.4.3 + 1.4.4-SNAPSHOT Couchbase Kotlin SDK The official Couchbase Kotlin SDK diff --git a/kotlin-fit-performer/pom.xml b/kotlin-fit-performer/pom.xml index 833b1a7f7..07b8657ca 100644 --- a/kotlin-fit-performer/pom.xml +++ b/kotlin-fit-performer/pom.xml @@ -7,7 +7,7 @@ com.couchbase.client couchbase-jvm-clients - 1.16.3 + 1.16.4-SNAPSHOT fit-performer-kotlin diff --git a/metrics-micrometer/pom.xml b/metrics-micrometer/pom.xml index 2ca77a88a..859d131a3 100644 --- a/metrics-micrometer/pom.xml +++ b/metrics-micrometer/pom.xml @@ -8,11 +8,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.3 + 1.16.4-SNAPSHOT metrics-micrometer - 0.7.3 + 0.7.4-SNAPSHOT Micrometer Metrics Interoperability Provides interoperability with Micrometer diff --git a/metrics-opentelemetry/pom.xml b/metrics-opentelemetry/pom.xml index 05ed82437..628211140 100644 --- a/metrics-opentelemetry/pom.xml +++ b/metrics-opentelemetry/pom.xml @@ -6,11 +6,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.3 + 1.16.4-SNAPSHOT metrics-opentelemetry - 0.7.3 + 0.7.4-SNAPSHOT OpenTelemetry Metrics Interoperability Provides interoperability with OpenTelemetry Metrics diff --git a/osgi-feature/pom.xml b/osgi-feature/pom.xml index 3e96ee937..c7ed88c99 100644 --- a/osgi-feature/pom.xml +++ b/osgi-feature/pom.xml @@ -7,11 +7,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.3 + 1.16.4-SNAPSHOT osgi-feature - 3.7.3 + 3.7.4-SNAPSHOT pom Couchbase Java SDK OSGI Feature diff --git a/pom.xml b/pom.xml index 0c97d5771..b55e9a959 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.couchbase.client couchbase-jvm-clients - 1.16.3 + 1.16.4-SNAPSHOT pom Couchbase JVM Client Parent @@ -15,17 +15,17 @@ UTF-8 UTF-8 - 3.7.3 - 1.7.3 - 3.7.3 - 3.7.3 - 1.7.3 - 1.7.3 - 1.4.3 + 3.7.4-SNAPSHOT + 1.7.4-SNAPSHOT + 3.7.4-SNAPSHOT + 3.7.4-SNAPSHOT + 1.7.4-SNAPSHOT + 1.7.4-SNAPSHOT + 1.4.4-SNAPSHOT 0.1.0 - 1.5.3 - 0.7.3 - 1.7.3 + 1.5.4-SNAPSHOT + 0.7.4-SNAPSHOT + 1.7.4-SNAPSHOT 5.9.1 3.23.1 diff --git a/scala-client/pom.xml b/scala-client/pom.xml index ceaabf2f1..b8b2ab9cf 100644 --- a/scala-client/pom.xml +++ b/scala-client/pom.xml @@ -6,11 +6,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.3 + 1.16.4-SNAPSHOT scala-client_${scala.compat.version} - 1.7.3 + 1.7.4-SNAPSHOT jar Couchbase Scala SDK diff --git a/scala-fit-performer/pom.xml b/scala-fit-performer/pom.xml index 78ce4b4ef..356ce37af 100644 --- a/scala-fit-performer/pom.xml +++ b/scala-fit-performer/pom.xml @@ -5,7 +5,7 @@ com.couchbase.client couchbase-jvm-clients - 1.16.3 + 1.16.4-SNAPSHOT fit-performer-scala diff --git a/scala-implicits/pom.xml b/scala-implicits/pom.xml index 1462ac613..d6eab3581 100644 --- a/scala-implicits/pom.xml +++ b/scala-implicits/pom.xml @@ -7,11 +7,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.3 + 1.16.4-SNAPSHOT scala-implicits_${scala.compat.version} - 1.7.3 + 1.7.4-SNAPSHOT jar Couchbase Scala SDK Implicits diff --git a/test-utils/pom.xml b/test-utils/pom.xml index bb8e8ee4d..e354555b0 100644 --- a/test-utils/pom.xml +++ b/test-utils/pom.xml @@ -6,12 +6,12 @@ com.couchbase.client couchbase-jvm-clients - 1.16.3 + 1.16.4-SNAPSHOT Couchbase (Integration) Test Utilities test-utils - 1.7.3 + 1.7.4-SNAPSHOT diff --git a/tracing-micrometer-observation/pom.xml b/tracing-micrometer-observation/pom.xml index 6fd55d158..926039e90 100644 --- a/tracing-micrometer-observation/pom.xml +++ b/tracing-micrometer-observation/pom.xml @@ -6,11 +6,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.3 + 1.16.4-SNAPSHOT tracing-micrometer-observation - 1.5.3 + 1.5.4-SNAPSHOT Micrometer Observation Interoperability Provides interoperability with Micrometer Observation diff --git a/tracing-opentelemetry-deps/pom.xml b/tracing-opentelemetry-deps/pom.xml index 5e757d4ec..fefe0d918 100644 --- a/tracing-opentelemetry-deps/pom.xml +++ b/tracing-opentelemetry-deps/pom.xml @@ -9,7 +9,7 @@ com.couchbase.client tracing-opentelemetry-deps - 1.5.3 + 1.5.4-SNAPSHOT jar OpenTelemetry Interoperability Dependencies diff --git a/tracing-opentelemetry/pom.xml b/tracing-opentelemetry/pom.xml index 306d12423..abad3488f 100644 --- a/tracing-opentelemetry/pom.xml +++ b/tracing-opentelemetry/pom.xml @@ -6,11 +6,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.3 + 1.16.4-SNAPSHOT tracing-opentelemetry - 1.5.3 + 1.5.4-SNAPSHOT OpenTelemetry Interoperability Provides interoperability with OpenTelemetry @@ -24,7 +24,7 @@ com.couchbase.client tracing-opentelemetry-deps - 1.5.3 + 1.5.4-SNAPSHOT diff --git a/tracing-opentracing/pom.xml b/tracing-opentracing/pom.xml index 3882afb80..fb41f7dc2 100644 --- a/tracing-opentracing/pom.xml +++ b/tracing-opentracing/pom.xml @@ -6,11 +6,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.3 + 1.16.4-SNAPSHOT tracing-opentracing - 1.5.3 + 1.5.4-SNAPSHOT OpenTracing Interoperability Provides interoperability with OpenTracing From fbe0ea7eaec3f6e8bf92307098f8f626ff7ef2f4 Mon Sep 17 00:00:00 2001 From: David Nault Date: Tue, 24 Sep 2024 16:56:14 -0700 Subject: [PATCH 02/73] JCO-15 Support `tls_verify` connection string parameter JCO-16 Support `srv` connection string parameter Change-Id: I968c6623be3d97e6441b5d45a46c9cf089551230 Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/216695 Reviewed-by: David Nault Tested-by: David Nault --- .../columnar/client/java/Cluster.java | 31 +++++++++++++++++-- .../columnar/client/java/ClusterOptions.java | 17 ++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/Cluster.java b/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/Cluster.java index 9be118489..52025753b 100644 --- a/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/Cluster.java +++ b/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/Cluster.java @@ -32,6 +32,7 @@ import java.io.Closeable; import java.time.Duration; import java.util.Collections; +import java.util.LinkedHashMap; import java.util.Map; import java.util.Optional; import java.util.concurrent.atomic.AtomicBoolean; @@ -124,7 +125,7 @@ public static Cluster newInstance( optionsCustomizer.accept(builder); BuilderPropertySetter propertySetter = new BuilderPropertySetter("", Collections.emptyMap()); - propertySetter.set(builder, cs.params()); + propertySetter.set(builder, translateTlsVerify(cs.params())); // do we really want to allow a system property to disable server certificate verification? //propertySetter.set(builder, systemPropertyMap(SYSTEM_PROPERTY_PREFIX)); @@ -134,7 +135,10 @@ public static Cluster newInstance( Environment.Builder envBuilder = new Environment.Builder() .transactionsConfig(disableTransactionsCleanup()) .deserializer(opts.deserializer()) - .ioConfig(it -> it.maxHttpConnections(Integer.MAX_VALUE)) + .ioConfig(it -> it + .enableDnsSrv(opts.srv()) + .maxHttpConnections(Integer.MAX_VALUE) + ) .securityConfig(it -> { SecurityOptions.Unmodifiable security = opts.security(); @@ -171,6 +175,29 @@ public static Cluster newInstance( return new Cluster(cs, credential.toInternalAuthenticator(), env); } + private static Map translateTlsVerify(Map connectionStringProperties) { + Map properties = new LinkedHashMap<>(connectionStringProperties); + String tlsVerify = properties.remove("tls_verify"); + if (tlsVerify == null) { + return properties; + } + + String javaName = "security.verifyServerCertificate"; + switch (tlsVerify) { + case "none": + properties.put(javaName, "false"); + break; + case "peer": + properties.put(javaName, "true"); + break; + default: + throw new IllegalArgumentException( + "Unexpected value for connection string parameter 'tls_verify'; expected 'none' or 'peer', but got: '" + tlsVerify + "'"); + } + + return properties; + } + private static CoreTransactionsConfig disableTransactionsCleanup() { return new CoreTransactionsConfig( DEFAULT_TRANSACTION_DURABILITY_LEVEL, diff --git a/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/ClusterOptions.java b/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/ClusterOptions.java index 52c6e0151..d6bbc7fd2 100644 --- a/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/ClusterOptions.java +++ b/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/ClusterOptions.java @@ -23,6 +23,7 @@ import java.util.function.Consumer; public final class ClusterOptions { + boolean srv = true; @Nullable Deserializer deserializer; final SecurityOptions security = new SecurityOptions(); final TimeoutOptions timeout = new TimeoutOptions(); @@ -34,6 +35,15 @@ Unmodifiable build() { return new Unmodifiable(this); } + /** + * Specifies whether the SDK should treat the connection string address + * as a DNS SRV record. Defaults to true. + */ + public ClusterOptions srv(boolean useDnsSrv) { + this.srv = useDnsSrv; + return this; + } + /** * Sets the default deserializer for converting query result rows into Java objects. *

@@ -65,10 +75,12 @@ static class Unmodifiable { private final TimeoutOptions.Unmodifiable timeout; private final SecurityOptions.Unmodifiable security; private final Deserializer deserializer; + private final boolean srv; Unmodifiable(ClusterOptions builder) { this.timeout = builder.timeout.build(); this.security = builder.security.build(); + this.srv = builder.srv; this.deserializer = builder.deserializer != null ? builder.deserializer @@ -87,12 +99,17 @@ public Deserializer deserializer() { return deserializer; } + public boolean srv() { + return srv; + } + @Override public String toString() { return "ClusterOptions{" + "timeout=" + timeout + ", security=" + security + ", deserializer=" + deserializer + + ", srv=" + srv + '}'; } From e79aad48533defcdf0957392c908ad199a006faa Mon Sep 17 00:00:00 2001 From: Graham Pople Date: Tue, 24 Sep 2024 12:00:21 +0100 Subject: [PATCH 03/73] JCBC-2147: Implement Zone Aware Read from Replica (1/n) Allows the user to do reads from a preferred server group. See the linked ticket for links out to the spec for this feature. This initial commit parses the server group info from the config and makes it available. Change-Id: Ieee122d63a120d9ff17462c1fce99499e65d16b1 Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/216659 Reviewed-by: David Nault Tested-by: Build Bot --- .../client/core/config/GlobalConfig.java | 2 +- .../core/config/LegacyConfigHelper.java | 3 ++- .../client/core/config/PortInfo.java | 19 ++++++++++++++++--- .../core/config/loader/BaseBucketLoader.java | 2 +- .../core/config/loader/GlobalLoader.java | 4 ++-- .../core/topology/HostAndServicePorts.java | 14 +++++++++++--- .../topology/HostAndServicePortsParser.java | 5 ++++- .../client/core/config/ClusterConfigTest.java | 2 +- .../config/refresher/GlobalRefresherTest.java | 8 ++++---- 9 files changed, 42 insertions(+), 17 deletions(-) diff --git a/core-io/src/main/java/com/couchbase/client/core/config/GlobalConfig.java b/core-io/src/main/java/com/couchbase/client/core/config/GlobalConfig.java index d12339942..6a630d977 100644 --- a/core-io/src/main/java/com/couchbase/client/core/config/GlobalConfig.java +++ b/core-io/src/main/java/com/couchbase/client/core/config/GlobalConfig.java @@ -81,7 +81,7 @@ private List enrichPortInfos(final List portInfos, final Str List enriched = new ArrayList<>(portInfos.size()); for (PortInfo portInfo : portInfos) { if (portInfo.hostname() == null) { - enriched.add(new PortInfo(portInfo.ports(), portInfo.sslPorts(), portInfo.alternateAddresses(), origin)); + enriched.add(new PortInfo(portInfo.ports(), portInfo.sslPorts(), portInfo.alternateAddresses(), origin, portInfo.serverGroup())); } else { enriched.add(portInfo); } diff --git a/core-io/src/main/java/com/couchbase/client/core/config/LegacyConfigHelper.java b/core-io/src/main/java/com/couchbase/client/core/config/LegacyConfigHelper.java index 9755e9251..160c32f7d 100644 --- a/core-io/src/main/java/com/couchbase/client/core/config/LegacyConfigHelper.java +++ b/core-io/src/main/java/com/couchbase/client/core/config/LegacyConfigHelper.java @@ -53,7 +53,8 @@ static List getPortInfos(ClusterTopology topology) { tlsPorts(topology, it), emptyMap(), // The host is always accurate -- there is never an alternate. it.host(), - it.id().toLegacy() + it.id().toLegacy(), + it.serverGroup() ) ); } diff --git a/core-io/src/main/java/com/couchbase/client/core/config/PortInfo.java b/core-io/src/main/java/com/couchbase/client/core/config/PortInfo.java index 2c6f816c0..3b1a566fc 100644 --- a/core-io/src/main/java/com/couchbase/client/core/config/PortInfo.java +++ b/core-io/src/main/java/com/couchbase/client/core/config/PortInfo.java @@ -19,6 +19,7 @@ import com.couchbase.client.core.deps.com.fasterxml.jackson.annotation.JsonCreator; import com.couchbase.client.core.deps.com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.couchbase.client.core.deps.com.fasterxml.jackson.annotation.JsonProperty; +import reactor.util.annotation.Nullable; import com.couchbase.client.core.node.NodeIdentifier; import com.couchbase.client.core.service.ServiceType; @@ -40,6 +41,7 @@ public class PortInfo { private final Map sslPorts; private final Map alternateAddresses; private final String hostname; + private final @Nullable String serverGroup; private final NodeIdentifier nodeIdentifier; /** @@ -54,12 +56,14 @@ public class PortInfo { public PortInfo( @JsonProperty("services") Map services, @JsonProperty("hostname") String hostname, - @JsonProperty("alternateAddresses") Map aa + @JsonProperty("alternateAddresses") Map aa, + @JsonProperty("serverGroup") String serverGroup ) { ports = new HashMap<>(); sslPorts = new HashMap<>(); alternateAddresses = aa == null ? Collections.emptyMap() : aa; this.hostname = hostname; // might be null when decoded from JSON, covered at a higher level + this.serverGroup = serverGroup; extractPorts(services, ports, sslPorts); @@ -75,11 +79,12 @@ public PortInfo( * @param hostname the hostname of the port info (node). */ PortInfo(final Map ports, final Map sslPorts, - final Map alternateAddresses, final String hostname) { + final Map alternateAddresses, final String hostname, final @Nullable String serverGroup) { this.ports = requireNonNull(ports); this.sslPorts = requireNonNull(sslPorts); this.alternateAddresses = requireNonNull(alternateAddresses); this.hostname = requireNonNull(hostname); + this.serverGroup = serverGroup; this.nodeIdentifier = initNodeIdentifier(hostname, ports, sslPorts); } @@ -88,13 +93,15 @@ public PortInfo( final Map sslPorts, final Map alternateAddresses, final String hostname, - final NodeIdentifier nodeIdentifier + final NodeIdentifier nodeIdentifier, + final @Nullable String serverGroup ) { this.ports = requireNonNull(ports); this.sslPorts = requireNonNull(sslPorts); this.alternateAddresses = requireNonNull(alternateAddresses); this.hostname = requireNonNull(hostname); this.nodeIdentifier = requireNonNull(nodeIdentifier); + this.serverGroup = serverGroup; } public NodeIdentifier identifier() { @@ -183,6 +190,11 @@ public Map alternateAddresses() { return alternateAddresses; } + @Nullable + public String serverGroup() { + return serverGroup; + } + @Override public String toString() { return "PortInfo{" @@ -190,6 +202,7 @@ public String toString() { + ", sslPorts=" + sslPorts + ", hostname='" + hostname + ", alternateAddresses=" + alternateAddresses + + ", serverGroup=" + serverGroup + '\'' + '}'; } diff --git a/core-io/src/main/java/com/couchbase/client/core/config/loader/BaseBucketLoader.java b/core-io/src/main/java/com/couchbase/client/core/config/loader/BaseBucketLoader.java index eb14ad0c3..e71d2d4ed 100644 --- a/core-io/src/main/java/com/couchbase/client/core/config/loader/BaseBucketLoader.java +++ b/core-io/src/main/java/com/couchbase/client/core/config/loader/BaseBucketLoader.java @@ -82,7 +82,7 @@ public abstract class BaseBucketLoader implements BucketLoader { * all non-config exceptions into config exceptions so that the upper level only needs to handle * one specific exception type.

* - *

At this point, we are passing an {@link Optional#empty()} for alternate addresses when the + *

At this point, we are passing a null for server group info when the * service is created, since we do not have a config to check against at this point. The config provider * will take care of this at a later point in time, before the rest of the bootstrap happens.

* diff --git a/core-io/src/main/java/com/couchbase/client/core/config/loader/GlobalLoader.java b/core-io/src/main/java/com/couchbase/client/core/config/loader/GlobalLoader.java index f466892f7..9b7b16a76 100644 --- a/core-io/src/main/java/com/couchbase/client/core/config/loader/GlobalLoader.java +++ b/core-io/src/main/java/com/couchbase/client/core/config/loader/GlobalLoader.java @@ -53,9 +53,9 @@ public GlobalLoader(final Core core) { /** * Tries to load the global configuration. * - *

Please note that at this point, we are passing an {@link Optional#empty()} for alternate addresses when the + *

Please note that at this point, we are passing a null for server group info when the * service is created, since we do not have a config to check against at this point. The config provider - * will take care of this at a later point in time, before the rest of the bootstrap happens.

+ * will take care of this at a later point in time, before the rest of the bootstrap happens. * * @param seed the seed node to load from. * @param port the port number for the KV service. diff --git a/core-io/src/main/java/com/couchbase/client/core/topology/HostAndServicePorts.java b/core-io/src/main/java/com/couchbase/client/core/topology/HostAndServicePorts.java index 5812197a6..7229d0fb0 100644 --- a/core-io/src/main/java/com/couchbase/client/core/topology/HostAndServicePorts.java +++ b/core-io/src/main/java/com/couchbase/client/core/topology/HostAndServicePorts.java @@ -50,6 +50,7 @@ public class HostAndServicePorts implements KetamaRingNode { "", emptyMap(), new NodeIdentifier("", 0, ""), + null, null ); @@ -57,17 +58,20 @@ public class HostAndServicePorts implements KetamaRingNode { private final Map ports; private final NodeIdentifier id; @Nullable private final HostAndPort ketamaAuthority; + @Nullable private final String serverGroup; public HostAndServicePorts( String host, Map ports, NodeIdentifier id, - @Nullable HostAndPort ketamaAuthority + @Nullable HostAndPort ketamaAuthority, + @Nullable String serverGroup ) { this.host = requireNonNull(host); this.ports = unmodifiableMap(newEnumMap(ServiceType.class, ports)); this.id = requireNonNull(id); this.ketamaAuthority = ketamaAuthority; + this.serverGroup = serverGroup; } public boolean inaccessible() { @@ -105,6 +109,10 @@ public Map ports() { return ports; } + public @Nullable String serverGroup() { + return serverGroup; + } + public boolean has(ServiceType serviceType) { return ports.containsKey(serviceType); } @@ -121,7 +129,7 @@ public HostAndServicePorts without(ServiceType service, ServiceType... moreServi temp.remove(t); } - return new HostAndServicePorts(this.host, temp, this.id, this.ketamaAuthority); + return new HostAndServicePorts(this.host, temp, this.id, this.ketamaAuthority, this.serverGroup); } @Stability.Internal @@ -129,7 +137,7 @@ public HostAndServicePorts withKetamaAuthority(@Nullable HostAndPort ketamaAutho if (Objects.equals(this.ketamaAuthority, ketamaAuthority)) { return this; } - return new HostAndServicePorts(this.host, this.ports, this.id, ketamaAuthority); + return new HostAndServicePorts(this.host, this.ports, this.id, ketamaAuthority, this.serverGroup); } boolean matches(SeedNode seedNode) { diff --git a/core-io/src/main/java/com/couchbase/client/core/topology/HostAndServicePortsParser.java b/core-io/src/main/java/com/couchbase/client/core/topology/HostAndServicePortsParser.java index 219d9c658..6b03b7007 100644 --- a/core-io/src/main/java/com/couchbase/client/core/topology/HostAndServicePortsParser.java +++ b/core-io/src/main/java/com/couchbase/client/core/topology/HostAndServicePortsParser.java @@ -54,13 +54,16 @@ public static Map parse( ) { Map raw = parseIntermediate(json); HostAndPort ketamaAuthority = getKetamaAuthority(raw); + String serverGroup = json.path("serverGroup").asText(); + final String serverGroupFinal = serverGroup.isEmpty() ? null : serverGroup; return transformValues(raw, value -> new HostAndServicePorts( value.host, portSelector.selectPorts(value.rawServicePorts), getId(value.host, raw), - ketamaAuthority + ketamaAuthority, + serverGroupFinal ) ); } diff --git a/core-io/src/test/java/com/couchbase/client/core/config/ClusterConfigTest.java b/core-io/src/test/java/com/couchbase/client/core/config/ClusterConfigTest.java index 028ea8bc2..1762a882e 100644 --- a/core-io/src/test/java/com/couchbase/client/core/config/ClusterConfigTest.java +++ b/core-io/src/test/java/com/couchbase/client/core/config/ClusterConfigTest.java @@ -34,7 +34,7 @@ class ClusterConfigTest { private static PortInfo minimalPortInfo(String host) { - return new PortInfo(mapOf("mgmt", 8091), host, emptyMap()); + return new PortInfo(mapOf("mgmt", 8091), host, emptyMap(), null); } private static NodeInfo minimalNodeInfo(String host, int port) { diff --git a/core-io/src/test/java/com/couchbase/client/core/config/refresher/GlobalRefresherTest.java b/core-io/src/test/java/com/couchbase/client/core/config/refresher/GlobalRefresherTest.java index 0c102adcd..1c970c7de 100644 --- a/core-io/src/test/java/com/couchbase/client/core/config/refresher/GlobalRefresherTest.java +++ b/core-io/src/test/java/com/couchbase/client/core/config/refresher/GlobalRefresherTest.java @@ -87,8 +87,8 @@ void respectsPollInterval() { GlobalConfig config = mock(GlobalConfig.class); clusterConfig.setGlobalConfig(config); when(config.portInfos()).thenReturn(Arrays.asList( - new PortInfo(mapOf("kv", 11210, "mgmt", 8091), "foo", Collections.emptyMap()), - new PortInfo(mapOf("kv", 11210, "mgmt", 8091), "bar", Collections.emptyMap()) + new PortInfo(mapOf("kv", 11210, "mgmt", 8091), "foo", Collections.emptyMap(), null), + new PortInfo(mapOf("kv", 11210, "mgmt", 8091), "bar", Collections.emptyMap(), null) )); final AtomicInteger invocationCounter = new AtomicInteger(0); @@ -135,8 +135,8 @@ void triggersEventIfAllNodesFailedToRefresh() { GlobalConfig config = mock(GlobalConfig.class); clusterConfig.setGlobalConfig(config); when(config.portInfos()).thenReturn(Arrays.asList( - new PortInfo(mapOf("kv", 11210, "mgmt", 8091), "foo", Collections.emptyMap()), - new PortInfo(mapOf("kv", 11210, "mgmt", 8091), "bar", Collections.emptyMap()) + new PortInfo(mapOf("kv", 11210, "mgmt", 8091), "foo", Collections.emptyMap(), null), + new PortInfo(mapOf("kv", 11210, "mgmt", 8091), "bar", Collections.emptyMap(), null) )); final AtomicInteger invocationCounter = new AtomicInteger(0); From 3dfeb0dad9a7ab58053c15a2722d8eda7df22c02 Mon Sep 17 00:00:00 2001 From: David Nault Date: Wed, 25 Sep 2024 13:41:46 -0700 Subject: [PATCH 04/73] JCO-17 Cluster.close(Duration) should not be supported Change-Id: Ic6a64ab70640a048c14a197b9c1870eb8f7f9bad Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/216763 Reviewed-by: David Nault Tested-by: David Nault --- .../java/com/couchbase/columnar/client/java/Cluster.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/Cluster.java b/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/Cluster.java index 52025753b..1fbe9c652 100644 --- a/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/Cluster.java +++ b/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/Cluster.java @@ -246,10 +246,7 @@ private Cluster( } public void close() { - close(environment.timeoutConfig().disconnectTimeout()); - } - - public void close(Duration timeout) { + Duration timeout = environment.timeoutConfig().disconnectTimeout(); disconnectInternal(disconnected, timeout, couchbaseOps, environment).block(); } From 431cd268505b6e8cab2746918078fd13ca0b7179 Mon Sep 17 00:00:00 2001 From: David Nault Date: Wed, 25 Sep 2024 18:27:59 -0700 Subject: [PATCH 05/73] FIT performer: Fix checkstyle rule violation Modifications -------------- Add a copyright header. Reformat the description as a Javadoc class comment. Change-Id: I4dd68ba37c42026758473ddbbb0261dbc39a0ace Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/216786 Reviewed-by: David Nault Tested-by: David Nault --- .../couchbase/utils/CustomJsonSerializer.java | 59 ++++++++++++------- 1 file changed, 38 insertions(+), 21 deletions(-) diff --git a/java-fit-performer/src/main/java/com/couchbase/utils/CustomJsonSerializer.java b/java-fit-performer/src/main/java/com/couchbase/utils/CustomJsonSerializer.java index 2f04e5e15..12c4abbee 100644 --- a/java-fit-performer/src/main/java/com/couchbase/utils/CustomJsonSerializer.java +++ b/java-fit-performer/src/main/java/com/couchbase/utils/CustomJsonSerializer.java @@ -1,24 +1,18 @@ -/** - * CustomJsonSerializer provides a generic implementation of the JsonSerializer interface. - - * This serializer is designed to handle the conversion of Java objects to JSON format - * and back, with an additional boolean flag (`Serialized`) that indicates whether - * the object has been serialized. The flag is included in the JSON payload, making - * it easy to track the serialization state of objects. - - * Use Cases: - * - This serializer can be used in scenarios where you need to serialize and deserialize - * objects while keeping track of their serialization state. - - * Limitations: - * - The current implementation assumes that the input objects can be serialized into - * a JSON format using Jackson's ObjectMapper. Complex or non-standard objects may - * require additional handling. - * - The `deserialize` methods in this implementation modify the original JSON object - * by setting the `Serialized` flag to `false`, which might not be suitable for - * all use cases. +/* + * Copyright 2024 Couchbase, Inc. + * + * Licensed 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 com.couchbase.utils; import com.couchbase.client.core.deps.com.fasterxml.jackson.core.JsonProcessingException; @@ -28,7 +22,30 @@ import com.couchbase.client.java.codec.TypeRef; import com.couchbase.client.java.json.JsonObject; - +/** + * CustomJsonSerializer provides a generic implementation of the JsonSerializer interface. + *

+ * This serializer is designed to handle the conversion of Java objects to JSON format + * and back, with an additional boolean flag (`Serialized`) that indicates whether + * the object has been serialized. The flag is included in the JSON payload, making + * it easy to track the serialization state of objects. + *

+ * Use Cases: + *

    + *
  • This serializer can be used in scenarios where you need to serialize and deserialize + * objects while keeping track of their serialization state. + *
+ *

+ * Limitations: + *

    + *
  • The current implementation assumes that the input objects can be serialized into + * a JSON format using Jackson's ObjectMapper. Complex or non-standard objects may + * require additional handling. + *
  • The `deserialize` methods in this implementation modify the original JSON object + * by setting the `Serialized` flag to `false`, which might not be suitable for + * all use cases. + *
+ */ public class CustomJsonSerializer implements JsonSerializer { @Override public byte[] serialize(Object input) { From fbafda7c8b1adf26b24d665f634fa4ec62e06c03 Mon Sep 17 00:00:00 2001 From: David Nault Date: Wed, 25 Sep 2024 18:31:24 -0700 Subject: [PATCH 06/73] scalafmt Change-Id: I0a311381f52b3eaa69149ceee0d28fa51be14919 Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/216787 Tested-by: David Nault Reviewed-by: David Nault --- .../client/scala/KeyValueExpirySpec.scala | 52 ++++++++++++------- .../scala/util/ScalaIntegrationTest.scala | 2 +- 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/scala-client/src/integrationTest/scala/com/couchbase/client/scala/KeyValueExpirySpec.scala b/scala-client/src/integrationTest/scala/com/couchbase/client/scala/KeyValueExpirySpec.scala index a0e81a8f6..4c93776d6 100644 --- a/scala-client/src/integrationTest/scala/com/couchbase/client/scala/KeyValueExpirySpec.scala +++ b/scala-client/src/integrationTest/scala/com/couchbase/client/scala/KeyValueExpirySpec.scala @@ -75,33 +75,40 @@ class KeyValueExpirySpec extends ScalaIntegrationTest { val expiryDuration = 3.second val nearFuture = Instant.now.plus(expiryDuration.toSeconds, SECONDS) - val insertWithInstant = DocAndOperation("insertWithInstant", + val insertWithInstant = DocAndOperation( + "insertWithInstant", (docId) => assert(coll.insert(docId, content, InsertOptions().expiry(nearFuture)).isSuccess), upsertDocFirst = false ) - val insertWithDuration = DocAndOperation("insertWithDuration", + val insertWithDuration = DocAndOperation( + "insertWithDuration", (docId) => assert(coll.insert(docId, content, InsertOptions().expiry(expiryDuration)).isSuccess), upsertDocFirst = false ) - val replaceWithInstant = DocAndOperation("replaceWithInstant", + val replaceWithInstant = DocAndOperation( + "replaceWithInstant", (docId) => assert(coll.replace(docId, content, ReplaceOptions().expiry(nearFuture)).isSuccess) ) - val replaceWithDuration = DocAndOperation("replaceWithDuration", + val replaceWithDuration = DocAndOperation( + "replaceWithDuration", (docId) => assert(coll.replace(docId, content, ReplaceOptions().expiry(expiryDuration)).isSuccess) ) - val upsertWithInstant = DocAndOperation("upsertWithInstant", + val upsertWithInstant = DocAndOperation( + "upsertWithInstant", (docId) => assert(coll.upsert(docId, content, UpsertOptions().expiry(nearFuture)).isSuccess) ) - val upsertWithDuration = DocAndOperation("upsertWithDuration", + val upsertWithDuration = DocAndOperation( + "upsertWithDuration", (docId) => assert(coll.upsert(docId, content, UpsertOptions().expiry(expiryDuration)).isSuccess) ) - val mutateInWithInstant = DocAndOperation("mutateInWithInstant", + val mutateInWithInstant = DocAndOperation( + "mutateInWithInstant", (docId) => assert( coll @@ -113,7 +120,8 @@ class KeyValueExpirySpec extends ScalaIntegrationTest { .isSuccess ) ) - val mutateInWithDuration = DocAndOperation("mutateInWithDuration", + val mutateInWithDuration = DocAndOperation( + "mutateInWithDuration", (docId) => assert( coll @@ -126,7 +134,8 @@ class KeyValueExpirySpec extends ScalaIntegrationTest { ) ) - val incrementWithInstant = DocAndOperation("incrementWithInstant", + val incrementWithInstant = DocAndOperation( + "incrementWithInstant", (docId) => assert( coll.binary @@ -135,7 +144,8 @@ class KeyValueExpirySpec extends ScalaIntegrationTest { ), upsertDocFirst = false ) - val incrementWithDuration = DocAndOperation("incrementWithDuration", + val incrementWithDuration = DocAndOperation( + "incrementWithDuration", (docId) => assert( coll.binary @@ -145,7 +155,8 @@ class KeyValueExpirySpec extends ScalaIntegrationTest { upsertDocFirst = false ) - val decrementWithInstant = DocAndOperation("decrementWithInstant", + val decrementWithInstant = DocAndOperation( + "decrementWithInstant", (docId) => assert( coll.binary @@ -154,7 +165,8 @@ class KeyValueExpirySpec extends ScalaIntegrationTest { ), upsertDocFirst = false ) - val decrementWithDuration = DocAndOperation("decrementWithDuration", + val decrementWithDuration = DocAndOperation( + "decrementWithDuration", (docId) => assert( coll.binary @@ -200,9 +212,9 @@ class KeyValueExpirySpec extends ScalaIntegrationTest { // Immediately after, the doc should exist coll.get(operation.docId, GetOptions().withExpiry(true)) match { case Success(result) => - ScalaIntegrationTest.Logger.info(s"${operation.name}: fetched ${result}") - assert(result.expiry.isDefined) - case Failure(err) => assert(false, s"unexpected error $err") + ScalaIntegrationTest.Logger.info(s"${operation.name}: fetched ${result}") + assert(result.expiry.isDefined) + case Failure(err) => assert(false, s"unexpected error $err") } }) @@ -211,13 +223,13 @@ class KeyValueExpirySpec extends ScalaIntegrationTest { // After a sleep the doc should be gone operations.foreach(operation => { - ScalaIntegrationTest.Logger.info(s"${operation.name}: fetching after sleep") + ScalaIntegrationTest.Logger.info(s"${operation.name}: fetching after sleep") - coll.get(operation.docId) match { + coll.get(operation.docId) match { case Failure(x: DocumentNotFoundException) => - case x => - ScalaIntegrationTest.Logger.info(s"${operation.name}: fetched after sleep $x") - assert(false, s"Unexpected result $x") + case x => + ScalaIntegrationTest.Logger.info(s"${operation.name}: fetched after sleep $x") + assert(false, s"Unexpected result $x") } }) } diff --git a/scala-client/src/integrationTest/scala/com/couchbase/client/scala/util/ScalaIntegrationTest.scala b/scala-client/src/integrationTest/scala/com/couchbase/client/scala/util/ScalaIntegrationTest.scala index 731287cae..fd6206a71 100644 --- a/scala-client/src/integrationTest/scala/com/couchbase/client/scala/util/ScalaIntegrationTest.scala +++ b/scala-client/src/integrationTest/scala/com/couchbase/client/scala/util/ScalaIntegrationTest.scala @@ -44,7 +44,7 @@ import scala.jdk.CollectionConverters._ * @since 3.0.0 */ object ScalaIntegrationTest { - val Logger = LoggerFactory.getLogger(classOf[ScalaIntegrationTest]) + val Logger = LoggerFactory.getLogger(classOf[ScalaIntegrationTest]) } // Temporarily increased timeout to (possibly) workaround MB-37011 when Developer Preview enabled From 932812ba3e8600337e606d36f287192bf3d05702 Mon Sep 17 00:00:00 2001 From: Graham Pople Date: Thu, 26 Sep 2024 14:27:05 +0100 Subject: [PATCH 07/73] JVMCBC-1573: ExtParallelUnstaging leading to OOM when many concurrent transactions executed `.parallel(1000)`, when run on an elastic thread pool (as we are doing), results in 1000 threads being created, no matter how many operations will actually be run in that chain. This is of course expensive, and leading to OOM issues if many concurrent transactions are run. Testing indicates that 200 parallel transactions are using 3.6GiB of memory, when previously they would use < 200MiB. Plus, creating 1000 * 1000 threads. Replacing this with .flatMap(), which leads to threads only being created as required. In testing only a small number are now created for the 1,000 concurrent ops, and memory usage is back to < 200MiB. Also adding a backdoor to control parallelism, for emergency purposes. Change-Id: I38fcd1f154abfdf6ed8cdf69b6750bede6f0af60 Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/216821 Tested-by: Build Bot Reviewed-by: David Nault --- .../CoreTransactionAttemptContext.java | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/core-io/src/main/java/com/couchbase/client/core/transaction/CoreTransactionAttemptContext.java b/core-io/src/main/java/com/couchbase/client/core/transaction/CoreTransactionAttemptContext.java index 9bd1b6d45..d0beee251 100644 --- a/core-io/src/main/java/com/couchbase/client/core/transaction/CoreTransactionAttemptContext.java +++ b/core-io/src/main/java/com/couchbase/client/core/transaction/CoreTransactionAttemptContext.java @@ -199,7 +199,7 @@ public TransactionQueryContext(NodeIdentifier queryTarget, @Nullable CoreQueryCo public static final int STATE_BITS_POSITION_FINAL_ERROR = 4; public static final int STATE_BITS_MASK_FINAL_ERROR = 0b1110000; public static final int STATE_BITS_MASK_BITS = 0b0001111; - public static final int UNSTAGING_PARALLELISM = 1000; + public static final int UNSTAGING_PARALLELISM = Integer.parseInt(System.getProperty("com.couchbase.transactions.unstagingParallelism", "1000"));; private final AtomicInteger stateBits = new AtomicInteger(0); @@ -2641,14 +2641,9 @@ private Mono commitDocsLocked(SpanWrapper span) { long start = System.nanoTime(); return Flux.fromIterable(stagedMutationsLocked) - .parallel(UNSTAGING_PARALLELISM) - .runOn(scheduler()) - - .concatMap(staged -> { - return commitDocWrapperLocked(span, staged); - }) + .publishOn(scheduler()) - .sequential() + .flatMap(staged -> commitDocWrapperLocked(span, staged), UNSTAGING_PARALLELISM) .then(Mono.defer(() -> { long elapsed = TimeUnit.NANOSECONDS.toMicros(System.nanoTime() - start); @@ -3611,19 +3606,16 @@ private Mono atrRollbackCompleteLocked(boolean isAppRollback, String prefi private Mono rollbackDocsLocked(boolean isAppRollback, SpanWrapper span) { return Mono.defer(() -> { return Flux.fromIterable(stagedMutationsLocked) - .parallel(UNSTAGING_PARALLELISM) - .runOn(scheduler()) + .publishOn(scheduler()) - .concatMap(staged -> { + .flatMap(staged -> { switch (staged.type) { case INSERT: return rollbackStagedInsertLocked(isAppRollback, span, staged.collection, staged.id, staged.cas); default: return rollbackStagedReplaceOrRemoveLocked(isAppRollback, span, staged.collection, staged.id, staged.cas, staged.currentUserFlags); } - }) - - .sequential() + }, UNSTAGING_PARALLELISM) .doOnNext(v -> { LOGGER.info(attemptId, "rollback - docs rolled back"); From 2970ad6323d196cd902c004dd18433431cc951ac Mon Sep 17 00:00:00 2001 From: Graham Pople Date: Tue, 24 Sep 2024 12:18:00 +0100 Subject: [PATCH 08/73] JVMCBC-1570: SDK produces an incorrect partition map from buckets with >= 2 replicas Change-Id: I0c8cea61dae59af64aafb68ef7b39355e7bda7eb Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/216665 Tested-by: Build Bot Reviewed-by: David Nault --- .../com/couchbase/client/core/config/CouchbaseBucketConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core-io/src/main/java/com/couchbase/client/core/config/CouchbaseBucketConfig.java b/core-io/src/main/java/com/couchbase/client/core/config/CouchbaseBucketConfig.java index 529873d81..2842db2af 100644 --- a/core-io/src/main/java/com/couchbase/client/core/config/CouchbaseBucketConfig.java +++ b/core-io/src/main/java/com/couchbase/client/core/config/CouchbaseBucketConfig.java @@ -164,7 +164,7 @@ private static List toLegacyPartitions( return transform(map.values(), it -> { short[] replicas = new short[numberOfReplicas]; for (int i = 0; i < numberOfReplicas; i++) { - replicas[i] = (short) it.nodeIndexForReplica(0).orElse(PARTITION_NOT_EXISTENT); + replicas[i] = (short) it.nodeIndexForReplica(i).orElse(PARTITION_NOT_EXISTENT); } return new Partition( (short) it.nodeIndexForActive().orElse(PARTITION_NOT_EXISTENT), From e026e6189ffb7af4f4e4429d16e8c09307adda68 Mon Sep 17 00:00:00 2001 From: Graham Pople Date: Wed, 25 Sep 2024 10:55:30 +0100 Subject: [PATCH 09/73] Gardening: fix invalid mock config The mock tests are mocking a one node cluster, but with one replica configured which would require a two node cluster. Change-Id: I8c9c758daf5e2e636768d3b2801c4f634c5047f2 Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/216723 Tested-by: Build Bot Reviewed-by: David Nault Reviewed-by: Michael Reiche --- .../resources/integration.properties | 2 +- .../java/ReplicaReadIntegrationTest.java | 33 ++++++++++++++++--- .../resources/integration.properties | 2 +- .../resources/integration.properties | 2 +- .../resources/integration.properties | 2 +- .../resources/integration.properties | 2 +- .../resources/integration.properties | 2 +- .../resources/integration.properties | 2 +- .../resources/integration.properties | 2 +- .../resources/integration.properties | 2 +- 10 files changed, 37 insertions(+), 14 deletions(-) diff --git a/core-io/src/integrationTest/resources/integration.properties b/core-io/src/integrationTest/resources/integration.properties index f151f3c17..6bb61ccea 100644 --- a/core-io/src/integrationTest/resources/integration.properties +++ b/core-io/src/integrationTest/resources/integration.properties @@ -11,7 +11,7 @@ cluster.adminPassword=password # Default configs for the mocked environment cluster.mocked.numNodes=1 -cluster.mocked.numReplicas=1 +cluster.mocked.numReplicas=0 # Entry point configuration if not managed # value of hostname and ns_server port diff --git a/java-client/src/integrationTest/java/com/couchbase/client/java/ReplicaReadIntegrationTest.java b/java-client/src/integrationTest/java/com/couchbase/client/java/ReplicaReadIntegrationTest.java index e0018f0d8..ff7a2bcf5 100644 --- a/java-client/src/integrationTest/java/com/couchbase/client/java/ReplicaReadIntegrationTest.java +++ b/java-client/src/integrationTest/java/com/couchbase/client/java/ReplicaReadIntegrationTest.java @@ -17,7 +17,7 @@ package com.couchbase.client.java; import com.couchbase.client.core.cnc.events.request.IndividualReplicaGetFailedEvent; -import com.couchbase.client.core.deps.com.google.common.collect.Sets; +import com.couchbase.client.core.config.CouchbaseBucketConfig; import com.couchbase.client.core.error.DocumentNotFoundException; import com.couchbase.client.core.error.DocumentUnretrievableException; import com.couchbase.client.core.error.UnambiguousTimeoutException; @@ -46,11 +46,14 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import java.util.stream.Collectors; +import java.util.stream.IntStream; import static com.couchbase.client.core.util.CbCollections.setCopyOf; import static com.couchbase.client.core.util.CbCollections.setOf; +import static com.couchbase.client.core.node.KeyValueLocator.partitionForKey; import static com.couchbase.client.core.util.CbCollections.transform; import static com.couchbase.client.test.Util.waitUntilCondition; +import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Collections.emptyList; import static java.util.Collections.emptySet; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -101,7 +104,7 @@ void alwaysPassesWithAll() { collection.upsert(id, "Hello, World!"); List results = collection.getAllReplicas(id).collect(Collectors.toList()); - assertFalse(results.isEmpty()); + assertEquals(numAvailableCopies(id), results.size(), results::toString); for (GetResult result : results) { assertEquals("Hello, World!", result.contentAs(String.class)); assertFalse(result.expiryTime().isPresent()); @@ -181,8 +184,7 @@ void asyncGetAnyReturnsFailedFutureWhenNotFound() throws InterruptedException { void asyncGetAllReturnsListOfFailedFuturesWhenNotFound() throws Exception { List> futures = collection.async().getAllReplicas(absentId()).get(); - // one result for each replica, plus 1 for active - assertEquals(config().numReplicas() + 1, futures.size()); + assertEquals(numAvailableCopies(absentId()), futures.size(), futures::toString); List> errorClasses = transform(futures, future -> { ExecutionException e = assertThrows(ExecutionException.class, future::get); @@ -242,7 +244,7 @@ void reactiveGetAllReturnsResult() throws Exception { .block(); assertNotNull(results); - assertNotEquals(0, results.size()); + assertEquals(numAvailableCopies(absentId()), results.size(), results::toString); int primaryCount = 0; for (GetReplicaResult result : results) { @@ -378,4 +380,25 @@ void noMonoReturnsErrorIfEmpty() { assertThrows(NoSuchElementException.class, () -> flux3.next().block()); } + + /** + * Returns the number of available replicas + active associated with the + * given document ID. + */ + private static int numAvailableCopies(String key) { + CouchbaseBucketConfig bucket = (CouchbaseBucketConfig) cluster.core() + .configurationProvider() + .config() + .bucketConfig(config().bucketname()); + + int partition = partitionForKey(key.getBytes(UTF_8), bucket.numberOfPartitions()); + + return (int) IntStream.range(0, bucket.numberOfReplicas()) + .filter(replicaIndex -> + bucket.nodeIndexForReplica(partition, replicaIndex, false) >= 0 + ) + .count() + // Plus active + + 1; + } } diff --git a/java-client/src/integrationTest/resources/integration.properties b/java-client/src/integrationTest/resources/integration.properties index cf416415f..f48ab5a3d 100644 --- a/java-client/src/integrationTest/resources/integration.properties +++ b/java-client/src/integrationTest/resources/integration.properties @@ -11,7 +11,7 @@ cluster.adminPassword=password # Default configs for the mocked environment cluster.mocked.numNodes=1 -cluster.mocked.numReplicas=1 +cluster.mocked.numReplicas=0 # Entry point configuration if not managed # value of hostname and ns_server port diff --git a/kotlin-client/src/integrationTest/resources/integration.properties b/kotlin-client/src/integrationTest/resources/integration.properties index cf416415f..f48ab5a3d 100644 --- a/kotlin-client/src/integrationTest/resources/integration.properties +++ b/kotlin-client/src/integrationTest/resources/integration.properties @@ -11,7 +11,7 @@ cluster.adminPassword=password # Default configs for the mocked environment cluster.mocked.numNodes=1 -cluster.mocked.numReplicas=1 +cluster.mocked.numReplicas=0 # Entry point configuration if not managed # value of hostname and ns_server port diff --git a/metrics-micrometer/src/integrationTest/resources/integration.properties b/metrics-micrometer/src/integrationTest/resources/integration.properties index cf416415f..f48ab5a3d 100644 --- a/metrics-micrometer/src/integrationTest/resources/integration.properties +++ b/metrics-micrometer/src/integrationTest/resources/integration.properties @@ -11,7 +11,7 @@ cluster.adminPassword=password # Default configs for the mocked environment cluster.mocked.numNodes=1 -cluster.mocked.numReplicas=1 +cluster.mocked.numReplicas=0 # Entry point configuration if not managed # value of hostname and ns_server port diff --git a/metrics-opentelemetry/src/integrationTest/resources/integration.properties b/metrics-opentelemetry/src/integrationTest/resources/integration.properties index cf416415f..f48ab5a3d 100644 --- a/metrics-opentelemetry/src/integrationTest/resources/integration.properties +++ b/metrics-opentelemetry/src/integrationTest/resources/integration.properties @@ -11,7 +11,7 @@ cluster.adminPassword=password # Default configs for the mocked environment cluster.mocked.numNodes=1 -cluster.mocked.numReplicas=1 +cluster.mocked.numReplicas=0 # Entry point configuration if not managed # value of hostname and ns_server port diff --git a/scala-client/src/integrationTest/resources/integration.properties b/scala-client/src/integrationTest/resources/integration.properties index cf416415f..f48ab5a3d 100644 --- a/scala-client/src/integrationTest/resources/integration.properties +++ b/scala-client/src/integrationTest/resources/integration.properties @@ -11,7 +11,7 @@ cluster.adminPassword=password # Default configs for the mocked environment cluster.mocked.numNodes=1 -cluster.mocked.numReplicas=1 +cluster.mocked.numReplicas=0 # Entry point configuration if not managed # value of hostname and ns_server port diff --git a/tracing-micrometer-observation/src/integrationTest/resources/integration.properties b/tracing-micrometer-observation/src/integrationTest/resources/integration.properties index cf416415f..f48ab5a3d 100644 --- a/tracing-micrometer-observation/src/integrationTest/resources/integration.properties +++ b/tracing-micrometer-observation/src/integrationTest/resources/integration.properties @@ -11,7 +11,7 @@ cluster.adminPassword=password # Default configs for the mocked environment cluster.mocked.numNodes=1 -cluster.mocked.numReplicas=1 +cluster.mocked.numReplicas=0 # Entry point configuration if not managed # value of hostname and ns_server port diff --git a/tracing-opentelemetry/src/integrationTest/resources/integration.properties b/tracing-opentelemetry/src/integrationTest/resources/integration.properties index cf416415f..f48ab5a3d 100644 --- a/tracing-opentelemetry/src/integrationTest/resources/integration.properties +++ b/tracing-opentelemetry/src/integrationTest/resources/integration.properties @@ -11,7 +11,7 @@ cluster.adminPassword=password # Default configs for the mocked environment cluster.mocked.numNodes=1 -cluster.mocked.numReplicas=1 +cluster.mocked.numReplicas=0 # Entry point configuration if not managed # value of hostname and ns_server port diff --git a/tracing-opentracing/src/integrationTest/resources/integration.properties b/tracing-opentracing/src/integrationTest/resources/integration.properties index cf416415f..f48ab5a3d 100644 --- a/tracing-opentracing/src/integrationTest/resources/integration.properties +++ b/tracing-opentracing/src/integrationTest/resources/integration.properties @@ -11,7 +11,7 @@ cluster.adminPassword=password # Default configs for the mocked environment cluster.mocked.numNodes=1 -cluster.mocked.numReplicas=1 +cluster.mocked.numReplicas=0 # Entry point configuration if not managed # value of hostname and ns_server port From ae4afe1760dc77b2f8541cd4339d3fd1ef529639 Mon Sep 17 00:00:00 2001 From: David Nault Date: Fri, 27 Sep 2024 13:58:13 -0700 Subject: [PATCH 10/73] Columnar: Update for Connection RFC revision 5 Modifications ------------ Changed `verifyServerCertificate` (default true) to `disableServerCertificateVerification` (default false). Change-Id: I9a062eebb95a0876dfd6a6a38211758463506966 Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/216947 Reviewed-by: David Nault Tested-by: Build Bot --- .../columnar/client/java/SecurityOptions.java | 16 ++++++++-------- .../columnar/client/java/sandbox/Sandbox.java | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/SecurityOptions.java b/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/SecurityOptions.java index 648afd4aa..3a2d0a9a1 100644 --- a/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/SecurityOptions.java +++ b/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/SecurityOptions.java @@ -36,7 +36,7 @@ public final class SecurityOptions { private List cipherSuites = emptyList(); @Nullable private TrustSource trustSource = null; - private boolean verifyServerCertificate = true; + private boolean disableServerCertificateVerification = false; Unmodifiable build() { return new Unmodifiable(this); @@ -107,7 +107,7 @@ public SecurityOptions trustOnlyFactory(TrustManagerFactory factory) { /** * Server certification verification is enabled by default. - * You can disable it by passing false to this method, + * You can disable it by passing true to this method, * but you almost certainly shouldn't. Instead, call one of the * {@code trust} methods to tell the SDK which certificates * it should trust. @@ -116,7 +116,7 @@ public SecurityOptions trustOnlyFactory(TrustManagerFactory factory) { * because it exposes you to on-path attacks. Never do this in production. * In fact, you probably shouldn't do it anywhere. * - * @param verify If false, the SDK does not verify the certificate + * @param disable If true, the SDK does not verify the certificate * presented by the server. * @see #trustOnlyPemFile(Path) * @see #trustOnlyPemString(String) @@ -126,8 +126,8 @@ public SecurityOptions trustOnlyFactory(TrustManagerFactory factory) { * is almost always a bad idea. */ @Deprecated - public SecurityOptions verifyServerCertificate(boolean verify) { - this.verifyServerCertificate = verify; + public SecurityOptions disableServerCertificateVerification(boolean disable) { + this.disableServerCertificateVerification = disable; return this; } @@ -142,9 +142,9 @@ static class Unmodifiable { Unmodifiable(SecurityOptions builder) { this.cipherSuites = builder.cipherSuites; - this.trustSource = builder.verifyServerCertificate - ? (builder.trustSource != null ? builder.trustSource : TrustSource.from(Certificates.getCapellaCertificates())) - : TrustSource.insecure(); + this.trustSource = builder.disableServerCertificateVerification + ? TrustSource.insecure() + : (builder.trustSource != null ? builder.trustSource : TrustSource.from(Certificates.getCapellaCertificates())); } public List cipherSuites() { diff --git a/columnar-java-client/src/test/java/com/couchbase/columnar/client/java/sandbox/Sandbox.java b/columnar-java-client/src/test/java/com/couchbase/columnar/client/java/sandbox/Sandbox.java index 73efa5a2c..a5463ceea 100644 --- a/columnar-java-client/src/test/java/com/couchbase/columnar/client/java/sandbox/Sandbox.java +++ b/columnar-java-client/src/test/java/com/couchbase/columnar/client/java/sandbox/Sandbox.java @@ -35,7 +35,7 @@ public class Sandbox { public static void main(String[] args) throws Exception { - String connectionString = "couchbases://127.0.0.1?security.verifyServerCertificate=false"; + String connectionString = "couchbases://127.0.0.1?security.disableServerCertificateVerification=true"; String username = "Administrator"; String password = "password"; From c26c644f91e736a5efeb51d4fc650c958de68f67 Mon Sep 17 00:00:00 2001 From: Graham Pople Date: Tue, 24 Sep 2024 12:04:14 +0100 Subject: [PATCH 11/73] JCBC-2147: Implement Zone Aware Read from Replica (2/n) This commit lets the user set a preferred server group. Change-Id: If7e9a5d7b2157f5fa5bfe32f24daac330d183f11 Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/216660 Reviewed-by: David Nault Tested-by: Build Bot --- .../client/core/env/CoreEnvironment.java | 21 +++++++++++++++++++ .../couchbase/client/core/env/IoConfig.java | 1 + 2 files changed, 22 insertions(+) diff --git a/core-io/src/main/java/com/couchbase/client/core/env/CoreEnvironment.java b/core-io/src/main/java/com/couchbase/client/core/env/CoreEnvironment.java index 7bef1913f..d3396ee54 100644 --- a/core-io/src/main/java/com/couchbase/client/core/env/CoreEnvironment.java +++ b/core-io/src/main/java/com/couchbase/client/core/env/CoreEnvironment.java @@ -45,6 +45,7 @@ import reactor.core.publisher.Mono; import reactor.core.scheduler.Scheduler; import reactor.core.scheduler.Schedulers; +import reactor.util.annotation.Nullable; import java.time.Duration; import java.util.ArrayList; @@ -120,6 +121,7 @@ public class CoreEnvironment implements AutoCloseable { private final Set appliedProfiles; private final CoreTransactionsSchedulers transactionsSchedulers = new CoreTransactionsSchedulers(); + private final @Nullable String preferredServerGroup; public static CoreEnvironment create() { return builder().build(); @@ -208,6 +210,7 @@ protected CoreEnvironment(final Builder builder) { } this.requestCallbacks = Collections.unmodifiableList(builder.requestCallbacks); + this.preferredServerGroup = builder.preferredServerGroup; checkInsecureTlsConfig(); } @@ -420,6 +423,13 @@ public CoreTransactionsSchedulers transactionsSchedulers() { return transactionsSchedulers; } + /** + * The preferred server group to use for operations that support such. + */ + public @Nullable String preferredServerGroup() { + return preferredServerGroup; + } + /** * Shuts down this Environment with the default disconnect timeout. * @@ -594,6 +604,7 @@ public static class Builder> { private long maxNumRequestsInRetry = DEFAULT_MAX_NUM_REQUESTS_IN_RETRY; private final List requestCallbacks = new ArrayList<>(); protected CoreTransactionsConfig transactionsConfig = null; + private String preferredServerGroup = null; private final Set appliedProfiles = new LinkedHashSet<>(); @@ -1150,6 +1161,16 @@ public SELF applyProfile(final String profileName) { + registeredProfileNames()); } + /** + * Sets a preferred server group, that will be used for operations that support this feature. + * + * @return this {@link Builder} for chaining purposes. + */ + public SELF preferredServerGroup(final @Nullable String preferredServerGroup) { + this.preferredServerGroup = preferredServerGroup; + return self(); + } + /** * You might wonder why callers can't use * {@link #load(PropertyLoader)} to load system properties. diff --git a/core-io/src/main/java/com/couchbase/client/core/env/IoConfig.java b/core-io/src/main/java/com/couchbase/client/core/env/IoConfig.java index 187733d5b..c33a00aee 100644 --- a/core-io/src/main/java/com/couchbase/client/core/env/IoConfig.java +++ b/core-io/src/main/java/com/couchbase/client/core/env/IoConfig.java @@ -24,6 +24,7 @@ import com.couchbase.client.core.node.StandardMemcachedHashingStrategy; import com.couchbase.client.core.service.AbstractPooledEndpointServiceConfig; import com.couchbase.client.core.service.ServiceType; +import reactor.util.annotation.Nullable; import java.time.Duration; import java.util.Arrays; From f74aff0fc4eab41e811bc07abdd281044a7d8dc0 Mon Sep 17 00:00:00 2001 From: Graham Pople Date: Tue, 24 Sep 2024 12:09:05 +0100 Subject: [PATCH 12/73] JCBC-2147: Implement Zone Aware Read from Replica (3/n) This commit adds support for this feature to non-transactional KV ops. Change-Id: Ibe77e4c9d78f21885d3ae83e52611123d2197877 Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/216661 Reviewed-by: David Nault Tested-by: Build Bot --- .../client/core/api/kv/CoreKvOps.java | 12 ++- .../core/api/kv/CoreReadPreference.java | 21 ++++ .../core/classic/kv/ClassicCoreKvOps.java | 21 ++-- .../error/DocumentUnretrievableException.java | 7 ++ .../kv/ProtostellarCoreKvOps.java | 9 +- .../core/service/kv/NodeIndexCalculator.java | 84 ++++++++++++++++ .../client/core/service/kv/ReplicaHelper.java | 97 ++++++++++++------- .../client/java/AsyncCollection.java | 4 + .../client/java/ReactiveCollection.java | 9 +- .../client/java/kv/GetAllReplicasOptions.java | 17 ++++ .../client/java/kv/GetAnyReplicaOptions.java | 20 +++- .../java/kv/LookupInAllReplicasOptions.java | 17 ++++ .../java/kv/LookupInAnyReplicaOptions.java | 18 ++++ .../client/java/kv/ReadPreference.java | 48 +++++++++ .../com/couchbase/client/kotlin/Collection.kt | 9 +- .../client/scala/AsyncCollection.scala | 36 +++++-- .../client/scala/ReactiveCollection.scala | 50 +++++++--- 17 files changed, 398 insertions(+), 81 deletions(-) create mode 100644 core-io/src/main/java/com/couchbase/client/core/api/kv/CoreReadPreference.java create mode 100644 core-io/src/main/java/com/couchbase/client/core/service/kv/NodeIndexCalculator.java create mode 100644 java-client/src/main/java/com/couchbase/client/java/kv/ReadPreference.java diff --git a/core-io/src/main/java/com/couchbase/client/core/api/kv/CoreKvOps.java b/core-io/src/main/java/com/couchbase/client/core/api/kv/CoreKvOps.java index afa4d703e..d720e9417 100644 --- a/core-io/src/main/java/com/couchbase/client/core/api/kv/CoreKvOps.java +++ b/core-io/src/main/java/com/couchbase/client/core/api/kv/CoreKvOps.java @@ -328,23 +328,27 @@ default Mono subdocGetReactive( Flux subdocGetAllReplicasReactive( CoreCommonOptions common, String key, - List commands + List commands, + CoreReadPreference readPreference ); Mono subdocGetAnyReplicaReactive( CoreCommonOptions common, String key, - List commands + List commands, + CoreReadPreference readPreference ); Flux getAllReplicasReactive( CoreCommonOptions common, - String key + String key, + CoreReadPreference readPreference ); Mono getAnyReplicaReactive( CoreCommonOptions common, - String key + String key, + CoreReadPreference readPreference ); CoreAsyncResponse subdocMutateAsync( diff --git a/core-io/src/main/java/com/couchbase/client/core/api/kv/CoreReadPreference.java b/core-io/src/main/java/com/couchbase/client/core/api/kv/CoreReadPreference.java new file mode 100644 index 000000000..0ff983d50 --- /dev/null +++ b/core-io/src/main/java/com/couchbase/client/core/api/kv/CoreReadPreference.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2024 Couchbase, Inc. + * + * Licensed 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 com.couchbase.client.core.api.kv; + +public enum CoreReadPreference { + NO_PREFERENCE, + PREFERRED_SERVER_GROUP +} diff --git a/core-io/src/main/java/com/couchbase/client/core/classic/kv/ClassicCoreKvOps.java b/core-io/src/main/java/com/couchbase/client/core/classic/kv/ClassicCoreKvOps.java index 0b7d4a9ee..82fe56990 100644 --- a/core-io/src/main/java/com/couchbase/client/core/classic/kv/ClassicCoreKvOps.java +++ b/core-io/src/main/java/com/couchbase/client/core/classic/kv/ClassicCoreKvOps.java @@ -27,7 +27,6 @@ import com.couchbase.client.core.api.kv.CoreExpiry; import com.couchbase.client.core.api.kv.CoreGetResult; import com.couchbase.client.core.api.kv.CoreKvOps; -import com.couchbase.client.core.api.kv.CoreKvParamValidators; import com.couchbase.client.core.api.kv.CoreKvResponseMetadata; import com.couchbase.client.core.api.kv.CoreLookupInMacro; import com.couchbase.client.core.api.kv.CoreMutationResult; @@ -36,6 +35,7 @@ import com.couchbase.client.core.api.kv.CoreSubdocGetResult; import com.couchbase.client.core.api.kv.CoreSubdocMutateCommand; import com.couchbase.client.core.api.kv.CoreSubdocMutateResult; +import com.couchbase.client.core.api.kv.CoreReadPreference; import com.couchbase.client.core.classic.ClassicHelper; import com.couchbase.client.core.cnc.CbTracing; import com.couchbase.client.core.cnc.RequestSpan; @@ -49,7 +49,6 @@ import com.couchbase.client.core.error.DocumentNotFoundException; import com.couchbase.client.core.error.DocumentUnretrievableException; import com.couchbase.client.core.error.InvalidArgumentException; -import com.couchbase.client.core.error.context.ErrorContext; import com.couchbase.client.core.error.context.KeyValueErrorContext; import com.couchbase.client.core.error.context.ReducedKeyValueErrorContext; import com.couchbase.client.core.io.CollectionIdentifier; @@ -723,7 +722,7 @@ public CoreAsyncResponse subdocGetAsync( } @Override - public Flux getAllReplicasReactive(CoreCommonOptions common, String key) { + public Flux getAllReplicasReactive(CoreCommonOptions common, String key, CoreReadPreference readPreference) { validateGetAllReplicasParams(common, key); Duration timeout = timeout(common); @@ -736,7 +735,8 @@ public Flux getAllReplicasReactive(CoreCommonOptions common, Stri timeout, retryStrategy, common.clientContext(), - common.parentSpan().orElse(null) + common.parentSpan().orElse(null), + readPreference ).map(it -> new CoreGetResult( CoreKvResponseMetadata.from(it.getResponse().flexibleExtras()), keyspace, @@ -750,17 +750,17 @@ public Flux getAllReplicasReactive(CoreCommonOptions common, Stri } @Override - public Mono getAnyReplicaReactive(CoreCommonOptions common, String key) { + public Mono getAnyReplicaReactive(CoreCommonOptions common, String key, CoreReadPreference readPreference) { validateGetAnyReplicaParams(common, key); RequestSpan getAnySpan = span(common, TracingIdentifiers.SPAN_GET_ANY_REPLICA); - return getAllReplicasReactive(common.withParentSpan(getAnySpan), key) + return getAllReplicasReactive(common.withParentSpan(getAnySpan), key, readPreference) .next() .doFinally(signalType -> getAnySpan.end()); } @Override - public Flux subdocGetAllReplicasReactive(CoreCommonOptions common, String key, List commands) { + public Flux subdocGetAllReplicasReactive(CoreCommonOptions common, String key, List commands, CoreReadPreference readPreference) { validateSubdocGetAllParams(common, key, commands); Duration timeout = timeout(common); @@ -774,15 +774,16 @@ public Flux subdocGetAllReplicasReactive(CoreCommonOptions timeout, retryStrategy, common.clientContext(), - common.parentSpan().orElse(null) + common.parentSpan().orElse(null), + readPreference ); } @Override - public Mono subdocGetAnyReplicaReactive(CoreCommonOptions common, String key, List commands) { + public Mono subdocGetAnyReplicaReactive(CoreCommonOptions common, String key, List commands, CoreReadPreference readPreference) { validateSubdocGetAnyParams(common, key, commands); RequestSpan getAnySpan = span(common, TracingIdentifiers.SPAN_GET_ANY_REPLICA); - return subdocGetAllReplicasReactive(common.withParentSpan(getAnySpan), key, commands) + return subdocGetAllReplicasReactive(common.withParentSpan(getAnySpan), key, commands, readPreference) .next() .switchIfEmpty(Mono.error(new DocumentUnretrievableException(ReducedKeyValueErrorContext.create(key, collectionIdentifier)))) .doFinally(signalType -> getAnySpan.end()); diff --git a/core-io/src/main/java/com/couchbase/client/core/error/DocumentUnretrievableException.java b/core-io/src/main/java/com/couchbase/client/core/error/DocumentUnretrievableException.java index 03193c9f4..0b1a3d05d 100644 --- a/core-io/src/main/java/com/couchbase/client/core/error/DocumentUnretrievableException.java +++ b/core-io/src/main/java/com/couchbase/client/core/error/DocumentUnretrievableException.java @@ -28,4 +28,11 @@ public DocumentUnretrievableException(final ErrorContext ctx) { super("No document retrievable with a successful status", ctx); } + public DocumentUnretrievableException(final String message, final ErrorContext ctx) { + super(message, ctx); + } + + public static DocumentUnretrievableException noReplicasSuitable() { + return new DocumentUnretrievableException("No suitable replicas were available. Note that it is advised to always have a try-catch fallback to e.g. a regular get, when using replica gets", null); + } } diff --git a/core-io/src/main/java/com/couchbase/client/core/protostellar/kv/ProtostellarCoreKvOps.java b/core-io/src/main/java/com/couchbase/client/core/protostellar/kv/ProtostellarCoreKvOps.java index 832bf2963..fce5d5ea5 100644 --- a/core-io/src/main/java/com/couchbase/client/core/protostellar/kv/ProtostellarCoreKvOps.java +++ b/core-io/src/main/java/com/couchbase/client/core/protostellar/kv/ProtostellarCoreKvOps.java @@ -32,6 +32,7 @@ import com.couchbase.client.core.api.kv.CoreSubdocGetResult; import com.couchbase.client.core.api.kv.CoreSubdocMutateCommand; import com.couchbase.client.core.api.kv.CoreSubdocMutateResult; +import com.couchbase.client.core.api.kv.CoreReadPreference; import com.couchbase.client.core.endpoint.http.CoreCommonOptions; import com.couchbase.client.core.env.CompressionConfig; import com.couchbase.client.core.error.FeatureNotAvailableException; @@ -308,25 +309,25 @@ public Mono subdocGetReactive(CoreCommonOptions common, Str @Override - public Flux subdocGetAllReplicasReactive(CoreCommonOptions common, String key, List commands) { + public Flux subdocGetAllReplicasReactive(CoreCommonOptions common, String key, List commands, CoreReadPreference readPreference) { // Protostellar subdoc-from-replica support is currently incomplete. throw unsupported(); } @Override - public Mono subdocGetAnyReplicaReactive(CoreCommonOptions common, String key, List commands) { + public Mono subdocGetAnyReplicaReactive(CoreCommonOptions common, String key, List commands, CoreReadPreference readPreference) { // Protostellar subdoc-from-replica support is currently incomplete. throw unsupported(); } @Override - public Flux getAllReplicasReactive(CoreCommonOptions common, String key) { + public Flux getAllReplicasReactive(CoreCommonOptions common, String key, CoreReadPreference readPreference) { // Protostellar get-from-replica support is currently incomplete. JVMCBC-1263. throw unsupported(); } @Override - public Mono getAnyReplicaReactive(CoreCommonOptions common, String key) { + public Mono getAnyReplicaReactive(CoreCommonOptions common, String key, CoreReadPreference readPreference) { // Protostellar get-from-replica support is currently incomplete. JVMCBC-1263. throw unsupported(); } diff --git a/core-io/src/main/java/com/couchbase/client/core/service/kv/NodeIndexCalculator.java b/core-io/src/main/java/com/couchbase/client/core/service/kv/NodeIndexCalculator.java new file mode 100644 index 000000000..e381c57d1 --- /dev/null +++ b/core-io/src/main/java/com/couchbase/client/core/service/kv/NodeIndexCalculator.java @@ -0,0 +1,84 @@ +/* + * Copyright 2024 Couchbase, Inc. + * + * Licensed 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 com.couchbase.client.core.service.kv; + +import com.couchbase.client.core.CoreContext; +import com.couchbase.client.core.annotation.Stability; +import com.couchbase.client.core.api.kv.CoreReadPreference; +import com.couchbase.client.core.config.CouchbaseBucketConfig; +import com.couchbase.client.core.config.PortInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.charset.StandardCharsets; + +import static com.couchbase.client.core.node.KeyValueLocator.partitionForKey; + +@Stability.Internal +public class NodeIndexCalculator { + private final boolean[] allowedNodeIndexes; + private final CouchbaseBucketConfig topology; + private final static Logger logger = LoggerFactory.getLogger(NodeIndexCalculator.class); + + public NodeIndexCalculator(CoreReadPreference readPreference, CouchbaseBucketConfig topology, CoreContext coreContext) { + boolean[] allowedNodeIndexesOut = new boolean[topology.nodes().size()]; + + for (int nodeIndex = 0; nodeIndex < topology.portInfos().size(); nodeIndex++) { + boolean canUseNode = true; + PortInfo node = topology.portInfos().get(nodeIndex); + + if (readPreference == CoreReadPreference.PREFERRED_SERVER_GROUP) { + canUseNode = node.serverGroup() != null && node.serverGroup().equals(coreContext.environment().preferredServerGroup()); + } + + allowedNodeIndexesOut[nodeIndex] = canUseNode; + } + + this.allowedNodeIndexes = allowedNodeIndexesOut; + this.topology = topology; + } + + public boolean canUseNode(String documentId, int replicaIndex, boolean isActive) { + boolean useFastForward = false; + int partitionId = partitionForKey(documentId.getBytes(StandardCharsets.UTF_8), topology.numberOfPartitions()); + int nodeIndex; + + if (isActive) { + nodeIndex = topology.nodeIndexForActive(partitionId, useFastForward); + } else { + nodeIndex = topology.nodeIndexForReplica(partitionId, replicaIndex, useFastForward); + } + + logger.trace("Checking whether doc can use node. doc={} isActive={} replica={} pid={} ni={} allowed={}", documentId, isActive, replicaIndex, partitionId, nodeIndex, allowedNodeIndexes); + + return check(nodeIndex); + } + + public boolean canUseNodeForActive(String documentId) { + return canUseNode(documentId, 0, true); + } + + public boolean canUseNodeForReplica(String documentId, int replicaIndex) { + return canUseNode(documentId, replicaIndex, false); + } + + public boolean check(int nodeIndex) { + if (nodeIndex < 0 || nodeIndex > allowedNodeIndexes.length) { + return false; + } + return allowedNodeIndexes[nodeIndex]; + } +} diff --git a/core-io/src/main/java/com/couchbase/client/core/service/kv/ReplicaHelper.java b/core-io/src/main/java/com/couchbase/client/core/service/kv/ReplicaHelper.java index 7ab7f68af..809581f4f 100644 --- a/core-io/src/main/java/com/couchbase/client/core/service/kv/ReplicaHelper.java +++ b/core-io/src/main/java/com/couchbase/client/core/service/kv/ReplicaHelper.java @@ -24,6 +24,7 @@ import com.couchbase.client.core.api.kv.CoreKvResponseMetadata; import com.couchbase.client.core.api.kv.CoreSubdocGetCommand; import com.couchbase.client.core.api.kv.CoreSubdocGetResult; +import com.couchbase.client.core.api.kv.CoreReadPreference; import com.couchbase.client.core.cnc.RequestSpan; import com.couchbase.client.core.cnc.TracingIdentifiers; import com.couchbase.client.core.cnc.events.request.IndividualReplicaGetFailedEvent; @@ -106,7 +107,8 @@ public static Flux getAllReplicasReactive( final Duration timeout, final RetryStrategy retryStrategy, Map clientContext, - RequestSpan parentSpan + RequestSpan parentSpan, + CoreReadPreference readPreference ) { notNullOrEmpty(documentId, "Id", () -> ReducedKeyValueErrorContext.create(documentId, collectionIdentifier)); @@ -114,7 +116,7 @@ public static Flux getAllReplicasReactive( RequestSpan getAllSpan = env.requestTracer().requestSpan(TracingIdentifiers.SPAN_GET_ALL_REPLICAS, parentSpan); return Reactor - .toMono(() -> getAllReplicasRequests(core, collectionIdentifier, documentId, clientContext, retryStrategy, timeout, getAllSpan)) + .toMono(() -> getAllReplicasRequests(core, collectionIdentifier, documentId, clientContext, retryStrategy, timeout, getAllSpan, readPreference)) .flux() .flatMap(Flux::fromStream) .flatMap(request -> Reactor @@ -147,14 +149,15 @@ public static Flux lookupInAllReplicasReactive( final Duration timeout, final RetryStrategy retryStrategy, Map clientContext, - RequestSpan parentSpan + RequestSpan parentSpan, + CoreReadPreference readPreference ) { notNullOrEmpty(documentId, "Id", () -> ReducedKeyValueErrorContext.create(documentId, collectionIdentifier)); CoreEnvironment env = core.context().environment(); RequestSpan getAllSpan = env.requestTracer().requestSpan(TracingIdentifiers.SPAN_LOOKUP_IN_ALL_REPLICAS, parentSpan); return Reactor - .toMono(() -> lookupInAllReplicasRequests(core, collectionIdentifier, documentId, commands, clientContext, retryStrategy, timeout, getAllSpan)) + .toMono(() -> lookupInAllReplicasRequests(core, collectionIdentifier, documentId, commands, clientContext, retryStrategy, timeout, getAllSpan, readPreference)) .flux() .flatMap(Flux::fromStream) .flatMap(request -> Reactor @@ -184,12 +187,13 @@ public static CompletableFuture>> getAllReplicasAs final RetryStrategy retryStrategy, final Map clientContext, final RequestSpan parentSpan, + final CoreReadPreference readPreference, final Function responseMapper ) { CoreEnvironment env = core.context().environment(); RequestSpan getAllSpan = env.requestTracer().requestSpan(TracingIdentifiers.SPAN_LOOKUP_IN_ALL_REPLICAS, parentSpan); - return getAllReplicasRequests(core, collectionIdentifier, documentId, clientContext, retryStrategy, timeout, getAllSpan) + return getAllReplicasRequests(core, collectionIdentifier, documentId, clientContext, retryStrategy, timeout, getAllSpan, readPreference) .thenApply(stream -> stream.map(request -> get(core, request) @@ -232,12 +236,13 @@ public static CompletableFuture>> lookupInAllRepli final RetryStrategy retryStrategy, final Map clientContext, final RequestSpan parentSpan, + final CoreReadPreference readPreference, final Function responseMapper ) { CoreEnvironment env = core.context().environment(); RequestSpan getAllSpan = env.requestTracer().requestSpan(TracingIdentifiers.SPAN_GET_ALL_REPLICAS, parentSpan); - return lookupInAllReplicasRequests(core, collectionIdentifier, documentId, commands, clientContext, retryStrategy, timeout, getAllSpan) + return lookupInAllReplicasRequests(core, collectionIdentifier, documentId, commands, clientContext, retryStrategy, timeout, getAllSpan, readPreference) .thenApply(stream -> stream.map(request -> get(core, request) @@ -269,13 +274,14 @@ public static CompletableFuture getAnyReplicaAsync( final RetryStrategy retryStrategy, final Map clientContext, final RequestSpan parentSpan, + final CoreReadPreference readPreference, final Function responseMapper) { RequestSpan getAnySpan = core.context().environment().requestTracer() .requestSpan(TracingIdentifiers.SPAN_GET_ANY_REPLICA, parentSpan); CompletableFuture>> listOfFutures = getAllReplicasAsync( - core, collectionIdentifier, documentId, timeout, retryStrategy, clientContext, getAnySpan, responseMapper + core, collectionIdentifier, documentId, timeout, retryStrategy, clientContext, getAnySpan, readPreference, responseMapper ); // Aggregating the futures here will discard the individual errors, which we don't need @@ -304,13 +310,14 @@ public static CompletableFuture lookupInAnyReplicaAsync( final RetryStrategy retryStrategy, final Map clientContext, final RequestSpan parentSpan, + final CoreReadPreference readPreference, final Function responseMapper) { RequestSpan getAnySpan = core.context().environment().requestTracer() .requestSpan(TracingIdentifiers.SPAN_LOOKUP_IN_ANY_REPLICA, parentSpan); CompletableFuture>> listOfFutures = lookupInAllReplicasAsync( - core, collectionIdentifier, documentId, commands, timeout, retryStrategy, clientContext, getAnySpan, responseMapper + core, collectionIdentifier, documentId, commands, timeout, retryStrategy, clientContext, getAnySpan, readPreference, responseMapper ); // Aggregating the futures here will discard the individual errors, which we don't need @@ -372,7 +379,8 @@ public static CompletableFuture> getAllReplicasRequests( final Map clientContext, final RetryStrategy retryStrategy, final Duration timeout, - final RequestSpan parent + final RequestSpan parent, + final CoreReadPreference readPreference ) { notNullOrEmpty(documentId, "Id"); @@ -381,21 +389,32 @@ public static CompletableFuture> getAllReplicasRequests( final BucketConfig config = core.clusterConfig().bucketConfig(collectionIdentifier.bucket()); if (config instanceof CouchbaseBucketConfig) { - int numReplicas = ((CouchbaseBucketConfig) config).numberOfReplicas(); + CouchbaseBucketConfig topology = (CouchbaseBucketConfig) config; + int numReplicas = topology.numberOfReplicas(); List requests = new ArrayList<>(numReplicas + 1); + NodeIndexCalculator allowedNodeIndexes = new NodeIndexCalculator(readPreference, topology, coreContext); - RequestSpan span = environment.requestTracer().requestSpan(TracingIdentifiers.SPAN_REQUEST_KV_GET, parent); - GetRequest activeRequest = new GetRequest(documentId, timeout, coreContext, collectionIdentifier, retryStrategy, span); - activeRequest.context().clientContext(clientContext); - requests.add(activeRequest); + if (allowedNodeIndexes.canUseNodeForActive(documentId)) { + RequestSpan span = environment.requestTracer().requestSpan(TracingIdentifiers.SPAN_REQUEST_KV_GET, parent); + GetRequest activeRequest = new GetRequest(documentId, timeout, coreContext, collectionIdentifier, retryStrategy, span); + activeRequest.context().clientContext(clientContext); + requests.add(activeRequest); + } for (short replica = 1; replica <= numReplicas; replica++) { - RequestSpan replicaSpan = environment.requestTracer().requestSpan(TracingIdentifiers.SPAN_REQUEST_KV_GET_REPLICA, parent); - ReplicaGetRequest replicaRequest = new ReplicaGetRequest( + if (allowedNodeIndexes.canUseNodeForReplica(documentId, replica - 1)) { + RequestSpan replicaSpan = environment.requestTracer().requestSpan(TracingIdentifiers.SPAN_REQUEST_KV_GET_REPLICA, parent); + ReplicaGetRequest replicaRequest = new ReplicaGetRequest( documentId, timeout, coreContext, collectionIdentifier, retryStrategy, replica, replicaSpan - ); - replicaRequest.context().clientContext(clientContext); - requests.add(replicaRequest); + ); + replicaRequest.context().clientContext(clientContext); + requests.add(replicaRequest); + } + } + if (requests.isEmpty()) { + CompletableFuture> future = new CompletableFuture<>(); + future.completeExceptionally(DocumentUnretrievableException.noReplicasSuitable()); + return future; } return CompletableFuture.completedFuture(requests.stream()); } else if (config == null) { @@ -404,7 +423,7 @@ public static CompletableFuture> getAllReplicasRequests( final Duration retryDelay = Duration.ofMillis(100); final CompletableFuture> future = new CompletableFuture<>(); coreContext.environment().timer().schedule(() -> { - getAllReplicasRequests(core, collectionIdentifier, documentId, clientContext, retryStrategy, timeout.minus(retryDelay), parent).whenComplete((getRequestStream, throwable) -> { + getAllReplicasRequests(core, collectionIdentifier, documentId, clientContext, retryStrategy, timeout.minus(retryDelay), parent, readPreference).whenComplete((getRequestStream, throwable) -> { if (throwable != null) { future.completeExceptionally(throwable); } else { @@ -441,7 +460,8 @@ public static CompletableFuture> lookupInAllReplicasReq final Map clientContext, final RetryStrategy retryStrategy, final Duration timeout, - final RequestSpan parent + final RequestSpan parent, + final CoreReadPreference readPreference ) { notNullOrEmpty(documentId, "Id"); @@ -450,26 +470,37 @@ public static CompletableFuture> lookupInAllReplicasReq final BucketConfig config = core.clusterConfig().bucketConfig(collectionIdentifier.bucket()); if (config instanceof CouchbaseBucketConfig) { + CouchbaseBucketConfig topology = (CouchbaseBucketConfig) config; if (!config.bucketCapabilities().contains(BucketCapabilities.SUBDOC_READ_REPLICA)) { return failedFuture(FeatureNotAvailableException.subdocReadReplica()); } - int numReplicas = ((CouchbaseBucketConfig) config).numberOfReplicas(); + int numReplicas = topology.numberOfReplicas(); List requests = new ArrayList<>(numReplicas + 1); + NodeIndexCalculator allowedNodeIndexes = new NodeIndexCalculator(readPreference, topology, coreContext); - RequestSpan span = environment.requestTracer().requestSpan(TracingIdentifiers.SPAN_REQUEST_KV_LOOKUP_IN, parent); - SubdocGetRequest activeRequest = SubdocGetRequest.create(timeout, coreContext, collectionIdentifier, retryStrategy, documentId, (byte)0, commands, span); - activeRequest.context().clientContext(clientContext); - requests.add(activeRequest); + if (allowedNodeIndexes.canUseNodeForActive(documentId)) { + RequestSpan span = environment.requestTracer().requestSpan(TracingIdentifiers.SPAN_REQUEST_KV_LOOKUP_IN, parent); + SubdocGetRequest activeRequest = SubdocGetRequest.create(timeout, coreContext, collectionIdentifier, retryStrategy, documentId, (byte) 0, commands, span); + activeRequest.context().clientContext(clientContext); + requests.add(activeRequest); + } for (short replica = 1; replica <= numReplicas; replica++) { - RequestSpan replicaSpan = environment.requestTracer().requestSpan(TracingIdentifiers.SPAN_LOOKUP_IN_ALL_REPLICAS, parent); - ReplicaSubdocGetRequest replicaRequest = ReplicaSubdocGetRequest.create( - timeout, coreContext, collectionIdentifier, retryStrategy, documentId, (byte)0, commands, replica, replicaSpan - ); - replicaRequest.context().clientContext(clientContext); - requests.add(replicaRequest); + if (allowedNodeIndexes.canUseNodeForReplica(documentId, replica - 1)) { + RequestSpan replicaSpan = environment.requestTracer().requestSpan(TracingIdentifiers.SPAN_LOOKUP_IN_ALL_REPLICAS, parent); + ReplicaSubdocGetRequest replicaRequest = ReplicaSubdocGetRequest.create( + timeout, coreContext, collectionIdentifier, retryStrategy, documentId, (byte) 0, commands, replica, replicaSpan + ); + replicaRequest.context().clientContext(clientContext); + requests.add(replicaRequest); + } + } + if (requests.isEmpty()) { + CompletableFuture> future = new CompletableFuture<>(); + future.completeExceptionally(DocumentUnretrievableException.noReplicasSuitable()); + return future; } return CompletableFuture.completedFuture(requests.stream()); } else if (config == null) { @@ -478,7 +509,7 @@ public static CompletableFuture> lookupInAllReplicasReq final Duration retryDelay = Duration.ofMillis(100); final CompletableFuture> future = new CompletableFuture<>(); coreContext.environment().timer().schedule(() -> { - lookupInAllReplicasRequests(core, collectionIdentifier, documentId, commands, clientContext, retryStrategy, timeout.minus(retryDelay), parent).whenComplete((getRequestStream, throwable) -> { + lookupInAllReplicasRequests(core, collectionIdentifier, documentId, commands, clientContext, retryStrategy, timeout.minus(retryDelay), parent, readPreference).whenComplete((getRequestStream, throwable) -> { if (throwable != null) { future.completeExceptionally(throwable); } else { diff --git a/java-client/src/main/java/com/couchbase/client/java/AsyncCollection.java b/java-client/src/main/java/com/couchbase/client/java/AsyncCollection.java index cb2e63028..5bfbabe98 100644 --- a/java-client/src/main/java/com/couchbase/client/java/AsyncCollection.java +++ b/java-client/src/main/java/com/couchbase/client/java/AsyncCollection.java @@ -356,6 +356,7 @@ public CompletableFuture>> getAllReplic opts.retryStrategy().orElse(environment().retryStrategy()), opts.clientContext(), opts.parentSpan().orElse(null), + opts.readPreference(), response -> GetReplicaResult.from(response, transcoder)); } @@ -390,6 +391,7 @@ public CompletableFuture getAnyReplica(final String id, final opts.retryStrategy().orElse(environment().retryStrategy()), opts.clientContext(), opts.parentSpan().orElse(null), + opts.readPreference(), response -> GetReplicaResult.from(response, transcoder)); } @@ -709,6 +711,7 @@ public CompletableFuture>> lookupI opts.retryStrategy().orElse(environment().retryStrategy()), opts.clientContext(), opts.parentSpan().orElse(null), + opts.readPreference(), response -> LookupInReplicaResult.from(response, serializer)); } @@ -748,6 +751,7 @@ public CompletableFuture lookupInAnyReplica(final String opts.retryStrategy().orElse(environment().retryStrategy()), opts.clientContext(), opts.parentSpan().orElse(null), + opts.readPreference(), response -> LookupInReplicaResult.from(response, serializer)); } diff --git a/java-client/src/main/java/com/couchbase/client/java/ReactiveCollection.java b/java-client/src/main/java/com/couchbase/client/java/ReactiveCollection.java index e6f3159bb..c8bb9e866 100644 --- a/java-client/src/main/java/com/couchbase/client/java/ReactiveCollection.java +++ b/java-client/src/main/java/com/couchbase/client/java/ReactiveCollection.java @@ -351,7 +351,7 @@ public Flux getAllReplicas(final String id, final GetAllReplic GetAllReplicasOptions.Built opts = options.build(); final Transcoder transcoder = Optional.ofNullable(opts.transcoder()).orElse(environment().transcoder()); - return kvOps.getAllReplicasReactive(opts, id) + return kvOps.getAllReplicasReactive(opts, id, opts.readPreference()) .map(response -> GetReplicaResult.from(response, transcoder)); } @@ -381,7 +381,7 @@ public Mono getAnyReplica(final String id, final GetAnyReplica GetAnyReplicaOptions.Built opts = options.build(); final Transcoder transcoder = Optional.ofNullable(opts.transcoder()).orElse(environment().transcoder()); - return kvOps.getAnyReplicaReactive(opts, id) + return kvOps.getAnyReplicaReactive(opts, id, opts.readPreference()) .map(response -> GetReplicaResult.from(response, transcoder)); } @@ -779,7 +779,8 @@ public Flux lookupInAllReplicas(String id, List LookupInReplicaResult.from(response, serializer)); + return kvOps.subdocGetAllReplicasReactive(opts, id, transform(lookupInSpecs, LookupInSpec::toCore), opts.readPreference()) + .map(response -> LookupInReplicaResult.from(response, serializer)); } /** @@ -810,7 +811,7 @@ public Mono lookupInAnyReplica(final String id, final Lis LookupInAnyReplicaOptions.Built opts = options.build(); final JsonSerializer serializer = Optional.ofNullable(opts.serializer()).orElse(environment().jsonSerializer()); - return kvOps.subdocGetAnyReplicaReactive(opts, id, transform(lookupInSpecs, LookupInSpec::toCore)) + return kvOps.subdocGetAnyReplicaReactive(opts, id, transform(lookupInSpecs, LookupInSpec::toCore), opts.readPreference()) .map(response -> LookupInReplicaResult.from(response, serializer)); } } diff --git a/java-client/src/main/java/com/couchbase/client/java/kv/GetAllReplicasOptions.java b/java-client/src/main/java/com/couchbase/client/java/kv/GetAllReplicasOptions.java index a0992a8af..0105cbf78 100644 --- a/java-client/src/main/java/com/couchbase/client/java/kv/GetAllReplicasOptions.java +++ b/java-client/src/main/java/com/couchbase/client/java/kv/GetAllReplicasOptions.java @@ -17,9 +17,11 @@ package com.couchbase.client.java.kv; import com.couchbase.client.core.annotation.Stability; +import com.couchbase.client.core.api.kv.CoreReadPreference; import com.couchbase.client.java.CommonOptions; import com.couchbase.client.java.codec.Transcoder; import com.couchbase.client.java.json.JsonObject; +import reactor.util.annotation.Nullable; import static com.couchbase.client.core.util.Validators.notNull; @@ -29,6 +31,7 @@ public class GetAllReplicasOptions extends CommonOptions * Holds the transcoder used for decoding. */ private Transcoder transcoder; + private CoreReadPreference readPreference; /** * Creates a new set of {@link GetAllReplicasOptions}. @@ -54,6 +57,17 @@ public GetAllReplicasOptions transcoder(final Transcoder transcoder) { return this; } + /** + * Set a read preference for this operation. + * + * @see ReadPreference + * @return this to allow method chaining. + */ + public GetAllReplicasOptions readPreference(ReadPreference readPreference) { + this.readPreference = readPreference.toCore(); + return this; + } + @Stability.Internal public Built build() { return new Built(); @@ -67,6 +81,9 @@ public Transcoder transcoder() { return transcoder; } + public @Nullable CoreReadPreference readPreference() { + return readPreference; + } } } diff --git a/java-client/src/main/java/com/couchbase/client/java/kv/GetAnyReplicaOptions.java b/java-client/src/main/java/com/couchbase/client/java/kv/GetAnyReplicaOptions.java index b5d26c04a..8cb9610f9 100644 --- a/java-client/src/main/java/com/couchbase/client/java/kv/GetAnyReplicaOptions.java +++ b/java-client/src/main/java/com/couchbase/client/java/kv/GetAnyReplicaOptions.java @@ -17,8 +17,10 @@ package com.couchbase.client.java.kv; import com.couchbase.client.core.annotation.Stability; +import com.couchbase.client.core.api.kv.CoreReadPreference; import com.couchbase.client.java.CommonOptions; import com.couchbase.client.java.codec.Transcoder; +import reactor.util.annotation.Nullable; import static com.couchbase.client.core.util.Validators.notNull; @@ -29,6 +31,8 @@ public class GetAnyReplicaOptions extends CommonOptions { */ private Transcoder transcoder; + private CoreReadPreference readPreference; + /** * Creates a new set of {@link GetAnyReplicaOptions}. * @@ -45,7 +49,7 @@ private GetAnyReplicaOptions() { * Allows to specify a custom transcoder that is used to decode the content of the result. * * @param transcoder the custom transcoder that should be used for decoding. - * @return the {@link GetOptions} to allow method chaining. + * @return this to allow method chaining. */ public GetAnyReplicaOptions transcoder(final Transcoder transcoder) { notNull(transcoder, "Transcoder"); @@ -53,6 +57,17 @@ public GetAnyReplicaOptions transcoder(final Transcoder transcoder) { return this; } + /** + * Set a read preference for this operation. + * + * @see ReadPreference + * @return this to allow method chaining. + */ + public GetAnyReplicaOptions readPreference(ReadPreference readPreference) { + this.readPreference = readPreference.toCore(); + return this; + } + @Stability.Internal public Built build() { return new Built(); @@ -66,6 +81,9 @@ public Transcoder transcoder() { return transcoder; } + public @Nullable CoreReadPreference readPreference() { + return readPreference; + } } } diff --git a/java-client/src/main/java/com/couchbase/client/java/kv/LookupInAllReplicasOptions.java b/java-client/src/main/java/com/couchbase/client/java/kv/LookupInAllReplicasOptions.java index 9f1cf7d58..409e8b4d4 100644 --- a/java-client/src/main/java/com/couchbase/client/java/kv/LookupInAllReplicasOptions.java +++ b/java-client/src/main/java/com/couchbase/client/java/kv/LookupInAllReplicasOptions.java @@ -17,8 +17,10 @@ package com.couchbase.client.java.kv; import com.couchbase.client.core.annotation.Stability; +import com.couchbase.client.core.api.kv.CoreReadPreference; import com.couchbase.client.java.CommonOptions; import com.couchbase.client.java.codec.JsonSerializer; +import reactor.util.annotation.Nullable; import static com.couchbase.client.core.util.Validators.notNull; @@ -27,6 +29,7 @@ public class LookupInAllReplicasOptions extends CommonOptions { - return kvOps.getAllReplicasReactive(common.toCore(), id) + return kvOps.getAllReplicasReactive(common.toCore(), id, CoreReadPreference.NO_PREFERENCE) .asFlow().map { GetReplicaResult(id, it, defaultTranscoder) } } @@ -363,7 +364,7 @@ public class Collection internal constructor( id: String, common: CommonOptions = CommonOptions.Default, ): GetReplicaResult? { - val response = kvOps.getAnyReplicaReactive(common.toCore(), id) + val response = kvOps.getAnyReplicaReactive(common.toCore(), id, CoreReadPreference.NO_PREFERENCE) .awaitFirstOrNull() ?: return null return GetReplicaResult(id, response, defaultTranscoder) @@ -643,7 +644,8 @@ public class Collection internal constructor( val coreResult = kvOps.subdocGetAnyReplicaReactive( common.toCore(), id, - spec.commands + spec.commands, + CoreReadPreference.NO_PREFERENCE, ).awaitFirst() return LookupInReplicaResult(coreResult, defaultJsonSerializer, spec) @@ -663,6 +665,7 @@ public class Collection internal constructor( common.toCore(), id, spec.commands, + CoreReadPreference.NO_PREFERENCE, ) return flux .map { coreResult -> LookupInReplicaResult(coreResult, defaultJsonSerializer, spec) } diff --git a/scala-client/src/main/scala/com/couchbase/client/scala/AsyncCollection.scala b/scala-client/src/main/scala/com/couchbase/client/scala/AsyncCollection.scala index c9fb0636e..234fbb387 100644 --- a/scala-client/src/main/scala/com/couchbase/client/scala/AsyncCollection.scala +++ b/scala-client/src/main/scala/com/couchbase/client/scala/AsyncCollection.scala @@ -17,7 +17,12 @@ package com.couchbase.client.scala import com.couchbase.client.core.annotation.SinceCouchbase import com.couchbase.client.core.api.CoreCouchbaseOps -import com.couchbase.client.core.api.kv.{CoreExpiry, CoreSubdocGetCommand, CoreSubdocGetResult} +import com.couchbase.client.core.api.kv.{ + CoreExpiry, + CoreReadPreference, + CoreSubdocGetCommand, + CoreSubdocGetResult +} import com.couchbase.client.core.api.shared.CoreMutationState import com.couchbase.client.core.cnc.RequestSpan import com.couchbase.client.core.endpoint.http.CoreCommonOptions @@ -490,8 +495,9 @@ class AsyncCollection( id: String, timeout: Duration = kvReadTimeout ): Future[GetReplicaResult] = { - convert(kvOps.getAnyReplicaReactive(makeCommonOptions(timeout), id)) - .map(result => convertReplica(result, environment, None)) + convert( + kvOps.getAnyReplicaReactive(makeCommonOptions(timeout), id, CoreReadPreference.NO_PREFERENCE) + ).map(result => convertReplica(result, environment, None)) .toFuture } @@ -502,7 +508,7 @@ class AsyncCollection( id: String, options: GetAnyReplicaOptions ): Future[GetReplicaResult] = { - convert(kvOps.getAnyReplicaReactive(convert(options), id)) + convert(kvOps.getAnyReplicaReactive(convert(options), id, CoreReadPreference.NO_PREFERENCE)) .map(result => convertReplica(result, environment, options.transcoder)) .toFuture } @@ -538,7 +544,7 @@ class AsyncCollection( // Since the API here returns a Seq and not a Future, there is unfortunately // no option but to block & buffer the stream and return already completed/failed Futures. // Users that require a true streaming solution should use the reactive version. - convert(kvOps.getAllReplicasReactive(convert(options), id)) + convert(kvOps.getAllReplicasReactive(convert(options), id, CoreReadPreference.NO_PREFERENCE)) .map(result => convertReplica(result, environment, options.transcoder)) .collectSeq() .block(options.timeout) @@ -575,8 +581,14 @@ class AsyncCollection( spec: collection.Seq[LookupInSpec], options: LookupInAnyReplicaOptions ): Future[LookupInReplicaResult] = { - convert(kvOps.subdocGetAnyReplicaReactive(convert(options), id, LookupInSpec.map(spec).asJava)) - .map(result => convertLookupInReplica(result, environment)) + convert( + kvOps.subdocGetAnyReplicaReactive( + convert(options), + id, + LookupInSpec.map(spec).asJava, + CoreReadPreference.NO_PREFERENCE + ) + ).map(result => convertLookupInReplica(result, environment)) .toFuture } @@ -618,8 +630,14 @@ class AsyncCollection( spec: collection.Seq[LookupInSpec], options: LookupInAllReplicasOptions ): Seq[Future[LookupInReplicaResult]] = { - convert(kvOps.subdocGetAllReplicasReactive(convert(options), id, LookupInSpec.map(spec).asJava)) - .map(result => convertLookupInReplica(result, environment)) + convert( + kvOps.subdocGetAllReplicasReactive( + convert(options), + id, + LookupInSpec.map(spec).asJava, + CoreReadPreference.NO_PREFERENCE + ) + ).map(result => convertLookupInReplica(result, environment)) .collectSeq() .block(options.timeout) .map(result => Future.successful(result)) diff --git a/scala-client/src/main/scala/com/couchbase/client/scala/ReactiveCollection.scala b/scala-client/src/main/scala/com/couchbase/client/scala/ReactiveCollection.scala index 911f9163c..e66221db4 100644 --- a/scala-client/src/main/scala/com/couchbase/client/scala/ReactiveCollection.scala +++ b/scala-client/src/main/scala/com/couchbase/client/scala/ReactiveCollection.scala @@ -17,7 +17,7 @@ package com.couchbase.client.scala import com.couchbase.client.core.annotation.SinceCouchbase -import com.couchbase.client.core.api.kv.CoreExpiry +import com.couchbase.client.core.api.kv.{CoreExpiry, CoreReadPreference} import com.couchbase.client.core.io.CollectionIdentifier import com.couchbase.client.scala.codec.JsonSerializer import com.couchbase.client.scala.durability.Durability @@ -399,7 +399,12 @@ class ReactiveCollection(async: AsyncCollection) { ): SMono[LookupInReplicaResult] = { convert( kvOps - .subdocGetAnyReplicaReactive(makeCommonOptions(timeout), id, LookupInSpec.map(spec).asJava) + .subdocGetAnyReplicaReactive( + makeCommonOptions(timeout), + id, + LookupInSpec.map(spec).asJava, + CoreReadPreference.NO_PREFERENCE + ) ).map(result => convertLookupInReplica(result, environment)) } @@ -421,8 +426,14 @@ class ReactiveCollection(async: AsyncCollection) { spec: collection.Seq[LookupInSpec], options: LookupInAnyReplicaOptions ): SMono[LookupInReplicaResult] = { - convert(kvOps.subdocGetAnyReplicaReactive(convert(options), id, LookupInSpec.map(spec).asJava)) - .map(result => convertLookupInReplica(result, environment)) + convert( + kvOps.subdocGetAnyReplicaReactive( + convert(options), + id, + LookupInSpec.map(spec).asJava, + CoreReadPreference.NO_PREFERENCE + ) + ).map(result => convertLookupInReplica(result, environment)) } /** SubDocument lookups allow retrieving parts of a JSON document directly, which may be more efficient than @@ -445,7 +456,12 @@ class ReactiveCollection(async: AsyncCollection) { ): SFlux[LookupInReplicaResult] = { convert( kvOps - .subdocGetAllReplicasReactive(makeCommonOptions(timeout), id, LookupInSpec.map(spec).asJava) + .subdocGetAllReplicasReactive( + makeCommonOptions(timeout), + id, + LookupInSpec.map(spec).asJava, + CoreReadPreference.NO_PREFERENCE + ) ).map(result => convertLookupInReplica(result, environment)) } @@ -467,8 +483,14 @@ class ReactiveCollection(async: AsyncCollection) { spec: collection.Seq[LookupInSpec], options: LookupInAllReplicasOptions ): SFlux[LookupInReplicaResult] = { - convert(kvOps.subdocGetAllReplicasReactive(convert(options), id, LookupInSpec.map(spec).asJava)) - .map(result => convertLookupInReplica(result, environment)) + convert( + kvOps.subdocGetAllReplicasReactive( + convert(options), + id, + LookupInSpec.map(spec).asJava, + CoreReadPreference.NO_PREFERENCE + ) + ).map(result => convertLookupInReplica(result, environment)) } /** Retrieves any available version of the document. @@ -478,8 +500,9 @@ class ReactiveCollection(async: AsyncCollection) { id: String, timeout: Duration = kvReadTimeout ): SMono[GetReplicaResult] = { - convert(kvOps.getAnyReplicaReactive(makeCommonOptions(timeout), id)) - .map(result => convertReplica(result, environment, None)) + convert( + kvOps.getAnyReplicaReactive(makeCommonOptions(timeout), id, CoreReadPreference.NO_PREFERENCE) + ).map(result => convertReplica(result, environment, None)) } /** Retrieves any available version of the document. @@ -489,7 +512,7 @@ class ReactiveCollection(async: AsyncCollection) { id: String, options: GetAnyReplicaOptions ): SMono[GetReplicaResult] = { - convert(kvOps.getAnyReplicaReactive(convert(options), id)) + convert(kvOps.getAnyReplicaReactive(convert(options), id, CoreReadPreference.NO_PREFERENCE)) .map(result => convertReplica(result, environment, options.transcoder)) } @@ -500,8 +523,9 @@ class ReactiveCollection(async: AsyncCollection) { id: String, timeout: Duration = kvReadTimeout ): SFlux[GetReplicaResult] = { - convert(kvOps.getAllReplicasReactive(makeCommonOptions(timeout), id)) - .map(result => convertReplica(result, environment, None)) + convert( + kvOps.getAllReplicasReactive(makeCommonOptions(timeout), id, CoreReadPreference.NO_PREFERENCE) + ).map(result => convertReplica(result, environment, None)) } /** Retrieves all available versions of the document. @@ -511,7 +535,7 @@ class ReactiveCollection(async: AsyncCollection) { id: String, options: GetAllReplicasOptions ): SFlux[GetReplicaResult] = { - convert(kvOps.getAllReplicasReactive(convert(options), id)) + convert(kvOps.getAllReplicasReactive(convert(options), id, CoreReadPreference.NO_PREFERENCE)) .map(result => convertReplica(result, environment, options.transcoder)) } From 49ebcb8e9758a252085867fd7e1fcaba900d5b74 Mon Sep 17 00:00:00 2001 From: Graham Pople Date: Tue, 24 Sep 2024 12:11:17 +0100 Subject: [PATCH 13/73] JCBC-2147: Implement Zone Aware Read from Replica (4/n) For unclear reasons there are two abstractions for CoreSubdocGetResult/SubdocGetResponse. The transactions logic was using the latter, the replica helper logic the former. In order to use the replica helper logic from transactions, I've aligned transactions with CoreSubdocGetResult. This commit lays some of that refactoring groundwork. Change-Id: I8969b56f770990e6c9adc10d70d84ec7db997b94 Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/216662 Reviewed-by: David Nault Tested-by: Build Bot --- .../core/classic/kv/ClassicCoreKvOps.java | 9 +--- .../client/core/msg/kv/SubdocGetRequest.java | 15 ++++++ .../client/core/msg/kv/SubdocGetResponse.java | 15 ++++++ .../CoreTransactionAttemptContext.java | 27 +++++----- .../transaction/CoreTransactionGetResult.java | 49 ++++++++++--------- .../transaction/cleanup/ClientRecord.java | 9 ++-- .../cleanup/TransactionsCleaner.java | 17 ++++--- .../components/ActiveTransactionRecord.java | 16 +++--- .../components/DocumentGetter.java | 15 +++--- .../core/transaction/util/MeteringUnits.java | 10 ++++ .../util/TransactionKVHandler.java | 9 +++- 11 files changed, 119 insertions(+), 72 deletions(-) diff --git a/core-io/src/main/java/com/couchbase/client/core/classic/kv/ClassicCoreKvOps.java b/core-io/src/main/java/com/couchbase/client/core/classic/kv/ClassicCoreKvOps.java index 82fe56990..54cd51c4c 100644 --- a/core-io/src/main/java/com/couchbase/client/core/classic/kv/ClassicCoreKvOps.java +++ b/core-io/src/main/java/com/couchbase/client/core/classic/kv/ClassicCoreKvOps.java @@ -710,14 +710,7 @@ public CoreAsyncResponse subdocGetAsync( // This should be superfluous now - if the op failed then error() should be set - but leaving as a fail-safe. commonKvResponseCheck(req, res); }, - it -> new CoreSubdocGetResult( - keyspace, - key, - CoreKvResponseMetadata.from(it.flexibleExtras()), - Arrays.asList(it.values()), - it.cas(), - it.isDeleted() - ) + it -> it.toCore(keyspace, key) ); } diff --git a/core-io/src/main/java/com/couchbase/client/core/msg/kv/SubdocGetRequest.java b/core-io/src/main/java/com/couchbase/client/core/msg/kv/SubdocGetRequest.java index 9a4379068..64f5bc370 100644 --- a/core-io/src/main/java/com/couchbase/client/core/msg/kv/SubdocGetRequest.java +++ b/core-io/src/main/java/com/couchbase/client/core/msg/kv/SubdocGetRequest.java @@ -17,6 +17,7 @@ package com.couchbase.client.core.msg.kv; import com.couchbase.client.core.CoreContext; +import com.couchbase.client.core.annotation.Stability; import com.couchbase.client.core.api.kv.CoreSubdocGetCommand; import com.couchbase.client.core.cnc.RequestSpan; import com.couchbase.client.core.cnc.TracingIdentifiers; @@ -102,6 +103,20 @@ static List convertCommands(List commands) { return result; } + @Stability.Internal + public static List convertCommandsToCore(List commands) { + List result = new ArrayList<>(commands.size()); + for (Command cmd : commands) { + result.add(new CoreSubdocGetCommand( + cmd.type, + cmd.path, + cmd.xattr, + cmd.binary + )); + } + return result; + } + @Override public ByteBuf encode(ByteBufAllocator alloc, int opaque, KeyValueChannelContext ctx) { ByteBuf key = null; diff --git a/core-io/src/main/java/com/couchbase/client/core/msg/kv/SubdocGetResponse.java b/core-io/src/main/java/com/couchbase/client/core/msg/kv/SubdocGetResponse.java index f8a64642f..ed36efaca 100644 --- a/core-io/src/main/java/com/couchbase/client/core/msg/kv/SubdocGetResponse.java +++ b/core-io/src/main/java/com/couchbase/client/core/msg/kv/SubdocGetResponse.java @@ -16,6 +16,10 @@ package com.couchbase.client.core.msg.kv; +import com.couchbase.client.core.CoreKeyspace; +import com.couchbase.client.core.annotation.Stability; +import com.couchbase.client.core.api.kv.CoreKvResponseMetadata; +import com.couchbase.client.core.api.kv.CoreSubdocGetResult; import com.couchbase.client.core.error.CouchbaseException; import com.couchbase.client.core.io.netty.kv.MemcacheProtocol; import com.couchbase.client.core.msg.ResponseStatus; @@ -70,4 +74,15 @@ public String toString() { ", isDeleted=" + isDeleted + '}'; } + + @Stability.Internal + public CoreSubdocGetResult toCore(CoreKeyspace keyspace, String key) { + return new CoreSubdocGetResult(keyspace, + key, + CoreKvResponseMetadata.from(flexibleExtras()), + Arrays.asList(values()), + cas(), + isDeleted() + ); + } } diff --git a/core-io/src/main/java/com/couchbase/client/core/transaction/CoreTransactionAttemptContext.java b/core-io/src/main/java/com/couchbase/client/core/transaction/CoreTransactionAttemptContext.java index d0beee251..ddf862488 100644 --- a/core-io/src/main/java/com/couchbase/client/core/transaction/CoreTransactionAttemptContext.java +++ b/core-io/src/main/java/com/couchbase/client/core/transaction/CoreTransactionAttemptContext.java @@ -19,6 +19,8 @@ import com.couchbase.client.core.Core; import com.couchbase.client.core.annotation.Stability; import com.couchbase.client.core.annotation.UsedBy; +import com.couchbase.client.core.api.kv.CoreKvResponseMetadata; +import com.couchbase.client.core.api.kv.CoreSubdocGetResult; import com.couchbase.client.core.api.query.CoreQueryContext; import com.couchbase.client.core.api.query.CoreQueryOps; import com.couchbase.client.core.api.query.CoreQueryOptions; @@ -1894,18 +1896,18 @@ private Mono handleDocExistsDuringStagedInsert(String .flatMap(v -> { if (v.isPresent()) { - Tuple2 results = v.get(); + Tuple2 results = v.get(); CoreTransactionGetResult r = results.getT1(); - SubdocGetResponse lir = results.getT2(); + CoreSubdocGetResult lir = results.getT2(); MeteringUnits built = addUnits(units.build()); LOGGER.info(attemptId, "{} doc {} exists inTransaction={} isDeleted={}{}", - bp, DebugUtil.docId(collection, id), r.links(), lir.isDeleted(), DebugUtil.dbg(built)); + bp, DebugUtil.docId(collection, id), r.links(), lir.tombstone(), DebugUtil.dbg(built)); return forwardCompatibilityCheck(ForwardCompatibilityStage.WRITE_WRITE_CONFLICT_INSERTING_GET, r.links().forwardCompatibility()) .then(Mono.defer(() -> { - if (lir.isDeleted() && !r.links().isDocumentInTransaction()) { + if (lir.tombstone() && !r.links().isDocumentInTransaction()) { LOGGER.info(attemptId, "{} doc {} is a regular tombstone without txn metadata, proceeding to overwrite", bp, DebugUtil.docId(collection, id)); @@ -1969,12 +1971,12 @@ private Mono overwriteStagedInsert(String operationId, SpanWrapper pspan, String bp, CoreTransactionGetResult r, - SubdocGetResponse lir) { + CoreSubdocGetResult lir) { return Mono.defer(() -> { CbPreconditions.check(r.links().isDocumentInTransaction()); CbPreconditions.check(r.links().op().get().equals(OperationTypes.INSERT)); - if (lir.isDeleted()) { + if (lir.tombstone()) { return createStagedInsert(operationId, collection, id, content, flags, pspan, Optional.of(r.cas())); } else { @@ -2834,6 +2836,10 @@ false, false, false, false, false, cas, staged.stagedUserFlags, durabilityLevel( }); } + private void addUnits(CoreKvResponseMetadata meta) { + meteringUnitsBuilder.add(meta); + } + private void addUnits(@Nullable MemcacheProtocol.FlexibleExtras flexibleExtras) { meteringUnitsBuilder.add(flexibleExtras); } @@ -3173,15 +3179,14 @@ private Mono atrCommitAmbiguityResolutionLocked(AtomicReference over .flatMap(result -> { String status = null; try { - status = Mapper.reader().readValue(result.values()[0].value(), String.class); + status = Mapper.reader().readValue(result.field(0).value(), String.class); } catch (IOException e) { - LOGGER.info(attemptId, "failed to parse ATR {} status '{}'", getAtrDebug(atrCollection, atrId), new String(result.values()[0].value())); + LOGGER.info(attemptId, "failed to parse ATR {} status '{}'", getAtrDebug(atrCollection, atrId), new String(result.field(0).value())); status = "UNKNOWN"; } - addUnits(result.flexibleExtras()); - LOGGER.info(attemptId, "got status of ATR {}{}: '{}'", getAtrDebug(atrCollection, atrId), - DebugUtil.dbg(result.flexibleExtras()), status); + addUnits(result.meta()); + LOGGER.info(attemptId, "got status of ATR {}: '{}'", getAtrDebug(atrCollection, atrId), status); AttemptState state = AttemptState.convert(status); diff --git a/core-io/src/main/java/com/couchbase/client/core/transaction/CoreTransactionGetResult.java b/core-io/src/main/java/com/couchbase/client/core/transaction/CoreTransactionGetResult.java index 6d20dbba5..79c755768 100644 --- a/core-io/src/main/java/com/couchbase/client/core/transaction/CoreTransactionGetResult.java +++ b/core-io/src/main/java/com/couchbase/client/core/transaction/CoreTransactionGetResult.java @@ -17,6 +17,7 @@ package com.couchbase.client.core.transaction; import com.couchbase.client.core.annotation.Stability; +import com.couchbase.client.core.api.kv.CoreSubdocGetResult; import com.couchbase.client.core.deps.com.fasterxml.jackson.databind.JsonNode; import com.couchbase.client.core.deps.com.fasterxml.jackson.databind.ObjectMapper; import com.couchbase.client.core.io.CollectionIdentifier; @@ -237,7 +238,7 @@ public static CoreTransactionGetResult createFrom(CoreTransactionGetResult doc, @Stability.Internal public static CoreTransactionGetResult createFrom(CollectionIdentifier collection, String documentId, - SubdocGetResponse doc) throws IOException { + CoreSubdocGetResult doc) throws IOException { Optional atrId = Optional.empty(); Optional transactionId = Optional.empty(); Optional attemptId = Optional.empty(); @@ -259,16 +260,16 @@ public static CoreTransactionGetResult createFrom(CollectionIdentifier collectio Optional op = Optional.empty(); // "txn.id" - if (doc.values()[0].status().success()) { - JsonNode id = MAPPER.readValue(doc.values()[0].value(), JsonNode.class); + if (doc.field(0).status().success()) { + JsonNode id = MAPPER.readValue(doc.field(0).value(), JsonNode.class); transactionId = Optional.ofNullable(id.path("txn").textValue()); attemptId = Optional.ofNullable(id.path("atmpt").textValue()); operationId = Optional.ofNullable(id.path("op").textValue()); } // "txn.atr" - if (doc.values()[1].status().success()) { - JsonNode atr = MAPPER.readValue(doc.values()[1].value(), JsonNode.class); + if (doc.field(1).status().success()) { + JsonNode atr = MAPPER.readValue(doc.field(1).value(), JsonNode.class); atrId = Optional.ofNullable(atr.path("id").textValue()); atrBucketName = Optional.ofNullable(atr.path("bkt").textValue()); String scope = atr.path("scp").textValue(); @@ -287,24 +288,24 @@ public static CoreTransactionGetResult createFrom(CollectionIdentifier collectio } // "txn.op.type" - if (doc.values()[2].status().success()) { - op = Optional.of(Mapper.reader().readValue(doc.values()[2].value(), String.class)); + if (doc.field(2).status().success()) { + op = Optional.of(Mapper.reader().readValue(doc.field(2).value(), String.class)); } // "txn.op.stgd" - if (doc.values()[3].status().success()) { - byte[] raw = doc.values()[3].value(); + if (doc.field(3).status().success()) { + byte[] raw = doc.field(3).value(); stagedContentJson = Optional.of(raw); } // "txn.op.crc32" - if (doc.values()[4].status().success()) { - crc32OfStaging = Optional.of(Mapper.reader().readValue(doc.values()[4].value(), String.class)); + if (doc.field(4).status().success()) { + crc32OfStaging = Optional.of(Mapper.reader().readValue(doc.field(4).value(), String.class)); } // "txn.restore" - if (doc.values()[5].status().success()) { - JsonNode restore = MAPPER.readValue(doc.values()[5].value(), JsonNode.class); + if (doc.field(5).status().success()) { + JsonNode restore = MAPPER.readValue(doc.field(5).value(), JsonNode.class); casPreTxn = Optional.of(restore.path("CAS").textValue()); // Only present in 6.5+ revidPreTxn = Optional.of(restore.path("revid").textValue()); @@ -312,17 +313,17 @@ public static CoreTransactionGetResult createFrom(CollectionIdentifier collectio } // "txn.fc" - if (doc.values()[6].status().success()) { - JsonNode json = MAPPER.readValue(doc.values()[6].value(), JsonNode.class); + if (doc.field(6).status().success()) { + JsonNode json = MAPPER.readValue(doc.field(6).value(), JsonNode.class); ForwardCompatibility fc = new ForwardCompatibility(json); forwardCompatibility = Optional.of(fc); } - if (!doc.values()[7].status().success()) { + if (!doc.field(7).status().success()) { throw new IllegalStateException("$document requested but not received"); } // Read from $document - JsonNode restore = MAPPER.readValue(doc.values()[7].value(), JsonNode.class); + JsonNode restore = MAPPER.readValue(doc.field(7).value(), JsonNode.class); String casFromDocument = restore.path("CAS").textValue(); // Only present in 6.5+ String revidFromDocument = restore.path("revid").textValue(); @@ -333,14 +334,14 @@ public static CoreTransactionGetResult createFrom(CollectionIdentifier collectio int currentUserFlags = restore.path("flags").intValue(); // "txn.op.bin" - if (doc.values()[8].status().success()) { - byte[] raw = doc.values()[8].value(); + if (doc.field(8).status().success()) { + byte[] raw = doc.field(8).value(); stagedContentBinary = Optional.of(raw); } // "txn.aux" - if (doc.values()[9].status().success()) { - JsonNode aux = MAPPER.readValue(doc.values()[9].value(), JsonNode.class); + if (doc.field(9).status().success()) { + JsonNode aux = MAPPER.readValue(doc.field(9).value(), JsonNode.class); if (aux.has("uf")) { stagedUserFlags = Optional.of(aux.get("uf").intValue()); } @@ -349,8 +350,8 @@ public static CoreTransactionGetResult createFrom(CollectionIdentifier collectio byte[] bodyContent; // body - if (doc.values()[10].status().success()) { - bodyContent = doc.values()[10].value(); + if (doc.field(10).status().success()) { + bodyContent = doc.field(10).value(); } else { bodyContent = new byte[] {}; @@ -369,7 +370,7 @@ public static CoreTransactionGetResult createFrom(CollectionIdentifier collectio revidPreTxn, exptimePreTxn, op, - doc.isDeleted(), + doc.tombstone(), crc32OfStaging, forwardCompatibility, operationId, diff --git a/core-io/src/main/java/com/couchbase/client/core/transaction/cleanup/ClientRecord.java b/core-io/src/main/java/com/couchbase/client/core/transaction/cleanup/ClientRecord.java index 6813d7241..228f762e6 100644 --- a/core-io/src/main/java/com/couchbase/client/core/transaction/cleanup/ClientRecord.java +++ b/core-io/src/main/java/com/couchbase/client/core/transaction/cleanup/ClientRecord.java @@ -17,6 +17,7 @@ import com.couchbase.client.core.Core; import com.couchbase.client.core.annotation.Stability; +import com.couchbase.client.core.api.kv.CoreSubdocGetResult; import com.couchbase.client.core.cnc.RequestTracer; import com.couchbase.client.core.cnc.TracingIdentifiers; import com.couchbase.client.core.deps.com.fasterxml.jackson.core.JsonProcessingException; @@ -179,10 +180,10 @@ private Duration nonMutatingTimeout() { return core.context().environment().timeoutConfig().kvTimeout(); } - public static ClientRecordDetails parseClientRecord(SubdocGetResponse clientRecord, String clientUuid) { + public static ClientRecordDetails parseClientRecord(CoreSubdocGetResult clientRecord, String clientUuid) { try { - JsonNode records = Mapper.reader().readValue(clientRecord.values()[0].value(), JsonNode.class); - JsonNode hlcRaw = Mapper.reader().readValue(clientRecord.values()[1].value(), JsonNode.class); + JsonNode records = Mapper.reader().readValue(clientRecord.field(0).value(), JsonNode.class); + JsonNode hlcRaw = Mapper.reader().readValue(clientRecord.field(1).value(), JsonNode.class); ActiveTransactionRecord.ParsedHLC parsedHLC = new ActiveTransactionRecord.ParsedHLC(hlcRaw); JsonNode clients = records.get("clients"); @@ -244,7 +245,7 @@ public static ClientRecordDetails parseClientRecord(SubdocGetResponse clientReco } } - public Mono getClientRecord(CollectionIdentifier collection, @Nullable SpanWrapper span) { + public Mono getClientRecord(CollectionIdentifier collection, @Nullable SpanWrapper span) { return TransactionKVHandler.lookupIn(core, collection, CLIENT_RECORD_DOC_ID, nonMutatingTimeout(), false, OptionsUtil.createClientContext("ClientRecord::getClientRecord"), span, Arrays.asList( new SubdocGetRequest.Command(SubdocCommandType.GET, FIELD_RECORDS, true, 0), diff --git a/core-io/src/main/java/com/couchbase/client/core/transaction/cleanup/TransactionsCleaner.java b/core-io/src/main/java/com/couchbase/client/core/transaction/cleanup/TransactionsCleaner.java index b928e32f6..80f81ac25 100644 --- a/core-io/src/main/java/com/couchbase/client/core/transaction/cleanup/TransactionsCleaner.java +++ b/core-io/src/main/java/com/couchbase/client/core/transaction/cleanup/TransactionsCleaner.java @@ -17,6 +17,7 @@ import com.couchbase.client.core.Core; import com.couchbase.client.core.annotation.Stability; +import com.couchbase.client.core.api.kv.CoreSubdocGetResult; import com.couchbase.client.core.cnc.Event; import com.couchbase.client.core.cnc.RequestTracer; import com.couchbase.client.core.cnc.TracingIdentifiers; @@ -152,7 +153,7 @@ private Mono commitDocs(CoreTransactionLogger perEntryLog, return hooks.beforeCommitDoc.apply(doc.id()) // Testing hook .then(Mono.defer(() -> { - if (lir.isDeleted()) { + if (lir.tombstone()) { return TransactionKVHandler.insert(core, collection, doc.id(), content, doc.links().stagedUserFlags().orElse(CodecFlags.JSON_COMMON_FLAGS), kvDurableTimeout(), req.durabilityLevel(), OptionsUtil.createClientContext("Cleaner::commitDocsInsert"), pspan); } else { @@ -163,7 +164,7 @@ private Mono commitDocs(CoreTransactionLogger perEntryLog, ); return TransactionKVHandler.mutateIn(core, collection, doc.id(), kvDurableTimeout(), false, false, false, - lir.isDeleted(), false, doc.cas(), doc.links().stagedUserFlags().orElse(CodecFlags.JSON_COMMON_FLAGS), + lir.tombstone(), false, doc.cas(), doc.links().stagedUserFlags().orElse(CodecFlags.JSON_COMMON_FLAGS), req.durabilityLevel(), OptionsUtil.createClientContext("Cleaner::commitDocs"), pspan, commands); } @@ -192,7 +193,7 @@ private Mono removeTxnLinks(CoreTransactionLogger perEntryLog, .then(TransactionKVHandler.mutateIn(core, collectionIdentifier, doc.id(), kvDurableTimeout(), false, false, false, - lir.isDeleted(), false, doc.cas(), doc.userFlags(), + lir.tombstone(), false, doc.cas(), doc.userFlags(), req.durabilityLevel(), OptionsUtil.createClientContext("Cleaner::removeTxnLinks"), pspan, Arrays.asList( new SubdocMutateRequest.Command(SubdocCommandType.DELETE, TransactionFields.TRANSACTION_INTERFACE_PREFIX_ONLY, Bytes.EMPTY_BYTE_ARRAY, false, true, false, 0) ))) @@ -240,7 +241,7 @@ private Mono removeDocs(CoreTransactionLogger perEntryLog, return hooks.beforeRemoveDoc.apply(doc.id()) .then(Mono.defer(() -> { - if (lir.isDeleted()) { + if (lir.tombstone()) { return TransactionKVHandler.mutateIn(core, collection, doc.id(), kvDurableTimeout(), false, false, false, true, false, doc.cas(), doc.userFlags(), @@ -266,7 +267,7 @@ private Mono doPerDoc(CoreTransactionLogger perEntryLog, List docs, SpanWrapper pspan, boolean requireCrc32ToMatchStaging, - TriFunction> perDoc) { + TriFunction> perDoc) { return Flux.fromIterable(docs) .publishOn(core.context().environment().transactionsSchedulers().schedulerCleanup()) .concatMap(docRecord -> { @@ -292,7 +293,7 @@ private Mono doPerDocGotDoc(CoreTransactionLogger perEntryLog, String attemptId, SpanWrapper pspan, boolean requireCrc32ToMatchStaging, - TriFunction> perDoc, + TriFunction> perDoc, DocRecord docRecord, CollectionIdentifier collection, MeteringUnits.MeteringUnitsBuilder units) { @@ -301,12 +302,12 @@ private Mono doPerDocGotDoc(CoreTransactionLogger perEntryLog, .flatMap(docOpt -> { if (docOpt.isPresent()) { CoreTransactionGetResult doc = docOpt.get().getT1(); - SubdocGetResponse lir = docOpt.get().getT2(); + CoreSubdocGetResult lir = docOpt.get().getT2(); MeteringUnits built = units.build(); perEntryLog.debug(attemptId, "handling doc {} with cas {} " + "and links {}, isTombstone={}{}", - DebugUtil.docId(doc), doc.cas(), doc.links(), lir.isDeleted(), DebugUtil.dbg(built)); + DebugUtil.docId(doc), doc.cas(), doc.links(), lir.tombstone(), DebugUtil.dbg(built)); if (!doc.links().isDocumentInTransaction()) { // The txn probably committed this doc then crashed. This is fine, can skip. diff --git a/core-io/src/main/java/com/couchbase/client/core/transaction/components/ActiveTransactionRecord.java b/core-io/src/main/java/com/couchbase/client/core/transaction/components/ActiveTransactionRecord.java index 998140904..cc20cac78 100644 --- a/core-io/src/main/java/com/couchbase/client/core/transaction/components/ActiveTransactionRecord.java +++ b/core-io/src/main/java/com/couchbase/client/core/transaction/components/ActiveTransactionRecord.java @@ -104,15 +104,15 @@ false, createClientContext("ATR::findEntryForTransaction"), pspan, .map(d -> { if (units != null) { - units.add(d.flexibleExtras()); + units.add(d.meta()); } - if (!d.values()[0].status().success()) { + if (!d.field(0).status().success()) { return Optional.empty(); } else { try { - JsonNode atr = MAPPER.readValue(d.values()[0].value(), JsonNode.class); - JsonNode hlc = MAPPER.readValue(d.values()[1].value(), JsonNode.class); + JsonNode atr = MAPPER.readValue(d.field(0).value(), JsonNode.class); + JsonNode hlc = MAPPER.readValue(d.field(1).value(), JsonNode.class); ParsedHLC parsedHLC = new ParsedHLC(hlc); ActiveTransactionRecordEntry entry = createFrom(atrCollection.bucket(), @@ -129,10 +129,10 @@ false, createClientContext("ATR::findEntryForTransaction"), pspan, atrCollection.bucket(), atrCollection.scope(), atrCollection.collection(), atrId, attemptId, DebugUtil.dbg(err)); logger.warn("Attempt to dump raw JSON of ATR entry:"); try { - byte[] raw = d.values()[0].value(); + byte[] raw = d.field(0).value(); String asStr = new String(raw, StandardCharsets.UTF_8); logger.info("", "Raw JSON: {}", asStr); - byte[] rawHLC = d.values()[1].value(); + byte[] rawHLC = d.field(1).value(); String asStrHLC = new String(rawHLC, StandardCharsets.UTF_8); logger.info("", "Raw JSON HLC: {}", asStrHLC); } @@ -244,8 +244,8 @@ public static Mono> getAtr(Core core, // So this code should always be safe. .map(d -> { try { - JsonNode attempts = MAPPER.readValue(d.values()[0].value(), JsonNode.class); - JsonNode hlc = MAPPER.readValue(d.values()[1].value(), JsonNode.class); + JsonNode attempts = MAPPER.readValue(d.field(0).value(), JsonNode.class); + JsonNode hlc = MAPPER.readValue(d.field(1).value(), JsonNode.class); ParsedHLC parsedHLC = new ParsedHLC(hlc); return Optional.of(mapToAtr(atrCollection, atrId, attempts, parsedHLC.nowInNanos(), parsedHLC.mode())); diff --git a/core-io/src/main/java/com/couchbase/client/core/transaction/components/DocumentGetter.java b/core-io/src/main/java/com/couchbase/client/core/transaction/components/DocumentGetter.java index c05d084e5..27ab54b01 100644 --- a/core-io/src/main/java/com/couchbase/client/core/transaction/components/DocumentGetter.java +++ b/core-io/src/main/java/com/couchbase/client/core/transaction/components/DocumentGetter.java @@ -18,6 +18,7 @@ import com.couchbase.client.core.Core; import com.couchbase.client.core.annotation.Stability; +import com.couchbase.client.core.api.kv.CoreSubdocGetResult; import com.couchbase.client.core.io.CollectionIdentifier; import com.couchbase.client.core.transaction.error.internal.ErrorClass; import com.couchbase.client.core.transaction.util.MeteringUnits; @@ -74,10 +75,10 @@ public static Mono> getAsync(Core core, return Mono.just(origTrans.map(v -> v.getT1())); } else if (origTrans.isPresent()) { CoreTransactionGetResult r = origTrans.get().getT1(); - SubdocGetResponse lir = origTrans.get().getT2(); + CoreSubdocGetResult lir = origTrans.get().getT2(); if (!r.links().isDocumentInTransaction()) { - if (lir.isDeleted()) { + if (lir.tombstone()) { return Mono.just(Optional.empty()); } else { @@ -127,7 +128,7 @@ else if (resolvingMissingATREntry.equals(r.links().stagedAttemptId())) { }); } - public static Mono>> + public static Mono>> justGetDoc(Core core, CollectionIdentifier collection, String docId, @@ -154,7 +155,7 @@ else if (resolvingMissingATREntry.equals(r.links().stagedAttemptId())) { )) .map(fragment -> { - units.add(fragment.flexibleExtras()); + units.add(fragment.meta()); try { return Optional.of(Tuples.of(CoreTransactionGetResult.createFrom(collection, docId, @@ -183,10 +184,10 @@ else if (resolvingMissingATREntry.equals(r.links().stagedAttemptId())) { }); } - private static void dumpRawLookupInField(CoreTransactionLogger logger, SubdocGetResponse fragment, int index) { + private static void dumpRawLookupInField(CoreTransactionLogger logger, CoreSubdocGetResult fragment, int index) { try { - if (fragment.values()[index].status().success()) { - byte[] raw = fragment.values()[index].value(); + if (fragment.field(index).status().success()) { + byte[] raw = fragment.field(index).value(); String asStr = new String(raw, StandardCharsets.UTF_8); logger.info("", "Field {}: {}", index, asStr); } diff --git a/core-io/src/main/java/com/couchbase/client/core/transaction/util/MeteringUnits.java b/core-io/src/main/java/com/couchbase/client/core/transaction/util/MeteringUnits.java index bd215c96c..a2b4ea4dc 100644 --- a/core-io/src/main/java/com/couchbase/client/core/transaction/util/MeteringUnits.java +++ b/core-io/src/main/java/com/couchbase/client/core/transaction/util/MeteringUnits.java @@ -16,6 +16,7 @@ package com.couchbase.client.core.transaction.util; import com.couchbase.client.core.annotation.Stability; +import com.couchbase.client.core.api.kv.CoreKvResponseMetadata; import com.couchbase.client.core.error.CouchbaseException; import com.couchbase.client.core.error.context.KeyValueErrorContext; import com.couchbase.client.core.io.netty.kv.MemcacheProtocol; @@ -81,6 +82,15 @@ public void add(@Nullable MemcacheProtocol.FlexibleExtras flexibleExtras) { } } + public void add(CoreKvResponseMetadata meta) { + if (meta.readUnits() != null) { + readUnits += meta.readUnits(); + } + if (meta.writeUnits() != null) { + writeUnits += meta.writeUnits(); + } + } + public void add(Throwable err) { add(from(err)); } diff --git a/core-io/src/main/java/com/couchbase/client/core/transaction/util/TransactionKVHandler.java b/core-io/src/main/java/com/couchbase/client/core/transaction/util/TransactionKVHandler.java index 883f4ef7e..c6bb3092f 100644 --- a/core-io/src/main/java/com/couchbase/client/core/transaction/util/TransactionKVHandler.java +++ b/core-io/src/main/java/com/couchbase/client/core/transaction/util/TransactionKVHandler.java @@ -16,7 +16,11 @@ package com.couchbase.client.core.transaction.util; import com.couchbase.client.core.Core; +import com.couchbase.client.core.CoreKeyspace; +import com.couchbase.client.core.Reactor; import com.couchbase.client.core.annotation.Stability; +import com.couchbase.client.core.api.kv.CoreReadPreference; +import com.couchbase.client.core.api.kv.CoreSubdocGetResult; import com.couchbase.client.core.cnc.TracingIdentifiers; import com.couchbase.client.core.config.BucketConfig; import com.couchbase.client.core.error.DocumentNotFoundException; @@ -46,6 +50,7 @@ import java.util.concurrent.CompletableFuture; import static com.couchbase.client.core.error.DefaultErrorUtil.keyValueStatusToException; +import static com.couchbase.client.core.msg.kv.SubdocGetRequest.convertCommandsToCore; /** * Transactions does a lot of KV work from core-io. This logic is essentially a mini version of java-client, providing @@ -131,7 +136,7 @@ public static Mono remove(final Core core, }); } - public static Mono lookupIn(final Core core, + public static Mono lookupIn(final Core core, CollectionIdentifier collectionIdentifier, final String id, final Duration timeout, @@ -166,7 +171,7 @@ public static Mono lookupIn(final Core core, .response() .thenApply(response -> { if (response.status().success() || response.status() == ResponseStatus.SUBDOC_FAILURE) { - return response; + return response.toCore(CoreKeyspace.from(collectionIdentifier), id); } throw keyValueStatusToException(request, response); }) From 41834f54f30be0a4dd7fc9953de0fbaac76255db Mon Sep 17 00:00:00 2001 From: Graham Pople Date: Tue, 24 Sep 2024 12:12:54 +0100 Subject: [PATCH 14/73] JCBC-2147: Implement Zone Aware Read from Replica (5/n) This commit enables the feature inside transactions. Change-Id: Ib85d30ca6dafe8461c318d3e64c9dc2bcf0ec19e Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/216663 Reviewed-by: Graham Pople Tested-by: Build Bot --- .../client/core/cnc/TracingIdentifiers.java | 1 + .../CoreTransactionAttemptContext.java | 58 ++++++++++++--- .../transaction/cleanup/ClientRecord.java | 1 + .../cleanup/TransactionsCleaner.java | 2 +- .../components/ActiveTransactionRecord.java | 2 + .../components/DocumentGetter.java | 9 ++- .../forwards/CoreTransactionsExtension.java | 1 + .../util/TransactionKVHandler.java | 15 ++++ .../ReactiveTransactionAttemptContext.java | 40 ++++++++++- .../TransactionAttemptContext.java | 45 ++++++++++-- ...eplicaFromPreferredServerGroupOptions.java | 71 +++++++++++++++++++ .../TransactionsSupportedExtensionsUtil.java | 5 +- 12 files changed, 227 insertions(+), 23 deletions(-) create mode 100644 java-client/src/main/java/com/couchbase/client/java/transactions/config/TransactionGetReplicaFromPreferredServerGroupOptions.java diff --git a/core-io/src/main/java/com/couchbase/client/core/cnc/TracingIdentifiers.java b/core-io/src/main/java/com/couchbase/client/core/cnc/TracingIdentifiers.java index 5200bf637..357fcc2ba 100644 --- a/core-io/src/main/java/com/couchbase/client/core/cnc/TracingIdentifiers.java +++ b/core-io/src/main/java/com/couchbase/client/core/cnc/TracingIdentifiers.java @@ -291,6 +291,7 @@ private TracingIdentifiers() {} public static final String TRANSACTION_OP_INSERT = "transaction_insert"; public static final String TRANSACTION_OP_REMOVE = "transaction_remove"; public static final String TRANSACTION_OP_GET = "transaction_get"; + public static final String TRANSACTION_OP_GET_REPLICA_FROM_PREFERRED_SERVER_GROUP = "transaction_get_replica_from_preferred_server_group"; public static final String TRANSACTION_OP_QUERY = "transaction_query"; public static final String TRANSACTION_OP_INSERT_STAGE = "transaction_insert_stage"; public static final String TRANSACTION_OP_REPLACE_STAGE = "transaction_replace_stage"; diff --git a/core-io/src/main/java/com/couchbase/client/core/transaction/CoreTransactionAttemptContext.java b/core-io/src/main/java/com/couchbase/client/core/transaction/CoreTransactionAttemptContext.java index ddf862488..9d34ac8df 100644 --- a/core-io/src/main/java/com/couchbase/client/core/transaction/CoreTransactionAttemptContext.java +++ b/core-io/src/main/java/com/couchbase/client/core/transaction/CoreTransactionAttemptContext.java @@ -44,6 +44,7 @@ import com.couchbase.client.core.error.DecodingFailureException; import com.couchbase.client.core.error.DocumentExistsException; import com.couchbase.client.core.error.DocumentNotFoundException; +import com.couchbase.client.core.error.DocumentUnretrievableException; import com.couchbase.client.core.error.FeatureNotAvailableException; import com.couchbase.client.core.error.context.ReducedKeyValueErrorContext; import com.couchbase.client.core.error.transaction.ActiveTransactionRecordEntryNotFoundException; @@ -440,7 +441,19 @@ private Mono> getInternal(CollectionIdentifie if (queryModeLocked()) { return getWithQueryLocked(collection, id, lockToken, span); } else { - return getWithKVLocked(collection, id, Optional.empty(), span, lockToken); + return getWithKVLocked(collection, id, Optional.empty(), span, lockToken, false); + } + })); + } + + private Mono> getReplicaFromPreferredServerGroupInternal(CollectionIdentifier collection, String id, SpanWrapper pspan) { + + return doKVOperation("get " + DebugUtil.docId(collection, id), pspan, CoreTransactionAttemptContextHooks.HOOK_GET, collection, id, + (operationId, span, lockToken) -> Mono.defer(() -> { + if (queryModeLocked()) { + return Mono.error(new FeatureNotAvailableException("getReplicaFromPreferredServerGroup cannot presently be used in a transaction that has previously involved the query service. It can however be used before any query call.")); + } else { + return getWithKVLocked(collection, id, Optional.empty(), span, lockToken, true); } })); } @@ -449,12 +462,13 @@ private Mono> getWithKVLocked(CollectionIdent String id, Optional resolvingMissingATREntry, SpanWrapper pspan, - ReactiveLock.Waiter lockToken) { + ReactiveLock.Waiter lockToken, + boolean preferredReplicaMode) { return Mono.defer(() -> { assertLocked("getWithKV"); - LOGGER.info(attemptId, "getting doc {}, resolvingMissingATREntry={}", DebugUtil.docId(collection, id), - resolvingMissingATREntry.orElse("")); + LOGGER.info(attemptId, "getting doc {}, resolvingMissingATREntry={}, preferredReplicaMode={}", DebugUtil.docId(collection, id), + resolvingMissingATREntry.orElse(""), preferredReplicaMode); Optional ownWrite = checkForOwnWriteLocked(collection, id); if (ownWrite.isPresent()) { @@ -499,7 +513,8 @@ private Mono> getWithKVLocked(CollectionIdent pspan, resolvingMissingATREntry, units, - overall.supported())) + overall.supported(), + preferredReplicaMode)) .publishOn(scheduler()) @@ -511,7 +526,10 @@ private Mono> getWithKVLocked(CollectionIdent LOGGER.warn(attemptId, "got error while getting doc {}{} in {}us: {}", DebugUtil.docId(collection, id), DebugUtil.dbg(built), pspan.elapsedMicros(), dbg(err)); - if (err instanceof ForwardCompatibilityRequiresRetryException + if (err instanceof DocumentUnretrievableException) { + return Mono.error(err); + } + else if (err instanceof ForwardCompatibilityRequiresRetryException || err instanceof ForwardCompatibilityFailureException) { TransactionOperationFailedException.Builder error = createError() .cause(new ForwardCompatibilityFailureException()); @@ -538,7 +556,8 @@ else if (err instanceof ActiveTransactionRecordNotFoundException || err instance id, Optional.of(attemptIdToCheck), pspan, - newLockToken) + newLockToken, + preferredReplicaMode) .onErrorResume(e -> unlock(newLockToken, "relock error") .then(Mono.error(e)))); @@ -691,6 +710,22 @@ public Mono get(CollectionIdentifier collection, Strin }); } + public Mono getReplicaFromPreferredServerGroup(CollectionIdentifier collection, String id) { + return Mono.defer(() -> { + SpanWrapper span = SpanWrapperUtil.createOp(this, tracer(), collection, id, TracingIdentifiers.TRANSACTION_OP_GET_REPLICA_FROM_PREFERRED_SERVER_GROUP, attemptSpan); + return getReplicaFromPreferredServerGroupInternal(collection, id, span) + .doOnError(err -> span.finishWithErrorStatus()) + .flatMap(doc -> { + span.finish(); + if (doc.isPresent()) { + return Mono.just(doc.get()); + } else { + return Mono.error(new DocumentUnretrievableException(ReducedKeyValueErrorContext.create(id))); + } + }); + }); + } + boolean hasExpiredClientSide(String place, Optional docId) { boolean over = overall.hasExpiredClientSide(); boolean hook = hooks.hasExpiredClientSideHook.apply(this, place, docId); @@ -1873,7 +1908,7 @@ private Mono handleDocExistsDuringStagedInsert(String return hooks.beforeGetDocInExistsDuringStagedInsert.apply(this, id) // testing hook - .then(DocumentGetter.justGetDoc(core, collection, id, kvTimeoutNonMutating(), pspan, true, logger(), units)) + .then(DocumentGetter.justGetDoc(core, collection, id, kvTimeoutNonMutating(), pspan, true, logger(), units, false)) .publishOn(scheduler()) @@ -2932,7 +2967,7 @@ private Mono handleDocChangedDuringCommit(SpanWrapper span, } }) .then(hooks.beforeDocChangedDuringCommit.apply(this, id)) // testing hook - .then(DocumentGetter.getAsync(core, LOGGER, staged.collection, config, staged.id, attemptId, true, span, Optional.empty(), units, overall.supported())) + .then(DocumentGetter.getAsync(core, LOGGER, staged.collection, config, staged.id, attemptId, true, span, Optional.empty(), units, overall.supported(), false)) .publishOn(scheduler()) .onErrorResume(err -> { ErrorClass ec = classify(err); @@ -3002,7 +3037,7 @@ private Mono handleDocChangedDuringStaging(SpanWrapper span, throwIfExpired(id, HOOK_STAGING_DOC_CHANGED); }) .then(hooks.beforeDocChangedDuringStaging.apply(this, id)) // testing hook - .then(DocumentGetter.getAsync(core, LOGGER, collection, config, id, attemptId, true, span, Optional.empty(), units, overall.supported())) + .then(DocumentGetter.getAsync(core, LOGGER, collection, config, id, attemptId, true, span, Optional.empty(), units, overall.supported(), false)) .publishOn(scheduler()) .onErrorResume(err -> { MeteringUnits built = addUnits(units.build()); @@ -3090,7 +3125,7 @@ private Mono handleDocChangedDuringRollback(SpanWrapper span, throwIfExpired(id, HOOK_ROLLBACK_DOC_CHANGED); }) .then(hooks.beforeDocChangedDuringRollback.apply(this, id)) // testing hook - .then(DocumentGetter.getAsync(core, LOGGER, collection, config, id, attemptId, true, span, Optional.empty(), units, overall.supported())) + .then(DocumentGetter.getAsync(core, LOGGER, collection, config, id, attemptId, true, span, Optional.empty(), units, overall.supported(), false)) .publishOn(scheduler()) .onErrorResume(err -> { MeteringUnits built = addUnits(units.build()); @@ -3171,6 +3206,7 @@ private Mono atrCommitAmbiguityResolutionLocked(AtomicReference over .then(hooks.beforeAtrCommitAmbiguityResolution.apply(this)) // testing hook .then(TransactionKVHandler.lookupIn(core, atrCollection.get(), atrId.get(), kvTimeoutNonMutating(), false, OptionsUtil.createClientContext("atrCommitAmbiguityResolution"), span, + false, Arrays.asList( new SubdocGetRequest.Command(SubdocCommandType.GET, "attempts." + attemptId + "." + TransactionFields.ATR_FIELD_STATUS, true, 0) ))) diff --git a/core-io/src/main/java/com/couchbase/client/core/transaction/cleanup/ClientRecord.java b/core-io/src/main/java/com/couchbase/client/core/transaction/cleanup/ClientRecord.java index 228f762e6..bb8e99d47 100644 --- a/core-io/src/main/java/com/couchbase/client/core/transaction/cleanup/ClientRecord.java +++ b/core-io/src/main/java/com/couchbase/client/core/transaction/cleanup/ClientRecord.java @@ -247,6 +247,7 @@ public static ClientRecordDetails parseClientRecord(CoreSubdocGetResult clientRe public Mono getClientRecord(CollectionIdentifier collection, @Nullable SpanWrapper span) { return TransactionKVHandler.lookupIn(core, collection, CLIENT_RECORD_DOC_ID, nonMutatingTimeout(), false, OptionsUtil.createClientContext("ClientRecord::getClientRecord"), span, + false, Arrays.asList( new SubdocGetRequest.Command(SubdocCommandType.GET, FIELD_RECORDS, true, 0), new SubdocGetRequest.Command(SubdocCommandType.GET, "$vbucket.HLC", true, 1) diff --git a/core-io/src/main/java/com/couchbase/client/core/transaction/cleanup/TransactionsCleaner.java b/core-io/src/main/java/com/couchbase/client/core/transaction/cleanup/TransactionsCleaner.java index 80f81ac25..02188e30f 100644 --- a/core-io/src/main/java/com/couchbase/client/core/transaction/cleanup/TransactionsCleaner.java +++ b/core-io/src/main/java/com/couchbase/client/core/transaction/cleanup/TransactionsCleaner.java @@ -297,7 +297,7 @@ private Mono doPerDocGotDoc(CoreTransactionLogger perEntryLog, DocRecord docRecord, CollectionIdentifier collection, MeteringUnits.MeteringUnitsBuilder units) { - return DocumentGetter.justGetDoc(core, collection, docRecord.id(), kvNonMutatingTimeout(), pspan, true, perEntryLog, units) + return DocumentGetter.justGetDoc(core, collection, docRecord.id(), kvNonMutatingTimeout(), pspan, true, perEntryLog, units, false) .flatMap(docOpt -> { if (docOpt.isPresent()) { diff --git a/core-io/src/main/java/com/couchbase/client/core/transaction/components/ActiveTransactionRecord.java b/core-io/src/main/java/com/couchbase/client/core/transaction/components/ActiveTransactionRecord.java index cc20cac78..ebd72ea73 100644 --- a/core-io/src/main/java/com/couchbase/client/core/transaction/components/ActiveTransactionRecord.java +++ b/core-io/src/main/java/com/couchbase/client/core/transaction/components/ActiveTransactionRecord.java @@ -97,6 +97,7 @@ public static Mono> findEntryForTransacti return TransactionKVHandler.lookupIn(core, atrCollection, atrId, kvTimeoutNonMutating(core), false, createClientContext("ATR::findEntryForTransaction"), pspan, + false, Arrays.asList( new SubdocGetRequest.Command(SubdocCommandType.GET, ATR_FIELD_ATTEMPTS + "." + attemptId, true, 0), new SubdocGetRequest.Command(SubdocCommandType.GET, "$vbucket.HLC", true, 1) @@ -228,6 +229,7 @@ public static Mono> getAtr(Core core, Duration timeout, @Nullable SpanWrapper pspan) { return TransactionKVHandler.lookupIn(core, atrCollection, atrId, timeout, false, createClientContext("ATR::getAtr"), pspan, + false, Arrays.asList( new SubdocGetRequest.Command(SubdocCommandType.GET, ATR_FIELD_ATTEMPTS, true, 0), new SubdocGetRequest.Command(SubdocCommandType.GET, "$vbucket.HLC", true, 1) diff --git a/core-io/src/main/java/com/couchbase/client/core/transaction/components/DocumentGetter.java b/core-io/src/main/java/com/couchbase/client/core/transaction/components/DocumentGetter.java index 27ab54b01..8c44e6020 100644 --- a/core-io/src/main/java/com/couchbase/client/core/transaction/components/DocumentGetter.java +++ b/core-io/src/main/java/com/couchbase/client/core/transaction/components/DocumentGetter.java @@ -68,8 +68,9 @@ public static Mono> getAsync(Core core, @Nullable SpanWrapper span, Optional resolvingMissingATREntry, MeteringUnits.MeteringUnitsBuilder units, - CoreTransactionsSupportedExtensions supported) { - return justGetDoc(core, collection, docId, kvTimeoutNonMutating(core), span, true, LOGGER, units) + CoreTransactionsSupportedExtensions supported, + boolean preferredReplicaMode) { + return justGetDoc(core, collection, docId, kvTimeoutNonMutating(core), span, true, LOGGER, units, preferredReplicaMode) .flatMap(origTrans -> { if (justReturn) { return Mono.just(origTrans.map(v -> v.getT1())); @@ -136,9 +137,11 @@ else if (resolvingMissingATREntry.equals(r.links().stagedAttemptId())) { @Nullable SpanWrapper span, boolean accessDeleted, CoreTransactionLogger logger, - MeteringUnits.MeteringUnitsBuilder units) { + MeteringUnits.MeteringUnitsBuilder units, + boolean preferredReplicaMode) { return TransactionKVHandler.lookupIn(core, collection, docId, timeout, accessDeleted, createClientContext("DocumentGetter::justGetDoc"), span, + preferredReplicaMode, Arrays.asList( // The design doc details why these specs are fetched (rather than all of "txn") new SubdocGetRequest.Command(SubdocCommandType.GET, "txn.id", true, 0), diff --git a/core-io/src/main/java/com/couchbase/client/core/transaction/forwards/CoreTransactionsExtension.java b/core-io/src/main/java/com/couchbase/client/core/transaction/forwards/CoreTransactionsExtension.java index 18e7844b2..daec50408 100644 --- a/core-io/src/main/java/com/couchbase/client/core/transaction/forwards/CoreTransactionsExtension.java +++ b/core-io/src/main/java/com/couchbase/client/core/transaction/forwards/CoreTransactionsExtension.java @@ -53,6 +53,7 @@ public enum CoreTransactionsExtension { EXT_QUERY_CONTEXT("QC"), EXT_BINARY_SUPPORT("BS"), EXT_PARALLEL_UNSTAGING("PU"), + EXT_REPLICA_FROM_PREFERRED_GROUP("RP"), ; private final String value; diff --git a/core-io/src/main/java/com/couchbase/client/core/transaction/util/TransactionKVHandler.java b/core-io/src/main/java/com/couchbase/client/core/transaction/util/TransactionKVHandler.java index c6bb3092f..f5865ec3f 100644 --- a/core-io/src/main/java/com/couchbase/client/core/transaction/util/TransactionKVHandler.java +++ b/core-io/src/main/java/com/couchbase/client/core/transaction/util/TransactionKVHandler.java @@ -24,6 +24,8 @@ import com.couchbase.client.core.cnc.TracingIdentifiers; import com.couchbase.client.core.config.BucketConfig; import com.couchbase.client.core.error.DocumentNotFoundException; +import com.couchbase.client.core.error.DocumentUnretrievableException; +import com.couchbase.client.core.error.context.ReducedKeyValueErrorContext; import com.couchbase.client.core.io.CollectionIdentifier; import com.couchbase.client.core.msg.ResponseStatus; import com.couchbase.client.core.msg.kv.DurabilityLevel; @@ -36,6 +38,7 @@ import com.couchbase.client.core.msg.kv.SubdocMutateRequest; import com.couchbase.client.core.msg.kv.SubdocMutateResponse; import com.couchbase.client.core.retry.BestEffortRetryStrategy; +import com.couchbase.client.core.service.kv.ReplicaHelper; import com.couchbase.client.core.transaction.support.SpanWrapper; import com.couchbase.client.core.transaction.log.CoreTransactionLogger; import com.couchbase.client.core.transaction.support.SpanWrapperUtil; @@ -143,11 +146,23 @@ public static Mono lookupIn(final Core core, boolean accessDeleted, final Map clientContext, @Nullable final SpanWrapper pspan, + boolean preferredReplicaMode, final List commands) { return Mono.defer(() -> { long start = System.nanoTime(); SpanWrapper span = SpanWrapperUtil.createOp(null, core.context().environment().requestTracer(), collectionIdentifier, id, TracingIdentifiers.SPAN_REQUEST_KV_LOOKUP_IN, pspan); + + if (preferredReplicaMode) { + CompletableFuture replicas = ReplicaHelper.lookupInAnyReplicaAsync(core, collectionIdentifier, id, convertCommandsToCore(commands), timeout, BestEffortRetryStrategy.INSTANCE, + clientContext, pspan == null ? null : pspan.span(), CoreReadPreference.PREFERRED_SERVER_GROUP, (r) -> r); + + return Reactor.wrap(replicas, () -> {}) + .switchIfEmpty(Mono.error(new DocumentUnretrievableException(ReducedKeyValueErrorContext.create(id, collectionIdentifier)))) + .doOnError(span::recordException) + .doOnTerminate(span::finish); + } + byte flags = 0; if (accessDeleted) { flags |= SubdocMutateRequest.SUBDOC_DOC_FLAG_ACCESS_DELETED; diff --git a/java-client/src/main/java/com/couchbase/client/java/transactions/ReactiveTransactionAttemptContext.java b/java-client/src/main/java/com/couchbase/client/java/transactions/ReactiveTransactionAttemptContext.java index 118542e3a..e3d9afba9 100644 --- a/java-client/src/main/java/com/couchbase/client/java/transactions/ReactiveTransactionAttemptContext.java +++ b/java-client/src/main/java/com/couchbase/client/java/transactions/ReactiveTransactionAttemptContext.java @@ -22,7 +22,6 @@ import com.couchbase.client.core.cnc.CbTracing; import com.couchbase.client.core.cnc.RequestSpan; import com.couchbase.client.core.cnc.TracingIdentifiers; -import com.couchbase.client.core.deps.com.fasterxml.jackson.databind.node.ObjectNode; import com.couchbase.client.core.error.DocumentNotFoundException; import com.couchbase.client.core.transaction.CoreTransactionAttemptContext; import com.couchbase.client.core.transaction.log.CoreTransactionLogger; @@ -32,6 +31,7 @@ import com.couchbase.client.java.codec.JsonSerializer; import com.couchbase.client.java.codec.Transcoder; import com.couchbase.client.java.transactions.config.TransactionGetOptions; +import com.couchbase.client.java.transactions.config.TransactionGetReplicaFromPreferredServerGroupOptions; import com.couchbase.client.java.transactions.config.TransactionInsertOptions; import com.couchbase.client.java.transactions.config.TransactionReplaceOptions; import reactor.core.publisher.Mono; @@ -41,6 +41,7 @@ import static com.couchbase.client.core.cnc.TracingIdentifiers.TRANSACTION_OP_INSERT; import static com.couchbase.client.core.cnc.TracingIdentifiers.TRANSACTION_OP_REMOVE; import static com.couchbase.client.core.cnc.TracingIdentifiers.TRANSACTION_OP_REPLACE; +import static com.couchbase.client.core.util.Validators.notNull; import static com.couchbase.client.java.transactions.internal.ConverterUtil.makeCollectionIdentifier; import static com.couchbase.client.java.transactions.internal.EncodingUtil.encode; @@ -91,6 +92,43 @@ public Mono get(ReactiveCollection collection, String id, .map(result -> new TransactionGetResult(result, serializer(), built.transcoder())); } + /** + * A convenience wrapper around {@link #getReplicaFromPreferredServerGroup(ReactiveCollection, String, TransactionGetReplicaFromPreferredServerGroupOptions)} + * using default options. + */ + public Mono getReplicaFromPreferredServerGroup(ReactiveCollection collection, String id) { + return getReplicaFromPreferredServerGroup(collection, id, TransactionGetReplicaFromPreferredServerGroupOptions.DEFAULT); + } + + /** + * Gets a document from the specified Couchbase collection matching the specified id. + *

+ * It will be fetched only from document copies that on nodes in the preferred server group, which can + * be configured with {@link com.couchbase.client.java.env.ClusterEnvironment.Builder#preferredServerGroup(String)}. + *

+ * If no replica can be retrieved, which can include for reasons such as this preferredServerGroup not being set, + * and misconfigured server groups, then {@link com.couchbase.client.core.error.DocumentUnretrievableException} + * can be raised. It is strongly recommended that this method always be used with a fallback strategy, such as: + * + * try { + * var result = ctx.getReplicaFromPreferredServerGroup(collection, id); + * } catch (DocumentUnretrievableException err) { + * var result = ctx.get(collection, id); + * } + * + * + * @param collection the Couchbase collection the document exists on + * @param id the document's ID + * @param options options controlling the operation + * @return a TransactionGetResult containing the document + */ + public Mono getReplicaFromPreferredServerGroup(ReactiveCollection collection, String id, TransactionGetReplicaFromPreferredServerGroupOptions options) { + notNull(options, "Options"); + TransactionGetReplicaFromPreferredServerGroupOptions.Built built = options.build(); + return internal.getReplicaFromPreferredServerGroup(makeCollectionIdentifier(collection.async()), id) + .map(result -> new TransactionGetResult(result, serializer(), built.transcoder())); + } + /** * Inserts a new document into the specified Couchbase collection. * diff --git a/java-client/src/main/java/com/couchbase/client/java/transactions/TransactionAttemptContext.java b/java-client/src/main/java/com/couchbase/client/java/transactions/TransactionAttemptContext.java index f8b64a397..999fc6c77 100644 --- a/java-client/src/main/java/com/couchbase/client/java/transactions/TransactionAttemptContext.java +++ b/java-client/src/main/java/com/couchbase/client/java/transactions/TransactionAttemptContext.java @@ -22,11 +22,7 @@ import com.couchbase.client.core.cnc.CbTracing; import com.couchbase.client.core.cnc.RequestSpan; import com.couchbase.client.core.cnc.TracingIdentifiers; -import com.couchbase.client.core.deps.com.fasterxml.jackson.databind.node.ObjectNode; import com.couchbase.client.core.error.CouchbaseException; -import com.couchbase.client.core.error.EncodingFailureException; -import com.couchbase.client.core.json.Mapper; -import com.couchbase.client.core.msg.query.QueryRequest; import com.couchbase.client.core.transaction.CoreTransactionAttemptContext; import com.couchbase.client.core.transaction.log.CoreTransactionLogger; import com.couchbase.client.core.transaction.support.SpanWrapper; @@ -36,11 +32,10 @@ import com.couchbase.client.java.codec.Transcoder; import com.couchbase.client.java.json.JsonObject; import com.couchbase.client.java.transactions.config.TransactionGetOptions; +import com.couchbase.client.java.transactions.config.TransactionGetReplicaFromPreferredServerGroupOptions; import com.couchbase.client.java.transactions.config.TransactionInsertOptions; import com.couchbase.client.java.transactions.config.TransactionReplaceOptions; -import reactor.util.annotation.Nullable; -import java.io.IOException; import java.util.Objects; import static com.couchbase.client.core.cnc.TracingIdentifiers.TRANSACTION_OP_INSERT; @@ -108,6 +103,44 @@ public TransactionGetResult get(Collection collection, String id, TransactionGet .block(); } + /** + * A convenience wrapper around {@link #getReplicaFromPreferredServerGroup(Collection, String, TransactionGetReplicaFromPreferredServerGroupOptions)} + * using default options. + */ + public TransactionGetResult getReplicaFromPreferredServerGroup(Collection collection, String id) { + return getReplicaFromPreferredServerGroup(collection, id, TransactionGetReplicaFromPreferredServerGroupOptions.DEFAULT); + } + + /** + * Gets a document from the specified Couchbase collection matching the specified id. + *

+ * It will be fetched only from document copies that on nodes in the preferred server group, which can + * be configured with {@link com.couchbase.client.java.env.ClusterEnvironment.Builder#preferredServerGroup(String)}. + *

+ * If no replica can be retrieved, which can include for reasons such as this preferredServerGroup not being set, + * and misconfigured server groups, then {@link com.couchbase.client.core.error.DocumentUnretrievableException} + * can be raised. It is strongly recommended that this method always be used with a fallback strategy, such as: + * + * try { + * var result = ctx.getReplicaFromPreferredServerGroup(collection, id); + * } catch (DocumentUnretrievableException err) { + * var result = ctx.get(collection, id); + * } + * + * + * @param collection the Couchbase collection the document exists on + * @param id the document's ID + * @param options options controlling the operation + * @return a TransactionGetResult containing the document + */ + public TransactionGetResult getReplicaFromPreferredServerGroup(Collection collection, String id, TransactionGetReplicaFromPreferredServerGroupOptions options) { + notNull(options, "Options"); + TransactionGetReplicaFromPreferredServerGroupOptions.Built built = options.build(); + return internal.getReplicaFromPreferredServerGroup(makeCollectionIdentifier(collection.async()), id) + .map(result -> new TransactionGetResult(result, serializer(), built.transcoder())) + .block(); + } + /** * Mutates the specified doc with new content. *

diff --git a/java-client/src/main/java/com/couchbase/client/java/transactions/config/TransactionGetReplicaFromPreferredServerGroupOptions.java b/java-client/src/main/java/com/couchbase/client/java/transactions/config/TransactionGetReplicaFromPreferredServerGroupOptions.java new file mode 100644 index 000000000..67a1ef696 --- /dev/null +++ b/java-client/src/main/java/com/couchbase/client/java/transactions/config/TransactionGetReplicaFromPreferredServerGroupOptions.java @@ -0,0 +1,71 @@ +/* + * Copyright 2024 Couchbase, Inc. + * + * Licensed 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 com.couchbase.client.java.transactions.config; + +import com.couchbase.client.core.annotation.SinceCouchbase; +import com.couchbase.client.core.annotation.Stability; +import com.couchbase.client.java.codec.Transcoder; +import reactor.util.annotation.Nullable; + +/** + * Operations controlling a transactional getReplicaFromPreferredServerGroup. + */ +@Stability.Volatile +public class TransactionGetReplicaFromPreferredServerGroupOptions { + public static final TransactionGetReplicaFromPreferredServerGroupOptions DEFAULT = new TransactionGetReplicaFromPreferredServerGroupOptions(); + + private @Nullable Transcoder transcoder; + + private TransactionGetReplicaFromPreferredServerGroupOptions() { + } + + public static TransactionGetReplicaFromPreferredServerGroupOptions transactionGetReplicaFromPreferredServerGroupOptions() { + return new TransactionGetReplicaFromPreferredServerGroupOptions(); + } + + /** + * Specify a custom {@link Transcoder} that is used to decode the content of the result. + *

+ * If not-specified, the {@link com.couchbase.client.java.env.ClusterEnvironment}'s {@link com.couchbase.client.java.codec.JsonSerializer} + * (NOT transcoder) is used. + *

+ * It is marked as being available from 7.6.2 because prior to this, only JSON documents were supported in transactions. This release added + * support for binary documents. + * + * @param transcoder the custom transcoder that should be used for decoding. + * @return this to allow method chaining. + */ + @SinceCouchbase("7.6.2") + public TransactionGetReplicaFromPreferredServerGroupOptions transcoder(Transcoder transcoder) { + this.transcoder = transcoder; + return this; + } + + @Stability.Internal + public TransactionGetReplicaFromPreferredServerGroupOptions.Built build() { + return new TransactionGetReplicaFromPreferredServerGroupOptions.Built(); + } + + @Stability.Internal + public class Built { + Built() { + } + + public Transcoder transcoder() { + return transcoder; + } + } +} diff --git a/java-client/src/main/java/com/couchbase/client/java/transactions/internal/TransactionsSupportedExtensionsUtil.java b/java-client/src/main/java/com/couchbase/client/java/transactions/internal/TransactionsSupportedExtensionsUtil.java index 5a114bda9..ea14b97c2 100644 --- a/java-client/src/main/java/com/couchbase/client/java/transactions/internal/TransactionsSupportedExtensionsUtil.java +++ b/java-client/src/main/java/com/couchbase/client/java/transactions/internal/TransactionsSupportedExtensionsUtil.java @@ -56,7 +56,10 @@ public class TransactionsSupportedExtensionsUtil { /* @since 3.7.0 */ CoreTransactionsExtension.EXT_BINARY_SUPPORT, + /* @since 3.7.3 */ + CoreTransactionsExtension.EXT_PARALLEL_UNSTAGING, + /* @since 3.7.4 */ - CoreTransactionsExtension.EXT_PARALLEL_UNSTAGING + CoreTransactionsExtension.EXT_REPLICA_FROM_PREFERRED_GROUP ); } From 46dff11b97541fc0d64ed8c2a88d272018ab2e77 Mon Sep 17 00:00:00 2001 From: David Nault Date: Wed, 4 Sep 2024 16:06:44 -0700 Subject: [PATCH 15/73] KCBC-96 Add transaction support to Kotlin SDK Change-Id: Ib69cb2b852283b4e0fc6e44c47d726c9bc5428cf Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/215597 Reviewed-by: David Nault Tested-by: Build Bot --- kotlin-client/pom.xml | 17 +- .../com/couchbase/client/kotlin/Cluster.kt | 9 + .../com/couchbase/client/kotlin/Scope.kt | 5 +- .../kotlin/query/internal/QueryExecutor.kt | 145 ++++++---- .../transactions/TransactionAttemptContext.kt | 248 ++++++++++++++++++ .../TransactionCommitAmbiguousException.kt | 38 +++ .../TransactionExpiredException.kt | 27 ++ .../TransactionFailedException.kt | 54 ++++ .../transactions/TransactionGetResult.kt | 59 +++++ .../kotlin/transactions/TransactionResult.kt | 71 +++++ .../kotlin/transactions/Transactions.kt | 131 +++++++++ 11 files changed, 746 insertions(+), 58 deletions(-) create mode 100644 kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/transactions/TransactionAttemptContext.kt create mode 100644 kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/transactions/TransactionCommitAmbiguousException.kt create mode 100644 kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/transactions/TransactionExpiredException.kt create mode 100644 kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/transactions/TransactionFailedException.kt create mode 100644 kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/transactions/TransactionGetResult.kt create mode 100644 kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/transactions/TransactionResult.kt create mode 100644 kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/transactions/Transactions.kt diff --git a/kotlin-client/pom.xml b/kotlin-client/pom.xml index 9b4e13035..3729fd66e 100644 --- a/kotlin-client/pom.xml +++ b/kotlin-client/pom.xml @@ -30,6 +30,12 @@ + + + com.couchbase.client + core-io + + org.jetbrains.kotlin @@ -51,6 +57,11 @@ kotlinx-coroutines-reactive ${kotlin.coroutines.version} + + org.jetbrains.kotlinx + kotlinx-coroutines-reactor + ${kotlin.coroutines.version} + @@ -85,12 +96,6 @@ true - - - com.couchbase.client - core-io - - com.couchbase.client diff --git a/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/Cluster.kt b/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/Cluster.kt index 4821ce3c5..4ca775c05 100644 --- a/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/Cluster.kt +++ b/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/Cluster.kt @@ -39,6 +39,7 @@ import com.couchbase.client.kotlin.analytics.AnalyticsPriority import com.couchbase.client.kotlin.analytics.AnalyticsScanConsistency import com.couchbase.client.kotlin.analytics.internal.AnalyticsExecutor import com.couchbase.client.kotlin.annotations.UncommittedCouchbaseApi +import com.couchbase.client.kotlin.annotations.VolatileCouchbaseApi import com.couchbase.client.kotlin.codec.JsonSerializer import com.couchbase.client.kotlin.diagnostics.DiagnosticsResult import com.couchbase.client.kotlin.diagnostics.PingResult @@ -71,6 +72,7 @@ import com.couchbase.client.kotlin.search.SearchRow import com.couchbase.client.kotlin.search.SearchScanConsistency import com.couchbase.client.kotlin.search.SearchSort import com.couchbase.client.kotlin.search.SearchSpec +import com.couchbase.client.kotlin.transactions.Transactions import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.future.await import kotlinx.coroutines.reactive.awaitSingle @@ -186,6 +188,13 @@ public class Cluster internal constructor( public val httpClient: CouchbaseHttpClient get() = CouchbaseHttpClient(this) + /** + * A runner for transactional operations. + */ + @VolatileCouchbaseApi + public val transactions: Transactions + get() = Transactions(core) + /** * A manager for administering buckets (create, update, drop, flush, list, etc.) */ diff --git a/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/Scope.kt b/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/Scope.kt index b7ab9f8f6..c159d056a 100644 --- a/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/Scope.kt +++ b/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/Scope.kt @@ -71,13 +71,14 @@ import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.reactive.asFlow import kotlinx.coroutines.reactive.awaitSingle -import java.util.* +import java.util.UUID import java.util.concurrent.ConcurrentHashMap public class Scope( public val name: String, public val bucket: Bucket, ) { + internal val queryContext = CoreQueryContext.of(bucket.name, name) internal val couchbaseOps: CoreCouchbaseOps = bucket.couchbaseOps private val searchOps = couchbaseOps.searchOps(CoreBucketAndScope(bucket.name, name)) @@ -85,7 +86,7 @@ public class Scope( private val queryExecutor = QueryExecutor( couchbaseOps.queryOps(), - CoreQueryContext.of(bucket.name, name), + queryContext, bucket.env.jsonSerializer, ) diff --git a/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/query/internal/QueryExecutor.kt b/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/query/internal/QueryExecutor.kt index ee56ffbe3..8810c101d 100644 --- a/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/query/internal/QueryExecutor.kt +++ b/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/query/internal/QueryExecutor.kt @@ -77,56 +77,25 @@ internal class QueryExecutor( ): Flow { val actualSerializer = serializer ?: defaultSerializer - val coreQueryOpts = object : CoreQueryOptions { - override fun adhoc(): Boolean = adhoc - override fun clientContextId(): String? = clientContextId - override fun consistentWith(): CoreMutationState? = (consistency as? QueryScanConsistency.ConsistentWith)?.toCore() - override fun maxParallelism(): Int? = maxParallelism - override fun metrics(): Boolean = metrics - - override fun namedParameters(): ObjectNode? = (parameters as? QueryParameters.Named)?.let { params -> - // Let the user's serializer serialize arguments - val map = mutableMapOf() - params.inject(map) - val queryBytes = actualSerializer.serialize(map, typeRef()) - Mapper.decodeIntoTree(queryBytes) as ObjectNode - } - - override fun pipelineBatch(): Int? = pipelineBatch - override fun pipelineCap(): Int? = pipelineCap - - override fun positionalParameters(): ArrayNode? = (parameters as? QueryParameters.Positional)?.let { params -> - // Let the user's serializer serialize arguments - val map = mutableMapOf() - params.inject(map) - val queryBytes = actualSerializer.serialize(map, typeRef()) - Mapper.decodeIntoTree(queryBytes).get("args") as? ArrayNode - } - - override fun profile(): CoreQueryProfile = profile.core - - override fun raw(): JsonNode? { - if (raw.isEmpty()) return null - val rawBytes = actualSerializer.serialize(raw, typeRef()) - return Mapper.decodeIntoTree(rawBytes) - } - - override fun readonly(): Boolean = readonly - override fun scanWait(): java.time.Duration? = consistency.scanWait?.toJavaDuration() - override fun scanCap(): Int? = scanCap - - override fun scanConsistency(): CoreQueryScanConsistency? = when (consistency) { - is QueryScanConsistency.NotBounded -> CoreQueryScanConsistency.NOT_BOUNDED - is QueryScanConsistency.RequestPlus -> CoreQueryScanConsistency.REQUEST_PLUS - else -> null // ConsistentWith is handled by separate consistentWith() accessor - } - - override fun flexIndex(): Boolean = flexIndex - override fun preserveExpiry(): Boolean? = if (preserveExpiry) true else null - override fun asTransactionOptions(): CoreSingleQueryTransactionOptions? = null - override fun commonOptions(): CoreCommonOptions = common.toCore() - override fun useReplica(): Boolean? = useReplica - } + val coreQueryOpts = CoreQueryOptions( + common = common, + parameters = parameters, + preserveExpiry = preserveExpiry, + actualSerializer = actualSerializer, + consistency = consistency, + readonly = readonly, + adhoc = adhoc, + flexIndex = flexIndex, + metrics = metrics, + profile = profile, + maxParallelism = maxParallelism, + scanCap = scanCap, + pipelineBatch = pipelineBatch, + pipelineCap = pipelineCap, + clientContextId = clientContextId, + raw = raw, + useReplica = useReplica, + ) return flow { val response = queryOps.queryReactive( @@ -147,3 +116,79 @@ internal class QueryExecutor( } } } + +internal fun CoreQueryOptions( + common: CommonOptions, + parameters: QueryParameters, + preserveExpiry: Boolean, + + actualSerializer: JsonSerializer, + + consistency: QueryScanConsistency, + readonly: Boolean, + adhoc: Boolean, + flexIndex: Boolean, + + metrics: Boolean, + profile: QueryProfile, + + maxParallelism: Int?, + scanCap: Int?, + pipelineBatch: Int?, + pipelineCap: Int?, + + clientContextId: String?, + raw: Map, + useReplica: Boolean?, +): CoreQueryOptions { + return object : CoreQueryOptions { + override fun adhoc(): Boolean = adhoc + override fun clientContextId(): String? = clientContextId + override fun consistentWith(): CoreMutationState? = (consistency as? QueryScanConsistency.ConsistentWith)?.toCore() + override fun maxParallelism(): Int? = maxParallelism + override fun metrics(): Boolean = metrics + + override fun namedParameters(): ObjectNode? = (parameters as? QueryParameters.Named)?.let { params -> + // Let the user's serializer serialize arguments + val map = mutableMapOf() + params.inject(map) + val queryBytes = actualSerializer.serialize(map, typeRef()) + Mapper.decodeIntoTree(queryBytes) as ObjectNode + } + + override fun pipelineBatch(): Int? = pipelineBatch + override fun pipelineCap(): Int? = pipelineCap + + override fun positionalParameters(): ArrayNode? = (parameters as? QueryParameters.Positional)?.let { params -> + // Let the user's serializer serialize arguments + val map = mutableMapOf() + params.inject(map) + val queryBytes = actualSerializer.serialize(map, typeRef()) + Mapper.decodeIntoTree(queryBytes).get("args") as? ArrayNode + } + + override fun profile(): CoreQueryProfile = profile.core + + override fun raw(): JsonNode? { + if (raw.isEmpty()) return null + val rawBytes = actualSerializer.serialize(raw, typeRef()) + return Mapper.decodeIntoTree(rawBytes) + } + + override fun readonly(): Boolean = readonly + override fun scanWait(): java.time.Duration? = consistency.scanWait?.toJavaDuration() + override fun scanCap(): Int? = scanCap + + override fun scanConsistency(): CoreQueryScanConsistency? = when (consistency) { + is QueryScanConsistency.NotBounded -> CoreQueryScanConsistency.NOT_BOUNDED + is QueryScanConsistency.RequestPlus -> CoreQueryScanConsistency.REQUEST_PLUS + else -> null // ConsistentWith is handled by separate consistentWith() accessor + } + + override fun flexIndex(): Boolean = flexIndex + override fun preserveExpiry(): Boolean? = if (preserveExpiry) true else null + override fun asTransactionOptions(): CoreSingleQueryTransactionOptions? = null + override fun commonOptions(): CoreCommonOptions = common.toCore() + override fun useReplica(): Boolean? = useReplica + } +} diff --git a/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/transactions/TransactionAttemptContext.kt b/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/transactions/TransactionAttemptContext.kt new file mode 100644 index 000000000..27db7945a --- /dev/null +++ b/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/transactions/TransactionAttemptContext.kt @@ -0,0 +1,248 @@ +/* + * Copyright 2024 Couchbase, Inc. + * + * Licensed 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 com.couchbase.client.kotlin.transactions + +import com.couchbase.client.core.api.query.CoreQueryResult +import com.couchbase.client.core.cnc.CbTracing +import com.couchbase.client.core.cnc.RequestSpan +import com.couchbase.client.core.cnc.TracingIdentifiers +import com.couchbase.client.core.error.CasMismatchException +import com.couchbase.client.core.error.DocumentExistsException +import com.couchbase.client.core.error.DocumentNotFoundException +import com.couchbase.client.core.msg.kv.CodecFlags +import com.couchbase.client.core.transaction.CoreTransactionAttemptContext +import com.couchbase.client.core.transaction.support.SpanWrapper +import com.couchbase.client.kotlin.Collection +import com.couchbase.client.kotlin.CommonOptions +import com.couchbase.client.kotlin.Scope +import com.couchbase.client.kotlin.codec.Content +import com.couchbase.client.kotlin.codec.JsonSerializer +import com.couchbase.client.kotlin.codec.TypeRef +import com.couchbase.client.kotlin.codec.typeRef +import com.couchbase.client.kotlin.internal.await +import com.couchbase.client.kotlin.query.QueryMetadata +import com.couchbase.client.kotlin.query.QueryParameters +import com.couchbase.client.kotlin.query.QueryProfile +import com.couchbase.client.kotlin.query.QueryResult +import com.couchbase.client.kotlin.query.QueryRow +import com.couchbase.client.kotlin.query.QueryScanConsistency +import com.couchbase.client.kotlin.query.internal.CoreQueryOptions +import kotlinx.coroutines.reactive.awaitSingle +import java.util.UUID +import java.util.stream.Collectors + +public class TransactionAttemptContext internal constructor( + private val internal: CoreTransactionAttemptContext, + private val defaultJsonSerializer: JsonSerializer, +) { + /** + * Gets a document from the specified Couchbase [collection] matching the specified [id]. + * + * @param collection the Couchbase collection containing the document + * @param id the ID of the document to get + * @return a [TransactionGetResult] containing the document + * @throws DocumentNotFoundException if the document does not exist + */ + public suspend fun get(collection: Collection, id: String): TransactionGetResult { + val core = internal.get(collection.collectionId, id).awaitSingle() + return TransactionGetResult(core, defaultJsonSerializer) + } + + /** + * Mutates the specified [doc] with new content. + * + * The mutation is staged until the transaction is committed. + * That is, any read of the document by any Couchbase component + * will see the document's current value, rather than this staged or 'dirty' data. + * If the attempt is rolled back, the staged mutation will be removed. + * + * This staged data effectively locks the document from other transactional writes + * until the attempt completes (commits or rolls back). + * + * If the mutation fails with a [CasMismatchException] or any other exception, + * the transaction will automatically roll back this attempt, then retry. + * + * @param doc identifies the document to update + * @param content the new content for the document. + * @return the document, updated with its new CAS value. + */ + public suspend inline fun replace( + doc: TransactionGetResult, + content: T, + jsonSerializer: JsonSerializer? = null, + ): TransactionGetResult { + return replaceInternal(doc, content, typeRef(), jsonSerializer) + } + + @PublishedApi + internal suspend fun replaceInternal( + doc: TransactionGetResult, + content: T, + type: TypeRef, + jsonSerializer: JsonSerializer?, + ): TransactionGetResult { + val span: RequestSpan = CbTracing.newSpan(internal.core().context(), TracingIdentifiers.TRANSACTION_OP_REPLACE, internal.span()) + + val encoded = serialize(content, type, jsonSerializer) + val core = internal.replace(doc.internal, encoded.bytes, encoded.flags, SpanWrapper(span)).awaitSingle() + return TransactionGetResult(core, defaultJsonSerializer) + } + + /** + * Inserts a new document into the specified Couchbase [collection]. + * + * As with [replace], the insert is staged until the transaction is committed. + * Due to technical limitations, it is not as possible to completely hide the staged data + * from the rest of the Couchbase platform, as an empty document must be created. + * + * This staged data effectively locks the document from other transactional writes + * until the attempt completes (commits or rolls back). + * + * @param collection the Couchbase collection in which to insert the doc + * @param id the document's unique ID + * @param content the content to insert. + * @return the document, updated with its new CAS value and ID, and converted to a [TransactionGetResult] + * @throws DocumentExistsException if the collection already contains a document with the given ID. + */ + public suspend inline fun insert( + collection: Collection, + id: String, + content: T, + jsonSerializer: JsonSerializer? = null, + ): TransactionGetResult { + return insertInternal(collection, id, content, typeRef(), jsonSerializer) + } + + @PublishedApi + internal suspend fun insertInternal( + collection: Collection, + id: String, + content: T, + type: TypeRef, + jsonSerializer: JsonSerializer?, + ): TransactionGetResult { + val span: RequestSpan = CbTracing.newSpan(internal.core().context(), TracingIdentifiers.TRANSACTION_OP_INSERT, internal.span()) + + val encoded = serialize(content, type, jsonSerializer) + val core = internal.insert(collection.collectionId, id, encoded.bytes, encoded.flags, SpanWrapper(span)).awaitSingle() + return TransactionGetResult(core, defaultJsonSerializer) + } + + /** + * Removes the specified [doc]. + * + * As with [replace], the remove is staged until the transaction is committed. + * That is, the document will continue to exist, and the rest of the Couchbase platform will continue to see it. + * + * This staged data effectively locks the document from other transactional writes + * until the attempt completes (commits or rolls back). + * + * Note that an overload that takes the document ID as a string is not possible, as it's necessary to check a provided + * [TransactionGetResult] to determine if the document is involved in another transaction. + * + * @param doc the document to remove + */ + public suspend fun remove(doc: TransactionGetResult) { + val span: RequestSpan = CbTracing.newSpan(internal.core().context(), TracingIdentifiers.TRANSACTION_OP_REMOVE, internal.span()) + + internal.remove(doc.internal, SpanWrapper(span)).await() + } + + public suspend fun query( + statement: String, + parameters: QueryParameters = QueryParameters.None, + scope: Scope? = null, + serializer: JsonSerializer? = null, + + consistency: QueryScanConsistency = QueryScanConsistency.notBounded(), + readonly: Boolean = false, + adhoc: Boolean = true, + flexIndex: Boolean = false, + + profile: QueryProfile = QueryProfile.OFF, + + scanCap: Int? = null, + pipelineBatch: Int? = null, + pipelineCap: Int? = null, + + clientContextId: String? = UUID.randomUUID().toString(), + raw: Map = emptyMap(), + ): QueryResult { + require(consistency !is QueryScanConsistency.ConsistentWith) { + "Query in transaction does not support `QueryScanConsistency.ConsistentWith`." + } + + val actualSerializer = serializer ?: defaultJsonSerializer + + val common = CommonOptions.Default + val maxParallelism: Int? = null + val metrics = true + val preserveExpiry = false + val useReplica: Boolean? = null + + val coreQueryOpts = CoreQueryOptions( + common = common, + parameters = parameters, + preserveExpiry = preserveExpiry, + actualSerializer = actualSerializer, + consistency = consistency, + readonly = readonly, + adhoc = adhoc, + flexIndex = flexIndex, + metrics = metrics, + profile = profile, + maxParallelism = maxParallelism, + scanCap = scanCap, + pipelineBatch = pipelineBatch, + pipelineCap = pipelineCap, + clientContextId = clientContextId, + raw = raw, + useReplica = useReplica, + ) + + val coreQueryResult: CoreQueryResult = internal.queryBlocking( + statement, + scope?.queryContext, + coreQueryOpts, + false, + ).awaitSingle() + + val rows = coreQueryResult.rows() + .map { QueryRow(it.data(), actualSerializer) } + .collect(Collectors.toList()) + + val metadata = QueryMetadata(coreQueryResult.metaData()) + + return QueryResult(rows, metadata) + } + + private fun serialize( + content: T, + type: TypeRef, + jsonSerializer: JsonSerializer?, + ): Content { + if (content is Content) { + require(content.flags == CodecFlags.JSON_COMPAT_FLAGS || content.flags == CodecFlags.BINARY_COMPAT_FLAGS) { + "Content in transaction must be flagged as JSON or BINARY, but got ${content.flags}" + } + return content + } + val jsonBytes = (jsonSerializer ?: defaultJsonSerializer).serialize(content, type) + return Content.json(jsonBytes) + } + +} diff --git a/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/transactions/TransactionCommitAmbiguousException.kt b/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/transactions/TransactionCommitAmbiguousException.kt new file mode 100644 index 000000000..0a3328367 --- /dev/null +++ b/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/transactions/TransactionCommitAmbiguousException.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2024 Couchbase, Inc. + * + * Licensed 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 com.couchbase.client.kotlin.transactions + +import com.couchbase.client.core.error.transaction.internal.CoreTransactionCommitAmbiguousException + +/** + * The transaction expired at the point of trying to commit it. + * It is ambiguous whether the transaction has committed or not. + * Actors may be able to see the content of this transaction. + * + * This error is result of inevitable and unavoidable edge cases when working with unreliable networks. + * For example, consider an ordinary mutation being made over the network to any database. + * The mutation could succeed on the database-side, and then just before the result is returned to the client, the network connection drops. + * The client cannot receive the success result and will time out - it is ambiguous to it whether the mutation succeeded or not. + * + * The transactions layer will work to resolve the ambiguity up until the transaction expires, but if unable to resolve + * it in that time, it is forced to raise this error. + * The transaction may or may not have been successful, and error-handling of this is highly application-dependent. + * + * An asynchronous cleanup process will try to complete the transaction: roll it back if it didn't commit, roll it + * forwards if it did. + */ +public class TransactionCommitAmbiguousException(e: CoreTransactionCommitAmbiguousException) : TransactionFailedException(e) diff --git a/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/transactions/TransactionExpiredException.kt b/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/transactions/TransactionExpiredException.kt new file mode 100644 index 000000000..ea3d92950 --- /dev/null +++ b/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/transactions/TransactionExpiredException.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2024 Couchbase, Inc. + * + * Licensed 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 com.couchbase.client.kotlin.transactions + +import com.couchbase.client.core.error.transaction.internal.CoreTransactionExpiredException + +/** + * The transaction could not be fully completed in the configured timeout. + * + * It is in an undefined state, but it unambiguously did not reach the commit point. + * No actors will be able to see the contents of this transaction. + */ +public class TransactionExpiredException(e: CoreTransactionExpiredException) : TransactionFailedException(e) diff --git a/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/transactions/TransactionFailedException.kt b/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/transactions/TransactionFailedException.kt new file mode 100644 index 000000000..e521af8ad --- /dev/null +++ b/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/transactions/TransactionFailedException.kt @@ -0,0 +1,54 @@ +/* + * Copyright 2024 Couchbase, Inc. + * + * Licensed 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 com.couchbase.client.kotlin.transactions + +import com.couchbase.client.core.cnc.events.transaction.TransactionLogEvent +import com.couchbase.client.core.error.CouchbaseException +import com.couchbase.client.core.error.transaction.internal.CoreTransactionCommitAmbiguousException +import com.couchbase.client.core.error.transaction.internal.CoreTransactionExpiredException +import com.couchbase.client.core.error.transaction.internal.CoreTransactionFailedException + +/** + * The transaction failed to reach the Committed point. + * + * No actors can see any changes made by this transaction. + */ +public open class TransactionFailedException( + private val wrapped: CoreTransactionFailedException, +) : CouchbaseException(wrapped.message, wrapped.cause) { + + /** + * Returns the in-memory log built up during each transaction. + * The application may want to write this to their own logs, for example upon transaction failure. + */ + public val logs: List + get() = wrapped.logger().logs() + + public val transactionId: String + get() = wrapped.transactionId() + + internal companion object { + internal fun convertTransactionFailedInternal(err: Throwable) : Throwable { + return when (err) { + is CoreTransactionCommitAmbiguousException -> TransactionCommitAmbiguousException(err) + is CoreTransactionExpiredException -> TransactionExpiredException(err) + is CoreTransactionFailedException -> TransactionFailedException(err) + else -> err + } + } + } +} diff --git a/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/transactions/TransactionGetResult.kt b/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/transactions/TransactionGetResult.kt new file mode 100644 index 000000000..33fe3e98b --- /dev/null +++ b/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/transactions/TransactionGetResult.kt @@ -0,0 +1,59 @@ +/* + * Copyright 2024 Couchbase, Inc. + * + * Licensed 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 com.couchbase.client.kotlin.transactions + +import com.couchbase.client.core.transaction.CoreTransactionGetResult +import com.couchbase.client.kotlin.Keyspace +import com.couchbase.client.kotlin.codec.Content +import com.couchbase.client.kotlin.codec.JsonSerializer +import com.couchbase.client.kotlin.codec.typeRef + +public class TransactionGetResult internal constructor( + internal val internal: CoreTransactionGetResult, + @property:PublishedApi internal val defaultJsonSerializer: JsonSerializer, +) { + /** + * The document's ID. + */ + public val id: String = internal.id() + + /** + * The fully-qualified name of the collection containing the document. + */ + public val keyspace: Keyspace = internal.collection().toKeyspace() + + /** + * The retrieved content. Useful for accessing the raw bytes + * of the document. + */ + public val content: Content = + if (internal.isBinary) Content.binary(internal.contentAsBytes()) + else Content.json(internal.contentAsBytes()) + + /** + * Returns the document content after deserializing it into the type + * specified by the type parameter. + * + * @param jsonSerializer the serializer to use, or null to use the serializer + * configured on the cluster environment. + */ + public inline fun contentAs(jsonSerializer: JsonSerializer? = null): T { + return (jsonSerializer ?: defaultJsonSerializer).deserialize(content.bytes, typeRef()) + } + + override fun toString(): String = internal.toString() +} diff --git a/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/transactions/TransactionResult.kt b/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/transactions/TransactionResult.kt new file mode 100644 index 000000000..0c7c8c080 --- /dev/null +++ b/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/transactions/TransactionResult.kt @@ -0,0 +1,71 @@ +/* + * Copyright 2024 Couchbase, Inc. + * + * Licensed 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 com.couchbase.client.kotlin.transactions + +import com.couchbase.client.core.cnc.events.transaction.TransactionLogEvent +import com.couchbase.client.core.transaction.CoreTransactionResult +import kotlin.time.Duration +import kotlin.time.toKotlinDuration + +/** + * Holds the value returned by the transaction lambda, as well as + * debugging and logging facilities for tracking what happened during the transaction. + * + * Note that the success or failure of a transaction is determined solely by whether it threw a + * [TransactionFailedException]. + * + * @param V The type of value returned by the transaction lambda + * @property value The value returned by the transaction lambda + */ +public class TransactionResult internal constructor( + public val value: V, + internal val internal: CoreTransactionResult, +) { + /** + * The in-memory log built up during each transaction. The application may want to write this to their own logs, + * for example upon transaction failure. + */ + public val logs: List + get() = internal.log().logs() + + /** + * Total time taken by a transaction. + */ + public val timeTaken: Duration + get() = internal.timeTaken().toKotlinDuration() + + /** + * The ID of this transaction. + */ + public val transactionId: String + get() = internal.transactionId() + + /** + * Indicates whether all documents were successfully unstaged (committed). + * + * This will only be true if the transaction reached the COMMIT point and then went on to reach + * the COMPLETE point. + * + * It will be false for transactions that: + * - Rolled back + * - Were read-only + */ + public val unstagingComplete: Boolean + get() = internal.unstagingComplete() + + override fun toString(): String = "TransactionResult(value=$value, internal=$internal)" +} diff --git a/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/transactions/Transactions.kt b/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/transactions/Transactions.kt new file mode 100644 index 000000000..c77d6e057 --- /dev/null +++ b/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/transactions/Transactions.kt @@ -0,0 +1,131 @@ +/* + * Copyright 2024 Couchbase, Inc. + * + * Licensed 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 com.couchbase.client.kotlin.transactions + +import com.couchbase.client.core.Core +import com.couchbase.client.core.cnc.RequestSpan +import com.couchbase.client.core.error.transaction.internal.CoreTransactionFailedException +import com.couchbase.client.core.io.CollectionIdentifier +import com.couchbase.client.core.io.CollectionIdentifier.DEFAULT_COLLECTION +import com.couchbase.client.core.io.CollectionIdentifier.DEFAULT_SCOPE +import com.couchbase.client.core.transaction.CoreTransactionAttemptContext +import com.couchbase.client.core.transaction.CoreTransactionsReactive +import com.couchbase.client.core.transaction.config.CoreTransactionOptions +import com.couchbase.client.core.transaction.support.TransactionAttemptContextFactory +import com.couchbase.client.kotlin.Keyspace +import com.couchbase.client.kotlin.env.env +import com.couchbase.client.kotlin.internal.toOptional +import com.couchbase.client.kotlin.kv.Durability +import com.couchbase.client.kotlin.manager.bucket.levelIfSynchronous +import kotlinx.coroutines.reactive.awaitSingle +import kotlinx.coroutines.reactor.mono +import java.util.Optional +import kotlin.time.Duration +import kotlin.time.toJavaDuration + + +public class Transactions internal constructor(internal val core: Core) { + private val internal = CoreTransactionsReactive(core, core.env.transactionsConfig()) + + /** + * Runs supplied transactional logic until success or failure. + * + * The supplied transactional logic will be run if necessary multiple times, until either: + * + * - The transaction successfully commits + * - The transactional logic requests an explicit rollback + * - The transaction times out. + * - An exception is thrown, either inside the transaction library or by the supplied transaction logic, + * that cannot be handled. + * + * The transaction logic {@link Consumer} is provided an {@link TransactionAttemptContext}, which contains methods allowing it + * to read, mutate, insert and delete documents, as well as commit or rollback the transaction. + * + * If the transaction logic performs a commit or rollback it must be the last operation performed. + * Else a [TransactionFailedException] will be thrown. + * Similarly, there cannot be a commit followed by a rollback, or vice versa - this will also raise a [TransactionFailedException]. + * + * If the transaction logic does not perform an explicit commit or rollback, then a commit will be performed + * anyway. + * + * @return there is no need to check the returned [TransactionResult], as success is implied by the lack of a + * thrown exception. The result contains information useful only for debugging and logging. + * @throws TransactionFailedException or a derived exception if the transaction fails to commit for any reason, possibly + * after multiple retries. The exception contains further details of the error + */ + public suspend fun run( + durability: Durability = Durability.none(), + parentSpan: RequestSpan? = null, + timeout: Duration? = null, + metadataCollection: Keyspace? = null, + transactionLogic: suspend TransactionAttemptContext.() -> V, + ): TransactionResult { + return runInternal( + durability, parentSpan, timeout, metadataCollection, null, transactionLogic, + ) + } + + internal suspend fun runInternal( + durability: Durability = Durability.none(), + parentSpan: RequestSpan? = null, + timeout: Duration? = null, + metadataCollection: Keyspace? = null, + attemptContextFactory: TransactionAttemptContextFactory?, + transactionLogic: suspend TransactionAttemptContext.() -> V, + ): TransactionResult { + + require(durability !is Durability.ClientVerified) { "Client-verified durability is not supported for transactions." } + + val perConfig = CoreTransactionOptions( + durability.levelIfSynchronous(), + Optional.empty(), // scan consistency + parentSpan.toOptional(), + timeout?.toJavaDuration().toOptional(), // TODO or get from txn config + metadataCollection?.toCollectionIdentifier().toOptional(), + attemptContextFactory.toOptional(), + ) + + var value: V? = null + val function = { ctx: CoreTransactionAttemptContext -> + mono { + value = transactionLogic(TransactionAttemptContext(ctx, core.env.jsonSerializer)) + } + } + + try { + val coreResult = internal.run(function, perConfig).awaitSingle() + @Suppress("UNCHECKED_CAST") + return TransactionResult(value as V, coreResult) + } catch (t: CoreTransactionFailedException) { + throw TransactionFailedException.convertTransactionFailedInternal(t) + } + } +} + +internal fun Keyspace.toCollectionIdentifier() = + CollectionIdentifier( + bucket, + Optional.of(scope), + Optional.of(collection) + ) + +internal fun CollectionIdentifier.toKeyspace() = + Keyspace( + bucket(), + scope().orElse(DEFAULT_SCOPE), + collection().orElse(DEFAULT_COLLECTION) + ) From fbc926db13776fb6fd7d1983b6a4b2485240313f Mon Sep 17 00:00:00 2001 From: Aaliya7516 Date: Mon, 30 Sep 2024 18:05:13 +0530 Subject: [PATCH 16/73] JCO-18: Temporary workaround for disableServerCertificateVerification as Java SDK now supports it but gRPC/perf need update Change-Id: Ic952cdedc12d2ea8f6e9205b8b568d290471b478 Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/217024 Tested-by: Build Bot Reviewed-by: Will Broadbelt --- .../couchbase/columnar/cluster/ColumnarClusterConnection.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/cluster/ColumnarClusterConnection.java b/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/cluster/ColumnarClusterConnection.java index 8061d410b..499038a2c 100644 --- a/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/cluster/ColumnarClusterConnection.java +++ b/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/cluster/ColumnarClusterConnection.java @@ -89,7 +89,7 @@ public ColumnarClusterConnection(fit.columnar.ClusterNewInstanceRequest request, } if (secOptions.hasVerifyServerCertificate()) { //noinspection deprecation - sec.verifyServerCertificate(secOptions.getVerifyServerCertificate()); + sec.disableServerCertificateVerification(!secOptions.getVerifyServerCertificate()); } if (secOptions.getCipherSuitesCount() > 0) { sec.cipherSuites(secOptions.getCipherSuitesList()); From bb8a31867507903fb285972233334c7f52729744 Mon Sep 17 00:00:00 2001 From: David Nault Date: Wed, 2 Oct 2024 10:56:42 -0700 Subject: [PATCH 17/73] JCO-19 Support standard connection string parameter names Motivation ---------- Standardize connection string parameter names across Columnar SDKs Modifications ------------- Throw if connection string parameter has uppercase letter. BuilderPropertySetter now applies a function that transforms each path component (for example, from lower_snake_case to lowerCamelCase). Remove the `tls_verify` alias in favor of standardizing on `security.disable_server_certificate_verification`. Change-Id: Id66d5bcae951898915638cfab60aede120f72b69 Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/217176 Tested-by: Build Bot Reviewed-by: David Nault --- .../columnar/client/java/Cluster.java | 54 +++++++++++-------- .../columnar/client/java/sandbox/Sandbox.java | 2 +- .../core/env/BuilderPropertySetter.java | 23 +++++--- 3 files changed, 49 insertions(+), 30 deletions(-) diff --git a/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/Cluster.java b/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/Cluster.java index 1fbe9c652..045b5b07d 100644 --- a/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/Cluster.java +++ b/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/Cluster.java @@ -32,7 +32,6 @@ import java.io.Closeable; import java.time.Duration; import java.util.Collections; -import java.util.LinkedHashMap; import java.util.Map; import java.util.Optional; import java.util.concurrent.atomic.AtomicBoolean; @@ -121,11 +120,13 @@ public static Cluster newInstance( throw new IllegalArgumentException("Invalid connection string; must start with secure scheme \"couchbases://\" (note the final 's') but got: " + redactUser(cs.original())); } + checkParameterNamesAreLowercase(cs); + ClusterOptions builder = new ClusterOptions(); optionsCustomizer.accept(builder); - BuilderPropertySetter propertySetter = new BuilderPropertySetter("", Collections.emptyMap()); - propertySetter.set(builder, translateTlsVerify(cs.params())); + BuilderPropertySetter propertySetter = new BuilderPropertySetter("", Collections.emptyMap(), Cluster::lowerSnakeCaseToLowerCamelCase); + propertySetter.set(builder, cs.params()); // do we really want to allow a system property to disable server certificate verification? //propertySetter.set(builder, systemPropertyMap(SYSTEM_PROPERTY_PREFIX)); @@ -175,27 +176,38 @@ public static Cluster newInstance( return new Cluster(cs, credential.toInternalAuthenticator(), env); } - private static Map translateTlsVerify(Map connectionStringProperties) { - Map properties = new LinkedHashMap<>(connectionStringProperties); - String tlsVerify = properties.remove("tls_verify"); - if (tlsVerify == null) { - return properties; - } + private static void checkParameterNamesAreLowercase(ConnectionString cs) { + cs.params().keySet().stream() + .filter(Cluster::hasUppercase) + .findFirst() + .ifPresent(badName -> { + throw new IllegalArgumentException("Invalid connection string parameter '" + badName + "'. Please use lower_snake_case in connection string parameter names."); + }); + } + + private static boolean hasUppercase(String s) { + return s.codePoints().anyMatch(Character::isUpperCase); + } - String javaName = "security.verifyServerCertificate"; - switch (tlsVerify) { - case "none": - properties.put(javaName, "false"); - break; - case "peer": - properties.put(javaName, "true"); - break; - default: - throw new IllegalArgumentException( - "Unexpected value for connection string parameter 'tls_verify'; expected 'none' or 'peer', but got: '" + tlsVerify + "'"); + private static String lowerSnakeCaseToLowerCamelCase(String s) { + StringBuilder sb = new StringBuilder(); + int[] codePoints = s.codePoints().toArray(); + + boolean prevWasUnderscore = false; + for (int i : codePoints) { + if (i == '_') { + prevWasUnderscore = true; + continue; + } + + if (prevWasUnderscore) { + i = Character.toUpperCase(i); + } + sb.appendCodePoint(i); + prevWasUnderscore = false; } - return properties; + return sb.toString(); } private static CoreTransactionsConfig disableTransactionsCleanup() { diff --git a/columnar-java-client/src/test/java/com/couchbase/columnar/client/java/sandbox/Sandbox.java b/columnar-java-client/src/test/java/com/couchbase/columnar/client/java/sandbox/Sandbox.java index a5463ceea..eca5731c4 100644 --- a/columnar-java-client/src/test/java/com/couchbase/columnar/client/java/sandbox/Sandbox.java +++ b/columnar-java-client/src/test/java/com/couchbase/columnar/client/java/sandbox/Sandbox.java @@ -35,7 +35,7 @@ public class Sandbox { public static void main(String[] args) throws Exception { - String connectionString = "couchbases://127.0.0.1?security.disableServerCertificateVerification=true"; + String connectionString = "couchbases://127.0.0.1?security.disable_server_certificate_verification=true&srv=0"; String username = "Administrator"; String password = "password"; diff --git a/core-io/src/main/java/com/couchbase/client/core/env/BuilderPropertySetter.java b/core-io/src/main/java/com/couchbase/client/core/env/BuilderPropertySetter.java index 81d0f37f3..f3f9bdd0e 100644 --- a/core-io/src/main/java/com/couchbase/client/core/env/BuilderPropertySetter.java +++ b/core-io/src/main/java/com/couchbase/client/core/env/BuilderPropertySetter.java @@ -44,11 +44,11 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import java.util.function.Function; -import java.util.stream.Collectors; import static com.couchbase.client.core.util.CbCollections.mapCopyOf; import static com.couchbase.client.core.util.CbCollections.mapOf; import static java.util.Objects.requireNonNull; +import static java.util.stream.Collectors.toList; @SuppressWarnings("rawtypes") @Stability.Internal @@ -60,16 +60,22 @@ public class BuilderPropertySetter { // Escape hatch in case some accessors don't follow the convention. private final Map irregularChildBuilderAccessors; + // Converts an input path component to match the Java method name, + // for translating case conventions. + private final Function pathComponentTransformer; + public BuilderPropertySetter() { - this("Config", mapOf("ioEnvironment", "ioEnvironment")); + this("Config", mapOf("ioEnvironment", "ioEnvironment"), name -> name); } public BuilderPropertySetter( String childBuilderAccessorSuffix, - Map irregularChildBuilderAccessors + Map irregularChildBuilderAccessors, + Function pathComponentTransformer ) { this.childBuilderAccessorSuffix = requireNonNull(childBuilderAccessorSuffix); this.irregularChildBuilderAccessors = mapCopyOf(irregularChildBuilderAccessors); + this.pathComponentTransformer = requireNonNull(pathComponentTransformer); } /** @@ -83,10 +89,11 @@ public void set(Object builder, Map properties) { * @throws InvalidPropertyException if the property could not be applied to the builder */ public void set(Object builder, String propertyName, String propertyValue) { - - try { - final List propertyComponents = Arrays.asList(propertyName.split("\\.", -1)); + final List propertyComponents = Arrays.stream(propertyName.split("\\.", -1)) + .map(pathComponentTransformer) + .collect(toList()); + final List pathToBuilder = propertyComponents.subList(0, propertyComponents.size() - 1); final String setterName = propertyComponents.get(propertyComponents.size() - 1); @@ -111,7 +118,7 @@ public void set(Object builder, String propertyName, String propertyValue) { final List candidates = Arrays.stream(builder.getClass().getMethods()) .filter(m -> m.getName().equals(setterName)) .filter(m -> m.getParameterCount() == 1) - .collect(Collectors.toList()); + .collect(toList()); if (candidates.isEmpty()) { throw InvalidArgumentException.fromMessage("No one-arg setter for property \"" + propertyName + "\" in " + builder.getClass()); @@ -256,7 +263,7 @@ private static E convertEnum(Class enumClass, String value) try { return (E) Enum.valueOf(enumClass, value); } catch (IllegalArgumentException e) { - List enumValueNames = Arrays.stream(enumClass.getEnumConstants()).map(Enum::name).collect(Collectors.toList()); + List enumValueNames = Arrays.stream(enumClass.getEnumConstants()).map(Enum::name).collect(toList()); throw InvalidArgumentException.fromMessage("Expected one of " + enumValueNames + " but got \"" + value + "\""); } } From 19d0f1b746b845037ddbccf676023216e3c16762 Mon Sep 17 00:00:00 2001 From: Will Broadbelt Date: Thu, 12 Sep 2024 17:32:04 +0100 Subject: [PATCH 18/73] SDKQE-3384: FIT-Columnar - Support error grpc changes Change-Id: I96b3782e917d62eec710f324894cef8929a5ebf6 Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/216125 Reviewed-by: Graham Pople Tested-by: Build Bot --- .../columnar/fit/core/util/ResultUtil.java | 48 ------------------- .../query/QueryResultBufferedStreamer.java | 2 +- .../query/QueryResultPushBasedStreamer.java | 2 +- .../columnar/query/QueryRowUtil.java | 2 +- .../rpc/JavaColumnarCrossService.java | 4 +- .../columnar/rpc/JavaColumnarService.java | 2 +- .../couchbase/columnar}/util/ErrorUtil.java | 47 +++++++++++------- .../couchbase/columnar/util/ResultUtil.java | 34 +++++++++++++ 8 files changed, 71 insertions(+), 70 deletions(-) delete mode 100644 columnar-fit-performer-shared/src/main/java/com/couchbase/columnar/fit/core/util/ResultUtil.java rename {columnar-fit-performer-shared/src/main/java/com/couchbase/columnar/fit/core => columnar-java-fit-performer/src/main/java/com/couchbase/columnar}/util/ErrorUtil.java (51%) create mode 100644 columnar-java-fit-performer/src/main/java/com/couchbase/columnar/util/ResultUtil.java diff --git a/columnar-fit-performer-shared/src/main/java/com/couchbase/columnar/fit/core/util/ResultUtil.java b/columnar-fit-performer-shared/src/main/java/com/couchbase/columnar/fit/core/util/ResultUtil.java deleted file mode 100644 index 5d783b7a1..000000000 --- a/columnar-fit-performer-shared/src/main/java/com/couchbase/columnar/fit/core/util/ResultUtil.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2024 Couchbase, Inc. - * - * Licensed 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 com.couchbase.columnar.fit.core.util; - -import fit.columnar.EmptyResultOrFailureResponse; -import fit.columnar.ResponseMetadata; - -public class ResultUtil { - public static EmptyResultOrFailureResponse success(StartTimes startTime) { - return fit.columnar.EmptyResultOrFailureResponse.newBuilder() - .setEmptySuccess(true) - .setMetadata(responseMetadata(startTime)) - .build(); - } - - public static EmptyResultOrFailureResponse failure(Throwable err, StartTimes startTime) { - return fit.columnar.EmptyResultOrFailureResponse.newBuilder() - .setMetadata(responseMetadata(startTime)) - .setError(ErrorUtil.convertError(err)) - .build(); - } - - public static ResponseMetadata responseMetadata(StartTimes startTime) { - if (startTime != null) { - return fit.columnar.ResponseMetadata.newBuilder() - .setElapsedNanos(System.nanoTime() - startTime.asSystem()) - .setInitiated(startTime.asWallclock()) - .build(); - } - else { - // todo remove when fix timings - return fit.columnar.ResponseMetadata.newBuilder().build(); - } - } -} diff --git a/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/query/QueryResultBufferedStreamer.java b/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/query/QueryResultBufferedStreamer.java index 78d77cdd7..dae3663ae 100644 --- a/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/query/QueryResultBufferedStreamer.java +++ b/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/query/QueryResultBufferedStreamer.java @@ -19,7 +19,7 @@ import com.couchbase.columnar.client.java.QueryResult; import com.couchbase.columnar.client.java.Queryable; import com.couchbase.columnar.client.java.Row; -import com.couchbase.columnar.fit.core.util.ResultUtil; +import com.couchbase.columnar.util.ResultUtil; import fit.columnar.EmptyResultOrFailureResponse; import fit.columnar.ExecuteQueryRequest; import fit.columnar.QueryResultMetadataResponse; diff --git a/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/query/QueryResultPushBasedStreamer.java b/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/query/QueryResultPushBasedStreamer.java index 477f355c3..af0dbbcc1 100644 --- a/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/query/QueryResultPushBasedStreamer.java +++ b/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/query/QueryResultPushBasedStreamer.java @@ -23,7 +23,7 @@ import com.couchbase.columnar.client.java.json.JsonArray; import com.couchbase.columnar.client.java.json.JsonObject; import com.couchbase.columnar.content.ContentAsUtil; -import com.couchbase.columnar.fit.core.util.ErrorUtil; +import com.couchbase.columnar.util.ErrorUtil; import fit.columnar.EmptyResultOrFailureResponse; import fit.columnar.ExecuteQueryRequest; import fit.columnar.QueryResultMetadataResponse; diff --git a/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/query/QueryRowUtil.java b/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/query/QueryRowUtil.java index 9f083c862..f819e5b0f 100644 --- a/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/query/QueryRowUtil.java +++ b/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/query/QueryRowUtil.java @@ -20,7 +20,7 @@ import com.couchbase.columnar.client.java.json.JsonArray; import com.couchbase.columnar.client.java.json.JsonObject; import com.couchbase.columnar.content.ContentAsUtil; -import com.couchbase.columnar.fit.core.util.ErrorUtil; +import com.couchbase.columnar.util.ErrorUtil; import fit.columnar.QueryRowResponse; import javax.annotation.Nullable; diff --git a/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/rpc/JavaColumnarCrossService.java b/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/rpc/JavaColumnarCrossService.java index 1336aa867..87aebf4bf 100644 --- a/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/rpc/JavaColumnarCrossService.java +++ b/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/rpc/JavaColumnarCrossService.java @@ -21,8 +21,8 @@ import com.couchbase.columnar.query.ExecuteQueryStreamer; import com.couchbase.columnar.query.QueryResultBufferedStreamer; import com.couchbase.columnar.query.QueryResultPushBasedStreamer; -import com.couchbase.columnar.fit.core.util.ErrorUtil; -import com.couchbase.columnar.fit.core.util.ResultUtil; +import com.couchbase.columnar.util.ErrorUtil; +import com.couchbase.columnar.util.ResultUtil; import fit.columnar.CloseAllQueryResultsRequest; import fit.columnar.CloseQueryResultRequest; import fit.columnar.ColumnarCrossServiceGrpc; diff --git a/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/rpc/JavaColumnarService.java b/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/rpc/JavaColumnarService.java index 255ee38ba..e934e29e3 100644 --- a/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/rpc/JavaColumnarService.java +++ b/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/rpc/JavaColumnarService.java @@ -21,8 +21,8 @@ import com.couchbase.columnar.cluster.ColumnarClusterConnection; import com.couchbase.columnar.modes.Mode; import com.couchbase.columnar.fit.core.exceptions.ExceptionGrpcMappingUtil; -import com.couchbase.columnar.fit.core.util.ResultUtil; import com.couchbase.columnar.fit.core.util.VersionUtil; +import com.couchbase.columnar.util.ResultUtil; import fit.columnar.CloseAllColumnarClustersRequest; import fit.columnar.ClusterCloseRequest; import fit.columnar.ClusterNewInstanceRequest; diff --git a/columnar-fit-performer-shared/src/main/java/com/couchbase/columnar/fit/core/util/ErrorUtil.java b/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/util/ErrorUtil.java similarity index 51% rename from columnar-fit-performer-shared/src/main/java/com/couchbase/columnar/fit/core/util/ErrorUtil.java rename to columnar-java-fit-performer/src/main/java/com/couchbase/columnar/util/ErrorUtil.java index 4ab993939..166c9a061 100644 --- a/columnar-fit-performer-shared/src/main/java/com/couchbase/columnar/fit/core/util/ErrorUtil.java +++ b/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/util/ErrorUtil.java @@ -13,26 +13,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.couchbase.columnar.fit.core.util; +package com.couchbase.columnar.util; -import fit.columnar.ColumnarErrorType; +import com.couchbase.columnar.client.java.InvalidCredentialException; +import com.couchbase.columnar.client.java.QueryException; +import com.couchbase.columnar.client.java.TimeoutException; import fit.columnar.PlatformErrorType; -import javax.annotation.Nullable; - public class ErrorUtil { private ErrorUtil() { throw new AssertionError("not instantiable"); } - private static @Nullable fit.columnar.ColumnarErrorType convertColumnarError(Throwable exception) { + private static boolean isColumnarError(Throwable exception) { String simpleName = exception.getClass().getSimpleName(); return switch (simpleName) { - case "QueryException" -> ColumnarErrorType.COLUMNAR_EXCEPTION_QUERY; - case "InvalidCredentialException" -> ColumnarErrorType.COLUMNAR_EXCEPTION_INVALID_CREDENTIAL; - case "TimeoutException" -> ColumnarErrorType.COLUMNAR_EXCEPTION_TIMEOUT; - default -> null; + case "QueryException", "InvalidCredentialException", "TimeoutException", "ColumnarException" -> true; + default -> false; }; } @@ -45,12 +43,29 @@ private static fit.columnar.PlatformErrorType convertPlatformError(Throwable exc public static fit.columnar.Error convertError(Throwable raw) { var ret = fit.columnar.Error.newBuilder(); - var type = ErrorUtil.convertColumnarError(raw); - - if (type != null) { + if (isColumnarError(raw)) { var out = fit.columnar.ColumnarError.newBuilder() - .setType(type) - .setAsString(raw.toString()); + .setAsString(raw.toString()); + + if (raw instanceof QueryException queryException) { + out.setSubException(fit.columnar.SubColumnarError.newBuilder().setQueryException( + fit.columnar.QueryException.newBuilder() + .setErrorCode(queryException.code()) + .setServerMessage(queryException.serverMessage()) + .build()) + .build()); + } + if (raw instanceof InvalidCredentialException) { + out.setSubException(fit.columnar.SubColumnarError.newBuilder().setInvalidCredentialException( + fit.columnar.InvalidCredentialException.newBuilder().build()) + .build()); + } + + if (raw instanceof TimeoutException) { + out.setSubException(fit.columnar.SubColumnarError.newBuilder().setTimeoutException(fit.columnar.TimeoutException.newBuilder().build()) + .build()); + } + if (raw.getCause() != null) { out.setCause(convertError(raw.getCause())); } @@ -58,8 +73,8 @@ public static fit.columnar.Error convertError(Throwable raw) { ret.setColumnar(out); } else { ret.setPlatform(fit.columnar.PlatformError.newBuilder() - .setType(convertPlatformError(raw)) - .setAsString(raw.toString())); + .setType(convertPlatformError(raw)) + .setAsString(raw.toString())); } return ret.build(); diff --git a/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/util/ResultUtil.java b/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/util/ResultUtil.java new file mode 100644 index 000000000..0bfbb30fd --- /dev/null +++ b/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/util/ResultUtil.java @@ -0,0 +1,34 @@ +package com.couchbase.columnar.util; + +import com.couchbase.columnar.fit.core.util.StartTimes; +import fit.columnar.EmptyResultOrFailureResponse; +import fit.columnar.ResponseMetadata; + +public class ResultUtil { + public static EmptyResultOrFailureResponse success(StartTimes startTime) { + return fit.columnar.EmptyResultOrFailureResponse.newBuilder() + .setEmptySuccess(true) + .setMetadata(responseMetadata(startTime)) + .build(); + } + + public static EmptyResultOrFailureResponse failure(Throwable err, StartTimes startTime) { + return fit.columnar.EmptyResultOrFailureResponse.newBuilder() + .setMetadata(responseMetadata(startTime)) + .setError(ErrorUtil.convertError(err)) + .build(); + } + + public static ResponseMetadata responseMetadata(StartTimes startTime) { + if (startTime != null) { + return fit.columnar.ResponseMetadata.newBuilder() + .setElapsedNanos(System.nanoTime() - startTime.asSystem()) + .setInitiated(startTime.asWallclock()) + .build(); + } + else { + // todo remove when fix timings + return fit.columnar.ResponseMetadata.newBuilder().build(); + } + } +} From 8c5e4f4da0ab1f7d15b62de09232797163e811fe Mon Sep 17 00:00:00 2001 From: David Nault Date: Thu, 3 Oct 2024 14:59:57 -0700 Subject: [PATCH 19/73] Gardening: Remove dead code in QueryScanConsistency.kt Motivation ---------- Now that Kotlin uses CoreQueryOps (see KCBC-113), the code to serialize the scan consistency as JSON is no longer required. Change-Id: I98d779dd236a8eaf42e93c73a85c8a70a5052ba6 Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/217279 Tested-by: Build Bot Reviewed-by: David Nault --- .../kotlin/query/QueryScanConsistency.kt | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/query/QueryScanConsistency.kt b/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/query/QueryScanConsistency.kt index 559b08aaf..762191a75 100644 --- a/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/query/QueryScanConsistency.kt +++ b/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/query/QueryScanConsistency.kt @@ -17,29 +17,21 @@ package com.couchbase.client.kotlin.query import com.couchbase.client.core.api.shared.CoreMutationState -import com.couchbase.client.core.util.Golang import com.couchbase.client.kotlin.internal.isEmpty import com.couchbase.client.kotlin.kv.MutationState import com.couchbase.client.kotlin.query.QueryScanConsistency.Companion.consistentWith import com.couchbase.client.kotlin.query.QueryScanConsistency.Companion.notBounded import com.couchbase.client.kotlin.query.QueryScanConsistency.Companion.requestPlus import kotlin.time.Duration -import kotlin.time.toJavaDuration /** * Create instances using the [requestPlus], [consistentWith], or [notBounded] * factory methods. */ public sealed class QueryScanConsistency( - private val wireName: String?, internal val scanWait: Duration?, ) { - internal open fun inject(queryJson: MutableMap): Unit { - wireName?.let { queryJson["scan_consistency"] = it } - scanWait?.let { queryJson["scan_wait"] = Golang.encodeDurationToMs(it.toJavaDuration()) } - } - public companion object { /** * For when speed matters more than consistency. Executes the query @@ -70,20 +62,15 @@ public sealed class QueryScanConsistency( if (tokens.isEmpty()) NotBounded else ConsistentWith(tokens, scanWait) } - internal object NotBounded : QueryScanConsistency(null, null) + internal object NotBounded : QueryScanConsistency(null) internal class RequestPlus internal constructor(scanWait: Duration? = null) : - QueryScanConsistency("request_plus", scanWait) + QueryScanConsistency(scanWait) internal class ConsistentWith internal constructor( private val tokens: MutationState, scanWait: Duration? = null, - ) : QueryScanConsistency("at_plus", scanWait) { - override fun inject(queryJson: MutableMap): Unit { - super.inject(queryJson) - queryJson["scan_vectors"] = tokens.export() - } - + ) : QueryScanConsistency(scanWait) { fun toCore(): CoreMutationState = CoreMutationState(tokens) } } From f53918b3a8f9864cd410b1f70531a0ae24f61ad0 Mon Sep 17 00:00:00 2001 From: David Nault Date: Thu, 3 Oct 2024 15:36:15 -0700 Subject: [PATCH 20/73] KCBC-172 Expose transactions config via DSL builder Motivation ---------- Support configuring transaction settings. Change-Id: I6759f78a31d4ac4a09876578e14c5b9801a4da76 Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/217280 Reviewed-by: David Nault Tested-by: David Nault --- .../env/dsl/ClusterEnvironmentDslBuilder.kt | 8 ++ .../TransactionsCleanupConfigDslBuilder.kt | 75 ++++++++++++ .../env/dsl/TransactionsConfigDslBuilder.kt | 113 ++++++++++++++++++ .../client/kotlin/samples/ClusterSamples.kt | 11 +- .../transactions/TransactionAttemptContext.kt | 54 ++++++++- .../kotlin/transactions/Transactions.kt | 27 +++-- 6 files changed, 276 insertions(+), 12 deletions(-) create mode 100644 kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/env/dsl/TransactionsCleanupConfigDslBuilder.kt create mode 100644 kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/env/dsl/TransactionsConfigDslBuilder.kt diff --git a/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/env/dsl/ClusterEnvironmentDslBuilder.kt b/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/env/dsl/ClusterEnvironmentDslBuilder.kt index 8e03115cf..ba0c723b4 100644 --- a/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/env/dsl/ClusterEnvironmentDslBuilder.kt +++ b/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/env/dsl/ClusterEnvironmentDslBuilder.kt @@ -124,6 +124,14 @@ public class ClusterEnvironmentDslBuilder { loggingMeterConfigDslBuilder.initializer() } + private var transactionsConfigDslBuilder = TransactionsConfigDslBuilder() + @VolatileCouchbaseApi + public fun transactions(initializer: TransactionsConfigDslBuilder.() -> Unit) { + transactionsConfigDslBuilder.initializer() + + wrapped.transactionsConfig(transactionsConfigDslBuilder.toCore()) + } + /** * @see CoreEnvironment.Builder.eventBus */ diff --git a/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/env/dsl/TransactionsCleanupConfigDslBuilder.kt b/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/env/dsl/TransactionsCleanupConfigDslBuilder.kt new file mode 100644 index 000000000..48f606c85 --- /dev/null +++ b/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/env/dsl/TransactionsCleanupConfigDslBuilder.kt @@ -0,0 +1,75 @@ +/* + * Copyright 2024 Couchbase, Inc. + * + * Licensed 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 com.couchbase.client.kotlin.env.dsl + +import com.couchbase.client.kotlin.Keyspace +import kotlin.time.Duration +import kotlin.time.Duration.Companion.minutes + +@ClusterEnvironmentDslMarker +public class TransactionsCleanupConfigDslBuilder { + /** + * Controls whether a background thread is created to clean up any transaction attempts made by this client. + * + * Defaults to true. + * + * This should typically be left at the default value. + * + * If false, this client's transactions will only be cleaned up + * by the lost attempts cleanup process, which is by necessity slower. + */ + public var cleanupClientAttempts: Boolean = true + + /** + * Controls whether a background process is created to clean up any 'lost' transaction attempts. + * + * Defaults to true. + * + * This should typically be left at the default value, because cleanup is an essential part of Couchbase + * transactions. + */ + public var cleanupLostAttempts: Boolean = true + + + /** + * Determines how frequently the SDK scans for 'lost' transaction attempts + * when [cleanupLostAttempts] is true. + * + * Defaults to 1 minute. + * + * The default value balances promptness of lost transaction discovery + * against the cost of doing the scan, and is suitable for most environments. + * + * An application that prefers to discover lost transactions sooner may reduce this value + * at the cost of increased resource usage on client and server. + */ + public var cleanupWindow: Duration = 1.minutes + + /** + * The initial set of transaction metadata collections to clean up. + * Has no effect if transaction cleanup is explicitly disabled. + * + * Defaults to an empty set. + * + * Specifying at least one collection has the side effect of starting cleanup immediately, + * instead of deferring cleanup until the first transaction starts. + * + * Note that [TransactionsConfigDslBuilder.metadataCollection] does not need to be specified here, + * since it's always cleaned up (unless transaction cleanup is explicitly disabled). + */ + public var collections: Set = emptySet() +} diff --git a/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/env/dsl/TransactionsConfigDslBuilder.kt b/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/env/dsl/TransactionsConfigDslBuilder.kt new file mode 100644 index 000000000..6f45fe8e8 --- /dev/null +++ b/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/env/dsl/TransactionsConfigDslBuilder.kt @@ -0,0 +1,113 @@ +/* + * Copyright 2024 Couchbase, Inc. + * + * Licensed 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 com.couchbase.client.kotlin.env.dsl + +import com.couchbase.client.core.transaction.atr.ActiveTransactionRecordIds +import com.couchbase.client.core.transaction.config.CoreTransactionsCleanupConfig +import com.couchbase.client.core.transaction.config.CoreTransactionsConfig +import com.couchbase.client.core.transaction.forwards.CoreTransactionsSupportedExtensions +import com.couchbase.client.kotlin.Keyspace +import com.couchbase.client.kotlin.internal.toOptional +import com.couchbase.client.kotlin.kv.Durability +import com.couchbase.client.kotlin.transactions.toCollectionIdentifier +import java.util.Optional +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds +import kotlin.time.toJavaDuration + + +internal fun checkTransactionDurability(value: Durability?) { + require(value !is Durability.ClientVerified) { "Transaction durability must not be ClientVerified." } + require(value !is Durability.None && value !is Durability.Disabled) { "Transaction durability must not be None." } +} + +@ClusterEnvironmentDslMarker +public class TransactionsConfigDslBuilder { + /** + * Default durability level for all writes in transactions. + * + * The initial value is [Durability.majority], meaning a transaction will pause on each write + * until it is available in-memory on a majority of configured replicas. + * + * Must not be [Durability.none] or [Durability.clientVerified], which are not compatible with transactions. + */ + public var durabilityLevel: Durability = Durability.majority() + set(value) { + checkTransactionDurability(value) + field = value + } + + /** + * Default maximum time a transaction can run for. + * + * The initial value is 15 seconds. + * + * After this time, the transaction will abort. Note that this could be mid-commit, in which case the cleanup process + * will complete the transaction asynchronously at a later point. + * + * Applications can increase or decrease this as desired. The trade-off to understand is that documents + * being mutated in a transaction `A` are effectively locked from being updated by other transactions until + * transaction `A` has completed (committed or rolled back). If transaction `A` is unable to complete for whatever + * reason, the document can be locked for this [timeout] time. + */ + public var timeout: Duration = 15.seconds + set(value) { + require(value.inWholeMilliseconds > 0) { "Transaction timeout must be at least 1 millisecond, but got $value" } + field = value + } + + /** + * Default collection for storing transaction metadata documents. + * + * The initial value is null. If null, the documents are stored in the default collection + * of the bucket containing the first mutated document in the transaction. + * + * This collection is automatically added to the set of collections to clean up. + */ + public var metadataCollection: Keyspace? = null + + /** + * Configure options related to transaction cleanup. + */ + private val cleanupConfigDlsBuilder = TransactionsCleanupConfigDslBuilder() + public fun cleanup(initializer: TransactionsCleanupConfigDslBuilder.() -> Unit) { + cleanupConfigDlsBuilder.initializer() + } + + internal fun toCore(): CoreTransactionsConfig { + return CoreTransactionsConfig( + durabilityLevel.toCore().levelIfSynchronous().orElseThrow { NoSuchElementException() }, + timeout.toJavaDuration(), + with(cleanupConfigDlsBuilder) { + CoreTransactionsCleanupConfig( + cleanupLostAttempts, + cleanupClientAttempts, + cleanupWindow.toJavaDuration(), + collections.map { it.toCollectionIdentifier() }.toSet() + ) + }, + null, // txn attempt context factory + null, // cleaner factory + null, // client record factory + ActiveTransactionRecordIds.NUM_ATRS_DEFAULT, + metadataCollection?.toCollectionIdentifier().toOptional(), + Optional.empty(), // For Kotlin, scan consistency is specified on each query request. + CoreTransactionsSupportedExtensions.ALL, + ) + } +} + diff --git a/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/samples/ClusterSamples.kt b/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/samples/ClusterSamples.kt index 01deeea83..9c9feabd8 100644 --- a/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/samples/ClusterSamples.kt +++ b/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/samples/ClusterSamples.kt @@ -23,10 +23,11 @@ import com.couchbase.client.kotlin.Cluster import com.couchbase.client.kotlin.codec.RawJsonTranscoder import com.couchbase.client.kotlin.env.ClusterEnvironment import com.couchbase.client.kotlin.env.dsl.TrustSource +import com.couchbase.client.kotlin.kv.Durability import com.couchbase.client.kotlin.query.execute import kotlinx.coroutines.runBlocking import java.nio.file.Paths -import java.util.* +import java.util.Optional import kotlin.time.Duration.Companion.seconds @Suppress("UNUSED_VARIABLE") @@ -140,6 +141,14 @@ internal fun configureManyThingsUsingDsl() { connectTimeout = 15.seconds } + transactions { + durabilityLevel = Durability.majorityAndPersistToActive() + + cleanup { + cleanupWindow = 30.seconds + } + } + orphanReporter { emitInterval = 20.seconds } diff --git a/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/transactions/TransactionAttemptContext.kt b/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/transactions/TransactionAttemptContext.kt index 27db7945a..c28fb76e5 100644 --- a/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/transactions/TransactionAttemptContext.kt +++ b/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/transactions/TransactionAttemptContext.kt @@ -162,13 +162,65 @@ public class TransactionAttemptContext internal constructor( internal.remove(doc.internal, SpanWrapper(span)).await() } + /** + * Executes a SQL++ query, buffers all result rows in memory, and returns them as a [QueryResult]. + * + * @param statement the SQL++ statement to execute. + * + * @param parameters parameters to the SQL++ statement. + * + * @param scope the query context, or null to execute a cluster-level query. + * + * @param serializer the serializer to use for converting parameters to JSON, + * and the default serializer for parsing [QueryRow] content. + * Defaults to the serializer configured on the cluster environment. + * + * @param consistency the scan consistency level for this query. + * Only [QueryScanConsistency.requestPlus] and [QueryScanConsistency.notBounded] are + * supported in transactions. Note that the default scan consistency for transactional queries is + * [QueryScanConsistency.requestPlus]; this differs from non-transaction queries, + * which default to [QueryScanConsistency.notBounded]. + * + * @param readonly pass true if the SQL++ statement does not modify documents. + * This enables certain optimizations, and ensures a query fails instead of accidentally modifying data. + * + * @param adhoc pass false if this is a commonly used query that should be + * turned into a prepared statement for faster execution. + * + * @param flexIndex pass true to use a full-text index instead of a query index. + * + * @param profile specifies how much profiling information to include in + * the response (access via [QueryMetadata.profile]). Profiling is + * relatively expensive, and can impact the performance of the server + * query engine. Not recommended for use in production, unless you're + * diagnosing a specific issue. Note this is an Enterprise Edition feature. + * On Community Edition the parameter will be accepted, but no profiling + * information returned. + * + * @param scanCap Maximum buffered channel size between the indexer client + * and the query service for index scans. This parameter controls when to use + * scan backfill. Use 0 or a negative number to disable. Smaller values + * reduce GC, while larger values reduce indexer backfill. + * + * @param pipelineBatch Controls the number of items execution operators + * can batch for Fetch from the KV. + * + * @param pipelineCap Maximum number of items each execution operator + * can buffer between various operators. + * + * @param clientContextId an arbitrary string that identifies this query + * for diagnostic purposes. + * + * @param raw an "escape hatch" for passing arbitrary query options that + * aren't otherwise exposed by this method. + */ public suspend fun query( statement: String, parameters: QueryParameters = QueryParameters.None, scope: Scope? = null, serializer: JsonSerializer? = null, - consistency: QueryScanConsistency = QueryScanConsistency.notBounded(), + consistency: QueryScanConsistency = QueryScanConsistency.requestPlus(), readonly: Boolean = false, adhoc: Boolean = true, flexIndex: Boolean = false, diff --git a/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/transactions/Transactions.kt b/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/transactions/Transactions.kt index c77d6e057..f6b0d9158 100644 --- a/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/transactions/Transactions.kt +++ b/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/transactions/Transactions.kt @@ -27,6 +27,7 @@ import com.couchbase.client.core.transaction.CoreTransactionsReactive import com.couchbase.client.core.transaction.config.CoreTransactionOptions import com.couchbase.client.core.transaction.support.TransactionAttemptContextFactory import com.couchbase.client.kotlin.Keyspace +import com.couchbase.client.kotlin.env.dsl.checkTransactionDurability import com.couchbase.client.kotlin.env.env import com.couchbase.client.kotlin.internal.toOptional import com.couchbase.client.kotlin.kv.Durability @@ -62,39 +63,45 @@ public class Transactions internal constructor(internal val core: Core) { * If the transaction logic does not perform an explicit commit or rollback, then a commit will be performed * anyway. * - * @return there is no need to check the returned [TransactionResult], as success is implied by the lack of a - * thrown exception. The result contains information useful only for debugging and logging. + * @param durability Durability level for this transaction, or null to use the cluster environment's default transaction durability. + * Must not be [Durability.none] or [Durability.clientVerified], which are not compatible with transactions. + * + * @param timeout Time allowed for this transaction to complete, or null to use the cluster environment's default timeout duration. + * @param metadataCollection The location in Couchbase to store metadata this transaction, or null to use the cluster environment's default metadata collection. + * + * @return The value returned by the transaction logic, along with diagnostic information. + * * @throws TransactionFailedException or a derived exception if the transaction fails to commit for any reason, possibly * after multiple retries. The exception contains further details of the error */ public suspend fun run( - durability: Durability = Durability.none(), - parentSpan: RequestSpan? = null, timeout: Duration? = null, + parentSpan: RequestSpan? = null, + durability: Durability? = null, metadataCollection: Keyspace? = null, transactionLogic: suspend TransactionAttemptContext.() -> V, ): TransactionResult { return runInternal( - durability, parentSpan, timeout, metadataCollection, null, transactionLogic, + timeout, parentSpan, durability, metadataCollection, null, transactionLogic, ) } internal suspend fun runInternal( - durability: Durability = Durability.none(), - parentSpan: RequestSpan? = null, timeout: Duration? = null, + parentSpan: RequestSpan? = null, + durability: Durability? = null, metadataCollection: Keyspace? = null, attemptContextFactory: TransactionAttemptContextFactory?, transactionLogic: suspend TransactionAttemptContext.() -> V, ): TransactionResult { - require(durability !is Durability.ClientVerified) { "Client-verified durability is not supported for transactions." } + checkTransactionDurability(durability) val perConfig = CoreTransactionOptions( - durability.levelIfSynchronous(), + durability?.levelIfSynchronous() ?: Optional.empty(), Optional.empty(), // scan consistency parentSpan.toOptional(), - timeout?.toJavaDuration().toOptional(), // TODO or get from txn config + timeout?.toJavaDuration().toOptional(), metadataCollection?.toCollectionIdentifier().toOptional(), attemptContextFactory.toOptional(), ) From 66152e00a35085c9195b6bbad7474723494897b7 Mon Sep 17 00:00:00 2001 From: David Nault Date: Fri, 4 Oct 2024 13:27:22 -0700 Subject: [PATCH 21/73] Gardening: Update Columnar example project Include jetbrains annotations so IntelliJ warns about calling unstable / internal API. Remove references to configuring non-prod certificates, because the primary audience is external users. Change-Id: Iddc6ca394dbbf87f95fc35bc083194a75ca6e024 Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/217355 Reviewed-by: David Nault Tested-by: David Nault --- .../examples/maven-project-template/pom.xml | 13 ++++++++++++- .../com/example/couchbase/columnar/Example.java | 8 ++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/columnar-java-client/examples/maven-project-template/pom.xml b/columnar-java-client/examples/maven-project-template/pom.xml index 0122fcf95..63515b319 100644 --- a/columnar-java-client/examples/maven-project-template/pom.xml +++ b/columnar-java-client/examples/maven-project-template/pom.xml @@ -40,6 +40,16 @@ couchbase-columnar-java-client 0.1.0-SNAPSHOT + + + + org.jetbrains + annotations + 24.1.0 + provided + @@ -53,7 +63,8 @@ - + org.apache.maven.plugins maven-checkstyle-plugin diff --git a/columnar-java-client/examples/maven-project-template/src/main/java/com/example/couchbase/columnar/Example.java b/columnar-java-client/examples/maven-project-template/src/main/java/com/example/couchbase/columnar/Example.java index 9e1e56e15..afbb5340c 100644 --- a/columnar-java-client/examples/maven-project-template/src/main/java/com/example/couchbase/columnar/Example.java +++ b/columnar-java-client/examples/maven-project-template/src/main/java/com/example/couchbase/columnar/Example.java @@ -19,8 +19,8 @@ import com.couchbase.columnar.client.java.Cluster; import com.couchbase.columnar.client.java.Credential; import com.couchbase.columnar.client.java.QueryResult; -import com.couchbase.columnar.client.java.internal.Certificates; +import java.time.Duration; import java.util.List; public class Example { @@ -32,10 +32,10 @@ public static void main(String[] args) { try (Cluster cluster = Cluster.newInstance( connectionString, Credential.of(username, password), + // The third parameter is optional. + // This example sets the default query timeout to 2 minutes. clusterOptions -> clusterOptions - // Configure a secure connection to Couchbase internal pre-production cluster. - // (Not required when connecting to a production cluster!) - .security(it -> it.trustOnlyCertificates(Certificates.getNonProdCertificates())) + .timeout(it -> it.queryTimeout(Duration.ofMinutes(2))) )) { // Buffered query. All rows must fit in memory. From ce7da6401e0b4206812e7c36496eb49d60b2ae38 Mon Sep 17 00:00:00 2001 From: David Nault Date: Fri, 4 Oct 2024 16:49:46 -0700 Subject: [PATCH 22/73] Columnar: Support `security.trust_only_non_prod` connection string param Motivation ---------- Provide a standard connection string param that internal Couchbase developers can use with any SDK to connect securely to a non-prod cluster. Change-Id: I3b5abde65502ed0c8dc3d9c1f7dc70bab0f8fdc7 Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/217390 Reviewed-by: David Nault Tested-by: Build Bot --- .../columnar/client/java/Cluster.java | 83 ++++++++++++++----- 1 file changed, 61 insertions(+), 22 deletions(-) diff --git a/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/Cluster.java b/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/Cluster.java index 045b5b07d..0019f22fb 100644 --- a/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/Cluster.java +++ b/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/Cluster.java @@ -21,19 +21,22 @@ import com.couchbase.client.core.env.Authenticator; import com.couchbase.client.core.env.BuilderPropertySetter; import com.couchbase.client.core.env.CoreEnvironment; +import com.couchbase.client.core.env.InvalidPropertyException; import com.couchbase.client.core.transaction.atr.ActiveTransactionRecordIds; import com.couchbase.client.core.transaction.config.CoreTransactionsCleanupConfig; import com.couchbase.client.core.transaction.config.CoreTransactionsConfig; import com.couchbase.client.core.transaction.forwards.CoreTransactionsSupportedExtensions; import com.couchbase.client.core.util.ConnectionString; +import com.couchbase.columnar.client.java.internal.Certificates; import reactor.core.publisher.Mono; import javax.net.ssl.TrustManagerFactory; import java.io.Closeable; import java.time.Duration; import java.util.Collections; -import java.util.Map; +import java.util.LinkedHashMap; import java.util.Optional; +import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; @@ -43,7 +46,6 @@ import static com.couchbase.client.core.transaction.config.CoreTransactionsConfig.DEFAULT_TRANSACTION_TIMEOUT; import static java.util.Collections.emptySet; import static java.util.Objects.requireNonNull; -import static java.util.stream.Collectors.toMap; /** * Create a new instance by calling {@link #newInstance}. @@ -125,11 +127,7 @@ public static Cluster newInstance( ClusterOptions builder = new ClusterOptions(); optionsCustomizer.accept(builder); - BuilderPropertySetter propertySetter = new BuilderPropertySetter("", Collections.emptyMap(), Cluster::lowerSnakeCaseToLowerCamelCase); - propertySetter.set(builder, cs.params()); - - // do we really want to allow a system property to disable server certificate verification? - //propertySetter.set(builder, systemPropertyMap(SYSTEM_PROPERTY_PREFIX)); + applyConnectionStringParameters(builder, cs); ClusterOptions.Unmodifiable opts = builder.build(); @@ -176,6 +174,62 @@ public static Cluster newInstance( return new Cluster(cs, credential.toInternalAuthenticator(), env); } + private static void applyConnectionStringParameters(ClusterOptions builder, ConnectionString cs) { + // Make a mutable copy so we can remove entries that require special handling. + LinkedHashMap params = new LinkedHashMap<>(cs.params()); + + // "security.trust_only_non_prod" is special; it doesn't have a corresponding programmatic + // config option. It's not a secret, but we don't want to confuse external users with a + // security config option they never need to set. + boolean trustOnlyNonProdCertificates = lastTrustParamIsNonProd(params); + + try { + BuilderPropertySetter propertySetter = new BuilderPropertySetter("", Collections.emptyMap(), Cluster::lowerSnakeCaseToLowerCamelCase); + propertySetter.set(builder, params); + + } catch (InvalidPropertyException e) { + // Translate core-io exception (internal API) to platform exception! + throw new IllegalArgumentException(e.getMessage(), e.getCause()); + } + + // Do this last, after any other "trust_only_*" params are validated and applied. + // Otherwise, the earlier params would clobber the config set by this param. + // (There's no compelling use case for including multiple "trust_only_*" params in + // the connection string, but we behave consistently if someone tries it.) + if (trustOnlyNonProdCertificates) { + builder.security(it -> it.trustOnlyCertificates(Certificates.getNonProdCertificates())); + } + } + + /** + * Returns true if the "security.trust_only_non_prod" connection string param is + * present, and no other trust params appear after it (since last one wins). + *

+ * Side effect: Removes that param from the map. + * + * @throws IllegalArgumentException if the param has an invalid value + */ + private static boolean lastTrustParamIsNonProd(LinkedHashMap params) { + final String TRUST_ONLY_NON_PROD_PARAM = "security.trust_only_non_prod"; + + // Last trust param wins, so check whether "trust only non-prod" was last trust param. + boolean trustOnlyNonProdWasLast = params.keySet().stream() + .filter(it -> it.startsWith("security.trust_")) + .reduce((a, b) -> b) // last + .orElse("") + .equals(TRUST_ONLY_NON_PROD_PARAM); + + // Always remove it, so later processing doesn't treat it as unrecognized param. + String trustOnlyNonProdValue = params.remove(TRUST_ONLY_NON_PROD_PARAM); + + // Always validate if present, regardless of whether it was last. + if (trustOnlyNonProdValue != null && !Set.of("", "true", "1").contains(trustOnlyNonProdValue)) { + throw new IllegalArgumentException("Invalid value for connection string property '" + TRUST_ONLY_NON_PROD_PARAM + "'; expected 'true', '1', or empty string, but got: '" + trustOnlyNonProdValue + "'"); + } + + return trustOnlyNonProdWasLast; + } + private static void checkParameterNamesAreLowercase(ConnectionString cs) { cs.params().keySet().stream() .filter(Cluster::hasUppercase) @@ -225,21 +279,6 @@ private static CoreTransactionsConfig disableTransactionsCleanup() { ); } - private static final String SYSTEM_PROPERTY_PREFIX = "com.couchbase.columnar.env."; - - /** - * Returns a map of all system properties whose names start with the given prefix, - * transformed to remove the prefix. - */ - private static Map systemPropertyMap(String prefix) { - return System.getProperties() - .entrySet() - .stream() - .filter(entry -> entry.getKey() instanceof String && entry.getValue() instanceof String) - .filter(entry -> ((String) entry.getKey()).startsWith(prefix)) - .collect(toMap(e -> ((String) e.getKey()).substring(prefix.length()), e -> (String) e.getValue())); - } - /** * @see #newInstance */ From 3d0380cd689ff537c995f50716fe7842280dd25f Mon Sep 17 00:00:00 2001 From: mikereiche Date: Tue, 8 Oct 2024 15:49:27 -0700 Subject: [PATCH 23/73] Prepare for release selene-sr4 3.7.4. Change-Id: Ida506e9623c4af24ea965b485be61fdb5c63b319 Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/217543 Tested-by: Build Bot Reviewed-by: Michael Reiche --- columnar-fit-performer-shared/pom.xml | 2 +- .../examples/maven-project-template/pom.xml | 4 ++-- columnar-java-client/pom.xml | 4 ++-- columnar-java-fit-performer/pom.xml | 2 +- core-fit-performer/pom.xml | 2 +- core-io-deps/pom.xml | 2 +- core-io/pom.xml | 4 ++-- java-client/pom.xml | 4 ++-- java-examples/pom.xml | 4 ++-- java-fit-performer/pom.xml | 2 +- kotlin-client/pom.xml | 4 ++-- kotlin-fit-performer/pom.xml | 2 +- metrics-micrometer/pom.xml | 4 ++-- metrics-opentelemetry/pom.xml | 4 ++-- osgi-feature/pom.xml | 4 ++-- pom.xml | 24 +++++++++---------- scala-client/pom.xml | 4 ++-- scala-fit-performer/pom.xml | 2 +- scala-implicits/pom.xml | 4 ++-- test-utils/pom.xml | 4 ++-- tracing-micrometer-observation/pom.xml | 4 ++-- tracing-opentelemetry-deps/pom.xml | 2 +- tracing-opentelemetry/pom.xml | 6 ++--- tracing-opentracing/pom.xml | 4 ++-- 24 files changed, 51 insertions(+), 51 deletions(-) diff --git a/columnar-fit-performer-shared/pom.xml b/columnar-fit-performer-shared/pom.xml index b8540709d..ad9624ea1 100644 --- a/columnar-fit-performer-shared/pom.xml +++ b/columnar-fit-performer-shared/pom.xml @@ -7,7 +7,7 @@ com.couchbase.client couchbase-jvm-clients - 1.16.4-SNAPSHOT + 1.16.4 columnar-fit-performer-shared diff --git a/columnar-java-client/examples/maven-project-template/pom.xml b/columnar-java-client/examples/maven-project-template/pom.xml index 63515b319..71a789efb 100644 --- a/columnar-java-client/examples/maven-project-template/pom.xml +++ b/columnar-java-client/examples/maven-project-template/pom.xml @@ -6,7 +6,7 @@ com.example couchbase-columnar-java-example - 1.0.0-SNAPSHOT + 1.0.0 Couchbase Columnar Java SDK Project Template Examples project for Couchbase Columnar Java SDK @@ -38,7 +38,7 @@ com.couchbase.client couchbase-columnar-java-client - 0.1.0-SNAPSHOT + 1.0.0 + 1.16.4 couchbase-columnar-java-client - 0.1.0 + 1.0.0 Couchbase Columnar Java SDK diff --git a/columnar-java-fit-performer/pom.xml b/columnar-java-fit-performer/pom.xml index 7e956e77d..a532b634a 100644 --- a/columnar-java-fit-performer/pom.xml +++ b/columnar-java-fit-performer/pom.xml @@ -7,7 +7,7 @@ com.couchbase.client couchbase-jvm-clients - 1.16.4-SNAPSHOT + 1.16.4 columnar-java-fit-performer diff --git a/core-fit-performer/pom.xml b/core-fit-performer/pom.xml index a2ed40ccf..5320214d3 100644 --- a/core-fit-performer/pom.xml +++ b/core-fit-performer/pom.xml @@ -7,7 +7,7 @@ com.couchbase.client couchbase-jvm-clients - 1.16.4-SNAPSHOT + 1.16.4 fit-performer-core diff --git a/core-io-deps/pom.xml b/core-io-deps/pom.xml index 9dd8bd190..8c9853ee3 100644 --- a/core-io-deps/pom.xml +++ b/core-io-deps/pom.xml @@ -6,7 +6,7 @@ com.couchbase.client core-io-deps - 1.7.4-SNAPSHOT + 1.7.4 jar Couchbase JVM Core IO Dependencies diff --git a/core-io/pom.xml b/core-io/pom.xml index 8f2b1bf59..127b1a188 100644 --- a/core-io/pom.xml +++ b/core-io/pom.xml @@ -7,11 +7,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.4-SNAPSHOT + 1.16.4 core-io - 3.7.4-SNAPSHOT + 3.7.4 diff --git a/java-client/pom.xml b/java-client/pom.xml index 76197f8b1..38acd4014 100644 --- a/java-client/pom.xml +++ b/java-client/pom.xml @@ -6,11 +6,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.4-SNAPSHOT + 1.16.4 java-client - 3.7.4-SNAPSHOT + 3.7.4 Couchbase Java SDK diff --git a/java-examples/pom.xml b/java-examples/pom.xml index 639ad7829..d17db1108 100644 --- a/java-examples/pom.xml +++ b/java-examples/pom.xml @@ -6,11 +6,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.4-SNAPSHOT + 1.16.4 java-examples - 1.7.4-SNAPSHOT + 1.7.4 Couchbase Java SDK Examples diff --git a/java-fit-performer/pom.xml b/java-fit-performer/pom.xml index eb0103912..89591a684 100644 --- a/java-fit-performer/pom.xml +++ b/java-fit-performer/pom.xml @@ -7,7 +7,7 @@ com.couchbase.client couchbase-jvm-clients - 1.16.4-SNAPSHOT + 1.16.4 fit-performer-java-sdk diff --git a/kotlin-client/pom.xml b/kotlin-client/pom.xml index 3729fd66e..7d0cf915e 100644 --- a/kotlin-client/pom.xml +++ b/kotlin-client/pom.xml @@ -7,11 +7,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.4-SNAPSHOT + 1.16.4 kotlin-client - 1.4.4-SNAPSHOT + 1.4.4 Couchbase Kotlin SDK The official Couchbase Kotlin SDK diff --git a/kotlin-fit-performer/pom.xml b/kotlin-fit-performer/pom.xml index 07b8657ca..7d971a882 100644 --- a/kotlin-fit-performer/pom.xml +++ b/kotlin-fit-performer/pom.xml @@ -7,7 +7,7 @@ com.couchbase.client couchbase-jvm-clients - 1.16.4-SNAPSHOT + 1.16.4 fit-performer-kotlin diff --git a/metrics-micrometer/pom.xml b/metrics-micrometer/pom.xml index 859d131a3..187a2f26a 100644 --- a/metrics-micrometer/pom.xml +++ b/metrics-micrometer/pom.xml @@ -8,11 +8,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.4-SNAPSHOT + 1.16.4 metrics-micrometer - 0.7.4-SNAPSHOT + 0.7.4 Micrometer Metrics Interoperability Provides interoperability with Micrometer diff --git a/metrics-opentelemetry/pom.xml b/metrics-opentelemetry/pom.xml index 628211140..22e13489f 100644 --- a/metrics-opentelemetry/pom.xml +++ b/metrics-opentelemetry/pom.xml @@ -6,11 +6,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.4-SNAPSHOT + 1.16.4 metrics-opentelemetry - 0.7.4-SNAPSHOT + 0.7.4 OpenTelemetry Metrics Interoperability Provides interoperability with OpenTelemetry Metrics diff --git a/osgi-feature/pom.xml b/osgi-feature/pom.xml index c7ed88c99..396cb3b10 100644 --- a/osgi-feature/pom.xml +++ b/osgi-feature/pom.xml @@ -7,11 +7,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.4-SNAPSHOT + 1.16.4 osgi-feature - 3.7.4-SNAPSHOT + 3.7.4 pom Couchbase Java SDK OSGI Feature diff --git a/pom.xml b/pom.xml index b55e9a959..04e0456b3 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.couchbase.client couchbase-jvm-clients - 1.16.4-SNAPSHOT + 1.16.4 pom Couchbase JVM Client Parent @@ -15,17 +15,17 @@ UTF-8 UTF-8 - 3.7.4-SNAPSHOT - 1.7.4-SNAPSHOT - 3.7.4-SNAPSHOT - 3.7.4-SNAPSHOT - 1.7.4-SNAPSHOT - 1.7.4-SNAPSHOT - 1.4.4-SNAPSHOT - 0.1.0 - 1.5.4-SNAPSHOT - 0.7.4-SNAPSHOT - 1.7.4-SNAPSHOT + 3.7.4 + 1.7.4 + 3.7.4 + 3.7.4 + 1.7.4 + 1.7.4 + 1.4.4 + 1.0.0 + 1.5.4 + 0.7.4 + 1.7.4 5.9.1 3.23.1 diff --git a/scala-client/pom.xml b/scala-client/pom.xml index b8b2ab9cf..a11b27a3a 100644 --- a/scala-client/pom.xml +++ b/scala-client/pom.xml @@ -6,11 +6,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.4-SNAPSHOT + 1.16.4 scala-client_${scala.compat.version} - 1.7.4-SNAPSHOT + 1.7.4 jar Couchbase Scala SDK diff --git a/scala-fit-performer/pom.xml b/scala-fit-performer/pom.xml index 356ce37af..d13079f0e 100644 --- a/scala-fit-performer/pom.xml +++ b/scala-fit-performer/pom.xml @@ -5,7 +5,7 @@ com.couchbase.client couchbase-jvm-clients - 1.16.4-SNAPSHOT + 1.16.4 fit-performer-scala diff --git a/scala-implicits/pom.xml b/scala-implicits/pom.xml index d6eab3581..9173e02f6 100644 --- a/scala-implicits/pom.xml +++ b/scala-implicits/pom.xml @@ -7,11 +7,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.4-SNAPSHOT + 1.16.4 scala-implicits_${scala.compat.version} - 1.7.4-SNAPSHOT + 1.7.4 jar Couchbase Scala SDK Implicits diff --git a/test-utils/pom.xml b/test-utils/pom.xml index e354555b0..1ad57a81d 100644 --- a/test-utils/pom.xml +++ b/test-utils/pom.xml @@ -6,12 +6,12 @@ com.couchbase.client couchbase-jvm-clients - 1.16.4-SNAPSHOT + 1.16.4 Couchbase (Integration) Test Utilities test-utils - 1.7.4-SNAPSHOT + 1.7.4 diff --git a/tracing-micrometer-observation/pom.xml b/tracing-micrometer-observation/pom.xml index 926039e90..e7c5b21d6 100644 --- a/tracing-micrometer-observation/pom.xml +++ b/tracing-micrometer-observation/pom.xml @@ -6,11 +6,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.4-SNAPSHOT + 1.16.4 tracing-micrometer-observation - 1.5.4-SNAPSHOT + 1.5.4 Micrometer Observation Interoperability Provides interoperability with Micrometer Observation diff --git a/tracing-opentelemetry-deps/pom.xml b/tracing-opentelemetry-deps/pom.xml index fefe0d918..76e25dd48 100644 --- a/tracing-opentelemetry-deps/pom.xml +++ b/tracing-opentelemetry-deps/pom.xml @@ -9,7 +9,7 @@ com.couchbase.client tracing-opentelemetry-deps - 1.5.4-SNAPSHOT + 1.5.4 jar OpenTelemetry Interoperability Dependencies diff --git a/tracing-opentelemetry/pom.xml b/tracing-opentelemetry/pom.xml index abad3488f..467707975 100644 --- a/tracing-opentelemetry/pom.xml +++ b/tracing-opentelemetry/pom.xml @@ -6,11 +6,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.4-SNAPSHOT + 1.16.4 tracing-opentelemetry - 1.5.4-SNAPSHOT + 1.5.4 OpenTelemetry Interoperability Provides interoperability with OpenTelemetry @@ -24,7 +24,7 @@ com.couchbase.client tracing-opentelemetry-deps - 1.5.4-SNAPSHOT + 1.5.4 diff --git a/tracing-opentracing/pom.xml b/tracing-opentracing/pom.xml index fb41f7dc2..1ef2bcb3c 100644 --- a/tracing-opentracing/pom.xml +++ b/tracing-opentracing/pom.xml @@ -6,11 +6,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.4-SNAPSHOT + 1.16.4 tracing-opentracing - 1.5.4-SNAPSHOT + 1.5.4 OpenTracing Interoperability Provides interoperability with OpenTracing From 56803324e3c7173d28062699816bc4f4d2177556 Mon Sep 17 00:00:00 2001 From: mikereiche Date: Tue, 8 Oct 2024 21:00:01 -0700 Subject: [PATCH 24/73] Prepare for selene-sr5 3.7.5 development. Change-Id: I503bf8bd5d355a466ec411b1425918b7467b916d Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/217550 Tested-by: Build Bot Reviewed-by: Michael Reiche --- columnar-fit-performer-shared/pom.xml | 2 +- .../examples/maven-project-template/pom.xml | 4 ++-- columnar-java-client/pom.xml | 4 ++-- columnar-java-fit-performer/pom.xml | 2 +- core-fit-performer/pom.xml | 2 +- core-io-deps/pom.xml | 2 +- core-io/pom.xml | 4 ++-- java-client/pom.xml | 4 ++-- java-examples/pom.xml | 4 ++-- java-fit-performer/pom.xml | 2 +- kotlin-client/pom.xml | 4 ++-- kotlin-fit-performer/pom.xml | 2 +- metrics-micrometer/pom.xml | 4 ++-- metrics-opentelemetry/pom.xml | 4 ++-- osgi-feature/pom.xml | 4 ++-- pom.xml | 24 +++++++++---------- scala-client/pom.xml | 4 ++-- scala-fit-performer/pom.xml | 2 +- scala-implicits/pom.xml | 4 ++-- test-utils/pom.xml | 4 ++-- tracing-micrometer-observation/pom.xml | 4 ++-- tracing-opentelemetry-deps/pom.xml | 2 +- tracing-opentelemetry/pom.xml | 6 ++--- tracing-opentracing/pom.xml | 4 ++-- 24 files changed, 51 insertions(+), 51 deletions(-) diff --git a/columnar-fit-performer-shared/pom.xml b/columnar-fit-performer-shared/pom.xml index ad9624ea1..6a943677c 100644 --- a/columnar-fit-performer-shared/pom.xml +++ b/columnar-fit-performer-shared/pom.xml @@ -7,7 +7,7 @@ com.couchbase.client couchbase-jvm-clients - 1.16.4 + 1.16.5-SNAPSHOT columnar-fit-performer-shared diff --git a/columnar-java-client/examples/maven-project-template/pom.xml b/columnar-java-client/examples/maven-project-template/pom.xml index 71a789efb..826e5e4c4 100644 --- a/columnar-java-client/examples/maven-project-template/pom.xml +++ b/columnar-java-client/examples/maven-project-template/pom.xml @@ -6,7 +6,7 @@ com.example couchbase-columnar-java-example - 1.0.0 + 1.0.1-SNAPSHOT Couchbase Columnar Java SDK Project Template Examples project for Couchbase Columnar Java SDK @@ -38,7 +38,7 @@ com.couchbase.client couchbase-columnar-java-client - 1.0.0 + 1.0.1-SNAPSHOT + 1.16.5-SNAPSHOT couchbase-columnar-java-client - 1.0.0 + 1.0.1-SNAPSHOT Couchbase Columnar Java SDK diff --git a/columnar-java-fit-performer/pom.xml b/columnar-java-fit-performer/pom.xml index a532b634a..18558c790 100644 --- a/columnar-java-fit-performer/pom.xml +++ b/columnar-java-fit-performer/pom.xml @@ -7,7 +7,7 @@ com.couchbase.client couchbase-jvm-clients - 1.16.4 + 1.16.5-SNAPSHOT columnar-java-fit-performer diff --git a/core-fit-performer/pom.xml b/core-fit-performer/pom.xml index 5320214d3..ef2ac0d14 100644 --- a/core-fit-performer/pom.xml +++ b/core-fit-performer/pom.xml @@ -7,7 +7,7 @@ com.couchbase.client couchbase-jvm-clients - 1.16.4 + 1.16.5-SNAPSHOT fit-performer-core diff --git a/core-io-deps/pom.xml b/core-io-deps/pom.xml index 8c9853ee3..a070ec650 100644 --- a/core-io-deps/pom.xml +++ b/core-io-deps/pom.xml @@ -6,7 +6,7 @@ com.couchbase.client core-io-deps - 1.7.4 + 1.7.5-SNAPSHOT jar Couchbase JVM Core IO Dependencies diff --git a/core-io/pom.xml b/core-io/pom.xml index 127b1a188..204f95309 100644 --- a/core-io/pom.xml +++ b/core-io/pom.xml @@ -7,11 +7,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.4 + 1.16.5-SNAPSHOT core-io - 3.7.4 + 3.7.5-SNAPSHOT diff --git a/java-client/pom.xml b/java-client/pom.xml index 38acd4014..64e5da325 100644 --- a/java-client/pom.xml +++ b/java-client/pom.xml @@ -6,11 +6,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.4 + 1.16.5-SNAPSHOT java-client - 3.7.4 + 3.7.5-SNAPSHOT Couchbase Java SDK diff --git a/java-examples/pom.xml b/java-examples/pom.xml index d17db1108..51db1e695 100644 --- a/java-examples/pom.xml +++ b/java-examples/pom.xml @@ -6,11 +6,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.4 + 1.16.5-SNAPSHOT java-examples - 1.7.4 + 1.7.5-SNAPSHOT Couchbase Java SDK Examples diff --git a/java-fit-performer/pom.xml b/java-fit-performer/pom.xml index 89591a684..050ac698e 100644 --- a/java-fit-performer/pom.xml +++ b/java-fit-performer/pom.xml @@ -7,7 +7,7 @@ com.couchbase.client couchbase-jvm-clients - 1.16.4 + 1.16.5-SNAPSHOT fit-performer-java-sdk diff --git a/kotlin-client/pom.xml b/kotlin-client/pom.xml index 7d0cf915e..e137bcd2d 100644 --- a/kotlin-client/pom.xml +++ b/kotlin-client/pom.xml @@ -7,11 +7,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.4 + 1.16.5-SNAPSHOT kotlin-client - 1.4.4 + 1.4.5-SNAPSHOT Couchbase Kotlin SDK The official Couchbase Kotlin SDK diff --git a/kotlin-fit-performer/pom.xml b/kotlin-fit-performer/pom.xml index 7d971a882..682010d30 100644 --- a/kotlin-fit-performer/pom.xml +++ b/kotlin-fit-performer/pom.xml @@ -7,7 +7,7 @@ com.couchbase.client couchbase-jvm-clients - 1.16.4 + 1.16.5-SNAPSHOT fit-performer-kotlin diff --git a/metrics-micrometer/pom.xml b/metrics-micrometer/pom.xml index 187a2f26a..4655e2476 100644 --- a/metrics-micrometer/pom.xml +++ b/metrics-micrometer/pom.xml @@ -8,11 +8,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.4 + 1.16.5-SNAPSHOT metrics-micrometer - 0.7.4 + 0.7.5-SNAPSHOT Micrometer Metrics Interoperability Provides interoperability with Micrometer diff --git a/metrics-opentelemetry/pom.xml b/metrics-opentelemetry/pom.xml index 22e13489f..1d8d332c1 100644 --- a/metrics-opentelemetry/pom.xml +++ b/metrics-opentelemetry/pom.xml @@ -6,11 +6,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.4 + 1.16.5-SNAPSHOT metrics-opentelemetry - 0.7.4 + 0.7.5-SNAPSHOT OpenTelemetry Metrics Interoperability Provides interoperability with OpenTelemetry Metrics diff --git a/osgi-feature/pom.xml b/osgi-feature/pom.xml index 396cb3b10..f9bebc39b 100644 --- a/osgi-feature/pom.xml +++ b/osgi-feature/pom.xml @@ -7,11 +7,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.4 + 1.16.5-SNAPSHOT osgi-feature - 3.7.4 + 3.7.5-SNAPSHOT pom Couchbase Java SDK OSGI Feature diff --git a/pom.xml b/pom.xml index 04e0456b3..22a7df2d2 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.couchbase.client couchbase-jvm-clients - 1.16.4 + 1.16.5-SNAPSHOT pom Couchbase JVM Client Parent @@ -15,17 +15,17 @@ UTF-8 UTF-8 - 3.7.4 - 1.7.4 - 3.7.4 - 3.7.4 - 1.7.4 - 1.7.4 - 1.4.4 - 1.0.0 - 1.5.4 - 0.7.4 - 1.7.4 + 3.7.5-SNAPSHOT + 1.7.5-SNAPSHOT + 3.7.5-SNAPSHOT + 3.7.5-SNAPSHOT + 1.7.5-SNAPSHOT + 1.7.5-SNAPSHOT + 1.4.5-SNAPSHOT + 1.0.1-SNAPSHOT + 1.5.5-SNAPSHOT + 0.7.5-SNAPSHOT + 1.7.5-SNAPSHOT 5.9.1 3.23.1 diff --git a/scala-client/pom.xml b/scala-client/pom.xml index a11b27a3a..381ddd5a2 100644 --- a/scala-client/pom.xml +++ b/scala-client/pom.xml @@ -6,11 +6,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.4 + 1.16.5-SNAPSHOT scala-client_${scala.compat.version} - 1.7.4 + 1.7.5-SNAPSHOT jar Couchbase Scala SDK diff --git a/scala-fit-performer/pom.xml b/scala-fit-performer/pom.xml index d13079f0e..9330ba283 100644 --- a/scala-fit-performer/pom.xml +++ b/scala-fit-performer/pom.xml @@ -5,7 +5,7 @@ com.couchbase.client couchbase-jvm-clients - 1.16.4 + 1.16.5-SNAPSHOT fit-performer-scala diff --git a/scala-implicits/pom.xml b/scala-implicits/pom.xml index 9173e02f6..05e394412 100644 --- a/scala-implicits/pom.xml +++ b/scala-implicits/pom.xml @@ -7,11 +7,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.4 + 1.16.5-SNAPSHOT scala-implicits_${scala.compat.version} - 1.7.4 + 1.7.5-SNAPSHOT jar Couchbase Scala SDK Implicits diff --git a/test-utils/pom.xml b/test-utils/pom.xml index 1ad57a81d..09c7f9b3e 100644 --- a/test-utils/pom.xml +++ b/test-utils/pom.xml @@ -6,12 +6,12 @@ com.couchbase.client couchbase-jvm-clients - 1.16.4 + 1.16.5-SNAPSHOT Couchbase (Integration) Test Utilities test-utils - 1.7.4 + 1.7.5-SNAPSHOT diff --git a/tracing-micrometer-observation/pom.xml b/tracing-micrometer-observation/pom.xml index e7c5b21d6..b7b356264 100644 --- a/tracing-micrometer-observation/pom.xml +++ b/tracing-micrometer-observation/pom.xml @@ -6,11 +6,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.4 + 1.16.5-SNAPSHOT tracing-micrometer-observation - 1.5.4 + 1.5.5-SNAPSHOT Micrometer Observation Interoperability Provides interoperability with Micrometer Observation diff --git a/tracing-opentelemetry-deps/pom.xml b/tracing-opentelemetry-deps/pom.xml index 76e25dd48..41f6dad2a 100644 --- a/tracing-opentelemetry-deps/pom.xml +++ b/tracing-opentelemetry-deps/pom.xml @@ -9,7 +9,7 @@ com.couchbase.client tracing-opentelemetry-deps - 1.5.4 + 1.5.5-SNAPSHOT jar OpenTelemetry Interoperability Dependencies diff --git a/tracing-opentelemetry/pom.xml b/tracing-opentelemetry/pom.xml index 467707975..95f2a0a9d 100644 --- a/tracing-opentelemetry/pom.xml +++ b/tracing-opentelemetry/pom.xml @@ -6,11 +6,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.4 + 1.16.5-SNAPSHOT tracing-opentelemetry - 1.5.4 + 1.5.5-SNAPSHOT OpenTelemetry Interoperability Provides interoperability with OpenTelemetry @@ -24,7 +24,7 @@ com.couchbase.client tracing-opentelemetry-deps - 1.5.4 + 1.5.5-SNAPSHOT diff --git a/tracing-opentracing/pom.xml b/tracing-opentracing/pom.xml index 1ef2bcb3c..5dc6b6515 100644 --- a/tracing-opentracing/pom.xml +++ b/tracing-opentracing/pom.xml @@ -6,11 +6,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.4 + 1.16.5-SNAPSHOT tracing-opentracing - 1.5.4 + 1.5.5-SNAPSHOT OpenTracing Interoperability Provides interoperability with OpenTracing From f05c1aeef82b1c3e61995b078858e2f7a6a91415 Mon Sep 17 00:00:00 2001 From: mikereiche Date: Thu, 10 Oct 2024 13:48:31 -0700 Subject: [PATCH 25/73] JCO-21 - Change patch version to 5 (1.0.5) to align with patch version of other JVM modules. Also in columnar example, reference the previous released version of the columnar SDK. Change-Id: Iac25163d061c19e9446020cd82e8fd4709c4836b Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/217710 Reviewed-by: David Nault Tested-by: Build Bot --- columnar-java-client/examples/maven-project-template/pom.xml | 4 ++-- columnar-java-client/pom.xml | 2 +- pom.xml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/columnar-java-client/examples/maven-project-template/pom.xml b/columnar-java-client/examples/maven-project-template/pom.xml index 826e5e4c4..74d803245 100644 --- a/columnar-java-client/examples/maven-project-template/pom.xml +++ b/columnar-java-client/examples/maven-project-template/pom.xml @@ -6,7 +6,7 @@ com.example couchbase-columnar-java-example - 1.0.1-SNAPSHOT + 1.0.5-SNAPSHOT Couchbase Columnar Java SDK Project Template Examples project for Couchbase Columnar Java SDK @@ -38,7 +38,7 @@ com.couchbase.client couchbase-columnar-java-client - 1.0.1-SNAPSHOT + 1.0.0 + 1.0.5-SNAPSHOT Couchbase Columnar Java SDK diff --git a/pom.xml b/pom.xml index 22a7df2d2..decb38a1d 100644 --- a/pom.xml +++ b/pom.xml @@ -22,7 +22,7 @@ 1.7.5-SNAPSHOT 1.7.5-SNAPSHOT 1.4.5-SNAPSHOT - 1.0.1-SNAPSHOT + 1.0.5-SNAPSHOT 1.5.5-SNAPSHOT 0.7.5-SNAPSHOT 1.7.5-SNAPSHOT From f1be14bf36cb70af655b97c24e119f86a9bcfd62 Mon Sep 17 00:00:00 2001 From: David Nault Date: Thu, 10 Oct 2024 15:52:56 -0700 Subject: [PATCH 26/73] Gardening: Improve Columnar Javadoc Change-Id: I6573ca1f573467cd0770fa14e3c9bee04b26231e Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/217713 Tested-by: Build Bot Reviewed-by: David Nault --- .../columnar/client/java/Cluster.java | 31 +++++++++++++ .../columnar/client/java/ClusterOptions.java | 5 +++ .../columnar/client/java/Credential.java | 2 + .../columnar/client/java/Database.java | 6 +++ .../columnar/client/java/QueryMetadata.java | 2 + .../columnar/client/java/QueryMetrics.java | 2 + .../columnar/client/java/QueryResult.java | 2 + .../columnar/client/java/QueryWarning.java | 2 + .../columnar/client/java/Queryable.java | 3 ++ .../couchbase/columnar/client/java/Row.java | 4 ++ .../couchbase/columnar/client/java/Scope.java | 2 + .../client/java/codec/Deserializer.java | 2 + .../java/codec/JacksonDeserializer.java | 7 +++ .../columnar/client/java/codec/TypeRef.java | 3 ++ .../client/java/codec/package-info.java | 4 ++ .../client/java/internal/ThreadSafe.java | 43 +++++++++++++++++++ .../columnar/client/java/package-info.java | 3 ++ 17 files changed, 123 insertions(+) create mode 100644 columnar-java-client/src/main/java/com/couchbase/columnar/client/java/internal/ThreadSafe.java diff --git a/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/Cluster.java b/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/Cluster.java index 0019f22fb..2c40c11ee 100644 --- a/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/Cluster.java +++ b/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/Cluster.java @@ -28,6 +28,7 @@ import com.couchbase.client.core.transaction.forwards.CoreTransactionsSupportedExtensions; import com.couchbase.client.core.util.ConnectionString; import com.couchbase.columnar.client.java.internal.Certificates; +import com.couchbase.columnar.client.java.internal.ThreadSafe; import reactor.core.publisher.Mono; import javax.net.ssl.TrustManagerFactory; @@ -67,7 +68,13 @@ * .deserializer(new JacksonDeserializer(new ObjectMapper())) * ); * + * For best efficiency, create a single `Cluster` instance + * per Columnar cluster and share it throughout your application. + *

+ * When you're done interacting with the cluster, it's important to call + * {@link Cluster#close()} to release resources used by the cluster. */ +@ThreadSafe public final class Cluster implements Closeable, Queryable { private final Environment environment; private final CoreCouchbaseOps couchbaseOps; @@ -107,9 +114,21 @@ public static Cluster newInstance( /** * Returns a new instance, with options customized by the {@code optionsCustomizer} callback. + *

+ * Example usage: + *

+   * Cluster cluster = Cluster.newInstance(
+   *     connectionString,
+   *     Credential.of(username, password),
+   *     options -> options
+   *         .timeout(it -> it.queryTimeout(Duration.ofMinutes(5)))
+   *         .deserializer(new JacksonDeserializer(new ObjectMapper()))
+   * );
+   * 
* * @see Credential#of(String, String) * @see #newInstance(String, Credential) + * @see ClusterOptions */ public static Cluster newInstance( String connectionString, @@ -296,6 +315,9 @@ private Cluster( this.queryExecutor = new QueryExecutor(core, environment, connectionString); } + /** + * Releases resources and prevents further use of this object. + */ public void close() { Duration timeout = environment.timeoutConfig().disconnectTimeout(); disconnectInternal(disconnected, timeout, couchbaseOps, environment).block(); @@ -312,6 +334,15 @@ static Mono disconnectInternal( .then(Mono.fromRunnable(() -> disconnected.set(true))); } + /** + * Returns the database in this cluster with the given name. + *

+ * A database is a container for {@link Scope}s. + *

+ * If the database does not exist, this method still returns a + * non-null object, but operations using that object fail with + * an exception indicating the database does not exist. + */ public Database database(String name) { return new Database(this, name); } diff --git a/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/ClusterOptions.java b/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/ClusterOptions.java index d6bbc7fd2..d4e3df530 100644 --- a/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/ClusterOptions.java +++ b/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/ClusterOptions.java @@ -22,6 +22,11 @@ import java.util.function.Consumer; +/** + * A mutable builder for configuring the cluster's behavior. + * + * @see Cluster#newInstance(String, Credential, Consumer) + */ public final class ClusterOptions { boolean srv = true; @Nullable Deserializer deserializer; diff --git a/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/Credential.java b/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/Credential.java index 7be3b46a0..bf7400451 100644 --- a/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/Credential.java +++ b/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/Credential.java @@ -20,6 +20,7 @@ import com.couchbase.client.core.env.Authenticator; import com.couchbase.client.core.env.PasswordAuthenticator; import com.couchbase.columnar.client.java.internal.DynamicAuthenticator; +import com.couchbase.columnar.client.java.internal.ThreadSafe; import java.util.function.Supplier; @@ -34,6 +35,7 @@ * For advanced use cases involving dynamic credentials, see * {@link Credential#ofDynamic(Supplier)}. */ +@ThreadSafe public abstract class Credential { /** diff --git a/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/Database.java b/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/Database.java index 12dad16e7..c108ce2e5 100644 --- a/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/Database.java +++ b/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/Database.java @@ -16,8 +16,14 @@ package com.couchbase.columnar.client.java; +import com.couchbase.columnar.client.java.internal.ThreadSafe; + import static java.util.Objects.requireNonNull; +/** + * Contains {@link Scope}s. + */ +@ThreadSafe public final class Database { private final Cluster cluster; private final String name; diff --git a/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/QueryMetadata.java b/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/QueryMetadata.java index cd387032a..2e88b172c 100644 --- a/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/QueryMetadata.java +++ b/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/QueryMetadata.java @@ -19,6 +19,7 @@ import com.couchbase.client.core.error.ErrorCodeAndMessage; import com.couchbase.client.core.msg.analytics.AnalyticsChunkHeader; import com.couchbase.client.core.msg.analytics.AnalyticsChunkTrailer; +import com.couchbase.columnar.client.java.internal.ThreadSafe; import java.util.Collections; import java.util.List; @@ -27,6 +28,7 @@ /** * Holds associated metadata returned by the server. */ +@ThreadSafe public final class QueryMetadata { private final AnalyticsChunkHeader header; diff --git a/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/QueryMetrics.java b/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/QueryMetrics.java index dbd853a41..2e5ac1853 100644 --- a/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/QueryMetrics.java +++ b/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/QueryMetrics.java @@ -21,6 +21,7 @@ import com.couchbase.client.core.json.Mapper; import com.couchbase.client.core.util.Golang; import com.couchbase.columnar.client.java.internal.JacksonTransformers; +import com.couchbase.columnar.client.java.internal.ThreadSafe; import java.io.IOException; import java.time.Duration; @@ -31,6 +32,7 @@ /** * Holds the metrics as returned from an analytics response. */ +@ThreadSafe public final class QueryMetrics { /** diff --git a/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/QueryResult.java b/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/QueryResult.java index 5b6826366..8d52d4f57 100644 --- a/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/QueryResult.java +++ b/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/QueryResult.java @@ -18,11 +18,13 @@ import com.couchbase.client.core.msg.analytics.AnalyticsChunkHeader; import com.couchbase.client.core.msg.analytics.AnalyticsChunkTrailer; +import com.couchbase.columnar.client.java.internal.ThreadSafe; import java.util.List; import static com.couchbase.client.core.util.CbCollections.listCopyOf; +@ThreadSafe(caveat = "Unless you modify the byte array returned by Row.bytes()") public final class QueryResult { private final List rows; private final QueryMetadata metadata; diff --git a/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/QueryWarning.java b/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/QueryWarning.java index 0a7d94f15..ae3f9fe0c 100644 --- a/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/QueryWarning.java +++ b/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/QueryWarning.java @@ -17,6 +17,7 @@ package com.couchbase.columnar.client.java; import com.couchbase.client.core.error.ErrorCodeAndMessage; +import com.couchbase.columnar.client.java.internal.ThreadSafe; import static java.util.Objects.requireNonNull; @@ -25,6 +26,7 @@ *

* Note that warnings are not terminal errors, but hints from the engine that something went not as expected. */ +@ThreadSafe public final class QueryWarning { private final ErrorCodeAndMessage inner; diff --git a/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/Queryable.java b/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/Queryable.java index ebb1e85f9..d4b478b30 100644 --- a/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/Queryable.java +++ b/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/Queryable.java @@ -16,9 +16,12 @@ package com.couchbase.columnar.client.java; +import com.couchbase.columnar.client.java.internal.ThreadSafe; + import java.util.concurrent.CancellationException; import java.util.function.Consumer; +@ThreadSafe public interface Queryable { /** diff --git a/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/Row.java b/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/Row.java index 3ed7620b6..6e43ec2ec 100644 --- a/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/Row.java +++ b/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/Row.java @@ -19,6 +19,7 @@ import com.couchbase.columnar.client.java.codec.Deserializer; import com.couchbase.columnar.client.java.codec.TypeRef; import com.couchbase.columnar.client.java.internal.InternalJacksonSerDes; +import com.couchbase.columnar.client.java.internal.ThreadSafe; import com.couchbase.columnar.client.java.json.JsonArray; import com.couchbase.columnar.client.java.json.JsonObject; import com.couchbase.columnar.client.java.json.JsonValue; @@ -61,6 +62,7 @@ * @see ClusterOptions#deserializer(Deserializer) * @see QueryOptions#deserializer(Deserializer) */ +@ThreadSafe(caveat = "Unless you modify the byte array returned by Row.bytes()") public final class Row { private final byte[] content; private final Deserializer deserializer; @@ -76,6 +78,8 @@ public final class Row { /** * Returns the raw content of the row, exactly as it was received from * the server. + *

+ * This method returns the same array each time it is called. */ public byte[] bytes() { return content; diff --git a/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/Scope.java b/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/Scope.java index 34c8cd05f..cf481004b 100644 --- a/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/Scope.java +++ b/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/Scope.java @@ -18,11 +18,13 @@ import com.couchbase.client.core.api.manager.CoreBucketAndScope; +import com.couchbase.columnar.client.java.internal.ThreadSafe; import java.util.function.Consumer; import static java.util.Objects.requireNonNull; +@ThreadSafe public final class Scope implements Queryable { private final Cluster cluster; private final Database database; diff --git a/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/codec/Deserializer.java b/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/codec/Deserializer.java index a047b787f..a879875b4 100644 --- a/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/codec/Deserializer.java +++ b/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/codec/Deserializer.java @@ -16,6 +16,7 @@ package com.couchbase.columnar.client.java.codec; +import com.couchbase.columnar.client.java.internal.ThreadSafe; import reactor.util.annotation.Nullable; import java.io.IOException; @@ -23,6 +24,7 @@ /** * Converts query result rows into Java objects. */ +@ThreadSafe public interface Deserializer { /** * Deserializes raw input into the target class. diff --git a/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/codec/JacksonDeserializer.java b/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/codec/JacksonDeserializer.java index 8b6e929d6..57900dac7 100644 --- a/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/codec/JacksonDeserializer.java +++ b/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/codec/JacksonDeserializer.java @@ -18,6 +18,9 @@ // CHECKSTYLE:OFF IllegalImport - Allow unbundled Jackson +import com.couchbase.columnar.client.java.ClusterOptions; +import com.couchbase.columnar.client.java.QueryOptions; +import com.couchbase.columnar.client.java.internal.ThreadSafe; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; @@ -35,7 +38,11 @@ * .build(); * var deserializer = new JacksonDeserializer(mapper); * + * + * @see ClusterOptions#deserializer(Deserializer) + * @see QueryOptions#deserializer(Deserializer) */ +@ThreadSafe public final class JacksonDeserializer implements Deserializer { private final ObjectMapper mapper; diff --git a/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/codec/TypeRef.java b/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/codec/TypeRef.java index 6d2ec0c7c..7716b98d1 100644 --- a/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/codec/TypeRef.java +++ b/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/codec/TypeRef.java @@ -16,6 +16,8 @@ package com.couchbase.columnar.client.java.codec; +import com.couchbase.columnar.client.java.internal.ThreadSafe; + import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; @@ -34,6 +36,7 @@ * * Super Type Tokens. */ +@ThreadSafe public abstract class TypeRef { private final Type type; diff --git a/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/codec/package-info.java b/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/codec/package-info.java index 4ec46a559..5d794279d 100644 --- a/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/codec/package-info.java +++ b/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/codec/package-info.java @@ -14,6 +14,10 @@ * limitations under the License. */ +/** + * Classes that turn query result rows into Java objects + * when you call {@code row.as(Class)} or {@code row.as(TypeRef)}. + */ @NonNullApi package com.couchbase.columnar.client.java.codec; diff --git a/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/internal/ThreadSafe.java b/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/internal/ThreadSafe.java new file mode 100644 index 000000000..927d4a1e0 --- /dev/null +++ b/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/internal/ThreadSafe.java @@ -0,0 +1,43 @@ +/* + * Copyright 2024 Couchbase, Inc. + * + * Licensed 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 com.couchbase.columnar.client.java.internal; + +import org.jetbrains.annotations.ApiStatus; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Documents that the annotated type is thread-safe. + *

+ * When applied to an interface or abstract class, + * documents that implementations and subclasses must be thread-safe. + *

+ * This annotation only provides information; nothing magical happens + * when it is present. The implementor of the annotated class/interface + * is responsible for ensuring it is actually thread-safe. + */ +@ApiStatus.Internal +@Retention(RetentionPolicy.SOURCE) +@Documented +@Target(ElementType.TYPE) +public @interface ThreadSafe { + String caveat() default ""; +} diff --git a/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/package-info.java b/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/package-info.java index 0954f8a5a..ec0440988 100644 --- a/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/package-info.java +++ b/columnar-java-client/src/main/java/com/couchbase/columnar/client/java/package-info.java @@ -14,6 +14,9 @@ * limitations under the License. */ +/** + * Start here by creating a {@link com.couchbase.columnar.client.java.Cluster}. + */ @NonNullApi package com.couchbase.columnar.client.java; From 0e0d8ef7cd1d3d87610b77d4aa402254a627a65a Mon Sep 17 00:00:00 2001 From: David Nault Date: Thu, 17 Oct 2024 12:32:52 -0500 Subject: [PATCH 27/73] Sample code for Kotlin transactions Change-Id: I8b9fec83099f39697eb0fdb1701e5737bdeb58b6 Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/218154 Tested-by: Build Bot Reviewed-by: David Nault --- .../com/couchbase/client/kotlin/Cluster.kt | 2 + .../kotlin/samples/TransactionSamples.kt | 62 +++++++++++++++++++ .../transactions/TransactionGetResult.kt | 3 + .../kotlin/transactions/Transactions.kt | 8 ++- 4 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/samples/TransactionSamples.kt diff --git a/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/Cluster.kt b/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/Cluster.kt index 4ca775c05..dba994bef 100644 --- a/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/Cluster.kt +++ b/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/Cluster.kt @@ -190,6 +190,8 @@ public class Cluster internal constructor( /** * A runner for transactional operations. + * + * @sample com.couchbase.client.kotlin.samples.simpleTransactionExample */ @VolatileCouchbaseApi public val transactions: Transactions diff --git a/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/samples/TransactionSamples.kt b/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/samples/TransactionSamples.kt new file mode 100644 index 000000000..ca5a80fd9 --- /dev/null +++ b/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/samples/TransactionSamples.kt @@ -0,0 +1,62 @@ +/* + * Copyright 2024 Couchbase, Inc. + * + * Licensed 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 com.couchbase.client.kotlin.samples + +import com.couchbase.client.kotlin.Cluster +import com.couchbase.client.kotlin.Collection +import com.couchbase.client.kotlin.transactions.TransactionGetResult +import kotlin.random.Random + + +internal suspend fun simpleTransactionExample( + cluster: Cluster, + collection: Collection, + sourceDocId: String, + destDocId: String, + value: Int +) { + // Assume two documents both contain integers. + // Subtract a value from the one document and add it to the other. + cluster.transactions.run { + // The SDK may execute this lambda multiple times + // if there are conflicts between transactions. + // All logic related to the transaction must happen + // inside this lambda. + + // Inside the lambda, `this` is a `TransactionAttemptContext` + // with instance methods like `get`, `replace`, `insert`, `remove`, + // and `query` for interacting with documents in a transactional way. + // These are the only methods you should use to interact with documents + // inside the transaction lambda. + + val source: TransactionGetResult = get(collection, sourceDocId) + val dest: TransactionGetResult = get(collection, destDocId) + + val newSourceValue: Int = source.contentAs() - value + val newDestValue: Int = dest.contentAs() + value + + replace(source, newSourceValue) + + // Throwing any exception triggers a rollback and causes + // `transactions.run` to throw TransactionFailedException. + if (Random.nextBoolean()) throw RuntimeException("simulated error") + require(newSourceValue >= 0) { "transfer would result in negative source value" } + require(newDestValue >= 0) { "transfer would result in dest value overflow" } + + replace(dest, newDestValue) + } +} diff --git a/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/transactions/TransactionGetResult.kt b/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/transactions/TransactionGetResult.kt index 33fe3e98b..f2fcabc5c 100644 --- a/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/transactions/TransactionGetResult.kt +++ b/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/transactions/TransactionGetResult.kt @@ -22,6 +22,9 @@ import com.couchbase.client.kotlin.codec.Content import com.couchbase.client.kotlin.codec.JsonSerializer import com.couchbase.client.kotlin.codec.typeRef +/** + * A document inside a transaction. + */ public class TransactionGetResult internal constructor( internal val internal: CoreTransactionGetResult, @property:PublishedApi internal val defaultJsonSerializer: JsonSerializer, diff --git a/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/transactions/Transactions.kt b/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/transactions/Transactions.kt index f6b0d9158..73c165260 100644 --- a/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/transactions/Transactions.kt +++ b/kotlin-client/src/main/kotlin/com/couchbase/client/kotlin/transactions/Transactions.kt @@ -38,7 +38,11 @@ import java.util.Optional import kotlin.time.Duration import kotlin.time.toJavaDuration - +/** + * The starting point for accessing Couchbase transactions. + * + * @see run + */ public class Transactions internal constructor(internal val core: Core) { private val internal = CoreTransactionsReactive(core, core.env.transactionsConfig()) @@ -73,6 +77,8 @@ public class Transactions internal constructor(internal val core: Core) { * * @throws TransactionFailedException or a derived exception if the transaction fails to commit for any reason, possibly * after multiple retries. The exception contains further details of the error + * + * @sample com.couchbase.client.kotlin.samples.simpleTransactionExample */ public suspend fun run( timeout: Duration? = null, From 2feb9f1a73be29ae054b7aed5fc40a3f46231377 Mon Sep 17 00:00:00 2001 From: David Nault Date: Tue, 22 Oct 2024 16:45:43 -0700 Subject: [PATCH 28/73] JVMCBC-1576 Bump protobuf version from 3.23.2 to 3.25.5 Change-Id: I060c6846958edf98067b318498ebd27de75f6bac Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/218405 Tested-by: Build Bot Reviewed-by: David Nault --- protostellar/pom.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/protostellar/pom.xml b/protostellar/pom.xml index 8be5c95fd..7ff035665 100644 --- a/protostellar/pom.xml +++ b/protostellar/pom.xml @@ -15,9 +15,10 @@ 9999.0-SNAPSHOT - + 1.66.0 - 3.23.2 + 3.25.5 8 8 UTF-8 From 48554885809416c73c42b3aecc9680636a7dc19c Mon Sep 17 00:00:00 2001 From: David Nault Date: Tue, 22 Oct 2024 18:14:19 -0700 Subject: [PATCH 29/73] JVMCBC-1577 ClusterTopology refactoring broke serviceShowsUpInConfig() Motivation ---------- After the refactoring, only one of NodeInfo.services() or NodeInfo.sslServices() is populated, depending on whether TLS was used to connect. Same for PortInfo.ports() / PortInto.sslPorts(). This could cause the SDK to erroneously report a service is not available, even though it is actually available. Modifications ------------- Update serviceShowsUpInConfig() to look in both places, instead of just the non-TLS place. Take advantage of BucketConfig.serviceEnabled() which does the check correctly (looks in both places). Change-Id: I63cf3a8a2c174c95e49c4eade433da9c3a42cfc6 Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/218411 Reviewed-by: Michael Reiche Tested-by: Build Bot --- .../client/core/node/RoundRobinLocator.java | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/core-io/src/main/java/com/couchbase/client/core/node/RoundRobinLocator.java b/core-io/src/main/java/com/couchbase/client/core/node/RoundRobinLocator.java index 7994a1696..861614561 100644 --- a/core-io/src/main/java/com/couchbase/client/core/node/RoundRobinLocator.java +++ b/core-io/src/main/java/com/couchbase/client/core/node/RoundRobinLocator.java @@ -18,9 +18,7 @@ import com.couchbase.client.core.CoreContext; import com.couchbase.client.core.cnc.events.node.NodeLocatorBugIdentifiedEvent; -import com.couchbase.client.core.config.BucketConfig; import com.couchbase.client.core.config.ClusterConfig; -import com.couchbase.client.core.config.NodeInfo; import com.couchbase.client.core.config.PortInfo; import com.couchbase.client.core.error.FeatureNotAvailableException; import com.couchbase.client.core.error.ServiceNotAvailableException; @@ -140,21 +138,14 @@ public void dispatch(final Request request, final List private boolean serviceShowsUpInConfig(final ClusterConfig clusterConfig) { if (clusterConfig.globalConfig() != null) { for (PortInfo portInfo : clusterConfig.globalConfig().portInfos()) { - if (portInfo.ports().containsKey(serviceType)) { + if (portInfo.ports().containsKey(serviceType) || portInfo.sslPorts().containsKey(serviceType)) { return true; } } } - for (BucketConfig bucketConfig : clusterConfig.bucketConfigs().values()) { - for (NodeInfo nodeInfo : bucketConfig.nodes()) { - if (nodeInfo.services().containsKey(serviceType)) { - return true; - } - } - } - - return false; + return clusterConfig.bucketConfigs().values().stream() + .anyMatch(it -> it.serviceEnabled(serviceType)); } /** From 781f89a549952c59f3fdd9c53e5947b9c0e0c9e3 Mon Sep 17 00:00:00 2001 From: David Nault Date: Tue, 22 Oct 2024 18:09:01 -0700 Subject: [PATCH 30/73] JVMCBC-1572 ClusterTopology refactoring broke extractPingTargets() Motivation ---------- After the refactoring, only one of NodeInfo.services() or NodeInfo.sslServices() is populated, depending on whether TLS was used to connect. Same for PortInfo.ports() / PortInto.sslPorts(). Modifications ------------- Update extractPingTargets() to look in both places, instead of just the non-TLS place. Change-Id: I958ea82a7bb9721aa42290c6cc6ce55d76d1b74f Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/218410 Tested-by: Build Bot Reviewed-by: Michael Reiche --- .../client/core/diagnostics/HealthPinger.java | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/core-io/src/main/java/com/couchbase/client/core/diagnostics/HealthPinger.java b/core-io/src/main/java/com/couchbase/client/core/diagnostics/HealthPinger.java index 7ec808b5f..b0de6f5dd 100644 --- a/core-io/src/main/java/com/couchbase/client/core/diagnostics/HealthPinger.java +++ b/core-io/src/main/java/com/couchbase/client/core/diagnostics/HealthPinger.java @@ -33,7 +33,6 @@ import com.couchbase.client.core.msg.kv.KvPingRequest; import com.couchbase.client.core.msg.kv.KvPingResponse; import com.couchbase.client.core.node.NodeIdentifier; -import com.couchbase.client.core.protostellar.CoreProtostellarUtil; import com.couchbase.client.core.retry.RetryStrategy; import com.couchbase.client.core.service.ServiceType; import com.couchbase.client.core.util.CbThrowables; @@ -157,7 +156,7 @@ static Set extractPingTargets( log.message("extractPingTargets: getting ping targets from global config portInfos: " + globalConfig.portInfos()); for (PortInfo portInfo : globalConfig.portInfos()) { - for (ServiceType serviceType : portInfo.ports().keySet()) { + for (ServiceType serviceType : advertisedServices(portInfo)) { if (serviceType == ServiceType.KV || serviceType == ServiceType.VIEWS) { // do not check bucket-level resources from a global level (null bucket name will not work) continue; @@ -175,7 +174,7 @@ static Set extractPingTargets( log.message("extractPingTargets: getting targets from bucket config via global config for bucket " + bucketConfig.getKey() + " : " + bucketConfig.getValue()); for (NodeInfo nodeInfo : bucketConfig.getValue().nodes()) { - for (ServiceType serviceType : nodeInfo.services().keySet()) { + for (ServiceType serviceType : advertisedServices(nodeInfo)) { if (serviceType == ServiceType.KV || serviceType == ServiceType.VIEWS) { // do not check bucket-level resources from a global level (null bucket name will not work) continue; @@ -191,7 +190,7 @@ static Set extractPingTargets( if (bucketConfig != null) { log.message("extractPingTargets: Getting targets from bucket config: " + bucketConfig); for (NodeInfo nodeInfo : bucketConfig.nodes()) { - for (ServiceType serviceType : nodeInfo.services().keySet()) { + for (ServiceType serviceType : advertisedServices(nodeInfo)) { RequestTarget target; if (serviceType != ServiceType.VIEWS && serviceType != ServiceType.KV) { target = new RequestTarget(serviceType, nodeInfo.identifier(), null); @@ -220,6 +219,22 @@ static Set extractPingTargets( return targets; } + private static Set advertisedServices(PortInfo info) { + Set result = EnumSet.noneOf(ServiceType.class); + // add both because only is populated + result.addAll(info.ports().keySet()); + result.addAll(info.sslPorts().keySet()); + return result; + } + + private static Set advertisedServices(NodeInfo info) { + Set result = EnumSet.noneOf(ServiceType.class); + // add both because only is populated + result.addAll(info.services().keySet()); + result.addAll(info.sslServices().keySet()); + return result; + } + private static String format(NodeIdentifier id) { return new HostAndPort(id.address(), id.managerPort()).format(); } From 17930ed88d23834a555ac6e6d942e984bf568bb9 Mon Sep 17 00:00:00 2001 From: David Nault Date: Thu, 17 Oct 2024 16:50:17 -0500 Subject: [PATCH 31/73] JCBC-2167 Reactive API: publish on configurable scheduler Motivation ---------- Simplify integration with Vert.x Avoid issues related to running user code on the SDK's Netty event loops. Modifications ------------- Add `userScheduler` CoreEnvironment config option. Publish all reactive API results on that Scheduler (if specified). Change-Id: I226debde8bac9fbf255e311817dd0587ad2f561f Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/214702 Tested-by: Build Bot Reviewed-by: Michael Reiche --- .../client/core/env/CoreEnvironment.java | 44 ++++++++- .../client/core/util/ReactorOps.java | 89 +++++++++++++++++++ .../com/couchbase/client/java/Cluster.java | 6 +- .../client/java/ReactiveBinaryCollection.java | 11 ++- .../couchbase/client/java/ReactiveBucket.java | 4 +- .../client/java/ReactiveCluster.java | 33 +++---- .../client/java/ReactiveCollection.java | 11 ++- .../couchbase/client/java/ReactiveScope.java | 16 ++-- .../java/batch/ReactiveBatchHelper.java | 4 +- .../http/ReactiveCouchbaseHttpClient.java | 21 +++-- .../ReactiveAnalyticsIndexManager.java | 72 ++++++++------- .../java/manager/bucket/BucketManager.java | 5 +- .../manager/bucket/ReactiveBucketManager.java | 30 ++++--- .../collection/ReactiveCollectionManager.java | 31 ++++--- .../ReactiveEventingFunctionManager.java | 27 +++--- .../ReactiveScopeEventingFunctionManager.java | 27 +++--- .../java/manager/query/QueryIndexManager.java | 5 +- .../ReactiveCollectionQueryIndexManager.java | 34 +++---- .../query/ReactiveQueryIndexManager.java | 34 +++---- .../search/ReactiveSearchIndexManager.java | 34 +++---- .../manager/user/ReactiveUserManager.java | 47 +++++----- .../client/java/manager/user/UserManager.java | 8 +- .../view/ReactiveViewIndexManager.java | 27 +++--- .../ReactiveTransactionAttemptContext.java | 62 ++++++++----- .../transactions/ReactiveTransactions.java | 10 ++- 25 files changed, 448 insertions(+), 244 deletions(-) create mode 100644 core-io/src/main/java/com/couchbase/client/core/util/ReactorOps.java diff --git a/core-io/src/main/java/com/couchbase/client/core/env/CoreEnvironment.java b/core-io/src/main/java/com/couchbase/client/core/env/CoreEnvironment.java index d3396ee54..868a939c8 100644 --- a/core-io/src/main/java/com/couchbase/client/core/env/CoreEnvironment.java +++ b/core-io/src/main/java/com/couchbase/client/core/env/CoreEnvironment.java @@ -42,6 +42,8 @@ import com.couchbase.client.core.transaction.config.CoreTransactionsConfig; import com.couchbase.client.core.transaction.forwards.CoreTransactionsSupportedExtensions; import com.couchbase.client.core.transaction.util.CoreTransactionsSchedulers; +import com.couchbase.client.core.util.ReactorOps; +import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.core.scheduler.Scheduler; import reactor.core.scheduler.Schedulers; @@ -64,6 +66,7 @@ import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; +import java.util.function.Supplier; import static com.couchbase.client.core.env.OwnedOrExternal.external; import static com.couchbase.client.core.env.OwnedOrExternal.owned; @@ -76,7 +79,7 @@ * Note that unless you are using the core directly, you want to consider the child implementations for each * language binding (i.e. the ClusterEnvironment for the java client). */ -public class CoreEnvironment implements AutoCloseable { +public class CoreEnvironment implements ReactorOps, AutoCloseable { private static final VersionAndGitHash coreVersion = VersionAndGitHash.from(Core.class); private static final String CORE_AGENT_TITLE = "java-core"; @@ -113,6 +116,7 @@ public class CoreEnvironment implements AutoCloseable { private final RetryStrategy retryStrategy; private final OwnedOrExternal scheduler; private final OwnedOrExternal executor; + @Nullable private final Supplier userScheduler; private final int schedulerThreadCount; private final OrphanReporter orphanReporter; private final long maxNumRequestsInRetry; @@ -140,6 +144,7 @@ protected CoreEnvironment(final Builder builder) { .orElse(owned( Schedulers.newParallel("cb-comp", schedulerThreadCount, true)) ); + this.userScheduler = builder.userScheduler; // JVMCBC-1196: configuration options for the executor will be provided. String executorMaxThreadCountRaw = System.getProperty("com.couchbase.protostellar.executorMaxThreadCount"); @@ -354,6 +359,26 @@ public Scheduler scheduler() { return scheduler.get(); } + /** + * Returns the supplier for the scheduler where Reactive API results should be published, + * or null if the user does not want to switch schedulers. + */ + @Stability.Internal + @Nullable + public Supplier userScheduler() { + return userScheduler; + } + + @Stability.Internal + public Mono publishOnUserScheduler(Mono mono) { + return userScheduler == null ? mono : Mono.defer(() -> mono.publishOn(userScheduler.get())); + } + + @Stability.Internal + public Flux publishOnUserScheduler(Flux flux) { + return userScheduler == null ? flux : Flux.defer(() -> flux.publishOn(userScheduler.get())); + } + /** * Returns the executor used to schedule non-reactive async tasks across the SDK. */ @@ -597,6 +622,7 @@ public static class Builder> { private LoggingMeterConfig.Builder loggingMeterConfig = new LoggingMeterConfig.Builder(); private OwnedOrExternal eventBus = null; private OwnedOrExternal scheduler = null; + private Supplier userScheduler = null; private int schedulerThreadCount = Schedulers.DEFAULT_POOL_SIZE; private OwnedOrExternal requestTracer = null; private OwnedOrExternal meter = null; @@ -903,6 +929,22 @@ public SELF thresholdLoggingTracerConfig(final ThresholdLoggingTracerConfig.Buil return self(); } + /** + * Specifies the supplier the SDK uses to get the Scheduler for publishing Reactive API results. + *

+ * Defaults to null, which means reactive results are published immediately + * in a thread owned by the SDK -- typically the SDK's Netty event loop. + *

+ * The supplier is invoked once for every subscription, by the same thread that subscribes to the Mono/Flux. + * + * @return this {@link Builder} for chaining purposes. + */ + @Stability.Volatile + public SELF publishOnScheduler(@Nullable final Supplier publishOnScheduler) { + this.userScheduler = publishOnScheduler; + return self(); + } + public ThresholdLoggingTracerConfig.Builder thresholdLoggingTracerConfig() { return thresholdLoggingTracerConfig; } diff --git a/core-io/src/main/java/com/couchbase/client/core/util/ReactorOps.java b/core-io/src/main/java/com/couchbase/client/core/util/ReactorOps.java new file mode 100644 index 000000000..e474224b6 --- /dev/null +++ b/core-io/src/main/java/com/couchbase/client/core/util/ReactorOps.java @@ -0,0 +1,89 @@ +/* + * Copyright 2024 Couchbase, Inc. + * + * Licensed 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 com.couchbase.client.core.util; + + +import com.couchbase.client.core.Reactor; +import com.couchbase.client.core.annotation.Stability; +import reactor.core.CorePublisher; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Scheduler; +import reactor.util.annotation.Nullable; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.concurrent.CompletableFuture; +import java.util.function.Supplier; + +@Stability.Internal +public interface ReactorOps { + + default Mono publishOnUserScheduler(Supplier> future) { + return publishOnUserScheduler(Reactor.toMono(future)); + } + + Mono publishOnUserScheduler(Mono mono); + + Flux publishOnUserScheduler(Flux mono); + + /** + * Returns a dynamic proxy for the given object + * (unless the given supplier is null, in which case + * the same object is returned). + *

+ * Any Flux or Mono instances returned by the proxied interface methods + * are published on the scheduler returned by the given supplier. + */ + @Stability.Internal + static T proxyToPublishOnSuppliedScheduler( + T obj, + Class interfaceToProxy, + @Nullable Supplier scheduler + ) { + if (scheduler == null) { + return obj; + } + return interfaceToProxy.cast( + Proxy.newProxyInstance( + interfaceToProxy.getClassLoader(), + new Class[]{interfaceToProxy}, + new InvocationHandler() { + @SuppressWarnings("ReactiveStreamsUnusedPublisher") + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + try { + Object result = method.invoke(obj, args); + if (result instanceof CorePublisher) { + if (result instanceof Mono) { + return Mono.defer(() -> ((Mono) result).publishOn(scheduler.get())); + } + if (result instanceof Flux) { + return Flux.defer(() -> ((Flux) result).publishOn(scheduler.get())); + } + } + return result; + } catch (InvocationTargetException e) { + throw e.getCause(); + } + } + }) + ); + } +} diff --git a/java-client/src/main/java/com/couchbase/client/java/Cluster.java b/java-client/src/main/java/com/couchbase/client/java/Cluster.java index 3fe2ec8c0..b9aebbd13 100644 --- a/java-client/src/main/java/com/couchbase/client/java/Cluster.java +++ b/java-client/src/main/java/com/couchbase/client/java/Cluster.java @@ -334,14 +334,14 @@ public CouchbaseHttpClient httpClient() { * The user manager allows to manage users and groups. */ public UserManager users() { - return new UserManager(asyncCluster.users()); + return new UserManager(environment(), asyncCluster.users()); } /** * The bucket manager allows to perform administrative tasks on buckets and their resources. */ public BucketManager buckets() { - return new BucketManager(asyncCluster.buckets()); + return new BucketManager(environment(), asyncCluster.buckets()); } /** @@ -355,7 +355,7 @@ public AnalyticsIndexManager analyticsIndexes() { * The query index manager allows to modify and create indexes for the query service. */ public QueryIndexManager queryIndexes() { - return new QueryIndexManager(asyncCluster.queryIndexes()); + return new QueryIndexManager(environment(), asyncCluster.queryIndexes()); } /** diff --git a/java-client/src/main/java/com/couchbase/client/java/ReactiveBinaryCollection.java b/java-client/src/main/java/com/couchbase/client/java/ReactiveBinaryCollection.java index f1eff7a3e..96c100d73 100644 --- a/java-client/src/main/java/com/couchbase/client/java/ReactiveBinaryCollection.java +++ b/java-client/src/main/java/com/couchbase/client/java/ReactiveBinaryCollection.java @@ -16,6 +16,7 @@ package com.couchbase.client.java; +import static com.couchbase.client.core.util.ReactorOps.proxyToPublishOnSuppliedScheduler; import static com.couchbase.client.core.util.Validators.notNull; import static com.couchbase.client.java.BinaryCollection.DEFAULT_APPEND_OPTIONS; import static com.couchbase.client.java.BinaryCollection.DEFAULT_DECREMENT_OPTIONS; @@ -25,9 +26,9 @@ import com.couchbase.client.core.io.CollectionIdentifier; import com.couchbase.client.core.util.PreventsGarbageCollection; +import com.couchbase.client.java.env.ClusterEnvironment; import reactor.core.publisher.Mono; -import com.couchbase.client.core.Core; import com.couchbase.client.core.api.kv.CoreKvBinaryOps; import com.couchbase.client.core.error.CasMismatchException; import com.couchbase.client.core.error.CouchbaseException; @@ -53,9 +54,13 @@ public class ReactiveBinaryCollection { @PreventsGarbageCollection private final AsyncBinaryCollection async; - ReactiveBinaryCollection(final AsyncBinaryCollection async) { + ReactiveBinaryCollection(final AsyncBinaryCollection async, final ClusterEnvironment env) { this.collectionIdentifier = async.collectionIdentifier(); - this.coreKvBinaryOps = async.coreKvBinaryOps; + this.coreKvBinaryOps = proxyToPublishOnSuppliedScheduler( + async.coreKvBinaryOps, + CoreKvBinaryOps.class, + env.userScheduler() + ); this.async = requireNonNull(async); } diff --git a/java-client/src/main/java/com/couchbase/client/java/ReactiveBucket.java b/java-client/src/main/java/com/couchbase/client/java/ReactiveBucket.java index d389ca323..7764092e4 100644 --- a/java-client/src/main/java/com/couchbase/client/java/ReactiveBucket.java +++ b/java-client/src/main/java/com/couchbase/client/java/ReactiveBucket.java @@ -93,11 +93,11 @@ public Core core() { } public ReactiveCollectionManager collections() { - return new ReactiveCollectionManager(asyncBucket.collections()); + return new ReactiveCollectionManager(environment(), asyncBucket.collections()); } public ReactiveViewIndexManager viewIndexes() { - return new ReactiveViewIndexManager(asyncBucket.viewIndexes()); + return new ReactiveViewIndexManager(environment(), asyncBucket.viewIndexes()); } /** diff --git a/java-client/src/main/java/com/couchbase/client/java/ReactiveCluster.java b/java-client/src/main/java/com/couchbase/client/java/ReactiveCluster.java index 6959bc5c7..c405afae0 100644 --- a/java-client/src/main/java/com/couchbase/client/java/ReactiveCluster.java +++ b/java-client/src/main/java/com/couchbase/client/java/ReactiveCluster.java @@ -32,6 +32,7 @@ import com.couchbase.client.core.error.context.ReducedAnalyticsErrorContext; import com.couchbase.client.core.error.context.ReducedQueryErrorContext; import com.couchbase.client.core.error.context.ReducedSearchErrorContext; +import com.couchbase.client.core.util.ReactorOps; import com.couchbase.client.core.util.ConnectionString; import com.couchbase.client.java.analytics.AnalyticsAccessor; import com.couchbase.client.java.analytics.AnalyticsOptions; @@ -115,6 +116,8 @@ public class ReactiveCluster { */ private final AsyncCluster asyncCluster; + private final ReactorOps reactor; + /** * Stores already opened buckets for reuse. */ @@ -182,9 +185,9 @@ private ReactiveCluster( */ ReactiveCluster(final AsyncCluster asyncCluster) { this.asyncCluster = asyncCluster; + this.reactor = asyncCluster.environment(); } - /** * Provides access to the underlying {@link Core}. * @@ -200,21 +203,21 @@ public Core core() { */ @Stability.Volatile public ReactiveCouchbaseHttpClient httpClient() { - return new ReactiveCouchbaseHttpClient(asyncCluster.httpClient()); + return new ReactiveCouchbaseHttpClient(reactor, asyncCluster.httpClient()); } /** * Provides access to the user management services. */ public ReactiveUserManager users() { - return new ReactiveUserManager(asyncCluster.users()); + return new ReactiveUserManager(reactor, asyncCluster.users()); } /** * Provides access to the bucket management services. */ public ReactiveBucketManager buckets() { - return new ReactiveBucketManager(async().buckets()); + return new ReactiveBucketManager(reactor, async().buckets()); } /** @@ -228,14 +231,14 @@ public ReactiveAnalyticsIndexManager analyticsIndexes() { * Provides access to the search index management services. */ public ReactiveSearchIndexManager searchIndexes() { - return new ReactiveSearchIndexManager(async().searchIndexes()); + return new ReactiveSearchIndexManager(reactor, async().searchIndexes()); } /** * Provides access to the N1QL index management services. */ public ReactiveQueryIndexManager queryIndexes() { - return new ReactiveQueryIndexManager(async().queryIndexes()); + return new ReactiveQueryIndexManager(reactor, async().queryIndexes()); } /** @@ -243,7 +246,7 @@ public ReactiveQueryIndexManager queryIndexes() { */ @Stability.Uncommitted public ReactiveEventingFunctionManager eventingFunctions() { - return new ReactiveEventingFunctionManager(async().eventingFunctions()); + return new ReactiveEventingFunctionManager(reactor, async().eventingFunctions()); } /** @@ -281,7 +284,7 @@ public Mono query(final String statement, final QueryOption notNull(options, "QueryOptions", () -> new ReducedQueryErrorContext(statement)); final QueryOptions.Built opts = options.build(); JsonSerializer serializer = opts.serializer() == null ? environment().jsonSerializer() : opts.serializer(); - return async().queryOps.queryReactive(statement, opts, null, null, QueryAccessor::convertCoreQueryError) + return reactor.publishOnUserScheduler(async().queryOps.queryReactive(statement, opts, null, null, QueryAccessor::convertCoreQueryError)) .map(result -> new ReactiveQueryResult(result, serializer)); } @@ -307,13 +310,13 @@ public Mono analyticsQuery(final String statement, fina notNull(options, "AnalyticsOptions", () -> new ReducedAnalyticsErrorContext(statement)); AnalyticsOptions.Built opts = options.build(); JsonSerializer serializer = opts.serializer() == null ? environment().jsonSerializer() : opts.serializer(); - return Mono.defer(() -> { + return reactor.publishOnUserScheduler(Mono.defer(() -> { return AnalyticsAccessor.analyticsQueryReactive( asyncCluster.core(), asyncCluster.analyticsRequest(statement, opts), serializer ); - }); + })); } /** @@ -350,7 +353,7 @@ public Mono search(final String indexName, final SearchReq CoreSearchRequest coreRequest = searchRequest.toCore(); SearchOptions.Built opts = options.build(); JsonSerializer serializer = opts.serializer() == null ? environment().jsonSerializer() : opts.serializer(); - return asyncCluster.searchOps.searchReactive(indexName, coreRequest, opts) + return reactor.publishOnUserScheduler(asyncCluster.searchOps.searchReactive(indexName, coreRequest, opts)) .map(r -> new ReactiveSearchResult(r, serializer)); } @@ -387,7 +390,7 @@ public Mono searchQuery(final String indexName, final Sear notNull(options, "SearchOptions", () -> new ReducedSearchErrorContext(indexName, coreQuery)); SearchOptions.Built opts = options.build(); JsonSerializer serializer = opts.serializer() == null ? environment().jsonSerializer() : opts.serializer(); - return asyncCluster.searchOps.searchQueryReactive(indexName, coreQuery, opts) + return reactor.publishOnUserScheduler(asyncCluster.searchOps.searchQueryReactive(indexName, coreQuery, opts)) .map(result -> new ReactiveSearchResult(result, serializer)); } @@ -450,7 +453,7 @@ public Mono diagnostics() { * @return the {@link DiagnosticsResult} once complete. */ public Mono diagnostics(final DiagnosticsOptions options) { - return Mono.defer(() -> Mono.fromFuture(asyncCluster.diagnostics(options))); + return reactor.publishOnUserScheduler(Mono.defer(() -> Mono.fromFuture(asyncCluster.diagnostics(options)))); } /** @@ -477,7 +480,7 @@ public Mono ping() { * @return the {@link PingResult} once complete. */ public Mono ping(final PingOptions options) { - return Mono.defer(() -> Mono.fromFuture(asyncCluster.ping(options))); + return reactor.publishOnUserScheduler(Mono.defer(() -> Mono.fromFuture(asyncCluster.ping(options)))); } /** @@ -506,7 +509,7 @@ public Mono waitUntilReady(final Duration timeout) { * @return a mono that completes either once ready or timeout. */ public Mono waitUntilReady(final Duration timeout, final WaitUntilReadyOptions options) { - return Mono.defer(() -> Mono.fromFuture(asyncCluster.waitUntilReady(timeout, options))); + return reactor.publishOnUserScheduler(Mono.defer(() -> Mono.fromFuture(asyncCluster.waitUntilReady(timeout, options)))); } /** diff --git a/java-client/src/main/java/com/couchbase/client/java/ReactiveCollection.java b/java-client/src/main/java/com/couchbase/client/java/ReactiveCollection.java index c8bb9e866..6a7d4e5f2 100644 --- a/java-client/src/main/java/com/couchbase/client/java/ReactiveCollection.java +++ b/java-client/src/main/java/com/couchbase/client/java/ReactiveCollection.java @@ -65,6 +65,7 @@ import java.util.Optional; import static com.couchbase.client.core.util.CbCollections.transform; +import static com.couchbase.client.core.util.ReactorOps.proxyToPublishOnSuppliedScheduler; import static com.couchbase.client.core.util.Validators.notNull; import static com.couchbase.client.java.kv.ExistsOptions.existsOptions; import static com.couchbase.client.java.kv.GetAllReplicasOptions.getAllReplicasOptions; @@ -135,9 +136,13 @@ public class ReactiveCollection { ReactiveCollection(final AsyncCollection asyncCollection) { this.asyncCollection = asyncCollection; - this.reactiveBinaryCollection = new ReactiveBinaryCollection(asyncCollection.binary()); - this.kvOps = asyncCollection.kvOps; - this.queryIndexManager = new ReactiveCollectionQueryIndexManager(asyncCollection.queryIndexes()); + this.reactiveBinaryCollection = new ReactiveBinaryCollection(asyncCollection.binary(), asyncCollection.environment()); + this.kvOps = proxyToPublishOnSuppliedScheduler( + asyncCollection.kvOps, + CoreKvOps.class, + asyncCollection.environment().userScheduler() + ); + this.queryIndexManager = new ReactiveCollectionQueryIndexManager(environment(), asyncCollection.queryIndexes()); } /** diff --git a/java-client/src/main/java/com/couchbase/client/java/ReactiveScope.java b/java-client/src/main/java/com/couchbase/client/java/ReactiveScope.java index 01503874d..951a64e8b 100644 --- a/java-client/src/main/java/com/couchbase/client/java/ReactiveScope.java +++ b/java-client/src/main/java/com/couchbase/client/java/ReactiveScope.java @@ -27,6 +27,7 @@ import com.couchbase.client.core.error.context.ReducedQueryErrorContext; import com.couchbase.client.core.error.context.ReducedSearchErrorContext; import com.couchbase.client.core.io.CollectionIdentifier; +import com.couchbase.client.core.util.ReactorOps; import com.couchbase.client.java.analytics.AnalyticsAccessor; import com.couchbase.client.java.analytics.AnalyticsOptions; import com.couchbase.client.java.analytics.ReactiveAnalyticsResult; @@ -67,6 +68,8 @@ public class ReactiveScope { */ private final AsyncScope asyncScope; + private final ReactorOps reactor; + /** * Stores already opened collections for reuse. */ @@ -78,6 +81,7 @@ public class ReactiveScope { * @param asyncScope the underlying async scope. */ ReactiveScope(final AsyncScope asyncScope) { + this.reactor = asyncScope.environment(); this.asyncScope = asyncScope; } @@ -164,7 +168,7 @@ public Mono query(final String statement, final QueryOption notNull(options, "QueryOptions", () -> new ReducedQueryErrorContext(statement)); final QueryOptions.Built opts = options.build(); JsonSerializer serializer = opts.serializer() == null ? environment().jsonSerializer() : opts.serializer(); - return async().queryOps.queryReactive(statement, opts, asyncScope.queryContext, null, QueryAccessor::convertCoreQueryError) + return reactor.publishOnUserScheduler(async().queryOps.queryReactive(statement, opts, asyncScope.queryContext, null, QueryAccessor::convertCoreQueryError)) .map(result -> new ReactiveQueryResult(result, serializer)); } @@ -190,13 +194,13 @@ public Mono analyticsQuery(final String statement, fina notNull(options, "AnalyticsOptions", () -> new ReducedAnalyticsErrorContext(statement)); AnalyticsOptions.Built opts = options.build(); JsonSerializer serializer = opts.serializer() == null ? environment().jsonSerializer() : opts.serializer(); - return Mono.defer(() -> { + return reactor.publishOnUserScheduler(Mono.defer(() -> { return AnalyticsAccessor.analyticsQueryReactive( asyncScope.core(), asyncScope.analyticsRequest(statement, opts), serializer ); - }); + })); } /** @@ -235,7 +239,7 @@ public Mono search(final String indexName, final SearchReq CoreSearchRequest coreRequest = searchRequest.toCore(); SearchOptions.Built opts = options.build(); JsonSerializer serializer = opts.serializer() == null ? environment().jsonSerializer() : opts.serializer(); - return asyncScope.searchOps.searchReactive(indexName, coreRequest, opts) + return reactor.publishOnUserScheduler(asyncScope.searchOps.searchReactive(indexName, coreRequest, opts)) .map(r -> new ReactiveSearchResult(r, serializer)); } @@ -272,7 +276,7 @@ public Mono searchQuery(final String indexName, final Sear notNull(options, "SearchOptions", () -> new ReducedSearchErrorContext(indexName, coreQuery)); SearchOptions.Built opts = options.build(); JsonSerializer serializer = opts.serializer() == null ? environment().jsonSerializer() : opts.serializer(); - return asyncScope.searchOps.searchQueryReactive(indexName, coreQuery, opts) + return reactor.publishOnUserScheduler(asyncScope.searchOps.searchQueryReactive(indexName, coreQuery, opts)) .map(result -> new ReactiveSearchResult(result, serializer)); } @@ -282,6 +286,6 @@ public Mono searchQuery(final String indexName, final Sear @Stability.Volatile @SinceCouchbase("7.1") public ReactiveScopeEventingFunctionManager eventingFunctions() { - return new ReactiveScopeEventingFunctionManager(asyncScope.eventingFunctions()); + return new ReactiveScopeEventingFunctionManager(environment(), asyncScope.eventingFunctions()); } } diff --git a/java-client/src/main/java/com/couchbase/client/java/batch/ReactiveBatchHelper.java b/java-client/src/main/java/com/couchbase/client/java/batch/ReactiveBatchHelper.java index 6c9499568..171ca410b 100644 --- a/java-client/src/main/java/com/couchbase/client/java/batch/ReactiveBatchHelper.java +++ b/java-client/src/main/java/com/couchbase/client/java/batch/ReactiveBatchHelper.java @@ -166,7 +166,7 @@ private static Flux existsBytes(final Collection collection, final java. responses.add(Reactor.wrap(request, request.response(), true)); } - return Flux + return env.publishOnUserScheduler(Flux .merge(responses) .flatMap(response -> Flux.fromIterable(response.observed().keySet())) .onErrorMap(throwable -> { @@ -176,7 +176,7 @@ private static Flux existsBytes(final Collection collection, final java. .doOnComplete(() -> core.context().environment().eventBus().publish(new BatchHelperExistsCompletedEvent( Duration.ofNanos(System.nanoTime() - start), new BatchErrorContext(Collections.unmodifiableList(requests)) - ))); + )))); } } diff --git a/java-client/src/main/java/com/couchbase/client/java/http/ReactiveCouchbaseHttpClient.java b/java-client/src/main/java/com/couchbase/client/java/http/ReactiveCouchbaseHttpClient.java index 2462dc8a4..45b8b19f8 100644 --- a/java-client/src/main/java/com/couchbase/client/java/http/ReactiveCouchbaseHttpClient.java +++ b/java-client/src/main/java/com/couchbase/client/java/http/ReactiveCouchbaseHttpClient.java @@ -17,6 +17,7 @@ package com.couchbase.client.java.http; import com.couchbase.client.core.annotation.Stability; +import com.couchbase.client.core.util.ReactorOps; import com.couchbase.client.java.ReactiveCluster; import reactor.core.publisher.Mono; @@ -32,10 +33,12 @@ * @see AsyncCouchbaseHttpClient */ public class ReactiveCouchbaseHttpClient { + private final ReactorOps reactor; private final AsyncCouchbaseHttpClient async; @Stability.Internal - public ReactiveCouchbaseHttpClient(AsyncCouchbaseHttpClient async) { + public ReactiveCouchbaseHttpClient(ReactorOps reactor, AsyncCouchbaseHttpClient async) { + this.reactor = requireNonNull(reactor); this.async = requireNonNull(async); } @@ -46,7 +49,7 @@ public ReactiveCouchbaseHttpClient(AsyncCouchbaseHttpClient async) { * or include the query string in the path. */ public Mono get(HttpTarget target, HttpPath path) { - return toMono(() -> async.get(target, path)); + return reactor.publishOnUserScheduler(() -> async.get(target, path)); } /** @@ -59,7 +62,7 @@ public Mono get(HttpTarget target, HttpPath path) { * */ public Mono get(HttpTarget target, HttpPath path, HttpGetOptions options) { - return toMono(() -> async.get(target, path, options)); + return reactor.publishOnUserScheduler(() -> async.get(target, path, options)); } /** @@ -68,7 +71,7 @@ public Mono get(HttpTarget target, HttpPath path, HttpGetOptions o * To specify a request body, use the overload that takes {@link HttpPostOptions}. */ public Mono post(HttpTarget target, HttpPath path) { - return toMono(() -> async.post(target, path)); + return reactor.publishOnUserScheduler(() -> async.post(target, path)); } /** @@ -86,7 +89,7 @@ public Mono post(HttpTarget target, HttpPath path) { * */ public Mono post(HttpTarget target, HttpPath path, HttpPostOptions options) { - return toMono(() -> async.post(target, path, options)); + return reactor.publishOnUserScheduler(() -> async.post(target, path, options)); } /** @@ -95,7 +98,7 @@ public Mono post(HttpTarget target, HttpPath path, HttpPostOptions * To specify a request body, use the overload that takes {@link HttpPutOptions}. */ public Mono put(HttpTarget target, HttpPath path) { - return toMono(() -> async.put(target, path)); + return reactor.publishOnUserScheduler(() -> async.put(target, path)); } /** @@ -113,20 +116,20 @@ public Mono put(HttpTarget target, HttpPath path) { * */ public Mono put(HttpTarget target, HttpPath path, HttpPutOptions options) { - return toMono(() -> async.put(target, path, options)); + return reactor.publishOnUserScheduler(() -> async.put(target, path, options)); } /** * Returns a Mono that, when subscribed, issues a DELETE request with default options. */ public Mono delete(HttpTarget target, HttpPath path) { - return toMono(() -> async.delete(target, path)); + return reactor.publishOnUserScheduler(() -> async.delete(target, path)); } /** * Returns a Mono that, when subscribed, issues a DELETE request with given options. */ public Mono delete(HttpTarget target, HttpPath path, HttpDeleteOptions options) { - return toMono(() -> async.delete(target, path, options)); + return reactor.publishOnUserScheduler(() -> async.delete(target, path, options)); } } diff --git a/java-client/src/main/java/com/couchbase/client/java/manager/analytics/ReactiveAnalyticsIndexManager.java b/java-client/src/main/java/com/couchbase/client/java/manager/analytics/ReactiveAnalyticsIndexManager.java index 6cd2064f8..43f4bf720 100644 --- a/java-client/src/main/java/com/couchbase/client/java/manager/analytics/ReactiveAnalyticsIndexManager.java +++ b/java-client/src/main/java/com/couchbase/client/java/manager/analytics/ReactiveAnalyticsIndexManager.java @@ -29,6 +29,7 @@ import com.couchbase.client.core.error.InvalidArgumentException; import com.couchbase.client.core.error.LinkExistsException; import com.couchbase.client.core.error.LinkNotFoundException; +import com.couchbase.client.core.util.ReactorOps; import com.couchbase.client.java.AsyncCluster; import com.couchbase.client.java.ReactiveCluster; import com.couchbase.client.java.manager.analytics.link.AnalyticsLink; @@ -40,7 +41,6 @@ import java.util.Map; import static com.couchbase.client.core.Reactor.toFlux; -import static com.couchbase.client.core.Reactor.toMono; /** * Performs management operations on analytics indexes. @@ -48,6 +48,7 @@ public class ReactiveAnalyticsIndexManager { private final AsyncAnalyticsIndexManager async; + private final ReactorOps reactor; /** * Creates a new {@link ReactiveAnalyticsIndexManager}. @@ -58,7 +59,10 @@ public class ReactiveAnalyticsIndexManager { * @param cluster the async cluster to perform the analytics queries on. */ @Stability.Internal - public ReactiveAnalyticsIndexManager(final AsyncCluster cluster) { + public ReactiveAnalyticsIndexManager( + final AsyncCluster cluster + ) { + this.reactor = cluster.environment(); this.async = new AsyncAnalyticsIndexManager(cluster); } @@ -80,7 +84,7 @@ public AsyncAnalyticsIndexManager async() { * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono createDataverse(final String dataverseName) { - return toMono(() -> async.createDataverse(dataverseName)); + return reactor.publishOnUserScheduler(() -> async.createDataverse(dataverseName)); } /** @@ -93,7 +97,7 @@ public Mono createDataverse(final String dataverseName) { * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono createDataverse(String dataverseName, CreateDataverseAnalyticsOptions options) { - return toMono(() -> async.createDataverse(dataverseName, options)); + return reactor.publishOnUserScheduler(() -> async.createDataverse(dataverseName, options)); } /** @@ -106,7 +110,7 @@ public Mono createDataverse(String dataverseName, CreateDataverseAnalytics * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono dropDataverse(String dataverseName) { - return toMono(() -> async.dropDataverse(dataverseName)); + return reactor.publishOnUserScheduler(() -> async.dropDataverse(dataverseName)); } /** @@ -120,7 +124,7 @@ public Mono dropDataverse(String dataverseName) { * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono dropDataverse(String dataverseName, DropDataverseAnalyticsOptions options) { - return toMono(() -> async.dropDataverse(dataverseName, options)); + return reactor.publishOnUserScheduler(() -> async.dropDataverse(dataverseName, options)); } /** @@ -131,7 +135,7 @@ public Mono dropDataverse(String dataverseName, DropDataverseAnalyticsOpti */ @Stability.Uncommitted public Flux getAllDataverses() { - return toFlux(async::getAllDataverses); + return reactor.publishOnUserScheduler(toFlux(async::getAllDataverses)); } /** @@ -143,7 +147,7 @@ public Flux getAllDataverses() { */ @Stability.Uncommitted public Flux getAllDataverses(GetAllDataversesAnalyticsOptions options) { - return toFlux(() -> async.getAllDataverses(options)); + return reactor.publishOnUserScheduler(toFlux(() -> async.getAllDataverses(options))); } /** @@ -156,7 +160,7 @@ public Flux getAllDataverses(GetAllDataversesAnalyticsOption * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono createDataset(String datasetName, String bucketName) { - return toMono(() -> async.createDataset(datasetName, bucketName)); + return reactor.publishOnUserScheduler(() -> async.createDataset(datasetName, bucketName)); } /** @@ -170,7 +174,7 @@ public Mono createDataset(String datasetName, String bucketName) { * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono createDataset(String datasetName, String bucketName, CreateDatasetAnalyticsOptions options) { - return toMono(() -> async.createDataset(datasetName, bucketName, options)); + return reactor.publishOnUserScheduler(() -> async.createDataset(datasetName, bucketName, options)); } /** @@ -182,7 +186,7 @@ public Mono createDataset(String datasetName, String bucketName, CreateDat * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono dropDataset(String datasetName) { - return toMono(() -> async.dropDataset(datasetName)); + return reactor.publishOnUserScheduler(() -> async.dropDataset(datasetName)); } /** @@ -195,7 +199,7 @@ public Mono dropDataset(String datasetName) { * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono dropDataset(String datasetName, DropDatasetAnalyticsOptions options) { - return toMono(() -> async.dropDataset(datasetName, options)); + return reactor.publishOnUserScheduler(() -> async.dropDataset(datasetName, options)); } /** @@ -205,7 +209,7 @@ public Mono dropDataset(String datasetName, DropDatasetAnalyticsOptions op * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Flux getAllDatasets() { - return toFlux(async::getAllDatasets); + return reactor.publishOnUserScheduler(toFlux(async::getAllDatasets)); } /** @@ -216,7 +220,7 @@ public Flux getAllDatasets() { * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Flux getAllDatasets(GetAllDatasetsAnalyticsOptions options) { - return toFlux(() -> async.getAllDatasets(options)); + return reactor.publishOnUserScheduler(toFlux(() -> async.getAllDatasets(options))); } /** @@ -232,7 +236,7 @@ public Flux getAllDatasets(GetAllDatasetsAnalyticsOptions opti * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono createIndex(String indexName, String datasetName, Map fields) { - return toMono(() -> async.createIndex(indexName, datasetName, fields)); + return reactor.publishOnUserScheduler(() -> async.createIndex(indexName, datasetName, fields)); } /** @@ -249,7 +253,7 @@ public Mono createIndex(String indexName, String datasetName, Map createIndex(String indexName, String datasetName, Map fields, CreateIndexAnalyticsOptions options) { - return toMono(() -> async.createIndex(indexName, datasetName, fields, options)); + return reactor.publishOnUserScheduler(() -> async.createIndex(indexName, datasetName, fields, options)); } /** @@ -264,7 +268,7 @@ public Mono createIndex(String indexName, String datasetName, Map dropIndex(String indexName, String datasetName) { - return toMono(() -> async.dropIndex(indexName, datasetName)); + return reactor.publishOnUserScheduler(() -> async.dropIndex(indexName, datasetName)); } /** @@ -280,7 +284,7 @@ public Mono dropIndex(String indexName, String datasetName) { * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono dropIndex(String indexName, String datasetName, DropIndexAnalyticsOptions options) { - return toMono(() -> async.dropIndex(indexName, datasetName, options)); + return reactor.publishOnUserScheduler(() -> async.dropIndex(indexName, datasetName, options)); } /** @@ -290,7 +294,7 @@ public Mono dropIndex(String indexName, String datasetName, DropIndexAnaly * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Flux getAllIndexes() { - return toFlux(async::getAllIndexes); + return reactor.publishOnUserScheduler(toFlux(async::getAllIndexes)); } /** @@ -301,7 +305,7 @@ public Flux getAllIndexes() { * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Flux getAllIndexes(GetAllIndexesAnalyticsOptions options) { - return toFlux(() -> async.getAllIndexes(options)); + return reactor.publishOnUserScheduler(toFlux(() -> async.getAllIndexes(options))); } /** @@ -311,7 +315,7 @@ public Flux getAllIndexes(GetAllIndexesAnalyticsOptions options) * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono connectLink() { - return toMono(async::connectLink); + return reactor.publishOnUserScheduler(async::connectLink); } /** @@ -324,7 +328,7 @@ public Mono connectLink() { * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono connectLink(ConnectLinkAnalyticsOptions options) { - return toMono(() -> async.connectLink(options)); + return reactor.publishOnUserScheduler(() -> async.connectLink(options)); } /** @@ -334,7 +338,7 @@ public Mono connectLink(ConnectLinkAnalyticsOptions options) { * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono disconnectLink() { - return toMono(async::disconnectLink); + return reactor.publishOnUserScheduler(async::disconnectLink); } /** @@ -347,7 +351,7 @@ public Mono disconnectLink() { * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono disconnectLink(DisconnectLinkAnalyticsOptions options) { - return toMono(() -> async.disconnectLink(options)); + return reactor.publishOnUserScheduler(() -> async.disconnectLink(options)); } /** @@ -357,7 +361,7 @@ public Mono disconnectLink(DisconnectLinkAnalyticsOptions options) { * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono>> getPendingMutations() { - return toMono(async::getPendingMutations); + return reactor.publishOnUserScheduler(async::getPendingMutations); } /** @@ -368,7 +372,7 @@ public Mono>> getPendingMutations() { * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono>> getPendingMutations(final GetPendingMutationsAnalyticsOptions options) { - return toMono(() -> async.getPendingMutations(options)); + return reactor.publishOnUserScheduler(() -> async.getPendingMutations(options)); } /** @@ -382,7 +386,7 @@ public Mono>> getPendingMutations(final GetPending * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono createLink(AnalyticsLink link) { - return toMono(() -> async.createLink(link)); + return reactor.publishOnUserScheduler(() -> async.createLink(link)); } /** @@ -397,7 +401,7 @@ public Mono createLink(AnalyticsLink link) { * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono createLink(AnalyticsLink link, CreateLinkAnalyticsOptions options) { - return toMono(() -> async.createLink(link, options)); + return reactor.publishOnUserScheduler(() -> async.createLink(link, options)); } /** @@ -411,7 +415,7 @@ public Mono createLink(AnalyticsLink link, CreateLinkAnalyticsOptions opti * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono replaceLink(AnalyticsLink link) { - return toMono(() -> async.replaceLink(link)); + return reactor.publishOnUserScheduler(() -> async.replaceLink(link)); } /** @@ -426,7 +430,7 @@ public Mono replaceLink(AnalyticsLink link) { * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono replaceLink(AnalyticsLink link, ReplaceLinkAnalyticsOptions options) { - return toMono(() -> async.replaceLink(link, options)); + return reactor.publishOnUserScheduler(() -> async.replaceLink(link, options)); } /** @@ -440,7 +444,7 @@ public Mono replaceLink(AnalyticsLink link, ReplaceLinkAnalyticsOptions op * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono dropLink(String linkName, String dataverse) { - return toMono(() -> async.dropLink(linkName, dataverse)); + return reactor.publishOnUserScheduler(() -> async.dropLink(linkName, dataverse)); } /** @@ -455,7 +459,7 @@ public Mono dropLink(String linkName, String dataverse) { * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono dropLink(String linkName, String dataverse, DropLinkAnalyticsOptions options) { - return toMono(() -> async.dropLink(linkName, dataverse, options)); + return reactor.publishOnUserScheduler(() -> async.dropLink(linkName, dataverse, options)); } /** @@ -472,7 +476,7 @@ public Mono dropLink(String linkName, String dataverse, DropLinkAnalyticsO * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Flux getLinks() { - return toFlux(async::getLinks); + return reactor.publishOnUserScheduler(toFlux(async::getLinks)); } /** @@ -490,7 +494,7 @@ public Flux getLinks() { * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Flux getLinks(GetLinksAnalyticsOptions options) { - return toFlux(() -> async.getLinks(options)); + return reactor.publishOnUserScheduler(toFlux(() -> async.getLinks(options))); } } diff --git a/java-client/src/main/java/com/couchbase/client/java/manager/bucket/BucketManager.java b/java-client/src/main/java/com/couchbase/client/java/manager/bucket/BucketManager.java index d7daf8de2..bb4d58868 100644 --- a/java-client/src/main/java/com/couchbase/client/java/manager/bucket/BucketManager.java +++ b/java-client/src/main/java/com/couchbase/client/java/manager/bucket/BucketManager.java @@ -21,6 +21,7 @@ import com.couchbase.client.core.error.BucketNotFlushableException; import com.couchbase.client.core.error.BucketNotFoundException; import com.couchbase.client.core.error.CouchbaseException; +import com.couchbase.client.core.util.ReactorOps; import com.couchbase.client.java.Cluster; import java.util.Map; @@ -56,9 +57,9 @@ public class BucketManager { * @param async the underlying async manager that performs the ops. */ @Stability.Internal - public BucketManager(final AsyncBucketManager async) { + public BucketManager(final ReactorOps reactor, final AsyncBucketManager async) { this.async = requireNonNull(async); - this.reactive = new ReactiveBucketManager(async); + this.reactive = new ReactiveBucketManager(reactor, async); } /** diff --git a/java-client/src/main/java/com/couchbase/client/java/manager/bucket/ReactiveBucketManager.java b/java-client/src/main/java/com/couchbase/client/java/manager/bucket/ReactiveBucketManager.java index bec47388b..a56895fad 100644 --- a/java-client/src/main/java/com/couchbase/client/java/manager/bucket/ReactiveBucketManager.java +++ b/java-client/src/main/java/com/couchbase/client/java/manager/bucket/ReactiveBucketManager.java @@ -21,12 +21,12 @@ import com.couchbase.client.core.error.BucketNotFlushableException; import com.couchbase.client.core.error.BucketNotFoundException; import com.couchbase.client.core.error.CouchbaseException; +import com.couchbase.client.core.util.ReactorOps; import com.couchbase.client.java.ReactiveCluster; import reactor.core.publisher.Mono; import java.util.Map; -import static com.couchbase.client.core.Reactor.toMono; import static java.util.Objects.requireNonNull; /** @@ -42,6 +42,7 @@ public class ReactiveBucketManager { * Holds the underlying async bucket manager. */ private final AsyncBucketManager async; + private final ReactorOps reactor; /** * Creates a new {@link ReactiveBucketManager}. @@ -52,7 +53,8 @@ public class ReactiveBucketManager { * @param async the underlying async manager that performs the ops. */ @Stability.Internal - public ReactiveBucketManager(final AsyncBucketManager async) { + public ReactiveBucketManager(final ReactorOps reactor, final AsyncBucketManager async) { + this.reactor = requireNonNull(reactor); this.async = requireNonNull(async); } @@ -76,7 +78,7 @@ public AsyncBucketManager async() { * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono createBucket(final BucketSettings settings) { - return toMono(() -> async.createBucket(settings)); + return reactor.publishOnUserScheduler(() -> async.createBucket(settings)); } /** @@ -93,7 +95,7 @@ public Mono createBucket(final BucketSettings settings) { * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono createBucket(final BucketSettings settings, final CreateBucketOptions options) { - return toMono(() -> async.createBucket(settings, options)); + return reactor.publishOnUserScheduler(() -> async.createBucket(settings, options)); } /** @@ -119,7 +121,7 @@ public Mono createBucket(final BucketSettings settings, final CreateBucket * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono updateBucket(final BucketSettings settings) { - return toMono(() -> async.updateBucket(settings)); + return reactor.publishOnUserScheduler(() -> async.updateBucket(settings)); } /** @@ -146,7 +148,7 @@ public Mono updateBucket(final BucketSettings settings) { * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono updateBucket(final BucketSettings settings, final UpdateBucketOptions options) { - return toMono(() -> async.updateBucket(settings, options)); + return reactor.publishOnUserScheduler(() -> async.updateBucket(settings, options)); } /** @@ -158,7 +160,7 @@ public Mono updateBucket(final BucketSettings settings, final UpdateBucket * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono dropBucket(final String bucketName) { - return toMono(() -> async.dropBucket(bucketName)); + return reactor.publishOnUserScheduler(() -> async.dropBucket(bucketName)); } /** @@ -171,7 +173,7 @@ public Mono dropBucket(final String bucketName) { * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono dropBucket(final String bucketName, final DropBucketOptions options) { - return toMono(() -> async.dropBucket(bucketName, options)); + return reactor.publishOnUserScheduler(() -> async.dropBucket(bucketName, options)); } /** @@ -183,7 +185,7 @@ public Mono dropBucket(final String bucketName, final DropBucketOptions op * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono getBucket(final String bucketName) { - return toMono(() -> async.getBucket(bucketName)); + return reactor.publishOnUserScheduler(() -> async.getBucket(bucketName)); } /** @@ -196,7 +198,7 @@ public Mono getBucket(final String bucketName) { * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono getBucket(final String bucketName, final GetBucketOptions options) { - return toMono(() -> async.getBucket(bucketName, options)); + return reactor.publishOnUserScheduler(() -> async.getBucket(bucketName, options)); } /** @@ -206,7 +208,7 @@ public Mono getBucket(final String bucketName, final GetBucketOp * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono> getAllBuckets() { - return toMono(async::getAllBuckets); + return reactor.publishOnUserScheduler(async::getAllBuckets); } /** @@ -217,7 +219,7 @@ public Mono> getAllBuckets() { * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono> getAllBuckets(final GetAllBucketOptions options) { - return toMono(() -> async.getAllBuckets(options)); + return reactor.publishOnUserScheduler(() -> async.getAllBuckets(options)); } /** @@ -237,7 +239,7 @@ public Mono> getAllBuckets(final GetAllBucketOptions * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono flushBucket(final String bucketName) { - return toMono(() -> async.flushBucket(bucketName)); + return reactor.publishOnUserScheduler(() -> async.flushBucket(bucketName)); } /** @@ -258,7 +260,7 @@ public Mono flushBucket(final String bucketName) { * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono flushBucket(final String bucketName, final FlushBucketOptions options) { - return toMono(() -> async.flushBucket(bucketName, options)); + return reactor.publishOnUserScheduler(() -> async.flushBucket(bucketName, options)); } } diff --git a/java-client/src/main/java/com/couchbase/client/java/manager/collection/ReactiveCollectionManager.java b/java-client/src/main/java/com/couchbase/client/java/manager/collection/ReactiveCollectionManager.java index 6f19827ad..242d9891d 100644 --- a/java-client/src/main/java/com/couchbase/client/java/manager/collection/ReactiveCollectionManager.java +++ b/java-client/src/main/java/com/couchbase/client/java/manager/collection/ReactiveCollectionManager.java @@ -22,19 +22,20 @@ import com.couchbase.client.core.error.CouchbaseException; import com.couchbase.client.core.error.ScopeExistsException; import com.couchbase.client.core.error.ScopeNotFoundException; +import com.couchbase.client.core.util.ReactorOps; import com.couchbase.client.java.ReactiveBucket; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.util.concurrent.CompletableFuture; -import static com.couchbase.client.core.Reactor.toMono; import static com.couchbase.client.java.manager.collection.CreateCollectionOptions.createCollectionOptions; import static com.couchbase.client.java.manager.collection.CreateScopeOptions.createScopeOptions; import static com.couchbase.client.java.manager.collection.DropCollectionOptions.dropCollectionOptions; import static com.couchbase.client.java.manager.collection.DropScopeOptions.dropScopeOptions; import static com.couchbase.client.java.manager.collection.GetAllScopesOptions.getAllScopesOptions; import static com.couchbase.client.java.manager.collection.GetScopeOptions.getScopeOptions; +import static java.util.Objects.requireNonNull; /** * The {@link ReactiveCollectionManager} provides APIs to manage collections and scopes within a bucket. @@ -46,6 +47,7 @@ public class ReactiveCollectionManager { * The underlying async collection manager. */ private final AsyncCollectionManager async; + private final ReactorOps reactor; /** * Creates a new {@link ReactiveCollectionManager}. @@ -56,8 +58,9 @@ public class ReactiveCollectionManager { * @param async the underlying async collection manager. */ @Stability.Internal - public ReactiveCollectionManager(final AsyncCollectionManager async) { - this.async = async; + public ReactiveCollectionManager(final ReactorOps reactor, final AsyncCollectionManager async) { + this.reactor = requireNonNull(reactor); + this.async = requireNonNull(async); } /** @@ -94,7 +97,7 @@ public Mono createCollection(final CollectionSpec collectionSpec) { */ @Deprecated public Mono createCollection(final CollectionSpec collectionSpec, final CreateCollectionOptions options) { - return toMono(() -> async.createCollection(collectionSpec, options)); + return reactor.publishOnUserScheduler(() -> async.createCollection(collectionSpec, options)); } /** @@ -112,7 +115,7 @@ public Mono createCollection(final CollectionSpec collectionSpec, final Cr */ @Stability.Volatile public Mono createCollection(final String scopeName, final String collectionName, final CreateCollectionSettings settings) { - return toMono(() -> async.createCollection(scopeName, collectionName, settings)); + return reactor.publishOnUserScheduler(() -> async.createCollection(scopeName, collectionName, settings)); } /** @@ -131,7 +134,7 @@ public Mono createCollection(final String scopeName, final String collecti */ @Stability.Volatile public Mono createCollection(final String scopeName, final String collectionName, final CreateCollectionSettings settings, final CreateCollectionOptions options) { - return toMono(() -> async.createCollection(scopeName, collectionName, settings, options)); + return reactor.publishOnUserScheduler(() -> async.createCollection(scopeName, collectionName, settings, options)); } /** @@ -156,7 +159,7 @@ public Mono createScope(final String scopeName) { * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono createScope(final String scopeName, final CreateScopeOptions options) { - return toMono(() -> async.createScope(scopeName, options)); + return reactor.publishOnUserScheduler(() -> async.createScope(scopeName, options)); } /** @@ -172,7 +175,7 @@ public Mono createScope(final String scopeName, final CreateScopeOptions o */ @Stability.Volatile public Mono updateCollection(String scopeName, String collectionName, UpdateCollectionSettings settings) { - return toMono(() -> async.updateCollection(scopeName, collectionName, settings)); + return reactor.publishOnUserScheduler(() -> async.updateCollection(scopeName, collectionName, settings)); } /** @@ -189,7 +192,7 @@ public Mono updateCollection(String scopeName, String collectionName, Upda */ @Stability.Volatile public Mono updateCollection(String scopeName, String collectionName, UpdateCollectionSettings settings, UpdateCollectionOptions options) { - return toMono(() -> async.updateCollection(scopeName, collectionName, settings, options)); + return reactor.publishOnUserScheduler(() -> async.updateCollection(scopeName, collectionName, settings, options)); } /** @@ -220,7 +223,7 @@ public Mono dropCollection(final CollectionSpec collectionSpec) { */ @Deprecated public Mono dropCollection(final CollectionSpec collectionSpec, final DropCollectionOptions options) { - return toMono(() -> async.dropCollection(collectionSpec, options)); + return reactor.publishOnUserScheduler(() -> async.dropCollection(collectionSpec, options)); } /** @@ -251,7 +254,7 @@ public Mono dropCollection(final String scopeName, final String collection */ @Stability.Volatile public Mono dropCollection(final String scopeName, final String collectionName, final DropCollectionOptions options) { - return toMono(() -> async.dropCollection(scopeName, collectionName, options)); + return reactor.publishOnUserScheduler(() -> async.dropCollection(scopeName, collectionName, options)); } /** @@ -276,7 +279,7 @@ public Mono dropScope(final String scopeName) { * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono dropScope(final String scopeName, final DropScopeOptions options) { - return toMono(() -> async.dropScope(scopeName, options)); + return reactor.publishOnUserScheduler(() -> async.dropScope(scopeName, options)); } /** @@ -305,7 +308,7 @@ public Mono getScope(final String scopeName) { */ @Deprecated public Mono getScope(final String scopeName, final GetScopeOptions options) { - return toMono(() -> async.getScope(scopeName, options)); + return reactor.publishOnUserScheduler(() -> async.getScope(scopeName, options)); } /** @@ -326,7 +329,7 @@ public Flux getAllScopes() { * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Flux getAllScopes(final GetAllScopesOptions options) { - return toMono(() -> async.getAllScopes(options)).flatMapMany(Flux::fromIterable); + return reactor.publishOnUserScheduler(() -> async.getAllScopes(options)).flatMapMany(Flux::fromIterable); } } diff --git a/java-client/src/main/java/com/couchbase/client/java/manager/eventing/ReactiveEventingFunctionManager.java b/java-client/src/main/java/com/couchbase/client/java/manager/eventing/ReactiveEventingFunctionManager.java index c3d44e459..6439c4a34 100644 --- a/java-client/src/main/java/com/couchbase/client/java/manager/eventing/ReactiveEventingFunctionManager.java +++ b/java-client/src/main/java/com/couchbase/client/java/manager/eventing/ReactiveEventingFunctionManager.java @@ -16,7 +16,6 @@ package com.couchbase.client.java.manager.eventing; -import com.couchbase.client.core.Reactor; import com.couchbase.client.core.annotation.Stability; import com.couchbase.client.core.error.BucketNotFoundException; import com.couchbase.client.core.error.CollectionNotFoundException; @@ -27,11 +26,13 @@ import com.couchbase.client.core.error.EventingFunctionNotBootstrappedException; import com.couchbase.client.core.error.EventingFunctionNotDeployedException; import com.couchbase.client.core.error.EventingFunctionNotFoundException; +import com.couchbase.client.core.util.ReactorOps; import com.couchbase.client.java.ReactiveCluster; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import static com.couchbase.client.java.manager.eventing.GetAllFunctionsOptions.getAllFunctionsOptions; +import static java.util.Objects.requireNonNull; /** * Performs management operations on {@link EventingFunction EventingFunctions}. @@ -43,6 +44,7 @@ public class ReactiveEventingFunctionManager { * The underlying async function manager which performs the actual ops and does the conversions. */ private final AsyncEventingFunctionManager asyncManager; + private final ReactorOps reactor; /** * Creates a new {@link ReactiveEventingFunctionManager}. @@ -53,8 +55,9 @@ public class ReactiveEventingFunctionManager { * @param asyncManager the underlying async manager that performs the ops. */ @Stability.Internal - public ReactiveEventingFunctionManager(AsyncEventingFunctionManager asyncManager) { - this.asyncManager = asyncManager; + public ReactiveEventingFunctionManager(ReactorOps reactor, AsyncEventingFunctionManager asyncManager) { + this.reactor = requireNonNull(reactor); + this.asyncManager = requireNonNull(asyncManager); } /** @@ -99,7 +102,7 @@ public Mono upsertFunction(final EventingFunction function) { * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono upsertFunction(final EventingFunction function, final UpsertFunctionOptions options) { - return Reactor.toMono(() -> asyncManager.upsertFunction(function, options)); + return reactor.publishOnUserScheduler(() -> asyncManager.upsertFunction(function, options)); } /** @@ -124,7 +127,7 @@ public Mono getFunction(final String name) { * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono getFunction(final String name, final GetFunctionOptions options) { - return Reactor.toMono(() -> asyncManager.getFunction(name, options)); + return reactor.publishOnUserScheduler(() -> asyncManager.getFunction(name, options)); } /** @@ -149,7 +152,7 @@ public Flux getAllFunctions() { * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Flux getAllFunctions(final GetAllFunctionsOptions options) { - return Reactor.toMono(() -> asyncManager.getAllFunctions(options)).flatMapMany(Flux::fromIterable); + return reactor.publishOnUserScheduler(() -> asyncManager.getAllFunctions(options)).flatMapMany(Flux::fromIterable); } /** @@ -186,7 +189,7 @@ public Mono dropFunction(final String name) { * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono dropFunction(final String name, final DropFunctionOptions options) { - return Reactor.toMono(() -> asyncManager.dropFunction(name, options)); + return reactor.publishOnUserScheduler(() -> asyncManager.dropFunction(name, options)); } /** @@ -219,7 +222,7 @@ public Mono deployFunction(final String name) { * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono deployFunction(final String name, final DeployFunctionOptions options) { - return Reactor.toMono(() -> asyncManager.deployFunction(name, options)); + return reactor.publishOnUserScheduler(() -> asyncManager.deployFunction(name, options)); } /** @@ -260,7 +263,7 @@ public Mono undeployFunction(final String name) { * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono undeployFunction(final String name, final UndeployFunctionOptions options) { - return Reactor.toMono(() -> asyncManager.undeployFunction(name, options)); + return reactor.publishOnUserScheduler(() -> asyncManager.undeployFunction(name, options)); } /** @@ -293,7 +296,7 @@ public Mono pauseFunction(final String name) { * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono pauseFunction(final String name, final PauseFunctionOptions options) { - return Reactor.toMono(() -> asyncManager.pauseFunction(name, options)); + return reactor.publishOnUserScheduler(() -> asyncManager.pauseFunction(name, options)); } /** @@ -334,7 +337,7 @@ public Mono resumeFunction(final String name) { * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono resumeFunction(final String name, final ResumeFunctionOptions options) { - return Reactor.toMono(() -> asyncManager.resumeFunction(name, options)); + return reactor.publishOnUserScheduler(() -> asyncManager.resumeFunction(name, options)); } /** @@ -355,7 +358,7 @@ public Mono functionsStatus() { * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono functionsStatus(final FunctionsStatusOptions options) { - return Reactor.toMono(() -> asyncManager.functionsStatus(options)); + return reactor.publishOnUserScheduler(() -> asyncManager.functionsStatus(options)); } } diff --git a/java-client/src/main/java/com/couchbase/client/java/manager/eventing/ReactiveScopeEventingFunctionManager.java b/java-client/src/main/java/com/couchbase/client/java/manager/eventing/ReactiveScopeEventingFunctionManager.java index adfc446b8..32cb9cafe 100644 --- a/java-client/src/main/java/com/couchbase/client/java/manager/eventing/ReactiveScopeEventingFunctionManager.java +++ b/java-client/src/main/java/com/couchbase/client/java/manager/eventing/ReactiveScopeEventingFunctionManager.java @@ -16,7 +16,6 @@ package com.couchbase.client.java.manager.eventing; -import com.couchbase.client.core.Reactor; import com.couchbase.client.core.annotation.Stability; import com.couchbase.client.core.error.BucketNotFoundException; import com.couchbase.client.core.error.CollectionNotFoundException; @@ -27,11 +26,13 @@ import com.couchbase.client.core.error.EventingFunctionNotBootstrappedException; import com.couchbase.client.core.error.EventingFunctionNotDeployedException; import com.couchbase.client.core.error.EventingFunctionNotFoundException; +import com.couchbase.client.core.util.ReactorOps; import com.couchbase.client.java.ReactiveCluster; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import static com.couchbase.client.java.manager.eventing.GetAllFunctionsOptions.getAllFunctionsOptions; +import static java.util.Objects.requireNonNull; /** * Performs management operations on {@link EventingFunction EventingFunctions}. @@ -43,6 +44,7 @@ public class ReactiveScopeEventingFunctionManager { * The underlying async function manager which performs the actual ops and does the conversions. */ private final AsyncScopeEventingFunctionManager asyncManager; + private final ReactorOps reactor; /** * Creates a new {@link ReactiveScopeEventingFunctionManager}. @@ -53,8 +55,9 @@ public class ReactiveScopeEventingFunctionManager { * @param asyncManager the underlying async manager that performs the ops. */ @Stability.Internal - public ReactiveScopeEventingFunctionManager(AsyncScopeEventingFunctionManager asyncManager) { - this.asyncManager = asyncManager; + public ReactiveScopeEventingFunctionManager(ReactorOps reactor, AsyncScopeEventingFunctionManager asyncManager) { + this.reactor = requireNonNull(reactor); + this.asyncManager = requireNonNull(asyncManager); } /** @@ -99,7 +102,7 @@ public Mono upsertFunction(final EventingFunction function) { * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono upsertFunction(final EventingFunction function, final UpsertFunctionOptions options) { - return Reactor.toMono(() -> asyncManager.upsertFunction(function, options)); + return reactor.publishOnUserScheduler(() -> asyncManager.upsertFunction(function, options)); } /** @@ -124,7 +127,7 @@ public Mono getFunction(final String name) { * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono getFunction(final String name, final GetFunctionOptions options) { - return Reactor.toMono(() -> asyncManager.getFunction(name, options)); + return reactor.publishOnUserScheduler(() -> asyncManager.getFunction(name, options)); } /** @@ -149,7 +152,7 @@ public Flux getAllFunctions() { * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Flux getAllFunctions(final GetAllFunctionsOptions options) { - return Reactor.toMono(() -> asyncManager.getAllFunctions(options)).flatMapMany(Flux::fromIterable); + return reactor.publishOnUserScheduler(() -> asyncManager.getAllFunctions(options)).flatMapMany(Flux::fromIterable); } /** @@ -186,7 +189,7 @@ public Mono dropFunction(final String name) { * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono dropFunction(final String name, final DropFunctionOptions options) { - return Reactor.toMono(() -> asyncManager.dropFunction(name, options)); + return reactor.publishOnUserScheduler(() -> asyncManager.dropFunction(name, options)); } /** @@ -219,7 +222,7 @@ public Mono deployFunction(final String name) { * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono deployFunction(final String name, final DeployFunctionOptions options) { - return Reactor.toMono(() -> asyncManager.deployFunction(name, options)); + return reactor.publishOnUserScheduler(() -> asyncManager.deployFunction(name, options)); } /** @@ -260,7 +263,7 @@ public Mono undeployFunction(final String name) { * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono undeployFunction(final String name, final UndeployFunctionOptions options) { - return Reactor.toMono(() -> asyncManager.undeployFunction(name, options)); + return reactor.publishOnUserScheduler(() -> asyncManager.undeployFunction(name, options)); } /** @@ -293,7 +296,7 @@ public Mono pauseFunction(final String name) { * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono pauseFunction(final String name, final PauseFunctionOptions options) { - return Reactor.toMono(() -> asyncManager.pauseFunction(name, options)); + return reactor.publishOnUserScheduler(() -> asyncManager.pauseFunction(name, options)); } /** @@ -334,7 +337,7 @@ public Mono resumeFunction(final String name) { * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono resumeFunction(final String name, final ResumeFunctionOptions options) { - return Reactor.toMono(() -> asyncManager.resumeFunction(name, options)); + return reactor.publishOnUserScheduler(() -> asyncManager.resumeFunction(name, options)); } /** @@ -355,7 +358,7 @@ public Mono functionsStatus() { * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono functionsStatus(final FunctionsStatusOptions options) { - return Reactor.toMono(() -> asyncManager.functionsStatus(options)); + return reactor.publishOnUserScheduler(() -> asyncManager.functionsStatus(options)); } } diff --git a/java-client/src/main/java/com/couchbase/client/java/manager/query/QueryIndexManager.java b/java-client/src/main/java/com/couchbase/client/java/manager/query/QueryIndexManager.java index c5e1c5cbc..86d59ebc3 100644 --- a/java-client/src/main/java/com/couchbase/client/java/manager/query/QueryIndexManager.java +++ b/java-client/src/main/java/com/couchbase/client/java/manager/query/QueryIndexManager.java @@ -21,6 +21,7 @@ import com.couchbase.client.core.error.IndexExistsException; import com.couchbase.client.core.error.IndexFailureException; import com.couchbase.client.core.error.IndexNotFoundException; +import com.couchbase.client.core.util.ReactorOps; import com.couchbase.client.java.Cluster; import java.time.Duration; @@ -54,9 +55,9 @@ public class QueryIndexManager { * @param async the async index manager. */ @Stability.Internal - public QueryIndexManager(final AsyncQueryIndexManager async) { + public QueryIndexManager(final ReactorOps reactor, final AsyncQueryIndexManager async) { this.async = requireNonNull(async); - this.reactive = new ReactiveQueryIndexManager(async); + this.reactive = new ReactiveQueryIndexManager(reactor, async); } /** diff --git a/java-client/src/main/java/com/couchbase/client/java/manager/query/ReactiveCollectionQueryIndexManager.java b/java-client/src/main/java/com/couchbase/client/java/manager/query/ReactiveCollectionQueryIndexManager.java index 2e646a813..f17a7d64b 100644 --- a/java-client/src/main/java/com/couchbase/client/java/manager/query/ReactiveCollectionQueryIndexManager.java +++ b/java-client/src/main/java/com/couchbase/client/java/manager/query/ReactiveCollectionQueryIndexManager.java @@ -21,6 +21,7 @@ import com.couchbase.client.core.error.IndexExistsException; import com.couchbase.client.core.error.IndexFailureException; import com.couchbase.client.core.error.IndexNotFoundException; +import com.couchbase.client.core.util.ReactorOps; import com.couchbase.client.java.ReactiveCluster; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -29,7 +30,6 @@ import java.util.Collection; import static com.couchbase.client.core.Reactor.toFlux; -import static com.couchbase.client.core.Reactor.toMono; import static java.util.Objects.requireNonNull; /** @@ -42,6 +42,7 @@ public class ReactiveCollectionQueryIndexManager { * The underlying async query index manager which performs the actual ops and does the conversions. */ private final AsyncCollectionQueryIndexManager async; + private final ReactorOps reactor; /** * Creates a new {@link ReactiveCollectionQueryIndexManager}. @@ -52,7 +53,8 @@ public class ReactiveCollectionQueryIndexManager { * @param async the async index manager. */ @Stability.Internal - public ReactiveCollectionQueryIndexManager(final AsyncCollectionQueryIndexManager async) { + public ReactiveCollectionQueryIndexManager(final ReactorOps reactor, final AsyncCollectionQueryIndexManager async) { + this.reactor = requireNonNull(reactor); this.async = requireNonNull(async); } @@ -66,7 +68,7 @@ public ReactiveCollectionQueryIndexManager(final AsyncCollectionQueryIndexManage * @throws CouchbaseException if any other generic unhandled/unexpected errors. */ public Mono createIndex(final String indexName, final Collection fields) { - return toMono(() -> async.createIndex(indexName, fields)); + return reactor.publishOnUserScheduler(() -> async.createIndex(indexName, fields)); } /** @@ -81,7 +83,7 @@ public Mono createIndex(final String indexName, final Collection f */ public Mono createIndex(final String indexName, final Collection fields, final CreateQueryIndexOptions options) { - return toMono(() -> async.createIndex(indexName, fields, options)); + return reactor.publishOnUserScheduler(() -> async.createIndex(indexName, fields, options)); } /** @@ -92,7 +94,7 @@ public Mono createIndex(final String indexName, final Collection f * @throws CouchbaseException if any other generic unhandled/unexpected errors. */ public Mono createPrimaryIndex() { - return toMono(() -> async.createPrimaryIndex()); + return reactor.publishOnUserScheduler(() -> async.createPrimaryIndex()); } /** @@ -104,7 +106,7 @@ public Mono createPrimaryIndex() { * @throws CouchbaseException if any other generic unhandled/unexpected errors. */ public Mono createPrimaryIndex(final CreatePrimaryQueryIndexOptions options) { - return toMono(() -> async.createPrimaryIndex(options)); + return reactor.publishOnUserScheduler(() -> async.createPrimaryIndex(options)); } /** @@ -114,7 +116,7 @@ public Mono createPrimaryIndex(final CreatePrimaryQueryIndexOptions option * @throws CouchbaseException if any other generic unhandled/unexpected errors. */ public Flux getAllIndexes() { - return toFlux(() -> async.getAllIndexes()); + return reactor.publishOnUserScheduler(toFlux(() -> async.getAllIndexes())); } /** @@ -125,7 +127,7 @@ public Flux getAllIndexes() { * @throws CouchbaseException if any other generic unhandled/unexpected errors. */ public Flux getAllIndexes(final GetAllQueryIndexesOptions options) { - return toFlux(() -> async.getAllIndexes(options)); + return reactor.publishOnUserScheduler(toFlux(() -> async.getAllIndexes(options))); } /** @@ -136,7 +138,7 @@ public Flux getAllIndexes(final GetAllQueryIndexesOptions options) { * @throws CouchbaseException if any other generic unhandled/unexpected errors. */ public Mono dropPrimaryIndex() { - return toMono(() -> async.dropPrimaryIndex()); + return reactor.publishOnUserScheduler(() -> async.dropPrimaryIndex()); } /** @@ -147,7 +149,7 @@ public Mono dropPrimaryIndex() { * @throws CouchbaseException if any other generic unhandled/unexpected errors. */ public Mono dropPrimaryIndex(final DropPrimaryQueryIndexOptions options) { - return toMono(() -> async.dropPrimaryIndex(options)); + return reactor.publishOnUserScheduler(() -> async.dropPrimaryIndex(options)); } /** @@ -159,7 +161,7 @@ public Mono dropPrimaryIndex(final DropPrimaryQueryIndexOptions options) { * @throws CouchbaseException if any other generic unhandled/unexpected errors. */ public Mono dropIndex(final String indexName) { - return toMono(() -> async.dropIndex(indexName)); + return reactor.publishOnUserScheduler(() -> async.dropIndex(indexName)); } /** @@ -172,7 +174,7 @@ public Mono dropIndex(final String indexName) { * @throws CouchbaseException if any other generic unhandled/unexpected errors. */ public Mono dropIndex(final String indexName, final DropQueryIndexOptions options) { - return toMono(() -> async.dropIndex(indexName, options)); + return reactor.publishOnUserScheduler(() -> async.dropIndex(indexName, options)); } /** @@ -181,7 +183,7 @@ public Mono dropIndex(final String indexName, final DropQueryIndexOptions * @throws CouchbaseException if any other generic unhandled/unexpected errors. */ public Mono buildDeferredIndexes() { - return toMono(() -> async.buildDeferredIndexes()); + return reactor.publishOnUserScheduler(() -> async.buildDeferredIndexes()); } /** @@ -191,7 +193,7 @@ public Mono buildDeferredIndexes() { * @throws CouchbaseException if any other generic unhandled/unexpected errors. */ public Mono buildDeferredIndexes(final BuildQueryIndexOptions options) { - return toMono(() -> async.buildDeferredIndexes(options)); + return reactor.publishOnUserScheduler(() -> async.buildDeferredIndexes(options)); } /** @@ -203,7 +205,7 @@ public Mono buildDeferredIndexes(final BuildQueryIndexOptions options) { */ public Mono watchIndexes(final Collection indexNames, final Duration timeout) { - return toMono(() -> async.watchIndexes(indexNames, timeout)); + return reactor.publishOnUserScheduler(() -> async.watchIndexes(indexNames, timeout)); } /** @@ -216,6 +218,6 @@ public Mono watchIndexes(final Collection indexNames, */ public Mono watchIndexes(final Collection indexNames, final Duration timeout, final WatchQueryIndexesOptions options) { - return toMono(() -> async.watchIndexes(indexNames, timeout, options)); + return reactor.publishOnUserScheduler(() -> async.watchIndexes(indexNames, timeout, options)); } } diff --git a/java-client/src/main/java/com/couchbase/client/java/manager/query/ReactiveQueryIndexManager.java b/java-client/src/main/java/com/couchbase/client/java/manager/query/ReactiveQueryIndexManager.java index 7c67b4001..740544486 100644 --- a/java-client/src/main/java/com/couchbase/client/java/manager/query/ReactiveQueryIndexManager.java +++ b/java-client/src/main/java/com/couchbase/client/java/manager/query/ReactiveQueryIndexManager.java @@ -21,6 +21,7 @@ import com.couchbase.client.core.error.IndexExistsException; import com.couchbase.client.core.error.IndexFailureException; import com.couchbase.client.core.error.IndexNotFoundException; +import com.couchbase.client.core.util.ReactorOps; import com.couchbase.client.java.ReactiveCluster; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -29,7 +30,6 @@ import java.util.Collection; import static com.couchbase.client.core.Reactor.toFlux; -import static com.couchbase.client.core.Reactor.toMono; import static java.util.Objects.requireNonNull; /** @@ -41,6 +41,7 @@ public class ReactiveQueryIndexManager { * The underlying async query index manager which performs the actual ops and does the conversions. */ private final AsyncQueryIndexManager async; + private final ReactorOps reactor; /** * Creates a new {@link ReactiveQueryIndexManager}. @@ -51,7 +52,8 @@ public class ReactiveQueryIndexManager { * @param async the async index manager. */ @Stability.Internal - public ReactiveQueryIndexManager(final AsyncQueryIndexManager async) { + public ReactiveQueryIndexManager(final ReactorOps reactor, final AsyncQueryIndexManager async) { + this.reactor = requireNonNull(reactor); this.async = requireNonNull(async); } @@ -78,7 +80,7 @@ public AsyncQueryIndexManager async() { * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono createIndex(final String bucketName, final String indexName, final Collection fields) { - return toMono(() -> async.createIndex(bucketName, indexName, fields)); + return reactor.publishOnUserScheduler(() -> async.createIndex(bucketName, indexName, fields)); } /** @@ -99,7 +101,7 @@ public Mono createIndex(final String bucketName, final String indexName, f */ public Mono createIndex(final String bucketName, final String indexName, final Collection fields, final CreateQueryIndexOptions options) { - return toMono(() -> async.createIndex(bucketName, indexName, fields, options)); + return reactor.publishOnUserScheduler(() -> async.createIndex(bucketName, indexName, fields, options)); } /** @@ -116,7 +118,7 @@ public Mono createIndex(final String bucketName, final String indexName, f * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono createPrimaryIndex(final String bucketName) { - return toMono(() -> async.createPrimaryIndex(bucketName)); + return reactor.publishOnUserScheduler(() -> async.createPrimaryIndex(bucketName)); } /** @@ -134,7 +136,7 @@ public Mono createPrimaryIndex(final String bucketName) { * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono createPrimaryIndex(final String bucketName, final CreatePrimaryQueryIndexOptions options) { - return toMono(() -> async.createPrimaryIndex(bucketName, options)); + return reactor.publishOnUserScheduler(() -> async.createPrimaryIndex(bucketName, options)); } /** @@ -150,7 +152,7 @@ public Mono createPrimaryIndex(final String bucketName, final CreatePrimar * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Flux getAllIndexes(final String bucketName) { - return toFlux(() -> async.getAllIndexes(bucketName)); + return reactor.publishOnUserScheduler(toFlux(() -> async.getAllIndexes(bucketName))); } /** @@ -167,7 +169,7 @@ public Flux getAllIndexes(final String bucketName) { * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Flux getAllIndexes(final String bucketName, final GetAllQueryIndexesOptions options) { - return toFlux(() -> async.getAllIndexes(bucketName, options)); + return reactor.publishOnUserScheduler(toFlux(() -> async.getAllIndexes(bucketName, options))); } /** @@ -184,7 +186,7 @@ public Flux getAllIndexes(final String bucketName, final GetAllQuery * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono dropPrimaryIndex(final String bucketName) { - return toMono(() -> async.dropPrimaryIndex(bucketName)); + return reactor.publishOnUserScheduler(() -> async.dropPrimaryIndex(bucketName)); } /** @@ -202,7 +204,7 @@ public Mono dropPrimaryIndex(final String bucketName) { * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono dropPrimaryIndex(final String bucketName, final DropPrimaryQueryIndexOptions options) { - return toMono(() -> async.dropPrimaryIndex(bucketName, options)); + return reactor.publishOnUserScheduler(() -> async.dropPrimaryIndex(bucketName, options)); } /** @@ -220,7 +222,7 @@ public Mono dropPrimaryIndex(final String bucketName, final DropPrimaryQue * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono dropIndex(final String bucketName, final String indexName) { - return toMono(() -> async.dropIndex(bucketName, indexName)); + return reactor.publishOnUserScheduler(() -> async.dropIndex(bucketName, indexName)); } /** @@ -239,7 +241,7 @@ public Mono dropIndex(final String bucketName, final String indexName) { * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono dropIndex(final String bucketName, final String indexName, final DropQueryIndexOptions options) { - return toMono(() -> async.dropIndex(bucketName, indexName, options)); + return reactor.publishOnUserScheduler(() -> async.dropIndex(bucketName, indexName, options)); } /** @@ -252,7 +254,7 @@ public Mono dropIndex(final String bucketName, final String indexName, fin * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono buildDeferredIndexes(final String bucketName) { - return toMono(() -> async.buildDeferredIndexes(bucketName)); + return reactor.publishOnUserScheduler(() -> async.buildDeferredIndexes(bucketName)); } /** @@ -269,7 +271,7 @@ public Mono buildDeferredIndexes(final String bucketName) { * @throws CouchbaseException (async) if any other generic unhandled/unexpected errors. */ public Mono buildDeferredIndexes(final String bucketName, final BuildQueryIndexOptions options) { - return toMono(() -> async.buildDeferredIndexes(bucketName, options)); + return reactor.publishOnUserScheduler(() -> async.buildDeferredIndexes(bucketName, options)); } /** @@ -287,7 +289,7 @@ public Mono buildDeferredIndexes(final String bucketName, final BuildQuery */ public Mono watchIndexes(final String bucketName, final Collection indexNames, final Duration timeout) { - return toMono(() -> async.watchIndexes(bucketName, indexNames, timeout)); + return reactor.publishOnUserScheduler(() -> async.watchIndexes(bucketName, indexNames, timeout)); } /** @@ -306,7 +308,7 @@ public Mono watchIndexes(final String bucketName, final Collection */ public Mono watchIndexes(final String bucketName, final Collection indexNames, final Duration timeout, final WatchQueryIndexesOptions options) { - return toMono(() -> async.watchIndexes(bucketName, indexNames, timeout, options)); + return reactor.publishOnUserScheduler(() -> async.watchIndexes(bucketName, indexNames, timeout, options)); } } diff --git a/java-client/src/main/java/com/couchbase/client/java/manager/search/ReactiveSearchIndexManager.java b/java-client/src/main/java/com/couchbase/client/java/manager/search/ReactiveSearchIndexManager.java index bf1a09748..4abb07b7c 100644 --- a/java-client/src/main/java/com/couchbase/client/java/manager/search/ReactiveSearchIndexManager.java +++ b/java-client/src/main/java/com/couchbase/client/java/manager/search/ReactiveSearchIndexManager.java @@ -16,11 +16,12 @@ package com.couchbase.client.java.manager.search; -import com.couchbase.client.core.Reactor; +import com.couchbase.client.core.util.ReactorOps; import com.couchbase.client.java.json.JsonObject; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import static com.couchbase.client.core.Reactor.toFlux; import static com.couchbase.client.java.manager.search.AllowQueryingSearchIndexOptions.allowQueryingSearchIndexOptions; import static com.couchbase.client.java.manager.search.AnalyzeDocumentOptions.analyzeDocumentOptions; import static com.couchbase.client.java.manager.search.DisallowQueryingSearchIndexOptions.disallowQueryingSearchIndexOptions; @@ -33,6 +34,7 @@ import static com.couchbase.client.java.manager.search.ResumeIngestSearchIndexOptions.resumeIngestSearchIndexOptions; import static com.couchbase.client.java.manager.search.UnfreezePlanSearchIndexOptions.unfreezePlanSearchIndexOptions; import static com.couchbase.client.java.manager.search.UpsertSearchIndexOptions.upsertSearchIndexOptions; +import static java.util.Objects.requireNonNull; /** * The {@link ReactiveSearchIndexManager} allows to manage search index structures in a couchbase cluster. @@ -42,9 +44,11 @@ public class ReactiveSearchIndexManager { private final AsyncSearchIndexManager asyncIndexManager; + private final ReactorOps reactor; - public ReactiveSearchIndexManager(final AsyncSearchIndexManager asyncIndexManager) { - this.asyncIndexManager = asyncIndexManager; + public ReactiveSearchIndexManager(final ReactorOps reactor, final AsyncSearchIndexManager asyncIndexManager) { + this.reactor = requireNonNull(reactor); + this.asyncIndexManager = requireNonNull(asyncIndexManager); } /** @@ -54,7 +58,7 @@ public ReactiveSearchIndexManager(final AsyncSearchIndexManager asyncIndexManage * @return the index definition if it exists. */ public Mono getIndex(final String name, final GetSearchIndexOptions options) { - return Reactor.toMono(() -> asyncIndexManager.getIndex(name, options)); + return reactor.publishOnUserScheduler(() -> asyncIndexManager.getIndex(name, options)); } /** @@ -63,7 +67,7 @@ public Mono getIndex(final String name, final GetSearchIndexOptions * @return all currently present indexes. */ public Flux getAllIndexes(final GetAllSearchIndexesOptions options) { - return Reactor.toFlux(() -> asyncIndexManager.getAllIndexes(options)); + return reactor.publishOnUserScheduler(toFlux(() -> asyncIndexManager.getAllIndexes(options))); } /** @@ -73,7 +77,7 @@ public Flux getAllIndexes(final GetAllSearchIndexesOptions options) * @return the number of indexed documents. */ public Mono getIndexedDocumentsCount(final String name, final GetIndexedSearchIndexOptions options) { - return Reactor.toMono(() -> asyncIndexManager.getIndexedDocumentsCount(name, options)); + return reactor.publishOnUserScheduler(() -> asyncIndexManager.getIndexedDocumentsCount(name, options)); } /** @@ -84,7 +88,7 @@ public Mono getIndexedDocumentsCount(final String name, final GetIndexedSe * @return the analyzed sections for the document. */ public Flux analyzeDocument(final String name, final JsonObject document, final AnalyzeDocumentOptions options) { - return Reactor.toFlux(() -> asyncIndexManager.analyzeDocument(name, document, options)); + return reactor.publishOnUserScheduler(toFlux(() -> asyncIndexManager.analyzeDocument(name, document, options))); } /** @@ -93,7 +97,7 @@ public Flux analyzeDocument(final String name, final JsonObject docu * @param index the index definition including name and settings. */ public Mono upsertIndex(final SearchIndex index, final UpsertSearchIndexOptions options) { - return Reactor.toMono(() -> asyncIndexManager.upsertIndex(index, options)); + return reactor.publishOnUserScheduler(() -> asyncIndexManager.upsertIndex(index, options)); } /** @@ -102,7 +106,7 @@ public Mono upsertIndex(final SearchIndex index, final UpsertSearchIndexOp * @param name the name of the search index. */ public Mono dropIndex(final String name, final DropSearchIndexOptions options) { - return Reactor.toMono(() -> asyncIndexManager.dropIndex(name, options)); + return reactor.publishOnUserScheduler(() -> asyncIndexManager.dropIndex(name, options)); } /** @@ -111,7 +115,7 @@ public Mono dropIndex(final String name, final DropSearchIndexOptions opti * @param name the name of the search index. */ public Mono pauseIngest(final String name, final PauseIngestSearchIndexOptions options) { - return Reactor.toMono(() -> asyncIndexManager.pauseIngest(name, options)); + return reactor.publishOnUserScheduler(() -> asyncIndexManager.pauseIngest(name, options)); } /** @@ -120,7 +124,7 @@ public Mono pauseIngest(final String name, final PauseIngestSearchIndexOpt * @param name the name of the search index. */ public Mono resumeIngest(final String name, ResumeIngestSearchIndexOptions options) { - return Reactor.toMono(() -> asyncIndexManager.resumeIngest(name, options)); + return reactor.publishOnUserScheduler(() -> asyncIndexManager.resumeIngest(name, options)); } /** @@ -129,7 +133,7 @@ public Mono resumeIngest(final String name, ResumeIngestSearchIndexOptions * @param name the name of the search index. */ public Mono allowQuerying(final String name, AllowQueryingSearchIndexOptions options) { - return Reactor.toMono(() -> asyncIndexManager.allowQuerying(name, options)); + return reactor.publishOnUserScheduler(() -> asyncIndexManager.allowQuerying(name, options)); } /** @@ -138,7 +142,7 @@ public Mono allowQuerying(final String name, AllowQueryingSearchIndexOptio * @param name the name of the search index. */ public Mono disallowQuerying(final String name, final DisallowQueryingSearchIndexOptions options) { - return Reactor.toMono(() -> asyncIndexManager.disallowQuerying(name, options)); + return reactor.publishOnUserScheduler(() -> asyncIndexManager.disallowQuerying(name, options)); } /** @@ -147,7 +151,7 @@ public Mono disallowQuerying(final String name, final DisallowQueryingSear * @param name the name of the search index. */ public Mono freezePlan(final String name, final FreezePlanSearchIndexOptions options) { - return Reactor.toMono(() -> asyncIndexManager.freezePlan(name, options)); + return reactor.publishOnUserScheduler(() -> asyncIndexManager.freezePlan(name, options)); } /** @@ -156,7 +160,7 @@ public Mono freezePlan(final String name, final FreezePlanSearchIndexOptio * @param name the name of the search index. */ public Mono unfreezePlan(final String name, final UnfreezePlanSearchIndexOptions options) { - return Reactor.toMono(() -> asyncIndexManager.unfreezePlan(name, options)); + return reactor.publishOnUserScheduler(() -> asyncIndexManager.unfreezePlan(name, options)); } /** diff --git a/java-client/src/main/java/com/couchbase/client/java/manager/user/ReactiveUserManager.java b/java-client/src/main/java/com/couchbase/client/java/manager/user/ReactiveUserManager.java index 6a9da406d..d59ee1e43 100644 --- a/java-client/src/main/java/com/couchbase/client/java/manager/user/ReactiveUserManager.java +++ b/java-client/src/main/java/com/couchbase/client/java/manager/user/ReactiveUserManager.java @@ -16,42 +16,45 @@ package com.couchbase.client.java.manager.user; -import com.couchbase.client.core.Reactor; +import com.couchbase.client.core.util.ReactorOps; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import static com.couchbase.client.core.Reactor.toFlux; import static java.util.Objects.requireNonNull; public class ReactiveUserManager { private final AsyncUserManager async; + private final ReactorOps reactor; - public ReactiveUserManager(AsyncUserManager async) { + public ReactiveUserManager(ReactorOps reactor, AsyncUserManager async) { + this.reactor = requireNonNull(reactor); this.async = requireNonNull(async); } public Mono getUser(AuthDomain domain, String username) { - return Reactor.toMono(() -> async.getUser(domain, username)); + return reactor.publishOnUserScheduler(() -> async.getUser(domain, username)); } public Mono getUser(AuthDomain domain, String username, GetUserOptions options) { - return Reactor.toMono(() -> async.getUser(domain, username, options)); + return reactor.publishOnUserScheduler(() -> async.getUser(domain, username, options)); } public Flux getAllUsers() { - return Reactor.toFlux(() -> async.getAllUsers()); + return reactor.publishOnUserScheduler(toFlux(() -> async.getAllUsers())); } public Flux getAllUsers(GetAllUsersOptions options) { - return Reactor.toFlux(() -> async.getAllUsers(options)); + return reactor.publishOnUserScheduler(toFlux(() -> async.getAllUsers(options))); } public Flux getRoles() { - return Reactor.toFlux(() -> async.getRoles()); + return reactor.publishOnUserScheduler(toFlux(() -> async.getRoles())); } public Flux getRoles(GetRolesOptions options) { - return Reactor.toFlux(() -> async.getRoles(options)); + return reactor.publishOnUserScheduler(toFlux(() -> async.getRoles(options))); } /** @@ -62,7 +65,7 @@ public Flux getRoles(GetRolesOptions options) { * @param options Common options (timeout, retry...) */ public Mono changePassword(String newPassword, ChangePasswordOptions options) { - return Reactor.toMono(() -> async.changePassword(newPassword, options)); + return reactor.publishOnUserScheduler(() -> async.changePassword(newPassword, options)); } /** * Changes the password of the currently authenticated user. @@ -70,53 +73,53 @@ public Mono changePassword(String newPassword, ChangePasswordOptions optio * be valid. * @param newPassword String to replace the previous password with. */ - public Mono changePassword(String newPassword) { return Reactor.toMono(() -> async.changePassword(newPassword)); } + public Mono changePassword(String newPassword) { return reactor.publishOnUserScheduler(() -> async.changePassword(newPassword)); } public Mono upsertUser(User user) { - return Reactor.toMono(() -> async.upsertUser(user)); + return reactor.publishOnUserScheduler(() -> async.upsertUser(user)); } public Mono upsertUser(User user, UpsertUserOptions options) { - return Reactor.toMono(() -> async.upsertUser(user, options)); + return reactor.publishOnUserScheduler(() -> async.upsertUser(user, options)); } public Mono dropUser(String username) { - return Reactor.toMono(() -> async.dropUser(username)); + return reactor.publishOnUserScheduler(() -> async.dropUser(username)); } public Mono dropUser(String username, DropUserOptions options) { - return Reactor.toMono(() -> async.dropUser(username, options)); + return reactor.publishOnUserScheduler(() -> async.dropUser(username, options)); } public Mono getGroup(String groupName) { - return Reactor.toMono(() -> async.getGroup(groupName)); + return reactor.publishOnUserScheduler(() -> async.getGroup(groupName)); } public Mono getGroup(String groupName, GetGroupOptions options) { - return Reactor.toMono(() -> async.getGroup(groupName, options)); + return reactor.publishOnUserScheduler(() -> async.getGroup(groupName, options)); } public Flux getAllGroups() { - return Reactor.toFlux(() -> async.getAllGroups()); + return reactor.publishOnUserScheduler(toFlux(() -> async.getAllGroups())); } public Flux getAllGroups(GetAllGroupsOptions options) { - return Reactor.toFlux(() -> async.getAllGroups(options)); + return reactor.publishOnUserScheduler(toFlux(() -> async.getAllGroups(options))); } public Mono upsertGroup(Group group) { - return Reactor.toMono(() -> async.upsertGroup(group)); + return reactor.publishOnUserScheduler(() -> async.upsertGroup(group)); } public Mono upsertGroup(Group group, UpsertGroupOptions options) { - return Reactor.toMono(() -> async.upsertGroup(group, options)); + return reactor.publishOnUserScheduler(() -> async.upsertGroup(group, options)); } public Mono dropGroup(String groupName) { - return Reactor.toMono(() -> async.dropGroup(groupName)); + return reactor.publishOnUserScheduler(() -> async.dropGroup(groupName)); } public Mono dropGroup(String groupName, DropGroupOptions options) { - return Reactor.toMono(() -> async.dropGroup(groupName, options)); + return reactor.publishOnUserScheduler(() -> async.dropGroup(groupName, options)); } } diff --git a/java-client/src/main/java/com/couchbase/client/java/manager/user/UserManager.java b/java-client/src/main/java/com/couchbase/client/java/manager/user/UserManager.java index 4e7558ff3..a73e79481 100644 --- a/java-client/src/main/java/com/couchbase/client/java/manager/user/UserManager.java +++ b/java-client/src/main/java/com/couchbase/client/java/manager/user/UserManager.java @@ -16,6 +16,8 @@ package com.couchbase.client.java.manager.user; +import com.couchbase.client.core.util.ReactorOps; + import java.util.List; import static com.couchbase.client.java.AsyncUtils.block; @@ -24,8 +26,10 @@ public class UserManager { private final AsyncUserManager async; + private final ReactorOps reactor; - public UserManager(AsyncUserManager async) { + public UserManager(ReactorOps reactor, AsyncUserManager async) { + this.reactor = requireNonNull(reactor); this.async = requireNonNull(async); } @@ -34,7 +38,7 @@ public AsyncUserManager async() { } public ReactiveUserManager reactive() { - return new ReactiveUserManager(async); + return new ReactiveUserManager(reactor, async); } public UserAndMetadata getUser(AuthDomain domain, String username) { diff --git a/java-client/src/main/java/com/couchbase/client/java/manager/view/ReactiveViewIndexManager.java b/java-client/src/main/java/com/couchbase/client/java/manager/view/ReactiveViewIndexManager.java index 0183e86f6..0f781afea 100644 --- a/java-client/src/main/java/com/couchbase/client/java/manager/view/ReactiveViewIndexManager.java +++ b/java-client/src/main/java/com/couchbase/client/java/manager/view/ReactiveViewIndexManager.java @@ -16,21 +16,24 @@ package com.couchbase.client.java.manager.view; -import com.couchbase.client.core.Reactor; import com.couchbase.client.core.error.CouchbaseException; import com.couchbase.client.core.error.DesignDocumentNotFoundException; import com.couchbase.client.core.error.TimeoutException; +import com.couchbase.client.core.util.ReactorOps; import com.couchbase.client.java.view.DesignDocumentNamespace; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import static com.couchbase.client.core.Reactor.toFlux; import static java.util.Objects.requireNonNull; public class ReactiveViewIndexManager { private final AsyncViewIndexManager async; + private final ReactorOps reactor; - public ReactiveViewIndexManager(AsyncViewIndexManager async) { + public ReactiveViewIndexManager(ReactorOps reactor, AsyncViewIndexManager async) { + this.reactor = requireNonNull(reactor); this.async = requireNonNull(async); } @@ -44,7 +47,7 @@ public ReactiveViewIndexManager(AsyncViewIndexManager async) { * @throws CouchbaseException (async) for all other error reasons (acts as a base type and catch-all). */ public Mono getDesignDocument(String name, DesignDocumentNamespace namespace) { - return Reactor.toMono(() -> async.getDesignDocument(name, namespace)); + return reactor.publishOnUserScheduler(() -> async.getDesignDocument(name, namespace)); } /** @@ -58,7 +61,7 @@ public Mono getDesignDocument(String name, DesignDocumentNamespa * @throws CouchbaseException (async) for all other error reasons (acts as a base type and catch-all). */ public Mono getDesignDocument(String name, DesignDocumentNamespace namespace, GetDesignDocumentOptions options) { - return Reactor.toMono(() -> async.getDesignDocument(name, namespace, options)); + return reactor.publishOnUserScheduler(() -> async.getDesignDocument(name, namespace, options)); } /** @@ -71,7 +74,7 @@ public Mono getDesignDocument(String name, DesignDocumentNamespa * @throws CouchbaseException (async) for all other error reasons (acts as a base type and catch-all). */ public Mono upsertDesignDocument(DesignDocument designDocument, DesignDocumentNamespace namespace) { - return Reactor.toMono(() -> async.upsertDesignDocument(designDocument, namespace)); + return reactor.publishOnUserScheduler(() -> async.upsertDesignDocument(designDocument, namespace)); } /** @@ -85,7 +88,7 @@ public Mono upsertDesignDocument(DesignDocument designDocument, DesignDocu * @throws CouchbaseException (async) for all other error reasons (acts as a base type and catch-all). */ public Mono upsertDesignDocument(DesignDocument designDocument, DesignDocumentNamespace namespace, UpsertDesignDocumentOptions options) { - return Reactor.toMono(() -> async.upsertDesignDocument(designDocument, namespace, options)); + return reactor.publishOnUserScheduler(() -> async.upsertDesignDocument(designDocument, namespace, options)); } /** @@ -98,7 +101,7 @@ public Mono upsertDesignDocument(DesignDocument designDocument, DesignDocu * @throws CouchbaseException (async) for all other error reasons (acts as a base type and catch-all). */ public Mono publishDesignDocument(String name) { - return Reactor.toMono(() -> async.publishDesignDocument(name)); + return reactor.publishOnUserScheduler(() -> async.publishDesignDocument(name)); } /** @@ -112,7 +115,7 @@ public Mono publishDesignDocument(String name) { * @throws CouchbaseException (async) for all other error reasons (acts as a base type and catch-all). */ public Mono publishDesignDocument(String name, PublishDesignDocumentOptions options) { - return Reactor.toMono(() -> async.publishDesignDocument(name, options)); + return reactor.publishOnUserScheduler(() -> async.publishDesignDocument(name, options)); } /** @@ -125,7 +128,7 @@ public Mono publishDesignDocument(String name, PublishDesignDocumentOption * @throws CouchbaseException (async) for all other error reasons (acts as a base type and catch-all). */ public Mono dropDesignDocument(String name, DesignDocumentNamespace namespace) { - return Reactor.toMono(() -> async.dropDesignDocument(name, namespace)); + return reactor.publishOnUserScheduler(() -> async.dropDesignDocument(name, namespace)); } /** @@ -139,7 +142,7 @@ public Mono dropDesignDocument(String name, DesignDocumentNamespace namesp * @throws CouchbaseException (async) for all other error reasons (acts as a base type and catch-all). */ public Mono dropDesignDocument(String name, DesignDocumentNamespace namespace, DropDesignDocumentOptions options) { - return Reactor.toMono(() -> async.dropDesignDocument(name, namespace, options)); + return reactor.publishOnUserScheduler(() -> async.dropDesignDocument(name, namespace, options)); } /** @@ -150,7 +153,7 @@ public Mono dropDesignDocument(String name, DesignDocumentNamespace namesp * @throws CouchbaseException (async) for all other error reasons (acts as a base type and catch-all). */ public Flux getAllDesignDocuments(DesignDocumentNamespace namespace) { - return Reactor.toFlux(() -> async.getAllDesignDocuments(namespace)); + return reactor.publishOnUserScheduler(toFlux(() -> async.getAllDesignDocuments(namespace))); } /** @@ -162,6 +165,6 @@ public Flux getAllDesignDocuments(DesignDocumentNamespace namesp * @throws CouchbaseException (async) for all other error reasons (acts as a base type and catch-all). */ public Flux getAllDesignDocuments(DesignDocumentNamespace namespace, GetAllDesignDocumentsOptions options) { - return Reactor.toFlux(() -> async.getAllDesignDocuments(namespace, options)); + return reactor.publishOnUserScheduler(toFlux(() -> async.getAllDesignDocuments(namespace, options))); } } diff --git a/java-client/src/main/java/com/couchbase/client/java/transactions/ReactiveTransactionAttemptContext.java b/java-client/src/main/java/com/couchbase/client/java/transactions/ReactiveTransactionAttemptContext.java index e3d9afba9..0422bf257 100644 --- a/java-client/src/main/java/com/couchbase/client/java/transactions/ReactiveTransactionAttemptContext.java +++ b/java-client/src/main/java/com/couchbase/client/java/transactions/ReactiveTransactionAttemptContext.java @@ -16,6 +16,7 @@ package com.couchbase.client.java.transactions; +import com.couchbase.client.core.util.ReactorOps; import com.couchbase.client.core.annotation.Stability; import com.couchbase.client.core.api.query.CoreQueryContext; import com.couchbase.client.core.api.query.CoreQueryOptions; @@ -36,14 +37,13 @@ import com.couchbase.client.java.transactions.config.TransactionReplaceOptions; import reactor.core.publisher.Mono; -import java.util.Objects; - import static com.couchbase.client.core.cnc.TracingIdentifiers.TRANSACTION_OP_INSERT; import static com.couchbase.client.core.cnc.TracingIdentifiers.TRANSACTION_OP_REMOVE; import static com.couchbase.client.core.cnc.TracingIdentifiers.TRANSACTION_OP_REPLACE; import static com.couchbase.client.core.util.Validators.notNull; import static com.couchbase.client.java.transactions.internal.ConverterUtil.makeCollectionIdentifier; import static com.couchbase.client.java.transactions.internal.EncodingUtil.encode; +import static java.util.Objects.requireNonNull; /** * Provides methods to allow an application's transaction logic to read, mutate, insert and delete documents, as well @@ -52,10 +52,12 @@ public class ReactiveTransactionAttemptContext { private final CoreTransactionAttemptContext internal; private final JsonSerializer serializer; + private final ReactorOps reactor; - ReactiveTransactionAttemptContext(CoreTransactionAttemptContext internal, JsonSerializer serializer) { - this.internal = Objects.requireNonNull(internal); - this.serializer = Objects.requireNonNull(serializer); + ReactiveTransactionAttemptContext(ReactorOps reactor, CoreTransactionAttemptContext internal, JsonSerializer serializer) { + this.reactor = requireNonNull(reactor); + this.internal = requireNonNull(internal); + this.serializer = requireNonNull(serializer); } @Stability.Internal @@ -88,8 +90,10 @@ public Mono get(ReactiveCollection collection, String id) */ public Mono get(ReactiveCollection collection, String id, TransactionGetOptions options) { TransactionGetOptions.Built built = options.build(); - return internal.get(makeCollectionIdentifier(collection.async()), id) - .map(result -> new TransactionGetResult(result, serializer(), built.transcoder())); + return reactor.publishOnUserScheduler( + internal.get(makeCollectionIdentifier(collection.async()), id) + .map(result -> new TransactionGetResult(result, serializer(), built.transcoder())) + ); } /** @@ -125,8 +129,10 @@ public Mono getReplicaFromPreferredServerGroup(ReactiveCol public Mono getReplicaFromPreferredServerGroup(ReactiveCollection collection, String id, TransactionGetReplicaFromPreferredServerGroupOptions options) { notNull(options, "Options"); TransactionGetReplicaFromPreferredServerGroupOptions.Built built = options.build(); - return internal.getReplicaFromPreferredServerGroup(makeCollectionIdentifier(collection.async()), id) - .map(result -> new TransactionGetResult(result, serializer(), built.transcoder())); + return reactor.publishOnUserScheduler( + internal.getReplicaFromPreferredServerGroup(makeCollectionIdentifier(collection.async()), id) + .map(result -> new TransactionGetResult(result, serializer(), built.transcoder())) + ); } /** @@ -156,10 +162,12 @@ public Mono insert(ReactiveCollection collection, String i span.lowCardinalityAttribute(TracingIdentifiers.ATTR_OPERATION, TRANSACTION_OP_INSERT); Transcoder.EncodedValue encoded = encode(content, span, serializer, built.transcoder(), internal.core().context()); - return internal.insert(makeCollectionIdentifier(collection.async()), id, encoded.encoded(), encoded.flags(), new SpanWrapper(span)) - .map(result -> new TransactionGetResult(result, serializer(), built.transcoder())) - .doOnError(err -> span.status(RequestSpan.StatusCode.ERROR)) - .doOnTerminate(() -> span.end()); + return reactor.publishOnUserScheduler( + internal.insert(makeCollectionIdentifier(collection.async()), id, encoded.encoded(), encoded.flags(), new SpanWrapper(span)) + .map(result -> new TransactionGetResult(result, serializer(), built.transcoder())) + .doOnError(err -> span.status(RequestSpan.StatusCode.ERROR)) + .doOnTerminate(() -> span.end()) + ); } private JsonSerializer serializer() { @@ -192,10 +200,12 @@ public Mono replace(TransactionGetResult doc, Object conte RequestSpan span = CbTracing.newSpan(internal.core().context(), TRANSACTION_OP_REPLACE, internal.span()); span.lowCardinalityAttribute(TracingIdentifiers.ATTR_OPERATION, TRANSACTION_OP_REPLACE); Transcoder.EncodedValue encoded = encode(content, span, serializer, built.transcoder(), internal.core().context()); - return internal.replace(doc.internal(), encoded.encoded(), encoded.flags(), new SpanWrapper(span)) - .map(result -> new TransactionGetResult(result, serializer(), built.transcoder())) - .doOnError(err -> span.status(RequestSpan.StatusCode.ERROR)) - .doOnTerminate(() -> span.end()); + return reactor.publishOnUserScheduler( + internal.replace(doc.internal(), encoded.encoded(), encoded.flags(), new SpanWrapper(span)) + .map(result -> new TransactionGetResult(result, serializer(), built.transcoder())) + .doOnError(err -> span.status(RequestSpan.StatusCode.ERROR)) + .doOnTerminate(() -> span.end()) + ); } /** @@ -206,9 +216,11 @@ public Mono replace(TransactionGetResult doc, Object conte public Mono remove(TransactionGetResult doc) { RequestSpan span = CbTracing.newSpan(internal.core().context(), TRANSACTION_OP_REMOVE, internal.span()); span.lowCardinalityAttribute(TracingIdentifiers.ATTR_OPERATION, TRANSACTION_OP_REMOVE); - return internal.remove(doc.internal(), new SpanWrapper(span)) + return reactor.publishOnUserScheduler( + internal.remove(doc.internal(), new SpanWrapper(span)) .doOnError(err -> span.status(RequestSpan.StatusCode.ERROR)) - .doOnTerminate(() -> span.end()); + .doOnTerminate(() -> span.end()) + ); } @SuppressWarnings("unused") @@ -272,10 +284,12 @@ public Mono query(final ReactiveScope scope, final String statement, final TransactionQueryOptions options) { CoreQueryOptions opts = options != null ? options.builder().build() : null; - return internal.queryBlocking(statement, - scope == null ? null : CoreQueryContext.of(scope.bucketName(), scope.name()), - opts, - false) - .map(response -> new TransactionQueryResult(response, serializer())); + return reactor.publishOnUserScheduler( + internal.queryBlocking(statement, + scope == null ? null : CoreQueryContext.of(scope.bucketName(), scope.name()), + opts, + false) + .map(response -> new TransactionQueryResult(response, serializer())) + ); } } diff --git a/java-client/src/main/java/com/couchbase/client/java/transactions/ReactiveTransactions.java b/java-client/src/main/java/com/couchbase/client/java/transactions/ReactiveTransactions.java index 788816a45..b1eefff44 100644 --- a/java-client/src/main/java/com/couchbase/client/java/transactions/ReactiveTransactions.java +++ b/java-client/src/main/java/com/couchbase/client/java/transactions/ReactiveTransactions.java @@ -17,6 +17,7 @@ package com.couchbase.client.java.transactions; import com.couchbase.client.core.Core; +import com.couchbase.client.core.util.ReactorOps; import com.couchbase.client.core.annotation.Stability; import com.couchbase.client.core.transaction.CoreTransactionAttemptContext; import com.couchbase.client.core.transaction.CoreTransactionContext; @@ -25,7 +26,6 @@ import com.couchbase.client.core.transaction.config.CoreTransactionOptions; import com.couchbase.client.core.transaction.threadlocal.TransactionMarker; import com.couchbase.client.core.transaction.threadlocal.TransactionMarkerOwner; -import com.couchbase.client.core.transaction.util.CoreTransactionsSchedulers; import com.couchbase.client.java.codec.JsonSerializer; import com.couchbase.client.java.transactions.config.TransactionOptions; import com.couchbase.client.java.transactions.error.TransactionFailedException; @@ -48,6 +48,7 @@ public class ReactiveTransactions { private final CoreTransactionsReactive internal; private final JsonSerializer serializer; + private final ReactorOps reactor; @Stability.Internal public ReactiveTransactions(Core core, JsonSerializer serializer) { @@ -55,6 +56,7 @@ public ReactiveTransactions(Core core, JsonSerializer serializer) { this.internal = new CoreTransactionsReactive(core, core.context().environment().transactionsConfig()); this.serializer = Objects.requireNonNull(serializer); + this.reactor = core.environment(); } /** @@ -81,9 +83,11 @@ public ReactiveTransactions(Core core, JsonSerializer serializer) { */ public Mono run(Function> transactionLogic, @Nullable TransactionOptions options) { - return internal.run((ctx) -> transactionLogic.apply(new ReactiveTransactionAttemptContext(ctx, serializer)), options == null ? null : options.build()) + return reactor.publishOnUserScheduler( + internal.run((ctx) -> transactionLogic.apply(new ReactiveTransactionAttemptContext(reactor, ctx, serializer)), options == null ? null : options.build()) .onErrorResume(ErrorUtil::convertTransactionFailedInternal) - .map(TransactionResult::new); + .map(TransactionResult::new) + ); } /** From c805dff34b8b6f4c4117fb63853b67dbf0bf47d8 Mon Sep 17 00:00:00 2001 From: David Nault Date: Thu, 24 Oct 2024 17:03:33 -0700 Subject: [PATCH 32/73] JVMCBC-1579 Deprecate com.couchbase.client.core.node.NodeIdentifier Motivation ---------- Groundwork for migrating to the new ClusterTopology model. Modifications ------------- Use `topology.NodeIdentifier` instead of `node.NodeIdentifier`. The legacy config classes are kinda-sorta public API, so they still use the old `config.NodeIdentifier` to avoid a breaking change at this time. Change-Id: I54db3412fdfb626af8efcad68fb3b57eda0ef1ee Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/218563 Tested-by: Build Bot Reviewed-by: Graham Pople --- ...terManagerBucketLoaderIntegrationTest.java | 2 +- .../loader/GlobalLoaderIntegrationTest.java | 2 +- .../KeyValueBucketLoaderIntegrationTest.java | 2 +- .../GlobalBucketRefresherIntegrationTest.java | 2 +- .../java/com/couchbase/client/core/Core.java | 14 +- .../client/core/api/query/CoreQueryOps.java | 2 +- .../core/api/query/CoreQueryResult.java | 2 +- .../api/query/CoreReactiveQueryResult.java | 2 +- .../classic/query/ClassicCoreQueryOps.java | 2 +- .../classic/query/ClassicCoreQueryResult.java | 2 +- .../query/ClassicCoreReactiveQueryResult.java | 2 +- .../config/DefaultConfigurationProvider.java | 2 +- .../client/core/config/NodeInfo.java | 8 ++ .../client/core/config/PortInfo.java | 12 +- .../core/config/loader/BaseBucketLoader.java | 6 +- .../core/config/loader/BucketLoader.java | 2 +- .../loader/ClusterManagerBucketLoader.java | 2 +- .../core/config/loader/GlobalLoader.java | 6 +- .../config/loader/KeyValueBucketLoader.java | 2 +- .../config/refresher/GlobalRefresher.java | 2 +- .../refresher/KeyValueBucketRefresher.java | 2 +- .../client/core/diagnostics/HealthPinger.java | 18 +-- .../core/endpoint/http/CoreHttpRequest.java | 4 +- .../couchbase/client/core/msg/Request.java | 2 +- .../client/core/msg/RequestContext.java | 4 +- .../client/core/msg/RequestTarget.java | 4 +- .../msg/kv/CarrierBucketConfigRequest.java | 6 +- .../msg/kv/CarrierGlobalConfigRequest.java | 6 +- .../client/core/msg/kv/KvPingRequest.java | 6 +- .../msg/kv/MultiObserveViaCasRequest.java | 4 +- .../core/msg/manager/BucketConfigRequest.java | 4 +- .../client/core/msg/query/QueryRequest.java | 2 +- .../client/core/node/KeyValueLocator.java | 5 +- .../com/couchbase/client/core/node/Node.java | 3 +- .../client/core/node/NodeContext.java | 17 ++- .../client/core/node/NodeIdentifier.java | 35 +++-- .../client/core/node/RoundRobinLocator.java | 1 + .../client/core/node/ViewLocator.java | 2 +- .../query/ProtostellarCoreQueryOps.java | 2 +- .../query/ProtostellarCoreQueryResult.java | 2 +- .../ProtostellarCoreReactiveQueryResult.java | 2 +- .../client/core/topology/NodeIdentifier.java | 21 ++- .../CoreTransactionAttemptContext.java | 14 +- .../transaction/CoreTransactionsReactive.java | 8 +- .../client/core/util/ConsistencyUtil.java | 9 +- .../com/couchbase/client/core/CoreTest.java | 124 ++++++++---------- .../DefaultConfigurationProviderTest.java | 2 +- .../config/loader/BaseBucketLoaderTest.java | 5 +- .../ClusterManagerBucketLoaderTest.java | 2 +- .../loader/KeyValueBucketLoaderTest.java | 2 +- .../config/refresher/GlobalRefresherTest.java | 31 +++-- .../KeyValueBucketRefresherTest.java | 14 +- .../client/core/node/KeyValueLocatorTest.java | 55 ++++---- .../couchbase/client/core/node/NodeTest.java | 1 + .../core/node/RoundRobinLocatorTest.java | 25 ++-- .../client/core/node/ViewLocatorTest.java | 13 +- .../core/topology/TopologyTestUtils.java | 119 +++++++++++++++++ .../CouchbaseHttpClientIntegrationTest.java | 4 +- .../java/batch/ReactiveBatchHelper.java | 6 +- .../client/java/http/HttpTarget.java | 4 +- .../client/java/query/QueryAccessor.java | 2 +- 61 files changed, 397 insertions(+), 271 deletions(-) create mode 100644 core-io/src/test/java/com/couchbase/client/core/topology/TopologyTestUtils.java diff --git a/core-io/src/integrationTest/java/com/couchbase/client/core/config/loader/ClusterManagerBucketLoaderIntegrationTest.java b/core-io/src/integrationTest/java/com/couchbase/client/core/config/loader/ClusterManagerBucketLoaderIntegrationTest.java index 1728058ee..3a94cb781 100644 --- a/core-io/src/integrationTest/java/com/couchbase/client/core/config/loader/ClusterManagerBucketLoaderIntegrationTest.java +++ b/core-io/src/integrationTest/java/com/couchbase/client/core/config/loader/ClusterManagerBucketLoaderIntegrationTest.java @@ -22,7 +22,7 @@ import com.couchbase.client.core.config.ProposedBucketConfigContext; import com.couchbase.client.core.diagnostics.ClusterState; import com.couchbase.client.core.env.CoreEnvironment; -import com.couchbase.client.core.node.NodeIdentifier; +import com.couchbase.client.core.topology.NodeIdentifier; import com.couchbase.client.core.service.ServiceType; import com.couchbase.client.core.util.ConfigWaitHelper; import com.couchbase.client.core.util.CoreIntegrationTest; diff --git a/core-io/src/integrationTest/java/com/couchbase/client/core/config/loader/GlobalLoaderIntegrationTest.java b/core-io/src/integrationTest/java/com/couchbase/client/core/config/loader/GlobalLoaderIntegrationTest.java index 34c6475df..0b5c3724c 100644 --- a/core-io/src/integrationTest/java/com/couchbase/client/core/config/loader/GlobalLoaderIntegrationTest.java +++ b/core-io/src/integrationTest/java/com/couchbase/client/core/config/loader/GlobalLoaderIntegrationTest.java @@ -19,7 +19,7 @@ import com.couchbase.client.core.Core; import com.couchbase.client.core.config.ProposedGlobalConfigContext; import com.couchbase.client.core.env.CoreEnvironment; -import com.couchbase.client.core.node.NodeIdentifier; +import com.couchbase.client.core.topology.NodeIdentifier; import com.couchbase.client.core.util.ConfigWaitHelper; import com.couchbase.client.core.util.CoreIntegrationTest; import com.couchbase.client.test.Capabilities; diff --git a/core-io/src/integrationTest/java/com/couchbase/client/core/config/loader/KeyValueBucketLoaderIntegrationTest.java b/core-io/src/integrationTest/java/com/couchbase/client/core/config/loader/KeyValueBucketLoaderIntegrationTest.java index 5ebd665e6..2c92f7924 100644 --- a/core-io/src/integrationTest/java/com/couchbase/client/core/config/loader/KeyValueBucketLoaderIntegrationTest.java +++ b/core-io/src/integrationTest/java/com/couchbase/client/core/config/loader/KeyValueBucketLoaderIntegrationTest.java @@ -19,7 +19,7 @@ import com.couchbase.client.core.Core; import com.couchbase.client.core.config.ProposedBucketConfigContext; import com.couchbase.client.core.env.CoreEnvironment; -import com.couchbase.client.core.node.NodeIdentifier; +import com.couchbase.client.core.topology.NodeIdentifier; import com.couchbase.client.core.util.ConfigWaitHelper; import com.couchbase.client.core.util.CoreIntegrationTest; import com.couchbase.client.test.Services; diff --git a/core-io/src/integrationTest/java/com/couchbase/client/core/config/refresher/GlobalBucketRefresherIntegrationTest.java b/core-io/src/integrationTest/java/com/couchbase/client/core/config/refresher/GlobalBucketRefresherIntegrationTest.java index 11d2eebb8..c667e8eb8 100644 --- a/core-io/src/integrationTest/java/com/couchbase/client/core/config/refresher/GlobalBucketRefresherIntegrationTest.java +++ b/core-io/src/integrationTest/java/com/couchbase/client/core/config/refresher/GlobalBucketRefresherIntegrationTest.java @@ -22,7 +22,7 @@ import com.couchbase.client.core.config.ProposedGlobalConfigContext; import com.couchbase.client.core.config.loader.GlobalLoader; import com.couchbase.client.core.env.CoreEnvironment; -import com.couchbase.client.core.node.NodeIdentifier; +import com.couchbase.client.core.topology.NodeIdentifier; import com.couchbase.client.core.util.CoreIntegrationTest; import com.couchbase.client.test.Capabilities; import com.couchbase.client.test.IgnoreWhen; diff --git a/core-io/src/main/java/com/couchbase/client/core/Core.java b/core-io/src/main/java/com/couchbase/client/core/Core.java index ece1848f8..f71b4ea77 100644 --- a/core-io/src/main/java/com/couchbase/client/core/Core.java +++ b/core-io/src/main/java/com/couchbase/client/core/Core.java @@ -87,7 +87,7 @@ import com.couchbase.client.core.node.KeyValueLocator; import com.couchbase.client.core.node.Locator; import com.couchbase.client.core.node.Node; -import com.couchbase.client.core.node.NodeIdentifier; +import com.couchbase.client.core.topology.NodeIdentifier; import com.couchbase.client.core.node.RoundRobinLocator; import com.couchbase.client.core.node.ViewLocator; import com.couchbase.client.core.service.ServiceScope; @@ -631,7 +631,7 @@ private Mono maybeRemoveNode(final Node node, final ClusterConfig config) .values() .stream() .flatMap(bc -> bc.nodes().stream()) - .anyMatch(ni -> ni.identifier().equals(node.identifier())); + .anyMatch(ni -> ni.id().equals(node.identifier())); boolean stillPresentInGlobal; @@ -640,7 +640,7 @@ private Mono maybeRemoveNode(final Node node, final ClusterConfig config) .globalConfig() .portInfos() .stream() - .anyMatch(ni -> ni.identifier().equals(node.identifier())); + .anyMatch(ni -> ni.id().equals(node.identifier())); } else { stillPresentInGlobal = false; } @@ -798,7 +798,7 @@ private Mono reconfigureGlobal(final GlobalConfig config) { .fromArray(ServiceType.values()) .filter(s -> !services.containsKey(s)) .flatMap(s -> removeServiceFrom( - ni.identifier(), + ni.id(), s, Optional.empty()) .onErrorResume(throwable -> { @@ -816,7 +816,7 @@ private Mono reconfigureGlobal(final GlobalConfig config) { Flux serviceAddFlux = Flux .fromIterable(services.entrySet()) .flatMap(s -> ensureServiceAt( - ni.identifier(), + ni.id(), s.getKey(), s.getValue(), Optional.empty()) @@ -855,7 +855,7 @@ private Mono reconfigureBuckets(final Flux bucketConfigs) { .fromArray(ServiceType.values()) .filter(s -> !services.containsKey(s)) .flatMap(s -> removeServiceFrom( - ni.identifier(), + ni.id(), s, s.scope() == ServiceScope.BUCKET ? Optional.of(bc.name()) : Optional.empty()) .onErrorResume(throwable -> { @@ -872,7 +872,7 @@ private Mono reconfigureBuckets(final Flux bucketConfigs) { Flux serviceAddFlux = Flux .fromIterable(services.entrySet()) .flatMap(s -> ensureServiceAt( - ni.identifier(), + ni.id(), s.getKey(), s.getValue(), s.getKey().scope() == ServiceScope.BUCKET ? Optional.of(bc.name()) : Optional.empty()) diff --git a/core-io/src/main/java/com/couchbase/client/core/api/query/CoreQueryOps.java b/core-io/src/main/java/com/couchbase/client/core/api/query/CoreQueryOps.java index 9bb4965be..439916ea2 100644 --- a/core-io/src/main/java/com/couchbase/client/core/api/query/CoreQueryOps.java +++ b/core-io/src/main/java/com/couchbase/client/core/api/query/CoreQueryOps.java @@ -17,7 +17,7 @@ import com.couchbase.client.core.annotation.Stability; import com.couchbase.client.core.api.kv.CoreAsyncResponse; -import com.couchbase.client.core.node.NodeIdentifier; +import com.couchbase.client.core.topology.NodeIdentifier; import reactor.core.publisher.Mono; import reactor.util.annotation.Nullable; diff --git a/core-io/src/main/java/com/couchbase/client/core/api/query/CoreQueryResult.java b/core-io/src/main/java/com/couchbase/client/core/api/query/CoreQueryResult.java index 19a997d17..f98c25dcc 100644 --- a/core-io/src/main/java/com/couchbase/client/core/api/query/CoreQueryResult.java +++ b/core-io/src/main/java/com/couchbase/client/core/api/query/CoreQueryResult.java @@ -18,7 +18,7 @@ import com.couchbase.client.core.annotation.Stability; import com.couchbase.client.core.msg.query.QueryChunkRow; -import com.couchbase.client.core.node.NodeIdentifier; +import com.couchbase.client.core.topology.NodeIdentifier; import reactor.util.annotation.Nullable; import java.util.List; diff --git a/core-io/src/main/java/com/couchbase/client/core/api/query/CoreReactiveQueryResult.java b/core-io/src/main/java/com/couchbase/client/core/api/query/CoreReactiveQueryResult.java index 6e149d3a0..793d7e041 100644 --- a/core-io/src/main/java/com/couchbase/client/core/api/query/CoreReactiveQueryResult.java +++ b/core-io/src/main/java/com/couchbase/client/core/api/query/CoreReactiveQueryResult.java @@ -18,7 +18,7 @@ import com.couchbase.client.core.annotation.Stability; import com.couchbase.client.core.msg.query.QueryChunkRow; -import com.couchbase.client.core.node.NodeIdentifier; +import com.couchbase.client.core.topology.NodeIdentifier; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.util.annotation.Nullable; diff --git a/core-io/src/main/java/com/couchbase/client/core/classic/query/ClassicCoreQueryOps.java b/core-io/src/main/java/com/couchbase/client/core/classic/query/ClassicCoreQueryOps.java index 97d24db34..344ee2823 100644 --- a/core-io/src/main/java/com/couchbase/client/core/classic/query/ClassicCoreQueryOps.java +++ b/core-io/src/main/java/com/couchbase/client/core/classic/query/ClassicCoreQueryOps.java @@ -43,10 +43,10 @@ import com.couchbase.client.core.msg.kv.MutationToken; import com.couchbase.client.core.msg.query.QueryRequest; import com.couchbase.client.core.msg.query.QueryResponse; -import com.couchbase.client.core.node.NodeIdentifier; import com.couchbase.client.core.retry.RetryReason; import com.couchbase.client.core.retry.RetryStrategy; import com.couchbase.client.core.service.ServiceType; +import com.couchbase.client.core.topology.NodeIdentifier; import com.couchbase.client.core.transaction.CoreTransactionsReactive; import com.couchbase.client.core.transaction.config.CoreSingleQueryTransactionOptions; import com.couchbase.client.core.transaction.config.CoreTransactionsConfig; diff --git a/core-io/src/main/java/com/couchbase/client/core/classic/query/ClassicCoreQueryResult.java b/core-io/src/main/java/com/couchbase/client/core/classic/query/ClassicCoreQueryResult.java index 2bee6f096..57c040b84 100644 --- a/core-io/src/main/java/com/couchbase/client/core/classic/query/ClassicCoreQueryResult.java +++ b/core-io/src/main/java/com/couchbase/client/core/classic/query/ClassicCoreQueryResult.java @@ -22,7 +22,7 @@ import com.couchbase.client.core.msg.query.QueryChunkHeader; import com.couchbase.client.core.msg.query.QueryChunkRow; import com.couchbase.client.core.msg.query.QueryChunkTrailer; -import com.couchbase.client.core.node.NodeIdentifier; +import com.couchbase.client.core.topology.NodeIdentifier; import java.util.ArrayList; import java.util.List; diff --git a/core-io/src/main/java/com/couchbase/client/core/classic/query/ClassicCoreReactiveQueryResult.java b/core-io/src/main/java/com/couchbase/client/core/classic/query/ClassicCoreReactiveQueryResult.java index 364f62777..5bd93a607 100644 --- a/core-io/src/main/java/com/couchbase/client/core/classic/query/ClassicCoreReactiveQueryResult.java +++ b/core-io/src/main/java/com/couchbase/client/core/classic/query/ClassicCoreReactiveQueryResult.java @@ -21,7 +21,7 @@ import com.couchbase.client.core.api.query.CoreReactiveQueryResult; import com.couchbase.client.core.msg.query.QueryChunkRow; import com.couchbase.client.core.msg.query.QueryResponse; -import com.couchbase.client.core.node.NodeIdentifier; +import com.couchbase.client.core.topology.NodeIdentifier; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.util.annotation.Nullable; diff --git a/core-io/src/main/java/com/couchbase/client/core/config/DefaultConfigurationProvider.java b/core-io/src/main/java/com/couchbase/client/core/config/DefaultConfigurationProvider.java index ba71cfb8d..a772a80f1 100644 --- a/core-io/src/main/java/com/couchbase/client/core/config/DefaultConfigurationProvider.java +++ b/core-io/src/main/java/com/couchbase/client/core/config/DefaultConfigurationProvider.java @@ -58,7 +58,7 @@ import com.couchbase.client.core.msg.CancellationReason; import com.couchbase.client.core.msg.ResponseStatus; import com.couchbase.client.core.msg.kv.GetCollectionIdRequest; -import com.couchbase.client.core.node.NodeIdentifier; +import com.couchbase.client.core.topology.NodeIdentifier; import com.couchbase.client.core.retry.BestEffortRetryStrategy; import com.couchbase.client.core.service.ServiceType; import com.couchbase.client.core.topology.ClusterTopology; diff --git a/core-io/src/main/java/com/couchbase/client/core/config/NodeInfo.java b/core-io/src/main/java/com/couchbase/client/core/config/NodeInfo.java index e2c0c69f1..3f30e739f 100644 --- a/core-io/src/main/java/com/couchbase/client/core/config/NodeInfo.java +++ b/core-io/src/main/java/com/couchbase/client/core/config/NodeInfo.java @@ -157,10 +157,18 @@ public String hostname() { return hostname; } + /** + * @deprecated In favor of {@link #id()} + */ + @Deprecated public NodeIdentifier identifier() { return nodeIdentifier; } + public com.couchbase.client.core.topology.NodeIdentifier id() { + return nodeIdentifier.asTopologyNodeIdentifier(); + } + public Map services() { return directServices; } diff --git a/core-io/src/main/java/com/couchbase/client/core/config/PortInfo.java b/core-io/src/main/java/com/couchbase/client/core/config/PortInfo.java index 3b1a566fc..fe0de1a4a 100644 --- a/core-io/src/main/java/com/couchbase/client/core/config/PortInfo.java +++ b/core-io/src/main/java/com/couchbase/client/core/config/PortInfo.java @@ -19,9 +19,9 @@ import com.couchbase.client.core.deps.com.fasterxml.jackson.annotation.JsonCreator; import com.couchbase.client.core.deps.com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.couchbase.client.core.deps.com.fasterxml.jackson.annotation.JsonProperty; -import reactor.util.annotation.Nullable; import com.couchbase.client.core.node.NodeIdentifier; import com.couchbase.client.core.service.ServiceType; +import reactor.util.annotation.Nullable; import java.util.Collections; import java.util.HashMap; @@ -104,8 +104,16 @@ public PortInfo( this.serverGroup = serverGroup; } + /** + * @deprecated In favor of {@link #id()} + */ + @Deprecated public NodeIdentifier identifier() { - return nodeIdentifier; + return nodeIdentifier; + } + + public com.couchbase.client.core.topology.NodeIdentifier id() { + return nodeIdentifier.asTopologyNodeIdentifier(); } /** diff --git a/core-io/src/main/java/com/couchbase/client/core/config/loader/BaseBucketLoader.java b/core-io/src/main/java/com/couchbase/client/core/config/loader/BaseBucketLoader.java index e71d2d4ed..5e4333b71 100644 --- a/core-io/src/main/java/com/couchbase/client/core/config/loader/BaseBucketLoader.java +++ b/core-io/src/main/java/com/couchbase/client/core/config/loader/BaseBucketLoader.java @@ -20,9 +20,9 @@ import com.couchbase.client.core.config.ProposedBucketConfigContext; import com.couchbase.client.core.error.ConfigException; import com.couchbase.client.core.error.SeedNodeOutdatedException; -import com.couchbase.client.core.node.NodeIdentifier; import com.couchbase.client.core.service.ServiceState; import com.couchbase.client.core.service.ServiceType; +import com.couchbase.client.core.topology.NodeIdentifier; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -98,8 +98,8 @@ public Mono load(final NodeIdentifier seed, final i .then(ensureServiceConnected(seed, serviceType, Optional.of(bucket))) .then(discoverConfig(seed, bucket)) .map(config -> new String(config, UTF_8)) - .map(config -> config.replace("$HOST", seed.address())) - .map(config -> new ProposedBucketConfigContext(bucket, config, seed.address())) + .map(config -> config.replace("$HOST", seed.hostForNetworkConnections())) + .map(config -> new ProposedBucketConfigContext(bucket, config, seed.hostForNetworkConnections())) .onErrorResume(ex -> Mono.error(ex instanceof ConfigException ? ex : new ConfigException("Caught exception while loading config.", ex) diff --git a/core-io/src/main/java/com/couchbase/client/core/config/loader/BucketLoader.java b/core-io/src/main/java/com/couchbase/client/core/config/loader/BucketLoader.java index ffd3aa617..a0006ec97 100644 --- a/core-io/src/main/java/com/couchbase/client/core/config/loader/BucketLoader.java +++ b/core-io/src/main/java/com/couchbase/client/core/config/loader/BucketLoader.java @@ -17,7 +17,7 @@ package com.couchbase.client.core.config.loader; import com.couchbase.client.core.config.ProposedBucketConfigContext; -import com.couchbase.client.core.node.NodeIdentifier; +import com.couchbase.client.core.topology.NodeIdentifier; import reactor.core.publisher.Mono; /** diff --git a/core-io/src/main/java/com/couchbase/client/core/config/loader/ClusterManagerBucketLoader.java b/core-io/src/main/java/com/couchbase/client/core/config/loader/ClusterManagerBucketLoader.java index d1d2b96ca..83f8f4f41 100644 --- a/core-io/src/main/java/com/couchbase/client/core/config/loader/ClusterManagerBucketLoader.java +++ b/core-io/src/main/java/com/couchbase/client/core/config/loader/ClusterManagerBucketLoader.java @@ -23,9 +23,9 @@ import com.couchbase.client.core.error.ConfigException; import com.couchbase.client.core.error.NoAccessDuringConfigLoadException; import com.couchbase.client.core.msg.manager.BucketConfigRequest; -import com.couchbase.client.core.node.NodeIdentifier; import com.couchbase.client.core.retry.BestEffortRetryStrategy; import com.couchbase.client.core.service.ServiceType; +import com.couchbase.client.core.topology.NodeIdentifier; import reactor.core.publisher.Mono; import static com.couchbase.client.core.logging.RedactableArgument.redactMeta; diff --git a/core-io/src/main/java/com/couchbase/client/core/config/loader/GlobalLoader.java b/core-io/src/main/java/com/couchbase/client/core/config/loader/GlobalLoader.java index 9b7b16a76..144d4bce2 100644 --- a/core-io/src/main/java/com/couchbase/client/core/config/loader/GlobalLoader.java +++ b/core-io/src/main/java/com/couchbase/client/core/config/loader/GlobalLoader.java @@ -26,9 +26,9 @@ import com.couchbase.client.core.error.UnsupportedConfigMechanismException; import com.couchbase.client.core.msg.ResponseStatus; import com.couchbase.client.core.msg.kv.CarrierGlobalConfigRequest; -import com.couchbase.client.core.node.NodeIdentifier; import com.couchbase.client.core.retry.BestEffortRetryStrategy; import com.couchbase.client.core.service.ServiceType; +import com.couchbase.client.core.topology.NodeIdentifier; import reactor.core.publisher.Mono; import java.util.Optional; @@ -66,8 +66,8 @@ public Mono load(final NodeIdentifier seed, final i .ensureServiceAt(seed, ServiceType.KV, port, Optional.empty()) .then(discoverConfig(seed)) .map(config -> new String(config, UTF_8)) - .map(config -> config.replace("$HOST", seed.address())) - .map(config -> new ProposedGlobalConfigContext(config, seed.address())) + .map(config -> config.replace("$HOST", seed.hostForNetworkConnections())) + .map(config -> new ProposedGlobalConfigContext(config, seed.hostForNetworkConnections())) .onErrorResume(ex -> Mono.error(ex instanceof ConfigException ? ex : new ConfigException("Caught exception while loading global config.", ex) diff --git a/core-io/src/main/java/com/couchbase/client/core/config/loader/KeyValueBucketLoader.java b/core-io/src/main/java/com/couchbase/client/core/config/loader/KeyValueBucketLoader.java index f35f8ef9b..cd4b1ee1a 100644 --- a/core-io/src/main/java/com/couchbase/client/core/config/loader/KeyValueBucketLoader.java +++ b/core-io/src/main/java/com/couchbase/client/core/config/loader/KeyValueBucketLoader.java @@ -25,9 +25,9 @@ import com.couchbase.client.core.io.CollectionIdentifier; import com.couchbase.client.core.msg.ResponseStatus; import com.couchbase.client.core.msg.kv.CarrierBucketConfigRequest; -import com.couchbase.client.core.node.NodeIdentifier; import com.couchbase.client.core.retry.BestEffortRetryStrategy; import com.couchbase.client.core.service.ServiceType; +import com.couchbase.client.core.topology.NodeIdentifier; import reactor.core.publisher.Mono; import java.util.Optional; diff --git a/core-io/src/main/java/com/couchbase/client/core/config/refresher/GlobalRefresher.java b/core-io/src/main/java/com/couchbase/client/core/config/refresher/GlobalRefresher.java index 8d05dc0bb..6b4a7f769 100644 --- a/core-io/src/main/java/com/couchbase/client/core/config/refresher/GlobalRefresher.java +++ b/core-io/src/main/java/com/couchbase/client/core/config/refresher/GlobalRefresher.java @@ -163,7 +163,7 @@ private Flux attemptUpdateGlobalConfig(final Flux

fetchConfigPerNode(final String name, ctx, new CollectionIdentifier(name, Optional.empty(), Optional.empty()), FailFastRetryStrategy.INSTANCE, - nodeInfo.identifier(), + nodeInfo.id(), currentVersion(name) ); core.send(request); diff --git a/core-io/src/main/java/com/couchbase/client/core/diagnostics/HealthPinger.java b/core-io/src/main/java/com/couchbase/client/core/diagnostics/HealthPinger.java index b0de6f5dd..2677583f8 100644 --- a/core-io/src/main/java/com/couchbase/client/core/diagnostics/HealthPinger.java +++ b/core-io/src/main/java/com/couchbase/client/core/diagnostics/HealthPinger.java @@ -32,11 +32,9 @@ import com.couchbase.client.core.msg.RequestTarget; import com.couchbase.client.core.msg.kv.KvPingRequest; import com.couchbase.client.core.msg.kv.KvPingResponse; -import com.couchbase.client.core.node.NodeIdentifier; import com.couchbase.client.core.retry.RetryStrategy; import com.couchbase.client.core.service.ServiceType; import com.couchbase.client.core.util.CbThrowables; -import com.couchbase.client.core.util.HostAndPort; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.util.annotation.Nullable; @@ -161,7 +159,7 @@ static Set extractPingTargets( // do not check bucket-level resources from a global level (null bucket name will not work) continue; } - RequestTarget target = new RequestTarget(serviceType, portInfo.identifier(), null); + RequestTarget target = new RequestTarget(serviceType, portInfo.id(), null); log.message("extractPingTargets: adding target from global config: " + target); targets.add(target); } @@ -179,9 +177,9 @@ static Set extractPingTargets( // do not check bucket-level resources from a global level (null bucket name will not work) continue; } - RequestTarget target = new RequestTarget(serviceType, nodeInfo.identifier(), null); + RequestTarget target = new RequestTarget(serviceType, nodeInfo.id(), null); log.message("extractPingTargets: adding target from bucket config via global config: " + target); - targets.add(new RequestTarget(serviceType, nodeInfo.identifier(), null)); + targets.add(new RequestTarget(serviceType, nodeInfo.id(), null)); } } } @@ -193,9 +191,9 @@ static Set extractPingTargets( for (ServiceType serviceType : advertisedServices(nodeInfo)) { RequestTarget target; if (serviceType != ServiceType.VIEWS && serviceType != ServiceType.KV) { - target = new RequestTarget(serviceType, nodeInfo.identifier(), null); + target = new RequestTarget(serviceType, nodeInfo.id(), null); } else { - target = new RequestTarget(serviceType, nodeInfo.identifier(), bucketName.get()); + target = new RequestTarget(serviceType, nodeInfo.id(), bucketName.get()); } log.message("extractPingTargets: adding target from bucket config: " + target); @@ -235,17 +233,13 @@ private static Set advertisedServices(NodeInfo info) { return result; } - private static String format(NodeIdentifier id) { - return new HostAndPort(id.address(), id.managerPort()).format(); - } - /** * Returns a map where the key is a redacted node identifier (stringified), * and the value is the list of service types (stringified) associated with that node. */ static Map> formatGroupedByNode(Collection targets) { Map> grouped = targets.stream() - .collect(Collectors.groupingBy(requestTarget -> redactSystem(format(requestTarget.nodeIdentifier())).toString())); + .collect(Collectors.groupingBy(requestTarget -> redactSystem(requestTarget.nodeIdentifier()).toString())); return transformValues(grouped, it -> transform(it, target -> target.serviceType().toString())); } diff --git a/core-io/src/main/java/com/couchbase/client/core/endpoint/http/CoreHttpRequest.java b/core-io/src/main/java/com/couchbase/client/core/endpoint/http/CoreHttpRequest.java index 70a1c8c49..3eda56ab7 100644 --- a/core-io/src/main/java/com/couchbase/client/core/endpoint/http/CoreHttpRequest.java +++ b/core-io/src/main/java/com/couchbase/client/core/endpoint/http/CoreHttpRequest.java @@ -40,9 +40,9 @@ import com.couchbase.client.core.msg.BaseRequest; import com.couchbase.client.core.msg.NonChunkedHttpRequest; import com.couchbase.client.core.msg.RequestTarget; -import com.couchbase.client.core.node.NodeIdentifier; import com.couchbase.client.core.retry.RetryStrategy; import com.couchbase.client.core.service.ServiceType; +import com.couchbase.client.core.topology.NodeIdentifier; import com.couchbase.client.core.util.UrlQueryStringBuilder; import reactor.util.annotation.Nullable; @@ -161,7 +161,7 @@ public Map serviceContext() { ctx.put("method", method.toString()); ctx.put("path", redactMeta(pathAndQueryString())); if (target() != null) { - ctx.put("target", redactSystem(target().address())); + ctx.put("target", redactSystem(target())); } if (bucket() != null) { ctx.put("bucket", redactMeta(bucket())); diff --git a/core-io/src/main/java/com/couchbase/client/core/msg/Request.java b/core-io/src/main/java/com/couchbase/client/core/msg/Request.java index ccb6c3bcb..6a5288827 100644 --- a/core-io/src/main/java/com/couchbase/client/core/msg/Request.java +++ b/core-io/src/main/java/com/couchbase/client/core/msg/Request.java @@ -18,9 +18,9 @@ import com.couchbase.client.core.cnc.RequestSpan; import com.couchbase.client.core.deps.io.netty.util.Timeout; -import com.couchbase.client.core.node.NodeIdentifier; import com.couchbase.client.core.retry.RetryStrategy; import com.couchbase.client.core.service.ServiceType; +import com.couchbase.client.core.topology.NodeIdentifier; import reactor.util.annotation.Nullable; import java.time.Duration; diff --git a/core-io/src/main/java/com/couchbase/client/core/msg/RequestContext.java b/core-io/src/main/java/com/couchbase/client/core/msg/RequestContext.java index 1c86e6bea..1d0e2dd22 100644 --- a/core-io/src/main/java/com/couchbase/client/core/msg/RequestContext.java +++ b/core-io/src/main/java/com/couchbase/client/core/msg/RequestContext.java @@ -25,7 +25,7 @@ import com.couchbase.client.core.cnc.metrics.NoopMeter; import com.couchbase.client.core.env.Authenticator; import com.couchbase.client.core.env.CoreEnvironment; -import com.couchbase.client.core.node.NodeIdentifier; +import com.couchbase.client.core.topology.NodeIdentifier; import com.couchbase.client.core.retry.RetryReason; import com.couchbase.client.core.util.HostAndPort; import reactor.util.annotation.Nullable; @@ -404,7 +404,7 @@ public void injectExportableParams(final Map input) { if (lastDispatchedTo != null) { input.put("lastDispatchedTo", redactSystem(lastDispatchedTo)); } else if (lastDispatchedToNode != null) { - input.put("lastDispatchedTo", redactSystem(lastDispatchedToNode.address())); + input.put("lastDispatchedTo", redactSystem(lastDispatchedToNode)); } if (lastDispatchedFrom != null) { diff --git a/core-io/src/main/java/com/couchbase/client/core/msg/RequestTarget.java b/core-io/src/main/java/com/couchbase/client/core/msg/RequestTarget.java index 055f035f1..f556d107a 100644 --- a/core-io/src/main/java/com/couchbase/client/core/msg/RequestTarget.java +++ b/core-io/src/main/java/com/couchbase/client/core/msg/RequestTarget.java @@ -17,8 +17,8 @@ package com.couchbase.client.core.msg; import com.couchbase.client.core.annotation.Stability; -import com.couchbase.client.core.node.NodeIdentifier; import com.couchbase.client.core.service.ServiceType; +import com.couchbase.client.core.topology.NodeIdentifier; import java.util.Objects; @@ -97,7 +97,7 @@ public NodeIdentifier nodeIdentifier() { @Override public String toString() { - String base = serviceType + "@" + redactSystem(nodeIdentifier.address() + ":" + nodeIdentifier.managerPort()); + String base = serviceType + "@" + redactSystem(nodeIdentifier); return bucketName == null ? base : base + "/" + redactMeta(bucketName); } diff --git a/core-io/src/main/java/com/couchbase/client/core/msg/kv/CarrierBucketConfigRequest.java b/core-io/src/main/java/com/couchbase/client/core/msg/kv/CarrierBucketConfigRequest.java index b1f5b8207..05b9b1dc7 100644 --- a/core-io/src/main/java/com/couchbase/client/core/msg/kv/CarrierBucketConfigRequest.java +++ b/core-io/src/main/java/com/couchbase/client/core/msg/kv/CarrierBucketConfigRequest.java @@ -24,8 +24,8 @@ import com.couchbase.client.core.io.netty.kv.KeyValueChannelContext; import com.couchbase.client.core.msg.TargetedRequest; import com.couchbase.client.core.msg.UnmonitoredRequest; -import com.couchbase.client.core.node.NodeIdentifier; import com.couchbase.client.core.retry.RetryStrategy; +import com.couchbase.client.core.topology.NodeIdentifier; import reactor.util.annotation.Nullable; import java.time.Duration; @@ -87,7 +87,7 @@ public boolean idempotent() { public Map serviceContext() { final Map ctx = super.serviceContext(); if (target != null) { - ctx.put("target", redactSystem(target.address())); + ctx.put("target", redactSystem(target)); } return ctx; } @@ -100,7 +100,7 @@ public String name() { @Override public String toString() { return "CarrierBucketConfigRequest{" + - "target=" + redactSystem(target.address()) + + "target=" + redactSystem(target) + ", bucket=" + redactMeta(collectionIdentifier().bucket()) + ", ifNewerThan=" + ifNewerThan + '}'; diff --git a/core-io/src/main/java/com/couchbase/client/core/msg/kv/CarrierGlobalConfigRequest.java b/core-io/src/main/java/com/couchbase/client/core/msg/kv/CarrierGlobalConfigRequest.java index 909dbea79..d7abcafb7 100644 --- a/core-io/src/main/java/com/couchbase/client/core/msg/kv/CarrierGlobalConfigRequest.java +++ b/core-io/src/main/java/com/couchbase/client/core/msg/kv/CarrierGlobalConfigRequest.java @@ -25,7 +25,7 @@ import com.couchbase.client.core.io.netty.kv.MemcacheProtocol; import com.couchbase.client.core.msg.TargetedRequest; import com.couchbase.client.core.msg.UnmonitoredRequest; -import com.couchbase.client.core.node.NodeIdentifier; +import com.couchbase.client.core.topology.NodeIdentifier; import com.couchbase.client.core.retry.RetryStrategy; import reactor.util.annotation.Nullable; @@ -130,7 +130,7 @@ public boolean idempotent() { public Map serviceContext() { final Map ctx = super.serviceContext(); if (target != null) { - ctx.put("target", redactSystem(target.address())); + ctx.put("target", redactSystem(target)); } return ctx; } @@ -143,7 +143,7 @@ public String name() { @Override public String toString() { return "CarrierGlobalConfigRequest{" + - "target=" + redactSystem(target.address()) + + "target=" + redactSystem(target) + ", ifNewerThan=" + ifNewerThan + '}'; } diff --git a/core-io/src/main/java/com/couchbase/client/core/msg/kv/KvPingRequest.java b/core-io/src/main/java/com/couchbase/client/core/msg/kv/KvPingRequest.java index 364bd06cd..5f3159c55 100644 --- a/core-io/src/main/java/com/couchbase/client/core/msg/kv/KvPingRequest.java +++ b/core-io/src/main/java/com/couchbase/client/core/msg/kv/KvPingRequest.java @@ -22,14 +22,12 @@ import com.couchbase.client.core.io.netty.kv.KeyValueChannelContext; import com.couchbase.client.core.io.netty.kv.MemcacheProtocol; import com.couchbase.client.core.msg.TargetedRequest; -import com.couchbase.client.core.node.NodeIdentifier; import com.couchbase.client.core.retry.RetryStrategy; +import com.couchbase.client.core.topology.NodeIdentifier; import java.time.Duration; import java.util.Map; -import java.util.TreeMap; -import static com.couchbase.client.core.logging.RedactableArgument.redactMeta; import static com.couchbase.client.core.logging.RedactableArgument.redactSystem; public class KvPingRequest extends NoopRequest implements TargetedRequest { @@ -56,7 +54,7 @@ public NodeIdentifier target() { public Map serviceContext() { final Map ctx = super.serviceContext(); if (target != null) { - ctx.put("target", redactSystem(target.address())); + ctx.put("target", redactSystem(target)); } return ctx; } diff --git a/core-io/src/main/java/com/couchbase/client/core/msg/kv/MultiObserveViaCasRequest.java b/core-io/src/main/java/com/couchbase/client/core/msg/kv/MultiObserveViaCasRequest.java index dea847b94..7a2bd5e6a 100644 --- a/core-io/src/main/java/com/couchbase/client/core/msg/kv/MultiObserveViaCasRequest.java +++ b/core-io/src/main/java/com/couchbase/client/core/msg/kv/MultiObserveViaCasRequest.java @@ -25,8 +25,8 @@ import com.couchbase.client.core.io.netty.kv.MemcacheProtocol; import com.couchbase.client.core.msg.ResponseStatus; import com.couchbase.client.core.msg.TargetedRequest; -import com.couchbase.client.core.node.NodeIdentifier; import com.couchbase.client.core.retry.RetryStrategy; +import com.couchbase.client.core.topology.NodeIdentifier; import com.couchbase.client.core.util.UnsignedLEB128; import java.time.Duration; @@ -136,7 +136,7 @@ public String name() { @Override public Map serviceContext() { final Map parentCtx = super.serviceContext(); - parentCtx.put("target", target.address()); + parentCtx.put("target", target); parentCtx.put("numKeys", keys.size()); return parentCtx; } diff --git a/core-io/src/main/java/com/couchbase/client/core/msg/manager/BucketConfigRequest.java b/core-io/src/main/java/com/couchbase/client/core/msg/manager/BucketConfigRequest.java index d4569bff4..27e2b221e 100644 --- a/core-io/src/main/java/com/couchbase/client/core/msg/manager/BucketConfigRequest.java +++ b/core-io/src/main/java/com/couchbase/client/core/msg/manager/BucketConfigRequest.java @@ -25,8 +25,8 @@ import com.couchbase.client.core.endpoint.http.CoreHttpPath; import com.couchbase.client.core.env.Authenticator; import com.couchbase.client.core.msg.TargetedRequest; -import com.couchbase.client.core.node.NodeIdentifier; import com.couchbase.client.core.retry.RetryStrategy; +import com.couchbase.client.core.topology.NodeIdentifier; import java.time.Duration; import java.util.Map; @@ -84,7 +84,7 @@ public Map serviceContext() { ctx.put("type", serviceType().ident()); ctx.put("bucket", redactMeta(bucketName)); if (target != null) { - ctx.put("target", redactSystem(target.address())); + ctx.put("target", redactSystem(target)); } return ctx; } diff --git a/core-io/src/main/java/com/couchbase/client/core/msg/query/QueryRequest.java b/core-io/src/main/java/com/couchbase/client/core/msg/query/QueryRequest.java index 846508704..dbee82f3a 100644 --- a/core-io/src/main/java/com/couchbase/client/core/msg/query/QueryRequest.java +++ b/core-io/src/main/java/com/couchbase/client/core/msg/query/QueryRequest.java @@ -36,9 +36,9 @@ import com.couchbase.client.core.msg.BaseRequest; import com.couchbase.client.core.msg.HttpRequest; import com.couchbase.client.core.msg.ResponseStatus; -import com.couchbase.client.core.node.NodeIdentifier; import com.couchbase.client.core.retry.RetryStrategy; import com.couchbase.client.core.service.ServiceType; +import com.couchbase.client.core.topology.NodeIdentifier; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; diff --git a/core-io/src/main/java/com/couchbase/client/core/node/KeyValueLocator.java b/core-io/src/main/java/com/couchbase/client/core/node/KeyValueLocator.java index 8ed561cd8..998d7e46d 100644 --- a/core-io/src/main/java/com/couchbase/client/core/node/KeyValueLocator.java +++ b/core-io/src/main/java/com/couchbase/client/core/node/KeyValueLocator.java @@ -38,6 +38,7 @@ import com.couchbase.client.core.retry.AuthErrorDecider; import com.couchbase.client.core.retry.RetryOrchestrator; import com.couchbase.client.core.retry.RetryReason; +import com.couchbase.client.core.topology.NodeIdentifier; import java.util.List; import java.util.Optional; @@ -155,7 +156,7 @@ private static void couchbaseBucket(final KeyValueRequest request, final List NodeInfo nodeInfo = config.nodeAtIndex(nodeId); for (Node node : nodes) { - if (node.identifier().equals(nodeInfo.identifier())) { + if (node.identifier().equals(nodeInfo.id())) { node.send(request); return; } @@ -229,7 +230,7 @@ private static void memcacheBucket(final KeyValueRequest request, final List< return; } - NodeIdentifier identifier = config.nodeForKey(request.key()).identifier(); + NodeIdentifier identifier = config.nodeForKey(request.key()).id(); request.partition((short) 0); for (Node node : nodes) { diff --git a/core-io/src/main/java/com/couchbase/client/core/node/Node.java b/core-io/src/main/java/com/couchbase/client/core/node/Node.java index 0bcb95aa8..08e020123 100644 --- a/core-io/src/main/java/com/couchbase/client/core/node/Node.java +++ b/core-io/src/main/java/com/couchbase/client/core/node/Node.java @@ -54,10 +54,11 @@ import com.couchbase.client.core.service.ServiceType; import com.couchbase.client.core.service.ViewService; import com.couchbase.client.core.service.ViewServiceConfig; +import com.couchbase.client.core.topology.NodeIdentifier; +import com.couchbase.client.core.util.AtomicEnumSet; import com.couchbase.client.core.util.CompositeStateful; import com.couchbase.client.core.util.HostAndPort; import com.couchbase.client.core.util.NanoTimestamp; -import com.couchbase.client.core.util.AtomicEnumSet; import com.couchbase.client.core.util.Stateful; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; diff --git a/core-io/src/main/java/com/couchbase/client/core/node/NodeContext.java b/core-io/src/main/java/com/couchbase/client/core/node/NodeContext.java index 4647347c2..f459b97f2 100644 --- a/core-io/src/main/java/com/couchbase/client/core/node/NodeContext.java +++ b/core-io/src/main/java/com/couchbase/client/core/node/NodeContext.java @@ -17,6 +17,7 @@ package com.couchbase.client.core.node; import com.couchbase.client.core.CoreContext; +import com.couchbase.client.core.topology.NodeIdentifier; import java.util.Map; @@ -24,25 +25,27 @@ public class NodeContext extends CoreContext { - /** - * The hostname of this node. - */ - private final NodeIdentifier nodeIdentifier; + private final com.couchbase.client.core.node.NodeIdentifier legacyNodeIdentifier; public NodeContext(CoreContext ctx, NodeIdentifier nodeIdentifier) { super(ctx.core(), ctx.id(), ctx.environment(), ctx.authenticator()); - this.nodeIdentifier = nodeIdentifier; + this.legacyNodeIdentifier = nodeIdentifier.toLegacy(); } + /** + * @deprecated This is the node's canonical hostname; it's not useful by itself, + * since it does not uniquely identify a node. + */ + @Deprecated public String remoteHostname() { - return nodeIdentifier.address(); + return legacyNodeIdentifier.address(); } @Override public void injectExportableParams(final Map input) { super.injectExportableParams(input); input.put("remote", redactSystem(remoteHostname())); - input.put("managerPort", redactSystem(nodeIdentifier.managerPort())); + input.put("managerPort", redactSystem(legacyNodeIdentifier.managerPort())); } diff --git a/core-io/src/main/java/com/couchbase/client/core/node/NodeIdentifier.java b/core-io/src/main/java/com/couchbase/client/core/node/NodeIdentifier.java index 9e7d4486d..3ba18345d 100644 --- a/core-io/src/main/java/com/couchbase/client/core/node/NodeIdentifier.java +++ b/core-io/src/main/java/com/couchbase/client/core/node/NodeIdentifier.java @@ -28,14 +28,17 @@ /** * Uniquely identifies a node within the cluster, using the node's * host and manager port from the default network. + * + * @deprecated In favor of {@link com.couchbase.client.core.topology.NodeIdentifier} */ +@Deprecated public class NodeIdentifier { private final String canonicalHost; private final int canonicalManagerPort; - // Nullable only when created by a - @Nullable private final String hostForNetworkConnections; + // Null only when created by a legacy global/bucket config parser + @Nullable private final com.couchbase.client.core.topology.NodeIdentifier topologyNodeIdentifier; @Deprecated public NodeIdentifier( @@ -44,15 +47,7 @@ public NodeIdentifier( ) { this.canonicalHost = canonicalHost; this.canonicalManagerPort = canonicalManagerPort; - this.hostForNetworkConnections = null; - } - - @Stability.Internal - public static NodeIdentifier forBootstrap(String bootstrapHost, int bootstrapPort) { - // This address isn't really "canonical", since it may be an "external" address. - // If it's an external address, the node created from this identifier will be discarded - // when the config with the _real_ canonical addresses is applied. - return new NodeIdentifier(new HostAndPort(bootstrapHost, bootstrapPort), bootstrapHost); + this.topologyNodeIdentifier = null; } public NodeIdentifier( @@ -61,7 +56,7 @@ public NodeIdentifier( ) { this.canonicalHost = canonicalAddress.host(); this.canonicalManagerPort = canonicalAddress.port(); - this.hostForNetworkConnections = requireNonNull(hostForNetworkConnections); + this.topologyNodeIdentifier = new com.couchbase.client.core.topology.NodeIdentifier(canonicalHost, canonicalManagerPort, hostForNetworkConnections); } /** @@ -72,13 +67,17 @@ public NodeIdentifier( * @throws NoSuchElementException if this info is not available */ public String hostForNetworkConnections() throws NoSuchElementException { - if (hostForNetworkConnections == null) { - throw new NoSuchElementException( - "This NodeIdentifier (" + this + ") doesn't have the host to use for network connections." + - " It might have been created by a legacy config parser or some other component that did not specify it." + return asTopologyNodeIdentifier().hostForNetworkConnections(); + } + + @Stability.Internal + public com.couchbase.client.core.topology.NodeIdentifier asTopologyNodeIdentifier() { + if (topologyNodeIdentifier == null) { + throw new NoSuchElementException("This NodeIdentifier (" + this + ") doesn't have the host to use for network connections." + + " It might have been created by a legacy config parser or some other component that did not specify it." ); } - return hostForNetworkConnections; + return topologyNodeIdentifier; } /** @@ -96,7 +95,7 @@ public int managerPort() { public String toString() { return "NodeIdentifier{" + "canonicalAddress=" + redactSystem(canonicalHost + ":" + canonicalManagerPort) + - ", hostForNetworkConnections=" + hostForNetworkConnections + + ", hostForNetworkConnections=" + (topologyNodeIdentifier == null ? null : hostForNetworkConnections()) + "}"; } diff --git a/core-io/src/main/java/com/couchbase/client/core/node/RoundRobinLocator.java b/core-io/src/main/java/com/couchbase/client/core/node/RoundRobinLocator.java index 861614561..733c273d3 100644 --- a/core-io/src/main/java/com/couchbase/client/core/node/RoundRobinLocator.java +++ b/core-io/src/main/java/com/couchbase/client/core/node/RoundRobinLocator.java @@ -30,6 +30,7 @@ import com.couchbase.client.core.retry.RetryOrchestrator; import com.couchbase.client.core.retry.RetryReason; import com.couchbase.client.core.service.ServiceType; +import com.couchbase.client.core.topology.NodeIdentifier; import java.util.ArrayList; import java.util.List; diff --git a/core-io/src/main/java/com/couchbase/client/core/node/ViewLocator.java b/core-io/src/main/java/com/couchbase/client/core/node/ViewLocator.java index caf0dee29..ceabdb914 100644 --- a/core-io/src/main/java/com/couchbase/client/core/node/ViewLocator.java +++ b/core-io/src/main/java/com/couchbase/client/core/node/ViewLocator.java @@ -68,7 +68,7 @@ protected boolean nodeCanBeUsed(final Node node, final Request getConfig(Core core) { if (core.clusterConfig().globalConfig() != null) { List nodes = core.clusterConfig().globalConfig().portInfos() .stream() - .map(PortInfo::identifier) + .map(PortInfo::id) .collect(Collectors.toList()); logger.info("Adding nodes from global config: {}", nodes); @@ -286,7 +285,7 @@ private static Set getConfig(Core core) { if (core.clusterConfig().bucketConfigs() != null) { List nodes = core.clusterConfig().bucketConfigs().entrySet() .stream() - .flatMap(v -> v.getValue().nodes().stream().map(x -> x.identifier())) + .flatMap(v -> v.getValue().nodes().stream().map(x -> x.id())) .collect(Collectors.toList()); logger.info("Adding nodes from bucket configs: {}", nodes); @@ -340,7 +339,7 @@ private static void waitUntilAllNodesMatchPredicate(Core core, while (!done) { - String debug = String.format("%s:%d waiting for %s", node.address(), node.managerPort(), predicateDesc); + String debug = node + " waiting for " + predicateDesc; CoreHttpRequest request = createRequest.apply(node); diff --git a/core-io/src/test/java/com/couchbase/client/core/CoreTest.java b/core-io/src/test/java/com/couchbase/client/core/CoreTest.java index 14272acfb..5eb9fb520 100644 --- a/core-io/src/test/java/com/couchbase/client/core/CoreTest.java +++ b/core-io/src/test/java/com/couchbase/client/core/CoreTest.java @@ -29,8 +29,8 @@ import com.couchbase.client.core.error.GlobalConfigNotFoundException; import com.couchbase.client.core.error.UnsupportedConfigMechanismException; import com.couchbase.client.core.node.Node; -import com.couchbase.client.core.node.NodeIdentifier; import com.couchbase.client.core.service.ServiceType; +import com.couchbase.client.core.topology.NodeIdentifier; import com.couchbase.client.core.util.ConnectionString; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; @@ -43,10 +43,12 @@ import reactor.core.publisher.Sinks; import java.util.Arrays; -import java.util.HashMap; import java.util.Map; import java.util.Optional; +import static com.couchbase.client.core.topology.TopologyTestUtils.nodeId; +import static com.couchbase.client.core.topology.TopologyTestUtils.topologyParser; +import static com.couchbase.client.core.util.CbCollections.mapOf; import static com.couchbase.client.test.Util.readResource; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; @@ -124,9 +126,10 @@ void addNodesAndServicesOnNewConfig() throws Exception { configureMock(mock101, "mock101", "10.143.190.101", 8091); configureMock(mock102, "mock102", "10.143.190.102", 8091); - final Map mocks = new HashMap<>(); - mocks.put("10.143.190.101", mock101); - mocks.put("10.143.190.102", mock102); + final Map mocks = mapOf( + mock101.identifier(), mock101, + mock102.identifier(), mock102 + ); try (Core core = new Core(ENV, AUTHENTICATOR, CONNECTION_STRING) { @Override public ConfigurationProvider createConfigurationProvider() { @@ -135,17 +138,15 @@ public ConfigurationProvider createConfigurationProvider() { @Override protected Node createNode(final NodeIdentifier target) { - return mocks.get(target.address()); + return mocks.get(target); } }) { logger.info("Validating"); verify(mock101, timeout(TIMEOUT).times(0)).addService(any(), anyInt(), any()); verify(mock102, timeout(TIMEOUT).times(0)).addService(any(), anyInt(), any()); - BucketConfig oneNodeConfig = BucketConfigParser.parse( - readResource("one_node_config.json", CoreTest.class), - ENV, - LOCALHOST + BucketConfig oneNodeConfig = topologyParser().parseBucketConfig( + readResource("one_node_config.json", CoreTest.class) ); mockConfigProvider.accept(oneNodeConfig); @@ -167,10 +168,8 @@ protected Node createNode(final NodeIdentifier target) { verify(mock102, never()).addService(ServiceType.QUERY, 8093, Optional.empty()); verify(mock102, never()).addService(ServiceType.KV, 11210, Optional.of("travel-sample")); - BucketConfig twoNodeConfig = BucketConfigParser.parse( - readResource("two_nodes_config.json", CoreTest.class), - ENV, - LOCALHOST + BucketConfig twoNodeConfig = topologyParser().parseBucketConfig( + readResource("two_nodes_config.json", CoreTest.class) ); mockConfigProvider.accept(twoNodeConfig); @@ -196,7 +195,7 @@ protected Node createNode(final NodeIdentifier target) { } void configureMock(Node mock, String id, String ip, int port) { - when(mock.identifier()).thenReturn(new NodeIdentifier(ip, port)); + when(mock.identifier()).thenReturn(nodeId(ip, port)); when(mock.addService(any(ServiceType.class), anyInt(), any(Optional.class))) .thenAnswer((Answer>) invocation -> { logger.info("{}.addService called with arguments: {}", id, Arrays.toString(invocation.getArguments())); @@ -222,9 +221,10 @@ void addServicesOnNewConfig() throws Exception { configureMock(mock101, "mock101", "10.143.190.101", 8091); configureMock(mock102, "mock102", "10.143.190.102", 8091); - final Map mocks = new HashMap<>(); - mocks.put("10.143.190.101", mock101); - mocks.put("10.143.190.102", mock102); + final Map mocks = mapOf( + mock101.identifier(), mock101, + mock102.identifier(), mock102 + ); try (Core core = new Core(ENV, AUTHENTICATOR, CONNECTION_STRING) { @Override public ConfigurationProvider createConfigurationProvider() { @@ -233,17 +233,15 @@ public ConfigurationProvider createConfigurationProvider() { @Override protected Node createNode(final NodeIdentifier target) { - return mocks.get(target.address()); + return mocks.get(target); } }) { logger.info("Validating"); verify(mock101, timeout(TIMEOUT).times(0)).addService(any(), anyInt(), any()); verify(mock102, timeout(TIMEOUT).times(0)).addService(any(), anyInt(), any()); - BucketConfig twoNodesConfig = BucketConfigParser.parse( - readResource("two_nodes_config.json", CoreTest.class), - ENV, - LOCALHOST + BucketConfig twoNodesConfig = topologyParser().parseBucketConfig( + readResource("two_nodes_config.json", CoreTest.class) ); mockConfigProvider.accept(twoNodesConfig); @@ -266,10 +264,8 @@ protected Node createNode(final NodeIdentifier target) { verify(mock102, timeout(TIMEOUT).times(1)) .addService(ServiceType.KV, 11210, Optional.of("travel-sample")); - BucketConfig twoNodesConfigMore = BucketConfigParser.parse( - readResource("two_nodes_config_more_services.json", CoreTest.class), - ENV, - LOCALHOST + BucketConfig twoNodesConfigMore = topologyParser().parseBucketConfig( + readResource("two_nodes_config_more_services.json", CoreTest.class) ); mockConfigProvider.accept(twoNodesConfigMore); @@ -306,9 +302,10 @@ void removeNodesAndServicesOnNewConfig() throws Exception { configureMock(mock101, "mock101", "10.143.190.101", 8091); configureMock(mock102, "mock102", "10.143.190.102", 8091); - final Map mocks = new HashMap<>(); - mocks.put("10.143.190.101", mock101); - mocks.put("10.143.190.102", mock102); + final Map mocks = mapOf( + mock101.identifier(), mock101, + mock102.identifier(), mock102 + ); try (Core core = new Core(ENV, AUTHENTICATOR, CONNECTION_STRING) { @Override public ConfigurationProvider createConfigurationProvider() { @@ -318,17 +315,15 @@ public ConfigurationProvider createConfigurationProvider() { @Override protected Node createNode(final NodeIdentifier target) { logger.info("createNode {}", target); - return mocks.get(target.address()); + return mocks.get(target); } }) { logger.info("Validating"); verify(mock101, timeout(TIMEOUT).times(0)).addService(any(), anyInt(), any()); verify(mock102, timeout(TIMEOUT).times(0)).addService(any(), anyInt(), any()); - BucketConfig twoNodesConfig = BucketConfigParser.parse( - readResource("two_nodes_config_more_services.json", CoreTest.class), - ENV, - LOCALHOST + BucketConfig twoNodesConfig = topologyParser().parseBucketConfig( + readResource("two_nodes_config_more_services.json", CoreTest.class) ); mockConfigProvider.accept(twoNodesConfig); @@ -340,7 +335,7 @@ protected Node createNode(final NodeIdentifier target) { verify(mock101, timeout(TIMEOUT).times(1)) .addService(ServiceType.QUERY, 8093, Optional.empty()); verify(mock101, timeout(TIMEOUT).times(1)) - .addService(ServiceType.KV, 11210, Optional.of("travel-sample")); + .addService(ServiceType.KV, 11210, Optional.of(twoNodesConfig.name())); verify(mock102, timeout(TIMEOUT).times(1)) .addService(ServiceType.VIEWS, 8092, Optional.empty()); @@ -349,14 +344,12 @@ protected Node createNode(final NodeIdentifier target) { verify(mock102, timeout(TIMEOUT).times(1)) .addService(ServiceType.QUERY, 8093, Optional.empty()); verify(mock102, timeout(TIMEOUT).times(1)) - .addService(ServiceType.KV, 11210, Optional.of("travel-sample")); + .addService(ServiceType.KV, 11210, Optional.of(twoNodesConfig.name())); verify(mock102, timeout(TIMEOUT).times(1)) .addService(ServiceType.SEARCH, 8094, Optional.empty()); - BucketConfig twoNodesLessServices = BucketConfigParser.parse( - readResource("two_nodes_config.json", CoreTest.class), - ENV, - LOCALHOST + BucketConfig twoNodesLessServices = topologyParser().parseBucketConfig( + readResource("two_nodes_config.json", CoreTest.class) ); mockConfigProvider.accept(twoNodesLessServices); @@ -375,9 +368,10 @@ void removesNodeIfNotPresentInConfigAnymore() throws Exception { configureMock(mock101, "mock101", "10.143.190.101", 8091); configureMock(mock102, "mock102", "10.143.190.102", 8091); - final Map mocks = new HashMap<>(); - mocks.put("10.143.190.101", mock101); - mocks.put("10.143.190.102", mock102); + final Map mocks = mapOf( + mock101.identifier(), mock101, + mock102.identifier(), mock102 + ); try (Core core = new Core(ENV, AUTHENTICATOR, CONNECTION_STRING) { @Override public ConfigurationProvider createConfigurationProvider() { @@ -386,17 +380,15 @@ public ConfigurationProvider createConfigurationProvider() { @Override protected Node createNode(final NodeIdentifier target) { - return mocks.get(target.address()); + return mocks.get(target); } }) { logger.info("Validating"); verify(mock101, timeout(TIMEOUT).times(0)).addService(any(), anyInt(), any()); verify(mock102, timeout(TIMEOUT).times(0)).addService(any(), anyInt(), any()); - BucketConfig twoNodesConfig = BucketConfigParser.parse( - readResource("two_nodes_config_more_services.json", CoreTest.class), - ENV, - LOCALHOST + BucketConfig twoNodesConfig = topologyParser().parseBucketConfig( + readResource("two_nodes_config_more_services.json", CoreTest.class) ); mockConfigProvider.accept(twoNodesConfig); @@ -408,7 +400,7 @@ protected Node createNode(final NodeIdentifier target) { verify(mock101, timeout(TIMEOUT).times(1)) .addService(ServiceType.QUERY, 8093, Optional.empty()); verify(mock101, timeout(TIMEOUT).times(1)) - .addService(ServiceType.KV, 11210, Optional.of("travel-sample")); + .addService(ServiceType.KV, 11210, Optional.of(twoNodesConfig.name())); verify(mock102, timeout(TIMEOUT).times(1)) .addService(ServiceType.VIEWS, 8092, Optional.empty()); @@ -417,14 +409,12 @@ protected Node createNode(final NodeIdentifier target) { verify(mock102, timeout(TIMEOUT).times(1)) .addService(ServiceType.QUERY, 8093, Optional.empty()); verify(mock102, timeout(TIMEOUT).times(1)) - .addService(ServiceType.KV, 11210, Optional.of("travel-sample")); + .addService(ServiceType.KV, 11210, Optional.of(twoNodesConfig.name())); verify(mock102, timeout(TIMEOUT).times(1)) .addService(ServiceType.SEARCH, 8094, Optional.empty()); - BucketConfig twoNodesLessServices = BucketConfigParser.parse( - readResource("one_node_config.json", CoreTest.class), - ENV, - LOCALHOST + BucketConfig twoNodesLessServices = topologyParser().parseBucketConfig( + readResource("one_node_config.json", CoreTest.class) ); mockConfigProvider.accept(twoNodesLessServices); @@ -444,13 +434,13 @@ void addsSecondNodeIfBothSameHostname() throws Exception { Node mock101 = mock(Node.class); Node mock102 = mock(Node.class); - configureMock(mock101, "mock101", LOCALHOST, 9000); - configureMock(mock102, "mock102", LOCALHOST, 9001); - - final Map mocks = new HashMap<>(); + configureMock(mock101, "mock101", "192.168.1.194", 9000); + configureMock(mock102, "mock102", "192.168.1.194", 9001); - mocks.put("127.0.0.1:9000", mock101); - mocks.put("127.0.0.1:9001", mock102); + final Map mocks = mapOf( + mock101.identifier(), mock101, + mock102.identifier(), mock102 + ); try (Core core = new Core(ENV, AUTHENTICATOR, CONNECTION_STRING) { @Override public ConfigurationProvider createConfigurationProvider() { @@ -459,17 +449,15 @@ public ConfigurationProvider createConfigurationProvider() { @Override protected Node createNode(final NodeIdentifier target) { - return mocks.get(target.address() + ":" + target.managerPort()); + return mocks.get(target); } }) { logger.info("Validating"); verify(mock101, timeout(TIMEOUT).times(0)).addService(any(), anyInt(), any()); verify(mock102, timeout(TIMEOUT).times(0)).addService(any(), anyInt(), any()); - BucketConfig oneNodeConfig = BucketConfigParser.parse( - readResource("cluster_run_two_nodes.json", CoreTest.class), - ENV, - LOCALHOST + BucketConfig oneNodeConfig = topologyParser().parseBucketConfig( + readResource("config/cluster_run_two_nodes_same_host.json", CoreTest.class) ); mockConfigProvider.accept(oneNodeConfig); @@ -479,14 +467,14 @@ protected Node createNode(final NodeIdentifier target) { verify(mock101, timeout(TIMEOUT).times(1)) .addService(ServiceType.MANAGER, 9000, Optional.empty()); verify(mock101, timeout(TIMEOUT).times(1)) - .addService(ServiceType.KV, 12000, Optional.of("default")); + .addService(ServiceType.KV, 12000, Optional.of(oneNodeConfig.name())); verify(mock102, timeout(TIMEOUT).times(1)) .addService(ServiceType.VIEWS, 9501, Optional.empty()); verify(mock102, timeout(TIMEOUT).times(1)) .addService(ServiceType.MANAGER, 9001, Optional.empty()); verify(mock102, timeout(TIMEOUT).times(1)) - .addService(ServiceType.KV, 12002, Optional.of("default")); + .addService(ServiceType.KV, 12002, Optional.of(oneNodeConfig.name())); } } diff --git a/core-io/src/test/java/com/couchbase/client/core/config/DefaultConfigurationProviderTest.java b/core-io/src/test/java/com/couchbase/client/core/config/DefaultConfigurationProviderTest.java index 98e3bc7d1..7424592c5 100644 --- a/core-io/src/test/java/com/couchbase/client/core/config/DefaultConfigurationProviderTest.java +++ b/core-io/src/test/java/com/couchbase/client/core/config/DefaultConfigurationProviderTest.java @@ -31,7 +31,7 @@ import com.couchbase.client.core.msg.ResponseStatus; import com.couchbase.client.core.msg.kv.GetCollectionIdRequest; import com.couchbase.client.core.msg.kv.GetCollectionIdResponse; -import com.couchbase.client.core.node.NodeIdentifier; +import com.couchbase.client.core.topology.NodeIdentifier; import com.couchbase.client.core.util.ConnectionString; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; diff --git a/core-io/src/test/java/com/couchbase/client/core/config/loader/BaseBucketLoaderTest.java b/core-io/src/test/java/com/couchbase/client/core/config/loader/BaseBucketLoaderTest.java index 7ad761f4a..ed2c892dd 100644 --- a/core-io/src/test/java/com/couchbase/client/core/config/loader/BaseBucketLoaderTest.java +++ b/core-io/src/test/java/com/couchbase/client/core/config/loader/BaseBucketLoaderTest.java @@ -25,9 +25,9 @@ import com.couchbase.client.core.env.CoreEnvironment; import com.couchbase.client.core.error.ConfigException; import com.couchbase.client.core.error.CouchbaseException; -import com.couchbase.client.core.node.NodeIdentifier; import com.couchbase.client.core.node.StandardMemcachedHashingStrategy; import com.couchbase.client.core.service.ServiceType; +import com.couchbase.client.core.topology.NodeIdentifier; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import reactor.core.publisher.Flux; @@ -35,6 +35,7 @@ import java.util.Optional; +import static com.couchbase.client.core.topology.TopologyTestUtils.nodeId; import static com.couchbase.client.test.Util.readResource; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -50,7 +51,7 @@ */ class BaseBucketLoaderTest { - private static final NodeIdentifier SEED = new NodeIdentifier("127.0.0.1", 8091); + private static final NodeIdentifier SEED = nodeId("127.0.0.1", 8091); private static final String BUCKET = "bucket"; private static final int PORT = 1234; private static final ServiceType SERVICE = ServiceType.KV; diff --git a/core-io/src/test/java/com/couchbase/client/core/config/loader/ClusterManagerBucketLoaderTest.java b/core-io/src/test/java/com/couchbase/client/core/config/loader/ClusterManagerBucketLoaderTest.java index aae355072..012311ab0 100644 --- a/core-io/src/test/java/com/couchbase/client/core/config/loader/ClusterManagerBucketLoaderTest.java +++ b/core-io/src/test/java/com/couchbase/client/core/config/loader/ClusterManagerBucketLoaderTest.java @@ -26,7 +26,7 @@ import com.couchbase.client.core.msg.ResponseStatus; import com.couchbase.client.core.msg.manager.BucketConfigRequest; import com.couchbase.client.core.msg.manager.BucketConfigResponse; -import com.couchbase.client.core.node.NodeIdentifier; +import com.couchbase.client.core.topology.NodeIdentifier; import com.couchbase.client.core.retry.BestEffortRetryStrategy; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/core-io/src/test/java/com/couchbase/client/core/config/loader/KeyValueBucketLoaderTest.java b/core-io/src/test/java/com/couchbase/client/core/config/loader/KeyValueBucketLoaderTest.java index 4275d90ba..7ec17af75 100644 --- a/core-io/src/test/java/com/couchbase/client/core/config/loader/KeyValueBucketLoaderTest.java +++ b/core-io/src/test/java/com/couchbase/client/core/config/loader/KeyValueBucketLoaderTest.java @@ -27,8 +27,8 @@ import com.couchbase.client.core.msg.ResponseStatus; import com.couchbase.client.core.msg.kv.CarrierBucketConfigRequest; import com.couchbase.client.core.msg.kv.CarrierBucketConfigResponse; -import com.couchbase.client.core.node.NodeIdentifier; import com.couchbase.client.core.retry.BestEffortRetryStrategy; +import com.couchbase.client.core.topology.NodeIdentifier; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import reactor.core.Disposable; diff --git a/core-io/src/test/java/com/couchbase/client/core/config/refresher/GlobalRefresherTest.java b/core-io/src/test/java/com/couchbase/client/core/config/refresher/GlobalRefresherTest.java index 1c970c7de..60f067c5d 100644 --- a/core-io/src/test/java/com/couchbase/client/core/config/refresher/GlobalRefresherTest.java +++ b/core-io/src/test/java/com/couchbase/client/core/config/refresher/GlobalRefresherTest.java @@ -22,12 +22,12 @@ import com.couchbase.client.core.config.ConfigRefreshFailure; import com.couchbase.client.core.config.ConfigurationProvider; import com.couchbase.client.core.config.GlobalConfig; -import com.couchbase.client.core.config.PortInfo; import com.couchbase.client.core.env.CoreEnvironment; import com.couchbase.client.core.msg.Request; import com.couchbase.client.core.msg.ResponseStatus; import com.couchbase.client.core.msg.kv.CarrierGlobalConfigRequest; import com.couchbase.client.core.msg.kv.CarrierGlobalConfigResponse; +import com.couchbase.client.core.service.ServiceType; import com.couchbase.client.core.util.Bytes; import com.couchbase.client.core.util.NanoTimestamp; import org.junit.jupiter.api.AfterEach; @@ -36,10 +36,11 @@ import reactor.core.publisher.Flux; import java.time.Duration; -import java.util.Arrays; -import java.util.Collections; import java.util.concurrent.atomic.AtomicInteger; +import static com.couchbase.client.core.topology.TopologyTestUtils.clusterTopology; +import static com.couchbase.client.core.topology.TopologyTestUtils.node; +import static com.couchbase.client.core.util.CbCollections.listOf; import static com.couchbase.client.core.util.CbCollections.mapOf; import static com.couchbase.client.test.Util.waitUntilCondition; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -84,12 +85,14 @@ void respectsPollInterval() { ClusterConfig clusterConfig = new ClusterConfig(); when(provider.config()).thenReturn(clusterConfig); when(provider.configChangeNotifications()).thenReturn(Flux.empty()); - GlobalConfig config = mock(GlobalConfig.class); + GlobalConfig config = new GlobalConfig(clusterTopology( + listOf( + node("foo", mapOf(ServiceType.KV, 11210, ServiceType.MANAGER, 8091)), + node("bar", mapOf(ServiceType.KV, 11210, ServiceType.MANAGER, 8091)) + )) + ); + clusterConfig.setGlobalConfig(config); - when(config.portInfos()).thenReturn(Arrays.asList( - new PortInfo(mapOf("kv", 11210, "mgmt", 8091), "foo", Collections.emptyMap(), null), - new PortInfo(mapOf("kv", 11210, "mgmt", 8091), "bar", Collections.emptyMap(), null) - )); final AtomicInteger invocationCounter = new AtomicInteger(0); @@ -132,12 +135,14 @@ void triggersEventIfAllNodesFailedToRefresh() { ClusterConfig clusterConfig = new ClusterConfig(); when(provider.config()).thenReturn(clusterConfig); when(provider.configChangeNotifications()).thenReturn(Flux.empty()); - GlobalConfig config = mock(GlobalConfig.class); + GlobalConfig config = new GlobalConfig(clusterTopology( + listOf( + node("foo", mapOf(ServiceType.KV, 11210, ServiceType.MANAGER, 8091)), + node("bar", mapOf(ServiceType.KV, 11210, ServiceType.MANAGER, 8091)) + )) + ); + clusterConfig.setGlobalConfig(config); - when(config.portInfos()).thenReturn(Arrays.asList( - new PortInfo(mapOf("kv", 11210, "mgmt", 8091), "foo", Collections.emptyMap(), null), - new PortInfo(mapOf("kv", 11210, "mgmt", 8091), "bar", Collections.emptyMap(), null) - )); final AtomicInteger invocationCounter = new AtomicInteger(0); diff --git a/core-io/src/test/java/com/couchbase/client/core/config/refresher/KeyValueBucketRefresherTest.java b/core-io/src/test/java/com/couchbase/client/core/config/refresher/KeyValueBucketRefresherTest.java index 2656a6a90..4df195c9c 100644 --- a/core-io/src/test/java/com/couchbase/client/core/config/refresher/KeyValueBucketRefresherTest.java +++ b/core-io/src/test/java/com/couchbase/client/core/config/refresher/KeyValueBucketRefresherTest.java @@ -22,9 +22,7 @@ import com.couchbase.client.core.config.ClusterConfig; import com.couchbase.client.core.config.ConfigRefreshFailure; import com.couchbase.client.core.config.ConfigurationProvider; -import com.couchbase.client.core.config.NodeInfo; import com.couchbase.client.core.env.CoreEnvironment; -import com.couchbase.client.core.env.IoConfig; import com.couchbase.client.core.msg.Request; import com.couchbase.client.core.msg.ResponseStatus; import com.couchbase.client.core.msg.kv.CarrierBucketConfigRequest; @@ -37,10 +35,10 @@ import reactor.core.publisher.Flux; import java.time.Duration; -import java.util.Arrays; -import java.util.Collections; import java.util.concurrent.atomic.AtomicInteger; +import static com.couchbase.client.core.topology.TopologyTestUtils.nodeInfo; +import static com.couchbase.client.core.util.CbCollections.listOf; import static com.couchbase.client.core.util.CbCollections.mapOf; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.isA; @@ -87,11 +85,9 @@ void triggersEventIfAllNodesFailedToRefresh() { BucketConfig config = mock(BucketConfig.class); when(config.name()).thenReturn("bucket"); clusterConfig.setBucketConfig(config); - when(config.nodes()).thenReturn(Arrays.asList( - new NodeInfo("foo", mapOf(ServiceType.KV, 11210, ServiceType.MANAGER, 8091), - Collections.emptyMap(), Collections.emptyMap()), - new NodeInfo("bar", mapOf(ServiceType.KV, 11210, ServiceType.MANAGER, 8091), - Collections.emptyMap(), Collections.emptyMap()) + when(config.nodes()).thenReturn(listOf( + nodeInfo("foo", mapOf(ServiceType.KV, 11210, ServiceType.MANAGER, 8091)), + nodeInfo("bar", mapOf(ServiceType.KV, 11210, ServiceType.MANAGER, 8091)) )); final AtomicInteger invocationCounter = new AtomicInteger(0); diff --git a/core-io/src/test/java/com/couchbase/client/core/node/KeyValueLocatorTest.java b/core-io/src/test/java/com/couchbase/client/core/node/KeyValueLocatorTest.java index 4d806e9b0..9c5cd4b2c 100644 --- a/core-io/src/test/java/com/couchbase/client/core/node/KeyValueLocatorTest.java +++ b/core-io/src/test/java/com/couchbase/client/core/node/KeyValueLocatorTest.java @@ -26,7 +26,6 @@ import com.couchbase.client.core.msg.CancellationReason; import com.couchbase.client.core.msg.Request; import com.couchbase.client.core.msg.RequestContext; -import com.couchbase.client.core.msg.TargetedRequest; import com.couchbase.client.core.msg.kv.CarrierBucketConfigRequest; import com.couchbase.client.core.msg.kv.GetRequest; import org.junit.jupiter.api.Test; @@ -36,7 +35,10 @@ import java.util.Collections; import java.util.List; +import static com.couchbase.client.core.topology.TopologyTestUtils.nodeId; +import static com.couchbase.client.core.topology.TopologyTestUtils.nodeInfo; import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Collections.emptyMap; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; @@ -53,17 +55,15 @@ class KeyValueLocatorTest { void locateGetRequestForCouchbaseBucket() { Locator locator = new KeyValueLocator(); - NodeInfo nodeInfo1 = new NodeInfo("http://foo:1234", "192.168.56.101:8091", - Collections.EMPTY_MAP, null); - NodeInfo nodeInfo2 = new NodeInfo("http://foo:1234", "192.168.56.102:8091", - Collections.EMPTY_MAP, null); + NodeInfo nodeInfo1 = nodeInfo("192.168.56.101", emptyMap()); + NodeInfo nodeInfo2 = nodeInfo("192.168.56.102", emptyMap()); GetRequest getRequestMock = mock(GetRequest.class); ClusterConfig configMock = mock(ClusterConfig.class); Node node1Mock = mock(Node.class); - when(node1Mock.identifier()).thenReturn(new NodeIdentifier("192.168.56.101", 8091)); + when(node1Mock.identifier()).thenReturn(nodeInfo1.id()); Node node2Mock = mock(Node.class); - when(node2Mock.identifier()).thenReturn(new NodeIdentifier("192.168.56.102", 8091)); + when(node2Mock.identifier()).thenReturn(nodeInfo2.id()); List nodes = new ArrayList<>(Arrays.asList(node1Mock, node2Mock)); CouchbaseBucketConfig bucketMock = mock(CouchbaseBucketConfig.class); when(getRequestMock.bucket()).thenReturn("bucket"); @@ -86,16 +86,13 @@ void locateGetRequestForCouchbaseBucket() { void pickFastForwardIfAvailableAndNmvbSeen() { Locator locator = new KeyValueLocator(); - // Setup 2 nodes - NodeInfo nodeInfo1 = new NodeInfo("http://foo:1234", "192.168.56.101:8091", - Collections.EMPTY_MAP, null); - NodeInfo nodeInfo2 = new NodeInfo("http://foo:1234", "192.168.56.102:8091", - Collections.EMPTY_MAP, null); + NodeInfo nodeInfo1 = nodeInfo("192.168.56.101", emptyMap()); + NodeInfo nodeInfo2 = nodeInfo("192.168.56.102", emptyMap()); Node node1Mock = mock(Node.class); - when(node1Mock.identifier()).thenReturn(new NodeIdentifier("192.168.56.101", 8091)); + when(node1Mock.identifier()).thenReturn(nodeInfo1.id()); Node node2Mock = mock(Node.class); - when(node2Mock.identifier()).thenReturn(new NodeIdentifier("192.168.56.102", 8091)); + when(node2Mock.identifier()).thenReturn(nodeInfo2.id()); List nodes = new ArrayList<>(Arrays.asList(node1Mock, node2Mock)); // Configure Cluster and Bucket config @@ -144,15 +141,13 @@ void pickFastForwardIfAvailableAndNmvbSeen() { void pickCurrentIfNoFFMapAndRetry() { Locator locator = new KeyValueLocator(); - // Setup 2 nodes - NodeInfo nodeInfo1 = new NodeInfo("http://foo:1234", "192.168.56.101:8091", - Collections.EMPTY_MAP, null); - NodeInfo nodeInfo2 = new NodeInfo("http://foo:1234", "192.168.56.102:8091", - Collections.EMPTY_MAP, null); + NodeInfo nodeInfo1 = nodeInfo("192.168.56.101", emptyMap()); + NodeInfo nodeInfo2 = nodeInfo("192.168.56.102", emptyMap()); + Node node1Mock = mock(Node.class); - when(node1Mock.identifier()).thenReturn(new NodeIdentifier("192.168.56.101", 8091)); + when(node1Mock.identifier()).thenReturn(nodeInfo1.id()); Node node2Mock = mock(Node.class); - when(node2Mock.identifier()).thenReturn(new NodeIdentifier("192.168.56.102", 8091)); + when(node2Mock.identifier()).thenReturn(nodeInfo2.id()); List nodes = new ArrayList<>(Arrays.asList(node1Mock, node2Mock)); // Configure Cluster and Bucket config @@ -200,15 +195,13 @@ void pickCurrentIfNoFFMapAndRetry() { void pickCurrentIfNoFFMapAndNmvbSeen() { Locator locator = new KeyValueLocator(); - // Setup 2 nodes - NodeInfo nodeInfo1 = new NodeInfo("http://foo:1234", "192.168.56.101:8091", - Collections.EMPTY_MAP, null); - NodeInfo nodeInfo2 = new NodeInfo("http://foo:1234", "192.168.56.102:8091", - Collections.EMPTY_MAP, null); + NodeInfo nodeInfo1 = nodeInfo("192.168.56.101", emptyMap()); + NodeInfo nodeInfo2 = nodeInfo("192.168.56.102", emptyMap()); + Node node1Mock = mock(Node.class); - when(node1Mock.identifier()).thenReturn(new NodeIdentifier("192.168.56.101", 8091)); + when(node1Mock.identifier()).thenReturn(nodeInfo1.id()); Node node2Mock = mock(Node.class); - when(node2Mock.identifier()).thenReturn(new NodeIdentifier("192.168.56.102", 8091)); + when(node2Mock.identifier()).thenReturn(nodeInfo2.id()); List nodes = new ArrayList<>(Arrays.asList(node1Mock, node2Mock)); // Configure Cluster and Bucket config @@ -248,7 +241,7 @@ void cancelsTargetedRequestIfNodeListEmpty() { Locator locator = new KeyValueLocator(); Request request = mock(CarrierBucketConfigRequest.class); - when(request.target()).thenReturn(new NodeIdentifier("localhost", 8091)); + when(request.target()).thenReturn(nodeId("localhost", 8091)); locator.dispatch(request, Collections.emptyList(), null, null); @@ -260,11 +253,11 @@ void cancelsTargetedRequestIfNodeNotInList() { Locator locator = new KeyValueLocator(); Request request = mock(CarrierBucketConfigRequest.class); - when(request.target()).thenReturn(new NodeIdentifier("hostb", 8091)); + when(request.target()).thenReturn(nodeId("hostb", 8091)); Node node = mock(Node.class); when(node.state()).thenReturn(NodeState.CONNECTED); - when(node.identifier()).thenReturn(new NodeIdentifier("hosta", 8091)); + when(node.identifier()).thenReturn(nodeId("hosta", 8091)); locator.dispatch(request, Collections.singletonList(node), null, null); diff --git a/core-io/src/test/java/com/couchbase/client/core/node/NodeTest.java b/core-io/src/test/java/com/couchbase/client/core/node/NodeTest.java index 20cf751e3..e8bf41597 100644 --- a/core-io/src/test/java/com/couchbase/client/core/node/NodeTest.java +++ b/core-io/src/test/java/com/couchbase/client/core/node/NodeTest.java @@ -39,6 +39,7 @@ import com.couchbase.client.core.service.Service; import com.couchbase.client.core.service.ServiceState; import com.couchbase.client.core.service.ServiceType; +import com.couchbase.client.core.topology.NodeIdentifier; import com.couchbase.client.core.util.HostAndPort; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; diff --git a/core-io/src/test/java/com/couchbase/client/core/node/RoundRobinLocatorTest.java b/core-io/src/test/java/com/couchbase/client/core/node/RoundRobinLocatorTest.java index 8687d1292..07af6aaba 100644 --- a/core-io/src/test/java/com/couchbase/client/core/node/RoundRobinLocatorTest.java +++ b/core-io/src/test/java/com/couchbase/client/core/node/RoundRobinLocatorTest.java @@ -19,10 +19,10 @@ import com.couchbase.client.core.config.ClusterConfig; import com.couchbase.client.core.msg.CancellationReason; import com.couchbase.client.core.msg.Request; -import com.couchbase.client.core.msg.TargetedRequest; import com.couchbase.client.core.msg.manager.BucketConfigRequest; import com.couchbase.client.core.msg.query.QueryRequest; import com.couchbase.client.core.service.ServiceType; +import com.couchbase.client.core.topology.NodeIdentifier; import org.junit.jupiter.api.Test; import java.util.ArrayList; @@ -30,6 +30,7 @@ import java.util.Collections; import java.util.List; +import static com.couchbase.client.core.topology.TopologyTestUtils.nodeId; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; @@ -51,10 +52,10 @@ void selectNextNode() { Node node1Mock = mock(Node.class); when(node1Mock.serviceEnabled(ServiceType.QUERY)).thenReturn(true); - when(node1Mock.identifier()).thenReturn(new NodeIdentifier("192.168.56.101", 8091)); + when(node1Mock.identifier()).thenReturn(nodeId("192.168.56.101", 8091)); Node node2Mock = mock(Node.class); when(node2Mock.serviceEnabled(ServiceType.QUERY)).thenReturn(true); - when(node2Mock.identifier()).thenReturn(new NodeIdentifier("192.168.56.102", 8091)); + when(node2Mock.identifier()).thenReturn(nodeId("192.168.56.102", 8091)); List nodes = new ArrayList<>(Arrays.asList(node1Mock, node2Mock)); locator.dispatch(request, nodes, configMock, null); @@ -79,13 +80,13 @@ void skipNodeWithoutServiceEnabled() { when(configMock.hasClusterOrBucketConfig()).thenReturn(true); Node node1Mock = mock(Node.class); - when(node1Mock.identifier()).thenReturn(new NodeIdentifier("192.168.56.101", 8091)); + when(node1Mock.identifier()).thenReturn(nodeId("192.168.56.101", 8091)); when(node1Mock.serviceEnabled(ServiceType.QUERY)).thenReturn(false); Node node2Mock = mock(Node.class); - when(node2Mock.identifier()).thenReturn(new NodeIdentifier("192.168.56.102", 8091)); + when(node2Mock.identifier()).thenReturn(nodeId("192.168.56.102", 8091)); when(node2Mock.serviceEnabled(ServiceType.QUERY)).thenReturn(true); Node node3Mock = mock(Node.class); - when(node3Mock.identifier()).thenReturn(new NodeIdentifier("192.168.56.103", 8091)); + when(node3Mock.identifier()).thenReturn(nodeId("192.168.56.103", 8091)); when(node3Mock.serviceEnabled(ServiceType.QUERY)).thenReturn(false); List nodes = new ArrayList<>(Arrays.asList(node1Mock, node2Mock, node3Mock)); @@ -119,16 +120,16 @@ void shouldDistributeFairlyUnderMDS() { when(configMock.hasClusterOrBucketConfig()).thenReturn(true); Node node1Mock = mock(Node.class); - when(node1Mock.identifier()).thenReturn(new NodeIdentifier("192.168.56.101", 8091)); + when(node1Mock.identifier()).thenReturn(nodeId("192.168.56.101", 8091)); when(node1Mock.serviceEnabled(ServiceType.QUERY)).thenReturn(false); Node node2Mock = mock(Node.class); - when(node2Mock.identifier()).thenReturn(new NodeIdentifier("192.168.56.102", 8091)); + when(node2Mock.identifier()).thenReturn(nodeId("192.168.56.102", 8091)); when(node2Mock.serviceEnabled(ServiceType.QUERY)).thenReturn(false); Node node3Mock = mock(Node.class); - when(node3Mock.identifier()).thenReturn(new NodeIdentifier("192.168.56.103", 8091)); + when(node3Mock.identifier()).thenReturn(nodeId("192.168.56.103", 8091)); when(node3Mock.serviceEnabled(ServiceType.QUERY)).thenReturn(true); Node node4Mock = mock(Node.class); - when(node4Mock.identifier()).thenReturn(new NodeIdentifier("192.168.56.104", 8091)); + when(node4Mock.identifier()).thenReturn(nodeId("192.168.56.104", 8091)); when(node4Mock.serviceEnabled(ServiceType.QUERY)).thenReturn(true); List nodes = new ArrayList<>(Arrays.asList(node1Mock, node2Mock, node3Mock, node4Mock)); @@ -162,11 +163,11 @@ void cancelsTargetedRequestIfNodeNotInList() { Locator locator = new RoundRobinLocator(ServiceType.QUERY); Request request = mock(BucketConfigRequest.class); - when(request.target()).thenReturn(new NodeIdentifier("hostb", 8091)); + when(request.target()).thenReturn(nodeId("hostb", 8091)); Node node = mock(Node.class); when(node.state()).thenReturn(NodeState.CONNECTED); - when(node.identifier()).thenReturn(new NodeIdentifier("hosta", 8091)); + when(node.identifier()).thenReturn(nodeId("hosta", 8091)); when(node.serviceEnabled(ServiceType.QUERY)).thenReturn(true); locator.dispatch(request, Collections.singletonList(node), null, null); diff --git a/core-io/src/test/java/com/couchbase/client/core/node/ViewLocatorTest.java b/core-io/src/test/java/com/couchbase/client/core/node/ViewLocatorTest.java index a036114b7..a18695ff8 100644 --- a/core-io/src/test/java/com/couchbase/client/core/node/ViewLocatorTest.java +++ b/core-io/src/test/java/com/couchbase/client/core/node/ViewLocatorTest.java @@ -24,6 +24,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import com.couchbase.client.core.topology.NodeIdentifier; class ViewLocatorTest { @@ -36,15 +37,19 @@ void dispatchesOnlyToHostsWithPrimaryPartitionsEnabled() { CouchbaseBucketConfig bucketConfig = mock(CouchbaseBucketConfig.class); ClusterConfig config = mock(ClusterConfig.class); when(config.bucketConfig("bucket")).thenReturn(bucketConfig); - when(bucketConfig.hasPrimaryPartitionsOnNode(new NodeIdentifier("1.2.3.4", 1234))).thenReturn(true); - when(bucketConfig.hasPrimaryPartitionsOnNode(new NodeIdentifier("1.2.3.5", 1234))).thenReturn(false); + + NodeIdentifier id1 = NodeIdentifier.forBootstrap("1.2.3.4", 1234); + NodeIdentifier id2 = NodeIdentifier.forBootstrap("1.2.3.5", 1234); + + when(bucketConfig.hasPrimaryPartitionsOnNode(id1.toLegacy())).thenReturn(true); + when(bucketConfig.hasPrimaryPartitionsOnNode(id2.toLegacy())).thenReturn(false); Node node1 = mock(Node.class); - when(node1.identifier()).thenReturn(new NodeIdentifier("1.2.3.4", 1234)); + when(node1.identifier()).thenReturn(id1); assertTrue(locator.nodeCanBeUsed(node1, request, config)); Node node2 = mock(Node.class); - when(node2.identifier()).thenReturn(new NodeIdentifier("1.2.3.5", 1234)); + when(node2.identifier()).thenReturn(id2); assertFalse(locator.nodeCanBeUsed(node2, request, config)); } diff --git a/core-io/src/test/java/com/couchbase/client/core/topology/TopologyTestUtils.java b/core-io/src/test/java/com/couchbase/client/core/topology/TopologyTestUtils.java new file mode 100644 index 000000000..46887e8de --- /dev/null +++ b/core-io/src/test/java/com/couchbase/client/core/topology/TopologyTestUtils.java @@ -0,0 +1,119 @@ +/* + * Copyright 2024 Couchbase, Inc. + * + * Licensed 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 + * + * https://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 com.couchbase.client.core.topology; + +import com.couchbase.client.core.config.BucketConfig; +import com.couchbase.client.core.config.GlobalConfig; +import com.couchbase.client.core.config.LegacyConfigHelper; +import com.couchbase.client.core.config.NodeInfo; +import com.couchbase.client.core.env.NetworkResolution; +import com.couchbase.client.core.node.MemcachedHashingStrategy; +import com.couchbase.client.core.node.StandardMemcachedHashingStrategy; +import com.couchbase.client.core.service.ServiceType; + +import java.util.List; +import java.util.Map; + +import static java.util.Collections.emptyMap; +import static java.util.Collections.emptySet; +import static java.util.Objects.requireNonNull; + +public class TopologyTestUtils { + private TopologyTestUtils() { + } + + public static NodeIdentifier nodeId(String host) { + return nodeId(host, 8091); + } + + public static NodeIdentifier nodeId(String host, int port) { + return new NodeIdentifier(host, port, host); + } + + public static HostAndServicePorts node(String host, Map ports) { + return new HostAndServicePorts(host, ports, nodeId(host, ports.getOrDefault(ServiceType.MANAGER, 8091)), null, null); + } + + @Deprecated + public static NodeInfo nodeInfo(String host, Map ports) { + return new NodeInfo(host, ports, emptyMap(), null, nodeId(host, ports.getOrDefault(ServiceType.MANAGER, 8091)).toLegacy()); + } + + public static ClusterTopology clusterTopology(List nodes) { + return ClusterTopology.of( + new TopologyRevision(1, 1), + nodes, + emptySet(), + NetworkResolution.DEFAULT, + PortSelector.NON_TLS, + null // no bucket + ); + } + + public static TestTopologyParser topologyParser() { + return new TestTopologyParser(); + } + + public static class TestTopologyParser { + private String originHost = "127.0.0.1"; + private NetworkSelector networkSelector = NetworkSelector.DEFAULT; + private PortSelector portSelector = PortSelector.NON_TLS; + private MemcachedHashingStrategy memcachedHashingStrategy = StandardMemcachedHashingStrategy.INSTANCE; + + private TestTopologyParser() { + } + + private TestTopologyParser(String originHost, NetworkSelector networkSelector, PortSelector portSelector, MemcachedHashingStrategy memcachedHashingStrategy) { + this.originHost = requireNonNull(originHost); + this.networkSelector = requireNonNull(networkSelector); + this.portSelector = requireNonNull(portSelector); + this.memcachedHashingStrategy = requireNonNull(memcachedHashingStrategy); + } + + public TestTopologyParser originHost(String originHost) { + return new TestTopologyParser(originHost, networkSelector, portSelector, memcachedHashingStrategy); + } + + public TestTopologyParser networkSelector(NetworkSelector networkSelector) { + return new TestTopologyParser(originHost, networkSelector, portSelector, memcachedHashingStrategy); + } + + public TestTopologyParser portSelector(PortSelector portSelector) { + return new TestTopologyParser(originHost, networkSelector, portSelector, memcachedHashingStrategy); + } + + public TestTopologyParser memcachedHashingStrategy(MemcachedHashingStrategy memcachedHashingStrategy) { + return new TestTopologyParser(originHost, networkSelector, portSelector, memcachedHashingStrategy); + } + + public ClusterTopology parse(String json) { + return new TopologyParser(networkSelector, portSelector, memcachedHashingStrategy).parse(json, originHost); + } + + @Deprecated + public BucketConfig parseBucketConfig(String json) { + return LegacyConfigHelper.toLegacyBucketConfig(parse(json).requireBucket()); + } + + @Deprecated + public GlobalConfig parseGlobalConfig(String json) { + return new GlobalConfig(parse(json)); + } + } + +} + diff --git a/java-client/src/integrationTest/java/com/couchbase/client/java/http/CouchbaseHttpClientIntegrationTest.java b/java-client/src/integrationTest/java/com/couchbase/client/java/http/CouchbaseHttpClientIntegrationTest.java index 4d4c2e213..10934e751 100644 --- a/java-client/src/integrationTest/java/com/couchbase/client/java/http/CouchbaseHttpClientIntegrationTest.java +++ b/java-client/src/integrationTest/java/com/couchbase/client/java/http/CouchbaseHttpClientIntegrationTest.java @@ -16,7 +16,7 @@ package com.couchbase.client.java.http; -import com.couchbase.client.core.node.NodeIdentifier; +import com.couchbase.client.core.topology.NodeIdentifier; import com.couchbase.client.java.Cluster; import com.couchbase.client.java.util.JavaIntegrationTest; import com.couchbase.client.test.Capabilities; @@ -87,7 +87,7 @@ void canReadStatusCodeForBadRequestEventing() { NodeIdentifier firstNode() { TestNodeConfig n = config().nodes().get(0); - return new NodeIdentifier(n.hostname(), n.ports().get(Services.MANAGER)); + return NodeIdentifier.forBootstrap(n.hostname(), n.ports().get(Services.MANAGER)); } // On 6.0 and below, fails with: diff --git a/java-client/src/main/java/com/couchbase/client/java/batch/ReactiveBatchHelper.java b/java-client/src/main/java/com/couchbase/client/java/batch/ReactiveBatchHelper.java index 171ca410b..bafde0fe9 100644 --- a/java-client/src/main/java/com/couchbase/client/java/batch/ReactiveBatchHelper.java +++ b/java-client/src/main/java/com/couchbase/client/java/batch/ReactiveBatchHelper.java @@ -28,7 +28,7 @@ import com.couchbase.client.core.msg.kv.MultiObserveViaCasResponse; import com.couchbase.client.core.msg.kv.ObserveViaCasResponse; import com.couchbase.client.core.node.KeyValueLocator; -import com.couchbase.client.core.node.NodeIdentifier; +import com.couchbase.client.core.topology.NodeIdentifier; import com.couchbase.client.java.Collection; import com.couchbase.client.java.cnc.evnts.BatchHelperExistsCompletedEvent; import com.couchbase.client.java.kv.GetResult; @@ -124,7 +124,7 @@ private static Flux existsBytes(final Collection collection, final java. Map> nodeEntries = new HashMap<>(config.nodes().size()); for (NodeInfo node : config.nodes()) { - nodeEntries.put(node.identifier(), new HashMap<>(ids.size() / config.nodes().size())); + nodeEntries.put(node.id(), new HashMap<>(ids.size() / config.nodes().size())); } CouchbaseBucketConfig cbc = (CouchbaseBucketConfig) config; @@ -139,7 +139,7 @@ private static Flux existsBytes(final Collection collection, final java. int partitionId = KeyValueLocator.partitionForKey(encodedId, cbc.numberOfPartitions()); int nodeId = cbc.nodeIndexForActive(partitionId, false); NodeInfo nodeInfo = cbc.nodeAtIndex(nodeId); - nodeEntries.get(nodeInfo.identifier()).put(encodedId, (short) partitionId); + nodeEntries.get(nodeInfo.id()).put(encodedId, (short) partitionId); } List> responses = new ArrayList<>(nodeEntries.size()); diff --git a/java-client/src/main/java/com/couchbase/client/java/http/HttpTarget.java b/java-client/src/main/java/com/couchbase/client/java/http/HttpTarget.java index 0aeaa0027..68ebcc429 100644 --- a/java-client/src/main/java/com/couchbase/client/java/http/HttpTarget.java +++ b/java-client/src/main/java/com/couchbase/client/java/http/HttpTarget.java @@ -18,10 +18,8 @@ import com.couchbase.client.core.annotation.Stability; import com.couchbase.client.core.msg.RequestTarget; -import com.couchbase.client.core.node.NodeIdentifier; import com.couchbase.client.core.service.ServiceType; - -import static java.util.Objects.requireNonNull; +import com.couchbase.client.core.topology.NodeIdentifier; /** * Specifies which Couchbase service should receive the request. diff --git a/java-client/src/main/java/com/couchbase/client/java/query/QueryAccessor.java b/java-client/src/main/java/com/couchbase/client/java/query/QueryAccessor.java index 0d454aaef..910504c55 100644 --- a/java-client/src/main/java/com/couchbase/client/java/query/QueryAccessor.java +++ b/java-client/src/main/java/com/couchbase/client/java/query/QueryAccessor.java @@ -24,8 +24,8 @@ import com.couchbase.client.core.error.transaction.internal.CoreTransactionExpiredException; import com.couchbase.client.core.error.transaction.internal.CoreTransactionFailedException; import com.couchbase.client.core.msg.query.QueryRequest; -import com.couchbase.client.core.node.NodeIdentifier; import com.couchbase.client.core.retry.RetryStrategy; +import com.couchbase.client.core.topology.NodeIdentifier; import com.couchbase.client.java.transactions.error.TransactionCommitAmbiguousException; import com.couchbase.client.java.transactions.error.TransactionExpiredException; import com.couchbase.client.java.transactions.error.TransactionFailedException; From 5b067bff7ca6fff697b3b4568cf1146f2656f157 Mon Sep 17 00:00:00 2001 From: David Nault Date: Sun, 27 Oct 2024 17:22:24 -0700 Subject: [PATCH 33/73] Gardening: Support getting ClusterTopology from legacy config Motivation ---------- Groundwork for migrating to new ClusterTopology model. Let components that are ready for the new model get ClusterTopology from ClusterConfig. Modifications ------------- When converting a ClusterTopology to legacy config, keep a reference to the ClusterTopology. Add ClusterConfig methods for exposing cluster topology as ClusterTopology[WithBucket]. Change-Id: I3aa19785913a95e578c439fcdc4ce19c74f445e0 Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/218631 Tested-by: Build Bot Reviewed-by: Graham Pople --- .../core/config/AbstractBucketConfig.java | 20 ++++++++++++- .../client/core/config/BucketConfig.java | 7 +++++ .../client/core/config/ClusterConfig.java | 28 +++++++++++++++++++ .../core/config/CouchbaseBucketConfig.java | 3 +- .../client/core/config/GlobalConfig.java | 19 ++++++++++++- .../core/config/MemcachedBucketConfig.java | 3 +- 6 files changed, 76 insertions(+), 4 deletions(-) diff --git a/core-io/src/main/java/com/couchbase/client/core/config/AbstractBucketConfig.java b/core-io/src/main/java/com/couchbase/client/core/config/AbstractBucketConfig.java index 85096f1ec..55e7c34ed 100644 --- a/core-io/src/main/java/com/couchbase/client/core/config/AbstractBucketConfig.java +++ b/core-io/src/main/java/com/couchbase/client/core/config/AbstractBucketConfig.java @@ -16,7 +16,10 @@ package com.couchbase.client.core.config; +import com.couchbase.client.core.annotation.Stability; import com.couchbase.client.core.service.ServiceType; +import com.couchbase.client.core.topology.ClusterTopologyWithBucket; +import reactor.util.annotation.Nullable; import java.util.ArrayList; import java.util.Collections; @@ -29,6 +32,7 @@ import java.util.stream.Collectors; import static com.couchbase.client.core.util.CbCollections.isNullOrEmpty; +import static java.util.Objects.requireNonNull; @Deprecated public abstract class AbstractBucketConfig implements BucketConfig { @@ -45,6 +49,9 @@ public abstract class AbstractBucketConfig implements BucketConfig { private final List portInfos; private final ConfigVersion version; + // Null only if this bucket config was created by a legacy config parser + @Nullable private final ClusterTopologyWithBucket clusterTopology; + /** * A "dumb" constructor that assigns the given values * directly to the corresponding fields, without any funny business. @@ -60,7 +67,8 @@ protected AbstractBucketConfig( Map> clusterCapabilities, String origin, List portInfos, - ConfigVersion version + ConfigVersion version, + ClusterTopologyWithBucket clusterTopology ) { this.uuid = uuid; this.name = name; @@ -73,6 +81,7 @@ protected AbstractBucketConfig( this.origin = origin; this.portInfos = portInfos; this.version = version; + this.clusterTopology = requireNonNull(clusterTopology); } protected AbstractBucketConfig(String uuid, String name, BucketNodeLocator locator, String uri, String streamingUri, @@ -92,6 +101,15 @@ protected AbstractBucketConfig(String uuid, String name, BucketNodeLocator locat this.version = new ConfigVersion(revEpoch, rev); this.portInfos = portInfos == null ? Collections.emptyList() : portInfos; this.nodeInfo = portInfos == null ? nodeInfos : nodeInfoFromExtended(portInfos, nodeInfos); + this.clusterTopology = null; + } + + @Stability.Internal + public ClusterTopologyWithBucket asClusterTopology() { + if (clusterTopology == null) { + throw new IllegalStateException("This BucketConfig instance was not created from a ClusterTopologyWithBucket."); + } + return clusterTopology; } static Set convertBucketCapabilities(final List input) { diff --git a/core-io/src/main/java/com/couchbase/client/core/config/BucketConfig.java b/core-io/src/main/java/com/couchbase/client/core/config/BucketConfig.java index 89b1210b9..a3e1e7d9b 100644 --- a/core-io/src/main/java/com/couchbase/client/core/config/BucketConfig.java +++ b/core-io/src/main/java/com/couchbase/client/core/config/BucketConfig.java @@ -16,9 +16,11 @@ package com.couchbase.client.core.config; +import com.couchbase.client.core.annotation.Stability; import com.couchbase.client.core.service.ServiceType; import com.couchbase.client.core.deps.com.fasterxml.jackson.annotation.JsonSubTypes; import com.couchbase.client.core.deps.com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.couchbase.client.core.topology.ClusterTopologyWithBucket; import java.util.List; import java.util.Map; @@ -163,4 +165,9 @@ public interface BucketConfig { */ List portInfos(); + /** + * @throws IllegalStateException if this BucketConfig was not created from a ClusterTopologyWithBucket. + */ + @Stability.Internal + ClusterTopologyWithBucket asClusterTopology(); } diff --git a/core-io/src/main/java/com/couchbase/client/core/config/ClusterConfig.java b/core-io/src/main/java/com/couchbase/client/core/config/ClusterConfig.java index abf1d140d..71fb38e88 100644 --- a/core-io/src/main/java/com/couchbase/client/core/config/ClusterConfig.java +++ b/core-io/src/main/java/com/couchbase/client/core/config/ClusterConfig.java @@ -18,7 +18,11 @@ import com.couchbase.client.core.annotation.Stability; import com.couchbase.client.core.service.ServiceType; +import com.couchbase.client.core.topology.ClusterTopology; +import com.couchbase.client.core.topology.ClusterTopologyWithBucket; +import reactor.util.annotation.Nullable; +import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Map; @@ -26,6 +30,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicReference; +import static com.couchbase.client.core.util.CbCollections.transform; + /** * The {@link ClusterConfig} holds bucket and global configurations in a central place. */ @@ -49,6 +55,28 @@ public ClusterConfig() { globalConfig = new AtomicReference<>(); } + @Stability.Internal + public Set bucketNames() { + return bucketConfigs.keySet(); + } + + @Stability.Internal + public @Nullable ClusterTopologyWithBucket bucketTopology(final String bucketName) { + BucketConfig bucketConfig = bucketConfigs.get(bucketName); + return bucketConfig == null ? null : bucketConfig.asClusterTopology(); + } + + @Stability.Internal + public Collection bucketTopologies() { + return transform(bucketConfigs.values(), BucketConfig::asClusterTopology); + } + + @Stability.Internal + public @Nullable ClusterTopology globalTopology() { + GlobalConfig g = globalConfig(); + return g == null ? null : g.asClusterTopology(); + } + public BucketConfig bucketConfig(final String bucketName) { return bucketConfigs.get(bucketName); } diff --git a/core-io/src/main/java/com/couchbase/client/core/config/CouchbaseBucketConfig.java b/core-io/src/main/java/com/couchbase/client/core/config/CouchbaseBucketConfig.java index 2842db2af..8366ce9d5 100644 --- a/core-io/src/main/java/com/couchbase/client/core/config/CouchbaseBucketConfig.java +++ b/core-io/src/main/java/com/couchbase/client/core/config/CouchbaseBucketConfig.java @@ -131,7 +131,8 @@ public CouchbaseBucketConfig(ClusterTopologyWithBucket cluster) { getClusterCapabilities(cluster), "", getPortInfos(cluster), - LegacyConfigHelper.toLegacy(cluster.revision()) + LegacyConfigHelper.toLegacy(cluster.revision()), + cluster ); CouchbaseBucketTopology bucket = (CouchbaseBucketTopology) cluster.bucket(); diff --git a/core-io/src/main/java/com/couchbase/client/core/config/GlobalConfig.java b/core-io/src/main/java/com/couchbase/client/core/config/GlobalConfig.java index 6a630d977..1633e053f 100644 --- a/core-io/src/main/java/com/couchbase/client/core/config/GlobalConfig.java +++ b/core-io/src/main/java/com/couchbase/client/core/config/GlobalConfig.java @@ -16,12 +16,14 @@ package com.couchbase.client.core.config; +import com.couchbase.client.core.annotation.Stability; import com.couchbase.client.core.deps.com.fasterxml.jackson.annotation.JacksonInject; import com.couchbase.client.core.deps.com.fasterxml.jackson.annotation.JsonCreator; import com.couchbase.client.core.deps.com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.couchbase.client.core.deps.com.fasterxml.jackson.annotation.JsonProperty; import com.couchbase.client.core.service.ServiceType; import com.couchbase.client.core.topology.ClusterTopology; +import reactor.util.annotation.Nullable; import java.util.ArrayList; import java.util.List; @@ -46,10 +48,14 @@ public class GlobalConfig { private final List portInfos; private final Map> clusterCapabilities; + // Null only if the GlobalConfig was created by a legacy config parser + @Nullable private final ClusterTopology clusterTopology; + public GlobalConfig(ClusterTopology topology) { this.version = LegacyConfigHelper.toLegacy(topology.revision()); this.portInfos = LegacyConfigHelper.getPortInfos(topology); this.clusterCapabilities = LegacyConfigHelper.getClusterCapabilities(topology); + this.clusterTopology = topology; } @JsonCreator @@ -63,9 +69,9 @@ public GlobalConfig( this.version = new ConfigVersion(revEpoch, rev); this.portInfos = enrichPortInfos(portInfos, origin); this.clusterCapabilities = AbstractBucketConfig.convertClusterCapabilities(clusterCapabilities); + this.clusterTopology = null; } - /** * Helper method to enrich the port infos with a synthetic origin host if not present. * @@ -127,6 +133,17 @@ public List portInfos() { return portInfos; } + /** + * @throws IllegalStateException if this GlobalConfig was not created from a ClusterTopology. + */ + @Stability.Internal + ClusterTopology asClusterTopology() { + if (clusterTopology == null) { + throw new IllegalStateException("This GlobalConfig instance was not created from a ClusterTopology."); + } + return clusterTopology; + } + @Override public String toString() { return "GlobalConfig{" + diff --git a/core-io/src/main/java/com/couchbase/client/core/config/MemcachedBucketConfig.java b/core-io/src/main/java/com/couchbase/client/core/config/MemcachedBucketConfig.java index 904664b35..df43eafb0 100644 --- a/core-io/src/main/java/com/couchbase/client/core/config/MemcachedBucketConfig.java +++ b/core-io/src/main/java/com/couchbase/client/core/config/MemcachedBucketConfig.java @@ -91,7 +91,8 @@ public MemcachedBucketConfig( LegacyConfigHelper.getClusterCapabilities(cluster), "", LegacyConfigHelper.getPortInfos(cluster), - LegacyConfigHelper.toLegacy(cluster.revision()) + LegacyConfigHelper.toLegacy(cluster.revision()), + cluster ); this.ketamaRing = KetamaRing.create( From 090a80c6a3c3a51d3bfdc3170e933de05c418bab Mon Sep 17 00:00:00 2001 From: David Nault Date: Fri, 25 Oct 2024 09:43:50 -0700 Subject: [PATCH 34/73] JCBC-2169 Add QueryMetrics.toString() Motivation ---------- Better experience when user logs QueryMetrics. Rejected Alternatives --------------------- Could have added the new toString() to core via a new AbstractCoreQueryMetrics, but that wouldn't benefit Scala or Kotlin since they already have their own idiomatic QueryMetrics.toString() methods. Change-Id: Ia7c87818582072a0a338591c6455bb9809227bfd Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/218593 Tested-by: Build Bot Reviewed-by: David Nault --- .../com/couchbase/client/java/query/QueryMetrics.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/java-client/src/main/java/com/couchbase/client/java/query/QueryMetrics.java b/java-client/src/main/java/com/couchbase/client/java/query/QueryMetrics.java index 2f834bb05..a508702d2 100644 --- a/java-client/src/main/java/com/couchbase/client/java/query/QueryMetrics.java +++ b/java-client/src/main/java/com/couchbase/client/java/query/QueryMetrics.java @@ -96,6 +96,15 @@ public long warningCount() { @Override public String toString() { - return internal.toString(); + return "QueryMetrics{" + + "elapsedTime=" + elapsedTime() + + ", executionTime=" + executionTime() + + ", sortCount=" + sortCount() + + ", resultCount=" + resultCount() + + ", resultSize=" + resultSize() + + ", mutationCount=" + mutationCount() + + ", errorCount=" + errorCount() + + ", warningCount=" + warningCount() + + '}'; } } From 0c0762a49ed25e87453235d92a7578a40964d9e1 Mon Sep 17 00:00:00 2001 From: Will Broadbelt Date: Mon, 30 Sep 2024 16:34:52 +0100 Subject: [PATCH 35/73] SDKQE-3434: FIT-Columnar - Update disable server cert option Change-Id: I193c8e32f6486eae09990e6326ec089dd5deb733 Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/217021 Reviewed-by: Tested-by: Build Bot --- .../columnar/cluster/ColumnarClusterConnection.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/cluster/ColumnarClusterConnection.java b/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/cluster/ColumnarClusterConnection.java index 499038a2c..e1520e72b 100644 --- a/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/cluster/ColumnarClusterConnection.java +++ b/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/cluster/ColumnarClusterConnection.java @@ -87,9 +87,8 @@ public ColumnarClusterConnection(fit.columnar.ClusterNewInstanceRequest request, if (secOptions.hasTrustOnlyPlatform()) { sec.trustOnlyJvm(); } - if (secOptions.hasVerifyServerCertificate()) { - //noinspection deprecation - sec.disableServerCertificateVerification(!secOptions.getVerifyServerCertificate()); + if (secOptions.hasDisableServerCertificateVerification()) { + sec.disableServerCertificateVerification(secOptions.getDisableServerCertificateVerification()); } if (secOptions.getCipherSuitesCount() > 0) { sec.cipherSuites(secOptions.getCipherSuitesList()); From 9dbd436311b46124595208fbcfcb2adb32715692 Mon Sep 17 00:00:00 2001 From: David Nault Date: Mon, 28 Oct 2024 15:25:30 -0700 Subject: [PATCH 36/73] JCBC-2170 (1/n) Don't switch schedulers when converting reactive query result Motivation ---------- Groundwork for asserting reactive results are published on the user's custom Scheduler. Modifications ------------- In reactive returnQueryResult(), build a fully non-blocking reactive chain instead of switching to bounded elastic scheduler and blocking. Rework the relevant utility methods in ContentAsUtil to support the reactive chain. Change-Id: Ifab9e60ef71f8c6e8011bf9ac303eea7fdddcb92 Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/218707 Tested-by: Build Bot Reviewed-by: Michael Reiche Reviewed-by: Graham Pople --- .../com/couchbase/JavaSdkCommandExecutor.java | 19 ++-- .../ReactiveJavaSdkCommandExecutor.java | 55 +++++------- .../com/couchbase/utils/ContentAsUtil.java | 88 +++++++------------ 3 files changed, 58 insertions(+), 104 deletions(-) diff --git a/java-fit-performer/src/main/java/com/couchbase/JavaSdkCommandExecutor.java b/java-fit-performer/src/main/java/com/couchbase/JavaSdkCommandExecutor.java index 5ba4b1558..b3f9674c4 100644 --- a/java-fit-performer/src/main/java/com/couchbase/JavaSdkCommandExecutor.java +++ b/java-fit-performer/src/main/java/com/couchbase/JavaSdkCommandExecutor.java @@ -1020,20 +1020,13 @@ public static void populateResult(com.couchbase.client.protocol.sdk.query.Comman var builder = com.couchbase.client.protocol.sdk.query.QueryResult.newBuilder(); - var content = ContentAsUtil.contentTypeList(request.getContentAs(), - () -> values.rowsAs(byte[].class), - () -> values.rowsAs(String.class), - () -> values.rowsAs(JsonObject.class), - () -> values.rowsAs(JsonArray.class), - () -> values.rowsAs(Boolean.class), - () -> values.rowsAs(Integer.class), - () -> values.rowsAs(Double.class)); - - if (content.isFailure()) { - throw content.exception(); - } + var contentAs = request.getContentAs(); + var rowType = ContentAsUtil.toJavaClass(contentAs); + var content = values.rowsAs(rowType).stream() + .map(row -> ContentAsUtil.toFitContent(row, contentAs)) + .toList(); - builder.addAllContent(content.value()); + builder.addAllContent(content); // Metadata var convertedMetaData = convertMetaData(values.metaData()); diff --git a/java-fit-performer/src/main/java/com/couchbase/ReactiveJavaSdkCommandExecutor.java b/java-fit-performer/src/main/java/com/couchbase/ReactiveJavaSdkCommandExecutor.java index aede0a967..a7f59fed9 100644 --- a/java-fit-performer/src/main/java/com/couchbase/ReactiveJavaSdkCommandExecutor.java +++ b/java-fit-performer/src/main/java/com/couchbase/ReactiveJavaSdkCommandExecutor.java @@ -17,8 +17,6 @@ import com.couchbase.client.core.cnc.RequestSpan; import com.couchbase.client.java.ReactiveCollection; -import com.couchbase.client.java.json.JsonArray; -import com.couchbase.client.java.json.JsonObject; import com.couchbase.client.java.kv.*; import com.couchbase.client.java.query.ReactiveQueryResult; import com.couchbase.client.java.kv.GetResult; @@ -58,7 +56,6 @@ import com.couchbase.utils.ClusterConnection; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import reactor.core.scheduler.Schedulers; import java.time.Duration; import java.time.Instant; @@ -687,37 +684,25 @@ protected Exception convertException(Throwable raw) { return convertExceptionShared(raw); } - private Mono returnQueryResult(com.couchbase.client.protocol.sdk.query.Command request, Mono queryResult, Result.Builder result, Long start) { - return queryResult.publishOn(Schedulers.boundedElastic()).map(r -> { - result.setElapsedNanos(System.nanoTime() - start); - - var builder = com.couchbase.client.protocol.sdk.query.QueryResult.newBuilder(); - - // FIT only supports testing blocking (not streaming) queries currently, so the .block() here to gather - // the rows is fine. - var content = ContentAsUtil.contentTypeList(request.getContentAs(), - () -> r.rowsAs(byte[].class).collectList().block(), - () -> r.rowsAs(String.class).collectList().block(), - () -> r.rowsAs(JsonObject.class).collectList().block(), - () -> r.rowsAs(JsonArray.class).collectList().block(), - () -> r.rowsAs(Boolean.class).collectList().block(), - () -> r.rowsAs(Integer.class).collectList().block(), - () -> r.rowsAs(Double.class).collectList().block()); - - if (content.isFailure()) { - throw content.exception(); - } - - builder.addAllContent(content.value()); - - // Metadata - var convertedMetaData = convertMetaData(r.metaData().block()); - builder.setMetaData(convertedMetaData); - - result.setSdk(com.couchbase.client.protocol.sdk.Result.newBuilder() - .setQueryResult(builder)); + private Mono returnQueryResult(com.couchbase.client.protocol.sdk.query.Command request, Mono queryResult, Result.Builder result, Long start) { + return queryResult.flatMap(r -> { + result.setElapsedNanos(System.nanoTime() - start); - return result.build(); - }); - } + var contentAs = request.getContentAs(); + var rowType = ContentAsUtil.toJavaClass(contentAs); + return r.rowsAs(rowType) + .map(it -> ContentAsUtil.toFitContent(it, contentAs)) + .collectList() + .zipWith(r.metaData().map(JavaSdkCommandExecutor::convertMetaData)) + .map(fitRowsAndMetadata -> { + var fitQueryResult = com.couchbase.client.protocol.sdk.query.QueryResult.newBuilder() + .addAllContent(fitRowsAndMetadata.getT1()) + .setMetaData(fitRowsAndMetadata.getT2()); + + result.setSdk(com.couchbase.client.protocol.sdk.Result.newBuilder() + .setQueryResult(fitQueryResult)); + return result.build(); + }); + }); + } } diff --git a/java-fit-performer/src/main/java/com/couchbase/utils/ContentAsUtil.java b/java-fit-performer/src/main/java/com/couchbase/utils/ContentAsUtil.java index b38a6bf8f..ca52754c7 100644 --- a/java-fit-performer/src/main/java/com/couchbase/utils/ContentAsUtil.java +++ b/java-fit-performer/src/main/java/com/couchbase/utils/ContentAsUtil.java @@ -20,8 +20,8 @@ import com.couchbase.client.protocol.shared.ContentAs; import com.couchbase.client.protocol.shared.ContentTypes; import com.google.protobuf.ByteString; +import reactor.util.annotation.Nullable; -import java.util.List; import java.util.function.Supplier; public class ContentAsUtil { @@ -91,62 +91,38 @@ public static Try contentType(ContentAs contentAs, } } - public static Try> contentTypeList(ContentAs contentAs, - Supplier> asByteArray, - Supplier> asString, - Supplier> asJsonObject, - Supplier> asJsonArray, - Supplier> asBoolean, - Supplier> asInteger, - Supplier> asDouble) { - try { - if (contentAs.hasAsByteArray()) { - return new Try<>(asByteArray.get().stream() - .map(v -> v != null - ? ContentTypes.newBuilder().setContentAsBytes(ByteString.copyFrom(v)).build() - : getNullContentType().value()) - .toList()); - } else if (contentAs.hasAsString()) { - return new Try<>(asString.get().stream() - .map(v -> v != null - ? ContentTypes.newBuilder().setContentAsString(v).build() - : getNullContentType().value()).toList()); - } else if (contentAs.hasAsJsonObject()) { - return new Try<>(asJsonObject.get().stream() - .map(v -> v != null - ? ContentTypes.newBuilder().setContentAsBytes(ByteString.copyFrom(v.toBytes())).build() - : getNullContentType().value()) - .toList()); - } else if (contentAs.hasAsJsonArray()) { - return new Try<>(asJsonArray.get().stream() - .map(v -> v != null - ? ContentTypes.newBuilder().setContentAsBytes(ByteString.copyFrom(v.toBytes())).build() - : getNullContentType().value()) - .toList()); - } else if (contentAs.getAsBoolean()) { - return new Try<>(asBoolean.get().stream() - .map(v -> v != null - ? ContentTypes.newBuilder().setContentAsBool(v).build() - : getNullContentType().value()) - .toList()); - } else if (contentAs.hasAsInteger()) { - return new Try<>(asInteger.get().stream() - .map(v -> v != null - ? ContentTypes.newBuilder().setContentAsInt64(v).build() - : getNullContentType().value()) - .toList()); - } else if (contentAs.hasAsFloatingPoint()) { - return new Try<>(asDouble.get().stream() - .map(v -> v != null - ?ContentTypes.newBuilder().setContentAsDouble(v).build() - : getNullContentType().value()) - .toList()); - } else { - throw new UnsupportedOperationException("Java performer cannot handle contentAs " + contentAs.toString()); - } - } catch (RuntimeException err) { - return new Try<>(err); + public static Class toJavaClass(ContentAs contentAs) { + return switch (contentAs.getAsCase()) { + case AS_STRING -> String.class; + case AS_BYTE_ARRAY -> byte[].class; + case AS_JSON_OBJECT -> JsonObject.class; + case AS_JSON_ARRAY -> JsonArray.class; + case AS_BOOLEAN -> Boolean.class; + case AS_INTEGER -> Integer.class; + case AS_FLOATING_POINT -> Double.class; + + default -> throw new UnsupportedOperationException("Java performer cannot handle contentAs " + contentAs); + }; + } + + public static ContentTypes toFitContent(@Nullable Object value, ContentAs contentAs) { + ContentTypes.Builder builder = ContentTypes.newBuilder(); + + if (value == null) return builder.setContentAsNull(ContentTypes.NullValue.getDefaultInstance()).build(); + + switch (contentAs.getAsCase()) { + case AS_STRING -> builder.setContentAsString((String) value); + case AS_BYTE_ARRAY -> builder.setContentAsBytes(ByteString.copyFrom((byte[]) value)); + case AS_JSON_OBJECT -> builder.setContentAsBytes(ByteString.copyFrom(((JsonObject) value).toBytes())); + case AS_JSON_ARRAY -> builder.setContentAsBytes(ByteString.copyFrom(((JsonArray) value).toBytes())); + case AS_BOOLEAN -> builder.setContentAsBool((Boolean) value); + case AS_INTEGER -> builder.setContentAsInt64((Integer) value); + case AS_FLOATING_POINT -> builder.setContentAsDouble((Double) value); + + default -> throw new UnsupportedOperationException("Java performer cannot handle contentAs " + contentAs); } + + return builder.build(); } public static byte[] convert(ContentTypes content) { From 749e156e2590f45a96b73ab3e956b783e068fa79 Mon Sep 17 00:00:00 2001 From: Graham Pople Date: Tue, 15 Oct 2024 15:15:30 +0100 Subject: [PATCH 37/73] SCBC-471: Bump dependencies Change-Id: Ib590b43c227e6fe888416110060ebb0ec981e4b0 Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/217957 Tested-by: Build Bot Reviewed-by: David Nault --- .mvn/maven.config | 2 +- pom.xml | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.mvn/maven.config b/.mvn/maven.config index 92af3731b..54bc6dc85 100644 --- a/.mvn/maven.config +++ b/.mvn/maven.config @@ -1,2 +1,2 @@ -Dscala.compat.version=2.12 --Dscala.compat.library.version=2.12.19 +-Dscala.compat.library.version=2.12.20 diff --git a/pom.xml b/pom.xml index decb38a1d..a06312da1 100644 --- a/pom.xml +++ b/pom.xml @@ -27,14 +27,14 @@ 0.7.5-SNAPSHOT 1.7.5-SNAPSHOT - 5.9.1 + 5.11.2 3.23.1 2.20.0 2.17.2 3.6.9 1.0.4 - 1.0.9.RELEASE + 1.0.10.RELEASE 1.12.9 1.7.36 4.4.3 @@ -42,8 +42,9 @@ 1.2.11 2.9.1 + 0.8.0 - 0.14.3 + 0.14.10 2.9.4 @@ -51,9 +52,10 @@ 0.9.23 3.6.12 - 1.5.1 - 3.2.0 - 2.11.0 + 1.6.0 + + 3.3.1 + 2.12.0 3.2.4 3.2.1 From 6c1fd2360083f6cb7541852485ae29efeec8d5b9 Mon Sep 17 00:00:00 2001 From: Graham Pople Date: Tue, 15 Oct 2024 16:37:11 +0100 Subject: [PATCH 38/73] Gardening: DRY mocking of Core This is setting up for a future change that adds CoreResources. Change-Id: Ifd0676618900b3b29d4391912c7d1837f175ade9 Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/217960 Reviewed-by: Graham Pople Tested-by: Build Bot --- .../client/core/CoreContextTest.java | 3 +- .../com/couchbase/client/core/TimerTest.java | 5 ++- .../DefaultConfigurationProviderTest.java | 3 +- .../config/loader/BaseBucketLoaderTest.java | 3 +- .../ClusterManagerBucketLoaderTest.java | 3 +- .../loader/KeyValueBucketLoaderTest.java | 3 +- .../ClusterManagerBucketRefresherTest.java | 3 +- .../config/refresher/GlobalRefresherTest.java | 3 +- .../KeyValueBucketRefresherTest.java | 3 +- .../core/endpoint/BaseEndpointTest.java | 3 +- .../netty/kv/ErrorMapLoadingHandlerTest.java | 3 +- .../kv/FeatureNegotiatingHandlerTest.java | 3 +- .../netty/kv/KeyValueMessageHandlerTest.java | 5 ++- .../io/netty/kv/MemcacheProtocolTest.java | 3 +- .../kv/SaslListMechanismsHandlerTest.java | 3 +- .../io/netty/kv/SelectBucketHandlerTest.java | 3 +- .../manager/ManagerMessageHandlerTest.java | 3 +- .../netty/query/QueryMessageHandlerTest.java | 3 +- .../client/core/kv/OrchestratorProxy.java | 3 +- .../client/core/msg/RequestContextTest.java | 7 ++-- .../client/core/node/KeyValueLocatorTest.java | 3 +- .../couchbase/client/core/node/NodeTest.java | 3 +- .../core/retry/RetryOrchestratorTest.java | 3 +- .../core/service/PooledServiceTest.java | 5 ++- .../couchbase/client/core/util/MockUtil.java | 41 +++++++++++++++++++ 25 files changed, 94 insertions(+), 29 deletions(-) create mode 100644 core-io/src/test/java/com/couchbase/client/core/util/MockUtil.java diff --git a/core-io/src/test/java/com/couchbase/client/core/CoreContextTest.java b/core-io/src/test/java/com/couchbase/client/core/CoreContextTest.java index 546d259d8..a7043d1ad 100644 --- a/core-io/src/test/java/com/couchbase/client/core/CoreContextTest.java +++ b/core-io/src/test/java/com/couchbase/client/core/CoreContextTest.java @@ -21,6 +21,7 @@ import com.couchbase.client.core.env.CoreEnvironment; import org.junit.jupiter.api.Test; +import static com.couchbase.client.core.util.MockUtil.mockCore; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.mock; @@ -33,7 +34,7 @@ class CoreContextTest { void getAndExportProperties() { long id = 12345; CoreEnvironment env = mock(CoreEnvironment.class); - Core core = mock(Core.class); + Core core = mockCore(); Authenticator authenticator = mock(Authenticator.class); CoreContext ctx = new CoreContext(core, id, env, authenticator); diff --git a/core-io/src/test/java/com/couchbase/client/core/TimerTest.java b/core-io/src/test/java/com/couchbase/client/core/TimerTest.java index af76bbeab..2e090c52a 100644 --- a/core-io/src/test/java/com/couchbase/client/core/TimerTest.java +++ b/core-io/src/test/java/com/couchbase/client/core/TimerTest.java @@ -24,6 +24,7 @@ import java.time.Duration; +import static com.couchbase.client.core.util.MockUtil.mockCore; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; @@ -38,7 +39,7 @@ class TimerTest { void performsBackpressureWhenOverLimit() throws Exception { Timer timer = Timer.createAndStart(2); try { - Core core = mock(Core.class); + Core core = mockCore(); assertEquals(0, timer.outstandingForRetry()); Request request = mock(Request.class); @@ -81,4 +82,4 @@ void assignsRegistrationToRequest() { } } -} \ No newline at end of file +} diff --git a/core-io/src/test/java/com/couchbase/client/core/config/DefaultConfigurationProviderTest.java b/core-io/src/test/java/com/couchbase/client/core/config/DefaultConfigurationProviderTest.java index 7424592c5..9c3aeabf6 100644 --- a/core-io/src/test/java/com/couchbase/client/core/config/DefaultConfigurationProviderTest.java +++ b/core-io/src/test/java/com/couchbase/client/core/config/DefaultConfigurationProviderTest.java @@ -33,6 +33,7 @@ import com.couchbase.client.core.msg.kv.GetCollectionIdResponse; import com.couchbase.client.core.topology.NodeIdentifier; import com.couchbase.client.core.util.ConnectionString; +import com.couchbase.client.core.util.MockUtil; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; @@ -287,7 +288,7 @@ static Core mockCore(CoreEnvironment env) { CoreContext ctx = mock(CoreContext.class); when(ctx.environment()).thenReturn(env); - Core core = mock(Core.class); + Core core = MockUtil.mockCore(); when(core.context()).thenReturn(ctx); when(core.environment()).thenReturn(env); return core; diff --git a/core-io/src/test/java/com/couchbase/client/core/config/loader/BaseBucketLoaderTest.java b/core-io/src/test/java/com/couchbase/client/core/config/loader/BaseBucketLoaderTest.java index ed2c892dd..2bf2b6668 100644 --- a/core-io/src/test/java/com/couchbase/client/core/config/loader/BaseBucketLoaderTest.java +++ b/core-io/src/test/java/com/couchbase/client/core/config/loader/BaseBucketLoaderTest.java @@ -36,6 +36,7 @@ import java.util.Optional; import static com.couchbase.client.core.topology.TopologyTestUtils.nodeId; +import static com.couchbase.client.core.util.MockUtil.mockCore; import static com.couchbase.client.test.Util.readResource; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -61,7 +62,7 @@ class BaseBucketLoaderTest { @BeforeEach void setup() { CoreEnvironment env = mock(CoreEnvironment.class); - core = mock(Core.class); + core = mockCore(); CoreContext ctx = new CoreContext(core, 1, env, mock(Authenticator.class)); when(core.context()).thenReturn(ctx); } diff --git a/core-io/src/test/java/com/couchbase/client/core/config/loader/ClusterManagerBucketLoaderTest.java b/core-io/src/test/java/com/couchbase/client/core/config/loader/ClusterManagerBucketLoaderTest.java index 012311ab0..16629b213 100644 --- a/core-io/src/test/java/com/couchbase/client/core/config/loader/ClusterManagerBucketLoaderTest.java +++ b/core-io/src/test/java/com/couchbase/client/core/config/loader/ClusterManagerBucketLoaderTest.java @@ -34,6 +34,7 @@ import java.util.concurrent.atomic.AtomicReference; +import static com.couchbase.client.core.util.MockUtil.mockCore; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -61,7 +62,7 @@ void setup() { when(env.timeoutConfig()).thenReturn(TimeoutConfig.create()); when(env.retryStrategy()).thenReturn(BestEffortRetryStrategy.INSTANCE); - core = mock(Core.class); + core = mockCore(); CoreContext ctx = new CoreContext(core, 1, env, mock(Authenticator.class)); when(core.context()).thenReturn(ctx); loader = new ClusterManagerBucketLoader(core); diff --git a/core-io/src/test/java/com/couchbase/client/core/config/loader/KeyValueBucketLoaderTest.java b/core-io/src/test/java/com/couchbase/client/core/config/loader/KeyValueBucketLoaderTest.java index 7ec17af75..3c9b8cb6c 100644 --- a/core-io/src/test/java/com/couchbase/client/core/config/loader/KeyValueBucketLoaderTest.java +++ b/core-io/src/test/java/com/couchbase/client/core/config/loader/KeyValueBucketLoaderTest.java @@ -35,6 +35,7 @@ import java.util.concurrent.atomic.AtomicReference; +import static com.couchbase.client.core.util.MockUtil.mockCore; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -62,7 +63,7 @@ void setup() { when(env.timeoutConfig()).thenReturn(TimeoutConfig.create()); when(env.retryStrategy()).thenReturn(BestEffortRetryStrategy.INSTANCE); - core = mock(Core.class); + core = mockCore(); CoreContext ctx = new CoreContext(core, 1, env, mock(Authenticator.class)); when(core.context()).thenReturn(ctx); loader = new KeyValueBucketLoader(core); diff --git a/core-io/src/test/java/com/couchbase/client/core/config/refresher/ClusterManagerBucketRefresherTest.java b/core-io/src/test/java/com/couchbase/client/core/config/refresher/ClusterManagerBucketRefresherTest.java index c6255cebb..971a7507c 100644 --- a/core-io/src/test/java/com/couchbase/client/core/config/refresher/ClusterManagerBucketRefresherTest.java +++ b/core-io/src/test/java/com/couchbase/client/core/config/refresher/ClusterManagerBucketRefresherTest.java @@ -34,6 +34,7 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; +import static com.couchbase.client.core.util.MockUtil.mockCore; import static com.couchbase.client.test.Util.waitUntilCondition; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; @@ -55,7 +56,7 @@ void beforeEach() { env = CoreEnvironment.builder().eventBus(eventBus).build(); CoreContext coreContext = mock(CoreContext.class); - core = mock(Core.class); + core = mockCore(); when(core.context()).thenReturn(coreContext); when(coreContext.environment()).thenReturn(env); ConfigurationProvider provider = mock(ConfigurationProvider.class); diff --git a/core-io/src/test/java/com/couchbase/client/core/config/refresher/GlobalRefresherTest.java b/core-io/src/test/java/com/couchbase/client/core/config/refresher/GlobalRefresherTest.java index 60f067c5d..e66ae072d 100644 --- a/core-io/src/test/java/com/couchbase/client/core/config/refresher/GlobalRefresherTest.java +++ b/core-io/src/test/java/com/couchbase/client/core/config/refresher/GlobalRefresherTest.java @@ -42,6 +42,7 @@ import static com.couchbase.client.core.topology.TopologyTestUtils.node; import static com.couchbase.client.core.util.CbCollections.listOf; import static com.couchbase.client.core.util.CbCollections.mapOf; +import static com.couchbase.client.core.util.MockUtil.mockCore; import static com.couchbase.client.test.Util.waitUntilCondition; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -68,7 +69,7 @@ void beforeEach() { .build(); CoreContext coreContext = mock(CoreContext.class); - core = mock(Core.class); + core = mockCore(); when(core.context()).thenReturn(coreContext); when(coreContext.environment()).thenReturn(env); } diff --git a/core-io/src/test/java/com/couchbase/client/core/config/refresher/KeyValueBucketRefresherTest.java b/core-io/src/test/java/com/couchbase/client/core/config/refresher/KeyValueBucketRefresherTest.java index 4df195c9c..88b01147e 100644 --- a/core-io/src/test/java/com/couchbase/client/core/config/refresher/KeyValueBucketRefresherTest.java +++ b/core-io/src/test/java/com/couchbase/client/core/config/refresher/KeyValueBucketRefresherTest.java @@ -40,6 +40,7 @@ import static com.couchbase.client.core.topology.TopologyTestUtils.nodeInfo; import static com.couchbase.client.core.util.CbCollections.listOf; import static com.couchbase.client.core.util.CbCollections.mapOf; +import static com.couchbase.client.core.util.MockUtil.mockCore; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.isA; import static org.mockito.Mockito.doAnswer; @@ -64,7 +65,7 @@ void beforeEach() { .build(); CoreContext coreContext = mock(CoreContext.class); - core = mock(Core.class); + core = mockCore(); when(core.context()).thenReturn(coreContext); when(coreContext.environment()).thenReturn(env); } diff --git a/core-io/src/test/java/com/couchbase/client/core/endpoint/BaseEndpointTest.java b/core-io/src/test/java/com/couchbase/client/core/endpoint/BaseEndpointTest.java index 3d4b05945..5e1eda3fd 100644 --- a/core-io/src/test/java/com/couchbase/client/core/endpoint/BaseEndpointTest.java +++ b/core-io/src/test/java/com/couchbase/client/core/endpoint/BaseEndpointTest.java @@ -57,6 +57,7 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; +import static com.couchbase.client.core.util.MockUtil.mockCore; import static com.couchbase.client.test.Util.waitUntilCondition; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -91,7 +92,7 @@ void beforeEach() { eventLoopGroup = new NioEventLoopGroup(1); eventBus = new SimpleEventBus(true, Collections.singletonList(EndpointStateChangedEvent.class)); environment = CoreEnvironment.builder().eventBus(eventBus).build(); - CoreContext coreContext = new CoreContext(mock(Core.class), 1, environment, authenticator); + CoreContext coreContext = new CoreContext(mockCore(), 1, environment, authenticator); ctx = new ServiceContext(coreContext, LOCALHOST, 1234, ServiceType.KV, Optional.empty()); } diff --git a/core-io/src/test/java/com/couchbase/client/core/io/netty/kv/ErrorMapLoadingHandlerTest.java b/core-io/src/test/java/com/couchbase/client/core/io/netty/kv/ErrorMapLoadingHandlerTest.java index 87aad6089..5dd1bccc6 100644 --- a/core-io/src/test/java/com/couchbase/client/core/io/netty/kv/ErrorMapLoadingHandlerTest.java +++ b/core-io/src/test/java/com/couchbase/client/core/io/netty/kv/ErrorMapLoadingHandlerTest.java @@ -49,6 +49,7 @@ import static com.couchbase.client.core.io.netty.kv.ProtocolVerifier.decodeHexDump; import static com.couchbase.client.core.io.netty.kv.ProtocolVerifier.verifyRequest; +import static com.couchbase.client.core.util.MockUtil.mockCore; import static com.couchbase.client.test.Util.readResource; import static com.couchbase.client.test.Util.waitUntilCondition; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -77,7 +78,7 @@ protected void beforeEach() { when(env.eventBus()).thenReturn(eventBus); when(env.timeoutConfig()).thenReturn(timeoutConfig); when(timeoutConfig.connectTimeout()).thenReturn(Duration.ofMillis(1000)); - CoreContext coreContext = new CoreContext(mock(Core.class), 1, env, mock(Authenticator.class)); + CoreContext coreContext = new CoreContext(mockCore(), 1, env, mock(Authenticator.class)); endpointContext = new EndpointContext(coreContext, new HostAndPort("127.0.0.1", 1234), null, ServiceType.KV, Optional.empty(), Optional.empty(), Optional.empty()); } diff --git a/core-io/src/test/java/com/couchbase/client/core/io/netty/kv/FeatureNegotiatingHandlerTest.java b/core-io/src/test/java/com/couchbase/client/core/io/netty/kv/FeatureNegotiatingHandlerTest.java index 6ac63c58e..cb5cadded 100644 --- a/core-io/src/test/java/com/couchbase/client/core/io/netty/kv/FeatureNegotiatingHandlerTest.java +++ b/core-io/src/test/java/com/couchbase/client/core/io/netty/kv/FeatureNegotiatingHandlerTest.java @@ -18,6 +18,7 @@ import static com.couchbase.client.core.io.netty.kv.ProtocolVerifier.decodeHexDump; import static com.couchbase.client.core.io.netty.kv.ProtocolVerifier.verifyRequest; +import static com.couchbase.client.core.util.MockUtil.mockCore; import static com.couchbase.client.test.Util.readResource; import static com.couchbase.client.test.Util.waitUntilCondition; import static java.nio.charset.StandardCharsets.UTF_8; @@ -87,7 +88,7 @@ protected void beforeEach() { when(env.timeoutConfig()).thenReturn(timeoutConfig); when(env.userAgent()).thenReturn(new UserAgent("some", Optional.empty(), Optional.empty(), Optional.empty())); when(timeoutConfig.connectTimeout()).thenReturn(Duration.ofMillis(1000)); - CoreContext coreContext = new CoreContext(mock(Core.class), 1, env, mock(Authenticator.class)); + CoreContext coreContext = new CoreContext(mockCore(), 1, env, mock(Authenticator.class)); endpointContext = new EndpointContext(coreContext, new HostAndPort("127.0.0.1", 1234), null, ServiceType.KV, Optional.empty(), Optional.empty(), Optional.empty()); } diff --git a/core-io/src/test/java/com/couchbase/client/core/io/netty/kv/KeyValueMessageHandlerTest.java b/core-io/src/test/java/com/couchbase/client/core/io/netty/kv/KeyValueMessageHandlerTest.java index 623276d53..6c22df92e 100644 --- a/core-io/src/test/java/com/couchbase/client/core/io/netty/kv/KeyValueMessageHandlerTest.java +++ b/core-io/src/test/java/com/couchbase/client/core/io/netty/kv/KeyValueMessageHandlerTest.java @@ -52,6 +52,7 @@ import java.util.Set; import java.util.concurrent.ExecutionException; +import static com.couchbase.client.core.util.MockUtil.mockCore; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertInstanceOf; @@ -77,7 +78,7 @@ class KeyValueMessageHandlerTest { @BeforeAll static void setup() { ENV = CoreEnvironment.builder().eventBus(new SimpleEventBus(true)).build(); - Core core = mock(Core.class); + Core core = mockCore(); CoreContext coreContext = new CoreContext(core, 1, ENV, PasswordAuthenticator.create("foo", "bar")); ConfigurationProvider configurationProvider = mock(ConfigurationProvider.class); when(configurationProvider.collectionMap()).thenReturn(new CollectionMap()); @@ -282,4 +283,4 @@ void incrementsNotMyVbucketIndicator() { } } -} \ No newline at end of file +} diff --git a/core-io/src/test/java/com/couchbase/client/core/io/netty/kv/MemcacheProtocolTest.java b/core-io/src/test/java/com/couchbase/client/core/io/netty/kv/MemcacheProtocolTest.java index eeecfcf1d..10350b8c7 100644 --- a/core-io/src/test/java/com/couchbase/client/core/io/netty/kv/MemcacheProtocolTest.java +++ b/core-io/src/test/java/com/couchbase/client/core/io/netty/kv/MemcacheProtocolTest.java @@ -37,6 +37,7 @@ import java.util.function.Function; import static com.couchbase.client.core.util.CbCollections.setOf; +import static com.couchbase.client.core.util.MockUtil.mockCore; import static java.util.Collections.emptySet; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; @@ -61,7 +62,7 @@ class MemcacheProtocolTest { void before() { eventBus = mock(EventBus.class); CoreEnvironment env = mock(CoreEnvironment.class); - context = new CoreContext(mock(Core.class), 1, env, mock(Authenticator.class)); + context = new CoreContext(mockCore(), 1, env, mock(Authenticator.class)); when(env.eventBus()).thenReturn(eventBus); } diff --git a/core-io/src/test/java/com/couchbase/client/core/io/netty/kv/SaslListMechanismsHandlerTest.java b/core-io/src/test/java/com/couchbase/client/core/io/netty/kv/SaslListMechanismsHandlerTest.java index 5da7c013f..c7160bec2 100644 --- a/core-io/src/test/java/com/couchbase/client/core/io/netty/kv/SaslListMechanismsHandlerTest.java +++ b/core-io/src/test/java/com/couchbase/client/core/io/netty/kv/SaslListMechanismsHandlerTest.java @@ -51,6 +51,7 @@ import static com.couchbase.client.core.io.netty.kv.ProtocolVerifier.decodeHexDump; import static com.couchbase.client.core.io.netty.kv.ProtocolVerifier.verifyRequest; +import static com.couchbase.client.core.util.MockUtil.mockCore; import static com.couchbase.client.test.Util.readResource; import static com.couchbase.client.test.Util.waitUntilCondition; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -78,7 +79,7 @@ protected void beforeEach() { when(env.eventBus()).thenReturn(eventBus); when(env.timeoutConfig()).thenReturn(timeoutConfig); when(timeoutConfig.connectTimeout()).thenReturn(Duration.ofMillis(1000)); - CoreContext coreContext = new CoreContext(mock(Core.class), 1, env, mock(Authenticator.class)); + CoreContext coreContext = new CoreContext(mockCore(), 1, env, mock(Authenticator.class)); endpointContext = new EndpointContext(coreContext, new HostAndPort("127.0.0.1", 1234), null, ServiceType.KV, Optional.empty(), Optional.empty(), Optional.empty()); } diff --git a/core-io/src/test/java/com/couchbase/client/core/io/netty/kv/SelectBucketHandlerTest.java b/core-io/src/test/java/com/couchbase/client/core/io/netty/kv/SelectBucketHandlerTest.java index 8823908bd..54dc3b6ac 100644 --- a/core-io/src/test/java/com/couchbase/client/core/io/netty/kv/SelectBucketHandlerTest.java +++ b/core-io/src/test/java/com/couchbase/client/core/io/netty/kv/SelectBucketHandlerTest.java @@ -42,6 +42,7 @@ import java.util.Optional; import java.util.concurrent.TimeoutException; +import static com.couchbase.client.core.util.MockUtil.mockCore; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -71,7 +72,7 @@ void setup() { when(env.eventBus()).thenReturn(simpleEventBus); when(env.timeoutConfig()).thenReturn(timeoutConfig); when(timeoutConfig.connectTimeout()).thenReturn(Duration.ofMillis(10)); - CoreContext coreContext = new CoreContext(mock(Core.class), 1, env, mock(Authenticator.class)); + CoreContext coreContext = new CoreContext(mockCore(), 1, env, mock(Authenticator.class)); endpointContext = new EndpointContext(coreContext, new HostAndPort("127.0.0.1", 1234), null, ServiceType.KV, Optional.empty(), Optional.empty(), Optional.empty()); } diff --git a/core-io/src/test/java/com/couchbase/client/core/io/netty/manager/ManagerMessageHandlerTest.java b/core-io/src/test/java/com/couchbase/client/core/io/netty/manager/ManagerMessageHandlerTest.java index 17b81b213..14185a1ff 100644 --- a/core-io/src/test/java/com/couchbase/client/core/io/netty/manager/ManagerMessageHandlerTest.java +++ b/core-io/src/test/java/com/couchbase/client/core/io/netty/manager/ManagerMessageHandlerTest.java @@ -55,6 +55,7 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicBoolean; +import static com.couchbase.client.core.util.MockUtil.mockCore; import static com.couchbase.client.test.Util.readResource; import static com.couchbase.client.test.Util.waitUntilCondition; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -90,7 +91,7 @@ static void teardown() { */ @Test void returnsNewConfigsWhenChunked() throws Exception { - CoreContext ctx = new CoreContext(mock(Core.class), 1, ENV, PasswordAuthenticator.create(USER, PASS)); + CoreContext ctx = new CoreContext(mockCore(), 1, ENV, PasswordAuthenticator.create(USER, PASS)); BaseEndpoint endpoint = mock(BaseEndpoint.class); EndpointContext endpointContext = mock(EndpointContext.class); when(endpointContext.environment()).thenReturn(ENV); diff --git a/core-io/src/test/java/com/couchbase/client/core/io/netty/query/QueryMessageHandlerTest.java b/core-io/src/test/java/com/couchbase/client/core/io/netty/query/QueryMessageHandlerTest.java index 962466138..5db77f074 100644 --- a/core-io/src/test/java/com/couchbase/client/core/io/netty/query/QueryMessageHandlerTest.java +++ b/core-io/src/test/java/com/couchbase/client/core/io/netty/query/QueryMessageHandlerTest.java @@ -47,6 +47,7 @@ import java.util.Optional; +import static com.couchbase.client.core.util.MockUtil.mockCore; import static com.couchbase.client.test.Util.readResource; import static com.couchbase.client.test.Util.waitUntilCondition; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -73,7 +74,7 @@ class QueryMessageHandlerTest { @BeforeAll static void setup() { ENV = CoreEnvironment.create(); - CORE_CTX = new CoreContext(mock(Core.class), 1, ENV, PasswordAuthenticator.create("user", "pass")); + CORE_CTX = new CoreContext(mockCore(), 1, ENV, PasswordAuthenticator.create("user", "pass")); ENDPOINT_CTX = new EndpointContext(CORE_CTX, new HostAndPort("127.0.0.1", 1234), NoopCircuitBreaker.INSTANCE, ServiceType.QUERY, Optional.empty(), Optional.empty(), Optional.empty()); } diff --git a/core-io/src/test/java/com/couchbase/client/core/kv/OrchestratorProxy.java b/core-io/src/test/java/com/couchbase/client/core/kv/OrchestratorProxy.java index 0f35de273..5d19fb766 100644 --- a/core-io/src/test/java/com/couchbase/client/core/kv/OrchestratorProxy.java +++ b/core-io/src/test/java/com/couchbase/client/core/kv/OrchestratorProxy.java @@ -45,6 +45,7 @@ import java.util.Optional; import java.util.UUID; +import static com.couchbase.client.core.util.MockUtil.mockCore; import static org.mockito.ArgumentMatchers.isA; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; @@ -74,7 +75,7 @@ public OrchestratorProxy(final CoreEnvironment environment, boolean capabilityEn when(configurationProvider.config()).thenReturn(clusterConfig); // Set up core - core = mock(Core.class); + core = mockCore(); CoreContext coreContext = new CoreContext(core, 1, environment, null); when(core.context()).thenReturn(coreContext); when(core.configurationProvider()).thenReturn(configurationProvider); diff --git a/core-io/src/test/java/com/couchbase/client/core/msg/RequestContextTest.java b/core-io/src/test/java/com/couchbase/client/core/msg/RequestContextTest.java index 02a4fe7e2..93dead274 100644 --- a/core-io/src/test/java/com/couchbase/client/core/msg/RequestContextTest.java +++ b/core-io/src/test/java/com/couchbase/client/core/msg/RequestContextTest.java @@ -25,6 +25,7 @@ import java.util.HashMap; import java.util.Map; +import static com.couchbase.client.core.util.MockUtil.mockCore; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; @@ -40,7 +41,7 @@ class RequestContextTest { @Test void requestCancellation() { Request request = mock(Request.class); - Core core = mock(Core.class); + Core core = mockCore(); RequestContext ctx = new RequestContext(new CoreContext(core, 1, null, mock(Authenticator.class)), request); ctx.cancel(); @@ -50,7 +51,7 @@ void requestCancellation() { @Test void customPayloadCanBeAttached() { Request request = mock(Request.class); - Core core = mock(Core.class); + Core core = mockCore(); RequestContext ctx = new RequestContext(new CoreContext(core, 1, null, mock(Authenticator.class)), request); assertNull(ctx.clientContext()); @@ -61,4 +62,4 @@ void customPayloadCanBeAttached() { assertEquals(payload, ctx.clientContext()); } -} \ No newline at end of file +} diff --git a/core-io/src/test/java/com/couchbase/client/core/node/KeyValueLocatorTest.java b/core-io/src/test/java/com/couchbase/client/core/node/KeyValueLocatorTest.java index 9c5cd4b2c..9656e0373 100644 --- a/core-io/src/test/java/com/couchbase/client/core/node/KeyValueLocatorTest.java +++ b/core-io/src/test/java/com/couchbase/client/core/node/KeyValueLocatorTest.java @@ -35,6 +35,7 @@ import java.util.Collections; import java.util.List; +import static com.couchbase.client.core.util.MockUtil.mockCore; import static com.couchbase.client.core.topology.TopologyTestUtils.nodeId; import static com.couchbase.client.core.topology.TopologyTestUtils.nodeInfo; import static java.nio.charset.StandardCharsets.UTF_8; @@ -68,7 +69,7 @@ void locateGetRequestForCouchbaseBucket() { CouchbaseBucketConfig bucketMock = mock(CouchbaseBucketConfig.class); when(getRequestMock.bucket()).thenReturn("bucket"); when(getRequestMock.key()).thenReturn("key".getBytes(UTF_8)); - CoreContext coreContext = new CoreContext(mock(Core.class), 1, mock(CoreEnvironment.class), mock(Authenticator.class)); + CoreContext coreContext = new CoreContext(mockCore(), 1, mock(CoreEnvironment.class), mock(Authenticator.class)); when(getRequestMock.context()).thenReturn(new RequestContext(coreContext, getRequestMock)); when(configMock.bucketConfig("bucket")).thenReturn(bucketMock); when(bucketMock.nodes()).thenReturn(Arrays.asList(nodeInfo1, nodeInfo2)); diff --git a/core-io/src/test/java/com/couchbase/client/core/node/NodeTest.java b/core-io/src/test/java/com/couchbase/client/core/node/NodeTest.java index e8bf41597..818df5d83 100644 --- a/core-io/src/test/java/com/couchbase/client/core/node/NodeTest.java +++ b/core-io/src/test/java/com/couchbase/client/core/node/NodeTest.java @@ -53,6 +53,7 @@ import java.util.concurrent.atomic.AtomicReference; import static com.couchbase.client.core.util.CbCollections.listOf; +import static com.couchbase.client.core.util.MockUtil.mockCore; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertInstanceOf; @@ -80,7 +81,7 @@ private static NodeIdentifier testNodeIdentifier() { @BeforeAll static void beforeAll() { - Core core = mock(Core.class); + Core core = mockCore(); ENV = CoreEnvironment .builder() .build(); diff --git a/core-io/src/test/java/com/couchbase/client/core/retry/RetryOrchestratorTest.java b/core-io/src/test/java/com/couchbase/client/core/retry/RetryOrchestratorTest.java index 98520d055..4d448df1b 100644 --- a/core-io/src/test/java/com/couchbase/client/core/retry/RetryOrchestratorTest.java +++ b/core-io/src/test/java/com/couchbase/client/core/retry/RetryOrchestratorTest.java @@ -36,6 +36,7 @@ import java.util.concurrent.TimeUnit; import java.util.function.Function; +import static com.couchbase.client.core.util.MockUtil.mockCore; import static com.couchbase.client.test.Util.waitUntilCondition; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -80,7 +81,7 @@ void cancelIfNoMoreRetriesAllowed() { CoreEnvironment env = mock(CoreEnvironment.class); SimpleEventBus eventBus = new SimpleEventBus(true); when(env.eventBus()).thenReturn(eventBus); - CoreContext context = new CoreContext(mock(Core.class), 1, env, mock(Authenticator.class)); + CoreContext context = new CoreContext(mockCore(), 1, env, mock(Authenticator.class)); RetryOrchestrator.maybeRetry(context, request, RetryReason.UNKNOWN); verify(request, times(1)).cancel(CancellationReason.noMoreRetries(RetryReason.UNKNOWN), Function.identity()); diff --git a/core-io/src/test/java/com/couchbase/client/core/service/PooledServiceTest.java b/core-io/src/test/java/com/couchbase/client/core/service/PooledServiceTest.java index 914b22ad2..a3ae9938a 100644 --- a/core-io/src/test/java/com/couchbase/client/core/service/PooledServiceTest.java +++ b/core-io/src/test/java/com/couchbase/client/core/service/PooledServiceTest.java @@ -45,6 +45,7 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; +import static com.couchbase.client.core.util.MockUtil.mockCore; import static com.couchbase.client.test.Util.waitUntilCondition; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.atLeastOnce; @@ -71,7 +72,7 @@ class PooledServiceTest { void beforeEach() { eventBus = new SimpleEventBus(true, Collections.singletonList(ServiceStateChangedEvent.class)); environment = CoreEnvironment.builder().eventBus(eventBus).build(); - CoreContext coreContext = new CoreContext(mock(Core.class), 1, environment, authenticator); + CoreContext coreContext = new CoreContext(mockCore(), 1, environment, authenticator); serviceContext = new ServiceContext(coreContext, "127.0.0.1", 1234, ServiceType.KV, Optional.empty()); } @@ -836,4 +837,4 @@ public > Endpoint select(R r, List Date: Wed, 30 Oct 2024 15:40:08 -0700 Subject: [PATCH 39/73] Gardening - fix missing braces in javadoc. Change-Id: I6bcc47c1e5f81631dcbe9b2e813bb2da87aac707 Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/218881 Reviewed-by: David Nault Tested-by: Build Bot --- .../com/couchbase/client/core/config/ClusterCapabilities.java | 2 +- .../java/com/couchbase/client/core/config/ConfigVersion.java | 2 +- .../java/com/couchbase/client/core/config/PartitionInfo.java | 2 +- .../main/java/com/couchbase/client/core/config/PortInfo.java | 2 +- .../com/couchbase/client/core/env/PasswordAuthenticator.java | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core-io/src/main/java/com/couchbase/client/core/config/ClusterCapabilities.java b/core-io/src/main/java/com/couchbase/client/core/config/ClusterCapabilities.java index d183d93bd..258cde681 100644 --- a/core-io/src/main/java/com/couchbase/client/core/config/ClusterCapabilities.java +++ b/core-io/src/main/java/com/couchbase/client/core/config/ClusterCapabilities.java @@ -22,7 +22,7 @@ * Contains all the cluster capabilities this SDK supports (depending on the server version, the cluster may * export more than these). * - * @deprecated In favor of {@link com.couchbase.client.core.topology.ClusterCapability + * @deprecated In favor of {@link com.couchbase.client.core.topology.ClusterCapability} */ @Deprecated public enum ClusterCapabilities { diff --git a/core-io/src/main/java/com/couchbase/client/core/config/ConfigVersion.java b/core-io/src/main/java/com/couchbase/client/core/config/ConfigVersion.java index 7d648313b..beb70bde7 100644 --- a/core-io/src/main/java/com/couchbase/client/core/config/ConfigVersion.java +++ b/core-io/src/main/java/com/couchbase/client/core/config/ConfigVersion.java @@ -23,7 +23,7 @@ import java.util.Objects; /** - * @deprecated In favor of {@link com.couchbase.client.core.topology.TopologyRevision + * @deprecated In favor of {@link com.couchbase.client.core.topology.TopologyRevision} */ @Deprecated @Stability.Internal diff --git a/core-io/src/main/java/com/couchbase/client/core/config/PartitionInfo.java b/core-io/src/main/java/com/couchbase/client/core/config/PartitionInfo.java index 5c6a5f3a8..8cb87778d 100644 --- a/core-io/src/main/java/com/couchbase/client/core/config/PartitionInfo.java +++ b/core-io/src/main/java/com/couchbase/client/core/config/PartitionInfo.java @@ -30,7 +30,7 @@ * Represents the partition information for a bucket. * * @since 1.1.0 - * @deprecated In favor of {@link com.couchbase.client.core.topology.PartitionMap + * @deprecated In favor of {@link com.couchbase.client.core.topology.PartitionMap} */ @Deprecated @JsonIgnoreProperties(ignoreUnknown = true) diff --git a/core-io/src/main/java/com/couchbase/client/core/config/PortInfo.java b/core-io/src/main/java/com/couchbase/client/core/config/PortInfo.java index fe0de1a4a..d5acd6297 100644 --- a/core-io/src/main/java/com/couchbase/client/core/config/PortInfo.java +++ b/core-io/src/main/java/com/couchbase/client/core/config/PortInfo.java @@ -31,7 +31,7 @@ import static java.util.Objects.requireNonNull; /** - * @deprecated In favor of {@link com.couchbase.client.core.topology.HostAndServicePorts + * @deprecated In favor of {@link com.couchbase.client.core.topology.HostAndServicePorts} */ @Deprecated @JsonIgnoreProperties(ignoreUnknown = true) diff --git a/core-io/src/main/java/com/couchbase/client/core/env/PasswordAuthenticator.java b/core-io/src/main/java/com/couchbase/client/core/env/PasswordAuthenticator.java index 88cd7e953..9e8aefa19 100644 --- a/core-io/src/main/java/com/couchbase/client/core/env/PasswordAuthenticator.java +++ b/core-io/src/main/java/com/couchbase/client/core/env/PasswordAuthenticator.java @@ -227,7 +227,7 @@ private Builder(String username, String password) { } /** - * @deprecated Please use {@link Builder(String, String) or {@link Builder(Supplier)} instead. + * @deprecated Please use {@link Builder(String, String)} or {@link Builder(Supplier)} instead. */ @Deprecated public Builder() { From dcc5f18b03106e23328757491ebec744d8a9db39 Mon Sep 17 00:00:00 2001 From: David Nault Date: Fri, 1 Nov 2024 13:51:26 -0700 Subject: [PATCH 40/73] JCBC-2167 (followup) Reactive API: publish on configurable scheduler Publish ReactiveCluster.disconnect() completion on the user scheduler. Change-Id: I9a47a47f9b0c640ff93d66203f01c36ac9c8c840 Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/219007 Tested-by: Build Bot Reviewed-by: Michael Reiche --- .../main/java/com/couchbase/client/java/ReactiveCluster.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/java-client/src/main/java/com/couchbase/client/java/ReactiveCluster.java b/java-client/src/main/java/com/couchbase/client/java/ReactiveCluster.java index c405afae0..ace1d1b0a 100644 --- a/java-client/src/main/java/com/couchbase/client/java/ReactiveCluster.java +++ b/java-client/src/main/java/com/couchbase/client/java/ReactiveCluster.java @@ -32,8 +32,8 @@ import com.couchbase.client.core.error.context.ReducedAnalyticsErrorContext; import com.couchbase.client.core.error.context.ReducedQueryErrorContext; import com.couchbase.client.core.error.context.ReducedSearchErrorContext; -import com.couchbase.client.core.util.ReactorOps; import com.couchbase.client.core.util.ConnectionString; +import com.couchbase.client.core.util.ReactorOps; import com.couchbase.client.java.analytics.AnalyticsAccessor; import com.couchbase.client.java.analytics.AnalyticsOptions; import com.couchbase.client.java.analytics.ReactiveAnalyticsResult; @@ -426,7 +426,7 @@ public Mono disconnect() { * @param timeout overriding the default disconnect timeout if needed. */ public Mono disconnect(final Duration timeout) { - return asyncCluster.disconnectInternal(timeout); + return reactor.publishOnUserScheduler(asyncCluster.disconnectInternal(timeout)); } /** From 7f36cf2976cde94d5e0fa4fcf8d186d3aa5344a6 Mon Sep 17 00:00:00 2001 From: David Nault Date: Fri, 1 Nov 2024 13:43:51 -0700 Subject: [PATCH 41/73] JCBC-2170 FIT: ClusterEnvironment.publishOnScheduler() Motivation ---------- Verify the SDK publishes results on the configured scheduler. Modifications ------------- Configure the FIT ClusterEnvironment to publish on a custom scheduler. Add UserSchedulerUtil.withSchedulerCheck(Mono/Flux). It returns a new Mono/Flux with doOn* callbacks that verify the signals are published on the custom scheduler. Everywhere the performer calls a reactive SDK method, wrap the result in `withSchedulerCheck()`. Change-Id: I40a5c053603d34ed8e86743f286c91bf77273010 Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/219006 Tested-by: Build Bot Reviewed-by: Michael Reiche --- .../java/com/couchbase/JavaPerformer.java | 7 ++ .../java/com/couchbase/LookupInHelper.java | 7 +- .../ReactiveJavaSdkCommandExecutor.java | 45 ++++----- .../couchbase/eventing/EventingHelper.java | 3 +- .../manager/BucketManagerHelper.java | 11 ++- .../manager/CollectionManagerHelper.java | 13 +-- .../query/QueryIndexManagerHelper.java | 16 ++-- .../com/couchbase/search/SearchHelper.java | 5 +- .../SingleQueryTransactionExecutor.java | 13 ++- .../twoway/TwoWayTransactionReactive.java | 4 +- .../java/com/couchbase/utils/OptionsUtil.java | 5 +- .../couchbase/utils/UserSchedulerUtil.java | 94 +++++++++++++++++++ 12 files changed, 168 insertions(+), 55 deletions(-) create mode 100644 java-fit-performer/src/main/java/com/couchbase/utils/UserSchedulerUtil.java diff --git a/java-fit-performer/src/main/java/com/couchbase/JavaPerformer.java b/java-fit-performer/src/main/java/com/couchbase/JavaPerformer.java index da76fd010..79ef92e94 100644 --- a/java-fit-performer/src/main/java/com/couchbase/JavaPerformer.java +++ b/java-fit-performer/src/main/java/com/couchbase/JavaPerformer.java @@ -81,6 +81,7 @@ import com.couchbase.utils.Capabilities; import com.couchbase.utils.ClusterConnection; import com.couchbase.utils.OptionsUtil; +import com.couchbase.utils.UserSchedulerUtil; import io.grpc.Server; import io.grpc.ServerBuilder; import io.grpc.Status; @@ -232,6 +233,12 @@ public void clusterConnectionCreate(ClusterConnectionCreateRequest request, var clusterEnvironment = OptionsUtil.convertClusterConfig(request, getCluster, onClusterConnectionClose); + // [if:3.7.5] first version that allows specifying custom publishOn scheduler + var userExecutorAndScheduler = UserSchedulerUtil.userExecutorAndScheduler(); + onClusterConnectionClose.add(userExecutorAndScheduler::dispose); + clusterEnvironment.publishOnScheduler(userExecutorAndScheduler::scheduler); + // [end] + var connection = new ClusterConnection(request.getClusterHostname(), request.getClusterUsername(), request.getClusterPassword(), diff --git a/java-fit-performer/src/main/java/com/couchbase/LookupInHelper.java b/java-fit-performer/src/main/java/com/couchbase/LookupInHelper.java index 6e4cd5373..0b67e4aec 100644 --- a/java-fit-performer/src/main/java/com/couchbase/LookupInHelper.java +++ b/java-fit-performer/src/main/java/com/couchbase/LookupInHelper.java @@ -48,6 +48,7 @@ import static com.couchbase.JavaSdkCommandExecutor.convertExceptionShared; import static com.couchbase.JavaSdkCommandExecutor.setSuccess; import static com.couchbase.client.performer.core.util.TimeUtil.getTimeNow; +import static com.couchbase.utils.UserSchedulerUtil.withSchedulerCheck; public class LookupInHelper { public static Result.Builder handleLookupIn(PerRun perRun, @@ -247,7 +248,7 @@ private static Mono handleLookupInReactive( result = collection.lookupIn(docId, specs); } - return result.doOnNext(r -> { + return withSchedulerCheck(result).doOnNext(r -> { out.setElapsedNanos(System.nanoTime() - start); if (command.getReturnResult()) { @@ -284,7 +285,7 @@ private static Mono handleLookupInAnyReplicaReactive( result = collection.lookupInAnyReplica(docId, specs); } - return result + return withSchedulerCheck(result) .doOnError(err -> err.printStackTrace()) .doFinally(v -> System.out.println("Finished")) .doOnNext(v -> { @@ -331,7 +332,7 @@ private static Mono handleLookupInAllReplicasReactive( out.setElapsedNanos(System.nanoTime() - start); var streamer = new FluxStreamer( - results, + withSchedulerCheck(results), perRun, req.getStreamConfig().getStreamId(), req.getStreamConfig(), diff --git a/java-fit-performer/src/main/java/com/couchbase/ReactiveJavaSdkCommandExecutor.java b/java-fit-performer/src/main/java/com/couchbase/ReactiveJavaSdkCommandExecutor.java index a7f59fed9..aa34bd329 100644 --- a/java-fit-performer/src/main/java/com/couchbase/ReactiveJavaSdkCommandExecutor.java +++ b/java-fit-performer/src/main/java/com/couchbase/ReactiveJavaSdkCommandExecutor.java @@ -66,6 +66,7 @@ // [if:3.6.0] import static com.couchbase.search.SearchHelper.handleSearchReactive; // [end] +import static com.couchbase.utils.UserSchedulerUtil.withSchedulerCheck; /** @@ -105,7 +106,7 @@ private Mono performOperationReactive(com.couchbase.client.protocol.sdk. Mono mr; if (options == null) mr = collection.insert(docId, content); else mr = collection.insert(docId, content, options); - return mr.map(r -> { + return withSchedulerCheck(mr).map(r -> { result.setElapsedNanos(System.nanoTime() - start); if (op.getReturnResult()) populateResult(result, r); else setSuccess(result); @@ -121,7 +122,7 @@ private Mono performOperationReactive(com.couchbase.client.protocol.sdk. Mono gr; if (options == null) gr = collection.get(docId); else gr = collection.get(docId, options); - return gr.map(r -> { + return withSchedulerCheck(gr).map(r -> { result.setElapsedNanos(System.nanoTime() - start); if (op.getReturnResult()) populateResult(request.getContentAs(), result, r); else setSuccess(result); @@ -138,7 +139,7 @@ private Mono performOperationReactive(com.couchbase.client.protocol.sdk. if (options == null) mr = collection.remove(docId); else mr = collection.remove(docId, options); result.setElapsedNanos(System.nanoTime() - start); - return mr.map(r -> { + return withSchedulerCheck(mr).map(r -> { if (op.getReturnResult()) populateResult(result, r); else setSuccess(result); return result.build(); @@ -154,7 +155,7 @@ private Mono performOperationReactive(com.couchbase.client.protocol.sdk. Mono mr; if (options == null) mr = collection.replace(docId, content); else mr = collection.replace(docId, content, options); - return mr.map(r -> { + return withSchedulerCheck(mr).map(r -> { result.setElapsedNanos(System.nanoTime() - start); if (op.getReturnResult()) populateResult(result, r); else setSuccess(result); @@ -172,7 +173,7 @@ private Mono performOperationReactive(com.couchbase.client.protocol.sdk. if (options == null) mr = collection.upsert(docId, content); else mr = collection.upsert(docId, content, options); result.setElapsedNanos(System.nanoTime() - start); - return mr.map(r -> { + return withSchedulerCheck(mr).map(r -> { if (op.getReturnResult()) populateResult(result, r); else setSuccess(result); return result.build(); @@ -189,7 +190,7 @@ private Mono performOperationReactive(com.couchbase.client.protocol.sdk. if (options != null) results = collection.scan(scanType, options); else results = collection.scan(scanType); result.setElapsedNanos(System.nanoTime() - start); - var streamer = new FluxStreamer(results, perRun, request.getStreamConfig().getStreamId(), request.getStreamConfig(), + var streamer = new FluxStreamer(withSchedulerCheck(results), perRun, request.getStreamConfig().getStreamId(), request.getStreamConfig(), (ScanResult r) -> processScanResult(request, r), (Throwable err) -> convertException(err)); perRun.streamerOwner().addAndStart(streamer); @@ -244,7 +245,7 @@ private Mono handleCollectionLevelCommand(Command op, PerRun perRun, Res } else { gr = collection.getAndLock(docId, Duration.ofSeconds(duration.getSeconds()), options); } - return gr.map(r -> { + return withSchedulerCheck(gr).map(r -> { result.setElapsedNanos(System.nanoTime() - start); if (op.getReturnResult()) { populateResult(request.getContentAs(), result, r); @@ -268,7 +269,7 @@ private Mono handleCollectionLevelCommand(Command op, PerRun perRun, Res } else { gr = collection.unlock(docId, cas, options); } - return gr.then(Mono.fromCallable(() -> { + return withSchedulerCheck(gr).then(Mono.fromCallable(() -> { result.setElapsedNanos(System.nanoTime() - start); setSuccess(result); return result.build(); @@ -293,7 +294,7 @@ private Mono handleCollectionLevelCommand(Command op, PerRun perRun, Res } else { gr = collection.getAndTouch(docId, expiry, options); } - return gr.map(r -> { + return withSchedulerCheck(gr).map(r -> { result.setElapsedNanos(System.nanoTime() - start); if (op.getReturnResult()) { populateResult(request.getContentAs(), result, r); @@ -322,7 +323,7 @@ private Mono handleCollectionLevelCommand(Command op, PerRun perRun, Res } else { mr = collection.touch(docId, expiry, options); } - return mr.map(r -> { + return withSchedulerCheck(mr).map(r -> { result.setElapsedNanos(System.nanoTime() - start); if (op.getReturnResult()) { populateResult(result, r); @@ -337,7 +338,7 @@ private Mono handleCollectionLevelCommand(Command op, PerRun perRun, Res var request = clc.getExists(); var docId = getDocId(request.getLocation()); var exists = collection.exists(docId); - return exists.map(r -> { + return withSchedulerCheck(exists).map(r -> { if (op.getReturnResult()) { populateResult(result, r); } else { @@ -357,7 +358,7 @@ private Mono handleCollectionLevelCommand(Command op, PerRun perRun, Res } else { mr = collection.mutateIn(docId, request.getSpecList().stream().map(v -> convertMutateInSpec(v)).toList(), options); } - return mr.map(r -> { + return withSchedulerCheck(mr).map(r -> { if (op.getReturnResult()) { populateResult(result, r, request); } else { @@ -380,7 +381,7 @@ private Mono handleCollectionLevelCommand(Command op, PerRun perRun, Res results = collection.getAllReplicas(docId, options); } result.setElapsedNanos(System.nanoTime() - start); - var streamer = new FluxStreamer<>(results, perRun, request.getStreamConfig().getStreamId(), request.getStreamConfig(), + var streamer = new FluxStreamer<>(withSchedulerCheck(results), perRun, request.getStreamConfig().getStreamId(), request.getStreamConfig(), (GetReplicaResult r) -> processGetAllReplicasResult(request, r), this::convertException); perRun.streamerOwner().addAndStart(streamer); @@ -403,7 +404,7 @@ private Mono handleCollectionLevelCommand(Command op, PerRun perRun, Res } else { gr = collection.getAnyReplica(docId, options); } - return gr.map(r -> { + return withSchedulerCheck(gr).map(r -> { result.setElapsedNanos(System.nanoTime() - start); if (op.getReturnResult()) { populateResult(result, r, request.getContentAs()); @@ -431,7 +432,7 @@ private Mono handleCollectionLevelCommand(Command op, PerRun perRun, Res cr = collection.binary().increment(docId, options); } result.setElapsedNanos(System.nanoTime() - start); - return cr.map(r -> { + return withSchedulerCheck(cr).map(r -> { if (op.getReturnResult()) { populateResult(result, r); } else { @@ -454,7 +455,7 @@ private Mono handleCollectionLevelCommand(Command op, PerRun perRun, Res cr = collection.binary().decrement(docId, options); } result.setElapsedNanos(System.nanoTime() - start); - return cr.map(r -> { + return withSchedulerCheck(cr).map(r -> { if (op.getReturnResult()) { populateResult(result, r); } else { @@ -477,7 +478,7 @@ private Mono handleCollectionLevelCommand(Command op, PerRun perRun, Res mr = collection.binary().append(docId, request.getContent().toByteArray(), options); } result.setElapsedNanos(System.nanoTime() - start); - return mr.map(r -> { + return withSchedulerCheck(mr).map(r -> { if (op.getReturnResult()) { populateResult(result, r); } else { @@ -500,7 +501,7 @@ private Mono handleCollectionLevelCommand(Command op, PerRun perRun, Res mr = collection.binary().prepend(docId, request.getContent().toByteArray(), options); } result.setElapsedNanos(System.nanoTime() - start); - return mr.map(r -> { + return withSchedulerCheck(mr).map(r -> { if (op.getReturnResult()) { populateResult(result, r); } else { @@ -537,7 +538,7 @@ private Mono handleScopeLevelCommand(Command op, PerRun perRun, Result.B queryResult = scope.reactive().query(query); } - return returnQueryResult(request, queryResult, result, start); + return returnQueryResult(request, withSchedulerCheck(queryResult), result, start); } // [end] @@ -581,7 +582,7 @@ private Mono handleBucketLevelCommand(Command op, PerRun perRun, Result. response = bucket.waitUntilReady(timeout); } - return response.then(Mono.fromCallable(() -> { + return withSchedulerCheck(response).then(Mono.fromCallable(() -> { setSuccess(result); return result.build(); })); @@ -614,7 +615,7 @@ private Mono handleClusterLevelCommand(Command op, PerRun perRun, Result response = cluster.waitUntilReady(timeout); } - return response.then(Mono.fromCallable(() -> { + return withSchedulerCheck(response).then(Mono.fromCallable(() -> { setSuccess(result); return result.build(); })); @@ -673,7 +674,7 @@ else if (clc.hasEventingFunctionManager()) { queryResult = connection.cluster().reactive().query(query); } - return returnQueryResult(request, queryResult, result, start); + return returnQueryResult(request, withSchedulerCheck(queryResult), result, start); } return Mono.error(new UnsupportedOperationException("Unknown command " + op)); diff --git a/java-fit-performer/src/main/java/com/couchbase/eventing/EventingHelper.java b/java-fit-performer/src/main/java/com/couchbase/eventing/EventingHelper.java index 531570996..43682977b 100644 --- a/java-fit-performer/src/main/java/com/couchbase/eventing/EventingHelper.java +++ b/java-fit-performer/src/main/java/com/couchbase/eventing/EventingHelper.java @@ -30,6 +30,7 @@ import java.util.concurrent.ConcurrentHashMap; import static com.couchbase.utils.OptionsUtil.convertDuration; +import static com.couchbase.utils.UserSchedulerUtil.withSchedulerCheck; public class EventingHelper { @@ -82,7 +83,7 @@ public static Mono handleEventingFunctionManagerReactive(ReactiveCluster } - return response.map(r -> { + return withSchedulerCheck(response).map(r -> { minimalEventingFunctionFromResult(result, r); return result.build(); }); diff --git a/java-fit-performer/src/main/java/com/couchbase/manager/BucketManagerHelper.java b/java-fit-performer/src/main/java/com/couchbase/manager/BucketManagerHelper.java index 4a8b04da7..0ec87c217 100644 --- a/java-fit-performer/src/main/java/com/couchbase/manager/BucketManagerHelper.java +++ b/java-fit-performer/src/main/java/com/couchbase/manager/BucketManagerHelper.java @@ -43,6 +43,7 @@ import java.util.concurrent.ConcurrentHashMap; import static com.couchbase.JavaSdkCommandExecutor.setSuccess; +import static com.couchbase.utils.UserSchedulerUtil.withSchedulerCheck; public class BucketManagerHelper { @@ -157,7 +158,7 @@ public static Mono handleBucketManagerReactive(ReactiveCluster cluster, var options = createBucketOptions(request.getOptions(), spans); response = cluster.buckets().createBucket(createBucketSettings(request.getSettings(), spans), options); } - return response.then(Mono.fromCallable(() -> { + return withSchedulerCheck(response).then(Mono.fromCallable(() -> { populateResult(start, result, null); result.setElapsedNanos(System.nanoTime() - start); setSuccess(result); @@ -173,7 +174,7 @@ public static Mono handleBucketManagerReactive(ReactiveCluster cluster, var options = updateBucketOptions(request.getOptions(), spans); response = cluster.buckets().updateBucket(updateBucketSettings(request.getSettings(), spans), options); } - return response.then(Mono.fromCallable(() -> { + return withSchedulerCheck(response).then(Mono.fromCallable(() -> { populateResult(start, result, null); result.setElapsedNanos(System.nanoTime() - start); setSuccess(result); @@ -189,7 +190,7 @@ public static Mono handleBucketManagerReactive(ReactiveCluster cluster, var options = dropBucketOptions(request.getOptions(), spans); response = cluster.buckets().dropBucket(request.getBucketName(), options); } - return response.then(Mono.fromCallable(() -> { + return withSchedulerCheck(response).then(Mono.fromCallable(() -> { populateResult(start, result, null); result.setElapsedNanos(System.nanoTime() - start); setSuccess(result); @@ -205,7 +206,7 @@ public static Mono handleBucketManagerReactive(ReactiveCluster cluster, var options = flushBucketOptions(request.getOptions(), spans); response = cluster.buckets().flushBucket(request.getBucketName(), options); } - return response.then(Mono.fromCallable(() -> { + return withSchedulerCheck(response).then(Mono.fromCallable(() -> { populateResult(start, result, null); result.setElapsedNanos(System.nanoTime() - start); setSuccess(result); @@ -220,7 +221,7 @@ public static Mono handleBucketManagerReactive(ReactiveCluster cluster, var options = createGetAllBucketsOptions(request.getOptions(), spans); response = cluster.buckets().getAllBuckets(options); } - return response.map(rr -> { + return withSchedulerCheck(response).map(rr -> { rr.forEach((name, settings) -> populateResult(start, result, settings)); return result.build(); diff --git a/java-fit-performer/src/main/java/com/couchbase/manager/CollectionManagerHelper.java b/java-fit-performer/src/main/java/com/couchbase/manager/CollectionManagerHelper.java index 1a5efec5d..f9bfb7608 100644 --- a/java-fit-performer/src/main/java/com/couchbase/manager/CollectionManagerHelper.java +++ b/java-fit-performer/src/main/java/com/couchbase/manager/CollectionManagerHelper.java @@ -41,6 +41,7 @@ import java.util.stream.Collectors; import static com.couchbase.JavaSdkCommandExecutor.setSuccess; +import static com.couchbase.utils.UserSchedulerUtil.withSchedulerCheck; public class CollectionManagerHelper { @@ -137,7 +138,7 @@ public static Mono handleCollectionManagerReactive(ReactiveCluster clust var options = createGetAllScopesOptions(request.getOptions(), spans); response = cluster.bucket(bucketName).collections().getAllScopes(options); } - Flux f = response.map(r -> { + Flux f = withSchedulerCheck(response).map(r -> { populateResult(result, r); return result.build(); }); @@ -151,7 +152,7 @@ public static Mono handleCollectionManagerReactive(ReactiveCluster clust } else { r = collections.createScope(request.getName()); } - return r.then(Mono.fromSupplier(() -> { + return withSchedulerCheck(r).then(Mono.fromSupplier(() -> { setSuccess(result); return result.build(); })); @@ -164,7 +165,7 @@ public static Mono handleCollectionManagerReactive(ReactiveCluster clust } else { r = collections.dropScope(request.getName()); } - return r.then(Mono.fromSupplier(() -> { + return withSchedulerCheck(r).then(Mono.fromSupplier(() -> { setSuccess(result); return result.build(); })); @@ -178,7 +179,7 @@ public static Mono handleCollectionManagerReactive(ReactiveCluster clust } else { r = collections.createCollection(request.getScopeName(), request.getName(), settings); } - return r.then(Mono.fromSupplier(() -> { + return withSchedulerCheck(r).then(Mono.fromSupplier(() -> { setSuccess(result); return result.build(); })); @@ -192,7 +193,7 @@ public static Mono handleCollectionManagerReactive(ReactiveCluster clust } else { r = collections.updateCollection(request.getScopeName(), request.getName(), settings); } - return r.then(Mono.fromSupplier(() -> { + return withSchedulerCheck(r).then(Mono.fromSupplier(() -> { setSuccess(result); return result.build(); })); @@ -205,7 +206,7 @@ public static Mono handleCollectionManagerReactive(ReactiveCluster clust } else { r = collections.dropCollection(request.getScopeName(), request.getName()); } - return r.then(Mono.fromSupplier(() -> { + return withSchedulerCheck(r).then(Mono.fromSupplier(() -> { setSuccess(result); return result.build(); })); diff --git a/java-fit-performer/src/main/java/com/couchbase/query/QueryIndexManagerHelper.java b/java-fit-performer/src/main/java/com/couchbase/query/QueryIndexManagerHelper.java index 18b0242c7..06e43e90c 100644 --- a/java-fit-performer/src/main/java/com/couchbase/query/QueryIndexManagerHelper.java +++ b/java-fit-performer/src/main/java/com/couchbase/query/QueryIndexManagerHelper.java @@ -40,12 +40,12 @@ import java.time.Duration; import java.util.HashSet; import java.util.List; -import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; import static com.couchbase.JavaSdkCommandExecutor.setSuccess; import static com.couchbase.client.performer.core.util.TimeUtil.getTimeNow; +import static com.couchbase.utils.UserSchedulerUtil.withSchedulerCheck; public class QueryIndexManagerHelper { private QueryIndexManagerHelper() { @@ -301,7 +301,7 @@ private static Mono handleQueryIndexManagerSharedReactive(@Nullable Reac res = collection.queryIndexes().createPrimaryIndex(options); } } - return res.then(Mono.fromCallable(() -> { + return withSchedulerCheck(res).then(Mono.fromCallable(() -> { result.setElapsedNanos(System.nanoTime() - start); setSuccess(result); return result.build(); @@ -326,7 +326,7 @@ private static Mono handleQueryIndexManagerSharedReactive(@Nullable Reac res = collection.queryIndexes().createIndex(request.getIndexName(), fields, options); } } - return res.then(Mono.fromCallable(() -> { + return withSchedulerCheck(res).then(Mono.fromCallable(() -> { result.setElapsedNanos(System.nanoTime() - start); setSuccess(result); return result.build(); @@ -350,7 +350,7 @@ private static Mono handleQueryIndexManagerSharedReactive(@Nullable Reac indexes = collection.queryIndexes().getAllIndexes(options); } } - return indexes.collectList().map(i -> { + return withSchedulerCheck(indexes).collectList().map(i -> { result.setElapsedNanos(System.nanoTime() - start); if (op.getReturnResult()) { populateResult(result, i); @@ -378,7 +378,7 @@ private static Mono handleQueryIndexManagerSharedReactive(@Nullable Reac res = collection.queryIndexes().dropPrimaryIndex(options); } } - return res.then(Mono.fromCallable(() -> { + return withSchedulerCheck(res).then(Mono.fromCallable(() -> { result.setElapsedNanos(System.nanoTime() - start); setSuccess(result); return result.build(); @@ -402,7 +402,7 @@ private static Mono handleQueryIndexManagerSharedReactive(@Nullable Reac res = collection.queryIndexes().dropIndex(request.getIndexName(), options); } } - return res.then(Mono.fromCallable(() -> { + return withSchedulerCheck(res).then(Mono.fromCallable(() -> { result.setElapsedNanos(System.nanoTime() - start); setSuccess(result); return result.build(); @@ -426,7 +426,7 @@ private static Mono handleQueryIndexManagerSharedReactive(@Nullable Reac res = collection.queryIndexes().watchIndexes(request.getIndexNamesList().stream().toList(), Duration.ofMillis(request.getTimeoutMsecs()), options); } } - return res.then(Mono.fromCallable(() -> { + return withSchedulerCheck(res).then(Mono.fromCallable(() -> { result.setElapsedNanos(System.nanoTime() - start); setSuccess(result); return result.build(); @@ -450,7 +450,7 @@ private static Mono handleQueryIndexManagerSharedReactive(@Nullable Reac res = collection.queryIndexes().buildDeferredIndexes(options); } } - return res.then(Mono.fromCallable(() -> { + return withSchedulerCheck(res).then(Mono.fromCallable(() -> { result.setElapsedNanos(System.nanoTime() - start); setSuccess(result); return result.build(); diff --git a/java-fit-performer/src/main/java/com/couchbase/search/SearchHelper.java b/java-fit-performer/src/main/java/com/couchbase/search/SearchHelper.java index d73f2af71..b1b662122 100644 --- a/java-fit-performer/src/main/java/com/couchbase/search/SearchHelper.java +++ b/java-fit-performer/src/main/java/com/couchbase/search/SearchHelper.java @@ -104,6 +104,7 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import static com.couchbase.utils.UserSchedulerUtil.withSchedulerCheck; import static com.couchbase.JavaSdkCommandExecutor.convertMutationState; import static com.couchbase.JavaSdkCommandExecutor.setSuccess; import static com.couchbase.client.performer.core.util.TimeUtil.getTimeNow; @@ -688,7 +689,7 @@ public static Mono handleSearchQueryReactive(Cluster cluster, } } - return r.doOnNext(re -> { + return withSchedulerCheck(r).doOnNext(re -> { result.setElapsedNanos(System.nanoTime() - start); var streamer = new ReactiveSearchResultStreamer(re, @@ -774,7 +775,7 @@ public static Mono handleSearchReactive(Cluster cluster, } } - return r.doOnNext(re -> { + return withSchedulerCheck(r).doOnNext(re -> { result.setElapsedNanos(System.nanoTime() - start); var streamer = new ReactiveSearchResultStreamer(re, diff --git a/java-fit-performer/src/main/java/com/couchbase/transactions/SingleQueryTransactionExecutor.java b/java-fit-performer/src/main/java/com/couchbase/transactions/SingleQueryTransactionExecutor.java index 065c3ba0c..55a23c798 100644 --- a/java-fit-performer/src/main/java/com/couchbase/transactions/SingleQueryTransactionExecutor.java +++ b/java-fit-performer/src/main/java/com/couchbase/transactions/SingleQueryTransactionExecutor.java @@ -43,6 +43,7 @@ import com.couchbase.utils.ResultsUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import reactor.core.publisher.Mono; import java.time.Duration; import java.util.ArrayList; @@ -52,6 +53,8 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; +import static com.couchbase.utils.UserSchedulerUtil.withSchedulerCheck; + /** * Executes single query transactions (tximplicit). */ @@ -106,17 +109,19 @@ private static TransactionSingleQueryResponse reactive(TransactionSingleQueryReq ClusterConnection connection, ConcurrentHashMap spans) { QueryOptions options = setSingleQueryTransactionOptions(request, connection, spans); - ReactiveQueryResult result; + Mono resultMono; if (request.getQuery().hasScope()) { String bucketName = request.getQuery().getScope().getBucketName(); String scopeName = request.getQuery().getScope().getScopeName(); Scope scope = connection.cluster().bucket(bucketName).scope(scopeName); - result = scope.reactive().query(request.getQuery().getStatement(), options).block(); + resultMono = scope.reactive().query(request.getQuery().getStatement(), options); } else { - result = connection.cluster().reactive().query(request.getQuery().getStatement(), options).block(); + resultMono = connection.cluster().reactive().query(request.getQuery().getStatement(), options); } + ReactiveQueryResult result = withSchedulerCheck(resultMono).block(); + AtomicReference errorDuringStreaming = new AtomicReference<>(); AtomicReference causeDuringStreaming = new AtomicReference<>(); AtomicBoolean rowValidationPerformed = new AtomicBoolean(); @@ -256,4 +261,4 @@ private static QueryOptions setSingleQueryTransactionOptions(TransactionSingleQu return QueryOptions.queryOptions().asTransaction(); } } -} \ No newline at end of file +} diff --git a/java-fit-performer/src/main/java/com/couchbase/twoway/TwoWayTransactionReactive.java b/java-fit-performer/src/main/java/com/couchbase/twoway/TwoWayTransactionReactive.java index 9f7490dec..5ed7ed548 100644 --- a/java-fit-performer/src/main/java/com/couchbase/twoway/TwoWayTransactionReactive.java +++ b/java-fit-performer/src/main/java/com/couchbase/twoway/TwoWayTransactionReactive.java @@ -67,6 +67,8 @@ import java.util.function.Function; import java.util.function.Supplier; +import static com.couchbase.utils.UserSchedulerUtil.withSchedulerCheck; + /** * Version of TwoWayTransaction that uses the reactive API. */ @@ -381,7 +383,7 @@ private Mono performOperation(Mono preOp, } return preOp - .then(op.get()) + .then(withSchedulerCheck(op.get())) .then(Mono.defer(() -> { if (!performanceMode) { logger.info("Took {} millis to run command '{}'", System.currentTimeMillis() - now, opDebug); diff --git a/java-fit-performer/src/main/java/com/couchbase/utils/OptionsUtil.java b/java-fit-performer/src/main/java/com/couchbase/utils/OptionsUtil.java index 52a3e772a..48ed9ef05 100644 --- a/java-fit-performer/src/main/java/com/couchbase/utils/OptionsUtil.java +++ b/java-fit-performer/src/main/java/com/couchbase/utils/OptionsUtil.java @@ -92,15 +92,14 @@ public class OptionsUtil { private OptionsUtil() {} - @Nullable public static + public static ClusterEnvironment.Builder convertClusterConfig(ClusterConnectionCreateRequest request, Supplier getCluster, ArrayList onClusterConnectionClose) { - ClusterEnvironment.Builder clusterEnvironment = null; + ClusterEnvironment.Builder clusterEnvironment = ClusterEnvironment.builder(); if (request.hasClusterConfig()) { var cc = request.getClusterConfig(); - clusterEnvironment = ClusterEnvironment.builder(); if (cc.getUseCustomSerializer()) { clusterEnvironment.jsonSerializer(new CustomSerializer()); diff --git a/java-fit-performer/src/main/java/com/couchbase/utils/UserSchedulerUtil.java b/java-fit-performer/src/main/java/com/couchbase/utils/UserSchedulerUtil.java new file mode 100644 index 000000000..9a9c0f900 --- /dev/null +++ b/java-fit-performer/src/main/java/com/couchbase/utils/UserSchedulerUtil.java @@ -0,0 +1,94 @@ +/* + * Copyright 2024 Couchbase, Inc. + * + * Licensed 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 com.couchbase.utils; + +import com.couchbase.InternalPerformerFailure; +import com.couchbase.client.core.deps.io.netty.util.concurrent.DefaultThreadFactory; +import com.google.common.base.Throwables; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import reactor.core.Disposable; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Scheduler; +import reactor.core.scheduler.Schedulers; + +import javax.annotation.Nullable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class UserSchedulerUtil { + private UserSchedulerUtil() { + } + + private static final Logger logger = LoggerFactory.getLogger(UserSchedulerUtil.class); + + private static final String USER_SCHEDULER_THREAD_POOL_NAME = "custom-user-scheduler"; + + public record ExecutorAndScheduler(ExecutorService executorService, Scheduler scheduler) implements Disposable { + @Override + public void dispose() { + scheduler.dispose(); + executorService.shutdownNow(); + } + } + + public static ExecutorAndScheduler userExecutorAndScheduler() { + var executorService = Executors.newCachedThreadPool(new DefaultThreadFactory(USER_SCHEDULER_THREAD_POOL_NAME, true)); + var userScheduler = Schedulers.fromExecutor(executorService); + return new ExecutorAndScheduler(executorService, userScheduler); + } + + // Getting stack traces is expensive; skip it in case this is a performance test. + private static final boolean CAPTURE_STACK_TRACE = false; + + public static Mono withSchedulerCheck(Mono publisher) { + Exception location = CAPTURE_STACK_TRACE ? new Exception("Scheduler check was applied here") : null; + return publisher + .doOnNext(it -> assertInCustomUserSchedulerThread("onNext", location)) + .doOnError(it -> assertInCustomUserSchedulerThread("onError", location)) + .doOnSuccess(it -> assertInCustomUserSchedulerThread("onSuccess", location)); + } + + public static Flux withSchedulerCheck(Flux publisher) { + Exception location = CAPTURE_STACK_TRACE ? new Exception("Scheduler check was applied here") : null; + return publisher + .doOnNext(it -> assertInCustomUserSchedulerThread("onNext", location)) + .doOnError(it -> assertInCustomUserSchedulerThread("onError", location)) + .doOnComplete(() -> assertInCustomUserSchedulerThread("onComplete", location)); + } + + private static void assertInCustomUserSchedulerThread( + String hookType, + @Nullable Exception locationWhereSchedulerCheckWasApplied + ) { + // [if:3.7.5] first version that allows specifying custom publishOn scheduler + String threadName = Thread.currentThread().getName(); + boolean isUserThread = threadName.contains(USER_SCHEDULER_THREAD_POOL_NAME); + + if (!isUserThread) { + String location = locationWhereSchedulerCheckWasApplied == null + ? "To discover the location of the failed scheduler check, set CAPTURE_STACK_TRACE to true in performer source code." + : Throwables.getStackTraceAsString(locationWhereSchedulerCheckWasApplied); + + String msg = "Expected reactive " + hookType + " handler to run in custom user scheduler thread, but thread name was: " + threadName + " ; location = " + location; + logger.error(msg); + throw new InternalPerformerFailure(new RuntimeException(msg)); + } + // [end] + } +} From 374a8202c65d35964a25ccc284afa873955243c8 Mon Sep 17 00:00:00 2001 From: Graham Pople Date: Mon, 10 Jun 2024 09:36:40 +0100 Subject: [PATCH 42/73] SCBC-461: Support for base64 encoded vector types [FIT] FIT performer side of this work. Change-Id: Ibfb2e7c055e7677b7c8409d53d342c7952084528 Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/211117 Reviewed-by: Graham Pople Tested-by: Build Bot --- .../performer/scala/search/SearchHelper.scala | 39 ++++++++++--------- .../performer/scala/util/Capabilities.scala | 4 ++ 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/scala-fit-performer/src/main/scala/com/couchbase/client/performer/scala/search/SearchHelper.scala b/scala-fit-performer/src/main/scala/com/couchbase/client/performer/scala/search/SearchHelper.scala index 68e44f823..6cef1a97c 100644 --- a/scala-fit-performer/src/main/scala/com/couchbase/client/performer/scala/search/SearchHelper.scala +++ b/scala-fit-performer/src/main/scala/com/couchbase/client/performer/scala/search/SearchHelper.scala @@ -219,14 +219,11 @@ object SearchHelper { opts = opts.facets(facets) } if (o.hasTimeoutMillis) { - // [start:1.4.5] + // [if:1.4.5] opts = opts.timeout(Duration(o.getTimeoutMillis, TimeUnit.MILLISECONDS)) - // [end:1.4.5] - // [start:<1.4.5] - /* - throw new UnsupportedOperationException() - // [end:<1.4.5] - */ + // [else] + //? throw new UnsupportedOperationException() + // [end] } if (o.hasParentSpanId) throw new UnsupportedOperationException() if (o.getRawCount > 0) opts = opts.raw(o.getRawMap.asScala.toMap) @@ -453,7 +450,7 @@ object SearchHelper { result } - // [start:1.6.0] + // [if:1.6.0] def handleSearchBlocking( cluster: Cluster, command: com.couchbase.client.protocol.sdk.search.SearchWrapper @@ -533,8 +530,17 @@ object SearchHelper { def convertVectorQuery( vq: com.couchbase.client.protocol.sdk.search.VectorQuery ): com.couchbase.client.scala.search.vector.VectorQuery = { - val query: Array[Float] = vq.getVectorQueryList.asScala.toArray.map(v => v.asInstanceOf[Float]) - var out = com.couchbase.client.scala.search.vector.VectorQuery(vq.getVectorFieldName, query) + // [if:1.6.2] + var out = if (vq.hasBase64VectorQuery) { + com.couchbase.client.scala.search.vector.VectorQuery(vq.getVectorFieldName, vq.getBase64VectorQuery) + } else { + val query: Array[Float] = vq.getVectorQueryList.asScala.toArray.map(v => v.asInstanceOf[Float]) + com.couchbase.client.scala.search.vector.VectorQuery(vq.getVectorFieldName, query) + } + // [else] + //? val query: Array[Float] = vq.getVectorQueryList.asScala.toArray.map(v => v.asInstanceOf[Float]) + //? var out = com.couchbase.client.scala.search.vector.VectorQuery(vq.getVectorFieldName, query) + // [end] if (vq.hasOptions) { val opts = vq.getOptions if (opts.hasNumCandidates) out = out.numCandidates(opts.getNumCandidates) @@ -561,7 +567,7 @@ object SearchHelper { } } - // [end:1.6.0] + // [end] private def convertResult( result: SearchResult, @@ -732,7 +738,7 @@ object SearchHelper { } else if (command.hasUpsertIndex) { val req = command.getUpsertIndex - // [start:1.4.5] + // [if:1.4.5] val converted = SearchIndex.fromJson(req.getIndexDefinition.toStringUtf8).get result.setInitiated(getTimeNow) val start = System.nanoTime @@ -747,12 +753,9 @@ object SearchHelper { .get result.setElapsedNanos(System.nanoTime - start) setSuccess(result) - // [end:1.4.5] - // [start:<1.4.5] - /* - throw new UnsupportedOperationException() - // [end:<1.4.5] - */ + // [else] + //? throw new UnsupportedOperationException() + // [end] } else if (command.hasDropIndex) { val req = command.getDropIndex diff --git a/scala-fit-performer/src/main/scala/com/couchbase/client/performer/scala/util/Capabilities.scala b/scala-fit-performer/src/main/scala/com/couchbase/client/performer/scala/util/Capabilities.scala index 55da511f5..d16c07c47 100644 --- a/scala-fit-performer/src/main/scala/com/couchbase/client/performer/scala/util/Capabilities.scala +++ b/scala-fit-performer/src/main/scala/com/couchbase/client/performer/scala/util/Capabilities.scala @@ -74,6 +74,10 @@ object Capabilities { out.add(Caps.SDK_VECTOR_SEARCH) // [end:1.6.0] + // [start:1.6.2] + out.add(Caps.SDK_VECTOR_SEARCH_BASE64) + // [end:1.6.2] + out } From c72e5c807e9236badc2ad12424b72ebac7c5ce38 Mon Sep 17 00:00:00 2001 From: Emilien Bevierre Date: Tue, 5 Nov 2024 11:58:50 +0000 Subject: [PATCH 43/73] JCBC-2173: Upgrade jctools-core to 4.0.5 Motivation ---------- The Quarkus extension needs to substitute usages of MpscArrayQueue for MpscAtomicUnpaddedArrayQueue, which requires the dependency jctools-core of core-io-deps to be upgraded from 4.0.1 to 4.0.5. Change-Id: I945761cc49c5a5a5923be25a95016ce98b448eb5 Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/219112 Tested-by: Build Bot Reviewed-by: Graham Pople --- core-io-deps/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core-io-deps/pom.xml b/core-io-deps/pom.xml index a070ec650..8bc89b15c 100644 --- a/core-io-deps/pom.xml +++ b/core-io-deps/pom.xml @@ -79,7 +79,7 @@ org.jctools jctools-core - 4.0.1 + 4.0.5 com.fasterxml.jackson.core From 4b921ffcffd3d3978a2f427cc0431a80558c1787 Mon Sep 17 00:00:00 2001 From: Emilien Bevierre Date: Tue, 5 Nov 2024 16:42:29 +0000 Subject: [PATCH 44/73] Revert "JCBC-2173: Upgrade jctools-core to 4.0.5" This reverts commit c72e5c807e9236badc2ad12424b72ebac7c5ce38. Reason for revert: Merges should be freezed once testing for release starts. Change-Id: I53d2da878bfc5399316f355b8a8f196064c44370 Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/219137 Tested-by: Emilien Bevierre Reviewed-by: Emilien Bevierre --- core-io-deps/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core-io-deps/pom.xml b/core-io-deps/pom.xml index 8bc89b15c..a070ec650 100644 --- a/core-io-deps/pom.xml +++ b/core-io-deps/pom.xml @@ -79,7 +79,7 @@ org.jctools jctools-core - 4.0.5 + 4.0.1 com.fasterxml.jackson.core From 4e778fe4e035b3c3d1c0613ff04b107999e289cb Mon Sep 17 00:00:00 2001 From: mikereiche Date: Mon, 4 Nov 2024 15:15:28 -0800 Subject: [PATCH 45/73] Release selene-5 3.7.5 Change-Id: I809b060a0e2cf0c9cf5241c22891dcad027e8cf5 Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/219088 Tested-by: Build Bot Reviewed-by: Michael Reiche --- columnar-fit-performer-shared/pom.xml | 2 +- .../examples/maven-project-template/pom.xml | 2 +- columnar-java-client/pom.xml | 4 ++-- columnar-java-fit-performer/pom.xml | 2 +- core-fit-performer/pom.xml | 2 +- core-io-deps/pom.xml | 2 +- core-io/pom.xml | 4 ++-- java-client/pom.xml | 4 ++-- java-examples/pom.xml | 4 ++-- java-fit-performer/pom.xml | 2 +- kotlin-client/pom.xml | 4 ++-- kotlin-fit-performer/pom.xml | 2 +- metrics-micrometer/pom.xml | 4 ++-- metrics-opentelemetry/pom.xml | 4 ++-- osgi-feature/pom.xml | 4 ++-- pom.xml | 24 +++++++++---------- scala-client/pom.xml | 4 ++-- scala-fit-performer/pom.xml | 2 +- scala-implicits/pom.xml | 4 ++-- test-utils/pom.xml | 4 ++-- tracing-micrometer-observation/pom.xml | 4 ++-- tracing-opentelemetry-deps/pom.xml | 2 +- tracing-opentelemetry/pom.xml | 6 ++--- tracing-opentracing/pom.xml | 4 ++-- 24 files changed, 50 insertions(+), 50 deletions(-) diff --git a/columnar-fit-performer-shared/pom.xml b/columnar-fit-performer-shared/pom.xml index 6a943677c..ee211b2ac 100644 --- a/columnar-fit-performer-shared/pom.xml +++ b/columnar-fit-performer-shared/pom.xml @@ -7,7 +7,7 @@ com.couchbase.client couchbase-jvm-clients - 1.16.5-SNAPSHOT + 1.16.5 columnar-fit-performer-shared diff --git a/columnar-java-client/examples/maven-project-template/pom.xml b/columnar-java-client/examples/maven-project-template/pom.xml index 74d803245..f8662da9a 100644 --- a/columnar-java-client/examples/maven-project-template/pom.xml +++ b/columnar-java-client/examples/maven-project-template/pom.xml @@ -6,7 +6,7 @@ com.example couchbase-columnar-java-example - 1.0.5-SNAPSHOT + 1.0.5 Couchbase Columnar Java SDK Project Template Examples project for Couchbase Columnar Java SDK diff --git a/columnar-java-client/pom.xml b/columnar-java-client/pom.xml index d908c6b4a..b583bdbad 100644 --- a/columnar-java-client/pom.xml +++ b/columnar-java-client/pom.xml @@ -6,11 +6,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.5-SNAPSHOT + 1.16.5 couchbase-columnar-java-client - 1.0.5-SNAPSHOT + 1.0.5 Couchbase Columnar Java SDK diff --git a/columnar-java-fit-performer/pom.xml b/columnar-java-fit-performer/pom.xml index 18558c790..a8cef05df 100644 --- a/columnar-java-fit-performer/pom.xml +++ b/columnar-java-fit-performer/pom.xml @@ -7,7 +7,7 @@ com.couchbase.client couchbase-jvm-clients - 1.16.5-SNAPSHOT + 1.16.5 columnar-java-fit-performer diff --git a/core-fit-performer/pom.xml b/core-fit-performer/pom.xml index ef2ac0d14..289e39bf2 100644 --- a/core-fit-performer/pom.xml +++ b/core-fit-performer/pom.xml @@ -7,7 +7,7 @@ com.couchbase.client couchbase-jvm-clients - 1.16.5-SNAPSHOT + 1.16.5 fit-performer-core diff --git a/core-io-deps/pom.xml b/core-io-deps/pom.xml index a070ec650..e5792d537 100644 --- a/core-io-deps/pom.xml +++ b/core-io-deps/pom.xml @@ -6,7 +6,7 @@ com.couchbase.client core-io-deps - 1.7.5-SNAPSHOT + 1.7.5 jar Couchbase JVM Core IO Dependencies diff --git a/core-io/pom.xml b/core-io/pom.xml index 204f95309..55b319f2f 100644 --- a/core-io/pom.xml +++ b/core-io/pom.xml @@ -7,11 +7,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.5-SNAPSHOT + 1.16.5 core-io - 3.7.5-SNAPSHOT + 3.7.5 diff --git a/java-client/pom.xml b/java-client/pom.xml index 64e5da325..2a5cbd9c9 100644 --- a/java-client/pom.xml +++ b/java-client/pom.xml @@ -6,11 +6,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.5-SNAPSHOT + 1.16.5 java-client - 3.7.5-SNAPSHOT + 3.7.5 Couchbase Java SDK diff --git a/java-examples/pom.xml b/java-examples/pom.xml index 51db1e695..a0a4b81f5 100644 --- a/java-examples/pom.xml +++ b/java-examples/pom.xml @@ -6,11 +6,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.5-SNAPSHOT + 1.16.5 java-examples - 1.7.5-SNAPSHOT + 1.7.5 Couchbase Java SDK Examples diff --git a/java-fit-performer/pom.xml b/java-fit-performer/pom.xml index 050ac698e..c83c51616 100644 --- a/java-fit-performer/pom.xml +++ b/java-fit-performer/pom.xml @@ -7,7 +7,7 @@ com.couchbase.client couchbase-jvm-clients - 1.16.5-SNAPSHOT + 1.16.5 fit-performer-java-sdk diff --git a/kotlin-client/pom.xml b/kotlin-client/pom.xml index e137bcd2d..e9aacd2c8 100644 --- a/kotlin-client/pom.xml +++ b/kotlin-client/pom.xml @@ -7,11 +7,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.5-SNAPSHOT + 1.16.5 kotlin-client - 1.4.5-SNAPSHOT + 1.4.5 Couchbase Kotlin SDK The official Couchbase Kotlin SDK diff --git a/kotlin-fit-performer/pom.xml b/kotlin-fit-performer/pom.xml index 682010d30..c6d4629e0 100644 --- a/kotlin-fit-performer/pom.xml +++ b/kotlin-fit-performer/pom.xml @@ -7,7 +7,7 @@ com.couchbase.client couchbase-jvm-clients - 1.16.5-SNAPSHOT + 1.16.5 fit-performer-kotlin diff --git a/metrics-micrometer/pom.xml b/metrics-micrometer/pom.xml index 4655e2476..acdd5b244 100644 --- a/metrics-micrometer/pom.xml +++ b/metrics-micrometer/pom.xml @@ -8,11 +8,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.5-SNAPSHOT + 1.16.5 metrics-micrometer - 0.7.5-SNAPSHOT + 0.7.5 Micrometer Metrics Interoperability Provides interoperability with Micrometer diff --git a/metrics-opentelemetry/pom.xml b/metrics-opentelemetry/pom.xml index 1d8d332c1..3c618c0b1 100644 --- a/metrics-opentelemetry/pom.xml +++ b/metrics-opentelemetry/pom.xml @@ -6,11 +6,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.5-SNAPSHOT + 1.16.5 metrics-opentelemetry - 0.7.5-SNAPSHOT + 0.7.5 OpenTelemetry Metrics Interoperability Provides interoperability with OpenTelemetry Metrics diff --git a/osgi-feature/pom.xml b/osgi-feature/pom.xml index f9bebc39b..d6ce8f59d 100644 --- a/osgi-feature/pom.xml +++ b/osgi-feature/pom.xml @@ -7,11 +7,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.5-SNAPSHOT + 1.16.5 osgi-feature - 3.7.5-SNAPSHOT + 3.7.5 pom Couchbase Java SDK OSGI Feature diff --git a/pom.xml b/pom.xml index a06312da1..45d897930 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.couchbase.client couchbase-jvm-clients - 1.16.5-SNAPSHOT + 1.16.5 pom Couchbase JVM Client Parent @@ -15,17 +15,17 @@ UTF-8 UTF-8 - 3.7.5-SNAPSHOT - 1.7.5-SNAPSHOT - 3.7.5-SNAPSHOT - 3.7.5-SNAPSHOT - 1.7.5-SNAPSHOT - 1.7.5-SNAPSHOT - 1.4.5-SNAPSHOT - 1.0.5-SNAPSHOT - 1.5.5-SNAPSHOT - 0.7.5-SNAPSHOT - 1.7.5-SNAPSHOT + 3.7.5 + 1.7.5 + 3.7.5 + 3.7.5 + 1.7.5 + 1.7.5 + 1.4.5 + 1.0.5 + 1.5.5 + 0.7.5 + 1.7.5 5.11.2 3.23.1 diff --git a/scala-client/pom.xml b/scala-client/pom.xml index 381ddd5a2..394247e38 100644 --- a/scala-client/pom.xml +++ b/scala-client/pom.xml @@ -6,11 +6,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.5-SNAPSHOT + 1.16.5 scala-client_${scala.compat.version} - 1.7.5-SNAPSHOT + 1.7.5 jar Couchbase Scala SDK diff --git a/scala-fit-performer/pom.xml b/scala-fit-performer/pom.xml index 9330ba283..2a41077a8 100644 --- a/scala-fit-performer/pom.xml +++ b/scala-fit-performer/pom.xml @@ -5,7 +5,7 @@ com.couchbase.client couchbase-jvm-clients - 1.16.5-SNAPSHOT + 1.16.5 fit-performer-scala diff --git a/scala-implicits/pom.xml b/scala-implicits/pom.xml index 05e394412..3a7025a62 100644 --- a/scala-implicits/pom.xml +++ b/scala-implicits/pom.xml @@ -7,11 +7,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.5-SNAPSHOT + 1.16.5 scala-implicits_${scala.compat.version} - 1.7.5-SNAPSHOT + 1.7.5 jar Couchbase Scala SDK Implicits diff --git a/test-utils/pom.xml b/test-utils/pom.xml index 09c7f9b3e..bf3843686 100644 --- a/test-utils/pom.xml +++ b/test-utils/pom.xml @@ -6,12 +6,12 @@ com.couchbase.client couchbase-jvm-clients - 1.16.5-SNAPSHOT + 1.16.5 Couchbase (Integration) Test Utilities test-utils - 1.7.5-SNAPSHOT + 1.7.5 diff --git a/tracing-micrometer-observation/pom.xml b/tracing-micrometer-observation/pom.xml index b7b356264..32f3231b6 100644 --- a/tracing-micrometer-observation/pom.xml +++ b/tracing-micrometer-observation/pom.xml @@ -6,11 +6,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.5-SNAPSHOT + 1.16.5 tracing-micrometer-observation - 1.5.5-SNAPSHOT + 1.5.5 Micrometer Observation Interoperability Provides interoperability with Micrometer Observation diff --git a/tracing-opentelemetry-deps/pom.xml b/tracing-opentelemetry-deps/pom.xml index 41f6dad2a..3d7e3e703 100644 --- a/tracing-opentelemetry-deps/pom.xml +++ b/tracing-opentelemetry-deps/pom.xml @@ -9,7 +9,7 @@ com.couchbase.client tracing-opentelemetry-deps - 1.5.5-SNAPSHOT + 1.5.5 jar OpenTelemetry Interoperability Dependencies diff --git a/tracing-opentelemetry/pom.xml b/tracing-opentelemetry/pom.xml index 95f2a0a9d..24de5784a 100644 --- a/tracing-opentelemetry/pom.xml +++ b/tracing-opentelemetry/pom.xml @@ -6,11 +6,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.5-SNAPSHOT + 1.16.5 tracing-opentelemetry - 1.5.5-SNAPSHOT + 1.5.5 OpenTelemetry Interoperability Provides interoperability with OpenTelemetry @@ -24,7 +24,7 @@ com.couchbase.client tracing-opentelemetry-deps - 1.5.5-SNAPSHOT + 1.5.5 diff --git a/tracing-opentracing/pom.xml b/tracing-opentracing/pom.xml index 5dc6b6515..dcfdce27e 100644 --- a/tracing-opentracing/pom.xml +++ b/tracing-opentracing/pom.xml @@ -6,11 +6,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.5-SNAPSHOT + 1.16.5 tracing-opentracing - 1.5.5-SNAPSHOT + 1.5.5 OpenTracing Interoperability Provides interoperability with OpenTracing From b622caa00ac3a66ae1b7efaf045d02aa78536675 Mon Sep 17 00:00:00 2001 From: mikereiche Date: Wed, 6 Nov 2024 12:12:03 -0800 Subject: [PATCH 46/73] Prepare for selene-sr-6 3.7.6 development. Change-Id: I1a23f84a3017f1ecb09b25b677197283cefc230a Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/219218 Reviewed-by: Michael Reiche Tested-by: Build Bot --- columnar-fit-performer-shared/pom.xml | 2 +- .../examples/maven-project-template/pom.xml | 2 +- columnar-java-client/pom.xml | 4 ++-- columnar-java-fit-performer/pom.xml | 2 +- core-fit-performer/pom.xml | 2 +- core-io-deps/pom.xml | 2 +- core-io/pom.xml | 4 ++-- java-client/pom.xml | 4 ++-- java-examples/pom.xml | 4 ++-- java-fit-performer/pom.xml | 2 +- kotlin-client/pom.xml | 4 ++-- kotlin-fit-performer/pom.xml | 2 +- metrics-micrometer/pom.xml | 4 ++-- metrics-opentelemetry/pom.xml | 4 ++-- osgi-feature/pom.xml | 4 ++-- pom.xml | 24 +++++++++---------- scala-client/pom.xml | 4 ++-- scala-fit-performer/pom.xml | 2 +- scala-implicits/pom.xml | 4 ++-- test-utils/pom.xml | 4 ++-- tracing-micrometer-observation/pom.xml | 4 ++-- tracing-opentelemetry-deps/pom.xml | 2 +- tracing-opentelemetry/pom.xml | 6 ++--- tracing-opentracing/pom.xml | 4 ++-- 24 files changed, 50 insertions(+), 50 deletions(-) diff --git a/columnar-fit-performer-shared/pom.xml b/columnar-fit-performer-shared/pom.xml index ee211b2ac..a21b1ea70 100644 --- a/columnar-fit-performer-shared/pom.xml +++ b/columnar-fit-performer-shared/pom.xml @@ -7,7 +7,7 @@ com.couchbase.client couchbase-jvm-clients - 1.16.5 + 1.16.6-SNAPSHOT columnar-fit-performer-shared diff --git a/columnar-java-client/examples/maven-project-template/pom.xml b/columnar-java-client/examples/maven-project-template/pom.xml index f8662da9a..b75820c74 100644 --- a/columnar-java-client/examples/maven-project-template/pom.xml +++ b/columnar-java-client/examples/maven-project-template/pom.xml @@ -6,7 +6,7 @@ com.example couchbase-columnar-java-example - 1.0.5 + 1.0.6-SNAPSHOT Couchbase Columnar Java SDK Project Template Examples project for Couchbase Columnar Java SDK diff --git a/columnar-java-client/pom.xml b/columnar-java-client/pom.xml index b583bdbad..8310eb53b 100644 --- a/columnar-java-client/pom.xml +++ b/columnar-java-client/pom.xml @@ -6,11 +6,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.5 + 1.16.6-SNAPSHOT couchbase-columnar-java-client - 1.0.5 + 1.0.6-SNAPSHOT Couchbase Columnar Java SDK diff --git a/columnar-java-fit-performer/pom.xml b/columnar-java-fit-performer/pom.xml index a8cef05df..cc37e7ce0 100644 --- a/columnar-java-fit-performer/pom.xml +++ b/columnar-java-fit-performer/pom.xml @@ -7,7 +7,7 @@ com.couchbase.client couchbase-jvm-clients - 1.16.5 + 1.16.6-SNAPSHOT columnar-java-fit-performer diff --git a/core-fit-performer/pom.xml b/core-fit-performer/pom.xml index 289e39bf2..4593f0ddb 100644 --- a/core-fit-performer/pom.xml +++ b/core-fit-performer/pom.xml @@ -7,7 +7,7 @@ com.couchbase.client couchbase-jvm-clients - 1.16.5 + 1.16.6-SNAPSHOT fit-performer-core diff --git a/core-io-deps/pom.xml b/core-io-deps/pom.xml index e5792d537..387093da5 100644 --- a/core-io-deps/pom.xml +++ b/core-io-deps/pom.xml @@ -6,7 +6,7 @@ com.couchbase.client core-io-deps - 1.7.5 + 1.7.6-SNAPSHOT jar Couchbase JVM Core IO Dependencies diff --git a/core-io/pom.xml b/core-io/pom.xml index 55b319f2f..d9f488593 100644 --- a/core-io/pom.xml +++ b/core-io/pom.xml @@ -7,11 +7,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.5 + 1.16.6-SNAPSHOT core-io - 3.7.5 + 3.7.6-SNAPSHOT diff --git a/java-client/pom.xml b/java-client/pom.xml index 2a5cbd9c9..d41d0640c 100644 --- a/java-client/pom.xml +++ b/java-client/pom.xml @@ -6,11 +6,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.5 + 1.16.6-SNAPSHOT java-client - 3.7.5 + 3.7.6-SNAPSHOT Couchbase Java SDK diff --git a/java-examples/pom.xml b/java-examples/pom.xml index a0a4b81f5..0783e6dcb 100644 --- a/java-examples/pom.xml +++ b/java-examples/pom.xml @@ -6,11 +6,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.5 + 1.16.6-SNAPSHOT java-examples - 1.7.5 + 1.7.6-SNAPSHOT Couchbase Java SDK Examples diff --git a/java-fit-performer/pom.xml b/java-fit-performer/pom.xml index c83c51616..e32f6ab1b 100644 --- a/java-fit-performer/pom.xml +++ b/java-fit-performer/pom.xml @@ -7,7 +7,7 @@ com.couchbase.client couchbase-jvm-clients - 1.16.5 + 1.16.6-SNAPSHOT fit-performer-java-sdk diff --git a/kotlin-client/pom.xml b/kotlin-client/pom.xml index e9aacd2c8..c40625da8 100644 --- a/kotlin-client/pom.xml +++ b/kotlin-client/pom.xml @@ -7,11 +7,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.5 + 1.16.6-SNAPSHOT kotlin-client - 1.4.5 + 1.4.6-SNAPSHOT Couchbase Kotlin SDK The official Couchbase Kotlin SDK diff --git a/kotlin-fit-performer/pom.xml b/kotlin-fit-performer/pom.xml index c6d4629e0..3dbd652ff 100644 --- a/kotlin-fit-performer/pom.xml +++ b/kotlin-fit-performer/pom.xml @@ -7,7 +7,7 @@ com.couchbase.client couchbase-jvm-clients - 1.16.5 + 1.16.6-SNAPSHOT fit-performer-kotlin diff --git a/metrics-micrometer/pom.xml b/metrics-micrometer/pom.xml index acdd5b244..6d17cfb77 100644 --- a/metrics-micrometer/pom.xml +++ b/metrics-micrometer/pom.xml @@ -8,11 +8,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.5 + 1.16.6-SNAPSHOT metrics-micrometer - 0.7.5 + 0.7.6-SNAPSHOT Micrometer Metrics Interoperability Provides interoperability with Micrometer diff --git a/metrics-opentelemetry/pom.xml b/metrics-opentelemetry/pom.xml index 3c618c0b1..fc88e32e3 100644 --- a/metrics-opentelemetry/pom.xml +++ b/metrics-opentelemetry/pom.xml @@ -6,11 +6,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.5 + 1.16.6-SNAPSHOT metrics-opentelemetry - 0.7.5 + 0.7.6-SNAPSHOT OpenTelemetry Metrics Interoperability Provides interoperability with OpenTelemetry Metrics diff --git a/osgi-feature/pom.xml b/osgi-feature/pom.xml index d6ce8f59d..9bc678594 100644 --- a/osgi-feature/pom.xml +++ b/osgi-feature/pom.xml @@ -7,11 +7,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.5 + 1.16.6-SNAPSHOT osgi-feature - 3.7.5 + 3.7.6-SNAPSHOT pom Couchbase Java SDK OSGI Feature diff --git a/pom.xml b/pom.xml index 45d897930..0f8758b2a 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.couchbase.client couchbase-jvm-clients - 1.16.5 + 1.16.6-SNAPSHOT pom Couchbase JVM Client Parent @@ -15,17 +15,17 @@ UTF-8 UTF-8 - 3.7.5 - 1.7.5 - 3.7.5 - 3.7.5 - 1.7.5 - 1.7.5 - 1.4.5 - 1.0.5 - 1.5.5 - 0.7.5 - 1.7.5 + 3.7.6-SNAPSHOT + 1.7.6-SNAPSHOT + 3.7.6-SNAPSHOT + 3.7.6-SNAPSHOT + 1.7.6-SNAPSHOT + 1.7.6-SNAPSHOT + 1.4.6-SNAPSHOT + 1.0.6-SNAPSHOT + 1.5.6-SNAPSHOT + 0.7.6-SNAPSHOT + 1.7.6-SNAPSHOT 5.11.2 3.23.1 diff --git a/scala-client/pom.xml b/scala-client/pom.xml index 394247e38..ef49f9eef 100644 --- a/scala-client/pom.xml +++ b/scala-client/pom.xml @@ -6,11 +6,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.5 + 1.16.6-SNAPSHOT scala-client_${scala.compat.version} - 1.7.5 + 1.7.6-SNAPSHOT jar Couchbase Scala SDK diff --git a/scala-fit-performer/pom.xml b/scala-fit-performer/pom.xml index 2a41077a8..94c3e77dd 100644 --- a/scala-fit-performer/pom.xml +++ b/scala-fit-performer/pom.xml @@ -5,7 +5,7 @@ com.couchbase.client couchbase-jvm-clients - 1.16.5 + 1.16.6-SNAPSHOT fit-performer-scala diff --git a/scala-implicits/pom.xml b/scala-implicits/pom.xml index 3a7025a62..2ddd5b2eb 100644 --- a/scala-implicits/pom.xml +++ b/scala-implicits/pom.xml @@ -7,11 +7,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.5 + 1.16.6-SNAPSHOT scala-implicits_${scala.compat.version} - 1.7.5 + 1.7.6-SNAPSHOT jar Couchbase Scala SDK Implicits diff --git a/test-utils/pom.xml b/test-utils/pom.xml index bf3843686..50fe51dd7 100644 --- a/test-utils/pom.xml +++ b/test-utils/pom.xml @@ -6,12 +6,12 @@ com.couchbase.client couchbase-jvm-clients - 1.16.5 + 1.16.6-SNAPSHOT Couchbase (Integration) Test Utilities test-utils - 1.7.5 + 1.7.6-SNAPSHOT diff --git a/tracing-micrometer-observation/pom.xml b/tracing-micrometer-observation/pom.xml index 32f3231b6..ef84025cb 100644 --- a/tracing-micrometer-observation/pom.xml +++ b/tracing-micrometer-observation/pom.xml @@ -6,11 +6,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.5 + 1.16.6-SNAPSHOT tracing-micrometer-observation - 1.5.5 + 1.5.6-SNAPSHOT Micrometer Observation Interoperability Provides interoperability with Micrometer Observation diff --git a/tracing-opentelemetry-deps/pom.xml b/tracing-opentelemetry-deps/pom.xml index 3d7e3e703..9372cd288 100644 --- a/tracing-opentelemetry-deps/pom.xml +++ b/tracing-opentelemetry-deps/pom.xml @@ -9,7 +9,7 @@ com.couchbase.client tracing-opentelemetry-deps - 1.5.5 + 1.5.6-SNAPSHOT jar OpenTelemetry Interoperability Dependencies diff --git a/tracing-opentelemetry/pom.xml b/tracing-opentelemetry/pom.xml index 24de5784a..4f7442e8d 100644 --- a/tracing-opentelemetry/pom.xml +++ b/tracing-opentelemetry/pom.xml @@ -6,11 +6,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.5 + 1.16.6-SNAPSHOT tracing-opentelemetry - 1.5.5 + 1.5.6-SNAPSHOT OpenTelemetry Interoperability Provides interoperability with OpenTelemetry @@ -24,7 +24,7 @@ com.couchbase.client tracing-opentelemetry-deps - 1.5.5 + 1.5.6-SNAPSHOT diff --git a/tracing-opentracing/pom.xml b/tracing-opentracing/pom.xml index dcfdce27e..c83a292e0 100644 --- a/tracing-opentracing/pom.xml +++ b/tracing-opentracing/pom.xml @@ -6,11 +6,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.5 + 1.16.6-SNAPSHOT tracing-opentracing - 1.5.5 + 1.5.6-SNAPSHOT OpenTracing Interoperability Provides interoperability with OpenTracing From 4a583f15746d0f6c634895fff14ea70801765982 Mon Sep 17 00:00:00 2001 From: David Nault Date: Mon, 4 Nov 2024 15:41:44 -0800 Subject: [PATCH 47/73] Gardening: Use ClusterTopology in Core Motivation ---------- Migrate to new ClusterTopology model. Modifications ------------- DRY: reconfigureGlobal() and reconfigureBuckets() can now delegate to the same reconfigureGlobalOrBucket() method. The only difference is whether the services get created at global or bucket scope (depending on whether the `bucket` argument is null). DRY in maybeRemoveNode -- code to look for node is now the same regardless of whether the topology is "global" or came from a bucket. Change-Id: I1405d480404711071819c8034a519dca5219e6d8 Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/219091 Tested-by: Build Bot Reviewed-by: Michael Reiche --- .../java/com/couchbase/client/core/Core.java | 167 +++++++----------- 1 file changed, 60 insertions(+), 107 deletions(-) diff --git a/core-io/src/main/java/com/couchbase/client/core/Core.java b/core-io/src/main/java/com/couchbase/client/core/Core.java index f71b4ea77..8ef864d57 100644 --- a/core-io/src/main/java/com/couchbase/client/core/Core.java +++ b/core-io/src/main/java/com/couchbase/client/core/Core.java @@ -53,11 +53,9 @@ import com.couchbase.client.core.cnc.events.core.WatchdogRunFailedEvent; import com.couchbase.client.core.cnc.events.transaction.TransactionsStartedEvent; import com.couchbase.client.core.cnc.metrics.LoggingMeter; -import com.couchbase.client.core.config.BucketConfig; import com.couchbase.client.core.config.ClusterConfig; import com.couchbase.client.core.config.ConfigurationProvider; import com.couchbase.client.core.config.DefaultConfigurationProvider; -import com.couchbase.client.core.config.GlobalConfig; import com.couchbase.client.core.diagnostics.ClusterState; import com.couchbase.client.core.diagnostics.EndpointDiagnostics; import com.couchbase.client.core.diagnostics.InternalEndpointDiagnostics; @@ -87,12 +85,14 @@ import com.couchbase.client.core.node.KeyValueLocator; import com.couchbase.client.core.node.Locator; import com.couchbase.client.core.node.Node; -import com.couchbase.client.core.topology.NodeIdentifier; import com.couchbase.client.core.node.RoundRobinLocator; import com.couchbase.client.core.node.ViewLocator; import com.couchbase.client.core.service.ServiceScope; import com.couchbase.client.core.service.ServiceState; import com.couchbase.client.core.service.ServiceType; +import com.couchbase.client.core.topology.ClusterTopology; +import com.couchbase.client.core.topology.ClusterTopologyWithBucket; +import com.couchbase.client.core.topology.NodeIdentifier; import com.couchbase.client.core.transaction.cleanup.CoreTransactionsCleanup; import com.couchbase.client.core.transaction.components.CoreTransactionRequest; import com.couchbase.client.core.transaction.context.CoreTransactionsContext; @@ -107,6 +107,7 @@ import java.time.Duration; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -626,24 +627,11 @@ protected Node createNode(final NodeIdentifier identifier) { */ private Mono maybeRemoveNode(final Node node, final ClusterConfig config) { return Mono.defer(() -> { - boolean stillPresentInBuckets = config - .bucketConfigs() - .values() - .stream() - .flatMap(bc -> bc.nodes().stream()) - .anyMatch(ni -> ni.id().equals(node.identifier())); - - - boolean stillPresentInGlobal; - if (config.globalConfig() != null) { - stillPresentInGlobal = config - .globalConfig() - .portInfos() - .stream() - .anyMatch(ni -> ni.id().equals(node.identifier())); - } else { - stillPresentInGlobal = false; - } + boolean stillPresentInBuckets = config.bucketTopologies().stream() + .anyMatch(topology -> hasNode(topology, node.identifier())); + + ClusterTopology globalTopology = config.globalTopology(); + boolean stillPresentInGlobal = globalTopology != null && hasNode(globalTopology, node.identifier()); if ((!stillPresentInBuckets && !stillPresentInGlobal) || !node.hasServicesEnabled()) { return node.disconnect().doOnTerminate(() -> nodes.remove(node)); @@ -653,6 +641,11 @@ private Mono maybeRemoveNode(final Node node, final ClusterConfig config) }); } + private static boolean hasNode(ClusterTopology topology, NodeIdentifier nodeId) { + return topology.nodes().stream() + .anyMatch(node -> node.id().equals(nodeId)); + } + /** * This method is used to remove a service from a node. * @@ -688,7 +681,7 @@ public Mono shutdown(final Duration timeout) { invalidStateWatchdog.dispose(); return Flux - .fromIterable(currentConfig.bucketConfigs().keySet()) + .fromIterable(currentConfig.bucketNames()) .flatMap(this::closeBucket) .then(configurationProvider.shutdown()) .then(configurationProcessor.awaitTermination()) @@ -717,18 +710,18 @@ public Mono shutdown(final Duration timeout) { private void reconfigure(Runnable doFinally) { final ClusterConfig configForThisAttempt = currentConfig; - if (configForThisAttempt.bucketConfigs().isEmpty() && configForThisAttempt.globalConfig() == null) { + final ClusterTopology globalTopology = configForThisAttempt.globalTopology(); + final Collection bucketTopologies = configForThisAttempt.bucketTopologies(); + + if (bucketTopologies.isEmpty() && globalTopology == null) { reconfigureDisconnectAll(doFinally); return; } final NanoTimestamp start = NanoTimestamp.now(); - Flux bucketConfigFlux = Flux - .just(configForThisAttempt) - .flatMap(cc -> Flux.fromIterable(cc.bucketConfigs().values())); - reconfigureBuckets(bucketConfigFlux) - .then(reconfigureGlobal(configForThisAttempt.globalConfig())) + reconfigureBuckets(Flux.fromIterable(bucketTopologies)) + .then(reconfigureGlobal(globalTopology)) .then(Mono.defer(() -> Flux .fromIterable(new ArrayList<>(nodes)) @@ -781,115 +774,75 @@ private void reconfigureDisconnectAll(Runnable doFinally) { ); } - private Mono reconfigureGlobal(final GlobalConfig config) { - return Mono.defer(() -> { - if (config == null) { - return Mono.empty(); - } - - return Flux - .fromIterable(config.portInfos()) - .flatMap(ni -> { - boolean tls = coreContext.environment().securityConfig().tlsEnabled(); - - final Map services = tls ? ni.sslPorts() : ni.ports(); - - Flux serviceRemoveFlux = Flux - .fromArray(ServiceType.values()) - .filter(s -> !services.containsKey(s)) - .flatMap(s -> removeServiceFrom( - ni.id(), - s, - Optional.empty()) - .onErrorResume(throwable -> { - eventBus.publish(new ServiceReconfigurationFailedEvent( - coreContext, - ni.hostname(), - s, - throwable - )); - return Mono.empty(); - }) - ); - - - Flux serviceAddFlux = Flux - .fromIterable(services.entrySet()) - .flatMap(s -> ensureServiceAt( - ni.id(), - s.getKey(), - s.getValue(), - Optional.empty()) - .onErrorResume(throwable -> { - eventBus.publish(new ServiceReconfigurationFailedEvent( - coreContext, - ni.hostname(), - s.getKey(), - throwable - )); - return Mono.empty(); - }) - ); - - return Flux.merge(serviceAddFlux, serviceRemoveFlux); - }) - .then(); - }); + private Mono reconfigureGlobal(final @Nullable ClusterTopology topology) { + return topology == null + ? Mono.empty() + : reconfigureGlobalOrBucket(topology, null); } /** * Contains logic to perform reconfiguration for a bucket config. * - * @param bucketConfigs the flux of bucket configs currently open. + * @param bucketTopologies the flux of topologies from currently open buckets * @return a mono once reconfiguration for all buckets is complete */ - private Mono reconfigureBuckets(final Flux bucketConfigs) { - return bucketConfigs.flatMap(bc -> - Flux.fromIterable(bc.nodes()) - .flatMap(ni -> { - boolean tls = coreContext.environment().securityConfig().tlsEnabled(); - - final Map services = tls ? ni.sslServices() : ni.services(); - - Flux serviceRemoveFlux = Flux - .fromArray(ServiceType.values()) - .filter(s -> !services.containsKey(s)) - .flatMap(s -> removeServiceFrom( + private Mono reconfigureBuckets(final Flux bucketTopologies) { + return bucketTopologies.flatMap(bc -> reconfigureGlobalOrBucket(bc, bc.bucket().name())) + .then(); + } + + /** + * @param bucketName pass non-null if using the topology to configure bucket-scoped services. + * + * @implNote Maybe in the future we can inspect the ClusterTopology to see if it has a BucketTopology, + * and get the bucket name from there. However, let's make it explicit for now; this leaves the door open + * to using a ClusterTopologyWithBucket to configure global services (by passing a null bucket name). + */ + private Mono reconfigureGlobalOrBucket( + ClusterTopology topology, + @Nullable String bucketName + ) { + return Flux.fromIterable(topology.nodes()) + .flatMap(ni -> { + Flux serviceRemoveFlux = Flux + .fromArray(ServiceType.values()) + .filter(s -> !ni.has(s)) + .flatMap(s -> removeServiceFrom( ni.id(), s, - s.scope() == ServiceScope.BUCKET ? Optional.of(bc.name()) : Optional.empty()) + s.scope() == ServiceScope.BUCKET ? Optional.ofNullable(bucketName) : Optional.empty()) .onErrorResume(throwable -> { eventBus.publish(new ServiceReconfigurationFailedEvent( coreContext, - ni.hostname(), + ni.host(), s, throwable )); return Mono.empty(); }) - ); + ); - Flux serviceAddFlux = Flux - .fromIterable(services.entrySet()) - .flatMap(s -> ensureServiceAt( + Flux serviceAddFlux = Flux + .fromIterable(ni.ports().entrySet()) + .flatMap(s -> ensureServiceAt( ni.id(), s.getKey(), s.getValue(), - s.getKey().scope() == ServiceScope.BUCKET ? Optional.of(bc.name()) : Optional.empty()) + s.getKey().scope() == ServiceScope.BUCKET ? Optional.ofNullable(bucketName) : Optional.empty()) .onErrorResume(throwable -> { eventBus.publish(new ServiceReconfigurationFailedEvent( coreContext, - ni.hostname(), + ni.host(), s.getKey(), throwable )); return Mono.empty(); }) - ); + ); - return Flux.merge(serviceAddFlux, serviceRemoveFlux); - }) - ).then(); + return Flux.merge(serviceAddFlux, serviceRemoveFlux); + }) + .then(); } /** From 190777dee0ad556b48d7cff7a6eabdee35009692 Mon Sep 17 00:00:00 2001 From: David Nault Date: Sun, 3 Nov 2024 13:53:26 -0800 Subject: [PATCH 48/73] Gardening: Use ClusterTopology in HealthPinger Motivation ---------- Migrate to new ClusterTopology model. Modifications ------------- Replace GlobalConfig with ClusterTopology. Replace BucketConfig with ClusterTopologyWithBucket. DRY: Exploit commonality between ClusterTopology and ClusterTopologyWithBucket. Change-Id: If9dcbd935182217cda04f1c7a178f3ca3b46fff1 Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/219028 Tested-by: Build Bot Reviewed-by: Michael Reiche --- .../client/core/diagnostics/HealthPinger.java | 129 +++++++----------- 1 file changed, 46 insertions(+), 83 deletions(-) diff --git a/core-io/src/main/java/com/couchbase/client/core/diagnostics/HealthPinger.java b/core-io/src/main/java/com/couchbase/client/core/diagnostics/HealthPinger.java index 2677583f8..cde7bf02b 100644 --- a/core-io/src/main/java/com/couchbase/client/core/diagnostics/HealthPinger.java +++ b/core-io/src/main/java/com/couchbase/client/core/diagnostics/HealthPinger.java @@ -19,11 +19,7 @@ import com.couchbase.client.core.Core; import com.couchbase.client.core.Reactor; import com.couchbase.client.core.annotation.Stability; -import com.couchbase.client.core.config.BucketConfig; import com.couchbase.client.core.config.ClusterConfig; -import com.couchbase.client.core.config.GlobalConfig; -import com.couchbase.client.core.config.NodeInfo; -import com.couchbase.client.core.config.PortInfo; import com.couchbase.client.core.endpoint.http.CoreCommonOptions; import com.couchbase.client.core.endpoint.http.CoreHttpRequest; import com.couchbase.client.core.error.TimeoutException; @@ -34,17 +30,22 @@ import com.couchbase.client.core.msg.kv.KvPingResponse; import com.couchbase.client.core.retry.RetryStrategy; import com.couchbase.client.core.service.ServiceType; +import com.couchbase.client.core.topology.ClusterTopology; +import com.couchbase.client.core.topology.ClusterTopologyWithBucket; +import com.couchbase.client.core.topology.HostAndServicePorts; import com.couchbase.client.core.util.CbThrowables; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.util.annotation.Nullable; import java.time.Duration; +import java.util.ArrayList; import java.util.Collection; import java.util.EnumSet; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.UUID; @@ -62,7 +63,6 @@ import static com.couchbase.client.core.util.CbCollections.transformValues; import static java.util.Collections.unmodifiableSet; import static java.util.stream.Collectors.groupingBy; -import static java.util.stream.Collectors.toSet; /** * The {@link HealthPinger} allows to "ping" individual services with real operations for their health. @@ -83,6 +83,11 @@ public class HealthPinger { ANALYTICS )); + private static final Set servicesThatRequireBucket = unmodifiableSet(EnumSet.of( + KV, + VIEWS + )); + @Stability.Internal public static Mono ping( final Core core, @@ -140,97 +145,55 @@ static Set extractPingTargets( final Optional bucketName, final WaitUntilReadyHelper.WaitUntilReadyLogger log ) { - final Set serviceTypes = isNullOrEmpty(serviceTypesOrEmpty) + final String bucket = bucketName.orElse(null); + final Set serviceTypeFilter = isNullOrEmpty(serviceTypesOrEmpty) ? EnumSet.allOf(ServiceType.class) : EnumSet.copyOf(serviceTypesOrEmpty); - serviceTypes.retainAll(pingableServices); // narrow to the ones we can actually ping + serviceTypeFilter.retainAll(pingableServices); // narrow to the ones we can actually ping - Set targets = new HashSet<>(); - log.message("extractPingTargets: starting ping target extraction with candidate services: " + serviceTypes); + log.message("extractPingTargets: starting ping target extraction with candidate services: " + serviceTypeFilter + " and bucket: " + bucket); - if (!bucketName.isPresent()) { - if (clusterConfig.globalConfig() != null) { - GlobalConfig globalConfig = clusterConfig.globalConfig(); - - log.message("extractPingTargets: getting ping targets from global config portInfos: " + globalConfig.portInfos()); - for (PortInfo portInfo : globalConfig.portInfos()) { - for (ServiceType serviceType : advertisedServices(portInfo)) { - if (serviceType == ServiceType.KV || serviceType == ServiceType.VIEWS) { - // do not check bucket-level resources from a global level (null bucket name will not work) - continue; - } - RequestTarget target = new RequestTarget(serviceType, portInfo.id(), null); - log.message("extractPingTargets: adding target from global config: " + target); - targets.add(target); - } - } - log.message("extractPingTargets: ping targets after scanning global config: " + targets); - } else { - log.message("extractPingTargets: globalConfig is absent"); - } - for (Map.Entry bucketConfig : clusterConfig.bucketConfigs().entrySet()) { - log.message("extractPingTargets: getting targets from bucket config via global config for bucket " + bucketConfig.getKey() + " : " + bucketConfig.getValue()); - - for (NodeInfo nodeInfo : bucketConfig.getValue().nodes()) { - for (ServiceType serviceType : advertisedServices(nodeInfo)) { - if (serviceType == ServiceType.KV || serviceType == ServiceType.VIEWS) { - // do not check bucket-level resources from a global level (null bucket name will not work) - continue; - } - RequestTarget target = new RequestTarget(serviceType, nodeInfo.id(), null); - log.message("extractPingTargets: adding target from bucket config via global config: " + target); - targets.add(new RequestTarget(serviceType, nodeInfo.id(), null)); - } - } - } + final List topologiesToScan = new ArrayList<>(); + if (bucket != null) { + topologiesToScan.add(clusterConfig.bucketTopology(bucket)); } else { - BucketConfig bucketConfig = clusterConfig.bucketConfig(bucketName.get()); - if (bucketConfig != null) { - log.message("extractPingTargets: Getting targets from bucket config: " + bucketConfig); - for (NodeInfo nodeInfo : bucketConfig.nodes()) { - for (ServiceType serviceType : advertisedServices(nodeInfo)) { - RequestTarget target; - if (serviceType != ServiceType.VIEWS && serviceType != ServiceType.KV) { - target = new RequestTarget(serviceType, nodeInfo.id(), null); - } else { - target = new RequestTarget(serviceType, nodeInfo.id(), bucketName.get()); - } + serviceTypeFilter.removeAll(servicesThatRequireBucket); // narrow to the ones that can be pinged without a bucket + topologiesToScan.add(clusterConfig.globalTopology()); + topologiesToScan.addAll(clusterConfig.bucketTopologies()); + } - log.message("extractPingTargets: adding target from bucket config: " + target); - targets.add(target); + Set result = new HashSet<>(); + topologiesToScan.stream() + .filter(Objects::nonNull) // global or specific bucket topology might be absent + .forEach(topology -> { + log.message("extractPingTargets: scanning " + describe(topology)); + for (HostAndServicePorts node : topology.nodes()) { + for (ServiceType advertisedService : advertisedServices(node)) { + if (serviceTypeFilter.contains(advertisedService)) { + boolean serviceRequiresBucket = servicesThatRequireBucket.contains(advertisedService); + String targetBucketOrNull = serviceRequiresBucket ? bucket : null; + RequestTarget target = new RequestTarget(advertisedService, node.id(), targetBucketOrNull); + if (result.add(target)) { + log.message("extractPingTargets: found new target " + target); + } + } } } - } else { - log.message("extractPingTargets: Bucket name was present, but clusterConfig has no config for bucket " + bucketName); - } - } - - // Narrow the results to only pingable services the caller is interested in. - targets = targets.stream() - .filter(t -> serviceTypes.contains(t.serviceType())) - .collect(toSet()); + }); - log.message( - "extractPingTargets: Finished. Returning filtered targets (grouped by node): " + formatGroupedByNode(targets) - ); - - return targets; + log.message("extractPingTargets: Finished. Returning filtered targets (grouped by node): " + formatGroupedByNode(result)); + return result; } - private static Set advertisedServices(PortInfo info) { - Set result = EnumSet.noneOf(ServiceType.class); - // add both because only is populated - result.addAll(info.ports().keySet()); - result.addAll(info.sslPorts().keySet()); - return result; + private static String describe(ClusterTopology topology) { + String bucketOrGlobal = (topology instanceof ClusterTopologyWithBucket) + ? "bucket '" + topology.requireBucket().bucket().name() + "'" + : "global"; + return "topology from " + bucketOrGlobal + " ; nodes=" + topology.nodes(); } - private static Set advertisedServices(NodeInfo info) { - Set result = EnumSet.noneOf(ServiceType.class); - // add both because only is populated - result.addAll(info.services().keySet()); - result.addAll(info.sslServices().keySet()); - return result; + private static Set advertisedServices(HostAndServicePorts node) { + return node.ports().keySet(); } /** From 280a07ef7f835dcead3c92d1c6921e14473f31cb Mon Sep 17 00:00:00 2001 From: David Nault Date: Sun, 3 Nov 2024 13:38:49 -0800 Subject: [PATCH 49/73] Gardening: Use ClusterTopology in SearchCapabilityCheck Motivation ---------- Migrate to new ClusterTopology model. Modifications ------------- Add core.waitForClusterTopology(), adapted from ClusterCapabilitiesUtil.waitForClusterCapabilities() and updated to use non-deprecated Retry API. Instead of waiting specifically for capabilities, wait for a ClusterTopology (with or without bucket) and get the cluster capabilities from there. Note: This wasn't possible with GlobalConfig/BucketConfig, because those two classes had different methods for getting cluster capabilities. In the new model, both the global and bucket-specific topologies are instances of ClusterTopology, the class that exposes cluster capabilities. DRY in SearchCapabilityCheck. Change-Id: Ic67eb5cefc8cd1fa3b5735652606a004d0748d27 Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/219027 Tested-by: Build Bot Reviewed-by: Michael Reiche --- .../java/com/couchbase/client/core/Core.java | 30 ++++++++ .../search/util/SearchCapabilityCheck.java | 37 +++++----- .../core/util/ClusterCapabilitiesUtil.java | 69 ------------------- 3 files changed, 48 insertions(+), 88 deletions(-) delete mode 100644 core-io/src/main/java/com/couchbase/client/core/util/ClusterCapabilitiesUtil.java diff --git a/core-io/src/main/java/com/couchbase/client/core/Core.java b/core-io/src/main/java/com/couchbase/client/core/Core.java index 8ef864d57..406eb929d 100644 --- a/core-io/src/main/java/com/couchbase/client/core/Core.java +++ b/core-io/src/main/java/com/couchbase/client/core/Core.java @@ -69,6 +69,7 @@ import com.couchbase.client.core.error.GlobalConfigNotFoundException; import com.couchbase.client.core.error.InvalidArgumentException; import com.couchbase.client.core.error.RequestCanceledException; +import com.couchbase.client.core.error.UnambiguousTimeoutException; import com.couchbase.client.core.error.UnsupportedConfigMechanismException; import com.couchbase.client.core.io.CollectionIdentifier; import com.couchbase.client.core.manager.CoreBucketManagerOps; @@ -98,12 +99,14 @@ import com.couchbase.client.core.transaction.context.CoreTransactionsContext; import com.couchbase.client.core.util.ConnectionString; import com.couchbase.client.core.util.CoreIdGenerator; +import com.couchbase.client.core.util.Deadline; import com.couchbase.client.core.util.LatestStateSubscription; import com.couchbase.client.core.util.NanoTimestamp; import reactor.core.Disposable; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.util.annotation.Nullable; +import reactor.util.retry.Retry; import java.time.Duration; import java.util.ArrayList; @@ -111,6 +114,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.NoSuchElementException; import java.util.Objects; import java.util.Optional; import java.util.Set; @@ -526,6 +530,32 @@ private Mono closeBucket(final String name) { }); } + @Stability.Internal + public Mono waitForClusterTopology(Duration timeout) { + return Mono.defer(() -> { + Deadline deadline = Deadline.of(timeout); + + return Mono.fromCallable(() -> { + ClusterTopology globalTopology = clusterConfig().globalTopology(); + if (globalTopology != null) { + return globalTopology; + } + + for (ClusterTopologyWithBucket topology : clusterConfig().bucketTopologies()) { + return topology; + } + + throw deadline.exceeded() + ? new UnambiguousTimeoutException("Timed out while waiting for cluster topology", null) + : new NoSuchElementException(); // trigger retry! + }) + .retryWhen(Retry + .fixedDelay(Long.MAX_VALUE, Duration.ofMillis(100)) + .filter(t -> t instanceof NoSuchElementException) + ); + }); + } + /** * This method can be used by a caller to make sure a certain service is enabled at the given * target node. diff --git a/core-io/src/main/java/com/couchbase/client/core/api/search/util/SearchCapabilityCheck.java b/core-io/src/main/java/com/couchbase/client/core/api/search/util/SearchCapabilityCheck.java index 259867c8f..77731202c 100644 --- a/core-io/src/main/java/com/couchbase/client/core/api/search/util/SearchCapabilityCheck.java +++ b/core-io/src/main/java/com/couchbase/client/core/api/search/util/SearchCapabilityCheck.java @@ -17,10 +17,8 @@ import com.couchbase.client.core.Core; import com.couchbase.client.core.annotation.Stability; -import com.couchbase.client.core.config.ClusterCapabilities; import com.couchbase.client.core.error.FeatureNotAvailableException; -import com.couchbase.client.core.service.ServiceType; -import com.couchbase.client.core.util.ClusterCapabilitiesUtil; +import com.couchbase.client.core.topology.ClusterCapability; import java.time.Duration; import java.util.concurrent.CompletableFuture; @@ -31,25 +29,26 @@ private SearchCapabilityCheck() { } public static CompletableFuture scopedSearchIndexCapabilityCheck(Core core, Duration timeout) { - return ClusterCapabilitiesUtil.waitForClusterCapabilities(core, timeout) - .doOnNext(clusterCapabilities -> { - if (!clusterCapabilities.get(ServiceType.SEARCH).contains(ClusterCapabilities.SCOPED_SEARCH_INDEX)) { - throw new FeatureNotAvailableException("This method cannot be used with this cluster, as it does not support scoped search indexes. Please use a cluster fully upgraded to Couchbase Server 7.6 or above."); - } - }) - .then() - .toFuture(); + return requireCapability(core, timeout, ClusterCapability.SEARCH_SCOPED, + "This method cannot be used with this cluster, as it does not support scoped search indexes." + + " Please use a cluster fully upgraded to Couchbase Server 7.6 or above."); } public static CompletableFuture vectorSearchCapabilityCheck(Core core, Duration timeout) { - return ClusterCapabilitiesUtil.waitForClusterCapabilities(core, timeout) - .doOnNext(clusterCapabilities -> { - if (!clusterCapabilities.get(ServiceType.SEARCH).contains(ClusterCapabilities.VECTOR_SEARCH)) { - throw new FeatureNotAvailableException("This method cannot be used with this cluster, as it does not support vector search. Please use a cluster fully upgraded to Couchbase Server 7.6 or above."); - } - }) - .then() - .toFuture(); + return requireCapability(core, timeout, ClusterCapability.SEARCH_VECTOR, + "This method cannot be used with this cluster, as it does not support vector search." + + " Please use a cluster fully upgraded to Couchbase Server 7.6 or above."); + } + + private static CompletableFuture requireCapability(Core core, Duration timeout, ClusterCapability capability, String message) { + return core.waitForClusterTopology(timeout) + .doOnNext(topology -> { + if (!topology.hasCapability(capability)) { + throw new FeatureNotAvailableException(message); + } + }) + .then() + .toFuture(); } } diff --git a/core-io/src/main/java/com/couchbase/client/core/util/ClusterCapabilitiesUtil.java b/core-io/src/main/java/com/couchbase/client/core/util/ClusterCapabilitiesUtil.java deleted file mode 100644 index 0a69e04d0..000000000 --- a/core-io/src/main/java/com/couchbase/client/core/util/ClusterCapabilitiesUtil.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) 2024 Couchbase, Inc. - * - * Licensed 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 com.couchbase.client.core.util; - -import com.couchbase.client.core.Core; -import com.couchbase.client.core.annotation.Stability; -import com.couchbase.client.core.config.BucketConfig; -import com.couchbase.client.core.config.ClusterCapabilities; -import com.couchbase.client.core.config.GlobalConfig; -import com.couchbase.client.core.error.UnambiguousTimeoutException; -import com.couchbase.client.core.retry.reactor.Backoff; -import com.couchbase.client.core.retry.reactor.Retry; -import com.couchbase.client.core.retry.reactor.RetryExhaustedException; -import com.couchbase.client.core.service.ServiceType; -import reactor.core.publisher.Mono; - -import java.time.Duration; -import java.util.Map; -import java.util.Set; - -/** - * Defines helpful routines for working with cluster capabilities. - */ -@Stability.Internal -public class ClusterCapabilitiesUtil { - private ClusterCapabilitiesUtil() { - } - - private static final Duration retryDelay = Duration.ofMillis(100); - - public static Mono>> waitForClusterCapabilities(final Core core, - final Duration timeout) { - return Mono.fromCallable(() -> { - // Cluster capabilities should be the same across global and bucket configs, so just use whatever is available. - GlobalConfig globalConfig = core.clusterConfig().globalConfig(); - if (globalConfig != null) { - return globalConfig.clusterCapabilities(); - } - Map bucketConfigs = core.clusterConfig().bucketConfigs(); - if (bucketConfigs != null && !bucketConfigs.isEmpty()) { - return bucketConfigs.values().iterator().next().clusterCapabilities(); - } - throw new NullPointerException(); - }).retryWhen(Retry.anyOf(NullPointerException.class) - .timeout(timeout) - .backoff(Backoff.fixed(retryDelay)) - .toReactorRetry()) - .onErrorResume(err -> { - if (err instanceof RetryExhaustedException) { - return Mono.error(new UnambiguousTimeoutException("Timed out while waiting for global config", null)); - } else { - return Mono.error(err); - } - }); - } -} From d0729b0f3e7ba8cc98720da3e4c429df80786431 Mon Sep 17 00:00:00 2001 From: Emilien Bevierre Date: Thu, 7 Nov 2024 10:22:42 +0000 Subject: [PATCH 50/73] Revert^2 "JCBC-2173: Upgrade jctools-core to 4.0.5" This reverts commit 4b921ffcffd3d3978a2f427cc0431a80558c1787. Reason for revert: Re-merging now that 3.7.5 has been released. Change-Id: I37b05df70db38971335659c19fb1750f80a40a24 Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/219250 Reviewed-by: Emilien Bevierre Tested-by: Build Bot --- core-io-deps/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core-io-deps/pom.xml b/core-io-deps/pom.xml index 387093da5..c8c317292 100644 --- a/core-io-deps/pom.xml +++ b/core-io-deps/pom.xml @@ -79,7 +79,7 @@ org.jctools jctools-core - 4.0.1 + 4.0.5 com.fasterxml.jackson.core From a3dfd96e09e09890bb4b68a67b3118317b82088f Mon Sep 17 00:00:00 2001 From: David Nault Date: Thu, 7 Nov 2024 10:03:45 -0800 Subject: [PATCH 51/73] scalafmt Change-Id: I1c38d41ee75b62840a35b3b2207d6f257d215125 Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/219276 Tested-by: David Nault Reviewed-by: David Nault --- .../scala/com/couchbase/client/scala/AsyncCollection.scala | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/scala-client/src/main/scala/com/couchbase/client/scala/AsyncCollection.scala b/scala-client/src/main/scala/com/couchbase/client/scala/AsyncCollection.scala index 234fbb387..759e250fc 100644 --- a/scala-client/src/main/scala/com/couchbase/client/scala/AsyncCollection.scala +++ b/scala-client/src/main/scala/com/couchbase/client/scala/AsyncCollection.scala @@ -497,8 +497,7 @@ class AsyncCollection( ): Future[GetReplicaResult] = { convert( kvOps.getAnyReplicaReactive(makeCommonOptions(timeout), id, CoreReadPreference.NO_PREFERENCE) - ).map(result => convertReplica(result, environment, None)) - .toFuture + ).map(result => convertReplica(result, environment, None)).toFuture } /** Retrieves any available version of the document. @@ -588,8 +587,7 @@ class AsyncCollection( LookupInSpec.map(spec).asJava, CoreReadPreference.NO_PREFERENCE ) - ).map(result => convertLookupInReplica(result, environment)) - .toFuture + ).map(result => convertLookupInReplica(result, environment)).toFuture } /** SubDocument lookups allow retrieving parts of a JSON document directly, which may be more efficient than From d0e583868f8efd9d9e1594c63d61733f56d4cfda Mon Sep 17 00:00:00 2001 From: David Nault Date: Wed, 6 Nov 2024 08:57:59 -0800 Subject: [PATCH 52/73] Gardening: Create new OrchestratorProxy for each test Motivation ---------- Groundwork for migrating to new ClusterTopology model. Makes it easier to use a real ClusterTopology instance instead of a mock (coming in a future change). Important because with the new model it's easier to create a real ClusterTopology than a mock one. Modifications ------------- For each test, create a new OrchestratorProxy initialized with the desired partition data. Change-Id: Icf4cd42d644f5fe0a5e8daa9108d45f54d51d66a Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/219213 Tested-by: Build Bot Reviewed-by: Michael Reiche --- .../client/core/kv/OrchestratorProxy.java | 6 ++- .../core/kv/RangeScanOrchestratorTest.java | 41 +++++++++---------- 2 files changed, 23 insertions(+), 24 deletions(-) diff --git a/core-io/src/test/java/com/couchbase/client/core/kv/OrchestratorProxy.java b/core-io/src/test/java/com/couchbase/client/core/kv/OrchestratorProxy.java index 5d19fb766..14ea3403a 100644 --- a/core-io/src/test/java/com/couchbase/client/core/kv/OrchestratorProxy.java +++ b/core-io/src/test/java/com/couchbase/client/core/kv/OrchestratorProxy.java @@ -59,7 +59,7 @@ class OrchestratorProxy { private final CouchbaseBucketConfig bucketConfig; - public OrchestratorProxy(final CoreEnvironment environment, boolean capabilityEnabled) { + public OrchestratorProxy(final CoreEnvironment environment, boolean capabilityEnabled, Map> data) { // Set up Bucket Config ClusterConfig clusterConfig = new ClusterConfig(); bucketConfig = mock(CouchbaseBucketConfig.class); @@ -81,9 +81,11 @@ public OrchestratorProxy(final CoreEnvironment environment, boolean capabilityEn when(core.configurationProvider()).thenReturn(configurationProvider); rangeScanOrchestrator = new RangeScanOrchestrator(core, collectionIdentifier); + + prepare(data); } - void prepare(final Map> data) { + private void prepare(final Map> data) { when(bucketConfig.numberOfPartitions()).thenReturn(data.size()); Map uuids = new HashMap<>(); diff --git a/core-io/src/test/java/com/couchbase/client/core/kv/RangeScanOrchestratorTest.java b/core-io/src/test/java/com/couchbase/client/core/kv/RangeScanOrchestratorTest.java index 04bda1d6b..6c4b06e4b 100644 --- a/core-io/src/test/java/com/couchbase/client/core/kv/RangeScanOrchestratorTest.java +++ b/core-io/src/test/java/com/couchbase/client/core/kv/RangeScanOrchestratorTest.java @@ -21,21 +21,21 @@ import com.couchbase.client.core.env.CoreEnvironment; import com.couchbase.client.core.error.FeatureNotAvailableException; import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import reactor.core.publisher.Flux; import java.nio.charset.StandardCharsets; import java.time.Instant; import java.util.Comparator; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Random; +import static com.couchbase.client.core.util.CbCollections.mapOf; import static com.couchbase.client.core.util.CbStrings.MAX_CODE_POINT_AS_STRING; import static com.couchbase.client.core.util.CbStrings.MIN_CODE_POINT_AS_STRING; +import static java.util.Collections.emptyMap; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -46,27 +46,24 @@ class RangeScanOrchestratorTest { private static final CoreEnvironment ENVIRONMENT = CoreEnvironment.create(); - private OrchestratorProxy orchestrator; - - @BeforeEach - void beforeEach() { - orchestrator = new OrchestratorProxy(ENVIRONMENT, true); - } - @AfterAll static void afterAll() { ENVIRONMENT.shutdown(); } + private static OrchestratorProxy newProxy(Map> data) { + return new OrchestratorProxy(ENVIRONMENT, true, data); + } + /** * Most basic test which makes sure that items are streamed as-is from the underlying partitions. */ @Test void streamsUnsortedRangeScan() { - Map> data = new HashMap<>(); - data.put((short) 0, randomItemsSorted(5)); - data.put((short) 1, randomItemsSorted(3)); - orchestrator.prepare(data); + OrchestratorProxy orchestrator = newProxy(mapOf( + (short) 0, randomItemsSorted(5), + (short) 1, randomItemsSorted(3) + )); List result = orchestrator.runRangeScan(new TestRangeScan(), new TestScanOptions()); assertEquals(8, result.size()); @@ -77,10 +74,10 @@ void streamsUnsortedRangeScan() { */ @Test void streamsUnsortedSamplingScan() { - Map> data = new HashMap<>(); - data.put((short) 0, randomItemsSorted(3)); - data.put((short) 1, randomItemsSorted(4)); - orchestrator.prepare(data); + OrchestratorProxy orchestrator = newProxy(mapOf( + (short) 0, randomItemsSorted(3), + (short) 1, randomItemsSorted(4) + )); List result = orchestrator.runSamplingScan(new TestSamplingScan(10), new TestScanOptions()); assertEquals(7, result.size()); @@ -91,10 +88,10 @@ void streamsUnsortedSamplingScan() { */ @Test void samplingStopsAtLimit() { - Map> data = new HashMap<>(); - data.put((short) 0, randomItemsSorted(12)); - data.put((short) 1, randomItemsSorted(10)); - orchestrator.prepare(data); + OrchestratorProxy orchestrator = newProxy(mapOf( + (short) 0, randomItemsSorted(12), + (short) 1, randomItemsSorted(10) + )); List result = orchestrator.runSamplingScan( new TestSamplingScan(10), new TestScanOptions()); assertEquals(10, result.size()); @@ -105,7 +102,7 @@ void samplingStopsAtLimit() { */ @Test void failIfBucketCapabilityNotAvailable() { - OrchestratorProxy orchestrator = new OrchestratorProxy(ENVIRONMENT, false); + OrchestratorProxy orchestrator = new OrchestratorProxy(ENVIRONMENT, false, emptyMap()); assertThrows(FeatureNotAvailableException.class, () -> orchestrator.runRangeScan(new TestRangeScan(), new TestScanOptions())); } From a370328f62575b0bc4bfa6512e645ed18ae7c5af Mon Sep 17 00:00:00 2001 From: David Nault Date: Thu, 7 Nov 2024 10:33:01 -0800 Subject: [PATCH 53/73] Gardening: Simplify KeyValueLocator.calculateNodeId() Motivation ---------- Remove special-case logic for requests that target replicas. Reinforce the idea that the primary ("active") replica set member shares the same index space as the replicas. In other words, 0 is the primary, 1 is the first replica, etc. just like in the bucket topology JSON. Modifications ------------- Push `replica()` from ReplicaGetRequest and friends up into the KeyValueRequest interface, with a default implementation that says the request targets the primary. Change-Id: I84159e72f3218af01e3989854909bf84dc362edb Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/219277 Tested-by: Build Bot Reviewed-by: Michael Reiche --- .../client/core/msg/kv/KeyValueRequest.java | 13 +++++++++++++ .../client/core/msg/kv/ObserveViaCasRequest.java | 1 + .../core/msg/kv/ObserveViaSeqnoRequest.java | 1 + .../client/core/msg/kv/ReplicaGetRequest.java | 3 ++- .../core/msg/kv/ReplicaSubdocGetRequest.java | 3 ++- .../client/core/node/KeyValueLocator.java | 16 ++++------------ 6 files changed, 23 insertions(+), 14 deletions(-) diff --git a/core-io/src/main/java/com/couchbase/client/core/msg/kv/KeyValueRequest.java b/core-io/src/main/java/com/couchbase/client/core/msg/kv/KeyValueRequest.java index 7fa5f7e68..1396b0c88 100644 --- a/core-io/src/main/java/com/couchbase/client/core/msg/kv/KeyValueRequest.java +++ b/core-io/src/main/java/com/couchbase/client/core/msg/kv/KeyValueRequest.java @@ -45,6 +45,19 @@ public interface KeyValueRequest extends Request, ScopedR */ void partition(short partition); + /** + * Returns the index of the replica set member this request targets. + *

    + *
  • 0 = primary ("active") + *
  • 1 = first replica + *
  • 2 = second replica + *
  • 3 = third replica + *
+ */ + default int replica() { + return 0; + } + /** * Encode this request with the given allocator and opaque. * diff --git a/core-io/src/main/java/com/couchbase/client/core/msg/kv/ObserveViaCasRequest.java b/core-io/src/main/java/com/couchbase/client/core/msg/kv/ObserveViaCasRequest.java index 6d5489d6a..f7af2775a 100644 --- a/core-io/src/main/java/com/couchbase/client/core/msg/kv/ObserveViaCasRequest.java +++ b/core-io/src/main/java/com/couchbase/client/core/msg/kv/ObserveViaCasRequest.java @@ -44,6 +44,7 @@ public ObserveViaCasRequest(final Duration timeout, final CoreContext ctx, Colle this.replica = replica; } + @Override public int replica() { return replica; } diff --git a/core-io/src/main/java/com/couchbase/client/core/msg/kv/ObserveViaSeqnoRequest.java b/core-io/src/main/java/com/couchbase/client/core/msg/kv/ObserveViaSeqnoRequest.java index a89ec6e32..c2bde2f08 100644 --- a/core-io/src/main/java/com/couchbase/client/core/msg/kv/ObserveViaSeqnoRequest.java +++ b/core-io/src/main/java/com/couchbase/client/core/msg/kv/ObserveViaSeqnoRequest.java @@ -52,6 +52,7 @@ public ObserveViaSeqnoRequest(final Duration timeout, final CoreContext ctx, Col } } + @Override public int replica() { return replica; } diff --git a/core-io/src/main/java/com/couchbase/client/core/msg/kv/ReplicaGetRequest.java b/core-io/src/main/java/com/couchbase/client/core/msg/kv/ReplicaGetRequest.java index 47766cf6c..c681af08b 100644 --- a/core-io/src/main/java/com/couchbase/client/core/msg/kv/ReplicaGetRequest.java +++ b/core-io/src/main/java/com/couchbase/client/core/msg/kv/ReplicaGetRequest.java @@ -50,7 +50,8 @@ public ReplicaGetRequest(final String key, final Duration timeout, } } - public short replica() { + @Override + public int replica() { return replica; } diff --git a/core-io/src/main/java/com/couchbase/client/core/msg/kv/ReplicaSubdocGetRequest.java b/core-io/src/main/java/com/couchbase/client/core/msg/kv/ReplicaSubdocGetRequest.java index 1b1082bef..3c719cabb 100644 --- a/core-io/src/main/java/com/couchbase/client/core/msg/kv/ReplicaSubdocGetRequest.java +++ b/core-io/src/main/java/com/couchbase/client/core/msg/kv/ReplicaSubdocGetRequest.java @@ -50,7 +50,8 @@ public static ReplicaSubdocGetRequest create(final Duration timeout, final CoreC } } - public short replica() { + @Override + public int replica() { return replica; } diff --git a/core-io/src/main/java/com/couchbase/client/core/node/KeyValueLocator.java b/core-io/src/main/java/com/couchbase/client/core/node/KeyValueLocator.java index 998d7e46d..a48b864e9 100644 --- a/core-io/src/main/java/com/couchbase/client/core/node/KeyValueLocator.java +++ b/core-io/src/main/java/com/couchbase/client/core/node/KeyValueLocator.java @@ -30,10 +30,7 @@ import com.couchbase.client.core.msg.Response; import com.couchbase.client.core.msg.kv.DurabilityLevel; import com.couchbase.client.core.msg.kv.KeyValueRequest; -import com.couchbase.client.core.msg.kv.ObserveViaSeqnoRequest; import com.couchbase.client.core.msg.kv.PredeterminedPartitionRequest; -import com.couchbase.client.core.msg.kv.ReplicaGetRequest; -import com.couchbase.client.core.msg.kv.ReplicaSubdocGetRequest; import com.couchbase.client.core.msg.kv.SyncDurabilityRequest; import com.couchbase.client.core.retry.AuthErrorDecider; import com.couchbase.client.core.retry.RetryOrchestrator; @@ -205,15 +202,10 @@ private static int calculateNodeId(int partitionId, final KeyValueRequest req // having the request being stuck on the server side during rebalance. boolean useFastForward = config.hasFastForwardMap() && request.rejectedWithNotMyVbucket() > 0; - if (request instanceof ReplicaGetRequest) { - return config.nodeIndexForReplica(partitionId, ((ReplicaGetRequest) request).replica() - 1, useFastForward); - } else if (request instanceof ReplicaSubdocGetRequest) { - return config.nodeIndexForReplica(partitionId, ((ReplicaSubdocGetRequest) request).replica() - 1, useFastForward); - } else if (request instanceof ObserveViaSeqnoRequest && ((ObserveViaSeqnoRequest) request).replica() > 0) { - return config.nodeIndexForReplica(partitionId, ((ObserveViaSeqnoRequest) request).replica() - 1, useFastForward); - } else { - return config.nodeIndexForActive(partitionId, useFastForward); - } + int replica = request.replica(); + return replica == 0 + ? config.nodeIndexForActive(partitionId, useFastForward) + : config.nodeIndexForReplica(partitionId, replica - 1, useFastForward); } From 385612558d1c5ba5998b9e1a5f224e7177612d19 Mon Sep 17 00:00:00 2001 From: Aaliya7516 Date: Thu, 30 May 2024 14:03:18 +0530 Subject: [PATCH 54/73] Added ClientContextId TransactionQueryOption to Java and Scala performers. Change-Id: I51a25283d4a335e770bb42b270b37bd424b8421e Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/210623 Reviewed-by: Will Broadbelt Tested-by: Build Bot --- .../com/couchbase/client/performer/core/CorePerformer.java | 1 + .../transactions/SingleQueryTransactionExecutor.java | 4 ++++ .../src/main/java/com/couchbase/utils/OptionsUtil.java | 4 ++++ .../couchbase/client/performer/scala/util/OptionsUtil.scala | 1 + 4 files changed, 10 insertions(+) diff --git a/core-fit-performer/src/main/java/com/couchbase/client/performer/core/CorePerformer.java b/core-fit-performer/src/main/java/com/couchbase/client/performer/core/CorePerformer.java index ee3fd614f..0cc3ad993 100644 --- a/core-fit-performer/src/main/java/com/couchbase/client/performer/core/CorePerformer.java +++ b/core-fit-performer/src/main/java/com/couchbase/client/performer/core/CorePerformer.java @@ -64,6 +64,7 @@ public void performerCapsFetch(PerformerCapsFetchRequest request, StreamObserver .addPerformerCaps(Caps.GRPC_TESTING) // Add any shared caps here that all 3 performers possess: .addPerformerCaps(Caps.KV_SUPPORT_1) + .addPerformerCaps(Caps.TXN_CLIENT_CONTEXT_ID_SUPPORT) .addSdkImplementationCaps(com.couchbase.client.protocol.sdk.Caps.WAIT_UNTIL_READY) .addSdkImplementationCaps(com.couchbase.client.protocol.sdk.Caps.PROTOSTELLAR) .addSdkImplementationCaps(com.couchbase.client.protocol.sdk.Caps.SDK_SEARCH_RFC_REVISION_11) diff --git a/java-fit-performer/src/main/java/com/couchbase/transactions/SingleQueryTransactionExecutor.java b/java-fit-performer/src/main/java/com/couchbase/transactions/SingleQueryTransactionExecutor.java index 55a23c798..fea16cc39 100644 --- a/java-fit-performer/src/main/java/com/couchbase/transactions/SingleQueryTransactionExecutor.java +++ b/java-fit-performer/src/main/java/com/couchbase/transactions/SingleQueryTransactionExecutor.java @@ -230,6 +230,10 @@ private static QueryOptions setSingleQueryTransactionOptions(TransactionSingleQu queryOptions.parentSpan(spans.get(grpcQueryOptions.getParentSpanId())); } + if (grpcQueryOptions.hasClientContextId()) { + queryOptions.clientContextId(grpcQueryOptions.getClientContextId()); + } + if (grpcQueryOptions.hasSingleQueryTransactionOptions()) { com.couchbase.client.protocol.sdk.query.SingleQueryTransactionOptions grpcSingleQueryOptions = grpcQueryOptions.getSingleQueryTransactionOptions(); SingleQueryTransactionOptions singleQueryOptions = SingleQueryTransactionOptions.singleQueryTransactionOptions(); diff --git a/java-fit-performer/src/main/java/com/couchbase/utils/OptionsUtil.java b/java-fit-performer/src/main/java/com/couchbase/utils/OptionsUtil.java index 48ed9ef05..7d5c06c69 100644 --- a/java-fit-performer/src/main/java/com/couchbase/utils/OptionsUtil.java +++ b/java-fit-performer/src/main/java/com/couchbase/utils/OptionsUtil.java @@ -602,6 +602,10 @@ public static com.couchbase.client.java.transactions.TransactionQueryOptions tra if (qo.hasScanWaitMillis()) { queryOptions.scanWait(Duration.ofMillis(qo.getScanWaitMillis())); } + + if (qo.hasClientContextId()) { + queryOptions.clientContextId(qo.getClientContextId()); + } } return queryOptions; } diff --git a/scala-fit-performer/src/main/scala/com/couchbase/client/performer/scala/util/OptionsUtil.scala b/scala-fit-performer/src/main/scala/com/couchbase/client/performer/scala/util/OptionsUtil.scala index fa4e31f49..c3d120f50 100644 --- a/scala-fit-performer/src/main/scala/com/couchbase/client/performer/scala/util/OptionsUtil.scala +++ b/scala-fit-performer/src/main/scala/com/couchbase/client/performer/scala/util/OptionsUtil.scala @@ -390,6 +390,7 @@ object OptionsUtil { if (qo.hasPipelineCap) queryOptions = queryOptions.pipelineCap(qo.getPipelineCap) if (qo.hasPipelineBatch) queryOptions = queryOptions.pipelineBatch(qo.getPipelineBatch) if (qo.hasScanCap) queryOptions = queryOptions.scanCap(qo.getScanCap) + if (qo.hasClientContextId) queryOptions = queryOptions.clientContextId(qo.getClientContextId) } Option(queryOptions) } From a99941a1ee174e292fd3da854e743b8d2aee50ea Mon Sep 17 00:00:00 2001 From: CosmicSaaurabh <2bsaurabh@gmail.com> Date: Fri, 11 Oct 2024 15:31:00 +0530 Subject: [PATCH 55/73] JCO-22 :- Return supported connection error in fetchPerformerCap as per gRPC With the change in gRPC, performer needs to be updated to send caps according to new gRPC. 1. There is separate message for ClusterNewInstance and ClusterClose 2. There are SdkConnectionError caps according to sdk throws different exception in invalidCredentialError and badhostname case. Patch for gRPC change: https://review.couchbase.org/c/transactions-fit-performer/+/215526 Change-Id: Id4cbff8c2c189d38ee6b4dfa542ab754c1cb9ac0 Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/217764 Tested-by: Build Bot Reviewed-by: --- .../couchbase/columnar/rpc/JavaColumnarService.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/rpc/JavaColumnarService.java b/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/rpc/JavaColumnarService.java index e934e29e3..55a209b2d 100644 --- a/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/rpc/JavaColumnarService.java +++ b/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/rpc/JavaColumnarService.java @@ -28,6 +28,7 @@ import fit.columnar.ClusterNewInstanceRequest; import fit.columnar.ColumnarServiceGrpc; import fit.columnar.EmptyResultOrFailureResponse; +import fit.columnar.SdkConnectionError; import io.grpc.stub.StreamObserver; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -53,8 +54,8 @@ public void fetchPerformerCaps(fit.columnar.FetchPerformerCapsRequest request, S builder.setSdkVersion(sdkVersion); } builder.setSdk(fit.columnar.SDK.SDK_JAVA); - builder.putClusterNewInstance(0, fit.columnar.PerApiElementGeneric.getDefaultInstance()); - builder.putClusterClose(0, fit.columnar.PerApiElementGeneric.getDefaultInstance()); + builder.putClusterNewInstance(0, fit.columnar.PerApiElementClusterNewInstance.getDefaultInstance()); + builder.putClusterClose(0, fit.columnar.PerApiElementClusterClose.getDefaultInstance()); // The SDK has two main modes: buffered and push-based streaming. var executeQueryBuffered = fit.columnar.PerApiElementExecuteQuery.newBuilder() .setExecuteQueryReturns(fit.columnar.PerApiElementExecuteQuery.ExecuteQueryReturns.EXECUTE_QUERY_RETURNS_QUERY_RESULT) @@ -70,6 +71,13 @@ public void fetchPerformerCaps(fit.columnar.FetchPerformerCapsRequest request, S builder.putClusterExecuteQuery(Mode.BUFFERED.ordinal(), executeQueryBuffered); builder.putScopeExecuteQuery(Mode.PUSH_BASED_STREAMING.ordinal(), executeQueryPushBased); builder.putScopeExecuteQuery(Mode.BUFFERED.ordinal(), executeQueryBuffered); + for (Mode mode : new Mode[]{Mode.PUSH_BASED_STREAMING, Mode.BUFFERED}) { + builder.putSdkConnectionError(mode.ordinal(), SdkConnectionError.newBuilder() + .setInvalidCredErrorType(SdkConnectionError.InvalidCredentialErrorType.AS_INVALID_CREDENTIAL_EXCEPTION) + .setBootstrapErrorType(SdkConnectionError.BootstrapErrorType.ERROR_AS_TIMEOUT_EXCEPTION) + .build() + ); + } responseObserver.onNext(builder.build()); responseObserver.onCompleted(); } catch (RuntimeException err) { From 1f8dc7f45e29770db3b0eeceb9c38d241b280e9d Mon Sep 17 00:00:00 2001 From: Aaliya7516 Date: Wed, 6 Nov 2024 19:53:53 +0530 Subject: [PATCH 56/73] SDKQE-3420: Java columnar performer change to support Custom Deserializer. Change-Id: I4690cc62bd5ef25562a024811981d4228bd051db Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/219199 Tested-by: Aaliya Haque Reviewed-by: Will Broadbelt --- .../columnar/content/ContentAsUtil.java | 7 +++- .../columnar/query/QueryOptionsUtil.java | 5 +++ .../query/QueryResultPushBasedStreamer.java | 7 +++- .../columnar/query/QueryRowUtil.java | 3 +- .../columnar/rpc/JavaColumnarService.java | 4 +- .../columnar/util/CustomDeserializer.java | 41 +++++++++++++++++++ 6 files changed, 62 insertions(+), 5 deletions(-) create mode 100644 columnar-java-fit-performer/src/main/java/com/couchbase/columnar/util/CustomDeserializer.java diff --git a/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/content/ContentAsUtil.java b/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/content/ContentAsUtil.java index a36220268..57505d970 100644 --- a/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/content/ContentAsUtil.java +++ b/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/content/ContentAsUtil.java @@ -29,7 +29,8 @@ public static Try contentType( fit.columnar.ContentAs contentAs, Supplier asByteArray, Supplier asList, - Supplier asMap + Supplier asMap, + Supplier asString ) { try { if (contentAs.hasAsByteArray()) { @@ -44,6 +45,10 @@ public static Try contentType( return new Try<>(fit.columnar.ContentWas.newBuilder() .setContentWasMap(ProtobufConversions.jsonObjectToStruct(asMap.get())) .build()); + } else if (contentAs.hasAsString()) { + return new Try<>(fit.columnar.ContentWas.newBuilder() + .setContentWasString(asString.get()) + .build()); } else { throw new UnsupportedOperationException("Java performer cannot handle contentAs " + contentAs); } diff --git a/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/query/QueryOptionsUtil.java b/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/query/QueryOptionsUtil.java index dd9aa4a42..21511c46c 100644 --- a/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/query/QueryOptionsUtil.java +++ b/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/query/QueryOptionsUtil.java @@ -19,6 +19,7 @@ import com.couchbase.columnar.client.java.QueryOptions; import com.couchbase.columnar.client.java.QueryPriority; import com.couchbase.columnar.client.java.ScanConsistency; +import com.couchbase.columnar.util.CustomDeserializer; import com.couchbase.columnar.util.grpc.ProtobufConversions; import reactor.util.annotation.Nullable; @@ -60,6 +61,10 @@ public class QueryOptionsUtil { if (opts.hasTimeout()) { options.timeout(Duration.ofSeconds(opts.getTimeout().getSeconds())); } + if (opts.hasDeserializer() && opts.getDeserializer().hasCustom()) { + CustomDeserializer customDeserializer = new CustomDeserializer(); + options.deserializer(customDeserializer); + } }; } } diff --git a/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/query/QueryResultPushBasedStreamer.java b/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/query/QueryResultPushBasedStreamer.java index af0dbbcc1..d868808df 100644 --- a/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/query/QueryResultPushBasedStreamer.java +++ b/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/query/QueryResultPushBasedStreamer.java @@ -152,10 +152,13 @@ private void handleRow(Row row) { private RowProcessingResult processRow(Row row) { if (executeQueryRequest.hasContentAs()) { - var content = ContentAsUtil.contentType(executeQueryRequest.getContentAs(), + var content = ContentAsUtil.contentType( + executeQueryRequest.getContentAs(), () -> row.bytes(), () -> row.asNullable(JsonArray.class), - () -> row.asNullable(JsonObject.class)); + () -> row.asNullable(JsonObject.class), + () -> row.asNullable(String.class) + ); if (content.isSuccess()) { return new RowProcessingResult(null, fit.columnar.QueryRowResponse.newBuilder() diff --git a/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/query/QueryRowUtil.java b/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/query/QueryRowUtil.java index f819e5b0f..8932016d3 100644 --- a/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/query/QueryRowUtil.java +++ b/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/query/QueryRowUtil.java @@ -36,7 +36,8 @@ public static RowProcessingResult processRow(fit.columnar.ExecuteQueryRequest ex executeQueryRequest.getContentAs(), () -> row.bytes(), () -> row.asNullable(JsonArray.class), - () -> row.asNullable(JsonObject.class) + () -> row.asNullable(JsonObject.class), + () -> row.asNullable(String.class) ); if (content.isSuccess()) { diff --git a/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/rpc/JavaColumnarService.java b/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/rpc/JavaColumnarService.java index 55a209b2d..1b5c316dc 100644 --- a/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/rpc/JavaColumnarService.java +++ b/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/rpc/JavaColumnarService.java @@ -19,9 +19,9 @@ import com.couchbase.client.protocol.shared.EchoRequest; import com.couchbase.client.protocol.shared.EchoResponse; import com.couchbase.columnar.cluster.ColumnarClusterConnection; -import com.couchbase.columnar.modes.Mode; import com.couchbase.columnar.fit.core.exceptions.ExceptionGrpcMappingUtil; import com.couchbase.columnar.fit.core.util.VersionUtil; +import com.couchbase.columnar.modes.Mode; import com.couchbase.columnar.util.ResultUtil; import fit.columnar.CloseAllColumnarClustersRequest; import fit.columnar.ClusterCloseRequest; @@ -61,11 +61,13 @@ public void fetchPerformerCaps(fit.columnar.FetchPerformerCapsRequest request, S .setExecuteQueryReturns(fit.columnar.PerApiElementExecuteQuery.ExecuteQueryReturns.EXECUTE_QUERY_RETURNS_QUERY_RESULT) .setRowIteration(fit.columnar.PerApiElementExecuteQuery.RowIteration.ROW_ITERATION_BUFFERED) .setRowDeserialization(fit.columnar.PerApiElementExecuteQuery.RowDeserialization.ROW_DESERIALIZATION_STATIC_ROW_TYPING_INDIVIDUAL) + .setSupportsCustomDeserializer(true) .build(); var executeQueryPushBased = fit.columnar.PerApiElementExecuteQuery.newBuilder() .setExecuteQueryReturns(fit.columnar.PerApiElementExecuteQuery.ExecuteQueryReturns.EXECUTE_QUERY_RETURNS_QUERY_METADATA) .setRowIteration(fit.columnar.PerApiElementExecuteQuery.RowIteration.ROW_ITERATION_STREAMING_PUSH_BASED) .setRowDeserialization(fit.columnar.PerApiElementExecuteQuery.RowDeserialization.ROW_DESERIALIZATION_STATIC_ROW_TYPING_INDIVIDUAL) + .setSupportsCustomDeserializer(true) .build(); builder.putClusterExecuteQuery(Mode.PUSH_BASED_STREAMING.ordinal(), executeQueryPushBased); builder.putClusterExecuteQuery(Mode.BUFFERED.ordinal(), executeQueryBuffered); diff --git a/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/util/CustomDeserializer.java b/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/util/CustomDeserializer.java new file mode 100644 index 000000000..2454fd1fb --- /dev/null +++ b/columnar-java-fit-performer/src/main/java/com/couchbase/columnar/util/CustomDeserializer.java @@ -0,0 +1,41 @@ +package com.couchbase.columnar.util; + +import com.couchbase.columnar.client.java.codec.Deserializer; +import com.couchbase.columnar.client.java.codec.TypeRef; +import com.couchbase.columnar.client.java.json.JsonObject; + +/** + * CustomJsonDeserializer provides a generic implementation of the Deserializer interface. + *

+ * This deserializer is designed to handle the conversion of Java objects to String format + * and back, with an additional boolean flag ("Serialized": false) that indicates whether + * the object has been deserialized. The flag is included in the JSON payload and then + * converted to string, making it easy to track the deserialization state of objects. + *

+ * Use Cases: + * - This deserializer can be used in scenarios where you need to deserialize + * objects while keeping track of their deserialization state. + *

+ * Limitations: + * - The current implementation assumes that the input objects can be deserialized into + * string format. Complex or non-standard objects may require additional handling. + * - The `deserialize` methods in this implementation modify the original JSON object + * by setting the `Serialized` flag to `false`, which might not be suitable for + * all use cases. + */ + +public class CustomDeserializer implements Deserializer { + @Override + public T deserialize(Class target, byte[] input) { + JsonObject obj = JsonObject.fromJson(input); + obj.put("Serialized", false); + return (T) obj.toString(); + } + + @Override + public T deserialize(TypeRef target, byte[] input) { + JsonObject obj = JsonObject.fromJson(input); + obj.put("Serialized", false); + return (T) obj.toString(); + } +} From de71895d9ba7c3c0114618a22370af65524ea61e Mon Sep 17 00:00:00 2001 From: David Nault Date: Thu, 14 Nov 2024 10:59:05 -0800 Subject: [PATCH 57/73] Gardening: Remove unnecessary synchronization in LoggingMeter Motivation ---------- LoggingMeter.valueRecorder() does not need to be synchronized, because valueRecorders is a ConcurrentMap, and ConcurrentMap.computeIfAbsent is an atomic operation. LoggingMeter.Worker.dumpMetrics() does not need to be synchronized, because it can only be called by the metrics worker thread. Note: At first glance, it might look like the synchronization prevented dumpMetrics() from running at the same time as valueRecorder(), but that's not the case! These are methods of two different classes, so the sync was independent. (Even if that was the original intent, it's unnecessary.) Modifications ------------- Remove synchronized keyword from LoggingMeter.valueRecorder() and LoggingMeter.Worker.dumpMetrics(). Change the type of `valueRecorders` from Map to ConcurrentMap. This does not affect the code's behavior, but it gives readers another clue that valueRecorders.computeIfAbsent() is atomic. Change-Id: I1e3e67247ca7846bde5b753099abac78b2d16491 Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/219596 Tested-by: Build Bot Reviewed-by: Michael Reiche --- .../couchbase/client/core/cnc/metrics/LoggingMeter.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/core-io/src/main/java/com/couchbase/client/core/cnc/metrics/LoggingMeter.java b/core-io/src/main/java/com/couchbase/client/core/cnc/metrics/LoggingMeter.java index a0dae3796..0faeae5c1 100644 --- a/core-io/src/main/java/com/couchbase/client/core/cnc/metrics/LoggingMeter.java +++ b/core-io/src/main/java/com/couchbase/client/core/cnc/metrics/LoggingMeter.java @@ -32,6 +32,7 @@ import java.util.LinkedHashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -47,7 +48,7 @@ public class LoggingMeter implements Meter { private final Thread worker; private final AtomicBoolean running = new AtomicBoolean(false); - private final Map valueRecorders = new ConcurrentHashMap<>(); + private final ConcurrentMap valueRecorders = new ConcurrentHashMap<>(); private final long emitIntervalMs; private final LoggingMeterConfig config; @@ -86,7 +87,7 @@ public Counter counter(String name, Map tags) { } @Override - public synchronized ValueRecorder valueRecorder(String name, Map tags) { + public ValueRecorder valueRecorder(String name, Map tags) { try { return valueRecorders.computeIfAbsent( new NameAndTags(name, tags), @@ -141,7 +142,7 @@ public void run() { } @SuppressWarnings("unchecked") - private synchronized void dumpMetrics() { + private void dumpMetrics() { Map output = new HashMap<>(); Map meta = new HashMap<>(); From ca98ab3d528f97dd04b3a68c407f881373180ac9 Mon Sep 17 00:00:00 2001 From: David Nault Date: Thu, 14 Nov 2024 12:40:35 -0800 Subject: [PATCH 58/73] Gardening: Reduce allocations in NoopMeter Motivation ---------- Improve NoopMeter performance. Motivation ---------- Add `NoopCounter.INSTANCE` & `NoopValueRecorder.INSTANCE` and use them instead of allocating new instances. Change-Id: Iaa8e9aff2680b882cd57fa1c81ba3f220e027923 Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/219603 Tested-by: Build Bot Reviewed-by: David Nault --- .../com/couchbase/client/core/cnc/metrics/NoopCounter.java | 2 ++ .../java/com/couchbase/client/core/cnc/metrics/NoopMeter.java | 4 ++-- .../couchbase/client/core/cnc/metrics/NoopValueRecorder.java | 2 ++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/core-io/src/main/java/com/couchbase/client/core/cnc/metrics/NoopCounter.java b/core-io/src/main/java/com/couchbase/client/core/cnc/metrics/NoopCounter.java index 53d124400..171ed0904 100644 --- a/core-io/src/main/java/com/couchbase/client/core/cnc/metrics/NoopCounter.java +++ b/core-io/src/main/java/com/couchbase/client/core/cnc/metrics/NoopCounter.java @@ -20,6 +20,8 @@ public class NoopCounter implements Counter { + public static final NoopCounter INSTANCE = new NoopCounter(); + @Override public void incrementBy(long number) { diff --git a/core-io/src/main/java/com/couchbase/client/core/cnc/metrics/NoopMeter.java b/core-io/src/main/java/com/couchbase/client/core/cnc/metrics/NoopMeter.java index e9b3de268..ce3b35271 100644 --- a/core-io/src/main/java/com/couchbase/client/core/cnc/metrics/NoopMeter.java +++ b/core-io/src/main/java/com/couchbase/client/core/cnc/metrics/NoopMeter.java @@ -30,12 +30,12 @@ private NoopMeter() {} @Override public Counter counter(String name, Map tags) { - return new NoopCounter(); + return NoopCounter.INSTANCE; } @Override public ValueRecorder valueRecorder(String name, Map tags) { - return new NoopValueRecorder(); + return NoopValueRecorder.INSTANCE; } @Override diff --git a/core-io/src/main/java/com/couchbase/client/core/cnc/metrics/NoopValueRecorder.java b/core-io/src/main/java/com/couchbase/client/core/cnc/metrics/NoopValueRecorder.java index 4cc068a03..5bc30f433 100644 --- a/core-io/src/main/java/com/couchbase/client/core/cnc/metrics/NoopValueRecorder.java +++ b/core-io/src/main/java/com/couchbase/client/core/cnc/metrics/NoopValueRecorder.java @@ -20,6 +20,8 @@ public class NoopValueRecorder implements ValueRecorder { + public static final NoopValueRecorder INSTANCE = new NoopValueRecorder(); + @Override public void recordValue(long value) { From e99a2394efe19baeddadb669f21fe911a98b0011 Mon Sep 17 00:00:00 2001 From: Emilien Bevierre Date: Thu, 14 Nov 2024 10:08:10 +0000 Subject: [PATCH 59/73] JCBC-2175: Separate MpscArrayQueue creation in certain classes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Motivation ---------- For native image compatibility, instances of MpscArrayQueue must be substituted for MpscAtomicUnpaddedArrayQueue from the jctools library. The affected classes are DefaultEventBus, OrphanReporter and ThresholdLoggingTracer. While it’s possible to substitute the constructor of a class, it somehow caused unwanted repercussions for the OrphanReporter and ThresholdLoggingTracer classes. To simplify the substitution, a separate static factory method should be added to produce a Queue object, which can be easily substituted in the quarkus extension. Changes ------- - Separated Queue assignment in static factory method in NativeImageHelper - Added UsedBy(QURAKUS_COUCHBASE) annotation - Added checkstyle for using org.jctools library Change-Id: I397ded51911c6f4c346a5e2e2d8105a3b1d4ed4d Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/219572 Reviewed-by: Graham Pople Tested-by: Build Bot --- config/checkstyle/checkstyle-basic.xml | 4 ++ .../client/core/annotation/UsedBy.java | 3 +- .../client/core/cnc/DefaultEventBus.java | 4 +- .../client/core/cnc/OrphanReporter.java | 4 +- .../cnc/tracing/ThresholdLoggingTracer.java | 4 +- .../client/core/util/NativeImageHelper.java | 48 +++++++++++++++++++ .../client/java/env/ClusterEnvironment.java | 3 ++ 7 files changed, 63 insertions(+), 7 deletions(-) create mode 100644 core-io/src/main/java/com/couchbase/client/core/util/NativeImageHelper.java diff --git a/config/checkstyle/checkstyle-basic.xml b/config/checkstyle/checkstyle-basic.xml index 952ef6a7e..18b9ebcf2 100644 --- a/config/checkstyle/checkstyle-basic.xml +++ b/config/checkstyle/checkstyle-basic.xml @@ -16,6 +16,10 @@ + + + - 4.1.112.Final + 4.1.115.Final 2.17.2 com.couchbase.client.core.deps. From 1f18f02a5e5359c5d3eef7849f9bba7179cfbb24 Mon Sep 17 00:00:00 2001 From: David Nault Date: Fri, 22 Nov 2024 10:40:29 -0800 Subject: [PATCH 66/73] JVMCBC-1560: When mocking Core, use NOOP CoreResources. Change-Id: I84450810896187d86f7246353e6d698338779f2c Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/219975 Reviewed-by: Michael Reiche Tested-by: Build Bot --- .../couchbase/client/core/util/MockUtil.java | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/core-io/src/test/java/com/couchbase/client/core/util/MockUtil.java b/core-io/src/test/java/com/couchbase/client/core/util/MockUtil.java index b78afd54b..ee086636d 100644 --- a/core-io/src/test/java/com/couchbase/client/core/util/MockUtil.java +++ b/core-io/src/test/java/com/couchbase/client/core/util/MockUtil.java @@ -16,7 +16,7 @@ package com.couchbase.client.core.util; import com.couchbase.client.core.Core; -// import com.couchbase.client.core.CoreResources; +import com.couchbase.client.core.CoreResources; import com.couchbase.client.core.cnc.RequestTracer; import com.couchbase.client.core.cnc.tracing.NoopRequestTracer; @@ -28,14 +28,13 @@ private MockUtil() {} public static Core mockCore() { Core core = mock(Core.class); - // Will be enabled in a future commit -// CoreResources coreResources = new CoreResources() { -// @Override -// public RequestTracer requestTracer() { -// return NoopRequestTracer.INSTANCE; -// } -// }; -// when(core.coreResources()).thenReturn(coreResources); + CoreResources coreResources = new CoreResources() { + @Override + public RequestTracer requestTracer() { + return NoopRequestTracer.INSTANCE; + } + }; + when(core.coreResources()).thenReturn(coreResources); return core; } } From 81ffed82b074c56148d1a20fe9ff93b24f610034 Mon Sep 17 00:00:00 2001 From: Graham Pople Date: Mon, 30 Sep 2024 15:28:39 +0100 Subject: [PATCH 67/73] JVMCBC-1560: Add cluster UUID and name to metrics and spans (3/n) Adding cluster uuid and name config parsing. This will only be sent from fully upgraded 7.7+ clusters. Change-Id: Ied58e6f4cd99d596d07017d0cf1d8b2d81fd554b Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/217015 Tested-by: Build Bot Reviewed-by: Graham Pople --- .../client/core/config/GlobalConfig.java | 8 +++ .../core/topology/ClusterIdentifier.java | 59 +++++++++++++++++++ .../client/core/topology/ClusterTopology.java | 17 +++++- .../core/topology/ClusterTopologyParser.java | 3 + .../topology/ClusterTopologyWithBucket.java | 6 +- .../core/topology/TopologyTestUtils.java | 1 + 6 files changed, 89 insertions(+), 5 deletions(-) create mode 100644 core-io/src/main/java/com/couchbase/client/core/topology/ClusterIdentifier.java diff --git a/core-io/src/main/java/com/couchbase/client/core/config/GlobalConfig.java b/core-io/src/main/java/com/couchbase/client/core/config/GlobalConfig.java index 1633e053f..a55a79ddc 100644 --- a/core-io/src/main/java/com/couchbase/client/core/config/GlobalConfig.java +++ b/core-io/src/main/java/com/couchbase/client/core/config/GlobalConfig.java @@ -22,6 +22,7 @@ import com.couchbase.client.core.deps.com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.couchbase.client.core.deps.com.fasterxml.jackson.annotation.JsonProperty; import com.couchbase.client.core.service.ServiceType; +import com.couchbase.client.core.topology.ClusterIdentifier; import com.couchbase.client.core.topology.ClusterTopology; import reactor.util.annotation.Nullable; @@ -47,6 +48,7 @@ public class GlobalConfig { private final ConfigVersion version; private final List portInfos; private final Map> clusterCapabilities; + private final @Nullable ClusterIdentifier clusterIdent; // Null only if the GlobalConfig was created by a legacy config parser @Nullable private final ClusterTopology clusterTopology; @@ -56,6 +58,7 @@ public GlobalConfig(ClusterTopology topology) { this.portInfos = LegacyConfigHelper.getPortInfos(topology); this.clusterCapabilities = LegacyConfigHelper.getClusterCapabilities(topology); this.clusterTopology = topology; + this.clusterIdent = topology.id(); } @JsonCreator @@ -70,6 +73,7 @@ public GlobalConfig( this.portInfos = enrichPortInfos(portInfos, origin); this.clusterCapabilities = AbstractBucketConfig.convertClusterCapabilities(clusterCapabilities); this.clusterTopology = null; + this.clusterIdent = null; } /** @@ -126,6 +130,10 @@ public Map> clusterCapabilities() { return clusterCapabilities; } + @Nullable public ClusterIdentifier clusterIdent() { + return clusterIdent; + } + /** * The node/port infos for each node in the list. */ diff --git a/core-io/src/main/java/com/couchbase/client/core/topology/ClusterIdentifier.java b/core-io/src/main/java/com/couchbase/client/core/topology/ClusterIdentifier.java new file mode 100644 index 000000000..3f8c72eeb --- /dev/null +++ b/core-io/src/main/java/com/couchbase/client/core/topology/ClusterIdentifier.java @@ -0,0 +1,59 @@ +/* + * Copyright 2024 Couchbase, Inc. + * + * Licensed 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 + * + * https://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 com.couchbase.client.core.topology; + +import com.couchbase.client.core.annotation.Stability; +import com.couchbase.client.core.deps.com.fasterxml.jackson.databind.JsonNode; +import com.couchbase.client.core.deps.com.fasterxml.jackson.databind.node.ObjectNode; +import reactor.util.annotation.Nullable; + +import static com.couchbase.client.core.logging.RedactableArgument.redactMeta; + +@Stability.Internal +public class ClusterIdentifier { + private final String clusterUuid; + private final String clusterName; + + ClusterIdentifier(String clusterUuid, String clusterName) { + this.clusterUuid = clusterUuid; + this.clusterName = clusterName; + } + + public static @Nullable ClusterIdentifier parse(ObjectNode config) { + JsonNode clusterUuid = config.path("clusterUUID"); + JsonNode clusterName = config.path("clusterName"); + if (clusterUuid.isMissingNode() || clusterName.isMissingNode()) { + return null; + } + return new ClusterIdentifier(clusterUuid.asText(), clusterName.asText()); + } + + public String clusterUuid() { + return clusterUuid; + } + + public String clusterName() { + return clusterName; + } + + @Override + public String toString() { + return "ClusterIdent{" + + "clusterUuid='" + clusterUuid + '\'' + + ", clusterName='" + redactMeta(clusterName) + '\'' + + '}'; + } +} diff --git a/core-io/src/main/java/com/couchbase/client/core/topology/ClusterTopology.java b/core-io/src/main/java/com/couchbase/client/core/topology/ClusterTopology.java index eda14a390..3858c9024 100644 --- a/core-io/src/main/java/com/couchbase/client/core/topology/ClusterTopology.java +++ b/core-io/src/main/java/com/couchbase/client/core/topology/ClusterTopology.java @@ -36,9 +36,11 @@ public class ClusterTopology { private final NetworkResolution network; private final Set capabilities; private final List nodes; + @Nullable private final ClusterIdentifier clusterIdent; public static ClusterTopology of( TopologyRevision revision, + @Nullable ClusterIdentifier clusterIdent, List nodes, Set capabilities, NetworkResolution network, @@ -52,7 +54,8 @@ public static ClusterTopology of( capabilities, network, portSelector, - bucket + bucket, + clusterIdent ); } @@ -61,7 +64,8 @@ public static ClusterTopology of( nodes, capabilities, network, - portSelector + portSelector, + clusterIdent ); } @@ -70,7 +74,8 @@ protected ClusterTopology( List nodes, Set capabilities, NetworkResolution network, - PortSelector portSelector + PortSelector portSelector, + @Nullable ClusterIdentifier clusterIdent ) { if (network.equals(NetworkResolution.AUTO)) { throw new IllegalArgumentException("Must resolve 'auto' network before creating config."); @@ -81,6 +86,7 @@ protected ClusterTopology( this.capabilities = unmodifiableSet(newEnumSet(ClusterCapability.class, capabilities)); this.network = requireNonNull(network); this.tls = requireNonNull(portSelector) == PortSelector.TLS; + this.clusterIdent = clusterIdent; } public TopologyRevision revision() { @@ -114,12 +120,17 @@ public ClusterTopologyWithBucket requireBucket() { throw new NoSuchElementException("Bucket topology is absent."); } + @Nullable public ClusterIdentifier id() { + return clusterIdent; + } + @Override public String toString() { String bucket = this instanceof ClusterTopologyWithBucket ? this.requireBucket().bucket().toString() : ""; return "ClusterTopology{" + "revision=" + revision + + ", clusterIdent=" + clusterIdent + ", tls=" + tls + ", network=" + network + ", capabilities=" + capabilities + diff --git a/core-io/src/main/java/com/couchbase/client/core/topology/ClusterTopologyParser.java b/core-io/src/main/java/com/couchbase/client/core/topology/ClusterTopologyParser.java index 78d20c455..a37194abe 100644 --- a/core-io/src/main/java/com/couchbase/client/core/topology/ClusterTopologyParser.java +++ b/core-io/src/main/java/com/couchbase/client/core/topology/ClusterTopologyParser.java @@ -123,8 +123,11 @@ resolvedNetwork, redactSystem(it) BucketTopology bucket = BucketTopology.parse(clusterConfig, nodesReadyToServiceThisBucket, memcachedHashingStrategy); + ClusterIdentifier clusterIdent = ClusterIdentifier.parse(clusterConfig); + return ClusterTopology.of( TopologyRevision.parse(clusterConfig), + clusterIdent, resolvedNodes, parseCapabilities(clusterConfig), resolvedNetwork, diff --git a/core-io/src/main/java/com/couchbase/client/core/topology/ClusterTopologyWithBucket.java b/core-io/src/main/java/com/couchbase/client/core/topology/ClusterTopologyWithBucket.java index 53afd70cc..2c56aefc9 100644 --- a/core-io/src/main/java/com/couchbase/client/core/topology/ClusterTopologyWithBucket.java +++ b/core-io/src/main/java/com/couchbase/client/core/topology/ClusterTopologyWithBucket.java @@ -18,6 +18,7 @@ import com.couchbase.client.core.annotation.Stability; import com.couchbase.client.core.env.NetworkResolution; +import reactor.util.annotation.Nullable; import java.util.List; import java.util.Set; @@ -40,9 +41,10 @@ public class ClusterTopologyWithBucket extends ClusterTopology { Set capabilities, NetworkResolution network, PortSelector portSelector, - BucketTopology bucket + BucketTopology bucket, + @Nullable ClusterIdentifier clusterIdent ) { - super(revision, nodes, capabilities, network, portSelector); + super(revision, nodes, capabilities, network, portSelector, clusterIdent); this.bucket = requireNonNull(bucket); } diff --git a/core-io/src/test/java/com/couchbase/client/core/topology/TopologyTestUtils.java b/core-io/src/test/java/com/couchbase/client/core/topology/TopologyTestUtils.java index 46887e8de..711265dc6 100644 --- a/core-io/src/test/java/com/couchbase/client/core/topology/TopologyTestUtils.java +++ b/core-io/src/test/java/com/couchbase/client/core/topology/TopologyTestUtils.java @@ -56,6 +56,7 @@ public static NodeInfo nodeInfo(String host, Map ports) { public static ClusterTopology clusterTopology(List nodes) { return ClusterTopology.of( new TopologyRevision(1, 1), + null, nodes, emptySet(), NetworkResolution.DEFAULT, From 7b849a0c95b2dc2b7fe5aed56f2c5555ddc9e256 Mon Sep 17 00:00:00 2001 From: Graham Pople Date: Mon, 30 Sep 2024 15:39:36 +0100 Subject: [PATCH 68/73] JVMCBC-1560: Add cluster UUID and name to metrics and spans (4/n) "Decorate" the CoreEnvironment's RequestTracer. Change-Id: I520f72a91b8e92dda05308f71109e4eb19c89d83 Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/217016 Tested-by: Build Bot Reviewed-by: David Nault --- .../java/com/couchbase/client/core/Core.java | 22 +++++++++++++++++-- .../client/core/env/CoreEnvironment.java | 3 +-- ...butes.java => RequestTracerDecorator.java} | 13 ++++++++--- 3 files changed, 31 insertions(+), 7 deletions(-) rename core-io/src/main/java/com/couchbase/client/core/env/{RequestTracerWithCommonAttributes.java => RequestTracerDecorator.java} (73%) diff --git a/core-io/src/main/java/com/couchbase/client/core/Core.java b/core-io/src/main/java/com/couchbase/client/core/Core.java index 96d1c5f93..2b29a1a9c 100644 --- a/core-io/src/main/java/com/couchbase/client/core/Core.java +++ b/core-io/src/main/java/com/couchbase/client/core/Core.java @@ -36,6 +36,7 @@ import com.couchbase.client.core.cnc.CbTracing; import com.couchbase.client.core.cnc.Event; import com.couchbase.client.core.cnc.EventBus; +import com.couchbase.client.core.cnc.RequestTracer; import com.couchbase.client.core.cnc.TracingIdentifiers; import com.couchbase.client.core.cnc.ValueRecorder; import com.couchbase.client.core.cnc.events.core.BucketClosedEvent; @@ -63,6 +64,7 @@ import com.couchbase.client.core.endpoint.http.CoreHttpClient; import com.couchbase.client.core.env.Authenticator; import com.couchbase.client.core.env.CoreEnvironment; +import com.couchbase.client.core.env.RequestTracerDecorator; import com.couchbase.client.core.env.SeedNode; import com.couchbase.client.core.error.AlreadyShutdownException; import com.couchbase.client.core.error.ConfigException; @@ -287,8 +289,24 @@ protected Core( CoreLimiter.incrementAndVerifyNumInstances(environment.eventBus()); this.connectionString = requireNonNull(connectionString); - // No-op for now; follow-up commit will provide the clusterIdent - this.coreResources = () -> environment.requestTracer(); + boolean ignoresAttributes = CbTracing.isInternalTracer(environment.requestTracer()); + RequestTracer requestTracerDecoratedIfRequired = ignoresAttributes + ? environment.requestTracer() + : new RequestTracerDecorator(environment.requestTracer(), () -> { + if (currentConfig == null) { + return null; + } + if (currentConfig.globalConfig() == null) { + return null; + } + return currentConfig.globalConfig().clusterIdent(); + }); + this.coreResources = new CoreResources() { + @Override + public RequestTracer requestTracer() { + return requestTracerDecoratedIfRequired; + } + }; this.coreContext = new CoreContext(this, CoreIdGenerator.nextId(), environment, authenticator); this.configurationProvider = createConfigurationProvider(); this.nodes = new CopyOnWriteArrayList<>(); diff --git a/core-io/src/main/java/com/couchbase/client/core/env/CoreEnvironment.java b/core-io/src/main/java/com/couchbase/client/core/env/CoreEnvironment.java index 17227869c..2ea5c4216 100644 --- a/core-io/src/main/java/com/couchbase/client/core/env/CoreEnvironment.java +++ b/core-io/src/main/java/com/couchbase/client/core/env/CoreEnvironment.java @@ -1127,8 +1127,7 @@ public SELF retryStrategy(final RetryStrategy retryStrategy) { public SELF requestTracer(final RequestTracer requestTracer) { notNull(requestTracer, "RequestTracer"); - boolean ignoresAttributes = CbTracing.isInternalTracer(requestTracer); - this.requestTracer = external(ignoresAttributes ? requestTracer : new RequestTracerWithCommonAttributes(requestTracer)); + this.requestTracer = external(requestTracer); return self(); } diff --git a/core-io/src/main/java/com/couchbase/client/core/env/RequestTracerWithCommonAttributes.java b/core-io/src/main/java/com/couchbase/client/core/env/RequestTracerDecorator.java similarity index 73% rename from core-io/src/main/java/com/couchbase/client/core/env/RequestTracerWithCommonAttributes.java rename to core-io/src/main/java/com/couchbase/client/core/env/RequestTracerDecorator.java index c652eefaf..45759470b 100644 --- a/core-io/src/main/java/com/couchbase/client/core/env/RequestTracerWithCommonAttributes.java +++ b/core-io/src/main/java/com/couchbase/client/core/env/RequestTracerDecorator.java @@ -16,23 +16,30 @@ package com.couchbase.client.core.env; +import com.couchbase.client.core.annotation.Stability; import com.couchbase.client.core.cnc.RequestSpan; import com.couchbase.client.core.cnc.RequestTracer; import com.couchbase.client.core.cnc.TracingIdentifiers; +import com.couchbase.client.core.topology.ClusterIdentifier; import reactor.core.publisher.Mono; +import reactor.util.annotation.Nullable; import java.time.Duration; +import java.util.function.Supplier; import static java.util.Objects.requireNonNull; /** - * Applies a set of common attributes to spans created by this tracer. + * Wraps a {@link RequestTracer} to provide new attributes. */ -class RequestTracerWithCommonAttributes implements RequestTracer { +@Stability.Internal +public class RequestTracerDecorator implements RequestTracer { private final RequestTracer wrapped; + private final Supplier clusterIdentSupplier; - RequestTracerWithCommonAttributes(RequestTracer wrapped) { + public RequestTracerDecorator(RequestTracer wrapped, Supplier clusterIdentSupplier) { this.wrapped = requireNonNull(wrapped); + this.clusterIdentSupplier = clusterIdentSupplier; } @Override From 448eda115ced5500f1dc063b8a5ce2f3c35cc400 Mon Sep 17 00:00:00 2001 From: Graham Pople Date: Mon, 30 Sep 2024 15:40:28 +0100 Subject: [PATCH 69/73] JVMCBC-1560: Add cluster UUID and name to metrics and spans (5/n) Adding cluster UUID and name attributes for non-transactional spans and metrics. Change-Id: I65b08286e83a7c7a54ff1b531d19ea527e05b786 Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/217017 Tested-by: Build Bot Reviewed-by: Graham Pople --- .../java/com/couchbase/client/core/Core.java | 41 +++++++++++++++---- .../client/core/cnc/TracingIdentifiers.java | 3 ++ .../core/env/RequestTracerDecorator.java | 5 +++ .../com/couchbase/utils/Capabilities.java | 4 ++ 4 files changed, 44 insertions(+), 9 deletions(-) diff --git a/core-io/src/main/java/com/couchbase/client/core/Core.java b/core-io/src/main/java/com/couchbase/client/core/Core.java index 2b29a1a9c..b1938ddd8 100644 --- a/core-io/src/main/java/com/couchbase/client/core/Core.java +++ b/core-io/src/main/java/com/couchbase/client/core/Core.java @@ -93,6 +93,7 @@ import com.couchbase.client.core.service.ServiceScope; import com.couchbase.client.core.service.ServiceState; import com.couchbase.client.core.service.ServiceType; +import com.couchbase.client.core.topology.ClusterIdentifier; import com.couchbase.client.core.topology.ClusterTopology; import com.couchbase.client.core.topology.ClusterTopologyWithBucket; import com.couchbase.client.core.topology.NodeIdentifier; @@ -240,7 +241,8 @@ public class Core implements CoreCouchbaseOps, AutoCloseable { private final Disposable invalidStateWatchdog; /** - * Holds the response metrics per + * Holds the response metrics. + * Note that because tags have to be provided on ValueRecorder creation, every unique combination of tags needs to be represented in the ResponseMetricIdentifier key. */ private final Map responseMetrics = new ConcurrentHashMap<>(); @@ -627,9 +629,10 @@ public ValueRecorder responseMetric(final Request request, @Nullable Throwabl } } final String finalExceptionSimpleName = exceptionSimpleName; + final ClusterIdentifier clusterIdent = currentConfig == null ? null : currentConfig.globalConfig() == null ? null : currentConfig.globalConfig().clusterIdent(); - return responseMetrics.computeIfAbsent(new ResponseMetricIdentifier(request, exceptionSimpleName), key -> { - Map tags = new HashMap<>(7); + return responseMetrics.computeIfAbsent(new ResponseMetricIdentifier(request, exceptionSimpleName, clusterIdent), key -> { + Map tags = new HashMap<>(9); if (key.serviceType == null) { // Virtual service if (request instanceof CoreTransactionRequest) { @@ -643,9 +646,21 @@ public ValueRecorder responseMetric(final Request request, @Nullable Throwabl // The LoggingMeter only uses the service and operation labels, so optimise this hot-path by skipping // assigning other labels. if (!isDefaultLoggingMeter) { - tags.put(TracingIdentifiers.ATTR_NAME, key.bucketName); - tags.put(TracingIdentifiers.ATTR_SCOPE, key.scopeName); - tags.put(TracingIdentifiers.ATTR_COLLECTION, key.collectionName); + // Crucial note for Micrometer: + // If we are ever going to output an attribute from a given JVM run then we must always + // output that attribute in this run. Specifying null as an attribute value allows the OTel backend to strip it, and + // the Micrometer backend to provide a default value. + // See (internal to Couchbase) discussion here for full details: + // https://issues.couchbase.com/browse/CBSE-17070?focusedId=779820&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-779820 + // If this rule is not followed, then Micrometer will silently discard some metrics. Micrometer requires that + // every value output under a given metric has the same set of attributes. + + tags.put(TracingIdentifiers.ATTR_NAME, key.bucketName); + tags.put(TracingIdentifiers.ATTR_SCOPE, key.scopeName); + tags.put(TracingIdentifiers.ATTR_COLLECTION, key.collectionName); + + tags.put(TracingIdentifiers.ATTR_CLUSTER_UUID, key.clusterUuid); + tags.put(TracingIdentifiers.ATTR_CLUSTER_NAME, key.clusterName); if (finalExceptionSimpleName != null) { tags.put(TracingIdentifiers.ATTR_OUTCOME, finalExceptionSimpleName); @@ -1016,8 +1031,10 @@ public static class ResponseMetricIdentifier { private final @Nullable String scopeName; private final @Nullable String collectionName; private final @Nullable String exceptionSimpleName; + private final @Nullable String clusterName; + private final @Nullable String clusterUuid; - ResponseMetricIdentifier(final Request request, @Nullable String exceptionSimpleName) { + ResponseMetricIdentifier(final Request request, @Nullable String exceptionSimpleName, @Nullable ClusterIdentifier clusterIdent) { this.exceptionSimpleName = exceptionSimpleName; if (request.serviceType() == null) { if (request instanceof CoreTransactionRequest) { @@ -1030,6 +1047,8 @@ public static class ResponseMetricIdentifier { this.serviceType = CbTracing.getTracingId(request.serviceType()); } this.requestName = request.name(); + this.clusterName = clusterIdent == null ? null : clusterIdent.clusterName(); + this.clusterUuid = clusterIdent == null ? null : clusterIdent.clusterUuid(); if (request instanceof KeyValueRequest) { KeyValueRequest kv = (KeyValueRequest) request; bucketName = request.bucket(); @@ -1064,6 +1083,8 @@ public ResponseMetricIdentifier(final String serviceType, final String requestNa this.scopeName = null; this.collectionName = null; this.exceptionSimpleName = null; + this.clusterName = null; + this.clusterUuid = null; } public String serviceType() { @@ -1084,12 +1105,14 @@ public boolean equals(Object o) { && Objects.equals(bucketName, that.bucketName) && Objects.equals(scopeName, that.scopeName) && Objects.equals(collectionName, that.collectionName) - && Objects.equals(exceptionSimpleName, that.exceptionSimpleName); + && Objects.equals(exceptionSimpleName, that.exceptionSimpleName) + && Objects.equals(clusterName, that.clusterName) + && Objects.equals(clusterUuid, that.clusterUuid); } @Override public int hashCode() { - return Objects.hash(serviceType, requestName, bucketName, scopeName, collectionName, exceptionSimpleName); + return Objects.hash(serviceType, requestName, bucketName, scopeName, collectionName, exceptionSimpleName, clusterName, clusterUuid); } } diff --git a/core-io/src/main/java/com/couchbase/client/core/cnc/TracingIdentifiers.java b/core-io/src/main/java/com/couchbase/client/core/cnc/TracingIdentifiers.java index 357fcc2ba..af42c3c78 100644 --- a/core-io/src/main/java/com/couchbase/client/core/cnc/TracingIdentifiers.java +++ b/core-io/src/main/java/com/couchbase/client/core/cnc/TracingIdentifiers.java @@ -264,6 +264,9 @@ private TracingIdentifiers() {} public static final String ATTR_SCOPE = "db.couchbase.scope"; public static final String ATTR_DOCUMENT_ID = "db.couchbase.document_id"; + public static final String ATTR_CLUSTER_UUID = "db.couchbase.cluster_uuid"; + public static final String ATTR_CLUSTER_NAME = "db.couchbase.cluster_name"; + public static final String ATTR_TRANSACTION_ID = "db.couchbase.transaction.id"; public static final String ATTR_TRANSACTION_ATTEMPT_ID = "db.couchbase.transaction.attempt_id"; public static final String ATTR_TRANSACTION_STATE = "db.couchbase.transaction.state"; diff --git a/core-io/src/main/java/com/couchbase/client/core/env/RequestTracerDecorator.java b/core-io/src/main/java/com/couchbase/client/core/env/RequestTracerDecorator.java index 45759470b..b4a11f5d6 100644 --- a/core-io/src/main/java/com/couchbase/client/core/env/RequestTracerDecorator.java +++ b/core-io/src/main/java/com/couchbase/client/core/env/RequestTracerDecorator.java @@ -46,6 +46,11 @@ public RequestTracerDecorator(RequestTracer wrapped, Supplier public RequestSpan requestSpan(String name, RequestSpan parent) { RequestSpan span = wrapped.requestSpan(name, parent); span.lowCardinalityAttribute(TracingIdentifiers.ATTR_SYSTEM, TracingIdentifiers.ATTR_SYSTEM_COUCHBASE); + ClusterIdentifier clusterIdent = clusterIdentSupplier.get(); + if (clusterIdent != null) { + span.attribute(TracingIdentifiers.ATTR_CLUSTER_NAME, clusterIdent.clusterName()); + span.attribute(TracingIdentifiers.ATTR_CLUSTER_UUID, clusterIdent.clusterUuid()); + } return span; } diff --git a/java-fit-performer/src/main/java/com/couchbase/utils/Capabilities.java b/java-fit-performer/src/main/java/com/couchbase/utils/Capabilities.java index fedc28801..3710a8d60 100644 --- a/java-fit-performer/src/main/java/com/couchbase/utils/Capabilities.java +++ b/java-fit-performer/src/main/java/com/couchbase/utils/Capabilities.java @@ -88,6 +88,10 @@ public static List sdkImplementationCaps() { out.add(Caps.SDK_ZONE_AWARE_READ_FROM_REPLICA); // [end] + // [if:3.7.6] + out.add(Caps.SDK_OBSERVABILITY_CLUSTER_LABELS); + // [end] + return out; } } From a8b506e1fd6290406fc6ac696b6eceb31db1a169 Mon Sep 17 00:00:00 2001 From: Graham Pople Date: Mon, 25 Nov 2024 15:07:20 +0000 Subject: [PATCH 70/73] JVMCBC-1560: Add cluster UUID and name to metrics and spans (6/6) Adding cluster UUID and name attributes for transactional metrics. Change-Id: Ic195c68a884d1b6b52eabb04c16b50c0da206fc9 Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/217018 Tested-by: Build Bot Reviewed-by: Graham Pople --- .../java/com/couchbase/client/core/Core.java | 5 +- .../core/topology/ClusterIdentifierUtil.java | 27 ++++++ .../context/CoreTransactionsContext.java | 5 +- .../context/CoreTransactionsCounters.java | 84 +++++++++++++++---- 4 files changed, 99 insertions(+), 22 deletions(-) create mode 100644 core-io/src/main/java/com/couchbase/client/core/topology/ClusterIdentifierUtil.java diff --git a/core-io/src/main/java/com/couchbase/client/core/Core.java b/core-io/src/main/java/com/couchbase/client/core/Core.java index b1938ddd8..cde50fa38 100644 --- a/core-io/src/main/java/com/couchbase/client/core/Core.java +++ b/core-io/src/main/java/com/couchbase/client/core/Core.java @@ -94,6 +94,7 @@ import com.couchbase.client.core.service.ServiceState; import com.couchbase.client.core.service.ServiceType; import com.couchbase.client.core.topology.ClusterIdentifier; +import com.couchbase.client.core.topology.ClusterIdentifierUtil; import com.couchbase.client.core.topology.ClusterTopology; import com.couchbase.client.core.topology.ClusterTopologyWithBucket; import com.couchbase.client.core.topology.NodeIdentifier; @@ -348,7 +349,7 @@ public RequestTracer requestTracer() { ); this.transactionsCleanup = new CoreTransactionsCleanup(this, environment.transactionsConfig()); - this.transactionsContext = new CoreTransactionsContext(environment.meter()); + this.transactionsContext = new CoreTransactionsContext(this, environment.meter()); context().environment().eventBus().publish(new TransactionsStartedEvent(environment.transactionsConfig().cleanupConfig().runLostAttemptsCleanupThread(), environment.transactionsConfig().cleanupConfig().runRegularAttemptsCleanupThread())); } @@ -629,7 +630,7 @@ public ValueRecorder responseMetric(final Request request, @Nullable Throwabl } } final String finalExceptionSimpleName = exceptionSimpleName; - final ClusterIdentifier clusterIdent = currentConfig == null ? null : currentConfig.globalConfig() == null ? null : currentConfig.globalConfig().clusterIdent(); + final ClusterIdentifier clusterIdent = ClusterIdentifierUtil.fromConfig(currentConfig); return responseMetrics.computeIfAbsent(new ResponseMetricIdentifier(request, exceptionSimpleName, clusterIdent), key -> { Map tags = new HashMap<>(9); diff --git a/core-io/src/main/java/com/couchbase/client/core/topology/ClusterIdentifierUtil.java b/core-io/src/main/java/com/couchbase/client/core/topology/ClusterIdentifierUtil.java new file mode 100644 index 000000000..033c7ade1 --- /dev/null +++ b/core-io/src/main/java/com/couchbase/client/core/topology/ClusterIdentifierUtil.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2024 Couchbase, Inc. + * + * Licensed 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 com.couchbase.client.core.topology; + +import com.couchbase.client.core.config.ClusterConfig; +import reactor.util.annotation.Nullable; + +public class ClusterIdentifierUtil { + private ClusterIdentifierUtil() {} + + public static @Nullable ClusterIdentifier fromConfig(@Nullable ClusterConfig config) { + return config == null ? null : config.globalConfig() == null ? null : config.globalConfig().clusterIdent(); + } +} diff --git a/core-io/src/main/java/com/couchbase/client/core/transaction/context/CoreTransactionsContext.java b/core-io/src/main/java/com/couchbase/client/core/transaction/context/CoreTransactionsContext.java index 4172037cd..650c8fc28 100644 --- a/core-io/src/main/java/com/couchbase/client/core/transaction/context/CoreTransactionsContext.java +++ b/core-io/src/main/java/com/couchbase/client/core/transaction/context/CoreTransactionsContext.java @@ -15,6 +15,7 @@ */ package com.couchbase.client.core.transaction.context; +import com.couchbase.client.core.Core; import com.couchbase.client.core.annotation.Stability; import com.couchbase.client.core.cnc.Meter; @@ -27,8 +28,8 @@ public class CoreTransactionsContext { private final CoreTransactionsCounters counters; - public CoreTransactionsContext(Meter meter) { - this.counters = new CoreTransactionsCounters(meter); + public CoreTransactionsContext(Core core, Meter meter) { + this.counters = new CoreTransactionsCounters(core, meter); } public CoreTransactionsCounters counters() { diff --git a/core-io/src/main/java/com/couchbase/client/core/transaction/context/CoreTransactionsCounters.java b/core-io/src/main/java/com/couchbase/client/core/transaction/context/CoreTransactionsCounters.java index 985f94ca3..19058aa28 100644 --- a/core-io/src/main/java/com/couchbase/client/core/transaction/context/CoreTransactionsCounters.java +++ b/core-io/src/main/java/com/couchbase/client/core/transaction/context/CoreTransactionsCounters.java @@ -15,15 +15,21 @@ */ package com.couchbase.client.core.transaction.context; +import com.couchbase.client.core.Core; import com.couchbase.client.core.annotation.Stability; import com.couchbase.client.core.cnc.Counter; import com.couchbase.client.core.cnc.Meter; import com.couchbase.client.core.cnc.TracingIdentifiers; -import com.couchbase.client.core.cnc.ValueRecorder; +import com.couchbase.client.core.config.ClusterConfig; +import com.couchbase.client.core.topology.ClusterIdentifier; +import com.couchbase.client.core.topology.ClusterIdentifierUtil; import com.couchbase.client.core.util.CbCollections; +import reactor.util.annotation.Nullable; import java.util.HashMap; import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; import static com.couchbase.client.core.cnc.TracingIdentifiers.METER_TRANSACTION_ATTEMPTS; import static com.couchbase.client.core.cnc.TracingIdentifiers.METER_TRANSACTION_TOTAL; @@ -31,21 +37,63 @@ @Stability.Internal public class CoreTransactionsCounters { - private final Counter transactions; - private final Counter attempts; - // A histogram of transaction durations - - public CoreTransactionsCounters(Meter meter) { - Map tags = CbCollections.mapOf(TracingIdentifiers.ATTR_SERVICE, SERVICE_TRANSACTIONS); - transactions = meter.counter(METER_TRANSACTION_TOTAL, tags); - attempts = meter.counter(METER_TRANSACTION_ATTEMPTS, tags); - } - - public Counter attempts() { - return attempts; - } - - public Counter transactions() { - return transactions; - } + @Stability.Internal + public static class TransactionMetricIdentifier { + + private final @Nullable String clusterName; + private final @Nullable String clusterUuid; + + TransactionMetricIdentifier(@Nullable ClusterIdentifier clusterIdent) { + clusterName = clusterIdent == null ? null : clusterIdent.clusterName(); + clusterUuid = clusterIdent == null ? null : clusterIdent.clusterUuid(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TransactionMetricIdentifier that = (TransactionMetricIdentifier) o; + return Objects.equals(clusterName, that.clusterName) + && Objects.equals(clusterUuid, that.clusterUuid); + } + + @Override + public int hashCode() { + return Objects.hash(clusterName, clusterUuid); + } + } + + private final Map transactionsMetrics = new ConcurrentHashMap<>(); + private final Map attemptMetrics = new ConcurrentHashMap<>(); + private final Core core; + private final Meter meter; + + public CoreTransactionsCounters(Core core, Meter meter) { + this.core = core; + this.meter = meter; + } + + public Counter attempts() { + return genericCounter(METER_TRANSACTION_ATTEMPTS, attemptMetrics); + } + + public Counter transactions() { + return genericCounter(METER_TRANSACTION_TOTAL, transactionsMetrics); + } + + private Counter genericCounter(String name, Map metricsMap) { + ClusterConfig config = core.configurationProvider().config(); + ClusterIdentifier clusterIdent = ClusterIdentifierUtil.fromConfig(config); + return metricsMap.computeIfAbsent(new TransactionMetricIdentifier(clusterIdent), id -> { + HashMap tags = new HashMap<>(); + tags.put(TracingIdentifiers.ATTR_SYSTEM, TracingIdentifiers.ATTR_SYSTEM_COUCHBASE); + if (id.clusterName != null) { + tags.put(TracingIdentifiers.ATTR_CLUSTER_NAME, id.clusterName); + } + if (id.clusterUuid != null) { + tags.put(TracingIdentifiers.ATTR_CLUSTER_UUID, id.clusterUuid); + } + return meter.counter(name, tags); + }); + } } From 965b0e8bf886db8c516e31c32bc4c9d8115f97b7 Mon Sep 17 00:00:00 2001 From: David Nault Date: Fri, 22 Nov 2024 15:11:58 -0800 Subject: [PATCH 71/73] JVMCBC-1583 SDK tries to connect to nodes that are rebalanced out Motivation ---------- DeferredCloseEndpoint was failing to disconnect endpoints if the server dropped the connection while a query was in flight. Modifications ------------- DeferredCloseEndpoint now responds to notifyChannelInactive() by resuming a deferred disconnect. Removed the check for the number of outstanding requests, because we don't want any such requests to further delay endpoint disconnection. Call super.disconnect() before super.markRequestCompletion() to avoid a race condition that could make the doomed endpoint's freeToWrite() method return true even though we _really_ shouldn't dispatch more requests to this endpoint. Change-Id: I7590c1cb1e6fa86c9df5699b2206b7186b8bb92e Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/219991 Tested-by: Build Bot Reviewed-by: David Nault --- .../core/endpoint/DeferredCloseEndpoint.java | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/core-io/src/main/java/com/couchbase/client/core/endpoint/DeferredCloseEndpoint.java b/core-io/src/main/java/com/couchbase/client/core/endpoint/DeferredCloseEndpoint.java index f3bd578eb..23aa4b7cb 100644 --- a/core-io/src/main/java/com/couchbase/client/core/endpoint/DeferredCloseEndpoint.java +++ b/core-io/src/main/java/com/couchbase/client/core/endpoint/DeferredCloseEndpoint.java @@ -61,10 +61,21 @@ private void closeWhenDone() { @Stability.Internal @Override public synchronized void markRequestCompletion() { + maybeResumeDisconnect(); super.markRequestCompletion(); - if (closeWhenDone && outstandingRequests() <= 0) { + } + + @Stability.Internal + @Override + public synchronized void notifyChannelInactive() { + maybeResumeDisconnect(); + super.notifyChannelInactive(); + } + + private void maybeResumeDisconnect() { + if (closeWhenDone) { endpointContext.get().environment().eventBus().publish(new EndpointDisconnectResumedEvent(endpointContext.get())); - closeChannel(this.channel); + super.disconnect(); closeWhenDone = false; } } From 94b57b29cd07fc9b60f214e75fcf56f0b5df3319 Mon Sep 17 00:00:00 2001 From: David Nault Date: Mon, 25 Nov 2024 11:34:17 -0800 Subject: [PATCH 72/73] Fix regression in 190777dee that caused bucket.waitUntilReady to time out for bucket types that do not support views (magma, ephemeral, and memcached). Change-Id: If5fa0d5539b736799b42b9c4ee9d3d8a675ea74c Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/220049 Tested-by: Build Bot Reviewed-by: Matt Ingenthron --- .../couchbase/client/core/diagnostics/HealthPinger.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/core-io/src/main/java/com/couchbase/client/core/diagnostics/HealthPinger.java b/core-io/src/main/java/com/couchbase/client/core/diagnostics/HealthPinger.java index cde7bf02b..d7ed88287 100644 --- a/core-io/src/main/java/com/couchbase/client/core/diagnostics/HealthPinger.java +++ b/core-io/src/main/java/com/couchbase/client/core/diagnostics/HealthPinger.java @@ -30,6 +30,7 @@ import com.couchbase.client.core.msg.kv.KvPingResponse; import com.couchbase.client.core.retry.RetryStrategy; import com.couchbase.client.core.service.ServiceType; +import com.couchbase.client.core.topology.BucketCapability; import com.couchbase.client.core.topology.ClusterTopology; import com.couchbase.client.core.topology.ClusterTopologyWithBucket; import com.couchbase.client.core.topology.HostAndServicePorts; @@ -155,7 +156,11 @@ static Set extractPingTargets( final List topologiesToScan = new ArrayList<>(); if (bucket != null) { - topologiesToScan.add(clusterConfig.bucketTopology(bucket)); + ClusterTopologyWithBucket topology = clusterConfig.bucketTopology(bucket); + if (topology != null && !topology.bucket().hasCapability(BucketCapability.COUCHAPI)) { + serviceTypeFilter.remove(VIEWS); + } + topologiesToScan.add(topology); } else { serviceTypeFilter.removeAll(servicesThatRequireBucket); // narrow to the ones that can be pinged without a bucket topologiesToScan.add(clusterConfig.globalTopology()); From e35e002cae9b524fa765021e1fb57aefd39d9b5d Mon Sep 17 00:00:00 2001 From: mikereiche Date: Wed, 4 Dec 2024 13:43:43 -0800 Subject: [PATCH 73/73] Release 3.7.6 selene-sr6. Change-Id: I227523e69177a0cecfa0137a79d3a99f2880f8ba Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/220470 Tested-by: Michael Reiche Reviewed-by: Michael Reiche --- columnar-fit-performer-shared/pom.xml | 2 +- .../examples/maven-project-template/pom.xml | 2 +- columnar-java-client/pom.xml | 4 ++-- columnar-java-fit-performer/pom.xml | 2 +- core-fit-performer/pom.xml | 2 +- core-io-deps/pom.xml | 2 +- core-io/pom.xml | 4 ++-- java-client/pom.xml | 4 ++-- java-examples/pom.xml | 4 ++-- java-fit-performer/pom.xml | 2 +- kotlin-client/pom.xml | 4 ++-- kotlin-fit-performer/pom.xml | 2 +- metrics-micrometer/pom.xml | 4 ++-- metrics-opentelemetry/pom.xml | 4 ++-- osgi-feature/pom.xml | 4 ++-- pom.xml | 24 +++++++++---------- scala-client/pom.xml | 4 ++-- scala-fit-performer/pom.xml | 2 +- scala-implicits/pom.xml | 4 ++-- test-utils/pom.xml | 4 ++-- tracing-micrometer-observation/pom.xml | 4 ++-- tracing-opentelemetry-deps/pom.xml | 2 +- tracing-opentelemetry/pom.xml | 6 ++--- tracing-opentracing/pom.xml | 4 ++-- 24 files changed, 50 insertions(+), 50 deletions(-) diff --git a/columnar-fit-performer-shared/pom.xml b/columnar-fit-performer-shared/pom.xml index a21b1ea70..c7b927afb 100644 --- a/columnar-fit-performer-shared/pom.xml +++ b/columnar-fit-performer-shared/pom.xml @@ -7,7 +7,7 @@ com.couchbase.client couchbase-jvm-clients - 1.16.6-SNAPSHOT + 1.16.6 columnar-fit-performer-shared diff --git a/columnar-java-client/examples/maven-project-template/pom.xml b/columnar-java-client/examples/maven-project-template/pom.xml index b75820c74..0856d4388 100644 --- a/columnar-java-client/examples/maven-project-template/pom.xml +++ b/columnar-java-client/examples/maven-project-template/pom.xml @@ -6,7 +6,7 @@ com.example couchbase-columnar-java-example - 1.0.6-SNAPSHOT + 1.0.6 Couchbase Columnar Java SDK Project Template Examples project for Couchbase Columnar Java SDK diff --git a/columnar-java-client/pom.xml b/columnar-java-client/pom.xml index 8310eb53b..60a02e2a9 100644 --- a/columnar-java-client/pom.xml +++ b/columnar-java-client/pom.xml @@ -6,11 +6,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.6-SNAPSHOT + 1.16.6 couchbase-columnar-java-client - 1.0.6-SNAPSHOT + 1.0.6 Couchbase Columnar Java SDK diff --git a/columnar-java-fit-performer/pom.xml b/columnar-java-fit-performer/pom.xml index cc37e7ce0..8d0b9a187 100644 --- a/columnar-java-fit-performer/pom.xml +++ b/columnar-java-fit-performer/pom.xml @@ -7,7 +7,7 @@ com.couchbase.client couchbase-jvm-clients - 1.16.6-SNAPSHOT + 1.16.6 columnar-java-fit-performer diff --git a/core-fit-performer/pom.xml b/core-fit-performer/pom.xml index 4593f0ddb..64ba37765 100644 --- a/core-fit-performer/pom.xml +++ b/core-fit-performer/pom.xml @@ -7,7 +7,7 @@ com.couchbase.client couchbase-jvm-clients - 1.16.6-SNAPSHOT + 1.16.6 fit-performer-core diff --git a/core-io-deps/pom.xml b/core-io-deps/pom.xml index 31f73a261..5b50c4741 100644 --- a/core-io-deps/pom.xml +++ b/core-io-deps/pom.xml @@ -6,7 +6,7 @@ com.couchbase.client core-io-deps - 1.7.6-SNAPSHOT + 1.7.6 jar Couchbase JVM Core IO Dependencies diff --git a/core-io/pom.xml b/core-io/pom.xml index d9f488593..c3d40dcd7 100644 --- a/core-io/pom.xml +++ b/core-io/pom.xml @@ -7,11 +7,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.6-SNAPSHOT + 1.16.6 core-io - 3.7.6-SNAPSHOT + 3.7.6 diff --git a/java-client/pom.xml b/java-client/pom.xml index d41d0640c..4df936c12 100644 --- a/java-client/pom.xml +++ b/java-client/pom.xml @@ -6,11 +6,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.6-SNAPSHOT + 1.16.6 java-client - 3.7.6-SNAPSHOT + 3.7.6 Couchbase Java SDK diff --git a/java-examples/pom.xml b/java-examples/pom.xml index 0783e6dcb..f79655f91 100644 --- a/java-examples/pom.xml +++ b/java-examples/pom.xml @@ -6,11 +6,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.6-SNAPSHOT + 1.16.6 java-examples - 1.7.6-SNAPSHOT + 1.7.6 Couchbase Java SDK Examples diff --git a/java-fit-performer/pom.xml b/java-fit-performer/pom.xml index e32f6ab1b..e36d8e603 100644 --- a/java-fit-performer/pom.xml +++ b/java-fit-performer/pom.xml @@ -7,7 +7,7 @@ com.couchbase.client couchbase-jvm-clients - 1.16.6-SNAPSHOT + 1.16.6 fit-performer-java-sdk diff --git a/kotlin-client/pom.xml b/kotlin-client/pom.xml index c40625da8..438743971 100644 --- a/kotlin-client/pom.xml +++ b/kotlin-client/pom.xml @@ -7,11 +7,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.6-SNAPSHOT + 1.16.6 kotlin-client - 1.4.6-SNAPSHOT + 1.4.6 Couchbase Kotlin SDK The official Couchbase Kotlin SDK diff --git a/kotlin-fit-performer/pom.xml b/kotlin-fit-performer/pom.xml index 3dbd652ff..e00de55e7 100644 --- a/kotlin-fit-performer/pom.xml +++ b/kotlin-fit-performer/pom.xml @@ -7,7 +7,7 @@ com.couchbase.client couchbase-jvm-clients - 1.16.6-SNAPSHOT + 1.16.6 fit-performer-kotlin diff --git a/metrics-micrometer/pom.xml b/metrics-micrometer/pom.xml index 6d17cfb77..e8bedb5b3 100644 --- a/metrics-micrometer/pom.xml +++ b/metrics-micrometer/pom.xml @@ -8,11 +8,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.6-SNAPSHOT + 1.16.6 metrics-micrometer - 0.7.6-SNAPSHOT + 0.7.6 Micrometer Metrics Interoperability Provides interoperability with Micrometer diff --git a/metrics-opentelemetry/pom.xml b/metrics-opentelemetry/pom.xml index fc88e32e3..2b1245c29 100644 --- a/metrics-opentelemetry/pom.xml +++ b/metrics-opentelemetry/pom.xml @@ -6,11 +6,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.6-SNAPSHOT + 1.16.6 metrics-opentelemetry - 0.7.6-SNAPSHOT + 0.7.6 OpenTelemetry Metrics Interoperability Provides interoperability with OpenTelemetry Metrics diff --git a/osgi-feature/pom.xml b/osgi-feature/pom.xml index 9bc678594..b45a82d44 100644 --- a/osgi-feature/pom.xml +++ b/osgi-feature/pom.xml @@ -7,11 +7,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.6-SNAPSHOT + 1.16.6 osgi-feature - 3.7.6-SNAPSHOT + 3.7.6 pom Couchbase Java SDK OSGI Feature diff --git a/pom.xml b/pom.xml index 0f8758b2a..10ee17a80 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.couchbase.client couchbase-jvm-clients - 1.16.6-SNAPSHOT + 1.16.6 pom Couchbase JVM Client Parent @@ -15,17 +15,17 @@ UTF-8 UTF-8 - 3.7.6-SNAPSHOT - 1.7.6-SNAPSHOT - 3.7.6-SNAPSHOT - 3.7.6-SNAPSHOT - 1.7.6-SNAPSHOT - 1.7.6-SNAPSHOT - 1.4.6-SNAPSHOT - 1.0.6-SNAPSHOT - 1.5.6-SNAPSHOT - 0.7.6-SNAPSHOT - 1.7.6-SNAPSHOT + 3.7.6 + 1.7.6 + 3.7.6 + 3.7.6 + 1.7.6 + 1.7.6 + 1.4.6 + 1.0.6 + 1.5.6 + 0.7.6 + 1.7.6 5.11.2 3.23.1 diff --git a/scala-client/pom.xml b/scala-client/pom.xml index ef49f9eef..ab282be34 100644 --- a/scala-client/pom.xml +++ b/scala-client/pom.xml @@ -6,11 +6,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.6-SNAPSHOT + 1.16.6 scala-client_${scala.compat.version} - 1.7.6-SNAPSHOT + 1.7.6 jar Couchbase Scala SDK diff --git a/scala-fit-performer/pom.xml b/scala-fit-performer/pom.xml index 94c3e77dd..a5702f05a 100644 --- a/scala-fit-performer/pom.xml +++ b/scala-fit-performer/pom.xml @@ -5,7 +5,7 @@ com.couchbase.client couchbase-jvm-clients - 1.16.6-SNAPSHOT + 1.16.6 fit-performer-scala diff --git a/scala-implicits/pom.xml b/scala-implicits/pom.xml index 2ddd5b2eb..bff11bdc6 100644 --- a/scala-implicits/pom.xml +++ b/scala-implicits/pom.xml @@ -7,11 +7,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.6-SNAPSHOT + 1.16.6 scala-implicits_${scala.compat.version} - 1.7.6-SNAPSHOT + 1.7.6 jar Couchbase Scala SDK Implicits diff --git a/test-utils/pom.xml b/test-utils/pom.xml index 50fe51dd7..ded5db1f5 100644 --- a/test-utils/pom.xml +++ b/test-utils/pom.xml @@ -6,12 +6,12 @@ com.couchbase.client couchbase-jvm-clients - 1.16.6-SNAPSHOT + 1.16.6 Couchbase (Integration) Test Utilities test-utils - 1.7.6-SNAPSHOT + 1.7.6 diff --git a/tracing-micrometer-observation/pom.xml b/tracing-micrometer-observation/pom.xml index ef84025cb..977e94a2b 100644 --- a/tracing-micrometer-observation/pom.xml +++ b/tracing-micrometer-observation/pom.xml @@ -6,11 +6,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.6-SNAPSHOT + 1.16.6 tracing-micrometer-observation - 1.5.6-SNAPSHOT + 1.5.6 Micrometer Observation Interoperability Provides interoperability with Micrometer Observation diff --git a/tracing-opentelemetry-deps/pom.xml b/tracing-opentelemetry-deps/pom.xml index 9372cd288..8b8076bc1 100644 --- a/tracing-opentelemetry-deps/pom.xml +++ b/tracing-opentelemetry-deps/pom.xml @@ -9,7 +9,7 @@ com.couchbase.client tracing-opentelemetry-deps - 1.5.6-SNAPSHOT + 1.5.6 jar OpenTelemetry Interoperability Dependencies diff --git a/tracing-opentelemetry/pom.xml b/tracing-opentelemetry/pom.xml index 4f7442e8d..94b293fef 100644 --- a/tracing-opentelemetry/pom.xml +++ b/tracing-opentelemetry/pom.xml @@ -6,11 +6,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.6-SNAPSHOT + 1.16.6 tracing-opentelemetry - 1.5.6-SNAPSHOT + 1.5.6 OpenTelemetry Interoperability Provides interoperability with OpenTelemetry @@ -24,7 +24,7 @@ com.couchbase.client tracing-opentelemetry-deps - 1.5.6-SNAPSHOT + 1.5.6 diff --git a/tracing-opentracing/pom.xml b/tracing-opentracing/pom.xml index c83a292e0..8b223b7a0 100644 --- a/tracing-opentracing/pom.xml +++ b/tracing-opentracing/pom.xml @@ -6,11 +6,11 @@ com.couchbase.client couchbase-jvm-clients - 1.16.6-SNAPSHOT + 1.16.6 tracing-opentracing - 1.5.6-SNAPSHOT + 1.5.6 OpenTracing Interoperability Provides interoperability with OpenTracing