DATACOUCH - 534 Add support for Query annotation

This commit is contained in:
mikereiche
2020-05-10 01:50:13 -07:00
parent 3f3bd9d1a8
commit 749c0a84e8
26 changed files with 1315 additions and 254 deletions

View File

@@ -67,8 +67,8 @@ 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("name").is("Bubba").or(where("age").gt(12).or("country").is("Austria")).and(
where("state").notIn(new String[] { "Alabama", "Florida" }));
assertEquals("`name` = \"Bubba\" or (`age` > 12 or `country` = \"Austria\") and "
+ "(not( (`state` in ( [ \"Alabama\", \"Florida\" ] )) ))", c.export());
}
@@ -106,19 +106,28 @@ class QueryCriteriaTests {
@Test
void testStartingWith() {
QueryCriteria c = where("name").startingWith("Cou");
assertEquals("`name` like \"Cou%\"", c.export());
assertEquals("`name` like (\"Cou\"||\"%\")", c.export());
}
/* cannot do this properly yet because in arg to when() in
* startingWith() cannot be a QueryCriteria
@Test
void testStartingWithExpr() {
QueryCriteria c = where("name").startingWith(where("name").plus(""));
assertEquals("`name` like ((\"%\" + ((`name` + \"\"))))", c.export());
QueryCriteria c = where("name").startingWith(where("name").plus("xxx"));
assertEquals("`name` like (((`name` || "xxx") || ""%""))", c.export());
}
*/
@Test
void testEndingWith() {
QueryCriteria c = where("name").endingWith("ouch");
assertEquals("`name` like \"%ouch\"", c.export());
assertEquals("`name` like (\"%\"||\"ouch\")", c.export());
}
@Test
void testEndingWithExpr() {
QueryCriteria c = where("name").endingWith(where("name").plus("xxx"));
assertEquals("`name` like (\"%\"||((`name` || \"xxx\")))", c.export());
}
@Test
@@ -148,7 +157,7 @@ class QueryCriteriaTests {
@Test
void testNotLike() {
QueryCriteria c = where("name").notLike("%Elvis%");
assertEquals("not( (`name` like \"%Elvis%\") )", c.export());
assertEquals("not(`name` like \"%Elvis%\")", c.export());
}
@Test

View File

@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.domain;
import org.springframework.context.annotation.Bean;
@@ -23,6 +24,11 @@ import org.springframework.data.couchbase.domain.time.AuditingDateTimeProvider;
import org.springframework.data.couchbase.repository.auditing.EnableCouchbaseAuditing;
import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories;
/**
* @author Michael Nitschinger
* @author Michael Reiche
* @since 3.0
*/
@Configuration
@EnableCouchbaseRepositories
@EnableCouchbaseAuditing // this activates auditing
@@ -32,23 +38,69 @@ public class Config extends AbstractCouchbaseConfiguration {
String password = "password";
String connectionString = "127.0.0.1";
// if running a clusterAwareIntegrationTests, use those properties
static Class clusterAware = null;
static {
try {
clusterAware = Class.forName("org.springframework.data.couchbase.util.ClusterAwareIntegrationTests");
} catch (ClassNotFoundException cnfe) {
}
}
@Override
public String getConnectionString() {
if (clusterAware != null) {
try {
if (clusterAware.getMethod("config").invoke(null, (Object[]) null) != null) {
return (String) clusterAware.getMethod("connectionString").invoke(null, (Object[]) null);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
return connectionString;
}
@Override
public String getUserName() {
if (clusterAware != null) {
try {
if (clusterAware.getMethod("config").invoke(null, (Object[]) null) != null) {
return (String) clusterAware.getMethod("username").invoke(null, (Object[]) null);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
return username;
}
@Override
public String getPassword() {
if (clusterAware != null) {
try {
if (clusterAware.getMethod("config").invoke(null, (Object[]) null) != null) {
return (String) clusterAware.getMethod("password").invoke(null, (Object[]) null);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
return password;
}
@Override
public String getBucketName() {
if (clusterAware != null) {
try {
if (clusterAware.getMethod("config").invoke(null, (Object[]) null) != null) {
return (String) clusterAware.getMethod("bucketName").invoke(null, (Object[]) null);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
return bucketname;
}

View File

@@ -30,17 +30,23 @@ public class Person extends AbstractEntity {
Optional<String> firstname;
Optional<String> lastname;
@CreatedBy private String creator;
@CreatedBy
private String creator;
@LastModifiedBy private String lastModifiedBy;
@LastModifiedBy
private String lastModifiedBy;
@LastModifiedDate private long lastModification;
@LastModifiedDate
private long lastModification;
@CreatedDate private long creationDate; // =System.currentTimeMillis();
@CreatedDate
private long creationDate; // =System.currentTimeMillis();
@Version private long version;
@Version
private long version;
public Person() {}
public Person() {
}
public Person(String firstname, String lastname) {
this();
@@ -54,11 +60,13 @@ public class Person extends AbstractEntity {
}
static String optional(String name, Optional<String> obj) {
if (obj != null)
if (obj.isPresent())
if (obj != null) {
if (obj.isPresent()) {
return (" " + name + ": '" + obj.get() + "'\n");
else
} else {
return " " + name + ": null\n";
}
}
return "";
}
@@ -86,6 +94,10 @@ public class Person extends AbstractEntity {
this.lastname = lastname;
}
public long getVersion() {
return version;
}
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Person : {\n");
@@ -93,15 +105,31 @@ public class Person extends AbstractEntity {
sb.append(optional(", firstname", firstname));
sb.append(optional(", lastname", lastname));
sb.append(", version : " + version);
if (creator != null)
if (creator != null) {
sb.append(", creator : " + creator);
if (creationDate != 0)
}
if (creationDate != 0) {
sb.append(", creationDate : " + creationDate);
if (lastModifiedBy != null)
}
if (lastModifiedBy != null) {
sb.append(", lastModifiedBy : " + lastModifiedBy);
if (lastModification != 0)
}
if (lastModification != 0) {
sb.append(", lastModification : " + lastModification);
}
sb.append("}");
return sb.toString();
}
static String optional(String name, String obj) {
if (obj != null) {
if (obj != null /*.isPresent() */) {
return (" " + name + ": '" + obj/*.get()*/ + "'\n");
} else {
return " " + name + ": null\n";
}
}
return "";
}
}

View File

@@ -0,0 +1,108 @@
/*
* Copyright 2012-2020 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.
* 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 org.springframework.data.couchbase.domain;
import org.springframework.data.couchbase.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
/**
* @author Michael Reiche
*/
public interface PersonRepository extends CrudRepository<Person, String> {
/*
* These methods are exercised in HomeController of the test spring-boot DemoApplication
*/
public List<Person> findByLastname(String lastname);
@Query("#{#n1ql.selectEntity} where firstname = 'Reba' and lastname = $last")
public List<Person> any(@Param("last") String any);
@Query("#{#n1ql.selectEntity} where firstname = 'Reba' and lastname = 'McIntyre'")
public List<Person> none();
@Query("#{#n1ql.selectEntity} where firstname = 'Reba' and lastname = $1")
public List<Person> one(@Param("last") String any);
@Query("#{#n1ql.selectEntity} where lastname = $2 and firstname = $1")
public List<Person> two(@Param("one") String one, @Param("two") String two);
@Query("#{#n1ql.selectEntity} where lastname in ( $1 )")
public List<Person> lastnameIn(@Param("lastnames") String[] lastnames);
public Person findByFirstname(String firstname);
public Person findFromReplicasByFirstname(String firstname);
public List<Person> findFromReplicasById(String id);
public List<Person> findByFirstnameLike(String firstname);
public List<Person> findByFirstnameIsNull();
public List<Person> findByFirstnameIsNotNull();
public List<Person> findByFirstnameNotLike(String firstname);
public List<Person> findByFirstnameStartingWith(String firstname);
public List<Person> findByFirstnameEndingWith(String firstname);
public List<Person> findByFirstnameContaining(String firstname);
public List<Person> findByFirstnameNotContaining(String firstname);
public List<Person> findByFirstnameBetween(String firstname1, String firstname2);
public List<Person> findByFirstnameIn(String... firstnames);
public List<Person> findByFirstnameNotIn(String... firstnames);
public List<Person> findByFirstnameTrue(Object... o);
public List<Person> findByFirstnameFalse(Object... o);
List<Person> findByFirstnameAndLastname(String firstname, String lastname);
List<Person> findByFirstnameOrLastname(String firstname, String lastname);
<S extends Person> S save(S var1);
<S extends Person> Iterable<S> saveAll(Iterable<S> var1);
Optional<Person> findById(UUID var1);
boolean existsById(UUID var1);
Iterable<Person> findAll();
long count();
void deleteById(UUID var1);
void delete(Person var1);
void deleteAll(Iterable<? extends Person> var1);
void deleteAll();
}

View File

@@ -18,7 +18,9 @@ package org.springframework.data.couchbase.domain;
import java.util.List;
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;
/**
@@ -34,4 +36,9 @@ public interface UserRepository extends PagingAndSortingRepository<User, String>
List<User> findByFirstnameAndLastname(String firstname, String lastname);
@Query("#{#n1ql.selectEntity} where firstname = $1 and lastname = $2")
List<User> getByFirstnameAndLastname(String firstname, String lastname);
@Query("#{#n1ql.selectEntity} where (firstname = $first or lastname = $last)")
List<User> getByFirstnameOrLastname(@Param("first")String firstname, @Param("last")String lastname);
}

View File

@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.repository.query;
import static org.junit.jupiter.api.Assertions.*;
@@ -32,12 +31,13 @@ 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.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.*;
import org.springframework.data.repository.query.parser.PartTree;
/**
* @author Michael Nitschinger
* @author Michael Reiche
*/
class N1qlQueryCreatorTests {
MappingContext<? extends CouchbasePersistentEntity<?>, CouchbasePersistentProperty> context;
@@ -55,7 +55,8 @@ 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"), context);
N1qlQueryCreator creator = new N1qlQueryCreator(tree, getAccessor(getParameters(method), "Oliver"), null,
converter);
Query query = creator.createQuery();
assertEquals(query.export(), " WHERE " + where("firstname").is("Oliver").export());
@@ -66,8 +67,8 @@ class N1qlQueryCreatorTests {
String input = "findByFirstnameAndLastname";
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"), context);
N1qlQueryCreator creator = new N1qlQueryCreator(tree, getAccessor(getParameters(method), "John", "Doe"), null,
converter);
Query query = creator.createQuery();
assertEquals(query.export(), " WHERE " + where("firstname").is("John").and("lastname").is("Doe").export());

View File

@@ -0,0 +1,182 @@
/*
* Copyright 2017-2019 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.
* 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 org.springframework.data.couchbase.repository.query;
import static org.junit.jupiter.api.Assertions.*;
import static org.springframework.data.couchbase.config.BeanNames.COUCHBASE_TEMPLATE;
import java.lang.reflect.Method;
import java.util.Properties;
import java.util.UUID;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.data.couchbase.core.CouchbaseTemplate;
import org.springframework.data.couchbase.core.ExecutableFindByQueryOperation;
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter;
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.Query;
import org.springframework.data.couchbase.domain.Config;
import org.springframework.data.couchbase.domain.User;
import org.springframework.data.couchbase.domain.UserRepository;
import org.springframework.data.couchbase.util.Capabilities;
import org.springframework.data.couchbase.util.ClusterType;
import org.springframework.data.couchbase.util.IgnoreWhen;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
import org.springframework.data.repository.core.support.PropertiesBasedNamedQueries;
import org.springframework.data.repository.query.*;
import javax.el.MethodNotFoundException;
/**
* @author Michael Nitschinger
* @author Michael Reiche
*/
class StringN1qlQueryCreatorTests {
MappingContext<? extends CouchbasePersistentEntity<?>, CouchbasePersistentProperty> context;
CouchbaseConverter converter;
CouchbaseTemplate couchbaseTemplate;
static NamedQueries namedQueries = new PropertiesBasedNamedQueries(new Properties());
@BeforeEach
public void beforeEach() {
context = new CouchbaseMappingContext();
converter = new MappingCouchbaseConverter(context);
ApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
couchbaseTemplate = (CouchbaseTemplate) ac.getBean(COUCHBASE_TEMPLATE);
}
@Test
void createsQueryCorrectly() throws Exception {
String input = "getByFirstnameAndLastname";
Method method = UserRepository.class.getMethod(input, String.class, String.class);
CouchbaseQueryMethod queryMethod = new CouchbaseQueryMethod(method,
new DefaultRepositoryMetadata(UserRepository.class), new SpelAwareProxyProjectionFactory(),
converter.getMappingContext());
StringN1qlQueryCreator creator = new StringN1qlQueryCreator(
getAccessor(getParameters(method), "Oliver", "Twist"), queryMethod, converter, "travel-sample",
QueryMethodEvaluationContextProvider.DEFAULT, namedQueries);
Query query = creator.createQuery();
assertEquals(
"SELECT META(`travel-sample`).id AS __id, META(`travel-sample`).cas AS __cas, `travel-sample`.* FROM `travel-sample` where firstname = $1 and lastname = $2 AND `_class` = \"org.springframework.data.couchbase.domain.User\"",
query.toN1qlString(couchbaseTemplate.reactive(), User.class, false));
}
@Test
void createsQueryCorrectly2() throws Exception {
String input = "getByFirstnameOrLastname";
Method method = UserRepository.class.getMethod(input, String.class, String.class);
CouchbaseQueryMethod queryMethod = new CouchbaseQueryMethod(method,
new DefaultRepositoryMetadata(UserRepository.class), new SpelAwareProxyProjectionFactory(),
converter.getMappingContext());
StringN1qlQueryCreator creator = new StringN1qlQueryCreator(
getAccessor(getParameters(method), "Oliver", "Twist"), queryMethod, converter, "travel-sample",
QueryMethodEvaluationContextProvider.DEFAULT, namedQueries);
Query query = creator.createQuery();
assertEquals(
"SELECT META(`travel-sample`).id AS __id, META(`travel-sample`).cas AS __cas, `travel-sample`.* FROM `travel-sample` where (firstname = $first or lastname = $last) AND `_class` = \"org.springframework.data.couchbase.domain.User\"",
query.toN1qlString(couchbaseTemplate.reactive(), User.class, false));
}
@Test
void wrongNumberArgs() throws Exception {
String input = "getByFirstnameOrLastname";
Method method = UserRepository.class.getMethod(input, String.class, String.class);
CouchbaseQueryMethod queryMethod = new CouchbaseQueryMethod(method,
new DefaultRepositoryMetadata(UserRepository.class), new SpelAwareProxyProjectionFactory(),
converter.getMappingContext());
try {
StringN1qlQueryCreator creator = new StringN1qlQueryCreator(getAccessor(getParameters(method), "Oliver"),
queryMethod, converter, "travel-sample", QueryMethodEvaluationContextProvider.DEFAULT,
namedQueries);
} catch (IllegalArgumentException e) {
return;
}
fail("should have failed with IllegalArgumentException: Invalid number of parameters given!");
}
@Test
void doesNotHaveAnnotation() throws Exception {
String input = "findByFirstname";
Method method = UserRepository.class.getMethod(input, String.class);
CouchbaseQueryMethod queryMethod = new CouchbaseQueryMethod(method,
new DefaultRepositoryMetadata(UserRepository.class), new SpelAwareProxyProjectionFactory(),
converter.getMappingContext());
try {
StringN1qlQueryCreator creator = new StringN1qlQueryCreator(getAccessor(getParameters(method), "Oliver"),
queryMethod, converter, "travel-sample", QueryMethodEvaluationContextProvider.DEFAULT,
namedQueries);
} catch (IllegalArgumentException e) {
return;
}
fail("should have failed with IllegalArgumentException: query has no inline Query or named Query not found");
}
@Test
@IgnoreWhen(missesCapabilities = Capabilities.QUERY, clusterTypes = ClusterType.MOCKED)
void findUsingStringNq1l() throws Exception {
User user = new User(UUID.randomUUID().toString(), "Oliver", "Twist");
User modified = couchbaseTemplate.upsertById(User.class).one(user);
String input = "getByFirstnameOrLastname";
Method method = UserRepository.class.getMethod(input, String.class, String.class);
CouchbaseQueryMethod queryMethod = new CouchbaseQueryMethod(method,
new DefaultRepositoryMetadata(UserRepository.class), new SpelAwareProxyProjectionFactory(),
converter.getMappingContext());
StringN1qlQueryCreator creator = new StringN1qlQueryCreator(
getAccessor(getParameters(method), "Oliver", "Twist"), queryMethod, converter, "travel-sample",
QueryMethodEvaluationContextProvider.DEFAULT, namedQueries);
Query query = creator.createQuery();
ExecutableFindByQueryOperation.ExecutableFindByQuery q = (ExecutableFindByQueryOperation.ExecutableFindByQuery) couchbaseTemplate.findByQuery(
User.class).matching(query);
User u = (User) q.oneValue();
assertEquals(user, u);
couchbaseTemplate.removeById().one(user.getId());
}
private ParameterAccessor getAccessor(Parameters<?, ?> params, Object... values) {
return new ParametersParameterAccessor(params, values);
}
private Parameters<?, ?> getParameters(Method method) {
return new DefaultParameters(method);
}
}

View File

@@ -52,6 +52,8 @@ public abstract class ClusterAwareIntegrationTests {
return PasswordAuthenticator.create(config().adminUsername(), config().adminPassword());
}
public static String username() { return config().adminUsername(); }
public static String password() { return config().adminPassword(); }
public static String bucketName() {
return config().bucketname();
}
@@ -62,13 +64,33 @@ public abstract class ClusterAwareIntegrationTests {
* @return the connection string to connect.
*/
public static String connectionString() {
/*
return seedNodes().stream().map(s -> {
if (s.kvPort().isPresent()) {
return s.address() + ":" + s.kvPort().get();
return s.address() + ":" + s.kvPort().get() + "=" + Services.KV;
} else if (s.clusterManagerPort().isPresent()) {
return s.address() + ":" + s.clusterManagerPort().get() + "=" + Services.MANAGER;
} else {
return s.address();
return s.address() ;
}
}).collect(Collectors.joining(","));
*/
StringBuffer sb = new StringBuffer();
for(SeedNode s:seedNodes()) {
if (s.kvPort().isPresent()) {
if(sb.length() > 0 ) sb.append(",");
sb.append (s.address() + ":" + s.kvPort().get() + "=" + Services.KV);
}
if (s.clusterManagerPort().isPresent()) {
if (sb.length() > 0)
sb.append(",");
sb.append(s.address() + ":" + s.clusterManagerPort().get() + "=" + Services.MANAGER);
}
if(sb.length() == 0 ){
sb.append(s.address());
}
}
return sb.toString();
}
public static Set<SeedNode> seedNodes() {