Skip to content

Commit 8847544

Browse files
committed
Only project id and cas if required. (#1404)
Closes #1402.
1 parent b1272bc commit 8847544

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
@@ -143,7 +143,7 @@ public <T> T decodeEntity(String id, String source, Long cas, Class<T> entityCla
143143

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

146-
if (cas != 0 && persistentEntity.getVersionProperty() != null) {
146+
if (cas != null && cas != 0 && persistentEntity.getVersionProperty() != null) {
147147
accessor.setProperty(persistentEntity.getVersionProperty(), cas);
148148
}
149149
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.util.Map;
@@ -87,8 +89,16 @@ public Mono<CouchbaseDocument> encodeEntity(final Object entityToEncode) {
8789
public <T> Mono<T> decodeEntity(String id, String source, Long cas, Class<T> entityClass, String scope,
8890
String collection) {
8991
return Mono.fromSupplier(() -> {
90-
final CouchbaseDocument converted = new CouchbaseDocument(id);
91-
converted.setId(id);
92+
// this is the entity class defined for the repository. It may not be the class of the document that was read
93+
// we will reset it after reading the document
94+
//
95+
// This will fail for the case where:
96+
// 1) The version is defined in the concrete class, but not in the abstract class; and
97+
// 2) The constructor takes a "long version" argument resulting in an exception would be thrown if version in
98+
// the source is null.
99+
// We could expose from the MappingCouchbaseConverter determining the persistent entity from the source,
100+
// but that is a lot of work to do every time just for this very rare and avoidable case.
101+
// TypeInformation<? extends R> typeToUse = typeMapper.readType(source, type);
92102

93103
CouchbasePersistentEntity persistentEntity = couldBePersistentEntity(entityClass);
94104

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

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

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

112-
if (persistentEntity.getVersionProperty() != null) {
144+
persistentEntity = couldBePersistentEntity(readEntity.getClass());
145+
146+
if (cas != null && cas != 0 && persistentEntity.getVersionProperty() != null) {
113147
accessor.setProperty(persistentEntity.getVersionProperty(), cas);
114148
}
115149
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)