diff --git a/mysql-test/main/tablesample.result b/mysql-test/main/tablesample.result new file mode 100644 index 0000000000000..8473b23f78264 --- /dev/null +++ b/mysql-test/main/tablesample.result @@ -0,0 +1,79 @@ +DROP TABLE IF EXISTS t1, t2; +DROP VIEW IF EXISTS v1; +CREATE TABLE t1 (a INT, b INT, KEY idx_b(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 +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 SYSTEM (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(40.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 +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 (50); +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 (50)' 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 +DROP TABLE t1, t2; +DROP VIEW v1; diff --git a/mysql-test/main/tablesample.test b/mysql-test/main/tablesample.test new file mode 100644 index 0000000000000..1610c851974dc --- /dev/null +++ b/mysql-test/main/tablesample.test @@ -0,0 +1,133 @@ +# +# MDEV-38992 SQL Standard TABLESAMPLE clause +# +# For now, there are tests just for checking syntax +# + +--disable_warnings +DROP TABLE IF EXISTS t1, t2; +DROP VIEW IF EXISTS v1; +--enable_warnings + +CREATE TABLE t1 (a INT, b INT, KEY idx_b(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; + +DEALLOCATE PREPARE stmt2; + +# +# Testing stored procedures +# +DROP PROCEDURE IF EXISTS p1; + +DELIMITER //; +CREATE PROCEDURE p1(IN sample_pct INT) +BEGIN + SELECT * FROM t1 TABLESAMPLE SYSTEM (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(40.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 ;// + +# +# 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 (50); + +--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; + +# +# Cleanup +# +DROP TABLE t1, t2; +DROP VIEW v1; \ No newline at end of file diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt index 2665778d5ff97..330b190a7d926 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 diff --git a/sql/item.cc b/sql/item.cc index b05a7d5665cab..2b49363e04a9c 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..a1fe754265c47 100644 --- a/sql/opt_hints.cc +++ b/sql/opt_hints.cc @@ -958,6 +958,24 @@ 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) { + /* + A TABLESAMPLE clause forces a sampling scan of the table and + index-based access can bias the result. Ignore any index hints + (old- or new-style) entirely and make sure no key is considered + usable, regardless of what the hints say. + */ + 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..0e1d9021c2a16 100644 --- a/sql/records.cc +++ b/sql/records.cc @@ -30,9 +30,12 @@ #include "sql_class.h" // THD #include "sql_base.h" #include "sql_sort.h" // SORT_ADDON_FIELD +#include "sql_tablesample.h" 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 +190,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 +321,21 @@ 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_sampling_method(); + if (sampling_method == tablesample_method_enum::TABLESAMPLE_BERNOULLI) + info->read_record_func= rr_sampling_bernoulli; + else + info->read_record_func= rr_sampling_system; + if (unlikely(table->file->ha_rnd_init_with_error(1))) + DBUG_RETURN(1); + tablesample_clause->seed_sample_rand(&info->sample_rand); + } else { DBUG_PRINT("info",("using rr_sequential")); @@ -515,6 +535,47 @@ 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; + 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; +} + static int rr_from_tempfile(READ_RECORD *info) { int tmp; diff --git a/sql/records.h b/sql/records.h index 48ce8e2c91752..dc7e167002297 100644 --- a/sql/records.h +++ b/sql/records.h @@ -67,6 +67,9 @@ 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; + /* Structure storing information about sorting */ diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt index a9babd0ea928a..c0d74d3c67fd4 100644 --- a/sql/share/errmsg-utf8.txt +++ b/sql/share/errmsg-utf8.txt @@ -12414,3 +12414,5 @@ ER_PARTITION_INTERVAL_MAXVALUE eng "MAXVALUE is not allowed in range partitioning with interval" ER_RANGE_INTERVAL_PART_FAILED eng "Range partition table %`s.%`s: adding INTERVAL partition(s) failed" +ER_WRONG_SPVAR_TYPE_IN_TABLESAMPLE + eng "A variable of a non-numeric based type in TABLESAMPLE clause" diff --git a/sql/sp_head.cc b/sql/sp_head.cc index fc818b465fd00..2e408f003bcfe 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_base.cc b/sql/sql_base.cc index b451b9bd6e9e2..170c1d4c9c0c2 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))) + { + 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))) + { + 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_lex.cc b/sql/sql_lex.cc index bd83d7f3def98..7b7b7bccfb622 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -9516,7 +9516,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()); @@ -9546,15 +9546,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) { @@ -9578,12 +9599,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 a63a6f376031d..ed72add592dfb 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 }; @@ -4491,6 +4497,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 @@ -4517,6 +4551,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..ca503687e16a2 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->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 8e78bc9f994cf..55dbb4a85a837 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -70,6 +70,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. @@ -3457,6 +3458,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)) { @@ -8802,6 +8804,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"); @@ -9935,14 +9939,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; @@ -10022,7 +10026,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); @@ -13452,6 +13457,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) @@ -13473,7 +13480,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; @@ -16066,6 +16073,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) @@ -16157,7 +16165,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; @@ -16240,6 +16249,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, @@ -16526,6 +16536,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; /* @@ -16874,6 +16885,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; @@ -16906,9 +16918,42 @@ 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->tablesample_clause; + if (unlikely(sampling_info && sampling_info->get_sampling_method() == + tablesample_method_enum::TABLESAMPLE_SYSTEM)) + { + cached_covering_key= table->s->primary_key; + if (cached_covering_key != MAX_KEY) + { + if (file->is_clustering_key(cached_covering_key)) + { + cost->index_cost= + file->ha_keyread_clustered_time(cached_covering_key, records, 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, records, 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 + } + } + 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 + } } } } @@ -17005,7 +17050,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) { diff --git a/sql/sql_select.h b/sql/sql_select.h index 23a927bfcb14e..48c07480152fd 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -434,7 +434,9 @@ enum join_type Shown as "hash_index_merge" in EXPLAIN. */ - JT_HASH_INDEX_MERGE + JT_HASH_INDEX_MERGE, + JT_SAMPLE, + JT_HASH_SAMPLE }; class JOIN; diff --git a/sql/sql_statistics.cc b/sql/sql_statistics.cc index d621f377afd72..39e12d23c251a 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 @@ -4155,6 +4156,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 1c465f3ea219a..9a32daa4d7ebc 100644 --- a/sql/sql_string.cc +++ b/sql/sql_string.cc @@ -584,6 +584,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 d6a0257ad9bb6..005765010d908 100644 --- a/sql/sql_string.h +++ b/sql/sql_string.h @@ -1060,6 +1060,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..b4e3c760ac6a1 --- /dev/null +++ b/sql/sql_tablesample.cc @@ -0,0 +1,38 @@ +/* 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" + +int Lex_tablesample::fix_tablesample_fields(THD *thd) +{ +DBUG_ENTER("Lex_tablesample::fix_tablesample_fields"); +DBUG_ASSERT(thd); +bool err= sampling_percentage->fix_fields_if_needed(thd, NULL); +if(err) + DBUG_RETURN(1); +if (sampling_percentage->const_item()) +{ + double d= sampling_percentage->val_real(); + if (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); +} + +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..5ca1c76f6c866 --- /dev/null +++ b/sql/sql_tablesample.h @@ -0,0 +1,65 @@ +/* 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 "item.h" +#include "sql_alloc.h" + +enum tablesample_method_enum +{ + TABLESAMPLE_NONE= 0, + TABLESAMPLE_SYSTEM, + TABLESAMPLE_BERNOULLI +}; + +class THD; + +class Lex_tablesample: public Sql_alloc +{ +private: + enum tablesample_method_enum sampling_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), sampling_percentage(percentage) {} + + int fix_tablesample_fields(THD *thd); + + double get_sampling_percentage_fraction() const + { + return percentage; + } + + tablesample_method_enum get_sampling_method() const + { + return sampling_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 65d9378d5a221..12cf77aa1d6fd 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 @@ -12851,7 +12860,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, @@ -12862,6 +12871,8 @@ table_primary_ident: MYSQL_YYABORT; if ($3) $$->vers_conditions= Lex->vers_conditions; + if ($6) + $$->tablesample_clause= $6; } ; @@ -13070,6 +13081,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..2b3c1515876e2 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -9084,6 +9084,24 @@ Item_subselect *TABLE_LIST::containing_subselect() */ bool TABLE_LIST::process_index_hints(TABLE *tbl) { + /* + A TABLESAMPLE clause forces a sampling scan of the table and + index-based access can bias the result. Ignore any index hints + (old- or new-style) entirely and make sure no key is considered + usable, regardless of what the hints say. + */ + 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.