From af62c228dcce773c275b476e73d554b4b1d6c2b4 Mon Sep 17 00:00:00 2001 From: mikereiche Date: Thu, 6 Jan 2022 12:36:27 -0800 Subject: [PATCH] Incorporate changes from 5.0.x and bump Couchbase SDK. Closes #1286. --- pom.xml | 4 +- .../cache/CouchbaseCacheConfiguration.java | 4 +- .../cache/CouchbaseCacheManager.java | 4 +- .../AbstractCouchbaseConfiguration.java | 8 +- .../core/CouchbaseExceptionTranslator.java | 4 +- .../core/CouchbaseTemplateSupport.java | 26 ++++- .../ReactiveCouchbaseTemplateSupport.java | 27 ++++- .../convert/AbstractCouchbaseConverter.java | 4 +- .../convert/CouchbaseCustomConversions.java | 4 +- .../convert/CouchbaseJsr310Converters.java | 31 +++++- .../core/convert/CustomConversions.java | 4 +- .../convert/MappingCouchbaseConverter.java | 5 +- .../BasicCouchbasePersistentProperty.java | 6 +- .../core/mapping/CouchbaseDocument.java | 12 +-- .../couchbase/core/mapping/CouchbaseList.java | 6 +- .../mapping/CouchbasePersistentEntity.java | 6 +- .../mapping/CouchbasePersistentProperty.java | 4 +- .../core/mapping/CouchbaseStorable.java | 4 +- .../ValidatingCouchbaseEventListener.java | 2 +- .../core/query/N1qlSecondaryIndexed.java | 6 +- .../data/couchbase/core/query/Query.java | 3 +- .../couchbase/core/query/QueryCriteria.java | 41 ++++--- .../couchbase/core/query/StringQuery.java | 4 +- .../couchbase/core/query/ViewIndexed.java | 6 +- .../couchbase/core/query/WithConsistency.java | 4 +- .../data/couchbase/core/support/AnyId.java | 4 +- .../couchbase/core/support/AnyIdReactive.java | 4 +- .../couchbase/core/support/OneAndAll.java | 4 +- .../core/support/OneAndAllEntity.java | 4 +- .../core/support/OneAndAllEntityReactive.java | 4 +- .../core/support/OneAndAllExists.java | 6 +- .../core/support/OneAndAllExistsReactive.java | 5 +- .../couchbase/core/support/OneAndAllId.java | 4 +- .../core/support/OneAndAllIdReactive.java | 4 +- .../core/support/OneAndAllReactive.java | 4 +- .../repository/DynamicProxyable.java | 4 +- .../data/couchbase/repository/Query.java | 20 ++-- .../repository/query/CountFragment.java | 6 +- .../repository/query/N1qlQueryCreator.java | 4 +- .../repository/query/OldN1qlQueryCreator.java | 3 +- .../query/StringBasedN1qlQueryParser.java | 101 +++++++++--------- ...mplateQueryCollectionIntegrationTests.java | 13 +-- ...ouchbaseTemplateQueryIntegrationTests.java | 24 +---- .../core/query/QueryCriteriaTests.java | 29 ++--- ...mplateQueryCollectionIntegrationTests.java | 13 +-- .../couchbase/domain/AirportRepository.java | 24 +++-- ...chbaseRepositoryQueryIntegrationTests.java | 48 ++++++--- .../query/N1qlQueryCreatorTests.java | 8 +- .../StringN1qlQueryCreatorMockedTests.java | 6 +- .../couchbase/util/JavaIntegrationTests.java | 37 ++++++- 50 files changed, 354 insertions(+), 258 deletions(-) diff --git a/pom.xml b/pom.xml index 821942c19..9f25d2a2e 100644 --- a/pom.xml +++ b/pom.xml @@ -18,8 +18,8 @@ - 3.2.3 - 3.2.3 + 3.2.4 + 3.2.4 2.7.0-SNAPSHOT spring.data.couchbase diff --git a/src/main/java/org/springframework/data/couchbase/cache/CouchbaseCacheConfiguration.java b/src/main/java/org/springframework/data/couchbase/cache/CouchbaseCacheConfiguration.java index 841815b41..031e8b445 100644 --- a/src/main/java/org/springframework/data/couchbase/cache/CouchbaseCacheConfiguration.java +++ b/src/main/java/org/springframework/data/couchbase/cache/CouchbaseCacheConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors + * Copyright 2012-2022 the original author or authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -64,7 +64,7 @@ public static CouchbaseCacheConfiguration defaultCacheConfig() { * * @param registry must not be {@literal null}. */ public static void registerDefaultConverters(final ConverterRegistry registry) { diff --git a/src/main/java/org/springframework/data/couchbase/cache/CouchbaseCacheManager.java b/src/main/java/org/springframework/data/couchbase/cache/CouchbaseCacheManager.java index a96c8e161..3ec1f1ff7 100644 --- a/src/main/java/org/springframework/data/couchbase/cache/CouchbaseCacheManager.java +++ b/src/main/java/org/springframework/data/couchbase/cache/CouchbaseCacheManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors + * Copyright 2012-2022 the original author or authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -228,7 +228,7 @@ public CouchbaseCacheManagerBuilder withCacheConfiguration(String cacheName, /** * Disable in-flight {@link org.springframework.cache.Cache} creation for unconfigured caches. - *

+ *

* {@link CouchbaseCacheManager#getMissingCache(String)} returns {@literal null} for any unconfigured * {@link org.springframework.cache.Cache} instead of a new {@link CouchbaseCache} instance. This allows eg. * {@link org.springframework.cache.support.CompositeCacheManager} to chime in. diff --git a/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java b/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java index 2c9ff9718..6982d64f4 100644 --- a/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java +++ b/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors + * Copyright 2012-2022 the original author or authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -254,8 +254,6 @@ public String typeKey() { /** * Creates a {@link MappingCouchbaseConverter} using the configured {@link #couchbaseMappingContext}. - * - * @throws Exception on Bean construction failure. */ @Bean public MappingCouchbaseConverter mappingCouchbaseConverter(CouchbaseMappingContext couchbaseMappingContext, @@ -283,7 +281,6 @@ public TranslationService couchbaseTranslationService() { /** * Creates a {@link CouchbaseMappingContext} equipped with entity classes scanned from the mapping base package. * - * @throws Exception on Bean construction failure. */ @Bean public CouchbaseMappingContext couchbaseMappingContext(CustomConversions customConversions) throws Exception { @@ -299,7 +296,6 @@ public CouchbaseMappingContext couchbaseMappingContext(CustomConversions customC /** * Creates a {@link ObjectMapper} for the jsonSerializer of the ClusterEnvironment * - * @throws Exception on Bean construction failure. * @return ObjectMapper */ @@ -337,11 +333,9 @@ public CustomConversions customConversions() { /** * Return the base package to scan for mapped {@link Document}s. Will return the package name of the configuration * class (the concrete class, not this one here) by default. - *

*

* So if you have a {@code com.acme.AppConfig} extending {@link AbstractCouchbaseConfiguration} the base package will * be considered {@code com.acme} unless the method is overridden to implement alternate behavior. - *

* * @return the base package to scan for mapped {@link Document} classes or {@literal null} to not enable scanning for * entities. diff --git a/src/main/java/org/springframework/data/couchbase/core/CouchbaseExceptionTranslator.java b/src/main/java/org/springframework/data/couchbase/core/CouchbaseExceptionTranslator.java index 3ae3cb21a..264c8334e 100644 --- a/src/main/java/org/springframework/data/couchbase/core/CouchbaseExceptionTranslator.java +++ b/src/main/java/org/springframework/data/couchbase/core/CouchbaseExceptionTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors + * Copyright 2012-2022 the original author or authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,7 +33,7 @@ /** * Simple {@link PersistenceExceptionTranslator} for Couchbase. - *

+ *

* Convert the given runtime exception to an appropriate exception from the {@code org.springframework.dao} hierarchy. * Return {@literal null} if no translation is appropriate: any other exception may have resulted from user code, and * should not be translated. 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..6754f0297 100644 --- a/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplateSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplateSupport.java @@ -1,6 +1,5 @@ /* -/* - * Copyright 2012-2021 the original author or authors + * Copyright 2012-2022 the original author or authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +16,9 @@ package org.springframework.data.couchbase.core; +import java.util.Map; +import java.util.Set; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; @@ -40,6 +42,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 +87,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 +112,12 @@ 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; + } + return mappingContext.getPersistentEntity(entityClass); + } @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..7805b2b94 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplateSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplateSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors + * Copyright 2012-2022 the original author or authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,9 @@ import reactor.core.publisher.Mono; +import java.util.Map; +import java.util.Set; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; @@ -42,6 +45,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 +88,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 +116,13 @@ public Mono decodeEntity(String id, String source, long cas, Class ent }); } + CouchbasePersistentEntity couldBePersistentEntity(Class entityClass) { + if (ClassUtils.isPrimitiveOrWrapper(entityClass) || entityClass == String.class) { + return null; + } + return mappingContext.getPersistentEntity(entityClass); + } + @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..e216779ff 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors + * Copyright 2012-2022 the original author or authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,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/CouchbaseCustomConversions.java b/src/main/java/org/springframework/data/couchbase/core/convert/CouchbaseCustomConversions.java index bfffbe514..ba265d4e4 100644 --- a/src/main/java/org/springframework/data/couchbase/core/convert/CouchbaseCustomConversions.java +++ b/src/main/java/org/springframework/data/couchbase/core/convert/CouchbaseCustomConversions.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2021 the original author or authors. + * Copyright 2017-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,11 +24,9 @@ /** * Value object to capture custom conversion. - *

*

* Types that can be mapped directly onto JSON are considered simple ones, because they neither need deeper inspection * nor nested conversion. - *

* * @author Michael Nitschinger * @author Oliver Gierke 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..ead6f1ae4 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 @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 the original author or authors. + * Copyright 2017-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -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/CustomConversions.java b/src/main/java/org/springframework/data/couchbase/core/convert/CustomConversions.java index b68dc4f9a..8b19a21ad 100644 --- a/src/main/java/org/springframework/data/couchbase/core/convert/CustomConversions.java +++ b/src/main/java/org/springframework/data/couchbase/core/convert/CustomConversions.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors + * Copyright 2012-2022 the original author or authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,11 +20,9 @@ /** * Value object to capture custom conversion. - *

*

* Types that can be mapped directly onto JSON are considered simple ones, because they neither need deeper inspection * nor nested conversion. - *

* * @author Michael Nitschinger * @author Oliver Gierke 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..6436cba0b 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors + * Copyright 2012-2022 the original author or authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -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; @@ -816,7 +816,6 @@ public void setApplicationContext(ApplicationContext applicationContext) { /** * COPIED Set the {@link EntityCallbacks} instance to use when invoking * {@link org.springframework.data.mapping.callback.EntityCallback callbacks} like the {@link AfterConvertCallback}. - *

* Overrides potentially existing {@link EntityCallbacks}. * * @param entityCallbacks must not be {@literal null}. diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/BasicCouchbasePersistentProperty.java b/src/main/java/org/springframework/data/couchbase/core/mapping/BasicCouchbasePersistentProperty.java index d5e61466b..9d1d0b686 100644 --- a/src/main/java/org/springframework/data/couchbase/core/mapping/BasicCouchbasePersistentProperty.java +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/BasicCouchbasePersistentProperty.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors + * Copyright 2012-2022 the original author or authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,11 +32,9 @@ /** * Implements annotated property representations of a given {@link Field} instance. - *

*

* This object is used to gather information out of properties on objects that need to be persisted. For example, it * supports overriding of the actual property name by providing custom annotations. - *

* * @author Michael Nitschinger * @author Mark Paluch @@ -71,7 +69,7 @@ protected Association createAssociation() { /** * Returns the field name of the property. - *

+ *

* The field name can be different from the actual property name by using a custom annotation. */ @Override diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbaseDocument.java b/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbaseDocument.java index 0e2164536..3df69933b 100644 --- a/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbaseDocument.java +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbaseDocument.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors + * Copyright 2012-2022 the original author or authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,18 +23,14 @@ /** * A {@link CouchbaseDocument} is an abstract representation of a document stored inside Couchbase Server. - *

*

* It acts like a {@link TreeMap}, but only allows those types to be written that are supported by the underlying * storage format, which is currently JSON. Note that JSON conversion is not happening here, but performed at a * different stage based on the payload stored in the {@link CouchbaseDocument}. - *

- *

*

* In addition to the actual content, meta data is also stored. This especially refers to the document ID and its * expiration time. Note that this information is not mandatory, since documents can be nested and therefore only the * topmost document most likely has an ID. - *

* * @author Michael Nitschinger */ @@ -114,7 +110,7 @@ public final Object get(final String key) { /** * Returns the current payload, including all recursive elements. - *

+ *

* It either returns the raw results or makes sure that the recusrive elements are also exported properly. * * @return @@ -187,10 +183,8 @@ public final int size(final boolean recursive) { /** * Returns the underlying payload. - *

*

* Note that unlike {@link #export()}, the nested objects are not converted, so the "raw" map is returned. - *

* * @return the underlying payload. */ @@ -268,10 +262,8 @@ public CouchbaseDocument setId(String id) { /** * Verifies that only values of a certain and supported type can be stored. - *

*

* If this is not the case, a {@link IllegalArgumentException} is thrown. - *

* * @param value the object to verify its type. */ diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbaseList.java b/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbaseList.java index 4a1b90e98..a6ba8990f 100644 --- a/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbaseList.java +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbaseList.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors + * Copyright 2012-2022 the original author or authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,11 +25,9 @@ /** * A {@link CouchbaseList} is an abstract list that represents an array stored in a (most of the times JSON) document. - *

*

* This {@link CouchbaseList} is part of the potentially nested structure inside one or more {@link CouchbaseDocument}s. * It can also contain them recursively, depending on how the document is modeled. - *

*/ public class CouchbaseList implements CouchbaseStorable { @@ -145,7 +143,7 @@ public final int size(final boolean recursive) { /** * Returns the current payload, including all recursive elements. - *

+ *

* It either returns the raw results or makes sure that the recusrive elements are also exported properly. * * @return diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbasePersistentEntity.java b/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbasePersistentEntity.java index 8d236d320..f768aab9c 100644 --- a/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbasePersistentEntity.java +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbasePersistentEntity.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors + * Copyright 2012-2022 the original author or authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,7 +34,7 @@ public interface CouchbasePersistentEntity extends PersistentEntity + *

* The Couchbase format for expiration time is: - for TTL < 31 days (<= 30 * 24 * 60 * 60): expressed as a TTL in * seconds - for TTL > 30 days: expressed as Unix UTC time of expiry (number of SECONDS since the Epoch) * @@ -45,7 +45,7 @@ public interface CouchbasePersistentEntity extends PersistentEntity + *

* The Couchbase format for expiration time is: - for TTL < 31 days (<= 30 * 24 * 60 * 60): expressed as a TTL in * seconds - for TTL > 30 days: expressed as Unix UTC time of expiry (number of SECONDS since the Epoch) * diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbasePersistentProperty.java b/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbasePersistentProperty.java index 7525f6c43..dba19b16d 100644 --- a/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbasePersistentProperty.java +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbasePersistentProperty.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors + * Copyright 2012-2022 the original author or authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +27,7 @@ public interface CouchbasePersistentProperty extends PersistentProperty + *

* The field name can be different from the actual property name by using a custom annotation. */ String getFieldName(); diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbaseStorable.java b/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbaseStorable.java index 965acbbb9..b4e5ae15a 100644 --- a/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbaseStorable.java +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbaseStorable.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors + * Copyright 2012-2022 the original author or authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ /** * Marker Interface to identify either a {@link CouchbaseDocument} or a {@link CouchbaseList}. - *

+ *

* This interface will be extended in the future to refactor the needed infrastructure into the common interface. * * @author Michael Nitschinger 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..33f9e12e2 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors + * Copyright 2012-2022 the original author or authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/org/springframework/data/couchbase/core/query/N1qlSecondaryIndexed.java b/src/main/java/org/springframework/data/couchbase/core/query/N1qlSecondaryIndexed.java index a4d1e4039..50b9597f0 100644 --- a/src/main/java/org/springframework/data/couchbase/core/query/N1qlSecondaryIndexed.java +++ b/src/main/java/org/springframework/data/couchbase/core/query/N1qlSecondaryIndexed.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors + * Copyright 2012-2022 the original author or authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,10 +26,10 @@ /** * This annotation is targeted at {@link CouchbaseRepository Repository} interfaces, indicating that the framework * should ensure a N1QL Secondary Index is present when the repository is instantiated. - *

+ *

* Said index will relate to the "type" field (the one bearing type information) and restrict on documents that match * the repository's entity class. - *

+ *

* Be sure to also use {@link N1qlPrimaryIndexed} to make sure the PRIMARY INDEX is there as well. * * @author Simon Baslé diff --git a/src/main/java/org/springframework/data/couchbase/core/query/Query.java b/src/main/java/org/springframework/data/couchbase/core/query/Query.java index cfcbf7d87..e0b38e66a 100644 --- a/src/main/java/org/springframework/data/couchbase/core/query/Query.java +++ b/src/main/java/org/springframework/data/couchbase/core/query/Query.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors + * Copyright 2012-2022 the original author or authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -383,7 +383,6 @@ public QueryOptions buildQueryOptions(QueryOptions options, QueryScanConsistency * This will find annotations included in composed annotations as well. Ideally * * @param method representing the query. - * @return the query with the annotations applied */ public void setMeta(CouchbaseQueryMethod method, Class typeToRead) { meta = OptionsBuilder.buildMeta(method, typeToRead); 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 9726e0737..df9fd644a 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors + * Copyright 2012-2022 the original author or authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,15 +25,13 @@ import java.util.List; import org.springframework.data.couchbase.core.convert.CouchbaseConverter; -import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; -import org.springframework.data.couchbase.core.mapping.CouchbaseList; import org.springframework.lang.Nullable; +import org.springframework.util.CollectionUtils; import com.couchbase.client.core.error.InvalidArgumentException; import com.couchbase.client.java.json.JsonArray; import com.couchbase.client.java.json.JsonObject; import com.couchbase.client.java.json.JsonValue; -import org.springframework.util.CollectionUtils; /** * @author Michael Nitschinger @@ -283,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; } @@ -414,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[] || value instanceof Collection) { - addAsCollection(params, asCollection(value), converter); - } else { - throw iae; - } } + return "$" + (++paramIndexPtr[0]); // these are generated in order } else { JsonObject params = (JsonObject) parameters; @@ -488,7 +496,6 @@ private static Collection asCollection(final Object 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/core/query/StringQuery.java b/src/main/java/org/springframework/data/couchbase/core/query/StringQuery.java index 47248e624..52ed05d13 100644 --- a/src/main/java/org/springframework/data/couchbase/core/query/StringQuery.java +++ b/src/main/java/org/springframework/data/couchbase/core/query/StringQuery.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 the original author or authors. + * Copyright 2017-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,7 +28,7 @@ * *

  * @Query("#{#n1ql.selectEntity} where #{#n1ql.filter} and firstname = $1 and lastname = $2")
- * List getByFirstnameAndLastname(String firstname, String lastname);
+ * List<User> getByFirstnameAndLastname(String firstname, String lastname);
  * 
* * It must include the SELECT ... FROM ... preferably via the #n1ql expression, in addition to any predicates required, diff --git a/src/main/java/org/springframework/data/couchbase/core/query/ViewIndexed.java b/src/main/java/org/springframework/data/couchbase/core/query/ViewIndexed.java index 43b212151..001ecd405 100644 --- a/src/main/java/org/springframework/data/couchbase/core/query/ViewIndexed.java +++ b/src/main/java/org/springframework/data/couchbase/core/query/ViewIndexed.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors + * Copyright 2012-2022 the original author or authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,10 +26,10 @@ /** * This annotation is targeted at {@link CouchbaseRepository Repository} interfaces, indicating that the framework * should ensure a View is present when the repository is instantiated. - *

+ *

* The view must at least be described as a design document name and view name. Default map function will filter * documents on the type associated to the repository, and default reduce function is "_count". - *

+ *

* One can specify a custom reduce function as well as a non-default map function. * * @author Simon Baslé diff --git a/src/main/java/org/springframework/data/couchbase/core/query/WithConsistency.java b/src/main/java/org/springframework/data/couchbase/core/query/WithConsistency.java index e44bc7876..99470174f 100644 --- a/src/main/java/org/springframework/data/couchbase/core/query/WithConsistency.java +++ b/src/main/java/org/springframework/data/couchbase/core/query/WithConsistency.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2020 the original author or authors. + * Copyright 2013-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,7 +30,7 @@ /** * Annotation to set the scan consistency of N1QL queries with Couchbase. This controls whether couchbase waits for all * changes to be processed by an index or whether stale results are acceptable. - *

+ *

* If not set, the default consistency set in {@link AbstractCouchbaseConfiguration#getDefaultConsistency()} is used. * * @author Johannes Jasper. diff --git a/src/main/java/org/springframework/data/couchbase/core/support/AnyId.java b/src/main/java/org/springframework/data/couchbase/core/support/AnyId.java index ea3a7facc..9b77d031a 100644 --- a/src/main/java/org/springframework/data/couchbase/core/support/AnyId.java +++ b/src/main/java/org/springframework/data/couchbase/core/support/AnyId.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 the original author or authors + * Copyright 2020-2022 the original author or authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ import java.util.Collection; /** - * A common interface for those that support one(T), all(Collection) + * A common interface for those that support one(T), all(Collection<T>) * * @author Michael Reiche * @param - the entity class diff --git a/src/main/java/org/springframework/data/couchbase/core/support/AnyIdReactive.java b/src/main/java/org/springframework/data/couchbase/core/support/AnyIdReactive.java index 9bd97ae15..7a387fcb9 100644 --- a/src/main/java/org/springframework/data/couchbase/core/support/AnyIdReactive.java +++ b/src/main/java/org/springframework/data/couchbase/core/support/AnyIdReactive.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 the original author or authors + * Copyright 2020-2022 the original author or authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ import java.util.Collection; /** - * A common interface for those that support one(T), all(Collection) + * A common interface for those that support one(T), all(Collection<T%gt;) * * @author Michael Reiche * @param - the entity class diff --git a/src/main/java/org/springframework/data/couchbase/core/support/OneAndAll.java b/src/main/java/org/springframework/data/couchbase/core/support/OneAndAll.java index cacad7ecd..1cc17c634 100644 --- a/src/main/java/org/springframework/data/couchbase/core/support/OneAndAll.java +++ b/src/main/java/org/springframework/data/couchbase/core/support/OneAndAll.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 the original author or authors + * Copyright 2020-2022 the original author or authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ import java.util.stream.Stream; /** - * A common interface for those that support one(T), all(Collection) + * A common interface for those that support one(T), all(Collection<T>) * * @author Michael Reiche * diff --git a/src/main/java/org/springframework/data/couchbase/core/support/OneAndAllEntity.java b/src/main/java/org/springframework/data/couchbase/core/support/OneAndAllEntity.java index be028fa84..da4f0dcb2 100644 --- a/src/main/java/org/springframework/data/couchbase/core/support/OneAndAllEntity.java +++ b/src/main/java/org/springframework/data/couchbase/core/support/OneAndAllEntity.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 the original author or authors + * Copyright 2020-2022 the original author or authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ import java.util.Collection; /** - * A common interface for those that support one(T), all(Collection) + * A common interface for those that support one(T), all(Collection<T>) * * @author Michael Reiche * diff --git a/src/main/java/org/springframework/data/couchbase/core/support/OneAndAllEntityReactive.java b/src/main/java/org/springframework/data/couchbase/core/support/OneAndAllEntityReactive.java index 67fbba7b1..a4f77fea4 100644 --- a/src/main/java/org/springframework/data/couchbase/core/support/OneAndAllEntityReactive.java +++ b/src/main/java/org/springframework/data/couchbase/core/support/OneAndAllEntityReactive.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 the original author or authors + * Copyright 2020-2022 the original author or authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ import java.util.Collection; /** - * A common interface for those that support one(T), all(Collection) + * A common interface for those that support one(T), all(Collection<T>) * * @author Michael Reiche * @param - the entity class diff --git a/src/main/java/org/springframework/data/couchbase/core/support/OneAndAllExists.java b/src/main/java/org/springframework/data/couchbase/core/support/OneAndAllExists.java index 1b4041ae6..42736507f 100644 --- a/src/main/java/org/springframework/data/couchbase/core/support/OneAndAllExists.java +++ b/src/main/java/org/springframework/data/couchbase/core/support/OneAndAllExists.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 the original author or authors + * Copyright 2020-2022 the original author or authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,11 +21,9 @@ import java.util.stream.Stream; /** - * A common interface for those that support one(T), all(Collection) + * A common interface for those that support one(T), all(Collection<T>) * * @author Michael Reiche - * - * @param - the entity class */ public interface OneAndAllExists { boolean one(String id); diff --git a/src/main/java/org/springframework/data/couchbase/core/support/OneAndAllExistsReactive.java b/src/main/java/org/springframework/data/couchbase/core/support/OneAndAllExistsReactive.java index 67487c3b5..03d244d45 100644 --- a/src/main/java/org/springframework/data/couchbase/core/support/OneAndAllExistsReactive.java +++ b/src/main/java/org/springframework/data/couchbase/core/support/OneAndAllExistsReactive.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 the original author or authors + * Copyright 2020-2022 the original author or authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,11 +21,10 @@ import java.util.Map; /** - * A common interface for those that support one(T), all(Collection) + * A common interface for those that support one(T), all(Collection<T>) * * @author Michael Reiche * - * @param - the entity class */ public interface OneAndAllExistsReactive { Mono one(String id); diff --git a/src/main/java/org/springframework/data/couchbase/core/support/OneAndAllId.java b/src/main/java/org/springframework/data/couchbase/core/support/OneAndAllId.java index 370388c02..104849003 100644 --- a/src/main/java/org/springframework/data/couchbase/core/support/OneAndAllId.java +++ b/src/main/java/org/springframework/data/couchbase/core/support/OneAndAllId.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 the original author or authors + * Copyright 2020-2022 the original author or authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ import java.util.Collection; /** - * A common interface for those that support one(String), all(Collection) + * A common interface for those that support one(String), all(Collection<String>) * * @author Michael Reiche * diff --git a/src/main/java/org/springframework/data/couchbase/core/support/OneAndAllIdReactive.java b/src/main/java/org/springframework/data/couchbase/core/support/OneAndAllIdReactive.java index 5c8b74f29..b7e000d65 100644 --- a/src/main/java/org/springframework/data/couchbase/core/support/OneAndAllIdReactive.java +++ b/src/main/java/org/springframework/data/couchbase/core/support/OneAndAllIdReactive.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 the original author or authors + * Copyright 2020-2022 the original author or authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ import java.util.Collection; /** - * A common interface for those that support one(String), all(Collection) + * A common interface for those that support one(String), all(Collection<String>) * * @author Michael Reiche * @param - the entity class diff --git a/src/main/java/org/springframework/data/couchbase/core/support/OneAndAllReactive.java b/src/main/java/org/springframework/data/couchbase/core/support/OneAndAllReactive.java index 7e5414dab..ef477759c 100644 --- a/src/main/java/org/springframework/data/couchbase/core/support/OneAndAllReactive.java +++ b/src/main/java/org/springframework/data/couchbase/core/support/OneAndAllReactive.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 the original author or authors + * Copyright 2020-2022 the original author or authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,7 @@ import java.util.stream.Stream; /** - * A common interface for those that support one(T), all(Collection) + * A common interface for those that support one(T), all(Collection<T>) * * @author Michael Reiche * @param - the entity class diff --git a/src/main/java/org/springframework/data/couchbase/repository/DynamicProxyable.java b/src/main/java/org/springframework/data/couchbase/repository/DynamicProxyable.java index c97f9b9ed..01e56fb32 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/DynamicProxyable.java +++ b/src/main/java/org/springframework/data/couchbase/repository/DynamicProxyable.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2021 the original author or authors. + * Copyright 2017-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,7 +26,7 @@ /** * The generic parameter needs to be REPO which is either a CouchbaseRepository parameterized on T,ID or a * ReactiveCouchbaseRepository parameterized on T,ID. i.e.: interface AirportRepository extends - * CouchbaseRepository, DynamicProxyable + * CouchbaseRepository<Airport, String>, DynamicProxyable<AirportRepository> * * @param * @author Michael Reiche diff --git a/src/main/java/org/springframework/data/couchbase/repository/Query.java b/src/main/java/org/springframework/data/couchbase/repository/Query.java index 21562a787..39eab1425 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/Query.java +++ b/src/main/java/org/springframework/data/couchbase/repository/Query.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors + * Copyright 2012-2022 the original author or authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,23 +23,25 @@ import org.springframework.data.annotation.QueryAnnotation; import org.springframework.data.couchbase.core.CouchbaseTemplate; +import org.springframework.data.couchbase.repository.query.StringBasedN1qlQueryParser; +import org.springframework.data.couchbase.repository.query.StringN1qlQueryCreator; /** - * Annotation to support the use of N1QL queries with Couchbase. - *

+ * Annotation to support the use of N1QL queries with Couchbase. Queries are crated by {@link StringN1qlQueryCreator} + *

* Using it without parameter will resolve the query from the method name. Providing a value (an inline N1QL statement) * will execute that statement instead. - *

+ *

* In this case, one can use a placeholder notation of {@code ?0}, {@code ?1} and so on. - *

+ *

* Also, SpEL in the form #{spelExpression} is supported, including the following N1QL variables that will * be replaced by the underlying {@link CouchbaseTemplate} associated information: *

    - *
  • {@value StringN1qlBasedQuery#SPEL_SELECT_FROM_CLAUSE} (see {@link StringN1qlBasedQuery#SPEL_SELECT_FROM_CLAUSE}) + *
  • {@value StringBasedN1qlQueryParser#SPEL_SELECT_FROM_CLAUSE} (see {@link StringBasedN1qlQueryParser#SPEL_SELECT_FROM_CLAUSE}) *
  • - *
  • {@value StringN1qlBasedQuery#SPEL_BUCKET} (see {@link StringN1qlBasedQuery#SPEL_BUCKET})
  • - *
  • {@value StringN1qlBasedQuery#SPEL_ENTITY} (see {@link StringN1qlBasedQuery#SPEL_ENTITY})
  • - *
  • {@value StringN1qlBasedQuery#SPEL_FILTER} (see {@link StringN1qlBasedQuery#SPEL_FILTER})
  • + *
  • {@value StringBasedN1qlQueryParser#SPEL_BUCKET} (see {@link StringBasedN1qlQueryParser#SPEL_BUCKET})
  • + *
  • {@value StringBasedN1qlQueryParser#SPEL_ENTITY} (see {@link StringBasedN1qlQueryParser#SPEL_ENTITY})
  • + *
  • {@value StringBasedN1qlQueryParser#SPEL_FILTER} (see {@link StringBasedN1qlQueryParser#SPEL_FILTER})
  • *
* * @author Simon Baslé. diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/CountFragment.java b/src/main/java/org/springframework/data/couchbase/repository/query/CountFragment.java index 918192c95..0503e1b4d 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/CountFragment.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/CountFragment.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors + * Copyright 2012-2022 the original author or authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,9 +18,9 @@ /** * An utility entity that allows to extract total row count out of a COUNT(*) N1QL query. - *

+ *

* The query should use the COUNT_ALIAS, eg.: SELECT COUNT(*) AS count FROM default; - *

+ *

* This ensures that the framework will be able to map the JSON result to this {@link CountFragment} class so that it * can be used. */ 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..8af78c70c 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors + * Copyright 2012-2022 the original author or authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -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/OldN1qlQueryCreator.java b/src/main/java/org/springframework/data/couchbase/repository/query/OldN1qlQueryCreator.java index 0bda1358d..a8bbe2215 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/OldN1qlQueryCreator.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/OldN1qlQueryCreator.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors + * Copyright 2012-2022 the original author or authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -75,7 +75,6 @@ *

    *
  • NEAR, WITHIN: geospatial is not supported in N1QL as of now
  • *
- *

* * @author Simon Baslé * @author Subhashni Balakrishnan 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..6a5e4c584 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 @@ -1,5 +1,5 @@ /* - * Copyright 2017-2021 the original author or authors. + * Copyright 2017-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -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 + "\""; @@ -183,68 +184,66 @@ public N1qlSpelValues createN1qlSpelValues(String bucketName, String collection, String delete = N1QLExpression.delete().from(b).toString(); String returning = " returning " + N1qlUtils.createReturningExpressionForDelete(b).toString(); - return new N1qlSpelValues(selectEntity, entity, b, typeSelection, delete, returning); + return new N1qlSpelValues(selectEntity, entity, i(b).toString(), 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/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateQueryCollectionIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateQueryCollectionIntegrationTests.java index c84e09eae..7cb960916 100644 --- a/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateQueryCollectionIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateQueryCollectionIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 the original author or authors + * Copyright 2021-2022 the original author or authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -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..3239e22c3 100644 --- a/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateQueryIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateQueryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors + * Copyright 2012-2022 the original author or authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -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..8c8ce4d57 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 @@ -1,5 +1,5 @@ /* - * Copyright 2017-2021 the original author or authors. + * Copyright 2017-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -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..5c5fd29cf 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 @@ -1,5 +1,5 @@ /* - * Copyright 2021 the original author or authors + * Copyright 2021-2022 the original author or authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -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/AirportRepository.java b/src/test/java/org/springframework/data/couchbase/domain/AirportRepository.java index 8c39699c0..d17c3d39e 100644 --- a/src/test/java/org/springframework/data/couchbase/domain/AirportRepository.java +++ b/src/test/java/org/springframework/data/couchbase/domain/AirportRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2021 the original author or authors. + * Copyright 2017-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -95,11 +95,11 @@ public interface AirportRepository extends CouchbaseRepository, @ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS) List deleteByIata(String iata); - @Query("SELECT __cas, * from `#{#n1ql.bucket}` where iata = $1") + @Query("SELECT __cas, * from #{#n1ql.bucket} where iata = $1") @ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS) List getAllByIataNoID(String iata); - @Query("SELECT __id, * from `#{#n1ql.bucket}` where iata = $1") + @Query("SELECT __id, * from #{#n1ql.bucket} where iata = $1") @ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS) List getAllByIataNoCAS(String iata); @@ -122,10 +122,10 @@ public interface AirportRepository extends CouchbaseRepository, Long countFancyExpression(@Param("projectIds") List projectIds, @Param("planIds") List planIds, @Param("active") Boolean active); - @Query("SELECT 1 FROM `#{#n1ql.bucket}` WHERE anything = 'count(*)'") // looks like count query, but is not + @Query("SELECT 1 FROM #{#n1ql.bucket} WHERE anything = 'count(*)'") // looks like count query, but is not Long countBad(); - @Query("SELECT count(*) FROM `#{#n1ql.bucket}`") + @Query("SELECT count(*) FROM #{#n1ql.bucket}") Long countGood(); @ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS) @@ -135,6 +135,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); @@ -150,7 +162,7 @@ Long countFancyExpression(@Param("projectIds") List projectIds, @Param(" @ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS) Long countDistinctIcaoBy(); - @Query("SELECT 1 FROM `#{#n1ql.bucket}` WHERE #{#n1ql.filter} " + " #{#projectIds != null ? 'AND blah IN $1' : ''} " + @Query("SELECT 1 FROM #{#n1ql.bucket} WHERE #{#n1ql.filter} " + " #{#projectIds != null ? 'AND blah IN $1' : ''} " + " #{#planIds != null ? 'AND blahblah IN $2' : ''} " + " #{#active != null ? 'AND false = $3' : ''} ") Long countOne(); 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 b9f093991..36d003f31 100644 --- a/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryQueryIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryQueryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2021 the original author or authors. + * Copyright 2017-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -102,7 +102,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; @@ -276,6 +275,7 @@ void findBySimplePropertyReturnType() { @Test public void saveNotBoundedRequestPlus() { + airportRepository.withOptions(QueryOptions.queryOptions().scanConsistency(REQUEST_PLUS)).deleteAll(); ApplicationContext ac = new AnnotationConfigApplicationContext(ConfigRequestPlus.class); // the Config class has been modified, these need to be loaded again CouchbaseTemplate couchbaseTemplateRP = (CouchbaseTemplate) ac.getBean(COUCHBASE_TEMPLATE); @@ -314,20 +314,21 @@ public void saveNotBoundedRequestPlus() { @Test public void saveNotBoundedWithDefaultRepository() { - + airportRepository.withOptions(QueryOptions.queryOptions().scanConsistency(REQUEST_PLUS)).deleteAll(); 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 @@ -335,19 +336,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; @@ -374,15 +375,14 @@ void findByEnum() { Airport airport2 = airportRepository.findByIata(Iata.vie); assertNotNull(airport2, "should have found " + vie); assertEquals(airport2.getId(), vie.getId()); - - Airport airport3 = airportRepository.findByIataIn(new Iata[]{Iata.vie, Iata.xxx}); + Airport airport3 = airportRepository.findByIataIn(new Iata[] { Iata.vie, Iata.xxx }); assertNotNull(airport3, "should have found " + vie); assertEquals(airport3.getId(), vie.getId()); java.util.Collection iatas = new ArrayList<>(); iatas.add(Iata.vie); iatas.add(Iata.xxx); - Airport airport4 = airportRepository.findByIataIn( iatas ); + Airport airport4 = airportRepository.findByIataIn(iatas); assertNotNull(airport4, "should have found " + vie); assertEquals(airport4.getId(), vie.getId()); @@ -557,8 +557,29 @@ 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() { + airportRepository.withOptions(QueryOptions.queryOptions().scanConsistency(REQUEST_PLUS)).deleteAll(); String[] iatas = { "JFK", "IAD", "SFO", "SJC", "SEA", "LAX", "PHX" }; airportRepository.countOne(); @@ -657,6 +678,7 @@ void threadSafeParametersTest() throws Exception { void distinct() { String[] iatas = { "JFK", "IAD", "SFO", "SJC", "SEA", "LAX", "PHX" }; String[] icaos = { "ic0", "ic1", "ic0", "ic1", "ic0", "ic1", "ic0" }; + airportRepository.withOptions(QueryOptions.queryOptions().scanConsistency(REQUEST_PLUS)).deleteAll(); try { for (int i = 0; i < iatas.length; i++) { 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..e60f3865e 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 @@ -1,5 +1,5 @@ /* - * Copyright 2017-2021 the original author or authors. + * Copyright 2017-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -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..66f124e3a 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 @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 the original author or authors. + * Copyright 2017-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -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)); } diff --git a/src/test/java/org/springframework/data/couchbase/util/JavaIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/util/JavaIntegrationTests.java index 5ba592487..90b9ab0c8 100644 --- a/src/test/java/org/springframework/data/couchbase/util/JavaIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/util/JavaIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 the original author or authors + * Copyright 2020-2022 the original author or authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +27,12 @@ import static org.springframework.data.couchbase.config.BeanNames.REACTIVE_COUCHBASE_TEMPLATE; import static org.springframework.data.couchbase.util.Util.waitUntilCondition; +import okhttp3.Credentials; +import okhttp3.FormBody; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + import java.io.IOException; import java.time.Duration; import java.util.Collections; @@ -39,7 +45,6 @@ import java.util.function.Function; import java.util.function.Predicate; -import com.couchbase.client.core.io.CollectionIdentifier; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Timeout; import org.springframework.context.ApplicationContext; @@ -60,6 +65,7 @@ import com.couchbase.client.core.error.QueryException; import com.couchbase.client.core.error.ScopeNotFoundException; import com.couchbase.client.core.error.UnambiguousTimeoutException; +import com.couchbase.client.core.io.CollectionIdentifier; import com.couchbase.client.core.json.Mapper; import com.couchbase.client.core.service.ServiceType; import com.couchbase.client.java.Bucket; @@ -200,6 +206,7 @@ protected static void waitForQueryIndexerToHaveBucket(final Cluster cluster, fin } if (!ready) { + createAndDeleteBucket();// need to do this because of https://issues.couchbase.com/browse/MB-50132 try { Thread.sleep(50); } catch (InterruptedException e) {} @@ -211,6 +218,32 @@ protected static void waitForQueryIndexerToHaveBucket(final Cluster cluster, fin } } + private static void createAndDeleteBucket() { + final OkHttpClient httpClient = new OkHttpClient.Builder().connectTimeout(30, TimeUnit.SECONDS) + .readTimeout(30, TimeUnit.SECONDS).writeTimeout(30, TimeUnit.SECONDS).build(); + String hostPort = connectionString().replace("11210", "8091"); + String bucketname = UUID.randomUUID().toString(); + try { + + Response postResponse = httpClient.newCall(new Request.Builder() + .header("Authorization", Credentials.basic(config().adminUsername(), config().adminPassword())) + .url("http://" + hostPort + "/pools/default/buckets/") + .post(new FormBody.Builder().add("name", bucketname).add("bucketType", "membase").add("ramQuotaMB", "100") + .add("replicaNumber", Integer.toString(0)).add("flushEnabled", "1").build()) + .build()).execute(); + + if (postResponse.code() != 202) { + throw new IOException("Could not create bucket: " + postResponse + ", Reason: " + postResponse.body().string()); + } + Response deleteResponse = httpClient.newCall(new Request.Builder() + .header("Authorization", Credentials.basic(config().adminUsername(), config().adminPassword())) + .url("http://" + hostPort + "/pools/default/buckets/" + bucketname).delete().build()).execute(); + System.out.println("deleteResponse: " + deleteResponse); + } catch (IOException ioe) { + ioe.printStackTrace(); + } + } + /** * Improve test stability by waiting for a given service to report itself ready. */