Skip to content

Commit dff149a

Browse files
committed
Option to not use typeKey=typeAlias property and predicate. (#1777)
If the configuration getType() or the @typealias is an empty string, then do not add the typeKey:typeAlias property when storing documents, and do not add the typeKey=typeAlias property on queries - including removeByQuery.
1 parent 102d90f commit dff149a

File tree

10 files changed

+287
-29
lines changed

10 files changed

+287
-29
lines changed

src/main/java/org/springframework/data/couchbase/core/convert/DefaultCouchbaseTypeMapper.java

+3
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ public CouchbaseDocumentTypeAliasAccessor(final String typeKey) {
6767

6868
@Override
6969
public Alias readAliasFrom(final CouchbaseDocument source) {
70+
if (typeKey == null || typeKey.length() == 0) {
71+
return Alias.NONE;
72+
}
7073
return Alias.ofNullable(source.get(typeKey));
7174
}
7275

src/main/java/org/springframework/data/couchbase/core/index/CouchbasePersistentEntityIndexResolver.java

+13-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
*/
1616
package org.springframework.data.couchbase.core.index;
1717

18+
import static org.springframework.data.couchbase.core.query.N1QLExpression.i;
19+
import static org.springframework.data.couchbase.core.query.N1QLExpression.s;
20+
1821
import java.util.ArrayList;
1922
import java.util.Arrays;
2023
import java.util.List;
@@ -25,6 +28,7 @@
2528
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
2629
import org.springframework.data.couchbase.core.mapping.Document;
2730
import org.springframework.data.couchbase.repository.support.MappingCouchbaseEntityInformation;
31+
import org.springframework.data.mapping.Alias;
2832
import org.springframework.data.mapping.PropertyHandler;
2933
import org.springframework.data.mapping.context.MappingContext;
3034
import org.springframework.data.util.TypeInformation;
@@ -142,7 +146,15 @@ protected List<IndexDefinitionHolder> createCompositeQueryIndexDefinitions(final
142146
private String getPredicate(final MappingCouchbaseEntityInformation<?, Object> entityInfo) {
143147
String typeKey = operations.getConverter().getTypeKey();
144148
String typeValue = entityInfo.getJavaType().getName();
145-
return "`" + typeKey + "` = \"" + typeValue + "\"";
149+
Alias alias = operations.getConverter().getTypeAlias(TypeInformation.of(entityInfo.getJavaType()));
150+
if (alias != null && alias.isPresent()) {
151+
typeValue = alias.toString();
152+
}
153+
return !empty(typeKey) && !empty(typeValue) ? i(typeKey).eq(s(typeValue)).toString() : null;
154+
}
155+
156+
private static boolean empty(String s){
157+
return s == null || s.length() == 0;
146158
}
147159

148160
public static class IndexDefinitionHolder implements IndexDefinition {

src/main/java/org/springframework/data/couchbase/core/query/Query.java

+6-2
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,9 @@ public String toN1qlSelectString(CouchbaseConverter converter, String bucketName
353353
domainClass, returnClass, isCount, distinctFields, fields);
354354
final StringBuilder statement = new StringBuilder();
355355
appendString(statement, n1ql.selectEntity); // select ...
356-
appendWhereString(statement, n1ql.filter); // typeKey = typeValue
356+
if (n1ql.filter != null) {
357+
appendWhereString(statement, n1ql.filter); // typeKey = typeValue
358+
}
357359
appendWhere(statement, new int[] { 0 }, converter); // criteria on this Query
358360
if (!isCount) {
359361
appendSort(statement);
@@ -368,7 +370,9 @@ public String toN1qlRemoveString(CouchbaseConverter converter, String bucketName
368370
domainClass, null, false, null, null);
369371
final StringBuilder statement = new StringBuilder();
370372
appendString(statement, n1ql.delete); // delete ...
371-
appendWhereString(statement, n1ql.filter); // typeKey = typeValue
373+
if (n1ql.filter != null) {
374+
appendWhereString(statement, n1ql.filter); // typeKey = typeValue
375+
}
372376
appendWhere(statement, null, converter); // criteria on this Query
373377
appendString(statement, n1ql.returning);
374378
return statement.toString();

src/main/java/org/springframework/data/couchbase/repository/query/N1qlMutateQueryCreator.java

+4
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
* supported
3838
*
3939
* @author Subhashni Balakrishnan
40+
* @author Michael Reiche
4041
*/
4142
public class N1qlMutateQueryCreator extends AbstractQueryCreator<N1QLExpression, N1QLExpression>
4243
implements PartTreeN1qlQueryCreator {
@@ -82,6 +83,9 @@ protected N1QLExpression or(N1QLExpression base, N1QLExpression criteria) {
8283
protected N1QLExpression complete(N1QLExpression criteria, Sort sort) {
8384
N1QLExpression whereCriteria = N1qlUtils.createWhereFilterForEntity(criteria, this.converter,
8485
this.queryMethod.getEntityInformation());
86+
if (whereCriteria == null) {
87+
return mutateFrom;
88+
}
8589
return mutateFrom.where(whereCriteria);
8690
}
8791

src/main/java/org/springframework/data/couchbase/repository/query/OldN1qlQueryCreator.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ protected N1QLExpression complete(N1QLExpression criteria, Sort sort) {
124124
N1QLExpression whereCriteria = N1qlUtils.createWhereFilterForEntity(criteria, this.converter,
125125
this.queryMethod.getEntityInformation());
126126

127-
N1QLExpression selectFromWhere = selectFrom.where(whereCriteria);
127+
N1QLExpression selectFromWhere = whereCriteria != null ? selectFrom.where(whereCriteria) : selectFrom;
128128

129129
// sort of the Pageable takes precedence over the sort in the query name
130130
if ((queryMethod.isPageQuery() || queryMethod.isSliceQuery()) && accessor.getPageable().isPaged()) {

src/main/java/org/springframework/data/couchbase/repository/query/StringBasedN1qlQueryParser.java

+24-18
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.springframework.data.couchbase.repository.query;
1717

1818
import static org.springframework.data.couchbase.core.query.N1QLExpression.i;
19+
import static org.springframework.data.couchbase.core.query.N1QLExpression.s;
1920
import static org.springframework.data.couchbase.core.query.N1QLExpression.x;
2021
import static org.springframework.data.couchbase.core.support.TemplateUtils.SELECT_CAS;
2122
import static org.springframework.data.couchbase.core.support.TemplateUtils.SELECT_ID;
@@ -199,30 +200,30 @@ public StringBasedN1qlQueryParser(String bucketName, String scope, String collec
199200
}
200201

201202
/**
202-
* Create the n1ql spel values. The domainClass is needed, but not the returnClass. Mapping the domainClass to the
203-
* returnClass is the responsibility of decoding.
204-
*
205-
* @param bucketName
206-
* @param scope
207-
* @param collection
208-
* @param domainClass
209-
* @param typeField
210-
* @param typeValue
211-
* @param isCount
212-
* @param distinctFields
213-
* @param fields
214-
* @return
215-
*/
203+
* Create the n1ql spel values. The domainClass is needed, but not the returnClass. Mapping the domainClass to the
204+
* returnClass is the responsibility of decoding.
205+
*
206+
* @param bucketName
207+
* @param scope
208+
* @param collection
209+
* @param domainClass
210+
* @param typeKey
211+
* @param typeValue
212+
* @param isCount
213+
* @param distinctFields
214+
* @param fields
215+
* @return
216+
*/
216217
public N1qlSpelValues createN1qlSpelValues(String bucketName, String scope, String collection, Class domainClass,
217-
String typeField, String typeValue, boolean isCount, String[] distinctFields, String[] fields) {
218+
String typeKey, String typeValue, boolean isCount, String[] distinctFields, String[] fields) {
218219
String b = bucketName;
219220
String keyspace = collection != null ? collection : bucketName;
220221
Assert.isTrue(!(distinctFields != null && fields != null),
221222
"only one of project(fields) and distinct(distinctFields) can be specified");
222223
String entityFields = "";
223224
String selectEntity;
224225
if (distinctFields != null) {
225-
String distinctFieldsStr = getProjectedOrDistinctFields(b, domainClass, typeField, fields, distinctFields);
226+
String distinctFieldsStr = getProjectedOrDistinctFields(b, domainClass, typeKey, fields, distinctFields);
226227
if (isCount) {
227228
selectEntity = N1QLExpression.select(N1QLExpression.count(N1QLExpression.distinct(x(distinctFieldsStr)))
228229
.as(i(CountFragment.COUNT_ALIAS)).from(keyspace)).toString();
@@ -233,11 +234,12 @@ public N1qlSpelValues createN1qlSpelValues(String bucketName, String scope, Stri
233234
selectEntity = N1QLExpression.select(N1QLExpression.count(x("\"*\"")).as(i(CountFragment.COUNT_ALIAS)))
234235
.from(keyspace).toString();
235236
} else {
236-
String projectedFields = getProjectedOrDistinctFields(keyspace, domainClass, typeField, fields, distinctFields);
237+
String projectedFields = getProjectedOrDistinctFields(keyspace, domainClass, typeKey, fields,
238+
distinctFields);
237239
entityFields = projectedFields;
238240
selectEntity = N1QLExpression.select(x(projectedFields)).from(keyspace).toString();
239241
}
240-
String typeSelection = "`" + typeField + "` = \"" + typeValue + "\"";
242+
String typeSelection = !empty(typeKey) && !empty(typeValue) ? i(typeKey).eq(s(typeValue)).toString() : null;
241243

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

251+
private static boolean empty(String s) {
252+
return s == null || s.length() == 0;
253+
}
254+
249255
private String getProjectedOrDistinctFields(String b, Class resultClass, String typeField, String[] fields,
250256
String[] distinctFields) {
251257
if (distinctFields != null && distinctFields.length != 0) {

src/main/java/org/springframework/data/couchbase/repository/query/support/N1qlUtils.java

+29-6
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,15 @@
1616

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

19-
import static org.springframework.data.couchbase.core.query.N1QLExpression.*;
20-
import static org.springframework.data.couchbase.core.support.TemplateUtils.*;
19+
import static org.springframework.data.couchbase.core.query.N1QLExpression.count;
20+
import static org.springframework.data.couchbase.core.query.N1QLExpression.i;
21+
import static org.springframework.data.couchbase.core.query.N1QLExpression.meta;
22+
import static org.springframework.data.couchbase.core.query.N1QLExpression.path;
23+
import static org.springframework.data.couchbase.core.query.N1QLExpression.s;
24+
import static org.springframework.data.couchbase.core.query.N1QLExpression.select;
25+
import static org.springframework.data.couchbase.core.query.N1QLExpression.x;
26+
import static org.springframework.data.couchbase.core.support.TemplateUtils.SELECT_CAS;
27+
import static org.springframework.data.couchbase.core.support.TemplateUtils.SELECT_ID;
2128

2229
import java.util.ArrayList;
2330
import java.util.List;
@@ -32,10 +39,12 @@
3239
import org.springframework.data.couchbase.repository.query.CouchbaseEntityInformation;
3340
import org.springframework.data.couchbase.repository.query.CountFragment;
3441
import org.springframework.data.domain.Sort;
42+
import org.springframework.data.mapping.Alias;
3543
import org.springframework.data.mapping.PersistentPropertyPath;
3644
import org.springframework.data.mapping.PropertyPath;
3745
import org.springframework.data.repository.core.EntityMetadata;
3846
import org.springframework.data.repository.query.ReturnedType;
47+
import org.springframework.data.util.TypeInformation;
3948

4049
import com.couchbase.client.java.json.JsonArray;
4150
import com.couchbase.client.java.json.JsonObject;
@@ -50,6 +59,7 @@
5059
* @author Simon Baslé
5160
* @author Subhashni Balakrishnan
5261
* @author Mark Paluch
62+
* @author Michael Reiche
5363
*/
5464
public class N1qlUtils {
5565

@@ -170,15 +180,23 @@ public static N1QLExpression createWhereFilterForEntity(N1QLExpression baseWhere
170180
// add part that filters on type key
171181
String typeKey = converter.getTypeKey();
172182
String typeValue = entityInformation.getJavaType().getName();
173-
N1QLExpression typeSelector = i(typeKey).eq(s(typeValue));
183+
Alias alias = converter.getTypeAlias(TypeInformation.of(entityInformation.getJavaType()));
184+
if (alias != null && alias.isPresent()) {
185+
typeValue = alias.toString();
186+
}
187+
N1QLExpression typeSelector = !empty(typeKey) && !empty(typeValue) ? i(typeKey).eq(s(typeValue)) : null;
174188
if (baseWhereCriteria == null) {
175189
baseWhereCriteria = typeSelector;
176-
} else {
190+
} else if (typeSelector != null) {
177191
baseWhereCriteria = x("(" + baseWhereCriteria.toString() + ")").and(typeSelector);
178192
}
179193
return baseWhereCriteria;
180194
}
181195

196+
private static boolean empty(String s) {
197+
return s == null || s.length() == 0;
198+
}
199+
182200
/**
183201
* Given a common {@link PropertyPath}, returns the corresponding {@link PersistentPropertyPath} of
184202
* {@link CouchbasePersistentProperty} which will allow to discover alternative naming for fields.
@@ -241,8 +259,13 @@ public static N1QLExpression[] createSort(Sort sort) {
241259
*/
242260
public static <T> N1QLExpression createCountQueryForEntity(String bucketName, CouchbaseConverter converter,
243261
CouchbaseEntityInformation<T, String> entityInformation) {
244-
return select(count(x("*")).as(x(CountFragment.COUNT_ALIAS))).from(escapedBucket(bucketName))
245-
.where(createWhereFilterForEntity(null, converter, entityInformation));
262+
N1QLExpression entityFilter = createWhereFilterForEntity(null, converter, entityInformation);
263+
N1QLExpression expression = select(
264+
(count(x("*")).as(x(CountFragment.COUNT_ALIAS))).from(escapedBucket(bucketName)));
265+
if (entityFilter == null) {
266+
return expression;
267+
}
268+
return expression.where(entityFilter);
246269
}
247270

248271
/**

src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateKeyValueIntegrationTests.java

+27-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import static org.junit.jupiter.api.Assertions.assertNull;
2424
import static org.junit.jupiter.api.Assertions.assertThrows;
2525
import static org.junit.jupiter.api.Assertions.assertTrue;
26+
import static org.springframework.data.couchbase.core.query.N1QLExpression.i;
2627

2728
import java.lang.reflect.Constructor;
2829
import java.lang.reflect.InvocationTargetException;
@@ -38,15 +39,17 @@
3839
import java.util.stream.Stream;
3940

4041
import org.junit.jupiter.api.BeforeEach;
42+
import org.junit.jupiter.api.Disabled;
4143
import org.junit.jupiter.api.Test;
4244
import org.springframework.beans.factory.annotation.Autowired;
43-
import org.springframework.context.annotation.Configuration;
4445
import org.springframework.dao.DuplicateKeyException;
4546
import org.springframework.dao.InvalidDataAccessResourceUsageException;
4647
import org.springframework.dao.OptimisticLockingFailureException;
4748
import org.springframework.data.couchbase.core.ExecutableFindByIdOperation.ExecutableFindById;
4849
import org.springframework.data.couchbase.core.ExecutableRemoveByIdOperation.ExecutableRemoveById;
4950
import org.springframework.data.couchbase.core.ExecutableReplaceByIdOperation.ExecutableReplaceById;
51+
import org.springframework.data.couchbase.core.query.Query;
52+
import org.springframework.data.couchbase.core.query.QueryCriteria;
5053
import org.springframework.data.couchbase.core.support.OneAndAllEntity;
5154
import org.springframework.data.couchbase.core.support.OneAndAllId;
5255
import org.springframework.data.couchbase.core.support.WithDurability;
@@ -66,6 +69,7 @@
6669
import org.springframework.data.couchbase.domain.UserAnnotatedPersistTo;
6770
import org.springframework.data.couchbase.domain.UserAnnotatedReplicateTo;
6871
import org.springframework.data.couchbase.domain.UserAnnotatedTouchOnRead;
72+
import org.springframework.data.couchbase.domain.UserNoAlias;
6973
import org.springframework.data.couchbase.domain.UserSubmission;
7074
import org.springframework.data.couchbase.util.ClusterType;
7175
import org.springframework.data.couchbase.util.IgnoreWhen;
@@ -142,6 +146,27 @@ void findByIdWithLock() {
142146

143147
}
144148

149+
@Test
150+
void findByIdNoAlias() {
151+
String firstname = UUID.randomUUID().toString();
152+
try {
153+
UserNoAlias user = new UserNoAlias("1", firstname, "user1");
154+
couchbaseTemplate.upsertById(UserNoAlias.class).one(user);
155+
UserNoAlias foundUser = couchbaseTemplate.findById(UserNoAlias.class).one(user.getId());
156+
user.setVersion(foundUser.getVersion());// version will have changed
157+
assertEquals(user, foundUser);
158+
Query query = new Query(QueryCriteria.where(i("firstname")).eq(firstname));
159+
List<UserNoAlias> queriedUsers = couchbaseTemplate.findByQuery(UserNoAlias.class)
160+
.withConsistency(QueryScanConsistency.REQUEST_PLUS).matching(query).all();
161+
assertEquals(1, queriedUsers.size(), "should have found exactly one");
162+
} finally {
163+
Query query = new Query(QueryCriteria.where(i("firstname")).eq(firstname));
164+
List<RemoveResult> removeResult = couchbaseTemplate.removeByQuery(UserNoAlias.class)
165+
.withConsistency(QueryScanConsistency.REQUEST_PLUS).matching(query).all();
166+
assertEquals(1, removeResult.size(), "should have removed exactly one");
167+
}
168+
}
169+
145170
@Test
146171
void findByIdWithExpiry() {
147172
try {
@@ -1289,6 +1314,7 @@ void rangeScanId() {
12891314
}
12901315

12911316
@Test
1317+
@Disabled // it's finding _txn documents with source = a single 0 byte which fails to deserialize
12921318
void sampleScan() {
12931319
String id = "A";
12941320
String lower = null;

src/test/java/org/springframework/data/couchbase/core/mapping/MappingCouchbaseConverterTests.java

+45
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
import org.springframework.data.couchbase.domain.Config;
6464
import org.springframework.data.couchbase.domain.Person;
6565
import org.springframework.data.couchbase.domain.User;
66+
import org.springframework.data.couchbase.domain.UserNoAlias;
6667
import org.springframework.data.mapping.MappingException;
6768

6869
/**
@@ -75,10 +76,17 @@ public class MappingCouchbaseConverterTests {
7576

7677
private static MappingCouchbaseConverter converter = new MappingCouchbaseConverter();
7778
private static MappingCouchbaseConverter customConverter = (new Config()).mappingCouchbaseConverter();
79+
private static MappingCouchbaseConverter noTypeKeyConverter = (new Config(){
80+
@Override
81+
public String typeKey() {
82+
return "";
83+
}
84+
}).mappingCouchbaseConverter();
7885

7986
static {
8087
converter.afterPropertiesSet();
8188
customConverter.afterPropertiesSet();
89+
noTypeKeyConverter.afterPropertiesSet();
8290
}
8391

8492
@Test
@@ -152,6 +160,43 @@ void readsString() {
152160
assertThat(converted.attr0).isEqualTo(source.get("attr0"));
153161
}
154162

163+
@Test
164+
void writesStringNoTypeKey() {
165+
CouchbaseDocument converted = new CouchbaseDocument();
166+
StringEntity entity = new StringEntity("foobar");
167+
168+
noTypeKeyConverter.write(entity, converted);
169+
Map<String, Object> result = converted.export();
170+
assertThat(result.get("_class")).isEqualTo(null);
171+
assertThat(result.get("attr0")).isEqualTo(entity.attr0);
172+
assertThat(converted.getId()).isEqualTo(BaseEntity.ID);
173+
}
174+
175+
@Test
176+
void readsStringNoTypeKey() {
177+
CouchbaseDocument source = new CouchbaseDocument();
178+
source.put("attr0", "foobar");
179+
StringEntity converted = noTypeKeyConverter.read(StringEntity.class, source);
180+
assertThat(converted.attr0).isEqualTo(source.get("attr0"));
181+
}
182+
183+
@Test
184+
void writesNoTypeAlias() {
185+
CouchbaseDocument converted = new CouchbaseDocument();
186+
UserNoAlias entity = new UserNoAlias(UUID.randomUUID().toString(), "first", "last");
187+
noTypeKeyConverter.write(entity, converted);
188+
Map<String, Object> result = converted.export();
189+
assertThat(result.get("_class")).isEqualTo(null);
190+
assertThat(converted.getId()).isEqualTo(entity.getId());
191+
}
192+
193+
@Test
194+
void readsNoTypeAlias() {
195+
CouchbaseDocument document = new CouchbaseDocument("001");
196+
UserNoAlias user = noTypeKeyConverter.read(UserNoAlias.class, document);
197+
assertThat(user.getId()).isEqualTo("001");
198+
}
199+
155200
@Test
156201
void writesBigInteger() {
157202
CouchbaseDocument converted = new CouchbaseDocument();

0 commit comments

Comments
 (0)