Skip to content

Commit

Permalink
Merge pull request #1638 from pengpeng-lu/hint
Browse files Browse the repository at this point in the history
resolves #1671: match index hints in a query
  • Loading branch information
normen662 committed May 14, 2022
2 parents f9b3328 + 893a858 commit d80d1db
Show file tree
Hide file tree
Showing 10 changed files with 444 additions and 55 deletions.
1 change: 1 addition & 0 deletions docs/ReleaseNotes.md
Expand Up @@ -31,6 +31,7 @@ This release also updates downstream dependency versions. Most notably, the prot
* **Performance** Improvement 4 [(Issue #NNN)](https://github.com/FoundationDB/fdb-record-layer/issues/NNN)
* **Performance** Improvement 5 [(Issue #NNN)](https://github.com/FoundationDB/fdb-record-layer/issues/NNN)
* **Feature** The FDB API version can now be configured through the `FDBDatabaseFactory` [(Issue #1639)](https://github.com/FoundationDB/fdb-record-layer/issues/1639)
* **Feature** Match index hints in a query [(Issue #1671)](https://github.com/FoundationDB/fdb-record-layer/issues/1671)
* **Feature** Feature 2 [(Issue #NNN)](https://github.com/FoundationDB/fdb-record-layer/issues/NNN)
* **Feature** Feature 3 [(Issue #NNN)](https://github.com/FoundationDB/fdb-record-layer/issues/NNN)
* **Feature** Feature 4 [(Issue #NNN)](https://github.com/FoundationDB/fdb-record-layer/issues/NNN)
Expand Down
@@ -0,0 +1,37 @@
/*
* AccessHint.java
*
* This source file is part of the FoundationDB open source project
*
* Copyright 2015-2022 Apple Inc. and the FoundationDB project authors
*
* 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.apple.foundationdb.record.query.plan.cascades;

import javax.annotation.Nonnull;

/**
* Interface to represent an access hint. An access hint can be of type INDEX and PRIMARY.
*/
public interface AccessHint {

/**
* Gets the type of the access hint.
* @return the type of the access hint
*/
@Nonnull
String getAccessHintType();
}

@@ -0,0 +1,59 @@
/*
* AccessHints.java
*
* This source file is part of the FoundationDB open source project
*
* Copyright 2015-2022 Apple Inc. and the FoundationDB project authors
*
* 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.apple.foundationdb.record.query.plan.cascades;

import javax.annotation.Nonnull;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

/**
* Represents a set of AccessHint a query or a match candidate has.
*/
public class AccessHints {
@Nonnull
private final Set<AccessHint> accessHintSet = new HashSet<>();

public AccessHints(AccessHint... accessHints) {
this.accessHintSet.addAll(Arrays.asList(accessHints));
}

@Nonnull
public Set<AccessHint> getAccessHintSet() {
return accessHintSet;
}

public int size() {
return accessHintSet.size();
}

public boolean satisfies(@Nonnull AccessHints other) {
// if no hint is set, it's considered to include all possible hints
if (size() == 0) {
return true;
}
// if other is empty, this does not include other
if (other.size() == 0) {
return false;
}
return accessHintSet.containsAll(other.getAccessHintSet());
}
}
@@ -0,0 +1,64 @@
/*
* IndexAccessHint.java
*
* This source file is part of the FoundationDB open source project
*
* Copyright 2015-2022 Apple Inc. and the FoundationDB project authors
*
* 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.apple.foundationdb.record.query.plan.cascades;

import javax.annotation.Nonnull;
import java.util.Objects;

/**
* Represents an index hint.
*/
public class IndexAccessHint implements AccessHint {
@Nonnull
private final String indexName;

public IndexAccessHint(@Nonnull final String indexName) {
this.indexName = indexName;
}

@Override
@Nonnull
public String getAccessHintType() {
return "INDEX";
}

@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (other == null || getClass() != other.getClass()) {
return false;
}
return indexName.equals(((IndexAccessHint)other).getIndexName());
}

@Override
public int hashCode() {
return Objects.hash(indexName);
}

@Nonnull
public String getIndexName() {
return indexName;
}

}
Expand Up @@ -29,8 +29,8 @@
import com.apple.foundationdb.record.query.plan.ScanComparisons;
import com.apple.foundationdb.record.query.plan.cascades.expressions.FullUnorderedScanExpression;
import com.apple.foundationdb.record.query.plan.cascades.expressions.LogicalTypeFilterExpression;
import com.apple.foundationdb.record.query.plan.cascades.typing.Type;
import com.apple.foundationdb.record.query.plan.cascades.predicates.QueryPredicate;
import com.apple.foundationdb.record.query.plan.cascades.typing.Type;
import com.google.common.base.Verify;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
Expand Down Expand Up @@ -64,7 +64,8 @@ public interface MatchCandidate {
Logger LOGGER = LoggerFactory.getLogger(MatchCandidate.class);

/**
* Returns the name of the match candidate. If this candidate represents and index, it will be the name of the index.
* Returns the name of the match candidate. If this candidate represents an index, it will be the name of the index.
*
* @return the name of this match candidate
*/
@Nonnull
Expand Down Expand Up @@ -291,7 +292,7 @@ private static Optional<MatchCandidate> expandIndexMatchCandidate(@Nonnull Recor
final boolean isReverse,
@Nullable final KeyExpression commonPrimaryKeyForIndex,
@Nonnull final ExpansionVisitor<?> expansionVisitor) {
final var baseRef = createBaseRef(recordMetaData, availableRecordTypes, recordTypeNamesForIndex);
final var baseRef = createBaseRef(recordMetaData, availableRecordTypes, recordTypeNamesForIndex, new IndexAccessHint(index.getName()));
try {
return Optional.of(expansionVisitor.expand(() -> Quantifier.forEach(baseRef), commonPrimaryKeyForIndex, isReverse));
} catch (final UnsupportedOperationException uOE) {
Expand All @@ -314,7 +315,7 @@ static Optional<MatchCandidate> fromPrimaryDefinition(@Nonnull final RecordMetaD
final boolean isReverse) {
if (commonPrimaryKey != null) {
final var availableRecordTypes = metaData.getRecordTypes().keySet();
final var baseRef = createBaseRef(metaData, availableRecordTypes, recordTypes);
final var baseRef = createBaseRef(metaData, availableRecordTypes, recordTypes, new PrimaryAccessHint());
final var expansionVisitor = new PrimaryAccessExpansionVisitor(availableRecordTypes, recordTypes);
return Optional.of(expansionVisitor.expand(() -> Quantifier.forEach(baseRef), commonPrimaryKey, isReverse));
}
Expand All @@ -323,9 +324,9 @@ static Optional<MatchCandidate> fromPrimaryDefinition(@Nonnull final RecordMetaD
}

@Nonnull
static GroupExpressionRef<RelationalExpression> createBaseRef(@Nonnull RecordMetaData metaData, @Nonnull final Set<String> allAvailableRecordTypes, @Nonnull final Set<String> recordTypesForIndex) {
static GroupExpressionRef<RelationalExpression> createBaseRef(@Nonnull RecordMetaData metaData, @Nonnull final Set<String> allAvailableRecordTypes, @Nonnull final Set<String> recordTypesForIndex, @Nonnull AccessHint accessHint) {
final var quantifier =
Quantifier.forEach(GroupExpressionRef.of(new FullUnorderedScanExpression(allAvailableRecordTypes)));
Quantifier.forEach(GroupExpressionRef.of(new FullUnorderedScanExpression(allAvailableRecordTypes, new AccessHints(accessHint))));
return GroupExpressionRef.of(
new LogicalTypeFilterExpression(recordTypesForIndex,
quantifier,
Expand Down
@@ -0,0 +1,34 @@
/*
* PrimaryAccessHint.java
*
* This source file is part of the FoundationDB open source project
*
* Copyright 2015-2022 Apple Inc. and the FoundationDB project authors
*
* 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.apple.foundationdb.record.query.plan.cascades;

import javax.annotation.Nonnull;

/**
* Represents reading a table directly without using an index plus fetch.
*/
public class PrimaryAccessHint implements AccessHint {
@Override
@Nonnull
public String getAccessHintType() {
return "PRIMARY";
}
}
Expand Up @@ -107,10 +107,10 @@ static RelationalExpression fromRecordQuery(@Nonnull PlanContext context,
final GroupExpressionRef<? extends RelationalExpression> baseRef;
Quantifier.ForEach quantifier;
if (recordTypes.isEmpty()) {
baseRef = GroupExpressionRef.of(new FullUnorderedScanExpression(context.getMetaData().getRecordTypes().keySet()));
baseRef = GroupExpressionRef.of(new FullUnorderedScanExpression(context.getMetaData().getRecordTypes().keySet(), new AccessHints()));
quantifier = Quantifier.forEach(baseRef);
} else {
final var fuseRef = GroupExpressionRef.of(new FullUnorderedScanExpression(context.getMetaData().getRecordTypes().keySet()));
final var fuseRef = GroupExpressionRef.of(new FullUnorderedScanExpression(context.getMetaData().getRecordTypes().keySet(), new AccessHints()));
baseRef = GroupExpressionRef.of(
new LogicalTypeFilterExpression(
new HashSet<>(recordTypes),
Expand Down Expand Up @@ -330,7 +330,6 @@ default Iterable<AliasMap> findMatches(@Nonnull final RelationalExpression other
/**
* A functional interface to combine the matches computed over pairs of quantifiers during matching into a
* boolean result (for the bound correlatedTo set handed into {@link #combine}).
*
*/
@FunctionalInterface
interface CombinePredicate {
Expand Down
Expand Up @@ -21,6 +21,7 @@
package com.apple.foundationdb.record.query.plan.cascades.expressions;

import com.apple.foundationdb.annotation.API;
import com.apple.foundationdb.record.query.plan.cascades.AccessHints;
import com.apple.foundationdb.record.query.plan.cascades.AliasMap;
import com.apple.foundationdb.record.query.plan.cascades.ComparisonRange;
import com.apple.foundationdb.record.query.plan.cascades.Compensation;
Expand All @@ -30,11 +31,11 @@
import com.apple.foundationdb.record.query.plan.cascades.PartialMatch;
import com.apple.foundationdb.record.query.plan.cascades.Quantifier;
import com.apple.foundationdb.record.query.plan.cascades.RelationalExpression;
import com.apple.foundationdb.record.query.plan.cascades.typing.Type;
import com.apple.foundationdb.record.query.plan.cascades.explain.Attribute;
import com.apple.foundationdb.record.query.plan.cascades.explain.NodeInfo;
import com.apple.foundationdb.record.query.plan.cascades.explain.PlannerGraph;
import com.apple.foundationdb.record.query.plan.cascades.explain.PlannerGraphRewritable;
import com.apple.foundationdb.record.query.plan.cascades.typing.Type;
import com.apple.foundationdb.record.query.plan.cascades.values.QueriedValue;
import com.apple.foundationdb.record.query.plan.cascades.values.Value;
import com.google.common.base.Verify;
Expand All @@ -55,25 +56,34 @@
* {@link com.apple.foundationdb.record.query.plan.plans.RecordQueryScanPlan}, a {@code FullUnorderedScanExpression}
* is not implicitly ordered by the primary key.
*
* <p>
*
* This expression is useful as the source of records for the initial planner expression produced from a
* {@link com.apple.foundationdb.record.query.RecordQuery}.
* </p>
*
*/
@API(API.Status.EXPERIMENTAL)
public class FullUnorderedScanExpression implements RelationalExpression, PlannerGraphRewritable {
@Nonnull
private final Set<String> recordTypes;

public FullUnorderedScanExpression(final Set<String> recordTypes) {
@Nonnull
final AccessHints accessHints;

public FullUnorderedScanExpression(final Set<String> recordTypes, @Nonnull final AccessHints accessHints) {
this.recordTypes = ImmutableSet.copyOf(recordTypes);
this.accessHints = accessHints;
}

@Nonnull
public Set<String> getRecordTypes() {
return recordTypes;
}

@Nonnull
public AccessHints getAccessHints() {
return accessHints;
}

@Nonnull
@Override
public Value getResultValue() {
Expand Down Expand Up @@ -134,7 +144,15 @@ public String toString() {
@Nonnull
@Override
public Iterable<MatchInfo> subsumedBy(@Nonnull final RelationalExpression candidateExpression, @Nonnull final AliasMap aliasMap, @Nonnull final IdentityBiMap<Quantifier, PartialMatch> partialMatchMap) {
return exactlySubsumedBy(candidateExpression, aliasMap, partialMatchMap);
if (getClass() != candidateExpression.getClass()) {
return ImmutableList.of();
}
// if query doesnot contain candidate's indexes, the query cannot be subsumed by the candidate
if (getAccessHints().satisfies(((FullUnorderedScanExpression)candidateExpression).getAccessHints())) {
return exactlySubsumedBy(candidateExpression, aliasMap, partialMatchMap);
} else {
return ImmutableList.of();
}
}

@Override
Expand Down

0 comments on commit d80d1db

Please sign in to comment.