use of com.apple.foundationdb.record.query.expressions.QueryComponent in project fdb-record-layer by FoundationDB.
the class RecordQueryPlanner method planIntersection.
@Nullable
private ScoredPlan planIntersection(@Nonnull List<ScoredPlan> intersectionCandidates, @Nonnull KeyExpression comparisonKey) {
// Prefer plans that handle more filters (leave fewer unhandled), more index filters
intersectionCandidates.sort(Comparator.comparingInt(ScoredPlan::getNumNonSargables).thenComparing(Comparator.comparingInt(ScoredPlan::getNumIndexFilters).reversed()));
// Since we limited to isPrimaryKeyOrdered(), comparisonKey will always work.
ScoredPlan plan1 = intersectionCandidates.get(0);
List<QueryComponent> nonSargables = new ArrayList<>(plan1.combineNonSargables());
Set<RankComparisons.RankComparison> includedRankComparisons = mergeRankComparisons(null, plan1.includedRankComparisons);
RecordQueryPlan plan = plan1.plan;
List<RecordQueryPlan> includedPlans = new ArrayList<>(intersectionCandidates.size());
includedPlans.add(plan);
// TODO optimize so that we don't do excessive intersections
for (int i = 1; i < intersectionCandidates.size(); i++) {
ScoredPlan nextPlan = intersectionCandidates.get(i);
List<QueryComponent> nextNonSargables = new ArrayList<>(nextPlan.combineNonSargables());
int oldCount = nonSargables.size();
nonSargables.retainAll(nextNonSargables);
if (nonSargables.size() < oldCount) {
if (plan.isReverse() != nextPlan.plan.isReverse()) {
// Cannot intersect plans with incompatible reverse settings.
return null;
}
includedPlans.add(nextPlan.plan);
}
includedRankComparisons = mergeRankComparisons(includedRankComparisons, nextPlan.includedRankComparisons);
}
if (includedPlans.size() > 1) {
// Calculating the new score would require more state, not doing, because we currently ignore the score
// after this call.
final RecordQueryPlan intersectionPlan = RecordQueryIntersectionPlan.from(includedPlans, comparisonKey);
if (intersectionPlan.getComplexity() > configuration.getComplexityThreshold()) {
throw new RecordQueryPlanComplexityException(intersectionPlan);
}
return new ScoredPlan(intersectionPlan, nonSargables, Collections.emptyList(), plan1.score, plan1.createsDuplicates, includedRankComparisons);
} else {
return null;
}
}
use of com.apple.foundationdb.record.query.expressions.QueryComponent in project fdb-record-layer by FoundationDB.
the class RecordQueryPlanner method planRankWithAnd.
@Nullable
private ScoredPlan planRankWithAnd(@Nonnull CandidateScan candidateScan, @Nonnull Index index, @Nonnull GroupingKeyExpression indexExpr, @Nonnull AndComponent and) {
final List<QueryComponent> filters = and.getChildren();
for (QueryComponent filter : filters) {
if (filter instanceof QueryRecordFunctionWithComparison) {
final QueryRecordFunctionWithComparison filterComparison = (QueryRecordFunctionWithComparison) filter;
final RankComparisons.RankComparison rankComparison = candidateScan.planContext.rankComparisons.getPlanComparison(filterComparison);
if (rankComparison != null && rankComparison.getIndex() == index && RankComparisons.matchesSort(indexExpr, candidateScan.planContext.query.getSort())) {
ScanComparisons scanComparisons = rankComparison.getScanComparisons();
final Set<RankComparisons.RankComparison> includedRankComparisons = new HashSet<>();
includedRankComparisons.add(rankComparison);
final List<QueryComponent> unsatisfiedFilters = new ArrayList<>(filters);
unsatisfiedFilters.remove(filter);
unsatisfiedFilters.removeAll(rankComparison.getGroupFilters());
for (int i = 0; i < unsatisfiedFilters.size(); i++) {
final QueryComponent otherFilter = unsatisfiedFilters.get(i);
if (otherFilter instanceof QueryRecordFunctionWithComparison) {
final QueryRecordFunctionWithComparison otherComparison = (QueryRecordFunctionWithComparison) otherFilter;
final RankComparisons.RankComparison otherRank = candidateScan.planContext.rankComparisons.getPlanComparison(otherComparison);
if (otherRank != null) {
ScanComparisons mergedScanComparisons = scanComparisons.merge(otherRank.getScanComparisons());
if (mergedScanComparisons != null) {
scanComparisons = mergedScanComparisons;
includedRankComparisons.add(otherRank);
unsatisfiedFilters.remove(i--);
}
}
}
}
final RecordQueryPlan scan = rankScan(candidateScan, filterComparison, scanComparisons);
final boolean createsDuplicates = RankComparisons.createsDuplicates(index, indexExpr);
return new ScoredPlan(scan, unsatisfiedFilters, Collections.emptyList(), indexExpr.getColumnSize(), createsDuplicates, includedRankComparisons);
}
}
}
return null;
}
use of com.apple.foundationdb.record.query.expressions.QueryComponent in project fdb-record-layer by FoundationDB.
the class RecordQueryPlanner method normalizeAndOrForInAsOr.
private QueryComponent normalizeAndOrForInAsOr(@Nonnull QueryComponent component) {
if (!(component instanceof AndComponent)) {
return component;
}
final AndComponent and = (AndComponent) component;
OrComponent singleOrChild = null;
final List<QueryComponent> otherChildren = new ArrayList<>();
for (QueryComponent child : and.getChildren()) {
if (child instanceof OrComponent) {
if (singleOrChild == null) {
singleOrChild = (OrComponent) child;
} else {
return and;
}
} else if (Query.isSingleFieldComparison(child)) {
otherChildren.add(child);
} else {
return and;
}
}
if (singleOrChild == null) {
return and;
}
// We have exactly one OR child and the others are single field comparisons
return OrComponent.from(distributeAnd(otherChildren, singleOrChild.getChildren()));
}
use of com.apple.foundationdb.record.query.expressions.QueryComponent in project fdb-record-layer by FoundationDB.
the class RecordQueryPlanner method planAndWithNesting.
@Nullable
private ScoredPlan planAndWithNesting(@Nonnull CandidateScan candidateScan, @Nonnull NestingKeyExpression indexExpr, @Nonnull AndComponent filter, @Nullable KeyExpression sort) {
final FieldKeyExpression parent = indexExpr.getParent();
if (parent.getFanType() == FanType.None) {
// For non-spread case, we can do a better job trying to match more than one of the filter children if
// they have the same nesting.
final List<QueryComponent> nestedFilters = new ArrayList<>();
final List<QueryComponent> remainingFilters = new ArrayList<>();
for (QueryComponent filterChild : filter.getChildren()) {
QueryComponent filterComponent = candidateScan.planContext.rankComparisons.planComparisonSubstitute(filterChild);
if (filterComponent instanceof NestedField) {
final NestedField nestedField = (NestedField) filterComponent;
if (parent.getFieldName().equals(nestedField.getFieldName())) {
nestedFilters.add(nestedField.getChild());
continue;
}
}
remainingFilters.add(filterChild);
}
if (nestedFilters.size() > 1) {
final NestedField nestedAnd = new NestedField(parent.getFieldName(), Query.and(nestedFilters));
final ScoredPlan plan = planNestedField(candidateScan, indexExpr, nestedAnd, sort);
if (plan != null) {
if (remainingFilters.isEmpty()) {
return plan;
} else {
return plan.withUnsatisfiedFilters(remainingFilters);
}
} else {
return null;
}
}
}
List<QueryComponent> unsatisfiedFilters = new ArrayList<>(filter.getChildren());
for (QueryComponent filterChild : filter.getChildren()) {
QueryComponent filterComponent = candidateScan.planContext.rankComparisons.planComparisonSubstitute(filterChild);
if (filterComponent instanceof NestedField) {
NestedField nestedField = (NestedField) filterComponent;
final ScoredPlan plan = planNestedField(candidateScan, indexExpr, nestedField, sort);
if (plan != null) {
unsatisfiedFilters.remove(filterChild);
return plan.withUnsatisfiedFilters(unsatisfiedFilters);
}
}
}
return null;
}
use of com.apple.foundationdb.record.query.expressions.QueryComponent in project fdb-record-layer by FoundationDB.
the class RecordQueryPlanner method planIndex.
@Nullable
private ScoredPlan planIndex(@Nonnull PlanContext planContext, @Nonnull QueryComponent filter, @Nullable Index index, @Nonnull KeyExpression indexExpr, @Nonnull List<ScoredPlan> intersectionCandidates) {
final KeyExpression sort = planContext.query.getSort();
final boolean sortReverse = planContext.query.isSortReverse();
final CandidateScan candidateScan = new CandidateScan(planContext, index, sortReverse);
ScoredPlan p = null;
if (index != null) {
if (indexTypes.getRankTypes().contains(index.getType())) {
GroupingKeyExpression grouping = (GroupingKeyExpression) indexExpr;
p = planRank(candidateScan, index, grouping, filter);
// Plan as just value index.
indexExpr = grouping.getWholeKey();
} else if (indexTypes.getTextTypes().contains(index.getType())) {
p = planText(candidateScan, index, filter, sort);
if (p != null) {
p = planRemoveDuplicates(planContext, p);
}
if (p != null) {
p = computeIndexFilters(planContext, p);
}
return p;
} else if (indexTypes.getLuceneTypes().contains(index.getType())) {
p = planLucene(candidateScan, index, filter, sort);
if (p != null) {
p = planRemoveDuplicates(planContext, p);
}
if (p != null) {
p = computeIndexFilters(planContext, p);
}
return p;
} else if (!indexTypes.getValueTypes().contains(index.getType())) {
return null;
}
}
if (p == null) {
p = planCandidateScan(candidateScan, indexExpr, filter, sort);
}
if (p == null) {
// we can't match the filter, but maybe the sort
p = planSortOnly(candidateScan, indexExpr, sort);
if (p != null) {
final List<QueryComponent> unsatisfiedFilters = filter instanceof AndComponent ? ((AndComponent) filter).getChildren() : Collections.singletonList(filter);
p = new ScoredPlan(0, p.plan, unsatisfiedFilters, p.createsDuplicates);
}
}
if (p != null) {
if (getConfiguration().shouldOptimizeForIndexFilters()) {
// partition index filters
if (index == null) {
// if we scan without an index all filters become index filters as we don't need a fetch
// to evaluate these filters
p = p.withFilters(p.combineNonSargables(), Collections.emptyList());
} else {
p = computeIndexFilters(planContext, p);
}
}
}
if (p != null) {
p = planRemoveDuplicates(planContext, p);
if (p != null && p.getNumNonSargables() > 0) {
PlanOrderingKey planOrderingKey = PlanOrderingKey.forPlan(metaData, p.plan, planContext.commonPrimaryKey);
if (planOrderingKey != null && (sort != null || planOrderingKey.isPrimaryKeyOrdered())) {
// If there is a sort, all chosen plans should be ordered by it and so compatible.
// Otherwise, by requiring pkey order, we miss out on the possible intersection of
// X < 10 AND X < 5, which should have been handled already. We gain simplicity
// in not trying X < 10 AND Y = 5 AND Z = 'foo', where we would need to throw
// some out as we fail to align them all.
p.planOrderingKey = planOrderingKey;
intersectionCandidates.add(p);
}
}
}
return p;
}
Aggregations