From 362bb1f514722a952a3c9bdabe67e73b637a768c Mon Sep 17 00:00:00 2001 From: Michele Rastelli Date: Fri, 9 Sep 2022 11:50:50 +0200 Subject: [PATCH 1/4] getDocumentsDirtyRead --- .../java/com/arangodb/entity/MultiDocumentEntity.java | 8 ++++++++ .../com/arangodb/internal/InternalArangoCollection.java | 2 ++ src/test/java/com/arangodb/ArangoCollectionTest.java | 1 + 3 files changed, 11 insertions(+) diff --git a/src/main/java/com/arangodb/entity/MultiDocumentEntity.java b/src/main/java/com/arangodb/entity/MultiDocumentEntity.java index bd619c4f9..ca3d53bbd 100644 --- a/src/main/java/com/arangodb/entity/MultiDocumentEntity.java +++ b/src/main/java/com/arangodb/entity/MultiDocumentEntity.java @@ -30,6 +30,7 @@ public class MultiDocumentEntity implements Entity { private Collection documents; private Collection errors; private Collection documentsAndErrors; + private boolean isPotentialDirtyRead = false; public MultiDocumentEntity() { super(); @@ -68,4 +69,11 @@ public void setDocumentsAndErrors(final Collection documentsAndErrors) { this.documentsAndErrors = documentsAndErrors; } + public Boolean isPotentialDirtyRead() { + return isPotentialDirtyRead; + } + + public void setPotentialDirtyRead(final Boolean isPotentialDirtyRead) { + this.isPotentialDirtyRead = isPotentialDirtyRead; + } } diff --git a/src/main/java/com/arangodb/internal/InternalArangoCollection.java b/src/main/java/com/arangodb/internal/InternalArangoCollection.java index 9a03f510b..f7d87537e 100644 --- a/src/main/java/com/arangodb/internal/InternalArangoCollection.java +++ b/src/main/java/com/arangodb/internal/InternalArangoCollection.java @@ -229,6 +229,8 @@ protected ResponseDeserializer> getDocumentsResponseD final Class type, final DocumentReadOptions options) { return response -> { final MultiDocumentEntity multiDocument = new MultiDocumentEntity<>(); + boolean potentialDirtyRead = Boolean.parseBoolean(response.getMeta().get("X-Arango-Potential-Dirty-Read")); + multiDocument.setPotentialDirtyRead(potentialDirtyRead); final Collection docs = new ArrayList<>(); final Collection errors = new ArrayList<>(); final Collection documentsAndErrors = new ArrayList<>(); diff --git a/src/test/java/com/arangodb/ArangoCollectionTest.java b/src/test/java/com/arangodb/ArangoCollectionTest.java index 62df21b73..79c8b2fc8 100644 --- a/src/test/java/com/arangodb/ArangoCollectionTest.java +++ b/src/test/java/com/arangodb/ArangoCollectionTest.java @@ -524,6 +524,7 @@ void getDocumentsDirtyRead(ArangoCollection collection) { .getDocuments(Arrays.asList("1", "2", "3"), BaseDocument.class, new DocumentReadOptions().allowDirtyRead(true)); assertThat(documents).isNotNull(); + assertThat(documents.isPotentialDirtyRead()).isTrue(); assertThat(documents.getDocuments()).hasSize(3); for (final BaseDocument document : documents.getDocuments()) { assertThat(document.getId()).isIn(COLLECTION_NAME + "/" + "1", COLLECTION_NAME + "/" + "2", COLLECTION_NAME + "/" + "3"); From ccedac01ae37a9114fe31328b1549fff42c98bce Mon Sep 17 00:00:00 2001 From: Michele Rastelli Date: Fri, 9 Sep 2022 12:09:17 +0200 Subject: [PATCH 2/4] queryAllowDirtyRead --- src/main/java/com/arangodb/ArangoCursor.java | 2 ++ .../com/arangodb/internal/cursor/ArangoCursorImpl.java | 7 +++++++ src/test/java/com/arangodb/ArangoDatabaseTest.java | 1 + 3 files changed, 10 insertions(+) diff --git a/src/main/java/com/arangodb/ArangoCursor.java b/src/main/java/com/arangodb/ArangoCursor.java index eab1e2a88..f14ce6824 100644 --- a/src/main/java/com/arangodb/ArangoCursor.java +++ b/src/main/java/com/arangodb/ArangoCursor.java @@ -70,4 +70,6 @@ public interface ArangoCursor extends ArangoIterable, ArangoIterator, C */ List asListRemaining(); + boolean isPotentialDirtyRead(); + } diff --git a/src/main/java/com/arangodb/internal/cursor/ArangoCursorImpl.java b/src/main/java/com/arangodb/internal/cursor/ArangoCursorImpl.java index 228989657..1c86ceefe 100644 --- a/src/main/java/com/arangodb/internal/cursor/ArangoCursorImpl.java +++ b/src/main/java/com/arangodb/internal/cursor/ArangoCursorImpl.java @@ -43,6 +43,7 @@ public class ArangoCursorImpl extends AbstractArangoIterable implements Ar protected final ArangoCursorIterator iterator; private final String id; private final ArangoCursorExecute execute; + private final boolean isPontentialDirtyRead; public ArangoCursorImpl(final InternalArangoDatabase db, final ArangoCursorExecute execute, final Class type, final CursorEntity result) { @@ -51,6 +52,7 @@ public ArangoCursorImpl(final InternalArangoDatabase db, final ArangoCurso this.type = type; iterator = createIterator(this, db, execute, result); id = result.getId(); + this.isPontentialDirtyRead = Boolean.parseBoolean(result.getMeta().get("X-Arango-Potential-Dirty-Read")); } protected ArangoCursorIterator createIterator( @@ -120,6 +122,11 @@ public List asListRemaining() { return remaining; } + @Override + public boolean isPotentialDirtyRead() { + return isPontentialDirtyRead; + } + @Override public void remove() { throw new UnsupportedOperationException(); diff --git a/src/test/java/com/arangodb/ArangoDatabaseTest.java b/src/test/java/com/arangodb/ArangoDatabaseTest.java index f7738a89d..6625d82c2 100644 --- a/src/test/java/com/arangodb/ArangoDatabaseTest.java +++ b/src/test/java/com/arangodb/ArangoDatabaseTest.java @@ -959,6 +959,7 @@ void queryAllowDirtyRead(ArangoDatabase db) throws IOException { final ArangoCursor cursor = db.query("FOR i IN @@col FILTER i.test == @test RETURN i", new MapBuilder().put("@col", CNAME1).put("test", null).get(), new AqlQueryOptions().allowDirtyRead(true), BaseDocument.class); + assertThat(cursor.isPotentialDirtyRead()).isTrue(); cursor.close(); } From e305dda82a53c3a2a43ba5a7cfd86609593deef6 Mon Sep 17 00:00:00 2001 From: Michele Rastelli Date: Fri, 9 Sep 2022 14:42:05 +0200 Subject: [PATCH 3/4] tests fixes --- src/main/java/com/arangodb/ArangoCursor.java | 4 ++++ src/main/java/com/arangodb/entity/CursorEntity.java | 1 + src/main/java/com/arangodb/entity/MultiDocumentEntity.java | 4 ++++ src/test/java/com/arangodb/ArangoCollectionTest.java | 4 +++- src/test/java/com/arangodb/ArangoDatabaseTest.java | 4 +++- 5 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/arangodb/ArangoCursor.java b/src/main/java/com/arangodb/ArangoCursor.java index f14ce6824..734fc57b8 100644 --- a/src/main/java/com/arangodb/ArangoCursor.java +++ b/src/main/java/com/arangodb/ArangoCursor.java @@ -70,6 +70,10 @@ public interface ArangoCursor extends ArangoIterable, ArangoIterator, C */ List asListRemaining(); + /** + * @return true if the result is a potential dirty read + * @since ArangoDB 3.10 + */ boolean isPotentialDirtyRead(); } diff --git a/src/main/java/com/arangodb/entity/CursorEntity.java b/src/main/java/com/arangodb/entity/CursorEntity.java index ade2234b7..f00f20700 100644 --- a/src/main/java/com/arangodb/entity/CursorEntity.java +++ b/src/main/java/com/arangodb/entity/CursorEntity.java @@ -88,6 +88,7 @@ public VPackSlice getResult() { } public Map getMeta() { + if (meta == null) return Collections.emptyMap(); return meta; } diff --git a/src/main/java/com/arangodb/entity/MultiDocumentEntity.java b/src/main/java/com/arangodb/entity/MultiDocumentEntity.java index ca3d53bbd..714f2c129 100644 --- a/src/main/java/com/arangodb/entity/MultiDocumentEntity.java +++ b/src/main/java/com/arangodb/entity/MultiDocumentEntity.java @@ -69,6 +69,10 @@ public void setDocumentsAndErrors(final Collection documentsAndErrors) { this.documentsAndErrors = documentsAndErrors; } + /** + * @return true if the result is a potential dirty read + * @since ArangoDB 3.10 + */ public Boolean isPotentialDirtyRead() { return isPotentialDirtyRead; } diff --git a/src/test/java/com/arangodb/ArangoCollectionTest.java b/src/test/java/com/arangodb/ArangoCollectionTest.java index 79c8b2fc8..67437779d 100644 --- a/src/test/java/com/arangodb/ArangoCollectionTest.java +++ b/src/test/java/com/arangodb/ArangoCollectionTest.java @@ -524,7 +524,9 @@ void getDocumentsDirtyRead(ArangoCollection collection) { .getDocuments(Arrays.asList("1", "2", "3"), BaseDocument.class, new DocumentReadOptions().allowDirtyRead(true)); assertThat(documents).isNotNull(); - assertThat(documents.isPotentialDirtyRead()).isTrue(); + if (isAtLeastVersion(3, 10)) { + assertThat(documents.isPotentialDirtyRead()).isTrue(); + } assertThat(documents.getDocuments()).hasSize(3); for (final BaseDocument document : documents.getDocuments()) { assertThat(document.getId()).isIn(COLLECTION_NAME + "/" + "1", COLLECTION_NAME + "/" + "2", COLLECTION_NAME + "/" + "3"); diff --git a/src/test/java/com/arangodb/ArangoDatabaseTest.java b/src/test/java/com/arangodb/ArangoDatabaseTest.java index 6625d82c2..946a261d8 100644 --- a/src/test/java/com/arangodb/ArangoDatabaseTest.java +++ b/src/test/java/com/arangodb/ArangoDatabaseTest.java @@ -959,7 +959,9 @@ void queryAllowDirtyRead(ArangoDatabase db) throws IOException { final ArangoCursor cursor = db.query("FOR i IN @@col FILTER i.test == @test RETURN i", new MapBuilder().put("@col", CNAME1).put("test", null).get(), new AqlQueryOptions().allowDirtyRead(true), BaseDocument.class); - assertThat(cursor.isPotentialDirtyRead()).isTrue(); + if (isAtLeastVersion(3, 10)) { + assertThat(cursor.isPotentialDirtyRead()).isTrue(); + } cursor.close(); } From 6695ac836f454862199a447f8eec0638dc8c7788 Mon Sep 17 00:00:00 2001 From: Michele Rastelli Date: Fri, 9 Sep 2022 15:30:12 +0200 Subject: [PATCH 4/4] transactionDirtyRead --- .../internal/InternalArangoDatabase.java | 9 ++++-- .../model/StreamTransactionOptions.java | 20 ++++++++++++ .../com/arangodb/StreamTransactionTest.java | 32 +++++++++++++++++++ 3 files changed, 59 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/arangodb/internal/InternalArangoDatabase.java b/src/main/java/com/arangodb/internal/InternalArangoDatabase.java index 933b5c175..5549dbf74 100644 --- a/src/main/java/com/arangodb/internal/InternalArangoDatabase.java +++ b/src/main/java/com/arangodb/internal/InternalArangoDatabase.java @@ -361,8 +361,13 @@ protected ResponseDeserializer transactionResponseDeserializer(final Clas } protected Request beginStreamTransactionRequest(final StreamTransactionOptions options) { - return request(dbName, RequestType.POST, PATH_API_BEGIN_STREAM_TRANSACTION) - .setBody(util().serialize(options != null ? options : new StreamTransactionOptions())); + StreamTransactionOptions opts = options != null ? options : new StreamTransactionOptions(); + Request r = request(dbName, RequestType.POST, PATH_API_BEGIN_STREAM_TRANSACTION) + .setBody(util().serialize(opts)); + if(Boolean.TRUE.equals(opts.getAllowDirtyRead())) { + RequestUtils.allowDirtyRead(r); + } + return r; } protected Request abortStreamTransactionRequest(String id) { diff --git a/src/main/java/com/arangodb/model/StreamTransactionOptions.java b/src/main/java/com/arangodb/model/StreamTransactionOptions.java index 98f8b451d..b85b1cb99 100644 --- a/src/main/java/com/arangodb/model/StreamTransactionOptions.java +++ b/src/main/java/com/arangodb/model/StreamTransactionOptions.java @@ -20,6 +20,8 @@ package com.arangodb.model; +import com.arangodb.velocypack.annotations.Expose; + /** * @author Mark Vollmary * @author Michele Rastelli @@ -33,6 +35,8 @@ public class StreamTransactionOptions { private Boolean waitForSync; private Long maxTransactionSize; private Boolean allowImplicit; + @Expose(serialize = false) + private Boolean allowDirtyRead; public StreamTransactionOptions() { super(); @@ -123,4 +127,20 @@ public StreamTransactionOptions maxTransactionSize(final Long maxTransactionSize return this; } + public Boolean getAllowDirtyRead() { + return allowDirtyRead; + } + + /** + * @param allowDirtyRead Set to {@code true} allows reading from followers in an active-failover setup. + * @return options + * @see API + * Documentation + * @since ArangoDB 3.4.0 + */ + public StreamTransactionOptions allowDirtyRead(final Boolean allowDirtyRead) { + this.allowDirtyRead = allowDirtyRead; + return this; + } + } diff --git a/src/test/java/com/arangodb/StreamTransactionTest.java b/src/test/java/com/arangodb/StreamTransactionTest.java index 4330b13fd..11f5f462c 100644 --- a/src/test/java/com/arangodb/StreamTransactionTest.java +++ b/src/test/java/com/arangodb/StreamTransactionTest.java @@ -26,6 +26,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; +import java.io.IOException; import java.util.*; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -788,4 +789,35 @@ void transactionAllowImplicitFalse(ArangoDatabase db) { db.abortStreamTransaction(tx.getId()); } + + @ParameterizedTest(name = "{index}") + @MethodSource("dbs") + void transactionDirtyRead(ArangoDatabase db) throws IOException { + assumeTrue(isCluster()); + assumeTrue(isAtLeastVersion(3, 10)); + + ArangoCollection collection = db.collection(COLLECTION_NAME); + DocumentCreateEntity doc = collection.insertDocument(new BaseDocument()); + + StreamTransactionEntity tx = db + .beginStreamTransaction(new StreamTransactionOptions() + .readCollections(COLLECTION_NAME) + .allowDirtyRead(true)); + + MultiDocumentEntity readDocs = collection.getDocuments(Collections.singletonList(doc.getKey()), + BaseDocument.class, + new DocumentReadOptions().streamTransactionId(tx.getId())); + + assertThat(readDocs.isPotentialDirtyRead()).isTrue(); + assertThat(readDocs.getDocuments()).hasSize(1); + + final ArangoCursor cursor = db.query("FOR i IN @@col RETURN i", + Collections.singletonMap("@col", COLLECTION_NAME), + new AqlQueryOptions().streamTransactionId(tx.getId()), BaseDocument.class); + assertThat(cursor.isPotentialDirtyRead()).isTrue(); + cursor.close(); + + db.abortStreamTransaction(tx.getId()); + } + }