From 61674e80aa262cddd6ff1dd1a21959da70111097 Mon Sep 17 00:00:00 2001 From: Michele Rastelli Date: Tue, 9 May 2023 22:15:30 +0200 Subject: [PATCH 1/8] removed MetaAware --- .../java/com/arangodb/entity/MetaAware.java | 14 -------- .../internal/ArangoCursorExecute.java | 5 ++- .../arangodb/internal/ArangoDatabaseImpl.java | 12 +++---- .../arangodb/internal/ArangoExecutorSync.java | 14 +------- .../internal/InternalArangoDatabase.java | 22 +++++++------ .../internal/cursor/ArangoCursorImpl.java | 4 +-- .../internal/cursor/ArangoCursorIterator.java | 2 +- .../cursor/entity/InternalCursorEntity.java | 33 ++++++------------- .../arangodb-java-driver/reflect-config.json | 12 ------- .../reflect-config.json | 12 ------- 10 files changed, 34 insertions(+), 96 deletions(-) delete mode 100644 core/src/main/java/com/arangodb/entity/MetaAware.java diff --git a/core/src/main/java/com/arangodb/entity/MetaAware.java b/core/src/main/java/com/arangodb/entity/MetaAware.java deleted file mode 100644 index a6ddc9e14..000000000 --- a/core/src/main/java/com/arangodb/entity/MetaAware.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.arangodb.entity; - -import java.util.Map; - -/** - * @author Mark Vollmary - */ -public interface MetaAware { - - Map getMeta(); - - void setMeta(final Map meta); - -} diff --git a/core/src/main/java/com/arangodb/internal/ArangoCursorExecute.java b/core/src/main/java/com/arangodb/internal/ArangoCursorExecute.java index 7efc5eef9..b4214f25b 100644 --- a/core/src/main/java/com/arangodb/internal/ArangoCursorExecute.java +++ b/core/src/main/java/com/arangodb/internal/ArangoCursorExecute.java @@ -22,15 +22,14 @@ import com.arangodb.internal.cursor.entity.InternalCursorEntity; -import java.util.Map; /** * @author Mark Vollmary */ public interface ArangoCursorExecute { - InternalCursorEntity next(String id, Map meta); + InternalCursorEntity next(String id); - void close(String id, Map meta); + void close(String id); } diff --git a/core/src/main/java/com/arangodb/internal/ArangoDatabaseImpl.java b/core/src/main/java/com/arangodb/internal/ArangoDatabaseImpl.java index b17048afa..3379798ad 100644 --- a/core/src/main/java/com/arangodb/internal/ArangoDatabaseImpl.java +++ b/core/src/main/java/com/arangodb/internal/ArangoDatabaseImpl.java @@ -161,7 +161,7 @@ public ArangoCursor query( final String query, final Class type, final Map bindVars, final AqlQueryOptions options) { final InternalRequest request = queryRequest(query, bindVars, options); final HostHandle hostHandle = new HostHandle(); - final InternalCursorEntity result = executor.execute(request, InternalCursorEntity.class, hostHandle); + final InternalCursorEntity result = executor.execute(request, internalCursorEntityDeserializer(), hostHandle); return createCursor(result, type, options, hostHandle); } @@ -184,7 +184,7 @@ public ArangoCursor query(final String query, final Class type) { public ArangoCursor cursor(final String cursorId, final Class type) { final HostHandle hostHandle = new HostHandle(); final InternalCursorEntity result = executor - .execute(queryNextRequest(cursorId, null, null), InternalCursorEntity.class, hostHandle); + .execute(queryNextRequest(cursorId, null), internalCursorEntityDeserializer(), hostHandle); return createCursor(result, type, null, hostHandle); } @@ -196,13 +196,13 @@ private ArangoCursor createCursor( final ArangoCursorExecute execute = new ArangoCursorExecute() { @Override - public InternalCursorEntity next(final String id, Map meta) { - return executor.execute(queryNextRequest(id, options, meta), InternalCursorEntity.class, hostHandle); + public InternalCursorEntity next(final String id) { + return executor.execute(queryNextRequest(id, options), internalCursorEntityDeserializer(), hostHandle); } @Override - public void close(final String id, Map meta) { - executor.execute(queryCloseRequest(id, options, meta), Void.class, hostHandle); + public void close(final String id) { + executor.execute(queryCloseRequest(id, options), Void.class, hostHandle); } }; diff --git a/core/src/main/java/com/arangodb/internal/ArangoExecutorSync.java b/core/src/main/java/com/arangodb/internal/ArangoExecutorSync.java index 00a6ae6f7..c0f10815f 100644 --- a/core/src/main/java/com/arangodb/internal/ArangoExecutorSync.java +++ b/core/src/main/java/com/arangodb/internal/ArangoExecutorSync.java @@ -21,12 +21,9 @@ package com.arangodb.internal; import com.arangodb.ArangoDBException; -import com.arangodb.entity.MetaAware; import com.arangodb.internal.config.ArangoConfig; import com.arangodb.internal.net.CommunicationProtocol; import com.arangodb.internal.net.HostHandle; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.IOException; import java.lang.reflect.Type; @@ -36,8 +33,6 @@ */ public class ArangoExecutorSync extends ArangoExecutor { - private static final Logger LOG = LoggerFactory.getLogger(ArangoExecutorSync.class); - private final CommunicationProtocol protocol; public ArangoExecutorSync(final CommunicationProtocol protocol, final ArangoConfig config) { @@ -64,14 +59,7 @@ public T execute( final InternalResponse response = protocol.execute(interceptRequest(request), hostHandle); interceptResponse(response); - T deserialize = responseDeserializer.deserialize(response); - - if (deserialize instanceof MetaAware) { - LOG.debug("Response is MetaAware {}", deserialize.getClass().getName()); - ((MetaAware) deserialize).setMeta(response.getMeta()); - } - - return deserialize; + return responseDeserializer.deserialize(response); } public void disconnect() { diff --git a/core/src/main/java/com/arangodb/internal/InternalArangoDatabase.java b/core/src/main/java/com/arangodb/internal/InternalArangoDatabase.java index 9fdb8c705..454147c11 100644 --- a/core/src/main/java/com/arangodb/internal/InternalArangoDatabase.java +++ b/core/src/main/java/com/arangodb/internal/InternalArangoDatabase.java @@ -23,6 +23,7 @@ import com.arangodb.entity.*; import com.arangodb.entity.arangosearch.analyzer.SearchAnalyzer; import com.arangodb.internal.ArangoExecutor.ResponseDeserializer; +import com.arangodb.internal.cursor.entity.InternalCursorEntity; import com.arangodb.internal.util.RequestUtils; import com.arangodb.model.*; import com.arangodb.model.arangosearch.*; @@ -156,13 +157,9 @@ protected InternalRequest queryRequest(final String query, final Map meta) { - + protected InternalRequest queryNextRequest(final String id, final AqlQueryOptions options) { final InternalRequest request = request(name, RequestType.POST, PATH_API_CURSOR, id); - request.putHeaderParams(meta); - final AqlQueryOptions opt = options != null ? options : new AqlQueryOptions(); - if (Boolean.TRUE.equals(opt.getAllowDirtyRead())) { RequestUtils.allowDirtyRead(request); } @@ -170,13 +167,9 @@ protected InternalRequest queryNextRequest(final String id, final AqlQueryOption return request; } - protected InternalRequest queryCloseRequest(final String id, final AqlQueryOptions options, Map meta) { - + protected InternalRequest queryCloseRequest(final String id, final AqlQueryOptions options) { final InternalRequest request = request(name, RequestType.DELETE, PATH_API_CURSOR, id); - request.putHeaderParams(meta); - final AqlQueryOptions opt = options != null ? options : new AqlQueryOptions(); - if (Boolean.TRUE.equals(opt.getAllowDirtyRead())) { RequestUtils.allowDirtyRead(request); } @@ -243,6 +236,15 @@ protected InternalRequest deleteAqlFunctionRequest(final String name, final AqlF return request; } + protected ResponseDeserializer internalCursorEntityDeserializer() { + return response -> { + InternalCursorEntity e = getSerde().deserialize(response.getBody(), InternalCursorEntity.class); + boolean potentialDirtyRead = Boolean.parseBoolean(response.getMeta("X-Arango-Potential-Dirty-Read")); + e.setPontentialDirtyRead(potentialDirtyRead); + return e; + }; + } + protected ResponseDeserializer deleteAqlFunctionResponseDeserializer() { return response -> getSerde().deserialize(response.getBody(), "/deletedCount", Integer.class); } diff --git a/core/src/main/java/com/arangodb/internal/cursor/ArangoCursorImpl.java b/core/src/main/java/com/arangodb/internal/cursor/ArangoCursorImpl.java index 2dfdbd194..4424221a1 100644 --- a/core/src/main/java/com/arangodb/internal/cursor/ArangoCursorImpl.java +++ b/core/src/main/java/com/arangodb/internal/cursor/ArangoCursorImpl.java @@ -51,7 +51,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")); + isPontentialDirtyRead = result.isPontentialDirtyRead(); } protected ArangoCursorIterator createIterator( @@ -98,7 +98,7 @@ public boolean isCached() { @Override public void close() { if (id != null && hasNext()) { - execute.close(id, iterator.getResult().getMeta()); + execute.close(id); } } diff --git a/core/src/main/java/com/arangodb/internal/cursor/ArangoCursorIterator.java b/core/src/main/java/com/arangodb/internal/cursor/ArangoCursorIterator.java index e6d681045..a2272d5dc 100644 --- a/core/src/main/java/com/arangodb/internal/cursor/ArangoCursorIterator.java +++ b/core/src/main/java/com/arangodb/internal/cursor/ArangoCursorIterator.java @@ -64,7 +64,7 @@ public boolean hasNext() { @Override public T next() { if (!arrayIterator.hasNext() && Boolean.TRUE.equals(result.getHasMore())) { - result = execute.next(cursor.getId(), result.getMeta()); + result = execute.next(cursor.getId()); arrayIterator = result.getResult().iterator(); } if (!hasNext()) { diff --git a/core/src/main/java/com/arangodb/internal/cursor/entity/InternalCursorEntity.java b/core/src/main/java/com/arangodb/internal/cursor/entity/InternalCursorEntity.java index 535715e3b..2a5421eb5 100644 --- a/core/src/main/java/com/arangodb/internal/cursor/entity/InternalCursorEntity.java +++ b/core/src/main/java/com/arangodb/internal/cursor/entity/InternalCursorEntity.java @@ -22,20 +22,17 @@ import com.arangodb.entity.CursorStats; import com.arangodb.entity.CursorWarning; -import com.arangodb.entity.MetaAware; import com.fasterxml.jackson.databind.JsonNode; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; -import java.util.Map; /** * @author Mark Vollmary * @see API * Documentation */ -public final class InternalCursorEntity implements MetaAware { +public final class InternalCursorEntity { private final Extras extra = new Extras(); private String id; @@ -43,8 +40,7 @@ public final class InternalCursorEntity implements MetaAware { private Boolean cached; private Boolean hasMore; private JsonNode result; - - private Map meta; + private Boolean pontentialDirtyRead; public String getId() { return id; @@ -91,25 +87,16 @@ public JsonNode getResult() { return result; } - @Override - public Map getMeta() { - if (meta == null) return Collections.emptyMap(); - return Collections.unmodifiableMap(meta); - } - - @Override - public void setMeta(Map meta) { - this.meta = cleanupMeta(new HashMap<>(meta)); - } - /** - * @return remove not allowed (valid storable) meta information + * @return true if the result is a potential dirty read + * @since ArangoDB 3.10 */ - public Map cleanupMeta(Map meta) { - meta.remove("content-length"); - meta.remove("transfer-encoding"); - meta.remove("x-arango-queue-time-seconds"); - return meta; + public Boolean isPontentialDirtyRead() { + return pontentialDirtyRead; + } + + public void setPontentialDirtyRead(final Boolean pontentialDirtyRead) { + this.pontentialDirtyRead = pontentialDirtyRead; } public static final class Extras { diff --git a/driver/src/main/resources/META-INF/native-image/com.arangodb/arangodb-java-driver/reflect-config.json b/driver/src/main/resources/META-INF/native-image/com.arangodb/arangodb-java-driver/reflect-config.json index 4f9cd16ca..3325fca05 100644 --- a/driver/src/main/resources/META-INF/native-image/com.arangodb/arangodb-java-driver/reflect-config.json +++ b/driver/src/main/resources/META-INF/native-image/com.arangodb/arangodb-java-driver/reflect-config.json @@ -335,12 +335,6 @@ "allDeclaredMethods": true, "allDeclaredConstructors": true }, - { - "name": "com.arangodb.entity.MetaAware", - "allDeclaredFields": true, - "allDeclaredMethods": true, - "allDeclaredConstructors": true - }, { "name": "com.arangodb.entity.arangosearch.analyzer.GeoS2AnalyzerProperties", "allDeclaredFields": true, @@ -1529,12 +1523,6 @@ "allDeclaredMethods": true, "allDeclaredConstructors": true }, - { - "name": "com.arangodb.entity.MetaAware", - "allDeclaredFields": true, - "allDeclaredMethods": true, - "allDeclaredConstructors": true - }, { "name": "com.arangodb.internal.cursor.entity.InternalCursorEntity", "allDeclaredFields": true, diff --git a/shaded/src/main/resources/META-INF/native-image/com.arangodb/arangodb-java-driver-shaded/reflect-config.json b/shaded/src/main/resources/META-INF/native-image/com.arangodb/arangodb-java-driver-shaded/reflect-config.json index 4f9cd16ca..3325fca05 100644 --- a/shaded/src/main/resources/META-INF/native-image/com.arangodb/arangodb-java-driver-shaded/reflect-config.json +++ b/shaded/src/main/resources/META-INF/native-image/com.arangodb/arangodb-java-driver-shaded/reflect-config.json @@ -335,12 +335,6 @@ "allDeclaredMethods": true, "allDeclaredConstructors": true }, - { - "name": "com.arangodb.entity.MetaAware", - "allDeclaredFields": true, - "allDeclaredMethods": true, - "allDeclaredConstructors": true - }, { "name": "com.arangodb.entity.arangosearch.analyzer.GeoS2AnalyzerProperties", "allDeclaredFields": true, @@ -1529,12 +1523,6 @@ "allDeclaredMethods": true, "allDeclaredConstructors": true }, - { - "name": "com.arangodb.entity.MetaAware", - "allDeclaredFields": true, - "allDeclaredMethods": true, - "allDeclaredConstructors": true - }, { "name": "com.arangodb.internal.cursor.entity.InternalCursorEntity", "allDeclaredFields": true, From 3627461fbd0e086a8274dbb4c6ef75b81103021d Mon Sep 17 00:00:00 2001 From: Michele Rastelli Date: Wed, 10 May 2023 13:42:11 +0200 Subject: [PATCH 2/8] retriable cursor --- .../main/java/com/arangodb/ArangoCursor.java | 7 ++ .../internal/ArangoCursorExecute.java | 2 +- .../arangodb/internal/ArangoDatabaseImpl.java | 6 +- .../internal/InternalArangoDatabase.java | 11 ++ .../cursor/AbstractArangoIterable.java | 38 ------ .../internal/cursor/ArangoCursorImpl.java | 112 +++++++++++++----- .../internal/cursor/ArangoCursorIterator.java | 85 ------------- .../cursor/entity/InternalCursorEntity.java | 10 ++ .../com/arangodb/model/AqlQueryOptions.java | 26 +++- 9 files changed, 135 insertions(+), 162 deletions(-) delete mode 100644 core/src/main/java/com/arangodb/internal/cursor/AbstractArangoIterable.java delete mode 100644 core/src/main/java/com/arangodb/internal/cursor/ArangoCursorIterator.java diff --git a/core/src/main/java/com/arangodb/ArangoCursor.java b/core/src/main/java/com/arangodb/ArangoCursor.java index 259b81d73..049d2be0b 100644 --- a/core/src/main/java/com/arangodb/ArangoCursor.java +++ b/core/src/main/java/com/arangodb/ArangoCursor.java @@ -76,4 +76,11 @@ public interface ArangoCursor extends ArangoIterable, ArangoIterator, C */ boolean isPotentialDirtyRead(); + /** + * @return The ID of the batch after the current one. The first batch has an ID of 1 and the value is incremented by + * 1 with every batch. Only set if the allowRetry query option is enabled. + * @since ArangoDB 3.11 + */ + String getNextBatchId(); + } diff --git a/core/src/main/java/com/arangodb/internal/ArangoCursorExecute.java b/core/src/main/java/com/arangodb/internal/ArangoCursorExecute.java index b4214f25b..209f11cff 100644 --- a/core/src/main/java/com/arangodb/internal/ArangoCursorExecute.java +++ b/core/src/main/java/com/arangodb/internal/ArangoCursorExecute.java @@ -28,7 +28,7 @@ */ public interface ArangoCursorExecute { - InternalCursorEntity next(String id); + InternalCursorEntity next(String id, String nextBatchId); void close(String id); diff --git a/core/src/main/java/com/arangodb/internal/ArangoDatabaseImpl.java b/core/src/main/java/com/arangodb/internal/ArangoDatabaseImpl.java index 3379798ad..55bc886ed 100644 --- a/core/src/main/java/com/arangodb/internal/ArangoDatabaseImpl.java +++ b/core/src/main/java/com/arangodb/internal/ArangoDatabaseImpl.java @@ -196,8 +196,10 @@ private ArangoCursor createCursor( final ArangoCursorExecute execute = new ArangoCursorExecute() { @Override - public InternalCursorEntity next(final String id) { - return executor.execute(queryNextRequest(id, options), internalCursorEntityDeserializer(), hostHandle); + public InternalCursorEntity next(final String id, final String nextBatchId) { + InternalRequest request = nextBatchId == null ? + queryNextRequest(id, options) : queryNextByBatchIdRequest(id, nextBatchId, options); + return executor.execute(request, internalCursorEntityDeserializer(), hostHandle); } @Override diff --git a/core/src/main/java/com/arangodb/internal/InternalArangoDatabase.java b/core/src/main/java/com/arangodb/internal/InternalArangoDatabase.java index 454147c11..3db44c1f7 100644 --- a/core/src/main/java/com/arangodb/internal/InternalArangoDatabase.java +++ b/core/src/main/java/com/arangodb/internal/InternalArangoDatabase.java @@ -159,6 +159,17 @@ protected InternalRequest queryRequest(final String query, final Map implements ArangoIterable { - - @Override - public Stream stream() { - return StreamSupport.stream(spliterator(), false); - } - -} diff --git a/core/src/main/java/com/arangodb/internal/cursor/ArangoCursorImpl.java b/core/src/main/java/com/arangodb/internal/cursor/ArangoCursorImpl.java index 4424221a1..06a506f0d 100644 --- a/core/src/main/java/com/arangodb/internal/cursor/ArangoCursorImpl.java +++ b/core/src/main/java/com/arangodb/internal/cursor/ArangoCursorImpl.java @@ -27,39 +27,49 @@ import com.arangodb.internal.ArangoCursorExecute; import com.arangodb.internal.InternalArangoDatabase; import com.arangodb.internal.cursor.entity.InternalCursorEntity; -import com.arangodb.internal.cursor.entity.InternalCursorEntity.Extras; +import com.fasterxml.jackson.databind.JsonNode; import java.util.ArrayList; import java.util.Collection; +import java.util.Iterator; +import java.util.NoSuchElementException; import java.util.List; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; /** * @author Mark Vollmary */ -public class ArangoCursorImpl extends AbstractArangoIterable implements ArangoCursor { +public class ArangoCursorImpl implements ArangoCursor { protected final ArangoCursorIterator iterator; private final Class type; private final String id; private final ArangoCursorExecute execute; - private final boolean isPontentialDirtyRead; + private final boolean pontentialDirtyRead; + private final boolean allowRetry; public ArangoCursorImpl(final InternalArangoDatabase db, final ArangoCursorExecute execute, final Class type, final InternalCursorEntity result) { super(); this.execute = execute; this.type = type; - iterator = createIterator(this, db, execute, result); id = result.getId(); - isPontentialDirtyRead = result.isPontentialDirtyRead(); + pontentialDirtyRead = result.isPontentialDirtyRead(); + iterator = new ArangoCursorIterator<>(id, type, execute, db, result); + this.allowRetry = result.getNextBatchId() != null; } - protected ArangoCursorIterator createIterator( - final ArangoCursor cursor, - final InternalArangoDatabase db, - final ArangoCursorExecute execute, - final InternalCursorEntity result) { - return new ArangoCursorIterator<>(cursor, execute, db, result); + @Override + public void close() { + if (getId() != null && (allowRetry || iterator.result.getHasMore())) { + getExecute().close(getId()); + } + } + + @Override + public T next() { + return iterator.next(); } @Override @@ -74,44 +84,32 @@ public Class getType() { @Override public Integer getCount() { - return iterator.getResult().getCount(); + return iterator.result.getCount(); } @Override public CursorStats getStats() { - final Extras extra = iterator.getResult().getExtra(); + final InternalCursorEntity.Extras extra = iterator.result.getExtra(); return extra != null ? extra.getStats() : null; } @Override public Collection getWarnings() { - final Extras extra = iterator.getResult().getExtra(); + final InternalCursorEntity.Extras extra = iterator.result.getExtra(); return extra != null ? extra.getWarnings() : null; } @Override public boolean isCached() { - final Boolean cached = iterator.getResult().getCached(); + final Boolean cached = iterator.result.getCached(); return Boolean.TRUE.equals(cached); } - @Override - public void close() { - if (id != null && hasNext()) { - execute.close(id); - } - } - @Override public boolean hasNext() { return iterator.hasNext(); } - @Override - public T next() { - return iterator.next(); - } - @Override public List asListRemaining() { final List remaining = new ArrayList<>(); @@ -123,17 +121,67 @@ public List asListRemaining() { @Override public boolean isPotentialDirtyRead() { - return isPontentialDirtyRead; + return pontentialDirtyRead; } @Override - public void remove() { - throw new UnsupportedOperationException(); + public ArangoIterator iterator() { + return iterator; } @Override - public ArangoIterator iterator() { - return iterator; + public String getNextBatchId() { + return iterator.result.getNextBatchId(); + } + + protected ArangoCursorExecute getExecute() { + return execute; + } + + public Stream stream() { + return StreamSupport.stream(spliterator(), false); + } + + protected static class ArangoCursorIterator implements ArangoIterator { + private final String cursorId; + private final Class type; + private final InternalArangoDatabase db; + private final ArangoCursorExecute execute; + private InternalCursorEntity result; + private Iterator arrayIterator; + + protected ArangoCursorIterator(final String cursorId, final Class type, final ArangoCursorExecute execute, + final InternalArangoDatabase db, final InternalCursorEntity result) { + this.cursorId = cursorId; + this.type = type; + this.execute = execute; + this.db = db; + this.result = result; + arrayIterator = result.getResult().iterator(); + } + + @Override + public boolean hasNext() { + return arrayIterator.hasNext() || result.getHasMore(); + } + + @Override + public T next() { + if (!arrayIterator.hasNext() && Boolean.TRUE.equals(result.getHasMore())) { + result = execute.next(cursorId, result.getNextBatchId()); + arrayIterator = result.getResult().iterator(); + } + if (!hasNext()) { + throw new NoSuchElementException(); + } + return deserialize(db.getSerde().serialize(arrayIterator.next()), type); + } + + private R deserialize(final byte[] result, final Class type) { + return db.getSerde().deserializeUserData(result, type); + } + } } + diff --git a/core/src/main/java/com/arangodb/internal/cursor/ArangoCursorIterator.java b/core/src/main/java/com/arangodb/internal/cursor/ArangoCursorIterator.java deleted file mode 100644 index a2272d5dc..000000000 --- a/core/src/main/java/com/arangodb/internal/cursor/ArangoCursorIterator.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * DISCLAIMER - * - * Copyright 2016 ArangoDB GmbH, Cologne, Germany - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Copyright holder is ArangoDB GmbH, Cologne, Germany - */ - -package com.arangodb.internal.cursor; - -import com.arangodb.ArangoCursor; -import com.arangodb.ArangoIterator; -import com.arangodb.internal.ArangoCursorExecute; -import com.arangodb.internal.InternalArangoDatabase; -import com.arangodb.internal.cursor.entity.InternalCursorEntity; -import com.fasterxml.jackson.databind.JsonNode; - -import java.util.Iterator; -import java.util.NoSuchElementException; - -/** - * @param - * @author Mark Vollmary - */ -public class ArangoCursorIterator implements ArangoIterator { - - private final ArangoCursor cursor; - private final InternalArangoDatabase db; - private final ArangoCursorExecute execute; - private InternalCursorEntity result; - private Iterator arrayIterator; - - protected ArangoCursorIterator(final ArangoCursor cursor, final ArangoCursorExecute execute, - final InternalArangoDatabase db, final InternalCursorEntity result) { - super(); - this.cursor = cursor; - this.execute = execute; - this.db = db; - this.result = result; - arrayIterator = result.getResult().iterator(); - } - - public InternalCursorEntity getResult() { - return result; - } - - @Override - public boolean hasNext() { - return arrayIterator.hasNext() || result.getHasMore(); - } - - @Override - public T next() { - if (!arrayIterator.hasNext() && Boolean.TRUE.equals(result.getHasMore())) { - result = execute.next(cursor.getId()); - arrayIterator = result.getResult().iterator(); - } - if (!hasNext()) { - throw new NoSuchElementException(); - } - return deserialize(db.getSerde().serialize(arrayIterator.next()), cursor.getType()); - } - - protected R deserialize(final byte[] result, final Class type) { - return db.getSerde().deserializeUserData(result, type); - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - -} diff --git a/core/src/main/java/com/arangodb/internal/cursor/entity/InternalCursorEntity.java b/core/src/main/java/com/arangodb/internal/cursor/entity/InternalCursorEntity.java index 2a5421eb5..6d479d0fe 100644 --- a/core/src/main/java/com/arangodb/internal/cursor/entity/InternalCursorEntity.java +++ b/core/src/main/java/com/arangodb/internal/cursor/entity/InternalCursorEntity.java @@ -41,6 +41,7 @@ public final class InternalCursorEntity { private Boolean hasMore; private JsonNode result; private Boolean pontentialDirtyRead; + private String nextBatchId; public String getId() { return id; @@ -99,6 +100,15 @@ public void setPontentialDirtyRead(final Boolean pontentialDirtyRead) { this.pontentialDirtyRead = pontentialDirtyRead; } + /** + * @return The ID of the batch after the current one. The first batch has an ID of 1 and the value is incremented by + * 1 with every batch. Only set if the allowRetry query option is enabled. + * @since ArangoDB 3.11 + */ + public String getNextBatchId() { + return nextBatchId; + } + public static final class Extras { private final Collection warnings = Collections.emptyList(); private CursorStats stats; diff --git a/core/src/main/java/com/arangodb/model/AqlQueryOptions.java b/core/src/main/java/com/arangodb/model/AqlQueryOptions.java index 3aba96dd4..1c898486f 100644 --- a/core/src/main/java/com/arangodb/model/AqlQueryOptions.java +++ b/core/src/main/java/com/arangodb/model/AqlQueryOptions.java @@ -42,10 +42,7 @@ public final class AqlQueryOptions implements Cloneable { private Options options; private Boolean allowDirtyRead; private String streamTransactionId; - - public AqlQueryOptions() { - super(); - } + private Boolean allowRetry; public Boolean getCount() { return count; @@ -489,6 +486,27 @@ public AqlQueryOptions streamTransactionId(final String streamTransactionId) { return this; } + public Boolean getAllowRetry() { + return allowRetry; + } + + /** + * @param allowRetry Set this option to true to make it possible to retry fetching the latest batch from a cursor. + * This makes possible to safely retry invoking {@link com.arangodb.ArangoCursor#next()} in + * case of {@link java.io.IOException}. + *

+ * If set to false (default), retry invoking {@link com.arangodb.ArangoCursor#next()} in case of + * {@link java.io.IOException} is not safe, since the request to fetch the next batch is not + * idempotent (i.e. the cursor may be advanced multiple times on the server). + * + * @return options + * @since ArangoDB 3.11 + */ + public AqlQueryOptions allowRetry(final Boolean allowRetry) { + this.allowRetry = allowRetry; + return this; + } + @Override public AqlQueryOptions clone() { try { From 9c79ae87a5c7cec455dee26455d3dfe0ff123566 Mon Sep 17 00:00:00 2001 From: Michele Rastelli Date: Wed, 10 May 2023 14:28:41 +0200 Subject: [PATCH 3/8] tests --- .../internal/cursor/ArangoCursorImpl.java | 8 +++ .../com/arangodb/model/AqlQueryOptions.java | 10 ++-- .../java/com/arangodb/ArangoDatabaseTest.java | 50 +++++++++++++++++++ 3 files changed, 65 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/com/arangodb/internal/cursor/ArangoCursorImpl.java b/core/src/main/java/com/arangodb/internal/cursor/ArangoCursorImpl.java index 06a506f0d..8112e8ce4 100644 --- a/core/src/main/java/com/arangodb/internal/cursor/ArangoCursorImpl.java +++ b/core/src/main/java/com/arangodb/internal/cursor/ArangoCursorImpl.java @@ -28,6 +28,8 @@ import com.arangodb.internal.InternalArangoDatabase; import com.arangodb.internal.cursor.entity.InternalCursorEntity; import com.fasterxml.jackson.databind.JsonNode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.Collection; @@ -41,6 +43,7 @@ * @author Mark Vollmary */ public class ArangoCursorImpl implements ArangoCursor { + private final static Logger LOG = LoggerFactory.getLogger(ArangoCursorImpl.class); protected final ArangoCursorIterator iterator; private final Class type; @@ -116,6 +119,11 @@ public List asListRemaining() { while (hasNext()) { remaining.add(next()); } + try { + close(); + } catch (final Exception e) { + LOG.warn("Could not close cursor: ", e); + } return remaining; } diff --git a/core/src/main/java/com/arangodb/model/AqlQueryOptions.java b/core/src/main/java/com/arangodb/model/AqlQueryOptions.java index 1c898486f..fb0ead110 100644 --- a/core/src/main/java/com/arangodb/model/AqlQueryOptions.java +++ b/core/src/main/java/com/arangodb/model/AqlQueryOptions.java @@ -42,7 +42,6 @@ public final class AqlQueryOptions implements Cloneable { private Options options; private Boolean allowDirtyRead; private String streamTransactionId; - private Boolean allowRetry; public Boolean getCount() { return count; @@ -487,7 +486,7 @@ public AqlQueryOptions streamTransactionId(final String streamTransactionId) { } public Boolean getAllowRetry() { - return allowRetry; + return getOptions().allowRetry; } /** @@ -503,7 +502,7 @@ public Boolean getAllowRetry() { * @since ArangoDB 3.11 */ public AqlQueryOptions allowRetry(final Boolean allowRetry) { - this.allowRetry = allowRetry; + getOptions().allowRetry = allowRetry; return this; } @@ -537,6 +536,7 @@ public static final class Options implements Cloneable { private Double maxRuntime; private Boolean fillBlockCache; private String forceOneShardAttributeValue; + private Boolean allowRetry; public Boolean getFailOnWarning() { return failOnWarning; @@ -605,6 +605,10 @@ public Collection getShardIds() { return shardIds; } + public Boolean getAllowRetry() { + return allowRetry; + } + @Override public Options clone() { try { diff --git a/driver/src/test/java/com/arangodb/ArangoDatabaseTest.java b/driver/src/test/java/com/arangodb/ArangoDatabaseTest.java index b8b249b00..c2392f9cf 100644 --- a/driver/src/test/java/com/arangodb/ArangoDatabaseTest.java +++ b/driver/src/test/java/com/arangodb/ArangoDatabaseTest.java @@ -1006,6 +1006,56 @@ BaseDocument.class, new MapBuilder().put("@col", CNAME1).put("test", null).get() cursor.close(); } + @ParameterizedTest(name = "{index}") + @MethodSource("arangos") + void queryAllowRetry(ArangoDB arangoDB) throws IOException { + assumeTrue(isAtLeastVersion(3, 11)); + final ArangoCursor cursor = arangoDB.db() + .query("for i in 1..2 return i", String.class, new AqlQueryOptions().allowRetry(true).batchSize(1)); + assertThat(cursor.asListRemaining()).containsExactly("1", "2"); + cursor.close(); + } + + @ParameterizedTest(name = "{index}") + @MethodSource("arangos") + void queryAllowRetryClose(ArangoDB arangoDB) throws IOException { + assumeTrue(isAtLeastVersion(3, 11)); + final ArangoCursor cursor = arangoDB.db() + .query("for i in 1..2 return i", String.class, new AqlQueryOptions().allowRetry(true).batchSize(1)); + assertThat(cursor.hasNext()).isTrue(); + assertThat(cursor.next()).isEqualTo("1"); + assertThat(cursor.hasNext()).isTrue(); + assertThat(cursor.next()).isEqualTo("2"); + assertThat(cursor.hasNext()).isFalse(); + cursor.close(); + } + + @ParameterizedTest(name = "{index}") + @MethodSource("arangos") + void queryAllowRetryCloseBeforeLatestBatch(ArangoDB arangoDB) throws IOException { + assumeTrue(isAtLeastVersion(3, 11)); + final ArangoCursor cursor = arangoDB.db() + .query("for i in 1..2 return i", String.class, new AqlQueryOptions().allowRetry(true).batchSize(1)); + assertThat(cursor.hasNext()).isTrue(); + assertThat(cursor.next()).isEqualTo("1"); + assertThat(cursor.hasNext()).isTrue(); + cursor.close(); + } + + @ParameterizedTest(name = "{index}") + @MethodSource("arangos") + void queryAllowRetryCloseSingleBatch(ArangoDB arangoDB) throws IOException { + assumeTrue(isAtLeastVersion(3, 11)); + final ArangoCursor cursor = arangoDB.db() + .query("for i in 1..2 return i", String.class, new AqlQueryOptions().allowRetry(true)); + assertThat(cursor.hasNext()).isTrue(); + assertThat(cursor.next()).isEqualTo("1"); + assertThat(cursor.hasNext()).isTrue(); + assertThat(cursor.next()).isEqualTo("2"); + assertThat(cursor.hasNext()).isFalse(); + cursor.close(); + } + @ParameterizedTest(name = "{index}") @MethodSource("dbs") void explainQuery(ArangoDatabase db) { From 6acd318d860d2560668d934ce2910dda0b821f1b Mon Sep 17 00:00:00 2001 From: Michele Rastelli Date: Wed, 10 May 2023 14:47:51 +0200 Subject: [PATCH 4/8] resilience test --- .../resilience/retry/RetriableCursorTest.java | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 resilience-tests/src/test/java/resilience/retry/RetriableCursorTest.java diff --git a/resilience-tests/src/test/java/resilience/retry/RetriableCursorTest.java b/resilience-tests/src/test/java/resilience/retry/RetriableCursorTest.java new file mode 100644 index 000000000..9009536bd --- /dev/null +++ b/resilience-tests/src/test/java/resilience/retry/RetriableCursorTest.java @@ -0,0 +1,56 @@ +package resilience.retry; + +import com.arangodb.ArangoCursor; +import com.arangodb.ArangoDB; +import com.arangodb.ArangoDBException; +import com.arangodb.Protocol; +import com.arangodb.model.AqlQueryOptions; +import eu.rekawek.toxiproxy.model.ToxicDirection; +import eu.rekawek.toxiproxy.model.toxic.Latency; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import resilience.SingleServerTest; + +import java.io.IOException; +import java.util.concurrent.TimeoutException; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowable; + +/** + * @author Michele Rastelli + */ +class RetriableCursorTest extends SingleServerTest { + + static Stream arangoProvider() { + return Stream.of( + dbBuilder().timeout(1_000).protocol(Protocol.VST).build(), + dbBuilder().timeout(1_000).protocol(Protocol.HTTP_JSON).build(), + dbBuilder().timeout(1_000).protocol(Protocol.HTTP2_VPACK).build() + ); + } + + @ParameterizedTest + @MethodSource("arangoProvider") + void retryCursor(ArangoDB arangoDB) throws IOException { + try (ArangoCursor cursor = arangoDB.db() + .query("for i in 1..2 return i", + String.class, + new AqlQueryOptions().batchSize(1).allowRetry(true))) { + + assertThat(cursor.hasNext()).isTrue(); + assertThat(cursor.next()).isEqualTo("1"); + assertThat(cursor.hasNext()).isTrue(); + Latency toxic = getEndpoint().getProxy().toxics().latency("latency", ToxicDirection.DOWNSTREAM, 10_000); + Throwable thrown = catchThrowable(cursor::next); + assertThat(thrown).isInstanceOf(ArangoDBException.class); + assertThat(thrown.getCause()).isInstanceOfAny(TimeoutException.class, IOException.class); + toxic.remove(); + assertThat(cursor.next()).isEqualTo("2"); + assertThat(cursor.hasNext()).isFalse(); + } + arangoDB.shutdown(); + } + +} From 0629f9a0a40a2f08d0a3aa4185c2cb1f638470df Mon Sep 17 00:00:00 2001 From: Michele Rastelli Date: Wed, 10 May 2023 19:27:37 +0200 Subject: [PATCH 5/8] test fix --- driver/src/test/java/com/arangodb/ArangoDatabaseTest.java | 1 - .../src/test/java/resilience/retry/RetriableCursorTest.java | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/driver/src/test/java/com/arangodb/ArangoDatabaseTest.java b/driver/src/test/java/com/arangodb/ArangoDatabaseTest.java index c2392f9cf..36dffb249 100644 --- a/driver/src/test/java/com/arangodb/ArangoDatabaseTest.java +++ b/driver/src/test/java/com/arangodb/ArangoDatabaseTest.java @@ -1013,7 +1013,6 @@ void queryAllowRetry(ArangoDB arangoDB) throws IOException { final ArangoCursor cursor = arangoDB.db() .query("for i in 1..2 return i", String.class, new AqlQueryOptions().allowRetry(true).batchSize(1)); assertThat(cursor.asListRemaining()).containsExactly("1", "2"); - cursor.close(); } @ParameterizedTest(name = "{index}") diff --git a/resilience-tests/src/test/java/resilience/retry/RetriableCursorTest.java b/resilience-tests/src/test/java/resilience/retry/RetriableCursorTest.java index 9009536bd..51f7c0c2f 100644 --- a/resilience-tests/src/test/java/resilience/retry/RetriableCursorTest.java +++ b/resilience-tests/src/test/java/resilience/retry/RetriableCursorTest.java @@ -27,7 +27,7 @@ static Stream arangoProvider() { return Stream.of( dbBuilder().timeout(1_000).protocol(Protocol.VST).build(), dbBuilder().timeout(1_000).protocol(Protocol.HTTP_JSON).build(), - dbBuilder().timeout(1_000).protocol(Protocol.HTTP2_VPACK).build() + dbBuilder().timeout(1_000).protocol(Protocol.HTTP_VPACK).build() ); } @@ -45,7 +45,7 @@ void retryCursor(ArangoDB arangoDB) throws IOException { Latency toxic = getEndpoint().getProxy().toxics().latency("latency", ToxicDirection.DOWNSTREAM, 10_000); Throwable thrown = catchThrowable(cursor::next); assertThat(thrown).isInstanceOf(ArangoDBException.class); - assertThat(thrown.getCause()).isInstanceOfAny(TimeoutException.class, IOException.class); + assertThat(thrown.getCause()).isInstanceOfAny(TimeoutException.class); toxic.remove(); assertThat(cursor.next()).isEqualTo("2"); assertThat(cursor.hasNext()).isFalse(); From 41b62d6f7aeda82429eb673d0b7e7a5be0276de1 Mon Sep 17 00:00:00 2001 From: Michele Rastelli Date: Wed, 10 May 2023 19:51:13 +0200 Subject: [PATCH 6/8] resume cursor --- .../java/com/arangodb/ArangoDatabase.java | 14 ++++++ .../arangodb/internal/ArangoDatabaseImpl.java | 16 ++++++- .../java/com/arangodb/ArangoDatabaseTest.java | 44 +++++++++++-------- 3 files changed, 54 insertions(+), 20 deletions(-) diff --git a/core/src/main/java/com/arangodb/ArangoDatabase.java b/core/src/main/java/com/arangodb/ArangoDatabase.java index dfc99ea64..db4c9a6d7 100644 --- a/core/src/main/java/com/arangodb/ArangoDatabase.java +++ b/core/src/main/java/com/arangodb/ArangoDatabase.java @@ -310,6 +310,20 @@ public interface ArangoDatabase extends ArangoSerdeAccessor { */ ArangoCursor cursor(String cursorId, Class type); + /** + * Return an cursor from the given cursor-ID if still existing + * + * @param cursorId The ID of the cursor + * @param type The type of the result (POJO or {@link com.arangodb.util.RawData}) + * @param nextBatchId The ID of the next cursor batch (set only if cursor allows retries, see + * {@link AqlQueryOptions#allowRetry(Boolean)} + * @return cursor of the results + * @see API Documentation + * @since ArangoDB 3.11 + */ + ArangoCursor cursor(String cursorId, Class type, String nextBatchId); + /** * Explain an AQL query and return information about it * diff --git a/core/src/main/java/com/arangodb/internal/ArangoDatabaseImpl.java b/core/src/main/java/com/arangodb/internal/ArangoDatabaseImpl.java index 55bc886ed..0ad1abdb1 100644 --- a/core/src/main/java/com/arangodb/internal/ArangoDatabaseImpl.java +++ b/core/src/main/java/com/arangodb/internal/ArangoDatabaseImpl.java @@ -183,8 +183,20 @@ public ArangoCursor query(final String query, final Class type) { @Override public ArangoCursor cursor(final String cursorId, final Class type) { final HostHandle hostHandle = new HostHandle(); - final InternalCursorEntity result = executor - .execute(queryNextRequest(cursorId, null), internalCursorEntityDeserializer(), hostHandle); + final InternalCursorEntity result = executor.execute( + queryNextRequest(cursorId, null), + internalCursorEntityDeserializer(), + hostHandle); + return createCursor(result, type, null, hostHandle); + } + + @Override + public ArangoCursor cursor(final String cursorId, final Class type, final String nextBatchId) { + final HostHandle hostHandle = new HostHandle(); + final InternalCursorEntity result = executor.execute( + queryNextByBatchIdRequest(cursorId, nextBatchId, null), + internalCursorEntityDeserializer(), + hostHandle); return createCursor(result, type, null, hostHandle); } diff --git a/driver/src/test/java/com/arangodb/ArangoDatabaseTest.java b/driver/src/test/java/com/arangodb/ArangoDatabaseTest.java index 36dffb249..ff7fdeda2 100644 --- a/driver/src/test/java/com/arangodb/ArangoDatabaseTest.java +++ b/driver/src/test/java/com/arangodb/ArangoDatabaseTest.java @@ -834,25 +834,33 @@ void queryWithMaxWarningCount(ArangoDatabase db) { @ParameterizedTest(name = "{index}") @MethodSource("dbs") void queryCursor(ArangoDatabase db) { - final int numbDocs = 10; - for (int i = 0; i < numbDocs; i++) { - db.collection(CNAME1).insertDocument(new BaseDocument(), null); - } - - final int batchSize = 5; - final ArangoCursor cursor = db.query("for i in " + CNAME1 + " return i._id", String.class, - new AqlQueryOptions().batchSize(batchSize).count(true)); - assertThat((Object) cursor).isNotNull(); - assertThat(cursor.getCount()).isGreaterThanOrEqualTo(numbDocs); - - final ArangoCursor cursor2 = db.cursor(cursor.getId(), String.class); - assertThat((Object) cursor2).isNotNull(); - assertThat(cursor2.getCount()).isGreaterThanOrEqualTo(numbDocs); - assertThat((Iterator) cursor2).hasNext(); + ArangoCursor cursor = db.query("for i in 1..4 return i", Integer.class, + new AqlQueryOptions().batchSize(1)); + List result = new ArrayList<>(); + result.add(cursor.next()); + result.add(cursor.next()); + ArangoCursor cursor2 = db.cursor(cursor.getId(), Integer.class); + result.add(cursor2.next()); + result.add(cursor2.next()); + assertThat(cursor2.hasNext()).isFalse(); + assertThat(result).containsExactly(1, 2, 3, 4); + } - for (int i = 0; i < batchSize; i++, cursor.next()) { - assertThat((Iterator) cursor).hasNext(); - } + @ParameterizedTest(name = "{index}") + @MethodSource("dbs") + void queryCursorRetry(ArangoDatabase db) throws IOException { + assumeTrue(isAtLeastVersion(3, 11)); + ArangoCursor cursor = db.query("for i in 1..4 return i", Integer.class, + new AqlQueryOptions().batchSize(1).allowRetry(true)); + List result = new ArrayList<>(); + result.add(cursor.next()); + result.add(cursor.next()); + ArangoCursor cursor2 = db.cursor(cursor.getId(), Integer.class, cursor.getNextBatchId()); + result.add(cursor2.next()); + result.add(cursor2.next()); + cursor2.close(); + assertThat(cursor2.hasNext()).isFalse(); + assertThat(result).containsExactly(1, 2, 3, 4); } @ParameterizedTest(name = "{index}") From 8f9e50104a1d4c80d02da0a77a6fe12b1b846208 Mon Sep 17 00:00:00 2001 From: Michele Rastelli Date: Wed, 10 May 2023 20:45:55 +0200 Subject: [PATCH 7/8] doc --- .../main/java/com/arangodb/ArangoCursor.java | 18 ++++++++++++++++++ .../com/arangodb/model/AqlQueryOptions.java | 14 ++++++++------ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/com/arangodb/ArangoCursor.java b/core/src/main/java/com/arangodb/ArangoCursor.java index 049d2be0b..05c378636 100644 --- a/core/src/main/java/com/arangodb/ArangoCursor.java +++ b/core/src/main/java/com/arangodb/ArangoCursor.java @@ -22,10 +22,12 @@ import com.arangodb.entity.CursorStats; import com.arangodb.entity.CursorWarning; +import com.arangodb.model.AqlQueryOptions; import java.io.Closeable; import java.util.Collection; import java.util.List; +import java.util.NoSuchElementException; /** * @author Mark Vollmary @@ -83,4 +85,20 @@ public interface ArangoCursor extends ArangoIterable, ArangoIterator, C */ String getNextBatchId(); + /** + * Returns the next element in the iteration. + *

+ * If the cursor allows retries (see {@link AqlQueryOptions#allowRetry(Boolean)}), then it is safe to retry invoking + * this method in case of I/O exceptions (which are actually thrown as {@link com.arangodb.ArangoDBException} with + * cause {@link java.io.IOException}). + *

+ * If the cursor does not allow retries (default), then it is not safe to retry invoking this method in case of I/O + * exceptions, since the request to fetch the next batch is not idempotent (i.e. the cursor may advance multiple + * times on the server). + * + * @return the next element in the iteration + * @throws NoSuchElementException if the iteration has no more elements + */ + @Override + T next(); } diff --git a/core/src/main/java/com/arangodb/model/AqlQueryOptions.java b/core/src/main/java/com/arangodb/model/AqlQueryOptions.java index fb0ead110..92e65f3eb 100644 --- a/core/src/main/java/com/arangodb/model/AqlQueryOptions.java +++ b/core/src/main/java/com/arangodb/model/AqlQueryOptions.java @@ -491,13 +491,15 @@ public Boolean getAllowRetry() { /** * @param allowRetry Set this option to true to make it possible to retry fetching the latest batch from a cursor. + *

* This makes possible to safely retry invoking {@link com.arangodb.ArangoCursor#next()} in - * case of {@link java.io.IOException}. - *

- * If set to false (default), retry invoking {@link com.arangodb.ArangoCursor#next()} in case of - * {@link java.io.IOException} is not safe, since the request to fetch the next batch is not - * idempotent (i.e. the cursor may be advanced multiple times on the server). - * + * case of I/O exceptions (which are actually thrown as {@link com.arangodb.ArangoDBException} + * with cause {@link java.io.IOException}) + *

+ * If set to false (default), then it is not safe to retry invoking + * {@link com.arangodb.ArangoCursor#next()} in case of I/O exceptions, since the request to + * fetch the next batch is not idempotent (i.e. the cursor may advance multiple times on the + * server). * @return options * @since ArangoDB 3.11 */ From 3b1bca2999cfce0030f10b416eb1316bc4c490da Mon Sep 17 00:00:00 2001 From: Michele Rastelli Date: Wed, 10 May 2023 20:52:56 +0200 Subject: [PATCH 8/8] doc --- core/src/main/java/com/arangodb/model/AqlQueryOptions.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/src/main/java/com/arangodb/model/AqlQueryOptions.java b/core/src/main/java/com/arangodb/model/AqlQueryOptions.java index 92e65f3eb..5b12698a4 100644 --- a/core/src/main/java/com/arangodb/model/AqlQueryOptions.java +++ b/core/src/main/java/com/arangodb/model/AqlQueryOptions.java @@ -500,6 +500,10 @@ public Boolean getAllowRetry() { * {@link com.arangodb.ArangoCursor#next()} in case of I/O exceptions, since the request to * fetch the next batch is not idempotent (i.e. the cursor may advance multiple times on the * server). + *

+ * Note: once you successfully received the last batch, you should call + * {@link com.arangodb.ArangoCursor#close()} so that the server does not unnecessary keep the + * batch until the cursor times out ({@link AqlQueryOptions#ttl(Integer)}). * @return options * @since ArangoDB 3.11 */