Skip to content

Conversation

@SemionPar
Copy link
Contributor

@SemionPar SemionPar commented Apr 30, 2025

Description

Fix "joinConditions is empty" error.

Additional context and related issues

Bisected to 91a29a7

SELECT a.c_num FROM left a LEFT JOIN right b ON DATE '2025-03-19' = b.c_date;

Query of this shape results in

Caused by: com.google.common.base.VerifyException: joinConditions is empty
	at com.google.common.base.Verify.verify(Verify.java:126)
	at io.trino.plugin.jdbc.DefaultQueryBuilder.prepareJoinQuery(DefaultQueryBuilder.java:126)
	at io.trino.plugin.jdbc.BaseJdbcClient.implementJoin(BaseJdbcClient.java:537)
	at io.trino.plugin.postgresql.PostgreSqlClient.lambda$implementJoin$13(PostgreSqlClient.java:1074)
	at io.trino.plugin.jdbc.JdbcJoinPushdownUtil.implementJoinCostAware(JdbcJoinPushdownUtil.java:46)
	at io.trino.plugin.postgresql.PostgreSqlClient.implementJoin(PostgreSqlClient.java:1068)
	at io.trino.plugin.jdbc.ForwardingJdbcClient.implementJoin(ForwardingJdbcClient.java:220)
	at io.trino.plugin.jdbc.jmx.StatisticsAwareJdbcClient.lambda$implementJoin$20(StatisticsAwareJdbcClient.java:239)
	at io.trino.plugin.jdbc.jmx.JdbcApiStats.wrap(JdbcApiStats.java:34)
	at io.trino.plugin.jdbc.jmx.StatisticsAwareJdbcClient.implementJoin(StatisticsAwareJdbcClient.java:239)
	at io.trino.plugin.jdbc.CachingJdbcClient.implementJoin(CachingJdbcClient.java:293)
	at io.trino.plugin.jdbc.CachingJdbcClient.implementJoin(CachingJdbcClient.java:293)
	at io.trino.plugin.jdbc.DefaultJdbcMetadata.applyJoin(DefaultJdbcMetadata.java:497)
	at io.trino.plugin.base.classloader.ClassLoaderSafeConnectorMetadata.applyJoin(ClassLoaderSafeConnectorMetadata.java:1042)
	at io.trino.tracing.TracingConnectorMetadata.applyJoin(TracingConnectorMetadata.java:1206)
	at io.trino.metadata.MetadataManager.applyJoin(MetadataManager.java:1957)
	at io.trino.tracing.TracingMetadata.applyJoin(TracingMetadata.java:988)
	at io.trino.sql.planner.iterative.rule.PushJoinIntoTableScan.apply(PushJoinIntoTableScan.java:149)
	at io.trino.sql.planner.iterative.rule.PushJoinIntoTableScan.apply(PushJoinIntoTableScan.java:68)
	at io.trino.sql.planner.iterative.IterativeOptimizer.transform(IterativeOptimizer.java:209)
	at io.trino.sql.planner.iterative.IterativeOptimizer.exploreNode(IterativeOptimizer.java:176)
	at io.trino.sql.planner.iterative.IterativeOptimizer.exploreGroup(IterativeOptimizer.java:139)
	at io.trino.sql.planner.iterative.IterativeOptimizer.exploreChildren(IterativeOptimizer.java:259)
	at io.trino.sql.planner.iterative.IterativeOptimizer.exploreGroup(IterativeOptimizer.java:141)
	at io.trino.sql.planner.iterative.IterativeOptimizer.optimize(IterativeOptimizer.java:123)
	at io.trino.sql.planner.LogicalPlanner.runOptimizer(LogicalPlanner.java:309)
	at io.trino.sql.planner.LogicalPlanner.plan(LogicalPlanner.java:270)
	at io.trino.sql.planner.LogicalPlanner.plan(LogicalPlanner.java:239)
	at io.trino.sql.planner.LogicalPlanner.plan(LogicalPlanner.java:234)
	at io.trino.execution.SqlQueryExecution.doPlanQuery(SqlQueryExecution.java:486)
	at io.trino.execution.SqlQueryExecution.planQuery(SqlQueryExecution.java:466)
	at io.trino.execution.SqlQueryExecution.start(SqlQueryExecution.java:404)
	at io.trino.execution.SqlQueryManager.createQuery(SqlQueryManager.java:264)
	at io.trino.dispatcher.LocalDispatchQuery.startExecution(LocalDispatchQuery.java:145)
	at io.trino.dispatcher.LocalDispatchQuery.lambda$waitForMinimumWorkers$2(LocalDispatchQuery.java:129)
	at io.airlift.concurrent.MoreFutures.lambda$addSuccessCallback$12(MoreFutures.java:568)
	at io.airlift.concurrent.MoreFutures$3.onSuccess(MoreFutures.java:543)
	at com.google.common.util.concurrent.Futures$CallbackListener.run(Futures.java:1133)
	at io.trino.$gen.Trino_testversion____20250430_152414_75.run(Unknown Source)
	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)

Joins with effectively empty filters should not be pushed down.

Release notes

( ) This is not user-visible or is docs only, and no release notes are required.
( ) Release notes are required. Please propose a release note for me.
( ) Release notes are required, with the following suggested text:

## Section
* Fix `joinConditions is empty` error happening for certain query shapes.

@cla-bot cla-bot bot added the cla-signed label Apr 30, 2025
@github-actions github-actions bot added the postgresql PostgreSQL connector label Apr 30, 2025
@SemionPar SemionPar force-pushed the semionpar/empty-join-condition branch from 7c045a9 to 3c044a2 Compare April 30, 2025 16:11
@SemionPar SemionPar force-pushed the semionpar/empty-join-condition branch from 3c044a2 to 884346b Compare May 13, 2025 12:12
@SemionPar SemionPar changed the title [wip] Fix empty join conditions query failure Fix empty join conditions query failure May 13, 2025
@SemionPar SemionPar marked this pull request as ready for review May 13, 2025 15:53
@ebyhr ebyhr requested review from Praveen2112 and kasiafi May 13, 2025 21:44
@SemionPar SemionPar force-pushed the semionpar/empty-join-condition branch 2 times, most recently from 8a28693 to 60003a5 Compare May 15, 2025 13:39
@SemionPar SemionPar force-pushed the semionpar/empty-join-condition branch 2 times, most recently from 16b962a to f43bce5 Compare May 19, 2025 12:01
@SemionPar
Copy link
Contributor Author

Oracle ci failure is unrelated, PTAL @Praveen2112

@SemionPar SemionPar force-pushed the semionpar/empty-join-condition branch from f43bce5 to b872e5a Compare May 20, 2025 08:21
@SemionPar SemionPar requested a review from Praveen2112 May 20, 2025 12:19
Copy link
Member

@Praveen2112 Praveen2112 left a comment

Choose a reason for hiding this comment

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

% nit

@SemionPar SemionPar force-pushed the semionpar/empty-join-condition branch 2 times, most recently from 38e6564 to 5a326fd Compare May 26, 2025 08:05
@SemionPar
Copy link
Contributor Author

Fixed the nit and rebased - @Praveen2112 please merge once CI is done

@SemionPar SemionPar force-pushed the semionpar/empty-join-condition branch from 5a326fd to 5555be7 Compare May 26, 2025 13:22
@SemionPar SemionPar force-pushed the semionpar/empty-join-condition branch from 5555be7 to aaf3194 Compare May 27, 2025 07:41
Copy link
Member

@Praveen2112 Praveen2112 left a comment

Choose a reason for hiding this comment

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

I have one question on what’s special for LEFT and FULL join


protected static boolean expectJoinPushdownOnEmptyProjection(JoinOperator joinOperator)
{
return joinOperator == LEFT_JOIN || joinOperator == FULL_JOIN;
Copy link
Member

Choose a reason for hiding this comment

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

Any specific reason on pushdown is applied for LEFT or FULL join ?

Copy link
Contributor Author

@SemionPar SemionPar May 28, 2025

Choose a reason for hiding this comment

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

This 'decorative' join query shape looks slightly different for different join operators, e.g. the join filter is retained in the join node for the LEFT JOIN:

SELECT n.name FROM postgresql.tpch.nation n LEFT JOIN postgresql.tpch.orders o ON n.regionkey = 1;

LeftJoin[filter = (regionkey = bigint '1'), distribution = REPLICATED]
│   Layout: [name:varchar(25)]
│   Distribution: REPLICATED
├─ TableScan[table = postgresql:tpch.nation tpch.tpch.nation columns=[name:varchar(25):varchar, regionkey:bigint:int8]]
│      Layout: [name:varchar(25), regionkey:bigint]
└─ LocalExchange[partitioning = SINGLE]
  └─ RemoteSource[sourceFragmentIds = [1]]

Fragment 1 [SOURCE]
    Output partitioning: BROADCAST []
    TableScan[table = postgresql:tpch.orders tpch.tpch.orders columns=[]]
        Layout: []

On the other hand, for RIGHT JOIN, condition is pushed down into constraint on the scan:

SELECT n.name FROM postgresql.tpch.nation n RIGHT JOIN postgresql.tpch.orders o ON n.regionkey = 1

RightJoin[distribution = PARTITIONED]
│   Layout: [name:varchar(25)]
│   Distribution: PARTITIONED
├─ RemoteSource[sourceFragmentIds = [1]]
│      Layout: [name:varchar(25)]
└─ LocalExchange[partitioning = SINGLE]
  │   Layout: []
  └─ RemoteSource[sourceFragmentIds = [2]]
         Layout: []

Fragment 1 [SOURCE]
    Output partitioning: HASH []
    TableScan[table = postgresql:tpch.nation tpch.tpch.nation constraint on [regionkey] columns=[name:varchar(25):varchar]]
        Layout: [name:varchar(25)]

Fragment 2 [SOURCE]
    Output partitioning: HASH []
    TableScan[table = postgresql:tpch.orders tpch.tpch.orders columns=[]]
        Layout: []

There is actually no join condition to push down, so applyJoin returns empty-handed.

@Praveen2112 Praveen2112 merged commit 1f54957 into trinodb:master May 30, 2025
63 checks passed
@Praveen2112
Copy link
Member

@SemionPar Can you please update the release notes.

@github-actions github-actions bot added this to the 476 milestone May 30, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla-signed postgresql PostgreSQL connector

Development

Successfully merging this pull request may close these issues.

4 participants