Skip to content

Commit 7392930

Browse files
authored
Only project id and cas if required. (#1404)
Closes #1402.
1 parent dbdbc03 commit 7392930

File tree

4 files changed

+79
-15
lines changed

4 files changed

+79
-15
lines changed

src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplateSupport.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ public <T> T decodeEntity(String id, String source, Long cas, Class<T> entityCla
144144

145145
persistentEntity = couldBePersistentEntity(readEntity.getClass());
146146

147-
if (cas != 0 && persistentEntity.getVersionProperty() != null) {
147+
if (cas != null && cas != 0 && persistentEntity.getVersionProperty() != null) {
148148
accessor.setProperty(persistentEntity.getVersionProperty(), cas);
149149
}
150150
N1qlJoinResolver.handleProperties(persistentEntity, accessor, template.reactive(), id, scope, collection);

src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplateSupport.java

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.data.couchbase.core;
1818

19+
import com.couchbase.client.core.error.CouchbaseException;
20+
import org.springframework.data.couchbase.core.support.TemplateUtils;
1921
import reactor.core.publisher.Mono;
2022

2123
import java.lang.reflect.InaccessibleObjectException;
@@ -88,8 +90,16 @@ public Mono<CouchbaseDocument> encodeEntity(final Object entityToEncode) {
8890
public <T> Mono<T> decodeEntity(String id, String source, Long cas, Class<T> entityClass, String scope,
8991
String collection) {
9092
return Mono.fromSupplier(() -> {
91-
final CouchbaseDocument converted = new CouchbaseDocument(id);
92-
converted.setId(id);
93+
// this is the entity class defined for the repository. It may not be the class of the document that was read
94+
// we will reset it after reading the document
95+
//
96+
// This will fail for the case where:
97+
// 1) The version is defined in the concrete class, but not in the abstract class; and
98+
// 2) The constructor takes a "long version" argument resulting in an exception would be thrown if version in
99+
// the source is null.
100+
// We could expose from the MappingCouchbaseConverter determining the persistent entity from the source,
101+
// but that is a lot of work to do every time just for this very rare and avoidable case.
102+
// TypeInformation<? extends R> typeToUse = typeMapper.readType(source, type);
93103

94104
CouchbasePersistentEntity persistentEntity = couldBePersistentEntity(entityClass);
95105

@@ -98,19 +108,43 @@ public <T> Mono<T> decodeEntity(String id, String source, Long cas, Class<T> ent
98108
// to unwrap. This results in List<String[]> being unwrapped past String[] to String, so this may also be a
99109
// Collection (or Array) of entityClass. We have no way of knowing - so just assume it is what we are told.
100110
// if this is a Collection or array, only the first element will be returned.
111+
final CouchbaseDocument converted = new CouchbaseDocument(id);
101112
Set<Map.Entry<String, Object>> set = ((CouchbaseDocument) translationService.decode(source, converted))
102113
.getContent().entrySet();
103114
return (T) set.iterator().next().getValue();
104115
}
105116

106-
if (cas != 0 && persistentEntity.getVersionProperty() != null) {
107-
converted.put(persistentEntity.getVersionProperty().getName(), cas);
117+
if (id == null) {
118+
throw new CouchbaseException(TemplateUtils.SELECT_ID + " was null. Either use #{#n1ql.selectEntity} or project "
119+
+ TemplateUtils.SELECT_ID);
120+
}
121+
122+
final CouchbaseDocument converted = new CouchbaseDocument(id);
123+
124+
// if possible, set the version property in the source so that if the constructor has a long version argument,
125+
// it will have a value and not fail (as null is not a valid argument for a long argument). This possible failure
126+
// can be avoid by defining the argument as Long instead of long.
127+
// persistentEntity is still the (possibly abstract) class specified in the repository definition
128+
// it's possible that the abstract class does not have a version property, and this won't be able to set the version
129+
if (persistentEntity.getVersionProperty() != null) {
130+
if (cas == null) {
131+
throw new CouchbaseException("version/cas in the entity but " + TemplateUtils.SELECT_CAS
132+
+ " was not in result. Either use #{#n1ql.selectEntity} or project " + TemplateUtils.SELECT_CAS);
133+
}
134+
if (cas != 0) {
135+
converted.put(persistentEntity.getVersionProperty().getName(), cas);
136+
}
108137
}
109138

139+
// if the constructor has an argument that is long version, then construction will fail if the 'version'
140+
// is not available as 'null' is not a legal value for a long. Changing the arg to "Long version" would solve this.
141+
// (Version doesn't come from 'source', it comes from the cas argument to decodeEntity)
110142
T readEntity = converter.read(entityClass, (CouchbaseDocument) translationService.decode(source, converted));
111143
final ConvertingPropertyAccessor<T> accessor = getPropertyAccessor(readEntity);
112144

113-
if (persistentEntity.getVersionProperty() != null) {
145+
persistentEntity = couldBePersistentEntity(readEntity.getClass());
146+
147+
if (cas != null && cas != 0 && persistentEntity.getVersionProperty() != null) {
114148
accessor.setProperty(persistentEntity.getVersionProperty(), cas);
115149
}
116150
N1qlJoinResolver.handleProperties(persistentEntity, accessor, template, id, scope, collection);

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

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -162,9 +162,7 @@ public N1qlSpelValues createN1qlSpelValues(String bucketName, String collection,
162162
String b = collection != null ? collection : bucketName;
163163
Assert.isTrue(!(distinctFields != null && fields != null),
164164
"only one of project(fields) and distinct(distinctFields) can be specified");
165-
String entity = "META(" + i(b) + ").id AS " + SELECT_ID + ", META(" + i(b) + ").cas AS " + SELECT_CAS + ", "
166-
+ i(typeField);
167-
String count = "COUNT(*) AS " + CountFragment.COUNT_ALIAS;
165+
String entityFields = "";
168166
String selectEntity;
169167
if (distinctFields != null) {
170168
String distinctFieldsStr = getProjectedOrDistinctFields(b, domainClass, typeField, fields, distinctFields);
@@ -175,47 +173,79 @@ public N1qlSpelValues createN1qlSpelValues(String bucketName, String collection,
175173
selectEntity = "SELECT DISTINCT " + distinctFieldsStr + " FROM " + i(b);
176174
}
177175
} else if (isCount) {
178-
selectEntity = "SELECT " + count + " FROM " + i(b);
176+
selectEntity = "SELECT " + "COUNT(*) AS " + CountFragment.COUNT_ALIAS + " FROM " + i(b);
179177
} else {
180178
String projectedFields = getProjectedOrDistinctFields(b, domainClass, typeField, fields, distinctFields);
181-
selectEntity = "SELECT " + entity + (!projectedFields.isEmpty() ? ", " : " ") + projectedFields + " FROM " + i(b);
179+
entityFields = projectedFields;
180+
selectEntity = "SELECT " + projectedFields + " FROM " + i(b);
182181
}
183182
String typeSelection = "`" + typeField + "` = \"" + typeValue + "\"";
184183

185184
String delete = N1QLExpression.delete().from(b).toString();
186185
String returning = " returning " + N1qlUtils.createReturningExpressionForDelete(b).toString();
187186

188-
return new N1qlSpelValues(selectEntity, entity, i(b).toString(), typeSelection, delete, returning);
187+
return new N1qlSpelValues(selectEntity, entityFields, i(b).toString(), typeSelection, delete, returning);
189188
}
190189

191190
private String getProjectedOrDistinctFields(String b, Class resultClass, String typeField, String[] fields,
192191
String[] distinctFields) {
193192
if (distinctFields != null && distinctFields.length != 0) {
194193
return i(distinctFields).toString();
195194
}
196-
String projectedFields = i(b) + ".*"; // if we can't get further information of the fields needed project everything
195+
String projectedFields;
197196
if (resultClass != null && !Modifier.isAbstract(resultClass.getModifiers())) {
198197
PersistentEntity persistentEntity = couchbaseConverter.getMappingContext().getPersistentEntity(resultClass);
199198
StringBuilder sb = new StringBuilder();
200199
getProjectedFieldsInternal(b, null, sb, persistentEntity, typeField, fields, distinctFields != null);
201200
projectedFields = sb.toString();
201+
} else {
202+
projectedFields = i(b) + ".*, " + "META(`" + b + "`).id AS " + SELECT_ID + ", META(`" + b + "`).cas AS "
203+
+ SELECT_CAS; // if we can't get further information of the fields needed, then project everything
202204
}
203205
return projectedFields;
204206
}
205207

206208
private void getProjectedFieldsInternal(String bucketName, CouchbasePersistentProperty parent, StringBuilder sb,
207209
PersistentEntity persistentEntity, String typeField, String[] fields, boolean forDistinct) {
208210

211+
sb.append(i(typeField));
212+
209213
if (persistentEntity != null) {
210214
Set<String> fieldList = fields != null ? new HashSet<>(Arrays.asList(fields)) : null;
211215

212216
// do not include the id and cas metadata fields.
213217

214218
persistentEntity.doWithProperties((PropertyHandler<CouchbasePersistentProperty>) prop -> {
215219
if (prop == persistentEntity.getIdProperty() && parent == null) {
220+
if (forDistinct) {
221+
return;
222+
}
223+
if (sb.length() > 0) {
224+
sb.append(", ");
225+
}
226+
PersistentPropertyPath<CouchbasePersistentProperty> path = couchbaseConverter.getMappingContext()
227+
.getPersistentPropertyPath(prop.getName(), persistentEntity.getTypeInformation().getType());
228+
String projectField = N1qlQueryCreator.addMetaIfRequired(bucketName, path, prop, persistentEntity).toString();
229+
sb.append(projectField + " AS " + SELECT_ID);
230+
if (fieldList != null) {
231+
fieldList.remove(prop.getFieldName());
232+
}
216233
return;
217234
}
218235
if (prop == persistentEntity.getVersionProperty() && parent == null) {
236+
if (forDistinct) {
237+
return;
238+
}
239+
if (sb.length() > 0) {
240+
sb.append(", ");
241+
}
242+
PersistentPropertyPath<CouchbasePersistentProperty> path = couchbaseConverter.getMappingContext()
243+
.getPersistentPropertyPath(prop.getName(), persistentEntity.getTypeInformation().getType());
244+
String projectField = N1qlQueryCreator.addMetaIfRequired(bucketName, path, prop, persistentEntity).toString();
245+
sb.append(projectField + " AS " + SELECT_CAS);
246+
if (fieldList != null) {
247+
fieldList.remove(prop.getFieldName());
248+
}
219249
return;
220250
}
221251
if (prop.getFieldName().equals(typeField)) // typeField already projected

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ void createsQueryCorrectly() throws Exception {
8989

9090
Query query = creator.createQuery();
9191
assertEquals(
92-
"SELECT META(`travel-sample`).id AS __id, META(`travel-sample`).cas AS __cas, `_class`, `createdBy`, `createdDate`, `lastModifiedBy`, `lastModifiedDate`, `firstname`, `lastname`, `subtype` FROM `travel-sample` where `_class` = \"abstractuser\" and firstname = $1 and lastname = $2",
92+
"SELECT `_class`, META(`travel-sample`).`cas` AS __cas, `createdBy`, `createdDate`, `lastModifiedBy`, `lastModifiedDate`, META(`travel-sample`).`id` AS __id, `firstname`, `lastname`, `subtype` FROM `travel-sample` where `_class` = \"abstractuser\" and firstname = $1 and lastname = $2",
9393
query.toN1qlSelectString(couchbaseTemplate.reactive(), User.class, false));
9494
}
9595

@@ -108,7 +108,7 @@ void createsQueryCorrectly2() throws Exception {
108108

109109
Query query = creator.createQuery();
110110
assertEquals(
111-
"SELECT META(`travel-sample`).id AS __id, META(`travel-sample`).cas AS __cas, `_class`, `createdBy`, `createdDate`, `lastModifiedBy`, `lastModifiedDate`, `firstname`, `lastname`, `subtype` FROM `travel-sample` where `_class` = \"abstractuser\" and (firstname = $first or lastname = $last)",
111+
"SELECT `_class`, META(`travel-sample`).`cas` AS __cas, `createdBy`, `createdDate`, `lastModifiedBy`, `lastModifiedDate`, META(`travel-sample`).`id` AS __id, `firstname`, `lastname`, `subtype` FROM `travel-sample` where `_class` = \"abstractuser\" and (firstname = $first or lastname = $last)",
112112
query.toN1qlSelectString(couchbaseTemplate.reactive(), User.class, false));
113113
}
114114

0 commit comments

Comments
 (0)