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

Translate to NULLIF #35327

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open

Translate to NULLIF #35327

wants to merge 1 commit into from

Conversation

roji
Copy link
Member

@roji roji commented Dec 13, 2024

Originally done for PostgreSQL by @WhatzGames in npgsql/efcore.pg#3403. Since all relational databases support it, moved this to EFCore.Relational and implemented as a construction-time optimization (in SqlExpressionFactory), so that it potentially applies in scenarios other than just translation.

Closes #31682

@roji roji requested a review from a team December 13, 2024 11:31
@roji
Copy link
Member Author

roji commented Dec 13, 2024

/azp run

Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@roji roji enabled auto-merge (squash) December 13, 2024 16:55
@roji
Copy link
Member Author

roji commented Dec 15, 2024

@dotnet/efteam this is ready for review.

Comment on lines 829 to 830
// a == null ? null : a -> a
// a != null ? a : null -> a
Copy link
Contributor

Choose a reason for hiding this comment

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

we might generalize this to

        // a == b ? b : a -> a
        // a != b ? a : b -> a

but we might want to wait for fast comparison before (yes, I should really get that done 😅 )

Copy link
Contributor

Choose a reason for hiding this comment

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

let's reference #34149 here so it's easier to keep track of this

Copy link
Member Author

@roji roji Dec 15, 2024

Choose a reason for hiding this comment

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

So actually, this PR already introduces deep equality (to compare the a's in a == b ? null : a), doing what you suggest would only introduce another one.

In these kinds of very constrained cases, where the deep equality only happens after a very specific pattern match, I think it's OK to do this even without the fast comparison (in other contexts probably not). So I've just gone ahead and done this (pushed a commit).

Let me know how it looks!

Copy link
Contributor

Choose a reason for hiding this comment

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

I believe it looks very good 🚀

Specifically, this simplifies away a bunch of

WHERE CASE
    WHEN [g].[HasSoulPatch] = CAST(0 AS bit) THEN CAST(0 AS bit)
    ELSE [g].[HasSoulPatch]
END = CAST(0 AS bit)

to

WHERE [g].[HasSoulPatch] = CAST(0 AS bit)

which can likely lead to much more efficient queries (the CASE query is unlikely to use an index).

Sorry for causing so much churn that is not really related to NULLIF, but now the PR takes care of simplifying many more (in)equality + ternary patterns :)

Copy link
Member Author

Choose a reason for hiding this comment

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

Not at all, this is all great and your comments have been very helpful.

SELECT CASE
WHEN "c"."Name" IS NOT NULL THEN "c"."Name"
END
SELECT "c"."Name"
Copy link
Contributor

Choose a reason for hiding this comment

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

🤩

WHEN [e].[NullableStringA] = [e].[NullableStringB] OR ([e].[NullableStringA] IS NULL AND [e].[NullableStringB] IS NULL) THEN [e].[NullableStringA]
ELSE [e].[NullableStringB]
END IS NOT NULL)
WHERE ([e].[NullableStringC] <> [e].[NullableStringB] OR [e].[NullableStringC] IS NULL OR [e].[NullableStringB] IS NULL) AND ([e].[NullableStringC] IS NOT NULL OR [e].[NullableStringB] IS NOT NULL)
Copy link
Member Author

Choose a reason for hiding this comment

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

@ranma42 and @dotnet/efteam check this change out :)

Copy link
Contributor

Choose a reason for hiding this comment

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

the original condition is

e => e.NullableStringC
                    != (e.NullableStringA == e.NullableStringB
                        ? e.NullableStringA
                        : e.NullableStringB

which simplifies to

e => e.NullableStringC != e.NullableStringB

so the SQL translation looks correct to me

a different question is: what is the test checking for?
maybe it relied on the assumption that some of the (sub)expressions were not transformed 🤔

Copy link
Member Author

Choose a reason for hiding this comment

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

Yep, the test needs to be modified here - it's exercising general null semantics for conditional expressions, but happens to be written in a way which can be simplified away.

Copy link
Member Author

Choose a reason for hiding this comment

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

FYI I've modified the test to avoid the simplification taking place.

@roji roji force-pushed the NullIf branch 2 times, most recently from fa790d7 to b7c0e4a Compare December 18, 2024 12:04
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.

Translate to NULLIF
2 participants