This is an automated email from the ASF dual-hosted git repository.
bobhan1 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/master by this push:
new 895cbec74a6 [fix](update) Include generated column dependencies in
updates (#67348)
895cbec74a6 is described below
commit 895cbec74a68d050d791a117695969b743ee7552
Author: bobhan1 <[email protected]>
AuthorDate: Wed Oct 14 15:13:41 2026 +0800
[fix](update) Include generated column dependencies in updates (#67348)
### What problem does this PR solve?
Issue Number: N/A
Related PR: N/A
Problem Summary: UPDATE statements on merge-on-write unique tables can
be planned as partial updates when only a small number of columns are
assigned. The partial-update projection previously included only key and
assigned columns, while BindSink recomputes generated columns and
requires their ordinary source columns. Updating an unrelated column
therefore failed during analysis when a generated-column dependency was
omitted.
This change includes unassigned ordinary columns referenced by generated
columns in the UPDATE partial-update projection. An unassigned
dependency with an ON UPDATE value is deliberately left out: BindSink
already supplies its ON UPDATE expression and then uses that new value
to recompute the generated column. Explicit assignments remain in the
projection.
### Original reproduction
```sql
CREATE TABLE t (
a INT,
b INT,
c INT AS (b + 1),
d INT
)
UNIQUE KEY(a)
DISTRIBUTED BY HASH(a) BUCKETS 1
PROPERTIES (
"replication_num" = "1",
"enable_unique_key_merge_on_write" = "true"
);
INSERT INTO t(a, b, d) VALUES (1, 10, 100);
UPDATE t SET d = 999 WHERE a = 1;
```
Before this change, the UPDATE failed during FE analysis:
```text
cannot find column from target table [b]
```
The row remained `1 | 10 | 11 | 100`. After this change, the UPDATE
succeeds and the row becomes `1 | 10 | 11 | 999`.
### ON UPDATE dependency found during review
```sql
CREATE TABLE t_on_update (
id INT,
ts DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
g INT AS (YEAR(ts)),
v INT
)
UNIQUE KEY(id)
DISTRIBUTED BY HASH(id) BUCKETS 1
PROPERTIES (
"replication_num" = "1",
"enable_unique_key_merge_on_write" = "true"
);
INSERT INTO t_on_update(id, ts, v) VALUES (1, '2000-01-01 00:00:00', 100);
UPDATE t_on_update SET v = 999 WHERE id = 1;
```
Projecting the stored value of `ts` would make BindSink treat it as
explicitly supplied and bypass `ON UPDATE CURRENT_TIMESTAMP`. The final
implementation omits that unassigned dependency, so `ts` advances, `g =
YEAR(ts)` remains consistent with the new timestamp, and `v` becomes
`999`.
Generated columns can reference generated columns defined before them.
This PR keeps those generated values out of the partial projection and
supplies their ordinary leaf dependencies; the existing BindSink path
recomputes the generated-column chain in schema order.
### Release note
Fix UPDATE failures on merge-on-write unique tables with generated
columns while preserving automatic ON UPDATE values used by generated
columns.
### Check List (For Author)
- Test
- [x] Regression test
- [x] Unit Test
- [ ] Manual test (add detailed scripts or steps below)
- [ ] No need to test or manual test. Explain why:
- [ ] This is a refactor/code format and no logic has been changed.
- [ ] Previous test can cover this change.
- [ ] No code files have been changed.
- [ ] Other reason
Commands run:
- `./build.sh --fe -j100`
- `./build.sh --be -j100`
- `./run-fe-ut.sh --run
org.apache.doris.nereids.trees.plans.UpdateCommandTest`
- `./run-regression-test.sh --run -d
ddl_p0/test_create_table_generated_column -s
test_update_generated_column_dependency`
- `./run-regression-test.sh --run -d
ddl_p0/test_create_table_generated_column -s
test_partial_update_generated_column`
- Behavior changed:
- [ ] No.
- [x] Yes. UPDATE partial projections include ordinary generated-column
dependencies while unassigned ON UPDATE dependencies continue through
BindSink's automatic-value path.
- Does this need documentation?
- [x] No.
- [ ] Yes.
### Check List (For Reviewer who merge this PR)
- [ ] Confirm the release note
- [ ] Confirm test cases
- [ ] Confirm document
- [ ] Add branch pick label
---
.../trees/plans/commands/UpdateCommand.java | 7 ++-
.../nereids/trees/plans/UpdateCommandTest.java | 33 ++++++++++++
.../test_update_generated_column_dependency.out | 7 +++
.../test_update_generated_column_dependency.groovy | 58 ++++++++++++++++++++++
4 files changed, 104 insertions(+), 1 deletion(-)
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/UpdateCommand.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/UpdateCommand.java
index aa6853cea9f..e030d010234 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/UpdateCommand.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/UpdateCommand.java
@@ -52,6 +52,7 @@ import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
+import org.apache.commons.collections4.CollectionUtils;
import java.util.ArrayList;
import java.util.List;
@@ -197,7 +198,11 @@ public class UpdateCommand extends Command implements
ForwardWithSync, Explainab
break;
}
}
- if (column.isKey() || existInExpr) {
+ boolean isGeneratedColumnDependency =
column.getGeneratedColumnInfo() == null
+ &&
CollectionUtils.isNotEmpty(column.getGeneratedColumnsThatReferToThis());
+ boolean shouldReadGeneratedColumnDependency =
isGeneratedColumnDependency
+ && !column.hasOnUpdateDefaultValue();
+ if (column.isKey() || existInExpr ||
shouldReadGeneratedColumnDependency) {
partialUpdateSelectItems.add(expr instanceof UnboundSlot
? ((NamedExpression) expr)
: new UnboundAlias(expr));
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/UpdateCommandTest.java
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/UpdateCommandTest.java
index 4151115a2c8..2ed79848fa7 100644
---
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/UpdateCommandTest.java
+++
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/UpdateCommandTest.java
@@ -66,6 +66,18 @@ public class UpdateCommandTest extends TestWithFeService
implements PlanPatternM
+ "properties(\n"
+ " \"replication_num\"=\"1\"\n"
+ ")");
+ createTable("create table generated_column_table (\n"
+ + " k1 int,\n"
+ + " v1 int,\n"
+ + " v2 int as (v1 + 1),\n"
+ + " v3 int\n"
+ + ")\n"
+ + "unique key(k1)\n"
+ + "distributed by hash(k1) buckets 4\n"
+ + "properties(\n"
+ + " \"replication_num\"=\"1\",\n"
+ + " \"enable_unique_key_merge_on_write\"=\"true\"\n"
+ + ")");
}
@Test
@@ -117,4 +129,25 @@ public class UpdateCommandTest extends TestWithFeService
implements PlanPatternM
)
);
}
+
+ @Test
+ public void testPartialUpdateWithGeneratedColumnDependency() {
+ String sql = "update generated_column_table set v3 = 999 where k1 = 1";
+ LogicalPlan parsed = new NereidsParser().parseSingle(sql);
+ Assertions.assertInstanceOf(UpdateCommand.class, parsed);
+ UpdateCommand command = ((UpdateCommand) parsed);
+ LogicalPlan plan = command.completeQueryPlan(connectContext,
command.getLogicalQuery());
+ PlanChecker.from(connectContext, plan)
+ .analyze(plan)
+ .rewrite()
+ .matches(
+ logicalOlapTableSink(
+ logicalProject(
+ logicalFilter(
+ logicalOlapScan()
+ )
+ )
+ )
+ );
+ }
}
diff --git
a/regression-test/data/ddl_p0/test_create_table_generated_column/test_update_generated_column_dependency.out
b/regression-test/data/ddl_p0/test_create_table_generated_column/test_update_generated_column_dependency.out
new file mode 100644
index 00000000000..4fa0df261f2
--- /dev/null
+++
b/regression-test/data/ddl_p0/test_create_table_generated_column/test_update_generated_column_dependency.out
@@ -0,0 +1,7 @@
+-- This file is automatically generated. You should know what you did if you
want to edit this
+-- !update_unrelated_column --
+1 10 11 999
+
+-- !update_on_update_dependency --
+1 updated matched 999
+
diff --git
a/regression-test/suites/ddl_p0/test_create_table_generated_column/test_update_generated_column_dependency.groovy
b/regression-test/suites/ddl_p0/test_create_table_generated_column/test_update_generated_column_dependency.groovy
new file mode 100644
index 00000000000..b99df3a5b15
--- /dev/null
+++
b/regression-test/suites/ddl_p0/test_create_table_generated_column/test_update_generated_column_dependency.groovy
@@ -0,0 +1,58 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+suite("test_update_generated_column_dependency") {
+ sql "drop table if exists test_update_generated_column_dependency"
+ sql """create table test_update_generated_column_dependency (
+ a int,
+ b int,
+ c int generated always as (b + 1),
+ d int
+ )
+ unique key(a)
+ distributed by hash(a) buckets 1
+ properties(
+ "replication_num" = "1",
+ "enable_unique_key_merge_on_write" = "true"
+ );"""
+
+ sql "insert into test_update_generated_column_dependency(a, b, d)
values(1, 10, 100)"
+ sql "update test_update_generated_column_dependency set d = 999 where a =
1"
+ order_qt_update_unrelated_column "select a, b, c, d from
test_update_generated_column_dependency"
+
+ sql "drop table if exists
test_update_generated_column_on_update_dependency"
+ sql """create table test_update_generated_column_on_update_dependency (
+ id int,
+ ts datetime default current_timestamp on update current_timestamp,
+ g int generated always as (year(ts)),
+ v int
+ )
+ unique key(id)
+ distributed by hash(id) buckets 1
+ properties(
+ "replication_num" = "1",
+ "enable_unique_key_merge_on_write" = "true"
+ );"""
+
+ sql """insert into test_update_generated_column_on_update_dependency(id,
ts, v)
+ values(1, '2000-01-01 00:00:00', 100)"""
+ sql "update test_update_generated_column_on_update_dependency set v = 999
where id = 1"
+ order_qt_update_on_update_dependency """select id,
+ if(ts > '2000-01-01 00:00:00', 'updated', 'stale'),
+ if(g = year(ts), 'matched', 'mismatched'), v
+ from test_update_generated_column_on_update_dependency"""
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]