Skip to content

Commit 9f2d7ee

Browse files
authored
Field named id treated as document id. (#1261)
Closes #1258. Co-authored-by: mikereiche <[email protected]>
1 parent e1b0ea9 commit 9f2d7ee

File tree

6 files changed

+122
-38
lines changed

6 files changed

+122
-38
lines changed

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

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
import org.springframework.data.mapping.Association;
5353
import org.springframework.data.mapping.AssociationHandler;
5454
import org.springframework.data.mapping.MappingException;
55+
import org.springframework.data.mapping.PersistentEntity;
5556
import org.springframework.data.mapping.PersistentPropertyAccessor;
5657
import org.springframework.data.mapping.PreferredConstructor.Parameter;
5758
import org.springframework.data.mapping.PropertyHandler;
@@ -271,7 +272,8 @@ public void doWithPersistentProperty(final CouchbasePersistentProperty prop) {
271272
|| prop.isAnnotationPresent(N1qlJoin.class)) {
272273
return;
273274
}
274-
Object obj = prop.isIdProperty() && parent == null ? source.getId() : getValueInternal(prop, source, instance);
275+
Object obj = prop == entity.getIdProperty() && parent == null ? source.getId()
276+
: getValueInternal(prop, source, instance, entity);
275277
accessor.setProperty(prop, obj);
276278
}
277279

@@ -286,7 +288,7 @@ private boolean isIdConstructionProperty(final CouchbasePersistentProperty prope
286288

287289
entity.doWithAssociations((AssociationHandler<CouchbasePersistentProperty>) association -> {
288290
CouchbasePersistentProperty inverseProp = association.getInverse();
289-
Object obj = getValueInternal(inverseProp, source, instance);
291+
Object obj = getValueInternal(inverseProp, source, instance, entity);
290292
accessor.setProperty(inverseProp, obj);
291293
});
292294

@@ -302,8 +304,8 @@ private boolean isIdConstructionProperty(final CouchbasePersistentProperty prope
302304
* @return the actual property value.
303305
*/
304306
protected Object getValueInternal(final CouchbasePersistentProperty property, final CouchbaseDocument source,
305-
final Object parent) {
306-
return new CouchbasePropertyValueProvider(source, spELContext, parent).getPropertyValue(property);
307+
final Object parent, PersistentEntity entity) {
308+
return new CouchbasePropertyValueProvider(source, spELContext, parent, entity).getPropertyValue(property);
307309
}
308310

309311
/**
@@ -318,7 +320,7 @@ protected Object getValueInternal(final CouchbasePersistentProperty property, fi
318320
private ParameterValueProvider<CouchbasePersistentProperty> getParameterProvider(
319321
final CouchbasePersistentEntity<?> entity, final CouchbaseDocument source,
320322
final DefaultSpELExpressionEvaluator evaluator, final Object parent) {
321-
CouchbasePropertyValueProvider provider = new CouchbasePropertyValueProvider(source, evaluator, parent);
323+
CouchbasePropertyValueProvider provider = new CouchbasePropertyValueProvider(source, evaluator, parent, entity);
322324
PersistentEntityParameterValueProvider<CouchbasePersistentProperty> parameterProvider = new PersistentEntityParameterValueProvider<>(
323325
entity, provider, parent);
324326

@@ -503,7 +505,7 @@ protected void writeInternal(final Object source, final CouchbaseDocument target
503505
final TreeMap<Integer, String> suffixes = new TreeMap<>();
504506
final TreeMap<Integer, String> idAttributes = new TreeMap<>();
505507

506-
target.setExpiration((int)(entity.getExpiryDuration().getSeconds()));
508+
target.setExpiration((int) (entity.getExpiryDuration().getSeconds()));
507509

508510
entity.doWithProperties(new PropertyHandler<CouchbasePersistentProperty>() {
509511
@Override
@@ -926,19 +928,25 @@ private class CouchbasePropertyValueProvider implements PropertyValueProvider<Co
926928
*/
927929
private final Object parent;
928930

931+
/**
932+
* The entity of the property
933+
*/
934+
private final PersistentEntity entity;
935+
929936
public CouchbasePropertyValueProvider(final CouchbaseDocument source, final SpELContext factory,
930-
final Object parent) {
931-
this(source, new DefaultSpELExpressionEvaluator(source, factory), parent);
937+
final Object parent, final PersistentEntity entity) {
938+
this(source, new DefaultSpELExpressionEvaluator(source, factory), parent, entity);
932939
}
933940

934941
public CouchbasePropertyValueProvider(final CouchbaseDocument source,
935-
final DefaultSpELExpressionEvaluator evaluator, final Object parent) {
942+
final DefaultSpELExpressionEvaluator evaluator, final Object parent, final PersistentEntity entity) {
936943
Assert.notNull(source, "CouchbaseDocument must not be null!");
937944
Assert.notNull(evaluator, "DefaultSpELExpressionEvaluator must not be null!");
938945

939946
this.source = source;
940947
this.evaluator = evaluator;
941948
this.parent = parent;
949+
this.entity = entity;
942950
}
943951

944952
@Override
@@ -947,7 +955,7 @@ public <R> R getPropertyValue(final CouchbasePersistentProperty property) {
947955
String expression = property.getSpelExpression();
948956
Object value = expression != null ? evaluator.evaluate(expression) : source.get(property.getFieldName());
949957

950-
if (property.isIdProperty() && parent == null) {
958+
if (property == entity.getIdProperty() && parent == null) {
951959
return (R) source.getId();
952960
}
953961
if (value == null) {

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

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import org.springframework.data.couchbase.core.query.QueryCriteria;
3333
import org.springframework.data.domain.Pageable;
3434
import org.springframework.data.domain.Sort;
35+
import org.springframework.data.mapping.PersistentEntity;
3536
import org.springframework.data.mapping.PersistentPropertyPath;
3637
import org.springframework.data.mapping.context.MappingContext;
3738
import org.springframework.data.repository.query.ParameterAccessor;
@@ -57,6 +58,7 @@ public class N1qlQueryCreator extends AbstractQueryCreator<Query, QueryCriteria>
5758
private final QueryMethod queryMethod;
5859
private final CouchbaseConverter converter;
5960
private final String bucketName;
61+
private final PersistentEntity entity;
6062

6163
public N1qlQueryCreator(final PartTree tree, final ParameterAccessor accessor, final QueryMethod queryMethod,
6264
final CouchbaseConverter converter, final String bucketName) {
@@ -67,13 +69,14 @@ public N1qlQueryCreator(final PartTree tree, final ParameterAccessor accessor, f
6769
this.queryMethod = queryMethod;
6870
this.converter = converter;
6971
this.bucketName = bucketName;
72+
this.entity = converter.getMappingContext().getPersistentEntity(queryMethod.getReturnedObjectType());
7073
}
7174

7275
@Override
7376
protected QueryCriteria create(final Part part, final Iterator<Object> iterator) {
7477
PersistentPropertyPath<CouchbasePersistentProperty> path = context.getPersistentPropertyPath(part.getProperty());
7578
CouchbasePersistentProperty property = path.getLeafProperty();
76-
return from(part, property, where(addMetaIfRequired(bucketName, path, property)), iterator);
79+
return from(part, property, where(addMetaIfRequired(bucketName, path, property, entity)), iterator);
7780
}
7881

7982
@Override
@@ -107,7 +110,7 @@ protected QueryCriteria and(final Part part, final QueryCriteria base, final Ite
107110
PersistentPropertyPath<CouchbasePersistentProperty> path = context.getPersistentPropertyPath(part.getProperty());
108111
CouchbasePersistentProperty property = path.getLeafProperty();
109112

110-
return from(part, property, base.and(addMetaIfRequired(bucketName, path, property)), iterator);
113+
return from(part, property, base.and(addMetaIfRequired(bucketName, path, property, entity)), iterator);
111114
}
112115

113116
@Override
@@ -186,11 +189,11 @@ private QueryCriteria from(final Part part, final CouchbasePersistentProperty pr
186189

187190
public static N1QLExpression addMetaIfRequired(String bucketName,
188191
final PersistentPropertyPath<CouchbasePersistentProperty> persistentPropertyPath,
189-
final CouchbasePersistentProperty property) {
190-
if (property.isIdProperty()) {
192+
final CouchbasePersistentProperty property, final PersistentEntity entity) {
193+
if (entity != null && property == entity.getIdProperty()) {
191194
return path(meta(i(bucketName)), i(META_ID_PROPERTY));
192195
}
193-
if (property.isVersionProperty()) {
196+
if (property == entity.getVersionProperty()) {
194197
return path(meta(i(bucketName)), i(META_CAS_PROPERTY));
195198
}
196199
if (property.isExpirationProperty()) {

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
import java.util.regex.Matcher;
3030
import java.util.regex.Pattern;
3131

32-
import com.couchbase.client.core.error.CouchbaseException;
3332
import org.slf4j.Logger;
3433
import org.slf4j.LoggerFactory;
3534
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
@@ -51,6 +50,7 @@
5150
import org.springframework.expression.spel.standard.SpelExpressionParser;
5251
import org.springframework.util.Assert;
5352

53+
import com.couchbase.client.core.error.CouchbaseException;
5454
import com.couchbase.client.core.error.InvalidArgumentException;
5555
import com.couchbase.client.java.json.JsonArray;
5656
import com.couchbase.client.java.json.JsonObject;
@@ -213,18 +213,18 @@ private void getProjectedFieldsInternal(String bucketName, CouchbasePersistentPr
213213
persistentEntity.doWithProperties(new PropertyHandler<CouchbasePersistentProperty>() {
214214
@Override
215215
public void doWithPersistentProperty(final CouchbasePersistentProperty prop) {
216-
if (prop.isIdProperty() && parent == null) {
216+
if (prop == persistentEntity.getIdProperty() && parent == null) {
217217
return;
218218
}
219-
if (prop.isVersionProperty()) {
219+
if (prop == persistentEntity.getVersionProperty() && parent == null) {
220220
return;
221221
}
222222
String projectField = null;
223223

224224
if (fieldList == null || fieldList.contains(prop.getFieldName())) {
225225
PersistentPropertyPath<CouchbasePersistentProperty> path = couchbaseConverter.getMappingContext()
226226
.getPersistentPropertyPath(prop.getName(), resultClass.getType());
227-
projectField = N1qlQueryCreator.addMetaIfRequired(bucketName, path, prop).toString();
227+
projectField = N1qlQueryCreator.addMetaIfRequired(bucketName, path, prop, persistentEntity).toString();
228228
if (sb.length() > 0) {
229229
sb.append(", ");
230230
}
@@ -250,7 +250,7 @@ public void doWithPersistentProperty(final CouchbasePersistentProperty prop) {
250250
// needs further discussion as removing a field from an entity could cause this and not necessarily be an error
251251
if (fieldList != null && !fieldList.isEmpty()) {
252252
throw new CouchbaseException(
253-
"projected fields (" + fieldList + ") not found in entity: " + persistentEntity.getName());
253+
"projected fields (" + fieldList + ") not found in entity: " + persistentEntity.getName());
254254
}
255255
} else {
256256
for (String field : fields) {

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.springframework.data.couchbase.core.query.QueryCriteria;
3636
import org.springframework.data.couchbase.domain.Address;
3737
import org.springframework.data.couchbase.domain.Airport;
38+
import org.springframework.data.couchbase.domain.AssessmentDO;
3839
import org.springframework.data.couchbase.domain.Course;
3940
import org.springframework.data.couchbase.domain.NaiveAuditorAware;
4041
import org.springframework.data.couchbase.domain.Submission;
@@ -132,6 +133,21 @@ void findByMatchingQuery() {
132133
assertEquals(1, foundUsers.size());
133134
}
134135

136+
@Test
137+
void findAssessmentDO() {
138+
AssessmentDO ado = new AssessmentDO();
139+
ado.setEventTimestamp(44444444);// this is also an @IdAttribute
140+
ado.setId("123");
141+
ado = couchbaseTemplate.upsertById(AssessmentDO.class).one(ado);
142+
143+
Query specialUsers = new Query(QueryCriteria.where(i("id")).is(ado.getId()));
144+
final List<AssessmentDO> foundUsers = couchbaseTemplate.findByQuery(AssessmentDO.class)
145+
.withConsistency(QueryScanConsistency.REQUEST_PLUS).matching(specialUsers).all();
146+
assertEquals("123", foundUsers.get(0).getId(), "id");
147+
assertEquals("44444444", foundUsers.get(0).getDocumentId(), "documentId");
148+
assertEquals(ado, foundUsers.get(0));
149+
}
150+
135151
@Test
136152
void findByMatchingQueryProjected() {
137153

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright 2012-2021 the original author or authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.data.couchbase.domain;
18+
19+
import lombok.Data;
20+
import lombok.NoArgsConstructor;
21+
22+
import org.springframework.data.annotation.Id;
23+
import org.springframework.data.couchbase.core.mapping.Document;
24+
import org.springframework.data.couchbase.core.mapping.Field;
25+
import org.springframework.data.couchbase.core.mapping.id.GeneratedValue;
26+
import org.springframework.data.couchbase.core.mapping.id.GenerationStrategy;
27+
import org.springframework.data.couchbase.core.mapping.id.IdAttribute;
28+
29+
/**
30+
* @author Michael Reiche
31+
*/
32+
@Document()
33+
@Data
34+
@NoArgsConstructor
35+
public class AssessmentDO {
36+
@Id @GeneratedValue(strategy = GenerationStrategy.USE_ATTRIBUTES) private String documentId;
37+
38+
@Field @IdAttribute private long eventTimestamp;
39+
40+
@Field("docType") private String documentType;
41+
42+
@Field private String id;
43+
}

src/test/java/org/springframework/data/couchbase/repository/query/N1qlQueryCreatorTests.java

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,13 @@
3737
import org.springframework.data.couchbase.domain.User;
3838
import org.springframework.data.couchbase.domain.UserRepository;
3939
import org.springframework.data.mapping.context.MappingContext;
40+
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
41+
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
4042
import org.springframework.data.repository.query.DefaultParameters;
4143
import org.springframework.data.repository.query.ParameterAccessor;
4244
import org.springframework.data.repository.query.Parameters;
4345
import org.springframework.data.repository.query.ParametersParameterAccessor;
46+
import org.springframework.data.repository.query.QueryMethod;
4447
import org.springframework.data.repository.query.parser.PartTree;
4548

4649
import com.couchbase.client.java.json.JsonArray;
@@ -69,9 +72,10 @@ void createsQueryCorrectly() throws Exception {
6972
String input = "findByFirstname";
7073
PartTree tree = new PartTree(input, User.class);
7174
Method method = UserRepository.class.getMethod(input, String.class);
72-
73-
N1qlQueryCreator creator = new N1qlQueryCreator(tree, getAccessor(getParameters(method), "Oliver"), null, converter,
74-
bucketName);
75+
QueryMethod queryMethod = new QueryMethod(method, new DefaultRepositoryMetadata(UserRepository.class),
76+
new SpelAwareProxyProjectionFactory());
77+
N1qlQueryCreator creator = new N1qlQueryCreator(tree, getAccessor(getParameters(method), "Oliver"), queryMethod,
78+
converter, bucketName);
7579
Query query = creator.createQuery();
7680

7781
assertEquals(query.export(), " WHERE " + where(i("firstname")).is("Oliver").export());
@@ -82,9 +86,10 @@ void createsQueryFieldAnnotationCorrectly() throws Exception {
8286
String input = "findByMiddlename";
8387
PartTree tree = new PartTree(input, Person.class);
8488
Method method = PersonRepository.class.getMethod(input, String.class);
85-
86-
N1qlQueryCreator creator = new N1qlQueryCreator(tree, getAccessor(getParameters(method), "Oliver"), null, converter,
87-
bucketName);
89+
QueryMethod queryMethod = new QueryMethod(method, new DefaultRepositoryMetadata(UserRepository.class),
90+
new SpelAwareProxyProjectionFactory());
91+
N1qlQueryCreator creator = new N1qlQueryCreator(tree, getAccessor(getParameters(method), "Oliver"), queryMethod,
92+
converter, bucketName);
8893
Query query = creator.createQuery();
8994

9095
assertEquals(query.export(), " WHERE " + where(i("nickname")).is("Oliver").export());
@@ -95,10 +100,12 @@ void queryParametersArray() throws Exception {
95100
String input = "findByFirstnameIn";
96101
PartTree tree = new PartTree(input, User.class);
97102
Method method = UserRepository.class.getMethod(input, String[].class);
103+
QueryMethod queryMethod = new QueryMethod(method, new DefaultRepositoryMetadata(UserRepository.class),
104+
new SpelAwareProxyProjectionFactory());
98105
Query expected = (new Query()).addCriteria(where(i("firstname")).in("Oliver", "Charles"));
99106
N1qlQueryCreator creator = new N1qlQueryCreator(tree,
100-
getAccessor(getParameters(method), new Object[] { new Object[] { "Oliver", "Charles" } }), null, converter,
101-
bucketName);
107+
getAccessor(getParameters(method), new Object[] { new Object[] { "Oliver", "Charles" } }), queryMethod,
108+
converter, bucketName);
102109
Query query = creator.createQuery();
103110

104111
// Query expected = (new Query()).addCriteria(where("firstname").in("Oliver", "Charles"));
@@ -115,11 +122,12 @@ void queryParametersJsonArray() throws Exception {
115122
String input = "findByFirstnameIn";
116123
PartTree tree = new PartTree(input, User.class);
117124
Method method = UserRepository.class.getMethod(input, JsonArray.class);
118-
125+
QueryMethod queryMethod = new QueryMethod(method, new DefaultRepositoryMetadata(UserRepository.class),
126+
new SpelAwareProxyProjectionFactory());
119127
JsonArray jsonArray = JsonArray.create();
120128
jsonArray.add("Oliver");
121129
jsonArray.add("Charles");
122-
N1qlQueryCreator creator = new N1qlQueryCreator(tree, getAccessor(getParameters(method), jsonArray), null,
130+
N1qlQueryCreator creator = new N1qlQueryCreator(tree, getAccessor(getParameters(method), jsonArray), queryMethod,
123131
converter, bucketName);
124132
Query query = creator.createQuery();
125133

@@ -137,11 +145,13 @@ void queryParametersList() throws Exception {
137145
String input = "findByFirstnameIn";
138146
PartTree tree = new PartTree(input, User.class);
139147
Method method = UserRepository.class.getMethod(input, String[].class);
148+
QueryMethod queryMethod = new QueryMethod(method, new DefaultRepositoryMetadata(UserRepository.class),
149+
new SpelAwareProxyProjectionFactory());
140150
List<String> list = new LinkedList<>();
141151
list.add("Oliver");
142152
list.add("Charles");
143153
N1qlQueryCreator creator = new N1qlQueryCreator(tree, getAccessor(getParameters(method), new Object[] { list }),
144-
null, converter, bucketName);
154+
queryMethod, converter, bucketName);
145155
Query query = creator.createQuery();
146156

147157
Query expected = (new Query()).addCriteria(where(i("firstname")).in("Oliver", "Charles"));
@@ -159,8 +169,10 @@ void createsAndQueryCorrectly() throws Exception {
159169
String input = "findByFirstnameAndLastname";
160170
PartTree tree = new PartTree(input, User.class);
161171
Method method = UserRepository.class.getMethod(input, String.class, String.class);
162-
N1qlQueryCreator creator = new N1qlQueryCreator(tree, getAccessor(getParameters(method), "John", "Doe"), null,
163-
converter, bucketName);
172+
QueryMethod queryMethod = new QueryMethod(method, new DefaultRepositoryMetadata(UserRepository.class),
173+
new SpelAwareProxyProjectionFactory());
174+
N1qlQueryCreator creator = new N1qlQueryCreator(tree, getAccessor(getParameters(method), "John", "Doe"),
175+
queryMethod, converter, bucketName);
164176
Query query = creator.createQuery();
165177

166178
assertEquals(" WHERE " + where(i("firstname")).is("John").and(i("lastname")).is("Doe").export(), query.export());
@@ -171,9 +183,10 @@ void createsQueryFindByIdIsNotNullAndFirstname() throws Exception {
171183
String input = "findByIdIsNotNullAndFirstnameEquals";
172184
PartTree tree = new PartTree(input, User.class);
173185
Method method = UserRepository.class.getMethod(input, String.class);
174-
175-
N1qlQueryCreator creator = new N1qlQueryCreator(tree, getAccessor(getParameters(method), "Oliver"), null, converter,
176-
bucketName);
186+
QueryMethod queryMethod = new QueryMethod(method, new DefaultRepositoryMetadata(UserRepository.class),
187+
new SpelAwareProxyProjectionFactory());
188+
N1qlQueryCreator creator = new N1qlQueryCreator(tree, getAccessor(getParameters(method), "Oliver"), queryMethod,
189+
converter, bucketName);
177190
Query query = creator.createQuery();
178191

179192
assertEquals(query.export(),
@@ -185,9 +198,10 @@ void createsQueryFindByVersionEqualsAndAndFirstname() throws Exception {
185198
String input = "findByVersionEqualsAndFirstnameEquals";
186199
PartTree tree = new PartTree(input, User.class);
187200
Method method = UserRepository.class.getMethod(input, Long.class, String.class);
188-
201+
QueryMethod queryMethod = new QueryMethod(method, new DefaultRepositoryMetadata(UserRepository.class),
202+
new SpelAwareProxyProjectionFactory());
189203
N1qlQueryCreator creator = new N1qlQueryCreator(tree,
190-
getAccessor(getParameters(method), 1611287177404088320L, "Oliver"), null, converter, bucketName);
204+
getAccessor(getParameters(method), 1611287177404088320L, "Oliver"), queryMethod, converter, bucketName);
191205
Query query = creator.createQuery();
192206

193207
assertEquals(query.export(), " WHERE " + where(x("META(`" + bucketName + "`).`cas`")).is(1611287177404088320L)

0 commit comments

Comments
 (0)