Skip to content

Commit 62b2d60

Browse files
authored
[DE-371] cluster dirty reads (#455)
* getDocumentsDirtyRead * queryAllowDirtyRead * tests fixes * transactionDirtyRead
1 parent 0d1f01a commit 62b2d60

10 files changed

+93
-2
lines changed

src/main/java/com/arangodb/ArangoCursor.java

+6
Original file line numberDiff line numberDiff line change
@@ -70,4 +70,10 @@ public interface ArangoCursor<T> extends ArangoIterable<T>, ArangoIterator<T>, C
7070
*/
7171
List<T> asListRemaining();
7272

73+
/**
74+
* @return true if the result is a potential dirty read
75+
* @since ArangoDB 3.10
76+
*/
77+
boolean isPotentialDirtyRead();
78+
7379
}

src/main/java/com/arangodb/entity/CursorEntity.java

+1
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ public VPackSlice getResult() {
8888
}
8989

9090
public Map<String, String> getMeta() {
91+
if (meta == null) return Collections.emptyMap();
9192
return meta;
9293
}
9394

src/main/java/com/arangodb/entity/MultiDocumentEntity.java

+12
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public class MultiDocumentEntity<E> implements Entity {
3030
private Collection<E> documents;
3131
private Collection<ErrorEntity> errors;
3232
private Collection<Object> documentsAndErrors;
33+
private boolean isPotentialDirtyRead = false;
3334

3435
public MultiDocumentEntity() {
3536
super();
@@ -68,4 +69,15 @@ public void setDocumentsAndErrors(final Collection<Object> documentsAndErrors) {
6869
this.documentsAndErrors = documentsAndErrors;
6970
}
7071

72+
/**
73+
* @return true if the result is a potential dirty read
74+
* @since ArangoDB 3.10
75+
*/
76+
public Boolean isPotentialDirtyRead() {
77+
return isPotentialDirtyRead;
78+
}
79+
80+
public void setPotentialDirtyRead(final Boolean isPotentialDirtyRead) {
81+
this.isPotentialDirtyRead = isPotentialDirtyRead;
82+
}
7183
}

src/main/java/com/arangodb/internal/InternalArangoCollection.java

+2
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,8 @@ protected <T> ResponseDeserializer<MultiDocumentEntity<T>> getDocumentsResponseD
229229
final Class<T> type, final DocumentReadOptions options) {
230230
return response -> {
231231
final MultiDocumentEntity<T> multiDocument = new MultiDocumentEntity<>();
232+
boolean potentialDirtyRead = Boolean.parseBoolean(response.getMeta().get("X-Arango-Potential-Dirty-Read"));
233+
multiDocument.setPotentialDirtyRead(potentialDirtyRead);
232234
final Collection<T> docs = new ArrayList<>();
233235
final Collection<ErrorEntity> errors = new ArrayList<>();
234236
final Collection<Object> documentsAndErrors = new ArrayList<>();

src/main/java/com/arangodb/internal/InternalArangoDatabase.java

+7-2
Original file line numberDiff line numberDiff line change
@@ -361,8 +361,13 @@ protected <T> ResponseDeserializer<T> transactionResponseDeserializer(final Clas
361361
}
362362

363363
protected Request beginStreamTransactionRequest(final StreamTransactionOptions options) {
364-
return request(dbName, RequestType.POST, PATH_API_BEGIN_STREAM_TRANSACTION)
365-
.setBody(util().serialize(options != null ? options : new StreamTransactionOptions()));
364+
StreamTransactionOptions opts = options != null ? options : new StreamTransactionOptions();
365+
Request r = request(dbName, RequestType.POST, PATH_API_BEGIN_STREAM_TRANSACTION)
366+
.setBody(util().serialize(opts));
367+
if(Boolean.TRUE.equals(opts.getAllowDirtyRead())) {
368+
RequestUtils.allowDirtyRead(r);
369+
}
370+
return r;
366371
}
367372

368373
protected Request abortStreamTransactionRequest(String id) {

src/main/java/com/arangodb/internal/cursor/ArangoCursorImpl.java

+7
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ public class ArangoCursorImpl<T> extends AbstractArangoIterable<T> implements Ar
4343
protected final ArangoCursorIterator<T> iterator;
4444
private final String id;
4545
private final ArangoCursorExecute execute;
46+
private final boolean isPontentialDirtyRead;
4647

4748
public ArangoCursorImpl(final InternalArangoDatabase<?, ?> db, final ArangoCursorExecute execute,
4849
final Class<T> type, final CursorEntity result) {
@@ -51,6 +52,7 @@ public ArangoCursorImpl(final InternalArangoDatabase<?, ?> db, final ArangoCurso
5152
this.type = type;
5253
iterator = createIterator(this, db, execute, result);
5354
id = result.getId();
55+
this.isPontentialDirtyRead = Boolean.parseBoolean(result.getMeta().get("X-Arango-Potential-Dirty-Read"));
5456
}
5557

5658
protected ArangoCursorIterator<T> createIterator(
@@ -120,6 +122,11 @@ public List<T> asListRemaining() {
120122
return remaining;
121123
}
122124

125+
@Override
126+
public boolean isPotentialDirtyRead() {
127+
return isPontentialDirtyRead;
128+
}
129+
123130
@Override
124131
public void remove() {
125132
throw new UnsupportedOperationException();

src/main/java/com/arangodb/model/StreamTransactionOptions.java

+20
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020

2121
package com.arangodb.model;
2222

23+
import com.arangodb.velocypack.annotations.Expose;
24+
2325
/**
2426
* @author Mark Vollmary
2527
* @author Michele Rastelli
@@ -33,6 +35,8 @@ public class StreamTransactionOptions {
3335
private Boolean waitForSync;
3436
private Long maxTransactionSize;
3537
private Boolean allowImplicit;
38+
@Expose(serialize = false)
39+
private Boolean allowDirtyRead;
3640

3741
public StreamTransactionOptions() {
3842
super();
@@ -123,4 +127,20 @@ public StreamTransactionOptions maxTransactionSize(final Long maxTransactionSize
123127
return this;
124128
}
125129

130+
public Boolean getAllowDirtyRead() {
131+
return allowDirtyRead;
132+
}
133+
134+
/**
135+
* @param allowDirtyRead Set to {@code true} allows reading from followers in an active-failover setup.
136+
* @return options
137+
* @see <a href="https://www.arangodb.com/docs/stable/administration-active-failover.html#reading-from-follower">API
138+
* Documentation</a>
139+
* @since ArangoDB 3.4.0
140+
*/
141+
public StreamTransactionOptions allowDirtyRead(final Boolean allowDirtyRead) {
142+
this.allowDirtyRead = allowDirtyRead;
143+
return this;
144+
}
145+
126146
}

src/test/java/com/arangodb/ArangoCollectionTest.java

+3
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,9 @@ void getDocumentsDirtyRead(ArangoCollection collection) {
524524
.getDocuments(Arrays.asList("1", "2", "3"), BaseDocument.class,
525525
new DocumentReadOptions().allowDirtyRead(true));
526526
assertThat(documents).isNotNull();
527+
if (isAtLeastVersion(3, 10)) {
528+
assertThat(documents.isPotentialDirtyRead()).isTrue();
529+
}
527530
assertThat(documents.getDocuments()).hasSize(3);
528531
for (final BaseDocument document : documents.getDocuments()) {
529532
assertThat(document.getId()).isIn(COLLECTION_NAME + "/" + "1", COLLECTION_NAME + "/" + "2", COLLECTION_NAME + "/" + "3");

src/test/java/com/arangodb/ArangoDatabaseTest.java

+3
Original file line numberDiff line numberDiff line change
@@ -959,6 +959,9 @@ void queryAllowDirtyRead(ArangoDatabase db) throws IOException {
959959
final ArangoCursor<BaseDocument> cursor = db.query("FOR i IN @@col FILTER i.test == @test RETURN i",
960960
new MapBuilder().put("@col", CNAME1).put("test", null).get(),
961961
new AqlQueryOptions().allowDirtyRead(true), BaseDocument.class);
962+
if (isAtLeastVersion(3, 10)) {
963+
assertThat(cursor.isPotentialDirtyRead()).isTrue();
964+
}
962965
cursor.close();
963966
}
964967

src/test/java/com/arangodb/StreamTransactionTest.java

+32
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.junit.jupiter.params.ParameterizedTest;
2727
import org.junit.jupiter.params.provider.MethodSource;
2828

29+
import java.io.IOException;
2930
import java.util.*;
3031
import java.util.stream.Collectors;
3132
import java.util.stream.IntStream;
@@ -788,4 +789,35 @@ void transactionAllowImplicitFalse(ArangoDatabase db) {
788789

789790
db.abortStreamTransaction(tx.getId());
790791
}
792+
793+
@ParameterizedTest(name = "{index}")
794+
@MethodSource("dbs")
795+
void transactionDirtyRead(ArangoDatabase db) throws IOException {
796+
assumeTrue(isCluster());
797+
assumeTrue(isAtLeastVersion(3, 10));
798+
799+
ArangoCollection collection = db.collection(COLLECTION_NAME);
800+
DocumentCreateEntity<BaseDocument> doc = collection.insertDocument(new BaseDocument());
801+
802+
StreamTransactionEntity tx = db
803+
.beginStreamTransaction(new StreamTransactionOptions()
804+
.readCollections(COLLECTION_NAME)
805+
.allowDirtyRead(true));
806+
807+
MultiDocumentEntity<BaseDocument> readDocs = collection.getDocuments(Collections.singletonList(doc.getKey()),
808+
BaseDocument.class,
809+
new DocumentReadOptions().streamTransactionId(tx.getId()));
810+
811+
assertThat(readDocs.isPotentialDirtyRead()).isTrue();
812+
assertThat(readDocs.getDocuments()).hasSize(1);
813+
814+
final ArangoCursor<BaseDocument> cursor = db.query("FOR i IN @@col RETURN i",
815+
Collections.singletonMap("@col", COLLECTION_NAME),
816+
new AqlQueryOptions().streamTransactionId(tx.getId()), BaseDocument.class);
817+
assertThat(cursor.isPotentialDirtyRead()).isTrue();
818+
cursor.close();
819+
820+
db.abortStreamTransaction(tx.getId());
821+
}
822+
791823
}

0 commit comments

Comments
 (0)