Skip to content
/ server Public

MDEV-12302: Execute triggers for foreign key updates/deletes#3496

Open
Simoffsky wants to merge 6 commits intoMariaDB:mainfrom
Simoffsky:triggers-cascade
Open

MDEV-12302: Execute triggers for foreign key updates/deletes#3496
Simoffsky wants to merge 6 commits intoMariaDB:mainfrom
Simoffsky:triggers-cascade

Conversation

@Simoffsky
Copy link

@Simoffsky Simoffsky commented Sep 3, 2024

  • The Jira issue number for this PR is: MDEV-12302

Description

In this approach, an sql-layer api is added for calling on the row delete or update. These callbacks are supposed to be expandable for various needs, and ideally should be capable of everything that would happen during usual update/delete row events: check constraints, binlogging, etc, and in particular, trigger invocation, which is implemented in the base commit.

Innodb fills up the sql-format records and invokes the sql-side. It re-uses an equsting update_node, serving for cascade action, and delivers que_thr_t for re-use. As a result, cascade calls are provisioned with the information on call depth and can handle the depth exceeded errors as before.

For correct error handling of whatever could happen on the sql side, a new error code is introduced both for handler api and innodb: HA_ERR_CASCADE_SQL aka DB_SQL_ERROR. This error is distinguished from generic DB_ERROR: the latter is supposed for the unclassifiable cases and shoudn't normally be exposed through the handler api (so it's a backup plan code).

Foreign key depth control is further refectored for better error handling, more efficient and clear code.

Roadmap:
✅ Simple Delete
✅ Simple Update
✅ Fetch data from secondary indexes
✅ Handle changes coming from BEFORE triggers
✅ Virtual fields
✅ Test ON DELETE/UPDATE SET NULL
✅ Bit fields (probably just test it)
✅ Long blobs

Release Notes

Added support for triggers on events from cascade delete/update foreign key actions. The triggers are invoked the same way as for usual delete/update/insert events.

Basing the PR against the correct MariaDB version

  • This is a new feature or a refactoring, and the PR is based against the main branch.
  • This is a bug fix, and the PR is based against the earliest maintained branch in which the bug can be reproduced.

PR quality check

  • I checked the CODING_STANDARDS.md file and my PR conforms to this where appropriate.
  • For any trivial modifications to the PR, I am ok with the reviewer making the changes themselves.

@CLAassistant
Copy link

CLAassistant commented Sep 3, 2024

CLA assistant check
All committers have signed the CLA.

@FooBarrior FooBarrior self-requested a review October 11, 2024 13:27
@Simoffsky Simoffsky force-pushed the triggers-cascade branch 2 times, most recently from 954b0e6 to bfda058 Compare October 12, 2024 12:57
@FooBarrior
Copy link
Contributor

FooBarrior commented Nov 4, 2024

Hello @Simoffsky! As the project is close to its final stage, it's time to reorganize your code, also covering @vuvova's request from #3354.

Please move the triggers processing (and other sql-layer "hooks") code to the sql layer.

Let's consider two approaches to this.

1. Make two sql-side functions related to hooks: before and after

I will refer to hooks as to ephemere trigger like actions, that are to be done before or after a row change.

We can already observe that before hooks differ from after hooks: before hooks also include updating virtual fields, whereas after hooks don't. In the close future, the list will expand (like with row logging, CHEK constraints, etc).

So it makes sense for making two functions.

This approach would look as placing two functions in sql_class.cc:

int sql_do_before_triggers(TABLE*, bool is_delete);
int sql_do_after_triggers(TABLE*, bool is_delete);

This also could make sense to be done in four functions instead of two.

Then, in ha_innodb.cc, adding an sql-format row conversion function, which also retreives the necessary sql-layer objects, like maria_table:

dberr_t innodb_convert_ib_row_to_sql(dict_index_t *, row_t *,..., TABLE **maria_table /*!< out */,  ...);

This approach also requires a manual conversion back from sql format (in case if triggers change the data)

dberr_t innodb_convert_sql_row_to_ib(...);

2. Call an sql-layer function instead of innodb's row_upd.

We can instead make a whole row processing on the sql side:
sql_table.cc would be a suitable place for the following two functions:

sql_update_row(TABLE *);
sql_delete_row(TABLE *);

One will have to call table->file->ha_update_row (or ha_delete_row) inside.

Also positioning the cursor with table->file->ha_rnd_pos is mandatory before any ha_update_row and ha_delete_row calls.

In ha_innodb.cc, adding

dberr_t innodb_do_foreign_cascade(dict_table_t *, upd_node_t*,...);

and replace the row_upd_step call in row_update_cascade_for_mysql.

Which way to choose

1 preserves innodb row_upd_step call, which makes things faster, especially if no hooks are required to call.

OTOH, 2 gives a more flexibility on what the sql layer can do. ha_* will call some extra hooks, like long-text UNIQUE checks, and WITHOUT OVERLAPS feature. We can also replace ha_delete_row with TABLE::delete_row call, which can eventyally bring System Versioning support in the sql layer. This is actually very desired.

2 also needs no manual conversion backward, which can be less error-prone, and just easier.

sql_update_row and sql_delete_row wil allso be able to be eventually re-used Sql_cmd_{update|delete}, or in other commands, that have to make general-purpose row updates (like REPLACE or INSERT...ON DUPLICATE KEY UPDATE, or yet unimplemented MERGE)

So I'm asking you to go on with approach 2.

@FooBarrior
Copy link
Contributor

Please also join all tests in a single file. Test execution time is a problem for us, as every test run has a significant latency.

Note that we maintain 80 characters width for the c/c++ sode, with putting an opening { brace on a blank line.
Though https://github.com/MariaDB/server/blob/main/CODING_STANDARDS.md holds just a basic set of rules, it's better to read and follow it to pass a review with less edits.

@FooBarrior FooBarrior self-assigned this Nov 4, 2024
@cvicentiu cvicentiu added GSoC External Contribution All PRs from entities outside of MariaDB Foundation, Corporation, Codership agreements. labels Nov 26, 2024
@Simoffsky Simoffsky force-pushed the triggers-cascade branch 2 times, most recently from b700b79 to 33545f7 Compare December 6, 2024 13:39
@Simoffsky Simoffsky force-pushed the triggers-cascade branch 2 times, most recently from f2b8b9f to 1ca8660 Compare December 22, 2024 16:05
@Simoffsky Simoffsky force-pushed the triggers-cascade branch 3 times, most recently from 42c3ab6 to 5709403 Compare December 30, 2024 12:48
@Simoffsky Simoffsky changed the base branch from 11.6 to main December 30, 2024 12:48
@Simoffsky Simoffsky force-pushed the triggers-cascade branch 3 times, most recently from 9c53dcf to 11a22ef Compare March 2, 2025 15:48
@Simoffsky Simoffsky force-pushed the triggers-cascade branch 3 times, most recently from f1fe0e8 to 79551cf Compare April 6, 2025 10:53
@svoj svoj marked this pull request as draft July 12, 2025 06:51
@FooBarrior FooBarrior changed the title MDEV-12302: Execute triggers for foreign key updates/deletes [WIP] MDEV-12302: Execute triggers for foreign key updates/deletes Jan 27, 2026
@FooBarrior FooBarrior marked this pull request as ready for review January 27, 2026 17:24
Copy link
Contributor

@FooBarrior FooBarrior left a comment

Choose a reason for hiding this comment

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

@Simoffsky I'm going to have a time to focus on my leftovers, and this patch is of my high interest.

Let's go through it, perhaps, and see what could we miss?

row_sel_store_mysql_rec(maria_table->record[1], prebuilt, rec, NULL,
true, clust_index, offsets);

//if (node->upd_row == NULL) {
Copy link
Contributor

Choose a reason for hiding this comment

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

do you remember what was the problem with this line? Perhaps, just a comment not removed

thr->prebuilt->m_mysql_table = NULL;
row_upd_step(thr);
thr->prebuilt->m_mysql_table = mysql_table;
//row_upd_step(thr);
Copy link
Contributor

Choose a reason for hiding this comment

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

another quirk. what should we do with it?

@FooBarrior FooBarrior force-pushed the triggers-cascade branch 4 times, most recently from b61e283 to ba4d913 Compare February 13, 2026 19:24
Simoffsky and others added 6 commits February 13, 2026 21:17
Bypass the errors from sql layer, through innodb back to sql layer.
New handler error: HA_ERR_CASCADE_SQL
New innodb error: DB_SQL_ERROR

This error is not visible to user and serves only for correct error
transport. The real error code is expected to be stored in
thd->m_stmt_da, that is, already reported

This is a fixup commit.
Original author: Simoffsky <maks-kirsanov26@mail.ru>
* Move inc and dec for fk_cascade_depth close to each other, in a
symmetrical manner, wrapping the recursive call.

* Remove conputation-heavy node traversal with
row_ins_cascade_n_ancestors for each cascade call,
resulting in O(depth^2) total passes. It should be enough to do that
only as an assertion. Historically, there could be more than one thr,
joined in chains (but then we'd better pass over a chain of thr). Now a
single thr is used for cascade call, so no need in any graph traversal.

* Fix max cascade error handling -- now HA_ERR_FK_DEPTH_EXCEEDED is
returned, as implied.
que_thr_t is not bypassed in between trigger calls, that is the count
starts from 0 each time in the trigger.

This means that an effective depth restriction is a multiplication of
FK_MAX_CASCADE_DEL and max_sp_recursion_depth.

Moving the control to THD changes this to addition.

Now the depth limit is: FK_MAX_CASCADE_DEL + max_sp_recursion_depth
The problem is row_update_vers_insert invoked before the cascade update.
Cascade update relies on pre-defined node->pcur, and an insert can
invalidate the cursor, if b-tree node split happens.

One possible solution is to move the versioned insert after cascade
update.

This patch goes the described way, but also moves versioned insert to
the sql layer.

We have to re-set row event bits in trg_event_map, because
node->is_delete is determined based on those values.

TODO is to fix versioning.foreign 'trx_id,unique' result mismatch.
All the technical provisioning was done yet by
MDEV-12302 Execute triggers for foreign key updates/deletes

As it executes ha_update_row and ha_delete_row directly.

All that is left is remove the restriction and add the tests.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

External Contribution All PRs from entities outside of MariaDB Foundation, Corporation, Codership agreements. GSoC

Development

Successfully merging this pull request may close these issues.

4 participants