/**
* Copyright (C) 2014-2016 LinkedIn Corp. (pinot-core@linkedin.com)
*
* Licensed 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 com.linkedin.pinot.core.query.reduce;
import com.linkedin.pinot.common.exception.QueryException;
import com.linkedin.pinot.common.request.BrokerRequest;
import com.linkedin.pinot.common.request.Selection;
import com.linkedin.pinot.common.response.ProcessingException;
import com.linkedin.pinot.common.utils.DataSchema;
import com.linkedin.pinot.core.operator.blocks.IntermediateResultsBlock;
import com.linkedin.pinot.core.query.aggregation.AggregationFunctionContext;
import com.linkedin.pinot.core.query.selection.SelectionOperatorUtils;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.PriorityQueue;
import javax.annotation.Nonnull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The <code>CombineService</code> class provides the utility methods to combine {@link IntermediateResultsBlock}s.
*/
@SuppressWarnings({"ConstantConditions", "unchecked"})
public class CombineService {
private CombineService() {
}
private static final Logger LOGGER = LoggerFactory.getLogger(CombineService.class);
public static void mergeTwoBlocks(@Nonnull BrokerRequest brokerRequest, @Nonnull IntermediateResultsBlock mergedBlock,
@Nonnull IntermediateResultsBlock blockToMerge) {
// Combine processing exceptions.
List<ProcessingException> mergedProcessingExceptions = mergedBlock.getProcessingExceptions();
List<ProcessingException> processingExceptionsToMerge = blockToMerge.getProcessingExceptions();
if (mergedProcessingExceptions == null) {
mergedBlock.setProcessingExceptions(processingExceptionsToMerge);
} else if (processingExceptionsToMerge != null) {
mergedProcessingExceptions.addAll(processingExceptionsToMerge);
}
// Combine result.
if (brokerRequest.isSetAggregationsInfo()) {
// Combine aggregation result.
if (!brokerRequest.isSetGroupBy()) {
// Combine aggregation only result.
// Might be null if caught exception during query execution.
List<Object> aggregationResultToMerge = blockToMerge.getAggregationResult();
if (aggregationResultToMerge == null) {
// No data in block to merge.
return;
}
AggregationFunctionContext[] mergedAggregationFunctionContexts = mergedBlock.getAggregationFunctionContexts();
if (mergedAggregationFunctionContexts == null) {
// No data in merged block.
mergedBlock.setAggregationFunctionContexts(blockToMerge.getAggregationFunctionContexts());
mergedBlock.setAggregationResults(aggregationResultToMerge);
}
// Merge two block.
List<Object> mergedAggregationResult = mergedBlock.getAggregationResult();
int numAggregationFunctions = mergedAggregationFunctionContexts.length;
for (int i = 0; i < numAggregationFunctions; i++) {
mergedAggregationResult.set(i, mergedAggregationFunctionContexts[i].getAggregationFunction()
.merge(mergedAggregationResult.get(i), aggregationResultToMerge.get(i)));
}
} else {
// Combine aggregation group-by result, which should not come into CombineService.
throw new UnsupportedOperationException();
}
} else {
// Combine selection result.
// Data schema will be null if exceptions caught during query processing.
// Result set size will be zero if no row matches the predicate.
DataSchema mergedBlockSchema = mergedBlock.getSelectionDataSchema();
DataSchema blockToMergeSchema = blockToMerge.getSelectionDataSchema();
Collection<Serializable[]> mergedBlockResultSet = mergedBlock.getSelectionResult();
Collection<Serializable[]> blockToMergeResultSet = blockToMerge.getSelectionResult();
if (mergedBlockSchema == null || mergedBlockResultSet.size() == 0) {
// No data in merged block.
// If block to merge schema is not null, set its data schema and result to the merged block.
if (blockToMergeSchema != null) {
mergedBlock.setSelectionDataSchema(blockToMergeSchema);
mergedBlock.setSelectionResult(blockToMergeResultSet);
}
} else {
// Some data in merged block.
Selection selection = brokerRequest.getSelections();
boolean isSelectionOrderBy = selection.isSetSelectionSortSequence();
int selectionSize = selection.getSize();
// No need to merge if already got enough rows for selection only.
if (!isSelectionOrderBy && (mergedBlockResultSet.size() == selectionSize)) {
return;
}
// Merge only if there are data in block to merge.
if (blockToMergeSchema != null && blockToMergeResultSet.size() > 0) {
if (mergedBlockSchema.isTypeCompatibleWith(blockToMergeSchema)) {
// Two blocks are mergeable.
// Upgrade the merged block schema if necessary.
mergedBlockSchema.upgradeToCover(blockToMergeSchema);
// Merge two blocks.
if (isSelectionOrderBy) {
// Combine selection order-by.
SelectionOperatorUtils.mergeWithOrdering((PriorityQueue<Serializable[]>) mergedBlockResultSet,
blockToMergeResultSet, selection.getOffset() + selectionSize);
} else {
// Combine selection only.
SelectionOperatorUtils.mergeWithoutOrdering(mergedBlockResultSet, blockToMergeResultSet, selectionSize);
}
mergedBlock.setSelectionResult(mergedBlockResultSet);
} else {
// Two blocks are not mergeable.
String errorMessage = "Data schema inconsistency between merged block schema: " + mergedBlockSchema
+ " and block to merge schema: " + blockToMergeSchema + ", drop block to merge";
LOGGER.info(errorMessage);
mergedBlock.addToProcessingExceptions(
QueryException.getException(QueryException.MERGE_RESPONSE_ERROR, errorMessage));
}
}
}
}
}
}