This is a patch posted by Marek Stipek's review, with following comment:
The row images of a reported cascade action lived in two plain THD
members, on the assumption that at most one cascade action is in flight
per THD. InnoDB performs the next level of an FK chain from inside the
outer cascade action, though (row_update_cascade_for_mysql() recurses
into row_ins_foreign_check_on_constraint()). For a chain like
gp -> p -> c:
- the nested action for c overwrote the before-image captured for p,
reported c and freed the images, so thd_fk_cascade_row() for p found
no image and silently dropped the row. Only the deepest level reached
the binary log, and since its events carry FK_CASCADE_EVENTS_F the
replica did not re-run the cascade either: it kept the rows of p,
silently diverging from the primary;
- the nested capture reused the buffer allocated for p's row length, so
a child with a longer row than its parent overran it (debug builds
report "Freeing overrun buffer" and crash).
Keep the images on a stack instead: capturing a before-image opens a
frame with buffers sized for its own table, and thd_fk_cascade_row() or
thd_fk_cascade_abort() closes it. InnoDB now closes the frame also when
capturing the before-image failed, as the service contract already
asked for, so every frame is closed exactly once.
Self-referencing foreign keys: the child of such a cascade is the table
the statement itself changes, through the instance of it opened for the
foreign key. That handler has not read anything, so it has no row
template yet, and ha_innobase::fk_cascade_fetch_row() refused to convert
the row, although it rebuilds the template anyway, allocating it if
needed. The cascaded rows were not logged; when the statement also
cascaded into another table, whose rows were, the replica did not re-run
the cascade either and kept the self-referencing rows. Drop the check.
The same check made capture fail on a replica, for the handler of the
child opened for the foreign key there has not read anything either: the
pull request documents the feature as inert on the applier and pins it
in rpl_fk_cascade_binlog_row_slave_option, but the child is open via FK
prelocking there too. With the check gone, a replica with the feature
ON (origin OFF) captures the cascades it re-runs and logs them in its own
binary log, as any other session does; the test and the design document
now say so.
Order of the cascaded rows: the queue was emitted at statement end, after
all rows of the statement itself. When a cascade changes a row that the
statement changes after it - with the self-referencing cascades now
captured, e.g. DELETE FROM s WHERE id IN (1,2) with s.parent_id -> s.id
ON DELETE SET NULL - the binlog had DELETE 2 before the cascaded UPDATE
of 2, and the replica stopped with ERROR 1032. handler::binlog_log_row()
now emits the queue before each row of the statement is logged: the
cascades that row change ran precede it, as they did in execution.
Derived rows and the statement's own go to separate row events, so that
FK_CASCADE_DERIVED_F keeps marking exactly the derived ones.
- rpl_fk_cascade_binlog_row_mixed_eligibility: a statement with an
ineligible child table is not captured at all (an eligible and an
ineligible sibling, an ineligible grandchild, an all-eligible control
case), checked on the replica and in the binlog. The design document
refers to it, but it is not in the pull request.
- rpl_fk_cascade_binlog_row_multilevel: cascades through several levels
up to the InnoDB maximum of 14 below the parent, and one level deeper
(the statement fails, nothing is logged); UPDATE and SET NULL chains,
a diamond, savepoints, self-referencing foreign keys with and without
another child, and the order of the cascaded rows relative to the
statement's own. Each case is checked on the replica.
- galera_fk_cascade_binlog_events, ..._log_bin: the same in Galera,
without and with a binary log, with parallel applying.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>