MDEV-39226: Push whole multi-table update/delete down into engines - #5529
MDEV-39226: Push whole multi-table update/delete down into engines#5529bsrikanth-mariadb wants to merge 1 commit into
Conversation
f2ce815 to
44765e4
Compare
| const char *pushed_select_text= "PUSHED SELECT"; | ||
| const char *pushed_update_text= "PUSHED DOWN UPDATE"; | ||
| const char *pushed_delete_text= "PUSHED DOWN DELETE"; | ||
|
|
There was a problem hiding this comment.
Please follow the pattern: if we use PUSHED SELECT, let's add PUSHED UPDATE , not PUSHED DOWN UPDATE.
There was a problem hiding this comment.
Yeah, true. But, felt "PUSHED DOWN XXXX" was sounding better.
Anyways, will keep them consistent.
b931524 to
eb052c3
Compare
168a3b1 to
f59b0bd
Compare
| static multi_upddel_handler * | ||
| create_federatedx_multi_upddel_handler(THD *thd, SELECT_LEX *sel_lex) | ||
| { | ||
| if (!use_pushdown || !is_supported_update_delete(thd->lex->sql_command)) |
There was a problem hiding this comment.
What is this for? Do we get into this function for non-UPDATE/DELETE ?
I have added
DBUG_ASSERT(is_supported_update_delete(thd->lex->sql_command));
and it survived the tests.
There was a problem hiding this comment.
hmm. will add the DBUG_ASSERT() instead.
|
Please apply this patch: cleanups.patch |
| str_eq(a->hostname, b->hostname) && | ||
| str_eq(a->socket, b->socket) && | ||
| str_eq(a->username, b->username) && | ||
| str_eq(a->password, b->password); |
There was a problem hiding this comment.
Even password?
I'm not sure if it's possible to see different data depending on the user... Is password necessary?
There was a problem hiding this comment.
sure, password check can be removed.
|
|
||
| ha_federatedx_multi_upddel_handler::~ha_federatedx_multi_upddel_handler() | ||
| = default; | ||
|
|
There was a problem hiding this comment.
not really.
| The range of the error codes of the client library, CR_MIN_ERROR and | ||
| CR_MAX_ERROR of errmsg.h. That header cannot be included here because it | ||
| defines ER, which the server defines differently. | ||
| */ |
There was a problem hiding this comment.
If ER is the only problem, can one use
#undef ER
#include ...
?
There was a problem hiding this comment.
it is actually not needed. removed it now.
spetrunia
left a comment
There was a problem hiding this comment.
See above input.
Also, should create_multi_upddel accept SELECT_LEX argument, or LEX would be more meaningful?
actually, SELECT_LEX is sufficient. |
f59b0bd to
ed733d2
Compare
|
WARNINGS-QUESTION: There shouldn't be many cases where that happens because the default logic is "warnings are errors in UPDATE". Warnings can remain warnings when using UPDATE IGNORE, but we disable pushdown when IGNORE is present. However, one can also disable warnings by removing Claude also suggests there are warnings which do not turn into errors:
|
|
Why does multi_update::direct_update_delete_done use while multi_delete::direct_update_delete_done use were the checks borrowed from somewhere? |
|
Please change return type of |
It is sufficient but it's not logical. Let's discuss changing it. |
spetrunia
left a comment
There was a problem hiding this comment.
Please check the above.
Also please chekc those and add if there are no objections:
commit b89e5c654688c4c9c8db12d98d6d75a097699eb2 (HEAD -> 13.2-MDEV-39226-review-input, origin/13.2-MDEV-39226-review-input, 13.2-MDEV-39226-direct-multi_table-update-delete)
Author: Sergei Petrunia <sergey@mariadb.com>
Date: Tue Aug 25 13:55:30 2026 +0300
Review input 3: don't check for is_supported_update_delete()
* we only get into create_federatedx_multi_upddel_handler for UPDATE/DELETE
* Use of is_supported_update_delete() implies that this checks
for UPDATEs/DELETEs of which only some are supported.
The function actually returned TRUE for any UPDATE/DELETE.
commit e582992ea4b935b079496a99d1574cc11882beab
Author: Sergei Petrunia <sergey@mariadb.com>
Date: Tue Aug 25 13:53:12 2026 +0300
Review input 2: re-word comments
commit 9b4a8958024d205556cdabe398483c08ebb1d4ae
Author: Sergei Petrunia <sergey@mariadb.com>
Date: Tue Aug 25 13:52:40 2026 +0300
Review input 1: simplify code in EXPLAIN handling
remove is_pushed_down_select_type(), keep just
get_pushed_down_select_text().
Yes, following similar logic from direct_update, and direct_delete |
Actually, the default implementation in select_result is to return true; So, only multi_delete, and multi_update can return false. However, changed the return type to void, as it doesn't throw any real errors. |
ed733d2 to
e2323e5
Compare
warnings are swallowed from the remote server. So, nothing gets shown to the client. The behaviour is same for PUSHED SELECT, and single table delete/update. Looks like FederatedX doesn't have a mechanism to handle warnings. |
3deb33d to
5992b3d
Compare
Done. |
|
Please consider the below patch. It does two things.
diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc
index 3166fa32ecc..130db3df4d0 100644
--- a/sql/sql_delete.cc
+++ b/sql/sql_delete.cc
@@ -1739,9 +1739,9 @@ int multi_delete::rowid_table_deletes(TABLE *table, bool ignore)
/*
The engine has performed the whole multi-table DELETE on its own, the
statement was pushed down through the select_handler interface. Remember
- the row counts reported by the engine and mark the tables we deleted from,
- so that send_eof() can binlog the statement and send the OK packet without
- running the SQL-layer delete loop.
+ the row counts reported by the engine and check whether modified tables
+ were transactional. send_eof() will need this info to binlog the statement
+ correctly.
*/
void multi_delete::direct_update_delete_done(ha_rows found_rows,
@@ -1753,11 +1753,13 @@ void multi_delete::direct_update_delete_done(ha_rows found_rows,
deleted= affected_rows;
direct_dml_done= true;
- for (TABLE_LIST *walk= delete_tables; walk; walk= walk->next_local)
+ /*
+ Do the same as multi_delete's initialize_tables() and send_data() do:
+ walk the tables that we delete from and check if they are transactional
+ */
+ for (TABLE_LIST *tbl= delete_tables; tbl; tbl= tbl->next_local)
{
- TABLE_LIST *tbl= walk->table ? walk :
- walk->correspondent_table->find_table_for_update();
- if (!tbl || !tbl->table)
+ if (!tbl->table)
continue;
if (tbl->table->file->has_transactions())
transactional_tables= 1;
diff --git a/sql/sql_update.cc b/sql/sql_update.cc
index 9040d2d7a54..c8cc1513fff 100644
--- a/sql/sql_update.cc
+++ b/sql/sql_update.cc
@@ -2934,9 +2934,9 @@ int multi_update::do_updates()
/*
The engine has performed the whole multi-table UPDATE on its own, the
statement was pushed down through the select_handler interface. Remember
- the row counts reported by the engine and mark the updated tables as
- modified, so that send_eof() can binlog the statement and send the OK
- packet without running the SQL-layer update loop.
+ the row counts reported by the engine and check whether modified tables
+ were transactional. send_eof() will need this info to binlog the statement
+ correctly.
*/
void multi_update::direct_update_delete_done(ha_rows found_rows,
@@ -2948,6 +2948,10 @@ void multi_update::direct_update_delete_done(ha_rows found_rows,
updated= affected_rows;
direct_dml_done= true;
+ /*
+ Do the same as multi_update::do_updates() does:
+ walk the tables that were updated and check if they were transactional
+ */
for (TABLE_LIST *cur_table= update_tables; cur_table;
cur_table= cur_table->next_local)
{ |
I added view tests, and they confirm that the call to |
01b2b18 to
86ccfaa
Compare
Agree, it actually does that. Missed it. if (!tab->bush_children && tab->table->map & tables_to_delete_from)
{
/* We are going to delete from this table */
TABLE *tbl=walk->table=tab->table;note that walk->table is assigned tab->table here. |
|
Note that in this patch federatedx actually disables support for VIEWs. I get view references in the the query that is submitted to the backend: |
|
But what if some federated engine DOES support VIEWs... /* We are going to delete from this table */
TABLE *tbl=walk->table=tab->table;Does it mean that call after all? |
|
As discussed via voice: it looks like we need to put this back in: - TABLE_LIST *tbl= walk->table ? walk :
- walk->correspondent_table->find_table_for_update();
- if (!tbl || !tbl->table)It should be there but we can't have the test coverage for it currently. |
spetrunia
left a comment
There was a problem hiding this comment.
After that, the patch is good to go.
Give storage engines a way to take over an entire multi-table UPDATE/DELETE, the way they can already take over a SELECT. Without it the join, the row matching and every modification run in the SQL layer even when an engine could do the whole statement itself in one step; a single-table UPDATE/DELETE already avoids this via direct_update_rows()/direct_delete_rows(), but a multi-table statement has no primary handler object to drive that path. This adds a generic, engine-agnostic pushdown interface: the SQL layer offers the statement to the engine, and if the engine accepts it, it performs the whole thing and reports only the row counts. - Split select_handler into a pushdown_handler base with select_handler (result set) and a new multi_upddel_handler (runs a whole UPDATE/DELETE, reports row counts, reported as PUSHED UPDATE/PUSHED DELETE); add handlerton::create_multi_upddel, looked up in Sql_cmd_dml::execute_inner(). - multi_update/multi_delete gain direct_update_delete_done(), which records the engine's counts so send_eof() binlogs and replies without the SQL-layer loop; it forces statement-format binlogging so the change still replicates under binlog_format=ROW, and errors out instead of silently dropping counts for an unsupported result object. - FederatedX implements the interface as the reference engine used to test correctness: it prints the statement back and runs it remotely, passes the engine's error code/SQLSTATE through, reads the matched count from the remote info string, executes IGNORE locally, and only pushes down when all tables share one remote server (same as SELECT/derived/unit pushdown). Test: federated.federatedx_pushdown_upd_del.
86ccfaa to
7d0792a
Compare
Give storage engines a way to take over an entire multi-table
UPDATE/DELETE, the way they can already take over a SELECT. Without it the
join, the row matching and every modification run in the SQL layer even
when an engine could do the whole statement itself in one step; a
single-table UPDATE/DELETE already avoids this via
direct_update_rows()/direct_delete_rows(), but a multi-table statement has
no primary handler object to drive that path.
This adds a generic, engine-agnostic pushdown interface: the SQL layer
offers the statement to the engine, and if the engine accepts it, it
performs the whole thing and reports only the row counts.
(result set) and a new multi_upddel_handler (runs a whole UPDATE/DELETE,
reports row counts, reported as PUSHED UPDATE/PUSHED DELETE); add
handlerton::create_multi_upddel, looked up in Sql_cmd_dml::execute_inner().
the engine's counts so send_eof() binlogs and replies without the
SQL-layer loop; it forces statement-format binlogging so the change still
replicates under binlog_format=ROW, and errors out instead of silently
dropping counts for an unsupported result object.
correctness: it prints the statement back and runs it remotely, passes
the engine's error code/SQLSTATE through, reads the matched count from the
remote info string, executes IGNORE locally, and only pushes down when all
tables share one remote server (same as SELECT/derived/unit pushdown).
Test: federated.federatedx_pushdown_upd_del.