Skip to content

Commit bc1c431

Browse files
committed
[DE-371] cluster dirty reads (#455)
* getDocumentsDirtyRead * queryAllowDirtyRead * tests fixes * transactionDirtyRead (cherry picked from commit 62b2d60)
1 parent 9073b31 commit bc1c431

10 files changed

+92
-1
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/MultiDocumentEntity.java

+12
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public final class MultiDocumentEntity<E> {
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
@@ -195,6 +195,8 @@ protected <T> ResponseDeserializer<MultiDocumentEntity<T>> getDocumentsResponseD
195195
final Class<T> type, final DocumentReadOptions options) {
196196
return response -> {
197197
final MultiDocumentEntity<T> multiDocument = new MultiDocumentEntity<>();
198+
boolean potentialDirtyRead = Boolean.parseBoolean(response.getMeta().get("X-Arango-Potential-Dirty-Read"));
199+
multiDocument.setPotentialDirtyRead(potentialDirtyRead);
198200
final Collection<T> docs = new ArrayList<>();
199201
final Collection<ErrorEntity> errors = new ArrayList<>();
200202
final Collection<Object> documentsAndErrors = new ArrayList<>();

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

+6-1
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,12 @@ protected <T> ResponseDeserializer<T> transactionResponseDeserializer(final Clas
300300
}
301301

302302
protected Request beginStreamTransactionRequest(final StreamTransactionOptions options) {
303-
return request(dbName, RequestType.POST, PATH_API_BEGIN_STREAM_TRANSACTION).setBody(getSerde().serialize(options != null ? options : new StreamTransactionOptions()));
303+
StreamTransactionOptions opts = options != null ? options : new StreamTransactionOptions();
304+
Request r = request(dbName, RequestType.POST, PATH_API_BEGIN_STREAM_TRANSACTION).setBody(getSerde().serialize(opts));
305+
if(Boolean.TRUE.equals(opts.getAllowDirtyRead())) {
306+
RequestUtils.allowDirtyRead(r);
307+
}
308+
return r;
304309
}
305310

306311
protected Request abortStreamTransactionRequest(String id) {

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

+7
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public class ArangoCursorImpl<T> extends AbstractArangoIterable<T> implements Ar
4242
private final Class<T> type;
4343
private final String id;
4444
private final ArangoCursorExecute execute;
45+
private final boolean isPontentialDirtyRead;
4546

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

5557
protected ArangoCursorIterator<T> createIterator(
@@ -119,6 +121,11 @@ public List<T> asListRemaining() {
119121
return remaining;
120122
}
121123

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

src/main/java/com/arangodb/internal/cursor/entity/InternalCursorEntity.java

+1
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ public JsonNode getResult() {
9090
}
9191

9292
public Map<String, String> getMeta() {
93+
if (meta == null) return Collections.emptyMap();
9394
return meta;
9495
}
9596

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 final 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();
@@ -129,4 +133,20 @@ public StreamTransactionOptions maxTransactionSize(final Long maxTransactionSize
129133
return this;
130134
}
131135

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

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

+3
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,9 @@ void getDocumentsDirtyRead(ArangoCollection collection) {
545545
final MultiDocumentEntity<BaseDocument> documents = collection.getDocuments(Arrays.asList("1", "2", "3"),
546546
BaseDocument.class, new DocumentReadOptions().allowDirtyRead(true));
547547
assertThat(documents).isNotNull();
548+
if (isAtLeastVersion(3, 10)) {
549+
assertThat(documents.isPotentialDirtyRead()).isTrue();
550+
}
548551
assertThat(documents.getDocuments()).hasSize(3);
549552
for (final BaseDocument document : documents.getDocuments()) {
550553
assertThat(document.getId()).isIn(COLLECTION_NAME + "/" + "1", COLLECTION_NAME + "/" + "2",

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

+3
Original file line numberDiff line numberDiff line change
@@ -941,6 +941,9 @@ void queryAllowDirtyRead(ArangoDatabase db) throws IOException {
941941
final ArangoCursor<BaseDocument> cursor = db.query("FOR i IN @@col FILTER i.test == @test RETURN i",
942942
new MapBuilder().put("@col", CNAME1).put("test", null).get(),
943943
new AqlQueryOptions().allowDirtyRead(true), BaseDocument.class);
944+
if (isAtLeastVersion(3, 10)) {
945+
assertThat(cursor.isPotentialDirtyRead()).isTrue();
946+
}
944947
cursor.close();
945948
}
946949

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;
@@ -785,4 +786,35 @@ void transactionAllowImplicitFalse(ArangoDatabase db) {
785786

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

0 commit comments

Comments
 (0)