Skip to content

MERGE fails on a Partitioned Iceberg tables without duplicates with MERGE_TARGET_ROW_MULTIPLE_MATCHES #30639

Description

@akroptya

Trino version

483

Please describe the bug

Environment

  • Trino versions tested:
    - 479 - fails consistently
    - 482, 483 - fail consistently with join_distribution_type = 'PARTITIONED', often succeed without
  • Catalog: Iceberg (JDBC PostgreSQL)
  • File format: Parquet, Iceberg format-version 2
  • Engine path: dbt-trino or DBeaver

Summary

MERGE into a Partitioned Iceberg table fails with a false positive error, when neither the source table nor the target table contain duplicates on the merge key.

One MERGE target table row matched more than one source row

Reproduction

-- 1. Bucket-partitioned Iceberg target, 3,000 rows
CREATE TABLE repro_t WITH (partitioning = ARRAY['bucket(k1, 16)', 'bucket(k2, 8)']) AS
SELECT 'a-' || cast(i AS varchar) AS k1, 'b-' || cast(i AS varchar) AS k2,
       'UPDATED' AS a, TIMESTAMP '2026-01-01 00:00:00.000000' AS ts
FROM UNNEST(sequence(1, 3000)) t(i);

-- 2. Source: BOTH row classes are required —
--    3,000 rows matching target pairs with EQUAL timestamps (blocked by the update guard)
--    + 42,000 unmatched rows with a = 'DELETED' (blocked by the insert condition)
CREATE TABLE repro_s AS
SELECT 'a-' || cast(i AS varchar) AS k1, 'b-' || cast(i AS varchar) AS k2,
       'UPDATED' AS a, TIMESTAMP '2026-01-01 00:00:00.000000' AS ts
FROM UNNEST(sequence(1, 3000)) t(i)
UNION ALL
SELECT 'x-' || cast(a.i AS varchar) || '-' || cast(b.j AS varchar),
       'y-' || cast(a.i AS varchar) || '-' || cast(b.j AS varchar),
       'DELETED', TIMESTAMP '2026-01-02 00:00:00.000000'
FROM UNNEST(sequence(1, 6000)) a(i) CROSS JOIN UNNEST(sequence(1, 7)) b(j);

-- no duplicates
select k1, k2 from repro_s group by  k1, k2 having count(*) > 1;
select k1, k2 from repro_t group by  k1, k2 having count(*) > 1;

-- 3. Force the flipped merge-join plan (multi-node optimizers pick it unforced)
SET SESSION join_distribution_type = 'PARTITIONED';

-- 4. The merge: every row is blocked by a condition, so the CORRECT result is
--    "MERGE: 0 rows". Instead it fails:
--    "One MERGE target table row matched more than one source row"
MERGE INTO repro_t t USING repro_s s
  ON t.k1 = s.k1 AND t.k2 = s.k2
WHEN MATCHED AND s.ts > t.ts THEN UPDATE SET a = s.a, ts = s.ts
WHEN NOT MATCHED AND s.a <> 'DELETED' THEN INSERT (k1, k2, a, ts) VALUES (s.k1, s.k2, s.a, s.ts);

Stack trace (failed run, Trino 483)

io.trino.spi.TrinoException: One MERGE target table row matched more than one source row
	at io.trino.operator.scalar.FailureFunction.fail(FailureFunction.java:62)
	at io.trino.$gen.PageFilter_20260810_092523_242.filter(Unknown Source)
	at io.trino.$gen.PageFilter_20260810_092523_242.filter(Unknown Source)
	at io.trino.operator.project.DictionaryAwarePageFilter.filter(DictionaryAwarePageFilter.java:82)
	at io.trino.sql.gen.columnar.PageFilterEvaluator.evaluate(PageFilterEvaluator.java:42)
	at io.trino.operator.project.PageProcessor.createWorkProcessor(PageProcessor.java:123)
	at io.trino.operator.FilterAndProjectOperator.lambda$new$0(FilterAndProjectOperator.java:58)
	at io.trino.operator.WorkProcessorUtils.lambda$flatMap$0(WorkProcessorUtils.java:285)
	at io.trino.operator.WorkProcessorUtils$3.process(WorkProcessorUtils.java:359)
	at io.trino.operator.WorkProcessorUtils$ProcessWorkProcessor.process(WorkProcessorUtils.java:426)
	at io.trino.operator.WorkProcessorUtils$3.process(WorkProcessorUtils.java:346)
	at io.trino.operator.WorkProcessorUtils$ProcessWorkProcessor.process(WorkProcessorUtils.java:426)
	at io.trino.operator.WorkProcessorUtils$3.process(WorkProcessorUtils.java:346)
	at io.trino.operator.WorkProcessorUtils$ProcessWorkProcessor.process(WorkProcessorUtils.java:426)
	at io.trino.operator.WorkProcessorUtils.getNextState(WorkProcessorUtils.java:261)
	at io.trino.operator.WorkProcessorUtils$BlockingProcess.process(WorkProcessorUtils.java:207)
	at io.trino.operator.WorkProcessorUtils$ProcessWorkProcessor.process(WorkProcessorUtils.java:426)
	at io.trino.operator.WorkProcessorOperatorAdapter.getOutput(WorkProcessorOperatorAdapter.java:139)
	at io.trino.operator.Driver.processInternal(Driver.java:403)
	at io.trino.operator.Driver.lambda$process$0(Driver.java:306)
	at io.trino.operator.Driver.tryWithLock(Driver.java:709)
	at io.trino.operator.Driver.process(Driver.java:298)
	at io.trino.operator.Driver.processForDuration(Driver.java:269)
	at io.trino.execution.SqlTaskExecution$DriverSplitRunner.processFor(SqlTaskExecution.java:852)
	at io.trino.execution.executor.dedicated.SplitProcessor.run(SplitProcessor.java:77)
	at io.trino.execution.executor.dedicated.TaskEntry$VersionEmbedderBridge.lambda$run$0(TaskEntry.java:205)
	at io.trino.$gen.Trino_483____20260810_091753_2.run(Unknown Source)
	at io.trino.execution.executor.dedicated.TaskEntry$VersionEmbedderBridge.run(TaskEntry.java:206)
	at io.trino.execution.executor.scheduler.FairScheduler.runTask(FairScheduler.java:177)
	at io.trino.execution.executor.scheduler.FairScheduler.lambda$submit$0(FairScheduler.java:164)
	at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:545)
	at com.google.common.util.concurrent.TrustedListenableFutureTask$TrustedFutureInterruptibleTask.runInterruptibly(TrustedListenableFutureTask.java:128)
	at com.google.common.util.concurrent.InterruptibleTask.run(InterruptibleTask.java:74)
	at com.google.common.util.concurrent.TrustedListenableFutureTask.run(TrustedListenableFutureTask.java:80)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1090)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:614)
	at java.base/java.lang.Thread.run(Thread.java:1474)

20260810_093132_00028_h7f4w.json

MERGE produces duplicate rows in Iceberg table (regression in Trino 477) #26968 is fixed before the Trino version we face an issue on.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions