DATACOUCH-1066 - Make QueryCriteria key a N1qlExpression. (#1082)

This is a merge of mmonti's changeset into master. The changeset was made
on top of 4.1.x instead of master so it has a little catching up to do.

Co-authored-by: mikereiche <michael.reiche@couchbase.com>
This commit is contained in:
Michael Reiche
2021-02-16 18:08:06 -08:00
committed by GitHub
parent 9356e43008
commit 41ee1f99ee
13 changed files with 240 additions and 129 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,25 +15,29 @@
*/
package org.springframework.data.couchbase.core.query;
import static org.springframework.data.couchbase.core.query.N1QLExpression.x;
import java.util.ArrayList;
import java.util.Formatter;
import java.util.LinkedList;
import java.util.List;
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
import org.springframework.lang.Nullable;
import com.couchbase.client.core.error.InvalidArgumentException;
import com.couchbase.client.java.json.JsonArray;
import com.couchbase.client.java.json.JsonObject;
import com.couchbase.client.java.json.JsonValue;
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
import org.springframework.lang.Nullable;
/**
* @author Michael Nitschinger
* @author Michael Reiche
* @author Mauro Monti
*/
public class QueryCriteria implements QueryCriteriaDefinition {
private final String key;
private final N1QLExpression key;
/**
* Holds the chain itself, the current operator being always the last one.
*/
@@ -46,16 +50,16 @@ public class QueryCriteria implements QueryCriteriaDefinition {
private Object[] value;
private String format;
QueryCriteria(List<QueryCriteria> chain, String key, Object[] value, ChainOperator chainOperator) {
QueryCriteria(List<QueryCriteria> chain, N1QLExpression key, Object[] value, ChainOperator chainOperator) {
this(chain, key, value, chainOperator, null, null);
}
QueryCriteria(List<QueryCriteria> chain, String key, Object value, ChainOperator chainOperator) {
QueryCriteria(List<QueryCriteria> chain, N1QLExpression key, Object value, ChainOperator chainOperator) {
this(chain, key, new Object[] { value }, chainOperator, null, null);
}
QueryCriteria(List<QueryCriteria> chain, String key, Object[] value, ChainOperator chainOperator, String operator,
String format) {
QueryCriteria(List<QueryCriteria> chain, N1QLExpression key, Object[] value, ChainOperator chainOperator,
String operator, String format) {
this.criteriaChain = chain;
criteriaChain.add(this);
this.key = key;
@@ -70,9 +74,16 @@ public class QueryCriteria implements QueryCriteriaDefinition {
}
/**
* Static factory method to create a Criteria using the provided key.
* Static factory method to create a Criteria using the provided String key.
*/
public static QueryCriteria where(String key) {
return where(x(key));
}
/**
* Static factory method to create a Criteria using the provided N1QLExpression key.
*/
public static QueryCriteria where(N1QLExpression key) {
return new QueryCriteria(new ArrayList<>(), key, null, null);
}
@@ -83,6 +94,10 @@ public class QueryCriteria implements QueryCriteriaDefinition {
}
public QueryCriteria and(String key) {
return and(x(key));
}
public QueryCriteria and(N1QLExpression key) {
return new QueryCriteria(this.criteriaChain, key, null, ChainOperator.AND);
}
@@ -90,14 +105,18 @@ public class QueryCriteria implements QueryCriteriaDefinition {
return new QueryCriteria(this.criteriaChain, null, criteria, ChainOperator.AND);
}
public QueryCriteria or(QueryCriteria criteria) {
return new QueryCriteria(this.criteriaChain, null, criteria, ChainOperator.OR);
public QueryCriteria or(String key) {
return or(x(key));
}
public QueryCriteria or(String key) {
public QueryCriteria or(N1QLExpression key) {
return new QueryCriteria(this.criteriaChain, key, null, ChainOperator.OR);
}
public QueryCriteria or(QueryCriteria criteria) {
return new QueryCriteria(this.criteriaChain, null, criteria, ChainOperator.OR);
}
public QueryCriteria eq(@Nullable Object o) {
return is(o);
}
@@ -343,7 +362,7 @@ public class QueryCriteria implements QueryCriteriaDefinition {
*/
private StringBuilder exportSingle(StringBuilder sb, int[] paramIndexPtr, JsonValue parameters,
CouchbaseConverter converter) {
String fieldName = maybeBackTic(key);
String fieldName = key == null ? null : key.toString(); // maybeBackTic(key);
int valueLen = value == null ? 0 : value.length;
Object[] v = new Object[valueLen + 2];
v[0] = fieldName;
@@ -377,7 +396,7 @@ public class QueryCriteria implements QueryCriteriaDefinition {
* @param parameters - parameters of the query. If operands are parameterized, their values are added to parameters
* @return string containing part of N1QL query
*/
private String maybeWrapValue(String key, Object value, int[] paramIndexPtr, JsonValue parameters,
private String maybeWrapValue(N1QLExpression key, Object value, int[] paramIndexPtr, JsonValue parameters,
CouchbaseConverter converter) {
if (paramIndexPtr != null) {
if (paramIndexPtr[0] >= 0) {
@@ -397,10 +416,10 @@ public class QueryCriteria implements QueryCriteriaDefinition {
JsonObject params = (JsonObject) parameters;
// from StringBasedN1qlQueryParser.getNamedPlaceholderValues()
try {
params.put(key, convert(converter, value));
params.put(key.toString(), convert(converter, value));
} catch (InvalidArgumentException iae) {
if (value instanceof Object[]) {
params.put(key, JsonArray.from((Object[]) value));
params.put(key.toString(), JsonArray.from((Object[]) value));
} else {
throw iae;
}
@@ -416,7 +435,7 @@ public class QueryCriteria implements QueryCriteriaDefinition {
} else if (value == null) {
return "null";
} else if (value instanceof Object[]) { // convert array into sequence of comma-separated values
StringBuffer l = new StringBuffer();
StringBuilder l = new StringBuilder();
l.append("[");
Object[] array = (Object[]) value;
for (int i = 0; i < array.length; i++) {

View File

@@ -118,6 +118,11 @@ public class N1qlCountQueryCreator extends OldN1qlQueryCreator {
return delegate.first();
}
@Override
public Pageable withPage(int i) {
return new CountPageable(delegate.withPage(i));
}
public boolean hasPrevious() {
return delegate.hasPrevious();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,6 +15,10 @@
*/
package org.springframework.data.couchbase.repository.query;
import static org.springframework.data.couchbase.core.query.N1QLExpression.i;
import static org.springframework.data.couchbase.core.query.N1QLExpression.meta;
import static org.springframework.data.couchbase.core.query.N1QLExpression.path;
import static org.springframework.data.couchbase.core.query.N1QLExpression.x;
import static org.springframework.data.couchbase.core.query.QueryCriteria.where;
import java.util.Iterator;
@@ -22,6 +26,7 @@ import java.util.Iterator;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
import org.springframework.data.couchbase.core.query.N1QLExpression;
import org.springframework.data.couchbase.core.query.Query;
import org.springframework.data.couchbase.core.query.QueryCriteria;
import org.springframework.data.domain.Sort;
@@ -36,33 +41,39 @@ import org.springframework.data.repository.query.parser.PartTree;
/**
* @author Michael Nitschinger
* @author Michael Reiche
* @author Mauro Monti
*/
public class N1qlQueryCreator extends AbstractQueryCreator<Query, QueryCriteria> {
private static final String META_ID_PROPERTY = "id";
private static final String META_CAS_PROPERTY = "cas";
private final ParameterAccessor accessor;
private final MappingContext<?, CouchbasePersistentProperty> context;
private final QueryMethod queryMethod;
private final CouchbaseConverter converter;
private final String bucketName;
public N1qlQueryCreator(final PartTree tree, final ParameterAccessor accessor, QueryMethod queryMethod,
CouchbaseConverter converter) {
public N1qlQueryCreator(final PartTree tree, final ParameterAccessor accessor, final QueryMethod queryMethod,
final CouchbaseConverter converter, final String bucketName) {
super(tree, accessor);
this.accessor = accessor;
this.context = converter.getMappingContext();
this.queryMethod = queryMethod;
this.converter = converter;
this.bucketName = bucketName;
}
@Override
protected QueryCriteria create(final Part part, final Iterator<Object> iterator) {
PersistentPropertyPath<CouchbasePersistentProperty> path = context.getPersistentPropertyPath(part.getProperty());
CouchbasePersistentProperty property = path.getLeafProperty();
return from(part, property, where(path.toDotPath(cvtr)), iterator);
return from(part, property, where(addMetaIfRequired(path, property)), iterator);
}
static Converter<? super CouchbasePersistentProperty, String> cvtr = (
source) -> new StringBuilder(source.getName().length() + 2).append('`').append(source.getName()).append('`')
.toString();
source) -> new StringBuilder(source.getFieldName().length() + 2).append('`').append(source.getFieldName())
.append('`').toString();
@Override
protected QueryCriteria and(final Part part, final QueryCriteria base, final Iterator<Object> iterator) {
@@ -73,7 +84,7 @@ public class N1qlQueryCreator extends AbstractQueryCreator<Query, QueryCriteria>
PersistentPropertyPath<CouchbasePersistentProperty> path = context.getPersistentPropertyPath(part.getProperty());
CouchbasePersistentProperty property = path.getLeafProperty();
return from(part, property, base.and(path.toDotPath()), iterator);
return from(part, property, base.and(addMetaIfRequired(path, property)), iterator);
}
@Override
@@ -149,4 +160,16 @@ public class N1qlQueryCreator extends AbstractQueryCreator<Query, QueryCriteria>
}
}
private N1QLExpression addMetaIfRequired(
final PersistentPropertyPath<CouchbasePersistentProperty> persistentPropertyPath,
final CouchbasePersistentProperty property) {
if (property.isIdProperty()) {
return path(meta(i(bucketName)), i(META_ID_PROPERTY));
}
if (property.isVersionProperty()) {
return path(meta(i(bucketName)), i(META_CAS_PROPERTY));
}
return x(persistentPropertyPath.toDotPath(cvtr));
}
}

View File

@@ -70,7 +70,7 @@ public class N1qlRepositoryQueryExecutor {
SPEL_PARSER, evaluationContextProvider, namedQueries).createQuery();
} else {
final PartTree tree = new PartTree(queryMethod.getName(), domainClass);
query = new N1qlQueryCreator(tree, accessor, queryMethod, operations.getConverter()).createQuery();
query = new N1qlQueryCreator(tree, accessor, queryMethod, operations.getConverter(), operations.getBucketName()).createQuery();
}
ExecutableFindByQueryOperation.ExecutableFindByQuery<?> operation = (ExecutableFindByQueryOperation.ExecutableFindByQuery<?>) operations

View File

@@ -72,7 +72,8 @@ public class PartTreeCouchbaseQuery extends AbstractCouchbaseQuery {
@Override
protected Query createQuery(ParametersParameterAccessor accessor) {
N1qlQueryCreator creator = new N1qlQueryCreator(tree, accessor, getQueryMethod(), converter);
N1qlQueryCreator creator = new N1qlQueryCreator(tree, accessor, getQueryMethod(), converter,
getOperations().getBucketName());
Query query = creator.createQuery();
if (tree.isLimiting()) {
@@ -88,7 +89,8 @@ public class PartTreeCouchbaseQuery extends AbstractCouchbaseQuery {
*/
@Override
protected Query createCountQuery(ParametersParameterAccessor accessor) {
return new N1qlQueryCreator(tree, accessor, getQueryMethod(), converter).createQuery();
return new N1qlQueryCreator(tree, accessor, getQueryMethod(), converter, getOperations().getBucketName())
.createQuery();
}
/*

View File

@@ -73,7 +73,8 @@ public class ReactivePartTreeCouchbaseQuery extends AbstractReactiveCouchbaseQue
@Override
protected Query createQuery(ParametersParameterAccessor accessor) {
N1qlQueryCreator creator = new N1qlQueryCreator(tree, accessor, getQueryMethod(), converter);
N1qlQueryCreator creator = new N1qlQueryCreator(tree, accessor, getQueryMethod(), converter,
getOperations().getBucketName());
Query query = creator.createQuery();
if (tree.isLimiting()) {
@@ -91,7 +92,8 @@ public class ReactivePartTreeCouchbaseQuery extends AbstractReactiveCouchbaseQue
*/
@Override
protected Query createCountQuery(ParametersParameterAccessor accessor) {
Query query = new N1qlQueryCreator(tree, accessor, getQueryMethod(), converter).createQuery();
Query query = new N1qlQueryCreator(tree, accessor, getQueryMethod(), converter, getOperations().getBucketName())
.createQuery();
if (LOG.isDebugEnabled()) {
LOG.debug("Created query {} for * fields.", query.export());
}

View File

@@ -79,6 +79,7 @@ public class ReactiveStringBasedCouchbaseQuery extends AbstractReactiveCouchbase
StringN1qlQueryCreator creator = new StringN1qlQueryCreator(accessor, getQueryMethod(),
getOperations().getConverter(), getOperations().getBucketName(), expressionParser, evaluationContextProvider,
namedQueries);
Query query = creator.createQuery();
if (LOG.isDebugEnabled()) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020 the original author or authors
* Copyright 2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,9 +15,11 @@
*/
package org.springframework.data.couchbase.repository.query;
import com.couchbase.client.java.json.JsonArray;
import com.couchbase.client.java.json.JsonObject;
import com.couchbase.client.java.json.JsonValue;
import static org.springframework.data.couchbase.core.query.N1QLExpression.x;
import static org.springframework.data.couchbase.core.query.QueryCriteria.where;
import java.util.Iterator;
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
import org.springframework.data.couchbase.core.query.N1QLExpression;
@@ -36,12 +38,13 @@ import org.springframework.data.repository.query.parser.Part;
import org.springframework.data.repository.query.parser.PartTree;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import java.util.Iterator;
import static org.springframework.data.couchbase.core.query.QueryCriteria.where;
import com.couchbase.client.java.json.JsonArray;
import com.couchbase.client.java.json.JsonObject;
import com.couchbase.client.java.json.JsonValue;
/**
* @author Michael Reiche
* @author Mauro Monti
*/
public class StringN1qlQueryCreator extends AbstractQueryCreator<Query, QueryCriteria> {
@@ -98,10 +101,9 @@ public class StringN1qlQueryCreator extends AbstractQueryCreator<Query, QueryCri
@Override
protected QueryCriteria create(final Part part, final Iterator<Object> iterator) {
PersistentPropertyPath<CouchbasePersistentProperty> path = context.getPersistentPropertyPath(
part.getProperty());
PersistentPropertyPath<CouchbasePersistentProperty> path = context.getPersistentPropertyPath(part.getProperty());
CouchbasePersistentProperty property = path.getLeafProperty();
return from(part, property, where(path.toDotPath()), iterator);
return from(part, property, where(x(path.toDotPath())), iterator);
}
@Override
@@ -110,11 +112,10 @@ public class StringN1qlQueryCreator extends AbstractQueryCreator<Query, QueryCri
return create(part, iterator);
}
PersistentPropertyPath<CouchbasePersistentProperty> path = context.getPersistentPropertyPath(
part.getProperty());
PersistentPropertyPath<CouchbasePersistentProperty> path = context.getPersistentPropertyPath(part.getProperty());
CouchbasePersistentProperty property = path.getLeafProperty();
return from(part, property, base.and(path.toDotPath()), iterator);
return from(part, property, base.and(x(path.toDotPath())), iterator);
}
@Override
@@ -139,10 +140,10 @@ public class StringN1qlQueryCreator extends AbstractQueryCreator<Query, QueryCri
final Part.Type type = part.getType();
switch (type) {
case SIMPLE_PROPERTY:
return criteria; // this will be the dummy from PartTree
default:
throw new IllegalArgumentException("Unsupported keyword!");
case SIMPLE_PROPERTY:
return criteria; // this will be the dummy from PartTree
default:
throw new IllegalArgumentException("Unsupported keyword!");
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@ import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.springframework.data.couchbase.config.BeanNames.COUCHBASE_TEMPLATE;
import static org.springframework.data.couchbase.config.BeanNames.REACTIVE_COUCHBASE_TEMPLATE;
import static org.springframework.data.couchbase.core.query.N1QLExpression.i;
import java.time.Instant;
import java.time.temporal.TemporalAccessor;
@@ -60,6 +61,7 @@ import com.couchbase.client.java.query.QueryScanConsistency;
* @author Michael Nitschinger
* @author Michael Reiche
* @author Haris Alesevic
* @author Mauro Monti
*/
@IgnoreWhen(missesCapabilities = Capabilities.QUERY, clusterTypes = ClusterType.MOCKED)
class CouchbaseTemplateQueryIntegrationTests extends JavaIntegrationTests {
@@ -124,7 +126,7 @@ class CouchbaseTemplateQueryIntegrationTests extends JavaIntegrationTests {
couchbaseTemplate.upsertById(User.class).all(Arrays.asList(user1, user2, specialUser));
Query specialUsers = new Query(QueryCriteria.where("firstname").like("special"));
Query specialUsers = new Query(QueryCriteria.where(i("firstname")).like("special"));
final List<User> foundUsers = couchbaseTemplate.findByQuery(User.class)
.withConsistency(QueryScanConsistency.REQUEST_PLUS).matching(specialUsers).all();
@@ -206,7 +208,7 @@ class CouchbaseTemplateQueryIntegrationTests extends JavaIntegrationTests {
assertTrue(couchbaseTemplate.existsById().one(user2.getId()));
assertTrue(couchbaseTemplate.existsById().one(specialUser.getId()));
Query nonSpecialUsers = new Query(QueryCriteria.where("firstname").notLike("special"));
Query nonSpecialUsers = new Query(QueryCriteria.where(i("firstname")).notLike("special"));
couchbaseTemplate.removeByQuery(User.class).withConsistency(QueryScanConsistency.REQUEST_PLUS)
.matching(nonSpecialUsers).all();

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017-2019 the original author or authors.
* Copyright 2017-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,57 +16,56 @@
package org.springframework.data.couchbase.core.query;
import static org.junit.jupiter.api.Assertions.*;
import static org.springframework.data.couchbase.core.query.QueryCriteria.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.springframework.data.couchbase.core.query.N1QLExpression.i;
import static org.springframework.data.couchbase.core.query.N1QLExpression.meta;
import static org.springframework.data.couchbase.core.query.N1QLExpression.path;
import static org.springframework.data.couchbase.core.query.N1QLExpression.x;
import static org.springframework.data.couchbase.core.query.QueryCriteria.where;
import static org.springframework.data.couchbase.repository.query.support.N1qlUtils.escapedBucket;
import org.junit.jupiter.api.Test;
import com.couchbase.client.java.json.JsonArray;
import com.couchbase.client.java.json.JsonObject;
import org.junit.jupiter.api.Test;
import org.springframework.data.couchbase.domain.User;
import org.springframework.data.couchbase.domain.UserRepository;
import org.springframework.data.couchbase.repository.query.N1qlQueryCreator;
import org.springframework.data.repository.query.parser.PartTree;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
/**
* @author Mauro Monti
*/
class QueryCriteriaTests {
@Test
void testSimpleCriteria() {
QueryCriteria c = where("name").is("Bubba");
QueryCriteria c = where(i("name")).is("Bubba");
assertEquals("`name` = \"Bubba\"", c.export());
}
@Test
public void testNullValue() {
QueryCriteria c = where("name").is(null);
QueryCriteria c = where(i("name")).is(null);
assertEquals("`name` = null", c.export());
}
@Test
void testSimpleNumber() {
QueryCriteria c = where("name").is(5);
QueryCriteria c = where(i("name")).is(5);
assertEquals("`name` = 5", c.export());
}
@Test
void testNotEqualCriteria() {
QueryCriteria c = where("name").ne("Bubba");
QueryCriteria c = where(i("name")).ne("Bubba");
assertEquals("`name` != \"Bubba\"", c.export());
}
@Test
void testChainedCriteria() {
QueryCriteria c = where("name").is("Bubba").and("age").lt(21).or("country").is("Austria");
QueryCriteria c = where(i("name")).is("Bubba").and(i("age")).lt(21).or(i("country")).is("Austria");
assertEquals("`name` = \"Bubba\" and `age` < 21 or `country` = \"Austria\"", c.export());
}
@Test
void testNestedAndCriteria() {
QueryCriteria c = where("name").is("Bubba").and(where("age").gt(12).or("country").is("Austria"));
QueryCriteria c = where(i("name")).is("Bubba").and(where(i("age")).gt(12).or(i("country")).is("Austria"));
JsonArray parameters = JsonArray.create();
assertEquals("`name` = $1 and (`age` > $2 or `country` = $3)", c.export(new int[1], parameters, null));
assertEquals("[\"Bubba\",12,\"Austria\"]", parameters.toString());
@@ -74,7 +73,7 @@ class QueryCriteriaTests {
@Test
void testNestedOrCriteria() {
QueryCriteria c = where("name").is("Bubba").or(where("age").gt(12).or("country").is("Austria"));
QueryCriteria c = where(i("name")).is("Bubba").or(where(i("age")).gt(12).or(i("country")).is("Austria"));
JsonArray parameters = JsonArray.create();
assertEquals("`name` = $1 or (`age` > $2 or `country` = $3)", c.export(new int[1], parameters, null));
assertEquals("[\"Bubba\",12,\"Austria\"]", parameters.toString());
@@ -82,45 +81,45 @@ class QueryCriteriaTests {
@Test
void testNestedNotIn() {
QueryCriteria c = where("name").is("Bubba").or(where("age").gt(12).or("country").is("Austria"))
.and(where("state").notIn(new String[] { "Alabama", "Florida" }));
QueryCriteria c = where(i("name")).is("Bubba").or(where(i("age")).gt(12).or(i("country")).is("Austria"))
.and(where(i("state")).notIn(new String[] { "Alabama", "Florida" }));
assertEquals("`name` = \"Bubba\" or (`age` > 12 or `country` = \"Austria\") and "
+ "(not( (`state` in ( [\"Alabama\",\"Florida\"] )) ))", c.export());
}
@Test
void testLt() {
QueryCriteria c = where("name").lt("Couch");
QueryCriteria c = where(i("name")).lt("Couch");
assertEquals("`name` < \"Couch\"", c.export());
}
@Test
void testLte() {
QueryCriteria c = where("name").lte("Couch");
QueryCriteria c = where(i("name")).lte("Couch");
assertEquals("`name` <= \"Couch\"", c.export());
}
@Test
void testGt() {
QueryCriteria c = where("name").gt("Couch");
QueryCriteria c = where(i("name")).gt("Couch");
assertEquals("`name` > \"Couch\"", c.export());
}
@Test
void testGte() {
QueryCriteria c = where("name").gte("Couch");
QueryCriteria c = where(i("name")).gte("Couch");
assertEquals("`name` >= \"Couch\"", c.export());
}
@Test
void testNe() {
QueryCriteria c = where("name").ne("Couch");
QueryCriteria c = where(i("name")).ne("Couch");
assertEquals("`name` != \"Couch\"", c.export());
}
@Test
void testStartingWith() {
QueryCriteria c = where("name").startingWith("Cou");
QueryCriteria c = where(i("name")).startingWith("Cou");
assertEquals("`name` like (\"Cou\"||\"%\")", c.export());
}
@@ -128,99 +127,99 @@ class QueryCriteriaTests {
* startingWith() cannot be a QueryCriteria
@Test
void testStartingWithExpr() {
QueryCriteria c = where("name").startingWith(where("name").plus("xxx"));
QueryCriteria c = where(i("name")).startingWith(where(i("name")).plus("xxx"));
assertEquals("`name` like (((`name` || "xxx") || ""%""))", c.export());
}
*/
@Test
void testEndingWith() {
QueryCriteria c = where("name").endingWith("ouch");
QueryCriteria c = where(i("name")).endingWith("ouch");
assertEquals("`name` like (\"%\"||\"ouch\")", c.export());
}
@Test
void testEndingWithExpr() {
QueryCriteria c = where("name").endingWith(where("name").plus("xxx"));
QueryCriteria c = where(i("name")).endingWith(where(i("name")).plus("xxx"));
assertEquals("`name` like (\"%\"||((`name` || \"xxx\")))", c.export());
}
@Test
void testRegex() {
QueryCriteria c = where("name").regex("C.*h");
QueryCriteria c = where(i("name")).regex("C.*h");
assertEquals("regexp_like(`name`, \"C.*h\")", c.export());
}
@Test
void testContaining() {
QueryCriteria c = where("name").containing("ouch");
QueryCriteria c = where(i("name")).containing("ouch");
assertEquals("contains(`name`, \"ouch\")", c.export());
}
@Test
void testNotContaining() {
QueryCriteria c = where("name").notContaining("Elvis");
QueryCriteria c = where(i("name")).notContaining("Elvis");
assertEquals("not( (contains(`name`, \"Elvis\")) )", c.export());
}
@Test
void testLike() {
QueryCriteria c = where("name").like("%ouch%");
QueryCriteria c = where(i("name")).like("%ouch%");
assertEquals("`name` like \"%ouch%\"", c.export());
}
@Test
void testNotLike() {
QueryCriteria c = where("name").notLike("%Elvis%");
QueryCriteria c = where(i("name")).notLike("%Elvis%");
assertEquals("not(`name` like \"%Elvis%\")", c.export());
}
@Test
void testIsNull() {
QueryCriteria c = where("name").isNull();
QueryCriteria c = where(i("name")).isNull();
assertEquals("`name` is null", c.export());
}
@Test
void testIsNotNull() {
QueryCriteria c = where("name").isNotNull();
QueryCriteria c = where(i("name")).isNotNull();
assertEquals("`name` is not null", c.export());
}
@Test
void testIsMissing() {
QueryCriteria c = where("name").isMissing();
QueryCriteria c = where(i("name")).isMissing();
assertEquals("`name` is missing", c.export());
}
@Test
void testIsNotMissing() {
QueryCriteria c = where("name").isNotMissing();
QueryCriteria c = where(i("name")).isNotMissing();
assertEquals("`name` is not missing", c.export());
}
@Test
void testIsValued() {
QueryCriteria c = where("name").isValued();
QueryCriteria c = where(i("name")).isValued();
assertEquals("`name` is valued", c.export());
}
@Test
void testIsNotValued() {
QueryCriteria c = where("name").isNotValued();
QueryCriteria c = where(i("name")).isNotValued();
assertEquals("`name` is not valued", c.export());
}
@Test
void testBetween() {
QueryCriteria c = where("name").between("Davis", "Gump");
QueryCriteria c = where(i("name")).between("Davis", "Gump");
assertEquals("`name` between \"Davis\" and \"Gump\"", c.export());
}
@Test
void testIn() {
String[] args = new String[] { "gump", "davis" };
QueryCriteria c = where("name").in(args);
QueryCriteria c = where(i("name")).in(args);
assertEquals("`name` in ( [\"gump\",\"davis\"] )", c.export());
JsonArray parameters = JsonArray.create();
assertEquals("`name` in ( $1 )", c.export(new int[1], parameters, null));
@@ -230,7 +229,7 @@ class QueryCriteriaTests {
@Test
void testNotIn() {
String[] args = new String[] { "gump", "davis" };
QueryCriteria c = where("name").notIn(args);
QueryCriteria c = where(i("name")).notIn(args);
assertEquals("not( (`name` in ( [\"gump\",\"davis\"] )) )", c.export());
JsonArray parameters = JsonArray.create();
assertEquals("not( (`name` in ( $1 )) )", c.export(new int[1], parameters, null));
@@ -239,16 +238,32 @@ class QueryCriteriaTests {
@Test
void testTrue() {
QueryCriteria c = where("name").TRUE();
QueryCriteria c = where(i("name")).TRUE();
assertEquals("`name`", c.export());
}
@Test
void testFalse() {
QueryCriteria c = where("name").FALSE();
QueryCriteria c = where(i("name")).FALSE();
assertEquals("not( (`name`) )", c.export());
}
@Test // https://github.com/spring-projects/spring-data-couchbase/issues/1066
void testCriteriaCorrectlyEscapedWhenUsingMetaOnLHS() {
final String bucketName = "sample-bucket";
final String version = "1611287177404088320";
QueryCriteria criteria = QueryCriteria.where(path(meta(escapedBucket(bucketName)), "cas")).eq(x(version));
assertEquals("META(`" + bucketName + "`).cas = " + x(version), criteria.export());
}
@Test // https://github.com/spring-projects/spring-data-couchbase/issues/1066
void testCriteriaCorrectlyEscapedWhenUsingMetaOnRHS() {
final String bucketName = "sample-bucket";
final String version = "1611287177404088320";
QueryCriteria criteria = QueryCriteria.where(x(version)).eq(path(meta(escapedBucket(bucketName)), "cas"));
assertEquals(x(version) + " = META(`" + bucketName + "`).cas", criteria.export());
}
private String arrayToString(Object[] array) {
StringBuilder sb = new StringBuilder();
if (array != null) {

View File

@@ -19,12 +19,12 @@ package org.springframework.data.couchbase.domain;
import java.util.List;
import org.springframework.data.couchbase.repository.CouchbaseRepository;
import com.couchbase.client.java.json.JsonArray;
import org.springframework.data.couchbase.repository.Query;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import com.couchbase.client.java.json.JsonArray;
/**
* User Repository for tests
*
@@ -47,4 +47,8 @@ public interface UserRepository extends CouchbaseRepository<User, String> {
@Query("#{#n1ql.selectEntity} where #{#n1ql.filter} and (firstname = $first or lastname = $last)")
List<User> getByFirstnameOrLastname(@Param("first") String firstname, @Param("last") String lastname);
List<User> findByIdIsNotNullAndFirstnameEquals(String firstname);
List<User> findByVersionEqualsAndFirstnameEquals(Long version, String firstname);
}

View File

@@ -16,9 +16,12 @@
package org.springframework.data.couchbase.repository;
import static java.util.Arrays.*;
import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.*;
import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.ArrayList;
import java.util.Arrays;
@@ -49,14 +52,12 @@ import org.springframework.data.couchbase.util.Capabilities;
import org.springframework.data.couchbase.util.ClusterAwareIntegrationTests;
import org.springframework.data.couchbase.util.ClusterType;
import org.springframework.data.couchbase.util.IgnoreWhen;
import org.springframework.data.util.StreamUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import com.couchbase.client.core.error.IndexExistsException;
import reactor.core.publisher.Flux;
/**
* Repository tests
@@ -173,7 +174,9 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
try {
airportRepository.saveAll( Arrays.stream(iatas).map((iata) -> new Airport("airports::"+iata, iata, iata.toLowerCase(Locale.ROOT))).collect(Collectors.toSet()));
airportRepository.saveAll(
Arrays.stream(iatas).map((iata) -> new Airport("airports::" + iata, iata, iata.toLowerCase(Locale.ROOT)))
.collect(Collectors.toSet()));
Long count = airportRepository.countFancyExpression(asList("JFK"), asList("jfk"), false);
assertEquals(1, count);
@@ -198,7 +201,8 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
assertEquals(0, airportCount);
} finally {
airportRepository.deleteAllById(Arrays.stream(iatas).map((iata) -> "airports::"+iata).collect(Collectors.toSet()));
airportRepository
.deleteAllById(Arrays.stream(iatas).map((iata) -> "airports::" + iata).collect(Collectors.toSet()));
}
}
@@ -295,7 +299,6 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
airportRepository.deleteAllById(asList(vienna.getId(), losAngeles.getId()));
assertThat(airportRepository.findAll()).containsExactly(frankfurt);
} finally {
airportRepository.deleteAll();
@@ -305,9 +308,7 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
private void sleep(int millis) {
try {
Thread.sleep(millis); // so they are executed out-of-order
} catch (InterruptedException ie) {
;
}
} catch (InterruptedException ie) {}
}
@Configuration

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017-2019 the original author or authors.
* Copyright 2017-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,17 +15,15 @@
*/
package org.springframework.data.couchbase.repository.query;
import static org.junit.jupiter.api.Assertions.*;
import static org.springframework.data.couchbase.core.query.QueryCriteria.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.springframework.data.couchbase.core.query.N1QLExpression.i;
import static org.springframework.data.couchbase.core.query.N1QLExpression.x;
import static org.springframework.data.couchbase.core.query.QueryCriteria.where;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import com.couchbase.client.java.json.JsonArray;
import com.couchbase.client.java.json.JsonObject;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
@@ -37,22 +35,31 @@ import org.springframework.data.couchbase.core.query.Query;
import org.springframework.data.couchbase.domain.User;
import org.springframework.data.couchbase.domain.UserRepository;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.repository.query.*;
import org.springframework.data.repository.query.DefaultParameters;
import org.springframework.data.repository.query.ParameterAccessor;
import org.springframework.data.repository.query.Parameters;
import org.springframework.data.repository.query.ParametersParameterAccessor;
import org.springframework.data.repository.query.parser.PartTree;
import com.couchbase.client.java.json.JsonArray;
import com.couchbase.client.java.json.JsonObject;
/**
* @author Michael Nitschinger
* @author Michael Reiche
* @author Mauro Monti
*/
class N1qlQueryCreatorTests {
MappingContext<? extends CouchbasePersistentEntity<?>, CouchbasePersistentProperty> context;
CouchbaseConverter converter;
String bucketName;
@BeforeEach
public void beforeEach() {
context = new CouchbaseMappingContext();
converter = new MappingCouchbaseConverter(context);
bucketName = "sample-bucket";
}
@Test
@@ -61,11 +68,11 @@ class N1qlQueryCreatorTests {
PartTree tree = new PartTree(input, User.class);
Method method = UserRepository.class.getMethod(input, String.class);
N1qlQueryCreator creator = new N1qlQueryCreator(tree, getAccessor(getParameters(method), "Oliver"), null,
converter);
N1qlQueryCreator creator = new N1qlQueryCreator(tree, getAccessor(getParameters(method), "Oliver"), null, converter,
bucketName);
Query query = creator.createQuery();
assertEquals(query.export(), " WHERE " + where("firstname").is("Oliver").export());
assertEquals(query.export(), " WHERE " + where(i("firstname")).is("Oliver").export());
}
@Test
@@ -73,9 +80,10 @@ class N1qlQueryCreatorTests {
String input = "findByFirstnameIn";
PartTree tree = new PartTree(input, User.class);
Method method = UserRepository.class.getMethod(input, String[].class);
Query expected = (new Query()).addCriteria(where("firstname").in("Oliver", "Charles"));
Query expected = (new Query()).addCriteria(where(i("firstname")).in("Oliver", "Charles"));
N1qlQueryCreator creator = new N1qlQueryCreator(tree,
getAccessor(getParameters(method), new Object[] { new Object[] { "Oliver", "Charles" } }), null, converter);
getAccessor(getParameters(method), new Object[] { new Object[] { "Oliver", "Charles" } }), null, converter,
bucketName);
Query query = creator.createQuery();
// Query expected = (new Query()).addCriteria(where("firstname").in("Oliver", "Charles"));
@@ -97,10 +105,10 @@ class N1qlQueryCreatorTests {
jsonArray.add("Oliver");
jsonArray.add("Charles");
N1qlQueryCreator creator = new N1qlQueryCreator(tree, getAccessor(getParameters(method), jsonArray), null,
converter);
converter, bucketName);
Query query = creator.createQuery();
Query expected = (new Query()).addCriteria(where("firstname").in("Oliver", "Charles"));
Query expected = (new Query()).addCriteria(where(i("firstname")).in("Oliver", "Charles"));
assertEquals(expected.export(new int[1]), query.export(new int[1]));
JsonObject expectedOptions = JsonObject.create();
expected.buildQueryOptions(null).build().injectParams(expectedOptions);
@@ -118,10 +126,10 @@ class N1qlQueryCreatorTests {
list.add("Oliver");
list.add("Charles");
N1qlQueryCreator creator = new N1qlQueryCreator(tree, getAccessor(getParameters(method), new Object[] { list }),
null, converter);
null, converter, bucketName);
Query query = creator.createQuery();
Query expected = (new Query()).addCriteria(where("firstname").in("Oliver", "Charles"));
Query expected = (new Query()).addCriteria(where(i("firstname")).in("Oliver", "Charles"));
assertEquals(expected.export(new int[1]), query.export(new int[1]));
JsonObject expectedOptions = JsonObject.create();
@@ -137,10 +145,38 @@ class N1qlQueryCreatorTests {
PartTree tree = new PartTree(input, User.class);
Method method = UserRepository.class.getMethod(input, String.class, String.class);
N1qlQueryCreator creator = new N1qlQueryCreator(tree, getAccessor(getParameters(method), "John", "Doe"), null,
converter);
converter, bucketName);
Query query = creator.createQuery();
assertEquals(" WHERE " + where("firstname").is("John").and("lastname").is("Doe").export(), query.export());
assertEquals(" WHERE " + where(i("firstname")).is("John").and(i("lastname")).is("Doe").export(), query.export());
}
@Test // https://github.com/spring-projects/spring-data-couchbase/issues/1072
void createsQueryFindByIdIsNotNullAndFirstname() throws Exception {
String input = "findByIdIsNotNullAndFirstnameEquals";
PartTree tree = new PartTree(input, User.class);
Method method = UserRepository.class.getMethod(input, String.class);
N1qlQueryCreator creator = new N1qlQueryCreator(tree, getAccessor(getParameters(method), "Oliver"), null, converter,
bucketName);
Query query = creator.createQuery();
assertEquals(query.export(),
" WHERE " + where(x("META(`" + bucketName + "`).`id`")).isNotNull().and(i("firstname")).is("Oliver").export());
}
@Test // https://github.com/spring-projects/spring-data-couchbase/issues/1072
void createsQueryFindByVersionEqualsAndAndFirstname() throws Exception {
String input = "findByVersionEqualsAndFirstnameEquals";
PartTree tree = new PartTree(input, User.class);
Method method = UserRepository.class.getMethod(input, Long.class, String.class);
N1qlQueryCreator creator = new N1qlQueryCreator(tree,
getAccessor(getParameters(method), 1611287177404088320L, "Oliver"), null, converter, bucketName);
Query query = creator.createQuery();
assertEquals(query.export(), " WHERE " + where(x("META(`" + bucketName + "`).`cas`")).is(1611287177404088320L)
.and(i("firstname")).is("Oliver").export());
}
private ParameterAccessor getAccessor(Parameters<?, ?> params, Object... values) {