diff --git a/pom.xml b/pom.xml index 545a836b1..14e57daca 100644 --- a/pom.xml +++ b/pom.xml @@ -18,8 +18,8 @@ - 3.4.1 - 3.4.1 + 3.4.3 + 3.4.3 3.1.0-SNAPSHOT spring.data.couchbase 7.0.1.Final diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByQueryOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByQueryOperationSupport.java index 205fc9c2d..a8f753d2a 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByQueryOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByQueryOperationSupport.java @@ -15,6 +15,8 @@ */ package org.springframework.data.couchbase.core; +import com.couchbase.client.core.api.query.CoreQueryContext; +import com.couchbase.client.core.io.CollectionIdentifier; import reactor.core.publisher.Flux; import java.util.Optional; @@ -28,7 +30,6 @@ import org.springframework.data.couchbase.core.support.TemplateUtils; import org.springframework.util.Assert; -import com.couchbase.client.core.deps.com.fasterxml.jackson.databind.node.ObjectNode; import com.couchbase.client.java.ReactiveScope; import com.couchbase.client.java.json.JsonObject; import com.couchbase.client.java.query.QueryOptions; @@ -100,11 +101,10 @@ public Flux all() { } else { TransactionQueryOptions opts = OptionsBuilder .buildTransactionQueryOptions(buildQueryOptions(pArgs.getOptions())); - ObjectNode convertedOptions = com.couchbase.client.java.transactions.internal.OptionsUtil - .createTransactionOptions(pArgs.getScope() == null ? null : rs, statement, opts); + CoreQueryContext queryContext = CollectionIdentifier.DEFAULT_SCOPE.equals(rs.name()) ? null : CoreQueryContext.of(rs.bucketName(), rs.name()); return transactionContext.get().getCore() - .queryBlocking(statement, template.getBucketName(), pArgs.getScope(), convertedOptions, false) - .flatMapIterable(result -> result.rows).map(row -> { + .queryBlocking(statement, queryContext, opts.builder().build(), false) + .flatMapIterable(result -> result.collectRows()).map(row -> { JsonObject json = JsonObject.fromJson(row.data()); return new RemoveResult(json.getString(TemplateUtils.SELECT_ID), json.getLong(TemplateUtils.SELECT_CAS), Optional.empty()); diff --git a/src/main/java/org/springframework/data/couchbase/core/query/N1QLQuery.java b/src/main/java/org/springframework/data/couchbase/core/query/N1QLQuery.java index 93858255a..c7ac56878 100644 --- a/src/main/java/org/springframework/data/couchbase/core/query/N1QLQuery.java +++ b/src/main/java/org/springframework/data/couchbase/core/query/N1QLQuery.java @@ -41,9 +41,10 @@ public QueryOptions getOptions() { return options; } + // for logging only public JsonObject n1ql() { JsonObject query = JsonObject.create().put("statement", expression.toString()); - options.build().injectParams(query); + query.put("options", OptionsBuilder.getQueryOpts(options.build())); return query; } diff --git a/src/main/java/org/springframework/data/couchbase/core/query/OptionsBuilder.java b/src/main/java/org/springframework/data/couchbase/core/query/OptionsBuilder.java index c3a0e2cf0..db6eebf19 100644 --- a/src/main/java/org/springframework/data/couchbase/core/query/OptionsBuilder.java +++ b/src/main/java/org/springframework/data/couchbase/core/query/OptionsBuilder.java @@ -15,6 +15,7 @@ */ package org.springframework.data.couchbase.core.query; +import static com.couchbase.client.core.util.Validators.notNull; import static org.springframework.data.couchbase.core.query.Meta.MetaKey.RETRY_STRATEGY; import static org.springframework.data.couchbase.core.query.Meta.MetaKey.SCAN_CONSISTENCY; import static org.springframework.data.couchbase.core.query.Meta.MetaKey.TIMEOUT; @@ -24,9 +25,13 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.time.Duration; +import java.util.HashMap; import java.util.Map; import java.util.Optional; +import com.couchbase.client.core.api.query.CoreQueryScanConsistency; +import com.couchbase.client.core.classic.query.ClassicCoreQueryOps; +import com.couchbase.client.core.error.InvalidArgumentException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.annotation.AnnotatedElementUtils; @@ -67,21 +72,21 @@ public class OptionsBuilder { static QueryOptions buildQueryOptions(Query query, QueryOptions options, QueryScanConsistency scanConsistency) { options = options != null ? options : QueryOptions.queryOptions(); if (query.getParameters() != null) { - if (query.getParameters() instanceof JsonArray) { + if (query.getParameters() instanceof JsonArray && !((JsonArray) query.getParameters()).isEmpty()) { options.parameters((JsonArray) query.getParameters()); - } else { + } else if( query.getParameters() instanceof JsonObject && !((JsonObject)query.getParameters()).isEmpty()){ options.parameters((JsonObject) query.getParameters()); } } Meta meta = query.getMeta() != null ? query.getMeta() : new Meta(); QueryOptions.Built optsBuilt = options.build(); - JsonObject optsJson = getQueryOpts(optsBuilt); + QueryScanConsistency metaQueryScanConsistency = meta.get(SCAN_CONSISTENCY) != null ? ((ScanConsistency) meta.get(SCAN_CONSISTENCY)).query() : null; QueryScanConsistency qsc = fromFirst(QueryScanConsistency.NOT_BOUNDED, query.getScanConsistency(), - getScanConsistency(optsJson), scanConsistency, metaQueryScanConsistency); + scanConsistency(optsBuilt), scanConsistency, metaQueryScanConsistency); Duration timeout = fromFirst(Duration.ofSeconds(0), getTimeout(optsBuilt), meta.get(TIMEOUT)); RetryStrategy retryStrategy = fromFirst(null, getRetryStrategy(optsBuilt), meta.get(RETRY_STRATEGY)); @@ -100,6 +105,21 @@ static QueryOptions buildQueryOptions(Query query, QueryOptions options, QuerySc return options; } + private static QueryScanConsistency scanConsistency(QueryOptions.Built optsBuilt){ + CoreQueryScanConsistency scanConsistency = optsBuilt.scanConsistency(); + if (scanConsistency == null){ + return null; + } + switch (scanConsistency) { + case NOT_BOUNDED: + return QueryScanConsistency.NOT_BOUNDED; + case REQUEST_PLUS: + return QueryScanConsistency.REQUEST_PLUS; + default: + throw new InvalidArgumentException("Unknown scan consistency type " + scanConsistency, null, null); + } + } + public static TransactionQueryOptions buildTransactionQueryOptions(QueryOptions options) { QueryOptions.Built built = options.build(); TransactionQueryOptions txOptions = TransactionQueryOptions.queryOptions(); @@ -110,8 +130,21 @@ public static TransactionQueryOptions buildTransactionQueryOptions(QueryOptions throw new IllegalArgumentException("QueryOptions.flexIndex is not supported in a transaction"); } + Object value = optsJson.get("args"); + if(value instanceof JsonObject){ + txOptions.parameters((JsonObject)value); + }else if(value instanceof JsonArray) { + txOptions.parameters((JsonArray) value); + } else if(value != null) { + throw InvalidArgumentException.fromMessage( + "non-null args property was neither JsonObject(namedParameters) nor JsonArray(positionalParameters) " + + value); + } + for (Map.Entry entry : optsJson.toMap().entrySet()) { - txOptions.raw(entry.getKey(), entry.getValue()); + if(!entry.getKey().equals("args")) { + txOptions.raw(entry.getKey(), entry.getValue()); + } } if (LOG.isDebugEnabled()) { @@ -370,10 +403,8 @@ static String toString(MutateInOptions o) { return s.toString(); } - private static JsonObject getQueryOpts(QueryOptions.Built optsBuilt) { - JsonObject jo = JsonObject.create(); - optsBuilt.injectParams(jo); - return jo; + public static JsonObject getQueryOpts(QueryOptions.Built optsBuilt) { + return JsonObject.fromJson(ClassicCoreQueryOps.convertOptions(optsBuilt).toString().getBytes()); } /** @@ -396,18 +427,6 @@ public static T fromFirst(T deflt, Object... choice) { return chosen; } - private static QueryScanConsistency getScanConsistency(JsonObject opts) { - String str = opts.getString("scan_consistency"); - if ("at_plus".equals(str)) { - return null; - } - return str == null ? null : QueryScanConsistency.valueOf(str.toUpperCase()); - } - - private static JsonObject getScanVectors(JsonObject opts) { - return opts.getObject("scan_vectors"); - } - private static Duration getTimeout(QueryOptions.Built optsBuilt) { Optional timeout = optsBuilt.timeout(); return timeout.isPresent() ? timeout.get() : null; diff --git a/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateKeyValueIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateKeyValueIntegrationTests.java index e78365746..8b9d08ce7 100644 --- a/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateKeyValueIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateKeyValueIntegrationTests.java @@ -55,7 +55,7 @@ import com.couchbase.client.java.query.QueryScanConsistency; /** - * KV tests Theses tests rely on a cb server running. + * KV test - these tests rely on a cb server running. * * @author Michael Nitschinger * @author Michael Reiche @@ -81,11 +81,11 @@ public void beforeEach() { @Test void findByIdWithLock() { try { - User user = new User(UUID.randomUUID().toString(), "user1", "user1"); + User user = new User("1", "user1", "user1"); couchbaseTemplate.upsertById(User.class).one(user); - User foundUser = couchbaseTemplate.findById(User.class).withLock(Duration.ofSeconds(2)).one(user.getId()); + User foundUser = couchbaseTemplate.findById(User.class).withLock(Duration.ofSeconds(5)).one(user.getId()); user.setVersion(foundUser.getVersion());// version will have changed assertEquals(user, foundUser); @@ -94,8 +94,15 @@ void findByIdWithLock() { ); assertTrue(exception.retryReasons().contains(RetryReason.KV_LOCKED), "should have been locked"); } finally { - sleepSecs(2); - couchbaseTemplate.removeByQuery(User.class).withConsistency(QueryScanConsistency.REQUEST_PLUS).all(); + for(int i=0; i< 10; i++) { + sleepSecs(2); + try { + couchbaseTemplate.removeByQuery(User.class).withConsistency(QueryScanConsistency.REQUEST_PLUS).all(); + break; + } catch (Exception e) { + e.printStackTrace(); // gives IndexFailureException if the lock is still active + } + } } } diff --git a/src/test/java/org/springframework/data/couchbase/repository/query/N1qlQueryCreatorTests.java b/src/test/java/org/springframework/data/couchbase/repository/query/N1qlQueryCreatorTests.java index 8167adf5c..8c8a2d98f 100644 --- a/src/test/java/org/springframework/data/couchbase/repository/query/N1qlQueryCreatorTests.java +++ b/src/test/java/org/springframework/data/couchbase/repository/query/N1qlQueryCreatorTests.java @@ -25,6 +25,8 @@ import java.util.List; import java.util.Locale; +import com.couchbase.client.core.deps.com.fasterxml.jackson.databind.node.ArrayNode; +import com.couchbase.client.java.query.QueryOptions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.data.couchbase.core.convert.CouchbaseConverter; @@ -32,6 +34,7 @@ import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext; import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity; import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty; +import org.springframework.data.couchbase.core.query.OptionsBuilder; import org.springframework.data.couchbase.core.query.Query; import org.springframework.data.couchbase.domain.Person; import org.springframework.data.couchbase.domain.PersonRepository; @@ -119,18 +122,17 @@ void queryParametersArray() throws Exception { QueryMethod queryMethod = new QueryMethod(method, new DefaultRepositoryMetadata(UserRepository.class), new SpelAwareProxyProjectionFactory()); Query expected = (new Query()).addCriteria(where(i("firstname")).in("Oliver", "Charles")); + JsonArray parameters = JsonArray.create().add(JsonArray.create().add("Oliver").add("Charles")); + QueryOptions expectedQOptions = QueryOptions.queryOptions().parameters(parameters); N1qlQueryCreator creator = new N1qlQueryCreator(tree, getAccessor(getParameters(method), new Object[] { new Object[] { "Oliver", "Charles" } }), queryMethod, converter, bucketName); Query query = creator.createQuery(); - // Query expected = (new Query()).addCriteria(where("firstname").in("Oliver", "Charles")); assertEquals(" WHERE `firstname` in $1", query.export(new int[1])); - JsonObject expectedOptions = JsonObject.create(); - expected.buildQueryOptions(null, null).build().injectParams(expectedOptions); - JsonObject actualOptions = JsonObject.create(); - expected.buildQueryOptions(null, null).build().injectParams(actualOptions); - assertEquals(expectedOptions.removeKey("client_context_id"), actualOptions.removeKey("client_context_id")); + ArrayNode expectedOptions = expected.buildQueryOptions(expectedQOptions, null).build().positionalParameters(); + ArrayNode actualOptions = query.buildQueryOptions(null, null).build().positionalParameters(); + assertEquals(expectedOptions.toString(), actualOptions.toString()); } @Test @@ -148,12 +150,12 @@ void queryParametersJsonArray() throws Exception { Query query = creator.createQuery(); Query expected = (new Query()).addCriteria(where(i("firstname")).in("Oliver", "Charles")); + JsonArray parameters = JsonArray.create().add(JsonArray.create().add("Oliver").add("Charles")); + QueryOptions expectedQOptions = QueryOptions.queryOptions().parameters(parameters); assertEquals(" WHERE `firstname` in $1", query.export(new int[1])); - JsonObject expectedOptions = JsonObject.create(); - expected.buildQueryOptions(null, null).build().injectParams(expectedOptions); - JsonObject actualOptions = JsonObject.create(); - expected.buildQueryOptions(null, null).build().injectParams(actualOptions); - assertEquals(expectedOptions.removeKey("client_context_id"), actualOptions.removeKey("client_context_id")); + ArrayNode expectedOptions = expected.buildQueryOptions(expectedQOptions, null).build().positionalParameters(); + ArrayNode actualOptions = query.buildQueryOptions(null, null).build().positionalParameters(); + assertEquals(expectedOptions.toString(), actualOptions.toString()); } @Test @@ -171,13 +173,13 @@ void queryParametersList() throws Exception { Query query = creator.createQuery(); Query expected = (new Query()).addCriteria(where(i("firstname")).in("Oliver", "Charles")); + JsonArray parameters = JsonArray.create().add(JsonArray.create().add("Oliver").add("Charles")); + QueryOptions expectedQOptions = QueryOptions.queryOptions().parameters(parameters); assertEquals(" WHERE `firstname` in $1", query.export(new int[1])); - JsonObject expectedOptions = JsonObject.create(); - expected.buildQueryOptions(null, null).build().injectParams(expectedOptions); - JsonObject actualOptions = JsonObject.create(); - expected.buildQueryOptions(null, null).build().injectParams(actualOptions); - assertEquals(expectedOptions.removeKey("client_context_id"), actualOptions.removeKey("client_context_id")); + ArrayNode expectedOptions = expected.buildQueryOptions(expectedQOptions, null).build().positionalParameters(); + ArrayNode actualOptions = query.buildQueryOptions(null, null).build().positionalParameters(); + assertEquals(expectedOptions.toString(), actualOptions.toString()); } @Test diff --git a/src/test/java/org/springframework/data/couchbase/util/JavaIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/util/JavaIntegrationTests.java index 1ef84ffcc..a10b485e5 100644 --- a/src/test/java/org/springframework/data/couchbase/util/JavaIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/util/JavaIntegrationTests.java @@ -285,7 +285,7 @@ public static CompletableFuture createPrimaryIndex(Cluster cluster, String options.timeout(Duration.ofSeconds(300)); options.ignoreIfExists(true); final CreatePrimaryQueryIndexOptions.Built builtOpts = options.build(); - final String indexName = builtOpts.indexName().orElse(null); + final String indexName = builtOpts.indexName(); String keyspace = "default:`" + bucketName + "`.`" + scopeName + "`.`" + collectionName + "`"; String statement = "CREATE PRIMARY INDEX ";