Expiration annotation on field will retrieve expiration in N1ql queries. (#1188)

Closes #1060.
This commit is contained in:
Michael Reiche
2021-08-16 12:53:46 -07:00
committed by GitHub
parent 6512fc087b
commit 52b1832cb5
5 changed files with 45 additions and 27 deletions

View File

@@ -45,8 +45,9 @@ import org.springframework.data.repository.query.parser.PartTree;
*/
public class N1qlQueryCreator extends AbstractQueryCreator<Query, QueryCriteria> {
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<?, CouchbasePersistentProperty> context;
@@ -68,7 +69,7 @@ public class N1qlQueryCreator extends AbstractQueryCreator<Query, QueryCriteria>
protected QueryCriteria create(final Part part, final Iterator<Object> iterator) {
PersistentPropertyPath<CouchbasePersistentProperty> 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<? super CouchbasePersistentProperty, String> cvtr = new MyConverter();
@@ -76,16 +77,7 @@ public class N1qlQueryCreator extends AbstractQueryCreator<Query, QueryCriteria>
static class MyConverter implements Converter<CouchbasePersistentProperty, String> {
@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 @@ public class N1qlQueryCreator extends AbstractQueryCreator<Query, QueryCriteria>
PersistentPropertyPath<CouchbasePersistentProperty> 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 @@ public class N1qlQueryCreator extends AbstractQueryCreator<Query, QueryCriteria>
}
}
private N1QLExpression addMetaIfRequired(
public static N1QLExpression addMetaIfRequired(
String bucketName,
final PersistentPropertyPath<CouchbasePersistentProperty> persistentPropertyPath,
final CouchbasePersistentProperty property) {
if (property.isIdProperty()) {
@@ -183,6 +176,9 @@ public class N1qlQueryCreator extends AbstractQueryCreator<Query, QueryCriteria>
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));
}

View File

@@ -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 java.util.regex.Pattern;
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.couchbase.repository.query.support.N1qlUtils;
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 @@ public class StringBasedN1qlQueryParser {
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<CouchbasePersistentProperty>() {
@Override
public void doWithPersistentProperty(final CouchbasePersistentProperty prop) {
if (prop.isIdProperty() && parent == null) {
return;
}
if (prop.isVersionProperty()) {
return;
}
PersistentPropertyPath<CouchbasePersistentProperty> 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 class StringBasedN1qlQueryParser {
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
// }
}
});

View File

@@ -22,6 +22,7 @@ import org.springframework.data.annotation.PersistenceConstructor;
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 class Airport extends ComparableEntity {
return icao;
}
public long getExpiration() {
return expiration;
}
public Airport withId(String id) {
return new Airport(id, this.iata, this.icao);
}

View File

@@ -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 class User extends ComparableEntity {
return Objects.hash(id, firstname, lastname);
}
public String getTransientInfo(){
public String getTransientInfo() {
return transientInfo;
}
public void setTransientInfo(String something) {
transientInfo = something;
}

View File

@@ -20,6 +20,7 @@ import static java.util.Arrays.asList;
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.concurrent.Future;
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 class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
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");