blob: fbf42ca376510b942eafd6408d2b5b0b09d22c3f [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.druid.query.groupby.having;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import org.apache.druid.jackson.DefaultObjectMapper;
import org.apache.druid.java.util.common.StringUtils;
import org.apache.druid.query.cache.CacheKeyBuilder;
import org.apache.druid.query.groupby.GroupByQuery;
import org.apache.druid.query.groupby.ResultRow;
import org.junit.Assert;
import org.junit.Test;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
public class HavingSpecTest
{
private static final ResultRow ROW = ResultRow.of(10f);
@Test
public void testHavingClauseSerde()
{
List<HavingSpec> havings = Arrays.asList(
new GreaterThanHavingSpec("agg", 1.3),
new OrHavingSpec(
Arrays.asList(
new LessThanHavingSpec("lessAgg", 1L),
new NotHavingSpec(new EqualToHavingSpec("equalAgg", 2.0))
)
)
);
HavingSpec andHavingSpec = new AndHavingSpec(havings);
Map<String, Object> notMap = ImmutableMap.of(
"type", "not",
"havingSpec", ImmutableMap.of("type", "equalTo", "aggregation", "equalAgg", "value", 2.0)
);
Map<String, Object> lessMap = ImmutableMap.of(
"type", "lessThan",
"aggregation", "lessAgg",
"value", 1
);
Map<String, Object> greaterMap = ImmutableMap.of(
"type", "greaterThan",
"aggregation", "agg",
"value", 1.3
);
Map<String, Object> orMap = ImmutableMap.of(
"type", "or",
"havingSpecs", ImmutableList.of(lessMap, notMap)
);
Map<String, Object> payloadMap = ImmutableMap.of(
"type", "and",
"havingSpecs", ImmutableList.of(greaterMap, orMap)
);
ObjectMapper mapper = new DefaultObjectMapper();
Assert.assertEquals(andHavingSpec, mapper.convertValue(payloadMap, AndHavingSpec.class));
}
@Test(expected = IllegalArgumentException.class)
public void testTypeTypo()
{
Map<String, Object> greaterMap = ImmutableMap.of(
"type", "nonExistingType",
"aggregation", "agg",
"value", 1.3
);
ObjectMapper mapper = new DefaultObjectMapper();
// noinspection unused
HavingSpec spec = mapper.convertValue(greaterMap, HavingSpec.class);
}
@Test
public void testGreaterThanHavingSpec()
{
GreaterThanHavingSpec spec = new GreaterThanHavingSpec("metric", Long.MAX_VALUE - 10);
Assert.assertFalse(spec.eval(getTestRow(Long.MAX_VALUE - 10)));
Assert.assertFalse(spec.eval(getTestRow(Long.MAX_VALUE - 15)));
Assert.assertTrue(spec.eval(getTestRow(Long.MAX_VALUE - 5)));
Assert.assertTrue(spec.eval(getTestRow(String.valueOf(Long.MAX_VALUE - 5))));
Assert.assertFalse(spec.eval(getTestRow(100.05f)));
spec = new GreaterThanHavingSpec("metric", 100.56f);
Assert.assertFalse(spec.eval(getTestRow(100.56f)));
Assert.assertFalse(spec.eval(getTestRow(90.53f)));
Assert.assertFalse(spec.eval(getTestRow("90.53f")));
Assert.assertTrue(spec.eval(getTestRow(101.34f)));
Assert.assertTrue(spec.eval(getTestRow(Long.MAX_VALUE)));
}
@Test
public void testLessThanHavingSpec()
{
LessThanHavingSpec spec = new LessThanHavingSpec("metric", Long.MAX_VALUE - 10);
Assert.assertFalse(spec.eval(getTestRow(Long.MAX_VALUE - 10)));
Assert.assertTrue(spec.eval(getTestRow(Long.MAX_VALUE - 15)));
Assert.assertTrue(spec.eval(getTestRow(String.valueOf(Long.MAX_VALUE - 15))));
Assert.assertFalse(spec.eval(getTestRow(Long.MAX_VALUE - 5)));
Assert.assertTrue(spec.eval(getTestRow(100.05f)));
spec = new LessThanHavingSpec("metric", 100.56f);
Assert.assertFalse(spec.eval(getTestRow(100.56f)));
Assert.assertTrue(spec.eval(getTestRow(90.53f)));
Assert.assertFalse(spec.eval(getTestRow(101.34f)));
Assert.assertFalse(spec.eval(getTestRow("101.34f")));
Assert.assertFalse(spec.eval(getTestRow(Long.MAX_VALUE)));
}
private ResultRow getTestRow(Object metricValue)
{
return ResultRow.of(metricValue);
}
@Test
public void testEqualHavingSpec()
{
EqualToHavingSpec spec = new EqualToHavingSpec("metric", Long.MAX_VALUE - 10);
Assert.assertTrue(spec.eval(getTestRow(Long.MAX_VALUE - 10)));
Assert.assertFalse(spec.eval(getTestRow(Long.MAX_VALUE - 5)));
Assert.assertFalse(spec.eval(getTestRow(100.05f)));
spec = new EqualToHavingSpec("metric", 100.56f);
Assert.assertFalse(spec.eval(getTestRow(100L)));
Assert.assertFalse(spec.eval(getTestRow(100.0)));
Assert.assertFalse(spec.eval(getTestRow(100d)));
Assert.assertFalse(spec.eval(getTestRow(100.56d))); // False since 100.56d != (double) 100.56f
Assert.assertFalse(spec.eval(getTestRow(90.53d)));
Assert.assertTrue(spec.eval(getTestRow(100.56f)));
Assert.assertFalse(spec.eval(getTestRow(90.53f)));
Assert.assertFalse(spec.eval(getTestRow(Long.MAX_VALUE)));
spec = new EqualToHavingSpec("metric", 100.56d);
Assert.assertFalse(spec.eval(getTestRow(100L)));
Assert.assertFalse(spec.eval(getTestRow(100.0)));
Assert.assertFalse(spec.eval(getTestRow(100d)));
Assert.assertTrue(spec.eval(getTestRow(100.56d)));
Assert.assertFalse(spec.eval(getTestRow(90.53d)));
Assert.assertFalse(spec.eval(getTestRow(100.56f))); // False since 100.56d != (double) 100.56f
Assert.assertFalse(spec.eval(getTestRow(90.53f)));
Assert.assertFalse(spec.eval(getTestRow(Long.MAX_VALUE)));
spec = new EqualToHavingSpec("metric", 100.0f);
Assert.assertTrue(spec.eval(getTestRow(100L)));
Assert.assertTrue(spec.eval(getTestRow(100.0)));
Assert.assertTrue(spec.eval(getTestRow(100d)));
Assert.assertFalse(spec.eval(getTestRow(100.56d)));
Assert.assertFalse(spec.eval(getTestRow(90.53d)));
Assert.assertFalse(spec.eval(getTestRow(100.56f)));
Assert.assertFalse(spec.eval(getTestRow(90.53f)));
Assert.assertFalse(spec.eval(getTestRow(Long.MAX_VALUE)));
spec = new EqualToHavingSpec("metric", 100.0d);
Assert.assertTrue(spec.eval(getTestRow(100L)));
Assert.assertTrue(spec.eval(getTestRow(100.0)));
Assert.assertTrue(spec.eval(getTestRow(100d)));
Assert.assertFalse(spec.eval(getTestRow(100.56d)));
Assert.assertFalse(spec.eval(getTestRow(90.53d)));
Assert.assertFalse(spec.eval(getTestRow(100.56f)));
Assert.assertFalse(spec.eval(getTestRow(90.53f)));
Assert.assertFalse(spec.eval(getTestRow(Long.MAX_VALUE)));
spec = new EqualToHavingSpec("metric", 100);
Assert.assertTrue(spec.eval(getTestRow(100L)));
Assert.assertTrue(spec.eval(getTestRow(100.0)));
Assert.assertTrue(spec.eval(getTestRow(100d)));
Assert.assertFalse(spec.eval(getTestRow(100.56d)));
Assert.assertFalse(spec.eval(getTestRow(90.53d)));
Assert.assertFalse(spec.eval(getTestRow(100.56f)));
Assert.assertFalse(spec.eval(getTestRow(90.53f)));
Assert.assertFalse(spec.eval(getTestRow(Long.MAX_VALUE)));
spec = new EqualToHavingSpec("metric", 100L);
Assert.assertTrue(spec.eval(getTestRow(100L)));
Assert.assertTrue(spec.eval(getTestRow(100.0)));
Assert.assertTrue(spec.eval(getTestRow(100d)));
Assert.assertFalse(spec.eval(getTestRow(100.56d)));
Assert.assertFalse(spec.eval(getTestRow(90.53d)));
Assert.assertFalse(spec.eval(getTestRow(100.56f)));
Assert.assertFalse(spec.eval(getTestRow(90.53f)));
Assert.assertFalse(spec.eval(getTestRow(Long.MAX_VALUE)));
}
private static class CountingHavingSpec implements HavingSpec
{
private final AtomicInteger counter;
private final boolean value;
private CountingHavingSpec(AtomicInteger counter, boolean value)
{
this.counter = counter;
this.value = value;
}
@Override
public void setQuery(GroupByQuery query)
{
// Nothing to do.
}
@Override
public boolean eval(ResultRow row)
{
counter.incrementAndGet();
return value;
}
@Override
public byte[] getCacheKey()
{
return new CacheKeyBuilder(HavingSpecUtil.CACHE_TYPE_ID_COUNTING)
.appendByte((byte) (value ? 1 : 0))
.appendByteArray(StringUtils.toUtf8(String.valueOf(counter)))
.build();
}
}
@Test
public void testAndHavingSpecShouldSupportShortcutEvaluation()
{
AtomicInteger counter = new AtomicInteger(0);
AndHavingSpec spec = new AndHavingSpec(ImmutableList.of(
new CountingHavingSpec(counter, true),
new CountingHavingSpec(counter, false),
new CountingHavingSpec(counter, true),
new CountingHavingSpec(counter, false)
));
spec.eval(ROW);
Assert.assertEquals(2, counter.get());
}
@Test
public void testAndHavingSpec()
{
AtomicInteger counter = new AtomicInteger(0);
AndHavingSpec spec = new AndHavingSpec(ImmutableList.of(
new CountingHavingSpec(counter, true),
new CountingHavingSpec(counter, true),
new CountingHavingSpec(counter, true),
new CountingHavingSpec(counter, true)
));
spec.eval(ROW);
Assert.assertEquals(4, counter.get());
counter.set(0);
spec = new AndHavingSpec(ImmutableList.of(
new CountingHavingSpec(counter, false),
new CountingHavingSpec(counter, true),
new CountingHavingSpec(counter, true),
new CountingHavingSpec(counter, true)
));
spec.eval(ROW);
Assert.assertEquals(1, counter.get());
}
@Test
public void testOrHavingSpecSupportsShortcutEvaluation()
{
AtomicInteger counter = new AtomicInteger(0);
OrHavingSpec spec = new OrHavingSpec(ImmutableList.of(
new CountingHavingSpec(counter, true),
new CountingHavingSpec(counter, true),
new CountingHavingSpec(counter, true),
new CountingHavingSpec(counter, false)
));
spec.eval(ROW);
Assert.assertEquals(1, counter.get());
}
@Test
public void testOrHavingSpec()
{
AtomicInteger counter = new AtomicInteger(0);
OrHavingSpec spec = new OrHavingSpec(ImmutableList.of(
new CountingHavingSpec(counter, false),
new CountingHavingSpec(counter, false),
new CountingHavingSpec(counter, false),
new CountingHavingSpec(counter, false)
));
spec.eval(ROW);
Assert.assertEquals(4, counter.get());
counter.set(0);
spec = new OrHavingSpec(ImmutableList.of(
new CountingHavingSpec(counter, false),
new CountingHavingSpec(counter, false),
new CountingHavingSpec(counter, false),
new CountingHavingSpec(counter, true)
));
spec.eval(ROW);
Assert.assertEquals(4, counter.get());
}
@Test
public void testNotHavingSepc()
{
NotHavingSpec spec = new NotHavingSpec(new NeverHavingSpec());
Assert.assertTrue(spec.eval(ROW));
spec = new NotHavingSpec(new AlwaysHavingSpec());
Assert.assertFalse(spec.eval(ROW));
}
}