-
Notifications
You must be signed in to change notification settings - Fork 108
Add validation/verification to the planner to avoid alias ambiguities and unresolved aliases #3405
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -215,7 +215,7 @@ public void yieldUnknownExpression(@Nonnull final RelationalExpression expressio | |
} | ||
|
||
private void yieldExpression(@Nonnull final RelationalExpression expression, final boolean isFinal) { | ||
verifyChildrenMemoized(expression); | ||
validateNewExpression(expression); | ||
if (isFinal) { | ||
if (root.insertFinalExpression(expression)) { | ||
newFinalExpressions.add(expression); | ||
|
@@ -229,6 +229,12 @@ private void yieldExpression(@Nonnull final RelationalExpression expression, fin | |
} | ||
} | ||
|
||
protected void validateNewExpression(@Nonnull final RelationalExpression expression) { | ||
Debugger.sanityCheck(() -> verifyChildrenMemoized(expression)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we want this to be part of a sanity check? The old code always validated that the children were memoized, so this is making things laxer, at least in the production configuration of the code There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That is correct. My thinking was that this has never triggered and we possibly can use the cycles elsewhere. |
||
Debugger.sanityCheck(() -> getRoot().verifyCorrelationsForNewExpression(traversal, expression, | ||
getEvaluationContext())); | ||
} | ||
|
||
private void verifyChildrenMemoized(@Nonnull RelationalExpression expression) { | ||
for (final var quantifier : expression.getQuantifiers()) { | ||
final var rangesOver = quantifier.getRangesOver(); | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -21,6 +21,7 @@ | |||||
package com.apple.foundationdb.record.query.plan.cascades; | ||||||
|
||||||
import com.apple.foundationdb.annotation.API; | ||||||
import com.apple.foundationdb.record.EvaluationContext; | ||||||
import com.apple.foundationdb.record.RecordCoreException; | ||||||
import com.apple.foundationdb.record.query.plan.HeuristicPlanner; | ||||||
import com.apple.foundationdb.record.query.plan.cascades.debug.Debugger; | ||||||
|
@@ -40,6 +41,7 @@ | |||||
import com.google.common.collect.Iterables; | ||||||
import com.google.common.collect.LinkedHashMultimap; | ||||||
import com.google.common.collect.SetMultimap; | ||||||
import com.google.common.collect.Sets; | ||||||
import com.google.errorprone.annotations.CanIgnoreReturnValue; | ||||||
|
||||||
import javax.annotation.Nonnull; | ||||||
|
@@ -708,6 +710,132 @@ public boolean addPartialMatchForCandidate(final MatchCandidate candidate, final | |||||
return partialMatchMap.put(candidate, partialMatch); | ||||||
} | ||||||
|
||||||
public void verifyCorrelationsForNewExpression(@Nonnull final Traversal traversal, | ||||||
@Nonnull final RelationalExpression expression, | ||||||
@Nonnull final EvaluationContext evaluationContext) { | ||||||
final Set<CorrelationIdentifier> correlatedToWithoutChildren; | ||||||
if (expression instanceof RelationalExpressionWithChildren) { | ||||||
correlatedToWithoutChildren = ((RelationalExpressionWithChildren)expression).getCorrelatedToWithoutChildren(); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a reason this doesn't go down the children of this expression and validate those correlations as well? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That gets a little more tricky. In a lot of cases the expression that is yielded is the only thing that has changed. In those cases, this is exhaustive. If there is a memoization call that returns a new reference, and an expression in that new reference has a problem, we wouldn't catch it. Maybe I can call it when the exploration of that new reference starts. That however, would make the graph validation at beginning of planning superfluous. |
||||||
} else { | ||||||
correlatedToWithoutChildren = expression.getCorrelatedTo(); | ||||||
} | ||||||
|
||||||
final var visibleThroughEvaluationContext = evaluationContext.getBindings().getBoundCorrelationAliases(); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this for things like constants? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, there are bunch of test cases written using incomplete graphs, and sometimes using temp tables. This seems to be the easier way out. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Constant themselves live in a different namespace among the bindings, so this is for actual |
||||||
final var locallyVisibleAliases = expression.getLocallyVisibleAliases(); | ||||||
|
||||||
final var currentResolvedCorrelatedToBuilder = | ||||||
ImmutableSet.<CorrelationIdentifier>builder(); | ||||||
final var currentUnresolvedCorrelatedToBuilder = | ||||||
ImmutableSet.<CorrelationIdentifier>builder(); | ||||||
for (final var unresolvedAlias : correlatedToWithoutChildren) { | ||||||
if (visibleThroughEvaluationContext.contains(unresolvedAlias) || | ||||||
locallyVisibleAliases.contains(unresolvedAlias)) { | ||||||
currentResolvedCorrelatedToBuilder.add(unresolvedAlias); | ||||||
} else { | ||||||
// still unresolved | ||||||
currentUnresolvedCorrelatedToBuilder.add(unresolvedAlias); | ||||||
} | ||||||
} | ||||||
|
||||||
final var currentUnresolvedCorrelatedTo = | ||||||
currentUnresolvedCorrelatedToBuilder.build(); | ||||||
final var currentResolvedCorrelatedTo = | ||||||
currentResolvedCorrelatedToBuilder.build(); | ||||||
|
||||||
final var parentRefPaths = traversal.getParentRefPaths(this); | ||||||
|
||||||
if (parentRefPaths.isEmpty()) { | ||||||
Verify.verify(currentUnresolvedCorrelatedTo.isEmpty(), "unresolved aliases: " + currentUnresolvedCorrelatedTo); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
To prevent us from calculating the error message even if the verify succeeds |
||||||
} else { | ||||||
for (final var parentRefPath : parentRefPaths) { | ||||||
final var parentReference = parentRefPath.getReference(); | ||||||
parentReference.verifyCorrelationsForNewExpressionRecursive(traversal, parentRefPath.getExpression(), | ||||||
parentRefPath.getQuantifier(), currentUnresolvedCorrelatedTo, currentResolvedCorrelatedTo); | ||||||
} | ||||||
} | ||||||
} | ||||||
|
||||||
private void verifyCorrelationsForNewExpressionRecursive(@Nonnull final Traversal traversal, | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It feels like there's a fair amount of duplicated code between this method and |
||||||
@Nonnull final RelationalExpression expression, | ||||||
@Nonnull final Quantifier quantifier, | ||||||
@Nonnull final Set<CorrelationIdentifier> unresolvedCorrelatedTo, | ||||||
@Nonnull final Set<CorrelationIdentifier> resolvedCorrelatedTo) { | ||||||
|
||||||
final Set<CorrelationIdentifier> localVisibleAliases; | ||||||
if (expression.canCorrelate()) { | ||||||
final var allLocallyVisibleAliases = expression.getLocallyVisibleAliases(); | ||||||
Verify.verify(allLocallyVisibleAliases.contains(quantifier.getAlias())); | ||||||
localVisibleAliases = | ||||||
Sets.difference(allLocallyVisibleAliases, ImmutableSet.of(quantifier.getAlias())); | ||||||
} else { | ||||||
localVisibleAliases = ImmutableSet.of(); | ||||||
} | ||||||
|
||||||
final var intersection = Sets.intersection(localVisibleAliases, resolvedCorrelatedTo); | ||||||
Verify.verify(intersection.isEmpty(), "ambiguous aliases: " + intersection); | ||||||
final var currentResolvedCorrelatedToBuilder = | ||||||
ImmutableSet.<CorrelationIdentifier>builder(); | ||||||
currentResolvedCorrelatedToBuilder.addAll(resolvedCorrelatedTo); | ||||||
|
||||||
final var currentUnresolvedCorrelatedToBuilder = | ||||||
ImmutableSet.<CorrelationIdentifier>builder(); | ||||||
for (final var unresolvedAlias : unresolvedCorrelatedTo) { | ||||||
if (localVisibleAliases.contains(unresolvedAlias)) { | ||||||
currentResolvedCorrelatedToBuilder.add(unresolvedAlias); | ||||||
} else { | ||||||
// still unresolved | ||||||
currentUnresolvedCorrelatedToBuilder.add(unresolvedAlias); | ||||||
} | ||||||
} | ||||||
|
||||||
final var currentUnresolvedCorrelatedTo = | ||||||
currentUnresolvedCorrelatedToBuilder.build(); | ||||||
final var currentResolvedCorrelatedTo = | ||||||
currentResolvedCorrelatedToBuilder.build(); | ||||||
|
||||||
final var parentRefPaths = traversal.getParentRefPaths(this); | ||||||
|
||||||
if (parentRefPaths.isEmpty()) { | ||||||
Verify.verify(currentUnresolvedCorrelatedTo.isEmpty(), "unresolved aliases: " + | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
currentUnresolvedCorrelatedTo); | ||||||
} else { | ||||||
for (final var parentRefPath : parentRefPaths) { | ||||||
final var parentReference = parentRefPath.getReference(); | ||||||
parentReference.verifyCorrelationsForNewExpressionRecursive(traversal, | ||||||
parentRefPath.getExpression(), parentRefPath.getQuantifier(), currentUnresolvedCorrelatedTo, | ||||||
currentResolvedCorrelatedTo); | ||||||
} | ||||||
} | ||||||
} | ||||||
|
||||||
public void verifyCorrelationsRecursive(@Nonnull final Set<CorrelationIdentifier> visibleAliases) { | ||||||
for (final var expression : getAllMemberExpressions()) { | ||||||
final var locallyVisibleAliases = expression.getLocallyVisibleAliases(); | ||||||
final var intersection = Sets.intersection(visibleAliases, locallyVisibleAliases); | ||||||
Verify.verify(intersection.isEmpty(), "ambiguous aliases: ", intersection); | ||||||
|
||||||
final var allVisibleAliases = Sets.union(visibleAliases, locallyVisibleAliases); | ||||||
final Set<CorrelationIdentifier> correlatedToWithoutChildren; | ||||||
if (expression instanceof RelationalExpressionWithChildren) { | ||||||
correlatedToWithoutChildren = ((RelationalExpressionWithChildren)expression).getCorrelatedToWithoutChildren(); | ||||||
} else { | ||||||
correlatedToWithoutChildren = expression.getCorrelatedTo(); | ||||||
} | ||||||
final var difference = Sets.difference(correlatedToWithoutChildren, allVisibleAliases); | ||||||
Verify.verify(difference.isEmpty(), "unresolved aliases: " + difference); | ||||||
|
||||||
for (final var quantifier : expression.getQuantifiers()) { | ||||||
final var lowerReference = quantifier.getRangesOver(); | ||||||
if (expression.canCorrelate()) { | ||||||
lowerReference.verifyCorrelationsRecursive(Sets.difference(allVisibleAliases, | ||||||
ImmutableSet.of(quantifier.getAlias()))); | ||||||
} else { | ||||||
lowerReference.verifyCorrelationsRecursive(visibleAliases); | ||||||
} | ||||||
} | ||||||
} | ||||||
} | ||||||
|
||||||
/** | ||||||
* Method to render the graph rooted at this reference. This is needed for graph integration into IntelliJ as | ||||||
* IntelliJ only ever evaluates selfish methods. Add this method as a custom renderer for the type | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How expensive do you think it would be to run this check here at the beginning of planning? Running it with every new expression (outside of a sanity check) is probably too much, but it would probably be a good idea to verify at the start that the initial input is okay. Maybe there's a concern that we'd start failing someone's query that is technically illegal (or that we identify as illegal as a bug) which results in failures in production code. We may also need to switch this away from the
Verify
framework so that we generate an appropriate error code if we're using this validate user inputThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could always run it, sure! If this triggers I would always view it as a bug answer the plan generator should have caught it, so I guess
verify
is fine.