Skip to content

[OPENJPA-2956] Unwrap ID(), honour null precedence and reject set operations in memory - #178

Open
rzo1 wants to merge 2 commits into
masterfrom
OPENJPA-2956
Open

[OPENJPA-2956] Unwrap ID(), honour null precedence and reject set operations in memory#178
rzo1 wants to merge 2 commits into
masterfrom
OPENJPA-2956

Conversation

@rzo1

@rzo1 rzo1 commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Three separate defects, all reachable: the in-memory executor is selected whenever a candidate collection is supplied, when the store does not support datastore execution (openjpa-xmlstore), or when dirty instances are queried with FlushBeforeQueries disabled.

ID() returned the internal identity wrapper rather than the raw key, so a comparison against the plain key threw ClassCastException out of Filters.convert for numeric ids, never matched for an @EmbeddedId (OpenJPAId.equals requires class equality), and matched only by accident for a String id. It now unwraps exactly as the JDBC projection does. One correction to the review comment: JDBC does not unwrap the comparison operand — it normalises the other side, and its only unwrap is GetNativeObjectId.load(). The wrapper-returning getObjectId() is unchanged.

NULLS FIRST/NULLS LAST were ignored: the comparator hard-coded nulls last when ascending and first when descending, so two of the four combinations were already right and two were silently wrong. The requested precedence is now threaded through, falling back to the previous policy when none is given — so rejecting these instead would have been a regression.

Set operations produced a NullPointerException from a compound QueryExpressions with no filter, or an empty result. They are now rejected with a message saying why the query is running in memory and how to avoid it. Implementing them would be a feature, not a fix: the executor is built for a single candidate extent, setCandidateCollection has no defined meaning across operands, and the kernel has no multiset semantics for the ALL variants.

Note that the in-memory path cannot yet be exercised end to end from JPQL with an identification variable: JPQLExpressionBuilder casts the value from getThis() to Path, and the in-memory factory returns a Val, so it fails with a ClassCastException. That is an older, separate defect and wants its own issue; it is why the ID() change has no end-to-end test here.

…rations in memory

Three separate defects on the in-memory path, all reachable: the executor
is selected whenever a candidate collection is supplied, when the store
does not support datastore execution, or when dirty instances are queried
with FlushBeforeQueries disabled.

ID() returned the internal identity wrapper rather than the raw key, so a
comparison against the plain key threw a ClassCastException out of
Filters.convert for numeric ids, never matched for an @EmbeddedId, and
matched only by accident for a String id. It now unwraps exactly as the
JDBC projection does. The wrapper-returning getObjectId() is unchanged.

NULLS FIRST and NULLS LAST were ignored: the comparator hard coded nulls
last when ascending and first when descending, so two of the four
combinations were right by chance and two were silently wrong. The
requested precedence is now threaded through, falling back to the previous
policy when none is given.

Set operations produced a NullPointerException from a compound expression
with no filter, or an empty result. They are now rejected with a message
that says why the query is running in memory and how to avoid it: the
executor is built for one candidate extent and has no multiset semantics,
and a candidate collection has no defined meaning across operands.

Note that the in-memory path cannot yet be exercised end to end from JPQL
with an identification variable: JPQLExpressionBuilder casts the value from
getThis() to Path, and the in-memory factory returns a Val, so it fails with
a ClassCastException. That is an older, separate defect and wants its own
issue.
@rzo1 rzo1 self-assigned this Sep 2, 2026
@rzo1
rzo1 requested review from cristof and solomax September 2, 2026 19:18

@solomax solomax left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Do we need tests for this?

…rejection

Review feedback. Both cases run through the in-memory executor by supplying
a candidate collection, and both fail against the unchanged kernel: the null
precedence test because the comparator ignored NULLS FIRST/LAST, the set
operation test because the query failed with a NullPointerException instead
of saying it cannot be evaluated in memory.

ID() is deliberately not covered. It cannot be reached from JPQL in memory
at all: JPQLExpressionBuilder casts the value from getThis() to Path, the
in-memory factory returns a Val, and the query dies with a
ClassCastException before the identity is evaluated. That is an older,
separate defect; a test for ID() has to wait for it.
@rzo1

rzo1 commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

Added tests for two of the three, in TestInMemoryScalarExpressions — both force the in-memory executor by supplying a candidate collection, and both fail against the unchanged kernel:

  • testNullPrecedenceInMemory — all four combinations of ASC/DESC with NULLS FIRST/LAST. Two of them were already correct by accident, the other two were silently wrong.
  • testSetOperationInMemoryIsRejected — asserts the message actually says the query cannot be evaluated in memory. My first version of this assertion passed against master, because the query failed there anyway with a NullPointerException whose message echoed the query text; it now pins the rejection rather than any failure.

ID() has no test, and cannot have one yet. It is unreachable from JPQL in memory: JPQLExpressionBuilder:2013 casts the value from factory.getThis() to Path, the JDBC factory returns a PCPath but the in-memory factory returns This extends Val, so the query dies with

java.lang.ClassCastException: class org.apache.openjpa.kernel.exps.This
    cannot be cast to class org.apache.openjpa.kernel.exps.Path

before the identity is ever evaluated. I confirmed that by writing the test and watching it error. That is an older defect, unrelated to this change, and I would rather fix it in its own issue than bundle a This/Path rework in here — the unwrap itself is a byte-for-byte mirror of the JDBC projection rule (GetNativeObjectId.load()), and the existing suites confirm it changes nothing else.

Happy to open that issue and add the ID() test on top of it if you prefer that order.

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