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

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

Open
jnehlmeier opened this issue Sep 27, 2022 · 5 comments

Comments

@jnehlmeier
Copy link

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
Copy link
Author

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 #1867 and the fix for issue #1354 authored by @dazey3 did not catch all cases.

The issue occurs irregularly so it still seems to be a concurrency issue.

@jnehlmeier jnehlmeier changed the title 2.7.11: NullPointerException in ExpressionOperator.printCollection (likely concurrency issue) 2.7.11+: NullPointerException in ExpressionOperator.printCollection (likely concurrency issue) Jul 12, 2023
@jnehlmeier
Copy link
Author

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

@netsrotr
Copy link

netsrotr commented Jun 6, 2024

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

@igormukhin
Copy link
Contributor

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

@jnehlmeier
Copy link
Author

@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

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

No branches or pull requests

3 participants