DATACOUCH-186 - Index-backed methods safe from deleted stales

This commit ensures that methods in the template and the repository base classes are correctly ignoring documents that have been deleted but are still identified by the view due to using weak consistency (Stale.TRUE).

FindByView in template now ignores null rows in its result list. It also correctly forces the includeDocs parameter in order not to risk a bad transcoding of the document (javadoc clarified).

DeleteAll in the repository CRUD implementation will correctly detect attempts at deleting a non-existing document (DocumentDoesNotExistException is also correctly mapped to a Spring Data exception now).

DeleteAll behavior with DataRetrievalFailureException is tested in RepositoryIndexUsageTest.
This commit is contained in:
Simon Baslé
2016-01-08 16:57:53 +01:00
parent d81dc9aacc
commit c589a13699
6 changed files with 73 additions and 10 deletions

View File

@@ -33,6 +33,7 @@ import com.couchbase.client.java.error.BucketDoesNotExistException;
import com.couchbase.client.java.error.CASMismatchException;
import com.couchbase.client.java.error.DesignDocumentException;
import com.couchbase.client.java.error.DocumentAlreadyExistsException;
import com.couchbase.client.java.error.DocumentDoesNotExistException;
import com.couchbase.client.java.error.DurabilityException;
import com.couchbase.client.java.error.InvalidPasswordException;
import com.couchbase.client.java.error.RequestTooBigException;
@@ -90,6 +91,10 @@ public class CouchbaseExceptionTranslator implements PersistenceExceptionTransla
return new DuplicateKeyException(ex.getMessage(), ex);
}
if (ex instanceof DocumentDoesNotExistException) {
return new DataRetrievalFailureException(ex.getMessage(), ex);
}
if (ex instanceof CASMismatchException
|| ex instanceof DocumentConcurrentlyModifiedException
|| ex instanceof ReplicaNotConfiguredException

View File

@@ -190,9 +190,13 @@ public interface CouchbaseOperations {
/**
* Query a View for a list of documents of type T.
* <p/>
* <p>There is no need to {@link ViewQuery#includeDocs(boolean) set includeDocs} explicitly, because it will be set to
* true all the time. It is valid to pass in a empty constructed {@link ViewQuery} object.</p>
* <p>There is no need to {@link ViewQuery#includeDocs(boolean) set includeDocs} explicitly, since this method will
* manage the retrieval of documents internally. It is valid to pass in a empty constructed {@link ViewQuery} object.</p>
* <p/>
* <p>Weak consistency in the query (<code>stale(Stale.TRUE)</code>) can lead to some documents being unreachable due
* to their deletion not having been indexed. These deleted null documents are eliminated from the result of this
* method.</p>
* </p>
* <p>This method does not work with reduced views, because they by design do not contain references to original
* objects. Use the provided {@link #queryView} method for more flexibility and direct access.</p>
*
@@ -217,9 +221,13 @@ public interface CouchbaseOperations {
/**
* Query a Spatial View for a list of documents of type T.
* </p>
* <p>There is no need to {@link SpatialViewQuery#includeDocs(boolean) set includeDocs} explicitly, since this method
* will manage the retrieval of documents internally. It is valid to pass in a empty constructed {@link SpatialViewQuery} object.</p>
* <p/>
* <p>It is valid to pass in a empty constructed {@link SpatialViewQuery} object.</p>
* <p/>
* <p>Weak consistency in the query (<code>stale(Stale.TRUE)</code>) can lead to some documents being unreachable due
* to their deletion not having been indexed. These deleted null documents are eliminated from the result of this
* method.</p>
*
* @param query the SpatialViewQuery object (also specifying view design document and view name).
* @param entityClass the entity to map to.
@@ -248,6 +256,10 @@ public interface CouchbaseOperations {
* statement contains placeholders.
* <br/>
* Use {@link N1qlQuery}'s factory methods to construct such a Query.</p>
* <p/>
* <p>Weak consistency in the query (eg. <code>ScanConsistency.NOT_BOUND</code>) can lead to some documents being
* unreachable due to their deletion not having been indexed. These deleted null documents are eliminated from the
* result of this method.</p>
*
* @param n1ql the N1QL query.
* @param entityClass the target class for the returned entities.
@@ -266,6 +278,10 @@ public interface CouchbaseOperations {
* statement contains placeholders.
* <br/>
* Use {@link N1qlQuery}'s factory methods to construct such a Query.</p>
* <p/>
* <p>Weak consistency in the query (eg. <code>ScanConsistency.NOT_BOUND</code>) can lead to some documents being
* unreachable due to their deletion not having been indexed. These deleted null documents are eliminated from the
* result of this method.</p>
*
* @param n1ql the N1QL query.
* @param fragmentClass the target class for the returned fragments.

View File

@@ -293,7 +293,10 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationEventP
@Override
public <T> List<T> findByView(ViewQuery query, Class<T> entityClass) {
query.includeDocs(false); //FIXME the doc says it is set to true...
//we'll always need to get documents, as a RawJsonDocument, so we should force includeDocs(false)
//so that the caller doesn't set a bad target class unintentionally, pre-loading with a bad type.
query.includeDocs(false);
//we'll always map the document to the entity, hence reduce never makes sense.
query.reduce(false);
try {
@@ -307,7 +310,11 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationEventP
final List<T> result = new ArrayList<T>(allRows.size());
for (final ViewRow row : allRows) {
result.add(mapToEntity(row.id(), row.document(RawJsonDocument.class), entityClass));
//cope with potential weak consistency and deletions
T entity = mapToEntity(row.id(), row.document(RawJsonDocument.class), entityClass);
if (entity != null) {
result.add(entity);
}
}
return result;
@@ -329,7 +336,9 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationEventP
@Override
public <T> List<T> findBySpatialView(SpatialViewQuery query, Class<T> entityClass) {
//note: don't make any assumption about includeDocs and let the user decide
//we'll always need to get documents, as a RawJsonDocument, so we should force includeDocs(false)
//so that the caller doesn't set a bad target class unintentionally, pre-loading with a bad type.
query.includeDocs(false);
try {
final SpatialViewResult response = querySpatialView(query);
@@ -342,7 +351,11 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationEventP
final List<T> result = new ArrayList<T>(allRows.size());
for (final SpatialViewRow row : allRows) {
result.add(mapToEntity(row.id(), row.document(RawJsonDocument.class), entityClass));
//cope with potential weak consistency and deletions
T entity = mapToEntity(row.id(), row.document(RawJsonDocument.class), entityClass);
if (entity != null) {
result.add(entity);
}
}
return result;
@@ -379,7 +392,7 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationEventP
throw new CouchbaseQueryExecutionException("Unable to retrieve enough metadata for N1QL to entity mapping, " +
"have you selected " + SELECT_ID + " and " + SELECT_CAS + "?");
}
json = json.removeKey("_ID").removeKey("_CAS");
json = json.removeKey(SELECT_ID).removeKey(SELECT_CAS);
RawJsonDocument entityDoc = RawJsonDocument.create(id, json.toString(), cas);
T decoded = mapToEntity(id, entityDoc, entityClass);
result.add(decoded);

View File

@@ -21,10 +21,12 @@ import java.util.ArrayList;
import java.util.List;
import com.couchbase.client.java.document.json.JsonArray;
import com.couchbase.client.java.error.DocumentDoesNotExistException;
import com.couchbase.client.java.view.ViewQuery;
import com.couchbase.client.java.view.ViewResult;
import com.couchbase.client.java.view.ViewRow;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.data.couchbase.core.CouchbaseOperations;
import org.springframework.data.couchbase.core.query.View;
import org.springframework.data.couchbase.repository.CouchbaseRepository;
@@ -180,7 +182,12 @@ public class SimpleCouchbaseRepository<T, ID extends Serializable> implements Co
ViewResult response = couchbaseOperations.queryView(query);
for (ViewRow row : response) {
couchbaseOperations.remove(row.id());
try {
couchbaseOperations.remove(row.id());
} catch (DataRetrievalFailureException e) {
//ignore stale deletions
if (!(e.getCause() instanceof DocumentDoesNotExistException)) throw e;
}
}
}