diff --git a/pom.xml b/pom.xml index 6b51b5d29..7837f92cd 100644 --- a/pom.xml +++ b/pom.xml @@ -1,11 +1,12 @@ - + 4.0.0 org.springframework.data spring-data-couchbase - 4.3.0-SNAPSHOT + 5.0.0-SNAPSHOT Spring Data Couchbase Spring Data integration for Couchbase @@ -14,14 +15,17 @@ org.springframework.data.build spring-data-parent - 2.6.0-SNAPSHOT + 3.0.0-SNAPSHOT - 3.2.1 - 3.2.1 - 2.6.0-SNAPSHOT + 3.2.3 + 3.2.3 + 3.0.0-SNAPSHOT spring.data.couchbase + 2.10.13 + 3.0.1 + 7.0.1.Final @@ -64,6 +68,13 @@ ${couchbase} + + + jakarta.enterprise + jakarta.enterprise.cdi-api + 3.0.0 + + org.springframework spring-test @@ -72,9 +83,9 @@ - org.hibernate + org.hibernate.validator hibernate-validator - 5.3.6.Final + 7.0.1.Final test @@ -112,24 +123,25 @@ - javax.validation - validation-api + jakarta.validation + jakarta.validation-api ${validation} - true - javax.el - javax.el-api - 3.0.0 - test + jakarta.el + jakarta.el-api + 4.0.0 + provided + true org.glassfish - javax.el - 3.0.0 - test + jakarta.el + 4.0.2 + provided + true @@ -148,21 +160,6 @@ test - - javax.enterprise - cdi-api - ${cdi} - provided - true - - - - javax.annotation - javax.annotation-api - ${javax-annotation-api} - test - - org.apache.openwebbeans openwebbeans-se @@ -279,17 +276,6 @@ - - org.apache.maven.plugins - maven-jar-plugin - - - - ${java-module-name} - - - - org.apache.maven.plugins maven-assembly-plugin diff --git a/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplateSupport.java b/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplateSupport.java index ffb70fc65..ca5f4aec9 100644 --- a/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplateSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplateSupport.java @@ -17,6 +17,10 @@ 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.LoggerFactory; import org.springframework.beans.BeansException; @@ -40,6 +44,7 @@ import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.model.ConvertingPropertyAccessor; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; /** * Internal encode/decode support for CouchbaseTemplate. @@ -84,7 +89,18 @@ public CouchbaseDocument encodeEntity(final Object entityToEncode) { public T decodeEntity(String id, String source, long cas, Class entityClass) { final CouchbaseDocument converted = new CouchbaseDocument(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 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> set = ((CouchbaseDocument) translationService.decode(source, converted)) + .getContent().entrySet(); + return (T) set.iterator().next().getValue(); + } + if (cas != 0 && persistentEntity.getVersionProperty() != null) { converted.put(persistentEntity.getVersionProperty().getName(), cas); } @@ -98,6 +114,17 @@ public T decodeEntity(String id, String source, long cas, Class entityCla N1qlJoinResolver.handleProperties(persistentEntity, accessor, template.reactive(), id); 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 public Object applyUpdatedCas(final Object entity, CouchbaseDocument converted, final long cas) { diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplateSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplateSupport.java index d2f9d69a0..e5560ff39 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplateSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplateSupport.java @@ -18,6 +18,10 @@ 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.LoggerFactory; import org.springframework.beans.BeansException; @@ -42,6 +46,7 @@ import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.model.ConvertingPropertyAccessor; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; /** * Internal encode/decode support for {@link ReactiveCouchbaseTemplate}. @@ -84,7 +89,19 @@ public Mono decodeEntity(String id, String source, long cas, Class ent return Mono.fromSupplier(() -> { final CouchbaseDocument converted = new CouchbaseDocument(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 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> set = ((CouchbaseDocument) translationService.decode(source, converted)) + .getContent().entrySet(); + return (T) set.iterator().next().getValue(); + } + if (cas != 0 && persistentEntity.getVersionProperty() != null) { converted.put(persistentEntity.getVersionProperty().getName(), cas); } @@ -100,6 +117,18 @@ public Mono decodeEntity(String id, String source, long cas, Class ent }); } + CouchbasePersistentEntity couldBePersistentEntity(Class entityClass) { + if (ClassUtils.isPrimitiveOrWrapper(entityClass) || entityClass == String.class) { + return null; + } + try { + return mappingContext.getPersistentEntity(entityClass); + } catch (InaccessibleObjectException t) { + + } + return null; + } + @Override public Mono applyUpdatedCas(final Object entity, CouchbaseDocument converted, final long cas) { return Mono.fromSupplier(() -> { diff --git a/src/main/java/org/springframework/data/couchbase/core/convert/AbstractCouchbaseConverter.java b/src/main/java/org/springframework/data/couchbase/core/convert/AbstractCouchbaseConverter.java index 0e70fb47c..4139162e1 100644 --- a/src/main/java/org/springframework/data/couchbase/core/convert/AbstractCouchbaseConverter.java +++ b/src/main/java/org/springframework/data/couchbase/core/convert/AbstractCouchbaseConverter.java @@ -22,7 +22,7 @@ import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.support.GenericConversionService; 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}. diff --git a/src/main/java/org/springframework/data/couchbase/core/convert/CouchbaseJsr310Converters.java b/src/main/java/org/springframework/data/couchbase/core/convert/CouchbaseJsr310Converters.java index e001f33fa..ecb84232a 100644 --- a/src/main/java/org/springframework/data/couchbase/core/convert/CouchbaseJsr310Converters.java +++ b/src/main/java/org/springframework/data/couchbase/core/convert/CouchbaseJsr310Converters.java @@ -27,6 +27,7 @@ import java.time.LocalTime; import java.time.Period; import java.time.ZoneId; +import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Collection; import java.util.Date; @@ -71,6 +72,9 @@ private CouchbaseJsr310Converters() { converters.add(StringToDurationConverter.INSTANCE); converters.add(PeriodToStringConverter.INSTANCE); converters.add(StringToPeriodConverter.INSTANCE); + converters.add(ZonedDateTimeToLongConverter.INSTANCE); + converters.add(NumberToZonedDateTimeConverter.INSTANCE); + return converters; } @@ -99,6 +103,31 @@ public Long convert(LocalDateTime source) { } } + @ReadingConverter + public enum NumberToZonedDateTimeConverter implements Converter { + + 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 { + + INSTANCE; + + @Override + public Long convert(ZonedDateTime source) { + return source == null ? null + : DateConverters.DateToLongConverter.INSTANCE.convert(Date.from(source.toInstant())); + } + } + @ReadingConverter public enum NumberToLocalDateConverter implements Converter { diff --git a/src/main/java/org/springframework/data/couchbase/core/convert/MappingCouchbaseConverter.java b/src/main/java/org/springframework/data/couchbase/core/convert/MappingCouchbaseConverter.java index 421560203..467853682 100644 --- a/src/main/java/org/springframework/data/couchbase/core/convert/MappingCouchbaseConverter.java +++ b/src/main/java/org/springframework/data/couchbase/core/convert/MappingCouchbaseConverter.java @@ -36,7 +36,6 @@ import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.data.annotation.Transient; 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.CouchbaseList; import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext; @@ -60,6 +59,7 @@ import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.model.ConvertingPropertyAccessor; 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.PersistentEntityParameterValueProvider; import org.springframework.data.mapping.model.PropertyValueProvider; diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/event/ValidatingCouchbaseEventListener.java b/src/main/java/org/springframework/data/couchbase/core/mapping/event/ValidatingCouchbaseEventListener.java index bdfffa572..66bdacfd8 100644 --- a/src/main/java/org/springframework/data/couchbase/core/mapping/event/ValidatingCouchbaseEventListener.java +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/event/ValidatingCouchbaseEventListener.java @@ -18,13 +18,13 @@ import java.util.Set; -import javax.validation.ConstraintViolationException; -import javax.validation.Validator; +import jakarta.validation.ConstraintViolationException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; 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 @@ -38,14 +38,14 @@ public class ValidatingCouchbaseEventListener extends AbstractCouchbaseEventList 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}. * * @param validator must not be {@literal null}. */ - public ValidatingCouchbaseEventListener(Validator validator) { + public ValidatingCouchbaseEventListener(LocalValidatorFactoryBean validator) { Assert.notNull(validator, "Validator must not be null!"); this.validator = validator; } diff --git a/src/main/java/org/springframework/data/couchbase/core/query/QueryCriteria.java b/src/main/java/org/springframework/data/couchbase/core/query/QueryCriteria.java index 4b7da2459..776936e37 100644 --- a/src/main/java/org/springframework/data/couchbase/core/query/QueryCriteria.java +++ b/src/main/java/org/springframework/data/couchbase/core/query/QueryCriteria.java @@ -18,12 +18,15 @@ import static org.springframework.data.couchbase.core.query.N1QLExpression.x; import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; 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 org.springframework.util.CollectionUtils; import com.couchbase.client.core.error.InvalidArgumentException; import com.couchbase.client.java.json.JsonArray; @@ -278,18 +281,30 @@ public QueryCriteria between(@Nullable Object o1, @Nullable Object o2) { public QueryCriteria in(@Nullable Object... o) { operator = "IN"; - format = "%1$s in ( %3$s )"; - // IN takes a single argument that is a list + format = "%1$s in ( "; if (o.length > 0) { if (o[0] instanceof JsonArray || o[0] instanceof List || o[0] instanceof Object[]) { if (o.length != 1) { 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 { - value = new Object[1]; - value[0] = o; // JsonArray.from(o); + value = o; } + for (int i = 0; i < value.length; i++) { + if (i > 0) + format = format + ", "; + format = format + "%" + (i + 3) + "$s"; + } + format = format + " )"; } return this; } @@ -409,15 +424,13 @@ private String maybeWrapValue(N1QLExpression key, Object value, int[] paramIndex if (paramIndexPtr[0] >= 0) { JsonArray params = (JsonArray) parameters; // from StringBasedN1qlQueryParser.getPositionalPlaceholderValues() - try { + + if (value instanceof Object[] || value instanceof Collection) { + addAsCollection(params, asCollection(value), converter); + } else { 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 } else { JsonObject params = (JsonObject) parameters; @@ -462,15 +475,27 @@ private static Object convert(CouchbaseConverter converter, Object value) { return converter != null ? converter.convertForWriteIfNeeded(value) : value; } - private void addAsArray(JsonArray posValues, Object o, CouchbaseConverter converter) { - Object[] array = (Object[]) o; + private void addAsCollection(JsonArray posValues, Collection collection, CouchbaseConverter converter) { JsonArray ja = JsonValue.ja(); - for (Object e : array) { + for (Object e : collection) { ja.add(String.valueOf(convert(converter, e))); } 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) { if (value == null || (value.startsWith("`") && value.endsWith("`"))) { return value; diff --git a/src/main/java/org/springframework/data/couchbase/repository/auditing/CouchbaseAuditingRegistrar.java b/src/main/java/org/springframework/data/couchbase/repository/auditing/CouchbaseAuditingRegistrar.java index 7120205ce..9338caaf2 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/auditing/CouchbaseAuditingRegistrar.java +++ b/src/main/java/org/springframework/data/couchbase/repository/auditing/CouchbaseAuditingRegistrar.java @@ -19,6 +19,7 @@ import java.lang.annotation.Annotation; 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.BeanDefinitionRegistry; import org.springframework.beans.factory.support.RootBeanDefinition; @@ -70,7 +71,11 @@ protected BeanDefinitionBuilder getAuditHandlerBeanDefinitionBuilder(AuditingCon Assert.notNull(configuration, "AuditingConfiguration must not be null!"); 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); } diff --git a/src/main/java/org/springframework/data/couchbase/repository/cdi/CouchbaseRepositoryBean.java b/src/main/java/org/springframework/data/couchbase/repository/cdi/CouchbaseRepositoryBean.java index de107eab7..4f2b5449a 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/cdi/CouchbaseRepositoryBean.java +++ b/src/main/java/org/springframework/data/couchbase/repository/cdi/CouchbaseRepositoryBean.java @@ -15,14 +15,13 @@ */ 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.util.Optional; 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.repository.config.RepositoryOperationsMapping; import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory; @@ -63,8 +62,7 @@ public CouchbaseRepositoryBean(Bean operations, Set creationalContext, Class repositoryType) { - + protected T create(jakarta.enterprise.context.spi.CreationalContext creationalContext, Class repositoryType) { CouchbaseOperations couchbaseOperations = getDependencyInstance(couchbaseOperationsBean, CouchbaseOperations.class); RepositoryOperationsMapping couchbaseOperationsMapping = new RepositoryOperationsMapping(couchbaseOperations); @@ -75,4 +73,5 @@ protected T create(CreationalContext creationalContext, Class repositoryTy public Class getScope() { return couchbaseOperationsBean.getScope(); } + } diff --git a/src/main/java/org/springframework/data/couchbase/repository/cdi/CouchbaseRepositoryExtension.java b/src/main/java/org/springframework/data/couchbase/repository/cdi/CouchbaseRepositoryExtension.java index 5800cdb5b..e00f9d422 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/cdi/CouchbaseRepositoryExtension.java +++ b/src/main/java/org/springframework/data/couchbase/repository/cdi/CouchbaseRepositoryExtension.java @@ -22,13 +22,12 @@ import java.util.Map; import java.util.Set; -import javax.enterprise.event.Observes; -import javax.enterprise.inject.UnsatisfiedResolutionException; -import javax.enterprise.inject.spi.AfterBeanDiscovery; -import javax.enterprise.inject.spi.Bean; -import javax.enterprise.inject.spi.BeanManager; -import javax.enterprise.inject.spi.ProcessBean; - +import jakarta.enterprise.event.Observes; +import jakarta.enterprise.inject.UnsatisfiedResolutionException; +import jakarta.enterprise.inject.spi.AfterBeanDiscovery; +import jakarta.enterprise.inject.spi.Bean; +import jakarta.enterprise.inject.spi.BeanManager; +import jakarta.enterprise.inject.spi.ProcessBean; import org.springframework.data.couchbase.core.CouchbaseOperations; import org.springframework.data.repository.cdi.CdiRepositoryBean; import org.springframework.data.repository.cdi.CdiRepositoryExtensionSupport; @@ -67,6 +66,7 @@ void processBean(@Observes ProcessBean processBean) { * * @param beanManager The BeanManager instance. */ + void afterBeanDiscovery(@Observes AfterBeanDiscovery afterBeanDiscovery, BeanManager beanManager) { for (Map.Entry, Set> entry : getRepositoryTypes()) { diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/CouchbaseQueryExecution.java b/src/main/java/org/springframework/data/couchbase/repository/query/CouchbaseQueryExecution.java index 9a98018fd..771455288 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/CouchbaseQueryExecution.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/CouchbaseQueryExecution.java @@ -26,7 +26,7 @@ import org.springframework.data.domain.Slice; import org.springframework.data.domain.SliceImpl; 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; /** diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/N1qlCountQueryCreator.java b/src/main/java/org/springframework/data/couchbase/repository/query/N1qlCountQueryCreator.java index 04fd39093..526fcfd53 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/N1qlCountQueryCreator.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/N1qlCountQueryCreator.java @@ -58,10 +58,6 @@ public Sort getSort() { return Sort.unsorted(); } - public Optional> getDynamicProjection() { - return delegate.getDynamicProjection(); - } - @Override public Class findDynamicProjection() { return delegate.findDynamicProjection(); diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/N1qlQueryCreator.java b/src/main/java/org/springframework/data/couchbase/repository/query/N1qlQueryCreator.java index e4b3279e1..1a9b5e444 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/N1qlQueryCreator.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/N1qlQueryCreator.java @@ -69,7 +69,7 @@ public N1qlQueryCreator(final PartTree tree, final ParameterAccessor accessor, f this.queryMethod = queryMethod; this.converter = converter; this.bucketName = bucketName; - this.entity = converter.getMappingContext().getPersistentEntity(queryMethod.getReturnedObjectType()); + this.entity = converter.getMappingContext().getPersistentEntity(queryMethod.getEntityInformation().getJavaType()); } @Override diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/StringBasedN1qlQueryParser.java b/src/main/java/org/springframework/data/couchbase/repository/query/StringBasedN1qlQueryParser.java index 3aa200729..576cb9f8b 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/StringBasedN1qlQueryParser.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/StringBasedN1qlQueryParser.java @@ -33,6 +33,7 @@ import org.slf4j.LoggerFactory; import org.springframework.data.couchbase.core.convert.CouchbaseConverter; 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.repository.Query; import org.springframework.data.couchbase.repository.query.support.N1qlUtils; @@ -44,7 +45,6 @@ import org.springframework.data.repository.query.ParameterAccessor; import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; import org.springframework.data.repository.query.ReturnedType; -import org.springframework.data.util.TypeInformation; import org.springframework.expression.EvaluationContext; import org.springframework.expression.common.TemplateParserContext; import org.springframework.expression.spel.standard.SpelExpressionParser; @@ -130,10 +130,11 @@ public StringBasedN1qlQueryParser(String statement, CouchbaseQueryMethod queryMe this.queryMethod = queryMethod; this.couchbaseConverter = couchbaseConverter; String collection = queryMethod.getCollection(); - this.statementContext = createN1qlSpelValues(bucketName, collection, null, null, typeField, typeValue, false, null, - null); - this.countContext = createN1qlSpelValues(bucketName, collection, null, null, typeField, typeValue, true, null, - null); + this.statementContext = createN1qlSpelValues(bucketName, collection, + queryMethod.getEntityInformation().getJavaType(), queryMethod.getReturnedObjectType(), typeField, typeValue, + false, 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); checkPlaceholders(this.parsedExpression.toString()); } @@ -161,12 +162,11 @@ public N1qlSpelValues createN1qlSpelValues(String bucketName, String collection, String b = collection != null ? collection : bucketName; Assert.isTrue(!(distinctFields != null && fields != null), "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 count = "COUNT(*) AS " + CountFragment.COUNT_ALIAS; String selectEntity; if (distinctFields != null) { - String distinctFieldsStr = distinctFields.length == 0 ? projectedFields : getDistinctFields(distinctFields); + String distinctFieldsStr = getProjectedOrDistinctFields(b, domainClass, fields, distinctFields); if (isCount) { selectEntity = "SELECT COUNT( DISTINCT {" + distinctFieldsStr + "} ) " + CountFragment.COUNT_ALIAS + " FROM " + i(b); @@ -176,6 +176,7 @@ public N1qlSpelValues createN1qlSpelValues(String bucketName, String collection, } else if (isCount) { selectEntity = "SELECT " + count + " FROM " + i(b); } else { + String projectedFields = getProjectedOrDistinctFields(b, domainClass, fields, distinctFields); selectEntity = "SELECT " + entity + (!projectedFields.isEmpty() ? ", " : " ") + projectedFields + " FROM " + i(b); } String typeSelection = "`" + typeField + "` = \"" + typeValue + "\""; @@ -186,65 +187,63 @@ public N1qlSpelValues createN1qlSpelValues(String bucketName, String collection, return new N1qlSpelValues(selectEntity, entity, b, typeSelection, delete, returning); } - private String getDistinctFields(String... distinctFields) { - return i(distinctFields).toString(); - } - - private String getProjectedFields(String b, Class resultClass, String[] fields) { - + private String getProjectedOrDistinctFields(String b, Class resultClass, String[] fields, String[] distinctFields) { + if (distinctFields != null && distinctFields.length != 0) { + return i(distinctFields).toString(); + } String projectedFields = i(b) + ".*"; if (resultClass != null) { PersistentEntity persistentEntity = couchbaseConverter.getMappingContext().getPersistentEntity(resultClass); StringBuilder sb = new StringBuilder(); - getProjectedFieldsInternal(b, null, sb, persistentEntity.getTypeInformation(), fields/*, ""*/); + getProjectedFieldsInternal(b, null, sb, persistentEntity, fields, distinctFields != null); projectedFields = sb.toString(); } return projectedFields; } 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 fieldList = fields != null ? new HashSet<>(Arrays.asList(fields)) : null; - PersistentEntity persistentEntity = couchbaseConverter.getMappingContext().getPersistentEntity(resultClass); - // CouchbasePersistentProperty property = path.getLeafProperty(); - persistentEntity.doWithProperties(new PropertyHandler() { - @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())) { - PersistentPropertyPath path = couchbaseConverter.getMappingContext() - .getPersistentPropertyPath(prop.getName(), resultClass.getType()); - projectField = N1qlQueryCreator.addMetaIfRequired(bucketName, path, prop, persistentEntity).toString(); - if (sb.length() > 0) { - sb.append(", "); - } - sb.append(projectField); // from N1qlQueryCreator - } + // do not include the id and cas metadata fields. - if (fieldList != null) { - fieldList.remove(prop.getFieldName()); + persistentEntity.doWithProperties((PropertyHandler) prop -> { + if (prop == persistentEntity.getIdProperty() && parent == null) { + return; + } + 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 path = couchbaseConverter.getMappingContext() + .getPersistentPropertyPath(prop.getName(), persistentEntity.getTypeInformation().getType()); + projectField = N1qlQueryCreator.addMetaIfRequired(bucketName, path, prop, persistentEntity).toString(); + if (sb.length() > 0) { + sb.append(", "); } - // 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 { + 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. // needs further discussion as removing a field from an entity could cause this and not necessarily be an error diff --git a/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension b/src/main/resources/META-INF/services/jakarta.enterprise.inject.spi.Extension similarity index 100% rename from src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension rename to src/main/resources/META-INF/services/jakarta.enterprise.inject.spi.Extension diff --git a/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateQueryCollectionIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateQueryCollectionIntegrationTests.java index c84e09eae..4b4543eae 100644 --- a/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateQueryCollectionIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateQueryCollectionIntegrationTests.java @@ -349,20 +349,15 @@ void distinctReactive() { assertEquals(7, airports2.size()); // 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" }) .as(Airport.class).withConsistency(QueryScanConsistency.REQUEST_PLUS).inCollection(collectionName).count() .block(); assertEquals(2, count1); - // count( distinct (all fields in icaoClass) // which only has one field - // not currently possible to have multiple fields in COUNT(DISTINCT field1, field2, ... ) due to MB43475 - Class icaoClass = (new Object() { - String icao; - }).getClass(); - long count2 = (long) reactiveCouchbaseTemplate.findByQuery(Airport.class).distinct(new String[] {}).as(icaoClass) + // count (distinct { iata, icao } ) + Long count2 = reactiveCouchbaseTemplate.findByQuery(Airport.class).distinct(new String[] {"iata", "icao"}) .withConsistency(QueryScanConsistency.REQUEST_PLUS).inCollection(collectionName).count().block(); - assertEquals(2, count2); + assertEquals(7, count2); } finally { reactiveCouchbaseTemplate.removeById().inCollection(collectionName) diff --git a/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateQueryIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateQueryIntegrationTests.java index f7987c6a0..7c1c7b85e 100644 --- a/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateQueryIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateQueryIntegrationTests.java @@ -265,15 +265,6 @@ void distinct() { .as(Airport.class).withConsistency(QueryScanConsistency.REQUEST_PLUS).count(); 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 { couchbaseTemplate.removeById() .all(Arrays.stream(iatas).map((iata) -> "airports::" + iata).collect(Collectors.toSet())); @@ -305,19 +296,14 @@ void distinctReactive() { assertEquals(7, airports2.size()); // 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(); assertEquals(2, count1); - // count( distinct (all fields in icaoClass) // which only has one field - // not currently possible to have multiple fields in COUNT(DISTINCT field1, field2, ... ) due to MB43475 - Class icaoClass = (new Object() { - String icao; - }).getClass(); - long count2 = (long) reactiveCouchbaseTemplate.findByQuery(Airport.class).distinct(new String[] {}).as(icaoClass) + // count( distinct { icao, iata } ) + Long count2 = reactiveCouchbaseTemplate.findByQuery(Airport.class).distinct(new String[] { "icao", "iata" }) .withConsistency(QueryScanConsistency.REQUEST_PLUS).count().block(); - assertEquals(2, count2); + assertEquals(7, count2); } finally { reactiveCouchbaseTemplate.removeById() diff --git a/src/test/java/org/springframework/data/couchbase/core/query/QueryCriteriaTests.java b/src/test/java/org/springframework/data/couchbase/core/query/QueryCriteriaTests.java index fe7edbeb6..226a5b3aa 100644 --- a/src/test/java/org/springframework/data/couchbase/core/query/QueryCriteriaTests.java +++ b/src/test/java/org/springframework/data/couchbase/core/query/QueryCriteriaTests.java @@ -24,12 +24,12 @@ import static org.springframework.data.couchbase.core.query.QueryCriteria.where; import static org.springframework.data.couchbase.repository.query.support.N1qlUtils.escapedBucket; +import java.util.Arrays; + import org.junit.jupiter.api.Test; import com.couchbase.client.java.json.JsonArray; -import java.util.Arrays; - /** * @author Mauro Monti */ @@ -85,8 +85,9 @@ void testNestedOrCriteria() { void testNestedNotIn() { 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()); + JsonArray parameters = JsonArray.create(); + assertEquals("`name` = $1 or (`age` > $2 or `country` = $3) and (not( (`state` in ( $4, $5 )) ))", + c.export(new int[1], parameters, null)); } @Test @@ -224,21 +225,22 @@ void testBetween() { @Test void testIn() { String[] args = new String[] { "gump", "davis" }; - QueryCriteria c = where(i("name")).in((Object)args); - assertEquals("`name` in ( [\"gump\",\"davis\"] )", c.export()); + QueryCriteria c = where(i("name")).in((Object) args); + assertEquals("`name` in ( \"gump\", \"davis\" )", c.export()); JsonArray parameters = JsonArray.create(); - assertEquals("`name` in ( $1 )", c.export(new int[1], parameters, null)); - assertEquals(arrayToString(args), parameters.get(0).toString()); + assertEquals("`name` in ( $1, $2 )", c.export(new int[1], parameters, null)); + assertEquals(arrayToString(args), parameters.toString()); } @Test void testNotIn() { String[] args = new String[] { "gump", "davis" }; - QueryCriteria c = where(i("name")).notIn((Object)args); - assertEquals("not( (`name` in ( [\"gump\",\"davis\"] )) )", c.export()); + QueryCriteria c = where(i("name")).notIn((Object) args); + assertEquals("not( (`name` in ( \"gump\", \"davis\" )) )", c.export()); + // this tests creating parameters from the args. JsonArray parameters = JsonArray.create(); - assertEquals("not( (`name` in ( $1 )) )", c.export(new int[1], parameters, null)); - assertEquals(arrayToString(args), parameters.get(0).toString()); + assertEquals("not( (`name` in ( $1, $2 )) )", c.export(new int[1], parameters, null)); + assertEquals(arrayToString(args), parameters.toString()); } @Test @@ -261,7 +263,6 @@ void testKeys() { assertEquals(" USE KEYS []", expression.keys(Arrays.asList()).toString()); } - @Test // https://github.com/spring-projects/spring-data-couchbase/issues/1066 void testCriteriaCorrectlyEscapedWhenUsingMetaOnLHS() { final String bucketName = "sample-bucket"; diff --git a/src/test/java/org/springframework/data/couchbase/core/query/ReactiveCouchbaseTemplateQueryCollectionIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/core/query/ReactiveCouchbaseTemplateQueryCollectionIntegrationTests.java index afc7f5c15..a6306e9a4 100644 --- a/src/test/java/org/springframework/data/couchbase/core/query/ReactiveCouchbaseTemplateQueryCollectionIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/core/query/ReactiveCouchbaseTemplateQueryCollectionIntegrationTests.java @@ -344,20 +344,15 @@ void distinctReactive() { assertEquals(7, airports2.size()); // 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" }) .as(Airport.class).withConsistency(QueryScanConsistency.REQUEST_PLUS).inCollection(collectionName).count() .block(); assertEquals(2, count1); - // count( distinct (all fields in icaoClass) // which only has one field - // not currently possible to have multiple fields in COUNT(DISTINCT field1, field2, ... ) due to MB43475 - Class icaoClass = (new Object() { - String icao; - }).getClass(); - long count2 = (long) reactiveCouchbaseTemplate.findByQuery(Airport.class).distinct(new String[] {}).as(icaoClass) + // count( distinct { iata, icao } ) + Long count2 = reactiveCouchbaseTemplate.findByQuery(Airport.class).distinct(new String[] {"iata","icao"}) .withConsistency(QueryScanConsistency.REQUEST_PLUS).inCollection(collectionName).count().block(); - assertEquals(2, count2); + assertEquals(7, count2); } finally { reactiveCouchbaseTemplate.removeById().inCollection(collectionName) diff --git a/src/test/java/org/springframework/data/couchbase/domain/Airport.java b/src/test/java/org/springframework/data/couchbase/domain/Airport.java index 041436264..b6d10d2fb 100644 --- a/src/test/java/org/springframework/data/couchbase/domain/Airport.java +++ b/src/test/java/org/springframework/data/couchbase/domain/Airport.java @@ -16,6 +16,7 @@ package org.springframework.data.couchbase.domain; +import jakarta.validation.constraints.Max; import org.springframework.data.annotation.CreatedBy; import org.springframework.data.annotation.Id; import org.springframework.data.annotation.PersistenceConstructor; @@ -24,8 +25,6 @@ import org.springframework.data.couchbase.core.mapping.Document; import org.springframework.data.couchbase.core.mapping.Expiration; -import javax.validation.constraints.Max; - /** * Airport entity * diff --git a/src/test/java/org/springframework/data/couchbase/domain/AirportRepository.java b/src/test/java/org/springframework/data/couchbase/domain/AirportRepository.java index 327557bd2..ca4267bb8 100644 --- a/src/test/java/org/springframework/data/couchbase/domain/AirportRepository.java +++ b/src/test/java/org/springframework/data/couchbase/domain/AirportRepository.java @@ -129,6 +129,18 @@ Long countFancyExpression(@Param("projectIds") List projectIds, @Param(" @Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND iata != $1") Page 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 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 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 getStringArrays(); + @ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS) Optional findByIdAndIata(String id, String iata); diff --git a/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryQueryIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryQueryIntegrationTests.java index c4d1f9f30..4c812a761 100644 --- a/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryQueryIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryQueryIntegrationTests.java @@ -29,6 +29,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.springframework.data.couchbase.config.BeanNames.COUCHBASE_TEMPLATE; +import jakarta.validation.ConstraintViolationException; import junit.framework.AssertionFailedError; import java.lang.reflect.Method; @@ -45,8 +46,6 @@ import java.util.concurrent.Future; import java.util.stream.Collectors; -import javax.validation.ConstraintViolationException; - import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; @@ -101,7 +100,6 @@ import com.couchbase.client.java.env.ClusterEnvironment; import com.couchbase.client.java.json.JsonArray; 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.query.QueryOptions; import com.couchbase.client.java.query.QueryScanConsistency; @@ -317,16 +315,17 @@ public void saveNotBoundedWithDefaultRepository() { ApplicationContext ac = new AnnotationConfigApplicationContext(Config.class); // the Config class has been modified, these need to be loaded again CouchbaseTemplate couchbaseTemplateRP = (CouchbaseTemplate) ac.getBean(COUCHBASE_TEMPLATE); - AirportRepositoryScanConsistencyTest airportRepositoryRP = (AirportRepositoryScanConsistencyTest) ac.getBean("airportRepositoryScanConsistencyTest"); + AirportRepositoryScanConsistencyTest airportRepositoryRP = (AirportRepositoryScanConsistencyTest) ac + .getBean("airportRepositoryScanConsistencyTest"); List sizeBeforeTest = airportRepositoryRP.findAll(); 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); List allSaved = airportRepositoryRP.findAll(); 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 @@ -334,19 +333,19 @@ public void saveRequestPlusWithDefaultRepository() { ApplicationContext ac = new AnnotationConfigApplicationContext(ConfigRequestPlus.class); // 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 sizeBeforeTest = airportRepositoryRP.findAll(); 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); List allSaved = airportRepositoryRP.findAll(); 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 void findByTypeAlias() { Airport vie = null; @@ -545,6 +544,25 @@ public void testExpiryAnnotation() { 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 iatas = airportRepository.getStrings(); + assertEquals(Arrays.asList(airport1.getIata(), airport2.getIata()), iatas); + List 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 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 void count() { String[] iatas = { "JFK", "IAD", "SFO", "SJC", "SEA", "LAX", "PHX" }; @@ -886,13 +904,13 @@ public LocalValidatorFactoryBean validator() { @Bean public ValidatingCouchbaseEventListener validationEventListener() { - return new ValidatingCouchbaseEventListener(validator()); + return new ValidatingCouchbaseEventListener( validator()); } } @Configuration @EnableCouchbaseRepositories("org.springframework.data.couchbase") - @EnableCouchbaseAuditing(auditorAwareRef = "auditorAwareRef", dateTimeProviderRef = "dateTimeProviderRef") + // @EnableCouchbaseAuditing(auditorAwareRef = "auditorAwareRef", dateTimeProviderRef = "dateTimeProviderRef") static class ConfigRequestPlus extends AbstractCouchbaseConfiguration { @Override diff --git a/src/test/java/org/springframework/data/couchbase/repository/query/N1qlQueryCreatorTests.java b/src/test/java/org/springframework/data/couchbase/repository/query/N1qlQueryCreatorTests.java index 52fe975bf..05dfe8dae 100644 --- a/src/test/java/org/springframework/data/couchbase/repository/query/N1qlQueryCreatorTests.java +++ b/src/test/java/org/springframework/data/couchbase/repository/query/N1qlQueryCreatorTests.java @@ -109,7 +109,7 @@ void queryParametersArray() throws Exception { Query query = creator.createQuery(); // 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(); expected.buildQueryOptions(null, null).build().injectParams(expectedOptions); JsonObject actualOptions = JsonObject.create(); @@ -132,7 +132,7 @@ void queryParametersJsonArray() throws Exception { Query query = creator.createQuery(); 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(); expected.buildQueryOptions(null, null).build().injectParams(expectedOptions); JsonObject actualOptions = JsonObject.create(); @@ -156,7 +156,7 @@ void queryParametersList() throws Exception { 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(); expected.buildQueryOptions(null, null).build().injectParams(expectedOptions); JsonObject actualOptions = JsonObject.create(); diff --git a/src/test/java/org/springframework/data/couchbase/repository/query/StringN1qlQueryCreatorMockedTests.java b/src/test/java/org/springframework/data/couchbase/repository/query/StringN1qlQueryCreatorMockedTests.java index a0de49fe0..f1ac9b4ee 100644 --- a/src/test/java/org/springframework/data/couchbase/repository/query/StringN1qlQueryCreatorMockedTests.java +++ b/src/test/java/org/springframework/data/couchbase/repository/query/StringN1qlQueryCreatorMockedTests.java @@ -85,7 +85,7 @@ void createsQueryCorrectly() throws Exception { Query query = creator.createQuery(); 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)); } @@ -104,7 +104,7 @@ void createsQueryCorrectly2() throws Exception { Query query = creator.createQuery(); 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)); }