matrixorigin / matrixorigin/matrixone
[Bug]: Data Branch MERGE/PICK rejects non-key updates on referenced rows
- Dominant language
- Go
- Stars
- 1.9k
- Forks
- 311
- Avg merge
- 1d 3h
- Merged PRs (30d)
- 768
Description
## Description
`DATA BRANCH MERGE` and `DATA BRANCH PICK` cannot apply a branch-side update that changes only a non-key column when another table has a foreign key to the destination row. The same ordinary `UPDATE` succeeds. Both Data Branch operations reject the change before modifying the destination.
## Environment
- Branch: `main`
- Commit: `33af6ae3c0c23e4f277137460577ea64678f7ecf`
- Deployment: fresh local single-CN / single-TN / single-Log cluster
- Date: 2026-09-02
## Steps to reproduce
```sql
create database branch_fk_apply;
use branch_fk_apply;
create table child_t (id int primary key, note varchar(32));
create table parent_t (
id int primary key,
child_id int,
constraint fk_parent_child foreign key (child_id) references child_t(id)
);
insert into child_t values (1,'one'),(2,'two');
insert into parent_t values (1,2);
data branch create table leaf_merge from child_t;
update leaf_merge set note = 'leaf-merge' where id = 2;
data branch merge leaf_merge into child_t when conflict accept;
data branch create table leaf_pick from child_t;
update leaf_pick set note = 'leaf-pick' where id = 2;
update child_t set note = 'direct-control' where id = 2;
data branch pick leaf_pick into child_t keys(2) when conflict accept;
```
## Actual behavior
Both `MERGE` and `PICK` return:
```text
internal error: Cannot delete or update a parent row: a foreign key constraint fails
```
After the failed `MERGE`, `child_t.id=2` remains `two`; after the failed `PICK`, it remains `direct-control`. No partial destination row change was observed.
## Expected behavior
Changing only `child_t.note` must be applicable through Data Branch just as it is through ordinary `UPDATE`; the foreign key references `child_t.id`, which is unchanged. `MERGE` should leave `leaf-merge` and `PICK` should apply `leaf-pick` under `WHEN CONFLICT ACCEPT`.
## Stability and controls
- Minimal reproducer: 3/3 for both `MERGE` and `PICK`.
- Direct control: `update child_t set note = 'direct-control' where id = 2` succeeds in all three runs.
- Non-FK Data Branch control: the same branch-side non-key update on a table without a referencing parent merges successfully in all three runs.
- Atomicity: each rejected operation leaves the destination value unchanged.
## Code analysis
The update apply path materializes ordinary updates as a `diffDelete` followed by `diffInsert`: `appendDataBranchApplyRowAsSQLValues` writes the two kinds separately in `pkg/frontend/data_branch_output.go`, and `tryFlushDeletesOrInserts` flushes deletes before inserts. Deleting the referenced `child_t` row violates `parent_t.child_id -> child_t.id` even though the user-visible operation does not modify `id`. The exact-key `UPDATE` path is restricted to FLOAT/DOUBLE key handling, so an integer primary key uses delete/insert replay.
## Regression coverage
Add a deterministic Data Branch regression in `test/distributed/cases/git4data/branch/merge` and the corresponding PICK suite: a table referenced by a foreign key, a branch-side non-key update, `MERGE`/`PICK` with `WHEN CONFLICT ACCEPT`, direct-UPDATE control, and destination-value assertions.
## Related
- #26111 covers database-branch creation for cyclic foreign keys; it does not cover replay of branch updates into a referenced row.
Contributor guide
Assessment
This issue has not been assessed yet.