From a76b9d7ff847010db9dc86565db456ed2cd129f8 Mon Sep 17 00:00:00 2001 From: Andreas Schnederle-Wagner Date: Fri, 23 Oct 2026 13:01:54 +0200 Subject: [PATCH 1/2] MDEV-41747 Aria: DROP TABLE warns "uses an extension that doesn't exist" for a table with a fulltext parser plugin maria_delete_table() opens the table to find out whether it is transactional. A table whose fulltext index uses a parser plugin has HA_OPTION_RELIES_ON_SQL_LAYER, so maria_open() refused it with HA_ERR_UNSUPPORTED. DROP TABLE then warned "Table uses an extension that doesn't exist in this MariaDB version" and skipped the REDO_DROP_TABLE log record of a transactional table. Opening for drop only reads the share, so allow it. Co-Authored-By: Claude Opus 5.5 --- .../suite/maria/fulltext_parser_drop.result | 12 ++++++++++++ mysql-test/suite/maria/fulltext_parser_drop.test | 15 +++++++++++++++ storage/maria/ma_open.c | 2 +- 3 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 mysql-test/suite/maria/fulltext_parser_drop.result create mode 100644 mysql-test/suite/maria/fulltext_parser_drop.test diff --git a/mysql-test/suite/maria/fulltext_parser_drop.result b/mysql-test/suite/maria/fulltext_parser_drop.result new file mode 100644 index 0000000000000..41a7164ef48cb --- /dev/null +++ b/mysql-test/suite/maria/fulltext_parser_drop.result @@ -0,0 +1,12 @@ +# +# DROP TABLE of an Aria table with a fulltext parser plugin +# must not warn about an unsupported extension +# +INSTALL PLUGIN simple_parser SONAME 'mypluglib'; +CREATE TABLE t1 (a TEXT, FULLTEXT(a) WITH PARSER simple_parser) ENGINE=Aria; +INSERT INTO t1 VALUES ('hello world'); +DROP TABLE t1; +CREATE TABLE t1 (a TEXT, FULLTEXT(a) WITH PARSER simple_parser) +ENGINE=Aria TRANSACTIONAL=0; +DROP TABLE t1; +UNINSTALL PLUGIN simple_parser; diff --git a/mysql-test/suite/maria/fulltext_parser_drop.test b/mysql-test/suite/maria/fulltext_parser_drop.test new file mode 100644 index 0000000000000..726cd39e57510 --- /dev/null +++ b/mysql-test/suite/maria/fulltext_parser_drop.test @@ -0,0 +1,15 @@ +--source include/have_simple_parser.inc + +--echo # +--echo # DROP TABLE of an Aria table with a fulltext parser plugin +--echo # must not warn about an unsupported extension +--echo # + +INSTALL PLUGIN simple_parser SONAME 'mypluglib'; +CREATE TABLE t1 (a TEXT, FULLTEXT(a) WITH PARSER simple_parser) ENGINE=Aria; +INSERT INTO t1 VALUES ('hello world'); +DROP TABLE t1; +CREATE TABLE t1 (a TEXT, FULLTEXT(a) WITH PARSER simple_parser) + ENGINE=Aria TRANSACTIONAL=0; +DROP TABLE t1; +UNINSTALL PLUGIN simple_parser; diff --git a/storage/maria/ma_open.c b/storage/maria/ma_open.c index 3a31663942d62..4952ee316dd6e 100644 --- a/storage/maria/ma_open.c +++ b/storage/maria/ma_open.c @@ -435,7 +435,7 @@ MARIA_HA *maria_open(const char *name, int mode, uint open_flags, goto err; } if ((share->options & HA_OPTION_RELIES_ON_SQL_LAYER) && - ! (open_flags & HA_OPEN_FROM_SQL_LAYER)) + ! (open_flags & (HA_OPEN_FROM_SQL_LAYER | HA_OPEN_FOR_DROP))) { DBUG_PRINT("error", ("table cannot be opened from non-sql layer")); my_errno= HA_ERR_UNSUPPORTED; From d140177991a912b62645b3ea370998fb32f67e03 Mon Sep 17 00:00:00 2001 From: Andreas Schnederle-Wagner Date: Fri, 23 Oct 2026 23:13:12 +0200 Subject: [PATCH 2/2] MDEV-41752 Aria: RENAME TABLE, ALTER TABLE, REPAIR TABLE and crash recovery fail for a table with a fulltext parser plugin A table whose fulltext index uses a parser plugin has HA_OPTION_RELIES_ON_SQL_LAYER, and maria_open() refuses it unless HA_OPEN_FROM_SQL_LAYER is set, which only ha_maria::open() did. So maria_rename() (RENAME TABLE, and the renames of ALTER TABLE ... ALGORITHM=COPY), the second data handle of a ROW_FORMAT=PAGE repair (create_new_data_handle()) and crash recovery (new_table()) failed for such tables, and recovery skipped their REDO records. Inside the server, open such tables with HA_OPEN_FROM_SQL_LAYER in these places. Recovery can pass the flag for every table, because for the others its only effect, the state LSN update, already follows from maria_in_recovery. aria_chk and aria_read_log still refuse such tables. Repair builds the fulltext keys through the handle that the SQL layer opened, which has the parser of the table, and recovery applies logged rows and key values without a parser. Recovery cannot replay REDO_REPAIR_TABLE for such a table, because the parser plugin is not loaded then. It skips the record and keeps the files of the repair. To make that safe, write_log_record_for_repair() now syncs the files of such a table before it logs the repair: flushing the pages does not make them durable. Marking the table as crashed instead would make recovery skip all later REDO records of the table, which can be committed changes. The debug points maria_crash_after_repair_log and maria_before_repair_log are for maria.fulltext_parser_debug. Co-Authored-By: Claude Opus 5.5 --- .../suite/maria/fulltext_parser-master.opt | 2 + mysql-test/suite/maria/fulltext_parser.result | 150 ++++++++++++++++++ mysql-test/suite/maria/fulltext_parser.test | 87 ++++++++++ .../maria/fulltext_parser_debug-master.opt | 2 + .../suite/maria/fulltext_parser_debug.result | 64 ++++++++ .../suite/maria/fulltext_parser_debug.test | 82 ++++++++++ .../maria/include/fulltext_parser_ops.inc | 38 +++++ storage/maria/ma_check.c | 23 ++- storage/maria/ma_recovery.c | 30 +++- storage/maria/ma_rename.c | 10 +- 10 files changed, 483 insertions(+), 5 deletions(-) create mode 100644 mysql-test/suite/maria/fulltext_parser-master.opt create mode 100644 mysql-test/suite/maria/fulltext_parser.result create mode 100644 mysql-test/suite/maria/fulltext_parser.test create mode 100644 mysql-test/suite/maria/fulltext_parser_debug-master.opt create mode 100644 mysql-test/suite/maria/fulltext_parser_debug.result create mode 100644 mysql-test/suite/maria/fulltext_parser_debug.test create mode 100644 mysql-test/suite/maria/include/fulltext_parser_ops.inc diff --git a/mysql-test/suite/maria/fulltext_parser-master.opt b/mysql-test/suite/maria/fulltext_parser-master.opt new file mode 100644 index 0000000000000..79c596c97e106 --- /dev/null +++ b/mysql-test/suite/maria/fulltext_parser-master.opt @@ -0,0 +1,2 @@ +--aria-checkpoint-interval=0 +--aria-recover-options=OFF diff --git a/mysql-test/suite/maria/fulltext_parser.result b/mysql-test/suite/maria/fulltext_parser.result new file mode 100644 index 0000000000000..164ea3dabbac9 --- /dev/null +++ b/mysql-test/suite/maria/fulltext_parser.result @@ -0,0 +1,150 @@ +INSTALL PLUGIN simple_parser SONAME 'mypluglib'; +# Fulltext parser with ROW_FORMAT=PAGE +CREATE TABLE t1 (a TEXT, FULLTEXT(a) WITH PARSER simple_parser) ENGINE=Aria ROW_FORMAT=PAGE TRANSACTIONAL=1; +INSERT INTO t1 VALUES ('abc-def'), ('abcd-efgh'), ('other text'); +RENAME TABLE t1 TO t2; +SELECT a FROM t2 WHERE MATCH(a) AGAINST('abc-def abcd'); +a +abc-def +DROP TABLE t2; +CREATE TABLE t1 (a TEXT, FULLTEXT(a) WITH PARSER simple_parser) ENGINE=Aria ROW_FORMAT=PAGE TRANSACTIONAL=1; +INSERT INTO t1 VALUES ('abc-def'), ('abcd-efgh'), ('other text'); +ALTER TABLE t1 ADD COLUMN b INT, ALGORITHM=COPY; +SELECT a FROM t1 WHERE MATCH(a) AGAINST('abc-def abcd'); +a +abc-def +DROP TABLE t1; +CREATE TABLE t1 (a TEXT, FULLTEXT(a) WITH PARSER simple_parser) ENGINE=Aria ROW_FORMAT=PAGE TRANSACTIONAL=1; +INSERT INTO t1 VALUES ('abc-def'), ('abcd-efgh'), ('other text'); +REPAIR TABLE t1; +Table Op Msg_type Msg_text +test.t1 repair status OK +SELECT a FROM t1 WHERE MATCH(a) AGAINST('abc-def abcd'); +a +abc-def +DROP TABLE t1; +CREATE TABLE t1 (a TEXT, FULLTEXT(a) WITH PARSER simple_parser) ENGINE=Aria ROW_FORMAT=PAGE TRANSACTIONAL=1; +INSERT INTO t1 VALUES ('abc-def'), ('abcd-efgh'), ('other text'); +REPAIR TABLE t1 EXTENDED; +Table Op Msg_type Msg_text +test.t1 repair status OK +SELECT a FROM t1 WHERE MATCH(a) AGAINST('abc-def abcd'); +a +abc-def +DROP TABLE t1; +CREATE TABLE t1 (a TEXT) +ENGINE=Aria ROW_FORMAT=PAGE TRANSACTIONAL=1; +INSERT INTO t1 VALUES ('abc-def'), ('abcd-efgh'), ('other text'); +ALTER TABLE t1 ADD FULLTEXT(a) WITH PARSER simple_parser, ALGORITHM=COPY; +SELECT a FROM t1 WHERE MATCH(a) AGAINST('abc-def abcd'); +a +abc-def +DROP TABLE t1; +# Fulltext parser with ROW_FORMAT=DYNAMIC +CREATE TABLE t1 (a TEXT, FULLTEXT(a) WITH PARSER simple_parser) ENGINE=Aria ROW_FORMAT=DYNAMIC TRANSACTIONAL=0; +INSERT INTO t1 VALUES ('abc-def'), ('abcd-efgh'), ('other text'); +RENAME TABLE t1 TO t2; +SELECT a FROM t2 WHERE MATCH(a) AGAINST('abc-def abcd'); +a +abc-def +DROP TABLE t2; +CREATE TABLE t1 (a TEXT, FULLTEXT(a) WITH PARSER simple_parser) ENGINE=Aria ROW_FORMAT=DYNAMIC TRANSACTIONAL=0; +INSERT INTO t1 VALUES ('abc-def'), ('abcd-efgh'), ('other text'); +ALTER TABLE t1 ADD COLUMN b INT, ALGORITHM=COPY; +SELECT a FROM t1 WHERE MATCH(a) AGAINST('abc-def abcd'); +a +abc-def +DROP TABLE t1; +CREATE TABLE t1 (a TEXT, FULLTEXT(a) WITH PARSER simple_parser) ENGINE=Aria ROW_FORMAT=DYNAMIC TRANSACTIONAL=0; +INSERT INTO t1 VALUES ('abc-def'), ('abcd-efgh'), ('other text'); +REPAIR TABLE t1; +Table Op Msg_type Msg_text +test.t1 repair status OK +SELECT a FROM t1 WHERE MATCH(a) AGAINST('abc-def abcd'); +a +abc-def +DROP TABLE t1; +CREATE TABLE t1 (a TEXT, FULLTEXT(a) WITH PARSER simple_parser) ENGINE=Aria ROW_FORMAT=DYNAMIC TRANSACTIONAL=0; +INSERT INTO t1 VALUES ('abc-def'), ('abcd-efgh'), ('other text'); +REPAIR TABLE t1 EXTENDED; +Table Op Msg_type Msg_text +test.t1 repair status OK +SELECT a FROM t1 WHERE MATCH(a) AGAINST('abc-def abcd'); +a +abc-def +DROP TABLE t1; +CREATE TABLE t1 (a TEXT) +ENGINE=Aria ROW_FORMAT=DYNAMIC TRANSACTIONAL=0; +INSERT INTO t1 VALUES ('abc-def'), ('abcd-efgh'), ('other text'); +ALTER TABLE t1 ADD FULLTEXT(a) WITH PARSER simple_parser, ALGORITHM=COPY; +SELECT a FROM t1 WHERE MATCH(a) AGAINST('abc-def abcd'); +a +abc-def +DROP TABLE t1; +# Recover committed rows written after the last checkpoint +CREATE TABLE t1 (a TEXT, FULLTEXT(a) WITH PARSER simple_parser) +ENGINE=Aria ROW_FORMAT=PAGE TRANSACTIONAL=1; +INSERT INTO t1 VALUES ('abc-def'), ('baseline-two'), ('baseline-three'); +CREATE TABLE t_flush (a INT) ENGINE=Aria TRANSACTIONAL=1; +INSERT INTO t_flush VALUES (0); +# restart +INSERT INTO t1 VALUES ('commit-one'), ('commit-two'); +# restart +SELECT a FROM t1 ORDER BY a; +a +abc-def +baseline-three +baseline-two +commit-one +commit-two +SELECT a FROM t1 WHERE MATCH(a) AGAINST('commit-one commit-two') ORDER BY a; +a +commit-one +commit-two +CHECK TABLE t1 EXTENDED; +Table Op Msg_type Msg_text +test.t1 check status OK +# Roll back row and fulltext key changes without loading the parser +connect writer,localhost,root,,test; +LOCK TABLES t1 WRITE; +INSERT INTO t1 VALUES ('undo-insert'); +UPDATE t1 SET a = 'undo-update' WHERE a = 'abc-def'; +DELETE FROM t1 WHERE a = 'baseline-two'; +connection default; +INSERT INTO t_flush VALUES (1); +# restart +disconnect writer; +include/assert_grep.inc [Recovery rolled back the writer transaction] +include/assert_grep.inc [Recovery did not skip UNDO records] +SELECT a FROM t1 ORDER BY a; +a +abc-def +baseline-three +baseline-two +commit-one +commit-two +SELECT a FROM t1 +WHERE MATCH(a) AGAINST('abc-def baseline-two undo-insert undo-update') +ORDER BY a; +a +abc-def +baseline-two +CHECK TABLE t1 EXTENDED; +Table Op Msg_type Msg_text +test.t1 check status OK +# Recover a completed bulk-insert repair with a parser plugin +CREATE TABLE t_src (a TEXT) ENGINE=Aria; +INSERT INTO t_src VALUES ('abc-def'), ('abcd-efgh'), ('other text'); +CREATE TABLE t2 (a TEXT, FULLTEXT(a) WITH PARSER simple_parser) +ENGINE=Aria ROW_FORMAT=PAGE TRANSACTIONAL=1; +INSERT INTO t2 SELECT a FROM t_src; +# restart +SELECT a FROM t2 WHERE MATCH(a) AGAINST('abc-def abcd'); +a +abc-def +CHECK TABLE t2 EXTENDED; +Table Op Msg_type Msg_text +test.t2 check status OK +DROP TABLE t2, t_src; +DROP TABLE t1, t_flush; +UNINSTALL PLUGIN simple_parser; diff --git a/mysql-test/suite/maria/fulltext_parser.test b/mysql-test/suite/maria/fulltext_parser.test new file mode 100644 index 0000000000000..799d1ffb602e5 --- /dev/null +++ b/mysql-test/suite/maria/fulltext_parser.test @@ -0,0 +1,87 @@ +--source include/have_maria.inc +--source include/have_simple_parser.inc +--source include/not_embedded.inc + +INSTALL PLUGIN simple_parser SONAME 'mypluglib'; + +# simple_parser keeps punctuation in words: 'abc-def abcd' finds +# 'abc-def', but not the fragment 'abcd' of 'abcd-efgh'. +--let $row_format= PAGE +--let $transactional= 1 +--source suite/maria/include/fulltext_parser_ops.inc +--let $row_format= DYNAMIC +--let $transactional= 0 +--source suite/maria/include/fulltext_parser_ops.inc + +--echo # Recover committed rows written after the last checkpoint +CREATE TABLE t1 (a TEXT, FULLTEXT(a) WITH PARSER simple_parser) + ENGINE=Aria ROW_FORMAT=PAGE TRANSACTIONAL=1; +INSERT INTO t1 VALUES ('abc-def'), ('baseline-two'), ('baseline-three'); +CREATE TABLE t_flush (a INT) ENGINE=Aria TRANSACTIONAL=1; +INSERT INTO t_flush VALUES (0); + +# A clean restart establishes the on-disk baseline. The .opt file disables +# periodic checkpoints and automatic repair, so they cannot hide failures. +--source include/restart_mysqld.inc +INSERT INTO t1 VALUES ('commit-one'), ('commit-two'); +--let $shutdown_timeout= 0 +--source include/restart_mysqld.inc + +# Check the recovered data and index before CHECK TABLE or REPAIR can help. +SELECT a FROM t1 ORDER BY a; +SELECT a FROM t1 WHERE MATCH(a) AGAINST('commit-one commit-two') ORDER BY a; +CHECK TABLE t1 EXTENDED; + +--echo # Roll back row and fulltext key changes without loading the parser +# Aria defers its commit until UNLOCK TABLES, unlike ordinary statements. +--connect (writer,localhost,root,,test) +LOCK TABLES t1 WRITE; +INSERT INTO t1 VALUES ('undo-insert'); +UPDATE t1 SET a = 'undo-update' WHERE a = 'abc-def'; +DELETE FROM t1 WHERE a = 'baseline-two'; + +--connection default +# Committing another Aria table flushes the preceding UNDO and REDO records +# without committing the writer or flushing t1's dirty pages. +INSERT INTO t_flush VALUES (1); +--let $shutdown_timeout= 0 +--source include/restart_mysqld.inc +--disconnect writer + +# The row checks alone could pass if recovery skipped t1 and its changes +# never reached disk. Require a completed rollback with no skipped UNDO +# records from this restart; the writer is the only uncommitted transaction. +--let $assert_file= $MYSQLTEST_VARDIR/log/mysqld.1.err +--let $assert_only_after= Starting MariaDB +--let $assert_text= Recovery rolled back the writer transaction +--let $assert_select= transactions to roll back: 1 0 +--let $assert_count= 1 +--source include/assert_grep.inc +--let $assert_text= Recovery did not skip UNDO records +--let $assert_select= UNDO records skipped in UNDO phase +--let $assert_count= 0 +--source include/assert_grep.inc + +SELECT a FROM t1 ORDER BY a; +SELECT a FROM t1 + WHERE MATCH(a) AGAINST('abc-def baseline-two undo-insert undo-update') + ORDER BY a; +CHECK TABLE t1 EXTENDED; + +--echo # Recover a completed bulk-insert repair with a parser plugin +# INSERT ... SELECT into an empty table disables the keys and rebuilds them +# with a repair. Its state LSNs normally make recovery skip REDO_REPAIR_TABLE. +# fulltext_parser_debug covers a repair record newer than the saved state LSNs. +CREATE TABLE t_src (a TEXT) ENGINE=Aria; +INSERT INTO t_src VALUES ('abc-def'), ('abcd-efgh'), ('other text'); +CREATE TABLE t2 (a TEXT, FULLTEXT(a) WITH PARSER simple_parser) + ENGINE=Aria ROW_FORMAT=PAGE TRANSACTIONAL=1; +INSERT INTO t2 SELECT a FROM t_src; +--let $shutdown_timeout= 0 +--source include/restart_mysqld.inc +SELECT a FROM t2 WHERE MATCH(a) AGAINST('abc-def abcd'); +CHECK TABLE t2 EXTENDED; +DROP TABLE t2, t_src; + +DROP TABLE t1, t_flush; +UNINSTALL PLUGIN simple_parser; diff --git a/mysql-test/suite/maria/fulltext_parser_debug-master.opt b/mysql-test/suite/maria/fulltext_parser_debug-master.opt new file mode 100644 index 0000000000000..79c596c97e106 --- /dev/null +++ b/mysql-test/suite/maria/fulltext_parser_debug-master.opt @@ -0,0 +1,2 @@ +--aria-checkpoint-interval=0 +--aria-recover-options=OFF diff --git a/mysql-test/suite/maria/fulltext_parser_debug.result b/mysql-test/suite/maria/fulltext_parser_debug.result new file mode 100644 index 0000000000000..46711deb71111 --- /dev/null +++ b/mysql-test/suite/maria/fulltext_parser_debug.result @@ -0,0 +1,64 @@ +INSTALL PLUGIN simple_parser SONAME 'mypluglib'; +# Keep the durable repair result without replaying it +CREATE TABLE t1 (a TEXT, FULLTEXT(a) WITH PARSER simple_parser) +ENGINE=Aria ROW_FORMAT=PAGE TRANSACTIONAL=1; +INSERT INTO t1 VALUES +('abc-def'), ('abcd-efgh'), ('plain words'), ('other text'), ('more rows'); +FLUSH TABLES t1; +SET SESSION debug_dbug='+d,maria_crash_after_repair_log'; +REPAIR TABLE t1; +# restart +SELECT a FROM t1 WHERE MATCH(a) AGAINST('abc-def abcd') ORDER BY a; +a +abc-def +CHECK TABLE t1 EXTENDED; +Table Op Msg_type Msg_text +test.t1 check status OK +DROP TABLE t1; +# Recover changes committed after a bulk-insert repair record +CREATE TABLE t_src (a TEXT) ENGINE=Aria; +INSERT INTO t_src VALUES +('abcd-efgh'), ('keep-one'), ('keep-two'), +('update-before'), ('delete-before'); +CREATE TABLE t1 (a TEXT, FULLTEXT(a) WITH PARSER simple_parser) +ENGINE=Aria ROW_FORMAT=PAGE TRANSACTIONAL=1; +CREATE TABLE t_flush (a INT) ENGINE=Aria ROW_FORMAT=PAGE TRANSACTIONAL=1; +INSERT INTO t_flush VALUES (0); +# restart +connect bulk,localhost,root,,test; +SET DEBUG_SYNC= +'maria_before_repair_log SIGNAL repair_ready WAIT_FOR log_advanced'; +INSERT INTO t1 SELECT a FROM t_src; +connection default; +SET DEBUG_SYNC= 'now WAIT_FOR repair_ready'; +INSERT INTO t_flush VALUES (1); +SET DEBUG_SYNC= 'now SIGNAL log_advanced'; +connection bulk; +SET DEBUG_SYNC= 'RESET'; +INSERT INTO t1 VALUES ('commit-one'), ('commit-two'); +UPDATE t1 SET a= 'update-after' WHERE a= 'update-before'; +DELETE FROM t1 WHERE a= 'delete-before'; +connection default; +INSERT INTO t_flush VALUES (2); +# restart +disconnect bulk; +SELECT a FROM t1 ORDER BY a; +a +abcd-efgh +commit-one +commit-two +keep-one +keep-two +update-after +SELECT a FROM t1 +WHERE MATCH(a) AGAINST('abcd-efgh commit-one update-after update-before') +ORDER BY a; +a +abcd-efgh +commit-one +update-after +CHECK TABLE t1 EXTENDED; +Table Op Msg_type Msg_text +test.t1 check status OK +DROP TABLE t1, t_src, t_flush; +UNINSTALL PLUGIN simple_parser; diff --git a/mysql-test/suite/maria/fulltext_parser_debug.test b/mysql-test/suite/maria/fulltext_parser_debug.test new file mode 100644 index 0000000000000..e6491aaa5d630 --- /dev/null +++ b/mysql-test/suite/maria/fulltext_parser_debug.test @@ -0,0 +1,82 @@ +--source include/have_maria.inc +--source include/have_simple_parser.inc +--source include/have_debug.inc +--source include/have_debug_sync.inc +--source include/not_embedded.inc +--source include/not_valgrind.inc +--source include/not_crashrep.inc + +INSTALL PLUGIN simple_parser SONAME 'mypluglib'; + +--echo # Keep the durable repair result without replaying it +CREATE TABLE t1 (a TEXT, FULLTEXT(a) WITH PARSER simple_parser) + ENGINE=Aria ROW_FORMAT=PAGE TRANSACTIONAL=1; +INSERT INTO t1 VALUES + ('abc-def'), ('abcd-efgh'), ('plain words'), ('other text'), ('more rows'); + +# Closing the share makes repair log a new FILE_ID before REDO_REPAIR_TABLE. +# The repair record is then strictly newer than protect_against_repair_crash's +# skip_redo_lsn, even without concurrent writes to the log. +FLUSH TABLES t1; +SET SESSION debug_dbug='+d,maria_crash_after_repair_log'; +--source include/expect_crash.inc +--disable_result_log +# send/reap, so that --sp-protocol does not wrap the crashing statement +--send REPAIR TABLE t1 +--error 2013,2026 +--reap +--enable_result_log +--source include/start_mysqld.inc + +# The table must be usable immediately, with the plugin's index intact. +SELECT a FROM t1 WHERE MATCH(a) AGAINST('abc-def abcd') ORDER BY a; +CHECK TABLE t1 EXTENDED; +DROP TABLE t1; + +--echo # Recover changes committed after a bulk-insert repair record +CREATE TABLE t_src (a TEXT) ENGINE=Aria; +INSERT INTO t_src VALUES + ('abcd-efgh'), ('keep-one'), ('keep-two'), + ('update-before'), ('delete-before'); +CREATE TABLE t1 (a TEXT, FULLTEXT(a) WITH PARSER simple_parser) + ENGINE=Aria ROW_FORMAT=PAGE TRANSACTIONAL=1; +CREATE TABLE t_flush (a INT) ENGINE=Aria ROW_FORMAT=PAGE TRANSACTIONAL=1; +INSERT INTO t_flush VALUES (0); + +# Establish an empty on-disk t1 before the bulk insert. +--source include/restart_mysqld.inc +--connect (bulk,localhost,root,,test) +SET DEBUG_SYNC= + 'maria_before_repair_log SIGNAL repair_ready WAIT_FOR log_advanced'; +--send INSERT INTO t1 SELECT a FROM t_src + +--connection default +SET DEBUG_SYNC= 'now WAIT_FOR repair_ready'; +# The quick, keys-only repair has written t1's state. Advance the log before +# its repair record, making that record strictly newer than is_of_horizon. +# T_NO_CREATE_RENAME_LSN prevents the repair from updating the LSNs later. +INSERT INTO t_flush VALUES (1); +SET DEBUG_SYNC= 'now SIGNAL log_advanced'; + +--connection bulk +--reap +SET DEBUG_SYNC= 'RESET'; +# Each statement commits. Keep the table open and its changed pages cached: +# the .opt file disables checkpoints, and we do not flush or check t1. +INSERT INTO t1 VALUES ('commit-one'), ('commit-two'); +UPDATE t1 SET a= 'update-after' WHERE a= 'update-before'; +DELETE FROM t1 WHERE a= 'delete-before'; + +--connection default +INSERT INTO t_flush VALUES (2); +--let $shutdown_timeout= 0 +--source include/restart_mysqld.inc +--disconnect bulk + +SELECT a FROM t1 ORDER BY a; +SELECT a FROM t1 + WHERE MATCH(a) AGAINST('abcd-efgh commit-one update-after update-before') + ORDER BY a; +CHECK TABLE t1 EXTENDED; +DROP TABLE t1, t_src, t_flush; +UNINSTALL PLUGIN simple_parser; diff --git a/mysql-test/suite/maria/include/fulltext_parser_ops.inc b/mysql-test/suite/maria/include/fulltext_parser_ops.inc new file mode 100644 index 0000000000000..a76194950dd69 --- /dev/null +++ b/mysql-test/suite/maria/include/fulltext_parser_ops.inc @@ -0,0 +1,38 @@ +# RENAME TABLE, ALTER TABLE ... ALGORITHM=COPY, REPAIR TABLE and ADD FULLTEXT +# on an Aria table with a fulltext parser plugin, each on its own table, +# with $row_format and $transactional. +--echo # Fulltext parser with ROW_FORMAT=$row_format +--let $create= CREATE TABLE t1 (a TEXT, FULLTEXT(a) WITH PARSER simple_parser) ENGINE=Aria ROW_FORMAT=$row_format TRANSACTIONAL=$transactional +--let $rows= INSERT INTO t1 VALUES ('abc-def'), ('abcd-efgh'), ('other text') + +eval $create; +eval $rows; +RENAME TABLE t1 TO t2; +SELECT a FROM t2 WHERE MATCH(a) AGAINST('abc-def abcd'); +DROP TABLE t2; + +eval $create; +eval $rows; +ALTER TABLE t1 ADD COLUMN b INT, ALGORITHM=COPY; +SELECT a FROM t1 WHERE MATCH(a) AGAINST('abc-def abcd'); +DROP TABLE t1; + +# Default repair sorts keys; EXTENDED repairs through the key cache. +eval $create; +eval $rows; +REPAIR TABLE t1; +SELECT a FROM t1 WHERE MATCH(a) AGAINST('abc-def abcd'); +DROP TABLE t1; + +eval $create; +eval $rows; +REPAIR TABLE t1 EXTENDED; +SELECT a FROM t1 WHERE MATCH(a) AGAINST('abc-def abcd'); +DROP TABLE t1; + +eval CREATE TABLE t1 (a TEXT) + ENGINE=Aria ROW_FORMAT=$row_format TRANSACTIONAL=$transactional; +eval $rows; +ALTER TABLE t1 ADD FULLTEXT(a) WITH PARSER simple_parser, ALGORITHM=COPY; +SELECT a FROM t1 WHERE MATCH(a) AGAINST('abc-def abcd'); +DROP TABLE t1; diff --git a/storage/maria/ma_check.c b/storage/maria/ma_check.c index b41570e9535bf..3c5f6ebf06f6c 100644 --- a/storage/maria/ma_check.c +++ b/storage/maria/ma_check.c @@ -6686,11 +6686,14 @@ static my_bool create_new_data_handle(MARIA_SORT_PARAM *param, File new_file) MARIA_SORT_INFO *sort_info= param->sort_info; MARIA_HA *info= sort_info->info; MARIA_HA *new_info; + uint open_flags= HA_OPEN_COPY | HA_OPEN_FOR_REPAIR | HA_OPEN_INTERNAL_TABLE; DBUG_ENTER("create_new_data_handle"); + /* Keys are rebuilt using info, which already has the fulltext parsers. */ + if (info->s->options & HA_OPTION_RELIES_ON_SQL_LAYER) + open_flags|= HA_OPEN_FROM_SQL_LAYER; if (!(sort_info->new_info= maria_open(info->s->open_file_name.str, O_RDWR, - HA_OPEN_COPY | HA_OPEN_FOR_REPAIR | - HA_OPEN_INTERNAL_TABLE, 0))) + open_flags, 0))) DBUG_RETURN(1); new_info= sort_info->new_info; @@ -7011,7 +7014,17 @@ my_bool write_log_record_for_repair(const HA_CHECK *param, MARIA_HA *info) log_array[TRANSLOG_INTERNAL_PARTS + 0].str= log_data; log_array[TRANSLOG_INTERNAL_PARTS + 0].length= sizeof(log_data); + /* + Repair has flushed pages and state, including for a quick index rebuild. + Recovery cannot replay this repair without the SQL layer, so sync the + file contents before logging it. MY_SYNC_DIR only made renames durable. + */ + if ((share->options & HA_OPTION_RELIES_ON_SQL_LAYER) && + _ma_sync_table_files(info)) + return TRUE; + share->now_transactional= 1; + DEBUG_SYNC_C("maria_before_repair_log"); if (unlikely(translog_write_record(&lsn, LOGREC_REDO_REPAIR_TABLE, &dummy_transaction_object, info, (translog_size_t) sizeof(log_data), @@ -7019,6 +7032,11 @@ my_bool write_log_record_for_repair(const HA_CHECK *param, MARIA_HA *info) log_array, log_data, NULL) || translog_flush(lsn))) return TRUE; + DBUG_EXECUTE_IF("maria_crash_after_repair_log", + { + DBUG_PRINT("maria_crash_after_repair_log", ("now")); + DBUG_SUICIDE(); + }); /* The table's existence was made durable earlier (MY_SYNC_DIR passed to maria_change_to_newfile()). All pages have been flushed, state too, we @@ -7031,6 +7049,7 @@ my_bool write_log_record_for_repair(const HA_CHECK *param, MARIA_HA *info) The fully correct order would be: sync data and index file, remove crash mark and update LSNs then write state and sync index file. But at this point state (without crash mark) is already written. + SQL-layer tables were synced above; sync again for the updated LSNs. */ if ((!(param->testflag & T_NO_CREATE_RENAME_LSN) && _ma_update_state_lsns(share, lsn, share->state.create_trid, FALSE, diff --git a/storage/maria/ma_recovery.c b/storage/maria/ma_recovery.c index 962e10998c2ce..a693815136973 100644 --- a/storage/maria/ma_recovery.c +++ b/storage/maria/ma_recovery.c @@ -50,6 +50,7 @@ static LSN current_group_end_lsn; static MARIA_HA *current_group_table; #endif static my_bool skip_DDLs; /**< if REDO phase should skip DDL records */ +static my_bool recovery_from_sql_layer; /**< server startup, not aria_read_log */ /** @brief to avoid writing a checkpoint if recovery did nothing. */ static my_bool checkpoint_useful; static my_bool in_redo_phase; @@ -228,6 +229,7 @@ int maria_recovery_from_log(void) DBUG_ASSERT(!maria_in_recovery); maria_in_recovery= TRUE; + recovery_from_sql_layer= TRUE; #ifdef EXTRA_DEBUG fn_format(name_buff, "aria_recovery.trace", maria_data_root, "", MYF(0)); @@ -250,6 +252,7 @@ int maria_recovery_from_log(void) if (trace_file) my_fclose(trace_file, MYF(0)); maria_in_recovery= FALSE; + recovery_from_sql_layer= FALSE; DBUG_RETURN(res); } @@ -1198,6 +1201,18 @@ prototype_redo_exec_hook(REDO_REPAIR_TABLE) DBUG_RETURN(0); } + if (info->s->options & HA_OPTION_RELIES_ON_SQL_LAYER) + { + /* + Parser plugins are not available until the SQL layer opens the table. + write_log_record_for_repair() syncs these tables before logging repair, + so keep the rebuilt files and allow subsequent REDO and UNDO records. + */ + tprint(tracef, "we skip repair requiring the SQL layer;" + " files were synced before logging\n"); + DBUG_RETURN(0); + } + /* Otherwise, the mapping is newer than the table, and our record is newer than the mapping, so we can repair. @@ -1404,7 +1419,16 @@ static int new_table(uint16 sid, const char *name, LSN lsn_of_file_id) goto end; } tprint(tracef, "Table '%s', id %u", name, sid); - info= maria_open(name, O_RDWR, HA_OPEN_FOR_REPAIR, 0); + /* + Row and key recovery uses logged values, without fulltext parsers. + REDO_REPAIR_TABLE keeps the indexes already rebuilt by the SQL layer. + Keep refusing these tables in aria_read_log. + In server recovery, maria_in_recovery already triggers the state LSN + initialization done by HA_OPEN_FROM_SQL_LAYER. + */ + info= maria_open(name, O_RDWR, + HA_OPEN_FOR_REPAIR | + (recovery_from_sql_layer ? HA_OPEN_FROM_SQL_LAYER : 0), 0); if (info == NULL) { tprint(tracef, ", is absent (must have been dropped later?)" @@ -1472,7 +1496,9 @@ static int new_table(uint16 sid, const char *name, LSN lsn_of_file_id) { tprint(tracef, "\n"); eprint(tracef, "Table '%s' is crashed, skipping it. Please repair it with" - " aria_chk -r", share->open_file_name.str); + " %s", share->open_file_name.str, + (share->options & HA_OPTION_RELIES_ON_SQL_LAYER) ? + "REPAIR TABLE" : "aria_chk -r"); recovery_found_crashed_tables++; error= -1; /* not fatal, try with other tables */ goto end; diff --git a/storage/maria/ma_rename.c b/storage/maria/ma_rename.c index ade6e52f3342e..c0e9f2d8aca0e 100644 --- a/storage/maria/ma_rename.c +++ b/storage/maria/ma_rename.c @@ -49,7 +49,15 @@ int maria_rename(const char *old_name, const char *new_name) _ma_check_table_is_closed(new_name,"rename new table2"); #endif /** @todo LOCK take X-lock on table */ - if (!(info= maria_open(old_name, O_RDWR, HA_OPEN_FOR_REPAIR, 0))) + info= maria_open(old_name, O_RDWR, HA_OPEN_FOR_REPAIR, 0); + /* + Retry with HA_OPEN_FROM_SQL_LAYER only for tables that need it, so that + its state LSN side effect does not apply to other tables. + */ + if (!info && my_errno == HA_ERR_UNSUPPORTED && maria_in_ha_maria) + info= maria_open(old_name, O_RDWR, + HA_OPEN_FOR_REPAIR | HA_OPEN_FROM_SQL_LAYER, 0); + if (!info) { int error= my_errno; /*