-
Notifications
You must be signed in to change notification settings - Fork 3.2k
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
Use null propagation to optimize away IS NOT NULL
checks
#34127
base: main
Are you sure you want to change the base?
Conversation
IS NOT NULL
checks
3769918
to
afcfba2
Compare
afcfba2
to
d9c0736
Compare
089d329
to
fb551f8
Compare
fb551f8
to
bbe125a
Compare
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.
Great work as always @ranma42, it's really nice to see our SQL baselines getting progressively tighter and better.
See mainly nits below.
} | ||
|
||
// optimize expressions such as expr != null ? expr : null and expr == null ? null : expr | ||
if (testIsCondition && whenClauses is [var clause] && (elseResult == null || IsNull(clause.Result))) |
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.
I'm personally moving to is null
pretty systematically (here and below):
if (testIsCondition && whenClauses is [var clause] && (elseResult == null || IsNull(clause.Result))) | |
if (testIsCondition && whenClauses is [var clause] && (elseResult is null || IsNull(clause.Result))) |
(I actually use is
for any constant/literal check at this point).
SqlExpression test, expr; | ||
|
||
if (elseResult == null) | ||
{ | ||
expr = clause.Result; | ||
test = clause.Test; | ||
} | ||
else | ||
{ | ||
expr = elseResult; | ||
test = _sqlExpressionFactory.Not(clause.Test); | ||
} |
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.
nit:
SqlExpression test, expr; | |
if (elseResult == null) | |
{ | |
expr = clause.Result; | |
test = clause.Test; | |
} | |
else | |
{ | |
expr = elseResult; | |
test = _sqlExpressionFactory.Not(clause.Test); | |
} | |
var (test, expr) = elseResult is null | |
? (clause.Result, clause.Test) | |
: (_sqlExpressionFactory.Not(clause.Test), elseResult); |
... and move nullPropagatedOperands below.
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.
oops, this was swapped (clause.Result, clause.Test)
vs (clause.Test, clause.Result)
{ | ||
operands.Add(expression); | ||
|
||
if (expression is SqlUnaryExpression unary |
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.
nit: this can all be one nice switch statement with pattern matching ;)
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.
will it work even if the result is void
? I am unable to get its syntax right :\
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.
uh, statement, not expression... right!
}; | ||
|
||
// FIXME: unify nullability computations | ||
static void NullPropagatedOperands(SqlExpression expression, HashSet<SqlExpression> operands) |
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.
Naming-wise, maybe DetectNullPropagatingNodes?
_ => expression, | ||
}; | ||
|
||
// FIXME: unify nullability computations |
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.
Yeah, this definitely feels like something more general than as a very local utility to case, plus there should be extensibility for provider-specific expression types. We could even compute this information and bubble it up as part of the normal visitation of SqlNullabilityProcessor - assuming it's useful enough for other parts of the visitor.
But of course this can all be done later.
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.
This is what I was considering doing in #33889 (comment) (instead of simple boolean nullability, propagate the "nullability expression") 😉
I will most likely do that in a later experiment, at the very least to evaluate whether the nullability expressions are simple to create/consume.
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.
Yep, makes sense!
NullPropagatedOperands(binary.Left, operands); | ||
NullPropagatedOperands(binary.Right, operands); | ||
} | ||
else if (expression is SqlFunctionExpression { IsNullable: true } func) |
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.
AtTimeZone, Collate (which are types of binary expressions)
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.
this function is mostly based on ProcessNullNotNull
(which, just like this function, ignores AtTimeZone
, Collate
, ...)
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.
I see... We should probably fix that in both places - if you prefer not to do this in this PR we can open a separate issue.
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.
I will add the part specific to this PR here; I added the other part in #34263
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.
added in 12a532b
operands.Add(expression); | ||
|
||
if (expression is SqlUnaryExpression unary | ||
&& unary.OperatorType is ExpressionType.Not or ExpressionType.Negate or ExpressionType.Convert) |
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.
Are there other unary operators that propagate? Maybe better to exclude those that you don't want, rather than specifying those that you do?
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.
the advantage of specifying them in a positive way is that changing the supported OperatorType
s is safe
OTOH it is unlikely to happen and would likely require other changes across the repo, so I can replace this by excluding Equal
and NotEqual
if that is easier to read
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.
Just noting that binary just below is negative rather than positive. Up to you - the possible values here in ExpressionType is a closed set, so it should be safe either way.
c2b1eff
to
69ae99a
Compare
When a CASE expression simply replicates SQL null propagation, simplify it.
69ae99a
to
65d9a68
Compare
as per review.
When a CASE expression simply replicates SQL null propagation, simplify it.
Contributes to #34126.