fix: ORDER BY CASE WHEN expression fails with NPE on baseExpr.identifier#3778
Conversation
…ier (#3777) Add null check for baseExpr.identifier in visitOrderByItem to handle CASE WHEN expressions, which produce a BaseExpression with identifier=null. Store the full expression in OrderByItem.expression for evaluation at sort time, and use it in calculateAdditionalOrderByProjections to inject a computed projection (SELECT fields case) or evaluate directly (SELECT *). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request fixes a NullPointerException occurring when complex expressions, such as CASE WHEN, are used in an ORDER BY clause. The changes involve updating SQLASTBuilder to store the Expression object directly within OrderByItem instead of relying on identifier extraction or string-based aliases. Additionally, SelectExecutionPlanner and OrderByItem were updated to handle the evaluation and projection of these expressions. Regression tests have been added to SQLCaseTest to verify the fix. I have no feedback to provide.
Code ReviewOverall, this is a clean and well-targeted fix for the NPE issue. The approach - storing the What works well
Issues1. The method currently checks only public boolean refersToParent() {
if (alias != null && alias.equalsIgnoreCase("$parent"))
return true;
return modifier != null && modifier.refersToParent();
}If a CASE WHEN expression in ORDER BY references if (expression != null && expression.refersToParent())
return true;2. Inconsistent field access pattern The new 3. public String getName() {
return alias != null ? alias : recordAttr != null ? recordAttr : null;
}This returns if (!allAliases.contains(item.getName()))
4. The The new branch: } else {
continue;
}This silently skips an ORDER BY item that has no alias, no expression, and no recordAttr. Previously this case couldn't occur (the expression was serialized to an alias string). With the new code it still shouldn't occur, but if it does (e.g. a bug elsewhere), the ORDER BY item would be silently ignored. A defensive log warning here would help debugging. Minor
SummaryThe fix is correct and the test coverage is solid. The main actionable items before merge are:
|
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 6 |
🟢 Coverage 100.00% diff coverage · -7.87% coverage variation
Metric Results Coverage variation ✅ -7.87% coverage variation Diff coverage ✅ 100.00% diff coverage Coverage variation details
Coverable lines Covered lines Coverage Common ancestor commit (6964490) 116313 85427 73.45% Head commit (ce5a052) 147469 (+31156) 96700 (+11273) 65.57% (-7.87%) Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch:
<coverage of head commit> - <coverage of common ancestor commit>Diff coverage details
Coverable lines Covered lines Diff coverage Pull request (#3778) 6 6 100.00% Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified:
<covered lines added or modified>/<coverable lines added or modified> * 100%
TIP This summary will be updated as you push new changes. Give us feedback
- Check expression.refersToParent() in OrderByItem.refersToParent() to correctly handle CASE WHEN expressions referencing $parent in subquery contexts - Add comment to getName() clarifying null return semantics when only expression is set Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Code Review (follow-up correction)I need to correct an error in my previous review comment: if (expression != null && expression.refersToParent())
return true;Accurate assessment of remaining points
Mutation in
OverallThe fix is correct and complete. All the right methods ( |
|
Addressed the review in a follow-up commit:
The getter/setter suggestion was skipped - |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #3778 +/- ##
==========================================
+ Coverage 64.61% 65.15% +0.54%
==========================================
Files 1580 1580
Lines 116313 116354 +41
Branches 24669 24677 +8
==========================================
+ Hits 75155 75813 +658
+ Misses 30914 30224 -690
- Partials 10244 10317 +73 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
…ip ci] Bumps [org.postgresql:postgresql](https://github.com/pgjdbc/pgjdbc) from 42.7.7 to 42.7.8. Release notes *Sourced from [org.postgresql:postgresql's releases](https://github.com/pgjdbc/pgjdbc/releases).* > v42.7.8 > ------- > > Notable changes: > ---------------- > > * Releases are signed with a new PGP key which is generated at GitHub Actions and stored only there [`@vlsi`](https://github.com/vlsi) ([ArcadeData#3701](https://redirect.github.com/pgjdbc/pgjdbc/issues/3701)) > > Changes > ------- > > * fix: Update release plugin config to use .set(...) for props and inject nexus secrets via props [`@sehrope`](https://github.com/sehrope) ([ArcadeData#3802](https://redirect.github.com/pgjdbc/pgjdbc/issues/3802)) > * update version to 42.7.8 [`@davecramer`](https://github.com/davecramer) ([ArcadeData#3801](https://redirect.github.com/pgjdbc/pgjdbc/issues/3801)) > * change logs for version 42.7.8 [`@davecramer`](https://github.com/davecramer) ([ArcadeData#3797](https://redirect.github.com/pgjdbc/pgjdbc/issues/3797)) > * Fix getNotifications() documentation [`@pdewacht`](https://github.com/pdewacht) ([ArcadeData#3800](https://redirect.github.com/pgjdbc/pgjdbc/issues/3800)) > * fix(deps): update dependency om.ongres.scram:scram-client to 3.2 [`@jorsol`](https://github.com/jorsol) ([ArcadeData#3799](https://redirect.github.com/pgjdbc/pgjdbc/issues/3799)) > * Add configurable boolean-to-numeric conversion for ResultSet getters [`@vwassan`](https://github.com/vwassan) ([ArcadeData#3796](https://redirect.github.com/pgjdbc/pgjdbc/issues/3796)) > * Update CONTRIBUTING.md [`@davecramer`](https://github.com/davecramer) ([ArcadeData#3794](https://redirect.github.com/pgjdbc/pgjdbc/issues/3794)) > * perf: remove QUERY\_ONESHOT flag when calling getMetaData [`@ShenFeng312`](https://github.com/ShenFeng312) ([ArcadeData#3783](https://redirect.github.com/pgjdbc/pgjdbc/issues/3783)) > * test: add bench for batch insert via unnest with arrays [`@lantalex`](https://github.com/lantalex) ([ArcadeData#3782](https://redirect.github.com/pgjdbc/pgjdbc/issues/3782)) > * fix: Change "PST" timezone in TimestampTest to "Pacific Standard Time" [`@simon-greatrix`](https://github.com/simon-greatrix) ([ArcadeData#3774](https://redirect.github.com/pgjdbc/pgjdbc/issues/3774)) > * Use `BufferedInputStream` with `FileInputStream` [`@jgardn3r`](https://github.com/jgardn3r) ([ArcadeData#3750](https://redirect.github.com/pgjdbc/pgjdbc/issues/3750)) > * Fix [ArcadeData#3747](https://redirect.github.com/pgjdbc/pgjdbc/issues/3747): Incorrect class comparison in PGXmlFactoryFactory validation [`@eitch`](https://github.com/eitch) ([ArcadeData#3748](https://redirect.github.com/pgjdbc/pgjdbc/issues/3748)) > * fix: traverse the current dimension to get the correct pos in PgArray#calcRemainingDataLength [`@sly461`](https://github.com/sly461) ([ArcadeData#3746](https://redirect.github.com/pgjdbc/pgjdbc/issues/3746)) > * test: add channelBinding to SslTest [`@vlsi`](https://github.com/vlsi) ([ArcadeData#3665](https://redirect.github.com/pgjdbc/pgjdbc/issues/3665)) > * fix: remove excessive ReentrantLock.lock usages [`@vlsi`](https://github.com/vlsi) ([ArcadeData#3703](https://redirect.github.com/pgjdbc/pgjdbc/issues/3703)) > * test: add ossf-scorecard security scanning [`@vlsi`](https://github.com/vlsi) ([ArcadeData#3695](https://redirect.github.com/pgjdbc/pgjdbc/issues/3695)) > * fix indentation to let CI pass [`@mohitsatr`](https://github.com/mohitsatr) ([ArcadeData#3682](https://redirect.github.com/pgjdbc/pgjdbc/issues/3682)) > * test: extract pgjdbc/testFixtures to testkit project [`@vlsi`](https://github.com/vlsi) ([ArcadeData#3666](https://redirect.github.com/pgjdbc/pgjdbc/issues/3666)) > * fix: make sure getImportedExportedKeys returns columns in consistent order [`@vlsi`](https://github.com/vlsi) ([ArcadeData#3663](https://redirect.github.com/pgjdbc/pgjdbc/issues/3663)) > * feat: use PreparedStatement for DatabaseMetaData.getCrossReference, getImportedKeys, getExportedKeys [`@vlsi`](https://github.com/vlsi) ([ArcadeData#3641](https://redirect.github.com/pgjdbc/pgjdbc/issues/3641)) > * Add "SELF\_REFERENCING\_COL\_NAME" field to getTables' ResultSetMetaData to fix NullPointerException [`@SophiahHo`](https://github.com/SophiahHo) ([ArcadeData#3660](https://redirect.github.com/pgjdbc/pgjdbc/issues/3660)) > > 🐛 Bug Fixes > ----------- > > * fix: avoid IllegalStateException: Timer already cancelled when StatementCancelTimerTask.run throws a runtime error [`@vlsi`](https://github.com/vlsi) ([ArcadeData#3778](https://redirect.github.com/pgjdbc/pgjdbc/issues/3778)) > * fix: avoid NullPointerException when cancelling a query if cancel key is not known yet [`@vlsi`](https://github.com/vlsi) ([ArcadeData#3780](https://redirect.github.com/pgjdbc/pgjdbc/issues/3780)) > * fix: unable to open replication connection to servers < 12 [`@vlsi`](https://github.com/vlsi) ([ArcadeData#3678](https://redirect.github.com/pgjdbc/pgjdbc/issues/3678)) > > 🧰 Maintenance > ------------- > > * chore: fix published project name [`@vlsi`](https://github.com/vlsi) ([ArcadeData#3809](https://redirect.github.com/pgjdbc/pgjdbc/issues/3809)) > * chore: update publish to Central Portal task name after bumping nmcp [`@vlsi`](https://github.com/vlsi) ([ArcadeData#3808](https://redirect.github.com/pgjdbc/pgjdbc/issues/3808)) > * fix(deps): update com.gradleup.nmcp to 1.1.0 [`@vlsi`](https://github.com/vlsi) ([ArcadeData#3807](https://redirect.github.com/pgjdbc/pgjdbc/issues/3807)) > * Revert "fix: Update release plugin config to use .set(...) for props and inject nexus creds via gradle props" [`@vlsi`](https://github.com/vlsi) ([ArcadeData#3803](https://redirect.github.com/pgjdbc/pgjdbc/issues/3803)) > * chore: group com.gradleup.nmcp version updates [`@vlsi`](https://github.com/vlsi) ([ArcadeData#3805](https://redirect.github.com/pgjdbc/pgjdbc/issues/3805)) > * chore: use bump org.apache.bcel:bcel test dependency in testCompileClasspath as well [`@vlsi`](https://github.com/vlsi) ([ArcadeData#3775](https://redirect.github.com/pgjdbc/pgjdbc/issues/3775)) > * Fix typo in PGReplicationStream.java [`@atorik`](https://github.com/atorik) ([ArcadeData#3758](https://redirect.github.com/pgjdbc/pgjdbc/issues/3758)) > * chore: remove JDK versions from the key workflow names [`@vlsi`](https://github.com/vlsi) ([ArcadeData#3759](https://redirect.github.com/pgjdbc/pgjdbc/issues/3759)) > * chore: add GitHub Actions workflow for generating release PGP key [`@vlsi`](https://github.com/vlsi) ([ArcadeData#3701](https://redirect.github.com/pgjdbc/pgjdbc/issues/3701)) > * chore: replace StandardCharsets with Charsets to simplify code [`@vlsi`](https://github.com/vlsi) ([ArcadeData#3751](https://redirect.github.com/pgjdbc/pgjdbc/issues/3751)) > * chore: migrate publish workflow to Central Portal publishing via com.gradleup.nmcp [`@vlsi`](https://github.com/vlsi) ([ArcadeData#3686](https://redirect.github.com/pgjdbc/pgjdbc/issues/3686)) > * chore: adjust the default branch name for ossf scorecard scan [`@vlsi`](https://github.com/vlsi) ([ArcadeData#3697](https://redirect.github.com/pgjdbc/pgjdbc/issues/3697)) > * chore: add top-level read-only permissions for GitHub Actions when missing [`@vlsi`](https://github.com/vlsi) ([ArcadeData#3696](https://redirect.github.com/pgjdbc/pgjdbc/issues/3696)) > * chore: use config:best-practices preset for Renovate [`@vlsi`](https://github.com/vlsi) ([ArcadeData#3687](https://redirect.github.com/pgjdbc/pgjdbc/issues/3687)) ... (truncated) Changelog *Sourced from [org.postgresql:postgresql's changelog](https://github.com/pgjdbc/pgjdbc/blob/master/CHANGELOG.md).* > [42.7.8] (2025-09-18) > --------------------- > > ### Added > > * feat: Add configurable boolean-to-numeric conversion for ResultSet getters [PR [ArcadeData#3796](https://redirect.github.com/pgjdbc/pgjdbc/issues/3796)]([pgjdbc/pgjdbc#3796](https://redirect.github.com/pgjdbc/pgjdbc/pull/3796)) > > ### Changed > > * perf: remove QUERY\_ONESHOT flag when calling getMetaData [PR [ArcadeData#3783](https://redirect.github.com/pgjdbc/pgjdbc/issues/3783)]([pgjdbc/pgjdbc#3783](https://redirect.github.com/pgjdbc/pgjdbc/pull/3783)) > * perf: use `BufferedInputStream` with `FileInputStream` [PR [ArcadeData#3750](https://redirect.github.com/pgjdbc/pgjdbc/issues/3750)]([pgjdbc/pgjdbc#3750](https://redirect.github.com/pgjdbc/pgjdbc/pull/3750)) > * perf: enable server-prepared statements for DatabaseMetaData > > ### Fixed > > * fix: avoid NullPointerException when cancelling a query if cancel key is not known yet > * fix: Change "PST" timezone in TimestampTest to "Pacific Standard Time" [PR [ArcadeData#3774](https://redirect.github.com/pgjdbc/pgjdbc/issues/3774)]([pgjdbc/pgjdbc#3774](https://redirect.github.com/pgjdbc/pgjdbc/pull/3774)) > * fix: traverse the current dimension to get the correct pos in PgArray#calcRemainingDataLength [PR [ArcadeData#3746](https://redirect.github.com/pgjdbc/pgjdbc/issues/3746)]([pgjdbc/pgjdbc#3746](https://redirect.github.com/pgjdbc/pgjdbc/pull/3746)) > * fix: make sure getImportedExportedKeys returns columns in consistent order > * fix: Add "SELF\_REFERENCING\_COL\_NAME" field to getTables' ResultSetMetaData to fix NullPointerException [PR [ArcadeData#3660](https://redirect.github.com/pgjdbc/pgjdbc/issues/3660)]([pgjdbc/pgjdbc#3660](https://redirect.github.com/pgjdbc/pgjdbc/pull/3660)) > * fix: unable to open replication connection to servers < 12 > * fix: avoid closing statement caused by driver's internal ResultSet#close() > * fix: return empty metadata for empty catalog names as it was before > * fix: Incorrect class comparison in PGXmlFactoryFactory validation Commits * [`9a5492d`](pgjdbc/pgjdbc@9a5492d) chore: fix published project name * [`ca064f8`](pgjdbc/pgjdbc@ca064f8) chore: update publish to Central Portal task name after bumping nmcp * [`3d97bb8`](pgjdbc/pgjdbc@3d97bb8) fix: avoid IllegalStateException: Timer already cancelled when StatementCanc... * [`faa7dfc`](pgjdbc/pgjdbc@faa7dfc) test: move BaseTest4 to testkit module * [`dbf2847`](pgjdbc/pgjdbc@dbf2847) fix(deps): update com.gradleup.nmcp to 1.1.0 * [`9245e26`](pgjdbc/pgjdbc@9245e26) Revert "fix: Update release plugin config to use .set(...) for props and inje... * [`8e833c3`](pgjdbc/pgjdbc@8e833c3) chore: group com.gradleup.nmcp version updates * [`ec5a088`](pgjdbc/pgjdbc@ec5a088) fix: Update release plugin config to use .set(...) for props and inject nexus... * [`c03db58`](pgjdbc/pgjdbc@c03db58) update version to 42.7.8 ([ArcadeData#3801](https://redirect.github.com/pgjdbc/pgjdbc/issues/3801)) * [`50ff169`](pgjdbc/pgjdbc@50ff169) change logs for version 42.7.8 ([ArcadeData#3797](https://redirect.github.com/pgjdbc/pgjdbc/issues/3797)) * Additional commits viewable in [compare view](pgjdbc/pgjdbc@REL42.7.7...REL42.7.8) [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- Dependabot commands and options You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
…p ci] Bumps [org.mockito:mockito-core](https://github.com/mockito/mockito) from 5.21.0 to 5.22.0. Release notes *Sourced from [org.mockito:mockito-core's releases](https://github.com/mockito/mockito/releases).* > v5.22.0 > ------- > > *Changelog generated by [Shipkit Changelog Gradle Plugin](https://github.com/shipkit/shipkit-changelog)* > > #### 5.22.0 > > * 2026-02-27 - [6 commit(s)](mockito/mockito@v5.21.0...v5.22.0) by Joshua Selbo, NiMv1, Rafael Winterhalter, dependabot[bot], eunbin son > * Avoid mocking of internal static utilities [([ArcadeData#3785](https://redirect.github.com/mockito/mockito/issues/3785))]([mockito/mockito#3785](https://redirect.github.com/mockito/mockito/pull/3785)) > * Bump graalvm/setup-graalvm from 1.4.4 to 1.4.5 [([ArcadeData#3780](https://redirect.github.com/mockito/mockito/issues/3780))]([mockito/mockito#3780](https://redirect.github.com/mockito/mockito/pull/3780)) > * Static mocking of UUID.class corrupted under JDK 25 [([ArcadeData#3778](https://redirect.github.com/mockito/mockito/issues/3778))]([mockito/mockito#3778](https://redirect.github.com/mockito/mockito/issues/3778)) > * Bump actions/upload-artifact from 5 to 6 [([ArcadeData#3774](https://redirect.github.com/mockito/mockito/issues/3774))]([mockito/mockito#3774](https://redirect.github.com/mockito/mockito/pull/3774)) > * docs: clarify RETURNS\_MOCKS behavior with sealed abstract enums (Java 15+) [([ArcadeData#3773](https://redirect.github.com/mockito/mockito/issues/3773))]([mockito/mockito#3773](https://redirect.github.com/mockito/mockito/pull/3773)) > * Add tests for Sets utility class [([ArcadeData#3771](https://redirect.github.com/mockito/mockito/issues/3771))]([mockito/mockito#3771](https://redirect.github.com/mockito/mockito/pull/3771)) > * Add core API to enable Kotlin singleton mocking [([ArcadeData#3762](https://redirect.github.com/mockito/mockito/issues/3762))]([mockito/mockito#3762](https://redirect.github.com/mockito/mockito/pull/3762)) > * Stubbing Kotlin `object` singletons [([ArcadeData#3652](https://redirect.github.com/mockito/mockito/issues/3652))]([mockito/mockito#3652](https://redirect.github.com/mockito/mockito/issues/3652)) > * Incorrect documentation for RETURNS\_MOCKS [([ArcadeData#3285](https://redirect.github.com/mockito/mockito/issues/3285))]([mockito/mockito#3285](https://redirect.github.com/mockito/mockito/issues/3285)) Commits * [`25f1395`](mockito/mockito@25f1395) Add core API to enable Kotlin singleton mocking ([ArcadeData#3762](https://redirect.github.com/mockito/mockito/issues/3762)) * [`ef9ee55`](mockito/mockito@ef9ee55) Avoids mocking private static methods, as well as package-private static meth... * [`d16fcfc`](mockito/mockito@d16fcfc) Bump graalvm/setup-graalvm from 1.4.4 to 1.4.5 ([ArcadeData#3780](https://redirect.github.com/mockito/mockito/issues/3780)) * [`27eb8a3`](mockito/mockito@27eb8a3) Clarify `RETURNS_MOCKS` behavior with sealed abstract enums (Java 15+) ([ArcadeData#3773](https://redirect.github.com/mockito/mockito/issues/3773)) * [`9e5d449`](mockito/mockito@9e5d449) Add tests for Sets utility class ([ArcadeData#3771](https://redirect.github.com/mockito/mockito/issues/3771)) * [`8d9a62f`](mockito/mockito@8d9a62f) Bump actions/upload-artifact from 5 to 6 ([ArcadeData#3774](https://redirect.github.com/mockito/mockito/issues/3774)) * See full diff in [compare view](mockito/mockito@v5.21.0...v5.22.0) [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- Dependabot commands and options You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Summary
Fixes #3777
baseExpr.identifierinSQLASTBuilder.visitOrderByItem-CASE WHENproduces aBaseExpressionwithidentifier = null, causing NPE on.suffixaccessexpressionfield toOrderByItemto store complex expressions (CASE WHEN, etc.)OrderByItem.compare()to evaluate the expression directly forSELECT *queriesSelectExecutionPlanner.calculateAdditionalOrderByProjections()to inject a computed projection forSELECT fieldsqueriesTest plan
orderByCaseWhenExpression- inline CASE WHEN in ORDER BY ASCorderByCaseWhenExpressionDesc- inline CASE WHEN in ORDER BY DESCorderByCaseWhenWithSelectStar- CASE WHEN in ORDER BY with SELECT *orderByExtendedCaseWhenExpression- extended CASE expr WHEN val THEN result in ORDER BYSQLCaseTesttests pass, no regressions in ORDER BY related tests🤖 Generated with Claude Code