mirror of https://github.com/apache/druid.git
SQL: Fix toTimeseriesQuery and toTopNQuery. (#4780)
The former would sometimes eat limits, and the latter would sometimes use the wrong dimension comparator.
This commit is contained in:
parent
3a29521273
commit
c3a1ce6933
|
@ -348,7 +348,7 @@ public class GroupByQuery extends BaseQuery<Row>
|
||||||
throw new IAE("When forcing limit push down, a limit spec must be provided.");
|
throw new IAE("When forcing limit push down, a limit spec must be provided.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (((DefaultLimitSpec) limitSpec).getLimit() == Integer.MAX_VALUE) {
|
if (!((DefaultLimitSpec) limitSpec).isLimited()) {
|
||||||
throw new IAE("When forcing limit push down, the provided limit spec must have a limit.");
|
throw new IAE("When forcing limit push down, the provided limit spec must have a limit.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -373,7 +373,7 @@ public class GroupByQuery extends BaseQuery<Row>
|
||||||
DefaultLimitSpec defaultLimitSpec = (DefaultLimitSpec) limitSpec;
|
DefaultLimitSpec defaultLimitSpec = (DefaultLimitSpec) limitSpec;
|
||||||
|
|
||||||
// If only applying an orderby without a limit, don't try to push down
|
// If only applying an orderby without a limit, don't try to push down
|
||||||
if (defaultLimitSpec.getLimit() == Integer.MAX_VALUE) {
|
if (!defaultLimitSpec.isLimited()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -110,6 +110,11 @@ public class DefaultLimitSpec implements LimitSpec
|
||||||
return limit;
|
return limit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isLimited()
|
||||||
|
{
|
||||||
|
return limit < Integer.MAX_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Function<Sequence<Row>, Sequence<Row>> build(
|
public Function<Sequence<Row>, Sequence<Row>> build(
|
||||||
List<DimensionSpec> dimensions,
|
List<DimensionSpec> dimensions,
|
||||||
|
@ -163,16 +168,16 @@ public class DefaultLimitSpec implements LimitSpec
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sortingNeeded) {
|
if (!sortingNeeded) {
|
||||||
return limit == Integer.MAX_VALUE ? Functions.<Sequence<Row>>identity() : new LimitingFn(limit);
|
return isLimited() ? new LimitingFn(limit) : Functions.identity();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Materialize the Comparator first for fast-fail error checking.
|
// Materialize the Comparator first for fast-fail error checking.
|
||||||
final Ordering<Row> ordering = makeComparator(dimensions, aggs, postAggs);
|
final Ordering<Row> ordering = makeComparator(dimensions, aggs, postAggs);
|
||||||
|
|
||||||
if (limit == Integer.MAX_VALUE) {
|
if (isLimited()) {
|
||||||
return new SortingFn(ordering);
|
|
||||||
} else {
|
|
||||||
return new TopNFunction(ordering, limit);
|
return new TopNFunction(ordering, limit);
|
||||||
|
} else {
|
||||||
|
return new SortingFn(ordering);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -133,6 +133,11 @@ public class Calcites
|
||||||
public static StringComparator getStringComparatorForSqlTypeName(SqlTypeName sqlTypeName)
|
public static StringComparator getStringComparatorForSqlTypeName(SqlTypeName sqlTypeName)
|
||||||
{
|
{
|
||||||
final ValueType valueType = getValueTypeForSqlTypeName(sqlTypeName);
|
final ValueType valueType = getValueTypeForSqlTypeName(sqlTypeName);
|
||||||
|
return getStringComparatorForValueType(valueType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static StringComparator getStringComparatorForValueType(ValueType valueType)
|
||||||
|
{
|
||||||
if (ValueType.isNumeric(valueType)) {
|
if (ValueType.isNumeric(valueType)) {
|
||||||
return StringComparators.NUMERIC;
|
return StringComparators.NUMERIC;
|
||||||
} else if (ValueType.STRING == valueType) {
|
} else if (ValueType.STRING == valueType) {
|
||||||
|
|
|
@ -38,7 +38,6 @@ import io.druid.query.groupby.GroupByQuery;
|
||||||
import io.druid.query.groupby.having.DimFilterHavingSpec;
|
import io.druid.query.groupby.having.DimFilterHavingSpec;
|
||||||
import io.druid.query.groupby.orderby.DefaultLimitSpec;
|
import io.druid.query.groupby.orderby.DefaultLimitSpec;
|
||||||
import io.druid.query.groupby.orderby.OrderByColumnSpec;
|
import io.druid.query.groupby.orderby.OrderByColumnSpec;
|
||||||
import io.druid.query.ordering.StringComparators;
|
|
||||||
import io.druid.query.select.PagingSpec;
|
import io.druid.query.select.PagingSpec;
|
||||||
import io.druid.query.select.SelectQuery;
|
import io.druid.query.select.SelectQuery;
|
||||||
import io.druid.query.timeseries.TimeseriesQuery;
|
import io.druid.query.timeseries.TimeseriesQuery;
|
||||||
|
@ -408,10 +407,12 @@ public class DruidQueryBuilder
|
||||||
}
|
}
|
||||||
|
|
||||||
final Granularity queryGranularity;
|
final Granularity queryGranularity;
|
||||||
|
final boolean descending;
|
||||||
final List<DimensionSpec> dimensions = grouping.getDimensionSpecs();
|
final List<DimensionSpec> dimensions = grouping.getDimensionSpecs();
|
||||||
|
|
||||||
if (dimensions.isEmpty()) {
|
if (dimensions.isEmpty()) {
|
||||||
queryGranularity = Granularities.ALL;
|
queryGranularity = Granularities.ALL;
|
||||||
|
descending = false;
|
||||||
} else if (dimensions.size() == 1) {
|
} else if (dimensions.size() == 1) {
|
||||||
final DimensionSpec dimensionSpec = Iterables.getOnlyElement(dimensions);
|
final DimensionSpec dimensionSpec = Iterables.getOnlyElement(dimensions);
|
||||||
final Granularity gran = ExtractionFns.toQueryGranularity(dimensionSpec.getExtractionFn());
|
final Granularity gran = ExtractionFns.toQueryGranularity(dimensionSpec.getExtractionFn());
|
||||||
|
@ -419,32 +420,42 @@ public class DruidQueryBuilder
|
||||||
if (gran == null || !dimensionSpec.getDimension().equals(Column.TIME_COLUMN_NAME)) {
|
if (gran == null || !dimensionSpec.getDimension().equals(Column.TIME_COLUMN_NAME)) {
|
||||||
// Timeseries only applies if the single dimension is granular __time.
|
// Timeseries only applies if the single dimension is granular __time.
|
||||||
return null;
|
return null;
|
||||||
|
} else {
|
||||||
|
queryGranularity = gran;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Timeseries only applies if sort is null, or if the first sort field is the time dimension.
|
if (limitSpec != null) {
|
||||||
final boolean sortingOnTime =
|
// If there is a limit spec, timeseries cannot LIMIT; and must be ORDER BY time (or nothing).
|
||||||
limitSpec == null || limitSpec.getColumns().isEmpty()
|
|
||||||
|| (limitSpec.getLimit() == Integer.MAX_VALUE
|
|
||||||
&& limitSpec.getColumns().get(0).getDimension().equals(dimensionSpec.getOutputName()));
|
|
||||||
|
|
||||||
if (sortingOnTime) {
|
if (limitSpec.isLimited()) {
|
||||||
queryGranularity = gran;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (limitSpec.getColumns().isEmpty()) {
|
||||||
|
descending = false;
|
||||||
|
} else {
|
||||||
|
// We're ok if the first order by is time (since every time value is distinct, the rest of the columns
|
||||||
|
// wouldn't matter anyway).
|
||||||
|
final OrderByColumnSpec firstOrderBy = limitSpec.getColumns().get(0);
|
||||||
|
|
||||||
|
if (firstOrderBy.getDimension().equals(dimensionSpec.getOutputName())) {
|
||||||
|
// Order by time.
|
||||||
|
descending = firstOrderBy.getDirection() == OrderByColumnSpec.Direction.DESCENDING;
|
||||||
|
} else {
|
||||||
|
// Order by something else.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return null;
|
// No limitSpec.
|
||||||
|
descending = false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// More than one dimension, timeseries cannot handle.
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
final Filtration filtration = Filtration.create(filter).optimize(sourceRowSignature);
|
final Filtration filtration = Filtration.create(filter).optimize(sourceRowSignature);
|
||||||
|
|
||||||
final boolean descending;
|
|
||||||
if (limitSpec != null && !limitSpec.getColumns().isEmpty()) {
|
|
||||||
descending = limitSpec.getColumns().get(0).getDirection() == OrderByColumnSpec.Direction.DESCENDING;
|
|
||||||
} else {
|
|
||||||
descending = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
final Map<String, Object> theContext = Maps.newHashMap();
|
final Map<String, Object> theContext = Maps.newHashMap();
|
||||||
theContext.put("skipEmptyBuckets", true);
|
theContext.put("skipEmptyBuckets", true);
|
||||||
theContext.putAll(plannerContext.getQueryContext());
|
theContext.putAll(plannerContext.getQueryContext());
|
||||||
|
@ -494,7 +505,7 @@ public class DruidQueryBuilder
|
||||||
limitColumn = new OrderByColumnSpec(
|
limitColumn = new OrderByColumnSpec(
|
||||||
dimensionSpec.getOutputName(),
|
dimensionSpec.getOutputName(),
|
||||||
OrderByColumnSpec.Direction.ASCENDING,
|
OrderByColumnSpec.Direction.ASCENDING,
|
||||||
StringComparators.LEXICOGRAPHIC
|
Calcites.getStringComparatorForValueType(dimensionSpec.getOutputType())
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
limitColumn = Iterables.getOnlyElement(limitSpec.getColumns());
|
limitColumn = Iterables.getOnlyElement(limitSpec.getColumns());
|
||||||
|
|
|
@ -220,7 +220,7 @@ public class DruidQueryRel extends DruidRel<DruidQueryRel>
|
||||||
cost += COST_PER_COLUMN * queryBuilder.getGrouping().getPostAggregators().size();
|
cost += COST_PER_COLUMN * queryBuilder.getGrouping().getPostAggregators().size();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (queryBuilder.getLimitSpec() != null && queryBuilder.getLimitSpec().getLimit() < Integer.MAX_VALUE) {
|
if (queryBuilder.getLimitSpec() != null && queryBuilder.getLimitSpec().isLimited()) {
|
||||||
cost *= COST_LIMIT_MULTIPLIER;
|
cost *= COST_LIMIT_MULTIPLIER;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -648,7 +648,7 @@ public class GroupByRules
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!orderBys.isEmpty() || limitSpec.getLimit() < Integer.MAX_VALUE) {
|
if (!orderBys.isEmpty() || limitSpec.isLimited()) {
|
||||||
return druidRel.withQueryBuilder(
|
return druidRel.withQueryBuilder(
|
||||||
druidRel.getQueryBuilder()
|
druidRel.getQueryBuilder()
|
||||||
.withAdjustedGrouping(
|
.withAdjustedGrouping(
|
||||||
|
|
|
@ -5197,6 +5197,42 @@ public class CalciteQueryTest
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testTimeseriesWithLimit() throws Exception
|
public void testTimeseriesWithLimit() throws Exception
|
||||||
|
{
|
||||||
|
testQuery(
|
||||||
|
"SELECT gran, SUM(cnt)\n"
|
||||||
|
+ "FROM (\n"
|
||||||
|
+ " SELECT floor(__time TO month) AS gran, cnt\n"
|
||||||
|
+ " FROM druid.foo\n"
|
||||||
|
+ ") AS x\n"
|
||||||
|
+ "GROUP BY gran\n"
|
||||||
|
+ "LIMIT 1",
|
||||||
|
ImmutableList.of(
|
||||||
|
new TopNQueryBuilder()
|
||||||
|
.dataSource(CalciteTests.DATASOURCE1)
|
||||||
|
.intervals(QSS(Filtration.eternity()))
|
||||||
|
.granularity(Granularities.ALL)
|
||||||
|
.dimension(
|
||||||
|
new ExtractionDimensionSpec(
|
||||||
|
"__time",
|
||||||
|
"d0",
|
||||||
|
ValueType.LONG,
|
||||||
|
new TimeFormatExtractionFn(null, null, null, Granularities.MONTH, true)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.aggregators(AGGS(new LongSumAggregatorFactory("a0", "cnt")))
|
||||||
|
.metric(new DimensionTopNMetricSpec(null, StringComparators.NUMERIC))
|
||||||
|
.threshold(1)
|
||||||
|
.context(QUERY_CONTEXT_DEFAULT)
|
||||||
|
.build()
|
||||||
|
),
|
||||||
|
ImmutableList.of(
|
||||||
|
new Object[]{T("2000-01-01"), 3L}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTimeseriesWithOrderByAndLimit() throws Exception
|
||||||
{
|
{
|
||||||
testQuery(
|
testQuery(
|
||||||
"SELECT gran, SUM(cnt)\n"
|
"SELECT gran, SUM(cnt)\n"
|
||||||
|
|
Loading…
Reference in New Issue