blob: 1d69c82cbbdbca4aea35f772d971a3023e454a51 [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;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.apache.druid.guice.annotations.PublicApi;
import org.apache.druid.java.util.common.ISE;
import org.apache.druid.query.aggregation.AggregatorFactory;
import org.apache.druid.query.aggregation.PostAggregator;
import org.apache.druid.query.planning.DataSourceAnalysis;
import org.apache.druid.query.spec.MultipleSpecificSegmentSpec;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@PublicApi
public class Queries
{
public static List<PostAggregator> decoratePostAggregators(
List<PostAggregator> postAggs,
Map<String, AggregatorFactory> aggFactories
)
{
List<PostAggregator> decorated = Lists.newArrayListWithExpectedSize(postAggs.size());
for (PostAggregator aggregator : postAggs) {
decorated.add(aggregator.decorate(aggFactories));
}
return decorated;
}
/**
* Like {@link #prepareAggregations(List, List, List)} but with otherOutputNames as an empty list. Deprecated
* because it makes it easy to forget to include dimensions, etc. in "otherOutputNames".
*
* @param aggFactories aggregator factories for this query
* @param postAggs post-aggregators for this query
*
* @return decorated post-aggregators
*
* @throws NullPointerException if aggFactories is null
* @throws IllegalArgumentException if there are any output name collisions or missing post-aggregator inputs
*/
@Deprecated
public static List<PostAggregator> prepareAggregations(
List<AggregatorFactory> aggFactories,
List<PostAggregator> postAggs
)
{
return prepareAggregations(Collections.emptyList(), aggFactories, postAggs);
}
/**
* Returns decorated post-aggregators, based on original un-decorated post-aggregators. In addition, this method
* also verifies that there are no output name collisions, and that all of the post-aggregators' required input
* fields are present.
*
* @param otherOutputNames names of fields that will appear in the same output namespace as aggregators and
* post-aggregators, and are also assumed to be valid inputs to post-aggregators. For most
* built-in query types, this is either empty, or the list of dimension output names.
* @param aggFactories aggregator factories for this query
* @param postAggs post-aggregators for this query
*
* @return decorated post-aggregators
*
* @throws NullPointerException if otherOutputNames or aggFactories is null
* @throws IllegalArgumentException if there are any output name collisions or missing post-aggregator inputs
*/
public static List<PostAggregator> prepareAggregations(
List<String> otherOutputNames,
List<AggregatorFactory> aggFactories,
List<PostAggregator> postAggs
)
{
Preconditions.checkNotNull(otherOutputNames, "otherOutputNames cannot be null");
Preconditions.checkNotNull(aggFactories, "aggregations cannot be null");
final Set<String> combinedOutputNames = new HashSet<>(otherOutputNames);
final Map<String, AggregatorFactory> aggsFactoryMap = new HashMap<>();
for (AggregatorFactory aggFactory : aggFactories) {
Preconditions.checkArgument(
combinedOutputNames.add(aggFactory.getName()),
"[%s] already defined", aggFactory.getName()
);
aggsFactoryMap.put(aggFactory.getName(), aggFactory);
}
if (postAggs != null && !postAggs.isEmpty()) {
List<PostAggregator> decorated = Lists.newArrayListWithExpectedSize(postAggs.size());
for (final PostAggregator postAgg : postAggs) {
final Set<String> dependencies = postAgg.getDependentFields();
final Set<String> missing = Sets.difference(dependencies, combinedOutputNames);
Preconditions.checkArgument(
missing.isEmpty(),
"Missing fields [%s] for postAggregator [%s]", missing, postAgg.getName()
);
Preconditions.checkArgument(
combinedOutputNames.add(postAgg.getName()),
"[%s] already defined", postAgg.getName()
);
decorated.add(postAgg.decorate(aggsFactoryMap));
}
return decorated;
}
return postAggs;
}
/**
* Rewrite "query" to refer to some specific segment descriptors.
*
* The dataSource for "query" must be based on a single table for this operation to be valid. Otherwise, this
* function will throw an exception.
*
* Unlike the seemingly-similar {@code query.withQuerySegmentSpec(new MultipleSpecificSegmentSpec(descriptors))},
* this this method will walk down subqueries found within the query datasource, if any, and modify the lowest-level
* subquery. The effect is that
* {@code DataSourceAnalysis.forDataSource(query.getDataSource()).getBaseQuerySegmentSpec()} is guaranteed to return
* either {@code new MultipleSpecificSegmentSpec(descriptors)} or empty.
*
* Because {@link BaseQuery#getRunner} is implemented using {@link DataSourceAnalysis#getBaseQuerySegmentSpec}, this
* method will cause the runner to be a specific-segments runner.
*/
public static <T> Query<T> withSpecificSegments(final Query<T> query, final List<SegmentDescriptor> descriptors)
{
final Query<T> retVal;
if (query.getDataSource() instanceof QueryDataSource) {
final Query<?> subQuery = ((QueryDataSource) query.getDataSource()).getQuery();
retVal = query.withDataSource(new QueryDataSource(withSpecificSegments(subQuery, descriptors)));
} else {
retVal = query.withQuerySegmentSpec(new MultipleSpecificSegmentSpec(descriptors));
}
// Verify preconditions and invariants, just in case.
final DataSourceAnalysis analysis = DataSourceAnalysis.forDataSource(retVal.getDataSource());
if (!analysis.getBaseTableDataSource().isPresent()) {
throw new ISE("Unable to apply specific segments to non-table-based dataSource[%s]", query.getDataSource());
}
if (analysis.getBaseQuerySegmentSpec().isPresent()
&& !analysis.getBaseQuerySegmentSpec().get().equals(new MultipleSpecificSegmentSpec(descriptors))) {
// If you see the error message below, it's a bug in either this function or in DataSourceAnalysis.
throw new ISE("Unable to apply specific segments to query with dataSource[%s]", query.getDataSource());
}
return retVal;
}
}