eclipselink icon indicating copy to clipboard operation
eclipselink copied to clipboard

2.7.11+: NullPointerException in ExpressionOperator.printCollection (likely concurrency issue)

Open jnehlmeier opened this issue 2 years ago • 5 comments

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.

jnehlmeier avatar Sep 27 '22 09:09 jnehlmeier

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.

jnehlmeier avatar Jul 12 '23 09:07 jnehlmeier

Still happens in 2.7.14 and query did not had COALESCE but instead ORDER BY CASE WHEN ... THEN ... ELSE ... END.

jnehlmeier avatar Mar 25 '24 09:03 jnehlmeier

Still happens in 2.7.15 eclipselink.v2.7.15.stacktrace.txt

netsrotr avatar Jun 06 '24 08:06 netsrotr

I just created a sample project that reproduces the issue: https://github.com/igormukhin/eclipselink-issue-1717-test

igormukhin avatar Jun 10 '24 19:06 igormukhin

@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

jnehlmeier avatar Jun 11 '24 09:06 jnehlmeier