From a9d12207ae5dcf7fbf54d5876309b911bb252959 Mon Sep 17 00:00:00 2001 From: bobhan1 Date: Tue, 30 Jun 2026 16:37:50 +0800 Subject: [PATCH] [fix](fe) Handle generated columns in delete partial update (#64884) Issue Number: N/A Related PR: N/A Original Problem: DELETE failed during analysis on a unique merge-on-write table that contains a generated column when light delete is disabled. The original reported table had a generated VARIANT column: ```sql new_col variant AS (receive_address_detail) NULL ``` and the DELETE failed with: ```text errCode = 2, detailMessage = cannot find column from target table /receive_address_detail ``` A minimized reproduction is: ```sql create table test_gen_col_mow_delete_partial_update ( a int, b int, c int as (b + 1), d int ) unique key(a) distributed by hash(a) buckets 1 properties ( "enable_unique_key_merge_on_write" = "true", "enable_mow_light_delete" = "false", "replication_num" = "1" ); ``` After inserting rows, running: ```sql delete from test_gen_col_mow_delete_partial_update where a = 1; ``` failed with: ```text java.sql.SQLException: errCode = 2, detailMessage = cannot find column from target table [b] ``` Problem Summary: DELETE on a unique merge-on-write table with light delete disabled is rewritten to a partial update load that writes key columns and the delete sign. BindSink used to auto-add every generated column and then recompute omitted generated columns, which required ordinary value columns that were not part of the DELETE output and caused analysis to fail. This change treats DELETE partial updates specially: generated columns that are not emitted by the child plan are skipped, and generated columns that are emitted by the child plan use that child output directly. Normal partial update generated-column dependency checks are unchanged. The tests cover generated value columns, generated key columns, a generated value-column table with an omitted NOT NULL value column that has no default value, and the original VARIANT generated-column shape based on `receive_address_detail`. Fix DELETE failure on unique merge-on-write tables with generated columns when light delete is disabled. - Test: Regression test / Unit Test - `./build.sh --fe -j100` - `./run-fe-ut.sh --run org.apache.doris.nereids.trees.plans.DeleteFromUsingCommandTest` - `./run-regression-test.sh --run -d regression-test/suites/ddl_p0/test_create_table_generated_column -s test_generated_column_delete -forceGenOut` - `./run-regression-test.sh --run -d regression-test/suites/ddl_p0/test_create_table_generated_column -s test_generated_column_delete` - `./run-regression-test.sh --run -d regression-test/suites/ddl_p0/test_create_table_generated_column -s test_partial_update_generated_column` - Behavior changed: Yes (DELETE now succeeds for unique merge-on-write tables with generated columns when light delete is disabled.) - Does this need documentation: No (cherry picked from commit c02808aae79b29e8c02e48f06baf89b43d4c4152) --- .../nereids/rules/analysis/BindSink.java | 28 ++++-- .../plans/DeleteFromUsingCommandTest.java | 89 +++++++++++++++++ .../test_delete_generated_column.out | 12 +++ .../test_delete_generated_column.groovy | 96 +++++++++++++++++++ 4 files changed, 218 insertions(+), 7 deletions(-) diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindSink.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindSink.java index d04b63031d8684..e398d6f905a51d 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindSink.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindSink.java @@ -159,6 +159,7 @@ private Plan bindOlapTableSink(MatchingContext> ctx) { Database database = pair.first; OlapTable table = pair.second; boolean isPartialUpdate = sink.isPartialUpdate() && table.getKeysType() == KeysType.UNIQUE_KEYS; + boolean isDeletePartialUpdate = isPartialUpdate && sink.getDMLCommandType() == DMLCommandType.DELETE; TPartialUpdateNewRowPolicy partialUpdateNewKeyPolicy = sink.getPartialUpdateNewRowPolicy(); LogicalPlan child = ((LogicalPlan) sink.child()); @@ -170,7 +171,7 @@ private Plan bindOlapTableSink(MatchingContext> ctx) { // 1. bind target columns: from sink's column names to target tables' Columns Pair, Integer> bindColumnsResult = bindTargetColumns(table, sink.getColNames(), childHasSeqCol, needExtraSeqCol, - sink.getDMLCommandType() == DMLCommandType.GROUP_COMMIT); + sink.getDMLCommandType() == DMLCommandType.GROUP_COMMIT, isDeletePartialUpdate); List bindColumns = bindColumnsResult.first; int extraColumnsNum = bindColumnsResult.second; @@ -255,7 +256,7 @@ private Plan bindOlapTableSink(MatchingContext> ctx) { } Map columnToOutput = getColumnToOutput( - ctx, table, isPartialUpdate, boundSink, child); + ctx, table, isPartialUpdate, isDeletePartialUpdate, boundSink, child); LogicalProject fullOutputProject = getOutputProjectByCoercion( table.getFullSchema(), child, columnToOutput); List columns = new ArrayList<>(table.getFullSchema().size()); @@ -347,7 +348,8 @@ private LogicalProject getOutputProjectByCoercion(List tableSchema, L private static Map getColumnToOutput( MatchingContext> ctx, - TableIf table, boolean isPartialUpdate, LogicalTableSink boundSink, LogicalPlan child) { + TableIf table, boolean isPartialUpdate, boolean isDeletePartialUpdate, + LogicalTableSink boundSink, LogicalPlan child) { // we need to insert all the columns of the target table // although some columns are not mentions. // so we add a projects to supply the default value. @@ -470,6 +472,18 @@ private static Map getColumnToOutput( // if processed in upper for loop, will lead to not found slot error // It's the same reason for moving the processing of materialized columns down. for (Column column : generatedColumns) { + if (isDeletePartialUpdate) { + NamedExpression childOutput = columnToChildOutput.get(column); + if (childOutput == null) { + continue; + } + Alias output = new Alias(TypeCoercionUtils.castIfNotSameType( + childOutput, DataType.fromCatalogType(column.getType())), column.getName()); + columnToOutput.put(column.getName(), output); + columnToReplaced.put(column.getName(), output.toSlot()); + replaceMap.put(output.toSlot(), output.child()); + continue; + } Map currentSessionVars = ctx.connectContext.getSessionVariable().getAffectQueryResultInPlanVariables(); try (AutoCloseSessionVariable autoClose = new AutoCloseSessionVariable(ctx.connectContext, @@ -594,7 +608,7 @@ private Plan bindHiveTableSink(MatchingContext> ctx) if (boundSink.getCols().size() != child.getOutput().size()) { throw new AnalysisException("insert into cols should be corresponding to the query output"); } - Map columnToOutput = getColumnToOutput(ctx, table, false, + Map columnToOutput = getColumnToOutput(ctx, table, false, false, boundSink, child); LogicalProject fullOutputProject = getOutputProjectByCoercion(table.getFullSchema(), child, columnToOutput); return boundSink.withChildAndUpdateOutput(fullOutputProject); @@ -635,7 +649,7 @@ private Plan bindIcebergTableSink(MatchingContext> if (boundSink.getCols().size() != child.getOutput().size()) { throw new AnalysisException("insert into cols should be corresponding to the query output"); } - Map columnToOutput = getColumnToOutput(ctx, table, false, + Map columnToOutput = getColumnToOutput(ctx, table, false, false, boundSink, child); LogicalProject fullOutputProject = getOutputProjectByCoercion(table.getFullSchema(), child, columnToOutput); return boundSink.withChildAndUpdateOutput(fullOutputProject); @@ -843,7 +857,7 @@ private List bindPartitionIds(OlapTable table, List partitions, bo // bindTargetColumns means bind sink node's target columns' names to target table's columns private Pair, Integer> bindTargetColumns(OlapTable table, List colsName, - boolean childHasSeqCol, boolean needExtraSeqCol, boolean isGroupCommit) { + boolean childHasSeqCol, boolean needExtraSeqCol, boolean isGroupCommit, boolean isDeletePartialUpdate) { // if the table set sequence column in stream load phase, the sequence map column is null, we query it. if (colsName.isEmpty()) { // ATTN: group commit without column list should return all base index column @@ -861,7 +875,7 @@ private Pair, Integer> bindTargetColumns(OlapTable table, List