Skip to content

Option to not use typeKey=typeAlias property and predicate. #1777

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ public CouchbaseDocumentTypeAliasAccessor(final String typeKey) {

@Override
public Alias readAliasFrom(final CouchbaseDocument source) {
if (typeKey == null || typeKey.length() == 0) {
return Alias.NONE;
}
return Alias.ofNullable(source.get(typeKey));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
*/
package org.springframework.data.couchbase.core.index;

import static org.springframework.data.couchbase.core.query.N1QLExpression.i;
import static org.springframework.data.couchbase.core.query.N1QLExpression.s;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
Expand All @@ -25,6 +28,7 @@
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
import org.springframework.data.couchbase.core.mapping.Document;
import org.springframework.data.couchbase.repository.support.MappingCouchbaseEntityInformation;
import org.springframework.data.mapping.Alias;
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.util.TypeInformation;
Expand Down Expand Up @@ -142,7 +146,15 @@ protected List<IndexDefinitionHolder> createCompositeQueryIndexDefinitions(final
private String getPredicate(final MappingCouchbaseEntityInformation<?, Object> entityInfo) {
String typeKey = operations.getConverter().getTypeKey();
String typeValue = entityInfo.getJavaType().getName();
return "`" + typeKey + "` = \"" + typeValue + "\"";
Alias alias = operations.getConverter().getTypeAlias(TypeInformation.of(entityInfo.getJavaType()));
if (alias != null && alias.isPresent()) {
typeValue = alias.toString();
}
return !empty(typeKey) && !empty(typeValue) ? i(typeKey).eq(s(typeValue)).toString() : null;
}

private static boolean empty(String s){
return s == null || s.length() == 0;
}

public static class IndexDefinitionHolder implements IndexDefinition {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,9 @@ public String toN1qlSelectString(CouchbaseConverter converter, String bucketName
domainClass, returnClass, isCount, distinctFields, fields);
final StringBuilder statement = new StringBuilder();
appendString(statement, n1ql.selectEntity); // select ...
appendWhereString(statement, n1ql.filter); // typeKey = typeValue
if (n1ql.filter != null) {
appendWhereString(statement, n1ql.filter); // typeKey = typeValue
}
appendWhere(statement, new int[] { 0 }, converter); // criteria on this Query
if (!isCount) {
appendSort(statement);
Expand All @@ -368,7 +370,9 @@ public String toN1qlRemoveString(CouchbaseConverter converter, String bucketName
domainClass, null, false, null, null);
final StringBuilder statement = new StringBuilder();
appendString(statement, n1ql.delete); // delete ...
appendWhereString(statement, n1ql.filter); // typeKey = typeValue
if (n1ql.filter != null) {
appendWhereString(statement, n1ql.filter); // typeKey = typeValue
}
appendWhere(statement, null, converter); // criteria on this Query
appendString(statement, n1ql.returning);
return statement.toString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
* supported
*
* @author Subhashni Balakrishnan
* @author Michael Reiche
*/
public class N1qlMutateQueryCreator extends AbstractQueryCreator<N1QLExpression, N1QLExpression>
implements PartTreeN1qlQueryCreator {
Expand Down Expand Up @@ -82,6 +83,9 @@ protected N1QLExpression or(N1QLExpression base, N1QLExpression criteria) {
protected N1QLExpression complete(N1QLExpression criteria, Sort sort) {
N1QLExpression whereCriteria = N1qlUtils.createWhereFilterForEntity(criteria, this.converter,
this.queryMethod.getEntityInformation());
if (whereCriteria == null) {
return mutateFrom;
}
return mutateFrom.where(whereCriteria);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ protected N1QLExpression complete(N1QLExpression criteria, Sort sort) {
N1QLExpression whereCriteria = N1qlUtils.createWhereFilterForEntity(criteria, this.converter,
this.queryMethod.getEntityInformation());

N1QLExpression selectFromWhere = selectFrom.where(whereCriteria);
N1QLExpression selectFromWhere = whereCriteria != null ? selectFrom.where(whereCriteria) : selectFrom;

// sort of the Pageable takes precedence over the sort in the query name
if ((queryMethod.isPageQuery() || queryMethod.isSliceQuery()) && accessor.getPageable().isPaged()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
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.s;
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;
Expand Down Expand Up @@ -199,30 +200,30 @@ public StringBasedN1qlQueryParser(String bucketName, String scope, String collec
}

/**
* Create the n1ql spel values. The domainClass is needed, but not the returnClass. Mapping the domainClass to the
* returnClass is the responsibility of decoding.
*
* @param bucketName
* @param scope
* @param collection
* @param domainClass
* @param typeField
* @param typeValue
* @param isCount
* @param distinctFields
* @param fields
* @return
*/
* Create the n1ql spel values. The domainClass is needed, but not the returnClass. Mapping the domainClass to the
* returnClass is the responsibility of decoding.
*
* @param bucketName
* @param scope
* @param collection
* @param domainClass
* @param typeKey
* @param typeValue
* @param isCount
* @param distinctFields
* @param fields
* @return
*/
public N1qlSpelValues createN1qlSpelValues(String bucketName, String scope, String collection, Class domainClass,
String typeField, String typeValue, boolean isCount, String[] distinctFields, String[] fields) {
String typeKey, String typeValue, boolean isCount, String[] distinctFields, String[] fields) {
String b = bucketName;
String keyspace = collection != null ? collection : bucketName;
Assert.isTrue(!(distinctFields != null && fields != null),
"only one of project(fields) and distinct(distinctFields) can be specified");
String entityFields = "";
String selectEntity;
if (distinctFields != null) {
String distinctFieldsStr = getProjectedOrDistinctFields(b, domainClass, typeField, fields, distinctFields);
String distinctFieldsStr = getProjectedOrDistinctFields(b, domainClass, typeKey, fields, distinctFields);
if (isCount) {
selectEntity = N1QLExpression.select(N1QLExpression.count(N1QLExpression.distinct(x(distinctFieldsStr)))
.as(i(CountFragment.COUNT_ALIAS)).from(keyspace)).toString();
Expand All @@ -233,11 +234,12 @@ public N1qlSpelValues createN1qlSpelValues(String bucketName, String scope, Stri
selectEntity = N1QLExpression.select(N1QLExpression.count(x("\"*\"")).as(i(CountFragment.COUNT_ALIAS)))
.from(keyspace).toString();
} else {
String projectedFields = getProjectedOrDistinctFields(keyspace, domainClass, typeField, fields, distinctFields);
String projectedFields = getProjectedOrDistinctFields(keyspace, domainClass, typeKey, fields,
distinctFields);
entityFields = projectedFields;
selectEntity = N1QLExpression.select(x(projectedFields)).from(keyspace).toString();
}
String typeSelection = "`" + typeField + "` = \"" + typeValue + "\"";
String typeSelection = !empty(typeKey) && !empty(typeValue) ? i(typeKey).eq(s(typeValue)).toString() : null;

String delete = N1QLExpression.delete().from(keyspace).toString();
String returning = " returning " + N1qlUtils.createReturningExpressionForDelete(keyspace);
Expand All @@ -246,6 +248,10 @@ public N1qlSpelValues createN1qlSpelValues(String bucketName, String scope, Stri
i(collection).toString(), typeSelection, delete, returning);
}

private static boolean empty(String s) {
return s == null || s.length() == 0;
}

private String getProjectedOrDistinctFields(String b, Class resultClass, String typeField, String[] fields,
String[] distinctFields) {
if (distinctFields != null && distinctFields.length != 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,15 @@

package org.springframework.data.couchbase.repository.query.support;

import static org.springframework.data.couchbase.core.query.N1QLExpression.*;
import static org.springframework.data.couchbase.core.support.TemplateUtils.*;
import static org.springframework.data.couchbase.core.query.N1QLExpression.count;
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.s;
import static org.springframework.data.couchbase.core.query.N1QLExpression.select;
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;

import java.util.ArrayList;
import java.util.List;
Expand All @@ -32,10 +39,12 @@
import org.springframework.data.couchbase.repository.query.CouchbaseEntityInformation;
import org.springframework.data.couchbase.repository.query.CountFragment;
import org.springframework.data.domain.Sort;
import org.springframework.data.mapping.Alias;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.mapping.PropertyPath;
import org.springframework.data.repository.core.EntityMetadata;
import org.springframework.data.repository.query.ReturnedType;
import org.springframework.data.util.TypeInformation;

import com.couchbase.client.java.json.JsonArray;
import com.couchbase.client.java.json.JsonObject;
Expand All @@ -50,6 +59,7 @@
* @author Simon Baslé
* @author Subhashni Balakrishnan
* @author Mark Paluch
* @author Michael Reiche
*/
public class N1qlUtils {

Expand Down Expand Up @@ -170,15 +180,23 @@ public static N1QLExpression createWhereFilterForEntity(N1QLExpression baseWhere
// add part that filters on type key
String typeKey = converter.getTypeKey();
String typeValue = entityInformation.getJavaType().getName();
N1QLExpression typeSelector = i(typeKey).eq(s(typeValue));
Alias alias = converter.getTypeAlias(TypeInformation.of(entityInformation.getJavaType()));
if (alias != null && alias.isPresent()) {
typeValue = alias.toString();
}
N1QLExpression typeSelector = !empty(typeKey) && !empty(typeValue) ? i(typeKey).eq(s(typeValue)) : null;
if (baseWhereCriteria == null) {
baseWhereCriteria = typeSelector;
} else {
} else if (typeSelector != null) {
baseWhereCriteria = x("(" + baseWhereCriteria.toString() + ")").and(typeSelector);
}
return baseWhereCriteria;
}

private static boolean empty(String s) {
return s == null || s.length() == 0;
}

/**
* Given a common {@link PropertyPath}, returns the corresponding {@link PersistentPropertyPath} of
* {@link CouchbasePersistentProperty} which will allow to discover alternative naming for fields.
Expand Down Expand Up @@ -241,8 +259,13 @@ public static N1QLExpression[] createSort(Sort sort) {
*/
public static <T> N1QLExpression createCountQueryForEntity(String bucketName, CouchbaseConverter converter,
CouchbaseEntityInformation<T, String> entityInformation) {
return select(count(x("*")).as(x(CountFragment.COUNT_ALIAS))).from(escapedBucket(bucketName))
.where(createWhereFilterForEntity(null, converter, entityInformation));
N1QLExpression entityFilter = createWhereFilterForEntity(null, converter, entityInformation);
N1QLExpression expression = select(
(count(x("*")).as(x(CountFragment.COUNT_ALIAS))).from(escapedBucket(bucketName)));
if (entityFilter == null) {
return expression;
}
return expression.where(entityFilter);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.springframework.data.couchbase.core.query.N1QLExpression.i;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
Expand All @@ -38,15 +39,17 @@
import java.util.stream.Stream;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.dao.InvalidDataAccessResourceUsageException;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.data.couchbase.core.ExecutableFindByIdOperation.ExecutableFindById;
import org.springframework.data.couchbase.core.ExecutableRemoveByIdOperation.ExecutableRemoveById;
import org.springframework.data.couchbase.core.ExecutableReplaceByIdOperation.ExecutableReplaceById;
import org.springframework.data.couchbase.core.query.Query;
import org.springframework.data.couchbase.core.query.QueryCriteria;
import org.springframework.data.couchbase.core.support.OneAndAllEntity;
import org.springframework.data.couchbase.core.support.OneAndAllId;
import org.springframework.data.couchbase.core.support.WithDurability;
Expand All @@ -66,6 +69,7 @@
import org.springframework.data.couchbase.domain.UserAnnotatedPersistTo;
import org.springframework.data.couchbase.domain.UserAnnotatedReplicateTo;
import org.springframework.data.couchbase.domain.UserAnnotatedTouchOnRead;
import org.springframework.data.couchbase.domain.UserNoAlias;
import org.springframework.data.couchbase.domain.UserSubmission;
import org.springframework.data.couchbase.util.ClusterType;
import org.springframework.data.couchbase.util.IgnoreWhen;
Expand Down Expand Up @@ -142,6 +146,27 @@ void findByIdWithLock() {

}

@Test
void findByIdNoAlias() {
String firstname = UUID.randomUUID().toString();
try {
UserNoAlias user = new UserNoAlias("1", firstname, "user1");
couchbaseTemplate.upsertById(UserNoAlias.class).one(user);
UserNoAlias foundUser = couchbaseTemplate.findById(UserNoAlias.class).one(user.getId());
user.setVersion(foundUser.getVersion());// version will have changed
assertEquals(user, foundUser);
Query query = new Query(QueryCriteria.where(i("firstname")).eq(firstname));
List<UserNoAlias> queriedUsers = couchbaseTemplate.findByQuery(UserNoAlias.class)
.withConsistency(QueryScanConsistency.REQUEST_PLUS).matching(query).all();
assertEquals(1, queriedUsers.size(), "should have found exactly one");
} finally {
Query query = new Query(QueryCriteria.where(i("firstname")).eq(firstname));
List<RemoveResult> removeResult = couchbaseTemplate.removeByQuery(UserNoAlias.class)
.withConsistency(QueryScanConsistency.REQUEST_PLUS).matching(query).all();
assertEquals(1, removeResult.size(), "should have removed exactly one");
}
}

@Test
void findByIdWithExpiry() {
try {
Expand Down Expand Up @@ -1289,6 +1314,7 @@ void rangeScanId() {
}

@Test
@Disabled // it's finding _txn documents with source = a single 0 byte which fails to deserialize
void sampleScan() {
String id = "A";
String lower = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
import org.springframework.data.couchbase.domain.Config;
import org.springframework.data.couchbase.domain.Person;
import org.springframework.data.couchbase.domain.User;
import org.springframework.data.couchbase.domain.UserNoAlias;
import org.springframework.data.mapping.MappingException;

/**
Expand All @@ -75,10 +76,17 @@ public class MappingCouchbaseConverterTests {

private static MappingCouchbaseConverter converter = new MappingCouchbaseConverter();
private static MappingCouchbaseConverter customConverter = (new Config()).mappingCouchbaseConverter();
private static MappingCouchbaseConverter noTypeKeyConverter = (new Config(){
@Override
public String typeKey() {
return "";
}
}).mappingCouchbaseConverter();

static {
converter.afterPropertiesSet();
customConverter.afterPropertiesSet();
noTypeKeyConverter.afterPropertiesSet();
}

@Test
Expand Down Expand Up @@ -152,6 +160,43 @@ void readsString() {
assertThat(converted.attr0).isEqualTo(source.get("attr0"));
}

@Test
void writesStringNoTypeKey() {
CouchbaseDocument converted = new CouchbaseDocument();
StringEntity entity = new StringEntity("foobar");

noTypeKeyConverter.write(entity, converted);
Map<String, Object> result = converted.export();
assertThat(result.get("_class")).isEqualTo(null);
assertThat(result.get("attr0")).isEqualTo(entity.attr0);
assertThat(converted.getId()).isEqualTo(BaseEntity.ID);
}

@Test
void readsStringNoTypeKey() {
CouchbaseDocument source = new CouchbaseDocument();
source.put("attr0", "foobar");
StringEntity converted = noTypeKeyConverter.read(StringEntity.class, source);
assertThat(converted.attr0).isEqualTo(source.get("attr0"));
}

@Test
void writesNoTypeAlias() {
CouchbaseDocument converted = new CouchbaseDocument();
UserNoAlias entity = new UserNoAlias(UUID.randomUUID().toString(), "first", "last");
noTypeKeyConverter.write(entity, converted);
Map<String, Object> result = converted.export();
assertThat(result.get("_class")).isEqualTo(null);
assertThat(converted.getId()).isEqualTo(entity.getId());
}

@Test
void readsNoTypeAlias() {
CouchbaseDocument document = new CouchbaseDocument("001");
UserNoAlias user = noTypeKeyConverter.read(UserNoAlias.class, document);
assertThat(user.getId()).isEqualTo("001");
}

@Test
void writesBigInteger() {
CouchbaseDocument converted = new CouchbaseDocument();
Expand Down
Loading