eclipselink
eclipselink copied to clipboard
2.7.11+: NullPointerException in ExpressionOperator.printCollection (likely concurrency issue)
We received the following NullPointerException in production:
java.lang.NullPointerException: Cannot read the array length because "this.argumentIndices" is null
at org.eclipse.persistence.expressions.ExpressionOperator.printCollection(ExpressionOperator.java:2391)
at org.eclipse.persistence.internal.expressions.ArgumentListFunctionExpression.printSQL(ArgumentListFunctionExpression.java:102)
at org.eclipse.persistence.expressions.ExpressionOperator.printCollection(ExpressionOperator.java:2400)
at org.eclipse.persistence.internal.expressions.FunctionExpression.printSQL(FunctionExpression.java:581)
at org.eclipse.persistence.internal.expressions.FunctionExpression.writeFields(FunctionExpression.java:759)
at org.eclipse.persistence.internal.expressions.SQLSelectStatement.writeFieldsFromExpression(SQLSelectStatement.java:2194)
at org.eclipse.persistence.internal.expressions.SQLSelectStatement.writeFieldsIn(SQLSelectStatement.java:2209)
at org.eclipse.persistence.internal.expressions.SQLSelectStatement.printSQLSelect(SQLSelectStatement.java:1789)
at org.eclipse.persistence.internal.expressions.SQLSelectStatement.printSQL(SQLSelectStatement.java:1752)
at org.eclipse.persistence.internal.databaseaccess.DatabasePlatform.printSQLSelectStatement(DatabasePlatform.java:3586)
at org.eclipse.persistence.platform.database.PostgreSQLPlatform.printSQLSelectStatement(PostgreSQLPlatform.java:543)
at org.eclipse.persistence.internal.expressions.SQLSelectStatement.buildCall(SQLSelectStatement.java:868)
at org.eclipse.persistence.internal.expressions.SQLSelectStatement.buildCall(SQLSelectStatement.java:879)
at org.eclipse.persistence.descriptors.ClassDescriptor.buildCallFromStatement(ClassDescriptor.java:885)
at org.eclipse.persistence.internal.queries.StatementQueryMechanism.setCallFromStatement(StatementQueryMechanism.java:393)
at org.eclipse.persistence.internal.queries.ExpressionQueryMechanism.prepareReportQuerySelectAllRows(ExpressionQueryMechanism.java:1699)
at org.eclipse.persistence.queries.ReportQuery.prepareSelectAllRows(ReportQuery.java:1249)
at org.eclipse.persistence.queries.ReadAllQuery.prepare(ReadAllQuery.java:841)
at org.eclipse.persistence.queries.ReportQuery.prepare(ReportQuery.java:1117)
at org.eclipse.persistence.queries.DatabaseQuery.checkPrepare(DatabaseQuery.java:675)
The code in question looks like:
if (this.argumentIndices == null) {
this.argumentIndices = new int[items.size()];
for (int i = 0; i < this.argumentIndices.length; i++){
this.argumentIndices[i] = i;
}
}
String[] dbStrings = getDatabaseStrings(items.size());
for (int i = 0; i < this.argumentIndices.length; i++) { // NPE here
final int index = this.argumentIndices[I];
...
...
}
The NPE happens in the for
-loop, so I guess a different Thread has set this.argumentIndices
to null
between the if
statement and the for
loop.
Issue still exists in EclipseLink 2.7.12.
java.lang.NullPointerException: Cannot read the array length because "this.argumentIndices" is null
at org.eclipse.persistence.expressions.ExpressionOperator.printCollection(ExpressionOperator.java:2391)
at org.eclipse.persistence.internal.expressions.ArgumentListFunctionExpression.printSQL(ArgumentListFunctionExpression.java:102)
at org.eclipse.persistence.expressions.ExpressionOperator.printCollection(ExpressionOperator.java:2400)
at org.eclipse.persistence.internal.expressions.FunctionExpression.printSQL(FunctionExpression.java:581)
at org.eclipse.persistence.expressions.ExpressionOperator.printCollection(ExpressionOperator.java:2400)
at org.eclipse.persistence.internal.expressions.FunctionExpression.printSQL(FunctionExpression.java:581)
at org.eclipse.persistence.expressions.ExpressionOperator.printCollection(ExpressionOperator.java:2400)
at org.eclipse.persistence.internal.expressions.FunctionExpression.printSQL(FunctionExpression.java:581)
at org.eclipse.persistence.expressions.ExpressionOperator.printCollection(ExpressionOperator.java:2400)
at org.eclipse.persistence.internal.expressions.FunctionExpression.printSQL(FunctionExpression.java:581)
at org.eclipse.persistence.expressions.ExpressionOperator.printCollection(ExpressionOperator.java:2400)
at org.eclipse.persistence.internal.expressions.FunctionExpression.printSQL(FunctionExpression.java:581)
at org.eclipse.persistence.expressions.ExpressionOperator.printCollection(ExpressionOperator.java:2400)
at org.eclipse.persistence.internal.expressions.FunctionExpression.printSQL(FunctionExpression.java:581)
at org.eclipse.persistence.expressions.ExpressionOperator.printCollection(ExpressionOperator.java:2400)
at org.eclipse.persistence.internal.expressions.FunctionExpression.printSQL(FunctionExpression.java:581)
at org.eclipse.persistence.expressions.ExpressionOperator.printDuo(ExpressionOperator.java:2447)
at org.eclipse.persistence.internal.expressions.CompoundExpression.printSQL(CompoundExpression.java:291)
at org.eclipse.persistence.expressions.ExpressionOperator.printDuo(ExpressionOperator.java:2442)
at org.eclipse.persistence.internal.expressions.CompoundExpression.printSQL(CompoundExpression.java:291)
at org.eclipse.persistence.internal.expressions.ExpressionSQLPrinter.translateExpression(ExpressionSQLPrinter.java:337)
at org.eclipse.persistence.internal.expressions.ExpressionSQLPrinter.printExpression(ExpressionSQLPrinter.java:135)
at org.eclipse.persistence.internal.expressions.SQLSelectStatement.printSQLWhereClause(SQLSelectStatement.java:1805)
at org.eclipse.persistence.internal.expressions.SQLSelectStatement.printSQL(SQLSelectStatement.java:1754)
at org.eclipse.persistence.internal.databaseaccess.DatabasePlatform.printSQLSelectStatement(DatabasePlatform.java:3593)
at org.eclipse.persistence.platform.database.PostgreSQLPlatform.printSQLSelectStatement(PostgreSQLPlatform.java:543)
at org.eclipse.persistence.internal.expressions.SQLSelectStatement.buildCall(SQLSelectStatement.java:868)
at org.eclipse.persistence.internal.expressions.SQLSelectStatement.buildCall(SQLSelectStatement.java:879)
at org.eclipse.persistence.descriptors.ClassDescriptor.buildCallFromStatement(ClassDescriptor.java:885)
at org.eclipse.persistence.internal.queries.StatementQueryMechanism.setCallFromStatement(StatementQueryMechanism.java:407)
at org.eclipse.persistence.internal.queries.StatementQueryMechanism.prepareSelectAllRows(StatementQueryMechanism.java:332)
at org.eclipse.persistence.internal.queries.ExpressionQueryMechanism.prepareSelectAllRows(ExpressionQueryMechanism.java:1724)
at org.eclipse.persistence.queries.ReadAllQuery.prepareSelectAllRows(ReadAllQuery.java:910)
at org.eclipse.persistence.queries.ReadAllQuery.prepare(ReadAllQuery.java:841)
at org.eclipse.persistence.queries.DatabaseQuery.checkPrepare(DatabaseQuery.java:675)
The query searches multiple nullable columns for multiple words. To do so it uses COALESCE
in case the column contains a null value and concatenates search column values using a white space. The resulting string will be searched using LIKE
.
Consider the search terms "word1" and "word2" then the query would look like
SELECT e
FROM Entity e JOIN e.person p
WHERE
p.id = :personId
AND (
LIKE (LOWER(CONCAT(CONCAT(COALESCE(e.column1, ""), " "), COALESCE(e.column2, ""))), "%word1%")
AND
LIKE (LOWER(CONCAT(CONCAT(COALESCE(e.column1, ""), " "), COALESCE(e.column2, ""))), "%word2%")
)
The query itself is generated using criteria builder. Because criteria builder does not have a varargs method for concat
it is called twice in code. To demonstrate that in the JPQL equivalent above I used two nested concat
.
So I guess the issue is similar to issue https://github.com/eclipse-ee4j/eclipselink/issues/1867 and the fix for issue https://github.com/eclipse-ee4j/eclipselink/issues/1354 authored by @dazey3 did not catch all cases.
The issue occurs irregularly so it still seems to be a concurrency issue.
Still happens in 2.7.14 and query did not had COALESCE
but instead ORDER BY CASE WHEN ... THEN ... ELSE ... END
.
Still happens in 2.7.15 eclipselink.v2.7.15.stacktrace.txt
I just created a sample project that reproduces the issue: https://github.com/igormukhin/eclipselink-issue-1717-test
@igormukhin As noted in my comment it is also possible to create the stacktrace using a query that does not use COALESCE
. Instead it uses ORDER BY CASE WHEN ...
. Maybe you can use your test infrastructure and add an additional query to try that.
The query in question did look like:
SELECT a
FROM A a JOIN a.b b JOIN b.c c
WHERE a.person.id = :personId
AND LENGTH(trim(a.description)) > 0
order by
case
when (b.date is not null)
then b.date
else b.originalDate
end DESC,
case
when (b.date is not null)
then b.startTime
else c.startTime
end DESC,
a.id DESC