From b5fd54477629b50e5ccdc26d1db3b35a3b2e18a1 Mon Sep 17 00:00:00 2001 From: yehzu Date: Tue, 7 Jan 2020 21:06:07 +0800 Subject: move root package to "ai.vespa" --- client/src/main/java/ai/vespa/client/dsl/A.java | 67 +++ .../main/java/ai/vespa/client/dsl/Aggregator.java | 22 + .../main/java/ai/vespa/client/dsl/Annotation.java | 29 + .../main/java/ai/vespa/client/dsl/DotProduct.java | 50 ++ .../main/java/ai/vespa/client/dsl/EndQuery.java | 114 ++++ .../src/main/java/ai/vespa/client/dsl/Field.java | 274 ++++++++++ .../main/java/ai/vespa/client/dsl/FixedQuery.java | 414 +++++++++++++++ client/src/main/java/ai/vespa/client/dsl/G.java | 46 ++ .../src/main/java/ai/vespa/client/dsl/Group.java | 24 + .../java/ai/vespa/client/dsl/GroupOperation.java | 34 ++ .../src/main/java/ai/vespa/client/dsl/IGroup.java | 10 + .../java/ai/vespa/client/dsl/IGroupOperation.java | 10 + .../main/java/ai/vespa/client/dsl/NonEmpty.java | 46 ++ client/src/main/java/ai/vespa/client/dsl/Q.java | 73 +++ .../src/main/java/ai/vespa/client/dsl/Query.java | 227 ++++++++ .../main/java/ai/vespa/client/dsl/QueryChain.java | 52 ++ client/src/main/java/ai/vespa/client/dsl/Rank.java | 50 ++ .../src/main/java/ai/vespa/client/dsl/Select.java | 38 ++ .../src/main/java/ai/vespa/client/dsl/Sources.java | 64 +++ .../main/java/ai/vespa/client/dsl/UserInput.java | 80 +++ client/src/main/java/ai/vespa/client/dsl/Wand.java | 71 +++ .../src/main/java/ai/vespa/client/dsl/WeakAnd.java | 63 +++ .../main/java/ai/vespa/client/dsl/WeightedSet.java | 50 ++ .../main/java/com/yahoo/vespa/client/dsl/A.java | 67 --- .../com/yahoo/vespa/client/dsl/Aggregator.java | 22 - .../com/yahoo/vespa/client/dsl/Annotation.java | 29 - .../com/yahoo/vespa/client/dsl/DotProduct.java | 50 -- .../java/com/yahoo/vespa/client/dsl/EndQuery.java | 114 ---- .../java/com/yahoo/vespa/client/dsl/Field.java | 274 ---------- .../com/yahoo/vespa/client/dsl/FixedQuery.java | 414 --------------- .../main/java/com/yahoo/vespa/client/dsl/G.java | 46 -- .../java/com/yahoo/vespa/client/dsl/Group.java | 24 - .../com/yahoo/vespa/client/dsl/GroupOperation.java | 34 -- .../java/com/yahoo/vespa/client/dsl/IGroup.java | 10 - .../yahoo/vespa/client/dsl/IGroupOperation.java | 10 - .../java/com/yahoo/vespa/client/dsl/NonEmpty.java | 46 -- .../main/java/com/yahoo/vespa/client/dsl/Q.java | 73 --- .../java/com/yahoo/vespa/client/dsl/Query.java | 227 -------- .../com/yahoo/vespa/client/dsl/QueryChain.java | 52 -- .../main/java/com/yahoo/vespa/client/dsl/Rank.java | 50 -- .../java/com/yahoo/vespa/client/dsl/Select.java | 38 -- .../java/com/yahoo/vespa/client/dsl/Sources.java | 64 --- .../java/com/yahoo/vespa/client/dsl/UserInput.java | 80 --- .../main/java/com/yahoo/vespa/client/dsl/Wand.java | 71 --- .../java/com/yahoo/vespa/client/dsl/WeakAnd.java | 63 --- .../com/yahoo/vespa/client/dsl/WeightedSet.java | 50 -- .../test/groovy/ai/vespa/client/dsl/QTest.groovy | 583 +++++++++++++++++++++ .../groovy/com/yahoo/vespa/client/dsl/QTest.groovy | 583 --------------------- 48 files changed, 2491 insertions(+), 2491 deletions(-) create mode 100644 client/src/main/java/ai/vespa/client/dsl/A.java create mode 100644 client/src/main/java/ai/vespa/client/dsl/Aggregator.java create mode 100644 client/src/main/java/ai/vespa/client/dsl/Annotation.java create mode 100644 client/src/main/java/ai/vespa/client/dsl/DotProduct.java create mode 100644 client/src/main/java/ai/vespa/client/dsl/EndQuery.java create mode 100644 client/src/main/java/ai/vespa/client/dsl/Field.java create mode 100644 client/src/main/java/ai/vespa/client/dsl/FixedQuery.java create mode 100644 client/src/main/java/ai/vespa/client/dsl/G.java create mode 100644 client/src/main/java/ai/vespa/client/dsl/Group.java create mode 100644 client/src/main/java/ai/vespa/client/dsl/GroupOperation.java create mode 100644 client/src/main/java/ai/vespa/client/dsl/IGroup.java create mode 100644 client/src/main/java/ai/vespa/client/dsl/IGroupOperation.java create mode 100644 client/src/main/java/ai/vespa/client/dsl/NonEmpty.java create mode 100644 client/src/main/java/ai/vespa/client/dsl/Q.java create mode 100644 client/src/main/java/ai/vespa/client/dsl/Query.java create mode 100644 client/src/main/java/ai/vespa/client/dsl/QueryChain.java create mode 100644 client/src/main/java/ai/vespa/client/dsl/Rank.java create mode 100644 client/src/main/java/ai/vespa/client/dsl/Select.java create mode 100644 client/src/main/java/ai/vespa/client/dsl/Sources.java create mode 100644 client/src/main/java/ai/vespa/client/dsl/UserInput.java create mode 100644 client/src/main/java/ai/vespa/client/dsl/Wand.java create mode 100644 client/src/main/java/ai/vespa/client/dsl/WeakAnd.java create mode 100644 client/src/main/java/ai/vespa/client/dsl/WeightedSet.java delete mode 100644 client/src/main/java/com/yahoo/vespa/client/dsl/A.java delete mode 100644 client/src/main/java/com/yahoo/vespa/client/dsl/Aggregator.java delete mode 100644 client/src/main/java/com/yahoo/vespa/client/dsl/Annotation.java delete mode 100644 client/src/main/java/com/yahoo/vespa/client/dsl/DotProduct.java delete mode 100644 client/src/main/java/com/yahoo/vespa/client/dsl/EndQuery.java delete mode 100644 client/src/main/java/com/yahoo/vespa/client/dsl/Field.java delete mode 100644 client/src/main/java/com/yahoo/vespa/client/dsl/FixedQuery.java delete mode 100644 client/src/main/java/com/yahoo/vespa/client/dsl/G.java delete mode 100644 client/src/main/java/com/yahoo/vespa/client/dsl/Group.java delete mode 100644 client/src/main/java/com/yahoo/vespa/client/dsl/GroupOperation.java delete mode 100644 client/src/main/java/com/yahoo/vespa/client/dsl/IGroup.java delete mode 100644 client/src/main/java/com/yahoo/vespa/client/dsl/IGroupOperation.java delete mode 100644 client/src/main/java/com/yahoo/vespa/client/dsl/NonEmpty.java delete mode 100644 client/src/main/java/com/yahoo/vespa/client/dsl/Q.java delete mode 100644 client/src/main/java/com/yahoo/vespa/client/dsl/Query.java delete mode 100644 client/src/main/java/com/yahoo/vespa/client/dsl/QueryChain.java delete mode 100644 client/src/main/java/com/yahoo/vespa/client/dsl/Rank.java delete mode 100644 client/src/main/java/com/yahoo/vespa/client/dsl/Select.java delete mode 100644 client/src/main/java/com/yahoo/vespa/client/dsl/Sources.java delete mode 100644 client/src/main/java/com/yahoo/vespa/client/dsl/UserInput.java delete mode 100644 client/src/main/java/com/yahoo/vespa/client/dsl/Wand.java delete mode 100644 client/src/main/java/com/yahoo/vespa/client/dsl/WeakAnd.java delete mode 100644 client/src/main/java/com/yahoo/vespa/client/dsl/WeightedSet.java create mode 100644 client/src/test/groovy/ai/vespa/client/dsl/QTest.groovy delete mode 100644 client/src/test/groovy/com/yahoo/vespa/client/dsl/QTest.groovy (limited to 'client/src') diff --git a/client/src/main/java/ai/vespa/client/dsl/A.java b/client/src/main/java/ai/vespa/client/dsl/A.java new file mode 100644 index 00000000000..b0b801c6015 --- /dev/null +++ b/client/src/main/java/ai/vespa/client/dsl/A.java @@ -0,0 +1,67 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.client.dsl; + +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public final class A { + + private final static Annotation EMPTY = new Annotation(); + + static public Annotation empty() { + return EMPTY; + } + + static public Annotation filter() { + return a("filter", true); + } + + static public Annotation defaultIndex(String index) { + return a("defaultIndex", index); + } + + public static Annotation a(String name, Object value) { + Map map = new HashMap<>(); + map.put(name, value); + return new Annotation(map); + } + + public static Annotation a(Map annotation) { + if (annotation.isEmpty()) { + return empty(); + } + return new Annotation(annotation); + } + + public static Annotation a(String n1, Object v1, String n2, Object v2) { + return a(new Object[][]{{n1, v1}, {n2, v2}}); + } + + public static Annotation a(String n1, Object v1, String n2, Object v2, String n3, Object v3) { + return a(new Object[][]{{n1, v1}, {n2, v2}, {n3, v3}}); + } + + public static Annotation a(String n1, Object v1, String n2, Object v2, String n3, Object v3, String n4, Object v4) { + return a(new Object[][]{{n1, v1}, {n2, v2}, {n3, v3}, {n4, v4}}); + } + + public static Annotation a(String n1, Object v1, String n2, Object v2, String n3, Object v3, String n4, Object v4, + String n5, Object v5) { + return a(new Object[][]{{n1, v1}, {n2, v2}, {n3, v3}, {n4, v4}, {n5, v5}}); + } + + public static Annotation a(String n1, Object v1, String n2, Object v2, String n3, Object v3, String n4, Object v4, + String n5, Object v5, String n6, Object v6) { + return a(new Object[][]{{n1, v1}, {n2, v2}, {n3, v3}, {n4, v4}, {n5, v5}, {n6, v6}}); + } + + private static Annotation a(Object[][] kvpairs) { + return new Annotation(Stream.of(kvpairs).collect(Collectors.toMap(o -> o[0].toString(), o -> o[1]))); + } + + static boolean hasAnnotation(Annotation annotation) { + return annotation != null && !EMPTY.equals(annotation); + } +} diff --git a/client/src/main/java/ai/vespa/client/dsl/Aggregator.java b/client/src/main/java/ai/vespa/client/dsl/Aggregator.java new file mode 100644 index 00000000000..c746eca0ac5 --- /dev/null +++ b/client/src/main/java/ai/vespa/client/dsl/Aggregator.java @@ -0,0 +1,22 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.client.dsl; + +public class Aggregator { + + String type; + Object value = ""; + + Aggregator(String type) { + this.type = type; + } + + Aggregator(String type, Object value) { + this.type = type; + this.value = value; + } + + @Override + public String toString() { + return String.format("%s(%s)", type, value); + } +} diff --git a/client/src/main/java/ai/vespa/client/dsl/Annotation.java b/client/src/main/java/ai/vespa/client/dsl/Annotation.java new file mode 100644 index 00000000000..906f2abcca0 --- /dev/null +++ b/client/src/main/java/ai/vespa/client/dsl/Annotation.java @@ -0,0 +1,29 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.client.dsl; + +import java.util.Collections; +import java.util.Map; + +public class Annotation { + + Map annotations = Collections.emptyMap(); + + Annotation() { + } + + Annotation(Map annotations) { + this.annotations = annotations; + } + + public Annotation append(Annotation a) { + this.annotations.putAll(a.annotations); + return this; + } + + @Override + public String toString() { + return annotations == null || annotations.isEmpty() + ? "" + : Q.gson.toJson(annotations); + } +} diff --git a/client/src/main/java/ai/vespa/client/dsl/DotProduct.java b/client/src/main/java/ai/vespa/client/dsl/DotProduct.java new file mode 100644 index 00000000000..a4b3a45060f --- /dev/null +++ b/client/src/main/java/ai/vespa/client/dsl/DotProduct.java @@ -0,0 +1,50 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.client.dsl; + +import java.util.Map; + +public class DotProduct extends QueryChain { + + private String fieldName; + private Map weightedSet; + + DotProduct(String fieldName, Map weightedSet) { + this.fieldName = fieldName; + this.weightedSet = weightedSet; + this.nonEmpty = true; + } + + @Override + public Select getSelect() { + return sources.select; + } + + @Override + public String toString() { + return "dotProduct(" + fieldName + ", " + Q.gson.toJson(weightedSet) + ")"; + } + + @Override + boolean hasPositiveSearchField(String fieldName) { + // TODO: implementation + return false; + } + + @Override + boolean hasPositiveSearchField(String fieldName, Object value) { + // TODO: implementation + return false; + } + + @Override + boolean hasNegativeSearchField(String fieldName) { + // TODO: implementation + return false; + } + + @Override + boolean hasNegativeSearchField(String fieldName, Object value) { + // TODO: implementation + return false; + } +} diff --git a/client/src/main/java/ai/vespa/client/dsl/EndQuery.java b/client/src/main/java/ai/vespa/client/dsl/EndQuery.java new file mode 100644 index 00000000000..112335e7c32 --- /dev/null +++ b/client/src/main/java/ai/vespa/client/dsl/EndQuery.java @@ -0,0 +1,114 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.client.dsl; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class EndQuery { + + QueryChain queryChain; + Map map = new LinkedHashMap<>(); + List order = new ArrayList<>(); + String groupQueryStr; + + EndQuery(QueryChain queryChain) { + this.queryChain = queryChain; + + // make sure the order of limit, offset and timeout + this.map.put("limit", null); + this.map.put("offset", null); + this.map.put("timeout", null); + } + + EndQuery setTimeout(Integer timeout) { + map.put("timeout", timeout); + return this; + } + + EndQuery setOffset(int offset) { + map.put("offset", offset); + return this; + } + + EndQuery setLimit(int limit) { + map.put("limit", limit); + return this; + } + + public EndQuery offset(int offset) { + return this.setOffset(offset); + } + + public EndQuery timeout(int timeout) { + return this.setTimeout(timeout); + } + + public EndQuery limit(int limit) { + return this.setLimit(limit); + } + + public FixedQuery semicolon() { + return new FixedQuery(this); + } + + public EndQuery group(Group group) { + this.groupQueryStr = group.toString(); + return this; + } + + public EndQuery group(String groupQueryStr) { + this.groupQueryStr = groupQueryStr; + return this; + } + + public EndQuery orderByAsc(Annotation annotation, String fieldName) { + order.add(new Object[]{annotation, fieldName, "asc"}); + return this; + } + + public EndQuery orderByAsc(String fieldName) { + order.add(new Object[]{A.empty(), fieldName, "asc"}); + return this; + } + + public EndQuery orderByDesc(Annotation annotation, String fieldName) { + order.add(new Object[]{annotation, fieldName, "desc"}); + return this; + } + + public EndQuery orderByDesc(String fieldName) { + order.add(new Object[]{A.empty(), fieldName, "desc"}); + return this; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + String orderStr = order.stream().map(array -> A.empty().equals(array[0]) + ? String.format("%s %s", array[1], array[2]) + : String.format("[%s]%s %s", array[0], array[1], array[2])) + .collect(Collectors.joining(", ")); + + String others = map.entrySet().stream() + .filter(entry -> entry.getValue() != null) + .map(entry -> entry.getKey() + " " + entry.getValue()) + .collect(Collectors.joining(" ")); + + if (orderStr.isEmpty()) { + sb.append(others.isEmpty() ? "" : others); + } else if (others.isEmpty()) { + sb.append("order by ").append(orderStr); + } else { + sb.append("order by ").append(orderStr).append(", ").append(others); + } + + if (groupQueryStr != null) { + sb.append("| ").append(groupQueryStr); + } + + return sb.toString(); + } +} diff --git a/client/src/main/java/ai/vespa/client/dsl/Field.java b/client/src/main/java/ai/vespa/client/dsl/Field.java new file mode 100644 index 00000000000..1a82430deb7 --- /dev/null +++ b/client/src/main/java/ai/vespa/client/dsl/Field.java @@ -0,0 +1,274 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.client.dsl; + +import org.apache.commons.text.StringEscapeUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class Field extends QueryChain { + + private String fieldName; + private List values = new ArrayList<>(); + private Annotation annotation = A.empty(); + private String relation; + + Field(Sources sources, String fieldName) { + this.sources = sources; + this.fieldName = fieldName; + } + + Field(Query query, String fieldName) { + this.query = query; + this.fieldName = fieldName; + } + + public Query contains(String value) { + return contains(A.empty(), value); + } + + public Query contains(Annotation annotation, String value) { + return common("contains", annotation, value); + } + + public Query containsPhrase(String value, String... others) { + return common("phrase", annotation, value, others); + } + + public Query containsPhrase(List values) { + if (values.isEmpty()) { + throw new IllegalArgumentException("value of \"contains phrase\" should not be empty"); + } + + return common("phrase", annotation, values); + } + + public Query containsNear(String value, String... others) { + return common("near", annotation, value, others); + } + + public Query containsNear(List values) { + if (values.isEmpty()) { + throw new IllegalArgumentException("value of \"contains near\" should not be empty"); + } + + return common("near", annotation, values); + } + + public Query containsNear(Annotation annotation, String value, String... others) { + return common("near", annotation, value, others); + } + + public Query containsNear(Annotation annotation, List values) { + if (values.isEmpty()) { + throw new IllegalArgumentException("value of \"contains near\" should not be empty"); + } + + return common("near", annotation, values); + } + + public Query containsOnear(String value, String... others) { + return common("onear", annotation, value, others); + } + + public Query containsOnear(List values) { + if (values.isEmpty()) { + throw new IllegalArgumentException("value of \"contains onear\" should not be empty"); + } + + return common("onear", annotation, values); + } + + public Query containsOnear(Annotation annotation, String value, String... others) { + return common("onear", annotation, value, others); + } + + public Query containsOnear(Annotation annotation, List values) { + if (values.isEmpty()) { + throw new IllegalArgumentException("value of \"contains onear\" should not be empty"); + } + + return common("onear", annotation, values); + } + + public Query containsSameElement(Query andQuery) { + return common("sameElement", annotation, andQuery); + } + + public Query containsEquiv(String value, String... others) { + return containsEquiv(Stream.concat(Stream.of(value), Stream.of(others)).collect(Collectors.toList())); + } + + public Query containsEquiv(List values) { + if (values.isEmpty()) { + throw new IllegalArgumentException("value of \"contains equiv\" should not be empty"); + } else if (values.size() == 1) { + // Vespa does not support one element equiv syntax, use contains instead + return contains(values.get(0)); + } else { + return common("equiv", annotation, values); + } + } + + public Query containsUri(String value) { + return common("uri", annotation, value) ; + } + + public Query containsUri(Annotation annotation, String value) { + return common("uri", annotation, value) ; + } + + public Query matches(String str) { + return common("matches", annotation, str); + } + + public Query eq(int t) { + return common("=", annotation, t); + } + + public Query ge(int t) { + return common(">=", annotation, t); + } + + public Query gt(int t) { + return common(">", annotation, t); + } + + public Query le(int t) { + return common("<=", annotation, t); + } + + public Query lt(int t) { + return common("<", annotation, t); + } + + public Query inRange(int l, int m) { + return common("range", annotation, l, new Integer[]{m}); + } + + public Query eq(long t) { + return common("=", annotation, t); + } + + public Query ge(long t) { + return common(">=", annotation, t); + } + + public Query gt(long t) { + return common(">", annotation, t); + } + + public Query le(long t) { + return common("<=", annotation, t); + } + + public Query lt(long t) { + return common("<", annotation, t); + } + + public Query inRange(long l, long m) { + return common("range", annotation, l, new Long[]{m}); + } + + + public Query isTrue() { + return common("=", annotation, true); + } + + public Query isFalse() { + return common("=", annotation, false); + } + + private Query common(String relation, Annotation annotation, Object value) { + return common(relation, annotation, value, values.toArray()); + } + + private Query common(String relation, Annotation annotation, String value) { + Object v = "\"" + StringEscapeUtils.escapeJava(value) + "\""; + return common(relation, annotation, v, values.toArray()); + } + + private Query common(String relation, Annotation annotation, List values) { + return common(relation, annotation, values.get(0), values.subList(1, values.size()).toArray(new String[0])); + } + + private Query common(String relation, Annotation annotation, String value, String[] others) { + Object v = "\"" + StringEscapeUtils.escapeJava(value) + "\""; + Object[] o = Stream.of(others).map(s -> "\"" + StringEscapeUtils.escapeJava(s) + "\"").toArray(); + return common(relation, annotation, v, o); + } + + private Query common(String relation, Annotation annotation, Object value, Object[] others) { + this.annotation = annotation; + this.relation = relation; + this.values = Stream.concat(Stream.of(value), Stream.of(others)).collect(Collectors.toList()); + this.nonEmpty = true; + return query != null + ? query + : new Query(sources, this); + } + + @Override + public String toString() { + boolean hasAnnotation = !A.empty().equals(annotation); + String valuesStr; + switch (relation) { + case "range": + valuesStr = values.stream() + .map(i -> i instanceof Long ? i.toString() + "L" : i.toString()) + .collect(Collectors.joining(", ")); + + return hasAnnotation + ? String.format("([%s]range(%s, %s))", annotation, fieldName, valuesStr) + : String.format("range(%s, %s)", fieldName, valuesStr); + case "near": + case "onear": + case "phrase": + case "equiv": + case "uri": + valuesStr = values.stream().map(Object::toString).collect(Collectors.joining(", ")); + return hasAnnotation + ? String.format("%s contains ([%s]%s(%s))", fieldName, annotation, relation, valuesStr) + : String.format("%s contains %s(%s)", fieldName, relation, valuesStr); + case "sameElement": + return String.format("%s contains %s(%s)", fieldName, relation, + ((Query) values.get(0)).toCommaSeparatedAndQueries()); + default: + Object value = values.get(0); + valuesStr = value instanceof Long ? value.toString() + "L" : value.toString(); + return hasAnnotation + ? String.format("%s %s ([%s]%s)", fieldName, relation, annotation, valuesStr) + : String.format("%s %s %s", fieldName, relation, valuesStr); + } + } + + @Override + boolean hasPositiveSearchField(String fieldName) { + return !"andnot".equals(this.op) && this.fieldName.equals(fieldName); + } + + @Override + boolean hasPositiveSearchField(String fieldName, Object value) { + return hasPositiveSearchField(fieldName) && valuesContains(value); + } + + @Override + boolean hasNegativeSearchField(String fieldName) { + return "andnot".equals(this.op) && this.fieldName.equals(fieldName); + } + + @Override + boolean hasNegativeSearchField(String fieldName, Object value) { + return hasNegativeSearchField(fieldName) && valuesContains(value); + } + + boolean valuesContains(Object value) { + if (value instanceof String) { + value = "\"" + value + "\""; + } + return values.contains(value); + } + +} diff --git a/client/src/main/java/ai/vespa/client/dsl/FixedQuery.java b/client/src/main/java/ai/vespa/client/dsl/FixedQuery.java new file mode 100644 index 00000000000..7b9872cbe11 --- /dev/null +++ b/client/src/main/java/ai/vespa/client/dsl/FixedQuery.java @@ -0,0 +1,414 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.client.dsl; + +import com.google.gson.internal.LinkedHashTreeMap; + +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +public class FixedQuery { + + final EndQuery endQuery; + Map others = new HashMap<>(); + Map queryMap; + + + public FixedQuery(EndQuery endQuery) { + this.endQuery = endQuery; + } + + public FixedQuery hits(int hits) { + this.param("hits", hits); + return this; + } + + public FixedQuery offset(int offset) { + this.param("offset", offset); + return this; + } + + public FixedQuery queryProfile(String queryProfile) { + this.param("queryProfile", queryProfile); + return this; + } + + public FixedQuery groupingSessionCache(boolean enable) { + this.param("groupingSessionCache", enable); + return this; + } + + public FixedQuery searchChain(String searchChain) { + this.param("searchChain", searchChain); + return this; + } + + public FixedQuery timeout(int second) { + this.param("timeout", second); + return this; + } + + public FixedQuery timeoutInMs(int milli) { + this.param("timeout", milli + "ms"); + return this; + } + + public FixedQuery tracelevel(int level) { + this.param("tracelevel", level); + return this; + } + + public FixedQuery traceTimestamps(boolean enable) { + this.param("trace.timestamps", enable); + return this; + } + + public FixedQuery defaultIndex(String indexName) { + this.param("default-index", indexName); + return this; + } + + public FixedQuery encoding(String encoding) { + this.param("encoding", encoding); + return this; + } + + public FixedQuery filter(String filter) { + this.param("filter", filter); + return this; + } + + public FixedQuery locale(String locale) { + this.param("locale", locale); + return this; + } + + public FixedQuery language(String language) { + this.param("language", language); + return this; + } + + @Deprecated + public FixedQuery query(String query) { + this.param("query", query); + return this; + } + + public FixedQuery restrict(String commaDelimitedDocTypeNames) { + this.param("restrict", commaDelimitedDocTypeNames); + return this; + } + + public FixedQuery path(String searchPath) { + this.param("path", searchPath); + return this; + } + + public FixedQuery sources(String commaDelimitedSourceNames) { + this.param("sources", commaDelimitedSourceNames); + return this; + } + + public FixedQuery type(String type) { + // web, all, any, phrase, yql, adv (deprecated) + this.param("type", type); + return this; + } + + public FixedQuery location(String location) { + this.param("location", location); + return this; + } + + public FixedQuery rankfeature(String featureName, String featureValue) { + this.param("rankfeature." + featureName, featureValue); + return this; + } + + public FixedQuery rankfeatures(boolean enable) { + this.param("rankfeatures", enable); + return this; + } + + public FixedQuery ranking(String rankProfileName) { + this.param("ranking", rankProfileName); + return this; + } + + public FixedQuery rankproperty(String propertyName, String propertyValue) { + this.param("rankproperty." + propertyName, propertyValue); + return this; + } + + public FixedQuery rankingSoftTimeout(boolean enable) { + this.param("ranking.softtimeout.enable", enable); + return this; + } + + public FixedQuery rankingSoftTimeout(boolean enable, double factor) { + this.param("ranking.softtimeout.enable", enable); + this.param("ranking.softtimeout.factor", factor); + return this; + } + + public FixedQuery sorting(String sorting) { + this.param("sorting", sorting); + return this; + } + + public FixedQuery rankingFreshness(String freshness) { + this.param("ranking.freshness", freshness); + return this; + } + + public FixedQuery rankingQueryCache(boolean enable) { + this.param("ranking.queryCache", enable); + return this; + } + + public FixedQuery bolding(boolean enable) { + this.param("bolding", enable); + return this; + } + + public FixedQuery format(String format) { + this.param("format", format); + return this; + } + + public FixedQuery summary(String summaryClass) { + this.param("summary", summaryClass); + return this; + } + + public FixedQuery presentationTemplate(String template) { + this.param("presentation.template", template); + return this; + } + + public FixedQuery presentationTiming(boolean enable) { + this.param("presentation.timing", enable); + return this; + } + + public FixedQuery select(String groupSyntax) { + this.param("select", groupSyntax); + return this; + } + + public FixedQuery select(Group group) { + this.param("select", group.toString()); + return this; + } + + public FixedQuery collapseField(String summaryFieldName) { + this.param("collapsefield", summaryFieldName); + return this; + } + + public FixedQuery collapseSummary(String summaryClass) { + this.param("collapse.summary", summaryClass); + return this; + } + + public FixedQuery collapseSize(int size) { + this.param("collapsesize", size); + return this; + } + + public FixedQuery posLatLong(String vespaLatLong) { + this.param("pos.ll", vespaLatLong); + return this; + } + + public FixedQuery posLatLong(double lat, double lon) { + String latlong = toVespaLatLong(lat, lon); + return posLatLong(latlong); + } + + private String toVespaLatLong(double lat, double lon) { + double absLat = Math.abs(lat); + double absLon = Math.abs(lon); + if (absLat > 90 || absLon > 180) { + throw new IllegalArgumentException(String.format("invalid lat long value, lat=%f, long=%f", lat, lon)); + } + + return String.format("%s%f;%s%f", + lat > 0 ? "N" : "S", absLat, + lon > 0 ? "E" : "W", absLon); + } + + public FixedQuery posRadiusInKilometer(int km) { + this.param("pos.radius", km + "km"); + return this; + } + + public FixedQuery posRadiusInMeter(int m) { + this.param("pos.radius", m + "m"); + return this; + } + + public FixedQuery posRadiusInMile(int mi) { + this.param("pos.radius", mi + "mi"); + return this; + } + + public FixedQuery posBoundingBox(double n, double s, double e, double w) { + this.param("pos.bb", String.format("n=%f,s=%f,e=%f,w=%f", n, s, e, w)); + return this; + } + + public FixedQuery streamingUserId(BigDecimal id) { + this.param("streaming.userid", id); + return this; + } + + public FixedQuery streamingGroupName(String groupName) { + this.param("streaming.groupname", groupName); + return this; + } + + public FixedQuery streamingSelection(String selection) { + this.param("streaming.selection", selection); + return this; + } + + public FixedQuery streamingPriority(String priority) { + this.param("streaming.priority", priority); + return this; + } + + public FixedQuery streamingMaxBucketsPerVisitor(int max) { + this.param("streaming.maxbucketspervisitor", max); + return this; + } + + public FixedQuery rulesOff(boolean bool) { + this.param("rules.off", bool); + return this; + } + + public FixedQuery rulesRulebase(String rulebase) { + this.param("rules.rulebase", rulebase); + return this; + } + + public FixedQuery recall(String recall) { + this.param("recall", recall); + return this; + } + + public FixedQuery user(String user) { + this.param("user", user); + return this; + } + + public FixedQuery hitCountEstimate(boolean enable) { + this.param("hitcountestimate", enable); + return this; + } + + public FixedQuery metricsIgnore(boolean bool) { + this.param("metrics.ignore", bool); + return this; + } + + public FixedQuery param(String key, String value) { + others.put(key, value); + return this; + } + + private FixedQuery param(String key, Object value) { + this.param(key, value.toString()); + return this; + } + + public FixedQuery params(Map params) { + others.putAll(params); + return this; + } + + public Map buildQueryMap() { + if (queryMap != null) { + return queryMap; + } + + assignIndex(); + + StringBuilder sb = new StringBuilder(); + sb.append("select ") + .append(endQuery.queryChain.getSelect()) + .append(" from ") + .append(endQuery.queryChain.getSources()) + .append(" where ") + .append(endQuery.queryChain); + + if (!"".equals(endQuery.toString())) { + sb.append(' ').append(endQuery); + } + sb.append(";"); + + queryMap = new LinkedHashTreeMap<>(); // for the order + queryMap.put("yql", sb.toString()); + queryMap.putAll(others); + queryMap.putAll(getUserInputs()); + return queryMap; + } + + + public String build() { + return buildQueryMap().entrySet().stream().map(entry -> entry.getKey() + "=" + entry.getValue()) + .collect(Collectors.joining("&")); + } + + private void assignIndex() { + assignIndex(endQuery.queryChain.getQuery(), new AtomicInteger()); + } + + private void assignIndex(QueryChain q, AtomicInteger ai) { + q.setIndex(ai.incrementAndGet()); + if (q instanceof Query) { + assignIndex((Query) q, ai); + } + } + + private void assignIndex(Query q, AtomicInteger ai) { + q.queries.stream() + .filter(QueryChain::nonEmpty) + .forEach(qu -> assignIndex(qu, ai)); + } + + private Map getUserInputs() { + return getUserInputs(endQuery.queryChain.getQuery()); + } + + private Map getUserInputs(Query q) { + Map param = new HashMap<>(); + q.queries.forEach(qu -> { + if (qu instanceof UserInput) { + param.putAll(((UserInput) qu).getParam()); + } else if (qu instanceof Query) { + param.putAll(getUserInputs((Query) qu)); + } + }); + return param; + } + + public boolean hasPositiveSearchField(String fieldName) { + return endQuery.queryChain.hasPositiveSearchField(fieldName); + } + + public boolean hasPositiveSearchField(String fieldName, Object value) { + return endQuery.queryChain.hasPositiveSearchField(fieldName, value); + } + + public boolean hasNegativeSearchField(String fieldName) { + return endQuery.queryChain.hasNegativeSearchField(fieldName); + } + + public boolean hasNegativeSearchField(String fieldName, Object value) { + return endQuery.queryChain.hasNegativeSearchField(fieldName, value); + } +} diff --git a/client/src/main/java/ai/vespa/client/dsl/G.java b/client/src/main/java/ai/vespa/client/dsl/G.java new file mode 100644 index 00000000000..f93cbe249b8 --- /dev/null +++ b/client/src/main/java/ai/vespa/client/dsl/G.java @@ -0,0 +1,46 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.client.dsl; + + +public final class G { + + public static Group all(IGroupOperation... ops) { + return new Group("all", ops); + } + + public static Group each(IGroupOperation... ops) { + return new Group("each", ops); + } + + public static GroupOperation group(String expr) { + return new GroupOperation("group", expr); + } + + public static GroupOperation maxRtn(int max) { + return new GroupOperation("max", max); + } + + public static GroupOperation order(String expr) { + return new GroupOperation("order", expr); + } + + public static GroupOperation output(Aggregator... aggrs) { + return new GroupOperation("output", aggrs); + } + + public static Aggregator max(int max) { + return new Aggregator("max", max); + } + + public static Aggregator summary() { + return new Aggregator("summary"); + } + + public static Aggregator count() { + return new Aggregator("count"); + } + + public static Aggregator summary(String summaryClass) { + return new Aggregator("summary", summaryClass); + } +} diff --git a/client/src/main/java/ai/vespa/client/dsl/Group.java b/client/src/main/java/ai/vespa/client/dsl/Group.java new file mode 100644 index 00000000000..63e3d6d4410 --- /dev/null +++ b/client/src/main/java/ai/vespa/client/dsl/Group.java @@ -0,0 +1,24 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.client.dsl; + +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class Group implements IGroup, IGroupOperation { + + String type; + IGroupOperation[] operations; + + Group(String type, IGroupOperation[] operations) { + this.type = type; + this.operations = operations; + } + + @Override + public String toString() { + return String.format("%s(%s)", + type, + Stream.of(operations).map(Objects::toString).collect(Collectors.joining(" "))); + } +} diff --git a/client/src/main/java/ai/vespa/client/dsl/GroupOperation.java b/client/src/main/java/ai/vespa/client/dsl/GroupOperation.java new file mode 100644 index 00000000000..efd01ebcdec --- /dev/null +++ b/client/src/main/java/ai/vespa/client/dsl/GroupOperation.java @@ -0,0 +1,34 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.client.dsl; + +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class GroupOperation implements IGroupOperation { + + String type; + Object value; + Aggregator[] aggregators; + + public GroupOperation(String type, Object value) { + this.type = type; + this.value = value; + } + + public GroupOperation(String type, Aggregator[] aggregators) { + this.type = type; + this.aggregators = aggregators; + } + + @Override + public String toString() { + if (value != null) { + return String.format("%s(%s)", type, value); + } + + return String.format("%s(%s)", + type, + Stream.of(aggregators).map(Objects::toString).collect(Collectors.joining(" "))); + } +} diff --git a/client/src/main/java/ai/vespa/client/dsl/IGroup.java b/client/src/main/java/ai/vespa/client/dsl/IGroup.java new file mode 100644 index 00000000000..a4f38d539f0 --- /dev/null +++ b/client/src/main/java/ai/vespa/client/dsl/IGroup.java @@ -0,0 +1,10 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.client.dsl; + +/* + * interface for group syntax + */ + +public interface IGroup { + +} diff --git a/client/src/main/java/ai/vespa/client/dsl/IGroupOperation.java b/client/src/main/java/ai/vespa/client/dsl/IGroupOperation.java new file mode 100644 index 00000000000..620c964e266 --- /dev/null +++ b/client/src/main/java/ai/vespa/client/dsl/IGroupOperation.java @@ -0,0 +1,10 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.client.dsl; + +/* + * interface for group operation + */ + +public interface IGroupOperation { + +} diff --git a/client/src/main/java/ai/vespa/client/dsl/NonEmpty.java b/client/src/main/java/ai/vespa/client/dsl/NonEmpty.java new file mode 100644 index 00000000000..f3f677b77d2 --- /dev/null +++ b/client/src/main/java/ai/vespa/client/dsl/NonEmpty.java @@ -0,0 +1,46 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.client.dsl; + +public class NonEmpty extends QueryChain { + + private Query query; + + NonEmpty(Query query) { + this.query = query; + this.nonEmpty = true; + } + + @Override + public Select getSelect() { + return sources.select; + } + + @Override + public String toString() { + return String.format("nonEmpty(%s)", query); + } + + @Override + boolean hasPositiveSearchField(String fieldName) { + // TODO: implementation + return false; + } + + @Override + boolean hasPositiveSearchField(String fieldName, Object value) { + // TODO: implementation + return false; + } + + @Override + boolean hasNegativeSearchField(String fieldName) { + // TODO: implementation + return false; + } + + @Override + boolean hasNegativeSearchField(String fieldName, Object value) { + // TODO: implementation + return false; + } +} diff --git a/client/src/main/java/ai/vespa/client/dsl/Q.java b/client/src/main/java/ai/vespa/client/dsl/Q.java new file mode 100644 index 00000000000..c6aedeca12e --- /dev/null +++ b/client/src/main/java/ai/vespa/client/dsl/Q.java @@ -0,0 +1,73 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.client.dsl; + +import com.google.gson.Gson; + +import java.util.List; +import java.util.Map; + +public final class Q { + + static Gson gson = new Gson(); + private static Sources SELECT_ALL_FROM_SOURCES_ALL = new Sources(new Select("*"), "*"); + + public static Select select(String fieldName) { + return new Select(fieldName); + } + + public static Select select(String fieldName, String... others) { + return new Select(fieldName, others); + } + + public static Field p(String fieldName) { + return SELECT_ALL_FROM_SOURCES_ALL.where(fieldName); + } + + public static Query p(QueryChain query) { + return new Query(SELECT_ALL_FROM_SOURCES_ALL, query); + } + + public static Query p() { + return new Query(SELECT_ALL_FROM_SOURCES_ALL); + } + + public static Rank rank(Query query, Query... ranks) { + return new Rank(query, ranks); + } + + public static UserInput ui(String value) { + return new UserInput(value); + } + + public static UserInput ui(Annotation a, String value) { + return new UserInput(a, value); + } + + public static UserInput ui(String index, String value) { + return ui(A.defaultIndex(index), value); + } + + public static DotProduct dotPdt(String field, Map weightedSet) { + return new DotProduct(field, weightedSet); + } + + public static WeightedSet wtdSet(String field, Map weightedSet) { + return new WeightedSet(field, weightedSet); + } + + public static NonEmpty nonEmpty(Query query) { + return new NonEmpty(query); + } + + public static Wand wand(String field, Map weightedSet) { + return new Wand(field, weightedSet); + } + + public static Wand wand(String field, List> numericRange) { + return new Wand(field, numericRange); + } + + public static WeakAnd weakand(String field, Query query) { + return new WeakAnd(field, query); + } +} diff --git a/client/src/main/java/ai/vespa/client/dsl/Query.java b/client/src/main/java/ai/vespa/client/dsl/Query.java new file mode 100644 index 00000000000..3d10f5c429f --- /dev/null +++ b/client/src/main/java/ai/vespa/client/dsl/Query.java @@ -0,0 +1,227 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.client.dsl; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +public class Query extends QueryChain { + + Annotation annotation; + Sources sources; + + List queries = new ArrayList<>(); + + Query(Sources sources, QueryChain queryChain) { + this.sources = sources; + queries.add(queryChain); + nonEmpty = queryChain.nonEmpty; + } + + Query(Sources sources) { + this.sources = sources; + } + + String toCommaSeparatedAndQueries() { + return queries.stream() + .filter(qc -> "and".equals(qc.getOp())) + .map(Objects::toString) + .collect(Collectors.joining(", ")); + } + + @Override + public String toString() { + // TODO: need to refactor + if (!nonEmpty) { + return ""; + } + + boolean hasAnnotation = A.hasAnnotation(annotation); + + StringBuilder sb = new StringBuilder(); + + if (hasAnnotation) { + sb.append("([").append(annotation).append("]("); + } + + boolean firstQuery = true; + for (int i = 0; i < queries.size(); i++) { + QueryChain qc = queries.get(i); + if (!qc.nonEmpty) { + continue; + } + + boolean isNotAnd = "andnot".equals(qc.getOp()); + + if (!firstQuery) { + sb.append(" "); + if (isNotAnd) { + sb.append("and !"); + } else { + sb.append(qc.getOp()).append(' '); + } + } else { + firstQuery = false; + } + + boolean appendBrackets = + (qc instanceof Query && ((Query) qc).queries.size() > 1 && !A.hasAnnotation(((Query) qc).annotation)) + || isNotAnd; + if (appendBrackets) { + sb.append("("); + } + + sb.append(qc); + + if (appendBrackets) { + sb.append(")"); + } + + } + + if (hasAnnotation) { + sb.append("))"); + } + return sb.toString().trim(); + } + + public Field and(String fieldName) { + Field f = new Field(this, fieldName); + f.setOp("and"); + queries.add(f); + nonEmpty = true; + return f; + } + + public Field andnot(String fieldName) { + Field f = new Field(this, fieldName); + f.setOp("andnot"); + queries.add(f); + nonEmpty = true; + return f; + } + + public Field or(String fieldName) { + Field f = new Field(this, fieldName); + f.setOp("or"); + queries.add(f); + nonEmpty = true; + return f; + } + + public Query and(QueryChain query) { + query.setOp("and"); + queries.add(query); + nonEmpty = nonEmpty || query.nonEmpty; + return this; + } + + public Query andnot(QueryChain query) { + query.setOp("andnot"); + queries.add(query); + nonEmpty = nonEmpty || query.nonEmpty; + return this; + } + + public Query or(QueryChain query) { + query.setOp("or"); + queries.add(query); + nonEmpty = nonEmpty || query.nonEmpty; + return this; + } + + public Query annotate(Annotation annotation) { + this.annotation = annotation; + return this; + } + + public EndQuery offset(int offset) { + return new EndQuery(this).offset(offset); + } + + public EndQuery limit(int hits) { + return new EndQuery(this).limit(hits); + } + + public EndQuery timeout(int timeout) { + return new EndQuery(this).timeout(timeout); + } + + public EndQuery group(Group group) { + return new EndQuery(this).group(group); + } + + public EndQuery group(String groupStr) { + return new EndQuery(this).group(groupStr); + } + + public EndQuery orderByAsc(String fieldName) { + return new EndQuery(this).orderByAsc(fieldName); + } + + public EndQuery orderByDesc(String fieldName) { + return new EndQuery(this).orderByDesc(fieldName); + } + + public FixedQuery semicolon() { + return new FixedQuery(new EndQuery(this)); + } + + @Override + public Sources getSources() { + return sources; + } + + @Override + public void setSources(Sources sources) { + this.sources = sources; + } + + @Override + public Query getQuery() { + return this; + } + + @Override + public Select getSelect() { + return sources.select; + } + + @Override + public boolean hasPositiveSearchField(String fieldName) { + boolean hasPositiveInSubqueries = queries.stream().anyMatch(q -> q.hasPositiveSearchField(fieldName)); + boolean hasNegativeInSubqueries = queries.stream().anyMatch(q -> q.hasNegativeSearchField(fieldName)); + return nonEmpty + && ((!"andnot".equals(this.op) && hasPositiveInSubqueries) + || ("andnot".equals(this.op) && hasNegativeInSubqueries)); + } + + + @Override + public boolean hasPositiveSearchField(String fieldName, Object value) { + boolean hasPositiveInSubqueries = queries.stream().anyMatch(q -> q.hasPositiveSearchField(fieldName, value)); + boolean hasNegativeInSubqueries = queries.stream().anyMatch(q -> q.hasNegativeSearchField(fieldName, value)); + return nonEmpty && + (!"andnot".equals(this.op) && hasPositiveInSubqueries) + || ("andnot".equals(this.op) && hasNegativeInSubqueries); + } + + @Override + public boolean hasNegativeSearchField(String fieldName) { + boolean hasPositiveInSubqueries = queries.stream().anyMatch(q -> q.hasPositiveSearchField(fieldName)); + boolean hasNegativeInSubqueries = queries.stream().anyMatch(q -> q.hasNegativeSearchField(fieldName)); + return nonEmpty + && (!"andnot".equals(this.op) && hasNegativeInSubqueries) + || ("andnot".equals(this.op) && hasPositiveInSubqueries); + } + + @Override + public boolean hasNegativeSearchField(String fieldName, Object value) { + boolean hasPositiveInSubqueries = queries.stream().anyMatch(q -> q.hasPositiveSearchField(fieldName, value)); + boolean hasNegativeInSubqueries = queries.stream().anyMatch(q -> q.hasNegativeSearchField(fieldName, value)); + return nonEmpty + && (!"andnot".equals(this.op) && hasNegativeInSubqueries) + || ("andnot".equals(this.op) && hasPositiveInSubqueries); + } +} diff --git a/client/src/main/java/ai/vespa/client/dsl/QueryChain.java b/client/src/main/java/ai/vespa/client/dsl/QueryChain.java new file mode 100644 index 00000000000..195a58574a4 --- /dev/null +++ b/client/src/main/java/ai/vespa/client/dsl/QueryChain.java @@ -0,0 +1,52 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.client.dsl; + +public abstract class QueryChain { + + String op; + int index; // for distinct each query chain + Sources sources; + Select select; + Query query; + boolean nonEmpty; + + void setOp(String op) { + this.op = op; + } + + String getOp() { + return op; + } + + void setIndex(int index) { + this.index = index; + } + + Sources getSources() { + return sources; + } + + void setSources(Sources sources) { + this.sources = sources; + } + + Select getSelect() { + return select; + } + + Query getQuery() { + return query; + } + + boolean nonEmpty() { + return nonEmpty; + } + + abstract boolean hasPositiveSearchField(String fieldName); + + abstract boolean hasPositiveSearchField(String fieldName, Object value); + + abstract boolean hasNegativeSearchField(String fieldName); + + abstract boolean hasNegativeSearchField(String fieldName, Object value); +} diff --git a/client/src/main/java/ai/vespa/client/dsl/Rank.java b/client/src/main/java/ai/vespa/client/dsl/Rank.java new file mode 100644 index 00000000000..9f419a0c8a3 --- /dev/null +++ b/client/src/main/java/ai/vespa/client/dsl/Rank.java @@ -0,0 +1,50 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.client.dsl; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class Rank extends QueryChain { + + private List queries = new ArrayList<>(); + + Rank(Query query, Query... ranks) { + this.query = query; + this.nonEmpty = query.nonEmpty(); + queries.add(query); + queries.addAll(Stream.of(ranks).collect(Collectors.toList())); + } + + @Override + public Select getSelect() { + return sources.select; + } + + @Override + public String toString() { + return "rank(" + queries.stream().map(Objects::toString).collect(Collectors.joining(", ")) + ")"; + } + + @Override + boolean hasPositiveSearchField(String fieldName) { + return queries.get(0).hasPositiveSearchField(fieldName); + } + + @Override + boolean hasPositiveSearchField(String fieldName, Object value) { + return queries.get(0).hasPositiveSearchField(fieldName, value); + } + + @Override + boolean hasNegativeSearchField(String fieldName) { + return queries.get(0).hasNegativeSearchField(fieldName); + } + + @Override + boolean hasNegativeSearchField(String fieldName, Object value) { + return queries.get(0).hasNegativeSearchField(fieldName, value); + } +} diff --git a/client/src/main/java/ai/vespa/client/dsl/Select.java b/client/src/main/java/ai/vespa/client/dsl/Select.java new file mode 100644 index 00000000000..415de9fe985 --- /dev/null +++ b/client/src/main/java/ai/vespa/client/dsl/Select.java @@ -0,0 +1,38 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.client.dsl; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class Select { + + private List selectedFields = new ArrayList<>(); + + Select(String fieldName) { + selectedFields.add(fieldName); + } + + public Select(String fieldName, String... others) { + selectedFields.add(fieldName); + selectedFields.addAll(Stream.of(others).collect(Collectors.toList())); + } + + Select(List fieldNames) { + selectedFields = new ArrayList<>(fieldNames); + } + + public Sources from(String sd) { + return new Sources(this, sd); + } + + public Sources from(String sd, String... sds) { + return new Sources(this, sd, sds); + } + + @Override + public String toString() { + return selectedFields.isEmpty() ? "*" : String.join(", ", selectedFields); + } +} diff --git a/client/src/main/java/ai/vespa/client/dsl/Sources.java b/client/src/main/java/ai/vespa/client/dsl/Sources.java new file mode 100644 index 00000000000..3e21608f4ab --- /dev/null +++ b/client/src/main/java/ai/vespa/client/dsl/Sources.java @@ -0,0 +1,64 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.client.dsl; + + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class Sources { + + final Select select; + final List targetDocTypes; + + Sources(Select select, List searchDefinitions) { + this.select = select; + targetDocTypes = new ArrayList<>(searchDefinitions); + } + + Sources(Select select, String searchDefinition) { + this(select, Collections.singletonList(searchDefinition)); + } + + Sources(Select select, String searchDefinition, String... others) { + this(select, Stream.concat(Stream.of(searchDefinition), Stream.of(others)).collect(Collectors.toList())); + } + + @Override + public String toString() { + if (targetDocTypes.isEmpty() || targetDocTypes.size() == 1 && "*".equals(targetDocTypes.get(0))) { + return "sources *"; + } + + if (targetDocTypes.size() == 1) { + return targetDocTypes.get(0); + } + + return "sources " + String.join(", ", targetDocTypes); + } + + public Field where(String fieldName) { + Field f = new Field(this, fieldName); + f.setOp("and"); + return f; + } + + public Query where(QueryChain userinput) { + return whereReturnQuery(userinput); + } + + public EndQuery where(Rank rank) { + return whereReturnEndQuery(rank); + } + + private Query whereReturnQuery(QueryChain qc) { + return new Query(this, qc); + } + + private EndQuery whereReturnEndQuery(Rank rank) { + rank.setSources(this); + return new EndQuery(rank); + } +} diff --git a/client/src/main/java/ai/vespa/client/dsl/UserInput.java b/client/src/main/java/ai/vespa/client/dsl/UserInput.java new file mode 100644 index 00000000000..8c9247d17f7 --- /dev/null +++ b/client/src/main/java/ai/vespa/client/dsl/UserInput.java @@ -0,0 +1,80 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.client.dsl; + +import java.util.Collections; +import java.util.Map; +import java.util.UUID; + +public class UserInput extends QueryChain { + + Annotation annotation; // accept only defaultIndex annotation + String value; + String indexField; + String placeholder; // for generating unique param + boolean setDefaultIndex; + + UserInput(Sources sources, String value) { + this(sources, A.empty(), value); + } + + UserInput(Sources sources, Annotation annotation, String value) { + this.sources = sources; + this.annotation = annotation; + this.value = value; + this.nonEmpty = true; + + if (annotation.annotations.containsKey("defaultIndex")) { + setDefaultIndex = true; + indexField = (String) annotation.annotations.get("defaultIndex"); + } else { + indexField = UUID.randomUUID().toString().substring(0, 5); + } + } + + UserInput(String value) { + this(A.empty(), value); + } + + UserInput(Annotation annotation, String value) { + this(null, annotation, value); + } + + public void setIndex(int index) { + placeholder = setDefaultIndex + ? "_" + index + "_" + indexField + : "_" + index; + } + + @Override + public String toString() { + //([{"defaultIndex": "shpdescfree"}](userInput(@_shpdescfree_1))) + return setDefaultIndex + ? String.format("([%s]userInput(@%s))", annotation, placeholder) + : String.format("userInput(@%s)", placeholder); + } + + + Map getParam() { + return Collections.singletonMap(placeholder, value); + } + + @Override + boolean hasPositiveSearchField(String fieldName) { + return !"andnot".equals(this.op) && this.indexField.equals(fieldName); + } + + @Override + boolean hasPositiveSearchField(String fieldName, Object value) { + return hasPositiveSearchField(fieldName) && this.value.equals(value); + } + + @Override + boolean hasNegativeSearchField(String fieldName) { + return "andnot".equals(this.op) && this.indexField.equals(fieldName); + } + + @Override + boolean hasNegativeSearchField(String fieldName, Object value) { + return hasNegativeSearchField(fieldName) && this.value.equals(value); + } +} diff --git a/client/src/main/java/ai/vespa/client/dsl/Wand.java b/client/src/main/java/ai/vespa/client/dsl/Wand.java new file mode 100644 index 00000000000..e2ea2647203 --- /dev/null +++ b/client/src/main/java/ai/vespa/client/dsl/Wand.java @@ -0,0 +1,71 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.client.dsl; + +import java.util.List; +import java.util.Map; + +public class Wand extends QueryChain { + + private String fieldName; + private Annotation annotation; + private Object value; + + + Wand(String fieldName, Map weightedSet) { + this.fieldName = fieldName; + this.value = weightedSet; + this.nonEmpty = true; + } + + Wand(String fieldName, List> numeric) { + boolean invalid = numeric.stream().anyMatch(range -> range.size() != 2); + if (invalid) { + throw new IllegalArgumentException("incorrect range format"); + } + + this.fieldName = fieldName; + this.value = numeric; + this.nonEmpty = true; + } + + public Wand annotate(Annotation annotation) { + this.annotation = annotation; + return this; + } + + @Override + public Select getSelect() { + return sources.select; + } + + @Override + boolean hasPositiveSearchField(String fieldName) { + // TODO: implementation + return false; + } + + @Override + boolean hasPositiveSearchField(String fieldName, Object value) { + // TODO: implementation + return false; + } + + @Override + boolean hasNegativeSearchField(String fieldName) { + // TODO: implementation + return false; + } + + @Override + boolean hasNegativeSearchField(String fieldName, Object value) { + // TODO: implementation + return false; + } + + @Override + public String toString() { + boolean hasAnnotation = A.hasAnnotation(annotation); + String s = String.format("wand(%s, %s)", fieldName, Q.gson.toJson(value)); + return hasAnnotation ? String.format("([%s]%s)", annotation, s) : s; + } +} diff --git a/client/src/main/java/ai/vespa/client/dsl/WeakAnd.java b/client/src/main/java/ai/vespa/client/dsl/WeakAnd.java new file mode 100644 index 00000000000..fc539efea13 --- /dev/null +++ b/client/src/main/java/ai/vespa/client/dsl/WeakAnd.java @@ -0,0 +1,63 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.client.dsl; + +import java.util.stream.Collectors; + +public class WeakAnd extends QueryChain { + + private String fieldName; + private Annotation annotation; + private Query value; + + + WeakAnd(String fieldName, Query value) { + this.fieldName = fieldName; + this.value = value; + this.nonEmpty = true; + } + + public WeakAnd annotate(Annotation annotation) { + this.annotation = annotation; + return this; + } + + @Override + public Select getSelect() { + return sources.select; + } + + @Override + boolean hasPositiveSearchField(String fieldName) { + // TODO: implementation + return false; + } + + @Override + boolean hasPositiveSearchField(String fieldName, Object value) { + // TODO: implementation + return false; + } + + @Override + boolean hasNegativeSearchField(String fieldName) { + // TODO: implementation + return false; + } + + @Override + boolean hasNegativeSearchField(String fieldName, Object value) { + // TODO: implementation + return false; + } + + @Override + public String toString() { + boolean hasAnnotation = A.hasAnnotation(annotation); + String + s = + String.format("weakAnd(%s, %s)", fieldName, + value.queries.stream().map(Object::toString).collect(Collectors.joining(", "))); + return hasAnnotation ? String.format("([%s]%s)", annotation, s) : s; + } +} + diff --git a/client/src/main/java/ai/vespa/client/dsl/WeightedSet.java b/client/src/main/java/ai/vespa/client/dsl/WeightedSet.java new file mode 100644 index 00000000000..0707ae6302f --- /dev/null +++ b/client/src/main/java/ai/vespa/client/dsl/WeightedSet.java @@ -0,0 +1,50 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.client.dsl; + +import java.util.Map; + +public class WeightedSet extends QueryChain { + + private String fieldName; + private Map weightedSet; + + WeightedSet(String fieldName, Map weightedSet) { + this.fieldName = fieldName; + this.weightedSet = weightedSet; + this.nonEmpty = true; + } + + @Override + public Select getSelect() { + return sources.select; + } + + @Override + public String toString() { + return "weightedSet(" + fieldName + ", " + Q.gson.toJson(weightedSet) + ")"; + } + + @Override + boolean hasPositiveSearchField(String fieldName) { + // TODO: implementation + return false; + } + + @Override + boolean hasPositiveSearchField(String fieldName, Object value) { + // TODO: implementation + return false; + } + + @Override + boolean hasNegativeSearchField(String fieldName) { + // TODO: implementation + return false; + } + + @Override + boolean hasNegativeSearchField(String fieldName, Object value) { + // TODO: implementation + return false; + } +} diff --git a/client/src/main/java/com/yahoo/vespa/client/dsl/A.java b/client/src/main/java/com/yahoo/vespa/client/dsl/A.java deleted file mode 100644 index 86c722c98fe..00000000000 --- a/client/src/main/java/com/yahoo/vespa/client/dsl/A.java +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.client.dsl; - -import java.util.HashMap; -import java.util.Map; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -public final class A { - - private final static Annotation EMPTY = new Annotation(); - - static public Annotation empty() { - return EMPTY; - } - - static public Annotation filter() { - return a("filter", true); - } - - static public Annotation defaultIndex(String index) { - return a("defaultIndex", index); - } - - public static Annotation a(String name, Object value) { - Map map = new HashMap<>(); - map.put(name, value); - return new Annotation(map); - } - - public static Annotation a(Map annotation) { - if (annotation.isEmpty()) { - return empty(); - } - return new Annotation(annotation); - } - - public static Annotation a(String n1, Object v1, String n2, Object v2) { - return a(new Object[][]{{n1, v1}, {n2, v2}}); - } - - public static Annotation a(String n1, Object v1, String n2, Object v2, String n3, Object v3) { - return a(new Object[][]{{n1, v1}, {n2, v2}, {n3, v3}}); - } - - public static Annotation a(String n1, Object v1, String n2, Object v2, String n3, Object v3, String n4, Object v4) { - return a(new Object[][]{{n1, v1}, {n2, v2}, {n3, v3}, {n4, v4}}); - } - - public static Annotation a(String n1, Object v1, String n2, Object v2, String n3, Object v3, String n4, Object v4, - String n5, Object v5) { - return a(new Object[][]{{n1, v1}, {n2, v2}, {n3, v3}, {n4, v4}, {n5, v5}}); - } - - public static Annotation a(String n1, Object v1, String n2, Object v2, String n3, Object v3, String n4, Object v4, - String n5, Object v5, String n6, Object v6) { - return a(new Object[][]{{n1, v1}, {n2, v2}, {n3, v3}, {n4, v4}, {n5, v5}, {n6, v6}}); - } - - private static Annotation a(Object[][] kvpairs) { - return new Annotation(Stream.of(kvpairs).collect(Collectors.toMap(o -> o[0].toString(), o -> o[1]))); - } - - static boolean hasAnnotation(Annotation annotation) { - return annotation != null && !EMPTY.equals(annotation); - } -} diff --git a/client/src/main/java/com/yahoo/vespa/client/dsl/Aggregator.java b/client/src/main/java/com/yahoo/vespa/client/dsl/Aggregator.java deleted file mode 100644 index dc27c764236..00000000000 --- a/client/src/main/java/com/yahoo/vespa/client/dsl/Aggregator.java +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.client.dsl; - -public class Aggregator { - - String type; - Object value = ""; - - Aggregator(String type) { - this.type = type; - } - - Aggregator(String type, Object value) { - this.type = type; - this.value = value; - } - - @Override - public String toString() { - return String.format("%s(%s)", type, value); - } -} diff --git a/client/src/main/java/com/yahoo/vespa/client/dsl/Annotation.java b/client/src/main/java/com/yahoo/vespa/client/dsl/Annotation.java deleted file mode 100644 index bc70f2d366d..00000000000 --- a/client/src/main/java/com/yahoo/vespa/client/dsl/Annotation.java +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.client.dsl; - -import java.util.Collections; -import java.util.Map; - -public class Annotation { - - Map annotations = Collections.emptyMap(); - - Annotation() { - } - - Annotation(Map annotations) { - this.annotations = annotations; - } - - public Annotation append(Annotation a) { - this.annotations.putAll(a.annotations); - return this; - } - - @Override - public String toString() { - return annotations == null || annotations.isEmpty() - ? "" - : Q.gson.toJson(annotations); - } -} diff --git a/client/src/main/java/com/yahoo/vespa/client/dsl/DotProduct.java b/client/src/main/java/com/yahoo/vespa/client/dsl/DotProduct.java deleted file mode 100644 index b288f9a4afd..00000000000 --- a/client/src/main/java/com/yahoo/vespa/client/dsl/DotProduct.java +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.client.dsl; - -import java.util.Map; - -public class DotProduct extends QueryChain { - - private String fieldName; - private Map weightedSet; - - DotProduct(String fieldName, Map weightedSet) { - this.fieldName = fieldName; - this.weightedSet = weightedSet; - this.nonEmpty = true; - } - - @Override - public Select getSelect() { - return sources.select; - } - - @Override - public String toString() { - return "dotProduct(" + fieldName + ", " + Q.gson.toJson(weightedSet) + ")"; - } - - @Override - boolean hasPositiveSearchField(String fieldName) { - // TODO: implementation - return false; - } - - @Override - boolean hasPositiveSearchField(String fieldName, Object value) { - // TODO: implementation - return false; - } - - @Override - boolean hasNegativeSearchField(String fieldName) { - // TODO: implementation - return false; - } - - @Override - boolean hasNegativeSearchField(String fieldName, Object value) { - // TODO: implementation - return false; - } -} diff --git a/client/src/main/java/com/yahoo/vespa/client/dsl/EndQuery.java b/client/src/main/java/com/yahoo/vespa/client/dsl/EndQuery.java deleted file mode 100644 index 80e8d893a8e..00000000000 --- a/client/src/main/java/com/yahoo/vespa/client/dsl/EndQuery.java +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.client.dsl; - -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -public class EndQuery { - - QueryChain queryChain; - Map map = new LinkedHashMap<>(); - List order = new ArrayList<>(); - String groupQueryStr; - - EndQuery(QueryChain queryChain) { - this.queryChain = queryChain; - - // make sure the order of limit, offset and timeout - this.map.put("limit", null); - this.map.put("offset", null); - this.map.put("timeout", null); - } - - EndQuery setTimeout(Integer timeout) { - map.put("timeout", timeout); - return this; - } - - EndQuery setOffset(int offset) { - map.put("offset", offset); - return this; - } - - EndQuery setLimit(int limit) { - map.put("limit", limit); - return this; - } - - public EndQuery offset(int offset) { - return this.setOffset(offset); - } - - public EndQuery timeout(int timeout) { - return this.setTimeout(timeout); - } - - public EndQuery limit(int limit) { - return this.setLimit(limit); - } - - public FixedQuery semicolon() { - return new FixedQuery(this); - } - - public EndQuery group(Group group) { - this.groupQueryStr = group.toString(); - return this; - } - - public EndQuery group(String groupQueryStr) { - this.groupQueryStr = groupQueryStr; - return this; - } - - public EndQuery orderByAsc(Annotation annotation, String fieldName) { - order.add(new Object[]{annotation, fieldName, "asc"}); - return this; - } - - public EndQuery orderByAsc(String fieldName) { - order.add(new Object[]{A.empty(), fieldName, "asc"}); - return this; - } - - public EndQuery orderByDesc(Annotation annotation, String fieldName) { - order.add(new Object[]{annotation, fieldName, "desc"}); - return this; - } - - public EndQuery orderByDesc(String fieldName) { - order.add(new Object[]{A.empty(), fieldName, "desc"}); - return this; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - String orderStr = order.stream().map(array -> A.empty().equals(array[0]) - ? String.format("%s %s", array[1], array[2]) - : String.format("[%s]%s %s", array[0], array[1], array[2])) - .collect(Collectors.joining(", ")); - - String others = map.entrySet().stream() - .filter(entry -> entry.getValue() != null) - .map(entry -> entry.getKey() + " " + entry.getValue()) - .collect(Collectors.joining(" ")); - - if (orderStr.isEmpty()) { - sb.append(others.isEmpty() ? "" : others); - } else if (others.isEmpty()) { - sb.append("order by ").append(orderStr); - } else { - sb.append("order by ").append(orderStr).append(", ").append(others); - } - - if (groupQueryStr != null) { - sb.append("| ").append(groupQueryStr); - } - - return sb.toString(); - } -} diff --git a/client/src/main/java/com/yahoo/vespa/client/dsl/Field.java b/client/src/main/java/com/yahoo/vespa/client/dsl/Field.java deleted file mode 100644 index fc8d0ea7f86..00000000000 --- a/client/src/main/java/com/yahoo/vespa/client/dsl/Field.java +++ /dev/null @@ -1,274 +0,0 @@ -// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.client.dsl; - -import org.apache.commons.text.StringEscapeUtils; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -public class Field extends QueryChain { - - private String fieldName; - private List values = new ArrayList<>(); - private Annotation annotation = A.empty(); - private String relation; - - Field(Sources sources, String fieldName) { - this.sources = sources; - this.fieldName = fieldName; - } - - Field(Query query, String fieldName) { - this.query = query; - this.fieldName = fieldName; - } - - public Query contains(String value) { - return contains(A.empty(), value); - } - - public Query contains(Annotation annotation, String value) { - return common("contains", annotation, value); - } - - public Query containsPhrase(String value, String... others) { - return common("phrase", annotation, value, others); - } - - public Query containsPhrase(List values) { - if (values.isEmpty()) { - throw new IllegalArgumentException("value of \"contains phrase\" should not be empty"); - } - - return common("phrase", annotation, values); - } - - public Query containsNear(String value, String... others) { - return common("near", annotation, value, others); - } - - public Query containsNear(List values) { - if (values.isEmpty()) { - throw new IllegalArgumentException("value of \"contains near\" should not be empty"); - } - - return common("near", annotation, values); - } - - public Query containsNear(Annotation annotation, String value, String... others) { - return common("near", annotation, value, others); - } - - public Query containsNear(Annotation annotation, List values) { - if (values.isEmpty()) { - throw new IllegalArgumentException("value of \"contains near\" should not be empty"); - } - - return common("near", annotation, values); - } - - public Query containsOnear(String value, String... others) { - return common("onear", annotation, value, others); - } - - public Query containsOnear(List values) { - if (values.isEmpty()) { - throw new IllegalArgumentException("value of \"contains onear\" should not be empty"); - } - - return common("onear", annotation, values); - } - - public Query containsOnear(Annotation annotation, String value, String... others) { - return common("onear", annotation, value, others); - } - - public Query containsOnear(Annotation annotation, List values) { - if (values.isEmpty()) { - throw new IllegalArgumentException("value of \"contains onear\" should not be empty"); - } - - return common("onear", annotation, values); - } - - public Query containsSameElement(Query andQuery) { - return common("sameElement", annotation, andQuery); - } - - public Query containsEquiv(String value, String... others) { - return containsEquiv(Stream.concat(Stream.of(value), Stream.of(others)).collect(Collectors.toList())); - } - - public Query containsEquiv(List values) { - if (values.isEmpty()) { - throw new IllegalArgumentException("value of \"contains equiv\" should not be empty"); - } else if (values.size() == 1) { - // Vespa does not support one element equiv syntax, use contains instead - return contains(values.get(0)); - } else { - return common("equiv", annotation, values); - } - } - - public Query containsUri(String value) { - return common("uri", annotation, value) ; - } - - public Query containsUri(Annotation annotation, String value) { - return common("uri", annotation, value) ; - } - - public Query matches(String str) { - return common("matches", annotation, str); - } - - public Query eq(int t) { - return common("=", annotation, t); - } - - public Query ge(int t) { - return common(">=", annotation, t); - } - - public Query gt(int t) { - return common(">", annotation, t); - } - - public Query le(int t) { - return common("<=", annotation, t); - } - - public Query lt(int t) { - return common("<", annotation, t); - } - - public Query inRange(int l, int m) { - return common("range", annotation, l, new Integer[]{m}); - } - - public Query eq(long t) { - return common("=", annotation, t); - } - - public Query ge(long t) { - return common(">=", annotation, t); - } - - public Query gt(long t) { - return common(">", annotation, t); - } - - public Query le(long t) { - return common("<=", annotation, t); - } - - public Query lt(long t) { - return common("<", annotation, t); - } - - public Query inRange(long l, long m) { - return common("range", annotation, l, new Long[]{m}); - } - - - public Query isTrue() { - return common("=", annotation, true); - } - - public Query isFalse() { - return common("=", annotation, false); - } - - private Query common(String relation, Annotation annotation, Object value) { - return common(relation, annotation, value, values.toArray()); - } - - private Query common(String relation, Annotation annotation, String value) { - Object v = "\"" + StringEscapeUtils.escapeJava(value) + "\""; - return common(relation, annotation, v, values.toArray()); - } - - private Query common(String relation, Annotation annotation, List values) { - return common(relation, annotation, values.get(0), values.subList(1, values.size()).toArray(new String[0])); - } - - private Query common(String relation, Annotation annotation, String value, String[] others) { - Object v = "\"" + StringEscapeUtils.escapeJava(value) + "\""; - Object[] o = Stream.of(others).map(s -> "\"" + StringEscapeUtils.escapeJava(s) + "\"").toArray(); - return common(relation, annotation, v, o); - } - - private Query common(String relation, Annotation annotation, Object value, Object[] others) { - this.annotation = annotation; - this.relation = relation; - this.values = Stream.concat(Stream.of(value), Stream.of(others)).collect(Collectors.toList()); - this.nonEmpty = true; - return query != null - ? query - : new Query(sources, this); - } - - @Override - public String toString() { - boolean hasAnnotation = !A.empty().equals(annotation); - String valuesStr; - switch (relation) { - case "range": - valuesStr = values.stream() - .map(i -> i instanceof Long ? i.toString() + "L" : i.toString()) - .collect(Collectors.joining(", ")); - - return hasAnnotation - ? String.format("([%s]range(%s, %s))", annotation, fieldName, valuesStr) - : String.format("range(%s, %s)", fieldName, valuesStr); - case "near": - case "onear": - case "phrase": - case "equiv": - case "uri": - valuesStr = values.stream().map(Object::toString).collect(Collectors.joining(", ")); - return hasAnnotation - ? String.format("%s contains ([%s]%s(%s))", fieldName, annotation, relation, valuesStr) - : String.format("%s contains %s(%s)", fieldName, relation, valuesStr); - case "sameElement": - return String.format("%s contains %s(%s)", fieldName, relation, - ((Query) values.get(0)).toCommaSeparatedAndQueries()); - default: - Object value = values.get(0); - valuesStr = value instanceof Long ? value.toString() + "L" : value.toString(); - return hasAnnotation - ? String.format("%s %s ([%s]%s)", fieldName, relation, annotation, valuesStr) - : String.format("%s %s %s", fieldName, relation, valuesStr); - } - } - - @Override - boolean hasPositiveSearchField(String fieldName) { - return !"andnot".equals(this.op) && this.fieldName.equals(fieldName); - } - - @Override - boolean hasPositiveSearchField(String fieldName, Object value) { - return hasPositiveSearchField(fieldName) && valuesContains(value); - } - - @Override - boolean hasNegativeSearchField(String fieldName) { - return "andnot".equals(this.op) && this.fieldName.equals(fieldName); - } - - @Override - boolean hasNegativeSearchField(String fieldName, Object value) { - return hasNegativeSearchField(fieldName) && valuesContains(value); - } - - boolean valuesContains(Object value) { - if (value instanceof String) { - value = "\"" + value + "\""; - } - return values.contains(value); - } - -} diff --git a/client/src/main/java/com/yahoo/vespa/client/dsl/FixedQuery.java b/client/src/main/java/com/yahoo/vespa/client/dsl/FixedQuery.java deleted file mode 100644 index 76c3756abc3..00000000000 --- a/client/src/main/java/com/yahoo/vespa/client/dsl/FixedQuery.java +++ /dev/null @@ -1,414 +0,0 @@ -// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.client.dsl; - -import com.google.gson.internal.LinkedHashTreeMap; - -import java.math.BigDecimal; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.Collectors; - -public class FixedQuery { - - final EndQuery endQuery; - Map others = new HashMap<>(); - Map queryMap; - - - public FixedQuery(EndQuery endQuery) { - this.endQuery = endQuery; - } - - public FixedQuery hits(int hits) { - this.param("hits", hits); - return this; - } - - public FixedQuery offset(int offset) { - this.param("offset", offset); - return this; - } - - public FixedQuery queryProfile(String queryProfile) { - this.param("queryProfile", queryProfile); - return this; - } - - public FixedQuery groupingSessionCache(boolean enable) { - this.param("groupingSessionCache", enable); - return this; - } - - public FixedQuery searchChain(String searchChain) { - this.param("searchChain", searchChain); - return this; - } - - public FixedQuery timeout(int second) { - this.param("timeout", second); - return this; - } - - public FixedQuery timeoutInMs(int milli) { - this.param("timeout", milli + "ms"); - return this; - } - - public FixedQuery tracelevel(int level) { - this.param("tracelevel", level); - return this; - } - - public FixedQuery traceTimestamps(boolean enable) { - this.param("trace.timestamps", enable); - return this; - } - - public FixedQuery defaultIndex(String indexName) { - this.param("default-index", indexName); - return this; - } - - public FixedQuery encoding(String encoding) { - this.param("encoding", encoding); - return this; - } - - public FixedQuery filter(String filter) { - this.param("filter", filter); - return this; - } - - public FixedQuery locale(String locale) { - this.param("locale", locale); - return this; - } - - public FixedQuery language(String language) { - this.param("language", language); - return this; - } - - @Deprecated - public FixedQuery query(String query) { - this.param("query", query); - return this; - } - - public FixedQuery restrict(String commaDelimitedDocTypeNames) { - this.param("restrict", commaDelimitedDocTypeNames); - return this; - } - - public FixedQuery path(String searchPath) { - this.param("path", searchPath); - return this; - } - - public FixedQuery sources(String commaDelimitedSourceNames) { - this.param("sources", commaDelimitedSourceNames); - return this; - } - - public FixedQuery type(String type) { - // web, all, any, phrase, yql, adv (deprecated) - this.param("type", type); - return this; - } - - public FixedQuery location(String location) { - this.param("location", location); - return this; - } - - public FixedQuery rankfeature(String featureName, String featureValue) { - this.param("rankfeature." + featureName, featureValue); - return this; - } - - public FixedQuery rankfeatures(boolean enable) { - this.param("rankfeatures", enable); - return this; - } - - public FixedQuery ranking(String rankProfileName) { - this.param("ranking", rankProfileName); - return this; - } - - public FixedQuery rankproperty(String propertyName, String propertyValue) { - this.param("rankproperty." + propertyName, propertyValue); - return this; - } - - public FixedQuery rankingSoftTimeout(boolean enable) { - this.param("ranking.softtimeout.enable", enable); - return this; - } - - public FixedQuery rankingSoftTimeout(boolean enable, double factor) { - this.param("ranking.softtimeout.enable", enable); - this.param("ranking.softtimeout.factor", factor); - return this; - } - - public FixedQuery sorting(String sorting) { - this.param("sorting", sorting); - return this; - } - - public FixedQuery rankingFreshness(String freshness) { - this.param("ranking.freshness", freshness); - return this; - } - - public FixedQuery rankingQueryCache(boolean enable) { - this.param("ranking.queryCache", enable); - return this; - } - - public FixedQuery bolding(boolean enable) { - this.param("bolding", enable); - return this; - } - - public FixedQuery format(String format) { - this.param("format", format); - return this; - } - - public FixedQuery summary(String summaryClass) { - this.param("summary", summaryClass); - return this; - } - - public FixedQuery presentationTemplate(String template) { - this.param("presentation.template", template); - return this; - } - - public FixedQuery presentationTiming(boolean enable) { - this.param("presentation.timing", enable); - return this; - } - - public FixedQuery select(String groupSyntax) { - this.param("select", groupSyntax); - return this; - } - - public FixedQuery select(Group group) { - this.param("select", group.toString()); - return this; - } - - public FixedQuery collapseField(String summaryFieldName) { - this.param("collapsefield", summaryFieldName); - return this; - } - - public FixedQuery collapseSummary(String summaryClass) { - this.param("collapse.summary", summaryClass); - return this; - } - - public FixedQuery collapseSize(int size) { - this.param("collapsesize", size); - return this; - } - - public FixedQuery posLatLong(String vespaLatLong) { - this.param("pos.ll", vespaLatLong); - return this; - } - - public FixedQuery posLatLong(double lat, double lon) { - String latlong = toVespaLatLong(lat, lon); - return posLatLong(latlong); - } - - private String toVespaLatLong(double lat, double lon) { - double absLat = Math.abs(lat); - double absLon = Math.abs(lon); - if (absLat > 90 || absLon > 180) { - throw new IllegalArgumentException(String.format("invalid lat long value, lat=%f, long=%f", lat, lon)); - } - - return String.format("%s%f;%s%f", - lat > 0 ? "N" : "S", absLat, - lon > 0 ? "E" : "W", absLon); - } - - public FixedQuery posRadiusInKilometer(int km) { - this.param("pos.radius", km + "km"); - return this; - } - - public FixedQuery posRadiusInMeter(int m) { - this.param("pos.radius", m + "m"); - return this; - } - - public FixedQuery posRadiusInMile(int mi) { - this.param("pos.radius", mi + "mi"); - return this; - } - - public FixedQuery posBoundingBox(double n, double s, double e, double w) { - this.param("pos.bb", String.format("n=%f,s=%f,e=%f,w=%f", n, s, e, w)); - return this; - } - - public FixedQuery streamingUserId(BigDecimal id) { - this.param("streaming.userid", id); - return this; - } - - public FixedQuery streamingGroupName(String groupName) { - this.param("streaming.groupname", groupName); - return this; - } - - public FixedQuery streamingSelection(String selection) { - this.param("streaming.selection", selection); - return this; - } - - public FixedQuery streamingPriority(String priority) { - this.param("streaming.priority", priority); - return this; - } - - public FixedQuery streamingMaxBucketsPerVisitor(int max) { - this.param("streaming.maxbucketspervisitor", max); - return this; - } - - public FixedQuery rulesOff(boolean bool) { - this.param("rules.off", bool); - return this; - } - - public FixedQuery rulesRulebase(String rulebase) { - this.param("rules.rulebase", rulebase); - return this; - } - - public FixedQuery recall(String recall) { - this.param("recall", recall); - return this; - } - - public FixedQuery user(String user) { - this.param("user", user); - return this; - } - - public FixedQuery hitCountEstimate(boolean enable) { - this.param("hitcountestimate", enable); - return this; - } - - public FixedQuery metricsIgnore(boolean bool) { - this.param("metrics.ignore", bool); - return this; - } - - public FixedQuery param(String key, String value) { - others.put(key, value); - return this; - } - - private FixedQuery param(String key, Object value) { - this.param(key, value.toString()); - return this; - } - - public FixedQuery params(Map params) { - others.putAll(params); - return this; - } - - public Map buildQueryMap() { - if (queryMap != null) { - return queryMap; - } - - assignIndex(); - - StringBuilder sb = new StringBuilder(); - sb.append("select ") - .append(endQuery.queryChain.getSelect()) - .append(" from ") - .append(endQuery.queryChain.getSources()) - .append(" where ") - .append(endQuery.queryChain); - - if (!"".equals(endQuery.toString())) { - sb.append(' ').append(endQuery); - } - sb.append(";"); - - queryMap = new LinkedHashTreeMap<>(); // for the order - queryMap.put("yql", sb.toString()); - queryMap.putAll(others); - queryMap.putAll(getUserInputs()); - return queryMap; - } - - - public String build() { - return buildQueryMap().entrySet().stream().map(entry -> entry.getKey() + "=" + entry.getValue()) - .collect(Collectors.joining("&")); - } - - private void assignIndex() { - assignIndex(endQuery.queryChain.getQuery(), new AtomicInteger()); - } - - private void assignIndex(QueryChain q, AtomicInteger ai) { - q.setIndex(ai.incrementAndGet()); - if (q instanceof Query) { - assignIndex((Query) q, ai); - } - } - - private void assignIndex(Query q, AtomicInteger ai) { - q.queries.stream() - .filter(QueryChain::nonEmpty) - .forEach(qu -> assignIndex(qu, ai)); - } - - private Map getUserInputs() { - return getUserInputs(endQuery.queryChain.getQuery()); - } - - private Map getUserInputs(Query q) { - Map param = new HashMap<>(); - q.queries.forEach(qu -> { - if (qu instanceof UserInput) { - param.putAll(((UserInput) qu).getParam()); - } else if (qu instanceof Query) { - param.putAll(getUserInputs((Query) qu)); - } - }); - return param; - } - - public boolean hasPositiveSearchField(String fieldName) { - return endQuery.queryChain.hasPositiveSearchField(fieldName); - } - - public boolean hasPositiveSearchField(String fieldName, Object value) { - return endQuery.queryChain.hasPositiveSearchField(fieldName, value); - } - - public boolean hasNegativeSearchField(String fieldName) { - return endQuery.queryChain.hasNegativeSearchField(fieldName); - } - - public boolean hasNegativeSearchField(String fieldName, Object value) { - return endQuery.queryChain.hasNegativeSearchField(fieldName, value); - } -} diff --git a/client/src/main/java/com/yahoo/vespa/client/dsl/G.java b/client/src/main/java/com/yahoo/vespa/client/dsl/G.java deleted file mode 100644 index 23499dfb7e6..00000000000 --- a/client/src/main/java/com/yahoo/vespa/client/dsl/G.java +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.client.dsl; - - -public final class G { - - public static Group all(IGroupOperation... ops) { - return new Group("all", ops); - } - - public static Group each(IGroupOperation... ops) { - return new Group("each", ops); - } - - public static GroupOperation group(String expr) { - return new GroupOperation("group", expr); - } - - public static GroupOperation maxRtn(int max) { - return new GroupOperation("max", max); - } - - public static GroupOperation order(String expr) { - return new GroupOperation("order", expr); - } - - public static GroupOperation output(Aggregator... aggrs) { - return new GroupOperation("output", aggrs); - } - - public static Aggregator max(int max) { - return new Aggregator("max", max); - } - - public static Aggregator summary() { - return new Aggregator("summary"); - } - - public static Aggregator count() { - return new Aggregator("count"); - } - - public static Aggregator summary(String summaryClass) { - return new Aggregator("summary", summaryClass); - } -} diff --git a/client/src/main/java/com/yahoo/vespa/client/dsl/Group.java b/client/src/main/java/com/yahoo/vespa/client/dsl/Group.java deleted file mode 100644 index db27e7bc2ee..00000000000 --- a/client/src/main/java/com/yahoo/vespa/client/dsl/Group.java +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.client.dsl; - -import java.util.Objects; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -public class Group implements IGroup, IGroupOperation { - - String type; - IGroupOperation[] operations; - - Group(String type, IGroupOperation[] operations) { - this.type = type; - this.operations = operations; - } - - @Override - public String toString() { - return String.format("%s(%s)", - type, - Stream.of(operations).map(Objects::toString).collect(Collectors.joining(" "))); - } -} diff --git a/client/src/main/java/com/yahoo/vespa/client/dsl/GroupOperation.java b/client/src/main/java/com/yahoo/vespa/client/dsl/GroupOperation.java deleted file mode 100644 index a40b304f5da..00000000000 --- a/client/src/main/java/com/yahoo/vespa/client/dsl/GroupOperation.java +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.client.dsl; - -import java.util.Objects; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -public class GroupOperation implements IGroupOperation { - - String type; - Object value; - Aggregator[] aggregators; - - public GroupOperation(String type, Object value) { - this.type = type; - this.value = value; - } - - public GroupOperation(String type, Aggregator[] aggregators) { - this.type = type; - this.aggregators = aggregators; - } - - @Override - public String toString() { - if (value != null) { - return String.format("%s(%s)", type, value); - } - - return String.format("%s(%s)", - type, - Stream.of(aggregators).map(Objects::toString).collect(Collectors.joining(" "))); - } -} diff --git a/client/src/main/java/com/yahoo/vespa/client/dsl/IGroup.java b/client/src/main/java/com/yahoo/vespa/client/dsl/IGroup.java deleted file mode 100644 index 0b3a09914bc..00000000000 --- a/client/src/main/java/com/yahoo/vespa/client/dsl/IGroup.java +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.client.dsl; - -/* - * interface for group syntax - */ - -public interface IGroup { - -} diff --git a/client/src/main/java/com/yahoo/vespa/client/dsl/IGroupOperation.java b/client/src/main/java/com/yahoo/vespa/client/dsl/IGroupOperation.java deleted file mode 100644 index d60e76fd3bd..00000000000 --- a/client/src/main/java/com/yahoo/vespa/client/dsl/IGroupOperation.java +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.client.dsl; - -/* - * interface for group operation - */ - -public interface IGroupOperation { - -} diff --git a/client/src/main/java/com/yahoo/vespa/client/dsl/NonEmpty.java b/client/src/main/java/com/yahoo/vespa/client/dsl/NonEmpty.java deleted file mode 100644 index 385797943a5..00000000000 --- a/client/src/main/java/com/yahoo/vespa/client/dsl/NonEmpty.java +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.client.dsl; - -public class NonEmpty extends QueryChain { - - private Query query; - - NonEmpty(Query query) { - this.query = query; - this.nonEmpty = true; - } - - @Override - public Select getSelect() { - return sources.select; - } - - @Override - public String toString() { - return String.format("nonEmpty(%s)", query); - } - - @Override - boolean hasPositiveSearchField(String fieldName) { - // TODO: implementation - return false; - } - - @Override - boolean hasPositiveSearchField(String fieldName, Object value) { - // TODO: implementation - return false; - } - - @Override - boolean hasNegativeSearchField(String fieldName) { - // TODO: implementation - return false; - } - - @Override - boolean hasNegativeSearchField(String fieldName, Object value) { - // TODO: implementation - return false; - } -} diff --git a/client/src/main/java/com/yahoo/vespa/client/dsl/Q.java b/client/src/main/java/com/yahoo/vespa/client/dsl/Q.java deleted file mode 100644 index f6439302f6b..00000000000 --- a/client/src/main/java/com/yahoo/vespa/client/dsl/Q.java +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.client.dsl; - -import com.google.gson.Gson; - -import java.util.List; -import java.util.Map; - -public final class Q { - - static Gson gson = new Gson(); - private static Sources SELECT_ALL_FROM_SOURCES_ALL = new Sources(new Select("*"), "*"); - - public static Select select(String fieldName) { - return new Select(fieldName); - } - - public static Select select(String fieldName, String... others) { - return new Select(fieldName, others); - } - - public static Field p(String fieldName) { - return SELECT_ALL_FROM_SOURCES_ALL.where(fieldName); - } - - public static Query p(QueryChain query) { - return new Query(SELECT_ALL_FROM_SOURCES_ALL, query); - } - - public static Query p() { - return new Query(SELECT_ALL_FROM_SOURCES_ALL); - } - - public static Rank rank(Query query, Query... ranks) { - return new Rank(query, ranks); - } - - public static UserInput ui(String value) { - return new UserInput(value); - } - - public static UserInput ui(Annotation a, String value) { - return new UserInput(a, value); - } - - public static UserInput ui(String index, String value) { - return ui(A.defaultIndex(index), value); - } - - public static DotProduct dotPdt(String field, Map weightedSet) { - return new DotProduct(field, weightedSet); - } - - public static WeightedSet wtdSet(String field, Map weightedSet) { - return new WeightedSet(field, weightedSet); - } - - public static NonEmpty nonEmpty(Query query) { - return new NonEmpty(query); - } - - public static Wand wand(String field, Map weightedSet) { - return new Wand(field, weightedSet); - } - - public static Wand wand(String field, List> numericRange) { - return new Wand(field, numericRange); - } - - public static WeakAnd weakand(String field, Query query) { - return new WeakAnd(field, query); - } -} diff --git a/client/src/main/java/com/yahoo/vespa/client/dsl/Query.java b/client/src/main/java/com/yahoo/vespa/client/dsl/Query.java deleted file mode 100644 index 1b220a936cd..00000000000 --- a/client/src/main/java/com/yahoo/vespa/client/dsl/Query.java +++ /dev/null @@ -1,227 +0,0 @@ -// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.client.dsl; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; - -public class Query extends QueryChain { - - Annotation annotation; - Sources sources; - - List queries = new ArrayList<>(); - - Query(Sources sources, QueryChain queryChain) { - this.sources = sources; - queries.add(queryChain); - nonEmpty = queryChain.nonEmpty; - } - - Query(Sources sources) { - this.sources = sources; - } - - String toCommaSeparatedAndQueries() { - return queries.stream() - .filter(qc -> "and".equals(qc.getOp())) - .map(Objects::toString) - .collect(Collectors.joining(", ")); - } - - @Override - public String toString() { - // TODO: need to refactor - if (!nonEmpty) { - return ""; - } - - boolean hasAnnotation = A.hasAnnotation(annotation); - - StringBuilder sb = new StringBuilder(); - - if (hasAnnotation) { - sb.append("([").append(annotation).append("]("); - } - - boolean firstQuery = true; - for (int i = 0; i < queries.size(); i++) { - QueryChain qc = queries.get(i); - if (!qc.nonEmpty) { - continue; - } - - boolean isNotAnd = "andnot".equals(qc.getOp()); - - if (!firstQuery) { - sb.append(" "); - if (isNotAnd) { - sb.append("and !"); - } else { - sb.append(qc.getOp()).append(' '); - } - } else { - firstQuery = false; - } - - boolean appendBrackets = - (qc instanceof Query && ((Query) qc).queries.size() > 1 && !A.hasAnnotation(((Query) qc).annotation)) - || isNotAnd; - if (appendBrackets) { - sb.append("("); - } - - sb.append(qc); - - if (appendBrackets) { - sb.append(")"); - } - - } - - if (hasAnnotation) { - sb.append("))"); - } - return sb.toString().trim(); - } - - public Field and(String fieldName) { - Field f = new Field(this, fieldName); - f.setOp("and"); - queries.add(f); - nonEmpty = true; - return f; - } - - public Field andnot(String fieldName) { - Field f = new Field(this, fieldName); - f.setOp("andnot"); - queries.add(f); - nonEmpty = true; - return f; - } - - public Field or(String fieldName) { - Field f = new Field(this, fieldName); - f.setOp("or"); - queries.add(f); - nonEmpty = true; - return f; - } - - public Query and(QueryChain query) { - query.setOp("and"); - queries.add(query); - nonEmpty = nonEmpty || query.nonEmpty; - return this; - } - - public Query andnot(QueryChain query) { - query.setOp("andnot"); - queries.add(query); - nonEmpty = nonEmpty || query.nonEmpty; - return this; - } - - public Query or(QueryChain query) { - query.setOp("or"); - queries.add(query); - nonEmpty = nonEmpty || query.nonEmpty; - return this; - } - - public Query annotate(Annotation annotation) { - this.annotation = annotation; - return this; - } - - public EndQuery offset(int offset) { - return new EndQuery(this).offset(offset); - } - - public EndQuery limit(int hits) { - return new EndQuery(this).limit(hits); - } - - public EndQuery timeout(int timeout) { - return new EndQuery(this).timeout(timeout); - } - - public EndQuery group(Group group) { - return new EndQuery(this).group(group); - } - - public EndQuery group(String groupStr) { - return new EndQuery(this).group(groupStr); - } - - public EndQuery orderByAsc(String fieldName) { - return new EndQuery(this).orderByAsc(fieldName); - } - - public EndQuery orderByDesc(String fieldName) { - return new EndQuery(this).orderByDesc(fieldName); - } - - public FixedQuery semicolon() { - return new FixedQuery(new EndQuery(this)); - } - - @Override - public Sources getSources() { - return sources; - } - - @Override - public void setSources(Sources sources) { - this.sources = sources; - } - - @Override - public Query getQuery() { - return this; - } - - @Override - public Select getSelect() { - return sources.select; - } - - @Override - public boolean hasPositiveSearchField(String fieldName) { - boolean hasPositiveInSubqueries = queries.stream().anyMatch(q -> q.hasPositiveSearchField(fieldName)); - boolean hasNegativeInSubqueries = queries.stream().anyMatch(q -> q.hasNegativeSearchField(fieldName)); - return nonEmpty - && ((!"andnot".equals(this.op) && hasPositiveInSubqueries) - || ("andnot".equals(this.op) && hasNegativeInSubqueries)); - } - - - @Override - public boolean hasPositiveSearchField(String fieldName, Object value) { - boolean hasPositiveInSubqueries = queries.stream().anyMatch(q -> q.hasPositiveSearchField(fieldName, value)); - boolean hasNegativeInSubqueries = queries.stream().anyMatch(q -> q.hasNegativeSearchField(fieldName, value)); - return nonEmpty && - (!"andnot".equals(this.op) && hasPositiveInSubqueries) - || ("andnot".equals(this.op) && hasNegativeInSubqueries); - } - - @Override - public boolean hasNegativeSearchField(String fieldName) { - boolean hasPositiveInSubqueries = queries.stream().anyMatch(q -> q.hasPositiveSearchField(fieldName)); - boolean hasNegativeInSubqueries = queries.stream().anyMatch(q -> q.hasNegativeSearchField(fieldName)); - return nonEmpty - && (!"andnot".equals(this.op) && hasNegativeInSubqueries) - || ("andnot".equals(this.op) && hasPositiveInSubqueries); - } - - @Override - public boolean hasNegativeSearchField(String fieldName, Object value) { - boolean hasPositiveInSubqueries = queries.stream().anyMatch(q -> q.hasPositiveSearchField(fieldName, value)); - boolean hasNegativeInSubqueries = queries.stream().anyMatch(q -> q.hasNegativeSearchField(fieldName, value)); - return nonEmpty - && (!"andnot".equals(this.op) && hasNegativeInSubqueries) - || ("andnot".equals(this.op) && hasPositiveInSubqueries); - } -} diff --git a/client/src/main/java/com/yahoo/vespa/client/dsl/QueryChain.java b/client/src/main/java/com/yahoo/vespa/client/dsl/QueryChain.java deleted file mode 100644 index 02341ee997d..00000000000 --- a/client/src/main/java/com/yahoo/vespa/client/dsl/QueryChain.java +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.client.dsl; - -public abstract class QueryChain { - - String op; - int index; // for distinct each query chain - Sources sources; - Select select; - Query query; - boolean nonEmpty; - - void setOp(String op) { - this.op = op; - } - - String getOp() { - return op; - } - - void setIndex(int index) { - this.index = index; - } - - Sources getSources() { - return sources; - } - - void setSources(Sources sources) { - this.sources = sources; - } - - Select getSelect() { - return select; - } - - Query getQuery() { - return query; - } - - boolean nonEmpty() { - return nonEmpty; - } - - abstract boolean hasPositiveSearchField(String fieldName); - - abstract boolean hasPositiveSearchField(String fieldName, Object value); - - abstract boolean hasNegativeSearchField(String fieldName); - - abstract boolean hasNegativeSearchField(String fieldName, Object value); -} diff --git a/client/src/main/java/com/yahoo/vespa/client/dsl/Rank.java b/client/src/main/java/com/yahoo/vespa/client/dsl/Rank.java deleted file mode 100644 index 4eca6264f98..00000000000 --- a/client/src/main/java/com/yahoo/vespa/client/dsl/Rank.java +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.client.dsl; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -public class Rank extends QueryChain { - - private List queries = new ArrayList<>(); - - Rank(Query query, Query... ranks) { - this.query = query; - this.nonEmpty = query.nonEmpty(); - queries.add(query); - queries.addAll(Stream.of(ranks).collect(Collectors.toList())); - } - - @Override - public Select getSelect() { - return sources.select; - } - - @Override - public String toString() { - return "rank(" + queries.stream().map(Objects::toString).collect(Collectors.joining(", ")) + ")"; - } - - @Override - boolean hasPositiveSearchField(String fieldName) { - return queries.get(0).hasPositiveSearchField(fieldName); - } - - @Override - boolean hasPositiveSearchField(String fieldName, Object value) { - return queries.get(0).hasPositiveSearchField(fieldName, value); - } - - @Override - boolean hasNegativeSearchField(String fieldName) { - return queries.get(0).hasNegativeSearchField(fieldName); - } - - @Override - boolean hasNegativeSearchField(String fieldName, Object value) { - return queries.get(0).hasNegativeSearchField(fieldName, value); - } -} diff --git a/client/src/main/java/com/yahoo/vespa/client/dsl/Select.java b/client/src/main/java/com/yahoo/vespa/client/dsl/Select.java deleted file mode 100644 index 9f699f2c7fa..00000000000 --- a/client/src/main/java/com/yahoo/vespa/client/dsl/Select.java +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.client.dsl; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -public class Select { - - private List selectedFields = new ArrayList<>(); - - Select(String fieldName) { - selectedFields.add(fieldName); - } - - public Select(String fieldName, String... others) { - selectedFields.add(fieldName); - selectedFields.addAll(Stream.of(others).collect(Collectors.toList())); - } - - Select(List fieldNames) { - selectedFields = new ArrayList<>(fieldNames); - } - - public Sources from(String sd) { - return new Sources(this, sd); - } - - public Sources from(String sd, String... sds) { - return new Sources(this, sd, sds); - } - - @Override - public String toString() { - return selectedFields.isEmpty() ? "*" : String.join(", ", selectedFields); - } -} diff --git a/client/src/main/java/com/yahoo/vespa/client/dsl/Sources.java b/client/src/main/java/com/yahoo/vespa/client/dsl/Sources.java deleted file mode 100644 index bab5ad6b4a6..00000000000 --- a/client/src/main/java/com/yahoo/vespa/client/dsl/Sources.java +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.client.dsl; - - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -public class Sources { - - final Select select; - final List targetDocTypes; - - Sources(Select select, List searchDefinitions) { - this.select = select; - targetDocTypes = new ArrayList<>(searchDefinitions); - } - - Sources(Select select, String searchDefinition) { - this(select, Collections.singletonList(searchDefinition)); - } - - Sources(Select select, String searchDefinition, String... others) { - this(select, Stream.concat(Stream.of(searchDefinition), Stream.of(others)).collect(Collectors.toList())); - } - - @Override - public String toString() { - if (targetDocTypes.isEmpty() || targetDocTypes.size() == 1 && "*".equals(targetDocTypes.get(0))) { - return "sources *"; - } - - if (targetDocTypes.size() == 1) { - return targetDocTypes.get(0); - } - - return "sources " + String.join(", ", targetDocTypes); - } - - public Field where(String fieldName) { - Field f = new Field(this, fieldName); - f.setOp("and"); - return f; - } - - public Query where(QueryChain userinput) { - return whereReturnQuery(userinput); - } - - public EndQuery where(Rank rank) { - return whereReturnEndQuery(rank); - } - - private Query whereReturnQuery(QueryChain qc) { - return new Query(this, qc); - } - - private EndQuery whereReturnEndQuery(Rank rank) { - rank.setSources(this); - return new EndQuery(rank); - } -} diff --git a/client/src/main/java/com/yahoo/vespa/client/dsl/UserInput.java b/client/src/main/java/com/yahoo/vespa/client/dsl/UserInput.java deleted file mode 100644 index 8fad664b712..00000000000 --- a/client/src/main/java/com/yahoo/vespa/client/dsl/UserInput.java +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.client.dsl; - -import java.util.Collections; -import java.util.Map; -import java.util.UUID; - -public class UserInput extends QueryChain { - - Annotation annotation; // accept only defaultIndex annotation - String value; - String indexField; - String placeholder; // for generating unique param - boolean setDefaultIndex; - - UserInput(Sources sources, String value) { - this(sources, A.empty(), value); - } - - UserInput(Sources sources, Annotation annotation, String value) { - this.sources = sources; - this.annotation = annotation; - this.value = value; - this.nonEmpty = true; - - if (annotation.annotations.containsKey("defaultIndex")) { - setDefaultIndex = true; - indexField = (String) annotation.annotations.get("defaultIndex"); - } else { - indexField = UUID.randomUUID().toString().substring(0, 5); - } - } - - UserInput(String value) { - this(A.empty(), value); - } - - UserInput(Annotation annotation, String value) { - this(null, annotation, value); - } - - public void setIndex(int index) { - placeholder = setDefaultIndex - ? "_" + index + "_" + indexField - : "_" + index; - } - - @Override - public String toString() { - //([{"defaultIndex": "shpdescfree"}](userInput(@_shpdescfree_1))) - return setDefaultIndex - ? String.format("([%s]userInput(@%s))", annotation, placeholder) - : String.format("userInput(@%s)", placeholder); - } - - - Map getParam() { - return Collections.singletonMap(placeholder, value); - } - - @Override - boolean hasPositiveSearchField(String fieldName) { - return !"andnot".equals(this.op) && this.indexField.equals(fieldName); - } - - @Override - boolean hasPositiveSearchField(String fieldName, Object value) { - return hasPositiveSearchField(fieldName) && this.value.equals(value); - } - - @Override - boolean hasNegativeSearchField(String fieldName) { - return "andnot".equals(this.op) && this.indexField.equals(fieldName); - } - - @Override - boolean hasNegativeSearchField(String fieldName, Object value) { - return hasNegativeSearchField(fieldName) && this.value.equals(value); - } -} diff --git a/client/src/main/java/com/yahoo/vespa/client/dsl/Wand.java b/client/src/main/java/com/yahoo/vespa/client/dsl/Wand.java deleted file mode 100644 index f79e07eb656..00000000000 --- a/client/src/main/java/com/yahoo/vespa/client/dsl/Wand.java +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.client.dsl; - -import java.util.List; -import java.util.Map; - -public class Wand extends QueryChain { - - private String fieldName; - private Annotation annotation; - private Object value; - - - Wand(String fieldName, Map weightedSet) { - this.fieldName = fieldName; - this.value = weightedSet; - this.nonEmpty = true; - } - - Wand(String fieldName, List> numeric) { - boolean invalid = numeric.stream().anyMatch(range -> range.size() != 2); - if (invalid) { - throw new IllegalArgumentException("incorrect range format"); - } - - this.fieldName = fieldName; - this.value = numeric; - this.nonEmpty = true; - } - - public Wand annotate(Annotation annotation) { - this.annotation = annotation; - return this; - } - - @Override - public Select getSelect() { - return sources.select; - } - - @Override - boolean hasPositiveSearchField(String fieldName) { - // TODO: implementation - return false; - } - - @Override - boolean hasPositiveSearchField(String fieldName, Object value) { - // TODO: implementation - return false; - } - - @Override - boolean hasNegativeSearchField(String fieldName) { - // TODO: implementation - return false; - } - - @Override - boolean hasNegativeSearchField(String fieldName, Object value) { - // TODO: implementation - return false; - } - - @Override - public String toString() { - boolean hasAnnotation = A.hasAnnotation(annotation); - String s = String.format("wand(%s, %s)", fieldName, Q.gson.toJson(value)); - return hasAnnotation ? String.format("([%s]%s)", annotation, s) : s; - } -} diff --git a/client/src/main/java/com/yahoo/vespa/client/dsl/WeakAnd.java b/client/src/main/java/com/yahoo/vespa/client/dsl/WeakAnd.java deleted file mode 100644 index 43e7dd10ce2..00000000000 --- a/client/src/main/java/com/yahoo/vespa/client/dsl/WeakAnd.java +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.client.dsl; - -import java.util.stream.Collectors; - -public class WeakAnd extends QueryChain { - - private String fieldName; - private Annotation annotation; - private Query value; - - - WeakAnd(String fieldName, Query value) { - this.fieldName = fieldName; - this.value = value; - this.nonEmpty = true; - } - - public WeakAnd annotate(Annotation annotation) { - this.annotation = annotation; - return this; - } - - @Override - public Select getSelect() { - return sources.select; - } - - @Override - boolean hasPositiveSearchField(String fieldName) { - // TODO: implementation - return false; - } - - @Override - boolean hasPositiveSearchField(String fieldName, Object value) { - // TODO: implementation - return false; - } - - @Override - boolean hasNegativeSearchField(String fieldName) { - // TODO: implementation - return false; - } - - @Override - boolean hasNegativeSearchField(String fieldName, Object value) { - // TODO: implementation - return false; - } - - @Override - public String toString() { - boolean hasAnnotation = A.hasAnnotation(annotation); - String - s = - String.format("weakAnd(%s, %s)", fieldName, - value.queries.stream().map(Object::toString).collect(Collectors.joining(", "))); - return hasAnnotation ? String.format("([%s]%s)", annotation, s) : s; - } -} - diff --git a/client/src/main/java/com/yahoo/vespa/client/dsl/WeightedSet.java b/client/src/main/java/com/yahoo/vespa/client/dsl/WeightedSet.java deleted file mode 100644 index 31d435e22bb..00000000000 --- a/client/src/main/java/com/yahoo/vespa/client/dsl/WeightedSet.java +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.client.dsl; - -import java.util.Map; - -public class WeightedSet extends QueryChain { - - private String fieldName; - private Map weightedSet; - - WeightedSet(String fieldName, Map weightedSet) { - this.fieldName = fieldName; - this.weightedSet = weightedSet; - this.nonEmpty = true; - } - - @Override - public Select getSelect() { - return sources.select; - } - - @Override - public String toString() { - return "weightedSet(" + fieldName + ", " + Q.gson.toJson(weightedSet) + ")"; - } - - @Override - boolean hasPositiveSearchField(String fieldName) { - // TODO: implementation - return false; - } - - @Override - boolean hasPositiveSearchField(String fieldName, Object value) { - // TODO: implementation - return false; - } - - @Override - boolean hasNegativeSearchField(String fieldName) { - // TODO: implementation - return false; - } - - @Override - boolean hasNegativeSearchField(String fieldName, Object value) { - // TODO: implementation - return false; - } -} diff --git a/client/src/test/groovy/ai/vespa/client/dsl/QTest.groovy b/client/src/test/groovy/ai/vespa/client/dsl/QTest.groovy new file mode 100644 index 00000000000..e363a9bcc13 --- /dev/null +++ b/client/src/test/groovy/ai/vespa/client/dsl/QTest.groovy @@ -0,0 +1,583 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.client.dsl + +import spock.lang.Specification + +class QTest extends Specification { + + def "select specific fields"() { + given: + def q = Q.select("f1", "f2") + .from("sd1") + .where("f1").contains("v1") + .semicolon() + .build() + + expect: + q == """yql=select f1, f2 from sd1 where f1 contains "v1";""" + } + + def "select from specific sources"() { + given: + def q = Q.select("*") + .from("sd1") + .where("f1").contains("v1") + .semicolon() + .build() + + expect: + q == """yql=select * from sd1 where f1 contains "v1";""" + } + + def "select from multiples sources"() { + given: + def q = Q.select("*") + .from("sd1", "sd2") + .where("f1").contains("v1") + .semicolon() + .build() + + expect: + q == """yql=select * from sources sd1, sd2 where f1 contains "v1";""" + } + + def "basic 'and', 'andnot', 'or', 'offset', 'limit', 'param', 'order by', and 'contains'"() { + given: + def q = Q.select("*") + .from("sd1") + .where("f1").contains("v1") + .and("f2").contains("v2") + .or("f3").contains("v3") + .andnot("f4").contains("v4") + .offset(1) + .limit(2) + .timeout(3) + .orderByDesc("f1") + .orderByAsc("f2") + .semicolon() + .param("paramk1", "paramv1") + .build() + + expect: + q == """yql=select * from sd1 where f1 contains "v1" and f2 contains "v2" or f3 contains "v3" and !(f4 contains "v4") order by f1 desc, f2 asc, limit 2 offset 1 timeout 3;¶mk1=paramv1""" + } + + def "matches"() { + given: + def q = Q.select("*") + .from("sd1") + .where("f1").matches("v1") + .and("f2").matches("v2") + .or("f3").matches("v3") + .andnot("f4").matches("v4") + .semicolon() + .build() + + expect: + q == """yql=select * from sd1 where f1 matches "v1" and f2 matches "v2" or f3 matches "v3" and !(f4 matches "v4");""" + } + + def "numeric operations"() { + given: + def q = Q.select("*") + .from("sd1") + .where("f1").le(1) + .and("f2").lt(2) + .and("f3").ge(3) + .and("f4").gt(4) + .and("f5").eq(5) + .and("f6").inRange(6, 7) + .semicolon() + .build() + + expect: + q == """yql=select * from sd1 where f1 <= 1 and f2 < 2 and f3 >= 3 and f4 > 4 and f5 = 5 and range(f6, 6, 7);""" + } + + def "long numeric operations"() { + given: + def q = Q.select("*") + .from("sd1") + .where("f1").le(1L) + .and("f2").lt(2L) + .and("f3").ge(3L) + .and("f4").gt(4L) + .and("f5").eq(5L) + .and("f6").inRange(6L, 7L) + .semicolon() + .build() + + expect: + q == """yql=select * from sd1 where f1 <= 1L and f2 < 2L and f3 >= 3L and f4 > 4L and f5 = 5L and range(f6, 6L, 7L);""" + } + + def "nested queries"() { + given: + def q = Q.select("*") + .from("sd1") + .where("f1").contains("1") + .andnot(Q.p(Q.p("f2").contains("2").and("f3").contains("3")) + .or(Q.p("f2").contains("4").andnot("f3").contains("5"))) + .semicolon() + .build() + + expect: + q == """yql=select * from sd1 where f1 contains "1" and !((f2 contains "2" and f3 contains "3") or (f2 contains "4" and !(f3 contains "5")));""" + } + + def "userInput (with and with out defaultIndex)"() { + given: + def q = Q.select("*") + .from("sd1") + .where(Q.ui("value")) + .and(Q.ui("index", "value2")) + .semicolon() + .build() + + expect: + q == """yql=select * from sd1 where userInput(@_1) and ([{"defaultIndex":"index"}]userInput(@_2_index));&_2_index=value2&_1=value""" + } + + def "dot product"() { + given: + def q = Q.select("*") + .from("sd1") + .where(Q.dotPdt("f1", [a: 1, b: 2, c: 3])) + .and("f2").contains("1") + .semicolon() + .build() + + expect: + q == """yql=select * from sd1 where dotProduct(f1, {"a":1,"b":2,"c":3}) and f2 contains "1";""" + } + + def "weighted set"() { + given: + def q = Q.select("*") + .from("sd1") + .where(Q.wtdSet("f1", [a: 1, b: 2, c: 3])) + .and("f2").contains("1") + .semicolon() + .build() + + expect: + q == """yql=select * from sd1 where weightedSet(f1, {"a":1,"b":2,"c":3}) and f2 contains "1";""" + } + + def "non empty"() { + given: + def q = Q.select("*") + .from("sd1") + .where(Q.nonEmpty(Q.p("f1").contains("v1"))) + .and("f2").contains("v2") + .semicolon() + .build() + + expect: + q == """yql=select * from sd1 where nonEmpty(f1 contains "v1") and f2 contains "v2";""" + } + + + def "wand (with and without annotation)"() { + given: + def q = Q.select("*") + .from("sd1") + .where(Q.wand("f1", [a: 1, b: 2, c: 3])) + .and(Q.wand("f2", [[1, 1], [2, 2]])) + .and( + Q.wand("f3", [[1, 1], [2, 2]]) + .annotate(A.a("scoreThreshold", 0.13)) + ) + .semicolon() + .build() + + expect: + q == """yql=select * from sd1 where wand(f1, {"a":1,"b":2,"c":3}) and wand(f2, [[1,1],[2,2]]) and ([{"scoreThreshold":0.13}]wand(f3, [[1,1],[2,2]]));""" + } + + def "weak and (with and without annotation)"() { + given: + def q = Q.select("*") + .from("sd1") + .where(Q.weakand("f1", Q.p("f1").contains("v1").and("f2").contains("v2"))) + .and(Q.weakand("f3", Q.p("f1").contains("v1").and("f2").contains("v2")) + .annotate(A.a("scoreThreshold", 0.13)) + ) + .semicolon() + .build() + + expect: + q == """yql=select * from sd1 where weakAnd(f1, f1 contains "v1", f2 contains "v2") and ([{"scoreThreshold":0.13}]weakAnd(f3, f1 contains "v1", f2 contains "v2"));""" + } + + def "rank with only query"() { + given: + def q = Q.select("*") + .from("sd1") + .where(Q.rank( + Q.p("f1").contains("v1") + ) + ) + .semicolon() + .build() + + expect: + q == """yql=select * from sd1 where rank(f1 contains "v1");""" + } + + def "rank"() { + given: + def q = Q.select("*") + .from("sd1") + .where(Q.rank( + Q.p("f1").contains("v1"), + Q.p("f2").contains("v2"), + Q.p("f3").eq(3)) + ) + .semicolon() + .build() + + expect: + q == """yql=select * from sd1 where rank(f1 contains "v1", f2 contains "v2", f3 = 3);""" + } + + def "rank with rank query array"() { + given: + Query[] ranks = [Q.p("f2").contains("v2"), Q.p("f3").eq(3)].toArray() + def q = Q.select("*") + .from("sd1") + .where(Q.rank( + Q.p("f1").contains("v1"), + ranks) + ) + .semicolon() + .build() + + expect: + q == """yql=select * from sd1 where rank(f1 contains "v1", f2 contains "v2", f3 = 3);""" + } + + def "string/function annotations"() { + given: + def q = Q.select("*") + .from("sd1") + .where("f1").contains(annotation, "v1") + .semicolon() + .build() + + expect: + q == """yql=select * from sd1 where f1 contains (${expected}"v1");""" + + where: + annotation | expected + A.filter() | """[{"filter":true}]""" + A.defaultIndex("idx") | """[{"defaultIndex":"idx"}]""" + A.a([a1: [k1: "v1", k2: 2]]) | """[{"a1":{"k1":"v1","k2":2}}]""" + } + + def "sub-expression annotations"() { + given: + def q = Q.select("*") + .from("sd1") + .where("f1").contains("v1").annotate(A.a("ak1", "av1")) + .semicolon() + .build() + + expect: + q == """yql=select * from sd1 where ([{"ak1":"av1"}](f1 contains "v1"));""" + } + + def "sub-expressions annotations (annotate in the middle of query)"() { + given: + def q = Q.select("*") + .from("sd1") + .where(Q.p("f1").contains("v1").annotate(A.a("ak1", "av1")).and("f2").contains("v2")) + .semicolon() + .build() + + expect: + q == """yql=select * from sd1 where ([{"ak1":"av1"}](f1 contains "v1" and f2 contains "v2"));""" + } + + def "sub-expressions annotations (annotate in nested queries)"() { + given: + def q = Q.select("*") + .from("sd1") + .where(Q.p( + Q.p("f1").contains("v1").annotate(A.a("ak1", "av1"))) + .and("f2").contains("v2") + ) + .semicolon() + .build() + + expect: + q == """yql=select * from sd1 where (([{"ak1":"av1"}](f1 contains "v1")) and f2 contains "v2");""" + } + + def "build query which created from Q.b without select and sources"() { + given: + def q = Q.p("f1").contains("v1") + .semicolon() + .build() + + expect: + q == """yql=select * from sources * where f1 contains "v1";""" + } + + def "order by"() { + given: + def q = Q.p("f1").contains("v1") + .orderByAsc("f2") + .orderByAsc(A.a([function: "uca", locale: "en_US", strength: "IDENTICAL"]), "f3") + .orderByDesc("f4") + .orderByDesc(A.a([function: "lowercase"]), "f5") + .semicolon() + .build() + + expect: + q == """yql=select * from sources * where f1 contains "v1" order by f2 asc, [{"function":"uca","locale":"en_US","strength":"IDENTICAL"}]f3 asc, f4 desc, [{"function":"lowercase"}]f5 desc;""" + } + + def "contains sameElement"() { + given: + def q = Q.p("f1").containsSameElement(Q.p("stime").le(1).and("etime").gt(2)) + .semicolon() + .build() + + expect: + q == """yql=select * from sources * where f1 contains sameElement(stime <= 1, etime > 2);""" + } + + def "contains phrase/near/onear/equiv"() { + given: + def funcName = "contains${operator.capitalize()}" + def q1 = Q.p("f1")."$funcName"("p1", "p2", "p3") + .semicolon() + .build() + def q2 = Q.p("f1")."$funcName"(["p1", "p2", "p3"]) + .semicolon() + .build() + + expect: + q1 == """yql=select * from sources * where f1 contains ${operator}("p1", "p2", "p3");""" + q2 == """yql=select * from sources * where f1 contains ${operator}("p1", "p2", "p3");""" + + where: + operator | _ + "phrase" | _ + "near" | _ + "onear" | _ + "equiv" | _ + } + + def "contains uri"() { + given: + def q = Q.p("f1").containsUri("https://test.uri") + .semicolon() + .build() + + expect: + q == """yql=select * from sources * where f1 contains uri("https://test.uri");""" + } + + def "contains uri with annotation"() { + given: + def q = Q.p("f1").containsUri(A.a("key", "value"), "https://test.uri") + .semicolon() + .build() + + expect: + q == """yql=select * from sources * where f1 contains ([{"key":"value"}]uri("https://test.uri"));""" + } + + def "use contains instead of contains equiv when input size is 1"() { + def q = Q.p("f1").containsEquiv(["p1"]) + .semicolon() + .build() + + expect: + q == """yql=select * from sources * where f1 contains "p1";""" + } + + def "contains phrase/near/onear/equiv empty list should throw illegal argument exception"() { + given: + def funcName = "contains${operator.capitalize()}" + + when: + def q = Q.p("f1")."$funcName"([]) + .semicolon() + .build() + + then: + thrown(IllegalArgumentException) + + where: + operator | _ + "phrase" | _ + "near" | _ + "onear" | _ + "equiv" | _ + } + + + def "contains near/onear with annotation"() { + given: + def funcName = "contains${operator.capitalize()}" + def q = Q.p("f1")."$funcName"(A.a("distance", 5), "p1", "p2", "p3") + .semicolon() + .build() + + expect: + q == """yql=select * from sources * where f1 contains ([{"distance":5}]${operator}("p1", "p2", "p3"));""" + + where: + operator | _ + "near" | _ + "onear" | _ + } + + def "basic group syntax"() { + /* + example from vespa document: + https://docs.vespa.ai/documentation/grouping.html + all( group(a) max(5) each(output(count()) + all(max(1) each(output(summary()))) + all(group(b) each(output(count()) + all(max(1) each(output(summary()))) + all(group(c) each(output(count()) + all(max(1) each(output(summary())))))))) ); + */ + given: + def q = Q.p("f1").contains("v1") + .group( + G.all(G.group("a"), G.maxRtn(5), G.each(G.output(G.count()), + G.all(G.maxRtn(1), G.each(G.output(G.summary()))), + G.all(G.group("b"), G.each(G.output(G.count()), + G.all(G.maxRtn(1), G.each(G.output(G.summary()))), + G.all(G.group("c"), G.each(G.output(G.count()), + G.all(G.maxRtn(1), G.each(G.output(G.summary()))) + )) + )) + )) + ) + .semicolon() + .build() + + expect: + q == """yql=select * from sources * where f1 contains "v1" | all(group(a) max(5) each(output(count()) all(max(1) each(output(summary()))) all(group(b) each(output(count()) all(max(1) each(output(summary()))) all(group(c) each(output(count()) all(max(1) each(output(summary())))))))));""" + } + + def "set group syntax string directly"() { + /* + example from vespa document: + https://docs.vespa.ai/documentation/grouping.html + all( group(a) max(5) each(output(count()) + all(max(1) each(output(summary()))) + all(group(b) each(output(count()) + all(max(1) each(output(summary()))) + all(group(c) each(output(count()) + all(max(1) each(output(summary())))))))) ); + */ + given: + def q = Q.p("f1").contains("v1") + .group("all(group(a) max(5) each(output(count()) all(max(1) each(output(summary()))) all(group(b) each(output(count()) all(max(1) each(output(summary()))) all(group(c) each(output(count()) all(max(1) each(output(summary())))))))))") + .semicolon() + .build() + + expect: + q == """yql=select * from sources * where f1 contains "v1" | all(group(a) max(5) each(output(count()) all(max(1) each(output(summary()))) all(group(b) each(output(count()) all(max(1) each(output(summary()))) all(group(c) each(output(count()) all(max(1) each(output(summary())))))))));""" + } + + def "arbitrary annotations"() { + given: + def a = A.a("a1", "v1", "a2", 2, "a3", [k: "v", k2: 1], "a4", 4D, "a5", [1, 2, 3]) + expect: + a.toString() == """{"a1":"v1","a2":2,"a3":{"k":"v","k2":1},"a4":4.0,"a5":[1,2,3]}""" + } + + def "test programmability"() { + given: + def map = [a: "1", b: "2", c: "3"] + + when: + Query q = map + .entrySet() + .stream() + .map { entry -> Q.p(entry.key).contains(entry.value) } + .reduce { q1, q2 -> q1.and(q2) } + .get() + + then: + q.semicolon().build() == """yql=select * from sources * where a contains "1" and b contains "2" and c contains "3";""" + } + + def "test programmability 2"() { + given: + def map = [a: "1", b: "2", c: "3"] + def q = Q.p() + + when: + map.each { k, v -> + q.and(Q.p(k).contains(v)) + } + + then: + q.semicolon().build() == """yql=select * from sources * where a contains "1" and b contains "2" and c contains "3";""" + } + + def "empty queries should not print out"() { + given: + def q = Q.p(Q.p(Q.p().andnot(Q.p()).and(Q.p()))).and("a").contains("1").semicolon().build() + + expect: + q == """yql=select * from sources * where a contains "1";""" + } + + def "validate positive search term of strings"() { + given: + def q = Q.p(Q.p("k1").contains("v1").and("k2").contains("v2").andnot("k3").contains("v3")) + .andnot(Q.p("nk1").contains("nv1").and("nk2").contains("nv2").andnot("nk3").contains("nv3")) + .and(Q.p("k4").contains("v4") + .andnot(Q.p("k5").contains("v5").andnot("k6").contains("v6")) + ) + + expect: + q.hasPositiveSearchField("k1") + q.hasPositiveSearchField("k2") + q.hasPositiveSearchField("nk3") + q.hasPositiveSearchField("k6") + q.hasPositiveSearchField("k6", "v6") + !q.hasPositiveSearchField("k6", "v5") + + q.hasNegativeSearchField("k3") + q.hasNegativeSearchField("nk1") + q.hasNegativeSearchField("nk2") + q.hasNegativeSearchField("k5") + q.hasNegativeSearchField("k5", "v5") + !q.hasNegativeSearchField("k5", "v4") + } + + def "validate positive search term of user input"() { + given: + def q = Q.p(Q.ui("k1", "v1")).and(Q.ui("k2", "v2")).andnot(Q.ui("k3", "v3")) + .andnot(Q.p(Q.ui("nk1", "nv1")).and(Q.ui("nk2", "nv2")).andnot(Q.ui("nk3", "nv3"))) + .and(Q.p(Q.ui("k4", "v4")) + .andnot(Q.p(Q.ui("k5", "v5")).andnot(Q.ui("k6", "v6"))) + ) + + expect: + q.hasPositiveSearchField("k1") + q.hasPositiveSearchField("k2") + q.hasPositiveSearchField("nk3") + q.hasPositiveSearchField("k6") + q.hasPositiveSearchField("k6", "v6") + !q.hasPositiveSearchField("k6", "v5") + + q.hasNegativeSearchField("k3") + q.hasNegativeSearchField("nk1") + q.hasNegativeSearchField("nk2") + q.hasNegativeSearchField("k5") + q.hasNegativeSearchField("k5", "v5") + !q.hasNegativeSearchField("k5", "v4") + } +} diff --git a/client/src/test/groovy/com/yahoo/vespa/client/dsl/QTest.groovy b/client/src/test/groovy/com/yahoo/vespa/client/dsl/QTest.groovy deleted file mode 100644 index ef87bdd3688..00000000000 --- a/client/src/test/groovy/com/yahoo/vespa/client/dsl/QTest.groovy +++ /dev/null @@ -1,583 +0,0 @@ -// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.client.dsl - -import spock.lang.Specification - -class QTest extends Specification { - - def "select specific fields"() { - given: - def q = Q.select("f1", "f2") - .from("sd1") - .where("f1").contains("v1") - .semicolon() - .build() - - expect: - q == """yql=select f1, f2 from sd1 where f1 contains "v1";""" - } - - def "select from specific sources"() { - given: - def q = Q.select("*") - .from("sd1") - .where("f1").contains("v1") - .semicolon() - .build() - - expect: - q == """yql=select * from sd1 where f1 contains "v1";""" - } - - def "select from multiples sources"() { - given: - def q = Q.select("*") - .from("sd1", "sd2") - .where("f1").contains("v1") - .semicolon() - .build() - - expect: - q == """yql=select * from sources sd1, sd2 where f1 contains "v1";""" - } - - def "basic 'and', 'andnot', 'or', 'offset', 'limit', 'param', 'order by', and 'contains'"() { - given: - def q = Q.select("*") - .from("sd1") - .where("f1").contains("v1") - .and("f2").contains("v2") - .or("f3").contains("v3") - .andnot("f4").contains("v4") - .offset(1) - .limit(2) - .timeout(3) - .orderByDesc("f1") - .orderByAsc("f2") - .semicolon() - .param("paramk1", "paramv1") - .build() - - expect: - q == """yql=select * from sd1 where f1 contains "v1" and f2 contains "v2" or f3 contains "v3" and !(f4 contains "v4") order by f1 desc, f2 asc, limit 2 offset 1 timeout 3;¶mk1=paramv1""" - } - - def "matches"() { - given: - def q = Q.select("*") - .from("sd1") - .where("f1").matches("v1") - .and("f2").matches("v2") - .or("f3").matches("v3") - .andnot("f4").matches("v4") - .semicolon() - .build() - - expect: - q == """yql=select * from sd1 where f1 matches "v1" and f2 matches "v2" or f3 matches "v3" and !(f4 matches "v4");""" - } - - def "numeric operations"() { - given: - def q = Q.select("*") - .from("sd1") - .where("f1").le(1) - .and("f2").lt(2) - .and("f3").ge(3) - .and("f4").gt(4) - .and("f5").eq(5) - .and("f6").inRange(6, 7) - .semicolon() - .build() - - expect: - q == """yql=select * from sd1 where f1 <= 1 and f2 < 2 and f3 >= 3 and f4 > 4 and f5 = 5 and range(f6, 6, 7);""" - } - - def "long numeric operations"() { - given: - def q = Q.select("*") - .from("sd1") - .where("f1").le(1L) - .and("f2").lt(2L) - .and("f3").ge(3L) - .and("f4").gt(4L) - .and("f5").eq(5L) - .and("f6").inRange(6L, 7L) - .semicolon() - .build() - - expect: - q == """yql=select * from sd1 where f1 <= 1L and f2 < 2L and f3 >= 3L and f4 > 4L and f5 = 5L and range(f6, 6L, 7L);""" - } - - def "nested queries"() { - given: - def q = Q.select("*") - .from("sd1") - .where("f1").contains("1") - .andnot(Q.p(Q.p("f2").contains("2").and("f3").contains("3")) - .or(Q.p("f2").contains("4").andnot("f3").contains("5"))) - .semicolon() - .build() - - expect: - q == """yql=select * from sd1 where f1 contains "1" and !((f2 contains "2" and f3 contains "3") or (f2 contains "4" and !(f3 contains "5")));""" - } - - def "userInput (with and with out defaultIndex)"() { - given: - def q = Q.select("*") - .from("sd1") - .where(Q.ui("value")) - .and(Q.ui("index", "value2")) - .semicolon() - .build() - - expect: - q == """yql=select * from sd1 where userInput(@_1) and ([{"defaultIndex":"index"}]userInput(@_2_index));&_2_index=value2&_1=value""" - } - - def "dot product"() { - given: - def q = Q.select("*") - .from("sd1") - .where(Q.dotPdt("f1", [a: 1, b: 2, c: 3])) - .and("f2").contains("1") - .semicolon() - .build() - - expect: - q == """yql=select * from sd1 where dotProduct(f1, {"a":1,"b":2,"c":3}) and f2 contains "1";""" - } - - def "weighted set"() { - given: - def q = Q.select("*") - .from("sd1") - .where(Q.wtdSet("f1", [a: 1, b: 2, c: 3])) - .and("f2").contains("1") - .semicolon() - .build() - - expect: - q == """yql=select * from sd1 where weightedSet(f1, {"a":1,"b":2,"c":3}) and f2 contains "1";""" - } - - def "non empty"() { - given: - def q = Q.select("*") - .from("sd1") - .where(Q.nonEmpty(Q.p("f1").contains("v1"))) - .and("f2").contains("v2") - .semicolon() - .build() - - expect: - q == """yql=select * from sd1 where nonEmpty(f1 contains "v1") and f2 contains "v2";""" - } - - - def "wand (with and without annotation)"() { - given: - def q = Q.select("*") - .from("sd1") - .where(Q.wand("f1", [a: 1, b: 2, c: 3])) - .and(Q.wand("f2", [[1, 1], [2, 2]])) - .and( - Q.wand("f3", [[1, 1], [2, 2]]) - .annotate(A.a("scoreThreshold", 0.13)) - ) - .semicolon() - .build() - - expect: - q == """yql=select * from sd1 where wand(f1, {"a":1,"b":2,"c":3}) and wand(f2, [[1,1],[2,2]]) and ([{"scoreThreshold":0.13}]wand(f3, [[1,1],[2,2]]));""" - } - - def "weak and (with and without annotation)"() { - given: - def q = Q.select("*") - .from("sd1") - .where(Q.weakand("f1", Q.p("f1").contains("v1").and("f2").contains("v2"))) - .and(Q.weakand("f3", Q.p("f1").contains("v1").and("f2").contains("v2")) - .annotate(A.a("scoreThreshold", 0.13)) - ) - .semicolon() - .build() - - expect: - q == """yql=select * from sd1 where weakAnd(f1, f1 contains "v1", f2 contains "v2") and ([{"scoreThreshold":0.13}]weakAnd(f3, f1 contains "v1", f2 contains "v2"));""" - } - - def "rank with only query"() { - given: - def q = Q.select("*") - .from("sd1") - .where(Q.rank( - Q.p("f1").contains("v1") - ) - ) - .semicolon() - .build() - - expect: - q == """yql=select * from sd1 where rank(f1 contains "v1");""" - } - - def "rank"() { - given: - def q = Q.select("*") - .from("sd1") - .where(Q.rank( - Q.p("f1").contains("v1"), - Q.p("f2").contains("v2"), - Q.p("f3").eq(3)) - ) - .semicolon() - .build() - - expect: - q == """yql=select * from sd1 where rank(f1 contains "v1", f2 contains "v2", f3 = 3);""" - } - - def "rank with rank query array"() { - given: - Query[] ranks = [Q.p("f2").contains("v2"), Q.p("f3").eq(3)].toArray() - def q = Q.select("*") - .from("sd1") - .where(Q.rank( - Q.p("f1").contains("v1"), - ranks) - ) - .semicolon() - .build() - - expect: - q == """yql=select * from sd1 where rank(f1 contains "v1", f2 contains "v2", f3 = 3);""" - } - - def "string/function annotations"() { - given: - def q = Q.select("*") - .from("sd1") - .where("f1").contains(annotation, "v1") - .semicolon() - .build() - - expect: - q == """yql=select * from sd1 where f1 contains (${expected}"v1");""" - - where: - annotation | expected - A.filter() | """[{"filter":true}]""" - A.defaultIndex("idx") | """[{"defaultIndex":"idx"}]""" - A.a([a1: [k1: "v1", k2: 2]]) | """[{"a1":{"k1":"v1","k2":2}}]""" - } - - def "sub-expression annotations"() { - given: - def q = Q.select("*") - .from("sd1") - .where("f1").contains("v1").annotate(A.a("ak1", "av1")) - .semicolon() - .build() - - expect: - q == """yql=select * from sd1 where ([{"ak1":"av1"}](f1 contains "v1"));""" - } - - def "sub-expressions annotations (annotate in the middle of query)"() { - given: - def q = Q.select("*") - .from("sd1") - .where(Q.p("f1").contains("v1").annotate(A.a("ak1", "av1")).and("f2").contains("v2")) - .semicolon() - .build() - - expect: - q == """yql=select * from sd1 where ([{"ak1":"av1"}](f1 contains "v1" and f2 contains "v2"));""" - } - - def "sub-expressions annotations (annotate in nested queries)"() { - given: - def q = Q.select("*") - .from("sd1") - .where(Q.p( - Q.p("f1").contains("v1").annotate(A.a("ak1", "av1"))) - .and("f2").contains("v2") - ) - .semicolon() - .build() - - expect: - q == """yql=select * from sd1 where (([{"ak1":"av1"}](f1 contains "v1")) and f2 contains "v2");""" - } - - def "build query which created from Q.b without select and sources"() { - given: - def q = Q.p("f1").contains("v1") - .semicolon() - .build() - - expect: - q == """yql=select * from sources * where f1 contains "v1";""" - } - - def "order by"() { - given: - def q = Q.p("f1").contains("v1") - .orderByAsc("f2") - .orderByAsc(A.a([function: "uca", locale: "en_US", strength: "IDENTICAL"]), "f3") - .orderByDesc("f4") - .orderByDesc(A.a([function: "lowercase"]), "f5") - .semicolon() - .build() - - expect: - q == """yql=select * from sources * where f1 contains "v1" order by f2 asc, [{"function":"uca","locale":"en_US","strength":"IDENTICAL"}]f3 asc, f4 desc, [{"function":"lowercase"}]f5 desc;""" - } - - def "contains sameElement"() { - given: - def q = Q.p("f1").containsSameElement(Q.p("stime").le(1).and("etime").gt(2)) - .semicolon() - .build() - - expect: - q == """yql=select * from sources * where f1 contains sameElement(stime <= 1, etime > 2);""" - } - - def "contains phrase/near/onear/equiv"() { - given: - def funcName = "contains${operator.capitalize()}" - def q1 = Q.p("f1")."$funcName"("p1", "p2", "p3") - .semicolon() - .build() - def q2 = Q.p("f1")."$funcName"(["p1", "p2", "p3"]) - .semicolon() - .build() - - expect: - q1 == """yql=select * from sources * where f1 contains ${operator}("p1", "p2", "p3");""" - q2 == """yql=select * from sources * where f1 contains ${operator}("p1", "p2", "p3");""" - - where: - operator | _ - "phrase" | _ - "near" | _ - "onear" | _ - "equiv" | _ - } - - def "contains uri"() { - given: - def q = Q.p("f1").containsUri("https://test.uri") - .semicolon() - .build() - - expect: - q == """yql=select * from sources * where f1 contains uri("https://test.uri");""" - } - - def "contains uri with annotation"() { - given: - def q = Q.p("f1").containsUri(A.a("key", "value"), "https://test.uri") - .semicolon() - .build() - - expect: - q == """yql=select * from sources * where f1 contains ([{"key":"value"}]uri("https://test.uri"));""" - } - - def "use contains instead of contains equiv when input size is 1"() { - def q = Q.p("f1").containsEquiv(["p1"]) - .semicolon() - .build() - - expect: - q == """yql=select * from sources * where f1 contains "p1";""" - } - - def "contains phrase/near/onear/equiv empty list should throw illegal argument exception"() { - given: - def funcName = "contains${operator.capitalize()}" - - when: - def q = Q.p("f1")."$funcName"([]) - .semicolon() - .build() - - then: - thrown(IllegalArgumentException) - - where: - operator | _ - "phrase" | _ - "near" | _ - "onear" | _ - "equiv" | _ - } - - - def "contains near/onear with annotation"() { - given: - def funcName = "contains${operator.capitalize()}" - def q = Q.p("f1")."$funcName"(A.a("distance", 5), "p1", "p2", "p3") - .semicolon() - .build() - - expect: - q == """yql=select * from sources * where f1 contains ([{"distance":5}]${operator}("p1", "p2", "p3"));""" - - where: - operator | _ - "near" | _ - "onear" | _ - } - - def "basic group syntax"() { - /* - example from vespa document: - https://docs.vespa.ai/documentation/grouping.html - all( group(a) max(5) each(output(count()) - all(max(1) each(output(summary()))) - all(group(b) each(output(count()) - all(max(1) each(output(summary()))) - all(group(c) each(output(count()) - all(max(1) each(output(summary())))))))) ); - */ - given: - def q = Q.p("f1").contains("v1") - .group( - G.all(G.group("a"), G.maxRtn(5), G.each(G.output(G.count()), - G.all(G.maxRtn(1), G.each(G.output(G.summary()))), - G.all(G.group("b"), G.each(G.output(G.count()), - G.all(G.maxRtn(1), G.each(G.output(G.summary()))), - G.all(G.group("c"), G.each(G.output(G.count()), - G.all(G.maxRtn(1), G.each(G.output(G.summary()))) - )) - )) - )) - ) - .semicolon() - .build() - - expect: - q == """yql=select * from sources * where f1 contains "v1" | all(group(a) max(5) each(output(count()) all(max(1) each(output(summary()))) all(group(b) each(output(count()) all(max(1) each(output(summary()))) all(group(c) each(output(count()) all(max(1) each(output(summary())))))))));""" - } - - def "set group syntax string directly"() { - /* - example from vespa document: - https://docs.vespa.ai/documentation/grouping.html - all( group(a) max(5) each(output(count()) - all(max(1) each(output(summary()))) - all(group(b) each(output(count()) - all(max(1) each(output(summary()))) - all(group(c) each(output(count()) - all(max(1) each(output(summary())))))))) ); - */ - given: - def q = Q.p("f1").contains("v1") - .group("all(group(a) max(5) each(output(count()) all(max(1) each(output(summary()))) all(group(b) each(output(count()) all(max(1) each(output(summary()))) all(group(c) each(output(count()) all(max(1) each(output(summary())))))))))") - .semicolon() - .build() - - expect: - q == """yql=select * from sources * where f1 contains "v1" | all(group(a) max(5) each(output(count()) all(max(1) each(output(summary()))) all(group(b) each(output(count()) all(max(1) each(output(summary()))) all(group(c) each(output(count()) all(max(1) each(output(summary())))))))));""" - } - - def "arbitrary annotations"() { - given: - def a = A.a("a1", "v1", "a2", 2, "a3", [k: "v", k2: 1], "a4", 4D, "a5", [1, 2, 3]) - expect: - a.toString() == """{"a1":"v1","a2":2,"a3":{"k":"v","k2":1},"a4":4.0,"a5":[1,2,3]}""" - } - - def "test programmability"() { - given: - def map = [a: "1", b: "2", c: "3"] - - when: - Query q = map - .entrySet() - .stream() - .map { entry -> Q.p(entry.key).contains(entry.value) } - .reduce { q1, q2 -> q1.and(q2) } - .get() - - then: - q.semicolon().build() == """yql=select * from sources * where a contains "1" and b contains "2" and c contains "3";""" - } - - def "test programmability 2"() { - given: - def map = [a: "1", b: "2", c: "3"] - def q = Q.p() - - when: - map.each { k, v -> - q.and(Q.p(k).contains(v)) - } - - then: - q.semicolon().build() == """yql=select * from sources * where a contains "1" and b contains "2" and c contains "3";""" - } - - def "empty queries should not print out"() { - given: - def q = Q.p(Q.p(Q.p().andnot(Q.p()).and(Q.p()))).and("a").contains("1").semicolon().build() - - expect: - q == """yql=select * from sources * where a contains "1";""" - } - - def "validate positive search term of strings"() { - given: - def q = Q.p(Q.p("k1").contains("v1").and("k2").contains("v2").andnot("k3").contains("v3")) - .andnot(Q.p("nk1").contains("nv1").and("nk2").contains("nv2").andnot("nk3").contains("nv3")) - .and(Q.p("k4").contains("v4") - .andnot(Q.p("k5").contains("v5").andnot("k6").contains("v6")) - ) - - expect: - q.hasPositiveSearchField("k1") - q.hasPositiveSearchField("k2") - q.hasPositiveSearchField("nk3") - q.hasPositiveSearchField("k6") - q.hasPositiveSearchField("k6", "v6") - !q.hasPositiveSearchField("k6", "v5") - - q.hasNegativeSearchField("k3") - q.hasNegativeSearchField("nk1") - q.hasNegativeSearchField("nk2") - q.hasNegativeSearchField("k5") - q.hasNegativeSearchField("k5", "v5") - !q.hasNegativeSearchField("k5", "v4") - } - - def "validate positive search term of user input"() { - given: - def q = Q.p(Q.ui("k1", "v1")).and(Q.ui("k2", "v2")).andnot(Q.ui("k3", "v3")) - .andnot(Q.p(Q.ui("nk1", "nv1")).and(Q.ui("nk2", "nv2")).andnot(Q.ui("nk3", "nv3"))) - .and(Q.p(Q.ui("k4", "v4")) - .andnot(Q.p(Q.ui("k5", "v5")).andnot(Q.ui("k6", "v6"))) - ) - - expect: - q.hasPositiveSearchField("k1") - q.hasPositiveSearchField("k2") - q.hasPositiveSearchField("nk3") - q.hasPositiveSearchField("k6") - q.hasPositiveSearchField("k6", "v6") - !q.hasPositiveSearchField("k6", "v5") - - q.hasNegativeSearchField("k3") - q.hasNegativeSearchField("nk1") - q.hasNegativeSearchField("nk2") - q.hasNegativeSearchField("k5") - q.hasNegativeSearchField("k5", "v5") - !q.hasNegativeSearchField("k5", "v4") - } -} -- cgit v1.2.3