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

Fix null literal handling for null intolerant functions in multi-stage query engine #13255

Merged
merged 2 commits into from
May 30, 2024

Conversation

yashmayya
Copy link
Collaborator

  • Currently, queries such as SELECT add(null, 1) FROM mytable fail with the multi-stage query engine returning the following exception:
Caught exception planning request 346348145000000011: SELECT add(null, 1) FROM mytable;, Error composing query plan for: SELECT add(null, 1) FROM mytable;
org.apache.pinot.query.QueryEnvironment.planQuery(QueryEnvironment.java:138)
org.apache.pinot.broker.requesthandler.MultiStageBrokerRequestHandler.handleRequest(MultiStageBrokerRequestHandler.java:145)
org.apache.pinot.broker.requesthandler.BaseBrokerRequestHandler.handleRequest(BaseBrokerRequestHandler.java:125)
org.apache.pinot.broker.requesthandler.BrokerRequestHandlerDelegate.handleRequest(BrokerRequestHandlerDelegate.java:86)
Failed to generate a valid execution plan for query:
LogicalProject(EXPR$0=[add(null:JavaType(class java.lang.Double), 1)])
  LogicalTableScan(table=[[default, mytable]])

org.apache.pinot.query.QueryEnvironment.optimize(QueryEnvironment.java:289)
org.apache.pinot.query.QueryEnvironment.compileQuery(QueryEnvironment.java:236)
org.apache.pinot.query.QueryEnvironment.planQuery(QueryEnvironment.java:129)
org.apache.pinot.broker.requesthandler.MultiStageBrokerRequestHandler.handleRequest(MultiStageBrokerRequestHandler.java:145)
type mismatch:
type1:
DOUBLE
type2:
DOUBLE NOT NULL
org.apache.calcite.util.Litmus.lambda$static$0(Litmus.java:31)
org.apache.calcite.plan.RelOptUtil.eq(RelOptUtil.java:2204)
org.apache.calcite.rex.RexUtil.compatibleTypes(RexUtil.java:1220)
org.apache.calcite.rel.core.Project.isValid(Project.java:255)
  • This issue arises from a type mismatch issue when Calcite's rule based HepPlanner fires the PinotEvaluateLiteralRule.
  • The scalar function double plus(double a, double b) is registered with Calcite here and the function's return type is determined as DOUBLE NOT NULL because of the primitive return type of the method (see here / here). This causes the type mismatch issue in the PinotEvaluateLiteralRule because the new project created here has a RexLiteral with type DOUBLE and value null, whereas the old project has a RexCall with type DOUBLE NOT NULL.
  • The core issue is that even though these arithmetic functions return primitive values, they can still return null because they are "null intolerant" and null will be returned without even invoking the method if any of the arguments is null (here).
  • Calcite's Strict (function returns null if and only if one of the arguments are null) and SemiStrict (function returns null if one of the arguments is null, and possibly other times) annotations can help us out here - https://github.com/apache/calcite/blob/694b556a2ece4953d8e9145352eb2340e1fac908/core/src/main/java/org/apache/calcite/schema/impl/ScalarFunctionImpl.java#L185-L205. With this, the function's return type in Calcite will be DOUBLE instead of DOUBLE NOT NULL and the PinotEvaluateLiteralRule works out fine.
  • This patch also switches from table based null handling to column based null handling in NullHandlingIntegrationTest (to support null handling in the multi-stage query engine) and enables two additional tests to run on both query engines.
  • Suggested label: bugfix

@codecov-commenter
Copy link

codecov-commenter commented May 29, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 35.27%. Comparing base (59551e4) to head (bfa5cf6).
Report is 511 commits behind head on master.

Additional details and impacted files
@@              Coverage Diff              @@
##             master   #13255       +/-   ##
=============================================
- Coverage     61.75%   35.27%   -26.48%     
+ Complexity      207        6      -201     
=============================================
  Files          2436     2459       +23     
  Lines        133233   135356     +2123     
  Branches      20636    20987      +351     
=============================================
- Hits          82274    47750    -34524     
- Misses        44911    84091    +39180     
+ Partials       6048     3515     -2533     
Flag Coverage Δ
custom-integration1 <0.01% <ø> (-0.01%) ⬇️
integration <0.01% <ø> (-0.01%) ⬇️
integration1 <0.01% <ø> (-0.01%) ⬇️
integration2 0.00% <ø> (ø)
java-11 35.22% <ø> (-26.49%) ⬇️
java-21 35.16% <ø> (-26.46%) ⬇️
skip-bytebuffers-false 35.24% <ø> (-26.51%) ⬇️
skip-bytebuffers-true 35.14% <ø> (+7.41%) ⬆️
temurin 35.27% <ø> (-26.48%) ⬇️
unittests 46.68% <ø> (-15.06%) ⬇️
unittests1 46.68% <ø> (-0.21%) ⬇️
unittests2 ?

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@yashmayya yashmayya marked this pull request as ready for review May 29, 2024 08:01
}

@Test
public void testNullLiteralSelectionInV2() throws Exception {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we rewrite these tests as one method per test or, maybe better, one provider with the query and the expected results?

We have tons of tests like this (or the one above) where the test has a lot of assertions. That is an anti-pattern we should try to get rid of. We never have time to modify the old code, but we should try to write new tests in a better way.

Just to be clear, there are two main advantages of the suggested approach:

  1. The tests are easier to read (each test is a query and an expected result)
  2. In case some test fail, the following tests are executed. In the current version if test 2 fails, 3, 4, etc are not executed, which means it can be more difficult to find the actual problem (or to know if there are more than one!)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Makes sense, I've added a data provider and updated both these tests. Let me know if this is along the lines of what you were thinking about. We could very easily add another parameter for v1 / v2 and use that to determine the query engine and the expected number of rows in order to combine both the test methods into a single one. However, given the notable difference in behavior between the two engines in this case (v1 simply handles it in the broker and returns a single result), I preferred keeping them as separate tests using the same data provider to emphasize the difference.

Copy link
Contributor

@gortiz gortiz left a comment

Choose a reason for hiding this comment

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

It would be cool if we could change the test before merging it, but even if we don't I think this is ready to merge.

@gortiz gortiz merged commit 9302f18 into apache:master May 30, 2024
19 checks passed
@Jackie-Jiang Jackie-Jiang added bugfix multi-stage Related to the multi-stage query engine labels Jun 3, 2024
gortiz pushed a commit to gortiz/pinot that referenced this pull request Jun 14, 2024
…e query engine (apache#13255)

* Fix null literal handling for null intolerant functions in multi-stage query engine

* Use data provider for null literal selection integration tests
@npawar npawar added the v1v2 label Aug 15, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bugfix multi-stage Related to the multi-stage query engine v1v2
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants