diff options
-rw-r--r-- | libmysqld/CMakeLists.txt | 1 | ||||
-rw-r--r-- | sql/CMakeLists.txt | 1 | ||||
-rw-r--r-- | sql/group_by_handler.cc | 144 | ||||
-rw-r--r-- | sql/group_by_handler.h | 133 | ||||
-rw-r--r-- | sql/handler.h | 23 | ||||
-rw-r--r-- | sql/sql_select.cc | 158 | ||||
-rw-r--r-- | sql/sql_select.h | 11 | ||||
-rw-r--r-- | storage/sequence/mysql-test/sequence/group_by.result | 88 | ||||
-rw-r--r-- | storage/sequence/mysql-test/sequence/group_by.test | 42 | ||||
-rw-r--r-- | storage/sequence/sequence.cc | 165 |
10 files changed, 761 insertions, 5 deletions
diff --git a/libmysqld/CMakeLists.txt b/libmysqld/CMakeLists.txt index 84e1d5bd677..d4df327930d 100644 --- a/libmysqld/CMakeLists.txt +++ b/libmysqld/CMakeLists.txt @@ -75,6 +75,7 @@ SET(SQL_EMBEDDED_SOURCES emb_qcache.cc libmysqld.c lib_sql.cc ../sql/debug_sync.cc ../sql/opt_table_elimination.cc ../sql/sql_prepare.cc ../sql/sql_rename.cc ../sql/sql_repl.cc ../sql/sql_select.cc ../sql/sql_servers.cc + ../sql/group_by_handler.cc ../sql/sql_show.cc ../sql/sql_state.c ../sql/sql_statistics.cc ../sql/sql_string.cc ../sql/sql_tablespace.cc ../sql/sql_table.cc ../sql/sql_test.cc diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt index 0d9016c9d6d..9e5ed096e91 100644 --- a/sql/CMakeLists.txt +++ b/sql/CMakeLists.txt @@ -108,6 +108,7 @@ SET (SQL_SOURCE sql_partition.cc sql_plugin.cc sql_prepare.cc sql_rename.cc debug_sync.cc debug_sync.h sql_repl.cc sql_select.cc sql_show.cc sql_state.c + group_by_handler.cc sql_statistics.cc sql_string.cc sql_table.cc sql_test.cc sql_trigger.cc sql_udf.cc sql_union.cc sql_update.cc sql_view.cc strfunc.cc table.cc thr_malloc.cc diff --git a/sql/group_by_handler.cc b/sql/group_by_handler.cc new file mode 100644 index 00000000000..db03ce9196b --- /dev/null +++ b/sql/group_by_handler.cc @@ -0,0 +1,144 @@ +/* + Copyright (c) 2014, SkySQL Ab & MariaDB Foundation + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +/* + This file implements the group_by_handler code. This interface + can be used by storage handlers that can intercept summary or GROUP + BY queries from MariaDB and itself return the result to the user or + upper level. +*/ + +#ifdef USE_PRAGMA_IMPLEMENTATION +#pragma implementation // gcc: Class implementation +#endif + +#include "sql_priv.h" +#include "sql_select.h" + +/* + Same return values as do_select(); + + @retval + 0 if ok + @retval + 1 if error is sent + @retval + -1 if error should be sent +*/ + +int group_by_handler::execute(JOIN *join) +{ + int err; + ha_rows max_limit; + ha_rows *reset_limit= 0; + Item **reset_item= 0; + DBUG_ENTER("group_by_handler"); + + if ((err= init_scan())) + goto error; + + if (store_data_in_temp_table) + { + max_limit= join->tmp_table_param.end_write_records; + reset_limit= &join->unit->select_limit_cnt; + } + else + { + max_limit= join->unit->select_limit_cnt; + if (join->unit->fake_select_lex) + reset_item= &join->unit->fake_select_lex->select_limit; + } + + while (!(err= next_row())) + { + if (thd->check_killed()) + { + thd->send_kill_message(); + (void) end_scan(); + DBUG_RETURN(-1); + } + + /* Check if we can accept the row */ + if (!having || having->val_bool()) + { + if (store_data_in_temp_table) + { + if ((err= table->file->ha_write_tmp_row(table->record[0]))) + { + bool is_duplicate; + if (!table->file->is_fatal_error(err, HA_CHECK_DUP)) + continue; // Distinct elimination + + if (create_internal_tmp_table_from_heap(thd, table, + join->tmp_table_param. + start_recinfo, + &join->tmp_table_param. + recinfo, + err, 1, &is_duplicate)) + DBUG_RETURN(1); + if (is_duplicate) + continue; + } + } + else + { + if (join->do_send_rows) + { + int error; + /* result < 0 if row was not accepted and should not be counted */ + if ((error= join->result->send_data(*join->fields))) + { + (void) end_scan(); + DBUG_RETURN(error < 0 ? 0 : -1); + } + } + } + + /* limit handling */ + if (++join->send_records >= max_limit && join->do_send_rows) + { + if (!(join->select_options & OPTION_FOUND_ROWS)) + break; // LIMIT reached + join->do_send_rows= 0; // Calculate FOUND_ROWS() + if (reset_limit) + *reset_limit= HA_POS_ERROR; + if (reset_item) + *reset_item= 0; + } + } + } + if (err != 0 && err != HA_ERR_END_OF_FILE) + goto error; + + if ((err= end_scan())) + goto error_2; + if (!store_data_in_temp_table && join->result->send_eof()) + DBUG_RETURN(1); // Don't send error to client + + DBUG_RETURN(0); + +error: + (void) end_scan(); +error_2: + print_error(err, MYF(0)); + DBUG_RETURN(-1); // Error not sent to client +} + + +void group_by_handler::print_error(int error, myf errflag) +{ + my_error(ER_GET_ERRNO, MYF(0), error, hton_name(ht)->str); +} diff --git a/sql/group_by_handler.h b/sql/group_by_handler.h new file mode 100644 index 00000000000..425e1440d17 --- /dev/null +++ b/sql/group_by_handler.h @@ -0,0 +1,133 @@ +/* + Copyright (c) 2014, SkySQL Ab & MariaDB Foundation + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +/* + This file implements the group_by_handler interface. This interface + can be used by storage handlers that can intercept summary or GROUP + BY queries from MariaDB and itself return the result to the user or + upper level. + + Both main and sub queries are supported. Here are some examples of what the + storage engine could intersept: + + SELECT count(*) FROM t1; + SELECT a,count(*) FROM t1 group by a; + SELECT a,count(*) as sum FROM t1 where b > 10 group by a, order by sum; + SELECT a,count(*) FROM t1,t2; + SELECT a, (select sum(*) from t2 where t1.a=t2.a) from t2; + + See https://mariadb.atlassian.net/browse/MDEV-6080 for more information. +*/ + +class JOIN; + +class group_by_handler +{ +public: + /* Arguments for group_by_handler, for usage later */ + THD *thd; + SELECT_LEX *select_lex; + List<Item> *fields; + TABLE_LIST *table_list; + ORDER *group_by, *order_by; + Item *where, *having; + handlerton *ht; /* storage engine of this handler */ + + /* + Bit's of things the storage engine can do for this query. + Should be initialized on object creation. + */ + /* Temporary table where all results should be stored in record[0] */ + TABLE *table; + + bool store_data_in_temp_table; /* Set by mariadb */ + + group_by_handler(THD *thd_arg, SELECT_LEX *select_lex_arg, + List<Item> *fields_arg, + TABLE_LIST *table_list_arg, ORDER *group_by_arg, + ORDER *order_by_arg, Item *where_arg, + Item *having_arg, handlerton *ht_arg) + : thd(thd_arg), select_lex(select_lex_arg), fields(fields_arg), + table_list(table_list_arg), group_by(group_by_arg), + order_by(order_by_arg), where(where_arg), having(having_arg), + ht(ht_arg), table(0), store_data_in_temp_table(0) + {} + virtual ~group_by_handler() {} + + /* + Store pointer to temporary table and objects modified to point to + the temporary table. This will happen during the optimize phase. + + We provide new 'having' and 'order_by' elements here. The differ from the + original ones in that these are modified to point to fields in the + temporary table 'table'. + + Return 1 if the storage handler cannot handle the GROUP BY after all, + in which case we have to give an error to the end user for the query. + This is becasue we can't revert back the old having and order_by elements. + */ + + virtual bool init(TABLE *temporary_table, Item *having_arg, + ORDER *order_by_arg) + { + table= temporary_table; + having= having_arg; + order_by= order_by_arg; + return 0; + } + + /* + Result data is sorted by the storage engine according to order_by (if it + exists) else according to the group_by. If this is not specified, + MariaDB will store the result set into the temporary table and sort the + result. + */ + #define GROUP_BY_ORDER_BY 1 + /* The storage engine can handle DISTINCT */ + #define GROUP_BY_DISTINCT 2 + virtual uint flags() { return 0; } + + /* + Functions to scan data. All these returns 0 if ok, error code in case + of error + */ + + /* + Initialize group_by scan, prepare for next_row(). + If this is a sub query with group by, this can be called many times for + a query. + */ + virtual int init_scan()= 0; + + /* + Return next group by result in table->record[0]. + Return 0 if row found, HA_ERR_END_OF_FILE if last row and other error + number in case of fatal error. + */ + virtual int next_row()= 0; + + /* End scanning */ + virtual int end_scan()=0; + + /* Information for optimizer (used by EXPLAIN) */ + virtual int info(uint flag, ha_statistics *stats)= 0; + + /* Function that calls the above scan functions */ + int execute(JOIN *join); + + /* Report errors */ + virtual void print_error(int error, myf errflag); +}; diff --git a/sql/handler.h b/sql/handler.h index 749c1aaf497..674b58aacd7 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -949,6 +949,10 @@ struct handler_iterator { }; class handler; +class group_by_handler; +typedef class st_select_lex SELECT_LEX; +typedef struct st_order ORDER; + /* handlerton is a singleton structure - one instance per storage engine - to provide access to storage engine functionality that works on the @@ -1251,6 +1255,24 @@ struct handlerton */ const char **tablefile_extensions; // by default - empty list + /********************************************************************** + Functions to intercept queries + **********************************************************************/ + + /* + Create and return a group_by_handler, if the storage engine can execute + the summary / group by query. + If the storage engine can't do that, return NULL. + + This is only called for SELECT's where all tables are from the same + storage engine. + */ + group_by_handler *(*create_group_by)(THD *thd, SELECT_LEX *select_lex, + List<Item> *fields, + TABLE_LIST *table_list, ORDER *group_by, + ORDER *order_by, Item *where, + Item *having); + /********************************************************************* Table discovery API. It allows the server to "discover" tables that exist in the storage @@ -4080,6 +4102,7 @@ protected: }; #include "multi_range_read.h" +#include "group_by_handler.h" bool key_uses_partial_cols(TABLE_SHARE *table, uint keyno); diff --git a/sql/sql_select.cc b/sql/sql_select.cc index a8c665d5684..3690df815ac 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -759,6 +759,12 @@ JOIN::prepare(Item ***rref_pointer_array, TABLE_LIST *tbl; List_iterator_fast<TABLE_LIST> li(select_lex->leaf_tables); + /* + If all tables comes from the same storage engine, one_storge_engine will + be set to point to the handlerton of this engine. + */ + one_storage_engine= 0; + uint table_loop_count= 0; while ((tbl= li++)) { /* @@ -770,8 +776,17 @@ JOIN::prepare(Item ***rref_pointer_array, Note: this loop doesn't touch tables inside merged semi-joins, because subquery-to-semijoin conversion has not been done yet. This is intended. */ - if (mixed_implicit_grouping && tbl->table) - tbl->table->maybe_null= 1; + if (tbl->table) + { + if (mixed_implicit_grouping) + tbl->table->maybe_null= 1; + if (!table_loop_count++) + one_storage_engine= tbl->table->file->ht; + else if (one_storage_engine != tbl->table->file->ht) + one_storage_engine= 0; + } + else + one_storage_engine= 0; } if ((wild_num && setup_wild(thd, tables_list, fields_list, &all_fields, @@ -1894,6 +1909,102 @@ TODO: make view to decide if it is possible to write to WHERE directly or make S optimize_schema_tables_reads(this); /* + All optimization is done. Check if we can use the storage engines + group by handler to evaluate the group by + */ + + if ((tmp_table_param.sum_func_count || group_list) && !procedure && + (one_storage_engine && one_storage_engine->create_group_by)) + { + /* Check if the storage engine can intercept the query */ + if ((storage_handler_for_group_by= + (one_storage_engine->create_group_by)(thd, select_lex, + &all_fields, + tables_list, + group_list, order, + conds, having))) + { + uint handler_flags= storage_handler_for_group_by->flags(); + int err; + + /* + We must store rows in the tmp table if we need to do an ORDER BY + or DISTINCT and the storage handler can't handle it. + */ + need_tmp= ((!(handler_flags & GROUP_BY_ORDER_BY) && + (order || group_list)) || + (!(handler_flags & GROUP_BY_DISTINCT) && select_distinct)); + tmp_table_param.hidden_field_count= (all_fields.elements - + fields_list.elements); + if (!(exec_tmp_table1= + create_tmp_table(thd, &tmp_table_param, all_fields, + 0, handler_flags & GROUP_BY_DISTINCT ? + 0 : select_distinct, 1, + select_options, HA_POS_ERROR, "", + !need_tmp, + (!order || + (handler_flags & GROUP_BY_ORDER_BY))))) + DBUG_RETURN(1); + + /* + Setup reference fields, used by summary functions and group by fields, + to point to the temporary table. + The actual switching to the temporary tables fields for HAVING + and ORDER BY is done in do_select() by calling + set_items_ref_array(items1). + */ + init_items_ref_array(); + items1= items0 + all_fields.elements; + if (change_to_use_tmp_fields(thd, items1, + tmp_fields_list1, tmp_all_fields1, + fields_list.elements, all_fields)) + DBUG_RETURN(1); + + /* Give storage engine access to temporary table */ + if ((err= storage_handler_for_group_by->init(exec_tmp_table1, + having, order))) + { + storage_handler_for_group_by->print_error(err, MYF(0)); + DBUG_RETURN(1); + } + storage_handler_for_group_by->store_data_in_temp_table= need_tmp; + /* + If there is not specified ORDER BY, we should sort things according + to the group_by + */ + if (!order) + order= group_list; + /* + Group by and having is calculated by the group_by handler. + Reset the group by and having + */ + group= 0; group_list= 0; + having= tmp_having= 0; + /* + Select distinct is handled by handler or by creating an unique index + over all fields in the temporary table + */ + select_distinct= 0; + if (handler_flags & GROUP_BY_ORDER_BY) + order= 0; + tmp_table_param.field_count+= tmp_table_param.sum_func_count; + tmp_table_param.sum_func_count= 0; + + /* Remember information about the original join */ + original_join_tab= join_tab; + original_table_count= table_count; + + /* Set up one join tab to get sorting to work */ + const_tables= 0; + table_count= 1; + join_tab= (JOIN_TAB*) thd->calloc(sizeof(JOIN_TAB)); + join_tab[0].table= exec_tmp_table1; + + DBUG_RETURN(thd->is_fatal_error); + } + } + + /* The loose index scan access method guarantees that all grouping or duplicate row elimination (for distinct) is already performed during data retrieval, and that all MIN/MAX functions are already @@ -1970,7 +2081,7 @@ int JOIN::init_execution() thd->lex->set_limit_rows_examined(); /* Create a tmp table if distinct or if the sort is too complicated */ - if (need_tmp) + if (need_tmp && ! storage_handler_for_group_by) { DBUG_PRINT("info",("Creating tmp table")); THD_STAGE_INFO(thd, stage_copying_to_tmp_table); @@ -2632,6 +2743,8 @@ void JOIN::exec_inner() */ curr_join->join_examined_rows= 0; + curr_join->do_select_call_count= 0; + /* Create a tmp table if distinct or if the sort is too complicated */ if (need_tmp) { @@ -11823,6 +11936,14 @@ void JOIN::cleanup(bool full) if (full) have_query_plan= QEP_DELETED; + if (original_join_tab) + { + /* Free the original optimized join created for the group_by_handler */ + join_tab= original_join_tab; + original_join_tab= 0; + table_count= original_table_count; + } + if (table) { JOIN_TAB *tab; @@ -11931,6 +12052,9 @@ void JOIN::cleanup(bool full) } tmp_table_param.cleanup(); + delete storage_handler_for_group_by; + storage_handler_for_group_by= 0; + if (!join_tab) { List_iterator<TABLE_LIST> li(*join_list); @@ -17727,6 +17851,18 @@ do_select(JOIN *join,List<Item> *fields,TABLE *table,Procedure *procedure) join->procedure=procedure; join->tmp_table= table; /* Save for easy recursion */ join->fields= fields; + join->do_select_call_count++; + + if (join->storage_handler_for_group_by && + join->do_select_call_count == 1) + { + /* Select fields are in the temporary table */ + join->fields= &join->tmp_fields_list1; + /* Setup HAVING to work with fields in temporary table */ + join->set_items_ref_array(join->items1); + /* The storage engine will take care of the group by query result */ + DBUG_RETURN(join->storage_handler_for_group_by->execute(join)); + } if (table) { @@ -24192,6 +24328,22 @@ int JOIN::save_explain_data_intern(Explain_query *output, bool need_tmp_table, explain->connection_type= Explain_node::EXPLAIN_NODE_DERIVED; output->add_node(explain); } + else if (storage_handler_for_group_by) + { + explain= new (output->mem_root) Explain_select(output->mem_root, + thd->lex->analyze_stmt); + select_lex->set_explain_type(true); + + explain->select_id= select_lex->select_number; + explain->select_type= select_lex->type; + explain->using_temporary= need_tmp; + explain->using_filesort= need_order; + explain->message= "Storage engine handles GROUP BY"; + + if (select_lex->master_unit()->derived) + explain->connection_type= Explain_node::EXPLAIN_NODE_DERIVED; + output->add_node(explain); + } else { Explain_select *xpl_sel; diff --git a/sql/sql_select.h b/sql/sql_select.h index 57c66bae8e2..4e77d5b6008 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -1011,6 +1011,7 @@ public: */ uint top_join_tab_count; uint send_group_parts; + uint do_select_call_count; /* True if the query has GROUP BY. (that is, if group_by != NULL. when DISTINCT is converted into GROUP BY, it @@ -1080,6 +1081,12 @@ public: /* Finally picked QEP. This is result of join optimization */ POSITION *best_positions; + /* points to a storage engine if all tables comes from the storage engine */ + handlerton *one_storage_engine; + group_by_handler *storage_handler_for_group_by; + JOIN_TAB *original_join_tab; + uint original_table_count; + /******* Join optimization state members start *******/ /* pointer - we're doing optimization for a semi-join materialization nest. @@ -1378,6 +1385,10 @@ public: group_optimized_away= 0; no_rows_in_result_called= 0; positions= best_positions= 0; + one_storage_engine= 0; + storage_handler_for_group_by= 0; + original_join_tab= 0; + do_select_call_count= 0; explain= NULL; diff --git a/storage/sequence/mysql-test/sequence/group_by.result b/storage/sequence/mysql-test/sequence/group_by.result new file mode 100644 index 00000000000..86bb158d9fc --- /dev/null +++ b/storage/sequence/mysql-test/sequence/group_by.result @@ -0,0 +1,88 @@ +show create table seq_1_to_15_step_2; +Table Create Table +seq_1_to_15_step_2 CREATE TABLE `seq_1_to_15_step_2` ( + `seq` bigint(20) unsigned NOT NULL, + PRIMARY KEY (`seq`) +) ENGINE=SEQUENCE DEFAULT CHARSET=latin1 +select count(seq),sum(seq),1 from seq_1_to_15_step_2; +count(seq) sum(seq) 1 +8 64 1 +# +# The engine should be able to optimize the following requests +# +select count(*) from seq_1_to_15_step_2; +count(*) +8 +explain select count(*) from seq_1_to_15_step_2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL Storage engine handles GROUP BY +select count(seq) from seq_1_to_15_step_2; +count(seq) +8 +explain select count(seq) from seq_1_to_15_step_2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL Storage engine handles GROUP BY +select sum(seq) from seq_1_to_15_step_2; +sum(seq) +64 +explain select sum(seq) from seq_1_to_15_step_2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL Storage engine handles GROUP BY +select count(seq),sum(seq) from seq_1_to_15_step_2; +count(seq) sum(seq) +8 64 +explain select count(seq),sum(seq) from seq_1_to_15_step_2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL Storage engine handles GROUP BY +select count(seq) as c from seq_1_to_15_step_2 having c > 5; +c +8 +explain select count(seq) as c from seq_1_to_15_step_2 having c > 5; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL Storage engine handles GROUP BY +select count(seq) as c from seq_1_to_15_step_2 having c > 1000; +c +explain select count(seq) as c from seq_1_to_15_step_2 having c > 1000; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL Storage engine handles GROUP BY +select distinct count(*) from seq_1_to_15_step_2; +count(*) +8 +explain select distinct count(*) from seq_1_to_15_step_2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL Storage engine handles GROUP BY +select * from seq_1_to_15_step_2, (select count(*) from seq_1_to_15_step_2) as t1; +seq count(*) +1 8 +3 8 +5 8 +7 8 +9 8 +11 8 +13 8 +15 8 +explain select * from seq_1_to_15_step_2, (select count(*) from seq_1_to_15_step_2) as t1; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY seq_1_to_15_step_2 index NULL PRIMARY 8 NULL # Using index +1 PRIMARY <derived2> ALL NULL NULL NULL NULL # Using join buffer (flat, BNL join) +2 DERIVED NULL NULL NULL NULL NULL NULL # Storage engine handles GROUP BY +# +# The engine can't optimize the following queries +# +select count(seq),sum(seq),1 from seq_1_to_15_step_2; +count(seq) sum(seq) 1 +8 64 1 +explain select count(seq),sum(seq),1 from seq_1_to_15_step_2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE seq_1_to_15_step_2 index NULL PRIMARY 8 NULL 8 Using index +explain select count(*) from seq_1_to_15_step_2, seq_1_to_15_step_2 as t2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE seq_1_to_15_step_2 index NULL PRIMARY 8 NULL 8 Using index +1 SIMPLE t2 index NULL PRIMARY 8 NULL 8 Using index; Using join buffer (flat, BNL join) +explain select count(*) from seq_1_to_15_step_2 where seq > 0; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE seq_1_to_15_step_2 index PRIMARY PRIMARY 8 NULL 8 Using where; Using index +explain select count(*) from seq_1_to_15_step_2 group by mod(seq,2); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE seq_1_to_15_step_2 index NULL PRIMARY 8 NULL 8 Using index; Using temporary; Using filesort +drop table seq_1_to_15_step_2; diff --git a/storage/sequence/mysql-test/sequence/group_by.test b/storage/sequence/mysql-test/sequence/group_by.test new file mode 100644 index 00000000000..870afd9ed89 --- /dev/null +++ b/storage/sequence/mysql-test/sequence/group_by.test @@ -0,0 +1,42 @@ +--source inc.inc + +# Check that group by handler forks for the sequence engine. +# The sequence engine can only optimize queries with COUNT(primary_key) or +# SUM(primary_key) when there is no GROUP BY. + +show create table seq_1_to_15_step_2; + +# Get the correct results +select count(seq),sum(seq),1 from seq_1_to_15_step_2; + +--echo # +--echo # The engine should be able to optimize the following requests +--echo # +select count(*) from seq_1_to_15_step_2; +explain select count(*) from seq_1_to_15_step_2; +select count(seq) from seq_1_to_15_step_2; +explain select count(seq) from seq_1_to_15_step_2; +select sum(seq) from seq_1_to_15_step_2; +explain select sum(seq) from seq_1_to_15_step_2; +select count(seq),sum(seq) from seq_1_to_15_step_2; +explain select count(seq),sum(seq) from seq_1_to_15_step_2; +select count(seq) as c from seq_1_to_15_step_2 having c > 5; +explain select count(seq) as c from seq_1_to_15_step_2 having c > 5; +select count(seq) as c from seq_1_to_15_step_2 having c > 1000; +explain select count(seq) as c from seq_1_to_15_step_2 having c > 1000; +select distinct count(*) from seq_1_to_15_step_2; +explain select distinct count(*) from seq_1_to_15_step_2; +select * from seq_1_to_15_step_2, (select count(*) from seq_1_to_15_step_2) as t1; +--replace_column 9 # +explain select * from seq_1_to_15_step_2, (select count(*) from seq_1_to_15_step_2) as t1; + +--echo # +--echo # The engine can't optimize the following queries +--echo # +select count(seq),sum(seq),1 from seq_1_to_15_step_2; +explain select count(seq),sum(seq),1 from seq_1_to_15_step_2; +explain select count(*) from seq_1_to_15_step_2, seq_1_to_15_step_2 as t2; +explain select count(*) from seq_1_to_15_step_2 where seq > 0; +explain select count(*) from seq_1_to_15_step_2 group by mod(seq,2); + +drop table seq_1_to_15_step_2; diff --git a/storage/sequence/sequence.cc b/storage/sequence/sequence.cc index 970ee7ca2f7..d6e1ed7412a 100644 --- a/storage/sequence/sequence.cc +++ b/storage/sequence/sequence.cc @@ -23,10 +23,14 @@ #include <my_config.h> #include <ctype.h> #include <mysql_version.h> +#include <item.h> +#include <item_sum.h> #include <handler.h> #include <table.h> #include <field.h> +handlerton *sequence_hton; + class Sequence_share : public Handler_share { public: const char *name; @@ -52,11 +56,11 @@ class ha_seq: public handler { private: THR_LOCK_DATA lock; - Sequence_share *seqs; Sequence_share *get_share(); ulonglong cur; public: + Sequence_share *seqs; ha_seq(handlerton *hton, TABLE_SHARE *table_arg) : handler(hton, table_arg), seqs(0) { } ulonglong table_flags() const @@ -346,9 +350,166 @@ static int discover_table_existence(handlerton *hton, const char *db, static int dummy_ret_int() { return 0; } +/***************************************************************************** + Example of a simple group by handler for queries like: + SELECT SUM(seq) from sequence_table; + + This implementation supports SUM() and COUNT() on primary key. +*****************************************************************************/ + +class ha_seq_group_by_handler: public group_by_handler +{ + bool first_row; + +public: + ha_seq_group_by_handler(THD *thd, SELECT_LEX *select_lex, + List<Item> *fields, + TABLE_LIST *table_list, ORDER *group_by, + ORDER *order_by, Item *where, + Item *having) + :group_by_handler(thd, select_lex, fields, table_list, group_by, + order_by, where, having, sequence_hton) + { + } + ~ha_seq_group_by_handler() {} + bool init(TABLE *temporary_table, Item *having_arg, + ORDER *order_by_arg); + int init_scan() { first_row= 1 ; return 0; } + int next_row(); + int end_scan() { return 0; } + int info(uint flag, ha_statistics *stats); +}; + +static group_by_handler * +create_group_by_handler(THD *thd, SELECT_LEX *select_lex, + List<Item> *fields, + TABLE_LIST *table_list, ORDER *group_by, + ORDER *order_by, Item *where, + Item *having) +{ + ha_seq_group_by_handler *handler; + Item *item; + List_iterator_fast<Item> it(*fields); + + /* check that only one table is used in FROM clause and no sub queries */ + if (table_list->next_local != 0) + return 0; + /* check that there is no where clause and no group_by */ + if (where != 0 || group_by != 0) + return 0; + + /* + Check that all fields are sum(primary_key) or count(primary_key) + For more ways to work with the field list and sum functions, see + opt_sum.cc::opt_sum_query(). + */ + while ((item= it++)) + { + Item *arg0; + Field *field; + if (item->type() != Item::SUM_FUNC_ITEM || + (((Item_sum*) item)->sum_func() != Item_sum::SUM_FUNC && + ((Item_sum*) item)->sum_func() != Item_sum::COUNT_FUNC)) + + return 0; // Not a SUM() function + arg0= ((Item_sum*) item)->get_arg(0); + if (arg0->type() != Item::FIELD_ITEM) + { + if ((((Item_sum*) item)->sum_func() == Item_sum::COUNT_FUNC) && + arg0->basic_const_item()) + continue; // Allow count(1) + return 0; + } + field= ((Item_field*) arg0)->field; + /* + Check that we are using the sequence table (the only table in the FROM + clause) and not an outer table. + */ + if (field->table != table_list->table) + return 0; + /* Check that we are using a SUM() on the primary key */ + if (strcmp(field->field_name, "seq")) + return 0; + } + + /* Create handler and return it */ + handler= new ha_seq_group_by_handler(thd, select_lex, fields, table_list, + group_by, + order_by, where, having); + return handler; +} + +bool ha_seq_group_by_handler::init(TABLE *temporary_table, Item *having_arg, + ORDER *order_by_arg) +{ + /* + Here we could add checks if the temporary table was created correctly + */ + return group_by_handler::init(temporary_table, having_arg, order_by_arg); +} + + +int ha_seq_group_by_handler::info(uint flag, ha_statistics *stats) +{ + bzero(stats, sizeof(*stats)); + /* We only return one records for a SUM(*) without a group by */ + stats->records= 1; + return 0; +} + +int ha_seq_group_by_handler::next_row() +{ + List_iterator_fast<Item> it(*fields); + Item_sum *item_sum; + Sequence_share *seqs= ((ha_seq*) table_list->table->file)->seqs; + DBUG_ENTER("ha_seq_group_by_handler"); + + /* + Check if this is the first call to the function. If not, we have already + returned all data. + */ + if (!first_row) + DBUG_RETURN(HA_ERR_END_OF_FILE); + first_row= 0; + + /* Pointer to first field in temporary table where we should store summary*/ + Field **field_ptr= table->field; + ulonglong elements= (seqs->to - seqs->from + seqs->step - 1) / seqs->step; + + while ((item_sum= (Item_sum*) it++)) + { + Field *field= *(field_ptr++); + switch (item_sum->sum_func()) { + case Item_sum::COUNT_FUNC: + { + field->store((longlong) elements, 1); + break; + } + case Item_sum::SUM_FUNC: + { + /* Calculate SUM(f, f+step, f+step*2 ... to) */ + ulonglong sum; + sum= seqs->from * elements + seqs->step * (elements*elements-elements)/2; + field->store((longlong) sum, 1); + break; + } + default: + DBUG_ASSERT(0); + } + field->set_notnull(); + } + DBUG_RETURN(0); +} + + +/***************************************************************************** + Initialize the interface between the sequence engine and MariaDB +*****************************************************************************/ + static int init(void *p) { handlerton *hton= (handlerton *)p; + sequence_hton= hton; hton->create= create_handler; hton->discover_table= discover_table; hton->discover_table_existence= discover_table_existence; @@ -356,7 +517,7 @@ static int init(void *p) (int (*)(handlerton *, THD *, bool)) &dummy_ret_int; hton->savepoint_set= hton->savepoint_rollback= hton->savepoint_release= (int (*)(handlerton *, THD *, void *)) &dummy_ret_int; - + hton->create_group_by= create_group_by_handler; return 0; } |