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 a6114ec22..896a1a066 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 @@ -45,8 +45,9 @@ */ public class N1qlQueryCreator extends AbstractQueryCreator { - private static final String META_ID_PROPERTY = "id"; - private static final String META_CAS_PROPERTY = "cas"; + public static final String META_ID_PROPERTY = "id"; + public static final String META_CAS_PROPERTY = "cas"; + public static final String META_EXPIRATION_PROPERTY = "expiration"; private final ParameterAccessor accessor; private final MappingContext context; @@ -68,7 +69,7 @@ public N1qlQueryCreator(final PartTree tree, final ParameterAccessor accessor, f protected QueryCriteria create(final Part part, final Iterator iterator) { PersistentPropertyPath path = context.getPersistentPropertyPath(part.getProperty()); CouchbasePersistentProperty property = path.getLeafProperty(); - return from(part, property, where(addMetaIfRequired(path, property)), iterator); + return from(part, property, where(addMetaIfRequired(bucketName, path, property)), iterator); } static Converter cvtr = new MyConverter(); @@ -76,16 +77,7 @@ protected QueryCriteria create(final Part part, final Iterator iterator) static class MyConverter implements Converter { @Override public String convert(CouchbasePersistentProperty source) { - if (source.isIdProperty()) { - return "META().id"; - } else if (source.isVersionProperty()) { - return "META().cas"; - } else if (source.isExpirationProperty()) { - return "META().expiration"; - } else { - return new StringBuilder(source.getFieldName().length() + 2).append('`').append(source.getFieldName()) - .append('`').toString(); - } + return new StringBuilder(source.getFieldName().length()+2).append("`").append(source.getFieldName()).append("`").toString(); } } @@ -98,7 +90,7 @@ protected QueryCriteria and(final Part part, final QueryCriteria base, final Ite PersistentPropertyPath path = context.getPersistentPropertyPath(part.getProperty()); CouchbasePersistentProperty property = path.getLeafProperty(); - return from(part, property, base.and(addMetaIfRequired(path, property)), iterator); + return from(part, property, base.and(addMetaIfRequired(bucketName,path, property)), iterator); } @Override @@ -174,7 +166,8 @@ private QueryCriteria from(final Part part, final CouchbasePersistentProperty pr } } - private N1QLExpression addMetaIfRequired( + public static N1QLExpression addMetaIfRequired( + String bucketName, final PersistentPropertyPath persistentPropertyPath, final CouchbasePersistentProperty property) { if (property.isIdProperty()) { @@ -183,6 +176,9 @@ private N1QLExpression addMetaIfRequired( if (property.isVersionProperty()) { return path(meta(i(bucketName)), i(META_CAS_PROPERTY)); } + if (property.isExpirationProperty()) { + return path(meta(i(bucketName)), i(META_EXPIRATION_PROPERTY)); + } return x(persistentPropertyPath.toDotPath(cvtr)); } 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 3716ac26e..80702f491 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 @@ -16,6 +16,8 @@ package org.springframework.data.couchbase.repository.query; import static org.springframework.data.couchbase.core.query.N1QLExpression.i; +import static org.springframework.data.couchbase.core.query.N1QLExpression.meta; +import static org.springframework.data.couchbase.core.query.N1QLExpression.path; import static org.springframework.data.couchbase.core.query.N1QLExpression.x; import static org.springframework.data.couchbase.core.support.TemplateUtils.SELECT_CAS; import static org.springframework.data.couchbase.core.support.TemplateUtils.SELECT_ID; @@ -29,6 +31,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.core.convert.converter.Converter; import org.springframework.data.couchbase.core.convert.CouchbaseConverter; import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty; import org.springframework.data.couchbase.core.query.N1QLExpression; @@ -37,6 +40,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.mapping.PersistentEntity; +import org.springframework.data.mapping.PersistentPropertyPath; import org.springframework.data.mapping.PropertyHandler; import org.springframework.data.repository.query.Parameter; import org.springframework.data.repository.query.ParameterAccessor; @@ -183,22 +187,28 @@ private String getProjectedFields(String b, Class resultClass) { if (resultClass != null) { PersistentEntity persistentEntity = couchbaseConverter.getMappingContext().getPersistentEntity(resultClass); StringBuilder sb = new StringBuilder(); - getProjectedFieldsInternal(null, sb, persistentEntity.getTypeInformation(), ""); + getProjectedFieldsInternal(b, null, sb, persistentEntity.getTypeInformation()/*, ""*/); projectedFields = sb.toString(); } return projectedFields; } - private void getProjectedFieldsInternal(CouchbasePersistentProperty parent, StringBuilder sb, - TypeInformation resultClass, String path) { + private void getProjectedFieldsInternal(String bucketName, CouchbasePersistentProperty parent, StringBuilder sb, + TypeInformation resultClass/*, String path*/) { PersistentEntity persistentEntity = couchbaseConverter.getMappingContext().getPersistentEntity(resultClass); + //CouchbasePersistentProperty property = path.getLeafProperty(); persistentEntity.doWithProperties(new PropertyHandler() { @Override public void doWithPersistentProperty(final CouchbasePersistentProperty prop) { if (prop.isIdProperty() && parent == null) { return; } + if (prop.isVersionProperty()) { + return; + } + PersistentPropertyPath path = couchbaseConverter.getMappingContext().getPersistentPropertyPath(prop.getName(), resultClass.getType()); + // 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 @@ -210,12 +220,7 @@ public void doWithPersistentProperty(final CouchbasePersistentProperty prop) { if (sb.length() > 0) { sb.append(", "); } - sb.append('`'); - if (path != null && path.length() != 0) { - sb.append(path); - } - sb.append(prop.getName()); - sb.append('`'); + sb.append(N1qlQueryCreator.addMetaIfRequired(bucketName, path, prop)); // from N1qlQueryCreator // } } }); 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 bb7ecae21..78e12dfa8 100644 --- a/src/test/java/org/springframework/data/couchbase/domain/Airport.java +++ b/src/test/java/org/springframework/data/couchbase/domain/Airport.java @@ -22,6 +22,7 @@ import org.springframework.data.annotation.TypeAlias; import org.springframework.data.annotation.Version; import org.springframework.data.couchbase.core.mapping.Document; +import org.springframework.data.couchbase.core.mapping.Expiration; /** * Airport entity @@ -41,6 +42,7 @@ public class Airport extends ComparableEntity { @Version Number version; @CreatedBy private String createdBy; + @Expiration private long expiration; @PersistenceConstructor public Airport(String id, String iata, String icao) { @@ -61,6 +63,10 @@ public String getIcao() { return icao; } + public long getExpiration() { + return expiration; + } + public Airport withId(String id) { return new Airport(id, this.iata, this.icao); } diff --git a/src/test/java/org/springframework/data/couchbase/domain/User.java b/src/test/java/org/springframework/data/couchbase/domain/User.java index ac2b92d63..f29146c09 100644 --- a/src/test/java/org/springframework/data/couchbase/domain/User.java +++ b/src/test/java/org/springframework/data/couchbase/domain/User.java @@ -42,8 +42,7 @@ public class User extends ComparableEntity { @Id private String id; private String firstname; private String lastname; - @Transient - private String transientInfo; + @Transient private String transientInfo; @CreatedBy private String createdBy; @CreatedDate private long createdDate; @LastModifiedBy private String lastModifiedBy; @@ -97,9 +96,10 @@ public int hashCode() { return Objects.hash(id, firstname, lastname); } - public String getTransientInfo(){ + public String getTransientInfo() { return transientInfo; } + public void setTransientInfo(String something) { transientInfo = something; } 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 cffeeaee2..c601a9d14 100644 --- a/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryQueryIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryQueryIntegrationTests.java @@ -20,6 +20,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -38,6 +39,7 @@ import java.util.Optional; import java.util.stream.Collectors; +import com.couchbase.client.java.kv.UpsertOptions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -376,6 +378,15 @@ public void testCas() { userRepository.delete(user); } + @Test + public void testExpiration() { + Airport airport = new Airport("1", "iata21", "icao21"); + airportRepository.withOptions(UpsertOptions.upsertOptions().expiry(Duration.ofSeconds(10))).save(airport); + Airport foundAirport = airportRepository.findByIata(airport.getIata()); + assertNotEquals(0, foundAirport.getExpiration()); + airportRepository.delete(airport); + } + @Test public void testStreamQuery() { User user1 = new User("1", "Dave", "Wilson");