summaryrefslogtreecommitdiffstats
path: root/container-search/src/test/java/com/yahoo/search/grouping/request
diff options
context:
space:
mode:
authorJon Bratseth <bratseth@yahoo-inc.com>2016-06-15 23:09:44 +0200
committerJon Bratseth <bratseth@yahoo-inc.com>2016-06-15 23:09:44 +0200
commit72231250ed81e10d66bfe70701e64fa5fe50f712 (patch)
tree2728bba1131a6f6e5bdf95afec7d7ff9358dac50 /container-search/src/test/java/com/yahoo/search/grouping/request
Publish
Diffstat (limited to 'container-search/src/test/java/com/yahoo/search/grouping/request')
-rw-r--r--container-search/src/test/java/com/yahoo/search/grouping/request/BucketResolverTestCase.java212
-rw-r--r--container-search/src/test/java/com/yahoo/search/grouping/request/ExpressionVisitorTestCase.java82
-rw-r--r--container-search/src/test/java/com/yahoo/search/grouping/request/GroupingOperationTestCase.java148
-rw-r--r--container-search/src/test/java/com/yahoo/search/grouping/request/MathFunctionsTestCase.java67
-rw-r--r--container-search/src/test/java/com/yahoo/search/grouping/request/MathResolverTestCase.java133
-rw-r--r--container-search/src/test/java/com/yahoo/search/grouping/request/RawBufferTestCase.java56
-rw-r--r--container-search/src/test/java/com/yahoo/search/grouping/request/RequestTestCase.java229
-rw-r--r--container-search/src/test/java/com/yahoo/search/grouping/request/parser/GroupingParserBenchmarkTest.java270
-rw-r--r--container-search/src/test/java/com/yahoo/search/grouping/request/parser/GroupingParserTestCase.java619
9 files changed, 1816 insertions, 0 deletions
diff --git a/container-search/src/test/java/com/yahoo/search/grouping/request/BucketResolverTestCase.java b/container-search/src/test/java/com/yahoo/search/grouping/request/BucketResolverTestCase.java
new file mode 100644
index 00000000000..0ee23a3f37f
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/grouping/request/BucketResolverTestCase.java
@@ -0,0 +1,212 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.search.grouping.request;
+
+import org.junit.Test;
+
+import java.text.ChoiceFormat;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+@SuppressWarnings({ "rawtypes" })
+public class BucketResolverTestCase {
+
+ // --------------------------------------------------------------------------------
+ //
+ // Tests
+ //
+ // --------------------------------------------------------------------------------
+
+ @Test
+ public void testResolve() {
+ BucketResolver resolver = new BucketResolver();
+ resolver.push(new StringValue("a"), true);
+ try {
+ resolver.resolve(new AttributeValue("foo"));
+ fail();
+ } catch (IllegalStateException e) {
+ assertEquals("Missing to-limit of last bucket.", e.getMessage());
+ }
+
+ resolver.push(new StringValue("b"), false);
+ PredefinedFunction fnc = resolver.resolve(new AttributeValue("foo"));
+ assertNotNull(fnc);
+ assertEquals(1, fnc.getNumBuckets());
+ BucketValue exp = fnc.getBucket(0);
+ assertNotNull(exp);
+ assertTrue(exp.getFrom() instanceof StringValue);
+ assertTrue(exp.getTo() instanceof StringValue);
+ BucketValue val = exp;
+ assertEquals("a", val.getFrom().getValue());
+ assertEquals("b", val.getTo().getValue());
+
+ resolver.push(new StringValue("c"), true);
+ try {
+ resolver.resolve(new AttributeValue("foo"));
+ fail();
+ } catch (IllegalStateException e) {
+ assertEquals("Missing to-limit of last bucket.", e.getMessage());
+ }
+
+ resolver.push(new StringValue("d"), false);
+ fnc = resolver.resolve(new AttributeValue("foo"));
+ assertNotNull(fnc);
+ assertEquals(2, fnc.getNumBuckets());
+ assertNotNull(exp = fnc.getBucket(0));
+ assertTrue(exp.getFrom() instanceof StringValue);
+ assertTrue(exp.getTo() instanceof StringValue);
+ val = exp;
+ assertEquals("a", val.getFrom().getValue());
+ assertEquals("b", val.getTo().getValue());
+ assertNotNull(exp = fnc.getBucket(1));
+ assertTrue(exp.getFrom() instanceof StringValue);
+ assertTrue(exp.getTo() instanceof StringValue);
+ val = exp;
+ assertEquals("c", val.getFrom().getValue());
+ assertEquals("d", val.getTo().getValue());
+ }
+
+ @Test
+ public void testBucketType() {
+ checkPushFail(Arrays.asList((ConstantValue)new StringValue("a"), new LongValue(1L)),
+ "Bucket type mismatch, expected 'StringValue' got 'LongValue'.");
+ checkPushFail(Arrays.asList((ConstantValue)new StringValue("a"), new DoubleValue(1.0)),
+ "Bucket type mismatch, expected 'StringValue' got 'DoubleValue'.");
+ checkPushFail(Arrays.asList((ConstantValue)new LongValue(1L), new StringValue("a")),
+ "Bucket type mismatch, expected 'LongValue' got 'StringValue'.");
+ checkPushFail(Arrays.asList((ConstantValue)new LongValue(1L), new DoubleValue(1.0)),
+ "Bucket type mismatch, expected 'LongValue' got 'DoubleValue'.");
+ checkPushFail(Arrays.asList((ConstantValue)new DoubleValue(1.0), new StringValue("a")),
+ "Bucket type mismatch, expected 'DoubleValue' got 'StringValue'.");
+ checkPushFail(Arrays.asList((ConstantValue)new DoubleValue(1.0), new LongValue(1L)),
+ "Bucket type mismatch, expected 'DoubleValue' got 'LongValue'.");
+ checkPushFail(Arrays.asList((ConstantValue)new InfiniteValue(new Infinite(true)), new InfiniteValue(new Infinite(false))),
+ "Bucket type mismatch, cannot both be infinity.");
+
+ }
+
+ @Test
+ public void testBucketOrder() {
+ checkPushFail(Arrays.asList((ConstantValue)new LongValue(2L), new LongValue(1L)),
+ "Bucket to-value can not be less than from-value.");
+ checkPushFail(Arrays.asList((ConstantValue)new DoubleValue(2.0), new DoubleValue(1.0)),
+ "Bucket to-value can not be less than from-value.");
+ checkPushFail(Arrays.asList((ConstantValue)new StringValue("b"), new StringValue("a")),
+ "Bucket to-value can not be less than from-value.");
+ }
+
+ public void assertBucketRange(BucketValue expected, ConstantValue from, boolean inclusiveFrom, ConstantValue to, boolean inclusiveTo) {
+ BucketResolver resolver = new BucketResolver();
+ resolver.push(from, inclusiveFrom);
+ resolver.push(to, inclusiveTo);
+ PredefinedFunction fnc = resolver.resolve(new AttributeValue("foo"));
+ assertNotNull(fnc);
+ BucketValue result = fnc.getBucket(0);
+ assertEquals(result.getFrom().getValue(), expected.getFrom().getValue());
+ assertEquals(result.getTo().getValue(), expected.getTo().getValue());
+ }
+
+ public void assertBucketOrder(BucketResolver resolver) {
+ PredefinedFunction fnc = resolver.resolve(new AttributeValue("foo"));
+ BucketValue prev = null;
+ for (int i = 0; i < fnc.getNumBuckets(); i++) {
+ BucketValue b = fnc.getBucket(i);
+ if (prev != null) {
+ assertTrue(prev.compareTo(b) < 0);
+ }
+ prev = b;
+ }
+ }
+
+ @Test
+ public void requireThatBucketRangesWork() {
+ BucketValue expected = new LongBucket(2, 5);
+ assertBucketRange(expected, new LongValue(1), false, new LongValue(4), true);
+ assertBucketRange(expected, new LongValue(1), false, new LongValue(5), false);
+ assertBucketRange(expected, new LongValue(2), true, new LongValue(4), true);
+ assertBucketRange(expected, new LongValue(2), true, new LongValue(5), false);
+
+
+ BucketResolver resolver = new BucketResolver();
+ resolver.push(new LongValue(1), true).push(new LongValue(2), false);
+ resolver.push(new LongValue(2), true).push(new LongValue(4), true);
+ resolver.push(new LongValue(4), false).push(new LongValue(5), false);
+ resolver.push(new LongValue(5), false).push(new LongValue(8), true);
+ assertBucketOrder(resolver);
+
+
+ expected = new StringBucket("aba ", "bab ");
+ assertBucketRange(expected, new StringValue("aba"), false, new StringValue("bab"), true);
+ assertBucketRange(expected, new StringValue("aba"), false, new StringValue("bab "), false);
+ assertBucketRange(expected, new StringValue("aba "), true, new StringValue("bab"), true);
+ assertBucketRange(expected, new StringValue("aba "), true, new StringValue("bab "), false);
+
+ resolver = new BucketResolver();
+ resolver.push(new StringValue("aaa"), true).push(new StringValue("aab"), false);
+ resolver.push(new StringValue("aab"), true).push(new StringValue("aac"), true);
+ resolver.push(new StringValue("aac"), false).push(new StringValue("aad"), false);
+ resolver.push(new StringValue("aad"), false).push(new StringValue("aae"), true);
+ assertBucketOrder(resolver);
+
+ RawBuffer r1 = new RawBuffer(new byte[]{0, 1, 3});
+ RawBuffer r1next = new RawBuffer(new byte[]{0, 1, 3, 0});
+ RawBuffer r2 = new RawBuffer(new byte[]{0, 2, 2});
+ RawBuffer r2next = new RawBuffer(new byte[]{0, 2, 2, 0});
+ RawBuffer r2nextnext = new RawBuffer(new byte[]{0, 2, 2, 0, 4});
+
+ expected = new RawBucket(r1next, r2next);
+ assertBucketRange(expected, new RawValue(r1), false, new RawValue(r2), true);
+ assertBucketRange(expected, new RawValue(r1), false, new RawValue(r2next), false);
+ assertBucketRange(expected, new RawValue(r1next), true, new RawValue(r2), true);
+ assertBucketRange(expected, new RawValue(r1next), true, new RawValue(r2next), false);
+
+ resolver = new BucketResolver();
+ resolver.push(new RawValue(r1), true).push(new RawValue(r1next), false);
+ resolver.push(new RawValue(r1next), true).push(new RawValue(r2), true);
+ resolver.push(new RawValue(r2), false).push(new RawValue(r2next), false);
+ resolver.push(new RawValue(r2next), false).push(new RawValue(r2nextnext), true);
+ assertBucketOrder(resolver);
+
+ double d1next = ChoiceFormat.nextDouble(1.414);
+ double d2next = ChoiceFormat.nextDouble(3.14159);
+ double d1 = ChoiceFormat.nextDouble(d1next);
+ double d2 = ChoiceFormat.nextDouble(d2next);
+ expected = new DoubleBucket(d1, d2);
+ assertBucketRange(expected, new DoubleValue(d1next), false, new DoubleValue(d2next), true);
+ assertBucketRange(expected, new DoubleValue(d1next), false, new DoubleValue(d2), false);
+ assertBucketRange(expected, new DoubleValue(d1), true, new DoubleValue(d2next), true);
+ assertBucketRange(expected, new DoubleValue(d1), true, new DoubleValue(d2), false);
+
+ resolver = new BucketResolver();
+ resolver.push(new DoubleValue(d1next), true).push(new DoubleValue(d1), false);
+ resolver.push(new DoubleValue(d1), true).push(new DoubleValue(d2next), true);
+ resolver.push(new DoubleValue(d2next), false).push(new DoubleValue(d2), false);
+ resolver.push(new DoubleValue(d2), false).push(new DoubleValue(ChoiceFormat.nextDouble(d2)), true);
+ assertBucketOrder(resolver);
+ }
+
+ // --------------------------------------------------------------------------------
+ //
+ // Utilities
+ //
+ // --------------------------------------------------------------------------------
+
+ private static void checkPushFail(List<ConstantValue> args, String expectedException) {
+ BucketResolver resolver = new BucketResolver();
+ try {
+ int i = 0;
+ for (ConstantValue exp : args) {
+ boolean inclusive = ((i % 2) == 0);
+ resolver.push(exp, inclusive);
+ i++;
+ }
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertEquals(expectedException, e.getMessage());
+ }
+ }
+}
diff --git a/container-search/src/test/java/com/yahoo/search/grouping/request/ExpressionVisitorTestCase.java b/container-search/src/test/java/com/yahoo/search/grouping/request/ExpressionVisitorTestCase.java
new file mode 100644
index 00000000000..f5d30497671
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/grouping/request/ExpressionVisitorTestCase.java
@@ -0,0 +1,82 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.search.grouping.request;
+
+import org.junit.Test;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class ExpressionVisitorTestCase {
+
+ @Test
+ public void requireThatExpressionsAreVisited() {
+ GroupingOperation op = new AllOperation();
+
+ final List<GroupingExpression> lst = new LinkedList<>();
+ GroupingExpression exp = new AttributeValue("groupBy");
+ op.setGroupBy(exp);
+ lst.add(exp);
+
+ op.addOrderBy(exp = new AttributeValue("orderBy1"));
+ lst.add(exp);
+ op.addOrderBy(exp = new AttributeValue("orderBy1"));
+ lst.add(exp);
+
+ op.addOutput(exp = new AttributeValue("output1"));
+ lst.add(exp);
+ op.addOutput(exp = new AttributeValue("output2"));
+ lst.add(exp);
+
+ op.visitExpressions(exp1 -> assertNotNull(lst.remove(exp1)));
+ assertTrue(lst.isEmpty());
+ }
+
+ @Test
+ public void requireThatChildOperationsAreVisited() {
+ GroupingOperation root, parentA, childA1, childA2, parentB, childB1;
+ root = new AllOperation()
+ .addChild(parentA = new AllOperation()
+ .addChild(childA1 = new AllOperation())
+ .addChild(childA2 = new AllOperation()))
+ .addChild(parentB = new AllOperation()
+ .addChild(childB1 = new AllOperation()));
+
+ final List<GroupingExpression> lst = new LinkedList<>();
+ GroupingExpression exp = new AttributeValue("parentA");
+ parentA.setGroupBy(exp);
+ lst.add(exp);
+
+ childA1.setGroupBy(exp = new AttributeValue("childA1"));
+ lst.add(exp);
+
+ childA2.setGroupBy(exp = new AttributeValue("childA2"));
+ lst.add(exp);
+
+ parentB.setGroupBy(exp = new AttributeValue("parentB"));
+ lst.add(exp);
+
+ childB1.setGroupBy(exp = new AttributeValue("childB1"));
+ lst.add(exp);
+
+ root.visitExpressions(exp1 -> assertNotNull(lst.remove(exp1)));
+ assertTrue(lst.isEmpty());
+ }
+
+ @Test
+ public void requireThatExpressionsArgumentsAreVisited() {
+ final List<GroupingExpression> lst = new LinkedList<>();
+ GroupingExpression arg1 = new AttributeValue("arg1");
+ lst.add(arg1);
+ GroupingExpression arg2 = new AttributeValue("arg2");
+ lst.add(arg2);
+
+ new AndFunction(arg1, arg2).visit(exp -> assertNotNull(lst.remove(exp)));
+ assertTrue(lst.isEmpty());
+ }
+}
diff --git a/container-search/src/test/java/com/yahoo/search/grouping/request/GroupingOperationTestCase.java b/container-search/src/test/java/com/yahoo/search/grouping/request/GroupingOperationTestCase.java
new file mode 100644
index 00000000000..614a126b54d
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/grouping/request/GroupingOperationTestCase.java
@@ -0,0 +1,148 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.search.grouping.request;
+
+import com.yahoo.search.grouping.request.parser.ParseException;
+import com.yahoo.search.grouping.request.parser.TokenMgrError;
+import org.junit.Test;
+
+import java.util.List;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class GroupingOperationTestCase {
+
+ @Test
+ public void requireThatAccessorsWork() {
+ GroupingOperation op = new AllOperation();
+ GroupingExpression exp = new AttributeValue("alias");
+ op.putAlias("alias", exp);
+ assertSame(exp, op.getAlias("alias"));
+
+ assertEquals(0, op.getHints().size());
+ assertFalse(op.containsHint("foo"));
+ assertFalse(op.containsHint("bar"));
+
+ op.addHint("foo");
+ assertEquals(1, op.getHints().size());
+ assertTrue(op.containsHint("foo"));
+ assertFalse(op.containsHint("bar"));
+
+ op.addHint("bar");
+ assertEquals(2, op.getHints().size());
+ assertTrue(op.containsHint("foo"));
+ assertTrue(op.containsHint("bar"));
+
+ op.setForceSinglePass(true);
+ assertTrue(op.getForceSinglePass());
+ op.setForceSinglePass(false);
+ assertFalse(op.getForceSinglePass());
+
+ exp = new AttributeValue("orderBy");
+ op.addOrderBy(exp);
+ assertEquals(1, op.getOrderBy().size());
+ assertSame(exp, op.getOrderBy(0));
+
+ exp = new AttributeValue("output");
+ op.addOutput(exp);
+ assertEquals(1, op.getOutputs().size());
+ assertSame(exp, op.getOutput(0));
+
+ GroupingOperation child = new AllOperation();
+ op.addChild(child);
+ assertEquals(1, op.getChildren().size());
+ assertSame(child, op.getChild(0));
+
+ exp = new AttributeValue("groupBy");
+ op.setGroupBy(exp);
+ assertSame(exp, op.getGroupBy());
+
+ op.setWhere("whereA");
+ assertEquals("whereA", op.getWhere());
+ op.setWhere("whereB");
+ assertEquals("whereB", op.getWhere());
+
+ op.setAccuracy(0.6);
+ assertEquals(0.6, op.getAccuracy(), 1E-6);
+ op.setAccuracy(0.9);
+ assertEquals(0.9, op.getAccuracy(), 1E-6);
+
+ op.setPrecision(6);
+ assertEquals(6, op.getPrecision());
+ op.setPrecision(9);
+ assertEquals(9, op.getPrecision());
+
+ assertFalse(op.hasMax());
+ op.setMax(6);
+ assertTrue(op.hasMax());
+ assertEquals(6, op.getMax());
+ op.setMax(9);
+ assertEquals(9, op.getMax());
+ assertTrue(op.hasMax());
+ op.setMax(0);
+ assertTrue(op.hasMax());
+ op.setMax(-7);
+ assertFalse(op.hasMax());
+ }
+
+ @Test
+ public void requireThatFromStringAsListParsesAllOperations() {
+ List<GroupingOperation> lst = GroupingOperation.fromStringAsList("");
+ assertTrue(lst.isEmpty());
+
+ lst = GroupingOperation.fromStringAsList("all()");
+ assertEquals(1, lst.size());
+ assertTrue(lst.get(0) instanceof AllOperation);
+
+ lst = GroupingOperation.fromStringAsList("each()");
+ assertEquals(1, lst.size());
+ assertTrue(lst.get(0) instanceof EachOperation);
+
+ lst = GroupingOperation.fromStringAsList("all();each()");
+ assertEquals(2, lst.size());
+ assertTrue(lst.get(0) instanceof AllOperation);
+ assertTrue(lst.get(1) instanceof EachOperation);
+ }
+
+ @Test
+ public void requireThatFromStringAcceptsOnlyOneOperation() {
+ try {
+ GroupingOperation.fromString("");
+ fail();
+ } catch (IllegalArgumentException e) {
+
+ }
+ assertTrue(GroupingOperation.fromString("all()") instanceof AllOperation);
+ assertTrue(GroupingOperation.fromString("each()") instanceof EachOperation);
+ try {
+ GroupingOperation.fromString("all();each()");
+ fail();
+ } catch (IllegalArgumentException e) {
+
+ }
+ }
+
+ @Test
+ public void requireThatParseExceptionsAreRethrown() {
+ try {
+ GroupingOperation.fromString("all(foo)");
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertTrue(e.getMessage().startsWith("Encountered \"foo\" at line 1, column 5.\n"));
+ assertTrue(e.getCause() instanceof ParseException);
+ }
+ }
+
+ @Test
+ public void requireThatTokenErrorsAreRethrown() {
+ try {
+ GroupingOperation.fromString("all(\\foo)");
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertTrue(e.getMessage().startsWith("Lexical error at line 1, column 6."));
+ assertTrue(e.getCause() instanceof TokenMgrError);
+ }
+ }
+}
diff --git a/container-search/src/test/java/com/yahoo/search/grouping/request/MathFunctionsTestCase.java b/container-search/src/test/java/com/yahoo/search/grouping/request/MathFunctionsTestCase.java
new file mode 100644
index 00000000000..14274e98182
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/grouping/request/MathFunctionsTestCase.java
@@ -0,0 +1,67 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.search.grouping.request;
+
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.sameInstance;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a>
+ * @since 5.1.9
+ */
+public class MathFunctionsTestCase {
+ @Test
+ public void testMathFunctions() {
+ //if this fails, update the count AND add a test in each of the two blocks below
+ assertThat(MathFunctions.Function.values().length, is(21));
+
+
+ assertThat(MathFunctions.Function.create(0), sameInstance(MathFunctions.Function.EXP));
+ assertThat(MathFunctions.Function.create(1), sameInstance(MathFunctions.Function.POW));
+ assertThat(MathFunctions.Function.create(2), sameInstance(MathFunctions.Function.LOG));
+ assertThat(MathFunctions.Function.create(3), sameInstance(MathFunctions.Function.LOG1P));
+ assertThat(MathFunctions.Function.create(4), sameInstance(MathFunctions.Function.LOG10));
+ assertThat(MathFunctions.Function.create(5), sameInstance(MathFunctions.Function.SIN));
+ assertThat(MathFunctions.Function.create(6), sameInstance(MathFunctions.Function.ASIN));
+ assertThat(MathFunctions.Function.create(7), sameInstance(MathFunctions.Function.COS));
+ assertThat(MathFunctions.Function.create(8), sameInstance(MathFunctions.Function.ACOS));
+ assertThat(MathFunctions.Function.create(9), sameInstance(MathFunctions.Function.TAN));
+ assertThat(MathFunctions.Function.create(10), sameInstance(MathFunctions.Function.ATAN));
+ assertThat(MathFunctions.Function.create(11), sameInstance(MathFunctions.Function.SQRT));
+ assertThat(MathFunctions.Function.create(12), sameInstance(MathFunctions.Function.SINH));
+ assertThat(MathFunctions.Function.create(13), sameInstance(MathFunctions.Function.ASINH));
+ assertThat(MathFunctions.Function.create(14), sameInstance(MathFunctions.Function.COSH));
+ assertThat(MathFunctions.Function.create(15), sameInstance(MathFunctions.Function.ACOSH));
+ assertThat(MathFunctions.Function.create(16), sameInstance(MathFunctions.Function.TANH));
+ assertThat(MathFunctions.Function.create(17), sameInstance(MathFunctions.Function.ATANH));
+ assertThat(MathFunctions.Function.create(18), sameInstance(MathFunctions.Function.CBRT));
+ assertThat(MathFunctions.Function.create(19), sameInstance(MathFunctions.Function.HYPOT));
+ assertThat(MathFunctions.Function.create(20), sameInstance(MathFunctions.Function.FLOOR));
+
+
+ assertThat(MathFunctions.newInstance(MathFunctions.Function.EXP, null, null), instanceOf(MathExpFunction.class));
+ assertThat(MathFunctions.newInstance(MathFunctions.Function.POW, null, null), instanceOf(MathPowFunction.class));
+ assertThat(MathFunctions.newInstance(MathFunctions.Function.LOG, null, null), instanceOf(MathLogFunction.class));
+ assertThat(MathFunctions.newInstance(MathFunctions.Function.LOG1P, null, null), instanceOf(MathLog1pFunction.class));
+ assertThat(MathFunctions.newInstance(MathFunctions.Function.LOG10, null, null), instanceOf(MathLog10Function.class));
+ assertThat(MathFunctions.newInstance(MathFunctions.Function.SIN, null, null), instanceOf(MathSinFunction.class));
+ assertThat(MathFunctions.newInstance(MathFunctions.Function.ASIN, null, null), instanceOf(MathASinFunction.class));
+ assertThat(MathFunctions.newInstance(MathFunctions.Function.COS, null, null), instanceOf(MathCosFunction.class));
+ assertThat(MathFunctions.newInstance(MathFunctions.Function.ACOS, null, null), instanceOf(MathACosFunction.class));
+ assertThat(MathFunctions.newInstance(MathFunctions.Function.TAN, null, null), instanceOf(MathTanFunction.class));
+ assertThat(MathFunctions.newInstance(MathFunctions.Function.ATAN, null, null), instanceOf(MathATanFunction.class));
+ assertThat(MathFunctions.newInstance(MathFunctions.Function.SQRT, null, null), instanceOf(MathSqrtFunction.class));
+ assertThat(MathFunctions.newInstance(MathFunctions.Function.SINH, null, null), instanceOf(MathSinHFunction.class));
+ assertThat(MathFunctions.newInstance(MathFunctions.Function.ASINH, null, null), instanceOf(MathASinHFunction.class));
+ assertThat(MathFunctions.newInstance(MathFunctions.Function.COSH, null, null), instanceOf(MathCosHFunction.class));
+ assertThat(MathFunctions.newInstance(MathFunctions.Function.ACOSH, null, null), instanceOf(MathACosHFunction.class));
+ assertThat(MathFunctions.newInstance(MathFunctions.Function.TANH, null, null), instanceOf(MathTanHFunction.class));
+ assertThat(MathFunctions.newInstance(MathFunctions.Function.ATANH, null, null), instanceOf(MathATanHFunction.class));
+ assertThat(MathFunctions.newInstance(MathFunctions.Function.CBRT, null, null), instanceOf(MathCbrtFunction.class));
+ assertThat(MathFunctions.newInstance(MathFunctions.Function.HYPOT, null, null), instanceOf(MathHypotFunction.class));
+ assertThat(MathFunctions.newInstance(MathFunctions.Function.FLOOR, null, null), instanceOf(MathFloorFunction.class));
+ }
+}
diff --git a/container-search/src/test/java/com/yahoo/search/grouping/request/MathResolverTestCase.java b/container-search/src/test/java/com/yahoo/search/grouping/request/MathResolverTestCase.java
new file mode 100644
index 00000000000..af007a6f85c
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/grouping/request/MathResolverTestCase.java
@@ -0,0 +1,133 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.search.grouping.request;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class MathResolverTestCase {
+
+ // --------------------------------------------------------------------------------
+ //
+ // Tests
+ //
+ // --------------------------------------------------------------------------------
+
+ @Test
+ public void testOperators() {
+ MathResolver resolver = new MathResolver();
+ resolver.push(MathResolver.Type.ADD, new LongValue(1));
+ resolver.push(MathResolver.Type.ADD, new LongValue(2));
+ assertEquals("add(1, 2)",
+ resolver.resolve().toString());
+
+ resolver = new MathResolver();
+ resolver.push(MathResolver.Type.ADD, new LongValue(1));
+ resolver.push(MathResolver.Type.SUB, new LongValue(2));
+ assertEquals("sub(1, 2)",
+ resolver.resolve().toString());
+
+ resolver = new MathResolver();
+ resolver.push(MathResolver.Type.ADD, new LongValue(1));
+ resolver.push(MathResolver.Type.DIV, new LongValue(2));
+ assertEquals("div(1, 2)",
+ resolver.resolve().toString());
+
+ resolver = new MathResolver();
+ resolver.push(MathResolver.Type.ADD, new LongValue(1));
+ resolver.push(MathResolver.Type.MOD, new LongValue(2));
+ assertEquals("mod(1, 2)",
+ resolver.resolve().toString());
+
+ resolver = new MathResolver();
+ resolver.push(MathResolver.Type.ADD, new LongValue(1));
+ resolver.push(MathResolver.Type.MUL, new LongValue(2));
+ assertEquals("mul(1, 2)",
+ resolver.resolve().toString());
+ }
+
+ @Test
+ public void testOperatorPrecedence() {
+ assertResolve("add(add(1, 2), 3)", MathResolver.Type.ADD, MathResolver.Type.ADD);
+ assertResolve("add(1, sub(2, 3))", MathResolver.Type.ADD, MathResolver.Type.SUB);
+ assertResolve("add(1, div(2, 3))", MathResolver.Type.ADD, MathResolver.Type.DIV);
+ assertResolve("add(1, mod(2, 3))", MathResolver.Type.ADD, MathResolver.Type.MOD);
+ assertResolve("add(1, mul(2, 3))", MathResolver.Type.ADD, MathResolver.Type.MUL);
+
+ assertResolve("add(sub(1, 2), 3)", MathResolver.Type.SUB, MathResolver.Type.ADD);
+ assertResolve("sub(sub(1, 2), 3)", MathResolver.Type.SUB, MathResolver.Type.SUB);
+ assertResolve("sub(1, div(2, 3))", MathResolver.Type.SUB, MathResolver.Type.DIV);
+ assertResolve("sub(1, mod(2, 3))", MathResolver.Type.SUB, MathResolver.Type.MOD);
+ assertResolve("sub(1, mul(2, 3))", MathResolver.Type.SUB, MathResolver.Type.MUL);
+
+ assertResolve("add(div(1, 2), 3)", MathResolver.Type.DIV, MathResolver.Type.ADD);
+ assertResolve("sub(div(1, 2), 3)", MathResolver.Type.DIV, MathResolver.Type.SUB);
+ assertResolve("div(div(1, 2), 3)", MathResolver.Type.DIV, MathResolver.Type.DIV);
+ assertResolve("div(1, mod(2, 3))", MathResolver.Type.DIV, MathResolver.Type.MOD);
+ assertResolve("div(1, mul(2, 3))", MathResolver.Type.DIV, MathResolver.Type.MUL);
+
+ assertResolve("add(mod(1, 2), 3)", MathResolver.Type.MOD, MathResolver.Type.ADD);
+ assertResolve("sub(mod(1, 2), 3)", MathResolver.Type.MOD, MathResolver.Type.SUB);
+ assertResolve("div(mod(1, 2), 3)", MathResolver.Type.MOD, MathResolver.Type.DIV);
+ assertResolve("mod(mod(1, 2), 3)", MathResolver.Type.MOD, MathResolver.Type.MOD);
+ assertResolve("mod(1, mul(2, 3))", MathResolver.Type.MOD, MathResolver.Type.MUL);
+
+ assertResolve("add(mul(1, 2), 3)", MathResolver.Type.MUL, MathResolver.Type.ADD);
+ assertResolve("sub(mul(1, 2), 3)", MathResolver.Type.MUL, MathResolver.Type.SUB);
+ assertResolve("div(mul(1, 2), 3)", MathResolver.Type.MUL, MathResolver.Type.DIV);
+ assertResolve("mod(mul(1, 2), 3)", MathResolver.Type.MUL, MathResolver.Type.MOD);
+ assertResolve("mul(mul(1, 2), 3)", MathResolver.Type.MUL, MathResolver.Type.MUL);
+
+ assertResolve("add(1, sub(div(2, mod(3, mul(4, 5))), 6))",
+ MathResolver.Type.ADD, MathResolver.Type.DIV, MathResolver.Type.MOD,
+ MathResolver.Type.MUL, MathResolver.Type.SUB);
+ assertResolve("add(sub(1, div(mod(mul(2, 3), 4), 5)), 6)",
+ MathResolver.Type.SUB, MathResolver.Type.MUL, MathResolver.Type.MOD,
+ MathResolver.Type.DIV, MathResolver.Type.ADD);
+ assertResolve("add(1, sub(2, div(3, mod(4, mul(5, 6)))))",
+ MathResolver.Type.ADD, MathResolver.Type.SUB, MathResolver.Type.DIV,
+ MathResolver.Type.MOD, MathResolver.Type.MUL);
+ assertResolve("add(sub(div(mod(mul(1, 2), 3), 4), 5), 6)",
+ MathResolver.Type.MUL, MathResolver.Type.MOD, MathResolver.Type.DIV,
+ MathResolver.Type.SUB, MathResolver.Type.ADD);
+ }
+
+ @Test
+ public void testOperatorSupport() {
+ MathResolver resolver = new MathResolver();
+ for (MathResolver.Type type : MathResolver.Type.values()) {
+ if (type == MathResolver.Type.ADD) {
+ continue;
+ }
+ try {
+ resolver.push(type, new AttributeValue("foo"));
+ } catch (IllegalArgumentException e) {
+ assertEquals("First item in an arithmetic operation must be an addition.", e.getMessage());
+ }
+ }
+ }
+
+ // --------------------------------------------------------------------------------
+ //
+ // Utilities
+ //
+ // --------------------------------------------------------------------------------
+
+ private static void assertResolve(String expected, MathResolver.Type... types) {
+ MathResolver resolver = new MathResolver();
+
+ int val = 0;
+ resolver.push(MathResolver.Type.ADD, new LongValue(++val));
+ for (MathResolver.Type type : types) {
+ resolver.push(type, new LongValue(++val));
+ }
+
+ GroupingExpression exp = resolver.resolve();
+ assertNotNull(exp);
+ assertEquals(expected, exp.toString());
+ }
+}
diff --git a/container-search/src/test/java/com/yahoo/search/grouping/request/RawBufferTestCase.java b/container-search/src/test/java/com/yahoo/search/grouping/request/RawBufferTestCase.java
new file mode 100644
index 00000000000..eba5a458cfd
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/grouping/request/RawBufferTestCase.java
@@ -0,0 +1,56 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.search.grouping.request;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author <a href="mailto:lulf@yahoo-inc.com">Ulf Lilleengen</a>
+ */
+public class RawBufferTestCase {
+
+ // --------------------------------------------------------------------------------
+ //
+ // Tests.
+ //
+ // --------------------------------------------------------------------------------
+
+ @Test
+ public void requireThatCompareWorks() {
+ RawBuffer buffer = new RawBuffer();
+ buffer.put((byte)'a');
+ buffer.put((byte)'b');
+
+ RawBuffer buffer2 = new RawBuffer();
+ buffer2.put((byte)'k');
+ buffer2.put((byte)'a');
+
+ ArrayList<Byte> backing = new ArrayList<>();
+ backing.add((byte)'a');
+ backing.add((byte)'b');
+ RawBuffer buffer3 = new RawBuffer(backing);
+
+ assertEquals(buffer.compareTo(buffer2), -1);
+ assertEquals(buffer2.compareTo(buffer), 1);
+ assertEquals(buffer.compareTo(buffer3), 0);
+ }
+
+ @Test
+ public void requireThatToStringWorks() {
+ assertToString(Arrays.asList("a".getBytes()[0], "b".getBytes()[0]), "{97,98}");
+ assertToString(Arrays.asList(new Byte((byte)2), new Byte((byte)6)), "{2,6}");
+ }
+
+ public void assertToString(List<Byte> data, String expected) {
+ RawBuffer buffer = new RawBuffer();
+ for (Byte b : data) {
+ buffer.put(b.byteValue());
+ }
+ assertEquals(buffer.toString(), expected);
+ }
+}
diff --git a/container-search/src/test/java/com/yahoo/search/grouping/request/RequestTestCase.java b/container-search/src/test/java/com/yahoo/search/grouping/request/RequestTestCase.java
new file mode 100644
index 00000000000..f2f8316f2db
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/grouping/request/RequestTestCase.java
@@ -0,0 +1,229 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.search.grouping.request;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class RequestTestCase {
+
+ @Test
+ public void requireThatApiWorks() {
+ GroupingOperation op = new AllOperation()
+ .setGroupBy(new AttributeValue("foo"))
+ .addOrderBy(new CountAggregator())
+ .addChildren(Arrays.asList(new AllOperation(), new EachOperation()))
+ .addChild(new EachOperation()
+ .addOutput(new CountAggregator())
+ .addOutput(new MinAggregator(new AttributeValue("bar")))
+ .addChild(new EachOperation()
+ .addOutput(new AddFunction(
+ new LongValue(69),
+ new AttributeValue("baz")))
+ .addOutput(new SummaryValue("cox"))));
+ assertEquals("all(group(foo) order(count()) all() each() " +
+ "each(output(count(), min(bar)) each(output(add(69, baz), summary(cox)))))",
+ op.toString());
+ op.resolveLevel(1);
+
+ GroupingExpression exp = op.getGroupBy();
+ assertNotNull(exp);
+ assertTrue(exp instanceof AttributeValue);
+ assertEquals("foo", ((AttributeValue)exp).getAttributeName());
+ assertEquals(1, op.getNumOrderBy());
+ assertNotNull(exp = op.getOrderBy(0));
+ assertTrue(exp instanceof CountAggregator);
+
+ assertEquals(3, op.getNumChildren());
+ assertTrue(op.getChild(0) instanceof AllOperation);
+ assertTrue(op.getChild(1) instanceof EachOperation);
+ assertNotNull(op = op.getChild(2));
+ assertTrue(op instanceof EachOperation);
+ assertEquals(2, op.getNumOutputs());
+ assertNotNull(exp = op.getOutput(0));
+ assertTrue(exp instanceof CountAggregator);
+ assertNotNull(exp = op.getOutput(1));
+ assertTrue(exp instanceof MinAggregator);
+ assertNotNull(exp = ((MinAggregator)exp).getExpression());
+ assertTrue(exp instanceof AttributeValue);
+ assertEquals("bar", ((AttributeValue)exp).getAttributeName());
+
+ assertEquals(1, op.getNumChildren());
+ assertNotNull(op = op.getChild(0));
+ assertTrue(op instanceof EachOperation);
+ assertEquals(2, op.getNumOutputs());
+ assertNotNull(exp = op.getOutput(0));
+ assertTrue(exp instanceof AddFunction);
+ assertEquals(2, ((AddFunction)exp).getNumArgs());
+ GroupingExpression arg = ((AddFunction)exp).getArg(0);
+ assertNotNull(arg);
+ assertTrue(arg instanceof LongValue);
+ assertEquals(69L, ((LongValue)arg).getValue().longValue());
+ assertNotNull(arg = ((AddFunction)exp).getArg(1));
+ assertTrue(arg instanceof AttributeValue);
+ assertEquals("baz", ((AttributeValue)arg).getAttributeName());
+ assertNotNull(exp = op.getOutput(1));
+ assertTrue(exp instanceof SummaryValue);
+ assertEquals("cox", ((SummaryValue)exp).getSummaryName());
+ }
+
+ @Test
+ public void requireThatPredefinedApiWorks() {
+ PredefinedFunction fnc = new LongPredefined(new AttributeValue("foo"),
+ new LongBucket(1, 2),
+ new LongBucket(3, 4));
+ assertEquals(2, fnc.getNumBuckets());
+ BucketValue bucket = fnc.getBucket(0);
+ assertNotNull(bucket);
+ assertTrue(bucket instanceof LongBucket);
+ assertEquals(1L, bucket.getFrom().getValue());
+ assertEquals(2L, bucket.getTo().getValue());
+
+ assertNotNull(bucket = fnc.getBucket(1));
+ assertTrue(bucket instanceof LongBucket);
+ assertEquals(3L, bucket.getFrom().getValue());
+ assertEquals(4L, bucket.getTo().getValue());
+ }
+
+ @Test
+ public void requireThatBucketIntegrityIsChecked() {
+ try {
+ new LongBucket(2, 1);
+ } catch (IllegalArgumentException e) {
+ assertEquals("Bucket to-value can not be less than from-value.", e.getMessage());
+ }
+ try {
+ new LongPredefined(new AttributeValue("foo"),
+ new LongBucket(3, 4),
+ new LongBucket(1, 2));
+ } catch (IllegalArgumentException e) {
+ assertEquals("Buckets must be monotonically increasing, got bucket[3, 4> before bucket[1, 2>.",
+ e.getMessage());
+ }
+ }
+
+ @Test
+ public void requireThatAliasWorks() {
+ GroupingOperation all = new AllOperation();
+ all.putAlias("myalias", new AttributeValue("foo"));
+ GroupingExpression exp = all.getAlias("myalias");
+ assertNotNull(exp);
+ assertTrue(exp instanceof AttributeValue);
+ assertEquals("foo", ((AttributeValue)exp).getAttributeName());
+
+ GroupingOperation each = new EachOperation();
+ all.addChild(each);
+ assertNotNull(exp = each.getAlias("myalias"));
+ assertTrue(exp instanceof AttributeValue);
+ assertEquals("foo", ((AttributeValue)exp).getAttributeName());
+
+ each.putAlias("myalias", new AttributeValue("bar"));
+ assertNotNull(exp = each.getAlias("myalias"));
+ assertTrue(exp instanceof AttributeValue);
+ assertEquals("bar", ((AttributeValue)exp).getAttributeName());
+ }
+
+ @Test
+ public void testOrderBy() {
+ GroupingOperation all = new AllOperation();
+ all.addOrderBy(new AttributeValue("foo"));
+ try {
+ all.resolveLevel(0);
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertEquals("Operation 'all(order(foo))' can not order single hit.", e.getMessage());
+ }
+ all.resolveLevel(1);
+ assertEquals(0, all.getOrderBy(0).getLevel());
+ }
+
+ @Test
+ public void testMax() {
+ GroupingOperation all = new AllOperation();
+ all.setMax(69);
+ try {
+ all.resolveLevel(0);
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertEquals("Operation 'all(max(69))' can not apply max to single hit.", e.getMessage());
+ }
+ all.resolveLevel(1);
+ }
+
+ @Test
+ public void testAccuracy() {
+ GroupingOperation all = new AllOperation();
+ all.setAccuracy(0.53);
+ assertEquals((long)(100.0 * all.getAccuracy()), 53);
+ try {
+ all.setAccuracy(1.2);
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertEquals("Illegal accuracy '1.2'. Must be between 0 and 1.", e.getMessage());
+ }
+ try {
+ all.setAccuracy(-0.5);
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertEquals("Illegal accuracy '-0.5'. Must be between 0 and 1.", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testLevelChange() {
+ GroupingOperation all = new AllOperation();
+ all.resolveLevel(0);
+ assertEquals(0, all.getLevel());
+ all.setGroupBy(new AttributeValue("foo"));
+ all.resolveLevel(1);
+ assertEquals(2, all.getLevel());
+
+ GroupingOperation each = new EachOperation();
+ try {
+ each.resolveLevel(0);
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertEquals("Operation '" + each + "' can not operate on single hit.", e.getMessage());
+ }
+ each.resolveLevel(1);
+ assertEquals(0, each.getLevel());
+ each.setGroupBy(new AttributeValue("foo"));
+ each.resolveLevel(2);
+ assertEquals(2, each.getLevel());
+ }
+
+ @Test
+ public void testLevelInheritance() {
+ GroupingOperation grandParent, parent, child, grandChild;
+ grandParent = new AllOperation()
+ .addChild(parent = new EachOperation()
+ .addChild(child = new AllOperation()
+ .addChild(grandChild = new EachOperation())));
+
+ grandParent.resolveLevel(69);
+ assertEquals(69, grandParent.getLevel());
+ assertEquals(68, parent.getLevel());
+ assertEquals(68, child.getLevel());
+ assertEquals(67, grandChild.getLevel());
+ }
+
+ @Test
+ public void testLevelPropagation() {
+ GroupingOperation all = new AllOperation()
+ .setGroupBy(new AttributeValue("foo"))
+ .addOrderBy(new MaxAggregator(new AttributeValue("bar")))
+ .addChild(new EachOperation()
+ .addOutput(new MaxAggregator(new AttributeValue("baz"))));
+
+ all.resolveLevel(1);
+ assertEquals(0, all.getGroupBy().getLevel());
+ assertEquals(1, all.getOrderBy(0).getLevel());
+ assertEquals(1, all.getChild(0).getOutput(0).getLevel());
+ assertEquals(0, ((AggregatorNode)all.getChild(0).getOutput(0)).getExpression().getLevel());
+ }
+}
diff --git a/container-search/src/test/java/com/yahoo/search/grouping/request/parser/GroupingParserBenchmarkTest.java b/container-search/src/test/java/com/yahoo/search/grouping/request/parser/GroupingParserBenchmarkTest.java
new file mode 100644
index 00000000000..2abd4a01dd7
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/grouping/request/parser/GroupingParserBenchmarkTest.java
@@ -0,0 +1,270 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.search.grouping.request.parser;
+
+import com.yahoo.search.grouping.request.GroupingOperation;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class GroupingParserBenchmarkTest {
+
+ private static final int NUM_RUNS = 10;//000;
+ private static final Map<String, Long> PREV_RESULTS = new LinkedHashMap<>();
+
+ static {
+ PREV_RESULTS.put("Original", 79276393L);
+ PREV_RESULTS.put("NoCache", 71728602L);
+ PREV_RESULTS.put("CharStream", 43852348L);
+ PREV_RESULTS.put("CharArray", 22936513L);
+ }
+
+ @Test
+ public void requireThatGroupingParserIsFast() {
+ List<String> inputs = getInputs();
+ long ignore = 0;
+ long now = 0;
+ for (int i = 0; i < 2; ++i) {
+ now = System.nanoTime();
+ for (int j = 0; j < NUM_RUNS; ++j) {
+ for (String str : inputs) {
+ ignore += parseRequest(str);
+ }
+ }
+ }
+ long micros = TimeUnit.NANOSECONDS.toMicros(System.nanoTime() - now);
+ System.out.format("%d \u03bcs (avg %.2f)\n", micros, (double)micros / (NUM_RUNS * inputs.size()));
+ for (Map.Entry<String, Long> entry : PREV_RESULTS.entrySet()) {
+ System.out.format("%-20s : %4.2f\n", entry.getKey(), (double)micros / entry.getValue());
+ }
+ System.out.println("\nignore " + ignore);
+ }
+
+ private static int parseRequest(String str) {
+ return GroupingOperation.fromStringAsList(str).size();
+ }
+
+ private static List<String> getInputs() {
+ return Arrays.asList(
+ " all(group(foo)each(output(max(bar))))",
+ "all( group(foo)each(output(max(bar))))",
+ "all(group( foo)each(output(max(bar))))",
+ "all(group(foo )each(output(max(bar))))",
+ "all(group(foo) each(output(max(bar))))",
+ "all(group(foo)each( output(max(bar))))",
+ "all(group(foo)each(output( max(bar))))",
+ "all(group(foo)each(output(max(bar))))",
+ "all(group(foo)each(output(max( bar))))",
+ "all(group(foo)each(output(max(bar ))))",
+ "all(group(foo)each(output(max(bar) )))",
+ "all(group(foo)each(output(max(bar)) ))",
+ "all(group(foo)each(output(max(bar))) )",
+ "all(group(foo)each(output(max(bar)))) ",
+ "all()",
+ "each()",
+ "all(each())",
+ "each(all())",
+ "all(all() all())",
+ "all(all() each())",
+ "all(each() all())",
+ "all(each() each())",
+ "each(all() all())",
+ "all(group(foo))",
+ "all(max(1))",
+ "all(order(foo))",
+ "all(order(+foo))",
+ "all(order(-foo))",
+ "all(order(foo, bar))",
+ "all(order(foo, +bar))",
+ "all(order(foo, -bar))",
+ "all(order(+foo, bar))",
+ "all(order(+foo, +bar))",
+ "all(order(+foo, -bar))",
+ "all(order(-foo, bar))",
+ "all(order(-foo, +bar))",
+ "all(order(-foo, -bar))",
+ "all(output(min(a)))",
+ "all(output(min(a), min(b)))",
+ "all(precision(1))",
+ "all(where(foo))",
+ "all(where($foo))",
+ "all(group(fixedwidth(foo, 1)))",
+ "all(group(fixedwidth(foo, 1.2)))",
+ "all(group(md5(foo, 1)))",
+ "all(group(predefined(foo, bucket(1, 2))))",
+ "all(group(predefined(foo, bucket(-1, 2))))",
+ "all(group(predefined(foo, bucket(-2, -1))))",
+ "all(group(predefined(foo, bucket(1, 2), bucket(3, 4))))",
+ "all(group(predefined(foo, bucket(1, 2), bucket(3, 4), bucket(5, 6))))",
+ "all(group(predefined(foo, bucket(1, 2), bucket(2, 3), bucket(3, 4))))",
+ "all(group(predefined(foo, bucket(-100, 0), bucket(0), bucket<0, 100))))",
+ "all(group(predefined(foo, bucket[1, 2>)))",
+ "all(group(predefined(foo, bucket[-1, 2>)))",
+ "all(group(predefined(foo, bucket[-2, -1>)))",
+ "all(group(predefined(foo, bucket[1, 2>, bucket(3, 4>)))",
+ "all(group(predefined(foo, bucket[1, 2>, bucket[3, 4>, bucket[5, 6>)))",
+ "all(group(predefined(foo, bucket[1, 2>, bucket[2, 3>, bucket[3, 4>)))",
+ "all(group(predefined(foo, bucket<1, 5>)))",
+ "all(group(predefined(foo, bucket[1, 5>)))",
+ "all(group(predefined(foo, bucket<1, 5])))",
+ "all(group(predefined(foo, bucket[1, 5])))",
+ "all(group(predefined(foo, bucket<1, inf>)))",
+ "all(group(predefined(foo, bucket<-inf, -1>)))",
+ "all(group(predefined(foo, bucket<a, inf>)))",
+ "all(group(predefined(foo, bucket<'a', inf>)))",
+ "all(group(predefined(foo, bucket<-inf, a>)))",
+ "all(group(predefined(foo, bucket[-inf, 'a'>)))",
+ "all(group(predefined(foo, bucket<-inf, -0.3>)))",
+ "all(group(predefined(foo, bucket<0.3, inf])))",
+ "all(group(predefined(foo, bucket<0.3, inf])))",
+ "all(group(predefined(foo, bucket<infinite, inf])))",
+ "all(group(predefined(foo, bucket<myinf, inf])))",
+ "all(group(predefined(foo, bucket<-inf, infinite])))",
+ "all(group(predefined(foo, bucket<-inf, myinf])))",
+ "all(group(predefined(foo, bucket(1.0, 2.0))))",
+ "all(group(predefined(foo, bucket(1.0, 2.0), bucket(3.0, 4.0))))",
+ "all(group(predefined(foo, bucket(1.0, 2.0), bucket(3.0, 4.0), bucket(5.0, 6.0))))",
+ "all(group(predefined(foo, bucket<1.0, 2.0>)))",
+ "all(group(predefined(foo, bucket[1.0, 2.0>)))",
+ "all(group(predefined(foo, bucket<1.0, 2.0])))",
+ "all(group(predefined(foo, bucket[1.0, 2.0])))",
+ "all(group(predefined(foo, bucket[1.0, 2.0>, bucket[3.0, 4.0>)))",
+ "all(group(predefined(foo, bucket[1.0, 2.0>, bucket[3.0, 4.0>, bucket[5.0, 6.0>)))",
+ "all(group(predefined(foo, bucket[1.0, 2.0>, bucket[2.0], bucket<2.0, 6.0>)))",
+ "all(group(predefined(foo, bucket('a', 'b'))))",
+ "all(group(predefined(foo, bucket['a', 'b'>)))",
+ "all(group(predefined(foo, bucket<'a', 'c'>)))",
+ "all(group(predefined(foo, bucket<'a', 'b'])))",
+ "all(group(predefined(foo, bucket['a', 'b'])))",
+ "all(group(predefined(foo, bucket('a', 'b'), bucket('c', 'd'))))",
+ "all(group(predefined(foo, bucket('a', 'b'), bucket('c', 'd'), bucket('e', 'f'))))",
+ "all(group(predefined(foo, bucket(\"a\", \"b\"))))",
+ "all(group(predefined(foo, bucket(\"a\", \"b\"), bucket(\"c\", \"d\"))))",
+ "all(group(predefined(foo, bucket(\"a\", \"b\"), bucket(\"c\", \"d\"), bucket(\"e\", \"f\"))))",
+ "all(group(predefined(foo, bucket(a, b))))",
+ "all(group(predefined(foo, bucket(a, b), bucket(c, d))))",
+ "all(group(predefined(foo, bucket(a, b), bucket(c, d), bucket(e, f))))",
+ "all(group(predefined(foo, bucket(a, b), bucket(c), bucket(e, f))))",
+ "all(group(predefined(foo, bucket('a', \"b\"))))",
+ "all(group(predefined(foo, bucket('a', \"b\"), bucket(c, 'd'))))",
+ "all(group(predefined(foo, bucket('a', \"b\"), bucket(c, 'd'), bucket(\"e\", f))))",
+ "all(group(predefined(foo, bucket('a(', \"b)\"), bucket(c, 'd()'))))",
+ "all(group(predefined(foo, bucket({2}, {6}), bucket({7}, {12}))))",
+ "all(group(predefined(foo, bucket({0, 2}, {0, 6}), bucket({0, 7}, {0, 12}))))",
+ "all(group(predefined(foo, bucket({'b', 'a'}, {'k', 'a'}), bucket({'k', 'a'}, {'u', 'b'}))))",
+ "all(group(xorbit(foo, 1)))",
+ "all(group(1))",
+ "all(group(1+2))",
+ "all(group(1-2))",
+ "all(group(1*2))",
+ "all(group(1/2))",
+ "all(group(1%2))",
+ "all(group(1+2+3))",
+ "all(group(1+2-3))",
+ "all(group(1+2*3))",
+ "all(group(1+2/3))",
+ "all(group(1+2%3))",
+ "all(group((1+2)+3))",
+ "all(group((1+2)-3))",
+ "all(group((1+2)*3))",
+ "all(group((1+2)/3))",
+ "all(group((1+2)%3))",
+ "each() as(foo)",
+ "all(each() as(foo) each() as(bar))",
+ "all(group(a) each(each() as(foo) each() as(bar)) each() as(baz))",
+ "all(output(min(a) as(foo)))",
+ "all(output(min(a) as(foo), max(b) as(bar)))",
+ "all(where(bar) all(group(foo)))",
+ "all(group(foo)) where(bar)",
+ "all(group(attribute(foo)))",
+ "all(group(attribute(foo)) order(sum(attribute(a))))",
+ "all(accuracy(0.5))",
+ "all(group(foo) accuracy(1.0))",
+ "all(group(my.little{key}))", "all(group(my.little{\"key\"}))",
+ "all(group(my.little{key }))", "all(group(my.little{\"key\"}))",
+ "all(group(my.little{\"key\"}))", "all(group(my.little{\"key\"}))",
+ "all(group(my.little{\"key{}%\"}))", "all(group(my.little{\"key{}%\"}))",
+ "all(group(artist) max(7))",
+ "all(max(76) all(group(artist) max(7)))",
+ "all(group(artist) max(7) where(true))",
+ "all(group(artist) order(sum(a)) output(count()))",
+ "all(group(artist) order(+sum(a)) output(count()))",
+ "all(group(artist) order(-sum(a)) output(count()))",
+ "all(group(artist) order(-sum(a), +xor(b)) output(count()))",
+ "all(group(artist) max(1) output(count()))",
+ "all(group(-(m)) max(1) output(count()))",
+ "all(group(min) max(1) output(count()))",
+ "all(group(artist) max(2) each(each(output(summary()))))",
+ "all(group(artist) max(2) each(each(output(summary(simple)))))",
+ "all(group(artist) max(5) each(output(count()) each(output(summary()))))",
+ "all(group(ymum()))",
+ "all(group(strlen(attr)))",
+ "all(group(normalizesubject(attr)))",
+ "all(group(strcat(attr, attr2)))",
+ "all(group(tostring(attr)))",
+ "all(group(toraw(attr)))",
+ "all(group(zcurve.x(attr)))",
+ "all(group(zcurve.y(attr)))",
+ "all(group(uca(attr, \"foo\")))",
+ "all(group(uca(attr, \"foo\", \"PRIMARY\")))",
+ "all(group(uca(attr, \"foo\", \"SECONDARY\")))",
+ "all(group(uca(attr, \"foo\", \"TERTIARY\")))",
+ "all(group(uca(attr, \"foo\", \"QUATERNARY\")))",
+ "all(group(uca(attr, \"foo\", \"IDENTICAL\")))",
+ "all(group(tolong(attr)))",
+ "all(group(sort(attr)))",
+ "all(group(reverse(attr)))",
+ "all(group(docidnsspecific()))",
+ "all(group(relevance()))",
+ "all(group(artist) each(each(output(summary()))))",
+ "all(group(artist) max(13) " +
+ " each(group(fixedwidth(year, 21.34)) max(55) output(count()) " +
+ " each(each(output(summary())))))",
+ "all(group(artist) max(13) " +
+ " each(group(predefined(year, bucket(7, 19), bucket(90, 300))) max(55) output(count()) " +
+ " each(each(output(summary())))))",
+ "all(group(artist) max(13) " +
+ " each(group(predefined(year, bucket(7.1, 19.0), bucket(90.7, 300.0))) max(55) output(count()) " +
+ " each(each(output(summary())))))",
+ "all(group(artist) max(13) " +
+ " each(group(predefined(year, bucket('a', 'b'), bucket('peder', 'pedersen'))) " +
+ " max(55) output(count()) " +
+ " each(each(output(summary())))))",
+ "all(output(count()))",
+ "all(group(album) output(count()))",
+ "all(group(album) each(output(count())))",
+ "all(group(artist) each(group(album) output(count()))" +
+ " each(group(song) output(count())))",
+ "all(group(artist) output(count())" +
+ " each(group(album) output(count())" +
+ " each(group(song) output(count())" +
+ " each(each(output(summary()))))))",
+ "all(group(album) order(-$total=sum(length)) each(output($total)))",
+ "all(group(album) max(1) each(output(sum(length))))",
+ "all(group(artist) each(max(2) each(output(summary()))))",
+ "all(group(artist) max(3)" +
+ " each(group(album as(albumsongs)) each(each(output(summary()))))" +
+ " each(group(album as(albumlength)) output(sum(sum(length)))))",
+ "all(group(artist) max(15)" +
+ " each(group(album) " +
+ " each(group(song)" +
+ " each(max(2) each(output(summary()))))))",
+ "all(group(artist) max(15)" +
+ " each(group(album)" +
+ " each(group(song)" +
+ " each(max(2) each(output(summary())))))" +
+ " each(group(song) max(5) order(sum(popularity))" +
+ " each(output(sum(sold)) each(output(summary())))))",
+ "all(group(artist) order(max(relevance) * count()) each(output(count())))",
+ "all(group(artist) each(output(sum(popularity) / count())))",
+ "all(group(artist) accuracy(0.1) each(output(sum(popularity) / count())))",
+ "all(group(debugwait(artist, 3.3, true)))",
+ "all(group(debugwait(artist, 3.3, false)))");
+ }
+}
diff --git a/container-search/src/test/java/com/yahoo/search/grouping/request/parser/GroupingParserTestCase.java b/container-search/src/test/java/com/yahoo/search/grouping/request/parser/GroupingParserTestCase.java
new file mode 100644
index 00000000000..c9fbcad28f2
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/grouping/request/parser/GroupingParserTestCase.java
@@ -0,0 +1,619 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.search.grouping.request.parser;
+
+import com.yahoo.search.grouping.request.AllOperation;
+import com.yahoo.search.grouping.request.EachOperation;
+import com.yahoo.search.grouping.request.GroupingOperation;
+import com.yahoo.search.query.parser.Parsable;
+import com.yahoo.search.query.parser.ParserEnvironment;
+import com.yahoo.search.yql.VespaGroupingStep;
+import com.yahoo.search.yql.YqlParser;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class GroupingParserTestCase {
+
+ // --------------------------------------------------------------------------------
+ //
+ // Tests.
+ //
+ // --------------------------------------------------------------------------------
+
+ @Test
+ public void requireThatMathAllowsWhitespace() {
+ for (String op : Arrays.asList("+", " +", " + ", "+ ",
+ "-", " -", " - ", "- ",
+ "*", " *", " * ", "* ",
+ "/", " /", " / ", "/ ",
+ "%", " %", " % ", "% ")) {
+ assertParse("all(group(foo " + op + " 69) each(output(count())))");
+ assertParse("all(group(foo " + op + " 6 " + op + " 9) each(output(count())))");
+ assertParse("all(group(69 " + op + " foo) each(output(count())))");
+ assertParse("all(group(6 " + op + " 9 " + op + " foo) each(output(count())))");
+ }
+ }
+
+ @Test
+ public void testRequestList() {
+ List<GroupingOperation> lst = GroupingOperation.fromStringAsList("all();each();all() where(true);each()");
+ assertNotNull(lst);
+ assertEquals(4, lst.size());
+ assertTrue(lst.get(0) instanceof AllOperation);
+ assertTrue(lst.get(1) instanceof EachOperation);
+ assertTrue(lst.get(2) instanceof AllOperation);
+ assertTrue(lst.get(3) instanceof EachOperation);
+ }
+
+ @Test
+ public void testAttributeFunctions() {
+ assertParse("all(group(foo) each(output(sum(attribute(bar)))))",
+ "all(group(foo) each(output(sum(attribute(bar)))))");
+ assertParse("all(group(foo) each(output(sum(interpolatedlookup(bar, 0.25)))))",
+ "all(group(foo) each(output(sum(interpolatedlookup(bar, 0.25)))))");
+ assertParse("all(group(foo) each(output(sum(array.at(bar, 42.0)))))",
+ "all(group(foo) each(output(sum(array.at(bar, 42.0)))))");
+ }
+
+ @Test
+ public void requireThatTokenImagesAreNotReservedWords() {
+ List<String> images = Arrays.asList("acos",
+ "acosh",
+ "accuracy",
+ "add",
+ "alias",
+ "all",
+ "and",
+ "array",
+ "as",
+ "at",
+ "asin",
+ "asinh",
+ "atan",
+ "atanh",
+ "attribute",
+ "avg",
+ "bucket",
+ "cat",
+ "cbrt",
+ "cos",
+ "cosh",
+ "count",
+ "debugwait",
+ "div",
+ "docidnsspecific",
+ "each",
+ "exp",
+ "fixedwidth",
+ "floor",
+ "group",
+ "hint",
+ "hypot",
+ "log",
+ "log1p",
+ "log10",
+ "math",
+ "max",
+ "md5",
+ "min",
+ "mod",
+ "mul",
+ "neg",
+ "normalizesubject",
+ "now",
+ "or",
+ "order",
+ "output",
+ "pow",
+ "precision",
+ "predefined",
+ "relevance",
+ "reverse",
+ "sin",
+ "sinh",
+ "size",
+ "sort",
+ "interpolatedlookup",
+ "sqrt",
+ "strcat",
+ "strlen",
+ "sub",
+ "sum",
+ "summary",
+ "tan",
+ "tanh",
+ "time",
+ "date",
+ "dayofmonth",
+ "dayofweek",
+ "dayofyear",
+ "hourofday",
+ "minuteofhour",
+ "monthofyear",
+ "secondofminute",
+ "year",
+ "todouble",
+ "tolong",
+ "toraw",
+ "tostring",
+ "true",
+ "false",
+ "uca",
+ "where",
+ "x",
+ "xor",
+ "xorbit",
+ "y",
+ "ymum",
+ "zcurve");
+ for (String image : images) {
+ assertParse("all(group(" + image + "))", "all(group(" + image + "))");
+ }
+ }
+
+ @Test
+ public void testTokenizedWhitespace() {
+ String expected = "all(group(foo) each(output(max(bar))))";
+
+ assertParse(" all(group(foo)each(output(max(bar))))", expected);
+ assertIllegalArgument("all (group(foo)each(output(max(bar))))", "Encountered \" \" at line 1, column 4.");
+ assertParse("all( group(foo)each(output(max(bar))))", expected);
+ assertIllegalArgument("all(group (foo)each(output(max(bar))))", "Encountered \" \" at line 1, column 10.");
+ assertParse("all(group( foo)each(output(max(bar))))", expected);
+ assertParse("all(group(foo )each(output(max(bar))))", expected);
+ assertParse("all(group(foo) each(output(max(bar))))", expected);
+ assertIllegalArgument("all(group(foo)each (output(max(bar))))", "Encountered \" \" at line 1, column 19.");
+ assertParse("all(group(foo)each( output(max(bar))))", expected);
+ assertIllegalArgument("all(group(foo)each(output (max(bar))))", "Encountered \" \" at line 1, column 26.");
+ assertParse("all(group(foo)each(output( max(bar))))", expected);
+ assertParse("all(group(foo)each(output(max(bar))))", expected);
+ assertParse("all(group(foo)each(output(max( bar))))", expected);
+ assertParse("all(group(foo)each(output(max(bar ))))", expected);
+ assertParse("all(group(foo)each(output(max(bar) )))", expected);
+ assertParse("all(group(foo)each(output(max(bar)) ))", expected);
+ assertParse("all(group(foo)each(output(max(bar))) )", expected);
+ assertParse("all(group(foo)each(output(max(bar)))) ", expected);
+ }
+
+ @Test
+ public void testOperationTypes() {
+ assertParse("all()");
+ assertParse("each()");
+ assertParse("all(each())");
+ assertParse("each(all())");
+ assertParse("all(all() all())");
+ assertParse("all(all() each())");
+ assertParse("all(each() all())");
+ assertParse("all(each() each())");
+ assertParse("each(all() all())");
+ assertIllegalArgument("each(all() each())",
+ "Operation 'each()' can not operate on single hit.");
+ assertIllegalArgument("each(group(foo) all() each())",
+ "Operation 'each(group(foo) all() each())' can not group single hit.");
+ assertIllegalArgument("each(each() all())",
+ "Operation 'each()' can not operate on single hit.");
+ assertIllegalArgument("each(group(foo) each() all())",
+ "Operation 'each(group(foo) each() all())' can not group single hit.");
+ assertIllegalArgument("each(each() each())",
+ "Operation 'each()' can not operate on single hit.");
+ assertIllegalArgument("each(group(foo) each() each())",
+ "Operation 'each(group(foo) each() each())' can not group single hit.");
+ }
+
+ @Test
+ public void testOperationParts() {
+ assertParse("all(group(foo))");
+ assertParse("all(hint(foo))");
+ assertParse("all(hint(foo) hint(bar))");
+ assertParse("all(max(1))");
+ assertParse("all(order(foo))");
+ assertParse("all(order(+foo))");
+ assertParse("all(order(-foo))");
+ assertParse("all(order(foo, bar))");
+ assertParse("all(order(foo, +bar))");
+ assertParse("all(order(foo, -bar))");
+ assertParse("all(order(+foo, bar))");
+ assertParse("all(order(+foo, +bar))");
+ assertParse("all(order(+foo, -bar))");
+ assertParse("all(order(-foo, bar))");
+ assertParse("all(order(-foo, +bar))");
+ assertParse("all(order(-foo, -bar))");
+ assertParse("all(output(min(a)))");
+ assertParse("all(output(min(a), min(b)))");
+ assertParse("all(precision(1))");
+ assertParse("all(where(foo))");
+ assertParse("all(where($foo))");
+ }
+
+ @Test
+ public void testComplexExpressionTypes() {
+ // fixedwidth
+ assertParse("all(group(fixedwidth(foo, 1)))");
+ assertParse("all(group(fixedwidth(foo, 1.2)))");
+
+ // md5
+ assertParse("all(group(md5(foo, 1)))");
+
+ // predefined
+ assertParse("all(group(predefined(foo, bucket(1, 2))))");
+ assertParse("all(group(predefined(foo, bucket(-1, 2))))");
+ assertParse("all(group(predefined(foo, bucket(-2, -1))))");
+ assertParse("all(group(predefined(foo, bucket(1, 2), bucket(3, 4))))");
+ assertParse("all(group(predefined(foo, bucket(1, 2), bucket(3, 4), bucket(5, 6))))");
+ assertParse("all(group(predefined(foo, bucket(1, 2), bucket(2, 3), bucket(3, 4))))");
+ assertParse("all(group(predefined(foo, bucket(-100, 0), bucket(0), bucket<0, 100))))");
+
+ assertParse("all(group(predefined(foo, bucket[1, 2>)))");
+ assertParse("all(group(predefined(foo, bucket[-1, 2>)))");
+ assertParse("all(group(predefined(foo, bucket[-2, -1>)))");
+ assertParse("all(group(predefined(foo, bucket[1, 2>, bucket(3, 4>)))");
+ assertParse("all(group(predefined(foo, bucket[1, 2>, bucket[3, 4>, bucket[5, 6>)))");
+ assertParse("all(group(predefined(foo, bucket[1, 2>, bucket[2, 3>, bucket[3, 4>)))");
+
+ assertParse("all(group(predefined(foo, bucket<1, 5>)))");
+ assertParse("all(group(predefined(foo, bucket[1, 5>)))");
+ assertParse("all(group(predefined(foo, bucket<1, 5])))");
+ assertParse("all(group(predefined(foo, bucket[1, 5])))");
+
+ assertParse("all(group(predefined(foo, bucket<1, inf>)))");
+ assertParse("all(group(predefined(foo, bucket<-inf, -1>)))");
+ assertParse("all(group(predefined(foo, bucket<a, inf>)))");
+ assertParse("all(group(predefined(foo, bucket<'a', inf>)))");
+ assertParse("all(group(predefined(foo, bucket<-inf, a>)))");
+ assertParse("all(group(predefined(foo, bucket[-inf, 'a'>)))");
+ assertParse("all(group(predefined(foo, bucket<-inf, -0.3>)))");
+ assertParse("all(group(predefined(foo, bucket<0.3, inf])))");
+ assertParse("all(group(predefined(foo, bucket<0.3, inf])))");
+ assertParse("all(group(predefined(foo, bucket<infinite, inf])))");
+ assertParse("all(group(predefined(foo, bucket<myinf, inf])))");
+ assertParse("all(group(predefined(foo, bucket<-inf, infinite])))");
+ assertParse("all(group(predefined(foo, bucket<-inf, myinf])))");
+
+ assertParse("all(group(predefined(foo, bucket(1.0, 2.0))))");
+ assertParse("all(group(predefined(foo, bucket(1.0, 2.0), bucket(3.0, 4.0))))");
+ assertParse("all(group(predefined(foo, bucket(1.0, 2.0), bucket(3.0, 4.0), bucket(5.0, 6.0))))");
+
+ assertParse("all(group(predefined(foo, bucket<1.0, 2.0>)))");
+ assertParse("all(group(predefined(foo, bucket[1.0, 2.0>)))");
+ assertParse("all(group(predefined(foo, bucket<1.0, 2.0])))");
+ assertParse("all(group(predefined(foo, bucket[1.0, 2.0])))");
+ assertParse("all(group(predefined(foo, bucket[1.0, 2.0>, bucket[3.0, 4.0>)))");
+ assertParse("all(group(predefined(foo, bucket[1.0, 2.0>, bucket[3.0, 4.0>, bucket[5.0, 6.0>)))");
+ assertParse("all(group(predefined(foo, bucket[1.0, 2.0>, bucket[2.0], bucket<2.0, 6.0>)))");
+
+ assertParse("all(group(predefined(foo, bucket('a', 'b'))))");
+ assertParse("all(group(predefined(foo, bucket['a', 'b'>)))");
+ assertParse("all(group(predefined(foo, bucket<'a', 'c'>)))");
+ assertParse("all(group(predefined(foo, bucket<'a', 'b'])))");
+ assertParse("all(group(predefined(foo, bucket['a', 'b'])))");
+ assertParse("all(group(predefined(foo, bucket('a', 'b'), bucket('c', 'd'))))");
+ assertParse("all(group(predefined(foo, bucket('a', 'b'), bucket('c', 'd'), bucket('e', 'f'))))");
+
+ assertParse("all(group(predefined(foo, bucket(\"a\", \"b\"))))");
+ assertParse("all(group(predefined(foo, bucket(\"a\", \"b\"), bucket(\"c\", \"d\"))))");
+ assertParse("all(group(predefined(foo, bucket(\"a\", \"b\"), bucket(\"c\", \"d\"), bucket(\"e\", \"f\"))))");
+
+ assertParse("all(group(predefined(foo, bucket(a, b))))");
+ assertParse("all(group(predefined(foo, bucket(a, b), bucket(c, d))))");
+ assertParse("all(group(predefined(foo, bucket(a, b), bucket(c, d), bucket(e, f))))");
+ assertParse("all(group(predefined(foo, bucket(a, b), bucket(c), bucket(e, f))))");
+
+ assertParse("all(group(predefined(foo, bucket('a', \"b\"))))");
+ assertParse("all(group(predefined(foo, bucket('a', \"b\"), bucket(c, 'd'))))");
+ assertParse("all(group(predefined(foo, bucket('a', \"b\"), bucket(c, 'd'), bucket(\"e\", f))))");
+
+ assertParse("all(group(predefined(foo, bucket('a(', \"b)\"), bucket(c, 'd()'))))");
+ assertParse("all(group(predefined(foo, bucket({2}, {6}), bucket({7}, {12}))))");
+ assertParse("all(group(predefined(foo, bucket({0, 2}, {0, 6}), bucket({0, 7}, {0, 12}))))");
+ assertParse("all(group(predefined(foo, bucket({'b', 'a'}, {'k', 'a'}), bucket({'k', 'a'}, {'u', 'b'}))))");
+
+ assertIllegalArgument("all(group(predefined(foo, bucket(1, 2.0))))",
+ "Bucket type mismatch, expected 'LongValue' got 'DoubleValue'.");
+ assertIllegalArgument("all(group(predefined(foo, bucket(1, '2'))))",
+ "Bucket type mismatch, expected 'LongValue' got 'StringValue'.");
+ assertIllegalArgument("all(group(predefined(foo, bucket(1, 2), bucket(3.0, 4.0))))",
+ "Bucket type mismatch, expected 'LongValue' got 'DoubleValue'.");
+ assertIllegalArgument("all(group(predefined(foo, bucket(1, 2), bucket('3', '4'))))",
+ "Bucket type mismatch, expected 'LongValue' got 'StringValue'.");
+ assertIllegalArgument("all(group(predefined(foo, bucket(1, 2), bucket(\"3\", \"4\"))))",
+ "Bucket type mismatch, expected 'LongValue' got 'StringValue'.");
+ assertIllegalArgument("all(group(predefined(foo, bucket(1, 2), bucket(three, four))))",
+ "Bucket type mismatch, expected 'LongValue' got 'StringValue'.");
+ assertIllegalArgument("all(group(predefined(foo, bucket<-inf, inf>)))",
+ "Bucket type mismatch, cannot both be infinity");
+ assertIllegalArgument("all(group(predefined(foo, bucket<inf, -inf>)))",
+ "Encountered \"inf\" at line 1, column 34.");
+
+ assertIllegalArgument("all(group(predefined(foo, bucket(2, 1))))",
+ "Bucket to-value can not be less than from-value.");
+ assertIllegalArgument("all(group(predefined(foo, bucket(3, 4), bucket(1, 2))))",
+ "Buckets must be monotonically increasing, got bucket[3, 4> before bucket[1, 2>.");
+ assertIllegalArgument("all(group(predefined(foo, bucket(b, a))))",
+ "Bucket to-value can not be less than from-value.");
+ assertIllegalArgument("all(group(predefined(foo, bucket(b, -inf))))",
+ "Encountered \"-inf\" at line 1, column 37.");
+ assertIllegalArgument("all(group(predefined(foo, bucket(c, d), bucket(a, b))))",
+ "Buckets must be monotonically increasing, got bucket[\"c\", \"d\"> before bucket[\"a\", \"b\">.");
+ assertIllegalArgument("all(group(predefined(foo, bucket(c, d), bucket(-inf, e))))",
+ "Buckets must be monotonically increasing, got bucket[\"c\", \"d\"> before bucket[-inf, \"e\">.");
+ assertIllegalArgument("all(group(predefined(foo, bucket(u, inf), bucket(e, i))))",
+ "Buckets must be monotonically increasing, got bucket[\"u\", inf> before bucket[\"e\", \"i\">.");
+
+ // xorbit
+ assertParse("all(group(xorbit(foo, 1)))");
+ }
+
+ @Test
+ public void testInfixArithmetic() {
+ assertParse("all(group(1))", "all(group(1))");
+ assertParse("all(group(1+2))", "all(group(add(1, 2)))");
+ assertParse("all(group(1-2))", "all(group(sub(1, 2)))");
+ assertParse("all(group(1*2))", "all(group(mul(1, 2)))");
+ assertParse("all(group(1/2))", "all(group(div(1, 2)))");
+ assertParse("all(group(1%2))", "all(group(mod(1, 2)))");
+ assertParse("all(group(1+2+3))", "all(group(add(add(1, 2), 3)))");
+ assertParse("all(group(1+2-3))", "all(group(add(1, sub(2, 3))))");
+ assertParse("all(group(1+2*3))", "all(group(add(1, mul(2, 3))))");
+ assertParse("all(group(1+2/3))", "all(group(add(1, div(2, 3))))");
+ assertParse("all(group(1+2%3))", "all(group(add(1, mod(2, 3))))");
+ assertParse("all(group((1+2)+3))", "all(group(add(add(1, 2), 3)))");
+ assertParse("all(group((1+2)-3))", "all(group(sub(add(1, 2), 3)))");
+ assertParse("all(group((1+2)*3))", "all(group(mul(add(1, 2), 3)))");
+ assertParse("all(group((1+2)/3))", "all(group(div(add(1, 2), 3)))");
+ assertParse("all(group((1+2)%3))", "all(group(mod(add(1, 2), 3)))");
+ }
+
+ @Test
+ public void testOperationLabel() {
+ assertParse("each() as(foo)",
+ "each() as(foo)");
+ assertParse("all(each() as(foo)" +
+ " each() as(bar))",
+ "all(each() as(foo) each() as(bar))");
+ assertParse("all(group(a) each(each() as(foo)" +
+ " each() as(bar))" +
+ " each() as(baz))",
+ "all(group(a) each(each() as(foo) each() as(bar)) each() as(baz))");
+
+ assertIllegalArgument("all() as(foo)", "Encountered \"as\" at line 1, column 7.");
+ assertIllegalArgument("all(all() as(foo))", "Encountered \"as\" at line 1, column 11.");
+ assertIllegalArgument("each(all() as(foo))", "Encountered \"as\" at line 1, column 12.");
+ }
+
+ @Test
+ public void testAttributeName() {
+ assertParse("all(group(foo))");
+ assertIllegalArgument("all(group(foo.))",
+ "Encountered \")\" at line 1, column 15.");
+ assertParse("all(group(foo.bar))");
+ assertIllegalArgument("all(group(foo.bar.))",
+ "Encountered \")\" at line 1, column 19.");
+ assertParse("all(group(foo.bar.baz))");
+ }
+
+ @Test
+ public void testOutputLabel() {
+ assertParse("all(output(min(a) as(foo)))",
+ "all(output(min(a) as(foo)))");
+ assertParse("all(output(min(a) as(foo), max(b) as(bar)))",
+ "all(output(min(a) as(foo), max(b) as(bar)))");
+
+ assertIllegalArgument("all(output(min(a)) as(foo))",
+ "Encountered \"as\" at line 1, column 20.");
+ }
+
+ @Test
+ public void testRootWhere() {
+ String expected = "all(where(bar) all(group(foo)))";
+ assertParse("all(where(bar) all(group(foo)))", expected);
+ assertParse("all(group(foo)) where(bar)", expected);
+ }
+
+ @Test
+ public void testParseBadRequest() {
+ assertIllegalArgument("output(count())",
+ "Encountered \"output\" at line 1, column 1.");
+ assertIllegalArgument("each(output(count()))",
+ "Expression 'count()' not applicable for single hit.");
+ assertIllegalArgument("all(output(count())))",
+ "Encountered \")\" at line 1, column 21.");
+ }
+
+ @Test
+ public void testAttributeFunction() {
+ assertParse("all(group(attribute(foo)))");
+ assertParse("all(group(attribute(foo)) order(sum(attribute(a))))");
+ }
+
+ @Test
+ public void testAccuracy() {
+ assertParse("all(accuracy(0.5))");
+ assertParse("all(group(foo) accuracy(1.0))");
+ }
+
+ @Test
+ public void testMapSyntax() {
+ assertParse("all(group(my.little{key}))", "all(group(my.little{\"key\"}))");
+ assertParse("all(group(my.little{key }))", "all(group(my.little{\"key\"}))");
+ assertParse("all(group(my.little{\"key\"}))", "all(group(my.little{\"key\"}))");
+ assertParse("all(group(my.little{\"key{}%\"}))", "all(group(my.little{\"key{}%\"}))");
+ }
+
+ @Test
+ public void testMisc() {
+ for (String fnc : Arrays.asList("time.date",
+ "time.dayofmonth",
+ "time.dayofweek",
+ "time.dayofyear",
+ "time.hourofday",
+ "time.minuteofhour",
+ "time.monthofyear",
+ "time.secondofminute",
+ "time.year")) {
+ assertParse("each(output(" + fnc + "(foo)))");
+ }
+
+ assertParse("all(group(artist) max(7))");
+ assertParse("all(max(76) all(group(artist) max(7)))");
+ assertParse("all(group(artist) max(7) where(true))");
+ assertParse("all(group(artist) order(sum(a)) output(count()))");
+ assertParse("all(group(artist) order(+sum(a)) output(count()))");
+ assertParse("all(group(artist) order(-sum(a)) output(count()))");
+ assertParse("all(group(artist) order(-sum(a), +xor(b)) output(count()))");
+ assertParse("all(group(artist) max(1) output(count()))");
+ assertParse("all(group(-(m)) max(1) output(count()))");
+ assertParse("all(group(min) max(1) output(count()))");
+ assertParse("all(group(artist) max(2) each(each(output(summary()))))");
+ assertParse("all(group(artist) max(2) each(each(output(summary(simple)))))");
+ assertParse("all(group(artist) max(5) each(output(count()) each(output(summary()))))");
+ assertParse("all(group(ymum()))");
+ assertParse("all(group(strlen(attr)))");
+ assertParse("all(group(normalizesubject(attr)))");
+ assertParse("all(group(strcat(attr, attr2)))");
+ assertParse("all(group(tostring(attr)))");
+ assertParse("all(group(toraw(attr)))");
+ assertParse("all(group(zcurve.x(attr)))");
+ assertParse("all(group(zcurve.y(attr)))");
+ assertParse("all(group(uca(attr, \"foo\")))");
+ assertParse("all(group(uca(attr, \"foo\", \"PRIMARY\")))");
+ assertParse("all(group(uca(attr, \"foo\", \"SECONDARY\")))");
+ assertParse("all(group(uca(attr, \"foo\", \"TERTIARY\")))");
+ assertParse("all(group(uca(attr, \"foo\", \"QUATERNARY\")))");
+ assertParse("all(group(uca(attr, \"foo\", \"IDENTICAL\")))");
+ assertIllegalArgument("all(group(uca(attr, \"foo\", \"foo\")))", "Not a valid UCA strength: foo");
+ assertParse("all(group(tolong(attr)))");
+ assertParse("all(group(sort(attr)))");
+ assertParse("all(group(reverse(attr)))");
+ assertParse("all(group(docidnsspecific()))");
+ assertParse("all(group(relevance()))");
+ // TODO: assertParseRequest("all(group(a) each(output(xor(md5(b)) xor(md5(b, 0, 64)))))");
+ // TODO: assertParseRequest("all(group(a) each(output(xor(xorbit(b)) xor(xorbit(b, 64)))))");
+ assertParse("all(group(artist) each(each(output(summary()))))");
+ assertParse("all(group(artist) max(13) each(group(fixedwidth(year, 21.34)) max(55) output(count()) " +
+ "each(each(output(summary())))))");
+ assertParse("all(group(artist) max(13) each(group(predefined(year, bucket(7, 19), bucket(90, 300))) " +
+ "max(55) output(count()) each(each(output(summary())))))");
+ assertParse("all(group(artist) max(13) each(group(predefined(year, bucket(7.1, 19.0), bucket(90.7, 300.0))) " +
+ "max(55) output(count()) each(each(output(summary())))))");
+ assertParse("all(group(artist) max(13) each(group(predefined(year, bucket('a', 'b'), bucket('cd', 'cde'))) " +
+ "max(55) output(count()) each(each(output(summary())))))");
+
+ assertParse("all(output(count()))");
+ assertParse("all(group(album) output(count()))");
+ assertParse("all(group(album) each(output(count())))");
+ assertParse("all(group(artist) each(group(album) output(count()))" +
+ " each(group(song) output(count())))");
+ assertParse("all(group(artist) output(count())" +
+ " each(group(album) output(count())" +
+ " each(group(song) output(count())" +
+ " each(each(output(summary()))))))");
+ assertParse("all(group(album) order(-$total=sum(length)) each(output($total)))");
+ assertParse("all(group(album) max(1) each(output(sum(length))))");
+ assertParse("all(group(artist) each(max(2) each(output(summary()))))");
+ assertParse("all(group(artist) max(3)" +
+ " each(group(album as(albumsongs)) each(each(output(summary()))))" +
+ " each(group(album as(albumlength)) output(sum(sum(length)))))");
+ assertParse("all(group(artist) max(15)" +
+ " each(group(album) " +
+ " each(group(song)" +
+ " each(max(2) each(output(summary()))))))");
+ assertParse("all(group(artist) max(15)" +
+ " each(group(album)" +
+ " each(group(song)" +
+ " each(max(2) each(output(summary())))))" +
+ " each(group(song) max(5) order(sum(popularity))" +
+ " each(output(sum(sold)) each(output(summary())))))");
+
+ assertParse("all(group(artist) order(max(relevance) * count()) each(output(count())))");
+ assertParse("all(group(artist) each(output(sum(popularity) / count())))");
+ assertParse("all(group(artist) accuracy(0.1) each(output(sum(popularity) / count())))");
+ assertParse("all(group(debugwait(artist, 3.3, true)))");
+ assertParse("all(group(debugwait(artist, 3.3, false)))");
+ assertIllegalArgument("all(group(debugwait(artist, -3.3, true)))",
+ "Encountered \"-\" at line 1, column 29");
+ assertIllegalArgument("all(group(debugwait(artist, 3.3, lol)))",
+ "Encountered \"lol\" at line 1, column 34");
+ }
+
+ @Test
+ public void requireThatParseExceptionMessagesContainErrorMarker() {
+ assertIllegalArgument("foo",
+ "Encountered \"foo\" at line 1, column 1.\n" +
+ "Was expecting one of:\n" +
+ " <SPACE> ...\n" +
+ " \"all\" ...\n" +
+ " \"each\" ...\n" +
+ " \n" +
+ "At position:\n" +
+ "foo\n" +
+ "^");
+ assertIllegalArgument("\n foo",
+ "Encountered \"foo\" at line 2, column 2.\n" +
+ "Was expecting one of:\n" +
+ " <SPACE> ...\n" +
+ " \"all\" ...\n" +
+ " \"each\" ...\n" +
+ " \n" +
+ "At position:\n" +
+ " foo\n" +
+ " ^");
+ }
+
+ // --------------------------------------------------------------------------------
+ //
+ // Utilities.
+ //
+ // --------------------------------------------------------------------------------
+
+ private static void assertParse(String request, String... expectedOperations) {
+ List<GroupingOperation> operations = GroupingOperation.fromStringAsList(request);
+ List<String> actual = new ArrayList<>(operations.size());
+ for (GroupingOperation operation : operations) {
+ operation.resolveLevel(1);
+ actual.add(operation.toString());
+ }
+ if (expectedOperations.length > 0) {
+ assertEquals(Arrays.asList(expectedOperations), actual);
+ }
+
+ // make sure that operation does not mutate through toString() -> fromString()
+ for (GroupingOperation operation : operations) {
+ assertEquals(operation.toString(), GroupingOperation.fromString(operation.toString()).toString());
+ }
+
+ // make sure that yql+ is capable of handling request
+ assertYqlParsable(request, expectedOperations);
+ }
+
+ private static void assertYqlParsable(String request, String... expectedOperations) {
+ YqlParser parser = new YqlParser(new ParserEnvironment());
+ parser.parse(new Parsable().setQuery("select foo from bar where baz contains 'baz' | " + request + ";"));
+ List<VespaGroupingStep> steps = parser.getGroupingSteps();
+ List<String> actual = new ArrayList<>(steps.size());
+ for (VespaGroupingStep step : steps) {
+ actual.add(step.getOperation().toString());
+ }
+ if (expectedOperations.length > 0) {
+ assertEquals(Arrays.asList(expectedOperations), actual);
+ }
+ }
+
+ private static void assertIllegalArgument(String request, String expectedException) {
+ try {
+ GroupingOperation.fromString(request).resolveLevel(1);
+ fail("Expected: " + expectedException);
+ } catch (IllegalArgumentException e) {
+ assertTrue(e.getMessage(), e.getMessage().startsWith(expectedException));
+ }
+ }
+}