Port to jdk17 and spring 3.0.0 (#1279)

This also allows support of repository methods that return a simple type.
i.e.
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
@Query("SELECT iata, \"\" as __id, 0 as __cas from #{#n1ql.bucket} WHERE #{#n1ql.filter}")
List<String> getStrings();

Closes #1278.
This commit is contained in:
Michael Reiche
2021-12-09 15:50:52 -08:00
committed by GitHub
parent 9f2d7ee6b5
commit 49e7de06d8
25 changed files with 307 additions and 206 deletions

78
pom.xml
View File

@@ -1,11 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.data</groupId> <groupId>org.springframework.data</groupId>
<artifactId>spring-data-couchbase</artifactId> <artifactId>spring-data-couchbase</artifactId>
<version>4.3.0-SNAPSHOT</version> <version>5.0.0-SNAPSHOT</version>
<name>Spring Data Couchbase</name> <name>Spring Data Couchbase</name>
<description>Spring Data integration for Couchbase</description> <description>Spring Data integration for Couchbase</description>
@@ -14,14 +15,17 @@
<parent> <parent>
<groupId>org.springframework.data.build</groupId> <groupId>org.springframework.data.build</groupId>
<artifactId>spring-data-parent</artifactId> <artifactId>spring-data-parent</artifactId>
<version>2.6.0-SNAPSHOT</version> <version>3.0.0-SNAPSHOT</version>
</parent> </parent>
<properties> <properties>
<couchbase>3.2.1</couchbase> <couchbase>3.2.3</couchbase>
<couchbase.osgi>3.2.1</couchbase.osgi> <couchbase.osgi>3.2.3</couchbase.osgi>
<springdata.commons>2.6.0-SNAPSHOT</springdata.commons> <springdata.commons>3.0.0-SNAPSHOT</springdata.commons>
<java-module-name>spring.data.couchbase</java-module-name> <java-module-name>spring.data.couchbase</java-module-name>
<jodatime>2.10.13</jodatime>
<jakarta.validation>3.0.1</jakarta.validation>
<hibernate.validator>7.0.1.Final</hibernate.validator>
</properties> </properties>
<dependencyManagement> <dependencyManagement>
@@ -64,6 +68,13 @@
<version>${couchbase}</version> <version>${couchbase}</version>
</dependency> </dependency>
<!-- CDI -->
<dependency>
<groupId>jakarta.enterprise</groupId>
<artifactId>jakarta.enterprise.cdi-api</artifactId>
<version>3.0.0</version>
</dependency>
<dependency> <dependency>
<groupId>org.springframework</groupId> <groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId> <artifactId>spring-test</artifactId>
@@ -72,9 +83,9 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.hibernate</groupId> <groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId> <artifactId>hibernate-validator</artifactId>
<version>5.3.6.Final</version> <version>7.0.1.Final</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
@@ -112,24 +123,25 @@
<!-- JSR 303 Validation --> <!-- JSR 303 Validation -->
<dependency> <dependency>
<groupId>javax.validation</groupId> <groupId>jakarta.validation</groupId>
<artifactId>validation-api</artifactId> <artifactId>jakarta.validation-api</artifactId>
<version>${validation}</version> <version>${validation}</version>
</dependency>
<dependency>
<groupId>jakarta.el</groupId>
<artifactId>jakarta.el-api</artifactId>
<version>4.0.0</version>
<scope>provided</scope>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency>
<groupId>javax.el</groupId>
<artifactId>javax.el-api</artifactId>
<version>3.0.0</version>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.glassfish</groupId> <groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId> <artifactId>jakarta.el</artifactId>
<version>3.0.0</version> <version>4.0.2</version>
<scope>test</scope> <scope>provided</scope>
<optional>true</optional>
</dependency> </dependency>
<!-- CDI --> <!-- CDI -->
@@ -148,21 +160,6 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>javax.enterprise</groupId>
<artifactId>cdi-api</artifactId>
<version>${cdi}</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>${javax-annotation-api}</version>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.apache.openwebbeans</groupId> <groupId>org.apache.openwebbeans</groupId>
<artifactId>openwebbeans-se</artifactId> <artifactId>openwebbeans-se</artifactId>
@@ -279,17 +276,6 @@
</execution> </execution>
</executions> </executions>
</plugin> </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestEntries>
<Automatic-Module-Name>${java-module-name}</Automatic-Module-Name>
</manifestEntries>
</archive>
</configuration>
</plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId> <artifactId>maven-assembly-plugin</artifactId>

View File

@@ -17,6 +17,10 @@
package org.springframework.data.couchbase.core; package org.springframework.data.couchbase.core;
import java.lang.reflect.InaccessibleObjectException;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
@@ -40,6 +44,7 @@ import org.springframework.data.mapping.callback.EntityCallbacks;
import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.ConvertingPropertyAccessor; import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/** /**
* Internal encode/decode support for CouchbaseTemplate. * Internal encode/decode support for CouchbaseTemplate.
@@ -84,7 +89,18 @@ class CouchbaseTemplateSupport implements ApplicationContextAware, TemplateSuppo
public <T> T decodeEntity(String id, String source, long cas, Class<T> entityClass) { public <T> T decodeEntity(String id, String source, long cas, Class<T> entityClass) {
final CouchbaseDocument converted = new CouchbaseDocument(id); final CouchbaseDocument converted = new CouchbaseDocument(id);
converted.setId(id); converted.setId(id);
CouchbasePersistentEntity<?> persistentEntity = mappingContext.getRequiredPersistentEntity(entityClass); CouchbasePersistentEntity persistentEntity = couldBePersistentEntity(entityClass);
if (persistentEntity == null) { // method could return a Long, Boolean, String etc.
// QueryExecutionConverters.unwrapWrapperTypes will recursively unwrap until there is nothing left
// to unwrap. This results in List<String[]> being unwrapped past String[] to String, so this may also be a
// Collection (or Array) of entityClass. We have no way of knowing - so just assume it is what we are told.
// if this is a Collection or array, only the first element will be returned.
Set<Map.Entry<String, Object>> set = ((CouchbaseDocument) translationService.decode(source, converted))
.getContent().entrySet();
return (T) set.iterator().next().getValue();
}
if (cas != 0 && persistentEntity.getVersionProperty() != null) { if (cas != 0 && persistentEntity.getVersionProperty() != null) {
converted.put(persistentEntity.getVersionProperty().getName(), cas); converted.put(persistentEntity.getVersionProperty().getName(), cas);
} }
@@ -98,6 +114,17 @@ class CouchbaseTemplateSupport implements ApplicationContextAware, TemplateSuppo
N1qlJoinResolver.handleProperties(persistentEntity, accessor, template.reactive(), id); N1qlJoinResolver.handleProperties(persistentEntity, accessor, template.reactive(), id);
return accessor.getBean(); return accessor.getBean();
} }
CouchbasePersistentEntity couldBePersistentEntity(Class<?> entityClass) {
if (ClassUtils.isPrimitiveOrWrapper(entityClass) || entityClass == String.class) {
return null;
}
try {
return mappingContext.getPersistentEntity(entityClass);
} catch (InaccessibleObjectException t) {
}
return null;
}
@Override @Override
public Object applyUpdatedCas(final Object entity, CouchbaseDocument converted, final long cas) { public Object applyUpdatedCas(final Object entity, CouchbaseDocument converted, final long cas) {

View File

@@ -18,6 +18,10 @@ package org.springframework.data.couchbase.core;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import java.lang.reflect.InaccessibleObjectException;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
@@ -42,6 +46,7 @@ import org.springframework.data.mapping.callback.ReactiveEntityCallbacks;
import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.ConvertingPropertyAccessor; import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/** /**
* Internal encode/decode support for {@link ReactiveCouchbaseTemplate}. * Internal encode/decode support for {@link ReactiveCouchbaseTemplate}.
@@ -84,7 +89,19 @@ class ReactiveCouchbaseTemplateSupport implements ApplicationContextAware, React
return Mono.fromSupplier(() -> { return Mono.fromSupplier(() -> {
final CouchbaseDocument converted = new CouchbaseDocument(id); final CouchbaseDocument converted = new CouchbaseDocument(id);
converted.setId(id); converted.setId(id);
CouchbasePersistentEntity<?> persistentEntity = mappingContext.getRequiredPersistentEntity(entityClass);
CouchbasePersistentEntity persistentEntity = couldBePersistentEntity(entityClass);
if (persistentEntity == null) { // method could return a Long, Boolean, String etc.
// QueryExecutionConverters.unwrapWrapperTypes will recursively unwrap until there is nothing left
// to unwrap. This results in List<String[]> being unwrapped past String[] to String, so this may also be a
// Collection (or Array) of entityClass. We have no way of knowing - so just assume it is what we are told.
// if this is a Collection or array, only the first element will be returned.
Set<Map.Entry<String, Object>> set = ((CouchbaseDocument) translationService.decode(source, converted))
.getContent().entrySet();
return (T) set.iterator().next().getValue();
}
if (cas != 0 && persistentEntity.getVersionProperty() != null) { if (cas != 0 && persistentEntity.getVersionProperty() != null) {
converted.put(persistentEntity.getVersionProperty().getName(), cas); converted.put(persistentEntity.getVersionProperty().getName(), cas);
} }
@@ -100,6 +117,18 @@ class ReactiveCouchbaseTemplateSupport implements ApplicationContextAware, React
}); });
} }
CouchbasePersistentEntity couldBePersistentEntity(Class<?> entityClass) {
if (ClassUtils.isPrimitiveOrWrapper(entityClass) || entityClass == String.class) {
return null;
}
try {
return mappingContext.getPersistentEntity(entityClass);
} catch (InaccessibleObjectException t) {
}
return null;
}
@Override @Override
public Mono<Object> applyUpdatedCas(final Object entity, CouchbaseDocument converted, final long cas) { public Mono<Object> applyUpdatedCas(final Object entity, CouchbaseDocument converted, final long cas) {
return Mono.fromSupplier(() -> { return Mono.fromSupplier(() -> {

View File

@@ -22,7 +22,7 @@ import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.GenericConversionService; import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.data.convert.CustomConversions; import org.springframework.data.convert.CustomConversions;
import org.springframework.data.convert.EntityInstantiators; import org.springframework.data.mapping.model.EntityInstantiators;
/** /**
* An abstract {@link CouchbaseConverter} that provides the basics for the {@link MappingCouchbaseConverter}. * An abstract {@link CouchbaseConverter} that provides the basics for the {@link MappingCouchbaseConverter}.

View File

@@ -27,6 +27,7 @@ import java.time.LocalDateTime;
import java.time.LocalTime; import java.time.LocalTime;
import java.time.Period; import java.time.Period;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Date; import java.util.Date;
@@ -71,6 +72,9 @@ public final class CouchbaseJsr310Converters {
converters.add(StringToDurationConverter.INSTANCE); converters.add(StringToDurationConverter.INSTANCE);
converters.add(PeriodToStringConverter.INSTANCE); converters.add(PeriodToStringConverter.INSTANCE);
converters.add(StringToPeriodConverter.INSTANCE); converters.add(StringToPeriodConverter.INSTANCE);
converters.add(ZonedDateTimeToLongConverter.INSTANCE);
converters.add(NumberToZonedDateTimeConverter.INSTANCE);
return converters; return converters;
} }
@@ -99,6 +103,31 @@ public final class CouchbaseJsr310Converters {
} }
} }
@ReadingConverter
public enum NumberToZonedDateTimeConverter implements Converter<Number, ZonedDateTime> {
INSTANCE;
@Override
public ZonedDateTime convert(Number source) {
return source == null ? null
: ZonedDateTime.ofInstant(DateConverters.SerializedObjectToDateConverter.INSTANCE.convert(source).toInstant(),
systemDefault());
}
}
@WritingConverter
public enum ZonedDateTimeToLongConverter implements Converter<ZonedDateTime, Long> {
INSTANCE;
@Override
public Long convert(ZonedDateTime source) {
return source == null ? null
: DateConverters.DateToLongConverter.INSTANCE.convert(Date.from(source.toInstant()));
}
}
@ReadingConverter @ReadingConverter
public enum NumberToLocalDateConverter implements Converter<Number, LocalDate> { public enum NumberToLocalDateConverter implements Converter<Number, LocalDate> {

View File

@@ -36,7 +36,6 @@ import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.data.annotation.Transient; import org.springframework.data.annotation.Transient;
import org.springframework.data.convert.CustomConversions; import org.springframework.data.convert.CustomConversions;
import org.springframework.data.convert.EntityInstantiator;
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
import org.springframework.data.couchbase.core.mapping.CouchbaseList; import org.springframework.data.couchbase.core.mapping.CouchbaseList;
import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext; import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext;
@@ -60,6 +59,7 @@ import org.springframework.data.mapping.callback.EntityCallbacks;
import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.ConvertingPropertyAccessor; import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
import org.springframework.data.mapping.model.DefaultSpELExpressionEvaluator; import org.springframework.data.mapping.model.DefaultSpELExpressionEvaluator;
import org.springframework.data.mapping.model.EntityInstantiator;
import org.springframework.data.mapping.model.ParameterValueProvider; import org.springframework.data.mapping.model.ParameterValueProvider;
import org.springframework.data.mapping.model.PersistentEntityParameterValueProvider; import org.springframework.data.mapping.model.PersistentEntityParameterValueProvider;
import org.springframework.data.mapping.model.PropertyValueProvider; import org.springframework.data.mapping.model.PropertyValueProvider;

View File

@@ -18,13 +18,13 @@ package org.springframework.data.couchbase.core.mapping.event;
import java.util.Set; import java.util.Set;
import javax.validation.ConstraintViolationException;
import javax.validation.Validator;
import jakarta.validation.ConstraintViolationException;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
/** /**
* javax.validation dependant entities validator. When it is registered as Spring component its automatically invoked * javax.validation dependant entities validator. When it is registered as Spring component its automatically invoked
@@ -38,14 +38,14 @@ public class ValidatingCouchbaseEventListener extends AbstractCouchbaseEventList
private static final Logger LOG = LoggerFactory.getLogger(ValidatingCouchbaseEventListener.class); private static final Logger LOG = LoggerFactory.getLogger(ValidatingCouchbaseEventListener.class);
private final Validator validator; private final LocalValidatorFactoryBean validator;
/** /**
* Creates a new {@link ValidatingCouchbaseEventListener} using the given {@link Validator}. * Creates a new {@link ValidatingCouchbaseEventListener} using the given {@link Validator}.
* *
* @param validator must not be {@literal null}. * @param validator must not be {@literal null}.
*/ */
public ValidatingCouchbaseEventListener(Validator validator) { public ValidatingCouchbaseEventListener(LocalValidatorFactoryBean validator) {
Assert.notNull(validator, "Validator must not be null!"); Assert.notNull(validator, "Validator must not be null!");
this.validator = validator; this.validator = validator;
} }

View File

@@ -18,12 +18,15 @@ package org.springframework.data.couchbase.core.query;
import static org.springframework.data.couchbase.core.query.N1QLExpression.x; import static org.springframework.data.couchbase.core.query.N1QLExpression.x;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Formatter; import java.util.Formatter;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import org.springframework.data.couchbase.core.convert.CouchbaseConverter; import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;
import com.couchbase.client.core.error.InvalidArgumentException; import com.couchbase.client.core.error.InvalidArgumentException;
import com.couchbase.client.java.json.JsonArray; import com.couchbase.client.java.json.JsonArray;
@@ -278,18 +281,30 @@ public class QueryCriteria implements QueryCriteriaDefinition {
public QueryCriteria in(@Nullable Object... o) { public QueryCriteria in(@Nullable Object... o) {
operator = "IN"; operator = "IN";
format = "%1$s in ( %3$s )"; format = "%1$s in ( ";
// IN takes a single argument that is a list
if (o.length > 0) { if (o.length > 0) {
if (o[0] instanceof JsonArray || o[0] instanceof List || o[0] instanceof Object[]) { if (o[0] instanceof JsonArray || o[0] instanceof List || o[0] instanceof Object[]) {
if (o.length != 1) { if (o.length != 1) {
throw new RuntimeException("IN cannot take multiple lists"); throw new RuntimeException("IN cannot take multiple lists");
} }
value = o; if (o[0] instanceof Object[]) {
value = (Object[]) o[0];
} else if (o[0] instanceof JsonArray) {
JsonArray ja = ((JsonArray) o[0]);
value = ja.toList().toArray();
} else if (o[0] instanceof List) {
List l = ((List) o[0]);
value = l.toArray();
}
} else { } else {
value = new Object[1]; value = o;
value[0] = o; // JsonArray.from(o);
} }
for (int i = 0; i < value.length; i++) {
if (i > 0)
format = format + ", ";
format = format + "%" + (i + 3) + "$s";
}
format = format + " )";
} }
return this; return this;
} }
@@ -409,15 +424,13 @@ public class QueryCriteria implements QueryCriteriaDefinition {
if (paramIndexPtr[0] >= 0) { if (paramIndexPtr[0] >= 0) {
JsonArray params = (JsonArray) parameters; JsonArray params = (JsonArray) parameters;
// from StringBasedN1qlQueryParser.getPositionalPlaceholderValues() // from StringBasedN1qlQueryParser.getPositionalPlaceholderValues()
try {
if (value instanceof Object[] || value instanceof Collection) {
addAsCollection(params, asCollection(value), converter);
} else {
params.add(convert(converter, value)); params.add(convert(converter, value));
} catch (InvalidArgumentException iae) {
if (value instanceof Object[]) {
addAsArray(params, value, converter);
} else {
throw iae;
}
} }
return "$" + (++paramIndexPtr[0]); // these are generated in order return "$" + (++paramIndexPtr[0]); // these are generated in order
} else { } else {
JsonObject params = (JsonObject) parameters; JsonObject params = (JsonObject) parameters;
@@ -462,15 +475,27 @@ public class QueryCriteria implements QueryCriteriaDefinition {
return converter != null ? converter.convertForWriteIfNeeded(value) : value; return converter != null ? converter.convertForWriteIfNeeded(value) : value;
} }
private void addAsArray(JsonArray posValues, Object o, CouchbaseConverter converter) { private void addAsCollection(JsonArray posValues, Collection collection, CouchbaseConverter converter) {
Object[] array = (Object[]) o;
JsonArray ja = JsonValue.ja(); JsonArray ja = JsonValue.ja();
for (Object e : array) { for (Object e : collection) {
ja.add(String.valueOf(convert(converter, e))); ja.add(String.valueOf(convert(converter, e)));
} }
posValues.add(ja); posValues.add(ja);
} }
/**
* Returns a collection from the given source object. From MappingCouchbaseConverter.
*
* @param source the source object.
* @return the target collection.
*/
private static Collection<?> asCollection(final Object source) {
if (source instanceof Collection) {
return (Collection<?>) source;
}
return source.getClass().isArray() ? CollectionUtils.arrayToList(source) : Collections.singleton(source);
}
private String maybeBackTic(String value) { private String maybeBackTic(String value) {
if (value == null || (value.startsWith("`") && value.endsWith("`"))) { if (value == null || (value.startsWith("`") && value.endsWith("`"))) {
return value; return value;

View File

@@ -19,6 +19,7 @@ package org.springframework.data.couchbase.repository.auditing;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition;
@@ -70,7 +71,11 @@ public class CouchbaseAuditingRegistrar extends AuditingBeanDefinitionRegistrarS
Assert.notNull(configuration, "AuditingConfiguration must not be null!"); Assert.notNull(configuration, "AuditingConfiguration must not be null!");
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(IsNewAwareAuditingHandler.class); BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(IsNewAwareAuditingHandler.class);
builder.addConstructorArgReference(BeanNames.COUCHBASE_MAPPING_CONTEXT);
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(PersistentEntitiesFactoryBean.class);
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
builder.addConstructorArgValue(definition.getBeanDefinition());
return configureDefaultAuditHandlerAttributes(configuration, builder); return configureDefaultAuditHandlerAttributes(configuration, builder);
} }

View File

@@ -15,14 +15,13 @@
*/ */
package org.springframework.data.couchbase.repository.cdi; package org.springframework.data.couchbase.repository.cdi;
import jakarta.enterprise.inject.spi.Bean;
import jakarta.enterprise.inject.spi.BeanManager;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
import org.springframework.data.couchbase.core.CouchbaseOperations; import org.springframework.data.couchbase.core.CouchbaseOperations;
import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping; import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping;
import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory; import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory;
@@ -63,8 +62,7 @@ public class CouchbaseRepositoryBean<T> extends CdiRepositoryBean<T> {
* @see org.springframework.data.repository.cdi.CdiRepositoryBean#create(javax.enterprise.context.spi.CreationalContext, java.lang.Class) * @see org.springframework.data.repository.cdi.CdiRepositoryBean#create(javax.enterprise.context.spi.CreationalContext, java.lang.Class)
*/ */
@Override @Override
protected T create(CreationalContext<T> creationalContext, Class<T> repositoryType) { protected T create(jakarta.enterprise.context.spi.CreationalContext<T> creationalContext, Class<T> repositoryType) {
CouchbaseOperations couchbaseOperations = getDependencyInstance(couchbaseOperationsBean, CouchbaseOperations.class); CouchbaseOperations couchbaseOperations = getDependencyInstance(couchbaseOperationsBean, CouchbaseOperations.class);
RepositoryOperationsMapping couchbaseOperationsMapping = new RepositoryOperationsMapping(couchbaseOperations); RepositoryOperationsMapping couchbaseOperationsMapping = new RepositoryOperationsMapping(couchbaseOperations);
@@ -75,4 +73,5 @@ public class CouchbaseRepositoryBean<T> extends CdiRepositoryBean<T> {
public Class<? extends Annotation> getScope() { public Class<? extends Annotation> getScope() {
return couchbaseOperationsBean.getScope(); return couchbaseOperationsBean.getScope();
} }
} }

View File

@@ -22,13 +22,12 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import javax.enterprise.event.Observes; import jakarta.enterprise.event.Observes;
import javax.enterprise.inject.UnsatisfiedResolutionException; import jakarta.enterprise.inject.UnsatisfiedResolutionException;
import javax.enterprise.inject.spi.AfterBeanDiscovery; import jakarta.enterprise.inject.spi.AfterBeanDiscovery;
import javax.enterprise.inject.spi.Bean; import jakarta.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager; import jakarta.enterprise.inject.spi.BeanManager;
import javax.enterprise.inject.spi.ProcessBean; import jakarta.enterprise.inject.spi.ProcessBean;
import org.springframework.data.couchbase.core.CouchbaseOperations; import org.springframework.data.couchbase.core.CouchbaseOperations;
import org.springframework.data.repository.cdi.CdiRepositoryBean; import org.springframework.data.repository.cdi.CdiRepositoryBean;
import org.springframework.data.repository.cdi.CdiRepositoryExtensionSupport; import org.springframework.data.repository.cdi.CdiRepositoryExtensionSupport;
@@ -67,6 +66,7 @@ public class CouchbaseRepositoryExtension extends CdiRepositoryExtensionSupport
* *
* @param beanManager The BeanManager instance. * @param beanManager The BeanManager instance.
*/ */
void afterBeanDiscovery(@Observes AfterBeanDiscovery afterBeanDiscovery, BeanManager beanManager) { void afterBeanDiscovery(@Observes AfterBeanDiscovery afterBeanDiscovery, BeanManager beanManager) {
for (Map.Entry<Class<?>, Set<Annotation>> entry : getRepositoryTypes()) { for (Map.Entry<Class<?>, Set<Annotation>> entry : getRepositoryTypes()) {

View File

@@ -26,7 +26,7 @@ import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice; import org.springframework.data.domain.Slice;
import org.springframework.data.domain.SliceImpl; import org.springframework.data.domain.SliceImpl;
import org.springframework.data.repository.query.QueryMethod; import org.springframework.data.repository.query.QueryMethod;
import org.springframework.data.repository.support.PageableExecutionUtils; import org.springframework.data.support.PageableExecutionUtils;
import org.springframework.util.Assert; import org.springframework.util.Assert;
/** /**

View File

@@ -58,10 +58,6 @@ public class N1qlCountQueryCreator extends OldN1qlQueryCreator {
return Sort.unsorted(); return Sort.unsorted();
} }
public Optional<Class<?>> getDynamicProjection() {
return delegate.getDynamicProjection();
}
@Override @Override
public Class<?> findDynamicProjection() { public Class<?> findDynamicProjection() {
return delegate.findDynamicProjection(); return delegate.findDynamicProjection();

View File

@@ -69,7 +69,7 @@ public class N1qlQueryCreator extends AbstractQueryCreator<Query, QueryCriteria>
this.queryMethod = queryMethod; this.queryMethod = queryMethod;
this.converter = converter; this.converter = converter;
this.bucketName = bucketName; this.bucketName = bucketName;
this.entity = converter.getMappingContext().getPersistentEntity(queryMethod.getReturnedObjectType()); this.entity = converter.getMappingContext().getPersistentEntity(queryMethod.getEntityInformation().getJavaType());
} }
@Override @Override

View File

@@ -33,6 +33,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.data.couchbase.core.convert.CouchbaseConverter; import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty; import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
import org.springframework.data.couchbase.core.mapping.Expiration;
import org.springframework.data.couchbase.core.query.N1QLExpression; import org.springframework.data.couchbase.core.query.N1QLExpression;
import org.springframework.data.couchbase.repository.Query; import org.springframework.data.couchbase.repository.Query;
import org.springframework.data.couchbase.repository.query.support.N1qlUtils; import org.springframework.data.couchbase.repository.query.support.N1qlUtils;
@@ -44,7 +45,6 @@ import org.springframework.data.repository.query.Parameter;
import org.springframework.data.repository.query.ParameterAccessor; import org.springframework.data.repository.query.ParameterAccessor;
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
import org.springframework.data.repository.query.ReturnedType; import org.springframework.data.repository.query.ReturnedType;
import org.springframework.data.util.TypeInformation;
import org.springframework.expression.EvaluationContext; import org.springframework.expression.EvaluationContext;
import org.springframework.expression.common.TemplateParserContext; import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser;
@@ -130,10 +130,11 @@ public class StringBasedN1qlQueryParser {
this.queryMethod = queryMethod; this.queryMethod = queryMethod;
this.couchbaseConverter = couchbaseConverter; this.couchbaseConverter = couchbaseConverter;
String collection = queryMethod.getCollection(); String collection = queryMethod.getCollection();
this.statementContext = createN1qlSpelValues(bucketName, collection, null, null, typeField, typeValue, false, null, this.statementContext = createN1qlSpelValues(bucketName, collection,
null); queryMethod.getEntityInformation().getJavaType(), queryMethod.getReturnedObjectType(), typeField, typeValue,
this.countContext = createN1qlSpelValues(bucketName, collection, null, null, typeField, typeValue, true, null, false, null, null);
null); this.countContext = createN1qlSpelValues(bucketName, collection, queryMethod.getEntityInformation().getJavaType(),
queryMethod.getReturnedObjectType(), typeField, typeValue, true, null, null);
this.parsedExpression = getExpression(accessor, getParameters(accessor), null, parser, evaluationContextProvider); this.parsedExpression = getExpression(accessor, getParameters(accessor), null, parser, evaluationContextProvider);
checkPlaceholders(this.parsedExpression.toString()); checkPlaceholders(this.parsedExpression.toString());
} }
@@ -161,12 +162,11 @@ public class StringBasedN1qlQueryParser {
String b = collection != null ? collection : bucketName; String b = collection != null ? collection : bucketName;
Assert.isTrue(!(distinctFields != null && fields != null), Assert.isTrue(!(distinctFields != null && fields != null),
"only one of project(fields) and distinct(distinctFields) can be specified"); "only one of project(fields) and distinct(distinctFields) can be specified");
String projectedFields = getProjectedFields(b, resultClass, fields);
String entity = "META(" + i(b) + ").id AS " + SELECT_ID + ", META(" + i(b) + ").cas AS " + SELECT_CAS; String entity = "META(" + i(b) + ").id AS " + SELECT_ID + ", META(" + i(b) + ").cas AS " + SELECT_CAS;
String count = "COUNT(*) AS " + CountFragment.COUNT_ALIAS; String count = "COUNT(*) AS " + CountFragment.COUNT_ALIAS;
String selectEntity; String selectEntity;
if (distinctFields != null) { if (distinctFields != null) {
String distinctFieldsStr = distinctFields.length == 0 ? projectedFields : getDistinctFields(distinctFields); String distinctFieldsStr = getProjectedOrDistinctFields(b, domainClass, fields, distinctFields);
if (isCount) { if (isCount) {
selectEntity = "SELECT COUNT( DISTINCT {" + distinctFieldsStr + "} ) " + CountFragment.COUNT_ALIAS + " FROM " selectEntity = "SELECT COUNT( DISTINCT {" + distinctFieldsStr + "} ) " + CountFragment.COUNT_ALIAS + " FROM "
+ i(b); + i(b);
@@ -176,6 +176,7 @@ public class StringBasedN1qlQueryParser {
} else if (isCount) { } else if (isCount) {
selectEntity = "SELECT " + count + " FROM " + i(b); selectEntity = "SELECT " + count + " FROM " + i(b);
} else { } else {
String projectedFields = getProjectedOrDistinctFields(b, domainClass, fields, distinctFields);
selectEntity = "SELECT " + entity + (!projectedFields.isEmpty() ? ", " : " ") + projectedFields + " FROM " + i(b); selectEntity = "SELECT " + entity + (!projectedFields.isEmpty() ? ", " : " ") + projectedFields + " FROM " + i(b);
} }
String typeSelection = "`" + typeField + "` = \"" + typeValue + "\""; String typeSelection = "`" + typeField + "` = \"" + typeValue + "\"";
@@ -186,65 +187,63 @@ public class StringBasedN1qlQueryParser {
return new N1qlSpelValues(selectEntity, entity, b, typeSelection, delete, returning); return new N1qlSpelValues(selectEntity, entity, b, typeSelection, delete, returning);
} }
private String getDistinctFields(String... distinctFields) { private String getProjectedOrDistinctFields(String b, Class resultClass, String[] fields, String[] distinctFields) {
return i(distinctFields).toString(); if (distinctFields != null && distinctFields.length != 0) {
} return i(distinctFields).toString();
}
private String getProjectedFields(String b, Class resultClass, String[] fields) {
String projectedFields = i(b) + ".*"; String projectedFields = i(b) + ".*";
if (resultClass != null) { if (resultClass != null) {
PersistentEntity persistentEntity = couchbaseConverter.getMappingContext().getPersistentEntity(resultClass); PersistentEntity persistentEntity = couchbaseConverter.getMappingContext().getPersistentEntity(resultClass);
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
getProjectedFieldsInternal(b, null, sb, persistentEntity.getTypeInformation(), fields/*, ""*/); getProjectedFieldsInternal(b, null, sb, persistentEntity, fields, distinctFields != null);
projectedFields = sb.toString(); projectedFields = sb.toString();
} }
return projectedFields; return projectedFields;
} }
private void getProjectedFieldsInternal(String bucketName, CouchbasePersistentProperty parent, StringBuilder sb, private void getProjectedFieldsInternal(String bucketName, CouchbasePersistentProperty parent, StringBuilder sb,
TypeInformation resultClass, String[] fields/*, String path*/) { PersistentEntity persistentEntity, String[] fields, boolean forDistinct) {
if (resultClass != null) { if (persistentEntity != null) {
Set<String> fieldList = fields != null ? new HashSet<>(Arrays.asList(fields)) : null; Set<String> fieldList = fields != null ? new HashSet<>(Arrays.asList(fields)) : null;
PersistentEntity persistentEntity = couchbaseConverter.getMappingContext().getPersistentEntity(resultClass);
// CouchbasePersistentProperty property = path.getLeafProperty();
persistentEntity.doWithProperties(new PropertyHandler<CouchbasePersistentProperty>() { // do not include the id and cas metadata fields.
@Override
public void doWithPersistentProperty(final CouchbasePersistentProperty prop) {
if (prop == persistentEntity.getIdProperty() && parent == null) {
return;
}
if (prop == persistentEntity.getVersionProperty() && parent == null) {
return;
}
String projectField = null;
if (fieldList == null || fieldList.contains(prop.getFieldName())) { persistentEntity.doWithProperties((PropertyHandler<CouchbasePersistentProperty>) prop -> {
PersistentPropertyPath<CouchbasePersistentProperty> path = couchbaseConverter.getMappingContext() if (prop == persistentEntity.getIdProperty() && parent == null) {
.getPersistentPropertyPath(prop.getName(), resultClass.getType()); return;
projectField = N1qlQueryCreator.addMetaIfRequired(bucketName, path, prop, persistentEntity).toString();
if (sb.length() > 0) {
sb.append(", ");
}
sb.append(projectField); // from N1qlQueryCreator
}
if (fieldList != null) {
fieldList.remove(prop.getFieldName());
}
// The current limitation is that only top-level properties can be projected
// This traversing of nested data structures would need to replicate the processing done by
// MappingCouchbaseConverter. Either the read or write
// And the n1ql to project lower-level properties is complex
// if (!conversions.isSimpleType(prop.getType())) {
// getProjectedFieldsInternal(prop, sb, prop.getTypeInformation(), path+prop.getName()+".");
// } else {
// }
} }
if (prop == persistentEntity.getVersionProperty() && parent == null) {
return;
}
// for distinct when no distinctFields were provided, do not include the expiration field.
if (forDistinct && prop.findAnnotation(Expiration.class) != null && parent == null) {
return;
}
String projectField = null;
if (fieldList == null || fieldList.contains(prop.getFieldName())) {
PersistentPropertyPath<CouchbasePersistentProperty> path = couchbaseConverter.getMappingContext()
.getPersistentPropertyPath(prop.getName(), persistentEntity.getTypeInformation().getType());
projectField = N1qlQueryCreator.addMetaIfRequired(bucketName, path, prop, persistentEntity).toString();
if (sb.length() > 0) {
sb.append(", ");
}
sb.append(projectField); // from N1qlQueryCreator
}
if (fieldList != null) {
fieldList.remove(prop.getFieldName());
}
// The current limitation is that only top-level properties can be projected
// This traversing of nested data structures would need to replicate the processing done by
// MappingCouchbaseConverter. Either the read or write
// And the n1ql to project lower-level properties is complex
// if (!conversions.isSimpleType(prop.getType())) {
// getProjectedFieldsInternal(prop, sb, prop.getTypeInformation(), path+prop.getName()+".");
// } else {
// }
}); });
// throw an exception if there is an request for a field not in the entity. // throw an exception if there is an request for a field not in the entity.
// needs further discussion as removing a field from an entity could cause this and not necessarily be an error // needs further discussion as removing a field from an entity could cause this and not necessarily be an error

View File

@@ -349,20 +349,15 @@ class CouchbaseTemplateQueryCollectionIntegrationTests extends CollectionAwareIn
assertEquals(7, airports2.size()); assertEquals(7, airports2.size());
// count( distinct icao ) // count( distinct icao )
// not currently possible to have multiple fields in COUNT(DISTINCT field1, field2, ... ) due to MB43475
Long count1 = reactiveCouchbaseTemplate.findByQuery(Airport.class).distinct(new String[] { "icao" }) Long count1 = reactiveCouchbaseTemplate.findByQuery(Airport.class).distinct(new String[] { "icao" })
.as(Airport.class).withConsistency(QueryScanConsistency.REQUEST_PLUS).inCollection(collectionName).count() .as(Airport.class).withConsistency(QueryScanConsistency.REQUEST_PLUS).inCollection(collectionName).count()
.block(); .block();
assertEquals(2, count1); assertEquals(2, count1);
// count( distinct (all fields in icaoClass) // which only has one field // count (distinct { iata, icao } )
// not currently possible to have multiple fields in COUNT(DISTINCT field1, field2, ... ) due to MB43475 Long count2 = reactiveCouchbaseTemplate.findByQuery(Airport.class).distinct(new String[] {"iata", "icao"})
Class icaoClass = (new Object() {
String icao;
}).getClass();
long count2 = (long) reactiveCouchbaseTemplate.findByQuery(Airport.class).distinct(new String[] {}).as(icaoClass)
.withConsistency(QueryScanConsistency.REQUEST_PLUS).inCollection(collectionName).count().block(); .withConsistency(QueryScanConsistency.REQUEST_PLUS).inCollection(collectionName).count().block();
assertEquals(2, count2); assertEquals(7, count2);
} finally { } finally {
reactiveCouchbaseTemplate.removeById().inCollection(collectionName) reactiveCouchbaseTemplate.removeById().inCollection(collectionName)

View File

@@ -265,15 +265,6 @@ class CouchbaseTemplateQueryIntegrationTests extends JavaIntegrationTests {
.as(Airport.class).withConsistency(QueryScanConsistency.REQUEST_PLUS).count(); .as(Airport.class).withConsistency(QueryScanConsistency.REQUEST_PLUS).count();
assertEquals(7, count1); assertEquals(7, count1);
// count( distinct (all fields in icaoClass)
Class icaoClass = (new Object() {
String iata;
String icao;
}).getClass();
long count2 = couchbaseTemplate.findByQuery(Airport.class).distinct(new String[] {}).as(icaoClass)
.withConsistency(QueryScanConsistency.REQUEST_PLUS).count();
assertEquals(7, count2);
} finally { } finally {
couchbaseTemplate.removeById() couchbaseTemplate.removeById()
.all(Arrays.stream(iatas).map((iata) -> "airports::" + iata).collect(Collectors.toSet())); .all(Arrays.stream(iatas).map((iata) -> "airports::" + iata).collect(Collectors.toSet()));
@@ -305,19 +296,14 @@ class CouchbaseTemplateQueryIntegrationTests extends JavaIntegrationTests {
assertEquals(7, airports2.size()); assertEquals(7, airports2.size());
// count( distinct icao ) // count( distinct icao )
// not currently possible to have multiple fields in COUNT(DISTINCT field1, field2, ... ) due to MB43475 Long count1 = reactiveCouchbaseTemplate.findByQuery(Airport.class).distinct(new String[] { "icao" })
long count1 = reactiveCouchbaseTemplate.findByQuery(Airport.class).distinct(new String[] { "icao" })
.as(Airport.class).withConsistency(QueryScanConsistency.REQUEST_PLUS).count().block(); .as(Airport.class).withConsistency(QueryScanConsistency.REQUEST_PLUS).count().block();
assertEquals(2, count1); assertEquals(2, count1);
// count( distinct (all fields in icaoClass) // which only has one field // count( distinct { icao, iata } )
// not currently possible to have multiple fields in COUNT(DISTINCT field1, field2, ... ) due to MB43475 Long count2 = reactiveCouchbaseTemplate.findByQuery(Airport.class).distinct(new String[] { "icao", "iata" })
Class icaoClass = (new Object() {
String icao;
}).getClass();
long count2 = (long) reactiveCouchbaseTemplate.findByQuery(Airport.class).distinct(new String[] {}).as(icaoClass)
.withConsistency(QueryScanConsistency.REQUEST_PLUS).count().block(); .withConsistency(QueryScanConsistency.REQUEST_PLUS).count().block();
assertEquals(2, count2); assertEquals(7, count2);
} finally { } finally {
reactiveCouchbaseTemplate.removeById() reactiveCouchbaseTemplate.removeById()

View File

@@ -24,12 +24,12 @@ 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.core.query.QueryCriteria.where;
import static org.springframework.data.couchbase.repository.query.support.N1qlUtils.escapedBucket; import static org.springframework.data.couchbase.repository.query.support.N1qlUtils.escapedBucket;
import java.util.Arrays;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import com.couchbase.client.java.json.JsonArray; import com.couchbase.client.java.json.JsonArray;
import java.util.Arrays;
/** /**
* @author Mauro Monti * @author Mauro Monti
*/ */
@@ -85,8 +85,9 @@ class QueryCriteriaTests {
void testNestedNotIn() { void testNestedNotIn() {
QueryCriteria c = where(i("name")).is("Bubba").or(where(i("age")).gt(12).or(i("country")).is("Austria")) 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" })); .and(where(i("state")).notIn(new String[] { "Alabama", "Florida" }));
assertEquals("`name` = \"Bubba\" or (`age` > 12 or `country` = \"Austria\") and " JsonArray parameters = JsonArray.create();
+ "(not( (`state` in ( [\"Alabama\",\"Florida\"] )) ))", c.export()); assertEquals("`name` = $1 or (`age` > $2 or `country` = $3) and (not( (`state` in ( $4, $5 )) ))",
c.export(new int[1], parameters, null));
} }
@Test @Test
@@ -224,21 +225,22 @@ class QueryCriteriaTests {
@Test @Test
void testIn() { void testIn() {
String[] args = new String[] { "gump", "davis" }; String[] args = new String[] { "gump", "davis" };
QueryCriteria c = where(i("name")).in((Object)args); QueryCriteria c = where(i("name")).in((Object) args);
assertEquals("`name` in ( [\"gump\",\"davis\"] )", c.export()); assertEquals("`name` in ( \"gump\", \"davis\" )", c.export());
JsonArray parameters = JsonArray.create(); JsonArray parameters = JsonArray.create();
assertEquals("`name` in ( $1 )", c.export(new int[1], parameters, null)); assertEquals("`name` in ( $1, $2 )", c.export(new int[1], parameters, null));
assertEquals(arrayToString(args), parameters.get(0).toString()); assertEquals(arrayToString(args), parameters.toString());
} }
@Test @Test
void testNotIn() { void testNotIn() {
String[] args = new String[] { "gump", "davis" }; String[] args = new String[] { "gump", "davis" };
QueryCriteria c = where(i("name")).notIn((Object)args); QueryCriteria c = where(i("name")).notIn((Object) args);
assertEquals("not( (`name` in ( [\"gump\",\"davis\"] )) )", c.export()); assertEquals("not( (`name` in ( \"gump\", \"davis\" )) )", c.export());
// this tests creating parameters from the args.
JsonArray parameters = JsonArray.create(); JsonArray parameters = JsonArray.create();
assertEquals("not( (`name` in ( $1 )) )", c.export(new int[1], parameters, null)); assertEquals("not( (`name` in ( $1, $2 )) )", c.export(new int[1], parameters, null));
assertEquals(arrayToString(args), parameters.get(0).toString()); assertEquals(arrayToString(args), parameters.toString());
} }
@Test @Test
@@ -261,7 +263,6 @@ class QueryCriteriaTests {
assertEquals(" USE KEYS []", expression.keys(Arrays.asList()).toString()); assertEquals(" USE KEYS []", expression.keys(Arrays.asList()).toString());
} }
@Test // https://github.com/spring-projects/spring-data-couchbase/issues/1066 @Test // https://github.com/spring-projects/spring-data-couchbase/issues/1066
void testCriteriaCorrectlyEscapedWhenUsingMetaOnLHS() { void testCriteriaCorrectlyEscapedWhenUsingMetaOnLHS() {
final String bucketName = "sample-bucket"; final String bucketName = "sample-bucket";

View File

@@ -344,20 +344,15 @@ class ReactiveCouchbaseTemplateQueryCollectionIntegrationTests extends Collectio
assertEquals(7, airports2.size()); assertEquals(7, airports2.size());
// count( distinct icao ) // count( distinct icao )
// not currently possible to have multiple fields in COUNT(DISTINCT field1, field2, ... ) due to MB43475
Long count1 = reactiveCouchbaseTemplate.findByQuery(Airport.class).distinct(new String[] { "icao" }) Long count1 = reactiveCouchbaseTemplate.findByQuery(Airport.class).distinct(new String[] { "icao" })
.as(Airport.class).withConsistency(QueryScanConsistency.REQUEST_PLUS).inCollection(collectionName).count() .as(Airport.class).withConsistency(QueryScanConsistency.REQUEST_PLUS).inCollection(collectionName).count()
.block(); .block();
assertEquals(2, count1); assertEquals(2, count1);
// count( distinct (all fields in icaoClass) // which only has one field // count( distinct { iata, icao } )
// not currently possible to have multiple fields in COUNT(DISTINCT field1, field2, ... ) due to MB43475 Long count2 = reactiveCouchbaseTemplate.findByQuery(Airport.class).distinct(new String[] {"iata","icao"})
Class icaoClass = (new Object() {
String icao;
}).getClass();
long count2 = (long) reactiveCouchbaseTemplate.findByQuery(Airport.class).distinct(new String[] {}).as(icaoClass)
.withConsistency(QueryScanConsistency.REQUEST_PLUS).inCollection(collectionName).count().block(); .withConsistency(QueryScanConsistency.REQUEST_PLUS).inCollection(collectionName).count().block();
assertEquals(2, count2); assertEquals(7, count2);
} finally { } finally {
reactiveCouchbaseTemplate.removeById().inCollection(collectionName) reactiveCouchbaseTemplate.removeById().inCollection(collectionName)

View File

@@ -16,6 +16,7 @@
package org.springframework.data.couchbase.domain; package org.springframework.data.couchbase.domain;
import jakarta.validation.constraints.Max;
import org.springframework.data.annotation.CreatedBy; import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.PersistenceConstructor; import org.springframework.data.annotation.PersistenceConstructor;
@@ -24,8 +25,6 @@ import org.springframework.data.annotation.Version;
import org.springframework.data.couchbase.core.mapping.Document; import org.springframework.data.couchbase.core.mapping.Document;
import org.springframework.data.couchbase.core.mapping.Expiration; import org.springframework.data.couchbase.core.mapping.Expiration;
import javax.validation.constraints.Max;
/** /**
* Airport entity * Airport entity
* *

View File

@@ -129,6 +129,18 @@ public interface AirportRepository extends CouchbaseRepository<Airport, String>,
@Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND iata != $1") @Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND iata != $1")
Page<Airport> getAllByIataNot(String iata, Pageable pageable); Page<Airport> getAllByIataNot(String iata, Pageable pageable);
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
@Query("SELECT iata, \"\" as __id, 0 as __cas from #{#n1ql.bucket} WHERE #{#n1ql.filter} order by meta().id")
List<String> getStrings();
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
@Query("SELECT length(iata), \"\" as __id, 0 as __cas from #{#n1ql.bucket} WHERE #{#n1ql.filter} order by meta().id")
List<Long> getLongs();
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
@Query("SELECT iata, icao, \"\" as __id, 0 as __cas from #{#n1ql.bucket} WHERE #{#n1ql.filter} order by meta().id")
List<String[]> getStringArrays();
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS) @ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
Optional<Airport> findByIdAndIata(String id, String iata); Optional<Airport> findByIdAndIata(String id, String iata);

View File

@@ -29,6 +29,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue; 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.COUCHBASE_TEMPLATE;
import jakarta.validation.ConstraintViolationException;
import junit.framework.AssertionFailedError; import junit.framework.AssertionFailedError;
import java.lang.reflect.Method; import java.lang.reflect.Method;
@@ -45,8 +46,6 @@ import java.util.concurrent.Executors;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.validation.ConstraintViolationException;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
@@ -101,7 +100,6 @@ import com.couchbase.client.core.error.IndexFailureException;
import com.couchbase.client.java.env.ClusterEnvironment; import com.couchbase.client.java.env.ClusterEnvironment;
import com.couchbase.client.java.json.JsonArray; import com.couchbase.client.java.json.JsonArray;
import com.couchbase.client.java.kv.GetResult; import com.couchbase.client.java.kv.GetResult;
import com.couchbase.client.java.kv.MutationState;
import com.couchbase.client.java.kv.UpsertOptions; import com.couchbase.client.java.kv.UpsertOptions;
import com.couchbase.client.java.query.QueryOptions; import com.couchbase.client.java.query.QueryOptions;
import com.couchbase.client.java.query.QueryScanConsistency; import com.couchbase.client.java.query.QueryScanConsistency;
@@ -317,16 +315,17 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
ApplicationContext ac = new AnnotationConfigApplicationContext(Config.class); ApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
// the Config class has been modified, these need to be loaded again // the Config class has been modified, these need to be loaded again
CouchbaseTemplate couchbaseTemplateRP = (CouchbaseTemplate) ac.getBean(COUCHBASE_TEMPLATE); CouchbaseTemplate couchbaseTemplateRP = (CouchbaseTemplate) ac.getBean(COUCHBASE_TEMPLATE);
AirportRepositoryScanConsistencyTest airportRepositoryRP = (AirportRepositoryScanConsistencyTest) ac.getBean("airportRepositoryScanConsistencyTest"); AirportRepositoryScanConsistencyTest airportRepositoryRP = (AirportRepositoryScanConsistencyTest) ac
.getBean("airportRepositoryScanConsistencyTest");
List<Airport> sizeBeforeTest = airportRepositoryRP.findAll(); List<Airport> sizeBeforeTest = airportRepositoryRP.findAll();
assertEquals(0, sizeBeforeTest.size()); assertEquals(0, sizeBeforeTest.size());
Airport vie = new Airport("airports::vie", "vie" , "low9"); Airport vie = new Airport("airports::vie", "vie", "low9");
Airport saved = airportRepositoryRP.save(vie); Airport saved = airportRepositoryRP.save(vie);
List<Airport> allSaved = airportRepositoryRP.findAll(); List<Airport> allSaved = airportRepositoryRP.findAll();
couchbaseTemplate.removeById(Airport.class).one(saved.getId()); couchbaseTemplate.removeById(Airport.class).one(saved.getId());
assertNotEquals( 1, allSaved.size(),"should not have found 1 airport"); assertNotEquals(1, allSaved.size(), "should not have found 1 airport");
} }
@Test @Test
@@ -334,19 +333,19 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
ApplicationContext ac = new AnnotationConfigApplicationContext(ConfigRequestPlus.class); ApplicationContext ac = new AnnotationConfigApplicationContext(ConfigRequestPlus.class);
// the Config class has been modified, these need to be loaded again // the Config class has been modified, these need to be loaded again
AirportRepositoryScanConsistencyTest airportRepositoryRP = (AirportRepositoryScanConsistencyTest) ac.getBean("airportRepositoryScanConsistencyTest"); AirportRepositoryScanConsistencyTest airportRepositoryRP = (AirportRepositoryScanConsistencyTest) ac
.getBean("airportRepositoryScanConsistencyTest");
List<Airport> sizeBeforeTest = airportRepositoryRP.findAll(); List<Airport> sizeBeforeTest = airportRepositoryRP.findAll();
assertEquals(0, sizeBeforeTest.size()); assertEquals(0, sizeBeforeTest.size());
Airport vie = new Airport("airports::vie", "vie" , "low9"); Airport vie = new Airport("airports::vie", "vie", "low9");
Airport saved = airportRepositoryRP.save(vie); Airport saved = airportRepositoryRP.save(vie);
List<Airport> allSaved = airportRepositoryRP.findAll(); List<Airport> allSaved = airportRepositoryRP.findAll();
couchbaseTemplate.removeById(Airport.class).one(saved.getId()); couchbaseTemplate.removeById(Airport.class).one(saved.getId());
assertEquals( 1, allSaved.size(),"should have found 1 airport"); assertEquals(1, allSaved.size(), "should have found 1 airport");
} }
@Test @Test
void findByTypeAlias() { void findByTypeAlias() {
Airport vie = null; Airport vie = null;
@@ -545,6 +544,25 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
assertThrows(DataRetrievalFailureException.class, () -> userRepository.delete(user)); assertThrows(DataRetrievalFailureException.class, () -> userRepository.delete(user));
} }
@Test
void stringQueryReturnsSimpleType(){
Airport airport1 = new Airport("1", "myIata1", "MyIcao");
airportRepository.save(airport1);
Airport airport2 = new Airport("2", "myIata2__", "MyIcao");
airportRepository.save(airport2);
List<String> iatas = airportRepository.getStrings();
assertEquals(Arrays.asList(airport1.getIata(), airport2.getIata()), iatas);
List<Long> iataLengths = airportRepository.getLongs();
assertEquals(Arrays.asList(airport1.getIata().length(), airport2.getIata().length()).toString(), iataLengths.toString());
// this is somewhat broken, because decode is told that each "row" is just a String instead of a String[]
// As such, only the first element is returned. (QueryExecutionConverts.unwrapWrapperTypes)
List<String[]> iataAndIcaos = airportRepository.getStringArrays();
assertEquals(airport1.getIata(), iataAndIcaos.get(0)[0]);
assertEquals(airport2.getIata(), iataAndIcaos.get(1)[0]);
airportRepository.deleteById(airport1.getId());
airportRepository.deleteById(airport2.getId());
}
@Test @Test
void count() { void count() {
String[] iatas = { "JFK", "IAD", "SFO", "SJC", "SEA", "LAX", "PHX" }; String[] iatas = { "JFK", "IAD", "SFO", "SJC", "SEA", "LAX", "PHX" };
@@ -886,13 +904,13 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
@Bean @Bean
public ValidatingCouchbaseEventListener validationEventListener() { public ValidatingCouchbaseEventListener validationEventListener() {
return new ValidatingCouchbaseEventListener(validator()); return new ValidatingCouchbaseEventListener( validator());
} }
} }
@Configuration @Configuration
@EnableCouchbaseRepositories("org.springframework.data.couchbase") @EnableCouchbaseRepositories("org.springframework.data.couchbase")
@EnableCouchbaseAuditing(auditorAwareRef = "auditorAwareRef", dateTimeProviderRef = "dateTimeProviderRef") // @EnableCouchbaseAuditing(auditorAwareRef = "auditorAwareRef", dateTimeProviderRef = "dateTimeProviderRef")
static class ConfigRequestPlus extends AbstractCouchbaseConfiguration { static class ConfigRequestPlus extends AbstractCouchbaseConfiguration {
@Override @Override

View File

@@ -109,7 +109,7 @@ class N1qlQueryCreatorTests {
Query query = creator.createQuery(); Query query = creator.createQuery();
// Query expected = (new Query()).addCriteria(where("firstname").in("Oliver", "Charles")); // Query expected = (new Query()).addCriteria(where("firstname").in("Oliver", "Charles"));
assertEquals(expected.export(new int[1]), query.export(new int[1])); assertEquals(" WHERE `firstname` in ( $1, $2 )", query.export(new int[1]));
JsonObject expectedOptions = JsonObject.create(); JsonObject expectedOptions = JsonObject.create();
expected.buildQueryOptions(null, null).build().injectParams(expectedOptions); expected.buildQueryOptions(null, null).build().injectParams(expectedOptions);
JsonObject actualOptions = JsonObject.create(); JsonObject actualOptions = JsonObject.create();
@@ -132,7 +132,7 @@ class N1qlQueryCreatorTests {
Query query = creator.createQuery(); Query query = creator.createQuery();
Query expected = (new Query()).addCriteria(where(i("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])); assertEquals(" WHERE `firstname` in ( $1, $2 )", query.export(new int[1]));
JsonObject expectedOptions = JsonObject.create(); JsonObject expectedOptions = JsonObject.create();
expected.buildQueryOptions(null, null).build().injectParams(expectedOptions); expected.buildQueryOptions(null, null).build().injectParams(expectedOptions);
JsonObject actualOptions = JsonObject.create(); JsonObject actualOptions = JsonObject.create();
@@ -156,7 +156,7 @@ class N1qlQueryCreatorTests {
Query expected = (new Query()).addCriteria(where(i("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])); assertEquals(" WHERE `firstname` in ( $1, $2 )", query.export(new int[1]));
JsonObject expectedOptions = JsonObject.create(); JsonObject expectedOptions = JsonObject.create();
expected.buildQueryOptions(null, null).build().injectParams(expectedOptions); expected.buildQueryOptions(null, null).build().injectParams(expectedOptions);
JsonObject actualOptions = JsonObject.create(); JsonObject actualOptions = JsonObject.create();

View File

@@ -85,7 +85,7 @@ class StringN1qlQueryCreatorMockedTests extends ClusterAwareIntegrationTests {
Query query = creator.createQuery(); Query query = creator.createQuery();
assertEquals( assertEquals(
"SELECT META(`travel-sample`).id AS __id, META(`travel-sample`).cas AS __cas, `travel-sample`.* FROM `travel-sample` where `_class` = \"org.springframework.data.couchbase.domain.User\" and firstname = $1 and lastname = $2", "SELECT META(`travel-sample`).id AS __id, META(`travel-sample`).cas AS __cas, `firstname`, `lastname`, `createdBy`, `createdDate`, `lastModifiedBy`, `lastModifiedDate` FROM `travel-sample` where `_class` = \"org.springframework.data.couchbase.domain.User\" and firstname = $1 and lastname = $2",
query.toN1qlSelectString(couchbaseTemplate.reactive(), User.class, false)); query.toN1qlSelectString(couchbaseTemplate.reactive(), User.class, false));
} }
@@ -104,7 +104,7 @@ class StringN1qlQueryCreatorMockedTests extends ClusterAwareIntegrationTests {
Query query = creator.createQuery(); Query query = creator.createQuery();
assertEquals( assertEquals(
"SELECT META(`travel-sample`).id AS __id, META(`travel-sample`).cas AS __cas, `travel-sample`.* FROM `travel-sample` where `_class` = \"org.springframework.data.couchbase.domain.User\" and (firstname = $first or lastname = $last)", "SELECT META(`travel-sample`).id AS __id, META(`travel-sample`).cas AS __cas, `firstname`, `lastname`, `createdBy`, `createdDate`, `lastModifiedBy`, `lastModifiedDate` FROM `travel-sample` where `_class` = \"org.springframework.data.couchbase.domain.User\" and (firstname = $first or lastname = $last)",
query.toN1qlSelectString(couchbaseTemplate.reactive(), User.class, false)); query.toN1qlSelectString(couchbaseTemplate.reactive(), User.class, false));
} }