opensearch-project / opensearch-project/sql

[BUG] LEFT OUTER JOIN causes a NPE when left hand side join field is null

Open
#4,346 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug error-experience SQL
Dominant language
Java
Stars
176
Forks
229
Avg merge
2d 21h
Merged PRs (30d)
43

Description

What is the bug?
LEFT OUTER JOIN causes a NPE when left hand side join field is null. After removing ORDER BY, the error does not present

How can one reproduce the bug?
Execute the below code in the devtools window

DELETE /entity-a-idx
PUT /entity-a-idx
{
  "settings": {
    "index": {
      "number_of_shards": 2,
      "number_of_replicas": 1
    }
  },
  "mappings": {
    "properties": {
      "product": {
        "type": "keyword"
      },
      "category": {
        "type": "keyword"
      }
    }
  }
}
POST entity-a-idx/_create/1
{
  "product": "Apples",
  "category": "Fruit"
}

POST entity-a-idx/_create/2
{
  "product": "Oranges",
  "category": "Fruit"
}

POST entity-a-idx/_create/3
{
  "product": "Potato",
  "category": null
}


DELETE /entity-b-idx
PUT /entity-b-idx
{
  "settings": {
    "index": {
      "number_of_shards": 2,
      "number_of_replicas": 1
    }
  },
  "mappings": {
    "properties": {
      "category": {
        "type": "keyword"
      }
    }
  }
}
POST entity-b-idx/_create/1
{
  "category": "Fruit"
}


POST _plugins/_sql/
{
  "query":"SELECT a.product, a.category, b.category from entity-a-idx as a left outer join entity-b-idx as b on a.category=b.category order by a.category"
}

What is the expected behavior?
No error is raised, right hand side is empty

What is your host/environment?

  • Windows
  • 3.1
  • SQL Plugin

Do you have any additional context?

[2025-09-22T08:57:06,373][WARN ][o.o.s.l.e.f.PrettyFormatRestExecutor] [opensearch-node1] Error happened in pretty formatter
java.lang.IllegalStateException: Error occurred during join query run
	at org.opensearch.sql.legacy.executor.join.ElasticJoinExecutor.run(ElasticJoinExecutor.java:97) ~[?:?]
	at org.opensearch.sql.legacy.executor.QueryActionElasticExecutor.executeJoinSearchAction(QueryActionElasticExecutor.java:47) ~[?:?]
	at org.opensearch.sql.legacy.executor.QueryActionElasticExecutor.executeAnyAction(QueryActionElasticExecutor.java:104) ~[?:?]
	at org.opensearch.sql.legacy.executor.format.PrettyFormatRestExecutor.execute(PrettyFormatRestExecutor.java:72) ~[?:?]
	at org.opensearch.sql.legacy.executor.format.PrettyFormatRestExecutor.execute(PrettyFormatRestExecutor.java:46) ~[?:?]
	at org.opensearch.sql.legacy.executor.AsyncRestExecutor.doExecuteWithTimeMeasured(AsyncRestExecutor.java:154) ~[?:?]
	at org.opensearch.sql.legacy.executor.AsyncRestExecutor.lambda$async$1(AsyncRestExecutor.java:110) ~[?:?]
	at org.opensearch.sql.common.utils.QueryContext.lambda$withCurrentContext$0(QueryContext.java:61) ~[?:?]
	at org.opensearch.common.util.concurrent.ThreadContext$ContextPreservingRunnable.run(ThreadContext.java:916) ~[opensearch-3.1.0.jar:3.1.0]
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144) ~[?:?]
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642) ~[?:?]
	at java.base/java.lang.Thread.run(Thread.java:1583) [?:?]
Caused by: java.lang.IllegalStateException: Error happened during execution
	at org.opensearch.sql.legacy.query.planner.physical.PhysicalPlan.execute(PhysicalPlan.java:60) ~[?:?]
	at org.opensearch.sql.legacy.query.planner.core.QueryPlanner.execute(QueryPlanner.java:67) ~[?:?]
	at org.opensearch.sql.legacy.executor.join.QueryPlanElasticExecutor.innerRun(QueryPlanElasticExecutor.java:30) ~[?:?]
	at org.opensearch.sql.legacy.executor.join.ElasticJoinExecutor.run(ElasticJoinExecutor.java:92) ~[?:?]
	... 11 more
Caused by: java.lang.IllegalStateException: Failed to prefetch next batch
	at org.opensearch.sql.legacy.query.planner.physical.node.BatchPhysicalOperator.prefetchSafely(BatchPhysicalOperator.java:74) ~[?:?]
	at org.opensearch.sql.legacy.query.planner.physical.node.BatchPhysicalOperator.hasNext(BatchPhysicalOperator.java:47) ~[?:?]
	at org.opensearch.sql.legacy.query.planner.logical.node.Top.hasNext(Top.java:41) ~[?:?]
	at org.opensearch.sql.legacy.query.planner.logical.node.Project.hasNext(Project.java:78) ~[?:?]
	at org.opensearch.sql.legacy.query.planner.physical.PhysicalPlan.doExecutePlan(PhysicalPlan.java:76) ~[?:?]
	at org.opensearch.sql.legacy.query.planner.physical.PhysicalPlan.execute(PhysicalPlan.java:56) ~[?:?]
	at org.opensearch.sql.legacy.query.planner.core.QueryPlanner.execute(QueryPlanner.java:67) ~[?:?]
	at org.opensearch.sql.legacy.executor.join.QueryPlanElasticExecutor.innerRun(QueryPlanElasticExecutor.java:30) ~[?:?]
	at org.opensearch.sql.legacy.executor.join.ElasticJoinExecutor.run(ElasticJoinExecutor.java:92) ~[?:?]
	... 11 more
Caused by: java.lang.NullPointerException: Cannot invoke "java.lang.Comparable.compareTo(Object)" because the return value of "java.util.function.Function.apply(Object)" is null
	at java.base/java.util.Comparator.lambda$comparing$77a9974f$1(Comparator.java:473) ~[?:?]
	at java.base/java.util.TimSort.countRunAndMakeAscending(TimSort.java:360) ~[?:?]
	at java.base/java.util.TimSort.sort(TimSort.java:220) ~[?:?]
	at java.base/java.util.Arrays.sort(Arrays.java:1308) ~[?:?]
	at java.base/java.util.ArrayList.sort(ArrayList.java:1804) ~[?:?]
	at org.opensearch.sql.legacy.query.planner.physical.node.sort.QuickSort.prefetch(QuickSort.java:75) ~[?:?]
	at org.opensearch.sql.legacy.query.planner.physical.node.BatchPhysicalOperator.prefetchSafely(BatchPhysicalOperator.java:72) ~[?:?]
	at org.opensearch.sql.legacy.query.planner.physical.node.BatchPhysicalOperator.hasNext(BatchPhysicalOperator.java:47) ~[?:?]
	at org.opensearch.sql.legacy.query.planner.logical.node.Top.hasNext(Top.java:41) ~[?:?]
	at org.opensearch.sql.legacy.query.planner.logical.node.Project.hasNext(Project.java:78) ~[?:?]
	at org.opensearch.sql.legacy.query.planner.physical.PhysicalPlan.doExecutePlan(PhysicalPlan.java:76) ~[?:?]
	at org.opensearch.sql.legacy.query.planner.physical.PhysicalPlan.execute(PhysicalPlan.java:56) ~[?:?]
	at org.opensearch.sql.legacy.query.planner.core.QueryPlanner.execute(QueryPlanner.java:67) ~[?:?]
	at org.opensearch.sql.legacy.executor.join.QueryPlanElasticExecutor.innerRun(QueryPlanElasticExecutor.java:30) ~[?:?]
	at org.opensearch.sql.legacy.executor.join.ElasticJoinExecutor.run(ElasticJoinExecutor.java:92) ~[?:?]
	... 11 more

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the SQL join from the issue, then inspect org.opensearch.sql.legacy.query.planner.physical.node.sort.QuickSort, where the stack trace identifies the failure. Verify the behavior with a null left-hand join field and ORDER BY; done means the query completes without a NullPointerException and returns an empty right-hand value for that row.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, sql
Domain
databases
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.