diff --git a/ChangeLog.md b/ChangeLog.md index 8b9214f87..21444178d 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ## [Unreleased] +## [6.22.0] - 2023-03-07 + +- added support to `forceOneShardAttributeValue` query parameter (DE-541) + ## [6.21.0] - 2023-03-07 - added `x-arango-driver` header (DE-479) diff --git a/src/main/java/com/arangodb/model/AqlQueryOptions.java b/src/main/java/com/arangodb/model/AqlQueryOptions.java index 52e344663..c09fd143a 100644 --- a/src/main/java/com/arangodb/model/AqlQueryOptions.java +++ b/src/main/java/com/arangodb/model/AqlQueryOptions.java @@ -403,6 +403,28 @@ public AqlQueryOptions shardIds(final String... shardIds) { return this; } + + public String getForceOneShardAttributeValue() { + return options != null ? options.forceOneShardAttributeValue : null; + } + + /** + * @param forceOneShardAttributeValue This query option can be used in complex queries in case the query optimizer + * cannot automatically detect that the query can be limited to only a single + * server (e.g. in a disjoint smart graph case). + *

+ * If the option is set incorrectly, i.e. to a wrong shard key value, then the + * query may be shipped to a wrong DB server and may not return results (i.e. + * empty result set). + *

+ * Use at your own risk. + * @return options + */ + public AqlQueryOptions forceOneShardAttributeValue(final String forceOneShardAttributeValue) { + getOptions().forceOneShardAttributeValue = forceOneShardAttributeValue; + return this; + } + private Options getOptions() { if (options == null) { options = new Options(); @@ -429,6 +451,7 @@ public static class Options implements Serializable { private Collection shardIds; private Double maxRuntime; private Boolean fillBlockCache; + private String forceOneShardAttributeValue; protected Optimizer getOptimizer() { if (optimizer == null) { diff --git a/src/test/java/com/arangodb/ArangoDatabaseTest.java b/src/test/java/com/arangodb/ArangoDatabaseTest.java index 7f3705736..5678ce660 100644 --- a/src/test/java/com/arangodb/ArangoDatabaseTest.java +++ b/src/test/java/com/arangodb/ArangoDatabaseTest.java @@ -912,13 +912,41 @@ void queryWithWarning(ArangoDB arangoDB) { @ParameterizedTest(name = "{index}") @MethodSource("dbs") void queryStream(ArangoDatabase db) { - if (isAtLeastVersion(3, 4)) { - final ArangoCursor cursor = db - .query("FOR i IN 1..2 RETURN i", null, new AqlQueryOptions().stream(true).count(true), - VPackSlice.class); - assertThat((Object) cursor).isNotNull(); - assertThat(cursor.getCount()).isNull(); - } + final ArangoCursor cursor = db + .query("FOR i IN 1..2 RETURN i", null, new AqlQueryOptions().stream(true).count(true), + VPackSlice.class); + assertThat((Object) cursor).isNotNull(); + assertThat(cursor.getCount()).isNull(); + } + + @ParameterizedTest(name = "{index}") + @MethodSource("dbs") + void queryForceOneShardAttributeValue(ArangoDatabase db) { + assumeTrue(isAtLeastVersion(3, 10)); + assumeTrue(isCluster()); + assumeTrue(isEnterprise()); + + String cname = "forceOneShardAttr-" + UUID.randomUUID(); + db.createCollection(cname, new CollectionCreateOptions() + .shardKeys("foo") + .numberOfShards(3)); + ArangoCollection col = db.collection(cname); + BaseDocument doc = new BaseDocument(); + doc.addAttribute("foo", "bar"); + col.insertDocument(doc); + + ArangoCursor c1 = db + .query("FOR d IN @@c RETURN d", Collections.singletonMap("@c", cname), new AqlQueryOptions() + .forceOneShardAttributeValue("bar"), + BaseDocument.class); + assertThat(c1.hasNext()).isTrue(); + assertThat(c1.next().getAttribute("foo")).isEqualTo("bar"); + + ArangoCursor c2 = db + .query("FOR d IN @@c RETURN d", Collections.singletonMap("@c", cname), new AqlQueryOptions() + .forceOneShardAttributeValue("ooo"), + BaseDocument.class); + assertThat(c2.hasNext()).isFalse(); } @ParameterizedTest(name = "{index}")