diff --git a/include/myisam.h b/include/myisam.h index 5c75f3fbe28cf..135b752ab1c11 100644 --- a/include/myisam.h +++ b/include/myisam.h @@ -224,6 +224,8 @@ struct st_mi_bit_buff; type, length, null_bit and null_pos */ +#define MI_NO_FIELD_NR 0xFFFFU + typedef struct st_columndef /* column information */ { enum en_fieldtype type; @@ -231,6 +233,11 @@ typedef struct st_columndef /* column information */ uint32 offset; /* Offset to position in row */ uint8 null_bit; /* If column may be 0 */ uint16 null_pos; /* position for null marker */ + /* + SQL column number for this recinfo entry, when known. + Entries that only represent packed/null marker bytes are MI_NO_FIELD_NR. + */ + uint16 fieldnr; #ifndef NOT_PACKED_DATABASES void (*unpack)(struct st_columndef *rec,struct st_mi_bit_buff *buff, diff --git a/mysql-test/suite/innodb/r/innodb_null_only.result b/mysql-test/suite/innodb/r/innodb_null_only.result new file mode 100644 index 0000000000000..a7f27421419cc --- /dev/null +++ b/mysql-test/suite/innodb/r/innodb_null_only.result @@ -0,0 +1,167 @@ +SET GLOBAL innodb_monitor_disable='module_buffer_page'; +SET GLOBAL innodb_monitor_reset_all='module_buffer_page'; +SET GLOBAL innodb_monitor_enable='module_buffer_page'; +CREATE TABLE t ( +id INT PRIMARY KEY, +b MEDIUMBLOB NULL, +c INT NOT NULL +) ENGINE=InnoDB; +INSERT INTO t VALUES +(1, REPEAT('a', 100000), 1), +(2, NULL, 2), +(3, REPEAT('b', 100000), 3), +(4, NULL, 4); +# Restart to flush buffer pool state before measuring blob page reads. +SET GLOBAL innodb_monitor_disable='module_buffer_page'; +SET GLOBAL innodb_monitor_reset_all='module_buffer_page'; +SET GLOBAL innodb_monitor_enable='module_buffer_page'; +FLUSH STATUS; +SELECT COUNT(*) FROM t WHERE b IS NULL; +COUNT(*) +2 +SELECT VARIABLE_VALUE > 0 AS null_only_used +FROM information_schema.session_status +WHERE VARIABLE_NAME='Handler_null_only_columns'; +null_only_used +1 +SELECT SUM(COUNT) AS blob_reads +FROM information_schema.innodb_metrics +WHERE NAME IN ('buffer_page_read_blob','buffer_page_read_zblob', +'buffer_page_read_zblob2'); +blob_reads +0 +SET GLOBAL innodb_monitor_disable='module_buffer_page'; +SET GLOBAL innodb_monitor_reset_all='module_buffer_page'; +SET GLOBAL innodb_monitor_enable='module_buffer_page'; +FLUSH STATUS; +SELECT COUNT(*) FROM t WHERE b <=> NULL; +COUNT(*) +2 +SELECT VARIABLE_VALUE > 0 AS null_only_used +FROM information_schema.session_status +WHERE VARIABLE_NAME='Handler_null_only_columns'; +null_only_used +1 +SELECT SUM(COUNT) AS blob_reads +FROM information_schema.innodb_metrics +WHERE NAME IN ('buffer_page_read_blob','buffer_page_read_zblob', +'buffer_page_read_zblob2'); +blob_reads +0 +FLUSH STATUS; +SELECT COUNT(*) +FROM t t1 JOIN t t2 +ON t1.id= t2.id AND t1.b IS NULL; +COUNT(*) +2 +SELECT VARIABLE_VALUE > 0 AS null_only_used +FROM information_schema.session_status +WHERE VARIABLE_NAME='Handler_null_only_columns'; +null_only_used +1 +FLUSH STATUS; +SELECT COUNT(*) +FROM t outer_t +WHERE EXISTS (SELECT 1 +FROM t inner_t +WHERE inner_t.id= outer_t.id +AND inner_t.b IS NULL); +COUNT(*) +2 +SELECT VARIABLE_VALUE > 0 AS null_only_used +FROM information_schema.session_status +WHERE VARIABLE_NAME='Handler_null_only_columns'; +null_only_used +1 +SET GLOBAL innodb_monitor_disable='module_buffer_page'; +SET GLOBAL innodb_monitor_reset_all='module_buffer_page'; +SET GLOBAL innodb_monitor_enable='module_buffer_page'; +FLUSH STATUS; +SELECT SUM(CRC32(b)) FROM t WHERE b IS NULL OR c = 3; +SUM(CRC32(b)) +4149198040 +SELECT VARIABLE_VALUE = 0 AS null_only_used +FROM information_schema.session_status +WHERE VARIABLE_NAME='Handler_null_only_columns'; +null_only_used +1 +SELECT SUM(COUNT) > 0 AS blob_reads +FROM information_schema.innodb_metrics +WHERE NAME IN ('buffer_page_read_blob','buffer_page_read_zblob', +'buffer_page_read_zblob2'); +blob_reads +1 +# Restart to force disk reads for the FOR UPDATE guardrail check. +SET GLOBAL innodb_monitor_disable='module_buffer_page'; +SET GLOBAL innodb_monitor_reset_all='module_buffer_page'; +SET GLOBAL innodb_monitor_enable='module_buffer_page'; +SELECT c FROM t WHERE b IS NULL FOR UPDATE; +c +2 +4 +COMMIT; +SELECT SUM(COUNT) > 0 AS blob_reads +FROM information_schema.innodb_metrics +WHERE NAME IN ('buffer_page_read_blob','buffer_page_read_zblob', +'buffer_page_read_zblob2'); +blob_reads +1 +DROP TABLE t; +# ICP check: c-range on leading key part and direct NULL predicate +# on second key part (d) should use index condition pushdown. +CREATE TABLE t_icp ( +id INT PRIMARY KEY, +c INT NOT NULL, +d VARCHAR(64) NULL, +payload INT NOT NULL, +KEY idx_cd (c, d) +) ENGINE=InnoDB; +INSERT INTO t_icp VALUES +(1, 1, 'x', 10), +(2, 2, NULL, 20), +(3, 3, 'y', 30), +(4, 4, NULL, 40); +SET optimizer_switch='index_condition_pushdown=on'; +FLUSH STATUS; +EXPLAIN SELECT payload +FROM t_icp FORCE INDEX (idx_cd) +WHERE c > 0 AND d IS NULL; +id select_type table type possible_keys key key_len ref rows Extra +# SIMPLE t_icp range idx_cd idx_cd # # # Using index condition +SELECT SUM(payload) +FROM t_icp FORCE INDEX (idx_cd) +WHERE c > 0 AND d IS NULL; +SUM(payload) +60 +SELECT VARIABLE_VALUE > 0 AS icp_used +FROM information_schema.session_status +WHERE VARIABLE_NAME='HANDLER_ICP_ATTEMPTS'; +icp_used +1 +DROP TABLE t_icp; +CREATE TABLE t_vcol ( +id INT PRIMARY KEY, +b MEDIUMBLOB NULL, +v VARBINARY(1) AS (SUBSTR(b, 1, 1)) VIRTUAL +) ENGINE=InnoDB; +INSERT INTO t_vcol (id, b) VALUES +(1, REPEAT('a', 100000)), +(2, NULL), +(3, REPEAT('b', 100000)), +(4, NULL); +# Restart to validate virtual-column guardrail with cold-page metrics. +SET GLOBAL innodb_monitor_disable='module_buffer_page'; +SET GLOBAL innodb_monitor_reset_all='module_buffer_page'; +SET GLOBAL innodb_monitor_enable='module_buffer_page'; +SELECT COUNT(*) FROM t_vcol WHERE v IS NULL; +COUNT(*) +2 +SELECT SUM(COUNT) > 0 AS blob_reads +FROM information_schema.innodb_metrics +WHERE NAME IN ('buffer_page_read_blob','buffer_page_read_zblob', +'buffer_page_read_zblob2'); +blob_reads +1 +DROP TABLE t_vcol; +SET GLOBAL innodb_monitor_disable='module_buffer_page'; +SET GLOBAL innodb_monitor_reset_all='module_buffer_page'; diff --git a/mysql-test/suite/innodb/t/innodb_null_only.test b/mysql-test/suite/innodb/t/innodb_null_only.test new file mode 100644 index 0000000000000..5fc4e0396f2cd --- /dev/null +++ b/mysql-test/suite/innodb/t/innodb_null_only.test @@ -0,0 +1,187 @@ +--source innodb_default_row_format.inc + +--disable_warnings +SET GLOBAL innodb_monitor_disable='module_buffer_page'; +SET GLOBAL innodb_monitor_reset_all='module_buffer_page'; +SET GLOBAL innodb_monitor_enable='module_buffer_page'; +--enable_warnings + +CREATE TABLE t ( + id INT PRIMARY KEY, + b MEDIUMBLOB NULL, + c INT NOT NULL +) ENGINE=InnoDB; + +INSERT INTO t VALUES + (1, REPEAT('a', 100000), 1), + (2, NULL, 2), + (3, REPEAT('b', 100000), 3), + (4, NULL, 4); + +--let $restart_noprint=2 +--echo # Restart to flush buffer pool state before measuring blob page reads. +--let $restart_parameters=--innodb-buffer-pool-load-at-startup=0 --innodb-buffer-pool-dump-at-shutdown=0 +--source include/restart_mysqld.inc +--let $restart_parameters= + +--disable_warnings +SET GLOBAL innodb_monitor_disable='module_buffer_page'; +SET GLOBAL innodb_monitor_reset_all='module_buffer_page'; +SET GLOBAL innodb_monitor_enable='module_buffer_page'; +--enable_warnings + +FLUSH STATUS; +SELECT COUNT(*) FROM t WHERE b IS NULL; +SELECT VARIABLE_VALUE > 0 AS null_only_used + FROM information_schema.session_status + WHERE VARIABLE_NAME='Handler_null_only_columns'; + +SELECT SUM(COUNT) AS blob_reads + FROM information_schema.innodb_metrics + WHERE NAME IN ('buffer_page_read_blob','buffer_page_read_zblob', + 'buffer_page_read_zblob2'); + +--disable_warnings +SET GLOBAL innodb_monitor_disable='module_buffer_page'; +SET GLOBAL innodb_monitor_reset_all='module_buffer_page'; +SET GLOBAL innodb_monitor_enable='module_buffer_page'; +--enable_warnings + +FLUSH STATUS; +SELECT COUNT(*) FROM t WHERE b <=> NULL; +SELECT VARIABLE_VALUE > 0 AS null_only_used + FROM information_schema.session_status + WHERE VARIABLE_NAME='Handler_null_only_columns'; + +SELECT SUM(COUNT) AS blob_reads + FROM information_schema.innodb_metrics + WHERE NAME IN ('buffer_page_read_blob','buffer_page_read_zblob', + 'buffer_page_read_zblob2'); + +FLUSH STATUS; +SELECT COUNT(*) + FROM t t1 JOIN t t2 + ON t1.id= t2.id AND t1.b IS NULL; +SELECT VARIABLE_VALUE > 0 AS null_only_used + FROM information_schema.session_status + WHERE VARIABLE_NAME='Handler_null_only_columns'; + +FLUSH STATUS; +SELECT COUNT(*) + FROM t outer_t + WHERE EXISTS (SELECT 1 + FROM t inner_t + WHERE inner_t.id= outer_t.id + AND inner_t.b IS NULL); +SELECT VARIABLE_VALUE > 0 AS null_only_used + FROM information_schema.session_status + WHERE VARIABLE_NAME='Handler_null_only_columns'; + +--disable_warnings +SET GLOBAL innodb_monitor_disable='module_buffer_page'; +SET GLOBAL innodb_monitor_reset_all='module_buffer_page'; +SET GLOBAL innodb_monitor_enable='module_buffer_page'; +--enable_warnings + +FLUSH STATUS; +SELECT SUM(CRC32(b)) FROM t WHERE b IS NULL OR c = 3; +SELECT VARIABLE_VALUE = 0 AS null_only_used + FROM information_schema.session_status + WHERE VARIABLE_NAME='Handler_null_only_columns'; + +SELECT SUM(COUNT) > 0 AS blob_reads + FROM information_schema.innodb_metrics + WHERE NAME IN ('buffer_page_read_blob','buffer_page_read_zblob', + 'buffer_page_read_zblob2'); + +--let $restart_parameters=--innodb-buffer-pool-load-at-startup=0 --innodb-buffer-pool-dump-at-shutdown=0 +--echo # Restart to force disk reads for the FOR UPDATE guardrail check. +--source include/restart_mysqld.inc +--let $restart_parameters= + +--disable_warnings +SET GLOBAL innodb_monitor_disable='module_buffer_page'; +SET GLOBAL innodb_monitor_reset_all='module_buffer_page'; +SET GLOBAL innodb_monitor_enable='module_buffer_page'; +--enable_warnings + +SELECT c FROM t WHERE b IS NULL FOR UPDATE; +COMMIT; + +SELECT SUM(COUNT) > 0 AS blob_reads + FROM information_schema.innodb_metrics + WHERE NAME IN ('buffer_page_read_blob','buffer_page_read_zblob', + 'buffer_page_read_zblob2'); + +DROP TABLE t; + +--echo # ICP check: c-range on leading key part and direct NULL predicate +--echo # on second key part (d) should use index condition pushdown. +CREATE TABLE t_icp ( + id INT PRIMARY KEY, + c INT NOT NULL, + d VARCHAR(64) NULL, + payload INT NOT NULL, + KEY idx_cd (c, d) +) ENGINE=InnoDB; + +INSERT INTO t_icp VALUES + (1, 1, 'x', 10), + (2, 2, NULL, 20), + (3, 3, 'y', 30), + (4, 4, NULL, 40); + +SET optimizer_switch='index_condition_pushdown=on'; +FLUSH STATUS; + +--replace_column 1 # 7 # 8 # 9 # +EXPLAIN SELECT payload + FROM t_icp FORCE INDEX (idx_cd) + WHERE c > 0 AND d IS NULL; + +SELECT SUM(payload) + FROM t_icp FORCE INDEX (idx_cd) + WHERE c > 0 AND d IS NULL; + +SELECT VARIABLE_VALUE > 0 AS icp_used + FROM information_schema.session_status + WHERE VARIABLE_NAME='HANDLER_ICP_ATTEMPTS'; + +DROP TABLE t_icp; + +CREATE TABLE t_vcol ( + id INT PRIMARY KEY, + b MEDIUMBLOB NULL, + v VARBINARY(1) AS (SUBSTR(b, 1, 1)) VIRTUAL +) ENGINE=InnoDB; + +INSERT INTO t_vcol (id, b) VALUES + (1, REPEAT('a', 100000)), + (2, NULL), + (3, REPEAT('b', 100000)), + (4, NULL); + +--let $restart_parameters=--innodb-buffer-pool-load-at-startup=0 --innodb-buffer-pool-dump-at-shutdown=0 +--echo # Restart to validate virtual-column guardrail with cold-page metrics. +--source include/restart_mysqld.inc +--let $restart_parameters= + +--disable_warnings +SET GLOBAL innodb_monitor_disable='module_buffer_page'; +SET GLOBAL innodb_monitor_reset_all='module_buffer_page'; +SET GLOBAL innodb_monitor_enable='module_buffer_page'; +--enable_warnings + +SELECT COUNT(*) FROM t_vcol WHERE v IS NULL; + +SELECT SUM(COUNT) > 0 AS blob_reads + FROM information_schema.innodb_metrics + WHERE NAME IN ('buffer_page_read_blob','buffer_page_read_zblob', + 'buffer_page_read_zblob2'); + +DROP TABLE t_vcol; + +--disable_warnings +SET GLOBAL innodb_monitor_disable='module_buffer_page'; +SET GLOBAL innodb_monitor_reset_all='module_buffer_page'; +--enable_warnings diff --git a/mysql-test/suite/maria/maria_null_only.result b/mysql-test/suite/maria/maria_null_only.result new file mode 100644 index 0000000000000..728518cc338f1 --- /dev/null +++ b/mysql-test/suite/maria/maria_null_only.result @@ -0,0 +1,126 @@ +DROP TABLE IF EXISTS t_dyn, t_page, t_v; +CREATE TABLE t_dyn ( +id INT PRIMARY KEY, +b MEDIUMBLOB NULL, +c INT +) ENGINE=Aria ROW_FORMAT=DYNAMIC TRANSACTIONAL=0; +INSERT INTO t_dyn VALUES +(1, REPEAT('a', 200000), 1), +(2, NULL, 2), +(3, REPEAT('b', 200000), 3), +(4, NULL, 4); +FLUSH STATUS; +SELECT COUNT(*) FROM t_dyn WHERE b IS NULL; +COUNT(*) +2 +SELECT VARIABLE_VALUE > 0 AS null_only_used +FROM information_schema.session_status +WHERE VARIABLE_NAME='Handler_null_only_columns'; +null_only_used +1 +FLUSH STATUS; +SELECT COUNT(*) FROM t_dyn WHERE b <=> NULL; +COUNT(*) +2 +SELECT VARIABLE_VALUE > 0 AS null_only_used +FROM information_schema.session_status +WHERE VARIABLE_NAME='Handler_null_only_columns'; +null_only_used +1 +FLUSH STATUS; +SELECT COUNT(*) +FROM t_dyn d1 JOIN t_dyn d2 +ON d1.id= d2.id AND d1.b IS NULL; +COUNT(*) +2 +SELECT VARIABLE_VALUE > 0 AS null_only_used +FROM information_schema.session_status +WHERE VARIABLE_NAME='Handler_null_only_columns'; +null_only_used +1 +FLUSH STATUS; +SELECT COUNT(*) +FROM t_dyn outer_t +WHERE EXISTS (SELECT 1 +FROM t_dyn inner_t +WHERE inner_t.id= outer_t.id +AND inner_t.b IS NULL); +COUNT(*) +2 +SELECT VARIABLE_VALUE > 0 AS null_only_used +FROM information_schema.session_status +WHERE VARIABLE_NAME='Handler_null_only_columns'; +null_only_used +1 +FLUSH STATUS; +SELECT id, IFNULL(LENGTH(b), -1) AS len +FROM t_dyn +WHERE b IS NULL OR c= 3 +ORDER BY id; +id len +2 -1 +3 200000 +4 -1 +SELECT VARIABLE_VALUE = 0 AS null_only_used +FROM information_schema.session_status +WHERE VARIABLE_NAME='Handler_null_only_columns'; +null_only_used +1 +CREATE TABLE t_page ( +id INT PRIMARY KEY, +b MEDIUMBLOB NULL, +c INT +) ENGINE=Aria ROW_FORMAT=PAGE TRANSACTIONAL=0; +INSERT INTO t_page VALUES +(1, REPEAT('c', 200000), 1), +(2, NULL, 2), +(3, REPEAT('d', 200000), 3), +(4, NULL, 4); +FLUSH STATUS; +SELECT COUNT(*) FROM t_page WHERE b IS NULL; +COUNT(*) +2 +SELECT VARIABLE_VALUE > 0 AS null_only_used +FROM information_schema.session_status +WHERE VARIABLE_NAME='Handler_null_only_columns'; +null_only_used +1 +FLUSH STATUS; +SELECT COUNT(*) FROM t_page WHERE b <=> NULL; +COUNT(*) +2 +SELECT VARIABLE_VALUE > 0 AS null_only_used +FROM information_schema.session_status +WHERE VARIABLE_NAME='Handler_null_only_columns'; +null_only_used +1 +FLUSH STATUS; +SELECT id, IFNULL(LENGTH(b), -1) AS len +FROM t_page +WHERE b IS NULL OR c= 3 +ORDER BY id; +id len +2 -1 +3 200000 +4 -1 +SELECT VARIABLE_VALUE = 0 AS null_only_used +FROM information_schema.session_status +WHERE VARIABLE_NAME='Handler_null_only_columns'; +null_only_used +1 +CREATE TABLE t_v ( +id INT PRIMARY KEY, +b MEDIUMBLOB NULL, +v VARBINARY(1) AS (SUBSTR(b, 1, 1)) VIRTUAL +) ENGINE=Aria ROW_FORMAT=PAGE TRANSACTIONAL=0; +INSERT INTO t_v (id, b) VALUES +(1, REPEAT('x', 200000)), +(2, NULL), +(3, REPEAT('y', 200000)), +(4, NULL); +SELECT COUNT(*) FROM t_v WHERE v IS NULL; +COUNT(*) +2 +DROP TABLE t_v; +DROP TABLE t_page; +DROP TABLE t_dyn; diff --git a/mysql-test/suite/maria/maria_null_only.test b/mysql-test/suite/maria/maria_null_only.test new file mode 100644 index 0000000000000..356545a0753cc --- /dev/null +++ b/mysql-test/suite/maria/maria_null_only.test @@ -0,0 +1,108 @@ +--source include/have_maria.inc + +--disable_warnings +DROP TABLE IF EXISTS t_dyn, t_page, t_v; +--enable_warnings + +CREATE TABLE t_dyn ( + id INT PRIMARY KEY, + b MEDIUMBLOB NULL, + c INT +) ENGINE=Aria ROW_FORMAT=DYNAMIC TRANSACTIONAL=0; + +INSERT INTO t_dyn VALUES + (1, REPEAT('a', 200000), 1), + (2, NULL, 2), + (3, REPEAT('b', 200000), 3), + (4, NULL, 4); + +FLUSH STATUS; +SELECT COUNT(*) FROM t_dyn WHERE b IS NULL; +SELECT VARIABLE_VALUE > 0 AS null_only_used + FROM information_schema.session_status + WHERE VARIABLE_NAME='Handler_null_only_columns'; + +FLUSH STATUS; +SELECT COUNT(*) FROM t_dyn WHERE b <=> NULL; +SELECT VARIABLE_VALUE > 0 AS null_only_used + FROM information_schema.session_status + WHERE VARIABLE_NAME='Handler_null_only_columns'; + +FLUSH STATUS; +SELECT COUNT(*) + FROM t_dyn d1 JOIN t_dyn d2 + ON d1.id= d2.id AND d1.b IS NULL; +SELECT VARIABLE_VALUE > 0 AS null_only_used + FROM information_schema.session_status + WHERE VARIABLE_NAME='Handler_null_only_columns'; + +FLUSH STATUS; +SELECT COUNT(*) + FROM t_dyn outer_t + WHERE EXISTS (SELECT 1 + FROM t_dyn inner_t + WHERE inner_t.id= outer_t.id + AND inner_t.b IS NULL); +SELECT VARIABLE_VALUE > 0 AS null_only_used + FROM information_schema.session_status + WHERE VARIABLE_NAME='Handler_null_only_columns'; + +FLUSH STATUS; +SELECT id, IFNULL(LENGTH(b), -1) AS len + FROM t_dyn + WHERE b IS NULL OR c= 3 + ORDER BY id; +SELECT VARIABLE_VALUE = 0 AS null_only_used + FROM information_schema.session_status + WHERE VARIABLE_NAME='Handler_null_only_columns'; + +CREATE TABLE t_page ( + id INT PRIMARY KEY, + b MEDIUMBLOB NULL, + c INT +) ENGINE=Aria ROW_FORMAT=PAGE TRANSACTIONAL=0; + +INSERT INTO t_page VALUES + (1, REPEAT('c', 200000), 1), + (2, NULL, 2), + (3, REPEAT('d', 200000), 3), + (4, NULL, 4); + +FLUSH STATUS; +SELECT COUNT(*) FROM t_page WHERE b IS NULL; +SELECT VARIABLE_VALUE > 0 AS null_only_used + FROM information_schema.session_status + WHERE VARIABLE_NAME='Handler_null_only_columns'; + +FLUSH STATUS; +SELECT COUNT(*) FROM t_page WHERE b <=> NULL; +SELECT VARIABLE_VALUE > 0 AS null_only_used + FROM information_schema.session_status + WHERE VARIABLE_NAME='Handler_null_only_columns'; + +FLUSH STATUS; +SELECT id, IFNULL(LENGTH(b), -1) AS len + FROM t_page + WHERE b IS NULL OR c= 3 + ORDER BY id; +SELECT VARIABLE_VALUE = 0 AS null_only_used + FROM information_schema.session_status + WHERE VARIABLE_NAME='Handler_null_only_columns'; + +CREATE TABLE t_v ( + id INT PRIMARY KEY, + b MEDIUMBLOB NULL, + v VARBINARY(1) AS (SUBSTR(b, 1, 1)) VIRTUAL +) ENGINE=Aria ROW_FORMAT=PAGE TRANSACTIONAL=0; + +INSERT INTO t_v (id, b) VALUES + (1, REPEAT('x', 200000)), + (2, NULL), + (3, REPEAT('y', 200000)), + (4, NULL); + +SELECT COUNT(*) FROM t_v WHERE v IS NULL; + +DROP TABLE t_v; +DROP TABLE t_page; +DROP TABLE t_dyn; diff --git a/mysql-test/suite/myisam/r/myisam_null_only.result b/mysql-test/suite/myisam/r/myisam_null_only.result new file mode 100644 index 0000000000000..db5e1ca706b30 --- /dev/null +++ b/mysql-test/suite/myisam/r/myisam_null_only.result @@ -0,0 +1,89 @@ +CREATE TABLE t ( +id INT PRIMARY KEY, +b MEDIUMBLOB NULL, +c INT +) ENGINE=MyISAM; +INSERT INTO t VALUES +(1, REPEAT('a', 10000), 1), +(2, NULL, 2), +(3, REPEAT('b', 10000), 3), +(4, NULL, 4); +FLUSH STATUS; +SELECT COUNT(*) FROM t WHERE b IS NULL; +COUNT(*) +2 +SELECT VARIABLE_VALUE > 0 AS null_only_used +FROM information_schema.session_status +WHERE VARIABLE_NAME='Handler_null_only_columns'; +null_only_used +1 +FLUSH STATUS; +SELECT COUNT(*) FROM t WHERE b <=> NULL; +COUNT(*) +2 +SELECT VARIABLE_VALUE > 0 AS null_only_used +FROM information_schema.session_status +WHERE VARIABLE_NAME='Handler_null_only_columns'; +null_only_used +1 +FLUSH STATUS; +SELECT COUNT(*) +FROM t t1 JOIN t t2 +ON t1.id= t2.id AND t1.b IS NULL; +COUNT(*) +2 +SELECT VARIABLE_VALUE > 0 AS null_only_used +FROM information_schema.session_status +WHERE VARIABLE_NAME='Handler_null_only_columns'; +null_only_used +1 +FLUSH STATUS; +SELECT COUNT(*) +FROM t outer_t +WHERE EXISTS (SELECT 1 +FROM t inner_t +WHERE inner_t.id= outer_t.id +AND inner_t.b IS NULL); +COUNT(*) +2 +SELECT VARIABLE_VALUE > 0 AS null_only_used +FROM information_schema.session_status +WHERE VARIABLE_NAME='Handler_null_only_columns'; +null_only_used +1 +FLUSH STATUS; +SELECT IFNULL(SUM(LENGTH(b)), -1) AS total_len +FROM t WHERE b IS NULL OR c = 3; +total_len +10000 +SELECT VARIABLE_VALUE = 0 AS null_only_used +FROM information_schema.session_status +WHERE VARIABLE_NAME='Handler_null_only_columns'; +null_only_used +1 +SELECT id, IFNULL(LENGTH(b), -1) AS len +FROM t WHERE b IS NULL OR c = 3 +ORDER BY id; +id len +2 -1 +3 10000 +4 -1 +SELECT id FROM t WHERE b IS NULL FOR UPDATE; +id +2 +4 +CREATE TABLE t_v ( +id INT PRIMARY KEY, +b MEDIUMBLOB NULL, +v VARBINARY(1) AS (SUBSTR(b, 1, 1)) VIRTUAL +) ENGINE=MyISAM; +INSERT INTO t_v (id, b) VALUES +(1, REPEAT('a', 10000)), +(2, NULL), +(3, REPEAT('b', 10000)), +(4, NULL); +SELECT COUNT(*) FROM t_v WHERE v IS NULL; +COUNT(*) +2 +DROP TABLE t_v; +DROP TABLE t; diff --git a/mysql-test/suite/myisam/t/myisam_null_only.test b/mysql-test/suite/myisam/t/myisam_null_only.test new file mode 100644 index 0000000000000..c46442c1c4cc8 --- /dev/null +++ b/mysql-test/suite/myisam/t/myisam_null_only.test @@ -0,0 +1,71 @@ +CREATE TABLE t ( + id INT PRIMARY KEY, + b MEDIUMBLOB NULL, + c INT +) ENGINE=MyISAM; + +INSERT INTO t VALUES + (1, REPEAT('a', 10000), 1), + (2, NULL, 2), + (3, REPEAT('b', 10000), 3), + (4, NULL, 4); + +FLUSH STATUS; +SELECT COUNT(*) FROM t WHERE b IS NULL; +SELECT VARIABLE_VALUE > 0 AS null_only_used + FROM information_schema.session_status + WHERE VARIABLE_NAME='Handler_null_only_columns'; + +FLUSH STATUS; +SELECT COUNT(*) FROM t WHERE b <=> NULL; +SELECT VARIABLE_VALUE > 0 AS null_only_used + FROM information_schema.session_status + WHERE VARIABLE_NAME='Handler_null_only_columns'; + +FLUSH STATUS; +SELECT COUNT(*) + FROM t t1 JOIN t t2 + ON t1.id= t2.id AND t1.b IS NULL; +SELECT VARIABLE_VALUE > 0 AS null_only_used + FROM information_schema.session_status + WHERE VARIABLE_NAME='Handler_null_only_columns'; + +FLUSH STATUS; +SELECT COUNT(*) + FROM t outer_t + WHERE EXISTS (SELECT 1 + FROM t inner_t + WHERE inner_t.id= outer_t.id + AND inner_t.b IS NULL); +SELECT VARIABLE_VALUE > 0 AS null_only_used + FROM information_schema.session_status + WHERE VARIABLE_NAME='Handler_null_only_columns'; + +FLUSH STATUS; +SELECT IFNULL(SUM(LENGTH(b)), -1) AS total_len + FROM t WHERE b IS NULL OR c = 3; +SELECT VARIABLE_VALUE = 0 AS null_only_used + FROM information_schema.session_status + WHERE VARIABLE_NAME='Handler_null_only_columns'; + +SELECT id, IFNULL(LENGTH(b), -1) AS len + FROM t WHERE b IS NULL OR c = 3 + ORDER BY id; +SELECT id FROM t WHERE b IS NULL FOR UPDATE; + +CREATE TABLE t_v ( + id INT PRIMARY KEY, + b MEDIUMBLOB NULL, + v VARBINARY(1) AS (SUBSTR(b, 1, 1)) VIRTUAL +) ENGINE=MyISAM; + +INSERT INTO t_v (id, b) VALUES + (1, REPEAT('a', 10000)), + (2, NULL), + (3, REPEAT('b', 10000)), + (4, NULL); + +SELECT COUNT(*) FROM t_v WHERE v IS NULL; + +DROP TABLE t_v; +DROP TABLE t; diff --git a/sql/handler.h b/sql/handler.h index 733ab464fd48f..46f6fd89e7e88 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -192,6 +192,8 @@ enum chf_create_flags { #define HA_HAS_OLD_CHECKSUM (1ULL << 24) /* Table data are stored in separate files (for lower_case_table_names) */ #define HA_FILE_BASED (1ULL << 26) +/* Reuses legacy bit 27 (HA_NO_VARCHAR), removed as unused. */ +#define HA_CAN_NULL_ONLY (1ULL << 27) #define HA_CAN_BIT_FIELD (1ULL << 28) /* supports bit fields */ #define HA_NEED_READ_RANGE_BUFFER (1ULL << 29) /* for read_multi_range */ #define HA_ANY_INDEX_MAY_BE_UNIQUE (1ULL << 30) diff --git a/sql/item.cc b/sql/item.cc index 3170f7bf27b4d..dedaf327a47ad 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -926,6 +926,14 @@ bool Item_field::register_field_in_bitmap(void *arg) return 0; } +bool Item_field::clear_null_only_fields_processor(void *arg) +{ + (void) arg; + if (field && field->table) + bitmap_clear_bit(&field->table->null_set, field->field_index); + return 0; +} + /* Mark field in write_map diff --git a/sql/item.h b/sql/item.h index 30899435f71d0..72b5b3f20256b 100644 --- a/sql/item.h +++ b/sql/item.h @@ -820,6 +820,7 @@ typedef uint8 item_walk_flags; const item_walk_flags WALK_SUBQUERY= 1; const item_walk_flags WALK_NO_CACHE_PROCESS= (1<<1); const item_walk_flags WALK_NO_REF= (1<<2); +const item_walk_flags WALK_SKIP_NULL_PREDICATE_ARGS= (1<<3); class Item :public Value_source, @@ -2282,6 +2283,8 @@ class Item :public Value_source, virtual bool register_field_in_read_map(void *arg) { return 0; } virtual bool register_field_in_write_map(void *arg) { return 0; } virtual bool register_field_in_bitmap(void *arg) { return 0; } + virtual bool mark_null_only_fields_processor(void *arg) { return 0; } + virtual bool clear_null_only_fields_processor(void *arg) { return 0; } virtual bool update_table_bitmaps_processor(void *arg) { return 0; } /* Compute the intersection of index coverings of all fields in the @@ -3960,6 +3963,7 @@ class Item_field :public Item_ident, bool register_field_in_read_map(void *arg) override; bool register_field_in_write_map(void *arg) override; bool register_field_in_bitmap(void *arg) override; + bool clear_null_only_fields_processor(void *arg) override; bool intersect_field_part_of_key(void *arg) override; bool check_partition_func_processor(void *) override {return false;} bool post_fix_fields_part_expr_processor(void *bool_arg) override; diff --git a/sql/item_cmpfunc.h b/sql/item_cmpfunc.h index 7b7997cc94b87..09db4556d4a51 100644 --- a/sql/item_cmpfunc.h +++ b/sql/item_cmpfunc.h @@ -817,6 +817,9 @@ class Item_func_nop_all :public Item_func_not_all { return get_item_copy(thd, this); } }; +static inline Field *null_predicate_field(Item *item); +static inline bool mark_null_only_field(Field *field, void *arg); +static inline Field *null_safe_equal_field(Item *left, Item *right); class Item_func_eq :public Item_bool_rowready_func2 { @@ -895,6 +898,19 @@ class Item_func_equal final :public Item_bool_rowready_func2 // block standard processor for never null bool add_maybe_null_after_ora_join_processor(void *arg) override { return 0; } + bool mark_null_only_fields_processor(void *arg) override + { + mark_null_only_field(null_safe_equal_field(args[0], args[1]), arg); + return 0; + } + bool walk(Item_processor processor, void *arg, + item_walk_flags flags) override + { + if ((flags & WALK_SKIP_NULL_PREDICATE_ARGS) + && null_safe_equal_field(args[0], args[1])) + return (this->*processor)(arg); + return Item_func_or_sum::walk(processor, arg, flags); + } }; @@ -2859,6 +2875,41 @@ class Item_func_null_predicate :public Item_bool_func Item* vcol_subst_transformer(THD *thd, uchar *arg) override; }; +static inline Field *null_predicate_field(Item *item) +{ + Item *real_item= item->real_item(); + if (real_item->type() != Item::FIELD_ITEM) + return NULL; + Item_field *item_field= static_cast(real_item); + Field *field= item_field->field; + if (!field || !field->stored_in_db()) + return NULL; + return field; +} + +static inline bool mark_null_only_field(Field *field, void *arg) +{ + if (!field || !field->table || !field->table->file + || !field->real_maybe_null()) + return false; + bitmap_set_bit(&field->table->null_set, field->field_index); + if (arg) + *static_cast(arg)= true; + return true; +} + +static inline Field *null_safe_equal_field(Item *left, Item *right) +{ + Item *left_real= left->real_item(); + Item *right_real= right->real_item(); + + if (left_real->type() == Item::NULL_ITEM) + return null_predicate_field(right); + if (right_real->type() == Item::NULL_ITEM) + return null_predicate_field(left); + return NULL; +} + class Item_func_isnull :public Item_func_null_predicate { @@ -2910,6 +2961,19 @@ class Item_func_isnull :public Item_func_null_predicate Item *neg_transformer(THD *thd) override; Item *do_get_copy(THD *thd) const override { return get_item_copy(thd, this); } + bool mark_null_only_fields_processor(void *arg) override + { + mark_null_only_field(null_predicate_field(args[0]), arg); + return 0; + } + bool walk(Item_processor processor, void *arg, + item_walk_flags flags) override + { + if ((flags & WALK_SKIP_NULL_PREDICATE_ARGS) + && null_predicate_field(args[0])) + return (this->*processor)(arg); + return Item_func_or_sum::walk(processor, arg, flags); + } }; /* Functions used by HAVING for rewriting IN subquery */ @@ -2963,6 +3027,19 @@ class Item_func_isnotnull :public Item_func_null_predicate void print(String *str, enum_query_type query_type) override; Item *do_get_copy(THD *thd) const override { return get_item_copy(thd, this); } + bool mark_null_only_fields_processor(void *arg) override + { + mark_null_only_field(null_predicate_field(args[0]), arg); + return 0; + } + bool walk(Item_processor processor, void *arg, + item_walk_flags flags) override + { + if ((flags & WALK_SKIP_NULL_PREDICATE_ARGS) + && null_predicate_field(args[0])) + return (this->*processor)(arg); + return Item_func_or_sum::walk(processor, arg, flags); + } }; diff --git a/sql/json_table.cc b/sql/json_table.cc index ffcc99096c6dd..f2aa9888c4aaf 100644 --- a/sql/json_table.cc +++ b/sql/json_table.cc @@ -914,9 +914,14 @@ TABLE *create_table_for_function(THD *thd, TABLE_LIST *sql_table) } sql_table->schema_table_name.length= 0; + uint bitmap_size= bitmap_buffer_size(field_count); my_bitmap_map* bitmaps= - (my_bitmap_map*) thd->alloc(bitmap_buffer_size(field_count)); + (my_bitmap_map*) thd->alloc(bitmap_size * 2); my_bitmap_init(&table->def_read_set, (my_bitmap_map*) bitmaps, field_count); + my_bitmap_init(&table->null_set, + (my_bitmap_map*)((uchar*) bitmaps + bitmap_size), + field_count); + bitmap_clear_all(&table->null_set); table->read_set= &table->def_read_set; bitmap_clear_all(table->read_set); table->alias_name_used= true; diff --git a/sql/mysqld.cc b/sql/mysqld.cc index c84329df86644..1b9d48598590a 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -7641,6 +7641,7 @@ SHOW_VAR status_vars[]= { {"Handler_mrr_init", (char*) offsetof(STATUS_VAR, ha_mrr_init_count), SHOW_LONG_STATUS}, {"Handler_mrr_key_refills", (char*) offsetof(STATUS_VAR, ha_mrr_key_refills_count), SHOW_LONG_STATUS}, {"Handler_mrr_rowid_refills",(char*) offsetof(STATUS_VAR, ha_mrr_rowid_refills_count), SHOW_LONG_STATUS}, + {"Handler_null_only_columns",(char*) offsetof(STATUS_VAR, ha_null_only_columns), SHOW_LONG_STATUS}, {"Handler_prepare", (char*) offsetof(STATUS_VAR, ha_prepare_count), SHOW_LONG_STATUS}, {"Handler_read_first", (char*) offsetof(STATUS_VAR, ha_read_first_count), SHOW_LONG_STATUS}, {"Handler_read_key", (char*) offsetof(STATUS_VAR, ha_read_key_count), SHOW_LONG_STATUS}, diff --git a/sql/sql_class.h b/sql/sql_class.h index 767495be8f788..72fb1e065114e 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -1012,6 +1012,7 @@ typedef struct system_status_var ulong ha_prepare_count; ulong ha_icp_attempts; ulong ha_icp_match; + ulong ha_null_only_columns; ulong ha_discover_count; ulong ha_savepoint_count; ulong ha_savepoint_rollback_count; diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 73d97861325ef..a2641513b3116 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -21,6 +21,7 @@ #include "mariadb.h" #include "sql_priv.h" #include "sql_class.h" // sql_lex.h: SQLCOM_END +#include "handler.h" #include "sql_lex.h" #include "sql_parse.h" // add_to_list #include "item_create.h" @@ -5491,6 +5492,149 @@ void SELECT_LEX::mark_as_belong_to_derived(TABLE_LIST *derived) in the leaf_tables list and of the conds expression (if any). */ +static bool table_supports_null_only(const TABLE_LIST *table_list) +{ + return table_list && table_list->table && table_list->table->file && + (table_list->table->file->ha_table_flags() & HA_CAN_NULL_ONLY); +} + +static void mark_null_only_in_expr(Item *expr, bool *any_marked) +{ + if (expr) + expr->walk(&Item::mark_null_only_fields_processor, any_marked, + WALK_SUBQUERY); +} + +static void clear_null_only_in_expr(Item *expr) +{ + if (expr) + expr->walk(&Item::clear_null_only_fields_processor, 0, + WALK_SUBQUERY | WALK_SKIP_NULL_PREDICATE_ARGS); +} + +static void update_null_only_set(SELECT_LEX *select) +{ + TABLE_LIST *tl; + List_iterator ti(select->leaf_tables); + bool any_supported= false; + + while ((tl= ti++)) + { + if (table_supports_null_only(tl)) + { + any_supported= true; + break; + } + } + + if (!any_supported) + return; + + ti.rewind(); + while ((tl= ti++)) + { + if (tl->table) + bitmap_clear_all(&tl->table->null_set); + } + + bool any_marked= false; + + ti.rewind(); + while ((tl= ti++)) + { + if (tl->on_expr && !is_eliminated_table(select->join->eliminated_tables, tl)) + mark_null_only_in_expr(tl->on_expr, &any_marked); + + if (tl->jtbm_subselect) + { + Item *left_expr= tl->jtbm_subselect->left_exp(); + mark_null_only_in_expr(left_expr, &any_marked); + } + + for (TABLE_LIST *embedding= tl->embedding; embedding; + embedding= embedding->embedding) + { + if (embedding->on_expr && + embedding->nested_join->join_list.head() == tl && + !is_eliminated_table(select->join->eliminated_tables, embedding)) + { + mark_null_only_in_expr(embedding->on_expr, &any_marked); + } + } + } + + mark_null_only_in_expr(select->join->conds, &any_marked); + mark_null_only_in_expr(select->join->having, &any_marked); + + if (!any_marked) + return; + + ti.rewind(); + while ((tl= ti++)) + { + if (tl->on_expr && !is_eliminated_table(select->join->eliminated_tables, tl)) + clear_null_only_in_expr(tl->on_expr); + + if (tl->jtbm_subselect) + { + Item *left_expr= tl->jtbm_subselect->left_exp(); + clear_null_only_in_expr(left_expr); + } + + for (TABLE_LIST *embedding= tl->embedding; embedding; + embedding= embedding->embedding) + { + if (embedding->on_expr && + embedding->nested_join->join_list.head() == tl && + !is_eliminated_table(select->join->eliminated_tables, embedding)) + { + clear_null_only_in_expr(embedding->on_expr); + } + } + } + + clear_null_only_in_expr(select->join->conds); + clear_null_only_in_expr(select->join->having); + + Item *item; + List_iterator_fast it(select->join->all_fields); + while ((item= it++)) + item->walk(&Item::clear_null_only_fields_processor, 0, + WALK_SUBQUERY | WALK_SKIP_NULL_PREDICATE_ARGS); + + Item_outer_ref *ref; + List_iterator_fast ref_it(select->inner_refs_list); + while ((ref= ref_it++)) + { + item= ref->outer_ref; + item->walk(&Item::clear_null_only_fields_processor, 0, + WALK_SUBQUERY | WALK_SKIP_NULL_PREDICATE_ARGS); + } + + for (ORDER *order= select->group_list.first; order; order= order->next) + (*order->item)->walk(&Item::clear_null_only_fields_processor, 0, + WALK_SUBQUERY | WALK_SKIP_NULL_PREDICATE_ARGS); + if (!select->master_unit()->is_unit_op() || + select->master_unit()->global_parameters() != select) + { + for (ORDER *order= select->order_list.first; order; order= order->next) + (*order->item)->walk(&Item::clear_null_only_fields_processor, 0, + WALK_SUBQUERY | WALK_SKIP_NULL_PREDICATE_ARGS); + } + + ulong null_only_columns= 0; + ti.rewind(); + while ((tl= ti++)) + { + if (table_supports_null_only(tl)) + null_only_columns+= bitmap_bits_set(&tl->table->null_set); + } + + if (null_only_columns) + status_var_add(current_thd->status_var.ha_null_only_columns, + null_only_columns); +} + void SELECT_LEX::update_used_tables() { TABLE_LIST *tl; @@ -5615,6 +5759,7 @@ void SELECT_LEX::update_used_tables() (*order->item)->update_used_tables(); } join->result->update_used_tables(); + update_null_only_set(this); } diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 8316ef8f8c3a3..4cecd3d4a7ec6 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -21923,6 +21923,10 @@ setup_tmp_table_column_bitmaps(TABLE *table, uchar *bitmaps, uint field_count) my_bitmap_init(&table->cond_set, (my_bitmap_map*) bitmaps, field_count); bitmaps+= bitmap_size; + my_bitmap_init(&table->null_set, + (my_bitmap_map*) bitmaps, field_count); + bitmaps+= bitmap_size; + bitmap_clear_all(&table->null_set); my_bitmap_init(&table->has_value_set, (my_bitmap_map*) bitmaps, field_count); /* write_set and all_set are copies of read_set */ @@ -22133,7 +22137,7 @@ TABLE *Create_tmp_table::start(THD *thd, &tmpname, (uint) strlen(path)+1, &m_group_buff, (m_group && ! m_using_unique_constraint ? param->group_length : 0), - &m_bitmaps, bitmap_buffer_size(field_count)*6, + &m_bitmaps, bitmap_buffer_size(field_count)*7, &const_key_parts, sizeof(*const_key_parts), NullS)) { diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 6636c44b60892..e7ea7d6ec5a29 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -9075,9 +9075,14 @@ TABLE *create_schema_table(THD *thd, TABLE_LIST *table_list) table_list->alias, !need_all_fields, keep_row_order))) DBUG_RETURN(0); + uint bitmap_size= bitmap_buffer_size(field_count); my_bitmap_map* bitmaps= - (my_bitmap_map*) thd->alloc(bitmap_buffer_size(field_count)); + (my_bitmap_map*) thd->alloc(bitmap_size * 2); my_bitmap_init(&table->def_read_set, bitmaps, field_count); + my_bitmap_init(&table->null_set, + (my_bitmap_map*)((uchar*) bitmaps + bitmap_size), + field_count); + bitmap_clear_all(&table->null_set); table->read_set= &table->def_read_set; bitmap_clear_all(table->read_set); table_list->schema_table_param= tmp_table_param; diff --git a/sql/table.cc b/sql/table.cc index 254450a671b2e..45a59cab74c06 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -4650,7 +4650,7 @@ enum open_frm_error open_table_from_share(THD *thd, TABLE_SHARE *share, /* Allocate bitmaps */ bitmap_size= share->column_bitmap_size; - bitmap_count= 7; + bitmap_count= 8; if (share->virtual_fields) bitmap_count++; @@ -4677,6 +4677,9 @@ enum open_frm_error open_table_from_share(THD *thd, TABLE_SHARE *share, my_bitmap_init(&outparam->cond_set, (my_bitmap_map*) bitmaps, share->fields); bitmaps+= bitmap_size; + my_bitmap_init(&outparam->null_set, + (my_bitmap_map*) bitmaps, share->fields); + bitmaps+= bitmap_size; my_bitmap_init(&outparam->def_rpl_write_set, (my_bitmap_map*) bitmaps, share->fields); outparam->default_column_bitmaps(); @@ -7726,6 +7729,7 @@ void TABLE::clear_column_bitmaps() s->column_bitmap_size * (s->virtual_fields ? 3 : 2)); column_bitmaps_set(&def_read_set, &def_write_set); rpl_write_set= 0; // Safety + bitmap_clear_all(&null_set); } diff --git a/sql/table.h b/sql/table.h index 369f4264f0b7c..abf88e043a6fe 100644 --- a/sql/table.h +++ b/sql/table.h @@ -1414,6 +1414,7 @@ struct TABLE MY_BITMAP def_rpl_write_set; MY_BITMAP eq_join_set; /* used to mark equi-joined fields */ MY_BITMAP cond_set; /* used to mark fields from sargable conditions*/ + MY_BITMAP null_set; /* used to mark fields used only for NULL checks */ /* Active column sets */ MY_BITMAP *read_set, *write_set, *rpl_write_set; /* On INSERT: fields that the user specified a value for */ diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index c9f9b2cb9e2d1..440832b1b69d9 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -2993,6 +2993,7 @@ ha_innobase::ha_innobase( | HA_TABLE_SCAN_ON_INDEX | HA_CAN_FULLTEXT | HA_CAN_FULLTEXT_EXT + | HA_CAN_NULL_ONLY /* JAN: TODO: MySQL 5.7 | HA_CAN_FULLTEXT_HINTS */ @@ -5380,13 +5381,12 @@ innobase_vcol_build_templ( } if (field->real_maybe_null()) { - templ->mysql_null_byte_offset = - field->null_offset(); - - templ->mysql_null_bit_mask = (ulint) field->null_bit; - } else { - templ->mysql_null_bit_mask = 0; - } + templ->mysql_null_byte_offset = field->null_offset(); + templ->mysql_null_bit_mask = + static_cast(field->null_bit); + } else { + templ->mysql_null_bit_mask = 0; + } templ->mysql_col_offset = static_cast( get_field_offset(table, field)); @@ -6975,6 +6975,7 @@ build_template_field( row_prebuilt_t* prebuilt, /*!< in/out: template */ dict_index_t* clust_index, /*!< in: InnoDB clustered index */ dict_index_t* index, /*!< in: InnoDB index to use */ + bool allow_null_only,/*!< in: allow null-only columns */ TABLE* table, /*!< in: MySQL table object */ const Field* field, /*!< in: field in MySQL table */ ulint i, /*!< in: field index in InnoDB table */ @@ -7083,11 +7084,16 @@ build_template_field( templ->mysql_null_byte_offset = field->null_offset(); - templ->mysql_null_bit_mask = (ulint) field->null_bit; + templ->mysql_null_bit_mask = + static_cast(field->null_bit); } else { templ->mysql_null_bit_mask = 0; } + templ->null_only = allow_null_only + && !templ->is_virtual + && bitmap_is_set(&table->null_set, field->field_index); + ut_ad(!templ->null_only || templ->mysql_null_bit_mask != 0); templ->mysql_col_offset = (ulint) get_field_offset(table, field); templ->mysql_col_len = (ulint) field->pack_length(); @@ -7129,7 +7135,7 @@ build_template_field( + templ->mysql_col_len; } - if (DATA_LARGE_MTYPE(templ->type)) { + if (DATA_LARGE_MTYPE(templ->type) && !templ->null_only) { prebuilt->templ_contains_blob = TRUE; } @@ -7227,6 +7233,11 @@ ha_innobase::build_template( m_prebuilt->template_type = whole_row ? ROW_MYSQL_WHOLE_ROW : ROW_MYSQL_REC_FIELDS; + const bool allow_null_only= + m_prebuilt->template_type != ROW_MYSQL_WHOLE_ROW + && m_prebuilt->select_lock_type != LOCK_X + && !m_prebuilt->pk_filter + && !m_prebuilt->in_fts_query; m_prebuilt->null_bitmap_len = table->s->null_bytes & dict_index_t::MAX_N_FIELDS; @@ -7325,6 +7336,7 @@ ha_innobase::build_template( mysql_row_templ_t* templ= build_template_field( m_prebuilt, clust_index, index, + allow_null_only, table, field, i - num_v, 0); ut_ad(!templ->is_virtual); @@ -7445,6 +7457,7 @@ ha_innobase::build_template( ut_d(mysql_row_templ_t* templ =) build_template_field( m_prebuilt, clust_index, index, + allow_null_only, table, field, i - num_v, num_v); ut_ad(templ->is_virtual == (ulint)is_v); @@ -7512,6 +7525,7 @@ ha_innobase::build_template( ut_d(mysql_row_templ_t* templ =) build_template_field( m_prebuilt, clust_index, index, + allow_null_only, table, field, i - num_v, num_v); ut_ad(templ->is_virtual == (ulint)is_v); if (is_v) { diff --git a/storage/innobase/include/row0mysql.h b/storage/innobase/include/row0mysql.h index 2f50aa1560eb2..4b352d80ce592 100644 --- a/storage/innobase/include/row0mysql.h +++ b/storage/innobase/include/row0mysql.h @@ -423,8 +423,9 @@ struct mysql_row_templ_t { row format */ ulint mysql_null_byte_offset; /*!< MySQL NULL bit byte offset in a MySQL record */ - ulint mysql_null_bit_mask; /*!< bit mask to get the NULL bit, + byte mysql_null_bit_mask; /*!< bit mask to get the NULL bit, zero if column cannot be NULL */ + bool null_only; /*!< only NULL status is required */ ulint type; /*!< column type in Innobase mtype numbers DATA_CHAR... */ ulint mysql_type; /*!< MySQL type code; this is always diff --git a/storage/innobase/row/row0sel.cc b/storage/innobase/row/row0sel.cc index c3c17dd1d305e..c211040efc7b4 100644 --- a/storage/innobase/row/row0sel.cc +++ b/storage/innobase/row/row0sel.cc @@ -3221,6 +3221,37 @@ static bool row_sel_store_mysql_rec( continue; } + if (templ->null_only) { + const ulint field_no = + rec_clust + ? templ->clust_rec_field_no + : templ->rec_field_no; + ulint len; + + ut_ad(!templ->is_virtual); + ut_ad(templ->mysql_null_bit_mask != 0); + rec_get_nth_cfield(rec, index, offsets, field_no, &len); + + if (len == UNIV_SQL_NULL) { + mysql_rec[templ->mysql_null_byte_offset] + |= static_cast( + templ->mysql_null_bit_mask); + MEM_CHECK_DEFINED(prebuilt->default_rec + + templ->mysql_col_offset, + templ->mysql_col_len); + memcpy(mysql_rec + templ->mysql_col_offset, + (const byte*) prebuilt->default_rec + + templ->mysql_col_offset, + templ->mysql_col_len); + continue; + } + + mysql_rec[templ->mysql_null_byte_offset] + &= static_cast( + ~templ->mysql_null_bit_mask); + continue; + } + const ulint field_no = rec_clust ? templ->clust_rec_field_no @@ -4068,6 +4099,35 @@ row_search_idx_cond_check( continue; } + if (templ->null_only) { + const ulint field_no = templ->icp_rec_field_no; + ulint len; + + ut_ad(field_no != ULINT_UNDEFINED); + ut_ad(templ->mysql_null_bit_mask != 0); + rec_get_nth_cfield(rec, prebuilt->index, offsets, + field_no, &len); + + if (len == UNIV_SQL_NULL) { + mysql_rec[templ->mysql_null_byte_offset] + |= static_cast( + templ->mysql_null_bit_mask); + MEM_CHECK_DEFINED(prebuilt->default_rec + + templ->mysql_col_offset, + templ->mysql_col_len); + memcpy(mysql_rec + templ->mysql_col_offset, + (const byte*) prebuilt->default_rec + + templ->mysql_col_offset, + templ->mysql_col_len); + continue; + } + + mysql_rec[templ->mysql_null_byte_offset] + &= static_cast( + ~templ->mysql_null_bit_mask); + continue; + } + if (!row_sel_store_mysql_field(mysql_rec, prebuilt, rec, prebuilt->index, offsets, templ->icp_rec_field_no, diff --git a/storage/maria/ha_maria.cc b/storage/maria/ha_maria.cc index 11ff5062854af..e4fc1de7608e6 100644 --- a/storage/maria/ha_maria.cc +++ b/storage/maria/ha_maria.cc @@ -991,6 +991,7 @@ int_table_flags(HA_NULL_IN_KEY | HA_CAN_FULLTEXT | HA_CAN_SQL_HANDLER | HA_FILE_BASED | HA_CAN_GEOMETRY | TRANSACTION_STATE | HA_CAN_BIT_FIELD | HA_CAN_RTREEKEYS | HA_CAN_REPAIR | HA_CAN_VIRTUAL_COLUMNS | HA_CAN_EXPORT | + HA_CAN_NULL_ONLY | HA_HAS_RECORDS | HA_STATS_RECORDS_IS_EXACT | HA_CAN_TABLES_WITHOUT_ROLLBACK), can_enable_indexes(0), bulk_insert_single_undo(BULK_INSERT_NONE) @@ -1155,6 +1156,11 @@ int ha_maria::open(const char *name, int mode, uint test_if_locked) file->s->chst_invalidator= query_cache_invalidate_by_MyISAM_filename_ref; /* Set external_ref, mainly for temporary tables */ file->external_ref= (void*) table; // For ma_killed() + file->null_set= 0; + if (table->s->tmp_table == NO_TMP_TABLE && + (file->s->data_file_type == DYNAMIC_RECORD || + file->s->data_file_type == BLOCK_RECORD)) + file->null_set= &table->null_set; if (test_if_locked & (HA_OPEN_IGNORE_IF_LOCKED | HA_OPEN_TMP_TABLE)) maria_extra(file, HA_EXTRA_NO_WAIT_LOCK, 0); @@ -1229,6 +1235,7 @@ int ha_maria::close(void) /* Ensure we have no open transactions */ DBUG_ASSERT(file->trn == 0 || file->trn == &dummy_transaction_object); DBUG_ASSERT(file->trn_next == 0 && file->trn_prev == 0); + tmp->null_set= 0; file= 0; return maria_close(tmp); } diff --git a/storage/maria/ma_blockrec.c b/storage/maria/ma_blockrec.c index 98ddd70c3d6b8..ddbd00e200047 100644 --- a/storage/maria/ma_blockrec.c +++ b/storage/maria/ma_blockrec.c @@ -4699,6 +4699,38 @@ static my_bool read_long_data2(MARIA_HA *info, uchar *to, ulong length, DBUG_RETURN(1); } +static my_bool skip_long_data2(MARIA_HA *info, ulong length, + MARIA_EXTENT_CURSOR *extent, + uchar **data, uchar **end_of_data) +{ + uint left_length= (uint) (*end_of_data - *data); + DBUG_ENTER("skip_long_data2"); + DBUG_PRINT("enter", ("length: %lu left_length: %u", + length, left_length)); + DBUG_ASSERT(*data <= *end_of_data); + + if (extent->first_extent && length > left_length) + { + *end_of_data= *data; + left_length= 0; + } + + for (;;) + { + if (unlikely(left_length >= length)) + { + (*data)+= length; + DBUG_PRINT("info", ("left_length: %u", left_length - (uint) length)); + DBUG_RETURN(0); + } + length-= left_length; + if (!(*data= read_next_extent(info, extent, end_of_data))) + break; + left_length= (uint) (*end_of_data - *data); + } + DBUG_RETURN(1); +} + static inline my_bool read_long_data(MARIA_HA *info, uchar *to, ulong length, MARIA_EXTENT_CURSOR *extent, uchar **data, uchar **end_of_data) @@ -4713,6 +4745,19 @@ static inline my_bool read_long_data(MARIA_HA *info, uchar *to, ulong length, return read_long_data2(info, to, length, extent, data, end_of_data); } +static inline my_bool skip_long_data(MARIA_HA *info, ulong length, + MARIA_EXTENT_CURSOR *extent, + uchar **data, uchar **end_of_data) +{ + uint left_length= (uint) (*end_of_data - *data); + if (likely(left_length >= length)) + { + (*data)+= length; + return 0; + } + return skip_long_data2(info, length, extent, data, end_of_data); +} + /* Read a record from page (helper function for _ma_read_block_record()) @@ -4748,6 +4793,7 @@ int _ma_read_block_record2(MARIA_HA *info, uchar *record, MARIA_SHARE *share= info->s; uchar *field_length_data= 0, *UNINIT_VAR(blob_buffer), *start_of_data; uint flag, null_bytes, cur_null_bytes, row_extents, field_lengths; + my_bool null_only; my_bool found_blob= 0; MARIA_EXTENT_CURSOR extent; MARIA_COLUMNDEF *column, *end_column; @@ -4888,6 +4934,7 @@ int _ma_read_block_record2(MARIA_HA *info, uchar *record, { enum en_fieldtype type= column->type; uchar *field_pos= record + column->offset; + null_only= ma_is_null_only_field(info, column); /* First check if field is present in record */ if ((record[column->null_pos] & column->null_bit) || (column->empty_bit && @@ -4901,11 +4948,19 @@ int _ma_read_block_record2(MARIA_HA *info, uchar *record, case FIELD_NORMAL: /* Fixed length field */ case FIELD_SKIP_PRESPACE: case FIELD_SKIP_ZERO: /* Fixed length field */ - if (data + column->length > end_of_data && - !(data= read_next_extent(info, &extent, &end_of_data))) - goto err; - memcpy(field_pos, data, column->length); - data+= column->length; + if (!null_only) + { + if (data + column->length > end_of_data && + !(data= read_next_extent(info, &extent, &end_of_data))) + goto err; + memcpy(field_pos, data, column->length); + data+= column->length; + } + else if (skip_long_data(info, column->length, &extent, &data, + &end_of_data)) + { + DBUG_RETURN(my_errno); + } break; case FIELD_SKIP_ENDSPACE: /* CHAR */ { @@ -4922,10 +4977,17 @@ int _ma_read_block_record2(MARIA_HA *info, uchar *record, if (length > column->length) goto err; #endif - if (read_long_data(info, field_pos, length, &extent, &data, - &end_of_data)) + if (!null_only) + { + if (read_long_data(info, field_pos, length, &extent, &data, + &end_of_data)) + DBUG_RETURN(my_errno); + bfill(field_pos + length, column->length - length, ' '); + } + else if (skip_long_data(info, length, &extent, &data, &end_of_data)) + { DBUG_RETURN(my_errno); - bfill(field_pos + length, column->length - length, ' '); + } break; } case FIELD_VARCHAR: @@ -4950,10 +5012,18 @@ int _ma_read_block_record2(MARIA_HA *info, uchar *record, if (length > column->length - pack_length) goto err; #endif - if (read_long_data(info, field_pos, length, &extent, &data, - &end_of_data)) + if (!null_only) + { + if (read_long_data(info, field_pos, length, &extent, &data, + &end_of_data)) + DBUG_RETURN(my_errno); + MEM_UNDEFINED(field_pos + length, + column->length - length - pack_length); + } + else if (skip_long_data(info, length, &extent, &data, &end_of_data)) + { DBUG_RETURN(my_errno); - MEM_UNDEFINED(field_pos + length, column->length - length - pack_length); + } break; } case FIELD_BLOB: @@ -4966,12 +5036,14 @@ int _ma_read_block_record2(MARIA_HA *info, uchar *record, { /* Calculate total length for all blobs */ ulong blob_lengths= 0; + ulong read_blob_lengths= 0; uchar *length_data= field_length_data; MARIA_COLUMNDEF *blob_field= column; found_blob= 1; for (; blob_field < end_column; blob_field++) { + ulong blob_part_length; uint size_length; if ((record[blob_field->null_pos] & blob_field->null_bit) || (blob_field->empty_bit & @@ -4979,20 +5051,27 @@ int _ma_read_block_record2(MARIA_HA *info, uchar *record, blob_field->empty_bit))) continue; size_length= blob_field->length - portable_sizeof_char_ptr; - blob_lengths+= _ma_calc_blob_length(size_length, length_data); + blob_part_length= _ma_calc_blob_length(size_length, length_data); + blob_lengths+= blob_part_length; + if (!ma_is_null_only_field(info, blob_field)) + read_blob_lengths+= blob_part_length; length_data+= size_length; } cur_row->blob_length= blob_lengths; DBUG_PRINT("info", ("Total blob length: %lu", blob_lengths)); - if (_ma_alloc_buffer(&info->blob_buff, &info->blob_buff_size, - blob_lengths, myflag)) + if (read_blob_lengths && + _ma_alloc_buffer(&info->blob_buff, &info->blob_buff_size, + read_blob_lengths, myflag)) DBUG_RETURN(my_errno); blob_buffer= info->blob_buff; } - memcpy(field_pos, field_length_data, column_size_length); - memcpy(field_pos + column_size_length, (uchar *) &blob_buffer, - sizeof(char*)); + if (!null_only) + { + memcpy(field_pos, field_length_data, column_size_length); + memcpy(field_pos + column_size_length, (uchar *) &blob_buffer, + sizeof(char*)); + } field_length_data+= column_size_length; /* @@ -5001,10 +5080,18 @@ int _ma_read_block_record2(MARIA_HA *info, uchar *record, if (!extent.first_extent || (ulong) (end_of_data - data) < blob_length) end_of_data= data; /* Force read of next extent */ - if (read_long_data(info, blob_buffer, blob_length, &extent, &data, - &end_of_data)) + if (!null_only) + { + if (read_long_data(info, blob_buffer, blob_length, &extent, &data, + &end_of_data)) + DBUG_RETURN(my_errno); + blob_buffer+= blob_length; + } + else if (skip_long_data(info, blob_length, &extent, &data, + &end_of_data)) + { DBUG_RETURN(my_errno); - blob_buffer+= blob_length; + } break; } default: diff --git a/storage/maria/ma_dynrec.c b/storage/maria/ma_dynrec.c index 7da564e88ff3b..f816fb6c03b9f 100644 --- a/storage/maria/ma_dynrec.c +++ b/storage/maria/ma_dynrec.c @@ -1260,6 +1260,7 @@ size_t _ma_rec_unpack(register MARIA_HA *info, register uchar *to, uchar *from, { uint flag,bit,length,min_pack_length, column_length; enum en_fieldtype type; + my_bool null_only; uchar *from_end,*to_end,*packpos; reg3 MARIA_COLUMNDEF *column, *end_column; DBUG_ENTER("_ma_rec_unpack"); @@ -1283,6 +1284,7 @@ size_t _ma_rec_unpack(register MARIA_HA *info, register uchar *to, uchar *from, for (column= info->s->columndef, end_column= column + info->s->base.fields; column < end_column ; to+= column_length, column++) { + null_only= ma_is_null_only_field(info, column); column_length= column->length; if ((type = (enum en_fieldtype) column->type) != FIELD_NORMAL && (type != FIELD_CHECK)) @@ -1295,20 +1297,26 @@ size_t _ma_rec_unpack(register MARIA_HA *info, register uchar *to, uchar *from, length= (uint) *(uchar*) from; if (length > column_length-1) goto err; - *to= *from++; + if (!null_only) + *to= *from; + from++; } else { get_key_length(length, from); if (length > column_length-2) goto err; - int2store(to,length); + if (!null_only) + int2store(to,length); } if (from+length > from_end) goto err; - memcpy(to+pack_length, from, length); - MEM_UNDEFINED(to+pack_length + length, - column_length - length - pack_length); + if (!null_only) + { + memcpy(to+pack_length, from, length); + MEM_UNDEFINED(to+pack_length + length, + column_length - length - pack_length); + } from+= length; min_pack_length--; continue; @@ -1316,7 +1324,10 @@ size_t _ma_rec_unpack(register MARIA_HA *info, register uchar *to, uchar *from, if (flag & bit) { if (type == FIELD_BLOB || type == FIELD_SKIP_ZERO) - bzero(to, column_length); + { + if (!null_only) + bzero(to, column_length); + } else if (type == FIELD_SKIP_ENDSPACE || type == FIELD_SKIP_PRESPACE) { @@ -1336,12 +1347,12 @@ size_t _ma_rec_unpack(register MARIA_HA *info, register uchar *to, uchar *from, if (length >= column_length || min_pack_length + length > (uint) (from_end - from)) goto err; - if (type == FIELD_SKIP_ENDSPACE) + if (!null_only && type == FIELD_SKIP_ENDSPACE) { memcpy(to, from, (size_t) length); bfill(to+length, column_length-length, ' '); } - else + else if (!null_only) { bfill(to, column_length-length, ' '); memcpy(to+column_length-length, from, (size_t) length); @@ -1358,9 +1369,11 @@ size_t _ma_rec_unpack(register MARIA_HA *info, register uchar *to, uchar *from, from_left - size_length < blob_length || from_left - size_length - blob_length < min_pack_length) goto err; - memcpy(to, from, (size_t) size_length); + if (!null_only) + memcpy(to, from, (size_t) size_length); from+=size_length; - memcpy(to+size_length,(uchar*) &from,sizeof(char*)); + if (!null_only) + memcpy(to+size_length,(uchar*) &from,sizeof(char*)); from+=blob_length; } else @@ -1369,7 +1382,9 @@ size_t _ma_rec_unpack(register MARIA_HA *info, register uchar *to, uchar *from, min_pack_length--; if (min_pack_length + column_length > (uint) (from_end - from)) goto err; - memcpy(to, from, (size_t) column_length); from+=column_length; + if (!null_only) + memcpy(to, from, (size_t) column_length); + from+=column_length; } if ((bit= bit << 1) >= 256) { @@ -1381,7 +1396,8 @@ size_t _ma_rec_unpack(register MARIA_HA *info, register uchar *to, uchar *from, if (min_pack_length > (uint) (from_end - from)) goto err; min_pack_length-=column_length; - memcpy(to, from, (size_t) column_length); + if (!null_only) + memcpy(to, from, (size_t) column_length); from+=column_length; } } diff --git a/storage/maria/maria_def.h b/storage/maria/maria_def.h index 7e9d507312fef..6c502921363f2 100644 --- a/storage/maria/maria_def.h +++ b/storage/maria/maria_def.h @@ -963,6 +963,7 @@ struct st_maria_handler MEM_ROOT ft_memroot; /* used by the parser */ MYSQL_FTPARSER_PARAM *ftparser_param; /* share info between init/deinit */ void *external_ref; /* For MariaDB TABLE */ + const MY_BITMAP *null_set; /* Columns used only for NULL tests */ uchar *buff; /* page buffer */ uchar *keyread_buff; /* Buffer for last key read */ uchar *lastkey_buff; /* Last used search key */ @@ -1060,6 +1061,14 @@ struct st_maria_handler void *rowid_filter_func_arg; /* parameter for the func */ }; +static inline my_bool ma_is_null_only_field(const MARIA_HA *info, + const MARIA_COLUMNDEF *column) +{ + return info->null_set && + column->column_nr < info->null_set->n_bits && + bitmap_is_set(info->null_set, column->column_nr); +} + /* Table options for the Aria and S3 storage engine */ struct ha_table_option_struct diff --git a/storage/myisam/ha_myisam.cc b/storage/myisam/ha_myisam.cc index 284dcb211641a..9a605210a1b09 100644 --- a/storage/myisam/ha_myisam.cc +++ b/storage/myisam/ha_myisam.cc @@ -388,6 +388,7 @@ int table2myisam(TABLE *table_arg, MI_KEYDEF **keydef_out, /* reserve space for null bits */ bzero((char*) recinfo_pos, sizeof(*recinfo_pos)); recinfo_pos->type= FIELD_NORMAL; + recinfo_pos->fieldnr= MI_NO_FIELD_NR; recinfo_pos++->length= (uint16) (minpos - recpos); } if (!found) @@ -433,6 +434,7 @@ int table2myisam(TABLE *table_arg, MI_KEYDEF **keydef_out, recinfo_pos->null_bit= 0; recinfo_pos->null_pos= 0; } + recinfo_pos->fieldnr= found->field_index; (recinfo_pos++)->length= (uint16) length; recpos= minpos + length; DBUG_PRINT("loop", ("length: %d type: %d", @@ -724,6 +726,7 @@ ha_myisam::ha_myisam(handlerton *hton, TABLE_SHARE *table_arg) int_table_flags(HA_NULL_IN_KEY | HA_CAN_FULLTEXT | HA_CAN_SQL_HANDLER | HA_BINLOG_ROW_CAPABLE | HA_BINLOG_STMT_CAPABLE | HA_CAN_VIRTUAL_COLUMNS | HA_CAN_EXPORT | + HA_CAN_NULL_ONLY | HA_REQUIRES_KEY_COLUMNS_FOR_DELETE | HA_DUPLICATE_POS | HA_CAN_INDEX_BLOBS | HA_AUTO_PART_KEY | HA_FILE_BASED | HA_CAN_GEOMETRY | HA_NO_TRANSACTIONS | @@ -750,6 +753,29 @@ static const char *ha_myisam_exts[] = { NullS }; +static void myisam_set_fieldnr_map(uint16 *dst, uint dst_count, + const MI_COLUMNDEF *dst_rec, + const MI_COLUMNDEF *src, uint src_count) +{ + uint i; + for (i= 0; i < dst_count; i++) + dst[i]= MI_NO_FIELD_NR; + + if (!src || src_count != dst_count) + return; + + for (i= 0; i < dst_count; i++) + { + if (dst_rec[i].type == src[i].type && + dst_rec[i].length == src[i].length && + dst_rec[i].null_bit == src[i].null_bit && + dst_rec[i].null_pos == src[i].null_pos) + { + dst[i]= src[i].fieldnr; + } + } +} + ulong ha_myisam::index_flags(uint inx, uint part, bool all_parts) const { ulong flags; @@ -816,18 +842,30 @@ int ha_myisam::open(const char *name, int mode, uint test_if_locked) file->s->chst_invalidator= query_cache_invalidate_by_MyISAM_filename_ref; /* Set external_ref, mainly for temporary tables */ file->external_ref= (void*) table; // For mi_killed() + file->null_set= 0; + file->fieldnr_map= 0; + + if (!(file->fieldnr_map= (uint16*) my_malloc(PSI_INSTRUMENT_ME, + file->s->base.fields * + sizeof(uint16), + MYF(MY_WME)))) + { + my_errno= HA_ERR_OUT_OF_MEM; + goto err; + } + + if ((my_errno= table2myisam(table, &keyinfo, &recinfo, &recs))) + { + /* purecov: begin inspected */ + DBUG_PRINT("error", ("Failed to convert TABLE object to MyISAM " + "key and column definition")); + goto err; + /* purecov: end */ + } /* No need to perform a check for tmp table or if it's already checked */ if (!table->s->tmp_table && file->s->reopen == 1) { - if ((my_errno= table2myisam(table, &keyinfo, &recinfo, &recs))) - { - /* purecov: begin inspected */ - DBUG_PRINT("error", ("Failed to convert TABLE object to MyISAM " - "key and column definition")); - goto err; - /* purecov: end */ - } if (check_definition(keyinfo, recinfo, table->s->keys, recs, file->s->keyinfo, file->s->rec, file->s->base.keys, file->s->base.fields, @@ -840,6 +878,12 @@ int ha_myisam::open(const char *name, int mode, uint test_if_locked) } } + myisam_set_fieldnr_map(file->fieldnr_map, file->s->base.fields, + file->s->rec, recinfo, recs); + if (table->s->tmp_table == NO_TMP_TABLE && + !(file->s->options & HA_OPTION_COMPRESS_RECORD)) + file->null_set= &table->null_set; + DBUG_EXECUTE_IF("key", Debug_key_myisam::print_keys_myisam(table->in_use, "ha_myisam::open: ", @@ -946,6 +990,11 @@ int ha_myisam::close(void) if (!tmp) return 0; file=0; + if (tmp->fieldnr_map) + { + my_free(tmp->fieldnr_map); + tmp->fieldnr_map= 0; + } return mi_close(tmp); } diff --git a/storage/myisam/mi_dynrec.c b/storage/myisam/mi_dynrec.c index 17d4e24884233..a5d4254092ee3 100644 --- a/storage/myisam/mi_dynrec.c +++ b/storage/myisam/mi_dynrec.c @@ -1220,6 +1220,7 @@ size_t _mi_rec_unpack(register MI_INFO *info, register uchar *to, uchar *from, ulong found_length) { uint flag,bit,length,rec_length,min_pack_length; + uint rec_idx; enum en_fieldtype type; uchar *from_end,*to_end,*packpos; reg3 MI_COLUMNDEF *rec,*end_field; @@ -1233,9 +1234,10 @@ size_t _mi_rec_unpack(register MI_INFO *info, register uchar *to, uchar *from, from+= info->s->base.pack_bits; min_pack_length=info->s->base.min_pack_length - info->s->base.pack_bits; - for (rec=info->s->rec , end_field=rec+info->s->base.fields ; - rec < end_field ; to+= rec_length, rec++) + for (rec=info->s->rec, end_field=rec+info->s->base.fields, rec_idx= 0; + rec < end_field; to+= rec_length, rec++, rec_idx++) { + my_bool null_only= mi_is_null_only_field(info, rec_idx); rec_length=rec->length; if ((type = (enum en_fieldtype) rec->type) != FIELD_NORMAL && (type != FIELD_CHECK)) @@ -1248,18 +1250,22 @@ size_t _mi_rec_unpack(register MI_INFO *info, register uchar *to, uchar *from, length= (uint) *(uchar*) from; if (length > rec_length-1) goto err; - *to= *from++; + if (!null_only) + *to= *from; + from++; } else { get_key_length(length, from); if (length > rec_length-2) goto err; - int2store(to,length); + if (!null_only) + int2store(to,length); } if (from+length > from_end) goto err; - memcpy(to+pack_length, from, length); + if (!null_only) + memcpy(to+pack_length, from, length); from+= length; min_pack_length--; continue; @@ -1267,7 +1273,10 @@ size_t _mi_rec_unpack(register MI_INFO *info, register uchar *to, uchar *from, if (flag & bit) { if (type == FIELD_BLOB || type == FIELD_SKIP_ZERO) - bzero((uchar*) to,rec_length); + { + if (!null_only) + bzero((uchar*) to,rec_length); + } else if (type == FIELD_SKIP_ENDSPACE || type == FIELD_SKIP_PRESPACE) { @@ -1287,12 +1296,12 @@ size_t _mi_rec_unpack(register MI_INFO *info, register uchar *to, uchar *from, if (length >= rec_length || min_pack_length + length > (uint) (from_end - from)) goto err; - if (type == FIELD_SKIP_ENDSPACE) + if (!null_only && type == FIELD_SKIP_ENDSPACE) { memcpy(to,(uchar*) from,(size_t) length); bfill((uchar*) to+length,rec_length-length,' '); } - else + else if (!null_only) { bfill((uchar*) to,rec_length-length,' '); memcpy(to+rec_length-length,(uchar*) from,(size_t) length); @@ -1309,9 +1318,15 @@ size_t _mi_rec_unpack(register MI_INFO *info, register uchar *to, uchar *from, from_left - size_length < blob_length || from_left - size_length - blob_length < min_pack_length) goto err; - memcpy(to, from, (size_t) size_length); + if (!null_only) + { + memcpy(to, from, (size_t) size_length); + } from+=size_length; - memcpy(to+size_length, &from, sizeof(char*)); + if (!null_only) + { + memcpy(to+size_length, &from, sizeof(char*)); + } from+=blob_length; } else @@ -1320,7 +1335,9 @@ size_t _mi_rec_unpack(register MI_INFO *info, register uchar *to, uchar *from, min_pack_length--; if (min_pack_length + rec_length > (uint) (from_end - from)) goto err; - memcpy(to,(uchar*) from,(size_t) rec_length); from+=rec_length; + if (!null_only) + memcpy(to,(uchar*) from,(size_t) rec_length); + from+=rec_length; } if ((bit= bit << 1) >= 256) { @@ -1332,7 +1349,8 @@ size_t _mi_rec_unpack(register MI_INFO *info, register uchar *to, uchar *from, if (min_pack_length > (uint) (from_end - from)) goto err; min_pack_length-=rec_length; - memcpy(to, (uchar*) from, (size_t) rec_length); + if (!null_only) + memcpy(to, (uchar*) from, (size_t) rec_length); from+=rec_length; } } diff --git a/storage/myisam/mi_open.c b/storage/myisam/mi_open.c index 0c805b0dfe9fd..25fa42d899a7e 100644 --- a/storage/myisam/mi_open.c +++ b/storage/myisam/mi_open.c @@ -1276,6 +1276,7 @@ uchar *mi_recinfo_read(uchar *ptr, MI_COLUMNDEF *recinfo) recinfo->length=mi_uint2korr(ptr); ptr +=2; recinfo->null_bit= (uint8) *ptr++; recinfo->null_pos=mi_uint2korr(ptr); ptr +=2; + recinfo->fieldnr= MI_NO_FIELD_NR; return ptr; } diff --git a/storage/myisam/myisamdef.h b/storage/myisam/myisamdef.h index 5b207efd7a4ab..9bd29b876c8fe 100644 --- a/storage/myisam/myisamdef.h +++ b/storage/myisam/myisamdef.h @@ -20,6 +20,7 @@ #include #include /* Structs & some defines */ #include /* packing of keys */ +#include #include #include #include @@ -246,6 +247,8 @@ struct st_myisam_info MEM_ROOT ft_memroot; /* used by the parser */ MYSQL_FTPARSER_PARAM *ftparser_param; /* share info between init/deinit */ void *external_ref; /* For MariaDB TABLE */ + const MY_BITMAP *null_set; /* Columns used only for NULL tests */ + uint16 *fieldnr_map; /* recinfo index -> SQL field index */ LIST in_use; /* Thread using this table */ char *filename; /* parameter to open filename */ uchar *buff, /* Temp area for key */ @@ -316,6 +319,15 @@ struct st_myisam_info int rtree_recursion_depth; }; +static inline my_bool mi_is_null_only_field(const MI_INFO *info, uint rec_idx) +{ + uint16 fieldnr; + if (!info->null_set || !info->fieldnr_map) + return 0; + fieldnr= info->fieldnr_map[rec_idx]; + return fieldnr != MI_NO_FIELD_NR && bitmap_is_set(info->null_set, fieldnr); +} + #define USE_WHOLE_KEY (HA_MAX_KEY_BUFF*2) /* Use whole key in _mi_search() */ #define F_EXTRA_LCK -1 /* bits in opt_flag */