diff options
author | Jon Bratseth <bratseth@yahoo-inc.com> | 2016-06-15 23:09:44 +0200 |
---|---|---|
committer | Jon Bratseth <bratseth@yahoo-inc.com> | 2016-06-15 23:09:44 +0200 |
commit | 72231250ed81e10d66bfe70701e64fa5fe50f712 (patch) | |
tree | 2728bba1131a6f6e5bdf95afec7d7ff9358dac50 /container-search/src/test/java/com/yahoo/search/grouping/request |
Publish
Diffstat (limited to 'container-search/src/test/java/com/yahoo/search/grouping/request')
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)); + } + } +} |