Skip to content

HIVE-29816: Iceberg: MERGE INTO with WHEN MATCHED THEN DELETE incorrectly duplicates rows under Copy-On-Write delete mode - #6701

Merged
deniskuzZ merged 7 commits into
apache:masterfrom
Aggarwal-Raghav:HIVE-29816
Sep 13, 2026
Merged

HIVE-29816: Iceberg: MERGE INTO with WHEN MATCHED THEN DELETE incorrectly duplicates rows under Copy-On-Write delete mode#6701
deniskuzZ merged 7 commits into
apache:masterfrom
Aggarwal-Raghav:HIVE-29816

Conversation

@Aggarwal-Raghav

Copy link
Copy Markdown
Contributor

What changes were proposed in this pull request?

When write.delete.mode=copy-on-write and write.merge.mode=merge-on-read (default), running a MERGE INTO ... WHEN MATCHED THEN DELETE query incorrectly causes the writer to use COW instead of MOR causing duplicate rows instead of deleting the rows.

Added an isMergeStatement boolean flag to FileSinkDesc and propagated it to the Hadoop JobConf during FileSinkOperator initialization via HiveCustomStorageHandlerUtils.setIsMergeStatement

Why are the changes needed?

Check HIVE-29816

Does this PR introduce any user-facing change?

Yes, executing MERGE INTO... DELETE query on tables utilizing Copy-On-Write delete modes will no longer experience data duplication and sequence number increments.

How was this patch tested?

Using, q files and on spark

@Aggarwal-Raghav

Copy link
Copy Markdown
Contributor Author

BEFORE FIX HIVE:
Screenshot 2026-08-15 at 12 19 13 AM

AFTER FIX HIVE:
Screenshot 2026-08-15 at 12 34 13 AM

SPARK:
Screenshot 2026-08-15 at 12 21 17 AM

@Aggarwal-Raghav

Copy link
Copy Markdown
Contributor Author

@ayushtkn , can you help with review?

@Aggarwal-Raghav

Copy link
Copy Markdown
Contributor Author

@deniskuzZ , can you please help with review?

@deniskuzZ

Copy link
Copy Markdown
Member

@Aggarwal-Raghav, sure, please give me some time

@deniskuzZ
deniskuzZ self-requested a review August 26, 2026 11:12
@Aggarwal-Raghav

Copy link
Copy Markdown
Contributor Author

@Aggarwal-Raghav, sure, please give me some time

understood

Comment thread ql/src/java/org/apache/hadoop/hive/ql/exec/FileSinkOperator.java Outdated
Comment on lines +643 to +644
setWriteOperation(jc, getConf().getTableInfo().getTableName(), getConf().getWriteOperation());
setStatementOperation(jc, getConf().getTableInfo().getTableName(), getConf().getStatementOperation());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why are both WriteOperation and StatementOperation needed? Aren't they the same?

@Aggarwal-Raghav Aggarwal-Raghav Aug 31, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hey @kasakrisz ,

StatementOperation represents the top-level intent during query compilation. For example: If a MERGE query is fired from Beeline, then StatementOperation = MERGE.

During Query execution, if the table is configured with write.merge.mode=COW and write.delete.mode=MOR, then MERGE query is splitted into INSERT and DELETE Branch (writeOperation = DELETE) and its incorrectly picking write.delete.mode instead of write.merge.mode.

The aim of this PR is to pass the top-level query operation (StatementOperation) from the compilation layer down to the execution layer so it honor correct iceberg table property.

@deniskuzZ deniskuzZ Sep 2, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@kasakrisz do you have in mind alternative solution?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ctx.getOperation() and queryState.getHiveOperation() will not match because for MERGE, Update or SELECT queryState.getHiveOperation() will return HiveOperation.QUERY.

queryState.getHiveOperation() is used extensively in LineageLogger (I've worked on that previously).

To test it I added this in SemanticAnalyzer#createFileSinkDesc() and re-ran the iceberg_mixed_write_modes.q file

LOG.info("TESTING_OPERATIONS: ctx.getOperation() = " + ctx.getOperation() + ", queryState.getHiveOperation() = " + queryState.getHiveOperation());

Output:
Screenshot 2026-09-03 at 12 07 05 AM

boolean isCOW = IcebergTableUtil.isCopyOnWriteMode(operation, table.properties()::getOrDefault);
boolean isCOW =
IcebergTableUtil.isCopyOnWriteMode(
statementOperation != null ? statementOperation : writeOperation,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ObjectUtils.defaultIfNull(statementOperation, writeOperation) ?

}
boolean isCOW = IcebergTableUtil.isCopyOnWriteMode(operation, conf::get);
boolean isCOW = IcebergTableUtil.isCopyOnWriteMode(
statementOperation != null ? statementOperation : writeOperation, conf::get);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

same.

@deniskuzZ deniskuzZ left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM, some minor things

- Revert `writeOperation` back to `operation` in WriterBuilder and HiveIcebergSerDe
- Use `ObjectUtils.defaultIfNull` instead of ternary operators.
- Extract `operation` fallback checks into local variables to improve readability and avoid spotless line wrapping.
- Update imports to use `Operation` directly instead of `Context.Operation`.
@Aggarwal-Raghav

Copy link
Copy Markdown
Contributor Author

rebased with master due to merge conflict becuase of HIVE-29815

private TaskAttemptID attemptID;
private String queryId;
private Operation operation;
private final boolean isCopyOnWrite;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: AFAIK the boolean field name doesn't need the is prefix. The getter has it.

field: boolean copyOnWrite;
getter: isCopyOnWrite()
setter: setCopyOnWrite(boolean)

private Path destPath;
private boolean isHiveServerQuery;
private boolean isMerge;
private boolean isCopyOnWrite = false;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit.

field: boolean copyOnWrite;
getter: isCopyOnWrite()
setter: setCopyOnWrite(boolean)

See other boolean fields

private boolean multiFileSpray;
private boolean temporary;
private boolean materialization;

public boolean isMultiFileSpray() {
return multiFileSpray;
}
/**
* @param multiFileSpray the multiFileSpray to set
*/
public void setMultiFileSpray(boolean multiFileSpray) {
this.multiFileSpray = multiFileSpray;
}
/**
* @return destination is temporary
*/
public boolean isTemporary() {
return temporary;
}
public void setTemporary(boolean temporary) {
this.temporary = temporary;
}

return Boolean.parseBoolean(operation);
}

public static void setIsCopyOnWrite(Configuration conf, String tableName, boolean isCopyOnWrite) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit.: setCopyOnWrite

@kasakrisz kasakrisz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

+1, pending tests

@Aggarwal-Raghav

Copy link
Copy Markdown
Contributor Author
Screenshot 2026-09-11 at 12 24 06 AM 2 un-related UT failure. re-triggering CI

@sonarqubecloud

Copy link
Copy Markdown

@deniskuzZ
deniskuzZ merged commit db55599 into apache:master Sep 13, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants