From bd127faa659bffb91cd659c5f739af30c8aba690 Mon Sep 17 00:00:00 2001 From: Fariha Shaikh Date: Fri, 22 Aug 2025 18:48:11 +0000 Subject: [PATCH 01/71] MDEV-33660 Add note when setting AUTO_INCREMENT lower than next value Currently, ALTER TABLE ... AUTO_INCREMENT = N silently fails when using values lower than the next AUTO_INCREMENT value in the column. Add a note when attempting to set AUTO_INCREMENT to a lower value than the next AUTO_INCREMENT value, and maintain the higher value. Update existing test suites and add a new test to the maria suite for the newly added note. All new code of the whole pull request, including one or several files that are either new files or modified ones, are contributed under the BSD-new license. I am contributing on behalf of my employer Amazon Web Services, Inc. --- mysql-test/main/alter_table.result | 2 + .../main/alter_table_autoinc-5574.result | 2 + .../suite/innodb/r/autoinc_persist.result | 8 +++ .../innodb/r/innodb-alter-autoinc.result | 6 ++ .../innodb/r/innodb-autoinc-44030.result | 4 +- mysql-test/suite/maria/alter.result | 57 +++++++++++++++++++ mysql-test/suite/maria/alter.test | 33 +++++++++++ sql/sql_table.cc | 39 +++++++++++++ .../rocksdb/r/autoinc_crash_safe.result | 8 +++ .../r/autoinc_crash_safe_partition.result | 8 +++ .../mysql-test/rocksdb/r/autoinc_vars.result | 4 ++ .../mysql-test/rocksdb/r/tbl_opt_ai.result | 2 + 12 files changed, 172 insertions(+), 1 deletion(-) diff --git a/mysql-test/main/alter_table.result b/mysql-test/main/alter_table.result index 8560f80b050..390fda9f768 100644 --- a/mysql-test/main/alter_table.result +++ b/mysql-test/main/alter_table.result @@ -1297,6 +1297,8 @@ ALTER TABLE t1 DROP i, ADD i INT UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT = 1; +Warnings: +Note 4190 ALTER TABLE is implicitly changing the value of 'AUTO_INCREMENT' from '1' to '4' DROP TABLE t1; CREATE TABLE t1 (a CHAR(1), PRIMARY KEY (a(255))); ERROR HY000: Incorrect prefix key; the used key part isn't a string, the used length is longer than the key part, or the storage engine doesn't support unique prefix keys diff --git a/mysql-test/main/alter_table_autoinc-5574.result b/mysql-test/main/alter_table_autoinc-5574.result index 9476313c773..0050d8bb352 100644 --- a/mysql-test/main/alter_table_autoinc-5574.result +++ b/mysql-test/main/alter_table_autoinc-5574.result @@ -2,6 +2,8 @@ create table t1(a int(10)unsigned not null auto_increment primary key, b varchar(255) not null) engine=innodb default charset=utf8; insert into t1 values(1,'aaa'),(2,'bbb'); alter table t1 auto_increment=1; +Warnings: +Note 4190 ALTER TABLE is implicitly changing the value of 'AUTO_INCREMENT' from '1' to '3' insert into t1 values(NULL, 'ccc'); select * from t1; a b diff --git a/mysql-test/suite/innodb/r/autoinc_persist.result b/mysql-test/suite/innodb/r/autoinc_persist.result index 8cbaac22763..8620897bdce 100644 --- a/mysql-test/suite/innodb/r/autoinc_persist.result +++ b/mysql-test/suite/innodb/r/autoinc_persist.result @@ -588,6 +588,8 @@ Expect 200 # This will not change the counter to 150, but to 201, which is the next # of current max counter in the table ALTER TABLE t3 AUTO_INCREMENT = 150; +Warnings: +Note 4190 ALTER TABLE is implicitly changing the value of 'AUTO_INCREMENT' from '150' to '201' SHOW CREATE TABLE t3; Table Create Table t3 CREATE TABLE `t3` ( @@ -629,6 +631,8 @@ UPDATE t3 SET a = 200 WHERE a = 150; INSERT INTO t3 VALUES(220); # This still fails to set to 120, but just 221 ALTER TABLE t3 AUTO_INCREMENT = 120; +Warnings: +Note 4190 ALTER TABLE is implicitly changing the value of 'AUTO_INCREMENT' from '120' to '221' SHOW CREATE TABLE t3; Table Create Table t3 CREATE TABLE `t3` ( @@ -751,6 +755,8 @@ t_inplace CREATE TABLE `t_inplace` ( # current max counter in the table, since the specified value # is smaller than the existing biggest value(50 < 123) ALTER TABLE t_inplace DROP COLUMN b, AUTO_INCREMENT = 50, ALGORITHM = INPLACE; +Warnings: +Note 4190 ALTER TABLE is implicitly changing the value of 'AUTO_INCREMENT' from '50' to '123' # We expect the counter to be 123 SHOW CREATE TABLE t_inplace; Table Create Table @@ -839,6 +845,8 @@ t_copy CREATE TABLE `t_copy` ( # current max counter in the table, since the specified value # is smaller than the existing biggest value(50 < 123) ALTER TABLE t_copy DROP COLUMN b, AUTO_INCREMENT = 50, ALGORITHM = COPY; +Warnings: +Note 4190 ALTER TABLE is implicitly changing the value of 'AUTO_INCREMENT' from '50' to '123' # We expect the counter to be 123 SHOW CREATE TABLE t_copy; Table Create Table diff --git a/mysql-test/suite/innodb/r/innodb-alter-autoinc.result b/mysql-test/suite/innodb/r/innodb-alter-autoinc.result index 3186b00aa38..f40264910e2 100644 --- a/mysql-test/suite/innodb/r/innodb-alter-autoinc.result +++ b/mysql-test/suite/innodb/r/innodb-alter-autoinc.result @@ -22,6 +22,8 @@ a id SET AUTO_INCREMENT_INCREMENT = 5, AUTO_INCREMENT_OFFSET = 30; ALTER TABLE t1 DROP PRIMARY KEY, ADD id INT AUTO_INCREMENT PRIMARY KEY, DROP COLUMN id, AUTO_INCREMENT = 42, ALGORITHM=COPY; +Warnings: +Note 4190 ALTER TABLE is implicitly changing the value of 'AUTO_INCREMENT' from '42' to '70' SHOW CREATE TABLE t1; Table Create Table t1 CREATE TABLE `t1` ( @@ -54,6 +56,8 @@ DROP COLUMN id, AUTO_INCREMENT = 42, LOCK=NONE; ERROR 0A000: LOCK=NONE is not supported. Reason: Adding an auto-increment column requires a lock. Try LOCK=SHARED ALTER TABLE t1 DROP PRIMARY KEY, ADD id INT AUTO_INCREMENT PRIMARY KEY, DROP COLUMN id, AUTO_INCREMENT = 42, ALGORITHM=INPLACE; +Warnings: +Note 4190 ALTER TABLE is implicitly changing the value of 'AUTO_INCREMENT' from '42' to '70' SELECT * FROM t1; a id 6 45 @@ -134,6 +138,8 @@ t1 CREATE TABLE `t1` ( ) ENGINE=InnoDB AUTO_INCREMENT=75 DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci ALTER TABLE t1 DROP PRIMARY KEY, ADD id INT AUTO_INCREMENT PRIMARY KEY, DROP COLUMN id, AUTO_INCREMENT = 42, ALGORITHM=COPY; +Warnings: +Note 4190 ALTER TABLE is implicitly changing the value of 'AUTO_INCREMENT' from '42' to '70' SELECT * FROM t1; a id 6 45 diff --git a/mysql-test/suite/innodb/r/innodb-autoinc-44030.result b/mysql-test/suite/innodb/r/innodb-autoinc-44030.result index 8db5952d1d0..60a2fcfdc81 100644 --- a/mysql-test/suite/innodb/r/innodb-autoinc-44030.result +++ b/mysql-test/suite/innodb/r/innodb-autoinc-44030.result @@ -24,7 +24,9 @@ d1 3 ALTER TABLE t1 AUTO_INCREMENT = 3; affected rows: 0 -info: Records: 0 Duplicates: 0 Warnings: 0 +info: Records: 0 Duplicates: 0 Warnings: 1 +Warnings: +Note 4190 ALTER TABLE is implicitly changing the value of 'AUTO_INCREMENT' from '3' to '4' SHOW CREATE TABLE t1; Table Create Table t1 CREATE TABLE `t1` ( diff --git a/mysql-test/suite/maria/alter.result b/mysql-test/suite/maria/alter.result index 9ba9338b861..33e25c37736 100644 --- a/mysql-test/suite/maria/alter.result +++ b/mysql-test/suite/maria/alter.result @@ -185,6 +185,63 @@ SET GLOBAL aria_max_sort_file_size= @max_size.save; # End of 10.4 test # # +# MDEV-33660 ALTER TABLE ... AUTO_INCREMENT = NN; should return a Warning if it can not use the provided value +# +CREATE TABLE t1 (id INT AUTO_INCREMENT PRIMARY KEY) ENGINE=Aria; +INSERT INTO t1 VALUES (1),(2),(3),(4),(5); +# Attempt to set AUTO_INCREMENT to a lower value +ALTER TABLE t1 AUTO_INCREMENT=3; +Warnings: +Note 4190 ALTER TABLE is implicitly changing the value of 'AUTO_INCREMENT' from '3' to '6' +SHOW WARNINGS; +Level Code Message +Note 4190 ALTER TABLE is implicitly changing the value of 'AUTO_INCREMENT' from '3' to '6' +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + PRIMARY KEY (`id`) +) ENGINE=Aria AUTO_INCREMENT=6 DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci PAGE_CHECKSUM=1 +# Verify that next insert uses the correct value +INSERT INTO t1 VALUES (NULL); +SELECT * FROM t1; +id +1 +2 +3 +4 +5 +6 +DROP TABLE t1; +# Test with CREATE TABLE and explicit AUTO_INCREMENT value +CREATE TABLE t1 (id INT AUTO_INCREMENT PRIMARY KEY) AUTO_INCREMENT=3 ENGINE=Aria; +INSERT INTO t1 VALUES (NULL), (NULL); +SELECT * FROM t1; +id +3 +4 +# Attempt to set AUTO_INCREMENT to a lower value +ALTER TABLE t1 AUTO_INCREMENT=3; +Warnings: +Note 4190 ALTER TABLE is implicitly changing the value of 'AUTO_INCREMENT' from '3' to '5' +SHOW WARNINGS; +Level Code Message +Note 4190 ALTER TABLE is implicitly changing the value of 'AUTO_INCREMENT' from '3' to '5' +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + PRIMARY KEY (`id`) +) ENGINE=Aria AUTO_INCREMENT=5 DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci PAGE_CHECKSUM=1 +# Verify that next insert uses the correct value +INSERT INTO t1 VALUES (NULL); +SELECT * FROM t1; +id +3 +4 +5 +DROP TABLE t1; +# # MDEV-25505 Assertion `old_flags == ((my_flags & 0x10000U) ? 1 : 0)' # failed in my_realloc # diff --git a/mysql-test/suite/maria/alter.test b/mysql-test/suite/maria/alter.test index a68b5f2e0d7..763283acf7d 100644 --- a/mysql-test/suite/maria/alter.test +++ b/mysql-test/suite/maria/alter.test @@ -193,6 +193,39 @@ SET GLOBAL aria_max_sort_file_size= @max_size.save; --echo # End of 10.4 test --echo # +--echo # +--echo # MDEV-33660 ALTER TABLE ... AUTO_INCREMENT = NN; should return a Warning if it can not use the provided value +--echo # +CREATE TABLE t1 (id INT AUTO_INCREMENT PRIMARY KEY) ENGINE=Aria; +INSERT INTO t1 VALUES (1),(2),(3),(4),(5); + +--echo # Attempt to set AUTO_INCREMENT to a lower value +ALTER TABLE t1 AUTO_INCREMENT=3; +SHOW WARNINGS; +SHOW CREATE TABLE t1; + +--echo # Verify that next insert uses the correct value +INSERT INTO t1 VALUES (NULL); +SELECT * FROM t1; + +DROP TABLE t1; + +--echo # Test with CREATE TABLE and explicit AUTO_INCREMENT value +CREATE TABLE t1 (id INT AUTO_INCREMENT PRIMARY KEY) AUTO_INCREMENT=3 ENGINE=Aria; +INSERT INTO t1 VALUES (NULL), (NULL); +SELECT * FROM t1; + +--echo # Attempt to set AUTO_INCREMENT to a lower value +ALTER TABLE t1 AUTO_INCREMENT=3; +SHOW WARNINGS; +SHOW CREATE TABLE t1; + +--echo # Verify that next insert uses the correct value +INSERT INTO t1 VALUES (NULL); +SELECT * FROM t1; + +DROP TABLE t1; + --echo # --echo # MDEV-25505 Assertion `old_flags == ((my_flags & 0x10000U) ? 1 : 0)' --echo # failed in my_realloc diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 533c9d720ff..021dca8b1e9 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -144,6 +144,40 @@ static bool append_table_to_dir(THD *thd, const char **filename_ptr, return 1; } +/** + Issue a note when ALTER TABLE ... AUTO_INCREMENT=N is used with N lower + than the next AUTO_INCREMENT value, in which case the engine keeps the + higher value. + + @param thd Thread handle + @param create_info Create info with requested auto_increment value + @param table The table being altered +*/ + +static void +check_auto_increment_lower_than_next(THD *thd, + const HA_CREATE_INFO *create_info, + TABLE *table) +{ + if (!(create_info->used_fields & HA_CREATE_USED_AUTO) || + create_info->auto_increment_value == 0 || + !table->found_next_number_field) + return; + + table->file->info(HA_STATUS_AUTO); + ulonglong next_auto_inc= table->file->stats.auto_increment_value; + if (next_auto_inc > create_info->auto_increment_value) + { + char llbuff[22], llbuff2[22]; + push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, + WARN_OPTION_CHANGING, + ER_THD(thd, WARN_OPTION_CHANGING), + "ALTER TABLE", "AUTO_INCREMENT", + llstr(create_info->auto_increment_value, llbuff), + llstr(next_auto_inc, llbuff2)); + } +} + /** @brief Helper function for explain_filename @param thd Thread handle @@ -8266,6 +8300,8 @@ static bool mysql_inplace_alter_table(THD *thd, commit_succeded_with_error= 1; } + check_auto_increment_lower_than_next(thd, ha_alter_info->create_info, table); + close_all_tables_for_name(thd, table->s, alter_ctx->is_table_renamed() ? HA_EXTRA_PREPARE_FOR_RENAME : @@ -11730,6 +11766,9 @@ do_continue:; if (wait_for_master(thd)) goto err_new_table_cleanup; } + + check_auto_increment_lower_than_next(thd, create_info, new_table); + if (table->s->tmp_table != NO_TMP_TABLE) { /* Release lock if this is a transactional temporary table */ diff --git a/storage/rocksdb/mysql-test/rocksdb/r/autoinc_crash_safe.result b/storage/rocksdb/mysql-test/rocksdb/r/autoinc_crash_safe.result index 60395eced7e..aaa032cd1eb 100644 --- a/storage/rocksdb/mysql-test/rocksdb/r/autoinc_crash_safe.result +++ b/storage/rocksdb/mysql-test/rocksdb/r/autoinc_crash_safe.result @@ -104,22 +104,30 @@ select table_schema, table_name, auto_increment from information_schema.tables w table_schema table_name auto_increment test t 1000 alter table t auto_increment = 1; +Warnings: +Note 4190 ALTER TABLE is implicitly changing the value of 'AUTO_INCREMENT' from '1' to '124' include/rpl_restart_server.inc [server_number=1] select table_schema, table_name, auto_increment from information_schema.tables where table_name = 't'; table_schema table_name auto_increment test t 124 alter table t drop primary key, add key (i), auto_increment = 1; +Warnings: +Note 4190 ALTER TABLE is implicitly changing the value of 'AUTO_INCREMENT' from '1' to '124' include/rpl_restart_server.inc [server_number=1] select table_schema, table_name, auto_increment from information_schema.tables where table_name = 't'; table_schema table_name auto_increment test t 124 alter table t add key (j), auto_increment = 1; +Warnings: +Note 4190 ALTER TABLE is implicitly changing the value of 'AUTO_INCREMENT' from '1' to '124' include/rpl_restart_server.inc [server_number=1] select table_schema, table_name, auto_increment from information_schema.tables where table_name = 't'; table_schema table_name auto_increment test t 124 alter table t modify i int; alter table t add column (k int auto_increment), add key(k), auto_increment=15; +Warnings: +Note 4190 ALTER TABLE is implicitly changing the value of 'AUTO_INCREMENT' from '15' to '16' include/rpl_restart_server.inc [server_number=1] select table_schema, table_name, auto_increment from information_schema.tables where table_name = 't'; table_schema table_name auto_increment diff --git a/storage/rocksdb/mysql-test/rocksdb/r/autoinc_crash_safe_partition.result b/storage/rocksdb/mysql-test/rocksdb/r/autoinc_crash_safe_partition.result index c837fb7c77d..19171653e98 100644 --- a/storage/rocksdb/mysql-test/rocksdb/r/autoinc_crash_safe_partition.result +++ b/storage/rocksdb/mysql-test/rocksdb/r/autoinc_crash_safe_partition.result @@ -104,22 +104,30 @@ select table_schema, table_name, auto_increment from information_schema.tables w table_schema table_name auto_increment test t 1000 alter table t auto_increment = 1; +Warnings: +Note 4190 ALTER TABLE is implicitly changing the value of 'AUTO_INCREMENT' from '1' to '1000' include/rpl_restart_server.inc [server_number=1] select table_schema, table_name, auto_increment from information_schema.tables where table_name = 't'; table_schema table_name auto_increment test t 124 alter table t drop primary key, add key (i), auto_increment = 1; +Warnings: +Note 4190 ALTER TABLE is implicitly changing the value of 'AUTO_INCREMENT' from '1' to '124' include/rpl_restart_server.inc [server_number=1] select table_schema, table_name, auto_increment from information_schema.tables where table_name = 't'; table_schema table_name auto_increment test t 124 alter table t add key (j), auto_increment = 1; +Warnings: +Note 4190 ALTER TABLE is implicitly changing the value of 'AUTO_INCREMENT' from '1' to '124' include/rpl_restart_server.inc [server_number=1] select table_schema, table_name, auto_increment from information_schema.tables where table_name = 't'; table_schema table_name auto_increment test t 124 alter table t modify i int; alter table t add column (k int auto_increment), add key(k), auto_increment=15; +Warnings: +Note 4190 ALTER TABLE is implicitly changing the value of 'AUTO_INCREMENT' from '15' to '16' include/rpl_restart_server.inc [server_number=1] select table_schema, table_name, auto_increment from information_schema.tables where table_name = 't'; table_schema table_name auto_increment diff --git a/storage/rocksdb/mysql-test/rocksdb/r/autoinc_vars.result b/storage/rocksdb/mysql-test/rocksdb/r/autoinc_vars.result index ef173234d70..583c51d27b4 100644 --- a/storage/rocksdb/mysql-test/rocksdb/r/autoinc_vars.result +++ b/storage/rocksdb/mysql-test/rocksdb/r/autoinc_vars.result @@ -157,6 +157,8 @@ CREATE TABLE t1 (pk INT AUTO_INCREMENT, a INT, PRIMARY KEY(pk)) ENGINE=RocksDB; INSERT INTO t1 (a) VALUES (1); UPDATE t1 SET pk = 3; ALTER TABLE t1 AUTO_INCREMENT 2; +Warnings: +Note 4190 ALTER TABLE is implicitly changing the value of 'AUTO_INCREMENT' from '2' to '4' DROP TABLE t1; #---------------------------------- # Issue #792 Crash in autoincrement @@ -175,6 +177,8 @@ CREATE TABLE t1 (pk INT AUTO_INCREMENT, a INT, PRIMARY KEY(pk)) ENGINE=RocksDB; INSERT INTO t1 (a) VALUES (1); UPDATE t1 SET pk = 3; ALTER TABLE t1 AUTO_INCREMENT 2; +Warnings: +Note 4190 ALTER TABLE is implicitly changing the value of 'AUTO_INCREMENT' from '2' to '4' DROP TABLE t1; #---------------------------------- # Issue #902 Debug assert in autoincrement with small field type diff --git a/storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_ai.result b/storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_ai.result index b9ce363f916..003820983e1 100644 --- a/storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_ai.result +++ b/storage/rocksdb/mysql-test/rocksdb/r/tbl_opt_ai.result @@ -23,6 +23,8 @@ a 10 100 ALTER TABLE t1 AUTO_INCREMENT=50; +Warnings: +Note 4190 ALTER TABLE is implicitly changing the value of 'AUTO_INCREMENT' from '50' to '101' SHOW CREATE TABLE t1; Table Create Table t1 CREATE TABLE `t1` ( From 2fa3b0f0c4b66f302b70e213fdf83f866adff7ec Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Thu, 13 Aug 2026 12:04:38 +1000 Subject: [PATCH 02/71] MDEV-37224 Remove UBSAN limitation from MTR tests Having a not_ubsan.inc as a test case exclusion mechanism is allowing developers to ignore UBSAN issues. As undefined behaviour detected at runtime or compile time isn't acceptable in the code base, remove the exclusion. The lotofstack test, the only user of not_ubsan.inc, has this exclusion because the stack size under UBSAN lacks predictability. Adjust its exclusion because of this criteria, and not its UBSAN status. Reviewer: Jimmy Hu --- mysql-test/include/not_ubsan.inc | 8 -------- mysql-test/main/lotofstack.test | 8 ++++++-- 2 files changed, 6 insertions(+), 10 deletions(-) delete mode 100644 mysql-test/include/not_ubsan.inc diff --git a/mysql-test/include/not_ubsan.inc b/mysql-test/include/not_ubsan.inc deleted file mode 100644 index 237adcb4896..00000000000 --- a/mysql-test/include/not_ubsan.inc +++ /dev/null @@ -1,8 +0,0 @@ -# This file should only be used with test that finds bugs in ASan that can not -# be overcome. In normal cases one should fix the bug server/test case or in -# the worst case add a (temporary?) suppression in asan.supp or lsan.supp - -if (`select count(*) from information_schema.system_variables where variable_name='have_sanitizer' and global_value LIKE "%UBSAN"`) -{ ---skip Can't be run with UBSAN -} diff --git a/mysql-test/main/lotofstack.test b/mysql-test/main/lotofstack.test index 20d9789b99c..5c624daff7e 100644 --- a/mysql-test/main/lotofstack.test +++ b/mysql-test/main/lotofstack.test @@ -5,11 +5,15 @@ # making writing tests that depend on reaching # the depth hard. # -source include/not_asan.inc; source include/not_embedded.inc; -source include/not_ubsan.inc; # MSAN with debug exceeds the stack early. source include/not_msan_with_debug.inc; +if (`select count(*) from information_schema.system_variables + where variable_name='have_sanitizer' + and global_value REGEXP '(A|UB)SAN'`) +{ +--skip Can't be run in UBSAN/ASAN (stack size predictability) +} # # Bug#10100 function (and stored procedure?) recursivity problem From bc8ef06ee96002de33e35bbd43f077801290b692 Mon Sep 17 00:00:00 2001 From: Dave Gosselin Date: Mon, 13 Apr 2026 09:05:28 -0400 Subject: [PATCH 03/71] MDEV-39441: Add dbug_print() helpers for join-related types Introduces new dbug_print() functions for the following types: dbug_print(NESTED_JOIN) dbug_print(JOIN) dbug_print(TABLE_LIST) dbug_print(JOIN_TAB) dbug_print(List) Typically pointers to instances of the given types are passed. The dbug_print(List) is intended to show result row before sending to client (e.g., invoke in debugger while tracing end_send) but may work in other contexts. These functions produce nicely formatted output, please use them in conjunction with the formatted printfs available in GDB or LLDB: (gdb) printf "%s", dbug_print(join_tab) (lldb) p printf("%s", dbug_print(table)) Example output from dbug_print(JOIN) in LLDB. The last line with value 1302 is the number of characters produced by the printf command. (lldb) p printf("%s",dbug_print(join)) JOIN [0x15801bfe8] table_count=3 const_tables=0 join_list 0x158018340 [1 element(s)]: --- #0 --- [0x15801af10] "(nest_last_join)" join=INNER (outer_join=0) nested_join=0x15801b618 (elements=2) NESTED_JOIN [0x15801b618] used_tables: 0x0 not_null_tables: 0x0 n_tables: 0 counter: 0 nest_type: 1 (JOIN_OP_NEST) nj_map: 0x0 join_list: join_list 0x15801b618 [2 element(s)]: --- #0 --- [0x15801a308] "t3" join=LEFT (outer_join=1) map=0x4 ON: `test`.`t2`.`a` = `test`.`t3`.`a` --- #1 --- [0x158019b30] "(nest_last_join)" join=INNER (outer_join=0) nested_join=0x15801a238 (elements=2) NESTED_JOIN [0x15801a238] used_tables: 0x0 not_null_tables: 0x0 n_tables: 0 counter: 0 nest_type: 0 nj_map: 0x0 join_list: join_list 0x15801a238 [2 element(s)]: --- #0 --- [0x1580187c0] "t1" join=RIGHT (outer_join=2) map=0x1 ON: `test`.`t1`.`a` = `test`.`t2`.`a` --- #1 --- [0x158018f08] "t2" join=INNER (outer_join=0) map=0x2 (int) 1302 --- sql/item.cc | 437 ++++++++++++++++++++++++++++++++++++++++++++++ sql/sql_select.cc | 215 +++++++++++++++++++++++ 2 files changed, 652 insertions(+) diff --git a/sql/item.cc b/sql/item.cc index 2fc2647b42c..b3fbf6b72e8 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -11454,6 +11454,443 @@ const char *dbug_print(Item *x) { return dbug_print_item(x); } const char *dbug_print(SELECT_LEX *x) { return dbug_print_select(x); } const char *dbug_print(SELECT_LEX_UNIT *x) { return dbug_print_unit(x); } + +/* + Debugger helper functions for visualizing TABLE_LIST join structures. + + All functions return const char* into a static buffer, following the same + pattern as dbug_print_item() above. + + (gdb) p dbug_print(join_list) + (gdb) p dbug_print(table) + (gdb) p dbug_print(table->nested_join) + + + NOTE: for nice formatting in gdb: + (gdb) printf "%s", dbug_print(join_tab) + + NOTE: for nice formatting in lldb: + (lldb) p printf("%s", dbug_print(table)) +*/ + +static char dbug_join_print_buf[16384]; + +static void dbug_join_indent(String *out, int depth) +{ + for (int i= 0; i < depth; i++) + out->append(" ", 2); +} + +static const char *dbug_join_type_str(uint outer_join) +{ + if (!outer_join) + return "INNER"; + if (outer_join & JOIN_TYPE_RIGHT) + return "RIGHT"; + if (outer_join & JOIN_TYPE_LEFT) + return "LEFT"; + if (outer_join & JOIN_TYPE_OUTER) + return "OUTER(marker)"; + return "???"; +} + +static const char *dbug_safe_alias(const TABLE_LIST *tbl) +{ + if (tbl->alias.str && tbl->alias.length) + return tbl->alias.str; + if (tbl->table_name.str && tbl->table_name.length) + return tbl->table_name.str; + return ""; +} + +static void dbug_str_append(String *out, const char *s) +{ + out->append(s, strlen(s)); +} + +static void dbug_str_append_ptr(String *out, const void *p) +{ + char tmp[32]; + size_t len= (size_t)snprintf(tmp, sizeof(tmp), "%p", p); + out->append(tmp, len); +} + +static void dbug_str_append_uint(String *out, uint val) +{ + char tmp[16]; + size_t len= (size_t)snprintf(tmp, sizeof(tmp), "%u", val); + out->append(tmp, len); +} + +static void dbug_str_append_hex(String *out, ulonglong val) +{ + char tmp[32]; + size_t len= (size_t)snprintf(tmp, sizeof(tmp), "0x%llx", val); + out->append(tmp, len); +} + +static void dbug_str_append_item(String *out, Item *item) +{ + if (!item) + out->append("NULL", 4); + else + item->print(out, QT_ORDINARY); +} + +static void dbug_dump_table_to_str(String *out, const TABLE_LIST *tbl, + int depth) +{ + if (!tbl) + { + dbug_join_indent(out, depth); + out->append("(null TABLE_LIST*)\n", 19); + return; + } + + dbug_join_indent(out, depth); + + out->append('['); + dbug_str_append_ptr(out, tbl); + out->append("] \"", 3); + dbug_str_append(out, dbug_safe_alias(tbl)); + out->append('"'); + + out->append(" join=", 7); + dbug_str_append(out, dbug_join_type_str(tbl->outer_join)); + out->append(" (outer_join=", 13); + dbug_str_append_uint(out, tbl->outer_join); + out->append(')'); + + if (tbl->table) + { + out->append(" map=", 6); + dbug_str_append_hex(out, (ulonglong) tbl->table->map); + } + + if (tbl->nested_join) + { + out->append(" nested_join=", 14); + dbug_str_append_ptr(out, tbl->nested_join); + out->append(" (elements=", 11); + dbug_str_append_uint(out, tbl->nested_join->join_list.elements); + out->append(')'); + } + + if (tbl->sj_on_expr) + out->append(" SJ_NEST", 9); + + out->append('\n'); + + if (tbl->on_expr) + { + dbug_join_indent(out, depth + 1); + out->append("ON: ", 4); + dbug_str_append_item(out, tbl->on_expr); + out->append('\n'); + } + + if (tbl->sj_on_expr) + { + dbug_join_indent(out, depth + 1); + out->append("SJ_ON: ", 7); + dbug_str_append_item(out, tbl->sj_on_expr); + out->append('\n'); + } + + if (tbl->prep_on_expr && tbl->prep_on_expr != tbl->on_expr) + { + dbug_join_indent(out, depth + 1); + out->append("PREP_ON: ", 9); + dbug_str_append_item(out, tbl->prep_on_expr); + out->append('\n'); + } + + if (tbl->dep_tables) + { + dbug_join_indent(out, depth + 1); + out->append("dep_tables: ", 12); + dbug_str_append_hex(out, (ulonglong) tbl->dep_tables); + out->append('\n'); + } + + if (tbl->on_expr_dep_tables) + { + dbug_join_indent(out, depth + 1); + out->append("on_expr_dep_tables: ", 20); + dbug_str_append_hex(out, (ulonglong) tbl->on_expr_dep_tables); + out->append('\n'); + } +} + +static void dbug_dump_join_list_to_str(String *out, List *jl, + int depth, bool recurse); + +static void dbug_dump_nested_join_to_str(String *out, const NESTED_JOIN *nj, + int depth) +{ + if (!nj) + { + dbug_join_indent(out, depth); + out->append("(null NESTED_JOIN*)\n", 20); + return; + } + dbug_join_indent(out, depth); + out->append("NESTED_JOIN [", 13); + dbug_str_append_ptr(out, nj); + out->append("]\n", 2); + dbug_join_indent(out, depth + 1); + out->append("used_tables: ", 17); + dbug_str_append_hex(out, (ulonglong) nj->used_tables); + out->append('\n'); + dbug_join_indent(out, depth + 1); + out->append("not_null_tables: ", 17); + dbug_str_append_hex(out, (ulonglong) nj->not_null_tables); + out->append('\n'); + dbug_join_indent(out, depth + 1); + out->append("n_tables: ", 17); + dbug_str_append_uint(out, nj->n_tables); + out->append('\n'); + dbug_join_indent(out, depth + 1); + out->append("counter: ", 17); + dbug_str_append_uint(out, nj->counter); + out->append('\n'); + dbug_join_indent(out, depth + 1); + out->append("nest_type: ", 17); + dbug_str_append_uint(out, nj->nest_type); + if (nj->nest_type & JOIN_OP_NEST) + out->append(" (JOIN_OP_NEST)", 15); + if (nj->nest_type & REBALANCED_NEST) + out->append(" (REBALANCED_NEST)", 18); + out->append('\n'); + dbug_join_indent(out, depth + 1); + out->append("nj_map: ", 17); + dbug_str_append_hex(out, (ulonglong) nj->nj_map); + out->append('\n'); + dbug_join_indent(out, depth + 1); + out->append("join_list:\n", 11); + dbug_dump_join_list_to_str(out, + &const_cast(nj)->join_list, + depth + 2, true); +} + +static void dbug_dump_join_list_to_str(String *out, List *jl, + int depth, bool recurse) +{ + if (!jl) + { + dbug_join_indent(out, depth); + out->append("(null join_list)\n", 17); + return; + } + + dbug_join_indent(out, depth); + out->append("join_list ", 10); + dbug_str_append_ptr(out, jl); + out->append(" [", 2); + dbug_str_append_uint(out, jl->elements); + out->append(" element(s)]:\n", 14); + + List_iterator it(*jl); + TABLE_LIST *tbl; + uint idx= 0; + while ((tbl= it++)) + { + dbug_join_indent(out, depth); + out->append("--- #", 5); + dbug_str_append_uint(out, idx++); + out->append(" ---\n", 5); + dbug_dump_table_to_str(out, tbl, depth + 1); + + if (recurse && tbl->nested_join) + { + dbug_dump_nested_join_to_str(out, tbl->nested_join, depth + 2); + } + } +} + +static const char *dbug_return_join_buf(String *str, char *buf) +{ + if (str->c_ptr_safe() == buf) + return buf; + else + return "Couldn't fit into buffer"; +} + +const char *dbug_print_table(const TABLE_LIST *tbl) +{ + char *buf= dbug_join_print_buf; + String str(buf, sizeof(dbug_join_print_buf), &my_charset_bin); + str.length(0); + if (!tbl) + return "(TABLE_LIST*)NULL"; + dbug_dump_table_to_str(&str, tbl, 0); + return dbug_return_join_buf(&str, buf); +} + +const char *dbug_print_join_list(List *jl) +{ + char *buf= dbug_join_print_buf; + String str(buf, sizeof(dbug_join_print_buf), &my_charset_bin); + str.length(0); + if (!jl) + return "(List*)NULL"; + dbug_dump_join_list_to_str(&str, jl, 0, false); + return dbug_return_join_buf(&str, buf); +} + +const char *dbug_print_join_tree(List *jl) +{ + char *buf= dbug_join_print_buf; + String str(buf, sizeof(dbug_join_print_buf), &my_charset_bin); + str.length(0); + if (!jl) + return "(List*)NULL"; + dbug_dump_join_list_to_str(&str, jl, 0, true); + return dbug_return_join_buf(&str, buf); +} + +const char *dbug_print_embedding(const TABLE_LIST *tbl) +{ + char *buf= dbug_join_print_buf; + String str(buf, sizeof(dbug_join_print_buf), &my_charset_bin); + str.length(0); + if (!tbl) + return "(TABLE_LIST*)NULL"; + + int depth= 0; + for (const TABLE_LIST *cur= tbl; cur; cur= cur->embedding) + { + dbug_join_indent(&str, depth); + str.append('['); + dbug_str_append_ptr(&str, cur); + str.append("] \"", 3); + dbug_str_append(&str, dbug_safe_alias(cur)); + str.append("\" join=", 8); + dbug_str_append(&str, dbug_join_type_str(cur->outer_join)); + str.append(" nested_join=", 14); + dbug_str_append_ptr(&str, cur->nested_join); + if (cur->on_expr) + { + str.append(" ON=<", 6); + dbug_str_append_item(&str, cur->on_expr); + str.append('>'); + } + str.append('\n'); + depth++; + } + return dbug_return_join_buf(&str, buf); +} + +const char *dbug_print_nested_join(const NESTED_JOIN *nj) +{ + char *buf= dbug_join_print_buf; + String str(buf, sizeof(dbug_join_print_buf), &my_charset_bin); + str.length(0); + if (!nj) + return "(NESTED_JOIN*)NULL"; + dbug_dump_nested_join_to_str(&str, nj, 0); + return dbug_return_join_buf(&str, buf); +} + +const char *dbug_print_join(JOIN *j) +{ + char *buf= dbug_join_print_buf; + String str(buf, sizeof(dbug_join_print_buf), &my_charset_bin); + str.length(0); + if (!j) + return "(JOIN*)NULL"; + if (!j->join_list) + { + str.append("JOIN [", 6); + dbug_str_append_ptr(&str, j); + str.append("]: join_list is NULL\n", 21); + return dbug_return_join_buf(&str, buf); + } + str.append("JOIN [", 6); + dbug_str_append_ptr(&str, j); + str.append("] table_count=", 15); + dbug_str_append_uint(&str, j->table_count); + str.append(" const_tables=", 15); + dbug_str_append_uint(&str, j->const_tables); + str.append('\n'); + if (j->conds) + { + str.append(" WHERE: ", 9); + dbug_str_append_item(&str, j->conds); + str.append('\n'); + } + dbug_dump_join_list_to_str(&str, j->join_list, 0, true); + return dbug_return_join_buf(&str, buf); +} + +/* + Debugger helper function for visualizing a result set row. + + Prints the name and current value of each Item in the row list, + as they would be sent to the client by Protocol::send_result_set_row(). + The output format matches dbug_print_table_row(): + + result_set_row(name1, name2, ...)=(val1, val2, ...) + + (gdb) p dbug_print_result_set_row(&items) + (lldb) p dbug_print_result_set_row(&items) +*/ + +const char *dbug_print_result_set_row(List *row_items) +{ + char *buf= dbug_join_print_buf; + String str(buf, sizeof(dbug_join_print_buf), &my_charset_bin); + str.length(0); + if (!row_items) + return "(List*)NULL"; + + str.append(STRING_WITH_LEN("(")); + + List_iterator_fast name_it(*row_items); + bool first= true; + for (Item *item= name_it++; item; item= name_it++) + { + if (first) + first= false; + else + str.append(STRING_WITH_LEN(", ")); + + if (item->name.str) + str.append(item->name.str, item->name.length); + else + str.append(STRING_WITH_LEN("(no name)")); + } + + str.append(STRING_WITH_LEN(")=(")); + + List_iterator_fast val_it(*row_items); + String val_tmp; + first= true; + for (Item *item= val_it++; item; item= val_it++) + { + if (first) + first= false; + else + str.append(STRING_WITH_LEN(", ")); + + String *val= item->val_str(&val_tmp); + if (val) + str.append(val->ptr(), val->length()); + else + str.append(&NULL_clex_str); + } + + str.append(')'); + + return dbug_return_join_buf(&str, buf); +} + +const char *dbug_print(const TABLE_LIST *x) { return dbug_print_table(x); } +const char *dbug_print(List *x) { return dbug_print_join_tree(x); } +const char *dbug_print(const NESTED_JOIN *x) { return dbug_print_nested_join(x); } +const char *dbug_print(JOIN *x) { return dbug_print_join(x); } +const char *dbug_print(List *x) { return dbug_print_result_set_row(x); } + #endif /*DBUG_OFF*/ void Item::register_in(THD *thd) diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 05d3bae186c..f0ef090d250 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -8238,6 +8238,221 @@ const char* dbug_print_join_prefix(const POSITION *join_positions, else return "Couldn't fit into buffer"; } + +/* + Debugger helper function for visualizing a JOIN_TAB and its context. + + Shows the tab's identity, neighbors in the join_tab array, the + JOIN::return_tab pointer, the writing_null_complements flag, and + the logical execution call chain from do_select() down to this tab. + + (gdb) p dbug_print(join_tab) + + + NOTE: for nice formatting in gdb: + (gdb) printf "%s", dbug_print(join_tab-1) + + NOTE: for nice formatting in lldb: + (lldb) p printf("%s", dbug_print(join_tab)) +*/ + +static const char *dbug_next_select_func_name(Next_select_func func) +{ + if (!func) return "(null)"; + if (func == sub_select) return "sub_select"; + if (func == sub_select_cache) return "sub_select_cache"; + if (func == sub_select_postjoin_aggr) return "sub_select_postjoin_aggr"; + if (func == end_send) return "end_send"; + if (func == end_send_group) return "end_send_group"; + if (func == end_write) return "end_write"; + if (func == end_write_group) return "end_write_group"; + if (func == end_update) return "end_update"; + if (func == end_unique_update) return "end_unique_update"; + return "(unknown)"; +} + +static void dbug_str_append_tab_brief(String *out, const JOIN_TAB *t) +{ + char tmp[64]; + size_t len= (size_t)snprintf(tmp, sizeof(tmp), "[%p]", t); + out->append(tmp, len); + if (t && t->table) + { + out->append(" \"", 2); + out->append(t->table->alias); + out->append('"'); + } +} + +static char dbug_join_tab_print_buf[16384]; + +const char *dbug_print_join_tab(const JOIN_TAB *tab) +{ + char *buf= dbug_join_tab_print_buf; + String str(buf, sizeof(dbug_join_tab_print_buf), &my_charset_bin); + str.length(0); + char tmp[64]; + size_t len; + + if (!tab) + return "(JOIN_TAB*)NULL"; + + JOIN *join= tab->join; + + /* Compute position in the join_tab array */ + int tab_idx= -1; + int total_tabs= 0; + if (join && join->join_tab) + { + tab_idx= (int)(tab - join->join_tab); + total_tabs= join->top_join_tab_count + join->aggr_tables; + } + + /* Header */ + str.append("JOIN_TAB ", 9); + dbug_str_append_tab_brief(&str, tab); + str.append('\n'); + + /* Position in array */ + if (tab_idx >= 0) + { + str.append(" position: #", 13); + len= (size_t)snprintf(tmp, sizeof(tmp), "%d of %u", tab_idx+1, total_tabs); + str.append(tmp, len); + str.append(" (join_tab array ", 17); + len= (size_t)snprintf(tmp, sizeof(tmp), "[%p]", join->join_tab); + str.append(tmp, len); + str.append(")\n", 2); + } + + /* Access type */ + str.append(" type: ", 8); + str.append(join_type_str[tab->type], strlen(join_type_str[tab->type])); + str.append('\n'); + + /* Neighbors */ + if (tab_idx > 0) + { + str.append(" prev: ", 8); + dbug_str_append_tab_brief(&str, tab - 1); + str.append(" (", 2); + str.append(join_type_str[(tab - 1)->type], strlen(join_type_str[(tab - 1)->type])); + str.append(")\n", 2); + } + else + str.append(" prev: (none)\n", 15); + + if (tab_idx >= 0 && (tab_idx + 1) < total_tabs) + { + str.append(" next: ", 8); + dbug_str_append_tab_brief(&str, tab + 1); + str.append(" (", 2); + str.append(join_type_str[(tab + 1)->type], strlen(join_type_str[(tab + 1)->type])); + str.append(")\n", 2); + } + else + str.append(" next: (none)\n", 15); + + /* return_tab */ + str.append(" return_tab: ", 14); + if (join && join->return_tab) + { + dbug_str_append_tab_brief(&str, join->return_tab); + if (join->join_tab) + { + int ret_idx= (int)(join->return_tab - join->join_tab); + len= (size_t)snprintf(tmp, sizeof(tmp), " (#%d)", ret_idx+1); + str.append(tmp, len); + } + str.append('\n'); + } + else + str.append("(null)\n", 7); + + /* next_select function pointer */ + str.append(" next_select: ", 15); + str.append(dbug_next_select_func_name(tab->next_select), strlen(dbug_next_select_func_name(tab->next_select))); + str.append('\n'); + + /* Outer join pointers */ + if (tab->first_inner) + { + str.append(" first_inner: ", 15); + dbug_str_append_tab_brief(&str, tab->first_inner); + str.append('\n'); + } + if (tab->last_inner) + { + str.append(" last_inner: ", 14); + dbug_str_append_tab_brief(&str, tab->last_inner); + str.append('\n'); + } + if (tab->first_upper) + { + str.append(" first_upper: ", 15); + dbug_str_append_tab_brief(&str, tab->first_upper); + str.append('\n'); + } + + /* select_cond */ + str.append(" select_cond: ", 15); + if (tab->select_cond) + { + len= (size_t)snprintf(tmp, sizeof(tmp), "[%p]", tab->select_cond); + str.append(tmp, len); + str.append(" ", 2); + const char* item_buf= nullptr; + item_buf= dbug_print_item(tab->select_cond); + str.append(item_buf, strlen(item_buf)); + str.append('\n'); + } + else + { + str.append("(none)", 6); + str.append('\n'); + } + + /* + Logical call chain from do_select() to this tab. + + do_select() calls join->first_select(join, join_tab[const_tables], ...), + which is typically sub_select. Each tab's next_select is then used to + advance to the next tab in the nested loop. + */ + if (join && join->join_tab && tab_idx >= 0) + { + int start= (int)join->const_tables; + + for (int i= start; i < total_tabs; i++) + { + const JOIN_TAB *t= &join->join_tab[i]; + str.append(" #", 5); + len= (size_t)snprintf(tmp, sizeof(tmp), "%d ", i+1); + str.append(tmp, len); + dbug_str_append_tab_brief(&str, t); + str.append(" (", 2); + str.append(join_type_str[t->type], strlen(join_type_str[t->type])); + str.append(')'); + + str.append(" next_select=", 14); + str.append(dbug_next_select_func_name(t->next_select), strlen(dbug_next_select_func_name(t->next_select))); + + if (i == tab_idx) + str.append(" <-- current", 13); + + str.append('\n'); + } + } + + if (str.c_ptr_safe() == buf) + return buf; + else + return "Couldn't fit into buffer"; +} + + +const char *dbug_print(const JOIN_TAB *x) { return dbug_print_join_tab(x); } + #endif /** From 12016fdeedf771099059e367bf41a4bf4066b471 Mon Sep 17 00:00:00 2001 From: Alexey Yurchenko Date: Fri, 14 Aug 2026 17:37:39 +0200 Subject: [PATCH 04/71] MDEV-38147 add missing result file for MDEV-38147_gtid_off test --- .../r/MDEV-38147_gtid_off.result | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 mysql-test/suite/galera_3nodes/r/MDEV-38147_gtid_off.result diff --git a/mysql-test/suite/galera_3nodes/r/MDEV-38147_gtid_off.result b/mysql-test/suite/galera_3nodes/r/MDEV-38147_gtid_off.result new file mode 100644 index 00000000000..eb4e2fc8b5f --- /dev/null +++ b/mysql-test/suite/galera_3nodes/r/MDEV-38147_gtid_off.result @@ -0,0 +1,25 @@ +connection node_2; +connection node_1; +connection node_1; +connection node_2; +connection node_3; +# Precondition: Galera GTID mode is OFF +SELECT @@global.wsrep_gtid_mode AS wsrep_gtid_mode; +wsrep_gtid_mode +0 +connection node_1; +CREATE TABLE t1 (pk INT PRIMARY KEY) ENGINE=InnoDB; +INSERT INTO t1 VALUES (1),(2),(3),(4),(5); +connection node_3; +connection node_3; +connection node_1; +connection node_1; +connection node_3; +SELECT COUNT(*) AS rows_on_joiner FROM t1; +rows_on_joiner +5 +include/assert_grep.inc [joiner did not seed binlog GTID state in wsrep_gtid_mode=OFF] +connection node_1; +DROP TABLE t1; +disconnect node_2; +disconnect node_1; From 0fe578244bc2904ae482d7836a7e3d4415e3482f Mon Sep 17 00:00:00 2001 From: Mohammad Tafzeel Shams Date: Tue, 7 Jul 2026 16:58:55 +0530 Subject: [PATCH 05/71] MDEV-39795: Assertion `n_reserved > 0' failed Problem: ======== 1. Assertion `n_reserved > 0` failed in fseg_create(): fsp_reserve_free_extents() has a special condition for small tablespaces where it reserves individual pages instead of full extents. In such cases, n_reserved can be 0 even when the reservation succeeds, causing the assertion ut_ad(n_reserved > 0) to fail incorrectly. The code was checking n_reserved to determine whether a reservation had already been attempted, but this logic breaks for small tablespaces where pages, rather than extents, are reserved. 2. Encryption metadata not cleared for compressed-only pages: buf_page_encrypt() only cleared encryption-related metadata fields (key-version and crypt-checksum) when the page was neither encrypted nor compressed. However, these fields should also be cleared when page_compressed is true but encrypted is false, to avoid leaving stale encryption metadata in compressed-only pages. Solution: ========= buf_page_encrypt(): Refactored the early-return logic. Encryption metadata fields are now cleared whenever encrypted is false, regardless of page_compressed. The function returns early only when both !encrypted and !page_compressed. fseg_create(): Reintroduced a boolean variable `reserved` to track whether fsp_reserve_free_extents() has been attempted (removed as part of MDEV-38419 | c7313da), replacing assertion `n_reserved > 0`. Added an early return when DB_DECRYPTION_FAILED is encountered during inode allocation. my_error_innodb(): Added handling for DB_DECRYPTION_FAILED to report decryption errors to the user through ER_GET_ERRMSG. --- .../r/innodb-encryption-alter.result | 16 ++++++++++ .../innodb-page_encryption_compression.result | 19 ++++++++++++ .../encryption/t/innodb-encryption-alter.test | 19 ++++++++++++ .../t/innodb-page_encryption_compression.test | 31 +++++++++++++++++++ storage/innobase/buf/buf0flu.cc | 7 +++-- storage/innobase/fsp/fsp0fsp.cc | 20 +++++++++--- storage/innobase/handler/handler0alter.cc | 4 +++ 7 files changed, 108 insertions(+), 8 deletions(-) diff --git a/mysql-test/suite/encryption/r/innodb-encryption-alter.result b/mysql-test/suite/encryption/r/innodb-encryption-alter.result index 338d385afb1..35094e49098 100644 --- a/mysql-test/suite/encryption/r/innodb-encryption-alter.result +++ b/mysql-test/suite/encryption/r/innodb-encryption-alter.result @@ -110,3 +110,19 @@ f1 f2 5 6 7 8 drop table t1,t2; +# +# MDEV 39795 : Assertion `n_reserved > 0' failed +# +SET GLOBAL innodb_encrypt_tables = ON; +CREATE TABLE t1 ( +id INT NOT NULL AUTO_INCREMENT, +col1 INT, +col2 INT, +PRIMARY KEY (id) +) ENGINE=InnoDB ROW_FORMAT = Compact PAGE_COMPRESSED=1; +INSERT INTO t1 (col1, col2) VALUES (10, 100); +SET STATEMENT debug_dbug = 'd,inode_page_decryption_failed' FOR +ALTER TABLE t1 ADD INDEX idx_col1 (col1); +ERROR HY000: Got error 63 'Table is compressed or encrypted but uncompress or decrypt failed.' from InnoDB +SET GLOBAL innodb_encrypt_tables = OFF; +DROP TABLE t1; diff --git a/mysql-test/suite/encryption/r/innodb-page_encryption_compression.result b/mysql-test/suite/encryption/r/innodb-page_encryption_compression.result index b855ca7c1ca..396e602f641 100644 --- a/mysql-test/suite/encryption/r/innodb-page_encryption_compression.result +++ b/mysql-test/suite/encryption/r/innodb-page_encryption_compression.result @@ -1,3 +1,17 @@ +# +# MDEV 39795 : Assertion `n_reserved > 0' failed +# +SET GLOBAL innodb_encrypt_tables = ON; +SET GLOBAL innodb_compression_algorithm = 'none'; +CREATE TABLE t1 ( +id INT AUTO_INCREMENT PRIMARY KEY, +col1 INT, +col2 INT, +col3 INT +) ENGINE=InnoDB ROW_FORMAT = Compact PAGE_COMPRESSED=1; +INSERT INTO t1 (col1, col2, col3) VALUES (1, 10, 100); +FLUSH TABLES t1 FOR EXPORT; +UNLOCK TABLES; set global innodb_compression_algorithm = 1; create table innodb_normal(c1 bigint not null, b char(200)) engine=innodb page_compressed=1; show warnings; @@ -86,3 +100,8 @@ drop procedure innodb_insert_proc; drop table innodb_normal; drop table innodb_compact; drop table innodb_dynamic; +ALTER TABLE t1 ADD INDEX idx_col1 (col1); +# restart: --innodb-encrypt-tables=OFF +ALTER TABLE t1 ADD INDEX idx_col2_col3 (col2, col3); +# Cleanup +DROP TABLE t1; diff --git a/mysql-test/suite/encryption/t/innodb-encryption-alter.test b/mysql-test/suite/encryption/t/innodb-encryption-alter.test index 6e5d449b10a..98c82eb3351 100644 --- a/mysql-test/suite/encryption/t/innodb-encryption-alter.test +++ b/mysql-test/suite/encryption/t/innodb-encryption-alter.test @@ -131,3 +131,22 @@ disconnect con1; select * from t1; drop table t1,t2; + +--echo # +--echo # MDEV 39795 : Assertion `n_reserved > 0' failed +--echo # +SET GLOBAL innodb_encrypt_tables = ON; +CREATE TABLE t1 ( + id INT NOT NULL AUTO_INCREMENT, + col1 INT, + col2 INT, + PRIMARY KEY (id) +) ENGINE=InnoDB ROW_FORMAT = Compact PAGE_COMPRESSED=1; + +INSERT INTO t1 (col1, col2) VALUES (10, 100); +--error ER_GET_ERRMSG +SET STATEMENT debug_dbug = 'd,inode_page_decryption_failed' FOR +ALTER TABLE t1 ADD INDEX idx_col1 (col1); +# Cleanup +SET GLOBAL innodb_encrypt_tables = OFF; +DROP TABLE t1; diff --git a/mysql-test/suite/encryption/t/innodb-page_encryption_compression.test b/mysql-test/suite/encryption/t/innodb-page_encryption_compression.test index 5fe6f686576..c8cddb124be 100644 --- a/mysql-test/suite/encryption/t/innodb-page_encryption_compression.test +++ b/mysql-test/suite/encryption/t/innodb-page_encryption_compression.test @@ -3,7 +3,29 @@ -- source include/have_file_key_management_plugin.inc let $innodb_compression_algorithm_orig=`SELECT @@innodb_compression_algorithm`; +let $innodb_encrypt_tables_orig=`SELECT @@innodb_encrypt_tables`; +--echo # +--echo # MDEV 39795 : Assertion `n_reserved > 0' failed +--echo # + +SET GLOBAL innodb_encrypt_tables = ON; +SET GLOBAL innodb_compression_algorithm = 'none'; + +CREATE TABLE t1 ( + id INT AUTO_INCREMENT PRIMARY KEY, + col1 INT, + col2 INT, + col3 INT +) ENGINE=InnoDB ROW_FORMAT = Compact PAGE_COMPRESSED=1; +INSERT INTO t1 (col1, col2, col3) VALUES (1, 10, 100); +FLUSH TABLES t1 FOR EXPORT; UNLOCK TABLES; +# continue further test after restart + +# reset system +--disable_query_log +EVAL SET GLOBAL innodb_encrypt_tables = $innodb_encrypt_tables_orig; +--enable_query_log # zlib set global innodb_compression_algorithm = 1; @@ -71,3 +93,12 @@ drop table innodb_dynamic; --disable_query_log EVAL SET GLOBAL innodb_compression_algorithm = $innodb_compression_algorithm_orig; --enable_query_log + +# Continuing test for MDEV-39795 +ALTER TABLE t1 ADD INDEX idx_col1 (col1); + +--source include/restart_mysqld.inc +ALTER TABLE t1 ADD INDEX idx_col2_col3 (col2, col3); + +--echo # Cleanup +DROP TABLE t1; diff --git a/storage/innobase/buf/buf0flu.cc b/storage/innobase/buf/buf0flu.cc index 8da27adcd8c..c496bdc4b9f 100644 --- a/storage/innobase/buf/buf0flu.cc +++ b/storage/innobase/buf/buf0flu.cc @@ -622,9 +622,9 @@ static byte *buf_page_encrypt(fil_space_t *space, buf_page_t *bpage, byte *s, const bool full_crc32= space->full_crc32(); - if (!encrypted && !page_compressed) + if (!encrypted) { - /* No need to encrypt or compress. Clear key-version & crypt-checksum. */ + /* No need to encrypt. Clear key-version & crypt-checksum. */ static_assert(FIL_PAGE_FCRC32_KEY_VERSION % 4 == 0, "alignment"); static_assert(FIL_PAGE_FILE_FLUSH_LSN_OR_KEY_VERSION % 4 == 2, "not perfect alignment"); @@ -632,7 +632,8 @@ static byte *buf_page_encrypt(fil_space_t *space, buf_page_t *bpage, byte *s, memset_aligned<4>(s + FIL_PAGE_FCRC32_KEY_VERSION, 0, 4); else memset_aligned<2>(s + FIL_PAGE_FILE_FLUSH_LSN_OR_KEY_VERSION, 0, 8); - return s; + if (!page_compressed) + return s; } static_assert(FIL_PAGE_FCRC32_END_LSN % 4 == 0, "alignment"); diff --git a/storage/innobase/fsp/fsp0fsp.cc b/storage/innobase/fsp/fsp0fsp.cc index f0e78adb893..58bb00238da 100644 --- a/storage/innobase/fsp/fsp0fsp.cc +++ b/storage/innobase/fsp/fsp0fsp.cc @@ -1674,6 +1674,7 @@ fseg_create(fil_space_t *space, ulint byte_offset, mtr_t *mtr, dberr_t *err, fseg_inode_t* inode; ib_id_t seg_id; uint32_t n_reserved = 0; + bool reserved = false; DBUG_ENTER("fseg_create"); @@ -1697,20 +1698,29 @@ fseg_create(fil_space_t *space, ulint byte_offset, mtr_t *mtr, dberr_t *err, inode_alloc: inode = fsp_alloc_seg_inode(space, header, &iblock, mtr, err); - + DBUG_EXECUTE_IF("inode_page_decryption_failed", + *err = DB_DECRYPTION_FAILED; + inode = nullptr; + ); + if (*err == DB_DECRYPTION_FAILED) { + block = nullptr; + goto funct_exit; + } if (!inode) { block = nullptr; reserve_extent: - if (!has_done_reservation && !n_reserved) { + if (!has_done_reservation && !reserved) { *err = fsp_reserve_free_extents(&n_reserved, space, 2, FSP_NORMAL, mtr); if (UNIV_UNLIKELY(*err != DB_SUCCESS)) { DBUG_RETURN(nullptr); } - ut_ad(n_reserved > 0); - /* Extents reserved successfully. So - try allocating the page or inode */ + /* Extents (or pages for small table) + reserved successfully. + See fsp_reserve_free_extents() + So try allocating the page or inode */ + reserved = true; if (inode) { goto page_alloc; } diff --git a/storage/innobase/handler/handler0alter.cc b/storage/innobase/handler/handler0alter.cc index 3267df636c0..99413f79c93 100644 --- a/storage/innobase/handler/handler0alter.cc +++ b/storage/innobase/handler/handler0alter.cc @@ -918,6 +918,10 @@ my_error_innodb(dberr_t error, const char *table, ulint flags) case DB_CORRUPTION: my_error(ER_NOT_KEYFILE, MYF(0), table); break; + case DB_DECRYPTION_FAILED: + my_error(ER_GET_ERRMSG, MYF(0), error, + ut_strerr(error), "InnoDB"); + break; case DB_TOO_BIG_RECORD: { /* Note that in page0zip.ic page_zip_rec_needs_ext() rec_size is limited to COMPRESSED_REC_MAX_DATA_SIZE (16K) or From 3523ffdf971f60d8d91f571618c768057fae4632 Mon Sep 17 00:00:00 2001 From: sjaakola Date: Thu, 6 Aug 2026 13:43:48 +0300 Subject: [PATCH 06/71] MDEV-36677 rsync sst fails with different innodb_log_group_home_dir and datadir Backported the fix done by Pekka Lampio for mariaDB 11.4 in PR https://github.com/mariadb-corporation/codership-mariadb-server/pull/543 The PR has a fix for wsrep_sst_rsync script and new mtr test: galera_3nodes.galera_mdev_36677" to check that the rsync SST method of Galera works correctly also when the joiner node store InnoDB log files in a dedicated directory separate from the data dictionary Note: merging this PR to 11.4 may not be fully functional as there are other changes in the rsync SST script. Take a look at the original 11.4 PR when merging. --- .../galera_3nodes/r/galera_mdev_36677.result | 91 +++++++++++++ .../galera_3nodes/t/galera_mdev_36677.cnf | 16 +++ .../galera_3nodes/t/galera_mdev_36677.test | 126 ++++++++++++++++++ scripts/wsrep_sst_rsync.sh | 2 + 4 files changed, 235 insertions(+) create mode 100644 mysql-test/suite/galera_3nodes/r/galera_mdev_36677.result create mode 100644 mysql-test/suite/galera_3nodes/t/galera_mdev_36677.cnf create mode 100644 mysql-test/suite/galera_3nodes/t/galera_mdev_36677.test diff --git a/mysql-test/suite/galera_3nodes/r/galera_mdev_36677.result b/mysql-test/suite/galera_3nodes/r/galera_mdev_36677.result new file mode 100644 index 00000000000..6bebe95cd43 --- /dev/null +++ b/mysql-test/suite/galera_3nodes/r/galera_mdev_36677.result @@ -0,0 +1,91 @@ +connection node_2; +connection node_1; +connection node_1; +connection node_2; +connection node_3; +connection node_1; +connection node_2; +connection node_3; +connection node_1; +CREATE TABLE t1 (id int not null primary key,f1 CHAR(255)) ENGINE=InnoDB; +SET AUTOCOMMIT=OFF; +START TRANSACTION; +INSERT INTO t1 VALUES (1,'node1_committed_at_start'); +INSERT INTO t1 VALUES (2,'node1_committed_at_start'); +INSERT INTO t1 VALUES (3,'node1_committed_at_start'); +INSERT INTO t1 VALUES (4,'node1_committed_at_start'); +INSERT INTO t1 VALUES (5,'node1_committed_at_start'); +COMMIT; +connection node_2; +connection node_3; +connection node_2; +SET AUTOCOMMIT=OFF; +START TRANSACTION; +INSERT INTO t1 VALUES (6,'node2_committed_when_node3_down'); +INSERT INTO t1 VALUES (7,'node2_committed_when_node3_down'); +INSERT INTO t1 VALUES (8,'node2_committed_when_node3_down'); +INSERT INTO t1 VALUES (9,'node2_committed_when_node3_down'); +INSERT INTO t1 VALUES (10,'node2_committed_when_node3_down'); +COMMIT; +connection node_3; +# restart: --wsrep_sst_donor=node2 +SET AUTOCOMMIT=ON; +SET SESSION wsrep_sync_wait=15; +SELECT COUNT(*) AS EXPECT_10 FROM t1; +EXPECT_10 +10 +SELECT * from t1; +id f1 +1 node1_committed_at_start +2 node1_committed_at_start +3 node1_committed_at_start +4 node1_committed_at_start +5 node1_committed_at_start +6 node2_committed_when_node3_down +7 node2_committed_when_node3_down +8 node2_committed_when_node3_down +9 node2_committed_when_node3_down +10 node2_committed_when_node3_down +SELECT COUNT(*) = 0 FROM (SELECT COUNT(*) AS c, f1 FROM t1 GROUP BY f1 HAVING c NOT IN (5, 10)) AS a1; +COUNT(*) = 0 +1 +connection node_2; +connection node_3; +SET AUTOCOMMIT=OFF; +START TRANSACTION; +INSERT INTO t1 VALUES (11,'node2_committed_when_node2_down'); +INSERT INTO t1 VALUES (12,'node2_committed_when_node2_down'); +INSERT INTO t1 VALUES (13,'node2_committed_when_node2_down'); +INSERT INTO t1 VALUES (14,'node2_committed_when_node2_down'); +INSERT INTO t1 VALUES (15,'node2_committed_when_node2_down'); +COMMIT; +connection node_2; +# restart: --wsrep_sst_donor=node3 +SET AUTOCOMMIT=ON; +SET SESSION wsrep_sync_wait=15; +SELECT COUNT(*) AS EXPECT_15 FROM t1; +EXPECT_15 +15 +SELECT * from t1; +id f1 +1 node1_committed_at_start +2 node1_committed_at_start +3 node1_committed_at_start +4 node1_committed_at_start +5 node1_committed_at_start +6 node2_committed_when_node3_down +7 node2_committed_when_node3_down +8 node2_committed_when_node3_down +9 node2_committed_when_node3_down +10 node2_committed_when_node3_down +11 node2_committed_when_node2_down +12 node2_committed_when_node2_down +13 node2_committed_when_node2_down +14 node2_committed_when_node2_down +15 node2_committed_when_node2_down +SELECT COUNT(*) = 0 FROM (SELECT COUNT(*) AS c, f1 FROM t1 GROUP BY f1 HAVING c NOT IN (10, 15)) AS a1; +COUNT(*) = 0 +0 +connection node_1; +DROP TABLE t1; +COMMIT; diff --git a/mysql-test/suite/galera_3nodes/t/galera_mdev_36677.cnf b/mysql-test/suite/galera_3nodes/t/galera_mdev_36677.cnf new file mode 100644 index 00000000000..1ef6d902442 --- /dev/null +++ b/mysql-test/suite/galera_3nodes/t/galera_mdev_36677.cnf @@ -0,0 +1,16 @@ +!include ../galera_3nodes.cnf + +[mysqld] +wsrep_sst_method=rsync + +[mysqld.1] +wsrep_provider_options='pc.ignore_sb=true;repl.causal_read_timeout=PT90S;base_port=@mysqld.1.#galera_port;evs.suspect_timeout=PT10S;evs.inactive_timeout=PT30S;evs.install_timeout=PT15S;pc.wait_prim_timeout=PT60S;gcache.size=10M' + +[mysqld.2] +wsrep_provider_options='pc.ignore_sb=true;repl.causal_read_timeout=PT90S;base_port=@mysqld.2.#galera_port;evs.suspect_timeout=PT10S;evs.inactive_timeout=PT30S;evs.install_timeout=PT15S;pc.wait_prim_timeout=PT60S;gcache.size=10M' +innodb_log_group_home_dir=@ENV.MYSQL_TMP_DIR/mysqld.2/mdev_36677_log_dir +aria_log_dir_path=@ENV.MYSQL_TMP_DIR/mysqld.2/mdev_36677_aria_log_dir + +[mysqld.3] +wsrep_provider_options='pc.ignore_sb=true;repl.causal_read_timeout=PT90S;base_port=@mysqld.3.#galera_port;evs.suspect_timeout=PT10S;evs.inactive_timeout=PT30S;evs.install_timeout=PT15S;pc.wait_prim_timeout=PT60S;gcache.size=10M' + diff --git a/mysql-test/suite/galera_3nodes/t/galera_mdev_36677.test b/mysql-test/suite/galera_3nodes/t/galera_mdev_36677.test new file mode 100644 index 00000000000..679f4a00c1c --- /dev/null +++ b/mysql-test/suite/galera_3nodes/t/galera_mdev_36677.test @@ -0,0 +1,126 @@ +# +# MDEV-36677 +# +# This test checks the rsync SST works also when InnoDB log files +# reside in a dedicated directory other than the data directory. + +# The test comprises of two parts: in Part A the SST donor has InnoDB +# log files outside the data dictionary and in Part B the SST joiner +# has log files outside the data dictionary. +# + +--source include/galera_cluster.inc +--source include/have_innodb.inc +--source include/big_test.inc + +# Set up a 3-node cluster where node_2 has log files outside data dictionary +--let $galera_connection_name = node_3 +--let $galera_server_number = 3 +--source include/galera_connect.inc + +--let $node_1=node_1 +--let $node_2=node_2 +--let $node_3=node_3 +--source ../galera/include/auto_increment_offset_save.inc + +--let $wait_condition = SELECT VARIABLE_VALUE = 3 FROM information_schema.global_status WHERE VARIABLE_NAME = 'wsrep_cluster_size' +--connection node_1 +--source include/wait_condition.inc +--connection node_2 +--source include/wait_condition.inc +--connection node_3 +--source include/wait_condition.inc + +--connection node_1 +CREATE TABLE t1 (id int not null primary key,f1 CHAR(255)) ENGINE=InnoDB; +SET AUTOCOMMIT=OFF; +START TRANSACTION; +INSERT INTO t1 VALUES (1,'node1_committed_at_start'); +INSERT INTO t1 VALUES (2,'node1_committed_at_start'); +INSERT INTO t1 VALUES (3,'node1_committed_at_start'); +INSERT INTO t1 VALUES (4,'node1_committed_at_start'); +INSERT INTO t1 VALUES (5,'node1_committed_at_start'); +COMMIT; + + +--connection node_2 +--source include/galera_wait_ready.inc + +let $MYSQLD2_DATADIR= `SELECT @@datadir`; +let $MYSQLD2_INNODB_LOGDIR= `SELECT @@innodb_log_group_home_dir`; +let $MYSQLD2_ARIA_LOGDIR= `SELECT @@aria_log_dir_path`; + +# Part A: SST from node_2 to node_3 +--connection node_3 +--source include/shutdown_mysqld.inc +--remove_file $MYSQLTEST_VARDIR/mysqld.3/data/grastate.dat +--remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.3/data + +--connection node_2 +SET AUTOCOMMIT=OFF; +START TRANSACTION; +INSERT INTO t1 VALUES (6,'node2_committed_when_node3_down'); +INSERT INTO t1 VALUES (7,'node2_committed_when_node3_down'); +INSERT INTO t1 VALUES (8,'node2_committed_when_node3_down'); +INSERT INTO t1 VALUES (9,'node2_committed_when_node3_down'); +INSERT INTO t1 VALUES (10,'node2_committed_when_node3_down'); +COMMIT; + +--connection node_3 +--let $restart_parameters = --wsrep_sst_donor=node2 +--source include/start_mysqld.inc +--source include/galera_wait_ready.inc + +--let $wait_condition = SELECT COUNT(*)=10 FROM t1 +--source include/wait_condition.inc + +SET AUTOCOMMIT=ON; +SET SESSION wsrep_sync_wait=15; +SELECT COUNT(*) AS EXPECT_10 FROM t1; +SELECT * from t1; +SELECT COUNT(*) = 0 FROM (SELECT COUNT(*) AS c, f1 FROM t1 GROUP BY f1 HAVING c NOT IN (5, 10)) AS a1; + +# Part B: SST from node_3 to node_2 +--connection node_2 +--source include/shutdown_mysqld.inc +--remove_files_wildcard $MYSQLTEST_VARDIR/mysqld.2/data +--remove_files_wildcard $MYSQL_TMP_DIR/mysqld.2/mdev_36677_log_dir +--remove_files_wildcard $MYSQL_TMP_DIR/mysqld.2/mdev_36677_aria_log_dir + +--connection node_3 +SET AUTOCOMMIT=OFF; +START TRANSACTION; +INSERT INTO t1 VALUES (11,'node2_committed_when_node2_down'); +INSERT INTO t1 VALUES (12,'node2_committed_when_node2_down'); +INSERT INTO t1 VALUES (13,'node2_committed_when_node2_down'); +INSERT INTO t1 VALUES (14,'node2_committed_when_node2_down'); +INSERT INTO t1 VALUES (15,'node2_committed_when_node2_down'); +COMMIT; + +--connection node_2 +--let $restart_parameters = --wsrep_sst_donor=node3 +--source include/start_mysqld.inc +--source include/galera_wait_ready.inc + +--let $wait_condition = SELECT COUNT(*)=15 FROM t1 +--source include/wait_condition.inc + +# check that InnoDB log file was not copied to a data directory, but to a dedicated directory +--exec if [ -f $MYSQLD2_DATADIR/ib_logfile0 ]; then false; fi +--exec if [ ! -f $MYSQLD2_INNODB_LOGDIR/ib_logfile0 ]; then false; fi +# check that Aria log file was not copied to a data directory, but to a dedicated directory +--exec if [ -f $MYSQLD2_DATADIR/aria_log.00000001 ]; then false; fi +--exec if [ ! -f $MYSQLD2_ARIA_LOGDIR/aria_log.00000001 ]; then false; fi + +SET AUTOCOMMIT=ON; +SET SESSION wsrep_sync_wait=15; +SELECT COUNT(*) AS EXPECT_15 FROM t1; +SELECT * from t1; +SELECT COUNT(*) = 0 FROM (SELECT COUNT(*) AS c, f1 FROM t1 GROUP BY f1 HAVING c NOT IN (10, 15)) AS a1; + +# cleanup +--connection node_1 +--source ../galera/include/auto_increment_offset_restore.inc + +DROP TABLE t1; +COMMIT; diff --git a/scripts/wsrep_sst_rsync.sh b/scripts/wsrep_sst_rsync.sh index a559c6749e9..4146255c747 100644 --- a/scripts/wsrep_sst_rsync.sh +++ b/scripts/wsrep_sst_rsync.sh @@ -324,6 +324,8 @@ RC=0 if [ "$WSREP_SST_OPT_ROLE" = 'donor' ]; then + create_dirs + if [ -n "$STUNNEL" ]; then cat << EOF > "$STUNNEL_CONF" key = $SSTKEY From 8f00e6caca633c783140db86d3a48a96de67cf38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Tue, 18 Aug 2026 11:14:26 +0300 Subject: [PATCH 07/71] MDEV-40756 Incorrect multi-batch recovery of file size file_name_t::page0_lsn: Keep track of the last applied recv_sys_t::parse_page0() so that a multi-batch recovery will not reset the file to a smaller size. Reviewed by: Thirunarayanan Balathandayuthapani --- storage/innobase/log/log0recv.cc | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/storage/innobase/log/log0recv.cc b/storage/innobase/log/log0recv.cc index f82556d3f3b..f43e0569c5d 100644 --- a/storage/innobase/log/log0recv.cc +++ b/storage/innobase/log/log0recv.cc @@ -650,6 +650,9 @@ struct file_name_t { /** Log sequence number of a FILE_CREATE record, or 0 */ lsn_t create_lsn = 0; + /** Last FSP_SIZE or FSP_SPACE_FLAGS change, or 0 */ + lsn_t page0_lsn = 0; + /** FSP_SIZE of tablespace */ uint32_t size = 0; @@ -2659,12 +2662,19 @@ void recv_sys_t::parse_page0(const page_id_t id, const byte *b, f{flags ? mach_read_from_4(FSP_HEADER_OFFSET + FSP_SPACE_FLAGS + b) : file_name_t::initial_flags}; recv_spaces_t::iterator it= recv_spaces.find(space_id); - if (it != recv_spaces.end() && !it->second.space) + if (it != recv_spaces.end()) { - if (size) - it->second.size= s; - if (flags) - it->second.flags= f; + if (it->second.page0_lsn > lsn) + return; + it->second.page0_lsn= lsn; + + if (!it->second.space) + { + if (size) + it->second.size= s; + if (flags) + it->second.flags= f; + } } fil_space_set_recv_size_and_flags(space_id, s, f); } From 83c5d87464801eb23e23ff4bcbc6b7c6f9b7edd4 Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Thu, 13 Aug 2026 17:42:19 +1000 Subject: [PATCH 08/71] MDEV-40750 gcc-16.1.0 on ppc64 causes innodb to fail to compile Assembler comes up with the error: unrecognized opcode: `dcbstps' dcbstps is a Power 10 instruction. The default target arch on most platforms is Power 8 or 9. Added the target power10 to the function pmem_phwsync. The execution of this function is gated on the ISA 3.1 in pmem_persist_init so there's no chance of a SIGILL. clang supports this target as arch=pwr10 and gcc as cpu=power10. Revert back to using opcodes for old versions. --- storage/innobase/sync/cache.cc | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/storage/innobase/sync/cache.cc b/storage/innobase/sync/cache.cc index 064c29d6f02..c43a1dae508 100644 --- a/storage/innobase/sync/cache.cc +++ b/storage/innobase/sync/cache.cc @@ -99,6 +99,17 @@ static decltype(pmem_control::persist) pmem_persist_init() return (getauxval(AT_HWCAP) & HWCAP_DCPOP) ? pmem_cvap : pmem_cvac; } # elif defined __powerpc64__ + +# if defined(__clang__) +# define HAVE_TARGET_POWER10 1 +__attribute__((target("arch=pwr10"))) +# elif defined(__GNUC__) + // GCC: Power10 support starts with GCC 10. +# if (__GNUC__ == 10 && __GNUC_MINOR__ >= 1) +# define HAVE_TARGET_POWER10 1 +__attribute__((target("cpu=power10"))) +# endif +# endif static void pmem_phwsync(const void* buf, size_t size) { for (uintptr_t u= uintptr_t(buf) & ~(CPU_LEVEL1_DCACHE_LINESIZE), @@ -111,14 +122,14 @@ static void pmem_phwsync(const void* buf, size_t size) Let us hope that having a recent enough GCC is an adequate proxy for having a recent enough assembler. */ -# if __GNUC__ >= 11 || (defined __clang_major__ && __clang_major__ >= 12) +# ifdef HAVE_TARGET_POWER10 __asm__ __volatile__("dcbstps 0,%0" :: "r"(u) : "memory"); # else __asm__ __volatile__(".long (0x7cc000AC | %0 << 11)" :: "r"(u) : "memory"); # endif } -# if __GNUC__ >= 11 || (defined __clang_major__ && __clang_major__ >= 18) +# ifdef HAVE_TARGET_POWER10 __asm__ __volatile__("phwsync" ::: "memory"); # else __asm__ __volatile__(".long 0x7c80040a" ::: "memory"); From 6415902490b1b0a9a500cee9bc0a41c342190605 Mon Sep 17 00:00:00 2001 From: Yuchen Pei Date: Thu, 20 Aug 2026 11:46:36 +1000 Subject: [PATCH 09/71] MDEV-40805 Do not call lock_rec_convert_impl_to_expl if a table S-lock is held lock_clust_rec_read_check_and_lock() skipped the implicit-to-explicit conversion only under a table LOCK_X. When a table LOCK_S is held the conversion is equally pointless: no other transaction can hold an implicit X-lock on the record, because modifying a row requires a table LOCK_IX and LOCK_IX is incompatible with our LOCK_S. lock_table_has() matches stronger modes, so testing LOCK_S subsumes the old LOCK_X test. --- storage/innobase/lock/lock0lock.cc | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/storage/innobase/lock/lock0lock.cc b/storage/innobase/lock/lock0lock.cc index 4838b4c8fb4..588e0dd8c36 100644 --- a/storage/innobase/lock/lock0lock.cc +++ b/storage/innobase/lock/lock0lock.cc @@ -6336,8 +6336,15 @@ lock_clust_rec_read_check_and_lock( const ulint heap_no = lock_get_heap_no(*block, rec); trx_t *trx = thr_get_trx(thr); - if (lock_table_has(trx, index->table, LOCK_X) - || heap_no == PAGE_HEAP_NO_SUPREMUM) { + if (heap_no == PAGE_HEAP_NO_SUPREMUM + || lock_table_has(trx, index->table, LOCK_S)) { + /* A conflicting record lock (implicit or explicit LOCK_X) + may only be acquired by a transaction that holds LOCK_IX + on the table; @see lock_rec_lock(). + + Our LOCK_S on the table is mutually exclusive with LOCK_IX. + + Any LOCK_S record locks do not conflict with us. */ } else if (lock_rec_convert_impl_to_expl(trx, *block, rec, index, offsets) == trx && gap_mode == LOCK_REC_NOT_GAP) { From 5af13b65a3277f7e57aadf9ef43e90fac4af4b13 Mon Sep 17 00:00:00 2001 From: drrtuy Date: Thu, 20 Aug 2026 21:03:04 +0100 Subject: [PATCH 10/71] fix: MDEV-40846 DuckDB handles functionality that is based on invisible columns, e.g. WITH SYSTEM VERSIONING. --- storage/duckdb/docs/architecture.md | 49 ++++-- storage/duckdb/ha_duckdb_pushdown.cc | 152 ++++++++++++++++-- .../duckdb/r/cross_engine_join.result | 63 ++++++++ .../duckdb/r/cross_engine_ryow.result | 45 ++++++ .../duckdb/t/cross_engine_join.test | 72 +++++++++ .../duckdb/t/cross_engine_ryow.test | 46 ++++++ storage/duckdb/runtime/cross_engine_scan.cc | 2 +- 7 files changed, 409 insertions(+), 20 deletions(-) diff --git a/storage/duckdb/docs/architecture.md b/storage/duckdb/docs/architecture.md index 3b5a2709133..6a6d7cf01d1 100644 --- a/storage/duckdb/docs/architecture.md +++ b/storage/duckdb/docs/architecture.md @@ -57,8 +57,9 @@ Primary path for analytical queries. MariaDB hands the **entire SELECT** to Duck ``` MariaDB parser → optimizer calls hton->create_select / hton->create_unit - → can_pushdown_to_duckdb(): at least one DuckDB table? - → creates ha_duckdb_select_handler + → factory eligibility checks in ha_duckdb_pushdown.cc + → requires at least one DuckDB table and supported query/table metadata + → creates ha_duckdb_select_handler → init_scan(): 1. Takes original SQL text from THD::query() 2. Rewrites MariaDB-specific syntax for DuckDB: @@ -74,11 +75,35 @@ MariaDB parser → end_scan(): releases result ``` -#### Single-SELECT pushdown eligibility +#### Pushdown eligibility -The single-SELECT handler declines pushdown when MariaDB marks the top-level -`SELECT_LEX` with `UNCACHEABLE_SIDEEFFECT`. The regular handler path (`rnd_*`) -is used instead. MariaDB sets this flag for: +Both the single-SELECT and whole-unit factories in `ha_duckdb_pushdown.cc` +decline pushdown for: + +- an explicit `FOR SYSTEM_TIME` clause, because DuckDB does not implement + MariaDB system-versioning syntax; +- an explicit reference to an invisible column, including references in the + select list, `WHERE`, `HAVING`, `GROUP BY`, `ORDER BY`, join conditions, and + window `PARTITION BY` / `ORDER BY` specifications; +- an external table where a visible column follows an invisible column, because + `_mdb_scan` can expose only a visible prefix while preserving MariaDB field + indexes; +- any invisible column on an external table when + `duckdb_cross_engine_ryow=ON`; the direct handler scan bypasses the synthetic + MariaDB SELECT that handles hidden metadata and implicit conditions. + +With `duckdb_cross_engine_ryow=OFF`, trailing invisible columns are supported: +`_mdb_scan` exposes only the visible prefix, while MariaDB applies implicit +system-versioning conditions inside the synthetic external-table SELECT. +Automatically generated system-versioning field references do not count as +explicit references. + +Returning no select handler makes MariaDB use its regular execution path; that +fallback can itself reject a mixed-engine plan if it requests an unsupported +DuckDB handler operation. + +The single-SELECT factory additionally declines pushdown when MariaDB marks the +top-level `SELECT_LEX` with `UNCACHEABLE_SIDEEFFECT`. MariaDB sets this flag for: - reads and assignments of user variables (`@var`, `@var := expr`); - reads of session or global system variables (`@@session.var`, `@@global.var`); @@ -115,10 +140,14 @@ The fiber runs on the same OS thread as DuckDB. TLS (`current_thd`, `THR_KEY_mys #### Pushdown modes: `duckdb_cross_engine_ryow` -The session variable `duckdb_cross_engine_ryow` (default `OFF`) selects, per -query, how external (non-DuckDB) tables are read. The flag is captured once in -`init_scan()` (`register_cross_engine_ryow()`), and the replacement scan then -redirects to one of two table functions. +For an eligible query, the session variable `duckdb_cross_engine_ryow` +(default `OFF`) selects how external (non-DuckDB) tables are read. The flag is +captured by `ha_duckdb_select_handler::init_scan()` in +`ha_duckdb_pushdown.cc`, which calls `register_cross_engine_ryow()`; the +replacement scan then redirects to one of two table functions. Eligibility is +checked first: with RYOW enabled, an external table containing any invisible +column makes the factory decline pushdown rather than select the direct table +function. | Aspect | `duckdb_cross_engine_ryow = OFF` (default) | `duckdb_cross_engine_ryow = ON` | |---|---|---| diff --git a/storage/duckdb/ha_duckdb_pushdown.cc b/storage/duckdb/ha_duckdb_pushdown.cc index 74e00f823ad..b1985f2cd99 100644 --- a/storage/duckdb/ha_duckdb_pushdown.cc +++ b/storage/duckdb/ha_duckdb_pushdown.cc @@ -21,6 +21,7 @@ #include #include "sql_class.h" #include "sql_select.h" +#include "sql_window.h" #include "log.h" #undef UNKNOWN @@ -35,6 +36,115 @@ extern handlerton *duckdb_hton; +class Invisible_field_enumerator : public Field_enumerator +{ +public: + explicit Invisible_field_enumerator(THD *thd) : m_thd(thd) {} + + void visit_field(Item_field *item) override + { + if (m_found || !item->field || item->field->invisible == VISIBLE || + is_implicit_versioning_field(item)) + return; + m_found= true; + } + + bool found() const { return m_found; } + +private: + bool is_implicit_versioning_field(Item_field *item) const + { + for (TABLE_LIST *tbl= m_thd->lex->query_tables; tbl; + tbl= tbl->next_global) + { + if (item == tbl->vers_conditions.field_start || + item == tbl->vers_conditions.field_end) + return true; + } + return false; + } + + THD *m_thd; + bool m_found= false; +}; + +static void inspect_item_for_invisible_fields( + Item *item, Invisible_field_enumerator &enumerator) +{ + if (item && !enumerator.found()) + item->walk(&Item::enumerate_field_refs_processor, false, &enumerator); +} + +static bool query_references_invisible_fields(THD *thd) +{ + Invisible_field_enumerator enumerator(thd); + + for (SELECT_LEX *sl= thd->lex->all_selects_list; sl && !enumerator.found(); + sl= sl->next_select_in_list()) + { + List_iterator_fast items(sl->item_list); + Item *item; + while ((item= items++)) + inspect_item_for_invisible_fields(item, enumerator); + + inspect_item_for_invisible_fields(sl->where, enumerator); + inspect_item_for_invisible_fields(sl->having, enumerator); + + for (ORDER *order= sl->group_list.first; order; order= order->next) + inspect_item_for_invisible_fields(*order->item, enumerator); + for (ORDER *order= sl->order_list.first; order; order= order->next) + inspect_item_for_invisible_fields(*order->item, enumerator); + + List_iterator_fast windows(sl->window_specs); + Window_spec *window; + while ((window= windows++)) + { + for (ORDER *order= window->partition_list->first; order; + order= order->next) + inspect_item_for_invisible_fields(*order->item, enumerator); + for (ORDER *order= window->order_list->first; order; + order= order->next) + inspect_item_for_invisible_fields(*order->item, enumerator); + } + + for (TABLE_LIST *tbl= sl->get_table_list(); tbl; tbl= tbl->next_local) + inspect_item_for_invisible_fields(tbl->on_expr, enumerator); + } + + return enumerator.found(); +} + +static bool query_has_explicit_system_time(THD *thd) +{ + for (TABLE_LIST *tbl= thd->lex->query_tables; tbl; tbl= tbl->next_global) + if (tbl->vers_conditions.was_set()) + return true; + return false; +} + +static bool has_supported_field_layout(TABLE *table, + bool allow_trailing_invisible) +{ + bool found_invisible= false; + + for (Field **field= table->field; *field; field++) + { + if ((*field)->invisible == VISIBLE) + { + if (found_invisible) + return false; + } + else + { + if (!allow_trailing_invisible) + return false; + found_invisible= true; + } + } + + return true; +} + /** Check whether a SELECT_LEX can be pushed down to DuckDB. @@ -45,7 +155,8 @@ extern handlerton *duckdb_hton; static bool can_pushdown_to_duckdb(SELECT_LEX *sel_lex, std::vector &external_tables, - bool &has_duckdb_table) + bool &has_duckdb_table, + bool allow_trailing_invisible) { for (TABLE_LIST *tbl= sel_lex->get_table_list(); tbl; tbl= tbl->next_global) { @@ -55,7 +166,12 @@ static bool can_pushdown_to_duckdb(SELECT_LEX *sel_lex, if (tbl->table->file->ht == duckdb_hton) has_duckdb_table= true; else + { + if (!has_supported_field_layout(tbl->table, + allow_trailing_invisible)) + return false; external_tables.emplace_back(tbl->table_name.str); + } } return has_duckdb_table; @@ -69,7 +185,8 @@ static bool can_pushdown_to_duckdb(SELECT_LEX *sel_lex, static bool can_pushdown_unit_to_duckdb(SELECT_LEX_UNIT *unit, std::vector &external_tables, - bool &has_duckdb_table) + bool &has_duckdb_table, + bool allow_trailing_invisible) { for (SELECT_LEX *sl= unit->first_select(); sl; sl= sl->next_select()) { @@ -81,7 +198,12 @@ can_pushdown_unit_to_duckdb(SELECT_LEX_UNIT *unit, if (tbl->table->file->ht == duckdb_hton) has_duckdb_table= true; else + { + if (!has_supported_field_layout(tbl->table, + allow_trailing_invisible)) + return false; external_tables.emplace_back(tbl->table_name.str); + } } } @@ -212,7 +334,9 @@ select_handler *create_duckdb_select_handler(THD *thd, SELECT_LEX *sel_lex, return nullptr; if (!sel_lex || has_duckdb_insert_target(thd) || - has_unsupported_insert_select_clauses(thd)) + has_unsupported_insert_select_clauses(thd) || + query_has_explicit_system_time(thd) || + query_references_invisible_fields(thd)) return nullptr; std::string query; @@ -222,7 +346,8 @@ select_handler *create_duckdb_select_handler(THD *thd, SELECT_LEX *sel_lex, std::vector external_tables; bool has_duckdb_table= false; - if (!can_pushdown_to_duckdb(sel_lex, external_tables, has_duckdb_table)) + if (!can_pushdown_to_duckdb(sel_lex, external_tables, has_duckdb_table, + !myduck::get_thd_cross_engine_ryow(thd))) return nullptr; /* At least one DuckDB table must participate */ @@ -252,7 +377,9 @@ select_handler *create_duckdb_unit_handler(THD *thd, SELECT_LEX_UNIT *sel_unit) if ((thd->lex->sql_command != SQLCOM_SELECT && thd->lex->sql_command != SQLCOM_INSERT_SELECT) || has_duckdb_insert_target(thd) || - has_unsupported_insert_select_clauses(thd)) + has_unsupported_insert_select_clauses(thd) || + query_has_explicit_system_time(thd) || + query_references_invisible_fields(thd)) return nullptr; if (thd->stmt_arena && thd->stmt_arena->is_stmt_prepare()) @@ -268,8 +395,9 @@ select_handler *create_duckdb_unit_handler(THD *thd, SELECT_LEX_UNIT *sel_unit) std::vector external_tables; bool has_duckdb_table= false; - if (!can_pushdown_unit_to_duckdb(sel_unit, external_tables, - has_duckdb_table)) + if (!can_pushdown_unit_to_duckdb( + sel_unit, external_tables, has_duckdb_table, + !myduck::get_thd_cross_engine_ryow(thd))) return nullptr; if (!has_duckdb_table) @@ -888,9 +1016,15 @@ int ha_duckdb_select_handler::next_row() for (Field **f= table->field; *f; f++) field_count++; - size_t ncols= (col_count < field_count) ? col_count : field_count; + if (col_count != field_count) + { + my_error(ER_GET_ERRMSG, MYF(0), HA_ERR_INTERNAL_ERROR, + "DuckDB result column count does not match MariaDB metadata", + "DuckDB"); + DBUG_RETURN(HA_ERR_INTERNAL_ERROR); + } - for (size_t col_idx= 0; col_idx < ncols; col_idx++) + for (size_t col_idx= 0; col_idx < col_count; col_idx++) { duckdb::Value value= current_chunk->GetValue(col_idx, current_row_index); Field *field= table->field[col_idx]; diff --git a/storage/duckdb/mysql-test/duckdb/r/cross_engine_join.result b/storage/duckdb/mysql-test/duckdb/r/cross_engine_join.result index c7664f6a8d3..359ad8e11d4 100644 --- a/storage/duckdb/mysql-test/duckdb/r/cross_engine_join.result +++ b/storage/duckdb/mysql-test/duckdb/r/cross_engine_join.result @@ -117,6 +117,65 @@ id dval ival 1 a NULL 2 NULL b +# (9) MDEV-40846: trailing system-versioning columns stay hidden + +SET SESSION default_storage_engine=DuckDB; +CREATE TABLE t_versioned ( +c1 INT KEY, +c2 DEC(1,0) NOT NULL, +c3 SET('a','b','c','1'), +c4 INT(1) UNSIGNED NOT NULL, +KEY(c2) +) ENGINE=MyISAM WITH SYSTEM VERSIONING; +INSERT INTO t_versioned (c1, c2, c3, c4) VALUES (1, 0, 1, 0); +CREATE TABLE t_default (c0 VARCHAR(1) BINARY, c1 FLOAT KEY, c2 FLOAT); +WITH a AS (SELECT * FROM t_versioned), +b AS (SELECT * FROM t_versioned), +c AS (SELECT * FROM t_default) +SELECT * FROM a JOIN b ON a.c2=b.c2 LEFT JOIN c ON b.c2=c.c2; +c1 c2 c3 c4 c1 c2 c3 c4 c0 c1 c2 +1 0 a 0 1 0 a 0 NULL NULL NULL +SET SESSION default_storage_engine=DEFAULT; + +# (10) Explicit trailing invisible column uses MariaDB fallback + +CREATE TABLE t_trailing_invisible ( +id INT PRIMARY KEY, +hidden INT INVISIBLE +) ENGINE=InnoDB; +INSERT INTO t_trailing_invisible (id, hidden) VALUES (1, 10); +SELECT d.id, i.hidden +FROM t_duck d JOIN t_trailing_invisible i ON d.id=i.id; +ERROR HY000: Storage engine DUCKDB of the table `cross_engine_join`.`t_duck` doesn't have this option + +# (11) Non-trailing invisible column uses MariaDB fallback + +CREATE TABLE t_nontrailing_invisible ( +id INT PRIMARY KEY, +hidden INT INVISIBLE, +val INT +) ENGINE=InnoDB; +INSERT INTO t_nontrailing_invisible (id, hidden, val) VALUES (1, 10, 100); +SELECT d.id, i.val +FROM t_duck d JOIN t_nontrailing_invisible i ON d.id=i.id; +ERROR HY000: Storage engine DUCKDB of the table `cross_engine_join`.`t_duck` doesn't have this option + +# (12) Explicit FOR SYSTEM_TIME uses MariaDB fallback + +SELECT d.id, i.c2 +FROM t_duck d +JOIN t_versioned FOR SYSTEM_TIME ALL AS i ON d.id=i.c1; +ERROR HY000: Storage engine DUCKDB of the table `cross_engine_join`.`t_duck` doesn't have this option + +# (13) Invisible columns in window specifications use MariaDB fallback + +SELECT ROW_NUMBER() OVER (PARTITION BY i.hidden) +FROM t_duck d JOIN t_trailing_invisible i ON d.id=i.id; +ERROR HY000: Storage engine DUCKDB of the table `cross_engine_join`.`t_duck` doesn't have this option +SELECT ROW_NUMBER() OVER (ORDER BY i.hidden) +FROM t_duck d JOIN t_trailing_invisible i ON d.id=i.id; +ERROR HY000: Storage engine DUCKDB of the table `cross_engine_join`.`t_duck` doesn't have this option + # Cleanup DROP TABLE t_duck; @@ -125,4 +184,8 @@ DROP TABLE t_duck_types; DROP TABLE t_inno_types; DROP TABLE t_duck_null; DROP TABLE t_inno_null; +DROP TABLE t_versioned; +DROP TABLE t_default; +DROP TABLE t_trailing_invisible; +DROP TABLE t_nontrailing_invisible; DROP DATABASE cross_engine_join; diff --git a/storage/duckdb/mysql-test/duckdb/r/cross_engine_ryow.result b/storage/duckdb/mysql-test/duckdb/r/cross_engine_ryow.result index f1d7fcf30d0..a8156bb157c 100644 --- a/storage/duckdb/mysql-test/duckdb/r/cross_engine_ryow.result +++ b/storage/duckdb/mysql-test/duckdb/r/cross_engine_ryow.result @@ -66,9 +66,54 @@ score ROLLBACK; DROP TABLE t_inno_big; +# (5) RYOW falls back for implicit system-versioning columns + +CREATE TABLE t_versioned (id INT PRIMARY KEY, score INT) +ENGINE=InnoDB WITH SYSTEM VERSIONING; +INSERT INTO t_versioned VALUES (1, 10); +SET SESSION duckdb_cross_engine_ryow=1; +SELECT d.id, i.score FROM t_duck d JOIN t_versioned i ON d.id=i.id; +ERROR HY000: Storage engine DUCKDB of the table `cross_engine_ryow`.`t_duck` doesn't have this option + +# (6) A trailing invisible column is supported only without RYOW + +CREATE TABLE t_trailing_invisible ( +id INT PRIMARY KEY, +score INT, +hidden INT INVISIBLE +) ENGINE=InnoDB; +INSERT INTO t_trailing_invisible (id, score, hidden) VALUES (1, 10, 100); +SET SESSION duckdb_cross_engine_ryow=0; +SELECT d.id, i.score +FROM t_duck d JOIN t_trailing_invisible i ON d.id=i.id; +id score +1 10 +SET SESSION duckdb_cross_engine_ryow=1; +SELECT d.id, i.score +FROM t_duck d JOIN t_trailing_invisible i ON d.id=i.id; +ERROR HY000: Storage engine DUCKDB of the table `cross_engine_ryow`.`t_duck` doesn't have this option +SELECT d.id, i.hidden +FROM t_duck d JOIN t_trailing_invisible i ON d.id=i.id; +ERROR HY000: Storage engine DUCKDB of the table `cross_engine_ryow`.`t_duck` doesn't have this option + +# (7) A non-trailing invisible column always uses handler fallback + +CREATE TABLE t_nontrailing_invisible ( +id INT PRIMARY KEY, +hidden INT INVISIBLE, +score INT +) ENGINE=InnoDB; +INSERT INTO t_nontrailing_invisible (id, hidden, score) VALUES (1, 100, 10); +SELECT d.id, i.score +FROM t_duck d JOIN t_nontrailing_invisible i ON d.id=i.id; +ERROR HY000: Storage engine DUCKDB of the table `cross_engine_ryow`.`t_duck` doesn't have this option + # Cleanup SET SESSION duckdb_cross_engine_ryow=DEFAULT; DROP TABLE t_duck; DROP TABLE t_inno; +DROP TABLE t_versioned; +DROP TABLE t_trailing_invisible; +DROP TABLE t_nontrailing_invisible; DROP DATABASE cross_engine_ryow; diff --git a/storage/duckdb/mysql-test/duckdb/t/cross_engine_join.test b/storage/duckdb/mysql-test/duckdb/t/cross_engine_join.test index 43d1e2fa2db..c4682e87c5c 100644 --- a/storage/duckdb/mysql-test/duckdb/t/cross_engine_join.test +++ b/storage/duckdb/mysql-test/duckdb/t/cross_engine_join.test @@ -120,6 +120,74 @@ SELECT d.id, d.val AS dval, i.val AS ival FROM t_duck_null d JOIN t_inno_null i ON d.id = i.id ORDER BY d.id; +--echo +--echo # (9) MDEV-40846: trailing system-versioning columns stay hidden +--echo + +SET SESSION default_storage_engine=DuckDB; +CREATE TABLE t_versioned ( + c1 INT KEY, + c2 DEC(1,0) NOT NULL, + c3 SET('a','b','c','1'), + c4 INT(1) UNSIGNED NOT NULL, + KEY(c2) +) ENGINE=MyISAM WITH SYSTEM VERSIONING; +INSERT INTO t_versioned (c1, c2, c3, c4) VALUES (1, 0, 1, 0); +CREATE TABLE t_default (c0 VARCHAR(1) BINARY, c1 FLOAT KEY, c2 FLOAT); + +WITH a AS (SELECT * FROM t_versioned), + b AS (SELECT * FROM t_versioned), + c AS (SELECT * FROM t_default) +SELECT * FROM a JOIN b ON a.c2=b.c2 LEFT JOIN c ON b.c2=c.c2; +SET SESSION default_storage_engine=DEFAULT; + +--echo +--echo # (10) Explicit trailing invisible column uses MariaDB fallback +--echo + +CREATE TABLE t_trailing_invisible ( + id INT PRIMARY KEY, + hidden INT INVISIBLE +) ENGINE=InnoDB; +INSERT INTO t_trailing_invisible (id, hidden) VALUES (1, 10); +--error ER_ILLEGAL_HA +SELECT d.id, i.hidden + FROM t_duck d JOIN t_trailing_invisible i ON d.id=i.id; + +--echo +--echo # (11) Non-trailing invisible column uses MariaDB fallback +--echo + +CREATE TABLE t_nontrailing_invisible ( + id INT PRIMARY KEY, + hidden INT INVISIBLE, + val INT +) ENGINE=InnoDB; +INSERT INTO t_nontrailing_invisible (id, hidden, val) VALUES (1, 10, 100); +--error ER_ILLEGAL_HA +SELECT d.id, i.val + FROM t_duck d JOIN t_nontrailing_invisible i ON d.id=i.id; + +--echo +--echo # (12) Explicit FOR SYSTEM_TIME uses MariaDB fallback +--echo + +--error ER_ILLEGAL_HA +SELECT d.id, i.c2 + FROM t_duck d + JOIN t_versioned FOR SYSTEM_TIME ALL AS i ON d.id=i.c1; + +--echo +--echo # (13) Invisible columns in window specifications use MariaDB fallback +--echo + +--error ER_ILLEGAL_HA +SELECT ROW_NUMBER() OVER (PARTITION BY i.hidden) + FROM t_duck d JOIN t_trailing_invisible i ON d.id=i.id; +--error ER_ILLEGAL_HA +SELECT ROW_NUMBER() OVER (ORDER BY i.hidden) + FROM t_duck d JOIN t_trailing_invisible i ON d.id=i.id; + --echo --echo # Cleanup --echo @@ -130,4 +198,8 @@ DROP TABLE t_duck_types; DROP TABLE t_inno_types; DROP TABLE t_duck_null; DROP TABLE t_inno_null; +DROP TABLE t_versioned; +DROP TABLE t_default; +DROP TABLE t_trailing_invisible; +DROP TABLE t_nontrailing_invisible; DROP DATABASE cross_engine_join; diff --git a/storage/duckdb/mysql-test/duckdb/t/cross_engine_ryow.test b/storage/duckdb/mysql-test/duckdb/t/cross_engine_ryow.test index 61dbe0076f2..753d7ecc126 100644 --- a/storage/duckdb/mysql-test/duckdb/t/cross_engine_ryow.test +++ b/storage/duckdb/mysql-test/duckdb/t/cross_engine_ryow.test @@ -58,10 +58,56 @@ SELECT i.score FROM t_duck d JOIN t_inno_big i ON d.id=i.id WHERE i.id=1; ROLLBACK; DROP TABLE t_inno_big; +--echo +--echo # (5) RYOW falls back for implicit system-versioning columns +--echo +CREATE TABLE t_versioned (id INT PRIMARY KEY, score INT) + ENGINE=InnoDB WITH SYSTEM VERSIONING; +INSERT INTO t_versioned VALUES (1, 10); +SET SESSION duckdb_cross_engine_ryow=1; +--error ER_ILLEGAL_HA +SELECT d.id, i.score FROM t_duck d JOIN t_versioned i ON d.id=i.id; + +--echo +--echo # (6) A trailing invisible column is supported only without RYOW +--echo +CREATE TABLE t_trailing_invisible ( + id INT PRIMARY KEY, + score INT, + hidden INT INVISIBLE +) ENGINE=InnoDB; +INSERT INTO t_trailing_invisible (id, score, hidden) VALUES (1, 10, 100); +SET SESSION duckdb_cross_engine_ryow=0; +SELECT d.id, i.score + FROM t_duck d JOIN t_trailing_invisible i ON d.id=i.id; +SET SESSION duckdb_cross_engine_ryow=1; +--error ER_ILLEGAL_HA +SELECT d.id, i.score + FROM t_duck d JOIN t_trailing_invisible i ON d.id=i.id; +--error ER_ILLEGAL_HA +SELECT d.id, i.hidden + FROM t_duck d JOIN t_trailing_invisible i ON d.id=i.id; + +--echo +--echo # (7) A non-trailing invisible column always uses handler fallback +--echo +CREATE TABLE t_nontrailing_invisible ( + id INT PRIMARY KEY, + hidden INT INVISIBLE, + score INT +) ENGINE=InnoDB; +INSERT INTO t_nontrailing_invisible (id, hidden, score) VALUES (1, 100, 10); +--error ER_ILLEGAL_HA +SELECT d.id, i.score + FROM t_duck d JOIN t_nontrailing_invisible i ON d.id=i.id; + --echo --echo # Cleanup --echo SET SESSION duckdb_cross_engine_ryow=DEFAULT; DROP TABLE t_duck; DROP TABLE t_inno; +DROP TABLE t_versioned; +DROP TABLE t_trailing_invisible; +DROP TABLE t_nontrailing_invisible; DROP DATABASE cross_engine_ryow; diff --git a/storage/duckdb/runtime/cross_engine_scan.cc b/storage/duckdb/runtime/cross_engine_scan.cc index d0f4c9058b3..a1839525adb 100644 --- a/storage/duckdb/runtime/cross_engine_scan.cc +++ b/storage/duckdb/runtime/cross_engine_scan.cc @@ -294,7 +294,7 @@ mdb_scan_bind(duckdb::ClientContext &context, "external table registry", key.c_str()); - for (Field **f= tbl->field; *f; f++) + for (Field **f= tbl->field; *f && (*f)->invisible == VISIBLE; f++) { names.push_back((*f)->field_name.str); return_types.push_back(field_to_logical_type(*f)); From 57ebfb607a08452584aea0adda069c14974f1ded Mon Sep 17 00:00:00 2001 From: sjaakola Date: Wed, 12 Aug 2026 16:51:04 +0300 Subject: [PATCH 11/71] MDEV_38952 Improve galera_sequences family of tests This commit fixes a sporadic failure with the test case 1, where recorded result depends on node 1 applying node 2's replicated sequence update before it resumes its already-open transaction: - Node 2 SELECT NEXTVAL(s) writes reserved_until=21 and replicates it. - On node 1 that lands in Rows_log_event::update_sequence() Since 21 > next_free_value (9), adjust_values(21) discards node 1's still-cached value 9. Nothing enforced that ordering: node 1's INSERTs run inside BEGIN, and sync wait does not happen mid-transaction. The fix is to use selarate session, node_1_ctrl, to wait until node 1 has applied the update, before node 1 resumes its transaction --- .../suite/galera/r/galera_sequences_recovery.result | 2 ++ .../suite/galera/t/galera_sequences_recovery.test | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/mysql-test/suite/galera/r/galera_sequences_recovery.result b/mysql-test/suite/galera/r/galera_sequences_recovery.result index 11b02400960..7c9f92488d4 100644 --- a/mysql-test/suite/galera/r/galera_sequences_recovery.result +++ b/mysql-test/suite/galera/r/galera_sequences_recovery.result @@ -31,6 +31,8 @@ SELECT LASTVAL(s); LASTVAL(s) 12 SET GLOBAL innodb_log_checkpoint_now=1; +connection node_1_ctrl; +# Wait for the next_not_cached_value = 21 FROM s (1_ctrl) connection node_1; INSERT INTO t1(f2) values (1); INSERT INTO t1(f2) values (1); diff --git a/mysql-test/suite/galera/t/galera_sequences_recovery.test b/mysql-test/suite/galera/t/galera_sequences_recovery.test index ff0f6b89bc9..f0f58a6924b 100644 --- a/mysql-test/suite/galera/t/galera_sequences_recovery.test +++ b/mysql-test/suite/galera/t/galera_sequences_recovery.test @@ -57,6 +57,16 @@ SELECT NEXTVAL(s); SELECT LASTVAL(s); SET GLOBAL innodb_log_checkpoint_now=1; +# Node 1 has an open transaction and wsrep_sync_wait is not effective +# inside a transaction. Wait here until node 1 has applied the sequence +# update replicated by NEXTVAL above, otherwise node 1 may still consume +# its own cached value before the update arrives. +--connection node_1_ctrl +--echo # Wait for the next_not_cached_value = 21 FROM s (1_ctrl) +--let $wait_condition = SELECT next_not_cached_value = 21 FROM s +--let $wait_condition_on_error_output = SELECT next_not_cached_value FROM s +--source include/wait_condition_with_debug.inc + --connection node_1 # Update the sequence value further INSERT INTO t1(f2) values (1); From 37d8577aee3bd87b5b04464144d064063b169039 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Mon, 24 Aug 2026 15:21:39 +0300 Subject: [PATCH 12/71] MDEV-40728 Recovery wrongly fails if FILE_CREATE is followed by FILE_RENAME deferred_spaces.deferred_dblwr(): Skip newly created tablespaces to avoid a bogus invocation of fil_space_free(). fil_name_process(): Simplify the logic. If no matching tablespace is found but file_name_t::create_lsn had been set in response to parsing a FILE_CREATE record, try to apply FILE_RENAME to deferred_spaces. log_parse_file(): Parse each FILE_ record only once. In multi-batch recovery, there may be redundant calls that would break the logic of fil_name_process(). --- storage/innobase/log/log0recv.cc | 67 +++++++++++++++++++------------- 1 file changed, 40 insertions(+), 27 deletions(-) diff --git a/storage/innobase/log/log0recv.cc b/storage/innobase/log/log0recv.cc index f43e0569c5d..f0fe1039366 100644 --- a/storage/innobase/log/log0recv.cc +++ b/storage/innobase/log/log0recv.cc @@ -940,6 +940,18 @@ processed: d++; continue; } + else + { + recv_spaces_t::iterator it{recv_spaces.find(d->first)}; + if (UNIV_UNLIKELY(it == recv_spaces.end())) + ut_ad("inconsistent data structures" == 0); + else if (it->second.create_lsn) + /* + Because a FILE_CREATE record exists, the entire file must + be recoverable via log records. + */ + goto next_item; + } const page_id_t page_id{d->first, 0}; const byte *page= recv_sys.dblwr.find_page(page_id, max_lsn); if (!page) @@ -1325,19 +1337,14 @@ static void fil_name_process(const char *name, ulint len, uint32_t space_id, ut_ad(p.first->first == space_id); file_name_t& f = p.first->second; - + ut_ad(!f.space || f.space->id == space_id); auto d = deferred_spaces.find(space_id); - if (d) { - if (deleted) { - d->deleted = true; - goto got_deleted; - } - goto reload; - } if (deleted) { -got_deleted: /* Got FILE_DELETE */ + if (d) { + d->deleted = true; + } if (!p.second && f.status != file_name_t::DELETED) { f.status = file_name_t::DELETED; if (f.space != NULL) { @@ -1347,10 +1354,9 @@ got_deleted: } ut_ad(f.space == NULL); - goto reset_create; - } else if (p.second // the first FILE_MODIFY or FILE_RENAME + f.create_lsn = 0; + } else if (d || p.second /* the first FILE_MODIFY or FILE_RENAME */ || f.name != fname.name) { -reload: if (f.name.size() == 0) { /* Augment the recv_spaces.emplace_hint() for the FILE_MODIFY record that had been added by @@ -1364,7 +1370,8 @@ reload: the space_id. If not, ignore the file after displaying a note. Abort if there are multiple files with the same space_id. */ - switch (fil_ibd_load(space_id, fname.name.c_str(), space)) { + switch (fil_load_status s + = fil_ibd_load(space_id, fname.name.c_str(), space)) { case FIL_LOAD_OK: ut_ad(space != NULL); @@ -1399,25 +1406,28 @@ same_space: break; case FIL_LOAD_ID_CHANGED: - ut_ad(space == NULL); - break; - case FIL_LOAD_NOT_FOUND: /* No matching tablespace was found; maybe it was renamed, and we will find a subsequent FILE_* record. */ ut_ad(space == NULL); - if (srv_operation == SRV_OPERATION_RESTORE && d - && ftype == FILE_RENAME) { + if (f.create_lsn) { + if (d && ftype == FILE_RENAME) { rename: - d->file_name = fname.name; - f.name = fname.name; + d->file_name = fname.name; + f.name = fname.name; + } break; } - if (f.create_lsn) { - return; + if (ftype == FILE_CREATE) { + f.create_lsn = lsn; + break; + } + + if (s == FIL_LOAD_ID_CHANGED) { + break; } if (srv_force_recovery @@ -1437,11 +1447,10 @@ rename: int(fname.name.size()), fname.name.data(), space_id); } - return; + break; case FIL_LOAD_DEFER: - if (d && ftype == FILE_RENAME - && srv_operation == SRV_OPERATION_RESTORE) { + if (d && ftype == FILE_RENAME && f.create_lsn) { goto rename; } /* Skip the deferred spaces @@ -1473,8 +1482,6 @@ rename: " due to innodb_force_recovery", int(len), name, space_id); } -reset_create: - f.create_lsn = 0; } else if (ftype == FILE_CREATE && !f.space) { f.create_lsn = lsn; } @@ -2796,6 +2803,12 @@ static recv_sys_t::parse_mtr_result log_parse_file(const page_id_t id, bool if_exists, const byte b, const byte *l, uint32_t rlen) { + if (recv_sys.scanned_lsn > recv_sys.lsn) + /* + We must already have parsed this record, in the skip_the_rest: + loop in recv_scan_log() before starting multi-batch recovery. + */ + return recv_sys_t::OK; if (UNIV_LIKELY(rlen)); else if (!id.raw() && b == FILE_CHECKPOINT + 2) /* FILE_CHECKPOINT followed by any number of NUL bytes could be From eeb8cb8587d94956d814e2e0465fc27b8f8280c0 Mon Sep 17 00:00:00 2001 From: Abdelrahman Hedia Date: Mon, 27 Apr 2026 01:10:14 +0300 Subject: [PATCH 13/71] MDEV-29803: Change mariadb-binlog --gtid-strict-mode default to OFF The --gtid-strict-mode option in mariadb-binlog was introduced in MDEV-4989 with a default of ON. This causes mariadb-binlog to refuse to display events when it encounters out-of-order GTIDs, which commonly happens when replaying a remote binlog into a server and then reading back the resulting local binlog files. This is overly restrictive for a diagnostic/display tool. While the server's gtid_strict_mode makes sense as a safety mechanism, applying the same strict validation by default in the client tool prevents users from even inspecting problematic binlog files. Change the default to OFF so that mariadb-binlog processes binlog files without erroring on out-of-order GTIDs by default. Users who want strict validation can still explicitly pass --gtid-strict-mode. Added regression test binlog.mdev_29803 that verifies: - Default (OFF): reading binlog files with replayed events succeeds - Explicit --gtid-strict-mode: still produces the expected error --- client/mysqlbinlog.cc | 4 +-- .../mysqlbinlog_gtid_window_test_cases.inc | 4 +-- .../r/binlog_mysqlbinlog_gtid_window.result | 4 +-- mysql-test/suite/binlog/r/mdev_29803.result | 9 +++++ mysql-test/suite/binlog/t/mdev_29803.test | 36 +++++++++++++++++++ 5 files changed, 51 insertions(+), 6 deletions(-) create mode 100644 mysql-test/suite/binlog/r/mdev_29803.result create mode 100644 mysql-test/suite/binlog/t/mdev_29803.test diff --git a/client/mysqlbinlog.cc b/client/mysqlbinlog.cc index aaf75a4f2e9..f323bb5d128 100644 --- a/client/mysqlbinlog.cc +++ b/client/mysqlbinlog.cc @@ -127,7 +127,7 @@ static my_bool print_row_count_used= 0, print_row_event_positions_used= 0; static my_bool debug_info_flag, debug_check_flag; static my_bool force_if_open_opt= 1; static my_bool opt_raw_mode= 0, opt_stop_never= 0; -my_bool opt_gtid_strict_mode= true; +my_bool opt_gtid_strict_mode= false; static ulong opt_stop_never_slave_server_id= 0; static my_bool opt_verify_binlog_checksum= 1; static ulonglong offset = 0; @@ -1887,7 +1887,7 @@ static struct my_option my_options[] = "start < stop comparison condition. Sequence numbers of any gtid domain " "must comprise monotically growing sequence", &opt_gtid_strict_mode, &opt_gtid_strict_mode, 0, - GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, 0}, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, {"stop-datetime", OPT_STOP_DATETIME, "Stop reading the binlog at first event having a datetime equal or " "posterior to the argument; the argument must be a date and time " diff --git a/mysql-test/suite/binlog/include/mysqlbinlog_gtid_window_test_cases.inc b/mysql-test/suite/binlog/include/mysqlbinlog_gtid_window_test_cases.inc index 2830f3f665d..cb8d17c313b 100644 --- a/mysql-test/suite/binlog/include/mysqlbinlog_gtid_window_test_cases.inc +++ b/mysql-test/suite/binlog/include/mysqlbinlog_gtid_window_test_cases.inc @@ -577,9 +577,9 @@ if ($is_remote == 0) --echo # Error Case 3: --echo # A GTID --start-position with any sequence numbers that are not --echo # eventually processed results in error ---echo # MYSQL_BINLOG BINLOG_FILE_PARAM2 --start-position=0-1-8,1-2-6 > tmp_out_ 2> err_out_ +--echo # MYSQL_BINLOG BINLOG_FILE_PARAM2 --start-position=0-1-8,1-2-6 --gtid-strict-mode > tmp_out_ 2> err_out_ --error 1 ---exec $MYSQL_BINLOG $BINLOG_FILE_PARAM2 --start-position=0-1-8,1-2-6 > $tmp_out_ 2> $err_out_ +--exec $MYSQL_BINLOG $BINLOG_FILE_PARAM2 --start-position=0-1-8,1-2-6 --gtid-strict-mode > $tmp_out_ 2> $err_out_ if ($is_remote == 1) { --let SEARCH_PATTERN=ERROR: Got error reading packet from server diff --git a/mysql-test/suite/binlog/r/binlog_mysqlbinlog_gtid_window.result b/mysql-test/suite/binlog/r/binlog_mysqlbinlog_gtid_window.result index fce5f95ce82..ec0c4b1caa8 100644 --- a/mysql-test/suite/binlog/r/binlog_mysqlbinlog_gtid_window.result +++ b/mysql-test/suite/binlog/r/binlog_mysqlbinlog_gtid_window.result @@ -208,7 +208,7 @@ FOUND 1 /ERROR: Binary logs are missing data for domain 0/ in err.out # Error Case 3: # A GTID --start-position with any sequence numbers that are not # eventually processed results in error -# MYSQL_BINLOG BINLOG_FILE_PARAM2 --start-position=0-1-8,1-2-6 > tmp_out_ 2> err_out_ +# MYSQL_BINLOG BINLOG_FILE_PARAM2 --start-position=0-1-8,1-2-6 --gtid-strict-mode > tmp_out_ 2> err_out_ FOUND 2 /ERROR: Binary logs never reached expected GTID state/ in err.out ###################################### # Test Group 2 @@ -440,7 +440,7 @@ FOUND 1 /ERROR: Got error reading packet from server/ in err.out # Error Case 3: # A GTID --start-position with any sequence numbers that are not # eventually processed results in error -# MYSQL_BINLOG BINLOG_FILE_PARAM2 --start-position=0-1-8,1-2-6 > tmp_out_ 2> err_out_ +# MYSQL_BINLOG BINLOG_FILE_PARAM2 --start-position=0-1-8,1-2-6 --gtid-strict-mode > tmp_out_ 2> err_out_ FOUND 1 /ERROR: Got error reading packet from server/ in err.out # # Remote-only cleanup from error cases diff --git a/mysql-test/suite/binlog/r/mdev_29803.result b/mysql-test/suite/binlog/r/mdev_29803.result new file mode 100644 index 00000000000..0094c8cd0e1 --- /dev/null +++ b/mysql-test/suite/binlog/r/mdev_29803.result @@ -0,0 +1,9 @@ +reset master; +create table t1 (a int); +create table t2 (a int); +drop table t1, t2; +flush binary logs; +drop table if exists t1, t2; +Warnings: +Note 1051 Unknown table 'test.t1,test.t2' +flush binary logs; diff --git a/mysql-test/suite/binlog/t/mdev_29803.test b/mysql-test/suite/binlog/t/mdev_29803.test new file mode 100644 index 00000000000..d5e26d9d6b6 --- /dev/null +++ b/mysql-test/suite/binlog/t/mdev_29803.test @@ -0,0 +1,36 @@ +--source include/have_log_bin.inc + +# +# MDEV-29803: Unexpected ERROR: Found out of order GTID after replaying +# binlog read from remote server, then reading the resulting local binlog. +# +# When mysqlbinlog replays a remote binlog into a server, and we then +# read back the local binlog files, the replayed events reset the GTID +# sequence, which causes a "Found out of order GTID" error if +# --gtid-strict-mode is enabled. +# +# The fix is to change the default of --gtid-strict-mode to OFF so that +# mysqlbinlog does not refuse to display events by default. +# + +reset master; +create table t1 (a int); +create table t2 (a int); +drop table t1, t2; +flush binary logs; + +# Replay remote binlog into this server +--exec $MYSQL_BINLOG --read-from-remote-server -uroot --protocol=tcp --port=$MASTER_MYPORT master-bin.000001 | $MYSQL test 2>&1 + +drop table if exists t1, t2; +flush binary logs; + +--let $datadir= `select @@datadir` + +# With the fix, the default --gtid-strict-mode=OFF allows reading both binlog +# files without error, even though replayed events create out-of-order GTIDs. +--exec $MYSQL_BINLOG $datadir/master-bin.000001 $datadir/master-bin.000002 > /dev/null 2>&1 + +# Explicitly enabling --gtid-strict-mode should still produce an error +--error 1 +--exec $MYSQL_BINLOG --gtid-strict-mode $datadir/master-bin.000001 $datadir/master-bin.000002 > /dev/null 2>&1 From b722cc53ed4da0b58fa1e4cf88deabf166abb1da Mon Sep 17 00:00:00 2001 From: Val Doroshchuk Date: Tue, 25 Aug 2026 12:48:46 +0200 Subject: [PATCH 14/71] Rename duckdb file name to allow to use duckdb as schema If duckdb is used as schema, DuckDB requires to use it in queries explicitly since the name conflicts with the catalog. This fixes Ambiguous reference to catalog or schema "duckdb" - use a fully qualified path like '.duckdb' --- .../duckdb/r/create_database_duckdb.result | 12 ++++++++++++ .../duckdb/t/create_database_duckdb.test | 16 ++++++++++++++++ storage/duckdb/runtime/duckdb_manager.h | 2 +- 3 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 storage/duckdb/mysql-test/duckdb/r/create_database_duckdb.result create mode 100644 storage/duckdb/mysql-test/duckdb/t/create_database_duckdb.test diff --git a/storage/duckdb/mysql-test/duckdb/r/create_database_duckdb.result b/storage/duckdb/mysql-test/duckdb/r/create_database_duckdb.result new file mode 100644 index 00000000000..bf5f8ba6ad3 --- /dev/null +++ b/storage/duckdb/mysql-test/duckdb/r/create_database_duckdb.result @@ -0,0 +1,12 @@ +CREATE DATABASE IF NOT EXISTS duckdb CHARACTER SET utf8mb4; +USE duckdb; +CREATE TABLE t1 ( +id INT PRIMARY KEY, +name VARCHAR(32) +) engine=duckdb; +INSERT INTO t1 VALUES (1, 'one'); +SELECT * FROM t1; +id name +1 one +DROP TABLE t1; +DROP DATABASE duckdb; diff --git a/storage/duckdb/mysql-test/duckdb/t/create_database_duckdb.test b/storage/duckdb/mysql-test/duckdb/t/create_database_duckdb.test new file mode 100644 index 00000000000..3addc04a5ab --- /dev/null +++ b/storage/duckdb/mysql-test/duckdb/t/create_database_duckdb.test @@ -0,0 +1,16 @@ +--source ../include/have_duckdb.inc + +CREATE DATABASE IF NOT EXISTS duckdb CHARACTER SET utf8mb4; +USE duckdb; + +CREATE TABLE t1 ( + id INT PRIMARY KEY, + name VARCHAR(32) +) engine=duckdb; + +INSERT INTO t1 VALUES (1, 'one'); +SELECT * FROM t1; +DROP TABLE t1; + +--source ../include/cleanup_duckdb.inc +DROP DATABASE duckdb; diff --git a/storage/duckdb/runtime/duckdb_manager.h b/storage/duckdb/runtime/duckdb_manager.h index 07ad99be854..baf0a5c8b4a 100644 --- a/storage/duckdb/runtime/duckdb_manager.h +++ b/storage/duckdb/runtime/duckdb_manager.h @@ -28,7 +28,7 @@ namespace myduck { -constexpr char DUCKDB_FILE_NAME[]= "duckdb.db"; +constexpr char DUCKDB_FILE_NAME[]= "__duckdb.db"; constexpr char DUCKDB_DEFAULT_TMP_NAME[]= "duckdb_tmp"; class DuckdbManager From 271ca969128f5e9372121199a90607fda3900040 Mon Sep 17 00:00:00 2001 From: Brandon Nesterenko Date: Wed, 26 Aug 2026 11:13:20 -0600 Subject: [PATCH 15/71] MDEV-40823: rpl.rpl_queue_event_length_mismatch 'row' fails: Error condition reached in include/wait_for_slave_param.inc rpl.rpl_queue_event_length_mismatch can sporadically fail at the start of the second test case, when starting the slave in an expectedly valid state. This is because the previous test case (1) could not properly finish cleaning up before it was torn down by the second test case. That is, the previous test case corrupts a rotate event and ensures the master catches and errors properly on this corruption. To clean up, test case 1 resets the debug_dbug state of the master's binlog_dump_thread, so it can re-send the rotate event that it had previously corrupted. The test case never actually ensures this rotate event made it to the slave though. The second test case also arms the binlog_dump_thread to corrupt a rotate event (but in a different way). If the second test case stopped the slave before the slave was able to retrieve this corrected event; the next time the binlog dump thread would start (from test case 2), it would start with a debug_dbug state that would again corrupt this rotate event (that otherwise should make it problem-free to the slave). The corrupted rotate event from test case 2 is only meant to apply to the rotate event *after* FLUSH LOGS. To fix this, test cases 1 and 2 now end by waiting for the IO thread to reach the master's position Signed-off-by: Brandon Nesterenko --- .../suite/rpl/r/rpl_queue_event_length_mismatch.result | 6 ++++++ .../suite/rpl/t/rpl_queue_event_length_mismatch.test | 10 ++++++++++ 2 files changed, 16 insertions(+) diff --git a/mysql-test/suite/rpl/r/rpl_queue_event_length_mismatch.result b/mysql-test/suite/rpl/r/rpl_queue_event_length_mismatch.result index 4f3c89fc900..e091a249f56 100644 --- a/mysql-test/suite/rpl/r/rpl_queue_event_length_mismatch.result +++ b/mysql-test/suite/rpl/r/rpl_queue_event_length_mismatch.result @@ -41,6 +41,9 @@ set @@global.debug_dbug= @saved_dbug; connection slave; include/stop_slave_sql.inc include/start_slave.inc +# Ensure the IO thread re-fetches the corrected Rotate before moving on +connection master; +include/sync_slave_io_with_master.inc # # Test Case 2: a Rotate event declaring more bytes than its packet # @@ -64,6 +67,9 @@ set @@global.debug_dbug= @saved_dbug; connection slave; include/stop_slave_sql.inc include/start_slave.inc +# Ensure the IO thread re-fetches the corrected Rotate before moving on +connection master; +include/sync_slave_io_with_master.inc # # Test Case 3: a packet carrying a second event behind the first # diff --git a/mysql-test/suite/rpl/t/rpl_queue_event_length_mismatch.test b/mysql-test/suite/rpl/t/rpl_queue_event_length_mismatch.test index 2d9132fae0f..62e5a22498e 100644 --- a/mysql-test/suite/rpl/t/rpl_queue_event_length_mismatch.test +++ b/mysql-test/suite/rpl/t/rpl_queue_event_length_mismatch.test @@ -107,6 +107,11 @@ set @@global.debug_dbug= @saved_dbug; --source include/stop_slave_sql.inc --source include/start_slave.inc +--echo # Ensure the IO thread re-fetches the corrected Rotate before moving on +--connection master +--source include/sync_slave_io_with_master.inc + + --echo # --echo # Test Case 2: a Rotate event declaring more bytes than its packet --echo # @@ -155,6 +160,11 @@ set @@global.debug_dbug= @saved_dbug; --source include/stop_slave_sql.inc --source include/start_slave.inc +--echo # Ensure the IO thread re-fetches the corrected Rotate before moving on +--connection master +--source include/sync_slave_io_with_master.inc + + --echo # --echo # Test Case 3: a packet carrying a second event behind the first --echo # From 81ef68784e3dfd95c2eaed1dc835cbbe6e6054cf Mon Sep 17 00:00:00 2001 From: sjaakola Date: Thu, 13 Aug 2026 15:01:56 +0300 Subject: [PATCH 16/71] MDEV-38869 sequence conflicts with streaming replication Sequence access conflicts with streaming replication could cause the server to hang, as shown in MDEV-38869. A sequence table is written from SEQUENCE::next_value() while SEQUENCE::mutex is held. For a streaming transaction the row write in handler::ha_write_row() would then replicate a fragment and block waiting for certification and commit order, while an applier may be waiting for the same mutex in SEQUENCE::set_value(). Neither side can proceed, the node deadlocks and the BF abort of the local transaction can never be delivered. This commit avoids the deadlock by skipping the streaming replication step for sequence table rows. The row is already in the write set and is replicated with the following fragment, or at commit. Only that one step is skipped. The skip is passed down as a parameter to wsrep_after_row() and wsrep_after_row_internal() rather than by not calling them at all, so the row is still counted against wsrep_max_ws_rows and wsrep_check_pk() still runs. A transaction using sequences heavily therefore cannot silently exceed the configured write set row limit. The commit has also a new mtr test for three sequence/SR conflict scenarios: galera.galera_sequences_bf_kill_sr - a streaming transaction and an applier competing for SEQUENCE::mutex, where both are expected to commit - the same, but with the applier also BF aborting the local transaction over a gap lock. A streaming transaction cannot be replayed, so it is rolled back and the client gets ER_LOCK_DEADLOCK. The applier is held at the abort_trx_end sync point until the abort has been issued, so that the local transaction cannot finish its fragment first - twelve row inserts on both nodes with wsrep_trx_fragment_unit=rows, so that each node reserves several sequence cache ranges and the sequence table writes land inside fragments carrying several rows. The values the two nodes hand out must not overlap --- .../r/galera_sequences_bf_kill_sr.result | 216 ++++++++++++ .../galera/t/galera_sequences_bf_kill_sr.cnf | 12 + .../galera_sequences_bf_kill_sr.combinations | 5 + .../galera/t/galera_sequences_bf_kill_sr.test | 308 ++++++++++++++++++ sql/handler.cc | 21 +- sql/wsrep_trans_observer.h | 8 +- 6 files changed, 565 insertions(+), 5 deletions(-) create mode 100644 mysql-test/suite/galera/r/galera_sequences_bf_kill_sr.result create mode 100644 mysql-test/suite/galera/t/galera_sequences_bf_kill_sr.cnf create mode 100644 mysql-test/suite/galera/t/galera_sequences_bf_kill_sr.combinations create mode 100644 mysql-test/suite/galera/t/galera_sequences_bf_kill_sr.test diff --git a/mysql-test/suite/galera/r/galera_sequences_bf_kill_sr.result b/mysql-test/suite/galera/r/galera_sequences_bf_kill_sr.result new file mode 100644 index 00000000000..476c86055e0 --- /dev/null +++ b/mysql-test/suite/galera/r/galera_sequences_bf_kill_sr.result @@ -0,0 +1,216 @@ +connection node_2; +connection node_1; +connection node_1; +CREATE SEQUENCE s INCREMENT=0 CACHE=5 ENGINE=InnoDB; +CREATE TABLE t1 (f1 INT PRIMARY KEY, f2 INT) ENGINE=InnoDB; +INSERT INTO t1 VALUES (1, 0), (3, 0); +connection node_1; +START TRANSACTION; +INSERT INTO t1 VALUES (4, 0); +connect node_1a, 127.0.0.1, root, , test, $NODE_MYPORT_1; +SET SESSION wsrep_sync_wait=0; +# The transaction above must have replicated a fragment, otherwise +# this test degenerates into galera_sequences_bf_kill +SELECT COUNT(*) > 0 AS EXPECT_1 FROM mysql.wsrep_streaming_log; +EXPECT_1 +1 +# Block the applier on node #1 and issue a conflicting insert on node #2 +SET GLOBAL wsrep_provider_options = 'dbug=d,apply_monitor_slave_enter_sync'; +connection node_2; +INSERT INTO t1 VALUES (2, next value for s); +connection node_1a; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'dbug='; +# Block the commit monitor and let the local transaction reserve a +# new range of sequence values, which conflicts with the applier +SET GLOBAL wsrep_provider_options = 'dbug=d,commit_monitor_master_enter_sync'; +connection node_1; +INSERT INTO t1 VALUES (5, next value for s); +connection node_1a; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'dbug='; +# Release the applier +SET GLOBAL wsrep_provider_options = 'signal=apply_monitor_slave_enter_sync'; +# Release the local transaction +SET GLOBAL wsrep_provider_options = 'signal=commit_monitor_master_enter_sync'; +connection node_1; +COMMIT; +# Both nodes must have the same table contents. The sequence values +# are node specific, each node adjusts them to its own +# auto_increment_offset +connection node_1; +SELECT * FROM t1; +f1 f2 +1 0 +2 2 +3 0 +4 0 +5 1 +SELECT LASTVAL(s); +LASTVAL(s) +1 +SELECT next_not_cached_value FROM s; +next_not_cached_value +11 +connection node_2; +SELECT * FROM t1; +f1 f2 +1 0 +2 2 +3 0 +4 0 +5 1 +SELECT LASTVAL(s); +LASTVAL(s) +2 +SELECT next_not_cached_value FROM s; +next_not_cached_value +12 +# All fragments must have been removed on commit +connection node_1a; +SELECT COUNT(*) AS EXPECT_0 FROM mysql.wsrep_streaming_log; +EXPECT_0 +0 +connection node_1; +DROP SEQUENCE s; +DROP TABLE t1; +connection node_1; +CREATE SEQUENCE s INCREMENT=0 CACHE=5 ENGINE=InnoDB; +CREATE TABLE t1 (f1 INT PRIMARY KEY, f2 INT) ENGINE=InnoDB; +INSERT INTO t1 VALUES (1, 0), (3, 0); +connection node_1; +START TRANSACTION; +INSERT INTO t1 VALUES (4, 0); +SELECT * FROM t1 WHERE f1 > 0 FOR UPDATE; +f1 f2 +1 0 +3 0 +4 0 +# Block the applier on node #1 and issue a conflicting insert on node #2 +connection node_1a; +SET GLOBAL wsrep_provider_options = 'dbug=d,apply_monitor_slave_enter_sync'; +connection node_2; +INSERT INTO t1 VALUES (2, next value for s); +connection node_1a; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'dbug='; +# Block the commit monitor and let the local transaction reserve a +# new range of sequence values, which conflicts with the applier +SET GLOBAL wsrep_provider_options = 'dbug=d,commit_monitor_master_enter_sync'; +connection node_1; +INSERT INTO t1 VALUES (5, next value for s); +connection node_1a; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'dbug='; +SET GLOBAL wsrep_provider_options = 'dbug=d,abort_trx_end'; +# Release the applier +SET GLOBAL wsrep_provider_options = 'signal=apply_monitor_slave_enter_sync'; +# Wait until the applier has BF aborted the local transaction +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'dbug='; +# Release the applier and the local transaction +SET GLOBAL wsrep_provider_options = 'signal=abort_trx_end'; +SET GLOBAL wsrep_provider_options = 'signal=commit_monitor_master_enter_sync'; +# The streaming transaction is BF aborted and rolled back +connection node_1; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +ROLLBACK; +# The rollback must have removed the already replicated fragments +# from both nodes, so neither node has row 4 +connection node_1; +SELECT * FROM t1; +f1 f2 +1 0 +2 2 +3 0 +connection node_2; +SELECT * FROM t1; +f1 f2 +1 0 +2 2 +3 0 +connection node_1a; +SELECT COUNT(*) AS EXPECT_0 FROM mysql.wsrep_streaming_log; +EXPECT_0 +0 +connection node_1; +DROP SEQUENCE s; +DROP TABLE t1; +connection node_1; +CREATE SEQUENCE s INCREMENT=0 CACHE=5 ENGINE=InnoDB; +CREATE TABLE t1 (f1 INT PRIMARY KEY, f2 BIGINT) ENGINE=InnoDB; +connection node_1; +SET SESSION wsrep_trx_fragment_unit='rows'; +SET SESSION wsrep_trx_fragment_size=3; +START TRANSACTION; +INSERT INTO t1 VALUES (100, NULL); +# Block the applier on node #1 and let node #2 reserve several +# ranges of its own +connection node_1a; +SET GLOBAL wsrep_provider_options = 'dbug=d,apply_monitor_slave_enter_sync'; +connection node_2; +INSERT INTO t1 VALUES (201,NEXT VALUE FOR s),(202,NEXT VALUE FOR s),(203,NEXT VALUE FOR s),(204,NEXT VALUE FOR s),(205,NEXT VALUE FOR s),(206,NEXT VALUE FOR s),(207,NEXT VALUE FOR s),(208,NEXT VALUE FOR s),(209,NEXT VALUE FOR s),(210,NEXT VALUE FOR s),(211,NEXT VALUE FOR s),(212,NEXT VALUE FOR s); +connection node_1a; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'dbug='; +# Block the commit monitor and let the local transaction reserve +# several ranges of its own, spread over several fragments +SET GLOBAL wsrep_provider_options = 'dbug=d,commit_monitor_master_enter_sync'; +connection node_1; +INSERT INTO t1 VALUES (101,NEXT VALUE FOR s),(102,NEXT VALUE FOR s),(103,NEXT VALUE FOR s),(104,NEXT VALUE FOR s),(105,NEXT VALUE FOR s),(106,NEXT VALUE FOR s),(107,NEXT VALUE FOR s),(108,NEXT VALUE FOR s),(109,NEXT VALUE FOR s),(110,NEXT VALUE FOR s),(111,NEXT VALUE FOR s),(112,NEXT VALUE FOR s); +connection node_1a; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'dbug='; +# Release the applier +SET GLOBAL wsrep_provider_options = 'signal=apply_monitor_slave_enter_sync'; +# Release the local transaction +SET GLOBAL wsrep_provider_options = 'signal=commit_monitor_master_enter_sync'; +connection node_1; +COMMIT; +# Both nodes reserved more than one cache range +connection node_1; +SELECT next_not_cached_value > 21 AS EXPECT_1 FROM s; +EXPECT_1 +1 +# No value was handed out twice, and the two nodes used disjoint +# value spaces +SELECT COUNT(*) AS EXPECT_25 FROM t1; +EXPECT_25 +25 +SELECT COUNT(f2) = COUNT(DISTINCT f2) AS EXPECT_1 FROM t1; +EXPECT_1 +1 +SELECT COUNT(*) AS EXPECT_0 FROM t1 WHERE f1 < 200 AND MOD(f2, 2) != 1; +EXPECT_0 +0 +SELECT COUNT(*) AS EXPECT_0 FROM t1 WHERE f1 > 200 AND MOD(f2, 2) != 0; +EXPECT_0 +0 +connection node_2; +SELECT COUNT(*) AS EXPECT_25 FROM t1; +EXPECT_25 +25 +SELECT COUNT(f2) = COUNT(DISTINCT f2) AS EXPECT_1 FROM t1; +EXPECT_1 +1 +# All fragments must have been removed on commit +connection node_1a; +SELECT COUNT(*) AS EXPECT_0 FROM mysql.wsrep_streaming_log; +EXPECT_0 +0 +connection node_1; +SET SESSION wsrep_trx_fragment_size=DEFAULT; +SET SESSION wsrep_trx_fragment_unit=DEFAULT; +DROP SEQUENCE s; +DROP TABLE t1; +connection node_1; +wsrep_local_replays +1 +disconnect node_1a; diff --git a/mysql-test/suite/galera/t/galera_sequences_bf_kill_sr.cnf b/mysql-test/suite/galera/t/galera_sequences_bf_kill_sr.cnf new file mode 100644 index 00000000000..fd5f4edab8d --- /dev/null +++ b/mysql-test/suite/galera/t/galera_sequences_bf_kill_sr.cnf @@ -0,0 +1,12 @@ +!include ../galera_2nodes.cnf + +[mysqld.1] +wsrep-auto-increment-control=OFF +auto-increment-increment=2 +auto-increment-offset=1 +wsrep_trx_fragment_size=1 + +[mysqld.2] +wsrep-auto-increment-control=OFF +auto-increment-increment=2 +auto-increment-offset=2 diff --git a/mysql-test/suite/galera/t/galera_sequences_bf_kill_sr.combinations b/mysql-test/suite/galera/t/galera_sequences_bf_kill_sr.combinations new file mode 100644 index 00000000000..cef98e75213 --- /dev/null +++ b/mysql-test/suite/galera/t/galera_sequences_bf_kill_sr.combinations @@ -0,0 +1,5 @@ +[binlogon] +log-bin +log-slave-updates + +[binlogoff] diff --git a/mysql-test/suite/galera/t/galera_sequences_bf_kill_sr.test b/mysql-test/suite/galera/t/galera_sequences_bf_kill_sr.test new file mode 100644 index 00000000000..692df933a9b --- /dev/null +++ b/mysql-test/suite/galera/t/galera_sequences_bf_kill_sr.test @@ -0,0 +1,308 @@ +# +# Test a conflict on an InnoDB sequence between a streaming replication +# transaction and an applier. +# +# The local transaction reserves a new range of sequence values, which +# writes the sequence table while holding SEQUENCE::mutex. At the same +# time an applier applies a sequence update originating from the other +# node, and needs the same mutex in SEQUENCE::set_value(). +# +# If the sequence table write replicates a streaming fragment while the +# mutex is held, that fragment waits for commit order behind the applier's +# write set, while the applier waits for the mutex. The node deadlocks and +# the BF abort of the local transaction can never be delivered. +# +# This is specific to streaming replication. Without it the write set is +# replicated at COMMIT, after SEQUENCE::mutex has been released. +# + +--source include/galera_cluster.inc +--source include/have_innodb.inc +--source include/have_sequence.inc +--source include/have_debug.inc +--source include/have_debug_sync.inc +--source include/galera_have_debug_sync.inc + +--disable_ps2_protocol + +# +# We create an InnoDB sequence with a small cache that is then used as +# value for a column in the table. The cache is exhausted immediately, so +# that the first NEXTVAL on each node has to write the sequence table. +# +--connection node_1 +--let $wsrep_local_replays_old = `SELECT VARIABLE_VALUE FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_local_replays'` +CREATE SEQUENCE s INCREMENT=0 CACHE=5 ENGINE=InnoDB; +CREATE TABLE t1 (f1 INT PRIMARY KEY, f2 INT) ENGINE=InnoDB; +INSERT INTO t1 VALUES (1, 0), (3, 0); + +--connection node_1 +START TRANSACTION; +INSERT INTO t1 VALUES (4, 0); # No conflict in cert + +--connect node_1a, 127.0.0.1, root, , test, $NODE_MYPORT_1 +SET SESSION wsrep_sync_wait=0; + +--echo # The transaction above must have replicated a fragment, otherwise +--echo # this test degenerates into galera_sequences_bf_kill +SELECT COUNT(*) > 0 AS EXPECT_1 FROM mysql.wsrep_streaming_log; + +--echo # Block the applier on node #1 and issue a conflicting insert on node #2 +--let $galera_sync_point = apply_monitor_slave_enter_sync +--source include/galera_set_sync_point.inc + +# +# Send conflicting INSERT. This updates the sequence table on node #2, +# and the applier on node #1 will have to apply that update. +# +--connection node_2 +INSERT INTO t1 VALUES (2, next value for s); + +--connection node_1a +--source include/galera_wait_sync_point.inc +--source include/galera_clear_sync_point.inc + +--echo # Block the commit monitor and let the local transaction reserve a +--echo # new range of sequence values, which conflicts with the applier +--let $galera_sync_point = commit_monitor_master_enter_sync +--source include/galera_set_sync_point.inc + +--connection node_1 +--send INSERT INTO t1 VALUES (5, next value for s) + +--connection node_1a +--let $galera_sync_point = apply_monitor_slave_enter_sync commit_monitor_master_enter_sync +--source include/galera_wait_sync_point.inc +--source include/galera_clear_sync_point.inc + +--echo # Release the applier +--let $galera_sync_point = apply_monitor_slave_enter_sync +--source include/galera_signal_sync_point.inc + +--echo # Release the local transaction +--let $galera_sync_point = commit_monitor_master_enter_sync +--source include/galera_signal_sync_point.inc + +--connection node_1 +--reap +COMMIT; + +--echo # Both nodes must have the same table contents. The sequence values +--echo # are node specific, each node adjusts them to its own +--echo # auto_increment_offset +--connection node_1 +SELECT * FROM t1; +SELECT LASTVAL(s); +SELECT next_not_cached_value FROM s; + +--connection node_2 +SELECT * FROM t1; +SELECT LASTVAL(s); +SELECT next_not_cached_value FROM s; + +--echo # All fragments must have been removed on commit +--connection node_1a +--let $wait_condition = SELECT COUNT(*) = 0 FROM mysql.wsrep_streaming_log +--source include/wait_condition.inc +SELECT COUNT(*) AS EXPECT_0 FROM mysql.wsrep_streaming_log; + +--connection node_1 +DROP SEQUENCE s; +DROP TABLE t1; + +# +# Case 2: the same sequence conflict, but this time the applier also has +# to BF abort the local transaction over a gap lock. A streaming +# transaction cannot be replayed, so it is rolled back and the client +# gets ER_LOCK_DEADLOCK. +# +--connection node_1 +CREATE SEQUENCE s INCREMENT=0 CACHE=5 ENGINE=InnoDB; +CREATE TABLE t1 (f1 INT PRIMARY KEY, f2 INT) ENGINE=InnoDB; +INSERT INTO t1 VALUES (1, 0), (3, 0); + +--connection node_1 +START TRANSACTION; +INSERT INTO t1 VALUES (4, 0); +SELECT * FROM t1 WHERE f1 > 0 FOR UPDATE; # Should cause GAP lock between 1 and 3 + +--echo # Block the applier on node #1 and issue a conflicting insert on node #2 +--connection node_1a +--let $galera_sync_point = apply_monitor_slave_enter_sync +--source include/galera_set_sync_point.inc + +--connection node_2 +INSERT INTO t1 VALUES (2, next value for s); # BF aborts on the GAP lock + +--connection node_1a +--source include/galera_wait_sync_point.inc +--source include/galera_clear_sync_point.inc + +--echo # Block the commit monitor and let the local transaction reserve a +--echo # new range of sequence values, which conflicts with the applier +--let $galera_sync_point = commit_monitor_master_enter_sync +--source include/galera_set_sync_point.inc + +--connection node_1 +--send INSERT INTO t1 VALUES (5, next value for s) + +--connection node_1a +--let $galera_sync_point = apply_monitor_slave_enter_sync commit_monitor_master_enter_sync +--source include/galera_wait_sync_point.inc +--source include/galera_clear_sync_point.inc + +# +# Arm a sync point in the BF abort path before releasing the applier. +# Otherwise the local transaction could be released from the commit +# monitor and finish its fragment before the applier has reached the gap +# lock conflict, and no BF abort would be delivered at all. The sibling +# test galera_sequences_bf_kill.test uses the same handshake. +# +--let $galera_sync_point = abort_trx_end +--source include/galera_set_sync_point.inc + +--echo # Release the applier +--let $galera_sync_point = apply_monitor_slave_enter_sync +--source include/galera_signal_sync_point.inc + +--echo # Wait until the applier has BF aborted the local transaction +--let $galera_sync_point = abort_trx_end commit_monitor_master_enter_sync +--source include/galera_wait_sync_point.inc +--source include/galera_clear_sync_point.inc + +--echo # Release the applier and the local transaction +--let $galera_sync_point = abort_trx_end +--source include/galera_signal_sync_point.inc +--let $galera_sync_point = commit_monitor_master_enter_sync +--source include/galera_signal_sync_point.inc + +--echo # The streaming transaction is BF aborted and rolled back +--connection node_1 +--error ER_LOCK_DEADLOCK +--reap +ROLLBACK; + +--echo # The rollback must have removed the already replicated fragments +--echo # from both nodes, so neither node has row 4 +--connection node_1 +SELECT * FROM t1; + +--connection node_2 +SELECT * FROM t1; + +--connection node_1a +--let $wait_condition = SELECT COUNT(*) = 0 FROM mysql.wsrep_streaming_log +--source include/wait_condition.inc +SELECT COUNT(*) AS EXPECT_0 FROM mysql.wsrep_streaming_log; + +--connection node_1 +DROP SEQUENCE s; +DROP TABLE t1; + +# +# Case 3: several rows per fragment and several cache range reservations +# inside one streaming transaction, on both nodes at the same time. +# +# The cases above stream one row per fragment and reserve a single cache +# range on each side. Here both nodes insert twelve rows, so with CACHE=5 +# each of them exhausts its cache three times and writes the sequence +# table three times, every time under SEQUENCE::mutex. On node #1 the +# fragment unit is rows and the fragment size is three, so those writes +# land inside fragments that carry several rows. +# +# The choreography is the one from case 1: the applier on node #1 is held +# back until the local transaction has reserved its ranges and is parked +# in the commit monitor, which is the state the deadlock needed. Both +# transactions must commit, and the values the two nodes handed out must +# be disjoint - each node reserves whole cache ranges and picks values +# from them by its own auto_increment_offset, node #1 the odd ones and +# node #2 the even ones. +# +--connection node_1 +CREATE SEQUENCE s INCREMENT=0 CACHE=5 ENGINE=InnoDB; +CREATE TABLE t1 (f1 INT PRIMARY KEY, f2 BIGINT) ENGINE=InnoDB; + +--connection node_1 +SET SESSION wsrep_trx_fragment_unit='rows'; +SET SESSION wsrep_trx_fragment_size=3; +START TRANSACTION; +INSERT INTO t1 VALUES (100, NULL); # No conflict in cert + +--echo # Block the applier on node #1 and let node #2 reserve several +--echo # ranges of its own +--connection node_1a +--let $galera_sync_point = apply_monitor_slave_enter_sync +--source include/galera_set_sync_point.inc + +--connection node_2 +INSERT INTO t1 VALUES (201,NEXT VALUE FOR s),(202,NEXT VALUE FOR s),(203,NEXT VALUE FOR s),(204,NEXT VALUE FOR s),(205,NEXT VALUE FOR s),(206,NEXT VALUE FOR s),(207,NEXT VALUE FOR s),(208,NEXT VALUE FOR s),(209,NEXT VALUE FOR s),(210,NEXT VALUE FOR s),(211,NEXT VALUE FOR s),(212,NEXT VALUE FOR s); + +--connection node_1a +--source include/galera_wait_sync_point.inc +--source include/galera_clear_sync_point.inc + +--echo # Block the commit monitor and let the local transaction reserve +--echo # several ranges of its own, spread over several fragments +--let $galera_sync_point = commit_monitor_master_enter_sync +--source include/galera_set_sync_point.inc + +--connection node_1 +--send INSERT INTO t1 VALUES (101,NEXT VALUE FOR s),(102,NEXT VALUE FOR s),(103,NEXT VALUE FOR s),(104,NEXT VALUE FOR s),(105,NEXT VALUE FOR s),(106,NEXT VALUE FOR s),(107,NEXT VALUE FOR s),(108,NEXT VALUE FOR s),(109,NEXT VALUE FOR s),(110,NEXT VALUE FOR s),(111,NEXT VALUE FOR s),(112,NEXT VALUE FOR s) + +--connection node_1a +--let $galera_sync_point = apply_monitor_slave_enter_sync commit_monitor_master_enter_sync +--source include/galera_wait_sync_point.inc +--source include/galera_clear_sync_point.inc + +--echo # Release the applier +--let $galera_sync_point = apply_monitor_slave_enter_sync +--source include/galera_signal_sync_point.inc + +--echo # Release the local transaction +--let $galera_sync_point = commit_monitor_master_enter_sync +--source include/galera_signal_sync_point.inc + +--connection node_1 +--reap +COMMIT; + +--echo # Both nodes reserved more than one cache range +--connection node_1 +--let $wait_condition = SELECT COUNT(*) = 25 FROM t1 +--source include/wait_condition.inc +SELECT next_not_cached_value > 21 AS EXPECT_1 FROM s; + +--echo # No value was handed out twice, and the two nodes used disjoint +--echo # value spaces +SELECT COUNT(*) AS EXPECT_25 FROM t1; +SELECT COUNT(f2) = COUNT(DISTINCT f2) AS EXPECT_1 FROM t1; +SELECT COUNT(*) AS EXPECT_0 FROM t1 WHERE f1 < 200 AND MOD(f2, 2) != 1; +SELECT COUNT(*) AS EXPECT_0 FROM t1 WHERE f1 > 200 AND MOD(f2, 2) != 0; + +--connection node_2 +--let $wait_condition = SELECT COUNT(*) = 25 FROM t1 +--source include/wait_condition.inc +SELECT COUNT(*) AS EXPECT_25 FROM t1; +SELECT COUNT(f2) = COUNT(DISTINCT f2) AS EXPECT_1 FROM t1; + +--echo # All fragments must have been removed on commit +--connection node_1a +--let $wait_condition = SELECT COUNT(*) = 0 FROM mysql.wsrep_streaming_log +--source include/wait_condition.inc +SELECT COUNT(*) AS EXPECT_0 FROM mysql.wsrep_streaming_log; + +--connection node_1 +SET SESSION wsrep_trx_fragment_size=DEFAULT; +SET SESSION wsrep_trx_fragment_unit=DEFAULT; +DROP SEQUENCE s; +DROP TABLE t1; + +# Streaming transactions are rolled back rather than replayed, so no +# replay happened in any of the cases above +--connection node_1 +--let $wsrep_local_replays_new = `SELECT VARIABLE_VALUE FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_local_replays'` +--disable_query_log +--eval SELECT $wsrep_local_replays_new - $wsrep_local_replays_old = 0 AS wsrep_local_replays; +--enable_query_log + +--disconnect node_1a diff --git a/sql/handler.cc b/sql/handler.cc index 27b3e668238..d7485096f5c 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -7440,7 +7440,11 @@ int handler::ha_reset() DBUG_RETURN(reset()); } -static int wsrep_after_row(THD *thd) +/* + If skip_streaming is true, the row is counted against wsrep_max_ws_rows + and validated as usual, but no streaming fragment is replicated for it. +*/ +static int wsrep_after_row(THD *thd, bool skip_streaming= false) { DBUG_ENTER("wsrep_after_row"); #ifdef WITH_WSREP @@ -7463,7 +7467,7 @@ static int wsrep_after_row(THD *thd) my_message(ER_ERROR_DURING_COMMIT, "wsrep_max_ws_rows exceeded", MYF(0)); DBUG_RETURN(ER_ERROR_DURING_COMMIT); } - else if (wsrep_after_row_internal(thd)) + else if (wsrep_after_row_internal(thd, skip_streaming)) { DBUG_RETURN(ER_LOCK_DEADLOCK); } @@ -7890,9 +7894,20 @@ int handler::ha_write_row(const uchar *buf) error= binlog_log_row(table, 0, buf, log_func); } + /* + Sequence tables are written with SEQUENCE::mutex held (see + SEQUENCE::next_value() and ha_sequence::write_row()). For a streaming + transaction the streaming step would replicate a fragment and block + waiting for certification and commit order, while an applier may be + waiting for the same mutex in SEQUENCE::set_value(). That deadlocks + the node, so skip only that step here. The row has already been + appended to the write set and will be replicated with the following + fragment, or at commit. Everything else wsrep_after_row() does, in + particular counting the row against wsrep_max_ws_rows, still applies. + */ if (WSREP_NNULL(ha_thd()) && table_share->tmp_table == NO_TMP_TABLE && ht->flags & HTON_WSREP_REPLICATION && !error) - error= wsrep_after_row(ha_thd()); + error= wsrep_after_row(ha_thd(), table_share->sequence != NULL); err: DEBUG_SYNC_C("ha_write_row_end"); diff --git a/sql/wsrep_trans_observer.h b/sql/wsrep_trans_observer.h index 2cc89044cc6..20409f3ecc3 100644 --- a/sql/wsrep_trans_observer.h +++ b/sql/wsrep_trans_observer.h @@ -165,9 +165,13 @@ static inline int wsrep_start_trx_if_not_started(THD* thd) /* Called after each row operation. + If skip_streaming is true, the streaming replication step is not run for + this row. The row is still validated here and accounted for by the caller; + it will be replicated with the following fragment, or at commit. + Return zero on succes, non-zero on failure. */ -static inline int wsrep_after_row_internal(THD* thd) +static inline int wsrep_after_row_internal(THD* thd, bool skip_streaming= false) { if (thd->wsrep_cs().state() != wsrep::client_state::s_none && wsrep_thd_is_local(thd)) @@ -176,7 +180,7 @@ static inline int wsrep_after_row_internal(THD* thd) { return 1; } - else if (wsrep_streaming_enabled(thd)) + else if (!skip_streaming && wsrep_streaming_enabled(thd)) { return thd->wsrep_cs().after_row(); } From 1500fc683958b4ce72c4bc5f4f0944e905d12b6e Mon Sep 17 00:00:00 2001 From: Vladislav Vaintroub Date: Thu, 27 Aug 2026 23:10:13 +0200 Subject: [PATCH 17/71] Rocksdb - suppress MSVC warning in external code ribbon_impl.h(879,1): warning C4723: potential divide by 0 on VS2025 --- storage/rocksdb/build_rocksdb.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/storage/rocksdb/build_rocksdb.cmake b/storage/rocksdb/build_rocksdb.cmake index 8866f26f03a..f2c88a42d8e 100644 --- a/storage/rocksdb/build_rocksdb.cmake +++ b/storage/rocksdb/build_rocksdb.cmake @@ -105,7 +105,7 @@ elseif(CMAKE_SYSTEM_NAME MATCHES "Windows") endif() IF(MSVC) - add_definitions(/wd4244) + add_definitions(/wd4244 /wd4723) ENDIF() if(NOT WIN32) add_definitions(-DROCKSDB_PLATFORM_POSIX -DROCKSDB_LIB_IO_POSIX) From bcecf1e5c8614111cfd8fc4133a4f564ac8b5566 Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Wed, 26 Aug 2026 15:00:25 +1000 Subject: [PATCH 18/71] MDEV-40921 Large allocations Use MMAP_NORESERVE (but not large_pages) The default innodb_buffer_pool_size_max of 8TiB cannot be reserved on Illumos because anonymous mappings reserve backing store (swap) when they are created, irrespective of the page protections. Pass MAP_NORESERVE when reserving the buffer pool address range; swap is still properly reserved, and out-of-memory reported, when ranges are committed. commit message by Andy Fiddaman. On Linux MAP_NORESERVE has similar meaning in that no swap space is reserved. In the Linux case per manual(mmap), mariadbd may SEGV if there isn't the swap space available. This quick kill seems preferable to attempting to run a buffer pool from swap. Note MAP_NORESERVE isn't used for large pages as we want the allocation failure to be early. Having failure on a first access here is unrecoverable while an large page allocation failure can fall back to a non-large page. Place -1 ptr constant with MAP_FAILED. Its used elsewhere in code and matches mmmap documentation. Other BSDs and MacOS appear to not implement the flag. --- mysys/my_largepage.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/mysys/my_largepage.c b/mysys/my_largepage.c index 66f45b8425d..731f41908d1 100644 --- a/mysys/my_largepage.c +++ b/mysys/my_largepage.c @@ -281,6 +281,9 @@ MAP_ANON but MAP_ANONYMOUS is marked "for compatibility" */ #else #error unsupported mmap - no MAP_ANON{YMOUS} #endif +#ifndef MAP_NORESERVE +#define MAP_NORESERVE 0 +#endif #endif /* HAVE_MMAP && !_WIN32 */ /** @@ -442,6 +445,10 @@ char *my_large_virtual_alloc(size_t *size) while ((large_page_size= my_next_large_page_size(*size, &page_i)) != 0) { + /* + note we don't use MAP_NORESERVE here. Doing so would successfully allocate + large pages that fail on the first access to those memory regions. + */ int mapflag= MAP_PRIVATE | # ifdef MAP_POPULATE MAP_POPULATE | @@ -463,7 +470,7 @@ char *my_large_virtual_alloc(size_t *size) size_t aligned_size= MY_ALIGN(*size, (size_t) large_page_size); ptr= mmap(NULL, aligned_size, PROT_READ | PROT_WRITE, mapflag, -1, 0); - if (ptr == (void*) -1) + if (ptr == MAP_FAILED) { ptr= NULL; /* try next smaller memory size */ @@ -493,7 +500,13 @@ char *my_large_virtual_alloc(size_t *size) ptr= mmap(NULL, *size, PROT_READ | PROT_WRITE, MAP_PRIVATE | OS_MAP_ANON, -1, 0); # else - ptr= mmap(NULL, *size, PROT_NONE, MAP_PRIVATE | OS_MAP_ANON, -1, 0); +/* + Illumos important to have MAP_NORESERVE otherwise reserves all swap. On + innodb_buffer_pool_size_max overallocation. + Linux is controlled on sysctl vm.overcommit_memory. +*/ + ptr= mmap(NULL, *size, PROT_NONE, MAP_PRIVATE | OS_MAP_ANON | MAP_NORESERVE, + -1, 0); # endif if (ptr == MAP_FAILED) ptr= NULL; From 677ed9a467f211a1ad97fc2fcf8edf28e8a33f1a Mon Sep 17 00:00:00 2001 From: Akshat Nehra Date: Tue, 25 Aug 2026 23:56:08 +0000 Subject: [PATCH 19/71] MDEV-40867 CONNECT writes unvalidated data from remote filter into fixed-len buffer TestFil() in storage/connect/tabtbl.cpp uses unbounded sscanf format specifiers to parse TABID filter values pushed from ha_connect::CheckCond(). When a WHERE tabname='...' filter exceeds NAME_LEN bytes (192), sscanf overflows the stack-allocated tn[NAME_LEN] buffer, corrupting the stack and crashing mysqld with SIGSEGV. Fix: add width specifiers to bound all sscanf writes: - %7s for op[8] - %192[^'] for tn (NAME_LEN bytes + null terminator) All new code of the whole pull request, including one or several files that are either new files or modified ones, are contributed under the BSD-new license. I am contributing on behalf of my employer Amazon Web Services, Inc. --- .../connect/mysql-test/connect/r/tbl.result | 31 +++++++++++++++++++ storage/connect/mysql-test/connect/t/tbl.test | 25 +++++++++++++++ storage/connect/tabtbl.cpp | 16 +++++++--- 3 files changed, 68 insertions(+), 4 deletions(-) diff --git a/storage/connect/mysql-test/connect/r/tbl.result b/storage/connect/mysql-test/connect/r/tbl.result index 0932220a263..e12148ceed0 100644 --- a/storage/connect/mysql-test/connect/r/tbl.result +++ b/storage/connect/mysql-test/connect/r/tbl.result @@ -150,3 +150,34 @@ x int, y int ) engine=connect table_type=FIX file_name='test.txt' with system versioning; ERROR HY000: System-versioned tables do not support CONNECT storage engine +# +# MDEV-40867 Stack buffer overflow in TDBTBL::TestFil() +# +CREATE TABLE t1 (a INT NOT NULL, message CHAR(10)) ENGINE=CONNECT; +Warnings: +Warning 1105 No table_type. Will be set to DOS +Warning 1105 No file name. Table will use t1.dos +INSERT INTO t1 VALUES (1, 'hello'); +CREATE TABLE t2 (a INT NOT NULL, message CHAR(10)) ENGINE=CONNECT; +Warnings: +Warning 1105 No table_type. Will be set to DOS +Warning 1105 No file name. Table will use t2.dos +INSERT INTO t2 VALUES (2, 'world'); +CREATE TABLE total ( +tabname CHAR(8) NOT NULL SPECIAL='TABID', +ta TINYINT NOT NULL FLAG=1, +message CHAR(20) +) ENGINE=CONNECT TABLE_TYPE=TBL TABLE_LIST='t1,t2'; +SELECT * FROM total WHERE tabname = 't1'; +tabname ta message +t1 1 hello +SELECT * FROM total WHERE tabname = REPEAT('A', 300); +tabname ta message +SELECT * FROM total WHERE tabname IN (REPEAT('B', 300)); +tabname ta message +SELECT * FROM total WHERE tabname NOT IN (REPEAT('C', 300)); +tabname ta message +t1 1 hello +t2 2 world +DROP TABLE total, t1, t2; +# End of 10.11 tests diff --git a/storage/connect/mysql-test/connect/t/tbl.test b/storage/connect/mysql-test/connect/t/tbl.test index 19f633e9bf7..832712dda11 100644 --- a/storage/connect/mysql-test/connect/t/tbl.test +++ b/storage/connect/mysql-test/connect/t/tbl.test @@ -62,3 +62,28 @@ create table t2 ( x int, y int ) engine=connect table_type=FIX file_name='test.txt' with system versioning; + +--echo # +--echo # MDEV-40867 Stack buffer overflow in TDBTBL::TestFil() +--echo # + +CREATE TABLE t1 (a INT NOT NULL, message CHAR(10)) ENGINE=CONNECT; +INSERT INTO t1 VALUES (1, 'hello'); + +CREATE TABLE t2 (a INT NOT NULL, message CHAR(10)) ENGINE=CONNECT; +INSERT INTO t2 VALUES (2, 'world'); + +CREATE TABLE total ( + tabname CHAR(8) NOT NULL SPECIAL='TABID', + ta TINYINT NOT NULL FLAG=1, + message CHAR(20) +) ENGINE=CONNECT TABLE_TYPE=TBL TABLE_LIST='t1,t2'; + +SELECT * FROM total WHERE tabname = 't1'; +SELECT * FROM total WHERE tabname = REPEAT('A', 300); +SELECT * FROM total WHERE tabname IN (REPEAT('B', 300)); +SELECT * FROM total WHERE tabname NOT IN (REPEAT('C', 300)); + +DROP TABLE total, t1, t2; + +--echo # End of 10.11 tests diff --git a/storage/connect/tabtbl.cpp b/storage/connect/tabtbl.cpp index 607e7d131f3..45ae6e02132 100644 --- a/storage/connect/tabtbl.cpp +++ b/storage/connect/tabtbl.cpp @@ -287,12 +287,20 @@ bool TDBTBL::InitTableList(PGLOBAL g) return FALSE; } // end of InitTableList +#define OP_SIZE 7 +#define TN_SIZE 192 +#if TN_SIZE != NAME_LEN +#error "TN_SIZE must equal NAME_LEN" +#endif +#define _OP STRINGIFY_ARG(OP_SIZE) +#define _TN STRINGIFY_ARG(TN_SIZE) + /***********************************************************************/ /* Test the tablename against the pseudo "local" filter. */ /***********************************************************************/ bool TDBTBL::TestFil(PGLOBAL g, PCFIL filp, PTABLE tabp) { - char *body, *fil, op[8], tn[NAME_LEN]; + char *body, *fil, op[OP_SIZE+1], tn[TN_SIZE+1]; bool neg; if (!filp) @@ -305,7 +313,7 @@ bool TDBTBL::TestFil(PGLOBAL g, PCFIL filp, PTABLE tabp) else fil = body + (*body == '(' ? 1 : 0); - if (sscanf(fil, "TABID %s", op) != 1) + if (sscanf(fil, "TABID %" _OP "s", op) != 1) return TRUE; // ignore invalid filter if ((neg = !strcmp(op, "NOT"))) @@ -313,7 +321,7 @@ bool TDBTBL::TestFil(PGLOBAL g, PCFIL filp, PTABLE tabp) if (!strcmp(op, "=")) { // Temporarily, filter must be "TABID = 'value'" only - if (sscanf(fil, "TABID = '%[^']'", tn) != 1) + if (sscanf(fil, "TABID = '%" _TN "[^']'", tn) != 1) return TRUE; // ignore invalid filter return !stricmp(tn, tabp->GetName()); @@ -333,7 +341,7 @@ bool TDBTBL::TestFil(PGLOBAL g, PCFIL filp, PTABLE tabp) if ((p = strchr(tnl, ','))) *p++ = 0; - if (sscanf(tnl, "'%[^']'", tn) != 1) + if (sscanf(tnl, "'%" _TN "[^']'", tn) != 1) return TRUE; // ignore invalid filter else if (!stricmp(tn, tabp->GetName())) return !neg; // Found From ca0e7216e18cb01f74e95b20619e94b209757f4b Mon Sep 17 00:00:00 2001 From: Kristian Nielsen Date: Fri, 28 Aug 2026 15:23:35 +0200 Subject: [PATCH 20/71] MDEV-39779: binlog.binlog_gtid_index sporadic failure The GTID index is written asynchronously from the binlog background thread, the test would fail when trying to read the index file before the background thread had time to write it. Fix by making the test case wait for the file to reach the expected size before accessing. Signed-off-by: Kristian Nielsen --- mysql-test/suite/binlog/t/binlog_gtid_index.test | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/mysql-test/suite/binlog/t/binlog_gtid_index.test b/mysql-test/suite/binlog/t/binlog_gtid_index.test index 458b77ec9a7..16a301c883e 100644 --- a/mysql-test/suite/binlog/t/binlog_gtid_index.test +++ b/mysql-test/suite/binlog/t/binlog_gtid_index.test @@ -120,6 +120,17 @@ use warnings; use Fcntl qw(:DEFAULT :seek); sysopen F, $ENV{FILE_TO_CORRUPT}, O_RDWR or die "Cannot open file $ENV{FILE_TO_CORRUPT}: $!\n"; + +# Wait for the index file to be written (this happens asynchronously). +my @s; +for (1 .. 500) { + @s= stat F; + last if $s[7] >= 17; # Check file size + select(undef, undef, undef, 0.1); # Portable sub-second sleep +} +die "Index file $ENV{FILE_TO_CORRUPT} never grew past $s[7] bytes\n" + unless $s[7] >= 17; + # Corrupt the flag byte with an unused flag. sysseek(F, 16, SEEK_SET) or die "Cannot seek file: $!\n"; From 6293023a57086d1625ed3897d0fe4895e555c00d Mon Sep 17 00:00:00 2001 From: Kristian Nielsen Date: Thu, 30 Jul 2026 17:46:32 +0200 Subject: [PATCH 21/71] MDEV-39774: Assertion on slave with binlog_row_image=MINIMAL When finding the row to modify for a row event, and when not using rnd_pos_by_record() to locate the row, the code would use table->use_all_columns(), which makes the read_set and write_set point to the table->s->all_set in the table share. This caused problems when other code later modified bits in the read_set or write_set, which ends up wrongly modifying the table share. We can just use bitmap_set_all(table->read_set) to mark to read all columns and leave the possibility to later change the bits as needed. This code changes in this patch must be null-merged from 10.11 to 11.4, as there the problem is fixed differently. Signed-off-by: Kristian Nielsen --- mysql-test/suite/rpl/r/rpl_row_img_min.result | 18 ++++++++++++ mysql-test/suite/rpl/t/rpl_row_img_min.test | 28 +++++++++++++++++++ sql/log_event_server.cc | 6 +++- 3 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 mysql-test/suite/rpl/r/rpl_row_img_min.result create mode 100644 mysql-test/suite/rpl/t/rpl_row_img_min.test diff --git a/mysql-test/suite/rpl/r/rpl_row_img_min.result b/mysql-test/suite/rpl/r/rpl_row_img_min.result new file mode 100644 index 00000000000..0fab7fffd41 --- /dev/null +++ b/mysql-test/suite/rpl/r/rpl_row_img_min.result @@ -0,0 +1,18 @@ +include/master-slave.inc +[connection master] +### MDEV-39774: Assertion `bitmap_is_set_all(&table->s->all_set)' fails on slave with binlog_row_image=MINIMAL +connection slave; +SET @old_binlog_image = @@global.binlog_row_image; +SET GLOBAL binlog_row_image = MINIMAL; +include/restart_slave.inc +connection master; +SET binlog_row_image = MINIMAL; +CREATE TABLE t (pk INT PRIMARY KEY, c CHAR(20), k INT); +INSERT INTO t (pk) SELECT seq FROM seq_1_to_325; +UPDATE t SET c = '01234567890123456789'; +connection slave; +SET GLOBAL binlog_row_image = @old_binlog_image; +include/restart_slave.inc +connection master; +DROP TABLE t; +include/rpl_end.inc diff --git a/mysql-test/suite/rpl/t/rpl_row_img_min.test b/mysql-test/suite/rpl/t/rpl_row_img_min.test new file mode 100644 index 00000000000..9f71189ef80 --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_row_img_min.test @@ -0,0 +1,28 @@ +--source include/have_sequence.inc +--source include/have_binlog_format_row.inc +--source include/master-slave.inc + +--echo ### MDEV-39774: Assertion `bitmap_is_set_all(&table->s->all_set)' fails on slave with binlog_row_image=MINIMAL +--connection slave +SET @old_binlog_image = @@global.binlog_row_image; +SET GLOBAL binlog_row_image = MINIMAL; +--source include/restart_slave_sql.inc + +--connection master +SET binlog_row_image = MINIMAL; + +CREATE TABLE t (pk INT PRIMARY KEY, c CHAR(20), k INT); +INSERT INTO t (pk) SELECT seq FROM seq_1_to_325; +UPDATE t SET c = '01234567890123456789'; + +--sync_slave_with_master + +# Cleanup + +SET GLOBAL binlog_row_image = @old_binlog_image; +--source include/restart_slave_sql.inc + +--connection master +DROP TABLE t; + +--source include/rpl_end.inc diff --git a/sql/log_event_server.cc b/sql/log_event_server.cc index 85bec1cc77c..bf0984a1030 100644 --- a/sql/log_event_server.cc +++ b/sql/log_event_server.cc @@ -8396,7 +8396,11 @@ int Rows_log_event::find_row(rpl_group_info *rgi) We need to retrieve all fields TODO: Move this out from this function to main loop */ - table->use_all_columns(); + /* + This MDEV-39774 change must be null-merged from 10.11 to 11.4, IOW the + following line should be removed in the merge. + */ + bitmap_set_all(table->read_set); /* Save copy of the record in table->record[1]. It might be needed From 40b0ba2e0f4d1e06630db8ec3aab85659a961d9a Mon Sep 17 00:00:00 2001 From: Kristian Nielsen Date: Sat, 29 Aug 2026 22:56:21 +0200 Subject: [PATCH 22/71] MDEV-40575: Sporadic failure of rpl.rpl_gtid_crash The test fails because the slave is configured in the test with the flaky --init-rpl-role=slave option by default. As the test case is crashing the slave at various points, this option occasionally causes the slave to *truncate* away a transaction during crash recovery, which is surely not intended for this test. The use of init-rpl-role=slave by default goes back to 2007(!), when this option did not have any functionality, and when the option was re-purposed for the flaky truncate-binlog-at-recovery functionality this default was overlooked and not removed. The tests that want to test this marginal functionality should (and do) enable it explicitly. So remove the use of init-rpl-role=slave by default in the mtr --suite=rpl. Signed-off-by: Kristian Nielsen --- mysql-test/suite/rpl/rpl_1slave_base.cnf | 1 - 1 file changed, 1 deletion(-) diff --git a/mysql-test/suite/rpl/rpl_1slave_base.cnf b/mysql-test/suite/rpl/rpl_1slave_base.cnf index a7a7f5c6218..3716d994d9f 100644 --- a/mysql-test/suite/rpl/rpl_1slave_base.cnf +++ b/mysql-test/suite/rpl/rpl_1slave_base.cnf @@ -22,7 +22,6 @@ log-basename= master log-basename= slave # relay-log= slave-relay-bin -init-rpl-role= slave log-slave-updates master-retry-count= 10 From 79f96bf15d2538cb78201cc85681571f56548957 Mon Sep 17 00:00:00 2001 From: Vladislav Vaintroub Date: Mon, 31 Aug 2026 12:52:07 +0200 Subject: [PATCH 23/71] Appveyor - post-fix 1a052f2 After 1a052f2 and 1fb0755, disabling RocksDB build via "git config" alone no longer works. Add -DPLUGIN_ROCKSDB=NO to cmake config line to workaround --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index cf220c17862..85740e90372 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -57,7 +57,7 @@ build_script: - set PATH=%OPENSSL_ROOT_DIR%;%PATH% - set GENERATOR=-GNinja - call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat" - - cmake -E time cmake %GENERATOR% .. -DWITH_SSL=system -DHAVE_OPENSSL_APPLINK_C=1 -DOPENSSL_ROOT_DIR=%OPENSSL_ROOT_DIR% -DCMAKE_BUILD_TYPE=%BUILD_TYPE% -DMYSQL_MAINTAINER_MODE=ERR -DFAST_BUILD=1 -DBISON_EXECUTABLE=C:\cygwin64\bin\bison.exe -DPLUGIN_PERFSCHEMA=NO -DPLUGIN_FEEDBACK=NO + - cmake -E time cmake %GENERATOR% .. -DWITH_SSL=system -DHAVE_OPENSSL_APPLINK_C=1 -DOPENSSL_ROOT_DIR=%OPENSSL_ROOT_DIR% -DCMAKE_BUILD_TYPE=%BUILD_TYPE% -DMYSQL_MAINTAINER_MODE=ERR -DFAST_BUILD=1 -DBISON_EXECUTABLE=C:\cygwin64\bin\bison.exe -DPLUGIN_PERFSCHEMA=NO -DPLUGIN_FEEDBACK=NO -DPLUGIN_ROCKSDB=NO - set /A jobs=2*%NUMBER_OF_PROCESSORS% - cmake -E time cmake --build . -j %jobs% --config %BUILD_TYPE% --target minbuild From 42038e1145dd43b3910055c364fb720a8a152d61 Mon Sep 17 00:00:00 2001 From: Thirunarayanan Balathandayuthapani Date: Thu, 3 Sep 2026 19:19:08 +0530 Subject: [PATCH 24/71] MDEV-26057 Assertion `!vcol->v_indexes.empty() in trx_undo_log_v_idx Problem: ======== - Rollback of an INPLACE ALTER TABLE is executed while holding only a shared metadata lock on the table, so DML can run concurrently. rollback_inplace_alter_table() resets dict_col_t::ord_part in a critical section of its own, after row_merge_drop_indexes() already removed the aborted indexes from the dictionary cache and emptied dict_v_col_t::v_indexes. During this time, DML statement can see a virtual column with ord_part set and an empty v_indexes, which makes assert failure in trx_undo_report_insert_virtual(). Solution: ======== row_merge_reset_ord_part(): Added a function to reset dict_col_t::ord_part for the columns that are no longer a field of any index remaining in the dictionary cache. For virtual columns the decision is based on dict_v_col_t::v_indexes being empty, and no element is ever removed from that list. row_merge_drop_indexes(): Added a call to row_merge_reset_ord_part() in the branch that removes the indexes from the cache, in the same dict_sys.latch critical section. That branch is taken only when MDL_EXCLUSIVE is held or when this is the only handle to the table, so no concurrent DML can observe the intermediate state. In the lazy drop branch the indexes and their v_indexes entries stay in the cache and nothing is reset; that is done later, when the indexes are dropped while holding MDL_EXCLUSIVE. check_col_exists_in_indexes(): Removed the only_committed parameter, which no longer has any caller. row_quiesce_col_ord_part(): Added a function to get dict_col_t::ord_part and dict_col_t::max_prefix of a column from the committed indexes that are present in the dictionary cache. row_quiesce_write_table(): Write the row_quiesce_col_ord_part() return values to the .cfg file instead of the cached dict_col_t fields, because a rolled back ADD INDEX leaves ord_part set until the aborted index is removed by a later DDL, and max_prefix is never reset when an index is dropped, which makes IMPORT TABLESPACE reject the tablespace with a bogus schema mismatch. --- .../innodb/r/import_tablespace_race.result | 2 +- .../r/innodb-virtual-columns-debug.result | 25 ++++++++ .../innodb/t/import_tablespace_race.test | 2 +- .../t/innodb-virtual-columns-debug.test | 25 ++++++++ storage/innobase/handler/handler0alter.cc | 25 +------- storage/innobase/row/row0merge.cc | 45 +++++++++++++++ storage/innobase/row/row0quiesce.cc | 57 ++++++++++++++++++- 7 files changed, 155 insertions(+), 26 deletions(-) diff --git a/mysql-test/suite/innodb/r/import_tablespace_race.result b/mysql-test/suite/innodb/r/import_tablespace_race.result index 6c8b2e3a26e..90c8c92241b 100644 --- a/mysql-test/suite/innodb/r/import_tablespace_race.result +++ b/mysql-test/suite/innodb/r/import_tablespace_race.result @@ -16,7 +16,7 @@ END REPEAT; END $ connection default; -ALTER TABLE t NOWAIT ADD INDEX (c); +ALTER TABLE t NOWAIT ADD INDEX (c(20)); connection con1; connection default; FLUSH TABLE t FOR EXPORT; diff --git a/mysql-test/suite/innodb/r/innodb-virtual-columns-debug.result b/mysql-test/suite/innodb/r/innodb-virtual-columns-debug.result index df6bf83d24f..21f4ee39093 100644 --- a/mysql-test/suite/innodb/r/innodb-virtual-columns-debug.result +++ b/mysql-test/suite/innodb/r/innodb-virtual-columns-debug.result @@ -16,3 +16,28 @@ disconnect con1; connection default; SET debug_sync= "RESET"; DROP TABLE t1; +# +# MDEV-26057 Assertion `!vcol->v_indexes.empty() in trx_undo_log_v_idx +# +CREATE TABLE t1(f1 INT NOT NULL, f2 INT NOT NULL, +f3 INT AS (f2) VIRTUAL) ENGINE=InnoDB; +INSERT INTO t1(f1, f2) VALUES(1, 1); +SET DEBUG_SYNC="alter_rollback_complete SIGNAL dml_start WAIT_FOR default_resume"; +set STATEMENT DEBUG_DBUG="d,innodb_OOM_inplace_alter" FOR alter table t1 add index(f3), algorithm=inplace; +connect con1,localhost,root,,,; +SET DEBUG_SYNC="now WAIT_FOR dml_start"; +INSERT INTO t1(f1, f2) VALUES(2, 2); +SET DEBUG_SYNC="now SIGNAL default_resume"; +connection default; +ERROR HY000: Out of memory. +connection default; +disconnect con1; +SET DEBUG_SYNC="RESET"; +SELECT * FROM t1; +f1 f2 f3 +1 1 1 +2 2 2 +CHECK TABLE t1; +Table Op Msg_type Msg_text +test.t1 check status OK +DROP TABLE t1; diff --git a/mysql-test/suite/innodb/t/import_tablespace_race.test b/mysql-test/suite/innodb/t/import_tablespace_race.test index aca0b5df06d..8f6a1a431d9 100644 --- a/mysql-test/suite/innodb/t/import_tablespace_race.test +++ b/mysql-test/suite/innodb/t/import_tablespace_race.test @@ -26,7 +26,7 @@ $ --connection default --error 0,ER_LOCK_WAIT_TIMEOUT -ALTER TABLE t NOWAIT ADD INDEX (c); +ALTER TABLE t NOWAIT ADD INDEX (c(20)); --connection con1 --reap diff --git a/mysql-test/suite/innodb/t/innodb-virtual-columns-debug.test b/mysql-test/suite/innodb/t/innodb-virtual-columns-debug.test index d4947ca4d8b..ed160b977a4 100644 --- a/mysql-test/suite/innodb/t/innodb-virtual-columns-debug.test +++ b/mysql-test/suite/innodb/t/innodb-virtual-columns-debug.test @@ -25,3 +25,28 @@ SET debug_sync= "now SIGNAL another_open"; --connection default SET debug_sync= "RESET"; DROP TABLE t1; + +--echo # +--echo # MDEV-26057 Assertion `!vcol->v_indexes.empty() in trx_undo_log_v_idx +--echo # +CREATE TABLE t1(f1 INT NOT NULL, f2 INT NOT NULL, + f3 INT AS (f2) VIRTUAL) ENGINE=InnoDB; +INSERT INTO t1(f1, f2) VALUES(1, 1); + +SET DEBUG_SYNC="alter_rollback_complete SIGNAL dml_start WAIT_FOR default_resume"; +send set STATEMENT DEBUG_DBUG="d,innodb_OOM_inplace_alter" FOR alter table t1 add index(f3), algorithm=inplace; + +connect(con1,localhost,root,,,); +SET DEBUG_SYNC="now WAIT_FOR dml_start"; +INSERT INTO t1(f1, f2) VALUES(2, 2); +SET DEBUG_SYNC="now SIGNAL default_resume"; + +connection default; +--error ER_OUT_OF_RESOURCES +--reap +connection default; +disconnect con1; +SET DEBUG_SYNC="RESET"; +SELECT * FROM t1; +CHECK TABLE t1; +DROP TABLE t1; diff --git a/storage/innobase/handler/handler0alter.cc b/storage/innobase/handler/handler0alter.cc index dfd7b261eea..b3d94d55956 100644 --- a/storage/innobase/handler/handler0alter.cc +++ b/storage/innobase/handler/handler0alter.cc @@ -9108,7 +9108,6 @@ argument passed. @param[in] table table @param[in] col_no column number @param[in] is_v if this is a virtual column -@param[in] only_committed whether to consider only committed indexes @retval true column exists @retval false column does not exist, true if column is system column or it is in the index. */ @@ -9117,8 +9116,7 @@ bool check_col_exists_in_indexes( const dict_table_t* table, ulint col_no, - bool is_v, - bool only_committed = false) + bool is_v) { /* This function does not check system columns */ if (!is_v && dict_table_get_nth_col(table, col_no)->mtype == DATA_SYS) { @@ -9129,9 +9127,7 @@ check_col_exists_in_indexes( index; index = dict_table_get_next_index(index)) { - if (only_committed - ? !index->is_committed() - : index->to_be_dropped) { + if (index->to_be_dropped) { continue; } @@ -9342,6 +9338,7 @@ free_and_exit: ctx->trx->free(); ctx->trx= nullptr; + DEBUG_SYNC_C("alter_rollback_complete"); dict_sys.lock(SRW_LOCK_CALL); if (ctx->add_vcol) @@ -9369,22 +9366,6 @@ free_and_exit: DBUG_ASSERT(prebuilt->table->indexes.start->online_status == ONLINE_INDEX_COMPLETE); - /* Reset dict_col_t::ord_part for unindexed columns */ - for (ulint i= 0; i < dict_table_get_n_cols(prebuilt->table); i++) - { - dict_col_t &col= prebuilt->table->cols[i]; - if (col.ord_part && !check_col_exists_in_indexes(prebuilt->table, i, false, - true)) - col.ord_part= 0; - } - - for (ulint i = 0; i < dict_table_get_n_v_cols(prebuilt->table); i++) - { - dict_col_t &col = prebuilt->table->v_cols[i].m_col; - if (col.ord_part && !check_col_exists_in_indexes(prebuilt->table, i, true, - true)) - col.ord_part= 0; - } dict_sys.unlock(); trx_commit_for_mysql(prebuilt->trx); prebuilt->trx_id = 0; diff --git a/storage/innobase/row/row0merge.cc b/storage/innobase/row/row0merge.cc index c60032a8d5e..e2bec8e5d36 100644 --- a/storage/innobase/row/row0merge.cc +++ b/storage/innobase/row/row0merge.cc @@ -4014,6 +4014,43 @@ static void row_merge_drop_fulltext_indexes(trx_t *trx, dict_table_t *table) DICT_TF2_FLAG_UNSET(table, DICT_TF2_FTS); } +/** @param table table +@param col a stored column of the table +@return whether the column is a field of any index of the table */ +static bool row_merge_col_is_indexed(const dict_table_t &table, + const dict_col_t &col) +{ + for (const dict_index_t *index= dict_table_get_first_index(&table); index; + index= dict_table_get_next_index(index)) + for (unsigned f= 0; f < index->n_uniq; f++) + if (dict_index_get_nth_col(index, f) == &col) + return true; + return false; +} + +/** Reset dict_col_t::ord_part for the columns that are no +longer a field of any index that remains in the data dictionary cache. +@param table table whose columns are to be adjusted */ +static void row_merge_reset_ord_part(dict_table_t *table) +{ + ut_ad(dict_sys.locked()); + + for (unsigned c= 0; c < table->n_v_def; c++) + { + dict_v_col_t &v= table->v_cols[c]; + if (v.m_col.ord_part && v.v_indexes.empty()) + v.m_col.ord_part= 0; + } + + for (unsigned c= 0; c < table->n_cols; c++) + { + dict_col_t &col= table->cols[c]; + if (col.ord_part && col.mtype != DATA_SYS && + !row_merge_col_is_indexed(*table, col)) + col.ord_part= 0; + } +} + /** Drop indexes that were created before an error occurred. The data dictionary must have been locked exclusively by the caller, because the transaction will not be committed. @@ -4188,6 +4225,14 @@ row_merge_drop_indexes( } row_merge_drop_fulltext_indexes(trx, table); + /* The aborted indexes are gone from the cache. + Adjust dict_col_t::ord_part while still holding the + same dict_sys.latch, so that DML will never observe a + column with ord_part set and an empty dict_v_col_t::v_indexes. + In the lazy drop above, the indexes remain in the cache, + and nothing must be reset; */ + ut_ad(locked || table->get_ref_count() == 1); + row_merge_reset_ord_part(table); table->drop_aborted = FALSE; ut_d(dict_table_check_for_dup_indexes(table, CHECK_ALL_COMPLETE)); } diff --git a/storage/innobase/row/row0quiesce.cc b/storage/innobase/row/row0quiesce.cc index 36d7e5a9c45..68b5f9e8e24 100644 --- a/storage/innobase/row/row0quiesce.cc +++ b/storage/innobase/row/row0quiesce.cc @@ -210,6 +210,54 @@ row_quiesce_write_indexes( return(err); } +/** Compute dict_col_t::ord_part and dict_col_t::max_prefix +of a column from the committed indexes that are present in +the data dictionary cache, in the same way as +dict_index_add_to_cache() would compute them when the table +definition is being loaded from SYS_INDEXES. + +The cached values can be stale. A rolled back ADD INDEX +can leave dict_col_t::ord_part set until the aborted index +is removed from the dictionary cache by a subsequent DDL +operation, and dict_col_t::max_prefix is never reset when an +index is being dropped. +@param table table +@param col a column of the table +@param max_prefix computed dict_col_t::max_prefix +@return computed dict_col_t::ord_part */ +static bool row_quiesce_col_ord_part(const dict_table_t &table, + const dict_col_t &col, + ulint &max_prefix) +{ + bool ord_part= false; + max_prefix= 0; + + for (const dict_index_t* index= UT_LIST_GET_FIRST(table.indexes); + index; index= UT_LIST_GET_NEXT(indexes, index)) + { + if (!index->is_committed()) + continue; + + for (unsigned i= 0; i < index->n_uniq; i++) + { + const dict_field_t& field= index->fields[i]; + if (field.col != &col) + continue; + + if (!field.prefix_len) + { + max_prefix= 0; + return true; + } + + if (field.prefix_len > max_prefix) + max_prefix= field.prefix_len; + ord_part= true; + } + } + return ord_part; +} + /*********************************************************************//** Write the meta data (table columns) config file. Serialise the contents of dict_col_t structure, along with the column name. All fields are serialized @@ -251,10 +299,15 @@ row_quiesce_write_table( mach_write_to_4(ptr, col->ind); ptr += sizeof(ib_uint32_t); - mach_write_to_4(ptr, col->ord_part); + /* Derive the values from the indexes rather than using + the cached dict_col_t fields, which can be stale. */ + ulint max_prefix; + + mach_write_to_4(ptr, row_quiesce_col_ord_part(*table, *col, + max_prefix)); ptr += sizeof(ib_uint32_t); - mach_write_to_4(ptr, col->max_prefix); + mach_write_to_4(ptr, max_prefix); DBUG_EXECUTE_IF("ib_export_io_write_failure_2", close(fileno(file));); From 4da5458ff7b1227f31a9aa24091ed5a5371d8b05 Mon Sep 17 00:00:00 2001 From: Luke Lu Date: Tue, 4 Aug 2026 17:08:45 +0000 Subject: [PATCH 25/71] MDEV-40385 use-of-uninitialized-value in Binary_string::c_ptr() SELECT KDF('','',1000,256) triggered an MSAN use-of-uninitialized-value report in Binary_string::c_ptr() (sql/sql_string.h) reached from Item_func_kdf::val_str(). The optional 4th argument (kdf_name) was evaluated into a result buffer and then read as a C string with c_ptr(). When that argument is an integer literal such as 256, Item_int::val_str() writes the digits "256" into the buffer without appending a trailing NUL, and String::alloc() intentionally skips reallocation, so the buffer stays non-"alloced" and unterminated. c_ptr() then reads Ptr[str_length] to test for an existing terminator, reading an uninitialized byte. The buffer is a stack-resident ValueBuffer from Protocol::send_result_set_row, so the byte is validly addressable but never initialized; only MSAN re-poisons the stack scope, which is why the report is MSAN-only and Valgrind does not flag it. Use c_ptr_safe() instead of c_ptr() when reading the kdf_name argument. c_ptr_safe() writes the NUL terminator after a capacity check without first reading Ptr[str_length], while c_ptr() reads that byte to detect an existing terminator. Behaviour is unchanged: a non-matching kdf_name still yields ER_STD_INVALID_ARGUMENT and NULL, and valid names (pbkdf2_hmac, hkdf) still work. The regression cases added to main.func_kdf reproduce the report only under an MSAN-instrumented build; on a normal build they pass on both the unfixed and fixed server because the stray read is harmless without a sanitizer. All new code of the whole pull request, including one or several files that are either new files or modified ones, are contributed under the BSD-new license. I am contributing on behalf of my employer Amazon Web Services, Inc. --- mysql-test/main/func_kdf.result | 22 ++++++++++++++++++++++ mysql-test/main/func_kdf.test | 14 ++++++++++++++ sql/item_strfunc.cc | 4 ++-- 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/mysql-test/main/func_kdf.result b/mysql-test/main/func_kdf.result index fda8c0581d2..e334125c6d5 100644 --- a/mysql-test/main/func_kdf.result +++ b/mysql-test/main/func_kdf.result @@ -161,3 +161,25 @@ ERROR 42000: Incorrect parameter count in the call to native function 'kdf' # # End of 11.3 tests # +# +# MDEV-40385 use-of-uninitialized-value in Binary_string::c_ptr() +# +SELECT KDF('','',1000,256); +KDF('','',1000,256) +NULL +Warnings: +Warning 3047 Invalid argument error: '256' in function kdf. +SELECT KDF('secret','salt',1000,999); +KDF('secret','salt',1000,999) +NULL +Warnings: +Warning 3047 Invalid argument error: '999' in function kdf. +SELECT KDF('secret','salt',1000,'pbkdf2_hmac') IS NOT NULL; +KDF('secret','salt',1000,'pbkdf2_hmac') IS NOT NULL +1 +SELECT KDF('secret','salt') IS NOT NULL; +KDF('secret','salt') IS NOT NULL +1 +# +# End of 11.4 tests +# diff --git a/mysql-test/main/func_kdf.test b/mysql-test/main/func_kdf.test index 01f53da3c7e..279323ede59 100644 --- a/mysql-test/main/func_kdf.test +++ b/mysql-test/main/func_kdf.test @@ -62,3 +62,17 @@ select kdf(); --echo # End of 11.3 tests --echo # +--echo # +--echo # MDEV-40385 use-of-uninitialized-value in Binary_string::c_ptr() +--echo # +# MSAN only: this test verifies the fix behaviorally (warning + NULL); the +# uninitialized read itself is only observable under an MSAN build. +SELECT KDF('','',1000,256); +SELECT KDF('secret','salt',1000,999); +SELECT KDF('secret','salt',1000,'pbkdf2_hmac') IS NOT NULL; +SELECT KDF('secret','salt') IS NOT NULL; + +--echo # +--echo # End of 11.4 tests +--echo # + diff --git a/sql/item_strfunc.cc b/sql/item_strfunc.cc index e1f49b34461..34e479889db 100644 --- a/sql/item_strfunc.cc +++ b/sql/item_strfunc.cc @@ -521,9 +521,9 @@ String *Item_func_kdf::val_str(String *buf) { if (String *s= args[3]->val_str(buf)) { - if (strcasecmp(s->c_ptr(), "hkdf") == 0) + if (strcasecmp(s->c_ptr_safe(), "hkdf") == 0) use_hkdf= true; - else if (strcasecmp(s->c_ptr(), "pbkdf2_hmac") != 0) + else if (strcasecmp(s->c_ptr_safe(), "pbkdf2_hmac") != 0) { invalid_argument_error(func_name(), ErrConvStringQ(s).ptr()); goto ret_null; From 03acdd67d599ab7136ef18c36f3479a306870290 Mon Sep 17 00:00:00 2001 From: Luke Lu Date: Fri, 24 Jul 2026 21:48:16 +0000 Subject: [PATCH 26/71] MDEV-35732 Failed ALTER TABLE leaves cached table metadata corrupted A failed ALTER TABLE ... RENAME INDEX ... ALGORITHM=INSTANT changed the outcome of a subsequent, unrelated ALTER TABLE ... ADD FOREIGN KEY. A failed statement must have no side effects, but here the second statement wrongly succeeded where on a fresh table it correctly fails with ER_DUP_KEYNAME. Root cause: while rebuilding the key list, mysql_prepare_alter_table() handled a RENAME INDEX request by clearing HA_GENERATED_KEY in place on key_info->flags. key_info points into the (possibly cached) TABLE object that is reused across statements. The ALGORITHM=INSTANT incompatibility is only detected later, after mysql_prepare_alter_table() has returned, so the statement fails with ER_ALTER_OPERATION_NOT_SUPPORTED with the cleared flag never restored. The cached generated FK-support index (fk1) was thus left permanently marked as user-defined. That corrupted flag flips the de-duplication tie-break in the next ALTER: adding FOREIGN KEY ind1 (b) creates a generated support index on column b that prefix-matches fk1(b). Normally fk1 (generated) is dropped and the new ind1 survives, colliding by name with the existing user index ind1(a) and raising ER_DUP_KEYNAME. With fk1 no longer marked generated, the new ind1 is dropped instead, so no name collision is reached and the ADD FOREIGN KEY silently succeeds. Fix: do not mutate the cached key_info->flags. Track the "renamed => no longer generated" decision in a per-key local variable (generated_key), initialised from the flag, set to false on rename, and passed to the Key constructor. This preserves the in-statement behaviour while leaving the cached TABLE metadata untouched, so a failed ALTER has no lingering effect. All new code of the whole pull request, including one or several files that are either new files or modified ones, are contributed under the BSD-new license. I am contributing on behalf of my employer Amazon Web Services, Inc. --- mysql-test/main/alter_table.result | 23 +++++++++++++++++++++++ mysql-test/main/alter_table.test | 30 ++++++++++++++++++++++++++++++ sql/sql_table.cc | 5 +++-- 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/mysql-test/main/alter_table.result b/mysql-test/main/alter_table.result index a14edf6cfe0..752cb9d841e 100644 --- a/mysql-test/main/alter_table.result +++ b/mysql-test/main/alter_table.result @@ -3225,5 +3225,28 @@ Tp4 CREATE TABLE `Tp4`-ok ( drop table Another.Tp4; drop database Another; # +# MDEV-35732: Failed ALTER TABLE causes inconsistency, changes behavior of the next statement +# +# It should be impossible for a failed rename index to affect +# a subsequent duplicate name check for a foreign key. +# +create table t1 (f1 int, f2 int, key(f1), key(f2)) engine=innodb; +create table t2 (pk int primary key, a int, b int, key ind1(a), +foreign key fk1 (b) references t1 (f1)) engine=innodb; +alter table t2 rename index fk1 to fk, algorithm=instant, order by a; +ERROR 0A000: ALGORITHM=INSTANT is not supported for this operation. Try ALGORITHM=COPY +alter table t2 add foreign key ind1 (b) references t1 (f2); +ERROR 42000: Duplicate key name 'ind1' +drop table t2, t1; +# +# It should be impossible to add a foreign key with a duplicate name. +# +create table t1 (f1 int, f2 int, key(f1), key(f2)) engine=innodb; +create table t2 (pk int primary key, a int, b int, key ind1(a), +foreign key fk1 (b) references t1 (f1)) engine=innodb; +alter table t2 add foreign key ind1 (b) references t1 (f2); +ERROR 42000: Duplicate key name 'ind1' +drop table t2, t1; +# # End of 11.4 tests # diff --git a/mysql-test/main/alter_table.test b/mysql-test/main/alter_table.test index c2f0494ae5e..67f28c90b6c 100644 --- a/mysql-test/main/alter_table.test +++ b/mysql-test/main/alter_table.test @@ -2520,6 +2520,36 @@ show create table Another.Tp4; drop table Another.Tp4; drop database Another; +--echo # +--echo # MDEV-35732: Failed ALTER TABLE causes inconsistency, changes behavior of the next statement +--echo # +--echo # It should be impossible for a failed rename index to affect +--echo # a subsequent duplicate name check for a foreign key. +--echo # +create table t1 (f1 int, f2 int, key(f1), key(f2)) engine=innodb; +create table t2 (pk int primary key, a int, b int, key ind1(a), + foreign key fk1 (b) references t1 (f1)) engine=innodb; + +--error ER_ALTER_OPERATION_NOT_SUPPORTED +alter table t2 rename index fk1 to fk, algorithm=instant, order by a; + +--error ER_DUP_KEYNAME +alter table t2 add foreign key ind1 (b) references t1 (f2); + +drop table t2, t1; + +--echo # +--echo # It should be impossible to add a foreign key with a duplicate name. +--echo # +create table t1 (f1 int, f2 int, key(f1), key(f2)) engine=innodb; +create table t2 (pk int primary key, a int, b int, key ind1(a), + foreign key fk1 (b) references t1 (f1)) engine=innodb; + +--error ER_DUP_KEYNAME +alter table t2 add foreign key ind1 (b) references t1 (f2); + +drop table t2, t1; + --echo # --echo # End of 11.4 tests --echo # diff --git a/sql/sql_table.cc b/sql/sql_table.cc index f055a9a9c76..8676373bc02 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -9024,6 +9024,7 @@ mysql_prepare_alter_table(THD *thd, TABLE *table, bool long_hash_key= false; if (key_info->flags & HA_INVISIBLE_KEY) continue; + bool generated_key= key_info->flags & HA_GENERATED_KEY; const char *key_name= key_info->name.str; const bool primary_key= table->s->primary_key == i; const bool explicit_pk= primary_key && @@ -9112,7 +9113,7 @@ mysql_prepare_alter_table(THD *thd, TABLE *table, dropped by mysql_prepare_create_table() and this will confuse code in fill_alter_inplace_info(). */ - key_info->flags&= ~HA_GENERATED_KEY; + generated_key= false; break; } } @@ -9312,7 +9313,7 @@ mysql_prepare_alter_table(THD *thd, TABLE *table, tmp_name.length= strlen(key_name); /* We dont need LONG_UNIQUE_HASH_FIELD flag because it will be autogenerated */ key= new (thd->mem_root) Key(key_type, &tmp_name, &key_create_info, - key_info->flags & HA_GENERATED_KEY, + generated_key, &key_parts, key_info->option_list, DDL_options()); key->without_overlaps= key_info->without_overlaps; key->period= table->s->period.name; From 5fde7c6c7b108be92489f4a4d5888c3e024f10bf Mon Sep 17 00:00:00 2001 From: drrtuy Date: Sat, 12 Sep 2026 13:44:58 +0100 Subject: [PATCH 27/71] MDEV-41001: cpack now skips data path that brings symbolic links to fix Windows build. --- cmake/make_dist.cmake.in | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cmake/make_dist.cmake.in b/cmake/make_dist.cmake.in index d5380b2034e..e1e927ffbbc 100644 --- a/cmake/make_dist.cmake.in +++ b/cmake/make_dist.cmake.in @@ -107,6 +107,13 @@ IF(NOT GIT_EXECUTABLE) ) ENDIF() +# DuckDB uses these symbolic links only to test globbing behavior. They cause +# source package extraction failures with tar on Windows. +FILE(REMOVE + "${PACKAGE_DIR}/storage/duckdb/third_parties/duckdb/data/csv/glob/crawl/.symbolic_link/link_to_file" + "${PACKAGE_DIR}/storage/duckdb/third_parties/duckdb/data/csv/glob/crawl/.symbolic_link/mydir/link_to_upper_dir" +) + # Copy bison output CONFIGURE_FILE(${CMAKE_BINARY_DIR}/sql/yy_mariadb.hh ${PACKAGE_DIR}/sql/yy_mariadb.hh COPYONLY) From 07b8693fee4345bcb2affe29067f473193bfb7c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Wed, 16 Sep 2026 14:44:00 +0300 Subject: [PATCH 28/71] MDEV-15736 fixup: clang -Wunused-but-set-global --- client/mysqladmin.cc | 3 --- 1 file changed, 3 deletions(-) diff --git a/client/mysqladmin.cc b/client/mysqladmin.cc index 7527b61f71f..09cc933de68 100644 --- a/client/mysqladmin.cc +++ b/client/mysqladmin.cc @@ -50,8 +50,6 @@ static bool sql_log_bin_off= false; static uint opt_protocol=0; static myf error_flags; /* flags to pass to my_printf_error, like ME_BELL */ -static my_bool ex_status_printed = 0; /* First output is not relative. */ - #include static void print_version(void); @@ -924,7 +922,6 @@ static int execute_commands(MYSQL *mysql,int argc, char **argv) (*func)(res, row, rownr++); print_top(res); - ex_status_printed = 1; /* From now on the output will be relative */ mysql_free_result(res); break; } From d10e5d726799b1cd57cc866f40aad68c720803da Mon Sep 17 00:00:00 2001 From: Alessandro Vetere Date: Fri, 18 Sep 2026 11:50:08 +0200 Subject: [PATCH 29/71] MDEV-41201 LRUItr::start(): fix inverted reset condition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The scan hand pointer m_hp must stay in place while it is still inside the old (cold) sublist of the LRU list, and rewind to the tail only once it has advanced past the old/young boundary into the young (hot) sublist. The condition was inverted, so start() rewound the pointer to the tail on every call instead of only at that boundary, defeating the intended scan-resumption optimization and biasing scans toward the tail of the old sublist. Port of percona/percona-server@dc344fba7e3d272ac18e8d6589b37c678f1e1ad5 by Paweł Olchawa (PS-11446). --- storage/innobase/include/buf0buf.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/storage/innobase/include/buf0buf.h b/storage/innobase/include/buf0buf.h index 7f6cf696b25..0cb00ff739b 100644 --- a/storage/innobase/include/buf0buf.h +++ b/storage/innobase/include/buf0buf.h @@ -1983,7 +1983,7 @@ inline buf_page_t *LRUItr::start() noexcept { mysql_mutex_assert_owner(m_mutex); - if (!m_hp || m_hp->old) + if (!m_hp || !m_hp->old) m_hp= UT_LIST_GET_LAST(buf_pool.LRU); return m_hp; From 51fc961a3f177b969269bc49cf08be635a794e9c Mon Sep 17 00:00:00 2001 From: bsrikanth-mariadb Date: Tue, 17 Jun 2025 14:28:07 -0400 Subject: [PATCH 30/71] MDEV-36096: Assertion failure in recompute_join_cost_with_limit An assert was present in method recompute_join_cost_with_limit() to make sure the recomputed cost is always >= 0. Although, the assert was correct, it was failing in CLang compiled versions due to floating point comparison, when we set sql_select_limit=1, and optimizer_join_limit_pref_ratio=1; In GCC compiled versions, the partial_join_cost was computed to +0.0. However, in CLang version, the cost turned out to be -0.0. Changed the assert such that partial_join_cost, would now be checked for a value >= -DBL_EPSILON. Following it, we set the partial_join_cost to 0, if it has a negative value. --- mysql-test/main/optimizer_crash.result | 24 ++++++++++++++++++++++ mysql-test/main/optimizer_crash.test | 28 ++++++++++++++++++++++++++ sql/sql_select.cc | 9 ++++++++- 3 files changed, 60 insertions(+), 1 deletion(-) diff --git a/mysql-test/main/optimizer_crash.result b/mysql-test/main/optimizer_crash.result index 8edac2eb30b..860da2a7f4a 100644 --- a/mysql-test/main/optimizer_crash.result +++ b/mysql-test/main/optimizer_crash.result @@ -71,3 +71,27 @@ DROP TABLE t1, t2; # # End of 11.0 tests # +# +# MDEV-36096: Assertion partial_join_cost >= 0.0 failed when +# sql_select_limit=1, and +# optimizer_join_limit_pref_ratio=1 +# +CREATE TABLE t (c INT NOT NULL UNIQUE) ENGINE=InnoDB; +INSERT INTO t VALUES (0),(1),(2),(3),(4),(5); +SET @save_sql_select_limit= @@sql_select_limit; +SET sql_select_limit=1,optimizer_join_limit_pref_ratio=1; +EXPLAIN +SELECT * FROM t ORDER BY c; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t index NULL c 4 NULL 1 +SET sql_select_limit= @save_sql_select_limit; +# similar issue with LIMIT 1 clause query +EXPLAIN +SELECT * FROM t ORDER BY c LIMIT 1; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t index NULL c 4 NULL 1 +SET optimizer_join_limit_pref_ratio= default; +DROP table t; +# +# End of 11.4 tests +# diff --git a/mysql-test/main/optimizer_crash.test b/mysql-test/main/optimizer_crash.test index bfcc4835f09..1d90f3bb053 100644 --- a/mysql-test/main/optimizer_crash.test +++ b/mysql-test/main/optimizer_crash.test @@ -80,3 +80,31 @@ DROP TABLE t1, t2; --echo # --echo # End of 11.0 tests --echo # + +--echo # +--echo # MDEV-36096: Assertion partial_join_cost >= 0.0 failed when +--echo # sql_select_limit=1, and +--echo # optimizer_join_limit_pref_ratio=1 +--echo # + +CREATE TABLE t (c INT NOT NULL UNIQUE) ENGINE=InnoDB; +INSERT INTO t VALUES (0),(1),(2),(3),(4),(5); + +SET @save_sql_select_limit= @@sql_select_limit; + +SET sql_select_limit=1,optimizer_join_limit_pref_ratio=1; +EXPLAIN +SELECT * FROM t ORDER BY c; + +SET sql_select_limit= @save_sql_select_limit; + +--echo # similar issue with LIMIT 1 clause query +EXPLAIN +SELECT * FROM t ORDER BY c LIMIT 1; + +SET optimizer_join_limit_pref_ratio= default; +DROP table t; + +--echo # +--echo # End of 11.4 tests +--echo # diff --git a/sql/sql_select.cc b/sql/sql_select.cc index eb7ba99eed0..e6ba61fbc3e 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -12002,7 +12002,14 @@ double recompute_join_cost_with_limit(const JOIN *join, bool skip_sorting, In 11.0+, that time is already included in pos->read_time) */ partial_join_cost -= pos->read_time*fraction; - DBUG_ASSERT(partial_join_cost >= 0.0); + /* + Handle possible rounding errors when + partial_join_cost==pos->read_time*fraction: + Catch larger negatives, proceed with non-negative cost. + */ + DBUG_ASSERT(partial_join_cost >= -DBL_EPSILON); + if (partial_join_cost < 0.0) + partial_join_cost= 0.0; /* Add the cost of the new access method we've got: */ partial_join_cost= COST_ADD(partial_join_cost, *first_table_cost); From 6c951fea5f62e1eb0ed7483ad8aa2cdcbfff5dd1 Mon Sep 17 00:00:00 2001 From: Oleksandr Byelkin Date: Fri, 18 Sep 2026 10:54:22 +0200 Subject: [PATCH 31/71] MDEV-39993 Use CREATE OR REPLACE for sys schema routines mariadb-upgrade silently dropped EXECUTE grants on sys schema stored functions and procedures. The sys schema install scripts reinstalled every routine with DROP FUNCTION/PROCEDURE IF EXISTS followed by CREATE. DROP cascades to delete the routine's rows in mysql.procs_priv, so any EXECUTE grant a DBA had issued on e.g. sys.table_exists or sys.quote_identifier was lost every time mariadb-upgrade reinstalled the sys schema, even though the routine itself came back unchanged. Fix: replace DROP ... IF EXISTS + CREATE with CREATE OR REPLACE in all 53 sys_schema function/procedure files and in the two templates (templates/function.sql, templates/procedure.sql) so future routines follow the same pattern. CREATE OR REPLACE PROCEDURE/FUNCTION goes through sp_drop_routine_internal(), which only deletes the mysql.proc row and never reaches sp_revoke_privileges() (that is only called from the explicit DROP PROCEDURE/FUNCTION statement), so mysql.procs_priv is left untouched and existing grants survive. This mirrors the pattern already used by sys schema views (CREATE OR REPLACE ... VIEW, since MDEV-9077), which never had this problem. Four of the converted files (functions/format_path.sql, functions/ps_is_account_enabled_57.sql, procedures/ps_setup_reset_to_default.sql, procedures/ps_trace_thread_57.sql) are not referenced by scripts/sys_schema/CMakeLists.txt; they were converted anyway for consistency and have no behavioural effect. As a side effect, a pre-existing UDF whose name collides with a sys routine name is no longer destroyed before the reinstall fails: DROP FUNCTION IF EXISTS resolved the UDF namespace first, silently dropping the UDF and then failing on ER_SP_ALREADY_EXISTS anyway; CREATE OR REPLACE fails immediately on ER_UDF_EXISTS with the UDF intact. scripts/maria_add_gis_sp.sql.in and the sys_config triggers were deliberately left untouched: the GIS procedures are only re-installed at bootstrap time (mariadb-upgrade instead patches their definer in place via UPDATE), and triggers carry no procs_priv rows, so neither is on the code path this bug is about. Added mysql-test/main/mysql_upgrade_sys_routine_grants.test, which grants EXECUTE on sys.table_exists and sys.quote_identifier, overwrites both routine bodies with a marker to prove the upgrade actually reinstalls them (rather than the sys schema install being skipped), runs mariadb-upgrade, and checks both that the grants survived and that the real routine bodies came back. Co-Authored-By: Claude Sonnet 5 --- .../mysql_upgrade_sys_routine_grants.result | 207 ++++++++++++++++++ .../mysql_upgrade_sys_routine_grants.test | 61 ++++++ .../extract_schema_from_file_name.sql | 4 +- .../extract_table_from_file_name.sql | 4 +- scripts/sys_schema/functions/format_bytes.sql | 4 +- scripts/sys_schema/functions/format_path.sql | 4 +- .../sys_schema/functions/format_path_57.sql | 4 +- .../sys_schema/functions/format_statement.sql | 4 +- scripts/sys_schema/functions/format_time.sql | 4 +- scripts/sys_schema/functions/list_add.sql | 4 +- scripts/sys_schema/functions/list_drop.sql | 4 +- .../functions/ps_is_account_enabled.sql | 4 +- .../functions/ps_is_account_enabled_57.sql | 4 +- .../functions/ps_is_consumer_enabled.sql | 4 +- .../ps_is_instrument_default_enabled.sql | 4 +- .../ps_is_instrument_default_timed.sql | 4 +- .../functions/ps_is_thread_instrumented.sql | 4 +- .../functions/ps_thread_account.sql | 4 +- scripts/sys_schema/functions/ps_thread_id.sql | 4 +- .../sys_schema/functions/ps_thread_stack.sql | 4 +- .../functions/ps_thread_trx_info.sql | 4 +- .../sys_schema/functions/quote_identifier.sql | 4 +- .../sys_schema/functions/sys_get_config.sql | 4 +- .../sys_schema/functions/version_major.sql | 4 +- .../sys_schema/functions/version_minor.sql | 4 +- .../sys_schema/functions/version_patch.sql | 4 +- scripts/sys_schema/generate_sql_file.sh | 8 +- .../procedures/create_synonym_db.sql | 4 +- scripts/sys_schema/procedures/diagnostics.sql | 4 +- .../procedures/execute_prepared_stmt.sql | 4 +- .../procedures/optimizer_switch.sql | 9 +- .../ps_setup_disable_background_threads.sql | 4 +- .../procedures/ps_setup_disable_consumer.sql | 4 +- .../ps_setup_disable_instrument.sql | 4 +- .../procedures/ps_setup_disable_thread.sql | 4 +- .../ps_setup_enable_background_threads.sql | 4 +- .../procedures/ps_setup_enable_consumer.sql | 4 +- .../procedures/ps_setup_enable_instrument.sql | 4 +- .../procedures/ps_setup_enable_thread.sql | 4 +- .../procedures/ps_setup_reload_saved.sql | 4 +- .../procedures/ps_setup_reset_to_default.sql | 4 +- .../ps_setup_reset_to_default_57.sql | 4 +- .../sys_schema/procedures/ps_setup_save.sql | 4 +- .../procedures/ps_setup_show_disabled.sql | 4 +- .../ps_setup_show_disabled_consumers.sql | 4 +- .../ps_setup_show_disabled_instruments.sql | 4 +- .../procedures/ps_setup_show_enabled.sql | 4 +- .../ps_setup_show_enabled_consumers.sql | 4 +- .../ps_setup_show_enabled_instruments.sql | 4 +- .../ps_statement_avg_latency_histogram.sql | 4 +- .../procedures/ps_trace_statement_digest.sql | 4 +- .../sys_schema/procedures/ps_trace_thread.sql | 4 +- .../procedures/ps_trace_thread_57.sql | 4 +- .../procedures/ps_truncate_all_tables.sql | 4 +- .../statement_performance_analyzer.sql | 4 +- .../sys_schema/procedures/table_exists.sql | 4 +- scripts/sys_schema/templates/function.sql | 4 +- scripts/sys_schema/templates/procedure.sql | 4 +- 58 files changed, 330 insertions(+), 171 deletions(-) create mode 100644 mysql-test/main/mysql_upgrade_sys_routine_grants.result create mode 100644 mysql-test/main/mysql_upgrade_sys_routine_grants.test diff --git a/mysql-test/main/mysql_upgrade_sys_routine_grants.result b/mysql-test/main/mysql_upgrade_sys_routine_grants.result new file mode 100644 index 00000000000..9351c156d6f --- /dev/null +++ b/mysql-test/main/mysql_upgrade_sys_routine_grants.result @@ -0,0 +1,207 @@ +# +# MDEV-39993 mariadb-upgrade Drops Existing EXECUTE Grants on sys +# Stored Routines During Upgrade +# +CREATE USER mdev39993_user@localhost; +GRANT EXECUTE ON PROCEDURE sys.table_exists TO mdev39993_user@localhost; +GRANT EXECUTE ON FUNCTION sys.quote_identifier TO mdev39993_user@localhost; +SHOW GRANTS FOR mdev39993_user@localhost; +Grants for mdev39993_user@localhost +GRANT USAGE ON *.* TO `mdev39993_user`@`localhost` +GRANT EXECUTE ON PROCEDURE `sys`.`table_exists` TO `mdev39993_user`@`localhost` +GRANT EXECUTE ON FUNCTION `sys`.`quote_identifier` TO `mdev39993_user`@`localhost` +# Overwrite the routine bodies with a marker so that a passing test +# below actually proves the routines were reinstalled by the +# upgrade, rather than merely left untouched (i.e. sys schema +# install skipped). +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' FUNCTION sys.quote_identifier(in_identifier TEXT) +RETURNS TEXT +SQL SECURITY INVOKER +DETERMINISTIC +BEGIN +RETURN 'stale_body_marker'; +END| +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' PROCEDURE sys.table_exists ( +IN in_db VARCHAR(64), IN in_table VARCHAR(64), +OUT out_exists ENUM('', 'BASE TABLE', 'VIEW', 'TEMPORARY', 'SEQUENCE', 'SYSTEM VIEW', 'TEMPORARY SEQUENCE') +) +SQL SECURITY INVOKER +BEGIN +SET out_exists = ''; +END| +SELECT sys.quote_identifier('abc') AS qi; +qi +stale_body_marker +CALL sys.table_exists('mysql', 'user', @exists); +SELECT @exists; +@exists + +Phase 1/8: Checking and upgrading mysql database +Processing databases +mysql +mysql.column_stats OK +mysql.columns_priv OK +mysql.db OK +mysql.event OK +mysql.func OK +mysql.global_priv OK +mysql.gtid_slave_pos OK +mysql.help_category OK +mysql.help_keyword OK +mysql.help_relation OK +mysql.help_topic OK +mysql.index_stats OK +mysql.innodb_index_stats OK +mysql.innodb_table_stats OK +mysql.plugin OK +mysql.proc OK +mysql.procs_priv OK +mysql.proxies_priv OK +mysql.roles_mapping OK +mysql.servers OK +mysql.table_stats OK +mysql.tables_priv OK +mysql.time_zone OK +mysql.time_zone_leap_second OK +mysql.time_zone_name OK +mysql.time_zone_transition OK +mysql.time_zone_transition_type OK +mysql.transaction_registry OK +Phase 2/8: Installing used storage engines... Skipped +Phase 3/8: Running 'mysql_fix_privilege_tables' +Phase 4/8: Fixing views +mysql.user OK +sys.host_summary OK +sys.host_summary_by_file_io OK +sys.host_summary_by_file_io_type OK +sys.host_summary_by_stages OK +sys.host_summary_by_statement_latency OK +sys.host_summary_by_statement_type OK +sys.innodb_buffer_stats_by_schema OK +sys.innodb_buffer_stats_by_table OK +sys.innodb_lock_waits OK +sys.io_by_thread_by_latency OK +sys.io_global_by_file_by_bytes OK +sys.io_global_by_file_by_latency OK +sys.io_global_by_wait_by_bytes OK +sys.io_global_by_wait_by_latency OK +sys.latest_file_io OK +sys.memory_by_host_by_current_bytes OK +sys.memory_by_thread_by_current_bytes OK +sys.memory_by_user_by_current_bytes OK +sys.memory_global_by_current_bytes OK +sys.memory_global_total OK +sys.metrics OK +sys.privileges_by_table_by_level OK +sys.processlist OK +sys.ps_check_lost_instrumentation OK +sys.schema_auto_increment_columns OK +sys.schema_index_statistics OK +sys.schema_object_overview OK +sys.schema_redundant_indexes OK +sys.schema_table_lock_waits OK +sys.schema_table_statistics OK +sys.schema_table_statistics_with_buffer OK +sys.schema_tables_with_full_table_scans OK +sys.schema_unused_indexes OK +sys.session OK +sys.session_ssl_status OK +sys.statement_analysis OK +sys.statements_with_errors_or_warnings OK +sys.statements_with_full_table_scans OK +sys.statements_with_runtimes_in_95th_percentile OK +sys.statements_with_sorting OK +sys.statements_with_temp_tables OK +sys.user_summary OK +sys.user_summary_by_file_io OK +sys.user_summary_by_file_io_type OK +sys.user_summary_by_stages OK +sys.user_summary_by_statement_latency OK +sys.user_summary_by_statement_type OK +sys.version OK +sys.wait_classes_global_by_avg_latency OK +sys.wait_classes_global_by_latency OK +sys.waits_by_host_by_latency OK +sys.waits_by_user_by_latency OK +sys.waits_global_by_latency OK +sys.x$host_summary OK +sys.x$host_summary_by_file_io OK +sys.x$host_summary_by_file_io_type OK +sys.x$host_summary_by_stages OK +sys.x$host_summary_by_statement_latency OK +sys.x$host_summary_by_statement_type OK +sys.x$innodb_buffer_stats_by_schema OK +sys.x$innodb_buffer_stats_by_table OK +sys.x$innodb_lock_waits OK +sys.x$io_by_thread_by_latency OK +sys.x$io_global_by_file_by_bytes OK +sys.x$io_global_by_file_by_latency OK +sys.x$io_global_by_wait_by_bytes OK +sys.x$io_global_by_wait_by_latency OK +sys.x$latest_file_io OK +sys.x$memory_by_host_by_current_bytes OK +sys.x$memory_by_thread_by_current_bytes OK +sys.x$memory_by_user_by_current_bytes OK +sys.x$memory_global_by_current_bytes OK +sys.x$memory_global_total OK +sys.x$processlist OK +sys.x$ps_digest_95th_percentile_by_avg_us OK +sys.x$ps_digest_avg_latency_distribution OK +sys.x$ps_schema_table_statistics_io OK +sys.x$schema_flattened_keys OK +sys.x$schema_index_statistics OK +sys.x$schema_table_lock_waits OK +sys.x$schema_table_statistics OK +sys.x$schema_table_statistics_with_buffer OK +sys.x$schema_tables_with_full_table_scans OK +sys.x$session OK +sys.x$statement_analysis OK +sys.x$statements_with_errors_or_warnings OK +sys.x$statements_with_full_table_scans OK +sys.x$statements_with_runtimes_in_95th_percentile OK +sys.x$statements_with_sorting OK +sys.x$statements_with_temp_tables OK +sys.x$user_summary OK +sys.x$user_summary_by_file_io OK +sys.x$user_summary_by_file_io_type OK +sys.x$user_summary_by_stages OK +sys.x$user_summary_by_statement_latency OK +sys.x$user_summary_by_statement_type OK +sys.x$wait_classes_global_by_avg_latency OK +sys.x$wait_classes_global_by_latency OK +sys.x$waits_by_host_by_latency OK +sys.x$waits_by_user_by_latency OK +sys.x$waits_global_by_latency OK +Phase 5/8: Fixing table and database names +Phase 6/8: Checking and upgrading tables +Processing databases +information_schema +mtr +mtr.global_suppressions OK +mtr.test_suppressions OK +performance_schema +sys +sys.sys_config OK +test +Phase 7/8: uninstalling plugins +Phase 8/8: Running 'FLUSH PRIVILEGES' +OK +# The EXECUTE grants on sys.table_exists and sys.quote_identifier +# must survive the reinstall of the sys schema. +SHOW GRANTS FOR mdev39993_user@localhost; +Grants for mdev39993_user@localhost +GRANT USAGE ON *.* TO `mdev39993_user`@`localhost` +GRANT EXECUTE ON PROCEDURE `sys`.`table_exists` TO `mdev39993_user`@`localhost` +GRANT EXECUTE ON FUNCTION `sys`.`quote_identifier` TO `mdev39993_user`@`localhost` +# The routine bodies must have been replaced back to their real +# definitions (proves the sys schema was actually reinstalled by +# the upgrade, not merely skipped). +SELECT sys.quote_identifier('abc') AS qi; +qi +`abc` +CALL sys.table_exists('mysql', 'user', @exists); +SELECT @exists; +@exists +VIEW +DROP USER mdev39993_user@localhost; +# End of 11.4 tests diff --git a/mysql-test/main/mysql_upgrade_sys_routine_grants.test b/mysql-test/main/mysql_upgrade_sys_routine_grants.test new file mode 100644 index 00000000000..7d4b0f34cd7 --- /dev/null +++ b/mysql-test/main/mysql_upgrade_sys_routine_grants.test @@ -0,0 +1,61 @@ +--source include/mysql_upgrade_preparation.inc +--source include/have_innodb.inc + +--echo # +--echo # MDEV-39993 mariadb-upgrade Drops Existing EXECUTE Grants on sys +--echo # Stored Routines During Upgrade +--echo # + +CREATE USER mdev39993_user@localhost; +GRANT EXECUTE ON PROCEDURE sys.table_exists TO mdev39993_user@localhost; +GRANT EXECUTE ON FUNCTION sys.quote_identifier TO mdev39993_user@localhost; + +SHOW GRANTS FOR mdev39993_user@localhost; + +--echo # Overwrite the routine bodies with a marker so that a passing test +--echo # below actually proves the routines were reinstalled by the +--echo # upgrade, rather than merely left untouched (i.e. sys schema +--echo # install skipped). +DELIMITER |; +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' FUNCTION sys.quote_identifier(in_identifier TEXT) + RETURNS TEXT + SQL SECURITY INVOKER + DETERMINISTIC +BEGIN + RETURN 'stale_body_marker'; +END| + +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' PROCEDURE sys.table_exists ( + IN in_db VARCHAR(64), IN in_table VARCHAR(64), + OUT out_exists ENUM('', 'BASE TABLE', 'VIEW', 'TEMPORARY', 'SEQUENCE', 'SYSTEM VIEW', 'TEMPORARY SEQUENCE') + ) + SQL SECURITY INVOKER +BEGIN + SET out_exists = ''; +END| +DELIMITER ;| + +SELECT sys.quote_identifier('abc') AS qi; +CALL sys.table_exists('mysql', 'user', @exists); +SELECT @exists; + +--exec $MYSQL_UPGRADE --force 2>&1 + +let $MYSQLD_DATADIR= `select @@datadir`; +--file_exists $MYSQLD_DATADIR/mariadb_upgrade_info +--remove_file $MYSQLD_DATADIR/mariadb_upgrade_info + +--echo # The EXECUTE grants on sys.table_exists and sys.quote_identifier +--echo # must survive the reinstall of the sys schema. +SHOW GRANTS FOR mdev39993_user@localhost; + +--echo # The routine bodies must have been replaced back to their real +--echo # definitions (proves the sys schema was actually reinstalled by +--echo # the upgrade, not merely skipped). +SELECT sys.quote_identifier('abc') AS qi; +CALL sys.table_exists('mysql', 'user', @exists); +SELECT @exists; + +DROP USER mdev39993_user@localhost; + +--echo # End of 11.4 tests diff --git a/scripts/sys_schema/functions/extract_schema_from_file_name.sql b/scripts/sys_schema/functions/extract_schema_from_file_name.sql index 92c127f9551..ecd7747d670 100644 --- a/scripts/sys_schema/functions/extract_schema_from_file_name.sql +++ b/scripts/sys_schema/functions/extract_schema_from_file_name.sql @@ -13,11 +13,9 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP FUNCTION IF EXISTS extract_schema_from_file_name; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' FUNCTION extract_schema_from_file_name ( +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' FUNCTION extract_schema_from_file_name ( path VARCHAR(512) ) RETURNS VARCHAR(64) diff --git a/scripts/sys_schema/functions/extract_table_from_file_name.sql b/scripts/sys_schema/functions/extract_table_from_file_name.sql index 54f925681c2..d3f135b11f2 100644 --- a/scripts/sys_schema/functions/extract_table_from_file_name.sql +++ b/scripts/sys_schema/functions/extract_table_from_file_name.sql @@ -13,11 +13,9 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP FUNCTION IF EXISTS extract_table_from_file_name; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' FUNCTION extract_table_from_file_name ( +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' FUNCTION extract_table_from_file_name ( path VARCHAR(512) ) RETURNS VARCHAR(64) diff --git a/scripts/sys_schema/functions/format_bytes.sql b/scripts/sys_schema/functions/format_bytes.sql index 361aa39a639..97f15d357dd 100644 --- a/scripts/sys_schema/functions/format_bytes.sql +++ b/scripts/sys_schema/functions/format_bytes.sql @@ -13,11 +13,9 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP FUNCTION IF EXISTS format_bytes; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' FUNCTION format_bytes ( +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' FUNCTION format_bytes ( -- We feed in and return TEXT here, as aggregates of -- bytes can return numbers larger than BIGINT UNSIGNED bytes TEXT diff --git a/scripts/sys_schema/functions/format_path.sql b/scripts/sys_schema/functions/format_path.sql index 426bfb1dac8..3c15b88a4f5 100644 --- a/scripts/sys_schema/functions/format_path.sql +++ b/scripts/sys_schema/functions/format_path.sql @@ -13,11 +13,9 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP FUNCTION IF EXISTS format_path; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' FUNCTION format_path ( +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' FUNCTION format_path ( in_path VARCHAR(512) ) RETURNS VARCHAR(512) CHARSET UTF8 diff --git a/scripts/sys_schema/functions/format_path_57.sql b/scripts/sys_schema/functions/format_path_57.sql index 2bb8b72395e..d103fd42f4e 100644 --- a/scripts/sys_schema/functions/format_path_57.sql +++ b/scripts/sys_schema/functions/format_path_57.sql @@ -13,11 +13,9 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP FUNCTION IF EXISTS format_path; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' FUNCTION format_path ( +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' FUNCTION format_path ( in_path VARCHAR(512) ) RETURNS VARCHAR(512) CHARSET UTF8 diff --git a/scripts/sys_schema/functions/format_statement.sql b/scripts/sys_schema/functions/format_statement.sql index 8e434b24bc2..4efcbb45ea2 100644 --- a/scripts/sys_schema/functions/format_statement.sql +++ b/scripts/sys_schema/functions/format_statement.sql @@ -13,11 +13,9 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP FUNCTION IF EXISTS format_statement; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' FUNCTION format_statement ( +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' FUNCTION format_statement ( statement LONGTEXT ) RETURNS LONGTEXT diff --git a/scripts/sys_schema/functions/format_time.sql b/scripts/sys_schema/functions/format_time.sql index 47ee0b85cb0..5b0e18b80e7 100644 --- a/scripts/sys_schema/functions/format_time.sql +++ b/scripts/sys_schema/functions/format_time.sql @@ -13,11 +13,9 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP FUNCTION IF EXISTS format_time; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' FUNCTION format_time ( +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' FUNCTION format_time ( -- We feed in and return TEXT here, as aggregates of -- picoseconds can return numbers larger than BIGINT UNSIGNED picoseconds TEXT diff --git a/scripts/sys_schema/functions/list_add.sql b/scripts/sys_schema/functions/list_add.sql index 6079fbc584c..e10150d3931 100644 --- a/scripts/sys_schema/functions/list_add.sql +++ b/scripts/sys_schema/functions/list_add.sql @@ -13,11 +13,9 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP FUNCTION IF EXISTS list_add; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' FUNCTION list_add ( +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' FUNCTION list_add ( in_list TEXT, in_add_value TEXT ) diff --git a/scripts/sys_schema/functions/list_drop.sql b/scripts/sys_schema/functions/list_drop.sql index eccce7ed47c..d00c2bbcddc 100644 --- a/scripts/sys_schema/functions/list_drop.sql +++ b/scripts/sys_schema/functions/list_drop.sql @@ -13,11 +13,9 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP FUNCTION IF EXISTS list_drop; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' FUNCTION list_drop ( +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' FUNCTION list_drop ( in_list TEXT, in_drop_value TEXT ) diff --git a/scripts/sys_schema/functions/ps_is_account_enabled.sql b/scripts/sys_schema/functions/ps_is_account_enabled.sql index a4bcecc4c41..d80d1df46cb 100644 --- a/scripts/sys_schema/functions/ps_is_account_enabled.sql +++ b/scripts/sys_schema/functions/ps_is_account_enabled.sql @@ -13,11 +13,9 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP FUNCTION IF EXISTS ps_is_account_enabled; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' FUNCTION ps_is_account_enabled ( +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' FUNCTION ps_is_account_enabled ( in_host VARCHAR(60), in_user VARCHAR(16) ) diff --git a/scripts/sys_schema/functions/ps_is_account_enabled_57.sql b/scripts/sys_schema/functions/ps_is_account_enabled_57.sql index 12480716d00..fbfa5c5c415 100644 --- a/scripts/sys_schema/functions/ps_is_account_enabled_57.sql +++ b/scripts/sys_schema/functions/ps_is_account_enabled_57.sql @@ -13,11 +13,9 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP FUNCTION IF EXISTS ps_is_account_enabled; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' FUNCTION ps_is_account_enabled ( +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' FUNCTION ps_is_account_enabled ( in_host VARCHAR(60), in_user VARCHAR(32) ) diff --git a/scripts/sys_schema/functions/ps_is_consumer_enabled.sql b/scripts/sys_schema/functions/ps_is_consumer_enabled.sql index c9290a9c919..5e05db09982 100644 --- a/scripts/sys_schema/functions/ps_is_consumer_enabled.sql +++ b/scripts/sys_schema/functions/ps_is_consumer_enabled.sql @@ -13,11 +13,9 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP FUNCTION IF EXISTS ps_is_consumer_enabled; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' FUNCTION ps_is_consumer_enabled ( +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' FUNCTION ps_is_consumer_enabled ( in_consumer varchar(64) ) RETURNS enum('YES', 'NO') diff --git a/scripts/sys_schema/functions/ps_is_instrument_default_enabled.sql b/scripts/sys_schema/functions/ps_is_instrument_default_enabled.sql index 2d79a1611ea..ccd107af58c 100644 --- a/scripts/sys_schema/functions/ps_is_instrument_default_enabled.sql +++ b/scripts/sys_schema/functions/ps_is_instrument_default_enabled.sql @@ -13,11 +13,9 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP FUNCTION IF EXISTS ps_is_instrument_default_enabled; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' FUNCTION ps_is_instrument_default_enabled ( +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' FUNCTION ps_is_instrument_default_enabled ( in_instrument VARCHAR(128) ) RETURNS ENUM('YES', 'NO') diff --git a/scripts/sys_schema/functions/ps_is_instrument_default_timed.sql b/scripts/sys_schema/functions/ps_is_instrument_default_timed.sql index d78e324d719..2648251c2b6 100644 --- a/scripts/sys_schema/functions/ps_is_instrument_default_timed.sql +++ b/scripts/sys_schema/functions/ps_is_instrument_default_timed.sql @@ -13,11 +13,9 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP FUNCTION IF EXISTS ps_is_instrument_default_timed; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' FUNCTION ps_is_instrument_default_timed ( +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' FUNCTION ps_is_instrument_default_timed ( in_instrument VARCHAR(128) ) RETURNS ENUM('YES', 'NO') diff --git a/scripts/sys_schema/functions/ps_is_thread_instrumented.sql b/scripts/sys_schema/functions/ps_is_thread_instrumented.sql index 06388312135..642077e468c 100644 --- a/scripts/sys_schema/functions/ps_is_thread_instrumented.sql +++ b/scripts/sys_schema/functions/ps_is_thread_instrumented.sql @@ -13,11 +13,9 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP FUNCTION IF EXISTS ps_is_thread_instrumented; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' FUNCTION ps_is_thread_instrumented ( +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' FUNCTION ps_is_thread_instrumented ( in_connection_id BIGINT UNSIGNED ) RETURNS ENUM('YES', 'NO', 'UNKNOWN') COMMENT ' diff --git a/scripts/sys_schema/functions/ps_thread_account.sql b/scripts/sys_schema/functions/ps_thread_account.sql index f91e8c66459..04d8748ce1d 100644 --- a/scripts/sys_schema/functions/ps_thread_account.sql +++ b/scripts/sys_schema/functions/ps_thread_account.sql @@ -13,11 +13,9 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP FUNCTION IF EXISTS ps_thread_account; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' FUNCTION ps_thread_account ( +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' FUNCTION ps_thread_account ( in_thread_id BIGINT UNSIGNED ) RETURNS TEXT COMMENT ' diff --git a/scripts/sys_schema/functions/ps_thread_id.sql b/scripts/sys_schema/functions/ps_thread_id.sql index f344e6e15d2..31517759b2b 100644 --- a/scripts/sys_schema/functions/ps_thread_id.sql +++ b/scripts/sys_schema/functions/ps_thread_id.sql @@ -13,11 +13,9 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP FUNCTION IF EXISTS ps_thread_id; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' FUNCTION ps_thread_id ( +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' FUNCTION ps_thread_id ( in_connection_id BIGINT UNSIGNED ) RETURNS BIGINT UNSIGNED COMMENT ' diff --git a/scripts/sys_schema/functions/ps_thread_stack.sql b/scripts/sys_schema/functions/ps_thread_stack.sql index 12b09bd6637..0236d400f95 100644 --- a/scripts/sys_schema/functions/ps_thread_stack.sql +++ b/scripts/sys_schema/functions/ps_thread_stack.sql @@ -13,11 +13,9 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP FUNCTION IF EXISTS ps_thread_stack; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' FUNCTION ps_thread_stack ( +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' FUNCTION ps_thread_stack ( thd_id BIGINT UNSIGNED, debug BOOLEAN ) diff --git a/scripts/sys_schema/functions/ps_thread_trx_info.sql b/scripts/sys_schema/functions/ps_thread_trx_info.sql index e0f7d17970d..d30c525016e 100644 --- a/scripts/sys_schema/functions/ps_thread_trx_info.sql +++ b/scripts/sys_schema/functions/ps_thread_trx_info.sql @@ -13,11 +13,9 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP FUNCTION IF EXISTS ps_thread_trx_info; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' FUNCTION ps_thread_trx_info ( +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' FUNCTION ps_thread_trx_info ( in_thread_id BIGINT UNSIGNED ) RETURNS LONGTEXT COMMENT ' diff --git a/scripts/sys_schema/functions/quote_identifier.sql b/scripts/sys_schema/functions/quote_identifier.sql index b661e5c7b01..481b359eea2 100644 --- a/scripts/sys_schema/functions/quote_identifier.sql +++ b/scripts/sys_schema/functions/quote_identifier.sql @@ -13,8 +13,6 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP FUNCTION IF EXISTS quote_identifier; - DELIMITER $$ -- https://dev.mysql.com/doc/refman/5.7/en/identifiers.html @@ -22,7 +20,7 @@ DELIMITER $$ -- Before that, user variables could have any length. -- -- Based on Paul Dubois' suggestion in Bug #78823/Bug #22011361. -CREATE DEFINER='mariadb.sys'@'localhost' FUNCTION quote_identifier(in_identifier TEXT) +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' FUNCTION quote_identifier(in_identifier TEXT) RETURNS TEXT CHARSET UTF8 COMMENT ' Description diff --git a/scripts/sys_schema/functions/sys_get_config.sql b/scripts/sys_schema/functions/sys_get_config.sql index 9c0c901167a..f2239e3a1ed 100644 --- a/scripts/sys_schema/functions/sys_get_config.sql +++ b/scripts/sys_schema/functions/sys_get_config.sql @@ -13,11 +13,9 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP FUNCTION IF EXISTS sys_get_config; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' FUNCTION sys_get_config ( +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' FUNCTION sys_get_config ( in_variable_name VARCHAR(128), in_default_value VARCHAR(128) ) diff --git a/scripts/sys_schema/functions/version_major.sql b/scripts/sys_schema/functions/version_major.sql index 8e44d8cf3e2..9779878960c 100644 --- a/scripts/sys_schema/functions/version_major.sql +++ b/scripts/sys_schema/functions/version_major.sql @@ -13,11 +13,9 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP FUNCTION IF EXISTS version_major; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' FUNCTION version_major () +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' FUNCTION version_major () RETURNS TINYINT UNSIGNED COMMENT ' Description diff --git a/scripts/sys_schema/functions/version_minor.sql b/scripts/sys_schema/functions/version_minor.sql index 6d8f654d6f2..de75311e356 100644 --- a/scripts/sys_schema/functions/version_minor.sql +++ b/scripts/sys_schema/functions/version_minor.sql @@ -13,11 +13,9 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP FUNCTION IF EXISTS version_minor; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' FUNCTION version_minor () +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' FUNCTION version_minor () RETURNS TINYINT UNSIGNED COMMENT ' Description diff --git a/scripts/sys_schema/functions/version_patch.sql b/scripts/sys_schema/functions/version_patch.sql index 156b5678a17..ee5d7954603 100644 --- a/scripts/sys_schema/functions/version_patch.sql +++ b/scripts/sys_schema/functions/version_patch.sql @@ -13,11 +13,9 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP FUNCTION IF EXISTS version_patch; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' FUNCTION version_patch () +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' FUNCTION version_patch () RETURNS TINYINT UNSIGNED COMMENT ' Description diff --git a/scripts/sys_schema/generate_sql_file.sh b/scripts/sys_schema/generate_sql_file.sh index 3005d92633f..a1fd36e797f 100644 --- a/scripts/sys_schema/generate_sql_file.sh +++ b/scripts/sys_schema/generate_sql_file.sh @@ -189,10 +189,12 @@ then # Add the rest of the files in install file order, removing new lines along the way cat "$SYSDIR/sys_$MYSQLVERSION.sql" | tr -d '\r' | grep 'SOURCE' | grep -v before_setup | grep -v after_setup | $SED_R 's .{8} ' | sed 's/^/./' > "./sys_$MYSQLVERSION.sql" while read file; do - # First try and get a DROP command - grep -E '(^DROP PROCEDURE|^DROP FUNCTION|^DROP TRIGGER)' $file >> $OUTPUTFILE + # First try and get a DROP command. Functions and procedures are + # installed with CREATE OR REPLACE (MDEV-39993, to preserve grants + # across reinstalls) and so never have one; only triggers still do. + grep -E '^DROP TRIGGER' $file >> $OUTPUTFILE # And remove any that may exist (but keep DROP TEMPORARY TABLE) - sed -i -e "/^DROP PROCEDURE/d;/^DROP FUNCTION/d;/^DROP TRIGGER/d" $file + sed -i -e "/^DROP TRIGGER/d" $file echo "" >> $OUTPUTFILE # Then collapse the rest of the file cat $file | tr '\n' ' ' >> $OUTPUTFILE diff --git a/scripts/sys_schema/procedures/create_synonym_db.sql b/scripts/sys_schema/procedures/create_synonym_db.sql index 4827037677c..e88be19135b 100644 --- a/scripts/sys_schema/procedures/create_synonym_db.sql +++ b/scripts/sys_schema/procedures/create_synonym_db.sql @@ -13,11 +13,9 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP PROCEDURE IF EXISTS create_synonym_db; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' PROCEDURE create_synonym_db ( +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' PROCEDURE create_synonym_db ( IN in_db_name VARCHAR(64), IN in_synonym VARCHAR(64) ) diff --git a/scripts/sys_schema/procedures/diagnostics.sql b/scripts/sys_schema/procedures/diagnostics.sql index 2d2f4664a2d..e30462c4b1f 100644 --- a/scripts/sys_schema/procedures/diagnostics.sql +++ b/scripts/sys_schema/procedures/diagnostics.sql @@ -13,11 +13,9 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP PROCEDURE IF EXISTS diagnostics; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' PROCEDURE diagnostics ( +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' PROCEDURE diagnostics ( IN in_max_runtime int unsigned, IN in_interval int unsigned, IN in_auto_config enum ('current', 'medium', 'full') ) diff --git a/scripts/sys_schema/procedures/execute_prepared_stmt.sql b/scripts/sys_schema/procedures/execute_prepared_stmt.sql index 8e975b909c2..72f904e561d 100644 --- a/scripts/sys_schema/procedures/execute_prepared_stmt.sql +++ b/scripts/sys_schema/procedures/execute_prepared_stmt.sql @@ -13,11 +13,9 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP PROCEDURE IF EXISTS execute_prepared_stmt; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' PROCEDURE execute_prepared_stmt ( +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' PROCEDURE execute_prepared_stmt ( IN in_query longtext CHARACTER SET UTF8 ) COMMENT ' diff --git a/scripts/sys_schema/procedures/optimizer_switch.sql b/scripts/sys_schema/procedures/optimizer_switch.sql index febeabc1208..10b358b483a 100644 --- a/scripts/sys_schema/procedures/optimizer_switch.sql +++ b/scripts/sys_schema/procedures/optimizer_switch.sql @@ -13,12 +13,9 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP PROCEDURE IF EXISTS optimizer_switch_choice; -DROP PROCEDURE IF EXISTS optimizer_switch_on; -DROP PROCEDURE IF EXISTS optimizer_switch_off; DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' PROCEDURE optimizer_switch_choice(IN on_off VARCHAR(3)) +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' PROCEDURE optimizer_switch_choice(IN on_off VARCHAR(3)) COMMENT 'return @@optimizer_switch options as a result set for easier readability' SQL SECURITY INVOKER NOT DETERMINISTIC @@ -47,7 +44,7 @@ BEGIN DROP TEMPORARY TABLE tmp_opt_switch; END$$ -CREATE DEFINER='mariadb.sys'@'localhost' PROCEDURE optimizer_switch_on() +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' PROCEDURE optimizer_switch_on() COMMENT 'return @@optimizer_switch options that are on' SQL SECURITY INVOKER NOT DETERMINISTIC @@ -56,7 +53,7 @@ BEGIN call optimizer_switch_choice("on"); END$$ -CREATE DEFINER='mariadb.sys'@'localhost' PROCEDURE optimizer_switch_off() +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' PROCEDURE optimizer_switch_off() COMMENT 'return @@optimizer_switch options that are off' SQL SECURITY INVOKER NOT DETERMINISTIC diff --git a/scripts/sys_schema/procedures/ps_setup_disable_background_threads.sql b/scripts/sys_schema/procedures/ps_setup_disable_background_threads.sql index 0c316bc78b8..665874ea886 100644 --- a/scripts/sys_schema/procedures/ps_setup_disable_background_threads.sql +++ b/scripts/sys_schema/procedures/ps_setup_disable_background_threads.sql @@ -13,11 +13,9 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP PROCEDURE IF EXISTS ps_setup_disable_background_threads; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' PROCEDURE ps_setup_disable_background_threads () +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' PROCEDURE ps_setup_disable_background_threads () COMMENT ' Description ----------- diff --git a/scripts/sys_schema/procedures/ps_setup_disable_consumer.sql b/scripts/sys_schema/procedures/ps_setup_disable_consumer.sql index bdebd62e419..857fb7f0305 100644 --- a/scripts/sys_schema/procedures/ps_setup_disable_consumer.sql +++ b/scripts/sys_schema/procedures/ps_setup_disable_consumer.sql @@ -13,11 +13,9 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP PROCEDURE IF EXISTS ps_setup_disable_consumer; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' PROCEDURE ps_setup_disable_consumer ( +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' PROCEDURE ps_setup_disable_consumer ( IN consumer VARCHAR(128) ) COMMENT ' diff --git a/scripts/sys_schema/procedures/ps_setup_disable_instrument.sql b/scripts/sys_schema/procedures/ps_setup_disable_instrument.sql index 14fe4d86b1d..2dcc1b71ff4 100644 --- a/scripts/sys_schema/procedures/ps_setup_disable_instrument.sql +++ b/scripts/sys_schema/procedures/ps_setup_disable_instrument.sql @@ -13,11 +13,9 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP PROCEDURE IF EXISTS ps_setup_disable_instrument; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' PROCEDURE ps_setup_disable_instrument ( +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' PROCEDURE ps_setup_disable_instrument ( IN in_pattern VARCHAR(128) ) COMMENT ' diff --git a/scripts/sys_schema/procedures/ps_setup_disable_thread.sql b/scripts/sys_schema/procedures/ps_setup_disable_thread.sql index fe0bb86275f..910b2395569 100644 --- a/scripts/sys_schema/procedures/ps_setup_disable_thread.sql +++ b/scripts/sys_schema/procedures/ps_setup_disable_thread.sql @@ -13,11 +13,9 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP PROCEDURE IF EXISTS ps_setup_disable_thread; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' PROCEDURE ps_setup_disable_thread ( +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' PROCEDURE ps_setup_disable_thread ( IN in_connection_id BIGINT ) COMMENT ' diff --git a/scripts/sys_schema/procedures/ps_setup_enable_background_threads.sql b/scripts/sys_schema/procedures/ps_setup_enable_background_threads.sql index ddca06b1287..210b25559f0 100644 --- a/scripts/sys_schema/procedures/ps_setup_enable_background_threads.sql +++ b/scripts/sys_schema/procedures/ps_setup_enable_background_threads.sql @@ -13,11 +13,9 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP PROCEDURE IF EXISTS ps_setup_enable_background_threads; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' PROCEDURE ps_setup_enable_background_threads () +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' PROCEDURE ps_setup_enable_background_threads () COMMENT ' Description ----------- diff --git a/scripts/sys_schema/procedures/ps_setup_enable_consumer.sql b/scripts/sys_schema/procedures/ps_setup_enable_consumer.sql index 66138604595..a37607e515f 100644 --- a/scripts/sys_schema/procedures/ps_setup_enable_consumer.sql +++ b/scripts/sys_schema/procedures/ps_setup_enable_consumer.sql @@ -13,11 +13,9 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP PROCEDURE IF EXISTS ps_setup_enable_consumer; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' PROCEDURE ps_setup_enable_consumer ( +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' PROCEDURE ps_setup_enable_consumer ( IN consumer VARCHAR(128) ) COMMENT ' diff --git a/scripts/sys_schema/procedures/ps_setup_enable_instrument.sql b/scripts/sys_schema/procedures/ps_setup_enable_instrument.sql index 964307de45a..9bcdca002d5 100644 --- a/scripts/sys_schema/procedures/ps_setup_enable_instrument.sql +++ b/scripts/sys_schema/procedures/ps_setup_enable_instrument.sql @@ -13,11 +13,9 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP PROCEDURE IF EXISTS ps_setup_enable_instrument; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' PROCEDURE ps_setup_enable_instrument ( +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' PROCEDURE ps_setup_enable_instrument ( IN in_pattern VARCHAR(128) ) COMMENT ' diff --git a/scripts/sys_schema/procedures/ps_setup_enable_thread.sql b/scripts/sys_schema/procedures/ps_setup_enable_thread.sql index 15385fc8a3e..e6d48f16875 100644 --- a/scripts/sys_schema/procedures/ps_setup_enable_thread.sql +++ b/scripts/sys_schema/procedures/ps_setup_enable_thread.sql @@ -13,11 +13,9 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP PROCEDURE IF EXISTS ps_setup_enable_thread; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' PROCEDURE ps_setup_enable_thread ( +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' PROCEDURE ps_setup_enable_thread ( IN in_connection_id BIGINT ) COMMENT ' diff --git a/scripts/sys_schema/procedures/ps_setup_reload_saved.sql b/scripts/sys_schema/procedures/ps_setup_reload_saved.sql index 38d463b1a4c..53e785a6bcd 100644 --- a/scripts/sys_schema/procedures/ps_setup_reload_saved.sql +++ b/scripts/sys_schema/procedures/ps_setup_reload_saved.sql @@ -13,11 +13,9 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP PROCEDURE IF EXISTS ps_setup_reload_saved; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' PROCEDURE ps_setup_reload_saved () +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' PROCEDURE ps_setup_reload_saved () COMMENT ' Description ----------- diff --git a/scripts/sys_schema/procedures/ps_setup_reset_to_default.sql b/scripts/sys_schema/procedures/ps_setup_reset_to_default.sql index b626fae82fd..467a6fae42e 100644 --- a/scripts/sys_schema/procedures/ps_setup_reset_to_default.sql +++ b/scripts/sys_schema/procedures/ps_setup_reset_to_default.sql @@ -13,11 +13,9 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP PROCEDURE IF EXISTS ps_setup_reset_to_default; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' PROCEDURE ps_setup_reset_to_default ( +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' PROCEDURE ps_setup_reset_to_default ( IN in_verbose BOOLEAN ) COMMENT ' diff --git a/scripts/sys_schema/procedures/ps_setup_reset_to_default_57.sql b/scripts/sys_schema/procedures/ps_setup_reset_to_default_57.sql index 9f2ea4682d0..4b48346390e 100644 --- a/scripts/sys_schema/procedures/ps_setup_reset_to_default_57.sql +++ b/scripts/sys_schema/procedures/ps_setup_reset_to_default_57.sql @@ -13,11 +13,9 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP PROCEDURE IF EXISTS ps_setup_reset_to_default; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' PROCEDURE ps_setup_reset_to_default ( +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' PROCEDURE ps_setup_reset_to_default ( IN in_verbose BOOLEAN ) COMMENT ' diff --git a/scripts/sys_schema/procedures/ps_setup_save.sql b/scripts/sys_schema/procedures/ps_setup_save.sql index 71844e781cd..e2036075bfa 100644 --- a/scripts/sys_schema/procedures/ps_setup_save.sql +++ b/scripts/sys_schema/procedures/ps_setup_save.sql @@ -13,11 +13,9 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP PROCEDURE IF EXISTS ps_setup_save; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' PROCEDURE ps_setup_save ( +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' PROCEDURE ps_setup_save ( IN in_timeout INT ) COMMENT ' diff --git a/scripts/sys_schema/procedures/ps_setup_show_disabled.sql b/scripts/sys_schema/procedures/ps_setup_show_disabled.sql index 82c69840566..86408d77d7d 100644 --- a/scripts/sys_schema/procedures/ps_setup_show_disabled.sql +++ b/scripts/sys_schema/procedures/ps_setup_show_disabled.sql @@ -13,11 +13,9 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP PROCEDURE IF EXISTS ps_setup_show_disabled; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' PROCEDURE ps_setup_show_disabled ( +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' PROCEDURE ps_setup_show_disabled ( IN in_show_instruments BOOLEAN, IN in_show_threads BOOLEAN ) diff --git a/scripts/sys_schema/procedures/ps_setup_show_disabled_consumers.sql b/scripts/sys_schema/procedures/ps_setup_show_disabled_consumers.sql index 4c913efdc0e..865084f75db 100644 --- a/scripts/sys_schema/procedures/ps_setup_show_disabled_consumers.sql +++ b/scripts/sys_schema/procedures/ps_setup_show_disabled_consumers.sql @@ -13,11 +13,9 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP PROCEDURE IF EXISTS ps_setup_show_disabled_consumers; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' PROCEDURE ps_setup_show_disabled_consumers () +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' PROCEDURE ps_setup_show_disabled_consumers () COMMENT ' Description ----------- diff --git a/scripts/sys_schema/procedures/ps_setup_show_disabled_instruments.sql b/scripts/sys_schema/procedures/ps_setup_show_disabled_instruments.sql index 45fe7c083df..fc10e991878 100644 --- a/scripts/sys_schema/procedures/ps_setup_show_disabled_instruments.sql +++ b/scripts/sys_schema/procedures/ps_setup_show_disabled_instruments.sql @@ -13,11 +13,9 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP PROCEDURE IF EXISTS ps_setup_show_disabled_instruments; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' PROCEDURE ps_setup_show_disabled_instruments () +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' PROCEDURE ps_setup_show_disabled_instruments () COMMENT ' Description ----------- diff --git a/scripts/sys_schema/procedures/ps_setup_show_enabled.sql b/scripts/sys_schema/procedures/ps_setup_show_enabled.sql index 8e7c0506677..5aa8ae08394 100644 --- a/scripts/sys_schema/procedures/ps_setup_show_enabled.sql +++ b/scripts/sys_schema/procedures/ps_setup_show_enabled.sql @@ -13,11 +13,9 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP PROCEDURE IF EXISTS ps_setup_show_enabled; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' PROCEDURE ps_setup_show_enabled ( +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' PROCEDURE ps_setup_show_enabled ( IN in_show_instruments BOOLEAN, IN in_show_threads BOOLEAN ) diff --git a/scripts/sys_schema/procedures/ps_setup_show_enabled_consumers.sql b/scripts/sys_schema/procedures/ps_setup_show_enabled_consumers.sql index 57ec2b751b5..ba2ef2225a1 100644 --- a/scripts/sys_schema/procedures/ps_setup_show_enabled_consumers.sql +++ b/scripts/sys_schema/procedures/ps_setup_show_enabled_consumers.sql @@ -13,11 +13,9 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP PROCEDURE IF EXISTS ps_setup_show_enabled_consumers; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' PROCEDURE ps_setup_show_enabled_consumers () +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' PROCEDURE ps_setup_show_enabled_consumers () COMMENT ' Description ----------- diff --git a/scripts/sys_schema/procedures/ps_setup_show_enabled_instruments.sql b/scripts/sys_schema/procedures/ps_setup_show_enabled_instruments.sql index d8cfb108995..1b9cd272e1f 100644 --- a/scripts/sys_schema/procedures/ps_setup_show_enabled_instruments.sql +++ b/scripts/sys_schema/procedures/ps_setup_show_enabled_instruments.sql @@ -13,11 +13,9 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP PROCEDURE IF EXISTS ps_setup_show_enabled_instruments; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' PROCEDURE ps_setup_show_enabled_instruments () +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' PROCEDURE ps_setup_show_enabled_instruments () COMMENT ' Description ----------- diff --git a/scripts/sys_schema/procedures/ps_statement_avg_latency_histogram.sql b/scripts/sys_schema/procedures/ps_statement_avg_latency_histogram.sql index e57cbc1d452..01d2ae42f41 100644 --- a/scripts/sys_schema/procedures/ps_statement_avg_latency_histogram.sql +++ b/scripts/sys_schema/procedures/ps_statement_avg_latency_histogram.sql @@ -13,11 +13,9 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP PROCEDURE IF EXISTS ps_statement_avg_latency_histogram; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' PROCEDURE ps_statement_avg_latency_histogram () +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' PROCEDURE ps_statement_avg_latency_histogram () COMMENT ' Description ----------- diff --git a/scripts/sys_schema/procedures/ps_trace_statement_digest.sql b/scripts/sys_schema/procedures/ps_trace_statement_digest.sql index 6ad01e4d8d8..10947ba1c3a 100644 --- a/scripts/sys_schema/procedures/ps_trace_statement_digest.sql +++ b/scripts/sys_schema/procedures/ps_trace_statement_digest.sql @@ -13,11 +13,9 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP PROCEDURE IF EXISTS ps_trace_statement_digest; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' PROCEDURE ps_trace_statement_digest ( +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' PROCEDURE ps_trace_statement_digest ( IN in_digest VARCHAR(32), IN in_runtime INT, IN in_interval DECIMAL(2,2), diff --git a/scripts/sys_schema/procedures/ps_trace_thread.sql b/scripts/sys_schema/procedures/ps_trace_thread.sql index 4d5ba14a84d..36273523438 100644 --- a/scripts/sys_schema/procedures/ps_trace_thread.sql +++ b/scripts/sys_schema/procedures/ps_trace_thread.sql @@ -13,10 +13,8 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP PROCEDURE IF EXISTS ps_trace_thread; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' PROCEDURE ps_trace_thread ( +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' PROCEDURE ps_trace_thread ( IN in_thread_id BIGINT UNSIGNED, IN in_outfile VARCHAR(255), IN in_max_runtime DECIMAL(20,2), diff --git a/scripts/sys_schema/procedures/ps_trace_thread_57.sql b/scripts/sys_schema/procedures/ps_trace_thread_57.sql index d88f824cebf..213ab765c4c 100644 --- a/scripts/sys_schema/procedures/ps_trace_thread_57.sql +++ b/scripts/sys_schema/procedures/ps_trace_thread_57.sql @@ -13,10 +13,8 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP PROCEDURE IF EXISTS ps_trace_thread; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' PROCEDURE ps_trace_thread ( +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' PROCEDURE ps_trace_thread ( IN in_thread_id BIGINT UNSIGNED, IN in_outfile VARCHAR(255), IN in_max_runtime DECIMAL(20,2), diff --git a/scripts/sys_schema/procedures/ps_truncate_all_tables.sql b/scripts/sys_schema/procedures/ps_truncate_all_tables.sql index 42d904f507c..78ba3fb3dc8 100644 --- a/scripts/sys_schema/procedures/ps_truncate_all_tables.sql +++ b/scripts/sys_schema/procedures/ps_truncate_all_tables.sql @@ -13,11 +13,9 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP PROCEDURE IF EXISTS ps_truncate_all_tables; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' PROCEDURE ps_truncate_all_tables ( +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' PROCEDURE ps_truncate_all_tables ( IN in_verbose BOOLEAN ) COMMENT ' diff --git a/scripts/sys_schema/procedures/statement_performance_analyzer.sql b/scripts/sys_schema/procedures/statement_performance_analyzer.sql index 3c26fd0b812..16c6deccf1e 100644 --- a/scripts/sys_schema/procedures/statement_performance_analyzer.sql +++ b/scripts/sys_schema/procedures/statement_performance_analyzer.sql @@ -13,11 +13,9 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP PROCEDURE IF EXISTS statement_performance_analyzer; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' PROCEDURE statement_performance_analyzer ( +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' PROCEDURE statement_performance_analyzer ( IN in_action ENUM('snapshot', 'overall', 'delta', 'create_table', 'create_tmp', 'save', 'cleanup'), IN in_table VARCHAR(129), IN in_views SET ('with_runtimes_in_95th_percentile', 'analysis', 'with_errors_or_warnings', 'with_full_table_scans', 'with_sorting', 'with_temp_tables', 'custom') diff --git a/scripts/sys_schema/procedures/table_exists.sql b/scripts/sys_schema/procedures/table_exists.sql index 3224c87e749..5c057707546 100644 --- a/scripts/sys_schema/procedures/table_exists.sql +++ b/scripts/sys_schema/procedures/table_exists.sql @@ -13,11 +13,9 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP PROCEDURE IF EXISTS table_exists; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' PROCEDURE table_exists ( +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' PROCEDURE table_exists ( IN in_db VARCHAR(64), IN in_table VARCHAR(64), OUT out_exists ENUM('', 'BASE TABLE', 'VIEW', 'TEMPORARY', 'SEQUENCE', 'SYSTEM VIEW', 'TEMPORARY SEQUENCE') ) diff --git a/scripts/sys_schema/templates/function.sql b/scripts/sys_schema/templates/function.sql index dac7899da0a..c297a3eb473 100644 --- a/scripts/sys_schema/templates/function.sql +++ b/scripts/sys_schema/templates/function.sql @@ -13,11 +13,9 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP FUNCTION IF EXISTS ; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' FUNCTION ( +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' FUNCTION ( /* Variables */ ) RETURNS diff --git a/scripts/sys_schema/templates/procedure.sql b/scripts/sys_schema/templates/procedure.sql index a6b0860ac7d..409e4209dd5 100644 --- a/scripts/sys_schema/templates/procedure.sql +++ b/scripts/sys_schema/templates/procedure.sql @@ -13,11 +13,9 @@ -- along with this program; if not, write to the Free Software -- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -DROP PROCEDURE IF EXISTS ; - DELIMITER $$ -CREATE DEFINER='mariadb.sys'@'localhost' PROCEDURE ( +CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' PROCEDURE ( /* Variables */ ) COMMENT ' From 650e852e2441f3183fe9e44edfb6d5ffbc93cecb Mon Sep 17 00:00:00 2001 From: Dave Gosselin Date: Tue, 8 Sep 2026 09:46:02 -0400 Subject: [PATCH 32/71] MDEV-33616: Routines of a mixed case database are not listed At lower_case_table_names=2 this returns nothing. CREATE DATABASE Db1; CREATE FUNCTION Db1.f1(a INT) RETURNS INT RETURN a; SELECT ROUTINE_NAME FROM information_schema.ROUTINES WHERE ROUTINE_SCHEMA='Db1'; mysql.proc records the function's database as db1, in lower case. Creating a routine lower-cases its database name whenever lower_case_table_names is anything but 0, at sql/sp_head.h:121. The datadir, SCHEMATA and DATABASE() all keep Db1. CALL Db1.f1() still works, because calling a routine lower-cases the database name too and then searches mysql.proc for db1. The query above never lower-cases it. It searches for Db1, and mysql.proc.db collates utf8mb3_bin, so the comparison runs byte for byte and no row matches. At setting 1 the server lower-cases the filter value as well, at sql/sql_show.cc:4394, and lower-cases every name it stores, so the query and the table always agree. Setting 2 lower-cases the routine's copy and nothing else. The fix lower-cases the filter value before the search. Sorting the same query brings the row back. SELECT ROUTINE_NAME FROM information_schema.ROUTINES WHERE ROUTINE_SCHEMA='Db1' ORDER BY ROUTINE_NAME; The sort keeps the filter from reaching that search. The server reads all of mysql.proc instead, then applies the WHERE to ROUTINE_SCHEMA, which compares case insensitively. That shape answered correctly all along. The same search fills PARAMETERS and backs SHOW FUNCTION STATUS, SHOW PROCEDURE STATUS, SHOW PACKAGE STATUS and SHOW PACKAGE BODY STATUS. Every one returned nothing for Db1. mariadb-dump lists routines with SHOW FUNCTION STATUS WHERE Db=..., at client/mysqldump.cc:2859, which is the main.mysqldump failure. Setting 0 keeps Db1 and db1 as two databases holding two routines. A case sensitive volume confirms both stay distinct before and after this change. beb9a5459d4 (MDEV-20609) added the search in 10.11.1. main.lowercase_routines runs both query shapes. --- mysql-test/main/lowercase_routines.opt | 1 + mysql-test/main/lowercase_routines.result | 46 ++++++++++++++++++++++ mysql-test/main/lowercase_routines.test | 47 +++++++++++++++++++++++ sql/sql_show.cc | 15 ++++++++ 4 files changed, 109 insertions(+) create mode 100644 mysql-test/main/lowercase_routines.opt create mode 100644 mysql-test/main/lowercase_routines.result create mode 100644 mysql-test/main/lowercase_routines.test diff --git a/mysql-test/main/lowercase_routines.opt b/mysql-test/main/lowercase_routines.opt new file mode 100644 index 00000000000..ac4d3211e89 --- /dev/null +++ b/mysql-test/main/lowercase_routines.opt @@ -0,0 +1 @@ +--lower-case-table-names=2 diff --git a/mysql-test/main/lowercase_routines.result b/mysql-test/main/lowercase_routines.result new file mode 100644 index 00000000000..761b98613d7 --- /dev/null +++ b/mysql-test/main/lowercase_routines.result @@ -0,0 +1,46 @@ +# +# MDEV-33616 Routines of a mixed case database are not listed when +# lower_case_table_names is 2 +# +CREATE DATABASE Db1; +USE Db1; +CREATE FUNCTION f1(a INT) RETURNS INT RETURN a; +CREATE PROCEDURE p1(b INT) BEGIN END; +SELECT SCHEMA_NAME FROM information_schema.SCHEMATA WHERE SCHEMA_NAME='Db1'; +SCHEMA_NAME +Db1 +SELECT db, name FROM mysql.proc WHERE name IN ('f1','p1'); +db name +db1 f1 +db1 p1 +SELECT ROUTINE_NAME FROM information_schema.ROUTINES +WHERE ROUTINE_SCHEMA='Db1'; +ROUTINE_NAME +f1 +p1 +SELECT SPECIFIC_NAME, ORDINAL_POSITION, PARAMETER_NAME +FROM information_schema.PARAMETERS WHERE SPECIFIC_SCHEMA='Db1'; +SPECIFIC_NAME ORDINAL_POSITION PARAMETER_NAME +f1 0 NULL +f1 1 a +p1 1 b +SHOW FUNCTION STATUS WHERE Db='Db1'; +Db Name Type Definer Modified Created Security_type Comment character_set_client collation_connection Database Collation +db1 f1 FUNCTION root@localhost # # DEFINER latin1 latin1_swedish_ci latin1_swedish_ci +SHOW PROCEDURE STATUS WHERE Db='Db1'; +Db Name Type Definer Modified Created Security_type Comment character_set_client collation_connection Database Collation +db1 p1 PROCEDURE root@localhost # # DEFINER latin1 latin1_swedish_ci latin1_swedish_ci +SELECT ROUTINE_NAME FROM information_schema.ROUTINES +WHERE ROUTINE_SCHEMA='Db1' ORDER BY ROUTINE_NAME; +ROUTINE_NAME +f1 +p1 +SELECT SPECIFIC_NAME, ORDINAL_POSITION, PARAMETER_NAME +FROM information_schema.PARAMETERS WHERE SPECIFIC_SCHEMA='Db1' + ORDER BY SPECIFIC_NAME, ORDINAL_POSITION; +SPECIFIC_NAME ORDINAL_POSITION PARAMETER_NAME +f1 0 NULL +f1 1 a +p1 1 b +USE test; +DROP DATABASE Db1; diff --git a/mysql-test/main/lowercase_routines.test b/mysql-test/main/lowercase_routines.test new file mode 100644 index 00000000000..4489d530ba7 --- /dev/null +++ b/mysql-test/main/lowercase_routines.test @@ -0,0 +1,47 @@ +# +# Tests that require lower_case_table_names to be 2 +# (the default when the data directory is on a case insensitive file system) +# +--source include/have_lowercase2.inc + +--echo # +--echo # MDEV-33616 Routines of a mixed case database are not listed when +--echo # lower_case_table_names is 2 +--echo # + +CREATE DATABASE Db1; +USE Db1; +CREATE FUNCTION f1(a INT) RETURNS INT RETURN a; +CREATE PROCEDURE p1(b INT) BEGIN END; + +# The database keeps the case it was created with, mysql.proc does not. +SELECT SCHEMA_NAME FROM information_schema.SCHEMATA WHERE SCHEMA_NAME='Db1'; +--sorted_result +SELECT db, name FROM mysql.proc WHERE name IN ('f1','p1'); + +# The name from the WHERE clause becomes the key of an index read on +# mysql.proc. mysql.proc.db is utf8mb3_bin and holds the folded name, so +# an unfolded key finds nothing. +--sorted_result +SELECT ROUTINE_NAME FROM information_schema.ROUTINES + WHERE ROUTINE_SCHEMA='Db1'; +--sorted_result +SELECT SPECIFIC_NAME, ORDINAL_POSITION, PARAMETER_NAME + FROM information_schema.PARAMETERS WHERE SPECIFIC_SCHEMA='Db1'; +--replace_column 5 # 6 # +SHOW FUNCTION STATUS WHERE Db='Db1'; +--replace_column 5 # 6 # +SHOW PROCEDURE STATUS WHERE Db='Db1'; + +# ORDER BY keeps the condition from reaching the routine that fills the +# table, so mysql.proc is read in full and the WHERE is applied afterwards +# over a utf8mb3_general_ci column. That shape returned the rows even +# while the queries above returned none. +SELECT ROUTINE_NAME FROM information_schema.ROUTINES + WHERE ROUTINE_SCHEMA='Db1' ORDER BY ROUTINE_NAME; +SELECT SPECIFIC_NAME, ORDINAL_POSITION, PARAMETER_NAME + FROM information_schema.PARAMETERS WHERE SPECIFIC_SCHEMA='Db1' + ORDER BY SPECIFIC_NAME, ORDINAL_POSITION; + +USE test; +DROP DATABASE Db1; diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 66999c555a5..8ab4588ec46 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -7115,6 +7115,21 @@ int fill_schema_proc(THD *thd, TABLE_LIST *tables, COND *cond) DBUG_RETURN(0); } + /* + A routine records its database in mysql.proc in lower case whenever + lower_case_table_names is set, see sp_name::sp_name(). The lookup value + is the name as the query spelled it, and get_lookup_field_values folds it + to lower case only for lower_case_table_names 1. Fold it here for the + remaining setting so that the index read below, and the comparison in + check_proc_record, both see the form that was stored. mysql.proc.db is + utf8mb3_bin, so neither of them can absorb the difference in case. + The value is a buffer allocated by get_lookup_field_values, so it can be + changed in place. + */ + if (lower_case_table_names && lookup.db_value.length) + lookup.db_value.length= my_casedn_str(files_charset_info, + (char*) lookup.db_value.str); + start_new_trans new_trans(thd); if (!(proc_table= open_proc_table_for_read(thd))) From 3f550b79330a894d5b4e2bb32b7b6cf0f653a09c Mon Sep 17 00:00:00 2001 From: Dave Gosselin Date: Tue, 8 Sep 2026 11:33:45 -0400 Subject: [PATCH 33/71] MDEV-33616: Only one of two routines named in a statement is found With lower_case_table_names 0 the server can have databases Db1 and db1, each with a function f1. A single statement naming both databases, like SELECT Db1.f1(), db1.f1(), reported that db1.f1 does not exist. The set of routines a statement uses compared its entries without regard to case. Only one routine was loaded but the reference to the other found nothing. The set now compares its entries exactly, as the routine cache and the lock manager already do. --- mysql-test/main/lowercase_routines0.result | 46 +++++++++++++++ mysql-test/main/lowercase_routines0.test | 65 ++++++++++++++++++++++ sql/sp.cc | 10 +++- sql/sp_head.cc | 3 +- 4 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 mysql-test/main/lowercase_routines0.result create mode 100644 mysql-test/main/lowercase_routines0.test diff --git a/mysql-test/main/lowercase_routines0.result b/mysql-test/main/lowercase_routines0.result new file mode 100644 index 00000000000..cafb7b25d3e --- /dev/null +++ b/mysql-test/main/lowercase_routines0.result @@ -0,0 +1,46 @@ +# +# MDEV-33616 A statement cannot reference two routines whose databases +# differ only in case +# +CREATE DATABASE Db1; +CREATE DATABASE db1; +CREATE FUNCTION Db1.f1() RETURNS INT RETURN 1; +CREATE FUNCTION db1.f1() RETURNS INT RETURN 2; +CREATE PROCEDURE test.p1() SELECT Db1.f1(), db1.f1(); +SELECT Db1.f1(); +Db1.f1() +1 +SELECT db1.f1(); +db1.f1() +2 +connect con1,localhost,root,,test; +SELECT Db1.f1(), db1.f1(); +Db1.f1() db1.f1() +1 2 +disconnect con1; +connect con2,localhost,root,,test; +SELECT db1.f1(), Db1.f1(); +db1.f1() Db1.f1() +2 1 +disconnect con2; +connect con3,localhost,root,,test; +CALL test.p1(); +Db1.f1() db1.f1() +1 2 +disconnect con3; +connect con4,localhost,root,,test; +SELECT db1.f1(); +db1.f1() +2 +connection default; +DROP FUNCTION db1.f1; +CREATE FUNCTION db1.f1() RETURNS INT RETURN 99; +connection con4; +SELECT Db1.f1(), db1.f1(); +Db1.f1() db1.f1() +1 99 +disconnect con4; +connection default; +DROP PROCEDURE test.p1; +DROP DATABASE Db1; +DROP DATABASE db1; diff --git a/mysql-test/main/lowercase_routines0.test b/mysql-test/main/lowercase_routines0.test new file mode 100644 index 00000000000..178f17dee27 --- /dev/null +++ b/mysql-test/main/lowercase_routines0.test @@ -0,0 +1,65 @@ +# +# Tests that require lower_case_table_names to be 0 +# (the default when the data directory is on a case sensitive file system) +# +--source include/have_lowercase0.inc +--source include/have_case_sensitive_file_system.inc + +--echo # +--echo # MDEV-33616 A statement cannot reference two routines whose databases +--echo # differ only in case +--echo # + +CREATE DATABASE Db1; +CREATE DATABASE db1; +CREATE FUNCTION Db1.f1() RETURNS INT RETURN 1; +CREATE FUNCTION db1.f1() RETURNS INT RETURN 2; +CREATE PROCEDURE test.p1() SELECT Db1.f1(), db1.f1(); + +SELECT Db1.f1(); +SELECT db1.f1(); + +# Every routine a statement names joins one set, keyed by the database and +# the routine name. The database name is case sensitive here, so the two +# routines have to stay apart and each reference has to reach its own. A +# connection that already holds one of them in its routine cache resolves +# both either way, so every check below starts from a connection that holds +# neither. +connect (con1,localhost,root,,test); +# A view compares its column names without regard to case, so under the +# view protocol the second column of a query like this one is given a +# generated name. +--disable_view_protocol +SELECT Db1.f1(), db1.f1(); +--enable_view_protocol +disconnect con1; + +connect (con2,localhost,root,,test); +--disable_view_protocol +SELECT db1.f1(), Db1.f1(); +--enable_view_protocol +disconnect con2; + +# A statement inside a routine body builds the same set. +connect (con3,localhost,root,,test); +CALL test.p1(); +disconnect con3; + +# A routine missing from the set is neither locked nor checked against the +# version of the routine cache, so it could execute a definition that +# another connection had already replaced. +connect (con4,localhost,root,,test); +SELECT db1.f1(); +connection default; +DROP FUNCTION db1.f1; +CREATE FUNCTION db1.f1() RETURNS INT RETURN 99; +connection con4; +--disable_view_protocol +SELECT Db1.f1(), db1.f1(); +--enable_view_protocol +disconnect con4; +connection default; + +DROP PROCEDURE test.p1; +DROP DATABASE Db1; +DROP DATABASE db1; diff --git a/sql/sp.cc b/sql/sp.cc index b5069603ed6..4d9c9e924c5 100644 --- a/sql/sp.cc +++ b/sql/sp.cc @@ -2346,7 +2346,15 @@ bool sp_add_used_routine(Query_tables_list *prelocking_ctx, Query_arena *arena, const Sp_handler *handler, TABLE_LIST *belong_to_view) { - my_hash_init_opt(PSI_INSTRUMENT_ME, &prelocking_ctx->sroutines, system_charset_info, + /* + Compare keys byte for byte. The key carries the database name as the + statement spelled it, and with lower_case_table_names 0 two databases + can differ only in case. A case insensitive comparison would collapse + the routines of both into one entry, so only one of them would be + loaded and locked, and a reference to the other would not find it. + */ + my_hash_init_opt(PSI_INSTRUMENT_ME, &prelocking_ctx->sroutines, + &my_charset_bin, Query_tables_list::START_SROUTINES_HASH_SIZE, 0, 0, sp_sroutine_key, 0, 0); diff --git a/sql/sp_head.cc b/sql/sp_head.cc index 98f302e2aa3..9dc02125dfe 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -604,7 +604,8 @@ sp_head::sp_head(MEM_ROOT *mem_root_arg, sp_package *parent, sizeof(sp_instr *), 16, 8, MYF(0)); my_hash_init(key_memory_sp_head_main_root, &m_sptabs, table_alias_charset, 0, 0, 0, sp_table_key, 0, 0); - my_hash_init(key_memory_sp_head_main_root, &m_sroutines, system_charset_info, + /* Keys are compared byte for byte, the database name is case sensitive. */ + my_hash_init(key_memory_sp_head_main_root, &m_sroutines, &my_charset_bin, 0, 0, 0, sp_sroutine_key, 0, 0); DBUG_VOID_RETURN; From 9b42c94b611ddb227556f363e8bd97d713a2d43b Mon Sep 17 00:00:00 2001 From: Dave Gosselin Date: Tue, 8 Sep 2026 12:05:04 -0400 Subject: [PATCH 34/71] MDEV-33616: Detect select() on macOS macOS declares select() in sys/select.h, which the HAVE_SELECT probe did not include. clang rejects a call to an undeclared function, so the probe failed and HAVE_SELECT was left undefined. my_sleep() then took its last fallback, a busy loop on time() that rounds the requested interval up to a whole second. Every sub-second sleep in the server became a one second spin on a CPU, which is what made rpl.rpl_perfschema_applier_status_by_worker, rpl.rpl_shutdown_sighup and rpl.rpl_semi_sync_shutdown_await_ack fail. --- configure.cmake | 2 ++ 1 file changed, 2 insertions(+) diff --git a/configure.cmake b/configure.cmake index 93b87f3a5dd..e4aee5448b4 100644 --- a/configure.cmake +++ b/configure.cmake @@ -590,6 +590,8 @@ CHECK_C_SOURCE_COMPILES(" #include #include #include +/* macOS declares select() here and nowhere else that this test includes */ +#include #endif int main() { From c43a8f06a167096f682b122a944244f2d11aaeb0 Mon Sep 17 00:00:00 2001 From: Dave Gosselin Date: Tue, 8 Sep 2026 12:05:04 -0400 Subject: [PATCH 35/71] MDEV-33616: Make two tests independent of lower_case_table_names macOS puts the data directory on a case insensitive file system, so lower_case_table_names is 2 and both tests recorded an answer that only holds for 0. period.i_s_notembedded looked up I_S.PERIODS and I_S.KEY_PERIOD_USAGE by the schema name TEST. That comparison follows the table name comparison, so it finds the table under 1 and 2 and finds nothing under 0. Those four queries move to the new test period.i_s_case_sensitive, which requires lower_case_table_names=0. The win rdiff of period.i_s_notembedded covered the same difference and is no longer needed. atomic.drop_db_long_names generated table and view names in upper case and compared the DROP statements that DDL recovery writes to the binary log. Under 2 the names come back from the directory in lower case. Generating them in lower case to begin with gives the same names on every setting. Lower case also changes where the view name sorts against its table name for the letters after v, which moves one view between two of the recorded DROP VIEW statements. --- .../suite/atomic/drop_db_long_names.result | 12 +++++----- .../suite/atomic/drop_db_long_names.test | 6 +++-- .../suite/period/r/i_s_case_sensitive.result | 14 ++++++++++++ .../suite/period/r/i_s_notembedded,win.rdiff | 20 ----------------- .../suite/period/r/i_s_notembedded.result | 11 ---------- .../suite/period/t/i_s_case_sensitive.test | 22 +++++++++++++++++++ .../suite/period/t/i_s_notembedded.test | 14 ++---------- 7 files changed, 48 insertions(+), 51 deletions(-) create mode 100644 mysql-test/suite/period/r/i_s_case_sensitive.result delete mode 100644 mysql-test/suite/period/r/i_s_notembedded,win.rdiff create mode 100644 mysql-test/suite/period/t/i_s_case_sensitive.test diff --git a/mysql-test/suite/atomic/drop_db_long_names.result b/mysql-test/suite/atomic/drop_db_long_names.result index e1d177ab447..ec1cc598260 100644 --- a/mysql-test/suite/atomic/drop_db_long_names.result +++ b/mysql-test/suite/atomic/drop_db_long_names.result @@ -1,11 +1,11 @@ RESET MASTER; "engine: aria crash point: ddl_log_drop_after_drop_tables position: 1" -master-bin.000002 # Query # # use `test2`; DROP TABLE IF EXISTS `tABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB`,`tACCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC`,`tADDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD`,`tAEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE`,`tAFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF`,`tAGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG`,`tAHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH`,`tAIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII`,`tAJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ`,`tAKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK`,`tALLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL` /* generated by ddl recovery */ -master-bin.000002 # Query # # use `test2`; DROP VIEW IF EXISTS `tABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBv`,`tACCCCCCCCCCCCCCCCCCCCCCCCCCCCCCv`,`tADDDDDDDDDDDDDDDDDDDDDDDDDDDDDDv`,`tAEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEv`,`tAFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFv`,`tAGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGv`,`tAHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHv`,`tAIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIv`,`tAJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJv`,`tAKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKv` /* generated by ddl recovery */ -master-bin.000002 # Query # # use `test2`; DROP TABLE IF EXISTS `tAMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM`,`tANNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN`,`tAOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO`,`tAPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP`,`tAQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ`,`tARRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR`,`tASSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS`,`tATTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT`,`tAUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU`,`tAVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV`,`tAWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW` /* generated by ddl recovery */ -master-bin.000002 # Query # # use `test2`; DROP VIEW IF EXISTS `tALLLLLLLLLLLLLLLLLLLLLLLLLLLLLLv`,`tAMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMv`,`tANNNNNNNNNNNNNNNNNNNNNNNNNNNNNNv`,`tAOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOv`,`tAPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPv`,`tAQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQv`,`tARRRRRRRRRRRRRRRRRRRRRRRRRRRRRRv`,`tASSSSSSSSSSSSSSSSSSSSSSSSSSSSSSv`,`tATTTTTTTTTTTTTTTTTTTTTTTTTTTTTTv`,`tAUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUv`,`tAVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVv` /* generated by ddl recovery */ -master-bin.000002 # Query # # use `test2`; DROP TABLE IF EXISTS `tAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX`,`tAYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY`,`tAZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ`,`tBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA`,`tBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB`,`tBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC`,`tBDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD`,`tBEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE` /* generated by ddl recovery */ -master-bin.000002 # Query # # use `test2`; DROP VIEW IF EXISTS `tAWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWv`,`tAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXv`,`tAYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYv`,`tAZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZv`,`tBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAv`,`tBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBv`,`tBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCv`,`tBDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDv`,`tBEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEv` /* generated by ddl recovery */ +master-bin.000002 # Query # # use `test2`; DROP TABLE IF EXISTS `tabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`,`tacccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc`,`tadddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd`,`taeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee`,`taffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff`,`tagggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg`,`tahhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh`,`taiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii`,`tajjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj`,`takkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk`,`tallllllllllllllllllllllllllllllllllllllllllllllllllllllllllll` /* generated by ddl recovery */ +master-bin.000002 # Query # # use `test2`; DROP VIEW IF EXISTS `tabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbv`,`taccccccccccccccccccccccccccccccv`,`taddddddddddddddddddddddddddddddv`,`taeeeeeeeeeeeeeeeeeeeeeeeeeeeeeev`,`taffffffffffffffffffffffffffffffv`,`taggggggggggggggggggggggggggggggv`,`tahhhhhhhhhhhhhhhhhhhhhhhhhhhhhhv`,`taiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiv`,`tajjjjjjjjjjjjjjjjjjjjjjjjjjjjjjv`,`takkkkkkkkkkkkkkkkkkkkkkkkkkkkkkv` /* generated by ddl recovery */ +master-bin.000002 # Query # # use `test2`; DROP TABLE IF EXISTS `tammmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm`,`tannnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn`,`taoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo`,`tapppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp`,`taqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq`,`tarrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr`,`tassssssssssssssssssssssssssssssssssssssssssssssssssssssssssss`,`tatttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt`,`tauuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu`,`tavvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv`,`tawwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww` /* generated by ddl recovery */ +master-bin.000002 # Query # # use `test2`; DROP VIEW IF EXISTS `tallllllllllllllllllllllllllllllv`,`tammmmmmmmmmmmmmmmmmmmmmmmmmmmmmv`,`tannnnnnnnnnnnnnnnnnnnnnnnnnnnnnv`,`taoooooooooooooooooooooooooooooov`,`tappppppppppppppppppppppppppppppv`,`taqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqv`,`tarrrrrrrrrrrrrrrrrrrrrrrrrrrrrrv`,`tassssssssssssssssssssssssssssssv`,`tattttttttttttttttttttttttttttttv`,`tauuuuuuuuuuuuuuuuuuuuuuuuuuuuuuv`,`tavvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv`,`tawwwwwwwwwwwwwwwwwwwwwwwwwwwwwwv` /* generated by ddl recovery */ +master-bin.000002 # Query # # use `test2`; DROP TABLE IF EXISTS `taxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`,`tayyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy`,`tazzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz`,`tbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`,`tbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`,`tbcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc`,`tbdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd`,`tbeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee` /* generated by ddl recovery */ +master-bin.000002 # Query # # use `test2`; DROP VIEW IF EXISTS `taxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxv`,`tayyyyyyyyyyyyyyyyyyyyyyyyyyyyyyv`,`tazzzzzzzzzzzzzzzzzzzzzzzzzzzzzzv`,`tbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaav`,`tbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbv`,`tbccccccccccccccccccccccccccccccv`,`tbddddddddddddddddddddddddddddddv`,`tbeeeeeeeeeeeeeeeeeeeeeeeeeeeeeev` /* generated by ddl recovery */ "engine: aria crash point: ddl_log_drop_before_binlog position: 1" master-bin.000002 # Query # # DROP DATABASE IF EXISTS `test2` /* generated by ddl recovery */ Warnings: diff --git a/mysql-test/suite/atomic/drop_db_long_names.test b/mysql-test/suite/atomic/drop_db_long_names.test index 1256a9dc92f..59914c41f3f 100644 --- a/mysql-test/suite/atomic/drop_db_long_names.test +++ b/mysql-test/suite/atomic/drop_db_long_names.test @@ -58,8 +58,10 @@ while ($e < $engine_count) while ($t < $max_tables) { inc $t; - let $name=`select concat("t",char(floor(65+$t/26)),repeat(char(65+mod($t,26)),60))`; - let $view=`select concat("t",char(floor(65+$t/26)),repeat(char(65+mod($t,26)),30),'v')`; + # Generate the names in lower case so that they are unchanged by a + # lower_case_table_names setting of 1 or 2. + let $name=`select concat("t",char(floor(ascii('a')+$t/26)),repeat(char(ascii('a')+mod($t,26)),60))`; + let $view=`select concat("t",char(floor(ascii('a')+$t/26)),repeat(char(ascii('a')+mod($t,26)),30),'v')`; --eval create table $name (a int not null) $extra_option --eval create view $view as select * from $name } diff --git a/mysql-test/suite/period/r/i_s_case_sensitive.result b/mysql-test/suite/period/r/i_s_case_sensitive.result new file mode 100644 index 00000000000..db678aec4d2 --- /dev/null +++ b/mysql-test/suite/period/r/i_s_case_sensitive.result @@ -0,0 +1,14 @@ +# MDEV-32503 Queries from KEY_PERIOD_USAGE don't obey case-sensitivity +create table t (a int, b date, c date, period for app(b,c), +unique idx(a, app without overlaps)); +set names latin1 collate latin1_general_cs; +select table_name from information_schema.periods where table_schema = 'TEST'; +table_name +select table_name from information_schema.key_period_usage where table_schema = 'TEST'; +table_name +set names latin1 collate latin1_general_ci; +select table_name from information_schema.periods where table_schema = 'TEST'; +table_name +select table_name from information_schema.key_period_usage where table_schema = 'TEST'; +table_name +drop table t; diff --git a/mysql-test/suite/period/r/i_s_notembedded,win.rdiff b/mysql-test/suite/period/r/i_s_notembedded,win.rdiff deleted file mode 100644 index eee35182c72..00000000000 --- a/mysql-test/suite/period/r/i_s_notembedded,win.rdiff +++ /dev/null @@ -1,20 +0,0 @@ ---- suite/period/r/i_s_notembedded.result 2024-01-01 19:50:37.000000000 +0100 -+++ suite/period/r/i_s_notembedded,win.reject 2024-01-01 19:57:18.888306500 +0100 -@@ -69,13 +69,17 @@ - set names latin1 collate latin1_general_cs; - select table_name from information_schema.periods where table_schema = 'TEST'; - table_name -+t - select table_name from information_schema.key_period_usage where table_schema = 'TEST'; - table_name -+t - set names latin1 collate latin1_general_ci; - select table_name from information_schema.periods where table_schema = 'TEST'; - table_name -+t - select table_name from information_schema.key_period_usage where table_schema = 'TEST'; - table_name -+t - # [DUPLICATE] MDEV-32504 Search by I_S.KEY_PERIOD_USAGE.CONSTRAINT_NAME - # does not work - select constraint_name from information_schema.key_period_usage where table_name = 't'; diff --git a/mysql-test/suite/period/r/i_s_notembedded.result b/mysql-test/suite/period/r/i_s_notembedded.result index 80070ef6116..8e92cf9fd60 100644 --- a/mysql-test/suite/period/r/i_s_notembedded.result +++ b/mysql-test/suite/period/r/i_s_notembedded.result @@ -63,19 +63,8 @@ def test t2 SYSTEM_TIME vs ve def test t2 mytime s e connection default; drop tables t1, t2; -# MDEV-32503 Queries from KEY_PERIOD_USAGE don't obey case-sensitivity create table t (a int, b date, c date, period for app(b,c), unique idx(a, app without overlaps)); -set names latin1 collate latin1_general_cs; -select table_name from information_schema.periods where table_schema = 'TEST'; -table_name -select table_name from information_schema.key_period_usage where table_schema = 'TEST'; -table_name -set names latin1 collate latin1_general_ci; -select table_name from information_schema.periods where table_schema = 'TEST'; -table_name -select table_name from information_schema.key_period_usage where table_schema = 'TEST'; -table_name # [DUPLICATE] MDEV-32504 Search by I_S.KEY_PERIOD_USAGE.CONSTRAINT_NAME # does not work select constraint_name from information_schema.key_period_usage where table_name = 't'; diff --git a/mysql-test/suite/period/t/i_s_case_sensitive.test b/mysql-test/suite/period/t/i_s_case_sensitive.test new file mode 100644 index 00000000000..639454b05a3 --- /dev/null +++ b/mysql-test/suite/period/t/i_s_case_sensitive.test @@ -0,0 +1,22 @@ +# +# Looking up I_S.PERIODS and I_S.KEY_PERIOD_USAGE by schema name compares the +# name the same way table names are compared, so the answer depends on +# lower_case_table_names. These cases record the case sensitive answer. +# +--source include/have_lowercase0.inc + +--echo # MDEV-32503 Queries from KEY_PERIOD_USAGE don't obey case-sensitivity + +create table t (a int, b date, c date, period for app(b,c), + unique idx(a, app without overlaps)); + +set names latin1 collate latin1_general_cs; + +select table_name from information_schema.periods where table_schema = 'TEST'; +select table_name from information_schema.key_period_usage where table_schema = 'TEST'; +set names latin1 collate latin1_general_ci; + +select table_name from information_schema.periods where table_schema = 'TEST'; +select table_name from information_schema.key_period_usage where table_schema = 'TEST'; + +drop table t; diff --git a/mysql-test/suite/period/t/i_s_notembedded.test b/mysql-test/suite/period/t/i_s_notembedded.test index 5bfb4e14e8b..065d3342926 100644 --- a/mysql-test/suite/period/t/i_s_notembedded.test +++ b/mysql-test/suite/period/t/i_s_notembedded.test @@ -1,5 +1,4 @@ --source include/not_embedded.inc ---source include/platform.inc select * from information_schema.periods; @@ -56,21 +55,12 @@ select * from information_schema.periods where table_schema = 'test'; --connection default drop tables t1, t2; ---echo # MDEV-32503 Queries from KEY_PERIOD_USAGE don't obey case-sensitivity +# The case sensitive lookups of MDEV-32503 need lower_case_table_names=0 and +# live in period.i_s_case_sensitive create table t (a int, b date, c date, period for app(b,c), unique idx(a, app without overlaps)); - -set names latin1 collate latin1_general_cs; - -select table_name from information_schema.periods where table_schema = 'TEST'; -select table_name from information_schema.key_period_usage where table_schema = 'TEST'; -set names latin1 collate latin1_general_ci; - -select table_name from information_schema.periods where table_schema = 'TEST'; -select table_name from information_schema.key_period_usage where table_schema = 'TEST'; - --echo # [DUPLICATE] MDEV-32504 Search by I_S.KEY_PERIOD_USAGE.CONSTRAINT_NAME --echo # does not work disable_warnings; # storage engine 'Innodb' is not found From 80b1a5c88d902d6dd1e0c15a8ae754546a3004ad Mon Sep 17 00:00:00 2001 From: Dave Gosselin Date: Tue, 8 Sep 2026 12:05:24 -0400 Subject: [PATCH 36/71] MDEV-33616: MTR flag to mark tests as incompatible with macOS Introduces a new MTR include, not_mac.inc, which when included at the top of a test, prevents that test from running on macOS. sys_vars.sysvars_readonly_debug is the first user. It expects the server to fault when a read only sysvar is written behind the sysvar interface. That protection needs the ro_after_init section, which a linker script places and ld64 has no option to take, so HAVE_RO_AFTER_INIT stays undefined on macOS. Without it no variable is moved into the read only root either, so neither of the two assignments is refused. --- mysql-test/include/not_mac.inc | 4 ++++ mysql-test/suite.pm | 1 + mysql-test/suite/sys_vars/t/sysvars_readonly_debug.test | 5 +++++ 3 files changed, 10 insertions(+) create mode 100644 mysql-test/include/not_mac.inc diff --git a/mysql-test/include/not_mac.inc b/mysql-test/include/not_mac.inc new file mode 100644 index 00000000000..3c4f2fbb037 --- /dev/null +++ b/mysql-test/include/not_mac.inc @@ -0,0 +1,4 @@ +# +# suite.pm will make sure that all tests including this file +# will be skipped if run under macOS +# diff --git a/mysql-test/suite.pm b/mysql-test/suite.pm index 97d406b2fc4..e2a1aa5550a 100644 --- a/mysql-test/suite.pm +++ b/mysql-test/suite.pm @@ -49,6 +49,7 @@ sub skip_combinations { $skip{'include/not_windows.inc'} = 'Requires not Windows' if IS_WINDOWS; $skip{'include/not_aix.inc'} = 'Requires not AIX' if IS_AIX; + $skip{'include/not_mac.inc'} = 'Requires not macOS' if IS_MAC; $skip{'include/not_ssl.inc'} = 'Skipped if --ssl' if $::opt_ssl; $skip{'main/plugin_loaderr.test'} = 'needs compiled-in innodb' diff --git a/mysql-test/suite/sys_vars/t/sysvars_readonly_debug.test b/mysql-test/suite/sys_vars/t/sysvars_readonly_debug.test index 192c481b4ce..3f71ed9b2ce 100644 --- a/mysql-test/suite/sys_vars/t/sysvars_readonly_debug.test +++ b/mysql-test/suite/sys_vars/t/sysvars_readonly_debug.test @@ -2,6 +2,11 @@ --source include/not_asan.inc --source include/not_embedded.inc --source include/not_valgrind.inc +# The read only segment is placed by a linker script and ld64 has no option to +# take one, so HAVE_RO_AFTER_INIT stays undefined on macOS. Without it no +# variable is moved into the read only root either, so neither assignment +# below is refused by the memory protection. +--source include/not_mac.inc --echo # --echo # MDEV-40341 store read-only sysvars in a read-only root and a read-only segment From 2471aa6dd701e09453d163c492c73968b0ff05d4 Mon Sep 17 00:00:00 2001 From: Dave Gosselin Date: Tue, 8 Sep 2026 12:05:24 -0400 Subject: [PATCH 37/71] MDEV-33616: Exclude innodb_log_file_mmap from sys_vars.sysvars_innodb Its default value depends on the operating system, ON where the log can be memory mapped and OFF elsewhere, so the recorded row only holds on some platforms. The other variables whose default depends on the operating system are already excluded the same way. --- .../sys_vars/r/sysvars_innodb,32bit.rdiff | 68 +++++++++---------- .../suite/sys_vars/r/sysvars_innodb.result | 13 +--- .../suite/sys_vars/t/sysvars_innodb.test | 1 + 3 files changed, 36 insertions(+), 46 deletions(-) diff --git a/mysql-test/suite/sys_vars/r/sysvars_innodb,32bit.rdiff b/mysql-test/suite/sys_vars/r/sysvars_innodb,32bit.rdiff index 513bc5e90e2..f0dcefe8037 100644 --- a/mysql-test/suite/sys_vars/r/sysvars_innodb,32bit.rdiff +++ b/mysql-test/suite/sys_vars/r/sysvars_innodb,32bit.rdiff @@ -1,6 +1,6 @@ --- sysvars_innodb.result +++ sysvars_innodb.result,32bit -@@ -47,7 +47,7 @@ +@@ -48,7 +48,7 @@ SESSION_VALUE NULL DEFAULT_VALUE 8 VARIABLE_SCOPE GLOBAL @@ -9,7 +9,7 @@ VARIABLE_COMMENT Number of InnoDB Adaptive Hash Index Partitions (default 8) NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 512 -@@ -83,7 +83,7 @@ +@@ -84,7 +84,7 @@ SESSION_VALUE NULL DEFAULT_VALUE 1 VARIABLE_SCOPE GLOBAL @@ -18,7 +18,7 @@ VARIABLE_COMMENT The AUTOINC lock modes supported by InnoDB: 0 => Old style AUTOINC locking (for backward compatibility); 1 => New style AUTOINC locking; 2 => No AUTOINC locking (unsafe for SBR) NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 2 -@@ -95,10 +95,10 @@ +@@ -96,10 +96,10 @@ SESSION_VALUE NULL DEFAULT_VALUE 0 VARIABLE_SCOPE GLOBAL @@ -31,7 +31,7 @@ NUMERIC_BLOCK_SIZE 1048576 ENUM_VALUE_LIST NULL READ_ONLY YES -@@ -131,7 +131,7 @@ +@@ -132,7 +132,7 @@ SESSION_VALUE NULL DEFAULT_VALUE 25 VARIABLE_SCOPE GLOBAL @@ -40,7 +40,7 @@ VARIABLE_COMMENT Dump only the hottest N% of each buffer pool, defaults to 25 NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 100 -@@ -203,10 +203,10 @@ +@@ -204,10 +204,10 @@ SESSION_VALUE NULL DEFAULT_VALUE 134217728 VARIABLE_SCOPE GLOBAL @@ -53,7 +53,7 @@ NUMERIC_BLOCK_SIZE 1048576 ENUM_VALUE_LIST NULL READ_ONLY NO -@@ -215,11 +215,11 @@ +@@ -216,11 +216,11 @@ SESSION_VALUE NULL DEFAULT_VALUE 0 VARIABLE_SCOPE GLOBAL @@ -68,7 +68,7 @@ ENUM_VALUE_LIST NULL READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED -@@ -239,7 +239,7 @@ +@@ -240,7 +240,7 @@ SESSION_VALUE NULL DEFAULT_VALUE 0 VARIABLE_SCOPE GLOBAL @@ -77,7 +77,7 @@ VARIABLE_COMMENT A number between [0, 100] that tells how oftern buffer pool dump status in percentages should be printed. E.g. 10 means that buffer pool dump status is printed when every 10% of number of buffer pool pages are dumped. Default is 0 (only start and end status is printed). NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 100 -@@ -359,7 +359,7 @@ +@@ -360,7 +360,7 @@ SESSION_VALUE NULL DEFAULT_VALUE 5 VARIABLE_SCOPE GLOBAL @@ -86,7 +86,7 @@ VARIABLE_COMMENT If the compression failure rate of a table is greater than this number more padding is added to the pages to reduce the failures. A value of zero implies no padding NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 100 -@@ -383,7 +383,7 @@ +@@ -384,7 +384,7 @@ SESSION_VALUE NULL DEFAULT_VALUE 50 VARIABLE_SCOPE GLOBAL @@ -95,7 +95,7 @@ VARIABLE_COMMENT Percentage of empty space on a data page that can be reserved to make the page compressible. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 75 -@@ -671,7 +671,7 @@ +@@ -672,7 +672,7 @@ SESSION_VALUE NULL DEFAULT_VALUE 600 VARIABLE_SCOPE GLOBAL @@ -104,7 +104,7 @@ VARIABLE_COMMENT Maximum number of seconds that semaphore times out in InnoDB. NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 4294967295 -@@ -719,7 +719,7 @@ +@@ -720,7 +720,7 @@ SESSION_VALUE NULL DEFAULT_VALUE 30 VARIABLE_SCOPE GLOBAL @@ -113,7 +113,7 @@ VARIABLE_COMMENT Number of iterations over which the background flushing is averaged. NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 1000 -@@ -743,7 +743,7 @@ +@@ -744,7 +744,7 @@ SESSION_VALUE NULL DEFAULT_VALUE 1 VARIABLE_SCOPE GLOBAL @@ -122,7 +122,7 @@ VARIABLE_COMMENT Controls the durability/speed trade-off for commits. Set to 0 (write and flush redo log to disk only once per second), 1 (flush to disk at each commit), 2 (write to log at commit but flush to disk only once per second) or 3 (flush to disk at prepare and at commit, slower and usually redundant). 1 and 3 guarantees that after a crash, committed transactions will not be lost and will be consistent with the binlog and other transactional engines. 2 can get inconsistent and lose transactions if there is a power failure or kernel crash but not if mysqld crashes. 0 has no guarantees in case of crash. 0 and 2 can be faster than 1 or 3. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 3 -@@ -767,7 +767,7 @@ +@@ -768,7 +768,7 @@ SESSION_VALUE NULL DEFAULT_VALUE 1 VARIABLE_SCOPE GLOBAL @@ -131,7 +131,7 @@ VARIABLE_COMMENT Set to 0 (don't flush neighbors from buffer pool), 1 (flush contiguous neighbors from buffer pool) or 2 (flush neighbors from buffer pool), when flushing a block NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 2 -@@ -803,7 +803,7 @@ +@@ -804,7 +804,7 @@ SESSION_VALUE NULL DEFAULT_VALUE 0 VARIABLE_SCOPE GLOBAL @@ -140,7 +140,7 @@ VARIABLE_COMMENT Helps to save your data in case the disk image of the database becomes corrupt. Value 5 can return bogus data, and 6 can permanently corrupt data. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 6 -@@ -827,10 +827,10 @@ +@@ -828,10 +828,10 @@ SESSION_VALUE NULL DEFAULT_VALUE 8000000 VARIABLE_SCOPE GLOBAL @@ -153,7 +153,7 @@ NUMERIC_BLOCK_SIZE 0 ENUM_VALUE_LIST NULL READ_ONLY NO -@@ -863,7 +863,7 @@ +@@ -864,7 +864,7 @@ SESSION_VALUE NULL DEFAULT_VALUE 84 VARIABLE_SCOPE GLOBAL @@ -162,7 +162,7 @@ VARIABLE_COMMENT InnoDB Fulltext search maximum token size in characters NUMERIC_MIN_VALUE 10 NUMERIC_MAX_VALUE 84 -@@ -875,7 +875,7 @@ +@@ -876,7 +876,7 @@ SESSION_VALUE NULL DEFAULT_VALUE 3 VARIABLE_SCOPE GLOBAL @@ -171,7 +171,7 @@ VARIABLE_COMMENT InnoDB Fulltext search minimum token size in characters NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 16 -@@ -887,7 +887,7 @@ +@@ -888,7 +888,7 @@ SESSION_VALUE NULL DEFAULT_VALUE 2000 VARIABLE_SCOPE GLOBAL @@ -180,7 +180,7 @@ VARIABLE_COMMENT InnoDB Fulltext search number of words to optimize for each optimize table call NUMERIC_MIN_VALUE 1000 NUMERIC_MAX_VALUE 10000 -@@ -899,10 +899,10 @@ +@@ -900,10 +900,10 @@ SESSION_VALUE NULL DEFAULT_VALUE 2000000000 VARIABLE_SCOPE GLOBAL @@ -193,7 +193,7 @@ NUMERIC_BLOCK_SIZE 0 ENUM_VALUE_LIST NULL READ_ONLY NO -@@ -923,7 +923,7 @@ +@@ -924,7 +924,7 @@ SESSION_VALUE NULL DEFAULT_VALUE 2 VARIABLE_SCOPE GLOBAL @@ -202,7 +202,7 @@ VARIABLE_COMMENT InnoDB Fulltext search parallel sort degree, will round up to nearest power of 2 number NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 16 -@@ -935,10 +935,10 @@ +@@ -936,10 +936,10 @@ SESSION_VALUE NULL DEFAULT_VALUE 640000000 VARIABLE_SCOPE GLOBAL @@ -215,7 +215,7 @@ NUMERIC_BLOCK_SIZE 0 ENUM_VALUE_LIST NULL READ_ONLY NO -@@ -983,7 +983,7 @@ +@@ -972,7 +972,7 @@ SESSION_VALUE NULL DEFAULT_VALUE 200 VARIABLE_SCOPE GLOBAL @@ -224,7 +224,7 @@ VARIABLE_COMMENT Number of IOPs the server can do. Tunes the background IO rate NUMERIC_MIN_VALUE 100 NUMERIC_MAX_VALUE 4294967295 -@@ -995,7 +995,7 @@ +@@ -984,7 +984,7 @@ SESSION_VALUE NULL DEFAULT_VALUE 4294967295 VARIABLE_SCOPE GLOBAL @@ -233,7 +233,7 @@ VARIABLE_COMMENT Limit to which innodb_io_capacity can be inflated. NUMERIC_MIN_VALUE 100 NUMERIC_MAX_VALUE 4294967295 -@@ -1115,10 +1115,10 @@ +@@ -1104,10 +1104,10 @@ SESSION_VALUE NULL DEFAULT_VALUE 32 VARIABLE_SCOPE GLOBAL @@ -246,7 +246,7 @@ NUMERIC_BLOCK_SIZE 0 ENUM_VALUE_LIST NULL READ_ONLY NO -@@ -1127,10 +1127,10 @@ +@@ -1116,10 +1116,10 @@ SESSION_VALUE NULL DEFAULT_VALUE 1536 VARIABLE_SCOPE GLOBAL @@ -259,7 +259,7 @@ NUMERIC_BLOCK_SIZE 0 ENUM_VALUE_LIST NULL READ_ONLY NO -@@ -1163,10 +1163,10 @@ +@@ -1152,10 +1152,10 @@ SESSION_VALUE NULL DEFAULT_VALUE 0 VARIABLE_SCOPE GLOBAL @@ -272,7 +272,7 @@ NUMERIC_BLOCK_SIZE 0 ENUM_VALUE_LIST NULL READ_ONLY NO -@@ -1175,7 +1175,7 @@ +@@ -1164,7 +1164,7 @@ SESSION_VALUE NULL DEFAULT_VALUE 0 VARIABLE_SCOPE GLOBAL @@ -281,7 +281,7 @@ VARIABLE_COMMENT Maximum delay of user threads in micro-seconds NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 10000000 -@@ -1307,10 +1307,10 @@ +@@ -1296,10 +1296,10 @@ SESSION_VALUE NULL DEFAULT_VALUE 0 VARIABLE_SCOPE GLOBAL @@ -294,7 +294,7 @@ NUMERIC_BLOCK_SIZE 0 ENUM_VALUE_LIST NULL READ_ONLY YES -@@ -1331,7 +1331,7 @@ +@@ -1320,7 +1320,7 @@ SESSION_VALUE NULL DEFAULT_VALUE 16384 VARIABLE_SCOPE GLOBAL @@ -303,7 +303,7 @@ VARIABLE_COMMENT Page size to use for all InnoDB tablespaces. NUMERIC_MIN_VALUE 4096 NUMERIC_MAX_VALUE 65536 -@@ -1367,7 +1367,7 @@ +@@ -1356,7 +1356,7 @@ SESSION_VALUE NULL DEFAULT_VALUE 127 VARIABLE_SCOPE GLOBAL @@ -312,7 +312,7 @@ VARIABLE_COMMENT Number of UNDO log pages to purge in one batch from the history list. NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 5000 -@@ -1379,7 +1379,7 @@ +@@ -1368,7 +1368,7 @@ SESSION_VALUE NULL DEFAULT_VALUE 128 VARIABLE_SCOPE GLOBAL @@ -321,7 +321,7 @@ VARIABLE_COMMENT Deprecated parameter with no effect NUMERIC_MIN_VALUE 1 NUMERIC_MAX_VALUE 128 -@@ -1415,7 +1415,7 @@ +@@ -1404,7 +1404,7 @@ SESSION_VALUE NULL DEFAULT_VALUE 56 VARIABLE_SCOPE GLOBAL @@ -330,7 +330,7 @@ VARIABLE_COMMENT Number of pages that must be accessed sequentially for InnoDB to trigger a readahead. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 64 -@@ -1499,7 +1499,7 @@ +@@ -1488,7 +1488,7 @@ SESSION_VALUE NULL DEFAULT_VALUE 1048576 VARIABLE_SCOPE GLOBAL @@ -339,7 +339,7 @@ VARIABLE_COMMENT Memory buffer size for index creation NUMERIC_MIN_VALUE 65536 NUMERIC_MAX_VALUE 67108864 -@@ -1667,10 +1667,10 @@ +@@ -1656,10 +1656,10 @@ SESSION_VALUE NULL DEFAULT_VALUE 30 VARIABLE_SCOPE GLOBAL diff --git a/mysql-test/suite/sys_vars/r/sysvars_innodb.result b/mysql-test/suite/sys_vars/r/sysvars_innodb.result index cad0893798f..0b5b7eab058 100644 --- a/mysql-test/suite/sys_vars/r/sysvars_innodb.result +++ b/mysql-test/suite/sys_vars/r/sysvars_innodb.result @@ -7,6 +7,7 @@ variable_name not in ( 'innodb_buffer_pool_size_max', # default value depends on OS 'innodb_buffer_pool_in_core_dump', # only available on Linux and FreeBSD 'innodb_log_file_buffering', # only available on Linux and Windows +'innodb_log_file_mmap', # default value depends on OS 'innodb_linux_aio', # existence depends on OS 'innodb_buffer_pool_load_pages_abort') # debug build only, and is only for testing order by variable_name; @@ -946,18 +947,6 @@ NUMERIC_BLOCK_SIZE NULL ENUM_VALUE_LIST OFF,ON READ_ONLY NO COMMAND_LINE_ARGUMENT OPTIONAL -VARIABLE_NAME INNODB_LOG_FILE_MMAP -SESSION_VALUE NULL -DEFAULT_VALUE ON -VARIABLE_SCOPE GLOBAL -VARIABLE_TYPE BOOLEAN -VARIABLE_COMMENT Whether ib_logfile0 resides in persistent memory (when supported) or should initially be memory-mapped -NUMERIC_MIN_VALUE NULL -NUMERIC_MAX_VALUE NULL -NUMERIC_BLOCK_SIZE NULL -ENUM_VALUE_LIST OFF,ON -READ_ONLY YES -COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME INNODB_LOG_FILE_SIZE SESSION_VALUE NULL DEFAULT_VALUE 100663296 diff --git a/mysql-test/suite/sys_vars/t/sysvars_innodb.test b/mysql-test/suite/sys_vars/t/sysvars_innodb.test index 7cda33d9fb6..219d39be915 100644 --- a/mysql-test/suite/sys_vars/t/sysvars_innodb.test +++ b/mysql-test/suite/sys_vars/t/sysvars_innodb.test @@ -18,6 +18,7 @@ select VARIABLE_NAME, SESSION_VALUE, DEFAULT_VALUE, VARIABLE_SCOPE, VARIABLE_TYP 'innodb_buffer_pool_size_max', # default value depends on OS 'innodb_buffer_pool_in_core_dump', # only available on Linux and FreeBSD 'innodb_log_file_buffering', # only available on Linux and Windows + 'innodb_log_file_mmap', # default value depends on OS 'innodb_linux_aio', # existence depends on OS 'innodb_buffer_pool_load_pages_abort') # debug build only, and is only for testing order by variable_name; From 2184fcfa803fadfa2d2fd83a558b1225c37bd1ff Mon Sep 17 00:00:00 2001 From: Dave Gosselin Date: Tue, 8 Sep 2026 12:05:24 -0400 Subject: [PATCH 38/71] MDEV-33616: Widen the block count filter in the buffer pool resize test The test replaces the number of buffer pool blocks with a fixed value so that the message is stable. The pattern only accepted 5.., and macOS builds without a futex use SUX_LOCK_GENERIC, which enlarges buf_block_t enough to bring the count down into 4... --- .../suite/innodb/t/innodb_buffer_pool_resize_temporary.test | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mysql-test/suite/innodb/t/innodb_buffer_pool_resize_temporary.test b/mysql-test/suite/innodb/t/innodb_buffer_pool_resize_temporary.test index f96f89c39d5..8ca34b294d1 100644 --- a/mysql-test/suite/innodb/t/innodb_buffer_pool_resize_temporary.test +++ b/mysql-test/suite/innodb/t/innodb_buffer_pool_resize_temporary.test @@ -35,8 +35,8 @@ SET DEBUG_SYNC='buf_pool_shrink_before_wakeup SIGNAL blocked WAIT_FOR go'; send SET GLOBAL innodb_buffer_pool_size=8388608; connection default; SET DEBUG_SYNC='now WAIT_FOR blocked'; -# adjust for 32-bit and SUX_LOCK_GENERIC ---replace_regex /(5..)\/\1/505\/505/ +# IA-32: 504 blocks; IA-32 glibc SUX_LOCK_GENERIC: 500; macOS SUX_LOCK_GENERIC: 498 +--replace_regex /([45]..)\/\1/505\/505/ SHOW STATUS LIKE 'innodb_buffer_pool_resize_status'; SET DEBUG_SYNC='now SIGNAL go'; connection con1; From 4e0fb28a02d16184b0ba4cfdf25269e9d0f8d5ae Mon Sep 17 00:00:00 2001 From: Dave Gosselin Date: Tue, 8 Sep 2026 12:06:38 -0400 Subject: [PATCH 39/71] MDEV-33616: Normalize the strerror text in innodb_fts.index_table The injected deadlock reaches the client as ER_GET_ERRNO carrying errno 11, and the text comes from my_strerror(). 11 is EAGAIN on Linux and EDEADLK on macOS, so the message reads "Resource temporarily unavailable" on one and "Resource deadlock avoided" on the other. Replace the quoted text so the test does not depend on it. --- mysql-test/suite/innodb_fts/t/index_table.test | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mysql-test/suite/innodb_fts/t/index_table.test b/mysql-test/suite/innodb_fts/t/index_table.test index 89c09053230..3ad193fd04a 100644 --- a/mysql-test/suite/innodb_fts/t/index_table.test +++ b/mysql-test/suite/innodb_fts/t/index_table.test @@ -17,6 +17,9 @@ CREATE TABLE articles ( content TEXT ) ENGINE= InnoDB; +# The error number is reported through my_strerror(), whose text for 11 +# differs between systems, so normalize it +--replace_regex /".*" from/"Resource temporarily unavailable" from/ --error ER_GET_ERRNO SET STATEMENT debug_dbug='+d,innodb_report_deadlock' FOR CREATE FULLTEXT INDEX idx ON articles (title, content); From 8ec4dbcf51a6424524b6bd3ff3abf91d1ba46f64 Mon Sep 17 00:00:00 2001 From: Dave Gosselin Date: Tue, 8 Sep 2026 12:06:38 -0400 Subject: [PATCH 40/71] MDEV-33616: Match the macOS dlopen error in plugins.multiauth The client reports why it could not load client_ed25519, and macOS names every path that dlopen() tried. Two expressions are added, one for the chunk that holds the start of that message and one for the chunk that holds the rest of it. The line runs to 563 bytes, 52 of prefix and the 511 that the client error buffer holds, while do_exec() reads the output with fgets() into a 512 byte buffer and runs the replacements on each chunk on its own. A long enough vardir therefore splits the line, because the path appears four times in the dlopen text. The second chunk is the tail of a path and carries no colon, where the first chunk keeps the colons of the mysqltest prefix. That chunk also holds the only line terminator the error line gets, so the expression captures the newline and the replacement puts it back. A replacement is inserted as written, so a \n spelled there would reach the output as a backslash and an n. Both expressions stop at a newline. reg_replace compiles with REG_DOTALL, so an unrestricted .* runs past the line terminator whenever the whole message reaches the replacement in one chunk, and the error line then joins the line after it. --- mysql-test/suite/plugins/t/multiauth.test | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/mysql-test/suite/plugins/t/multiauth.test b/mysql-test/suite/plugins/t/multiauth.test index fa902ab3b2c..2b51044fab7 100644 --- a/mysql-test/suite/plugins/t/multiauth.test +++ b/mysql-test/suite/plugins/t/multiauth.test @@ -206,8 +206,17 @@ create user mysqltest1 identified via ed25519 as password("good"); grant select on test.* to mysqltest1; show create user mysqltest1; --echo # no plugin = failure -# covers Linux (1st re), FreeBSD (2nd), AIX (3rd and 4th) ---replace_regex /loaded: .*client_ed25519.so: cannot open shared object file: No such file or directory/loaded: no such file/ /loaded: Cannot open.*client_ed25519.so./loaded: no such file/ /loaded: .*Could not load module.*client_ed25519.so.\n/loaded: no such file/ /System error: No such file or directory// +# covers Linux (1st re), FreeBSD (2nd), AIX (3rd and 4th), macOS (5th and 6th). +# The macOS text names every path that dlopen() tried, so with a long vardir +# the line outgrows the 512 byte buffer that --exec output is read in and +# arrives as two chunks, each matched on its own. The 5th expression rewrites +# the chunk that starts the message. The 6th drops the chunk that continues +# it, which is the tail of a path and carries no colon. The line terminator +# that chunk holds comes back through the back reference, since a replacement +# is inserted as it is written. Both stop at a newline because the expressions +# are compiled with REG_DOTALL, where an unrestricted .* would swallow the +# terminator and join this line to the next one. +--replace_regex /loaded: .*client_ed25519.so: cannot open shared object file: No such file or directory/loaded: no such file/ /loaded: Cannot open.*client_ed25519.so./loaded: no such file/ /loaded: .*Could not load module.*client_ed25519.so.\n/loaded: no such file/ /System error: No such file or directory// /loaded: dlopen\([^\n]*/loaded: no such file/ /^[^:\n]*(\n)/\1/ --error 1 --exec $try_auth -u mysqltest1 -pgood --plugin-dir=$plugindir/no alter user mysqltest1 identified via ed25519 as password("good") OR mysql_native_password as password("works"); From fedce76ceaa327191ee64c9e87e59fdca09e5723 Mon Sep 17 00:00:00 2001 From: Dave Gosselin Date: Fri, 18 Sep 2026 09:26:19 -0400 Subject: [PATCH 41/71] MDEV-33616: Allocate the recovery buffer from the heap recv_sys.tmp_buf comes from malloc() rather than from the large page allocator. main.large_pages fails on macOS with "Warning: Memory not freed: 16375" at shutdown. recv_sys_t::find_checkpoint() asks for 1048585 bytes, my_large_malloc() rounds that up to 1064960 and charges the rounded figure to the server memory accounting, and recv_sys_t::tmp_free() credits back the 1048585 that was requested. ut_malloc_dontdump() takes the size by value, so it has nowhere to report what my_large_malloc() wrote back. The rounding happens whenever my_next_large_page_size() finds a reported large page size at or below the request. macOS has no huge page interface for my_get_large_page_sizes() to consult, so its fallback branch reports the ordinary page size, 16384 on Apple silicon, and the request is always rounded. Linux reads the sizes from /sys/kernel/mm/hugepages, where the smallest entry is usually 2 MiB, and a 1 MiB request then gets no large page and no rounding. The buffer has no alignment requirement. recv_sys_t::parse() copies a mini-transaction into it when the record is encrypted in the FORMAT_ENC_11 log, where it is then decrypted in place, or when the record wraps around the end of the log file, and reads it back as a byte sequence. tmp_free() calls std::free() because the member function recv_sys_t::free() hides the one from . log_sys.buf and log_sys.flush_buf keep the large page allocator. They round the same way, so a server started with --large-pages --innodb-log-buffer-size=2101248 still reports 24576 on macOS. The core dump exclusion that recv_sys.tmp_buf gives up applies only where MADV_DONTDUMP exists, so nothing changes on macOS, while a release build on Linux would now include the buffer in a dump. tmp_free() overwrites the redo log records that innodb_encrypt_log decrypted before releasing the memory, through a volatile function pointer because GCC removes a plain memset() that is followed by free(). Co-Authored-By: Claude Opus 5 (1M context) --- storage/innobase/log/log0recv.cc | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/storage/innobase/log/log0recv.cc b/storage/innobase/log/log0recv.cc index 28e2763d7fe..e309b627dbf 100644 --- a/storage/innobase/log/log0recv.cc +++ b/storage/innobase/log/log0recv.cc @@ -26,6 +26,7 @@ Created 9/20/1997 Heikki Tuuri #include "univ.i" +#include #include #include #include @@ -1478,11 +1479,15 @@ void recv_sys_t::clear() void recv_sys_t::tmp_free() noexcept { - if (tmp_buf) - { - ut_free_dodump(tmp_buf, tmp_buf_size); - tmp_buf= nullptr; - } + if (!tmp_buf) + return; + + /* GCC deletes a plain memset() that is followed by free(). */ + static void *(*const volatile erase)(void*, int, size_t)= memset; + erase(tmp_buf, 0, tmp_buf_size); + /* The member function recv_sys_t::free() hides the one from . */ + std::free(tmp_buf); + tmp_buf= nullptr; } /** Free most recovery data structures. */ @@ -1869,8 +1874,7 @@ dberr_t recv_sys_t::find_checkpoint() if (!tmp_buf) { - tmp_buf= static_cast - (ut_malloc_dontdump(tmp_buf_size, PSI_INSTRUMENT_ME)); + tmp_buf= static_cast(malloc(tmp_buf_size)); if (!tmp_buf) return DB_OUT_OF_MEMORY; } From e0a80ad6bcf600492a52ba8bac43e290d08384cf Mon Sep 17 00:00:00 2001 From: Dave Gosselin Date: Wed, 9 Sep 2026 08:25:33 -0400 Subject: [PATCH 42/71] MDEV-33616: Skip the redo log upgrade tests without sparse file support innodb.log_upgrade and innodb.log_upgrade_101_flags build 8GB redo log files by seeking past the end of an empty file and writing a single byte. That needs a filesystem which leaves the skipped range unallocated. HFS on macOS allocates every block of it instead, so the write fails with ENOSPC and the test reports a perl failure. include/have_sparse_files.inc probes a directory the caller names, writing one byte 64MB into an empty file there and comparing the allocated block count against that offset. --- mysql-test/include/have_sparse_files.inc | 55 +++++++++++++++++++ mysql-test/suite/innodb/t/log_upgrade.test | 11 ++-- .../suite/innodb/t/log_upgrade_101_flags.test | 3 +- 3 files changed, 64 insertions(+), 5 deletions(-) create mode 100644 mysql-test/include/have_sparse_files.inc diff --git a/mysql-test/include/have_sparse_files.inc b/mysql-test/include/have_sparse_files.inc new file mode 100644 index 00000000000..939899afc4a --- /dev/null +++ b/mysql-test/include/have_sparse_files.inc @@ -0,0 +1,55 @@ +# Skip the test unless the filesystem holding $sparse_file_dir stores files +# with holes. The caller sets $sparse_file_dir to a directory on the +# filesystem where it builds its own sparse file, because vardir and datadir +# can be on different filesystems. +# +# A test that builds a large file by seeking past its end and writing a +# single byte relies on the skipped range staying unallocated. HFS on +# macOS allocates every block of that range, so the file consumes its full +# apparent size and the write fails with ENOSPC once the range exceeds the +# free space on the volume. +# +# The probe writes one byte 64MB past the start of an empty file and compares +# the allocated block count against that offset. A filesystem that leaves a +# hole allocates only the block holding the byte. The offset has to stay well +# above 16MB, since APFS fills the range of a file smaller than that rather +# than recording a hole. + +if (!$sparse_file_dir) +{ + --die have_sparse_files.inc requires the caller to set sparse_file_dir +} +# The probe cannot tell a directory it may not write from a filesystem +# without holes, thus a missing directory ends the test rather than skipping +# it. +--file_exists $sparse_file_dir +# mysqltest keeps variables in a case insensitive hash and exports a value +# under whichever spelling created the entry, so the exported name has to +# differ from $sparse_file_dir by more than case. +--let SPARSE_PROBE_DIR= $sparse_file_dir + +--error 0,1 +perl; +use strict; +use warnings; + +my $probe= "$ENV{SPARSE_PROBE_DIR}/sparse_probe"; +my $offset= 64 * 1024 * 1024; +my $sparse= 0; + +if (open(my $fh, '>', $probe)) +{ + binmode $fh; + if (seek($fh, $offset, 0) and print($fh chr(0)) and close($fh)) + { + my $blocks= (stat $probe)[12]; + $sparse= 1 if defined($blocks) and $blocks * 512 < $offset; + } +} +unlink $probe; +exit($sparse ? 0 : 1); +EOF +if ($errno) +{ + --skip Requires a filesystem that stores files with holes +} diff --git a/mysql-test/suite/innodb/t/log_upgrade.test b/mysql-test/suite/innodb/t/log_upgrade.test index a3d237875fe..efb531206c5 100644 --- a/mysql-test/suite/innodb/t/log_upgrade.test +++ b/mysql-test/suite/innodb/t/log_upgrade.test @@ -1,11 +1,14 @@ --source include/have_innodb.inc --source include/have_innodb_16k.inc -# Some operating systems or file systems do not support sparse files. -# For example, tmpfs on FreeBSD does not support them. -# On Microsoft Windows, sparse files have to be created in a special way. ---source include/big_test.inc # include/shutdown_mysqld.inc does not work in ./mtr --embedded --source include/not_embedded.inc +# Some operating systems or file systems do not support sparse files. +# For example, tmpfs on FreeBSD does not support them. +# On macOS, HFS allocates every block of the skipped range. APFS allocates +# the whole range for a file smaller than 16MB, and records a hole above that. +# On Microsoft Windows, sparse files have to be created in a special way. +--let $sparse_file_dir= $MYSQLTEST_VARDIR/tmp +--source include/have_sparse_files.inc call mtr.add_suppression("InnoDB: The change buffer is corrupted"); diff --git a/mysql-test/suite/innodb/t/log_upgrade_101_flags.test b/mysql-test/suite/innodb/t/log_upgrade_101_flags.test index 7b19986f73e..2c7c01cca7e 100644 --- a/mysql-test/suite/innodb/t/log_upgrade_101_flags.test +++ b/mysql-test/suite/innodb/t/log_upgrade_101_flags.test @@ -1,6 +1,7 @@ --source include/have_innodb.inc ---source include/big_test.inc --source include/not_embedded.inc +--let $sparse_file_dir= $MYSQLTEST_VARDIR/tmp +--source include/have_sparse_files.inc call mtr.add_suppression("InnoDB: The change buffer is corrupted"); call mtr.add_suppression("InnoDB: Tablespace size stored in header is 768 pages, but the sum of data file sizes is 384 pages"); call mtr.add_suppression("InnoDB: adjusting FSP_SPACE_FLAGS of file"); From eabcbedf16c4c6435ee37607dd6bea40a2865c3f Mon Sep 17 00:00:00 2001 From: Dave Gosselin Date: Mon, 14 Sep 2026 16:37:45 -0400 Subject: [PATCH 43/71] MDEV-33616: Take the read lock many times in perfschema.func_mutex The wait timer can have a granularity coarser than the time an uncontended read lock is held, so the recorded duration of one lock can be zero, which reads back as NULL. This can cause the test to fail with a false negative. Take the lock twenty more times at each measurement point, with the extra statements silent so the recorded result does not change. The mutex part of the test already works this way, since one SELECT produces ten THR_LOCK::mutex events. Co-Authored-By: Claude Opus 5 (1M context) --- mysql-test/suite/perfschema/t/func_mutex.test | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/mysql-test/suite/perfschema/t/func_mutex.test b/mysql-test/suite/perfschema/t/func_mutex.test index 1964852134d..9aee54b7ccc 100644 --- a/mysql-test/suite/perfschema/t/func_mutex.test +++ b/mysql-test/suite/perfschema/t/func_mutex.test @@ -90,12 +90,39 @@ TRUNCATE TABLE performance_schema.events_waits_current; show variables like "%not_found%"; +# The wait timer can have a granularity coarser than the time an +# uncontended read lock is held, so the recorded duration of one lock can +# be zero, which reads back as NULL. Take the lock repeatedly at each +# measurement point so that at least one duration is greater than zero. +# The extra statements are silent to leave the recorded result unchanged. +--disable_query_log +--disable_result_log +let $i= 20; +while ($i) +{ + show variables like "%not_found%"; + dec $i; +} +--enable_result_log +--enable_query_log + SET @before_count = (SELECT SUM(TIMER_WAIT) FROM performance_schema.events_waits_history_long WHERE (EVENT_NAME = 'wait/synch/rwlock/sql/LOCK_system_variables_hash')); show variables like "%not_found%"; +--disable_query_log +--disable_result_log +let $i= 20; +while ($i) +{ + show variables like "%not_found%"; + dec $i; +} +--enable_result_log +--enable_query_log + SET @after_count = (SELECT SUM(TIMER_WAIT) FROM performance_schema.events_waits_history_long WHERE (EVENT_NAME = 'wait/synch/rwlock/sql/LOCK_system_variables_hash')); From 30ee95c7cc730564400b3c6461f8938ee77e3342 Mon Sep 17 00:00:00 2001 From: Oleksandr Byelkin Date: Mon, 21 Sep 2026 17:23:48 +0200 Subject: [PATCH 44/71] MDEV-41094 KDF() aliases large iteration/width to weak 32-bit values KDF() narrowed its iteration-count and key-width arguments without checking they fit, so values differing by 2^32 aliased to the same small value and silently derived a much weaker key. Added checks that the key width fits a 16-bit unsigned value and the iteration count fits a 32-bit value before narrowing them, rejecting out-of-range values with an error instead of aliasing. --- mysql-test/main/func_kdf.result | 39 ++++++++++++++++++++++++++++++ mysql-test/main/func_kdf.test | 16 +++++++++++++ sql/item_strfunc.cc | 42 +++++++++++++++++++-------------- 3 files changed, 79 insertions(+), 18 deletions(-) diff --git a/mysql-test/main/func_kdf.result b/mysql-test/main/func_kdf.result index e334125c6d5..93d83b251bc 100644 --- a/mysql-test/main/func_kdf.result +++ b/mysql-test/main/func_kdf.result @@ -181,5 +181,44 @@ SELECT KDF('secret','salt') IS NOT NULL; KDF('secret','salt') IS NOT NULL 1 # +# MDEV-41094 KDF() silently reduces large PBKDF2 iteration counts +# and key widths to weak 32-bit aliases +# +SET @@block_encryption_mode=DEFAULT; +SELECT HEX(KDF('foo', 'bar', 1000, 'pbkdf2_hmac')); +HEX(KDF('foo', 'bar', 1000, 'pbkdf2_hmac')) +76BA6DEC5C3F6A60704D730A2A4BAA1C +SELECT KDF('foo', 'bar', 4294968296, 'pbkdf2_hmac'); +KDF('foo', 'bar', 4294968296, 'pbkdf2_hmac') +NULL +Warnings: +Warning 3047 Invalid argument error: 4294968296 in function kdf. +SELECT KDF('foo', 'bar', 2147483648, 'pbkdf2_hmac'); +KDF('foo', 'bar', 2147483648, 'pbkdf2_hmac') +NULL +Warnings: +Warning 3047 Invalid argument error: 2147483648 in function kdf. +SELECT HEX(KDF('foo', 'bar', 1000, 'pbkdf2_hmac', 256)); +HEX(KDF('foo', 'bar', 1000, 'pbkdf2_hmac', 256)) +76BA6DEC5C3F6A60704D730A2A4BAA1C596F278AE35408EE5D67ECE0942C14F4 +SELECT KDF('foo', 'bar', 1000, 'pbkdf2_hmac', 4294967552); +KDF('foo', 'bar', 1000, 'pbkdf2_hmac', 4294967552) +NULL +Warnings: +Warning 3047 Invalid argument error: 4294967552 in function kdf. +SELECT KDF('foo', 'bar', 1000, 'pbkdf2_hmac', -4294967040); +KDF('foo', 'bar', 1000, 'pbkdf2_hmac', -4294967040) +NULL +Warnings: +Warning 3047 Invalid argument error: -4294967040 in function kdf. +CREATE TABLE t1 (w BIGINT); +INSERT INTO t1 VALUES (4294967552); +SELECT KDF('foo', 'bar', 1000, 'pbkdf2_hmac', w) FROM t1; +KDF('foo', 'bar', 1000, 'pbkdf2_hmac', w) +NULL +Warnings: +Warning 3047 Invalid argument error: 4294967552 in function kdf. +DROP TABLE t1; +# # End of 11.4 tests # diff --git a/mysql-test/main/func_kdf.test b/mysql-test/main/func_kdf.test index 279323ede59..40a8ec81117 100644 --- a/mysql-test/main/func_kdf.test +++ b/mysql-test/main/func_kdf.test @@ -72,6 +72,22 @@ SELECT KDF('secret','salt',1000,999); SELECT KDF('secret','salt',1000,'pbkdf2_hmac') IS NOT NULL; SELECT KDF('secret','salt') IS NOT NULL; +--echo # +--echo # MDEV-41094 KDF() silently reduces large PBKDF2 iteration counts +--echo # and key widths to weak 32-bit aliases +--echo # +SET @@block_encryption_mode=DEFAULT; +SELECT HEX(KDF('foo', 'bar', 1000, 'pbkdf2_hmac')); +SELECT KDF('foo', 'bar', 4294968296, 'pbkdf2_hmac'); +SELECT KDF('foo', 'bar', 2147483648, 'pbkdf2_hmac'); +SELECT HEX(KDF('foo', 'bar', 1000, 'pbkdf2_hmac', 256)); +SELECT KDF('foo', 'bar', 1000, 'pbkdf2_hmac', 4294967552); +SELECT KDF('foo', 'bar', 1000, 'pbkdf2_hmac', -4294967040); +CREATE TABLE t1 (w BIGINT); +INSERT INTO t1 VALUES (4294967552); +SELECT KDF('foo', 'bar', 1000, 'pbkdf2_hmac', w) FROM t1; +DROP TABLE t1; + --echo # --echo # End of 11.4 tests --echo # diff --git a/sql/item_strfunc.cc b/sql/item_strfunc.cc index 34e479889db..1135f4a6dea 100644 --- a/sql/item_strfunc.cc +++ b/sql/item_strfunc.cc @@ -482,8 +482,11 @@ bool Item_func_kdf::fix_length_and_dec(THD *thd) { if (arg_count > 4 && args[4]->const_item()) { - if (((key_length= (uint)args[4]->val_int()) % 8) || key_length > 65536) + longlong width= args[4]->val_int(); + if (width <= 0 || width > UINT_MAX16 + 1 || width % 8) key_length= 0; + else + key_length= uint(width); } else if (arg_count <= 4) key_length= block_encryption_mode_to_key_length(thd->variables.block_encryption_mode); @@ -513,6 +516,7 @@ String *Item_func_kdf::val_str(String *buf) bool use_hkdf= false; size_t klen= key_length; bool ok= false; + longlong iter= 0; if (!key || !salt) goto ret_null; @@ -534,16 +538,28 @@ String *Item_func_kdf::val_str(String *buf) } if (!klen) { - klen= args[4]->val_int(); - if (!klen || klen % 8 || klen > 65536) + longlong width= args[4]->val_int(); + if (width <= 0 || width > UINT_MAX16 + 1 || width % 8) { if (!args[4]->null_value) invalid_argument_error(func_name(), - ErrConvInteger({(ssize_t)klen, args[4]->unsigned_flag}).ptr()); + ErrConvInteger({width, args[4]->unsigned_flag}).ptr()); goto ret_null; } - klen/= 8; + klen= size_t(width) / 8; } + if (!use_hkdf) + { + iter= arg_count > 2 ? args[2]->val_int() : 1000; + if (iter <= 0 || iter > INT_MAX32) + { + if (!args[2]->null_value) + invalid_argument_error(func_name(), + ErrConvInteger({iter, args[2]->unsigned_flag}).ptr()); + goto ret_null; + } + } + buf->reserve(klen); buf->length(klen); @@ -570,19 +586,9 @@ String *Item_func_kdf::val_str(String *buf) #endif } else - { - longlong iter= arg_count > 2 ? args[2]->val_int() : 1000; - if (iter <= 0) - { - if (!args[2]->null_value) - invalid_argument_error(func_name(), - ErrConvInteger({iter, args[2]->unsigned_flag}).ptr()); - } - else - ok= PKCS5_PBKDF2_HMAC(key->ptr(), key->length(), - (const uchar*)salt->ptr(), salt->length(), (int)iter, - EVP_sha512(), (int)klen, (uchar*)buf->ptr()); - } + ok= PKCS5_PBKDF2_HMAC(key->ptr(), key->length(), + (const uchar*)salt->ptr(), salt->length(), (int)iter, + EVP_sha512(), (int)klen, (uchar*)buf->ptr()); if (ok) { From d49a7da399c707842f6e2690f5afbb378c4b1585 Mon Sep 17 00:00:00 2001 From: Sergei Petrunia Date: Tue, 22 Sep 2026 14:47:40 +0300 Subject: [PATCH 45/71] Add comment about Create_tmp_table::m_group --- sql/create_tmp_table.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sql/create_tmp_table.h b/sql/create_tmp_table.h index ce86c9456e4..63ba3b042fe 100644 --- a/sql/create_tmp_table.h +++ b/sql/create_tmp_table.h @@ -32,6 +32,10 @@ protected: uint m_alloced_field_count; bool m_using_unique_constraint; uint m_temp_pool_slot; + /* + If set, create a unique index over listed columns. Used by GROUP BY's + TemporaryTableWithPartialSums algorithm and also by multi-table UPDATE. + */ ORDER *m_group; bool m_distinct; bool m_save_sum_fields; From 511d7527eb356aab52fa183456333c5ba9741bcf Mon Sep 17 00:00:00 2001 From: Dave Gosselin Date: Mon, 6 Jul 2026 14:41:40 -0400 Subject: [PATCH 46/71] MDEV-28509: Dereferenced null pointer of type 'struct JOIN_TAB' in add_key_field setup_group no longer writes Item::marker. The ONLY_FULL_GROUP_BY check now tests GROUP BY membership by walking the GROUP BY list. A query that defines a WINDOW but never refers to it could crash in add_key_field, for example WITH cte AS (SELECT i FROM (SELECT i FROM t1 GROUP BY i) dt WINDOW w AS (PARTITION BY i)) SELECT a.i FROM cte a JOIN cte b ON a.i=b.i WHERE a.i != 5; A query that defines a WINDOW goes through setup_group, which set marker to MARKER_UNDEF_POS (-1) on each GROUP BY expression so that the ONLY_FULL_GROUP_BY check could skip it. Other code reads marker as a set of flag bits (-1 sets all bits). Item_direct_view_ref::grouping_field_transformer_for_where then took the ref as flagged for substitution and followed a path that ends in the crash. The ONLY_FULL_GROUP_BY check was the only reader of that value, so MARKER_UNDEF_POS is removed. The necessary check is local to the setup_group function. Co-Authored-By: Claude Opus 5.5 --- mysql-test/main/win.result | 31 +++++++++++++++++++ mysql-test/main/win.test | 27 ++++++++++++++++ .../encryption/r/tempfiles_encrypted.result | 31 +++++++++++++++++++ sql/item.h | 2 -- sql/sql_select.cc | 18 +++++++++-- 5 files changed, 105 insertions(+), 4 deletions(-) diff --git a/mysql-test/main/win.result b/mysql-test/main/win.result index 4b72d1813ac..78f39de208c 100644 --- a/mysql-test/main/win.result +++ b/mysql-test/main/win.result @@ -4832,5 +4832,36 @@ c1 c2 2 2 DROP TABLE t1; # +# MDEV-28509: Access null pointer during add_key_field call +# +CREATE TABLE t1 (i INT); +INSERT INTO t1 VALUES (1),(5),(7),(1); +WITH cte AS (SELECT i FROM (SELECT i FROM t1 GROUP BY i) dt WINDOW w AS (PARTITION BY i)) +SELECT a.i FROM cte a JOIN cte b ON a.i=b.i WHERE a.i != 5; +i +1 +7 +# The same crash occurs when the WHERE uses < instead of !=. +WITH cte AS (SELECT i FROM (SELECT i FROM t1 GROUP BY i) dt WINDOW w AS (PARTITION BY i)) +SELECT a.i FROM cte a JOIN cte b ON a.i=b.i WHERE a.i < 5; +i +1 +SET @save_sql_mode= @@sql_mode; +SET sql_mode= ONLY_FULL_GROUP_BY; +WITH cte AS (SELECT i FROM (SELECT i FROM t1 GROUP BY i) dt WINDOW w AS (PARTITION BY i)) +SELECT a.i FROM cte a JOIN cte b ON a.i=b.i WHERE a.i != 5; +i +1 +7 +WITH cte AS (SELECT i FROM (SELECT i FROM t1 GROUP BY i) dt WINDOW w AS (PARTITION BY i)) +SELECT a.i FROM cte a JOIN cte b ON a.i=b.i WHERE a.i < 5; +i +1 +# An unreferenced WINDOW does not exempt b.i from ONLY_FULL_GROUP_BY. +SELECT b.i FROM t1 a, t1 b GROUP BY a.i WINDOW w AS (PARTITION BY a.i); +ERROR 42000: 'test.b.i' isn't in GROUP BY +SET @@sql_mode= @save_sql_mode; +DROP TABLE t1; +# # End of 10.11 tests # diff --git a/mysql-test/main/win.test b/mysql-test/main/win.test index 5ba2a328c56..dd40e61d312 100644 --- a/mysql-test/main/win.test +++ b/mysql-test/main/win.test @@ -3120,6 +3120,33 @@ from t1; DROP TABLE t1; +--echo # +--echo # MDEV-28509: Access null pointer during add_key_field call +--echo # +CREATE TABLE t1 (i INT); +INSERT INTO t1 VALUES (1),(5),(7),(1); + +WITH cte AS (SELECT i FROM (SELECT i FROM t1 GROUP BY i) dt WINDOW w AS (PARTITION BY i)) +SELECT a.i FROM cte a JOIN cte b ON a.i=b.i WHERE a.i != 5; + +--echo # The same crash occurs when the WHERE uses < instead of !=. +WITH cte AS (SELECT i FROM (SELECT i FROM t1 GROUP BY i) dt WINDOW w AS (PARTITION BY i)) +SELECT a.i FROM cte a JOIN cte b ON a.i=b.i WHERE a.i < 5; + +SET @save_sql_mode= @@sql_mode; +SET sql_mode= ONLY_FULL_GROUP_BY; +WITH cte AS (SELECT i FROM (SELECT i FROM t1 GROUP BY i) dt WINDOW w AS (PARTITION BY i)) +SELECT a.i FROM cte a JOIN cte b ON a.i=b.i WHERE a.i != 5; +WITH cte AS (SELECT i FROM (SELECT i FROM t1 GROUP BY i) dt WINDOW w AS (PARTITION BY i)) +SELECT a.i FROM cte a JOIN cte b ON a.i=b.i WHERE a.i < 5; + +--echo # An unreferenced WINDOW does not exempt b.i from ONLY_FULL_GROUP_BY. +--error ER_WRONG_FIELD_WITH_GROUP +SELECT b.i FROM t1 a, t1 b GROUP BY a.i WINDOW w AS (PARTITION BY a.i); + +SET @@sql_mode= @save_sql_mode; +DROP TABLE t1; + --echo # --echo # End of 10.11 tests --echo # diff --git a/mysql-test/suite/encryption/r/tempfiles_encrypted.result b/mysql-test/suite/encryption/r/tempfiles_encrypted.result index e844c6bc688..7131fdd42c2 100644 --- a/mysql-test/suite/encryption/r/tempfiles_encrypted.result +++ b/mysql-test/suite/encryption/r/tempfiles_encrypted.result @@ -4838,6 +4838,37 @@ c1 c2 2 2 DROP TABLE t1; # +# MDEV-28509: Access null pointer during add_key_field call +# +CREATE TABLE t1 (i INT); +INSERT INTO t1 VALUES (1),(5),(7),(1); +WITH cte AS (SELECT i FROM (SELECT i FROM t1 GROUP BY i) dt WINDOW w AS (PARTITION BY i)) +SELECT a.i FROM cte a JOIN cte b ON a.i=b.i WHERE a.i != 5; +i +1 +7 +# The same crash occurs when the WHERE uses < instead of !=. +WITH cte AS (SELECT i FROM (SELECT i FROM t1 GROUP BY i) dt WINDOW w AS (PARTITION BY i)) +SELECT a.i FROM cte a JOIN cte b ON a.i=b.i WHERE a.i < 5; +i +1 +SET @save_sql_mode= @@sql_mode; +SET sql_mode= ONLY_FULL_GROUP_BY; +WITH cte AS (SELECT i FROM (SELECT i FROM t1 GROUP BY i) dt WINDOW w AS (PARTITION BY i)) +SELECT a.i FROM cte a JOIN cte b ON a.i=b.i WHERE a.i != 5; +i +1 +7 +WITH cte AS (SELECT i FROM (SELECT i FROM t1 GROUP BY i) dt WINDOW w AS (PARTITION BY i)) +SELECT a.i FROM cte a JOIN cte b ON a.i=b.i WHERE a.i < 5; +i +1 +# An unreferenced WINDOW does not exempt b.i from ONLY_FULL_GROUP_BY. +SELECT b.i FROM t1 a, t1 b GROUP BY a.i WINDOW w AS (PARTITION BY a.i); +ERROR 42000: 'test.b.i' isn't in GROUP BY +SET @@sql_mode= @save_sql_mode; +DROP TABLE t1; +# # End of 10.11 tests # # diff --git a/sql/item.h b/sql/item.h index 5889c295f90..3f319e90e36 100644 --- a/sql/item.h +++ b/sql/item.h @@ -158,8 +158,6 @@ bool mark_unsupported_function(const char *w1, const char *w2, Note that for Item_fields, the marker contains 'select->cur_pos_in_select_list */ -/* Used to check GROUP BY list in the MODE_ONLY_FULL_GROUP_BY mode */ -#define MARKER_UNDEF_POS -1 #define MARKER_UNUSED 0 #define MARKER_CHANGE_COND 1 #define MARKER_PROCESSED 2 diff --git a/sql/sql_select.cc b/sql/sql_select.cc index e6ba61fbc3e..b1ebbf744c8 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -28944,6 +28944,21 @@ int setup_order(THD *thd, Ref_ptr_array ref_pointer_array, TABLE_LIST *tables, } +/* + Is item one of the expressions of the GROUP BY list? find_order_in_list() + has already pointed every ORDER entry at its select list expression; direct + comparison is sufficient. +*/ + +static bool item_in_group_list(ORDER *order, Item *item) +{ + for (ORDER *ord= order; ord; ord= ord->next) + if (*ord->item == item) + return true; + return false; +} + + /** Intitialize the GROUP BY list. @@ -28992,7 +29007,6 @@ setup_group(THD *thd, Ref_ptr_array ref_pointer_array, TABLE_LIST *tables, if (find_order_in_list(thd, ref_pointer_array, tables, ord, fields, all_fields, true, true, from_window_spec)) return 1; - (*ord->item)->marker= MARKER_UNDEF_POS; /* Mark found */ if ((*ord->item)->with_sum_func() && context_analysis_place == IN_GROUP_BY) { my_error(ER_WRONG_GROUP_FIELD, MYF(0), (*ord->item)->full_name()); @@ -29042,7 +29056,7 @@ setup_group(THD *thd, Ref_ptr_array ref_pointer_array, TABLE_LIST *tables, while (field && (item=li++)) { if (item->type() != Item::SUM_FUNC_ITEM && - item->marker != MARKER_UNDEF_POS && + !item_in_group_list(order, item) && !item->const_item() && !(item->real_item()->type() == Item::FIELD_ITEM && item->used_tables() & OUTER_REF_TABLE_BIT)) From 56becfbe195b212b7a51d6dec9f00d49e408f2d2 Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Sun, 13 Sep 2026 20:36:24 +0200 Subject: [PATCH 47/71] fix the build for -G "Ninja Multi-Config" --- cmake/mysql_add_executable.cmake | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/cmake/mysql_add_executable.cmake b/cmake/mysql_add_executable.cmake index f548534a056..b38537dd017 100644 --- a/cmake/mysql_add_executable.cmake +++ b/cmake/mysql_add_executable.cmake @@ -98,14 +98,11 @@ FUNCTION (MYSQL_ADD_EXECUTABLE) IF(link) IF(UNIX) ADD_CUSTOM_COMMAND(TARGET ${target} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E create_symlink - ${target} ${link} + COMMAND ${CMAKE_COMMAND} -E create_symlink ${target} ${link} COMMENT "Creating ${link} link" - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}) - INSTALL(PROGRAMS - ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${link} - DESTINATION - ${ARG_DESTINATION} + WORKING_DIRECTORY $/) + INSTALL(PROGRAMS $/${link} + DESTINATION ${ARG_DESTINATION} COMPONENT ${COMP}Symlinks) ELSE() # Windows note: From 382da0f62a4f53975ce63b9fe96740908e1f7d38 Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Wed, 16 Sep 2026 14:22:24 +0200 Subject: [PATCH 48/71] fix errmsg-utf8.txt dependencies for Ninja generator GenError's custom command must specify headers as OUTPUT, otherwise ninja cannot deduce that mysqld.cc depends on errmsg-utf8.txt As a bonus, BYPRODUCTS lists generated files for `ninja clean` --- extra/CMakeLists.txt | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/extra/CMakeLists.txt b/extra/CMakeLists.txt index e6db43fec62..d0c68e4a6df 100644 --- a/extra/CMakeLists.txt +++ b/extra/CMakeLists.txt @@ -1,4 +1,5 @@ # Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2026, MariaDB plc # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -28,7 +29,16 @@ ENDIF() # mysqld_error.h results in rebuild of the almost whole server) # To preserve timestamp, first generate a temp header file, then copy it # to mysqld_error.h using cmake -E copy_if_different -ADD_CUSTOM_COMMAND(OUTPUT ${CMAKE_BINARY_DIR}/include/mysqld_error.h.tmp +SET(GENERATED_ERROR_HEADERS + ${CMAKE_BINARY_DIR}/include/mysqld_error.h + ${CMAKE_BINARY_DIR}/include/mysqld_ername.h + ${CMAKE_BINARY_DIR}/include/sql_state.h) + +ADD_CUSTOM_COMMAND(OUTPUT ${GENERATED_ERROR_HEADERS} + BYPRODUCTS + ${CMAKE_BINARY_DIR}/include/mysqld_error.h.tmp + ${CMAKE_BINARY_DIR}/include/mysqld_ername.h.tmp + ${CMAKE_BINARY_DIR}/include/sql_state.h.tmp COMMAND comp_err --charset=${PROJECT_SOURCE_DIR}/sql/share/charsets --out-dir=${CMAKE_BINARY_DIR}/sql/share/ @@ -44,7 +54,7 @@ ADD_CUSTOM_COMMAND(OUTPUT ${CMAKE_BINARY_DIR}/include/mysqld_error.h.tmp ADD_CUSTOM_TARGET(GenError ALL DEPENDS - ${CMAKE_BINARY_DIR}/include/mysqld_error.h.tmp) + ${GENERATED_ERROR_HEADERS}) MYSQL_ADD_EXECUTABLE(my_print_defaults my_print_defaults.c COMPONENT Client) TARGET_LINK_LIBRARIES(my_print_defaults mysys) From 80c8e3be0b79048cb9903fe2071bcd1f9ed79001 Mon Sep 17 00:00:00 2001 From: Vladislav Vaintroub Date: Sat, 5 Sep 2026 00:44:06 +0200 Subject: [PATCH 49/71] build mysqlservices without an embedded CRT requirement mysqlservices only exposes a thin C API, no CRT state crosses it, so don't force whatever CRT/config built the server onto a plugin linking it. Without /Zl, a plugin built in a config with no matching installed mysqlservices variant (CMake silently substitutes one - verified with a toy project) gets an ignorable but noisy LNK4098 warning. Assisted-by: Claude:claude-5-sonnet --- libservices/CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libservices/CMakeLists.txt b/libservices/CMakeLists.txt index 37c24646791..8142fdcc49a 100644 --- a/libservices/CMakeLists.txt +++ b/libservices/CMakeLists.txt @@ -48,4 +48,9 @@ SET(MYSQLSERVICES_SOURCES ) ADD_CONVENIENCE_LIBRARY(mysqlservices ${MYSQLSERVICES_SOURCES}) +IF(MSVC) + # thin C API, no CRT state crosses it - don't force a CRT match on plugins + TARGET_COMPILE_OPTIONS(mysqlservices PRIVATE /Zl) +ENDIF() + INSTALL(TARGETS mysqlservices DESTINATION ${INSTALL_LIBDIR} COMPONENT Development) From 3977a282b74c780aedef2390828a7e611a751cd5 Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Tue, 4 Aug 2026 16:04:35 +0200 Subject: [PATCH 50/71] MDEV-40608 MariaDB-devel is incomplete for plugins This works on Linux and on Windows, with rpm/deb/tar.gz/zip installations. For rpm/deb it just works, for tar.gz/zip there is no standard location, so one needs to configure plugin with -DCMAKE_PREFIX_PATH=/pah/to/mariadb/basedir after that, `cmake --install .` works too, installing in the same basedir. `cmake --build . --target package` works, creating rpm/deb/targz/zip depending on whether it's Linux or Windows and whether -DRPM or -DDEB was specified. * create and install mariadb-plugin-config.cmake * for now it only supports one plugin per project, error out if there are many * deb: move all headers that plugins need to libmariadb-dev, together with libmysqlservices.a. At least until we'll create mariadb-plugin-dev. Nobody should need huge libmariadbd-dev to develop a plugin * rpm: all in MariaDB-devel already, no changes here * install wsrep headers too, THD layout depends on WITH_WSREP * show DBUG_OFF, ENABLED_DEBUG_SYNC, and SAFE_MUTEX to plugins, same reason (it doesn't happen automatically as they're not in my_config.h) * but don't install config.h - high chance of name conflict with other projects and it's an exact copy of my_config.h anyway. * adjust plugin.cmake to work for external plugins * move server-internal part of it to top-level CMakeLists.txt * remove double-defined macros from unireg.h (the guard doesn't help if unireg.h is included first) * package plugin metadata as yaml in .tar.gz/.zip ColumnStore, until fixed, needs a backward-compatibility workaround --- .gitignore | 2 + CMakeLists.txt | 15 +- cmake/install_macros.cmake | 33 ----- cmake/plugin.cmake | 132 +++++++++++------ debian/libmariadb-dev.install | 9 +- debian/libmariadbd-dev.install | 1 - include/CMakeLists.txt | 2 +- libservices/CMakeLists.txt | 3 +- sql/CMakeLists.txt | 15 +- sql/unireg.h | 9 -- storage/columnstore/CMakeLists.txt | 1 + support-files/CMakeLists.txt | 18 ++- support-files/mariadb-plugin-config.cmake.in | 140 +++++++++++++++++++ 13 files changed, 286 insertions(+), 94 deletions(-) create mode 100644 support-files/mariadb-plugin-config.cmake.in diff --git a/.gitignore b/.gitignore index 11a6c4600d7..9e4c4cdb3e9 100644 --- a/.gitignore +++ b/.gitignore @@ -264,6 +264,8 @@ support-files/mariadb-extra.socket support-files/mariadb@.service support-files/mariadb@.socket support-files/mariadb-extra@.socket +support-files/mariadb-plugin-config.cmake +support-files/mariadb-plugin-defines.cmake support-files/mini-benchmark support-files/my-huge.cnf support-files/my-innodb-heavy-4G.cnf diff --git a/CMakeLists.txt b/CMakeLists.txt index 3f68ec93a13..49b2cfaa57f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ # Copyright (c) 2006, 2017, Oracle and/or its affiliates. -# Copyright (c) 2008, 2022, MariaDB Corporation. +# Copyright (c) 2008, 2026, MariaDB Corporation. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -493,7 +493,18 @@ SET(PYTHON_SHEBANG "/usr/bin/env python3" CACHE STRING "python shebang") MARK_AS_ADVANCED(PYTHON_SHEBANG) # Add storage engines and plugins. -CONFIGURE_PLUGINS() +IF(NOT WITHOUT_SERVER) + FILE(GLOB dirs_storage ${CMAKE_SOURCE_DIR}/storage/*) +ENDIF() + +FILE(GLOB dirs_plugin ${CMAKE_SOURCE_DIR}/plugin/*) +FOREACH(dir ${dirs_storage} ${dirs_plugin}) + IF (EXISTS ${dir}/CMakeLists.txt) + ADD_SUBDIRECTORY(${dir}) + ENDIF() +ENDFOREACH() + +VERIFY_PLUGINS() ADD_SUBDIRECTORY(include) ADD_SUBDIRECTORY(dbug) diff --git a/cmake/install_macros.cmake b/cmake/install_macros.cmake index 282800a3285..557f0d693d7 100644 --- a/cmake/install_macros.cmake +++ b/cmake/install_macros.cmake @@ -300,36 +300,3 @@ ENDFUNCTION() # For Makefile generators we default Debug build directory to ${buildroot}/../debug. GET_FILENAME_COMPONENT(BINARY_PARENTDIR ${CMAKE_BINARY_DIR} PATH) SET(DEBUGBUILDDIR "${BINARY_PARENTDIR}/debug" CACHE INTERNAL "Directory of debug build") - -FUNCTION(INSTALL_MYSQL_TEST from to) - IF(INSTALL_MYSQLTESTDIR) - IF(NOT WITH_WSREP) - SET(EXCL_GALERA "(suite/(galera|wsrep|sys_vars/[rt]/(sysvars_)?wsrep).*|std_data/(galera|wsrep).*)") - ELSE() - SET(EXCL_GALERA "^DOES_NOT_EXIST$") - ENDIF() - INSTALL( - DIRECTORY ${from} - DESTINATION "${INSTALL_MYSQLTESTDIR}/${to}" - USE_SOURCE_PERMISSIONS - COMPONENT Test - PATTERN "var" EXCLUDE - PATTERN "lib/My/SafeProcess" EXCLUDE - PATTERN "lib/t*" EXCLUDE - PATTERN "CPack" EXCLUDE - PATTERN "CMake*" EXCLUDE - PATTERN "cmake_install.cmake" EXCLUDE - PATTERN "mtr.out*" EXCLUDE - PATTERN ".cvsignore" EXCLUDE - PATTERN "*.am" EXCLUDE - PATTERN "*.in" EXCLUDE - PATTERN "Makefile" EXCLUDE - PATTERN "*.vcxproj" EXCLUDE - PATTERN "*.vcxproj.filters" EXCLUDE - PATTERN "*.vcxproj.user" EXCLUDE - PATTERN "CTest*" EXCLUDE - PATTERN "*~" EXCLUDE - REGEX "${EXCL_GALERA}" EXCLUDE - ) - ENDIF() -ENDFUNCTION() diff --git a/cmake/plugin.cmake b/cmake/plugin.cmake index a0deacbcb57..32ca521b289 100644 --- a/cmake/plugin.cmake +++ b/cmake/plugin.cmake @@ -14,10 +14,46 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA +IF(NOT EXTERNAL_PLUGIN) + STRING(CONCAT PARENT_SCOPE PARENT_SCOPE) # cannot use SET here +ENDIF() + +FUNCTION(INSTALL_MYSQL_TEST from to) + IF(INSTALL_MYSQLTESTDIR) + IF(NOT WITH_WSREP) + SET(EXCL_GALERA "(suite/(galera|wsrep|sys_vars/[rt]/(sysvars_)?wsrep).*|std_data/(galera|wsrep).*)") + ELSE() + SET(EXCL_GALERA "^DOES_NOT_EXIST$") + ENDIF() + INSTALL( + DIRECTORY ${from} + DESTINATION "${INSTALL_MYSQLTESTDIR}/${to}" + USE_SOURCE_PERMISSIONS + COMPONENT Test + PATTERN "var" EXCLUDE + PATTERN "lib/My/SafeProcess" EXCLUDE + PATTERN "lib/t*" EXCLUDE + PATTERN "CPack" EXCLUDE + PATTERN "CMake*" EXCLUDE + PATTERN "cmake_install.cmake" EXCLUDE + PATTERN "mtr.out*" EXCLUDE + PATTERN ".cvsignore" EXCLUDE + PATTERN "*.am" EXCLUDE + PATTERN "*.in" EXCLUDE + PATTERN "Makefile" EXCLUDE + PATTERN "*.vcxproj" EXCLUDE + PATTERN "*.vcxproj.filters" EXCLUDE + PATTERN "*.vcxproj.user" EXCLUDE + PATTERN "CTest*" EXCLUDE + PATTERN "*~" EXCLUDE + REGEX "${EXCL_GALERA}" EXCLUDE + ) + ENDIF() +ENDFUNCTION() INCLUDE(CMakeParseArguments) -# MYSQL_ADD_PLUGIN(plugin_name source1...sourceN +# MARIADB_ADD_PLUGIN(plugin_name source1...sourceN # [STORAGE_ENGINE] # [STATIC_ONLY|MODULE_ONLY] # [MANDATORY|DEFAULT] @@ -32,10 +68,10 @@ INCLUDE(CMakeParseArguments) # [LINK_LIBRARIES lib1...libN] # [DEPENDS target1...targetN] -MACRO(MYSQL_ADD_PLUGIN) +MACRO(MARIADB_ADD_PLUGIN) CMAKE_PARSE_ARGUMENTS(ARG "STORAGE_ENGINE;STATIC_ONLY;MODULE_ONLY;MANDATORY;DEFAULT;DISABLED;NOT_EMBEDDED;RECOMPILE_FOR_EMBEDDED;CLIENT" - "MODULE_OUTPUT_NAME;STATIC_OUTPUT_NAME;COMPONENT;CONFIG;VERSION" + "MODULE_OUTPUT_NAME;STATIC_OUTPUT_NAME;COMPONENT;CONFIG;VERSION;AUTHOR;DESCRIPTION" "LINK_LIBRARIES;DEPENDS" ${ARGN} ) @@ -108,6 +144,10 @@ MACRO(MYSQL_ADD_PLUGIN) MESSAGE(FATAL_ERROR "Invalid value for PLUGIN_${plugin}") ENDIF() + IF(EXTERNAL_PLUGIN) + EXTERNAL_PLUGIN_PRE() + ENDIF() + # Validate that the requested build mode is compatible with what the # plugin actually supports. STATIC_ONLY plugins cannot be built as # dynamic modules, and MODULE_ONLY plugins cannot be linked statically. @@ -129,11 +169,24 @@ MACRO(MYSQL_ADD_PLUGIN) IF(NOT ARG_DEPENDS) SET(ARG_DEPENDS) ENDIF() - - IF(ARG_VERSION) - SET(version_string ";PLUGIN_${plugin}_VERSION=\"${ARG_VERSION}\"") + IF(NOT EXTERNAL_PLUGIN) + SET(ARG_DEPENDS GenError ${ARG_DEPENDS}) ENDIF() - + + SET(definitions ";PLUGIN_NAME=\"${target}\";PLUGIN_LICENSE=PLUGIN_LICENSE_GPL") + IF(ARG_VERSION) + IF(NOT ARG_VERSION MATCHES "^([0-9]+)\\.([0-9]+)" OR CMAKE_MATCH_2 GREATER 255) + MESSAGE(FATAL_ERROR "Plugin ${plugin} has invalid VERSION") + ENDIF() + SET(definitions "${definitions};PLUGIN_VERSION=\"${ARG_VERSION}\";PLUGIN_HEX_VERSION=${CMAKE_MATCH_1}*256+${CMAKE_MATCH_2}") + ENDIF() + IF(ARG_AUTHOR) + SET(definitions "${definitions};PLUGIN_AUTHOR=\"${ARG_AUTHOR}\"") + ENDIF() + IF(ARG_DESCRIPTION) + SET(definitions "${definitions};PLUGIN_DESCRIPTION=\"${ARG_DESCRIPTION}\"") + ENDIF() + IF(NOT ARG_MODULE_OUTPUT_NAME) IF(ARG_STORAGE_ENGINE) SET(ARG_MODULE_OUTPUT_NAME "ha_${target}") @@ -157,7 +210,7 @@ MACRO(MYSQL_ADD_PLUGIN) ADD_LIBRARY(${target} STATIC ${SOURCES}) DTRACE_INSTRUMENT(${target}) - ADD_DEPENDENCIES(${target} GenError ${ARG_DEPENDS}) + ADD_DEPENDENCIES(${target} ${ARG_DEPENDS}) RESTRICT_SYMBOL_EXPORTS(${target}) IF(WITH_EMBEDDED_SERVER AND (NOT ARG_NOT_EMBEDDED)) # Embedded library should contain PIC code and be linkable @@ -169,9 +222,9 @@ MACRO(MYSQL_ADD_PLUGIN) DTRACE_INSTRUMENT(${target}_embedded) IF(ARG_RECOMPILE_FOR_EMBEDDED) SET_TARGET_PROPERTIES(${target}_embedded - PROPERTIES COMPILE_DEFINITIONS "EMBEDDED_LIBRARY${version_string}") + PROPERTIES COMPILE_DEFINITIONS "EMBEDDED_LIBRARY${definitions}") ENDIF() - ADD_DEPENDENCIES(${target}_embedded GenError ${ARG_DEPENDS}) + ADD_DEPENDENCIES(${target}_embedded ${ARG_DEPENDS}) IF(ARG_LINK_LIBRARIES) TARGET_LINK_LIBRARIES (${target}_embedded ${ARG_LINK_LIBRARIES}) ENDIF() @@ -207,11 +260,11 @@ MACRO(MYSQL_ADD_PLUGIN) IF(ARG_MANDATORY) SET (mysql_mandatory_plugins "${mysql_mandatory_plugins}${builtin_entry}\n") - SET (mysql_mandatory_plugins ${mysql_mandatory_plugins} PARENT_SCOPE) + SET (mysql_mandatory_plugins ${mysql_mandatory_plugins} ${PARENT_SCOPE}) ELSE() SET (mysql_optional_plugins "${mysql_optional_plugins}${builtin_entry}\n") - SET (mysql_optional_plugins ${mysql_optional_plugins} PARENT_SCOPE) + SET (mysql_optional_plugins ${mysql_optional_plugins} ${PARENT_SCOPE}) ENDIF() ELSEIF(PLUGIN_${plugin} MATCHES "(DYNAMIC|AUTO|YES)" AND NOT ARG_STATIC_ONLY AND NOT WITHOUT_DYNAMIC_PLUGINS) @@ -223,7 +276,7 @@ MACRO(MYSQL_ADD_PLUGIN) SET_TARGET_PROPERTIES (${target} PROPERTIES PREFIX "") IF (NOT ARG_CLIENT) SET_TARGET_PROPERTIES (${target} PROPERTIES - COMPILE_DEFINITIONS "MYSQL_DYNAMIC_PLUGIN${version_string}") + COMPILE_DEFINITIONS "MYSQL_DYNAMIC_PLUGIN${definitions}") ENDIF() TARGET_LINK_LIBRARIES (${target} mysqlservices ${ARG_LINK_LIBRARIES}) @@ -254,7 +307,7 @@ MACRO(MYSQL_ADD_PLUGIN) TARGET_LINK_LIBRARIES (${target} "-Wl,--no-undefined") ENDIF() - ADD_DEPENDENCIES(${target} GenError ${ARG_DEPENDS}) + ADD_DEPENDENCIES(${target} ${ARG_DEPENDS}) SET_TARGET_PROPERTIES(${target} PROPERTIES OUTPUT_NAME "${ARG_MODULE_OUTPUT_NAME}") @@ -263,24 +316,29 @@ MACRO(MYSQL_ADD_PLUGIN) IF(CPACK_COMPONENTS_ALL AND NOT CPACK_COMPONENTS_ALL MATCHES ${ARG_COMPONENT} AND INSTALL_SYSCONF2DIR) - IF (ARG_STORAGE_ENGINE) - STRING(REPLACE "-" "_" ver ${SERVER_VERSION}) - SET(ver " = ${ver}-%{release}") + IF (ARG_STORAGE_ENGINE OR ARG_RECOMPILE_FOR_EMBEDDED) + IF(RPM) + STRING(REPLACE "-" "_" ver ${SERVER_VERSION}) + SET(ver " = ${ver}-%{release}") + ELSE() # DEB + SET(ver " (= 1:${SERVER_VERSION}+maria~${DEBIAN_ABBR})") + ENDIF() ELSE() SET(ver "") ENDIF() STRING(TOUPPER ${ARG_COMPONENT} ARG_COMPONENT_UPPER) - SET(CPACK_COMPONENT_${ARG_COMPONENT_UPPER}SYMLINKS_GROUP ${ARG_COMPONENT} PARENT_SCOPE) - SET(CPACK_COMPONENT_${ARG_COMPONENT_UPPER}_GROUP ${ARG_COMPONENT} PARENT_SCOPE) + SET(CPACK_COMPONENT_${ARG_COMPONENT_UPPER}SYMLINKS_GROUP ${ARG_COMPONENT} ${PARENT_SCOPE}) + SET(CPACK_COMPONENT_${ARG_COMPONENT_UPPER}_GROUP ${ARG_COMPONENT} ${PARENT_SCOPE}) SET(CPACK_COMPONENTS_ALL ${CPACK_COMPONENTS_ALL} ${ARG_COMPONENT} ${ARG_COMPONENT}Symlinks) - SET(CPACK_COMPONENTS_ALL ${CPACK_COMPONENTS_ALL} PARENT_SCOPE) + SET(CPACK_COMPONENTS_ALL ${CPACK_COMPONENTS_ALL} ${PARENT_SCOPE}) IF (NOT ARG_CLIENT) - SET(CPACK_RPM_${ARG_COMPONENT}_PACKAGE_REQUIRES "MariaDB-server${ver}" PARENT_SCOPE) + SET(CPACK_RPM_${ARG_COMPONENT}_PACKAGE_REQUIRES "MariaDB-server${ver}" ${PARENT_SCOPE}) + SET(CPACK_DEBIAN_PACKAGE_DEPENDS "mariadb-server${ver}") ENDIF() - SET(CPACK_RPM_${ARG_COMPONENT}_USER_FILELIST ${ignored} PARENT_SCOPE) + SET(CPACK_RPM_${ARG_COMPONENT}_USER_FILELIST ${ignored} ${PARENT_SCOPE}) IF (ARG_VERSION) - SET(CPACK_RPM_${ARG_COMPONENT}_PACKAGE_VERSION ${SERVER_VERSION}_${ARG_VERSION} PARENT_SCOPE) + SET(CPACK_RPM_${ARG_COMPONENT}_PACKAGE_VERSION ${SERVER_VERSION}_${ARG_VERSION} ${PARENT_SCOPE}) SET_PLUGIN_DEB_VERSION(${target} ${SERVER_VERSION}-${ARG_VERSION}) ENDIF() IF(NOT ARG_CLIENT AND UNIX) @@ -288,9 +346,9 @@ MACRO(MYSQL_ADD_PLUGIN) SET(ARG_CONFIG "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${target}.cnf") FILE(WRITE ${ARG_CONFIG} "[mariadb]\nplugin-load-add=${ARG_MODULE_OUTPUT_NAME}.so\n") ENDIF() - SET(CPACK_RPM_${ARG_COMPONENT}_USER_FILELIST ${ignored} "%config(noreplace) ${INSTALL_SYSCONF2DIR}/*" PARENT_SCOPE) - SET(CPACK_RPM_${ARG_COMPONENT}_POST_INSTALL_SCRIPT_FILE ${CMAKE_SOURCE_DIR}/support-files/rpm/plugin-postin.sh PARENT_SCOPE) - SET(CPACK_RPM_${ARG_COMPONENT}_POST_TRANS_SCRIPT_FILE ${CMAKE_SOURCE_DIR}/support-files/rpm/server-posttrans.sh PARENT_SCOPE) + SET(CPACK_RPM_${ARG_COMPONENT}_USER_FILELIST ${ignored} "%config(noreplace) ${INSTALL_SYSCONF2DIR}/*" ${PARENT_SCOPE}) + SET(CPACK_RPM_${ARG_COMPONENT}_POST_INSTALL_SCRIPT_FILE ${CMAKE_SOURCE_DIR}/support-files/rpm/plugin-postin.sh ${PARENT_SCOPE}) + SET(CPACK_RPM_${ARG_COMPONENT}_POST_TRANS_SCRIPT_FILE ${CMAKE_SOURCE_DIR}/support-files/rpm/server-posttrans.sh ${PARENT_SCOPE}) ENDIF() ENDIF() ELSE() @@ -322,24 +380,18 @@ MACRO(MYSQL_ADD_PLUGIN) ELSE() ADD_FEATURE_INFO(${plugin} ${have_target} "Server plugin ${plugin_type}") ENDIF() + IF (EXTERNAL_PLUGIN) + EXTERNAL_PLUGIN_POST() + ENDIF() ENDIF(NOT WITHOUT_SERVER OR ARG_CLIENT) ENDMACRO() +MACRO(MYSQL_ADD_PLUGIN) + MARIADB_ADD_PLUGIN(${ARGV}) +ENDMACRO() -# Add all CMake projects under storage and plugin -# subdirectories, configure sql_builtins.cc -MACRO(CONFIGURE_PLUGINS) - IF(NOT WITHOUT_SERVER) - FILE(GLOB dirs_storage ${CMAKE_SOURCE_DIR}/storage/*) - ENDIF() - - FILE(GLOB dirs_plugin ${CMAKE_SOURCE_DIR}/plugin/*) - FOREACH(dir ${dirs_storage} ${dirs_plugin}) - IF (EXISTS ${dir}/CMakeLists.txt) - ADD_SUBDIRECTORY(${dir}) - ENDIF() - ENDFOREACH() - +# verify that all -DPLUGIN_xxx=YES plugins are being built +MACRO(VERIFY_PLUGINS) GET_CMAKE_PROPERTY(ALL_VARS VARIABLES) FOREACH (V ${ALL_VARS}) IF (V MATCHES "^PLUGIN_") diff --git a/debian/libmariadb-dev.install b/debian/libmariadb-dev.install index 48120822f81..72147bb60ee 100644 --- a/debian/libmariadb-dev.install +++ b/debian/libmariadb-dev.install @@ -4,8 +4,7 @@ usr/include/mariadb/errmsg.h usr/include/mariadb/ma_list.h usr/include/mariadb/ma_pvio.h usr/include/mariadb/ma_tls.h -usr/include/mariadb/mariadb/ -usr/include/mariadb/mariadb/ma_io.h +usr/include/mariadb/mariadb usr/include/mariadb/mariadb_com.h usr/include/mariadb/mariadb_ctype.h usr/include/mariadb/mariadb_dyncol.h @@ -16,13 +15,12 @@ usr/include/mariadb/my_alloca.h usr/include/mariadb/my_config.h usr/include/mariadb/my_global.h usr/include/mariadb/my_sys.h +usr/include/mariadb/mysql usr/include/mariadb/mysql.h -usr/include/mariadb/mysql/ -usr/include/mariadb/mysql/client_plugin.h -usr/include/mariadb/mysql/plugin_auth.h usr/include/mariadb/mysql_com.h usr/include/mariadb/mysql_version.h usr/include/mariadb/mysqld_error.h +usr/include/mariadb/server usr/lib/*/libmariadb.a usr/lib/*/libmariadb.so usr/lib/*/libmariadbclient.a @@ -30,6 +28,7 @@ usr/lib/*/libmariadbclient.so usr/lib/*/libmysqlservices.a usr/lib/*/pkgconfig/libmariadb.pc usr/share/aclocal/mysql.m4 +usr/share/cmake/mariadb-plugin usr/share/man/man1/mariadb_config.1 usr/share/man/man1/mysql_config.1 usr/share/man/man3/*.3 diff --git a/debian/libmariadbd-dev.install b/debian/libmariadbd-dev.install index 3eebe631b9e..13d961409cc 100644 --- a/debian/libmariadbd-dev.install +++ b/debian/libmariadbd-dev.install @@ -1,4 +1,3 @@ -usr/include/mariadb/server usr/lib/*/libmariadbd.a usr/lib/*/libmariadbd.so usr/lib/*/libmysqld.a diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt index 1cc6459d59a..6593819c4dc 100644 --- a/include/CMakeLists.txt +++ b/include/CMakeLists.txt @@ -83,7 +83,7 @@ INSTALL(DIRECTORY mysql/ PATTERN "${EXCL_SERVICE_WSREP}" EXCLUDE ) -STRING(REPLACE "." "\\." EXCL_RE "${HEADERS};${HEADERS_GEN_CONFIGURE}") +STRING(REPLACE "." "\\." EXCL_RE "${HEADERS};${HEADERS_GEN_CONFIGURE};config.h") STRING(REPLACE ";" "|" EXCL_RE "${EXCL_RE}") MACRO(INSTALL_PRIVATE DIR) diff --git a/libservices/CMakeLists.txt b/libservices/CMakeLists.txt index 8142fdcc49a..0cd9b8b007d 100644 --- a/libservices/CMakeLists.txt +++ b/libservices/CMakeLists.txt @@ -53,4 +53,5 @@ IF(MSVC) TARGET_COMPILE_OPTIONS(mysqlservices PRIVATE /Zl) ENDIF() -INSTALL(TARGETS mysqlservices DESTINATION ${INSTALL_LIBDIR} COMPONENT Development) +INSTALL(TARGETS mysqlservices EXPORT mariadb-plugin-targets + DESTINATION ${INSTALL_LIBDIR} COMPONENT Development) diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt index 83e64f8c1a5..76227f7d403 100644 --- a/sql/CMakeLists.txt +++ b/sql/CMakeLists.txt @@ -307,6 +307,11 @@ IF(MSVC OR CMAKE_SYSTEM_NAME MATCHES AIX) SET_TARGET_PROPERTIES(server PROPERTIES AIX_EXPORT_ALL_SYMBOLS TRUE) ENDIF() MYSQL_INSTALL_TARGETS(server DESTINATION ${INSTALL_BINDIR} COMPONENT Server) + IF(MSVC) + # ARCHIVE = server.lib only, not server.dll, enough for linking plugins + INSTALL(TARGETS server EXPORT mariadb-plugin-targets ARCHIVE + DESTINATION ${INSTALL_LIBDIR} COMPONENT Development) + ENDIF() ENDIF() ADD_LIBRARY(sql_builtins STATIC ${CMAKE_CURRENT_BINARY_DIR}/sql_builtin.cc) @@ -544,13 +549,21 @@ ENDIF(WIN32) IF(NOT WITH_WSREP) SET(EXCL_WSREP "wsrep_[a-np-z]*.h") +ELSE() + INSTALL(DIRECTORY ../wsrep-lib/include/wsrep + DESTINATION ${INSTALL_INCLUDEDIR}/server/private COMPONENT Development + FILES_MATCHING PATTERN "*.hpp") + INSTALL(FILES ../wsrep-lib/wsrep-API/v26/wsrep_api.h + DESTINATION ${INSTALL_INCLUDEDIR}/server/private + COMPONENT Development) ENDIF() INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/lex_hash.h ${CMAKE_CURRENT_BINARY_DIR}/lex_token.h DESTINATION ${INSTALL_INCLUDEDIR}/server/private COMPONENT Development) -INSTALL(DIRECTORY . DESTINATION ${INSTALL_INCLUDEDIR}/server/private COMPONENT Development +INSTALL(DIRECTORY . + DESTINATION ${INSTALL_INCLUDEDIR}/server/private COMPONENT Development FILES_MATCHING PATTERN "*.h" PATTERN share EXCLUDE PATTERN "${EXCL_WSREP}" EXCLUDE diff --git a/sql/unireg.h b/sql/unireg.h index 7a7e97c082c..694aba5a7dd 100644 --- a/sql/unireg.h +++ b/sql/unireg.h @@ -33,15 +33,6 @@ #define TEMP_PREFIX "MY" #define LOG_PREFIX "ML" #define PROGDIR "bin/" -#ifndef MYSQL_DATADIR -#define MYSQL_DATADIR "data/" -#endif -#ifndef SHAREDIR -#define SHAREDIR "share/" -#endif -#ifndef PLUGINDIR -#define PLUGINDIR "lib/plugin" -#endif #define MAX_ERROR_RANGES 4 /* 1000-2000, 2000-3000, 3000-4000, 4000-5000 */ #define ERRORS_PER_RANGE 1000 diff --git a/storage/columnstore/CMakeLists.txt b/storage/columnstore/CMakeLists.txt index 52f30b67161..d29f91f6615 100644 --- a/storage/columnstore/CMakeLists.txt +++ b/storage/columnstore/CMakeLists.txt @@ -9,6 +9,7 @@ add_definitions(-DBOOST_BIND_GLOBAL_PLACEHOLDERS) IF(NOT PLUGIN_PERFSCHEMA STREQUAL NO) add_definitions(-DHAVE_PSI_INTERFACE=) ENDIF() +add_definitions(-DPLUGIN_COLUMNSTORE_VERSION=PLUGIN_VERSION) # let's temporarily disable the warning, until ColumnStore is fixed MY_CHECK_AND_SET_COMPILER_FLAG("-Wno-error=suggest-override") diff --git a/support-files/CMakeLists.txt b/support-files/CMakeLists.txt index 8dc3d235dcd..7ecf048d73b 100644 --- a/support-files/CMakeLists.txt +++ b/support-files/CMakeLists.txt @@ -1,5 +1,5 @@ # Copyright (c) 2006, 2016, Oracle and/or its affiliates. -# Copyright (c) 2012, 2022, MariaDB +# Copyright (c) 2012, 2026, MariaDB plc # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -254,3 +254,19 @@ IF(UNIX AND NOT WITHOUT_SERVER) COMPONENT SupportFiles) ENDIF(INSTALL_SYSCONFDIR) ENDIF() + +# mariadb-plugin-config.cmake lets out-of-tree plugins build against an +# installed MariaDB-devel / MariaDB SDK; needed on Windows same as on UNIX. +IF(NOT WITHOUT_SERVER) + FILE(READ ${CMAKE_SOURCE_DIR}/cmake/install_layout.cmake install_layout.cmake) + FILE(READ ${CMAKE_SOURCE_DIR}/cmake/plugin.cmake plugin.cmake) + CONFIGURE_FILE(mariadb-plugin-config.cmake.in mariadb-plugin-config.cmake @ONLY) + # DBUG_OFF/ENABLED_DEBUG_SYNC/SAFE_MUTEX affect THD's ABI + SET(defines "TARGET_COMPILE_DEFINITIONS(mariadb_private INTERFACE $<$:ENABLED_DEBUG_SYNC> $<$>:DBUG_OFF> $<$,$>>:SAFE_MUTEX>)\n") + FILE(GENERATE OUTPUT $/mariadb-plugin-defines.cmake CONTENT "${defines}") + INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/mariadb-plugin-config.cmake + ${CMAKE_CURRENT_BINARY_DIR}/$/mariadb-plugin-defines.cmake + DESTINATION ${INSTALL_SHAREDIR}/cmake/mariadb-plugin/ COMPONENT Development) + INSTALL(EXPORT mariadb-plugin-targets FILE mariadb-plugin-targets.cmake + DESTINATION ${INSTALL_SHAREDIR}/cmake/mariadb-plugin/ COMPONENT Development) +ENDIF() diff --git a/support-files/mariadb-plugin-config.cmake.in b/support-files/mariadb-plugin-config.cmake.in new file mode 100644 index 00000000000..705ede6e80f --- /dev/null +++ b/support-files/mariadb-plugin-config.cmake.in @@ -0,0 +1,140 @@ +# Copyright (c) 2026, MariaDB plc +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA + +SET(EXTERNAL_PLUGIN TRUE) +SET(SERVER_VERSION @SERVER_VERSION@) +STRING(TOLOWER @SYSTEM_NAME_AND_PROCESSOR@ SYSTEM_NAME_AND_PROCESSOR) + +GET_FILENAME_COMPONENT(basedir "${CMAKE_CURRENT_LIST_DIR}/../../.." ABSOLUTE) + +MACRO(DTRACE_INSTRUMENT) +ENDMACRO() +MACRO(ADD_VERSION_INFO) +ENDMACRO() +MACRO(MYSQL_INSTALL_TARGETS) + INSTALL(TARGETS ${ARGV}) +ENDMACRO() +MACRO(SET_PLUGIN_DEB_VERSION plugin ver) + SET(CPACK_DEBIAN_PACKAGE_VERSION "1:${ver}+maria~${DEBIAN_ABBR}") + SET(CPACK_DEBIAN_FILE_NAME "${CPACK_PACKAGE_NAME}-${ver}+maria~${DEBIAN_ABBR}_${DEBIAN_ARCH}.deb") +ENDMACRO() + +ADD_LIBRARY(mariadb_private INTERFACE) +TARGET_INCLUDE_DIRECTORIES(mariadb_private INTERFACE ${basedir}/@INSTALL_INCLUDEDIR@/server/private) + +INCLUDE(FeatureSummary) +INCLUDE("${CMAKE_CURRENT_LIST_DIR}/mariadb-plugin-targets.cmake") +INCLUDE("${CMAKE_CURRENT_LIST_DIR}/mariadb-plugin-defines.cmake") +TARGET_INCLUDE_DIRECTORIES(mysqlservices INTERFACE ${basedir}/@INSTALL_INCLUDEDIR@/server) + +# Run at the beginning of MARIADB_ADD_PLUGIN +MACRO(EXTERNAL_PLUGIN_PRE) + GET_PROPERTY(done GLOBAL PROPERTY done) + IF(done) + MESSAGE(FATAL_ERROR "Multiple MARIADB_ADD_PLUGIN() in the same project are not supported yet") + ENDIF() + SET_PROPERTY(GLOBAL PROPERTY done 1) + IF (NOT ARG_COMPONENT) + SET(ARG_COMPONENT ${target}) + ENDIF() + IF (ARG_STATIC_ONLY OR PLUGIN_${plugin} MATCHES "STATIC") + MESSAGE(FATAL_ERROR "Static plugin ${plugin} must be built from server source tree") + ENDIF() + IF(NOT ARG_AUTHOR) + MESSAGE(FATAL_ERROR "Plugin ${plugin} has no AUTHOR") + ENDIF() + IF(NOT ARG_DESCRIPTION) + MESSAGE(FATAL_ERROR "Plugin ${plugin} has no DESCRIPTION") + ENDIF() + IF(NOT ARG_VERSION) + MESSAGE(FATAL_ERROR "Plugin ${plugin} has no VERSION") + ENDIF() + CMAKE_POLICY(PUSH) + CMAKE_POLICY(SET CMP0048 NEW) + PROJECT(${target} VERSION ${ARG_VERSION} DESCRIPTION "${ARG_DESCRIPTION}") + CMAKE_POLICY(POP) + SET(CPACK_PACKAGE_NAME "mariadb-plugin-${ARG_COMPONENT}") + SET(CPACK_RPM_PACKAGE_NAME "MariaDB-plugin-${ARG_COMPONENT}") + SET(CPACK_PACKAGE_DESCRIPTION_SUMMARY "${ARG_DESCRIPTION}") + SET(CPACK_RPM_PACKAGE_DESCRIPTION "${ARG_DESCRIPTION}") + SET(CPACK_RPM_FILE_NAME RPM-DEFAULT) + SET(CPACK_RPM_PACKAGE_LICENSE "GPLv2") + SET(CPACK_RPM_PACKAGE_GROUP "Applications/Databases") + SET(CPACK_RPM_PACKAGE_RELEASE "1%{?dist}") + SET(CPACK_RPM_PACKAGE_VENDOR "${ARG_AUTHOR}") + SET(CPACK_PACKAGE_RELOCATABLE FALSE) + + FOREACH(p ${INSTALL_PLUGINDIRABS} ${INSTALL_MYSQLTESTDIRABS}/plugin ${INSTALL_SYSCONF2DIR}) + WHILE(NOT prev STREQUAL ${p}) + LIST(APPEND CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION "${p}") + SET(prev ${p}) + GET_FILENAME_COMPONENT(p "${p}" DIRECTORY) + ENDWHILE() + ENDFOREACH() + + SET(CPACK_DEBIAN_PACKAGE_MAINTAINER "${ARG_AUTHOR}") + SET(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) + SET(CPACK_COMPONENTS_ALL TRUE) # for version-fiddling IF in plugin.cmake + IF (DEB) + # match autobake-deb.sh naming + EXECUTE_PROCESS(COMMAND sh -c "echo $(lsb_release -si) $(lsb_release -sr)|sed 's/^\\(...\\)[^ ]* /\\L\\1/;s/\\.//g'" + RESULT_VARIABLE res OUTPUT_VARIABLE DEBIAN_ABBR OUTPUT_STRIP_TRAILING_WHITESPACE) + EXECUTE_PROCESS(COMMAND sh -c "dpkg --print-architecture" + RESULT_VARIABLE res OUTPUT_VARIABLE DEBIAN_ARCH OUTPUT_STRIP_TRAILING_WHITESPACE) + IF (NOT ("${DEBIAN_ABBR}_${DEBIAN_ARCH}" MATCHES "^(deb|ubu)[0-9]+_[a-z0-9_]+$")) + message(FATAL_ERROR "Cannot get debian version (${DEBIAN_ABBR}) or architecture (${DEBIAN_ARCH})") + endif() + ENDIF() +ENDMACRO(EXTERNAL_PLUGIN_PRE) + +# Run at the end of MARIADB_ADD_PLUGIN +MACRO(EXTERNAL_PLUGIN_POST) + IF(ARG_RECOMPILE_FOR_EMBEDDED OR ARG_STORAGE_ENGINE) + TARGET_LINK_LIBRARIES(${target} mariadb_private) + ENDIF() + SET(CPACK_RPM_PACKAGE_VERSION ${CPACK_RPM_${ARG_COMPONENT}_PACKAGE_VERSION}) + SET(CPACK_RPM_PACKAGE_REQUIRES ${CPACK_RPM_${ARG_COMPONENT}_PACKAGE_REQUIRES}) + SET(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${SERVER_VERSION}-${ARG_VERSION}-${SYSTEM_NAME_AND_PROCESSOR}") + + IF (NOT RPM AND NOT DEB) + FILE(WRITE "${CPACK_PACKAGE_FILE_NAME}.yml" "- name: ${target} + filename: ${CPACK_PACKAGE_FILE_NAME} + version: ${ARG_VERSION} + server: ${SERVER_VERSION} + platform: ${SYSTEM_NAME_AND_PROCESSOR} + author: ${ARG_AUTHOR} + description: ${ARG_DESCRIPTION} + license: ${CPACK_RPM_PACKAGE_LICENSE}\n") + INSTALL(FILES "${CPACK_PACKAGE_FILE_NAME}.yml" DESTINATION .) + ENDIF() +ENDMACRO(EXTERNAL_PLUGIN_POST) + +@install_layout.cmake@ + +IF(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + SET(CMAKE_INSTALL_PREFIX ${basedir} CACHE PATH "install prefix" FORCE) +ENDIF() + +@plugin.cmake@ + +IF(RPM) + set(CPACK_GENERATOR "RPM") +ELSEIF(DEB) + set(CPACK_GENERATOR "DEB") +ELSEIF(WIN32) + set(CPACK_GENERATOR "ZIP") +ELSE() + set(CPACK_GENERATOR "TGZ") +ENDIF() From 1f71dcfc3ce0d58feddf26a9a7c49739a71332fa Mon Sep 17 00:00:00 2001 From: Thirunarayanan Balathandayuthapani Date: Fri, 25 Sep 2026 18:34:05 +0530 Subject: [PATCH 51/71] MDEV-19574: innodb_stats_method is not honored when innodb_stats_persistent=ON Problem: ======= When persistent statistics are enabled (innodb_stats_persistent=ON), the innodb_stats_method setting is not properly utilized during statistics calculation. The statistics collection functions always use a hardcoded default behavior for NULL value comparison instead of respecting the configured stats method. This affects the accuracy of n_diff_key_vals (distinct key count), particularly for indexes with nullable columns containing NULL values. Moreover, stat_n_non_null_key_vals[] was never computed for persistent statistics; it stayed at the 0 that dict_stats_empty_index() assigns. With innodb_stats_method=nulls_ignored, innodb_rec_per_key() therefore always found n_diff <= n_null and reported one record per key for every index. This impacts the query optimizer, which makes decisions based on inaccurate cardinality estimates. Solution: ======== Introduced IndexLevelStats to collect statistics at a specific B-tree level during index analysis. Introduced PageStats to collect statistics for leaf page analysis. Refactored the following functions: dict_stats_analyze_index_level() to IndexLevelStats::analyze_level() dict_stats_analyze_index_for_n_prefix() to IndexLevelStats::sample_leaf_pages() dict_stats_analyze_index_below_cur() to PageStats::scan_below() dict_stats_scan_page() to PageStats::scan() The innodb_stats_method value is read once per table in dict_stats_update_persistent() and passed down, so that all indexes of a table are analyzed with the same method. Add the stats method name to stat_description when innodb_stats_method has a non-default value. The suffix is dropped when the description is already full. Added the new stat name n_nonnull_fld01, n_nonnull_fld02, etc. with a stats description, to indicate how many non-null values exist for the nth field of the index. This value is retrieved and stored in the index statistics in dict_stats_fetch_index_stats_step(). The counts are per column, not per n-column prefix. rec_get_n_blob_pages(): Calculate the number of externally stored pages for a record, using ceiling division by the usable BLOB page payload (blob_part_size), which differs between ROW_FORMAT=COMPRESSED (zip_size minus FIL_PAGE_DATA) and the other formats (srv_page_size minus the BLOB header and the page trailer). For ROW_FORMAT=COMPRESSED the length in the field reference is the uncompressed length, so the result is an upper bound. When the leaf level is scanned in full, the number of leaf pages that were scanned is reported as n_leaf_pages for a multi level index. Before, result.n_leaf_pages was overwritten with index->stat_n_leaf_pages, which dict_stats_empty_index() had just set to 1, so every index that took the full scan path reported n_leaf_pages=1. Single page indexes report 1. This changes cardinality estimates and therefore leads to multiple changes in existing test cases. Non-null values are counted only at the leaf level, since only leaf pages hold actual records. A full scan of the leaf level counts them exactly. When the level is sampled, the per column count is derived from the sampled leaves with the same formula as n_diff: n_ordinary_leaf_pages * n_non_null_all_analyzed_pages / n_leaf_pages_to_analyze This is an estimate for NOT NULL columns as well: the sampled leaves may hold fewer or more records than the average, and a dive that stops at a boring page contributes nothing to the sum while still counting in the divisor. innodb_rec_per_key(): stat_n_non_null_key_vals[i] holds the number of records in which the i-th indexed column alone is not NULL, while what has to be excluded here is the number of records whose first i+1 columns are all not NULL, because that is the population which the n-column prefix statistic stat_n_diff_key_vals[i] has to be corrected against when innodb_stats_method=nulls_ignored: with NULLs compared as unequal, every record carrying a NULL anywhere in the prefix adds a distinct value of its own to n_diff. PageStats::scan(): n_non_null is accumulated and assigned only for leaf pages, so that a non-leaf scan cannot leave a node pointer count behind when scan_below() stops at a boring page without reaching a leaf. IndexLevelStats::reset_for_level() also clears n_diff[], and dict_stats_analyze_index() zero initializes the buffer backing it, so that a level scan which finds no records (a failed btr_pcur_open_level(), or a non-leaf page whose first record is not marked as the leftmost one on the level) leaves n_diff[] at 0 instead of stale values. IndexLevelStats::sample_leaf_pages() returns early when the group boundaries for the prefix are empty, which is the same condition. IndexLevelStats::analyze_level(): Instead of copying the last record of the page, retain the latch on the page until the record has been compared with the first record of the next page dict_stats_fetch_index_stats_step() no longer resets stat_n_non_null_key_vals[] while processing an n_diff_pfxNN row: dict_stats_empty_table() has already cleared the array before the fetch, and with n_nonnull_fldNN rows now being read too, that reset would make the result depend on the order in which the rows arrive. dict_stats_save(): now static function in dict0stats.cc that takes the innodb_stats_method value, and is removed from dict0stats.h. dict_stats_update_persistent() saves the statistics itself, so its callers no longer have to. Replaced btr_rec_get_externally_stored_len() with rec_get_n_blob_pages() in dict0stats.cc. btr_rec_get_field_ref_offs() and btr_rec_get_field_ref(), together with the BTR_BLOB_HDR_* macros, were moved from btr0cur.cc to btr0cur.h so that rec_get_n_blob_pages() can reuse them; btr_rec_get_field_ref_offs() is now a noexcept function returning size_t. Changed stat_n_diff_key_vals and stat_n_non_null_key_vals from ib_uint64_t* to uint64_t* len_is_stored(): simplified to a single comparison, which is equivalent for the unsigned lengths that it is used with. Removed the unused UNIV_STATS_DEBUG build macro (univ.i) and turned the DEBUG_PRINTF() helper in dict0stats.cc into an unconditional no-op --- .../main/innodb_ext_key,covering,on.rdiff | 4 +- mysql-test/main/innodb_ext_key,off.rdiff | 4 +- .../main/innodb_ext_key,on,unoptimized.rdiff | 4 +- mysql-test/main/mysqldump-system.result | 6 + .../suite/gcol/r/innodb_virtual_stats.result | 42 + .../suite/innodb/r/innodb-index-online.result | 6 +- .../innodb/r/innodb_stats_auto_recalc.result | 9 + .../r/innodb_stats_auto_recalc_ddl.result | 6 + ...db_stats_auto_recalc_on_nonexistent.result | 10 +- .../innodb/r/innodb_stats_drop_locked.result | 7 + .../suite/innodb/r/innodb_stats_fetch.result | 30 + .../innodb/r/innodb_stats_rename_table.result | 8 + ...innodb_stats_rename_table_if_exists.result | 24 + .../r/instant_alter_index_rename.result | 13 + .../suite/innodb/r/records_in_range,4k.rdiff | 9 +- .../suite/innodb/r/records_in_range,8k.rdiff | 9 +- .../suite/innodb/r/records_in_range.result | 2 + .../innodb/r/stats_external_pages.result | 58 + .../innodb/r/stats_method,NULLS_IGNORED.rdiff | 76 + .../innodb/r/stats_method,NULLS_UNEQUAL.rdiff | 76 + mysql-test/suite/innodb/r/stats_method.result | 182 ++ .../suite/innodb/t/records_in_range.test | 2 + .../suite/innodb/t/stats_external_pages.test | 60 + .../suite/innodb/t/stats_method.combinations | 11 + mysql-test/suite/innodb/t/stats_method.test | 218 +++ storage/innobase/btr/btr0cur.cc | 84 +- storage/innobase/btr/btr0pcur.cc | 19 +- storage/innobase/dict/dict0stats.cc | 1738 +++++++++-------- storage/innobase/handler/ha_innodb.cc | 48 +- storage/innobase/include/btr0cur.h | 34 +- storage/innobase/include/btr0pcur.h | 24 +- storage/innobase/include/data0type.h | 2 +- storage/innobase/include/dict0mem.h | 11 +- storage/innobase/include/dict0stats.h | 19 +- storage/innobase/include/univ.i | 3 - storage/innobase/row/row0import.cc | 5 - 36 files changed, 1868 insertions(+), 995 deletions(-) create mode 100644 mysql-test/suite/innodb/r/stats_external_pages.result create mode 100644 mysql-test/suite/innodb/r/stats_method,NULLS_IGNORED.rdiff create mode 100644 mysql-test/suite/innodb/r/stats_method,NULLS_UNEQUAL.rdiff create mode 100644 mysql-test/suite/innodb/r/stats_method.result create mode 100644 mysql-test/suite/innodb/t/stats_external_pages.test create mode 100644 mysql-test/suite/innodb/t/stats_method.combinations create mode 100644 mysql-test/suite/innodb/t/stats_method.test diff --git a/mysql-test/main/innodb_ext_key,covering,on.rdiff b/mysql-test/main/innodb_ext_key,covering,on.rdiff index 0d8bcc6ce3c..8c5c6bce232 100644 --- a/mysql-test/main/innodb_ext_key,covering,on.rdiff +++ b/mysql-test/main/innodb_ext_key,covering,on.rdiff @@ -1,5 +1,5 @@ ---- ./main/innodb_ext_key.result -+++ ./main/innodb_ext_key.reject +--- ./innodb_ext_key.result ++++ innodb_ext_key,covering,on.result @@ -244,7 +244,7 @@ Variable_name Value Handler_read_first 0 diff --git a/mysql-test/main/innodb_ext_key,off.rdiff b/mysql-test/main/innodb_ext_key,off.rdiff index 8b24cc9a663..651f09323db 100644 --- a/mysql-test/main/innodb_ext_key,off.rdiff +++ b/mysql-test/main/innodb_ext_key,off.rdiff @@ -1,5 +1,5 @@ ---- main/innodb_ext_key.result -+++ main/innodb_ext_key,off.reject +--- ./innodb_ext_key.result ++++ innodb_ext_key,on.result @@ -9,7 +9,7 @@ explain select count(*) from lineitem where l_orderkey=130 and l_shipdate='1992-07-01'; diff --git a/mysql-test/main/innodb_ext_key,on,unoptimized.rdiff b/mysql-test/main/innodb_ext_key,on,unoptimized.rdiff index 0d8bcc6ce3c..e6909189aaf 100644 --- a/mysql-test/main/innodb_ext_key,on,unoptimized.rdiff +++ b/mysql-test/main/innodb_ext_key,on,unoptimized.rdiff @@ -1,5 +1,5 @@ ---- ./main/innodb_ext_key.result -+++ ./main/innodb_ext_key.reject +--- ./innodb_ext_key.result ++++ innodb_ext_key,on,unoptimized.result @@ -244,7 +244,7 @@ Variable_name Value Handler_read_first 0 diff --git a/mysql-test/main/mysqldump-system.result b/mysql-test/main/mysqldump-system.result index 5bec5268d8b..80beab0134a 100644 --- a/mysql-test/main/mysqldump-system.result +++ b/mysql-test/main/mysqldump-system.result @@ -127,6 +127,8 @@ REPLACE INTO `innodb_index_stats` VALUES ('mysql','tz','PRIMARY','2019-12-31 21:00:00','n_diff_pfx01',5,1,'Time_zone_id'), ('mysql','tz','PRIMARY','2019-12-31 21:00:00','n_diff_pfx02',394,1,'Time_zone_id,Transition_time'), ('mysql','tz','PRIMARY','2019-12-31 21:00:00','n_leaf_pages',1,NULL,'Number of leaf pages in the index'), +('mysql','tz','PRIMARY','2019-12-31 21:00:00','n_nonnull_fld01',394,1,'Time_zone_id'), +('mysql','tz','PRIMARY','2019-12-31 21:00:00','n_nonnull_fld02',394,1,'Transition_time'), ('mysql','tz','PRIMARY','2019-12-31 21:00:00','size',1,NULL,'Number of pages in the index'); /*!40000 ALTER TABLE `innodb_index_stats` ENABLE KEYS */; UNLOCK TABLES; @@ -750,6 +752,8 @@ REPLACE INTO `innodb_index_stats` VALUES ('mysql','tz','PRIMARY','2019-12-31 21:00:00','n_diff_pfx01',5,1,'Time_zone_id'), ('mysql','tz','PRIMARY','2019-12-31 21:00:00','n_diff_pfx02',394,1,'Time_zone_id,Transition_time'), ('mysql','tz','PRIMARY','2019-12-31 21:00:00','n_leaf_pages',1,NULL,'Number of leaf pages in the index'), +('mysql','tz','PRIMARY','2019-12-31 21:00:00','n_nonnull_fld01',394,1,'Time_zone_id'), +('mysql','tz','PRIMARY','2019-12-31 21:00:00','n_nonnull_fld02',394,1,'Transition_time'), ('mysql','tz','PRIMARY','2019-12-31 21:00:00','size',1,NULL,'Number of pages in the index'); /*!40000 ALTER TABLE `innodb_index_stats` ENABLE KEYS */; UNLOCK TABLES; @@ -1349,6 +1353,8 @@ INSERT IGNORE INTO `innodb_index_stats` VALUES ('mysql','tz','PRIMARY','2019-12-31 21:00:00','n_diff_pfx01',5,1,'Time_zone_id'), ('mysql','tz','PRIMARY','2019-12-31 21:00:00','n_diff_pfx02',394,1,'Time_zone_id,Transition_time'), ('mysql','tz','PRIMARY','2019-12-31 21:00:00','n_leaf_pages',1,NULL,'Number of leaf pages in the index'), +('mysql','tz','PRIMARY','2019-12-31 21:00:00','n_nonnull_fld01',394,1,'Time_zone_id'), +('mysql','tz','PRIMARY','2019-12-31 21:00:00','n_nonnull_fld02',394,1,'Transition_time'), ('mysql','tz','PRIMARY','2019-12-31 21:00:00','size',1,NULL,'Number of pages in the index'); /*!40000 ALTER TABLE `innodb_index_stats` ENABLE KEYS */; UNLOCK TABLES; diff --git a/mysql-test/suite/gcol/r/innodb_virtual_stats.result b/mysql-test/suite/gcol/r/innodb_virtual_stats.result index c0f595263df..f02c6993288 100644 --- a/mysql-test/suite/gcol/r/innodb_virtual_stats.result +++ b/mysql-test/suite/gcol/r/innodb_virtual_stats.result @@ -13,15 +13,21 @@ WHERE database_name = 'test' AND table_name = 't'; index_name stat_name stat_description GEN_CLUST_INDEX n_diff_pfx01 DB_ROW_ID GEN_CLUST_INDEX n_leaf_pages Number of leaf pages in the index +GEN_CLUST_INDEX n_nonnull_fld01 DB_ROW_ID GEN_CLUST_INDEX size Number of pages in the index idxa n_diff_pfx01 a idxa n_diff_pfx02 a,DB_ROW_ID idxa n_leaf_pages Number of leaf pages in the index +idxa n_nonnull_fld01 a +idxa n_nonnull_fld02 DB_ROW_ID idxa size Number of pages in the index vidxcd n_diff_pfx01 c vidxcd n_diff_pfx02 c,d vidxcd n_diff_pfx03 c,d,DB_ROW_ID vidxcd n_leaf_pages Number of leaf pages in the index +vidxcd n_nonnull_fld01 c +vidxcd n_nonnull_fld02 d +vidxcd n_nonnull_fld03 DB_ROW_ID vidxcd size Number of pages in the index ALTER TABLE t ADD COLUMN e INT GENERATED ALWAYS AS(a+a+b), ADD INDEX idxb (b), ALGORITHM=INPLACE; select count(*) from t; @@ -33,19 +39,27 @@ WHERE database_name = 'test' AND table_name = 't'; index_name stat_name stat_description GEN_CLUST_INDEX n_diff_pfx01 DB_ROW_ID GEN_CLUST_INDEX n_leaf_pages Number of leaf pages in the index +GEN_CLUST_INDEX n_nonnull_fld01 DB_ROW_ID GEN_CLUST_INDEX size Number of pages in the index idxa n_diff_pfx01 a idxa n_diff_pfx02 a,DB_ROW_ID idxa n_leaf_pages Number of leaf pages in the index +idxa n_nonnull_fld01 a +idxa n_nonnull_fld02 DB_ROW_ID idxa size Number of pages in the index idxb n_diff_pfx01 b idxb n_diff_pfx02 b,DB_ROW_ID idxb n_leaf_pages Number of leaf pages in the index +idxb n_nonnull_fld01 b +idxb n_nonnull_fld02 DB_ROW_ID idxb size Number of pages in the index vidxcd n_diff_pfx01 c vidxcd n_diff_pfx02 c,d vidxcd n_diff_pfx03 c,d,DB_ROW_ID vidxcd n_leaf_pages Number of leaf pages in the index +vidxcd n_nonnull_fld01 c +vidxcd n_nonnull_fld02 d +vidxcd n_nonnull_fld03 DB_ROW_ID vidxcd size Number of pages in the index ALTER TABLE t DROP COLUMN c, DROP INDEX idxa, ALGORITHM=INPLACE; select count(*) from t; @@ -57,14 +71,19 @@ WHERE database_name = 'test' AND table_name = 't'; index_name stat_name stat_description GEN_CLUST_INDEX n_diff_pfx01 DB_ROW_ID GEN_CLUST_INDEX n_leaf_pages Number of leaf pages in the index +GEN_CLUST_INDEX n_nonnull_fld01 DB_ROW_ID GEN_CLUST_INDEX size Number of pages in the index idxb n_diff_pfx01 b idxb n_diff_pfx02 b,DB_ROW_ID idxb n_leaf_pages Number of leaf pages in the index +idxb n_nonnull_fld01 b +idxb n_nonnull_fld02 DB_ROW_ID idxb size Number of pages in the index vidxcd n_diff_pfx01 d vidxcd n_diff_pfx02 d,DB_ROW_ID vidxcd n_leaf_pages Number of leaf pages in the index +vidxcd n_nonnull_fld01 d +vidxcd n_nonnull_fld02 DB_ROW_ID vidxcd size Number of pages in the index ALTER TABLE t ADD INDEX vidxe (e), ALGORITHM=INPLACE; select count(*) from t; @@ -76,18 +95,25 @@ WHERE database_name = 'test' AND table_name = 't'; index_name stat_name stat_description GEN_CLUST_INDEX n_diff_pfx01 DB_ROW_ID GEN_CLUST_INDEX n_leaf_pages Number of leaf pages in the index +GEN_CLUST_INDEX n_nonnull_fld01 DB_ROW_ID GEN_CLUST_INDEX size Number of pages in the index idxb n_diff_pfx01 b idxb n_diff_pfx02 b,DB_ROW_ID idxb n_leaf_pages Number of leaf pages in the index +idxb n_nonnull_fld01 b +idxb n_nonnull_fld02 DB_ROW_ID idxb size Number of pages in the index vidxcd n_diff_pfx01 d vidxcd n_diff_pfx02 d,DB_ROW_ID vidxcd n_leaf_pages Number of leaf pages in the index +vidxcd n_nonnull_fld01 d +vidxcd n_nonnull_fld02 DB_ROW_ID vidxcd size Number of pages in the index vidxe n_diff_pfx01 e vidxe n_diff_pfx02 e,DB_ROW_ID vidxe n_leaf_pages Number of leaf pages in the index +vidxe n_nonnull_fld01 e +vidxe n_nonnull_fld02 DB_ROW_ID vidxe size Number of pages in the index ALTER TABLE t ADD COLUMN f INT GENERATED ALWAYS AS(a + a), ADD INDEX vidxf (f), ALGORITHM=INPLACE; select count(*) from t; @@ -99,22 +125,31 @@ WHERE database_name = 'test' AND table_name = 't'; index_name stat_name stat_description GEN_CLUST_INDEX n_diff_pfx01 DB_ROW_ID GEN_CLUST_INDEX n_leaf_pages Number of leaf pages in the index +GEN_CLUST_INDEX n_nonnull_fld01 DB_ROW_ID GEN_CLUST_INDEX size Number of pages in the index idxb n_diff_pfx01 b idxb n_diff_pfx02 b,DB_ROW_ID idxb n_leaf_pages Number of leaf pages in the index +idxb n_nonnull_fld01 b +idxb n_nonnull_fld02 DB_ROW_ID idxb size Number of pages in the index vidxcd n_diff_pfx01 d vidxcd n_diff_pfx02 d,DB_ROW_ID vidxcd n_leaf_pages Number of leaf pages in the index +vidxcd n_nonnull_fld01 d +vidxcd n_nonnull_fld02 DB_ROW_ID vidxcd size Number of pages in the index vidxe n_diff_pfx01 e vidxe n_diff_pfx02 e,DB_ROW_ID vidxe n_leaf_pages Number of leaf pages in the index +vidxe n_nonnull_fld01 e +vidxe n_nonnull_fld02 DB_ROW_ID vidxe size Number of pages in the index vidxf n_diff_pfx01 f vidxf n_diff_pfx02 f,DB_ROW_ID vidxf n_leaf_pages Number of leaf pages in the index +vidxf n_nonnull_fld01 f +vidxf n_nonnull_fld02 DB_ROW_ID vidxf size Number of pages in the index ALTER TABLE t DROP INDEX vidxcd; SELECT index_name, stat_name, stat_description @@ -123,17 +158,24 @@ WHERE database_name = 'test' AND table_name = 't'; index_name stat_name stat_description GEN_CLUST_INDEX n_diff_pfx01 DB_ROW_ID GEN_CLUST_INDEX n_leaf_pages Number of leaf pages in the index +GEN_CLUST_INDEX n_nonnull_fld01 DB_ROW_ID GEN_CLUST_INDEX size Number of pages in the index idxb n_diff_pfx01 b idxb n_diff_pfx02 b,DB_ROW_ID idxb n_leaf_pages Number of leaf pages in the index +idxb n_nonnull_fld01 b +idxb n_nonnull_fld02 DB_ROW_ID idxb size Number of pages in the index vidxe n_diff_pfx01 e vidxe n_diff_pfx02 e,DB_ROW_ID vidxe n_leaf_pages Number of leaf pages in the index +vidxe n_nonnull_fld01 e +vidxe n_nonnull_fld02 DB_ROW_ID vidxe size Number of pages in the index vidxf n_diff_pfx01 f vidxf n_diff_pfx02 f,DB_ROW_ID vidxf n_leaf_pages Number of leaf pages in the index +vidxf n_nonnull_fld01 f +vidxf n_nonnull_fld02 DB_ROW_ID vidxf size Number of pages in the index DROP TABLE t; diff --git a/mysql-test/suite/innodb/r/innodb-index-online.result b/mysql-test/suite/innodb/r/innodb-index-online.result index aaba693a486..3ee2f2b2202 100644 --- a/mysql-test/suite/innodb/r/innodb-index-online.result +++ b/mysql-test/suite/innodb/r/innodb-index-online.result @@ -132,9 +132,11 @@ SELECT * FROM mysql.innodb_index_stats WHERE table_name IN ('t1'); database_name table_name index_name last_update stat_name stat_value sample_size stat_description test t1 PRIMARY LAST_UPDATE n_diff_pfx01 5 1 c1 test t1 PRIMARY LAST_UPDATE n_leaf_pages 1 NULL Number of leaf pages in the index +test t1 PRIMARY LAST_UPDATE n_nonnull_fld01 5 1 c1 test t1 PRIMARY LAST_UPDATE size 1 NULL Number of pages in the index test t1 c2 LAST_UPDATE n_diff_pfx01 5 1 c2 test t1 c2 LAST_UPDATE n_leaf_pages 1 NULL Number of leaf pages in the index +test t1 c2 LAST_UPDATE n_nonnull_fld01 5 1 c2 test t1 c2 LAST_UPDATE size 1 NULL Number of pages in the index CREATE TABLE t1_c2_stats SELECT * FROM mysql.innodb_index_stats WHERE database_name = 'test' AND table_name = 't1' and index_name = 'c2'; @@ -148,9 +150,11 @@ SELECT * FROM mysql.innodb_index_stats WHERE table_name IN ('t1', 't1_c2_stats') database_name table_name index_name last_update stat_name stat_value sample_size stat_description test t1 PRIMARY LAST_UPDATE n_diff_pfx01 5 1 c1 test t1 PRIMARY LAST_UPDATE n_leaf_pages 1 NULL Number of leaf pages in the index +test t1 PRIMARY LAST_UPDATE n_nonnull_fld01 5 1 c1 test t1 PRIMARY LAST_UPDATE size 1 NULL Number of pages in the index -test t1_c2_stats GEN_CLUST_INDEX LAST_UPDATE n_diff_pfx01 3 1 DB_ROW_ID +test t1_c2_stats GEN_CLUST_INDEX LAST_UPDATE n_diff_pfx01 4 1 DB_ROW_ID test t1_c2_stats GEN_CLUST_INDEX LAST_UPDATE n_leaf_pages 1 NULL Number of leaf pages in the index +test t1_c2_stats GEN_CLUST_INDEX LAST_UPDATE n_nonnull_fld01 4 1 DB_ROW_ID test t1_c2_stats GEN_CLUST_INDEX LAST_UPDATE size 1 NULL Number of pages in the index connection con1; KILL QUERY @id; diff --git a/mysql-test/suite/innodb/r/innodb_stats_auto_recalc.result b/mysql-test/suite/innodb/r/innodb_stats_auto_recalc.result index 40eae0a9385..9129224a802 100644 --- a/mysql-test/suite/innodb/r/innodb_stats_auto_recalc.result +++ b/mysql-test/suite/innodb/r/innodb_stats_auto_recalc.result @@ -10,6 +10,9 @@ index_name PRIMARY stat_name n_leaf_pages stat_value 1 index_name PRIMARY +stat_name n_nonnull_fld01 +stat_value 0 +index_name PRIMARY stat_name size stat_value 1 INSERT INTO autorecalc VALUES (1); @@ -25,6 +28,9 @@ index_name PRIMARY stat_name n_leaf_pages stat_value 1 index_name PRIMARY +stat_name n_nonnull_fld01 +stat_value 2 +index_name PRIMARY stat_name size stat_value 1 DELETE FROM autorecalc; @@ -39,6 +45,9 @@ index_name PRIMARY stat_name n_leaf_pages stat_value 1 index_name PRIMARY +stat_name n_nonnull_fld01 +stat_value 0 +index_name PRIMARY stat_name size stat_value 1 DROP TABLE autorecalc; diff --git a/mysql-test/suite/innodb/r/innodb_stats_auto_recalc_ddl.result b/mysql-test/suite/innodb/r/innodb_stats_auto_recalc_ddl.result index 8c68fe74504..4dc9824a5d8 100644 --- a/mysql-test/suite/innodb/r/innodb_stats_auto_recalc_ddl.result +++ b/mysql-test/suite/innodb/r/innodb_stats_auto_recalc_ddl.result @@ -12,6 +12,9 @@ index_name PRIMARY stat_name n_leaf_pages stat_value 1 index_name PRIMARY +stat_name n_nonnull_fld01 +stat_value 2 +index_name PRIMARY stat_name size stat_value 1 DROP TABLE arddl; @@ -29,6 +32,9 @@ index_name PRIMARY stat_name n_leaf_pages stat_value 1 index_name PRIMARY +stat_name n_nonnull_fld01 +stat_value 2 +index_name PRIMARY stat_name size stat_value 1 DROP TABLE arddl; diff --git a/mysql-test/suite/innodb/r/innodb_stats_auto_recalc_on_nonexistent.result b/mysql-test/suite/innodb/r/innodb_stats_auto_recalc_on_nonexistent.result index 9a216374125..aec77b12774 100644 --- a/mysql-test/suite/innodb/r/innodb_stats_auto_recalc_on_nonexistent.result +++ b/mysql-test/suite/innodb/r/innodb_stats_auto_recalc_on_nonexistent.result @@ -3,7 +3,7 @@ CREATE TABLE t (a INT, PRIMARY KEY (a)) ENGINE=INNODB; SELECT COUNT(*) FROM mysql.innodb_table_stats WHERE table_name = 't'; COUNT(*) 1 SELECT COUNT(*) FROM mysql.innodb_index_stats WHERE table_name = 't'; -COUNT(*) 3 +COUNT(*) 4 SELECT * FROM t; DELETE FROM mysql.innodb_index_stats WHERE table_name = 't'; DELETE FROM mysql.innodb_table_stats WHERE table_name = 't'; @@ -16,14 +16,14 @@ SELECT * FROM t; SELECT COUNT(*) FROM mysql.innodb_table_stats WHERE table_name = 't'; COUNT(*) 1 SELECT COUNT(*) FROM mysql.innodb_index_stats WHERE table_name = 't'; -COUNT(*) 3 +COUNT(*) 4 DROP TABLE t; Test with explicit enable CREATE TABLE t (a INT, PRIMARY KEY (a)) ENGINE=INNODB STATS_AUTO_RECALC=1; SELECT COUNT(*) FROM mysql.innodb_table_stats WHERE table_name = 't'; COUNT(*) 1 SELECT COUNT(*) FROM mysql.innodb_index_stats WHERE table_name = 't'; -COUNT(*) 3 +COUNT(*) 4 SELECT * FROM t; DELETE FROM mysql.innodb_index_stats WHERE table_name = 't'; DELETE FROM mysql.innodb_table_stats WHERE table_name = 't'; @@ -36,14 +36,14 @@ SELECT * FROM t; SELECT COUNT(*) FROM mysql.innodb_table_stats WHERE table_name = 't'; COUNT(*) 1 SELECT COUNT(*) FROM mysql.innodb_index_stats WHERE table_name = 't'; -COUNT(*) 3 +COUNT(*) 4 DROP TABLE t; Test with explicit disable CREATE TABLE t (a INT, PRIMARY KEY (a)) ENGINE=INNODB STATS_AUTO_RECALC=0; SELECT COUNT(*) FROM mysql.innodb_table_stats WHERE table_name = 't'; COUNT(*) 1 SELECT COUNT(*) FROM mysql.innodb_index_stats WHERE table_name = 't'; -COUNT(*) 3 +COUNT(*) 4 SELECT * FROM t; DELETE FROM mysql.innodb_index_stats WHERE table_name = 't'; DELETE FROM mysql.innodb_table_stats WHERE table_name = 't'; diff --git a/mysql-test/suite/innodb/r/innodb_stats_drop_locked.result b/mysql-test/suite/innodb/r/innodb_stats_drop_locked.result index 70f1edabf11..d06ae12e74b 100644 --- a/mysql-test/suite/innodb/r/innodb_stats_drop_locked.result +++ b/mysql-test/suite/innodb/r/innodb_stats_drop_locked.result @@ -25,6 +25,9 @@ innodb_stats_drop_locked innodb_stats_drop_locked innodb_stats_drop_locked innodb_stats_drop_locked +innodb_stats_drop_locked +innodb_stats_drop_locked +innodb_stats_drop_locked connect con1,localhost,root,,; SET innodb_lock_wait_timeout=1; ALTER TABLE innodb_stats_drop_locked DROP INDEX c_key; @@ -58,6 +61,9 @@ innodb_stats_drop_locked innodb_stats_drop_locked innodb_stats_drop_locked innodb_stats_drop_locked +innodb_stats_drop_locked +innodb_stats_drop_locked +innodb_stats_drop_locked ALTER TABLE innodb_stats_drop_locked DROP INDEX c_key; SELECT table_name FROM mysql.innodb_index_stats WHERE table_name='innodb_stats_drop_locked'; @@ -65,6 +71,7 @@ table_name innodb_stats_drop_locked innodb_stats_drop_locked innodb_stats_drop_locked +innodb_stats_drop_locked DROP TABLE innodb_stats_drop_locked; SELECT table_name FROM mysql.innodb_table_stats WHERE table_name='innodb_stats_drop_locked'; diff --git a/mysql-test/suite/innodb/r/innodb_stats_fetch.result b/mysql-test/suite/innodb/r/innodb_stats_fetch.result index a1194eb95ca..bf5d14f6610 100644 --- a/mysql-test/suite/innodb/r/innodb_stats_fetch.result +++ b/mysql-test/suite/innodb/r/innodb_stats_fetch.result @@ -36,6 +36,16 @@ stat_value 1 sample_size NULL stat_description Number of leaf pages in the index index_name PRIMARY +stat_name n_nonnull_fld01 +stat_value 0 +sample_size 1 +stat_description a +index_name PRIMARY +stat_name n_nonnull_fld02 +stat_value 0 +sample_size 1 +stat_description b +index_name PRIMARY stat_name size stat_value 1 sample_size NULL @@ -66,6 +76,26 @@ stat_value 1 sample_size NULL stat_description Number of leaf pages in the index index_name idx +stat_name n_nonnull_fld01 +stat_value 0 +sample_size 1 +stat_description c +index_name idx +stat_name n_nonnull_fld02 +stat_value 0 +sample_size 1 +stat_description d +index_name idx +stat_name n_nonnull_fld03 +stat_value 0 +sample_size 1 +stat_description a +index_name idx +stat_name n_nonnull_fld04 +stat_value 0 +sample_size 1 +stat_description b +index_name idx stat_name size stat_value 1 sample_size NULL diff --git a/mysql-test/suite/innodb/r/innodb_stats_rename_table.result b/mysql-test/suite/innodb/r/innodb_stats_rename_table.result index 20d82dff9ba..48de2c82432 100644 --- a/mysql-test/suite/innodb/r/innodb_stats_rename_table.result +++ b/mysql-test/suite/innodb/r/innodb_stats_rename_table.result @@ -24,6 +24,10 @@ stat_name n_leaf_pages stat_value 1 table_name stats_rename_old index_name PRIMARY +stat_name n_nonnull_fld01 +stat_value 0 +table_name stats_rename_old +index_name PRIMARY stat_name size stat_value 1 RENAME TABLE stats_rename_old TO stats_rename_new; @@ -45,6 +49,10 @@ stat_name n_leaf_pages stat_value 1 table_name stats_rename_new index_name PRIMARY +stat_name n_nonnull_fld01 +stat_value 0 +table_name stats_rename_new +index_name PRIMARY stat_name size stat_value 1 DROP TABLE stats_rename_new; diff --git a/mysql-test/suite/innodb/r/innodb_stats_rename_table_if_exists.result b/mysql-test/suite/innodb/r/innodb_stats_rename_table_if_exists.result index a966f629d7e..036e1d1c8e4 100644 --- a/mysql-test/suite/innodb/r/innodb_stats_rename_table_if_exists.result +++ b/mysql-test/suite/innodb/r/innodb_stats_rename_table_if_exists.result @@ -44,6 +44,10 @@ stat_name n_leaf_pages stat_value 1 table_name stats_rename1 index_name PRIMARY +stat_name n_nonnull_fld01 +stat_value 0 +table_name stats_rename1 +index_name PRIMARY stat_name size stat_value 1 table_name stats_rename1 @@ -56,6 +60,10 @@ stat_name n_leaf_pages stat_value 1 table_name stats_rename1 index_name b +stat_name n_nonnull_fld01 +stat_value 0 +table_name stats_rename1 +index_name b stat_name size stat_value 1 table_name stats_rename2 @@ -68,6 +76,10 @@ stat_name n_leaf_pages stat_value 567 table_name stats_rename2 index_name PRIMARY +stat_name n_nonnull_fld01 +stat_value 567 +table_name stats_rename2 +index_name PRIMARY stat_name size stat_value 567 table_name stats_rename2 @@ -80,6 +92,10 @@ stat_name n_leaf_pages stat_value 567 table_name stats_rename2 index_name b +stat_name n_nonnull_fld01 +stat_value 567 +table_name stats_rename2 +index_name b stat_name size stat_value 567 RENAME TABLE stats_rename1 TO stats_rename2; @@ -114,6 +130,10 @@ stat_name n_leaf_pages stat_value 1 table_name stats_rename2 index_name PRIMARY +stat_name n_nonnull_fld01 +stat_value 0 +table_name stats_rename2 +index_name PRIMARY stat_name size stat_value 1 table_name stats_rename2 @@ -126,6 +146,10 @@ stat_name n_leaf_pages stat_value 1 table_name stats_rename2 index_name c +stat_name n_nonnull_fld01 +stat_value 0 +table_name stats_rename2 +index_name c stat_name size stat_value 1 DROP TABLE stats_rename2; diff --git a/mysql-test/suite/innodb/r/instant_alter_index_rename.result b/mysql-test/suite/innodb/r/instant_alter_index_rename.result index 49f2213fbaf..ebccb27a2e0 100644 --- a/mysql-test/suite/innodb/r/instant_alter_index_rename.result +++ b/mysql-test/suite/innodb/r/instant_alter_index_rename.result @@ -208,14 +208,19 @@ SELECT table_name, index_name, stat_name FROM mysql.innodb_index_stats; table_name index_name stat_name t1 GEN_CLUST_INDEX n_diff_pfx01 t1 GEN_CLUST_INDEX n_leaf_pages +t1 GEN_CLUST_INDEX n_nonnull_fld01 t1 GEN_CLUST_INDEX size t1 ind1 n_diff_pfx01 t1 ind1 n_diff_pfx02 t1 ind1 n_leaf_pages +t1 ind1 n_nonnull_fld01 +t1 ind1 n_nonnull_fld02 t1 ind1 size t1 ind2 n_diff_pfx01 t1 ind2 n_diff_pfx02 t1 ind2 n_leaf_pages +t1 ind2 n_nonnull_fld01 +t1 ind2 n_nonnull_fld02 t1 ind2 size ALTER TABLE t1 DROP INDEX ind2, ADD INDEX ind3(b), DROP INDEX ind1, ADD INDEX ind2(c); @@ -223,24 +228,32 @@ SELECT table_name, index_name, stat_name FROM mysql.innodb_index_stats; table_name index_name stat_name t1 GEN_CLUST_INDEX n_diff_pfx01 t1 GEN_CLUST_INDEX n_leaf_pages +t1 GEN_CLUST_INDEX n_nonnull_fld01 t1 GEN_CLUST_INDEX size t1 ind2 n_diff_pfx01 t1 ind2 n_diff_pfx02 t1 ind2 n_leaf_pages +t1 ind2 n_nonnull_fld01 +t1 ind2 n_nonnull_fld02 t1 ind2 size t1 ind3 n_diff_pfx01 t1 ind3 n_diff_pfx02 t1 ind3 n_leaf_pages +t1 ind3 n_nonnull_fld01 +t1 ind3 n_nonnull_fld02 t1 ind3 size ALTER TABLE t1 DROP b, FORCE; SELECT table_name, index_name, stat_name FROM mysql.innodb_index_stats; table_name index_name stat_name t1 GEN_CLUST_INDEX n_diff_pfx01 t1 GEN_CLUST_INDEX n_leaf_pages +t1 GEN_CLUST_INDEX n_nonnull_fld01 t1 GEN_CLUST_INDEX size t1 ind2 n_diff_pfx01 t1 ind2 n_diff_pfx02 t1 ind2 n_leaf_pages +t1 ind2 n_nonnull_fld01 +t1 ind2 n_nonnull_fld02 t1 ind2 size UPDATE t1 SET a = 1 WHERE c = 'foo'; DROP TABLE t1; diff --git a/mysql-test/suite/innodb/r/records_in_range,4k.rdiff b/mysql-test/suite/innodb/r/records_in_range,4k.rdiff index 12b857ece22..d1a03aa78ad 100644 --- a/mysql-test/suite/innodb/r/records_in_range,4k.rdiff +++ b/mysql-test/suite/innodb/r/records_in_range,4k.rdiff @@ -1,4 +1,11 @@ -@@ -39,7 +39,7 @@ +@@ -33,13 +33,13 @@ + WHERE + table_name='records_in_range_test' AND stat_name = 'n_leaf_pages'; + index_name stat_name stat_value +-PRIMARY n_leaf_pages 1 ++PRIMARY n_leaf_pages 4 + SELECT index_name, stat_name, stat_value + FROM mysql.innodb_index_stats WHERE table_name='records_in_range_test' AND stat_name = 'size'; index_name stat_name stat_value diff --git a/mysql-test/suite/innodb/r/records_in_range,8k.rdiff b/mysql-test/suite/innodb/r/records_in_range,8k.rdiff index bd24af160ba..8085f295081 100644 --- a/mysql-test/suite/innodb/r/records_in_range,8k.rdiff +++ b/mysql-test/suite/innodb/r/records_in_range,8k.rdiff @@ -1,4 +1,11 @@ -@@ -39,7 +39,7 @@ +@@ -33,13 +33,13 @@ + WHERE + table_name='records_in_range_test' AND stat_name = 'n_leaf_pages'; + index_name stat_name stat_value +-PRIMARY n_leaf_pages 1 ++PRIMARY n_leaf_pages 2 + SELECT index_name, stat_name, stat_value + FROM mysql.innodb_index_stats WHERE table_name='records_in_range_test' AND stat_name = 'size'; index_name stat_name stat_value diff --git a/mysql-test/suite/innodb/r/records_in_range.result b/mysql-test/suite/innodb/r/records_in_range.result index e5a698f59cc..3ca1f241f0a 100644 --- a/mysql-test/suite/innodb/r/records_in_range.result +++ b/mysql-test/suite/innodb/r/records_in_range.result @@ -28,6 +28,8 @@ SET STATEMENT use_stat_tables=never FOR ANALYZE TABLE records_in_range_test; Table Op Msg_type Msg_text test.records_in_range_test analyze status OK +# n_leaf_pages changed because number of scanned leaf pages +# are assigned as leaf pages in case of multi level index SELECT index_name, stat_name, stat_value FROM mysql.innodb_index_stats WHERE diff --git a/mysql-test/suite/innodb/r/stats_external_pages.result b/mysql-test/suite/innodb/r/stats_external_pages.result new file mode 100644 index 00000000000..8c01c5ad7e7 --- /dev/null +++ b/mysql-test/suite/innodb/r/stats_external_pages.result @@ -0,0 +1,58 @@ +CREATE TABLE t_blob ( +id INT AUTO_INCREMENT PRIMARY KEY, +col_int INT, +col_text TEXT, +col_blob BLOB, +col_longtext LONGTEXT, +INDEX idx_int (col_int) +) ENGINE=InnoDB ROW_FORMAT=DYNAMIC; +INSERT INTO t_blob (col_int, col_text, col_blob, col_longtext) +SELECT +CASE WHEN seq <= 5000 THEN ((seq-1) % 500) + 1 ELSE NULL END, +REPEAT('A', 1000), +REPEAT('B', 5000), +REPEAT('C', 50000) +FROM seq_1_to_10000; +ANALYZE TABLE t_blob; +Table Op Msg_type Msg_text +test.t_blob analyze status Engine-independent statistics collected +test.t_blob analyze Warning Engine-independent statistics are not collected for column 'col_text' +test.t_blob analyze Warning Engine-independent statistics are not collected for column 'col_blob' +test.t_blob analyze Warning Engine-independent statistics are not collected for column 'col_longtext' +test.t_blob analyze status OK +SELECT @@global.innodb_stats_method AS stats_method, +index_name, +cardinality +FROM information_schema.STATISTICS +WHERE table_schema = 'test' + AND table_name = 't_blob' + AND index_name = 'idx_int'; +stats_method index_name cardinality +nulls_equal idx_int 1000 +DROP TABLE t_blob; +CREATE TABLE t_blob_compressed ( +id INT AUTO_INCREMENT PRIMARY KEY, +col_int INT, +col_longblob LONGBLOB, +INDEX idx_int (col_int) +) ENGINE=InnoDB ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=4; +INSERT INTO t_blob_compressed (col_int, col_longblob) +SELECT +CASE WHEN seq <= 5000 THEN ((seq-1) % 500) + 1 ELSE NULL END, +REPEAT('X', 100000) +FROM seq_1_to_10000; +ANALYZE TABLE t_blob_compressed; +Table Op Msg_type Msg_text +test.t_blob_compressed analyze status Engine-independent statistics collected +test.t_blob_compressed analyze Warning Engine-independent statistics are not collected for column 'col_longblob' +test.t_blob_compressed analyze status OK +SELECT @@global.innodb_stats_method AS stats_method, +index_name, +cardinality +FROM information_schema.STATISTICS +WHERE table_schema = 'test' + AND table_name = 't_blob_compressed' + AND index_name = 'idx_int'; +stats_method index_name cardinality +nulls_equal idx_int 1000 +DROP TABLE t_blob_compressed; diff --git a/mysql-test/suite/innodb/r/stats_method,NULLS_IGNORED.rdiff b/mysql-test/suite/innodb/r/stats_method,NULLS_IGNORED.rdiff new file mode 100644 index 00000000000..eb41bdb7e82 --- /dev/null +++ b/mysql-test/suite/innodb/r/stats_method,NULLS_IGNORED.rdiff @@ -0,0 +1,76 @@ +--- stats_method.result ++++ stats_method,NULLS_IGNORED.result +@@ -26,9 +26,9 @@ + WHERE table_schema = 'test' + AND table_name = 't_stats_method'; + stats_method index_name est_rows +-nulls_equal PRIMARY 100 +-nulls_equal idx_col1 11 +-nulls_equal idx_col2 6 ++nulls_ignored PRIMARY 100 ++nulls_ignored idx_col1 20 ++nulls_ignored idx_col2 10 + SELECT index_name, stat_name, stat_description, stat_value + FROM mysql.innodb_index_stats + WHERE table_name='t_stats_method' +@@ -76,12 +76,12 @@ + where table_name="t1" and index_name="PRIMARY" + and stat_name="n_diff_pfx01"; + stat_name stat_description +-n_diff_pfx01 c01꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵ ++n_diff_pfx01 c01꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵ NULLS_IGNORED + SELECT stat_name, stat_description FROM mysql.innodb_index_stats + where table_name="t1" and index_name="idx_all" + and stat_name="n_diff_pfx07"; + stat_name stat_description +-n_diff_pfx07 c33꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵,c34꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵,c35꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵,c36꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵,c37꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵ ++n_diff_pfx07 c33꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵,c34꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵,c35꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵,c36꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵,c37꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵ NULLS_IGNORED + DROP TABLE t1; + CREATE TABLE t_multilevel ( + id INT AUTO_INCREMENT PRIMARY KEY, +@@ -111,15 +111,15 @@ + AND stat_name IN ('n_diff_pfx01', 'n_nonnull_fld01', 'n_leaf_pages') + ORDER BY index_name, stat_name; + stats_method index_name stat_name stat_value +-nulls_equal idx_col1 n_diff_pfx01 501 +-nulls_equal idx_col1 n_leaf_pages 8 +-nulls_equal idx_col1 n_nonnull_fld01 2000 +-nulls_equal idx_col2 n_diff_pfx01 4001 +-nulls_equal idx_col2 n_leaf_pages 14 +-nulls_equal idx_col2 n_nonnull_fld01 8000 +-nulls_equal idx_col3 n_diff_pfx01 2501 +-nulls_equal idx_col3 n_leaf_pages 11 +-nulls_equal idx_col3 n_nonnull_fld01 5000 ++nulls_ignored idx_col1 n_diff_pfx01 8500 ++nulls_ignored idx_col1 n_leaf_pages 8 ++nulls_ignored idx_col1 n_nonnull_fld01 2000 ++nulls_ignored idx_col2 n_diff_pfx01 6000 ++nulls_ignored idx_col2 n_leaf_pages 14 ++nulls_ignored idx_col2 n_nonnull_fld01 8000 ++nulls_ignored idx_col3 n_diff_pfx01 7500 ++nulls_ignored idx_col3 n_leaf_pages 11 ++nulls_ignored idx_col3 n_nonnull_fld01 5000 + SELECT @@global.innodb_stats_method AS stats_method, + index_name, + cardinality +@@ -129,9 +129,9 @@ + AND index_name IN ('idx_col1', 'idx_col2', 'idx_col3') + ORDER BY index_name; + stats_method index_name cardinality +-nulls_equal idx_col1 525 +-nulls_equal idx_col2 4990 +-nulls_equal idx_col3 3326 ++nulls_ignored idx_col1 3326 ++nulls_ignored idx_col2 9980 ++nulls_ignored idx_col3 9980 + DROP TABLE t_multilevel; + CREATE TABLE t_not_null ( + id INT AUTO_INCREMENT PRIMARY KEY, +@@ -158,6 +158,6 @@ + PRIMARY n_nonnull_fld01 100 + idx_not_null n_diff_pfx01 10 + idx_not_null n_nonnull_fld01 100 +-idx_nullable n_diff_pfx01 11 ++idx_nullable n_diff_pfx01 60 + idx_nullable n_nonnull_fld01 50 + DROP TABLE t_not_null; diff --git a/mysql-test/suite/innodb/r/stats_method,NULLS_UNEQUAL.rdiff b/mysql-test/suite/innodb/r/stats_method,NULLS_UNEQUAL.rdiff new file mode 100644 index 00000000000..4d85da8532e --- /dev/null +++ b/mysql-test/suite/innodb/r/stats_method,NULLS_UNEQUAL.rdiff @@ -0,0 +1,76 @@ +--- stats_method.result ++++ stats_method,NULLS_UNEQUAL.result +@@ -26,9 +26,9 @@ + WHERE table_schema = 'test' + AND table_name = 't_stats_method'; + stats_method index_name est_rows +-nulls_equal PRIMARY 100 +-nulls_equal idx_col1 11 +-nulls_equal idx_col2 6 ++nulls_unequal PRIMARY 100 ++nulls_unequal idx_col1 100 ++nulls_unequal idx_col2 100 + SELECT index_name, stat_name, stat_description, stat_value + FROM mysql.innodb_index_stats + WHERE table_name='t_stats_method' +@@ -76,12 +76,12 @@ + where table_name="t1" and index_name="PRIMARY" + and stat_name="n_diff_pfx01"; + stat_name stat_description +-n_diff_pfx01 c01꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵ ++n_diff_pfx01 c01꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵ NULLS_UNEQUAL + SELECT stat_name, stat_description FROM mysql.innodb_index_stats + where table_name="t1" and index_name="idx_all" + and stat_name="n_diff_pfx07"; + stat_name stat_description +-n_diff_pfx07 c33꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵,c34꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵,c35꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵,c36꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵,c37꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵ ++n_diff_pfx07 c33꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵,c34꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵,c35꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵,c36꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵,c37꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵ NULLS_UNEQUAL + DROP TABLE t1; + CREATE TABLE t_multilevel ( + id INT AUTO_INCREMENT PRIMARY KEY, +@@ -111,15 +111,15 @@ + AND stat_name IN ('n_diff_pfx01', 'n_nonnull_fld01', 'n_leaf_pages') + ORDER BY index_name, stat_name; + stats_method index_name stat_name stat_value +-nulls_equal idx_col1 n_diff_pfx01 501 +-nulls_equal idx_col1 n_leaf_pages 8 +-nulls_equal idx_col1 n_nonnull_fld01 2000 +-nulls_equal idx_col2 n_diff_pfx01 4001 +-nulls_equal idx_col2 n_leaf_pages 14 +-nulls_equal idx_col2 n_nonnull_fld01 8000 +-nulls_equal idx_col3 n_diff_pfx01 2501 +-nulls_equal idx_col3 n_leaf_pages 11 +-nulls_equal idx_col3 n_nonnull_fld01 5000 ++nulls_unequal idx_col1 n_diff_pfx01 8500 ++nulls_unequal idx_col1 n_leaf_pages 8 ++nulls_unequal idx_col1 n_nonnull_fld01 2000 ++nulls_unequal idx_col2 n_diff_pfx01 6000 ++nulls_unequal idx_col2 n_leaf_pages 14 ++nulls_unequal idx_col2 n_nonnull_fld01 8000 ++nulls_unequal idx_col3 n_diff_pfx01 7500 ++nulls_unequal idx_col3 n_leaf_pages 11 ++nulls_unequal idx_col3 n_nonnull_fld01 5000 + SELECT @@global.innodb_stats_method AS stats_method, + index_name, + cardinality +@@ -129,9 +129,9 @@ + AND index_name IN ('idx_col1', 'idx_col2', 'idx_col3') + ORDER BY index_name; + stats_method index_name cardinality +-nulls_equal idx_col1 525 +-nulls_equal idx_col2 4990 +-nulls_equal idx_col3 3326 ++nulls_unequal idx_col1 9980 ++nulls_unequal idx_col2 9980 ++nulls_unequal idx_col3 9980 + DROP TABLE t_multilevel; + CREATE TABLE t_not_null ( + id INT AUTO_INCREMENT PRIMARY KEY, +@@ -158,6 +158,6 @@ + PRIMARY n_nonnull_fld01 100 + idx_not_null n_diff_pfx01 10 + idx_not_null n_nonnull_fld01 100 +-idx_nullable n_diff_pfx01 11 ++idx_nullable n_diff_pfx01 60 + idx_nullable n_nonnull_fld01 50 + DROP TABLE t_not_null; diff --git a/mysql-test/suite/innodb/r/stats_method.result b/mysql-test/suite/innodb/r/stats_method.result new file mode 100644 index 00000000000..23932f49bdd --- /dev/null +++ b/mysql-test/suite/innodb/r/stats_method.result @@ -0,0 +1,182 @@ +CREATE TABLE t_stats_method ( +id INT AUTO_INCREMENT PRIMARY KEY, +col1 INT, +col2 INT, +INDEX idx_col1 (col1), +INDEX idx_col2 (col2) +) ENGINE=InnoDB; +INSERT INTO t_stats_method (col1, col2) +SELECT +CASE +WHEN seq <= 50 THEN ((seq-1) % 10) + 1 +ELSE NULL +END, +CASE +WHEN seq <= 50 THEN ((seq-1) % 5) + 1 +ELSE NULL +END +FROM seq_1_to_100; +ANALYZE TABLE t_stats_method; +Table Op Msg_type Msg_text +test.t_stats_method analyze status OK +SELECT @@global.innodb_stats_method AS stats_method, +index_name, +cardinality AS est_rows +FROM information_schema.STATISTICS +WHERE table_schema = 'test' + AND table_name = 't_stats_method'; +stats_method index_name est_rows +nulls_equal PRIMARY 100 +nulls_equal idx_col1 11 +nulls_equal idx_col2 6 +SELECT index_name, stat_name, stat_description, stat_value +FROM mysql.innodb_index_stats +WHERE table_name='t_stats_method' +AND stat_name like 'n_nonnull_%'; +index_name stat_name stat_description stat_value +PRIMARY n_nonnull_fld01 id 100 +idx_col1 n_nonnull_fld01 col1 50 +idx_col1 n_nonnull_fld02 id 100 +idx_col2 n_nonnull_fld01 col2 50 +idx_col2 n_nonnull_fld02 id 100 +DROP TABLE t_stats_method; +SET NAMES utf8mb4; +BEGIN NOT ATOMIC +DECLARE c TEXT CHARACTER SET utf8mb4 DEFAULT ( +SELECT CONCAT('CREATE TABLE t1(', +GROUP_CONCAT(CONCAT('`c', LPAD(seq, 2, '0'), '꠵', +REPEAT('꠵', 59), '` INT') SEPARATOR ', '), ', ', +'PRIMARY KEY(', +(SELECT GROUP_CONCAT(CONCAT('`c', LPAD(seq, 2, '0'), '꠵', +REPEAT('꠵', 59), '`') SEPARATOR ', ') +FROM seq_1_to_32), +'), KEY `idx_all`(', +(SELECT GROUP_CONCAT(CONCAT('`c', LPAD(seq, 2, '0'), '꠵', +REPEAT('꠵', 59), '`') SEPARATOR ', ') +FROM seq_33_to_64), +')) ENGINE=InnoDB STATS_PERSISTENT=1') +FROM seq_1_to_64); +EXECUTE IMMEDIATE c; +END; +$$ +INSERT INTO t1 +SELECT seq, seq, seq, seq, seq, seq, seq, seq, +seq, seq, seq, seq, seq, seq, seq, seq, +seq, seq, seq, seq, seq, seq, seq, seq, +seq, seq, seq, seq, seq, seq, seq, seq, +seq, seq, seq, seq, seq, seq, seq, seq, +seq, seq, seq, seq, seq, seq, seq, seq, +seq, seq, seq, seq, seq, seq, seq, seq, +seq, seq, seq, seq, seq, seq, seq, seq +FROM seq_1_to_320; +ANALYZE TABLE t1; +Table Op Msg_type Msg_text +test.t1 analyze status OK +SELECT stat_name, stat_description FROM mysql.innodb_index_stats +where table_name="t1" and index_name="PRIMARY" +and stat_name="n_diff_pfx01"; +stat_name stat_description +n_diff_pfx01 c01꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵ +SELECT stat_name, stat_description FROM mysql.innodb_index_stats +where table_name="t1" and index_name="idx_all" +and stat_name="n_diff_pfx07"; +stat_name stat_description +n_diff_pfx07 c33꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵,c34꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵,c35꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵,c36꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵,c37꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵꠵ +DROP TABLE t1; +CREATE TABLE t_multilevel ( +id INT AUTO_INCREMENT PRIMARY KEY, +col1 INT, +col2 INT, +col3 INT, +INDEX idx_col1 (col1), +INDEX idx_col2 (col2), +INDEX idx_col3 (col3) +) ENGINE=InnoDB STATS_PERSISTENT=1 STATS_SAMPLE_PAGES=20; +INSERT INTO t_multilevel (col1, col2, col3) +SELECT +CASE WHEN seq <= 2000 THEN ((seq-1) % 500) + 1 ELSE NULL END, +CASE WHEN seq <= 8000 THEN ((seq-1) % 4000) + 1 ELSE NULL END, +CASE WHEN seq <= 5000 THEN ((seq-1) % 2500) + 1 ELSE NULL END +FROM seq_1_to_10000; +ANALYZE TABLE t_multilevel; +Table Op Msg_type Msg_text +test.t_multilevel analyze status OK +SELECT @@global.innodb_stats_method AS stats_method, +index_name, +stat_name, +stat_value +FROM mysql.innodb_index_stats +WHERE table_name='t_multilevel' + AND index_name IN ('idx_col1', 'idx_col2', 'idx_col3') +AND stat_name IN ('n_diff_pfx01', 'n_nonnull_fld01', 'n_leaf_pages') +ORDER BY index_name, stat_name; +stats_method index_name stat_name stat_value +nulls_equal idx_col1 n_diff_pfx01 501 +nulls_equal idx_col1 n_leaf_pages 8 +nulls_equal idx_col1 n_nonnull_fld01 2000 +nulls_equal idx_col2 n_diff_pfx01 4001 +nulls_equal idx_col2 n_leaf_pages 14 +nulls_equal idx_col2 n_nonnull_fld01 8000 +nulls_equal idx_col3 n_diff_pfx01 2501 +nulls_equal idx_col3 n_leaf_pages 11 +nulls_equal idx_col3 n_nonnull_fld01 5000 +SELECT @@global.innodb_stats_method AS stats_method, +index_name, +cardinality +FROM information_schema.STATISTICS +WHERE table_schema = 'test' + AND table_name = 't_multilevel' + AND index_name IN ('idx_col1', 'idx_col2', 'idx_col3') +ORDER BY index_name; +stats_method index_name cardinality +nulls_equal idx_col1 525 +nulls_equal idx_col2 4990 +nulls_equal idx_col3 3326 +DROP TABLE t_multilevel; +CREATE TABLE t_not_null ( +id INT AUTO_INCREMENT PRIMARY KEY, +col_nullable INT, +col_not_null INT NOT NULL, +INDEX idx_nullable (col_nullable), +INDEX idx_not_null (col_not_null) +) ENGINE=InnoDB STATS_PERSISTENT=1; +INSERT INTO t_not_null (col_nullable, col_not_null) +SELECT +CASE WHEN seq <= 50 THEN ((seq-1) % 10) + 1 ELSE NULL END, +((seq-1) % 10) + 1 +FROM seq_1_to_100; +ANALYZE TABLE t_not_null; +Table Op Msg_type Msg_text +test.t_not_null analyze status OK +SELECT index_name, stat_name, stat_value +FROM mysql.innodb_index_stats +WHERE table_name='t_not_null' +AND stat_name IN ('n_diff_pfx01', 'n_nonnull_fld01') +ORDER BY index_name, stat_name; +index_name stat_name stat_value +PRIMARY n_diff_pfx01 100 +PRIMARY n_nonnull_fld01 100 +idx_not_null n_diff_pfx01 10 +idx_not_null n_nonnull_fld01 100 +idx_nullable n_diff_pfx01 11 +idx_nullable n_nonnull_fld01 50 +DROP TABLE t_not_null; +CREATE TABLE t_no_nulls ( +id INT AUTO_INCREMENT PRIMARY KEY, +a INT NOT NULL, b INT NOT NULL, c INT NOT NULL, +KEY k (a, b, c) +) ENGINE=InnoDB STATS_PERSISTENT=1 STATS_SAMPLE_PAGES=5; +INSERT INTO t_no_nulls (a, b, c) +SELECT seq % 2, seq % 2, seq % 2 FROM seq_1_to_20000; +ANALYZE TABLE t_no_nulls; +Table Op Msg_type Msg_text +test.t_no_nulls analyze status OK +SELECT index_name, seq_in_index, column_name, cardinality < 100 AS sane_cardinality +FROM information_schema.STATISTICS +WHERE table_schema='test' AND table_name='t_no_nulls' AND index_name='k' +ORDER BY seq_in_index; +index_name seq_in_index column_name sane_cardinality +k 1 a 1 +k 2 b 1 +k 3 c 1 +DROP TABLE t_no_nulls; diff --git a/mysql-test/suite/innodb/t/records_in_range.test b/mysql-test/suite/innodb/t/records_in_range.test index 697dbc1e8dd..0fc87692a90 100644 --- a/mysql-test/suite/innodb/t/records_in_range.test +++ b/mysql-test/suite/innodb/t/records_in_range.test @@ -43,6 +43,8 @@ ANALYZE TABLE records_in_range_test; # 16k or bigger page size: 1 leaf page # 8k page size: 2 leaf pages # 4k page size: 4 leaf pages +--echo # n_leaf_pages changed because number of scanned leaf pages +--echo # are assigned as leaf pages in case of multi level index SELECT index_name, stat_name, stat_value FROM mysql.innodb_index_stats WHERE diff --git a/mysql-test/suite/innodb/t/stats_external_pages.test b/mysql-test/suite/innodb/t/stats_external_pages.test new file mode 100644 index 00000000000..3489bcdf9f9 --- /dev/null +++ b/mysql-test/suite/innodb/t/stats_external_pages.test @@ -0,0 +1,60 @@ +--source include/have_innodb.inc +--source include/have_sequence.inc + +CREATE TABLE t_blob ( + id INT AUTO_INCREMENT PRIMARY KEY, + col_int INT, + col_text TEXT, + col_blob BLOB, + col_longtext LONGTEXT, + INDEX idx_int (col_int) +) ENGINE=InnoDB ROW_FORMAT=DYNAMIC; + +# Insert data with large BLOBs that will be stored externally +INSERT INTO t_blob (col_int, col_text, col_blob, col_longtext) +SELECT + CASE WHEN seq <= 5000 THEN ((seq-1) % 500) + 1 ELSE NULL END, + REPEAT('A', 1000), + REPEAT('B', 5000), + REPEAT('C', 50000) +FROM seq_1_to_10000; + +ANALYZE TABLE t_blob; + +SELECT @@global.innodb_stats_method AS stats_method, + index_name, + cardinality +FROM information_schema.STATISTICS +WHERE table_schema = 'test' + AND table_name = 't_blob' + AND index_name = 'idx_int'; + +DROP TABLE t_blob; + +# Test with compressed table (different blob_part_size calculation) +CREATE TABLE t_blob_compressed ( + id INT AUTO_INCREMENT PRIMARY KEY, + col_int INT, + col_longblob LONGBLOB, + INDEX idx_int (col_int) +) ENGINE=InnoDB ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=4; + +# Insert large BLOBs +INSERT INTO t_blob_compressed (col_int, col_longblob) +SELECT + CASE WHEN seq <= 5000 THEN ((seq-1) % 500) + 1 ELSE NULL END, + REPEAT('X', 100000) +FROM seq_1_to_10000; + +ANALYZE TABLE t_blob_compressed; + + +SELECT @@global.innodb_stats_method AS stats_method, + index_name, + cardinality +FROM information_schema.STATISTICS +WHERE table_schema = 'test' + AND table_name = 't_blob_compressed' + AND index_name = 'idx_int'; + +DROP TABLE t_blob_compressed; diff --git a/mysql-test/suite/innodb/t/stats_method.combinations b/mysql-test/suite/innodb/t/stats_method.combinations new file mode 100644 index 00000000000..47325259998 --- /dev/null +++ b/mysql-test/suite/innodb/t/stats_method.combinations @@ -0,0 +1,11 @@ +[NULLS_EQUAL] +--innodb_stats_method=nulls_equal +--use_stat_tables=never + +[NULLS_UNEQUAL] +--innodb_stats_method=nulls_unequal +--use_stat_tables=never + +[NULLS_IGNORED] +--innodb_stats_method=nulls_ignored +--use_stat_tables=never diff --git a/mysql-test/suite/innodb/t/stats_method.test b/mysql-test/suite/innodb/t/stats_method.test new file mode 100644 index 00000000000..36074cd72fb --- /dev/null +++ b/mysql-test/suite/innodb/t/stats_method.test @@ -0,0 +1,218 @@ +--source include/have_innodb.inc +--source include/have_sequence.inc + +# Test case to demonstrate different innodb_stats_method settings +CREATE TABLE t_stats_method ( + id INT AUTO_INCREMENT PRIMARY KEY, + col1 INT, + col2 INT, + INDEX idx_col1 (col1), + INDEX idx_col2 (col2) +) ENGINE=InnoDB; + +# For col1: 50 NULLs, 10 distinct non-NULL values +# For col2: 50 NULLs, 5 distinct non-NULL values +INSERT INTO t_stats_method (col1, col2) +SELECT + CASE + WHEN seq <= 50 THEN ((seq-1) % 10) + 1 + ELSE NULL + END, + CASE + WHEN seq <= 50 THEN ((seq-1) % 5) + 1 + ELSE NULL + END +FROM seq_1_to_100; + +ANALYZE TABLE t_stats_method; + +# cardinality = number of distinct value +# Nulls_equal: +#============== +# idx_col1: 50 NULLs + 10 distinct value = 11 +# idx_col2: 50 NULLs + 5 distinct value = 6 + +# Nulls_unequal: +#============== +# idx_col1: 50 NULLs + 10 distinct value = 60 +# idx_col2: 50 NULLs + 5 distinct value = 55 + +# Nulls_ignored: +#=============== + +# innodb_rec_per_key = +# number of non-null records / number of distinct keys +# +# cardinality = number of records / innodb_rec_per_key; +# idx_col1: 50 NULLs + 10 distinct value + +# innodb_rec_per_key = 50/10 = 5 +# cardinality = 100 / 5 = 20 + +# idx_col2: 50 NULLs + 5 distinct value +# innodb_rec_per_key = 50 / 5 = 10 +# cardinality = 100 / 10 = 10 + +SELECT @@global.innodb_stats_method AS stats_method, + index_name, + cardinality AS est_rows +FROM information_schema.STATISTICS +WHERE table_schema = 'test' + AND table_name = 't_stats_method'; + +SELECT index_name, stat_name, stat_description, stat_value +FROM mysql.innodb_index_stats +WHERE table_name='t_stats_method' +AND stat_name like 'n_nonnull_%'; + +DROP TABLE t_stats_method; + +SET NAMES utf8mb4; +# Test maximum number of index columns (64) for statistics calculation +DELIMITER $$; +BEGIN NOT ATOMIC + DECLARE c TEXT CHARACTER SET utf8mb4 DEFAULT ( + SELECT CONCAT('CREATE TABLE t1(', + GROUP_CONCAT(CONCAT('`c', LPAD(seq, 2, '0'), '꠵', + REPEAT('꠵', 59), '` INT') SEPARATOR ', '), ', ', + 'PRIMARY KEY(', + (SELECT GROUP_CONCAT(CONCAT('`c', LPAD(seq, 2, '0'), '꠵', + REPEAT('꠵', 59), '`') SEPARATOR ', ') + FROM seq_1_to_32), + '), KEY `idx_all`(', + (SELECT GROUP_CONCAT(CONCAT('`c', LPAD(seq, 2, '0'), '꠵', + REPEAT('꠵', 59), '`') SEPARATOR ', ') + FROM seq_33_to_64), + ')) ENGINE=InnoDB STATS_PERSISTENT=1') + FROM seq_1_to_64); + EXECUTE IMMEDIATE c; +END; +$$ +DELIMITER ;$$ + + +INSERT INTO t1 +SELECT seq, seq, seq, seq, seq, seq, seq, seq, + seq, seq, seq, seq, seq, seq, seq, seq, + seq, seq, seq, seq, seq, seq, seq, seq, + seq, seq, seq, seq, seq, seq, seq, seq, + seq, seq, seq, seq, seq, seq, seq, seq, + seq, seq, seq, seq, seq, seq, seq, seq, + seq, seq, seq, seq, seq, seq, seq, seq, + seq, seq, seq, seq, seq, seq, seq, seq +FROM seq_1_to_320; + +ANALYZE TABLE t1; + +# stats_method appended at the end of stat_description in case +# of non-default value +SELECT stat_name, stat_description FROM mysql.innodb_index_stats +where table_name="t1" and index_name="PRIMARY" +and stat_name="n_diff_pfx01"; + +# Truncation of stat_description +SELECT stat_name, stat_description FROM mysql.innodb_index_stats +where table_name="t1" and index_name="idx_all" +and stat_name="n_diff_pfx07"; + +DROP TABLE t1; + +# Test cardinality calculation with multi-level tree +CREATE TABLE t_multilevel ( + id INT AUTO_INCREMENT PRIMARY KEY, + col1 INT, + col2 INT, + col3 INT, + INDEX idx_col1 (col1), + INDEX idx_col2 (col2), + INDEX idx_col3 (col3) +) ENGINE=InnoDB STATS_PERSISTENT=1 STATS_SAMPLE_PAGES=20; + +# col1: 8000 NULLs + 2000 non-NULLs with 500 distinct values +# col2: 2000 NULLs + 8000 non-NULLs with 4000 distinct values +# col3: 5000 NULLs + 5000 non-NULLs with 2500 distinct values +INSERT INTO t_multilevel (col1, col2, col3) +SELECT + CASE WHEN seq <= 2000 THEN ((seq-1) % 500) + 1 ELSE NULL END, + CASE WHEN seq <= 8000 THEN ((seq-1) % 4000) + 1 ELSE NULL END, + CASE WHEN seq <= 5000 THEN ((seq-1) % 2500) + 1 ELSE NULL END +FROM seq_1_to_10000; + +ANALYZE TABLE t_multilevel; + +# Show detailed statistics for all columns +SELECT @@global.innodb_stats_method AS stats_method, + index_name, + stat_name, + stat_value +FROM mysql.innodb_index_stats +WHERE table_name='t_multilevel' + AND index_name IN ('idx_col1', 'idx_col2', 'idx_col3') + AND stat_name IN ('n_diff_pfx01', 'n_nonnull_fld01', 'n_leaf_pages') +ORDER BY index_name, stat_name; + +# Show cardinality from information_schema +SELECT @@global.innodb_stats_method AS stats_method, + index_name, + cardinality +FROM information_schema.STATISTICS +WHERE table_schema = 'test' + AND table_name = 't_multilevel' + AND index_name IN ('idx_col1', 'idx_col2', 'idx_col3') +ORDER BY index_name; + +DROP TABLE t_multilevel; + +# Test with NOT NULL columns to verify n_non_null_key_vals = n_diff_key_vals +CREATE TABLE t_not_null ( + id INT AUTO_INCREMENT PRIMARY KEY, + col_nullable INT, + col_not_null INT NOT NULL, + INDEX idx_nullable (col_nullable), + INDEX idx_not_null (col_not_null) +) ENGINE=InnoDB STATS_PERSISTENT=1; + +# Insert data: nullable has NULLs, not_null has no NULLs +INSERT INTO t_not_null (col_nullable, col_not_null) +SELECT + CASE WHEN seq <= 50 THEN ((seq-1) % 10) + 1 ELSE NULL END, + ((seq-1) % 10) + 1 +FROM seq_1_to_100; + +ANALYZE TABLE t_not_null; + +# For NOT NULL column: n_nonnull should equal n_diff +# For nullable column: n_nonnull should be less than total records +SELECT index_name, stat_name, stat_value +FROM mysql.innodb_index_stats +WHERE table_name='t_not_null' +AND stat_name IN ('n_diff_pfx01', 'n_nonnull_fld01') +ORDER BY index_name, stat_name; + +DROP TABLE t_not_null; + +# An index that contains no NULL at all: +# innodb_stats_method must not influence any estimate, +# so the cardinality below has to come out the same +# under all three methods. STATS_SAMPLE_PAGES is low relative +# to the leaf page count, so statistics are sampled rather +# than fully scanned; that is the path +# on which the per-column non-null count of a NOT NULL column is an +# extrapolated record count rather than the record count itself. +CREATE TABLE t_no_nulls ( + id INT AUTO_INCREMENT PRIMARY KEY, + a INT NOT NULL, b INT NOT NULL, c INT NOT NULL, + KEY k (a, b, c) +) ENGINE=InnoDB STATS_PERSISTENT=1 STATS_SAMPLE_PAGES=5; + +INSERT INTO t_no_nulls (a, b, c) +SELECT seq % 2, seq % 2, seq % 2 FROM seq_1_to_20000; + +ANALYZE TABLE t_no_nulls; + +SELECT index_name, seq_in_index, column_name, cardinality < 100 AS sane_cardinality +FROM information_schema.STATISTICS +WHERE table_schema='test' AND table_name='t_no_nulls' AND index_name='k' +ORDER BY seq_in_index; + +DROP TABLE t_no_nulls; diff --git a/storage/innobase/btr/btr0cur.cc b/storage/innobase/btr/btr0cur.cc index 99c26ff3a67..d34b18339c0 100644 --- a/storage/innobase/btr/btr0cur.cc +++ b/storage/innobase/btr/btr0cur.cc @@ -111,19 +111,6 @@ uint btr_cur_limit_optimistic_insert_debug; can be released by page reorganize, then it is reorganized */ #define BTR_CUR_PAGE_REORGANIZE_LIMIT (srv_page_size / 32) -/** The structure of a BLOB part header */ -/* @{ */ -/*--------------------------------------*/ -#define BTR_BLOB_HDR_PART_LEN 0 /*!< BLOB part len on this - page */ -#define BTR_BLOB_HDR_NEXT_PAGE_NO 4 /*!< next BLOB part page no, - FIL_NULL if none */ -/*--------------------------------------*/ -#define BTR_BLOB_HDR_SIZE 8 /*!< Size of a BLOB - part header, in bytes */ - -/* @} */ - /*******************************************************************//** Marks all extern fields in a record as owned by the record. This function should be called if the delete mark of a record is removed: a not delete @@ -5444,69 +5431,19 @@ error: /*================== EXTERNAL STORAGE OF BIG FIELDS ===================*/ -/***********************************************************//** -Gets the offset of the pointer to the externally stored part of a field. +/** Gets the offset of the pointer to the externally stored part of a field. +@param offsets offsets of record +@param n index of the external field @return offset of the pointer to the externally stored part */ -static -ulint -btr_rec_get_field_ref_offs( -/*=======================*/ - const rec_offs* offsets,/*!< in: array returned by rec_get_offsets() */ - ulint n) /*!< in: index of the external field */ +size_t btr_rec_get_field_ref_offs(const rec_offs *offsets, size_t n) noexcept { - ulint field_ref_offs; - ulint local_len; + size_t local_len; + ut_ad(rec_offs_nth_extern(offsets, n)); + rec_offs field_ref_offs= rec_get_nth_field_offs(offsets, n, &local_len); + ut_ad(len_is_stored(local_len)); + ut_ad(local_len >= BTR_EXTERN_FIELD_REF_SIZE); - ut_a(rec_offs_nth_extern(offsets, n)); - field_ref_offs = rec_get_nth_field_offs(offsets, n, &local_len); - ut_a(len_is_stored(local_len)); - ut_a(local_len >= BTR_EXTERN_FIELD_REF_SIZE); - - return(field_ref_offs + local_len - BTR_EXTERN_FIELD_REF_SIZE); -} - -/** Gets a pointer to the externally stored part of a field. -@param rec record -@param offsets rec_get_offsets(rec) -@param n index of the externally stored field -@return pointer to the externally stored part */ -#define btr_rec_get_field_ref(rec, offsets, n) \ - ((rec) + btr_rec_get_field_ref_offs(offsets, n)) - -/** Gets the externally stored size of a record, in units of a database page. -@param[in] rec record -@param[in] offsets array returned by rec_get_offsets() -@return externally stored part, in units of a database page */ -ulint -btr_rec_get_externally_stored_len( - const rec_t* rec, - const rec_offs* offsets) -{ - ulint n_fields; - ulint total_extern_len = 0; - ulint i; - - ut_ad(!rec_offs_comp(offsets) || !rec_get_node_ptr_flag(rec)); - - if (!rec_offs_any_extern(offsets)) { - return(0); - } - - n_fields = rec_offs_n_fields(offsets); - - for (i = 0; i < n_fields; i++) { - if (rec_offs_nth_extern(offsets, i)) { - - ulint extern_len = mach_read_from_4( - btr_rec_get_field_ref(rec, offsets, i) - + BTR_EXTERN_LEN + 4); - - total_extern_len += ut_calc_align( - extern_len, ulint(srv_page_size)); - } - } - - return total_extern_len >> srv_page_size_shift; + return field_ref_offs + local_len - BTR_EXTERN_FIELD_REF_SIZE; } /*******************************************************************//** @@ -5880,7 +5817,6 @@ btr_store_big_rec_extern_fields( for (i = 0; i < big_rec_vec->n_fields; i++) { const ulint field_no = big_rec_vec->fields[i].field_no; - field_ref = btr_rec_get_field_ref(rec, offsets, field_no); #if defined UNIV_DEBUG || defined UNIV_BLOB_LIGHT_DEBUG /* A zero BLOB pointer should have been initially inserted. */ diff --git a/storage/innobase/btr/btr0pcur.cc b/storage/innobase/btr/btr0pcur.cc index 2b7803c8cdd..2fd50eb5736 100644 --- a/storage/innobase/btr/btr0pcur.cc +++ b/storage/innobase/btr/btr0pcur.cc @@ -474,17 +474,11 @@ btr_pcur_t::restore_position(btr_latch_mode restore_latch_mode, mtr_t *mtr) return ret_val; } -/*********************************************************//** -Moves the persistent cursor to the first record on the next page. Releases the -latch on the current page, and bufferunfixes it. Note that there must not be -modifications on the current page, as then the x-latch can be released only in -mtr_commit. */ dberr_t btr_pcur_move_to_next_page( -/*=======================*/ - btr_pcur_t* cursor, /*!< in: persistent cursor; must be on the - last record of the current page */ - mtr_t* mtr) /*!< in: mtr */ + btr_pcur_t* cursor, + mtr_t* mtr, + bool retain_latch) { ut_ad(cursor->pos_state == BTR_PCUR_IS_POSITIONED); ut_ad(cursor->latch_mode != BTR_NO_LATCHES); @@ -529,8 +523,11 @@ btr_pcur_move_to_next_page( ut_d(page_check_dir(next_page)); - const auto s = mtr->get_savepoint(); - mtr->rollback_to_savepoint(s - 2, s - 1); + if (!retain_latch) { + const auto s = mtr->get_savepoint(); + mtr->rollback_to_savepoint(s - 2, s - 1); + } + if (first_access) { buf_read_ahead_linear(next_block->page.id()); } diff --git a/storage/innobase/dict/dict0stats.cc b/storage/innobase/dict/dict0stats.cc index 1b64a5be866..00e10f9d84c 100644 --- a/storage/innobase/dict/dict0stats.cc +++ b/storage/innobase/dict/dict0stats.cc @@ -35,6 +35,8 @@ Created Jan 06, 2010 Vasil Dimov #include "que0que.h" #include "scope.h" #include "debug_sync.h" +#include "btr0cur.h" +#include "small_vector.h" #ifdef WITH_WSREP # include #endif @@ -44,87 +46,66 @@ Created Jan 06, 2010 Vasil Dimov #include #include -/* Sampling algorithm description @{ +/** Gets the externally stored size of a record, in units of a database page. +@param rec record +@param offsets array returned by rec_get_offsets() +@param table table containing the record +@return externally stored size in units of a database page */ +static uint32_t rec_get_n_blob_pages(const rec_t *rec, + const rec_offs *offsets, + const dict_table_t *table) noexcept +{ + ut_ad(!rec_offs_comp(offsets) || !rec_get_node_ptr_flag(rec)); + if (!rec_offs_any_extern(offsets)) + return 0; + uint32_t n_blobs= 0; + const uint32_t blob_part_size= (DICT_TF_MASK_ZIP_SSIZE & table->flags) + ? uint32_t(dict_tf_get_zip_size(table->flags)) - FIL_PAGE_DATA + : uint32_t(srv_page_size) - FIL_PAGE_DATA - + BTR_BLOB_HDR_SIZE - FIL_PAGE_DATA_END; + for (ulint i= rec_offs_n_fields(offsets); i--; ) + { + if (rec_offs_nth_extern(offsets, i)) + { + uint32_t len= + mach_read_from_4(btr_rec_get_field_ref(rec, offsets, i) + + BTR_EXTERN_LEN + 4); + n_blobs+= (len + blob_part_size - 1) / blob_part_size; + } + } + return n_blobs; +} -The algorithm is controlled by one number - N_SAMPLE_PAGES(index), -let it be A, which is the number of leaf pages to analyze for a given index -for each n-prefix (if the index is on 3 columns, then 3*A leaf pages will be -analyzed). +/* Persistent-statistics sampling algorithm (overview). -Let the total number of leaf pages in the table be T. -Level 0 - leaf pages, level H - root. +Estimates each index's cardinality by sampling A = N_SAMPLE_PAGES leaf pages +per n-column prefix (n = 1..n_uniq), rather than scanning all L leaf pages. -Definition: N-prefix-boring record is a record on a non-leaf page that equals -the next (to the right, cross page boundaries, skipping the supremum and -infimum) record on the same level when looking at the fist n-prefix columns. -The last (user) record on a level is not boring (it does not match the -non-existent user record to the right). We call the records boring because all -the records on the page below a boring record are equal to that boring record. +Symbols: +A = sampled pages/prefix; +L = size of BTR_SEG_LEAF (including possible BLOB pages in clustered index) +L_ORD = number of geniue (non-blob) leaf pages +LA = level chosen to sample from; see dict_stats_index_set_n_diff() [REF01] +for the final estimator and the meaning of TOTAL_LA, N_DIFF_LA, Pi, R. -We avoid diving below boring records when searching for a leaf page to -estimate the number of distinct records because we know that such a leaf -page will have number of distinct records == 1. - -For each n-prefix: start from the root level and full scan subsequent lower -levels until a level that contains at least A*10 distinct records is found. -Lets call this level LA. -As an optimization the search is canceled if it has reached level 1 (never -descend to the level 0 (leaf)) and also if the next level to be scanned -would contain more than A pages. The latter is because the user has asked -to analyze A leaf pages and it does not make sense to scan much more than -A non-leaf pages with the sole purpose of finding a good sample of A leaf -pages. - -After finding the appropriate level LA with >A*10 distinct records (or less in -the exceptions described above), divide it into groups of equal records and -pick A such groups. Then pick the last record from each group. For example, -let the level be: - -index: 0,1,2,3,4,5,6,7,8,9,10 -record: 1,1,1,2,2,7,7,7,7,7,9 - -There are 4 groups of distinct records and if A=2 random ones are selected, -e.g. 1,1,1 and 7,7,7,7,7, then records with indexes 2 and 9 will be selected. - -After selecting A records as described above, dive below them to find A leaf -pages and analyze them, finding the total number of distinct records. The -dive to the leaf level is performed by selecting a non-boring record from -each page and diving below it. - -This way, a total of A leaf pages are analyzed for the given n-prefix. - -Let the number of different key values found in each leaf page i be Pi (i=1..A). -Let N_DIFF_AVG_LEAF be (P1 + P2 + ... + PA) / A. -Let the number of different key values on level LA be N_DIFF_LA. -Let the total number of records on level LA be TOTAL_LA. -Let R be N_DIFF_LA / TOTAL_LA, we assume this ratio is the same on the -leaf level. -Let the number of leaf pages be N. -Then the total number of different key values on the leaf level is: -N * R * N_DIFF_AVG_LEAF. -See REF01 for the implementation. - -The above describes how to calculate the cardinality of an index. -This algorithm is executed for each n-prefix of a multi-column index -where n=1..n_uniq. -@} */ +Call graph: + dict_stats_analyze_index() driver; loops over n_prefix + IndexLevelStats::analyze_level() full-scan one level, collect n_diff[] + IndexLevelStats::sample_leaf_pages() pick A records on LA, dive below each + PageStats::scan_below() descend to a leaf via non-boring recs + PageStats::scan() full leaf scan: distinct/non-null/BLOB + dict_stats_index_set_n_diff() [REF01] turn samples into cardinality */ /* names of the tables from the persistent statistics storage */ #define TABLE_STATS_NAME_PRINT "mysql.innodb_table_stats" #define INDEX_STATS_NAME_PRINT "mysql.innodb_index_stats" - -#ifdef UNIV_STATS_DEBUG -#define DEBUG_PRINTF(fmt, ...) printf(fmt, ## __VA_ARGS__) -#else /* UNIV_STATS_DEBUG */ #define DEBUG_PRINTF(fmt, ...) /* noop */ -#endif /* UNIV_STATS_DEBUG */ /* Gets the number of leaf pages to sample in persistent stats estimation */ #define N_SAMPLE_PAGES(index) \ - static_cast( \ (index)->table->stats_sample_pages != 0 \ ? (index)->table->stats_sample_pages \ - : srv_stats_persistent_sample_pages) + : srv_stats_persistent_sample_pages /* number of distinct records on a given level that are required to stop descending to lower levels and fetch N_SAMPLE_PAGES(index) records @@ -136,7 +117,7 @@ of keys. For example if a btree level is: index: 0,1,2,3,4,5,6,7,8,9,10,11,12 data: b,b,b,b,b,b,g,g,j,j,j, x, y then we would store 5,7,10,11,12 in the array. */ -typedef std::vector > boundaries_t; +typedef small_vector Boundaries; /** Allocator type used for index_map_t. */ typedef ut_allocator > @@ -176,8 +157,10 @@ dict_stats_should_ignore_index( } +namespace { + /** expected column definition */ -struct dict_col_meta_t +struct ColMeta { /** column name */ const char *name; @@ -190,7 +173,7 @@ struct dict_col_meta_t }; /** For checking whether a table exists and has a predefined schema */ -struct dict_table_schema_t +struct TableSchema { /** table name */ span table_name; @@ -199,10 +182,12 @@ struct dict_table_schema_t /** number of columns */ unsigned n_cols; /** columns */ - const dict_col_meta_t columns[8]; + const ColMeta columns[8]; }; -static const dict_table_schema_t table_stats_schema = +} // anonymous namespace + +static const TableSchema table_stats_schema = { {C_STRING_WITH_LEN(TABLE_STATS_NAME)}, TABLE_STATS_NAME_PRINT, 6, { @@ -225,7 +210,7 @@ static const dict_table_schema_t table_stats_schema = } }; -static const dict_table_schema_t index_stats_schema = +static const TableSchema index_stats_schema = { {C_STRING_WITH_LEN(INDEX_STATS_NAME)}, INDEX_STATS_NAME_PRINT, 8, { @@ -346,7 +331,7 @@ static dberr_t dict_table_schema_check( /*====================*/ - const dict_table_schema_t* req_schema, /*!< in: required table + const TableSchema* req_schema, /*!< in: required table schema */ char* errstr, /*!< out: human readable error message if != DB_SUCCESS is @@ -680,15 +665,16 @@ dict_stats_assert_initialized( } /** Statistics for one field of an index. */ -struct index_field_stats_t +namespace { +struct IndexFieldStats { - ib_uint64_t n_diff_key_vals; - ib_uint64_t n_sample_sizes; - ib_uint64_t n_non_null_key_vals; + uint64_t n_diff_key_vals; + uint64_t n_sample_sizes; + uint64_t n_non_null_key_vals; - index_field_stats_t(ib_uint64_t n_diff_key_vals= 0, - ib_uint64_t n_sample_sizes= 0, - ib_uint64_t n_non_null_key_vals= 0) + IndexFieldStats(uint64_t n_diff_key_vals= 0, + uint64_t n_sample_sizes= 0, + uint64_t n_non_null_key_vals= 0) : n_diff_key_vals(n_diff_key_vals), n_sample_sizes(n_sample_sizes), n_non_null_key_vals(n_non_null_key_vals) { @@ -700,40 +686,25 @@ struct index_field_stats_t n_sample_sizes == UINT64_MAX && n_non_null_key_vals == UINT64_MAX; } }; +} // anonymous namespace -/*******************************************************************//** -Record the number of non_null key values in a given index for -each n-column prefix of the index where 1 <= n <= dict_index_get_n_unique(index). +/** Record the number of non_null key values in a given index for +each column of the index where 1 <= n <= dict_index_get_n_unique(index). The estimates are eventually stored in the array: -index->stat_n_non_null_key_vals[], which is indexed from 0 to n-1. */ -static -void -btr_record_not_null_field_in_rec( -/*=============================*/ - ulint n_unique, /*!< in: dict_index_get_n_unique(index), - number of columns uniquely determine - an index entry */ - const rec_offs* offsets, /*!< in: rec_get_offsets(rec, index), - its size could be for all fields or - that of "n_unique" */ - ib_uint64_t* n_not_null) /*!< in/out: array to record number of - not null rows for n-column prefix */ +index->stat_n_non_null_key_vals[], which is indexed from 0 to n-1. +The counts are per column; innodb_rec_per_key() derives from them the +prefix-wise count that it needs. +@param n_unique number of unique column for an index +@param offsets offsets for all fields that of n_unique +@param n_not_null array to record number of non null rows for each individual column */ +__attribute__((nonnull)) +static void btr_record_not_null_field_in_rec(ulint n_unique, + const rec_offs *offsets, + uint64_t *n_not_null) noexcept { - ulint i; - - ut_ad(rec_offs_n_fields(offsets) >= n_unique); - - if (n_not_null == NULL) { - return; - } - - for (i = 0; i < n_unique; i++) { - if (rec_offs_nth_sql_null(offsets, i)) { - break; - } - - n_not_null[i]++; - } + ut_ad(rec_offs_n_fields(offsets) >= n_unique); + for (ulint i = 0; i < n_unique; i++) + n_not_null[i] += !rec_offs_nth_sql_null(offsets, i); } inline dberr_t @@ -796,7 +767,7 @@ btr_cur_t::open_random_leaf(rec_offs *&offsets, mem_heap_t *&heap, mtr_t &mtr) @param not_empty table not empty @return estimated table wide stats from sampled value */ #define BTR_TABLE_STATS_FROM_SAMPLE(value, index, sample, ext_size, not_empty) \ - (((value) * static_cast(index->stat_n_leaf_pages) \ + (((value) * static_cast(index->stat_n_leaf_pages) \ + (sample) - 1 + (ext_size) + (not_empty)) / ((sample) + (ext_size))) /** Estimates the number of different key values in a given index, for @@ -812,26 +783,26 @@ array result.n_non_null_key_vals. @return vector with statistics information empty vector if the index is unavailable. */ static -std::vector +std::vector btr_estimate_number_of_different_key_vals(dict_index_t* index, trx_id_t bulk_trx_id) { page_t* page; rec_t* rec; ulint n_cols; - ib_uint64_t* n_diff; - ib_uint64_t* n_not_null; + uint64_t* n_diff; + uint64_t* n_not_null; bool stats_null_not_equal; - uint32_t n_sample_pages=1; /* number of pages to sample */ + uint32_t n_sample_pages = 1; /* number of pages to sample */ ulint not_empty_flag = 0; - ulint total_external_size = 0; + uint32_t n_blob_pages = 0; uintmax_t add_on; mtr_t mtr{nullptr}; mem_heap_t* heap = NULL; rec_offs* offsets_rec = NULL; rec_offs* offsets_next_rec = NULL; - std::vector result; + std::vector result; ut_ad(index->is_btree()); @@ -843,20 +814,17 @@ btr_estimate_number_of_different_key_vals(dict_index_t* index, * (sizeof *offsets_rec + sizeof *offsets_next_rec)); - n_diff = (ib_uint64_t*) mem_heap_zalloc( - heap, n_cols * sizeof(n_diff[0])); + n_diff = static_cast(mem_heap_zalloc( + heap, n_cols * sizeof *n_diff)); - n_not_null = NULL; + n_not_null = static_cast(mem_heap_zalloc( + heap, n_cols * sizeof *n_not_null)); /* Check srv_innodb_stats_method setting, and decide whether we need to record non-null value and also decide if NULL is considered equal (by setting stats_null_not_equal value) */ switch (srv_innodb_stats_method) { case SRV_STATS_NULLS_IGNORED: - n_not_null = (ib_uint64_t*) mem_heap_zalloc( - heap, n_cols * sizeof *n_not_null); - /* fall through */ - case SRV_STATS_NULLS_UNEQUAL: /* for both SRV_STATS_NULLS_IGNORED and SRV_STATS_NULLS_UNEQUAL case, we will treat NULLs as unequal value */ @@ -962,10 +930,8 @@ btr_estimate_number_of_different_key_vals(dict_index_t* index, n_core, ULINT_UNDEFINED, &heap); - if (n_not_null != NULL) { - btr_record_not_null_field_in_rec( - n_cols, offsets_rec, n_not_null); - } + btr_record_not_null_field_in_rec( + n_cols, offsets_rec, n_not_null); } while (rec != page_get_supremum_rec(page)) { @@ -973,9 +939,8 @@ btr_estimate_number_of_different_key_vals(dict_index_t* index, rec_t* next_rec = page_rec_get_next(rec); if (!next_rec || next_rec == page_get_supremum_rec(page)) { - total_external_size += - btr_rec_get_externally_stored_len( - rec, offsets_rec); + n_blob_pages += rec_get_n_blob_pages( + rec, offsets_rec, index->table); break; } @@ -997,14 +962,11 @@ btr_estimate_number_of_different_key_vals(dict_index_t* index, n_diff[j]++; } - if (n_not_null != NULL) { - btr_record_not_null_field_in_rec( - n_cols, offsets_next_rec, n_not_null); - } + btr_record_not_null_field_in_rec( + n_cols, offsets_next_rec, n_not_null); - total_external_size - += btr_rec_get_externally_stored_len( - rec, offsets_rec); + n_blob_pages += rec_get_n_blob_pages( + rec, offsets_rec, index->table); rec = next_rec; /* Initialize offsets_rec for the next round @@ -1047,15 +1009,15 @@ exit_loop: result.reserve(n_cols); for (ulint j = 0; j < n_cols; j++) { - index_field_stats_t stat; + IndexFieldStats stat; stat.n_diff_key_vals = BTR_TABLE_STATS_FROM_SAMPLE( n_diff[j], index, n_sample_pages, - total_external_size, not_empty_flag); + n_blob_pages, not_empty_flag); /* If the tree is small, smaller than - 10 * n_sample_pages + total_external_size, then + 10 * (n_sample_pages + n_blob_pages), then the above estimate is ok. For bigger trees it is common that we do not see any borders between key values in the few pages we pick. But still there may be n_sample_pages @@ -1064,7 +1026,7 @@ exit_loop: add_on = index->stat_n_leaf_pages / (10 * (n_sample_pages - + total_external_size)); + + n_blob_pages)); if (add_on > n_sample_pages) { add_on = n_sample_pages; @@ -1074,12 +1036,10 @@ exit_loop: stat.n_sample_sizes = n_sample_pages; - if (n_not_null != NULL) { - stat.n_non_null_key_vals = - BTR_TABLE_STATS_FROM_SAMPLE( - n_not_null[j], index, n_sample_pages, - total_external_size, not_empty_flag); - } + stat.n_non_null_key_vals = + BTR_TABLE_STATS_FROM_SAMPLE( + n_not_null[j], index, n_sample_pages, + n_blob_pages, not_empty_flag); result.push_back(stat); } @@ -1160,7 +1120,7 @@ invalid: /* Do not continue if table decryption has failed or table is already marked as corrupted. */ if (index->is_readable()) { - std::vector stats + std::vector stats = btr_estimate_number_of_different_key_vals( index, bulk_trx_id); @@ -1335,75 +1295,200 @@ static dberr_t btr_pcur_open_level(btr_pcur_t *pcur, ulint level, mtr_t *mtr, } -/* @{ Pseudo code about the relation between the following functions +namespace { -let N = N_SAMPLE_PAGES(index) +/** Input data that is used to calculate dict_index_t::stat_n_diff_key_vals[] +for each n-columns prefix (n from 1 to n_uniq). */ +struct NDiffData { + /** Index of the level on which the descent through the btree + stopped. level 0 is the leaf level. This is >= 1 because we + avoid scanning the leaf level because it may contain too many + pages and doing so is useless when combined with the random dives - + if we are to scan the leaf level, this means a full scan and we can + simply do that instead of fiddling with picking random records higher + in the tree and to dive below them. At the start of the analyzing + we may decide to do full scan of the leaf level, but then this + structure is not used in that code path. */ + ulint level; -dict_stats_analyze_index() - for each n_prefix - search for good enough level: - dict_stats_analyze_index_level() // only called if level has <= N pages - // full scan of the level in one mtr - collect statistics about the given level - if we are not satisfied with the level, search next lower level - we have found a good enough level here - dict_stats_analyze_index_for_n_prefix(that level, stats collected above) - // full scan of the level in one mtr - dive below some records and analyze the leaf page there: - dict_stats_analyze_index_below_cur() -@} */ + /** Number of records on the level where the descend through the btree + stopped. When we scan the btree from the root, we stop at some mid + level, choose some records from it and dive below them towards a leaf + page to analyze. */ + uint64_t n_recs_on_level; -/*********************************************************************//** -Find the total number and the number of distinct keys on a given level in -an index. Each of the 1..n_uniq prefixes are looked up and the results are -saved in the array n_diff[0] .. n_diff[n_uniq - 1]. The total number of -records on the level is saved in total_recs. -Also, the index of the last record in each group of equal records is saved -in n_diff_boundaries[0..n_uniq - 1], records indexing starts from the leftmost -record on the level and continues cross pages boundaries, counting from 0. */ -static -void -dict_stats_analyze_index_level( -/*===========================*/ - dict_index_t* index, /*!< in: index */ - ulint level, /*!< in: level */ - ib_uint64_t* n_diff, /*!< out: array for number of - distinct keys for all prefixes */ - ib_uint64_t* total_recs, /*!< out: total number of records */ - ib_uint64_t* total_pages, /*!< out: total number of pages */ - boundaries_t* n_diff_boundaries,/*!< out: boundaries of the groups - of distinct keys */ - mtr_t* mtr) /*!< in/out: mini-transaction */ + /** Number of different key values that were found on the mid level. */ + uint64_t n_diff_on_level; + + /** Number of leaf pages that are analyzed. This is also the same as + the number of records that we pick from the mid level and dive below + them. */ + uint32_t n_analyze_leaf_pages; + + /** Cumulative sum of the number of external pages (stored outside of + the btree but in the same file segment). */ + uint32_t n_external_pages; + + /** Cumulative sum of the number of different key values that were + found on all analyzed pages. */ + uint64_t n_diff_total; + + /** Cumulative sum of the number of non-null key values that + were found on all analyzed pages. */ + uint64_t n_non_null_total; +}; + +/** Statistics collected for a single B-tree level +during index analysis. Used to track distinct key values, +record counts, and page information when analyzing index +levels for the query optimizer's statistics. */ +struct IndexLevelStats +{ + /** Whether nulls are considered unequal for statistics */ + const bool nulls_unequal; + /** distance from the leaf level (0). */ + uint16_t level; + /** Array for number of distinct keys */ + uint64_t *const n_diff; + /** Array of the number of non-null values of each individual + column, or nullptr when the level being scanned is not the leaf + level */ + uint64_t *const n_non_null_per_col; + /** Boundaries of groups of distinct keys */ + Boundaries *const bounds; + /** Total number of records */ + uint64_t n_recs; + /** Index */ + dict_index_t *const index; + /** Mini-transaction */ + mtr_t *const mtr; + + IndexLevelStats(bool nulls_unequal_, uint16_t level_, + uint64_t *n_diff_, uint64_t *n_non_null_per_col_, + Boundaries *bounds_, dict_index_t *index_, + mtr_t *mtr_) : + nulls_unequal(nulls_unequal_), level(level_), + n_diff(n_diff_), n_non_null_per_col(n_non_null_per_col_), bounds(bounds_), + n_recs(!!level_), index(index_), mtr(mtr_) + { + } + + /** Reset statistics for scanning a new level + @param level_ new level number to scan */ + void reset_for_level(uint16_t level_) + { + level= level_; + n_recs= 0; + auto n= index->n_uniq; + memset(n_diff, 0, n * sizeof *n_diff); + for (auto i= n; i--; ) + bounds[i].clear(); + } + + /** Full-scan one B-tree level in a single mtr. + For every n-prefix (1..n_uniq) compute + n_diff[] (= N_DIFF_LA, distinct keys on the level) and + n_recs (= TOTAL_LA, records on the level), and + record in bounds[] the index of the last record of each group of equal keys + (0-based, counted continuously across pages). These boundaries + let sample_leaf_pages() split the level into groups and pick one + representative record per group. + @return number of pages in this level */ + uint32_t analyze_level() noexcept; + + /** Estimate the number of different key values in an index when looking at + the first n_prefix columns. Using the group boundaries from analyze_level(), + split level LA into n_analyze_leaf_pages (<= A) groups of equal keys, pick + the last record of each group and dive below it (scan_below()). Accumulate over + the samples: + n_diff_total (= sum of Pi), n_non_null_total and + n_external_pages (= E, BLOB pages). + @param[in] n_prefix look at first 'n_prefix' columns when comparing records + @param[in,out] n_diff_data n_diff_total and n_external_pages + in this structure will be set by this function. + The members level, n_diff_on_level and n_analyze_leaf_pages + must be set by the caller in advance. They are used by + some calculations inside this function */ + void sample_leaf_pages(trx_t *trx, uint16_t n_prefix, + NDiffData *n_diff_data)const noexcept; +}; + +/** Statistics collected for a leaf level during index +analysis. Used to calculate the number of different key values, +number of blob pages, number of non-null values in an index +when looking at the first n_prefix columns. This will be +initialized in dict_stats_scan_page() */ +struct PageStats +{ + /** Collecting statistics for this Index */ + const dict_index_t *index; + /** Number of columns to consider for prefix statistics */ + const uint8_t n_prefix; + /** Whether NULL values are considered unequal for statistics */ + const bool nulls_unequal; + /** Number of externally stored blob pages encountered in scan */ + uint32_t n_blob= 0; + /** Number of distinct key values found for the first n_prefix columns. + On leaf pages this is the full count; on non-leaf pages it is capped at 2 + (0 = empty, 1 = boring/all-equal, 2 = non-boring). see scan(). */ + uint64_t n_diff= 0; + /** Number of non-null key values found for the prefix in scan + for the prefix */ + uint64_t n_non_null= 0; + + rec_offs *offsets1= nullptr; + + rec_offs *offsets2= nullptr; + + mem_heap_t *heap= nullptr; + + PageStats(dict_index_t *ind, uint8_t n_pfx, bool nulls) : + index(ind), n_prefix(n_pfx), nulls_unequal(nulls) + { + ulint size= (1 + REC_OFFS_HEADER_SIZE) + 1 + index->n_fields; + heap= mem_heap_create(size * (2 * sizeof (rec_offs))); + offsets1= static_cast( + mem_heap_alloc(heap, size * sizeof (*offsets1))); + offsets2= static_cast( + mem_heap_alloc(heap, size * sizeof (*offsets2))); + rec_offs_set_n_alloc(offsets1, size); + rec_offs_set_n_alloc(offsets2, size); + } + + void scan_below(trx_t *trx, const btr_cur_t *cur) noexcept; + + uint32_t scan(const page_t *page, bool leaf= true) noexcept; + + ~PageStats() { mem_heap_free(heap); } +}; + +} // anonymous namespace + +uint32_t IndexLevelStats::analyze_level() noexcept { - ulint n_uniq; mem_heap_t* heap; btr_pcur_t pcur; const page_t* page; const rec_t* rec; const rec_t* prev_rec; - bool prev_rec_is_copied; - byte* prev_rec_buf = NULL; - ulint prev_rec_buf_size = 0; rec_offs* rec_offsets; rec_offs* prev_rec_offsets; - ulint i; - - DEBUG_PRINTF(" %s(table=%s, index=%s, level=" ULINTPF ")\n", - __func__, index->table->name, index->name, level); - - *total_recs = 0; - *total_pages = 0; - - n_uniq = dict_index_get_n_unique(index); - - /* elements in the n_diff array are 0..n_uniq-1 (inclusive) */ - memset(n_diff, 0x0, n_uniq * sizeof(n_diff[0])); + /* mtr savepoint of the page that contains prev_rec, in case + that page is not the one the cursor is positioned on; + 0 (the savepoint of index->lock) if there is no such page. + Instead of copying the last record of a page, retain the + latch on that page until the record has been compared with the + first record of the next page. */ + ulint prev_savepoint = 0; + /* Number of pages on this level. It is counted at the end of + each page, and is returned to the caller. */ + uint32_t n_pages = 0; /* Allocate space for the offsets header (the allocation size at offsets[0] and the REC_OFFS_HEADER_SIZE bytes), and n_uniq + 1, so that this will never be less than the size calculated in rec_get_offsets_func(). */ - i = (REC_OFFS_HEADER_SIZE + 1 + 1) + n_uniq; + ulint i = (REC_OFFS_HEADER_SIZE + 1 + 1) + index->n_uniq; heap = mem_heap_create((2 * sizeof *rec_offsets) * i); rec_offsets = static_cast( @@ -1413,19 +1498,14 @@ dict_stats_analyze_index_level( rec_offs_set_n_alloc(rec_offsets, i); rec_offs_set_n_alloc(prev_rec_offsets, i); - /* reset the dynamic arrays n_diff_boundaries[0..n_uniq-1] */ - if (n_diff_boundaries != NULL) { - for (i = 0; i < n_uniq; i++) { - n_diff_boundaries[i].erase( - n_diff_boundaries[i].begin(), - n_diff_boundaries[i].end()); - } - } + /* The mini-transaction must only be holding index->lock, + which was acquired at savepoint 0. */ + ut_ad(mtr->get_savepoint() == 1); /* Position pcur on the leftmost record on the leftmost page on the desired level. */ - if (btr_pcur_open_level(&pcur, level, mtr, index) != DB_SUCCESS + if (btr_pcur_open_level(&pcur, level, mtr, index) || !btr_pcur_move_to_next_on_page(&pcur)) { goto func_exit; } @@ -1437,7 +1517,6 @@ dict_stats_analyze_index_level( ut_ad(btr_pcur_is_on_user_rec(&pcur) || page_is_leaf(page)); prev_rec = NULL; - prev_rec_is_copied = false; if (REC_INFO_MIN_REC_FLAG & rec_get_info_bits( btr_pcur_get_rec(&pcur), page_is_comp(page))) { @@ -1456,28 +1535,26 @@ dict_stats_analyze_index_level( /* iterate over all user records on this level and compare each two adjacent ones, even the last on page X and the fist on page X+1 */ - for (; - btr_pcur_is_on_user_rec(&pcur); - btr_pcur_move_to_next_user_rec(&pcur, mtr)) { - + while (btr_pcur_is_on_user_rec(&pcur)) { bool rec_is_last_on_page; + page = btr_pcur_get_page(&pcur); rec = btr_pcur_get_rec(&pcur); - /* If rec and prev_rec are on different pages, then prev_rec - must have been copied, because we hold latch only on the page - where rec resides. */ - ut_ad(!prev_rec - || btr_pcur_get_page(&pcur) == page_align(prev_rec) - || prev_rec_is_copied); + /* If rec and prev_rec are on different pages, then we + must still be holding the latch on the page of prev_rec. */ + ut_ad(prev_savepoint < mtr->get_savepoint()); + ut_ad(prev_savepoint + ? page_align(prev_rec) + == mtr->at_savepoint(prev_savepoint)->page.frame + : !prev_rec || page_align(prev_rec) == page); rec_is_last_on_page = page_rec_get_next_const(rec) - == page_get_supremum_rec(btr_pcur_get_page(&pcur)); + == page_get_supremum_rec(page); /* increment the pages counter at the end of each page */ if (rec_is_last_on_page) { - - (*total_pages)++; + n_pages++; } /* Skip delete-marked records on the leaf level. If we @@ -1489,32 +1566,28 @@ dict_stats_analyze_index_level( if (level == 0 && !srv_stats_include_delete_marked - && rec_get_deleted_flag( - rec, page_is_comp(btr_pcur_get_page(&pcur)))) { - if (rec_is_last_on_page - && !prev_rec_is_copied - && prev_rec != NULL) { - /* copy prev_rec */ - - prev_rec_offsets = rec_get_offsets( - prev_rec, index, prev_rec_offsets, - index->n_core_fields, - n_uniq, &heap); - - prev_rec = rec_copy_prefix_to_buf( - prev_rec, index, n_uniq, - &prev_rec_buf, &prev_rec_buf_size); - - prev_rec_is_copied = true; - } - - continue; + && rec_get_deleted_flag(rec, page_is_comp(page))) { + /* The latch on the page of prev_rec must be + retained until a record that is not skipped is + found, even if that record is on a later page. */ + goto next_rec; } + rec_offsets = rec_get_offsets(rec, index, rec_offsets, level ? 0 : index->n_core_fields, - n_uniq, &heap); + index->n_uniq, &heap); - (*total_recs)++; + n_recs++; + + if (n_non_null_per_col) { + ut_ad(level == 0); + for (ulint i = 0; i < index->n_uniq; i++) { + n_non_null_per_col[i] + += index->fields[i].col->is_nullable() + && !rec_offs_nth_sql_null( + rec_offsets, i); + } + } if (prev_rec != NULL) { ulint matched_fields; @@ -1522,34 +1595,33 @@ dict_stats_analyze_index_level( prev_rec_offsets = rec_get_offsets( prev_rec, index, prev_rec_offsets, level ? 0 : index->n_core_fields, - n_uniq, &heap); + index->n_uniq, &heap); cmp_rec_rec(prev_rec, rec, prev_rec_offsets, rec_offsets, index, - false, &matched_fields); + nulls_unequal, + &matched_fields); - for (i = matched_fields; i < n_uniq; i++) { - - if (n_diff_boundaries != NULL) { + if (bounds) { + for (i = matched_fields; + i < index->n_uniq; i++) { /* push the index of the previous record, that is - the last one from a group of equal keys */ - ib_uint64_t idx; - /* the index of the current record - is total_recs - 1, the index of the - previous record is total_recs - 2; + is n_recs - 1, the index of the + previous record is n_recs - 2; we know that idx is not going to become negative here because if we are in this branch then there is a previous record and thus - total_recs >= 2 */ - idx = *total_recs - 2; - - n_diff_boundaries[i].push_back(idx); + n_recs >= 2 */ + bounds[i].emplace_back(n_recs - 2); } + } + for (i = matched_fields; i < index->n_uniq; i++) { /* increment the number of different keys for n_prefix=i+1 (e.g. if i=0 then we increment for n_prefix=1 which is stored in n_diff[0]) */ @@ -1557,102 +1629,112 @@ dict_stats_analyze_index_level( } } else { /* this is the first non-delete marked record */ - for (i = 0; i < n_uniq; i++) { + for (i = 0; i < index->n_uniq; i++) { n_diff[i] = 1; } } - if (rec_is_last_on_page) { - /* end of a page has been reached */ - - /* we need to copy the record instead of assigning - like prev_rec = rec; because when we traverse the - records on this level at some point we will jump from - one page to the next and then rec and prev_rec will - be on different pages and - btr_cur_move_to_next_user_rec() will release the - latch on the page that prev_rec is on */ - prev_rec = rec_copy_prefix_to_buf( - rec, index, n_uniq, - &prev_rec_buf, &prev_rec_buf_size); - prev_rec_is_copied = true; - - } else { - /* still on the same page, the next call to - btr_cur_move_to_next_user_rec() will not jump - on the next page, we can simply assign pointers - instead of copying the records like above */ - - prev_rec = rec; - prev_rec_is_copied = false; + /* prev_rec has been compared with rec; if prev_rec was on + a preceding page, that page can be released now. */ + if (prev_savepoint) { + mtr->rollback_to_savepoint(prev_savepoint, + prev_savepoint + 1); + prev_savepoint = 0; } - } - /* if *total_pages is left untouched then the above loop was not - entered at all and there is one page in the whole tree which is - empty or the loop was entered but this is level 0, contains one page - and all records are delete-marked */ - if (*total_pages == 0) { - - ut_ad(level == 0); - ut_ad(*total_recs == 0); - - *total_pages = 1; - } - - /* if there are records on this level and boundaries - should be saved */ - if (*total_recs > 0 && n_diff_boundaries != NULL) { - - /* remember the index of the last record on the level as the - last one from the last group of equal keys; this holds for - all possible prefixes */ - for (i = 0; i < n_uniq; i++) { - ib_uint64_t idx; - - idx = *total_recs - 1; - - n_diff_boundaries[i].push_back(idx); - } - } - - /* now in n_diff_boundaries[i] there are exactly n_diff[i] integers, - for i=0..n_uniq-1 */ - -#ifdef UNIV_STATS_DEBUG - for (i = 0; i < n_uniq; i++) { - - DEBUG_PRINTF(" %s(): total recs: " UINT64PF - ", total pages: " UINT64PF - ", n_diff[" ULINTPF "]: " UINT64PF "\n", - __func__, *total_recs, - *total_pages, - i, n_diff[i]); - -#if 0 - if (n_diff_boundaries != NULL) { - ib_uint64_t j; - - DEBUG_PRINTF(" %s(): boundaries[%lu]: ", - __func__, i); - - for (j = 0; j < n_diff[i]; j++) { - ib_uint64_t idx; - - idx = n_diff_boundaries[i][j]; - - DEBUG_PRINTF(UINT64PF "=" UINT64PF ", ", - j, idx); + prev_rec = rec; +next_rec: + if (!rec_is_last_on_page) { + /* still on the same page; moving to the next + record will not release any page latch */ + if (UNIV_UNLIKELY(!btr_pcur_move_to_next_on_page( + &pcur))) { + break; } - DEBUG_PRINTF("\n"); + continue; } -#endif - } -#endif /* UNIV_STATS_DEBUG */ + /* The end of a page has been reached. Position the cursor + on the supremum record, so that we can move to the next + page. */ + if (UNIV_UNLIKELY(!btr_pcur_move_to_next_on_page(&pcur))) { + break; + } + + for (;;) { + /* Instead of copying prev_rec, retain the latch on + the page that it resides on, so that prev_rec can be + compared with the next non-delete-marked record that + we may find. The latch will be released once that + comparison has been made. If prev_rec does not reside + on the current page of pcur, that page will simply be + released, and the latch on the page of prev_rec will + be kept */ + const bool retain_latch = prev_rec && !prev_savepoint; + ut_ad(retain_latch + == (prev_rec + && page_align(prev_rec) + == btr_pcur_get_page(&pcur))); + if (retain_latch) { + prev_savepoint = mtr->get_savepoint() - 1; + } + + ut_ad(btr_pcur_is_after_last_on_page(&pcur)); + + if (btr_pcur_move_to_next_page(&pcur, mtr, + retain_latch) + != DB_SUCCESS) { + goto end_of_level; + } + + if (UNIV_UNLIKELY(!btr_pcur_move_to_next_on_page( + &pcur))) { + goto end_of_level; + } + + if (btr_pcur_is_on_user_rec(&pcur)) { + break; + } + } + } + +end_of_level: + /* if n_pages is left untouched then + above loop was not entered at all and there is one page + in the whole tree which is empty or the loop was entered + but this is level 0, contains one page and all records + are delete-marked */ + if (n_pages == 0) { + ut_ad(level == 0); + ut_ad(n_recs == 0); + /* Nothing was counted: there are no boundaries to save, + and the zero counts are already in place, because + dict_stats_analyze_index() zero-initialized the arrays + before starting this analysis. */ + n_pages = 1; + } else { + if (n_recs > 0 && bounds != NULL) { + /* there are records on this level and boundaries + should be saved: remember the index of the last + record on the level as the last one from the last + group of equal keys; this holds for all possible + prefixes */ + for (ulint i = 0; i < index->n_uniq; i++) { + bounds[i].emplace_back(n_recs - 1); + } + } + + if (level == 0) { + for (ulint i = 0; i < index->n_uniq; i++) { + if (!index->fields[i].col->is_nullable()) { + n_non_null_per_col[i]= n_recs; + } + } + } + } func_exit: - ut_free(prev_rec_buf); mem_heap_free(heap); + return n_pages; } /************************************************************//** @@ -1674,168 +1756,110 @@ page_rec_get_next_non_del_marked(const page_t *page, const rec_t *rec) return rec ? rec : page + (comp ? PAGE_NEW_SUPREMUM : PAGE_OLD_SUPREMUM); } -/** Scan a page, reading records from left to right and counting the number -of distinct records (looking only at the first n_prefix -columns) and the number of external pages pointed by records from this page. -If scan_method is QUIT_ON_FIRST_NON_BORING then the function -will return as soon as it finds a record that does not match its neighbor -to the right, which means that in the case of QUIT_ON_FIRST_NON_BORING the -returned n_diff can either be 0 (empty page), 1 (the whole page has all keys -equal) or 2 (the function found a non-boring record and returned). -@param[out] out_rec record, or NULL -@param[out] offsets1 rec_get_offsets() working space (must -be big enough) -@param[out] offsets2 rec_get_offsets() working space (must -be big enough) -@param[in] index index of the page -@param[in] page the page to scan -@param[in] n_prefix look at the first n_prefix columns -@param[in] n_core 0, or index->n_core_fields for leaf -@param[out] n_diff number of distinct records encountered -@param[out] n_external_pages if this is non-NULL then it will be set -to the number of externally stored pages which were encountered -@return offsets1 or offsets2 (the offsets of *out_rec), -or NULL if the page is empty and does not contain user records. */ -UNIV_INLINE -rec_offs* -dict_stats_scan_page( - const rec_t** out_rec, - rec_offs* offsets1, - rec_offs* offsets2, - const dict_index_t* index, - const page_t* page, - ulint n_prefix, - ulint n_core, - ib_uint64_t* n_diff, - ib_uint64_t* n_external_pages) +/** Scan a page left to right, counting distinct records for the first n_prefix +columns (and, on leaf pages, non-null values and external/BLOB pages). + +An "n-prefix-boring" record is a non-leaf record equal to the next record to +its right on the same level (crossing pages, skipping infimum/supremum) in the +first n_prefix columns; the last user record on a level is never boring. Every +record in the subtree below a boring record is equal to it, so that subtree +contributes exactly 1 distinct key which is why scan_below() never descends +through boring records. +@tparam leaf true = leaf scan: count ALL distinct keys (per nulls_unequal), + non-null values and BLOB page counts; + false = non-leaf scan: quick boring-detection that stops at the + first non-boring record, so n_diff ends as 0 (empty), + 1 (all keys equal / boring page) or 2 (non-boring found) +@param page index page to scan +@retval 0 if leaf=true OR if page is empty +@retval child_page_no if leaf=false and page contains records */ +uint32_t PageStats::scan(const page_t *page, const bool leaf)noexcept { rec_offs* offsets_rec = offsets1; rec_offs* offsets_next_rec = offsets2; - const rec_t* rec; const rec_t* next_rec; - /* A dummy heap, to be passed to rec_get_offsets(). - Because offsets1,offsets2 should be big enough, - this memory heap should never be used. */ - mem_heap_t* heap = NULL; + const ulint n_core = leaf ? index->n_core_fields : 0; ut_ad(!!n_core == page_is_leaf(page)); + ulint n_recs = 1; const rec_t* (*get_next)(const page_t*, const rec_t*) = !n_core || srv_stats_include_delete_marked ? (page_is_comp(page) ? page_rec_next_get : page_rec_next_get) : page_is_comp(page) - ? page_rec_get_next_non_del_marked - : page_rec_get_next_non_del_marked; - - const bool should_count_external_pages = n_external_pages != NULL; - - if (should_count_external_pages) { - *n_external_pages = 0; - } - - rec = get_next(page, page_get_infimum_rec(page)); - + ? page_rec_get_next_non_del_marked + : page_rec_get_next_non_del_marked; + const rec_t *rec = get_next(page, page_get_infimum_rec(page)); + ut_ad(!n_blob); if (!rec || rec == page_get_supremum_rec(page)) { /* the page is empty or contains only delete-marked records */ - *n_diff = 0; - *out_rec = NULL; - return(NULL); + n_diff = 0; + return 0; } - + const bool nullable = index->fields[n_prefix - 1].col->is_nullable(); offsets_rec = rec_get_offsets(rec, index, offsets_rec, n_core, ULINT_UNDEFINED, &heap); - - if (should_count_external_pages) { - *n_external_pages += btr_rec_get_externally_stored_len( - rec, offsets_rec); + if (leaf) { + n_blob += rec_get_n_blob_pages(rec, offsets_rec, index->table); + n_non_null += static_cast( + nullable && !rec_offs_nth_sql_null(offsets_rec, + n_prefix - 1)); } next_rec = get_next(page, rec); - - *n_diff = 1; - - while (next_rec && next_rec != page_get_supremum_rec(page)) { - + n_diff = 1; + while (next_rec && !page_rec_is_supremum_low(next_rec - page)) { ulint matched_fields; + n_recs++; + offsets_next_rec = rec_get_offsets(next_rec, index, offsets_next_rec, + n_core, ULINT_UNDEFINED, &heap); - offsets_next_rec = rec_get_offsets(next_rec, index, - offsets_next_rec, n_core, - ULINT_UNDEFINED, - &heap); - - /* check whether rec != next_rec when looking at - the first n_prefix fields */ + /* check whether rec != next_rec when looking at the first + n_prefix fields */ cmp_rec_rec(rec, next_rec, offsets_rec, offsets_next_rec, - index, false, &matched_fields); - + index, nulls_unequal, &matched_fields); if (matched_fields < n_prefix) { /* rec != next_rec, => rec is non-boring */ - - (*n_diff)++; - + n_diff++; if (!n_core) { break; } } rec = next_rec; - /* Assign offsets_rec = offsets_next_rec so that - offsets_rec matches with rec which was just assigned - rec = next_rec above. Also need to point - offsets_next_rec to the place where offsets_rec was - pointing before because we have just 2 placeholders - where data is actually stored: offsets1 and offsets2 - and we are using them in circular fashion - (offsets[_next]_rec are just pointers to those - placeholders). */ + /* Assign offsets_rec = offsets_next_rec so that offsets_rec matches + with rec which was just assigned rec = next_rec above. Also need to + point offsets_next_rec to the place where offsets_rec was pointing + before because we have just 2 placeholders where data is actually + stored: offsets1 and offsets2 and we are using them in circular fashion + (offsets[_next]_rec are just pointers to those placeholders). */ std::swap(offsets_rec, offsets_next_rec); - if (should_count_external_pages) { - *n_external_pages += btr_rec_get_externally_stored_len( - rec, offsets_rec); + if (leaf) { + n_blob += rec_get_n_blob_pages(rec, offsets_rec, + index->table); + n_non_null += nullable && + !rec_offs_nth_sql_null(offsets_rec, + n_prefix - 1); } - next_rec = get_next(page, next_rec); } - /* offsets1,offsets2 should have been big enough */ - ut_a(heap == NULL); - *out_rec = rec; - return(offsets_rec); + if (leaf) { + n_non_null = nullable ? n_non_null : n_recs; + return 0; + } + + return btr_node_ptr_get_child_page_no(rec, offsets_rec); } -/** Dive below the current position of a cursor and calculate the number of -distinct records on the leaf page, when looking at the fist n_prefix -columns. Also calculate the number of external pages pointed by records -on the leaf page. -@param[in] cur cursor -@param[in,out] mtr mini-transaction -@param[in] n_prefix look at the first n_prefix columns -when comparing records -@param[out] n_diff number of distinct records -@param[out] n_external_pages number of external pages -@return number of distinct records on the leaf page */ -static -void -dict_stats_analyze_index_below_cur( - const btr_cur_t* cur, - mtr_t* mtr, - ulint n_prefix, - ib_uint64_t* n_diff, - ib_uint64_t* n_external_pages) +/** Descend from a record on level LA to a leaf page and scan it. +At each non-leaf level follow a non-boring child (a boring subtree is all-equal +and would only add 1 distinct key); stop early if the page is boring +(n_diff == 1), since no dive is needed. On the reached leaf, scan() +accumulates Pi (distinct records for the first n_prefix columns), non-null +counts, and the external/BLOB pages pointed to by records on that leaf. */ +void PageStats::scan_below(trx_t *trx, const btr_cur_t *cur) noexcept { - dict_index_t* index; - buf_block_t* block; - const page_t* page; - mem_heap_t* heap; - const rec_t* rec; - rec_offs* offsets1; - rec_offs* offsets2; - rec_offs* offsets_rec; - ulint size; - - const auto sp = mtr->get_savepoint(); - index = btr_cur_get_index(cur); - /* Allocate offsets for the record and the node pointer, for node pointer records. In a secondary index, the node pointer record will consist of all index fields followed by a child @@ -1844,44 +1868,26 @@ dict_stats_analyze_index_below_cur( offsets[0] and the REC_OFFS_HEADER_SIZE bytes), and n_fields + 1, so that this will never be less than the size calculated in rec_get_offsets_func(). */ - size = (1 + REC_OFFS_HEADER_SIZE) + 1 + dict_index_get_n_fields(index); - - heap = mem_heap_create(size * (sizeof *offsets1 + sizeof *offsets2)); - - offsets1 = static_cast(mem_heap_alloc( - heap, size * sizeof *offsets1)); - - offsets2 = static_cast(mem_heap_alloc( - heap, size * sizeof *offsets2)); - - rec_offs_set_n_alloc(offsets1, size); - rec_offs_set_n_alloc(offsets2, size); - - rec = btr_cur_get_rec(cur); - page = btr_cur_get_page(cur); + rec_t* rec = btr_cur_get_rec(cur); + const page_t* page = btr_cur_get_page(cur); ut_ad(!page_is_leaf(page)); - - offsets_rec = rec_get_offsets(rec, index, offsets1, 0, - ULINT_UNDEFINED, &heap); - - page_id_t page_id(index->table->space_id, - btr_node_ptr_get_child_page_no( - rec, offsets_rec)); - const ulint zip_size = index->table->space->zip_size(); - - /* assume no external pages by default - in case we quit from this - function without analyzing any leaf pages */ - *n_external_pages = 0; + rec_offs* offsets_rec = rec_get_offsets(rec, index, offsets1, 0, + ULINT_UNDEFINED, &heap); + page_id_t page_id(index->table->space_id, + btr_node_ptr_get_child_page_no(rec, offsets_rec)); + const ulint zip_size = index->table->space->zip_size(); + mtr_t mtr(trx); + mtr_start(&mtr); /* descend to the leaf level on the B-tree */ for (;;) { - dberr_t err; - - block = buf_page_get_gen(page_id, zip_size, - RW_S_LATCH, NULL, BUF_GET, - mtr, &err); + dberr_t err; + buf_block_t* block = buf_page_get_gen( + page_id, zip_size, RW_S_LATCH, nullptr, + BUF_GET, &mtr, &err); if (!block) { - goto func_exit; + mtr_commit(&mtr); + return; } page = block->page.frame; @@ -1890,149 +1896,65 @@ dict_stats_analyze_index_below_cur( /* leaf level */ break; } - /* else */ /* search for the first non-boring record on the page */ - offsets_rec = dict_stats_scan_page( - &rec, offsets1, offsets2, index, page, n_prefix, - 0, n_diff, NULL); + uint32_t child_page_no = scan(page, false); - /* pages on level > 0 are not allowed to be empty */ - ut_a(offsets_rec != NULL); + /* level > 0, so child page number shouldn't be 0 */ + ut_a(child_page_no != 0); /* if page is not empty (offsets_rec != NULL) then n_diff must be > 0, otherwise there is a bug in dict_stats_scan_page() */ - ut_a(*n_diff > 0); + ut_a(n_diff > 0); - if (*n_diff == 1) { - /* page has all keys equal and the end of the page - was reached by dict_stats_scan_page(), no need to - descend to the leaf level */ - /* can't get an estimate for n_external_pages here - because we do not dive to the leaf level, assume no - external pages (*n_external_pages was assigned to 0 - above). */ - goto func_exit; + if (n_diff == 1) { + mtr_commit(&mtr); + /* page has all keys equal and the end of the page was reached by + dict_stats_scan_page(), no need to descend to the leaf level */ + /* can't get an estimate for n_external_pages here because we + do not dive to the leaf level, assume no external pages + (*n_external_pages was assigned to 0 above). */ + return; } - /* else */ /* when we instruct dict_stats_scan_page() to quit on the first non-boring record it finds, then the returned n_diff can either be 0 (empty page), 1 (page has all keys equal) or 2 (non-boring record was found) */ - ut_a(*n_diff == 2); - + ut_a(n_diff == 2); /* we have a non-boring record in rec, descend below it */ - - page_id.set_page_no( - btr_node_ptr_get_child_page_no(rec, offsets_rec)); + page_id.set_page_no(child_page_no); } /* make sure we got a leaf page as a result from the above loop */ ut_ad(page_is_leaf(page)); + /* scan the leaf page and find the number of distinct keys, when looking + only at the first n_prefix columns; also estimate the number of externally + stored pages pointed by records on this page */ + scan(page); - /* scan the leaf page and find the number of distinct keys, - when looking only at the first n_prefix columns; also estimate - the number of externally stored pages pointed by records on this - page */ - - offsets_rec = dict_stats_scan_page( - &rec, offsets1, offsets2, index, page, n_prefix, - index->n_core_fields, n_diff, - n_external_pages); - -#if 0 - DEBUG_PRINTF(" %s(): n_diff below page_no=%lu: " UINT64PF "\n", - __func__, page_no, n_diff); -#endif - -func_exit: - mtr->rollback_to_savepoint(sp); - mem_heap_free(heap); + mtr_commit(&mtr); } -/** Input data that is used to calculate dict_index_t::stat_n_diff_key_vals[] -for each n-columns prefix (n from 1 to n_uniq). */ -struct n_diff_data_t { - /** Index of the level on which the descent through the btree - stopped. level 0 is the leaf level. This is >= 1 because we - avoid scanning the leaf level because it may contain too many - pages and doing so is useless when combined with the random dives - - if we are to scan the leaf level, this means a full scan and we can - simply do that instead of fiddling with picking random records higher - in the tree and to dive below them. At the start of the analyzing - we may decide to do full scan of the leaf level, but then this - structure is not used in that code path. */ - ulint level; - - /** Number of records on the level where the descend through the btree - stopped. When we scan the btree from the root, we stop at some mid - level, choose some records from it and dive below them towards a leaf - page to analyze. */ - ib_uint64_t n_recs_on_level; - - /** Number of different key values that were found on the mid level. */ - ib_uint64_t n_diff_on_level; - - /** Number of leaf pages that are analyzed. This is also the same as - the number of records that we pick from the mid level and dive below - them. */ - ib_uint64_t n_leaf_pages_to_analyze; - - /** Cumulative sum of the number of different key values that were - found on all analyzed pages. */ - ib_uint64_t n_diff_all_analyzed_pages; - - /** Cumulative sum of the number of external pages (stored outside of - the btree but in the same file segment). */ - ib_uint64_t n_external_pages_sum; -}; - -/** Estimate the number of different key values in an index when looking at -the first n_prefix columns. For a given level in an index select -n_diff_data->n_leaf_pages_to_analyze records from that level and dive below -them to the corresponding leaf pages, then scan those leaf pages and save the -sampling results in n_diff_data->n_diff_all_analyzed_pages. -@param[in] index index -@param[in] n_prefix look at first 'n_prefix' columns when -comparing records -@param[in] boundaries a vector that contains -n_diff_data->n_diff_on_level integers each of which represents the index (on -level 'level', counting from left/smallest to right/biggest from 0) of the -last record from each group of distinct keys -@param[in,out] n_diff_data n_diff_all_analyzed_pages and -n_external_pages_sum in this structure will be set by this function. The -members level, n_diff_on_level and n_leaf_pages_to_analyze must be set by the -caller in advance - they are used by some calculations inside this function -@param[in,out] mtr mini-transaction */ -static void -dict_stats_analyze_index_for_n_prefix( - dict_index_t* index, - ulint n_prefix, - const boundaries_t* boundaries, - n_diff_data_t* n_diff_data, - mtr_t* mtr) +IndexLevelStats::sample_leaf_pages( + trx_t* trx, + uint16_t n_prefix, + NDiffData* n_diff_data)const noexcept { btr_pcur_t pcur; const page_t* page; - ib_uint64_t rec_idx; - ib_uint64_t i; - -#if 0 - DEBUG_PRINTF(" %s(table=%s, index=%s, level=%lu, n_prefix=%lu," - " n_diff_on_level=" UINT64PF ")\n", - __func__, index->table->name, index->name, level, - n_prefix, n_diff_data->n_diff_on_level); -#endif + uint64_t rec_idx; + uint64_t i; ut_ad(n_diff_data->level); + if (bounds[n_prefix - 1].empty()) { + return; + } + /* Position pcur on the leftmost record on the leftmost page on the desired level. */ - n_diff_data->n_diff_all_analyzed_pages = 0; - n_diff_data->n_external_pages_sum = 0; - if (btr_pcur_open_level(&pcur, n_diff_data->level, mtr, index) != DB_SUCCESS || !btr_pcur_move_to_next_on_page(&pcur)) { @@ -2054,30 +1976,30 @@ dict_stats_analyze_index_for_n_prefix( return; } - const ib_uint64_t last_idx_on_level = boundaries->at( - static_cast(n_diff_data->n_diff_on_level - 1)); + const uint64_t last_idx_on_level = bounds[n_prefix - 1][ + unsigned(n_diff_data->n_diff_on_level - 1)]; rec_idx = 0; - for (i = 0; i < n_diff_data->n_leaf_pages_to_analyze; i++) { + for (i = 0; i < n_diff_data->n_analyze_leaf_pages; i++) { /* there are n_diff_on_level elements in 'boundaries' and we divide those elements - into n_leaf_pages_to_analyze segments, for example: + into n_analyze_leaf_pages segments, for example: - let n_diff_on_level=100, n_leaf_pages_to_analyze=4, then: + let n_diff_on_level=100, n_analyze_leaf_pages=4, then: segment i=0: [0, 24] segment i=1: [25, 49] segment i=2: [50, 74] segment i=3: [75, 99] or - let n_diff_on_level=1, n_leaf_pages_to_analyze=1, then: + let n_diff_on_level=1, n_analyze_leaf_pages=1, then: segment i=0: [0, 0] or - let n_diff_on_level=2, n_leaf_pages_to_analyze=2, then: + let n_diff_on_level=2, n_analyze_leaf_pages=2, then: segment i=0: [0, 0] segment i=1: [1, 1] or - let n_diff_on_level=13, n_leaf_pages_to_analyze=7, then: + let n_diff_on_level=13, n_analyze_leaf_pages=7, then: segment i=0: [0, 0] segment i=1: [1, 2] segment i=2: [3, 4] @@ -2088,12 +2010,11 @@ dict_stats_analyze_index_for_n_prefix( then we select a random record from each segment and dive below it */ - const ib_uint64_t n_diff = n_diff_data->n_diff_on_level; - const ib_uint64_t n_pick - = n_diff_data->n_leaf_pages_to_analyze; + const uint64_t n_diff = n_diff_data->n_diff_on_level; + const uint64_t n_pick = n_diff_data->n_analyze_leaf_pages; - const ib_uint64_t left = n_diff * i / n_pick; - const ib_uint64_t right = n_diff * (i + 1) / n_pick - 1; + const uint64_t left = n_diff * i / n_pick; + const uint64_t right = n_diff * (i + 1) / n_pick - 1; ut_a(left <= right); ut_a(right <= last_idx_on_level); @@ -2101,8 +2022,8 @@ dict_stats_analyze_index_for_n_prefix( const ulint rnd = ut_rnd_interval( static_cast(right - left)); - const ib_uint64_t dive_below_idx - = boundaries->at(static_cast(left + rnd)); + const uint64_t dive_below_idx + = bounds[n_prefix - 1][unsigned(left + rnd)]; #if 0 DEBUG_PRINTF(" %s(): dive below record with index=" @@ -2137,13 +2058,9 @@ dict_stats_analyze_index_for_n_prefix( ut_a(rec_idx == dive_below_idx); - ib_uint64_t n_diff_on_leaf_page; - ib_uint64_t n_external_pages; + PageStats leaf_stats{index, uint8_t(n_prefix), nulls_unequal}; - dict_stats_analyze_index_below_cur(btr_pcur_get_btr_cur(&pcur), - mtr, n_prefix, - &n_diff_on_leaf_page, - &n_external_pages); + leaf_stats.scan_below(trx, btr_pcur_get_btr_cur(&pcur)); /* We adjust n_diff_on_leaf_page here to avoid counting one value twice - once as the last on some page and once @@ -2159,28 +2076,31 @@ dict_stats_analyze_index_for_n_prefix( 2 distinct records per page (average). Having 4 pages below non-boring records, it would (wrongly) estimate the number of distinct records to 8. */ - if (n_diff_on_leaf_page > 0) { - n_diff_on_leaf_page--; + if (leaf_stats.n_diff) { + n_diff_data->n_diff_total += leaf_stats.n_diff - 1; } - n_diff_data->n_diff_all_analyzed_pages += n_diff_on_leaf_page; + /* n_non_null is a per-record count, so no boundary + double-count adjustment (unlike n_diff above). */ + n_diff_data->n_non_null_total += leaf_stats.n_non_null; - n_diff_data->n_external_pages_sum += n_external_pages; + n_diff_data->n_external_pages += leaf_stats.n_blob; } } /** statistics for an index */ -struct index_stats_t +namespace { +struct IndexStats { - std::vector stats; + std::vector stats; uint32_t index_size; uint32_t n_leaf_pages; - index_stats_t(ulint n_uniq) : index_size(1), n_leaf_pages(1) + IndexStats(ulint n_uniq) : index_size(1), n_leaf_pages(1) { stats.reserve(n_uniq); for (ulint i= 0; i < n_uniq; ++i) - stats.push_back(index_field_stats_t{0, 1, 0}); + stats.push_back(IndexFieldStats{0, 1, 0}); } void set_bulk_operation() @@ -2196,31 +2116,63 @@ struct index_stats_t return true; } }; +} // anonymous namespace -/** Set dict_index_t::stat_n_diff_key_vals[] and stat_n_sample_sizes[]. +/** Save the persistent statistics of a table or an index. +@param table table whose stats to save +@param stats_method innodb_stats_method variable value +@param index_id Index ID to save statistics for (0=all) +@return DB_SUCCESS or error code */ +static +dberr_t dict_stats_save(dict_table_t *table, unsigned stats_method, + index_id_t index_id= 0) noexcept; + +/** [REF01] Derive stat_n_diff_key_vals[] / stat_n_non_null_key_vals[] and +stat_n_sample_sizes[] from the sampled data. + +Per n-prefix, on the sampled level LA (see dict_stats_analyze_index()): + TOTAL_LA = records on LA (n_recs_on_level) + N_DIFF_LA= distinct keys on LA (n_diff_on_level) + Pi = distinct keys on the i-th sampled leaf page (i = 1..A) + D = ordinary leaves analyzed (n_analyze_leaf_pages) + E = external/BLOB pages linked from those D leaves (n_external_pages) + L = total index leaf pages (index_stats.n_leaf_pages) + + R = N_DIFF_LA / TOTAL_LA distinctness ratio on LA, assumed + to also hold at the leaf level + avg(Pi) = n_diff_total / D + L_ord = L * D/(D+E) ordinary (non-BLOB) leaf pages + n_diff = L_ord * R * avg(Pi) + +BLOB pages live in BTR_SEG_LEAF, so they are included in L; the D/(D+E) factor +removes them, giving an estimate over key-bearing leaves only. n_non_null uses +the same L_ord scaling applied to the per-sample non-null counts, so for a +NOT NULL column it yields the estimated record count. +@param[in] index index @param[in] n_diff_data input data to use to derive the results @param[in,out] index_stats index stats to set */ UNIV_INLINE void dict_stats_index_set_n_diff( - const n_diff_data_t* n_diff_data, - index_stats_t& index_stats) + const dict_index_t* index, + const NDiffData* n_diff_data, + IndexStats& index_stats) { for (ulint n_prefix = index_stats.stats.size(); n_prefix >= 1; n_prefix--) { - /* n_diff_all_analyzed_pages can be 0 here if + /* n_diff_total can be 0 here if all the leaf pages sampled contained only delete-marked records. In this case we should assign 0 to index->stat_n_diff_key_vals[n_prefix - 1], which the formula below does. */ - const n_diff_data_t* data = &n_diff_data[n_prefix - 1]; + const NDiffData* data = &n_diff_data[n_prefix - 1]; - ut_ad(data->n_leaf_pages_to_analyze > 0); + ut_ad(data->n_analyze_leaf_pages > 0); ut_ad(data->n_recs_on_level > 0); - ib_uint64_t n_ordinary_leaf_pages; + uint64_t n_ordinary_leaf_pages; if (data->level == 1) { /* If we know the number of records on level 1, then @@ -2232,29 +2184,44 @@ dict_stats_index_set_n_diff( external pages in total linked from those D ordinary leaf pages, then this means that the ratio ordinary/external is D/E. Then the ratio ordinary/total - is D / (D + E). Knowing that the total number of pages - is T (including ordinary and external) then we estimate - that the total number of ordinary leaf pages is - T * D / (D + E). */ + is D / (D + E). Knowing that the total number of leaf + pages is L (including ordinary and external) then we + estimate that the number of ordinary leaf pages is + L_ord = L * D / (D + E). */ n_ordinary_leaf_pages = index_stats.n_leaf_pages - * data->n_leaf_pages_to_analyze - / (data->n_leaf_pages_to_analyze - + data->n_external_pages_sum); + * data->n_analyze_leaf_pages + / (data->n_analyze_leaf_pages + + data->n_external_pages); } - /* See REF01 for an explanation of the algorithm */ + /* n_diff = L_ord * R * avg(Pi); see [REF01] above */ index_stats.stats[n_prefix - 1].n_diff_key_vals = n_ordinary_leaf_pages * data->n_diff_on_level / data->n_recs_on_level - * data->n_diff_all_analyzed_pages - / data->n_leaf_pages_to_analyze; + * data->n_diff_total + / data->n_analyze_leaf_pages; + + /* Calculate n_non_null_key_vals from sampled leaf pages. + Non-NULL values are counted only at the leaf level, because + non-leaf pages hold node pointers rather than data records and + NULL tracking is only meaningful for actual records. The ratio + of non-NULL values is assumed consistent across leaf pages, so + sampling gives an accurate estimate for the whole index. We use + the same formula as n_diff: multiply the average non-NULL count + per sampled leaf by the number of ordinary leaf pages. For a + NOT NULL column every record is counted, so this yields the + estimated record count (i.e. no NULLs). */ + index_stats.stats[n_prefix - 1].n_non_null_key_vals + = n_ordinary_leaf_pages + * data->n_non_null_total + / data->n_analyze_leaf_pages; index_stats.stats[n_prefix - 1].n_sample_sizes - = data->n_leaf_pages_to_analyze; + = data->n_analyze_leaf_pages; DEBUG_PRINTF(" %s(): n_diff=" UINT64PF " for n_prefix=" ULINTPF @@ -2267,25 +2234,41 @@ dict_stats_index_set_n_diff( index_stats.n_leaf_pages, data->n_diff_on_level, data->n_recs_on_level, - data->n_diff_all_analyzed_pages, - data->n_leaf_pages_to_analyze); + data->n_diff_total, + data->n_analyze_leaf_pages); } } -/** Calculates new statistics for a given index and saves them to the index -members stat_n_diff_key_vals[], stat_n_sample_sizes[], stat_index_size and -stat_n_leaf_pages. This function can be slow. -@param[in] index index to analyze +/** Calculate and persist statistics for one index by sampling. Saves the +results into stat_n_diff_key_vals[], stat_n_sample_sizes[], stat_index_size +and stat_n_leaf_pages. This function can be slow. + +For each n-prefix (n = 1..n_uniq): + 1. Descend from the root, full-scanning each level (analyze_level()) until a + level LA has >= 10*A distinct keys for this prefix. The search stops early + at level 1 (never scan the leaf level) or if the next level would exceed A + pages then scanning many more than A non-leaf pages to sample A leaf pages is + not worthwhile. + 2. Divide LA into groups of equal keys, pick A representative records, and + dive below each to a leaf page (sample_leaf_pages()). + 3. Derive the estimate from the samples (dict_stats_index_set_n_diff()). + +Shortcut: tiny indexes (root_level == 0, or A*n_uniq > L) are scanned in full +instead of sampled, which is both faster and exact. +@param[in] trx transaction +@param[in] index index to analyze +@param[in] stats_method innodb_stats_method variable @return index stats */ -static index_stats_t dict_stats_analyze_index(trx_t *trx, dict_index_t* index) +static IndexStats dict_stats_analyze_index(trx_t *trx, + dict_index_t *index, + unsigned stats_method) { bool level_is_analyzed; - ulint n_uniq; - ulint n_prefix; - ib_uint64_t total_recs; - ib_uint64_t total_pages; - mtr_t mtr{trx}; - index_stats_t result(index->n_uniq); + mtr_t mtr(trx); + IndexStats result(index->n_uniq); + uint32_t n_sample_pages = N_SAMPLE_PAGES(index); + uint64_t n_diff_required = N_DIFF_REQUIRED(index); + DBUG_ENTER("dict_stats_analyze_index"); DBUG_PRINT("info", ("index: %s, online status: %d", index->name(), @@ -2332,7 +2315,7 @@ empty_index: mtr.start(); mtr_sx_lock_index(index, &mtr); - n_uniq = dict_index_get_n_unique(index); + uint16_t n_uniq = dict_index_get_n_unique(index); /* If the tree has just one level (and one page) or if the user has requested to sample too many pages then do full scan. @@ -2344,7 +2327,7 @@ empty_index: since it will be faster and will give better results. */ if (root_level == 0 - || N_SAMPLE_PAGES(index) * n_uniq > result.n_leaf_pages) { + || n_sample_pages * n_uniq > result.n_leaf_pages) { if (root_level == 0) { DEBUG_PRINTF(" %s(): just one page," @@ -2354,49 +2337,51 @@ empty_index: " sampling, doing full scan\n", __func__); } + memset(index->stat_n_diff_key_vals, 0x0, + n_uniq * sizeof *index->stat_n_diff_key_vals); + if (index->stat_n_non_null_key_vals) { + memset(index->stat_n_non_null_key_vals, 0x0, + n_uniq * + sizeof *index->stat_n_non_null_key_vals); + } + /* do full scan of level 0; save results directly into the index */ - dict_stats_analyze_index_level(index, - 0 /* leaf level */, - index->stat_n_diff_key_vals, - &total_recs, - &total_pages, - NULL /* boundaries not needed */, - &mtr); + const uint32_t n_leaf_pages{ + IndexLevelStats{stats_method > SRV_STATS_NULLS_EQUAL, + 0, index->stat_n_diff_key_vals, + index->stat_n_non_null_key_vals, + nullptr, index, &mtr} + .analyze_level()}; mtr.commit(); index->table->stats_mutex_lock(); for (ulint i = 0; i < n_uniq; i++) { - result.stats[i].n_diff_key_vals = index->stat_n_diff_key_vals[i]; - result.stats[i].n_sample_sizes = total_pages; - result.stats[i].n_non_null_key_vals = index->stat_n_non_null_key_vals[i]; + result.stats[i].n_diff_key_vals = + index->stat_n_diff_key_vals[i]; + result.stats[i].n_sample_sizes = n_leaf_pages; + result.stats[i].n_non_null_key_vals = + index->stat_n_non_null_key_vals[i]; } - result.n_leaf_pages = index->stat_n_leaf_pages; + /* For multi-level indexes, use the actual number + of leaf pages counted during the full scan. + For single-page indexes (root_level == 0), + use the pre-calculated stat_n_leaf_pages which + is always 1. */ + result.n_leaf_pages = (root_level != 0) + ? n_leaf_pages + : index->stat_n_leaf_pages; index->table->stats_mutex_unlock(); DBUG_RETURN(result); } - /* For each level that is being scanned in the btree, this contains the - number of different key values for all possible n-column prefixes. */ - ib_uint64_t* n_diff_on_level = UT_NEW_ARRAY( - ib_uint64_t, n_uniq, mem_key_dict_stats_n_diff_on_level); - - /* For each level that is being scanned in the btree, this contains the - index of the last record from each group of equal records (when - comparing only the first n columns, n=1..n_uniq). */ - boundaries_t* n_diff_boundaries = UT_NEW_ARRAY_NOKEY(boundaries_t, - n_uniq); - - /* For each n-column prefix this array contains the input data that is - used to calculate dict_index_t::stat_n_diff_key_vals[]. */ - n_diff_data_t* n_diff_data = UT_NEW_ARRAY_NOKEY(n_diff_data_t, n_uniq); - - /* total_recs is also used to estimate the number of pages on one - level below, so at the start we have 1 page (the root) */ - total_recs = 1; + /* For each n-column prefix this array contains the + input data that is used to calculate + dict_index_t::stat_n_diff_key_vals[]. */ + NDiffData n_diff_data[MAX_REF_PARTS * 2]={{0, 0, 0, 0, 0, 0, 0}}; /* Here we use the following optimization: If we find that level L is the first one (searching from the @@ -2409,13 +2394,21 @@ empty_index: keys (on n_prefix columns) is L, we continue from L when searching for D distinct keys on n_prefix-1 columns. */ auto level = root_level; + uint16_t n_prefix; level_is_analyzed = false; - for (n_prefix = n_uniq; n_prefix >= 1; n_prefix--) { + /* Stack-allocated buffers for level statistics. + Since there can be at most 64 fields in an index, + stack allocation is safe and efficient. */ + uint64_t n_diff_buf[64]{}; + Boundaries bounds[64]; + ut_ad(n_uniq <= 64); - DEBUG_PRINTF(" %s(): searching level with >=%llu " - "distinct records, n_prefix=" ULINTPF "\n", - __func__, N_DIFF_REQUIRED(index), n_prefix); + IndexLevelStats level_stats(stats_method > SRV_STATS_NULLS_EQUAL, + level, n_diff_buf, nullptr, + bounds, index, &mtr); + + for (n_prefix = n_uniq; n_prefix >= 1; n_prefix--) { /* Commit the mtr to release the tree S lock to allow other threads to do some work too. */ @@ -2444,21 +2437,19 @@ empty_index: we pick level 1 even if it does not have enough distinct records because we do not want to scan the leaf level because it may contain too many records */ - if (level_is_analyzed - && (n_diff_on_level[n_prefix - 1] >= N_DIFF_REQUIRED(index) - || level == 1)) { - + if (level_is_analyzed && + (level_stats.n_diff[n_prefix - 1] >= + n_diff_required || level == 1)) { goto found_level; } /* search for a level that contains enough distinct records */ - if (level_is_analyzed && level > 1) { /* if this does not hold we should be on "found_level" instead of here */ - ut_ad(n_diff_on_level[n_prefix - 1] - < N_DIFF_REQUIRED(index)); + ut_ad(level_stats.n_diff[n_prefix - 1] + < n_diff_required); level--; level_is_analyzed = false; @@ -2479,16 +2470,18 @@ empty_index: Here we use the following fact: the number of pages on level L equals the number of records on level L+1, thus we deduce that the - following call would scan total_recs pages, because - total_recs is left from the previous iteration when - we scanned one level upper or we have not scanned any - levels yet in which case total_recs is 1. */ - if (total_recs > N_SAMPLE_PAGES(index)) { + following call would scan level_stats.n_recs pages, + because level_stats.n_recs is left from the previous + iteration when we scanned one level upper or we have + not scanned any levels yet in which case + level_stats.n_recs is 1. */ + if (level_stats.n_recs > n_sample_pages) { /* if the above cond is true then we are not at the root level since on the root - level total_recs == 1 (set before we - enter the n-prefix loop) and cannot + level level_stats.n_recs == 1 (set by + the IndexLevelStats constructor, before + we enter the n-prefix loop) and cannot be > N_SAMPLE_PAGES(index) */ ut_a(level != root_level); @@ -2501,19 +2494,17 @@ empty_index: } mtr.rollback_to_savepoint(1); - dict_stats_analyze_index_level(index, - level, - n_diff_on_level, - &total_recs, - &total_pages, - n_diff_boundaries, - &mtr); + + level_stats.reset_for_level(level); + + level_stats.analyze_level(); + mtr.rollback_to_savepoint(1); level_is_analyzed = true; if (level == 1 - || n_diff_on_level[n_prefix - 1] - >= N_DIFF_REQUIRED(index)) { + || level_stats.n_diff[n_prefix - 1] + >= n_diff_required) { /* we have reached the last level we could scan or we found a good level with many distinct records */ @@ -2524,12 +2515,6 @@ empty_index: level_is_analyzed = false; } found_level: - - DEBUG_PRINTF(" %s(): found level " ULINTPF - " that has " UINT64PF - " distinct records for n_prefix=" ULINTPF "\n", - __func__, level, n_diff_on_level[n_prefix - 1], - n_prefix); /* here we are either on level 1 or the level that we are on contains >= N_DIFF_REQUIRED distinct keys or we did not scan deeper levels because they would contain too many pages */ @@ -2541,45 +2526,37 @@ found_level: /* if any of these is 0 then there is exactly one page in the B-tree and it is empty and we should have done full scan and should not be here */ - ut_ad(total_recs > 0); - ut_ad(n_diff_on_level[n_prefix - 1] > 0); + ut_ad(level_stats.n_recs > 0); + ut_ad(level_stats.n_diff[n_prefix - 1] > 0); - ut_ad(N_SAMPLE_PAGES(index) > 0); + ut_ad(n_sample_pages > 0); - n_diff_data_t* data = &n_diff_data[n_prefix - 1]; + NDiffData* data = &n_diff_data[n_prefix - 1]; data->level = level; - data->n_recs_on_level = total_recs; + data->n_recs_on_level = level_stats.n_recs; - data->n_diff_on_level = n_diff_on_level[n_prefix - 1]; + data->n_diff_on_level = level_stats.n_diff[n_prefix - 1]; - data->n_leaf_pages_to_analyze = std::min( - N_SAMPLE_PAGES(index), - n_diff_on_level[n_prefix - 1]); + data->n_analyze_leaf_pages = std::min( + n_sample_pages, + uint32_t(level_stats.n_diff[n_prefix - 1])); - /* pick some records from this level and dive below them for - the given n_prefix */ - - dict_stats_analyze_index_for_n_prefix( - index, n_prefix, &n_diff_boundaries[n_prefix - 1], - data, &mtr); + ut_ad(level_stats.n_non_null_per_col == nullptr); + /* pick some records from this level and dive below + them for the given n_prefix */ + level_stats.sample_leaf_pages(trx, n_prefix, data); } mtr.commit(); - UT_DELETE_ARRAY(n_diff_boundaries); - - UT_DELETE_ARRAY(n_diff_on_level); - /* n_prefix == 0 means that the above loop did not end up prematurely due to tree being changed and so n_diff_data[] is set up. */ if (n_prefix == 0) { - dict_stats_index_set_n_diff(n_diff_data, result); + dict_stats_index_set_n_diff(index, n_diff_data, result); } - UT_DELETE_ARRAY(n_diff_data); - DBUG_RETURN(result); } @@ -2587,10 +2564,9 @@ dberr_t dict_stats_update_persistent(trx_t *trx, dict_table_t *table) noexcept { dict_index_t* index; - DEBUG_PRINTF("%s(table=%s)\n", __func__, table->name); - DEBUG_SYNC_C("dict_stats_update_persistent"); + const unsigned stats_method = unsigned(srv_innodb_stats_method); if (trx_sys.is_registered(nullptr, table->bulk_trx_id)) { dict_stats_empty_table(table); return DB_SUCCESS_LOCKED_REC; @@ -2614,7 +2590,7 @@ dberr_t dict_stats_update_persistent(trx_t *trx, dict_table_t *table) noexcept dict_stats_empty_index(index); table->stats_mutex_unlock(); - index_stats_t stats = dict_stats_analyze_index(trx, index); + IndexStats stats = dict_stats_analyze_index(trx, index, stats_method); if (stats.is_bulk_operation()) { dict_stats_empty_table(table); @@ -2627,7 +2603,8 @@ dberr_t dict_stats_update_persistent(trx_t *trx, dict_table_t *table) noexcept for (size_t i = 0; i < stats.stats.size(); ++i) { index->stat_n_diff_key_vals[i] = stats.stats[i].n_diff_key_vals; index->stat_n_sample_sizes[i] = stats.stats[i].n_sample_sizes; - index->stat_n_non_null_key_vals[i] = stats.stats[i].n_non_null_key_vals; + index->stat_n_non_null_key_vals[i] = + stats.stats[i].n_non_null_key_vals; } ulint n_unique = dict_index_get_n_unique(index); @@ -2655,7 +2632,7 @@ dberr_t dict_stats_update_persistent(trx_t *trx, dict_table_t *table) noexcept } table->stats_mutex_unlock(); - stats = dict_stats_analyze_index(trx, index); + stats = dict_stats_analyze_index(trx, index, stats_method); table->stats_mutex_lock(); if (stats.is_bulk_operation()) { @@ -2690,7 +2667,7 @@ dberr_t dict_stats_update_persistent(trx_t *trx, dict_table_t *table) noexcept table->stats_mutex_unlock(); - return(DB_SUCCESS); + return dict_stats_save(table, stats_method); } dberr_t dict_stats_update_persistent_try(trx_t *trx, dict_table_t *table) @@ -2698,11 +2675,7 @@ dberr_t dict_stats_update_persistent_try(trx_t *trx, dict_table_t *table) { if (table->stats_is_persistent() && dict_stats_persistent_storage_check(false) == SCHEMA_OK) - { - if (dberr_t err= dict_stats_update_persistent(trx, table)) - return err; - return dict_stats_save(table); - } + return dict_stats_update_persistent(trx, table); return DB_SUCCESS; } @@ -2722,8 +2695,8 @@ dict_stats_save_index_stat( dict_index_t* index, time_t last_update, const char* stat_name, - ib_uint64_t stat_value, - ib_uint64_t* sample_size, + uint64_t stat_value, + uint64_t* sample_size, const char* stat_description, trx_t* trx) { @@ -2829,11 +2802,25 @@ static dberr_t dict_stats_report_error(dict_table_t* table) return err; } -/** Save the persistent statistics of a table or an index. -@param table table whose stats to save -@param only_for_index the index ID to save statistics for (0=all) -@return DB_SUCCESS or error code */ -dberr_t dict_stats_save(dict_table_t* table, index_id_t index_id) +static const char *stats_method_name[]= { + nullptr, " NULLS_UNEQUAL", " NULLS_IGNORED" +}; + +/** Return a display name for the innodb_stats_method +@param stat_method innodb_stats_method during index statistics +@return innodb_stats_method name */ +static const char *get_innodb_stats_method(unsigned stat_method) +{ + ut_ad(stat_method < 3); + static_assert(SRV_STATS_NULLS_EQUAL == 0, ""); + static_assert(SRV_STATS_NULLS_UNEQUAL == 1, ""); + static_assert(SRV_STATS_NULLS_IGNORED == 2, ""); + return stat_method < 3 ? stats_method_name[stat_method] : nullptr; +} + +static +dberr_t dict_stats_save(dict_table_t* table, unsigned stats_method, + index_id_t index_id) noexcept { pars_info_t* pinfo; char db_utf8[MAX_DB_UTF8_LEN]; @@ -2993,13 +2980,31 @@ unlocked_free_and_exit: snprintf(stat_description, sizeof(stat_description), "%s", index->fields[0].name()); for (unsigned j = 1; j <= i; j++) { - size_t len; - - len = strlen(stat_description); - + size_t len = strlen(stat_description); + size_t remaining = + sizeof(stat_description) - len; + /* Ensure we have enough space for + "," + field_name + null terminator */ + size_t field_name_len = + strlen(index->fields[j].name()); + if (remaining < field_name_len + 2) { + break; + } snprintf(stat_description + len, - sizeof(stat_description) - len, - ",%s", index->fields[j].name()); + remaining, ",%s", + index->fields[j].name()); + } + + if (const char *stats_method_name = + get_innodb_stats_method(stats_method)) { + size_t desc_len = strlen(stat_description); + size_t method_len = strlen(stats_method_name); + size_t remaining = sizeof(stat_description) + - desc_len - 1; + if (method_len <= remaining) { + strncat(stat_description, + stats_method_name, remaining); + } } ret = dict_stats_save_index_stat( @@ -3011,6 +3016,24 @@ unlocked_free_and_exit: if (ret != DB_SUCCESS) { goto rollback_and_exit; } + + /* Update n_nonnull_fld */ + snprintf(stat_name, sizeof(stat_name), + "n_nonnull_fld%02u", i + 1); + + snprintf(stat_description, + sizeof(stat_description), + "%s", index->fields[i].name()); + + ret = dict_stats_save_index_stat( + index, now, stat_name, + index->stat_n_non_null_key_vals[i], + &index->stat_n_sample_sizes[i], + stat_description, trx); + + if (ret != DB_SUCCESS) { + goto rollback_and_exit; + } } ret = dict_stats_save_index_stat(index, now, "n_leaf_pages", @@ -3041,6 +3064,11 @@ unlocked_free_and_exit: goto free_and_exit; } +static dberr_t dict_stats_save(dict_table_t *table) +{ + return dict_stats_save(table, unsigned(srv_innodb_stats_method)); +} + void dict_stats_empty_table_and_save(dict_table_t *table) { dict_stats_empty_table(table); @@ -3131,13 +3159,41 @@ dict_stats_fetch_table_stats_step( return(TRUE); } +/** Report that a row with a malformed or out-of-range stat_name was found +in mysql.innodb_index_stats and is being ignored. +@param table table the statistics belong to +@param index index the statistics belong to +@param stat_name value of the stat_name column (not NUL-terminated) +@param stat_name_len length of stat_name */ +static void dict_stats_report_bad_index_stat( + const dict_table_t* table, + const dict_index_t* index, + const char* stat_name, + size_t stat_name_len) +{ + char db_utf8[MAX_DB_UTF8_LEN]; + char table_utf8[MAX_TABLE_UTF8_LEN]; + + dict_fs2utf8(table->name.m_name, db_utf8, sizeof(db_utf8), + table_utf8, sizeof(table_utf8)); + + sql_print_information("InnoDB: Malformed stat_name='%.*s' in " + INDEX_STATS_NAME_PRINT " WHERE " + "database_name=%`s AND table_name=%`s AND " + "index_name=%`s", + (int) stat_name_len, stat_name, + db_utf8, table_utf8, index->name()); +} + /** Aux struct used to pass a table and a boolean to dict_stats_fetch_index_stats_step(). */ -struct index_fetch_t { +namespace { +struct IndexFetch { dict_table_t* table; /*!< table whose indexes are to be modified */ bool stats_were_modified; /*!< will be set to true if at least one index stats were modified */ }; +} // anonymous namespace /*********************************************************************//** Called for the rows that are selected by @@ -3165,14 +3221,14 @@ dict_stats_fetch_index_stats_step( modified anything */ { sel_node_t* node = (sel_node_t*) node_void; - index_fetch_t* arg = (index_fetch_t*) arg_void; + IndexFetch* arg = (IndexFetch*) arg_void; dict_table_t* table = arg->table; dict_index_t* index = NULL; que_common_t* cnode; const char* stat_name = NULL; ulint stat_name_len = ULINT_UNDEFINED; - ib_uint64_t stat_value = UINT64_UNDEFINED; - ib_uint64_t sample_size = UINT64_UNDEFINED; + uint64_t stat_value = UINT64_UNDEFINED; + uint64_t sample_size = UINT64_UNDEFINED; int i; /* this should loop exactly 4 times - for the columns that @@ -3315,22 +3371,8 @@ dict_stats_fetch_index_stats_step( || num_ptr[0] < '0' || num_ptr[0] > '9' || num_ptr[1] < '0' || num_ptr[1] > '9') { - char db_utf8[MAX_DB_UTF8_LEN]; - char table_utf8[MAX_TABLE_UTF8_LEN]; - - dict_fs2utf8(table->name.m_name, - db_utf8, sizeof(db_utf8), - table_utf8, sizeof(table_utf8)); - - ib::info out; - out << "Ignoring strange row from " - << INDEX_STATS_NAME_PRINT << " WHERE" - " database_name = '" << db_utf8 - << "' AND table_name = '" << table_utf8 - << "' AND index_name = '" << index->name() - << "' AND stat_name = '"; - out.write(stat_name, stat_name_len); - out << "'; because stat_name is malformed"; + dict_stats_report_bad_index_stat( + table, index, stat_name, stat_name_len); return(TRUE); } /* else */ @@ -3343,24 +3385,8 @@ dict_stats_fetch_index_stats_step( if (n_pfx == 0 || n_pfx > n_uniq) { - char db_utf8[MAX_DB_UTF8_LEN]; - char table_utf8[MAX_TABLE_UTF8_LEN]; - - dict_fs2utf8(table->name.m_name, - db_utf8, sizeof(db_utf8), - table_utf8, sizeof(table_utf8)); - - ib::info out; - out << "Ignoring strange row from " - << INDEX_STATS_NAME_PRINT << " WHERE" - " database_name = '" << db_utf8 - << "' AND table_name = '" << table_utf8 - << "' AND index_name = '" << index->name() - << "' AND stat_name = '"; - out.write(stat_name, stat_name_len); - out << "'; because stat_name is out of range, the index" - " has " << n_uniq << " unique columns"; - + dict_stats_report_bad_index_stat( + table, index, stat_name, stat_name_len); return(TRUE); } /* else */ @@ -3369,14 +3395,49 @@ dict_stats_fetch_index_stats_step( if (sample_size != UINT64_UNDEFINED) { index->stat_n_sample_sizes[n_pfx - 1] = - std::max(sample_size, 1); + std::max(sample_size, 1); } else { /* hmm, strange... the user must have UPDATEd the table manually and SET sample_size = NULL */ index->stat_n_sample_sizes[n_pfx - 1] = 0; } - index->stat_n_non_null_key_vals[n_pfx - 1] = 0; + arg->stats_were_modified = true; + } else if (stat_name_len >= sizeof "n_nonnull_fld" + && strncasecmp("n_nonnull_fld", stat_name, + sizeof "n_nonnull_fld" - 1) == 0) { + + const char* num_ptr; + unsigned n_field; + + /* point num_ptr into "01" from "n_nonnull_fld01..." */ + num_ptr = stat_name + (sizeof "n_nonnull_fld" - 1); + + /* stat_name should have exactly 2 chars appended to "n_nonnull_fld" + and they should be digits */ + if (stat_name_len != sizeof "n_nonnull_fld01" - 1 + || num_ptr[0] < '0' || num_ptr[0] > '9' + || num_ptr[1] < '0' || num_ptr[1] > '9') { + + dict_stats_report_bad_index_stat( + table, index, stat_name, stat_name_len); + return(TRUE); + } + /* else */ + + /* extract 01 from "n_nonnull_fld01..." into n_col + note that stat_name does not have a terminating '\0' */ + n_field = (num_ptr[0] - '0') * 10 + (num_ptr[1] - '0'); + + if (n_field == 0 || n_field > index->n_uniq) { + + dict_stats_report_bad_index_stat( + table, index, stat_name, stat_name_len); + return(TRUE); + } + /* else */ + + index->stat_n_non_null_key_vals[n_field - 1] = stat_value; arg->stats_were_modified = true; } else { @@ -3391,7 +3452,7 @@ dict_stats_fetch_index_stats_step( /** Read the stored persistent statistics of a table. */ dberr_t dict_stats_fetch_from_ps(dict_table_t *table) { - index_fetch_t index_fetch_arg; + IndexFetch index_fetch_arg; pars_info_t* pinfo; char db_utf8[MAX_DB_UTF8_LEN]; char table_utf8[MAX_TABLE_UTF8_LEN]; @@ -3508,6 +3569,7 @@ dberr_t dict_stats_fetch_from_ps(dict_table_t *table) void dict_stats_update_for_index(trx_t *trx, dict_index_t *index) noexcept { dict_table_t *const table= index->table; + unsigned stats_method = unsigned(srv_innodb_stats_method); ut_ad(table->stat_initialized()); if (table->stats_is_persistent()) @@ -3530,7 +3592,7 @@ void dict_stats_update_for_index(trx_t *trx, dict_index_t *index) noexcept table->name.basename(), index->name()); break; case SCHEMA_OK: - index_stats_t stats{dict_stats_analyze_index(trx, index)}; + IndexStats stats{dict_stats_analyze_index(trx, index, stats_method)}; table->stats_mutex_lock(); index->stat_index_size = stats.index_size; index->stat_n_leaf_pages = stats.n_leaf_pages; @@ -3542,7 +3604,7 @@ void dict_stats_update_for_index(trx_t *trx, dict_index_t *index) noexcept } table->stat_sum_of_other_index_sizes+= index->stat_index_size; table->stats_mutex_unlock(); - dict_stats_save(table, index->id); + dict_stats_save(table, stats_method, index->id); return; } @@ -3775,12 +3837,12 @@ test_dict_stats_save() dict_table_t table; dict_index_t index1; dict_field_t index1_fields[1]; - ib_uint64_t index1_stat_n_diff_key_vals[1]; - ib_uint64_t index1_stat_n_sample_sizes[1]; + uint64_t index1_stat_n_diff_key_vals[1]; + uint64_t index1_stat_n_sample_sizes[1]; dict_index_t index2; dict_field_t index2_fields[4]; - ib_uint64_t index2_stat_n_diff_key_vals[4]; - ib_uint64_t index2_stat_n_sample_sizes[4]; + uint64_t index2_stat_n_diff_key_vals[4]; + uint64_t index2_stat_n_sample_sizes[4]; dberr_t ret; /* craft a dummy dict_table_t */ @@ -3931,11 +3993,11 @@ test_dict_stats_fetch_from_ps() { dict_table_t table; dict_index_t index1; - ib_uint64_t index1_stat_n_diff_key_vals[1]; - ib_uint64_t index1_stat_n_sample_sizes[1]; + uint64_t index1_stat_n_diff_key_vals[1]; + uint64_t index1_stat_n_sample_sizes[1]; dict_index_t index2; - ib_uint64_t index2_stat_n_diff_key_vals[4]; - ib_uint64_t index2_stat_n_sample_sizes[4]; + uint64_t index2_stat_n_diff_key_vals[4]; + uint64_t index2_stat_n_sample_sizes[4]; dberr_t ret; /* craft a dummy dict_table_t */ diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index 40191705c13..623b912b677 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -5685,12 +5685,8 @@ dberr_t ha_innobase::statistics_init(dict_table_t *table, bool recalc) switch (dict_stats_persistent_storage_check(false)) { case SCHEMA_OK: if (recalc) - { recalc: err= dict_stats_update_persistent(m_prebuilt->trx, table); - if (err == DB_SUCCESS) - err= dict_stats_save(table); - } else { err= dict_stats_fetch_from_ps(table); @@ -14662,16 +14658,42 @@ innodb_rec_per_key( rec_per_key = static_cast(records); } else if (srv_innodb_stats_method == SRV_STATS_NULLS_IGNORED) { - ib_uint64_t n_null; - ib_uint64_t n_non_null; + uint64_t n_null; + uint64_t n_non_null = records; - n_non_null = index->stat_n_non_null_key_vals[i]; + /* stat_n_non_null_key_vals[j] is the number of records in + which the j-th indexed column alone is not NULL, while what + has to be excluded below is the records that carry a NULL + anywhere in the first i+1 columns: with nulls_ignored those + records compare unequal to every other record, so each of them + adds a distinct value of its own to n_diff. Their number cannot + exceed the smallest of the per-column counts, because the + records whose first i+1 columns are all not NULL are a subset + of the records where any single one of those columns is not + NULL. Hence the minimum, which is exact whenever the NULLs of + the prefix are confined to one of its columns. - /* In theory, index->stat_n_non_null_key_vals[i] - should always be less than the number of records. - Since this is statistics value, the value could - have slight discrepancy. But we will make sure - the number of null values is not a negative number. */ + Only a NULL-able column can lower the count, so start from the + number of records and let just those columns reduce it: for a + NOT NULL column every record counts by definition, and feeding + its sampled estimate into the minimum would only let sampling + noise pull the result below the truth. A prefix made up + entirely of NOT NULL columns therefore keeps n_non_null = + records, i.e. n_null = 0, which is the correct answer because + such a prefix cannot contain a NULL at all. */ + for (ulint j = 0; j <= i; j++) { + if (index->fields[j].col->is_nullable()) { + n_non_null = std::min( + n_non_null, + index->stat_n_non_null_key_vals[j]); + } + } + + /* In theory, n_non_null should always be less than + the number of records. Since this is statistics value, + the value could have slight discrepancy. But we will + make sure the number of null values is not a + negative number. */ if (records < n_non_null) { n_null = 0; } else { @@ -21305,8 +21327,6 @@ void alter_stats_rebuild(dict_table_t *table, trx_t *trx, bool copy) noexcept DBUG_VOID_RETURN; dberr_t ret= dict_stats_update_persistent(trx, table); - if (ret == DB_SUCCESS) - ret= dict_stats_save(table); if (ret != DB_SUCCESS) push_warning_printf(trx->mysql_thd, Sql_condition::WARN_LEVEL_WARN, ER_ALTER_INFO, "Error updating stats for table after" diff --git a/storage/innobase/include/btr0cur.h b/storage/innobase/include/btr0cur.h index 9e2d74bf924..b7344982e59 100644 --- a/storage/innobase/include/btr0cur.h +++ b/storage/innobase/include/btr0cur.h @@ -66,6 +66,19 @@ enum { #define btr_cur_get_block(cursor) ((cursor)->page_cur.block) #define btr_cur_get_rec(cursor) ((cursor)->page_cur.rec) +/** The structure of a BLOB part header */ +/* @{ */ +/*--------------------------------------*/ +#define BTR_BLOB_HDR_PART_LEN 0 /*!< BLOB part len on this + page */ +#define BTR_BLOB_HDR_NEXT_PAGE_NO 4 /*!< next BLOB part page no, + FIL_NULL if none */ +/*--------------------------------------*/ +#define BTR_BLOB_HDR_SIZE 8 /*!< Size of a BLOB + part header, in bytes */ + +/* @} */ + /*********************************************************//** Returns the compressed page on which the tree cursor is positioned. @return pointer to compressed page, or NULL if the page is not compressed */ @@ -460,14 +473,19 @@ ha_rows btr_estimate_n_rows_in_range(trx_t *trx, dict_index_t *index, btr_pos_t *range_start, btr_pos_t *range_end); -/** Gets the externally stored size of a record, in units of a database page. -@param[in] rec record -@param[in] offsets array returned by rec_get_offsets() -@return externally stored part, in units of a database page */ -ulint -btr_rec_get_externally_stored_len( - const rec_t* rec, - const rec_offs* offsets); +/** Gets the offset of the pointer to the externally stored part of a field. +@param offsets offsets of record +@param n index of the external field +@return offset of the pointer to the externally stored part */ +size_t btr_rec_get_field_ref_offs(const rec_offs *offsets, size_t n) noexcept; + +/** Gets a pointer to the externally stored part of a field. +@param rec record +@param offsets rec_get_offsets(rec) +@param n index of the externally stored field +@return pointer to the externally stored part */ +#define btr_rec_get_field_ref(rec, offsets, n) \ + ((rec) + btr_rec_get_field_ref_offs(offsets, n)) /*******************************************************************//** Marks non-updated off-page fields as disowned by this record. The ownership diff --git a/storage/innobase/include/btr0pcur.h b/storage/innobase/include/btr0pcur.h index 5f84328da19..99dee337914 100644 --- a/storage/innobase/include/btr0pcur.h +++ b/storage/innobase/include/btr0pcur.h @@ -198,18 +198,22 @@ btr_pcur_move_to_next_user_rec( btr_pcur_t* cursor, /*!< in: persistent cursor; NOTE that the function may release the page latch */ mtr_t* mtr); /*!< in: mtr */ -/*********************************************************//** -Moves the persistent cursor to the first record on the next page. -Releases the latch on the current page, and bufferunfixes it. + +/** Moves the persistent cursor to the first record on the next page. +Releases the latch on the current page, and bufferunfixes it, unless +retain_latch was requested. The latch on the next page is always +acquired before the current page is released. Note that there must not be modifications on the current page, -as then the x-latch can be released only in mtr_commit. */ +as then the x-latch can be released only in mtr_commit. +@param cursor persistent cursor; must be on the last record + of the current page +@param mtr mini-transaction +@param retain_latch whether to retain the latch on the current page +@return error code */ dberr_t -btr_pcur_move_to_next_page( -/*=======================*/ - btr_pcur_t* cursor, /*!< in: persistent cursor; must be on the - last record of the current page */ - mtr_t* mtr) /*!< in: mtr */ - MY_ATTRIBUTE((nonnull, warn_unused_result)); +btr_pcur_move_to_next_page(btr_pcur_t *cursor, mtr_t *mtr, + bool retain_latch= false) + MY_ATTRIBUTE((nonnull, warn_unused_result)); #define btr_pcur_get_btr_cur(cursor) (&(cursor)->btr_cur) #define btr_pcur_get_page_cur(cursor) (&(cursor)->btr_cur.page_cur) diff --git a/storage/innobase/include/data0type.h b/storage/innobase/include/data0type.h index 98a99847285..b646e5c57d6 100644 --- a/storage/innobase/include/data0type.h +++ b/storage/innobase/include/data0type.h @@ -31,7 +31,7 @@ Created 1/16/1996 Heikki Tuuri #define UNIV_SQL_DEFAULT (UNIV_SQL_NULL - 1) /** @return whether a length is actually stored in a field */ -#define len_is_stored(len) (len != UNIV_SQL_NULL && len != UNIV_SQL_DEFAULT) +#define len_is_stored(len) (len < UNIV_SQL_DEFAULT) #define DATA_MYSQL_BINARY_CHARSET_COLL 63 diff --git a/storage/innobase/include/dict0mem.h b/storage/innobase/include/dict0mem.h index deae58f1687..99ed6fa83ff 100644 --- a/storage/innobase/include/dict0mem.h +++ b/storage/innobase/include/dict0mem.h @@ -1082,7 +1082,7 @@ struct dict_index_t { /*----------------------*/ /** Statistics for query optimization */ /* @{ */ - ib_uint64_t* stat_n_diff_key_vals; + uint64_t* stat_n_diff_key_vals; /*!< approximate number of different key values for this index, for each n-column prefix where 1 <= n <= @@ -1095,13 +1095,14 @@ struct dict_index_t { to calculate each of stat_n_diff_key_vals[], e.g. stat_n_sample_sizes[3] pages were sampled to get the number stat_n_diff_key_vals[3]. */ - ib_uint64_t* stat_n_non_null_key_vals; + uint64_t* stat_n_non_null_key_vals; /* approximate number of non-null key values for this index, for each column where 1 <= n <= dict_get_n_unique(index) (the array - is indexed from 0 to n_uniq-1); This - is used when innodb_stats_method is - "nulls_ignored". */ + is indexed from 0 to n_uniq-1); These are + per-column counts, unlike the n-column prefix + counts in stat_n_diff_key_vals[]. This is used + when innodb_stats_method is "nulls_ignored" */ uint32_t stat_index_size; /*!< approximate index size in database pages */ diff --git a/storage/innobase/include/dict0stats.h b/storage/innobase/include/dict0stats.h index f8375f87458..6a07d5acc63 100644 --- a/storage/innobase/include/dict0stats.h +++ b/storage/innobase/include/dict0stats.h @@ -83,12 +83,6 @@ enum dict_stats_schema_check { dict_stats_schema_check dict_stats_persistent_storage_check(bool dict_already_locked= false) noexcept; -/** Save the persistent statistics of a table or an index. -@param table table whose stats to save -@param only_for_index the index ID to save statistics for (0=all) -@return DB_SUCCESS or error code */ -dberr_t dict_stats_save(dict_table_t* table, index_id_t index_id= 0); - /** Read the stored persistent statistics of a table. */ dberr_t dict_stats_fetch_from_ps(dict_table_t *table); @@ -101,13 +95,14 @@ is relatively quick and is used to calculate non-persistent statistics. @retval DB_SUCCESS_LOCKED REC if the table under bulk insert operation */ dberr_t dict_stats_update_transient(trx_t *trx, dict_table_t *table) noexcept; -/** -Calculate new estimates for table and index statistics. This function -is slower than dict_stats_update_transient(). +/** Updates the persistent statistics for a table. +This function calculates and stores index statistics +for all indexes of the table. The statistics are stored +in the InnoDB data dictionary tables via dict_stats_save() @param trx transaction -@param table table for which the persistent statistics are being updated -@return DB_SUCCESS or error code -@retval DB_SUCCESS_LOCKED_REC if the table under bulk insert operation */ +@param table table whose stats to update +@retval DB_SUCCESS_LOCKED_REC if the table under bulk insert operation +@return DB_SUCCESS or error code */ dberr_t dict_stats_update_persistent(trx_t *trx, dict_table_t *table) noexcept; /** diff --git a/storage/innobase/include/univ.i b/storage/innobase/include/univ.i index 11356043b98..9af766f61d2 100644 --- a/storage/innobase/include/univ.i +++ b/storage/innobase/include/univ.i @@ -148,9 +148,6 @@ using the call command. */ #define UNIV_AIO_DEBUG /* prints info about submitted and reaped AIO requests to the log. */ -#define UNIV_STATS_DEBUG /* prints various stats - related debug info from - dict0stats.c */ #define FTS_INTERNAL_DIAG_PRINT /* FTS internal debugging info output */ #endif diff --git a/storage/innobase/row/row0import.cc b/storage/innobase/row/row0import.cc index dfbe6f758d4..b438654f060 100644 --- a/storage/innobase/row/row0import.cc +++ b/storage/innobase/row/row0import.cc @@ -3179,11 +3179,6 @@ row_import_read_meta_data( return(DB_ERROR); } -#define BTR_BLOB_HDR_PART_LEN 0 /*!< BLOB part len on this page */ -#define BTR_BLOB_HDR_NEXT_PAGE_NO 4 /*!< next BLOB part page no, - FIL_NULL if none */ -#define BTR_BLOB_HDR_SIZE 8 /*!< Size of a BLOB part header, in bytes */ - /* decrypt and decompress page if needed */ static dberr_t decrypt_decompress(fil_space_crypt_t *space_crypt, uint32_t space_flags, span page, From 94a62664bd7136987aefe3c80a020c0f9cd30eb2 Mon Sep 17 00:00:00 2001 From: Rucha Deodhar Date: Fri, 18 Sep 2026 13:00:44 -0400 Subject: [PATCH 52/71] MDEV-41181: ASAN heap-buffer-overflow after SELECT JSON_SCHEMA_VALID --- mysql-test/main/func_json.result | 6 ++++++ mysql-test/main/func_json.test | 6 ++++++ sql/json_schema_helper.cc | 7 +++++-- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/mysql-test/main/func_json.result b/mysql-test/main/func_json.result index 2401164d05d..2f791dc402f 100644 --- a/mysql-test/main/func_json.result +++ b/mysql-test/main/func_json.result @@ -5770,4 +5770,10 @@ SET @value= ' SELECT JSON_SCHEMA_VALID(@schema, @value); JSON_SCHEMA_VALID(@schema, @value) 1 +# +# MDEV-41181: ASAN heap-buffer-overflow after SELECT JSON_SCHEMA_VALID +# +SELECT JSON_SCHEMA_VALID( '{"enum": ["x"]}', CONVERT(CONCAT('"', REPEAT('e', 100000), '"') USING ucs2)) AS r; +r +0 # End of 11.4 tests diff --git a/mysql-test/main/func_json.test b/mysql-test/main/func_json.test index e33f41a2450..c6045cb2bb3 100644 --- a/mysql-test/main/func_json.test +++ b/mysql-test/main/func_json.test @@ -4444,4 +4444,10 @@ SET @value= ' SELECT JSON_SCHEMA_VALID(@schema, @value); +--echo # +--echo # MDEV-41181: ASAN heap-buffer-overflow after SELECT JSON_SCHEMA_VALID +--echo # + +SELECT JSON_SCHEMA_VALID( '{"enum": ["x"]}', CONVERT(CONCAT('"', REPEAT('e', 100000), '"') USING ucs2)) AS r; + --echo # End of 11.4 tests diff --git a/sql/json_schema_helper.cc b/sql/json_schema_helper.cc index 99281a6b8fb..17314729e82 100644 --- a/sql/json_schema_helper.cc +++ b/sql/json_schema_helper.cc @@ -65,7 +65,7 @@ void json_get_normalized_string(json_engine_t *je, String *res, String val("",0,je->s.cs); DYNAMIC_STRING a_res; - if (init_dynamic_string(&a_res, NULL, 0, 0)) + if (init_dynamic_string(&a_res, "", 32, 32)) goto error; if (!json_value_scalar(je)) @@ -95,8 +95,11 @@ void json_get_normalized_string(json_engine_t *je, String *res, } else if(je->value_type == JSON_VALUE_STRING) { - dynstr_set(&a_res, val.c_ptr()); + if (dynstr_realloc(&a_res, je->value_len + 1)) + goto error; + memcpy(a_res.str, val.ptr(), je->value_len); a_res.length= je->value_len; + a_res.str[je->value_len]= '\0'; } res->append(a_res.str, a_res.length, je->s.cs); From 8f3cadfb8e39f303c9401f6cfbf2573acca55b7a Mon Sep 17 00:00:00 2001 From: Georg Richter Date: Mon, 31 Aug 2026 19:13:06 +0200 Subject: [PATCH 53/71] MDEV-40146 vio_gencert function doesn't set serial number this apparently breaks RFC 5280, and makes python cryptography module unhappy. --- vio/viosslfactories.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vio/viosslfactories.c b/vio/viosslfactories.c index 35243d96a0b..7622d169690 100644 --- a/vio/viosslfactories.c +++ b/vio/viosslfactories.c @@ -132,6 +132,8 @@ static X509 *vio_gencert(EVP_PKEY *pkey) if (!X509_set_version(x, X509_VERSION_3)) goto err; + if (!ASN1_INTEGER_set(X509_get_serialNumber(x), 1)) + goto err; if (!(name= X509_get_subject_name(x))) goto err; if (!X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, From 322658cd442333a13b46521d1acca67d1e8366cb Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Sat, 26 Sep 2026 17:50:04 +0200 Subject: [PATCH 54/71] fix building of external plugins ARG_DEPENDS might be empty. tests should be under / not under whatever build dir happened to be named. --- cmake/plugin.cmake | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cmake/plugin.cmake b/cmake/plugin.cmake index 32ca521b289..57c3cce79b9 100644 --- a/cmake/plugin.cmake +++ b/cmake/plugin.cmake @@ -307,7 +307,9 @@ MACRO(MARIADB_ADD_PLUGIN) TARGET_LINK_LIBRARIES (${target} "-Wl,--no-undefined") ENDIF() - ADD_DEPENDENCIES(${target} ${ARG_DEPENDS}) + IF (ARG_DEPENDS) + ADD_DEPENDENCIES(${target} ${ARG_DEPENDS}) + ENDIF() SET_TARGET_PROPERTIES(${target} PROPERTIES OUTPUT_NAME "${ARG_MODULE_OUTPUT_NAME}") @@ -360,9 +362,8 @@ MACRO(MARIADB_ADD_PLUGIN) ENDIF() ENDIF() - GET_FILENAME_COMPONENT(subpath ${CMAKE_CURRENT_SOURCE_DIR} NAME) IF(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/mysql-test") - INSTALL_MYSQL_TEST("${CMAKE_CURRENT_SOURCE_DIR}/mysql-test/" "plugin/${subpath}") + INSTALL_MYSQL_TEST("${CMAKE_CURRENT_SOURCE_DIR}/mysql-test/" "plugin/${target}") ENDIF() IF(TARGET ${target}) From f49e838d367f2154b3edb43effa99ff9eb0d8602 Mon Sep 17 00:00:00 2001 From: DerZc <34330257+DerZc@users.noreply.github.com> Date: Mon, 21 Sep 2026 12:57:17 +0800 Subject: [PATCH 55/71] MDEV-40689 Wrong result: BIT_AND/BIT_OR/BIT_XOR in WINDOW functions over a frame containing NULL BIT_AND, BIT_OR, and BIT_XOR window functions can return incorrect values as a sliding frame moves past NULL input rows. Adding a NULL argument leaves the bit-aggregate state unchanged, but removing that row unconditionally calls remove_as_window() with val_int()'s value. The removal path therefore changes state for a row that never contributed to the aggregate. Evaluate the departing window argument once and retain its unsigned value. Call remove_as_window() only when the evaluated argument is non-NULL. Leave the existing incremental window algorithm and non-window aggregation path in place. The regression checks all three bit aggregates over ROWS BETWEEN 1 PRECEDING AND CURRENT ROW with interleaved NULL and non-NULL values, including removal of a real zero and restoration of the neutral values after the frame becomes all-NULL. Bug report: https://jira.mariadb.org/browse/MDEV-40689 --- mysql-test/main/win_bit.result | 21 +++++++++++++++++++++ mysql-test/main/win_bit.test | 9 +++++++++ sql/item_sum.h | 4 +++- 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/mysql-test/main/win_bit.result b/mysql-test/main/win_bit.result index ac7625beb90..f1579871587 100644 --- a/mysql-test/main/win_bit.result +++ b/mysql-test/main/win_bit.result @@ -115,3 +115,24 @@ pk a b bit_or bit_and bit_xor 8 2 16 208 0 192 drop table t1; drop table t2; +# +# MDEV-40689: Sliding bit aggregates with NULL inputs +# +CREATE TABLE t1 (pk INT PRIMARY KEY,a INT,b INT); +INSERT INTO t1 VALUES (1,0,NULL),(2,0,1),(3,0,2),(4,0,NULL),(5,0,3),(6,1,7),(7,1,NULL),(8,1,6),(9,1,0),(10,1,5),(11,1,NULL),(12,1,NULL); +SELECT pk,b,BIT_AND(b) OVER w AS bit_and,BIT_OR(b) OVER w AS bit_or,BIT_XOR(b) OVER w AS bit_xor FROM t1 WINDOW w AS (ORDER BY pk ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) ORDER BY pk; +pk b bit_and bit_or bit_xor +1 NULL 18446744073709551615 0 0 +2 1 1 1 1 +3 2 0 3 3 +4 NULL 2 2 2 +5 3 3 3 3 +6 7 3 7 4 +7 NULL 7 7 7 +8 6 6 6 6 +9 0 0 6 6 +10 5 0 5 5 +11 NULL 5 5 5 +12 NULL 18446744073709551615 0 0 +DROP TABLE t1; +# End of 11.4 tests diff --git a/mysql-test/main/win_bit.test b/mysql-test/main/win_bit.test index f077d0d67a0..a19ab9a4da6 100644 --- a/mysql-test/main/win_bit.test +++ b/mysql-test/main/win_bit.test @@ -87,3 +87,12 @@ from t2; drop table t1; drop table t2; + +--echo # +--echo # MDEV-40689: Sliding bit aggregates with NULL inputs +--echo # +CREATE TABLE t1 (pk INT PRIMARY KEY,a INT,b INT); +INSERT INTO t1 VALUES (1,0,NULL),(2,0,1),(3,0,2),(4,0,NULL),(5,0,3),(6,1,7),(7,1,NULL),(8,1,6),(9,1,0),(10,1,5),(11,1,NULL),(12,1,NULL); +SELECT pk,b,BIT_AND(b) OVER w AS bit_and,BIT_OR(b) OVER w AS bit_or,BIT_XOR(b) OVER w AS bit_xor FROM t1 WINDOW w AS (ORDER BY pk ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) ORDER BY pk; +DROP TABLE t1; +--echo # End of 11.4 tests diff --git a/sql/item_sum.h b/sql/item_sum.h index 77e02cac30f..a576bd2ea69 100644 --- a/sql/item_sum.h +++ b/sql/item_sum.h @@ -1297,7 +1297,9 @@ public: { if (as_window_function) { - remove_as_window(args[0]->val_int()); + ulonglong value= (ulonglong) args[0]->val_int(); + if (!args[0]->null_value) + remove_as_window(value); return; } // Unless we're counting bits, we can not remove anything. From 1aca995275e893a857e428c6b480287cfbb93a63 Mon Sep 17 00:00:00 2001 From: Alessandro Vetere Date: Fri, 18 Sep 2026 17:50:58 +0200 Subject: [PATCH 56/71] MDEV-39792 InnoDB: ALTER TABLE FORCE triggers assertion "s" in buf_page_get_gen() When rebuilding a table from ROW_FORMAT=COMPACT or DYNAMIC into ROW_FORMAT=REDUNDANT, row_merge_buf_add() fetches the full value of an externally stored (off-page) CHAR column in a multi-byte character set and pads it to REDUNDANT's fixed local width via row_merge_buf_redundant_convert(). That helper already dereferences the BLOB and calls dfield_set_data(), which clears the field's "externally stored" flag, since the value is now held in full locally. The "flag externally stored fields" step further down in row_merge_buf_add() did not know this had happened. It still consulted the row_ext_t cache built from the original (pre-conversion) record and, for a column that is not part of the clustered index's unique key, called dfield_set_ext() again on the very field that had just been converted, without restoring its data pointer to a valid 20-byte external reference. row_merge_copy_blobs() would then read the tail of the padded, space-filled buffer as if it were a BTR_EXTERN_FIELD_REF, deriving a garbage tablespace id and crashing buf_page_get_gen()'s fil_space_get() assertion when the alter tried to build the new clustered index. Skip the re-flagging step for a field whose "externally stored" flag is no longer set. row_build() flags every off-page column, and the row_ext_t cache only holds a subset of those columns, so a field that is not flagged is either a converted one (already fully local) or one that the cache does not hold. With the field no longer re-flagged, the rebuild completes, and the rebuilt table passes CHECK TABLE with the full column value. The MDEV-31025 case in innodb.default_row_format_alter failed on innodb_page_size=4k and 8k: its ROW_FORMAT=REDUNDANT table has eight utf32 CHAR(255) columns, which CREATE TABLE rejects with ER_TOO_BIG_ROWSIZE on those page sizes. Derive the number of columns from the page size, so that the record still exceeds the maximum local record size and the fixed-length column c is stored externally. The whole test now passes on every page size. --- .../innodb/r/default_row_format_alter.result | 20 ++++++-- .../innodb/t/default_row_format_alter.test | 48 +++++++++++++++++-- storage/innobase/row/row0merge.cc | 6 ++- 3 files changed, 65 insertions(+), 9 deletions(-) diff --git a/mysql-test/suite/innodb/r/default_row_format_alter.result b/mysql-test/suite/innodb/r/default_row_format_alter.result index 33936b59003..a3acf5f7d6a 100644 --- a/mysql-test/suite/innodb/r/default_row_format_alter.result +++ b/mysql-test/suite/innodb/r/default_row_format_alter.result @@ -135,10 +135,6 @@ DROP TABLE t1; # set @old_sql_mode = @@sql_mode; SET @@sql_mode=''; -CREATE TABLE t1(pk INT,c CHAR(255),c2 CHAR(255),c3 CHAR(255), -c4 char(255), c5 char(255), c6 char(255), -c7 char(255), c8 char(255), primary key(pk) -)Engine=InnoDB character set utf32 ROW_FORMAT=REDUNDANT; INSERT INTO t1(pk, c) VALUES (1, repeat('a', 255)); ALTER TABLE t1 FORCE; CHECK TABLE t1; @@ -150,4 +146,20 @@ LENGTH(c) DROP TABLE t1; set @@sql_mode = @old_sql_mode; # End of 10.4 tests +# +# MDEV-39792 InnoDB: ALTER TABLE FORCE triggers assertion "s" +# in buf_page_get_gen() when converting an externally stored +# CHAR column to ROW_FORMAT=REDUNDANT +# +SET @a=REPEAT(_utf8mb4 0xF09F9880,254); +SET GLOBAL innodb_default_row_format = @row_format; +ALTER TABLE t1 ROW_FORMAT=REDUNDANT; +CHECK TABLE t1; +Table Op Msg_type Msg_text +test.t1 check status OK +SELECT c1=@a FROM t1; +c1=@a +1 +DROP TABLE t1; +# End of 11.4 tests SET GLOBAL innodb_default_row_format = @row_format; diff --git a/mysql-test/suite/innodb/t/default_row_format_alter.test b/mysql-test/suite/innodb/t/default_row_format_alter.test index 5f2170454f3..1966bf01223 100644 --- a/mysql-test/suite/innodb/t/default_row_format_alter.test +++ b/mysql-test/suite/innodb/t/default_row_format_alter.test @@ -156,10 +156,20 @@ DROP TABLE t1; --echo # set @old_sql_mode = @@sql_mode; SET @@sql_mode=''; -CREATE TABLE t1(pk INT,c CHAR(255),c2 CHAR(255),c3 CHAR(255), - c4 char(255), c5 char(255), c6 char(255), - c7 char(255), c8 char(255), primary key(pk) - )Engine=InnoDB character set utf32 ROW_FORMAT=REDUNDANT; +# The record must exceed the maximum local record size of the page, +# so that the fixed-length column c is stored externally. +let $n_cols = `SELECT LEAST(8, @@innodb_page_size DIV 2048)`; +let $cols = c CHAR(255); +let $i = 2; +while ($i <= $n_cols) +{ + let $cols = $cols, c$i CHAR(255); + inc $i; +} +--disable_query_log +eval CREATE TABLE t1(pk INT, $cols, PRIMARY KEY(pk)) +ENGINE=InnoDB CHARACTER SET utf32 ROW_FORMAT=REDUNDANT; +--enable_query_log INSERT INTO t1(pk, c) VALUES (1, repeat('a', 255)); ALTER TABLE t1 FORCE; CHECK TABLE t1; @@ -169,4 +179,34 @@ set @@sql_mode = @old_sql_mode; --echo # End of 10.4 tests +--echo # +--echo # MDEV-39792 InnoDB: ALTER TABLE FORCE triggers assertion "s" +--echo # in buf_page_get_gen() when converting an externally stored +--echo # CHAR column to ROW_FORMAT=REDUNDANT +--echo # +# The record must exceed the maximum local record size of the page, so +# that c1 is stored externally, and still fit in ROW_FORMAT=REDUNDANT. +let $n_cols = `SELECT IF(@@innodb_page_size <= 16384, @@innodb_page_size DIV 2048, 18)`; +let $cols = c1 CHAR(255); +let $vals = @a; +let $i = 2; +while ($i <= $n_cols) +{ + let $cols = $cols, c$i CHAR(255); + let $vals = $vals, @a; + inc $i; +} +SET @a=REPEAT(_utf8mb4 0xF09F9880,254); +SET GLOBAL innodb_default_row_format = @row_format; +--disable_query_log +eval CREATE TABLE t1 ($cols, KEY(c1(1))) ENGINE=InnoDB CHARSET=utf8mb4; +eval INSERT INTO t1 VALUES ($vals); +--enable_query_log +ALTER TABLE t1 ROW_FORMAT=REDUNDANT; +CHECK TABLE t1; +SELECT c1=@a FROM t1; +DROP TABLE t1; + +--echo # End of 11.4 tests + SET GLOBAL innodb_default_row_format = @row_format; diff --git a/storage/innobase/row/row0merge.cc b/storage/innobase/row/row0merge.cc index e2bec8e5d36..b5d43d5c3c5 100644 --- a/storage/innobase/row/row0merge.cc +++ b/storage/innobase/row/row0merge.cc @@ -774,7 +774,11 @@ error: if (dfield_is_null(field)) { ut_ad(!(col->prtype & DATA_NOT_NULL)); continue; - } else if (!ext) { + } else if (!ext || !dfield_is_ext(field)) { + /* A field that + row_merge_buf_redundant_convert() fetched + in full is no longer externally stored, + and must not be re-flagged as such. */ } else if (dict_index_is_clust(index)) { /* Flag externally stored fields. */ const byte* buf = row_ext_lookup(ext, col->ind, From 6d35ae303ae4082d2f3507d6d1280679b99ec128 Mon Sep 17 00:00:00 2001 From: Mohammad Tafzeel Shams Date: Tue, 22 Sep 2026 15:57:13 +0530 Subject: [PATCH 57/71] MDEV-41242 : Fix resource leaks on InnoDB/mariabackup error paths found by Infer Several error-handling paths returned without releasing a resource already acquired earlier in the function, or checked the wrong handle entirely, risking use of an unopened handle. Changes: - SysTablespace::read_lsn_and_check_flags(): close the datafile handle on header-validation failure. - xb_process_datadir(): check the freshly opened `dir` handle instead of the stale `dbdir`, fixing a handle leak and a possible use of an unopened directory handle. - wsrep.cc / xb_load_list_file(): close file handles before die(), and null-check fopen() results in wsrep.cc. - datadir_iter_new(): free datadir_path and destroy the mutex on the os_file_opendir() failure path. --- extra/mariabackup/backup_copy.cc | 4 ++++ extra/mariabackup/wsrep.cc | 13 +++++++++++++ extra/mariabackup/xtrabackup.cc | 3 ++- storage/innobase/buf/buf0dump.cc | 1 + storage/innobase/fil/fil0fil.cc | 2 ++ storage/innobase/fsp/fsp0sysspace.cc | 1 + storage/innobase/fts/fts0blex.cc | 4 +++- storage/innobase/log/log0recv.cc | 4 ++++ 8 files changed, 30 insertions(+), 2 deletions(-) diff --git a/extra/mariabackup/backup_copy.cc b/extra/mariabackup/backup_copy.cc index 4d999d6f33f..14aaa6edab5 100644 --- a/extra/mariabackup/backup_copy.cc +++ b/extra/mariabackup/backup_copy.cc @@ -241,6 +241,10 @@ datadir_iter_new(const char *path, bool skip_first_level = true) return(it); error: + if (it) { + pthread_mutex_destroy(&it->mutex); + free(it->datadir_path); + } free(it); return(NULL); diff --git a/extra/mariabackup/wsrep.cc b/extra/mariabackup/wsrep.cc index 0a2cb8741d1..2520912d5a9 100644 --- a/extra/mariabackup/wsrep.cc +++ b/extra/mariabackup/wsrep.cc @@ -89,12 +89,23 @@ xb_write_galera_info(bool incremental_prepare) */ if (my_stat(MB_GALERA_INFO_FILENAME, &statinfo, MYF(0))) { FILE* fp_in = fopen(MB_GALERA_INFO_FILENAME, "r"); + if (!fp_in) { + die("could not open " MB_GALERA_INFO_FILENAME + ", errno = %d\n", errno); + } FILE* fp_out = fopen(MB_GALERA_INFO_FILENAME_SST, "w"); + if (!fp_out) { + fclose(fp_in); + die("could not open " MB_GALERA_INFO_FILENAME_SST + ", errno = %d\n", errno); + } char buf[BUFSIZ] = {'\0'}; size_t size; while ((size = fread(buf, 1, BUFSIZ, fp_in))) { if (fwrite(buf, 1, size, fp_out) != strlen(buf)) { + fclose(fp_out); + fclose(fp_in); die( "could not write to " MB_GALERA_INFO_FILENAME_SST @@ -103,6 +114,8 @@ xb_write_galera_info(bool incremental_prepare) } } if (!feof(fp_in)) { + fclose(fp_out); + fclose(fp_in); die( MB_GALERA_INFO_FILENAME_SST " not fully copied\n" diff --git a/extra/mariabackup/xtrabackup.cc b/extra/mariabackup/xtrabackup.cc index d8c959185e2..59d35d6c185 100644 --- a/extra/mariabackup/xtrabackup.cc +++ b/extra/mariabackup/xtrabackup.cc @@ -4845,6 +4845,7 @@ xb_load_list_file( if (p) { *p = '\0'; } else { + fclose(fp); die("`%s...` name is too long", name_buf); } @@ -6732,7 +6733,7 @@ next_file_item_1: /* single table tablespaces */ dir = os_file_opendir(path); - if (UNIV_UNLIKELY(dbdir == IF_WIN(INVALID_HANDLE_VALUE, nullptr))) { + if (UNIV_UNLIKELY(dir == IF_WIN(INVALID_HANDLE_VALUE, nullptr))) { msg("Can't open dir %s", path); return TRUE; } diff --git a/storage/innobase/buf/buf0dump.cc b/storage/innobase/buf/buf0dump.cc index ace236f3abe..f2507dd152e 100644 --- a/storage/innobase/buf/buf0dump.cc +++ b/storage/innobase/buf/buf0dump.cc @@ -331,6 +331,7 @@ buf_dump( bpage = UT_LIST_GET_NEXT(LRU, bpage)) { const auto status = bpage->state(); if (status < buf_page_t::UNFIXED) { + // @infer-ignore PULSE_RESOURCE_LEAK ut_a(status >= buf_page_t::FREED); continue; } diff --git a/storage/innobase/fil/fil0fil.cc b/storage/innobase/fil/fil0fil.cc index 3cff0de779d..ec49a2b330a 100644 --- a/storage/innobase/fil/fil0fil.cc +++ b/storage/innobase/fil/fil0fil.cc @@ -1254,6 +1254,8 @@ void fil_system_t::create(ulint hash_size) continue; } l = read(f, b, sizeof b); + // f was reassigned by open() after first close() + // @infer-ignore USE_AFTER_FREE ::close(f); if (l <= 0 || b[l - 1] != '\n') { continue; diff --git a/storage/innobase/fsp/fsp0sysspace.cc b/storage/innobase/fsp/fsp0sysspace.cc index 976021e94f7..a665204bf56 100644 --- a/storage/innobase/fsp/fsp0sysspace.cc +++ b/storage/innobase/fsp/fsp0sysspace.cc @@ -585,6 +585,7 @@ inline dberr_t SysTablespace::read_lsn_and_check_flags() m_ignore_read_only && srv_read_only_mode); if (err != DB_SUCCESS) { + it->close(); return(err); } diff --git a/storage/innobase/fts/fts0blex.cc b/storage/innobase/fts/fts0blex.cc index 8cd46c55362..23819971f9f 100644 --- a/storage/innobase/fts/fts0blex.cc +++ b/storage/innobase/fts/fts0blex.cc @@ -1526,8 +1526,10 @@ static void yy_load_buffer_state (yyscan_t yyscanner) * we need to put in 2 end-of-buffer characters. */ b->yy_ch_buf = (char *) yyalloc( (yy_size_t) (b->yy_buf_size + 2) , yyscanner ); - if ( ! b->yy_ch_buf ) + if ( ! b->yy_ch_buf ) { + yyfree( (void *) b , yyscanner ); YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); + } b->yy_is_our_buffer = 1; diff --git a/storage/innobase/log/log0recv.cc b/storage/innobase/log/log0recv.cc index e309b627dbf..d21daf48ef3 100644 --- a/storage/innobase/log/log0recv.cc +++ b/storage/innobase/log/log0recv.cc @@ -1737,6 +1737,8 @@ dberr_t recv_sys_t::find_checkpoint() os_file_close(file); return DB_ERROR; } + // file (log_sys.log) closed later by log_t::close(). + // @infer-ignore PULSE_RESOURCE_LEAK else if (!log_sys.attach(file, size)) goto err_exit; else @@ -1759,6 +1761,8 @@ dberr_t recv_sys_t::find_checkpoint() int(path.size()), path.data(), sz, size); wrong_size= true; } + // file (in recv_sys.files) closed later by recv_sys_t::close_files(). + // @infer-ignore PULSE_RESOURCE_LEAK. recv_sys.files.emplace_back(file); } From 6f8a8ed49ad3ae5a375943ca29bc9b166a3bf8fa Mon Sep 17 00:00:00 2001 From: Dave Gosselin Date: Mon, 28 Sep 2026 11:38:09 -0400 Subject: [PATCH 58/71] MDEV-31180: MyISAMMRG Crash on UPDATE of an updateable VIEW Attach the children of a MERGE table once per statement, and keep the value of pos_in_table_list for a MERGE table on subsequent executions of a prepared statement. --- mysql-test/suite/merge/merge.result | 37 +++++++++++++++++++++++++++++ mysql-test/suite/merge/merge.test | 31 ++++++++++++++++++++++++ sql/sql_base.cc | 33 ++++++++++++++++++------- 3 files changed, 92 insertions(+), 9 deletions(-) diff --git a/mysql-test/suite/merge/merge.result b/mysql-test/suite/merge/merge.result index bc513f26534..90a14e5795e 100644 --- a/mysql-test/suite/merge/merge.result +++ b/mysql-test/suite/merge/merge.result @@ -4001,6 +4001,43 @@ SET SQL_SAFE_UPDATES=1; UPDATE t1 STRAIGHT_JOIN t2 SET a = 89 WHERE 9 IN (SELECT c FROM t3 WHERE c IN (SELECT MAX(d) FROM t4)); ERROR HY000: You are using safe update mode and you tried to update a table without a WHERE that uses a KEY column DROP TABLE t1, t2, t3, t4; +SET SQL_SAFE_UPDATES=DEFAULT; # # End of 10.11 tests # +# +# MDEV-31180: MyISAMMRG Crash on UPDATE of an updateable VIEW +# +CREATE TABLE t (f INT) ENGINE=MyISAM; +INSERT INTO t VALUES (1),(2); +CREATE TABLE tm (f INT) ENGINE=MERGE UNION (t) INSERT_METHOD=FIRST; +CREATE VIEW v AS SELECT f FROM tm; +UPDATE v SET f = 3; +SELECT * FROM v; +f +3 +3 +SELECT * FROM tm; +f +3 +3 +DROP VIEW v; +DROP TABLE tm, t; +CREATE TABLE t (f INT) ENGINE=MyISAM; +INSERT INTO t VALUES (1),(2); +CREATE TABLE tm (f INT) ENGINE=MERGE UNION (t) INSERT_METHOD=FIRST; +CREATE VIEW v AS SELECT f FROM tm; +PREPARE s FROM 'UPDATE v SET f = 3'; +EXECUTE s; +EXECUTE s; +SELECT * FROM v; +f +3 +3 +SELECT * FROM tm; +f +3 +3 +DROP VIEW v; +DROP TABLE tm, t; +# End of 11.4 tests diff --git a/mysql-test/suite/merge/merge.test b/mysql-test/suite/merge/merge.test index 9d7233c15b0..7c1f329e20e 100644 --- a/mysql-test/suite/merge/merge.test +++ b/mysql-test/suite/merge/merge.test @@ -2945,7 +2945,38 @@ SET SQL_SAFE_UPDATES=1; UPDATE t1 STRAIGHT_JOIN t2 SET a = 89 WHERE 9 IN (SELECT c FROM t3 WHERE c IN (SELECT MAX(d) FROM t4)); DROP TABLE t1, t2, t3, t4; +SET SQL_SAFE_UPDATES=DEFAULT; --echo # --echo # End of 10.11 tests --echo # + +--echo # +--echo # MDEV-31180: MyISAMMRG Crash on UPDATE of an updateable VIEW +--echo # +# Simple UPDATE through a VIEW with the MERGE engine. +CREATE TABLE t (f INT) ENGINE=MyISAM; +INSERT INTO t VALUES (1),(2); +CREATE TABLE tm (f INT) ENGINE=MERGE UNION (t) INSERT_METHOD=FIRST; +CREATE VIEW v AS SELECT f FROM tm; +UPDATE v SET f = 3; # crashed here +SELECT * FROM v; +SELECT * FROM tm; +DROP VIEW v; +DROP TABLE tm, t; + +# Prepared statement UPDATE through a VIEW with the MERGE engine, multiple +# executions. +CREATE TABLE t (f INT) ENGINE=MyISAM; +INSERT INTO t VALUES (1),(2); +CREATE TABLE tm (f INT) ENGINE=MERGE UNION (t) INSERT_METHOD=FIRST; +CREATE VIEW v AS SELECT f FROM tm; +PREPARE s FROM 'UPDATE v SET f = 3'; +EXECUTE s; # no problem +EXECUTE s; # Asserted on pos_in_table_list->next_global == children_l, ha_myisammrg.cc:864 +SELECT * FROM v; +SELECT * FROM tm; +DROP VIEW v; +DROP TABLE tm, t; + +--echo # End of 11.4 tests diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 6df8853379c..dee70e54d03 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -4897,10 +4897,10 @@ restart: } /* - After successful open of all tables, including MERGE parents and - children, attach the children to their parents. At end of statement, - the children are detached. Attaching and detaching are always done, - even under LOCK TABLES. + After successfully opening the tables, including MERGE parents and + children, attach the children to their parents (unless they are already + attached). At the end of statement, the children are detached. Attaching + and detaching are always attempted, even under LOCK TABLES. We also convert all TL_WRITE_DEFAULT and TL_READ_DEFAULT locks to appropriate "real" lock types to be used for locking and to be passed @@ -4916,7 +4916,8 @@ restart: if (!tbl) continue; - if (tbl->file->ha_table_flags() & HA_CAN_MULTISTEP_MERGE) + if (tbl->file->ha_table_flags() & HA_CAN_MULTISTEP_MERGE && + !tbl->file->extra(HA_EXTRA_IS_ATTACHED_CHILDREN)) { /* MERGE tables need to access parent and child TABLE_LISTs. */ if (tbl->file->extra(HA_EXTRA_ATTACH_CHILDREN)) @@ -8545,10 +8546,24 @@ bool setup_tables(THD *thd, Name_resolution_context *context, } else { - table_list->table->tablenr= table_list->tablenr_exec; - table_list->table->map= table_list->map_exec; - table_list->table->maybe_null= table_list->maybe_null_exec; - table_list->table->pos_in_table_list= table_list; + TABLE *tbl= table_list->table; + tbl->tablenr= table_list->tablenr_exec; + tbl->map= table_list->map_exec; + tbl->maybe_null= table_list->maybe_null_exec; + /* + If this is a MyISAM MERGE table then don't overwrite + pos_in_table_list on subsequent execution of a prepared statement + where that statement references a MyISAM MERGE table. On the first + execution, the pos_in_table_list is set to a value from 'leaves'. + But on subsequent executions, that value was blindly overwritten from + 'leaf_tables_exec'. Most of the time that's safe, but not for the + MyISAM MERGE. The MyISAM MERGE engine relies on this value being + stable between executions so that it can find the children spliced in + behind the parent (the merged children). + */ + if (!(tbl->file->ha_table_flags() & HA_CAN_MULTISTEP_MERGE) || + !tbl->pos_in_table_list) + tbl->pos_in_table_list= table_list; if (table_list->process_index_hints(table_list->table)) DBUG_RETURN(1); } From 64929e06781b69f79e243a57a0cee7b291f0753f Mon Sep 17 00:00:00 2001 From: Brad Smith Date: Thu, 1 Oct 2026 12:15:32 -0400 Subject: [PATCH 59/71] crc32c: check elf_aux_info() return value in ppc64 probe elf_aux_info(3) leaves the output buffer unmodified on failure, so ignoring the return value could test an uninitialized cpufeatures and wrongly enable the POWER8 vector-crypto path. Treat failure as "no features" so the probe falls back to the generic implementation. --- mysys/crc32/crc32c.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mysys/crc32/crc32c.cc b/mysys/crc32/crc32c.cc index 30788130d59..5ac97c55c54 100644 --- a/mysys/crc32/crc32c.cc +++ b/mysys/crc32/crc32c.cc @@ -466,7 +466,8 @@ static int arch_ppc_probe(void) { arch_ppc_crc32 = 0; # if defined(__powerpc64__) - elf_aux_info(AT_HWCAP2, &cpufeatures, sizeof(cpufeatures)); + if (elf_aux_info(AT_HWCAP2, &cpufeatures, sizeof(cpufeatures)) != 0) + cpufeatures = 0; if (cpufeatures & PPC_FEATURE2_HAS_VEC_CRYPTO) arch_ppc_crc32 = 1; # endif /* __powerpc64__ */ From ce029f6148fc05c477741aa4585edc8432feafc9 Mon Sep 17 00:00:00 2001 From: Dave Gosselin Date: Thu, 24 Sep 2026 11:47:49 -0400 Subject: [PATCH 60/71] MDEV-41303: rand() in a semi-join subquery is checked on outer rows Do not merge a subquery into its parent as a semi-join when it has the UNCACHEABLE_RAND flag, which RAND() and ROWNUM set. Derived tables already follow this rule. ROWNUM sets the same flag, so this patch replaces the check for ROWNUM with a check for UNCACHEABLE_RAND. Previously, converting an IN subquery to a semi-join moved its WHERE into the parent WHERE. A condition there such as rand(1) < 0.09 doesn't rely on any columns, so it is attached to the last table of the join order that is outside any materialized semi-join. With SJ-Materialization it was checked once for each outer row instead of once for each row of the subquery, and the query returned a wrong count. Co-Authored-By: Claude Opus 5.5 (1M context) --- mysql-test/main/subselect_mat.result | 40 +++++++++++++++++++++++++ mysql-test/main/subselect_sj_mat.result | 40 +++++++++++++++++++++++++ mysql-test/main/subselect_sj_mat.test | 33 ++++++++++++++++++++ sql/opt_subselect.cc | 5 ++-- 4 files changed, 116 insertions(+), 2 deletions(-) diff --git a/mysql-test/main/subselect_mat.result b/mysql-test/main/subselect_mat.result index 7be03643d90..e8dbb85823c 100644 --- a/mysql-test/main/subselect_mat.result +++ b/mysql-test/main/subselect_mat.result @@ -2752,6 +2752,46 @@ c 0 DROP TABLE t1; # end of 10.11 tests +# +# MDEV-41303 rand() in a semi-join subquery is checked on outer rows +# +create table t1 (a int); +insert into t1 values (1),(2),(3),(100); +create table t2 (c int, d int); +insert into t2 select 1, 1 from seq_1_to_80; +set @save_optimizer_switch= @@optimizer_switch; +set optimizer_switch='firstmatch=off'; +# The subquery must check rand(1) < 0.09 on the 80 rows of t2. +# Must produce count(*)=1, not 0. +select count(*) from t1 +where t1.a in (select c from t2 where rand(1) < 0.09); +count(*) +1 +# Warning must have "(...) JOIN..." and not +# "`test`.`t1` semi join (`test`.`t2`)" +explain extended +select count(*) from t1 +where t1.a in (select c from t2 where rand(1) < 0.09); +id select_type table type possible_keys key key_len ref rows filtered Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 4 100.00 Using where +1 PRIMARY eq_ref distinct_key distinct_key 4 test.t1.a 1 100.00 +2 MATERIALIZED t2 ALL NULL NULL NULL NULL 80 100.00 Using where +Warnings: +Note 1003 /* select#1 */ select count(0) AS `count(*)` from (/* select#2 */ select `test`.`t2`.`c` from `test`.`t2` where rand(1) < 0.09) join `test`.`t1` where ``.`c` = `test`.`t1`.`a` +# rand() in the outer WHERE does not prevent the conversion to a +# semi-join. +explain extended +select count(*) from t1 +where rand(1) < 2 and t1.a in (select c from t2); +id select_type table type possible_keys key key_len ref rows filtered Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 4 100.00 Using where +1 PRIMARY eq_ref distinct_key distinct_key 4 func 1 100.00 +2 MATERIALIZED t2 ALL NULL NULL NULL NULL 80 100.00 +Warnings: +Note 1003 select count(0) AS `count(*)` from `test`.`t1` semi join (`test`.`t2`) where rand(1) < 2 +set optimizer_switch=@save_optimizer_switch; +drop table t1, t2; +# end of 11.4 tests set @subselect_mat_test_optimizer_switch_value=null; set @@optimizer_switch='materialization=on,in_to_exists=off,semijoin=off'; set optimizer_switch='mrr=on,mrr_sort_keys=on,index_condition_pushdown=on'; diff --git a/mysql-test/main/subselect_sj_mat.result b/mysql-test/main/subselect_sj_mat.result index f9b79f1b29e..b3682df6494 100644 --- a/mysql-test/main/subselect_sj_mat.result +++ b/mysql-test/main/subselect_sj_mat.result @@ -2778,3 +2778,43 @@ c 0 DROP TABLE t1; # end of 10.11 tests +# +# MDEV-41303 rand() in a semi-join subquery is checked on outer rows +# +create table t1 (a int); +insert into t1 values (1),(2),(3),(100); +create table t2 (c int, d int); +insert into t2 select 1, 1 from seq_1_to_80; +set @save_optimizer_switch= @@optimizer_switch; +set optimizer_switch='firstmatch=off'; +# The subquery must check rand(1) < 0.09 on the 80 rows of t2. +# Must produce count(*)=1, not 0. +select count(*) from t1 +where t1.a in (select c from t2 where rand(1) < 0.09); +count(*) +1 +# Warning must have "(...) JOIN..." and not +# "`test`.`t1` semi join (`test`.`t2`)" +explain extended +select count(*) from t1 +where t1.a in (select c from t2 where rand(1) < 0.09); +id select_type table type possible_keys key key_len ref rows filtered Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 4 100.00 Using where +1 PRIMARY eq_ref distinct_key distinct_key 4 test.t1.a 1 100.00 +2 MATERIALIZED t2 ALL NULL NULL NULL NULL 80 100.00 Using where +Warnings: +Note 1003 /* select#1 */ select count(0) AS `count(*)` from (/* select#2 */ select `test`.`t2`.`c` from `test`.`t2` where rand(1) < 0.09) join `test`.`t1` where ``.`c` = `test`.`t1`.`a` +# rand() in the outer WHERE does not prevent the conversion to a +# semi-join. +explain extended +select count(*) from t1 +where rand(1) < 2 and t1.a in (select c from t2); +id select_type table type possible_keys key key_len ref rows filtered Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 4 100.00 Using where +1 PRIMARY eq_ref distinct_key distinct_key 4 func 1 100.00 +2 MATERIALIZED t2 ALL NULL NULL NULL NULL 80 100.00 +Warnings: +Note 1003 select count(0) AS `count(*)` from `test`.`t1` semi join (`test`.`t2`) where rand(1) < 2 +set optimizer_switch=@save_optimizer_switch; +drop table t1, t2; +# end of 11.4 tests diff --git a/mysql-test/main/subselect_sj_mat.test b/mysql-test/main/subselect_sj_mat.test index 5662408a093..5fe54e8799d 100644 --- a/mysql-test/main/subselect_sj_mat.test +++ b/mysql-test/main/subselect_sj_mat.test @@ -2480,3 +2480,36 @@ SELECT * FROM t1 WHERE (0,0) IN (SELECT MAX(c),MIN(c) FROM t1); DROP TABLE t1; --echo # end of 10.11 tests + +--echo # +--echo # MDEV-41303 rand() in a semi-join subquery is checked on outer rows +--echo # + +create table t1 (a int); +insert into t1 values (1),(2),(3),(100); +create table t2 (c int, d int); +insert into t2 select 1, 1 from seq_1_to_80; + +set @save_optimizer_switch= @@optimizer_switch; +set optimizer_switch='firstmatch=off'; + +--echo # The subquery must check rand(1) < 0.09 on the 80 rows of t2. +--echo # Must produce count(*)=1, not 0. +select count(*) from t1 +where t1.a in (select c from t2 where rand(1) < 0.09); +--echo # Warning must have "(...) JOIN..." and not +--echo # "`test`.`t1` semi join (`test`.`t2`)" +explain extended +select count(*) from t1 +where t1.a in (select c from t2 where rand(1) < 0.09); + +--echo # rand() in the outer WHERE does not prevent the conversion to a +--echo # semi-join. +explain extended +select count(*) from t1 +where rand(1) < 2 and t1.a in (select c from t2); + +set optimizer_switch=@save_optimizer_switch; +drop table t1, t2; + +--echo # end of 11.4 tests diff --git a/sql/opt_subselect.cc b/sql/opt_subselect.cc index 5fd13776627..02b3682abf0 100644 --- a/sql/opt_subselect.cc +++ b/sql/opt_subselect.cc @@ -738,7 +738,8 @@ int check_and_do_in_subquery_rewrites(JOIN *join) 11. It is first optimisation (the subquery could be moved from ON clause during first optimisation and then be considered for SJ on the second when it is too late) - 13. Subquery does not have ROWNUM + 13. Subquery does not set UNCACHEABLE_RAND. RAND() and ROWNUM + set it. There are also other requirements which cannot be checked at this phase, yet. They are checked later in convert_join_subqueries_to_semijoins(), @@ -758,7 +759,7 @@ int check_and_do_in_subquery_rewrites(JOIN *join) select_lex->outer_select()->join->select_options) // 10 & SELECT_STRAIGHT_JOIN) && // 10 select_lex->first_cond_optimization && // 11 - !select_lex->with_rownum) // 13 + !(select_lex->uncacheable & UNCACHEABLE_RAND)) // 13 { DBUG_PRINT("info", ("Subquery is semi-join conversion candidate")); From ecda07c6564488ba0c807dcbdcfa3bea4b486ed9 Mon Sep 17 00:00:00 2001 From: Dave Gosselin Date: Fri, 2 Oct 2026 11:04:32 -0400 Subject: [PATCH 61/71] MDEV-41211 Remove unused index_read_idx() from both Federated engines ha_federated::index_read_idx() and ha_federatedx::index_read_idx() have no callers and do not override a handler method. Remove them, and remove the sentence in the comment on each index_read() that says index_read() calls it. Comments that describe index_read_idx() now name index_read() or index_read_idx_map(), whichever does that read. Co-Authored-By: Claude Opus 5.5 (1M context) --- storage/federated/ha_federated.cc | 44 ++++------------------------- storage/federated/ha_federated.h | 2 -- storage/federatedx/ha_federatedx.cc | 44 ++++------------------------- storage/federatedx/ha_federatedx.h | 2 -- 4 files changed, 12 insertions(+), 80 deletions(-) diff --git a/storage/federated/ha_federated.cc b/storage/federated/ha_federated.cc index 2e42b2d7588..d4179c46b9c 100644 --- a/storage/federated/ha_federated.cc +++ b/storage/federated/ha_federated.cc @@ -164,7 +164,6 @@ ha_federated::index_init ha_federated::index_read - ha_federated::index_read_idx ha_federated::rnd_next ha_federated::convert_row_to_internal_format ha_federated::update_row @@ -1156,7 +1155,7 @@ read_range_first: start_key 3 end_key 3 numeric keys: id = 4 -index_read_idx: start_key 0 end_key NULL +index_read_idx_map: start_key 0 end_key NULL id > 4 records_in_range: start_key 3 end_key NULL @@ -2369,8 +2368,7 @@ int ha_federated::delete_row(const uchar *buf) /* Positions an index cursor to the index specified in the handle. Fetches the row if available. If the key value is null, begin at the first key of the - index. This method, which is called in the case of an SQL statement having - a WHERE clause on a non-primary key index, simply calls index_read_idx. + index. */ int ha_federated::index_read(uchar *buf, const uchar *key, @@ -2387,36 +2385,6 @@ int ha_federated::index_read(uchar *buf, const uchar *key, } -/* - Positions an index cursor to the index specified in key. Fetches the - row if any. This is only used to read whole keys. - - This method is called via index_read in the case of a WHERE clause using - a primary key index OR is called DIRECTLY when the WHERE clause - uses a PRIMARY KEY index. - - NOTES - This uses an internal result set that is deleted before function - returns. We need to be able to be calable from ha_rnd_pos() -*/ - -int ha_federated::index_read_idx(uchar *buf, uint index, const uchar *key, - uint key_len, enum ha_rkey_function find_flag) -{ - int retval; - MYSQL_RES *mysql_result; - DBUG_ENTER("ha_federated::index_read_idx"); - - if ((retval= index_read_idx_with_result_set(buf, index, key, - key_len, find_flag, - &mysql_result))) - DBUG_RETURN(retval); - mysql_free_result(mysql_result); - results.elements--; - DBUG_RETURN(0); -} - - /* Create result set for rows matching query and return first row @@ -2598,7 +2566,7 @@ int ha_federated::rnd_init(bool scan) using indexed columns. When the initial query contains a WHERE clause of the query using an - indexed column, it's index_read_idx that selects the exact record from + indexed column, it's index_read that selects the exact record from the foreign database. When there is NO index in the query, either due to not having a WHERE @@ -2607,14 +2575,14 @@ int ha_federated::rnd_init(bool scan) a 'select * from ...' on the foreign table. In other words, this 'scan' flag gives us the means to ensure that if - there is an index involved in the query, we want index_read_idx to + there is an index involved in the query, we want index_read to retrieve the exact record (scan flag is 0), and do not want rnd_init to do a 'full table scan' and wipe out that result set. Prior to using this flag, the problem was most apparent with updates. An initial query like 'UPDATE tablename SET anything = whatever WHERE - indexedcol = someval', index_read_idx would get called, using a query + indexedcol = someval', the index lookup would run, using a query constructed with a WHERE clause built from the values of index ('indexcol' in this case, having a value of 'someval'). mysql_store_result would then get called (this would be the result set we want to use). @@ -2622,7 +2590,7 @@ int ha_federated::rnd_init(bool scan) After this rnd_init (from sql_update.cc) would be called, it would then unecessarily call "select * from table" on the foreign table, then call mysql_store_result, which would wipe out the correct previous result set - from the previous call of index_read_idx's that had the result set + from the previous index lookup that had the result set containing the correct record, hence update the wrong row! */ diff --git a/storage/federated/ha_federated.h b/storage/federated/ha_federated.h index 524b033f613..efb0f5e0c5f 100644 --- a/storage/federated/ha_federated.h +++ b/storage/federated/ha_federated.h @@ -219,8 +219,6 @@ public: ha_rows estimate_rows_upper_bound() override; int index_read(uchar *buf, const uchar *key, uint key_len, enum ha_rkey_function find_flag) override; - int index_read_idx(uchar *buf, uint idx, const uchar *key, - uint key_len, enum ha_rkey_function find_flag); int index_next(uchar *buf) override; int index_end() override; int read_range_first(const key_range *start_key, diff --git a/storage/federatedx/ha_federatedx.cc b/storage/federatedx/ha_federatedx.cc index b84c33f4c35..3e645fd2bbc 100644 --- a/storage/federatedx/ha_federatedx.cc +++ b/storage/federatedx/ha_federatedx.cc @@ -176,7 +176,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ha_federatedx::index_init ha_federatedx::index_read - ha_federatedx::index_read_idx ha_federatedx::rnd_next ha_federatedx::convert_row_to_internal_format ha_federatedx::update_row @@ -1100,7 +1099,7 @@ read_range_first: start_key 3 end_key 3 numeric keys: id = 4 -index_read_idx: start_key 0 end_key NULL +index_read_idx_map: start_key 0 end_key NULL id > 4 records_in_range: start_key 3 end_key NULL @@ -2579,8 +2578,7 @@ int ha_federatedx::delete_row(const uchar *buf) /* Positions an index cursor to the index specified in the handle. Fetches the row if available. If the key value is null, begin at the first key of the - index. This method, which is called in the case of an SQL statement having - a WHERE clause on a non-primary key index, simply calls index_read_idx. + index. */ int ha_federatedx::index_read(uchar *buf, const uchar *key, @@ -2596,36 +2594,6 @@ int ha_federatedx::index_read(uchar *buf, const uchar *key, } -/* - Positions an index cursor to the index specified in key. Fetches the - row if any. This is only used to read whole keys. - - This method is called via index_read in the case of a WHERE clause using - a primary key index OR is called DIRECTLY when the WHERE clause - uses a PRIMARY KEY index. - - NOTES - This uses an internal result set that is deleted before function - returns. We need to be able to be callable from ha_rnd_pos() -*/ - -int ha_federatedx::index_read_idx(uchar *buf, uint index, const uchar *key, - uint key_len, enum ha_rkey_function find_flag) -{ - int retval; - FEDERATEDX_IO_RESULT *io_result= 0; - DBUG_ENTER("ha_federatedx::index_read_idx"); - - if ((retval= index_read_idx_with_result_set(buf, index, key, - key_len, find_flag, - &io_result))) - DBUG_RETURN(retval); - /* io is correct, as index_read_idx_with_result_set was ok */ - io->free_result(io_result); - DBUG_RETURN(retval); -} - - /* Create result set for rows matching query and return first row @@ -2811,7 +2779,7 @@ int ha_federatedx::rnd_init(bool scan) using indexed columns. When the initial query contains a WHERE clause of the query using an - indexed column, it's index_read_idx that selects the exact record from + indexed column, it's index_read that selects the exact record from the foreign database. When there is NO index in the query, either due to not having a WHERE @@ -2820,14 +2788,14 @@ int ha_federatedx::rnd_init(bool scan) a 'select * from ...' on the foreign table. In other words, this 'scan' flag gives us the means to ensure that if - there is an index involved in the query, we want index_read_idx to + there is an index involved in the query, we want index_read to retrieve the exact record (scan flag is 0), and do not want rnd_init to do a 'full table scan' and wipe out that result set. Prior to using this flag, the problem was most apparent with updates. An initial query like 'UPDATE tablename SET anything = whatever WHERE - indexedcol = someval', index_read_idx would get called, using a query + indexedcol = someval', the index lookup would run, using a query constructed with a WHERE clause built from the values of index ('indexcol' in this case, having a value of 'someval'). mysql_store_result would then get called (this would be the result set we want to use). @@ -2835,7 +2803,7 @@ int ha_federatedx::rnd_init(bool scan) After this rnd_init (from sql_update.cc) would be called, it would then unecessarily call "select * from table" on the foreign table, then call mysql_store_result, which would wipe out the correct previous result set - from the previous call of index_read_idx's that had the result set + from the previous index lookup that had the result set containing the correct record, hence update the wrong row! */ diff --git a/storage/federatedx/ha_federatedx.h b/storage/federatedx/ha_federatedx.h index fce761540b3..9a4b2a8b639 100644 --- a/storage/federatedx/ha_federatedx.h +++ b/storage/federatedx/ha_federatedx.h @@ -405,8 +405,6 @@ public: ha_rows estimate_rows_upper_bound() override; int index_read(uchar *buf, const uchar *key, uint key_len, enum ha_rkey_function find_flag) override; - int index_read_idx(uchar *buf, uint idx, const uchar *key, - uint key_len, enum ha_rkey_function find_flag); int index_next(uchar *buf) override; int index_end() override; int read_range_first(const key_range *start_key, From b51d2f12bc27a29897caf8cb982077683fe47fcd Mon Sep 17 00:00:00 2001 From: Dave Gosselin Date: Fri, 2 Oct 2026 09:44:41 -0400 Subject: [PATCH 62/71] MDEV-41211 Federated multi-table DELETE keeps a const table row A multi-table DELETE on a FEDERATED or FederatedX table went wrong when a primary key lookup made the target a const table. The optimizer reads a const table's row through index_read_idx_map(), whose default implementation ends the index scan and frees the result set. The server asks for the row's position later, during execution, so the saved position was empty. FederatedX skipped the row and FEDERATED crashed in rnd_pos(). Both engines now override index_read_idx_map() so that the lookup leaves its result set open, as index_read() does. position() then records a valid position, and the result set is freed at the end of the statement. FEDERATED's reset() now also clears stored_result, which otherwise pointed at a freed result set and was freed again when the table was closed. Tests include a multitable UPDATE with a const target, which crashed both engines before the fix. Co-Authored-By: Claude Opus 5.5 (1M context) --- mysql-test/suite/federated/federated.result | 53 +++++++++++++++++++++ mysql-test/suite/federated/federated.test | 41 ++++++++++++++++ storage/federated/ha_federated.cc | 41 ++++++++++++++-- storage/federated/ha_federated.h | 5 ++ storage/federatedx/ha_federatedx.cc | 38 +++++++++++++-- storage/federatedx/ha_federatedx.h | 5 ++ 6 files changed, 175 insertions(+), 8 deletions(-) diff --git a/mysql-test/suite/federated/federated.result b/mysql-test/suite/federated/federated.result index 7734546fbf4..53e2546d29e 100644 --- a/mysql-test/suite/federated/federated.result +++ b/mysql-test/suite/federated/federated.result @@ -18,6 +18,59 @@ drop table t1; select * from fed; Got one of the listed errors drop table fed; +# +# MDEV-41211 Federated multi_delete skips some records +# +connection slave; +CREATE TABLE federated.t1 (pk INT NOT NULL PRIMARY KEY, a INT); +INSERT INTO federated.t1 VALUES (1,1),(5,5),(9,9); +connection master; +CREATE TABLE federated.t1 (pk INT NOT NULL PRIMARY KEY, a INT) +ENGINE=FEDERATED +CONNECTION='mysql://root@127.0.0.1:SLAVE_PORT/federated/t1'; +EXPLAIN DELETE a FROM federated.t1 a, federated.t1 b WHERE a.pk = 5 AND b.pk > 0; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE a const PRIMARY PRIMARY 4 const 1 +1 SIMPLE b range PRIMARY PRIMARY 4 NULL 2 Using where +DELETE a FROM federated.t1 a, federated.t1 b WHERE a.pk = 5 AND b.pk > 0; +SELECT * FROM federated.t1 ORDER BY pk; +pk a +1 1 +9 9 +# Both tables are const +EXPLAIN DELETE a FROM federated.t1 a, federated.t1 b WHERE a.pk = 1 AND b.pk = 9; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE a const PRIMARY PRIMARY 4 const 1 +1 SIMPLE b const PRIMARY PRIMARY 4 const 1 +DELETE a FROM federated.t1 a, federated.t1 b WHERE a.pk = 1 AND b.pk = 9; +SELECT * FROM federated.t1 ORDER BY pk; +pk a +9 9 +# Multi-table UPDATE with a const target +INSERT INTO federated.t1 VALUES (1,1),(5,5); +EXPLAIN UPDATE federated.t1 a, federated.t1 b SET a.a = 0 +WHERE a.pk = 5 AND b.pk > 0; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE a const PRIMARY PRIMARY 4 const 1 +1 SIMPLE b range PRIMARY PRIMARY 4 NULL 2 Using where +UPDATE federated.t1 a, federated.t1 b SET a.a = 0 WHERE a.pk = 5 AND b.pk > 0; +SELECT * FROM federated.t1 ORDER BY pk; +pk a +1 1 +5 0 +9 9 +connection slave; +SELECT * FROM federated.t1 ORDER BY pk; +pk a +1 1 +5 0 +9 9 +connection master; +DROP TABLE federated.t1; +connection slave; +DROP TABLE federated.t1; +connection master; +# End of 11.4 tests connection master; DROP TABLE IF EXISTS federated.t1; DROP DATABASE IF EXISTS federated; diff --git a/mysql-test/suite/federated/federated.test b/mysql-test/suite/federated/federated.test index 0cb0551d23e..164d937e459 100644 --- a/mysql-test/suite/federated/federated.test +++ b/mysql-test/suite/federated/federated.test @@ -21,5 +21,46 @@ drop table t1; select * from fed; drop table fed; +--echo # +--echo # MDEV-41211 Federated multi_delete skips some records +--echo # + +connection slave; +CREATE TABLE federated.t1 (pk INT NOT NULL PRIMARY KEY, a INT); +INSERT INTO federated.t1 VALUES (1,1),(5,5),(9,9); + +connection master; +--replace_result $SLAVE_MYPORT SLAVE_PORT +eval CREATE TABLE federated.t1 (pk INT NOT NULL PRIMARY KEY, a INT) + ENGINE=FEDERATED + CONNECTION='mysql://root@127.0.0.1:$SLAVE_MYPORT/federated/t1'; + +EXPLAIN DELETE a FROM federated.t1 a, federated.t1 b WHERE a.pk = 5 AND b.pk > 0; +DELETE a FROM federated.t1 a, federated.t1 b WHERE a.pk = 5 AND b.pk > 0; +SELECT * FROM federated.t1 ORDER BY pk; + +--echo # Both tables are const +EXPLAIN DELETE a FROM federated.t1 a, federated.t1 b WHERE a.pk = 1 AND b.pk = 9; +DELETE a FROM federated.t1 a, federated.t1 b WHERE a.pk = 1 AND b.pk = 9; +SELECT * FROM federated.t1 ORDER BY pk; + +--echo # Multi-table UPDATE with a const target +INSERT INTO federated.t1 VALUES (1,1),(5,5); +EXPLAIN UPDATE federated.t1 a, federated.t1 b SET a.a = 0 + WHERE a.pk = 5 AND b.pk > 0; +UPDATE federated.t1 a, federated.t1 b SET a.a = 0 WHERE a.pk = 5 AND b.pk > 0; +SELECT * FROM federated.t1 ORDER BY pk; + +connection slave; +SELECT * FROM federated.t1 ORDER BY pk; + +connection master; +DROP TABLE federated.t1; +connection slave; +DROP TABLE federated.t1; +connection master; + +--echo # End of 11.4 tests + source include/federated_cleanup.inc; diff --git a/storage/federated/ha_federated.cc b/storage/federated/ha_federated.cc index d4179c46b9c..2abf991521d 100644 --- a/storage/federated/ha_federated.cc +++ b/storage/federated/ha_federated.cc @@ -2374,14 +2374,44 @@ int ha_federated::delete_row(const uchar *buf) int ha_federated::index_read(uchar *buf, const uchar *key, uint key_len, ha_rkey_function find_flag) { - int rc; DBUG_ENTER("ha_federated::index_read"); + DBUG_RETURN(index_read_keep_result(buf, active_index, key, key_len, + find_flag)); +} + + +/* + The default implementation ends the index scan after the read, which + frees the result set. A const table is read this way during + optimization, but position() is called for its row during execution, so + the result set must stay in stored_result. +*/ + +int ha_federated::index_read_idx_map(uchar *buf, uint index, + const uchar *key, + key_part_map keypart_map, + enum ha_rkey_function find_flag) +{ + uint key_len= calculate_key_len(table, index, key, keypart_map); + DBUG_ENTER("ha_federated::index_read_idx_map"); + DBUG_RETURN(index_read_keep_result(buf, index, key, key_len, find_flag)); +} + + +/* + Reads the first row matching key and keeps its result set in + stored_result, so that position() can record the row. +*/ + +int ha_federated::index_read_keep_result(uchar *buf, uint index, + const uchar *key, uint key_len, + ha_rkey_function find_flag) +{ + DBUG_ENTER("ha_federated::index_read_keep_result"); free_result(); - rc= index_read_idx_with_result_set(buf, active_index, key, - key_len, find_flag, - &stored_result); - DBUG_RETURN(rc); + DBUG_RETURN(index_read_idx_with_result_set(buf, index, key, key_len, + find_flag, &stored_result)); } @@ -2968,6 +2998,7 @@ int ha_federated::reset(void) mysql_free_result(result); } reset_dynamic(&results); + stored_result= 0; if (mysql) mysql->net.thd= NULL; diff --git a/storage/federated/ha_federated.h b/storage/federated/ha_federated.h index efb0f5e0c5f..847211890dd 100644 --- a/storage/federated/ha_federated.h +++ b/storage/federated/ha_federated.h @@ -117,6 +117,8 @@ private: uint key_len, ha_rkey_function find_flag, MYSQL_RES **result); + int index_read_keep_result(uchar *buf, uint index, const uchar *key, + uint key_len, ha_rkey_function find_flag); int real_query(const char *query, size_t length); int real_connect(); public: @@ -219,6 +221,9 @@ public: ha_rows estimate_rows_upper_bound() override; int index_read(uchar *buf, const uchar *key, uint key_len, enum ha_rkey_function find_flag) override; + int index_read_idx_map(uchar *buf, uint index, const uchar *key, + key_part_map keypart_map, + enum ha_rkey_function find_flag) override; int index_next(uchar *buf) override; int index_end() override; int read_range_first(const key_range *start_key, diff --git a/storage/federatedx/ha_federatedx.cc b/storage/federatedx/ha_federatedx.cc index 3e645fd2bbc..6f2cfade7e1 100644 --- a/storage/federatedx/ha_federatedx.cc +++ b/storage/federatedx/ha_federatedx.cc @@ -2585,12 +2585,44 @@ int ha_federatedx::index_read(uchar *buf, const uchar *key, uint key_len, ha_rkey_function find_flag) { DBUG_ENTER("ha_federatedx::index_read"); + DBUG_RETURN(index_read_keep_result(buf, active_index, key, key_len, + find_flag)); +} + + +/* + The default implementation ends the index scan after the read, which + frees the result set. A const table is read this way during + optimization, but position() is called for its row during execution, so + the result set must stay in stored_result. +*/ + +int ha_federatedx::index_read_idx_map(uchar *buf, uint index, + const uchar *key, + key_part_map keypart_map, + enum ha_rkey_function find_flag) +{ + uint key_len= calculate_key_len(table, index, key, keypart_map); + DBUG_ENTER("ha_federatedx::index_read_idx_map"); + DBUG_RETURN(index_read_keep_result(buf, index, key, key_len, find_flag)); +} + + +/* + Reads the first row matching key and keeps its result set in + stored_result, so that position() can record the row. +*/ + +int ha_federatedx::index_read_keep_result(uchar *buf, uint index, + const uchar *key, uint key_len, + ha_rkey_function find_flag) +{ + DBUG_ENTER("ha_federatedx::index_read_keep_result"); if (stored_result) (void) free_result(); - DBUG_RETURN(index_read_idx_with_result_set(buf, active_index, key, - key_len, find_flag, - &stored_result)); + DBUG_RETURN(index_read_idx_with_result_set(buf, index, key, key_len, + find_flag, &stored_result)); } diff --git a/storage/federatedx/ha_federatedx.h b/storage/federatedx/ha_federatedx.h index 9a4b2a8b639..d841b6d0466 100644 --- a/storage/federatedx/ha_federatedx.h +++ b/storage/federatedx/ha_federatedx.h @@ -311,6 +311,8 @@ private: uint key_len, ha_rkey_function find_flag, FEDERATEDX_IO_RESULT **result); + int index_read_keep_result(uchar *buf, uint index, const uchar *key, + uint key_len, ha_rkey_function find_flag); int real_query(const char *query, uint length); int real_connect(FEDERATEDX_SHARE *my_share, uint create_flag); public: @@ -405,6 +407,9 @@ public: ha_rows estimate_rows_upper_bound() override; int index_read(uchar *buf, const uchar *key, uint key_len, enum ha_rkey_function find_flag) override; + int index_read_idx_map(uchar *buf, uint index, const uchar *key, + key_part_map keypart_map, + enum ha_rkey_function find_flag) override; int index_next(uchar *buf) override; int index_end() override; int read_range_first(const key_range *start_key, From 19719d64f0252bd2e0950457c46fdea90b1cfcbc Mon Sep 17 00:00:00 2001 From: ParadoxV5 Date: Fri, 2 Oct 2026 17:11:21 -0600 Subject: [PATCH 63/71] MDEV-38849 slave_connections_needed_for_purge prevents independent machine from purging binary logs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `@@slave_connections_needed_for_purge`’s default of `1` ensures binary log availability on replication masters, but is not a sensible default suitable for all scenarios, especially for long-term slave servers and standalone (not in a replication setup) servers. The outcome was that standalone server users were confused why automatic binlog purging does not work. This commit changes this default to `0`, which is suitable for both standalone and (when backed by prompt failure recovery) replication setups. `0` also more closely matches the behaviour before MDEV-31404, which added this variable, out of the box. This commit also adds a one-time replication warning when registering a slave, but `@@slave_connections_needed_for_purge` is left unchanged. Rather than enforcing a defence with an unsensible default, this reminder will bring awareness of the risk of automatic binlog purging. This commit also cleans up Galera and MTR workarounds to the introduction of the `@@slave_connections_needed_for_purge=1` default. --- mysql-test/include/mtr_warnings.sql | 1 + mysql-test/main/mysqld--help.result | 3 +- mysql-test/suite/binlog/my.cnf | 4 -- .../binlog_encryption/binlog_index-master.opt | 1 - mysql-test/suite/galera/r/basic.result | 6 --- mysql-test/suite/galera/t/basic.test | 3 -- mysql-test/suite/rpl/my.cnf | 2 - .../r/warn_slaves_not_needed_for_purge.result | 25 +++++++++ .../t/warn_slaves_not_needed_for_purge.test | 52 +++++++++++++++++++ .../sys_vars/r/sysvars_server_embedded.result | 2 +- .../r/sysvars_server_notembedded.result | 2 +- sql/mysqld.cc | 15 ++---- sql/mysqld.h | 1 + sql/repl_failsafe.cc | 9 ++++ sql/sql_repl.h | 9 ++++ sql/sys_vars.cc | 22 +++++--- 16 files changed, 121 insertions(+), 36 deletions(-) delete mode 100644 mysql-test/suite/binlog/my.cnf delete mode 100644 mysql-test/suite/binlog_encryption/binlog_index-master.opt create mode 100644 mysql-test/suite/rpl/r/warn_slaves_not_needed_for_purge.result create mode 100644 mysql-test/suite/rpl/t/warn_slaves_not_needed_for_purge.test diff --git a/mysql-test/include/mtr_warnings.sql b/mysql-test/include/mtr_warnings.sql index 88b3eb69377..fce8eee6766 100644 --- a/mysql-test/include/mtr_warnings.sql +++ b/mysql-test/include/mtr_warnings.sql @@ -148,6 +148,7 @@ INSERT INTO global_suppressions VALUES ("Slave: .*Duplicate entry"), ("Statement may not be safe to log in statement format"), + ("slave_connections_needed_for_purge"), /* innodb foreign key tests that fail in ALTER or RENAME produce this */ ("InnoDB: Error: in ALTER TABLE `test`.`t[123]`"), diff --git a/mysql-test/main/mysqld--help.result b/mysql-test/main/mysqld--help.result index 6c13ce88bf4..4b8e70fa191 100644 --- a/mysql-test/main/mysqld--help.result +++ b/mysql-test/main/mysqld--help.result @@ -1321,7 +1321,6 @@ The following specify which files/extra groups are read (specified before remain Minimum number of connected slaves required for automatic binary log purge with max_binlog_total_size, binlog_expire_logs_seconds or binlog_expire_logs_days. - Default is 0 when Galera is enabled and 1 otherwise. --slave-ddl-exec-mode=name How replication events should be executed. Legal values are STRICT and IDEMPOTENT (default). In IDEMPOTENT mode, @@ -1955,7 +1954,7 @@ skip-networking FALSE skip-show-database FALSE skip-slave-start FALSE slave-compressed-protocol FALSE -slave-connections-needed-for-purge 1 +slave-connections-needed-for-purge 0 slave-ddl-exec-mode IDEMPOTENT slave-domain-parallel-threads 0 slave-exec-mode STRICT diff --git a/mysql-test/suite/binlog/my.cnf b/mysql-test/suite/binlog/my.cnf deleted file mode 100644 index a563433d165..00000000000 --- a/mysql-test/suite/binlog/my.cnf +++ /dev/null @@ -1,4 +0,0 @@ -!include include/default_my.cnf - -[mariadbd] -slave_connections_needed_for_purge=0 diff --git a/mysql-test/suite/binlog_encryption/binlog_index-master.opt b/mysql-test/suite/binlog_encryption/binlog_index-master.opt deleted file mode 100644 index f2673d08020..00000000000 --- a/mysql-test/suite/binlog_encryption/binlog_index-master.opt +++ /dev/null @@ -1 +0,0 @@ ---slave_connections_needed_for_purge=0 diff --git a/mysql-test/suite/galera/r/basic.result b/mysql-test/suite/galera/r/basic.result index 7b4cbd93c7b..10f180e7a94 100644 --- a/mysql-test/suite/galera/r/basic.result +++ b/mysql-test/suite/galera/r/basic.result @@ -1,11 +1,5 @@ connection node_2; connection node_1; -select @@slave_connections_needed_for_purge; -@@slave_connections_needed_for_purge -0 -select VARIABLE_NAME, GLOBAL_VALUE, GLOBAL_VALUE_ORIGIN from information_schema.system_variables where variable_name="slave_connections_needed_for_purge"; -VARIABLE_NAME GLOBAL_VALUE GLOBAL_VALUE_ORIGIN -SLAVE_CONNECTIONS_NEEDED_FOR_PURGE 0 AUTO USE test; CREATE TABLE t1(c1 INT PRIMARY KEY) ENGINE=INNODB; INSERT INTO t1 VALUES (1), (2), (3), (4), (5); diff --git a/mysql-test/suite/galera/t/basic.test b/mysql-test/suite/galera/t/basic.test index a70ee962bc0..8fc6eee3b3b 100644 --- a/mysql-test/suite/galera/t/basic.test +++ b/mysql-test/suite/galera/t/basic.test @@ -1,9 +1,6 @@ --source include/galera_cluster.inc --source include/have_innodb.inc -select @@slave_connections_needed_for_purge; -select VARIABLE_NAME, GLOBAL_VALUE, GLOBAL_VALUE_ORIGIN from information_schema.system_variables where variable_name="slave_connections_needed_for_purge"; - USE test; CREATE TABLE t1(c1 INT PRIMARY KEY) ENGINE=INNODB; INSERT INTO t1 VALUES (1), (2), (3), (4), (5); diff --git a/mysql-test/suite/rpl/my.cnf b/mysql-test/suite/rpl/my.cnf index fa0a2c268fb..d61e32ed51a 100644 --- a/mysql-test/suite/rpl/my.cnf +++ b/mysql-test/suite/rpl/my.cnf @@ -6,5 +6,3 @@ # E.g. after !include ../my.cnf, in your `test.cnf`, specify your configuration # in option group e.g [mysqld.x], so that number `x` corresponds to the number # in the rpl server topology. -[mariadbd] -slave_connections_needed_for_purge=0 diff --git a/mysql-test/suite/rpl/r/warn_slaves_not_needed_for_purge.result b/mysql-test/suite/rpl/r/warn_slaves_not_needed_for_purge.result new file mode 100644 index 00000000000..5383e2ac36c --- /dev/null +++ b/mysql-test/suite/rpl/r/warn_slaves_not_needed_for_purge.result @@ -0,0 +1,25 @@ +include/master-slave.inc +[connection master] +NOT FOUND /slave_connections_needed_for_purge/ in mysqld.1.err +connection slave; +START SLAVE IO_THREAD; +include/wait_for_slave_io_to_start.inc +FOUND 1 /slave_connections_needed_for_purge/ in mysqld.1.err +include/stop_slave_io.inc +START SLAVE IO_THREAD; +include/wait_for_slave_io_to_start.inc +include/stop_slave_io.inc +include/rpl_restart_server.inc [server_number=1 parameters: --slave-connections-needed-for-purge 0] +connection slave; +START SLAVE IO_THREAD; +include/wait_for_slave_io_to_start.inc +include/stop_slave_io.inc +include/rpl_restart_server.inc [server_number=1] +connection master; +SET @@GLOBAL.slave_connections_needed_for_purge= DEFAULT; +connection slave; +START SLAVE IO_THREAD; +include/wait_for_slave_io_to_start.inc +connection master; +FOUND 1 /slave_connections_needed_for_purge/ in mysqld.1.err +include/rpl_end.inc diff --git a/mysql-test/suite/rpl/t/warn_slaves_not_needed_for_purge.test b/mysql-test/suite/rpl/t/warn_slaves_not_needed_for_purge.test new file mode 100644 index 00000000000..3c46730f74f --- /dev/null +++ b/mysql-test/suite/rpl/t/warn_slaves_not_needed_for_purge.test @@ -0,0 +1,52 @@ +# MDEV-38849: Test the warning that the _default_ for +# `@@slave_connections_needed_for_purge` does not match the replication setup. +# (Do _not_ warn if `@@slave_connections_needed_for_purge` is manually set.) + +--source include/have_binlog_format_mixed.inc # no actual binlog content +--let $rpl_skip_start_slave= 1 +--source include/master-slave.inc + + +--let SEARCH_FILE= `SELECT @@log_error` +--let SEARCH_PATTERN= slave_connections_needed_for_purge +# Not connected: should not warn +--source include/search_pattern_in_file.inc + + +--connection slave +START SLAVE IO_THREAD; +--source include/wait_for_slave_io_to_start.inc +# Connected when left as default: should warn +--source include/search_pattern_in_file.inc + + +--source include/stop_slave_io.inc +START SLAVE IO_THREAD; +--source include/wait_for_slave_io_to_start.inc +# Reconnected: should not warn a second time + +--source include/stop_slave_io.inc + --let $rpl_server_parameters= --slave-connections-needed-for-purge 0 + --let $rpl_server_number= 1 + --source include/rpl_restart_server.inc + --connection slave +START SLAVE IO_THREAD; +--source include/wait_for_slave_io_to_start.inc +# Explicitly set to 0 in server options: should not warn + +--source include/stop_slave_io.inc + --let $rpl_server_parameters= + --source include/rpl_restart_server.inc + --connection master + SET @@GLOBAL.slave_connections_needed_for_purge= DEFAULT; + --connection slave +START SLAVE IO_THREAD; +--source include/wait_for_slave_io_to_start.inc +# Explicitly set to DEFAULT in system variables: should _not_ warn + +--connection master +--source include/search_pattern_in_file.inc + + +--let $rpl_only_running_threads= 1 +--source include/rpl_end.inc diff --git a/mysql-test/suite/sys_vars/r/sysvars_server_embedded.result b/mysql-test/suite/sys_vars/r/sysvars_server_embedded.result index 114eff7521d..2bccea99fab 100644 --- a/mysql-test/suite/sys_vars/r/sysvars_server_embedded.result +++ b/mysql-test/suite/sys_vars/r/sysvars_server_embedded.result @@ -3465,7 +3465,7 @@ COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME SLAVE_CONNECTIONS_NEEDED_FOR_PURGE VARIABLE_SCOPE GLOBAL VARIABLE_TYPE INT UNSIGNED -VARIABLE_COMMENT Minimum number of connected slaves required for automatic binary log purge with max_binlog_total_size, binlog_expire_logs_seconds or binlog_expire_logs_days. Default is 0 when Galera is enabled and 1 otherwise. +VARIABLE_COMMENT Minimum number of connected slaves required for automatic binary log purge with max_binlog_total_size, binlog_expire_logs_seconds or binlog_expire_logs_days. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 4294967295 NUMERIC_BLOCK_SIZE 1 diff --git a/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result b/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result index 12d884d1258..8401752dfc6 100644 --- a/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result +++ b/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result @@ -4035,7 +4035,7 @@ COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME SLAVE_CONNECTIONS_NEEDED_FOR_PURGE VARIABLE_SCOPE GLOBAL VARIABLE_TYPE INT UNSIGNED -VARIABLE_COMMENT Minimum number of connected slaves required for automatic binary log purge with max_binlog_total_size, binlog_expire_logs_seconds or binlog_expire_logs_days. Default is 0 when Galera is enabled and 1 otherwise. +VARIABLE_COMMENT Minimum number of connected slaves required for automatic binary log purge with max_binlog_total_size, binlog_expire_logs_seconds or binlog_expire_logs_days. NUMERIC_MIN_VALUE 0 NUMERIC_MAX_VALUE 4294967295 NUMERIC_BLOCK_SIZE 1 diff --git a/sql/mysqld.cc b/sql/mysqld.cc index f662270e8dc..7052221aa43 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -6042,16 +6042,6 @@ int mysqld_main(int argc, char **argv) SYSVAR_AUTOSIZE(global_system_variables.binlog_format, BINLOG_FORMAT_ROW); } binlog_format_used= 1; - if (IS_SYSVAR_AUTOSIZE(&internal_slave_connections_needed_for_purge)) - { - slave_connections_needed_for_purge= - internal_slave_connections_needed_for_purge= 0; - SYSVAR_AUTOSIZE(internal_slave_connections_needed_for_purge, 0); - sql_print_information( - "slave_connections_needed_for_purge changed to 0 because " - "of Galera. Change it to 1 or higher if this Galera node " - "is also Master in a normal replication setup"); - } } #endif /* WITH_WSREP */ @@ -8443,6 +8433,11 @@ mysqld_get_one_option(const struct my_option *opt, const char *argument, } #ifdef HAVE_REPLICATION + case OPT_SLAVE_CONNECTIONS_NEEDED_FOR_PURGE: + warn_slaves_not_needed_for_purge.store(false, + std::memory_order_relaxed); // no other threads to sync with + break; + case (int)OPT_REPLICATE_IGNORE_DB: { cur_rpl_filter->add_ignore_db(argument); diff --git a/sql/mysqld.h b/sql/mysqld.h index b37c8aa6ca8..4f24e8c7291 100644 --- a/sql/mysqld.h +++ b/sql/mysqld.h @@ -830,6 +830,7 @@ enum options_mysqld OPT_COSTS_ROWID_COPY_COST, OPT_EXPIRE_LOGS_DAYS, OPT_BINLOG_EXPIRE_LOGS_SECONDS, + OPT_SLAVE_CONNECTIONS_NEEDED_FOR_PURGE, OPT_CONSOLE, OPT_DEBUG_SYNC_TIMEOUT, OPT_REMOVED_OPTION, diff --git a/sql/repl_failsafe.cc b/sql/repl_failsafe.cc index 164ae02df4d..0c9493c6b04 100644 --- a/sql/repl_failsafe.cc +++ b/sql/repl_failsafe.cc @@ -50,6 +50,7 @@ struct Slave_info }; +std::atomic warn_slaves_not_needed_for_purge= {true}; Atomic_counter binlog_dump_thread_count; ulong rpl_status=RPL_NULL; mysql_mutex_t LOCK_rpl_status; @@ -120,6 +121,14 @@ int THD::register_slave(uchar *packet, size_t packet_length) Slave_info *si; uchar *p= packet, *p_end= packet + packet_length; const char *errmsg= "Wrong parameters to function register_slave"; + if (variables.log_warnings >= 1 && // Verbosity Level "Binlog/Replication" + warn_slaves_not_needed_for_purge.exchange(false, + // no need to sync with `@@slave_connections_needed_for_purge` itself + std::memory_order_relaxed)) + sql_print_warning( + "`@@slave_connections_needed_for_purge` defaults to 0, " + "which means this master does not retain older logs from " + "automatic binary log purge even if slaves need them."); if (check_access(this, PRIV_COM_REGISTER_SLAVE, any_db.str, NULL,NULL,0,0)) return 1; diff --git a/sql/sql_repl.h b/sql/sql_repl.h index c03384aa5a3..8fb44b5d640 100644 --- a/sql/sql_repl.h +++ b/sql/sql_repl.h @@ -23,6 +23,15 @@ struct slave_connection_state; +/** + Whether to log a warning once about an unfit + @ref slave_connections_needed_for_purge upon @ref COM_REGISTER_SLAVE + * Starts as `true` (do warn), and becomes `false` + (don't warn) when warning for the first time + * Also becomes `false` when the user intetionally sets + `@@slave_connections_needed_for_purge` +*/ +extern std::atomic warn_slaves_not_needed_for_purge; extern my_bool opt_show_slave_auth_info; extern char *master_host, *master_info_file; diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc index 2a06379df5d..5f708552c4c 100644 --- a/sql/sys_vars.cc +++ b/sql/sys_vars.cc @@ -1326,13 +1326,23 @@ Sys_slave_connections_needed_for_purge( "slave_connections_needed_for_purge", "Minimum number of connected slaves required for automatic binary " "log purge with max_binlog_total_size, binlog_expire_logs_seconds " - "or binlog_expire_logs_days. Default is 0 when Galera is enabled and 1 " - "otherwise.", + "or binlog_expire_logs_days.", GLOBAL_VAR(internal_slave_connections_needed_for_purge), - CMD_LINE(REQUIRED_ARG), - VALID_RANGE(0, UINT_MAX), DEFAULT(1), BLOCK_SIZE(1), - NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0), - ON_UPDATE(update_binlog_space_limit)); + CMD_LINE(REQUIRED_ARG, OPT_SLAVE_CONNECTIONS_NEEDED_FOR_PURGE), + VALID_RANGE(0, UINT_MAX), DEFAULT(0), BLOCK_SIZE(1), + NO_MUTEX_GUARD, NOT_IN_BINLOG, +#ifdef HAVE_REPLICATION + ON_CHECK([](sys_var *, THD *, set_var *) + { + warn_slaves_not_needed_for_purge.store(false, + // no need to sync with the variable itself + std::memory_order_relaxed); + return false; + }) +#else + ON_CHECK(0) +#endif + , ON_UPDATE(update_binlog_space_limit)); static Sys_var_mybool Sys_flush( From b6398296aeeeb253bdb3d1dd1620d2bb03836bf7 Mon Sep 17 00:00:00 2001 From: Khaled Riyad Date: Mon, 21 Sep 2026 18:29:39 +0300 Subject: [PATCH 64/71] MDEV-38861 heap-use-after-free in Prepared_statement::execute() DROP PROCEDURE and CREATE OR REPLACE PROCEDURE executed from inside the routine itself removed it from the SP cache. sp_head::destroy() then freed the memory root that the running sp_head, its LEX and its instructions live in, and the caller kept using them. Skip the removal while the routine is being executed. sp_cache_invalidate() above has already bumped the cache version, so the stale entry is removed by the next lookup, after IS_INVOKED has been cleared. --- mysql-test/main/sp-bugs.result | 63 ++++++++++++++++ mysql-test/main/sp-bugs.test | 73 +++++++++++++++++++ .../suite/compat/oracle/r/sp-package.result | 39 ++++++++++ .../suite/compat/oracle/t/sp-package.test | 41 +++++++++++ sql/sp.cc | 3 +- 5 files changed, 218 insertions(+), 1 deletion(-) diff --git a/mysql-test/main/sp-bugs.result b/mysql-test/main/sp-bugs.result index b435ba4a91a..24e5828506b 100644 --- a/mysql-test/main/sp-bugs.result +++ b/mysql-test/main/sp-bugs.result @@ -400,6 +400,69 @@ DROP FUNCTION f; # End of 10.5 tests # # +# MDEV-38861 heap-use-after-free in Prepared_statement::execute() +# +# A routine must not be removed from the SP cache while it is executing. +# +# DROP of the running routine +create procedure p0() +begin +prepare stmt from "drop procedure p0"; +execute stmt; +end$$ +call p0(); +call p0(); +ERROR 42000: PROCEDURE test.p0 does not exist +deallocate prepare stmt; +# CREATE OR REPLACE of the running routine +create procedure p0() +begin +prepare stmt from "create or replace procedure p0() begin select 1 as replaced; end"; +execute stmt; +end$$ +call p0(); +call p0(); +replaced +1 +deallocate prepare stmt; +drop procedure p0; +# Recursive call after the self-DROP, with the default recursion depth +set @save_depth=@@max_sp_recursion_depth; +set @@max_sp_recursion_depth=0; +create procedure p0() +begin +prepare stmt from "drop procedure p0"; +execute stmt; +call p0(); +end$$ +call p0(); +ERROR HY000: Recursive limit 0 (as set by the max_sp_recursion_depth variable) was exceeded for routine p0 +call p0(); +ERROR 42000: PROCEDURE test.p0 does not exist +deallocate prepare stmt; +set @@max_sp_recursion_depth=@save_depth; +# DROP of the caller, which then keeps running +create procedure outer_p() +begin +call inner_p(); +select 'outer survived' as state; +end$$ +create procedure inner_p() +begin +prepare stmt from "drop procedure outer_p"; +execute stmt; +end$$ +call outer_p(); +state +outer survived +call outer_p(); +ERROR 42000: PROCEDURE test.outer_p does not exist +deallocate prepare stmt; +drop procedure inner_p; +# +# End of 11.4 tests +# +# # MDEV-36876 Crash during the Item_subselect::init - outer_select is NULL # # Vectors are not allowed for SOME/ANY diff --git a/mysql-test/main/sp-bugs.test b/mysql-test/main/sp-bugs.test index 067ca9b6ae8..92f1146b609 100644 --- a/mysql-test/main/sp-bugs.test +++ b/mysql-test/main/sp-bugs.test @@ -438,6 +438,79 @@ DROP FUNCTION f; --echo # End of 10.5 tests --echo # +--echo # +--echo # MDEV-38861 heap-use-after-free in Prepared_statement::execute() +--echo # +--echo # A routine must not be removed from the SP cache while it is executing. +--echo # + +--echo # DROP of the running routine +delimiter $$; +create procedure p0() +begin + prepare stmt from "drop procedure p0"; + execute stmt; +end$$ +delimiter ;$$ +call p0(); +--error ER_SP_DOES_NOT_EXIST +call p0(); +deallocate prepare stmt; + +--echo # CREATE OR REPLACE of the running routine +delimiter $$; +create procedure p0() +begin + prepare stmt from "create or replace procedure p0() begin select 1 as replaced; end"; + execute stmt; +end$$ +delimiter ;$$ +call p0(); +call p0(); +deallocate prepare stmt; +drop procedure p0; + +--echo # Recursive call after the self-DROP, with the default recursion depth +set @save_depth=@@max_sp_recursion_depth; +set @@max_sp_recursion_depth=0; +delimiter $$; +create procedure p0() +begin + prepare stmt from "drop procedure p0"; + execute stmt; + call p0(); +end$$ +delimiter ;$$ +--error ER_SP_RECURSION_LIMIT +call p0(); +--error ER_SP_DOES_NOT_EXIST +call p0(); +deallocate prepare stmt; +set @@max_sp_recursion_depth=@save_depth; + +--echo # DROP of the caller, which then keeps running +delimiter $$; +create procedure outer_p() +begin + call inner_p(); + select 'outer survived' as state; +end$$ +create procedure inner_p() +begin + prepare stmt from "drop procedure outer_p"; + execute stmt; +end$$ +delimiter ;$$ +call outer_p(); +--error ER_SP_DOES_NOT_EXIST +call outer_p(); +deallocate prepare stmt; +drop procedure inner_p; + +--echo # +--echo # End of 11.4 tests +--echo # + --echo # --echo # MDEV-36876 Crash during the Item_subselect::init - outer_select is NULL --echo # diff --git a/mysql-test/suite/compat/oracle/r/sp-package.result b/mysql-test/suite/compat/oracle/r/sp-package.result index 2372cfea60a..deb0ee2e9c8 100644 --- a/mysql-test/suite/compat/oracle/r/sp-package.result +++ b/mysql-test/suite/compat/oracle/r/sp-package.result @@ -3381,3 +3381,42 @@ DROP TABLE t1; DROP FUNCTION f1_deterministic; DROP FUNCTION f2_not_deterministic; DROP PACKAGE pkg1; +# +# MDEV-38861 heap-use-after-free in Prepared_statement::execute() +# +SET sql_mode=ORACLE; +# DROP PACKAGE BODY of the running package +CREATE PACKAGE pkg AS +PROCEDURE p1; +END$$ +CREATE PACKAGE BODY pkg AS +PROCEDURE p1 AS +BEGIN +EXECUTE IMMEDIATE 'DROP PACKAGE BODY pkg'; +SELECT 'body survived' AS state; +END; +END$$ +CALL pkg.p1(); +state +body survived +CALL pkg.p1(); +ERROR 42000: PROCEDURE test.pkg.p1 does not exist +DROP PACKAGE pkg; +# CREATE OR REPLACE PACKAGE BODY of the running package +CREATE PACKAGE pkg AS +PROCEDURE p1; +END$$ +CREATE PACKAGE BODY pkg AS +PROCEDURE p1 AS +BEGIN +EXECUTE IMMEDIATE 'CREATE OR REPLACE PACKAGE BODY pkg AS PROCEDURE p1 AS BEGIN SELECT ''replaced'' AS state; END; END'; +SELECT 'body survived' AS state; +END; +END$$ +CALL pkg.p1(); +state +body survived +CALL pkg.p1(); +state +replaced +DROP PACKAGE pkg; diff --git a/mysql-test/suite/compat/oracle/t/sp-package.test b/mysql-test/suite/compat/oracle/t/sp-package.test index aefede41c8b..d90ecc9c8c5 100644 --- a/mysql-test/suite/compat/oracle/t/sp-package.test +++ b/mysql-test/suite/compat/oracle/t/sp-package.test @@ -3109,3 +3109,44 @@ DROP TABLE t1; DROP FUNCTION f1_deterministic; DROP FUNCTION f2_not_deterministic; DROP PACKAGE pkg1; + +--echo # +--echo # MDEV-38861 heap-use-after-free in Prepared_statement::execute() +--echo # + +SET sql_mode=ORACLE; + +--echo # DROP PACKAGE BODY of the running package +DELIMITER $$; +CREATE PACKAGE pkg AS + PROCEDURE p1; +END$$ +CREATE PACKAGE BODY pkg AS + PROCEDURE p1 AS + BEGIN + EXECUTE IMMEDIATE 'DROP PACKAGE BODY pkg'; + SELECT 'body survived' AS state; + END; +END$$ +DELIMITER ;$$ +CALL pkg.p1(); +--error ER_SP_DOES_NOT_EXIST +CALL pkg.p1(); +DROP PACKAGE pkg; + +--echo # CREATE OR REPLACE PACKAGE BODY of the running package +DELIMITER $$; +CREATE PACKAGE pkg AS + PROCEDURE p1; +END$$ +CREATE PACKAGE BODY pkg AS + PROCEDURE p1 AS + BEGIN + EXECUTE IMMEDIATE 'CREATE OR REPLACE PACKAGE BODY pkg AS PROCEDURE p1 AS BEGIN SELECT ''replaced'' AS state; END; END'; + SELECT 'body survived' AS state; + END; +END$$ +DELIMITER ;$$ +CALL pkg.p1(); +CALL pkg.p1(); +DROP PACKAGE pkg; diff --git a/sql/sp.cc b/sql/sp.cc index 4d9c9e924c5..9b6548d84da 100644 --- a/sql/sp.cc +++ b/sql/sp.cc @@ -1148,7 +1148,8 @@ Sp_handler::sp_drop_routine_internal(THD *thd, sp_head *sp; sp_cache **spc= get_cache(thd); DBUG_ASSERT(spc); - if ((sp= sp_cache_lookup(spc, name))) + /* Removing a running routine would free the LEX being executed */ + if ((sp= sp_cache_lookup(spc, name)) && !sp->is_invoked()) sp_cache_remove(spc, &sp); /* Drop statistics for this stored program from performance schema. */ MYSQL_DROP_SP(type(), name->m_db.str, static_cast(name->m_db.length), From 15174ecd8efe4a2cc95cb00c8fc6b01e8a54b484 Mon Sep 17 00:00:00 2001 From: drrtuy Date: Sat, 12 Sep 2026 21:57:31 +0100 Subject: [PATCH 65/71] MDEV-40957 various fixes for mixed DML path that suffered from uninit bitmaps and wrong SQL statements that failed in DuckDB. --- storage/duckdb/convertor/dml_convertor.cc | 4 +- storage/duckdb/docs/architecture.md | 10 +- storage/duckdb/ha_duckdb.cc | 63 +++++++--- storage/duckdb/ha_duckdb.h | 1 + .../mysql-test/duckdb/r/mdev_40957.result | 15 +++ .../mysql-test/duckdb/r/mixed_dml.result | 113 +++++++++++++++++ .../mysql-test/duckdb/t/mdev_40957.test | 30 +++++ .../duckdb/mysql-test/duckdb/t/mixed_dml.test | 115 ++++++++++++++++++ storage/duckdb/runtime/delta_appender.cc | 43 ++++--- storage/duckdb/runtime/delta_appender.h | 2 +- 10 files changed, 356 insertions(+), 40 deletions(-) create mode 100644 storage/duckdb/mysql-test/duckdb/r/mdev_40957.result create mode 100644 storage/duckdb/mysql-test/duckdb/r/mixed_dml.result create mode 100644 storage/duckdb/mysql-test/duckdb/t/mdev_40957.test create mode 100644 storage/duckdb/mysql-test/duckdb/t/mixed_dml.test diff --git a/storage/duckdb/convertor/dml_convertor.cc b/storage/duckdb/convertor/dml_convertor.cc index 4685ed5f04d..c52c8068e73 100644 --- a/storage/duckdb/convertor/dml_convertor.cc +++ b/storage/duckdb/convertor/dml_convertor.cc @@ -216,9 +216,9 @@ std::string DMLConvertor::translate() void DMLConvertor::fill_index_fields_for_where(std::vector &fields) { - KEY *key_info= m_table->key_info; - if (key_info) + if (m_table->s->primary_key != MAX_KEY) { + KEY *key_info= m_table->key_info + m_table->s->primary_key; KEY_PART_INFO *key_part= key_info->key_part; for (uint j= 0; j < key_info->user_defined_key_parts; j++, key_part++) { diff --git a/storage/duckdb/docs/architecture.md b/storage/duckdb/docs/architecture.md index 6a6d7cf01d1..b1bee0a5a8b 100644 --- a/storage/duckdb/docs/architecture.md +++ b/storage/duckdb/docs/architecture.md @@ -293,12 +293,14 @@ XA transactions are rejected (`reject_xa_if_active`). ## Batch DML (DeltaAppender) -For bulk INSERT (and mixed INSERT/UPDATE/DELETE within a single transaction): +For bulk INSERT, the insert-only path appends directly to the target table. For mixed +INSERT/UPDATE/DELETE within a transaction: 1. Creates a **temporary table** cloning the target schema + 3 auxiliary columns (`#mdb_delete_flag`, `#mdb_row_no`, `#mdb_trx_no`). -2. Writes rows via the `duckdb::Appender` API (much faster than per-row SQL INSERT). -3. On `flush()` (at commit/prepare): executes `INSERT INTO target SELECT … FROM tmp` and/or `DELETE FROM target WHERE pk IN (SELECT pk FROM tmp WHERE delete_flag)`. -4. The temp table lives in the connection-local temporary catalog — invisible to other sessions. +2. Writes inserted row versions and delete markers via the `duckdb::Appender` API. +3. On `flush()` (at commit/prepare), deletes target rows for every primary key present in the temporary table. +4. For each primary key, selects the temporary row with the greatest `#mdb_row_no` and inserts it unless that latest row is a delete marker. +5. The temp table lives in the connection-local temporary catalog — invisible to other sessions. --- diff --git a/storage/duckdb/ha_duckdb.cc b/storage/duckdb/ha_duckdb.cc index c1e39473998..2c44076f83d 100644 --- a/storage/duckdb/ha_duckdb.cc +++ b/storage/duckdb/ha_duckdb.cc @@ -423,20 +423,36 @@ static bool calc_pk_difference(const uchar *old_row, const uchar *new_row, return false; } -static myduck::BatchState get_batch_state(THD *thd) +static int get_batch_state(THD *thd, bool insert_only, + myduck::BatchState *batch_state) { auto *ctx= get_duckdb_context(thd); - myduck::BatchState batch_state= ctx->get_batch_state(); + *batch_state= ctx->get_batch_state(); - if (batch_state == myduck::BatchState::UNDEFINED) + if (*batch_state == myduck::BatchState::IN_INSERT_ONLY_BATCH && + !insert_only) { - if (dml_in_batch) - batch_state= myduck::BatchState::IN_INSERT_ONLY_BATCH; - else - batch_state= myduck::BatchState::NOT_IN_BATCH; - ctx->set_batch_state(batch_state); + std::string error_msg; + if (ctx->flush_appenders(error_msg)) + { + my_error(ER_GET_ERRMSG, MYF(0), HA_DUCKDB_APPEND_ERROR, + error_msg.c_str(), "DuckDB"); + return HA_DUCKDB_APPEND_ERROR; + } + *batch_state= myduck::BatchState::UNDEFINED; } - return batch_state; + + if (*batch_state == myduck::BatchState::UNDEFINED) + { + if (!dml_in_batch) + *batch_state= myduck::BatchState::NOT_IN_BATCH; + else if (insert_only) + *batch_state= myduck::BatchState::IN_INSERT_ONLY_BATCH; + else + *batch_state= myduck::BatchState::IN_MIX_BATCH; + ctx->set_batch_state(*batch_state); + } + return 0; } /* Build duckdb type map of blob type */ @@ -490,7 +506,13 @@ int ha_duckdb::write_row(const uchar *buf) DBUG_RETURN(ret); } - myduck::BatchState batch_state= get_batch_state(thd); + myduck::BatchState batch_state; + ret= get_batch_state(thd, true, &batch_state); + if (ret) + { + dbug_tmp_restore_column_map(&table->read_set, org_bitmap); + DBUG_RETURN(ret); + } if (batch_state == myduck::BatchState::NOT_IN_BATCH) { @@ -666,7 +688,10 @@ int ha_duckdb::update_row(const uchar *old_row, const uchar *new_row) if (ret) DBUG_RETURN(ret); - myduck::BatchState batch_state= get_batch_state(thd); + myduck::BatchState batch_state; + ret= get_batch_state(thd, false, &batch_state); + if (ret) + DBUG_RETURN(ret); if (batch_state == myduck::BatchState::NOT_IN_BATCH) { @@ -711,7 +736,10 @@ int ha_duckdb::delete_row(const uchar *) if (ret) DBUG_RETURN(ret); - myduck::BatchState batch_state= get_batch_state(thd); + myduck::BatchState batch_state; + ret= get_batch_state(thd, false, &batch_state); + if (ret) + DBUG_RETURN(ret); if (batch_state == myduck::BatchState::NOT_IN_BATCH) { @@ -789,10 +817,17 @@ int ha_duckdb::rnd_init(bool) else DBUG_RETURN(HA_ERR_INTERNAL_ERROR); + auto *ctx= get_duckdb_context(thd); + std::string error_msg; + if (ctx->flush_appenders(error_msg)) + { + my_error(ER_GET_ERRMSG, MYF(0), HA_DUCKDB_APPEND_ERROR, + error_msg.c_str(), "DuckDB"); + DBUG_RETURN(HA_DUCKDB_APPEND_ERROR); + } + std::string query= "SELECT * FROM " + quote_duckdb_identifier(schema_name) + "." + quote_duckdb_identifier(table_name); - - auto *ctx= get_duckdb_context(thd); query_result= myduck::duckdb_query(ctx->get_connection(), query); if (query_result->HasError()) { diff --git a/storage/duckdb/ha_duckdb.h b/storage/duckdb/ha_duckdb.h index 4f32bbebe10..3933f53a0eb 100644 --- a/storage/duckdb/ha_duckdb.h +++ b/storage/duckdb/ha_duckdb.h @@ -93,6 +93,7 @@ public: { return (HA_BINLOG_STMT_CAPABLE | HA_BINLOG_ROW_CAPABLE | HA_NULL_IN_KEY | HA_CAN_INDEX_BLOBS | + HA_PRIMARY_KEY_REQUIRED_FOR_DELETE | HA_CAN_DIRECT_UPDATE_AND_DELETE); } diff --git a/storage/duckdb/mysql-test/duckdb/r/mdev_40957.result b/storage/duckdb/mysql-test/duckdb/r/mdev_40957.result new file mode 100644 index 00000000000..51874a1e9f5 --- /dev/null +++ b/storage/duckdb/mysql-test/duckdb/r/mdev_40957.result @@ -0,0 +1,15 @@ +CREATE TABLE invoice ( +id UUID DEFAULT UUID() PRIMARY KEY, +product VARCHAR(64) +) ENGINE=DuckDB; +CREATE TABLE invoice_audit (n INT) ENGINE=InnoDB; +CREATE TRIGGER invoice_ad AFTER DELETE ON invoice +FOR EACH ROW INSERT INTO invoice_audit VALUES (1); +INSERT INTO invoice (product) VALUES ('one'), ('two'), ('three'), ('four'); +DELETE FROM invoice; +SELECT COUNT(*) FROM invoice; +COUNT(*) +0 +SELECT COUNT(*) FROM invoice_audit; +COUNT(*) +4 diff --git a/storage/duckdb/mysql-test/duckdb/r/mixed_dml.result b/storage/duckdb/mysql-test/duckdb/r/mixed_dml.result new file mode 100644 index 00000000000..571efd92e1d --- /dev/null +++ b/storage/duckdb/mysql-test/duckdb/r/mixed_dml.result @@ -0,0 +1,113 @@ +# Commit and rollback after an insert-only to mixed batch transition +CREATE TABLE t1 ( +id INT PRIMARY KEY, +value VARCHAR(16) +) ENGINE=DuckDB; +CREATE TABLE t1_audit (event CHAR(1)) ENGINE=InnoDB; +CREATE TRIGGER t1_ad AFTER DELETE ON t1 +FOR EACH ROW INSERT INTO t1_audit VALUES ('D'); +CREATE TRIGGER t1_au AFTER UPDATE ON t1 +FOR EACH ROW INSERT INTO t1_audit VALUES ('U'); +BEGIN; +INSERT INTO t1 VALUES (1, 'one'), (2, 'two'), (3, 'three'); +DELETE FROM t1 WHERE value = 'two'; +UPDATE t1 SET value = 'THREE' WHERE value = 'three'; +COMMIT; +SELECT * FROM t1 ORDER BY id; +id value +1 one +3 THREE +SELECT * FROM t1_audit ORDER BY event; +event +D +U +BEGIN; +INSERT INTO t1 VALUES (4, 'four'); +DELETE FROM t1 WHERE value = 'one'; +UPDATE t1 SET value = 'changed' WHERE value = 'THREE'; +ROLLBACK; +SELECT * FROM t1 ORDER BY id; +id value +1 one +3 THREE +SELECT * FROM t1_audit ORDER BY event; +event +D +U +# Repeated changes, delete followed by insert, and primary key update +CREATE TABLE t2 ( +id INT PRIMARY KEY, +value VARCHAR(16) +) ENGINE=DuckDB; +CREATE TABLE t2_audit (event CHAR(1)) ENGINE=InnoDB; +CREATE TRIGGER t2_ad AFTER DELETE ON t2 +FOR EACH ROW INSERT INTO t2_audit VALUES ('D'); +CREATE TRIGGER t2_au AFTER UPDATE ON t2 +FOR EACH ROW INSERT INTO t2_audit VALUES ('U'); +INSERT INTO t2 VALUES (1, 'one'), (2, 'two'), (3, 'three'); +BEGIN; +UPDATE t2 SET value = 'first' WHERE value = 'one'; +UPDATE t2 SET value = 'second' WHERE value = 'first'; +DELETE FROM t2 WHERE value = 'second'; +DELETE FROM t2 WHERE value = 'two'; +INSERT INTO t2 VALUES (2, 'reinserted'); +UPDATE t2 SET value = 'first' WHERE value = 'three'; +UPDATE t2 SET value = 'second' WHERE value = 'first'; +UPDATE t2 SET id = 30, value = 'moved' WHERE value = 'second'; +COMMIT; +SELECT * FROM t2 ORDER BY id; +id value +2 reinserted +30 moved +SELECT event, COUNT(*) FROM t2_audit GROUP BY event ORDER BY event; +event COUNT(*) +D 2 +U 5 +# Composite primary key +CREATE TABLE t3 ( +a INT, +b INT, +value VARCHAR(16), +PRIMARY KEY (a, b) +) ENGINE=DuckDB; +CREATE TABLE t3_audit (event CHAR(1)) ENGINE=InnoDB; +CREATE TRIGGER t3_ad AFTER DELETE ON t3 +FOR EACH ROW INSERT INTO t3_audit VALUES ('D'); +CREATE TRIGGER t3_au AFTER UPDATE ON t3 +FOR EACH ROW INSERT INTO t3_audit VALUES ('U'); +INSERT INTO t3 VALUES (1, 1, 'alpha'), (1, 2, 'beta'), (2, 1, 'gamma'); +BEGIN; +DELETE FROM t3 WHERE value = 'alpha'; +UPDATE t3 SET value = 'BETA' WHERE value = 'beta'; +UPDATE t3 SET a = 3, value = 'moved' WHERE value = 'gamma'; +COMMIT; +SELECT * FROM t3 ORDER BY a, b; +a b value +1 2 BETA +3 1 moved +SELECT event, COUNT(*) FROM t3_audit GROUP BY event ORDER BY event; +event COUNT(*) +D 1 +U 2 +# A secondary index is not a row identity without a primary key +CREATE TABLE t4 ( +k INT, +value VARCHAR(16), +KEY secondary_key (k) +) ENGINE=DuckDB; +CREATE TABLE t4_audit (value VARCHAR(16)) ENGINE=InnoDB; +CREATE TRIGGER t4_ad AFTER DELETE ON t4 +FOR EACH ROW INSERT INTO t4_audit VALUES (OLD.value); +INSERT INTO t4 VALUES (1, 'delete-me'), (1, 'keep-me'), (2, 'other'); +DELETE FROM t4 WHERE value = 'delete-me'; +SELECT * FROM t4 ORDER BY k, value; +k value +1 keep-me +2 other +SELECT * FROM t4_audit ORDER BY value; +value +delete-me +delete_in_batch +5 +update_in_batch +9 diff --git a/storage/duckdb/mysql-test/duckdb/t/mdev_40957.test b/storage/duckdb/mysql-test/duckdb/t/mdev_40957.test new file mode 100644 index 00000000000..1909a4c3c5f --- /dev/null +++ b/storage/duckdb/mysql-test/duckdb/t/mdev_40957.test @@ -0,0 +1,30 @@ +--source ../include/have_duckdb.inc +--source include/not_msan.inc + +--disable_query_log +--disable_warnings +SET @saved_duckdb_dml_in_batch = @@GLOBAL.duckdb_dml_in_batch; +SET GLOBAL duckdb_dml_in_batch = ON; +DROP DATABASE IF EXISTS mdev_40957; +CREATE DATABASE mdev_40957 CHARACTER SET utf8mb4; +USE mdev_40957; +--enable_warnings +--enable_query_log + +CREATE TABLE invoice ( + id UUID DEFAULT UUID() PRIMARY KEY, + product VARCHAR(64) +) ENGINE=DuckDB; +CREATE TABLE invoice_audit (n INT) ENGINE=InnoDB; +CREATE TRIGGER invoice_ad AFTER DELETE ON invoice + FOR EACH ROW INSERT INTO invoice_audit VALUES (1); +INSERT INTO invoice (product) VALUES ('one'), ('two'), ('three'), ('four'); +DELETE FROM invoice; +SELECT COUNT(*) FROM invoice; +SELECT COUNT(*) FROM invoice_audit; + +--disable_query_log +DROP DATABASE mdev_40957; +USE test; +SET GLOBAL duckdb_dml_in_batch = @saved_duckdb_dml_in_batch; +--enable_query_log diff --git a/storage/duckdb/mysql-test/duckdb/t/mixed_dml.test b/storage/duckdb/mysql-test/duckdb/t/mixed_dml.test new file mode 100644 index 00000000000..b555c1ff9e4 --- /dev/null +++ b/storage/duckdb/mysql-test/duckdb/t/mixed_dml.test @@ -0,0 +1,115 @@ +--source ../include/have_duckdb.inc +--source include/not_msan.inc + +--disable_query_log +--disable_warnings +SET @saved_duckdb_dml_in_batch = @@GLOBAL.duckdb_dml_in_batch; +SET @saved_duckdb_require_primary_key = @@GLOBAL.duckdb_require_primary_key; +SET GLOBAL duckdb_dml_in_batch = ON; +SET GLOBAL duckdb_require_primary_key = OFF; +DROP DATABASE IF EXISTS mixed_dml; +CREATE DATABASE mixed_dml CHARACTER SET utf8mb4; +USE mixed_dml; +--let $delete_before = query_get_value(SHOW STATUS LIKE 'Duckdb_rows_delete_in_batch', Value, 1) +--let $update_before = query_get_value(SHOW STATUS LIKE 'Duckdb_rows_update_in_batch', Value, 1) +--enable_warnings +--enable_query_log + +--echo # Commit and rollback after an insert-only to mixed batch transition +CREATE TABLE t1 ( + id INT PRIMARY KEY, + value VARCHAR(16) +) ENGINE=DuckDB; +CREATE TABLE t1_audit (event CHAR(1)) ENGINE=InnoDB; +CREATE TRIGGER t1_ad AFTER DELETE ON t1 + FOR EACH ROW INSERT INTO t1_audit VALUES ('D'); +CREATE TRIGGER t1_au AFTER UPDATE ON t1 + FOR EACH ROW INSERT INTO t1_audit VALUES ('U'); + +BEGIN; +INSERT INTO t1 VALUES (1, 'one'), (2, 'two'), (3, 'three'); +DELETE FROM t1 WHERE value = 'two'; +UPDATE t1 SET value = 'THREE' WHERE value = 'three'; +COMMIT; +SELECT * FROM t1 ORDER BY id; +SELECT * FROM t1_audit ORDER BY event; + +BEGIN; +INSERT INTO t1 VALUES (4, 'four'); +DELETE FROM t1 WHERE value = 'one'; +UPDATE t1 SET value = 'changed' WHERE value = 'THREE'; +ROLLBACK; +SELECT * FROM t1 ORDER BY id; +SELECT * FROM t1_audit ORDER BY event; + +--echo # Repeated changes, delete followed by insert, and primary key update +CREATE TABLE t2 ( + id INT PRIMARY KEY, + value VARCHAR(16) +) ENGINE=DuckDB; +CREATE TABLE t2_audit (event CHAR(1)) ENGINE=InnoDB; +CREATE TRIGGER t2_ad AFTER DELETE ON t2 + FOR EACH ROW INSERT INTO t2_audit VALUES ('D'); +CREATE TRIGGER t2_au AFTER UPDATE ON t2 + FOR EACH ROW INSERT INTO t2_audit VALUES ('U'); +INSERT INTO t2 VALUES (1, 'one'), (2, 'two'), (3, 'three'); + +BEGIN; +UPDATE t2 SET value = 'first' WHERE value = 'one'; +UPDATE t2 SET value = 'second' WHERE value = 'first'; +DELETE FROM t2 WHERE value = 'second'; +DELETE FROM t2 WHERE value = 'two'; +INSERT INTO t2 VALUES (2, 'reinserted'); +UPDATE t2 SET value = 'first' WHERE value = 'three'; +UPDATE t2 SET value = 'second' WHERE value = 'first'; +UPDATE t2 SET id = 30, value = 'moved' WHERE value = 'second'; +COMMIT; +SELECT * FROM t2 ORDER BY id; +SELECT event, COUNT(*) FROM t2_audit GROUP BY event ORDER BY event; + +--echo # Composite primary key +CREATE TABLE t3 ( + a INT, + b INT, + value VARCHAR(16), + PRIMARY KEY (a, b) +) ENGINE=DuckDB; +CREATE TABLE t3_audit (event CHAR(1)) ENGINE=InnoDB; +CREATE TRIGGER t3_ad AFTER DELETE ON t3 + FOR EACH ROW INSERT INTO t3_audit VALUES ('D'); +CREATE TRIGGER t3_au AFTER UPDATE ON t3 + FOR EACH ROW INSERT INTO t3_audit VALUES ('U'); +INSERT INTO t3 VALUES (1, 1, 'alpha'), (1, 2, 'beta'), (2, 1, 'gamma'); + +BEGIN; +DELETE FROM t3 WHERE value = 'alpha'; +UPDATE t3 SET value = 'BETA' WHERE value = 'beta'; +UPDATE t3 SET a = 3, value = 'moved' WHERE value = 'gamma'; +COMMIT; +SELECT * FROM t3 ORDER BY a, b; +SELECT event, COUNT(*) FROM t3_audit GROUP BY event ORDER BY event; + +--echo # A secondary index is not a row identity without a primary key +CREATE TABLE t4 ( + k INT, + value VARCHAR(16), + KEY secondary_key (k) +) ENGINE=DuckDB; +CREATE TABLE t4_audit (value VARCHAR(16)) ENGINE=InnoDB; +CREATE TRIGGER t4_ad AFTER DELETE ON t4 + FOR EACH ROW INSERT INTO t4_audit VALUES (OLD.value); +INSERT INTO t4 VALUES (1, 'delete-me'), (1, 'keep-me'), (2, 'other'); +DELETE FROM t4 WHERE value = 'delete-me'; +SELECT * FROM t4 ORDER BY k, value; +SELECT * FROM t4_audit ORDER BY value; + +--let $delete_after = query_get_value(SHOW STATUS LIKE 'Duckdb_rows_delete_in_batch', Value, 1) +--let $update_after = query_get_value(SHOW STATUS LIKE 'Duckdb_rows_update_in_batch', Value, 1) +--disable_query_log +--eval SELECT $delete_after - $delete_before AS delete_in_batch +--eval SELECT $update_after - $update_before AS update_in_batch +DROP DATABASE mixed_dml; +USE test; +SET GLOBAL duckdb_dml_in_batch = @saved_duckdb_dml_in_batch; +SET GLOBAL duckdb_require_primary_key = @saved_duckdb_require_primary_key; +--enable_query_log diff --git a/storage/duckdb/runtime/delta_appender.cc b/storage/duckdb/runtime/delta_appender.cc index 46c4259b649..101cd66c8a5 100644 --- a/storage/duckdb/runtime/delta_appender.cc +++ b/storage/duckdb/runtime/delta_appender.cc @@ -153,6 +153,13 @@ int DeltaAppender::append_row_update(TABLE *table, ulonglong trx_no, int DeltaAppender::append_row_delete(TABLE *table, ulonglong trx_no, const uchar *old_row) { + if (!m_use_tmp_table) + { + my_error(ER_GET_ERRMSG, MYF(0), HA_DUCKDB_APPEND_ERROR, + "Delete requires a mixed batch", "DuckDB Appender"); + return HA_DUCKDB_APPEND_ERROR; + } + ++m_row_count; m_has_delete= true; @@ -241,10 +248,11 @@ bool DeltaAppender::Initialize(TABLE *table) return true; } - KEY *key_info= table->key_info; - if (!key_info) + if (table->s->primary_key == MAX_KEY) + return true; + KEY *key_info= table->key_info + table->s->primary_key; + if (my_bitmap_init(&m_pk_bitmap, nullptr, table->s->fields)) return true; - my_bitmap_init(&m_pk_bitmap, nullptr, table->s->fields); KEY_PART_INFO *key_part= key_info->key_part; for (uint i= 0; i < key_info->user_defined_key_parts; i++, key_part++) { @@ -470,18 +478,16 @@ int DeltaAppender::append_mysql_field(const Field *field_arg, return 0; } -static void appendSelectQuery(std::stringstream &ss, - const std::string &select_list, - const std::string &pk_list, - const std::string &table_name, int delete_flag) +static void appendLatestRowsQuery(std::stringstream &ss, + const std::string &select_list, + const std::string &pk_list, + const std::string &table_name) { - ss << "SELECT UNNEST(r) FROM (SELECT LAST(ROW(" << select_list - << ") ORDER BY \"#mdb_row_no\") AS r, " - "LAST(\"#mdb_delete_flag\" ORDER BY \"#mdb_row_no\") AS " - "\"#mdb_delete_flag\" FROM main." - << quote_duckdb_identifier(table_name) << " GROUP BY " << pk_list << ")"; - if (!delete_flag) - ss << " WHERE \"#mdb_delete_flag\" = " << delete_flag; + ss << "SELECT " << select_list << " FROM main." + << quote_duckdb_identifier(table_name) + << " QUALIFY ROW_NUMBER() OVER (PARTITION BY " << pk_list + << " ORDER BY \"#mdb_row_no\" DESC) = 1 AND NOT " + "\"#mdb_delete_flag\""; } void DeltaAppender::generateQuery(std::stringstream &ss, bool delete_flag) @@ -493,17 +499,16 @@ void DeltaAppender::generateQuery(std::stringstream &ss, bool delete_flag) { ss << "INSERT INTO " << quote_duckdb_identifier(m_schema_name) << "." << quote_duckdb_identifier(m_table_name) << " "; - appendSelectQuery(ss, m_col_list, m_pk_list, m_tmp_table_name, - delete_flag); + appendLatestRowsQuery(ss, m_col_list, m_pk_list, m_tmp_table_name); ss << ";"; } else { ss << "DELETE FROM " << quote_duckdb_identifier(m_schema_name) << "." << quote_duckdb_identifier(m_table_name) << " WHERE (" << m_pk_list - << ") IN ("; - appendSelectQuery(ss, m_pk_list, m_pk_list, m_tmp_table_name, delete_flag); - ss << ");"; + << ") IN (SELECT " << m_pk_list << " FROM main." + << quote_duckdb_identifier(m_tmp_table_name) << " GROUP BY " + << m_pk_list << ");"; } } diff --git a/storage/duckdb/runtime/delta_appender.h b/storage/duckdb/runtime/delta_appender.h index 44deb4ed98c..e81b5ae9070 100644 --- a/storage/duckdb/runtime/delta_appender.h +++ b/storage/duckdb/runtime/delta_appender.h @@ -71,7 +71,7 @@ private: std::string m_table_name; std::string m_tmp_table_name; - MY_BITMAP m_pk_bitmap; + MY_BITMAP m_pk_bitmap{}; std::string m_pk_list{""}; std::string m_col_list{""}; From 250ee9121fc18863086ac785914f181862acbb87 Mon Sep 17 00:00:00 2001 From: drrtuy Date: Fri, 18 Sep 2026 12:17:57 +0100 Subject: [PATCH 66/71] MDEV-40957 read-free DML on the slave for DuckDB. --- storage/duckdb/ha_duckdb.cc | 36 +++++++++ storage/duckdb/ha_duckdb.h | 2 + .../r/rpl_delete_no_uuid_default.result | 51 +++++++++++++ .../mysql-test/duckdb/t/mdev_40957_rpl.cnf | 15 ++++ .../mysql-test/duckdb/t/mdev_40957_rpl.test | 73 +++++++++++++++++++ .../duckdb/t/rpl_delete_no_uuid_default.cnf | 15 ++++ .../duckdb/t/rpl_delete_no_uuid_default.test | 49 +++++++++++++ storage/duckdb/runtime/duckdb_context.cc | 5 ++ storage/duckdb/runtime/duckdb_context.h | 3 + 9 files changed, 249 insertions(+) create mode 100644 storage/duckdb/mysql-test/duckdb/r/rpl_delete_no_uuid_default.result create mode 100644 storage/duckdb/mysql-test/duckdb/t/mdev_40957_rpl.cnf create mode 100644 storage/duckdb/mysql-test/duckdb/t/mdev_40957_rpl.test create mode 100644 storage/duckdb/mysql-test/duckdb/t/rpl_delete_no_uuid_default.cnf create mode 100644 storage/duckdb/mysql-test/duckdb/t/rpl_delete_no_uuid_default.test diff --git a/storage/duckdb/ha_duckdb.cc b/storage/duckdb/ha_duckdb.cc index 2c44076f83d..b5942a05394 100644 --- a/storage/duckdb/ha_duckdb.cc +++ b/storage/duckdb/ha_duckdb.cc @@ -709,6 +709,22 @@ int ha_duckdb::update_row(const uchar *old_row, const uchar *new_row) } else { + /* + The batched update is replayed as delete + insert of the complete + new row. A replicated after-image is complete only with + binlog_row_image=FULL; the columns missing from a partial image + hold defaults, not the actual values. + */ + if (myduck::thd_is_replication_applier(thd) && + !bitmap_is_set_all(table->write_set)) + { + sql_print_warning("DuckDB: 'binlog_row_image' is not set to 'FULL', " + "replicated UPDATE replay is not possible!"); + my_error(ER_GET_ERRMSG, MYF(0), HA_DUCKDB_DML_ERROR, + "'binlog_row_image' is not set to 'FULL'", "DuckDB"); + DBUG_RETURN(HA_DUCKDB_DML_ERROR); + } + auto *ctx= get_duckdb_context(thd); ret= ctx->append_row_update(table, old_row); if (ret == 0) @@ -914,6 +930,26 @@ int ha_duckdb::rnd_pos(uchar *, uchar *) DBUG_RETURN(HA_ERR_WRONG_COMMAND); } +/* + Read-free replication apply. Rows_log_event::find_row() unpacks the + event's before-image into record[0] before locating the row; with + HA_PRIMARY_KEY_REQUIRED_FOR_POSITION it locates it through this + method. DuckDB identifies rows by primary key taken from the record + itself, so no lookup is needed: report success and leave the + before-image in record[0]. + + Any caller outside of the replication applier (filesort, multi-table + UPDATE/DELETE) must keep getting an error: faking success there would + silently corrupt data. +*/ +int ha_duckdb::rnd_pos_by_record(uchar *) +{ + DBUG_ENTER("ha_duckdb::rnd_pos_by_record"); + if (!myduck::thd_is_replication_applier(ha_thd())) + DBUG_RETURN(HA_ERR_WRONG_COMMAND); + DBUG_RETURN(0); +} + int ha_duckdb::info(uint flag) { DBUG_ENTER("ha_duckdb::info"); diff --git a/storage/duckdb/ha_duckdb.h b/storage/duckdb/ha_duckdb.h index 3933f53a0eb..3f6adf5c609 100644 --- a/storage/duckdb/ha_duckdb.h +++ b/storage/duckdb/ha_duckdb.h @@ -94,6 +94,7 @@ public: return (HA_BINLOG_STMT_CAPABLE | HA_BINLOG_ROW_CAPABLE | HA_NULL_IN_KEY | HA_CAN_INDEX_BLOBS | HA_PRIMARY_KEY_REQUIRED_FOR_DELETE | + HA_PRIMARY_KEY_REQUIRED_FOR_POSITION | HA_CAN_DIRECT_UPDATE_AND_DELETE); } @@ -152,6 +153,7 @@ public: int rnd_end() override; int rnd_next(uchar *buf) override; int rnd_pos(uchar *buf, uchar *pos) override; + int rnd_pos_by_record(uchar *record) override; void position(const uchar *record) override; int info(uint) override; int extra(enum ha_extra_function operation) override; diff --git a/storage/duckdb/mysql-test/duckdb/r/rpl_delete_no_uuid_default.result b/storage/duckdb/mysql-test/duckdb/r/rpl_delete_no_uuid_default.result new file mode 100644 index 00000000000..f562584c5e3 --- /dev/null +++ b/storage/duckdb/mysql-test/duckdb/r/rpl_delete_no_uuid_default.result @@ -0,0 +1,51 @@ +include/master-slave.inc +[connection master] +# MIXED: without a non-deterministic DEFAULT (e.g. UUID()) the table +# is not marked binlog-unsafe, so DELETE stays statement-based and +# replicates correctly (see MDEV-41198 for the unsafe case) +connection master; +CREATE DATABASE rpl_delete_no_uuid_default CHARACTER SET utf8mb4; +USE rpl_delete_no_uuid_default; +SELECT @@binlog_format; +@@binlog_format +MIXED +CREATE TABLE invoice ( +id INT PRIMARY KEY, +product VARCHAR(64), +amount DECIMAL(12,2), +sold_at TIMESTAMP +) ENGINE=DuckDB DEFAULT CHARSET utf8mb4; +INSERT INTO invoice VALUES +(1, 'MariaDB mug', 12.00, NOW()), +(2, 'DuckDB sticker', 2.50, NOW()), +(3, 'Sea lion plush', 25.00, NOW()), +(4, 'DuckDB sticker', 2.50, NOW()); +connection slave; +USE rpl_delete_no_uuid_default; +SELECT COUNT(*) FROM invoice; +COUNT(*) +4 +# DELETE with WHERE (direct delete path on master) +connection master; +DELETE FROM invoice WHERE product = 'DuckDB sticker'; +SELECT COUNT(*) FROM invoice; +COUNT(*) +2 +connection slave; +SELECT COUNT(*) FROM invoice; +COUNT(*) +2 +# DELETE without WHERE (delete_all_rows path on master) +connection master; +DELETE FROM invoice; +SELECT COUNT(*) FROM invoice; +COUNT(*) +0 +connection slave; +SELECT COUNT(*) FROM invoice; +COUNT(*) +0 +connection master; +DROP DATABASE rpl_delete_no_uuid_default; +connection slave; +include/rpl_end.inc diff --git a/storage/duckdb/mysql-test/duckdb/t/mdev_40957_rpl.cnf b/storage/duckdb/mysql-test/duckdb/t/mdev_40957_rpl.cnf new file mode 100644 index 00000000000..1272b862041 --- /dev/null +++ b/storage/duckdb/mysql-test/duckdb/t/mdev_40957_rpl.cnf @@ -0,0 +1,15 @@ +!include suite/rpl/my.cnf + +[mysqld] +plugin-maturity=experimental +plugin-load-add=ha_duckdb.so +duckdb_dml_in_batch=ON +character-set-server=utf8mb4 + +[mysqld.1] +binlog-format=mixed +innodb + +[mysqld.2] +binlog-format=mixed +innodb diff --git a/storage/duckdb/mysql-test/duckdb/t/mdev_40957_rpl.test b/storage/duckdb/mysql-test/duckdb/t/mdev_40957_rpl.test new file mode 100644 index 00000000000..e4edd3041d6 --- /dev/null +++ b/storage/duckdb/mysql-test/duckdb/t/mdev_40957_rpl.test @@ -0,0 +1,73 @@ +--source ../include/have_duckdb.inc +--source include/master-slave.inc + +--echo # MIXED: UUID()/NOW() switch the statements to row events +--connection master +CREATE DATABASE mdev_40957_rpl CHARACTER SET utf8mb4; +USE mdev_40957_rpl; +SELECT @@binlog_format; +CREATE TABLE invoice ( + id UUID DEFAULT UUID() PRIMARY KEY, + product VARCHAR(64), + amount DECIMAL(12,2), + sold_at TIMESTAMP +) ENGINE=DuckDB DEFAULT CHARSET utf8mb4; +INSERT INTO invoice (product, amount, sold_at) VALUES + ('MariaDB mug', 12.00, NOW()), + ('DuckDB sticker', 2.50, NOW()), + ('Sea lion plush', 25.00, NOW()), + ('DuckDB sticker', 2.50, NOW()); + +--sync_slave_with_master +USE mdev_40957_rpl; +SELECT COUNT(*) FROM invoice; + +--connection master +DELETE FROM invoice; +SELECT COUNT(*) FROM invoice; + +--sync_slave_with_master +SELECT COUNT(*) FROM invoice; + +--echo # ROW: replicated UPDATE (including a PK change) and DELETE +--connection master +SET SESSION binlog_format=ROW; +CREATE TABLE t_upd (id INT PRIMARY KEY, v VARCHAR(16)) ENGINE=DuckDB; +INSERT INTO t_upd VALUES (1, 'one'), (2, 'two'), (3, 'three'); +# WHERE on a non-indexed column: an indexed predicate would need +# index_read_map(), which the engine does not implement yet +UPDATE t_upd SET v = 'TWO' WHERE v = 'two'; +UPDATE t_upd SET id = 30, v = 'moved' WHERE v = 'three'; +DELETE FROM t_upd WHERE v = 'one'; +SELECT * FROM t_upd ORDER BY id; + +--sync_slave_with_master +SELECT * FROM t_upd ORDER BY id; + +--connection master +DELETE FROM t_upd; + +--sync_slave_with_master +SELECT COUNT(*) FROM t_upd; + +--echo # MINIMAL row image: replicated UPDATE must fail instead of +--echo # destroying the columns missing from the after-image +--connection master +SET SESSION binlog_row_image=MINIMAL; +INSERT INTO t_upd VALUES (1, 'one'); +UPDATE t_upd SET v = 'ONE' WHERE v = 'one'; + +--connection slave +--let $slave_sql_errno= 1296 +--source include/wait_for_slave_sql_error.inc +--source include/stop_slave_io.inc +RESET SLAVE; +DROP DATABASE mdev_40957_rpl; + +--connection master +SET SESSION binlog_row_image=FULL; +SET SESSION binlog_format=MIXED; +DROP DATABASE mdev_40957_rpl; + +--let $rpl_only_running_threads= 1 +--source include/rpl_end.inc diff --git a/storage/duckdb/mysql-test/duckdb/t/rpl_delete_no_uuid_default.cnf b/storage/duckdb/mysql-test/duckdb/t/rpl_delete_no_uuid_default.cnf new file mode 100644 index 00000000000..1272b862041 --- /dev/null +++ b/storage/duckdb/mysql-test/duckdb/t/rpl_delete_no_uuid_default.cnf @@ -0,0 +1,15 @@ +!include suite/rpl/my.cnf + +[mysqld] +plugin-maturity=experimental +plugin-load-add=ha_duckdb.so +duckdb_dml_in_batch=ON +character-set-server=utf8mb4 + +[mysqld.1] +binlog-format=mixed +innodb + +[mysqld.2] +binlog-format=mixed +innodb diff --git a/storage/duckdb/mysql-test/duckdb/t/rpl_delete_no_uuid_default.test b/storage/duckdb/mysql-test/duckdb/t/rpl_delete_no_uuid_default.test new file mode 100644 index 00000000000..9c213a45d8c --- /dev/null +++ b/storage/duckdb/mysql-test/duckdb/t/rpl_delete_no_uuid_default.test @@ -0,0 +1,49 @@ +--source ../include/have_duckdb.inc +--source include/have_binlog_format_mixed.inc +--source include/master-slave.inc + +--echo # MIXED: without a non-deterministic DEFAULT (e.g. UUID()) the table +--echo # is not marked binlog-unsafe, so DELETE stays statement-based and +--echo # replicates correctly (see MDEV-41198 for the unsafe case) +--connection master +CREATE DATABASE rpl_delete_no_uuid_default CHARACTER SET utf8mb4; +USE rpl_delete_no_uuid_default; +SELECT @@binlog_format; +CREATE TABLE invoice ( + id INT PRIMARY KEY, + product VARCHAR(64), + amount DECIMAL(12,2), + sold_at TIMESTAMP +) ENGINE=DuckDB DEFAULT CHARSET utf8mb4; +INSERT INTO invoice VALUES + (1, 'MariaDB mug', 12.00, NOW()), + (2, 'DuckDB sticker', 2.50, NOW()), + (3, 'Sea lion plush', 25.00, NOW()), + (4, 'DuckDB sticker', 2.50, NOW()); + +--sync_slave_with_master +USE rpl_delete_no_uuid_default; +SELECT COUNT(*) FROM invoice; + +--echo # DELETE with WHERE (direct delete path on master) +--connection master +DELETE FROM invoice WHERE product = 'DuckDB sticker'; +SELECT COUNT(*) FROM invoice; + +--sync_slave_with_master +SELECT COUNT(*) FROM invoice; + +--echo # DELETE without WHERE (delete_all_rows path on master) +--connection master +DELETE FROM invoice; +SELECT COUNT(*) FROM invoice; + +--sync_slave_with_master +SELECT COUNT(*) FROM invoice; + +--connection master +DROP DATABASE rpl_delete_no_uuid_default; + +--sync_slave_with_master + +--source include/rpl_end.inc diff --git a/storage/duckdb/runtime/duckdb_context.cc b/storage/duckdb/runtime/duckdb_context.cc index 03d8da01ddc..d338e77ff17 100644 --- a/storage/duckdb/runtime/duckdb_context.cc +++ b/storage/duckdb/runtime/duckdb_context.cc @@ -180,6 +180,11 @@ int DuckdbThdContext::append_row_delete(TABLE *table) return delta ? delta->append_row_delete(table, 0) : HA_DUCKDB_APPEND_ERROR; } +bool thd_is_replication_applier(THD *thd) +{ + return thd->rgi_slave != nullptr; +} + bool reject_xa_if_active(THD *thd) { if (!thd->transaction->xid_state.is_explicit_XA()) diff --git a/storage/duckdb/runtime/duckdb_context.h b/storage/duckdb/runtime/duckdb_context.h index 621fa6f8213..732b3941993 100644 --- a/storage/duckdb/runtime/duckdb_context.h +++ b/storage/duckdb/runtime/duckdb_context.h @@ -178,4 +178,7 @@ private: transaction. DuckDB does not support XA. */ bool reject_xa_if_active(THD *thd); +/** Return true when THD is a replication applier thread. */ +bool thd_is_replication_applier(THD *thd); + } // namespace myduck From 189298bd2a36d1e14c076339404563ec04bd315c Mon Sep 17 00:00:00 2001 From: drrtuy Date: Fri, 18 Sep 2026 13:06:22 +0100 Subject: [PATCH 67/71] MDEV-40957 mixed-mode replication UPDATE/DELETE for DuckDB. --- storage/duckdb/ha_duckdb.cc | 16 ----- .../mysql-test/duckdb/r/mdev_40957_rpl.result | 72 +++++++++++++++++++ .../duckdb/t/mdev_40957_rpl-master.opt | 1 + .../duckdb/t/mdev_40957_rpl-slave.opt | 1 + .../mysql-test/duckdb/t/mdev_40957_rpl.test | 15 ++-- .../t/rpl_delete_no_uuid_default-master.opt | 1 + .../t/rpl_delete_no_uuid_default-slave.opt | 1 + 7 files changed, 82 insertions(+), 25 deletions(-) create mode 100644 storage/duckdb/mysql-test/duckdb/r/mdev_40957_rpl.result create mode 100644 storage/duckdb/mysql-test/duckdb/t/mdev_40957_rpl-master.opt create mode 100644 storage/duckdb/mysql-test/duckdb/t/mdev_40957_rpl-slave.opt create mode 100644 storage/duckdb/mysql-test/duckdb/t/rpl_delete_no_uuid_default-master.opt create mode 100644 storage/duckdb/mysql-test/duckdb/t/rpl_delete_no_uuid_default-slave.opt diff --git a/storage/duckdb/ha_duckdb.cc b/storage/duckdb/ha_duckdb.cc index b5942a05394..e8a4a66de22 100644 --- a/storage/duckdb/ha_duckdb.cc +++ b/storage/duckdb/ha_duckdb.cc @@ -709,22 +709,6 @@ int ha_duckdb::update_row(const uchar *old_row, const uchar *new_row) } else { - /* - The batched update is replayed as delete + insert of the complete - new row. A replicated after-image is complete only with - binlog_row_image=FULL; the columns missing from a partial image - hold defaults, not the actual values. - */ - if (myduck::thd_is_replication_applier(thd) && - !bitmap_is_set_all(table->write_set)) - { - sql_print_warning("DuckDB: 'binlog_row_image' is not set to 'FULL', " - "replicated UPDATE replay is not possible!"); - my_error(ER_GET_ERRMSG, MYF(0), HA_DUCKDB_DML_ERROR, - "'binlog_row_image' is not set to 'FULL'", "DuckDB"); - DBUG_RETURN(HA_DUCKDB_DML_ERROR); - } - auto *ctx= get_duckdb_context(thd); ret= ctx->append_row_update(table, old_row); if (ret == 0) diff --git a/storage/duckdb/mysql-test/duckdb/r/mdev_40957_rpl.result b/storage/duckdb/mysql-test/duckdb/r/mdev_40957_rpl.result new file mode 100644 index 00000000000..3f0c9b9540a --- /dev/null +++ b/storage/duckdb/mysql-test/duckdb/r/mdev_40957_rpl.result @@ -0,0 +1,72 @@ +include/master-slave.inc +[connection master] +# MIXED: UUID()/NOW() switch the statements to row events +connection master; +CREATE DATABASE mdev_40957_rpl CHARACTER SET utf8mb4; +USE mdev_40957_rpl; +SELECT @@binlog_format; +@@binlog_format +MIXED +CREATE TABLE invoice ( +id UUID DEFAULT UUID() PRIMARY KEY, +product VARCHAR(64), +amount DECIMAL(12,2), +sold_at TIMESTAMP +) ENGINE=DuckDB DEFAULT CHARSET utf8mb4; +INSERT INTO invoice (product, amount, sold_at) VALUES +('MariaDB mug', 12.00, NOW()), +('DuckDB sticker', 2.50, NOW()), +('Sea lion plush', 25.00, NOW()), +('DuckDB sticker', 2.50, NOW()); +connection slave; +USE mdev_40957_rpl; +SELECT COUNT(*) FROM invoice; +COUNT(*) +4 +connection master; +DELETE FROM invoice; +SELECT COUNT(*) FROM invoice; +COUNT(*) +0 +connection slave; +SELECT COUNT(*) FROM invoice; +COUNT(*) +0 +# ROW: replicated UPDATE (including a PK change) and DELETE +connection master; +SET SESSION binlog_format=ROW; +CREATE TABLE t_upd (id INT PRIMARY KEY, v VARCHAR(16)) ENGINE=DuckDB; +INSERT INTO t_upd VALUES (1, 'one'), (2, 'two'), (3, 'three'); +UPDATE t_upd SET v = 'TWO' WHERE v = 'two'; +UPDATE t_upd SET id = 30, v = 'moved' WHERE v = 'three'; +DELETE FROM t_upd WHERE v = 'one'; +SELECT * FROM t_upd ORDER BY id; +id v +2 TWO +30 moved +connection slave; +SELECT * FROM t_upd ORDER BY id; +id v +2 TWO +30 moved +connection master; +DELETE FROM t_upd; +connection slave; +SELECT COUNT(*) FROM t_upd; +COUNT(*) +0 +# MINIMAL row image: replicated UPDATE preserves unchanged columns +connection master; +SET SESSION binlog_row_image=MINIMAL; +INSERT INTO t_upd VALUES (1, 'one'); +UPDATE t_upd SET v = 'ONE' WHERE v = 'one'; +connection slave; +SELECT * FROM t_upd; +id v +1 ONE +connection master; +SET SESSION binlog_row_image=FULL; +SET SESSION binlog_format=MIXED; +DROP DATABASE mdev_40957_rpl; +connection slave; +include/rpl_end.inc diff --git a/storage/duckdb/mysql-test/duckdb/t/mdev_40957_rpl-master.opt b/storage/duckdb/mysql-test/duckdb/t/mdev_40957_rpl-master.opt new file mode 100644 index 00000000000..01d5bd318cb --- /dev/null +++ b/storage/duckdb/mysql-test/duckdb/t/mdev_40957_rpl-master.opt @@ -0,0 +1 @@ +--duckdb_dml_in_batch=ON diff --git a/storage/duckdb/mysql-test/duckdb/t/mdev_40957_rpl-slave.opt b/storage/duckdb/mysql-test/duckdb/t/mdev_40957_rpl-slave.opt new file mode 100644 index 00000000000..01d5bd318cb --- /dev/null +++ b/storage/duckdb/mysql-test/duckdb/t/mdev_40957_rpl-slave.opt @@ -0,0 +1 @@ +--duckdb_dml_in_batch=ON diff --git a/storage/duckdb/mysql-test/duckdb/t/mdev_40957_rpl.test b/storage/duckdb/mysql-test/duckdb/t/mdev_40957_rpl.test index e4edd3041d6..61e3ad6953b 100644 --- a/storage/duckdb/mysql-test/duckdb/t/mdev_40957_rpl.test +++ b/storage/duckdb/mysql-test/duckdb/t/mdev_40957_rpl.test @@ -1,4 +1,5 @@ --source ../include/have_duckdb.inc +--source include/have_binlog_format_mixed.inc --source include/master-slave.inc --echo # MIXED: UUID()/NOW() switch the statements to row events @@ -50,24 +51,20 @@ DELETE FROM t_upd; --sync_slave_with_master SELECT COUNT(*) FROM t_upd; ---echo # MINIMAL row image: replicated UPDATE must fail instead of ---echo # destroying the columns missing from the after-image +--echo # MINIMAL row image: replicated UPDATE preserves unchanged columns --connection master SET SESSION binlog_row_image=MINIMAL; INSERT INTO t_upd VALUES (1, 'one'); UPDATE t_upd SET v = 'ONE' WHERE v = 'one'; ---connection slave ---let $slave_sql_errno= 1296 ---source include/wait_for_slave_sql_error.inc ---source include/stop_slave_io.inc -RESET SLAVE; -DROP DATABASE mdev_40957_rpl; +--sync_slave_with_master +SELECT * FROM t_upd; --connection master SET SESSION binlog_row_image=FULL; SET SESSION binlog_format=MIXED; DROP DATABASE mdev_40957_rpl; ---let $rpl_only_running_threads= 1 +--sync_slave_with_master + --source include/rpl_end.inc diff --git a/storage/duckdb/mysql-test/duckdb/t/rpl_delete_no_uuid_default-master.opt b/storage/duckdb/mysql-test/duckdb/t/rpl_delete_no_uuid_default-master.opt new file mode 100644 index 00000000000..01d5bd318cb --- /dev/null +++ b/storage/duckdb/mysql-test/duckdb/t/rpl_delete_no_uuid_default-master.opt @@ -0,0 +1 @@ +--duckdb_dml_in_batch=ON diff --git a/storage/duckdb/mysql-test/duckdb/t/rpl_delete_no_uuid_default-slave.opt b/storage/duckdb/mysql-test/duckdb/t/rpl_delete_no_uuid_default-slave.opt new file mode 100644 index 00000000000..01d5bd318cb --- /dev/null +++ b/storage/duckdb/mysql-test/duckdb/t/rpl_delete_no_uuid_default-slave.opt @@ -0,0 +1 @@ +--duckdb_dml_in_batch=ON From 8dddd70215a68e26117178f730751a2e141ab9f8 Mon Sep 17 00:00:00 2001 From: drrtuy Date: Fri, 18 Sep 2026 18:52:03 +0100 Subject: [PATCH 68/71] MDEV-40957 MTR tests to cover DML statements in a replicated setup. --- storage/duckdb/ha_duckdb.cc | 66 ++++++++++++++++--- .../mysql-test/duckdb/r/mdev_40957_rpl.result | 62 +++++++++++++---- .../duckdb/mysql-test/duckdb/r/rpl_dml.result | 47 +++++++++++++ .../mysql-test/duckdb/t/mdev_40957_rpl.test | 49 ++++++++++++-- .../mysql-test/duckdb/t/rpl_dml-master.opt | 1 + .../mysql-test/duckdb/t/rpl_dml-slave.opt | 1 + .../duckdb/mysql-test/duckdb/t/rpl_dml.cnf | 13 ++++ .../duckdb/mysql-test/duckdb/t/rpl_dml.test | 45 +++++++++++++ 8 files changed, 257 insertions(+), 27 deletions(-) create mode 100644 storage/duckdb/mysql-test/duckdb/r/rpl_dml.result create mode 100644 storage/duckdb/mysql-test/duckdb/t/rpl_dml-master.opt create mode 100644 storage/duckdb/mysql-test/duckdb/t/rpl_dml-slave.opt create mode 100644 storage/duckdb/mysql-test/duckdb/t/rpl_dml.cnf create mode 100644 storage/duckdb/mysql-test/duckdb/t/rpl_dml.test diff --git a/storage/duckdb/ha_duckdb.cc b/storage/duckdb/ha_duckdb.cc index e8a4a66de22..1f6dc91cf86 100644 --- a/storage/duckdb/ha_duckdb.cc +++ b/storage/duckdb/ha_duckdb.cc @@ -405,8 +405,6 @@ static bool calc_row_difference(const uchar *old_row, const uchar *new_row, } /* check whether PK is modified */ -static bool calc_pk_difference(const uchar *old_row, const uchar *new_row, - TABLE *table) __attribute__((unused)); static bool calc_pk_difference(const uchar *old_row, const uchar *new_row, TABLE *table) { @@ -423,6 +421,23 @@ static bool calc_pk_difference(const uchar *old_row, const uchar *new_row, return false; } +static int execute_idempotent_pk_update(THD *thd, TABLE *table, + const uchar *old_row) +{ + DeleteConvertor delete_old(table, old_row); + int ret= execute_dml(thd, &delete_old); + if (ret) + return ret; + + DeleteConvertor delete_new(table); + ret= execute_dml(thd, &delete_new); + if (ret) + return ret; + + InsertConvertor insert_new(table, true); + return execute_dml(thd, &insert_new); +} + static int get_batch_state(THD *thd, bool insert_only, myduck::BatchState *batch_state) { @@ -683,29 +698,57 @@ int ha_duckdb::update_row(const uchar *old_row, const uchar *new_row) DBUG_ENTER("ha_duckdb::update_row"); int ret= 0; THD *thd= ha_thd(); + bool replication_applier= myduck::thd_is_replication_applier(thd); + bool pk_modified= calc_pk_difference(old_row, new_row, table); ret= duckdb_register_trx(thd); if (ret) DBUG_RETURN(ret); + if (!replication_applier && pk_modified) + table->rpl_write_set= &table->s->all_set; + myduck::BatchState batch_state; ret= get_batch_state(thd, false, &batch_state); if (ret) DBUG_RETURN(ret); - if (batch_state == myduck::BatchState::NOT_IN_BATCH) + if (replication_applier && !pk_modified) { - if (update_modified_column_only && - calc_row_difference(old_row, new_row, table)) + auto *ctx= get_duckdb_context(thd); + std::string error_msg; + if (ctx->flush_appenders(error_msg)) { - bitmap_copy(table->write_set, &table->tmp_set); + my_error(ER_GET_ERRMSG, MYF(0), HA_DUCKDB_APPEND_ERROR, + error_msg.c_str(), "DuckDB"); + DBUG_RETURN(HA_DUCKDB_APPEND_ERROR); } - bitmap_clear_all(&table->tmp_set); UpdateConvertor update_convertor(table, old_row); ret= execute_dml(thd, &update_convertor); if (ret == 0) srv_duckdb_status.duckdb_rows_update++; + DBUG_RETURN(ret); + } + + if (batch_state == myduck::BatchState::NOT_IN_BATCH) + { + if (replication_applier) + ret= execute_idempotent_pk_update(thd, table, old_row); + else + { + if (update_modified_column_only && + calc_row_difference(old_row, new_row, table)) + { + bitmap_copy(table->write_set, &table->tmp_set); + } + bitmap_clear_all(&table->tmp_set); + + UpdateConvertor update_convertor(table, old_row); + ret= execute_dml(thd, &update_convertor); + } + if (ret == 0) + srv_duckdb_status.duckdb_rows_update++; } else { @@ -716,8 +759,13 @@ int ha_duckdb::update_row(const uchar *old_row, const uchar *new_row) else { ctx->set_batch_state(myduck::BatchState::NOT_IN_BATCH); - UpdateConvertor update_convertor(table, old_row); - ret= execute_dml(thd, &update_convertor); + if (replication_applier) + ret= execute_idempotent_pk_update(thd, table, old_row); + else + { + UpdateConvertor update_convertor(table, old_row); + ret= execute_dml(thd, &update_convertor); + } if (ret == 0) srv_duckdb_status.duckdb_rows_update++; } diff --git a/storage/duckdb/mysql-test/duckdb/r/mdev_40957_rpl.result b/storage/duckdb/mysql-test/duckdb/r/mdev_40957_rpl.result index 3f0c9b9540a..4441d72129f 100644 --- a/storage/duckdb/mysql-test/duckdb/r/mdev_40957_rpl.result +++ b/storage/duckdb/mysql-test/duckdb/r/mdev_40957_rpl.result @@ -35,20 +35,33 @@ COUNT(*) # ROW: replicated UPDATE (including a PK change) and DELETE connection master; SET SESSION binlog_format=ROW; -CREATE TABLE t_upd (id INT PRIMARY KEY, v VARCHAR(16)) ENGINE=DuckDB; -INSERT INTO t_upd VALUES (1, 'one'), (2, 'two'), (3, 'three'); +CREATE TABLE t_upd ( +id INT PRIMARY KEY, +v VARCHAR(16), +untouched VARCHAR(16) +) ENGINE=DuckDB; +INSERT INTO t_upd VALUES +(1, 'one', 'keep-1'), +(2, 'two', 'keep-2'), +(3, 'three', 'keep-3'); +UPDATE t_upd SET v = 'TWO' WHERE id = 2; +ERROR HY000: Storage engine DUCKDB of the table `mdev_40957_rpl`.`t_upd` doesn't have this option +UPDATE t_upd SET id = 30, v = 'moved' WHERE id = 3; +ERROR HY000: Storage engine DUCKDB of the table `mdev_40957_rpl`.`t_upd` doesn't have this option +DELETE FROM t_upd WHERE id = 1; +ERROR HY000: Storage engine DUCKDB of the table `mdev_40957_rpl`.`t_upd` doesn't have this option UPDATE t_upd SET v = 'TWO' WHERE v = 'two'; UPDATE t_upd SET id = 30, v = 'moved' WHERE v = 'three'; DELETE FROM t_upd WHERE v = 'one'; SELECT * FROM t_upd ORDER BY id; -id v -2 TWO -30 moved +id v untouched +2 TWO keep-2 +30 moved keep-3 connection slave; SELECT * FROM t_upd ORDER BY id; -id v -2 TWO -30 moved +id v untouched +2 TWO keep-2 +30 moved keep-3 connection master; DELETE FROM t_upd; connection slave; @@ -57,14 +70,39 @@ COUNT(*) 0 # MINIMAL row image: replicated UPDATE preserves unchanged columns connection master; +SET GLOBAL duckdb_dml_in_batch=OFF; SET SESSION binlog_row_image=MINIMAL; -INSERT INTO t_upd VALUES (1, 'one'); +INSERT INTO t_upd VALUES +(1, 'one', 'must-survive'), +(2, 'other', 'must-not-leak'); UPDATE t_upd SET v = 'ONE' WHERE v = 'one'; connection slave; -SELECT * FROM t_upd; -id v -1 ONE +SELECT * FROM t_upd WHERE id = 1; +id v untouched +1 ONE must-survive connection master; +UPDATE t_upd SET id = 20 WHERE v = 'other'; +connection slave; +SELECT * FROM t_upd ORDER BY id; +id v untouched +1 ONE must-survive +20 other must-not-leak +UPDATE t_upd SET v = 'corrupt', untouched = 'corrupt' WHERE v = 'other'; +SELECT * FROM t_upd ORDER BY id; +id v untouched +1 ONE must-survive +20 corrupt corrupt +include/stop_slave.inc +CHANGE MASTER TO MASTER_LOG_FILE='REPLAY_FILE', MASTER_LOG_POS=REPLAY_POS, MASTER_USE_GTID=NO; +include/start_slave.inc +connection master; +connection slave; +SELECT * FROM t_upd ORDER BY id; +id v untouched +1 ONE must-survive +20 other must-not-leak +connection master; +SET GLOBAL duckdb_dml_in_batch=ON; SET SESSION binlog_row_image=FULL; SET SESSION binlog_format=MIXED; DROP DATABASE mdev_40957_rpl; diff --git a/storage/duckdb/mysql-test/duckdb/r/rpl_dml.result b/storage/duckdb/mysql-test/duckdb/r/rpl_dml.result new file mode 100644 index 00000000000..3b2217fd369 --- /dev/null +++ b/storage/duckdb/mysql-test/duckdb/r/rpl_dml.result @@ -0,0 +1,47 @@ +include/master-slave.inc +[connection master] +connection master; +CREATE DATABASE rpl_dml CHARACTER SET utf8mb4; +USE rpl_dml; +CREATE TABLE t1 ( +id INT PRIMARY KEY, +v VARCHAR(16), +untouched VARCHAR(16) +) ENGINE=DuckDB; +INSERT INTO t1 VALUES +(1, 'one', 'keep-1'), +(2, 'two', 'keep-2'), +(3, 'three', 'keep-3'); +connection slave; +USE rpl_dml; +SELECT * FROM t1 ORDER BY id; +id v untouched +1 one keep-1 +2 two keep-2 +3 three keep-3 +connection master; +UPDATE t1 SET v = 'TWO' WHERE v = 'two'; +UPDATE t1 SET id = 30, v = 'moved' WHERE v = 'three'; +connection slave; +SELECT * FROM t1 ORDER BY id; +id v untouched +1 one keep-1 +2 TWO keep-2 +30 moved keep-3 +connection master; +DELETE FROM t1 WHERE v = 'one'; +connection slave; +SELECT * FROM t1 ORDER BY id; +id v untouched +2 TWO keep-2 +30 moved keep-3 +connection master; +DELETE FROM t1; +connection slave; +SELECT COUNT(*) FROM t1; +COUNT(*) +0 +connection master; +DROP DATABASE rpl_dml; +connection slave; +include/rpl_end.inc diff --git a/storage/duckdb/mysql-test/duckdb/t/mdev_40957_rpl.test b/storage/duckdb/mysql-test/duckdb/t/mdev_40957_rpl.test index 61e3ad6953b..6ae4bf37f21 100644 --- a/storage/duckdb/mysql-test/duckdb/t/mdev_40957_rpl.test +++ b/storage/duckdb/mysql-test/duckdb/t/mdev_40957_rpl.test @@ -33,10 +33,24 @@ SELECT COUNT(*) FROM invoice; --echo # ROW: replicated UPDATE (including a PK change) and DELETE --connection master SET SESSION binlog_format=ROW; -CREATE TABLE t_upd (id INT PRIMARY KEY, v VARCHAR(16)) ENGINE=DuckDB; -INSERT INTO t_upd VALUES (1, 'one'), (2, 'two'), (3, 'three'); -# WHERE on a non-indexed column: an indexed predicate would need -# index_read_map(), which the engine does not implement yet +CREATE TABLE t_upd ( + id INT PRIMARY KEY, + v VARCHAR(16), + untouched VARCHAR(16) +) ENGINE=DuckDB; +INSERT INTO t_upd VALUES + (1, 'one', 'keep-1'), + (2, 'two', 'keep-2'), + (3, 'three', 'keep-3'); +# Indexed predicates fail while metadata-only DuckDB keys are considered +# by the range optimizer (MDEV-41223) +--error ER_ILLEGAL_HA +UPDATE t_upd SET v = 'TWO' WHERE id = 2; +--error ER_ILLEGAL_HA +UPDATE t_upd SET id = 30, v = 'moved' WHERE id = 3; +--error ER_ILLEGAL_HA +DELETE FROM t_upd WHERE id = 1; +# Continue replication coverage through table scans UPDATE t_upd SET v = 'TWO' WHERE v = 'two'; UPDATE t_upd SET id = 30, v = 'moved' WHERE v = 'three'; DELETE FROM t_upd WHERE v = 'one'; @@ -53,14 +67,37 @@ SELECT COUNT(*) FROM t_upd; --echo # MINIMAL row image: replicated UPDATE preserves unchanged columns --connection master +SET GLOBAL duckdb_dml_in_batch=OFF; SET SESSION binlog_row_image=MINIMAL; -INSERT INTO t_upd VALUES (1, 'one'); +INSERT INTO t_upd VALUES + (1, 'one', 'must-survive'), + (2, 'other', 'must-not-leak'); UPDATE t_upd SET v = 'ONE' WHERE v = 'one'; --sync_slave_with_master -SELECT * FROM t_upd; +SELECT * FROM t_upd WHERE id = 1; --connection master +--let $replay_file= query_get_value(SHOW MASTER STATUS, File, 1) +--let $replay_pos= query_get_value(SHOW MASTER STATUS, Position, 1) +UPDATE t_upd SET id = 20 WHERE v = 'other'; + +--sync_slave_with_master +SELECT * FROM t_upd ORDER BY id; +UPDATE t_upd SET v = 'corrupt', untouched = 'corrupt' WHERE v = 'other'; +SELECT * FROM t_upd ORDER BY id; + +--source include/stop_slave.inc +--replace_result $replay_file REPLAY_FILE $replay_pos REPLAY_POS +--eval CHANGE MASTER TO MASTER_LOG_FILE='$replay_file', MASTER_LOG_POS=$replay_pos, MASTER_USE_GTID=NO +--source include/start_slave.inc + +--connection master +--sync_slave_with_master +SELECT * FROM t_upd ORDER BY id; + +--connection master +SET GLOBAL duckdb_dml_in_batch=ON; SET SESSION binlog_row_image=FULL; SET SESSION binlog_format=MIXED; DROP DATABASE mdev_40957_rpl; diff --git a/storage/duckdb/mysql-test/duckdb/t/rpl_dml-master.opt b/storage/duckdb/mysql-test/duckdb/t/rpl_dml-master.opt new file mode 100644 index 00000000000..01d5bd318cb --- /dev/null +++ b/storage/duckdb/mysql-test/duckdb/t/rpl_dml-master.opt @@ -0,0 +1 @@ +--duckdb_dml_in_batch=ON diff --git a/storage/duckdb/mysql-test/duckdb/t/rpl_dml-slave.opt b/storage/duckdb/mysql-test/duckdb/t/rpl_dml-slave.opt new file mode 100644 index 00000000000..01d5bd318cb --- /dev/null +++ b/storage/duckdb/mysql-test/duckdb/t/rpl_dml-slave.opt @@ -0,0 +1 @@ +--duckdb_dml_in_batch=ON diff --git a/storage/duckdb/mysql-test/duckdb/t/rpl_dml.cnf b/storage/duckdb/mysql-test/duckdb/t/rpl_dml.cnf new file mode 100644 index 00000000000..a44a003840a --- /dev/null +++ b/storage/duckdb/mysql-test/duckdb/t/rpl_dml.cnf @@ -0,0 +1,13 @@ +!include suite/rpl/my.cnf + +[mysqld] +plugin-maturity=experimental +plugin-load-add=ha_duckdb.so +duckdb_dml_in_batch=ON +character-set-server=utf8mb4 + +[mysqld.1] +innodb + +[mysqld.2] +innodb diff --git a/storage/duckdb/mysql-test/duckdb/t/rpl_dml.test b/storage/duckdb/mysql-test/duckdb/t/rpl_dml.test new file mode 100644 index 00000000000..47b33cb44b4 --- /dev/null +++ b/storage/duckdb/mysql-test/duckdb/t/rpl_dml.test @@ -0,0 +1,45 @@ +--source ../include/have_duckdb.inc +--source include/master-slave.inc + +--connection master +CREATE DATABASE rpl_dml CHARACTER SET utf8mb4; +USE rpl_dml; +CREATE TABLE t1 ( + id INT PRIMARY KEY, + v VARCHAR(16), + untouched VARCHAR(16) +) ENGINE=DuckDB; +INSERT INTO t1 VALUES + (1, 'one', 'keep-1'), + (2, 'two', 'keep-2'), + (3, 'three', 'keep-3'); + +--sync_slave_with_master +USE rpl_dml; +SELECT * FROM t1 ORDER BY id; + +--connection master +UPDATE t1 SET v = 'TWO' WHERE v = 'two'; +UPDATE t1 SET id = 30, v = 'moved' WHERE v = 'three'; + +--sync_slave_with_master +SELECT * FROM t1 ORDER BY id; + +--connection master +DELETE FROM t1 WHERE v = 'one'; + +--sync_slave_with_master +SELECT * FROM t1 ORDER BY id; + +--connection master +DELETE FROM t1; + +--sync_slave_with_master +SELECT COUNT(*) FROM t1; + +--connection master +DROP DATABASE rpl_dml; + +--sync_slave_with_master + +--source include/rpl_end.inc From 34e3dc803852a36d41c471af9048563c73070772 Mon Sep 17 00:00:00 2001 From: drrtuy Date: Mon, 21 Sep 2026 15:49:27 +0100 Subject: [PATCH 69/71] MDEV-40957 DuckDB engine now returns maximum cost for unimplemented index handler operations effectively disabling them. --- storage/duckdb/ha_duckdb.h | 8 ++----- .../duckdb/r/cross_engine_join.result | 17 ++++++++++---- .../duckdb/r/cross_engine_ryow.result | 17 +++++++++----- .../mysql-test/duckdb/r/dml_delete.result | 7 ++---- .../mysql-test/duckdb/r/mdev_40957_rpl.result | 22 +++++++++++++------ .../duckdb/t/cross_engine_join.test | 7 +++--- .../duckdb/t/cross_engine_ryow.test | 9 +++----- .../mysql-test/duckdb/t/dml_delete.test | 5 ++--- .../mysql-test/duckdb/t/mdev_40957_rpl.test | 21 +++++++++--------- 9 files changed, 62 insertions(+), 51 deletions(-) diff --git a/storage/duckdb/ha_duckdb.h b/storage/duckdb/ha_duckdb.h index 3f6adf5c609..cfccb7b31b7 100644 --- a/storage/duckdb/ha_duckdb.h +++ b/storage/duckdb/ha_duckdb.h @@ -122,13 +122,9 @@ public: return cost; } - IO_AND_CPU_COST keyread_time(uint, ulong, ha_rows rows, - ulonglong blocks) override + IO_AND_CPU_COST keyread_time(uint, ulong, ha_rows, ulonglong) override { - IO_AND_CPU_COST cost; - cost.io= blocks * DISK_READ_COST; - cost.cpu= (double) rows * 0.001; - return cost; + return {DBL_MAX, DBL_MAX}; } /* Methods implemented in ha_duckdb.cc */ diff --git a/storage/duckdb/mysql-test/duckdb/r/cross_engine_join.result b/storage/duckdb/mysql-test/duckdb/r/cross_engine_join.result index 359ad8e11d4..61c25a7708a 100644 --- a/storage/duckdb/mysql-test/duckdb/r/cross_engine_join.result +++ b/storage/duckdb/mysql-test/duckdb/r/cross_engine_join.result @@ -146,7 +146,13 @@ hidden INT INVISIBLE INSERT INTO t_trailing_invisible (id, hidden) VALUES (1, 10); SELECT d.id, i.hidden FROM t_duck d JOIN t_trailing_invisible i ON d.id=i.id; -ERROR HY000: Storage engine DUCKDB of the table `cross_engine_join`.`t_duck` doesn't have this option +id hidden +1 10 +SELECT d.id, i.hidden +FROM t_duck AS d FORCE INDEX(PRIMARY) +JOIN t_trailing_invisible i ON d.id=i.id; +id hidden +1 10 # (11) Non-trailing invisible column uses MariaDB fallback @@ -158,7 +164,8 @@ val INT INSERT INTO t_nontrailing_invisible (id, hidden, val) VALUES (1, 10, 100); SELECT d.id, i.val FROM t_duck d JOIN t_nontrailing_invisible i ON d.id=i.id; -ERROR HY000: Storage engine DUCKDB of the table `cross_engine_join`.`t_duck` doesn't have this option +id val +1 100 # (12) Explicit FOR SYSTEM_TIME uses MariaDB fallback @@ -171,10 +178,12 @@ ERROR HY000: Storage engine DUCKDB of the table `cross_engine_join`.`t_duck` doe SELECT ROW_NUMBER() OVER (PARTITION BY i.hidden) FROM t_duck d JOIN t_trailing_invisible i ON d.id=i.id; -ERROR HY000: Storage engine DUCKDB of the table `cross_engine_join`.`t_duck` doesn't have this option +ROW_NUMBER() OVER (PARTITION BY i.hidden) +1 SELECT ROW_NUMBER() OVER (ORDER BY i.hidden) FROM t_duck d JOIN t_trailing_invisible i ON d.id=i.id; -ERROR HY000: Storage engine DUCKDB of the table `cross_engine_join`.`t_duck` doesn't have this option +ROW_NUMBER() OVER (ORDER BY i.hidden) +1 # Cleanup diff --git a/storage/duckdb/mysql-test/duckdb/r/cross_engine_ryow.result b/storage/duckdb/mysql-test/duckdb/r/cross_engine_ryow.result index a8156bb157c..7f2a073a920 100644 --- a/storage/duckdb/mysql-test/duckdb/r/cross_engine_ryow.result +++ b/storage/duckdb/mysql-test/duckdb/r/cross_engine_ryow.result @@ -73,9 +73,10 @@ ENGINE=InnoDB WITH SYSTEM VERSIONING; INSERT INTO t_versioned VALUES (1, 10); SET SESSION duckdb_cross_engine_ryow=1; SELECT d.id, i.score FROM t_duck d JOIN t_versioned i ON d.id=i.id; -ERROR HY000: Storage engine DUCKDB of the table `cross_engine_ryow`.`t_duck` doesn't have this option +id score +1 10 -# (6) A trailing invisible column is supported only without RYOW +# (6) A trailing invisible column uses pushdown only without RYOW CREATE TABLE t_trailing_invisible ( id INT PRIMARY KEY, @@ -91,10 +92,12 @@ id score SET SESSION duckdb_cross_engine_ryow=1; SELECT d.id, i.score FROM t_duck d JOIN t_trailing_invisible i ON d.id=i.id; -ERROR HY000: Storage engine DUCKDB of the table `cross_engine_ryow`.`t_duck` doesn't have this option +id score +1 10 SELECT d.id, i.hidden FROM t_duck d JOIN t_trailing_invisible i ON d.id=i.id; -ERROR HY000: Storage engine DUCKDB of the table `cross_engine_ryow`.`t_duck` doesn't have this option +id hidden +1 100 # (7) A non-trailing invisible column always uses handler fallback @@ -105,8 +108,10 @@ score INT ) ENGINE=InnoDB; INSERT INTO t_nontrailing_invisible (id, hidden, score) VALUES (1, 100, 10); SELECT d.id, i.score -FROM t_duck d JOIN t_nontrailing_invisible i ON d.id=i.id; -ERROR HY000: Storage engine DUCKDB of the table `cross_engine_ryow`.`t_duck` doesn't have this option +FROM t_duck AS d FORCE INDEX(PRIMARY) +JOIN t_nontrailing_invisible i ON d.id=i.id; +id score +1 10 # Cleanup diff --git a/storage/duckdb/mysql-test/duckdb/r/dml_delete.result b/storage/duckdb/mysql-test/duckdb/r/dml_delete.result index 7a7529dd24f..72c9ef0ec97 100644 --- a/storage/duckdb/mysql-test/duckdb/r/dml_delete.result +++ b/storage/duckdb/mysql-test/duckdb/r/dml_delete.result @@ -53,15 +53,12 @@ INSERT INTO t1 VALUES (2, 'two'); INSERT INTO t1 VALUES (3, 'three'); INSERT INTO t1 VALUES (4, 'four'); INSERT INTO t1 VALUES (5, 'five'); -DELETE FROM t1 WHERE id IN (SELECT id FROM t2); -ERROR HY000: Storage engine DUCKDB of the table `db_dml_delete`.`t2` doesn't have this option +DELETE FROM t1 +WHERE id IN (SELECT id FROM t2 FORCE INDEX(PRIMARY)); SELECT * FROM t1 ORDER BY id; id val -1 one 2 two -3 three 4 four -5 five # Test 6: DELETE in transaction — COMMIT BEGIN; DELETE FROM t1 WHERE id = 2; diff --git a/storage/duckdb/mysql-test/duckdb/r/mdev_40957_rpl.result b/storage/duckdb/mysql-test/duckdb/r/mdev_40957_rpl.result index 4441d72129f..c84865f0d83 100644 --- a/storage/duckdb/mysql-test/duckdb/r/mdev_40957_rpl.result +++ b/storage/duckdb/mysql-test/duckdb/r/mdev_40957_rpl.result @@ -43,25 +43,33 @@ untouched VARCHAR(16) INSERT INTO t_upd VALUES (1, 'one', 'keep-1'), (2, 'two', 'keep-2'), -(3, 'three', 'keep-3'); +(3, 'three', 'keep-3'), +(4, 'four', 'keep-4'), +(6, 'six', 'keep-6'), +(7, 'seven', 'keep-7'), +(8, 'eight', 'keep-8'); UPDATE t_upd SET v = 'TWO' WHERE id = 2; -ERROR HY000: Storage engine DUCKDB of the table `mdev_40957_rpl`.`t_upd` doesn't have this option UPDATE t_upd SET id = 30, v = 'moved' WHERE id = 3; -ERROR HY000: Storage engine DUCKDB of the table `mdev_40957_rpl`.`t_upd` doesn't have this option DELETE FROM t_upd WHERE id = 1; -ERROR HY000: Storage engine DUCKDB of the table `mdev_40957_rpl`.`t_upd` doesn't have this option -UPDATE t_upd SET v = 'TWO' WHERE v = 'two'; -UPDATE t_upd SET id = 30, v = 'moved' WHERE v = 'three'; -DELETE FROM t_upd WHERE v = 'one'; +UPDATE t_upd FORCE INDEX(PRIMARY) SET v = 'FOUR' WHERE id = 4; +UPDATE t_upd SET v = 'SIX' WHERE v = 'six'; +UPDATE t_upd SET id = 70, v = 'moved-non-pk' WHERE v = 'seven'; +DELETE FROM t_upd WHERE v = 'eight'; SELECT * FROM t_upd ORDER BY id; id v untouched 2 TWO keep-2 +4 FOUR keep-4 +6 SIX keep-6 30 moved keep-3 +70 moved-non-pk keep-7 connection slave; SELECT * FROM t_upd ORDER BY id; id v untouched 2 TWO keep-2 +4 FOUR keep-4 +6 SIX keep-6 30 moved keep-3 +70 moved-non-pk keep-7 connection master; DELETE FROM t_upd; connection slave; diff --git a/storage/duckdb/mysql-test/duckdb/t/cross_engine_join.test b/storage/duckdb/mysql-test/duckdb/t/cross_engine_join.test index c4682e87c5c..0806df98e96 100644 --- a/storage/duckdb/mysql-test/duckdb/t/cross_engine_join.test +++ b/storage/duckdb/mysql-test/duckdb/t/cross_engine_join.test @@ -150,9 +150,11 @@ CREATE TABLE t_trailing_invisible ( hidden INT INVISIBLE ) ENGINE=InnoDB; INSERT INTO t_trailing_invisible (id, hidden) VALUES (1, 10); ---error ER_ILLEGAL_HA SELECT d.id, i.hidden FROM t_duck d JOIN t_trailing_invisible i ON d.id=i.id; +SELECT d.id, i.hidden + FROM t_duck AS d FORCE INDEX(PRIMARY) + JOIN t_trailing_invisible i ON d.id=i.id; --echo --echo # (11) Non-trailing invisible column uses MariaDB fallback @@ -164,7 +166,6 @@ CREATE TABLE t_nontrailing_invisible ( val INT ) ENGINE=InnoDB; INSERT INTO t_nontrailing_invisible (id, hidden, val) VALUES (1, 10, 100); ---error ER_ILLEGAL_HA SELECT d.id, i.val FROM t_duck d JOIN t_nontrailing_invisible i ON d.id=i.id; @@ -181,10 +182,8 @@ SELECT d.id, i.c2 --echo # (13) Invisible columns in window specifications use MariaDB fallback --echo ---error ER_ILLEGAL_HA SELECT ROW_NUMBER() OVER (PARTITION BY i.hidden) FROM t_duck d JOIN t_trailing_invisible i ON d.id=i.id; ---error ER_ILLEGAL_HA SELECT ROW_NUMBER() OVER (ORDER BY i.hidden) FROM t_duck d JOIN t_trailing_invisible i ON d.id=i.id; diff --git a/storage/duckdb/mysql-test/duckdb/t/cross_engine_ryow.test b/storage/duckdb/mysql-test/duckdb/t/cross_engine_ryow.test index 753d7ecc126..5ba8e936e3e 100644 --- a/storage/duckdb/mysql-test/duckdb/t/cross_engine_ryow.test +++ b/storage/duckdb/mysql-test/duckdb/t/cross_engine_ryow.test @@ -65,11 +65,10 @@ CREATE TABLE t_versioned (id INT PRIMARY KEY, score INT) ENGINE=InnoDB WITH SYSTEM VERSIONING; INSERT INTO t_versioned VALUES (1, 10); SET SESSION duckdb_cross_engine_ryow=1; ---error ER_ILLEGAL_HA SELECT d.id, i.score FROM t_duck d JOIN t_versioned i ON d.id=i.id; --echo ---echo # (6) A trailing invisible column is supported only without RYOW +--echo # (6) A trailing invisible column uses pushdown only without RYOW --echo CREATE TABLE t_trailing_invisible ( id INT PRIMARY KEY, @@ -81,10 +80,8 @@ SET SESSION duckdb_cross_engine_ryow=0; SELECT d.id, i.score FROM t_duck d JOIN t_trailing_invisible i ON d.id=i.id; SET SESSION duckdb_cross_engine_ryow=1; ---error ER_ILLEGAL_HA SELECT d.id, i.score FROM t_duck d JOIN t_trailing_invisible i ON d.id=i.id; ---error ER_ILLEGAL_HA SELECT d.id, i.hidden FROM t_duck d JOIN t_trailing_invisible i ON d.id=i.id; @@ -97,9 +94,9 @@ CREATE TABLE t_nontrailing_invisible ( score INT ) ENGINE=InnoDB; INSERT INTO t_nontrailing_invisible (id, hidden, score) VALUES (1, 100, 10); ---error ER_ILLEGAL_HA SELECT d.id, i.score - FROM t_duck d JOIN t_nontrailing_invisible i ON d.id=i.id; + FROM t_duck AS d FORCE INDEX(PRIMARY) + JOIN t_nontrailing_invisible i ON d.id=i.id; --echo --echo # Cleanup diff --git a/storage/duckdb/mysql-test/duckdb/t/dml_delete.test b/storage/duckdb/mysql-test/duckdb/t/dml_delete.test index 8fedb60aaed..d822cb86b29 100644 --- a/storage/duckdb/mysql-test/duckdb/t/dml_delete.test +++ b/storage/duckdb/mysql-test/duckdb/t/dml_delete.test @@ -81,9 +81,8 @@ INSERT INTO t1 VALUES (3, 'three'); INSERT INTO t1 VALUES (4, 'four'); INSERT INTO t1 VALUES (5, 'five'); -# Subquery DELETE requires rnd_pos on t2 which DuckDB doesn't support ---error ER_ILLEGAL_HA -DELETE FROM t1 WHERE id IN (SELECT id FROM t2); +DELETE FROM t1 + WHERE id IN (SELECT id FROM t2 FORCE INDEX(PRIMARY)); SELECT * FROM t1 ORDER BY id; # ----------------------------------------------------------------- diff --git a/storage/duckdb/mysql-test/duckdb/t/mdev_40957_rpl.test b/storage/duckdb/mysql-test/duckdb/t/mdev_40957_rpl.test index 6ae4bf37f21..85f6f2cc8b9 100644 --- a/storage/duckdb/mysql-test/duckdb/t/mdev_40957_rpl.test +++ b/storage/duckdb/mysql-test/duckdb/t/mdev_40957_rpl.test @@ -41,19 +41,20 @@ CREATE TABLE t_upd ( INSERT INTO t_upd VALUES (1, 'one', 'keep-1'), (2, 'two', 'keep-2'), - (3, 'three', 'keep-3'); -# Indexed predicates fail while metadata-only DuckDB keys are considered -# by the range optimizer (MDEV-41223) ---error ER_ILLEGAL_HA + (3, 'three', 'keep-3'), + (4, 'four', 'keep-4'), + (6, 'six', 'keep-6'), + (7, 'seven', 'keep-7'), + (8, 'eight', 'keep-8'); +# Metadata-only DuckDB keys must not make the optimizer choose an index scan UPDATE t_upd SET v = 'TWO' WHERE id = 2; ---error ER_ILLEGAL_HA UPDATE t_upd SET id = 30, v = 'moved' WHERE id = 3; ---error ER_ILLEGAL_HA DELETE FROM t_upd WHERE id = 1; -# Continue replication coverage through table scans -UPDATE t_upd SET v = 'TWO' WHERE v = 'two'; -UPDATE t_upd SET id = 30, v = 'moved' WHERE v = 'three'; -DELETE FROM t_upd WHERE v = 'one'; +UPDATE t_upd FORCE INDEX(PRIMARY) SET v = 'FOUR' WHERE id = 4; +# Non-indexed predicates continue to use table scans +UPDATE t_upd SET v = 'SIX' WHERE v = 'six'; +UPDATE t_upd SET id = 70, v = 'moved-non-pk' WHERE v = 'seven'; +DELETE FROM t_upd WHERE v = 'eight'; SELECT * FROM t_upd ORDER BY id; --sync_slave_with_master From 38bc5b954b88bc8004bc1d3a10894250cbcde807 Mon Sep 17 00:00:00 2001 From: drrtuy Date: Sun, 4 Oct 2026 20:59:30 +0100 Subject: [PATCH 70/71] MDEV-40957 replication from InnoDB into DuckDB works using FULL mode only. --- storage/duckdb/README.md | 2 + storage/duckdb/docs/architecture.md | 54 ++++++++++++ storage/duckdb/ha_duckdb.cc | 83 ++++++------------- .../mysql-test/duckdb/r/mdev_40957_rpl.result | 69 ++++++++------- .../mysql-test/duckdb/t/mdev_40957_rpl.test | 70 ++++++++++------ storage/duckdb/runtime/duckdb_context.cc | 5 ++ storage/duckdb/runtime/duckdb_context.h | 3 + 7 files changed, 176 insertions(+), 110 deletions(-) diff --git a/storage/duckdb/README.md b/storage/duckdb/README.md index a65283eb501..f1b85070d9b 100644 --- a/storage/duckdb/README.md +++ b/storage/duckdb/README.md @@ -141,6 +141,8 @@ DuckDB handles the join, aggregation, and sorting; InnoDB rows are produced on d - **Collations** — MariaDB UCA-based collation rules are approximated via DuckDB's built-in `NOCASE`/`NOACCENT` collations for UTF-8 charsets; non-UTF8 charsets fall back to binary comparison. See [`docs/collation-mapping.md`](docs/collation-mapping.md) for the full mapping and known gaps. - **Cross-engine scan is yet single-threaded** — each external (non-DuckDB) table is produced by a single fiber-driven MariaDB query (`_mdb_scan` reports `MaxThreads() == 1`); only the DuckDB side of the query is parallelized. - **ALTER COLUMN DROP DEFAULT** — not propagated to DuckDB catalog. +- **Tables without a primary key** (allowed only with `duckdb_require_primary_key=OFF`) — row-by-row `UPDATE`/`DELETE` (used e.g. with triggers or on a replica) silently skips rows containing NULL values and changes all copies of duplicate rows. See [`docs/architecture.md`](docs/architecture.md#path-3-row-by-row-dml). +- **Row-based replication into DuckDB tables requires `binlog_row_image=FULL` on the master** (the default). A replica rejects `UPDATE` events with a partial after-image (`MINIMAL`, `NOBLOB`, `FULL_NODUP`) instead of filling the missing columns with defaults. The replica does not detect missing or conflicting rows (`slave_exec_mode=STRICT` is not enforced) and is not crash-safe. See [`docs/architecture.md`](docs/architecture.md#path-3a-row-based-replication-apply). - **Timezone propagation** — `TIMESTAMP` columns are stored as `TIMESTAMPTZ`; timezone must be set consistently between MariaDB and DuckDB contexts to avoid shifts. See [`docs/mariadb-duckdb-incompatibilities.md`](docs/mariadb-duckdb-incompatibilities.md) for a detailed compatibility matrix. diff --git a/storage/duckdb/docs/architecture.md b/storage/duckdb/docs/architecture.md index b1bee0a5a8b..da9ee6e0afe 100644 --- a/storage/duckdb/docs/architecture.md +++ b/storage/duckdb/docs/architecture.md @@ -242,6 +242,60 @@ ha_duckdb::write_row() / update_row() / delete_row() → DuckDB Appender API → temp table → flushed at commit ``` +`UpdateConvertor` / `DeleteConvertor` identify the row by the primary key +(`DMLConvertor::fill_index_fields_for_where()` in `convertor/dml_convertor.cc`). +**Tables without a primary key are not reliably supported on this path** +(reached e.g. when the table has triggers, or when a replica applies row +events): the WHERE clause then lists every column, and + +- a NULL value is emitted as `col = NULL`, which never matches, so the row is + not updated or deleted although MariaDB counts it as changed; +- the NULL flag is read from the new row while the value comes from the old + row, so `value → NULL` updates are not applied either; +- exact duplicate rows are all deleted or updated at once. + +Keep `duckdb_require_primary_key=ON` (the default) for such workloads. + +### Path 3a: Row-Based Replication Apply + +A replica applies `Update_rows`/`Delete_rows` events through the same +`update_row()` / `delete_row()` entry points, without reading the row first: + +``` +Rows_log_event::find_row() (sql/log_event_server.cc) + → unpacks the before-image into record[0] + → ha_duckdb::rnd_pos_by_record() // HA_PRIMARY_KEY_REQUIRED_FOR_POSITION + → returns success without a lookup (only for the replication applier) +Update_rows_log_event::do_exec_row() + → unpacks the after-image into record[0] + → ha_duckdb::update_row() + → rejects the event unless write_set covers all columns + → batch: DeltaAppender::append_row_update() (delete marker + full row) + otherwise: UPDATE … WHERE +``` + +Requirements and limitations: + +- **The master must log full row images (`binlog_row_image=FULL`, the + default).** The batch path stores the whole `record[0]` as the new row + version, so columns absent from a `MINIMAL`, `NOBLOB` or `FULL_NODUP` + after-image would be replaced by defaults. `ha_duckdb::update_row()` checks + `write_set`, which `Rows_log_event::do_apply_event()` narrows to the + after-image columns, and fails the event with an error naming + `binlog_row_image=FULL` instead. The requirement applies to the master + table regardless of its engine (InnoDB master → DuckDB replica included). + The same check fires when the replica table has more columns than the + master table. +- **Missing or conflicting rows are not detected.** Because no lookup is done, + a DELETE of a row absent on the replica is a silent no-op; an UPDATE of such + a row is a no-op without batching and inserts the after-image with batching + (the flush deletes by key, then inserts). An UPDATE that changes the primary + key to an existing key replaces that row with batching and leaves a duplicate + key without batching (DuckDB tables carry no primary key constraint). + `slave_exec_mode=STRICT` is therefore not enforced for DuckDB tables. +- **Not crash-safe.** The replica position (`mysql.gtid_slave_pos`) and the + DuckDB data are committed separately. + ### Path 4: Direct UPDATE/DELETE (Statement Pushdown) For single-table statements with simple WHERE (`HA_CAN_DIRECT_UPDATE_AND_DELETE`): diff --git a/storage/duckdb/ha_duckdb.cc b/storage/duckdb/ha_duckdb.cc index 1f6dc91cf86..96442413cb0 100644 --- a/storage/duckdb/ha_duckdb.cc +++ b/storage/duckdb/ha_duckdb.cc @@ -405,6 +405,8 @@ static bool calc_row_difference(const uchar *old_row, const uchar *new_row, } /* check whether PK is modified */ +static bool calc_pk_difference(const uchar *old_row, const uchar *new_row, + TABLE *table) __attribute__((unused)); static bool calc_pk_difference(const uchar *old_row, const uchar *new_row, TABLE *table) { @@ -421,23 +423,6 @@ static bool calc_pk_difference(const uchar *old_row, const uchar *new_row, return false; } -static int execute_idempotent_pk_update(THD *thd, TABLE *table, - const uchar *old_row) -{ - DeleteConvertor delete_old(table, old_row); - int ret= execute_dml(thd, &delete_old); - if (ret) - return ret; - - DeleteConvertor delete_new(table); - ret= execute_dml(thd, &delete_new); - if (ret) - return ret; - - InsertConvertor insert_new(table, true); - return execute_dml(thd, &insert_new); -} - static int get_batch_state(THD *thd, bool insert_only, myduck::BatchState *batch_state) { @@ -698,57 +683,46 @@ int ha_duckdb::update_row(const uchar *old_row, const uchar *new_row) DBUG_ENTER("ha_duckdb::update_row"); int ret= 0; THD *thd= ha_thd(); - bool replication_applier= myduck::thd_is_replication_applier(thd); - bool pk_modified= calc_pk_difference(old_row, new_row, table); + + /* + The batch path writes the whole record[0] as the new row version, so + a replicated UPDATE needs a complete after-image. Rows_log_event:: + do_apply_event() narrows write_set to the after-image columns; with a + MINIMAL or NOBLOB master the missing columns would silently get + defaults. Reject such events instead. + */ + if (myduck::thd_is_row_injection(thd) && + !bitmap_is_set_all(table->write_set)) + { + my_error(ER_GET_ERRMSG, MYF(0), HA_DUCKDB_DML_ERROR, + "Replicated UPDATE lacks columns in the after-image; " + "DuckDB tables require binlog_row_image=FULL on the master", + "DuckDB"); + DBUG_RETURN(HA_DUCKDB_DML_ERROR); + } ret= duckdb_register_trx(thd); if (ret) DBUG_RETURN(ret); - if (!replication_applier && pk_modified) - table->rpl_write_set= &table->s->all_set; - myduck::BatchState batch_state; ret= get_batch_state(thd, false, &batch_state); if (ret) DBUG_RETURN(ret); - if (replication_applier && !pk_modified) + if (batch_state == myduck::BatchState::NOT_IN_BATCH) { - auto *ctx= get_duckdb_context(thd); - std::string error_msg; - if (ctx->flush_appenders(error_msg)) + if (update_modified_column_only && + calc_row_difference(old_row, new_row, table)) { - my_error(ER_GET_ERRMSG, MYF(0), HA_DUCKDB_APPEND_ERROR, - error_msg.c_str(), "DuckDB"); - DBUG_RETURN(HA_DUCKDB_APPEND_ERROR); + bitmap_copy(table->write_set, &table->tmp_set); } + bitmap_clear_all(&table->tmp_set); UpdateConvertor update_convertor(table, old_row); ret= execute_dml(thd, &update_convertor); if (ret == 0) srv_duckdb_status.duckdb_rows_update++; - DBUG_RETURN(ret); - } - - if (batch_state == myduck::BatchState::NOT_IN_BATCH) - { - if (replication_applier) - ret= execute_idempotent_pk_update(thd, table, old_row); - else - { - if (update_modified_column_only && - calc_row_difference(old_row, new_row, table)) - { - bitmap_copy(table->write_set, &table->tmp_set); - } - bitmap_clear_all(&table->tmp_set); - - UpdateConvertor update_convertor(table, old_row); - ret= execute_dml(thd, &update_convertor); - } - if (ret == 0) - srv_duckdb_status.duckdb_rows_update++; } else { @@ -759,13 +733,8 @@ int ha_duckdb::update_row(const uchar *old_row, const uchar *new_row) else { ctx->set_batch_state(myduck::BatchState::NOT_IN_BATCH); - if (replication_applier) - ret= execute_idempotent_pk_update(thd, table, old_row); - else - { - UpdateConvertor update_convertor(table, old_row); - ret= execute_dml(thd, &update_convertor); - } + UpdateConvertor update_convertor(table, old_row); + ret= execute_dml(thd, &update_convertor); if (ret == 0) srv_duckdb_status.duckdb_rows_update++; } diff --git a/storage/duckdb/mysql-test/duckdb/r/mdev_40957_rpl.result b/storage/duckdb/mysql-test/duckdb/r/mdev_40957_rpl.result index c84865f0d83..f7d95771164 100644 --- a/storage/duckdb/mysql-test/duckdb/r/mdev_40957_rpl.result +++ b/storage/duckdb/mysql-test/duckdb/r/mdev_40957_rpl.result @@ -76,42 +76,55 @@ connection slave; SELECT COUNT(*) FROM t_upd; COUNT(*) 0 -# MINIMAL row image: replicated UPDATE preserves unchanged columns +# InnoDB master, DuckDB slave: FULL row images are applied in batch +connection master; +SET SESSION sql_log_bin=0; +CREATE TABLE t_het ( +id INT PRIMARY KEY, +v VARCHAR(16), +untouched VARCHAR(16) +) ENGINE=InnoDB; +SET SESSION sql_log_bin=1; +connection slave; +SET SESSION sql_log_bin=0; +CREATE TABLE t_het ( +id INT PRIMARY KEY, +v VARCHAR(16), +untouched VARCHAR(16) +) ENGINE=DuckDB; +SET SESSION sql_log_bin=1; +connection master; +INSERT INTO t_het VALUES +(1, 'one', 'keep-1'), +(2, 'two', 'keep-2'), +(3, 'three', 'keep-3'); +UPDATE t_het SET v = 'ONE' WHERE id = 1; +UPDATE t_het SET id = 20 WHERE id = 2; +DELETE FROM t_het WHERE id = 3; +connection slave; +SELECT * FROM t_het ORDER BY id; +id v untouched +1 ONE keep-1 +20 two keep-2 +update_in_batch +2 +# MINIMAL row image is rejected instead of losing columns connection master; -SET GLOBAL duckdb_dml_in_batch=OFF; SET SESSION binlog_row_image=MINIMAL; -INSERT INTO t_upd VALUES -(1, 'one', 'must-survive'), -(2, 'other', 'must-not-leak'); -UPDATE t_upd SET v = 'ONE' WHERE v = 'one'; +UPDATE t_het SET v = 'TWENTY' WHERE id = 20; +SET SESSION binlog_row_image=FULL; connection slave; -SELECT * FROM t_upd WHERE id = 1; +call mtr.add_suppression("DuckDB tables require binlog_row_image=FULL on the master"); +include/wait_for_slave_sql_error.inc [errno=1296] +SELECT * FROM t_het ORDER BY id; id v untouched -1 ONE must-survive -connection master; -UPDATE t_upd SET id = 20 WHERE v = 'other'; -connection slave; -SELECT * FROM t_upd ORDER BY id; -id v untouched -1 ONE must-survive -20 other must-not-leak -UPDATE t_upd SET v = 'corrupt', untouched = 'corrupt' WHERE v = 'other'; -SELECT * FROM t_upd ORDER BY id; -id v untouched -1 ONE must-survive -20 corrupt corrupt -include/stop_slave.inc -CHANGE MASTER TO MASTER_LOG_FILE='REPLAY_FILE', MASTER_LOG_POS=REPLAY_POS, MASTER_USE_GTID=NO; +1 ONE keep-1 +20 two keep-2 +SET GLOBAL sql_slave_skip_counter=1; include/start_slave.inc connection master; connection slave; -SELECT * FROM t_upd ORDER BY id; -id v untouched -1 ONE must-survive -20 other must-not-leak connection master; -SET GLOBAL duckdb_dml_in_batch=ON; -SET SESSION binlog_row_image=FULL; SET SESSION binlog_format=MIXED; DROP DATABASE mdev_40957_rpl; connection slave; diff --git a/storage/duckdb/mysql-test/duckdb/t/mdev_40957_rpl.test b/storage/duckdb/mysql-test/duckdb/t/mdev_40957_rpl.test index 85f6f2cc8b9..685416f5eae 100644 --- a/storage/duckdb/mysql-test/duckdb/t/mdev_40957_rpl.test +++ b/storage/duckdb/mysql-test/duckdb/t/mdev_40957_rpl.test @@ -66,40 +66,60 @@ DELETE FROM t_upd; --sync_slave_with_master SELECT COUNT(*) FROM t_upd; ---echo # MINIMAL row image: replicated UPDATE preserves unchanged columns +--echo # InnoDB master, DuckDB slave: FULL row images are applied in batch +--connection master +SET SESSION sql_log_bin=0; +CREATE TABLE t_het ( + id INT PRIMARY KEY, + v VARCHAR(16), + untouched VARCHAR(16) +) ENGINE=InnoDB; +SET SESSION sql_log_bin=1; + +--connection slave +SET SESSION sql_log_bin=0; +CREATE TABLE t_het ( + id INT PRIMARY KEY, + v VARCHAR(16), + untouched VARCHAR(16) +) ENGINE=DuckDB; +SET SESSION sql_log_bin=1; +--let $update_before= query_get_value(SHOW GLOBAL STATUS LIKE 'Duckdb_rows_update_in_batch', Value, 1) + +--connection master +INSERT INTO t_het VALUES + (1, 'one', 'keep-1'), + (2, 'two', 'keep-2'), + (3, 'three', 'keep-3'); +UPDATE t_het SET v = 'ONE' WHERE id = 1; +UPDATE t_het SET id = 20 WHERE id = 2; +DELETE FROM t_het WHERE id = 3; + +--sync_slave_with_master +SELECT * FROM t_het ORDER BY id; +--let $update_after= query_get_value(SHOW GLOBAL STATUS LIKE 'Duckdb_rows_update_in_batch', Value, 1) +--disable_query_log +--eval SELECT $update_after - $update_before AS update_in_batch +--enable_query_log + +--echo # MINIMAL row image is rejected instead of losing columns --connection master -SET GLOBAL duckdb_dml_in_batch=OFF; SET SESSION binlog_row_image=MINIMAL; -INSERT INTO t_upd VALUES - (1, 'one', 'must-survive'), - (2, 'other', 'must-not-leak'); -UPDATE t_upd SET v = 'ONE' WHERE v = 'one'; +UPDATE t_het SET v = 'TWENTY' WHERE id = 20; +SET SESSION binlog_row_image=FULL; ---sync_slave_with_master -SELECT * FROM t_upd WHERE id = 1; - ---connection master ---let $replay_file= query_get_value(SHOW MASTER STATUS, File, 1) ---let $replay_pos= query_get_value(SHOW MASTER STATUS, Position, 1) -UPDATE t_upd SET id = 20 WHERE v = 'other'; - ---sync_slave_with_master -SELECT * FROM t_upd ORDER BY id; -UPDATE t_upd SET v = 'corrupt', untouched = 'corrupt' WHERE v = 'other'; -SELECT * FROM t_upd ORDER BY id; - ---source include/stop_slave.inc ---replace_result $replay_file REPLAY_FILE $replay_pos REPLAY_POS ---eval CHANGE MASTER TO MASTER_LOG_FILE='$replay_file', MASTER_LOG_POS=$replay_pos, MASTER_USE_GTID=NO +--connection slave +call mtr.add_suppression("DuckDB tables require binlog_row_image=FULL on the master"); +--let $slave_sql_errno= 1296 +--source include/wait_for_slave_sql_error.inc +SELECT * FROM t_het ORDER BY id; +SET GLOBAL sql_slave_skip_counter=1; --source include/start_slave.inc --connection master --sync_slave_with_master -SELECT * FROM t_upd ORDER BY id; --connection master -SET GLOBAL duckdb_dml_in_batch=ON; -SET SESSION binlog_row_image=FULL; SET SESSION binlog_format=MIXED; DROP DATABASE mdev_40957_rpl; diff --git a/storage/duckdb/runtime/duckdb_context.cc b/storage/duckdb/runtime/duckdb_context.cc index d338e77ff17..25ca799560d 100644 --- a/storage/duckdb/runtime/duckdb_context.cc +++ b/storage/duckdb/runtime/duckdb_context.cc @@ -185,6 +185,11 @@ bool thd_is_replication_applier(THD *thd) return thd->rgi_slave != nullptr; } +bool thd_is_row_injection(THD *thd) +{ + return thd->lex->is_stmt_row_injection(); +} + bool reject_xa_if_active(THD *thd) { if (!thd->transaction->xid_state.is_explicit_XA()) diff --git a/storage/duckdb/runtime/duckdb_context.h b/storage/duckdb/runtime/duckdb_context.h index 732b3941993..1708167cc6f 100644 --- a/storage/duckdb/runtime/duckdb_context.h +++ b/storage/duckdb/runtime/duckdb_context.h @@ -181,4 +181,7 @@ bool reject_xa_if_active(THD *thd); /** Return true when THD is a replication applier thread. */ bool thd_is_replication_applier(THD *thd); +/** Return true when THD applies row events (replication or BINLOG). */ +bool thd_is_row_injection(THD *thd); + } // namespace myduck From 2340857fa1e803202783c33ae379a7484bbf038f Mon Sep 17 00:00:00 2001 From: Khaled Riyad Date: Fri, 11 Sep 2026 21:19:13 +0300 Subject: [PATCH 71/71] MDEV-40377 Change Server source code to point to new docs (11.4 part) Replace the remaining Knowledge Base links with their MariaDB Documentation equivalents, including the 838 URLs in the help tables. Only URLs change in fill_help_tables.sql. Merging upward: fill_help_tables.sql conflicts at 10.11->11.4 and 11.8->12.3. At those two merges keep the target branch's version, since 11.4, 11.8 and 12.3 each carry their own URL fix. From 12.3 to 13.1 and 13.1 to main it merges cleanly; take the incoming change. .github/pull_request_template.md is deleted in 12.3; keep the deletion. --- .github/pull_request_template.md | 2 +- scripts/fill_help_tables.sql | 1710 ++++++++++++++-------------- sql/opt_subselect.cc | 2 +- tests/upgrade_from/test_upgrade.sh | 2 +- 4 files changed, 858 insertions(+), 858 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 7f25bb6d4a9..54f5b519144 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -22,7 +22,7 @@ TODO: fill description here ## Release Notes TODO: What should the release notes say about this change? -Include any changed system variables, status variables or behaviour. Optionally list any https://mariadb.com/kb/ pages that need changing. +Include any changed system variables, status variables or behaviour. Optionally list any https://mariadb.com/docs/ pages that need changing. ## How can this PR be tested? diff --git a/scripts/fill_help_tables.sql b/scripts/fill_help_tables.sql index 17d2e560626..0c4257b25b8 100644 --- a/scripts/fill_help_tables.sql +++ b/scripts/fill_help_tables.sql @@ -86,871 +86,871 @@ insert into help_category (help_category_id,name,parent_category_id,url) values insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (1,9,'HELP_DATE','Help Contents generated from the MariaDB Knowledge Base on 23 January 2024.','',''); insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (2,9,'HELP_VERSION','Help Contents generated for MariaDB 11.4 from the MariaDB Knowledge Base on 23 January 2024.','',''); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (3,2,'AREA','A synonym for ST_AREA.\n\nURL: https://mariadb.com/kb/en/polygon-properties-area/','','https://mariadb.com/kb/en/polygon-properties-area/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (4,2,'CENTROID','A synonym for ST_CENTROID.\n\nURL: https://mariadb.com/kb/en/centroid/','','https://mariadb.com/kb/en/centroid/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (5,2,'ExteriorRing','A synonym for ST_ExteriorRing.\n\nURL: https://mariadb.com/kb/en/polygon-properties-exteriorring/','','https://mariadb.com/kb/en/polygon-properties-exteriorring/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (6,2,'InteriorRingN','A synonym for ST_InteriorRingN.\n\nURL: https://mariadb.com/kb/en/polygon-properties-interiorringn/','','https://mariadb.com/kb/en/polygon-properties-interiorringn/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (7,2,'NumInteriorRings','A synonym for ST_NumInteriorRings.\n\nURL: https://mariadb.com/kb/en/polygon-properties-numinteriorrings/','','https://mariadb.com/kb/en/polygon-properties-numinteriorrings/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (8,2,'ST_AREA','Syntax\n------\n\nST_Area(poly)\nArea(poly)\n\nDescription\n-----------\n\nReturns as a double-precision number the area of the Polygon value poly, as\nmeasured in its spatial reference system.\n\nST_Area() and Area() are synonyms.\n\nExamples\n--------\n\nSET @poly = \'Polygon((0 0,0 3,3 0,0 0),(1 1,1 2,2 1,1 1))\';\n\nSELECT Area(GeomFromText(@poly));\n+---------------------------+\n| Area(GeomFromText(@poly)) |\n+---------------------------+\n| 4 |\n+---------------------------+\n\nURL: https://mariadb.com/kb/en/st_area/','','https://mariadb.com/kb/en/st_area/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (9,2,'ST_CENTROID','Syntax\n------\n\nST_Centroid(mpoly)\nCentroid(mpoly)\n\nDescription\n-----------\n\nReturns a point reflecting the mathematical centroid (geometric center) for\nthe MultiPolygon mpoly. The resulting point will not necessarily be on the\nMultiPolygon.\n\nST_Centroid() and Centroid() are synonyms.\n\nExamples\n--------\n\nSET @poly = ST_GeomFromText(\'POLYGON((0 0,20 0,20 20,0 20,0 0))\');\nSELECT ST_AsText(ST_Centroid(@poly)) AS center;\n+--------------+\n| center |\n+--------------+\n| POINT(10 10) |\n+--------------+\n\nURL: https://mariadb.com/kb/en/st_centroid/','','https://mariadb.com/kb/en/st_centroid/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (10,2,'ST_ExteriorRing','Syntax\n------\n\nST_ExteriorRing(poly)\nExteriorRing(poly)\n\nDescription\n-----------\n\nReturns the exterior ring of the Polygon value poly as a LineString.\n\nST_ExteriorRing() and ExteriorRing() are synonyms.\n\nExamples\n--------\n\nSET @poly = \'Polygon((0 0,0 3,3 3,3 0,0 0),(1 1,1 2,2 2,2 1,1 1))\';\n\nSELECT AsText(ExteriorRing(GeomFromText(@poly)));\n+-------------------------------------------+\n| AsText(ExteriorRing(GeomFromText(@poly))) |\n+-------------------------------------------+\n| LINESTRING(0 0,0 3,3 3,3 0,0 0) |\n+-------------------------------------------+\n\nURL: https://mariadb.com/kb/en/st_exteriorring/','','https://mariadb.com/kb/en/st_exteriorring/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (11,2,'ST_InteriorRingN','Syntax\n------\n\nST_InteriorRingN(poly,N)\nInteriorRingN(poly,N)\n\nDescription\n-----------\n\nReturns the N-th interior ring for the Polygon value poly as a LineString.\nRings are numbered beginning with 1.\n\nST_InteriorRingN() and InteriorRingN() are synonyms.\n\nExamples\n--------\n\nSET @poly = \'Polygon((0 0,0 3,3 3,3 0,0 0),(1 1,1 2,2 2,2 1,1 1))\';\n\nSELECT AsText(InteriorRingN(GeomFromText(@poly),1));\n+----------------------------------------------+\n| AsText(InteriorRingN(GeomFromText(@poly),1)) |\n+----------------------------------------------+\n| LINESTRING(1 1,1 2,2 2,2 1,1 1) |\n+----------------------------------------------+\n\nURL: https://mariadb.com/kb/en/st_interiorringn/','','https://mariadb.com/kb/en/st_interiorringn/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (12,2,'ST_NumInteriorRings','Syntax\n------\n\nST_NumInteriorRings(poly)\nNumInteriorRings(poly)\n\nDescription\n-----------\n\nReturns an integer containing the number of interior rings in the Polygon\nvalue poly.\n\nNote that according the the OpenGIS standard, a POLYGON should have exactly\none ExteriorRing and all other rings should lie within that ExteriorRing and\nthus be the InteriorRings. Practically, however, some systems, including\nMariaDB\'s, permit polygons to have several \'ExteriorRings\'. In the case of\nthere being multiple, non-overlapping exterior rings ST_NumInteriorRings()\nwill return 1.\n\nST_NumInteriorRings() and NumInteriorRings() are synonyms.\n\nExamples\n--------\n\nSET @poly = \'Polygon((0 0,0 3,3 3,3 0,0 0),(1 1,1 2,2 2,2 1,1 1))\';\n\nSELECT NumInteriorRings(GeomFromText(@poly));\n+---------------------------------------+\n| NumInteriorRings(GeomFromText(@poly)) |\n+---------------------------------------+\n| 1 |\n+---------------------------------------+\n\nNon-overlapping \'polygon\':\n\nSELECT ST_NumInteriorRings(ST_PolyFromText(\'POLYGON((0 0,10 0,10 10,0 10,0 0),\n (-1 -1,-5 -1,-5 -5,-1 -5,-1 -1))\')) AS NumInteriorRings;\n+------------------+\n| NumInteriorRings |\n+------------------+\n| 1 |\n+------------------+\n\nURL: https://mariadb.com/kb/en/st_numinteriorrings/','','https://mariadb.com/kb/en/st_numinteriorrings/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (13,3,'WKT Definition','Description\n-----------\n\nThe Well-Known Text (WKT) representation of Geometry is designed to exchange\ngeometry data in ASCII form. Examples of the basic geometry types include:\n\n+-----------------------------------------------------------------------------+\n| Geometry Types |\n+-----------------------------------------------------------------------------+\n| POINT |\n+-----------------------------------------------------------------------------+\n| LINESTRING |\n+-----------------------------------------------------------------------------+\n| POLYGON |\n+-----------------------------------------------------------------------------+\n| MULTIPOINT |\n+-----------------------------------------------------------------------------+\n| MULTILINESTRING |\n+-----------------------------------------------------------------------------+\n| MULTIPOLYGON |\n+-----------------------------------------------------------------------------+\n| GEOMETRYCOLLECTION |\n+-----------------------------------------------------------------------------+\n| GEOMETRY |\n+-----------------------------------------------------------------------------+\n\nURL: https://mariadb.com/kb/en/wkt-definition/','','https://mariadb.com/kb/en/wkt-definition/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (14,3,'AsText','A synonym for ST_AsText().\n\nURL: https://mariadb.com/kb/en/wkt-astext/','','https://mariadb.com/kb/en/wkt-astext/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (15,3,'AsWKT','A synonym for ST_AsText().\n\nURL: https://mariadb.com/kb/en/wkt-aswkt/','','https://mariadb.com/kb/en/wkt-aswkt/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (16,3,'GeomCollFromText','A synonym for ST_GeomCollFromText.\n\nURL: https://mariadb.com/kb/en/wkt-geomcollfromtext/','','https://mariadb.com/kb/en/wkt-geomcollfromtext/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (17,3,'GeometryCollectionFromText','A synonym for ST_GeomCollFromText.\n\nURL: https://mariadb.com/kb/en/geometrycollectionfromtext/','','https://mariadb.com/kb/en/geometrycollectionfromtext/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (18,3,'GeometryFromText','A synonym for ST_GeomFromText.\n\nURL: https://mariadb.com/kb/en/geometryfromtext/','','https://mariadb.com/kb/en/geometryfromtext/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (19,3,'GeomFromText','A synonym for ST_GeomFromText.\n\nURL: https://mariadb.com/kb/en/wkt-geomfromtext/','','https://mariadb.com/kb/en/wkt-geomfromtext/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (20,3,'LineFromText','A synonym for ST_LineFromText.\n\nURL: https://mariadb.com/kb/en/wkt-linefromtext/','','https://mariadb.com/kb/en/wkt-linefromtext/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (21,3,'LineStringFromText','A synonym for ST_LineFromText.\n\nURL: https://mariadb.com/kb/en/linestringfromtext/','','https://mariadb.com/kb/en/linestringfromtext/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (22,3,'MLineFromText','Syntax\n------\n\nMLineFromText(wkt[,srid])\nMultiLineStringFromText(wkt[,srid])\n\nDescription\n-----------\n\nConstructs a MULTILINESTRING value using its WKT representation and SRID.\n\nMLineFromText() and MultiLineStringFromText() are synonyms.\n\nExamples\n--------\n\nCREATE TABLE gis_multi_line (g MULTILINESTRING);\nSHOW FIELDS FROM gis_multi_line;\nINSERT INTO gis_multi_line VALUES\n (MultiLineStringFromText(\'MULTILINESTRING((10 48,10 21,10 0),(16 0,16\n23,16 48))\')),\n (MLineFromText(\'MULTILINESTRING((10 48,10 21,10 0))\')),\n (MLineFromWKB(AsWKB(MultiLineString(\n LineString(Point(1, 2), Point(3, 5)),\n LineString(Point(2, 5), Point(5, 8), Point(21, 7))))));\n\nURL: https://mariadb.com/kb/en/mlinefromtext/','','https://mariadb.com/kb/en/mlinefromtext/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (23,3,'MPointFromText','Syntax\n------\n\nMPointFromText(wkt[,srid])\nMultiPointFromText(wkt[,srid])\n\nDescription\n-----------\n\nConstructs a MULTIPOINT value using its WKT representation and SRID.\n\nMPointFromText() and MultiPointFromText() are synonyms.\n\nExamples\n--------\n\nCREATE TABLE gis_multi_point (g MULTIPOINT);\nSHOW FIELDS FROM gis_multi_point;\nINSERT INTO gis_multi_point VALUES\n (MultiPointFromText(\'MULTIPOINT(0 0,10 10,10 20,20 20)\')),\n (MPointFromText(\'MULTIPOINT(1 1,11 11,11 21,21 21)\')),\n (MPointFromWKB(AsWKB(MultiPoint(Point(3, 6), Point(4, 10)))));\n\nURL: https://mariadb.com/kb/en/mpointfromtext/','','https://mariadb.com/kb/en/mpointfromtext/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (24,3,'MPolyFromText','Syntax\n------\n\nMPolyFromText(wkt[,srid])\nMultiPolygonFromText(wkt[,srid])\n\nDescription\n-----------\n\nConstructs a MULTIPOLYGON value using its WKT representation and SRID.\n\nMPolyFromText() and MultiPolygonFromText() are synonyms.\n\nExamples\n--------\n\nCREATE TABLE gis_multi_polygon (g MULTIPOLYGON);\nSHOW FIELDS FROM gis_multi_polygon;\nINSERT INTO gis_multi_polygon VALUES\n (MultiPolygonFromText(\'MULTIPOLYGON(\n ((28 26,28 0,84 0,84 42,28 26),(52 18,66 23,73 9,48 6,52 18)),\n ((59 18,67 18,67 13,59 13,59 18)))\')),\n (MPolyFromText(\'MULTIPOLYGON(\n ((28 26,28 0,84 0,84 42,28 26),(52 18,66 23,73 9,48 6,52 18)),\n ((59 18,67 18,67 13,59 13,59 18)))\')),\n (MPolyFromWKB(AsWKB(MultiPolygon(Polygon(\n LineString(Point(0, 3), Point(3, 3), Point(3, 0), Point(0, 3)))))));\n\nURL: https://mariadb.com/kb/en/mpolyfromtext/','','https://mariadb.com/kb/en/mpolyfromtext/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (25,3,'MultiLineStringFromText','A synonym for MLineFromText.\n\nURL: https://mariadb.com/kb/en/multilinestringfromtext/','','https://mariadb.com/kb/en/multilinestringfromtext/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (26,3,'MultiPointFromText','A synonym for MPointFromText.\n\nURL: https://mariadb.com/kb/en/multipointfromtext/','','https://mariadb.com/kb/en/multipointfromtext/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (27,3,'MultiPolygonFromText','A synonym for MPolyFromText.\n\nURL: https://mariadb.com/kb/en/multipolygonfromtext/','','https://mariadb.com/kb/en/multipolygonfromtext/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (28,3,'PointFromText','A synonym for ST_PointFromText.\n\nURL: https://mariadb.com/kb/en/wkt-pointfromtext/','','https://mariadb.com/kb/en/wkt-pointfromtext/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (29,3,'PolyFromText','A synonym for ST_PolyFromText.\n\nURL: https://mariadb.com/kb/en/wkt-polyfromtext/','','https://mariadb.com/kb/en/wkt-polyfromtext/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (30,3,'PolygonFromText','A synonym for ST_PolyFromText.\n\nURL: https://mariadb.com/kb/en/polygonfromtext/','','https://mariadb.com/kb/en/polygonfromtext/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (31,3,'ST_AsText','Syntax\n------\n\nST_AsText(g)\nAsText(g)\nST_AsWKT(g)\nAsWKT(g)\n\nDescription\n-----------\n\nConverts a value in internal geometry format to its WKT representation and\nreturns the string result.\n\nST_AsText(), AsText(), ST_AsWKT() and AsWKT() are all synonyms.\n\nExamples\n--------\n\nSET @g = \'LineString(1 1,4 4,6 6)\';\n\nSELECT ST_AsText(ST_GeomFromText(@g));\n+--------------------------------+\n| ST_AsText(ST_GeomFromText(@g)) |\n+--------------------------------+\n| LINESTRING(1 1,4 4,6 6) |\n+--------------------------------+\n\nURL: https://mariadb.com/kb/en/st_astext/','','https://mariadb.com/kb/en/st_astext/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (32,3,'ST_ASWKT','A synonym for ST_ASTEXT().\n\nURL: https://mariadb.com/kb/en/st_aswkt/','','https://mariadb.com/kb/en/st_aswkt/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (33,3,'ST_GeomCollFromText','Syntax\n------\n\nST_GeomCollFromText(wkt[,srid])\nST_GeometryCollectionFromText(wkt[,srid])\nGeomCollFromText(wkt[,srid])\nGeometryCollectionFromText(wkt[,srid])\n\nDescription\n-----------\n\nConstructs a GEOMETRYCOLLECTION value using its WKT representation and SRID.\n\nST_GeomCollFromText(), ST_GeometryCollectionFromText(), GeomCollFromText() and\nGeometryCollectionFromText() are all synonyms.\n\nExample\n-------\n\nCREATE TABLE gis_geometrycollection (g GEOMETRYCOLLECTION);\nSHOW FIELDS FROM gis_geometrycollection;\nINSERT INTO gis_geometrycollection VALUES\n (GeomCollFromText(\'GEOMETRYCOLLECTION(POINT(0 0), LINESTRING(0 0,10\n10))\')),\n (GeometryFromWKB(AsWKB(GeometryCollection(Point(44, 6),\nLineString(Point(3, 6), Point(7, 9)))))),\n (GeomFromText(\'GeometryCollection()\')),\n (GeomFromText(\'GeometryCollection EMPTY\'));\n\nURL: https://mariadb.com/kb/en/st_geomcollfromtext/','','https://mariadb.com/kb/en/st_geomcollfromtext/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (34,3,'ST_GeometryCollectionFromText','A synonym for ST_GeomCollFromText.\n\nURL: https://mariadb.com/kb/en/st_geometrycollectionfromtext/','','https://mariadb.com/kb/en/st_geometrycollectionfromtext/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (35,3,'ST_GeometryFromText','A synonym for ST_GeomFromText.\n\nURL: https://mariadb.com/kb/en/st_geometryfromtext/','','https://mariadb.com/kb/en/st_geometryfromtext/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (36,3,'ST_GeomFromText','Syntax\n------\n\nST_GeomFromText(wkt[,srid])\nST_GeometryFromText(wkt[,srid])\nGeomFromText(wkt[,srid])\nGeometryFromText(wkt[,srid])\n\nDescription\n-----------\n\nConstructs a geometry value of any type using its WKT representation and SRID.\n\nGeomFromText(), GeometryFromText(), ST_GeomFromText() and\nST_GeometryFromText() are all synonyms.\n\nExample\n-------\n\nSET @g = ST_GEOMFROMTEXT(\'POLYGON((1 1,1 5,4 9,6 9,9 3,7 2,1 1))\');\n\nURL: https://mariadb.com/kb/en/st_geomfromtext/','','https://mariadb.com/kb/en/st_geomfromtext/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (37,3,'ST_LineFromText','Syntax\n------\n\nST_LineFromText(wkt[,srid])\nST_LineStringFromText(wkt[,srid])\nLineFromText(wkt[,srid])\nLineStringFromText(wkt[,srid])\n\nDescription\n-----------\n\nConstructs a LINESTRING value using its WKT representation and SRID.\n\nST_LineFromText(), ST_LineStringFromText(), ST_LineFromText() and\nST_LineStringFromText() are all synonyms.\n\nExamples\n--------\n\nCREATE TABLE gis_line (g LINESTRING);\nSHOW FIELDS FROM gis_line;\nINSERT INTO gis_line VALUES\n (LineFromText(\'LINESTRING(0 0,0 10,10 0)\')),\n (LineStringFromText(\'LINESTRING(10 10,20 10,20 20,10 20,10 10)\')),\n (LineStringFromWKB(AsWKB(LineString(Point(10, 10), Point(40, 10)))));\n\nURL: https://mariadb.com/kb/en/st_linefromtext/','','https://mariadb.com/kb/en/st_linefromtext/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (38,3,'ST_LineStringFromText','A synonym for ST_LineFromText.\n\nURL: https://mariadb.com/kb/en/st_linestringfromtext/','','https://mariadb.com/kb/en/st_linestringfromtext/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (39,3,'ST_PointFromText','Syntax\n------\n\nST_PointFromText(wkt[,srid])\nPointFromText(wkt[,srid])\n\nDescription\n-----------\n\nConstructs a POINT value using its WKT representation and SRID.\n\nST_PointFromText() and PointFromText() are synonyms.\n\nExamples\n--------\n\nCREATE TABLE gis_point (g POINT);\nSHOW FIELDS FROM gis_point;\nINSERT INTO gis_point VALUES\n (PointFromText(\'POINT(10 10)\')),\n (PointFromText(\'POINT(20 10)\')),\n (PointFromText(\'POINT(20 20)\')),\n (PointFromWKB(AsWKB(PointFromText(\'POINT(10 20)\'))));\n\nURL: https://mariadb.com/kb/en/st_pointfromtext/','','https://mariadb.com/kb/en/st_pointfromtext/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (40,3,'ST_PolyFromText','Syntax\n------\n\nST_PolyFromText(wkt[,srid])\nST_PolygonFromText(wkt[,srid])\nPolyFromText(wkt[,srid])\nPolygonFromText(wkt[,srid])\n\nDescription\n-----------\n\nConstructs a POLYGON value using its WKT representation and SRID.\n\nST_PolyFromText(), ST_PolygonFromText(), PolyFromText() and\nST_PolygonFromText() are all synonyms.\n\nExamples\n--------\n\nCREATE TABLE gis_polygon (g POLYGON);\nINSERT INTO gis_polygon VALUES\n (PolygonFromText(\'POLYGON((10 10,20 10,20 20,10 20,10 10))\')),\n (PolyFromText(\'POLYGON((0 0,50 0,50 50,0 50,0 0), (10 10,20 10,20 20,10\n20,10 10))\'));\n\nURL: https://mariadb.com/kb/en/st_polyfromtext/','','https://mariadb.com/kb/en/st_polyfromtext/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (41,3,'ST_PolygonFromText','A synonym for ST_PolyFromText.\n\nURL: https://mariadb.com/kb/en/st_polygonfromtext/','','https://mariadb.com/kb/en/st_polygonfromtext/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (42,4,'DIV','Syntax\n------\n\nDIV\n\nDescription\n-----------\n\nInteger division. Similar to FLOOR(), but is safe with BIGINT values.\nIncorrect results may occur for non-integer operands that exceed BIGINT range.\n\nIf the ERROR_ON_DIVISION_BY_ZERO SQL_MODE is used, a division by zero produces\nan error. Otherwise, it returns NULL.\n\nThe remainder of a division can be obtained using the MOD operator.\n\nExamples\n--------\n\nSELECT 300 DIV 7;\n+-----------+\n| 300 DIV 7 |\n+-----------+\n| 42 |\n+-----------+\n\nSELECT 300 DIV 0;\n+-----------+\n| 300 DIV 0 |\n+-----------+\n| NULL |\n+-----------+\n\nURL: https://mariadb.com/kb/en/div/','','https://mariadb.com/kb/en/div/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (43,4,'ABS','Syntax\n------\n\nABS(X)\n\nDescription\n-----------\n\nReturns the absolute (non-negative) value of X. If X is not a number, it is\nconverted to a numeric type.\n\nExamples\n--------\n\nSELECT ABS(42);\n+---------+\n| ABS(42) |\n+---------+\n| 42 |\n+---------+\n\nSELECT ABS(-42);\n+----------+\n| ABS(-42) |\n+----------+\n| 42 |\n+----------+\n\nSELECT ABS(DATE \'1994-01-01\');\n+------------------------+\n| ABS(DATE \'1994-01-01\') |\n+------------------------+\n| 19940101 |\n+------------------------+\n\nURL: https://mariadb.com/kb/en/abs/','','https://mariadb.com/kb/en/abs/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (44,4,'ACOS','Syntax\n------\n\nACOS(X)\n\nDescription\n-----------\n\nReturns the arc cosine of X, that is, the value whose cosine is X. Returns\nNULL if X is not in the range -1 to 1.\n\nExamples\n--------\n\nSELECT ACOS(1);\n+---------+\n| ACOS(1) |\n+---------+\n| 0 |\n+---------+\n\nSELECT ACOS(1.0001);\n+--------------+\n| ACOS(1.0001) |\n+--------------+\n| NULL |\n+--------------+\n\nSELECT ACOS(0);\n+-----------------+\n| ACOS(0) |\n+-----------------+\n| 1.5707963267949 |\n+-----------------+\n\nSELECT ACOS(0.234);\n+------------------+\n| ACOS(0.234) |\n+------------------+\n| 1.33460644244679 |\n+------------------+\n\nURL: https://mariadb.com/kb/en/acos/','','https://mariadb.com/kb/en/acos/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (45,4,'ASIN','Syntax\n------\n\nASIN(X)\n\nDescription\n-----------\n\nReturns the arc sine of X, that is, the value whose sine is X. Returns NULL if\nX is not in the range -1 to 1.\n\nExamples\n--------\n\nSELECT ASIN(0.2);\n+--------------------+\n| ASIN(0.2) |\n+--------------------+\n| 0.2013579207903308 |\n+--------------------+\n\nSELECT ASIN(\'foo\');\n+-------------+\n| ASIN(\'foo\') |\n+-------------+\n| 0 |\n+-------------+\n\nSHOW WARNINGS;\n+---------+------+-----------------------------------------+\n| Level | Code | Message |\n+---------+------+-----------------------------------------+\n| Warning | 1292 | Truncated incorrect DOUBLE value: \'foo\' |\n+---------+------+-----------------------------------------+\n\nURL: https://mariadb.com/kb/en/asin/','','https://mariadb.com/kb/en/asin/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (46,4,'ATAN','Syntax\n------\n\nATAN(X)\n\nDescription\n-----------\n\nReturns the arc tangent of X, that is, the value whose tangent is X.\n\nExamples\n--------\n\nSELECT ATAN(2);\n+--------------------+\n| ATAN(2) |\n+--------------------+\n| 1.1071487177940904 |\n+--------------------+\n\nSELECT ATAN(-2);\n+---------------------+\n| ATAN(-2) |\n+---------------------+\n| -1.1071487177940904 |\n+---------------------+\n\nURL: https://mariadb.com/kb/en/atan/','','https://mariadb.com/kb/en/atan/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (47,4,'ATAN2','Syntax\n------\n\nATAN(Y,X), ATAN2(Y,X)\n\nDescription\n-----------\n\nReturns the arc tangent of the two variables X and Y. It is similar to\ncalculating the arc tangent of Y / X, except that the signs of both arguments\nare used to determine the quadrant of the result.\n\nExamples\n--------\n\nSELECT ATAN(-2,2);\n+---------------------+\n| ATAN(-2,2) |\n+---------------------+\n| -0.7853981633974483 |\n+---------------------+\n\nSELECT ATAN2(PI(),0);\n+--------------------+\n| ATAN2(PI(),0) |\n+--------------------+\n| 1.5707963267948966 |\n+--------------------+\n\nURL: https://mariadb.com/kb/en/atan2/','','https://mariadb.com/kb/en/atan2/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (48,4,'CEIL','Syntax\n------\n\nCEIL(X)\n\nDescription\n-----------\n\nCEIL() is a synonym for CEILING().\n\nURL: https://mariadb.com/kb/en/ceil/','','https://mariadb.com/kb/en/ceil/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (49,4,'CEILING','Syntax\n------\n\nCEILING(X)\n\nDescription\n-----------\n\nReturns the smallest integer value not less than X.\n\nExamples\n--------\n\nSELECT CEILING(1.23);\n+---------------+\n| CEILING(1.23) |\n+---------------+\n| 2 |\n+---------------+\n\nSELECT CEILING(-1.23);\n+----------------+\n| CEILING(-1.23) |\n+----------------+\n| -1 |\n+----------------+\n\nURL: https://mariadb.com/kb/en/ceiling/','','https://mariadb.com/kb/en/ceiling/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (50,4,'CONV','Syntax\n------\n\nCONV(N,from_base,to_base)\n\nDescription\n-----------\n\nConverts numbers between different number bases. Returns a string\nrepresentation of the number N, converted from base from_base to base to_base.\n\nReturns NULL if any argument is NULL, or if the second or third argument are\nnot in the allowed range.\n\nThe argument N is interpreted as an integer, but may be specified as an\ninteger or a string. The minimum base is 2 and the maximum base is 36 (prior\nto MariaDB 11.4.0) or 62 (from MariaDB 11.4.0). If to_base is a negative\nnumber, N is regarded as a signed number. Otherwise, N is treated as unsigned.\nCONV() works with 64-bit precision.\n\nSome shortcuts for this function are also available: BIN(), OCT(), HEX(),\nUNHEX(). Also, MariaDB allows binary literal values and hexadecimal literal\nvalues.\n\nExamples\n--------\n\nSELECT CONV(\'a\',16,2);\n+----------------+\n| CONV(\'a\',16,2) |\n+----------------+\n| 1010 |\n+----------------+\n\nSELECT CONV(\'6E\',18,8);\n+-----------------+\n| CONV(\'6E\',18,8) |\n+-----------------+\n| 172 |\n+-----------------+\n\nSELECT CONV(-17,10,-18);\n+------------------+\n| CONV(-17,10,-18) |\n+------------------+\n| -H |\n+------------------+\n\nSELECT CONV(12+\'10\'+\'10\'+0xa,10,10);\n+------------------------------+\n| CONV(12+\'10\'+\'10\'+0xa,10,10) |\n+------------------------------+\n| 42 |\n+------------------------------+\n\nURL: https://mariadb.com/kb/en/conv/','','https://mariadb.com/kb/en/conv/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (51,4,'COS','Syntax\n------\n\nCOS(X)\n\nDescription\n-----------\n\nReturns the cosine of X, where X is given in radians.\n\nExamples\n--------\n\nSELECT COS(PI());\n+-----------+\n| COS(PI()) |\n+-----------+\n| -1 |\n+-----------+\n\nURL: https://mariadb.com/kb/en/cos/','','https://mariadb.com/kb/en/cos/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (52,4,'COT','Syntax\n------\n\nCOT(X)\n\nDescription\n-----------\n\nReturns the cotangent of X.\n\nExamples\n--------\n\nSELECT COT(42);\n+--------------------+\n| COT(42) |\n+--------------------+\n| 0.4364167060752729 |\n+--------------------+\n\nSELECT COT(12);\n+---------------------+\n| COT(12) |\n+---------------------+\n| -1.5726734063976893 |\n+---------------------+\n\nSELECT COT(0);\nERROR 1690 (22003): DOUBLE value is out of range in \'cot(0)\'\n\nURL: https://mariadb.com/kb/en/cot/','','https://mariadb.com/kb/en/cot/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (53,4,'CRC32','Syntax\n------\n\n<= MariaDB 10.7\n\nCRC32(expr)\n\nFrom MariaDB 10.8\n\nCRC32([par,]expr)\n\nDescription\n-----------\n\nComputes a cyclic redundancy check (CRC) value and returns a 32-bit unsigned\nvalue. The result is NULL if the argument is NULL. The argument is expected to\nbe a string and (if possible) is treated as one if it is not.\n\nUses the ISO 3309 polynomial that used by zlib and many others. MariaDB 10.8\nintroduced the CRC32C() function, which uses the alternate Castagnoli\npolynomia.\n\nMariaDB starting with 10.8\n--------------------------\nOften, CRC is computed in pieces. To facilitate this, MariaDB 10.8.0\nintroduced an optional parameter: CRC32(\'MariaDB\')=CRC32(CRC32(\'Maria\'),\'DB\').\n\nExamples\n--------\n\nSELECT CRC32(\'MariaDB\');\n+------------------+\n| CRC32(\'MariaDB\') |\n+------------------+\n| 4227209140 |\n+------------------+\n\nSELECT CRC32(\'mariadb\');\n+------------------+\n| CRC32(\'mariadb\') |\n+------------------+\n| 2594253378 |\n+------------------+\n\nFrom MariaDB 10.8.0\n\nSELECT CRC32(CRC32(\'Maria\'),\'DB\');\n+----------------------------+\n| CRC32(CRC32(\'Maria\'),\'DB\') |\n+----------------------------+\n| 4227209140 |\n+----------------------------+\n\nURL: https://mariadb.com/kb/en/crc32/','','https://mariadb.com/kb/en/crc32/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (54,4,'CRC32C','MariaDB starting with 10.8\n--------------------------\nIntroduced in MariaDB 10.8.0 to compute a cyclic redundancy check (CRC) value\nusing the Castagnoli polynomial.\n\nSyntax\n------\n\nCRC32C([par,]expr)\n\nDescription\n-----------\n\nMariaDB has always included a native unary function CRC32() that computes the\nCRC-32 of a string using the ISO 3309 polynomial that used by zlib and many\nothers.\n\nInnoDB and MyRocks use a different polynomial, which was implemented in SSE4.2\ninstructions that were introduced in the Intel Nehalem microarchitecture. This\nis commonly called CRC-32C (Castagnoli).\n\nThe CRC32C function uses the Castagnoli polynomial.\n\nThis allows SELECT…INTO DUMPFILE to be used for the creation of files with\nvalid checksums, such as a logically empty InnoDB redo log file ib_logfile0\ncorresponding to a particular log sequence number.\n\nThe optional parameter allows the checksum to be computed in pieces:\nCRC32C(\'MariaDB\')=CRC32C(CRC32C(\'Maria\'),\'DB\').\n\nExamples\n--------\n\nSELECT CRC32C(\'MariaDB\');\n+-------------------+\n| CRC32C(\'MariaDB\') |\n+-------------------+\n| 809606978 |\n+-------------------+\n\nSELECT CRC32C(CRC32C(\'Maria\'),\'DB\');\n+------------------------------+\n| CRC32C(CRC32C(\'Maria\'),\'DB\') |\n+------------------------------+\n| 809606978 |\n+------------------------------+\n\nURL: https://mariadb.com/kb/en/crc32c/','','https://mariadb.com/kb/en/crc32c/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (55,4,'DEGREES','Syntax\n------\n\nDEGREES(X)\n\nDescription\n-----------\n\nReturns the argument X, converted from radians to degrees.\n\nThis is the converse of the RADIANS() function.\n\nExamples\n--------\n\nSELECT DEGREES(PI());\n+---------------+\n| DEGREES(PI()) |\n+---------------+\n| 180 |\n+---------------+\n\nSELECT DEGREES(PI() / 2);\n+-------------------+\n| DEGREES(PI() / 2) |\n+-------------------+\n| 90 |\n+-------------------+\n\nSELECT DEGREES(45);\n+-----------------+\n| DEGREES(45) |\n+-----------------+\n| 2578.3100780887 |\n+-----------------+\n\nURL: https://mariadb.com/kb/en/degrees/','','https://mariadb.com/kb/en/degrees/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (56,4,'EXP','Syntax\n------\n\nEXP(X)\n\nDescription\n-----------\n\nReturns the value of e (the base of natural logarithms) raised to the power of\nX. The inverse of this function is LOG() (using a single argument only) or\nLN().\n\nIf X is NULL, this function returns NULL.\n\nExamples\n--------\n\nSELECT EXP(2);\n+------------------+\n| EXP(2) |\n+------------------+\n| 7.38905609893065 |\n+------------------+\n\nSELECT EXP(-2);\n+--------------------+\n| EXP(-2) |\n+--------------------+\n| 0.1353352832366127 |\n+--------------------+\n\nSELECT EXP(0);\n+--------+\n| EXP(0) |\n+--------+\n| 1 |\n+--------+\n\nSELECT EXP(NULL);\n+-----------+\n| EXP(NULL) |\n+-----------+\n| NULL |\n+-----------+\n\nURL: https://mariadb.com/kb/en/exp/','','https://mariadb.com/kb/en/exp/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (57,4,'FLOOR','Syntax\n------\n\nFLOOR(X)\n\nDescription\n-----------\n\nReturns the largest integer value not greater than X.\n\nExamples\n--------\n\nSELECT FLOOR(1.23);\n+-------------+\n| FLOOR(1.23) |\n+-------------+\n| 1 |\n+-------------+\n\nSELECT FLOOR(-1.23);\n+--------------+\n| FLOOR(-1.23) |\n+--------------+\n| -2 |\n+--------------+\n\nURL: https://mariadb.com/kb/en/floor/','','https://mariadb.com/kb/en/floor/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (58,4,'LN','Syntax\n------\n\nLN(X)\n\nDescription\n-----------\n\nReturns the natural logarithm of X; that is, the base-e logarithm of X. If X\nis less than or equal to 0, or NULL, then NULL is returned.\n\nThe inverse of this function is EXP().\n\nExamples\n--------\n\nSELECT LN(2);\n+-------------------+\n| LN(2) |\n+-------------------+\n| 0.693147180559945 |\n+-------------------+\n\nSELECT LN(-2);\n+--------+\n| LN(-2) |\n+--------+\n| NULL |\n+--------+\n\nURL: https://mariadb.com/kb/en/ln/','','https://mariadb.com/kb/en/ln/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (59,4,'LOG','Syntax\n------\n\nLOG(X), LOG(B,X)\n\nDescription\n-----------\n\nIf called with one parameter, this function returns the natural logarithm of\nX. If X is less than or equal to 0, then NULL is returned.\n\nIf called with two parameters, it returns the logarithm of X to the base B. If\nB is <= 1 or X <= 0, the function returns NULL.\n\nIf any argument is NULL, the function returns NULL.\n\nThe inverse of this function (when called with a single argument) is the EXP()\nfunction.\n\nExamples\n--------\n\nLOG(X):\n\nSELECT LOG(2);\n+-------------------+\n| LOG(2) |\n+-------------------+\n| 0.693147180559945 |\n+-------------------+\n\nSELECT LOG(-2);\n+---------+\n| LOG(-2) |\n+---------+\n| NULL |\n+---------+\n\nLOG(B,X)\n\nSELECT LOG(2,16);\n+-----------+\n| LOG(2,16) |\n+-----------+\n| 4 |\n+-----------+\n\nSELECT LOG(3,27);\n+-----------+\n| LOG(3,27) |\n+-----------+\n| 3 |\n+-----------+\n\nSELECT LOG(3,1);\n+----------+\n| LOG(3,1) |\n+----------+\n| 0 |\n+----------+\n\nSELECT LOG(3,0);\n+----------+\n| LOG(3,0) |\n+----------+\n| NULL |\n+----------+\n\nURL: https://mariadb.com/kb/en/log/','','https://mariadb.com/kb/en/log/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (60,4,'LOG10','Syntax\n------\n\nLOG10(X)\n\nDescription\n-----------\n\nReturns the base-10 logarithm of X.\n\nExamples\n--------\n\nSELECT LOG10(2);\n+-------------------+\n| LOG10(2) |\n+-------------------+\n| 0.301029995663981 |\n+-------------------+\n\nSELECT LOG10(100);\n+------------+\n| LOG10(100) |\n+------------+\n| 2 |\n+------------+\n\nSELECT LOG10(-100);\n+-------------+\n| LOG10(-100) |\n+-------------+\n| NULL |\n+-------------+\n\nURL: https://mariadb.com/kb/en/log10/','','https://mariadb.com/kb/en/log10/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (61,4,'LOG2','Syntax\n------\n\nLOG2(X)\n\nDescription\n-----------\n\nReturns the base-2 logarithm of X.\n\nExamples\n--------\n\nSELECT LOG2(4398046511104);\n+---------------------+\n| LOG2(4398046511104) |\n+---------------------+\n| 42 |\n+---------------------+\n\nSELECT LOG2(65536);\n+-------------+\n| LOG2(65536) |\n+-------------+\n| 16 |\n+-------------+\n\nSELECT LOG2(-100);\n+------------+\n| LOG2(-100) |\n+------------+\n| NULL |\n+------------+\n\nURL: https://mariadb.com/kb/en/log2/','','https://mariadb.com/kb/en/log2/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (62,4,'MOD','Syntax\n------\n\nMOD(N,M), N % M, N MOD M\n\nDescription\n-----------\n\nModulo operation. Returns the remainder of N divided by M. See also Modulo\nOperator.\n\nIf the ERROR_ON_DIVISION_BY_ZERO SQL_MODE is used, any number modulus zero\nproduces an error. Otherwise, it returns NULL.\n\nThe integer part of a division can be obtained using DIV.\n\nExamples\n--------\n\nSELECT 1042 % 50;\n+-----------+\n| 1042 % 50 |\n+-----------+\n| 42 |\n+-----------+\n\nSELECT MOD(234, 10);\n+--------------+\n| MOD(234, 10) |\n+--------------+\n| 4 |\n+--------------+\n\nSELECT 253 % 7;\n+---------+\n| 253 % 7 |\n+---------+\n| 1 |\n+---------+\n\nSELECT MOD(29,9);\n+-----------+\n| MOD(29,9) |\n+-----------+\n| 2 |\n+-----------+\n\nSELECT 29 MOD 9;\n+----------+\n| 29 MOD 9 |\n+----------+\n| 2 |\n+----------+\n\nURL: https://mariadb.com/kb/en/mod/','','https://mariadb.com/kb/en/mod/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (63,4,'OCT','Syntax\n------\n\nOCT(N)\n\nDescription\n-----------\n\nReturns a string representation of the octal value of N, where N is a longlong\n(BIGINT) number. This is equivalent to CONV(N,10,8). Returns NULL if N is NULL.\n\nExamples\n--------\n\nSELECT OCT(34);\n+---------+\n| OCT(34) |\n+---------+\n| 42 |\n+---------+\n\nSELECT OCT(12);\n+---------+\n| OCT(12) |\n+---------+\n| 14 |\n+---------+\n\nURL: https://mariadb.com/kb/en/oct/','','https://mariadb.com/kb/en/oct/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (64,4,'PI','Syntax\n------\n\nPI()\n\nDescription\n-----------\n\nReturns the value of π (pi). The default number of decimal places displayed is\nsix, but MariaDB uses the full double-precision value internally.\n\nExamples\n--------\n\nSELECT PI();\n+----------+\n| PI() |\n+----------+\n| 3.141593 |\n+----------+\n\nSELECT PI()+0.0000000000000000000000;\n+-------------------------------+\n| PI()+0.0000000000000000000000 |\n+-------------------------------+\n| 3.1415926535897931159980 |\n+-------------------------------+\n\nURL: https://mariadb.com/kb/en/pi/','','https://mariadb.com/kb/en/pi/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (65,4,'POW','Syntax\n------\n\nPOW(X,Y)\n\nDescription\n-----------\n\nReturns the value of X raised to the power of Y.\n\nPOWER() is a synonym.\n\nExamples\n--------\n\nSELECT POW(2,3);\n+----------+\n| POW(2,3) |\n+----------+\n| 8 |\n+----------+\n\nSELECT POW(2,-2);\n+-----------+\n| POW(2,-2) |\n+-----------+\n| 0.25 |\n+-----------+\n\nURL: https://mariadb.com/kb/en/pow/','','https://mariadb.com/kb/en/pow/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (66,4,'POWER','Syntax\n------\n\nPOWER(X,Y)\n\nDescription\n-----------\n\nThis is a synonym for POW(), which returns the value of X raised to the power\nof Y.\n\nURL: https://mariadb.com/kb/en/power/','','https://mariadb.com/kb/en/power/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (67,4,'RADIANS','Syntax\n------\n\nRADIANS(X)\n\nDescription\n-----------\n\nReturns the argument X, converted from degrees to radians. Note that π radians\nequals 180 degrees.\n\nThis is the converse of the DEGREES() function.\n\nExamples\n--------\n\nSELECT RADIANS(45);\n+-------------------+\n| RADIANS(45) |\n+-------------------+\n| 0.785398163397448 |\n+-------------------+\n\nSELECT RADIANS(90);\n+-----------------+\n| RADIANS(90) |\n+-----------------+\n| 1.5707963267949 |\n+-----------------+\n\nSELECT RADIANS(PI());\n+--------------------+\n| RADIANS(PI()) |\n+--------------------+\n| 0.0548311355616075 |\n+--------------------+\n\nSELECT RADIANS(180);\n+------------------+\n| RADIANS(180) |\n+------------------+\n| 3.14159265358979 |\n+------------------+\n\nURL: https://mariadb.com/kb/en/radians/','','https://mariadb.com/kb/en/radians/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (68,4,'RAND','Syntax\n------\n\nRAND(), RAND(N)\n\nDescription\n-----------\n\nReturns a random DOUBLE precision floating point value v in the range 0 <= v <\n1.0. If a constant integer argument N is specified, it is used as the seed\nvalue, which produces a repeatable sequence of column values. In the example\nbelow, note that the sequences of values produced by RAND(3) is the same both\nplaces where it occurs.\n\nIn a WHERE clause, RAND() is evaluated each time the WHERE is executed.\n\nStatements using the RAND() function are not safe for statement-based\nreplication.\n\nPractical uses\n--------------\n\nThe expression to get a random integer from a given range is the following:\n\nFLOOR(min_value + RAND() * (max_value - min_value +1))\n\nRAND() is often used to read random rows from a table, as follows:\n\nSELECT * FROM my_table ORDER BY RAND() LIMIT 10;\n\nNote, however, that this technique should never be used on a large table as it\nwill be extremely slow. MariaDB will read all rows in the table, generate a\nrandom value for each of them, order them, and finally will apply the LIMIT\nclause.\n\nExamples\n--------\n\nCREATE TABLE t (i INT);\n\nINSERT INTO t VALUES(1),(2),(3);\n\nSELECT i, RAND() FROM t;\n+------+-------------------+\n| i | RAND() |\n+------+-------------------+\n| 1 | 0.255651095188829 |\n| 2 | 0.833920199269355 |\n| 3 | 0.40264774151393 |\n+------+-------------------+\n\nSELECT i, RAND(3) FROM t;\n+------+-------------------+\n| i | RAND(3) |\n+------+-------------------+\n| 1 | 0.90576975597606 |\n| 2 | 0.373079058130345 |\n| 3 | 0.148086053457191 |\n+------+-------------------+\n\nSELECT i, RAND() FROM t;\n+------+-------------------+\n| i | RAND() |\n+------+-------------------+\n| 1 | 0.511478140495232 |\n| 2 | 0.349447508668012 |\n| 3 | 0.212803152588013 |\n+------+-------------------+\n\nUsing the same seed, the same sequence will be returned:\n\nSELECT i, RAND(3) FROM t;\n+------+-------------------+\n| i | RAND(3) |\n+------+-------------------+\n| 1 | 0.90576975597606 |\n| 2 | 0.373079058130345 |\n| 3 | 0.148086053457191 |\n+------+-------------------+\n\nGenerating a random number from 5 to 15:\n\nSELECT FLOOR(5 + (RAND() * 11));\n\nURL: https://mariadb.com/kb/en/rand/','','https://mariadb.com/kb/en/rand/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (69,4,'ROUND','Syntax\n------\n\nROUND(X), ROUND(X,D)\n\nDescription\n-----------\n\nRounds the argument X to D decimal places. D defaults to 0 if not specified. D\ncan be negative to cause D digits left of the decimal point of the value X to\nbecome zero.\n\nThe rounding algorithm depends on the data type of X:\n\n* for floating point types (FLOAT, DOUBLE) the C libraries rounding function\nis used, so the behavior *may* differ between operating systems\n* for fixed point types (DECIMAL, DEC/NUMBER/FIXED) the \"round half up\" rule\nis used, meaning that e.g. a value ending in exactly .5 is always rounded up.\n\nExamples\n--------\n\nSELECT ROUND(-1.23);\n+--------------+\n| ROUND(-1.23) |\n+--------------+\n| -1 |\n+--------------+\n\nSELECT ROUND(-1.58);\n+--------------+\n| ROUND(-1.58) |\n+--------------+\n| -2 |\n+--------------+\n\nSELECT ROUND(1.58); \n+-------------+\n| ROUND(1.58) |\n+-------------+\n| 2 |\n+-------------+\n\nSELECT ROUND(1.298, 1);\n+-----------------+\n| ROUND(1.298, 1) |\n+-----------------+\n| 1.3 |\n+-----------------+\n\nSELECT ROUND(1.298, 0);\n+-----------------+\n| ROUND(1.298, 0) |\n+-----------------+\n| 1 |\n+-----------------+\n\nSELECT ROUND(23.298, -1);\n+-------------------+\n| ROUND(23.298, -1) |\n+-------------------+\n| 20 |\n+-------------------+\n\nURL: https://mariadb.com/kb/en/round/','','https://mariadb.com/kb/en/round/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (70,4,'SIGN','Syntax\n------\n\nSIGN(X)\n\nDescription\n-----------\n\nReturns the sign of the argument as -1, 0, or 1, depending on whether X is\nnegative, zero, or positive.\n\nExamples\n--------\n\nSELECT SIGN(-32);\n+-----------+\n| SIGN(-32) |\n+-----------+\n| -1 |\n+-----------+\n\nSELECT SIGN(0);\n+---------+\n| SIGN(0) |\n+---------+\n| 0 |\n+---------+\n\nSELECT SIGN(234);\n+-----------+\n| SIGN(234) |\n+-----------+\n| 1 |\n+-----------+\n\nURL: https://mariadb.com/kb/en/sign/','','https://mariadb.com/kb/en/sign/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (71,4,'SIN','Syntax\n------\n\nSIN(X)\n\nDescription\n-----------\n\nReturns the sine of X, where X is given in radians.\n\nExamples\n--------\n\nSELECT SIN(1.5707963267948966);\n+-------------------------+\n| SIN(1.5707963267948966) |\n+-------------------------+\n| 1 |\n+-------------------------+\n\nSELECT SIN(PI());\n+----------------------+\n| SIN(PI()) |\n+----------------------+\n| 1.22460635382238e-16 |\n+----------------------+\n\nSELECT ROUND(SIN(PI()));\n+------------------+\n| ROUND(SIN(PI())) |\n+------------------+\n| 0 |\n+------------------+\n\nURL: https://mariadb.com/kb/en/sin/','','https://mariadb.com/kb/en/sin/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (72,4,'SQRT','Syntax\n------\n\nSQRT(X)\n\nDescription\n-----------\n\nReturns the square root of X. If X is negative, NULL is returned.\n\nExamples\n--------\n\nSELECT SQRT(4);\n+---------+\n| SQRT(4) |\n+---------+\n| 2 |\n+---------+\n\nSELECT SQRT(20);\n+------------------+\n| SQRT(20) |\n+------------------+\n| 4.47213595499958 |\n+------------------+\n\nSELECT SQRT(-16);\n+-----------+\n| SQRT(-16) |\n+-----------+\n| NULL |\n+-----------+\n\nSELECT SQRT(1764);\n+------------+\n| SQRT(1764) |\n+------------+\n| 42 |\n+------------+\n\nURL: https://mariadb.com/kb/en/sqrt/','','https://mariadb.com/kb/en/sqrt/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (73,4,'TAN','Syntax\n------\n\nTAN(X)\n\nDescription\n-----------\n\nReturns the tangent of X, where X is given in radians.\n\nExamples\n--------\n\nSELECT TAN(0.7853981633974483);\n+-------------------------+\n| TAN(0.7853981633974483) |\n+-------------------------+\n| 0.9999999999999999 |\n+-------------------------+\n\nSELECT TAN(PI());\n+-----------------------+\n| TAN(PI()) |\n+-----------------------+\n| -1.22460635382238e-16 |\n+-----------------------+\n\nSELECT TAN(PI()+1);\n+-----------------+\n| TAN(PI()+1) |\n+-----------------+\n| 1.5574077246549 |\n+-----------------+\n\nSELECT TAN(RADIANS(PI()));\n+--------------------+\n| TAN(RADIANS(PI())) |\n+--------------------+\n| 0.0548861508080033 |\n+--------------------+\n\nURL: https://mariadb.com/kb/en/tan/','','https://mariadb.com/kb/en/tan/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (74,4,'TRUNCATE','This page documents the TRUNCATE function. See TRUNCATE TABLE for the DDL\nstatement.\n\nSyntax\n------\n\nTRUNCATE(X,D)\n\nDescription\n-----------\n\nReturns the number X, truncated to D decimal places. If D is 0, the result has\nno decimal point or fractional part. D can be negative to cause D digits left\nof the decimal point of the value X to become zero.\n\nExamples\n--------\n\nSELECT TRUNCATE(1.223,1);\n+-------------------+\n| TRUNCATE(1.223,1) |\n+-------------------+\n| 1.2 |\n+-------------------+\n\nSELECT TRUNCATE(1.999,1);\n+-------------------+\n| TRUNCATE(1.999,1) |\n+-------------------+\n| 1.9 |\n+-------------------+\n\nSELECT TRUNCATE(1.999,0); \n+-------------------+\n| TRUNCATE(1.999,0) |\n+-------------------+\n| 1 |\n+-------------------+\n\nSELECT TRUNCATE(-1.999,1);\n+--------------------+\n| TRUNCATE(-1.999,1) |\n+--------------------+\n| -1.9 |\n+--------------------+\n\nSELECT TRUNCATE(122,-2);\n+------------------+\n| TRUNCATE(122,-2) |\n+------------------+\n| 100 |\n+------------------+\n\nSELECT TRUNCATE(10.28*100,0);\n+-----------------------+\n| TRUNCATE(10.28*100,0) |\n+-----------------------+\n| 1028 |\n+-----------------------+\n\nURL: https://mariadb.com/kb/en/truncate/','','https://mariadb.com/kb/en/truncate/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (75,5,'INSTALL PLUGIN','Syntax\n------\n\nINSTALL PLUGIN [IF NOT EXISTS] plugin_name SONAME \'plugin_library\'\n\nDescription\n-----------\n\nThis statement installs an individual plugin from the specified library. To\ninstall the whole library (which could be required), use INSTALL SONAME. See\nalso Installing a Plugin.\n\nplugin_name is the name of the plugin as defined in the plugin declaration\nstructure contained in the library file. Plugin names are not case sensitive.\nFor maximal compatibility, plugin names should be limited to ASCII letters,\ndigits, and underscore, because they are used in C source files, shell command\nlines, M4 and Bourne shell scripts, and SQL environments.\n\nplugin_library is the name of the shared library that contains the plugin\ncode. The file name extension can be omitted (which makes the statement look\nthe same on all architectures).\n\nThe shared library must be located in the plugin directory (that is, the\ndirectory named by the plugin_dir system variable). The library must be in the\nplugin directory itself, not in a subdirectory. By default, plugin_dir is\nplugin directory under the directory named by the pkglibdir configuration\nvariable, but it can be changed by setting the value of plugin_dir at server\nstartup. For example, set its value in a my.cnf file:\n\n[mysqld]\nplugin_dir=/path/to/plugin/directory\nIf the value of plugin_dir is a relative path name, it is taken to be relative\nto the MySQL base directory (the value of the basedir system variable).\n\nINSTALL PLUGIN adds a line to the mysql.plugin table that describes the\nplugin. This table contains the plugin name and library file name.\n\nINSTALL PLUGIN causes the server to read option (my.cnf) files just as during\nserver startup. This enables the plugin to pick up any relevant options from\nthose files. It is possible to add plugin options to an option file even\nbefore loading a plugin (if the loose prefix is used). It is also possible to\nuninstall a plugin, edit my.cnf, and install the plugin again. Restarting the\nplugin this way enables it to the new option values without a server restart.\n\nINSTALL PLUGIN also loads and initializes the plugin code to make the plugin\navailable for use. A plugin is initialized by executing its initialization\nfunction, which handles any setup that the plugin must perform before it can\nbe used.\n\nTo use INSTALL PLUGIN, you must have the INSERT privilege for the mysql.plugin\ntable.\n\nAt server startup, the server loads and initializes any plugin that is listed\nin the mysql.plugin table. This means that a plugin is installed with INSTALL\nPLUGIN only once, not every time the server starts. Plugin loading at startup\ndoes not occur if the server is started with the --skip-grant-tables option.\n\nWhen the server shuts down, it executes the de-initialization function for\neach plugin that is loaded so that the plugin has a chance to perform any\nfinal cleanup.\n\nIf you need to load plugins for a single server startup when the\n--skip-grant-tables option is given (which tells the server not to read system\ntables), use the --plugin-load mysqld option.\n\nMariaDB starting with 10.4.0\n----------------------------\n\nIF NOT EXISTS\n-------------\n\nWhen the IF NOT EXISTS clause is used, MariaDB will return a note instead of\nan error if the specified plugin already exists. See SHOW WARNINGS.\n\nExamples\n--------\n\nINSTALL PLUGIN sphinx SONAME \'ha_sphinx.so\';\n\nThe extension can also be omitted:\n\nINSTALL PLUGIN innodb SONAME \'ha_xtradb\';\n\nFrom MariaDB 10.4.0:\n\nINSTALL PLUGIN IF NOT EXISTS example SONAME \'ha_example\';\nQuery OK, 0 rows affected (0.104 sec)\n\nINSTALL PLUGIN IF NOT EXISTS example SONAME \'ha_example\';\nQuery OK, 0 rows affected, 1 warning (0.000 sec)\n\nSHOW WARNINGS;\n+-------+------+------------------------------------+\n| Level | Code | Message |\n+-------+------+------------------------------------+\n| Note | 1968 | Plugin \'example\' already installed |\n+-------+------+------------------------------------+\n\nURL: https://mariadb.com/kb/en/install-plugin/','','https://mariadb.com/kb/en/install-plugin/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (76,5,'UNINSTALL PLUGIN','Syntax\n------\n\nUNINSTALL PLUGIN [IF EXISTS] plugin_name\n\nDescription\n-----------\n\nThis statement removes a single installed plugin. To uninstall the whole\nlibrary which contains the plugin, use UNINSTALL SONAME. You cannot uninstall\na plugin if any table that uses it is open.\n\nplugin_name must be the name of some plugin that is listed in the mysql.plugin\ntable. The server executes the plugin\'s deinitialization function and removes\nthe row for the plugin from the mysql.plugin table, so that subsequent server\nrestarts will not load and initialize the plugin. UNINSTALL PLUGIN does not\nremove the plugin\'s shared library file.\n\nTo use UNINSTALL PLUGIN, you must have the DELETE privilege for the\nmysql.plugin table.\n\nMariaDB starting with 10.4.0\n----------------------------\n\nIF EXISTS\n---------\n\nIf the IF EXISTS clause is used, MariaDB will return a note instead of an\nerror if the plugin does not exist. See SHOW WARNINGS.\n\nExamples\n--------\n\nUNINSTALL PLUGIN example;\n\nFrom MariaDB 10.4.0:\n\nUNINSTALL PLUGIN IF EXISTS example;\nQuery OK, 0 rows affected (0.099 sec)\n\nUNINSTALL PLUGIN IF EXISTS example;\nQuery OK, 0 rows affected, 1 warning (0.000 sec)\n\nSHOW WARNINGS;\n+-------+------+-------------------------------+\n| Level | Code | Message |\n+-------+------+-------------------------------+\n| Note | 1305 | PLUGIN example does not exist |\n+-------+------+-------------------------------+\n\nURL: https://mariadb.com/kb/en/uninstall-plugin/','','https://mariadb.com/kb/en/uninstall-plugin/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (77,5,'INSTALL SONAME','Syntax\n------\n\nINSTALL SONAME \'plugin_library\'\n\nDescription\n-----------\n\nThis statement is a variant of INSTALL PLUGIN. It installs all plugins from a\ngiven plugin_library. See INSTALL PLUGIN for details.\n\nplugin_library is the name of the shared library that contains the plugin\ncode. The file name extension (for example, libmyplugin.so or libmyplugin.dll)\ncan be omitted (which makes the statement look the same on all architectures).\n\nThe shared library must be located in the plugin directory (that is, the\ndirectory named by the plugin_dir system variable). The library must be in the\nplugin directory itself, not in a subdirectory. By default, plugin_dir is\nplugin directory under the directory named by the pkglibdir configuration\nvariable, but it can be changed by setting the value of plugin_dir at server\nstartup. For example, set its value in a my.cnf file:\n\n[mysqld]\nplugin_dir=/path/to/plugin/directory\nIf the value of plugin_dir is a relative path name, it is taken to be relative\nto the MySQL base directory (the value of the basedir system variable).\n\nINSTALL SONAME adds one or more lines to the mysql.plugin table that describes\nthe plugin. This table contains the plugin name and library file name.\n\nINSTALL SONAME causes the server to read option (my.cnf) files just as during\nserver startup. This enables the plugin to pick up any relevant options from\nthose files. It is possible to add plugin options to an option file even\nbefore loading a plugin (if the loose prefix is used). It is also possible to\nuninstall a plugin, edit my.cnf, and install the plugin again. Restarting the\nplugin this way enables it to the new option values without a server restart.\n\nINSTALL SONAME also loads and initializes the plugin code to make the plugin\navailable for use. A plugin is initialized by executing its initialization\nfunction, which handles any setup that the plugin must perform before it can\nbe used.\n\nTo use INSTALL SONAME, you must have the INSERT privilege for the mysql.plugin\ntable.\n\nAt server startup, the server loads and initializes any plugin that is listed\nin the mysql.plugin table. This means that a plugin is installed with INSTALL\nSONAME only once, not every time the server starts. Plugin loading at startup\ndoes not occur if the server is started with the --skip-grant-tables option.\n\nWhen the server shuts down, it executes the de-initialization function for\neach plugin that is loaded so that the plugin has a chance to perform any\nfinal cleanup.\n\nIf you need to load plugins for a single server startup when the\n--skip-grant-tables option is given (which tells the server not to read system\ntables), use the --plugin-load mysqld option.\n\nIf you need to install only one plugin from a library, use the INSTALL PLUGIN\nstatement.\n\nExamples\n--------\n\nTo load the XtraDB storage engine and all of its information_schema tables\nwith one statement, use\n\nINSTALL SONAME \'ha_xtradb\';\n\nThis statement can be used instead of INSTALL PLUGIN even when the library\ncontains only one plugin:\n\nINSTALL SONAME \'ha_sequence\';\n\nURL: https://mariadb.com/kb/en/install-soname/','','https://mariadb.com/kb/en/install-soname/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (78,5,'UNINSTALL SONAME','Syntax\n------\n\nUNINSTALL SONAME [IF EXISTS] \'plugin_library\'\n\nDescription\n-----------\n\nThis statement is a variant of UNINSTALL PLUGIN statement, that removes all\nplugins belonging to a specified plugin_library. See UNINSTALL PLUGIN for\ndetails.\n\nplugin_library is the name of the shared library that contains the plugin\ncode. The file name extension (for example, libmyplugin.so or libmyplugin.dll)\ncan be omitted (which makes the statement look the same on all architectures).\n\nTo use UNINSTALL SONAME, you must have the DELETE privilege for the\nmysql.plugin table.\n\nMariaDB starting with 10.4.0\n----------------------------\n\nIF EXISTS\n---------\n\nIf the IF EXISTS clause is used, MariaDB will return a note instead of an\nerror if the plugin library does not exist. See SHOW WARNINGS.\n\nExamples\n--------\n\nTo uninstall the XtraDB plugin and all of its information_schema tables with\none statement, use\n\nUNINSTALL SONAME \'ha_xtradb\';\n\nFrom MariaDB 10.4.0:\n\nUNINSTALL SONAME IF EXISTS \'ha_example\';\nQuery OK, 0 rows affected (0.099 sec)\n\nUNINSTALL SONAME IF EXISTS \'ha_example\';\nQuery OK, 0 rows affected, 1 warning (0.000 sec)\n\nSHOW WARNINGS;\n+-------+------+-------------------------------------+\n| Level | Code | Message |\n+-------+------+-------------------------------------+\n| Note | 1305 | SONAME ha_example.so does not exist |\n+-------+------+-------------------------------------+\n\nURL: https://mariadb.com/kb/en/uninstall-soname/','','https://mariadb.com/kb/en/uninstall-soname/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (79,5,'Plugin Overview','Plugins are server components that enhance MariaDB in some way. These can be\nanything from new storage engines, plugins for enhancing full-text parsing, or\neven small enhancements, such as a plugin to get a timestamp as an integer.\n\nQuerying Plugin Information\n---------------------------\n\nThere are a number of ways to see which plugins are currently active.\n\nA server almost always has a large number of active plugins, because the\nserver contains a large number of built-in plugins, which are active by\ndefault and cannot be uninstalled.\n\nQuerying Plugin Information with SHOW PLUGINS\n---------------------------------------------\n\nThe SHOW PLUGINS statement can be used to query information about all active\nplugins.\n\nFor example:\n\nSHOW PLUGINS\\G;\n********************** 1. row **********************\n Name: binlog\n Status: ACTIVE\n Type: STORAGE ENGINE\nLibrary: NULL\nLicense: GPL\n********************** 2. row **********************\n Name: mysql_native_password\n Status: ACTIVE\n Type: AUTHENTICATION\nLibrary: NULL\nLicense: GPL\n********************** 3. row **********************\n Name: mysql_old_password\n Status: ACTIVE\n Type: AUTHENTICATION\nLibrary: NULL\nLicense: GPL\n...\n\nIf a plugin\'s Library column has a NULL value, then the plugin is built-in,\nand it cannot be uninstalled.\n\nQuerying Plugin Information with information_schema.PLUGINS\n-----------------------------------------------------------\n\nThe information_schema.PLUGINS table can be queried to get more detailed\ninformation about plugins.\n\nFor example:\n\nSELECT * FROM information_schema.PLUGINS\\G\n...\n*************************** 6. row ***************************\n PLUGIN_NAME: CSV\n PLUGIN_VERSION: 1.0\n PLUGIN_STATUS: ACTIVE\n PLUGIN_TYPE: STORAGE ENGINE\n PLUGIN_TYPE_VERSION: 100003.0\n PLUGIN_LIBRARY: NULL\nPLUGIN_LIBRARY_VERSION: NULL\n PLUGIN_AUTHOR: Brian Aker, MySQL AB\n PLUGIN_DESCRIPTION: CSV storage engine\n PLUGIN_LICENSE: GPL\n LOAD_OPTION: FORCE\n PLUGIN_MATURITY: Stable\n PLUGIN_AUTH_VERSION: 1.0\n*************************** 7. row ***************************\n PLUGIN_NAME: MEMORY\n PLUGIN_VERSION: 1.0\n PLUGIN_STATUS: ACTIVE\n PLUGIN_TYPE: STORAGE ENGINE\n PLUGIN_TYPE_VERSION: 100003.0\n PLUGIN_LIBRARY: NULL\nPLUGIN_LIBRARY_VERSION: NULL\n PLUGIN_AUTHOR: MySQL AB\n PLUGIN_DESCRIPTION: Hash based, stored in memory, useful for temporary\ntables\n PLUGIN_LICENSE: GPL\n LOAD_OPTION: FORCE\n PLUGIN_MATURITY: Stable\n PLUGIN_AUTH_VERSION: 1.0\n...\n\nIf a plugin\'s PLUGIN_LIBRARY column has the NULL value, then the plugin is\nbuilt-in, and it cannot be uninstalled.\n\nQuerying Plugin Information with mysql.plugin\n---------------------------------------------\n\nThe mysql.plugin table can be queried to get information about installed\nplugins.\n\nThis table only contains information about plugins that have been installed\nvia the following methods:\n\n* The INSTALL SONAME statement.\n* The INSTALL PLUGIN statement.\n* The mariadb-plugin utility.\n\nThis table does not contain information about:\n\n* Built-in plugins.\n* Plugins loaded with the --plugin-load-add option.\n* Plugins loaded with the --plugin-load option.\n\nThis table only contains enough information to reload the plugin when the\nserver is restarted, which means it only contains the plugin name and the\nplugin library.\n\nFor example:\n\nSELECT * FROM mysql.plugin;\n\n+------+------------+\n| name | dl |\n+------+------------+\n| PBXT | libpbxt.so |\n+------+------------+\n\nInstalling a Plugin\n-------------------\n\nThere are three primary ways to install a plugin:\n\n* A plugin can be installed dynamically with an SQL statement.\n* A plugin can be installed with a mariadbd option, but it requires a server\nrestart.\n* A plugin can be installed with the mariadb-plugin utility, while the server\nis completely offline.\n\nWhen you are installing a plugin, you also have to ensure that:\n\n* The server\'s plugin directory is properly configured, and the plugin\'s\nlibrary is in the plugin directory.\n* The server\'s minimum plugin maturity is properly configured, and the plugin\nis mature enough to be installed.\n\nInstalling a Plugin Dynamically\n-------------------------------\n\nA plugin can be installed dynamically by executing either the INSTALL SONAME\nor the INSTALL PLUGIN statement.\n\nIf a plugin is installed with one of these statements, then a record will be\nadded to the mysql.plugins table for the plugin. This means that the plugin\nwill automatically be loaded every time the server restarts, unless\nspecifically uninstalled or deactivated.\n\nInstalling a Plugin with INSTALL SONAME\n---------------------------------------\n\nYou can install a plugin dynamically by executing the INSTALL SONAME\nstatement. INSTALL SONAME installs all plugins from the given plugin library.\nThis could be required for some plugin libraries.\n\nFor example, to install all plugins in the server_audit plugin library (which\nis currently only the server_audit audit plugin), you could execute the\nfollowing:\n\nINSTALL SONAME \'server_audit\';\n\nInstalling a Plugin with INSTALL PLUGIN\n---------------------------------------\n\nYou can install a plugin dynamically by executing the INSTALL PLUGIN\nstatement. INSTALL PLUGIN installs a single plugin from the given plugin\nlibrary.\n\nFor example, to install the server_audit audit plugin from the server_audit\nplugin library, you could execute the following:\n\nINSTALL PLUGIN server_audit SONAME \'server_audit\';\n\nInstalling a Plugin with Plugin Load Options\n--------------------------------------------\n\nA plugin can be installed with a mariadbd option by providing either the\n--plugin-load-add or the --plugin-load option.\n\nIf a plugin is installed with one of these options, then a record will not be\nadded to the mysql.plugins table for the plugin. This means that if the server\nis restarted without the same option set, then the plugin will not\nautomatically be loaded.\n\nInstalling a Plugin with --plugin-load-add\n------------------------------------------\n\nYou can install a plugin with the --plugin-load-add option by specifying the\noption as a command-line argument to mariadbd or by specifying the option in a\nrelevant server option group in an option file.\n\nThe --plugin-load-add option uses the following format:\n\n* Plugins can be specified in the format name=library, where name is the\nplugin name and library is the plugin library. This format installs a single\nplugin from the given plugin library.\n* Plugins can also be specified in the format library, where library is the\nplugin library. This format installs all plugins from the given plugin library.\n* Multiple plugins can be specified by separating them with semicolons.\n\nFor example, to install all plugins in the server_audit plugin library (which\nis currently only the server_audit audit plugin) and also the ed25519\nauthentication plugin from the auth_ed25519 plugin library, you could set the\noption to the following values on the command-line:\n\n$ mariadbd --user=mysql --plugin-load-add=\'server_audit\'\n--plugin-load-add=\'ed25519=auth_ed25519\'\n\nYou could also set the option to the same values in an option file:\n\n[mariadb]\n...\nplugin_load_add = server_audit\nplugin_load_add = ed25519=auth_ed25519\n\nSpecial care must be taken when specifying both the --plugin-load option and\nthe --plugin-load-add option together. The --plugin-load option resets the\nplugin load list, and this can cause unexpected problems if you are not aware.\nThe --plugin-load-add option does not reset the plugin load list, so it is\nmuch safer to use. See Specifying Multiple Plugin Load Options for more\ninformation.\n\nInstalling a Plugin with --plugin-load\n--------------------------------------\n\nYou can install a plugin with the --plugin-load option by specifying the\noption as a command-line argument to mariadbd or by specifying the option in a\nrelevant server option group in an option file.\n\nThe --plugin-load option uses the following format:\n\n* Plugins can be specified in the format name=library, where name is the\nplugin name and library is the plugin library. This format installs a single\nplugin from the given plugin library.\n* Plugins can also be specified in the format library, where library is the\nplugin library. This format installs all plugins from the given plugin library.\n* Multiple plugins can be specified by separating them with semicolons.\n\nFor example, to install all plugins in the server_audit plugin library (which\nis currently only the server_audit audit plugin) and also the ed25519\nauthentication plugin from the auth_ed25519 plugin library, you could set the\noption to the following values on the command-line:\n\n$ mariadbd --user=mysql --plugin-load=\'server_audit;ed25519=auth_ed25519\'\n\nYou could also set the option to the same values in an option file:\n\n[mariadb]\n...\nplugin_load = server_audit;ed25519=auth_ed25519\n\nSpecial care must be taken when specifying the --plugin-load option multiple\ntimes, or when specifying both the --plugin-load option and the\n--plugin-load-add option together. The --plugin-load option resets the plugin\nload list, and this can cause unexpected problems if you are not aware. The\n--plugin-load-add option does not reset the plugin load list, so it is much\nsafer to use. See Specifying Multiple Plugin Load Options for more information.\n\nSpecifying Multiple Plugin Load Options\n---------------------------------------\n\nSpecial care must be taken when specifying the --plugin-load option multiple\ntimes, or when specifying both the --plugin-load option and the\n--plugin-load-add option. The --plugin-load option resets the plugin load\nlist, and this can cause unexpected problems if you are not aware. The\n--plugin-load-add option does not reset the plugin load list, so it is much\nsafer to use.\n\nThis can have the following consequences:\n\n* If the --plugin-load option is specified multiple times, then only the last\ninstance will have any effect. For example, in the following case, the first\ninstance of the option is reset:\n\n[mariadb]\n...\nplugin_load = server_audit\nplugin_load = ed25519=auth_ed25519\n\n* If the --plugin-load option is specified after the --plugin-load-add option,\nthen it will also reset the changes made by that option. For example, in the\nfollowing case, the --plugin-load-add option does not do anything, because the\nsubsequent --plugin-load option resets the plugin load list:\n\n[mariadb]\n...\nplugin_load_add = server_audit\nplugin_load = ed25519=auth_ed25519\n\n* In contrast, if the --plugin-load option is specified before the\n--plugin-load-add option, then it will work fine, because the\n--plugin-load-add option does not reset the plugin load list. For example, in\nthe following case, both plugins are properly loaded:\n\n[mariadb]\n...\nplugin_load = server_audit\nplugin_load_add = ed25519=auth_ed25519\n\nInstalling a Plugin with mariadb-plugin\n---------------------------------------\n\nA plugin can be installed with the mariadb-plugin utility if the server is\ncompletely offline.\n\nThe syntax is:\n\nmariadb-plugin [options] ENABLE|DISABLE\n\nFor example, to install the server_audit audit plugin, you could execute the\nfollowing:\n\nmariadb-plugin server_audit ENABLE\n\nIf a plugin is installed with this utility, then a record will be added to the\nmysql.plugins table for the plugin. This means that the plugin will\nautomatically be loaded every time the server restarts, unless specifically\nuninstalled or deactivated.\n\nConfiguring the Plugin Directory\n--------------------------------\n\nWhen a plugin is being installed, the server looks for the plugin\'s library in\nthe server\'s plugin directory. This directory is configured by the plugin_dir\nsystem variable. This can be specified as a command-line argument to mariadbd\nor it can be specified in a relevant server option group in an option file.\nFor example:\n\n[mariadb]\n...\nplugin_dir = /usr/lib64/mysql/plugin\n\nConfiguring the Minimum Plugin Maturity\n---------------------------------------\n\nWhen a plugin is being installed, the server compares the plugin\'s maturity\nlevel against the server\'s minimum allowed plugin maturity. This can help\nprevent users from using unstable plugins on production servers. This minimum\nplugin maturity is configured by the plugin_maturity system variable. This can\nbe specified as a command-line argument to mariadbd or it can be specified in\na relevant server option group in an option file. For example:\n\n[mariadb]\n...\nplugin_maturity = stable\n\nConfiguring Plugin Activation at Server Startup\n-----------------------------------------------\n\nA plugin will be loaded by default when the server starts if:\n\n* The plugin was installed with the INSTALL SONAME statement.\n* The plugin was installed with the INSTALL PLUGIN statement.\n* The plugin was installed with the mariadb-plugin utility.\n* The server is configured to load the plugin with the --plugin-load-add\noption.\n* The server is configured to load the plugin with the --plugin-load option.\n\nThis behavior can be changed with special options that take the form\n--plugin-name. For example, for the server_audit audit plugin, the special\noption is called --server-audit.\n\nThe possible values for these special options are:\n\n+---------------------------------------+------------------------------------+\n| Option Value | Description |\n+---------------------------------------+------------------------------------+\n| OFF | Disables the plugin without |\n| | removing it from the |\n| | mysql.plugins table. |\n+---------------------------------------+------------------------------------+\n| ON | Enables the plugin. If the plugin |\n| | cannot be initialized, then the |\n| | server will still continue |\n| | starting up, but the plugin will |\n| | be disabled. |\n+---------------------------------------+------------------------------------+','','https://mariadb.com/kb/en/plugin-overview/'); -update help_topic set description = CONCAT(description, '\n| FORCE | Enables the plugin. If the plugin |\n| | cannot be initialized, then the |\n| | server will fail to start with an |\n| | error. |\n+---------------------------------------+------------------------------------+\n| FORCE_PLUS_PERMANENT | Enables the plugin. If the plugin |\n| | cannot be initialized, then the |\n| | server will fail to start with an |\n| | error. In addition, the plugin |\n| | cannot be uninstalled with |\n| | UNINSTALL SONAME or UNINSTALL |\n| | PLUGIN while the server is |\n| | running. |\n+---------------------------------------+------------------------------------+\n\nA plugin\'s status can be found by looking at the PLUGIN_STATUS column of the\ninformation_schema.PLUGINS table.\n\nUninstalling Plugins\n--------------------\n\nPlugins that are found in the mysql.plugin table, that is those that were\ninstalled with INSTALL SONAME, INSTALL PLUGIN or mariadb-plugin can be\nuninstalled in one of two ways:\n\n* The UNINSTALL SONAME or the UNINSTALL PLUGIN statement while the server is\nrunning\n* With mariadb-plugin while the server is offline.\n\nPlugins that were enabled as a --plugin-load option do not need to be\nuninstalled. If --plugin-load is omitted the next time the server starts, or\nthe plugin is not listed as one of the --plugin-load entries, the plugin will\nnot be loaded.\n\nUNINSTALL PLUGIN uninstalls a single installed plugin, while UNINSTALL SONAME\nuninstalls all plugins belonging to a given library.\n\nURL: https://mariadb.com/kb/en/plugin-overview/') WHERE help_topic_id = 79; -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (80,6,'MBR Definition','Description\n-----------\n\nThe MBR (Minimum Bounding Rectangle), or Envelope is the bounding geometry,\nformed by the minimum and maximum (X,Y) coordinates:\n\nExamples\n--------\n\n((MINX MINY, MAXX MINY, MAXX MAXY, MINX MAXY, MINX MINY))\n\nURL: https://mariadb.com/kb/en/mbr-definition/','','https://mariadb.com/kb/en/mbr-definition/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (81,6,'MBRContains','Syntax\n------\n\nMBRContains(g1,g2)\n\nDescription\n-----------\n\nReturns 1 or 0 to indicate whether the Minimum Bounding Rectangle of g1\ncontains the Minimum Bounding Rectangle of g2. This tests the opposite\nrelationship as MBRWithin().\n\nExamples\n--------\n\nSET @g1 = GeomFromText(\'Polygon((0 0,0 3,3 3,3 0,0 0))\');\n\nSET @g2 = GeomFromText(\'Point(1 1)\');\n\nSELECT MBRContains(@g1,@g2), MBRContains(@g2,@g1);\n+----------------------+----------------------+\n| MBRContains(@g1,@g2) | MBRContains(@g2,@g1) |\n+----------------------+----------------------+\n| 1 | 0 |\n+----------------------+----------------------+\n\nURL: https://mariadb.com/kb/en/mbrcontains/','','https://mariadb.com/kb/en/mbrcontains/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (82,6,'MBRDisjoint','Syntax\n------\n\nMBRDisjoint(g1,g2)\n\nDescription\n-----------\n\nReturns 1 or 0 to indicate whether the Minimum Bounding Rectangles of the two\ngeometries g1 and g2 are disjoint. Two geometries are disjoint if they do not\nintersect, that is touch or overlap.\n\nExamples\n--------\n\nSET @g1 = GeomFromText(\'Polygon((0 0,0 3,3 3,3 0,0 0))\');\nSET @g2 = GeomFromText(\'Polygon((4 4,4 7,7 7,7 4,4 4))\');\nSELECTmbrdisjoint(@g1,@g2);\n+----------------------+\n| mbrdisjoint(@g1,@g2) |\n+----------------------+\n| 1 |\n+----------------------+\n\nSET @g1 = GeomFromText(\'Polygon((0 0,0 3,3 3,3 0,0 0))\');\nSET @g2 = GeomFromText(\'Polygon((3 3,3 6,6 6,6 3,3 3))\');\nSELECT mbrdisjoint(@g1,@g2);\n+----------------------+\n| mbrdisjoint(@g1,@g2) |\n+----------------------+\n| 0 |\n+----------------------+\n\nURL: https://mariadb.com/kb/en/mbrdisjoint/','','https://mariadb.com/kb/en/mbrdisjoint/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (83,6,'MBREqual','Syntax\n------\n\nMBREqual(g1,g2)\n\nDescription\n-----------\n\nReturns 1 or 0 to indicate whether the Minimum Bounding Rectangles of the two\ngeometries g1 and g2 are the same.\n\nExamples\n--------\n\nSET @g1=GEOMFROMTEXT(\'LINESTRING(0 0, 1 2)\');\nSET @g2=GEOMFROMTEXT(\'POLYGON((0 0, 0 2, 1 2, 1 0, 0 0))\');\nSELECT MbrEqual(@g1,@g2);\n+-------------------+\n| MbrEqual(@g1,@g2) |\n+-------------------+\n| 1 |\n+-------------------+\n\nSET @g1=GEOMFROMTEXT(\'LINESTRING(0 0, 1 3)\');\nSET @g2=GEOMFROMTEXT(\'POLYGON((0 0, 0 2, 1 4, 1 0, 0 0))\');\nSELECT MbrEqual(@g1,@g2);\n+-------------------+\n| MbrEqual(@g1,@g2) |\n+-------------------+\n| 0 |\n+-------------------+\n\nURL: https://mariadb.com/kb/en/mbrequal/','','https://mariadb.com/kb/en/mbrequal/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (84,6,'MBRIntersects','Syntax\n------\n\nMBRIntersects(g1,g2)\n\nDescription\n-----------\n\nReturns 1 or 0 to indicate whether the Minimum Bounding Rectangles of the two\ngeometries g1 and g2 intersect.\n\nExamples\n--------\n\nSET @g1 = GeomFromText(\'Polygon((0 0,0 3,3 3,3 0,0 0))\');\nSET @g2 = GeomFromText(\'Polygon((3 3,3 6,6 6,6 3,3 3))\');\nSELECT mbrintersects(@g1,@g2);\n+------------------------+\n| mbrintersects(@g1,@g2) |\n+------------------------+\n| 1 |\n+------------------------+\n\nSET @g1 = GeomFromText(\'Polygon((0 0,0 3,3 3,3 0,0 0))\');\nSET @g2 = GeomFromText(\'Polygon((4 4,4 7,7 7,7 4,4 4))\');\nSELECT mbrintersects(@g1,@g2);\n+------------------------+\n| mbrintersects(@g1,@g2) |\n+------------------------+\n| 0 |\n+------------------------+\n\nURL: https://mariadb.com/kb/en/mbrintersects/','','https://mariadb.com/kb/en/mbrintersects/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (85,6,'MBROverlaps','Syntax\n------\n\nMBROverlaps(g1,g2)\n\nDescription\n-----------\n\nReturns 1 or 0 to indicate whether the Minimum Bounding Rectangles of the two\ngeometries g1 and g2 overlap. The term spatially overlaps is used if two\ngeometries intersect and their intersection results in a geometry of the same\ndimension but not equal to either of the given geometries.\n\nExamples\n--------\n\nSET @g1 = GeomFromText(\'Polygon((0 0,0 3,3 3,3 0,0 0))\');\nSET @g2 = GeomFromText(\'Polygon((4 4,4 7,7 7,7 4,4 4))\');\nSELECT mbroverlaps(@g1,@g2);\n+----------------------+\n| mbroverlaps(@g1,@g2) |\n+----------------------+\n| 0 |\n+----------------------+\n\nSET @g1 = GeomFromText(\'Polygon((0 0,0 3,3 3,3 0,0 0))\');\nSET @g2 = GeomFromText(\'Polygon((3 3,3 6,6 6,6 3,3 3))\');\nSELECT mbroverlaps(@g1,@g2);\n+----------------------+\n| mbroverlaps(@g1,@g2) |\n+----------------------+\n| 0 |\n+----------------------+\n\nSET @g1 = GeomFromText(\'Polygon((0 0,0 4,4 4,4 0,0 0))\');\nSET @g2 = GeomFromText(\'Polygon((3 3,3 6,6 6,6 3,3 3))\');\nSELECT mbroverlaps(@g1,@g2);\n+----------------------+\n| mbroverlaps(@g1,@g2) |\n+----------------------+\n| 1 |\n+----------------------+\n\nURL: https://mariadb.com/kb/en/mbroverlaps/','','https://mariadb.com/kb/en/mbroverlaps/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (86,6,'MBRTouches','Syntax\n------\n\nMBRTouches(g1,g2)\n\nDescription\n-----------\n\nReturns 1 or 0 to indicate whether the Minimum Bounding Rectangles of the two\ngeometries g1 and g2 touch. Two geometries spatially touch if the interiors of\nthe geometries do not intersect, but the boundary of one of the geometries\nintersects either the boundary or the interior of the other.\n\nExamples\n--------\n\nSET @g1 = GeomFromText(\'Polygon((0 0,0 3,3 3,3 0,0 0))\');\nSET @g2 = GeomFromText(\'Polygon((4 4,4 7,7 7,7 4,4 4))\');\nSELECT mbrtouches(@g1,@g2);\n+---------------------+\n| mbrtouches(@g1,@g2) |\n+---------------------+\n| 0 |\n+---------------------+\n\nSET @g1 = GeomFromText(\'Polygon((0 0,0 3,3 3,3 0,0 0))\');\nSET @g2 = GeomFromText(\'Polygon((3 3,3 6,6 6,6 3,3 3))\');\nSELECT mbrtouches(@g1,@g2);\n+---------------------+\n| mbrtouches(@g1,@g2) |\n+---------------------+\n| 1 |\n+---------------------+\n\nSET @g1 = GeomFromText(\'Polygon((0 0,0 4,4 4,4 0,0 0))\');\nSET @g2 = GeomFromText(\'Polygon((3 3,3 6,6 6,6 3,3 3))\');\nSELECT mbrtouches(@g1,@g2);\n+---------------------+\n| mbrtouches(@g1,@g2) |\n+---------------------+\n| 0 |\n+---------------------+\n\nURL: https://mariadb.com/kb/en/mbrtouches/','','https://mariadb.com/kb/en/mbrtouches/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (87,6,'MBRWithin','Syntax\n------\n\nMBRWithin(g1,g2)\n\nDescription\n-----------\n\nReturns 1 or 0 to indicate whether the Minimum Bounding Rectangle of g1 is\nwithin the Minimum Bounding Rectangle of g2. This tests the opposite\nrelationship as MBRContains().\n\nExamples\n--------\n\nSET @g1 = GeomFromText(\'Polygon((0 0,0 3,3 3,3 0,0 0))\');\nSET @g2 = GeomFromText(\'Polygon((0 0,0 5,5 5,5 0,0 0))\');\nSELECT MBRWithin(@g1,@g2), MBRWithin(@g2,@g1);\n+--------------------+--------------------+\n| MBRWithin(@g1,@g2) | MBRWithin(@g2,@g1) |\n+--------------------+--------------------+\n| 1 | 0 |\n+--------------------+--------------------+\n\nURL: https://mariadb.com/kb/en/mbrwithin/','','https://mariadb.com/kb/en/mbrwithin/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (88,7,'CASE OPERATOR','Syntax\n------\n\nCASE value WHEN [compare_value] THEN result [WHEN [compare_value] THEN\nresult ...] [ELSE result] END\n\nCASE WHEN [condition] THEN result [WHEN [condition] THEN result ...]\n[ELSE result] END\n\nDescription\n-----------\n\nThe first version returns the result where value=compare_value. The second\nversion returns the result for the first condition that is true. If there was\nno matching result value, the result after ELSE is returned, or NULL if there\nis no ELSE part.\n\nThere is also a CASE statement, which differs from the CASE operator described\nhere.\n\nExamples\n--------\n\nSELECT CASE 1 WHEN 1 THEN \'one\' WHEN 2 THEN \'two\' ELSE \'more\' END;\n+------------------------------------------------------------+\n| CASE 1 WHEN 1 THEN \'one\' WHEN 2 THEN \'two\' ELSE \'more\' END |\n+------------------------------------------------------------+\n| one |\n+------------------------------------------------------------+\n\nSELECT CASE WHEN 1>0 THEN \'true\' ELSE \'false\' END;\n+--------------------------------------------+\n| CASE WHEN 1>0 THEN \'true\' ELSE \'false\' END |\n+--------------------------------------------+\n| true |\n+--------------------------------------------+\n\nSELECT CASE BINARY \'B\' WHEN \'a\' THEN 1 WHEN \'b\' THEN 2 END;\n+-----------------------------------------------------+\n| CASE BINARY \'B\' WHEN \'a\' THEN 1 WHEN \'b\' THEN 2 END |\n+-----------------------------------------------------+\n| NULL |\n+-----------------------------------------------------+\n\nURL: https://mariadb.com/kb/en/case-operator/','','https://mariadb.com/kb/en/case-operator/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (89,7,'IF Function','Syntax\n------\n\nIF(expr1,expr2,expr3)\n\nDescription\n-----------\n\nIf expr1 is TRUE (expr1 <> 0 and expr1 <> NULL) then IF() returns expr2;\notherwise it returns expr3. IF() returns a numeric or string value, depending\non the context in which it is used.\n\nNote: There is also an IF statement which differs from the IF() function\ndescribed here.\n\nExamples\n--------\n\nSELECT IF(1>2,2,3);\n+-------------+\n| IF(1>2,2,3) |\n+-------------+\n| 3 |\n+-------------+\n\nSELECT IF(1<2,\'yes\',\'no\');\n+--------------------+\n| IF(1<2,\'yes\',\'no\') |\n+--------------------+\n| yes |\n+--------------------+\n\nSELECT IF(STRCMP(\'test\',\'test1\'),\'no\',\'yes\');\n+---------------------------------------+\n| IF(STRCMP(\'test\',\'test1\'),\'no\',\'yes\') |\n+---------------------------------------+\n| no |\n+---------------------------------------+\n\nURL: https://mariadb.com/kb/en/if-function/','','https://mariadb.com/kb/en/if-function/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (90,7,'IFNULL','Syntax\n------\n\nIFNULL(expr1,expr2)\nNVL(expr1,expr2)\n\nDescription\n-----------\n\nIf expr1 is not NULL, IFNULL() returns expr1; otherwise it returns expr2.\nIFNULL() returns a numeric or string value, depending on the context in which\nit is used.\n\nFrom MariaDB 10.3, NVL() is an alias for IFNULL().\n\nExamples\n--------\n\nSELECT IFNULL(1,0); \n+-------------+\n| IFNULL(1,0) |\n+-------------+\n| 1 |\n+-------------+\n\nSELECT IFNULL(NULL,10);\n+-----------------+\n| IFNULL(NULL,10) |\n+-----------------+\n| 10 |\n+-----------------+\n\nSELECT IFNULL(1/0,10);\n+----------------+\n| IFNULL(1/0,10) |\n+----------------+\n| 10.0000 |\n+----------------+\n\nSELECT IFNULL(1/0,\'yes\');\n+-------------------+\n| IFNULL(1/0,\'yes\') |\n+-------------------+\n| yes |\n+-------------------+\n\nURL: https://mariadb.com/kb/en/ifnull/','','https://mariadb.com/kb/en/ifnull/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (91,7,'NULLIF','Syntax\n------\n\nNULLIF(expr1,expr2)\n\nDescription\n-----------\n\nReturns NULL if expr1 = expr2 is true, otherwise returns expr1. This is the\nsame as CASE WHEN expr1 = expr2 THEN NULL ELSE expr1 END.\n\nExamples\n--------\n\nSELECT NULLIF(1,1);\n+-------------+\n| NULLIF(1,1) |\n+-------------+\n| NULL |\n+-------------+\n\nSELECT NULLIF(1,2);\n+-------------+\n| NULLIF(1,2) |\n+-------------+\n| 1 |\n+-------------+\n\nURL: https://mariadb.com/kb/en/nullif/','','https://mariadb.com/kb/en/nullif/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (92,7,'NVL','MariaDB starting with 10.3\n--------------------------\nFrom MariaDB 10.3, NVL is a synonym for IFNULL.\n\nURL: https://mariadb.com/kb/en/nvl/','','https://mariadb.com/kb/en/nvl/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (93,7,'NVL2','MariaDB starting with 10.3\n--------------------------\nThe NLV2 function was introduced in MariaDB 10.3.0.\n\nSyntax\n------\n\nNVL2(expr1,expr2,expr3)\n\nDescription\n-----------\n\nThe NVL2 function returns a value based on whether a specified expression is\nNULL or not. If expr1 is not NULL, then NVL2 returns expr2. If expr1 is NULL,\nthen NVL2 returns expr3.\n\nExamples\n--------\n\nSELECT NVL2(NULL,1,2);\n+----------------+\n| NVL2(NULL,1,2) |\n+----------------+\n| 2 |\n+----------------+\n\nSELECT NVL2(\'x\',1,2);\n+---------------+\n| NVL2(\'x\',1,2) |\n+---------------+\n| 1 |\n+---------------+\n\nURL: https://mariadb.com/kb/en/nvl2/','','https://mariadb.com/kb/en/nvl2/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (94,8,'START TRANSACTION','Syntax\n------\n\nSTART TRANSACTION [transaction_property [, transaction_property] ...] | BEGIN\n[WORK]\nCOMMIT [WORK] [AND [NO] CHAIN] [[NO] RELEASE]\nROLLBACK [WORK] [AND [NO] CHAIN] [[NO] RELEASE]\nSET autocommit = {0 | 1}\n\ntransaction_property:\n WITH CONSISTENT SNAPSHOT\n | READ WRITE\n | READ ONLY\n\nDescription\n-----------\n\nThe START TRANSACTION or BEGIN statement begins a new transaction. COMMIT\ncommits the current transaction, making its changes permanent. ROLLBACK rolls\nback the current transaction, canceling its changes. The SET autocommit\nstatement disables or enables the default autocommit mode for the current\nsession.\n\nSTART TRANSACTION and SET autocommit = 1 implicitly commit the current\ntransaction, if any.\n\nThe optional WORK keyword is supported for COMMIT and ROLLBACK, as are the\nCHAIN and RELEASE clauses. CHAIN and RELEASE can be used for additional\ncontrol over transaction completion. The value of the completion_type system\nvariable determines the default completion behavior.\n\nThe AND CHAIN clause causes a new transaction to begin as soon as the current\none ends, and the new transaction has the same isolation level as the\njust-terminated transaction. The RELEASE clause causes the server to\ndisconnect the current client session after terminating the current\ntransaction. Including the NO keyword suppresses CHAIN or RELEASE completion,\nwhich can be useful if the completion_type system variable is set to cause\nchaining or release completion by default.\n\nAccess Mode\n-----------\n\nThe access mode specifies whether the transaction is allowed to write data or\nnot. By default, transactions are in READ WRITE mode (see the tx_read_only\nsystem variable). READ ONLY mode allows the storage engine to apply\noptimizations that cannot be used for transactions which write data. Note that\nunlike the global read_only mode, READ_ONLY ADMIN (and SUPER before MariaDB\n10.11.0) privilege doesn\'t allow writes and DDL statements on temporary tables\nare not allowed either.\n\nIt is not permitted to specify both READ WRITE and READ ONLY in the same\nstatement.\n\nREAD WRITE and READ ONLY can also be specified in the SET TRANSACTION\nstatement, in which case the specified mode is valid for all sessions, or for\nall subsequent transaction used by the current session.\n\nautocommit\n----------\n\nBy default, MariaDB runs with autocommit mode enabled. This means that as soon\nas you execute a statement that updates (modifies) a table, MariaDB stores the\nupdate on disk to make it permanent. To disable autocommit mode, use the\nfollowing statement:\n\nSET autocommit=0;\n\nAfter disabling autocommit mode by setting the autocommit variable to zero,\nchanges to transaction-safe tables (such as those for InnoDB or NDBCLUSTER)\nare not made permanent immediately. You must use COMMIT to store your changes\nto disk or ROLLBACK to ignore the changes.\n\nTo disable autocommit mode for a single series of statements, use the START\nTRANSACTION statement.\n\nDDL Statements\n--------------\n\nDDL statements (CREATE, ALTER, DROP) and administrative statements (FLUSH,\nRESET, OPTIMIZE, ANALYZE, CHECK, REPAIR, CACHE INDEX), transaction management\nstatements (BEGIN, START TRANSACTION) and LOAD DATA INFILE, cause an implicit\nCOMMIT and start a new transaction. An exception to this rule are the DDL that\noperate on temporary tables: you can CREATE, ALTER and DROP them without\ncausing any COMMIT, but those actions cannot be rolled back. This means that\nif you call ROLLBACK, the temporary tables you created in the transaction will\nremain, while the rest of the transaction will be rolled back.\n\nTransactions cannot be used in Stored Functions or Triggers. In Stored\nProcedures and Events BEGIN is not allowed, so you should use START\nTRANSACTION instead.\n\nA transaction acquires a metadata lock on every table it accesses to prevent\nother connections from altering their structure. The lock is released at the\nend of the transaction. This happens even with non-transactional storage\nengines (like MEMORY or CONNECT), so it makes sense to use transactions with\nnon-transactional tables.\n\nin_transaction\n--------------\n\nThe in_transaction system variable is a session-only, read-only variable that\nreturns 1 inside a transaction, and 0 if not in a transaction.\n\nWITH CONSISTENT SNAPSHOT\n------------------------\n\nThe WITH CONSISTENT SNAPSHOT option starts a consistent read for storage\nengines such as InnoDB that can do so, the same as if a START TRANSACTION\nfollowed by a SELECT from any InnoDB table was issued.\n\nSee Enhancements for START TRANSACTION WITH CONSISTENT SNAPSHOT.\n\nExamples\n--------\n\nSTART TRANSACTION;\nSELECT @A:=SUM(salary) FROM table1 WHERE type=1;\nUPDATE table2 SET summary=@A WHERE type=1;\nCOMMIT;\n\nURL: https://mariadb.com/kb/en/start-transaction/','','https://mariadb.com/kb/en/start-transaction/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (95,8,'COMMIT','The COMMIT statement ends a transaction, saving any changes to the data so\nthat they become visible to subsequent transactions. Also, unlocks metadata\nchanged by current transaction. If autocommit is set to 1, an implicit commit\nis performed after each statement. Otherwise, all transactions which don\'t end\nwith an explicit COMMIT are implicitly rollbacked and the changes are lost.\nThe ROLLBACK statement can be used to do this explicitly.\n\nThe required syntax for the COMMIT statement is as follows:\n\nCOMMIT [WORK] [AND [NO] CHAIN] [[NO] RELEASE]\n\nCOMMIT is the more important transaction terminator, as well as the more\ninteresting one. The basic form of the COMMIT statement is simply the keyword\nCOMMIT (the keyword WORK is simply noise and can be omitted without changing\nthe effect).\n\nThe optional AND CHAIN clause is a convenience for initiating a new\ntransaction as soon as the old transaction terminates. If AND CHAIN is\nspecified, then there is effectively nothing between the old and new\ntransactions, although they remain separate. The characteristics of the new\ntransaction will be the same as the characteristics of the old one — that is,\nthe new transaction will have the same access mode, isolation level and\ndiagnostics area size (we\'ll discuss all of these shortly) as the transaction\njust terminated.\n\nRELEASE tells the server to disconnect the client immediately after the\ncurrent transaction.\n\nThere are NO RELEASE and AND NO CHAIN options. By default, commits do not\nRELEASE or CHAIN, but it\'s possible to change this default behavior with the\ncompletion_type server system variable. In this case, the AND NO CHAIN and NO\nRELEASE options override the server default.\n\nURL: https://mariadb.com/kb/en/commit/','','https://mariadb.com/kb/en/commit/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (96,8,'ROLLBACK','The ROLLBACK statement rolls back (ends) a transaction, destroying any changes\nto SQL-data so that they never become visible to subsequent transactions. The\nrequired syntax for the ROLLBACK statement is as follows.\n\nROLLBACK [ WORK ] [ AND [ NO ] CHAIN ] \n[ TO [ SAVEPOINT ] { | } ]\n\nThe ROLLBACK statement will either end a transaction, destroying all data\nchanges that happened during any of the transaction, or it will just destroy\nany data changes that happened since you established a savepoint. The basic\nform of the ROLLBACK statement is just the keyword ROLLBACK (the keyword WORK\nis simply noise and can be omitted without changing the effect).\n\nThe optional AND CHAIN clause is a convenience for initiating a new\ntransaction as soon as the old transaction terminates. If AND CHAIN is\nspecified, then there is effectively nothing between the old and new\ntransactions, although they remain separate. The characteristics of the new\ntransaction will be the same as the characteristics of the old one — that is,\nthe new transaction will have the same access mode, isolation level and\ndiagnostics area size (we\'ll discuss all of these shortly) as the transaction\njust terminated. The AND NO CHAIN option just tells your DBMS to end the\ntransaction — that is, these four SQL statements are equivalent:\n\nROLLBACK; \nROLLBACK WORK; \nROLLBACK AND NO CHAIN; \nROLLBACK WORK AND NO CHAIN;\n\nAll of them end a transaction without saving any transaction characteristics.\nThe only other options, the equivalent statements:\n\nROLLBACK AND CHAIN;\nROLLBACK WORK AND CHAIN;\n\nboth tell your DBMS to end a transaction, but to save that transaction\'s\ncharacteristics for the next transaction.\n\nROLLBACK is much simpler than COMMIT: it may involve no more than a few\ndeletions (of Cursors, locks, prepared SQL statements and log-file entries).\nIt\'s usually assumed that ROLLBACK can\'t fail, although such a thing is\nconceivable (for example, an encompassing transaction might reject an attempt\nto ROLLBACK because it\'s lining up for a COMMIT).\n\nROLLBACK cancels all effects of a transaction. It does not cancel effects on\nobjects outside the DBMS\'s control (for example the values in host program\nvariables or the settings made by some SQL/CLI function calls). But in\ngeneral, it is a convenient statement for those situations when you say \"oops,\nthis isn\'t working\" or when you simply don\'t care whether your temporary work\nbecomes permanent or not.\n\nHere is a moot question. If all you\'ve been doing is SELECTs, so that there\nhave been no data changes, should you end the transaction with ROLLBACK or\nCOMMIT? It shouldn\'t really matter because both ROLLBACK and COMMIT do the\nsame transaction-terminating job. However, the popular conception is that\nROLLBACK implies failure, so after a successful series of SELECT statements\nthe convention is to end the transaction with COMMIT rather than ROLLBACK.\n\nMariaDB (and most other DBMSs) supports rollback of SQL-data change\nstatements, but not of SQL-Schema statements. This means that if you use any\nof CREATE, ALTER, DROP, GRANT, REVOKE, you are implicitly committing at\nexecution time.\n\nINSERT INTO Table_2 VALUES(5); \nDROP TABLE Table_3 CASCADE; \nROLLBACK;\n\nThe result will be that both the INSERT and the DROP will go through as\nseparate transactions so the ROLLBACK will have no effect.\n\nURL: https://mariadb.com/kb/en/rollback/','','https://mariadb.com/kb/en/rollback/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (97,8,'SET TRANSACTION','Syntax\n------\n\nSET [GLOBAL | SESSION] TRANSACTION\n transaction_property [, transaction_property] ...\n\ntransaction_property:\n ISOLATION LEVEL level\n | READ WRITE\n | READ ONLY\n\nlevel:\n REPEATABLE READ\n | READ COMMITTED\n | READ UNCOMMITTED\n | SERIALIZABLE\n\nDescription\n-----------\n\nThis statement sets the transaction isolation level or the transaction access\nmode globally, for the current session, or for the next transaction:\n\n* With the GLOBAL keyword, the statement sets the default\n transaction level globally for all subsequent sessions. Existing sessions are\n unaffected.\n* With the SESSION keyword, the statement sets the default\n transaction level for all subsequent transactions performed within the\n current session.\n* Without any SESSION or GLOBAL keyword,\n the statement sets the isolation level for only the next (not started)\ntransaction\n performed within the current session. After that it reverts to using the\nsession value.\n\nA change to the global default isolation level requires the SUPER privilege.\nAny session is free to change its session isolation level (even in the middle\nof a transaction), or the isolation level for its next transaction.\n\nIsolation Level\n---------------\n\nTo set the global default isolation level at server startup, use the\n--transaction-isolation=level option on the command line or in an option file.\nValues of level for this option use dashes rather than spaces, so the\nallowable values are READ_UNCOMMITTED, READ-COMMITTED, REPEATABLE-READ, or\nSERIALIZABLE. For example, to set the default isolation level to REPEATABLE\nREAD, use these lines in the [mariadb] section of an option file:\n\n[mariadb]\ntransaction-isolation = REPEATABLE-READ\nTo determine the global and session transaction isolation levels at runtime,\ncheck the value of the tx_isolation system variable (note that the variable\nhas been renamed transaction_isolation from MariaDB 11.1.1, to match the\noption, and the old name deprecated).\n\nSELECT @@GLOBAL.tx_isolation, @@tx_isolation;\n\nFrom MariaDB 11.1.1:\n\nSELECT @@GLOBAL.transaction_isolation, @@transaction_isolation;\n\nInnoDB supports each of the translation isolation levels described here using\ndifferent locking strategies. The default level is REPEATABLE READ. For\nadditional information about InnoDB record-level locks and how it uses them to\nexecute various types of statements, see InnoDB Lock Modes, and\nhttp://dev.mysql.com/doc/refman/en/innodb-locks-set.html.\n\nIsolation Levels\n----------------\n\nThe following sections describe how MariaDB supports the different transaction\nlevels.\n\nREAD UNCOMMITTED\n----------------\n\nSELECT statements are performed in a non-locking fashion, but a possible\nearlier version of a row might be used. Thus, using this isolation level, such\nreads are not consistent. This is also called a \"dirty read.\" Otherwise, this\nisolation level works like READ COMMITTED.\n\nREAD COMMITTED\n--------------\n\nA somewhat Oracle-like isolation level with respect to consistent\n(non-locking) reads: Each consistent read, even within the same transaction,\nsets and reads its own fresh snapshot. See\nhttp://dev.mysql.com/doc/refman/en/innodb-consistent-read.html.\n\nFor locking reads (SELECT with FOR UPDATE or LOCK IN SHARE MODE), InnoDB locks\nonly index records, not the gaps before them, and thus allows the free\ninsertion of new records next to locked records. For UPDATE and DELETE\nstatements, locking depends on whether the statement uses a unique index with\na unique search condition (such as WHERE id = 100), or a range-type search\ncondition (such as WHERE id > 100). For a unique index with a unique search\ncondition, InnoDB locks only the index record found, not the gap before it.\nFor range-type searches, InnoDB locks the index range scanned, using gap locks\nor next-key (gap plus index-record) locks to block insertions by other\nsessions into the gaps covered by the range. This is necessary because\n\"phantom rows\" must be blocked for MariaDB replication and recovery to work.\n\nNote: If the READ COMMITTED isolation level is used or the\ninnodb_locks_unsafe_for_binlog system variable is enabled, there is no InnoDB\ngap locking except for foreign-key constraint checking and duplicate-key\nchecking. Also, record locks for non-matching rows are released after MariaDB\nhas evaluated the WHERE condition.If you use READ COMMITTED or enable\ninnodb_locks_unsafe_for_binlog, you must use row-based binary logging.\n\nREPEATABLE READ\n---------------\n\nThis is the default isolation level for InnoDB. For consistent reads, there is\nan important difference from the READ COMMITTED isolation level: All\nconsistent reads within the same transaction read the snapshot established by\nthe first read. This convention means that if you issue several plain\n(non-locking) SELECT statements within the same transaction, these SELECT\nstatements are consistent also with respect to each other. See\nhttp://dev.mysql.com/doc/refman/en/innodb-consistent-read.html.\n\nFor locking reads (SELECT with FOR UPDATE or LOCK IN SHARE MODE), UPDATE, and\nDELETE statements, locking depends on whether the statement uses a unique\nindex with a unique search condition, or a range-type search condition. For a\nunique index with a unique search condition, InnoDB locks only the index\nrecord found, not the gap before it. For other search conditions, InnoDB locks\nthe index range scanned, using gap locks or next-key (gap plus index-record)\nlocks to block insertions by other sessions into the gaps covered by the range.\n\nThis is the minimum isolation level for non-distributed XA transactions.\n\nSERIALIZABLE\n------------\n\nThis level is like REPEATABLE READ, but InnoDB implicitly converts all plain\nSELECT statements to SELECT ... LOCK IN SHARE MODE if autocommit is disabled.\nIf autocommit is enabled, the SELECT is its own transaction. It therefore is\nknown to be read only and can be serialized if performed as a consistent\n(non-locking) read and need not block for other transactions. (This means that\nto force a plain SELECT to block if other transactions have modified the\nselected rows, you should disable autocommit.)\n\nDistributed XA transactions should always use this isolation level.\n\nAccess Mode\n-----------\n\nThe access mode specifies whether the transaction is allowed to write data or\nnot. By default, transactions are in READ WRITE mode (see the tx_read_only\nsystem variable). READ ONLY mode allows the storage engine to apply\noptimizations that cannot be used for transactions which write data. Note that\nunlike the global read_only mode, READ_ONLY ADMIN (and SUPER before MariaDB\n10.11.0) privilege doesn\'t allow writes and DDL statements on temporary tables\nare not allowed either.\n\nIt is not permitted to specify both READ WRITE and READ ONLY in the same\nstatement.\n\nREAD WRITE and READ ONLY can also be specified in the START TRANSACTION\nstatement, in which case the specified mode is only valid for one transaction.\n\nExamples\n--------\n\nSET GLOBAL TRANSACTION ISOLATION LEVEL SERIALIZABLE;\n\nAttempting to set the isolation level within an existing transaction without\nspecifying GLOBAL or SESSION.\n\nSTART TRANSACTION;\n\nSET TRANSACTION ISOLATION LEVEL SERIALIZABLE;\nERROR 1568 (25001): Transaction characteristics can\'t be changed while a\ntransaction is in progress\n\nURL: https://mariadb.com/kb/en/set-transaction/','','https://mariadb.com/kb/en/set-transaction/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (98,8,'LOCK TABLES','Syntax\n------\n\nLOCK TABLE[S]\n tbl_name [[AS] alias] lock_type\n [, tbl_name [[AS] alias] lock_type] ...\n [WAIT n|NOWAIT]\n\nlock_type:\n READ [LOCAL]\n | [LOW_PRIORITY] WRITE\n | WRITE CONCURRENT\n\nUNLOCK TABLES\n\nDescription\n-----------\n\nThe lock_type can be one of:\n\n+---------------------------+------------------------------------------------+\n| Option | Description |\n+---------------------------+------------------------------------------------+\n| READ | Read lock, no writes allowed |\n+---------------------------+------------------------------------------------+\n| READ LOCAL | Read lock, but allow concurrent inserts |\n+---------------------------+------------------------------------------------+\n| WRITE | Exclusive write lock. No other connections |\n| | can read or write to this table |\n+---------------------------+------------------------------------------------+\n| LOW_PRIORITY WRITE | Exclusive write lock, but allow new read |\n| | locks on the table until we get the write |\n| | lock. |\n+---------------------------+------------------------------------------------+\n| WRITE CONCURRENT | Exclusive write lock, but allow READ LOCAL |\n| | locks to the table. |\n+---------------------------+------------------------------------------------+\n\nMariaDB enables client sessions to acquire table locks explicitly for the\npurpose of cooperating with other sessions for access to tables, or to prevent\nother sessions from modifying tables during periods when a session requires\nexclusive access to them. A session can acquire or release locks only for\nitself. One session cannot acquire locks for another session or release locks\nheld by another session.\n\nLocks may be used to emulate transactions or to get more speed when updating\ntables.\n\nLOCK TABLES explicitly acquires table locks for the current client session.\nTable locks can be acquired for base tables or views. To use LOCK TABLES, you\nmust have the LOCK TABLES privilege, and the SELECT privilege for each object\nto be locked. See GRANT\n\nFor view locking, LOCK TABLES adds all base tables used in the view to the set\nof tables to be locked and locks them automatically. If you lock a table\nexplicitly with LOCK TABLES, any tables used in triggers are also locked\nimplicitly, as described in Triggers and Implicit Locks.\n\nUNLOCK TABLES explicitly releases any table locks held by the current session.\n\nMariaDB starting with 10.3.0\n----------------------------\n\nWAIT/NOWAIT\n-----------\n\nSet the lock wait timeout. See WAIT and NOWAIT.\n\nLimitations\n-----------\n\n* LOCK TABLES doesn\'t work when using Galera cluster. You may experience\ncrashes or locks when used with Galera.\n* LOCK TABLES works on XtraDB/InnoDB tables only if the innodb_table_locks\nsystem variable is set to 1 (the default) and autocommit is set to 0 (1 is\ndefault). Please note that no error message will be returned on LOCK TABLES\nwith innodb_table_locks = 0.\n* LOCK TABLES implicitly commits the active transaction, if any. Also,\nstarting a transaction always releases all table locks acquired with LOCK\nTABLES. This means that there is no way to have table locks and an active\ntransaction at the same time. The only exceptions are the transactions in\nautocommit mode. To preserve the data integrity between transactional and\nnon-transactional tables, the GET_LOCK() function can be used.\n* When using LOCK TABLES on a TEMPORARY table, it will always be locked with a\nWRITE lock.\n* While a connection holds an explicit read lock on a table, it cannot modify\nit. If you try, the following error will be produced:\n\nERROR 1099 (HY000): Table \'tab_name\' was locked with a READ lock and can\'t be\nupdated\n\n* While a connection holds an explicit lock on a table, it cannot access a\nnon-locked table. If you try, the following error will be produced:\n\nERROR 1100 (HY000): Table \'tab_name\' was not locked with LOCK TABLES\n\n* While a connection holds an explicit lock on a table, it cannot issue the\nfollowing: INSERT DELAYED, CREATE TABLE, CREATE TABLE ... LIKE, and DDL\nstatements involving stored programs and views (except for triggers). If you\ntry, the following error will be produced:\n\nERROR 1192 (HY000): Can\'t execute the given command because you have active\nlocked tables or an active transaction\n\n* LOCK TABLES can not be used in stored routines - if you try, the following\nerror will be produced on creation:\n\nERROR 1314 (0A000): LOCK is not allowed in stored procedures\n\nURL: https://mariadb.com/kb/en/lock-tables/','','https://mariadb.com/kb/en/lock-tables/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (99,8,'SAVEPOINT','Syntax\n------\n\nSAVEPOINT identifier\nROLLBACK [WORK] TO [SAVEPOINT] identifier\nRELEASE SAVEPOINT identifier\n\nDescription\n-----------\n\nInnoDB supports the SQL statements SAVEPOINT, ROLLBACK TO SAVEPOINT, RELEASE\nSAVEPOINT and the optional WORK keyword for ROLLBACK.\n\nEach savepoint must have a legal MariaDB identifier. A savepoint is a named\nsub-transaction.\n\nNormally ROLLBACK undoes the changes performed by the whole transaction. When\nused with the TO clause, it undoes the changes performed after the specified\nsavepoint, and erases all subsequent savepoints. However, all locks that have\nbeen acquired after the save point will survive. RELEASE SAVEPOINT does not\nrollback or commit any changes, but removes the specified savepoint.\n\nWhen the execution of a trigger or a stored function begins, it is not\npossible to use statements which reference a savepoint which was defined from\nout of that stored program.\n\nWhen a COMMIT (including implicit commits) or a ROLLBACK statement (with no TO\nclause) is performed, they act on the whole transaction, and all savepoints\nare removed.\n\nErrors\n------\n\nIf COMMIT or ROLLBACK is issued and no transaction was started, no error is\nreported.\n\nIf SAVEPOINT is issued and no transaction was started, no error is reported\nbut no savepoint is created. When ROLLBACK TO SAVEPOINT or RELEASE SAVEPOINT\nis called for a savepoint that does not exist, an error like this is issued:\n\nERROR 1305 (42000): SAVEPOINT svp_name does not exist\n\nURL: https://mariadb.com/kb/en/savepoint/','','https://mariadb.com/kb/en/savepoint/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (100,8,'Metadata Locking','MariaDB supports metadata locking. This means that when a transaction\n(including XA transactions) uses a table, it locks its metadata until the end\nof transaction. Non-transactional tables are also locked, as well as views and\nobjects which are related to locked tables/views (stored functions, triggers,\netc). When a connection tries to use a DDL statement (like an ALTER TABLE)\nwhich modifies a table that is locked, that connection is queued, and has to\nwait until it\'s unlocked. Using savepoints and performing a partial rollback\ndoes not release metadata locks.\n\nLOCK TABLES ... WRITE are also queued. Some wrong statements which produce an\nerror may not need to wait for the lock to be freed.\n\nThe metadata lock\'s timeout is determined by the value of the\nlock_wait_timeout server system variable (in seconds). However, note that its\ndefault value is 31536000 (1 year, MariaDB <= 10.2.3), or 86400 (1 day,\nMariaDB >= 10.2.4). If this timeout is exceeded, the following error is\nreturned:\n\nERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction\n\nIf the metadata_lock_info plugin is installed, the Information Schema\nmetadata_lock_info table stores information about existing metadata locks.\n\nMariaDB starting with 10.5.2\n----------------------------\nFrom MariaDB 10.5, the Performance Schema metadata_locks table contains\nmetadata lock information.\n\nExample\n-------\n\nLet\'s use the following MEMORY (non-transactional) table:\n\nCREATE TABLE t (a INT) ENGINE = MEMORY;\n\nConnection 1 starts a transaction, and INSERTs a row into t:\n\nSTART TRANSACTION;\n\nINSERT INTO t SET a=1;\n\nt\'s metadata is now locked by connection 1. Connection 2 tries to alter t, but\nhas to wait:\n\nALTER TABLE t ADD COLUMN b INT;\n\nConnection 2\'s prompt is blocked now.\n\nNow connection 1 ends the transaction:\n\nCOMMIT;\n\n...and connection 2 finally gets the output of its command:\n\nQuery OK, 1 row affected (35.23 sec)\nRecords: 1 Duplicates: 0 Warnings: 0\n\nURL: https://mariadb.com/kb/en/metadata-locking/','','https://mariadb.com/kb/en/metadata-locking/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (101,8,'Transaction Timeouts','MariaDB has always had the wait_timeout and interactive_timeout settings,\nwhich close connections after a certain period of inactivity.\n\nHowever, these are by default set to a long wait period. In situations where\ntransactions may be started, but not committed or rolled back, more granular\ncontrol and a shorter timeout may be desirable so as to avoid locks being held\nfor too long.\n\nMariaDB 10.3 introduced three new variables to handle this situation.\n\n* idle_transaction_timeout (all transactions)\n* idle_write_transaction_timeout (write transactions - called\nidle_readwrite_transaction_timeout until MariaDB 10.3.2)\n* idle_readonly_transaction_timeout (read transactions)\n\nThese accept a time in seconds to time out, by closing the connection,\ntransactions that are idle for longer than this period. By default all are set\nto zero, or no timeout.\n\nidle_transaction_timeout affects all transactions,\nidle_write_transaction_timeout affects write transactions only and\nidle_readonly_transaction_timeout affects read transactions only. The latter\ntwo variables work independently. However, if either is set along with\nidle_transaction_timeout, the settings for idle_write_transaction_timeout or\nidle_readonly_transaction_timeout will take precedence.\n\nExamples\n--------\n\nSET SESSION idle_transaction_timeout=2;\nBEGIN;\nSELECT * FROM t;\nEmpty set (0.000 sec)\n## wait 3 seconds\nSELECT * FROM t;\nERROR 2006 (HY000): MySQL server has gone away\n\nSET SESSION idle_write_transaction_timeout=2;\nBEGIN;\nSELECT * FROM t;\nEmpty set (0.000 sec)\n## wait 3 seconds\nSELECT * FROM t;\nEmpty set (0.000 sec)\nINSERT INTO t VALUES(1);\n## wait 3 seconds\nSELECT * FROM t;\nERROR 2006 (HY000): MySQL server has gone away\n\nSET SESSION idle_transaction_timeout=2, SESSION\nidle_readonly_transaction_timeout=10;\nBEGIN;\nSELECT * FROM t;\nEmpty set (0.000 sec)\n ## wait 3 seconds\nSELECT * FROM t;\nEmpty set (0.000 sec)\n## wait 11 seconds\nSELECT * FROM t;\nERROR 2006 (HY000): MySQL server has gone away\n\nURL: https://mariadb.com/kb/en/transaction-timeouts/','','https://mariadb.com/kb/en/transaction-timeouts/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (102,8,'UNLOCK TABLES','Syntax\n------\n\nUNLOCK TABLES\n\nDescription\n-----------\n\nUNLOCK TABLES explicitly releases any table locks held by the current session.\nSee LOCK TABLES for more information.\n\nIn addition to releasing table locks acquired by the LOCK TABLES statement,\nthe UNLOCK TABLES statement also releases the global read lock acquired by the\nFLUSH TABLES WITH READ LOCK statement. The FLUSH TABLES WITH READ LOCK\nstatement is very useful for performing backups. See FLUSH for more\ninformation about FLUSH TABLES WITH READ LOCK.\n\nURL: https://mariadb.com/kb/en/transactions-unlock-tables/','','https://mariadb.com/kb/en/transactions-unlock-tables/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (103,8,'WAIT and NOWAIT','Extended syntax so that it is possible to set innodb_lock_wait_timeout and\nlock_wait_timeout for the following statements:\n\nSyntax\n------\n\nALTER TABLE tbl_name [WAIT n|NOWAIT] ...\nCREATE ... INDEX ON tbl_name (index_col_name, ...) [WAIT n|NOWAIT] ...\nDROP INDEX ... [WAIT n|NOWAIT]\nDROP TABLE tbl_name [WAIT n|NOWAIT] ...\nLOCK TABLE ... [WAIT n|NOWAIT]\nOPTIMIZE TABLE tbl_name [WAIT n|NOWAIT]\nRENAME TABLE tbl_name [WAIT n|NOWAIT] ...\nSELECT ... FOR UPDATE [WAIT n|NOWAIT]\nSELECT ... LOCK IN SHARE MODE [WAIT n|NOWAIT]\nTRUNCATE TABLE tbl_name [WAIT n|NOWAIT]\n\nDescription\n-----------\n\nThe lock wait timeout can be explicitly set in the statement by using either\nWAIT n (to set the wait in seconds) or NOWAIT, in which case the statement\nwill immediately fail if the lock cannot be obtained. WAIT 0 is equivalent to\nNOWAIT.\n\nURL: https://mariadb.com/kb/en/wait-and-nowait/','','https://mariadb.com/kb/en/wait-and-nowait/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (104,8,'XA Transactions','Overview\n--------\n\nThe MariaDB XA implementation is based on the X/Open CAE document Distributed\nTransaction Processing: The XA Specification. This document is published by\nThe Open Group and available at\nhttp://www.opengroup.org/public/pubs/catalog/c193.htm.\n\nXA transactions are designed to allow distributed transactions, where a\ntransaction manager (the application) controls a transaction which involves\nmultiple resources. Such resources are usually DBMSs, but could be resources\nof any type. The whole set of required transactional operations is called a\nglobal transaction. Each subset of operations which involve a single resource\nis called a local transaction. XA used a 2-phases commit (2PC). With the first\ncommit, the transaction manager tells each resource to prepare an effective\ncommit, and waits for a confirm message. The changes are not still made\neffective at this point. If any of the resources encountered an error, the\ntransaction manager will rollback the global transaction. If all resources\ncommunicate that the first commit is successful, the transaction manager can\nrequire a second commit, which makes the changes effective.\n\nIn MariaDB, XA transactions can only be used with storage engines that support\nthem. At least InnoDB, TokuDB, SPIDER and MyRocks support them. For InnoDB,\nuntil MariaDB 10.2, XA transactions can be disabled by setting the\ninnodb_support_xa server system variable to 0. From MariaDB 10.3, XA\ntransactions are always supported.\n\nLike regular transactions, XA transactions create metadata locks on accessed\ntables.\n\nXA transactions require REPEATABLE READ as a minimum isolation level. However,\ndistributed transactions should always use SERIALIZABLE.\n\nTrying to start more than one XA transaction at the same time produces a 1400\nerror (SQLSTATE \'XAE09\'). The same error is produced when attempting to start\nan XA transaction while a regular transaction is in effect. Trying to start a\nregular transaction while an XA transaction is in effect produces a 1399 error\n(SQLSTATE \'XAE07\').\n\nThe statements that cause an implicit COMMIT for regular transactions produce\na 1400 error (SQLSTATE \'XAE09\') if a XA transaction is in effect.\n\nInternal XA vs External XA\n--------------------------\n\nXA transactions are an overloaded term in MariaDB. If a storage engine is\nXA-capable, it can mean one or both of these:\n\n* It supports MariaDB\'s internal two-phase commit API. This is transparent to\nthe user. Sometimes this is called \"internal XA\", since MariaDB\'s internal\ntransaction coordinator log can handle coordinating these transactions.\n\n* It supports XA transactions, with the XA START, XA PREPARE, XA COMMIT, etc.\nstatements. Sometimes this is called \"external XA\", since it requires the use\nof an external transaction coordinator to use this feature properly.\n\nTransaction Coordinator Log\n---------------------------\n\nIf you have two or more XA-capable storage engines enabled, then a transaction\ncoordinator log must be available.\n\nThere are currently two implementations of the transaction coordinator log:\n\n* Binary log-based transaction coordinator log\n* Memory-mapped file-based transaction coordinator log\n\nIf the binary log is enabled on a server, then the server will use the binary\nlog-based transaction coordinator log. Otherwise, it will use the\nmemory-mapped file-based transaction coordinator log.\n\nSee Transaction Coordinator Log for more information.\n\nSyntax\n------\n\nXA {START|BEGIN} xid [JOIN|RESUME]\n\nXA END xid [SUSPEND [FOR MIGRATE]]\n\nXA PREPARE xid\n\nXA COMMIT xid [ONE PHASE]\n\nXA ROLLBACK xid\n\nXA RECOVER [FORMAT=[\'RAW\'|\'SQL\']]\n\nxid: gtrid [, bqual [, formatID ]]\n\nThe interface to XA transactions is a set of SQL statements starting with XA.\nEach statement changes a transaction\'s state, determining which actions it can\nperform. A transaction which does not exist is in the NON-EXISTING state.\n\nXA START (or BEGIN) starts a transaction and defines its xid (a transaction\nidentifier). The JOIN or RESUME keywords have no effect. The new transaction\nwill be in ACTIVE state.\n\nThe xid can have 3 components, though only the first one is mandatory. gtrid\nis a quoted string representing a global transaction identifier. bqual is a\nquoted string representing a local transaction identifier. formatID is an\nunsigned integer indicating the format used for the first two components; if\nnot specified, defaults to 1. MariaDB does not interpret in any way these\ncomponents, and only uses them to identify a transaction. xids of transactions\nin effect must be unique.\n\nXA END declares that the specified ACTIVE transaction is finished and it\nchanges its state to IDLE. SUSPEND [FOR MIGRATE] has no effect.\n\nXA PREPARE prepares an IDLE transaction for commit, changing its state to\nPREPARED. This is the first commit.\n\nXA COMMIT definitely commits and terminates a transaction which has already\nbeen PREPARED. If the ONE PHASE clause is specified, this statements performs\na 1-phase commit on an IDLE transaction.\n\nXA ROLLBACK rolls back and terminates an IDLE or PREPARED transaction.\n\nXA RECOVER shows information about all PREPARED transactions.\n\nWhen trying to execute an operation which is not allowed for the transaction\'s\ncurrent state, an error is produced:\n\nXA COMMIT \'test\' ONE PHASE;\nERROR 1399 (XAE07): XAER_RMFAIL: The command cannot be executed when global\ntransaction is in the ACTIVE state\n\nXA COMMIT \'test2\';\nERROR 1399 (XAE07): XAER_RMFAIL: The command cannot be executed when global\ntransaction is in the NON-EXISTING state\n\nXA RECOVER\n----------\n\nThe XA RECOVER statement shows information about all transactions which are in\nthe PREPARED state. It does not matter which connection created the\ntransaction: if it has been PREPARED, it appears. But this does not mean that\na connection can commit or rollback a transaction which was started by another\nconnection. Note that transactions using a 1-phase commit are never in the\nPREPARED state, so they cannot be shown by XA RECOVER.\n\nXA RECOVER produces four columns:\n\nXA RECOVER;\n+----------+--------------+--------------+------+\n| formatID | gtrid_length | bqual_length | data |\n+----------+--------------+--------------+------+\n| 1 | 4 | 0 | test |\n+----------+--------------+--------------+------+\n\nMariaDB starting with 10.3.3\n----------------------------\nYou can use XA RECOVER FORMAT=\'SQL\' to get the data in a human readable form\nthat can be directly copy-pasted into XA COMMIT or XA ROLLBACK. This is\nparticularly useful for binary xid generated by some transaction coordinators.\n\nformatID is the formatID part of xid.\n\ndata are the gtrid and bqual parts of xid, concatenated.\n\ngtrid_length and bqual_length are the lengths of gtrid and bqual, respectevely.\n\nExamples\n--------\n\n2-phases commit:\n\nXA START \'test\';\n\nINSERT INTO t VALUES (1,2);\n\nXA END \'test\';\n\nXA PREPARE \'test\';\n\nXA COMMIT \'test\';\n\n1-phase commit:\n\nXA START \'test\';\n\nINSERT INTO t VALUES (1,2);\n\nXA END \'test\';\n\nXA COMMIT \'test\' ONE PHASE;\n\nHuman-readable:\n\nxa start \'12\\r34\\t67\\v78\', \'abc\\ndef\', 3;\n\ninsert t1 values (40);\n\nxa end \'12\\r34\\t67\\v78\', \'abc\\ndef\', 3;\n\nxa prepare \'12\\r34\\t67\\v78\', \'abc\\ndef\', 3;\n\nxa recover format=\'RAW\';\n+----------+--------------+--------------+--------------------+\n| formatID | gtrid_length | bqual_length | data |\n+----------+--------------+--------------+--------------------+\n34 67v78abc 11 | 7 | 12\ndef |\n+----------+--------------+--------------+--------------------+\n\nxa recover format=\'SQL\';\n+----------+--------------+--------------+-------------------------------------\n---------+\n| formatID | gtrid_length | bqual_length | data \n |\n+----------+--------------+--------------+-------------------------------------\n---------+\n| 3 | 11 | 7 |\nX\'31320d3334093637763738\',X\'6162630a646566\',3 |\n+----------+--------------+--------------+-------------------------------------\n---------+\n\nxa rollback X\'31320d3334093637763738\',X\'6162630a646566\',3;\n\nKnown Issues\n------------\n\nMariaDB Galera Cluster\n----------------------\n\nMariaDB Galera Cluster does not support XA transactions.\n\nHowever, MariaDB Galera Cluster builds include a built-in plugin called wsrep.\nPrior to MariaDB 10.4.3, this plugin was internally considered an XA-capable\nstorage engine. Consequently, these MariaDB Galera Cluster builds have\nmultiple XA-capable storage engines by default, even if the only \"real\"\nstorage engine that supports external XA transactions enabled on these builds\nby default is InnoDB. Therefore, when using one these builds MariaDB would be\nforced to use a transaction coordinator log by default, which could have\nperformance implications.\n\nSee Transaction Coordinator Log Overview: MariaDB Galera Cluster for more\ninformation.\n\nURL: https://mariadb.com/kb/en/xa-transactions/','','https://mariadb.com/kb/en/xa-transactions/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (105,10,'CREATE USER','Syntax\n------\n\nCREATE [OR REPLACE] USER [IF NOT EXISTS] \n user_specification [,user_specification ...] \n [REQUIRE {NONE | tls_option [[AND] tls_option ...] }]\n [WITH resource_option [resource_option ...] ]\n [lock_option] [password_option]\n\nuser_specification:\n username [authentication_option]\n\nauthentication_option:\n IDENTIFIED BY \'password\'\n | IDENTIFIED BY PASSWORD \'password_hash\'\n | IDENTIFIED {VIA|WITH} authentication_rule [OR authentication_rule ...]\n\nauthentication_rule:\n authentication_plugin\n | authentication_plugin {USING|AS} \'authentication_string\'\n | authentication_plugin {USING|AS} PASSWORD(\'password\')\n\ntls_option:\n SSL\n | X509\n | CIPHER \'cipher\'\n | ISSUER \'issuer\'\n | SUBJECT \'subject\'\n\nresource_option:\n MAX_QUERIES_PER_HOUR count\n | MAX_UPDATES_PER_HOUR count\n | MAX_CONNECTIONS_PER_HOUR count\n | MAX_USER_CONNECTIONS count\n | MAX_STATEMENT_TIME time\n\npassword_option:\n PASSWORD EXPIRE\n | PASSWORD EXPIRE DEFAULT\n | PASSWORD EXPIRE NEVER\n | PASSWORD EXPIRE INTERVAL N DAY\n\nlock_option:\n ACCOUNT LOCK\n | ACCOUNT UNLOCK\n}\n\nDescription\n-----------\n\nThe CREATE USER statement creates new MariaDB accounts. To use it, you must\nhave the global CREATE USER privilege or the INSERT privilege for the mysql\ndatabase. For each account, CREATE USER creates a new row in mysql.user (until\nMariaDB 10.3 this is a table, from MariaDB 10.4 it\'s a view) or\nmysql.global_priv_table (from MariaDB 10.4) that has no privileges.\n\nIf any of the specified accounts, or any permissions for the specified\naccounts, already exist, then the server returns ERROR 1396 (HY000). If an\nerror occurs, CREATE USER will still create the accounts that do not result in\nan error. Only one error is produced for all users which have not been created:\n\nERROR 1396 (HY000): \n Operation CREATE USER failed for \'u1\'@\'%\',\'u2\'@\'%\'\n\nCREATE USER, DROP USER, CREATE ROLE, and DROP ROLE all produce the same error\ncode when they fail.\n\nSee Account Names below for details on how account names are specified.\n\nOR REPLACE\n----------\n\nIf the optional OR REPLACE clause is used, it is basically a shortcut for:\n\nDROP USER IF EXISTS name;\nCREATE USER name ...;\n\nFor example:\n\nCREATE USER foo2@test IDENTIFIED BY \'password\';\nERROR 1396 (HY000): Operation CREATE USER failed for \'foo2\'@\'test\'\n\nCREATE OR REPLACE USER foo2@test IDENTIFIED BY \'password\';\nQuery OK, 0 rows affected (0.00 sec)\n\nIF NOT EXISTS\n-------------\n\nWhen the IF NOT EXISTS clause is used, MariaDB will return a warning instead\nof an error if the specified user already exists.\n\nFor example:\n\nCREATE USER foo2@test IDENTIFIED BY \'password\';\nERROR 1396 (HY000): Operation CREATE USER failed for \'foo2\'@\'test\'\n\nCREATE USER IF NOT EXISTS foo2@test IDENTIFIED BY \'password\';\nQuery OK, 0 rows affected, 1 warning (0.00 sec)\n\nSHOW WARNINGS;\n+-------+------+----------------------------------------------------+\n| Level | Code | Message |\n+-------+------+----------------------------------------------------+\n| Note | 1973 | Can\'t create user \'foo2\'@\'test\'; it already exists |\n+-------+------+----------------------------------------------------+\n\nAuthentication Options\n----------------------\n\nIDENTIFIED BY \'password\'\n------------------------\n\nThe optional IDENTIFIED BY clause can be used to provide an account with a\npassword. The password should be specified in plain text. It will be hashed by\nthe PASSWORD function prior to being stored in the\nmysql.user/mysql.global_priv_table table.\n\nFor example, if our password is mariadb, then we can create the user with:\n\nCREATE USER foo2@test IDENTIFIED BY \'mariadb\';\n\nIf you do not specify a password with the IDENTIFIED BY clause, the user will\nbe able to connect without a password. A blank password is not a wildcard to\nmatch any password. The user must connect without providing a password if no\npassword is set.\n\nThe only authentication plugins that this clause supports are\nmysql_native_password and mysql_old_password.\n\nIDENTIFIED BY PASSWORD \'password_hash\'\n--------------------------------------\n\nThe optional IDENTIFIED BY PASSWORD clause can be used to provide an account\nwith a password that has already been hashed. The password should be specified\nas a hash that was provided by the PASSWORD function. It will be stored in the\nmysql.user/mysql.global_priv_table table as-is.\n\nFor example, if our password is mariadb, then we can find the hash with:\n\nSELECT PASSWORD(\'mariadb\');\n+-------------------------------------------+\n| PASSWORD(\'mariadb\') |\n+-------------------------------------------+\n| *54958E764CE10E50764C2EECBB71D01F08549980 |\n+-------------------------------------------+\n1 row in set (0.00 sec)\n\nAnd then we can create a user with the hash:\n\nCREATE USER foo2@test IDENTIFIED BY PASSWORD\n\'*54958E764CE10E50764C2EECBB71D01F08549980\';\n\nIf you do not specify a password with the IDENTIFIED BY clause, the user will\nbe able to connect without a password. A blank password is not a wildcard to\nmatch any password. The user must connect without providing a password if no\npassword is set.\n\nThe only authentication plugins that this clause supports are\nmysql_native_password and mysql_old_password.\n\nIDENTIFIED {VIA|WITH} authentication_plugin\n-------------------------------------------\n\nThe optional IDENTIFIED VIA authentication_plugin allows you to specify that\nthe account should be authenticated by a specific authentication plugin. The\nplugin name must be an active authentication plugin as per SHOW PLUGINS. If it\ndoesn\'t show up in that output, then you will need to install it with INSTALL\nPLUGIN or INSTALL SONAME.\n\nVIA and WITH are synonyms.\n\nFor example, this could be used with the PAM authentication plugin:\n\nCREATE USER foo2@test IDENTIFIED VIA pam;\n\nSome authentication plugins allow additional arguments to be specified after a\nUSING or AS keyword. For example, the PAM authentication plugin accepts a\nservice name:\n\nCREATE USER foo2@test IDENTIFIED VIA pam USING \'mariadb\';\n\nThe exact meaning of the additional argument would depend on the specific\nauthentication plugin.\n\nMariaDB starting with 10.4.0\n----------------------------\nThe USING or AS keyword can also be used to provide a plain-text password to a\nplugin if it\'s provided as an argument to the PASSWORD() function. This is\nonly valid for authentication plugins that have implemented a hook for the\nPASSWORD() function. For example, the ed25519 authentication plugin supports\nthis:\n\nCREATE USER safe@\'%\' IDENTIFIED VIA ed25519 USING PASSWORD(\'secret\');\n\nMariaDB starting with 10.4.3\n----------------------------\nOne can specify many authentication plugins, they all work as alternatives\nways of authenticating a user:\n\nCREATE USER safe@\'%\' IDENTIFIED VIA ed25519 USING PASSWORD(\'secret\') OR\nunix_socket;\n\nBy default, when you create a user without specifying an authentication\nplugin, MariaDB uses the mysql_native_password plugin.\n\nTLS Options\n-----------\n\nBy default, MariaDB transmits data between the server and clients without\nencrypting it. This is generally acceptable when the server and client run on\nthe same host or in networks where security is guaranteed through other means.\nHowever, in cases where the server and client exist on separate networks or\nthey are in a high-risk network, the lack of encryption does introduce\nsecurity concerns as a malicious actor could potentially eavesdrop on the\ntraffic as it is sent over the network between them.\n\nTo mitigate this concern, MariaDB allows you to encrypt data in transit\nbetween the server and clients using the Transport Layer Security (TLS)\nprotocol. TLS was formerly known as Secure Socket Layer (SSL), but strictly\nspeaking the SSL protocol is a predecessor to TLS and, that version of the\nprotocol is now considered insecure. The documentation still uses the term SSL\noften and for compatibility reasons TLS-related server system and status\nvariables still use the prefix ssl_, but internally, MariaDB only supports its\nsecure successors.\n\nSee Secure Connections Overview for more information about how to determine\nwhether your MariaDB server has TLS support.\n\nYou can set certain TLS-related restrictions for specific user accounts. For\ninstance, you might use this with user accounts that require access to\nsensitive data while sending it across networks that you do not control. These\nrestrictions can be enabled for a user account with the CREATE USER, ALTER\nUSER, or GRANT statements. The following options are available:\n\n+---------------------------+------------------------------------------------+\n| Option | Description |\n+---------------------------+------------------------------------------------+\n| REQUIRE NONE | TLS is not required for this account, but can |\n| | still be used. |\n+---------------------------+------------------------------------------------+\n| REQUIRE SSL | The account must use TLS, but no valid X509 |\n| | certificate is required. This option cannot |\n| | be combined with other TLS options. |\n+---------------------------+------------------------------------------------+\n| REQUIRE X509 | The account must use TLS and must have a |\n| | valid X509 certificate. This option implies |\n| | REQUIRE SSL. This option cannot be combined |\n| | with other TLS options. |\n+---------------------------+------------------------------------------------+\n| REQUIRE ISSUER \'issuer\' | The account must use TLS and must have a |\n| | valid X509 certificate. Also, the Certificate |\n| | Authority must be the one specified via the |\n| | string issuer. This option implies REQUIRE |\n| | X509. This option can be combined with the |\n| | SUBJECT, and CIPHER options in any order. |\n+---------------------------+------------------------------------------------+\n| REQUIRE SUBJECT \'subject\' | The account must use TLS and must have a |\n| | valid X509 certificate. Also, the |\n| | certificate\'s Subject must be the one |\n| | specified via the string subject. This option |\n| | implies REQUIRE X509. This option can be |\n| | combined with the ISSUER, and CIPHER options |\n| | in any order. |\n+---------------------------+------------------------------------------------+\n| REQUIRE CIPHER \'cipher\' | The account must use TLS, but no valid X509 |\n| | certificate is required. Also, the encryption |\n| | used for the connection must use a specific |\n| | cipher method specified in the string cipher. |\n| | This option implies REQUIRE SSL. This option |\n| | can be combined with the ISSUER, and SUBJECT |\n| | options in any order. |\n+---------------------------+------------------------------------------------+\n\nThe REQUIRE keyword must be used only once for all specified options, and the\nAND keyword can be used to separate individual options, but it is not required.\n\nFor example, you can create a user account that requires these TLS options\nwith the following:\n\nCREATE USER \'alice\'@\'%\'\n REQUIRE SUBJECT \'/CN=alice/O=My Dom, Inc./C=US/ST=Oregon/L=Portland\'\n AND ISSUER \'/C=FI/ST=Somewhere/L=City/ O=Some Company/CN=Peter\nParker/emailAddress=p.parker@marvel.com\'\n AND CIPHER \'SHA-DES-CBC3-EDH-RSA\';\n\nIf any of these options are set for a specific user account, then any client\nwho tries to connect with that user account will have to be configured to\nconnect with TLS.\n\nSee Securing Connections for Client and Server for information on how to\nenable TLS on the client and server.\n\nResource Limit Options\n----------------------\n\nIt is possible to set per-account limits for certain server resources. The\nfollowing table shows the values that can be set per account:\n\n+--------------------------------------+--------------------------------------+\n| Limit Type | Decription |\n+--------------------------------------+--------------------------------------+\n| MAX_QUERIES_PER_HOUR | Number of statements that the |\n| | account can issue per hour |\n| | (including updates) |\n+--------------------------------------+--------------------------------------+\n| MAX_UPDATES_PER_HOUR | Number of updates (not queries) |\n| | that the account can issue per hour |\n+--------------------------------------+--------------------------------------+\n| MAX_CONNECTIONS_PER_HOUR | Number of connections that the |\n| | account can start per hour |\n+--------------------------------------+--------------------------------------+\n| MAX_USER_CONNECTIONS | Number of simultaneous connections |\n| | that can be accepted from the same |\n| | account; if it is 0, |\n| | max_connections will be used |\n| | instead; if max_connections is 0, |\n| | there is no limit for this |\n| | account\'s simultaneous connections. |\n+--------------------------------------+--------------------------------------+\n| MAX_STATEMENT_TIME | Timeout, in seconds, for statements |','','https://mariadb.com/kb/en/create-user/'); -update help_topic set description = CONCAT(description, '\n| | executed by the user. See also |\n| | Aborting Statements that Exceed a |\n| | Certain Time to Execute. |\n+--------------------------------------+--------------------------------------+\n\nIf any of these limits are set to 0, then there is no limit for that resource\nfor that user.\n\nHere is an example showing how to create a user with resource limits:\n\nCREATE USER \'someone\'@\'localhost\' WITH\n MAX_USER_CONNECTIONS 10\n MAX_QUERIES_PER_HOUR 200;\n\nThe resources are tracked per account, which means \'user\'@\'server\'; not per\nuser name or per connection.\n\nThe count can be reset for all users using FLUSH USER_RESOURCES, FLUSH\nPRIVILEGES or mariadb-admin reload.\n\nPer account resource limits are stored in the user table, in the mysql\ndatabase. Columns used for resources limits are named max_questions,\nmax_updates, max_connections (for MAX_CONNECTIONS_PER_HOUR), and\nmax_user_connections (for MAX_USER_CONNECTIONS).\n\nAccount Names\n-------------\n\nAccount names have both a user name component and a host name component, and\nare specified as \'user_name\'@\'host_name\'.\n\nThe user name and host name may be unquoted, quoted as strings using double\nquotes (\") or single quotes (\'), or quoted as identifiers using backticks (`).\nYou must use quotes when using special characters (such as a hyphen) or\nwildcard characters. If you quote, you must quote the user name and host name\nseparately (for example \'user_name\'@\'host_name\').\n\nHost Name Component\n-------------------\n\nIf the host name is not provided, it is assumed to be \'%\'.\n\nHost names may contain the wildcard characters % and _. They are matched as if\nby the LIKE clause. If you need to use a wildcard character literally (for\nexample, to match a domain name with an underscore), prefix the character with\na backslash. See LIKE for more information on escaping wildcard characters.\n\nHost name matches are case-insensitive. Host names can match either domain\nnames or IP addresses. Use \'localhost\' as the host name to allow only local\nclient connections. On Linux, the loopback interface (127.0.0.1) will not\nmatch \'localhost\' as it is not considered a local connection: this means that\nonly connections via UNIX-domain sockets will match \'localhost\'.\n\nYou can use a netmask to match a range of IP addresses using \'base_ip/netmask\'\nas the host name. A user with an IP address ip_addr will be allowed to connect\nif the following condition is true:\n\nip_addr & netmask = base_ip\n\nFor example, given a user:\n\nCREATE USER \'maria\'@\'247.150.130.0/255.255.255.0\';\n\nthe IP addresses satisfying this condition range from 247.150.130.0 to\n247.150.130.255.\n\nUsing 255.255.255.255 is equivalent to not using a netmask at all. Netmasks\ncannot be used for IPv6 addresses.\n\nNote that the credentials added when creating a user with the \'%\' wildcard\nhost will not grant access in all cases. For example, some systems come with\nan anonymous localhost user, and when connecting from localhost this will take\nprecedence.\n\nBefore MariaDB 10.6, the host name component could be up to 60 characters in\nlength. Starting from MariaDB 10.6, it can be up to 255 characters.\n\nUser Name Component\n-------------------\n\nUser names must match exactly, including case. A user name that is empty is\nknown as an anonymous account and is allowed to match a login attempt with any\nuser name component. These are described more in the next section.\n\nFor valid identifiers to use as user names, see Identifier Names.\n\nIt is possible for more than one account to match when a user connects.\nMariaDB selects the first matching account after sorting according to the\nfollowing criteria:\n\n* Accounts with an exact host name are sorted before accounts using a wildcard\nin the\nhost name. Host names using a netmask are considered to be exact for sorting.\n* Accounts with a wildcard in the host name are sorted according to the\nposition of\nthe first wildcard character. Those with a wildcard character later in the\nhost name\nsort before those with a wildcard character earlier in the host name.\n* Accounts with a non-empty user name sort before accounts with an empty user\nname.\n* Accounts with an empty user name are sorted last. As mentioned previously,\nthese are known as anonymous accounts. These are described more in the next\nsection.\n\nThe following table shows a list of example account as sorted by these\ncriteria:\n\n+---------+-------------+\n| User | Host |\n+---------+-------------+\n| joffrey | 192.168.0.3 |\n| | 192.168.0.% |\n| joffrey | 192.168.% |\n| | 192.168.% |\n+---------+-------------+\n\nOnce connected, you only have the privileges granted to the account that\nmatched, not all accounts that could have matched. For example, consider the\nfollowing commands:\n\nCREATE USER \'joffrey\'@\'192.168.0.3\';\nCREATE USER \'joffrey\'@\'%\';\nGRANT SELECT ON test.t1 to \'joffrey\'@\'192.168.0.3\';\nGRANT SELECT ON test.t2 to \'joffrey\'@\'%\';\n\nIf you connect as joffrey from 192.168.0.3, you will have the SELECT privilege\non the table test.t1, but not on the table test.t2. If you connect as joffrey\nfrom any other IP address, you will have the SELECT privilege on the table\ntest.t2, but not on the table test.t1.\n\nUsernames can be up to 80 characters long before 10.6 and starting from 10.6\nit can be 128 characters long.\n\nAnonymous Accounts\n------------------\n\nAnonymous accounts are accounts where the user name portion of the account\nname is empty. These accounts act as special catch-all accounts. If a user\nattempts to log into the system from a host, and an anonymous account exists\nwith a host name portion that matches the user\'s host, then the user will log\nin as the anonymous account if there is no more specific account match for the\nuser name that the user entered.\n\nFor example, here are some anonymous accounts:\n\nCREATE USER \'\'@\'localhost\';\nCREATE USER \'\'@\'192.168.0.3\';\n\nFixing a Legacy Default Anonymous Account\n-----------------------------------------\n\nOn some systems, the mysql.db table has some entries for the \'\'@\'%\' anonymous\naccount by default. Unfortunately, there is no matching entry in the\nmysql.user/mysql.global_priv_table table, which means that this anonymous\naccount doesn\'t exactly exist, but it does have privileges--usually on the\ndefault test database created by mariadb-install-db. These account-less\nprivileges are a legacy that is leftover from a time when MySQL\'s privilege\nsystem was less advanced.\n\nThis situation means that you will run into errors if you try to create a\n\'\'@\'%\' account. For example:\n\nCREATE USER \'\'@\'%\';\nERROR 1396 (HY000): Operation CREATE USER failed for \'\'@\'%\'\n\nThe fix is to DELETE the row in the mysql.db table and then execute FLUSH\nPRIVILEGES:\n\nDELETE FROM mysql.db WHERE User=\'\' AND Host=\'%\';\nFLUSH PRIVILEGES;\n\nNote that FLUSH PRIVILEGES is only needed if one modifies the mysql tables\ndirectly. It is not needed when using CREATE USER, DROP USER, GRANT etc.\n\nAnd then the account can be created:\n\nCREATE USER \'\'@\'%\';\nQuery OK, 0 rows affected (0.01 sec)\n\nSee MDEV-13486 for more information.\n\nPassword Expiry\n---------------\n\nMariaDB starting with 10.4.3\n----------------------------\nBesides automatic password expiry, as determined by default_password_lifetime,\npassword expiry times can be set on an individual user basis, overriding the\nglobal setting, for example:\n\nCREATE USER \'monty\'@\'localhost\' PASSWORD EXPIRE INTERVAL 120 DAY;\n\nSee User Password Expiry for more details.\n\nAccount Locking\n---------------\n\nMariaDB starting with 10.4.2\n----------------------------\nAccount locking permits privileged administrators to lock/unlock user\naccounts. No new client connections will be permitted if an account is locked\n(existing connections are not affected). For example:\n\nCREATE USER \'marijn\'@\'localhost\' ACCOUNT LOCK;\n\nSee Account Locking for more details.\n\nFrom MariaDB 10.4.7 and MariaDB 10.5.8, the lock_option and password_option\nclauses can occur in either order.\n\nURL: https://mariadb.com/kb/en/create-user/') WHERE help_topic_id = 105; -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (106,10,'ALTER USER','Syntax\n------\n\nALTER USER [IF EXISTS] \n user_specification [,user_specification] ...\n [REQUIRE {NONE | tls_option [[AND] tls_option] ...}]\n [WITH resource_option [resource_option] ...]\n [lock_option] [password_option]\n\nuser_specification:\n username [authentication_option]\n\nauthentication_option:\n IDENTIFIED BY \'password\'\n | IDENTIFIED BY PASSWORD \'password_hash\'\n | IDENTIFIED {VIA|WITH} authentication_rule [OR authentication_rule] ...\n\nauthentication_rule:\n authentication_plugin\n | authentication_plugin {USING|AS} \'authentication_string\'\n | authentication_plugin {USING|AS} PASSWORD(\'password\')\n\ntls_option\n SSL\n | X509\n | CIPHER \'cipher\'\n | ISSUER \'issuer\'\n | SUBJECT \'subject\'\n\nresource_option\n MAX_QUERIES_PER_HOUR count\n | MAX_UPDATES_PER_HOUR count\n | MAX_CONNECTIONS_PER_HOUR count\n | MAX_USER_CONNECTIONS count\n | MAX_STATEMENT_TIME time\n\npassword_option:\n PASSWORD EXPIRE\n | PASSWORD EXPIRE DEFAULT\n | PASSWORD EXPIRE NEVER\n | PASSWORD EXPIRE INTERVAL N DAY\n\nlock_option:\n ACCOUNT LOCK\n | ACCOUNT UNLOCK\n}\n\nDescription\n-----------\n\nThe ALTER USER statement modifies existing MariaDB accounts. To use it, you\nmust have the global CREATE USER privilege or the UPDATE privilege for the\nmysql database. The global SUPER privilege is also required if the read_only\nsystem variable is enabled.\n\nIf any of the specified user accounts do not yet exist, an error results. If\nan error occurs, ALTER USER will still modify the accounts that do not result\nin an error. Only one error is produced for all users which have not been\nmodified.\n\nFor renaming an existing account (user name and/or host), see RENAME USER.\n\nIF EXISTS\n---------\n\nWhen the IF EXISTS clause is used, MariaDB will return a warning instead of an\nerror for each specified user that does not exist.\n\nAccount Names\n-------------\n\nFor ALTER USER statements, account names are specified as the username\nargument in the same way as they are for CREATE USER statements. See account\nnames from the CREATE USER page for details on how account names are specified.\n\nCURRENT_USER or CURRENT_USER() can also be used to alter the account logged\ninto the current session. For example, to change the current user\'s password\nto mariadb:\n\nALTER USER CURRENT_USER() IDENTIFIED BY \'mariadb\';\n\nAuthentication Options\n----------------------\n\nMariaDB starting with 10.4\n--------------------------\nFrom MariaDB 10.4, it is possible to use more than one authentication plugin\nfor each user account. For example, this can be useful to slowly migrate users\nto the more secure ed25519 authentication plugin over time, while allowing the\nold mysql_native_password authentication plugin as an alternative for the\ntransitional period. See Authentication from MariaDB 10.4 for more.\n\nWhen running ALTER USER, not specifying an authentication option in the\nIDENTIFIED VIA clause will remove that authentication method. (However this\nwas not the case before MariaDB 10.4.13, see MDEV-21928)\n\nFor example, a user is created with the ability to authenticate via both a\npassword and unix_socket:\n\nCREATE USER \'bob\'@\'localhost\' \n IDENTIFIED VIA mysql_native_password USING PASSWORD(\'pwd\')\n OR unix_socket;\n\nSHOW CREATE USER \'bob\'@\'localhost\'\\G\n*************************** 1. row ***************************\nCREATE USER for bob@localhost: CREATE USER `bob`@`localhost` \n IDENTIFIED VIA mysql_native_password\n USING \'*975B2CD4FF9AE554FE8AD33168FBFC326D2021DD\'\n OR unix_socket\n\nIf the user\'s password is updated, but unix_socket authentication is not\nspecified in the IDENTIFIED VIA clause, unix_socket authentication will no\nlonger be permitted.\n\nALTER USER \'bob\'@\'localhost\' IDENTIFIED VIA mysql_native_password \n USING PASSWORD(\'pwd2\');\n\nSHOW CREATE USER \'bob\'@\'localhost\'\\G\n*************************** 1. row ***************************\nCREATE USER for bob@localhost: CREATE USER `bob`@`localhost` \n IDENTIFIED BY PASSWORD \'*38366FDA01695B6A5A9DD4E428D9FB8F7EB75512\'\n\nIDENTIFIED BY \'password\'\n------------------------\n\nThe optional IDENTIFIED BY clause can be used to provide an account with a\npassword. The password should be specified in plain text. It will be hashed by\nthe PASSWORD function prior to being stored to the mysql.user table.\n\nFor example, if our password is mariadb, then we can set the account\'s\npassword with:\n\nALTER USER foo2@test IDENTIFIED BY \'mariadb\';\n\nIf you do not specify a password with the IDENTIFIED BY clause, the user will\nbe able to connect without a password. A blank password is not a wildcard to\nmatch any password. The user must connect without providing a password if no\npassword is set.\n\nThe only authentication plugins that this clause supports are\nmysql_native_password and mysql_old_password.\n\nIDENTIFIED BY PASSWORD \'password_hash\'\n--------------------------------------\n\nThe optional IDENTIFIED BY PASSWORD clause can be used to provide an account\nwith a password that has already been hashed. The password should be specified\nas a hash that was provided by the PASSWORD#function. It will be stored to the\nmysql.user table as-is.\n\nFor example, if our password is mariadb, then we can find the hash with:\n\nSELECT PASSWORD(\'mariadb\');\n+-------------------------------------------+\n| PASSWORD(\'mariadb\') |\n+-------------------------------------------+\n| *54958E764CE10E50764C2EECBB71D01F08549980 |\n+-------------------------------------------+\n\nAnd then we can set an account\'s password with the hash:\n\nALTER USER foo2@test \n IDENTIFIED BY PASSWORD \'*54958E764CE10E50764C2EECBB71D01F08549980\';\n\nIf you do not specify a password with the IDENTIFIED BY clause, the user will\nbe able to connect without a password. A blank password is not a wildcard to\nmatch any password. The user must connect without providing a password if no\npassword is set.\n\nThe only authentication plugins that this clause supports are\nmysql_native_password and mysql_old_password.\n\nIDENTIFIED {VIA|WITH} authentication_plugin\n-------------------------------------------\n\nThe optional IDENTIFIED VIA authentication_plugin allows you to specify that\nthe account should be authenticated by a specific authentication plugin. The\nplugin name must be an active authentication plugin as per SHOW PLUGINS. If it\ndoesn\'t show up in that output, then you will need to install it with INSTALL\nPLUGIN or INSTALL SONAME.\n\nFor example, this could be used with the PAM authentication plugin:\n\nALTER USER foo2@test IDENTIFIED VIA pam;\n\nSome authentication plugins allow additional arguments to be specified after a\nUSING or AS keyword. For example, the PAM authentication plugin accepts a\nservice name:\n\nALTER USER foo2@test IDENTIFIED VIA pam USING \'mariadb\';\n\nThe exact meaning of the additional argument would depend on the specific\nauthentication plugin.\n\nIn MariaDB 10.4 and later, the USING or AS keyword can also be used to provide\na plain-text password to a plugin if it\'s provided as an argument to the\nPASSWORD() function. This is only valid for authentication plugins that have\nimplemented a hook for the PASSWORD() function. For example, the ed25519\nauthentication plugin supports this:\n\nALTER USER safe@\'%\' IDENTIFIED VIA ed25519 USING PASSWORD(\'secret\');\n\nTLS Options\n-----------\n\nBy default, MariaDB transmits data between the server and clients without\nencrypting it. This is generally acceptable when the server and client run on\nthe same host or in networks where security is guaranteed through other means.\nHowever, in cases where the server and client exist on separate networks or\nthey are in a high-risk network, the lack of encryption does introduce\nsecurity concerns as a malicious actor could potentially eavesdrop on the\ntraffic as it is sent over the network between them.\n\nTo mitigate this concern, MariaDB allows you to encrypt data in transit\nbetween the server and clients using the Transport Layer Security (TLS)\nprotocol. TLS was formerly known as Secure Socket Layer (SSL), but strictly\nspeaking the SSL protocol is a predecessor to TLS and, that version of the\nprotocol is now considered insecure. The documentation still uses the term SSL\noften and for compatibility reasons TLS-related server system and status\nvariables still use the prefix ssl_, but internally, MariaDB only supports its\nsecure successors.\n\nSee Secure Connections Overview for more information about how to determine\nwhether your MariaDB server has TLS support.\n\nYou can set certain TLS-related restrictions for specific user accounts. For\ninstance, you might use this with user accounts that require access to\nsensitive data while sending it across networks that you do not control. These\nrestrictions can be enabled for a user account with the CREATE USER, ALTER\nUSER, or GRANT statements. The following options are available:\n\n+---------------------------+------------------------------------------------+\n| Option | Description |\n+---------------------------+------------------------------------------------+\n| REQUIRE NONE | TLS is not required for this account, but can |\n| | still be used. |\n+---------------------------+------------------------------------------------+\n| REQUIRE SSL | The account must use TLS, but no valid X509 |\n| | certificate is required. This option cannot |\n| | be combined with other TLS options. |\n+---------------------------+------------------------------------------------+\n| REQUIRE X509 | The account must use TLS and must have a |\n| | valid X509 certificate. This option implies |\n| | REQUIRE SSL. This option cannot be combined |\n| | with other TLS options. |\n+---------------------------+------------------------------------------------+\n| REQUIRE ISSUER \'issuer\' | The account must use TLS and must have a |\n| | valid X509 certificate. Also, the Certificate |\n| | Authority must be the one specified via the |\n| | string issuer. This option implies REQUIRE |\n| | X509. This option can be combined with the |\n| | SUBJECT, and CIPHER options in any order. |\n+---------------------------+------------------------------------------------+\n| REQUIRE SUBJECT \'subject\' | The account must use TLS and must have a |\n| | valid X509 certificate. Also, the |\n| | certificate\'s Subject must be the one |\n| | specified via the string subject. This option |\n| | implies REQUIRE X509. This option can be |\n| | combined with the ISSUER, and CIPHER options |\n| | in any order. |\n+---------------------------+------------------------------------------------+\n| REQUIRE CIPHER \'cipher\' | The account must use TLS, but no valid X509 |\n| | certificate is required. Also, the encryption |\n| | used for the connection must use a specific |\n| | cipher method specified in the string cipher. |\n| | This option implies REQUIRE SSL. This option |\n| | can be combined with the ISSUER, and SUBJECT |\n| | options in any order. |\n+---------------------------+------------------------------------------------+\n\nThe REQUIRE keyword must be used only once for all specified options, and the\nAND keyword can be used to separate individual options, but it is not required.\n\nFor example, you can alter a user account to require these TLS options with\nthe following:\n\nALTER USER \'alice\'@\'%\'\n REQUIRE SUBJECT \'/CN=alice/O=My Dom, Inc./C=US/ST=Oregon/L=Portland\' AND\n ISSUER \'/C=FI/ST=Somewhere/L=City/ O=Some Company/CN=Peter\nParker/emailAddress=p.parker@marvel.com\'\n AND CIPHER \'SHA-DES-CBC3-EDH-RSA\';\n\nIf any of these options are set for a specific user account, then any client\nwho tries to connect with that user account will have to be configured to\nconnect with TLS.\n\nSee Securing Connections for Client and Server for information on how to\nenable TLS on the client and server.\n\nResource Limit Options\n----------------------\n\nIt is possible to set per-account limits for certain server resources. The\nfollowing table shows the values that can be set per account:\n\n+------------------------------------+---------------------------------------+\n| Limit Type | Description |\n+------------------------------------+---------------------------------------+\n| MAX_QUERIES_PER_HOUR | Number of statements that the |\n| | account can issue per hour |\n| | (including updates) |\n+------------------------------------+---------------------------------------+\n| MAX_UPDATES_PER_HOUR | Number of updates (not queries) that |\n| | the account can issue per hour |\n+------------------------------------+---------------------------------------+\n| MAX_CONNECTIONS_PER_HOUR | Number of connections that the |\n| | account can start per hour |\n+------------------------------------+---------------------------------------+\n| MAX_USER_CONNECTIONS | Number of simultaneous connections |\n| | that can be accepted from the same |\n| | account; if it is 0, max_connections |\n| | will be used instead; if |\n| | max_connections is 0, there is no |\n| | limit for this account\'s |','','https://mariadb.com/kb/en/alter-user/'); -update help_topic set description = CONCAT(description, '\n| | simultaneous connections. |\n+------------------------------------+---------------------------------------+\n| MAX_STATEMENT_TIME | Timeout, in seconds, for statements |\n| | executed by the user. See also |\n| | Aborting Statements that Exceed a |\n| | Certain Time to Execute. |\n+------------------------------------+---------------------------------------+\n\nIf any of these limits are set to 0, then there is no limit for that resource\nfor that user.\n\nHere is an example showing how to set an account\'s resource limits:\n\nALTER USER \'someone\'@\'localhost\' WITH\n MAX_USER_CONNECTIONS 10\n MAX_QUERIES_PER_HOUR 200;\n\nThe resources are tracked per account, which means \'user\'@\'server\'; not per\nuser name or per connection.\n\nThe count can be reset for all users using FLUSH USER_RESOURCES, FLUSH\nPRIVILEGES or mysqladmin reload.\n\nPer account resource limits are stored in the user table, in the mysql\ndatabase. Columns used for resources limits are named max_questions,\nmax_updates, max_connections (for MAX_CONNECTIONS_PER_HOUR), and\nmax_user_connections (for MAX_USER_CONNECTIONS).\n\nPassword Expiry\n---------------\n\nMariaDB starting with 10.4.3\n----------------------------\nBesides automatic password expiry, as determined by default_password_lifetime,\npassword expiry times can be set on an individual user basis, overriding the\nglobal setting, for example:\n\nALTER USER \'monty\'@\'localhost\' PASSWORD EXPIRE INTERVAL 120 DAY;\nALTER USER \'monty\'@\'localhost\' PASSWORD EXPIRE NEVER;\nALTER USER \'monty\'@\'localhost\' PASSWORD EXPIRE DEFAULT;\n\nSee User Password Expiry for more details.\n\nAccount Locking\n---------------\n\nMariaDB starting with 10.4.2\n----------------------------\nAccount locking permits privileged administrators to lock/unlock user\naccounts. No new client connections will be permitted if an account is locked\n(existing connections are not affected). For example:\n\nALTER USER \'marijn\'@\'localhost\' ACCOUNT LOCK;\n\nSee Account Locking for more details.\n\nFrom MariaDB 10.4.7 and MariaDB 10.5.8, the lock_option and password_option\nclauses can occur in either order.\n\nURL: https://mariadb.com/kb/en/alter-user/') WHERE help_topic_id = 106; -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (107,10,'DROP USER','Syntax\n------\n\nDROP USER [IF EXISTS] user_name [, user_name] ...\n\nDescription\n-----------\n\nThe DROP USER statement removes one or more MariaDB accounts. It removes\nprivilege rows for the account from all grant tables. To use this statement,\nyou must have the global CREATE USER privilege or the DELETE privilege for the\nmysql database. Each account is named using the same format as for the CREATE\nUSER statement; for example, \'jeffrey\'@\'localhost\'. If you specify only the\nuser name part of the account name, a host name part of \'%\' is used. For\nadditional information about specifying account names, see CREATE USER.\n\nNote that, if you specify an account that is currently connected, it will not\nbe deleted until the connection is closed. The connection will not be\nautomatically closed.\n\nIf any of the specified user accounts do not exist, ERROR 1396 (HY000)\nresults. If an error occurs, DROP USER will still drop the accounts that do\nnot result in an error. Only one error is produced for all users which have\nnot been dropped:\n\nERROR 1396 (HY000): Operation DROP USER failed for \'u1\'@\'%\',\'u2\'@\'%\'\n\nFailed CREATE or DROP operations, for both users and roles, produce the same\nerror code.\n\nIF EXISTS\n---------\n\nIf the IF EXISTS clause is used, MariaDB will return a note instead of an\nerror if the user does not exist.\n\nExamples\n--------\n\nDROP USER bob;\n\nDROP USER foo2@localhost,foo2@\'127.%\';\n\nIF EXISTS:\n\nDROP USER bob;\nERROR 1396 (HY000): Operation DROP USER failed for \'bob\'@\'%\'\n\nDROP USER IF EXISTS bob;\nQuery OK, 0 rows affected, 1 warning (0.00 sec)\n\nSHOW WARNINGS;\n+-------+------+---------------------------------------------+\n| Level | Code | Message |\n+-------+------+---------------------------------------------+\n| Note | 1974 | Can\'t drop user \'bob\'@\'%\'; it doesn\'t exist |\n+-------+------+---------------------------------------------+\n\nURL: https://mariadb.com/kb/en/drop-user/','','https://mariadb.com/kb/en/drop-user/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (108,10,'GRANT','Syntax\n------\n\nGRANT\n priv_type [(column_list)]\n [, priv_type [(column_list)]] ...\n ON [object_type] priv_level\n TO user_specification [ user_options ...]\n\nuser_specification:\n username [authentication_option]\n | PUBLIC\nauthentication_option:\n IDENTIFIED BY \'password\'\n | IDENTIFIED BY PASSWORD \'password_hash\'\n | IDENTIFIED {VIA|WITH} authentication_rule [OR authentication_rule ...]\n\nauthentication_rule:\n authentication_plugin\n | authentication_plugin {USING|AS} \'authentication_string\'\n | authentication_plugin {USING|AS} PASSWORD(\'password\')\n\nGRANT PROXY ON username\n TO user_specification [, user_specification ...]\n [WITH GRANT OPTION]\n\nGRANT rolename TO grantee [, grantee ...]\n [WITH ADMIN OPTION]\n\ngrantee:\n rolename\n username [authentication_option]\n\nuser_options:\n [REQUIRE {NONE | tls_option [[AND] tls_option] ...}]\n [WITH with_option [with_option] ...]\n\nobject_type:\n TABLE\n | FUNCTION\n | PROCEDURE\n | PACKAGE\n | PACKAGE BODY\n\npriv_level:\n *\n | *.*\n | db_name.*\n | db_name.tbl_name\n | tbl_name\n | db_name.routine_name\n\nwith_option:\n GRANT OPTION\n | resource_option\n\nresource_option:\n MAX_QUERIES_PER_HOUR count\n | MAX_UPDATES_PER_HOUR count\n | MAX_CONNECTIONS_PER_HOUR count\n | MAX_USER_CONNECTIONS count\n | MAX_STATEMENT_TIME time\n\ntls_option:\n SSL\n | X509\n | CIPHER \'cipher\'\n | ISSUER \'issuer\'\n | SUBJECT \'subject\'\n\nDescription\n-----------\n\nThe GRANT statement allows you to grant privileges or roles to accounts. To\nuse GRANT, you must have the GRANT OPTION privilege, and you must have the\nprivileges that you are granting.\n\nUse the REVOKE statement to revoke privileges granted with the GRANT statement.\n\nUse the SHOW GRANTS statement to determine what privileges an account has.\n\nAccount Names\n-------------\n\nFor GRANT statements, account names are specified as the username argument in\nthe same way as they are for CREATE USER statements. See account names from\nthe CREATE USER page for details on how account names are specified.\n\nImplicit Account Creation\n-------------------------\n\nThe GRANT statement also allows you to implicitly create accounts in some\ncases.\n\nIf the account does not yet exist, then GRANT can implicitly create it. To\nimplicitly create an account with GRANT, a user is required to have the same\nprivileges that would be required to explicitly create the account with the\nCREATE USER statement.\n\nIf the NO_AUTO_CREATE_USER SQL_MODE is set, then accounts can only be created\nif authentication information is specified, or with a CREATE USER statement.\nIf no authentication information is provided, GRANT will produce an error when\nthe specified account does not exist, for example:\n\nshow variables like \'%sql_mode%\' ;\n+---------------+--------------------------------------------+\n| Variable_name | Value |\n+---------------+--------------------------------------------+\n| sql_mode | NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION |\n+---------------+--------------------------------------------+\n\nGRANT USAGE ON *.* TO \'user123\'@\'%\' IDENTIFIED BY \'\';\nERROR 1133 (28000): Can\'t find any matching row in the user table\n\nGRANT USAGE ON *.* TO \'user123\'@\'%\' \n IDENTIFIED VIA PAM using \'mariadb\' require ssl ;\nQuery OK, 0 rows affected (0.00 sec)\n\nselect host, user from mysql.user where user=\'user123\' ;\n\n+------+----------+\n| host | user |\n+------+----------+\n| % | user123 |\n+------+----------+\n\nPrivilege Levels\n----------------\n\nPrivileges can be set globally, for an entire database, for a table or\nroutine, or for individual columns in a table. Certain privileges can only be\nset at certain levels.\n\nGlobal privileges do not take effect immediately and are only applied to\nconnections created after the GRANT statement was executed.\n\n* Global privileges priv_type are granted using *.* for\npriv_level. Global privileges include privileges to administer the database\nand manage user accounts, as well as privileges for all tables, functions, and\nprocedures. Global privileges are stored in the mysql.user table prior to\nMariaDB 10.4, and in mysql.global_priv table afterwards.\n* Database privileges priv_type are granted using db_name.*\nfor priv_level, or using just * to use default database. Database\nprivileges include privileges to create tables and functions, as well as\nprivileges for all tables, functions, and procedures in the database. Database\nprivileges are stored in the mysql.db table.\n* Table privileges priv_type are granted using db_name.tbl_name\nfor priv_level, or using just tbl_name to specify a table in the default\ndatabase. The TABLE keyword is optional. Table privileges include the\nability to select and change data in the table. Certain table privileges can\nbe granted for individual columns.\n* Column privileges priv_type are granted by specifying a table for\npriv_level and providing a column list after the privilege type. They allow\nyou to control exactly which columns in a table users can select and change.\n* Function privileges priv_type are granted using FUNCTION db_name.routine_name\nfor priv_level, or using just FUNCTION routine_name to specify a function\nin the default database.\n* Procedure privileges priv_type are granted using PROCEDURE\ndb_name.routine_name\nfor priv_level, or using just PROCEDURE routine_name to specify a procedure\nin the default database.\n\nThe USAGE Privilege\n-------------------\n\nThe USAGE privilege grants no real privileges. The SHOW GRANTS statement will\nshow a global USAGE privilege for a newly-created user. You can use USAGE with\nthe GRANT statement to change options like GRANT OPTION and\nMAX_USER_CONNECTIONS without changing any account privileges.\n\nThe ALL PRIVILEGES Privilege\n----------------------------\n\nThe ALL PRIVILEGES privilege grants all available privileges. Granting all\nprivileges only affects the given privilege level. For example, granting all\nprivileges on a table does not grant any privileges on the database or\nglobally.\n\nUsing ALL PRIVILEGES does not grant the special GRANT OPTION privilege.\n\nYou can use ALL instead of ALL PRIVILEGES.\n\nThe GRANT OPTION Privilege\n--------------------------\n\nUse the WITH GRANT OPTION clause to give users the ability to grant privileges\nto other users at the given privilege level. Users with the GRANT OPTION\nprivilege can only grant privileges they have. They cannot grant privileges at\na higher privilege level than they have the GRANT OPTION privilege.\n\nThe GRANT OPTION privilege cannot be set for individual columns. If you use\nWITH GRANT OPTION when specifying column privileges, the GRANT OPTION\nprivilege will be granted for the entire table.\n\nUsing the WITH GRANT OPTION clause is equivalent to listing GRANT OPTION as a\nprivilege.\n\nGlobal Privileges\n-----------------\n\nThe following table lists the privileges that can be granted globally. You can\nalso grant all database, table, and function privileges globally. When granted\nglobally, these privileges apply to all databases, tables, or functions,\nincluding those created later.\n\nTo set a global privilege, use *.* for priv_level.\n\nBINLOG ADMIN\n------------\n\nEnables administration of the binary log, including the PURGE BINARY LOGS\nstatement and setting the system variables:\n\n* binlog_annotate_row_events\n* binlog_cache_size\n* binlog_commit_wait_count\n* binlog_commit_wait_usec\n* binlog_direct_non_transactional_updates\n* binlog_expire_logs_seconds\n* binlog_file_cache_size\n* binlog_format\n* binlog_row_image\n* binlog_row_metadata\n* binlog_stmt_cache_size\n* expire_logs_days\n* log_bin_compress\n* log_bin_compress_min_len\n* log_bin_trust_function_creators\n* max_binlog_cache_size\n* max_binlog_size\n* max_binlog_stmt_cache_size\n* sql_log_bin and\n* sync_binlog.\n\nAdded in MariaDB 10.5.2.\n\nBINLOG MONITOR\n--------------\n\nNew name for REPLICATION CLIENT from MariaDB 10.5.2, (REPLICATION CLIENT still\nsupported as an alias for compatibility purposes). Permits running SHOW\ncommands related to the binary log, in particular the SHOW BINLOG STATUS and\nSHOW BINARY LOGS statements. Unlike REPLICATION CLIENT prior to MariaDB 10.5,\nSHOW SLAVE STATUS isn\'t included in this privilege, and SLAVE MONITOR is\nrequired.\n\nBINLOG REPLAY\n-------------\n\nEnables replaying the binary log with the BINLOG statement (generated by\nmariadb-binlog), executing SET timestamp when secure_timestamp is set to\nreplication, and setting the session values of system variables usually\nincluded in BINLOG output, in particular:\n\n* gtid_domain_id\n* gtid_seq_no\n* pseudo_thread_id\n* server_id.\n\nAdded in MariaDB 10.5.2\n\nCONNECTION ADMIN\n----------------\n\nEnables administering connection resource limit options. This includes\nignoring the limits specified by:\n\n* max_connections\n* max_user_connections and\n* max_password_errors.\n\nThe statements specified in init_connect are not executed, killing connections\nand queries owned by other users is permitted. The following\nconnection-related system variables can be changed:\n\n* connect_timeout\n* disconnect_on_expired_password\n* extra_max_connections\n* init_connect\n* max_connections\n* max_connect_errors\n* max_password_errors\n* proxy_protocol_networks\n* secure_auth\n* slow_launch_time\n* thread_pool_exact_stats\n* thread_pool_dedicated_listener\n* thread_pool_idle_timeout\n* thread_pool_max_threads\n* thread_pool_min_threads\n* thread_pool_oversubscribe\n* thread_pool_prio_kickup_timer\n* thread_pool_priority\n* thread_pool_size, and\n* thread_pool_stall_limit.\n\nAdded in MariaDB 10.5.2.\n\nCREATE USER\n-----------\n\nCreate a user using the CREATE USER statement, or implicitly create a user\nwith the GRANT statement.\n\nFEDERATED ADMIN\n---------------\n\nExecute CREATE SERVER, ALTER SERVER, and DROP SERVER statements. Added in\nMariaDB 10.5.2.\n\nFILE\n----\n\nRead and write files on the server, using statements like LOAD DATA INFILE or\nfunctions like LOAD_FILE(). Also needed to create CONNECT outward tables.\nMariaDB server must have the permissions to access those files.\n\nGRANT OPTION\n------------\n\nGrant global privileges. You can only grant privileges that you have.\n\nPROCESS\n-------\n\nShow information about the active processes, for example via SHOW PROCESSLIST\nor mariadb-admin processlist. If you have the PROCESS privilege, you can see\nall threads. Otherwise, you can see only your own threads (that is, threads\nassociated with the MariaDB account that you are using).\n\nREAD_ONLY ADMIN\n---------------\n\nUser can set the read_only system variable and allows the user to perform\nwrite operations, even when the read_only option is active. Added in MariaDB\n10.5.2.\n\nFrom MariaDB 10.11.0, the READ_ONLY ADMIN privilege has been removed from\nSUPER. The benefit of this is that one can remove the READ_ONLY ADMIN\nprivilege from all users and ensure that no one can make any changes on any\nnon-temporary tables. This is useful on slaves when one wants to ensure that\nthe slave is kept identical to the master.\n\nRELOAD\n------\n\nExecute FLUSH statements or equivalent mariadb-admin commands.\n\nREPLICATION CLIENT\n------------------\n\nExecute SHOW MASTER STATUS and SHOW BINARY LOGS informative statements.\nRenamed to BINLOG MONITOR in MariaDB 10.5.2 (but still supported as an alias\nfor compatibility reasons). SHOW SLAVE STATUS was part of REPLICATION CLIENT\nprior to MariaDB 10.5.\n\nREPLICATION MASTER ADMIN\n------------------------\n\nPermits administration of master servers, including the SHOW SLAVE HOSTS\nstatement, and setting the gtid_binlog_state, gtid_domain_id,\nmaster_verify_checksum and server_id system variables. Added in MariaDB 10.5.2.\n\nSLAVE MONITOR\n---------------\n\nPermit SHOW SLAVE STATUS and SHOW RELAYLOG EVENTS. From MariaDB 10.5.9.\n\nWhen a user would upgrade from an older major release to a MariaDB 10.5 minor\nrelease prior to MariaDB 10.5.9, certain user accounts would lose\ncapabilities. For example, a user account that had the REPLICATION CLIENT\nprivilege in older major releases could run SHOW SLAVE STATUS, but after\nupgrading to a MariaDB 10.5 minor release prior to MariaDB 10.5.9, they could\nno longer run SHOW SLAVE STATUS, because that statement was changed to\nrequire the REPLICATION SLAVE ADMIN privilege.\n\nThis issue is fixed in MariaDB 10.5.9 with this new privilege, which now\ngrants the user the ability to execute SHOW [ALL] (SLAVE | REPLICA) STATUS.\n\nWhen a database is upgraded from an older major release to MariaDB Server\n10.5.9 or later, any user accounts with the REPLICATION CLIENT or REPLICATION\nSLAVE privileges will automatically be granted the new SLAVE MONITOR\nprivilege. The privilege fix occurs when the server is started up, not when\nmariadb-upgrade is performed.\n\nHowever, when a database is upgraded from an early 10.5 minor release to\n10.5.9 and later, the user will have to fix any user account privileges\nmanually.\n\nREPLICATION REPLICA\n-------------------\n\nSynonym for REPLICATION SLAVE. From MariaDB 10.5.1.\n\nREPLICATION SLAVE\n-----------------\n\nAccounts used by slave servers on the master need this privilege. This is\nneeded to get the updates made on the master. From MariaDB 10.5.1, REPLICATION\nREPLICA is an alias for REPLICATION SLAVE.\n\nREPLICATION SLAVE ADMIN\n-----------------------\n\nPermits administering slave servers, including START SLAVE/REPLICA, STOP\SLAVE/nREPLICA, CHANGE MASTER, SHOW SLAVE/REPLICA STATUS, SHOW RELAYLOG EVENTS\nstatements, replaying the binary log with the BINLOG statement (generated by\nmariadb-binlog), and setting the system variables:\n\n* gtid_cleanup_batch_size\n* gtid_ignore_duplicates\n* gtid_pos_auto_engines\n* gtid_slave_pos\n* gtid_strict_mode\n* init_slave\n* read_binlog_speed_limit\n* relay_log_purge\n* relay_log_recovery\n* replicate_do_db\n* replicate_do_table\n* replicate_events_marked_for_skip\n* replicate_ignore_db\n* replicate_ignore_table\n* replicate_wild_do_table\n* replicate_wild_ignore_table\n* slave_compressed_protocol\n* slave_ddl_exec_mode\n* slave_domain_parallel_threads\n* slave_exec_mode\n* slave_max_allowed_packet\n* slave_net_timeout\n* slave_parallel_max_queued\n* slave_parallel_mode\n* slave_parallel_threads\n* slave_parallel_workers\n* slave_run_triggers_for_rbr\n* slave_sql_verify_checksum\n* slave_transaction_retry_interval\n* slave_type_conversions\n* sync_master_info','','https://mariadb.com/kb/en/grant/'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (3,2,'AREA','A synonym for ST_AREA.\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/polygon-properties/polygon-properties-area','','https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/polygon-properties/polygon-properties-area'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (4,2,'CENTROID','A synonym for ST_CENTROID.\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/polygon-properties/centroid','','https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/polygon-properties/centroid'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (5,2,'ExteriorRing','A synonym for ST_ExteriorRing.\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/polygon-properties/polygon-properties-exteriorring','','https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/polygon-properties/polygon-properties-exteriorring'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (6,2,'InteriorRingN','A synonym for ST_InteriorRingN.\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/polygon-properties/polygon-properties-interiorringn','','https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/polygon-properties/polygon-properties-interiorringn'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (7,2,'NumInteriorRings','A synonym for ST_NumInteriorRings.\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/polygon-properties/polygon-properties-numinteriorrings','','https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/polygon-properties/polygon-properties-numinteriorrings'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (8,2,'ST_AREA','Syntax\n------\n\nST_Area(poly)\nArea(poly)\n\nDescription\n-----------\n\nReturns as a double-precision number the area of the Polygon value poly, as\nmeasured in its spatial reference system.\n\nST_Area() and Area() are synonyms.\n\nExamples\n--------\n\nSET @poly = \'Polygon((0 0,0 3,3 0,0 0),(1 1,1 2,2 1,1 1))\';\n\nSELECT Area(GeomFromText(@poly));\n+---------------------------+\n| Area(GeomFromText(@poly)) |\n+---------------------------+\n| 4 |\n+---------------------------+\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/polygon-properties/st_area','','https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/polygon-properties/st_area'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (9,2,'ST_CENTROID','Syntax\n------\n\nST_Centroid(mpoly)\nCentroid(mpoly)\n\nDescription\n-----------\n\nReturns a point reflecting the mathematical centroid (geometric center) for\nthe MultiPolygon mpoly. The resulting point will not necessarily be on the\nMultiPolygon.\n\nST_Centroid() and Centroid() are synonyms.\n\nExamples\n--------\n\nSET @poly = ST_GeomFromText(\'POLYGON((0 0,20 0,20 20,0 20,0 0))\');\nSELECT ST_AsText(ST_Centroid(@poly)) AS center;\n+--------------+\n| center |\n+--------------+\n| POINT(10 10) |\n+--------------+\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/polygon-properties/st_centroid','','https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/polygon-properties/st_centroid'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (10,2,'ST_ExteriorRing','Syntax\n------\n\nST_ExteriorRing(poly)\nExteriorRing(poly)\n\nDescription\n-----------\n\nReturns the exterior ring of the Polygon value poly as a LineString.\n\nST_ExteriorRing() and ExteriorRing() are synonyms.\n\nExamples\n--------\n\nSET @poly = \'Polygon((0 0,0 3,3 3,3 0,0 0),(1 1,1 2,2 2,2 1,1 1))\';\n\nSELECT AsText(ExteriorRing(GeomFromText(@poly)));\n+-------------------------------------------+\n| AsText(ExteriorRing(GeomFromText(@poly))) |\n+-------------------------------------------+\n| LINESTRING(0 0,0 3,3 3,3 0,0 0) |\n+-------------------------------------------+\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/polygon-properties/st_exteriorring','','https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/polygon-properties/st_exteriorring'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (11,2,'ST_InteriorRingN','Syntax\n------\n\nST_InteriorRingN(poly,N)\nInteriorRingN(poly,N)\n\nDescription\n-----------\n\nReturns the N-th interior ring for the Polygon value poly as a LineString.\nRings are numbered beginning with 1.\n\nST_InteriorRingN() and InteriorRingN() are synonyms.\n\nExamples\n--------\n\nSET @poly = \'Polygon((0 0,0 3,3 3,3 0,0 0),(1 1,1 2,2 2,2 1,1 1))\';\n\nSELECT AsText(InteriorRingN(GeomFromText(@poly),1));\n+----------------------------------------------+\n| AsText(InteriorRingN(GeomFromText(@poly),1)) |\n+----------------------------------------------+\n| LINESTRING(1 1,1 2,2 2,2 1,1 1) |\n+----------------------------------------------+\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/polygon-properties/st_interiorringn','','https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/polygon-properties/st_interiorringn'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (12,2,'ST_NumInteriorRings','Syntax\n------\n\nST_NumInteriorRings(poly)\nNumInteriorRings(poly)\n\nDescription\n-----------\n\nReturns an integer containing the number of interior rings in the Polygon\nvalue poly.\n\nNote that according the the OpenGIS standard, a POLYGON should have exactly\none ExteriorRing and all other rings should lie within that ExteriorRing and\nthus be the InteriorRings. Practically, however, some systems, including\nMariaDB\'s, permit polygons to have several \'ExteriorRings\'. In the case of\nthere being multiple, non-overlapping exterior rings ST_NumInteriorRings()\nwill return 1.\n\nST_NumInteriorRings() and NumInteriorRings() are synonyms.\n\nExamples\n--------\n\nSET @poly = \'Polygon((0 0,0 3,3 3,3 0,0 0),(1 1,1 2,2 2,2 1,1 1))\';\n\nSELECT NumInteriorRings(GeomFromText(@poly));\n+---------------------------------------+\n| NumInteriorRings(GeomFromText(@poly)) |\n+---------------------------------------+\n| 1 |\n+---------------------------------------+\n\nNon-overlapping \'polygon\':\n\nSELECT ST_NumInteriorRings(ST_PolyFromText(\'POLYGON((0 0,10 0,10 10,0 10,0 0),\n (-1 -1,-5 -1,-5 -5,-1 -5,-1 -1))\')) AS NumInteriorRings;\n+------------------+\n| NumInteriorRings |\n+------------------+\n| 1 |\n+------------------+\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/polygon-properties/st_numinteriorrings','','https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/polygon-properties/st_numinteriorrings'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (13,3,'WKT Definition','Description\n-----------\n\nThe Well-Known Text (WKT) representation of Geometry is designed to exchange\ngeometry data in ASCII form. Examples of the basic geometry types include:\n\n+-----------------------------------------------------------------------------+\n| Geometry Types |\n+-----------------------------------------------------------------------------+\n| POINT |\n+-----------------------------------------------------------------------------+\n| LINESTRING |\n+-----------------------------------------------------------------------------+\n| POLYGON |\n+-----------------------------------------------------------------------------+\n| MULTIPOINT |\n+-----------------------------------------------------------------------------+\n| MULTILINESTRING |\n+-----------------------------------------------------------------------------+\n| MULTIPOLYGON |\n+-----------------------------------------------------------------------------+\n| GEOMETRYCOLLECTION |\n+-----------------------------------------------------------------------------+\n| GEOMETRY |\n+-----------------------------------------------------------------------------+\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/wkt-definition','','https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/wkt-definition'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (14,3,'AsText','A synonym for ST_AsText().\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/wkt-astext','','https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/wkt-astext'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (15,3,'AsWKT','A synonym for ST_AsText().\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/wkt-aswkt','','https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/wkt-aswkt'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (16,3,'GeomCollFromText','A synonym for ST_GeomCollFromText.\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/wkt-geomcollfromtext','','https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/wkt-geomcollfromtext'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (17,3,'GeometryCollectionFromText','A synonym for ST_GeomCollFromText.\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/geometrycollectionfromtext','','https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/geometrycollectionfromtext'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (18,3,'GeometryFromText','A synonym for ST_GeomFromText.\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/geometryfromtext','','https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/geometryfromtext'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (19,3,'GeomFromText','A synonym for ST_GeomFromText.\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/wkt-geomfromtext','','https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/wkt-geomfromtext'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (20,3,'LineFromText','A synonym for ST_LineFromText.\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/wkt-linefromtext','','https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/wkt-linefromtext'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (21,3,'LineStringFromText','A synonym for ST_LineFromText.\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/linestringfromtext','','https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/linestringfromtext'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (22,3,'MLineFromText','Syntax\n------\n\nMLineFromText(wkt[,srid])\nMultiLineStringFromText(wkt[,srid])\n\nDescription\n-----------\n\nConstructs a MULTILINESTRING value using its WKT representation and SRID.\n\nMLineFromText() and MultiLineStringFromText() are synonyms.\n\nExamples\n--------\n\nCREATE TABLE gis_multi_line (g MULTILINESTRING);\nSHOW FIELDS FROM gis_multi_line;\nINSERT INTO gis_multi_line VALUES\n (MultiLineStringFromText(\'MULTILINESTRING((10 48,10 21,10 0),(16 0,16\n23,16 48))\')),\n (MLineFromText(\'MULTILINESTRING((10 48,10 21,10 0))\')),\n (MLineFromWKB(AsWKB(MultiLineString(\n LineString(Point(1, 2), Point(3, 5)),\n LineString(Point(2, 5), Point(5, 8), Point(21, 7))))));\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/mlinefromtext','','https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/mlinefromtext'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (23,3,'MPointFromText','Syntax\n------\n\nMPointFromText(wkt[,srid])\nMultiPointFromText(wkt[,srid])\n\nDescription\n-----------\n\nConstructs a MULTIPOINT value using its WKT representation and SRID.\n\nMPointFromText() and MultiPointFromText() are synonyms.\n\nExamples\n--------\n\nCREATE TABLE gis_multi_point (g MULTIPOINT);\nSHOW FIELDS FROM gis_multi_point;\nINSERT INTO gis_multi_point VALUES\n (MultiPointFromText(\'MULTIPOINT(0 0,10 10,10 20,20 20)\')),\n (MPointFromText(\'MULTIPOINT(1 1,11 11,11 21,21 21)\')),\n (MPointFromWKB(AsWKB(MultiPoint(Point(3, 6), Point(4, 10)))));\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/mpointfromtext','','https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/mpointfromtext'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (24,3,'MPolyFromText','Syntax\n------\n\nMPolyFromText(wkt[,srid])\nMultiPolygonFromText(wkt[,srid])\n\nDescription\n-----------\n\nConstructs a MULTIPOLYGON value using its WKT representation and SRID.\n\nMPolyFromText() and MultiPolygonFromText() are synonyms.\n\nExamples\n--------\n\nCREATE TABLE gis_multi_polygon (g MULTIPOLYGON);\nSHOW FIELDS FROM gis_multi_polygon;\nINSERT INTO gis_multi_polygon VALUES\n (MultiPolygonFromText(\'MULTIPOLYGON(\n ((28 26,28 0,84 0,84 42,28 26),(52 18,66 23,73 9,48 6,52 18)),\n ((59 18,67 18,67 13,59 13,59 18)))\')),\n (MPolyFromText(\'MULTIPOLYGON(\n ((28 26,28 0,84 0,84 42,28 26),(52 18,66 23,73 9,48 6,52 18)),\n ((59 18,67 18,67 13,59 13,59 18)))\')),\n (MPolyFromWKB(AsWKB(MultiPolygon(Polygon(\n LineString(Point(0, 3), Point(3, 3), Point(3, 0), Point(0, 3)))))));\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/mpolyfromtext','','https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/mpolyfromtext'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (25,3,'MultiLineStringFromText','A synonym for MLineFromText.\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/multilinestringfromtext','','https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/multilinestringfromtext'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (26,3,'MultiPointFromText','A synonym for MPointFromText.\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/multipointfromtext','','https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/multipointfromtext'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (27,3,'MultiPolygonFromText','A synonym for MPolyFromText.\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/multipolygonfromtext','','https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/multipolygonfromtext'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (28,3,'PointFromText','A synonym for ST_PointFromText.\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/wkt-pointfromtext','','https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/wkt-pointfromtext'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (29,3,'PolyFromText','A synonym for ST_PolyFromText.\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/wkt-polyfromtext','','https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/wkt-polyfromtext'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (30,3,'PolygonFromText','A synonym for ST_PolyFromText.\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/polygonfromtext','','https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/polygonfromtext'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (31,3,'ST_AsText','Syntax\n------\n\nST_AsText(g)\nAsText(g)\nST_AsWKT(g)\nAsWKT(g)\n\nDescription\n-----------\n\nConverts a value in internal geometry format to its WKT representation and\nreturns the string result.\n\nST_AsText(), AsText(), ST_AsWKT() and AsWKT() are all synonyms.\n\nExamples\n--------\n\nSET @g = \'LineString(1 1,4 4,6 6)\';\n\nSELECT ST_AsText(ST_GeomFromText(@g));\n+--------------------------------+\n| ST_AsText(ST_GeomFromText(@g)) |\n+--------------------------------+\n| LINESTRING(1 1,4 4,6 6) |\n+--------------------------------+\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/st_astext','','https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/st_astext'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (32,3,'ST_ASWKT','A synonym for ST_ASTEXT().\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/st_aswkt','','https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/st_aswkt'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (33,3,'ST_GeomCollFromText','Syntax\n------\n\nST_GeomCollFromText(wkt[,srid])\nST_GeometryCollectionFromText(wkt[,srid])\nGeomCollFromText(wkt[,srid])\nGeometryCollectionFromText(wkt[,srid])\n\nDescription\n-----------\n\nConstructs a GEOMETRYCOLLECTION value using its WKT representation and SRID.\n\nST_GeomCollFromText(), ST_GeometryCollectionFromText(), GeomCollFromText() and\nGeometryCollectionFromText() are all synonyms.\n\nExample\n-------\n\nCREATE TABLE gis_geometrycollection (g GEOMETRYCOLLECTION);\nSHOW FIELDS FROM gis_geometrycollection;\nINSERT INTO gis_geometrycollection VALUES\n (GeomCollFromText(\'GEOMETRYCOLLECTION(POINT(0 0), LINESTRING(0 0,10\n10))\')),\n (GeometryFromWKB(AsWKB(GeometryCollection(Point(44, 6),\nLineString(Point(3, 6), Point(7, 9)))))),\n (GeomFromText(\'GeometryCollection()\')),\n (GeomFromText(\'GeometryCollection EMPTY\'));\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/st_geomcollfromtext','','https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/st_geomcollfromtext'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (34,3,'ST_GeometryCollectionFromText','A synonym for ST_GeomCollFromText.\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/st_geometrycollectionfromtext','','https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/st_geometrycollectionfromtext'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (35,3,'ST_GeometryFromText','A synonym for ST_GeomFromText.\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/st_geometryfromtext','','https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/st_geometryfromtext'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (36,3,'ST_GeomFromText','Syntax\n------\n\nST_GeomFromText(wkt[,srid])\nST_GeometryFromText(wkt[,srid])\nGeomFromText(wkt[,srid])\nGeometryFromText(wkt[,srid])\n\nDescription\n-----------\n\nConstructs a geometry value of any type using its WKT representation and SRID.\n\nGeomFromText(), GeometryFromText(), ST_GeomFromText() and\nST_GeometryFromText() are all synonyms.\n\nExample\n-------\n\nSET @g = ST_GEOMFROMTEXT(\'POLYGON((1 1,1 5,4 9,6 9,9 3,7 2,1 1))\');\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/st_geomfromtext','','https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/st_geomfromtext'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (37,3,'ST_LineFromText','Syntax\n------\n\nST_LineFromText(wkt[,srid])\nST_LineStringFromText(wkt[,srid])\nLineFromText(wkt[,srid])\nLineStringFromText(wkt[,srid])\n\nDescription\n-----------\n\nConstructs a LINESTRING value using its WKT representation and SRID.\n\nST_LineFromText(), ST_LineStringFromText(), ST_LineFromText() and\nST_LineStringFromText() are all synonyms.\n\nExamples\n--------\n\nCREATE TABLE gis_line (g LINESTRING);\nSHOW FIELDS FROM gis_line;\nINSERT INTO gis_line VALUES\n (LineFromText(\'LINESTRING(0 0,0 10,10 0)\')),\n (LineStringFromText(\'LINESTRING(10 10,20 10,20 20,10 20,10 10)\')),\n (LineStringFromWKB(AsWKB(LineString(Point(10, 10), Point(40, 10)))));\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/st_linefromtext','','https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/st_linefromtext'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (38,3,'ST_LineStringFromText','A synonym for ST_LineFromText.\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/st_linestringfromtext','','https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/st_linestringfromtext'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (39,3,'ST_PointFromText','Syntax\n------\n\nST_PointFromText(wkt[,srid])\nPointFromText(wkt[,srid])\n\nDescription\n-----------\n\nConstructs a POINT value using its WKT representation and SRID.\n\nST_PointFromText() and PointFromText() are synonyms.\n\nExamples\n--------\n\nCREATE TABLE gis_point (g POINT);\nSHOW FIELDS FROM gis_point;\nINSERT INTO gis_point VALUES\n (PointFromText(\'POINT(10 10)\')),\n (PointFromText(\'POINT(20 10)\')),\n (PointFromText(\'POINT(20 20)\')),\n (PointFromWKB(AsWKB(PointFromText(\'POINT(10 20)\'))));\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/st_pointfromtext','','https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/st_pointfromtext'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (40,3,'ST_PolyFromText','Syntax\n------\n\nST_PolyFromText(wkt[,srid])\nST_PolygonFromText(wkt[,srid])\nPolyFromText(wkt[,srid])\nPolygonFromText(wkt[,srid])\n\nDescription\n-----------\n\nConstructs a POLYGON value using its WKT representation and SRID.\n\nST_PolyFromText(), ST_PolygonFromText(), PolyFromText() and\nST_PolygonFromText() are all synonyms.\n\nExamples\n--------\n\nCREATE TABLE gis_polygon (g POLYGON);\nINSERT INTO gis_polygon VALUES\n (PolygonFromText(\'POLYGON((10 10,20 10,20 20,10 20,10 10))\')),\n (PolyFromText(\'POLYGON((0 0,50 0,50 50,0 50,0 0), (10 10,20 10,20 20,10\n20,10 10))\'));\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/st_polyfromtext','','https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/st_polyfromtext'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (41,3,'ST_PolygonFromText','A synonym for ST_PolyFromText.\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/st_polygonfromtext','','https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/wkt/st_polygonfromtext'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (42,4,'DIV','Syntax\n------\n\nDIV\n\nDescription\n-----------\n\nInteger division. Similar to FLOOR(), but is safe with BIGINT values.\nIncorrect results may occur for non-integer operands that exceed BIGINT range.\n\nIf the ERROR_ON_DIVISION_BY_ZERO SQL_MODE is used, a division by zero produces\nan error. Otherwise, it returns NULL.\n\nThe remainder of a division can be obtained using the MOD operator.\n\nExamples\n--------\n\nSELECT 300 DIV 7;\n+-----------+\n| 300 DIV 7 |\n+-----------+\n| 42 |\n+-----------+\n\nSELECT 300 DIV 0;\n+-----------+\n| 300 DIV 0 |\n+-----------+\n| NULL |\n+-----------+\n\nURL: https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/div','','https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/div'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (43,4,'ABS','Syntax\n------\n\nABS(X)\n\nDescription\n-----------\n\nReturns the absolute (non-negative) value of X. If X is not a number, it is\nconverted to a numeric type.\n\nExamples\n--------\n\nSELECT ABS(42);\n+---------+\n| ABS(42) |\n+---------+\n| 42 |\n+---------+\n\nSELECT ABS(-42);\n+----------+\n| ABS(-42) |\n+----------+\n| 42 |\n+----------+\n\nSELECT ABS(DATE \'1994-01-01\');\n+------------------------+\n| ABS(DATE \'1994-01-01\') |\n+------------------------+\n| 19940101 |\n+------------------------+\n\nURL: https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/abs','','https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/abs'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (44,4,'ACOS','Syntax\n------\n\nACOS(X)\n\nDescription\n-----------\n\nReturns the arc cosine of X, that is, the value whose cosine is X. Returns\nNULL if X is not in the range -1 to 1.\n\nExamples\n--------\n\nSELECT ACOS(1);\n+---------+\n| ACOS(1) |\n+---------+\n| 0 |\n+---------+\n\nSELECT ACOS(1.0001);\n+--------------+\n| ACOS(1.0001) |\n+--------------+\n| NULL |\n+--------------+\n\nSELECT ACOS(0);\n+-----------------+\n| ACOS(0) |\n+-----------------+\n| 1.5707963267949 |\n+-----------------+\n\nSELECT ACOS(0.234);\n+------------------+\n| ACOS(0.234) |\n+------------------+\n| 1.33460644244679 |\n+------------------+\n\nURL: https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/acos','','https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/acos'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (45,4,'ASIN','Syntax\n------\n\nASIN(X)\n\nDescription\n-----------\n\nReturns the arc sine of X, that is, the value whose sine is X. Returns NULL if\nX is not in the range -1 to 1.\n\nExamples\n--------\n\nSELECT ASIN(0.2);\n+--------------------+\n| ASIN(0.2) |\n+--------------------+\n| 0.2013579207903308 |\n+--------------------+\n\nSELECT ASIN(\'foo\');\n+-------------+\n| ASIN(\'foo\') |\n+-------------+\n| 0 |\n+-------------+\n\nSHOW WARNINGS;\n+---------+------+-----------------------------------------+\n| Level | Code | Message |\n+---------+------+-----------------------------------------+\n| Warning | 1292 | Truncated incorrect DOUBLE value: \'foo\' |\n+---------+------+-----------------------------------------+\n\nURL: https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/asin','','https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/asin'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (46,4,'ATAN','Syntax\n------\n\nATAN(X)\n\nDescription\n-----------\n\nReturns the arc tangent of X, that is, the value whose tangent is X.\n\nExamples\n--------\n\nSELECT ATAN(2);\n+--------------------+\n| ATAN(2) |\n+--------------------+\n| 1.1071487177940904 |\n+--------------------+\n\nSELECT ATAN(-2);\n+---------------------+\n| ATAN(-2) |\n+---------------------+\n| -1.1071487177940904 |\n+---------------------+\n\nURL: https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/atan','','https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/atan'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (47,4,'ATAN2','Syntax\n------\n\nATAN(Y,X), ATAN2(Y,X)\n\nDescription\n-----------\n\nReturns the arc tangent of the two variables X and Y. It is similar to\ncalculating the arc tangent of Y / X, except that the signs of both arguments\nare used to determine the quadrant of the result.\n\nExamples\n--------\n\nSELECT ATAN(-2,2);\n+---------------------+\n| ATAN(-2,2) |\n+---------------------+\n| -0.7853981633974483 |\n+---------------------+\n\nSELECT ATAN2(PI(),0);\n+--------------------+\n| ATAN2(PI(),0) |\n+--------------------+\n| 1.5707963267948966 |\n+--------------------+\n\nURL: https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/atan2','','https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/atan2'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (48,4,'CEIL','Syntax\n------\n\nCEIL(X)\n\nDescription\n-----------\n\nCEIL() is a synonym for CEILING().\n\nURL: https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/ceil','','https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/ceil'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (49,4,'CEILING','Syntax\n------\n\nCEILING(X)\n\nDescription\n-----------\n\nReturns the smallest integer value not less than X.\n\nExamples\n--------\n\nSELECT CEILING(1.23);\n+---------------+\n| CEILING(1.23) |\n+---------------+\n| 2 |\n+---------------+\n\nSELECT CEILING(-1.23);\n+----------------+\n| CEILING(-1.23) |\n+----------------+\n| -1 |\n+----------------+\n\nURL: https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/ceiling','','https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/ceiling'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (50,4,'CONV','Syntax\n------\n\nCONV(N,from_base,to_base)\n\nDescription\n-----------\n\nConverts numbers between different number bases. Returns a string\nrepresentation of the number N, converted from base from_base to base to_base.\n\nReturns NULL if any argument is NULL, or if the second or third argument are\nnot in the allowed range.\n\nThe argument N is interpreted as an integer, but may be specified as an\ninteger or a string. The minimum base is 2 and the maximum base is 36 (prior\nto MariaDB 11.4.0) or 62 (from MariaDB 11.4.0). If to_base is a negative\nnumber, N is regarded as a signed number. Otherwise, N is treated as unsigned.\nCONV() works with 64-bit precision.\n\nSome shortcuts for this function are also available: BIN(), OCT(), HEX(),\nUNHEX(). Also, MariaDB allows binary literal values and hexadecimal literal\nvalues.\n\nExamples\n--------\n\nSELECT CONV(\'a\',16,2);\n+----------------+\n| CONV(\'a\',16,2) |\n+----------------+\n| 1010 |\n+----------------+\n\nSELECT CONV(\'6E\',18,8);\n+-----------------+\n| CONV(\'6E\',18,8) |\n+-----------------+\n| 172 |\n+-----------------+\n\nSELECT CONV(-17,10,-18);\n+------------------+\n| CONV(-17,10,-18) |\n+------------------+\n| -H |\n+------------------+\n\nSELECT CONV(12+\'10\'+\'10\'+0xa,10,10);\n+------------------------------+\n| CONV(12+\'10\'+\'10\'+0xa,10,10) |\n+------------------------------+\n| 42 |\n+------------------------------+\n\nURL: https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/conv','','https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/conv'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (51,4,'COS','Syntax\n------\n\nCOS(X)\n\nDescription\n-----------\n\nReturns the cosine of X, where X is given in radians.\n\nExamples\n--------\n\nSELECT COS(PI());\n+-----------+\n| COS(PI()) |\n+-----------+\n| -1 |\n+-----------+\n\nURL: https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/cos','','https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/cos'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (52,4,'COT','Syntax\n------\n\nCOT(X)\n\nDescription\n-----------\n\nReturns the cotangent of X.\n\nExamples\n--------\n\nSELECT COT(42);\n+--------------------+\n| COT(42) |\n+--------------------+\n| 0.4364167060752729 |\n+--------------------+\n\nSELECT COT(12);\n+---------------------+\n| COT(12) |\n+---------------------+\n| -1.5726734063976893 |\n+---------------------+\n\nSELECT COT(0);\nERROR 1690 (22003): DOUBLE value is out of range in \'cot(0)\'\n\nURL: https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/cot','','https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/cot'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (53,4,'CRC32','Syntax\n------\n\n<= MariaDB 10.7\n\nCRC32(expr)\n\nFrom MariaDB 10.8\n\nCRC32([par,]expr)\n\nDescription\n-----------\n\nComputes a cyclic redundancy check (CRC) value and returns a 32-bit unsigned\nvalue. The result is NULL if the argument is NULL. The argument is expected to\nbe a string and (if possible) is treated as one if it is not.\n\nUses the ISO 3309 polynomial that used by zlib and many others. MariaDB 10.8\nintroduced the CRC32C() function, which uses the alternate Castagnoli\npolynomia.\n\nMariaDB starting with 10.8\n--------------------------\nOften, CRC is computed in pieces. To facilitate this, MariaDB 10.8.0\nintroduced an optional parameter: CRC32(\'MariaDB\')=CRC32(CRC32(\'Maria\'),\'DB\').\n\nExamples\n--------\n\nSELECT CRC32(\'MariaDB\');\n+------------------+\n| CRC32(\'MariaDB\') |\n+------------------+\n| 4227209140 |\n+------------------+\n\nSELECT CRC32(\'mariadb\');\n+------------------+\n| CRC32(\'mariadb\') |\n+------------------+\n| 2594253378 |\n+------------------+\n\nFrom MariaDB 10.8.0\n\nSELECT CRC32(CRC32(\'Maria\'),\'DB\');\n+----------------------------+\n| CRC32(CRC32(\'Maria\'),\'DB\') |\n+----------------------------+\n| 4227209140 |\n+----------------------------+\n\nURL: https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/crc32','','https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/crc32'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (54,4,'CRC32C','MariaDB starting with 10.8\n--------------------------\nIntroduced in MariaDB 10.8.0 to compute a cyclic redundancy check (CRC) value\nusing the Castagnoli polynomial.\n\nSyntax\n------\n\nCRC32C([par,]expr)\n\nDescription\n-----------\n\nMariaDB has always included a native unary function CRC32() that computes the\nCRC-32 of a string using the ISO 3309 polynomial that used by zlib and many\nothers.\n\nInnoDB and MyRocks use a different polynomial, which was implemented in SSE4.2\ninstructions that were introduced in the Intel Nehalem microarchitecture. This\nis commonly called CRC-32C (Castagnoli).\n\nThe CRC32C function uses the Castagnoli polynomial.\n\nThis allows SELECT…INTO DUMPFILE to be used for the creation of files with\nvalid checksums, such as a logically empty InnoDB redo log file ib_logfile0\ncorresponding to a particular log sequence number.\n\nThe optional parameter allows the checksum to be computed in pieces:\nCRC32C(\'MariaDB\')=CRC32C(CRC32C(\'Maria\'),\'DB\').\n\nExamples\n--------\n\nSELECT CRC32C(\'MariaDB\');\n+-------------------+\n| CRC32C(\'MariaDB\') |\n+-------------------+\n| 809606978 |\n+-------------------+\n\nSELECT CRC32C(CRC32C(\'Maria\'),\'DB\');\n+------------------------------+\n| CRC32C(CRC32C(\'Maria\'),\'DB\') |\n+------------------------------+\n| 809606978 |\n+------------------------------+\n\nURL: https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/crc32c','','https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/crc32c'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (55,4,'DEGREES','Syntax\n------\n\nDEGREES(X)\n\nDescription\n-----------\n\nReturns the argument X, converted from radians to degrees.\n\nThis is the converse of the RADIANS() function.\n\nExamples\n--------\n\nSELECT DEGREES(PI());\n+---------------+\n| DEGREES(PI()) |\n+---------------+\n| 180 |\n+---------------+\n\nSELECT DEGREES(PI() / 2);\n+-------------------+\n| DEGREES(PI() / 2) |\n+-------------------+\n| 90 |\n+-------------------+\n\nSELECT DEGREES(45);\n+-----------------+\n| DEGREES(45) |\n+-----------------+\n| 2578.3100780887 |\n+-----------------+\n\nURL: https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/degrees','','https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/degrees'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (56,4,'EXP','Syntax\n------\n\nEXP(X)\n\nDescription\n-----------\n\nReturns the value of e (the base of natural logarithms) raised to the power of\nX. The inverse of this function is LOG() (using a single argument only) or\nLN().\n\nIf X is NULL, this function returns NULL.\n\nExamples\n--------\n\nSELECT EXP(2);\n+------------------+\n| EXP(2) |\n+------------------+\n| 7.38905609893065 |\n+------------------+\n\nSELECT EXP(-2);\n+--------------------+\n| EXP(-2) |\n+--------------------+\n| 0.1353352832366127 |\n+--------------------+\n\nSELECT EXP(0);\n+--------+\n| EXP(0) |\n+--------+\n| 1 |\n+--------+\n\nSELECT EXP(NULL);\n+-----------+\n| EXP(NULL) |\n+-----------+\n| NULL |\n+-----------+\n\nURL: https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/exp','','https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/exp'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (57,4,'FLOOR','Syntax\n------\n\nFLOOR(X)\n\nDescription\n-----------\n\nReturns the largest integer value not greater than X.\n\nExamples\n--------\n\nSELECT FLOOR(1.23);\n+-------------+\n| FLOOR(1.23) |\n+-------------+\n| 1 |\n+-------------+\n\nSELECT FLOOR(-1.23);\n+--------------+\n| FLOOR(-1.23) |\n+--------------+\n| -2 |\n+--------------+\n\nURL: https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/floor','','https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/floor'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (58,4,'LN','Syntax\n------\n\nLN(X)\n\nDescription\n-----------\n\nReturns the natural logarithm of X; that is, the base-e logarithm of X. If X\nis less than or equal to 0, or NULL, then NULL is returned.\n\nThe inverse of this function is EXP().\n\nExamples\n--------\n\nSELECT LN(2);\n+-------------------+\n| LN(2) |\n+-------------------+\n| 0.693147180559945 |\n+-------------------+\n\nSELECT LN(-2);\n+--------+\n| LN(-2) |\n+--------+\n| NULL |\n+--------+\n\nURL: https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/ln','','https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/ln'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (59,4,'LOG','Syntax\n------\n\nLOG(X), LOG(B,X)\n\nDescription\n-----------\n\nIf called with one parameter, this function returns the natural logarithm of\nX. If X is less than or equal to 0, then NULL is returned.\n\nIf called with two parameters, it returns the logarithm of X to the base B. If\nB is <= 1 or X <= 0, the function returns NULL.\n\nIf any argument is NULL, the function returns NULL.\n\nThe inverse of this function (when called with a single argument) is the EXP()\nfunction.\n\nExamples\n--------\n\nLOG(X):\n\nSELECT LOG(2);\n+-------------------+\n| LOG(2) |\n+-------------------+\n| 0.693147180559945 |\n+-------------------+\n\nSELECT LOG(-2);\n+---------+\n| LOG(-2) |\n+---------+\n| NULL |\n+---------+\n\nLOG(B,X)\n\nSELECT LOG(2,16);\n+-----------+\n| LOG(2,16) |\n+-----------+\n| 4 |\n+-----------+\n\nSELECT LOG(3,27);\n+-----------+\n| LOG(3,27) |\n+-----------+\n| 3 |\n+-----------+\n\nSELECT LOG(3,1);\n+----------+\n| LOG(3,1) |\n+----------+\n| 0 |\n+----------+\n\nSELECT LOG(3,0);\n+----------+\n| LOG(3,0) |\n+----------+\n| NULL |\n+----------+\n\nURL: https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/log','','https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/log'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (60,4,'LOG10','Syntax\n------\n\nLOG10(X)\n\nDescription\n-----------\n\nReturns the base-10 logarithm of X.\n\nExamples\n--------\n\nSELECT LOG10(2);\n+-------------------+\n| LOG10(2) |\n+-------------------+\n| 0.301029995663981 |\n+-------------------+\n\nSELECT LOG10(100);\n+------------+\n| LOG10(100) |\n+------------+\n| 2 |\n+------------+\n\nSELECT LOG10(-100);\n+-------------+\n| LOG10(-100) |\n+-------------+\n| NULL |\n+-------------+\n\nURL: https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/log10','','https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/log10'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (61,4,'LOG2','Syntax\n------\n\nLOG2(X)\n\nDescription\n-----------\n\nReturns the base-2 logarithm of X.\n\nExamples\n--------\n\nSELECT LOG2(4398046511104);\n+---------------------+\n| LOG2(4398046511104) |\n+---------------------+\n| 42 |\n+---------------------+\n\nSELECT LOG2(65536);\n+-------------+\n| LOG2(65536) |\n+-------------+\n| 16 |\n+-------------+\n\nSELECT LOG2(-100);\n+------------+\n| LOG2(-100) |\n+------------+\n| NULL |\n+------------+\n\nURL: https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/log2','','https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/log2'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (62,4,'MOD','Syntax\n------\n\nMOD(N,M), N % M, N MOD M\n\nDescription\n-----------\n\nModulo operation. Returns the remainder of N divided by M. See also Modulo\nOperator.\n\nIf the ERROR_ON_DIVISION_BY_ZERO SQL_MODE is used, any number modulus zero\nproduces an error. Otherwise, it returns NULL.\n\nThe integer part of a division can be obtained using DIV.\n\nExamples\n--------\n\nSELECT 1042 % 50;\n+-----------+\n| 1042 % 50 |\n+-----------+\n| 42 |\n+-----------+\n\nSELECT MOD(234, 10);\n+--------------+\n| MOD(234, 10) |\n+--------------+\n| 4 |\n+--------------+\n\nSELECT 253 % 7;\n+---------+\n| 253 % 7 |\n+---------+\n| 1 |\n+---------+\n\nSELECT MOD(29,9);\n+-----------+\n| MOD(29,9) |\n+-----------+\n| 2 |\n+-----------+\n\nSELECT 29 MOD 9;\n+----------+\n| 29 MOD 9 |\n+----------+\n| 2 |\n+----------+\n\nURL: https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/mod','','https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/mod'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (63,4,'OCT','Syntax\n------\n\nOCT(N)\n\nDescription\n-----------\n\nReturns a string representation of the octal value of N, where N is a longlong\n(BIGINT) number. This is equivalent to CONV(N,10,8). Returns NULL if N is NULL.\n\nExamples\n--------\n\nSELECT OCT(34);\n+---------+\n| OCT(34) |\n+---------+\n| 42 |\n+---------+\n\nSELECT OCT(12);\n+---------+\n| OCT(12) |\n+---------+\n| 14 |\n+---------+\n\nURL: https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/oct','','https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/oct'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (64,4,'PI','Syntax\n------\n\nPI()\n\nDescription\n-----------\n\nReturns the value of π (pi). The default number of decimal places displayed is\nsix, but MariaDB uses the full double-precision value internally.\n\nExamples\n--------\n\nSELECT PI();\n+----------+\n| PI() |\n+----------+\n| 3.141593 |\n+----------+\n\nSELECT PI()+0.0000000000000000000000;\n+-------------------------------+\n| PI()+0.0000000000000000000000 |\n+-------------------------------+\n| 3.1415926535897931159980 |\n+-------------------------------+\n\nURL: https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/pi','','https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/pi'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (65,4,'POW','Syntax\n------\n\nPOW(X,Y)\n\nDescription\n-----------\n\nReturns the value of X raised to the power of Y.\n\nPOWER() is a synonym.\n\nExamples\n--------\n\nSELECT POW(2,3);\n+----------+\n| POW(2,3) |\n+----------+\n| 8 |\n+----------+\n\nSELECT POW(2,-2);\n+-----------+\n| POW(2,-2) |\n+-----------+\n| 0.25 |\n+-----------+\n\nURL: https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/pow','','https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/pow'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (66,4,'POWER','Syntax\n------\n\nPOWER(X,Y)\n\nDescription\n-----------\n\nThis is a synonym for POW(), which returns the value of X raised to the power\nof Y.\n\nURL: https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/power','','https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/power'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (67,4,'RADIANS','Syntax\n------\n\nRADIANS(X)\n\nDescription\n-----------\n\nReturns the argument X, converted from degrees to radians. Note that π radians\nequals 180 degrees.\n\nThis is the converse of the DEGREES() function.\n\nExamples\n--------\n\nSELECT RADIANS(45);\n+-------------------+\n| RADIANS(45) |\n+-------------------+\n| 0.785398163397448 |\n+-------------------+\n\nSELECT RADIANS(90);\n+-----------------+\n| RADIANS(90) |\n+-----------------+\n| 1.5707963267949 |\n+-----------------+\n\nSELECT RADIANS(PI());\n+--------------------+\n| RADIANS(PI()) |\n+--------------------+\n| 0.0548311355616075 |\n+--------------------+\n\nSELECT RADIANS(180);\n+------------------+\n| RADIANS(180) |\n+------------------+\n| 3.14159265358979 |\n+------------------+\n\nURL: https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/radians','','https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/radians'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (68,4,'RAND','Syntax\n------\n\nRAND(), RAND(N)\n\nDescription\n-----------\n\nReturns a random DOUBLE precision floating point value v in the range 0 <= v <\n1.0. If a constant integer argument N is specified, it is used as the seed\nvalue, which produces a repeatable sequence of column values. In the example\nbelow, note that the sequences of values produced by RAND(3) is the same both\nplaces where it occurs.\n\nIn a WHERE clause, RAND() is evaluated each time the WHERE is executed.\n\nStatements using the RAND() function are not safe for statement-based\nreplication.\n\nPractical uses\n--------------\n\nThe expression to get a random integer from a given range is the following:\n\nFLOOR(min_value + RAND() * (max_value - min_value +1))\n\nRAND() is often used to read random rows from a table, as follows:\n\nSELECT * FROM my_table ORDER BY RAND() LIMIT 10;\n\nNote, however, that this technique should never be used on a large table as it\nwill be extremely slow. MariaDB will read all rows in the table, generate a\nrandom value for each of them, order them, and finally will apply the LIMIT\nclause.\n\nExamples\n--------\n\nCREATE TABLE t (i INT);\n\nINSERT INTO t VALUES(1),(2),(3);\n\nSELECT i, RAND() FROM t;\n+------+-------------------+\n| i | RAND() |\n+------+-------------------+\n| 1 | 0.255651095188829 |\n| 2 | 0.833920199269355 |\n| 3 | 0.40264774151393 |\n+------+-------------------+\n\nSELECT i, RAND(3) FROM t;\n+------+-------------------+\n| i | RAND(3) |\n+------+-------------------+\n| 1 | 0.90576975597606 |\n| 2 | 0.373079058130345 |\n| 3 | 0.148086053457191 |\n+------+-------------------+\n\nSELECT i, RAND() FROM t;\n+------+-------------------+\n| i | RAND() |\n+------+-------------------+\n| 1 | 0.511478140495232 |\n| 2 | 0.349447508668012 |\n| 3 | 0.212803152588013 |\n+------+-------------------+\n\nUsing the same seed, the same sequence will be returned:\n\nSELECT i, RAND(3) FROM t;\n+------+-------------------+\n| i | RAND(3) |\n+------+-------------------+\n| 1 | 0.90576975597606 |\n| 2 | 0.373079058130345 |\n| 3 | 0.148086053457191 |\n+------+-------------------+\n\nGenerating a random number from 5 to 15:\n\nSELECT FLOOR(5 + (RAND() * 11));\n\nURL: https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/rand','','https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/rand'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (69,4,'ROUND','Syntax\n------\n\nROUND(X), ROUND(X,D)\n\nDescription\n-----------\n\nRounds the argument X to D decimal places. D defaults to 0 if not specified. D\ncan be negative to cause D digits left of the decimal point of the value X to\nbecome zero.\n\nThe rounding algorithm depends on the data type of X:\n\n* for floating point types (FLOAT, DOUBLE) the C libraries rounding function\nis used, so the behavior *may* differ between operating systems\n* for fixed point types (DECIMAL, DEC/NUMBER/FIXED) the \"round half up\" rule\nis used, meaning that e.g. a value ending in exactly .5 is always rounded up.\n\nExamples\n--------\n\nSELECT ROUND(-1.23);\n+--------------+\n| ROUND(-1.23) |\n+--------------+\n| -1 |\n+--------------+\n\nSELECT ROUND(-1.58);\n+--------------+\n| ROUND(-1.58) |\n+--------------+\n| -2 |\n+--------------+\n\nSELECT ROUND(1.58); \n+-------------+\n| ROUND(1.58) |\n+-------------+\n| 2 |\n+-------------+\n\nSELECT ROUND(1.298, 1);\n+-----------------+\n| ROUND(1.298, 1) |\n+-----------------+\n| 1.3 |\n+-----------------+\n\nSELECT ROUND(1.298, 0);\n+-----------------+\n| ROUND(1.298, 0) |\n+-----------------+\n| 1 |\n+-----------------+\n\nSELECT ROUND(23.298, -1);\n+-------------------+\n| ROUND(23.298, -1) |\n+-------------------+\n| 20 |\n+-------------------+\n\nURL: https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/round','','https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/round'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (70,4,'SIGN','Syntax\n------\n\nSIGN(X)\n\nDescription\n-----------\n\nReturns the sign of the argument as -1, 0, or 1, depending on whether X is\nnegative, zero, or positive.\n\nExamples\n--------\n\nSELECT SIGN(-32);\n+-----------+\n| SIGN(-32) |\n+-----------+\n| -1 |\n+-----------+\n\nSELECT SIGN(0);\n+---------+\n| SIGN(0) |\n+---------+\n| 0 |\n+---------+\n\nSELECT SIGN(234);\n+-----------+\n| SIGN(234) |\n+-----------+\n| 1 |\n+-----------+\n\nURL: https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/sign','','https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/sign'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (71,4,'SIN','Syntax\n------\n\nSIN(X)\n\nDescription\n-----------\n\nReturns the sine of X, where X is given in radians.\n\nExamples\n--------\n\nSELECT SIN(1.5707963267948966);\n+-------------------------+\n| SIN(1.5707963267948966) |\n+-------------------------+\n| 1 |\n+-------------------------+\n\nSELECT SIN(PI());\n+----------------------+\n| SIN(PI()) |\n+----------------------+\n| 1.22460635382238e-16 |\n+----------------------+\n\nSELECT ROUND(SIN(PI()));\n+------------------+\n| ROUND(SIN(PI())) |\n+------------------+\n| 0 |\n+------------------+\n\nURL: https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/sin','','https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/sin'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (72,4,'SQRT','Syntax\n------\n\nSQRT(X)\n\nDescription\n-----------\n\nReturns the square root of X. If X is negative, NULL is returned.\n\nExamples\n--------\n\nSELECT SQRT(4);\n+---------+\n| SQRT(4) |\n+---------+\n| 2 |\n+---------+\n\nSELECT SQRT(20);\n+------------------+\n| SQRT(20) |\n+------------------+\n| 4.47213595499958 |\n+------------------+\n\nSELECT SQRT(-16);\n+-----------+\n| SQRT(-16) |\n+-----------+\n| NULL |\n+-----------+\n\nSELECT SQRT(1764);\n+------------+\n| SQRT(1764) |\n+------------+\n| 42 |\n+------------+\n\nURL: https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/sqrt','','https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/sqrt'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (73,4,'TAN','Syntax\n------\n\nTAN(X)\n\nDescription\n-----------\n\nReturns the tangent of X, where X is given in radians.\n\nExamples\n--------\n\nSELECT TAN(0.7853981633974483);\n+-------------------------+\n| TAN(0.7853981633974483) |\n+-------------------------+\n| 0.9999999999999999 |\n+-------------------------+\n\nSELECT TAN(PI());\n+-----------------------+\n| TAN(PI()) |\n+-----------------------+\n| -1.22460635382238e-16 |\n+-----------------------+\n\nSELECT TAN(PI()+1);\n+-----------------+\n| TAN(PI()+1) |\n+-----------------+\n| 1.5574077246549 |\n+-----------------+\n\nSELECT TAN(RADIANS(PI()));\n+--------------------+\n| TAN(RADIANS(PI())) |\n+--------------------+\n| 0.0548861508080033 |\n+--------------------+\n\nURL: https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/tan','','https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/tan'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (74,4,'TRUNCATE','This page documents the TRUNCATE function. See TRUNCATE TABLE for the DDL\nstatement.\n\nSyntax\n------\n\nTRUNCATE(X,D)\n\nDescription\n-----------\n\nReturns the number X, truncated to D decimal places. If D is 0, the result has\nno decimal point or fractional part. D can be negative to cause D digits left\nof the decimal point of the value X to become zero.\n\nExamples\n--------\n\nSELECT TRUNCATE(1.223,1);\n+-------------------+\n| TRUNCATE(1.223,1) |\n+-------------------+\n| 1.2 |\n+-------------------+\n\nSELECT TRUNCATE(1.999,1);\n+-------------------+\n| TRUNCATE(1.999,1) |\n+-------------------+\n| 1.9 |\n+-------------------+\n\nSELECT TRUNCATE(1.999,0); \n+-------------------+\n| TRUNCATE(1.999,0) |\n+-------------------+\n| 1 |\n+-------------------+\n\nSELECT TRUNCATE(-1.999,1);\n+--------------------+\n| TRUNCATE(-1.999,1) |\n+--------------------+\n| -1.9 |\n+--------------------+\n\nSELECT TRUNCATE(122,-2);\n+------------------+\n| TRUNCATE(122,-2) |\n+------------------+\n| 100 |\n+------------------+\n\nSELECT TRUNCATE(10.28*100,0);\n+-----------------------+\n| TRUNCATE(10.28*100,0) |\n+-----------------------+\n| 1028 |\n+-----------------------+\n\nURL: https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/truncate','','https://mariadb.com/docs/server/reference/sql-functions/numeric-functions/truncate'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (75,5,'INSTALL PLUGIN','Syntax\n------\n\nINSTALL PLUGIN [IF NOT EXISTS] plugin_name SONAME \'plugin_library\'\n\nDescription\n-----------\n\nThis statement installs an individual plugin from the specified library. To\ninstall the whole library (which could be required), use INSTALL SONAME. See\nalso Installing a Plugin.\n\nplugin_name is the name of the plugin as defined in the plugin declaration\nstructure contained in the library file. Plugin names are not case sensitive.\nFor maximal compatibility, plugin names should be limited to ASCII letters,\ndigits, and underscore, because they are used in C source files, shell command\nlines, M4 and Bourne shell scripts, and SQL environments.\n\nplugin_library is the name of the shared library that contains the plugin\ncode. The file name extension can be omitted (which makes the statement look\nthe same on all architectures).\n\nThe shared library must be located in the plugin directory (that is, the\ndirectory named by the plugin_dir system variable). The library must be in the\nplugin directory itself, not in a subdirectory. By default, plugin_dir is\nplugin directory under the directory named by the pkglibdir configuration\nvariable, but it can be changed by setting the value of plugin_dir at server\nstartup. For example, set its value in a my.cnf file:\n\n[mysqld]\nplugin_dir=/path/to/plugin/directory\nIf the value of plugin_dir is a relative path name, it is taken to be relative\nto the MySQL base directory (the value of the basedir system variable).\n\nINSTALL PLUGIN adds a line to the mysql.plugin table that describes the\nplugin. This table contains the plugin name and library file name.\n\nINSTALL PLUGIN causes the server to read option (my.cnf) files just as during\nserver startup. This enables the plugin to pick up any relevant options from\nthose files. It is possible to add plugin options to an option file even\nbefore loading a plugin (if the loose prefix is used). It is also possible to\nuninstall a plugin, edit my.cnf, and install the plugin again. Restarting the\nplugin this way enables it to the new option values without a server restart.\n\nINSTALL PLUGIN also loads and initializes the plugin code to make the plugin\navailable for use. A plugin is initialized by executing its initialization\nfunction, which handles any setup that the plugin must perform before it can\nbe used.\n\nTo use INSTALL PLUGIN, you must have the INSERT privilege for the mysql.plugin\ntable.\n\nAt server startup, the server loads and initializes any plugin that is listed\nin the mysql.plugin table. This means that a plugin is installed with INSTALL\nPLUGIN only once, not every time the server starts. Plugin loading at startup\ndoes not occur if the server is started with the --skip-grant-tables option.\n\nWhen the server shuts down, it executes the de-initialization function for\neach plugin that is loaded so that the plugin has a chance to perform any\nfinal cleanup.\n\nIf you need to load plugins for a single server startup when the\n--skip-grant-tables option is given (which tells the server not to read system\ntables), use the --plugin-load mysqld option.\n\nMariaDB starting with 10.4.0\n----------------------------\n\nIF NOT EXISTS\n-------------\n\nWhen the IF NOT EXISTS clause is used, MariaDB will return a note instead of\nan error if the specified plugin already exists. See SHOW WARNINGS.\n\nExamples\n--------\n\nINSTALL PLUGIN sphinx SONAME \'ha_sphinx.so\';\n\nThe extension can also be omitted:\n\nINSTALL PLUGIN innodb SONAME \'ha_xtradb\';\n\nFrom MariaDB 10.4.0:\n\nINSTALL PLUGIN IF NOT EXISTS example SONAME \'ha_example\';\nQuery OK, 0 rows affected (0.104 sec)\n\nINSTALL PLUGIN IF NOT EXISTS example SONAME \'ha_example\';\nQuery OK, 0 rows affected, 1 warning (0.000 sec)\n\nSHOW WARNINGS;\n+-------+------+------------------------------------+\n| Level | Code | Message |\n+-------+------+------------------------------------+\n| Note | 1968 | Plugin \'example\' already installed |\n+-------+------+------------------------------------+\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/administrative-sql-statements/plugin-sql-statements/install-plugin','','https://mariadb.com/docs/server/reference/sql-statements/administrative-sql-statements/plugin-sql-statements/install-plugin'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (76,5,'UNINSTALL PLUGIN','Syntax\n------\n\nUNINSTALL PLUGIN [IF EXISTS] plugin_name\n\nDescription\n-----------\n\nThis statement removes a single installed plugin. To uninstall the whole\nlibrary which contains the plugin, use UNINSTALL SONAME. You cannot uninstall\na plugin if any table that uses it is open.\n\nplugin_name must be the name of some plugin that is listed in the mysql.plugin\ntable. The server executes the plugin\'s deinitialization function and removes\nthe row for the plugin from the mysql.plugin table, so that subsequent server\nrestarts will not load and initialize the plugin. UNINSTALL PLUGIN does not\nremove the plugin\'s shared library file.\n\nTo use UNINSTALL PLUGIN, you must have the DELETE privilege for the\nmysql.plugin table.\n\nMariaDB starting with 10.4.0\n----------------------------\n\nIF EXISTS\n---------\n\nIf the IF EXISTS clause is used, MariaDB will return a note instead of an\nerror if the plugin does not exist. See SHOW WARNINGS.\n\nExamples\n--------\n\nUNINSTALL PLUGIN example;\n\nFrom MariaDB 10.4.0:\n\nUNINSTALL PLUGIN IF EXISTS example;\nQuery OK, 0 rows affected (0.099 sec)\n\nUNINSTALL PLUGIN IF EXISTS example;\nQuery OK, 0 rows affected, 1 warning (0.000 sec)\n\nSHOW WARNINGS;\n+-------+------+-------------------------------+\n| Level | Code | Message |\n+-------+------+-------------------------------+\n| Note | 1305 | PLUGIN example does not exist |\n+-------+------+-------------------------------+\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/administrative-sql-statements/plugin-sql-statements/uninstall-plugin','','https://mariadb.com/docs/server/reference/sql-statements/administrative-sql-statements/plugin-sql-statements/uninstall-plugin'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (77,5,'INSTALL SONAME','Syntax\n------\n\nINSTALL SONAME \'plugin_library\'\n\nDescription\n-----------\n\nThis statement is a variant of INSTALL PLUGIN. It installs all plugins from a\ngiven plugin_library. See INSTALL PLUGIN for details.\n\nplugin_library is the name of the shared library that contains the plugin\ncode. The file name extension (for example, libmyplugin.so or libmyplugin.dll)\ncan be omitted (which makes the statement look the same on all architectures).\n\nThe shared library must be located in the plugin directory (that is, the\ndirectory named by the plugin_dir system variable). The library must be in the\nplugin directory itself, not in a subdirectory. By default, plugin_dir is\nplugin directory under the directory named by the pkglibdir configuration\nvariable, but it can be changed by setting the value of plugin_dir at server\nstartup. For example, set its value in a my.cnf file:\n\n[mysqld]\nplugin_dir=/path/to/plugin/directory\nIf the value of plugin_dir is a relative path name, it is taken to be relative\nto the MySQL base directory (the value of the basedir system variable).\n\nINSTALL SONAME adds one or more lines to the mysql.plugin table that describes\nthe plugin. This table contains the plugin name and library file name.\n\nINSTALL SONAME causes the server to read option (my.cnf) files just as during\nserver startup. This enables the plugin to pick up any relevant options from\nthose files. It is possible to add plugin options to an option file even\nbefore loading a plugin (if the loose prefix is used). It is also possible to\nuninstall a plugin, edit my.cnf, and install the plugin again. Restarting the\nplugin this way enables it to the new option values without a server restart.\n\nINSTALL SONAME also loads and initializes the plugin code to make the plugin\navailable for use. A plugin is initialized by executing its initialization\nfunction, which handles any setup that the plugin must perform before it can\nbe used.\n\nTo use INSTALL SONAME, you must have the INSERT privilege for the mysql.plugin\ntable.\n\nAt server startup, the server loads and initializes any plugin that is listed\nin the mysql.plugin table. This means that a plugin is installed with INSTALL\nSONAME only once, not every time the server starts. Plugin loading at startup\ndoes not occur if the server is started with the --skip-grant-tables option.\n\nWhen the server shuts down, it executes the de-initialization function for\neach plugin that is loaded so that the plugin has a chance to perform any\nfinal cleanup.\n\nIf you need to load plugins for a single server startup when the\n--skip-grant-tables option is given (which tells the server not to read system\ntables), use the --plugin-load mysqld option.\n\nIf you need to install only one plugin from a library, use the INSTALL PLUGIN\nstatement.\n\nExamples\n--------\n\nTo load the XtraDB storage engine and all of its information_schema tables\nwith one statement, use\n\nINSTALL SONAME \'ha_xtradb\';\n\nThis statement can be used instead of INSTALL PLUGIN even when the library\ncontains only one plugin:\n\nINSTALL SONAME \'ha_sequence\';\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/administrative-sql-statements/plugin-sql-statements/install-soname','','https://mariadb.com/docs/server/reference/sql-statements/administrative-sql-statements/plugin-sql-statements/install-soname'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (78,5,'UNINSTALL SONAME','Syntax\n------\n\nUNINSTALL SONAME [IF EXISTS] \'plugin_library\'\n\nDescription\n-----------\n\nThis statement is a variant of UNINSTALL PLUGIN statement, that removes all\nplugins belonging to a specified plugin_library. See UNINSTALL PLUGIN for\ndetails.\n\nplugin_library is the name of the shared library that contains the plugin\ncode. The file name extension (for example, libmyplugin.so or libmyplugin.dll)\ncan be omitted (which makes the statement look the same on all architectures).\n\nTo use UNINSTALL SONAME, you must have the DELETE privilege for the\nmysql.plugin table.\n\nMariaDB starting with 10.4.0\n----------------------------\n\nIF EXISTS\n---------\n\nIf the IF EXISTS clause is used, MariaDB will return a note instead of an\nerror if the plugin library does not exist. See SHOW WARNINGS.\n\nExamples\n--------\n\nTo uninstall the XtraDB plugin and all of its information_schema tables with\none statement, use\n\nUNINSTALL SONAME \'ha_xtradb\';\n\nFrom MariaDB 10.4.0:\n\nUNINSTALL SONAME IF EXISTS \'ha_example\';\nQuery OK, 0 rows affected (0.099 sec)\n\nUNINSTALL SONAME IF EXISTS \'ha_example\';\nQuery OK, 0 rows affected, 1 warning (0.000 sec)\n\nSHOW WARNINGS;\n+-------+------+-------------------------------------+\n| Level | Code | Message |\n+-------+------+-------------------------------------+\n| Note | 1305 | SONAME ha_example.so does not exist |\n+-------+------+-------------------------------------+\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/administrative-sql-statements/plugin-sql-statements/uninstall-soname','','https://mariadb.com/docs/server/reference/sql-statements/administrative-sql-statements/plugin-sql-statements/uninstall-soname'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (79,5,'Plugin Overview','Plugins are server components that enhance MariaDB in some way. These can be\nanything from new storage engines, plugins for enhancing full-text parsing, or\neven small enhancements, such as a plugin to get a timestamp as an integer.\n\nQuerying Plugin Information\n---------------------------\n\nThere are a number of ways to see which plugins are currently active.\n\nA server almost always has a large number of active plugins, because the\nserver contains a large number of built-in plugins, which are active by\ndefault and cannot be uninstalled.\n\nQuerying Plugin Information with SHOW PLUGINS\n---------------------------------------------\n\nThe SHOW PLUGINS statement can be used to query information about all active\nplugins.\n\nFor example:\n\nSHOW PLUGINS\\G;\n********************** 1. row **********************\n Name: binlog\n Status: ACTIVE\n Type: STORAGE ENGINE\nLibrary: NULL\nLicense: GPL\n********************** 2. row **********************\n Name: mysql_native_password\n Status: ACTIVE\n Type: AUTHENTICATION\nLibrary: NULL\nLicense: GPL\n********************** 3. row **********************\n Name: mysql_old_password\n Status: ACTIVE\n Type: AUTHENTICATION\nLibrary: NULL\nLicense: GPL\n...\n\nIf a plugin\'s Library column has a NULL value, then the plugin is built-in,\nand it cannot be uninstalled.\n\nQuerying Plugin Information with information_schema.PLUGINS\n-----------------------------------------------------------\n\nThe information_schema.PLUGINS table can be queried to get more detailed\ninformation about plugins.\n\nFor example:\n\nSELECT * FROM information_schema.PLUGINS\\G\n...\n*************************** 6. row ***************************\n PLUGIN_NAME: CSV\n PLUGIN_VERSION: 1.0\n PLUGIN_STATUS: ACTIVE\n PLUGIN_TYPE: STORAGE ENGINE\n PLUGIN_TYPE_VERSION: 100003.0\n PLUGIN_LIBRARY: NULL\nPLUGIN_LIBRARY_VERSION: NULL\n PLUGIN_AUTHOR: Brian Aker, MySQL AB\n PLUGIN_DESCRIPTION: CSV storage engine\n PLUGIN_LICENSE: GPL\n LOAD_OPTION: FORCE\n PLUGIN_MATURITY: Stable\n PLUGIN_AUTH_VERSION: 1.0\n*************************** 7. row ***************************\n PLUGIN_NAME: MEMORY\n PLUGIN_VERSION: 1.0\n PLUGIN_STATUS: ACTIVE\n PLUGIN_TYPE: STORAGE ENGINE\n PLUGIN_TYPE_VERSION: 100003.0\n PLUGIN_LIBRARY: NULL\nPLUGIN_LIBRARY_VERSION: NULL\n PLUGIN_AUTHOR: MySQL AB\n PLUGIN_DESCRIPTION: Hash based, stored in memory, useful for temporary\ntables\n PLUGIN_LICENSE: GPL\n LOAD_OPTION: FORCE\n PLUGIN_MATURITY: Stable\n PLUGIN_AUTH_VERSION: 1.0\n...\n\nIf a plugin\'s PLUGIN_LIBRARY column has the NULL value, then the plugin is\nbuilt-in, and it cannot be uninstalled.\n\nQuerying Plugin Information with mysql.plugin\n---------------------------------------------\n\nThe mysql.plugin table can be queried to get information about installed\nplugins.\n\nThis table only contains information about plugins that have been installed\nvia the following methods:\n\n* The INSTALL SONAME statement.\n* The INSTALL PLUGIN statement.\n* The mariadb-plugin utility.\n\nThis table does not contain information about:\n\n* Built-in plugins.\n* Plugins loaded with the --plugin-load-add option.\n* Plugins loaded with the --plugin-load option.\n\nThis table only contains enough information to reload the plugin when the\nserver is restarted, which means it only contains the plugin name and the\nplugin library.\n\nFor example:\n\nSELECT * FROM mysql.plugin;\n\n+------+------------+\n| name | dl |\n+------+------------+\n| PBXT | libpbxt.so |\n+------+------------+\n\nInstalling a Plugin\n-------------------\n\nThere are three primary ways to install a plugin:\n\n* A plugin can be installed dynamically with an SQL statement.\n* A plugin can be installed with a mariadbd option, but it requires a server\nrestart.\n* A plugin can be installed with the mariadb-plugin utility, while the server\nis completely offline.\n\nWhen you are installing a plugin, you also have to ensure that:\n\n* The server\'s plugin directory is properly configured, and the plugin\'s\nlibrary is in the plugin directory.\n* The server\'s minimum plugin maturity is properly configured, and the plugin\nis mature enough to be installed.\n\nInstalling a Plugin Dynamically\n-------------------------------\n\nA plugin can be installed dynamically by executing either the INSTALL SONAME\nor the INSTALL PLUGIN statement.\n\nIf a plugin is installed with one of these statements, then a record will be\nadded to the mysql.plugins table for the plugin. This means that the plugin\nwill automatically be loaded every time the server restarts, unless\nspecifically uninstalled or deactivated.\n\nInstalling a Plugin with INSTALL SONAME\n---------------------------------------\n\nYou can install a plugin dynamically by executing the INSTALL SONAME\nstatement. INSTALL SONAME installs all plugins from the given plugin library.\nThis could be required for some plugin libraries.\n\nFor example, to install all plugins in the server_audit plugin library (which\nis currently only the server_audit audit plugin), you could execute the\nfollowing:\n\nINSTALL SONAME \'server_audit\';\n\nInstalling a Plugin with INSTALL PLUGIN\n---------------------------------------\n\nYou can install a plugin dynamically by executing the INSTALL PLUGIN\nstatement. INSTALL PLUGIN installs a single plugin from the given plugin\nlibrary.\n\nFor example, to install the server_audit audit plugin from the server_audit\nplugin library, you could execute the following:\n\nINSTALL PLUGIN server_audit SONAME \'server_audit\';\n\nInstalling a Plugin with Plugin Load Options\n--------------------------------------------\n\nA plugin can be installed with a mariadbd option by providing either the\n--plugin-load-add or the --plugin-load option.\n\nIf a plugin is installed with one of these options, then a record will not be\nadded to the mysql.plugins table for the plugin. This means that if the server\nis restarted without the same option set, then the plugin will not\nautomatically be loaded.\n\nInstalling a Plugin with --plugin-load-add\n------------------------------------------\n\nYou can install a plugin with the --plugin-load-add option by specifying the\noption as a command-line argument to mariadbd or by specifying the option in a\nrelevant server option group in an option file.\n\nThe --plugin-load-add option uses the following format:\n\n* Plugins can be specified in the format name=library, where name is the\nplugin name and library is the plugin library. This format installs a single\nplugin from the given plugin library.\n* Plugins can also be specified in the format library, where library is the\nplugin library. This format installs all plugins from the given plugin library.\n* Multiple plugins can be specified by separating them with semicolons.\n\nFor example, to install all plugins in the server_audit plugin library (which\nis currently only the server_audit audit plugin) and also the ed25519\nauthentication plugin from the auth_ed25519 plugin library, you could set the\noption to the following values on the command-line:\n\n$ mariadbd --user=mysql --plugin-load-add=\'server_audit\'\n--plugin-load-add=\'ed25519=auth_ed25519\'\n\nYou could also set the option to the same values in an option file:\n\n[mariadb]\n...\nplugin_load_add = server_audit\nplugin_load_add = ed25519=auth_ed25519\n\nSpecial care must be taken when specifying both the --plugin-load option and\nthe --plugin-load-add option together. The --plugin-load option resets the\nplugin load list, and this can cause unexpected problems if you are not aware.\nThe --plugin-load-add option does not reset the plugin load list, so it is\nmuch safer to use. See Specifying Multiple Plugin Load Options for more\ninformation.\n\nInstalling a Plugin with --plugin-load\n--------------------------------------\n\nYou can install a plugin with the --plugin-load option by specifying the\noption as a command-line argument to mariadbd or by specifying the option in a\nrelevant server option group in an option file.\n\nThe --plugin-load option uses the following format:\n\n* Plugins can be specified in the format name=library, where name is the\nplugin name and library is the plugin library. This format installs a single\nplugin from the given plugin library.\n* Plugins can also be specified in the format library, where library is the\nplugin library. This format installs all plugins from the given plugin library.\n* Multiple plugins can be specified by separating them with semicolons.\n\nFor example, to install all plugins in the server_audit plugin library (which\nis currently only the server_audit audit plugin) and also the ed25519\nauthentication plugin from the auth_ed25519 plugin library, you could set the\noption to the following values on the command-line:\n\n$ mariadbd --user=mysql --plugin-load=\'server_audit;ed25519=auth_ed25519\'\n\nYou could also set the option to the same values in an option file:\n\n[mariadb]\n...\nplugin_load = server_audit;ed25519=auth_ed25519\n\nSpecial care must be taken when specifying the --plugin-load option multiple\ntimes, or when specifying both the --plugin-load option and the\n--plugin-load-add option together. The --plugin-load option resets the plugin\nload list, and this can cause unexpected problems if you are not aware. The\n--plugin-load-add option does not reset the plugin load list, so it is much\nsafer to use. See Specifying Multiple Plugin Load Options for more information.\n\nSpecifying Multiple Plugin Load Options\n---------------------------------------\n\nSpecial care must be taken when specifying the --plugin-load option multiple\ntimes, or when specifying both the --plugin-load option and the\n--plugin-load-add option. The --plugin-load option resets the plugin load\nlist, and this can cause unexpected problems if you are not aware. The\n--plugin-load-add option does not reset the plugin load list, so it is much\nsafer to use.\n\nThis can have the following consequences:\n\n* If the --plugin-load option is specified multiple times, then only the last\ninstance will have any effect. For example, in the following case, the first\ninstance of the option is reset:\n\n[mariadb]\n...\nplugin_load = server_audit\nplugin_load = ed25519=auth_ed25519\n\n* If the --plugin-load option is specified after the --plugin-load-add option,\nthen it will also reset the changes made by that option. For example, in the\nfollowing case, the --plugin-load-add option does not do anything, because the\nsubsequent --plugin-load option resets the plugin load list:\n\n[mariadb]\n...\nplugin_load_add = server_audit\nplugin_load = ed25519=auth_ed25519\n\n* In contrast, if the --plugin-load option is specified before the\n--plugin-load-add option, then it will work fine, because the\n--plugin-load-add option does not reset the plugin load list. For example, in\nthe following case, both plugins are properly loaded:\n\n[mariadb]\n...\nplugin_load = server_audit\nplugin_load_add = ed25519=auth_ed25519\n\nInstalling a Plugin with mariadb-plugin\n---------------------------------------\n\nA plugin can be installed with the mariadb-plugin utility if the server is\ncompletely offline.\n\nThe syntax is:\n\nmariadb-plugin [options] ENABLE|DISABLE\n\nFor example, to install the server_audit audit plugin, you could execute the\nfollowing:\n\nmariadb-plugin server_audit ENABLE\n\nIf a plugin is installed with this utility, then a record will be added to the\nmysql.plugins table for the plugin. This means that the plugin will\nautomatically be loaded every time the server restarts, unless specifically\nuninstalled or deactivated.\n\nConfiguring the Plugin Directory\n--------------------------------\n\nWhen a plugin is being installed, the server looks for the plugin\'s library in\nthe server\'s plugin directory. This directory is configured by the plugin_dir\nsystem variable. This can be specified as a command-line argument to mariadbd\nor it can be specified in a relevant server option group in an option file.\nFor example:\n\n[mariadb]\n...\nplugin_dir = /usr/lib64/mysql/plugin\n\nConfiguring the Minimum Plugin Maturity\n---------------------------------------\n\nWhen a plugin is being installed, the server compares the plugin\'s maturity\nlevel against the server\'s minimum allowed plugin maturity. This can help\nprevent users from using unstable plugins on production servers. This minimum\nplugin maturity is configured by the plugin_maturity system variable. This can\nbe specified as a command-line argument to mariadbd or it can be specified in\na relevant server option group in an option file. For example:\n\n[mariadb]\n...\nplugin_maturity = stable\n\nConfiguring Plugin Activation at Server Startup\n-----------------------------------------------\n\nA plugin will be loaded by default when the server starts if:\n\n* The plugin was installed with the INSTALL SONAME statement.\n* The plugin was installed with the INSTALL PLUGIN statement.\n* The plugin was installed with the mariadb-plugin utility.\n* The server is configured to load the plugin with the --plugin-load-add\noption.\n* The server is configured to load the plugin with the --plugin-load option.\n\nThis behavior can be changed with special options that take the form\n--plugin-name. For example, for the server_audit audit plugin, the special\noption is called --server-audit.\n\nThe possible values for these special options are:\n\n+---------------------------------------+------------------------------------+\n| Option Value | Description |\n+---------------------------------------+------------------------------------+\n| OFF | Disables the plugin without |\n| | removing it from the |\n| | mysql.plugins table. |\n+---------------------------------------+------------------------------------+\n| ON | Enables the plugin. If the plugin |\n| | cannot be initialized, then the |\n| | server will still continue |\n| | starting up, but the plugin will |\n| | be disabled. |\n+---------------------------------------+------------------------------------+','','https://mariadb.com/docs/server/reference/plugins/plugin-overview'); +update help_topic set description = CONCAT(description, '\n| FORCE | Enables the plugin. If the plugin |\n| | cannot be initialized, then the |\n| | server will fail to start with an |\n| | error. |\n+---------------------------------------+------------------------------------+\n| FORCE_PLUS_PERMANENT | Enables the plugin. If the plugin |\n| | cannot be initialized, then the |\n| | server will fail to start with an |\n| | error. In addition, the plugin |\n| | cannot be uninstalled with |\n| | UNINSTALL SONAME or UNINSTALL |\n| | PLUGIN while the server is |\n| | running. |\n+---------------------------------------+------------------------------------+\n\nA plugin\'s status can be found by looking at the PLUGIN_STATUS column of the\ninformation_schema.PLUGINS table.\n\nUninstalling Plugins\n--------------------\n\nPlugins that are found in the mysql.plugin table, that is those that were\ninstalled with INSTALL SONAME, INSTALL PLUGIN or mariadb-plugin can be\nuninstalled in one of two ways:\n\n* The UNINSTALL SONAME or the UNINSTALL PLUGIN statement while the server is\nrunning\n* With mariadb-plugin while the server is offline.\n\nPlugins that were enabled as a --plugin-load option do not need to be\nuninstalled. If --plugin-load is omitted the next time the server starts, or\nthe plugin is not listed as one of the --plugin-load entries, the plugin will\nnot be loaded.\n\nUNINSTALL PLUGIN uninstalls a single installed plugin, while UNINSTALL SONAME\nuninstalls all plugins belonging to a given library.\n\nURL: https://mariadb.com/docs/server/reference/plugins/plugin-overview') WHERE help_topic_id = 79; +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (80,6,'MBR Definition','Description\n-----------\n\nThe MBR (Minimum Bounding Rectangle), or Envelope is the bounding geometry,\nformed by the minimum and maximum (X,Y) coordinates:\n\nExamples\n--------\n\n((MINX MINY, MAXX MINY, MAXX MAXY, MINX MAXY, MINX MINY))\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/mbr-minimum-bounding-rectangle/mbr-definition','','https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/mbr-minimum-bounding-rectangle/mbr-definition'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (81,6,'MBRContains','Syntax\n------\n\nMBRContains(g1,g2)\n\nDescription\n-----------\n\nReturns 1 or 0 to indicate whether the Minimum Bounding Rectangle of g1\ncontains the Minimum Bounding Rectangle of g2. This tests the opposite\nrelationship as MBRWithin().\n\nExamples\n--------\n\nSET @g1 = GeomFromText(\'Polygon((0 0,0 3,3 3,3 0,0 0))\');\n\nSET @g2 = GeomFromText(\'Point(1 1)\');\n\nSELECT MBRContains(@g1,@g2), MBRContains(@g2,@g1);\n+----------------------+----------------------+\n| MBRContains(@g1,@g2) | MBRContains(@g2,@g1) |\n+----------------------+----------------------+\n| 1 | 0 |\n+----------------------+----------------------+\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/mbr-minimum-bounding-rectangle/mbrcontains','','https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/mbr-minimum-bounding-rectangle/mbrcontains'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (82,6,'MBRDisjoint','Syntax\n------\n\nMBRDisjoint(g1,g2)\n\nDescription\n-----------\n\nReturns 1 or 0 to indicate whether the Minimum Bounding Rectangles of the two\ngeometries g1 and g2 are disjoint. Two geometries are disjoint if they do not\nintersect, that is touch or overlap.\n\nExamples\n--------\n\nSET @g1 = GeomFromText(\'Polygon((0 0,0 3,3 3,3 0,0 0))\');\nSET @g2 = GeomFromText(\'Polygon((4 4,4 7,7 7,7 4,4 4))\');\nSELECTmbrdisjoint(@g1,@g2);\n+----------------------+\n| mbrdisjoint(@g1,@g2) |\n+----------------------+\n| 1 |\n+----------------------+\n\nSET @g1 = GeomFromText(\'Polygon((0 0,0 3,3 3,3 0,0 0))\');\nSET @g2 = GeomFromText(\'Polygon((3 3,3 6,6 6,6 3,3 3))\');\nSELECT mbrdisjoint(@g1,@g2);\n+----------------------+\n| mbrdisjoint(@g1,@g2) |\n+----------------------+\n| 0 |\n+----------------------+\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/mbr-minimum-bounding-rectangle/mbrdisjoint','','https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/mbr-minimum-bounding-rectangle/mbrdisjoint'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (83,6,'MBREqual','Syntax\n------\n\nMBREqual(g1,g2)\n\nDescription\n-----------\n\nReturns 1 or 0 to indicate whether the Minimum Bounding Rectangles of the two\ngeometries g1 and g2 are the same.\n\nExamples\n--------\n\nSET @g1=GEOMFROMTEXT(\'LINESTRING(0 0, 1 2)\');\nSET @g2=GEOMFROMTEXT(\'POLYGON((0 0, 0 2, 1 2, 1 0, 0 0))\');\nSELECT MbrEqual(@g1,@g2);\n+-------------------+\n| MbrEqual(@g1,@g2) |\n+-------------------+\n| 1 |\n+-------------------+\n\nSET @g1=GEOMFROMTEXT(\'LINESTRING(0 0, 1 3)\');\nSET @g2=GEOMFROMTEXT(\'POLYGON((0 0, 0 2, 1 4, 1 0, 0 0))\');\nSELECT MbrEqual(@g1,@g2);\n+-------------------+\n| MbrEqual(@g1,@g2) |\n+-------------------+\n| 0 |\n+-------------------+\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/mbr-minimum-bounding-rectangle/mbrequal','','https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/mbr-minimum-bounding-rectangle/mbrequal'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (84,6,'MBRIntersects','Syntax\n------\n\nMBRIntersects(g1,g2)\n\nDescription\n-----------\n\nReturns 1 or 0 to indicate whether the Minimum Bounding Rectangles of the two\ngeometries g1 and g2 intersect.\n\nExamples\n--------\n\nSET @g1 = GeomFromText(\'Polygon((0 0,0 3,3 3,3 0,0 0))\');\nSET @g2 = GeomFromText(\'Polygon((3 3,3 6,6 6,6 3,3 3))\');\nSELECT mbrintersects(@g1,@g2);\n+------------------------+\n| mbrintersects(@g1,@g2) |\n+------------------------+\n| 1 |\n+------------------------+\n\nSET @g1 = GeomFromText(\'Polygon((0 0,0 3,3 3,3 0,0 0))\');\nSET @g2 = GeomFromText(\'Polygon((4 4,4 7,7 7,7 4,4 4))\');\nSELECT mbrintersects(@g1,@g2);\n+------------------------+\n| mbrintersects(@g1,@g2) |\n+------------------------+\n| 0 |\n+------------------------+\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/mbr-minimum-bounding-rectangle/mbrintersects','','https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/mbr-minimum-bounding-rectangle/mbrintersects'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (85,6,'MBROverlaps','Syntax\n------\n\nMBROverlaps(g1,g2)\n\nDescription\n-----------\n\nReturns 1 or 0 to indicate whether the Minimum Bounding Rectangles of the two\ngeometries g1 and g2 overlap. The term spatially overlaps is used if two\ngeometries intersect and their intersection results in a geometry of the same\ndimension but not equal to either of the given geometries.\n\nExamples\n--------\n\nSET @g1 = GeomFromText(\'Polygon((0 0,0 3,3 3,3 0,0 0))\');\nSET @g2 = GeomFromText(\'Polygon((4 4,4 7,7 7,7 4,4 4))\');\nSELECT mbroverlaps(@g1,@g2);\n+----------------------+\n| mbroverlaps(@g1,@g2) |\n+----------------------+\n| 0 |\n+----------------------+\n\nSET @g1 = GeomFromText(\'Polygon((0 0,0 3,3 3,3 0,0 0))\');\nSET @g2 = GeomFromText(\'Polygon((3 3,3 6,6 6,6 3,3 3))\');\nSELECT mbroverlaps(@g1,@g2);\n+----------------------+\n| mbroverlaps(@g1,@g2) |\n+----------------------+\n| 0 |\n+----------------------+\n\nSET @g1 = GeomFromText(\'Polygon((0 0,0 4,4 4,4 0,0 0))\');\nSET @g2 = GeomFromText(\'Polygon((3 3,3 6,6 6,6 3,3 3))\');\nSELECT mbroverlaps(@g1,@g2);\n+----------------------+\n| mbroverlaps(@g1,@g2) |\n+----------------------+\n| 1 |\n+----------------------+\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/mbr-minimum-bounding-rectangle/mbroverlaps','','https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/mbr-minimum-bounding-rectangle/mbroverlaps'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (86,6,'MBRTouches','Syntax\n------\n\nMBRTouches(g1,g2)\n\nDescription\n-----------\n\nReturns 1 or 0 to indicate whether the Minimum Bounding Rectangles of the two\ngeometries g1 and g2 touch. Two geometries spatially touch if the interiors of\nthe geometries do not intersect, but the boundary of one of the geometries\nintersects either the boundary or the interior of the other.\n\nExamples\n--------\n\nSET @g1 = GeomFromText(\'Polygon((0 0,0 3,3 3,3 0,0 0))\');\nSET @g2 = GeomFromText(\'Polygon((4 4,4 7,7 7,7 4,4 4))\');\nSELECT mbrtouches(@g1,@g2);\n+---------------------+\n| mbrtouches(@g1,@g2) |\n+---------------------+\n| 0 |\n+---------------------+\n\nSET @g1 = GeomFromText(\'Polygon((0 0,0 3,3 3,3 0,0 0))\');\nSET @g2 = GeomFromText(\'Polygon((3 3,3 6,6 6,6 3,3 3))\');\nSELECT mbrtouches(@g1,@g2);\n+---------------------+\n| mbrtouches(@g1,@g2) |\n+---------------------+\n| 1 |\n+---------------------+\n\nSET @g1 = GeomFromText(\'Polygon((0 0,0 4,4 4,4 0,0 0))\');\nSET @g2 = GeomFromText(\'Polygon((3 3,3 6,6 6,6 3,3 3))\');\nSELECT mbrtouches(@g1,@g2);\n+---------------------+\n| mbrtouches(@g1,@g2) |\n+---------------------+\n| 0 |\n+---------------------+\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/mbr-minimum-bounding-rectangle/mbrtouches','','https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/mbr-minimum-bounding-rectangle/mbrtouches'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (87,6,'MBRWithin','Syntax\n------\n\nMBRWithin(g1,g2)\n\nDescription\n-----------\n\nReturns 1 or 0 to indicate whether the Minimum Bounding Rectangle of g1 is\nwithin the Minimum Bounding Rectangle of g2. This tests the opposite\nrelationship as MBRContains().\n\nExamples\n--------\n\nSET @g1 = GeomFromText(\'Polygon((0 0,0 3,3 3,3 0,0 0))\');\nSET @g2 = GeomFromText(\'Polygon((0 0,0 5,5 5,5 0,0 0))\');\nSELECT MBRWithin(@g1,@g2), MBRWithin(@g2,@g1);\n+--------------------+--------------------+\n| MBRWithin(@g1,@g2) | MBRWithin(@g2,@g1) |\n+--------------------+--------------------+\n| 1 | 0 |\n+--------------------+--------------------+\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/mbr-minimum-bounding-rectangle/mbrwithin','','https://mariadb.com/docs/server/reference/sql-statements/geometry-constructors/mbr-minimum-bounding-rectangle/mbrwithin'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (88,7,'CASE OPERATOR','Syntax\n------\n\nCASE value WHEN [compare_value] THEN result [WHEN [compare_value] THEN\nresult ...] [ELSE result] END\n\nCASE WHEN [condition] THEN result [WHEN [condition] THEN result ...]\n[ELSE result] END\n\nDescription\n-----------\n\nThe first version returns the result where value=compare_value. The second\nversion returns the result for the first condition that is true. If there was\nno matching result value, the result after ELSE is returned, or NULL if there\nis no ELSE part.\n\nThere is also a CASE statement, which differs from the CASE operator described\nhere.\n\nExamples\n--------\n\nSELECT CASE 1 WHEN 1 THEN \'one\' WHEN 2 THEN \'two\' ELSE \'more\' END;\n+------------------------------------------------------------+\n| CASE 1 WHEN 1 THEN \'one\' WHEN 2 THEN \'two\' ELSE \'more\' END |\n+------------------------------------------------------------+\n| one |\n+------------------------------------------------------------+\n\nSELECT CASE WHEN 1>0 THEN \'true\' ELSE \'false\' END;\n+--------------------------------------------+\n| CASE WHEN 1>0 THEN \'true\' ELSE \'false\' END |\n+--------------------------------------------+\n| true |\n+--------------------------------------------+\n\nSELECT CASE BINARY \'B\' WHEN \'a\' THEN 1 WHEN \'b\' THEN 2 END;\n+-----------------------------------------------------+\n| CASE BINARY \'B\' WHEN \'a\' THEN 1 WHEN \'b\' THEN 2 END |\n+-----------------------------------------------------+\n| NULL |\n+-----------------------------------------------------+\n\nURL: https://mariadb.com/docs/server/reference/sql-functions/control-flow-functions/case-operator','','https://mariadb.com/docs/server/reference/sql-functions/control-flow-functions/case-operator'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (89,7,'IF Function','Syntax\n------\n\nIF(expr1,expr2,expr3)\n\nDescription\n-----------\n\nIf expr1 is TRUE (expr1 <> 0 and expr1 <> NULL) then IF() returns expr2;\notherwise it returns expr3. IF() returns a numeric or string value, depending\non the context in which it is used.\n\nNote: There is also an IF statement which differs from the IF() function\ndescribed here.\n\nExamples\n--------\n\nSELECT IF(1>2,2,3);\n+-------------+\n| IF(1>2,2,3) |\n+-------------+\n| 3 |\n+-------------+\n\nSELECT IF(1<2,\'yes\',\'no\');\n+--------------------+\n| IF(1<2,\'yes\',\'no\') |\n+--------------------+\n| yes |\n+--------------------+\n\nSELECT IF(STRCMP(\'test\',\'test1\'),\'no\',\'yes\');\n+---------------------------------------+\n| IF(STRCMP(\'test\',\'test1\'),\'no\',\'yes\') |\n+---------------------------------------+\n| no |\n+---------------------------------------+\n\nURL: https://mariadb.com/docs/server/reference/sql-functions/control-flow-functions/if-function','','https://mariadb.com/docs/server/reference/sql-functions/control-flow-functions/if-function'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (90,7,'IFNULL','Syntax\n------\n\nIFNULL(expr1,expr2)\nNVL(expr1,expr2)\n\nDescription\n-----------\n\nIf expr1 is not NULL, IFNULL() returns expr1; otherwise it returns expr2.\nIFNULL() returns a numeric or string value, depending on the context in which\nit is used.\n\nFrom MariaDB 10.3, NVL() is an alias for IFNULL().\n\nExamples\n--------\n\nSELECT IFNULL(1,0); \n+-------------+\n| IFNULL(1,0) |\n+-------------+\n| 1 |\n+-------------+\n\nSELECT IFNULL(NULL,10);\n+-----------------+\n| IFNULL(NULL,10) |\n+-----------------+\n| 10 |\n+-----------------+\n\nSELECT IFNULL(1/0,10);\n+----------------+\n| IFNULL(1/0,10) |\n+----------------+\n| 10.0000 |\n+----------------+\n\nSELECT IFNULL(1/0,\'yes\');\n+-------------------+\n| IFNULL(1/0,\'yes\') |\n+-------------------+\n| yes |\n+-------------------+\n\nURL: https://mariadb.com/docs/server/reference/sql-functions/control-flow-functions/ifnull','','https://mariadb.com/docs/server/reference/sql-functions/control-flow-functions/ifnull'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (91,7,'NULLIF','Syntax\n------\n\nNULLIF(expr1,expr2)\n\nDescription\n-----------\n\nReturns NULL if expr1 = expr2 is true, otherwise returns expr1. This is the\nsame as CASE WHEN expr1 = expr2 THEN NULL ELSE expr1 END.\n\nExamples\n--------\n\nSELECT NULLIF(1,1);\n+-------------+\n| NULLIF(1,1) |\n+-------------+\n| NULL |\n+-------------+\n\nSELECT NULLIF(1,2);\n+-------------+\n| NULLIF(1,2) |\n+-------------+\n| 1 |\n+-------------+\n\nURL: https://mariadb.com/docs/server/reference/sql-functions/control-flow-functions/nullif','','https://mariadb.com/docs/server/reference/sql-functions/control-flow-functions/nullif'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (92,7,'NVL','MariaDB starting with 10.3\n--------------------------\nFrom MariaDB 10.3, NVL is a synonym for IFNULL.\n\nURL: https://mariadb.com/docs/server/reference/sql-functions/control-flow-functions/nvl','','https://mariadb.com/docs/server/reference/sql-functions/control-flow-functions/nvl'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (93,7,'NVL2','MariaDB starting with 10.3\n--------------------------\nThe NLV2 function was introduced in MariaDB 10.3.0.\n\nSyntax\n------\n\nNVL2(expr1,expr2,expr3)\n\nDescription\n-----------\n\nThe NVL2 function returns a value based on whether a specified expression is\nNULL or not. If expr1 is not NULL, then NVL2 returns expr2. If expr1 is NULL,\nthen NVL2 returns expr3.\n\nExamples\n--------\n\nSELECT NVL2(NULL,1,2);\n+----------------+\n| NVL2(NULL,1,2) |\n+----------------+\n| 2 |\n+----------------+\n\nSELECT NVL2(\'x\',1,2);\n+---------------+\n| NVL2(\'x\',1,2) |\n+---------------+\n| 1 |\n+---------------+\n\nURL: https://mariadb.com/docs/server/reference/sql-functions/control-flow-functions/nvl2','','https://mariadb.com/docs/server/reference/sql-functions/control-flow-functions/nvl2'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (94,8,'START TRANSACTION','Syntax\n------\n\nSTART TRANSACTION [transaction_property [, transaction_property] ...] | BEGIN\n[WORK]\nCOMMIT [WORK] [AND [NO] CHAIN] [[NO] RELEASE]\nROLLBACK [WORK] [AND [NO] CHAIN] [[NO] RELEASE]\nSET autocommit = {0 | 1}\n\ntransaction_property:\n WITH CONSISTENT SNAPSHOT\n | READ WRITE\n | READ ONLY\n\nDescription\n-----------\n\nThe START TRANSACTION or BEGIN statement begins a new transaction. COMMIT\ncommits the current transaction, making its changes permanent. ROLLBACK rolls\nback the current transaction, canceling its changes. The SET autocommit\nstatement disables or enables the default autocommit mode for the current\nsession.\n\nSTART TRANSACTION and SET autocommit = 1 implicitly commit the current\ntransaction, if any.\n\nThe optional WORK keyword is supported for COMMIT and ROLLBACK, as are the\nCHAIN and RELEASE clauses. CHAIN and RELEASE can be used for additional\ncontrol over transaction completion. The value of the completion_type system\nvariable determines the default completion behavior.\n\nThe AND CHAIN clause causes a new transaction to begin as soon as the current\none ends, and the new transaction has the same isolation level as the\njust-terminated transaction. The RELEASE clause causes the server to\ndisconnect the current client session after terminating the current\ntransaction. Including the NO keyword suppresses CHAIN or RELEASE completion,\nwhich can be useful if the completion_type system variable is set to cause\nchaining or release completion by default.\n\nAccess Mode\n-----------\n\nThe access mode specifies whether the transaction is allowed to write data or\nnot. By default, transactions are in READ WRITE mode (see the tx_read_only\nsystem variable). READ ONLY mode allows the storage engine to apply\noptimizations that cannot be used for transactions which write data. Note that\nunlike the global read_only mode, READ_ONLY ADMIN (and SUPER before MariaDB\n10.11.0) privilege doesn\'t allow writes and DDL statements on temporary tables\nare not allowed either.\n\nIt is not permitted to specify both READ WRITE and READ ONLY in the same\nstatement.\n\nREAD WRITE and READ ONLY can also be specified in the SET TRANSACTION\nstatement, in which case the specified mode is valid for all sessions, or for\nall subsequent transaction used by the current session.\n\nautocommit\n----------\n\nBy default, MariaDB runs with autocommit mode enabled. This means that as soon\nas you execute a statement that updates (modifies) a table, MariaDB stores the\nupdate on disk to make it permanent. To disable autocommit mode, use the\nfollowing statement:\n\nSET autocommit=0;\n\nAfter disabling autocommit mode by setting the autocommit variable to zero,\nchanges to transaction-safe tables (such as those for InnoDB or NDBCLUSTER)\nare not made permanent immediately. You must use COMMIT to store your changes\nto disk or ROLLBACK to ignore the changes.\n\nTo disable autocommit mode for a single series of statements, use the START\nTRANSACTION statement.\n\nDDL Statements\n--------------\n\nDDL statements (CREATE, ALTER, DROP) and administrative statements (FLUSH,\nRESET, OPTIMIZE, ANALYZE, CHECK, REPAIR, CACHE INDEX), transaction management\nstatements (BEGIN, START TRANSACTION) and LOAD DATA INFILE, cause an implicit\nCOMMIT and start a new transaction. An exception to this rule are the DDL that\noperate on temporary tables: you can CREATE, ALTER and DROP them without\ncausing any COMMIT, but those actions cannot be rolled back. This means that\nif you call ROLLBACK, the temporary tables you created in the transaction will\nremain, while the rest of the transaction will be rolled back.\n\nTransactions cannot be used in Stored Functions or Triggers. In Stored\nProcedures and Events BEGIN is not allowed, so you should use START\nTRANSACTION instead.\n\nA transaction acquires a metadata lock on every table it accesses to prevent\nother connections from altering their structure. The lock is released at the\nend of the transaction. This happens even with non-transactional storage\nengines (like MEMORY or CONNECT), so it makes sense to use transactions with\nnon-transactional tables.\n\nin_transaction\n--------------\n\nThe in_transaction system variable is a session-only, read-only variable that\nreturns 1 inside a transaction, and 0 if not in a transaction.\n\nWITH CONSISTENT SNAPSHOT\n------------------------\n\nThe WITH CONSISTENT SNAPSHOT option starts a consistent read for storage\nengines such as InnoDB that can do so, the same as if a START TRANSACTION\nfollowed by a SELECT from any InnoDB table was issued.\n\nSee Enhancements for START TRANSACTION WITH CONSISTENT SNAPSHOT.\n\nExamples\n--------\n\nSTART TRANSACTION;\nSELECT @A:=SUM(salary) FROM table1 WHERE type=1;\nUPDATE table2 SET summary=@A WHERE type=1;\nCOMMIT;\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/transactions/start-transaction','','https://mariadb.com/docs/server/reference/sql-statements/transactions/start-transaction'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (95,8,'COMMIT','The COMMIT statement ends a transaction, saving any changes to the data so\nthat they become visible to subsequent transactions. Also, unlocks metadata\nchanged by current transaction. If autocommit is set to 1, an implicit commit\nis performed after each statement. Otherwise, all transactions which don\'t end\nwith an explicit COMMIT are implicitly rollbacked and the changes are lost.\nThe ROLLBACK statement can be used to do this explicitly.\n\nThe required syntax for the COMMIT statement is as follows:\n\nCOMMIT [WORK] [AND [NO] CHAIN] [[NO] RELEASE]\n\nCOMMIT is the more important transaction terminator, as well as the more\ninteresting one. The basic form of the COMMIT statement is simply the keyword\nCOMMIT (the keyword WORK is simply noise and can be omitted without changing\nthe effect).\n\nThe optional AND CHAIN clause is a convenience for initiating a new\ntransaction as soon as the old transaction terminates. If AND CHAIN is\nspecified, then there is effectively nothing between the old and new\ntransactions, although they remain separate. The characteristics of the new\ntransaction will be the same as the characteristics of the old one — that is,\nthe new transaction will have the same access mode, isolation level and\ndiagnostics area size (we\'ll discuss all of these shortly) as the transaction\njust terminated.\n\nRELEASE tells the server to disconnect the client immediately after the\ncurrent transaction.\n\nThere are NO RELEASE and AND NO CHAIN options. By default, commits do not\nRELEASE or CHAIN, but it\'s possible to change this default behavior with the\ncompletion_type server system variable. In this case, the AND NO CHAIN and NO\nRELEASE options override the server default.\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/transactions/commit','','https://mariadb.com/docs/server/reference/sql-statements/transactions/commit'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (96,8,'ROLLBACK','The ROLLBACK statement rolls back (ends) a transaction, destroying any changes\nto SQL-data so that they never become visible to subsequent transactions. The\nrequired syntax for the ROLLBACK statement is as follows.\n\nROLLBACK [ WORK ] [ AND [ NO ] CHAIN ] \n[ TO [ SAVEPOINT ] { | } ]\n\nThe ROLLBACK statement will either end a transaction, destroying all data\nchanges that happened during any of the transaction, or it will just destroy\nany data changes that happened since you established a savepoint. The basic\nform of the ROLLBACK statement is just the keyword ROLLBACK (the keyword WORK\nis simply noise and can be omitted without changing the effect).\n\nThe optional AND CHAIN clause is a convenience for initiating a new\ntransaction as soon as the old transaction terminates. If AND CHAIN is\nspecified, then there is effectively nothing between the old and new\ntransactions, although they remain separate. The characteristics of the new\ntransaction will be the same as the characteristics of the old one — that is,\nthe new transaction will have the same access mode, isolation level and\ndiagnostics area size (we\'ll discuss all of these shortly) as the transaction\njust terminated. The AND NO CHAIN option just tells your DBMS to end the\ntransaction — that is, these four SQL statements are equivalent:\n\nROLLBACK; \nROLLBACK WORK; \nROLLBACK AND NO CHAIN; \nROLLBACK WORK AND NO CHAIN;\n\nAll of them end a transaction without saving any transaction characteristics.\nThe only other options, the equivalent statements:\n\nROLLBACK AND CHAIN;\nROLLBACK WORK AND CHAIN;\n\nboth tell your DBMS to end a transaction, but to save that transaction\'s\ncharacteristics for the next transaction.\n\nROLLBACK is much simpler than COMMIT: it may involve no more than a few\ndeletions (of Cursors, locks, prepared SQL statements and log-file entries).\nIt\'s usually assumed that ROLLBACK can\'t fail, although such a thing is\nconceivable (for example, an encompassing transaction might reject an attempt\nto ROLLBACK because it\'s lining up for a COMMIT).\n\nROLLBACK cancels all effects of a transaction. It does not cancel effects on\nobjects outside the DBMS\'s control (for example the values in host program\nvariables or the settings made by some SQL/CLI function calls). But in\ngeneral, it is a convenient statement for those situations when you say \"oops,\nthis isn\'t working\" or when you simply don\'t care whether your temporary work\nbecomes permanent or not.\n\nHere is a moot question. If all you\'ve been doing is SELECTs, so that there\nhave been no data changes, should you end the transaction with ROLLBACK or\nCOMMIT? It shouldn\'t really matter because both ROLLBACK and COMMIT do the\nsame transaction-terminating job. However, the popular conception is that\nROLLBACK implies failure, so after a successful series of SELECT statements\nthe convention is to end the transaction with COMMIT rather than ROLLBACK.\n\nMariaDB (and most other DBMSs) supports rollback of SQL-data change\nstatements, but not of SQL-Schema statements. This means that if you use any\nof CREATE, ALTER, DROP, GRANT, REVOKE, you are implicitly committing at\nexecution time.\n\nINSERT INTO Table_2 VALUES(5); \nDROP TABLE Table_3 CASCADE; \nROLLBACK;\n\nThe result will be that both the INSERT and the DROP will go through as\nseparate transactions so the ROLLBACK will have no effect.\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/transactions/rollback','','https://mariadb.com/docs/server/reference/sql-statements/transactions/rollback'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (97,8,'SET TRANSACTION','Syntax\n------\n\nSET [GLOBAL | SESSION] TRANSACTION\n transaction_property [, transaction_property] ...\n\ntransaction_property:\n ISOLATION LEVEL level\n | READ WRITE\n | READ ONLY\n\nlevel:\n REPEATABLE READ\n | READ COMMITTED\n | READ UNCOMMITTED\n | SERIALIZABLE\n\nDescription\n-----------\n\nThis statement sets the transaction isolation level or the transaction access\nmode globally, for the current session, or for the next transaction:\n\n* With the GLOBAL keyword, the statement sets the default\n transaction level globally for all subsequent sessions. Existing sessions are\n unaffected.\n* With the SESSION keyword, the statement sets the default\n transaction level for all subsequent transactions performed within the\n current session.\n* Without any SESSION or GLOBAL keyword,\n the statement sets the isolation level for only the next (not started)\ntransaction\n performed within the current session. After that it reverts to using the\nsession value.\n\nA change to the global default isolation level requires the SUPER privilege.\nAny session is free to change its session isolation level (even in the middle\nof a transaction), or the isolation level for its next transaction.\n\nIsolation Level\n---------------\n\nTo set the global default isolation level at server startup, use the\n--transaction-isolation=level option on the command line or in an option file.\nValues of level for this option use dashes rather than spaces, so the\nallowable values are READ_UNCOMMITTED, READ-COMMITTED, REPEATABLE-READ, or\nSERIALIZABLE. For example, to set the default isolation level to REPEATABLE\nREAD, use these lines in the [mariadb] section of an option file:\n\n[mariadb]\ntransaction-isolation = REPEATABLE-READ\nTo determine the global and session transaction isolation levels at runtime,\ncheck the value of the tx_isolation system variable (note that the variable\nhas been renamed transaction_isolation from MariaDB 11.1.1, to match the\noption, and the old name deprecated).\n\nSELECT @@GLOBAL.tx_isolation, @@tx_isolation;\n\nFrom MariaDB 11.1.1:\n\nSELECT @@GLOBAL.transaction_isolation, @@transaction_isolation;\n\nInnoDB supports each of the translation isolation levels described here using\ndifferent locking strategies. The default level is REPEATABLE READ. For\nadditional information about InnoDB record-level locks and how it uses them to\nexecute various types of statements, see InnoDB Lock Modes, and\nhttp://dev.mysql.com/doc/refman/en/innodb-locks-set.html.\n\nIsolation Levels\n----------------\n\nThe following sections describe how MariaDB supports the different transaction\nlevels.\n\nREAD UNCOMMITTED\n----------------\n\nSELECT statements are performed in a non-locking fashion, but a possible\nearlier version of a row might be used. Thus, using this isolation level, such\nreads are not consistent. This is also called a \"dirty read.\" Otherwise, this\nisolation level works like READ COMMITTED.\n\nREAD COMMITTED\n--------------\n\nA somewhat Oracle-like isolation level with respect to consistent\n(non-locking) reads: Each consistent read, even within the same transaction,\nsets and reads its own fresh snapshot. See\nhttp://dev.mysql.com/doc/refman/en/innodb-consistent-read.html.\n\nFor locking reads (SELECT with FOR UPDATE or LOCK IN SHARE MODE), InnoDB locks\nonly index records, not the gaps before them, and thus allows the free\ninsertion of new records next to locked records. For UPDATE and DELETE\nstatements, locking depends on whether the statement uses a unique index with\na unique search condition (such as WHERE id = 100), or a range-type search\ncondition (such as WHERE id > 100). For a unique index with a unique search\ncondition, InnoDB locks only the index record found, not the gap before it.\nFor range-type searches, InnoDB locks the index range scanned, using gap locks\nor next-key (gap plus index-record) locks to block insertions by other\nsessions into the gaps covered by the range. This is necessary because\n\"phantom rows\" must be blocked for MariaDB replication and recovery to work.\n\nNote: If the READ COMMITTED isolation level is used or the\ninnodb_locks_unsafe_for_binlog system variable is enabled, there is no InnoDB\ngap locking except for foreign-key constraint checking and duplicate-key\nchecking. Also, record locks for non-matching rows are released after MariaDB\nhas evaluated the WHERE condition.If you use READ COMMITTED or enable\ninnodb_locks_unsafe_for_binlog, you must use row-based binary logging.\n\nREPEATABLE READ\n---------------\n\nThis is the default isolation level for InnoDB. For consistent reads, there is\nan important difference from the READ COMMITTED isolation level: All\nconsistent reads within the same transaction read the snapshot established by\nthe first read. This convention means that if you issue several plain\n(non-locking) SELECT statements within the same transaction, these SELECT\nstatements are consistent also with respect to each other. See\nhttp://dev.mysql.com/doc/refman/en/innodb-consistent-read.html.\n\nFor locking reads (SELECT with FOR UPDATE or LOCK IN SHARE MODE), UPDATE, and\nDELETE statements, locking depends on whether the statement uses a unique\nindex with a unique search condition, or a range-type search condition. For a\nunique index with a unique search condition, InnoDB locks only the index\nrecord found, not the gap before it. For other search conditions, InnoDB locks\nthe index range scanned, using gap locks or next-key (gap plus index-record)\nlocks to block insertions by other sessions into the gaps covered by the range.\n\nThis is the minimum isolation level for non-distributed XA transactions.\n\nSERIALIZABLE\n------------\n\nThis level is like REPEATABLE READ, but InnoDB implicitly converts all plain\nSELECT statements to SELECT ... LOCK IN SHARE MODE if autocommit is disabled.\nIf autocommit is enabled, the SELECT is its own transaction. It therefore is\nknown to be read only and can be serialized if performed as a consistent\n(non-locking) read and need not block for other transactions. (This means that\nto force a plain SELECT to block if other transactions have modified the\nselected rows, you should disable autocommit.)\n\nDistributed XA transactions should always use this isolation level.\n\nAccess Mode\n-----------\n\nThe access mode specifies whether the transaction is allowed to write data or\nnot. By default, transactions are in READ WRITE mode (see the tx_read_only\nsystem variable). READ ONLY mode allows the storage engine to apply\noptimizations that cannot be used for transactions which write data. Note that\nunlike the global read_only mode, READ_ONLY ADMIN (and SUPER before MariaDB\n10.11.0) privilege doesn\'t allow writes and DDL statements on temporary tables\nare not allowed either.\n\nIt is not permitted to specify both READ WRITE and READ ONLY in the same\nstatement.\n\nREAD WRITE and READ ONLY can also be specified in the START TRANSACTION\nstatement, in which case the specified mode is only valid for one transaction.\n\nExamples\n--------\n\nSET GLOBAL TRANSACTION ISOLATION LEVEL SERIALIZABLE;\n\nAttempting to set the isolation level within an existing transaction without\nspecifying GLOBAL or SESSION.\n\nSTART TRANSACTION;\n\nSET TRANSACTION ISOLATION LEVEL SERIALIZABLE;\nERROR 1568 (25001): Transaction characteristics can\'t be changed while a\ntransaction is in progress\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/administrative-sql-statements/set-commands/set-transaction','','https://mariadb.com/docs/server/reference/sql-statements/administrative-sql-statements/set-commands/set-transaction'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (98,8,'LOCK TABLES','Syntax\n------\n\nLOCK TABLE[S]\n tbl_name [[AS] alias] lock_type\n [, tbl_name [[AS] alias] lock_type] ...\n [WAIT n|NOWAIT]\n\nlock_type:\n READ [LOCAL]\n | [LOW_PRIORITY] WRITE\n | WRITE CONCURRENT\n\nUNLOCK TABLES\n\nDescription\n-----------\n\nThe lock_type can be one of:\n\n+---------------------------+------------------------------------------------+\n| Option | Description |\n+---------------------------+------------------------------------------------+\n| READ | Read lock, no writes allowed |\n+---------------------------+------------------------------------------------+\n| READ LOCAL | Read lock, but allow concurrent inserts |\n+---------------------------+------------------------------------------------+\n| WRITE | Exclusive write lock. No other connections |\n| | can read or write to this table |\n+---------------------------+------------------------------------------------+\n| LOW_PRIORITY WRITE | Exclusive write lock, but allow new read |\n| | locks on the table until we get the write |\n| | lock. |\n+---------------------------+------------------------------------------------+\n| WRITE CONCURRENT | Exclusive write lock, but allow READ LOCAL |\n| | locks to the table. |\n+---------------------------+------------------------------------------------+\n\nMariaDB enables client sessions to acquire table locks explicitly for the\npurpose of cooperating with other sessions for access to tables, or to prevent\nother sessions from modifying tables during periods when a session requires\nexclusive access to them. A session can acquire or release locks only for\nitself. One session cannot acquire locks for another session or release locks\nheld by another session.\n\nLocks may be used to emulate transactions or to get more speed when updating\ntables.\n\nLOCK TABLES explicitly acquires table locks for the current client session.\nTable locks can be acquired for base tables or views. To use LOCK TABLES, you\nmust have the LOCK TABLES privilege, and the SELECT privilege for each object\nto be locked. See GRANT\n\nFor view locking, LOCK TABLES adds all base tables used in the view to the set\nof tables to be locked and locks them automatically. If you lock a table\nexplicitly with LOCK TABLES, any tables used in triggers are also locked\nimplicitly, as described in Triggers and Implicit Locks.\n\nUNLOCK TABLES explicitly releases any table locks held by the current session.\n\nMariaDB starting with 10.3.0\n----------------------------\n\nWAIT/NOWAIT\n-----------\n\nSet the lock wait timeout. See WAIT and NOWAIT.\n\nLimitations\n-----------\n\n* LOCK TABLES doesn\'t work when using Galera cluster. You may experience\ncrashes or locks when used with Galera.\n* LOCK TABLES works on XtraDB/InnoDB tables only if the innodb_table_locks\nsystem variable is set to 1 (the default) and autocommit is set to 0 (1 is\ndefault). Please note that no error message will be returned on LOCK TABLES\nwith innodb_table_locks = 0.\n* LOCK TABLES implicitly commits the active transaction, if any. Also,\nstarting a transaction always releases all table locks acquired with LOCK\nTABLES. This means that there is no way to have table locks and an active\ntransaction at the same time. The only exceptions are the transactions in\nautocommit mode. To preserve the data integrity between transactional and\nnon-transactional tables, the GET_LOCK() function can be used.\n* When using LOCK TABLES on a TEMPORARY table, it will always be locked with a\nWRITE lock.\n* While a connection holds an explicit read lock on a table, it cannot modify\nit. If you try, the following error will be produced:\n\nERROR 1099 (HY000): Table \'tab_name\' was locked with a READ lock and can\'t be\nupdated\n\n* While a connection holds an explicit lock on a table, it cannot access a\nnon-locked table. If you try, the following error will be produced:\n\nERROR 1100 (HY000): Table \'tab_name\' was not locked with LOCK TABLES\n\n* While a connection holds an explicit lock on a table, it cannot issue the\nfollowing: INSERT DELAYED, CREATE TABLE, CREATE TABLE ... LIKE, and DDL\nstatements involving stored programs and views (except for triggers). If you\ntry, the following error will be produced:\n\nERROR 1192 (HY000): Can\'t execute the given command because you have active\nlocked tables or an active transaction\n\n* LOCK TABLES can not be used in stored routines - if you try, the following\nerror will be produced on creation:\n\nERROR 1314 (0A000): LOCK is not allowed in stored procedures\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/transactions/lock-tables','','https://mariadb.com/docs/server/reference/sql-statements/transactions/lock-tables'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (99,8,'SAVEPOINT','Syntax\n------\n\nSAVEPOINT identifier\nROLLBACK [WORK] TO [SAVEPOINT] identifier\nRELEASE SAVEPOINT identifier\n\nDescription\n-----------\n\nInnoDB supports the SQL statements SAVEPOINT, ROLLBACK TO SAVEPOINT, RELEASE\nSAVEPOINT and the optional WORK keyword for ROLLBACK.\n\nEach savepoint must have a legal MariaDB identifier. A savepoint is a named\nsub-transaction.\n\nNormally ROLLBACK undoes the changes performed by the whole transaction. When\nused with the TO clause, it undoes the changes performed after the specified\nsavepoint, and erases all subsequent savepoints. However, all locks that have\nbeen acquired after the save point will survive. RELEASE SAVEPOINT does not\nrollback or commit any changes, but removes the specified savepoint.\n\nWhen the execution of a trigger or a stored function begins, it is not\npossible to use statements which reference a savepoint which was defined from\nout of that stored program.\n\nWhen a COMMIT (including implicit commits) or a ROLLBACK statement (with no TO\nclause) is performed, they act on the whole transaction, and all savepoints\nare removed.\n\nErrors\n------\n\nIf COMMIT or ROLLBACK is issued and no transaction was started, no error is\nreported.\n\nIf SAVEPOINT is issued and no transaction was started, no error is reported\nbut no savepoint is created. When ROLLBACK TO SAVEPOINT or RELEASE SAVEPOINT\nis called for a savepoint that does not exist, an error like this is issued:\n\nERROR 1305 (42000): SAVEPOINT svp_name does not exist\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/transactions/savepoint','','https://mariadb.com/docs/server/reference/sql-statements/transactions/savepoint'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (100,8,'Metadata Locking','MariaDB supports metadata locking. This means that when a transaction\n(including XA transactions) uses a table, it locks its metadata until the end\nof transaction. Non-transactional tables are also locked, as well as views and\nobjects which are related to locked tables/views (stored functions, triggers,\netc). When a connection tries to use a DDL statement (like an ALTER TABLE)\nwhich modifies a table that is locked, that connection is queued, and has to\nwait until it\'s unlocked. Using savepoints and performing a partial rollback\ndoes not release metadata locks.\n\nLOCK TABLES ... WRITE are also queued. Some wrong statements which produce an\nerror may not need to wait for the lock to be freed.\n\nThe metadata lock\'s timeout is determined by the value of the\nlock_wait_timeout server system variable (in seconds). However, note that its\ndefault value is 31536000 (1 year, MariaDB <= 10.2.3), or 86400 (1 day,\nMariaDB >= 10.2.4). If this timeout is exceeded, the following error is\nreturned:\n\nERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction\n\nIf the metadata_lock_info plugin is installed, the Information Schema\nmetadata_lock_info table stores information about existing metadata locks.\n\nMariaDB starting with 10.5.2\n----------------------------\nFrom MariaDB 10.5, the Performance Schema metadata_locks table contains\nmetadata lock information.\n\nExample\n-------\n\nLet\'s use the following MEMORY (non-transactional) table:\n\nCREATE TABLE t (a INT) ENGINE = MEMORY;\n\nConnection 1 starts a transaction, and INSERTs a row into t:\n\nSTART TRANSACTION;\n\nINSERT INTO t SET a=1;\n\nt\'s metadata is now locked by connection 1. Connection 2 tries to alter t, but\nhas to wait:\n\nALTER TABLE t ADD COLUMN b INT;\n\nConnection 2\'s prompt is blocked now.\n\nNow connection 1 ends the transaction:\n\nCOMMIT;\n\n...and connection 2 finally gets the output of its command:\n\nQuery OK, 1 row affected (35.23 sec)\nRecords: 1 Duplicates: 0 Warnings: 0\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/transactions/metadata-locking','','https://mariadb.com/docs/server/reference/sql-statements/transactions/metadata-locking'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (101,8,'Transaction Timeouts','MariaDB has always had the wait_timeout and interactive_timeout settings,\nwhich close connections after a certain period of inactivity.\n\nHowever, these are by default set to a long wait period. In situations where\ntransactions may be started, but not committed or rolled back, more granular\ncontrol and a shorter timeout may be desirable so as to avoid locks being held\nfor too long.\n\nMariaDB 10.3 introduced three new variables to handle this situation.\n\n* idle_transaction_timeout (all transactions)\n* idle_write_transaction_timeout (write transactions - called\nidle_readwrite_transaction_timeout until MariaDB 10.3.2)\n* idle_readonly_transaction_timeout (read transactions)\n\nThese accept a time in seconds to time out, by closing the connection,\ntransactions that are idle for longer than this period. By default all are set\nto zero, or no timeout.\n\nidle_transaction_timeout affects all transactions,\nidle_write_transaction_timeout affects write transactions only and\nidle_readonly_transaction_timeout affects read transactions only. The latter\ntwo variables work independently. However, if either is set along with\nidle_transaction_timeout, the settings for idle_write_transaction_timeout or\nidle_readonly_transaction_timeout will take precedence.\n\nExamples\n--------\n\nSET SESSION idle_transaction_timeout=2;\nBEGIN;\nSELECT * FROM t;\nEmpty set (0.000 sec)\n## wait 3 seconds\nSELECT * FROM t;\nERROR 2006 (HY000): MySQL server has gone away\n\nSET SESSION idle_write_transaction_timeout=2;\nBEGIN;\nSELECT * FROM t;\nEmpty set (0.000 sec)\n## wait 3 seconds\nSELECT * FROM t;\nEmpty set (0.000 sec)\nINSERT INTO t VALUES(1);\n## wait 3 seconds\nSELECT * FROM t;\nERROR 2006 (HY000): MySQL server has gone away\n\nSET SESSION idle_transaction_timeout=2, SESSION\nidle_readonly_transaction_timeout=10;\nBEGIN;\nSELECT * FROM t;\nEmpty set (0.000 sec)\n ## wait 3 seconds\nSELECT * FROM t;\nEmpty set (0.000 sec)\n## wait 11 seconds\nSELECT * FROM t;\nERROR 2006 (HY000): MySQL server has gone away\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/transactions/transaction-timeouts','','https://mariadb.com/docs/server/reference/sql-statements/transactions/transaction-timeouts'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (102,8,'UNLOCK TABLES','Syntax\n------\n\nUNLOCK TABLES\n\nDescription\n-----------\n\nUNLOCK TABLES explicitly releases any table locks held by the current session.\nSee LOCK TABLES for more information.\n\nIn addition to releasing table locks acquired by the LOCK TABLES statement,\nthe UNLOCK TABLES statement also releases the global read lock acquired by the\nFLUSH TABLES WITH READ LOCK statement. The FLUSH TABLES WITH READ LOCK\nstatement is very useful for performing backups. See FLUSH for more\ninformation about FLUSH TABLES WITH READ LOCK.\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/transactions/transactions-unlock-tables','','https://mariadb.com/docs/server/reference/sql-statements/transactions/transactions-unlock-tables'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (103,8,'WAIT and NOWAIT','Extended syntax so that it is possible to set innodb_lock_wait_timeout and\nlock_wait_timeout for the following statements:\n\nSyntax\n------\n\nALTER TABLE tbl_name [WAIT n|NOWAIT] ...\nCREATE ... INDEX ON tbl_name (index_col_name, ...) [WAIT n|NOWAIT] ...\nDROP INDEX ... [WAIT n|NOWAIT]\nDROP TABLE tbl_name [WAIT n|NOWAIT] ...\nLOCK TABLE ... [WAIT n|NOWAIT]\nOPTIMIZE TABLE tbl_name [WAIT n|NOWAIT]\nRENAME TABLE tbl_name [WAIT n|NOWAIT] ...\nSELECT ... FOR UPDATE [WAIT n|NOWAIT]\nSELECT ... LOCK IN SHARE MODE [WAIT n|NOWAIT]\nTRUNCATE TABLE tbl_name [WAIT n|NOWAIT]\n\nDescription\n-----------\n\nThe lock wait timeout can be explicitly set in the statement by using either\nWAIT n (to set the wait in seconds) or NOWAIT, in which case the statement\nwill immediately fail if the lock cannot be obtained. WAIT 0 is equivalent to\nNOWAIT.\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/transactions/wait-and-nowait','','https://mariadb.com/docs/server/reference/sql-statements/transactions/wait-and-nowait'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (104,8,'XA Transactions','Overview\n--------\n\nThe MariaDB XA implementation is based on the X/Open CAE document Distributed\nTransaction Processing: The XA Specification. This document is published by\nThe Open Group and available at\nhttp://www.opengroup.org/public/pubs/catalog/c193.htm.\n\nXA transactions are designed to allow distributed transactions, where a\ntransaction manager (the application) controls a transaction which involves\nmultiple resources. Such resources are usually DBMSs, but could be resources\nof any type. The whole set of required transactional operations is called a\nglobal transaction. Each subset of operations which involve a single resource\nis called a local transaction. XA used a 2-phases commit (2PC). With the first\ncommit, the transaction manager tells each resource to prepare an effective\ncommit, and waits for a confirm message. The changes are not still made\neffective at this point. If any of the resources encountered an error, the\ntransaction manager will rollback the global transaction. If all resources\ncommunicate that the first commit is successful, the transaction manager can\nrequire a second commit, which makes the changes effective.\n\nIn MariaDB, XA transactions can only be used with storage engines that support\nthem. At least InnoDB, TokuDB, SPIDER and MyRocks support them. For InnoDB,\nuntil MariaDB 10.2, XA transactions can be disabled by setting the\ninnodb_support_xa server system variable to 0. From MariaDB 10.3, XA\ntransactions are always supported.\n\nLike regular transactions, XA transactions create metadata locks on accessed\ntables.\n\nXA transactions require REPEATABLE READ as a minimum isolation level. However,\ndistributed transactions should always use SERIALIZABLE.\n\nTrying to start more than one XA transaction at the same time produces a 1400\nerror (SQLSTATE \'XAE09\'). The same error is produced when attempting to start\nan XA transaction while a regular transaction is in effect. Trying to start a\nregular transaction while an XA transaction is in effect produces a 1399 error\n(SQLSTATE \'XAE07\').\n\nThe statements that cause an implicit COMMIT for regular transactions produce\na 1400 error (SQLSTATE \'XAE09\') if a XA transaction is in effect.\n\nInternal XA vs External XA\n--------------------------\n\nXA transactions are an overloaded term in MariaDB. If a storage engine is\nXA-capable, it can mean one or both of these:\n\n* It supports MariaDB\'s internal two-phase commit API. This is transparent to\nthe user. Sometimes this is called \"internal XA\", since MariaDB\'s internal\ntransaction coordinator log can handle coordinating these transactions.\n\n* It supports XA transactions, with the XA START, XA PREPARE, XA COMMIT, etc.\nstatements. Sometimes this is called \"external XA\", since it requires the use\nof an external transaction coordinator to use this feature properly.\n\nTransaction Coordinator Log\n---------------------------\n\nIf you have two or more XA-capable storage engines enabled, then a transaction\ncoordinator log must be available.\n\nThere are currently two implementations of the transaction coordinator log:\n\n* Binary log-based transaction coordinator log\n* Memory-mapped file-based transaction coordinator log\n\nIf the binary log is enabled on a server, then the server will use the binary\nlog-based transaction coordinator log. Otherwise, it will use the\nmemory-mapped file-based transaction coordinator log.\n\nSee Transaction Coordinator Log for more information.\n\nSyntax\n------\n\nXA {START|BEGIN} xid [JOIN|RESUME]\n\nXA END xid [SUSPEND [FOR MIGRATE]]\n\nXA PREPARE xid\n\nXA COMMIT xid [ONE PHASE]\n\nXA ROLLBACK xid\n\nXA RECOVER [FORMAT=[\'RAW\'|\'SQL\']]\n\nxid: gtrid [, bqual [, formatID ]]\n\nThe interface to XA transactions is a set of SQL statements starting with XA.\nEach statement changes a transaction\'s state, determining which actions it can\nperform. A transaction which does not exist is in the NON-EXISTING state.\n\nXA START (or BEGIN) starts a transaction and defines its xid (a transaction\nidentifier). The JOIN or RESUME keywords have no effect. The new transaction\nwill be in ACTIVE state.\n\nThe xid can have 3 components, though only the first one is mandatory. gtrid\nis a quoted string representing a global transaction identifier. bqual is a\nquoted string representing a local transaction identifier. formatID is an\nunsigned integer indicating the format used for the first two components; if\nnot specified, defaults to 1. MariaDB does not interpret in any way these\ncomponents, and only uses them to identify a transaction. xids of transactions\nin effect must be unique.\n\nXA END declares that the specified ACTIVE transaction is finished and it\nchanges its state to IDLE. SUSPEND [FOR MIGRATE] has no effect.\n\nXA PREPARE prepares an IDLE transaction for commit, changing its state to\nPREPARED. This is the first commit.\n\nXA COMMIT definitely commits and terminates a transaction which has already\nbeen PREPARED. If the ONE PHASE clause is specified, this statements performs\na 1-phase commit on an IDLE transaction.\n\nXA ROLLBACK rolls back and terminates an IDLE or PREPARED transaction.\n\nXA RECOVER shows information about all PREPARED transactions.\n\nWhen trying to execute an operation which is not allowed for the transaction\'s\ncurrent state, an error is produced:\n\nXA COMMIT \'test\' ONE PHASE;\nERROR 1399 (XAE07): XAER_RMFAIL: The command cannot be executed when global\ntransaction is in the ACTIVE state\n\nXA COMMIT \'test2\';\nERROR 1399 (XAE07): XAER_RMFAIL: The command cannot be executed when global\ntransaction is in the NON-EXISTING state\n\nXA RECOVER\n----------\n\nThe XA RECOVER statement shows information about all transactions which are in\nthe PREPARED state. It does not matter which connection created the\ntransaction: if it has been PREPARED, it appears. But this does not mean that\na connection can commit or rollback a transaction which was started by another\nconnection. Note that transactions using a 1-phase commit are never in the\nPREPARED state, so they cannot be shown by XA RECOVER.\n\nXA RECOVER produces four columns:\n\nXA RECOVER;\n+----------+--------------+--------------+------+\n| formatID | gtrid_length | bqual_length | data |\n+----------+--------------+--------------+------+\n| 1 | 4 | 0 | test |\n+----------+--------------+--------------+------+\n\nMariaDB starting with 10.3.3\n----------------------------\nYou can use XA RECOVER FORMAT=\'SQL\' to get the data in a human readable form\nthat can be directly copy-pasted into XA COMMIT or XA ROLLBACK. This is\nparticularly useful for binary xid generated by some transaction coordinators.\n\nformatID is the formatID part of xid.\n\ndata are the gtrid and bqual parts of xid, concatenated.\n\ngtrid_length and bqual_length are the lengths of gtrid and bqual, respectevely.\n\nExamples\n--------\n\n2-phases commit:\n\nXA START \'test\';\n\nINSERT INTO t VALUES (1,2);\n\nXA END \'test\';\n\nXA PREPARE \'test\';\n\nXA COMMIT \'test\';\n\n1-phase commit:\n\nXA START \'test\';\n\nINSERT INTO t VALUES (1,2);\n\nXA END \'test\';\n\nXA COMMIT \'test\' ONE PHASE;\n\nHuman-readable:\n\nxa start \'12\\r34\\t67\\v78\', \'abc\\ndef\', 3;\n\ninsert t1 values (40);\n\nxa end \'12\\r34\\t67\\v78\', \'abc\\ndef\', 3;\n\nxa prepare \'12\\r34\\t67\\v78\', \'abc\\ndef\', 3;\n\nxa recover format=\'RAW\';\n+----------+--------------+--------------+--------------------+\n| formatID | gtrid_length | bqual_length | data |\n+----------+--------------+--------------+--------------------+\n34 67v78abc 11 | 7 | 12\ndef |\n+----------+--------------+--------------+--------------------+\n\nxa recover format=\'SQL\';\n+----------+--------------+--------------+-------------------------------------\n---------+\n| formatID | gtrid_length | bqual_length | data \n |\n+----------+--------------+--------------+-------------------------------------\n---------+\n| 3 | 11 | 7 |\nX\'31320d3334093637763738\',X\'6162630a646566\',3 |\n+----------+--------------+--------------+-------------------------------------\n---------+\n\nxa rollback X\'31320d3334093637763738\',X\'6162630a646566\',3;\n\nKnown Issues\n------------\n\nMariaDB Galera Cluster\n----------------------\n\nMariaDB Galera Cluster does not support XA transactions.\n\nHowever, MariaDB Galera Cluster builds include a built-in plugin called wsrep.\nPrior to MariaDB 10.4.3, this plugin was internally considered an XA-capable\nstorage engine. Consequently, these MariaDB Galera Cluster builds have\nmultiple XA-capable storage engines by default, even if the only \"real\"\nstorage engine that supports external XA transactions enabled on these builds\nby default is InnoDB. Therefore, when using one these builds MariaDB would be\nforced to use a transaction coordinator log by default, which could have\nperformance implications.\n\nSee Transaction Coordinator Log Overview: MariaDB Galera Cluster for more\ninformation.\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/transactions/xa-transactions','','https://mariadb.com/docs/server/reference/sql-statements/transactions/xa-transactions'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (105,10,'CREATE USER','Syntax\n------\n\nCREATE [OR REPLACE] USER [IF NOT EXISTS] \n user_specification [,user_specification ...] \n [REQUIRE {NONE | tls_option [[AND] tls_option ...] }]\n [WITH resource_option [resource_option ...] ]\n [lock_option] [password_option]\n\nuser_specification:\n username [authentication_option]\n\nauthentication_option:\n IDENTIFIED BY \'password\'\n | IDENTIFIED BY PASSWORD \'password_hash\'\n | IDENTIFIED {VIA|WITH} authentication_rule [OR authentication_rule ...]\n\nauthentication_rule:\n authentication_plugin\n | authentication_plugin {USING|AS} \'authentication_string\'\n | authentication_plugin {USING|AS} PASSWORD(\'password\')\n\ntls_option:\n SSL\n | X509\n | CIPHER \'cipher\'\n | ISSUER \'issuer\'\n | SUBJECT \'subject\'\n\nresource_option:\n MAX_QUERIES_PER_HOUR count\n | MAX_UPDATES_PER_HOUR count\n | MAX_CONNECTIONS_PER_HOUR count\n | MAX_USER_CONNECTIONS count\n | MAX_STATEMENT_TIME time\n\npassword_option:\n PASSWORD EXPIRE\n | PASSWORD EXPIRE DEFAULT\n | PASSWORD EXPIRE NEVER\n | PASSWORD EXPIRE INTERVAL N DAY\n\nlock_option:\n ACCOUNT LOCK\n | ACCOUNT UNLOCK\n}\n\nDescription\n-----------\n\nThe CREATE USER statement creates new MariaDB accounts. To use it, you must\nhave the global CREATE USER privilege or the INSERT privilege for the mysql\ndatabase. For each account, CREATE USER creates a new row in mysql.user (until\nMariaDB 10.3 this is a table, from MariaDB 10.4 it\'s a view) or\nmysql.global_priv_table (from MariaDB 10.4) that has no privileges.\n\nIf any of the specified accounts, or any permissions for the specified\naccounts, already exist, then the server returns ERROR 1396 (HY000). If an\nerror occurs, CREATE USER will still create the accounts that do not result in\nan error. Only one error is produced for all users which have not been created:\n\nERROR 1396 (HY000): \n Operation CREATE USER failed for \'u1\'@\'%\',\'u2\'@\'%\'\n\nCREATE USER, DROP USER, CREATE ROLE, and DROP ROLE all produce the same error\ncode when they fail.\n\nSee Account Names below for details on how account names are specified.\n\nOR REPLACE\n----------\n\nIf the optional OR REPLACE clause is used, it is basically a shortcut for:\n\nDROP USER IF EXISTS name;\nCREATE USER name ...;\n\nFor example:\n\nCREATE USER foo2@test IDENTIFIED BY \'password\';\nERROR 1396 (HY000): Operation CREATE USER failed for \'foo2\'@\'test\'\n\nCREATE OR REPLACE USER foo2@test IDENTIFIED BY \'password\';\nQuery OK, 0 rows affected (0.00 sec)\n\nIF NOT EXISTS\n-------------\n\nWhen the IF NOT EXISTS clause is used, MariaDB will return a warning instead\nof an error if the specified user already exists.\n\nFor example:\n\nCREATE USER foo2@test IDENTIFIED BY \'password\';\nERROR 1396 (HY000): Operation CREATE USER failed for \'foo2\'@\'test\'\n\nCREATE USER IF NOT EXISTS foo2@test IDENTIFIED BY \'password\';\nQuery OK, 0 rows affected, 1 warning (0.00 sec)\n\nSHOW WARNINGS;\n+-------+------+----------------------------------------------------+\n| Level | Code | Message |\n+-------+------+----------------------------------------------------+\n| Note | 1973 | Can\'t create user \'foo2\'@\'test\'; it already exists |\n+-------+------+----------------------------------------------------+\n\nAuthentication Options\n----------------------\n\nIDENTIFIED BY \'password\'\n------------------------\n\nThe optional IDENTIFIED BY clause can be used to provide an account with a\npassword. The password should be specified in plain text. It will be hashed by\nthe PASSWORD function prior to being stored in the\nmysql.user/mysql.global_priv_table table.\n\nFor example, if our password is mariadb, then we can create the user with:\n\nCREATE USER foo2@test IDENTIFIED BY \'mariadb\';\n\nIf you do not specify a password with the IDENTIFIED BY clause, the user will\nbe able to connect without a password. A blank password is not a wildcard to\nmatch any password. The user must connect without providing a password if no\npassword is set.\n\nThe only authentication plugins that this clause supports are\nmysql_native_password and mysql_old_password.\n\nIDENTIFIED BY PASSWORD \'password_hash\'\n--------------------------------------\n\nThe optional IDENTIFIED BY PASSWORD clause can be used to provide an account\nwith a password that has already been hashed. The password should be specified\nas a hash that was provided by the PASSWORD function. It will be stored in the\nmysql.user/mysql.global_priv_table table as-is.\n\nFor example, if our password is mariadb, then we can find the hash with:\n\nSELECT PASSWORD(\'mariadb\');\n+-------------------------------------------+\n| PASSWORD(\'mariadb\') |\n+-------------------------------------------+\n| *54958E764CE10E50764C2EECBB71D01F08549980 |\n+-------------------------------------------+\n1 row in set (0.00 sec)\n\nAnd then we can create a user with the hash:\n\nCREATE USER foo2@test IDENTIFIED BY PASSWORD\n\'*54958E764CE10E50764C2EECBB71D01F08549980\';\n\nIf you do not specify a password with the IDENTIFIED BY clause, the user will\nbe able to connect without a password. A blank password is not a wildcard to\nmatch any password. The user must connect without providing a password if no\npassword is set.\n\nThe only authentication plugins that this clause supports are\nmysql_native_password and mysql_old_password.\n\nIDENTIFIED {VIA|WITH} authentication_plugin\n-------------------------------------------\n\nThe optional IDENTIFIED VIA authentication_plugin allows you to specify that\nthe account should be authenticated by a specific authentication plugin. The\nplugin name must be an active authentication plugin as per SHOW PLUGINS. If it\ndoesn\'t show up in that output, then you will need to install it with INSTALL\nPLUGIN or INSTALL SONAME.\n\nVIA and WITH are synonyms.\n\nFor example, this could be used with the PAM authentication plugin:\n\nCREATE USER foo2@test IDENTIFIED VIA pam;\n\nSome authentication plugins allow additional arguments to be specified after a\nUSING or AS keyword. For example, the PAM authentication plugin accepts a\nservice name:\n\nCREATE USER foo2@test IDENTIFIED VIA pam USING \'mariadb\';\n\nThe exact meaning of the additional argument would depend on the specific\nauthentication plugin.\n\nMariaDB starting with 10.4.0\n----------------------------\nThe USING or AS keyword can also be used to provide a plain-text password to a\nplugin if it\'s provided as an argument to the PASSWORD() function. This is\nonly valid for authentication plugins that have implemented a hook for the\nPASSWORD() function. For example, the ed25519 authentication plugin supports\nthis:\n\nCREATE USER safe@\'%\' IDENTIFIED VIA ed25519 USING PASSWORD(\'secret\');\n\nMariaDB starting with 10.4.3\n----------------------------\nOne can specify many authentication plugins, they all work as alternatives\nways of authenticating a user:\n\nCREATE USER safe@\'%\' IDENTIFIED VIA ed25519 USING PASSWORD(\'secret\') OR\nunix_socket;\n\nBy default, when you create a user without specifying an authentication\nplugin, MariaDB uses the mysql_native_password plugin.\n\nTLS Options\n-----------\n\nBy default, MariaDB transmits data between the server and clients without\nencrypting it. This is generally acceptable when the server and client run on\nthe same host or in networks where security is guaranteed through other means.\nHowever, in cases where the server and client exist on separate networks or\nthey are in a high-risk network, the lack of encryption does introduce\nsecurity concerns as a malicious actor could potentially eavesdrop on the\ntraffic as it is sent over the network between them.\n\nTo mitigate this concern, MariaDB allows you to encrypt data in transit\nbetween the server and clients using the Transport Layer Security (TLS)\nprotocol. TLS was formerly known as Secure Socket Layer (SSL), but strictly\nspeaking the SSL protocol is a predecessor to TLS and, that version of the\nprotocol is now considered insecure. The documentation still uses the term SSL\noften and for compatibility reasons TLS-related server system and status\nvariables still use the prefix ssl_, but internally, MariaDB only supports its\nsecure successors.\n\nSee Secure Connections Overview for more information about how to determine\nwhether your MariaDB server has TLS support.\n\nYou can set certain TLS-related restrictions for specific user accounts. For\ninstance, you might use this with user accounts that require access to\nsensitive data while sending it across networks that you do not control. These\nrestrictions can be enabled for a user account with the CREATE USER, ALTER\nUSER, or GRANT statements. The following options are available:\n\n+---------------------------+------------------------------------------------+\n| Option | Description |\n+---------------------------+------------------------------------------------+\n| REQUIRE NONE | TLS is not required for this account, but can |\n| | still be used. |\n+---------------------------+------------------------------------------------+\n| REQUIRE SSL | The account must use TLS, but no valid X509 |\n| | certificate is required. This option cannot |\n| | be combined with other TLS options. |\n+---------------------------+------------------------------------------------+\n| REQUIRE X509 | The account must use TLS and must have a |\n| | valid X509 certificate. This option implies |\n| | REQUIRE SSL. This option cannot be combined |\n| | with other TLS options. |\n+---------------------------+------------------------------------------------+\n| REQUIRE ISSUER \'issuer\' | The account must use TLS and must have a |\n| | valid X509 certificate. Also, the Certificate |\n| | Authority must be the one specified via the |\n| | string issuer. This option implies REQUIRE |\n| | X509. This option can be combined with the |\n| | SUBJECT, and CIPHER options in any order. |\n+---------------------------+------------------------------------------------+\n| REQUIRE SUBJECT \'subject\' | The account must use TLS and must have a |\n| | valid X509 certificate. Also, the |\n| | certificate\'s Subject must be the one |\n| | specified via the string subject. This option |\n| | implies REQUIRE X509. This option can be |\n| | combined with the ISSUER, and CIPHER options |\n| | in any order. |\n+---------------------------+------------------------------------------------+\n| REQUIRE CIPHER \'cipher\' | The account must use TLS, but no valid X509 |\n| | certificate is required. Also, the encryption |\n| | used for the connection must use a specific |\n| | cipher method specified in the string cipher. |\n| | This option implies REQUIRE SSL. This option |\n| | can be combined with the ISSUER, and SUBJECT |\n| | options in any order. |\n+---------------------------+------------------------------------------------+\n\nThe REQUIRE keyword must be used only once for all specified options, and the\nAND keyword can be used to separate individual options, but it is not required.\n\nFor example, you can create a user account that requires these TLS options\nwith the following:\n\nCREATE USER \'alice\'@\'%\'\n REQUIRE SUBJECT \'/CN=alice/O=My Dom, Inc./C=US/ST=Oregon/L=Portland\'\n AND ISSUER \'/C=FI/ST=Somewhere/L=City/ O=Some Company/CN=Peter\nParker/emailAddress=p.parker@marvel.com\'\n AND CIPHER \'SHA-DES-CBC3-EDH-RSA\';\n\nIf any of these options are set for a specific user account, then any client\nwho tries to connect with that user account will have to be configured to\nconnect with TLS.\n\nSee Securing Connections for Client and Server for information on how to\nenable TLS on the client and server.\n\nResource Limit Options\n----------------------\n\nIt is possible to set per-account limits for certain server resources. The\nfollowing table shows the values that can be set per account:\n\n+--------------------------------------+--------------------------------------+\n| Limit Type | Decription |\n+--------------------------------------+--------------------------------------+\n| MAX_QUERIES_PER_HOUR | Number of statements that the |\n| | account can issue per hour |\n| | (including updates) |\n+--------------------------------------+--------------------------------------+\n| MAX_UPDATES_PER_HOUR | Number of updates (not queries) |\n| | that the account can issue per hour |\n+--------------------------------------+--------------------------------------+\n| MAX_CONNECTIONS_PER_HOUR | Number of connections that the |\n| | account can start per hour |\n+--------------------------------------+--------------------------------------+\n| MAX_USER_CONNECTIONS | Number of simultaneous connections |\n| | that can be accepted from the same |\n| | account; if it is 0, |\n| | max_connections will be used |\n| | instead; if max_connections is 0, |\n| | there is no limit for this |\n| | account\'s simultaneous connections. |\n+--------------------------------------+--------------------------------------+\n| MAX_STATEMENT_TIME | Timeout, in seconds, for statements |','','https://mariadb.com/docs/server/reference/sql-statements/account-management-sql-statements/create-user'); +update help_topic set description = CONCAT(description, '\n| | executed by the user. See also |\n| | Aborting Statements that Exceed a |\n| | Certain Time to Execute. |\n+--------------------------------------+--------------------------------------+\n\nIf any of these limits are set to 0, then there is no limit for that resource\nfor that user.\n\nHere is an example showing how to create a user with resource limits:\n\nCREATE USER \'someone\'@\'localhost\' WITH\n MAX_USER_CONNECTIONS 10\n MAX_QUERIES_PER_HOUR 200;\n\nThe resources are tracked per account, which means \'user\'@\'server\'; not per\nuser name or per connection.\n\nThe count can be reset for all users using FLUSH USER_RESOURCES, FLUSH\nPRIVILEGES or mariadb-admin reload.\n\nPer account resource limits are stored in the user table, in the mysql\ndatabase. Columns used for resources limits are named max_questions,\nmax_updates, max_connections (for MAX_CONNECTIONS_PER_HOUR), and\nmax_user_connections (for MAX_USER_CONNECTIONS).\n\nAccount Names\n-------------\n\nAccount names have both a user name component and a host name component, and\nare specified as \'user_name\'@\'host_name\'.\n\nThe user name and host name may be unquoted, quoted as strings using double\nquotes (\") or single quotes (\'), or quoted as identifiers using backticks (`).\nYou must use quotes when using special characters (such as a hyphen) or\nwildcard characters. If you quote, you must quote the user name and host name\nseparately (for example \'user_name\'@\'host_name\').\n\nHost Name Component\n-------------------\n\nIf the host name is not provided, it is assumed to be \'%\'.\n\nHost names may contain the wildcard characters % and _. They are matched as if\nby the LIKE clause. If you need to use a wildcard character literally (for\nexample, to match a domain name with an underscore), prefix the character with\na backslash. See LIKE for more information on escaping wildcard characters.\n\nHost name matches are case-insensitive. Host names can match either domain\nnames or IP addresses. Use \'localhost\' as the host name to allow only local\nclient connections. On Linux, the loopback interface (127.0.0.1) will not\nmatch \'localhost\' as it is not considered a local connection: this means that\nonly connections via UNIX-domain sockets will match \'localhost\'.\n\nYou can use a netmask to match a range of IP addresses using \'base_ip/netmask\'\nas the host name. A user with an IP address ip_addr will be allowed to connect\nif the following condition is true:\n\nip_addr & netmask = base_ip\n\nFor example, given a user:\n\nCREATE USER \'maria\'@\'247.150.130.0/255.255.255.0\';\n\nthe IP addresses satisfying this condition range from 247.150.130.0 to\n247.150.130.255.\n\nUsing 255.255.255.255 is equivalent to not using a netmask at all. Netmasks\ncannot be used for IPv6 addresses.\n\nNote that the credentials added when creating a user with the \'%\' wildcard\nhost will not grant access in all cases. For example, some systems come with\nan anonymous localhost user, and when connecting from localhost this will take\nprecedence.\n\nBefore MariaDB 10.6, the host name component could be up to 60 characters in\nlength. Starting from MariaDB 10.6, it can be up to 255 characters.\n\nUser Name Component\n-------------------\n\nUser names must match exactly, including case. A user name that is empty is\nknown as an anonymous account and is allowed to match a login attempt with any\nuser name component. These are described more in the next section.\n\nFor valid identifiers to use as user names, see Identifier Names.\n\nIt is possible for more than one account to match when a user connects.\nMariaDB selects the first matching account after sorting according to the\nfollowing criteria:\n\n* Accounts with an exact host name are sorted before accounts using a wildcard\nin the\nhost name. Host names using a netmask are considered to be exact for sorting.\n* Accounts with a wildcard in the host name are sorted according to the\nposition of\nthe first wildcard character. Those with a wildcard character later in the\nhost name\nsort before those with a wildcard character earlier in the host name.\n* Accounts with a non-empty user name sort before accounts with an empty user\nname.\n* Accounts with an empty user name are sorted last. As mentioned previously,\nthese are known as anonymous accounts. These are described more in the next\nsection.\n\nThe following table shows a list of example account as sorted by these\ncriteria:\n\n+---------+-------------+\n| User | Host |\n+---------+-------------+\n| joffrey | 192.168.0.3 |\n| | 192.168.0.% |\n| joffrey | 192.168.% |\n| | 192.168.% |\n+---------+-------------+\n\nOnce connected, you only have the privileges granted to the account that\nmatched, not all accounts that could have matched. For example, consider the\nfollowing commands:\n\nCREATE USER \'joffrey\'@\'192.168.0.3\';\nCREATE USER \'joffrey\'@\'%\';\nGRANT SELECT ON test.t1 to \'joffrey\'@\'192.168.0.3\';\nGRANT SELECT ON test.t2 to \'joffrey\'@\'%\';\n\nIf you connect as joffrey from 192.168.0.3, you will have the SELECT privilege\non the table test.t1, but not on the table test.t2. If you connect as joffrey\nfrom any other IP address, you will have the SELECT privilege on the table\ntest.t2, but not on the table test.t1.\n\nUsernames can be up to 80 characters long before 10.6 and starting from 10.6\nit can be 128 characters long.\n\nAnonymous Accounts\n------------------\n\nAnonymous accounts are accounts where the user name portion of the account\nname is empty. These accounts act as special catch-all accounts. If a user\nattempts to log into the system from a host, and an anonymous account exists\nwith a host name portion that matches the user\'s host, then the user will log\nin as the anonymous account if there is no more specific account match for the\nuser name that the user entered.\n\nFor example, here are some anonymous accounts:\n\nCREATE USER \'\'@\'localhost\';\nCREATE USER \'\'@\'192.168.0.3\';\n\nFixing a Legacy Default Anonymous Account\n-----------------------------------------\n\nOn some systems, the mysql.db table has some entries for the \'\'@\'%\' anonymous\naccount by default. Unfortunately, there is no matching entry in the\nmysql.user/mysql.global_priv_table table, which means that this anonymous\naccount doesn\'t exactly exist, but it does have privileges--usually on the\ndefault test database created by mariadb-install-db. These account-less\nprivileges are a legacy that is leftover from a time when MySQL\'s privilege\nsystem was less advanced.\n\nThis situation means that you will run into errors if you try to create a\n\'\'@\'%\' account. For example:\n\nCREATE USER \'\'@\'%\';\nERROR 1396 (HY000): Operation CREATE USER failed for \'\'@\'%\'\n\nThe fix is to DELETE the row in the mysql.db table and then execute FLUSH\nPRIVILEGES:\n\nDELETE FROM mysql.db WHERE User=\'\' AND Host=\'%\';\nFLUSH PRIVILEGES;\n\nNote that FLUSH PRIVILEGES is only needed if one modifies the mysql tables\ndirectly. It is not needed when using CREATE USER, DROP USER, GRANT etc.\n\nAnd then the account can be created:\n\nCREATE USER \'\'@\'%\';\nQuery OK, 0 rows affected (0.01 sec)\n\nSee MDEV-13486 for more information.\n\nPassword Expiry\n---------------\n\nMariaDB starting with 10.4.3\n----------------------------\nBesides automatic password expiry, as determined by default_password_lifetime,\npassword expiry times can be set on an individual user basis, overriding the\nglobal setting, for example:\n\nCREATE USER \'monty\'@\'localhost\' PASSWORD EXPIRE INTERVAL 120 DAY;\n\nSee User Password Expiry for more details.\n\nAccount Locking\n---------------\n\nMariaDB starting with 10.4.2\n----------------------------\nAccount locking permits privileged administrators to lock/unlock user\naccounts. No new client connections will be permitted if an account is locked\n(existing connections are not affected). For example:\n\nCREATE USER \'marijn\'@\'localhost\' ACCOUNT LOCK;\n\nSee Account Locking for more details.\n\nFrom MariaDB 10.4.7 and MariaDB 10.5.8, the lock_option and password_option\nclauses can occur in either order.\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/account-management-sql-statements/create-user') WHERE help_topic_id = 105; +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (106,10,'ALTER USER','Syntax\n------\n\nALTER USER [IF EXISTS] \n user_specification [,user_specification] ...\n [REQUIRE {NONE | tls_option [[AND] tls_option] ...}]\n [WITH resource_option [resource_option] ...]\n [lock_option] [password_option]\n\nuser_specification:\n username [authentication_option]\n\nauthentication_option:\n IDENTIFIED BY \'password\'\n | IDENTIFIED BY PASSWORD \'password_hash\'\n | IDENTIFIED {VIA|WITH} authentication_rule [OR authentication_rule] ...\n\nauthentication_rule:\n authentication_plugin\n | authentication_plugin {USING|AS} \'authentication_string\'\n | authentication_plugin {USING|AS} PASSWORD(\'password\')\n\ntls_option\n SSL\n | X509\n | CIPHER \'cipher\'\n | ISSUER \'issuer\'\n | SUBJECT \'subject\'\n\nresource_option\n MAX_QUERIES_PER_HOUR count\n | MAX_UPDATES_PER_HOUR count\n | MAX_CONNECTIONS_PER_HOUR count\n | MAX_USER_CONNECTIONS count\n | MAX_STATEMENT_TIME time\n\npassword_option:\n PASSWORD EXPIRE\n | PASSWORD EXPIRE DEFAULT\n | PASSWORD EXPIRE NEVER\n | PASSWORD EXPIRE INTERVAL N DAY\n\nlock_option:\n ACCOUNT LOCK\n | ACCOUNT UNLOCK\n}\n\nDescription\n-----------\n\nThe ALTER USER statement modifies existing MariaDB accounts. To use it, you\nmust have the global CREATE USER privilege or the UPDATE privilege for the\nmysql database. The global SUPER privilege is also required if the read_only\nsystem variable is enabled.\n\nIf any of the specified user accounts do not yet exist, an error results. If\nan error occurs, ALTER USER will still modify the accounts that do not result\nin an error. Only one error is produced for all users which have not been\nmodified.\n\nFor renaming an existing account (user name and/or host), see RENAME USER.\n\nIF EXISTS\n---------\n\nWhen the IF EXISTS clause is used, MariaDB will return a warning instead of an\nerror for each specified user that does not exist.\n\nAccount Names\n-------------\n\nFor ALTER USER statements, account names are specified as the username\nargument in the same way as they are for CREATE USER statements. See account\nnames from the CREATE USER page for details on how account names are specified.\n\nCURRENT_USER or CURRENT_USER() can also be used to alter the account logged\ninto the current session. For example, to change the current user\'s password\nto mariadb:\n\nALTER USER CURRENT_USER() IDENTIFIED BY \'mariadb\';\n\nAuthentication Options\n----------------------\n\nMariaDB starting with 10.4\n--------------------------\nFrom MariaDB 10.4, it is possible to use more than one authentication plugin\nfor each user account. For example, this can be useful to slowly migrate users\nto the more secure ed25519 authentication plugin over time, while allowing the\nold mysql_native_password authentication plugin as an alternative for the\ntransitional period. See Authentication from MariaDB 10.4 for more.\n\nWhen running ALTER USER, not specifying an authentication option in the\nIDENTIFIED VIA clause will remove that authentication method. (However this\nwas not the case before MariaDB 10.4.13, see MDEV-21928)\n\nFor example, a user is created with the ability to authenticate via both a\npassword and unix_socket:\n\nCREATE USER \'bob\'@\'localhost\' \n IDENTIFIED VIA mysql_native_password USING PASSWORD(\'pwd\')\n OR unix_socket;\n\nSHOW CREATE USER \'bob\'@\'localhost\'\\G\n*************************** 1. row ***************************\nCREATE USER for bob@localhost: CREATE USER `bob`@`localhost` \n IDENTIFIED VIA mysql_native_password\n USING \'*975B2CD4FF9AE554FE8AD33168FBFC326D2021DD\'\n OR unix_socket\n\nIf the user\'s password is updated, but unix_socket authentication is not\nspecified in the IDENTIFIED VIA clause, unix_socket authentication will no\nlonger be permitted.\n\nALTER USER \'bob\'@\'localhost\' IDENTIFIED VIA mysql_native_password \n USING PASSWORD(\'pwd2\');\n\nSHOW CREATE USER \'bob\'@\'localhost\'\\G\n*************************** 1. row ***************************\nCREATE USER for bob@localhost: CREATE USER `bob`@`localhost` \n IDENTIFIED BY PASSWORD \'*38366FDA01695B6A5A9DD4E428D9FB8F7EB75512\'\n\nIDENTIFIED BY \'password\'\n------------------------\n\nThe optional IDENTIFIED BY clause can be used to provide an account with a\npassword. The password should be specified in plain text. It will be hashed by\nthe PASSWORD function prior to being stored to the mysql.user table.\n\nFor example, if our password is mariadb, then we can set the account\'s\npassword with:\n\nALTER USER foo2@test IDENTIFIED BY \'mariadb\';\n\nIf you do not specify a password with the IDENTIFIED BY clause, the user will\nbe able to connect without a password. A blank password is not a wildcard to\nmatch any password. The user must connect without providing a password if no\npassword is set.\n\nThe only authentication plugins that this clause supports are\nmysql_native_password and mysql_old_password.\n\nIDENTIFIED BY PASSWORD \'password_hash\'\n--------------------------------------\n\nThe optional IDENTIFIED BY PASSWORD clause can be used to provide an account\nwith a password that has already been hashed. The password should be specified\nas a hash that was provided by the PASSWORD#function. It will be stored to the\nmysql.user table as-is.\n\nFor example, if our password is mariadb, then we can find the hash with:\n\nSELECT PASSWORD(\'mariadb\');\n+-------------------------------------------+\n| PASSWORD(\'mariadb\') |\n+-------------------------------------------+\n| *54958E764CE10E50764C2EECBB71D01F08549980 |\n+-------------------------------------------+\n\nAnd then we can set an account\'s password with the hash:\n\nALTER USER foo2@test \n IDENTIFIED BY PASSWORD \'*54958E764CE10E50764C2EECBB71D01F08549980\';\n\nIf you do not specify a password with the IDENTIFIED BY clause, the user will\nbe able to connect without a password. A blank password is not a wildcard to\nmatch any password. The user must connect without providing a password if no\npassword is set.\n\nThe only authentication plugins that this clause supports are\nmysql_native_password and mysql_old_password.\n\nIDENTIFIED {VIA|WITH} authentication_plugin\n-------------------------------------------\n\nThe optional IDENTIFIED VIA authentication_plugin allows you to specify that\nthe account should be authenticated by a specific authentication plugin. The\nplugin name must be an active authentication plugin as per SHOW PLUGINS. If it\ndoesn\'t show up in that output, then you will need to install it with INSTALL\nPLUGIN or INSTALL SONAME.\n\nFor example, this could be used with the PAM authentication plugin:\n\nALTER USER foo2@test IDENTIFIED VIA pam;\n\nSome authentication plugins allow additional arguments to be specified after a\nUSING or AS keyword. For example, the PAM authentication plugin accepts a\nservice name:\n\nALTER USER foo2@test IDENTIFIED VIA pam USING \'mariadb\';\n\nThe exact meaning of the additional argument would depend on the specific\nauthentication plugin.\n\nIn MariaDB 10.4 and later, the USING or AS keyword can also be used to provide\na plain-text password to a plugin if it\'s provided as an argument to the\nPASSWORD() function. This is only valid for authentication plugins that have\nimplemented a hook for the PASSWORD() function. For example, the ed25519\nauthentication plugin supports this:\n\nALTER USER safe@\'%\' IDENTIFIED VIA ed25519 USING PASSWORD(\'secret\');\n\nTLS Options\n-----------\n\nBy default, MariaDB transmits data between the server and clients without\nencrypting it. This is generally acceptable when the server and client run on\nthe same host or in networks where security is guaranteed through other means.\nHowever, in cases where the server and client exist on separate networks or\nthey are in a high-risk network, the lack of encryption does introduce\nsecurity concerns as a malicious actor could potentially eavesdrop on the\ntraffic as it is sent over the network between them.\n\nTo mitigate this concern, MariaDB allows you to encrypt data in transit\nbetween the server and clients using the Transport Layer Security (TLS)\nprotocol. TLS was formerly known as Secure Socket Layer (SSL), but strictly\nspeaking the SSL protocol is a predecessor to TLS and, that version of the\nprotocol is now considered insecure. The documentation still uses the term SSL\noften and for compatibility reasons TLS-related server system and status\nvariables still use the prefix ssl_, but internally, MariaDB only supports its\nsecure successors.\n\nSee Secure Connections Overview for more information about how to determine\nwhether your MariaDB server has TLS support.\n\nYou can set certain TLS-related restrictions for specific user accounts. For\ninstance, you might use this with user accounts that require access to\nsensitive data while sending it across networks that you do not control. These\nrestrictions can be enabled for a user account with the CREATE USER, ALTER\nUSER, or GRANT statements. The following options are available:\n\n+---------------------------+------------------------------------------------+\n| Option | Description |\n+---------------------------+------------------------------------------------+\n| REQUIRE NONE | TLS is not required for this account, but can |\n| | still be used. |\n+---------------------------+------------------------------------------------+\n| REQUIRE SSL | The account must use TLS, but no valid X509 |\n| | certificate is required. This option cannot |\n| | be combined with other TLS options. |\n+---------------------------+------------------------------------------------+\n| REQUIRE X509 | The account must use TLS and must have a |\n| | valid X509 certificate. This option implies |\n| | REQUIRE SSL. This option cannot be combined |\n| | with other TLS options. |\n+---------------------------+------------------------------------------------+\n| REQUIRE ISSUER \'issuer\' | The account must use TLS and must have a |\n| | valid X509 certificate. Also, the Certificate |\n| | Authority must be the one specified via the |\n| | string issuer. This option implies REQUIRE |\n| | X509. This option can be combined with the |\n| | SUBJECT, and CIPHER options in any order. |\n+---------------------------+------------------------------------------------+\n| REQUIRE SUBJECT \'subject\' | The account must use TLS and must have a |\n| | valid X509 certificate. Also, the |\n| | certificate\'s Subject must be the one |\n| | specified via the string subject. This option |\n| | implies REQUIRE X509. This option can be |\n| | combined with the ISSUER, and CIPHER options |\n| | in any order. |\n+---------------------------+------------------------------------------------+\n| REQUIRE CIPHER \'cipher\' | The account must use TLS, but no valid X509 |\n| | certificate is required. Also, the encryption |\n| | used for the connection must use a specific |\n| | cipher method specified in the string cipher. |\n| | This option implies REQUIRE SSL. This option |\n| | can be combined with the ISSUER, and SUBJECT |\n| | options in any order. |\n+---------------------------+------------------------------------------------+\n\nThe REQUIRE keyword must be used only once for all specified options, and the\nAND keyword can be used to separate individual options, but it is not required.\n\nFor example, you can alter a user account to require these TLS options with\nthe following:\n\nALTER USER \'alice\'@\'%\'\n REQUIRE SUBJECT \'/CN=alice/O=My Dom, Inc./C=US/ST=Oregon/L=Portland\' AND\n ISSUER \'/C=FI/ST=Somewhere/L=City/ O=Some Company/CN=Peter\nParker/emailAddress=p.parker@marvel.com\'\n AND CIPHER \'SHA-DES-CBC3-EDH-RSA\';\n\nIf any of these options are set for a specific user account, then any client\nwho tries to connect with that user account will have to be configured to\nconnect with TLS.\n\nSee Securing Connections for Client and Server for information on how to\nenable TLS on the client and server.\n\nResource Limit Options\n----------------------\n\nIt is possible to set per-account limits for certain server resources. The\nfollowing table shows the values that can be set per account:\n\n+------------------------------------+---------------------------------------+\n| Limit Type | Description |\n+------------------------------------+---------------------------------------+\n| MAX_QUERIES_PER_HOUR | Number of statements that the |\n| | account can issue per hour |\n| | (including updates) |\n+------------------------------------+---------------------------------------+\n| MAX_UPDATES_PER_HOUR | Number of updates (not queries) that |\n| | the account can issue per hour |\n+------------------------------------+---------------------------------------+\n| MAX_CONNECTIONS_PER_HOUR | Number of connections that the |\n| | account can start per hour |\n+------------------------------------+---------------------------------------+\n| MAX_USER_CONNECTIONS | Number of simultaneous connections |\n| | that can be accepted from the same |\n| | account; if it is 0, max_connections |\n| | will be used instead; if |\n| | max_connections is 0, there is no |\n| | limit for this account\'s |','','https://mariadb.com/docs/server/reference/sql-statements/account-management-sql-statements/alter-user'); +update help_topic set description = CONCAT(description, '\n| | simultaneous connections. |\n+------------------------------------+---------------------------------------+\n| MAX_STATEMENT_TIME | Timeout, in seconds, for statements |\n| | executed by the user. See also |\n| | Aborting Statements that Exceed a |\n| | Certain Time to Execute. |\n+------------------------------------+---------------------------------------+\n\nIf any of these limits are set to 0, then there is no limit for that resource\nfor that user.\n\nHere is an example showing how to set an account\'s resource limits:\n\nALTER USER \'someone\'@\'localhost\' WITH\n MAX_USER_CONNECTIONS 10\n MAX_QUERIES_PER_HOUR 200;\n\nThe resources are tracked per account, which means \'user\'@\'server\'; not per\nuser name or per connection.\n\nThe count can be reset for all users using FLUSH USER_RESOURCES, FLUSH\nPRIVILEGES or mysqladmin reload.\n\nPer account resource limits are stored in the user table, in the mysql\ndatabase. Columns used for resources limits are named max_questions,\nmax_updates, max_connections (for MAX_CONNECTIONS_PER_HOUR), and\nmax_user_connections (for MAX_USER_CONNECTIONS).\n\nPassword Expiry\n---------------\n\nMariaDB starting with 10.4.3\n----------------------------\nBesides automatic password expiry, as determined by default_password_lifetime,\npassword expiry times can be set on an individual user basis, overriding the\nglobal setting, for example:\n\nALTER USER \'monty\'@\'localhost\' PASSWORD EXPIRE INTERVAL 120 DAY;\nALTER USER \'monty\'@\'localhost\' PASSWORD EXPIRE NEVER;\nALTER USER \'monty\'@\'localhost\' PASSWORD EXPIRE DEFAULT;\n\nSee User Password Expiry for more details.\n\nAccount Locking\n---------------\n\nMariaDB starting with 10.4.2\n----------------------------\nAccount locking permits privileged administrators to lock/unlock user\naccounts. No new client connections will be permitted if an account is locked\n(existing connections are not affected). For example:\n\nALTER USER \'marijn\'@\'localhost\' ACCOUNT LOCK;\n\nSee Account Locking for more details.\n\nFrom MariaDB 10.4.7 and MariaDB 10.5.8, the lock_option and password_option\nclauses can occur in either order.\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/account-management-sql-statements/alter-user') WHERE help_topic_id = 106; +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (107,10,'DROP USER','Syntax\n------\n\nDROP USER [IF EXISTS] user_name [, user_name] ...\n\nDescription\n-----------\n\nThe DROP USER statement removes one or more MariaDB accounts. It removes\nprivilege rows for the account from all grant tables. To use this statement,\nyou must have the global CREATE USER privilege or the DELETE privilege for the\nmysql database. Each account is named using the same format as for the CREATE\nUSER statement; for example, \'jeffrey\'@\'localhost\'. If you specify only the\nuser name part of the account name, a host name part of \'%\' is used. For\nadditional information about specifying account names, see CREATE USER.\n\nNote that, if you specify an account that is currently connected, it will not\nbe deleted until the connection is closed. The connection will not be\nautomatically closed.\n\nIf any of the specified user accounts do not exist, ERROR 1396 (HY000)\nresults. If an error occurs, DROP USER will still drop the accounts that do\nnot result in an error. Only one error is produced for all users which have\nnot been dropped:\n\nERROR 1396 (HY000): Operation DROP USER failed for \'u1\'@\'%\',\'u2\'@\'%\'\n\nFailed CREATE or DROP operations, for both users and roles, produce the same\nerror code.\n\nIF EXISTS\n---------\n\nIf the IF EXISTS clause is used, MariaDB will return a note instead of an\nerror if the user does not exist.\n\nExamples\n--------\n\nDROP USER bob;\n\nDROP USER foo2@localhost,foo2@\'127.%\';\n\nIF EXISTS:\n\nDROP USER bob;\nERROR 1396 (HY000): Operation DROP USER failed for \'bob\'@\'%\'\n\nDROP USER IF EXISTS bob;\nQuery OK, 0 rows affected, 1 warning (0.00 sec)\n\nSHOW WARNINGS;\n+-------+------+---------------------------------------------+\n| Level | Code | Message |\n+-------+------+---------------------------------------------+\n| Note | 1974 | Can\'t drop user \'bob\'@\'%\'; it doesn\'t exist |\n+-------+------+---------------------------------------------+\n\nURL: https://mariadb.com/docs/server/reference/sql-statements/account-management-sql-statements/drop-user','','https://mariadb.com/docs/server/reference/sql-statements/account-management-sql-statements/drop-user'); +insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (108,10,'GRANT','Syntax\n------\n\nGRANT\n priv_type [(column_list)]\n [, priv_type [(column_list)]] ...\n ON [object_type] priv_level\n TO user_specification [ user_options ...]\n\nuser_specification:\n username [authentication_option]\n | PUBLIC\nauthentication_option:\n IDENTIFIED BY \'password\'\n | IDENTIFIED BY PASSWORD \'password_hash\'\n | IDENTIFIED {VIA|WITH} authentication_rule [OR authentication_rule ...]\n\nauthentication_rule:\n authentication_plugin\n | authentication_plugin {USING|AS} \'authentication_string\'\n | authentication_plugin {USING|AS} PASSWORD(\'password\')\n\nGRANT PROXY ON username\n TO user_specification [, user_specification ...]\n [WITH GRANT OPTION]\n\nGRANT rolename TO grantee [, grantee ...]\n [WITH ADMIN OPTION]\n\ngrantee:\n rolename\n username [authentication_option]\n\nuser_options:\n [REQUIRE {NONE | tls_option [[AND] tls_option] ...}]\n [WITH with_option [with_option] ...]\n\nobject_type:\n TABLE\n | FUNCTION\n | PROCEDURE\n | PACKAGE\n | PACKAGE BODY\n\npriv_level:\n *\n | *.*\n | db_name.*\n | db_name.tbl_name\n | tbl_name\n | db_name.routine_name\n\nwith_option:\n GRANT OPTION\n | resource_option\n\nresource_option:\n MAX_QUERIES_PER_HOUR count\n | MAX_UPDATES_PER_HOUR count\n | MAX_CONNECTIONS_PER_HOUR count\n | MAX_USER_CONNECTIONS count\n | MAX_STATEMENT_TIME time\n\ntls_option:\n SSL\n | X509\n | CIPHER \'cipher\'\n | ISSUER \'issuer\'\n | SUBJECT \'subject\'\n\nDescription\n-----------\n\nThe GRANT statement allows you to grant privileges or roles to accounts. To\nuse GRANT, you must have the GRANT OPTION privilege, and you must have the\nprivileges that you are granting.\n\nUse the REVOKE statement to revoke privileges granted with the GRANT statement.\n\nUse the SHOW GRANTS statement to determine what privileges an account has.\n\nAccount Names\n-------------\n\nFor GRANT statements, account names are specified as the username argument in\nthe same way as they are for CREATE USER statements. See account names from\nthe CREATE USER page for details on how account names are specified.\n\nImplicit Account Creation\n-------------------------\n\nThe GRANT statement also allows you to implicitly create accounts in some\ncases.\n\nIf the account does not yet exist, then GRANT can implicitly create it. To\nimplicitly create an account with GRANT, a user is required to have the same\nprivileges that would be required to explicitly create the account with the\nCREATE USER statement.\n\nIf the NO_AUTO_CREATE_USER SQL_MODE is set, then accounts can only be created\nif authentication information is specified, or with a CREATE USER statement.\nIf no authentication information is provided, GRANT will produce an error when\nthe specified account does not exist, for example:\n\nshow variables like \'%sql_mode%\' ;\n+---------------+--------------------------------------------+\n| Variable_name | Value |\n+---------------+--------------------------------------------+\n| sql_mode | NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION |\n+---------------+--------------------------------------------+\n\nGRANT USAGE ON *.* TO \'user123\'@\'%\' IDENTIFIED BY \'\';\nERROR 1133 (28000): Can\'t find any matching row in the user table\n\nGRANT USAGE ON *.* TO \'user123\'@\'%\' \n IDENTIFIED VIA PAM using \'mariadb\' require ssl ;\nQuery OK, 0 rows affected (0.00 sec)\n\nselect host, user from mysql.user where user=\'user123\' ;\n\n+------+----------+\n| host | user |\n+------+----------+\n| % | user123 |\n+------+----------+\n\nPrivilege Levels\n----------------\n\nPrivileges can be set globally, for an entire database, for a table or\nroutine, or for individual columns in a table. Certain privileges can only be\nset at certain levels.\n\nGlobal privileges do not take effect immediately and are only applied to\nconnections created after the GRANT statement was executed.\n\n* Global privileges priv_type are granted using *.* for\npriv_level. Global privileges include privileges to administer the database\nand manage user accounts, as well as privileges for all tables, functions, and\nprocedures. Global privileges are stored in the mysql.user table prior to\nMariaDB 10.4, and in mysql.global_priv table afterwards.\n* Database privileges priv_type are granted using db_name.*\nfor priv_level, or using just * to use default database. Database\nprivileges include privileges to create tables and functions, as well as\nprivileges for all tables, functions, and procedures in the database. Database\nprivileges are stored in the mysql.db table.\n* Table privileges priv_type are granted using db_name.tbl_name\nfor priv_level, or using just tbl_name to specify a table in the default\ndatabase. The TABLE keyword is optional. Table privileges include the\nability to select and change data in the table. Certain table privileges can\nbe granted for individual columns.\n* Column privileges priv_type are granted by specifying a table for\npriv_level and providing a column list after the privilege type. They allow\nyou to control exactly which columns in a table users can select and change.\n* Function privileges priv_type are granted using FUNCTION db_name.routine_name\nfor priv_level, or using just FUNCTION routine_name to specify a function\nin the default database.\n* Procedure privileges priv_type are granted using PROCEDURE\ndb_name.routine_name\nfor priv_level, or using just PROCEDURE routine_name to specify a procedure\nin the default database.\n\nThe USAGE Privilege\n-------------------\n\nThe USAGE privilege grants no real privileges. The SHOW GRANTS statement will\nshow a global USAGE privilege for a newly-created user. You can use USAGE with\nthe GRANT statement to change options like GRANT OPTION and\nMAX_USER_CONNECTIONS without changing any account privileges.\n\nThe ALL PRIVILEGES Privilege\n----------------------------\n\nThe ALL PRIVILEGES privilege grants all available privileges. Granting all\nprivileges only affects the given privilege level. For example, granting all\nprivileges on a table does not grant any privileges on the database or\nglobally.\n\nUsing ALL PRIVILEGES does not grant the special GRANT OPTION privilege.\n\nYou can use ALL instead of ALL PRIVILEGES.\n\nThe GRANT OPTION Privilege\n--------------------------\n\nUse the WITH GRANT OPTION clause to give users the ability to grant privileges\nto other users at the given privilege level. Users with the GRANT OPTION\nprivilege can only grant privileges they have. They cannot grant privileges at\na higher privilege level than they have the GRANT OPTION privilege.\n\nThe GRANT OPTION privilege cannot be set for individual columns. If you use\nWITH GRANT OPTION when specifying column privileges, the GRANT OPTION\nprivilege will be granted for the entire table.\n\nUsing the WITH GRANT OPTION clause is equivalent to listing GRANT OPTION as a\nprivilege.\n\nGlobal Privileges\n-----------------\n\nThe following table lists the privileges that can be granted globally. You can\nalso grant all database, table, and function privileges globally. When granted\nglobally, these privileges apply to all databases, tables, or functions,\nincluding those created later.\n\nTo set a global privilege, use *.* for priv_level.\n\nBINLOG ADMIN\n------------\n\nEnables administration of the binary log, including the PURGE BINARY LOGS\nstatement and setting the system variables:\n\n* binlog_annotate_row_events\n* binlog_cache_size\n* binlog_commit_wait_count\n* binlog_commit_wait_usec\n* binlog_direct_non_transactional_updates\n* binlog_expire_logs_seconds\n* binlog_file_cache_size\n* binlog_format\n* binlog_row_image\n* binlog_row_metadata\n* binlog_stmt_cache_size\n* expire_logs_days\n* log_bin_compress\n* log_bin_compress_min_len\n* log_bin_trust_function_creators\n* max_binlog_cache_size\n* max_binlog_size\n* max_binlog_stmt_cache_size\n* sql_log_bin and\n* sync_binlog.\n\nAdded in MariaDB 10.5.2.\n\nBINLOG MONITOR\n--------------\n\nNew name for REPLICATION CLIENT from MariaDB 10.5.2, (REPLICATION CLIENT still\nsupported as an alias for compatibility purposes). Permits running SHOW\ncommands related to the binary log, in particular the SHOW BINLOG STATUS and\nSHOW BINARY LOGS statements. Unlike REPLICATION CLIENT prior to MariaDB 10.5,\nSHOW SLAVE STATUS isn\'t included in this privilege, and SLAVE MONITOR is\nrequired.\n\nBINLOG REPLAY\n-------------\n\nEnables replaying the binary log with the BINLOG statement (generated by\nmariadb-binlog), executing SET timestamp when secure_timestamp is set to\nreplication, and setting the session values of system variables usually\nincluded in BINLOG output, in particular:\n\n* gtid_domain_id\n* gtid_seq_no\n* pseudo_thread_id\n* server_id.\n\nAdded in MariaDB 10.5.2\n\nCONNECTION ADMIN\n----------------\n\nEnables administering connection resource limit options. This includes\nignoring the limits specified by:\n\n* max_connections\n* max_user_connections and\n* max_password_errors.\n\nThe statements specified in init_connect are not executed, killing connections\nand queries owned by other users is permitted. The following\nconnection-related system variables can be changed:\n\n* connect_timeout\n* disconnect_on_expired_password\n* extra_max_connections\n* init_connect\n* max_connections\n* max_connect_errors\n* max_password_errors\n* proxy_protocol_networks\n* secure_auth\n* slow_launch_time\n* thread_pool_exact_stats\n* thread_pool_dedicated_listener\n* thread_pool_idle_timeout\n* thread_pool_max_threads\n* thread_pool_min_threads\n* thread_pool_oversubscribe\n* thread_pool_prio_kickup_timer\n* thread_pool_priority\n* thread_pool_size, and\n* thread_pool_stall_limit.\n\nAdded in MariaDB 10.5.2.\n\nCREATE USER\n-----------\n\nCreate a user using the CREATE USER statement, or implicitly create a user\nwith the GRANT statement.\n\nFEDERATED ADMIN\n---------------\n\nExecute CREATE SERVER, ALTER SERVER, and DROP SERVER statements. Added in\nMariaDB 10.5.2.\n\nFILE\n----\n\nRead and write files on the server, using statements like LOAD DATA INFILE or\nfunctions like LOAD_FILE(). Also needed to create CONNECT outward tables.\nMariaDB server must have the permissions to access those files.\n\nGRANT OPTION\n------------\n\nGrant global privileges. You can only grant privileges that you have.\n\nPROCESS\n-------\n\nShow information about the active processes, for example via SHOW PROCESSLIST\nor mariadb-admin processlist. If you have the PROCESS privilege, you can see\nall threads. Otherwise, you can see only your own threads (that is, threads\nassociated with the MariaDB account that you are using).\n\nREAD_ONLY ADMIN\n---------------\n\nUser can set the read_only system variable and allows the user to perform\nwrite operations, even when the read_only option is active. Added in MariaDB\n10.5.2.\n\nFrom MariaDB 10.11.0, the READ_ONLY ADMIN privilege has been removed from\nSUPER. The benefit of this is that one can remove the READ_ONLY ADMIN\nprivilege from all users and ensure that no one can make any changes on any\nnon-temporary tables. This is useful on slaves when one wants to ensure that\nthe slave is kept identical to the master.\n\nRELOAD\n------\n\nExecute FLUSH statements or equivalent mariadb-admin commands.\n\nREPLICATION CLIENT\n------------------\n\nExecute SHOW MASTER STATUS and SHOW BINARY LOGS informative statements.\nRenamed to BINLOG MONITOR in MariaDB 10.5.2 (but still supported as an alias\nfor compatibility reasons). SHOW SLAVE STATUS was part of REPLICATION CLIENT\nprior to MariaDB 10.5.\n\nREPLICATION MASTER ADMIN\n------------------------\n\nPermits administration of master servers, including the SHOW SLAVE HOSTS\nstatement, and setting the gtid_binlog_state, gtid_domain_id,\nmaster_verify_checksum and server_id system variables. Added in MariaDB 10.5.2.\n\nSLAVE MONITOR\n---------------\n\nPermit SHOW SLAVE STATUS and SHOW RELAYLOG EVENTS. From MariaDB 10.5.9.\n\nWhen a user would upgrade from an older major release to a MariaDB 10.5 minor\nrelease prior to MariaDB 10.5.9, certain user accounts would lose\ncapabilities. For example, a user account that had the REPLICATION CLIENT\nprivilege in older major releases could run SHOW SLAVE STATUS, but after\nupgrading to a MariaDB 10.5 minor release prior to MariaDB 10.5.9, they could\nno longer run SHOW SLAVE STATUS, because that statement was changed to\nrequire the REPLICATION SLAVE ADMIN privilege.\n\nThis issue is fixed in MariaDB 10.5.9 with this new privilege, which now\ngrants the user the ability to execute SHOW [ALL] (SLAVE | REPLICA) STATUS.\n\nWhen a database is upgraded from an older major release to MariaDB Server\n10.5.9 or later, any user accounts with the REPLICATION CLIENT or REPLICATION\nSLAVE privileges will automatically be granted the new SLAVE MONITOR\nprivilege. The privilege fix occurs when the server is started up, not when\nmariadb-upgrade is performed.\n\nHowever, when a database is upgraded from an early 10.5 minor release to\n10.5.9 and later, the user will have to fix any user account privileges\nmanually.\n\nREPLICATION REPLICA\n-------------------\n\nSynonym for REPLICATION SLAVE. From MariaDB 10.5.1.\n\nREPLICATION SLAVE\n-----------------\n\nAccounts used by slave servers on the master need this privilege. This is\nneeded to get the updates made on the master. From MariaDB 10.5.1, REPLICATION\nREPLICA is an alias for REPLICATION SLAVE.\n\nREPLICATION SLAVE ADMIN\n-----------------------\n\nPermits administering slave servers, including START SLAVE/REPLICA, STOP\SLAVE/nREPLICA, CHANGE MASTER, SHOW SLAVE/REPLICA STATUS, SHOW RELAYLOG EVENTS\nstatements, replaying the binary log with the BINLOG statement (generated by\nmariadb-binlog), and setting the system variables:\n\n* gtid_cleanup_batch_size\n* gtid_ignore_duplicates\n* gtid_pos_auto_engines\n* gtid_slave_pos\n* gtid_strict_mode\n* init_slave\n* read_binlog_speed_limit\n* relay_log_purge\n* relay_log_recovery\n* replicate_do_db\n* replicate_do_table\n* replicate_events_marked_for_skip\n* replicate_ignore_db\n* replicate_ignore_table\n* replicate_wild_do_table\n* replicate_wild_ignore_table\n* slave_compressed_protocol\n* slave_ddl_exec_mode\n* slave_domain_parallel_threads\n* slave_exec_mode\n* slave_max_allowed_packet\n* slave_net_timeout\n* slave_parallel_max_queued\n* slave_parallel_mode\n* slave_parallel_threads\n* slave_parallel_workers\n* slave_run_triggers_for_rbr\n* slave_sql_verify_checksum\n* slave_transaction_retry_interval\n* slave_type_conversions\n* sync_master_info','','https://mariadb.com/docs/server/reference/sql-statements/account-management-sql-statements/grant'); update help_topic set description = CONCAT(description, '\n* sync_relay_log, and\n* sync_relay_log_info.\n\nAdded in MariaDB 10.5.2.\n\nSET USER\n--------\n\nEnables setting the DEFINER when creating triggers, views, stored functions\nand stored procedures. Added in MariaDB 10.5.2.\n\nSHOW DATABASES\n--------------\n\nList all databases using the SHOW DATABASES statement. Without the SHOW\nDATABASES privilege, you can still issue the SHOW DATABASES statement, but it\nwill only list databases containing tables on which you have privileges.\n\nSHUTDOWN\n--------\n\nShut down the server using SHUTDOWN or the mariadb-admin shutdown command.\n\nSUPER\n-----\n\nExecute superuser statements: CHANGE MASTER TO, KILL (users who do not have\nthis privilege can only KILL their own threads), PURGE LOGS, SET global system\nvariables, or the mariadb-admin debug command. Also, this permission allows\nthe user to write data even if the read_only startup option is set, enable or\ndisable logging, enable or disable replication on slave, specify a DEFINER\nfor statements that support that clause, connect once reaching the\nMAX_CONNECTIONS. If a statement has been specified for the init-connect mysqld\noption, that command will not be executed when a user with SUPER privileges\nconnects to the server.\n\nThe SUPER privilege has been split into multiple smaller privileges from\nMariaDB 10.5.2 to allow for more fine-grained privileges (MDEV-21743). The\nprivileges are:\n\n* SET USER\n* FEDERATED ADMIN\n* CONNECTION ADMIN\n* REPLICATION SLAVE ADMIN\n* BINLOG ADMIN\n* BINLOG REPLAY\n* SLAVE MONITOR\n* BINLOG MONITOR\n* REPLICATION MASTER ADMIN\n* READ_ONLY ADMIN\n\nHowever, the smaller privileges are still a part of the SUPER grant in MariaDB\n10.5.2. From MariaDB 11.0.1 onwards, these grants are no longer a part of\nSUPER and need to be granted separately (MDEV-29668).\n\nFrom MariaDB 10.11.0, the READ_ONLY ADMIN privilege has been removed from\nSUPER. The benefit of this is that one can remove the READ_ONLY ADMIN\nprivilege from all users and ensure that no one can make any changes on any\nnon-temporary tables. This is useful on slaves when one wants to ensure that\nthe slave is kept identical to the master (MDEV-29596).\n\nDatabase Privileges\n-------------------\n\nThe following table lists the privileges that can be granted at the database\nlevel. You can also grant all table and function privileges at the database\nlevel. Table and function privileges on a database apply to all tables or\nfunctions in that database, including those created later.\n\nTo set a privilege for a database, specify the database using db_name.* for\npriv_level, or just use * to specify the default database.\n\n+----------------------------------+-----------------------------------------+\n| Privilege | Description |\n+----------------------------------+-----------------------------------------+\n| CREATE | Create a database using the CREATE |\n| | DATABASE statement, when the privilege |\n| | is granted for a database. You can |\n| | grant the CREATE privilege on |\n| | databases that do not yet exist. This |\n| | also grants the CREATE privilege on |\n| | all tables in the database. |\n+----------------------------------+-----------------------------------------+\n| CREATE ROUTINE | Create Stored Programs using the |\n| | CREATE PROCEDURE and CREATE FUNCTION |\n| | statements. |\n+----------------------------------+-----------------------------------------+\n| CREATE TEMPORARY TABLES | Create temporary tables with the |\n| | CREATE TEMPORARY TABLE statement. This |\n| | privilege enable writing and dropping |\n| | those temporary tables |\n+----------------------------------+-----------------------------------------+\n| DROP | Drop a database using the DROP |\n| | DATABASE statement, when the privilege |\n| | is granted for a database. This also |\n| | grants the DROP privilege on all |\n| | tables in the database. |\n+----------------------------------+-----------------------------------------+\n| EVENT | Create, drop and alter EVENTs. |\n+----------------------------------+-----------------------------------------+\n| GRANT OPTION | Grant database privileges. You can |\n| | only grant privileges that you have. |\n+----------------------------------+-----------------------------------------+\n| LOCK TABLES | Acquire explicit locks using the LOCK |\n| | TABLES statement; you also need to |\n| | have the SELECT privilege on a table, |\n| | in order to lock it. |\n+----------------------------------+-----------------------------------------+\n| SHOW CREATE ROUTINE | Permit viewing the SHOW CREATE |\n| | definition statement of a routine, for |\n| | example SHOW CREATE FUNCTION, even if |\n| | not the routine owner. From MariaDB |\n| | 11.3.0. |\n+----------------------------------+-----------------------------------------+\n\nTable Privileges\n----------------\n\n+----------------------------------+-----------------------------------------+\n| Privilege | Description |\n+----------------------------------+-----------------------------------------+\n| ALTER | Change the structure of an existing |\n| | table using the ALTER TABLE statement. |\n+----------------------------------+-----------------------------------------+\n| CREATE | Create a table using the CREATE TABLE |\n| | statement. You can grant the CREATE |\n| | privilege on tables that do not yet |\n| | exist. |\n+----------------------------------+-----------------------------------------+\n| CREATE VIEW | Create a view using the CREATE_VIEW |\n| | statement. |\n+----------------------------------+-----------------------------------------+\n| DELETE | Remove rows from a table using the |\n| | DELETE statement. |\n+----------------------------------+-----------------------------------------+\n| DELETE HISTORY | Remove historical rows from a table |\n| | using the DELETE HISTORY statement. |\n| | Displays as DELETE VERSIONING ROWS |\n| | when running SHOW GRANTS until MariaDB |\n| | 10.3.15 and until MariaDB 10.4.5 |\n| | (MDEV-17655), or when running SHOW |\n| | PRIVILEGES until MariaDB 10.5.2, |\n| | MariaDB 10.4.13 and MariaDB 10.3.23 |\n| | (MDEV-20382). From MariaDB 10.3.4. |\n| | From MariaDB 10.3.5, if a user has the |\n| | SUPER privilege but not this |\n| | privilege, running mariadb-upgrade |\n| | will grant this privilege as well. |\n+----------------------------------+-----------------------------------------+\n| DROP | Drop a table using the DROP TABLE |\n| | statement or a view using the DROP |\n| | VIEW statement. Also required to |\n| | execute the TRUNCATE TABLE statement. |\n+----------------------------------+-----------------------------------------+\n| GRANT OPTION | Grant table privileges. You can only |\n| | grant privileges that you have. |\n+----------------------------------+-----------------------------------------+\n| INDEX | Create an index on a table using the |\n| | CREATE INDEX statement. Without the |\n| | INDEX privilege, you can still create |\n| | indexes when creating a table using |\n| | the CREATE TABLE statement if the you |\n| | have the CREATE privilege, and you can |\n| | create indexes using the ALTER TABLE |\n| | statement if you have the ALTER |\n| | privilege. |\n+----------------------------------+-----------------------------------------+\n| INSERT | Add rows to a table using the INSERT |\n| | statement. The INSERT privilege can |\n| | also be set on individual columns; see |\n| | Column Privileges below for details. |\n+----------------------------------+-----------------------------------------+\n| REFERENCES | Unused. |\n+----------------------------------+-----------------------------------------+\n| SELECT | Read data from a table using the |\n| | SELECT statement. The SELECT privilege |\n| | can also be set on individual columns; |\n| | see Column Privileges below for |\n| | details. |\n+----------------------------------+-----------------------------------------+\n| SHOW VIEW | Show the CREATE VIEW statement to |\n| | create a view using the SHOW CREATE |\n| | VIEW statement. |\n+----------------------------------+-----------------------------------------+\n| TRIGGER | Required to run the CREATE TRIGGER, |\n| | DROP TRIGGER, and SHOW CREATE TRIGGER |\n| | statements. When another user |\n| | activates a trigger (running INSERT, |\n| | UPDATE, or DELETE statements on the |\n| | associated table), for the trigger to |\n| | execute, the user that defined the |\n| | trigger should have the TRIGGER |\n| | privilege for the table. The user |\n| | running the INSERT, UPDATE, or DELETE |\n| | statements on the table is not |\n| | required to have the TRIGGER privilege. |\n+----------------------------------+-----------------------------------------+\n| UPDATE | Update existing rows in a table using |\n| | the UPDATE statement. UPDATE |\n| | statements usually include a WHERE |\n| | clause to update only certain rows. |\n| | You must have SELECT privileges on the |\n| | table or the appropriate columns for |\n| | the WHERE clause. The UPDATE privilege |\n| | can also be set on individual columns; |\n| | see Column Privileges below for |\n| | details. |\n+----------------------------------+-----------------------------------------+\n\nColumn Privileges\n-----------------\n\nSome table privileges can be set for individual columns of a table. To use\ncolumn privileges, specify the table explicitly and provide a list of column\nnames after the privilege type. For example, the following statement would\nallow the user to read the names and positions of employees, but not other\ninformation from the same table, such as salaries.\n\nGRANT SELECT (name, position) on Employee to \'jeffrey\'@\'localhost\';\n\n+----------------------------------+-----------------------------------------+\n| Privilege | Description |\n+----------------------------------+-----------------------------------------+\n| INSERT (column_list) | Add rows specifying values in columns |\n| | using the INSERT statement. If you |\n| | only have column-level INSERT |\n| | privileges, you must specify the |\n| | columns you are setting in the INSERT |\n| | statement. All other columns will be |\n| | set to their default values, or NULL. |') WHERE help_topic_id = 108; update help_topic set description = CONCAT(description, '\n+----------------------------------+-----------------------------------------+\n| REFERENCES (column_list) | Unused. |\n+----------------------------------+-----------------------------------------+\n| SELECT (column_list) | Read values in columns using the |\n| | SELECT statement. You cannot access or |\n| | query any columns for which you do not |\n| | have SELECT privileges, including in |\n| | WHERE, ON, GROUP BY, and ORDER BY |\n| | clauses. |\n+----------------------------------+-----------------------------------------+\n| UPDATE (column_list) | Update values in columns of existing |\n| | rows using the UPDATE statement. |\n| | UPDATE statements usually include a |\n| | WHERE clause to update only certain |\n| | rows. You must have SELECT privileges |\n| | on the table or the appropriate |\n| | columns for the WHERE clause. |\n+----------------------------------+-----------------------------------------+\n\nFunction Privileges\n-------------------\n\n+----------------------------------+-----------------------------------------+\n| Privilege | Description |\n+----------------------------------+-----------------------------------------+\n| ALTER ROUTINE | Change the characteristics of a stored |\n| | function using the ALTER FUNCTION |\n| | statement. |\n+----------------------------------+-----------------------------------------+\n| EXECUTE | Use a stored function. You need SELECT |\n| | privileges for any tables or columns |\n| | accessed by the function. |\n+----------------------------------+-----------------------------------------+\n| GRANT OPTION | Grant function privileges. You can |\n| | only grant privileges that you have. |\n+----------------------------------+-----------------------------------------+\n\nProcedure Privileges\n--------------------\n\n+----------------------------------+-----------------------------------------+\n| Privilege | Description |\n+----------------------------------+-----------------------------------------+\n| ALTER ROUTINE | Change the characteristics of a stored |\n| | procedure using the ALTER PROCEDURE |\n| | statement. |\n+----------------------------------+-----------------------------------------+\n| EXECUTE | Execute a stored procedure using the |\n| | CALL statement. The privilege to call |\n| | a procedure may allow you to perform |\n| | actions you wouldn\'t otherwise be able |\n| | to do, such as insert rows into a |\n| | table. |\n+----------------------------------+-----------------------------------------+\n| GRANT OPTION | Grant procedure privileges. You can |\n| | only grant privileges that you have. |\n+----------------------------------+-----------------------------------------+\n\nGRANT EXECUTE ON PROCEDURE mysql.create_db TO maintainer;\n\nProxy Privileges\n----------------\n\n+----------------------------------+-----------------------------------------+\n| Privilege | Description |\n+----------------------------------+-----------------------------------------+\n| PROXY | Permits one user to be a proxy for |\n| | another. |\n+----------------------------------+-----------------------------------------+\n\nThe PROXY privilege allows one user to proxy as another user, which means\ntheir privileges change to that of the proxy user, and the CURRENT_USER()\nfunction returns the user name of the proxy user.\n\nThe PROXY privilege only works with authentication plugins that support it.\nThe default mysql_native_password authentication plugin does not support proxy\nusers.\n\nThe pam authentication plugin is the only plugin included with MariaDB that\ncurrently supports proxy users. The PROXY privilege is commonly used with the\npam authentication plugin to enable user and group mapping with PAM.\n\nFor example, to grant the PROXY privilege to an anonymous account that\nauthenticates with the pam authentication plugin, you could execute the\nfollowing:\n\nCREATE USER \'dba\'@\'%\' IDENTIFIED BY \'strongpassword\';\nGRANT ALL PRIVILEGES ON *.* TO \'dba\'@\'%\' ;\n\nCREATE USER \'\'@\'%\' IDENTIFIED VIA pam USING \'mariadb\';\nGRANT PROXY ON \'dba\'@\'%\' TO \'\'@\'%\';\n\nA user account can only grant the PROXY privilege for a specific user account\nif the granter also has the PROXY privilege for that specific user account,\nand if that privilege is defined WITH GRANT OPTION. For example, the following\nexample fails because the granter does not have the PROXY privilege for that\nspecific user account at all:\n\nSELECT USER(), CURRENT_USER();\n+-----------------+-----------------+\n| USER() | CURRENT_USER() |\n+-----------------+-----------------+\n| alice@localhost | alice@localhost |\n+-----------------+-----------------+\n\nSHOW GRANTS;\n+------------------------------------------------------------------------------\n----------------------------------------+\n| Grants for alice@localhost \n |\n+------------------------------------------------------------------------------\n----------------------------------------+\n| GRANT ALL PRIVILEGES ON *.* TO \'alice\'@\'localhost\' IDENTIFIED BY PASSWORD\n\'*2470C0C06DEE42FD1618BB99005ADCA2EC9D1E19\' |\n+------------------------------------------------------------------------------\n----------------------------------------+\n\nGRANT PROXY ON \'dba\'@\'localhost\' TO \'bob\'@\'localhost\';\nERROR 1698 (28000): Access denied for user \'alice\'@\'localhost\'\n\nAnd the following example fails because the granter does have the PROXY\nprivilege for that specific user account, but it is not defined WITH GRANT\nOPTION:\n\nSELECT USER(), CURRENT_USER();\n+-----------------+-----------------+\n| USER() | CURRENT_USER() |\n+-----------------+-----------------+\n| alice@localhost | alice@localhost |\n+-----------------+-----------------+\n\nSHOW GRANTS;\n+------------------------------------------------------------------------------\n----------------------------------------+\n| Grants for alice@localhost \n |\n+------------------------------------------------------------------------------\n----------------------------------------+\n| GRANT ALL PRIVILEGES ON *.* TO \'alice\'@\'localhost\' IDENTIFIED BY PASSWORD\n\'*2470C0C06DEE42FD1618BB99005ADCA2EC9D1E19\' |\n| GRANT PROXY ON \'dba\'@\'localhost\' TO \'alice\'@\'localhost\' \n |\n+------------------------------------------------------------------------------\n----------------------------------------+\n\nGRANT PROXY ON \'dba\'@\'localhost\' TO \'bob\'@\'localhost\';\nERROR 1698 (28000): Access denied for user \'alice\'@\'localhost\'\n\nBut the following example succeeds because the granter does have the PROXY\nprivilege for that specific user account, and it is defined WITH GRANT OPTION:\n\nSELECT USER(), CURRENT_USER();\n+-----------------+-----------------+\n| USER() | CURRENT_USER() |\n+-----------------+-----------------+\n| alice@localhost | alice@localhost |\n+-----------------+-----------------+\n\nSHOW GRANTS;\n+------------------------------------------------------------------------------\n----------------------------------------------------------+\n| Grants for alice@localhost \n |\n+------------------------------------------------------------------------------\n----------------------------------------------------------+\n| GRANT ALL PRIVILEGES ON *.* TO \'alice\'@\'localhost\' IDENTIFIED BY PASSWORD\n\'*2470C0C06DEE42FD1618BB99005ADCA2EC9D1E19\' WITH GRANT OPTION |\n| GRANT PROXY ON \'dba\'@\'localhost\' TO \'alice\'@\'localhost\' WITH GRANT OPTION \n |\n+------------------------------------------------------------------------------\n----------------------------------------------------------+\n\nGRANT PROXY ON \'dba\'@\'localhost\' TO \'bob\'@\'localhost\';\n\nA user account can grant the PROXY privilege for any other user account if the\ngranter has the PROXY privilege for the \'\'@\'%\' anonymous user account, like\nthis:\n\nGRANT PROXY ON \'\'@\'%\' TO \'dba\'@\'localhost\' WITH GRANT OPTION;\n\nFor example, the following example succeeds because the user can grant the\nPROXY privilege for any other user account:\n\nSELECT USER(), CURRENT_USER();\n+-----------------+-----------------+\n| USER() | CURRENT_USER() |\n+-----------------+-----------------+\n| alice@localhost | alice@localhost |\n+-----------------+-----------------+\n\nSHOW GRANTS;\n+------------------------------------------------------------------------------\n----------------------------------------------------------+\n| Grants for alice@localhost \n |\n+------------------------------------------------------------------------------\n----------------------------------------------------------+\n| GRANT ALL PRIVILEGES ON *.* TO \'alice\'@\'localhost\' IDENTIFIED BY PASSWORD\n\'*2470C0C06DEE42FD1618BB99005ADCA2EC9D1E19\' WITH GRANT OPTION |\n| GRANT PROXY ON \'\'@\'%\' TO \'alice\'@\'localhost\' WITH GRANT OPTION \n |\n+------------------------------------------------------------------------------\n----------------------------------------------------------+\n\nGRANT PROXY ON \'app1_dba\'@\'localhost\' TO \'bob\'@\'localhost\';\nQuery OK, 0 rows affected (0.004 sec)\n\nGRANT PROXY ON \'app2_dba\'@\'localhost\' TO \'carol\'@\'localhost\';\nQuery OK, 0 rows affected (0.004 sec)\n\nThe default root user accounts created by mariadb-install-db have this\nprivilege. For example:\n\nGRANT ALL PRIVILEGES ON *.* TO \'root\'@\'localhost\' WITH GRANT OPTION;\nGRANT PROXY ON \'\'@\'%\' TO \'root\'@\'localhost\' WITH GRANT OPTION;\n\nThis allows the default root user accounts to grant the PROXY privilege for\nany other user account, and it also allows the default root user accounts to\ngrant others the privilege to do the same.\n\nAuthentication Options\n----------------------\n\nThe authentication options for the GRANT statement are the same as those for\nthe CREATE USER statement.\n\nIDENTIFIED BY \'password\'\n------------------------\n\nThe optional IDENTIFIED BY clause can be used to provide an account with a\npassword. The password should be specified in plain text. It will be hashed by\nthe PASSWORD function prior to being stored.\n\nFor example, if our password is mariadb, then we can create the user with:\n\nGRANT USAGE ON *.* TO foo2@test IDENTIFIED BY \'mariadb\';\n\nIf you do not specify a password with the IDENTIFIED BY clause, the user will\nbe able to connect without a password. A blank password is not a wildcard to\nmatch any password. The user must connect without providing a password if no\npassword is set.\n\nIf the user account already exists and if you provide the IDENTIFIED BY\nclause, then the user\'s password will be changed. You must have the privileges\nneeded for the SET PASSWORD statement to change a user\'s password with GRANT.\n\nThe only authentication plugins that this clause supports are\nmysql_native_password and mysql_old_password.\n\nIDENTIFIED BY PASSWORD \'password_hash\'\n--------------------------------------\n\nThe optional IDENTIFIED BY PASSWORD clause can be used to provide an account\nwith a password that has already been hashed. The password should be specified\nas a hash that was provided by the PASSWORD function. It will be stored as-is.\n\nFor example, if our password is mariadb, then we can find the hash with:\n\nSELECT PASSWORD(\'mariadb\');\n+-------------------------------------------+\n| PASSWORD(\'mariadb\') |\n+-------------------------------------------+\n| *54958E764CE10E50764C2EECBB71D01F08549980 |\n+-------------------------------------------+\n1 row in set (0.00 sec)\n\nAnd then we can create a user with the hash:\n\nGRANT USAGE ON *.* TO foo2@test IDENTIFIED BY \n PASSWORD \'*54958E764CE10E50764C2EECBB71D01F08549980\';\n\nIf you do not specify a password with the IDENTIFIED BY clause, the user will\nbe able to connect without a password. A blank password is not a wildcard to\nmatch any password. The user must connect without providing a password if no\npassword is set.\n\nIf the user account already exists and if you provide the IDENTIFIED BY\nclause, then the user\'s password will be changed. You must have the privileges\nneeded for the SET PASSWORD statement to change a user\'s password with GRANT.\n\nThe only authentication plugins that this clause supports are\nmysql_native_password and mysql_old_password.\n\nIDENTIFIED {VIA|WITH} authentication_plugin\n-------------------------------------------\n\nThe optional IDENTIFIED VIA authentication_plugin allows you to specify that\nthe account should be authenticated by a specific authentication plugin. The\nplugin name must be an active authentication plugin as per SHOW PLUGINS. If it\ndoesn\'t show up in that output, then you will need to install it with INSTALL\nPLUGIN or INSTALL SONAME.\n') WHERE help_topic_id = 108; -update help_topic set description = CONCAT(description, '\nFor example, this could be used with the PAM authentication plugin:\n\nGRANT USAGE ON *.* TO foo2@test IDENTIFIED VIA pam;\n\nSome authentication plugins allow additional arguments to be specified after a\nUSING or AS keyword. For example, the PAM authentication plugin accepts a\nservice name:\n\nGRANT USAGE ON *.* TO foo2@test IDENTIFIED VIA pam USING \'mariadb\';\n\nThe exact meaning of the additional argument would depend on the specific\nauthentication plugin.\n\nMariaDB starting with 10.4.0\n----------------------------\nThe USING or AS keyword can also be used to provide a plain-text password to a\nplugin if it\'s provided as an argument to the PASSWORD() function. This is\nonly valid for authentication plugins that have implemented a hook for the\nPASSWORD() function. For example, the ed25519 authentication plugin supports\nthis:\n\nCREATE USER safe@\'%\' IDENTIFIED VIA ed25519 \n USING PASSWORD(\'secret\');\n\nMariaDB starting with 10.4.3\n----------------------------\nOne can specify many authentication plugins, they all work as alternatives\nways of authenticating a user:\n\nCREATE USER safe@\'%\' IDENTIFIED VIA ed25519 \n USING PASSWORD(\'secret\') OR unix_socket;\n\nBy default, when you create a user without specifying an authentication\nplugin, MariaDB uses the mysql_native_password plugin.\n\nResource Limit Options\n----------------------\n\nIt is possible to set per-account limits for certain server resources. The\nfollowing table shows the values that can be set per account:\n\n+--------------------------------------+--------------------------------------+\n| Limit Type | Decription |\n+--------------------------------------+--------------------------------------+\n| MAX_QUERIES_PER_HOUR | Number of statements that the |\n| | account can issue per hour |\n| | (including updates) |\n+--------------------------------------+--------------------------------------+\n| MAX_UPDATES_PER_HOUR | Number of updates (not queries) |\n| | that the account can issue per hour |\n+--------------------------------------+--------------------------------------+\n| MAX_CONNECTIONS_PER_HOUR | Number of connections that the |\n| | account can start per hour |\n+--------------------------------------+--------------------------------------+\n| MAX_USER_CONNECTIONS | Number of simultaneous connections |\n| | that can be accepted from the same |\n| | account; if it is 0, |\n| | max_connections will be used |\n| | instead; if max_connections is 0, |\n| | there is no limit for this |\n| | account\'s simultaneous connections. |\n+--------------------------------------+--------------------------------------+\n| MAX_STATEMENT_TIME | Timeout, in seconds, for statements |\n| | executed by the user. See also |\n| | Aborting Statements that Exceed a |\n| | Certain Time to Execute. |\n+--------------------------------------+--------------------------------------+\n\nIf any of these limits are set to 0, then there is no limit for that resource\nfor that user.\n\nTo set resource limits for an account, if you do not want to change that\naccount\'s privileges, you can issue a GRANT statement with the USAGE\nprivilege, which has no meaning. The statement can name some or all limit\ntypes, in any order.\n\nHere is an example showing how to set resource limits:\n\nGRANT USAGE ON *.* TO \'someone\'@\'localhost\' WITH\n MAX_USER_CONNECTIONS 0\n MAX_QUERIES_PER_HOUR 200;\n\nThe resources are tracked per account, which means \'user\'@\'server\'; not per\nuser name or per connection.\n\nThe count can be reset for all users using FLUSH USER_RESOURCES, FLUSH\nPRIVILEGES or mariadb-admin reload.\n\nUsers with the CONNECTION ADMIN privilege (in MariaDB 10.5.2 and later) or the\nSUPER privilege are not restricted by max_user_connections, max_connections,\nor max_password_errors.\n\nPer account resource limits are stored in the user table, in the mysql\ndatabase. Columns used for resources limits are named max_questions,\nmax_updates, max_connections (for MAX_CONNECTIONS_PER_HOUR), and\nmax_user_connections (for MAX_USER_CONNECTIONS).\n\nTLS Options\n-----------\n\nBy default, MariaDB transmits data between the server and clients without\nencrypting it. This is generally acceptable when the server and client run on\nthe same host or in networks where security is guaranteed through other means.\nHowever, in cases where the server and client exist on separate networks or\nthey are in a high-risk network, the lack of encryption does introduce\nsecurity concerns as a malicious actor could potentially eavesdrop on the\ntraffic as it is sent over the network between them.\n\nTo mitigate this concern, MariaDB allows you to encrypt data in transit\nbetween the server and clients using the Transport Layer Security (TLS)\nprotocol. TLS was formerly known as Secure Socket Layer (SSL), but strictly\nspeaking the SSL protocol is a predecessor to TLS and, that version of the\nprotocol is now considered insecure. The documentation still uses the term SSL\noften and for compatibility reasons TLS-related server system and status\nvariables still use the prefix ssl_, but internally, MariaDB only supports its\nsecure successors.\n\nSee Secure Connections Overview for more information about how to determine\nwhether your MariaDB server has TLS support.\n\nYou can set certain TLS-related restrictions for specific user accounts. For\ninstance, you might use this with user accounts that require access to\nsensitive data while sending it across networks that you do not control. These\nrestrictions can be enabled for a user account with the CREATE USER, ALTER\nUSER, or GRANT statements. The following options are available:\n\n+---------------------------+------------------------------------------------+\n| Option | Description |\n+---------------------------+------------------------------------------------+\n| REQUIRE NONE | TLS is not required for this account, but can |\n| | still be used. |\n+---------------------------+------------------------------------------------+\n| REQUIRE SSL | The account must use TLS, but no valid X509 |\n| | certificate is required. This option cannot |\n| | be combined with other TLS options. |\n+---------------------------+------------------------------------------------+\n| REQUIRE X509 | The account must use TLS and must have a |\n| | valid X509 certificate. This option implies |\n| | REQUIRE SSL. This option cannot be combined |\n| | with other TLS options. |\n+---------------------------+------------------------------------------------+\n| REQUIRE ISSUER \'issuer\' | The account must use TLS and must have a |\n| | valid X509 certificate. Also, the Certificate |\n| | Authority must be the one specified via the |\n| | string issuer. This option implies REQUIRE |\n| | X509. This option can be combined with the |\n| | SUBJECT, and CIPHER options in any order. |\n+---------------------------+------------------------------------------------+\n| REQUIRE SUBJECT \'subject\' | The account must use TLS and must have a |\n| | valid X509 certificate. Also, the |\n| | certificate\'s Subject must be the one |\n| | specified via the string subject. This option |\n| | implies REQUIRE X509. This option can be |\n| | combined with the ISSUER, and CIPHER options |\n| | in any order. |\n+---------------------------+------------------------------------------------+\n| REQUIRE CIPHER \'cipher\' | The account must use TLS, but no valid X509 |\n| | certificate is required. Also, the encryption |\n| | used for the connection must use a specific |\n| | cipher method specified in the string cipher. |\n| | This option implies REQUIRE SSL. This option |\n| | can be combined with the ISSUER, and SUBJECT |\n| | options in any order. |\n+---------------------------+------------------------------------------------+\n\nThe REQUIRE keyword must be used only once for all specified options, and the\nAND keyword can be used to separate individual options, but it is not required.\n\nFor example, you can create a user account that requires these TLS options\nwith the following:\n\nGRANT USAGE ON *.* TO \'alice\'@\'%\'\n REQUIRE SUBJECT \'/CN=alice/O=My Dom, Inc./C=US/ST=Oregon/L=Portland\'\n AND ISSUER \'/C=FI/ST=Somewhere/L=City/ O=Some Company/CN=Peter\nParker/emailAddress=p.parker@marvel.com\'\n AND CIPHER \'SHA-DES-CBC3-EDH-RSA\';\n\nIf any of these options are set for a specific user account, then any client\nwho tries to connect with that user account will have to be configured to\nconnect with TLS.\n\nSee Securing Connections for Client and Server for information on how to\nenable TLS on the client and server.\n\nRoles\n-----\n\nSyntax\n------\n\nGRANT role TO grantee [, grantee ... ]\n[ WITH ADMIN OPTION ]\n\ngrantee:\n rolename\n username [authentication_option]\n\nThe GRANT statement is also used to grant the use of a role to one or more\nusers or other roles. In order to be able to grant a role, the grantor doing\nso must have permission to do so (see WITH ADMIN in the CREATE ROLE article).\n\nSpecifying the WITH ADMIN OPTION permits the grantee to in turn grant the role\nto another.\n\nFor example, the following commands show how to grant the same role to a\ncouple different users.\n\nGRANT journalist TO hulda;\n\nGRANT journalist TO berengar WITH ADMIN OPTION;\n\nIf a user has been granted a role, they do not automatically obtain all\npermissions associated with that role. These permissions are only in use when\nthe user activates the role with the SET ROLE statement.\n\nTO PUBLIC\n---------\n\nMariaDB starting with 10.11\n---------------------------\n\nSyntax\n------\n\nGRANT ON . TO PUBLIC;\nREVOKE ON . FROM PUBLIC;\n\nGRANT ... TO PUBLIC grants privileges to all users with access to the server.\nThe privileges also apply to users created after the privileges are granted.\nThis can be useful when one only wants to state once that all users need to\nhave a certain set of privileges.\n\nWhen running SHOW GRANTS, a user will also see all privileges inherited from\nPUBLIC. SHOW GRANTS FOR PUBLIC will only show TO PUBLIC grants.\n\nGrant Examples\n--------------\n\nGranting Root-like Privileges\n-----------------------------\n\nYou can create a user that has privileges similar to the default root accounts\nby executing the following:\n\nCREATE USER \'alexander\'@\'localhost\';\nGRANT ALL PRIVILEGES ON *.* to \'alexander\'@\'localhost\' WITH GRANT OPTION;\n\nURL: https://mariadb.com/kb/en/grant/') WHERE help_topic_id = 108; -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (109,10,'RENAME USER','Syntax\n------\n\nRENAME USER old_user TO new_user\n [, old_user TO new_user] ...\n\nDescription\n-----------\n\nThe RENAME USER statement renames existing MariaDB accounts. To use it, you\nmust have the global CREATE USER privilege or the UPDATE privilege for the\nmysql database. Each account is named using the same format as for the CREATE\nUSER statement; for example, \'jeffrey\'@\'localhost\'. If you specify only the\nuser name part of the account name, a host name part of \'%\' is used.\n\nIf any of the old user accounts do not exist or any of the new user accounts\nalready exist, ERROR 1396 (HY000) results. If an error occurs, RENAME USER\nwill still rename the accounts that do not result in an error.\n\nFor modifying an existing account, see ALTER USER.\n\nExamples\n--------\n\nCREATE USER \'donald\', \'mickey\';\nRENAME USER \'donald\' TO \'duck\'@\'localhost\', \'mickey\' TO \'mouse\'@\'localhost\';\n\nRenaming the host component of a user\n\nRENAME USER \'foo\'@\'1.2.3.4\' TO \'foo\'@\'10.20.30.40\';\n\nURL: https://mariadb.com/kb/en/rename-user/','','https://mariadb.com/kb/en/rename-user/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (110,10,'REVOKE','Privileges\n----------\n\nSyntax\n------\n\nREVOKE \n priv_type [(column_list)]\n [, priv_type [(column_list)]] ...\n ON [object_type] priv_level\n FROM user [, user] ...\n\nREVOKE ALL PRIVILEGES, GRANT OPTION\n FROM user [, user] ...\n\nDescription\n-----------\n\nThe REVOKE statement enables system administrators to revoke privileges (or\nroles - see section below) from MariaDB accounts. Each account is named using\nthe same format as for the GRANT statement; for example,\n\'jeffrey\'@\'localhost\'. If you specify only the user name part of the account\nname, a host name part of \'%\' is used. For details on the levels at which\nprivileges exist, the allowable priv_type and priv_level values, and the\nsyntax for specifying users and passwords, see GRANT.\n\nTo use the first REVOKE syntax, you must have the GRANT OPTION privilege, and\nyou must have the privileges that you are revoking.\n\nTo revoke all privileges, use the second syntax, which drops all global,\ndatabase, table, column, and routine privileges for the named user or users:\n\nREVOKE ALL PRIVILEGES, GRANT OPTION FROM user [, user] ...\n\nTo use this REVOKE syntax, you must have the global CREATE USER privilege or\nthe UPDATE privilege for the mysql database. See GRANT.\n\nExamples\n--------\n\nREVOKE SUPER ON *.* FROM \'alexander\'@\'localhost\';\n\nRoles\n-----\n\nSyntax\n------\n\nREVOKE role [, role ...]\n FROM grantee [, grantee2 ... ]\n\nREVOKE ADMIN OPTION FOR role FROM grantee [, grantee2]\n\nDescription\n-----------\n\nREVOKE is also used to remove a role from a user or another role that it\'s\npreviously been assigned to. If a role has previously been set as a default\nrole, REVOKE does not remove the record of the default role from the\nmysql.user table. If the role is subsequently granted again, it will again be\nthe user\'s default. Use SET DEFAULT ROLE NONE to explicitly remove this.\n\nBefore MariaDB 10.1.13, the REVOKE role statement was not permitted in\nprepared statements.\n\nExample\n-------\n\nREVOKE journalist FROM hulda\n\nURL: https://mariadb.com/kb/en/revoke/','','https://mariadb.com/kb/en/revoke/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (111,10,'SET PASSWORD','Syntax\n------\n\nSET PASSWORD [FOR user] =\n {\n PASSWORD(\'some password\')\n | OLD_PASSWORD(\'some password\')\n | \'encrypted password\'\n }\n\nDescription\n-----------\n\nThe SET PASSWORD statement assigns a password to an existing MariaDB user\naccount.\n\nIf the password is specified using the PASSWORD() or OLD_PASSWORD() function,\nthe literal text of the password should be given. If the password is specified\nwithout using either function, the password should be the already-encrypted\npassword value as returned by PASSWORD().\n\nOLD_PASSWORD() should only be used if your MariaDB/MySQL clients are very old\n(< 4.0.0).\n\nWith no FOR clause, this statement sets the password for the current user. Any\nclient that has connected to the server using a non-anonymous account can\nchange the password for that account.\n\nWith a FOR clause, this statement sets the password for a specific account on\nthe current server host. Only clients that have the UPDATE privilege for the\nmysql database can do this. The user value should be given in\nuser_name@host_name format, where user_name and host_name are exactly as they\nare listed in the User and Host columns of the mysql.user table (or view in\nMariaDB-10.4 onwards) entry.\n\nThe argument to PASSWORD() and the password given to MariaDB clients can be of\narbitrary length.\n\nAuthentication Plugin Support\n-----------------------------\n\nMariaDB starting with 10.4\n--------------------------\nIn MariaDB 10.4 and later, SET PASSWORD (with or without PASSWORD()) works for\naccounts authenticated via any authentication plugin that supports passwords\nstored in the mysql.global_priv table.\n\nThe ed25519, mysql_native_password, and mysql_old_password authentication\nplugins store passwords in the mysql.global_priv table.\n\nIf you run SET PASSWORD on an account that authenticates with one of these\nauthentication plugins that stores passwords in the mysql.global_priv table,\nthen the PASSWORD() function is evaluated by the specific authentication\nplugin used by the account. The authentication plugin hashes the password with\na method that is compatible with that specific authentication plugin.\n\nThe unix_socket, named_pipe, gssapi, and pam authentication plugins do not\nstore passwords in the mysql.global_priv table. These authentication plugins\nrely on other methods to authenticate the user.\n\nIf you attempt to run SET PASSWORD on an account that authenticates with one\nof these authentication plugins that doesn\'t store a password in the\nmysql.global_priv table, then MariaDB Server will raise a warning like the\nfollowing:\n\nSET PASSWORD is ignored for users authenticating via unix_socket plugin\n\nSee Authentication from MariaDB 10.4 for an overview of authentication changes\nin MariaDB 10.4.\n\nMariaDB until 10.3\n------------------\nIn MariaDB 10.3 and before, SET PASSWORD (with or without PASSWORD()) only\nworks for accounts authenticated via mysql_native_password or\nmysql_old_password authentication plugins\n\nPasswordless User Accounts\n--------------------------\n\nUser accounts do not always require passwords to login.\n\nThe unix_socket , named_pipe and gssapi authentication plugins do not require\na password to authenticate the user.\n\nThe pam authentication plugin may or may not require a password to\nauthenticate the user, depending on the specific configuration.\n\nThe mysql_native_password and mysql_old_password authentication plugins\nrequire passwords for authentication, but the password can be blank. In that\ncase, no password is required.\n\nIf you provide a password while attempting to log into the server as an\naccount that doesn\'t require a password, then MariaDB server will simply\nignore the password.\n\nMariaDB starting with 10.4\n--------------------------\nIn MariaDB 10.4 and later, a user account can be defined to use multiple\nauthentication plugins in a specific order of preference. This specific\nscenario may be more noticeable in these versions, since an account could be\nassociated with some authentication plugins that require a password, and some\nthat do not.\n\nExample\n-------\n\nFor example, if you had an entry with User and Host column values of \'bob\' and\n\'%.loc.gov\', you would write the statement like this:\n\nSET PASSWORD FOR \'bob\'@\'%.loc.gov\' = PASSWORD(\'newpass\');\n\nIf you want to delete a password for a user, you would do:\n\nSET PASSWORD FOR \'bob\'@localhost = PASSWORD(\"\");\n\nURL: https://mariadb.com/kb/en/set-password/','','https://mariadb.com/kb/en/set-password/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (112,10,'CREATE ROLE','Syntax\n------\n\nCREATE [OR REPLACE] ROLE [IF NOT EXISTS] role \n [WITH ADMIN\n {CURRENT_USER | CURRENT_ROLE | user | role}]\n\nDescription\n-----------\n\nThe CREATE ROLE statement creates one or more MariaDB roles. To use it, you\nmust have the global CREATE USER privilege or the INSERT privilege for the\nmysql database. For each account, CREATE ROLE creates a new row in the\nmysql.user table that has no privileges, and with the corresponding is_role\nfield set to Y. It also creates a record in the mysql.roles_mapping table.\n\nIf any of the specified roles already exist, ERROR 1396 (HY000) results. If an\nerror occurs, CREATE ROLE will still create the roles that do not result in an\nerror. The maximum length for a role is 128 characters. Role names can be\nquoted, as explained in the Identifier names page. Only one error is produced\nfor all roles which have not been created:\n\nERROR 1396 (HY000): Operation CREATE ROLE failed for \'a\',\'b\',\'c\'\n\nFailed CREATE or DROP operations, for both users and roles, produce the same\nerror code.\n\nPUBLIC and NONE are reserved, and cannot be used as role names. NONE is used\nto unset a role and PUBLIC has a special use in other systems, such as Oracle,\nso is reserved for compatibility purposes.\n\nFor valid identifiers to use as role names, see Identifier Names.\n\nWITH ADMIN\n----------\n\nThe optional WITH ADMIN clause determines whether the current user, the\ncurrent role or another user or role has use of the newly created role. If the\nclause is omitted, WITH ADMIN CURRENT_USER is treated as the default, which\nmeans that the current user will be able to GRANT this role to users.\n\nOR REPLACE\n----------\n\nIf the optional OR REPLACE clause is used, it acts as a shortcut for:\n\nDROP ROLE IF EXISTS name;\nCREATE ROLE name ...;\n\nIF NOT EXISTS\n-------------\n\nWhen the IF NOT EXISTS clause is used, MariaDB will return a warning instead\nof an error if the specified role already exists. Cannot be used together with\nthe OR REPLACE clause.\n\nExamples\n--------\n\nCREATE ROLE journalist;\n\nCREATE ROLE developer WITH ADMIN lorinda@localhost;\n\nGranting the role to another user. Only user lorinda@localhost has permission\nto grant the developer role:\n\nSELECT USER();\n+-------------------+\n| USER() |\n+-------------------+\n| henning@localhost |\n+-------------------+\n...\nGRANT developer TO ian@localhost;\nAccess denied for user \'henning\'@\'localhost\'\n\nSELECT USER();\n+-------------------+\n| USER() |\n+-------------------+\n| lorinda@localhost |\n+-------------------+\n\nGRANT m_role TO ian@localhost;\n\nThe OR REPLACE and IF NOT EXISTS clauses. The journalist role already exists:\n\nCREATE ROLE journalist;\nERROR 1396 (HY000): Operation CREATE ROLE failed for \'journalist\'\n\nCREATE OR REPLACE ROLE journalist;\nQuery OK, 0 rows affected (0.00 sec)\n\nCREATE ROLE IF NOT EXISTS journalist;\nQuery OK, 0 rows affected, 1 warning (0.00 sec)\n\nSHOW WARNINGS;\n+-------+------+---------------------------------------------------+\n| Level | Code | Message |\n+-------+------+---------------------------------------------------+\n| Note | 1975 | Can\'t create role \'journalist\'; it already exists |\n+-------+------+---------------------------------------------------+\n\nURL: https://mariadb.com/kb/en/create-role/','','https://mariadb.com/kb/en/create-role/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (113,10,'DROP ROLE','Syntax\n------\n\nDROP ROLE [IF EXISTS] role_name [,role_name ...]\n\nDescription\n-----------\n\nThe DROP ROLE statement removes one or more MariaDB roles. To use this\nstatement, you must have the global CREATE USER privilege or the DELETE\nprivilege for the mysql database.\n\nDROP ROLE does not disable roles for connections which selected them with SET\nROLE. If a role has previously been set as a default role, DROP ROLE does not\nremove the record of the default role from the mysql.user table. If the role\nis subsequently recreated and granted, it will again be the user\'s default.\nUse SET DEFAULT ROLE NONE to explicitly remove this.\n\nIf any of the specified user accounts do not exist, ERROR 1396 (HY000)\nresults. If an error occurs, DROP ROLE will still drop the roles that do not\nresult in an error. Only one error is produced for all roles which have not\nbeen dropped:\n\nERROR 1396 (HY000): Operation DROP ROLE failed for \'a\',\'b\',\'c\'\n\nFailed CREATE or DROP operations, for both users and roles, produce the same\nerror code.\n\nIF EXISTS\n---------\n\nIf the IF EXISTS clause is used, MariaDB will return a warning instead of an\nerror if the role does not exist.\n\nExamples\n--------\n\nDROP ROLE journalist;\n\nThe same thing using the optional IF EXISTS clause:\n\nDROP ROLE journalist;\nERROR 1396 (HY000): Operation DROP ROLE failed for \'journalist\'\n\nDROP ROLE IF EXISTS journalist;\nQuery OK, 0 rows affected, 1 warning (0.00 sec)\n\nNote (Code 1975): Can\'t drop role \'journalist\'; it doesn\'t exist\n\nURL: https://mariadb.com/kb/en/drop-role/','','https://mariadb.com/kb/en/drop-role/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (114,10,'SET ROLE','Syntax\n------\n\nSET ROLE { role | NONE }\n\nDescription\n-----------\n\nThe SET ROLE statement enables a role, along with all of its associated\npermissions, for the current session. To unset a role, use NONE .\n\nIf a role that doesn\'t exist, or to which the user has not been assigned, is\nspecified, an ERROR 1959 (OP000): Invalid role specification error occurs.\n\nAn automatic SET ROLE is implicitly performed when a user connects if that\nuser has been assigned a default role. See SET DEFAULT ROLE.\n\nExample\n-------\n\nSELECT CURRENT_ROLE;\n+--------------+\n| CURRENT_ROLE |\n+--------------+\n| NULL |\n+--------------+\n\nSET ROLE staff;\n\nSELECT CURRENT_ROLE;\n+--------------+\n| CURRENT_ROLE |\n+--------------+\n| staff |\n+--------------+\n\nSET ROLE NONE;\n\nSELECT CURRENT_ROLE();\n+----------------+\n| CURRENT_ROLE() |\n+----------------+\n| NULL |\n+----------------+\n\nURL: https://mariadb.com/kb/en/set-role/','','https://mariadb.com/kb/en/set-role/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (115,10,'SET DEFAULT ROLE','Syntax\n------\n\nSET DEFAULT ROLE { role | NONE } [ FOR user@host ]\n\nDescription\n-----------\n\nThe SET DEFAULT ROLE statement sets a default role for a specified (or\ncurrent) user. A default role is automatically enabled when a user connects\n(an implicit SET ROLE statement is executed immediately after a connection is\nestablished).\n\nTo be able to set a role as a default, the role must already have been granted\nto that user, and one needs the privileges to enable this role (if you cannot\ndo SET ROLE X, you won\'t be able to do SET DEFAULT ROLE X). To set a default\nrole for another user one needs to have write access to the mysql database.\n\nTo remove a user\'s default role, use SET DEFAULT ROLE NONE [ FOR user@host ].\nThe record of the default role is not removed if the role is dropped or\nrevoked, so if the role is subsequently re-created or granted, it will again\nbe the user\'s default role.\n\nThe default role is stored in the default_role column in the mysql.user\ntable/view, as well as in the Information Schema APPLICABLE_ROLES table, so\nthese can be viewed to see which role has been assigned to a user as the\ndefault.\n\nExamples\n--------\n\nSetting a default role for the current user:\n\nSET DEFAULT ROLE journalist;\n\nRemoving a default role from the current user:\n\nSET DEFAULT ROLE NONE;\n\nSetting a default role for another user. The role has to have been granted to\nthe user before it can be set as default:\n\nCREATE ROLE journalist;\nCREATE USER taniel;\n\nSET DEFAULT ROLE journalist FOR taniel;\nERROR 1959 (OP000): Invalid role specification `journalist`\n\nGRANT journalist TO taniel;\nSET DEFAULT ROLE journalist FOR taniel;\n\nViewing mysql.user:\n\nselect * from mysql.user where user=\'taniel\'\\G\n*************************** 1. row ***************************\n Host: %\n User: taniel\n...\n is_role: N\n default_role: journalist\n...\n\nRemoving a default role for another user\n\nSET DEFAULT ROLE NONE FOR taniel;\n\nURL: https://mariadb.com/kb/en/set-default-role/','','https://mariadb.com/kb/en/set-default-role/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (116,10,'Roles Overview','Description\n-----------\n\nA role bundles a number of privileges together. It assists larger\norganizations where, typically, a number of users would have the same\nprivileges, and, previously, the only way to change the privileges for a group\nof users was by changing each user\'s privileges individually.\n\nAlternatively, multiple external users could have been assigned the same user,\nand there would have been no way to see which actual user was responsible for\nwhich action.\n\nWith roles, managing this is easy. For example, there could be a number of\nusers assigned to a journalist role, with identical privileges. Changing the\nprivileges for all the journalists is a matter of simply changing the role\'s\nprivileges, while the individual user is still linked with any changes that\ntake place.\n\nRoles are created with the CREATE ROLE statement, and dropped with the DROP\nROLE statement. Roles are then assigned to a user with an extension to the\nGRANT statement, while privileges are assigned to a role in the regular way\nwith GRANT. Similarly, the REVOKE statement can be used to both revoke a role\nfrom a user, or revoke a privilege from a role.\n\nOnce a user has connected, he can obtain all privileges associated with a role\nby setting a role with the SET ROLE statement. The CURRENT_ROLE function\nreturns the currently set role for the session, if any.\n\nOnly roles granted directly to a user can be set, roles granted to other roles\ncannot. Instead the privileges granted to a role, which is, in turn, granted\nto another role (grantee), will be immediately available to any user who sets\nthis second grantee role.\n\nThe SET DEFAULT ROLE statement allows one to set a default role for a user. A\ndefault role is automatically enabled when a user connects (an implicit SET\nROLE statement is executed immediately after a connection is established).\n\nRoles were implemented as a GSoC 2013 project by Vicentiu Ciorbaru.\n\nSystem Tables\n-------------\n\nInformation about roles and who they\'ve been granted to can be found in the\nInformation Schema APPLICABLE_ROLES table as well as the mysql.ROLES_MAPPING\ntable.\n\nThe Information Schema ENABLED_ROLES table shows the enabled roles for the\ncurrent session.\n\nExamples\n--------\n\nCreating a role and granting a privilege:\n\nCREATE ROLE journalist;\n\nGRANT SHOW DATABASES ON *.* TO journalist;\n\nGRANT journalist to hulda;\n\nNote, that hulda has no SHOW DATABASES privilege, even though she was granted\nthe journalist role. She needs to set the role first:\n\nSHOW DATABASES;\n+--------------------+\n| Database |\n+--------------------+\n| information_schema |\n+--------------------+\n\nSELECT CURRENT_ROLE;\n+--------------+\n| CURRENT_ROLE |\n+--------------+\n| NULL |\n+--------------+\n\nSET ROLE journalist;\n\nSELECT CURRENT_ROLE;\n+--------------+\n| CURRENT_ROLE |\n+--------------+\n| journalist |\n+--------------+\n\nSHOW DATABASES;\n+--------------------+\n| Database |\n+--------------------+\n| ... |\n| information_schema |\n| mysql |\n| performance_schema |\n| test |\n| ... |\n+--------------------+\n\nSET ROLE NONE;\n\nRoles can be granted to roles:\n\nCREATE ROLE writer;\n\nGRANT SELECT ON data.* TO writer;\n\nGRANT writer TO journalist;\n\nBut one does not need to set a role granted to a role. For example, hulda will\nautomatically get all writer privileges when she sets the journalist role:\n\nSELECT CURRENT_ROLE;\n+--------------+\n| CURRENT_ROLE |\n+--------------+\n| NULL |\n+--------------+\n\nSHOW TABLES FROM data;\nEmpty set (0.01 sec)\n\nSET ROLE journalist;\n\nSELECT CURRENT_ROLE;\n+--------------+\n| CURRENT_ROLE |\n+--------------+\n| journalist |\n+--------------+\n\nSHOW TABLES FROM data;\n+------------------------------+\n| Tables_in_data |\n+------------------------------+\n| set1 |\n| ... |\n+------------------------------+\n\nRoles and Views (and Stored Routines)\n-------------------------------------\n\nWhen a user sets a role, he, in a sense, has two identities with two\nassociated sets of privileges. But a view (or a stored routine) can have only\none definer. So, when a view (or a stored routine) is created with the SQL\nSECURITY DEFINER, one can specify whether the definer should be CURRENT_USER\n(and the view will have none of the privileges of the user\'s role) or\nCURRENT_ROLE (in this case, the view will use role\'s privileges, but none of\nthe user\'s privileges). As a result, sometimes one can create a view that is\nimpossible to use.\n\nCREATE ROLE r1;\n\nGRANT ALL ON db1.* TO r1;\n\nGRANT r1 TO foo@localhost;\n\nGRANT ALL ON db.* TO foo@localhost;\n\nSELECT CURRENT_USER\n+---------------+\n| current_user |\n+---------------+\n| foo@localhost |\n+---------------+\n\nSET ROLE r1;\n\nCREATE TABLE db1.t1 (i int);\n\nCREATE VIEW db.v1 AS SELECT * FROM db1.t1;\n\nSHOW CREATE VIEW db.v1;\n+------+-----------------------------------------------------------------------\n------------------------------------------------------------------+------------\n---------+----------------------+\n| View | Create View \n |\ncharacter_set_client | collation_connection |\n+------+-----------------------------------------------------------------------\n------------------------------------------------------------------+------------\n---------+----------------------+\n| v1 | CREATE ALGORITHM=UNDEFINED DEFINER=`foo`@`localhost` SQL SECURITY\nDEFINER VIEW `db`.`v1` AS SELECT `db1`.`t1`.`i` AS `i` from `db1`.`t1` | utf8 \n | utf8_general_ci |\n+------+-----------------------------------------------------------------------\n------------------------------------------------------------------+------------\n---------+----------------------+\n\nCREATE DEFINER=CURRENT_ROLE VIEW db.v2 AS SELECT * FROM db1.t1;\n\nSHOW CREATE VIEW db.b2;\n+------+-----------------------------------------------------------------------\n-----------------------------------------------------+----------------------+--\n-------------------+\n| View | Create View \n | character_set_client |\ncollation_connection |\n+------+-----------------------------------------------------------------------\n-----------------------------------------------------+----------------------+--\n-------------------+\n| v2 | CREATE ALGORITHM=UNDEFINED DEFINER=`r1` SQL SECURITY DEFINER VIEW\n`db`.`v2` AS select `db1`.`t1`.`a` AS `a` from `db1`.`t1` | utf8 \n | utf8_general_ci |\n+------+-----------------------------------------------------------------------\n-----------------------------------------------------+----------------------+--\n-------------------+\n\nOther Resources\n---------------\n\n* Roles Review by Peter Gulutzan\n\nURL: https://mariadb.com/kb/en/roles_overview/','','https://mariadb.com/kb/en/roles_overview/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (117,10,'Account Locking','MariaDB starting with 10.4.2\n----------------------------\nAccount locking was introduced in MariaDB 10.4.2.\n\nDescription\n-----------\n\nAccount locking permits privileged administrators to lock/unlock user\naccounts. No new client connections will be permitted if an account is locked\n(existing connections are not affected).\n\nUser accounts can be locked at creation, with the CREATE USER statement, or\nmodified after creation with the ALTER USER statement. For example:\n\nCREATE USER \'lorin\'@\'localhost\' ACCOUNT LOCK;\n\nor\n\nALTER USER \'marijn\'@\'localhost\' ACCOUNT LOCK;\n\nThe server will return an ER_ACCOUNT_HAS_BEEN_LOCKED error when locked users\nattempt to connect:\n\nmysql -ulorin\n ERROR 4151 (HY000): Access denied, this account is locked\n\nThe ALTER USER statement is also used to unlock a user:\n\nALTER USER \'lorin\'@\'localhost\' ACCOUNT UNLOCK;\n\nThe SHOW CREATE USER statement will show whether the account is locked:\n\nSHOW CREATE USER \'marijn\'@\'localhost\';\n+-----------------------------------------------+\n| CREATE USER for marijn@localhost |\n+-----------------------------------------------+\n| CREATE USER \'marijn\'@\'localhost\' ACCOUNT LOCK |\n+-----------------------------------------------+\n\nas well as querying the mysql.global_priv table:\n\nSELECT CONCAT(user, \'@\', host, \' => \', JSON_DETAILED(priv)) FROM\nmysql.global_priv \n WHERE user=\'marijn\';\n+------------------------------------------------------------------------------\n-------+\n| CONCAT(user, \'@\', host, \' => \', JSON_DETAILED(priv)) \n |\n+------------------------------------------------------------------------------\n-------+\n| marijn@localhost => {\n \"access\": 0,\n \"plugin\": \"mysql_native_password\",\n \"authentication_string\": \"\",\n \"account_locked\": true,\n \"password_last_changed\": 1558017158\n} |\n+------------------------------------------------------------------------------\n-------+\n\nURL: https://mariadb.com/kb/en/account-locking/','','https://mariadb.com/kb/en/account-locking/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (118,10,'Authentication from MariaDB 10.4','MariaDB starting with 10.4\n--------------------------\nMariaDB 10.4 introduced a number of changes to the authentication process,\nintended to make things easier and more intuitive.\n\nOverview\n--------\n\nThere are four new main features in 10.4 relating to authentication:\n\n* It is possible to use more than one authentication plugin for each user\naccount. For example, this can be useful to slowly migrate users to the more\nsecure ed25519 authentication plugin over time, while allowing the old\nmysql_native_password authentication plugin as an alternative for the\ntransitional period.\n* The root@localhost user account created by mariadb-install-db is created\nwith the ability to use two authentication plugins.\nFirst, it is configured to try to use the unix_socket authentication plugin.\nThis allows the root@localhost user to login without a password via the local\nUnix socket file defined by the socket system variable, as long as the login\nis attempted from a process owned by the operating system root user account.\nSecond, if authentication fails with the unix_socket authentication plugin,\nthen it is configured to try to use the mysql_native_password authentication\nplugin. However, an invalid password is initially set, so in order to\nauthenticate this way, a password must be set with SET PASSWORD.\nHowever, just using the unix_socket authentication plugin may be fine for many\nusers, and it is very secure. You may want to try going without password\nauthentication to see how well it works for you. Remember, the best way to\nkeep your password safe is not to have one!\n\n* All user accounts, passwords, and global privileges are now stored in the\nmysql.global_priv table. The mysql.user table still exists and has exactly the\nsame set of columns as before, but it’s now a view that references the\nmysql.global_priv table. Tools that analyze the mysql.user table should\ncontinue to work as before. From MariaDB 10.4.13, the dedicated mariadb.sys\nuser is created as the definer of this view. Previously root was the definer,\nwhich resulted in privilege problems when this username was changed.\n* MariaDB 10.4 adds supports for User Password Expiry, which is not active by\ndefault.\n\nDescription\n-----------\n\nAs a result of the above changes, the open-for-everyone all-powerful root\naccount is finally gone. And installation scripts will no longer demand that\nyou \"PLEASE REMEMBER TO SET A PASSWORD FOR THE MariaDB root USER !\", because\nthe root account is securely created automatically.\n\nTwo all-powerful accounts are created by default — root and the OS user that\nowns the data directory, typically mysql. They are created as:\n\nCREATE USER root@localhost IDENTIFIED VIA unix_socket OR mysql_native_password\nUSING \'invalid\'\nCREATE USER mysql@localhost IDENTIFIED VIA unix_socket OR\nmysql_native_password USING \'invalid\'\n\nUsing unix_socket means that if you are the system root user, you can login as\nroot@locahost without a password. This technique was pioneered by Otto\nKekäläinen in Debian MariaDB packages and has been successfully used in Debian\nsince as early as MariaDB 10.0.\n\nIt is based on a simple fact that asking the system root for a password adds\nno extra security — root has full access to all the data files and all process\nmemory anyway. But not asking for a password means, there is no root password\nto forget (no need for the numerous tutorials on \"how to reset MariaDB root\npassword\"). And if you want to script some tedious database work, there is no\nneed to store the root password in plain text for the script to use (no need\nfor debian-sys-maint user).\n\nStill, some users may wish to log in as MariaDB root without using sudo. Hence\nthe old authentication method — conventional MariaDB password — is still\navailable. By default it is disabled (\"invalid\" is not a valid password hash),\nbut one can set the password with a usual SET PASSWORD statement. And still\nretain the password-less access via sudo.\n\nIf you install MariaDB locally (say from a tarball), you would not want to use\nsudo to be able to login. This is why MariaDB creates a second all-powerful\nuser with the same name as a system user that owns the data directory. In\nlocal (not system-wide) installations, this will be the user who installed\nMariaDB — they automatically get convenient password-less root-like access,\nbecause they can access all the data files anyway.\n\nEven if MariaDB is installed system-wide, you may not want to run your\ndatabase maintenance scripts as system root — now you can run them as system\nmysql user. And you will know that they will never destroy your entire system,\neven if you make a typo in a shell script.\n\nHowever, seasoned MariaDB DBAs who are used to the old ways do need to make\nsome changes. See the examples below for common tasks.\n\nCookbook\n--------\n\nAfter installing MariaDB system-wide the first thing you’ve got used to doing\nis logging in into the unprotected root account and protecting it, that is,\nsetting the root password:\n\n$ sudo dnf install MariaDB-server\n$ mysql -uroot\n...\nMariaDB> set password = password(\"XH4VmT3_jt\");\n\nThis is not only unnecessary now, it will simply not work — there is no\nunprotected root account. To login as root use\n\n$ sudo dnf install MariaDB-server\n$ sudo mysql\n\nNote that it implies you are connecting via the unix socket, not tcp. If you\nhappen to have protocol=tcp in a system-wide /etc/my.cnf file, use sudo mysql\n--protocol=socket.\n\nAfter installing MariaDB locally you’ve also used to connect to the\nunprotected root account using mysql -uroot. This will not work either, simply\nuse mysql without specifying a username.\n\nIf you\'ve forgotten your root password, no problem — you can still connect\nusing sudo and change the password. And if you\'ve also removed unix_socket\nauthentication, to restore access do as follows:\n\n* restart MariaDB with --skip-grant-tables\n* login into the unprotected server\n* run FLUSH PRIVILEGES (note, before 10.4 this would’ve been the last step,\nnot anymore). This disables --skip-grant-tables and allows you to change the\nstored authentication method\n* run SET PASSWORD FOR root@localhost to change the root password.\n\nTo view inside privilege tables, the old mysql.user table still exists. You\ncan select from it as before, although you cannot update it anymore. It\ndoesn’t show alternative authentication plugins and this was one of the\nreasons for switching to the mysql.global_priv table — complex authentication\nrules did not fit into rigid structure of a relational table. You can select\nfrom the new table, for example:\n\nselect concat(user, \'@\', host, \' => \', json_detailed(priv)) from\nmysql.global_priv;\n\nReverting to the Previous Authentication Method for root@localhost\n------------------------------------------------------------------\n\nIf you don\'t want the root@localhost user account created by\nmariadb-install-db to use unix_socket authentication by default, then there\nare a few ways to revert to the previous mysql_native_password authentication\nmethod for this user account.\n\nConfiguring mariadb-install-db to Revert to the Previous Authentication Method\n------------------------------------------------------------------------------\n\nOne way to revert to the previous mysql_native_password authentication method\nfor the root@localhost user account is to execute mariadb-install-db with a\nspecial option. If mariadb-install-db is executed while\n--auth-root-authentication-method=normal is specified, then it will create the\ndefault user accounts using the default behavior of MariaDB 10.3 and before.\n\nThis means that the root@localhost user account will use mysql_native_password\nauthentication by default. There are some other differences as well. See\nmariadb-install-db: User Accounts Created by Default for more information.\n\nFor example, the option can be set on the command-line while running\nmariadb-install-db:\n\nmariadb-install-db --user=mysql --datadir=/var/lib/mysql\n--auth-root-authentication-method=normal\n\nThe option can also be set in an option file in an option group supported by\nmariadb-install-db. For example:\n\n[mysql_install_db]\nauth_root_authentication_method=normal\n\nIf the option is set in an option file and if mariadb-install-db is executed,\nthen mariadb-install-db will read this option from the option file, and it\nwill automatically set this option.\n\nAltering the User Account to Revert to the Previous Authentication Method\n-------------------------------------------------------------------------\n\nIf you have already installed MariaDB, and if the root@localhost user account\nis already using unix_socket authentication, then you can revert to the old\nmysql_native_password authentication method for the user account by executing\nthe following:\n\nALTER USER root@localhost IDENTIFIED VIA mysql_native_password USING\nPASSWORD(\"verysecret\")\n\nURL: https://mariadb.com/kb/en/authentication-from-mariadb-104/','','https://mariadb.com/kb/en/authentication-from-mariadb-104/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (119,10,'User Password Expiry','MariaDB starting with 10.4.3\n----------------------------\nUser password expiry was introduced in MariaDB 10.4.3.\n\nPassword expiry permits administrators to expire user passwords, either\nmanually or automatically.\n\nSystem Variables\n----------------\n\nThere are two system variables which affect password expiry:\ndefault_password_lifetime, which determines the amount of time between\nrequiring the user to change their password. 0, the default, means automatic\npassword expiry is not active.\n\nThe second variable, disconnect_on_expired_password determines whether a\nclient is permitted to connect if their password has expired, or whether they\nare permitted to connect in sandbox mode, able to perform a limited subset of\nqueries related to resetting the password, in particular SET PASSWORD and SET.\n\nSetting a Password Expiry Limit for a User\n------------------------------------------\n\nBesides automatic password expiry, as determined by default_password_lifetime,\npassword expiry times can be set on an individual user basis, overriding the\nglobal using the CREATE USER or ALTER USER statements, for example:\n\nCREATE USER \'monty\'@\'localhost\' PASSWORD EXPIRE INTERVAL 120 DAY;\n\nALTER USER \'monty\'@\'localhost\' PASSWORD EXPIRE INTERVAL 120 DAY;\n\nLimits can be disabled by use of the NEVER keyword, for example:\n\nCREATE USER \'monty\'@\'localhost\' PASSWORD EXPIRE NEVER;\n\nALTER USER \'monty\'@\'localhost\' PASSWORD EXPIRE NEVER;\n\nA manually set limit can be restored the system default by use of DEFAULT, for\nexample:\n\nCREATE USER \'monty\'@\'localhost\' PASSWORD EXPIRE DEFAULT;\n\nALTER USER \'monty\'@\'localhost\' PASSWORD EXPIRE DEFAULT;\n\nSHOW CREATE USER\n----------------\n\nThe SHOW CREATE USER statement will display information about the password\nexpiry status of the user. Unlike MySQL, it will not display if the user is\nunlocked, or if the password expiry is set to default.\n\nCREATE USER \'monty\'@\'localhost\' PASSWORD EXPIRE INTERVAL 120 DAY;\nCREATE USER \'konstantin\'@\'localhost\' PASSWORD EXPIRE NEVER;\nCREATE USER \'amse\'@\'localhost\' PASSWORD EXPIRE DEFAULT;\n\nSHOW CREATE USER \'monty\'@\'localhost\';\n+------------------------------------------------------------------+\n| CREATE USER for monty@localhost |\n+------------------------------------------------------------------+\n| CREATE USER \'monty\'@\'localhost\' PASSWORD EXPIRE INTERVAL 120 DAY |\n+------------------------------------------------------------------+\n\nSHOW CREATE USER \'konstantin\'@\'localhost\';\n+------------------------------------------------------------+\n| CREATE USER for konstantin@localhost |\n+------------------------------------------------------------+\n| CREATE USER \'konstantin\'@\'localhost\' PASSWORD EXPIRE NEVER |\n+------------------------------------------------------------+\n\nSHOW CREATE USER \'amse\'@\'localhost\';\n+--------------------------------+\n| CREATE USER for amse@localhost |\n+--------------------------------+\n| CREATE USER \'amse\'@\'localhost\' |\n+--------------------------------+\n\nChecking When Passwords Expire\n------------------------------\n\nThe following query can be used to check when the current passwords expire for\nall users:\n\nWITH password_expiration_info AS (\n SELECT User, Host,\n IF(\n IFNULL(JSON_EXTRACT(Priv, \'$.password_lifetime\'), -1) = -1,\n @@global.default_password_lifetime,\n JSON_EXTRACT(Priv, \'$.password_lifetime\')\n ) AS password_lifetime,\n JSON_EXTRACT(Priv, \'$.password_last_changed\') AS password_last_changed\n FROM mysql.global_priv\n)\nSELECT pei.User, pei.Host,\n pei.password_lifetime,\n FROM_UNIXTIME(pei.password_last_changed) AS password_last_changed_datetime,\n FROM_UNIXTIME(\n pei.password_last_changed +\n (pei.password_lifetime * 60 * 60 * 24)\n ) AS password_expiration_datetime\n FROM password_expiration_info pei\n WHERE pei.password_lifetime != 0\n AND pei.password_last_changed IS NOT NULL\nUNION\nSELECT pei.User, pei.Host,\n pei.password_lifetime,\n FROM_UNIXTIME(pei.password_last_changed) AS password_last_changed_datetime,\n 0 AS password_expiration_datetime\n FROM password_expiration_info pei\n WHERE pei.password_lifetime = 0\n OR pei.password_last_changed IS NULL;\n\n--connect-expired-password Client Option\n----------------------------------------\n\nThe mariadb client --connect-expired-password option notifies the server that\nthe client is prepared to handle expired password sandbox mode (even if the\n--batch option was specified).\n\nURL: https://mariadb.com/kb/en/user-password-expiry/','','https://mariadb.com/kb/en/user-password-expiry/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (120,11,'ST_X','Syntax\n------\n\nST_X(p)\nX(p)\n\nDescription\n-----------\n\nReturns the X-coordinate value for the point p as a double-precision number.\n\nST_X() and X() are synonyms.\n\nExamples\n--------\n\nSET @pt = \'Point(56.7 53.34)\';\n\nSELECT X(GeomFromText(@pt));\n+----------------------+\n| X(GeomFromText(@pt)) |\n+----------------------+\n| 56.7 |\n+----------------------+\n\nURL: https://mariadb.com/kb/en/st_x/','','https://mariadb.com/kb/en/st_x/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (121,11,'ST_Y','Syntax\n------\n\nST_Y(p)\nY(p)\n\nDescription\n-----------\n\nReturns the Y-coordinate value for the point p as a double-precision number.\n\nST_Y() and Y() are synonyms.\n\nExamples\n--------\n\nSET @pt = \'Point(56.7 53.34)\';\n\nSELECT Y(GeomFromText(@pt));\n+----------------------+\n| Y(GeomFromText(@pt)) |\n+----------------------+\n| 53.34 |\n+----------------------+\n\nURL: https://mariadb.com/kb/en/st_y/','','https://mariadb.com/kb/en/st_y/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (122,11,'X','A synonym for ST_X.\n\nURL: https://mariadb.com/kb/en/point-properties-x/','','https://mariadb.com/kb/en/point-properties-x/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (123,11,'Y','A synonym for ST_Y.\n\nURL: https://mariadb.com/kb/en/point-properties-y/','','https://mariadb.com/kb/en/point-properties-y/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (124,12,'UNCOMPRESS','Syntax\n------\n\nUNCOMPRESS(string_to_uncompress)\n\nDescription\n-----------\n\nUncompresses a string compressed by the COMPRESS() function. If the argument\nis not a compressed value, the result is NULL. This function requires MariaDB\nto have been compiled with a compression library such as zlib. Otherwise, the\nreturn value is always NULL. The have_compress server system variable\nindicates whether a compression library is present.\n\nExamples\n--------\n\nSELECT UNCOMPRESS(COMPRESS(\'a string\'));\n+----------------------------------+\n| UNCOMPRESS(COMPRESS(\'a string\')) |\n+----------------------------------+\n| a string |\n+----------------------------------+\n\nSELECT UNCOMPRESS(\'a string\');\n+------------------------+\n| UNCOMPRESS(\'a string\') |\n+------------------------+\n| NULL |\n+------------------------+\n\nURL: https://mariadb.com/kb/en/uncompress/','','https://mariadb.com/kb/en/uncompress/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (125,12,'DECODE','Syntax\n------\n\nDECODE(crypt_str,pass_str)\n\nIn Oracle mode from MariaDB 10.3.2:\n\nDECODE(expr, search_expr, result_expr [, search_expr2, result_expr2 ...]\n[default_expr])\n\nIn all modes from MariaDB 10.3.2:\n\nDECODE_ORACLE(expr, search_expr, result_expr [, search_expr2, result_expr2\n...] [default_expr])\n\nDescription\n-----------\n\nIn the default mode, DECODE decrypts the encrypted string crypt_str using\npass_str as the password. crypt_str should be a string returned from ENCODE().\nThe resulting string will be the original string only if pass_str is the same.\n\nIn Oracle mode from MariaDB 10.3.2, DECODE compares expr to the search\nexpressions, in order. If it finds a match, the corresponding result\nexpression is returned. If no matches are found, the default expression is\nreturned, or NULL if no default is provided.\n\nNULLs are treated as equivalent.\n\nDECODE_ORACLE is a synonym for the Oracle-mode version of the function, and is\navailable in all modes.\n\nExamples\n--------\n\nFrom MariaDB 10.3.2:\n\nSELECT DECODE_ORACLE(2+1,3*1,\'found1\',3*2,\'found2\',\'default\');\n+--------------------------------------------------------+\n| DECODE_ORACLE(2+1,3*1,\'found1\',3*2,\'found2\',\'default\') |\n+--------------------------------------------------------+\n| found1 |\n+--------------------------------------------------------+\n\nSELECT DECODE_ORACLE(2+4,3*1,\'found1\',3*2,\'found2\',\'default\');\n+--------------------------------------------------------+\n| DECODE_ORACLE(2+4,3*1,\'found1\',3*2,\'found2\',\'default\') |\n+--------------------------------------------------------+\n| found2 |\n+--------------------------------------------------------+\n\nSELECT DECODE_ORACLE(2+2,3*1,\'found1\',3*2,\'found2\',\'default\');\n+--------------------------------------------------------+\n| DECODE_ORACLE(2+2,3*1,\'found1\',3*2,\'found2\',\'default\') |\n+--------------------------------------------------------+\n| default |\n+--------------------------------------------------------+\n\nNulls are treated as equivalent:\n\nSELECT DECODE_ORACLE(NULL,NULL,\'Nulls are equivalent\',\'Nulls are not\nequivalent\');\n+----------------------------------------------------------------------------+\n| DECODE_ORACLE(NULL,NULL,\'Nulls are equivalent\',\'Nulls are not equivalent\') |\n+----------------------------------------------------------------------------+\n| Nulls are equivalent |\n+----------------------------------------------------------------------------+\n\nURL: https://mariadb.com/kb/en/decode/','','https://mariadb.com/kb/en/decode/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (126,12,'DECODE_ORACLE','MariaDB starting with 10.3.2\n----------------------------\nDECODE_ORACLE is a synonym for the Oracle mode version of the DECODE function,\nand is available in all modes.\n\nURL: https://mariadb.com/kb/en/decode_oracle/','','https://mariadb.com/kb/en/decode_oracle/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (127,12,'AES_DECRYPT','Syntax\n------\n\nAES_DECRYPT(crypt_str,key_str)\n\nFrom MariaDB 11.2.0\n\nAES_ENCRYPT(crypt_str, key_str, [, iv [, mode]])\n\nDescription\n-----------\n\nThis function allows decryption of data using the official AES (Advanced\nEncryption Standard) algorithm. For more information, see the description of\nAES_ENCRYPT().\n\nMariaDB starting with 11.2\n--------------------------\nFrom MariaDB 11.2, the function supports an initialization vector, and control\nof the block encryption mode. The default mode is specified by the\nblock_encryption_mode system variable, which can be changed when calling the\nfunction with a mode. mode is aes-{128,192,256}-{ecb,cbc,ctr} for example:\n\"AES-128-cbc\".\n\nFor modes that require it, the initialization_vector iv should be 16 bytes (it\ncan be longer, but the extra bytes are ignored). A shorter iv, where one is\nrequired, results in the function returning NULL. Calling RANDOM_BYTES(16)\nwill generate a random series of bytes that can be used for the iv.\n\nExamples\n--------\n\nFrom MariaDB 11.2.0:\n\nSELECT HEX(AES_ENCRYPT(\'foo\', \'bar\', \'0123456789abcdef\', \'aes-128-ctr\')) AS x; \n+--------+\n| x |\n+--------+\n| C57C4B |\n+--------+\n\nSELECT AES_DECRYPT(x\'C57C4B\', \'bar\', \'0123456789abcdef\', \'aes-128-ctr\'); \n+------------------------------------------------------------------+\n| AES_DECRYPT(x\'C57C4B\', \'bar\', \'0123456789abcdef\', \'aes-128-ctr\') |\n+------------------------------------------------------------------+\n| foo |\n+------------------------------------------------------------------+\n\nURL: https://mariadb.com/kb/en/aes_decrypt/','','https://mariadb.com/kb/en/aes_decrypt/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (128,12,'AES_ENCRYPT','Syntax\n------\n\nAES_ENCRYPT(str,key_str)\n\nFrom MariaDB 11.2.0\n\nAES_ENCRYPT(str, key, [, iv [, mode]])\n\nDescription\n-----------\n\nAES_ENCRYPT() and AES_DECRYPT() allow encryption and decryption of data using\nthe official AES (Advanced Encryption Standard) algorithm, previously known as\n\"Rijndael.\" Encoding with a 128-bit key length is used (from MariaDB 11.2.0,\nthis is the default, and can be changed). 128 bits is much faster and is\nsecure enough for most purposes.\n\nAES_ENCRYPT() encrypts a string str using the key key_str, and returns a\nbinary string.\n\nAES_DECRYPT() decrypts the encrypted string and returns the original string.\n\nThe input arguments may be any length. If either argument is NULL, the result\nof this function is also NULL.\n\nBecause AES is a block-level algorithm, padding is used to encode uneven\nlength strings and so the result string length may be calculated using this\nformula:\n\n16 x (trunc(string_length / 16) + 1)\n\nIf AES_DECRYPT() detects invalid data or incorrect padding, it returns NULL.\nHowever, it is possible for AES_DECRYPT() to return a non-NULL value (possibly\ngarbage) if the input data or the key is invalid.\n\nMariaDB starting with 11.2\n--------------------------\nFrom MariaDB 11.2, the function supports an initialization vector, and control\nof the block encryption mode. The default mode is specified by the\nblock_encryption_mode system variable, which can be changed when calling the\nfunction with a mode. mode is aes-{128,192,256}-{ecb,cbc,ctr} for example:\n\"AES-128-cbc\".\n\nAES_ENCRYPT(str, key) can no longer be used in persistent virtual columns (and\nthe like).\n\nExamples\n--------\n\nINSERT INTO t VALUES (AES_ENCRYPT(\'text\',SHA2(\'password\',512)));\n\nFrom MariaDB 11.2.0:\n\nSELECT HEX(AES_ENCRYPT(\'foo\', \'bar\', \'0123456789abcdef\', \'aes-256-cbc\')) AS x;\n+----------------------------------+\n| x |\n+----------------------------------+\n| 42A3EB91E6DFC40A900D278F99E0726E |\n+----------------------------------+\n\nURL: https://mariadb.com/kb/en/aes_encrypt/','','https://mariadb.com/kb/en/aes_encrypt/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (129,12,'COMPRESS','Syntax\n------\n\nCOMPRESS(string_to_compress)\n\nDescription\n-----------\n\nCompresses a string and returns the result as a binary string. This function\nrequires MariaDB to have been compiled with a compression library such as\nzlib. Otherwise, the return value is always NULL. The compressed string can be\nuncompressed with UNCOMPRESS().\n\nThe have_compress server system variable indicates whether a compression\nlibrary is present.\n\nExamples\n--------\n\nSELECT LENGTH(COMPRESS(REPEAT(\'a\',1000)));\n+------------------------------------+\n| LENGTH(COMPRESS(REPEAT(\'a\',1000))) |\n+------------------------------------+\n| 21 |\n+------------------------------------+\n\nSELECT LENGTH(COMPRESS(\'\'));\n+----------------------+\n| LENGTH(COMPRESS(\'\')) |\n+----------------------+\n| 0 |\n+----------------------+\n\nSELECT LENGTH(COMPRESS(\'a\'));\n+-----------------------+\n| LENGTH(COMPRESS(\'a\')) |\n+-----------------------+\n| 13 |\n+-----------------------+\n\nSELECT LENGTH(COMPRESS(REPEAT(\'a\',16)));\n+----------------------------------+\n| LENGTH(COMPRESS(REPEAT(\'a\',16))) |\n+----------------------------------+\n| 15 |\n+----------------------------------+\n\nURL: https://mariadb.com/kb/en/compress/','','https://mariadb.com/kb/en/compress/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (130,12,'DES_DECRYPT','DES_DECRYPT has been deprecated from MariaDB 10.10.0, and will be removed in a\nfuture release.\n\nSyntax\n------\n\nDES_DECRYPT(crypt_str[,key_str])\n\nDescription\n-----------\n\nDecrypts a string encrypted with DES_ENCRYPT(). If an error occurs, this\nfunction returns NULL.\n\nThis function works only if MariaDB has been configured with TLS support.\n\nIf no key_str argument is given, DES_DECRYPT() examines the first byte of the\nencrypted string to determine the DES key number that was used to encrypt the\noriginal string, and then reads the key from the DES key file to decrypt the\nmessage. For this to work, the user must have the SUPER privilege. The key\nfile can be specified with the --des-key-file server option.\n\nIf you pass this function a key_str argument, that string is used as the key\nfor decrypting the message.\n\nIf the crypt_str argument does not appear to be an encrypted string, MariaDB\nreturns the given crypt_str.\n\nURL: https://mariadb.com/kb/en/des_decrypt/','','https://mariadb.com/kb/en/des_decrypt/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (131,12,'DES_ENCRYPT','DES_ENCRYPT has been deprecated from MariaDB 10.10.0, and will be removed in a\nfuture release.\n\nSyntax\n------\n\nDES_ENCRYPT(str[,{key_num|key_str}])\n\nDescription\n-----------\n\nEncrypts the string with the given key using the Triple-DES algorithm.\n\nThis function works only if MariaDB has been configured with TLS support.\n\nThe encryption key to use is chosen based on the second argument to\nDES_ENCRYPT(), if one was given. With no argument, the first key from the DES\nkey file is used. With a key_num argument, the given key number (0-9) from the\nDES key file is used. With a key_str argument, the given key string is used to\nencrypt str.\n\nThe key file can be specified with the --des-key-file server option.\n\nThe return string is a binary string where the first character is CHAR(128 |\nkey_num). If an error occurs, DES_ENCRYPT() returns NULL.\n\nThe 128 is added to make it easier to recognize an encrypted key. If you use a\nstring key, key_num is 127.\n\nThe string length for the result is given by this formula:\n\nnew_len = orig_len + (8 - (orig_len % 8)) + 1\n\nEach line in the DES key file has the following format:\n\nkey_num des_key_str\n\nEach key_num value must be a number in the range from 0 to 9. Lines in the\nfile may be in any order. des_key_str is the string that is used to encrypt\nthe message. There should be at least one space between the number and the\nkey. The first key is the default key that is used if you do not specify any\nkey argument to DES_ENCRYPT().\n\nYou can tell MariaDB to read new key values from the key file with the FLUSH\nDES_KEY_FILE statement. This requires the RELOAD privilege.\n\nOne benefit of having a set of default keys is that it gives applications a\nway to check for the existence of encrypted column values, without giving the\nend user the right to decrypt those values.\n\nExamples\n--------\n\nSELECT customer_address FROM customer_table \n WHERE crypted_credit_card = DES_ENCRYPT(\'credit_card_number\');\n\nURL: https://mariadb.com/kb/en/des_encrypt/','','https://mariadb.com/kb/en/des_encrypt/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (132,12,'ENCODE','Syntax\n------\n\nENCODE(str,pass_str)\n\nDescription\n-----------\n\nENCODE is not considered cryptographically secure, and should not be used for\npassword encryption.\n\nEncrypt str using pass_str as the password. To decrypt the result, use\nDECODE().\n\nThe result is a binary string of the same length as str.\n\nThe strength of the encryption is based on how good the random generator is.\n\nIt is not recommended to rely on the encryption performed by the ENCODE\nfunction. Using a salt value (changed when a password is updated) will improve\nmatters somewhat, but for storing passwords, consider a more cryptographically\nsecure function, such as SHA2().\n\nExamples\n--------\n\nENCODE(\'not so secret text\', CONCAT(\'random_salt\',\'password\'))\n\nURL: https://mariadb.com/kb/en/encode/','','https://mariadb.com/kb/en/encode/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (133,12,'ENCRYPT','Syntax\n------\n\nENCRYPT(str[,salt])\n\nDescription\n-----------\n\nEncrypts a string using the Unix crypt() system call, returning an encrypted\nbinary string. The salt argument should be a string with at least two\ncharacters or the returned result will be NULL. If no salt argument is given,\na random value of sufficient length is used.\n\nIt is not recommended to use ENCRYPT() with utf16, utf32 or ucs2 multi-byte\ncharacter sets because the crypt() system call expects a string terminated\nwith a zero byte.\n\nNote that the underlying crypt() system call may have some limitations, such\nas ignoring all but the first eight characters.\n\nIf the have_crypt system variable is set to NO (because the crypt() system\ncall is not available), the ENCRYPT function will always return NULL.\n\nExamples\n--------\n\nSELECT ENCRYPT(\'encrypt me\');\n+-----------------------+\n| ENCRYPT(\'encrypt me\') |\n+-----------------------+\n| 4I5BsEx0lqTDk |\n+-----------------------+\n\nURL: https://mariadb.com/kb/en/encrypt/','','https://mariadb.com/kb/en/encrypt/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (134,12,'KDF','MariaDB starting with 11.3\n--------------------------\nKDF() is a key derivation function introduced in MariaDB 11.3.0.\n\nSyntax\n------\n\nKDF(key_str, salt [, {info | iterations} [, kdf_name [, width ]]])\n\nDescription\n-----------\n\nKDF is a key derivation function, similar to OpenSSL\'s EVP_KDF_derive(). The\npurpose of a KDF is to be slow, so if the calculated value is lost/stolen, the\noriginal key_str is not achievable easily with modern GPU. KDFs are therefore\nan ideal replacement for password hashes. KDFs can also pad out a password\nsecret to the number of bits used in encryption algorithms.\n\nFor generating good encryption keys for AES_ENCRYPT a less expensive function,\nbut cryptographically secure function like RANDOM_BYTES is recommended..\n\n* kdf_name is \"hkdf\" or \"pbkdf2_hmac\" (default)\n* width (in bits) can be any number divisible by 8, by default it\'s taken from\n@@block_encryption_mode\n* iterations must be positive, and is 1000 by default\n\nNote that OpenSSL 1.0 doesn\'t support HKDF, so in this case NULL is returned.\nThis OpenSSL version is still used in SLES 12 and CentOS 7.\n\nExamples\n--------\n\nselect hex(kdf(\'foo\', \'bar\', \'infa\', \'hkdf\')); \n+----------------------------------------+\n| hex(kdf(\'foo\', \'bar\', \'infa\', \'hkdf\')) |\n+----------------------------------------+\n| 612875F859CFB4EE0DFEFF9F2A18E836 |\n+----------------------------------------+\n\nURL: https://mariadb.com/kb/en/kdf/','','https://mariadb.com/kb/en/kdf/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (135,12,'MD5','Syntax\n------\n\nMD5(str)\n\nDescription\n-----------\n\nCalculates an MD5 128-bit checksum for the string.\n\nThe return value is a 32-hex digit string, and as of MariaDB 5.5, is a\nnonbinary string in the connection character set and collation, determined by\nthe values of the character_set_connection and collation_connection system\nvariables. Before 5.5, the return value was a binary string.\n\nNULL is returned if the argument was NULL.\n\nExamples\n--------\n\nSELECT MD5(\'testing\');\n+----------------------------------+\n| MD5(\'testing\') |\n+----------------------------------+\n| ae2b1fca515949e5d54fb22b8ed95575 |\n+----------------------------------+\n\nURL: https://mariadb.com/kb/en/md5/','','https://mariadb.com/kb/en/md5/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (136,12,'OLD_PASSWORD','Syntax\n------\n\nOLD_PASSWORD(str)\n\nDescription\n-----------\n\nOLD_PASSWORD() was added to MySQL when the implementation of PASSWORD() was\nchanged to improve security. OLD_PASSWORD() returns the value of the old\n(pre-MySQL 4.1) implementation of PASSWORD() as a string, and is intended to\npermit you to reset passwords for any pre-4.1 clients that need to connect to\na more recent MySQL server version, or any version of MariaDB, without locking\nthem out.\n\nAs of MariaDB 5.5, the return value is a nonbinary string in the connection\ncharacter set and collation, determined by the values of the\ncharacter_set_connection and collation_connection system variables. Before\n5.5, the return value was a binary string.\n\nThe return value is 16 bytes in length, or NULL if the argument was NULL.\n\nURL: https://mariadb.com/kb/en/old_password/','','https://mariadb.com/kb/en/old_password/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (137,12,'PASSWORD','Syntax\n------\n\nPASSWORD(str)\n\nDescription\n-----------\n\nThe PASSWORD() function is used for hashing passwords for use in\nauthentication by the MariaDB server. It is not intended for use in other\napplications.\n\nCalculates and returns a hashed password string from the plaintext password\nstr. Returns an empty string (>= MariaDB 10.0.4) if the argument was NULL.\n\nThe return value is a nonbinary string in the connection character set and\ncollation, determined by the values of the character_set_connection and\ncollation_connection system variables.\n\nThis is the function that is used for hashing MariaDB passwords for storage in\nthe Password column of the user table (see privileges), usually used with the\nSET PASSWORD statement. It is not intended for use in other applications.\n\nUntil MariaDB 10.3, the return value is 41-bytes in length, and the first\ncharacter is always \'*\'. From MariaDB 10.4, the function takes into account\nthe authentication plugin where applicable (A CREATE USER or SET PASSWORD\nstatement). For example, when used in conjunction with a user authenticated by\nthe ed25519 plugin, the statement will create a longer hash:\n\nCREATE USER edtest@localhost IDENTIFIED VIA ed25519 USING PASSWORD(\'secret\');\n\nCREATE USER edtest2@localhost IDENTIFIED BY \'secret\';\n\nSELECT CONCAT(user, \'@\', host, \' => \', JSON_DETAILED(priv)) FROM\nmysql.global_priv\n WHERE user LIKE \'edtest%\'\\G\n*************************** 1. row ***************************\nCONCAT(user, \'@\', host, \' => \', JSON_DETAILED(priv)): edtest@localhost => {\n...\n \"plugin\": \"ed25519\",\n \"authentication_string\": \"ZIgUREUg5PVgQ6LskhXmO+eZLS0nC8be6HPjYWR4YJY\",\n...\n}\n*************************** 2. row ***************************\nCONCAT(user, \'@\', host, \' => \', JSON_DETAILED(priv)): edtest2@localhost => {\n...\n \"plugin\": \"mysql_native_password\",\n \"authentication_string\": \"*14E65567ABDB5135D0CFD9A70B3032C179A49EE7\",\n...\n}\n\nThe behavior of this function is affected by the value of the old_passwords\nsystem variable. If this is set to 1 (0 is default), MariaDB reverts to using\nthe mysql_old_password authentication plugin by default for newly created\nusers and passwords.\n\nExamples\n--------\n\nSELECT PASSWORD(\'notagoodpwd\');\n+-------------------------------------------+\n| PASSWORD(\'notagoodpwd\') |\n+-------------------------------------------+\n| *3A70EE9FC6594F88CE9E959CD51C5A1C002DC937 |\n+-------------------------------------------+\n\nSET PASSWORD FOR \'bob\'@\'%.loc.gov\' = PASSWORD(\'newpass\');\n\nURL: https://mariadb.com/kb/en/password/','','https://mariadb.com/kb/en/password/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (138,12,'RANDOM_BYTES','MariaDB starting with 10.10.0\n-----------------------------\nThe RANDOM_BYTES function generates a binary string of random bytes. It was\nadded in MariaDB 10.10.0.\n\nSyntax\n------\n\nRANDOM_BYTES(length)\n\nDescription\n-----------\n\nGiven a length from 1 to 1024, generates a binary string of length consisting\nof random bytes generated by the SSL library\'s random number generator.\n\nSee the RAND_bytes() function documentation of your SSL library for\ninformation on the random number generator. In the case of OpenSSL, a\ncryptographically secure pseudo random generator (CSPRNG) is used.\n\nStatements containing the RANDOM_BYTES function are unsafe for statement-based\nreplication.\n\nAn error occurs if length is outside the range 1 to 1024.\n\nURL: https://mariadb.com/kb/en/random_bytes/','','https://mariadb.com/kb/en/random_bytes/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (139,12,'SHA1','Syntax\n------\n\nSHA1(str), SHA(str)\n\nDescription\n-----------\n\nCalculates an SHA-1 160-bit checksum for the string str, as described in RFC\n3174 (Secure Hash Algorithm).\n\nThe value is returned as a string of 40 hex digits, or NULL if the argument\nwas NULL. As of MariaDB 5.5, the return value is a nonbinary string in the\nconnection character set and collation, determined by the values of the\ncharacter_set_connection and collation_connection system variables. Before\n5.5, the return value was a binary string.\n\nExamples\n--------\n\nSELECT SHA1(\'some boring text\');\n+------------------------------------------+\n| SHA1(\'some boring text\') |\n+------------------------------------------+\n| af969fc2085b1bb6d31e517d5c456def5cdd7093 |\n+------------------------------------------+\n\nURL: https://mariadb.com/kb/en/sha1/','','https://mariadb.com/kb/en/sha1/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (140,12,'SHA2','Syntax\n------\n\nSHA2(str,hash_len)\n\nDescription\n-----------\n\nGiven a string str, calculates an SHA-2 checksum, which is considered more\ncryptographically secure than its SHA-1 equivalent. The SHA-2 family includes\nSHA-224, SHA-256, SHA-384, and SHA-512, and the hash_len must correspond to\none of these, i.e. 224, 256, 384 or 512. 0 is equivalent to 256.\n\nThe return value is a nonbinary string in the connection character set and\ncollation, determined by the values of the character_set_connection and\ncollation_connection system variables.\n\nNULL is returned if the hash length is not valid, or the string str is NULL.\n\nSHA2 will only work if MariaDB was has been configured with TLS support.\n\nExamples\n--------\n\nSELECT SHA2(\'Maria\',224);\n+----------------------------------------------------------+\n| SHA2(\'Maria\',224) |\n+----------------------------------------------------------+\n| 6cc67add32286412efcab9d0e1675a43a5c2ef3cec8879f81516ff83 |\n+----------------------------------------------------------+\n\nSELECT SHA2(\'Maria\',256);\n+------------------------------------------------------------------+\n| SHA2(\'Maria\',256) |\n+------------------------------------------------------------------+\n| 9ff18ebe7449349f358e3af0b57cf7a032c1c6b2272cb2656ff85eb112232f16 |\n+------------------------------------------------------------------+\n\nSELECT SHA2(\'Maria\',0);\n+------------------------------------------------------------------+\n| SHA2(\'Maria\',0) |\n+------------------------------------------------------------------+\n| 9ff18ebe7449349f358e3af0b57cf7a032c1c6b2272cb2656ff85eb112232f16 |\n+------------------------------------------------------------------+\n\nURL: https://mariadb.com/kb/en/sha2/','','https://mariadb.com/kb/en/sha2/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (141,13,'ENDPOINT','A synonym for ST_ENDPOINT.\n\nURL: https://mariadb.com/kb/en/linestring-properties-endpoint/','','https://mariadb.com/kb/en/linestring-properties-endpoint/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (142,13,'GLENGTH','Syntax\n------\n\nGLength(ls)\n\nDescription\n-----------\n\nReturns as a double-precision number the length of the LineString value ls in\nits associated spatial reference.\n\nExamples\n--------\n\nSET @ls = \'LineString(1 1,2 2,3 3)\';\n\nSELECT GLength(GeomFromText(@ls));\n+----------------------------+\n| GLength(GeomFromText(@ls)) |\n+----------------------------+\n| 2.82842712474619 |\n+----------------------------+\n\nURL: https://mariadb.com/kb/en/glength/','','https://mariadb.com/kb/en/glength/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (143,13,'NumPoints','A synonym for ST_NumPoints.\n\nURL: https://mariadb.com/kb/en/linestring-properties-numpoints/','','https://mariadb.com/kb/en/linestring-properties-numpoints/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (144,13,'PointN','A synonym for ST_PointN.\n\nURL: https://mariadb.com/kb/en/linestring-properties-pointn/','','https://mariadb.com/kb/en/linestring-properties-pointn/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (145,13,'STARTPOINT','A synonym for ST_STARTPOINT.\n\nURL: https://mariadb.com/kb/en/linestring-properties-startpoint/','','https://mariadb.com/kb/en/linestring-properties-startpoint/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (146,13,'ST_ENDPOINT','Syntax\n------\n\nST_EndPoint(ls)\nEndPoint(ls)\n\nDescription\n-----------\n\nReturns the Point that is the endpoint of the LineString value ls.\n\nST_EndPoint() and EndPoint() are synonyms.\n\nExamples\n--------\n\nSET @ls = \'LineString(1 1,2 2,3 3)\';\n\nSELECT AsText(EndPoint(GeomFromText(@ls)));\n+-------------------------------------+\n| AsText(EndPoint(GeomFromText(@ls))) |\n+-------------------------------------+\n| POINT(3 3) |\n+-------------------------------------+\n\nURL: https://mariadb.com/kb/en/st_endpoint/','','https://mariadb.com/kb/en/st_endpoint/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (147,13,'ST_NUMPOINTS','Syntax\n------\n\nST_NumPoints(ls)\nNumPoints(ls)\n\nDescription\n-----------\n\nReturns the number of Point objects in the LineString value ls.\n\nST_NumPoints() and NumPoints() are synonyms.\n\nExamples\n--------\n\nSET @ls = \'LineString(1 1,2 2,3 3)\';\n\nSELECT NumPoints(GeomFromText(@ls));\n+------------------------------+\n| NumPoints(GeomFromText(@ls)) |\n+------------------------------+\n| 3 |\n+------------------------------+\n\nURL: https://mariadb.com/kb/en/st_numpoints/','','https://mariadb.com/kb/en/st_numpoints/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (148,13,'ST_POINTN','Syntax\n------\n\nST_PointN(ls,N)\nPointN(ls,N)\n\nDescription\n-----------\n\nReturns the N-th Point in the LineString value ls. Points are numbered\nbeginning with 1.\n\nST_PointN() and PointN() are synonyms.\n\nExamples\n--------\n\nSET @ls = \'LineString(1 1,2 2,3 3)\';\n\nSELECT AsText(PointN(GeomFromText(@ls),2));\n+-------------------------------------+\n| AsText(PointN(GeomFromText(@ls),2)) |\n+-------------------------------------+\n| POINT(2 2) |\n+-------------------------------------+\n\nURL: https://mariadb.com/kb/en/st_pointn/','','https://mariadb.com/kb/en/st_pointn/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (149,13,'ST_STARTPOINT','Syntax\n------\n\nST_StartPoint(ls)\nStartPoint(ls)\n\nDescription\n-----------\n\nReturns the Point that is the start point of the LineString value ls.\n\nST_StartPoint() and StartPoint() are synonyms.\n\nExamples\n--------\n\nSET @ls = \'LineString(1 1,2 2,3 3)\';\n\nSELECT AsText(StartPoint(GeomFromText(@ls)));\n+---------------------------------------+\n| AsText(StartPoint(GeomFromText(@ls))) |\n+---------------------------------------+\n| POINT(1 1) |\n+---------------------------------------+\n\nURL: https://mariadb.com/kb/en/st_startpoint/','','https://mariadb.com/kb/en/st_startpoint/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (150,14,'GET_LOCK','Syntax\n------\n\nGET_LOCK(str,timeout)\n\nDescription\n-----------\n\nTries to obtain a lock with a name given by the string str, using a timeout of\ntimeout seconds. Returns 1 if the lock was obtained successfully, 0 if the\nattempt timed out (for example, because another client has previously locked\nthe name), or NULL if an error occurred (such as running out of memory or the\nthread was killed with mariadb-admin kill).\n\nA lock is released with RELEASE_LOCK(), when the connection terminates (either\nnormally or abnormally). A connection can hold multiple locks at the same\ntime, so a lock that is no longer needed needs to be explicitly released.\n\nThe IS_FREE_LOCK function returns whether a specified lock a free or not, and\nthe IS_USED_LOCK whether the function is in use or not.\n\nLocks obtained with GET_LOCK() do not interact with transactions. That is,\ncommitting a transaction does not release any such locks obtained during the\ntransaction.\n\nIt is also possible to recursively set the same lock. If a lock with the same\nname is set n times, it needs to be released n times as well.\n\nstr is case insensitive for GET_LOCK() and related functions. If str is an\nempty string or NULL, GET_LOCK() returns NULL and does nothing. timeout\nsupports microseconds.\n\nIf the metadata_lock_info plugin is installed, locks acquired with this\nfunction are visible in the Information Schema METADATA_LOCK_INFO table.\n\nThis function can be used to implement application locks or to simulate record\nlocks. Names are locked on a server-wide basis. If a name has been locked by\none client, GET_LOCK() blocks any request by another client for a lock with\nthe same name. This allows clients that agree on a given lock name to use the\nname to perform cooperative advisory locking. But be aware that it also allows\na client that is not among the set of cooperating clients to lock a name,\neither inadvertently or deliberately, and thus prevent any of the cooperating\nclients from locking that name. One way to reduce the likelihood of this is to\nuse lock names that are database-specific or application-specific. For\nexample, use lock names of the form db_name.str or app_name.str.\n\nStatements using the GET_LOCK function are not safe for statement-based\nreplication.\n\nThe patch to permit multiple locks was contributed by Konstantin \"Kostja\"\nOsipov (MDEV-3917).\n\nExamples\n--------\n\nSELECT GET_LOCK(\'lock1\',10);\n+----------------------+\n| GET_LOCK(\'lock1\',10) |\n+----------------------+\n| 1 |\n+----------------------+\n\nSELECT IS_FREE_LOCK(\'lock1\'), IS_USED_LOCK(\'lock1\');\n+-----------------------+-----------------------+\n| IS_FREE_LOCK(\'lock1\') | IS_USED_LOCK(\'lock1\') |\n+-----------------------+-----------------------+\n| 0 | 46 |\n+-----------------------+-----------------------+\n\nSELECT IS_FREE_LOCK(\'lock2\'), IS_USED_LOCK(\'lock2\');\n+-----------------------+-----------------------+\n| IS_FREE_LOCK(\'lock2\') | IS_USED_LOCK(\'lock2\') |\n+-----------------------+-----------------------+\n| 1 | NULL |\n+-----------------------+-----------------------+\n\nMultiple locks can be held:\n\nSELECT GET_LOCK(\'lock2\',10);\n+----------------------+\n| GET_LOCK(\'lock2\',10) |\n+----------------------+\n| 1 |\n+----------------------+\n\nSELECT IS_FREE_LOCK(\'lock1\'), IS_FREE_LOCK(\'lock2\');\n+-----------------------+-----------------------+\n| IS_FREE_LOCK(\'lock1\') | IS_FREE_LOCK(\'lock2\') |\n+-----------------------+-----------------------+\n| 0 | 0 |\n+-----------------------+-----------------------+\n\nSELECT RELEASE_LOCK(\'lock1\'), RELEASE_LOCK(\'lock2\');\n+-----------------------+-----------------------+\n| RELEASE_LOCK(\'lock1\') | RELEASE_LOCK(\'lock2\') |\n+-----------------------+-----------------------+\n| 1 | 1 |\n+-----------------------+-----------------------+\n\nIt is possible to hold the same lock recursively. This example is viewed using\nthe metadata_lock_info plugin:\n\nSELECT GET_LOCK(\'lock3\',10);\n+----------------------+\n| GET_LOCK(\'lock3\',10) |\n+----------------------+\n| 1 |\n+----------------------+\n\nSELECT GET_LOCK(\'lock3\',10);\n+----------------------+\n| GET_LOCK(\'lock3\',10) |\n+----------------------+\n| 1 |\n+----------------------+\n\nSELECT * FROM INFORMATION_SCHEMA.METADATA_LOCK_INFO;\n+-----------+---------------------+---------------+-----------+--------------+-\n----------+\n| THREAD_ID | LOCK_MODE | LOCK_DURATION | LOCK_TYPE | TABLE_SCHEMA |\nTABLE_NAME |\n+-----------+---------------------+---------------+-----------+--------------+-\n----------+\n| 46 | MDL_SHARED_NO_WRITE | NULL | User lock | lock3 |\n |\n+-----------+---------------------+---------------+-----------+--------------+-\n----------+\n\nSELECT RELEASE_LOCK(\'lock3\');\n+-----------------------+\n| RELEASE_LOCK(\'lock3\') |\n+-----------------------+\n| 1 |\n+-----------------------+\n\nSELECT * FROM INFORMATION_SCHEMA.METADATA_LOCK_INFO;\n+-----------+---------------------+---------------+-----------+--------------+-\n----------+\n| THREAD_ID | LOCK_MODE | LOCK_DURATION | LOCK_TYPE | TABLE_SCHEMA |\nTABLE_NAME |\n+-----------+---------------------+---------------+-----------+--------------+-\n----------+\n| 46 | MDL_SHARED_NO_WRITE | NULL | User lock | lock3 |\n |\n+-----------+---------------------+---------------+-----------+--------------+-\n----------+\n\nSELECT RELEASE_LOCK(\'lock3\');\n+-----------------------+\n| RELEASE_LOCK(\'lock3\') |\n+-----------------------+\n| 1 |\n+-----------------------+\n\nSELECT * FROM INFORMATION_SCHEMA.METADATA_LOCK_INFO;\nEmpty set (0.000 sec)\n\nTimeout example: Connection 1:\n\nSELECT GET_LOCK(\'lock4\',10);\n+----------------------+\n| GET_LOCK(\'lock4\',10) |\n+----------------------+\n| 1 |\n+----------------------+\n\nConnection 2:\n\nSELECT GET_LOCK(\'lock4\',10);\n\nAfter 10 seconds...\n\n+----------------------+\n| GET_LOCK(\'lock4\',10) |\n+----------------------+\n| 0 |\n+----------------------+\n\nDeadlocks are automatically detected and resolved. Connection 1:\n\nSELECT GET_LOCK(\'lock5\',10); \n+----------------------+\n| GET_LOCK(\'lock5\',10) |\n+----------------------+\n| 1 |\n+----------------------+\n\nConnection 2:\n\nSELECT GET_LOCK(\'lock6\',10);\n+----------------------+\n| GET_LOCK(\'lock6\',10) |\n+----------------------+\n| 1 |\n+----------------------+\n\nConnection 1:\n\nSELECT GET_LOCK(\'lock6\',10); \n+----------------------+\n| GET_LOCK(\'lock6\',10) |\n+----------------------+\n| 0 |\n+----------------------+\n\nConnection 2:\n\nSELECT GET_LOCK(\'lock5\',10);\nERROR 1213 (40001): Deadlock found when trying to get lock; try restarting\ntransaction\n\nURL: https://mariadb.com/kb/en/get_lock/','','https://mariadb.com/kb/en/get_lock/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (151,14,'INET6_ATON','Syntax\n------\n\nINET6_ATON(expr)\n\nDescription\n-----------\n\nGiven an IPv6 or IPv4 network address as a string, returns a binary string\nthat represents the numeric value of the address.\n\nNo trailing zone ID\'s or traling network masks are permitted. For IPv4\naddresses, or IPv6 addresses with IPv4 address parts, no classful addresses or\ntrailing port numbers are permitted and octal numbers are not supported.\n\nThe returned binary string will be VARBINARY(16) or VARBINARY(4) for IPv6 and\nIPv4 addresses respectively.\n\nReturns NULL if the argument is not understood.\n\nMariaDB starting with 10.5.0\n----------------------------\nFrom MariaDB 10.5.0, INET6_ATON can take INET6 as an argument.\n\nExamples\n--------\n\nSELECT HEX(INET6_ATON(\'10.0.1.1\'));\n+-----------------------------+\n| HEX(INET6_ATON(\'10.0.1.1\')) |\n+-----------------------------+\n| 0A000101 |\n+-----------------------------+\n\nSELECT HEX(INET6_ATON(\'48f3::d432:1431:ba23:846f\'));\n+----------------------------------------------+\n| HEX(INET6_ATON(\'48f3::d432:1431:ba23:846f\')) |\n+----------------------------------------------+\n| 48F3000000000000D4321431BA23846F |\n+----------------------------------------------+\n\nURL: https://mariadb.com/kb/en/inet6_aton/','','https://mariadb.com/kb/en/inet6_aton/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (152,14,'INET6_NTOA','Syntax\n------\n\nINET6_NTOA(expr)\n\nDescription\n-----------\n\nGiven an IPv6 or IPv4 network address as a numeric binary string, returns the\naddress as a nonbinary string in the connection character set.\n\nThe return string is lowercase, and is platform independent, since it does not\nuse functions specific to the operating system. It has a maximum length of 39\ncharacters.\n\nReturns NULL if the argument is not understood.\n\nExamples\n--------\n\nSELECT INET6_NTOA(UNHEX(\'0A000101\'));\n+-------------------------------+\n| INET6_NTOA(UNHEX(\'0A000101\')) |\n+-------------------------------+\n| 10.0.1.1 |\n+-------------------------------+\n\nSELECT INET6_NTOA(UNHEX(\'48F3000000000000D4321431BA23846F\'));\n+-------------------------------------------------------+\n| INET6_NTOA(UNHEX(\'48F3000000000000D4321431BA23846F\')) |\n+-------------------------------------------------------+\n| 48f3::d432:1431:ba23:846f |\n+-------------------------------------------------------+\n\nURL: https://mariadb.com/kb/en/inet6_ntoa/','','https://mariadb.com/kb/en/inet6_ntoa/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (153,14,'INET_ATON','Syntax\n------\n\nINET_ATON(expr)\n\nDescription\n-----------\n\nGiven the dotted-quad representation of an IPv4 network address as a string,\nreturns an integer that represents the numeric value of the address. Addresses\nmay be 4- or 8-byte addresses.\n\nReturns NULL if the argument is not understood.\n\nExamples\n--------\n\nSELECT INET_ATON(\'192.168.1.1\');\n+--------------------------+\n| INET_ATON(\'192.168.1.1\') |\n+--------------------------+\n| 3232235777 |\n+--------------------------+\n\nThis is calculated as follows: 192 x 2563 + 168 x 256 2 + 1 x 256 + 1\n\nURL: https://mariadb.com/kb/en/inet_aton/','','https://mariadb.com/kb/en/inet_aton/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (154,14,'INET_NTOA','Syntax\n------\n\nINET_NTOA(expr)\n\nDescription\n-----------\n\nGiven a numeric IPv4 network address in network byte order (4 or 8 byte),\nreturns the dotted-quad representation of the address as a string.\n\nExamples\n--------\n\nSELECT INET_NTOA(3232235777);\n+-----------------------+\n| INET_NTOA(3232235777) |\n+-----------------------+\n| 192.168.1.1 |\n+-----------------------+\n\n192.168.1.1 corresponds to 3232235777 since 192 x 2563 + 168 x 256 2 + 1 x 256\n+ 1 = 3232235777\n\nURL: https://mariadb.com/kb/en/inet_ntoa/','','https://mariadb.com/kb/en/inet_ntoa/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (155,14,'IS_FREE_LOCK','Syntax\n------\n\nIS_FREE_LOCK(str)\n\nDescription\n-----------\n\nChecks whether the lock named str is free to use (that is, not locked).\nReturns 1 if the lock is free (no one is using the lock), 0 if the lock is in\nuse, and NULL if an error occurs (such as an incorrect argument, like an empty\nstring or NULL). str is case insensitive.\n\nIf the metadata_lock_info plugin is installed, the Information Schema\nmetadata_lock_info table contains information about locks of this kind (as\nwell as metadata locks).\n\nStatements using the IS_FREE_LOCK function are not safe for statement-based\nreplication.\n\nURL: https://mariadb.com/kb/en/is_free_lock/','','https://mariadb.com/kb/en/is_free_lock/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (156,14,'IS_IPV4','Syntax\n------\n\nIS_IPV4(expr)\n\nDescription\n-----------\n\nIf the expression is a valid IPv4 address, returns 1, otherwise returns 0.\n\nIS_IPV4() is stricter than INET_ATON(), but as strict as INET6_ATON(), in\ndetermining the validity of an IPv4 address. This implies that if IS_IPV4\nreturns 1, the same expression will always return a non-NULL result when\npassed to INET_ATON(), but that the reverse may not apply.\n\nExamples\n--------\n\nSELECT IS_IPV4(\'1110.0.1.1\');\n+-----------------------+\n| IS_IPV4(\'1110.0.1.1\') |\n+-----------------------+\n| 0 |\n+-----------------------+\n\nSELECT IS_IPV4(\'48f3::d432:1431:ba23:846f\');\n+--------------------------------------+\n| IS_IPV4(\'48f3::d432:1431:ba23:846f\') |\n+--------------------------------------+\n| 0 |\n+--------------------------------------+\n\nURL: https://mariadb.com/kb/en/is_ipv4/','','https://mariadb.com/kb/en/is_ipv4/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (157,14,'IS_IPV4_COMPAT','Syntax\n------\n\nIS_IPV4_COMPAT(expr)\n\nDescription\n-----------\n\nReturns 1 if a given numeric binary string IPv6 address, such as returned by\nINET6_ATON(), is IPv4-compatible, otherwise returns 0.\n\nMariaDB starting with 10.5.0\n----------------------------\nFrom MariaDB 10.5.0, when the argument is not INET6, automatic implicit CAST\nto INET6 is applied. As a consequence, IS_IPV4_COMPAT now understands\narguments in both text representation and binary(16) representation. Before\nMariaDB 10.5.0, the function understood only binary(16) representation.\n\nExamples\n--------\n\nSELECT IS_IPV4_COMPAT(INET6_ATON(\'::10.0.1.1\'));\n+------------------------------------------+\n| IS_IPV4_COMPAT(INET6_ATON(\'::10.0.1.1\')) |\n+------------------------------------------+\n| 1 |\n+------------------------------------------+\n\nSELECT IS_IPV4_COMPAT(INET6_ATON(\'::48f3::d432:1431:ba23:846f\'));\n+-----------------------------------------------------------+\n| IS_IPV4_COMPAT(INET6_ATON(\'::48f3::d432:1431:ba23:846f\')) |\n+-----------------------------------------------------------+\n| 0 |\n+-----------------------------------------------------------+\n\nURL: https://mariadb.com/kb/en/is_ipv4_compat/','','https://mariadb.com/kb/en/is_ipv4_compat/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (158,14,'IS_IPV4_MAPPED','Syntax\n------\n\nIS_IPV4_MAPPED(expr)\n\nDescription\n-----------\n\nReturns 1 if a given a numeric binary string IPv6 address, such as returned by\nINET6_ATON(), is a valid IPv4-mapped address, otherwise returns 0.\n\nMariaDB starting with 10.5.0\n----------------------------\nFrom MariaDB 10.5.0, when the argument is not INET6, automatic implicit CAST\nto INET6 is applied. As a consequence, IS_IPV4_MAPPED now understands\narguments in both text representation and binary(16) representation. Before\nMariaDB 10.5.0, the function understood only binary(16) representation.\n\nExamples\n--------\n\nSELECT IS_IPV4_MAPPED(INET6_ATON(\'::10.0.1.1\'));\n+------------------------------------------+\n| IS_IPV4_MAPPED(INET6_ATON(\'::10.0.1.1\')) |\n+------------------------------------------+\n| 0 |\n+------------------------------------------+\n\nSELECT IS_IPV4_MAPPED(INET6_ATON(\'::ffff:10.0.1.1\'));\n+-----------------------------------------------+\n| IS_IPV4_MAPPED(INET6_ATON(\'::ffff:10.0.1.1\')) |\n+-----------------------------------------------+\n| 1 |\n+-----------------------------------------------+\n\nURL: https://mariadb.com/kb/en/is_ipv4_mapped/','','https://mariadb.com/kb/en/is_ipv4_mapped/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (159,14,'IS_IPV6','Syntax\n------\n\nIS_IPV6(expr)\n\nDescription\n-----------\n\nReturns 1 if the expression is a valid IPv6 address specified as a string,\notherwise returns 0. Does not consider IPv4 addresses to be valid IPv6\naddresses.\n\nExamples\n--------\n\nSELECT IS_IPV6(\'48f3::d432:1431:ba23:846f\');\n+--------------------------------------+\n| IS_IPV6(\'48f3::d432:1431:ba23:846f\') |\n+--------------------------------------+\n| 1 |\n+--------------------------------------+\n1 row in set (0.02 sec)\n\nSELECT IS_IPV6(\'10.0.1.1\');\n+---------------------+\n| IS_IPV6(\'10.0.1.1\') |\n+---------------------+\n| 0 |\n+---------------------+\n\nURL: https://mariadb.com/kb/en/is_ipv6/','','https://mariadb.com/kb/en/is_ipv6/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (160,14,'IS_USED_LOCK','Syntax\n------\n\nIS_USED_LOCK(str)\n\nDescription\n-----------\n\nChecks whether the lock named str is in use (that is, locked). If so, it\nreturns the connection identifier of the client that holds the lock.\nOtherwise, it returns NULL. str is case insensitive.\n\nIf the metadata_lock_info plugin is installed, the Information Schema\nmetadata_lock_info table contains information about locks of this kind (as\nwell as metadata locks).\n\nStatements using the IS_USED_LOCK function are not safe for statement-based\nreplication.\n\nURL: https://mariadb.com/kb/en/is_used_lock/','','https://mariadb.com/kb/en/is_used_lock/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (161,14,'MASTER_GTID_WAIT','Syntax\n------\n\nMASTER_GTID_WAIT(gtid-list[, timeout)\n\nDescription\n-----------\n\nThis function takes a string containing a comma-separated list of global\ntransaction id\'s (similar to the value of, for example, gtid_binlog_pos). It\nwaits until the value of gtid_slave_pos has the same or higher seq_no within\nall replication domains specified in the gtid-list; in other words, it waits\nuntil the slave has reached the specified GTID position.\n\nAn optional second argument gives a timeout in seconds. If the timeout expires\nbefore the specified GTID position is reached, then the function returns -1.\nPassing NULL or a negative number for the timeout means no timeout, and the\nfunction will wait indefinitely.\n\nIf the wait completes without a timeout, 0 is returned. Passing NULL for the\ngtid-list makes the function return NULL immediately, without waiting.\n\nThe gtid-list may be the empty string, in which case MASTER_GTID_WAIT()\nreturns immediately. If the gtid-list contains fewer domains than\ngtid_slave_pos, then only those domains are waited upon. If gtid-list contains\na domain that is not present in @@gtid_slave_pos, then MASTER_GTID_WAIT() will\nwait until an event containing such domain_id arrives on the slave (or until\ntimed out or killed).\n\nMASTER_GTID_WAIT() can be useful to ensure that a slave has caught up to a\nmaster. Simply take the value of gtid_binlog_pos on the master, and use it in\na MASTER_GTID_WAIT() call on the slave; when the call completes, the slave\nwill have caught up with that master position.\n\nMASTER_GTID_WAIT() can also be used in client applications together with the\nlast_gtid session variable. This is useful in a read-scaleout replication\nsetup, where the application writes to a single master but divides the reads\nout to a number of slaves to distribute the load. In such a setup, there is a\nrisk that an application could first do an update on the master, and then a\nbit later do a read on a slave, and if the slave is not fast enough, the data\nread from the slave might not include the update just made, possibly confusing\nthe application and/or the end-user. One way to avoid this is to request the\nvalue of last_gtid on the master just after the update. Then before doing the\nread on the slave, do a MASTER_GTID_WAIT() on the value obtained from the\nmaster; this will ensure that the read is not performed until the slave has\nreplicated sufficiently far for the update to have become visible.\n\nNote that MASTER_GTID_WAIT() can be used even if the slave is configured not\nto use GTID for connections (CHANGE MASTER TO master_use_gtid=no). This is\nbecause from MariaDB 10, GTIDs are always logged on the master server, and\nalways recorded on the slave servers.\n\nDifferences to MASTER_POS_WAIT()\n--------------------------------\n\n* MASTER_GTID_WAIT() is global; it waits for any master connection to reach\n the specified GTID position. MASTER_POS_WAIT() works only against a\n specific connection. This also means that while MASTER_POS_WAIT() aborts if\n its master connection is terminated with STOP SLAVE or due to an error,\n MASTER_GTID_WAIT() continues to wait while slaves are stopped.\n\n* MASTER_GTID_WAIT() can take its timeout as a floating-point value, so a\n timeout in fractional seconds is supported, eg. MASTER_GTID_WAIT(\"0-1-100\",\n 0.5). (The minimum wait is one microsecond, 0.000001 seconds).\n\n* MASTER_GTID_WAIT() allows one to specify a timeout of zero in order to do a\n non-blocking check to see if the slaves have progressed to a specific GTID\nposition\n (MASTER_POS_WAIT() takes a zero timeout as meaning an infinite wait). To do\n an infinite MASTER_GTID_WAIT(), specify a negative timeout, or omit the\n timeout argument.\n\n* MASTER_GTID_WAIT() does not return the number of events executed since the\n wait started, nor does it return NULL if a slave thread is stopped. It\n always returns either 0 for successful wait completed, or -1 for timeout\n reached (or NULL if the specified gtid-pos is NULL).\n\nSince MASTER_GTID_WAIT() looks only at the seq_no part of the GTIDs, not the\nserver_id, care is needed if a slave becomes diverged from another server so\nthat two different GTIDs with the same seq_no (in the same domain) arrive at\nthe same server. This situation is in any case best avoided; setting\ngtid_strict_mode is recommended, as this will prevent any such out-of-order\nsequence numbers from ever being replicated on a slave.\n\nURL: https://mariadb.com/kb/en/master_gtid_wait/','','https://mariadb.com/kb/en/master_gtid_wait/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (162,14,'MASTER_POS_WAIT','Syntax\n------\n\nMASTER_POS_WAIT(log_name,log_pos[,timeout,[\"connection_name\"]])\n\nDescription\n-----------\n\nThis function is useful in replication for controlling master/slave\nsynchronization. It blocks until the slave has read and applied all updates\nup to the specified position (log_name,log_pos) in the binary log. The return\nvalue is the number of log events the slave had to wait for to advance to\nthe specified position. The function returns NULL if the slave SQL thread is\nnot started, the slave\'s master information is not initialized, the\narguments are incorrect, or an error occurs. It returns -1 if the timeout has\nbeen exceeded. If the slave SQL thread stops while MASTER_POS_WAIT() is\nwaiting, the function returns NULL. If the slave is past the specified\nposition, the function returns immediately.\n\nIf a timeout value is specified, MASTER_POS_WAIT() stops waiting when timeout\nseconds have elapsed. timeout must be greater than 0; a zero or negative\ntimeout means no timeout.\n\nThe connection_name is used when you are using multi-source-replication. If\nyou don\'t specify it, it\'s set to the value of the default_master_connection\nsystem variable.\n\nStatements using the MASTER_POS_WAIT() function are not safe for\nstatement-based replication.\n\nURL: https://mariadb.com/kb/en/master_pos_wait/','','https://mariadb.com/kb/en/master_pos_wait/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (163,14,'NAME_CONST','Syntax\n------\n\nNAME_CONST(name,value)\n\nDescription\n-----------\n\nReturns the given value. When used to produce a result set column,\nNAME_CONST() causes the column to have the given name. The arguments should be\nconstants.\n\nThis function is used internally when replicating stored procedures. It makes\nlittle sense to use it explicitly in SQL statements, and it was not supposed\nto be used like that.\n\nSELECT NAME_CONST(\'myname\', 14);\n+--------+\n| myname |\n+--------+\n| 14 |\n+--------+\n\nURL: https://mariadb.com/kb/en/name_const/','','https://mariadb.com/kb/en/name_const/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (164,14,'RELEASE_ALL_LOCKS','MariaDB until 10.5.2\n--------------------\nRELEASE_ALL_LOCKS was added in MariaDB 10.5.2.\n\nSyntax\n------\n\nRELEASE_ALL_LOCKS()\n\nDescription\n-----------\n\nReleases all named locks held by the current session. Returns the number of\nlocks released, or 0 if none were held.\n\nStatements using the RELEASE_ALL_LOCKS function are not safe for\nstatement-based replication.\n\nExamples\n--------\n\nSELECT RELEASE_ALL_LOCKS();\n+---------------------+\n| RELEASE_ALL_LOCKS() | \n+---------------------+\n| 0 |\n+---------------------+\n\nSELECT GET_LOCK(\'lock1\',10);\n+----------------------+\n| GET_LOCK(\'lock1\',10) |\n+----------------------+\n| 1 |\n+----------------------+\n\nSELECT RELEASE_ALL_LOCKS();\n+---------------------+\n| RELEASE_ALL_LOCKS() | \n+---------------------+\n| 1 |\n+---------------------+\n\nURL: https://mariadb.com/kb/en/release_all_locks/','','https://mariadb.com/kb/en/release_all_locks/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (165,14,'RELEASE_LOCK','Syntax\n------\n\nRELEASE_LOCK(str)\n\nDescription\n-----------\n\nReleases the lock named by the string str that was obtained with GET_LOCK().\nReturns 1 if the lock was released, 0 if the lock was not established by this\nthread (in which case the lock is not released), and NULL if the named lock\ndid not exist. The lock does not exist if it was never obtained by a call to\nGET_LOCK() or if it has previously been released.\n\nstr is case insensitive. If str is an empty string or NULL, RELEASE_LOCK()\nreturns NULL and does nothing.\n\nStatements using the RELEASE_LOCK function are not safe for statement-based\nreplication.\n\nThe DO statement is convenient to use with RELEASE_LOCK().\n\nExamples\n--------\n\nConnection1:\n\nSELECT GET_LOCK(\'lock1\',10);\n+----------------------+\n| GET_LOCK(\'lock1\',10) |\n+----------------------+\n| 1 |\n+----------------------+\n\nConnection 2:\n\nSELECT GET_LOCK(\'lock2\',10);\n+----------------------+\n| GET_LOCK(\'lock2\',10) |\n+----------------------+\n| 1 |\n+----------------------+\n\nConnection 1:\n\nSELECT RELEASE_LOCK(\'lock1\'), RELEASE_LOCK(\'lock2\'), RELEASE_LOCK(\'lock3\');\n+-----------------------+-----------------------+-----------------------+\n| RELEASE_LOCK(\'lock1\') | RELEASE_LOCK(\'lock2\') | RELEASE_LOCK(\'lock3\') |\n+-----------------------+-----------------------+-----------------------+\n| 1 | 0 | NULL |\n+-----------------------+-----------------------+-----------------------+\n\nIt is possible to hold the same lock recursively. This example is viewed using\nthe metadata_lock_info plugin:\n\nSELECT GET_LOCK(\'lock3\',10);\n+----------------------+\n| GET_LOCK(\'lock3\',10) |\n+----------------------+\n| 1 |\n+----------------------+\n\nSELECT GET_LOCK(\'lock3\',10);\n+----------------------+\n| GET_LOCK(\'lock3\',10) |\n+----------------------+\n| 1 |\n+----------------------+\n\nSELECT * FROM INFORMATION_SCHEMA.METADATA_LOCK_INFO;\n+-----------+---------------------+---------------+-----------+--------------+-\n----------+\n| THREAD_ID | LOCK_MODE | LOCK_DURATION | LOCK_TYPE | TABLE_SCHEMA |\nTABLE_NAME |\n+-----------+---------------------+---------------+-----------+--------------+-\n----------+\n| 46 | MDL_SHARED_NO_WRITE | NULL | User lock | lock3 |\n |\n+-----------+---------------------+---------------+-----------+--------------+-\n----------+\n\nSELECT RELEASE_LOCK(\'lock3\');\n+-----------------------+\n| RELEASE_LOCK(\'lock3\') |\n+-----------------------+\n| 1 |\n+-----------------------+\n\nSELECT * FROM INFORMATION_SCHEMA.METADATA_LOCK_INFO;\n+-----------+---------------------+---------------+-----------+--------------+-\n----------+\n| THREAD_ID | LOCK_MODE | LOCK_DURATION | LOCK_TYPE | TABLE_SCHEMA |\nTABLE_NAME |\n+-----------+---------------------+---------------+-----------+--------------+-\n----------+\n| 46 | MDL_SHARED_NO_WRITE | NULL | User lock | lock3 |\n |\n+-----------+---------------------+---------------+-----------+--------------+-\n----------+\n\nSELECT RELEASE_LOCK(\'lock3\');\n+-----------------------+\n| RELEASE_LOCK(\'lock3\') |\n+-----------------------+\n| 1 |\n+-----------------------+\n\nSELECT * FROM INFORMATION_SCHEMA.METADATA_LOCK_INFO;\nEmpty set (0.000 sec)\n\nURL: https://mariadb.com/kb/en/release_lock/','','https://mariadb.com/kb/en/release_lock/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (166,14,'SLEEP','Syntax\n------\n\nSLEEP(duration)\n\nDescription\n-----------\n\nSleeps (pauses) for the number of seconds given by the duration argument, then\nreturns 0. If SLEEP() is interrupted, it returns 1. The duration may have a\nfractional part given in microseconds.\n\nStatements using the SLEEP() function are not safe for statement-based\nreplication.\n\nExample\n-------\n\nSELECT SLEEP(5.5);\n+------------+\n| SLEEP(5.5) |\n+------------+\n| 0 |\n+------------+\n1 row in set (5.50 sec)\n\nURL: https://mariadb.com/kb/en/sleep/','','https://mariadb.com/kb/en/sleep/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (167,14,'SYS_GUID','MariaDB starting with 10.6.1\n----------------------------\nThe SYS_GUID function was introduced in MariaDB 10.6.1 to enhance Oracle\ncompatibility. Similar functionality can be achieved with the UUID function.\n\nSyntax\n------\n\nSYS_GUID()\n\nDescription\n-----------\n\nReturns a 16-byte globally unique identifier (GUID), similar to the UUID\nfunction, but without the - character.\n\nExample\n-------\n\nSELECT SYS_GUID();\n+----------------------------------+\n| SYS_GUID() |\n+----------------------------------+\n| 2C574E45BA2811EBB265F859713E4BE4 |\n+----------------------------------+\n\nURL: https://mariadb.com/kb/en/sys_guid/','','https://mariadb.com/kb/en/sys_guid/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (168,14,'UUID','Syntax\n------\n\nUUID()\n\nDescription\n-----------\n\nReturns a Universally Unique Identifier (UUID).\n\nA UUID is designed as a number that is globally unique in space and time. Two\ncalls to UUID() are expected to generate two different values, even if these\ncalls are performed on two separate computers that are not connected to each\nother.\n\nUUID() results are intended to be unique, but cannot always be relied upon to\nbe unpredictable and unguessable.\n\nA UUID is a 128-bit number represented by a utf8 string of five hexadecimal\nnumbers in aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee format:\n\n* The first three numbers are generated from a timestamp.\n* The fourth number preserves temporal uniqueness in case the timestamp value\n loses monotonicity (for example, due to daylight saving time).\n* The fifth number is an IEEE 802 node number that provides spatial uniqueness.\n A random number is substituted if the latter is not available (for example,\n because the host computer has no Ethernet card, or we do not know how to find\n the hardware address of an interface on your operating system). In this case,\n spatial uniqueness cannot be guaranteed. Nevertheless, a collision should\n have very low probability.\n\nCurrently, the MAC address of an interface is taken into account only on\nFreeBSD and Linux. On other operating systems, MariaDB uses a randomly\ngenerated 48-bit number.\n\nStatements using the UUID() function are not safe for statement-based\nreplication.\n\nThe function generates a UUIDv1 and the results are generated according to the\n\"DCE 1.1:Remote Procedure Call\" (Appendix A) CAE (Common Applications\nEnvironment) Specifications published by The Open Group in October 1997\n(Document Number C706).\n\nExamples\n--------\n\nSELECT UUID();\n+--------------------------------------+\n| UUID() |\n+--------------------------------------+\n| cd41294a-afb0-11df-bc9b-00241dd75637 |\n+--------------------------------------+\n\nURL: https://mariadb.com/kb/en/uuid/','','https://mariadb.com/kb/en/uuid/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (169,14,'UUID_SHORT','Syntax\n------\n\nUUID_SHORT()\n\nDescription\n-----------\n\nReturns a \"short\" universally unique identifier as a 64-bit unsigned integer\n(rather than a string-form 128-bit identifier as returned by the UUID()\nfunction).\n\nThe value of UUID_SHORT() is guaranteed to be unique if the following\nconditions hold:\n\n* The server_id of the current host is unique among your set of master and\n slave servers\n* server_id is between 0 and 255\n* You don\'t set back your system time for your server between mysqld restarts\n* You do not invoke UUID_SHORT() on average more than 16\n million times per second between mysqld restarts\n\nThe UUID_SHORT() return value is constructed this way:\n\n(server_id & 255) << 56\n+ (server_startup_time_in_seconds << 24)\n+ incremented_variable++;\n\nStatements using the UUID_SHORT() function are not safe for statement-based\nreplication.\n\nExamples\n--------\n\nSELECT UUID_SHORT();\n+-------------------+\n| UUID_SHORT() |\n+-------------------+\n| 21517162376069120 |\n+-------------------+\n\ncreate table t1 (a bigint unsigned default(uuid_short()) primary key);\ninsert into t1 values(),();\nselect * from t1;\n+-------------------+\n| a |\n+-------------------+\n| 98113699159474176 |\n| 98113699159474177 |\n+-------------------+\n\nURL: https://mariadb.com/kb/en/uuid_short/','','https://mariadb.com/kb/en/uuid_short/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (170,14,'VALUES / VALUE','Syntax\n------\n\nMariaDB starting with 10.3.3\n----------------------------\n\nVALUE(col_name)\n\nMariaDB until 10.3.2\n--------------------\n\nVALUES(col_name)\n\nDescription\n-----------\n\nIn an INSERT ... ON DUPLICATE KEY UPDATE statement, you can use the\nVALUES(col_name) function in the UPDATE clause to refer to column values from\nthe INSERT portion of the statement. In other words, VALUES(col_name) in the\nUPDATE clause refers to the value of col_name that would be inserted, had no\nduplicate-key conflict occurred. This function is especially useful in\nmultiple-row inserts.\n\nThe VALUES() function is meaningful only in INSERT ... ON DUPLICATE KEY UPDATE\nstatements and returns NULL otherwise.\n\nIn MariaDB 10.3.3 this function was renamed to VALUE(), because it\'s\nincompatible with the standard Table Value Constructors syntax, implemented in\nMariaDB 10.3.3.\n\nThe VALUES() function can still be used even from MariaDB 10.3.3, but only in\nINSERT ... ON DUPLICATE KEY UPDATE statements; it\'s a syntax error otherwise.\n\nExamples\n--------\n\nMariaDB starting with 10.3.3\n----------------------------\n\nINSERT INTO t (a,b,c) VALUES (1,2,3),(4,5,6)\n ON DUPLICATE KEY UPDATE c=VALUE(a)+VALUE(b);\n\nMariaDB until 10.3.2\n--------------------\n\nINSERT INTO t (a,b,c) VALUES (1,2,3),(4,5,6)\n ON DUPLICATE KEY UPDATE c=VALUES(a)+VALUES(b);\n\nURL: https://mariadb.com/kb/en/values-value/','','https://mariadb.com/kb/en/values-value/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (171,15,'!','Syntax\n------\n\nNOT, !\n\nDescription\n-----------\n\nLogical NOT. Evaluates to 1 if the operand is 0, to 0 if the operand is\nnon-zero, and NOT NULL returns NULL.\n\nBy default, the ! operator has a higher precedence. If the HIGH_NOT_PRECEDENCE\nSQL_MODE flag is set, NOT and ! have the same precedence.\n\nExamples\n--------\n\nSELECT NOT 10;\n+--------+\n| NOT 10 |\n+--------+\n| 0 |\n+--------+\n\nSELECT NOT 0;\n+-------+\n| NOT 0 |\n+-------+\n| 1 |\n+-------+\n\nSELECT NOT NULL;\n+----------+\n| NOT NULL |\n+----------+\n| NULL |\n+----------+\n\nSELECT ! (1+1);\n+---------+\n| ! (1+1) |\n+---------+\n| 0 |\n+---------+\n\nSELECT ! 1+1;\n+-------+\n| ! 1+1 |\n+-------+\n| 1 |\n+-------+\n\nURL: https://mariadb.com/kb/en/not/','','https://mariadb.com/kb/en/not/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (172,15,'&&','Syntax\n------\n\nAND, &&\n\nDescription\n-----------\n\nLogical AND. Evaluates to 1 if all operands are non-zero and not NULL, to 0 if\none or more operands are 0, otherwise NULL is returned.\n\nFor this operator, short-circuit evaluation can be used.\n\nExamples\n--------\n\nSELECT 1 && 1;\n+--------+\n| 1 && 1 |\n+--------+\n| 1 |\n+--------+\n\nSELECT 1 && 0;\n+--------+\n| 1 && 0 |\n+--------+\n| 0 |\n+--------+\n\nSELECT 1 && NULL;\n+-----------+\n| 1 && NULL |\n+-----------+\n| NULL |\n+-----------+\n\nSELECT 0 && NULL;\n+-----------+\n| 0 && NULL |\n+-----------+\n| 0 |\n+-----------+\n\nSELECT NULL && 0;\n+-----------+\n| NULL && 0 |\n+-----------+\n| 0 |\n+-----------+\n\nURL: https://mariadb.com/kb/en/and/','','https://mariadb.com/kb/en/and/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (173,15,'XOR','Syntax\n------\n\nXOR\n\nDescription\n-----------\n\nXOR stands for eXclusive OR. Returns NULL if either operand is NULL. For\nnon-NULL operands, evaluates to 1 if an odd number of operands is non-zero,\notherwise 0 is returned.\n\nExamples\n--------\n\nSELECT 1 XOR 1;\n+---------+\n| 1 XOR 1 |\n+---------+\n| 0 |\n+---------+\n\nSELECT 1 XOR 0;\n+---------+\n| 1 XOR 0 |\n+---------+\n| 1 |\n+---------+\n\nSELECT 1 XOR NULL;\n+------------+\n| 1 XOR NULL |\n+------------+\n| NULL |\n+------------+\n\nIn the following example, the right 1 XOR 1 is evaluated first, and returns 0.\nThen, 1 XOR 0 is evaluated, and 1 is returned.\n\nSELECT 1 XOR 1 XOR 1;\n+---------------+\n| 1 XOR 1 XOR 1 |\n+---------------+\n| 1 |\n+---------------+\n\nURL: https://mariadb.com/kb/en/xor/','','https://mariadb.com/kb/en/xor/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (174,15,'||','Syntax\n------\n\nOR, ||\n\nDescription\n-----------\n\nLogical OR. When both operands are non-NULL, the result is 1 if any operand is\nnon-zero, and 0 otherwise. With a NULL operand, the result is 1 if the other\noperand is non-zero, and NULL otherwise. If both operands are NULL, the result\nis NULL.\n\nFor this operator, short-circuit evaluation can be used.\n\nNote that, if the PIPES_AS_CONCAT SQL_MODE is set, || is used as a string\nconcatenation operator. This means that a || b is the same as CONCAT(a,b). See\nCONCAT() for details.\n\nOracle Mode\n-----------\n\nMariaDB starting with 10.3\n--------------------------\nIn Oracle mode from MariaDB 10.3, || ignores NULL.\n\nExamples\n--------\n\nSELECT 1 || 1;\n+--------+\n| 1 || 1 |\n+--------+\n| 1 |\n+--------+\n\nSELECT 1 || 0;\n+--------+\n| 1 || 0 |\n+--------+\n| 1 |\n+--------+\n\nSELECT 0 || 0;\n+--------+\n| 0 || 0 |\n+--------+\n| 0 |\n+--------+\n\nSELECT 0 || NULL;\n+-----------+\n| 0 || NULL |\n+-----------+\n| NULL |\n+-----------+\n\nSELECT 1 || NULL;\n+-----------+\n| 1 || NULL |\n+-----------+\n| 1 |\n+-----------+\n\nIn Oracle mode, from MariaDB 10.3:\n\nSELECT 0 || NULL;\n+-----------+\n| 0 || NULL |\n+-----------+\n| 0 |\n+-----------+\n\nURL: https://mariadb.com/kb/en/or/','','https://mariadb.com/kb/en/or/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (175,16,'Stored Aggregate Functions','MariaDB starting with 10.3.3\n----------------------------\nThe ability to create stored aggregate functions was added in MariaDB 10.3.3.\n\nAggregate functions are functions that are computed over a sequence of rows\nand return one result for the sequence of rows.\n\nCreating a custom aggregate function is done using the CREATE FUNCTION\nstatement with two main differences:\n\n* The addition of the AGGREGATE keyword, so CREATE AGGREGATE FUNCTION\n* The FETCH GROUP NEXT ROW instruction inside the loop\n* Oracle PL/SQL compatibility using SQL/PL is provided\n\nStandard Syntax\n---------------\n\nCREATE AGGREGATE FUNCTION function_name (parameters) RETURNS return_type\nBEGIN\n All types of declarations\n DECLARE CONTINUE HANDLER FOR NOT FOUND RETURN return_val;\n LOOP\n FETCH GROUP NEXT ROW; // fetches next row from table\n other instructions\n END LOOP;\nEND\n\nStored aggregate functions were a 2016 Google Summer of Code project by Varun\nGupta.\n\nUsing SQL/PL\n------------\n\nSET sql_mode=Oracle;\nDELIMITER //\n\nCREATE AGGREGATE FUNCTION function_name (parameters) RETURN return_type\n declarations\nBEGIN\n LOOP\n FETCH GROUP NEXT ROW; -- fetches next row from table\n -- other instructions\n\nEND LOOP;\nEXCEPTION\n WHEN NO_DATA_FOUND THEN\n RETURN return_val;\nEND //\n\nDELIMITER ;\n\nExamples\n--------\n\nFirst a simplified example:\n\nCREATE TABLE marks(stud_id INT, grade_count INT);\n\nINSERT INTO marks VALUES (1,6), (2,4), (3,7), (4,5), (5,8);\n\nSELECT * FROM marks;\n+---------+-------------+\n| stud_id | grade_count |\n+---------+-------------+\n| 1 | 6 |\n| 2 | 4 |\n| 3 | 7 |\n| 4 | 5 |\n| 5 | 8 |\n+---------+-------------+\n\nDELIMITER //\nCREATE AGGREGATE FUNCTION IF NOT EXISTS aggregate_count(x INT) RETURNS INT\nBEGIN\n DECLARE count_students INT DEFAULT 0;\n DECLARE CONTINUE HANDLER FOR NOT FOUND\n RETURN count_students;\n LOOP\n FETCH GROUP NEXT ROW;\n IF x THEN\n SET count_students = count_students+1;\n END IF;\n END LOOP;\nEND //\nDELIMITER ;\n\nA non-trivial example that cannot easily be rewritten using existing functions:\n\nDELIMITER //\nCREATE AGGREGATE FUNCTION medi_int(x INT) RETURNS DOUBLE\nBEGIN\n DECLARE CONTINUE HANDLER FOR NOT FOUND\n BEGIN\n DECLARE res DOUBLE;\n DECLARE cnt INT DEFAULT (SELECT COUNT(*) FROM tt);\n DECLARE lim INT DEFAULT (cnt-1) DIV 2;\n IF cnt % 2 = 0 THEN\n SET res = (SELECT AVG(a) FROM (SELECT a FROM tt ORDER BY a LIMIT\nlim,2) ttt);\n ELSE\n SET res = (SELECT a FROM tt ORDER BY a LIMIT lim,1);\n END IF;\n DROP TEMPORARY TABLE tt;\n RETURN res;\n END;\n CREATE TEMPORARY TABLE tt (a INT);\n LOOP\n FETCH GROUP NEXT ROW;\n INSERT INTO tt VALUES (x);\n END LOOP;\nEND //\nDELIMITER ;\n\nSQL/PL Example\n--------------\n\nThis uses the same marks table as created above.\n\nSET sql_mode=Oracle;\nDELIMITER //\n\nCREATE AGGREGATE FUNCTION aggregate_count(x INT) RETURN INT AS count_students\nINT DEFAULT 0;\nBEGIN\n LOOP\n FETCH GROUP NEXT ROW;\n IF x THEN\n SET count_students := count_students+1;\n END IF;\n END LOOP;\nEXCEPTION\n WHEN NO_DATA_FOUND THEN\n RETURN count_students;\nEND aggregate_count //\nDELIMITER ;\n\nSELECT aggregate_count(stud_id) FROM marks;\n\nURL: https://mariadb.com/kb/en/stored-aggregate-functions/','','https://mariadb.com/kb/en/stored-aggregate-functions/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (176,16,'AVG','Syntax\n------\n\nAVG([DISTINCT] expr)\n\nDescription\n-----------\n\nReturns the average value of expr. The DISTINCT option can be used to return\nthe average of the distinct values of expr. NULL values are ignored. It is an\naggregate function, and so can be used with the GROUP BY clause.\n\nAVG() returns NULL if there were no matching rows.\n\nAVG() can be used as a window function.\n\nExamples\n--------\n\nCREATE TABLE sales (sales_value INT);\n\nINSERT INTO sales VALUES(10),(20),(20),(40);\n\nSELECT AVG(sales_value) FROM sales;\n+------------------+\n| AVG(sales_value) |\n+------------------+\n| 22.5000 |\n+------------------+\n\nSELECT AVG(DISTINCT(sales_value)) FROM sales;\n+----------------------------+\n| AVG(DISTINCT(sales_value)) |\n+----------------------------+\n| 23.3333 |\n+----------------------------+\n\nCommonly, AVG() is used with a GROUP BY clause:\n\nCREATE TABLE student (name CHAR(10), test CHAR(10), score TINYINT);\n\nINSERT INTO student VALUES \n (\'Chun\', \'SQL\', 75), (\'Chun\', \'Tuning\', 73),\n (\'Esben\', \'SQL\', 43), (\'Esben\', \'Tuning\', 31),\n (\'Kaolin\', \'SQL\', 56), (\'Kaolin\', \'Tuning\', 88),\n (\'Tatiana\', \'SQL\', 87), (\'Tatiana\', \'Tuning\', 83);\n\nSELECT name, AVG(score) FROM student GROUP BY name;\n+---------+------------+\n| name | AVG(score) |\n+---------+------------+\n| Chun | 74.0000 |\n| Esben | 37.0000 |\n| Kaolin | 72.0000 |\n| Tatiana | 85.0000 |\n+---------+------------+\n\nBe careful to avoid this common mistake, not grouping correctly and returning\nmismatched data:\n\nSELECT name,test,AVG(score) FROM student;\n+------+------+------------+\n| name | test | MIN(score) |\n+------+------+------------+\n| Chun | SQL | 31 |\n+------+------+------------+\n\nAs a window function:\n\nCREATE TABLE student_test (name CHAR(10), test CHAR(10), score TINYINT);\n\nINSERT INTO student_test VALUES \n (\'Chun\', \'SQL\', 75), (\'Chun\', \'Tuning\', 73),\n (\'Esben\', \'SQL\', 43), (\'Esben\', \'Tuning\', 31),\n (\'Kaolin\', \'SQL\', 56), (\'Kaolin\', \'Tuning\', 88),\n (\'Tatiana\', \'SQL\', 87), (\'Tatiana\', \'Tuning\', 83);\n\nSELECT name, test, score, AVG(score) OVER (PARTITION BY test) \n AS average_by_test FROM student_test;\n+---------+--------+-------+-----------------+\n| name | test | score | average_by_test |\n+---------+--------+-------+-----------------+\n| Chun | SQL | 75 | 65.2500 |\n| Chun | Tuning | 73 | 68.7500 |\n| Esben | SQL | 43 | 65.2500 |\n| Esben | Tuning | 31 | 68.7500 |\n| Kaolin | SQL | 56 | 65.2500 |\n| Kaolin | Tuning | 88 | 68.7500 |\n| Tatiana | SQL | 87 | 65.2500 |\n| Tatiana | Tuning | 83 | 68.7500 |\n+---------+--------+-------+-----------------+\n\nURL: https://mariadb.com/kb/en/avg/','','https://mariadb.com/kb/en/avg/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (177,16,'BIT_AND','Syntax\n------\n\nBIT_AND(expr) [over_clause]\n\nDescription\n-----------\n\nReturns the bitwise AND of all bits in expr. The calculation is performed with\n64-bit (BIGINT) precision. It is an aggregate function, and so can be used\nwith the GROUP BY clause.\n\nIf no rows match, BIT_AND will return a value with all bits set to 1. NULL\nvalues have no effect on the result unless all results are NULL, which is\ntreated as no match.\n\nBIT_AND can be used as a window function with the addition of the over_clause.\n\nExamples\n--------\n\nCREATE TABLE vals (x INT);\n\nINSERT INTO vals VALUES(111),(110),(100);\n\nSELECT BIT_AND(x), BIT_OR(x), BIT_XOR(x) FROM vals;\n+------------+-----------+------------+\n| BIT_AND(x) | BIT_OR(x) | BIT_XOR(x) |\n+------------+-----------+------------+\n| 100 | 111 | 101 |\n+------------+-----------+------------+\n\nAs an aggregate function:\n\nCREATE TABLE vals2 (category VARCHAR(1), x INT);\n\nINSERT INTO vals2 VALUES\n (\'a\',111),(\'a\',110),(\'a\',100),\n (\'b\',\'000\'),(\'b\',001),(\'b\',011);\n\nSELECT category, BIT_AND(x), BIT_OR(x), BIT_XOR(x) \n FROM vals GROUP BY category;\n+----------+------------+-----------+------------+\n| category | BIT_AND(x) | BIT_OR(x) | BIT_XOR(x) |\n+----------+------------+-----------+------------+\n| a | 100 | 111 | 101 |\n| b | 0 | 11 | 10 |\n+----------+------------+-----------+------------+\n\nNo match:\n\nSELECT BIT_AND(NULL);\n+----------------------+\n| BIT_AND(NULL) |\n+----------------------+\n| 18446744073709551615 |\n+----------------------+\n\nURL: https://mariadb.com/kb/en/bit_and/','','https://mariadb.com/kb/en/bit_and/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (178,16,'BIT_OR','Syntax\n------\n\nBIT_OR(expr) [over_clause]\n\nDescription\n-----------\n\nReturns the bitwise OR of all bits in expr. The calculation is performed with\n64-bit (BIGINT) precision. It is an aggregate function, and so can be used\nwith the GROUP BY clause.\n\nIf no rows match, BIT_OR will return a value with all bits set to 0. NULL\nvalues have no effect on the result unless all results are NULL, which is\ntreated as no match.\n\nBIT_OR can be used as a window function with the addition of the over_clause.\n\nExamples\n--------\n\nCREATE TABLE vals (x INT);\n\nINSERT INTO vals VALUES(111),(110),(100);\n\nSELECT BIT_AND(x), BIT_OR(x), BIT_XOR(x) FROM vals;\n+------------+-----------+------------+\n| BIT_AND(x) | BIT_OR(x) | BIT_XOR(x) |\n+------------+-----------+------------+\n| 100 | 111 | 101 |\n+------------+-----------+------------+\n\nAs an aggregate function:\n\nCREATE TABLE vals2 (category VARCHAR(1), x INT);\n\nINSERT INTO vals2 VALUES\n (\'a\',111),(\'a\',110),(\'a\',100),\n (\'b\',\'000\'),(\'b\',001),(\'b\',011);\n\nSELECT category, BIT_AND(x), BIT_OR(x), BIT_XOR(x) \n FROM vals GROUP BY category;\n+----------+------------+-----------+------------+\n| category | BIT_AND(x) | BIT_OR(x) | BIT_XOR(x) |\n+----------+------------+-----------+------------+\n| a | 100 | 111 | 101 |\n| b | 0 | 11 | 10 |\n+----------+------------+-----------+------------+\n\nNo match:\n\nSELECT BIT_OR(NULL);\n+--------------+\n| BIT_OR(NULL) |\n+--------------+\n| 0 |\n+--------------+\n\nURL: https://mariadb.com/kb/en/bit_or/','','https://mariadb.com/kb/en/bit_or/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (179,16,'BIT_XOR','Syntax\n------\n\nBIT_XOR(expr) [over_clause]\n\nDescription\n-----------\n\nReturns the bitwise XOR of all bits in expr. The calculation is performed with\n64-bit (BIGINT) precision. It is an aggregate function, and so can be used\nwith the GROUP BY clause.\n\nIf no rows match, BIT_XOR will return a value with all bits set to 0. NULL\nvalues have no effect on the result unless all results are NULL, which is\ntreated as no match.\n\nBIT_XOR can be used as a window function with the addition of the over_clause.\n\nExamples\n--------\n\nCREATE TABLE vals (x INT);\n\nINSERT INTO vals VALUES(111),(110),(100);\n\nSELECT BIT_AND(x), BIT_OR(x), BIT_XOR(x) FROM vals;\n+------------+-----------+------------+\n| BIT_AND(x) | BIT_OR(x) | BIT_XOR(x) |\n+------------+-----------+------------+\n| 100 | 111 | 101 |\n+------------+-----------+------------+\n\nAs an aggregate function:\n\nCREATE TABLE vals2 (category VARCHAR(1), x INT);\n\nINSERT INTO vals2 VALUES\n (\'a\',111),(\'a\',110),(\'a\',100),\n (\'b\',\'000\'),(\'b\',001),(\'b\',011);\n\nSELECT category, BIT_AND(x), BIT_OR(x), BIT_XOR(x) \n FROM vals GROUP BY category;\n+----------+------------+-----------+------------+\n| category | BIT_AND(x) | BIT_OR(x) | BIT_XOR(x) |\n+----------+------------+-----------+------------+\n| a | 100 | 111 | 101 |\n| b | 0 | 11 | 10 |\n+----------+------------+-----------+------------+\n\nNo match:\n\nSELECT BIT_XOR(NULL);\n+---------------+\n| BIT_XOR(NULL) |\n+---------------+\n| 0 |\n+---------------+\n\nURL: https://mariadb.com/kb/en/bit_xor/','','https://mariadb.com/kb/en/bit_xor/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (180,16,'COUNT','Syntax\n------\n\nCOUNT(expr)\n\nDescription\n-----------\n\nReturns a count of the number of non-NULL values of expr in the rows retrieved\nby a SELECT statement. The result is a BIGINT value. It is an aggregate\nfunction, and so can be used with the GROUP BY clause.\n\nCOUNT(*) counts the total number of rows in a table.\n\nCOUNT() returns 0 if there were no matching rows.\n\nCOUNT() can be used as a window function.\n\nExamples\n--------\n\nCREATE TABLE student (name CHAR(10), test CHAR(10), score TINYINT);\n\nINSERT INTO student VALUES \n (\'Chun\', \'SQL\', 75), (\'Chun\', \'Tuning\', 73),\n (\'Esben\', \'SQL\', 43), (\'Esben\', \'Tuning\', 31),\n (\'Kaolin\', \'SQL\', 56), (\'Kaolin\', \'Tuning\', 88),\n (\'Tatiana\', \'SQL\', 87), (\'Tatiana\', \'Tuning\', 83);\n\nSELECT COUNT(*) FROM student;\n+----------+\n| COUNT(*) |\n+----------+\n| 8 |\n+----------+\n\nCOUNT(DISTINCT) example:\n\nSELECT COUNT(DISTINCT (name)) FROM student;\n+------------------------+\n| COUNT(DISTINCT (name)) |\n+------------------------+\n| 4 |\n+------------------------+\n\nAs a window function\n\nCREATE OR REPLACE TABLE student_test (name CHAR(10), test CHAR(10), score\nTINYINT);\n\nINSERT INTO student_test VALUES \n (\'Chun\', \'SQL\', 75), (\'Chun\', \'Tuning\', 73),\n (\'Esben\', \'SQL\', 43), (\'Esben\', \'Tuning\', 31),\n (\'Kaolin\', \'SQL\', 56), (\'Kaolin\', \'Tuning\', 88),\n (\'Tatiana\', \'SQL\', 87);\n\nSELECT name, test, score, COUNT(score) OVER (PARTITION BY name) \n AS tests_written FROM student_test;\n+---------+--------+-------+---------------+\n| name | test | score | tests_written |\n+---------+--------+-------+---------------+\n| Chun | SQL | 75 | 2 |\n| Chun | Tuning | 73 | 2 |\n| Esben | SQL | 43 | 2 |\n| Esben | Tuning | 31 | 2 |\n| Kaolin | SQL | 56 | 2 |\n| Kaolin | Tuning | 88 | 2 |\n| Tatiana | SQL | 87 | 1 |\n+---------+--------+-------+---------------+\n\nURL: https://mariadb.com/kb/en/count/','','https://mariadb.com/kb/en/count/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (181,16,'COUNT DISTINCT','Syntax\n------\n\nCOUNT(DISTINCT expr,[expr...])\n\nDescription\n-----------\n\nReturns a count of the number of different non-NULL values.\n\nCOUNT(DISTINCT) returns 0 if there were no matching rows.\n\nAlthough, from MariaDB 10.2.0, COUNT can be used as a window function, COUNT\nDISTINCT cannot be.\n\nExamples\n--------\n\nCREATE TABLE student (name CHAR(10), test CHAR(10), score TINYINT);\n\nINSERT INTO student VALUES \n (\'Chun\', \'SQL\', 75), (\'Chun\', \'Tuning\', 73),\n (\'Esben\', \'SQL\', 43), (\'Esben\', \'Tuning\', 31),\n (\'Kaolin\', \'SQL\', 56), (\'Kaolin\', \'Tuning\', 88),\n (\'Tatiana\', \'SQL\', 87), (\'Tatiana\', \'Tuning\', 83);\n\nSELECT COUNT(*) FROM student;\n+----------+\n| COUNT(*) |\n+----------+\n| 8 |\n+----------+\n\nSELECT COUNT(DISTINCT (name)) FROM student;\n+------------------------+\n| COUNT(DISTINCT (name)) |\n+------------------------+\n| 4 |\n+------------------------+\n\nURL: https://mariadb.com/kb/en/count-distinct/','','https://mariadb.com/kb/en/count-distinct/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (182,16,'GROUP_CONCAT','Syntax\n------\n\nGROUP_CONCAT(expr)\n\nDescription\n-----------\n\nThis function returns a string result with the concatenated non-NULL values\nfrom a group. If any expr in GROUP_CONCAT evaluates to NULL, that tuple is not\npresent in the list returned by GROUP_CONCAT.\n\nIt returns NULL if all arguments are NULL, or there are no matching rows.\n\nThe maximum returned length in bytes is determined by the group_concat_max_len\nserver system variable, which defaults to 1M.\n\nIf group_concat_max_len <= 512, the return type is VARBINARY or VARCHAR;\notherwise, the return type is BLOB or TEXT. The choice between binary or\nnon-binary types depends from the input.\n\nThe full syntax is as follows:\n\nGROUP_CONCAT([DISTINCT] expr [,expr ...]\n [ORDER BY {unsigned_integer | col_name | expr}\n [ASC | DESC] [,col_name ...]]\n [SEPARATOR str_val]\n [LIMIT {[offset,] row_count | row_count OFFSET offset}])\n\nDISTINCT eliminates duplicate values from the output string.\n\nORDER BY determines the order of returned values.\n\nSEPARATOR specifies a separator between the values. The default separator is a\ncomma (,). It is possible to avoid using a separator by specifying an empty\nstring.\n\nLIMIT\n-----\n\nThe LIMIT clause can be used with GROUP_CONCAT. This was not possible prior to\nMariaDB 10.3.3.\n\nExamples\n--------\n\nSELECT student_name,\n GROUP_CONCAT(test_score)\n FROM student\n GROUP BY student_name;\n\nGet a readable list of MariaDB users from the mysql.user table:\n\nSELECT GROUP_CONCAT(DISTINCT User ORDER BY User SEPARATOR \'\\n\')\n FROM mysql.user;\n\nIn the former example, DISTINCT is used because the same user may occur more\nthan once. The new line (\\n) used as a SEPARATOR makes the results easier to\nread.\n\nGet a readable list of hosts from which each user can connect:\n\nSELECT User, GROUP_CONCAT(Host ORDER BY Host SEPARATOR \', \') \n FROM mysql.user GROUP BY User ORDER BY User;\n\nThe former example shows the difference between the GROUP_CONCAT\'s ORDER BY\n(which sorts the concatenated hosts), and the SELECT\'s ORDER BY (which sorts\nthe rows).\n\nFrom MariaDB 10.3.3, LIMIT can be used with GROUP_CONCAT, so, for example,\ngiven the following table:\n\nCREATE TABLE d (dd DATE, cc INT);\n\nINSERT INTO d VALUES (\'2017-01-01\',1);\nINSERT INTO d VALUES (\'2017-01-02\',2);\nINSERT INTO d VALUES (\'2017-01-04\',3);\n\nthe following query:\n\nSELECT SUBSTRING_INDEX(GROUP_CONCAT(CONCAT_WS(\":\",dd,cc) ORDER BY cc\nDESC),\",\",1) FROM d;\n+----------------------------------------------------------------------------+\n| SUBSTRING_INDEX(GROUP_CONCAT(CONCAT_WS(\":\",dd,cc) ORDER BY cc DESC),\",\",1) |\n+----------------------------------------------------------------------------+\n| 2017-01-04:3 |\n+----------------------------------------------------------------------------+\n\ncan be more simply rewritten as:\n\nSELECT GROUP_CONCAT(CONCAT_WS(\":\",dd,cc) ORDER BY cc DESC LIMIT 1) FROM d;\n+-------------------------------------------------------------+\n| GROUP_CONCAT(CONCAT_WS(\":\",dd,cc) ORDER BY cc DESC LIMIT 1) |\n+-------------------------------------------------------------+\n| 2017-01-04:3 |\n+-------------------------------------------------------------+\n\nNULLS:\n\nCREATE OR REPLACE TABLE t1 (a int, b char);\n\nINSERT INTO t1 VALUES (1, \'a\'), (2, NULL);\n\nSELECT GROUP_CONCAT(a, b) FROM t1;\n+--------------------+\n| GROUP_CONCAT(a, b) |\n+--------------------+\n| 1a |\n+--------------------+\n\nURL: https://mariadb.com/kb/en/group_concat/','','https://mariadb.com/kb/en/group_concat/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (183,16,'MAX','Syntax\n------\n\nMAX([DISTINCT] expr)\n\nDescription\n-----------\n\nReturns the largest, or maximum, value of expr. MAX() can also take a string\nargument in which case it returns the maximum string value. The DISTINCT\nkeyword can be used to find the maximum of the distinct values of expr,\nhowever, this produces the same result as omitting DISTINCT.\n\nNote that SET and ENUM fields are currently compared by their string value\nrather than their relative position in the set, so MAX() may produce a\ndifferent highest result than ORDER BY DESC.\n\nIt is an aggregate function, and so can be used with the GROUP BY clause.\n\nMAX() can be used as a window function.\n\nMAX() returns NULL if there were no matching rows.\n\nExamples\n--------\n\nCREATE TABLE student (name CHAR(10), test CHAR(10), score TINYINT);\n\nINSERT INTO student VALUES \n (\'Chun\', \'SQL\', 75), (\'Chun\', \'Tuning\', 73),\n (\'Esben\', \'SQL\', 43), (\'Esben\', \'Tuning\', 31),\n (\'Kaolin\', \'SQL\', 56), (\'Kaolin\', \'Tuning\', 88),\n (\'Tatiana\', \'SQL\', 87), (\'Tatiana\', \'Tuning\', 83);\n\nSELECT name, MAX(score) FROM student GROUP BY name;\n+---------+------------+\n| name | MAX(score) |\n+---------+------------+\n| Chun | 75 |\n| Esben | 43 |\n| Kaolin | 88 |\n| Tatiana | 87 |\n+---------+------------+\n\nMAX string:\n\nSELECT MAX(name) FROM student;\n+-----------+\n| MAX(name) |\n+-----------+\n| Tatiana |\n+-----------+\n\nBe careful to avoid this common mistake, not grouping correctly and returning\nmismatched data:\n\nSELECT name,test,MAX(SCORE) FROM student;\n+------+------+------------+\n| name | test | MAX(SCORE) |\n+------+------+------------+\n| Chun | SQL | 88 |\n+------+------+------------+\n\nDifference between ORDER BY DESC and MAX():\n\nCREATE TABLE student2(name CHAR(10),grade ENUM(\'b\',\'c\',\'a\'));\n\nINSERT INTO student2 VALUES(\'Chun\',\'b\'),(\'Esben\',\'c\'),(\'Kaolin\',\'a\');\n\nSELECT MAX(grade) FROM student2;\n+------------+\n| MAX(grade) |\n+------------+\n| c |\n+------------+\n\nSELECT grade FROM student2 ORDER BY grade DESC LIMIT 1;\n+-------+\n| grade |\n+-------+\n| a |\n+-------+\n\nAs a window function:\n\nCREATE OR REPLACE TABLE student_test (name CHAR(10), test CHAR(10), score\nTINYINT);\nINSERT INTO student_test VALUES \n (\'Chun\', \'SQL\', 75), (\'Chun\', \'Tuning\', 73),\n (\'Esben\', \'SQL\', 43), (\'Esben\', \'Tuning\', 31),\n (\'Kaolin\', \'SQL\', 56), (\'Kaolin\', \'Tuning\', 88),\n (\'Tatiana\', \'SQL\', 87);\n\nSELECT name, test, score, MAX(score) \n OVER (PARTITION BY name) AS highest_score FROM student_test;\n+---------+--------+-------+---------------+\n| name | test | score | highest_score |\n+---------+--------+-------+---------------+\n| Chun | SQL | 75 | 75 |\n| Chun | Tuning | 73 | 75 |\n| Esben | SQL | 43 | 43 |\n| Esben | Tuning | 31 | 43 |\n| Kaolin | SQL | 56 | 88 |\n| Kaolin | Tuning | 88 | 88 |\n| Tatiana | SQL | 87 | 87 |\n+---------+--------+-------+---------------+\n\nURL: https://mariadb.com/kb/en/max/','','https://mariadb.com/kb/en/max/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (184,16,'MIN','Syntax\n------\n\nMIN([DISTINCT] expr)\n\nDescription\n-----------\n\nReturns the minimum value of expr. MIN() may take a string argument, in which\ncase it returns the minimum string value. The DISTINCT keyword can be used to\nfind the minimum of the distinct values of expr, however, this produces the\nsame result as omitting DISTINCT.\n\nNote that SET and ENUM fields are currently compared by their string value\nrather than their relative position in the set, so MIN() may produce a\ndifferent lowest result than ORDER BY ASC.\n\nIt is an aggregate function, and so can be used with the GROUP BY clause.\n\nMIN() can be used as a window function.\n\nMIN() returns NULL if there were no matching rows.\n\nExamples\n--------\n\nCREATE TABLE student (name CHAR(10), test CHAR(10), score TINYINT);\n\nINSERT INTO student VALUES \n (\'Chun\', \'SQL\', 75), (\'Chun\', \'Tuning\', 73),\n (\'Esben\', \'SQL\', 43), (\'Esben\', \'Tuning\', 31),\n (\'Kaolin\', \'SQL\', 56), (\'Kaolin\', \'Tuning\', 88),\n (\'Tatiana\', \'SQL\', 87), (\'Tatiana\', \'Tuning\', 83);\n\nSELECT name, MIN(score) FROM student GROUP BY name;\n+---------+------------+\n| name | MIN(score) |\n+---------+------------+\n| Chun | 73 |\n| Esben | 31 |\n| Kaolin | 56 |\n| Tatiana | 83 |\n+---------+------------+\n\nMIN() with a string:\n\nSELECT MIN(name) FROM student;\n+-----------+\n| MIN(name) |\n+-----------+\n| Chun |\n+-----------+\n\nBe careful to avoid this common mistake, not grouping correctly and returning\nmismatched data:\n\nSELECT name,test,MIN(score) FROM student;\n+------+------+------------+\n| name | test | MIN(score) |\n+------+------+------------+\n| Chun | SQL | 31 |\n+------+------+------------+\n\nDifference between ORDER BY ASC and MIN():\n\nCREATE TABLE student2(name CHAR(10),grade ENUM(\'b\',\'c\',\'a\'));\n\nINSERT INTO student2 VALUES(\'Chun\',\'b\'),(\'Esben\',\'c\'),(\'Kaolin\',\'a\');\n\nSELECT MIN(grade) FROM student2;\n+------------+\n| MIN(grade) |\n+------------+\n| a |\n+------------+\n\nSELECT grade FROM student2 ORDER BY grade ASC LIMIT 1;\n+-------+\n| grade |\n+-------+\n| b |\n+-------+\n\nAs a window function:\n\nCREATE OR REPLACE TABLE student_test (name CHAR(10), test CHAR(10), score\nTINYINT);\nINSERT INTO student_test VALUES \n (\'Chun\', \'SQL\', 75), (\'Chun\', \'Tuning\', 73),\n (\'Esben\', \'SQL\', 43), (\'Esben\', \'Tuning\', 31),\n (\'Kaolin\', \'SQL\', 56), (\'Kaolin\', \'Tuning\', 88),\n (\'Tatiana\', \'SQL\', 87);\n\nSELECT name, test, score, MIN(score) \n OVER (PARTITION BY name) AS lowest_score FROM student_test;\n+---------+--------+-------+--------------+\n| name | test | score | lowest_score |\n+---------+--------+-------+--------------+\n| Chun | SQL | 75 | 73 |\n| Chun | Tuning | 73 | 73 |\n| Esben | SQL | 43 | 31 |\n| Esben | Tuning | 31 | 31 |\n| Kaolin | SQL | 56 | 56 |\n| Kaolin | Tuning | 88 | 56 |\n| Tatiana | SQL | 87 | 87 |\n+---------+--------+-------+--------------+\n\nURL: https://mariadb.com/kb/en/min/','','https://mariadb.com/kb/en/min/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (185,16,'STD','Syntax\n------\n\nSTD(expr)\n\nDescription\n-----------\n\nReturns the population standard deviation of expr. This is an extension to\nstandard SQL. The standard SQL function STDDEV_POP() can be used instead.\n\nIt is an aggregate function, and so can be used with the GROUP BY clause.\n\nSTD() can be used as a window function.\n\nThis function returns NULL if there were no matching rows.\n\nExamples\n--------\n\nAs an aggregate function:\n\nCREATE OR REPLACE TABLE stats (category VARCHAR(2), x INT);\n\nINSERT INTO stats VALUES \n (\'a\',1),(\'a\',2),(\'a\',3),\n (\'b\',11),(\'b\',12),(\'b\',20),(\'b\',30),(\'b\',60);\n\nSELECT category, STDDEV_POP(x), STDDEV_SAMP(x), VAR_POP(x) \n FROM stats GROUP BY category;\n+----------+---------------+----------------+------------+\n| category | STDDEV_POP(x) | STDDEV_SAMP(x) | VAR_POP(x) |\n+----------+---------------+----------------+------------+\n| a | 0.8165 | 1.0000 | 0.6667 |\n| b | 18.0400 | 20.1693 | 325.4400 |\n+----------+---------------+----------------+------------+\n\nAs a window function:\n\nCREATE OR REPLACE TABLE student_test (name CHAR(10), test CHAR(10), score\nTINYINT);\n\nINSERT INTO student_test VALUES \n (\'Chun\', \'SQL\', 75), (\'Chun\', \'Tuning\', 73),\n (\'Esben\', \'SQL\', 43), (\'Esben\', \'Tuning\', 31),\n (\'Kaolin\', \'SQL\', 56), (\'Kaolin\', \'Tuning\', 88),\n (\'Tatiana\', \'SQL\', 87);\n\nSELECT name, test, score, STDDEV_POP(score) \n OVER (PARTITION BY test) AS stddev_results FROM student_test;\n+---------+--------+-------+----------------+\n| name | test | score | stddev_results |\n+---------+--------+-------+----------------+\n| Chun | SQL | 75 | 16.9466 |\n| Chun | Tuning | 73 | 24.1247 |\n| Esben | SQL | 43 | 16.9466 |\n| Esben | Tuning | 31 | 24.1247 |\n| Kaolin | SQL | 56 | 16.9466 |\n| Kaolin | Tuning | 88 | 24.1247 |\n| Tatiana | SQL | 87 | 16.9466 |\n+---------+--------+-------+----------------+\n\nURL: https://mariadb.com/kb/en/std/','','https://mariadb.com/kb/en/std/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (186,16,'STDDEV','Syntax\n------\n\nSTDDEV(expr)\n\nDescription\n-----------\n\nReturns the population standard deviation of expr. This function is provided\nfor compatibility with Oracle. The standard SQL function STDDEV_POP() can be\nused instead.\n\nIt is an aggregate function, and so can be used with the GROUP BY clause.\n\nSTDDEV() can be used as a window function.\n\nThis function returns NULL if there were no matching rows.\n\nExamples\n--------\n\nAs an aggregate function:\n\nCREATE OR REPLACE TABLE stats (category VARCHAR(2), x INT);\n\nINSERT INTO stats VALUES \n (\'a\',1),(\'a\',2),(\'a\',3),\n (\'b\',11),(\'b\',12),(\'b\',20),(\'b\',30),(\'b\',60);\n\nSELECT category, STDDEV_POP(x), STDDEV_SAMP(x), VAR_POP(x) \n FROM stats GROUP BY category;\n+----------+---------------+----------------+------------+\n| category | STDDEV_POP(x) | STDDEV_SAMP(x) | VAR_POP(x) |\n+----------+---------------+----------------+------------+\n| a | 0.8165 | 1.0000 | 0.6667 |\n| b | 18.0400 | 20.1693 | 325.4400 |\n+----------+---------------+----------------+------------+\n\nAs a window function:\n\nCREATE OR REPLACE TABLE student_test (name CHAR(10), test CHAR(10), score\nTINYINT);\n\nINSERT INTO student_test VALUES \n (\'Chun\', \'SQL\', 75), (\'Chun\', \'Tuning\', 73),\n (\'Esben\', \'SQL\', 43), (\'Esben\', \'Tuning\', 31),\n (\'Kaolin\', \'SQL\', 56), (\'Kaolin\', \'Tuning\', 88),\n (\'Tatiana\', \'SQL\', 87);\n\nSELECT name, test, score, STDDEV_POP(score) \n OVER (PARTITION BY test) AS stddev_results FROM student_test;\n+---------+--------+-------+----------------+\n| name | test | score | stddev_results |\n+---------+--------+-------+----------------+\n| Chun | SQL | 75 | 16.9466 |\n| Chun | Tuning | 73 | 24.1247 |\n| Esben | SQL | 43 | 16.9466 |\n| Esben | Tuning | 31 | 24.1247 |\n| Kaolin | SQL | 56 | 16.9466 |\n| Kaolin | Tuning | 88 | 24.1247 |\n| Tatiana | SQL | 87 | 16.9466 |\n+---------+--------+-------+----------------+\n\nURL: https://mariadb.com/kb/en/stddev/','','https://mariadb.com/kb/en/stddev/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (187,16,'STDDEV_POP','Syntax\n------\n\nSTDDEV_POP(expr)\n\nDescription\n-----------\n\nReturns the population standard deviation of expr (the square root of\nVAR_POP()). You can also use STD() or STDDEV(), which are equivalent but not\nstandard SQL.\n\nIt is an aggregate function, and so can be used with the GROUP BY clause.\n\nSTDDEV_POP() can be used as a window function.\n\nSTDDEV_POP() returns NULL if there were no matching rows.\n\nExamples\n--------\n\nAs an aggregate function:\n\nCREATE OR REPLACE TABLE stats (category VARCHAR(2), x INT);\n\nINSERT INTO stats VALUES \n (\'a\',1),(\'a\',2),(\'a\',3),\n (\'b\',11),(\'b\',12),(\'b\',20),(\'b\',30),(\'b\',60);\n\nSELECT category, STDDEV_POP(x), STDDEV_SAMP(x), VAR_POP(x) \n FROM stats GROUP BY category;\n+----------+---------------+----------------+------------+\n| category | STDDEV_POP(x) | STDDEV_SAMP(x) | VAR_POP(x) |\n+----------+---------------+----------------+------------+\n| a | 0.8165 | 1.0000 | 0.6667 |\n| b | 18.0400 | 20.1693 | 325.4400 |\n+----------+---------------+----------------+------------+\n\nAs a window function:\n\nCREATE OR REPLACE TABLE student_test (name CHAR(10), test CHAR(10), score\nTINYINT);\n\nINSERT INTO student_test VALUES \n (\'Chun\', \'SQL\', 75), (\'Chun\', \'Tuning\', 73),\n (\'Esben\', \'SQL\', 43), (\'Esben\', \'Tuning\', 31),\n (\'Kaolin\', \'SQL\', 56), (\'Kaolin\', \'Tuning\', 88),\n (\'Tatiana\', \'SQL\', 87);\n\nSELECT name, test, score, STDDEV_POP(score) \n OVER (PARTITION BY test) AS stddev_results FROM student_test;\n+---------+--------+-------+----------------+\n| name | test | score | stddev_results |\n+---------+--------+-------+----------------+\n| Chun | SQL | 75 | 16.9466 |\n| Chun | Tuning | 73 | 24.1247 |\n| Esben | SQL | 43 | 16.9466 |\n| Esben | Tuning | 31 | 24.1247 |\n| Kaolin | SQL | 56 | 16.9466 |\n| Kaolin | Tuning | 88 | 24.1247 |\n| Tatiana | SQL | 87 | 16.9466 |\n+---------+--------+-------+----------------+\n\nURL: https://mariadb.com/kb/en/stddev_pop/','','https://mariadb.com/kb/en/stddev_pop/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (188,16,'STDDEV_SAMP','Syntax\n------\n\nSTDDEV_SAMP(expr)\n\nDescription\n-----------\n\nReturns the sample standard deviation of expr (the square root of VAR_SAMP()).\n\nIt is an aggregate function, and so can be used with the GROUP BY clause.\n\nSTDDEV_SAMP() can be used as a window function.\n\nSTDDEV_SAMP() returns NULL if there were no matching rows.\n\nURL: https://mariadb.com/kb/en/stddev_samp/','','https://mariadb.com/kb/en/stddev_samp/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (189,16,'SUM','Syntax\n------\n\nSUM([DISTINCT] expr)\n\nDescription\n-----------\n\nReturns the sum of expr. If the return set has no rows, SUM() returns NULL.\nThe DISTINCT keyword can be used to sum only the distinct values of expr.\n\nSUM() can be used as a window function, although not with the DISTINCT\nspecifier.\n\nExamples\n--------\n\nCREATE TABLE sales (sales_value INT);\nINSERT INTO sales VALUES(10),(20),(20),(40);\n\nSELECT SUM(sales_value) FROM sales;\n+------------------+\n| SUM(sales_value) |\n+------------------+\n| 90 |\n+------------------+\n\nSELECT SUM(DISTINCT(sales_value)) FROM sales;\n+----------------------------+\n| SUM(DISTINCT(sales_value)) |\n+----------------------------+\n| 70 |\n+----------------------------+\n\nCommonly, SUM is used with a GROUP BY clause:\n\nCREATE TABLE sales (name CHAR(10), month CHAR(10), units INT);\n\nINSERT INTO sales VALUES \n (\'Chun\', \'Jan\', 75), (\'Chun\', \'Feb\', 73),\n (\'Esben\', \'Jan\', 43), (\'Esben\', \'Feb\', 31),\n (\'Kaolin\', \'Jan\', 56), (\'Kaolin\', \'Feb\', 88),\n (\'Tatiana\', \'Jan\', 87), (\'Tatiana\', \'Feb\', 83);\n\nSELECT name, SUM(units) FROM sales GROUP BY name;\n+---------+------------+\n| name | SUM(units) |\n+---------+------------+\n| Chun | 148 |\n| Esben | 74 |\n| Kaolin | 144 |\n| Tatiana | 170 |\n+---------+------------+\n\nThe GROUP BY clause is required when using an aggregate function along with\nregular column data, otherwise the result will be a mismatch, as in the\nfollowing common type of mistake:\n\nSELECT name,SUM(units) FROM sales\n;+------+------------+\n| name | SUM(units) |\n+------+------------+\n| Chun | 536 |\n+------+------------+\n\nAs a window function:\n\nCREATE OR REPLACE TABLE student_test (name CHAR(10), test CHAR(10), score\nTINYINT);\nINSERT INTO student_test VALUES \n (\'Chun\', \'SQL\', 75), (\'Chun\', \'Tuning\', 73),\n (\'Esben\', \'SQL\', 43), (\'Esben\', \'Tuning\', 31),\n (\'Kaolin\', \'SQL\', 56), (\'Kaolin\', \'Tuning\', 88),\n (\'Tatiana\', \'SQL\', 87);\n\nSELECT name, test, score, SUM(score) OVER (PARTITION BY name) AS total_score\nFROM student_test;\n+---------+--------+-------+-------------+\n| name | test | score | total_score |\n+---------+--------+-------+-------------+\n| Chun | SQL | 75 | 148 |\n| Chun | Tuning | 73 | 148 |\n| Esben | SQL | 43 | 74 |\n| Esben | Tuning | 31 | 74 |\n| Kaolin | SQL | 56 | 144 |\n| Kaolin | Tuning | 88 | 144 |\n| Tatiana | SQL | 87 | 87 |\n+---------+--------+-------+-------------+\n\nURL: https://mariadb.com/kb/en/sum/','','https://mariadb.com/kb/en/sum/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (190,16,'VARIANCE','Syntax\n------\n\nVARIANCE(expr)\n\nDescription\n-----------\n\nReturns the population standard variance of expr. This is an extension to\nstandard SQL. The standard SQL function VAR_POP() can be used instead.\n\nVariance is calculated by\n\n* working out the mean for the set\n* for each number, subtracting the mean and squaring the result\n* calculate the average of the resulting differences\n\nIt is an aggregate function, and so can be used with the GROUP BY clause.\n\nVARIANCE() can be used as a window function.\n\nVARIANCE() returns NULL if there were no matching rows.\n\nExamples\n--------\n\nCREATE TABLE v(i tinyint);\n\nINSERT INTO v VALUES(101),(99);\n\nSELECT VARIANCE(i) FROM v;\n+-------------+\n| VARIANCE(i) |\n+-------------+\n| 1.0000 |\n+-------------+\n\nINSERT INTO v VALUES(120),(80);\n\nSELECT VARIANCE(i) FROM v;\n+-------------+\n| VARIANCE(i) |\n+-------------+\n| 200.5000 |\n+-------------+\n\nAs an aggregate function:\n\nCREATE OR REPLACE TABLE stats (category VARCHAR(2), x INT);\n\nINSERT INTO stats VALUES \n (\'a\',1),(\'a\',2),(\'a\',3),\n (\'b\',11),(\'b\',12),(\'b\',20),(\'b\',30),(\'b\',60);\n\nSELECT category, STDDEV_POP(x), STDDEV_SAMP(x), VAR_POP(x) \n FROM stats GROUP BY category;\n+----------+---------------+----------------+------------+\n| category | STDDEV_POP(x) | STDDEV_SAMP(x) | VAR_POP(x) |\n+----------+---------------+----------------+------------+\n| a | 0.8165 | 1.0000 | 0.6667 |\n| b | 18.0400 | 20.1693 | 325.4400 |\n+----------+---------------+----------------+------------+\n\nAs a window function:\n\nCREATE OR REPLACE TABLE student_test (name CHAR(10), test CHAR(10), score\nTINYINT);\n\nINSERT INTO student_test VALUES \n (\'Chun\', \'SQL\', 75), (\'Chun\', \'Tuning\', 73),\n (\'Esben\', \'SQL\', 43), (\'Esben\', \'Tuning\', 31),\n (\'Kaolin\', \'SQL\', 56), (\'Kaolin\', \'Tuning\', 88),\n (\'Tatiana\', \'SQL\', 87);\n\nSELECT name, test, score, VAR_POP(score) \n OVER (PARTITION BY test) AS variance_results FROM student_test;\n+---------+--------+-------+------------------+\n| name | test | score | variance_results |\n+---------+--------+-------+------------------+\n| Chun | SQL | 75 | 287.1875 |\n| Chun | Tuning | 73 | 582.0000 |\n| Esben | SQL | 43 | 287.1875 |\n| Esben | Tuning | 31 | 582.0000 |\n| Kaolin | SQL | 56 | 287.1875 |\n| Kaolin | Tuning | 88 | 582.0000 |\n| Tatiana | SQL | 87 | 287.1875 |\n+---------+--------+-------+------------------+\n\nURL: https://mariadb.com/kb/en/variance/','','https://mariadb.com/kb/en/variance/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (191,16,'VAR_POP','Syntax\n------\n\nVAR_POP(expr)\n\nDescription\n-----------\n\nReturns the population standard variance of expr. It considers rows as the\nwhole population, not as a sample, so it has the number of rows as the\ndenominator. You can also use VARIANCE(), which is equivalent but is not\nstandard SQL.\n\nVariance is calculated by\n\n* working out the mean for the set\n* for each number, subtracting the mean and squaring the result\n* calculate the average of the resulting differences\n\nIt is an aggregate function, and so can be used with the GROUP BY clause.\n\nVAR_POP() can be used as a window function.\n\nVAR_POP() returns NULL if there were no matching rows.\n\nExamples\n--------\n\nCREATE TABLE v(i tinyint);\n\nINSERT INTO v VALUES(101),(99);\n\nSELECT VAR_POP(i) FROM v;\n+------------+\n| VAR_POP(i) |\n+------------+\n| 1.0000 |\n+------------+\n\nINSERT INTO v VALUES(120),(80);\n\nSELECT VAR_POP(i) FROM v;\n+------------+\n| VAR_POP(i) |\n+------------+\n| 200.5000 |\n+------------+\n\nAs an aggregate function:\n\nCREATE OR REPLACE TABLE stats (category VARCHAR(2), x INT);\n\nINSERT INTO stats VALUES \n (\'a\',1),(\'a\',2),(\'a\',3),\n (\'b\',11),(\'b\',12),(\'b\',20),(\'b\',30),(\'b\',60);\n\nSELECT category, STDDEV_POP(x), STDDEV_SAMP(x), VAR_POP(x) \n FROM stats GROUP BY category;\n+----------+---------------+----------------+------------+\n| category | STDDEV_POP(x) | STDDEV_SAMP(x) | VAR_POP(x) |\n+----------+---------------+----------------+------------+\n| a | 0.8165 | 1.0000 | 0.6667 |\n| b | 18.0400 | 20.1693 | 325.4400 |\n+----------+---------------+----------------+------------+\n\nAs a window function:\n\nCREATE OR REPLACE TABLE student_test (name CHAR(10), test CHAR(10), score\nTINYINT);\n\nINSERT INTO student_test VALUES \n (\'Chun\', \'SQL\', 75), (\'Chun\', \'Tuning\', 73),\n (\'Esben\', \'SQL\', 43), (\'Esben\', \'Tuning\', 31),\n (\'Kaolin\', \'SQL\', 56), (\'Kaolin\', \'Tuning\', 88),\n (\'Tatiana\', \'SQL\', 87);\n\nSELECT name, test, score, VAR_POP(score) \n OVER (PARTITION BY test) AS variance_results FROM student_test;\n+---------+--------+-------+------------------+\n| name | test | score | variance_results |\n+---------+--------+-------+------------------+\n| Chun | SQL | 75 | 287.1875 |\n| Esben | SQL | 43 | 287.1875 |\n| Kaolin | SQL | 56 | 287.1875 |\n| Tatiana | SQL | 87 | 287.1875 |\n| Chun | Tuning | 73 | 582.0000 |\n| Esben | Tuning | 31 | 582.0000 |\n| Kaolin | Tuning | 88 | 582.0000 |\n+---------+--------+-------+------------------+\n\nURL: https://mariadb.com/kb/en/var_pop/','','https://mariadb.com/kb/en/var_pop/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (192,16,'VAR_SAMP','Syntax\n------\n\nVAR_SAMP(expr)\n\nDescription\n-----------\n\nReturns the sample variance of expr. That is, the denominator is the number of\nrows minus one.\n\nIt is an aggregate function, and so can be used with the GROUP BY clause.\n\nVAR_SAMP() can be used as a window function.\n\nVAR_SAMP() returns NULL if there were no matching rows.\n\nExamples\n--------\n\nAs an aggregate function:\n\nCREATE OR REPLACE TABLE stats (category VARCHAR(2), x INT);\n\nINSERT INTO stats VALUES \n (\'a\',1),(\'a\',2),(\'a\',3),\n (\'b\',11),(\'b\',12),(\'b\',20),(\'b\',30),(\'b\',60);\n\nSELECT category, STDDEV_POP(x), STDDEV_SAMP(x), VAR_POP(x) \n FROM stats GROUP BY category;\n+----------+---------------+----------------+------------+\n| category | STDDEV_POP(x) | STDDEV_SAMP(x) | VAR_POP(x) |\n+----------+---------------+----------------+------------+\n| a | 0.8165 | 1.0000 | 0.6667 |\n| b | 18.0400 | 20.1693 | 325.4400 |\n+----------+---------------+----------------+------------+\n\nAs a window function:\n\nCREATE OR REPLACE TABLE student_test (name CHAR(10), test CHAR(10), score\nTINYINT);\n\nINSERT INTO student_test VALUES \n (\'Chun\', \'SQL\', 75), (\'Chun\', \'Tuning\', 73),\n (\'Esben\', \'SQL\', 43), (\'Esben\', \'Tuning\', 31),\n (\'Kaolin\', \'SQL\', 56), (\'Kaolin\', \'Tuning\', 88),\n (\'Tatiana\', \'SQL\', 87);\n\nSELECT name, test, score, VAR_SAMP(score) \n OVER (PARTITION BY test) AS variance_results FROM student_test;\n+---------+--------+-------+------------------+\n| name | test | score | variance_results |\n+---------+--------+-------+------------------+\n| Chun | SQL | 75 | 382.9167 |\n| Chun | Tuning | 73 | 873.0000 |\n| Esben | SQL | 43 | 382.9167 |\n| Esben | Tuning | 31 | 873.0000 |\n| Kaolin | SQL | 56 | 382.9167 |\n| Kaolin | Tuning | 88 | 873.0000 |\n| Tatiana | SQL | 87 | 382.9167 |\n+---------+--------+-------+------------------+\n\nURL: https://mariadb.com/kb/en/var_samp/','','https://mariadb.com/kb/en/var_samp/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (193,17,'BENCHMARK','Syntax\n------\n\nBENCHMARK(count,expr)\n\nDescription\n-----------\n\nThe BENCHMARK() function executes the expression expr repeatedly count times.\nIt may be used to time how quickly MariaDB processes the expression. The\nresult value is always 0. The intended use is from within the mariadb client,\nwhich reports query execution times.\n\nExamples\n--------\n\nSELECT BENCHMARK(1000000,ENCODE(\'hello\',\'goodbye\'));\n+----------------------------------------------+\n| BENCHMARK(1000000,ENCODE(\'hello\',\'goodbye\')) |\n+----------------------------------------------+\n| 0 |\n+----------------------------------------------+\n1 row in set (0.21 sec)\n\nURL: https://mariadb.com/kb/en/benchmark/','','https://mariadb.com/kb/en/benchmark/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (194,17,'BINLOG_GTID_POS','Syntax\n------\n\nBINLOG_GTID_POS(binlog_filename,binlog_offset)\n\nDescription\n-----------\n\nThe BINLOG_GTID_POS() function takes as input an old-style binary log position\nin the form of a file name and a file offset. It looks up the position in the\ncurrent binlog, and returns a string representation of the corresponding GTID\nposition. If the position is not found in the current binlog, NULL is returned.\n\nExamples\n--------\n\nSELECT BINLOG_GTID_POS(\"master-bin.000001\", 600);\n\nURL: https://mariadb.com/kb/en/binlog_gtid_pos/','','https://mariadb.com/kb/en/binlog_gtid_pos/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (195,17,'CHARSET','Syntax\n------\n\nCHARSET(str)\n\nDescription\n-----------\n\nReturns the character set of the string argument. If str is not a string, it\nis considered as a binary string (so the function returns \'binary\'). This\napplies to NULL, too. The return value is a string in the utf8 character set.\n\nExamples\n--------\n\nSELECT CHARSET(\'abc\');\n+----------------+\n| CHARSET(\'abc\') |\n+----------------+\n| latin1 |\n+----------------+\n\nSELECT CHARSET(CONVERT(\'abc\' USING utf8));\n+------------------------------------+\n| CHARSET(CONVERT(\'abc\' USING utf8)) |\n+------------------------------------+\n| utf8 |\n+------------------------------------+\n\nSELECT CHARSET(USER());\n+-----------------+\n| CHARSET(USER()) |\n+-----------------+\n| utf8 |\n+-----------------+\n\nURL: https://mariadb.com/kb/en/charset/','','https://mariadb.com/kb/en/charset/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (196,17,'COERCIBILITY','Syntax\n------\n\nCOERCIBILITY(str)\n\nDescription\n-----------\n\nReturns the collation coercibility value of the string argument. Coercibility\ndefines what will be converted to what in case of collation conflict, with an\nexpression with higher coercibility being converted to the collation of an\nexpression with lower coercibility.\n\n+-----------------------------+---------------------------+------------------+\n| Coercibility | Description | Example |\n+-----------------------------+---------------------------+------------------+\n| 0 | Explicit | Value using a |\n| | | COLLATE clause |\n+-----------------------------+---------------------------+------------------+\n| 1 | No collation | Concatenated |\n| | | strings using |\n| | | different |\n| | | collations |\n+-----------------------------+---------------------------+------------------+\n| 2 | Implicit | A string data |\n| | | type column |\n| | | value, CAST to |\n| | | a string data |\n| | | type |\n+-----------------------------+---------------------------+------------------+\n| 3 | System constant | DATABASE(), |\n| | | USER() return |\n| | | value |\n+-----------------------------+---------------------------+------------------+\n| 4 | Coercible | Literal string |\n+-----------------------------+---------------------------+------------------+\n| 5 | Numeric | Numeric and |\n| | | temporal values |\n+-----------------------------+---------------------------+------------------+\n| 6 | Ignorable | NULL or derived |\n| | | from NULL |\n+-----------------------------+---------------------------+------------------+\n\nExamples\n--------\n\nSELECT COERCIBILITY(\'abc\' COLLATE latin1_swedish_ci);\n+-----------------------------------------------+\n| COERCIBILITY(\'abc\' COLLATE latin1_swedish_ci) |\n+-----------------------------------------------+\n| 0 |\n+-----------------------------------------------+\n\nSELECT COERCIBILITY(CAST(1 AS CHAR));\n+-------------------------------+\n| COERCIBILITY(CAST(1 AS CHAR)) |\n+-------------------------------+\n| 2 |\n+-------------------------------+\n\nSELECT COERCIBILITY(USER());\n+----------------------+\n| COERCIBILITY(USER()) |\n+----------------------+\n| 3 |\n+----------------------+\n\nSELECT COERCIBILITY(\'abc\');\n+---------------------+\n| COERCIBILITY(\'abc\') |\n+---------------------+\n| 4 |\n+---------------------+\n\nSELECT COERCIBILITY(1);\n+-----------------+\n| COERCIBILITY(1) |\n+-----------------+\n| 5 |\n+-----------------+\n\nSELECT COERCIBILITY(NULL);\n+--------------------+\n| COERCIBILITY(NULL) |\n+--------------------+\n| 6 |\n+--------------------+\n\nURL: https://mariadb.com/kb/en/coercibility/','','https://mariadb.com/kb/en/coercibility/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (197,17,'COLLATION','Syntax\n------\n\nCOLLATION(str)\n\nDescription\n-----------\n\nReturns the collation of the string argument. If str is not a string, it is\nconsidered as a binary string (so the function returns \'binary\'). This applies\nto NULL, too. The return value is a string in the utf8 character set.\n\nSee Character Sets and Collations.\n\nExamples\n--------\n\nSELECT COLLATION(\'abc\');\n+-------------------+\n| COLLATION(\'abc\') |\n+-------------------+\n| latin1_swedish_ci |\n+-------------------+\n\nSELECT COLLATION(_utf8\'abc\');\n+-----------------------+\n| COLLATION(_utf8\'abc\') |\n+-----------------------+\n| utf8_general_ci |\n+-----------------------+\n\nURL: https://mariadb.com/kb/en/collation/','','https://mariadb.com/kb/en/collation/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (198,17,'CONNECTION_ID','Syntax\n------\n\nCONNECTION_ID()\n\nDescription\n-----------\n\nReturns the connection ID for the connection. Every connection (including\nevents) has an ID that is unique among the set of currently connected clients.\n\nUntil MariaDB 10.3.1, returns MYSQL_TYPE_LONGLONG, or bigint(10), in all\ncases. From MariaDB 10.3.1, returns MYSQL_TYPE_LONG, or int(10), when the\nresult would fit within 32-bits.\n\nExamples\n--------\n\nSELECT CONNECTION_ID();\n+-----------------+\n| CONNECTION_ID() |\n+-----------------+\n| 3 |\n+-----------------+\n\nURL: https://mariadb.com/kb/en/connection_id/','','https://mariadb.com/kb/en/connection_id/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (199,17,'CURRENT_ROLE','Syntax\n------\n\nCURRENT_ROLE, CURRENT_ROLE()\n\nDescription\n-----------\n\nReturns the current role name. This determines your access privileges. The\nreturn value is a string in the utf8 character set.\n\nIf there is no current role, NULL is returned.\n\nThe output of SELECT CURRENT_ROLE is equivalent to the contents of the\nENABLED_ROLES Information Schema table.\n\nUSER() returns the combination of user and host used to login. CURRENT_USER()\nreturns the account used to determine current connection\'s privileges.\n\nStatements using the CURRENT_ROLE function are not safe for statement-based\nreplication.\n\nExamples\n--------\n\nSELECT CURRENT_ROLE;\n+--------------+\n| CURRENT_ROLE |\n+--------------+\n| NULL |\n+--------------+\n\nSET ROLE staff;\n\nSELECT CURRENT_ROLE;\n+--------------+\n| CURRENT_ROLE |\n+--------------+\n| staff |\n+--------------+\n\nURL: https://mariadb.com/kb/en/current_role/','','https://mariadb.com/kb/en/current_role/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (200,17,'CURRENT_USER','Syntax\n------\n\nCURRENT_USER, CURRENT_USER()\n\nDescription\n-----------\n\nReturns the user name and host name combination for the MariaDB account that\nthe server used to authenticate the current client. This account determines\nyour access privileges. The return value is a string in the utf8 character set.\n\nThe value of CURRENT_USER() can differ from the value of USER().\nCURRENT_ROLE() returns the current active role.\n\nStatements using the CURRENT_USER function are not safe for statement-based\nreplication.\n\nExamples\n--------\n\nshell> mysql --user=\"anonymous\"\n\nselect user(),current_user();\n+---------------------+----------------+\n| user() | current_user() |\n+---------------------+----------------+\n| anonymous@localhost | @localhost |\n+---------------------+----------------+\n\nWhen calling CURRENT_USER() in a stored procedure, it returns the owner of the\nstored procedure, as defined with DEFINER.\n\nURL: https://mariadb.com/kb/en/current_user/','','https://mariadb.com/kb/en/current_user/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (201,17,'DATABASE','Syntax\n------\n\nDATABASE()\nSCHEMA()\n\nDescription\n-----------\n\nReturns the default (current) database name as a string in the utf8 character\nset. If there is no default database, DATABASE() returns NULL. Within a stored\nroutine, the default database is the database that the routine is associated\nwith, which is not necessarily the same as the database that is the default in\nthe calling context.\n\nSCHEMA() is a synonym for DATABASE().\n\nTo select a default database, the USE statement can be run. Another way to set\nthe default database is specifying its name at mariadb command line client\nstartup.\n\nExamples\n--------\n\nSELECT DATABASE();\n+------------+\n| DATABASE() |\n+------------+\n| NULL |\n+------------+\n\nUSE test;\nDatabase changed\n\nSELECT DATABASE();\n+------------+\n| DATABASE() |\n+------------+\n| test |\n+------------+\n\nURL: https://mariadb.com/kb/en/database/','','https://mariadb.com/kb/en/database/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (202,17,'DECODE_HISTOGRAM','Syntax\n------\n\nDECODE_HISTOGRAM(hist_type,histogram)\n\nDescription\n-----------\n\nReturns a string of comma separated numeric values corresponding to a\nprobability distribution represented by the histogram of type hist_type\n(SINGLE_PREC_HB or DOUBLE_PREC_HB). The hist_type and histogram would be\ncommonly used from the mysql.column_stats table.\n\nSee Histogram Based Statistics for details.\n\nExamples\n--------\n\nCREATE TABLE origin (\n i INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,\n v INT UNSIGNED NOT NULL\n);\n\nINSERT INTO origin(v) VALUES \n (1),(2),(3),(4),(5),(10),(20),\n (30),(40),(50),(60),(70),(80),\n (90),(100),(200),(400),(800);\n\nSET histogram_size=10,histogram_type=SINGLE_PREC_HB;\n\nANALYZE TABLE origin PERSISTENT FOR ALL;\n+-------------+---------+----------+-----------------------------------------+\n| Table | Op | Msg_type | Msg_text |\n+-------------+---------+----------+-----------------------------------------+\n| test.origin | analyze | status | Engine-independent statistics collected |\n| test.origin | analyze | status | OK |\n+-------------+---------+----------+-----------------------------------------+\n\nSELECT db_name,table_name,column_name,hist_type,\n hex(histogram),decode_histogram(hist_type,histogram)\n FROM mysql.column_stats WHERE db_name=\'test\' and table_name=\'origin\';\n+---------+------------+-------------+----------------+----------------------+-\n-----------------------------------------------------------------+\n| db_name | table_name | column_name | hist_type | hex(histogram) |\ndecode_histogram(hist_type,histogram) |\n+---------+------------+-------------+----------------+----------------------+-\n-----------------------------------------------------------------+\n| test | origin | i | SINGLE_PREC_HB | 0F2D3C5A7887A5C3D2F0 |\n0.059,0.118,0.059,0.118,0.118,0.059,0.118,0.118,0.059,0.118,0.059 |\n| test | origin | v | SINGLE_PREC_HB | 000001060C0F161C1F7F |\n0.000,0.000,0.004,0.020,0.024,0.012,0.027,0.024,0.012,0.376,0.502 |\n+---------+------------+-------------+----------------+----------------------+-\n-----------------------------------------------------------------+\n\nSET histogram_size=20,histogram_type=DOUBLE_PREC_HB;\n\nANALYZE TABLE origin PERSISTENT FOR ALL;\n+-------------+---------+----------+-----------------------------------------+\n| Table | Op | Msg_type | Msg_text |\n+-------------+---------+----------+-----------------------------------------+\n| test.origin | analyze | status | Engine-independent statistics collected |\n| test.origin | analyze | status | OK |\n+-------------+---------+----------+-----------------------------------------+\n\nSELECT db_name,table_name,column_name,\n hist_type,hex(histogram),decode_histogram(hist_type,histogram)\n FROM mysql.column_stats WHERE db_name=\'test\' and table_name=\'origin\';\n+---------+------------+-------------+----------------+------------------------\n-----------------+-------------------------------------------------------------\n---------------------------+\n| db_name | table_name | column_name | hist_type | hex(histogram) \n | decode_histogram(hist_type,histogram)\n |\n+---------+------------+-------------+----------------+------------------------\n-----------------+-------------------------------------------------------------\n---------------------------+\n| test | origin | i | DOUBLE_PREC_HB |\n0F0F2D2D3C3C5A5A78788787A5A5C3C3D2D2F0F0 |\n0.05882,0.11765,0.05882,0.11765,0.11765,0.05882,0.11765,0.11765,0.05882,0.11765\n0.05882 |\n| test | origin | v | DOUBLE_PREC_HB |\n5200F600480116067E0CB30F1B16831CB81FD67F |\n0.00125,0.00250,0.00125,0.01877,0.02502,0.01253,0.02502,0.02502,0.01253,0.37546\n0.50063 |\n\nURL: https://mariadb.com/kb/en/decode_histogram/','','https://mariadb.com/kb/en/decode_histogram/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (203,17,'DEFAULT','Syntax\n------\n\nDEFAULT(col_name)\n\nDescription\n-----------\n\nReturns the default value for a table column. If the column has no default\nvalue (and is not NULLABLE - NULLABLE fields have a NULL default), an error is\nreturned.\n\nFor integer columns using AUTO_INCREMENT, 0 is returned.\n\nWhen using DEFAULT as a value to set in an INSERT or UPDATE statement, you can\nuse the bare keyword DEFAULT without the parentheses and argument to refer to\nthe column in context. You can only use DEFAULT as a bare keyword if you are\nusing it alone without a surrounding expression or function.\n\nExamples\n--------\n\nSelect only non-default values for a column:\n\nSELECT i FROM t WHERE i != DEFAULT(i);\n\nUpdate values to be one greater than the default value:\n\nUPDATE t SET i = DEFAULT(i)+1 WHERE i < 100;\n\nWhen referring to the default value exactly in UPDATE or INSERT, you can omit\nthe argument:\n\nINSERT INTO t (i) VALUES (DEFAULT);\nUPDATE t SET i = DEFAULT WHERE i < 100;\n\nCREATE OR REPLACE TABLE t (\n i INT NOT NULL AUTO_INCREMENT,\n j INT NOT NULL,\n k INT DEFAULT 3,\n l INT NOT NULL DEFAULT 4,\n m INT,\n PRIMARY KEY (i)\n);\n\nDESC t;\n+-------+---------+------+-----+---------+----------------+\n| Field | Type | Null | Key | Default | Extra |\n+-------+---------+------+-----+---------+----------------+\n| i | int(11) | NO | PRI | NULL | auto_increment |\n| j | int(11) | NO | | NULL | |\n| k | int(11) | YES | | 3 | |\n| l | int(11) | NO | | 4 | |\n| m | int(11) | YES | | NULL | |\n+-------+---------+------+-----+---------+----------------+\n\nINSERT INTO t (j) VALUES (1);\nINSERT INTO t (j,m) VALUES (2,2);\nINSERT INTO t (j,l,m) VALUES (3,3,3);\n\nSELECT * FROM t;\n+---+---+------+---+------+\n| i | j | k | l | m |\n+---+---+------+---+------+\n| 1 | 1 | 3 | 4 | NULL |\n| 2 | 2 | 3 | 4 | 2 |\n| 3 | 3 | 3 | 3 | 3 |\n+---+---+------+---+------+\n\nSELECT DEFAULT(i), DEFAULT(k), DEFAULT (l), DEFAULT(m) FROM t;\n+------------+------------+-------------+------------+\n| DEFAULT(i) | DEFAULT(k) | DEFAULT (l) | DEFAULT(m) |\n+------------+------------+-------------+------------+\n| 0 | 3 | 4 | NULL |\n| 0 | 3 | 4 | NULL |\n| 0 | 3 | 4 | NULL |\n+------------+------------+-------------+------------+\n\nSELECT DEFAULT(i), DEFAULT(k), DEFAULT (l), DEFAULT(m), DEFAULT(j) FROM t;\nERROR 1364 (HY000): Field \'j\' doesn\'t have a default value\n\nSELECT * FROM t WHERE i = DEFAULT(i);\nEmpty set (0.001 sec)\n\nSELECT * FROM t WHERE j = DEFAULT(j);\nERROR 1364 (HY000): Field \'j\' doesn\'t have a default value\n\nSELECT * FROM t WHERE k = DEFAULT(k);\n+---+---+------+---+------+\n| i | j | k | l | m |\n+---+---+------+---+------+\n| 1 | 1 | 3 | 4 | NULL |\n| 2 | 2 | 3 | 4 | 2 |\n| 3 | 3 | 3 | 3 | 3 |\n+---+---+------+---+------+\n\nSELECT * FROM t WHERE l = DEFAULT(l);\n+---+---+------+---+------+\n| i | j | k | l | m |\n+---+---+------+---+------+\n| 1 | 1 | 3 | 4 | NULL |\n| 2 | 2 | 3 | 4 | 2 |\n+---+---+------+---+------+\n\nSELECT * FROM t WHERE m = DEFAULT(m);\nEmpty set (0.001 sec)\n\nSELECT * FROM t WHERE m <=> DEFAULT(m);\n+---+---+------+---+------+\n| i | j | k | l | m |\n+---+---+------+---+------+\n| 1 | 1 | 3 | 4 | NULL |\n+---+---+------+---+------+\n\nURL: https://mariadb.com/kb/en/default/','','https://mariadb.com/kb/en/default/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (204,17,'FOUND_ROWS','Syntax\n------\n\nFOUND_ROWS()\n\nDescription\n-----------\n\nA SELECT statement may include a LIMIT clause to restrict the number of rows\nthe server returns to the client. In some cases, it is desirable to know how\nmany rows the statement would have returned without the LIMIT, but without\nrunning the statement again. To obtain this row count, include a\nSQL_CALC_FOUND_ROWS option in the SELECT statement, and then invoke\nFOUND_ROWS() afterwards.\n\nYou can also use FOUND_ROWS() to obtain the number of rows returned by a\nSELECT which does not contain a LIMIT clause. In this case you don\'t need to\nuse the SQL_CALC_FOUND_ROWS option. This can be useful for example in a stored\nprocedure.\n\nAlso, this function works with some other statements which return a resultset,\nincluding SHOW, DESC and HELP. For DELETE ... RETURNING you should use\nROW_COUNT(). It also works as a prepared statement, or after executing a\nprepared statement.\n\nStatements which don\'t return any results don\'t affect FOUND_ROWS() - the\nprevious value will still be returned.\n\nWarning: When used after a CALL statement, this function returns the number of\nrows selected by the last query in the procedure, not by the whole procedure.\n\nStatements using the FOUND_ROWS() function are not safe for statement-based\nreplication.\n\nExamples\n--------\n\nSHOW ENGINES\\G\n*************************** 1. row ***************************\n Engine: CSV\n Support: YES\n Comment: Stores tables as CSV files\nTransactions: NO\n XA: NO\n Savepoints: NO\n*************************** 2. row ***************************\n Engine: MRG_MyISAM\n Support: YES\n Comment: Collection of identical MyISAM tables\nTransactions: NO\n XA: NO\n Savepoints: NO\n\n...\n\n*************************** 8. row ***************************\n Engine: PERFORMANCE_SCHEMA\n Support: YES\n Comment: Performance Schema\nTransactions: NO\n XA: NO\n Savepoints: NO\n8 rows in set (0.000 sec)\n\nSELECT FOUND_ROWS();\n+--------------+\n| FOUND_ROWS() |\n+--------------+\n| 8 |\n+--------------+\n\nSELECT SQL_CALC_FOUND_ROWS * FROM tbl_name WHERE id > 100 LIMIT 10;\n\nSELECT FOUND_ROWS();\n+--------------+\n| FOUND_ROWS() |\n+--------------+\n| 23 |\n+--------------+\n\nURL: https://mariadb.com/kb/en/found_rows/','','https://mariadb.com/kb/en/found_rows/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (205,17,'LAST_INSERT_ID','Syntax\n------\n\nLAST_INSERT_ID(), LAST_INSERT_ID(expr)\n\nDescription\n-----------\n\nLAST_INSERT_ID() (no arguments) returns the first automatically generated\nvalue successfully inserted for an AUTO_INCREMENT column as a result of the\nmost recently executed INSERT statement. The value of LAST_INSERT_ID() remains\nunchanged if no rows are successfully inserted.\n\nIf one gives an argument to LAST_INSERT_ID(), then it will return the value of\nthe expression and the next call to LAST_INSERT_ID() will return the same\nvalue. The value will also be sent to the client and can be accessed by the\nmysql_insert_id function.\n\nFor example, after inserting a row that generates an AUTO_INCREMENT value, you\ncan get the value like this:\n\nSELECT LAST_INSERT_ID();\n+------------------+\n| LAST_INSERT_ID() |\n+------------------+\n| 9 |\n+------------------+\n\nYou can also use LAST_INSERT_ID() to delete the last inserted row:\n\nDELETE FROM product WHERE id = LAST_INSERT_ID();\n\nIf no rows were successfully inserted, LAST_INSERT_ID() returns 0.\n\nThe value of LAST_INSERT_ID() will be consistent across all versions if all\nrows in the INSERT or UPDATE statement were successful.\n\nThe currently executing statement does not affect the value of\nLAST_INSERT_ID(). Suppose that you generate an AUTO_INCREMENT value with one\nstatement, and then refer to LAST_INSERT_ID() in a multiple-row INSERT\nstatement that inserts rows into a table with its own AUTO_INCREMENT column.\nThe value of LAST_INSERT_ID() will remain stable in the second statement; its\nvalue for the second and later rows is not affected by the earlier row\ninsertions. (However, if you mix references to LAST_INSERT_ID() and\nLAST_INSERT_ID(expr), the effect is undefined.)\n\nIf the previous statement returned an error, the value of LAST_INSERT_ID() is\nundefined. For transactional tables, if the statement is rolled back due to an\nerror, the value of LAST_INSERT_ID() is left undefined. For manual ROLLBACK,\nthe value of LAST_INSERT_ID() is not restored to that before the transaction;\nit remains as it was at the point of the ROLLBACK.\n\nWithin the body of a stored routine (procedure or function) or a trigger, the\nvalue of LAST_INSERT_ID() changes the same way as for statements executed\noutside the body of these kinds of objects. The effect of a stored routine or\ntrigger upon the value of LAST_INSERT_ID() that is seen by following\nstatements depends on the kind of routine:\n\n* If a stored procedure executes statements that change the value of\nLAST_INSERT_ID(), the new value will be seen by statements that follow the\nprocedure call.\n\n* For stored functions and triggers that change the value, the value is\nrestored when the function or trigger ends, so following statements will not\nsee a changed value.\n\nExamples\n--------\n\nCREATE TABLE t (\n id INTEGER UNSIGNED AUTO_INCREMENT PRIMARY KEY,\n f VARCHAR(1))\nENGINE = InnoDB;\n\nINSERT INTO t(f) VALUES(\'a\');\n\nSELECT LAST_INSERT_ID();\n+------------------+\n| LAST_INSERT_ID() |\n+------------------+\n| 1 |\n+------------------+\n\nINSERT INTO t(f) VALUES(\'b\');\n\nINSERT INTO t(f) VALUES(\'c\');\n\nSELECT LAST_INSERT_ID();\n+------------------+\n| LAST_INSERT_ID() |\n+------------------+\n| 3 |\n+------------------+\n\nINSERT INTO t(f) VALUES(\'d\'),(\'e\');\n\nSELECT LAST_INSERT_ID();\n+------------------+\n| LAST_INSERT_ID() |\n+------------------+\n| 4 |\n+------------------+\n\nSELECT * FROM t;\n+----+------+\n| id | f |\n+----+------+\n| 1 | a |\n| 2 | b |\n| 3 | c |\n| 4 | d |\n| 5 | e |\n+----+------+\n\nSELECT LAST_INSERT_ID(12);\n+--------------------+\n| LAST_INSERT_ID(12) |\n+--------------------+\n| 12 |\n+--------------------+\n\nSELECT LAST_INSERT_ID();\n+------------------+\n| LAST_INSERT_ID() |\n+------------------+\n| 12 |\n+------------------+\n\nINSERT INTO t(f) VALUES(\'f\');\n\nSELECT LAST_INSERT_ID();\n+------------------+\n| LAST_INSERT_ID() |\n+------------------+\n| 6 |\n+------------------+\n\nSELECT * FROM t;\n+----+------+\n| id | f |\n+----+------+\n| 1 | a |\n| 2 | b |\n| 3 | c |\n| 4 | d |\n| 5 | e |\n| 6 | f |\n+----+------+\n\nSELECT LAST_INSERT_ID(12);\n+--------------------+\n| LAST_INSERT_ID(12) |\n+--------------------+\n| 12 |\n+--------------------+\n\nINSERT INTO t(f) VALUES(\'g\');\n\nSELECT * FROM t;\n+----+------+\n| id | f |\n+----+------+\n| 1 | a |\n| 2 | b |\n| 3 | c |\n| 4 | d |\n| 5 | e |\n| 6 | f |\n| 7 | g |\n+----+------+\n\nURL: https://mariadb.com/kb/en/last_insert_id/','','https://mariadb.com/kb/en/last_insert_id/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (206,17,'LAST_VALUE','Syntax\n------\n\nLAST_VALUE(expr,[expr,...])\n\nLAST_VALUE(expr) OVER (\n [ PARTITION BY partition_expression ]\n [ ORDER BY order_list ]\n)\n\nDescription\n-----------\n\nLAST_VALUE() evaluates all expressions and returns the last.\n\nThis is useful together with setting user variables to a value with\n@var:=expr, for example when you want to get data of rows updated/deleted\nwithout having to do two queries against the table.\n\nLAST_VALUE can be used as a window function.\n\nReturns NULL if no last value exists.\n\nExamples\n--------\n\nCREATE TABLE t1 (a int, b int);\nINSERT INTO t1 VALUES(1,10),(2,20);\nDELETE FROM t1 WHERE a=1 AND last_value(@a:=a,@b:=b,1);\nSELECT @a,@b;\n+------+------+\n| @a | @b |\n+------+------+\n| 1 | 10 |\n+------+------+\n\nAs a window function:\n\nCREATE TABLE t1 (\n pk int primary key,\n a int,\n b int,\n c char(10),\n d decimal(10, 3),\n e real\n);\n\nINSERT INTO t1 VALUES\n( 1, 0, 1, \'one\', 0.1, 0.001),\n( 2, 0, 2, \'two\', 0.2, 0.002),\n( 3, 0, 3, \'three\', 0.3, 0.003),\n( 4, 1, 2, \'three\', 0.4, 0.004),\n( 5, 1, 1, \'two\', 0.5, 0.005),\n( 6, 1, 1, \'one\', 0.6, 0.006),\n( 7, 2, NULL, \'n_one\', 0.5, 0.007),\n( 8, 2, 1, \'n_two\', NULL, 0.008),\n( 9, 2, 2, NULL, 0.7, 0.009),\n(10, 2, 0, \'n_four\', 0.8, 0.010),\n(11, 2, 10, NULL, 0.9, NULL);\n\nSELECT pk, FIRST_VALUE(pk) OVER (ORDER BY pk) AS first_asc,\n LAST_VALUE(pk) OVER (ORDER BY pk) AS last_asc,\n FIRST_VALUE(pk) OVER (ORDER BY pk DESC) AS first_desc,\n LAST_VALUE(pk) OVER (ORDER BY pk DESC) AS last_desc\nFROM t1\nORDER BY pk DESC;\n\n+----+-----------+----------+------------+-----------+\n| pk | first_asc | last_asc | first_desc | last_desc |\n+----+-----------+----------+------------+-----------+\n| 11 | 1 | 11 | 11 | 11 |\n| 10 | 1 | 10 | 11 | 10 |\n| 9 | 1 | 9 | 11 | 9 |\n| 8 | 1 | 8 | 11 | 8 |\n| 7 | 1 | 7 | 11 | 7 |\n| 6 | 1 | 6 | 11 | 6 |\n| 5 | 1 | 5 | 11 | 5 |\n| 4 | 1 | 4 | 11 | 4 |\n| 3 | 1 | 3 | 11 | 3 |\n| 2 | 1 | 2 | 11 | 2 |\n| 1 | 1 | 1 | 11 | 1 |\n+----+-----------+----------+------------+-----------+\n\nCREATE OR REPLACE TABLE t1 (i int);\nINSERT INTO t1 VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10);\n\nSELECT i,\n FIRST_VALUE(i) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW and 1 FOLLOWING) AS\nf_1f,\n LAST_VALUE(i) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW and 1 FOLLOWING) AS\nl_1f,\n FIRST_VALUE(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS\nf_1p1f,\n LAST_VALUE(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS\nl_1p1f,\n FIRST_VALUE(i) OVER (ORDER BY i ROWS BETWEEN 2 PRECEDING AND 1 PRECEDING) AS\nf_2p1p,\n LAST_VALUE(i) OVER (ORDER BY i ROWS BETWEEN 2 PRECEDING AND 1 PRECEDING) AS\nl_2p1p,\n FIRST_VALUE(i) OVER (ORDER BY i ROWS BETWEEN 1 FOLLOWING AND 2 FOLLOWING) AS\nf_1f2f,\n LAST_VALUE(i) OVER (ORDER BY i ROWS BETWEEN 1 FOLLOWING AND 2 FOLLOWING) AS\nl_1f2f\nFROM t1;\n\n+------+------+------+--------+--------+--------+--------+--------+--------+\n| i | f_1f | l_1f | f_1p1f | l_1p1f | f_2p1p | l_2p1p | f_1f2f | l_1f2f |\n+------+------+------+--------+--------+--------+--------+--------+--------+\n| 1 | 1 | 2 | 1 | 2 | NULL | NULL | 2 | 3 |\n| 2 | 2 | 3 | 1 | 3 | 1 | 1 | 3 | 4 |\n| 3 | 3 | 4 | 2 | 4 | 1 | 2 | 4 | 5 |\n| 4 | 4 | 5 | 3 | 5 | 2 | 3 | 5 | 6 |\n| 5 | 5 | 6 | 4 | 6 | 3 | 4 | 6 | 7 |\n| 6 | 6 | 7 | 5 | 7 | 4 | 5 | 7 | 8 |\n| 7 | 7 | 8 | 6 | 8 | 5 | 6 | 8 | 9 |\n| 8 | 8 | 9 | 7 | 9 | 6 | 7 | 9 | 10 |\n| 9 | 9 | 10 | 8 | 10 | 7 | 8 | 10 | 10 |\n| 10 | 10 | 10 | 9 | 10 | 8 | 9 | NULL | NULL |\n+------+------+------+--------+--------+--------+--------+--------+--------+\n\nURL: https://mariadb.com/kb/en/last_value/','','https://mariadb.com/kb/en/last_value/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (207,17,'PROCEDURE ANALYSE','Syntax\n------\n\nanalyse([max_elements[,max_memory]])\n\nDescription\n-----------\n\nThis procedure is defined in the sql/sql_analyse.cc file. It examines the\nresult from a query and returns an analysis of the results that suggests\noptimal data types for each column. To obtain this analysis, append PROCEDURE\nANALYSE to the end of a SELECT statement:\n\nSELECT ... FROM ... WHERE ... PROCEDURE ANALYSE([max_elements,[max_memory]])\n\nFor example:\n\nSELECT col1, col2 FROM table1 PROCEDURE ANALYSE(10, 2000);\n\nThe results show some statistics for the values returned by the query, and\npropose an optimal data type for the columns. This can be helpful for checking\nyour existing tables, or after importing new data. You may need to try\ndifferent settings for the arguments so that PROCEDURE ANALYSE() does not\nsuggest the ENUM data type when it is not appropriate.\n\nThe arguments are optional and are used as follows:\n\n* max_elements (default 256) is the maximum number of distinct values that\nanalyse notices per column. This is used by analyse to check whether the\noptimal data type should be of type ENUM; if there are more than max_elements\ndistinct values, then ENUM is not a suggested type.\n* max_memory (default 8192) is the maximum amount of memory that analyse\nshould allocate per column while trying to find all distinct values.\n\nURL: https://mariadb.com/kb/en/procedure-analyse/','','https://mariadb.com/kb/en/procedure-analyse/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (208,17,'ROWNUM','MariaDB starting with 10.6.1\n----------------------------\nFrom MariaDB 10.6.1, the ROWNUM() function is supported.\n\nSyntax\n------\n\nROWNUM()\n\nIn Oracle mode one can just use ROWNUM, without the parentheses.\n\nDescription\n-----------\n\nROWNUM() returns the current number of accepted rows in the current context.\nIt main purpose is to emulate the ROWNUM pseudo column in Oracle. For MariaDB\nnative applications, we recommend the usage of LIMIT, as it is easier to use\nand gives more predictable results than the usage of ROWNUM().\n\nThe main difference between using LIMIT and ROWNUM() to limit the rows in the\nresult is that LIMIT works on the result set while ROWNUM works on the number\nof accepted rows (before any ORDER or GROUP BY clauses).\n\nThe following queries will return the same results:\n\nSELECT * from t1 LIMIT 10;\nSELECT * from t1 WHERE ROWNUM() <= 10;\n\nWhile the following may return different results based on in which orders the\nrows are found:\n\nSELECT * from t1 ORDER BY a LIMIT 10;\nSELECT * from t1 ORDER BY a WHERE ROWNUM() <= 10;\n\nThe recommended way to use ROWNUM to limit the number of returned rows and get\npredictable results is to have the query in a subquery and test for ROWNUM()\nin the outer query:\n\nSELECT * FROM (select * from t1 ORDER BY a) WHERE ROWNUM() <= 10;\n\nROWNUM() can be used in the following contexts:\n\n* SELECT\n* INSERT\n* UPDATE\n* DELETE\n* LOAD DATA INFILE\n\nUsed in other contexts, ROWNUM() will return 0.\n\nExamples\n--------\n\nINSERT INTO t1 VALUES (1,ROWNUM()),(2,ROWNUM()),(3,ROWNUM());\n\nINSERT INTO t1 VALUES (1),(2) returning a, ROWNUM();\n\nUPDATE t1 SET row_num_column=ROWNUM();\n\nDELETE FROM t1 WHERE a < 10 AND ROWNUM() < 2;\n\nLOAD DATA INFILE \'filename\' into table t1 fields terminated by \',\' \n lines terminated by \"\\r\\n\" (a,b) set c=ROWNUM();\n\nOptimizations\n-------------\n\nIn many cases where ROWNUM() is used, MariaDB will use the same optimizations\nit uses with LIMIT.\n\nLIMIT optimization is possible when using ROWNUM in the following manner:\n\n* When one is in a top level WHERE clause comparing ROWNUM() with a numerical\nconstant using any of the following expressions:\nROWNUM() < number\nROWNUM() <= number\nROWNUM() = 1\nROWNUM() can be also be the right argument to the comparison function.\n\nIn the above cases, LIMIT optimization can be done in the following cases:\n\n* For the current sub query when the ROWNUM comparison is done on the top\nlevel:\n\nSELECT * from t1 WHERE ROWNUM() <= 2 AND t1.a > 0\n\n* For an inner sub query, when the upper level has only a ROWNUM() comparison\nin the WHERE clause:\n\nSELECT * from (select * from t1) as t WHERE ROWNUM() <= 2\n\nOther Changes Related to ROWNUM\n-------------------------------\n\nWhen ROWNUM() is used anywhere in a query, the optimization to ignore ORDER BY\nin subqueries are disabled.\n\nThis was done to get the following common Oracle query to work as expected:\n\nselect * from (select * from t1 order by a desc) as t where rownum() <= 2;\n\nBy default MariaDB ignores any ORDER BY in subqueries both because the SQL\nstandard defines results sets in subqueries to be un-ordered and because of\nperformance reasons (especially when using views in subqueries). See MDEV-3926\n\"Wrong result with GROUP BY ... WITH ROLLUP\" for a discussion of this topic.\n\nOther Considerations\n--------------------\n\nWhile MariaDB tries to emulate Oracle\'s usage of ROWNUM() as closely as\npossible, there are cases where the result is different:\n\n* When the optimizer finds rows in a different order (because of different\nstorage methods or optimization). This may also happen in Oracle if one adds\nor deletes an index, in which case the rows may be found in a different order.\n\nNote that usage of ROWNUM() in functions or stored procedures will use their\nown context, not the caller\'s context.\n\nURL: https://mariadb.com/kb/en/rownum/','','https://mariadb.com/kb/en/rownum/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (209,17,'ROW_COUNT','Syntax\n------\n\nROW_COUNT()\n\nDescription\n-----------\n\nROW_COUNT() returns the number of rows updated, inserted or deleted by the\npreceding statement. This is the same as the row count that the mariadb client\ndisplays and the value from the mysql_affected_rows() C API function.\n\nGenerally:\n\n* For statements which return a result set (such as SELECT, SHOW, DESC or\nHELP), returns -1, even when the result set is empty. This is also true for\nadministrative statements, such as OPTIMIZE.\n* For DML statements other than SELECT and for ALTER TABLE, returns the number\nof affected rows.\n* For DDL statements (including TRUNCATE) and for other statements which don\'t\nreturn any result set (such as USE, DO, SIGNAL or DEALLOCATE PREPARE), returns\n0.\n\nFor UPDATE, affected rows is by default the number of rows that were actually\nchanged. If the CLIENT_FOUND_ROWS flag to mysql_real_connect() is specified\nwhen connecting to mysqld, affected rows is instead the number of rows matched\nby the WHERE clause.\n\nFor REPLACE, deleted rows are also counted. So, if REPLACE deletes a row and\nadds a new row, ROW_COUNT() returns 2.\n\nFor INSERT ... ON DUPLICATE KEY, updated rows are counted twice. So, if INSERT\nadds a new rows and modifies another row, ROW_COUNT() returns 3.\n\nROW_COUNT() does not take into account rows that are not directly\ndeleted/updated by the last statement. This means that rows deleted by foreign\nkeys or triggers are not counted.\n\nWarning: You can use ROW_COUNT() with prepared statements, but you need to\ncall it after EXECUTE, not after DEALLOCATE PREPARE, because the row count for\nallocate prepare is always 0.\n\nWarning: When used after a CALL statement, this function returns the number of\nrows affected by the last statement in the procedure, not by the whole\nprocedure.\n\nWarning: After INSERT DELAYED, ROW_COUNT() returns the number of the rows you\ntried to insert, not the number of the successful writes.\n\nThis information can also be found in the diagnostics area.\n\nStatements using the ROW_COUNT() function are not safe for statement-based\nreplication.\n\nExamples\n--------\n\nCREATE TABLE t (A INT);\n\nINSERT INTO t VALUES(1),(2),(3);\n\nSELECT ROW_COUNT();\n+-------------+\n| ROW_COUNT() |\n+-------------+\n| 3 |\n+-------------+\n\nDELETE FROM t WHERE A IN(1,2);\n\nSELECT ROW_COUNT(); \n+-------------+\n| ROW_COUNT() |\n+-------------+\n| 2 |\n+-------------+\n\nExample with prepared statements:\n\nSET @q = \'INSERT INTO t VALUES(1),(2),(3);\';\n\nPREPARE stmt FROM @q;\n\nEXECUTE stmt;\nQuery OK, 3 rows affected (0.39 sec)\nRecords: 3 Duplicates: 0 Warnings: 0\n\nSELECT ROW_COUNT();\n+-------------+\n| ROW_COUNT() |\n+-------------+\n| 3 |\n+-------------+\n\nURL: https://mariadb.com/kb/en/row_count/','','https://mariadb.com/kb/en/row_count/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (210,17,'SCHEMA','Syntax\n------\n\nSCHEMA()\n\nDescription\n-----------\n\nThis function is a synonym for DATABASE().\n\nURL: https://mariadb.com/kb/en/schema/','','https://mariadb.com/kb/en/schema/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (211,17,'SESSION_USER','Syntax\n------\n\nSESSION_USER()\n\nDescription\n-----------\n\nSESSION_USER() is a synonym for USER().\n\nURL: https://mariadb.com/kb/en/session_user/','','https://mariadb.com/kb/en/session_user/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (212,17,'SYSTEM_USER','Syntax\n------\n\nSYSTEM_USER()\n\nDescription\n-----------\n\nSYSTEM_USER() is a synonym for USER().\n\nURL: https://mariadb.com/kb/en/system_user/','','https://mariadb.com/kb/en/system_user/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (213,17,'USER','Syntax\n------\n\nUSER()\n\nDescription\n-----------\n\nReturns the current MariaDB user name and host name, given when authenticating\nto MariaDB, as a string in the utf8 character set.\n\nNote that the value of USER() may differ from the value of CURRENT_USER(),\nwhich is the user used to authenticate the current client. CURRENT_ROLE()\nreturns the current active role.\n\nSYSTEM_USER() and SESSION_USER are synonyms for USER().\n\nStatements using the USER() function or one of its synonyms are not safe for\nstatement level replication.\n\nExamples\n--------\n\nshell> mysql --user=\"anonymous\"\n\nSELECT USER(),CURRENT_USER();\n+---------------------+----------------+\n| USER() | CURRENT_USER() |\n+---------------------+----------------+\n| anonymous@localhost | @localhost |\n+---------------------+----------------+\n\nTo select only the IP address, use SUBSTRING_INDEX(),\n\nSELECT SUBSTRING_INDEX(USER(), \'@\', -1);\n+----------------------------------+\n| SUBSTRING_INDEX(USER(), \'@\', -1) |\n+----------------------------------+\n| 192.168.0.101 |\n+----------------------------------+\n\nURL: https://mariadb.com/kb/en/user/','','https://mariadb.com/kb/en/user/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (214,17,'VERSION','Syntax\n------\n\nVERSION()\n\nDescription\n-----------\n\nReturns a string that indicates the MariaDB server version. The string uses\nthe utf8 character set.\n\nExamples\n--------\n\nSELECT VERSION();\n+----------------+\n| VERSION() |\n+----------------+\n| 10.4.7-MariaDB |\n+----------------+\n\nThe VERSION() string may have one or more of the following suffixes:\n\n+---------------------------+------------------------------------------------+\n| Suffix | Description |\n+---------------------------+------------------------------------------------+\n| -embedded | The server is an embedded server |\n| | (libmariadbd). |\n+---------------------------+------------------------------------------------+\n| -log | General logging, slow logging or binary |\n| | (replication) logging is enabled. |\n+---------------------------+------------------------------------------------+\n| -debug | The server is compiled for debugging. |\n+---------------------------+------------------------------------------------+\n| -valgrind | The server is compiled to be instrumented |\n| | with valgrind. |\n+---------------------------+------------------------------------------------+\n\nChanging the Version String\n---------------------------\n\nSome old legacy code may break because they are parsing the VERSION string and\nexpecting a MySQL string or a simple version string like Joomla til API17, see\nMDEV-7780.\n\nOne can fool these applications by setting the version string from the command\nline or the my.cnf files with --version=....\n\nURL: https://mariadb.com/kb/en/version/','','https://mariadb.com/kb/en/version/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (215,18,'Assignment Operator (:=)','Syntax\n------\n\nvar_name := expr\n\nDescription\n-----------\n\nAssignment operator for assigning a value. The value on the right is assigned\nto the variable on left.\n\nUnlike the = operator, := can always be used to assign a value to a variable.\n\nThis operator works with both user-defined variables and local variables.\n\nWhen assigning the same value to several variables, LAST_VALUE() can be useful.\n\nExamples\n--------\n\nSELECT @x := 10;\n+----------+\n| @x := 10 |\n+----------+\n| 10 |\n+----------+\n\nSELECT @x, @y := @x;\n+------+----------+\n| @x | @y := @x |\n+------+----------+\n| 10 | 10 |\n+------+----------+\n\nURL: https://mariadb.com/kb/en/assignment-operator/','','https://mariadb.com/kb/en/assignment-operator/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (216,18,'Assignment Operator (=)','Syntax\n------\n\nidentifier = expr\n\nDescription\n-----------\n\nThe equal sign is used as both an assignment operator in certain contexts, and\nas a comparison operator. When used as assignment operator, the value on the\nright is assigned to the variable (or column, in some contexts) on the left.\n\nSince its use can be ambiguous, unlike the := assignment operator, the =\nassignment operator cannot be used in all contexts, and is only valid as part\nof a SET statement, or the SET clause of an UPDATE statement\n\nThis operator works with both user-defined variables and local variables.\n\nExamples\n--------\n\nUPDATE table_name SET x = 2 WHERE x > 100;\n\nSET @x = 1, @y := 2;\n\nURL: https://mariadb.com/kb/en/assignment-operators-assignment-operator/','','https://mariadb.com/kb/en/assignment-operators-assignment-operator/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (217,19,'Not Equal Operator','Syntax\n------\n\n<>, !=\n\nDescription\n-----------\n\nNot equal operator. Evaluates both SQL expressions and returns 1 if they are\nnot equal and 0 if they are equal, or NULL if either expression is NULL. If\nthe expressions return different data types, (for instance, a number and a\nstring), performs type conversion.\n\nWhen used in row comparisons these two queries return the same results:\n\nSELECT (t1.a, t1.b) != (t2.x, t2.y) \nFROM t1 INNER JOIN t2;\n\nSELECT (t1.a != t2.x) OR (t1.b != t2.y)\nFROM t1 INNER JOIN t2;\n\nExamples\n--------\n\nSELECT \'.01\' <> \'0.01\';\n+-----------------+\n| \'.01\' <> \'0.01\' |\n+-----------------+\n| 1 |\n+-----------------+\n\nSELECT .01 <> \'0.01\';\n+---------------+\n| .01 <> \'0.01\' |\n+---------------+\n| 0 |\n+---------------+\n\nSELECT \'zapp\' <> \'zappp\';\n+-------------------+\n| \'zapp\' <> \'zappp\' |\n+-------------------+\n| 1 |\n+-------------------+\n\nURL: https://mariadb.com/kb/en/not-equal/','','https://mariadb.com/kb/en/not-equal/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (218,19,'<','Syntax\n------\n\n<\n\nDescription\n-----------\n\nLess than operator. Evaluates both SQL expressions and returns 1 if the left\nvalue is less than the right value and 0 if it is not, or NULL if either\nexpression is NULL. If the expressions return different data types, (for\ninstance, a number and a string), performs type conversion.\n\nWhen used in row comparisons these two queries return the same results:\n\nSELECT (t1.a, t1.b) < (t2.x, t2.y) \nFROM t1 INNER JOIN t2;\n\nSELECT (t1.a < t2.x) OR ((t1.a = t2.x) AND (t1.b < t2.y))\nFROM t1 INNER JOIN t2;\n\nExamples\n--------\n\nSELECT 2 < 2;\n+-------+\n| 2 < 2 |\n+-------+\n| 0 |\n+-------+\n\nType conversion:\n\nSELECT 3<\'4\';\n+-------+\n| 3<\'4\' |\n+-------+\n| 1 |\n+-------+\n\nCase insensitivity - see Character Sets and Collations:\n\nSELECT \'a\'<\'A\';\n+---------+\n| \'a\'<\'A\' |\n+---------+\n| 0 |\n+---------+\n\nURL: https://mariadb.com/kb/en/less-than/','','https://mariadb.com/kb/en/less-than/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (219,19,'<=','Syntax\n------\n\n<=\n\nDescription\n-----------\n\nLess than or equal operator. Evaluates both SQL expressions and returns 1 if\nthe left value is less than or equal to the right value and 0 if it is not, or\nNULL if either expression is NULL. If the expressions return different data\ntypes, (for instance, a number and a string), performs type conversion.\n\nWhen used in row comparisons these two queries return the same results:\n\nSELECT (t1.a, t1.b) <= (t2.x, t2.y) \nFROM t1 INNER JOIN t2;\n\nSELECT (t1.a < t2.x) OR ((t1.a = t2.x) AND (t1.b <= t2.y))\nFROM t1 INNER JOIN t2;\n\nExamples\n--------\n\nSELECT 0.1 <= 2;\n+----------+\n| 0.1 <= 2 |\n+----------+\n| 1 |\n+----------+\n\nSELECT \'a\'<=\'A\';\n+----------+\n| \'a\'<=\'A\' |\n+----------+\n| 1 |\n+----------+\n\nURL: https://mariadb.com/kb/en/less-than-or-equal/','','https://mariadb.com/kb/en/less-than-or-equal/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (220,19,'<=>','Syntax\n------\n\n<=>\n\nDescription\n-----------\n\nNULL-safe equal operator. It performs an equality comparison like the =\noperator, but returns 1 rather than NULL if both operands are NULL, and 0\nrather than NULL if one operand is NULL.\n\na <=> b is equivalent to a = b OR (a IS NULL AND b IS NULL).\n\nWhen used in row comparisons these two queries return the same results:\n\nSELECT (t1.a, t1.b) <=> (t2.x, t2.y) \nFROM t1 INNER JOIN t2;\n\nSELECT (t1.a <=> t2.x) AND (t1.b <=> t2.y)\nFROM t1 INNER JOIN t2;\n\nSee also NULL Values in MariaDB.\n\nExamples\n--------\n\nSELECT 1 <=> 1, NULL <=> NULL, 1 <=> NULL;\n+---------+---------------+------------+\n| 1 <=> 1 | NULL <=> NULL | 1 <=> NULL |\n+---------+---------------+------------+\n| 1 | 1 | 0 |\n+---------+---------------+------------+\n\nSELECT 1 = 1, NULL = NULL, 1 = NULL;\n+-------+-------------+----------+\n| 1 = 1 | NULL = NULL | 1 = NULL |\n+-------+-------------+----------+\n| 1 | NULL | NULL |\n+-------+-------------+----------+\n\nURL: https://mariadb.com/kb/en/null-safe-equal/','','https://mariadb.com/kb/en/null-safe-equal/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (221,19,'=','Syntax\n------\n\nleft_expr = right_expr\n\nDescription\n-----------\n\nEqual operator. Evaluates both SQL expressions and returns 1 if they are\nequal, 0 if they are not equal, or NULL if either expression is NULL. If the\nexpressions return different data types (for example, a number and a string),\na type conversion is performed.\n\nWhen used in row comparisons these two queries are synonymous and return the\nsame results:\n\nSELECT (t1.a, t1.b) = (t2.x, t2.y) FROM t1 INNER JOIN t2;\n\nSELECT (t1.a = t2.x) AND (t1.b = t2.y) FROM t1 INNER JOIN t2;\n\nTo perform a NULL-safe comparison, use the <=> operator.\n\n= can also be used as an assignment operator.\n\nExamples\n--------\n\nSELECT 1 = 0;\n+-------+\n| 1 = 0 |\n+-------+\n| 0 |\n+-------+\n\nSELECT \'0\' = 0;\n+---------+\n| \'0\' = 0 |\n+---------+\n| 1 |\n+---------+\n\nSELECT \'0.0\' = 0;\n+-----------+\n| \'0.0\' = 0 |\n+-----------+\n| 1 |\n+-----------+\n\nSELECT \'0.01\' = 0;\n+------------+\n| \'0.01\' = 0 |\n+------------+\n| 0 |\n+------------+\n\nSELECT \'.01\' = 0.01;\n+--------------+\n| \'.01\' = 0.01 |\n+--------------+\n| 1 |\n+--------------+\n\nSELECT (5 * 2) = CONCAT(\'1\', \'0\');\n+----------------------------+\n| (5 * 2) = CONCAT(\'1\', \'0\') |\n+----------------------------+\n| 1 |\n+----------------------------+\n\nSELECT 1 = NULL;\n+----------+\n| 1 = NULL |\n+----------+\n| NULL |\n+----------+\n\nSELECT NULL = NULL;\n+-------------+\n| NULL = NULL |\n+-------------+\n| NULL |\n+-------------+\n\nURL: https://mariadb.com/kb/en/equal/','','https://mariadb.com/kb/en/equal/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (222,19,'>','Syntax\n------\n\n>\n\nDescription\n-----------\n\nGreater than operator. Evaluates both SQL expressions and returns 1 if the\nleft value is greater than the right value and 0 if it is not, or NULL if\neither expression is NULL. If the expressions return different data types,\n(for instance, a number and a string), performs type conversion.\n\nWhen used in row comparisons these two queries return the same results:\n\nSELECT (t1.a, t1.b) > (t2.x, t2.y) \nFROM t1 INNER JOIN t2;\n\nSELECT (t1.a > t2.x) OR ((t1.a = t2.x) AND (t1.b > t2.y))\nFROM t1 INNER JOIN t2;\n\nExamples\n--------\n\nSELECT 2 > 2;\n+-------+\n| 2 > 2 |\n+-------+\n| 0 |\n+-------+\n\nSELECT \'b\' > \'a\';\n+-----------+\n| \'b\' > \'a\' |\n+-----------+\n| 1 |\n+-----------+\n\nURL: https://mariadb.com/kb/en/greater-than/','','https://mariadb.com/kb/en/greater-than/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (223,19,'>=','Syntax\n------\n\n>=\n\nDescription\n-----------\n\nGreater than or equal operator. Evaluates both SQL expressions and returns 1\nif the left value is greater than or equal to the right value and 0 if it is\nnot, or NULL if either expression is NULL. If the expressions return different\ndata types, (for instance, a number and a string), performs type conversion.\n\nWhen used in row comparisons these two queries return the same results:\n\nSELECT (t1.a, t1.b) >= (t2.x, t2.y) \nFROM t1 INNER JOIN t2;\n\nSELECT (t1.a > t2.x) OR ((t1.a = t2.x) AND (t1.b >= t2.y))\nFROM t1 INNER JOIN t2;\n\nExamples\n--------\n\nSELECT 2 >= 2;\n+--------+\n| 2 >= 2 |\n+--------+\n| 1 |\n+--------+\n\nSELECT \'A\' >= \'a\';\n+------------+\n| \'A\' >= \'a\' |\n+------------+\n| 1 |\n+------------+\n\nURL: https://mariadb.com/kb/en/greater-than-or-equal/','','https://mariadb.com/kb/en/greater-than-or-equal/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (224,19,'BETWEEN AND','Syntax\n------\n\nexpr BETWEEN min AND max\n\nDescription\n-----------\n\nIf expr is greater than or equal to min and expr is less than or equal to max,\nBETWEEN returns 1, otherwise it returns 0. This is equivalent to the\nexpression (min <= expr AND expr <= max) if all the arguments are of the same\ntype. Otherwise type conversion takes place according to the rules described\nat Type Conversion, but applied to all the three arguments.\n\nExamples\n--------\n\nSELECT 1 BETWEEN 2 AND 3;\n+-------------------+\n| 1 BETWEEN 2 AND 3 |\n+-------------------+\n| 0 |\n+-------------------+\n\nSELECT \'b\' BETWEEN \'a\' AND \'c\';\n+-------------------------+\n| \'b\' BETWEEN \'a\' AND \'c\' |\n+-------------------------+\n| 1 |\n+-------------------------+\n\nSELECT 2 BETWEEN 2 AND \'3\';\n+---------------------+\n| 2 BETWEEN 2 AND \'3\' |\n+---------------------+\n| 1 |\n+---------------------+\n\nSELECT 2 BETWEEN 2 AND \'x-3\';\n+-----------------------+\n| 2 BETWEEN 2 AND \'x-3\' |\n+-----------------------+\n| 0 |\n+-----------------------+\n1 row in set, 1 warning (0.00 sec)\n\nWarning (Code 1292): Truncated incorrect DOUBLE value: \'x-3\'\n\nNULL:\n\nSELECT 1 BETWEEN 1 AND NULL;\n+----------------------+\n| 1 BETWEEN 1 AND NULL |\n+----------------------+\n| NULL |\n+----------------------+\n\nDATE, DATETIME and TIMESTAMP examples. Omitting the time component compares\nagainst 00:00, so later times on the same date are not returned:\n\nCREATE TABLE `x` (\n a date ,\n b datetime,\n c timestamp\n)\n\nINSERT INTO x VALUES \n (\'2018-11-11\', \'2018-11-11 05:15\', \'2018-11-11 05:15\'), \n (\'2018-11-12\', \'2018-11-12 05:15\', \'2018-11-12 05:15\');\n\nSELECT * FROM x WHERE a BETWEEN \'2018-11-11\' AND \'2018-11-12\';\n+------------+---------------------+---------------------+\n| a | b | c |\n+------------+---------------------+---------------------+\n| 2018-11-11 | 2018-11-11 05:15:00 | 2018-11-11 05:15:00 |\n| 2018-11-12 | 2018-11-12 05:15:00 | 2018-11-12 05:15:00 |\n+------------+---------------------+---------------------+\n\nSELECT * FROM x WHERE b BETWEEN \'2018-11-11\' AND \'2018-11-12\';\n+------------+---------------------+---------------------+\n| a | b | c |\n+------------+---------------------+---------------------+\n| 2018-11-11 | 2018-11-11 05:15:00 | 2018-11-11 05:15:00 |\n+------------+---------------------+---------------------+\n\nSELECT * FROM x WHERE c BETWEEN \'2018-11-11\' AND \'2018-11-12\';\n+------------+---------------------+---------------------+\n| a | b | c |\n+------------+---------------------+---------------------+\n| 2018-11-11 | 2018-11-11 05:15:00 | 2018-11-11 05:15:00 |\n+------------+---------------------+---------------------+\n\nURL: https://mariadb.com/kb/en/between-and/','','https://mariadb.com/kb/en/between-and/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (225,19,'COALESCE','Syntax\n------\n\nCOALESCE(value,...)\n\nDescription\n-----------\n\nReturns the first non-NULL value in the list, or NULL if there are no non-NULL\nvalues. At least one parameter must be passed.\n\nThe function is useful when substituting a default value for null values when\ndisplaying data.\n\nSee also NULL Values in MariaDB.\n\nExamples\n--------\n\nSELECT COALESCE(NULL,1);\n+------------------+\n| COALESCE(NULL,1) |\n+------------------+\n| 1 |\n+------------------+\n\nSELECT COALESCE(NULL,NULL,NULL);\n+--------------------------+\n| COALESCE(NULL,NULL,NULL) |\n+--------------------------+\n| NULL |\n+--------------------------+\n\nWhen two arguments are given, COALESCE() is the same as IFNULL():\n\nSET @a=NULL, @b=1;\n\nSELECT COALESCE(@a, @b), IFNULL(@a, @b);\n+------------------+----------------+\n| COALESCE(@a, @b) | IFNULL(@a, @b) |\n+------------------+----------------+\n| 1 | 1 |\n+------------------+----------------+\n\nHex type confusion:\n\nCREATE TABLE t1 (a INT, b VARCHAR(10));\nINSERT INTO t1 VALUES (0x31, 0x61),(COALESCE(0x31), COALESCE(0x61));\n\nSELECT * FROM t1;\n+------+------+\n| a | b |\n+------+------+\n| 49 | a |\n| 1 | a |\n+------+------+\n\nThe reason for the differing results above is that when 0x31 is inserted\ndirectly to the column, it\'s treated as a number (see Hexadecimal Literals),\nwhile when 0x31 is passed to COALESCE(), it\'s treated as a string, because:\n\n* HEX values have a string data type by default.\n* COALESCE() has the same data type as the argument.\n\nSubstituting zero for NULL (in this case when the aggregate function returns\nNULL after finding no rows):\n\nSELECT SUM(score) FROM student;\n+------------+\n| SUM(score) |\n+------------+\n| NULL |\n+------------+\n\nSELECT COALESCE(SUM(score),0) FROM student;\n+------------------------+\n| COALESCE(SUM(score),0) |\n+------------------------+\n| 0 |\n+------------------------+\n\nURL: https://mariadb.com/kb/en/coalesce/','','https://mariadb.com/kb/en/coalesce/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (226,19,'GREATEST','Syntax\n------\n\nGREATEST(value1,value2,...)\n\nDescription\n-----------\n\nWith two or more arguments, returns the largest (maximum-valued) argument. The\narguments are compared using the same rules as for LEAST().\n\nExamples\n--------\n\nSELECT GREATEST(2,0);\n+---------------+\n| GREATEST(2,0) |\n+---------------+\n| 2 |\n+---------------+\n\nSELECT GREATEST(34.0,3.0,5.0,767.0);\n+------------------------------+\n| GREATEST(34.0,3.0,5.0,767.0) |\n+------------------------------+\n| 767.0 |\n+------------------------------+\n\nSELECT GREATEST(\'B\',\'A\',\'C\');\n+-----------------------+\n| GREATEST(\'B\',\'A\',\'C\') |\n+-----------------------+\n| C |\n+-----------------------+\n\nURL: https://mariadb.com/kb/en/greatest/','','https://mariadb.com/kb/en/greatest/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (227,19,'IN','Syntax\n------\n\nexpr IN (value,...)\n\nDescription\n-----------\n\nReturns 1 if expr is equal to any of the values in the IN list, else returns\n0. If all values are constants, they are evaluated according to the type of\nexpr and sorted. The search for the item then is done using a binary search.\nThis means IN is very quick if the IN value list consists entirely of\nconstants. Otherwise, type conversion takes place according to the rules\ndescribed at Type Conversion, but applied to all the arguments.\n\nIf expr is NULL, IN always returns NULL. If at least one of the values in the\nlist is NULL, and one of the comparisons is true, the result is 1. If at least\none of the values in the list is NULL and none of the comparisons is true, the\nresult is NULL.\n\nExamples\n--------\n\nSELECT 2 IN (0,3,5,7);\n+----------------+\n| 2 IN (0,3,5,7) |\n+----------------+\n| 0 |\n+----------------+\n\nSELECT \'wefwf\' IN (\'wee\',\'wefwf\',\'weg\');\n+----------------------------------+\n| \'wefwf\' IN (\'wee\',\'wefwf\',\'weg\') |\n+----------------------------------+\n| 1 |\n+----------------------------------+\n\nType conversion:\n\nSELECT 1 IN (\'1\', \'2\', \'3\');\n+----------------------+\n| 1 IN (\'1\', \'2\', \'3\') |\n+----------------------+\n| 1 |\n+----------------------+\n\nSELECT NULL IN (1, 2, 3);\n+-------------------+\n| NULL IN (1, 2, 3) |\n+-------------------+\n| NULL |\n+-------------------+\n\nSELECT 1 IN (1, 2, NULL);\n+-------------------+\n| 1 IN (1, 2, NULL) |\n+-------------------+\n| 1 |\n+-------------------+\n\nSELECT 5 IN (1, 2, NULL);\n+-------------------+\n| 5 IN (1, 2, NULL) |\n+-------------------+\n| NULL |\n+-------------------+\n\nURL: https://mariadb.com/kb/en/in/','','https://mariadb.com/kb/en/in/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (228,19,'INTERVAL','Syntax\n------\n\nINTERVAL(N,N1,N2,N3,...)\n\nDescription\n-----------\n\nReturns the index of the last argument that is less than the first argument or\nis NULL.\n\nReturns 0 if N < N1, 1 if N < N2, 2 if N < N3 and so on or -1 if N is NULL.\nAll arguments are treated as integers. It is required that N1 < N2 < N3 < ...\n< Nn for this function to work correctly. This is because a fast binary search\nis used.\n\nExamples\n--------\n\nSELECT INTERVAL(23, 1, 15, 17, 30, 44, 200);\n+--------------------------------------+\n| INTERVAL(23, 1, 15, 17, 30, 44, 200) |\n+--------------------------------------+\n| 3 |\n+--------------------------------------+\n\nSELECT INTERVAL(10, 1, 10, 100, 1000);\n+--------------------------------+\n| INTERVAL(10, 1, 10, 100, 1000) |\n+--------------------------------+\n| 2 |\n+--------------------------------+\n\nSELECT INTERVAL(22, 23, 30, 44, 200);\n+-------------------------------+\n| INTERVAL(22, 23, 30, 44, 200) |\n+-------------------------------+\n| 0 |\n+-------------------------------+\n\nSELECT INTERVAL(10, 2, NULL);\n+-----------------------+\n| INTERVAL(10, 2, NULL) |\n+-----------------------+\n| 2 |\n+-----------------------+\n\nURL: https://mariadb.com/kb/en/interval/','','https://mariadb.com/kb/en/interval/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (229,19,'IS','Syntax\n------\n\nIS boolean_value\n\nDescription\n-----------\n\nTests a value against a boolean value, where boolean_value can be TRUE, FALSE,\nor UNKNOWN.\n\nThere is an important difference between using IS TRUE or comparing a value\nwith TRUE using =. When using =, only 1 equals to TRUE. But when using IS\nTRUE, all values which are logically true (like a number > 1) return TRUE.\n\nExamples\n--------\n\nSELECT 1 IS TRUE, 0 IS FALSE, NULL IS UNKNOWN;\n+-----------+------------+-----------------+\n| 1 IS TRUE | 0 IS FALSE | NULL IS UNKNOWN |\n+-----------+------------+-----------------+\n| 1 | 1 | 1 |\n+-----------+------------+-----------------+\n\nDifference between = and IS TRUE:\n\nSELECT 2 = TRUE, 2 IS TRUE;\n+----------+-----------+\n| 2 = TRUE | 2 IS TRUE |\n+----------+-----------+\n| 0 | 1 |\n+----------+-----------+\n\nURL: https://mariadb.com/kb/en/is/','','https://mariadb.com/kb/en/is/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (230,19,'IS NOT','Syntax\n------\n\nIS NOT boolean_value\n\nDescription\n-----------\n\nTests a value against a boolean value, where boolean_value can be TRUE, FALSE,\nor UNKNOWN.\n\nExamples\n--------\n\nSELECT 1 IS NOT UNKNOWN, 0 IS NOT UNKNOWN, NULL IS NOT UNKNOWN;\n+------------------+------------------+---------------------+\n| 1 IS NOT UNKNOWN | 0 IS NOT UNKNOWN | NULL IS NOT UNKNOWN |\n+------------------+------------------+---------------------+\n| 1 | 1 | 0 |\n+------------------+------------------+---------------------+\n\nSELECT NULL IS NOT TRUE, NULL IS NOT FALSE;\n+------------------+-------------------+\n| NULL IS NOT TRUE | NULL IS NOT FALSE |\n+------------------+-------------------+\n| 1 | 1 |\n+------------------+-------------------+\n\nURL: https://mariadb.com/kb/en/is-not/','','https://mariadb.com/kb/en/is-not/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (231,19,'IS NOT NULL','Syntax\n------\n\nIS NOT NULL\n\nDescription\n-----------\n\nTests whether a value is not NULL. See also NULL Values in MariaDB.\n\nExamples\n--------\n\nSELECT 1 IS NOT NULL, 0 IS NOT NULL, NULL IS NOT NULL;\n+---------------+---------------+------------------+\n| 1 IS NOT NULL | 0 IS NOT NULL | NULL IS NOT NULL |\n+---------------+---------------+------------------+\n| 1 | 1 | 0 |\n+---------------+---------------+------------------+\n\nURL: https://mariadb.com/kb/en/is-not-null/','','https://mariadb.com/kb/en/is-not-null/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (232,19,'IS NULL','Syntax\n------\n\nIS NULL\n\nDescription\n-----------\n\nTests whether a value is NULL. See also NULL Values in MariaDB.\n\nExamples\n--------\n\nSELECT 1 IS NULL, 0 IS NULL, NULL IS NULL;\n+-----------+-----------+--------------+\n| 1 IS NULL | 0 IS NULL | NULL IS NULL |\n+-----------+-----------+--------------+\n| 0 | 0 | 1 |\n+-----------+-----------+--------------+\n\nCompatibility\n-------------\n\nSome ODBC applications use the syntax auto_increment_field IS NOT NULL to find\nthe latest row that was inserted with an autogenerated key value. If your\napplications need this, you can set the sql_auto_is_null variable to 1.\n\nSET @@sql_auto_is_null=1;\nCREATE TABLE t1 (auto_increment_column INT NOT NULL AUTO_INCREMENT PRIMARY\nKEY);\nINSERT INTO t1 VALUES (NULL);\nSELECT * FROM t1 WHERE auto_increment_column IS NULL;\n\n+-----------------------+\n| auto_increment_column |\n+-----------------------+\n| 1 |\n+-----------------------+\n\nURL: https://mariadb.com/kb/en/is-null/','','https://mariadb.com/kb/en/is-null/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (233,19,'ISNULL','Syntax\n------\n\nISNULL(expr)\n\nDescription\n-----------\n\nIf expr is NULL, ISNULL() returns 1, otherwise it returns 0.\n\nSee also NULL Values in MariaDB.\n\nExamples\n--------\n\nSELECT ISNULL(1+1);\n+-------------+\n| ISNULL(1+1) |\n+-------------+\n| 0 |\n+-------------+\n\nSELECT ISNULL(1/0);\n+-------------+\n| ISNULL(1/0) |\n+-------------+\n| 1 |\n+-------------+\n\nURL: https://mariadb.com/kb/en/isnull/','','https://mariadb.com/kb/en/isnull/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (234,19,'LEAST','Syntax\n------\n\nLEAST(value1,value2,...)\n\nDescription\n-----------\n\nWith two or more arguments, returns the smallest (minimum-valued) argument.\nThe arguments are compared using the following rules:\n\n* If the return value is used in an INTEGER context or all arguments are\ninteger-valued, they are compared as integers.\n* If the return value is used in a REAL context or all arguments are\nreal-valued, they are compared as reals.\n* If any argument is a case-sensitive string, the arguments are compared as\ncase-sensitive strings.\n* In all other cases, the arguments are compared as case-insensitive strings.\n\nLEAST() returns NULL if any argument is NULL.\n\nExamples\n--------\n\nSELECT LEAST(2,0);\n+------------+\n| LEAST(2,0) |\n+------------+\n| 0 |\n+------------+\n\nSELECT LEAST(34.0,3.0,5.0,767.0);\n+---------------------------+\n| LEAST(34.0,3.0,5.0,767.0) |\n+---------------------------+\n| 3.0 |\n+---------------------------+\n\nSELECT LEAST(\'B\',\'A\',\'C\');\n+--------------------+\n| LEAST(\'B\',\'A\',\'C\') |\n+--------------------+\n| A |\n+--------------------+\n\nURL: https://mariadb.com/kb/en/least/','','https://mariadb.com/kb/en/least/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (235,19,'NOT BETWEEN','Syntax\n------\n\nexpr NOT BETWEEN min AND max\n\nDescription\n-----------\n\nThis is the same as NOT (expr BETWEEN min AND max).\n\nNote that the meaning of the alternative form NOT expr BETWEEN min AND max is\naffected by the HIGH_NOT_PRECEDENCE SQL_MODE flag.\n\nExamples\n--------\n\nSELECT 1 NOT BETWEEN 2 AND 3;\n+-----------------------+\n| 1 NOT BETWEEN 2 AND 3 |\n+-----------------------+\n| 1 |\n+-----------------------+\n\nSELECT \'b\' NOT BETWEEN \'a\' AND \'c\';\n+-----------------------------+\n| \'b\' NOT BETWEEN \'a\' AND \'c\' |\n+-----------------------------+\n| 0 |\n+-----------------------------+\n\nNULL:\n\nSELECT 1 NOT BETWEEN 1 AND NULL;\n+--------------------------+\n| 1 NOT BETWEEN 1 AND NULL |\n+--------------------------+\n| NULL |\n+--------------------------+\n\nURL: https://mariadb.com/kb/en/not-between/','','https://mariadb.com/kb/en/not-between/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (236,19,'NOT IN','Syntax\n------\n\nexpr NOT IN (value,...)\n\nDescription\n-----------\n\nThis is the same as NOT (expr IN (value,...)).\n\nExamples\n--------\n\nSELECT 2 NOT IN (0,3,5,7);\n+--------------------+\n| 2 NOT IN (0,3,5,7) |\n+--------------------+\n| 1 |\n+--------------------+\n\nSELECT \'wefwf\' NOT IN (\'wee\',\'wefwf\',\'weg\');\n+--------------------------------------+\n| \'wefwf\' NOT IN (\'wee\',\'wefwf\',\'weg\') |\n+--------------------------------------+\n| 0 |\n+--------------------------------------+\n\nSELECT 1 NOT IN (\'1\', \'2\', \'3\');\n+--------------------------+\n| 1 NOT IN (\'1\', \'2\', \'3\') |\n+--------------------------+\n| 0 |\n+--------------------------+\n\nNULL:\n\nSELECT NULL NOT IN (1, 2, 3);\n+-----------------------+\n| NULL NOT IN (1, 2, 3) |\n+-----------------------+\n| NULL |\n+-----------------------+\n\nSELECT 1 NOT IN (1, 2, NULL);\n+-----------------------+\n| 1 NOT IN (1, 2, NULL) |\n+-----------------------+\n| 0 |\n+-----------------------+\n\nSELECT 5 NOT IN (1, 2, NULL);\n+-----------------------+\n| 5 NOT IN (1, 2, NULL) |\n+-----------------------+\n| NULL |\n+-----------------------+\n\nURL: https://mariadb.com/kb/en/not-in/','','https://mariadb.com/kb/en/not-in/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (237,20,'&','Syntax\n------\n\n&\n\nDescription\n-----------\n\nBitwise AND. Converts the values to binary and compares bits. Only if both the\ncorresponding bits are 1 is the resulting bit also 1.\n\nSee also bitwise OR.\n\nExamples\n--------\n\nSELECT 2&1;\n+-----+\n| 2&1 |\n+-----+\n| 0 |\n+-----+\n\nSELECT 3&1;\n+-----+\n| 3&1 |\n+-----+\n| 1 |\n+-----+\n\nSELECT 29 & 15;\n+---------+\n| 29 & 15 |\n+---------+\n| 13 |\n+---------+\n\nURL: https://mariadb.com/kb/en/bitwise_and/','','https://mariadb.com/kb/en/bitwise_and/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (238,20,'<<','Syntax\n------\n\nvalue1 << value2\n\nDescription\n-----------\n\nConverts a longlong (BIGINT) number (value1) to binary and shifts value2 units\nto the left.\n\nExamples\n--------\n\nSELECT 1 << 2;\n+--------+\n| 1 << 2 |\n+--------+\n| 4 |\n+--------+\n\nURL: https://mariadb.com/kb/en/shift-left/','','https://mariadb.com/kb/en/shift-left/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (239,20,'>>','Syntax\n------\n\nvalue1 >> value2\n\nDescription\n-----------\n\nConverts a longlong (BIGINT) number (value1) to binary and shifts value2 units\nto the right.\n\nExamples\n--------\n\nSELECT 4 >> 2;\n+--------+\n| 4 >> 2 |\n+--------+\n| 1 |\n+--------+\n\nURL: https://mariadb.com/kb/en/shift-right/','','https://mariadb.com/kb/en/shift-right/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (240,20,'BIT_COUNT','Syntax\n------\n\nBIT_COUNT(N)\n\nDescription\n-----------\n\nReturns the number of bits that are set in the argument N.\n\nExamples\n--------\n\nSELECT BIT_COUNT(29), BIT_COUNT(b\'101010\');\n+---------------+----------------------+\n| BIT_COUNT(29) | BIT_COUNT(b\'101010\') |\n+---------------+----------------------+\n| 4 | 3 |\n+---------------+----------------------+\n\nURL: https://mariadb.com/kb/en/bit_count/','','https://mariadb.com/kb/en/bit_count/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (241,20,'^','Syntax\n------\n\n^\n\nDescription\n-----------\n\nBitwise XOR. Converts the values to binary and compares bits. If one (and only\none) of the corresponding bits is 1 is the resulting bit also 1.\n\nExamples\n--------\n\nSELECT 1 ^ 1;\n+-------+\n| 1 ^ 1 |\n+-------+\n| 0 |\n+-------+\n\nSELECT 1 ^ 0;\n+-------+\n| 1 ^ 0 |\n+-------+\n| 1 |\n+-------+\n\nSELECT 11 ^ 3;\n+--------+\n| 11 ^ 3 |\n+--------+\n| 8 |\n+--------+\n\nURL: https://mariadb.com/kb/en/bitwise-xor/','','https://mariadb.com/kb/en/bitwise-xor/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (242,20,'|','Syntax\n------\n\n|\n\nDescription\n-----------\n\nBitwise OR. Converts the values to binary and compares bits. If either of the\ncorresponding bits has a value of 1, the resulting bit is also 1.\n\nSee also bitwise AND.\n\nExamples\n--------\n\nSELECT 2|1;\n+-----+\n| 2|1 |\n+-----+\n| 3 |\n+-----+\n\nSELECT 29 | 15;\n+---------+\n| 29 | 15 |\n+---------+\n| 31 |\n+---------+\n\nURL: https://mariadb.com/kb/en/bitwise-or/','','https://mariadb.com/kb/en/bitwise-or/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (243,20,'~','Syntax\n------\n\n~\n\nDescription\n-----------\n\nBitwise NOT. Converts the value to 4 bytes binary and inverts all bits.\n\nExamples\n--------\n\nSELECT 3 & ~1;\n+--------+\n| 3 & ~1 |\n+--------+\n| 2 |\n+--------+\n\nSELECT 5 & ~1;\n+--------+\n| 5 & ~1 |\n+--------+\n| 4 |\n+--------+\n\nURL: https://mariadb.com/kb/en/bitwise-not/','','https://mariadb.com/kb/en/bitwise-not/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (244,20,'Parentheses','Parentheses are sometimes called precedence operators - this means that they\ncan be used to change the other operator\'s precedence in an expression. The\nexpressions that are written between parentheses are computed before the\nexpressions that are written outside. Parentheses must always contain an\nexpression (that is, they cannot be empty), and can be nested.\n\nFor example, the following expressions could return different results:\n\n* NOT a OR b\n* NOT (a OR b)\n\nIn the first case, NOT applies to a, so if a is FALSE or b is TRUE, the\nexpression returns TRUE. In the second case, NOT applies to the result of a OR\nb, so if at least one of a or b is TRUE, the expression is TRUE.\n\nWhen the precedence of operators is not intuitive, you can use parentheses to\nmake it immediately clear for whoever reads the statement.\n\nThe precedence of the NOT operator can also be affected by the\nHIGH_NOT_PRECEDENCE SQL_MODE flag.\n\nOther uses\n----------\n\nParentheses must always be used to enclose subqueries.\n\nParentheses can also be used in a JOIN statement between multiple tables to\ndetermine which tables must be joined first.\n\nAlso, parentheses are used to enclose the list of parameters to be passed to\nbuilt-in functions, user-defined functions and stored routines. However, when\nno parameter is passed to a stored procedure, parentheses are optional. For\nbuiltin functions and user-defined functions, spaces are not allowed between\nthe function name and the open parenthesis, unless the IGNORE_SPACE SQL_MODE\nis set. For stored routines (and for functions if IGNORE_SPACE is set) spaces\nare allowed before the open parenthesis, including tab characters and new line\ncharacters.\n\nSyntax errors\n-------------\n\nIf there are more open parentheses than closed parentheses, the error usually\nlooks like this:\n\nERROR 1064 (42000): You have an error in your SQL syntax; check the manual that\ncorresponds to your MariaDB server version for the right syntax to use near \'\'\na\nt line 1\n\nNote the empty string.\n\nIf there are more closed parentheses than open parentheses, the error usually\nlooks like this:\n\nERROR 1064 (42000): You have an error in your SQL syntax; check the manual that\ncorresponds to your MariaDB server version for the right syntax to use near \')\'\nat line 1\n\nNote the quoted closed parenthesis.\n\nURL: https://mariadb.com/kb/en/parentheses/','','https://mariadb.com/kb/en/parentheses/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (245,20,'TRUE FALSE','Description\n-----------\n\nThe constants TRUE and FALSE evaluate to 1 and 0, respectively. The constant\nnames can be written in any lettercase.\n\nExamples\n--------\n\nSELECT TRUE, true, FALSE, false;\n+------+------+-------+-------+\n| TRUE | TRUE | FALSE | FALSE |\n+------+------+-------+-------+\n| 1 | 1 | 0 | 0 |\n+------+------+-------+-------+\n\nURL: https://mariadb.com/kb/en/true-false/','','https://mariadb.com/kb/en/true-false/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (246,21,'ANALYZE TABLE','Syntax\n------\n\nANALYZE [NO_WRITE_TO_BINLOG | LOCAL] TABLE tbl_name [,tbl_name ...]\n [PERSISTENT FOR\n { ALL\n | COLUMNS ([col_name [,col_name ...]]) INDEXES ([index_name [,index_name\n...]])\n }\n ]\n\nDescription\n-----------\n\nANALYZE TABLE analyzes and stores the key distribution for a table (index\nstatistics). This statement works with MyISAM, Aria and InnoDB tables. During\nthe analysis, InnoDB will allow reads/writes, and MyISAM/Aria reads/inserts.\nFor MyISAM tables, this statement is equivalent to using myisamchk --analyze.\n\nFor more information on how the analysis works within InnoDB, see InnoDB\nLimitations.\n\nMariaDB uses the stored key distribution to decide the order in which tables\nshould be joined when you perform a join on something other than a constant.\nIn addition, key distributions can be used when deciding which indexes to use\nfor a specific table within a query.\n\nThis statement requires SELECT and INSERT privileges for the table.\n\nBy default, ANALYZE TABLE statements are written to the binary log and will be\nreplicated. The NO_WRITE_TO_BINLOG keyword (LOCAL is an alias) will ensure the\nstatement is not written to the binary log.\n\nFrom MariaDB 10.3.19, ANALYZE TABLE statements are not logged to the binary\nlog if read_only is set. See also Read-Only Slaves.\n\nANALYZE TABLE is also supported for partitioned tables. You can use ALTER\nTABLE ... ANALYZE PARTITION to analyze one or more partitions.\n\nThe Aria storage engine supports progress reporting for the ANALYZE TABLE\nstatement.\n\nEngine-Independent Statistics / PERSISTENT FOR\n----------------------------------------------\n\nANALYZE TABLE supports engine-independent statistics. See Engine-Independent\nTable Statistics: Collecting Statistics with the ANALYZE TABLE Statement for\nmore information.\n\nUseful Variables\n----------------\n\nFor calculating the number of duplicates, ANALYZE TABLE uses a buffer of\nsort_buffer_size bytes per column. You can slightly increase the speed of\nANALYZE TABLE by increasing this variable.\n\nExamples\n--------\n\n-- update all engine-independent statistics for all columns and indexes\nANALYZE TABLE tbl PERSISTENT FOR ALL;\n\n-- update specific columns and indexes:\nANALYZE TABLE tbl PERSISTENT FOR COLUMNS (col1,col2,...) INDEXES\n(idx1,idx2,...);\n\n-- empty lists are allowed:\nANALYZE TABLE tbl PERSISTENT FOR COLUMNS (col1,col2,...) INDEXES ();\nANALYZE TABLE tbl PERSISTENT FOR COLUMNS () INDEXES (idx1,idx2,...);\n\n-- the following will only update mysql.table_stats fields:\nANALYZE TABLE tbl PERSISTENT FOR COLUMNS () INDEXES ();\n\n-- when use_stat_tables is set to \'COMPLEMENTARY\' or \'PREFERABLY\', \n-- a simple ANALYZE TABLE collects engine-independent statistics for all\ncolumns and indexes.\nSET SESSION use_stat_tables=\'COMPLEMENTARY\';\nANALYZE TABLE tbl;\n\nURL: https://mariadb.com/kb/en/analyze-table/','','https://mariadb.com/kb/en/analyze-table/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (247,21,'CHECK TABLE','Syntax\n------\n\nCHECK TABLE tbl_name [, tbl_name] ... [option] ...\n\noption = {FOR UPGRADE | QUICK | FAST | MEDIUM | EXTENDED | CHANGED}\n\nDescription\n-----------\n\nCHECK TABLE checks a table or tables for errors. CHECK TABLE works for\nArchive, Aria, CSV, InnoDB and MyISAM tables. For Aria and MyISAM tables, the\nkey statistics are updated as well. For CSV, see also Checking and Repairing\nCSV Tables.\n\nAs an alternative, myisamchk is a commandline tool for checking MyISAM tables\nwhen the tables are not being accessed. For Aria tables, there is a similar\ntool: aria_chk.\n\nFor checking dynamic columns integrity, COLUMN_CHECK() can be used.\n\nCHECK TABLE can also check views for problems, such as tables that are\nreferenced in the view definition that no longer exist.\n\nCHECK TABLE is also supported for partitioned tables. You can use ALTER TABLE\n... CHECK PARTITION to check one or more partitions.\n\nThe meaning of the different options are as follows - note that this can vary\na bit between storage engines:\n\n+-----+----------------------------------------------------------------------+\n| FOR | Do a very quick check if the storage format for the table has |\n| UPG | changed so that one needs to do a REPAIR. This is only needed when |\n| ADE | one upgrades between major versions of MariaDB or MySQL. This is |\n| | usually done by running mariadb-upgrade. |\n+-----+----------------------------------------------------------------------+\n| FAS | Only check tables that has not been closed properly or are marked |\n| | as corrupt. Only supported by the MyISAM and Aria engines. For |\n| | other engines the table is checked normally |\n+-----+----------------------------------------------------------------------+\n| CHA | Check only tables that has changed since last REPAIR / CHECK. Only |\n| GED | supported by the MyISAM and Aria engines. For other engines the |\n| | table is checked normally. |\n+-----+----------------------------------------------------------------------+\n| QUI | Do a fast check. For MyISAM and Aria, this means skipping the check |\n| K | of the delete link chain, which may take some time. |\n+-----+----------------------------------------------------------------------+\n| MED | Scan also the data files. Checks integrity between data and index |\n| UM | files with checksums. In most cases this should find all possible |\n| | errors. |\n+-----+----------------------------------------------------------------------+\n| EXT | Does a full check to verify every possible error. For InnoDB, Aria, |\n| NDE | and MyISAM, verify for each row that all its keys exists, and for |\n| | those index keys, they point back to the primary clustered key. |\n| | This may take a long time on large tables. This option was |\n| | previously ignored by InnoDB before MariaDB 10.6.11, MariaDB |\n| | 10.7.7, MariaDB 10.8.6 and MariaDB 10.9.4. |\n+-----+----------------------------------------------------------------------+\n\nFor most cases running CHECK TABLE without options or MEDIUM should be good\nenough.\n\nThe Aria storage engine supports progress reporting for this statement.\n\nIf you want to know if two tables are identical, take a look at CHECKSUM TABLE.\n\nInnoDB\n------\n\nIf CHECK TABLE finds an error in an InnoDB table, MariaDB might shutdown to\nprevent the error propagation. In this case, the problem will be reported in\nthe error log. Otherwise the table or an index might be marked as corrupted,\nto prevent use. This does not happen with some minor problems, like a wrong\nnumber of entries in a secondary index. Those problems are reported in the\noutput of CHECK TABLE.\n\nEach tablespace contains a header with metadata. This header is not checked by\nthis statement.\n\nDuring the execution of CHECK TABLE, other threads may be blocked.\n\nExamples\n--------\n\ncheck table y extended;\n+--------+-------+----------+----------+\n| Table | Op | Msg_type | Msg_text |\n+--------+-------+----------+----------+\n| test.y | check | status | OK |\n+--------+-------+----------+----------+\n\nURL: https://mariadb.com/kb/en/check-table/','','https://mariadb.com/kb/en/check-table/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (248,21,'CHECK VIEW','Syntax\n------\n\nCHECK VIEW view_name\n\nDescription\n-----------\n\nThe CHECK VIEW statement was introduced in MariaDB 10.0.18 to assist with\nfixing MDEV-6916, an issue introduced in MariaDB 5.2 where the view algorithms\nwere swapped. It checks whether the view algorithm is correct. It is run as\npart of mariadb-upgrade, and should not normally be required in regular use.\n\nURL: https://mariadb.com/kb/en/check-view/','','https://mariadb.com/kb/en/check-view/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (249,21,'CHECKSUM TABLE','Syntax\n------\n\nCHECKSUM TABLE tbl_name [, tbl_name] ... [ QUICK | EXTENDED ]\n\nDescription\n-----------\n\nCHECKSUM TABLE reports a table checksum. This is very useful if you want to\nknow if two tables are the same (for example on a master and slave).\n\nWith QUICK, the live table checksum is reported if it is available, or NULL\notherwise. This is very fast. A live checksum is enabled by specifying the\nCHECKSUM=1 table option when you create the table; currently, this is\nsupported only for Aria and MyISAM tables.\n\nWith EXTENDED, the entire table is read row by row and the checksum is\ncalculated. This can be very slow for large tables.\n\nIf neither QUICK nor EXTENDED is specified, MariaDB returns a live checksum if\nthe table storage engine supports it and scans the table otherwise.\n\nCHECKSUM TABLE requires the SELECT privilege for the table.\n\nFor a nonexistent table, CHECKSUM TABLE returns NULL and generates a warning.\n\nThe table row format affects the checksum value. If the row format changes,\nthe checksum will change. This means that when a table created with a\nMariaDB/MySQL version is upgraded to another version, the checksum value will\nprobably change.\n\nTwo identical tables should always match to the same checksum value; however,\nalso for non-identical tables there is a very slight chance that they will\nreturn the same value as the hashing algorithm is not completely\ncollision-free.\n\nIdentical Tables\n----------------\n\nIdentical tables mean that the CREATE statement is identical and that the\nfollowing variable, which affects the storage formats, was the same when the\ntables were created:\n\n* mysql56-temporal-format\n\nDifferences Between MariaDB and MySQL\n-------------------------------------\n\nCHECKSUM TABLE may give a different result as MariaDB doesn\'t ignore NULLs in\nthe columns as MySQL 5.1 does (Later MySQL versions should calculate checksums\nthe same way as MariaDB). You can get the \'old style\' checksum in MariaDB by\nstarting mysqld with the --old option. Note however that that the MyISAM and\nAria storage engines in MariaDB are using the new checksum internally, so if\nyou are using --old, the CHECKSUM command will be slower as it needs to\ncalculate the checksum row by row. Starting from MariaDB Server 10.9, --old is\ndeprecated and will be removed in a future release. Set --old-mode or OLD_MODE\nto COMPAT_5_1_CHECKSUM to get \'old style\' checksum.\n\nURL: https://mariadb.com/kb/en/checksum-table/','','https://mariadb.com/kb/en/checksum-table/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (250,21,'OPTIMIZE TABLE','Syntax\n------\n\nOPTIMIZE [NO_WRITE_TO_BINLOG | LOCAL] TABLE\n tbl_name [, tbl_name] ...\n [WAIT n | NOWAIT]\n\nDescription\n-----------\n\nOPTIMIZE TABLE has two main functions. It can either be used to defragment\ntables, or to update the InnoDB fulltext index.\n\nWAIT/NOWAIT\n-----------\n\nSet the lock wait timeout. See WAIT and NOWAIT.\n\nDefragmenting\n-------------\n\nOPTIMIZE TABLE works for InnoDB (before MariaDB 10.1.1, only if the\ninnodb_file_per_table server system variable is set), Aria, MyISAM and ARCHIVE\ntables, and should be used if you have deleted a large part of a table or if\nyou have made many changes to a table with variable-length rows (tables that\nhave VARCHAR, VARBINARY, BLOB, or TEXT columns). Deleted rows are maintained\nin a linked list and subsequent INSERT operations reuse old row positions.\n\nThis statement requires SELECT and INSERT privileges for the table.\n\nBy default, OPTIMIZE TABLE statements are written to the binary log and will\nbe replicated. The NO_WRITE_TO_BINLOG keyword (LOCAL is an alias) will ensure\nthe statement is not written to the binary log.\n\nOPTIMIZE TABLE statements are not logged to the binary log if read_only is\nset. See also Read-Only Slaves.\n\nOPTIMIZE TABLE is also supported for partitioned tables. You can use ALTER\nTABLE ... OPTIMIZE PARTITION to optimize one or more partitions.\n\nYou can use OPTIMIZE TABLE to reclaim the unused space and to defragment the\ndata file. With other storage engines, OPTIMIZE TABLE does nothing by default,\nand returns this message: \" The storage engine for the table doesn\'t support\noptimize\". However, if the server has been started with the --skip-new option,\nOPTIMIZE TABLE is linked to ALTER TABLE, and recreates the table. This\noperation frees the unused space and updates index statistics.\n\nThe Aria storage engine supports progress reporting for this statement.\n\nIf a MyISAM table is fragmented, concurrent inserts will not be performed\nuntil an OPTIMIZE TABLE statement is executed on that table, unless the\nconcurrent_insert server system variable is set to ALWAYS.\n\nUpdating an InnoDB fulltext index\n---------------------------------\n\nWhen rows are added or deleted to an InnoDB fulltext index, the index is not\nimmediately re-organized, as this can be an expensive operation. Change\nstatistics are stored in a separate location . The fulltext index is only\nfully re-organized when an OPTIMIZE TABLE statement is run.\n\nBy default, an OPTIMIZE TABLE will defragment a table. In order to use it to\nupdate fulltext index statistics, the innodb_optimize_fulltext_only system\nvariable must be set to 1. This is intended to be a temporary setting, and\nshould be reset to 0 once the fulltext index has been re-organized.\n\nSince fulltext re-organization can take a long time, the\ninnodb_ft_num_word_optimize variable limits the re-organization to a number of\nwords (2000 by default). You can run multiple OPTIMIZE statements to fully\nre-organize the index.\n\nDefragmenting InnoDB tablespaces\n--------------------------------\n\nMariaDB 10.1.1 merged the Facebook/Kakao defragmentation patch, allowing one\nto use OPTIMIZE TABLE to defragment InnoDB tablespaces. For this functionality\nto be enabled, the innodb_defragment system variable must be enabled. No new\ntables are created and there is no need to copy data from old tables to new\ntables. Instead, this feature loads n pages (determined by\ninnodb-defragment-n-pages) and tries to move records so that pages would be\nfull of records and then frees pages that are fully empty after the operation.\nNote that tablespace files (including ibdata1) will not shrink as the result\nof defragmentation, but one will get better memory utilization in the InnoDB\nbuffer pool as there are fewer data pages in use.\n\nSee Defragmenting InnoDB Tablespaces for more details.\n\nURL: https://mariadb.com/kb/en/optimize-table/','','https://mariadb.com/kb/en/optimize-table/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (251,21,'REPAIR TABLE','Syntax\n------\n\nREPAIR [NO_WRITE_TO_BINLOG | LOCAL] TABLE\n tbl_name [, tbl_name] ...\n [QUICK] [EXTENDED] [USE_FRM]\n\nDescription\n-----------\n\nREPAIR TABLE repairs a possibly corrupted table. By default, it has the same\neffect as\n\nmyisamchk --recover tbl_name\n\nor\n\naria_chk --recover tbl_name\n\nSee aria_chk and myisamchk for more.\n\nREPAIR TABLE works for Archive, Aria, CSV and MyISAM tables. For InnoDB, see\nrecovery modes. For CSV, see also Checking and Repairing CSV Tables. For\nArchive, this statement also improves compression. If the storage engine does\nnot support this statement, a warning is issued.\n\nThis statement requires SELECT and INSERT privileges for the table.\n\nBy default, REPAIR TABLE statements are written to the binary log and will be\nreplicated. The NO_WRITE_TO_BINLOG keyword (LOCAL is an alias) will ensure the\nstatement is not written to the binary log.\n\nFrom MariaDB 10.3.19, REPAIR TABLE statements are not logged to the binary log\nif read_only is set. See also Read-Only Slaves.\n\nWhen an index is recreated, the storage engine may use a configurable buffer\nin the process. Incrementing the buffer speeds up the index creation. Aria and\nMyISAM allocate a buffer whose size is defined by aria_sort_buffer_size or\nmyisam_sort_buffer_size, also used for ALTER TABLE.\n\nQUICK\n-----\n\nWhen specified, REPAIR TABLE will not modify the data file, only attempting to\nrepair the index file. The same behavior can be achieved with myisamchk\n--recover --quick.\n\nEXTENDED\n--------\n\nCreates the index row by row rather than sorting and creating a single index.\nSimilar to myisamchk --safe-recover.\n\nUSE_FRM\n-------\n\nFor use only when the index file is missing or its header corrupted. MariaDB\nthen attempts to recreate it using the .frm file. There is no equivalent\nmyisamchk option.\n\nPartitioned Tables\n------------------\n\nREPAIR TABLE is also supported for partitioned tables with the ALTER TABLE ...\nREPAIR PARTITION statement. However, the USE_FRM option cannot be used with\nthis statement on a partitioned table. See Repairing Partitions for details.\n\nProgress Reporting\n------------------\n\nThe Aria storage engine supports progress reporting for this statement.\n\nURL: https://mariadb.com/kb/en/repair-table/','','https://mariadb.com/kb/en/repair-table/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (252,21,'REPAIR VIEW','Syntax\n------\n\nREPAIR [NO_WRITE_TO_BINLOG | LOCAL] VIEW view_name[, view_name] ... [FROM\nMYSQL]\n\nDescription\n-----------\n\nThe REPAIR VIEW statement was introduced to assist with fixing MDEV-6916, an\nissue introduced in MariaDB 5.2 where the view algorithms were swapped\ncompared to their MySQL on disk representation. It checks whether the view\nalgorithm is correct. It is run as part of mariadb-upgrade, and should not\nnormally be required in regular use.\n\nBy default it corrects the checksum and if necessary adds the mariadb-version\nfield. If the optional FROM MYSQL clause is used, and no mariadb-version field\nis present, the MERGE and TEMPTABLE algorithms are toggled.\n\nBy default, REPAIR VIEW statements are written to the binary log and will be\nreplicated. The NO_WRITE_TO_BINLOG keyword (LOCAL is an alias) will ensure the\nstatement is not written to the binary log.\n\nURL: https://mariadb.com/kb/en/repair-view/','','https://mariadb.com/kb/en/repair-view/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (253,22,'mysql.func Table','The mysql.func table stores information about user-defined functions (UDFs)\ncreated with the CREATE FUNCTION UDF statement.\n\nMariaDB starting with 10.4\n--------------------------\nIn MariaDB 10.4 and later, this table uses the Aria storage engine.\n\nMariaDB until 10.3\n------------------\nIn MariaDB 10.3 and before, this table uses the MyISAM storage engine.\n\nThe mysql.func table contains the following fields:\n\n+----------+---------+---------+-------+--------------+---------------------+\n| Field | Type | Null | Key | Default | Description |\n+----------+---------+---------+-------+--------------+---------------------+\n| name | char(64 | NO | PRI | | UDF name |\n| | | | | | |\n+----------+---------+---------+-------+--------------+---------------------+\n| ret | tinyint | NO | | 0 | |\n| | 1) | | | | |\n+----------+---------+---------+-------+--------------+---------------------+\n| dl | char(12 | NO | | | Shared library name |\n| | ) | | | | |\n+----------+---------+---------+-------+--------------+---------------------+\n| type | enum(\'f | NO | | NULL | Type, either |\n| | nction\' | | | | function or |\n| | \'aggreg | | | | aggregate. |\n| | te\') | | | | Aggregate |\n| | | | | | functions are |\n| | | | | | summary functions |\n| | | | | | such as SUM() and |\n| | | | | | AVG(). |\n+----------+---------+---------+-------+--------------+---------------------+\n\nExample\n-------\n\nSELECT * FROM mysql.func;\n+------------------------------+-----+--------------+-----------+\n| name | ret | dl | type |\n+------------------------------+-----+--------------+-----------+\n| spider_direct_sql | 2 | ha_spider.so | function |\n| spider_bg_direct_sql | 2 | ha_spider.so | aggregate |\n| spider_ping_table | 2 | ha_spider.so | function |\n| spider_copy_tables | 2 | ha_spider.so | function |\n| spider_flush_table_mon_cache | 2 | ha_spider.so | function |\n+------------------------------+-----+--------------+-----------+\n\nURL: https://mariadb.com/kb/en/mysqlfunc-table/','','https://mariadb.com/kb/en/mysqlfunc-table/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (254,22,'CREATE FUNCTION UDF','Syntax\n------\n\nCREATE [OR REPLACE] [AGGREGATE] FUNCTION [IF NOT EXISTS] function_name\n RETURNS {STRING|INTEGER|REAL|DECIMAL}\n SONAME shared_library_name\n\nDescription\n-----------\n\nA user-defined function (UDF) is a way to extend MariaDB with a new function\nthat works like a native (built-in) MariaDB function such as ABS() or CONCAT().\n\nfunction_name is the name that should be used in SQL statements to invoke the\nfunction.\n\nTo create a function, you must have the INSERT privilege for the mysql\ndatabase. This is necessary becauseCREATE FUNCTION adds a row to the\nmysql.func system table that records the function\'s name, type, and shared\nlibrary name. If you do not have this table, you should run the\nmariadb-upgrade command to create it.\n\nUDFs need to be written in C, C++ or another language that uses C calling\nconventions, MariaDB needs to have been dynamically compiled, and your\noperating system must support dynamic loading.\n\nFor an example, see sql/udf_example.cc in the source tree. For a collection of\nexisting UDFs see http://www.mysqludf.org/.\n\nStatements making use of user-defined functions are not safe for replication.\n\nFor creating a stored function as opposed to a user-defined function, see\nCREATE FUNCTION.\n\nFor valid identifiers to use as function names, see Identifier Names.\n\nRETURNS\n-------\n\nThe RETURNS clause indicates the type of the function\'s return value, and can\nbe one of STRING, INTEGER, REAL or DECIMAL. DECIMAL functions currently return\nstring values and should be written like STRING functions.\n\nshared_library_name\n-------------------\n\nshared_library_name is the basename of the shared object file that contains\nthe code that implements the function. The file must be located in the plugin\ndirectory. This directory is given by the value of the plugin_dir system\nvariable. Note that before MariaDB/MySQL 5.1, the shared object could be\nlocated in any directory that was searched by your system\'s dynamic linker.\n\nAGGREGATE\n---------\n\nAggregate functions are summary functions such as SUM() and AVG().\n\nMariaDB starting with 10.4\n--------------------------\nAggregate UDF functions can be used as window functions.\n\nOR REPLACE\n----------\n\nMariaDB starting with 10.1.3\n----------------------------\nThe OR REPLACE clause was added in MariaDB 10.1.3\n\nIf the optional OR REPLACE clause is used, it acts as a shortcut for:\n\nDROP FUNCTION IF EXISTS function_name;\nCREATE FUNCTION name ...;\n\nIF NOT EXISTS\n-------------\n\nMariaDB starting with 10.1.3\n----------------------------\nThe IF NOT EXISTS clause was added in MariaDB 10.1.3\n\nWhen the IF NOT EXISTS clause is used, MariaDB will return a warning instead\nof an error if the specified function already exists. Cannot be used together\nwith OR REPLACE.\n\nUpgrading a UDF\n---------------\n\nTo upgrade the UDF\'s shared library, first run a DROP FUNCTION statement, then\nupgrade the shared library and finally run the CREATE FUNCTION statement. If\nyou upgrade without following this process, you may crash the server.\n\nExamples\n--------\n\nCREATE FUNCTION jsoncontains_path RETURNS integer SONAME \'ha_connect.so\';\nQuery OK, 0 rows affected (0.00 sec)\n\nOR REPLACE and IF NOT EXISTS:\n\nCREATE FUNCTION jsoncontains_path RETURNS integer SONAME \'ha_connect.so\';\nERROR 1125 (HY000): Function \'jsoncontains_path\' already exists\n\nCREATE OR REPLACE FUNCTION jsoncontains_path RETURNS integer SONAME\n\'ha_connect.so\';\nQuery OK, 0 rows affected (0.00 sec)\n\nCREATE FUNCTION IF NOT EXISTS jsoncontains_path RETURNS integer SONAME\n\'ha_connect.so\';\nQuery OK, 0 rows affected, 1 warning (0.00 sec)\n\nSHOW WARNINGS;\n+-------+------+---------------------------------------------+\n| Level | Code | Message |\n+-------+------+---------------------------------------------+\n| Note | 1125 | Function \'jsoncontains_path\' already exists |\n+-------+------+---------------------------------------------+\n\nURL: https://mariadb.com/kb/en/create-function-udf/','','https://mariadb.com/kb/en/create-function-udf/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (255,22,'DROP FUNCTION UDF','Syntax\n------\n\nDROP FUNCTION [IF EXISTS] function_name\n\nDescription\n-----------\n\nThis statement drops the user-defined function (UDF) named function_name.\n\nTo drop a function, you must have the DELETE privilege for the mysql database.\nThis is because DROP FUNCTION removes the row from the mysql.func system table\nthat records the function\'s name, type and shared library name.\n\nFor dropping a stored function, see DROP FUNCTION.\n\nUpgrading a UDF\n---------------\n\nTo upgrade the UDF\'s shared library, first run a DROP FUNCTION statement, then\nupgrade the shared library and finally run the CREATE FUNCTION statement. If\nyou upgrade without following this process, you may crash the server.\n\nExamples\n--------\n\nDROP FUNCTION jsoncontains_path;\n\nIF EXISTS:\n\nDROP FUNCTION jsoncontains_path;\nERROR 1305 (42000): FUNCTION test.jsoncontains_path does not exist\n\nDROP FUNCTION IF EXISTS jsoncontains_path;\nQuery OK, 0 rows affected, 1 warning (0.00 sec)\n\nSHOW WARNINGS;\n+-------+------+------------------------------------------------+\n| Level | Code | Message |\n+-------+------+------------------------------------------------+\n| Note | 1305 | FUNCTION test.jsoncontains_path does not exist |\n+-------+------+------------------------------------------------+\n\nURL: https://mariadb.com/kb/en/drop-function-udf/','','https://mariadb.com/kb/en/drop-function-udf/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (256,22,'Creating User-Defined Functions','User-defined functions allow MariaDB to be extended with a new function that\nworks like a native (built-in) MariaDB function such as ABS() or CONCAT().\nThere are alternative ways to add a new function: writing a native function\n(which requires modifying and compiling the server source code), or writing a\nstored function.\n\nStatements making use of user-defined functions are not safe for replication.\n\nFunctions are written in C or C++, and to make use of them, the operating\nsystem must support dynamic loading.\n\nEach new SQL function requires corresponding functions written in C/C++. In\nthe list below, at least the main function - x() - and one other, are\nrequired. x should be replaced by the name of the function you are creating.\n\nAll functions need to be thread-safe, so not global or static variables that\nchange can be allocated. Memory is allocated in x_init()/ and freed in\nx_deinit().\n\nSimple Functions\n----------------\n\nx()\n---\n\nRequired for all UDFs; this is where the results are calculated.\n\n+------------------------------------------+----------------------------------+\n| C/C++ type | SQL type |\n+------------------------------------------+----------------------------------+\n| char * | STRING |\n+------------------------------------------+----------------------------------+\n| long long | INTEGER |\n+------------------------------------------+----------------------------------+\n| double | REAL |\n+------------------------------------------+----------------------------------+\n\nDECIMAL functions return string values, and so should be written accordingly.\nIt is not possible to create ROW functions.\n\nx_init()\n--------\n\nInitialization function for x(). Can be used for the following:\n\n* Check the number of arguments to X() (the SQL equivalent).\n* Verify the argument types, or to force arguments to be of a particular type\nafter the function is called.\n* Specify whether the result can be NULL.\n* Specify the maximum result length.\n* For REAL functions, specify the maximum number of decimals for the result.\n* Allocate any required memory.\n\nx_deinit()\n----------\n\nDe-initialization function for x(). Used to de-allocate memory that was\nallocated in x_init().\n\nDescription\n-----------\n\nEach time the SQL function X() is called:\n\n* MariaDB will first call the C/C++ initialization function, x_init(),\nassuming it exists. All setup will be performed, and if it returns an error,\nthe SQL statement is aborted and no further functions are called.\n* If there is no x_init() function, or it has been called and did not return\nan error, x() is then called once per row.\n* After all rows have finished processing, x_deinit() is called, if present,\nto clean up by de-allocating any memory that was allocated in x_init().\n* See User-defined Functions Calling Sequences for more details on the\nfunctions.\n\nAggregate Functions\n-------------------\n\nThe following functions are required for aggregate functions, such as AVG()\nand SUM(). When using CREATE FUNCTION, the AGGREGATE keyword is required.\n\nx_clear()\n---------\n\nUsed to reset the current aggregate, but without inserting the argument as the\ninitial aggregate value for the new group.\n\nx_add()\n-------\n\nUsed to add the argument to the current aggregate.\n\nx_remove()\n----------\n\nStarting from MariaDB 10.4, improves the support of window functions (so it is\nnot obligatory to add it) and should remove the argument from the current\naggregate.\n\nDescription\n-----------\n\nEach time the aggregate SQL function X() is called:\n\n* MariaDB will first call the C/C++ initialization function, x_init(),\nassuming it exists. All setup will be performed, and if it returns an error,\nthe SQL statement is aborted and no further functions are called.\n* If there is no x_init() function, or it has been called and did not return\nan error, x() is then called once per row.\n* After all rows have finished processing, x_deinit() is called, if present,\nto clean up by de-allocating any memory that was allocated in x_init().\n\n* MariaDB will first call the C/C++ initialization function, x_init(),\nassuming it exists. All setup will be performed, and if it returns an error,\nthe SQL statement is aborted and no further functions are called.\n* The table is sorted according to the GROUP BY expression.\n* x_clear() is called for the first row of each new group.\n* x_add() is called once per row for each row in the same group.\n* x() is called when the group changes, or after the last row, to get the\naggregate result. \n* The latter three steps are repeated until all rows have been processed.\n* After all rows have finished processing, x_deinit() is called, if present,\nto clean up by de-allocating any memory that was allocated in x_init().\n\nExamples\n--------\n\nFor an example, see sql/udf_example.cc in the source tree. For a collection of\nexisting UDFs see https://github.com/mysqludf.\n\nURL: https://mariadb.com/kb/en/creating-user-defined-functions/','','https://mariadb.com/kb/en/creating-user-defined-functions/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (257,22,'User-Defined Functions Calling Sequences','The functions described in Creating User-defined Functions are expanded on\nthis page. They are declared as follows:\n\nSimple Functions\n----------------\n\nx()\n---\n\nIf x() returns an integer, it is declared as follows:\n\nlong long x(UDF_INIT *initid, UDF_ARGS *args,\n char *is_null, char *error);\n\nIf x() returns a string (DECIMAL functions also return string values), it is\ndeclared as follows:\n\nchar *x(UDF_INIT *initid, UDF_ARGS *args,\n char *result, unsigned long *length,\n char *is_null, char *error);\n\nIf x() returns a real, it is declared as follows:\n\ndouble x(UDF_INIT *initid, UDF_ARGS *args,\n char *is_null, char *error);\n\nx_init()\n--------\n\nmy_bool x_init(UDF_INIT *initid, UDF_ARGS *args, char *message);\n\nx_deinit()\n----------\n\nvoid x_deinit(UDF_INIT *initid);\n\nDescription\n-----------\n\ninitid is a parameter passed to all three functions that points to a UDF_INIT\nstructure, used for communicating information between the functions. Its\nstructure members are:\n\n* my_bool maybe_null\nmaybe_null should be set to 1 if x_init can return a NULL value, Defaults to 1\nif any arguments are declared maybe_null.\n\n* unsigned int decimals\nNumber of decimals after the decimal point. The default, if an explicit number\nof decimals is passed in the arguments to the main function, is the maximum\nnumber of decimals, so if 9.5, 9.55 and 9.555 are passed to the function, the\ndefault would be three (based on 9.555, the maximum). If there are no\nexplicit number of decimals, the default is set to 31, or one more than the\nmaximum for the DOUBLE, FLOAT and DECIMAL types. This default can be changed\nin the function to suit the actual calculation.\n\n* unsigned int max_length\nMaximum length of the result. For integers, the default is 21. For strings,\nthe length of the longest argument. For reals, the default is 13 plus the\nnumber of decimals indicated by initid->decimals. The length includes any\nsigns or decimal points. Can also be set to 65KB or 16MB in order to return a\nBLOB. The memory remains unallocated, but this is used to decide on the data\ntype to use if the data needs to be temporarily stored.\n\n* char *ptr\nA pointer for use as required by the function. Commonly, initid->ptr is used\nto communicate allocated memory, with x_init() allocating the memory and\nassigning it to this pointer, x() using it, and x_deinit() de-allocating it.\n\n* my_bool const_item\nShould be set to 1 in x_init() if x() always returns the same value, otherwise\n0.\n\nAggregate Functions\n-------------------\n\nx_clear()\n---------\n\nx_clear() is a required function for aggregate functions, and is declared as\nfollows:\n\nvoid x_clear(UDF_INIT *initid, char *is_null, char *error);\n\nIt is called when the summary results need to be reset, that is at the\nbeginning of each new group. but also to reset the values when there were no\nmatching rows.\n\nis_null is set to point to CHAR(0) before calling x_clear().\n\nIn the case of an error, you can store the value to which the error argument\npoints (a single-byte variable, not a string string buffer) in the variable.\n\nx_reset()\n---------\n\nx_reset() is declared as follows:\n\nvoid x_reset(UDF_INIT *initid, UDF_ARGS *args,\n char *is_null, char *error);\n\nIt is called on finding the first row in a new group. Should reset the summary\nvariables, and then use UDF_ARGS as the first value in the group\'s internal\nsummary value. The function is not required if the UDF interface uses\nx_clear().\n\nx_add()\n-------\n\nx_add() is declared as follows:\n\nvoid x_add(UDF_INIT *initid, UDF_ARGS *args,\n char *is_null, char *error);\n\nIt is called for all rows belonging to the same group, and should be used to\nadd the value in UDF_ARGS to the internal summary variable.\n\nx_remove()\n----------\n\nx_remove() was added in MariaDB 10.4 and is declared as follows (same as\nx_add()):\n\nvoid x_remove(UDF_INIT* initid, UDF_ARGS* args,\n char* is_null, char *error );\n\nIt adds more efficient support of aggregate UDFs as window functions.\nx_remove() should \"subtract\" the row (reverse x_add()). In MariaDB 10.4\naggregate UDFs will work as WINDOW functions without x_remove() but it will\nnot be so efficient.\n\nIf x_remove() supported (defined) detected automatically.\n\nURL: https://mariadb.com/kb/en/user-defined-functions-calling-sequences/','','https://mariadb.com/kb/en/user-defined-functions-calling-sequences/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (258,22,'User-Defined Functions Security','The MariaDB server imposes a number of limitations on user-defined functions\nfor security purposes.\n\n* The INSERT privilege for the mysql database is required to run CREATE\nFUNCTION, as a record will be added to the mysql.func-table.\n* The DELETE privilege for the mysql database is required to run DROP FUNCTION\nas the corresponding record will be removed from the mysql.func-table.\n* UDF object files can only be placed in the plugin directory, as specified by\nthe value of the plugin_dir system variable.\n* At least one symbol, beyond the required x() - corresponding to an SQL\nfunction X()) - is required. These can be x_init(), x_deinit(), xxx_reset(),\nx_clear() and x_add() functions (see Creating User-defined Functions). The\nallow-suspicious-udfs mariadbd option (by default unset) provides a\nworkaround, permitting only one symbol to be used. This is not recommended, as\nit opens the possibility of loading shared objects that are not legitimate\nuser-defined functions.\n\nURL: https://mariadb.com/kb/en/user-defined-functions-security/','','https://mariadb.com/kb/en/user-defined-functions-security/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (259,23,'Numeric Data Type Overview','There are a number of numeric data types:\n\n* TINYINT\n* BOOLEAN - Synonym for TINYINT(1)\n* INT1 - Synonym for TINYINT\n* SMALLINT\n* INT2 - Synonym for SMALLINT\n* MEDIUMINT\n* INT3 - Synonym for MEDIUMINT\n* INT, INTEGER\n* INT4 - Synonym for INT\n* BIGINT\n* INT8 - Synonym for BIGINT\n* DECIMAL, DEC, NUMERIC, FIXED\n* FLOAT\n* DOUBLE, DOUBLE PRECISION, REAL\n* BIT\n\nSee the specific articles for detailed information on each.\n\nSIGNED, UNSIGNED and ZEROFILL\n-----------------------------\n\nMost numeric types can be defined as SIGNED, UNSIGNED or ZEROFILL, for example:\n\nTINYINT[(M)] [SIGNED | UNSIGNED | ZEROFILL]\n\nIf SIGNED, or no attribute, is specified, a portion of the numeric type will\nbe reserved for the sign (plus or minus). For example, a TINYINT SIGNED can\nrange from -128 to 127.\n\nIf UNSIGNED is specified, no portion of the numeric type is reserved for the\nsign, so for integer types range can be larger. For example, a TINYINT\nUNSIGNED can range from 0 to 255. Floating point and fixed-point types also\ncan be UNSIGNED, but this only prevents negative values from being stored and\ndoesn\'t alter the range.\n\nIf ZEROFILL is specified, the column will be set to UNSIGNED and the spaces\nused by default to pad the field are replaced with zeros. ZEROFILL is ignored\nin expressions or as part of a UNION. ZEROFILL is a non-standard MySQL and\nMariaDB enhancement.\n\nNote that although the preferred syntax indicates that the attributes are\nexclusive, more than one attribute can be specified.\n\nUntil MariaDB 10.2.7 (MDEV-8659), any combination of the attributes could be\nused in any order, with duplicates. In this case:\n\n* the presence of ZEROFILL makes the column UNSIGNED ZEROFILL.\n* the presence of UNSIGNED makes the column UNSIGNED.\n\nFrom MariaDB 10.2.8, only the following combinations are supported:\n\n* SIGNED\n* UNSIGNED\n* ZEROFILL\n* UNSIGNED ZEROFILL\n* ZEROFILL UNSIGNED\n\nThe latter two should be replaced with simply ZEROFILL, but are still accepted\nby the parser.\n\nExamples\n--------\n\nCREATE TABLE zf (\n i1 TINYINT SIGNED,\n i2 TINYINT UNSIGNED,\n i3 TINYINT ZEROFILL\n);\n\nINSERT INTO zf VALUES (2,2,2);\n\nSELECT * FROM zf;\n+------+------+------+\n| i1 | i2 | i3 |\n+------+------+------+\n| 2 | 2 | 002 |\n+------+------+------+\n\nRange\n-----\n\nWhen attempting to add a value that is out of the valid range for the numeric\ntype, MariaDB will react depending on the strict SQL_MODE setting.\n\nIf strict_mode has been set (the default from MariaDB 10.2.4), MariaDB will\nreturn an error.\n\nIf strict_mode has not been set (the default until MariaDB 10.2.3), MariaDB\nwill adjust the number to fit in the field, returning a warning.\n\nExamples\n--------\n\nWith strict_mode set:\n\nSHOW VARIABLES LIKE \'sql_mode\';\n+---------------+--------------------------------------------------------------\n----------------------------+\n| Variable_name | Value \n |\n+---------------+--------------------------------------------------------------\n----------------------------+\n| sql_mode |\nSTRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SU\nSTITUTION |\n+---------------+--------------------------------------------------------------\n----------------------------+\n\nCREATE TABLE ranges (i1 TINYINT, i2 SMALLINT, i3 TINYINT UNSIGNED);\n\nINSERT INTO ranges VALUES (257,257,257);\nERROR 1264 (22003): Out of range value for column \'i1\' at row 1\n\nSELECT * FROM ranges;\nEmpty set (0.10 sec)\n\nWith strict_mode unset:\n\nSHOW VARIABLES LIKE \'sql_mode%\';\n+---------------+-------+\n| Variable_name | Value |\n+---------------+-------+\n| sql_mode | |\n+---------------+-------+\n\nCREATE TABLE ranges (i1 TINYINT, i2 SMALLINT, i3 TINYINT UNSIGNED);\n\nINSERT INTO ranges VALUES (257,257,257);\nQuery OK, 1 row affected, 2 warnings (0.00 sec)\n\nSHOW WARNINGS;\n+---------+------+---------------------------------------------+\n| Level | Code | Message |\n+---------+------+---------------------------------------------+\n| Warning | 1264 | Out of range value for column \'i1\' at row 1 |\n| Warning | 1264 | Out of range value for column \'i3\' at row 1 |\n+---------+------+---------------------------------------------+\n2 rows in set (0.00 sec)\n\nSELECT * FROM ranges;\n+------+------+------+\n| i1 | i2 | i3 |\n+------+------+------+\n| 127 | 257 | 255 |\n+------+------+------+\n\nAuto_increment\n--------------\n\nThe AUTO_INCREMENT attribute can be used to generate a unique identity for new\nrows. For more details, see auto_increment.\n\nURL: https://mariadb.com/kb/en/numeric-data-type-overview/','','https://mariadb.com/kb/en/numeric-data-type-overview/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (260,23,'TINYINT','Syntax\n------\n\nTINYINT[(M)] [SIGNED | UNSIGNED | ZEROFILL]\n\nDescription\n-----------\n\nA very small integer. The signed range is -128 to 127. The unsigned range is 0\nto 255. For details on the attributes, see Numeric Data Type Overview.\n\nINT1 is a synonym for TINYINT. BOOL and BOOLEAN are synonyms for TINYINT(1).\n\nExamples\n--------\n\nCREATE TABLE tinyints (a TINYINT,b TINYINT UNSIGNED,c TINYINT ZEROFILL);\n\nWith strict_mode set, the default from MariaDB 10.2.4:\n\nINSERT INTO tinyints VALUES (-10,-10,-10);\nERROR 1264 (22003): Out of range value for column \'b\' at row 1\n\nINSERT INTO tinyints VALUES (-10,10,-10);\nERROR 1264 (22003): Out of range value for column \'c\' at row 1\n\nINSERT INTO tinyints VALUES (-10,10,10);\n\nSELECT * FROM tinyints;\n+------+------+------+\n| a | b | c |\n+------+------+------+\n| -10 | 10 | 010 |\n+------+------+------+\n\nINSERT INTO tinyints VALUES (128,128,128);\nERROR 1264 (22003): Out of range value for column \'a\' at row 1\n\nINSERT INTO tinyints VALUES (127,128,128);\n\nSELECT * FROM tinyints;\n+------+------+------+\n| a | b | c |\n+------+------+------+\n| -10 | 10 | 010 |\n| 127 | 128 | 128 |\n+------+------+------+\n\nWith strict_mode unset, the default until MariaDB 10.2.3:\n\nINSERT INTO tinyints VALUES (-10,-10,-10);\nQuery OK, 1 row affected, 2 warnings (0.08 sec)\nWarning (Code 1264): Out of range value for column \'b\' at row 1\nWarning (Code 1264): Out of range value for column \'c\' at row 1\n\nINSERT INTO tinyints VALUES (-10,10,-10);\nQuery OK, 1 row affected, 1 warning (0.11 sec)\nWarning (Code 1264): Out of range value for column \'c\' at row 1\n\nINSERT INTO tinyints VALUES (-10,10,10);\n\nSELECT * FROM tinyints;\n+------+------+------+\n| a | b | c |\n+------+------+------+\n| -10 | 0 | 000 |\n| -10 | 10 | 000 |\n| -10 | 10 | 010 |\n+------+------+------+\n\nINSERT INTO tinyints VALUES (128,128,128);\nQuery OK, 1 row affected, 1 warning (0.19 sec)\nWarning (Code 1264): Out of range value for column \'a\' at row 1\n\nINSERT INTO tinyints VALUES (127,128,128);\n\nSELECT * FROM tinyints;\n+------+------+------+\n| a | b | c |\n+------+------+------+\n| -10 | 0 | 000 |\n| -10 | 10 | 000 |\n| -10 | 10 | 010 |\n| 127 | 128 | 128 |\n| 127 | 128 | 128 |\n+------+------+------+\n\nURL: https://mariadb.com/kb/en/tinyint/','','https://mariadb.com/kb/en/tinyint/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (261,23,'BOOLEAN','Syntax\n------\n\nBOOL, BOOLEAN\n\nDescription\n-----------\n\nThese types are synonyms for TINYINT(1). A value of zero is considered false.\nNon-zero values are considered true.\n\nHowever, the values TRUE and FALSE are merely aliases for 1 and 0. See Boolean\nLiterals, as well as the IS operator for testing values against a boolean.\n\nExamples\n--------\n\nCREATE TABLE boo (i BOOLEAN);\n\nDESC boo;\n+-------+------------+------+-----+---------+-------+\n| Field | Type | Null | Key | Default | Extra |\n+-------+------------+------+-----+---------+-------+\n| i | tinyint(1) | YES | | NULL | |\n+-------+------------+------+-----+---------+-------+\n\nSELECT IF(0, \'true\', \'false\');\n+------------------------+\n| IF(0, \'true\', \'false\') |\n+------------------------+\n| false |\n+------------------------+\n\nSELECT IF(1, \'true\', \'false\');\n+------------------------+\n| IF(1, \'true\', \'false\') |\n+------------------------+\n| true |\n+------------------------+\n\nSELECT IF(2, \'true\', \'false\');\n+------------------------+\n| IF(2, \'true\', \'false\') |\n+------------------------+\n| true |\n+------------------------+\n\nTRUE and FALSE as aliases for 1 and 0:\n\nSELECT IF(0 = FALSE, \'true\', \'false\');\n\n+--------------------------------+\n| IF(0 = FALSE, \'true\', \'false\') |\n+--------------------------------+\n| true |\n+--------------------------------+\n\nSELECT IF(1 = TRUE, \'true\', \'false\');\n+-------------------------------+\n| IF(1 = TRUE, \'true\', \'false\') |\n+-------------------------------+\n| true |\n+-------------------------------+\n\nSELECT IF(2 = TRUE, \'true\', \'false\');\n+-------------------------------+\n| IF(2 = TRUE, \'true\', \'false\') |\n+-------------------------------+\n| false |\n+-------------------------------+\n\nSELECT IF(2 = FALSE, \'true\', \'false\');\n+--------------------------------+\n| IF(2 = FALSE, \'true\', \'false\') |\n+--------------------------------+\n| false |\n+--------------------------------+\n\nThe last two statements display the results shown because 2 is equal to\nneither 1 nor 0.\n\nURL: https://mariadb.com/kb/en/boolean/','','https://mariadb.com/kb/en/boolean/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (262,23,'SMALLINT','Syntax\n------\n\nSMALLINT[(M)] [SIGNED | UNSIGNED | ZEROFILL]\n\nDescription\n-----------\n\nA small integer. The signed range is -32768 to 32767. The unsigned range is 0\nto 65535.\n\nIf a column has been set to ZEROFILL, all values will be prepended by zeros so\nthat the SMALLINT value contains a number of M digits.\n\nNote: If the ZEROFILL attribute has been specified, the column will\nautomatically become UNSIGNED.\n\nINT2 is a synonym for SMALLINT.\n\nFor more details on the attributes, see Numeric Data Type Overview.\n\nExamples\n--------\n\nCREATE TABLE smallints (a SMALLINT,b SMALLINT UNSIGNED,c SMALLINT ZEROFILL);\n\nWith strict_mode set, the default from MariaDB 10.2.4:\n\nINSERT INTO smallints VALUES (-10,-10,-10);\nERROR 1264 (22003): Out of range value for column \'b\' at row 1\n\nINSERT INTO smallints VALUES (-10,10,-10);\nERROR 1264 (22003): Out of range value for column \'c\' at row 1\n\nINSERT INTO smallints VALUES (-10,10,10);\n\nINSERT INTO smallints VALUES (32768,32768,32768);\nERROR 1264 (22003): Out of range value for column \'a\' at row 1\n\nINSERT INTO smallints VALUES (32767,32768,32768);\n\nSELECT * FROM smallints;\n+-------+-------+-------+\n| a | b | c |\n+-------+-------+-------+\n| -10 | 10 | 00010 |\n| 32767 | 32768 | 32768 |\n+-------+-------+-------+\n\nWith strict_mode unset, the default until MariaDB 10.2.3:\n\nINSERT INTO smallints VALUES (-10,-10,-10);\nQuery OK, 1 row affected, 2 warnings (0.09 sec)\nWarning (Code 1264): Out of range value for column \'b\' at row 1\nWarning (Code 1264): Out of range value for column \'c\' at row 1\n\nINSERT INTO smallints VALUES (-10,10,-10);\nQuery OK, 1 row affected, 1 warning (0.08 sec)\nWarning (Code 1264): Out of range value for column \'c\' at row 1\n\nINSERT INTO smallints VALUES (-10,10,10);\n\nINSERT INTO smallints VALUES (32768,32768,32768);\nQuery OK, 1 row affected, 1 warning (0.04 sec)\nWarning (Code 1264): Out of range value for column \'a\' at row 1\n\nINSERT INTO smallints VALUES (32767,32768,32768);\n\nSELECT * FROM smallints;\n+-------+-------+-------+\n| a | b | c |\n+-------+-------+-------+\n| -10 | 0 | 00000 |\n| -10 | 10 | 00000 |\n| -10 | 10 | 00010 |\n| 32767 | 32768 | 32768 |\n| 32767 | 32768 | 32768 |\n+-------+-------+-------+\n\nURL: https://mariadb.com/kb/en/smallint/','','https://mariadb.com/kb/en/smallint/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (263,23,'MEDIUMINT','Syntax\n------\n\nMEDIUMINT[(M)] [SIGNED | UNSIGNED | ZEROFILL]\n\nDescription\n-----------\n\nA medium-sized integer. The signed range is -8388608 to 8388607. The unsigned\nrange is 0 to 16777215.\n\nZEROFILL pads the integer with zeroes and assumes UNSIGNED (even if UNSIGNED\nis not specified).\n\nINT3 is a synonym for MEDIUMINT.\n\nFor details on the attributes, see Numeric Data Type Overview.\n\nExamples\n--------\n\nCREATE TABLE mediumints (a MEDIUMINT,b MEDIUMINT UNSIGNED,c MEDIUMINT\nZEROFILL);\n\nDESCRIBE mediumints;\n+-------+--------------------------------+------+-----+---------+-------+\n| Field | Type | Null | Key | Default | Extra |\n+-------+--------------------------------+------+-----+---------+-------+\n| a | mediumint(9) | YES | | NULL | |\n| b | mediumint(8) unsigned | YES | | NULL | |\n| c | mediumint(8) unsigned zerofill | YES | | NULL | |\n+-------+--------------------------------+------+-----+---------+-------+\n\nWith strict_mode set, the default from MariaDB 10.2.4:\n\nINSERT INTO mediumints VALUES (-10,-10,-10);\nERROR 1264 (22003): Out of range value for column \'b\' at row 1\n\nINSERT INTO mediumints VALUES (-10,10,-10);\nERROR 1264 (22003): Out of range value for column \'c\' at row 1\n\nINSERT INTO mediumints VALUES (-10,10,10);\n\nINSERT INTO mediumints VALUES (8388608,8388608,8388608);\nERROR 1264 (22003): Out of range value for column \'a\' at row 1\n\nINSERT INTO mediumints VALUES (8388607,8388608,8388608);\n\nSELECT * FROM mediumints;\n+---------+---------+----------+\n| a | b | c |\n+---------+---------+----------+\n| -10 | 10 | 00000010 |\n| 8388607 | 8388608 | 08388608 |\n+---------+---------+----------+\n\nWith strict_mode unset, the default until MariaDB 10.2.3:\n\nINSERT INTO mediumints VALUES (-10,-10,-10);\nQuery OK, 1 row affected, 2 warnings (0.05 sec)\nWarning (Code 1264): Out of range value for column \'b\' at row 1\nWarning (Code 1264): Out of range value for column \'c\' at row 1\n\nINSERT INTO mediumints VALUES (-10,10,-10);\nQuery OK, 1 row affected, 1 warning (0.08 sec)\nWarning (Code 1264): Out of range value for column \'c\' at row 1\n\nINSERT INTO mediumints VALUES (-10,10,10);\n\nINSERT INTO mediumints VALUES (8388608,8388608,8388608);\nQuery OK, 1 row affected, 1 warning (0.05 sec)\nWarning (Code 1264): Out of range value for column \'a\' at row 1\n\nINSERT INTO mediumints VALUES (8388607,8388608,8388608);\n\nSELECT * FROM mediumints;\n+---------+---------+----------+\n| a | b | c |\n+---------+---------+----------+\n| -10 | 0 | 00000000 |\n| -10 | 0 | 00000000 |\n| -10 | 10 | 00000000 |\n| -10 | 10 | 00000010 |\n| 8388607 | 8388608 | 08388608 |\n| 8388607 | 8388608 | 08388608 |\n+---------+---------+----------+\n\nURL: https://mariadb.com/kb/en/mediumint/','','https://mariadb.com/kb/en/mediumint/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (264,23,'INT','Syntax\n------\n\nINT[(M)] [SIGNED | UNSIGNED | ZEROFILL]\nINTEGER[(M)] [SIGNED | UNSIGNED | ZEROFILL]\n\nDescription\n-----------\n\nA normal-size integer. When marked UNSIGNED, it ranges from 0 to 4294967295,\notherwise its range is -2147483648 to 2147483647 (SIGNED is the default). If a\ncolumn has been set to ZEROFILL, all values will be prepended by zeros so that\nthe INT value contains a number of M digits. INTEGER is a synonym for INT.\n\nNote: If the ZEROFILL attribute has been specified, the column will\nautomatically become UNSIGNED.\n\nINT4 is a synonym for INT.\n\nFor details on the attributes, see Numeric Data Type Overview.\n\nExamples\n--------\n\nCREATE TABLE ints (a INT,b INT UNSIGNED,c INT ZEROFILL);\n\nWith strict_mode set, the default from MariaDB 10.2.4:\n\nINSERT INTO ints VALUES (-10,-10,-10);\nERROR 1264 (22003): Out of range value for column \'b\' at row 1\n\nINSERT INTO ints VALUES (-10,10,-10);\nERROR 1264 (22003): Out of range value for column \'c\' at row 1\n\nINSERT INTO ints VALUES (-10,10,10);\n\nINSERT INTO ints VALUES (2147483648,2147483648,2147483648);\nERROR 1264 (22003): Out of range value for column \'a\' at row 1\n\nINSERT INTO ints VALUES (2147483647,2147483648,2147483648);\n\nSELECT * FROM ints;\n+------------+------------+------------+\n| a | b | c |\n+------------+------------+------------+\n| -10 | 10 | 0000000010 |\n| 2147483647 | 2147483648 | 2147483648 |\n+------------+------------+------------+\n\nWith strict_mode unset, the default until MariaDB 10.2.3:\n\nINSERT INTO ints VALUES (-10,-10,-10);\nQuery OK, 1 row affected, 2 warnings (0.10 sec)\nWarning (Code 1264): Out of range value for column \'b\' at row 1\nWarning (Code 1264): Out of range value for column \'c\' at row 1\n\nINSERT INTO ints VALUES (-10,10,-10);\nQuery OK, 1 row affected, 1 warning (0.08 sec)\nWarning (Code 1264): Out of range value for column \'c\' at row 1\n\nINSERT INTO ints VALUES (-10,10,10);\n\nINSERT INTO ints VALUES (2147483648,2147483648,2147483648);\nQuery OK, 1 row affected, 1 warning (0.07 sec)\nWarning (Code 1264): Out of range value for column \'a\' at row 1\n\nINSERT INTO ints VALUES (2147483647,2147483648,2147483648);\n\nSELECT * FROM ints;\n+------------+------------+------------+\n| a | b | c |\n+------------+------------+------------+\n| -10 | 0 | 0000000000 |\n| -10 | 10 | 0000000000 |\n| -10 | 10 | 0000000010 |\n| 2147483647 | 2147483648 | 2147483648 |\n| 2147483647 | 2147483648 | 2147483648 |\n+------------+------------+------------+\n\nURL: https://mariadb.com/kb/en/int/','','https://mariadb.com/kb/en/int/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (265,23,'BIGINT','Syntax\n------\n\nBIGINT[(M)] [SIGNED | UNSIGNED | ZEROFILL]\n\nDescription\n-----------\n\nA large integer. The signed range is -9223372036854775808 to\n9223372036854775807. The unsigned range is 0 to 18446744073709551615.\n\nIf a column has been set to ZEROFILL, all values will be prepended by zeros so\nthat the BIGINT value contains a number of M digits.\n\nNote: If the ZEROFILL attribute has been specified, the column will\nautomatically become UNSIGNED.\n\nFor more details on the attributes, see Numeric Data Type Overview.\n\nSERIAL is an alias for:\n\nBIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE\n\nINT8 is a synonym for BIGINT.\n\nExamples\n--------\n\nCREATE TABLE bigints (a BIGINT,b BIGINT UNSIGNED,c BIGINT ZEROFILL);\n\nWith strict_mode set, the default from MariaDB 10.2.4:\n\nINSERT INTO bigints VALUES (-10,-10,-10);\nERROR 1264 (22003): Out of range value for column \'b\' at row 1\n\nINSERT INTO bigints VALUES (-10,10,-10);\nERROR 1264 (22003): Out of range value for column \'c\' at row 1\n\nINSERT INTO bigints VALUES (-10,10,10);\n\nINSERT INTO bigints VALUES\n(9223372036854775808,9223372036854775808,9223372036854775808);\nERROR 1264 (22003): Out of range value for column \'a\' at row 1\n\nINSERT INTO bigints VALUES\n(9223372036854775807,9223372036854775808,9223372036854775808);\n\nSELECT * FROM bigints;\n+---------------------+---------------------+----------------------+\n| a | b | c |\n+---------------------+---------------------+----------------------+\n| -10 | 10 | 00000000000000000010 |\n| 9223372036854775807 | 9223372036854775808 | 09223372036854775808 |\n+---------------------+---------------------+----------------------+\n\nWith strict_mode unset, the default until MariaDB 10.2.3:\n\nINSERT INTO bigints VALUES (-10,-10,-10);\nQuery OK, 1 row affected, 2 warnings (0.08 sec)\nWarning (Code 1264): Out of range value for column \'b\' at row 1\nWarning (Code 1264): Out of range value for column \'c\' at row 1\n\nINSERT INTO bigints VALUES (-10,10,-10);\nQuery OK, 1 row affected, 1 warning (0.08 sec)\nWarning (Code 1264): Out of range value for column \'c\' at row 1\n\nINSERT INTO bigints VALUES (-10,10,10);\n\nINSERT INTO bigints VALUES\n(9223372036854775808,9223372036854775808,9223372036854775808);\nQuery OK, 1 row affected, 1 warning (0.07 sec)\nWarning (Code 1264): Out of range value for column \'a\' at row 1\n\nINSERT INTO bigints VALUES\n(9223372036854775807,9223372036854775808,9223372036854775808);\n\nSELECT * FROM bigints;\n+---------------------+---------------------+----------------------+\n| a | b | c |\n+---------------------+---------------------+----------------------+\n| -10 | 0 | 00000000000000000000 |\n| -10 | 10 | 00000000000000000000 |\n| -10 | 10 | 00000000000000000010 |\n| 9223372036854775807 | 9223372036854775808 | 09223372036854775808 |\n| 9223372036854775807 | 9223372036854775808 | 09223372036854775808 |\n+---------------------+---------------------+----------------------+\n\nURL: https://mariadb.com/kb/en/bigint/','','https://mariadb.com/kb/en/bigint/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (266,23,'DECIMAL','Syntax\n------\n\nDECIMAL[(M[,D])] [SIGNED | UNSIGNED | ZEROFILL]\n\nDescription\n-----------\n\nA packed \"exact\" fixed-point number. M is the total number of digits (the\nprecision) and D is the number of digits after the decimal point (the scale).\n\n* The decimal point and (for negative numbers) the \"-\" sign are not\ncounted in M. \n* If D is 0, values have no decimal point or fractional\npart and on INSERT the value will be rounded to the nearest DECIMAL. \n* The maximum number of digits (M) for DECIMAL is 65. \n* The maximum number of supported decimals (D) is 30 before MariadB 10.2.1 and\n38 afterwards. \n* If D is omitted, the default is 0. If M is omitted, the default is 10.\n\nUNSIGNED, if specified, disallows negative values.\n\nZEROFILL, if specified, pads the number with zeros, up to the total number of\ndigits specified by M.\n\nAll basic calculations (+, -, *, /) with DECIMAL columns are done with a\nprecision of 65 digits.\n\nFor more details on the attributes, see Numeric Data Type Overview.\n\nDEC, NUMERIC and FIXED are synonyms, as well as NUMBER in Oracle mode from\nMariaDB 10.3.\n\nExamples\n--------\n\nCREATE TABLE t1 (d DECIMAL UNSIGNED ZEROFILL);\n\nINSERT INTO t1 VALUES (1),(2),(3),(4.0),(5.2),(5.7);\nQuery OK, 6 rows affected, 2 warnings (0.16 sec)\nRecords: 6 Duplicates: 0 Warnings: 2\n\nNote (Code 1265): Data truncated for column \'d\' at row 5\nNote (Code 1265): Data truncated for column \'d\' at row 6\n\nSELECT * FROM t1;\n+------------+\n| d |\n+------------+\n| 0000000001 |\n| 0000000002 |\n| 0000000003 |\n| 0000000004 |\n| 0000000005 |\n| 0000000006 |\n+------------+\n\nWith strict_mode set, the default from MariaDB 10.2.4:\n\nINSERT INTO t1 VALUES (-7);\nERROR 1264 (22003): Out of range value for column \'d\' at row 1\n\nWith strict_mode unset, the default until MariaDB 10.2.3:\n\nINSERT INTO t1 VALUES (-7);\nQuery OK, 1 row affected, 1 warning (0.02 sec)\nWarning (Code 1264): Out of range value for column \'d\' at row 1\n\nSELECT * FROM t1;\n+------------+\n| d |\n+------------+\n| 0000000001 |\n| 0000000002 |\n| 0000000003 |\n| 0000000004 |\n| 0000000005 |\n| 0000000006 |\n| 0000000000 |\n+------------+\n\nURL: https://mariadb.com/kb/en/decimal/','','https://mariadb.com/kb/en/decimal/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (267,23,'FLOAT','Syntax\n------\n\nFLOAT[(M,D)] [SIGNED | UNSIGNED | ZEROFILL]\n\nDescription\n-----------\n\nA small (single-precision) floating-point number (see DOUBLE for a\nregular-size floating point number). Allowable values are:\n\n* -3.402823466E+38 to -1.175494351E-38\n* 0\n* 1.175494351E-38 to 3.402823466E+38.\n\nThese are the theoretical limits, based on the IEEE standard. The actual range\nmight be slightly smaller depending on your hardware or operating system.\n\nM is the total number of digits and D is the number of digits following the\ndecimal point. If M and D are omitted, values are stored to the limits allowed\nby the hardware. A single-precision floating-point number is accurate to\napproximately 7 decimal places.\n\nUNSIGNED, if specified, disallows negative values.\n\nUsing FLOAT might give you some unexpected problems because all calculations\nin MariaDB are done with double precision. See Floating Point Accuracy.\n\nFor more details on the attributes, see Numeric Data Type Overview.\n\nURL: https://mariadb.com/kb/en/float/','','https://mariadb.com/kb/en/float/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (268,23,'DOUBLE','Syntax\n------\n\nDOUBLE[(M,D)] [SIGNED | UNSIGNED | ZEROFILL]\nDOUBLE PRECISION[(M,D)] [SIGNED | UNSIGNED | ZEROFILL]\nREAL[(M,D)] [SIGNED | UNSIGNED | ZEROFILL]\n\nDescription\n-----------\n\nA normal-size (double-precision) floating-point number (see FLOAT for a\nsingle-precision floating-point number).\n\nAllowable values are:\n\n* -1.7976931348623157E+308 to -2.2250738585072014E-308\n* 0\n* 2.2250738585072014E-308 to 1.7976931348623157E+308\n\nThese are the theoretical limits, based on the IEEE standard. The actual range\nmight be slightly smaller depending on your hardware or operating system.\n\nM is the total number of digits and D is the number of digits following the\ndecimal point. If M and D are omitted, values are stored to the limits allowed\nby the hardware. A double-precision floating-point number is accurate to\napproximately 15 decimal places.\n\nUNSIGNED, if specified, disallows negative values.\n\nZEROFILL, if specified, pads the number with zeros, up to the total number of\ndigits specified by M.\n\nREAL and DOUBLE PRECISION are synonyms, unless the REAL_AS_FLOAT SQL mode is\nenabled, in which case REAL is a synonym for FLOAT rather than DOUBLE.\n\nSee Floating Point Accuracy for issues when using floating-point numbers.\n\nFor more details on the attributes, see Numeric Data Type Overview.\n\nExamples\n--------\n\nCREATE TABLE t1 (d DOUBLE(5,0) zerofill);\n\nINSERT INTO t1 VALUES (1),(2),(3),(4);\n\nSELECT * FROM t1;\n+-------+\n| d |\n+-------+\n| 00001 |\n| 00002 |\n| 00003 |\n| 00004 |\n+-------+\n\nURL: https://mariadb.com/kb/en/double/','','https://mariadb.com/kb/en/double/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (269,23,'BIT','Syntax\n------\n\nBIT[(M)]\n\nDescription\n-----------\n\nA bit-field type. M indicates the number of bits per value, from 1 to 64. The\ndefault is 1 if M is omitted.\n\nBit values can be inserted with b\'value\' notation, where value is the bit\nvalue in 0\'s and 1\'s.\n\nBit fields are automatically zero-padded from the left to the full length of\nthe bit, so for example in a BIT(4) field, \'10\' is equivalent to \'0010\'.\n\nBits are returned as binary, so to display them, either add 0, or use a\nfunction such as HEX, OCT or BIN to convert them.\n\nExamples\n--------\n\nCREATE TABLE b ( b1 BIT(8) );\n\nWith strict_mode set, the default from MariaDB 10.2.4:\n\nINSERT INTO b VALUES (b\'11111111\');\n\nINSERT INTO b VALUES (b\'01010101\');\n\nINSERT INTO b VALUES (b\'1111111111111\');\nERROR 1406 (22001): Data too long for column \'b1\' at row 1\n\nSELECT b1+0, HEX(b1), OCT(b1), BIN(b1) FROM b;\n+------+---------+---------+----------+\n| b1+0 | HEX(b1) | OCT(b1) | BIN(b1) |\n+------+---------+---------+----------+\n| 255 | FF | 377 | 11111111 |\n| 85 | 55 | 125 | 1010101 |\n+------+---------+---------+----------+\n\nWith strict_mode unset, the default until MariaDB 10.2.3:\n\nINSERT INTO b VALUES (b\'11111111\'),(b\'01010101\'),(b\'1111111111111\');\nQuery OK, 3 rows affected, 1 warning (0.10 sec)\nRecords: 3 Duplicates: 0 Warnings: 1\n\nSHOW WARNINGS;\n+---------+------+---------------------------------------------+\n| Level | Code | Message |\n+---------+------+---------------------------------------------+\n| Warning | 1264 | Out of range value for column \'b1\' at row 3 |\n+---------+------+---------------------------------------------+\n\nSELECT b1+0, HEX(b1), OCT(b1), BIN(b1) FROM b;\n+------+---------+---------+----------+\n| b1+0 | HEX(b1) | OCT(b1) | BIN(b1) |\n+------+---------+---------+----------+\n| 255 | FF | 377 | 11111111 |\n| 85 | 55 | 125 | 1010101 |\n| 255 | FF | 377 | 11111111 |\n+------+---------+---------+----------+\n\nURL: https://mariadb.com/kb/en/bit/','','https://mariadb.com/kb/en/bit/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (270,23,'Floating-point Accuracy','Due to their nature, not all floating-point numbers can be stored with exact\nprecision. Hardware architecture, the CPU or even the compiler version and\noptimization level may affect the precision.\n\nIf you are comparing DOUBLEs or FLOATs with numeric decimals, it is not safe\nto use the equality operator.\n\nSometimes, changing a floating-point number from single-precision (FLOAT) to\ndouble-precision (DOUBLE) will fix the problem.\n\nExample\n-------\n\nf1, f2 and f3 have seemingly identical values across each row, but due to\nfloating point accuracy, the results may be unexpected.\n\nCREATE TABLE fpn (id INT, f1 FLOAT, f2 DOUBLE, f3 DECIMAL (10,3));\nINSERT INTO fpn VALUES (1,2,2,2),(2,0.1,0.1,0.1);\n\nSELECT * FROM fpn WHERE f1*f1 = f2*f2;\n+------+------+------+-------+\n| id | f1 | f2 | f3 |\n+------+------+------+-------+\n| 1 | 2 | 2 | 2.000 |\n+------+------+------+-------+\n\nThe reason why only one instead of two rows was returned becomes clear when we\nsee how the floating point squares were evaluated.\n\nSELECT f1*f1, f2*f2, f3*f3 FROM fpn;\n+----------------------+----------------------+----------+\n| f1*f1 | f2*f2 | f3*f3 |\n+----------------------+----------------------+----------+\n| 4 | 4 | 4.000000 |\n| 0.010000000298023226 | 0.010000000000000002 | 0.010000 |\n+----------------------+----------------------+----------+\n\nURL: https://mariadb.com/kb/en/floating-point-accuracy/','','https://mariadb.com/kb/en/floating-point-accuracy/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (271,23,'BINARY','This page describes the BINARY data type. For details about the operator, see\nBinary Operator.\n\nSyntax\n------\n\nBINARY(M)\n\nDescription\n-----------\n\nThe BINARY type is similar to the CHAR type, but stores binary byte strings\nrather than non-binary character strings. M represents the column length in\nbytes.\n\nIt contains no character set, and comparison and sorting are based on the\nnumeric value of the bytes.\n\nIf the maximum length is exceeded, and SQL strict mode is not enabled , the\nextra characters will be dropped with a warning. If strict mode is enabled, an\nerror will occur.\n\nBINARY values are right-padded with 0x00 (the zero byte) to the specified\nlength when inserted. The padding is not removed on select, so this needs to\nbe taken into account when sorting and comparing, where all bytes are\nsignificant. The zero byte, 0x00 is less than a space for comparison purposes.\n\nExamples\n--------\n\nInserting too many characters, first with strict mode off, then with it on:\n\nCREATE TABLE bins (a BINARY(10));\n\nINSERT INTO bins VALUES(\'12345678901\');\nQuery OK, 1 row affected, 1 warning (0.04 sec)\n\nSELECT * FROM bins;\n+------------+\n| a |\n+------------+\n| 1234567890 |\n+------------+\n\nSET sql_mode=\'STRICT_ALL_TABLES\';\n\nINSERT INTO bins VALUES(\'12345678901\');\nERROR 1406 (22001): Data too long for column \'a\' at row 1\n\nSorting is performed with the byte value:\n\nTRUNCATE bins;\n\nINSERT INTO bins VALUES(\'A\'),(\'B\'),(\'a\'),(\'b\');\n\nSELECT * FROM bins ORDER BY a;\n+------+\n| a |\n+------+\n| A |\n| B |\n| a |\n| b |\n+------+\n\nUsing CAST to sort as a CHAR instead:\n\nSELECT * FROM bins ORDER BY CAST(a AS CHAR);\n+------+\n| a |\n+------+\n| a |\n| A |\n| b |\n| B |\n+------+\n\nThe field is a BINARY(10), so padding of two \'\\0\'s are inserted, causing\ncomparisons that don\'t take this into account to fail:\n\nTRUNCATE bins;\n\nINSERT INTO bins VALUES(\'12345678\');\n\nSELECT a = \'12345678\', a = \'12345678\\0\\0\' from bins;\n+----------------+--------------------+\n| a = \'12345678\' | a = \'12345678\\0\\0\' |\n+----------------+--------------------+\n| 0 | 1 |\n+----------------+--------------------+\n\nURL: https://mariadb.com/kb/en/binary/','','https://mariadb.com/kb/en/binary/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (272,23,'BLOB','Syntax\n------\n\nBLOB[(M)]\n\nDescription\n-----------\n\nA BLOB column with a maximum length of 65,535 (216 - 1) bytes. Each BLOB value\nis stored using a two-byte length prefix that indicates the number of bytes in\nthe value.\n\nAn optional length M can be given for this type. If this is done, MariaDB\ncreates the column as the smallest BLOB type large enough to hold values M\nbytes long.\n\nBLOBS can also be used to store dynamic columns.\n\nBLOB and TEXT columns can both be assigned a DEFAULT value.\n\nIndexing\n--------\n\nMariaDB starting with 10.4\n--------------------------\nFrom MariaDB 10.4, it is possible to set a unique index on a column that uses\nthe BLOB data type. In previous releases this was not possible, as the index\nwould only guarantee the uniqueness of a fixed number of characters.\n\nOracle Mode\n-----------\n\nIn Oracle mode from MariaDB 10.3, BLOB is a synonym for LONGBLOB.\n\nURL: https://mariadb.com/kb/en/blob/','','https://mariadb.com/kb/en/blob/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (273,23,'BLOB and TEXT Data Types','Description\n-----------\n\nA BLOB is a binary large object that can hold a variable amount of data. The\nfour BLOB types are\n\n* TINYBLOB,\n* BLOB, \n* MEDIUMBLOB, and\n* LONGBLOB.\n\nThese differ only in the maximum length of the values they can hold.\n\nThe TEXT types are\n\n* TINYTEXT,\n* TEXT,\n* MEDIUMTEXT, and\n* LONGTEXT.\n* JSON (alias for LONGTEXT)\n\nThese correspond to the four BLOB types and have the same maximum lengths and\nstorage requirements.\n\nBLOB and TEXT columns can have a DEFAULT value.\n\nMariaDB starting with 10.4.3\n----------------------------\nFrom MariaDB 10.4, it is possible to set a unique index on columns that use\nthe BLOB or TEXT data types.\n\nURL: https://mariadb.com/kb/en/blob-and-text-data-types/','','https://mariadb.com/kb/en/blob-and-text-data-types/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (274,23,'CHAR','This article covers the CHAR data type. See CHAR Function for the function.\n\nSyntax\n------\n\n[NATIONAL] CHAR[(M)] [CHARACTER SET charset_name] [COLLATE collation_name]\n\nDescription\n-----------\n\nA fixed-length string that is always right-padded with spaces to the specified\nlength when stored. M represents the column length in characters. The range of\nM is 0 to 255. If M is omitted, the length is 1.\n\nCHAR(0) columns can contain 2 values: an empty string or NULL. Such columns\ncannot be part of an index. The CONNECT storage engine does not support\nCHAR(0).\n\nNote: Trailing spaces are removed when CHAR values are retrieved unless the\nPAD_CHAR_TO_FULL_LENGTH SQL mode is enabled.\n\nBefore MariaDB 10.2, all collations were of type PADSPACE, meaning that CHAR\n(as well as VARCHAR and TEXT) values are compared without regard for trailing\nspaces. This does not apply to the LIKE pattern-matching operator, which takes\ninto account trailing spaces.\n\nIf a unique index consists of a column where trailing pad characters are\nstripped or ignored, inserts into that column where values differ only by the\nnumber of trailing pad characters will result in a duplicate-key error.\n\nExamples\n--------\n\nTrailing spaces:\n\nCREATE TABLE strtest (c CHAR(10));\nINSERT INTO strtest VALUES(\'Maria \');\n\nSELECT c=\'Maria\',c=\'Maria \' FROM strtest;\n+-----------+--------------+\n| c=\'Maria\' | c=\'Maria \' |\n+-----------+--------------+\n| 1 | 1 |\n+-----------+--------------+\n\nSELECT c LIKE \'Maria\',c LIKE \'Maria \' FROM strtest;\n+----------------+-------------------+\n| c LIKE \'Maria\' | c LIKE \'Maria \' |\n+----------------+-------------------+\n| 1 | 0 |\n+----------------+-------------------+\n\nNO PAD Collations\n-----------------\n\nNO PAD collations regard trailing spaces as normal characters. You can get a\nlist of all NO PAD collations by querying the Information Schema Collations\ntable, for example:\n\nSELECT collation_name FROM information_schema.collations \n WHERE collation_name LIKE \"%nopad%\";\n+------------------------------+\n| collation_name |\n+------------------------------+\n| big5_chinese_nopad_ci |\n| big5_nopad_bin |\n...\n\nURL: https://mariadb.com/kb/en/char/','','https://mariadb.com/kb/en/char/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (275,23,'CHAR BYTE','Description\n-----------\n\nThe CHAR BYTE data type is an alias for the BINARY data type. This is a\ncompatibility feature.\n\nURL: https://mariadb.com/kb/en/char-byte/','','https://mariadb.com/kb/en/char-byte/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (276,23,'ENUM','Syntax\n------\n\nENUM(\'value1\',\'value2\',...) [CHARACTER SET charset_name] [COLLATE\ncollation_name]\n\nDescription\n-----------\n\nAn enumeration. A string object that can have only one value, chosen from the\nlist of values \'value1\', \'value2\', ..., NULL or the special \'\' error value. In\ntheory, an ENUM column can have a maximum of 65,535 distinct values; in\npractice, the real maximum depends on many factors. ENUM values are\nrepresented internally as integers.\n\nTrailing spaces are automatically stripped from ENUM values on table creation.\n\nENUMs require relatively little storage space compared to strings, either one\nor two bytes depending on the number of enumeration values.\n\nNULL and empty values\n---------------------\n\nAn ENUM can also contain NULL and empty values. If the ENUM column is declared\nto permit NULL values, NULL becomes a valid value, as well as the default\nvalue (see below). If strict SQL Mode is not enabled, and an invalid value is\ninserted into an ENUM, a special empty string, with an index value of zero\n(see Numeric index, below), is inserted, with a warning. This may be\nconfusing, because the empty string is also a possible value, and the only\ndifference if that is this case its index is not 0. Inserting will fail with\nan error if strict mode is active.\n\nIf a DEFAULT clause is missing, the default value will be:\n\n* NULL if the column is nullable;\n* otherwise, the first value in the enumeration.\n\nNumeric index\n-------------\n\nENUM values are indexed numerically in the order they are defined, and sorting\nwill be performed in this numeric order. We suggest not using ENUM to store\nnumerals, as there is little to no storage space benefit, and it is easy to\nconfuse the enum integer with the enum numeral value by leaving out the quotes.\n\nAn ENUM defined as ENUM(\'apple\',\'orange\',\'pear\') would have the following\nindex values:\n\n+--------------------------------------+--------------------------------------+\n| Index | Value |\n+--------------------------------------+--------------------------------------+\n| NULL | NULL |\n+--------------------------------------+--------------------------------------+\n| 0 | \'\' |\n+--------------------------------------+--------------------------------------+\n| 1 | \'apple\' |\n+--------------------------------------+--------------------------------------+\n| 2 | \'orange\' |\n+--------------------------------------+--------------------------------------+\n| 3 | \'pear\' |\n+--------------------------------------+--------------------------------------+\n\nExamples\n--------\n\nCREATE TABLE fruits (\n id INT NOT NULL auto_increment PRIMARY KEY,\n fruit ENUM(\'apple\',\'orange\',\'pear\'),\n bushels INT);\n\nDESCRIBE fruits;\n+---------+-------------------------------+------+-----+---------+-------------\n--+\n| Field | Type | Null | Key | Default | Extra \n |\n+---------+-------------------------------+------+-----+---------+-------------\n--+\n| id | int(11) | NO | PRI | NULL |\nauto_increment |\n| fruit | enum(\'apple\',\'orange\',\'pear\') | YES | | NULL | \n |\n| bushels | int(11) | YES | | NULL | \n |\n+---------+-------------------------------+------+-----+---------+-------------\n--+\n\nINSERT INTO fruits\n (fruit,bushels) VALUES\n (\'pear\',20),\n (\'apple\',100),\n (\'orange\',25);\n\nINSERT INTO fruits\n (fruit,bushels) VALUES\n (\'avocado\',10);\nERROR 1265 (01000): Data truncated for column \'fruit\' at row 1\n\nSELECT * FROM fruits;\n+----+--------+---------+\n| id | fruit | bushels |\n+----+--------+---------+\n| 1 | pear | 20 |\n| 2 | apple | 100 |\n| 3 | orange | 25 |\n+----+--------+---------+\n\nSelecting by numeric index:\n\nSELECT * FROM fruits WHERE fruit=2;\n+----+--------+---------+\n| id | fruit | bushels |\n+----+--------+---------+\n| 3 | orange | 25 |\n+----+--------+---------+\n\nSorting is according to the index value:\n\nCREATE TABLE enums (a ENUM(\'2\',\'1\'));\n\nINSERT INTO enums VALUES (\'1\'),(\'2\');\n\nSELECT * FROM enums ORDER BY a ASC;\n+------+\n| a |\n+------+\n| 2 |\n| 1 |\n+------+\n\nIt\'s easy to get confused between returning the enum integer with the stored\nvalue, so we don\'t suggest using ENUM to store numerals. The first example\nreturns the 1st indexed field (\'2\' has an index value of 1, as it\'s defined\nfirst), while the second example returns the string value \'1\'.\n\nSELECT * FROM enums WHERE a=1;\n+------+\n| a |\n+------+\n| 2 |\n+------+\n\nSELECT * FROM enums WHERE a=\'1\';\n+------+\n| a |\n+------+\n| 1 |\n+------+\n\nURL: https://mariadb.com/kb/en/enum/','','https://mariadb.com/kb/en/enum/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (277,23,'INET4','MariaDB starting with 10.10.0\n-----------------------------\nThe INET4 data type was added in MariaDB 10.10.0\n\nSyntax\n------\n\nINET4\n\nDescription\n-----------\n\nINET4 is a data type to store IPv4 addresses, as 4-byte binary strings.\n\nFrom MariaDB 11.3.0, casting from INET4 data types to INET6 is permitted,\nallowing for example comparisons between the two data types, and for INET 4\nvalues to be inserted into INET6 columns.\n\nExamples\n--------\n\nCREATE OR REPLACE TABLE t1 (a INET4);\n\nINSERT INTO t1 VALUES(\'0.0.0.0\'), (\'255.10.0.0\'), (\'255.255.255.255\');\n\nINSERT INTO t1 VALUES (0xa0000001);\nINSERT INTO t1 VALUES (0xf0000000);\nINSERT INTO t1 VALUES (0xff000001);\n\nSELECT HEX(a), a FROM t1 ORDER BY a;\n+----------+-----------------+\n| HEX(a) | a |\n+----------+-----------------+\n| 00000000 | 0.0.0.0 |\n| A0000001 | 160.0.0.1 |\n| F0000000 | 240.0.0.0 |\n| FF000001 | 255.0.0.1 |\n| FF0A0000 | 255.10.0.0 |\n| FFFFFFFF | 255.255.255.255 |\n+----------+-----------------+\n\nCasting from INET4 to INET6 is permitted, allowing direct inserts.\n\nBefore MariaDB 11.3:\n\nCREATE TABLE t1 (a INET6);\n\nINSERT INTO t1 VALUES(\'0.0.0.0\'), (\'255.10.0.0\'), (\'255.255.255.255\');\nERROR 1292 (22007): Incorrect inet6 value: \'0.0.0.0\' for column\n`test`.`t1`.`a` at row 1\n\nFrom MariaDB 11.3:\n\nCREATE TABLE t1 (a INET6);\n\nINSERT INTO t1 VALUES(\'0.0.0.0\'), (\'255.10.0.0\'), (\'255.255.255.255\');\nQuery OK, 3 rows affected (0.027 sec)\n\nComparisons are also permitted from MariaDB 11.3:\n\nCREATE OR REPLACE TABLE t1 (i4 INET4, i6 INET6); \nINSERT INTO t1 VALUES(\'10.10.10.10\',\'::ffff:192.168.0.1\');\n\nSELECT LEAST(i4,i6) FROM t1;\n+--------------------+\n| LEAST(i4,i6) |\n+--------------------+\n| ::ffff:10.10.10.10 |\n+--------------------+\n\nURL: https://mariadb.com/kb/en/inet4/','','https://mariadb.com/kb/en/inet4/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (278,23,'INET6','MariaDB starting with 10.5.0\n----------------------------\nThe INET6 data type was added in MariaDB 10.5.0\n\nSyntax\n------\n\nINET6\n\nDescription\n-----------\n\nThe INET6 data type is intended for storage of IPv6 addresses, as well as IPv4\naddresses assuming conventional mapping of IPv4 addresses into IPv6 addresses.\n\nBoth short and long IPv6 notation are permitted, according to RFC-5952.\n\n* Values are stored as a 16-byte fixed length binary string, with most\nsignificant byte first.\n* Storage engines see INET6 as BINARY(16).\n* Clients see INET6 as CHAR(39) and get text representation on retrieval.\n\nThe IPv4-compatible notation is considered as deprecated. It is supported for\ncompatibility with the INET6_ATON function, which also understands this\nformat. It\'s recommended to use the mapped format to store IPv4 addresses in\nINET6.\n\nWhen an IPv4 mapped (or compatible) value is stored in INET6, it still\noccupies 16 bytes:\n\nRetrieval\n---------\n\nOn retrieval, in the client-server text protocol, INET6 values are converted\nto the short text representation, according to RFC-5952, that is with all\nleading zeroes in each group removed and with consequent zero groups\ncompressed.\n\nBesides creating one\'s own stored function, there is no a way to retrieve an\nINET6 value using long text representation.\n\nCasting\n-------\n\n* CAST from a character string to INET6 understands addresses in short or long\ntext notation (including IPv4 mapped and compatible addresses). NULL is\nreturned if the format is not understood.\n* CAST from a binary string to INET6 requires a 16-byte string as an argument.\nNULL is returned if the argument length is not equal to 16.\n* CAST from other data types to INET6 first converts data to a character\nstring, then CAST from character string to INET6 is applied.\n* CAST from INET6 to CHAR returns short text address notation.\n* CAST from INET6 to BINARY returns its 16-byte binary string representation.\n* CAST from INET6 to data types other than CHAR (e.g. SIGNED, UNSIGNED, TIME,\netc) returns an error.\n\nComparisons\n-----------\n\nAn INET6 expression can be compared to:\n\n* another INET6 expression\n* a character string expression with a text (short or long) address\nrepresentation:\n* a 16-byte binary string expression:\n\nAttempting to compare INET6 to an expression of any other data type returns an\nerror.\n\nMixing INET6 Values for Result\n------------------------------\n\nAn INET6 expression can be mixed for result (i.e. UNION, CASE..THEN, COALESCE\netc) with:\n\n* another INET6 expression. The resulting data type is INET6.\n* a character string in text (short or long) address representation. The\nresult data type is INET6. The character string counterpart is automatically\nconverted to INET6. If the string format is not understood, it\'s converted\nwith a warning to either NULL or to \'::\', depending on the NULL-ability of the\nresult.\n* a 16-byte binary string. The resulting data type is INET6. The binary string\ncounterpart is automatically converted to INET6. If the length of the binary\nstring is not equal to 16, it\'s converted with a warning to NULL or to \'::\'\ndepending on the NULL-ability of the result.\n\nAttempts to mix INET6 for result with other data types will return an error.\n\nMixing INET6 with other data types for LEAST and GREATEST, when mixing for\ncomparison and mixing for result are involved at the same time, uses the same\nrules with mixing for result, described in the previous paragraphs.\n\nFunctions and Operators\n-----------------------\n\n* HEX() with an INET6 argument returns a hexadecimal representation of the\nunderlying 16-byte binary string\n* Arithmetic operators (+,-,*,/,MOD,DIV) are not supported for INET6. This may\nchange in the future.\n* The INET6_ATON function now understands INET6 values as an argument\n* The prototypes of the IS_IPV4_COMPAT and IS_IPV4_MAPPED functions have\nchanged from a BINARY(16) to a INET6,\n* When the argument for these two functions is not INET6, automatic implicit\nCAST to INET6 is applied. As a consequence, both functions now understand\narguments in both text representation and binary(16) representation. Before\nMariaDB 10.5.0, these functions understood only binary(16) representation.\n\nPrepared Statement Parameters\n-----------------------------\n\nINET6 understands both text and binary(16) address representation in prepared\nstatement parameters (PREPARE..EXECUTE and EXECUTE IMMEDIATE statements).\n\nMigration between BINARY(16) and INET6\n---------------------------------------\n\nBefore MariaDB 10.5.0, you may have used BINARY(16) as a storage for IPv6\ninternet addresses, in combination with INET6_ATON and INET6_NTOA to\nrespectively insert and retrieve data.\n\nFrom 10.5, you can ALTER BINARY(16) columns storing IPv6 addresses to INET6.\nAfter such an alter, there is no a need to use INET6_ATON() and INET6_NTOA().\nAddresses can be inserted and retrieved directly.\n\nIt is also possible to convert INET6 columns to BINARY(16) and continue using\nthe data in combination with INET6_NTOA() and INET6_ATON().\n\nExamples\n--------\n\nCREATE TABLE t1 (a INET6);\n\nInserting using short text address notation:\n\nINSERT INTO t1 VALUES (\'2001:db8::ff00:42:8329\');\n\nLong text address notation:\n\nINSERT INTO t1 VALUES (\'2001:0db8:0000:0000:0000:ff00:0042:8329\');\n\n16-byte binary string notation:\n\nINSERT INTO t1 VALUES (0x20010DB8000000000000FF0000428329);\nINSERT INTO t1 VALUES (UNHEX(\'20010DB8000000000000FF0000428329\'));\n\nIPv4 addresses, using IPv4-mapped and IPv4-compatible notations:\n\nINSERT INTO t1 VALUES (\'::ffff:192.0.2.128\'); -- mapped\nINSERT INTO t1 VALUES (\'::192.0.2.128\'); -- compatible\n\nSELECT * FROM t1;\n+------------------------+\n| a |\n+------------------------+\n| 2001:db8::ff00:42:8329 |\n| 2001:db8::ff00:42:8329 |\n| 2001:db8::ff00:42:8329 |\n| 2001:db8::ff00:42:8329 |\n| ::ffff:192.0.2.128 |\n| ::192.0.2.128 |\n+------------------------+\n\nIPv4 mapped (or compatible) values still occupy 16 bytes:\n\nCREATE OR REPLACE TABLE t1 (a INET6);\n\nINSERT INTO t1 VALUES (\'::ffff:192.0.2.128\');\n\nSELECT * FROM t1;\n+--------------------+\n| a |\n+--------------------+\n| ::ffff:192.0.2.128 |\n+--------------------+\n\nSELECT HEX(a) FROM t1;\n+----------------------------------+\n| HEX(a) |\n+----------------------------------+\n| 00000000000000000000FFFFC0000280 |\n+----------------------------------+\n\nCasting from INET6 to anything other than CHAR returns an error:\n\nSELECT CAST(a AS DECIMAL) FROM t1;\n\nERROR 4079 (HY000): Illegal parameter data type inet6 for operation\n\'decimal_typecast\'\n\nComparison Examples\n-------------------\n\nComparison with another INET6 expression:\n\nCREATE OR REPLACE TABLE t1 (a INET6);\n CREATE OR REPLACE TABLE t2 (a INET6);\n\nINSERT INTO t1 VALUES\n(\'2001:db8::ff00:42:8328\'),(\'2001:db8::ff00:42:8329\');\n INSERT INTO t2 VALUES\n(\'2001:db8::ff00:42:832a\'),(\'2001:db8::ff00:42:8329\');\n\nSELECT t1.* FROM t1,t2 WHERE t1.a=t2.a;\n +------------------------+\n | a |\n +------------------------+\n | 2001:db8::ff00:42:8329 |\n +------------------------+\n\nWith a character string expression with a text (short or long) address\nrepresentation:\n\nCREATE OR REPLACE TABLE t1 (a INET6);\n\nINSERT INTO t1 VALUES (\'2001:db8::ff00:42:8329\');\n\nSELECT * FROM t1 WHERE a=\'2001:db8::ff00:42:8329\';\n +------------------------+\n | a |\n +------------------------+\n | 2001:db8::ff00:42:8329 |\n +------------------------+\n\nWith a 16-byte binary string expression:\n\nCREATE OR REPLACE TABLE t1 (a INET6);\n\nINSERT INTO t1 VALUES (\'2001:db8::ff00:42:8329\');\n\nSELECT * FROM t1 WHERE a=X\'20010DB8000000000000FF0000428329\';\n +------------------------+\n | a |\n +------------------------+\n | 2001:db8::ff00:42:8329 |\n +------------------------+\n\nWith an expression of another data type:\n\nSELECT * FROM t1 WHERE a=1;\nERROR 4078 (HY000): Illegal parameter data types inet6 and int for operation\n\'=\'\n\nMixing for Result Examples\n--------------------------\n\nMixed with another INET6 expression, returning an INET6 data type:\n\nCREATE OR REPLACE TABLE t1 (a INET6, b INET6);\n\nINSERT INTO t1 VALUES (NULL,\'2001:db8::ff00:42:8329\');\n\nSELECT a FROM t1 UNION SELECT b FROM t1;\n +------------------------+\n | a |\n +------------------------+\n | NULL |\n | 2001:db8::ff00:42:8329 |\n +------------------------+\n\nSELECT COALESCE(a, b) FROM t1;\n +------------------------+\n | COALESCE(a, b) |\n +------------------------+\n | 2001:db8::ff00:42:8329 |\n +------------------------+\n\nMixed with a character string in text (short or long) address representation:\n\nCREATE OR REPLACE TABLE t1 (a INET6, b VARCHAR(64));\n\nINSERT INTO t1 VALUES (NULL,\'2001:db8::ff00:42:8328\');\n\nINSERT INTO t1 VALUES (NULL,\'2001:db8::ff00:42:832a garbage\');\n\nSELECT COALESCE(a,b) FROM t1;\n +------------------------+\n | COALESCE(a,b) |\n +------------------------+\n | 2001:db8::ff00:42:8328 |\n | NULL |\n +------------------------+\n 2 rows in set, 1 warning (0.001 sec)\n\nSHOW WARNINGS;\n\n+---------+------+---------------------------------------------------------+\n | Level | Code | Message\n|\n\n+---------+------+---------------------------------------------------------+\n | Warning | 1292 | Incorrect inet6 value: \'2001:db8::ff00:42:832a garbage\'\n|\n\n+---------+------+---------------------------------------------------------+\n\nMixed with a 16-byte binary string:\n\nCREATE OR REPLACE TABLE t1 (a INET6, b VARBINARY(16));\n\nINSERT INTO t1 VALUES (NULL,CONCAT(0xFFFF,REPEAT(0x0000,6),0xFFFF));\n\nINSERT INTO t1 VALUES (NULL,0x00/*garbage*/);\n\nSELECT COALESCE(a,b) FROM t1;\n +---------------+\n | COALESCE(a,b) |\n +---------------+\n | ffff::ffff |\n | NULL |\n +---------------+\n 2 rows in set, 1 warning (0.001 sec)\n\nSHOW WARNINGS;\n +---------+------+-------------------------------+\n | Level | Code | Message |\n +---------+------+-------------------------------+\n | Warning | 1292 | Incorrect inet6 value: \'\\x00\' |\n +---------+------+-------------------------------+\n\nMixing with other data types:\n\nSELECT CAST(\'ffff::ffff\' AS INET6) UNION SELECT 1;\nERROR 4078 (HY000): Illegal parameter data types inet6 and int for operation\n\'UNION\'\n\nFunctions and Operators Examples\n--------------------------------\n\nHEX with an INET6 argument returning a hexadecimal representation:\n\nSELECT HEX(CAST(\'2001:db8::ff00:42:8329\' AS INET6));\n +----------------------------------------------+\n | HEX(CAST(\'2001:db8::ff00:42:8329\' AS INET6)) |\n +----------------------------------------------+\n | 20010DB8000000000000FF0000428329 |\n +----------------------------------------------+\n\nINET6_ATON now understands INET6 values as an argument:\n\nCREATE OR REPLACE TABLE t1 (a INET6);\n\nINSERT INTO t1 VALUES (\'2001:db8::ff00:42:8329\');\n\nSELECT a, HEX(INET6_ATON(a)) FROM t1;\n +------------------------+----------------------------------+\n | a | HEX(INET6_ATON(a)) |\n +------------------------+----------------------------------+\n | 2001:db8::ff00:42:8329 | 20010DB8000000000000FF0000428329 |\n +------------------------+----------------------------------+\n\nIS_IPV4_COMPAT and IS_IPV4_MAPPED prototype now a BINARY(16)):\n\nCREATE OR REPLACE TABLE t1 (a INET6);\n\nINSERT INTO t1 VALUES (\'2001:db8::ff00:42:8329\');\n INSERT INTO t1 VALUES (\'::ffff:192.168.0.1\');\n INSERT INTO t1 VALUES (\'::192.168.0.1\');\n\nSELECT a, IS_IPV4_MAPPED(a), IS_IPV4_COMPAT(a) FROM t1;\n +------------------------+-------------------+-------------------+\n | a | IS_IPV4_MAPPED(a) | IS_IPV4_COMPAT(a) |\n +------------------------+-------------------+-------------------+\n | 2001:db8::ff00:42:8329 | 0 | 0 |\n | ::ffff:192.168.0.1 | 1 | 0 |\n | ::192.168.0.1 | 0 | 1 |\n +------------------------+-------------------+-------------------+\n\nAutomatic implicit CAST to INET6:\n\nCREATE OR REPLACE TABLE t1 (\n a INET6,\n b VARCHAR(39) DEFAULT a\n );\n\nINSERT INTO t1 (a) VALUES (\'ffff::ffff\'),(\'::ffff:192.168.0.1\');\n\nSELECT a, IS_IPV4_MAPPED(a), b, IS_IPV4_MAPPED(b) FROM t1;\n\n+--------------------+-------------------+--------------------+----------------\n--+\n | a | IS_IPV4_MAPPED(a) | b |\nIS_IPV4_MAPPED(b) |\n\n+--------------------+-------------------+--------------------+----------------\n--+\n | ffff::ffff | 0 | ffff::ffff |\n 0 |\n | ::ffff:192.168.0.1 | 1 | ::ffff:192.168.0.1 |\n 1 |\n\n+--------------------+-------------------+--------------------+----------------\n--+\n\nCREATE OR REPLACE TABLE t1 (\n a INET6,\n b BINARY(16) DEFAULT UNHEX(HEX(a))\n );\n\nINSERT INTO t1 (a) VALUES (\'ffff::ffff\'),(\'::ffff:192.168.0.1\');\n\nSELECT a, IS_IPV4_MAPPED(a), HEX(b), IS_IPV4_MAPPED(b) FROM t1;\n\n+--------------------+-------------------+----------------------------------+--\n----------------+\n | a | IS_IPV4_MAPPED(a) | HEX(b)\n | IS_IPV4_MAPPED(b) |\n\n+--------------------+-------------------+----------------------------------+--\n----------------+\n | ffff::ffff | 0 |\nFFFF000000000000000000000000FFFF | 0 |\n | ::ffff:192.168.0.1 | 1 |\n00000000000000000000FFFFC0A80001 | 1 |\n\n+--------------------+-------------------+----------------------------------+--\n----------------+\n\nPrepared Statement Parameters Examples\n--------------------------------------\n\nCREATE OR REPLACE TABLE t1 (a INET6);\n\nEXECUTE IMMEDIATE \'INSERT INTO t1 VALUES (?)\' USING \'ffff::fffe\';\nEXECUTE IMMEDIATE \'INSERT INTO t1 VALUES (?)\' USING\nX\'FFFF000000000000000000000000FFFF\';\n\nSELECT * FROM t1;\n+------------+\n| a |\n+------------+\n| ffff::fffe |\n| ffff::ffff |\n+------------+\n\nEXECUTE IMMEDIATE \'SELECT * FROM t1 WHERE a=?\' USING \'ffff::fffe\';\n+------------+\n| a |\n+------------+\n| ffff::fffe |\n+------------+\n\nEXECUTE IMMEDIATE \'SELECT * FROM t1 WHERE a=?\' USING','','https://mariadb.com/kb/en/inet6/'); -update help_topic set description = CONCAT(description, '\nX\'FFFF000000000000000000000000FFFF\';\n+------------+\n| a |\n+------------+\n| ffff::ffff |\n+------------+\n\nMigration between BINARY(16) and INET6 Examples\n-----------------------------------------------\n\nBefore MariaDB 10.5:\n\nCREATE OR REPLACE TABLE t1 (a BINARY(16));\n\nINSERT INTO t1 VALUES (INET6_ATON(\'ffff::ffff\'));\n\nSELECT INET6_NTOA(a) FROM t1;\n+---------------+\n| INET6_NTOA(a) |\n+---------------+\n| ffff::ffff |\n+---------------+\n\nMigrating to INET6, from MariaDB 10.5:\n\nALTER TABLE t1 MODIFY a INET6;\n\nINSERT INTO t1 VALUES (\'ffff::fffe\');\n\nSELECT * FROM t1;\n+------------+\n| a |\n+------------+\n| ffff::ffff |\n| ffff::fffe |\n+------------+\n\nMigration from INET6 to BINARY(16):\n\nCREATE OR REPLACE TABLE t1 (a INET6);\n\nINSERT INTO t1 VALUES (\'2001:db8::ff00:42:8329\');\nINSERT INTO t1 VALUES (\'::ffff:192.168.0.1\');\nINSERT INTO t1 VALUES (\'::192.168.0.1\');\n\nALTER TABLE t1 MODIFY a BINARY(16);\n\nSELECT INET6_NTOA(a) FROM t1;\n+------------------------+\n| INET6_NTOA(a) |\n+------------------------+\n| 2001:db8::ff00:42:8329 |\n| ::ffff:192.168.0.1 |\n| ::192.168.0.1 |\n+------------------------+\n\nCasting from INET4 to INET6\n---------------------------\n\nFrom MariaDB 11.3.0, casting from INET4 data types to INET6 is permitted,\nallowing INET4 values to be inserted into INET6 columns.\n\nBefore MariaDB 11.3:\n\nCREATE TABLE t1 (a INET6);\n\nINSERT INTO t1 VALUES(\'0.0.0.0\'), (\'255.10.0.0\'), (\'255.255.255.255\');\nERROR 1292 (22007): Incorrect inet6 value: \'0.0.0.0\' for column\n`test`.`t1`.`a` at row 1\n\nFrom MariaDB 11.3:\n\nCREATE TABLE t1 (a INET6);\n\nINSERT INTO t1 VALUES(\'0.0.0.0\'), (\'255.10.0.0\'), (\'255.255.255.255\');\nQuery OK, 3 rows affected (0.027 sec)\n\nURL: https://mariadb.com/kb/en/inet6/') WHERE help_topic_id = 278; -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (279,23,'JSON Data Type','The JSON alias was added to make it possible to use JSON columns in statement\nbased replication from MySQL to MariaDB and to make it possible for MariaDB to\nread mysqldumps from MySQL.\n\nJSON is an alias for LONGTEXT introduced for compatibility reasons with\nMySQL\'s JSON data type. MariaDB implements this as a LONGTEXT rather, as the\nJSON data type contradicts the SQL standard, and MariaDB\'s benchmarks indicate\nthat performance is at least equivalent.\n\nIn order to ensure that a a valid json document is inserted, the JSON_VALID\nfunction can be used as a CHECK constraint. This constraint is automatically\nincluded for types using the JSON alias from MariaDB 10.4.3.\n\nExamples\n--------\n\nCREATE TABLE t (j JSON);\n\nDESC t;\n+-------+----------+------+-----+---------+-------+\n| Field | Type | Null | Key | Default | Extra |\n+-------+----------+------+-----+---------+-------+\n| j | longtext | YES | | NULL | |\n+-------+----------+------+-----+---------+-------+\n\nWith validation:\n\nCREATE TABLE t2 (\n j JSON\n CHECK (JSON_VALID(j))\n);\n\nINSERT INTO t2 VALUES (\'invalid\');\nERROR 4025 (23000): CONSTRAINT `j` failed for `test`.`t2`\n\nINSERT INTO t2 VALUES (\'{\"id\": 1, \"name\": \"Monty\"}\');\nQuery OK, 1 row affected (0.13 sec)\n\nReplicating JSON Data Between MySQL and MariaDB\n-----------------------------------------------\n\nThe JSON type in MySQL stores the JSON object in a compact form, not as\nLONGTEXT as in MariaDB. This means that row based replication will not work\nfor JSON types from MySQL to MariaDB.\n\nThere are a a few different ways to solve this:\n\n* Use statement based replication.\n* Change the JSON column to type TEXT in MySQL\n* If you must use row-based replication and cannot change the MySQL master\nfrom JSON to TEXT, you can try to introduce an intermediate MySQL slave and\nchange the column type from JSON to TEXT on it. Then you replicate from this\nintermediate slave to MariaDB.\n\nConverting a MySQL TABLE with JSON Fields to MariaDB\n----------------------------------------------------\n\nMariaDB can\'t directly access MySQL\'s JSON format.\n\nThere are a a few different ways to move the table to MariaDB:\n\n* From MariaDB 10.5.7, see the you can use the mysql_json plugin. See Making\nMariaDB understand MySQL JSON.\n* Change the JSON column to type TEXT in MySQL. After this, MariaDB can\ndirectly use the table without any need for a dump and restore.\n* Use mysqldump to copy the table.\n\nDifferences Between MySQL JSON Strings and MariaDB JSON Strings\n---------------------------------------------------------------\n\n* In MySQL, JSON is an object and is compared according to json values. In\nMariaDB JSON strings are normal strings and compared as strings. One exception\nis when using JSON_EXTRACT() in which case strings are unescaped before\ncomparison.\n\nURL: https://mariadb.com/kb/en/json-data-type/','','https://mariadb.com/kb/en/json-data-type/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (280,23,'MEDIUMBLOB','Syntax\n------\n\nMEDIUMBLOB\n\nDescription\n-----------\n\nA BLOB column with a maximum length of 16,777,215 (224 - 1) bytes. Each\nMEDIUMBLOB value is stored using a three-byte length prefix that indicates the\nnumber of bytes in the value.\n\nURL: https://mariadb.com/kb/en/mediumblob/','','https://mariadb.com/kb/en/mediumblob/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (281,23,'MEDIUMTEXT','Syntax\n------\n\nMEDIUMTEXT [CHARACTER SET charset_name] [COLLATE collation_name]\n\nDescription\n-----------\n\nA TEXT column with a maximum length of 16,777,215 (224 - 1) characters. The\neffective maximum length is less if the value contains multi-byte characters.\nEach MEDIUMTEXT value is stored using a three-byte length prefix that\nindicates the number of bytes in the value.\n\nURL: https://mariadb.com/kb/en/mediumtext/','','https://mariadb.com/kb/en/mediumtext/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (282,23,'LONGBLOB','Syntax\n------\n\nLONGBLOB\n\nDescription\n-----------\n\nA BLOB column with a maximum length of 4,294,967,295 bytes or 4GB (232 - 1).\nThe effective maximum length of LONGBLOB columns depends on the configured\nmaximum packet size in the client/server protocol and available memory. Each\nLONGBLOB value is stored using a four-byte length prefix that indicates the\nnumber of bytes in the value.\n\nOracle Mode\n-----------\n\nIn Oracle mode from MariaDB 10.3, BLOB is a synonym for LONGBLOB.\n\nURL: https://mariadb.com/kb/en/longblob/','','https://mariadb.com/kb/en/longblob/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (283,23,'LONGTEXT','Syntax\n------\n\nLONGTEXT [CHARACTER SET charset_name] [COLLATE collation_name]\n\nDescription\n-----------\n\nA TEXT column with a maximum length of 4,294,967,295 or 4GB (232 - 1)\ncharacters. The effective maximum length is less if the value contains\nmulti-byte characters. The effective maximum length of LONGTEXT columns also\ndepends on the configured maximum packet size in the client/server protocol\nand available memory. Each LONGTEXT value is stored using a four-byte length\nprefix that indicates the number of bytes in the value.\n\nFrom MariaDB 10.2.7, JSON is an alias for LONGTEXT. See JSON Data Type for\ndetails.\n\nOracle Mode\n-----------\n\nIn Oracle mode from MariaDB 10.3, CLOB is a synonym for LONGTEXT.\n\nURL: https://mariadb.com/kb/en/longtext/','','https://mariadb.com/kb/en/longtext/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (284,23,'ROW','Syntax\n------\n\nROW ( [{, }... ])\n\nDescription\n-----------\n\nROW is a data type for stored procedure variables.\n\nFeatures\n--------\n\nROW fields as normal variables\n------------------------------\n\nROW fields (members) act as normal variables, and are able to appear in all\nquery parts where a stored procedure variable is allowed:\n\n* Assignment is using the := operator and the SET command:\n\na.x:= 10;\na.x:= b.x;\nSET a.x= 10, a.y=20, a.z= b.z;\n\n* Passing to functions and operators:\n\nSELECT f1(rec.a), rec.a<10;\n\n* Clauses (select list, WHERE, HAVING, LIMIT, etc...,):\n\nSELECT var.a, t1.b FROM t1 WHERE t1.b=var.b LIMIT var.c;\n\n* INSERT values:\n\nINSERT INTO t1 VALUES (rec.a, rec.b, rec.c);\n\n* SELECT .. INTO targets\n\nSELECT a,b INTO rec.a, rec.b FROM t1 WHERE t1.id=10;\n\n* Dynamic SQL out parameters (EXECUTE and EXECUTE IMMEDIATE)\n\nEXECUTE IMMEDIATE \'CALL proc_with_out_param(?)\' USING rec.a;\n\nROW type variables as FETCH targets\n-----------------------------------\n\nROW type variables are allowed as FETCH targets:\n\nFETCH cur INTO rec;\n\nwhere cur is a CURSOR and rec is a ROW type stored procedure variable.\n\nNote, currently an attempt to use FETCH for a ROW type variable returns this\nerror:\n\nERROR 1328 (HY000): Incorrect number of FETCH variables\n\nFETCH from a cursor cur into a ROW variable rec works as follows:\n\n* The number of fields in cur must match the number of fields in rec.\n Otherwise, an error is reported.\n\n* Assignment is done from left to right. The first cursor field is assigned to\n the first variable field, the second cursor field is assigned to the second\n variable field, etc.\n\n* Field names in rec are not important and can differ from field names\n in cur.\n\nSee FETCH Examples (below) for examples of using this with sql_mode=ORACLE and\nsql_mode=DEFAULT.\n\nROW type variables as SELECT...INTO targets\n-------------------------------------------\n\nROW type variables are allowed as SELECT..INTO targets with some differences\ndepending on which sql_mode is in use.\n\n* When using sql_mode=ORACLE, table%ROWTYPE and cursor%ROWTYPE\n variables can be used as SELECT...INTO targets.\n\n* Using multiple ROW variables in the SELECT..INTO list will report an\n error.\n\n* Using ROW variables with a different column count than in\n the SELECT..INTO list will report an error.\n\nSee SELECT...INTO Examples (below) for examples of using this with\nsql_mode=ORACLE and sql_mode=DEFAULT.\n\nFeatures not implemented\n------------------------\n\nThe following features are planned, but not implemented yet:\n\n* Returning a ROW type expression from a stored function (see MDEV-12252).\nThis will need some grammar change to support field names after parentheses:\n\nSELECT f1().x FROM DUAL;\n\n* Returning a ROW type expression from a built-in hybrid type function, such\nas CASE, IF, etc. \n* ROW of ROWs\n\nExamples\n--------\n\nDeclaring a ROW in a stored procedure\n-------------------------------------\n\nDELIMITER $$\nCREATE PROCEDURE p1()\nBEGIN\n DECLARE r ROW (c1 INT, c2 VARCHAR(10));\n SET r.c1= 10;\n SET r.c2= \'test\';\n INSERT INTO t1 VALUES (r.c1, r.c2);\nEND;\n$$\nDELIMITER ;\nCALL p1();\n\nFETCH Examples\n--------------\n\nA complete FETCH example for sql_mode=ORACLE:\n\nDROP TABLE IF EXISTS t1;\nCREATE TABLE t1 (a INT, b VARCHAR(32));\nINSERT INTO t1 VALUES (10,\'b10\');\nINSERT INTO t1 VALUES (20,\'b20\');\nINSERT INTO t1 VALUES (30,\'b30\');\n\nSET sql_mode=oracle;\nDROP PROCEDURE IF EXISTS p1;\nDELIMITER $$\nCREATE PROCEDURE p1 AS\n rec ROW(a INT, b VARCHAR(32));\n CURSOR c IS SELECT a,b FROM t1;\nBEGIN\n OPEN c;\n LOOP\n FETCH c INTO rec;\n EXIT WHEN c%NOTFOUND;\n SELECT (\'rec=(\' || rec.a ||\',\'|| rec.b||\')\');\n END LOOP;\n CLOSE c;\nEND;\n$$\nDELIMITER ;\nCALL p1();\n\nA complete FETCH example for sql_mode=DEFAULT:\n\nDROP TABLE IF EXISTS t1;\nCREATE TABLE t1 (a INT, b VARCHAR(32));\nINSERT INTO t1 VALUES (10,\'b10\');\nINSERT INTO t1 VALUES (20,\'b20\');\nINSERT INTO t1 VALUES (30,\'b30\');\n\nSET sql_mode=DEFAULT;\nDROP PROCEDURE IF EXISTS p1;\nDELIMITER $$\nCREATE PROCEDURE p1()\nBEGIN\n DECLARE done INT DEFAULT FALSE;\n DECLARE rec ROW(a INT, b VARCHAR(32));\n DECLARE c CURSOR FOR SELECT a,b FROM t1;\n DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;\n OPEN c;\nread_loop:\n LOOP\n FETCH c INTO rec;\n IF done THEN\n LEAVE read_loop;\n END IF;\n SELECT CONCAT(\'rec=(\',rec.a,\',\',rec.b,\')\');\n END LOOP;\n CLOSE c;\nEND;\n$$\nDELIMITER ;\nCALL p1();\n\nSELECT...INTO Examples\n----------------------\n\nA SELECT...INTO example for sql_mode=DEFAULT:\n\nSET sql_mode=DEFAULT;\nDROP TABLE IF EXISTS t1;\nDROP PROCEDURE IF EXISTS p1;\nCREATE TABLE t1 (a INT, b VARCHAR(32));\nINSERT INTO t1 VALUES (10,\'b10\');\nDELIMITER $$\nCREATE PROCEDURE p1()\nBEGIN\n DECLARE rec1 ROW(a INT, b VARCHAR(32));\n SELECT * FROM t1 INTO rec1;\n SELECT rec1.a, rec1.b;\nEND;\n$$\nDELIMITER ;\nCALL p1();\n\nThe above example returns:\n\n+--------+--------+\n| rec1.a | rec1.b |\n+--------+--------+\n| 10 | b10 |\n+--------+--------+\n\nA SELECT...INTO example for sql_mode=ORACLE:\n\nSET sql_mode=ORACLE;\nDROP TABLE IF EXISTS t1;\nDROP PROCEDURE IF EXISTS p1;\nCREATE TABLE t1 (a INT, b VARCHAR(32));\nINSERT INTO t1 VALUES (10,\'b10\');\nDELIMITER $$\nCREATE PROCEDURE p1 AS\n rec1 ROW(a INT, b VARCHAR(32));\nBEGIN\n SELECT * FROM t1 INTO rec1;\n SELECT rec1.a, rec1.b;\nEND;\n$$\nDELIMITER ;\nCALL p1();\n\nThe above example returns:\n\n+--------+--------+\n| rec1.a | rec1.b |\n+--------+--------+\n| 10 | b10 |\n+--------+--------+\n\nAn example for sql_mode=ORACLE using table%ROWTYPE variables as SELECT..INTO\ntargets:\n\nSET sql_mode=ORACLE;\nDROP TABLE IF EXISTS t1;\nDROP PROCEDURE IF EXISTS p1;\nCREATE TABLE t1 (a INT, b VARCHAR(32));\nINSERT INTO t1 VALUES (10,\'b10\');\nDELIMITER $$\nCREATE PROCEDURE p1 AS\n rec1 t1%ROWTYPE;\nBEGIN\n SELECT * FROM t1 INTO rec1;\n SELECT rec1.a, rec1.b;\nEND;\n$$\nDELIMITER ;\nCALL p1();\n\nThe above example returns:\n\n+--------+--------+\n| rec1.a | rec1.b |\n+--------+--------+\n| 10 | b10 |\n+--------+--------+\n\nAn example for sql_mode=ORACLE using cursor%ROWTYPE variables as SELECT..INTO\ntargets:\n\nSET sql_mode=ORACLE;\nDROP TABLE IF EXISTS t1;\nDROP PROCEDURE IF EXISTS p1;\nCREATE TABLE t1 (a INT, b VARCHAR(32));\nINSERT INTO t1 VALUES (10,\'b10\');\nDELIMITER $$\nCREATE PROCEDURE p1 AS\n CURSOR cur1 IS SELECT * FROM t1;\n rec1 cur1%ROWTYPE;\nBEGIN\n SELECT * FROM t1 INTO rec1;\n SELECT rec1.a, rec1.b;\nEND;\n$$\nDELIMITER ;\nCALL p1();\n\nThe above example returns:\n\n+--------+--------+\n| rec1.a | rec1.b |\n+--------+--------+\n| 10 | b10 |\n+--------+--------+\n\nURL: https://mariadb.com/kb/en/row/','','https://mariadb.com/kb/en/row/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (285,23,'TEXT','Syntax\n------\n\nTEXT[(M)] [CHARACTER SET charset_name] [COLLATE collation_name]\n\nDescription\n-----------\n\nA TEXT column with a maximum length of 65,535 (216 - 1) characters. The\neffective maximum length is less if the value contains multi-byte characters.\nEach TEXT value is stored using a two-byte length prefix that indicates the\nnumber of bytes in the value. If you need a bigger storage, consider using\nMEDIUMTEXT instead.\n\nAn optional length M can be given for this type. If this is done, MariaDB\ncreates the column as the smallest TEXT type large enough to hold values M\ncharacters long.\n\nBefore MariaDB 10.2, all MariaDB collations were of type PADSPACE, meaning\nthat TEXT (as well as VARCHAR and CHAR values) are compared without regard for\ntrailing spaces. This does not apply to the LIKE pattern-matching operator,\nwhich takes into account trailing spaces.\n\nBefore MariaDB 10.2.1, BLOB and TEXT columns could not be assigned a DEFAULT\nvalue. This restriction was lifted in MariaDB 10.2.1.\n\nExamples\n--------\n\nTrailing spaces:\n\nCREATE TABLE strtest (d TEXT(10));\nINSERT INTO strtest VALUES(\'Maria \');\n\nSELECT d=\'Maria\',d=\'Maria \' FROM strtest;\n+-----------+--------------+\n| d=\'Maria\' | d=\'Maria \' |\n+-----------+--------------+\n| 1 | 1 |\n+-----------+--------------+\n\nSELECT d LIKE \'Maria\',d LIKE \'Maria \' FROM strtest;\n+----------------+-------------------+\n| d LIKE \'Maria\' | d LIKE \'Maria \' |\n+----------------+-------------------+\n| 0 | 1 |\n+----------------+-------------------+\n\nIndexing\n--------\n\nTEXT columns can only be indexed over a specified length. This means that they\ncannot be used as the primary key of a table norm until MariaDB 10.4, can a\nunique index be created on them.\n\nMariaDB starting with 10.4\n--------------------------\nStarting with MariaDB 10.4, a unique index can be created on a TEXT column.\n\nInternally, this uses hash indexing to quickly check the values and if a hash\ncollision is found, the actual stored values are compared in order to retain\nthe uniqueness.\n\nDifference between VARCHAR and TEXT\n-----------------------------------\n\n* VARCHAR columns can be fully indexed. TEXT columns can only be indexed over\na specified length.\n* Using TEXT or BLOB in a SELECT query that uses temporary tables for storing\nintermediate results will force the temporary table to be disk based (using\nthe Aria storage engine instead of the memory storage engine, which is a bit\nslower. This is not that bad as the Aria storage engine caches the rows in\nmemory. To get the benefit of this, one should ensure that the\naria_pagecache_buffer_size variable is big enough to hold most of the row and\nindex data for temporary tables.\n\nFor Storage Engine Developers\n-----------------------------\n\n* Internally the full length of the VARCHAR column is allocated inside each\nTABLE objects record[] structure. As there are three such buffers, each open\ntable will allocate 3 times max-length-to-store-varchar bytes of memory.\n* TEXT and BLOB columns are stored with a pointer (4 or 8 bytes) + a 1-4 bytes\nlength. The TEXT data is only stored once. This means that internally TEXT\nuses less memory for each open table but instead has the additional overhead\nthat each TEXT object needs to be allocated and freed for each row access\n(with some caching in between).\n\nURL: https://mariadb.com/kb/en/text/','','https://mariadb.com/kb/en/text/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (286,23,'TINYBLOB','Syntax\n------\n\nTINYBLOB\n\nDescription\n-----------\n\nA BLOB column with a maximum length of 255 (28 - 1) bytes. Each TINYBLOB value\nis stored using a one-byte length prefix that indicates the number of bytes in\nthe value.\n\nURL: https://mariadb.com/kb/en/tinyblob/','','https://mariadb.com/kb/en/tinyblob/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (287,23,'TINYTEXT','Syntax\n------\n\nTINYTEXT [CHARACTER SET charset_name] [COLLATE collation_name]\n\nDescription\n-----------\n\nA TEXT column with a maximum length of 255 (28 - 1) characters. The effective\nmaximum length is less if the value contains multi-byte characters. Each\nTINYTEXT value is stored using a one-byte length prefix that indicates the\nnumber of bytes in the value.\n\nURL: https://mariadb.com/kb/en/tinytext/','','https://mariadb.com/kb/en/tinytext/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (288,23,'VARBINARY','Syntax\n------\n\nVARBINARY(M)\n\nDescription\n-----------\n\nThe VARBINARY type is similar to the VARCHAR type, but stores binary byte\nstrings rather than non-binary character strings. M represents the maximum\ncolumn length in bytes.\n\nIt contains no character set, and comparison and sorting are based on the\nnumeric value of the bytes.\n\nIf the maximum length is exceeded, and SQL strict mode is not enabled , the\nextra characters will be dropped with a warning. If strict mode is enabled, an\nerror will occur.\n\nUnlike BINARY values, VARBINARYs are not right-padded when inserting.\n\nOracle Mode\n-----------\n\nIn Oracle mode from MariaDB 10.3, RAW is a synonym for VARBINARY.\n\nExamples\n--------\n\nInserting too many characters, first with strict mode off, then with it on:\n\nCREATE TABLE varbins (a VARBINARY(10));\n\nINSERT INTO varbins VALUES(\'12345678901\');\nQuery OK, 1 row affected, 1 warning (0.04 sec)\n\nSELECT * FROM varbins;\n+------------+\n| a |\n+------------+\n| 1234567890 |\n+------------+\n\nSET sql_mode=\'STRICT_ALL_TABLES\';\n\nINSERT INTO varbins VALUES(\'12345678901\');\nERROR 1406 (22001): Data too long for column \'a\' at row 1\n\nSorting is performed with the byte value:\n\nTRUNCATE varbins;\n\nINSERT INTO varbins VALUES(\'A\'),(\'B\'),(\'a\'),(\'b\');\n\nSELECT * FROM varbins ORDER BY a;\n+------+\n| a |\n+------+\n| A |\n| B |\n| a |\n| b |\n+------+\n\nUsing CAST to sort as a CHAR instead:\n\nSELECT * FROM varbins ORDER BY CAST(a AS CHAR);\n+------+\n| a |\n+------+\n| a |\n| A |\n| b |\n| B |\n+------+\n\nURL: https://mariadb.com/kb/en/varbinary/','','https://mariadb.com/kb/en/varbinary/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (289,23,'VARCHAR','Syntax\n------\n\n[NATIONAL] VARCHAR(M) [CHARACTER SET charset_name] [COLLATE collation_name]\n\nDescription\n-----------\n\nA variable-length string. M represents the maximum column length in\ncharacters. The range of M is 0 to 65,532. The effective maximum length of a\nVARCHAR is subject to the maximum row size and the character set used. For\nexample, utf8 characters can require up to three bytes per character, so a\nVARCHAR column that uses the utf8 character set can be declared to be a\nmaximum of 21,844 characters.\n\nNote: For the ColumnStore engine, M represents the maximum column length in\nbytes.\n\nMariaDB stores VARCHAR values as a one-byte or two-byte length prefix plus\ndata. The length prefix indicates the number of bytes in the value. A VARCHAR\ncolumn uses one length byte if values require no more than 255 bytes, two\nlength bytes if values may require more than 255 bytes.\n\nMariaDB follows the standard SQL specification, and does not remove trailing\nspaces from VARCHAR values.\n\nVARCHAR(0) columns can contain 2 values: an empty string or NULL. Such columns\ncannot be part of an index. The CONNECT storage engine does not support\nVARCHAR(0).\n\nVARCHAR is shorthand for CHARACTER VARYING. NATIONAL VARCHAR is the standard\nSQL way to define that a VARCHAR column should use some predefined character\nset. MariaDB uses utf8 as this predefined character set, as does MySQL 4.1 and\nup. NVARCHAR is shorthand for NATIONAL VARCHAR.\n\nBefore MariaDB 10.2, all MariaDB collations were of type PADSPACE, meaning\nthat VARCHAR (as well as CHAR and TEXT values) are compared without regard for\ntrailing spaces. This does not apply to the LIKE pattern-matching operator,\nwhich takes into account trailing spaces. From MariaDB 10.2, a number of NO\nPAD collations are available.\n\nIf a unique index consists of a column where trailing pad characters are\nstripped or ignored, inserts into that column where values differ only by the\nnumber of trailing pad characters will result in a duplicate-key error.\n\nExamples\n--------\n\nThe following are equivalent:\n\nVARCHAR(30) CHARACTER SET utf8\nNATIONAL VARCHAR(30)\nNVARCHAR(30)\nNCHAR VARCHAR(30)\nNATIONAL CHARACTER VARYING(30)\nNATIONAL CHAR VARYING(30)\n\nTrailing spaces:\n\nCREATE TABLE strtest (v VARCHAR(10));\nINSERT INTO strtest VALUES(\'Maria \');\n\nSELECT v=\'Maria\',v=\'Maria \' FROM strtest;\n+-----------+--------------+\n| v=\'Maria\' | v=\'Maria \' |\n+-----------+--------------+\n| 1 | 1 |\n+-----------+--------------+\n\nSELECT v LIKE \'Maria\',v LIKE \'Maria \' FROM strtest;\n+----------------+-------------------+\n| v LIKE \'Maria\' | v LIKE \'Maria \' |\n+----------------+-------------------+\n| 0 | 1 |\n+----------------+-------------------+\n\nTruncation\n----------\n\n* Depending on whether or not strict sql mode is set, you will either get a\nwarning or an error if you try to insert a string that is too long into a\nVARCHAR column. If the extra characters are spaces, the spaces that can\'t fit\nwill be removed and you will always get a warning, regardless of the sql mode\nsetting.\n\nDifference Between VARCHAR and TEXT\n-----------------------------------\n\n* VARCHAR columns can be fully indexed. TEXT columns can only be indexed over\na specified length.\n* Using TEXT or BLOB in a SELECT query that uses temporary tables for storing\nintermediate results will force the temporary table to be disk based (using\nthe Aria storage engine instead of the memory storage engine, which is a bit\nslower. This is not that bad as the Aria storage engine caches the rows in\nmemory. To get the benefit of this, one should ensure that the\naria_pagecache_buffer_size variable is big enough to hold most of the row and\nindex data for temporary tables.\n\nOracle Mode\n-----------\n\nIn Oracle mode from MariaDB 10.3, VARCHAR2 is a synonym.\n\nFor Storage Engine Developers\n-----------------------------\n\n* Internally the full length of the VARCHAR column is allocated inside each\nTABLE objects record[] structure. As there are three such buffers, each open\ntable will allocate 3 times max-length-to-store-varchar bytes of memory.\n* TEXT and BLOB columns are stored with a pointer (4 or 8 bytes) + a 1-4 bytes\nlength. The TEXT data is only stored once. This means that internally TEXT\nuses less memory for each open table but instead has the additional overhead\nthat each TEXT object needs to be allocated and freed for each row access\n(with some caching in between).\n\nURL: https://mariadb.com/kb/en/varchar/','','https://mariadb.com/kb/en/varchar/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (290,23,'SET Data Type','Syntax\n------\n\nSET(\'value1\',\'value2\',...) [CHARACTER SET charset_name] [COLLATE\ncollation_name]\n\nDescription\n-----------\n\nA set. A string object that can have zero or more values, each of which must\nbe chosen from the list of values \'value1\', \'value2\', ... A SET column can\nhave a maximum of 64 members. SET values are represented internally as\nintegers.\n\nSET values cannot contain commas.\n\nIf a SET contains duplicate values, an error will be returned if strict mode\nis enabled, or a warning if strict mode is not enabled.\n\nURL: https://mariadb.com/kb/en/set-data-type/','','https://mariadb.com/kb/en/set-data-type/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (291,23,'UUID Data Type','MariaDB starting with 10.7\n--------------------------\nThe UUID data type was added in MariaDB 10.7.\n\nSyntax\n------\n\nUUID\n\nDescription\n-----------\n\nThe UUID data type is intended for the storage of 128-bit UUID (Universally\nUnique Identifier) data. See the UUID function page for more details on UUIDs\nthemselves.\n\nRetrieval\n---------\n\nData retrieved by this data type is in the string representation defined in\nRFC4122.\n\nCasting\n-------\n\nString literals of hexadecimal characters and CHAR/VARCHAR/TEXT can be cast to\nthe UUID data type. Likewise hexadecimal literals, binary-literals, and\nBINARY/VARBINARY/BLOB types can also be cast to UUID.\n\nThe data type will not accept a short UUID generated with the UUID_SHORT\nfunction, but will accept a value without the - character generated by the\nSYS_GUID function (or inserted directly). Hyphens can be partially omitted as\nwell, or included after any group of two digits.\n\nThe type does not accept UUIDs in braces, permitted by some implementations.\n\nStorage\n-------\n\nUUID are stored in an index friendly manner, the order of a UUID of\nllllllll-mmmm-Vhhh-vsss-nnnnnnnnnnnn is stored as:\n\nnnnnnnnnnnnn-vsss-Vhhh-mmmm-llllllll\n\nThis provides a sorting order, assuming a UUIDv1 (node and timestamp) is used,\nof the node, followed by the timestamp. The key aspect is the timestamps are\nsequential.\n\nMariaDB starting with 10.10\n---------------------------\nStarting from MariaDB 10.10.6 and MariaDB 10.11.5, taking into account that\nUUIDv7 and other versions are designed around time ordering, UUIDs version >=\n6 are now stored without byte-swapping, and UUIDs with version >=8 and\nvariant=0 are now considered invalid (as the standard expects)\n\nExamples\n--------\n\nCREATE TABLE t1 (id UUID);\n\nDirectly Inserting via string literals:\n\nINSERT INTO t1 VALUES(\'123e4567-e89b-12d3-a456-426655440000\');\n\nDirectly Inserting via hexadecimal literals:\n\nINSERT INTO t1 VALUES (x\'fffffffffffffffffffffffffffffffe\');\n\nGenerating and inserting via the UUID function.\n\nINSERT INTO t1 VALUES (UUID());\n\nRetrieval:\n\nSELECT * FROM t1;\n+--------------------------------------+\n| id |\n+--------------------------------------+\n| 123e4567-e89b-12d3-a456-426655440000 |\n| ffffffff-ffff-ffff-ffff-fffffffffffe |\n| 93aac041-1a14-11ec-ab4e-f859713e4be4 |\n+--------------------------------------+\n\nThe UUID_SHORT function does not generate valid full-length UUID:\n\nINSERT INTO t1 VALUES (UUID_SHORT());\nERROR 1292 (22007): Incorrect uuid value: \'99440417627439104\' \n for column `test`.`t1`.`id` at row 1\n\nAccepting a value without the - character, either directly or generated by the\nSYS_GUID function:\n\nINSERT INTO t1 VALUES (SYS_GUID());\n\nSELECT * FROM t1;\n+--------------------------------------+\n| id |\n+--------------------------------------+\n| 123e4567-e89b-12d3-a456-426655440000 |\n| ffffffff-ffff-ffff-ffff-fffffffffffe |\n| 93aac041-1a14-11ec-ab4e-f859713e4be4 |\n| ea0368d3-1a14-11ec-ab4e-f859713e4be4 |\n+--------------------------------------+\n\nSELECT SYS_GUID();\n+----------------------------------+\n| SYS_GUID() |\n+----------------------------------+\n| ff5b6bcc1a1411ecab4ef859713e4be4 |\n+----------------------------------+\n\nINSERT INTO t1 VALUES (\'ff5b6bcc1a1411ecab4ef859713e4be4\');\n\nSELECT * FROM t1;\n+--------------------------------------+\n| id |\n+--------------------------------------+\n| 123e4567-e89b-12d3-a456-426655440000 |\n| ffffffff-ffff-ffff-ffff-fffffffffffe |\n| 93aac041-1a14-11ec-ab4e-f859713e4be4 |\n| ea0368d3-1a14-11ec-ab4e-f859713e4be4 |\n| ff5b6bcc-1a14-11ec-ab4e-f859713e4be4 |\n+--------------------------------------+\n\nValid and invalid hyphen and brace usage:\n\nTRUNCATE t1;\n\nINSERT INTO t1 VALUES (\'f8aa-ed66-1a1b-11ec-ab4e-f859-713e-4be4\');\n\nINSERT INTO t1 VALUES (\'1b80667f1a1c-11ecab4ef859713e4be4\');\n\nINSERT INTO t1 VALUES (\'2fd6c945-1a-1c-11ec-ab4e-f859713e4be4\');\n\nINSERT INTO t1 VALUES (\'49-c9-f9-59-1a-1c-11ec-ab4e-f859713e4be4\');\n\nINSERT INTO t1 VALUES (\'57-96-da-c1-1a-1c-11-ec-ab-4e-f8-59-71-3e-4b-e4\');\n\nINSERT INTO t1 VALUES (\'6-eb74f8f-1a1c-11ec-ab4e-f859713e4be4\');\n\nINSERT INTO t1 VALUES (\'{29bad136-1a1d-11ec-ab4e-f859713e4be4}\');\nERROR 1292 (22007): Incorrect uuid value:\n\'{29bad136-1a1d-11ec-ab4e-f859713e4be4}\' \n for column `test`.`t1`.`id` at row 1\n\nSELECT * FROM t1;\n+--------------------------------------+\n| id |\n+--------------------------------------+\n| f8aaed66-1a1b-11ec-ab4e-f859713e4be4 |\n| 1b80667f-1a1c-11ec-ab4e-f859713e4be4 |\n| 2fd6c945-1a1c-11ec-ab4e-f859713e4be4 |\n| 49c9f959-1a1c-11ec-ab4e-f859713e4be4 |\n| 5796dac1-1a1c-11ec-ab4e-f859713e4be4 |\n| 6eb74f8f-1a1c-11ec-ab4e-f859713e4be4 |\n+--------------------------------------+\n\nURL: https://mariadb.com/kb/en/uuid-data-type/','','https://mariadb.com/kb/en/uuid-data-type/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (292,23,'DATE','Syntax\n------\n\nDATE\n\nDescription\n-----------\n\nA date. The supported range is \'1000-01-01\' to \'9999-12-31\'. MariaDB displays\nDATE values in \'YYYY-MM-DD\' format, but can be assigned dates in looser\nformats, including strings or numbers, as long as they make sense. These\ninclude a short year, YY-MM-DD, no delimiters, YYMMDD, or any other acceptable\ndelimiter, for example YYYY/MM/DD. For details, see date and time literals.\n\n\'0000-00-00\' is a permitted special value (zero-date), unless the NO_ZERO_DATE\nSQL_MODE is used. Also, individual components of a date can be set to 0 (for\nexample: \'2015-00-12\'), unless the NO_ZERO_IN_DATE SQL_MODE is used. In many\ncases, the result of en expression involving a zero-date, or a date with\nzero-parts, is NULL. If the ALLOW_INVALID_DATES SQL_MODE is enabled, if the\nday part is in the range between 1 and 31, the date does not produce any\nerror, even for months that have less than 31 days.\n\nOracle Mode\n-----------\n\nMariaDB starting with 10.3\n--------------------------\nIn Oracle mode from MariaDB 10.3, DATE with a time portion is a synonym for\nDATETIME. See also mariadb_schema.\n\nExamples\n--------\n\nCREATE TABLE t1 (d DATE);\n\nINSERT INTO t1 VALUES (\"2010-01-12\"), (\"2011-2-28\"), (\'120314\'),(\'13*04*21\');\n\nSELECT * FROM t1;\n+------------+\n| d |\n+------------+\n| 2010-01-12 |\n| 2011-02-28 |\n| 2012-03-14 |\n| 2013-04-21 |\n+------------+\n\nURL: https://mariadb.com/kb/en/date/','','https://mariadb.com/kb/en/date/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (293,23,'TIME','Syntax\n------\n\nTIME [()]\n\nDescription\n-----------\n\nA time. The range is \'-838:59:59.999999\' to \'838:59:59.999999\'. Microsecond\nprecision can be from 0-6; if not specified 0 is used. Microseconds have been\navailable since MariaDB 5.3.\n\nMariaDB displays TIME values in \'HH:MM:SS.ssssss\' format, but allows\nassignment of times in looser formats, including \'D HH:MM:SS\', \'HH:MM:SS\',\n\'HH:MM\', \'D HH:MM\', \'D HH\', \'SS\', or \'HHMMSS\', as well as permitting dropping\nof any leading zeros when a delimiter is provided, for example \'3:9:10\'. For\ndetails, see date and time literals.\n\nMariaDB 10.1.2 introduced the --mysql56-temporal-format option, on by default,\nwhich allows MariaDB to store TIMEs using the same low-level format MySQL 5.6\nuses.\n\nInternal Format\n---------------\n\nIn MariaDB 10.1.2 a new temporal format was introduced from MySQL 5.6 that\nalters how the TIME, DATETIME and TIMESTAMP columns operate at lower levels.\nThese changes allow these temporal data types to have fractional parts and\nnegative values. You can disable this feature using the\nmysql56_temporal_format system variable.\n\nTables that include TIMESTAMP values that were created on an older version of\nMariaDB or that were created while the mysql56_temporal_format system variable\nwas disabled continue to store data using the older data type format.\n\nIn order to update table columns from the older format to the newer format,\nexecute an ALTER TABLE... MODIFY COLUMN statement that changes the column to\nthe *same* data type. This change may be needed if you want to export the\ntable\'s tablespace and import it onto a server that has\nmysql56_temporal_format=ON set (see MDEV-15225).\n\nFor instance, if you have a TIME column in your table:\n\nSHOW VARIABLES LIKE \'mysql56_temporal_format\';\n\n+-------------------------+-------+\n| Variable_name | Value |\n+-------------------------+-------+\n| mysql56_temporal_format | ON |\n+-------------------------+-------+\n\nALTER TABLE example_table MODIFY ts_col TIME;\n\nWhen MariaDB executes the ALTER TABLE statement, it converts the data from the\nolder temporal format to the newer one.\n\nIn the event that you have several tables and columns using temporal data\ntypes that you want to switch over to the new format, make sure the system\nvariable is enabled, then perform a dump and restore using mariadb-dump. The\ncolumns using relevant temporal data types are restored using the new temporal\nformat.\n\nStarting from MariaDB 10.5.1 columns with old temporal formats are marked with\na /* mariadb-5.3 */ comment in the output of SHOW CREATE TABLE, SHOW COLUMNS,\nDESCRIBE statements, as well as in the COLUMN_TYPE column of the\nINFORMATION_SCHEMA.COLUMNS Table.\n\nSHOW CREATE TABLE mariadb5312_time\\G\n*************************** 1. row ***************************\n Table: mariadb5312_time\nCreate Table: CREATE TABLE `mariadb5312_time` (\n `t0` time /* mariadb-5.3 */ DEFAULT NULL,\n `t6` time(6) /* mariadb-5.3 */ DEFAULT NULL\n) ENGINE=MyISAM DEFAULT CHARSET=latin1\n\nNote, columns with the current format are not marked with a comment.\n\nExamples\n--------\n\nINSERT INTO time VALUES (\'90:00:00\'), (\'800:00:00\'), (800), (22), (151413),\n(\'9:6:3\'), (\'12 09\');\n\nSELECT * FROM time;\n+-----------+\n| t |\n+-----------+\n| 90:00:00 |\n| 800:00:00 |\n| 00:08:00 |\n| 00:00:22 |\n| 15:14:13 |\n| 09:06:03 |\n| 297:00:00 |\n+-----------+\n\nURL: https://mariadb.com/kb/en/time/','','https://mariadb.com/kb/en/time/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (294,23,'DATETIME','Syntax\n------\n\nDATETIME [(microsecond precision)]\n\nDescription\n-----------\n\nA date and time combination.\n\nMariaDB displays DATETIME values in \'YYYY-MM-DD HH:MM:SS.ffffff\' format, but\nallows assignment of values to DATETIME columns using either strings or\nnumbers. For details, see date and time literals.\n\nDATETIME columns also accept CURRENT_TIMESTAMP as the default value.\n\nMariaDB 10.1.2 introduced the --mysql56-temporal-format option, on by default,\nwhich allows MariaDB to store DATETMEs using the same low-level format MySQL\n5.6 uses. For more information, see Internal Format, below.\n\nFor storage requirements, see Data Type Storage Requirements.\n\nSupported Values\n----------------\n\nMariaDB stores values that use the DATETIME data type in a format that\nsupports values between 1000-01-01 00:00:00.000000 and 9999-12-31\n23:59:59.999999.\n\nMariaDB can also store microseconds with a precision between 0 and 6. If no\nmicrosecond precision is specified, then 0 is used by default.\n\nMariaDB also supports \'0000-00-00\' as a special zero-date value, unless\nNO_ZERO_DATE is specified in the SQL_MODE. Similarly, individual components of\na date can be set to 0 (for example: \'2015-00-12\'), unless NO_ZERO_IN_DATE is\nspecified in the SQL_MODE. In many cases, the result of en expression\ninvolving a zero-date, or a date with zero-parts, is NULL. If the\nALLOW_INVALID_DATES SQL_MODE is enabled, if the day part is in the range\nbetween 1 and 31, the date does not produce any error, even for months that\nhave less than 31 days.\n\nOracle Mode\n-----------\n\nMariaDB starting with 10.3\n--------------------------\nIn Oracle mode from MariaDB 10.3, DATE with a time portion is a synonym for\nDATETIME. See also mariadb_schema.\n\nInternal Format\n---------------\n\nIn MariaDB 10.1.2 a new temporal format was introduced from MySQL 5.6 that\nalters how the TIME, DATETIME and TIMESTAMP columns operate at lower levels.\nThese changes allow these temporal data types to have fractional parts and\nnegative values. You can disable this feature using the\nmysql56_temporal_format system variable.\n\nTables that include TIMESTAMP values that were created on an older version of\nMariaDB or that were created while the mysql56_temporal_format system variable\nwas disabled continue to store data using the older data type format.\n\nIn order to update table columns from the older format to the newer format,\nexecute an ALTER TABLE... MODIFY COLUMN statement that changes the column to\nthe *same* data type. This change may be needed if you want to export the\ntable\'s tablespace and import it onto a server that has\nmysql56_temporal_format=ON set (see MDEV-15225).\n\nFor instance, if you have a DATETIME column in your table:\n\nSHOW VARIABLES LIKE \'mysql56_temporal_format\';\n\n+-------------------------+-------+\n| Variable_name | Value |\n+-------------------------+-------+\n| mysql56_temporal_format | ON |\n+-------------------------+-------+\n\nALTER TABLE example_table MODIFY ts_col DATETIME;\n\nWhen MariaDB executes the ALTER TABLE statement, it converts the data from the\nolder temporal format to the newer one.\n\nIn the event that you have several tables and columns using temporal data\ntypes that you want to switch over to the new format, make sure the system\nvariable is enabled, then perform a dump and restore using mysqldump. The\ncolumns using relevant temporal data types are restored using the new temporal\nformat.\n\nStarting from MariaDB 10.5.1 columns with old temporal formats are marked with\na /* mariadb-5.3 */ comment in the output of SHOW CREATE TABLE, SHOW COLUMNS,\nDESCRIBE statements, as well as in the COLUMN_TYPE column of the\nINFORMATION_SCHEMA.COLUMNS Table.\n\nSHOW CREATE TABLE mariadb5312_datetime\\G\n*************************** 1. row ***************************\n Table: mariadb5312_datetime\nCreate Table: CREATE TABLE `mariadb5312_datetime` (\n `dt0` datetime /* mariadb-5.3 */ DEFAULT NULL,\n `dt6` datetime(6) /* mariadb-5.3 */ DEFAULT NULL\n) ENGINE=MyISAM DEFAULT CHARSET=latin1\n\nExamples\n--------\n\nCREATE TABLE t1 (d DATETIME);\n\nINSERT INTO t1 VALUES (\"2011-03-11\"), (\"2012-04-19 13:08:22\"),\n (\"2013-07-18 13:44:22.123456\");\n\nSELECT * FROM t1;\n+---------------------+\n| d |\n+---------------------+\n| 2011-03-11 00:00:00 |\n| 2012-04-19 13:08:22 |\n| 2013-07-18 13:44:22 |\n+---------------------+\n\nCREATE TABLE t2 (d DATETIME(6));\n\nINSERT INTO t2 VALUES (\"2011-03-11\"), (\"2012-04-19 13:08:22\"),\n (\"2013-07-18 13:44:22.123456\");\n\nSELECT * FROM t2;\n+----------------------------+\n| d |\n+----------------------------+\n| 2011-03-11 00:00:00.000000 |\n| 2012-04-19 13:08:22.000000 |\n| 2013-07-18 13:44:22.123456 |\n+----------------------------++\n\nStrings used in datetime context are automatically converted to datetime(6).\nIf you want to have a datetime without seconds, you should use\nCONVERT(..,datetime).\n\nSELECT CONVERT(\'2007-11-30 10:30:19\',datetime);\n+-----------------------------------------+\n| CONVERT(\'2007-11-30 10:30:19\',datetime) |\n+-----------------------------------------+\n| 2007-11-30 10:30:19 |\n+-----------------------------------------+\n\nSELECT CONVERT(\'2007-11-30 10:30:19\',datetime(6));\n+--------------------------------------------+\n| CONVERT(\'2007-11-30 10:30:19\',datetime(6)) |\n+--------------------------------------------+\n| 2007-11-30 10:30:19.000000 |\n+--------------------------------------------+\n\nURL: https://mariadb.com/kb/en/datetime/','','https://mariadb.com/kb/en/datetime/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (295,23,'TIMESTAMP','Syntax\n------\n\nTIMESTAMP [(0) THEN BEGIN NOT ATOMIC SELECT 1; END ; END IF;;\n\nExample of how to use WHILE loop:\n\nDELIMITER |\nBEGIN NOT ATOMIC\n DECLARE x INT DEFAULT 0;\n WHILE x <= 10 DO\n SET x = x + 1;\n SELECT x;\n END WHILE;\nEND|\nDELIMITER ;\n\nURL:\nhttps://mariadb.com/kb/en/using-compound-statements-outside-of-stored-programs/','','https://mariadb.com/kb/en/using-compound-statements-outside-of-stored-programs/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (299,24,'BEGIN END','Syntax\n------\n\n[begin_label:] BEGIN [NOT ATOMIC]\n [statement_list]\nEND [end_label]\n\nNOT ATOMIC is required when used outside of a stored procedure. Inside stored\nprocedures or within an anonymous block, BEGIN alone starts a new anonymous\nblock.\n\nDescription\n-----------\n\nBEGIN ... END syntax is used for writing compound statements. A compound\nstatement can contain multiple statements, enclosed by the BEGIN and END\nkeywords. statement_list represents a list of one or more statements, each\nterminated by a semicolon (i.e., ;) statement delimiter. statement_list is\noptional, which means that the empty compound statement (BEGIN END) is legal.\n\nNote that END will perform a commit. If you are running in autocommit mode,\nevery statement will be committed separately. If you are not running in\nautocommit mode, you must execute a COMMIT or ROLLBACK after END to get the\ndatabase up to date.\n\nUse of multiple statements requires that a client is able to send statement\nstrings containing the ; statement delimiter. This is handled in the mysql\ncommand-line client with the DELIMITER command. Changing the ;\nend-of-statement delimiter (for example, to //) allows ; to be used in a\nprogram body.\n\nA compound statement within a stored program can be labeled. end_label cannot\nbe given unless begin_label also is present. If both are present, they must be\nthe same.\n\nBEGIN ... END constructs can be nested. Each block can define its own\nvariables, a CONDITION, a HANDLER and a CURSOR, which don\'t exist in the outer\nblocks. The most local declarations override the outer objects which use the\nsame name (see example below).\n\nThe declarations order is the following:\n\n* DECLARE local variables;\n* DECLARE CONDITIONs;\n* DECLARE CURSORs;\n* DECLARE HANDLERs;\n\nNote that DECLARE HANDLER contains another BEGIN ... END construct.\n\nHere is an example of a very simple, anonymous block:\n\nBEGIN NOT ATOMIC\nSET @a=1;\nCREATE TABLE test.t1(a INT);\nEND|\n\nBelow is an example of nested blocks in a stored procedure:\n\nCREATE PROCEDURE t( )\nBEGIN\n DECLARE x TINYINT UNSIGNED DEFAULT 1;\n BEGIN\n DECLARE x CHAR(2) DEFAULT \'02\';\n DECLARE y TINYINT UNSIGNED DEFAULT 10;\n SELECT x, y;\n END;\n SELECT x;\nEND;\n\nIn this example, a TINYINT variable, x is declared in the outter block. But in\nthe inner block x is re-declared as a CHAR and an y variable is declared. The\ninner SELECT shows the \"new\" value of x, and the value of y. But when x is\nselected in the outer block, the \"old\" value is returned. The final SELECT\ndoesn\'t try to read y, because it doesn\'t exist in that context.\n\nURL: https://mariadb.com/kb/en/begin-end/','','https://mariadb.com/kb/en/begin-end/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (300,24,'CASE Statement','Syntax\n------\n\nCASE case_value\n WHEN when_value THEN statement_list\n [WHEN when_value THEN statement_list] ...\n [ELSE statement_list]\nEND CASE\n\nOr:\n\nCASE\n WHEN search_condition THEN statement_list\n [WHEN search_condition THEN statement_list] ...\n [ELSE statement_list]\nEND CASE\n\nDescription\n-----------\n\nThe text on this page describes the CASE statement for stored programs. See\nthe CASE OPERATOR for details on the CASE operator outside of stored programs.\n\nThe CASE statement for stored programs implements a complex conditional\nconstruct. If a search_condition evaluates to true, the corresponding SQL\nstatement list is executed. If no search condition matches, the statement list\nin the ELSE clause is executed. Each statement_list consists of one or more\nstatements.\n\nThe CASE statement cannot have an ELSE NULL clause, and it is terminated with\nEND CASE instead of END. implements a complex conditional construct. If a\nsearch_condition evaluates to true, the corresponding SQL statement list is\nexecuted. If no search condition matches, the statement list in the ELSE\nclause is executed. Each statement_list consists of one or more statements.\n\nIf no when_value or search_condition matches the value tested and the CASE\nstatement contains no ELSE clause, a Case not found for CASE statement error\nresults.\n\nEach statement_list consists of one or more statements; an empty\nstatement_list is not allowed. To handle situations where no value is matched\nby any WHEN clause, use an ELSE containing an empty BEGIN ... END block, as\nshown in this example:\n\nDELIMITER |\nCREATE PROCEDURE p()\nBEGIN\n DECLARE v INT DEFAULT 1;\n CASE v\n WHEN 2 THEN SELECT v;\n WHEN 3 THEN SELECT 0;\n ELSE BEGIN END;\n END CASE;\nEND;\n|\n\nThe indentation used here in the ELSE clause is for purposes of clarity only,\nand is not otherwise significant. See Delimiters in the mariadb client for\nmore on the use of the delimiter command.\n\nNote: The syntax of the CASE statement used inside stored programs differs\nslightly from that of the SQL CASE expression described in CASE OPERATOR. The\nCASE statement cannot have an ELSE NULL clause, and it is terminated with END\nCASE instead of END.\n\nURL: https://mariadb.com/kb/en/case-statement/','','https://mariadb.com/kb/en/case-statement/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (301,24,'DECLARE CONDITION','Syntax\n------\n\nDECLARE condition_name CONDITION FOR condition_value\n\ncondition_value:\n SQLSTATE [VALUE] sqlstate_value\n | mysql_error_code\n\nDescription\n-----------\n\nThe DECLARE ... CONDITION statement defines a named error condition. It\nspecifies a condition that needs specific handling and associates a name with\nthat condition. Later, the name can be used in a DECLARE ... HANDLER, SIGNAL\nor RESIGNAL statement (as long as the statement is located in the same BEGIN\n... END block).\n\nConditions must be declared after local variables, but before CURSORs and\nHANDLERs.\n\nA condition_value for DECLARE ... CONDITION can be an SQLSTATE value (a\n5-character string literal) or a MySQL error code (a number). You should not\nuse SQLSTATE value \'00000\' or MySQL error code 0, because those indicate\nsucess rather than an error condition. If you try, or if you specify an\ninvalid SQLSTATE value, an error like this is produced:\n\nERROR 1407 (42000): Bad SQLSTATE: \'00000\'\n\nFor a list of SQLSTATE values and MariaDB error codes, see MariaDB Error Codes.\n\nURL: https://mariadb.com/kb/en/declare-condition/','','https://mariadb.com/kb/en/declare-condition/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (302,24,'DECLARE HANDLER','Syntax\n------\n\nDECLARE handler_type HANDLER\n FOR condition_value [, condition_value] ...\n statement\n\nhandler_type:\n CONTINUE\n | EXIT\n | UNDO\n\ncondition_value:\n SQLSTATE [VALUE] sqlstate_value\n | condition_name\n | SQLWARNING\n | NOT FOUND\n | SQLEXCEPTION\n | mariadb_error_code\n\nDescription\n-----------\n\nThe DECLARE ... HANDLER statement specifies handlers that each may deal with\none or more conditions. If one of these conditions occurs, the specified\nstatement is executed. statement can be a simple statement (for example, SET\nvar_name = value), or it can be a compound statement written using BEGIN and\nEND.\n\nHandlers must be declared after local variables, a CONDITION and a CURSOR.\n\nFor a CONTINUE handler, execution of the current program continues after\nexecution of the handler statement. For an EXIT handler, execution terminates\nfor the BEGIN ... END compound statement in which the handler is declared.\n(This is true even if the condition occurs in an inner block.) The UNDO\nhandler type statement is not supported.\n\nIf a condition occurs for which no handler has been declared, the default\naction is EXIT.\n\nA condition_value for DECLARE ... HANDLER can be any of the following values:\n\n* An SQLSTATE value (a 5-character string literal) or a MariaDB error\ncode (a number). You should not use SQLSTATE value \'00000\' or MariaDB\nerror code 0, because those indicate sucess rather than an error\ncondition. For a list of SQLSTATE values and MariaDB error codes, see\nMariaDB Error Codes.\n* A condition name previously specified with DECLARE ... CONDITION. It must be\nin the same stored program. See DECLARE CONDITION.\n* SQLWARNING is shorthand for the class of SQLSTATE values that begin\nwith \'01\'.\n* NOT FOUND is shorthand for the class of SQLSTATE values that begin\nwith \'02\'. This is relevant only the context of cursors and is used to\ncontrol what happens when a cursor reaches the end of a data set. If\nno more rows are available, a No Data condition occurs with SQLSTATE\nvalue 02000. To detect this condition, you can set up a handler for it\n(or for a NOT FOUND condition). An example is shown in Cursor Overview. This\ncondition also occurs for SELECT ... INTO var_list statements that retrieve no\nrows.\n* SQLEXCEPTION is shorthand for the class of SQLSTATE values that do\nnot begin with \'00\', \'01\', or \'02\'.\n\nWhen an error raises, in some cases it could be handled by multiple HANDLERs.\nFor example, there may be an handler for 1050 error, a separate handler for\nthe 42S01 SQLSTATE, and another separate handler for the SQLEXCEPTION class:\nin theory all occurrences of HANDLER may catch the 1050 error, but MariaDB\nchooses the HANDLER with the highest precedence. Here are the precedence rules:\n\n* Handlers which refer to an error code have the highest precedence.\n* Handlers which refer to a SQLSTATE come next.\n* Handlers which refer to an error class have the lowest precedence.\n\nIn some cases, a statement could produce multiple errors. If this happens, in\nsome cases multiple handlers could have the highest precedence. In such cases,\nthe choice of the handler is indeterminate.\n\nNote that if an error occurs within a CONTINUE HANDLER block, it can be\nhandled by another HANDLER. However, a HANDLER which is already in the stack\n(that is, it has been called to handle an error and its execution didn\'t\nfinish yet) cannot handle new errors—this prevents endless loops. For example,\nsuppose that a stored procedure contains a CONTINUE HANDLER for SQLWARNING and\nanother CONTINUE HANDLER for NOT FOUND. At some point, a NOT FOUND error\noccurs, and the execution enters the NOT FOUND HANDLER. But within that\nhandler, a warning occurs, and the execution enters the SQLWARNING HANDLER. If\nanother NOT FOUND error occurs, it cannot be handled again by the NOT FOUND\nHANDLER, because its execution is not finished.\n\nWhen a DECLARE HANDLER block can handle more than one error condition, it may\nbe useful to know which errors occurred. To do so, you can use the GET\nDIAGNOSTICS statement.\n\nAn error that is handled by a DECLARE HANDLER construct can be issued again\nusing the RESIGNAL statement.\n\nBelow is an example using DECLARE HANDLER:\n\nCREATE TABLE test.t (s1 INT, PRIMARY KEY (s1));\n\nDELIMITER //\n\nCREATE PROCEDURE handlerdemo ( )\n BEGIN\n DECLARE CONTINUE HANDLER FOR SQLSTATE \'23000\' SET @x2 = 1;\n SET @x = 1;\n INSERT INTO test.t VALUES (1);\n SET @x = 2;\n INSERT INTO test.t VALUES (1);\n SET @x = 3;\n END;\n //\n\nDELIMITER ;\n\nCALL handlerdemo( );\n\nSELECT @x;\n+------+\n| @x |\n+------+\n| 3 |\n+------+\n\nURL: https://mariadb.com/kb/en/declare-handler/','','https://mariadb.com/kb/en/declare-handler/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (303,24,'DECLARE Variable','Syntax\n------\n\nDECLARE var_name [, var_name] ... [[ROW] TYPE OF]] type [DEFAULT value]\n\nDescription\n-----------\n\nThis statement is used to declare local variables within stored programs. To\nprovide a default value for the variable, include a DEFAULT clause. The value\ncan be specified as an expression (even subqueries are permitted); it need not\nbe a constant. If the DEFAULT clause is missing, the initial value is NULL.\n\nLocal variables are treated like stored routine parameters with respect to\ndata type and overflow checking. See CREATE PROCEDURE.\n\nLocal variables must be declared before CONDITIONs, CURSORs and HANDLERs.\n\nLocal variable names are not case sensitive.\n\nThe scope of a local variable is within the BEGIN ... END block where it is\ndeclared. The variable can be referred to in blocks nested within the\ndeclaring block, except those blocks that declare a variable with the same\nname.\n\nTYPE OF / ROW TYPE OF\n---------------------\n\nMariaDB starting with 10.3\n--------------------------\nTYPE OF and ROW TYPE OF anchored data types for stored routines were\nintroduced in MariaDB 10.3.\n\nAnchored data types allow a data type to be defined based on another object,\nsuch as a table row, rather than specifically set in the declaration. If the\nanchor object changes, so will the anchored data type. This can lead to\nroutines being easier to maintain, so that if the data type in the table is\nchanged, it will automatically be changed in the routine as well.\n\nVariables declared with ROW TYPE OF will have the same features as implicit\nROW variables. It is not possible to use ROW TYPE OF variables in a LIMIT\nclause.\n\nThe real data type of TYPE OF and ROW TYPE OF table_name will become known at\nthe very beginning of the stored routine call. ALTER TABLE or DROP TABLE\nstatements performed inside the current routine on the tables that appear in\nanchors won\'t affect the data type of the anchored variables, even if the\nvariable is declared after an ALTER TABLE or DROP TABLE statement.\n\nThe real data type of a ROW TYPE OF cursor_name variable will become known\nwhen execution enters into the block where the variable is declared. Data type\ninstantiation will happen only once. In a cursor ROW TYPE OF variable that is\ndeclared inside a loop, its data type will become known on the very first\niteration and won\'t change on further loop iterations.\n\nThe tables referenced in TYPE OF and ROW TYPE OF declarations will be checked\nfor existence at the beginning of the stored routine call. CREATE PROCEDURE or\nCREATE FUNCTION will not check the referenced tables for existence.\n\nExamples\n--------\n\nTYPE OF and ROW TYPE OF from MariaDB 10.3:\n\nDECLARE tmp TYPE OF t1.a; -- Get the data type from the column {{a}} in the\ntable {{t1}}\n\nDECLARE rec1 ROW TYPE OF t1; -- Get the row data type from the table {{t1}}\n\nDECLARE rec2 ROW TYPE OF cur1; -- Get the row data type from the cursor\n{{cur1}}\n\nURL: https://mariadb.com/kb/en/declare-variable/','','https://mariadb.com/kb/en/declare-variable/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (304,24,'FOR','MariaDB starting with 10.3\n--------------------------\nFOR loops were introduced in MariaDB 10.3.\n\nSyntax\n------\n\nInteger range FOR loop:\n\n[begin_label:]\nFOR var_name IN [ REVERSE ] lower_bound .. upper_bound\nDO statement_list\nEND FOR [ end_label ]\n\nExplicit cursor FOR loop\n\n[begin_label:]\nFOR record_name IN cursor_name [ ( cursor_actual_parameter_list)]\nDO statement_list\nEND FOR [ end_label ]\n\nExplicit cursor FOR loop (Oracle mode)\n\n[begin_label:]\nFOR record_name IN cursor_name [ ( cursor_actual_parameter_list)]\nLOOP\n statement_list\nEND LOOP [ end_label ]\n\nImplicit cursor FOR loop\n\n[begin_label:]\nFOR record_name IN ( select_statement )\nDO statement_list\nEND FOR [ end_label ]\n\nDescription\n-----------\n\nFOR loops allow code to be executed a fixed number of times.\n\nIn an integer range FOR loop, MariaDB will compare the lower bound and upper\nbound values, and assign the lower bound value to a counter. If REVERSE is not\nspecified, and the upper bound value is greater than or equal to the counter,\nthe counter will be incremented and the statement will continue, after which\nthe loop is entered again. If the upper bound value is greater than the\ncounter, the loop will be exited.\n\nIf REVERSE is specified, the counter is decremented, and the upper bound value\nneeds to be less than or equal for the loop to continue.\n\nExamples\n--------\n\nIntger range FOR loop:\n\nCREATE TABLE t1 (a INT);\n\nDELIMITER //\n\nFOR i IN 1..3\nDO\n INSERT INTO t1 VALUES (i);\nEND FOR;\n//\n\nDELIMITER ;\n\nSELECT * FROM t1;\n+------+\n| a |\n+------+\n| 1 |\n| 2 |\n| 3 |\n+------+\n\nREVERSE integer range FOR loop:\n\nCREATE OR REPLACE TABLE t1 (a INT);\n\nDELIMITER //\nFOR i IN REVERSE 4..12\n DO\n INSERT INTO t1 VALUES (i);\nEND FOR;\n//\nQuery OK, 9 rows affected (0.422 sec)\n\nDELIMITER ;\n\nSELECT * FROM t1;\n+------+\n| a |\n+------+\n| 12 |\n| 11 |\n| 10 |\n| 9 |\n| 8 |\n| 7 |\n| 6 |\n| 5 |\n| 4 |\n+------+\n\nExplicit cursor in Oracle mode:\n\nSET sql_mode=ORACLE;\n\nCREATE OR REPLACE TABLE t1 (a INT, b VARCHAR(32));\n\nINSERT INTO t1 VALUES (10,\'b0\');\nINSERT INTO t1 VALUES (11,\'b1\');\nINSERT INTO t1 VALUES (12,\'b2\');\n\nDELIMITER //\n\nCREATE OR REPLACE PROCEDURE p1(pa INT) AS \n CURSOR cur(va INT) IS\n SELECT a, b FROM t1 WHERE a=va;\nBEGIN\n FOR rec IN cur(pa)\n LOOP\n SELECT rec.a, rec.b;\n END LOOP;\nEND;\n//\n\nDELIMITER ;\n\nCALL p1(10);\n+-------+-------+\n| rec.a | rec.b |\n+-------+-------+\n| 10 | b0 |\n+-------+-------+\n\nCALL p1(11);\n+-------+-------+\n| rec.a | rec.b |\n+-------+-------+\n| 11 | b1 |\n+-------+-------+\n\nCALL p1(12);\n+-------+-------+\n| rec.a | rec.b |\n+-------+-------+\n| 12 | b2 |\n+-------+-------+\n\nCALL p1(13);\nQuery OK, 0 rows affected (0.000 sec)\n\nURL: https://mariadb.com/kb/en/for/','','https://mariadb.com/kb/en/for/'); -insert into help_topic (help_topic_id,help_category_id,name,description,example,url) values (305,24,'GOTO','MariaDB starting with 10.3\n--------------------------\nThe GOTO statement was introduced in MariaDB 10.3 for Oracle compatibility.\n\nSyntax\n------\n\nGOTO label\n\nDescription\n-----------\n\nThe GOTO statement causes the code to jump to the specified label, and\ncontinue operating from there. It is only accepted when in Oracle mode.\n\nExample\n-------\n\nSET sql_mode=ORACLE;\n\nDELIMITER //\n\nCREATE OR REPLACE PROCEDURE p1 AS\n\nBEGIN\n\nSELECT 1;\n GOTO label;\n SELECT 2;\n <