diff --git a/ChangeLog.md b/ChangeLog.md index f07a8bb88..af723dfb7 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -6,6 +6,16 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ## [Unreleased] +### Added + +- added dirty read support ([reading from followers](https://docs.arangodb.com/current/Manual/Administration/ActiveFailover/#reading-from-follower)) + + - added option `AqlQueryOptions#allowDirtyRead` for `ArangoDatabase#query`. + - added option `DocumentReadOptions#allowDirtyRead` for `ArangoCollection#getDocument` + - added option `DocumentReadOptions#allowDirtyRead` for `ArangoCollection#getDocuments` + - added option `DocumentReadOptions#allowDirtyRead` for `ArangoVertexCollection#getVertex` + - added option `DocumentReadOptions#allowDirtyRead` for `ArangoEdgeCollection#getEdge` + ### Changed - changed the internal connection pool and host management. There now exists a connection pool for every configured host. This changes the behavior of `ArangoDB.Builder#maxConnections` which now allows to configure the maximal number of connection per host and not overall. diff --git a/src/main/java/com/arangodb/ArangoCollection.java b/src/main/java/com/arangodb/ArangoCollection.java index d6d4a3aa7..29e179fdd 100644 --- a/src/main/java/com/arangodb/ArangoCollection.java +++ b/src/main/java/com/arangodb/ArangoCollection.java @@ -214,6 +214,21 @@ MultiDocumentEntity> insertDocuments( */ MultiDocumentEntity getDocuments(Collection keys, Class type) throws ArangoDBException; + /** + * Retrieves multiple documents with the given {@code _key} from the collection. + * + * @param keys + * The keys of the documents + * @param type + * The type of the documents (POJO class, VPackSlice or String for Json) + * @param options + * Additional options, can be null + * @return the documents and possible errors + * @throws ArangoDBException + */ + MultiDocumentEntity getDocuments(Collection keys, Class type, DocumentReadOptions options) + throws ArangoDBException; + /** * Replaces the document with {@code key} with the one in the body, provided there is such a document and no * precondition is violated diff --git a/src/main/java/com/arangodb/ArangoDB.java b/src/main/java/com/arangodb/ArangoDB.java index c2dbcc58f..683b25bbe 100644 --- a/src/main/java/com/arangodb/ArangoDB.java +++ b/src/main/java/com/arangodb/ArangoDB.java @@ -41,6 +41,7 @@ import com.arangodb.internal.http.HttpCommunication; import com.arangodb.internal.http.HttpConnectionFactory; import com.arangodb.internal.net.ConnectionFactory; +import com.arangodb.internal.net.Host; import com.arangodb.internal.net.HostHandle; import com.arangodb.internal.net.HostHandler; import com.arangodb.internal.net.HostResolver; @@ -620,8 +621,8 @@ public synchronized ArangoDB build() { : new HttpConnectionFactory(timeout, user, password, useSsl, sslContext, custom, protocol, connectionTtl); - final HostResolver hostResolver = createHostResolver(createHostList(max, connectionFactory), max, - connectionFactory); + final Collection hostList = createHostList(max, connectionFactory); + final HostResolver hostResolver = createHostResolver(hostList, max, connectionFactory); final HostHandler hostHandler = createHostHandler(hostResolver); return new ArangoDBImpl( new VstCommunicationSync.Builder(hostHandler).timeout(timeout).user(user).password(password) diff --git a/src/main/java/com/arangodb/internal/ArangoCollectionImpl.java b/src/main/java/com/arangodb/internal/ArangoCollectionImpl.java index 54f96a4d6..4f323a5e3 100644 --- a/src/main/java/com/arangodb/internal/ArangoCollectionImpl.java +++ b/src/main/java/com/arangodb/internal/ArangoCollectionImpl.java @@ -148,7 +148,14 @@ public T getDocument(final String key, final Class type, final DocumentRe @Override public MultiDocumentEntity getDocuments(final Collection keys, final Class type) throws ArangoDBException { - final DocumentReadOptions options = new DocumentReadOptions(); + return getDocuments(keys, type, new DocumentReadOptions()); + } + + @Override + public MultiDocumentEntity getDocuments( + final Collection keys, + final Class type, + final DocumentReadOptions options) throws ArangoDBException { return executor.execute(getDocumentsRequest(keys, options), getDocumentsResponseDeserializer(type, options)); } diff --git a/src/main/java/com/arangodb/internal/InternalArangoCollection.java b/src/main/java/com/arangodb/internal/InternalArangoCollection.java index d9dd293fb..cc024e09e 100644 --- a/src/main/java/com/arangodb/internal/InternalArangoCollection.java +++ b/src/main/java/com/arangodb/internal/InternalArangoCollection.java @@ -38,6 +38,7 @@ import com.arangodb.internal.ArangoExecutor.ResponseDeserializer; import com.arangodb.internal.util.ArangoSerializationFactory.Serializer; import com.arangodb.internal.util.DocumentUtil; +import com.arangodb.internal.util.RequestUtils; import com.arangodb.model.CollectionPropertiesOptions; import com.arangodb.model.CollectionRenameOptions; import com.arangodb.model.DocumentCreateOptions; @@ -221,13 +222,22 @@ protected Request getDocumentRequest(final String key, final DocumentReadOptions final DocumentReadOptions params = (options != null ? options : new DocumentReadOptions()); request.putHeaderParam(ArangoRequestParam.IF_NONE_MATCH, params.getIfNoneMatch()); request.putHeaderParam(ArangoRequestParam.IF_MATCH, params.getIfMatch()); + if (params.getAllowDirtyRead() == Boolean.TRUE) { + RequestUtils.allowDirtyRead(request); + } return request; } protected Request getDocumentsRequest(final Collection keys, final DocumentReadOptions options) { - return request(db.name(), RequestType.PUT, PATH_API_DOCUMENT, name).putQueryParam("onlyget", true) - .putHeaderParam(ArangoRequestParam.IF_NONE_MATCH, options.getIfNoneMatch()) - .putHeaderParam(ArangoRequestParam.IF_MATCH, options.getIfMatch()).setBody(util().serialize(keys)); + final DocumentReadOptions params = (options != null ? options : new DocumentReadOptions()); + final Request request = request(db.name(), RequestType.PUT, PATH_API_DOCUMENT, name) + .putQueryParam("onlyget", true) + .putHeaderParam(ArangoRequestParam.IF_NONE_MATCH, params.getIfNoneMatch()) + .putHeaderParam(ArangoRequestParam.IF_MATCH, params.getIfMatch()).setBody(util().serialize(keys)); + if (params.getAllowDirtyRead() == Boolean.TRUE) { + RequestUtils.allowDirtyRead(request); + } + return request; } protected ResponseDeserializer> getDocumentsResponseDeserializer( diff --git a/src/main/java/com/arangodb/internal/InternalArangoDBBuilder.java b/src/main/java/com/arangodb/internal/InternalArangoDBBuilder.java index 1c99aaa08..7187a8637 100644 --- a/src/main/java/com/arangodb/internal/InternalArangoDBBuilder.java +++ b/src/main/java/com/arangodb/internal/InternalArangoDBBuilder.java @@ -34,6 +34,7 @@ import com.arangodb.entity.LoadBalancingStrategy; import com.arangodb.internal.net.Connection; import com.arangodb.internal.net.ConnectionFactory; +import com.arangodb.internal.net.DirtyReadHostHandler; import com.arangodb.internal.net.ExtendedHostResolver; import com.arangodb.internal.net.FallbackHostHandler; import com.arangodb.internal.net.Host; @@ -213,7 +214,7 @@ protected HostHandler createHostHandler(final HostResolver hostResolver) { } else { hostHandler = new FallbackHostHandler(hostResolver); } - return hostHandler; + return new DirtyReadHostHandler(hostHandler, new RoundRobinHostHandler(hostResolver)); } private static void loadHosts(final Properties properties, final Collection hosts) { diff --git a/src/main/java/com/arangodb/internal/InternalArangoDatabase.java b/src/main/java/com/arangodb/internal/InternalArangoDatabase.java index 4f5d77ef7..3b488436c 100644 --- a/src/main/java/com/arangodb/internal/InternalArangoDatabase.java +++ b/src/main/java/com/arangodb/internal/InternalArangoDatabase.java @@ -39,6 +39,7 @@ import com.arangodb.entity.ViewType; import com.arangodb.internal.ArangoExecutor.ResponseDeserializer; import com.arangodb.internal.util.ArangoSerializationFactory.Serializer; +import com.arangodb.internal.util.RequestUtils; import com.arangodb.model.AqlFunctionCreateOptions; import com.arangodb.model.AqlFunctionDeleteOptions; import com.arangodb.model.AqlFunctionGetOptions; @@ -192,9 +193,14 @@ protected Request queryRequest( final String query, final Map bindVars, final AqlQueryOptions options) { - return request(name, RequestType.POST, PATH_API_CURSOR).setBody(util().serialize( - OptionsBuilder.build(options != null ? options : new AqlQueryOptions(), query, bindVars != null + final AqlQueryOptions opt = options != null ? options : new AqlQueryOptions(); + final Request request = request(name, RequestType.POST, PATH_API_CURSOR).setBody( + util().serialize(OptionsBuilder.build(opt, query, bindVars != null ? util().serialize(bindVars, new ArangoSerializer.Options().serializeNullValues(true)) : null))); + if (opt.getAllowDirtyRead() == Boolean.TRUE) { + RequestUtils.allowDirtyRead(request); + } + return request; } protected Request queryNextRequest(final String id) { diff --git a/src/main/java/com/arangodb/internal/InternalArangoEdgeCollection.java b/src/main/java/com/arangodb/internal/InternalArangoEdgeCollection.java index 0b2a25b72..2e2d1f8c4 100644 --- a/src/main/java/com/arangodb/internal/InternalArangoEdgeCollection.java +++ b/src/main/java/com/arangodb/internal/InternalArangoEdgeCollection.java @@ -29,6 +29,7 @@ import com.arangodb.internal.ArangoExecutor.ResponseDeserializer; import com.arangodb.internal.util.ArangoSerializationFactory.Serializer; import com.arangodb.internal.util.DocumentUtil; +import com.arangodb.internal.util.RequestUtils; import com.arangodb.model.DocumentReadOptions; import com.arangodb.model.EdgeCreateOptions; import com.arangodb.model.EdgeDeleteOptions; @@ -99,6 +100,9 @@ protected Request getEdgeRequest(final String key, final DocumentReadOptions opt final DocumentReadOptions params = (options != null ? options : new DocumentReadOptions()); request.putHeaderParam(ArangoRequestParam.IF_NONE_MATCH, params.getIfNoneMatch()); request.putHeaderParam(ArangoRequestParam.IF_MATCH, params.getIfMatch()); + if (params.getAllowDirtyRead()) { + RequestUtils.allowDirtyRead(request); + } return request; } diff --git a/src/main/java/com/arangodb/internal/InternalArangoVertexCollection.java b/src/main/java/com/arangodb/internal/InternalArangoVertexCollection.java index 470376079..0a527d698 100644 --- a/src/main/java/com/arangodb/internal/InternalArangoVertexCollection.java +++ b/src/main/java/com/arangodb/internal/InternalArangoVertexCollection.java @@ -29,6 +29,7 @@ import com.arangodb.internal.ArangoExecutor.ResponseDeserializer; import com.arangodb.internal.util.ArangoSerializationFactory.Serializer; import com.arangodb.internal.util.DocumentUtil; +import com.arangodb.internal.util.RequestUtils; import com.arangodb.model.DocumentReadOptions; import com.arangodb.model.VertexCreateOptions; import com.arangodb.model.VertexDeleteOptions; @@ -103,6 +104,9 @@ protected Request getVertexRequest(final String key, final DocumentReadOptions o final DocumentReadOptions params = (options != null ? options : new DocumentReadOptions()); request.putHeaderParam(ArangoRequestParam.IF_NONE_MATCH, params.getIfNoneMatch()); request.putHeaderParam(ArangoRequestParam.IF_MATCH, params.getIfMatch()); + if (params.getAllowDirtyRead()) { + RequestUtils.allowDirtyRead(request); + } return request; } diff --git a/src/main/java/com/arangodb/internal/http/HttpCommunication.java b/src/main/java/com/arangodb/internal/http/HttpCommunication.java index 594502395..b393f30a9 100644 --- a/src/main/java/com/arangodb/internal/http/HttpCommunication.java +++ b/src/main/java/com/arangodb/internal/http/HttpCommunication.java @@ -28,12 +28,14 @@ import org.slf4j.LoggerFactory; import com.arangodb.ArangoDBException; +import com.arangodb.internal.net.AccessType; import com.arangodb.internal.net.ArangoDBRedirectException; import com.arangodb.internal.net.Host; import com.arangodb.internal.net.HostDescription; import com.arangodb.internal.net.HostHandle; import com.arangodb.internal.net.HostHandler; import com.arangodb.internal.util.HostUtils; +import com.arangodb.internal.util.RequestUtils; import com.arangodb.util.ArangoSerialization; import com.arangodb.velocystream.Request; import com.arangodb.velocystream.Response; @@ -77,7 +79,8 @@ public void close() throws IOException { } public Response execute(final Request request, final HostHandle hostHandle) throws ArangoDBException, IOException { - Host host = hostHandler.get(hostHandle); + final AccessType accessType = RequestUtils.determineAccessType(request); + Host host = hostHandler.get(hostHandle, accessType); try { while (true) { try { @@ -89,7 +92,7 @@ public Response execute(final Request request, final HostHandle hostHandle) thro } catch (final SocketException se) { hostHandler.fail(); final Host failedHost = host; - host = hostHandler.get(hostHandle); + host = hostHandler.get(hostHandle, accessType); if (host != null) { LOGGER.warn(String.format("Could not connect to %s. Try connecting to %s", failedHost.getDescription(), host.getDescription())); diff --git a/src/main/java/com/arangodb/internal/net/AccessType.java b/src/main/java/com/arangodb/internal/net/AccessType.java new file mode 100644 index 000000000..39f302300 --- /dev/null +++ b/src/main/java/com/arangodb/internal/net/AccessType.java @@ -0,0 +1,31 @@ +/* + * DISCLAIMER + * + * Copyright 2018 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.net; + +/** + * @author Mark Vollmary + * + */ +public enum AccessType { + + WRITE, READ, DIRTY_READ + +} diff --git a/src/main/java/com/arangodb/internal/net/DirtyReadHostHandler.java b/src/main/java/com/arangodb/internal/net/DirtyReadHostHandler.java new file mode 100644 index 000000000..d5607a174 --- /dev/null +++ b/src/main/java/com/arangodb/internal/net/DirtyReadHostHandler.java @@ -0,0 +1,87 @@ +/* + * DISCLAIMER + * + * Copyright 2018 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.net; + +import java.io.IOException; + +/** + * @author Mark Vollmary + * + */ +public class DirtyReadHostHandler implements HostHandler { + + private final HostHandler master; + private final HostHandler follower; + private AccessType currentAccessType; + + public DirtyReadHostHandler(final HostHandler master, final HostHandler follower) { + super(); + this.master = master; + this.follower = follower; + } + + private HostHandler determineHostHandler() { + switch (currentAccessType) { + case DIRTY_READ: + return follower; + default: + return master; + } + } + + @Override + public Host get(final HostHandle hostHandle, final AccessType accessType) { + this.currentAccessType = accessType; + return determineHostHandler().get(hostHandle, accessType); + } + + @Override + public void success() { + determineHostHandler().success(); + } + + @Override + public void fail() { + determineHostHandler().fail(); + } + + @Override + public void reset() { + determineHostHandler().reset(); + } + + @Override + public void confirm() { + determineHostHandler().confirm(); + } + + @Override + public void close() throws IOException { + master.close(); + follower.close(); + } + + @Override + public void closeCurrentOnError() { + determineHostHandler().closeCurrentOnError(); + } + +} diff --git a/src/main/java/com/arangodb/internal/net/FallbackHostHandler.java b/src/main/java/com/arangodb/internal/net/FallbackHostHandler.java index 894ecc1cc..ce938173b 100644 --- a/src/main/java/com/arangodb/internal/net/FallbackHostHandler.java +++ b/src/main/java/com/arangodb/internal/net/FallbackHostHandler.java @@ -43,7 +43,7 @@ public FallbackHostHandler(final HostResolver resolver) { } @Override - public Host get(final HostHandle hostHandle) { + public Host get(final HostHandle hostHandle, AccessType accessType) { return current != lastSuccess || iterations < 3 ? current : null; } diff --git a/src/main/java/com/arangodb/internal/net/HostHandler.java b/src/main/java/com/arangodb/internal/net/HostHandler.java index ea74579a7..6577e5821 100644 --- a/src/main/java/com/arangodb/internal/net/HostHandler.java +++ b/src/main/java/com/arangodb/internal/net/HostHandler.java @@ -28,7 +28,7 @@ */ public interface HostHandler { - Host get(HostHandle hostHandle); + Host get(HostHandle hostHandle, AccessType accessType); void success(); diff --git a/src/main/java/com/arangodb/internal/net/RandomHostHandler.java b/src/main/java/com/arangodb/internal/net/RandomHostHandler.java index f52248643..1129f7156 100644 --- a/src/main/java/com/arangodb/internal/net/RandomHostHandler.java +++ b/src/main/java/com/arangodb/internal/net/RandomHostHandler.java @@ -44,7 +44,7 @@ public RandomHostHandler(final HostResolver resolver, final HostHandler fallback } @Override - public Host get(final HostHandle hostHandle) { + public Host get(final HostHandle hostHandle, AccessType accessType) { if (current == null) { origin = current = getRandomHost(false, true); } @@ -59,7 +59,7 @@ public void success() { @Override public void fail() { fallback.fail(); - current = fallback.get(null); + current = fallback.get(null, null); } private Host getRandomHost(final boolean initial, final boolean closeConnections) { diff --git a/src/main/java/com/arangodb/internal/net/RoundRobinHostHandler.java b/src/main/java/com/arangodb/internal/net/RoundRobinHostHandler.java index 22943b77c..6c1f19c9d 100644 --- a/src/main/java/com/arangodb/internal/net/RoundRobinHostHandler.java +++ b/src/main/java/com/arangodb/internal/net/RoundRobinHostHandler.java @@ -42,7 +42,7 @@ public RoundRobinHostHandler(final HostResolver resolver) { } @Override - public Host get(final HostHandle hostHandle) { + public Host get(final HostHandle hostHandle, AccessType accessType) { final List hosts = resolver.resolve(false, false); final int size = hosts.size(); if (fails > size) { diff --git a/src/main/java/com/arangodb/internal/util/RequestUtils.java b/src/main/java/com/arangodb/internal/util/RequestUtils.java new file mode 100644 index 000000000..6468b6109 --- /dev/null +++ b/src/main/java/com/arangodb/internal/util/RequestUtils.java @@ -0,0 +1,54 @@ +/* + * DISCLAIMER + * + * Copyright 2018 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.util; + +import com.arangodb.internal.net.AccessType; +import com.arangodb.velocystream.Request; + +/** + * @author Mark Vollmary + * + */ +public final class RequestUtils { + + public static final String HEADER_ALLOW_DIRTY_READ = "X-Arango-Allow-Dirty-Read"; + + private RequestUtils() { + super(); + } + + public static Request allowDirtyRead(final Request request) { + return request.putHeaderParam(HEADER_ALLOW_DIRTY_READ, "true"); + } + + public static AccessType determineAccessType(final Request request) { + if (request.getHeaderParam().containsKey(HEADER_ALLOW_DIRTY_READ)) { + return AccessType.DIRTY_READ; + } + switch (request.getRequestType()) { + case GET: + return AccessType.READ; + default: + return AccessType.WRITE; + } + } + +} diff --git a/src/main/java/com/arangodb/internal/velocystream/VstCommunication.java b/src/main/java/com/arangodb/internal/velocystream/VstCommunication.java index 19a300645..273c28dcd 100644 --- a/src/main/java/com/arangodb/internal/velocystream/VstCommunication.java +++ b/src/main/java/com/arangodb/internal/velocystream/VstCommunication.java @@ -33,12 +33,14 @@ import com.arangodb.ArangoDBException; import com.arangodb.internal.ArangoDefaults; +import com.arangodb.internal.net.AccessType; import com.arangodb.internal.net.ArangoDBRedirectException; +import com.arangodb.internal.net.Host; import com.arangodb.internal.net.HostDescription; import com.arangodb.internal.net.HostHandle; import com.arangodb.internal.net.HostHandler; -import com.arangodb.internal.net.Host; import com.arangodb.internal.util.HostUtils; +import com.arangodb.internal.util.RequestUtils; import com.arangodb.internal.util.ResponseUtils; import com.arangodb.internal.velocystream.internal.Chunk; import com.arangodb.internal.velocystream.internal.Message; @@ -78,8 +80,8 @@ protected VstCommunication(final Integer timeout, final String user, final Strin } @SuppressWarnings("unchecked") - protected synchronized C connect(final HostHandle hostHandle) { - Host host = hostHandler.get(hostHandle); + protected synchronized C connect(final HostHandle hostHandle, final AccessType accessType) { + Host host = hostHandler.get(hostHandle, accessType); while (true) { if (host == null) { hostHandler.reset(); @@ -100,7 +102,7 @@ protected synchronized C connect(final HostHandle hostHandle) { } catch (final IOException e) { hostHandler.fail(); final Host failedHost = host; - host = hostHandler.get(hostHandle); + host = hostHandler.get(hostHandle, accessType); if (host != null) { LOGGER.warn( String.format("Could not connect to %s or SSL Handshake failed. Try connecting to %s", @@ -123,7 +125,7 @@ public void close() throws IOException { public R execute(final Request request, final HostHandle hostHandle) throws ArangoDBException { try { - final C connection = connect(hostHandle); + final C connection = connect(hostHandle, RequestUtils.determineAccessType(request)); return execute(request, connection); } catch (final ArangoDBException e) { if (e instanceof ArangoDBRedirectException) { diff --git a/src/main/java/com/arangodb/model/AqlQueryOptions.java b/src/main/java/com/arangodb/model/AqlQueryOptions.java index 699245cdf..21864f46f 100644 --- a/src/main/java/com/arangodb/model/AqlQueryOptions.java +++ b/src/main/java/com/arangodb/model/AqlQueryOptions.java @@ -26,6 +26,7 @@ import java.util.Collection; import com.arangodb.velocypack.VPackSlice; +import com.arangodb.velocypack.annotations.Expose; /** * @author Mark Vollmary @@ -45,6 +46,8 @@ public class AqlQueryOptions implements Serializable { private VPackSlice bindVars; private String query; private Options options; + @Expose(serialize = false) + private Boolean allowDirtyRead; public AqlQueryOptions() { super(); @@ -435,4 +438,21 @@ private static class Optimizer { private Collection rules; } + /** + * @see API + * Documentation + * @param allowDirtyRead + * Set to {@code true} allows reading from followers in an active-failover setup. + * @since ArangoDB 3.4.0 + * @return options + */ + public AqlQueryOptions allowDirtyRead(final Boolean allowDirtyRead) { + this.allowDirtyRead = allowDirtyRead; + return this; + } + + public Boolean getAllowDirtyRead() { + return allowDirtyRead; + } + } diff --git a/src/main/java/com/arangodb/model/DocumentReadOptions.java b/src/main/java/com/arangodb/model/DocumentReadOptions.java index 5f9b40dee..7350792f7 100644 --- a/src/main/java/com/arangodb/model/DocumentReadOptions.java +++ b/src/main/java/com/arangodb/model/DocumentReadOptions.java @@ -20,6 +20,8 @@ package com.arangodb.model; +import com.arangodb.velocypack.annotations.Expose; + /** * @author Mark Vollmary * @@ -31,6 +33,8 @@ public class DocumentReadOptions { private String ifNoneMatch; private String ifMatch; private boolean catchException; + @Expose(serialize = false) + private Boolean allowDirtyRead; public DocumentReadOptions() { super(); @@ -79,4 +83,21 @@ public DocumentReadOptions catchException(final boolean catchException) { return this; } + /** + * @see API + * Documentation + * @param allowDirtyRead + * Set to {@code true} allows reading from followers in an active-failover setup. + * @since ArangoDB 3.4.0 + * @return options + */ + public DocumentReadOptions allowDirtyRead(final Boolean allowDirtyRead) { + this.allowDirtyRead = allowDirtyRead; + return this; + } + + public Boolean getAllowDirtyRead() { + return allowDirtyRead; + } + } diff --git a/src/test/java/com/arangodb/ArangoCollectionTest.java b/src/test/java/com/arangodb/ArangoCollectionTest.java index 35e1aa6e6..c3e071405 100644 --- a/src/test/java/com/arangodb/ArangoCollectionTest.java +++ b/src/test/java/com/arangodb/ArangoCollectionTest.java @@ -73,6 +73,7 @@ import com.arangodb.model.DocumentReadOptions; import com.arangodb.model.DocumentReplaceOptions; import com.arangodb.model.DocumentUpdateOptions; +import com.arangodb.velocypack.VPackSlice; /** * @author Mark Vollmary @@ -300,6 +301,14 @@ public void getDocumentWrongKey() { db.collection(COLLECTION_NAME).getDocument("no/no", BaseDocument.class); } + public void getDocumentDirtyRead() { + final BaseDocument doc = new BaseDocument(); + db.collection(COLLECTION_NAME).insertDocument(doc); + final VPackSlice document = db.collection(COLLECTION_NAME).getDocument(doc.getKey(), VPackSlice.class, + new DocumentReadOptions().allowDirtyRead(true)); + assertThat(document, is(notNullValue())); + } + @Test public void getDocuments() { final Collection values = new ArrayList(); @@ -317,6 +326,23 @@ public void getDocuments() { } } + @Test + public void getDocumentsDirtyRead() { + final Collection values = new ArrayList(); + values.add(new BaseDocument("1")); + values.add(new BaseDocument("2")); + values.add(new BaseDocument("3")); + db.collection(COLLECTION_NAME).insertDocuments(values); + final MultiDocumentEntity documents = db.collection(COLLECTION_NAME).getDocuments( + Arrays.asList("1", "2", "3"), BaseDocument.class, new DocumentReadOptions().allowDirtyRead(true)); + assertThat(documents, is(notNullValue())); + assertThat(documents.getDocuments().size(), is(3)); + for (final BaseDocument document : documents.getDocuments()) { + assertThat(document.getId(), + isOneOf(COLLECTION_NAME + "/" + "1", COLLECTION_NAME + "/" + "2", COLLECTION_NAME + "/" + "3")); + } + } + @Test public void getDocumentsNotFound() { final MultiDocumentEntity readResult = db.collection(COLLECTION_NAME) diff --git a/src/test/java/com/arangodb/ArangoDatabaseTest.java b/src/test/java/com/arangodb/ArangoDatabaseTest.java index a81193916..e7c76450e 100644 --- a/src/test/java/com/arangodb/ArangoDatabaseTest.java +++ b/src/test/java/com/arangodb/ArangoDatabaseTest.java @@ -852,6 +852,19 @@ public void queryWithNullBindParam() throws IOException { } } + @Test + public void queryAllowDirtyRead() throws IOException { + try { + db.createCollection(COLLECTION_NAME); + final ArangoCursor cursor = db.query("FOR i IN @@col FILTER i.test == @test RETURN i", + new MapBuilder().put("@col", COLLECTION_NAME).put("test", null).get(), + new AqlQueryOptions().allowDirtyRead(true), BaseDocument.class); + cursor.close(); + } finally { + db.collection(COLLECTION_NAME).drop(); + } + } + @Test public void explainQuery() { final AqlExecutionExplainEntity explain = arangoDB.db().explainQuery("for i in 1..1 return i", null, null); diff --git a/src/test/java/com/arangodb/internal/HostHandlerTest.java b/src/test/java/com/arangodb/internal/HostHandlerTest.java index 61280cbf8..9b1e6df85 100644 --- a/src/test/java/com/arangodb/internal/HostHandlerTest.java +++ b/src/test/java/com/arangodb/internal/HostHandlerTest.java @@ -79,26 +79,26 @@ public void init(final EndpointResolver resolver) { @Test public void fallbachHostHandlerSingleHost() { final HostHandler handler = new FallbackHostHandler(SINGLE_HOST); - assertThat(handler.get(null), is(HOST_0)); + assertThat(handler.get(null, null), is(HOST_0)); handler.fail(); - assertThat(handler.get(null), is(HOST_0)); + assertThat(handler.get(null, null), is(HOST_0)); } @Test public void fallbackHostHandlerMultipleHosts() { final HostHandler handler = new FallbackHostHandler(MULTIPLE_HOSTS); for (int i = 0; i < 3; i++) { - assertThat(handler.get(null), is(HOST_0)); + assertThat(handler.get(null, null), is(HOST_0)); handler.fail(); - assertThat(handler.get(null), is(HOST_1)); + assertThat(handler.get(null, null), is(HOST_1)); handler.fail(); - assertThat(handler.get(null), is(HOST_2)); + assertThat(handler.get(null, null), is(HOST_2)); if (i < 2) { handler.fail(); - assertThat(handler.get(null), is(HOST_0)); + assertThat(handler.get(null, null), is(HOST_0)); } else { handler.fail(); - assertThat(handler.get(null), is(nullValue())); + assertThat(handler.get(null, null), is(nullValue())); } } } @@ -106,42 +106,42 @@ public void fallbackHostHandlerMultipleHosts() { @Test public void randomHostHandlerSingleHost() { final HostHandler handler = new RandomHostHandler(SINGLE_HOST, new FallbackHostHandler(SINGLE_HOST)); - assertThat(handler.get(null), is(HOST_0)); + assertThat(handler.get(null, null), is(HOST_0)); handler.fail(); - assertThat(handler.get(null), is(HOST_0)); + assertThat(handler.get(null, null), is(HOST_0)); } @Test public void randomHostHandlerMultipeHosts() { final HostHandler handler = new RandomHostHandler(MULTIPLE_HOSTS, new FallbackHostHandler(MULTIPLE_HOSTS)); - final Host pick0 = handler.get(null); + final Host pick0 = handler.get(null, null); assertThat(pick0, anyOf(is(HOST_0), is(HOST_1), is(HOST_2))); handler.fail(); - assertThat(handler.get(null), anyOf(is(HOST_0), is(HOST_1), is(HOST_2))); + assertThat(handler.get(null, null), anyOf(is(HOST_0), is(HOST_1), is(HOST_2))); handler.success(); - assertThat(handler.get(null), is(pick0)); + assertThat(handler.get(null, null), is(pick0)); } @Test public void roundRobinHostHandlerSingleHost() { final HostHandler handler = new RoundRobinHostHandler(SINGLE_HOST); - assertThat(handler.get(null), is(HOST_0)); + assertThat(handler.get(null, null), is(HOST_0)); handler.fail(); - assertThat(handler.get(null), is(HOST_0)); + assertThat(handler.get(null, null), is(HOST_0)); } @Test public void roundRobinHostHandlerMultipleHosts() { final HostHandler handler = new RoundRobinHostHandler(MULTIPLE_HOSTS); - final Host pick0 = handler.get(null); + final Host pick0 = handler.get(null, null); assertThat(pick0, anyOf(is(HOST_0), is(HOST_1), is(HOST_2))); - final Host pick1 = handler.get(null); + final Host pick1 = handler.get(null, null); assertThat(pick1, anyOf(is(HOST_0), is(HOST_1), is(HOST_2))); assertThat(pick1, is(not(pick0))); - final Host pick2 = handler.get(null); + final Host pick2 = handler.get(null, null); assertThat(pick2, anyOf(is(HOST_0), is(HOST_1), is(HOST_2))); assertThat(pick2, not(anyOf(is(pick0), is(pick1)))); - final Host pick4 = handler.get(null); + final Host pick4 = handler.get(null, null); assertThat(pick4, is(pick0)); }