diff --git a/include/myisam.h b/include/myisam.h index 5c75f3fbe28cf..3179994433ac8 100644 --- a/include/myisam.h +++ b/include/myisam.h @@ -268,6 +268,8 @@ extern int mi_rlast(struct st_myisam_info *file,uchar *buf,int inx); extern int mi_rnext(struct st_myisam_info *file,uchar *buf,int inx); extern int mi_rnext_same(struct st_myisam_info *info, uchar *buf); extern int mi_rprev(struct st_myisam_info *file,uchar *buf,int inx); +extern int mi_random_dive(struct st_myisam_info *file, int inx, uchar *buf, + struct my_rnd_struct *rand_state); extern int mi_rrnd(struct st_myisam_info *file,uchar *buf, my_off_t pos); extern int mi_scan_init(struct st_myisam_info *file); extern int mi_scan(struct st_myisam_info *file,uchar *buf); diff --git a/libmysqld/CMakeLists.txt b/libmysqld/CMakeLists.txt index d38f2a48c737b..734a910cac992 100644 --- a/libmysqld/CMakeLists.txt +++ b/libmysqld/CMakeLists.txt @@ -106,6 +106,7 @@ SET(SQL_EMBEDDED_SOURCES emb_qcache.cc libmysqld.c lib_sql.cc ../sql/sql_show.cc ../sql/sql_state.c ../sql/sql_statistics.cc ../sql/sql_string.cc ../sql/sql_table.cc ../sql/sql_test.cc + ../sql/sql_tablesample.cc ../sql/ddl_log.cc ../sql/item_vectorfunc.cc ../sql/sql_trigger.cc ../sql/sql_udf.cc ../sql/sql_union.cc ../sql/sql_update.cc ../sql/sql_view.cc ../sql/sql_profile.cc diff --git a/mysql-test/main/tablesample.result b/mysql-test/main/tablesample.result new file mode 100644 index 0000000000000..1dd07e4403ac0 --- /dev/null +++ b/mysql-test/main/tablesample.result @@ -0,0 +1,151 @@ +DROP TABLE IF EXISTS t1, t2; +DROP VIEW IF EXISTS v1; +CREATE TABLE t1 (a INT, b INT, PRIMARY KEY (b)); +CREATE TABLE t2 (a INT, c INT); +CREATE VIEW v1 AS SELECT * FROM t1; +SELECT * FROM t1 TABLESAMPLE SYSTEM (10); +a b +SELECT * FROM t1 TABLESAMPLE BERNOULLI (50); +a b +SELECT * FROM t1 TABLESAMPLE SYSTEM (12.5); +a b +SELECT * FROM t1 TABLESAMPLE BERNOULLI (0.5); +a b +SELECT * FROM t1 TABLESAMPLE BERNOULLI (NULL); +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'NULL)' at line 1 +SELECT * FROM t1 TABLESAMPLE NONEXISTENTMETHOD (12.5); +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'NONEXISTENTMETHOD (12.5)' at line 1 +SELECT * FROM t1 TABLESAMPLE SYSTEM (-12.5); +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '-12.5)' at line 1 +SELECT * FROM t1 TABLESAMPLE SYSTEM (10) JOIN t2 ON t1.a = t2.a; +a b a c +SELECT * FROM v1 TABLESAMPLE SYSTEM (20); +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use +PREPARE stmt2 FROM 'SELECT * FROM t1 TABLESAMPLE BERNOULLI (?)'; +SET @pct = 30; +EXECUTE stmt2 USING @pct; +a b +SET @pct = 300; +EXECUTE stmt2 USING @pct; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use +SET @pct = NULL; +EXECUTE stmt2 USING @pct; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use +DEALLOCATE PREPARE stmt2; +DROP PROCEDURE IF EXISTS p1; +Warnings: +Note 1305 PROCEDURE test.p1 does not exist +CREATE PROCEDURE p1(IN sample_pct INT) +BEGIN +SELECT * FROM t1 TABLESAMPLE BERNOULLI (sample_pct); +END// +CALL p1(40); +a b +CALL p1(101); +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use +DROP PROCEDURE p1; +CREATE PROCEDURE p1(IN sample_pct DECIMAL) +BEGIN +SELECT * FROM t1 TABLESAMPLE SYSTEM (sample_pct); +END// +CALL p1(20.11); +a b +Warnings: +Note 1265 Data truncated for column 'sample_pct' at row 0 +DROP PROCEDURE p1; +DROP PROCEDURE IF EXISTS p_char; +Warnings: +Note 1305 PROCEDURE test.p_char does not exist +CREATE PROCEDURE p_char(IN sample_pct CHAR(10)) +BEGIN +SELECT * FROM t1 TABLESAMPLE SYSTEM (sample_pct); +END// +ERROR HY000: A variable of a non-numeric based type in TABLESAMPLE clause +DROP PROCEDURE IF EXISTS p_date; +Warnings: +Note 1305 PROCEDURE test.p_date does not exist +CREATE PROCEDURE p_date(IN sample_pct DATE) +BEGIN +SELECT * FROM t1 TABLESAMPLE BERNOULLI (sample_pct); +END// +ERROR HY000: A variable of a non-numeric based type in TABLESAMPLE clause +DROP PROCEDURE IF EXISTS p1; +Warnings: +Note 1305 PROCEDURE test.p1 does not exist +CREATE PROCEDURE p1() +BEGIN +DECLARE c SYS_REFCURSOR; +OPEN c FOR 'SELECT * FROM t1 TABLESAMPLE BERNOULLI (?)' USING NULL; +END// +CALL p1(); +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use +DROP PROCEDURE p1; +SELECT * FROM information_schema.tables TABLESAMPLE BERNOULLI (5); +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use +SELECT * FROM mysql.user TABLESAMPLE SYSTEM (10); +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use +SELECT * FROM (SELECT * FROM t1) AS derived_tbl TABLESAMPLE SYSTEM (30); +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'TABLESAMPLE SYSTEM (30)' at line 1 +WITH cte_tbl AS (SELECT * FROM t1) +SELECT * FROM cte_tbl TABLESAMPLE BERNOULLI (10); +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use +CREATE TABLE t_no_pk (a INT, b INT); +CREATE TABLE t_pk (a INT PRIMARY KEY, b INT); +SELECT * FROM t_no_pk TABLESAMPLE SYSTEM (10); +a b +Warnings: +Note 4272 TABLESAMPLE SYSTEM requires a primary key on table 't_no_pk'; using BERNOULLI sampling instead +SHOW WARNINGS; +Level Code Message +Note 4272 TABLESAMPLE SYSTEM requires a primary key on table 't_no_pk'; using BERNOULLI sampling instead +SELECT * FROM t_pk TABLESAMPLE SYSTEM (10); +a b +SHOW WARNINGS; +Level Code Message +SELECT * FROM t_pk TABLESAMPLE SYSTEM (30); +a b +SHOW WARNINGS; +Level Code Message +SELECT * FROM t_pk TABLESAMPLE SYSTEM (30.01); +a b +Warnings: +Note 4273 TABLESAMPLE SYSTEM on table 't_pk' requested more than 30% of rows; using BERNOULLI sampling instead +SHOW WARNINGS; +Level Code Message +Note 4273 TABLESAMPLE SYSTEM on table 't_pk' requested more than 30% of rows; using BERNOULLI sampling instead +SELECT * FROM t_pk TABLESAMPLE SYSTEM (50); +a b +Warnings: +Note 4273 TABLESAMPLE SYSTEM on table 't_pk' requested more than 30% of rows; using BERNOULLI sampling instead +SHOW WARNINGS; +Level Code Message +Note 4273 TABLESAMPLE SYSTEM on table 't_pk' requested more than 30% of rows; using BERNOULLI sampling instead +PREPARE stmt3 FROM 'SELECT * FROM t_no_pk TABLESAMPLE SYSTEM (10)'; +Warnings: +Note 4272 TABLESAMPLE SYSTEM requires a primary key on table 't_no_pk'; using BERNOULLI sampling instead +EXECUTE stmt3; +a b +Warnings: +Note 4272 TABLESAMPLE SYSTEM requires a primary key on table 't_no_pk'; using BERNOULLI sampling instead +SHOW WARNINGS; +Level Code Message +Note 4272 TABLESAMPLE SYSTEM requires a primary key on table 't_no_pk'; using BERNOULLI sampling instead +ALTER TABLE t_no_pk ADD PRIMARY KEY (a); +EXECUTE stmt3; +a b +SHOW WARNINGS; +Level Code Message +DEALLOCATE PREPARE stmt3; +DROP TABLE t_no_pk, t_pk; +CREATE TABLE t_data (a INT PRIMARY KEY, b INT) ENGINE=MyISAM; +INSERT INTO t_data SELECT seq, seq * 2 FROM seq_1_to_20; +SET @save_join_cache_level= @@join_cache_level; +SET join_cache_level= 3; +EXPLAIN SELECT * FROM t_data AS t1 TABLESAMPLE BERNOULLI (20) +JOIN t_data AS t2 TABLESAMPLE BERNOULLI (30) ON t1.a = t2.a; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 sample NULL NULL NULL NULL 4 +1 SIMPLE t2 hash_sample NULL #hash#$hj: 4: test.t1.a 6 Using where; Using join buffer (flat, BNLH join) +SET join_cache_level= @save_join_cache_level; +DROP TABLE t1, t2, t_data; +DROP VIEW v1; diff --git a/mysql-test/main/tablesample.test b/mysql-test/main/tablesample.test new file mode 100644 index 0000000000000..caaae50a7962f --- /dev/null +++ b/mysql-test/main/tablesample.test @@ -0,0 +1,221 @@ +# +# MDEV-38992 SQL Standard TABLESAMPLE clause +# +# For now, there are tests just for checking syntax +# + +# TABLESAMPLE is not allowed on views, and the view-protocol's +# CREATE VIEW/SELECT/DROP VIEW rewriting of plain SELECTs clears the +# session's warning list between statements, breaking the SHOW WARNINGS +# checks below. +--source include/no_view_protocol.inc + +--disable_warnings +DROP TABLE IF EXISTS t1, t2; +DROP VIEW IF EXISTS v1; +--enable_warnings + +CREATE TABLE t1 (a INT, b INT, PRIMARY KEY (b)); +CREATE TABLE t2 (a INT, c INT); +CREATE VIEW v1 AS SELECT * FROM t1; + +# +# Basic Syntax Validation +# +SELECT * FROM t1 TABLESAMPLE SYSTEM (10); +SELECT * FROM t1 TABLESAMPLE BERNOULLI (50); + +SELECT * FROM t1 TABLESAMPLE SYSTEM (12.5); +SELECT * FROM t1 TABLESAMPLE BERNOULLI (0.5); + +--error ER_PARSE_ERROR +SELECT * FROM t1 TABLESAMPLE BERNOULLI (NULL); + +--error ER_PARSE_ERROR +SELECT * FROM t1 TABLESAMPLE NONEXISTENTMETHOD (12.5); + +--error ER_PARSE_ERROR +SELECT * FROM t1 TABLESAMPLE SYSTEM (-12.5); + +SELECT * FROM t1 TABLESAMPLE SYSTEM (10) JOIN t2 ON t1.a = t2.a; + +--error ER_SYNTAX_ERROR +SELECT * FROM v1 TABLESAMPLE SYSTEM (20); + +# +# Testing Prepared statements +# +PREPARE stmt2 FROM 'SELECT * FROM t1 TABLESAMPLE BERNOULLI (?)'; + +SET @pct = 30; +EXECUTE stmt2 USING @pct; + +SET @pct = 300; +--error ER_SYNTAX_ERROR +EXECUTE stmt2 USING @pct; + +SET @pct = NULL; +--error ER_SYNTAX_ERROR +EXECUTE stmt2 USING @pct; + +DEALLOCATE PREPARE stmt2; + +# +# Testing stored procedures +# +DROP PROCEDURE IF EXISTS p1; + +DELIMITER //; +CREATE PROCEDURE p1(IN sample_pct INT) +BEGIN + SELECT * FROM t1 TABLESAMPLE BERNOULLI (sample_pct); +END// +DELIMITER ;// + +CALL p1(40); + +--error ER_SYNTAX_ERROR +CALL p1(101); + +DROP PROCEDURE p1; + +DELIMITER //; +CREATE PROCEDURE p1(IN sample_pct DECIMAL) +BEGIN + SELECT * FROM t1 TABLESAMPLE SYSTEM (sample_pct); +END// +DELIMITER ;// + +CALL p1(20.11); + +DROP PROCEDURE p1; + +DROP PROCEDURE IF EXISTS p_char; + +DELIMITER //; +--error ER_WRONG_SPVAR_TYPE_IN_TABLESAMPLE +CREATE PROCEDURE p_char(IN sample_pct CHAR(10)) +BEGIN + SELECT * FROM t1 TABLESAMPLE SYSTEM (sample_pct); +END// +DELIMITER ;// + +DROP PROCEDURE IF EXISTS p_date; + +DELIMITER //; +--error ER_WRONG_SPVAR_TYPE_IN_TABLESAMPLE +CREATE PROCEDURE p_date(IN sample_pct DATE) +BEGIN + SELECT * FROM t1 TABLESAMPLE BERNOULLI (sample_pct); +END// +DELIMITER ;// + +DROP PROCEDURE IF EXISTS p1; + +DELIMITER //; +CREATE PROCEDURE p1() +BEGIN + DECLARE c SYS_REFCURSOR; + OPEN c FOR 'SELECT * FROM t1 TABLESAMPLE BERNOULLI (?)' USING NULL; +END// +DELIMITER ;// + +--error ER_SYNTAX_ERROR +CALL p1(); + +DROP PROCEDURE p1; + +# +# TABLESAMPLE should not work on system tables +# +--error ER_SYNTAX_ERROR +SELECT * FROM information_schema.tables TABLESAMPLE BERNOULLI (5); + +--error ER_SYNTAX_ERROR +SELECT * FROM mysql.user TABLESAMPLE SYSTEM (10); + +# +# TABLESAMPLE should not work on derived tables +# +--error ER_PARSE_ERROR +SELECT * FROM (SELECT * FROM t1) AS derived_tbl TABLESAMPLE SYSTEM (30); + +--error ER_SYNTAX_ERROR +WITH cte_tbl AS (SELECT * FROM t1) +SELECT * FROM cte_tbl TABLESAMPLE BERNOULLI (10); + +# INSERT INTO t1 VALUES (1, 1), (1, 2), (1, 3), (1, 4), (1, 6), (1, 7), (1, 8), (1, 9), (1, 10); +# INSERT INTO t2 VALUES (1, 1), (1, 2), (1, 3), (1, 4), (1, 6), (1, 7), (1, 8), (1, 9), (1, 10); + +# SELECT * FROM t1 TABLESAMPLE BERNOULLI (50); + +# SELECT * FROM t1 TABLESAMPLE BERNOULLI (50) JOIN t2 TABLESAMPLE BERNOULLI (50) ON t1.a = t2.a; + +# +# TABLESAMPLE SYSTEM without a primary key falls back to BERNOULLI +# +CREATE TABLE t_no_pk (a INT, b INT); +CREATE TABLE t_pk (a INT PRIMARY KEY, b INT); + +SELECT * FROM t_no_pk TABLESAMPLE SYSTEM (10); +SHOW WARNINGS; + +SELECT * FROM t_pk TABLESAMPLE SYSTEM (10); +SHOW WARNINGS; + +# +# TABLESAMPLE SYSTEM with a sampling percentage above the +# SYSTEM_TO_BERNOULLI_MAX_FRACTION threshold falls back to +# BERNOULLI +# +SELECT * FROM t_pk TABLESAMPLE SYSTEM (30); +SHOW WARNINGS; + +SELECT * FROM t_pk TABLESAMPLE SYSTEM (30.01); +SHOW WARNINGS; + +SELECT * FROM t_pk TABLESAMPLE SYSTEM (50); +SHOW WARNINGS; + +# Same parsed statement re-executed after the table's primary key changes: +# the fallback (and its warning) must reflect the CURRENT execution's table, +# not stick around from an earlier execution. +PREPARE stmt3 FROM 'SELECT * FROM t_no_pk TABLESAMPLE SYSTEM (10)'; + +EXECUTE stmt3; +SHOW WARNINGS; + +ALTER TABLE t_no_pk ADD PRIMARY KEY (a); + +EXECUTE stmt3; +SHOW WARNINGS; + +DEALLOCATE PREPARE stmt3; +DROP TABLE t_no_pk, t_pk; + +--source include/have_sequence.inc + +CREATE TABLE t_data (a INT PRIMARY KEY, b INT) ENGINE=MyISAM; +INSERT INTO t_data SELECT seq, seq * 2 FROM seq_1_to_20; + +# SELECT * FROM t_data TABLESAMPLE SYSTEM (20); + +# SELECT * FROM t_data as t1 TABLESAMPLE BERNOULLI (20) JOIN t_data as t2 TABLESAMPLE BERNOULLI (30) ON t1.a = t2.a; + +# +# A TABLESAMPLE'd table joined via hash join (BNLH) shows "hash_sample" +# in EXPLAIN, not the misleading "hash_ALL" +# +SET @save_join_cache_level= @@join_cache_level; +SET join_cache_level= 3; + +EXPLAIN SELECT * FROM t_data AS t1 TABLESAMPLE BERNOULLI (20) + JOIN t_data AS t2 TABLESAMPLE BERNOULLI (30) ON t1.a = t2.a; + +SET join_cache_level= @save_join_cache_level; + +# +# Cleanup +# +DROP TABLE t1, t2, t_data; +DROP VIEW v1; \ No newline at end of file diff --git a/mysql-test/suite/perfschema/r/digest_view.result b/mysql-test/suite/perfschema/r/digest_view.result index 346b5e0c44e60..8e35daac2ae79 100644 --- a/mysql-test/suite/perfschema/r/digest_view.result +++ b/mysql-test/suite/perfschema/r/digest_view.result @@ -191,17 +191,17 @@ SELECT SCHEMA_NAME, DIGEST, DIGEST_TEXT, COUNT_STAR FROM performance_schema.events_statements_summary_by_digest ORDER BY DIGEST_TEXT; SCHEMA_NAME DIGEST DIGEST_TEXT COUNT_STAR -test cc5e38c5a702f49627052e59a2603818 EXPLAIN SELECT * FROM `test` . `v1` 1 -test 264b69debfd30bbfe374cdca018fa4f9 EXPLAIN SELECT * FROM `test` . `v1` WHERE `a` = ? 1 -test 75f31bf60b75a4f851ff9c9ee4e19d96 EXPLAIN SELECT * FROM `test` . `v1` WHERE `b` > ? 1 -test a8f8a85697afacda9f2f3d3b023f9ed0 EXPLAIN SELECT `a` , `b` FROM `test` . `v1` 1 -test 512598992d37826136f3f1292bb32e1e EXPLAIN SELECT `b` , `a` FROM `test` . `v1` 1 -test 54342cc19df16ce54b3ba43d1fd7552d SELECT * FROM `test` . `v1` 1 -test 3750b7d8c33e040b90a2cdccb4c642a3 SELECT * FROM `test` . `v1` WHERE `a` = ? 1 -test 94af70ef76a31364845f534863d99da8 SELECT * FROM `test` . `v1` WHERE `b` > ? 1 -test 0c87d86b62e664a81a23d4f176fbb377 SELECT `a` , `b` FROM `test` . `v1` 1 -test de7e6f1350ff14a952b97b373d76c1a4 SELECT `b` , `a` FROM `test` . `v1` 1 -test 3468091e6d6ce474aded20beaec36b53 TRUNCATE TABLE `performance_schema` . `events_statements_summary_by_digest` 1 +test c699db3b539c46397d75248202bfa2c0 EXPLAIN SELECT * FROM `test` . `v1` 1 +test 0533091953f8f00b28ba736031fbd34a EXPLAIN SELECT * FROM `test` . `v1` WHERE `a` = ? 1 +test 9d01d8279d3efdd1ca19525ab81867f5 EXPLAIN SELECT * FROM `test` . `v1` WHERE `b` > ? 1 +test e738c5785ebf35d0dc3294c57cac56b2 EXPLAIN SELECT `a` , `b` FROM `test` . `v1` 1 +test e529550d920a19bd9af0ff7b8be014db EXPLAIN SELECT `b` , `a` FROM `test` . `v1` 1 +test 8f07567d81b04a27905f93f4d21ae704 SELECT * FROM `test` . `v1` 1 +test 18892865d73c2762bf2116f65e2783f5 SELECT * FROM `test` . `v1` WHERE `a` = ? 1 +test 9e5974b88fe2d62ce119b11a438e6af9 SELECT * FROM `test` . `v1` WHERE `b` > ? 1 +test 895a6385ee5c0386780deec58dad43a6 SELECT `a` , `b` FROM `test` . `v1` 1 +test b25031d48f079aa480fd6c84baf8eb2b SELECT `b` , `a` FROM `test` . `v1` 1 +test 76be74b215fee3701da4d982dd87cd48 TRUNCATE TABLE `performance_schema` . `events_statements_summary_by_digest` 1 DROP TABLE test.v1; CREATE VIEW test.v1 AS SELECT * FROM test.t1; EXPLAIN SELECT * from test.v1; @@ -248,19 +248,19 @@ SELECT SCHEMA_NAME, DIGEST, DIGEST_TEXT, COUNT_STAR FROM performance_schema.events_statements_summary_by_digest ORDER BY DIGEST_TEXT; SCHEMA_NAME DIGEST DIGEST_TEXT COUNT_STAR -test ef8b10f452e117fa96af49687a93a10f CREATE VIEW `test` . `v1` AS SELECT * FROM `test` . `t1` 1 -test 6b99f4d2ad410f9fa4ee1d501b0db571 DROP TABLE `test` . `v1` 1 -test cc5e38c5a702f49627052e59a2603818 EXPLAIN SELECT * FROM `test` . `v1` 2 -test 264b69debfd30bbfe374cdca018fa4f9 EXPLAIN SELECT * FROM `test` . `v1` WHERE `a` = ? 2 -test 75f31bf60b75a4f851ff9c9ee4e19d96 EXPLAIN SELECT * FROM `test` . `v1` WHERE `b` > ? 2 -test a8f8a85697afacda9f2f3d3b023f9ed0 EXPLAIN SELECT `a` , `b` FROM `test` . `v1` 2 -test 512598992d37826136f3f1292bb32e1e EXPLAIN SELECT `b` , `a` FROM `test` . `v1` 2 -test 54342cc19df16ce54b3ba43d1fd7552d SELECT * FROM `test` . `v1` 2 -test 3750b7d8c33e040b90a2cdccb4c642a3 SELECT * FROM `test` . `v1` WHERE `a` = ? 2 -test 94af70ef76a31364845f534863d99da8 SELECT * FROM `test` . `v1` WHERE `b` > ? 2 -test 187a846fafe04b746eddaaab80b6a766 SELECT SCHEMA_NAME , `DIGEST` , `DIGEST_TEXT` , `COUNT_STAR` FROM `performance_schema` . `events_statements_summary_by_digest` ORDER BY `DIGEST_TEXT` 1 -test 0c87d86b62e664a81a23d4f176fbb377 SELECT `a` , `b` FROM `test` . `v1` 2 -test de7e6f1350ff14a952b97b373d76c1a4 SELECT `b` , `a` FROM `test` . `v1` 2 -test 3468091e6d6ce474aded20beaec36b53 TRUNCATE TABLE `performance_schema` . `events_statements_summary_by_digest` 1 +test f852e38b9370fbfe50ceb58f1b214695 CREATE VIEW `test` . `v1` AS SELECT * FROM `test` . `t1` 1 +test d61fc37b0d8796fa1760a9d0ffc1ca4e DROP TABLE `test` . `v1` 1 +test c699db3b539c46397d75248202bfa2c0 EXPLAIN SELECT * FROM `test` . `v1` 2 +test 0533091953f8f00b28ba736031fbd34a EXPLAIN SELECT * FROM `test` . `v1` WHERE `a` = ? 2 +test 9d01d8279d3efdd1ca19525ab81867f5 EXPLAIN SELECT * FROM `test` . `v1` WHERE `b` > ? 2 +test e738c5785ebf35d0dc3294c57cac56b2 EXPLAIN SELECT `a` , `b` FROM `test` . `v1` 2 +test e529550d920a19bd9af0ff7b8be014db EXPLAIN SELECT `b` , `a` FROM `test` . `v1` 2 +test 8f07567d81b04a27905f93f4d21ae704 SELECT * FROM `test` . `v1` 2 +test 18892865d73c2762bf2116f65e2783f5 SELECT * FROM `test` . `v1` WHERE `a` = ? 2 +test 9e5974b88fe2d62ce119b11a438e6af9 SELECT * FROM `test` . `v1` WHERE `b` > ? 2 +test 7fc6b5fae4fbeb78bccd2f9771c67075 SELECT SCHEMA_NAME , `DIGEST` , `DIGEST_TEXT` , `COUNT_STAR` FROM `performance_schema` . `events_statements_summary_by_digest` ORDER BY `DIGEST_TEXT` 1 +test 895a6385ee5c0386780deec58dad43a6 SELECT `a` , `b` FROM `test` . `v1` 2 +test b25031d48f079aa480fd6c84baf8eb2b SELECT `b` , `a` FROM `test` . `v1` 2 +test 76be74b215fee3701da4d982dd87cd48 TRUNCATE TABLE `performance_schema` . `events_statements_summary_by_digest` 1 DROP VIEW test.v1; DROP TABLE test.t1; diff --git a/mysql-test/suite/perfschema/r/start_server_low_digest_sql_length.result b/mysql-test/suite/perfschema/r/start_server_low_digest_sql_length.result index e4e2c8b294a3f..975f52df7c3bf 100644 --- a/mysql-test/suite/perfschema/r/start_server_low_digest_sql_length.result +++ b/mysql-test/suite/perfschema/r/start_server_low_digest_sql_length.result @@ -8,5 +8,5 @@ SELECT 1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1 #################################### SELECT event_name, digest, digest_text, sql_text FROM events_statements_history_long; event_name digest digest_text sql_text -statement/sql/select c9e142fe40c43498607ca5310e11d2ce SELECT ? + ? + SELECT ... -statement/sql/truncate 506e3496d92689cd2367329dc7725165 TRUNCATE TABLE truncat... +statement/sql/select 6e58e4950cd512b73ca69386ac9b919a SELECT ? + ? + SELECT ... +statement/sql/truncate fbc57747ac49e51b80f0f72177d8ff52 TRUNCATE TABLE truncat... diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt index 429a85f9cac30..ce9f914464bbd 100644 --- a/sql/CMakeLists.txt +++ b/sql/CMakeLists.txt @@ -185,6 +185,7 @@ SET (SQL_SOURCE sql_type_string.cc sql_type_geom.cc sql_type_vector.cc item_windowfunc.cc sql_window.cc + sql_tablesample.cc sql_cte.cc item_vers.cc sql_sequence.cc sql_sequence.h ha_sequence.h @@ -428,6 +429,7 @@ IF(NOT CMAKE_CROSSCOMPILING OR DEFINED CMAKE_CROSSCOMPILING_EMULATOR) ADD_EXECUTABLE(gen_lex_token gen_lex_token.cc ${CMAKE_CURRENT_BINARY_DIR}/yy_mariadb.hh) TARGET_LINK_LIBRARIES(gen_lex_token mysys dbug) # required by `yy_mariadb.hh` + ADD_DEPENDENCIES(gen_lex_token GenError) # sql_yacc.yy pulls in mysqld_error.h via item.h ADD_EXECUTABLE(gen_lex_hash gen_lex_hash.cc) ENDIF() diff --git a/sql/handler.cc b/sql/handler.cc index c915ccabc28f9..b86c56d1d6c4c 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -4314,6 +4314,45 @@ int handler::ha_index_next_same(uchar *buf, const uchar *key, uint keylen) } +/** + Land on an approximately random row of the active index. + + Used by TABLESAMPLE SYSTEM (@see rr_sampling_system()), which needs to + visit a random sample of rows without reading every row like BERNOULLI + sampling does. The active index (opened with ha_index_init() by the + caller) is assumed to be the table's primary key, i.e. a full row is + available at every key value. The actual dive strategy is implemented + by the index_random_dive() virtual, which storage engines override. + + @param buf Buffer to read the found row into. + @param rand_state Random generator state to use for the dive. + + @retval 0 on success, otherwise the error returned by the underlying + index operation (including end-of-file style errors). +*/ + +int handler::ha_index_random_dive(uchar *buf, struct my_rnd_struct *rand_state) +{ + int result; + DBUG_ENTER("handler::ha_index_random_dive"); + DBUG_ASSERT(table_share->tmp_table != NO_TMP_TABLE || + m_lock_type != F_UNLCK); + DBUG_ASSERT(inited == INDEX); + + TABLE_IO_WAIT(tracker, PSI_TABLE_FETCH_ROW, active_index, result, + { result= index_random_dive(buf, rand_state); }) + increment_statistics(&SSV::ha_read_key_count); + if (!result) + { + update_index_statistics(); + if (table->vfield && buf == table->record[0]) + table->update_virtual_fields(this, VCOL_UPDATE_FOR_READ); + } + table->status= result ? STATUS_NOT_FOUND : 0; + DBUG_RETURN(result); +} + + bool handler::ha_was_semi_consistent_read() { bool result= was_semi_consistent_read(); diff --git a/sql/handler.h b/sql/handler.h index fa9196f189a8f..cc4daa5a9925f 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -4409,6 +4409,16 @@ class handler :public Sql_alloc virtual int index_last(uchar * buf) { return HA_ERR_WRONG_COMMAND; } virtual int index_next_same(uchar *buf, const uchar *key, uint keylen); + /** + @brief + Positions an index cursor to an approximately random row of the + active index and fetches it into buf. Storage engines that want to + support TABLESAMPLE SYSTEM must override this with an implementation + that exploits their own index structure (e.g. picking a random leaf + page). + */ + virtual int index_random_dive(uchar *buf, struct my_rnd_struct *rand_state) + { return HA_ERR_WRONG_COMMAND; } /** @brief The following functions works like index_read, but it find the last @@ -4447,6 +4457,7 @@ class handler :public Sql_alloc int ha_index_first(uchar * buf); int ha_index_last(uchar * buf); int ha_index_next_same(uchar *buf, const uchar *key, uint keylen); + int ha_index_random_dive(uchar *buf, struct my_rnd_struct *rand_state); /* TODO: should we make for those functions non-virtual ha_func_name wrappers, too? diff --git a/sql/item.cc b/sql/item.cc index b5fbab9f6aeda..f7bb1abda33e1 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -4708,6 +4708,22 @@ bool Item_param::set_from_item(THD *thd, Item *item) DBUG_RETURN(set_limit_clause_param(val)); } } + if (tablesample_clause_param) + { + double val= item->val_real(); + if (item->null_value) + { + set_null(DTCollation_numeric()); + set_handler(&type_handler_null); + DBUG_RETURN(false); + } + else + { + unsigned_flag= item->unsigned_flag; + set_handler(item->type_handler()); + DBUG_RETURN(set_tablesample_clause_param(val)); + } + } st_value tmp; item->save_in_value(thd, &tmp); DBUG_RETURN(set_from_value(thd, tmp, item->type_handler(), *item)); diff --git a/sql/item.h b/sql/item.h index dabe4ca1c8801..d5c5dd375138c 100644 --- a/sql/item.h +++ b/sql/item.h @@ -526,10 +526,11 @@ class Rewritable_query_parameter uint len_in_query; bool limit_clause_param; + bool tablesample_clause_param; Rewritable_query_parameter(uint pos_in_q= 0, uint len_in_q= 0) : pos_in_query(pos_in_q), len_in_query(len_in_q), - limit_clause_param(false) + limit_clause_param(false), tablesample_clause_param(false) { } virtual ~Rewritable_query_parameter() = default; @@ -3405,7 +3406,7 @@ class Item_splocal :public Item_sp_variable, Field *create_field_for_create_select(MEM_ROOT *root, TABLE *table) override { return create_table_field_from_handler(root, table); } - bool is_valid_limit_clause_variable_with_error() const + bool is_valid_numeric_clause_variable_with_error() const { /* In case if the variable has an anchored data type, e.g.: @@ -3413,12 +3414,20 @@ class Item_splocal :public Item_sp_variable, type_handler() is set to &type_handler_null and this function detects such variable as not valid in LIMIT. */ - if (type_handler()->is_limit_clause_valid_type()) + if (type_handler()->is_numeric_clause_valid_type()) return true; my_error(ER_WRONG_SPVAR_TYPE_IN_LIMIT, MYF(0)); return false; } + bool is_valid_tablesample_clause_variable_with_error() const + { + if (type_handler()->is_tablesample_clause_valid_type()) + return true; + my_error(ER_WRONG_SPVAR_TYPE_IN_TABLESAMPLE, MYF(0)); + return false; + } + protected: Item *shallow_copy(THD *thd) const override { return get_item_copy(thd, this); } @@ -4746,6 +4755,12 @@ class Item_param final :public Item_basic_value, set_int(nr, MY_INT64_NUM_DECIMAL_DIGITS); return !unsigned_flag && value.integer < 0; } + bool set_tablesample_clause_param(double d) + { + value.set_handler(&type_handler_double); + set_double(d); + return !unsigned_flag && value.real < 0; + } const String *query_val_str(THD *thd, String *str) const; bool convert_str_value(THD *thd); @@ -4780,6 +4795,11 @@ class Item_param final :public Item_basic_value, return state == SHORT_DATA_VALUE && value.type_handler()->cmp_type() == INT_RESULT; } + bool has_double_value() const + { + return state == SHORT_DATA_VALUE && + value.type_handler()->cmp_type() == REAL_RESULT; + } bool is_stored_routine_parameter() const override { return true; } /* This method is used to make a copy of a basic constant item when diff --git a/sql/lex.h b/sql/lex.h index 61774df019f0d..a2f73eb6b0961 100644 --- a/sql/lex.h +++ b/sql/lex.h @@ -86,6 +86,7 @@ SYMBOL symbols[] = { { "BACKUP", SYM(BACKUP_SYM)}, { "BEFORE", SYM(BEFORE_SYM)}, { "BEGIN", SYM(BEGIN_MARIADB_SYM)}, + { "BERNOULLI", SYM(BERNOULLI)}, { "BETWEEN", SYM(BETWEEN_SYM)}, { "BIGINT", SYM(BIGINT)}, { "BINARY", SYM(BINARY)}, @@ -663,6 +664,7 @@ SYMBOL symbols[] = { { "TABLE", SYM(TABLE_SYM)}, { "TABLE_NAME", SYM(TABLE_NAME_SYM)}, { "TABLES", SYM(TABLES)}, + { "TABLESAMPLE", SYM(TABLESAMPLE_SYM)}, { "TABLESPACE", SYM(TABLESPACE)}, { "TABLE_CHECKSUM", SYM(TABLE_CHECKSUM_SYM)}, { "TEMPORARY", SYM(TEMPORARY)}, diff --git a/sql/opt_hints.cc b/sql/opt_hints.cc index 7d92f3c0adbe6..73a5d3e62c9c6 100644 --- a/sql/opt_hints.cc +++ b/sql/opt_hints.cc @@ -958,6 +958,18 @@ void Opt_hints_table::update_index_hint_map(Key_map *keys_to_use, bool Opt_hints_table::update_index_hint_maps(THD *thd, TABLE *tbl) { + if (tbl->pos_in_table_list && tbl->pos_in_table_list->tablesample_clause) + { + tbl->keys_in_use_for_query.clear_all(); + tbl->keys_in_use_for_group_by.clear_all(); + tbl->keys_in_use_for_order_by.clear_all(); + tbl->keys_in_use_for_rowid_filter.clear_all(); + tbl->covering_keys.clear_all(); + tbl->force_index= tbl->force_index_join= tbl->force_index_group= + tbl->force_index_order= false; + return true; // handled: caller must not also run process_index_hints() + } + if (!is_fixed(INDEX_HINT_ENUM) && !is_fixed(JOIN_INDEX_HINT_ENUM) && !is_fixed(GROUP_INDEX_HINT_ENUM) && !is_fixed(ORDER_INDEX_HINT_ENUM) && !is_fixed(ROWID_FILTER_HINT_ENUM)) diff --git a/sql/records.cc b/sql/records.cc index 5a7f5385f06c5..5c613e6bbf710 100644 --- a/sql/records.cc +++ b/sql/records.cc @@ -30,9 +30,13 @@ #include "sql_class.h" // THD #include "sql_base.h" #include "sql_sort.h" // SORT_ADDON_FIELD +#include "sql_tablesample.h" +#include "key.h" // key_copy static int rr_quick(READ_RECORD *info); int rr_sequential(READ_RECORD *info); +int rr_sampling_bernoulli(READ_RECORD *info); +int rr_sampling_system(READ_RECORD *info); static int rr_from_tempfile(READ_RECORD *info); template static int rr_unpack_from_tempfile(READ_RECORD *info); template static int rr_unpack_from_buffer(READ_RECORD *info); @@ -187,6 +191,8 @@ bool init_read_record(READ_RECORD *info,THD *thd, TABLE *table, const bool using_addon_fields= filesort && filesort->using_addon_fields(); bool using_packed_sortkeys= filesort && filesort->using_packed_sortkeys(); + const bool has_tablesample= table->pos_in_table_list + && table->pos_in_table_list->tablesample_clause; bzero((char*) info,sizeof(*info)); info->thd=thd; @@ -316,6 +322,38 @@ bool init_read_record(READ_RECORD *info,THD *thd, TABLE *table, DBUG_RETURN(1); } } + else if (has_tablesample) + { + DBUG_PRINT("info",("using rr_sampling")); + Lex_tablesample *tablesample_clause= + table->pos_in_table_list->tablesample_clause; + enum tablesample_method_enum sampling_method= + tablesample_clause->get_effective_sampling_method(); + if (sampling_method == tablesample_method_enum::TABLESAMPLE_BERNOULLI) + { + info->read_record_func= rr_sampling_bernoulli; + if (unlikely(table->file->ha_rnd_init_with_error(1))) + DBUG_RETURN(1); + } + else + { + int error; + info->read_record_func= rr_sampling_system; + info->sample_key= table->s->primary_key; + if (!table->file->inited && + unlikely((error= table->file->ha_index_init(info->sample_key, 0)))) + { + if (print_error) + table->file->print_error(error, MYF(0)); + DBUG_RETURN(1); + } + my_hash_init(PSI_INSTRUMENT_ME, &info->sample_seen_keys, + &my_charset_bin, 0, 0, + table->key_info[info->sample_key].key_length, + NULL, my_free, HASH_UNIQUE); + } + tablesample_clause->seed_sample_rand(&info->sample_rand); + } else { DBUG_PRINT("info",("using rr_sequential")); @@ -348,6 +386,8 @@ void end_read_record(READ_RECORD *info) { /* free cache if used */ free_cache(info); + if (my_hash_inited(&info->sample_seen_keys)) + my_hash_free(&info->sample_seen_keys); if (info->table) { if (info->table->db_stat) // if opened @@ -515,6 +555,75 @@ int rr_sequential(READ_RECORD *info) } +int rr_sampling_bernoulli(READ_RECORD *info) +{ + int tmp; + const double p= info->table->pos_in_table_list->tablesample_clause-> + get_sampling_percentage_fraction(); + + for (;;) + { + tmp= info->table->file->ha_rnd_next(info->record()); + if (tmp) + { + tmp= rr_handle_error(info, tmp); + break; + } + if (my_rnd(&info->sample_rand) < p) + break; + } + return tmp; +} + + +int rr_sampling_system(READ_RECORD *info) +{ + int tmp; + handler *file= info->table->file; + KEY *key_info= &info->table->key_info[info->sample_key]; + const uint key_length= key_info->key_length; + + /* + used_stat_records was scaled down to sampling_fraction * total_rows + before + */ + if (info->sample_seen_keys.records >= info->table->stat_records()) + return rr_handle_error(info, HA_ERR_END_OF_FILE); + + for (;;) + { + tmp= file->ha_index_random_dive(info->record(), &info->sample_rand); + if (tmp) + { + tmp= rr_handle_error(info, tmp); + break; + } + + uchar *key_buff= (uchar*) my_malloc(PSI_INSTRUMENT_ME, key_length, + MYF(MY_WME)); + if (unlikely(!key_buff)) + { + tmp= HA_ERR_OUT_OF_MEM; + break; + } + key_copy(key_buff, info->record(), key_info, key_length); + + if (my_hash_search(&info->sample_seen_keys, key_buff, key_length)) + { + my_free(key_buff); + continue; // already returned this row, dive again + } + + if (unlikely(my_hash_insert(&info->sample_seen_keys, key_buff))) + { + my_free(key_buff); + tmp= HA_ERR_OUT_OF_MEM; + } + break; + } + return tmp; +} + static int rr_from_tempfile(READ_RECORD *info) { int tmp; diff --git a/sql/records.h b/sql/records.h index 48ce8e2c91752..22b1345911a37 100644 --- a/sql/records.h +++ b/sql/records.h @@ -16,6 +16,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */ #include "table.h" +#include "hash.h" struct st_join_table; class handler; @@ -67,6 +68,18 @@ struct READ_RECORD uchar *rec_buf; /* to read field values after filesort */ uchar *cache,*cache_pos,*cache_end,*read_positions; + // initialised only if dealing with tablesample clause + struct my_rnd_struct sample_rand; + // initialised only when using rr_sampling_system (TABLESAMPLE SYSTEM) + uint sample_key; + /* + Primary key values of the rows already returned by rr_sampling_system(), + so that a later ha_index_random_dive() landing on a row we already + returned can be recognised and skipped instead of yielding a duplicate. + Initialised only when using rr_sampling_system (TABLESAMPLE SYSTEM). + */ + HASH sample_seen_keys; + /* Structure storing information about sorting */ @@ -84,7 +97,7 @@ struct READ_RECORD Copy_field *copy_field; Copy_field *copy_field_end; public: - READ_RECORD() : table(NULL), cache(NULL) {} + READ_RECORD() : table(NULL), cache(NULL), sample_seen_keys() {} ~READ_RECORD() { end_read_record(this); } }; diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt index d6436255da7d1..9394e2bf19f9b 100644 --- a/sql/share/errmsg-utf8.txt +++ b/sql/share/errmsg-utf8.txt @@ -12418,3 +12418,9 @@ ER_JSON_OPTIMIZER_REPLAY_CONTEXT_PARSE_FAILED eng "Failed to parse saved optimizer context: %s at offset %d." ER_JSON_OPTIMIZER_REPLAY_CONTEXT_MATCH_FAILED eng "Failed to match the stats from replay context with the optimizer stats: %s doesn't exist in %s" +ER_WRONG_SPVAR_TYPE_IN_TABLESAMPLE + eng "A variable of a non-numeric based type in TABLESAMPLE clause" +ER_TABLESAMPLE_SYSTEM_NO_PK + eng "TABLESAMPLE SYSTEM requires a primary key on table '%-.192s'; using BERNOULLI sampling instead" +ER_TABLESAMPLE_SYSTEM_HIGH_PCT + eng "TABLESAMPLE SYSTEM on table '%-.192s' requested more than %g%% of rows; using BERNOULLI sampling instead" diff --git a/sql/sp_head.cc b/sql/sp_head.cc index 1c2b199777351..8e95a781a03bf 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -121,6 +121,8 @@ bool Item_splocal::append_for_log(THD *thd, String *str) if (limit_clause_param) return str->append_ulonglong(val_uint()); + if (tablesample_clause_param) + return str->append_double(val_real()); /* ROW variables are currently not allowed in select_list, e.g.: @@ -159,6 +161,8 @@ bool Item_splocal_row_field::append_for_log(THD *thd, String *str) if (limit_clause_param) return str->append_ulonglong(val_uint()); + if (tablesample_clause_param) + return str->append_double(val_real()); if (str->append(STRING_WITH_LEN(" NAME_CONST('")) || str->append(&m_name) || diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index 450fa893f32a8..d63cb61ed9b16 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -2750,7 +2750,15 @@ class Grant_tables DBUG_RETURN(res); if (build_table_list(thd, &first, which_tables, lock_type, tables)) - goto func_exit; + { + /* + No table in 'tables' has been touched yet (all entries are still + uninitialized memory from the my_malloc() above), so we must not + run the pos_in_table_list cleanup loop in func_exit against it. + */ + my_free(tables); + DBUG_RETURN(res); + } res= really_open(thd, first, &counter); @@ -2802,6 +2810,9 @@ class Grant_tables } func_exit: + for (int i= USER_TABLE; i >= 0; i--) + if (tables[i].table) + tables[i].table->pos_in_table_list= NULL; my_free(tables); DBUG_RETURN(res); } diff --git a/sql/sql_base.cc b/sql/sql_base.cc index b451b9bd6e9e2..74d8c2b206ff6 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -69,6 +69,7 @@ #include "wsrep_trans_observer.h" #endif /* WITH_WSREP */ #include "opt_hints.h" +#include "sql_tablesample.h" bool No_such_table_error_handler::handle_condition(THD *, @@ -8557,6 +8558,14 @@ bool setup_tables(THD *thd, Name_resolution_context *context, table_list->alias); } + if (table_list->tablesample_clause && (get_table_category( + table_list->db, table_list->table_name) != TABLE_CATEGORY_USER || + table_list->tablesample_clause->fix_tablesample_fields(thd, table))) + { + my_error(ER_SYNTAX_ERROR, MYF(0)); + DBUG_RETURN(1); + } + if (!table_list->opt_hints_table || !table_list->opt_hints_table->update_index_hint_maps(thd, table)) { @@ -8609,6 +8618,14 @@ bool setup_tables(THD *thd, Name_resolution_context *context, table->maybe_null= table_list->maybe_null_exec; table->pos_in_table_list= table_list; + if (table_list->tablesample_clause && (get_table_category( + table_list->db, table_list->table_name) != TABLE_CATEGORY_USER || + table_list->tablesample_clause->fix_tablesample_fields(thd, table))) + { + my_error(ER_SYNTAX_ERROR, MYF(0)); + DBUG_RETURN(1); + } + if (!table_list->opt_hints_table || !table_list->opt_hints_table->update_index_hint_maps(thd, table)) { @@ -8628,6 +8645,12 @@ bool setup_tables(THD *thd, Name_resolution_context *context, table_list; table_list= table_list->next_local) { + if (table_list->tablesample_clause && table_list->is_view_or_derived()) + { + my_error(ER_SYNTAX_ERROR, MYF(0)); + DBUG_RETURN(1); + } + if (table_list->is_merged_derived() && table_list->merge_underlying_list) { Query_arena *arena, backup; diff --git a/sql/sql_explain.cc b/sql/sql_explain.cc index 2e894deae5fce..bfb9d97d3ce87 100644 --- a/sql/sql_explain.cc +++ b/sql/sql_explain.cc @@ -1394,8 +1394,9 @@ void Explain_table_access::push_extra(enum explain_extra_tag extra_tag) void Explain_table_access::fill_key_str(String *key_str, bool is_json) const { CHARSET_INFO *cs= &my_charset_utf8mb4_bin; - bool is_hj= (type == JT_HASH || type == JT_HASH_NEXT || - type == JT_HASH_RANGE || type == JT_HASH_INDEX_MERGE); + bool is_hj= (type == JT_HASH || type == JT_HASH_NEXT || + type == JT_HASH_RANGE || type == JT_HASH_INDEX_MERGE || + type == JT_HASH_SAMPLE); LEX_CSTRING hash_key_prefix= { STRING_WITH_LEN("#hash#") }; const char *key_name; @@ -1441,8 +1442,9 @@ void Explain_table_access::fill_key_str(String *key_str, bool is_json) const void Explain_table_access::fill_key_len_str(String *key_len_str, bool is_json) const { - bool is_hj= (type == JT_HASH || type == JT_HASH_NEXT || - type == JT_HASH_RANGE || type == JT_HASH_INDEX_MERGE); + bool is_hj= (type == JT_HASH || type == JT_HASH_NEXT || + type == JT_HASH_RANGE || type == JT_HASH_INDEX_MERGE || + type == JT_HASH_SAMPLE); if (key.get_key_len() != (uint)-1) { char buf[64]; diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 6e1c6df4715b7..7cec455879e26 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -9575,7 +9575,7 @@ bool LEX::mark_item_ident_for_ora_join(THD *thd, Item *item) } -Item *LEX::create_item_limit(THD *thd, const Lex_ident_cli_st *ca) +Item_splocal *LEX::create_item(THD *thd, const Lex_ident_cli_st *ca) { DBUG_ASSERT(thd->m_parser_state->m_lip.get_buf() <= ca->pos()); DBUG_ASSERT(ca->pos() <= ca->end()); @@ -9605,15 +9605,36 @@ Item *LEX::create_item_limit(THD *thd, const Lex_ident_cli_st *ca) #endif safe_to_cache_query= 0; - if (!item->is_valid_limit_clause_variable_with_error()) + return item; +} + +Item *LEX::create_item_limit(THD *thd, const Lex_ident_cli_st *ca) +{ + Item_splocal *num_item = create_item(thd, ca); + if (!num_item) + return NULL; + + if (!num_item->is_valid_numeric_clause_variable_with_error()) return NULL; - item->limit_clause_param= true; - return item; + num_item->limit_clause_param= true; + return num_item; } +Item *LEX::create_item_tablesample(THD *thd, const Lex_ident_cli_st *ca) +{ + Item_splocal *num_item = create_item(thd, ca); + if (!num_item) + return NULL; + + if (!num_item->is_valid_tablesample_clause_variable_with_error()) + return NULL; -Item *LEX::create_item_limit(THD *thd, + num_item->tablesample_clause_param= true; + return num_item; +} + +Item_splocal *LEX::create_item(THD *thd, const Lex_ident_cli_st *ca, const Lex_ident_cli_st *cb) { @@ -9637,12 +9658,39 @@ Item *LEX::create_item_limit(THD *thd, if (unlikely(!(item= create_item_spvar_row_field(thd, rh, &sa, &sb, spv, ca->pos(), cb->end())))) return NULL; - if (!item->is_valid_limit_clause_variable_with_error()) - return NULL; - item->limit_clause_param= true; + return item; } +Item *LEX::create_item_limit(THD *thd, + const Lex_ident_cli_st *ca, + const Lex_ident_cli_st *cb) +{ + Item_splocal *num_item = create_item(thd, ca, cb); + if (!num_item) + return NULL; + + if (!num_item->is_valid_numeric_clause_variable_with_error()) + return NULL; + + num_item->limit_clause_param= true; + return num_item; +} + +Item *LEX::create_item_tablesample(THD *thd, + const Lex_ident_cli_st *ca, + const Lex_ident_cli_st *cb) +{ + Item_splocal *num_item = create_item(thd, ca, cb); + if (!num_item) + return NULL; + + if (!num_item->is_valid_tablesample_clause_variable_with_error()) + return NULL; + + num_item->tablesample_clause_param= true; + return num_item; +} bool LEX::set_user_variable(THD *thd, const LEX_CSTRING *name, Item *val) { diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 5e0258ee42bf3..6ebf18ba28279 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -2092,6 +2092,12 @@ class Query_tables_list */ BINLOG_STMT_UNSAFE_SKIP_LOCKED, + /** + SELECT..TABLESAMPLE is unsafe because the set of rows returned cannot + be predicted. + */ + BINLOG_STMT_UNSAFE_TABLESAMPLE, + /* The last element of this enumeration type. */ BINLOG_STMT_UNSAFE_COUNT }; @@ -4495,6 +4501,34 @@ struct LEX: public Query_tables_list Longlong_hybrid value, ulonglong round, bool is_used); +private: + /* + Create an item for a name in numeric(LIMIT or TABLESAMPLE) clauses: + @param THD - THD, for mem_root + @param var_name - the variable name + @retval - a new Item corresponding to the SP variable, + or NULL on error + (non in SP, unknown variable, wrong data type). + */ + Item_splocal *create_item(THD *thd, const Lex_ident_cli_st *var_name); + + /* + Create an item for a qualified name in numeric(LIMIT or TABLESAMPLE) clause: + @param THD - THD, for mem_root + @param var_name - the variable name + @param field_name - the variable field name + @param start - start in the query (for binary log) + @param end - end in the query (for binary log) + @retval - a new Item corresponding to the SP variable, + or NULL on error + (non in SP, unknown variable, unknown ROW field, + wrong data type). + */ + Item_splocal *create_item(THD *thd, + const Lex_ident_cli_st *var_name, + const Lex_ident_cli_st *field_name); + +public: /* Create an item for a name in LIMIT clause: LIMIT var @param THD - THD, for mem_root @@ -4521,6 +4555,32 @@ struct LEX: public Query_tables_list const Lex_ident_cli_st *var_name, const Lex_ident_cli_st *field_name); + /* + Create an item for a name in TABLESAMPLE clause: SYSTEM(var) or BERNOULLI(var) + @param THD - THD, for mem_root + @param var_name - the variable name + @retval - a new Item corresponding to the SP variable, + or NULL on error + (non in SP, unknown variable, wrong data type). + */ + Item *create_item_tablesample(THD *thd, const Lex_ident_cli_st *var_name); + + /* + Create an item for a qualified name in TABLESAMPLE clause: SYSTEM(var.field) or BERNOULLI(var.field) + @param THD - THD, for mem_root + @param var_name - the variable name + @param field_name - the variable field name + @param start - start in the query (for binary log) + @param end - end in the query (for binary log) + @retval - a new Item corresponding to the SP variable, + or NULL on error + (non in SP, unknown variable, unknown ROW field, + wrong data type). + */ + Item *create_item_tablesample(THD *thd, + const Lex_ident_cli_st *var_name, + const Lex_ident_cli_st *field_name); + Item *create_item_query_expression(THD *thd, st_select_lex_unit *unit); Item *make_item_func_sysdate(THD *thd, uint fsp); diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index 6734c6ad911b8..db433c31fff11 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -294,6 +294,11 @@ class Prepared_statement: public Statement if (param->set_limit_clause_param(param->val_int())) DBUG_RETURN(true); } + if (param->tablesample_clause_param && !param->has_double_value()) + { + if (!param->is_null() && param->set_tablesample_clause_param(param->val_real())) + DBUG_RETURN(true); + } } DBUG_RETURN(false); } @@ -936,6 +941,11 @@ static bool insert_params_with_log(Prepared_statement *stmt, uchar *null_array, if (param->set_limit_clause_param(param->val_int())) DBUG_RETURN(1); } + if (param->tablesample_clause_param && !param->has_double_value()) + { + if (param->set_tablesample_clause_param(param->val_real())) + DBUG_RETURN(true); + } } } /* diff --git a/sql/sql_select.cc b/sql/sql_select.cc index dc9e7b6113333..fb399e8ec9a35 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -71,6 +71,7 @@ #include "derived_handler.h" #include "opt_hints.h" #include "opt_group_by_cardinality.h" +#include "sql_tablesample.h" /* A key part number that means we're using a fulltext scan. @@ -122,7 +123,8 @@ const char *join_type_str[]={ "UNKNOWN","system","const","eq_ref","ref", "MAYBE_REF","ALL","range","index","fulltext", "ref_or_null","unique_subquery","index_subquery", "index_merge", "hash_ALL", "hash_range", - "hash_index", "hash_index_merge" }; + "hash_index", "hash_index_merge", + "hash_sample", "sample" }; static const Lex_ident_column group_key= "group_key"_Lex_ident_column; static const Lex_ident_column distinct_key= "distinct_key"_Lex_ident_column; @@ -3458,6 +3460,7 @@ int JOIN::optimize_stage2() tab->type != JT_NEXT && tab->type != JT_FT && tab->type != JT_REF_OR_NULL && + tab->type != JT_SAMPLE && ((order && simple_order) || (group_list && simple_group))) { if (add_ref_to_table_cond(thd,tab)) { @@ -8814,6 +8817,8 @@ best_access_path(JOIN *join, table_map spl_pd_boundary= 0; Loose_scan_opt loose_scan_opt; struct best_plan best; + bool is_tablesample= table->pos_in_table_list && + table->pos_in_table_list->tablesample_clause; Json_writer_object trace_wrapper(thd, "best_access_path"); DBUG_ENTER("best_access_path"); @@ -9947,14 +9952,14 @@ best_access_path(JOIN *join, { /* No usable key, use table scan */ cost= s->cached_scan_and_compare_cost; - type= JT_ALL; + type= is_tablesample ? JT_SAMPLE : JT_ALL; } } } else // table scan { cost= s->cached_scan_and_compare_cost; - type= JT_ALL; + type= is_tablesample ? JT_SAMPLE : JT_ALL; } /* Cache result for other calls */ s->cached_forced_index_type= type; @@ -10034,7 +10039,8 @@ best_access_path(JOIN *join, { trace_access_scan. add("access_type", - type == JT_ALL ? scan_type : join_type_str[type]); + (type == JT_ALL || type == JT_SAMPLE) ? + scan_type : join_type_str[type]); if (type == JT_RANGE) trace_access_scan. add("range_index", table->key_info[s->quick->index].name); @@ -13464,6 +13470,8 @@ bool JOIN::get_best_combination() j->bush_root_tab= sjm_nest_root; form= table[tablenr]= j->table; + bool is_tablesample= form->pos_in_table_list && + form->pos_in_table_list->tablesample_clause; form->reginfo.join_tab=j; DBUG_PRINT("info",("type: %d", j->type)); if (j->type == JT_CONST) @@ -13485,7 +13493,7 @@ bool JOIN::get_best_combination() j->index= cur_pos->forced_index; } else - j->type= JT_ALL; + j->type= is_tablesample ? JT_SAMPLE : JT_ALL; if (cur_pos->use_join_buffer && tablenr != const_tables) full_join= 1; @@ -16078,6 +16086,7 @@ uint check_join_cache_usage(JOIN_TAB *tab, case JT_NEXT: case JT_ALL: case JT_RANGE: + case JT_SAMPLE: if (hint_disables_bnl) goto no_join_cache; if (cache_level == 1) @@ -16169,7 +16178,8 @@ uint check_join_cache_usage(JOIN_TAB *tab, } no_join_cache: - if (tab->type != JT_ALL && tab->type != JT_RANGE && tab->is_ref_for_hash_join()) + if (tab->type != JT_ALL && tab->type != JT_RANGE && + tab->type != JT_SAMPLE && tab->is_ref_for_hash_join()) { tab->type= JT_ALL; tab->ref.key_parts= 0; @@ -16252,6 +16262,7 @@ void check_join_cache_usage_for_tables(JOIN *join, ulonglong options, case JT_NEXT: case JT_ALL: case JT_RANGE: + case JT_SAMPLE: tab->used_join_cache_level= check_join_cache_usage(tab, options, no_jbuf_after, idx, @@ -16538,6 +16549,7 @@ make_join_readinfo(JOIN *join, ulonglong options, uint no_jbuf_after) case JT_ALL: case JT_RANGE: case JT_HASH: + case JT_SAMPLE: { bool have_quick_select= tab->select && tab->select->quick; /* @@ -16886,6 +16898,7 @@ void JOIN_TAB::estimate_scan_time() handler *file= table->file; double row_copy_cost, copy_cost; ALL_READ_COST * const cost= &cached_scan_and_compare_cost; + Lex_tablesample *sampling_info; cost->reset(); cached_covering_key= MAX_KEY; @@ -16918,9 +16931,41 @@ void JOIN_TAB::estimate_scan_time() } else { - cost->row_cost= file->ha_scan_time(records); - read_time= file->cost(cost->row_cost); - row_copy_cost= 0; // Included in ha_scan_time + sampling_info= table->pos_in_table_list ? + table->pos_in_table_list->tablesample_clause : NULL; + if (unlikely(sampling_info && sampling_info->get_effective_sampling_method() == + tablesample_method_enum::TABLESAMPLE_SYSTEM)) + { + /* + get_effective_sampling_method() only returns TABLESAMPLE_SYSTEM + when the table has a primary key (otherwise it has already + fallen back to TABLESAMPLE_BERNOULLI), so table->s->primary_key + is guaranteed to be usable here. + */ + cached_covering_key= table->s->primary_key; + DBUG_ASSERT(cached_covering_key != MAX_KEY); + if (file->is_clustering_key(cached_covering_key)) + { + cost->index_cost= + file->ha_keyread_clustered_time(cached_covering_key, 1, records, 0); + read_time= file->cost(cost->index_cost); + row_copy_cost= file->ROW_COPY_COST; + } + else + { + cost->index_cost= + file->ha_keyread_time(cached_covering_key, 1, records, 0); + cost->row_cost= file->ha_rnd_pos_time(records); + read_time= file->cost(cost->row_cost) + file->cost(cost->index_cost); + row_copy_cost= 0; // included in ha_rnd_pos_time + } + } + else + { + cost->row_cost= file->ha_scan_time(records); + read_time= file->cost(cost->row_cost); + row_copy_cost= 0; // Included in ha_scan_time + } } } } @@ -17017,7 +17062,7 @@ double JOIN_TAB::get_examined_rows() DBUG_ASSERT(examined_rows == sel->quick->records); } else if (type == JT_NEXT || type == JT_ALL || type == JT_RANGE || - type == JT_HASH || type == JT_HASH_NEXT) + type == JT_HASH || type == JT_HASH_NEXT || type == JT_SAMPLE) { if (limit) { @@ -31261,6 +31306,8 @@ bool JOIN_TAB::save_explain_data(Explain_table_access *eta, else tab_type= type == JT_HASH ? JT_HASH_RANGE : JT_RANGE; } + else if (type == JT_HASH && table_list && table_list->tablesample_clause) + tab_type= JT_HASH_SAMPLE; eta->type= tab_type; /* Build "possible_keys" value */ diff --git a/sql/sql_select.h b/sql/sql_select.h index 23a927bfcb14e..d2ac3fd4570e5 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -434,7 +434,19 @@ enum join_type Shown as "hash_index_merge" in EXPLAIN. */ - JT_HASH_INDEX_MERGE + JT_HASH_INDEX_MERGE, + + /* + JT_HASH where the joined table also carries a TABLESAMPLE clause, + so the buffer-refill scan reads a sample of the table's rows + instead of all of them. Appears only in EXPLAIN output, derived + from a JT_HASH table whose table has a tablesample_clause; never + the type of a JOIN_TAB. + + Shown as "hash_sample" in EXPLAIN. + */ + JT_HASH_SAMPLE, + JT_SAMPLE, }; class JOIN; diff --git a/sql/sql_statistics.cc b/sql/sql_statistics.cc index 06ae3531c426f..43a983cb458b1 100644 --- a/sql/sql_statistics.cc +++ b/sql/sql_statistics.cc @@ -34,6 +34,7 @@ #include "sql_show.h" #include "sql_partition.h" #include "sql_alter.h" // RENAME_STAT_PARAMS +#include "sql_tablesample.h" #include #include @@ -4161,6 +4162,10 @@ void set_statistics_for_table(THD *thd, TABLE *table) table->used_stat_records= table->file->stats.records; #endif + if (table->pos_in_table_list && table->pos_in_table_list->tablesample_clause) + table->used_stat_records= (ha_rows)(table->used_stat_records * + table->pos_in_table_list->tablesample_clause->get_sampling_percentage_fraction()); + KEY *key_info, *key_info_end; for (key_info= table->key_info, key_info_end= key_info+table->s->keys; key_info < key_info_end; key_info++) diff --git a/sql/sql_string.cc b/sql/sql_string.cc index b6bdb9365bb0c..30ccf25025124 100644 --- a/sql/sql_string.cc +++ b/sql/sql_string.cc @@ -604,6 +604,18 @@ bool Binary_string::append_ulonglong(ulonglong val) return FALSE; } + +bool String::append_double(double d) +{ + if (realloc(str_length+FLOATING_POINT_BUFFER+2)) + return TRUE; + + qs_append(d); + + return FALSE; +} + + /* Append a string in the given charset to the string with character set recoding diff --git a/sql/sql_string.h b/sql/sql_string.h index ae88a28dd9854..c214172bf7bcc 100644 --- a/sql/sql_string.h +++ b/sql/sql_string.h @@ -1062,6 +1062,8 @@ class String: public Charset, public Binary_string return append(s.str, s.length, cs); } + bool append_double(double d); + // Append a wide character bool append_wc(my_wc_t wc) { diff --git a/sql/sql_tablesample.cc b/sql/sql_tablesample.cc new file mode 100644 index 0000000000000..646288c129796 --- /dev/null +++ b/sql/sql_tablesample.cc @@ -0,0 +1,63 @@ +/* Copyright (c) 2026 Dearsh Oberoi + + 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 */ + +#include "sql_tablesample.h" +#include "sql_class.h" +#include "table.h" +#include "sql_error.h" + +int Lex_tablesample::fix_tablesample_fields(THD *thd, TABLE *table) +{ + DBUG_ENTER("Lex_tablesample::fix_tablesample_fields"); + DBUG_ASSERT(thd); + DBUG_ASSERT(table); + bool err= sampling_percentage->fix_fields_if_needed(thd, &sampling_percentage); + if(err) + DBUG_RETURN(1); + if (sampling_percentage->const_item()) + { + double d= sampling_percentage->val_real(); + if (sampling_percentage->null_value || d < 0.0 || d > 100.0) + DBUG_RETURN(1); + percentage= d / 100.0; + + seed1= (ulong)(my_rnd(&thd->rand) * (double) 0xFFFFFFFFUL); + seed2= (ulong)(my_rnd(&thd->rand) * (double) 0xFFFFFFFFUL); + } + + effective_method= sampling_method; + if (sampling_method == TABLESAMPLE_SYSTEM) + { + if (table->s->primary_key == MAX_KEY) + { + effective_method= TABLESAMPLE_BERNOULLI; + push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, + ER_TABLESAMPLE_SYSTEM_NO_PK, + ER_THD(thd, ER_TABLESAMPLE_SYSTEM_NO_PK), + table->alias.c_ptr()); + } + else if (percentage > SYSTEM_TO_BERNOULLI_MAX_FRACTION) + { + effective_method= TABLESAMPLE_BERNOULLI; + push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, + ER_TABLESAMPLE_SYSTEM_HIGH_PCT, + ER_THD(thd, ER_TABLESAMPLE_SYSTEM_HIGH_PCT), + table->alias.c_ptr(), + SYSTEM_TO_BERNOULLI_MAX_FRACTION * 100.0); + } + } + + DBUG_RETURN(0); +} \ No newline at end of file diff --git a/sql/sql_tablesample.h b/sql/sql_tablesample.h new file mode 100644 index 0000000000000..d703d4b0d7a4d --- /dev/null +++ b/sql/sql_tablesample.h @@ -0,0 +1,90 @@ +/* Copyright (c) 2026 Dearsh Oberoi + + 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 */ + +#ifndef SQL_TABLESAMPLE_INCLUDED +#define SQL_TABLESAMPLE_INCLUDED + + +#include "my_global.h" +#include "sql_alloc.h" +#include "my_rnd.h" + +enum tablesample_method_enum +{ + TABLESAMPLE_NONE= 0, + TABLESAMPLE_SYSTEM, + TABLESAMPLE_BERNOULLI +}; + +class THD; +struct TABLE; +class Item; + +class Lex_tablesample: public Sql_alloc +{ +private: + /* + SYSTEM sampling draws independent random index descents and rejects + duplicates (see mi_random_dive() / rr_sampling_system()). As the + requested fraction approaches 1, collisions between draws become + frequent, and the expected number of descents needed to collect p*N + distinct rows grows like N * ln(1/(1-p)) -- unboundedly worse than a + single sequential scan as p -> 1. Past this fraction, BERNOULLI + (cost O(N), independent of p) is cheaper, so SYSTEM falls back to it. + */ + static constexpr double SYSTEM_TO_BERNOULLI_MAX_FRACTION= 0.3; + + enum tablesample_method_enum sampling_method= + tablesample_method_enum::TABLESAMPLE_NONE; + /* + The method actually used at read/cost time, which may differ from + sampling_method (e.g. SYSTEM falls back to BERNOULLI when the table + has no primary key). Recomputed on every fix_tablesample_fields() call + (once per statement execution) from the immutable sampling_method, so + that a prepared statement/stored procedure re-executed against a table + whose primary key changed always reflects the current table, never a + stale decision from an earlier execution. + */ + enum tablesample_method_enum effective_method= + tablesample_method_enum::TABLESAMPLE_NONE; + Item *sampling_percentage; + double percentage= 0.0; + ulong seed1= 0; + ulong seed2= 0; + +public: + Lex_tablesample(enum tablesample_method_enum method, Item *percentage) : + sampling_method(method), effective_method(method), + sampling_percentage(percentage) {} + + int fix_tablesample_fields(THD *thd, TABLE *table); + + double get_sampling_percentage_fraction() const + { + return percentage; + } + + tablesample_method_enum get_effective_sampling_method() const + { + return effective_method; + } + + void seed_sample_rand(my_rnd_struct *out) const + { + my_rnd_init(out, seed1, seed2); + } +}; + +#endif \ No newline at end of file diff --git a/sql/sql_type.h b/sql/sql_type.h index 8a5e30e257551..47ffd6e2fc9a2 100644 --- a/sql/sql_type.h +++ b/sql/sql_type.h @@ -4106,7 +4106,11 @@ class Type_handler { return false; } - virtual bool is_limit_clause_valid_type() const + virtual bool is_numeric_clause_valid_type() const + { + return false; + } + virtual bool is_tablesample_clause_valid_type() const { return false; } @@ -4989,6 +4993,7 @@ class Type_handler_numeric: public Type_handler override; bool Item_char_typecast_fix_length_and_dec(Item_char_typecast *) const override; + bool is_tablesample_clause_valid_type() const override { return true; } }; @@ -5477,7 +5482,7 @@ class Type_handler_int_result: public Type_handler_numeric return attr->unsigned_flag ? DYN_COL_UINT : DYN_COL_INT; } bool is_order_clause_position_type() const override { return true; } - bool is_limit_clause_valid_type() const override { return true; } + bool is_numeric_clause_valid_type() const override { return true; } virtual ~Type_handler_int_result() = default; const Type_handler *type_handler_for_comparison() const override; int stored_field_cmp_to_item(THD *thd, Field *field, Item *item) const override; diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index c2fe9974c42df..5bc1edfd81ab3 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -198,6 +198,7 @@ void _CONCAT_UNDERSCORED(turn_parser_debug_on,yyparse)() { // Master_info_file, enum_master_use_gtid, std::optional #include "rpl_master_info_file.h" +#include "sql_tablesample.h" } %union { @@ -255,6 +256,7 @@ void _CONCAT_UNDERSCORED(turn_parser_debug_on,yyparse)() SQL_I_List *select_order; Lex_select_lock select_lock; Lex_select_limit select_limit; + Lex_tablesample *tablesample; Lex_order_limit_lock *order_limit_lock; struct { bool with_unique_keys; @@ -365,6 +367,7 @@ void _CONCAT_UNDERSCORED(turn_parser_debug_on,yyparse)() enum Column_definition::enum_column_versioning vers_column_versioning; enum plsql_cursor_attr_t plsql_cursor_attr; enum Alter_info::enum_alter_table_algorithm alter_table_algo_val; + enum tablesample_method_enum tblsmpl_method; enum_master_use_gtid master_use_gtid; privilege_t privilege; struct @@ -812,6 +815,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize); %token BACKUP_SYM %token BEGIN_MARIADB_SYM /* SQL-2003-R, PLSQL-R */ %token BEGIN_ORACLE_SYM /* SQL-2003-R, PLSQL-R */ +%token BERNOULLI %token BINLOG_SYM %token BIT_SYM /* MYSQL-FUNC */ %token BLOCK_SYM @@ -1165,6 +1169,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize); %token SYSTEM /* SQL-2011-R */ %token SYSTEM_TIME_SYM /* SQL-2011-R */ %token TABLES +%token TABLESAMPLE_SYM /* SQL-2016-R */ %token TABLESPACE %token TABLE_CHECKSUM_SYM %token TABLE_NAME_SYM /* SQL-2003-N */ @@ -1629,6 +1634,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize); opt_versioning_interval_start json_default_literal set_expr_misc + tablesample_percentage %type sql_statement_name @@ -1638,6 +1644,8 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize); %type opt_vers_auto_part +%type opt_tablesample_clause + %type param_marker %type @@ -1998,6 +2006,7 @@ rule: %type condition_information_item; %type condition_information_item_name; %type condition_information; +%type tablesample_method; %type row_field_name row_field_definition %type row_field_definition_list row_type_body @@ -12861,7 +12870,7 @@ join_table_parens: table_primary_ident: table_ident opt_use_partition opt_for_system_time_clause - opt_table_alias_clause opt_key_definition + opt_table_alias_clause opt_key_definition opt_tablesample_clause { if (!($$= Select->add_table_to_list(thd, $1, $4, 0, @@ -12872,6 +12881,8 @@ table_primary_ident: MYSQL_YYABORT; if ($3) $$->vers_conditions= Lex->vers_conditions; + if ($6) + $$->tablesample_clause= $6; } ; @@ -13080,6 +13091,47 @@ opt_having_clause: } ; +tablesample_method: + SYSTEM { $$= tablesample_method_enum::TABLESAMPLE_SYSTEM; } + | BERNOULLI { $$= tablesample_method_enum::TABLESAMPLE_BERNOULLI; } + ; + +tablesample_percentage: + ident_cli + { + if (unlikely(!($$= Lex->create_item_tablesample(thd, &$1)))) + MYSQL_YYABORT; + } + | ident_cli '.' ident_cli + { + if (unlikely(!($$= Lex->create_item_tablesample(thd, &$1, &$3)))) + MYSQL_YYABORT; + } + | param_marker + { + $1->tablesample_clause_param= TRUE; + } + | NUM_literal { $$= $1; } + ; + +opt_tablesample_clause: + /* empty */ + { $$= NULL; } + | TABLESAMPLE_SYM tablesample_method '(' tablesample_percentage ')' + { + $$ = new (thd->mem_root) Lex_tablesample($2, $4); + if (unlikely(!$$)) + YYABORT; + if ($4->basic_const_item()) { + double num = $4->val_real(); + if (num != 0.0 && num != 100.0) + Lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_TABLESAMPLE); + } else { + Lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_TABLESAMPLE); + } + } + ; + /* group by statement in select */ diff --git a/sql/table.cc b/sql/table.cc index acc6377de1b50..dedfa451706e4 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -9084,6 +9084,18 @@ Item_subselect *TABLE_LIST::containing_subselect() */ bool TABLE_LIST::process_index_hints(TABLE *tbl) { + if (tbl->pos_in_table_list && tbl->pos_in_table_list->tablesample_clause) + { + tbl->keys_in_use_for_query.clear_all(); + tbl->keys_in_use_for_group_by.clear_all(); + tbl->keys_in_use_for_order_by.clear_all(); + tbl->keys_in_use_for_rowid_filter.clear_all(); + tbl->covering_keys.clear_all(); + tbl->force_index= tbl->force_index_join= tbl->force_index_group= + tbl->force_index_order= false; + return false; + } + /* initialize the result variables */ tbl->keys_in_use_for_query= tbl->keys_in_use_for_group_by= tbl->keys_in_use_for_order_by= tbl->keys_in_use_for_rowid_filter= diff --git a/sql/table.h b/sql/table.h index d04521e889b07..57e39c6bc5fa3 100644 --- a/sql/table.h +++ b/sql/table.h @@ -86,6 +86,7 @@ class MYSQL_LOG; struct rpl_group_info; class Opt_hints_qb; class Opt_hints_table; +class Lex_tablesample; /* Used to identify NESTED_JOIN structures within a join (applicable only to @@ -2884,6 +2885,8 @@ struct TABLE_LIST qc_engine_callback callback_func; thr_lock_type lock_type; + Lex_tablesample *tablesample_clause; + /* Two fields below are set during parsing this table reference in the cases when the table reference can be potentially a reference to a CTE table. diff --git a/storage/myisam/CMakeLists.txt b/storage/myisam/CMakeLists.txt index 2f5d6211e366a..bb626c8fbcfd2 100644 --- a/storage/myisam/CMakeLists.txt +++ b/storage/myisam/CMakeLists.txt @@ -19,8 +19,9 @@ SET(MYISAM_SOURCES ft_boolean_search.c ft_nlq_search.c ft_parser.c ft_static.c ft_stopwords.c ft_update.c mi_cache.c mi_changed.c mi_check.c mi_checksum.c mi_close.c mi_create.c mi_dbug.c mi_delete.c mi_delete_all.c mi_delete_table.c mi_dynrec.c mi_extra.c mi_info.c - mi_key.c mi_keycache.c mi_locking.c mi_log.c mi_open.c - mi_packrec.c mi_page.c mi_panic.c mi_preload.c mi_range.c mi_rename.c + mi_key.c mi_keycache.c mi_locking.c mi_log.c mi_open.c + mi_packrec.c mi_page.c mi_panic.c mi_preload.c mi_random_dive.c + mi_range.c mi_rename.c mi_rfirst.c mi_rlast.c mi_rnext.c mi_rnext_same.c mi_rprev.c mi_rrnd.c mi_rsame.c mi_rsamepos.c mi_scan.c mi_search.c mi_static.c mi_statrec.c mi_unique.c mi_update.c mi_write.c rt_index.c rt_key.c rt_mbr.c diff --git a/storage/myisam/ha_myisam.cc b/storage/myisam/ha_myisam.cc index 7904b7193ae00..b62403aae0adf 100644 --- a/storage/myisam/ha_myisam.cc +++ b/storage/myisam/ha_myisam.cc @@ -2047,6 +2047,12 @@ int ha_myisam::index_next_same(uchar *buf, return error; } +int ha_myisam::index_random_dive(uchar *buf, struct my_rnd_struct *rand_state) +{ + DBUG_ASSERT(inited==INDEX); + return mi_random_dive(file, active_index, buf, rand_state); +} + int ha_myisam::rnd_init(bool scan) { diff --git a/storage/myisam/ha_myisam.h b/storage/myisam/ha_myisam.h index 0bfdc2d4b57a2..067d4888b7571 100644 --- a/storage/myisam/ha_myisam.h +++ b/storage/myisam/ha_myisam.h @@ -76,6 +76,7 @@ class ha_myisam final : public handler int index_first(uchar * buf) override; int index_last(uchar * buf) override; int index_next_same(uchar *buf, const uchar *key, uint keylen) override; + int index_random_dive(uchar *buf, struct my_rnd_struct *rand_state) override; int ft_init() override { if (!ft_handler) diff --git a/storage/myisam/mi_random_dive.c b/storage/myisam/mi_random_dive.c new file mode 100644 index 0000000000000..aa35cbac84289 --- /dev/null +++ b/storage/myisam/mi_random_dive.c @@ -0,0 +1,110 @@ +/* Copyright (c) 2026 Dearsh Oberoi + + 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 */ + +#include "myisamdef.h" +#include + +int mi_random_dive(MI_INFO *info, int inx, uchar *buf, + struct my_rnd_struct *rand_state) +{ + MI_KEYDEF *keyinfo; + my_off_t pos; + uchar *buff, *page, *end, t_buff[HA_MAX_KEY_BUFF]; + uint nod_flag, max_keynr, total_slots, chosen_slot, cur_slot; + DBUG_ENTER("mi_random_dive"); + + if ((inx= _mi_check_index(info,inx)) < 0) + DBUG_RETURN(my_errno); + + if (fast_mi_readinfo(info)) + DBUG_RETURN(my_errno); + + keyinfo=info->s->keyinfo+inx; + if (keyinfo->key_alg != HA_KEY_ALG_BTREE) + { + my_errno= HA_ERR_WRONG_COMMAND; + DBUG_RETURN(my_errno); + } + + if (info->s->concurrent_insert) + mysql_rwlock_rdlock(&info->s->key_root_lock[inx]); + + pos= info->s->state.key_root[inx]; + + for(;;) + { + if (!(buff=_mi_fetch_keypage(info,keyinfo,pos,DFLT_INIT_HITS,info->buff,1))) + goto err; + + nod_flag=mi_test_if_nod(buff); + page= buff+2+nod_flag; + end= buff+mi_getint(buff); + t_buff[0]= 0; + max_keynr= 0; + + while (page < end) + { + if (!(*keyinfo->get_key)(keyinfo,nod_flag,&page,t_buff)) + goto err; + max_keynr++; + } + + total_slots= max_keynr+(nod_flag > 0 ? 1 : 0); + if (!total_slots) + { + my_errno= HA_ERR_END_OF_FILE; + goto err; + } + chosen_slot= (uint) (total_slots*my_rnd(rand_state)); + if (chosen_slot >= total_slots) + chosen_slot--; + + page= buff+2+nod_flag; + t_buff[0]= 0; + cur_slot= 0; + while (page < end && cur_slot < chosen_slot + (nod_flag ? 0 : 1)) + { + if (!(*keyinfo->get_key)(keyinfo,nod_flag,&page,t_buff)) + goto err; + cur_slot++; + } + + if (nod_flag) + { + pos= _mi_kpos(nod_flag, page); + } else { + pos= _mi_dpos(info, nod_flag, page); + break; + } + } + + info->lastpos= pos; + + if (info->s->concurrent_insert) + mysql_rwlock_unlock(&info->s->key_root_lock[inx]); + + if (!(*info->read_record)(info,info->lastpos,buf)) + { + info->update|= HA_STATE_AKTIV; + DBUG_RETURN(0); + } + + DBUG_RETURN(my_errno); + +err: + if (info->s->concurrent_insert) + mysql_rwlock_unlock(&info->s->key_root_lock[inx]); + DBUG_RETURN(my_errno); +} \ No newline at end of file