blob: b22d9729252a9bb389e881032f63a950510c4081 [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.lens.cube.parse;
import static org.apache.lens.cube.parse.CandidateUtil.getColumns;
import java.util.*;
import org.apache.lens.cube.error.LensCubeErrorCode;
import org.apache.lens.cube.metadata.TimeRange;
import org.apache.lens.server.api.error.LensException;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class CandidateCoveringSetsResolver implements ContextRewriter {
@Override
public void rewriteContext(CubeQueryContext cubeql) throws LensException {
if (!cubeql.hasCubeInQuery()) {
return; //Dimension query
}
if (cubeql.getCandidates().size() == 0){
cubeql.throwNoCandidateFactException();
}
List<QueriedPhraseContext> qpcList = cubeql.getQueriedPhrases();
Set<QueriedPhraseContext> queriedMsrs = new HashSet<>();
for (QueriedPhraseContext qpc : qpcList) {
if (qpc.hasMeasures(cubeql)) {
queriedMsrs.add(qpc);
}
}
List<Candidate> timeRangeCoveringSet = resolveTimeRangeCoveringFactSet(cubeql, queriedMsrs, qpcList);
if (timeRangeCoveringSet.isEmpty()) {
throw new LensException(LensCubeErrorCode.NO_UNION_CANDIDATE_AVAILABLE.getLensErrorInfo(),
cubeql.getCube().getName(), cubeql.getTimeRanges().toString(), getColumns(queriedMsrs).toString());
}
log.info("Time covering candidates :{}", timeRangeCoveringSet);
if (queriedMsrs.isEmpty()) {
cubeql.getCandidates().clear();
cubeql.getCandidates().addAll(timeRangeCoveringSet);
} else if (!timeRangeCoveringSet.isEmpty()) {
List<List<Candidate>> measureCoveringSets = resolveJoinCandidates(timeRangeCoveringSet, queriedMsrs, cubeql);
if (measureCoveringSets.isEmpty()) {
throw new LensException(LensCubeErrorCode.NO_JOIN_CANDIDATE_AVAILABLE.getLensErrorInfo(),
cubeql.getCube().getName(), getColumns(queriedMsrs).toString());
}
updateFinalCandidates(measureCoveringSets, cubeql);
}
log.info("Final Time and Measure covering candidates :{}", cubeql.getCandidates());
}
private Candidate createJoinCandidate(List<Candidate> childCandidates, CubeQueryContext cubeql) {
Candidate cand;
Candidate first = childCandidates.get(0);
Candidate second = childCandidates.get(1);
cand = new JoinCandidate(first, second, cubeql);
for (int i = 2; i < childCandidates.size(); i++) {
cand = new JoinCandidate(cand, childCandidates.get(i), cubeql);
}
return cand;
}
private void updateFinalCandidates(List<List<Candidate>> joinCandidates, CubeQueryContext cubeql) {
List<Candidate> finalCandidates = new ArrayList<>();
for (List<Candidate> joinCandidate : joinCandidates) {
if (joinCandidate.size() == 1) {
finalCandidates.add(joinCandidate.iterator().next());
} else {
finalCandidates.add(createJoinCandidate(joinCandidate, cubeql));
}
}
cubeql.getCandidates().clear();
cubeql.getCandidates().addAll(finalCandidates);
}
private boolean isCandidateCoveringTimeRanges(UnionCandidate uc, List<TimeRange> ranges) {
for (TimeRange range : ranges) {
if (!CandidateUtil.isTimeRangeCovered(uc.getChildren(), range.getFromDate(), range.getToDate())) {
return false;
}
}
return true;
}
private void pruneUnionCandidatesNotCoveringAllRanges(List<UnionCandidate> ucs, CubeQueryContext cubeql) {
for (Iterator<UnionCandidate> itr = ucs.iterator(); itr.hasNext();) {
UnionCandidate uc = itr.next();
if (!isCandidateCoveringTimeRanges(uc, cubeql.getTimeRanges())) {
itr.remove();
cubeql.addCandidatePruningMsg(uc, CandidateTablePruneCause.storageNotAvailableInRange(cubeql.getTimeRanges()));
}
}
}
private List<Candidate> resolveTimeRangeCoveringFactSet(CubeQueryContext cubeql,
Set<QueriedPhraseContext> queriedMsrs, List<QueriedPhraseContext> qpcList) throws LensException {
List<Candidate> candidateSet = new ArrayList<>();
if (!cubeql.getCandidates().isEmpty()) {
// All Candidates
List<Candidate> allCandidates = new ArrayList<>(cubeql.getCandidates());
// Partially valid candidates
List<Candidate> allCandidatesPartiallyValid = new ArrayList<>();
for (Candidate cand : allCandidates) {
// Assuming initial list of candidates populated are StorageCandidate
assert (cand instanceof StorageCandidate);
StorageCandidate sc = (StorageCandidate) cand;
if (CandidateUtil.isValidForTimeRanges(sc, cubeql.getTimeRanges())) {
candidateSet.add(CandidateUtil.cloneStorageCandidate(sc));
} else if (CandidateUtil.isPartiallyValidForTimeRanges(sc, cubeql.getTimeRanges())) {
allCandidatesPartiallyValid.add(CandidateUtil.cloneStorageCandidate(sc));
} else {
cubeql.addCandidatePruningMsg(sc, CandidateTablePruneCause.storageNotAvailableInRange(
cubeql.getTimeRanges()));
}
}
// Get all covering fact sets
List<UnionCandidate> unionCoveringSet =
getCombinations(new ArrayList<>(allCandidatesPartiallyValid), cubeql);
// Sort the Collection based on no of elements
unionCoveringSet.sort(new CandidateUtil.ChildrenSizeBasedCandidateComparator<UnionCandidate>());
// prune non covering sets
pruneUnionCandidatesNotCoveringAllRanges(unionCoveringSet, cubeql);
// prune candidate set which doesn't contain any common measure i
pruneUnionCoveringSetWithoutAnyCommonMeasure(unionCoveringSet, queriedMsrs, cubeql);
// prune redundant covering sets
pruneRedundantUnionCoveringSets(unionCoveringSet);
// pruing done in the previous steps, now create union candidates
candidateSet.addAll(unionCoveringSet);
updateQueriableMeasures(candidateSet, qpcList, cubeql);
}
return candidateSet;
}
private boolean isMeasureAnswerablebyUnionCandidate(QueriedPhraseContext msr, Candidate uc,
CubeQueryContext cubeql) throws LensException {
// Candidate is a single StorageCandidate
if ((uc instanceof StorageCandidate) && !msr.isEvaluable(cubeql, (StorageCandidate) uc)) {
return false;
} else if ((uc instanceof UnionCandidate)){
for (Candidate cand : uc.getChildren()) {
if (!msr.isEvaluable(cubeql, (StorageCandidate) cand)) {
return false;
}
}
}
return true;
}
private void pruneUnionCoveringSetWithoutAnyCommonMeasure(List<UnionCandidate> ucs,
Set<QueriedPhraseContext> queriedMsrs,
CubeQueryContext cubeql) throws LensException {
for (ListIterator<UnionCandidate> itr = ucs.listIterator(); itr.hasNext();) {
boolean toRemove = true;
UnionCandidate uc = itr.next();
for (QueriedPhraseContext msr : queriedMsrs) {
if (isMeasureAnswerablebyUnionCandidate(msr, uc, cubeql)) {
toRemove = false;
break;
}
}
if (toRemove) {
itr.remove();
}
}
}
private void pruneRedundantUnionCoveringSets(List<UnionCandidate> candidates) {
for (int i = 0; i < candidates.size(); i++) {
UnionCandidate current = candidates.get(i);
int j = i + 1;
for (ListIterator<UnionCandidate> itr = candidates.listIterator(j); itr.hasNext();) {
UnionCandidate next = itr.next();
if (next.getChildren().containsAll(current.getChildren())) {
itr.remove();
}
}
}
}
private List<UnionCandidate> getCombinations(final List<Candidate> candidates, CubeQueryContext cubeql) {
List<UnionCandidate> combinations = new LinkedList<>();
int size = candidates.size();
int threshold = Double.valueOf(Math.pow(2, size)).intValue() - 1;
for (int i = 1; i <= threshold; ++i) {
LinkedList<Candidate> individualCombinationList = new LinkedList<>();
int count = size - 1;
int clonedI = i;
while (count >= 0) {
if ((clonedI & 1) != 0) {
individualCombinationList.addFirst(candidates.get(count));
}
clonedI = clonedI >>> 1;
--count;
}
combinations.add(new UnionCandidate(individualCombinationList, cubeql));
}
return combinations;
}
private List<List<Candidate>> resolveJoinCandidates(List<Candidate> unionCandidates,
Set<QueriedPhraseContext> msrs, CubeQueryContext cubeql) throws LensException {
List<List<Candidate>> msrCoveringSets = new ArrayList<>();
List<Candidate> ucSet = new ArrayList<>(unionCandidates);
// Check if a single set can answer all the measures and exprsWithMeasures
for (Iterator<Candidate> i = ucSet.iterator(); i.hasNext();) {
boolean evaluable = false;
Candidate uc = i.next();
for (QueriedPhraseContext msr : msrs) {
evaluable = isMeasureAnswerablebyUnionCandidate(msr, uc, cubeql);
if (!evaluable) {
break;
}
}
if (evaluable) {
// single set can answer all the measures as an UnionCandidate
List<Candidate> one = new ArrayList<>();
one.add(uc);
msrCoveringSets.add(one);
i.remove();
}
}
// Sets that contain all measures or no measures are removed from iteration.
// find other facts
for (Iterator<Candidate> i = ucSet.iterator(); i.hasNext();) {
Candidate candidate = i.next();
i.remove();
// find the remaining measures in other facts
if (i.hasNext()) {
Set<QueriedPhraseContext> remainingMsrs = new HashSet<>(msrs);
Set<QueriedPhraseContext> coveredMsrs = CandidateUtil.coveredMeasures(candidate, msrs, cubeql);
remainingMsrs.removeAll(coveredMsrs);
List<List<Candidate>> coveringSets = resolveJoinCandidates(ucSet, remainingMsrs, cubeql);
if (!coveringSets.isEmpty()) {
for (List<Candidate> candSet : coveringSets) {
candSet.add(candidate);
msrCoveringSets.add(candSet);
}
} else {
log.info("Couldnt find any set containing remaining measures:{} {} in {}", remainingMsrs,
ucSet);
}
}
}
log.info("Covering set {} for measures {} with factsPassed {}", msrCoveringSets, msrs, ucSet);
return msrCoveringSets;
}
private void updateQueriableMeasures(List<Candidate> cands,
List<QueriedPhraseContext> qpcList, CubeQueryContext cubeql) throws LensException {
for (Candidate cand : cands) {
updateStorageCandidateQueriableMeasures(cand, qpcList, cubeql);
}
}
private void updateStorageCandidateQueriableMeasures(Candidate unionCandidate,
List<QueriedPhraseContext> qpcList, CubeQueryContext cubeql) throws LensException {
QueriedPhraseContext msrPhrase;
boolean isEvaluable;
for (int index = 0; index < qpcList.size(); index++) {
if (!qpcList.get(index).hasMeasures(cubeql)) {
//Not a measure phrase. Skip it
continue;
}
msrPhrase = qpcList.get(index);
if (unionCandidate instanceof StorageCandidate && msrPhrase.isEvaluable(cubeql,
(StorageCandidate) unionCandidate)) {
((StorageCandidate) unionCandidate).setAnswerableMeasurePhraseIndices(index);
} else if (unionCandidate instanceof UnionCandidate) {
isEvaluable = true;
for (Candidate childCandidate : unionCandidate.getChildren()) {
if (!msrPhrase.isEvaluable(cubeql, (StorageCandidate) childCandidate)) {
isEvaluable = false;
break;
}
}
if (isEvaluable) {
//Set the index for all the children in this case
for (Candidate childCandidate : unionCandidate.getChildren()) {
((StorageCandidate) childCandidate).setAnswerableMeasurePhraseIndices(index);
}
}
}
}
}
}