-
Notifications
You must be signed in to change notification settings - Fork 62
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
Apply SQL 9.3 typing rules for CASE-WHEN #1391
Conversation
Conformance comparison report
Number passing in both: 5384 Number failing in both: 434 Number passing in Base (6634bc0) but now fail: 0 Number failing in Base (6634bc0) but now pass: 0 |
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #1391 +/- ##
============================================
- Coverage 73.17% 73.14% -0.03%
Complexity 2393 2393
============================================
Files 247 247
Lines 17616 17623 +7
Branches 3175 3176 +1
============================================
Hits 12890 12890
- Misses 3849 3856 +7
Partials 877 877
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
when (boolOrNull(condition.op)) { | ||
true -> return branch.rex // fold |
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 think there's a bug here. Let's say a CASE has three branches (the first two being booleans (but not constants) and the third being a boolean of literal true). With this logic, it seems like we'd accumulate the first two visited branches, but for the third, we'd immediately return it's 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.
Seems like we can only constant fold and return early if it's the first boolean and it's the boolean literal true
.
Also wondering if we should still check the other branches to report any incompatible types (i.e. non-boolean WHEN
branches).
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 going to leave constant folding true
out of this considering the urgency. The task of constant folding (which would include branches) is much larger than this PR.
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.
Had a high level question:
Consider: a: unionOf(int2, int4, null)
.
Given a query:
Case when a IS NULL then a
ELSE a END;
Should the common super type be
unionOf(int2, int4, null)
// a dynamic with constraint being int2, int4 and null
or
unionOf(int4, null)
. // a dynamic with constraint being int4, and null
If the later, should the query a
also has the type unionOf(int4, null)
?
I understand that the common super type rules applied for an aggregation over values of compatible data types.
We might need to clarify what is an aggregation in PartiQL. That is: one can argue that a simple project is an aggregation. In SQL that is an aggregation over values of same data type, in PartiQL it might be an aggregation over any possible data type.
val type = supertype.toNonNullStaticType() | ||
val mapping = args.map { it to supertype } | ||
return if (modifiers.isEmpty()) { | ||
type to mapping | ||
} else { | ||
StaticType.unionOf(setOf(type) + modifiers).flatten() to mapping | ||
} |
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.
If one would keep accumulating MISSING
, does not seem like the NULL type will be dropped.
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.
Could you please elaborate more?
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.
val typer = DynamicTyper() // at this stage, super Type is NULL
typer.accumulate(MISSING) // no touch on super type, missable is true
// modifier contains missing,
// type is supertype.toNonNullStaticType() -> NULL
// return is StaticType.unionOf(setOf(type) + modifiers).flatten() to mapping
typer.mapping() // UnionOf( NULL, MISSING)
partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/DynamicTyper.kt
Outdated
Show resolved
Hide resolved
partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/PlanTyper.kt
Outdated
Show resolved
Hide resolved
when (boolOrNull(condition.op)) { | ||
true -> return branch.rex // fold |
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.
Seems like we can only constant fold and return early if it's the first boolean and it's the boolean literal true
.
Also wondering if we should still check the other branches to report any incompatible types (i.e. non-boolean WHEN
branches).
partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/DynamicTyper.kt
Show resolved
Hide resolved
partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/DynamicTyper.kt
Show resolved
Hide resolved
partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/functions/NullIfTest.kt
Show resolved
Hide resolved
partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/DynamicTyper.kt
Show resolved
Hide resolved
partiql-planner/src/testFixtures/resources/catalogs/default/pql/t_item.ion
Show resolved
Hide resolved
partiql-planner/src/testFixtures/resources/inputs/basics/case.sql
Outdated
Show resolved
Hide resolved
-- type: (int32|int64|string) | ||
CASE t_item.t_string | ||
WHEN 'a' THEN t_item.t_int32 | ||
WHEN 'b' THEN t_item.t_int64 | ||
ELSE 'default' | ||
END; |
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.
So for the heterogeneous cases, we're opting to keep all of the return types in the minimal common subtype and not doing any sort of "flattening" (for this case, returning (int64, string)
)?
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.
Exactly. Once the minimal common supertype hits the top ie dynamic
, we don't coerce/cast and branches. We carry all types to the result.
Co-authored-by: Alan Cai <[email protected]>
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.
LGTM. Thanks.
Relevant Issues
#1390
Description
This PR introduces the typing rules of a CASE-WHEN which types branches to the minimum common supertype. In the case of types from different sub-type families (STF) then we go up to the ANY type (known now as
dynamic
).Other Information
Updated Unreleased Section in CHANGELOG: [YES/NO]
No
Any backward-incompatible changes? [YES/NO]
This is a behavioral change due to a bug.
Any new external dependencies? [YES/NO]
No
Do your changes comply with the Contributing Guidelines
and Code Style Guidelines? [YES/NO]
License Information
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.