Skip to content
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

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

ranma42
Copy link
Contributor

@ranma42 ranma42 commented Jun 30, 2024

When a CASE expression simply replicates SQL null propagation, simplify it.

Contributes to #34126.

@ranma42 ranma42 changed the title Optimize null propagation Use null propagation to optimize away IS NOT NULL checks Jun 30, 2024
@ranma42 ranma42 force-pushed the drop-not-null-checks branch 2 times, most recently from 3769918 to afcfba2 Compare June 30, 2024 19:46
@ranma42 ranma42 marked this pull request as draft July 2, 2024 23:39
@ranma42 ranma42 force-pushed the drop-not-null-checks branch from afcfba2 to d9c0736 Compare July 3, 2024 15:54
@ranma42 ranma42 marked this pull request as ready for review July 3, 2024 15:54
@ranma42 ranma42 marked this pull request as draft July 3, 2024 15:56
@ranma42 ranma42 force-pushed the drop-not-null-checks branch 2 times, most recently from 089d329 to fb551f8 Compare July 3, 2024 18:42
@ranma42 ranma42 marked this pull request as ready for review July 4, 2024 07:11
@ranma42 ranma42 force-pushed the drop-not-null-checks branch from fb551f8 to bbe125a Compare July 9, 2024 22:52
@roji roji self-assigned this Jul 12, 2024
Copy link
Member

@roji roji left a 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)))
Copy link
Member

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):

Suggested change
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).

Comment on lines 585 to 596
SqlExpression test, expr;

if (elseResult == null)
{
expr = clause.Result;
test = clause.Test;
}
else
{
expr = elseResult;
test = _sqlExpressionFactory.Not(clause.Test);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
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.

Copy link
Contributor Author

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
Copy link
Member

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 ;)

Copy link
Contributor Author

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 :\

Copy link
Contributor Author

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)
Copy link
Member

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
Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Member

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)
Copy link
Member

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)

Copy link
Contributor Author

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, ...)

Copy link
Member

@roji roji Jul 12, 2024

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.

Copy link
Contributor Author

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added in 12a532b

src/EFCore.Relational/Query/SqlNullabilityProcessor.cs Outdated Show resolved Hide resolved
operands.Add(expression);

if (expression is SqlUnaryExpression unary
&& unary.OperatorType is ExpressionType.Not or ExpressionType.Negate or ExpressionType.Convert)
Copy link
Member

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?

Copy link
Contributor Author

@ranma42 ranma42 Jul 12, 2024

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 OperatorTypes 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

Copy link
Member

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.

src/EFCore.Relational/Query/SqlNullabilityProcessor.cs Outdated Show resolved Hide resolved
@ranma42 ranma42 force-pushed the drop-not-null-checks branch from c2b1eff to 69ae99a Compare July 12, 2024 16:40
@ranma42 ranma42 requested a review from roji July 21, 2024 15:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants