From 305f78c374bb97ab17f35bbe73889c735296ae7f Mon Sep 17 00:00:00 2001 From: "Tatiana A. Nurnberg" Date: Thu, 29 Oct 2009 22:06:10 -0700 Subject: [PATCH 01/11] Bug#48319: Server crashes on "GRANT/REVOKE ... TO CURRENT_USER" CURRENT_USER() in GRANT ... TO CURRENT_USER() only gave us a definer, not a full user (i.e., password-element was not initiliazed). Hence dereferencing the password led to a crash. Properly initializes definers now, just so there are no misunderstandings. Also does some magic so IDENTIFIED BY ... works with CURRENT_USER(). --- mysql-test/r/grant2.result | 27 +++++++++++++++++++++++++++ mysql-test/t/grant2.test | 35 +++++++++++++++++++++++++++++++++++ sql/sql_acl.cc | 7 +++++++ sql/sql_parse.cc | 5 +++++ 4 files changed, 74 insertions(+) diff --git a/mysql-test/r/grant2.result b/mysql-test/r/grant2.result index 7c2023127f0..12269f0cb1c 100644 --- a/mysql-test/r/grant2.result +++ b/mysql-test/r/grant2.result @@ -443,3 +443,30 @@ DROP TABLE db1.t1, db1.t2; DROP USER mysqltest1@localhost; DROP DATABASE db1; End of 5.0 tests +USE mysql; +SELECT LEFT(CURRENT_USER(),INSTR(CURRENT_USER(),'@')-1) INTO @u; +SELECT MID(CURRENT_USER(),INSTR(CURRENT_USER(),'@')+1) INTO @h; +SELECT password FROM user WHERE user=@u AND host=@h INTO @pwd; +SELECT user,host,password,insert_priv FROM user WHERE user=@u AND host=@h; +user host password insert_priv +root localhost Y +UPDATE user SET insert_priv='N' WHERE user=@u AND host=@h; +SELECT user,host,password,insert_priv FROM user WHERE user=@u AND host=@h; +user host password insert_priv +root localhost N +GRANT INSERT ON *.* TO CURRENT_USER(); +SELECT user,host,password,insert_priv FROM user WHERE user=@u AND host=@h; +user host password insert_priv +root localhost Y +UPDATE user SET insert_priv='N' WHERE user=@u AND host=@h; +GRANT INSERT ON *.* TO CURRENT_USER() IDENTIFIED BY 'keksdose'; +SELECT user,host,password,insert_priv FROM user WHERE user=@u AND host=@h; +user host password insert_priv +root localhost *0BB7188CF0DE9B403BA66E9DD810D82652D002EB Y +UPDATE user SET password=@pwd WHERE user=@u AND host=@h; +SELECT user,host,password,insert_priv FROM user WHERE user=@u AND host=@h; +user host password insert_priv +root localhost Y +FLUSH PRIVILEGES; +USE test; +End of 5.1 tests diff --git a/mysql-test/t/grant2.test b/mysql-test/t/grant2.test index 54cdf8d6cc1..447848013f9 100644 --- a/mysql-test/t/grant2.test +++ b/mysql-test/t/grant2.test @@ -632,5 +632,40 @@ DROP DATABASE db1; --echo End of 5.0 tests +# +# Bug #48319: Server crashes on "GRANT/REVOKE ... TO CURRENT_USER" +# + +# work out who we are. +USE mysql; +SELECT LEFT(CURRENT_USER(),INSTR(CURRENT_USER(),'@')-1) INTO @u; +SELECT MID(CURRENT_USER(),INSTR(CURRENT_USER(),'@')+1) INTO @h; +SELECT password FROM user WHERE user=@u AND host=@h INTO @pwd; + +# show current privs. +SELECT user,host,password,insert_priv FROM user WHERE user=@u AND host=@h; + +# toggle INSERT +UPDATE user SET insert_priv='N' WHERE user=@u AND host=@h; +SELECT user,host,password,insert_priv FROM user WHERE user=@u AND host=@h; + +# show that GRANT ... TO CURRENT_USER() no longer crashes +GRANT INSERT ON *.* TO CURRENT_USER(); +SELECT user,host,password,insert_priv FROM user WHERE user=@u AND host=@h; +UPDATE user SET insert_priv='N' WHERE user=@u AND host=@h; + +# show that GRANT ... TO CURRENT_USER() IDENTIFIED BY ... works now +GRANT INSERT ON *.* TO CURRENT_USER() IDENTIFIED BY 'keksdose'; +SELECT user,host,password,insert_priv FROM user WHERE user=@u AND host=@h; + +UPDATE user SET password=@pwd WHERE user=@u AND host=@h; +SELECT user,host,password,insert_priv FROM user WHERE user=@u AND host=@h; + +FLUSH PRIVILEGES; + +USE test; + +--echo End of 5.1 tests + # Wait till we reached the initial number of concurrent sessions --source include/wait_until_count_sessions.inc diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index 0592bb3be1d..5259b560532 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -3451,6 +3451,13 @@ bool mysql_grant(THD *thd, const char *db, List &list, result= TRUE; continue; } + /* + No User, but a password? + They did GRANT ... TO CURRENT_USER() IDENTIFIED BY ... ! + Get the current user, and shallow-copy the new password to them! + */ + if (!tmp_Str->user.str && tmp_Str->password.str) + Str->password= tmp_Str->password; if (replace_user_table(thd, tables[0].table, *Str, (!db ? rights : 0), revoke_grant, create_new_users, test(thd->variables.sql_mode & diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 2e150ca1542..65d86814045 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -7573,6 +7573,9 @@ void get_default_definer(THD *thd, LEX_USER *definer) definer->host.str= (char *) sctx->priv_host; definer->host.length= strlen(definer->host.str); + + definer->password.str= NULL; + definer->password.length= 0; } @@ -7624,6 +7627,8 @@ LEX_USER *create_definer(THD *thd, LEX_STRING *user_name, LEX_STRING *host_name) definer->user= *user_name; definer->host= *host_name; + definer->password.str= NULL; + definer->password.length= 0; return definer; } From a0d5521f06030c3884ea26fd3c0e7520ca43ff26 Mon Sep 17 00:00:00 2001 From: "Tatiana A. Nurnberg" Date: Mon, 2 Nov 2009 00:46:00 -0800 Subject: [PATCH 02/11] Bug#47655: Memory free error when connecting to 4.1 server from 5.1+ client When starting the (5.1+) mysql command-line client, we try to get "select @@version_comment" from the server to present it to the user. Recent clients are aware that older servers do not have that variable and fall back on other info to be able to present *something* at least. This fallback string was allocated through the POSIX interface, but released through the my*() suite, which rightfully complained about the imbalance in calls when compiled with --debug. While this wasn't as bad as it looked (no double-free, use of uninitialized or freed buffer, etc.), it did look funky. Using my_strdup() now for what will be my_free()d later. --- client/mysql.cc | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/client/mysql.cc b/client/mysql.cc index b76a3d624ab..4f9aef411ca 100644 --- a/client/mysql.cc +++ b/client/mysql.cc @@ -4474,9 +4474,7 @@ server_version_string(MYSQL *con) */ if (server_version == NULL) - { - server_version= strdup(mysql_get_server_info(con)); - } + server_version= my_strdup(mysql_get_server_info(con), MYF(MY_WME)); } return server_version ? server_version : ""; From 4beeb3fa608073f7a344ea45407f38e5b5b9fd57 Mon Sep 17 00:00:00 2001 From: Davi Arnaut Date: Thu, 12 Nov 2009 23:03:26 -0200 Subject: [PATCH 03/11] Bug#47627 SET @@{global.session}.local_variable in stored routine causes crash This patch borrows ideas, text and code from Kristofer Pettersson's patch. An assignment of a system variable sharing the same base name as a declared stored procedure variable in the same context could lead to a crash. The reason was that during the parsing of the syntactic rule 'option_value' an uninitialized set_var object was pushed to the parameter stack of the SET statement. The parent rule 'option_type_value' interpreted the existence of variables on the parameter stack as an assignment and wrapped it in a sp_instr_set object. As the procedure later was executed an attempt was made to run the method 'check()' on an uninitialized member object (NULL value) belonging to the previously created but uninitialized object. This patch refactors the 'internal_variable_name' rule and copies the semantic analysis part to the depending parent rule: 'option_value'. This makes it possible to account for any prefixes affecting the interpretation of the internal_variable_name. --- mysql-test/r/sp.result | 58 ++++++++++ mysql-test/t/sp.test | 67 +++++++++++ sql/sql_yacc.yy | 252 +++++++++++++++++++++++++++-------------- 3 files changed, 289 insertions(+), 88 deletions(-) diff --git a/mysql-test/r/sp.result b/mysql-test/r/sp.result index 67514c314f4..83ad7545685 100644 --- a/mysql-test/r/sp.result +++ b/mysql-test/r/sp.result @@ -6979,6 +6979,64 @@ CALL p1; ERROR 42S22: Unknown column 'A.b' in 'IN/ALL/ANY subquery' DROP PROCEDURE p1; DROP TABLE t1, t2; +# +# Bug#47627: SET @@{global.session}.local_variable in stored routine causes crash +# Bug#48626: Crash or lost connection using SET for declared variables with @@ +# +DROP PROCEDURE IF EXISTS p1; +DROP PROCEDURE IF EXISTS p2; +DROP PROCEDURE IF EXISTS p3; +CREATE PROCEDURE p1() +BEGIN +DECLARE v INT DEFAULT 0; +SET @@SESSION.v= 10; +END// +ERROR HY000: Unknown system variable 'v' +CREATE PROCEDURE p2() +BEGIN +DECLARE v INT DEFAULT 0; +SET v= 10; +END// +call p2()// +CREATE PROCEDURE p3() +BEGIN +DECLARE v INT DEFAULT 0; +SELECT @@SESSION.v; +END// +ERROR HY000: Unknown system variable 'v' +CREATE PROCEDURE p4() +BEGIN +DECLARE v INT DEFAULT 0; +SET @@GLOBAL.v= 10; +END// +ERROR HY000: Unknown system variable 'v' +CREATE PROCEDURE p5() +BEGIN +DECLARE init_connect INT DEFAULT 0; +SET init_connect= 10; +SET @@GLOBAL.init_connect= 'SELECT 1'; +SET @@SESSION.IDENTITY= 1; +SELECT @@SESSION.IDENTITY; +SELECT @@GLOBAL.init_connect; +SELECT init_connect; +END// +CREATE PROCEDURE p6() +BEGIN +DECLARE v INT DEFAULT 0; +SET @@v= 0; +END// +ERROR HY000: Unknown system variable 'v' +SET @old_init_connect= @@GLOBAL.init_connect; +CALL p5(); +@@SESSION.IDENTITY +1 +@@GLOBAL.init_connect +SELECT 1 +init_connect +10 +SET @@GLOBAL.init_connect= @old_init_connect; +DROP PROCEDURE p2; +DROP PROCEDURE p5; # ------------------------------------------------------------------ # -- End of 5.1 tests # ------------------------------------------------------------------ diff --git a/mysql-test/t/sp.test b/mysql-test/t/sp.test index 44c4556340e..73ba62612b8 100644 --- a/mysql-test/t/sp.test +++ b/mysql-test/t/sp.test @@ -8263,6 +8263,73 @@ CALL p1; DROP PROCEDURE p1; DROP TABLE t1, t2; +--echo # +--echo # Bug#47627: SET @@{global.session}.local_variable in stored routine causes crash +--echo # Bug#48626: Crash or lost connection using SET for declared variables with @@ +--echo # + +--disable_warnings +DROP PROCEDURE IF EXISTS p1; +DROP PROCEDURE IF EXISTS p2; +DROP PROCEDURE IF EXISTS p3; +--enable_warnings + +delimiter //; + +--error ER_UNKNOWN_SYSTEM_VARIABLE +CREATE PROCEDURE p1() +BEGIN + DECLARE v INT DEFAULT 0; + SET @@SESSION.v= 10; +END// + +CREATE PROCEDURE p2() +BEGIN + DECLARE v INT DEFAULT 0; + SET v= 10; +END// +call p2()// + +--error ER_UNKNOWN_SYSTEM_VARIABLE +CREATE PROCEDURE p3() +BEGIN + DECLARE v INT DEFAULT 0; + SELECT @@SESSION.v; +END// + +--error ER_UNKNOWN_SYSTEM_VARIABLE +CREATE PROCEDURE p4() +BEGIN + DECLARE v INT DEFAULT 0; + SET @@GLOBAL.v= 10; +END// + +CREATE PROCEDURE p5() +BEGIN + DECLARE init_connect INT DEFAULT 0; + SET init_connect= 10; + SET @@GLOBAL.init_connect= 'SELECT 1'; + SET @@SESSION.IDENTITY= 1; + SELECT @@SESSION.IDENTITY; + SELECT @@GLOBAL.init_connect; + SELECT init_connect; +END// + +--error ER_UNKNOWN_SYSTEM_VARIABLE +CREATE PROCEDURE p6() +BEGIN + DECLARE v INT DEFAULT 0; + SET @@v= 0; +END// + +delimiter ;// + +SET @old_init_connect= @@GLOBAL.init_connect; +CALL p5(); +SET @@GLOBAL.init_connect= @old_init_connect; + +DROP PROCEDURE p2; +DROP PROCEDURE p5; --echo # ------------------------------------------------------------------ --echo # -- End of 5.1 tests diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 86bef83cf1a..b0f45fe845a 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -389,6 +389,138 @@ void case_stmt_action_end_case(LEX *lex, bool simple) lex->sphead->do_cont_backpatch(); } + +static bool +find_sys_var_null_base(THD *thd, struct sys_var_with_base *tmp) +{ + tmp->var= find_sys_var(thd, tmp->base_name.str, tmp->base_name.length); + + if (tmp->var == NULL) + my_error(ER_UNKNOWN_SYSTEM_VARIABLE, MYF(0), tmp->base_name.str); + else + tmp->base_name= null_lex_str; + + return thd->is_error(); +} + + +/** + Helper action for a SET statement. + Used to push a system variable into the assignment list. + + @param thd the current thread + @param tmp the system variable with base name + @param var_type the scope of the variable + @param val the value being assigned to the variable + + @return TRUE if error, FALSE otherwise. +*/ + +static bool +set_system_variable(THD *thd, struct sys_var_with_base *tmp, + enum enum_var_type var_type, Item *val) +{ + set_var *var; + LEX *lex= thd->lex; + + /* No AUTOCOMMIT from a stored function or trigger. */ + if (lex->spcont && tmp->var == &sys_autocommit) + lex->sphead->m_flags|= sp_head::HAS_SET_AUTOCOMMIT_STMT; + + if (! (var= new set_var(var_type, tmp->var, &tmp->base_name, val))) + return TRUE; + + return lex->var_list.push_back(var); +} + + +/** + Helper action for a SET statement. + Used to push a SP local variable into the assignment list. + + @param thd the current thread + @param var_type the SP local variable + @param val the value being assigned to the variable + + @return TRUE if error, FALSE otherwise. +*/ + +static bool +set_local_variable(THD *thd, sp_variable_t *spv, Item *val) +{ + Item *it; + LEX *lex= thd->lex; + sp_instr_set *sp_set; + + if (val) + it= val; + else if (spv->dflt) + it= spv->dflt; + else + { + it= new (thd->mem_root) Item_null(); + if (it == NULL) + return TRUE; + } + + sp_set= new sp_instr_set(lex->sphead->instructions(), lex->spcont, + spv->offset, it, spv->type, lex, TRUE); + + return (sp_set == NULL || lex->sphead->add_instr(sp_set)); +} + + +/** + Helper action for a SET statement. + Used to SET a field of NEW row. + + @param thd the current thread + @param name the field name + @param val the value being assigned to the row + + @return TRUE if error, FALSE otherwise. +*/ + +static bool +set_trigger_new_row(THD *thd, LEX_STRING *name, Item *val) +{ + LEX *lex= thd->lex; + Item_trigger_field *trg_fld; + sp_instr_set_trigger_field *sp_fld; + + /* QQ: Shouldn't this be field's default value ? */ + if (! val) + val= new Item_null(); + + DBUG_ASSERT(lex->trg_chistics.action_time == TRG_ACTION_BEFORE && + (lex->trg_chistics.event == TRG_EVENT_INSERT || + lex->trg_chistics.event == TRG_EVENT_UPDATE)); + + trg_fld= new (thd->mem_root) + Item_trigger_field(lex->current_context(), + Item_trigger_field::NEW_ROW, + name->str, UPDATE_ACL, FALSE); + + if (trg_fld == NULL) + return TRUE; + + sp_fld= new sp_instr_set_trigger_field(lex->sphead->instructions(), + lex->spcont, trg_fld, val, lex); + + if (sp_fld == NULL) + return TRUE; + + /* + Let us add this item to list of all Item_trigger_field + objects in trigger. + */ + lex->trg_table_fields.link_in_list((uchar *) trg_fld, + (uchar **) &trg_fld->next_trg_field); + + return lex->sphead->add_instr(sp_fld); +} + + /** Helper to resolve the SQL:2003 Syntax exception 1) in . See SQL:2003, Part 2, section 8.4 , Note 184, page 383. @@ -11809,98 +11941,42 @@ sys_option_value: option_type internal_variable_name equal set_expr_or_default { THD *thd= YYTHD; - LEX *lex=Lex; + LEX *lex= Lex; + LEX_STRING *name= &$2.base_name; if ($2.var == trg_new_row_fake_var) { /* We are in trigger and assigning value to field of new row */ - Item *it; - Item_trigger_field *trg_fld; - sp_instr_set_trigger_field *sp_fld; - LINT_INIT(sp_fld); if ($1) { my_parse_error(ER(ER_SYNTAX_ERROR)); MYSQL_YYABORT; } - if ($4) - it= $4; - else - { - /* QQ: Shouldn't this be field's default value ? */ - it= new Item_null(); - } - - DBUG_ASSERT(lex->trg_chistics.action_time == TRG_ACTION_BEFORE && - (lex->trg_chistics.event == TRG_EVENT_INSERT || - lex->trg_chistics.event == TRG_EVENT_UPDATE)); - - trg_fld= new (thd->mem_root) - Item_trigger_field(Lex->current_context(), - Item_trigger_field::NEW_ROW, - $2.base_name.str, - UPDATE_ACL, FALSE); - if (trg_fld == NULL) - MYSQL_YYABORT; - - sp_fld= new sp_instr_set_trigger_field(lex->sphead-> - instructions(), - lex->spcont, - trg_fld, - it, lex); - if (sp_fld == NULL) - MYSQL_YYABORT; - - /* - Let us add this item to list of all Item_trigger_field - objects in trigger. - */ - lex->trg_table_fields.link_in_list((uchar *)trg_fld, - (uchar **) &trg_fld-> - next_trg_field); - - if (lex->sphead->add_instr(sp_fld)) + if (set_trigger_new_row(YYTHD, name, $4)) MYSQL_YYABORT; } else if ($2.var) - { /* System variable */ + { if ($1) lex->option_type= $1; - set_var *var= new set_var(lex->option_type, $2.var, - &$2.base_name, $4); - if (var == NULL) + + /* It is a system variable. */ + if (set_system_variable(thd, &$2, lex->option_type, $4)) MYSQL_YYABORT; - lex->var_list.push_back(var); } else { - /* An SP local variable */ - sp_pcontext *ctx= lex->spcont; - sp_variable_t *spv; - sp_instr_set *sp_set; - Item *it; + sp_pcontext *spc= lex->spcont; + sp_variable_t *spv= spc->find_variable(name); + if ($1) { my_parse_error(ER(ER_SYNTAX_ERROR)); MYSQL_YYABORT; } - spv= ctx->find_variable(&$2.base_name); - - if ($4) - it= $4; - else if (spv->dflt) - it= spv->dflt; - else - { - it= new (thd->mem_root) Item_null(); - if (it == NULL) - MYSQL_YYABORT; - } - sp_set= new sp_instr_set(lex->sphead->instructions(), ctx, - spv->offset, it, spv->type, lex, TRUE); - if (sp_set == NULL || - lex->sphead->add_instr(sp_set)) + /* It is a local variable. */ + if (set_local_variable(thd, spv, $4)) MYSQL_YYABORT; } } @@ -11936,11 +12012,16 @@ option_value: } | '@' '@' opt_var_ident_type internal_variable_name equal set_expr_or_default { - LEX *lex=Lex; - set_var *var= new set_var($3, $4.var, &$4.base_name, $6); - if (var == NULL) + THD *thd= YYTHD; + struct sys_var_with_base tmp= $4; + /* Lookup if necessary: must be a system variable. */ + if (tmp.var == NULL) + { + if (find_sys_var_null_base(thd, &tmp)) + MYSQL_YYABORT; + } + if (set_system_variable(thd, &tmp, $3, $6)) MYSQL_YYABORT; - lex->var_list.push_back(var); } | charset old_or_new_charset_name_or_default { @@ -12033,31 +12114,26 @@ internal_variable_name: ident { THD *thd= YYTHD; - LEX *lex= thd->lex; - sp_pcontext *spc= lex->spcont; + sp_pcontext *spc= thd->lex->spcont; sp_variable_t *spv; - /* We have to lookup here since local vars can shadow sysvars */ + /* Best effort lookup for system variable. */ if (!spc || !(spv = spc->find_variable(&$1))) { + struct sys_var_with_base tmp= {NULL, $1}; + /* Not an SP local variable */ - sys_var *tmp=find_sys_var(thd, $1.str, $1.length); - if (!tmp) + if (find_sys_var_null_base(thd, &tmp)) MYSQL_YYABORT; - $$.var= tmp; - $$.base_name= null_lex_str; - if (spc && tmp == &sys_autocommit) - { - /* - We don't allow setting AUTOCOMMIT from a stored function - or trigger. - */ - lex->sphead->m_flags|= sp_head::HAS_SET_AUTOCOMMIT_STMT; - } + + $$= tmp; } else { - /* An SP local variable */ + /* + Possibly an SP local variable (or a shadowed sysvar). + Will depend on the context of the SET statement. + */ $$.var= NULL; $$.base_name= $1; } From 726e83907cea41206bb1f4333b55c26f467b90ab Mon Sep 17 00:00:00 2001 From: Evgeny Potemkin Date: Tue, 17 Nov 2009 17:06:46 +0300 Subject: [PATCH 04/11] Bug#43668: Wrong comparison and MIN/MAX for YEAR(2) MySQL manual describes values of the YEAR(2) field type as follows: values 00 - 69 mean 2000 - 2069 years and values 70 - 99 mean 1970 - 1999 years. MIN/MAX and comparison functions was comparing them as int values thus producing wrong result. Now the Arg_comparator class is extended with compare_year function which performs correct comparison of the YEAR type. The Item_sum_hybrid class now uses Item_cache and Arg_comparator objects to correctly calculate its value. To allow Arg_comparator to use func_name() function for Item_func and Item_sum objects the func_name declaration is moved to the Item_result_field class. A helper function is_owner_equal_func is added to the Arg_comparator class. It checks whether the Arg_comparator object owner is the <=> function or not. A helper function setup is added to the Item_sum_hybrid class. It sets up cache item and comparator. --- mysql-test/r/func_group.result | 195 ++++++++++++++++++++++- mysql-test/t/func_group.test | 32 ++++ sql/item.cc | 2 +- sql/item.h | 17 ++ sql/item_cmpfunc.cc | 275 ++++++++++++++++++++++++-------- sql/item_cmpfunc.h | 43 +++-- sql/item_func.h | 11 -- sql/item_geofunc.cc | 4 +- sql/item_subselect.h | 1 + sql/item_sum.cc | 283 +++++++-------------------------- sql/item_sum.h | 37 ++--- 11 files changed, 560 insertions(+), 340 deletions(-) diff --git a/mysql-test/r/func_group.result b/mysql-test/r/func_group.result index 3d989ad1730..b0877991367 100644 --- a/mysql-test/r/func_group.result +++ b/mysql-test/r/func_group.result @@ -885,7 +885,7 @@ cast(sum(distinct df) as signed) 3 select cast(min(df) as signed) from t1; cast(min(df) as signed) -0 +1 select 1e8 * sum(distinct df) from t1; 1e8 * sum(distinct df) 330000000 @@ -1477,3 +1477,196 @@ COUNT(*) SET SQL_MODE=default; DROP TABLE t1; End of 5.0 tests +# +# Bug#43668: Wrong comparison and MIN/MAX for YEAR(2) +# +create table t1 (f1 year(2), f2 year(4), f3 date, f4 datetime); +insert into t1 values +(98,1998,19980101,"1998-01-01 00:00:00"), +(00,2000,20000101,"2000-01-01 00:00:01"), +(02,2002,20020101,"2002-01-01 23:59:59"), +(60,2060,20600101,"2060-01-01 11:11:11"), +(70,1970,19700101,"1970-11-11 22:22:22"), +(NULL,NULL,NULL,NULL); +select min(f1),max(f1) from t1; +min(f1) max(f1) +70 60 +select min(f2),max(f2) from t1; +min(f2) max(f2) +1970 2060 +select min(f3),max(f3) from t1; +min(f3) max(f3) +1970-01-01 2060-01-01 +select min(f4),max(f4) from t1; +min(f4) max(f4) +1970-11-11 22:22:22 2060-01-01 11:11:11 +select a.f1 as a, b.f1 as b, a.f1 > b.f1 as gt, +a.f1 < b.f1 as lt, a.f1<=>b.f1 as eq +from t1 a, t1 b; +a b gt lt eq +98 98 0 0 1 +00 98 1 0 0 +02 98 1 0 0 +60 98 1 0 0 +70 98 0 1 0 +NULL 98 NULL NULL 0 +98 00 0 1 0 +00 00 0 0 1 +02 00 1 0 0 +60 00 1 0 0 +70 00 0 1 0 +NULL 00 NULL NULL 0 +98 02 0 1 0 +00 02 0 1 0 +02 02 0 0 1 +60 02 1 0 0 +70 02 0 1 0 +NULL 02 NULL NULL 0 +98 60 0 1 0 +00 60 0 1 0 +02 60 0 1 0 +60 60 0 0 1 +70 60 0 1 0 +NULL 60 NULL NULL 0 +98 70 1 0 0 +00 70 1 0 0 +02 70 1 0 0 +60 70 1 0 0 +70 70 0 0 1 +NULL 70 NULL NULL 0 +98 NULL NULL NULL 0 +00 NULL NULL NULL 0 +02 NULL NULL NULL 0 +60 NULL NULL NULL 0 +70 NULL NULL NULL 0 +NULL NULL NULL NULL 1 +select a.f1 as a, b.f2 as b, a.f1 > b.f2 as gt, +a.f1 < b.f2 as lt, a.f1<=>b.f2 as eq +from t1 a, t1 b; +a b gt lt eq +98 1998 0 0 1 +00 1998 1 0 0 +02 1998 1 0 0 +60 1998 1 0 0 +70 1998 0 1 0 +NULL 1998 NULL NULL 0 +98 2000 0 1 0 +00 2000 0 0 1 +02 2000 1 0 0 +60 2000 1 0 0 +70 2000 0 1 0 +NULL 2000 NULL NULL 0 +98 2002 0 1 0 +00 2002 0 1 0 +02 2002 0 0 1 +60 2002 1 0 0 +70 2002 0 1 0 +NULL 2002 NULL NULL 0 +98 2060 0 1 0 +00 2060 0 1 0 +02 2060 0 1 0 +60 2060 0 0 1 +70 2060 0 1 0 +NULL 2060 NULL NULL 0 +98 1970 1 0 0 +00 1970 1 0 0 +02 1970 1 0 0 +60 1970 1 0 0 +70 1970 0 0 1 +NULL 1970 NULL NULL 0 +98 NULL NULL NULL 0 +00 NULL NULL NULL 0 +02 NULL NULL NULL 0 +60 NULL NULL NULL 0 +70 NULL NULL NULL 0 +NULL NULL NULL NULL 1 +select a.f1 as a, b.f3 as b, a.f1 > b.f3 as gt, +a.f1 < b.f3 as lt, a.f1<=>b.f3 as eq +from t1 a, t1 b; +a b gt lt eq +98 1998-01-01 0 1 0 +00 1998-01-01 1 0 0 +02 1998-01-01 1 0 0 +60 1998-01-01 1 0 0 +70 1998-01-01 0 1 0 +NULL 1998-01-01 NULL NULL 0 +98 2000-01-01 0 1 0 +00 2000-01-01 0 1 0 +02 2000-01-01 1 0 0 +60 2000-01-01 1 0 0 +70 2000-01-01 0 1 0 +NULL 2000-01-01 NULL NULL 0 +98 2002-01-01 0 1 0 +00 2002-01-01 0 1 0 +02 2002-01-01 0 1 0 +60 2002-01-01 1 0 0 +70 2002-01-01 0 1 0 +NULL 2002-01-01 NULL NULL 0 +98 2060-01-01 0 1 0 +00 2060-01-01 0 1 0 +02 2060-01-01 0 1 0 +60 2060-01-01 0 1 0 +70 2060-01-01 0 1 0 +NULL 2060-01-01 NULL NULL 0 +98 1970-01-01 1 0 0 +00 1970-01-01 1 0 0 +02 1970-01-01 1 0 0 +60 1970-01-01 1 0 0 +70 1970-01-01 0 1 0 +NULL 1970-01-01 NULL NULL 0 +98 NULL NULL NULL 0 +00 NULL NULL NULL 0 +02 NULL NULL NULL 0 +60 NULL NULL NULL 0 +70 NULL NULL NULL 0 +NULL NULL NULL NULL 1 +select a.f1 as a, b.f4 as b, a.f1 > b.f4 as gt, +a.f1 < b.f4 as lt, a.f1<=>b.f4 as eq +from t1 a, t1 b; +a b gt lt eq +98 1998-01-01 00:00:00 0 1 0 +00 1998-01-01 00:00:00 1 0 0 +02 1998-01-01 00:00:00 1 0 0 +60 1998-01-01 00:00:00 1 0 0 +70 1998-01-01 00:00:00 0 1 0 +NULL 1998-01-01 00:00:00 NULL NULL 0 +98 2000-01-01 00:00:01 0 1 0 +00 2000-01-01 00:00:01 0 1 0 +02 2000-01-01 00:00:01 1 0 0 +60 2000-01-01 00:00:01 1 0 0 +70 2000-01-01 00:00:01 0 1 0 +NULL 2000-01-01 00:00:01 NULL NULL 0 +98 2002-01-01 23:59:59 0 1 0 +00 2002-01-01 23:59:59 0 1 0 +02 2002-01-01 23:59:59 0 1 0 +60 2002-01-01 23:59:59 1 0 0 +70 2002-01-01 23:59:59 0 1 0 +NULL 2002-01-01 23:59:59 NULL NULL 0 +98 2060-01-01 11:11:11 0 1 0 +00 2060-01-01 11:11:11 0 1 0 +02 2060-01-01 11:11:11 0 1 0 +60 2060-01-01 11:11:11 0 1 0 +70 2060-01-01 11:11:11 0 1 0 +NULL 2060-01-01 11:11:11 NULL NULL 0 +98 1970-11-11 22:22:22 1 0 0 +00 1970-11-11 22:22:22 1 0 0 +02 1970-11-11 22:22:22 1 0 0 +60 1970-11-11 22:22:22 1 0 0 +70 1970-11-11 22:22:22 0 1 0 +NULL 1970-11-11 22:22:22 NULL NULL 0 +98 NULL NULL NULL 0 +00 NULL NULL NULL 0 +02 NULL NULL NULL 0 +60 NULL NULL NULL 0 +70 NULL NULL NULL 0 +NULL NULL NULL NULL 1 +select *, f1 = f2 from t1; +f1 f2 f3 f4 f1 = f2 +98 1998 1998-01-01 1998-01-01 00:00:00 1 +00 2000 2000-01-01 2000-01-01 00:00:01 1 +02 2002 2002-01-01 2002-01-01 23:59:59 1 +60 2060 2060-01-01 2060-01-01 11:11:11 1 +70 1970 1970-01-01 1970-11-11 22:22:22 1 +NULL NULL NULL NULL NULL +drop table t1; +# diff --git a/mysql-test/t/func_group.test b/mysql-test/t/func_group.test index b0a3d0feb79..02d2676a3c2 100644 --- a/mysql-test/t/func_group.test +++ b/mysql-test/t/func_group.test @@ -1006,3 +1006,35 @@ DROP TABLE t1; ### --echo End of 5.0 tests + +--echo # +--echo # Bug#43668: Wrong comparison and MIN/MAX for YEAR(2) +--echo # +create table t1 (f1 year(2), f2 year(4), f3 date, f4 datetime); +insert into t1 values + (98,1998,19980101,"1998-01-01 00:00:00"), + (00,2000,20000101,"2000-01-01 00:00:01"), + (02,2002,20020101,"2002-01-01 23:59:59"), + (60,2060,20600101,"2060-01-01 11:11:11"), + (70,1970,19700101,"1970-11-11 22:22:22"), + (NULL,NULL,NULL,NULL); +select min(f1),max(f1) from t1; +select min(f2),max(f2) from t1; +select min(f3),max(f3) from t1; +select min(f4),max(f4) from t1; +select a.f1 as a, b.f1 as b, a.f1 > b.f1 as gt, + a.f1 < b.f1 as lt, a.f1<=>b.f1 as eq +from t1 a, t1 b; +select a.f1 as a, b.f2 as b, a.f1 > b.f2 as gt, + a.f1 < b.f2 as lt, a.f1<=>b.f2 as eq +from t1 a, t1 b; +select a.f1 as a, b.f3 as b, a.f1 > b.f3 as gt, + a.f1 < b.f3 as lt, a.f1<=>b.f3 as eq +from t1 a, t1 b; +select a.f1 as a, b.f4 as b, a.f1 > b.f4 as gt, + a.f1 < b.f4 as lt, a.f1<=>b.f4 as eq +from t1 a, t1 b; +select *, f1 = f2 from t1; +drop table t1; +--echo # + diff --git a/sql/item.cc b/sql/item.cc index 86e4551e55b..baa35b977e3 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -6940,7 +6940,7 @@ Item_cache* Item_cache::get_cache(const Item *item) { switch (item->result_type()) { case INT_RESULT: - return new Item_cache_int(); + return new Item_cache_int(item->field_type()); case REAL_RESULT: return new Item_cache_real(); case DECIMAL_RESULT: diff --git a/sql/item.h b/sql/item.h index a2cff3ab3a9..00ecda105d3 100644 --- a/sql/item.h +++ b/sql/item.h @@ -2135,6 +2135,23 @@ public: save_in_field(result_field, no_conversions); } void cleanup(); + /* + This method is used for debug purposes to print the name of an + item to the debug log. The second use of this method is as + a helper function of print() and error messages, where it is + applicable. To suit both goals it should return a meaningful, + distinguishable and sintactically correct string. This method + should not be used for runtime type identification, use enum + {Sum}Functype and Item_func::functype()/Item_sum::sum_func() + instead. + Added here, to the parent class of both Item_func and Item_sum_func. + + NOTE: for Items inherited from Item_sum, func_name() return part of + function name till first argument (including '(') to make difference in + names for functions with 'distinct' clause and without 'distinct' and + also to make printing of items inherited from Item_sum uniform. + */ + virtual const char *func_name() const= 0; }; diff --git a/sql/item_cmpfunc.cc b/sql/item_cmpfunc.cc index c29031d25b5..aa279bced44 100644 --- a/sql/item_cmpfunc.cc +++ b/sql/item_cmpfunc.cc @@ -30,6 +30,9 @@ #include "sql_select.h" static bool convert_constant_item(THD *, Item_field *, Item **); +static longlong +get_year_value(THD *thd, Item ***item_arg, Item **cache_arg, + Item *warn_item, bool *is_null); static Item_result item_store_type(Item_result a, Item *item, my_bool unsigned_flag) @@ -533,11 +536,12 @@ void Item_bool_func2::fix_length_and_dec() } -int Arg_comparator::set_compare_func(Item_bool_func2 *item, Item_result type) +int Arg_comparator::set_compare_func(Item_result_field *item, Item_result type) { owner= item; func= comparator_matrix[type] - [test(owner->functype() == Item_func::EQUAL_FUNC)]; + [is_owner_equal_func()]; + switch (type) { case ROW_RESULT: { @@ -557,7 +561,8 @@ int Arg_comparator::set_compare_func(Item_bool_func2 *item, Item_result type) my_error(ER_OPERAND_COLUMNS, MYF(0), (*a)->element_index(i)->cols()); return 1; } - if (comparators[i].set_cmp_func(owner, (*a)->addr(i), (*b)->addr(i))) + if (comparators[i].set_cmp_func(owner, (*a)->addr(i), (*b)->addr(i), + set_null)) return 1; } break; @@ -571,7 +576,8 @@ int Arg_comparator::set_compare_func(Item_bool_func2 *item, Item_result type) if (cmp_collation.set((*a)->collation, (*b)->collation) || cmp_collation.derivation == DERIVATION_NONE) { - my_coll_agg_error((*a)->collation, (*b)->collation, owner->func_name()); + my_coll_agg_error((*a)->collation, (*b)->collation, + owner->func_name()); return 1; } if (cmp_collation.collation == &my_charset_bin) @@ -849,7 +855,7 @@ get_time_value(THD *thd, Item ***item_arg, Item **cache_arg, } -int Arg_comparator::set_cmp_func(Item_bool_func2 *owner_arg, +int Arg_comparator::set_cmp_func(Item_result_field *owner_arg, Item **a1, Item **a2, Item_result type) { @@ -857,11 +863,11 @@ int Arg_comparator::set_cmp_func(Item_bool_func2 *owner_arg, ulonglong const_value= (ulonglong)-1; a= a1; b= a2; + owner= owner_arg; + thd= current_thd; if ((cmp_type= can_compare_as_dates(*a, *b, &const_value))) { - thd= current_thd; - owner= owner_arg; a_type= (*a)->field_type(); b_type= (*b)->field_type(); a_cache= 0; @@ -885,22 +891,22 @@ int Arg_comparator::set_cmp_func(Item_bool_func2 *owner_arg, b= (Item **)&b_cache; } } - is_nulls_eq= test(owner && owner->functype() == Item_func::EQUAL_FUNC); + is_nulls_eq= is_owner_equal_func(); func= &Arg_comparator::compare_datetime; - get_value_func= &get_datetime_value; + get_value_a_func= &get_datetime_value; + get_value_b_func= &get_datetime_value; return 0; } else if (type == STRING_RESULT && (*a)->field_type() == MYSQL_TYPE_TIME && (*b)->field_type() == MYSQL_TYPE_TIME) { /* Compare TIME values as integers. */ - thd= current_thd; - owner= owner_arg; a_cache= 0; b_cache= 0; - is_nulls_eq= test(owner && owner->functype() == Item_func::EQUAL_FUNC); + is_nulls_eq= is_owner_equal_func(); func= &Arg_comparator::compare_datetime; - get_value_func= &get_time_value; + get_value_a_func= &get_time_value; + get_value_b_func= &get_time_value; return 0; } else if (type == STRING_RESULT && @@ -909,20 +915,39 @@ int Arg_comparator::set_cmp_func(Item_bool_func2 *owner_arg, { DTCollation coll; coll.set((*a)->collation.collation); - if (agg_item_set_converter(coll, owner_arg->func_name(), + if (agg_item_set_converter(coll, owner->func_name(), b, 1, MY_COLL_CMP_CONV, 1)) return 1; - } + } else if (type != ROW_RESULT && ((*a)->field_type() == MYSQL_TYPE_YEAR || + (*b)->field_type() == MYSQL_TYPE_YEAR)) + { + is_nulls_eq= is_owner_equal_func(); + if ((*a)->is_datetime()) + { + year_as_datetime= TRUE; + get_value_a_func= &get_datetime_value; + } else if ((*a)->field_type() == MYSQL_TYPE_YEAR) + get_value_a_func= &get_year_value; + if ((*b)->is_datetime()) + { + year_as_datetime= TRUE; + get_value_b_func= &get_datetime_value; + } else if ((*b)->field_type() == MYSQL_TYPE_YEAR) + get_value_b_func= &get_year_value; + + func= &Arg_comparator::compare_year; + return 0; + } return set_compare_func(owner_arg, type); } -void Arg_comparator::set_datetime_cmp_func(Item **a1, Item **b1) +void Arg_comparator::set_datetime_cmp_func(Item_result_field *owner_arg, + Item **a1, Item **b1) { thd= current_thd; - /* A caller will handle null values by itself. */ - owner= NULL; + owner= owner_arg; a= a1; b= b1; a_type= (*a)->field_type(); @@ -931,7 +956,8 @@ void Arg_comparator::set_datetime_cmp_func(Item **a1, Item **b1) b_cache= 0; is_nulls_eq= FALSE; func= &Arg_comparator::compare_datetime; - get_value_func= &get_datetime_value; + get_value_a_func= &get_datetime_value; + get_value_b_func= &get_datetime_value; } @@ -1031,6 +1057,51 @@ get_datetime_value(THD *thd, Item ***item_arg, Item **cache_arg, return value; } + +/* + Retrieves YEAR value of 19XX form from given item. + + SYNOPSIS + get_year_value() + thd thread handle + item_arg [in/out] item to retrieve YEAR value from + cache_arg [in/out] pointer to place to store the caching item to + warn_item [in] item for issuing the conversion warning + is_null [out] TRUE <=> the item_arg is null + + DESCRIPTION + Retrieves the YEAR value of 19XX form from given item for comparison by the + compare_year() function. + + RETURN + obtained value +*/ + +static longlong +get_year_value(THD *thd, Item ***item_arg, Item **cache_arg, + Item *warn_item, bool *is_null) +{ + longlong value= 0; + Item *item= **item_arg; + + value= item->val_int(); + *is_null= item->null_value; + if (*is_null) + return ~(ulonglong) 0; + + /* + Coerce value to the 19XX form in order to correctly compare + YEAR(2) & YEAR(4) types. + */ + if (value < 70) + value+= 100; + if (value <= 1900) + value+= 1900; + + return value; +} + + /* Compare items values as dates. @@ -1063,25 +1134,25 @@ int Arg_comparator::compare_datetime() longlong a_value, b_value; /* Get DATE/DATETIME/TIME value of the 'a' item. */ - a_value= (*get_value_func)(thd, &a, &a_cache, *b, &a_is_null); + a_value= (*get_value_a_func)(thd, &a, &a_cache, *b, &a_is_null); if (!is_nulls_eq && a_is_null) { - if (owner) + if (set_null) owner->null_value= 1; return -1; } /* Get DATE/DATETIME/TIME value of the 'b' item. */ - b_value= (*get_value_func)(thd, &b, &b_cache, *a, &b_is_null); + b_value= (*get_value_b_func)(thd, &b, &b_cache, *a, &b_is_null); if (a_is_null || b_is_null) { - if (owner) + if (set_null) owner->null_value= is_nulls_eq ? 0 : 1; return is_nulls_eq ? (a_is_null == b_is_null) : -1; } /* Here we have two not-NULL values. */ - if (owner) + if (set_null) owner->null_value= 0; /* Compare values. */ @@ -1094,15 +1165,17 @@ int Arg_comparator::compare_datetime() int Arg_comparator::compare_string() { String *res1,*res2; - if ((res1= (*a)->val_str(&owner->tmp_value1))) + if ((res1= (*a)->val_str(&value1))) { - if ((res2= (*b)->val_str(&owner->tmp_value2))) + if ((res2= (*b)->val_str(&value2))) { - owner->null_value= 0; + if (set_null) + owner->null_value= 0; return sortcmp(res1,res2,cmp_collation.collation); } } - owner->null_value= 1; + if (set_null) + owner->null_value= 1; return -1; } @@ -1121,18 +1194,20 @@ int Arg_comparator::compare_string() int Arg_comparator::compare_binary_string() { String *res1,*res2; - if ((res1= (*a)->val_str(&owner->tmp_value1))) + if ((res1= (*a)->val_str(&value1))) { - if ((res2= (*b)->val_str(&owner->tmp_value2))) + if ((res2= (*b)->val_str(&value2))) { - owner->null_value= 0; + if (set_null) + owner->null_value= 0; uint res1_length= res1->length(); uint res2_length= res2->length(); int cmp= memcmp(res1->ptr(), res2->ptr(), min(res1_length,res2_length)); return cmp ? cmp : (int) (res1_length - res2_length); } } - owner->null_value= 1; + if (set_null) + owner->null_value= 1; return -1; } @@ -1145,8 +1220,8 @@ int Arg_comparator::compare_binary_string() int Arg_comparator::compare_e_string() { String *res1,*res2; - res1= (*a)->val_str(&owner->tmp_value1); - res2= (*b)->val_str(&owner->tmp_value2); + res1= (*a)->val_str(&value1); + res2= (*b)->val_str(&value2); if (!res1 || !res2) return test(res1 == res2); return test(sortcmp(res1, res2, cmp_collation.collation) == 0); @@ -1156,8 +1231,8 @@ int Arg_comparator::compare_e_string() int Arg_comparator::compare_e_binary_string() { String *res1,*res2; - res1= (*a)->val_str(&owner->tmp_value1); - res2= (*b)->val_str(&owner->tmp_value2); + res1= (*a)->val_str(&value1); + res2= (*b)->val_str(&value2); if (!res1 || !res2) return test(res1 == res2); return test(stringcmp(res1, res2) == 0); @@ -1178,13 +1253,15 @@ int Arg_comparator::compare_real() val2= (*b)->val_real(); if (!(*b)->null_value) { - owner->null_value= 0; + if (set_null) + owner->null_value= 0; if (val1 < val2) return -1; if (val1 == val2) return 0; return 1; } } - owner->null_value= 1; + if (set_null) + owner->null_value= 1; return -1; } @@ -1198,11 +1275,13 @@ int Arg_comparator::compare_decimal() my_decimal *val2= (*b)->val_decimal(&value2); if (!(*b)->null_value) { - owner->null_value= 0; + if (set_null) + owner->null_value= 0; return my_decimal_cmp(val1, val2); } } - owner->null_value= 1; + if (set_null) + owner->null_value= 1; return -1; } @@ -1240,7 +1319,8 @@ int Arg_comparator::compare_real_fixed() val2= (*b)->val_real(); if (!(*b)->null_value) { - owner->null_value= 0; + if (set_null) + owner->null_value= 0; if (val1 == val2 || fabs(val1 - val2) < precision) return 0; if (val1 < val2) @@ -1248,7 +1328,8 @@ int Arg_comparator::compare_real_fixed() return 1; } } - owner->null_value= 1; + if (set_null) + owner->null_value= 1; return -1; } @@ -1271,13 +1352,15 @@ int Arg_comparator::compare_int_signed() longlong val2= (*b)->val_int(); if (!(*b)->null_value) { - owner->null_value= 0; + if (set_null) + owner->null_value= 0; if (val1 < val2) return -1; if (val1 == val2) return 0; return 1; } } - owner->null_value= 1; + if (set_null) + owner->null_value= 1; return -1; } @@ -1294,13 +1377,15 @@ int Arg_comparator::compare_int_unsigned() ulonglong val2= (*b)->val_int(); if (!(*b)->null_value) { - owner->null_value= 0; + if (set_null) + owner->null_value= 0; if (val1 < val2) return -1; if (val1 == val2) return 0; return 1; } } - owner->null_value= 1; + if (set_null) + owner->null_value= 1; return -1; } @@ -1317,7 +1402,8 @@ int Arg_comparator::compare_int_signed_unsigned() ulonglong uval2= (ulonglong)(*b)->val_int(); if (!(*b)->null_value) { - owner->null_value= 0; + if (set_null) + owner->null_value= 0; if (sval1 < 0 || (ulonglong)sval1 < uval2) return -1; if ((ulonglong)sval1 == uval2) @@ -1325,7 +1411,8 @@ int Arg_comparator::compare_int_signed_unsigned() return 1; } } - owner->null_value= 1; + if (set_null) + owner->null_value= 1; return -1; } @@ -1342,7 +1429,8 @@ int Arg_comparator::compare_int_unsigned_signed() longlong sval2= (*b)->val_int(); if (!(*b)->null_value) { - owner->null_value= 0; + if (set_null) + owner->null_value= 0; if (sval2 < 0) return 1; if (uval1 < (ulonglong)sval2) @@ -1352,7 +1440,8 @@ int Arg_comparator::compare_int_unsigned_signed() return 1; } } - owner->null_value= 1; + if (set_null) + owner->null_value= 1; return -1; } @@ -1388,10 +1477,11 @@ int Arg_comparator::compare_row() for (uint i= 0; inull_value) + /* Aggregate functions don't need special null handling. */ + if (owner->null_value && owner->type() == Item::FUNC_ITEM) { // NULL was compared - switch (owner->functype()) { + switch (((Item_func*)owner)->functype()) { case Item_func::NE_FUNC: break; // NE never aborts on NULL even if abort_on_null is set case Item_func::LT_FUNC: @@ -1400,7 +1490,7 @@ int Arg_comparator::compare_row() case Item_func::GE_FUNC: return -1; // <, <=, > and >= always fail on NULL default: // EQ_FUNC - if (owner->abort_on_null) + if (((Item_bool_func2*)owner)->abort_on_null) return -1; // We do not need correct NULL returning } was_null= 1; @@ -1437,6 +1527,67 @@ int Arg_comparator::compare_e_row() } +/** + Compare values as YEAR. + + @details + Compare items as YEAR for EQUAL_FUNC and for other comparison functions. + The YEAR values of form 19XX are obtained with help of the get_year_value() + function. + If one of arguments is of DATE/DATETIME type its value is obtained + with help of the get_datetime_value function. In this case YEAR values + prior to comparison are converted to the ulonglong YYYY-00-00 00:00:00 + DATETIME form. + If an argument type neither YEAR nor DATE/DATEIME then val_int function + is used to obtain value for comparison. + + RETURN + If is_nulls_eq is TRUE: + 1 if items are equal or both are null + 0 otherwise + If is_nulls_eq is FALSE: + -1 a < b + 0 a == b or at least one of items is null + 1 a > b + See the table: + is_nulls_eq | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | + a_is_null | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | + b_is_null | 1 | 1 | 0 | 0 | 1 | 1 | 0 | 0 | + result | 1 | 0 | 0 |0/1| 0 | 0 | 0 |-1/0/1| +*/ + +int Arg_comparator::compare_year() +{ + bool a_is_null, b_is_null; + ulonglong val1= get_value_a_func ? + (*get_value_a_func)(thd, &a, &a_cache, *b, &a_is_null) : + (*a)->val_int(); + ulonglong val2= get_value_b_func ? + (*get_value_b_func)(thd, &b, &b_cache, *a, &b_is_null) : + (*b)->val_int(); + if (!(*a)->null_value) + { + if (!(*b)->null_value) + { + if (set_null) + owner->null_value= 0; + /* Convert year to DATETIME of form YYYY-00-00 00:00:00 when necessary. */ + if((*a)->field_type() == MYSQL_TYPE_YEAR && year_as_datetime) + val1*= 10000000000LL; + if((*b)->field_type() == MYSQL_TYPE_YEAR && year_as_datetime) + val2*= 10000000000LL; + + if (val1 < val2) return is_nulls_eq ? 0 : -1; + if (val1 == val2) return is_nulls_eq ? 1 : 0; + return is_nulls_eq ? 0 : 1; + } + } + if (set_null) + owner->null_value= is_nulls_eq ? 0 : 1; + return (is_nulls_eq && (*a)->null_value == (*b)->null_value) ? 1 : 0; +} + + void Item_func_truth::fix_length_and_dec() { maybe_null= 0; @@ -1711,8 +1862,8 @@ longlong Item_func_lt::val_int() longlong Item_func_strcmp::val_int() { DBUG_ASSERT(fixed == 1); - String *a=args[0]->val_str(&tmp_value1); - String *b=args[1]->val_str(&tmp_value2); + String *a=args[0]->val_str(&cmp.value1); + String *b=args[1]->val_str(&cmp.value2); if (!a || !b) { null_value=1; @@ -1995,8 +2146,8 @@ void Item_func_between::fix_length_and_dec() if (compare_as_dates) { - ge_cmp.set_datetime_cmp_func(args, args + 1); - le_cmp.set_datetime_cmp_func(args, args + 2); + ge_cmp.set_datetime_cmp_func(this, args, args + 1); + le_cmp.set_datetime_cmp_func(this, args, args + 2); } else if (time_items_found == 3) { @@ -4324,13 +4475,13 @@ void Item_func_isnotnull::print(String *str, enum_query_type query_type) longlong Item_func_like::val_int() { DBUG_ASSERT(fixed == 1); - String* res = args[0]->val_str(&tmp_value1); + String* res = args[0]->val_str(&cmp.value1); if (args[0]->null_value) { null_value=1; return 0; } - String* res2 = args[1]->val_str(&tmp_value2); + String* res2 = args[1]->val_str(&cmp.value2); if (args[1]->null_value) { null_value=1; @@ -4354,7 +4505,7 @@ Item_func::optimize_type Item_func_like::select_optimize() const { if (args[1]->const_item()) { - String* res2= args[1]->val_str((String *)&tmp_value2); + String* res2= args[1]->val_str((String *)&cmp.value2); if (!res2) return OPTIMIZE_NONE; @@ -4385,7 +4536,7 @@ bool Item_func_like::fix_fields(THD *thd, Item **ref) if (escape_item->const_item()) { /* If we are on execution stage */ - String *escape_str= escape_item->val_str(&tmp_value1); + String *escape_str= escape_item->val_str(&cmp.value1); if (escape_str) { if (escape_used_in_parsing && ( @@ -4440,7 +4591,7 @@ bool Item_func_like::fix_fields(THD *thd, Item **ref) if (args[1]->const_item() && !use_strnxfrm(collation.collation) && !(specialflag & SPECIAL_NO_NEW_FUNC)) { - String* res2 = args[1]->val_str(&tmp_value2); + String* res2 = args[1]->val_str(&cmp.value2); if (!res2) return FALSE; // Null argument diff --git a/sql/item_cmpfunc.h b/sql/item_cmpfunc.h index c2227fa04e0..3cf4a1473cc 100644 --- a/sql/item_cmpfunc.h +++ b/sql/item_cmpfunc.h @@ -32,7 +32,7 @@ class Arg_comparator: public Sql_alloc { Item **a, **b; arg_cmp_func func; - Item_bool_func2 *owner; + Item_result_field *owner; Arg_comparator *comparators; // used only for compare_row() double precision; /* Fields used in DATE/DATETIME comparison. */ @@ -40,30 +40,42 @@ class Arg_comparator: public Sql_alloc enum_field_types a_type, b_type; // Types of a and b items Item *a_cache, *b_cache; // Cached values of a and b items bool is_nulls_eq; // TRUE <=> compare for the EQUAL_FUNC + bool set_null; // TRUE <=> set owner->null_value + // when one of arguments is NULL. + bool year_as_datetime; // TRUE <=> convert YEAR value to + // the YYYY-00-00 00:00:00 DATETIME + // format. See compare_year. enum enum_date_cmp_type { CMP_DATE_DFLT= 0, CMP_DATE_WITH_DATE, CMP_DATE_WITH_STR, CMP_STR_WITH_DATE }; - longlong (*get_value_func)(THD *thd, Item ***item_arg, Item **cache_arg, - Item *warn_item, bool *is_null); + longlong (*get_value_a_func)(THD *thd, Item ***item_arg, Item **cache_arg, + Item *warn_item, bool *is_null); + longlong (*get_value_b_func)(THD *thd, Item ***item_arg, Item **cache_arg, + Item *warn_item, bool *is_null); public: DTCollation cmp_collation; + /* Allow owner function to use string buffers. */ + String value1, value2; - Arg_comparator(): thd(0), a_cache(0), b_cache(0) {}; + Arg_comparator(): thd(0), a_cache(0), b_cache(0), set_null(0), + year_as_datetime(0), get_value_a_func(0), get_value_b_func(0) {}; Arg_comparator(Item **a1, Item **a2): a(a1), b(a2), thd(0), - a_cache(0), b_cache(0) {}; + a_cache(0), b_cache(0), set_null(0), year_as_datetime(0), + get_value_a_func(0), get_value_b_func(0) {}; - int set_compare_func(Item_bool_func2 *owner, Item_result type); - inline int set_compare_func(Item_bool_func2 *owner_arg) + int set_compare_func(Item_result_field *owner, Item_result type); + inline int set_compare_func(Item_result_field *owner_arg) { return set_compare_func(owner_arg, item_cmp_type((*a)->result_type(), (*b)->result_type())); } - int set_cmp_func(Item_bool_func2 *owner_arg, + int set_cmp_func(Item_result_field *owner_arg, Item **a1, Item **a2, Item_result type); - inline int set_cmp_func(Item_bool_func2 *owner_arg, - Item **a1, Item **a2) + inline int set_cmp_func(Item_result_field *owner_arg, + Item **a1, Item **a2, bool set_null_arg) { + set_null= set_null_arg; return set_cmp_func(owner_arg, a1, a2, item_cmp_type((*a1)->result_type(), (*a2)->result_type())); @@ -89,12 +101,18 @@ public: int compare_real_fixed(); int compare_e_real_fixed(); int compare_datetime(); // compare args[0] & args[1] as DATETIMEs + int compare_year(); static enum enum_date_cmp_type can_compare_as_dates(Item *a, Item *b, ulonglong *const_val_arg); - void set_datetime_cmp_func(Item **a1, Item **b1); + void set_datetime_cmp_func(Item_result_field *owner_arg, Item **a1, Item **b1); static arg_cmp_func comparator_matrix [5][2]; + inline bool is_owner_equal_func() + { + return (owner->type() == Item::FUNC_ITEM && + ((Item_func*)owner)->functype() == Item_func::EQUAL_FUNC); + } friend class Item_func; }; @@ -324,7 +342,6 @@ class Item_bool_func2 :public Item_int_func { /* Bool with 2 string args */ protected: Arg_comparator cmp; - String tmp_value1,tmp_value2; bool abort_on_null; public: @@ -333,7 +350,7 @@ public: void fix_length_and_dec(); void set_cmp_func() { - cmp.set_cmp_func(this, tmp_arg, tmp_arg+1); + cmp.set_cmp_func(this, tmp_arg, tmp_arg+1, TRUE); } optimize_type select_optimize() const { return OPTIMIZE_OP; } virtual enum Functype rev_functype() const { return UNKNOWN_FUNC; } diff --git a/sql/item_func.h b/sql/item_func.h index fdbbff89e60..4a3632fd87e 100644 --- a/sql/item_func.h +++ b/sql/item_func.h @@ -124,17 +124,6 @@ public: virtual optimize_type select_optimize() const { return OPTIMIZE_NONE; } virtual bool have_rev_func() const { return 0; } virtual Item *key_item() const { return args[0]; } - /* - This method is used for debug purposes to print the name of an - item to the debug log. The second use of this method is as - a helper function of print(), where it is applicable. - To suit both goals it should return a meaningful, - distinguishable and sintactically correct string. This method - should not be used for runtime type identification, use enum - {Sum}Functype and Item_func::functype()/Item_sum::sum_func() - instead. - */ - virtual const char *func_name() const= 0; virtual bool const_item() const { return const_item_cache; } inline Item **arguments() const { return args; } void set_arguments(List &list); diff --git a/sql/item_geofunc.cc b/sql/item_geofunc.cc index a34204b7181..9b01f77c9a1 100644 --- a/sql/item_geofunc.cc +++ b/sql/item_geofunc.cc @@ -506,8 +506,8 @@ err: longlong Item_func_spatial_rel::val_int() { DBUG_ASSERT(fixed == 1); - String *res1= args[0]->val_str(&tmp_value1); - String *res2= args[1]->val_str(&tmp_value2); + String *res1= args[0]->val_str(&cmp.value1); + String *res2= args[1]->val_str(&cmp.value2); Geometry_buffer buffer1, buffer2; Geometry *g1, *g2; MBR mbr1, mbr2; diff --git a/sql/item_subselect.h b/sql/item_subselect.h index d4aa621c083..467e9b22637 100644 --- a/sql/item_subselect.h +++ b/sql/item_subselect.h @@ -132,6 +132,7 @@ public: @return the SELECT_LEX structure associated with this Item */ st_select_lex* get_select_lex(); + const char *func_name() const { DBUG_ASSERT(0); return "subselect"; } friend class select_subselect; friend class Item_in_optimizer; diff --git a/sql/item_sum.cc b/sql/item_sum.cc index 08a48c6ce2f..52292486662 100644 --- a/sql/item_sum.cc +++ b/sql/item_sum.cc @@ -614,35 +614,6 @@ Item_sum_num::fix_fields(THD *thd, Item **ref) } -Item_sum_hybrid::Item_sum_hybrid(THD *thd, Item_sum_hybrid *item) - :Item_sum(thd, item), value(item->value), hybrid_type(item->hybrid_type), - hybrid_field_type(item->hybrid_field_type), cmp_sign(item->cmp_sign), - was_values(item->was_values) -{ - /* copy results from old value */ - switch (hybrid_type) { - case INT_RESULT: - sum_int= item->sum_int; - break; - case DECIMAL_RESULT: - my_decimal2decimal(&item->sum_dec, &sum_dec); - break; - case REAL_RESULT: - sum= item->sum; - break; - case STRING_RESULT: - /* - This can happen with ROLLUP. Note that the value is already - copied at function call. - */ - break; - case ROW_RESULT: - default: - DBUG_ASSERT(0); - } - collation.set(item->collation); -} - bool Item_sum_hybrid::fix_fields(THD *thd, Item **ref) { @@ -662,15 +633,12 @@ Item_sum_hybrid::fix_fields(THD *thd, Item **ref) switch (hybrid_type= item->result_type()) { case INT_RESULT: max_length= 20; - sum_int= 0; break; case DECIMAL_RESULT: max_length= item->max_length; - my_decimal_set_zero(&sum_dec); break; case REAL_RESULT: max_length= float_length(decimals); - sum= 0.0; break; case STRING_RESULT: max_length= item->max_length; @@ -679,10 +647,10 @@ Item_sum_hybrid::fix_fields(THD *thd, Item **ref) default: DBUG_ASSERT(0); }; + setup(args[0], NULL); /* MIN/MAX can return NULL for empty set indepedent of the used column */ maybe_null= 1; unsigned_flag=item->unsigned_flag; - collation.set(item->collation); result_field=0; null_value=1; fix_length_and_dec(); @@ -700,6 +668,31 @@ Item_sum_hybrid::fix_fields(THD *thd, Item **ref) return FALSE; } + +/** + MIN/MAX function setup. + + @param item argument of MIN/MAX function + @param value_arg calculated value of MIN/MAX function + + @details + Setup cache/comparator of MIN/MAX functions. When called by the + copy_or_same function value_arg parameter contains calculated value + of the original MIN/MAX object and it is saved in this object's cache. +*/ + +void Item_sum_hybrid::setup(Item *item, Item *value_arg) +{ + value= Item_cache::get_cache(item); + value->setup(item); + if (value_arg) + value->store(value_arg); + cmp= new Arg_comparator(); + cmp->set_cmp_func(this, args, (Item**)&value, FALSE); + collation.set(item->collation); +} + + Field *Item_sum_hybrid::create_tmp_field(bool group, TABLE *table, uint convert_blob_length) { @@ -1591,19 +1584,7 @@ void Item_sum_variance::update_field() void Item_sum_hybrid::clear() { - switch (hybrid_type) { - case INT_RESULT: - sum_int= 0; - break; - case DECIMAL_RESULT: - my_decimal_set_zero(&sum_dec); - break; - case REAL_RESULT: - sum= 0.0; - break; - default: - value.length(0); - } + value->null_value= 1; null_value= 1; } @@ -1612,30 +1593,7 @@ double Item_sum_hybrid::val_real() DBUG_ASSERT(fixed == 1); if (null_value) return 0.0; - switch (hybrid_type) { - case STRING_RESULT: - { - char *end_not_used; - int err_not_used; - String *res; res=val_str(&str_value); - return (res ? my_strntod(res->charset(), (char*) res->ptr(), res->length(), - &end_not_used, &err_not_used) : 0.0); - } - case INT_RESULT: - if (unsigned_flag) - return ulonglong2double(sum_int); - return (double) sum_int; - case DECIMAL_RESULT: - my_decimal2double(E_DEC_FATAL_ERROR, &sum_dec, &sum); - return sum; - case REAL_RESULT: - return sum; - case ROW_RESULT: - default: - // This case should never be choosen - DBUG_ASSERT(0); - return 0; - } + return value->val_real(); } longlong Item_sum_hybrid::val_int() @@ -1643,18 +1601,7 @@ longlong Item_sum_hybrid::val_int() DBUG_ASSERT(fixed == 1); if (null_value) return 0; - switch (hybrid_type) { - case INT_RESULT: - return sum_int; - case DECIMAL_RESULT: - { - longlong result; - my_decimal2int(E_DEC_FATAL_ERROR, &sum_dec, unsigned_flag, &result); - return sum_int; - } - default: - return (longlong) rint(Item_sum_hybrid::val_real()); - } + return value->val_int(); } @@ -1663,26 +1610,7 @@ my_decimal *Item_sum_hybrid::val_decimal(my_decimal *val) DBUG_ASSERT(fixed == 1); if (null_value) return 0; - switch (hybrid_type) { - case STRING_RESULT: - string2my_decimal(E_DEC_FATAL_ERROR, &value, val); - break; - case REAL_RESULT: - double2my_decimal(E_DEC_FATAL_ERROR, sum, val); - break; - case DECIMAL_RESULT: - val= &sum_dec; - break; - case INT_RESULT: - int2my_decimal(E_DEC_FATAL_ERROR, sum_int, unsigned_flag, val); - break; - case ROW_RESULT: - default: - // This case should never be choosen - DBUG_ASSERT(0); - break; - } - return val; // Keep compiler happy + return value->val_decimal(val); } @@ -1692,25 +1620,7 @@ Item_sum_hybrid::val_str(String *str) DBUG_ASSERT(fixed == 1); if (null_value) return 0; - switch (hybrid_type) { - case STRING_RESULT: - return &value; - case REAL_RESULT: - str->set_real(sum,decimals, &my_charset_bin); - break; - case DECIMAL_RESULT: - my_decimal2string(E_DEC_FATAL_ERROR, &sum_dec, 0, 0, 0, str); - return str; - case INT_RESULT: - str->set_int(sum_int, unsigned_flag, &my_charset_bin); - break; - case ROW_RESULT: - default: - // This case should never be choosen - DBUG_ASSERT(0); - break; - } - return str; // Keep compiler happy + return value->val_str(str); } @@ -1719,7 +1629,9 @@ void Item_sum_hybrid::cleanup() DBUG_ENTER("Item_sum_hybrid::cleanup"); Item_sum::cleanup(); forced_const= FALSE; - + if (cmp) + delete cmp; + cmp= 0; /* by default it is TRUE to avoid TRUE reporting by Item_func_not_all/Item_func_nop_all if this item was never called. @@ -1740,63 +1652,21 @@ void Item_sum_hybrid::no_rows_in_result() Item *Item_sum_min::copy_or_same(THD* thd) { - return new (thd->mem_root) Item_sum_min(thd, this); + Item_sum_min *item= new (thd->mem_root) Item_sum_min(thd, this); + item->setup(args[0], value); + return item; } bool Item_sum_min::add() { - switch (hybrid_type) { - case STRING_RESULT: + /* args[0] < value */ + int res= cmp->compare(); + if (!args[0]->null_value && + (null_value || res < 0)) { - String *result=args[0]->val_str(&tmp_value); - if (!args[0]->null_value && - (null_value || sortcmp(&value,result,collation.collation) > 0)) - { - value.copy(*result); - null_value=0; - } - } - break; - case INT_RESULT: - { - longlong nr=args[0]->val_int(); - if (!args[0]->null_value && (null_value || - (unsigned_flag && - (ulonglong) nr < (ulonglong) sum_int) || - (!unsigned_flag && nr < sum_int))) - { - sum_int=nr; - null_value=0; - } - } - break; - case DECIMAL_RESULT: - { - my_decimal value_buff, *val= args[0]->val_decimal(&value_buff); - if (!args[0]->null_value && - (null_value || (my_decimal_cmp(&sum_dec, val) > 0))) - { - my_decimal2decimal(val, &sum_dec); - null_value= 0; - } - } - break; - case REAL_RESULT: - { - double nr= args[0]->val_real(); - if (!args[0]->null_value && (null_value || nr < sum)) - { - sum=nr; - null_value=0; - } - } - break; - case ROW_RESULT: - default: - // This case should never be choosen - DBUG_ASSERT(0); - break; + value->store(args[0]); + null_value= 0; } return 0; } @@ -1804,63 +1674,21 @@ bool Item_sum_min::add() Item *Item_sum_max::copy_or_same(THD* thd) { - return new (thd->mem_root) Item_sum_max(thd, this); + Item_sum_max *item= new (thd->mem_root) Item_sum_max(thd, this); + item->setup(args[0], value); + return item; } bool Item_sum_max::add() { - switch (hybrid_type) { - case STRING_RESULT: + /* args[0] > value */ + int res= cmp->compare(); + if (!args[0]->null_value && + (null_value || res > 0)) { - String *result=args[0]->val_str(&tmp_value); - if (!args[0]->null_value && - (null_value || sortcmp(&value,result,collation.collation) < 0)) - { - value.copy(*result); - null_value=0; - } - } - break; - case INT_RESULT: - { - longlong nr=args[0]->val_int(); - if (!args[0]->null_value && (null_value || - (unsigned_flag && - (ulonglong) nr > (ulonglong) sum_int) || - (!unsigned_flag && nr > sum_int))) - { - sum_int=nr; - null_value=0; - } - } - break; - case DECIMAL_RESULT: - { - my_decimal value_buff, *val= args[0]->val_decimal(&value_buff); - if (!args[0]->null_value && - (null_value || (my_decimal_cmp(val, &sum_dec) > 0))) - { - my_decimal2decimal(val, &sum_dec); - null_value= 0; - } - } - break; - case REAL_RESULT: - { - double nr= args[0]->val_real(); - if (!args[0]->null_value && (null_value || nr > sum)) - { - sum=nr; - null_value=0; - } - } - break; - case ROW_RESULT: - default: - // This case should never be choosen - DBUG_ASSERT(0); - break; + value->store(args[0]); + null_value= 0; } return 0; } @@ -2225,14 +2053,15 @@ void Item_sum_hybrid::update_field() void Item_sum_hybrid::min_max_update_str_field() { - String *res_str=args[0]->val_str(&value); + DBUG_ASSERT(cmp); + String *res_str=args[0]->val_str(&cmp->value1); if (!args[0]->null_value) { - result_field->val_str(&tmp_value); + result_field->val_str(&cmp->value2); if (result_field->is_null() || - (cmp_sign * sortcmp(res_str,&tmp_value,collation.collation)) < 0) + (cmp_sign * sortcmp(res_str,&cmp->value2,collation.collation)) < 0) result_field->store(res_str->ptr(),res_str->length(),res_str->charset()); result_field->set_notnull(); } diff --git a/sql/item_sum.h b/sql/item_sum.h index d991327d847..f70da52bcd1 100644 --- a/sql/item_sum.h +++ b/sql/item_sum.h @@ -323,22 +323,6 @@ public: virtual void update_field()=0; virtual bool keep_field_type(void) const { return 0; } virtual void fix_length_and_dec() { maybe_null=1; null_value=1; } - /* - This method is used for debug purposes to print the name of an - item to the debug log. The second use of this method is as - a helper function of print(), where it is applicable. - To suit both goals it should return a meaningful, - distinguishable and sintactically correct string. This method - should not be used for runtime type identification, use enum - {Sum}Functype and Item_func::functype()/Item_sum::sum_func() - instead. - - NOTE: for Items inherited from Item_sum, func_name() return part of - function name till first argument (including '(') to make difference in - names for functions with 'distinct' clause and without 'distinct' and - also to make printing of items inherited from Item_sum uniform. - */ - virtual const char *func_name() const= 0; virtual Item *result_item(Field *field) { return new Item_field(field); } table_map used_tables() const { return used_tables_cache; } @@ -664,6 +648,7 @@ public: } void fix_length_and_dec() {} enum Item_result result_type () const { return hybrid_type; } + const char *func_name() const { DBUG_ASSERT(0); return "avg_field"; } }; @@ -732,6 +717,7 @@ public: } void fix_length_and_dec() {} enum Item_result result_type () const { return hybrid_type; } + const char *func_name() const { DBUG_ASSERT(0); return "variance_field"; } }; @@ -807,6 +793,7 @@ public: my_decimal *val_decimal(my_decimal *); enum Item_result result_type () const { return REAL_RESULT; } enum_field_types field_type() const { return MYSQL_TYPE_DOUBLE;} + const char *func_name() const { DBUG_ASSERT(0); return "std_field"; } }; /* @@ -832,14 +819,13 @@ class Item_sum_std :public Item_sum_variance }; // This class is a string or number function depending on num_func - +class Arg_comparator; +class Item_cache; class Item_sum_hybrid :public Item_sum { protected: - String value,tmp_value; - double sum; - longlong sum_int; - my_decimal sum_dec; + Item_cache *value; + Arg_comparator *cmp; Item_result hybrid_type; enum_field_types hybrid_field_type; int cmp_sign; @@ -847,12 +833,17 @@ protected: public: Item_sum_hybrid(Item *item_par,int sign) - :Item_sum(item_par), sum(0.0), sum_int(0), + :Item_sum(item_par), value(0), cmp(0), hybrid_type(INT_RESULT), hybrid_field_type(MYSQL_TYPE_LONGLONG), cmp_sign(sign), was_values(TRUE) { collation.set(&my_charset_bin); } - Item_sum_hybrid(THD *thd, Item_sum_hybrid *item); + Item_sum_hybrid(THD *thd, Item_sum_hybrid *item) + :Item_sum(thd, item), value(item->value), hybrid_type(item->hybrid_type), + hybrid_field_type(item->hybrid_field_type), cmp_sign(item->cmp_sign), + was_values(item->was_values) + { } bool fix_fields(THD *, Item **); + void setup(Item *item, Item *value_arg); void clear(); double val_real(); longlong val_int(); From f24dbcc56b48ef1a2b361ac831f4b1f4de8efc48 Mon Sep 17 00:00:00 2001 From: Georgi Kodinov Date: Fri, 20 Nov 2009 12:10:47 +0200 Subject: [PATCH 05/11] Bug #45261 : Crash, stored procedure + decimal Bug #48370 Absolutely wrong calculations with GROUP BY and decimal fields when using IF Added the test cases in the above two bugs for regression testing. Added additional tests that demonstrate a incomplete fix. Added a new factory method for Field_new_decimal to create a field from an (decimal returning) Item. In the new method made sure that all the precision and length variables are capped in a proper way. This is required because Item's can have larger precision than the decimal fields and thus need to be capped when creating a field based on an Item type. Fixed the wrong typecast to Item_decimal. --- mysql-test/r/type_newdecimal.result | 284 ++++++++++++++++++++++++++++ mysql-test/t/type_newdecimal.test | 226 ++++++++++++++++++++++ sql/field.cc | 44 +++++ sql/field.h | 1 + sql/item.cc | 4 +- sql/item_func.cc | 39 +--- sql/item_sum.cc | 6 +- sql/sql_select.cc | 41 +--- 8 files changed, 560 insertions(+), 85 deletions(-) diff --git a/mysql-test/r/type_newdecimal.result b/mysql-test/r/type_newdecimal.result index 748aadee4fb..70ee3a56cf3 100644 --- a/mysql-test/r/type_newdecimal.result +++ b/mysql-test/r/type_newdecimal.result @@ -1630,3 +1630,287 @@ SELECT my_col FROM t1; my_col 0.012345687012345687012345687012 DROP TABLE t1; +# +# Bug#45261: Crash, stored procedure + decimal +# +DROP TABLE IF EXISTS t1; +CREATE TABLE t1 SELECT +/* 81 */ 100000000000000000000000000000000000000000000000000000000000000000000000000000001 +AS c1; +Warnings: +Warning 1264 Out of range value for column 'c1' at row 1 +DESC t1; +Field Type Null Key Default Extra +c1 decimal(65,0) NO 0 +SELECT * FROM t1; +c1 +99999999999999999999999999999999999999999999999999999999999999999 +DROP TABLE t1; +CREATE TABLE t1 SELECT +/* 81 */ 100000000000000000000000000000000000000000000000000000000000000000000000000000001. +AS c1; +Warnings: +Warning 1264 Out of range value for column 'c1' at row 1 +DESC t1; +Field Type Null Key Default Extra +c1 decimal(65,0) NO 0 +SELECT * FROM t1; +c1 +99999999999999999999999999999999999999999999999999999999999999999 +DROP TABLE t1; +CREATE TABLE t1 SELECT +/* 81 */ 100000000000000000000000000000000000000000000000000000000000000000000000000000001.1 /* 1 */ +AS c1; +Warnings: +Warning 1264 Out of range value for column 'c1' at row 1 +DESC t1; +Field Type Null Key Default Extra +c1 decimal(65,0) NO 0 +SELECT * FROM t1; +c1 +99999999999999999999999999999999999999999999999999999999999999999 +DROP TABLE t1; +CREATE TABLE t1 SELECT +/* 82 */ 1000000000000000000000000000000000000000000000000000000000000000000000000000000001 +AS c1; +Warnings: +Error 1292 Truncated incorrect DECIMAL value: '' +DESC t1; +Field Type Null Key Default Extra +c1 decimal(65,0) NO 0 +SELECT * FROM t1; +c1 +99999999999999999999999999999999999999999999999999999999999999999 +DROP TABLE t1; +CREATE TABLE t1 SELECT +/* 40 */ 1000000000000000000000000000000000000001.1000000000000000000000000000000000000001 /* 40 */ +AS c1; +Warnings: +Warning 1264 Out of range value for column 'c1' at row 1 +DESC t1; +Field Type Null Key Default Extra +c1 decimal(65,30) NO 0.000000000000000000000000000000 +SELECT * FROM t1; +c1 +99999999999999999999999999999999999.999999999999999999999999999999 +DROP TABLE t1; +CREATE TABLE t1 SELECT +/* 1 */ 1.10000000000000000000000000000000000000000000000000000000000000000000000000000001 /* 80 */ +AS c1; +DESC t1; +Field Type Null Key Default Extra +c1 decimal(31,30) NO 0.000000000000000000000000000000 +SELECT * FROM t1; +c1 +1.100000000000000000000000000000 +DROP TABLE t1; +CREATE TABLE t1 SELECT +/* 1 */ 1.100000000000000000000000000000000000000000000000000000000000000000000000000000001 /* 81 */ +AS c1; +DESC t1; +Field Type Null Key Default Extra +c1 decimal(31,30) NO 0.000000000000000000000000000000 +SELECT * FROM t1; +c1 +1.100000000000000000000000000000 +DROP TABLE t1; +CREATE TABLE t1 SELECT +.100000000000000000000000000000000000000000000000000000000000000000000000000000001 /* 81 */ +AS c1; +Warnings: +Note 1265 Data truncated for column 'c1' at row 1 +DESC t1; +Field Type Null Key Default Extra +c1 decimal(30,30) NO 0.000000000000000000000000000000 +SELECT * FROM t1; +c1 +0.100000000000000000000000000000 +DROP TABLE t1; +CREATE TABLE t1 SELECT +/* 45 */ 123456789012345678901234567890123456789012345.123456789012345678901234567890123456789012345 /* 45 */ +AS c1; +Warnings: +Warning 1264 Out of range value for column 'c1' at row 1 +DESC t1; +Field Type Null Key Default Extra +c1 decimal(65,30) NO 0.000000000000000000000000000000 +SELECT * FROM t1; +c1 +99999999999999999999999999999999999.999999999999999999999999999999 +DROP TABLE t1; +CREATE TABLE t1 SELECT +/* 65 */ 12345678901234567890123456789012345678901234567890123456789012345.1 /* 1 */ +AS c1; +Warnings: +Warning 1264 Out of range value for column 'c1' at row 1 +DESC t1; +Field Type Null Key Default Extra +c1 decimal(65,1) NO 0.0 +SELECT * FROM t1; +c1 +9999999999999999999999999999999999999999999999999999999999999999.9 +DROP TABLE t1; +CREATE TABLE t1 SELECT +/* 66 */ 123456789012345678901234567890123456789012345678901234567890123456.1 /* 1 */ +AS c1; +Warnings: +Warning 1264 Out of range value for column 'c1' at row 1 +DESC t1; +Field Type Null Key Default Extra +c1 decimal(65,1) NO 0.0 +SELECT * FROM t1; +c1 +9999999999999999999999999999999999999999999999999999999999999999.9 +DROP TABLE t1; +CREATE TABLE t1 SELECT +.123456789012345678901234567890123456789012345678901234567890123456 /* 66 */ +AS c1; +Warnings: +Note 1265 Data truncated for column 'c1' at row 1 +DESC t1; +Field Type Null Key Default Extra +c1 decimal(30,30) NO 0.000000000000000000000000000000 +SELECT * FROM t1; +c1 +0.123456789012345678901234567890 +DROP TABLE t1; +CREATE TABLE t1 AS SELECT 123.1234567890123456789012345678901 /* 31 */ AS c1; +Warnings: +Note 1265 Data truncated for column 'c1' at row 1 +DESC t1; +Field Type Null Key Default Extra +c1 decimal(33,30) NO 0.000000000000000000000000000000 +SELECT * FROM t1; +c1 +123.123456789012345678901234567890 +DROP TABLE t1; +CREATE TABLE t1 SELECT 1.1 + CAST(1 AS DECIMAL(65,30)) AS c1; +DESC t1; +Field Type Null Key Default Extra +c1 decimal(65,30) NO 0.000000000000000000000000000000 +SELECT * FROM t1; +c1 +2.100000000000000000000000000000 +DROP TABLE t1; +# +# Test that the integer and decimal parts are properly calculated. +# +CREATE TABLE t1 (a DECIMAL(30,30)); +INSERT INTO t1 VALUES (0.1),(0.2),(0.3); +CREATE TABLE t2 SELECT MIN(a + 0.0000000000000000000000000000001) AS c1 FROM t1; +Warnings: +Note 1265 Data truncated for column 'c1' at row 3 +DESC t2; +Field Type Null Key Default Extra +c1 decimal(32,30) YES NULL +DROP TABLE t1,t2; +CREATE TABLE t1 (a DECIMAL(30,30)); +INSERT INTO t1 VALUES (0.1),(0.2),(0.3); +CREATE TABLE t2 SELECT IFNULL(a + 0.0000000000000000000000000000001, NULL) AS c1 FROM t1; +Warnings: +Note 1265 Data truncated for column 'c1' at row 1 +Note 1265 Data truncated for column 'c1' at row 2 +Note 1265 Data truncated for column 'c1' at row 3 +DESC t2; +Field Type Null Key Default Extra +c1 decimal(34,0) YES NULL +DROP TABLE t1,t2; +CREATE TABLE t1 (a DECIMAL(30,30)); +INSERT INTO t1 VALUES (0.1),(0.2),(0.3); +CREATE TABLE t2 SELECT CASE a WHEN 0.1 THEN 0.0000000000000000000000000000000000000000000000000000000000000000001 END AS c1 FROM t1; +Warnings: +Note 1265 Data truncated for column 'c1' at row 1 +DESC t2; +Field Type Null Key Default Extra +c1 decimal(65,30) YES NULL +DROP TABLE t1,t2; +# +# Test that variables get maximum precision. +# +SET @decimal= 1.1; +CREATE TABLE t1 SELECT @decimal AS c1; +DESC t1; +Field Type Null Key Default Extra +c1 decimal(65,30) YES NULL +SELECT * FROM t1; +c1 +1.100000000000000000000000000000 +DROP TABLE t1; +# +# Bug #45261 : Crash, stored procedure + decimal +# Original test by the reporter. +# +# should not crash +CREATE TABLE t1 +SELECT .123456789012345678901234567890123456789012345678901234567890123456 AS a; +Warnings: +Note 1265 Data truncated for column 'a' at row 1 +DROP TABLE t1; +CREATE PROCEDURE test_proc() +BEGIN +# The las non critical CUSER definition is: +# DECLARE mycursor CURSOR FOR SELECT 1 % +# .12345678912345678912345678912345678912345678912345678912345678912 AS my_col; +DECLARE mycursor CURSOR FOR +SELECT 1 % +.123456789123456789123456789123456789123456789123456789123456789123456789123456789 +AS my_col; +OPEN mycursor; +CLOSE mycursor; +END| +# should not crash +CALL test_proc(); +DROP PROCEDURE test_proc; +# +# Bug #48370 Absolutely wrong calculations with GROUP BY and +# decimal fields when using IF +# +CREATE TABLE currencies (id int, rate decimal(16,4), +PRIMARY KEY (id), KEY (rate)); +INSERT INTO currencies VALUES (11,0.7028); +INSERT INTO currencies VALUES (1,1); +CREATE TABLE payments ( +id int, +supplier_id int, +status int, +currency_id int, +vat decimal(7,4), +PRIMARY KEY (id), +KEY currency_id (currency_id), +KEY supplier_id (supplier_id) +); +INSERT INTO payments (id,status,vat,supplier_id,currency_id) VALUES +(3001,2,0.0000,344,11), (1,2,0.0000,1,1); +CREATE TABLE sub_tasks ( +id int, +currency_id int, +price decimal(16,4), +discount decimal(10,4), +payment_id int, +PRIMARY KEY (id), +KEY currency_id (currency_id), +KEY payment_id (payment_id) +) ; +INSERT INTO sub_tasks (id, price, discount, payment_id, currency_id) VALUES +(52, 12.60, 0, 3001, 11), (56, 14.58, 0, 3001, 11); +# should return 1 and the same values in col 2 and 3 +select STRAIGHT_JOIN +(1 + PAY.vat) AS mult, +SUM(ROUND((SUB.price - ROUND(ROUND(SUB.price, 2) * SUB.discount, 2)) * +CUR.rate / CUR.rate, 2) +) v_net_with_discount, +SUM(ROUND((SUB.price - ROUND(ROUND(SUB.price, 2) * SUB.discount, 1)) * +CUR.rate / CUR.rate , 2) +* (1 + PAY.vat) +) v_total +from +currencies CUR, payments PAY, sub_tasks SUB +where +SUB.payment_id = PAY.id and +PAY.currency_id = CUR.id and +PAY.id > 2 +group by PAY.id + 1; +mult v_net_with_discount v_total +1.0000 27.18 27.180000 +DROP TABLE currencies, payments, sub_tasks; +End of 5.1 tests diff --git a/mysql-test/t/type_newdecimal.test b/mysql-test/t/type_newdecimal.test index cd3c3f81510..2cf7ab8fbdf 100644 --- a/mysql-test/t/type_newdecimal.test +++ b/mysql-test/t/type_newdecimal.test @@ -1286,3 +1286,229 @@ CREATE TABLE t1 SELECT 1 % .1234567891234567891234567891234567891234567891234567 DESCRIBE t1; SELECT my_col FROM t1; DROP TABLE t1; + +--echo # +--echo # Bug#45261: Crash, stored procedure + decimal +--echo # + +--disable_warnings +DROP TABLE IF EXISTS t1; +--enable_warnings + +CREATE TABLE t1 SELECT + /* 81 */ 100000000000000000000000000000000000000000000000000000000000000000000000000000001 + AS c1; +DESC t1; +SELECT * FROM t1; +DROP TABLE t1; + +CREATE TABLE t1 SELECT + /* 81 */ 100000000000000000000000000000000000000000000000000000000000000000000000000000001. + AS c1; +DESC t1; +SELECT * FROM t1; +DROP TABLE t1; + +CREATE TABLE t1 SELECT + /* 81 */ 100000000000000000000000000000000000000000000000000000000000000000000000000000001.1 /* 1 */ + AS c1; +DESC t1; +SELECT * FROM t1; +DROP TABLE t1; + +CREATE TABLE t1 SELECT + /* 82 */ 1000000000000000000000000000000000000000000000000000000000000000000000000000000001 + AS c1; +DESC t1; +SELECT * FROM t1; +DROP TABLE t1; + +CREATE TABLE t1 SELECT + /* 40 */ 1000000000000000000000000000000000000001.1000000000000000000000000000000000000001 /* 40 */ + AS c1; +DESC t1; +SELECT * FROM t1; +DROP TABLE t1; + +CREATE TABLE t1 SELECT + /* 1 */ 1.10000000000000000000000000000000000000000000000000000000000000000000000000000001 /* 80 */ + AS c1; +DESC t1; +SELECT * FROM t1; +DROP TABLE t1; + +CREATE TABLE t1 SELECT + /* 1 */ 1.100000000000000000000000000000000000000000000000000000000000000000000000000000001 /* 81 */ + AS c1; +DESC t1; +SELECT * FROM t1; +DROP TABLE t1; + +CREATE TABLE t1 SELECT + .100000000000000000000000000000000000000000000000000000000000000000000000000000001 /* 81 */ + AS c1; +DESC t1; +SELECT * FROM t1; +DROP TABLE t1; + +CREATE TABLE t1 SELECT + /* 45 */ 123456789012345678901234567890123456789012345.123456789012345678901234567890123456789012345 /* 45 */ + AS c1; +DESC t1; +SELECT * FROM t1; +DROP TABLE t1; + +CREATE TABLE t1 SELECT + /* 65 */ 12345678901234567890123456789012345678901234567890123456789012345.1 /* 1 */ + AS c1; +DESC t1; +SELECT * FROM t1; +DROP TABLE t1; + +CREATE TABLE t1 SELECT + /* 66 */ 123456789012345678901234567890123456789012345678901234567890123456.1 /* 1 */ + AS c1; +DESC t1; +SELECT * FROM t1; +DROP TABLE t1; + +CREATE TABLE t1 SELECT + .123456789012345678901234567890123456789012345678901234567890123456 /* 66 */ + AS c1; +DESC t1; +SELECT * FROM t1; +DROP TABLE t1; + +CREATE TABLE t1 AS SELECT 123.1234567890123456789012345678901 /* 31 */ AS c1; +DESC t1; +SELECT * FROM t1; +DROP TABLE t1; + +CREATE TABLE t1 SELECT 1.1 + CAST(1 AS DECIMAL(65,30)) AS c1; +DESC t1; +SELECT * FROM t1; +DROP TABLE t1; + +--echo # +--echo # Test that the integer and decimal parts are properly calculated. +--echo # + +CREATE TABLE t1 (a DECIMAL(30,30)); +INSERT INTO t1 VALUES (0.1),(0.2),(0.3); +CREATE TABLE t2 SELECT MIN(a + 0.0000000000000000000000000000001) AS c1 FROM t1; +DESC t2; +DROP TABLE t1,t2; + +CREATE TABLE t1 (a DECIMAL(30,30)); +INSERT INTO t1 VALUES (0.1),(0.2),(0.3); +CREATE TABLE t2 SELECT IFNULL(a + 0.0000000000000000000000000000001, NULL) AS c1 FROM t1; +DESC t2; +DROP TABLE t1,t2; + +CREATE TABLE t1 (a DECIMAL(30,30)); +INSERT INTO t1 VALUES (0.1),(0.2),(0.3); +CREATE TABLE t2 SELECT CASE a WHEN 0.1 THEN 0.0000000000000000000000000000000000000000000000000000000000000000001 END AS c1 FROM t1; +DESC t2; +DROP TABLE t1,t2; + +--echo # +--echo # Test that variables get maximum precision. +--echo # + +SET @decimal= 1.1; +CREATE TABLE t1 SELECT @decimal AS c1; +DESC t1; +SELECT * FROM t1; +DROP TABLE t1; + +--echo # +--echo # Bug #45261 : Crash, stored procedure + decimal +--echo # Original test by the reporter. +--echo # + +--echo # should not crash +CREATE TABLE t1 +SELECT .123456789012345678901234567890123456789012345678901234567890123456 AS a; +DROP TABLE t1; + +delimiter |; +CREATE PROCEDURE test_proc() +BEGIN + # The las non critical CUSER definition is: + # DECLARE mycursor CURSOR FOR SELECT 1 % + # .12345678912345678912345678912345678912345678912345678912345678912 AS my_col; + DECLARE mycursor CURSOR FOR +SELECT 1 % +.123456789123456789123456789123456789123456789123456789123456789123456789123456789 + AS my_col; + + OPEN mycursor; + CLOSE mycursor; +END| +delimiter ;| +--echo # should not crash +CALL test_proc(); +DROP PROCEDURE test_proc; + +--echo # +--echo # Bug #48370 Absolutely wrong calculations with GROUP BY and +--echo # decimal fields when using IF +--echo # + +CREATE TABLE currencies (id int, rate decimal(16,4), + PRIMARY KEY (id), KEY (rate)); + +INSERT INTO currencies VALUES (11,0.7028); +INSERT INTO currencies VALUES (1,1); + +CREATE TABLE payments ( + id int, + supplier_id int, + status int, + currency_id int, + vat decimal(7,4), + PRIMARY KEY (id), + KEY currency_id (currency_id), + KEY supplier_id (supplier_id) +); + +INSERT INTO payments (id,status,vat,supplier_id,currency_id) VALUES +(3001,2,0.0000,344,11), (1,2,0.0000,1,1); + +CREATE TABLE sub_tasks ( + id int, + currency_id int, + price decimal(16,4), + discount decimal(10,4), + payment_id int, + PRIMARY KEY (id), + KEY currency_id (currency_id), + KEY payment_id (payment_id) +) ; + +INSERT INTO sub_tasks (id, price, discount, payment_id, currency_id) VALUES +(52, 12.60, 0, 3001, 11), (56, 14.58, 0, 3001, 11); + +--echo # should return 1 and the same values in col 2 and 3 +select STRAIGHT_JOIN + (1 + PAY.vat) AS mult, + SUM(ROUND((SUB.price - ROUND(ROUND(SUB.price, 2) * SUB.discount, 2)) * + CUR.rate / CUR.rate, 2) + ) v_net_with_discount, + + SUM(ROUND((SUB.price - ROUND(ROUND(SUB.price, 2) * SUB.discount, 1)) * + CUR.rate / CUR.rate , 2) + * (1 + PAY.vat) + ) v_total +from + currencies CUR, payments PAY, sub_tasks SUB +where + SUB.payment_id = PAY.id and + PAY.currency_id = CUR.id and + PAY.id > 2 +group by PAY.id + 1; + +DROP TABLE currencies, payments, sub_tasks; + + +--echo End of 5.1 tests diff --git a/sql/field.cc b/sql/field.cc index 354c911e1c0..01ccc338782 100644 --- a/sql/field.cc +++ b/sql/field.cc @@ -2486,6 +2486,50 @@ Field_new_decimal::Field_new_decimal(uint32 len_arg, } +Field *Field_new_decimal::create_from_item (Item *item) +{ + uint8 dec= item->decimals; + uint8 intg= item->decimal_precision() - dec; + uint32 len= item->max_length; + + DBUG_ASSERT (item->result_type() == DECIMAL_RESULT); + + /* + Trying to put too many digits overall in a DECIMAL(prec,dec) + will always throw a warning. We must limit dec to + DECIMAL_MAX_SCALE however to prevent an assert() later. + */ + + if (dec > 0) + { + signed int overflow; + + dec= min(dec, DECIMAL_MAX_SCALE); + + /* + If the value still overflows the field with the corrected dec, + we'll throw out decimals rather than integers. This is still + bad and of course throws a truncation warning. + +1: for decimal point + */ + + const int required_length= + my_decimal_precision_to_length(intg + dec, dec, + item->unsigned_flag); + + overflow= required_length - len; + + if (overflow > 0) + dec= max(0, dec - overflow); // too long, discard fract + else + /* Corrected value fits. */ + len= required_length; + } + return new Field_new_decimal(len, item->maybe_null, item->name, + dec, item->unsigned_flag); +} + + int Field_new_decimal::reset(void) { store_value(&decimal_zero); diff --git a/sql/field.h b/sql/field.h index 784b9133790..ae074cc1a30 100644 --- a/sql/field.h +++ b/sql/field.h @@ -807,6 +807,7 @@ public: uint is_equal(Create_field *new_field); virtual const uchar *unpack(uchar* to, const uchar *from, uint param_data, bool low_byte_first); + static Field *create_from_item (Item *); }; diff --git a/sql/item.cc b/sql/item.cc index e1dbc1ba21a..ab70996e541 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -4899,9 +4899,7 @@ Field *Item::tmp_table_field_from_field_type(TABLE *table, bool fixed_length) switch (field_type()) { case MYSQL_TYPE_DECIMAL: case MYSQL_TYPE_NEWDECIMAL: - field= new Field_new_decimal((uchar*) 0, max_length, null_ptr, 0, - Field::NONE, name, decimals, 0, - unsigned_flag); + field= Field_new_decimal::create_from_item(this); break; case MYSQL_TYPE_TINY: field= new Field_tiny((uchar*) 0, max_length, null_ptr, 0, Field::NONE, diff --git a/sql/item_func.cc b/sql/item_func.cc index ac52f36474a..977a0de39af 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -451,45 +451,8 @@ Field *Item_func::tmp_table_field(TABLE *table) return make_string_field(table); break; case DECIMAL_RESULT: - { - uint8 dec= decimals; - uint8 intg= decimal_precision() - dec; - uint32 len= max_length; - - /* - Trying to put too many digits overall in a DECIMAL(prec,dec) - will always throw a warning. We must limit dec to - DECIMAL_MAX_SCALE however to prevent an assert() later. - */ - - if (dec > 0) - { - int overflow; - - dec= min(dec, DECIMAL_MAX_SCALE); - - /* - If the value still overflows the field with the corrected dec, - we'll throw out decimals rather than integers. This is still - bad and of course throws a truncation warning. - */ - - const int required_length= - my_decimal_precision_to_length(intg + dec, dec, - unsigned_flag); - - overflow= required_length - len; - - if (overflow > 0) - dec= max(0, dec - overflow); // too long, discard fract - else - /* Corrected value fits. */ - len= required_length; - } - - field= new Field_new_decimal(len, maybe_null, name, dec, unsigned_flag); + field= Field_new_decimal::create_from_item(this); break; - } case ROW_RESULT: default: // This case should never be chosen diff --git a/sql/item_sum.cc b/sql/item_sum.cc index 38251294053..7bbe167e7ac 100644 --- a/sql/item_sum.cc +++ b/sql/item_sum.cc @@ -517,8 +517,7 @@ Field *Item_sum::create_tmp_field(bool group, TABLE *table, name, table->s, collation.collation); break; case DECIMAL_RESULT: - field= new Field_new_decimal(max_length, maybe_null, name, - decimals, unsigned_flag); + field= Field_new_decimal::create_from_item(this); break; case ROW_RESULT: default: @@ -1270,8 +1269,7 @@ Field *Item_sum_avg::create_tmp_field(bool group, TABLE *table, 0, name, &my_charset_bin); } else if (hybrid_type == DECIMAL_RESULT) - field= new Field_new_decimal(max_length, maybe_null, name, - decimals, unsigned_flag); + field= Field_new_decimal::create_from_item(this); else field= new Field_double(max_length, maybe_null, name, decimals, TRUE); if (field) diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 18fc3b20748..569d8183ab6 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -9466,47 +9466,8 @@ static Field *create_tmp_field_from_item(THD *thd, Item *item, TABLE *table, new_field->set_derivation(item->collation.derivation); break; case DECIMAL_RESULT: - { - uint8 dec= item->decimals; - uint8 intg= ((Item_decimal *) item)->decimal_precision() - dec; - uint32 len= item->max_length; - - /* - Trying to put too many digits overall in a DECIMAL(prec,dec) - will always throw a warning. We must limit dec to - DECIMAL_MAX_SCALE however to prevent an assert() later. - */ - - if (dec > 0) - { - signed int overflow; - - dec= min(dec, DECIMAL_MAX_SCALE); - - /* - If the value still overflows the field with the corrected dec, - we'll throw out decimals rather than integers. This is still - bad and of course throws a truncation warning. - +1: for decimal point - */ - - const int required_length= - my_decimal_precision_to_length(intg + dec, dec, - item->unsigned_flag); - - overflow= required_length - len; - - if (overflow > 0) - dec= max(0, dec - overflow); // too long, discard fract - else - /* Corrected value fits. */ - len= required_length; - } - - new_field= new Field_new_decimal(len, maybe_null, item->name, - dec, item->unsigned_flag); + new_field= Field_new_decimal::create_from_item(item); break; - } case ROW_RESULT: default: // This case should never be choosen From ec3e44778d3b5805fe7323f0df35ee5a3cee23b7 Mon Sep 17 00:00:00 2001 From: Davi Arnaut Date: Tue, 24 Nov 2009 19:25:23 -0200 Subject: [PATCH 06/11] Post-merge fix for bug 41728: revert fix that was pushed in mistake to 5.1 and up. --- include/mysql.h | 10 ---------- include/mysql.h.pp | 10 ---------- libmysql/libmysql.c | 14 -------------- libmysql/libmysql.def | 1 - libmysqld/libmysqld.def | 1 - 5 files changed, 36 deletions(-) diff --git a/include/mysql.h b/include/mysql.h index 68cce3196a0..d114afb6c93 100644 --- a/include/mysql.h +++ b/include/mysql.h @@ -557,16 +557,6 @@ unsigned long STDCALL mysql_real_escape_string(MYSQL *mysql, char *to,const char *from, unsigned long length); void STDCALL mysql_debug(const char *debug); -char * STDCALL mysql_odbc_escape_string(MYSQL *mysql, - char *to, - unsigned long to_length, - const char *from, - unsigned long from_length, - void *param, - char * - (*extend_buffer) - (void *, char *to, - unsigned long *length)); void STDCALL myodbc_remove_escape(MYSQL *mysql,char *name); unsigned int STDCALL mysql_thread_safe(void); my_bool STDCALL mysql_embedded(void); diff --git a/include/mysql.h.pp b/include/mysql.h.pp index bd4c79916dd..633cde41130 100644 --- a/include/mysql.h.pp +++ b/include/mysql.h.pp @@ -518,16 +518,6 @@ unsigned long mysql_real_escape_string(MYSQL *mysql, char *to,const char *from, unsigned long length); void mysql_debug(const char *debug); -char * mysql_odbc_escape_string(MYSQL *mysql, - char *to, - unsigned long to_length, - const char *from, - unsigned long from_length, - void *param, - char * - (*extend_buffer) - (void *, char *to, - unsigned long *length)); void myodbc_remove_escape(MYSQL *mysql,char *name); unsigned int mysql_thread_safe(void); my_bool mysql_embedded(void); diff --git a/libmysql/libmysql.c b/libmysql/libmysql.c index 77ff2a01d7c..1264f2765ba 100644 --- a/libmysql/libmysql.c +++ b/libmysql/libmysql.c @@ -1629,20 +1629,6 @@ mysql_real_escape_string(MYSQL *mysql, char *to,const char *from, return (uint) escape_string_for_mysql(mysql->charset, to, 0, from, length); } - -char * STDCALL -mysql_odbc_escape_string(MYSQL *mysql __attribute__((unused)), - char *to __attribute__((unused)), - ulong to_length __attribute__((unused)), - const char *from __attribute__((unused)), - ulong from_length __attribute__((unused)), - void *param __attribute__((unused)), - char * (*extend_buffer)(void *, char *, ulong *) - __attribute__((unused))) -{ - return NULL; -} - void STDCALL myodbc_remove_escape(MYSQL *mysql,char *name) { diff --git a/libmysql/libmysql.def b/libmysql/libmysql.def index 8c6b71d9553..81f86dc8726 100644 --- a/libmysql/libmysql.def +++ b/libmysql/libmysql.def @@ -78,7 +78,6 @@ EXPORTS mysql_next_result mysql_num_fields mysql_num_rows - mysql_odbc_escape_string mysql_options mysql_stmt_param_count mysql_stmt_param_metadata diff --git a/libmysqld/libmysqld.def b/libmysqld/libmysqld.def index e0f02003963..047cfe0fe57 100644 --- a/libmysqld/libmysqld.def +++ b/libmysqld/libmysqld.def @@ -50,7 +50,6 @@ EXPORTS mysql_next_result mysql_num_fields mysql_num_rows - mysql_odbc_escape_string mysql_options mysql_ping mysql_query From dacd32f9553b3426944fb95a25bbe0f8d4829374 Mon Sep 17 00:00:00 2001 From: Satya B Date: Wed, 25 Nov 2009 12:25:49 +0530 Subject: [PATCH 07/11] Fix for BUG#47671 - wrong character-set after upgrade from 5.1.34 to 5.1.39 mysql client displays wrong character-set of server. When a user changes the charset of a server, mysql client 'status' command displays wrong charset but the command "SHOW VARIABLES LIKE "%charset%" displayed correct charset results. The problem is only with the mysql client's 'status' command output. In mysql client, the method mysql_store_lazy_result() returns 0 for success and non-zero for failure. The method com_status() was using this method wrongly. Fixed all such instances according to return value of the method mysql_store_lazy_result(). --- client/mysql.cc | 4 ++-- mysql-test/r/bug47671.result | 13 +++++++++++++ mysql-test/t/bug47671-master.opt | 1 + mysql-test/t/bug47671.test | 6 ++++++ 4 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 mysql-test/r/bug47671.result create mode 100644 mysql-test/t/bug47671-master.opt create mode 100644 mysql-test/t/bug47671.test diff --git a/client/mysql.cc b/client/mysql.cc index b76a3d624ab..44bd362fc56 100644 --- a/client/mysql.cc +++ b/client/mysql.cc @@ -4335,7 +4335,7 @@ com_status(String *buffer __attribute__((unused)), Don't remove "limit 1", it is protection againts SQL_SELECT_LIMIT=0 */ - if (mysql_store_result_for_lazy(&result)) + if (!mysql_store_result_for_lazy(&result)) { MYSQL_ROW cur=mysql_fetch_row(result); if (cur) @@ -4379,7 +4379,7 @@ com_status(String *buffer __attribute__((unused)), if (mysql_errno(&mysql) == CR_SERVER_GONE_ERROR) return 0; } - if (mysql_store_result_for_lazy(&result)) + if (!mysql_store_result_for_lazy(&result)) { MYSQL_ROW cur=mysql_fetch_row(result); if (cur) diff --git a/mysql-test/r/bug47671.result b/mysql-test/r/bug47671.result new file mode 100644 index 00000000000..2cff6f1b59c --- /dev/null +++ b/mysql-test/r/bug47671.result @@ -0,0 +1,13 @@ +# +# Bug#47671 - wrong character-set after upgrade from 5.1.34 to 5.1.39 +# +# Extract only charset information from 'status' command output using regex +-------------- + +Server characterset: utf8 +Db characterset: utf8 +Client characterset: utf8 +Conn. characterset: utf8 + +-------------- + diff --git a/mysql-test/t/bug47671-master.opt b/mysql-test/t/bug47671-master.opt new file mode 100644 index 00000000000..0afdf49e022 --- /dev/null +++ b/mysql-test/t/bug47671-master.opt @@ -0,0 +1 @@ +--default-character-set=utf8 --skip-character-set-client-handshake diff --git a/mysql-test/t/bug47671.test b/mysql-test/t/bug47671.test new file mode 100644 index 00000000000..3efff39ff58 --- /dev/null +++ b/mysql-test/t/bug47671.test @@ -0,0 +1,6 @@ +--echo # +--echo # Bug#47671 - wrong character-set after upgrade from 5.1.34 to 5.1.39 +--echo # +--echo # Extract only charset information from 'status' command output using regex +--replace_regex /.*mysql.*// /Connection.*// /Current.*// /SSL.*// /Using.*// /Server version.*// /Protocol.*// /UNIX.*// /Uptime.*// /Threads.*// +--exec $MYSQL -u root test -e "status"; From 0dd5eaa51e6ed0417ce5337100041a7460dd2fa0 Mon Sep 17 00:00:00 2001 From: Satya B Date: Wed, 25 Nov 2009 15:08:52 +0530 Subject: [PATCH 08/11] Applying InnoDB snapshot 5.0-ss6230, Part 1. Fixes BUG#47777 BUG#47777 - innodb dies with spatial pk: Failing assertion: buf <= original_buf + buf_len Detailed revision comments: r6178 | jyang | 2009-11-17 08:52:11 +0200 (Tue, 17 Nov 2009) | 6 lines branches/5.0: Merge fix for bug #47777 from branches/5.1 -r6045 to bracnches/5.0. Treat the Geometry data same as Binary BLOB in ha_innobase::store_key_val_for_row(), since the Geometry data is stored as Binary BLOB in Innodb. --- mysql-test/r/innodb_bug47777.result | 13 +++++++++++++ mysql-test/t/innodb_bug47777.test | 24 ++++++++++++++++++++++++ sql/ha_innodb.cc | 5 ++++- 3 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 mysql-test/r/innodb_bug47777.result create mode 100644 mysql-test/t/innodb_bug47777.test diff --git a/mysql-test/r/innodb_bug47777.result b/mysql-test/r/innodb_bug47777.result new file mode 100644 index 00000000000..fbba47edcfc --- /dev/null +++ b/mysql-test/r/innodb_bug47777.result @@ -0,0 +1,13 @@ +create table bug47777(c2 linestring not null, primary key (c2(1))) engine=innodb; +insert into bug47777 values (geomfromtext('linestring(1 2,3 4,5 6,7 8,9 10)')); +select count(*) from bug47777 where c2 =geomfromtext('linestring(1 2,3 4,5 6,7 8,9 10)'); +count(*) +1 +update bug47777 set c2=GeomFromText('POINT(1 1)'); +select count(*) from bug47777 where c2 =geomfromtext('linestring(1 2,3 4,5 6,7 8,9 10)'); +count(*) +0 +select count(*) from bug47777 where c2 = GeomFromText('POINT(1 1)'); +count(*) +1 +drop table bug47777; diff --git a/mysql-test/t/innodb_bug47777.test b/mysql-test/t/innodb_bug47777.test new file mode 100644 index 00000000000..8f2985b2cf0 --- /dev/null +++ b/mysql-test/t/innodb_bug47777.test @@ -0,0 +1,24 @@ +# This is the test for bug 47777. GEOMETRY +# data is treated as BLOB data in innodb. +# Consequently, its key value generation/storing +# should follow the process for the BLOB +# datatype as well. + +--source include/have_innodb.inc + +create table bug47777(c2 linestring not null, primary key (c2(1))) engine=innodb; + +insert into bug47777 values (geomfromtext('linestring(1 2,3 4,5 6,7 8,9 10)')); + +# Verify correct row get inserted. +select count(*) from bug47777 where c2 =geomfromtext('linestring(1 2,3 4,5 6,7 8,9 10)'); + +# Update table bug47777 should be successful. +update bug47777 set c2=GeomFromText('POINT(1 1)'); + +# Verify the row get updated successfully. The original +# c2 value should be changed to GeomFromText('POINT(1 1)'). +select count(*) from bug47777 where c2 =geomfromtext('linestring(1 2,3 4,5 6,7 8,9 10)'); +select count(*) from bug47777 where c2 = GeomFromText('POINT(1 1)'); + +drop table bug47777; diff --git a/sql/ha_innodb.cc b/sql/ha_innodb.cc index 4bd54805a95..29d416c666c 100644 --- a/sql/ha_innodb.cc +++ b/sql/ha_innodb.cc @@ -2767,7 +2767,10 @@ ha_innobase::store_key_val_for_row( } else if (mysql_type == FIELD_TYPE_TINY_BLOB || mysql_type == FIELD_TYPE_MEDIUM_BLOB || mysql_type == FIELD_TYPE_BLOB - || mysql_type == FIELD_TYPE_LONG_BLOB) { + || mysql_type == FIELD_TYPE_LONG_BLOB + /* MYSQL_TYPE_GEOMETRY data is treated + as BLOB data in innodb. */ + || mysql_type == FIELD_TYPE_GEOMETRY) { CHARSET_INFO* cs; ulint key_len; From 28b54d144d937fb1adcc4313285e7c3eb438ff38 Mon Sep 17 00:00:00 2001 From: Evgeny Potemkin Date: Wed, 25 Nov 2009 12:52:03 +0300 Subject: [PATCH 09/11] Additional fix for the bug#43668. Arg_comparator now takes into account that some of optimizations aren't done in the PREPARE stmt. --- sql/item_cmpfunc.cc | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/sql/item_cmpfunc.cc b/sql/item_cmpfunc.cc index 9662c0209c8..fd5eca8911a 100644 --- a/sql/item_cmpfunc.cc +++ b/sql/item_cmpfunc.cc @@ -960,12 +960,23 @@ int Arg_comparator::set_cmp_func(Item_result_field *owner_arg, (*b)->field_type() == MYSQL_TYPE_YEAR)) { is_nulls_eq= is_owner_equal_func(); + year_as_datetime= FALSE; + if ((*a)->is_datetime()) { year_as_datetime= TRUE; get_value_a_func= &get_datetime_value; } else if ((*a)->field_type() == MYSQL_TYPE_YEAR) get_value_a_func= &get_year_value; + else + { + /* + Because convert_constant_item is called only for EXECUTE in PS mode + the value of get_value_x_func set in PREPARE might be not + valid for EXECUTE. + */ + get_value_a_func= NULL; + } if ((*b)->is_datetime()) { @@ -973,6 +984,8 @@ int Arg_comparator::set_cmp_func(Item_result_field *owner_arg, get_value_b_func= &get_datetime_value; } else if ((*b)->field_type() == MYSQL_TYPE_YEAR) get_value_b_func= &get_year_value; + else + get_value_b_func= NULL; func= &Arg_comparator::compare_year; return 0; From e2afa05e2a0d20fdf32721cf084d27de68f79587 Mon Sep 17 00:00:00 2001 From: Satya B Date: Wed, 25 Nov 2009 15:29:25 +0530 Subject: [PATCH 10/11] Applying InnoDB snapshot 5.0-ss6230, part 2. Fixes BUG#46000 BUG#46000 - using index called GEN_CLUST_INDEX crashes server Detailed revision comments: r6180 | jyang | 2009-11-17 10:54:57 +0200 (Tue, 17 Nov 2009) | 7 lines branches/5.0: Merge/Port fix for bug #46000 from branches/5.1 -r5895 to branches/5.0. Disallow creating index with the name of "GEN_CLUST_INDEX" which is reserved for the default system primary index. Minor adjusts on table name screening format for added tests. --- mysql-test/r/innodb_bug46000.result | 13 ++++ mysql-test/t/innodb_bug46000.test | 34 +++++++++++ sql/ha_innodb.cc | 94 ++++++++++++++++++++++++----- 3 files changed, 127 insertions(+), 14 deletions(-) create mode 100644 mysql-test/r/innodb_bug46000.result create mode 100644 mysql-test/t/innodb_bug46000.test diff --git a/mysql-test/r/innodb_bug46000.result b/mysql-test/r/innodb_bug46000.result new file mode 100644 index 00000000000..8a92fa973c0 --- /dev/null +++ b/mysql-test/r/innodb_bug46000.result @@ -0,0 +1,13 @@ +create table bug46000(`id` int,key `GEN_CLUST_INDEX`(`id`))engine=innodb; +ERROR HY000: Can't create table bug46000.frm' (errno: -1) +show warnings; +Level Code Message +Warning 1005 Cannot Create Index with name 'GEN_CLUST_INDEX'. The name is reserved for the system default primary index. +Error 1005 Can't create table bug46000.frm' (errno: -1) +create table bug46000(`id` int, key `GEN_clust_INDEX`(`id`))engine=innodb; +ERROR HY000: Can't create table bug46000.frm' (errno: -1) +create table bug46000(id int) engine=innodb; +create index GEN_CLUST_INDEX on bug46000(id); +ERROR HY000: Can't create table #sql-temporary' (errno: -1) +create index idx on bug46000(id); +drop table bug46000; diff --git a/mysql-test/t/innodb_bug46000.test b/mysql-test/t/innodb_bug46000.test new file mode 100644 index 00000000000..d8b4ed6a638 --- /dev/null +++ b/mysql-test/t/innodb_bug46000.test @@ -0,0 +1,34 @@ +# This is the test for bug 46000. We shall +# block any index creation with the name of +# "GEN_CLUST_INDEX", which is the reserved +# name for innodb default primary index. + +--source include/have_innodb.inc + +# This 'create table' operation should fail because of +# using the reserve name as its index name. +--replace_regex /'[^']*test\/// +--error ER_CANT_CREATE_TABLE +create table bug46000(`id` int,key `GEN_CLUST_INDEX`(`id`))engine=innodb; + +--replace_regex /'[^']*test\/// +show warnings; + +# Mixed upper/lower case of the reserved key words +--replace_regex /'[^']*test\/// +--error ER_CANT_CREATE_TABLE +create table bug46000(`id` int, key `GEN_clust_INDEX`(`id`))engine=innodb; + +create table bug46000(id int) engine=innodb; + +# This 'create index' operation should fail. +--replace_regex /'[^']*test\/#sql-[0-9a-f_]*.frm/#sql-temporary/ +--error ER_CANT_CREATE_TABLE +create index GEN_CLUST_INDEX on bug46000(id); + +# This 'create index' operation should succeed, no +# temp table left from last failed create index +# operation. +create index idx on bug46000(id); + +drop table bug46000; diff --git a/sql/ha_innodb.cc b/sql/ha_innodb.cc index 29d416c666c..6586bf818c1 100644 --- a/sql/ha_innodb.cc +++ b/sql/ha_innodb.cc @@ -226,6 +226,24 @@ handlerton innobase_hton = { innobase_close_cursor_view, HTON_NO_FLAGS }; +/*********************************************************************** +This function checks each index name for a table against reserved +system default primary index name 'GEN_CLUST_INDEX'. If a name matches, +this function pushes an error message to the client, and returns true. */ +static +bool +innobase_index_name_is_reserved( +/*============================*/ + /* out: true if index name matches a + reserved name */ + const trx_t* trx, /* in: InnoDB transaction handle */ + const TABLE* form, /* in: information on table + columns and indexes */ + const char* norm_name); /* in: table name */ + +/* "GEN_CLUST_INDEX" is the name reserved for Innodb default +system primary index. */ +static const char innobase_index_reserve_name[]= "GEN_CLUST_INDEX"; /********************************************************************* Commits a transaction in an InnoDB database. */ @@ -4496,7 +4514,10 @@ create_index( n_fields = key->key_parts; - ind_type = 0; + /* Assert that "GEN_CLUST_INDEX" cannot be used as non-primary index */ + ut_a(innobase_strcasecmp(key->name, innobase_index_reserve_name) != 0); + + ind_type = 0; if (key_num == form->s->primary_key) { ind_type = ind_type | DICT_CLUSTERED; @@ -4606,9 +4627,8 @@ create_clustered_index_when_no_primary( /* We pass 0 as the space id, and determine at a lower level the space id where to store the table */ - - index = dict_mem_index_create((char*) table_name, - (char*) "GEN_CLUST_INDEX", + index = dict_mem_index_create(table_name, + innobase_index_reserve_name, 0, DICT_CLUSTERED, 0); error = row_create_index_for_mysql(index, trx, NULL); @@ -4706,16 +4726,6 @@ ha_innobase::create( row_mysql_lock_data_dictionary(trx); - /* Create the table definition in InnoDB */ - - error = create_table_def(trx, form, norm_name, - create_info->options & HA_LEX_CREATE_TMP_TABLE ? name2 : NULL, - form->s->row_type != ROW_TYPE_REDUNDANT); - - if (error) { - goto cleanup; - } - /* Look for a primary key */ primary_key_no= (table->s->primary_key != MAX_KEY ? @@ -4727,6 +4737,23 @@ ha_innobase::create( DBUG_ASSERT(primary_key_no == -1 || primary_key_no == 0); + /* Check for name conflicts (with reserved name) for + any user indices to be created. */ + if (innobase_index_name_is_reserved(trx, form, norm_name)) { + error = -1; + goto cleanup; + } + + /* Create the table definition in InnoDB */ + error = create_table_def(trx, form, norm_name, + create_info->options & HA_LEX_CREATE_TMP_TABLE ? name2 : NULL, + form->s->row_type != ROW_TYPE_REDUNDANT); + + if (error) { + goto cleanup; + } + + /* Create the keys */ if (form->s->keys == 0 || primary_key_no == -1) { @@ -7431,4 +7458,43 @@ innobase_set_cursor_view( (cursor_view_t*) curview); } +/*********************************************************************** +This function checks each index name for a table against reserved +system default primary index name 'GEN_CLUST_INDEX'. If a name matches, +this function pushes an error message to the client, and returns true. */ +static +bool +innobase_index_name_is_reserved( +/*============================*/ + /* out: true if an index name + matches the reserved name */ + const trx_t* trx, /* in: InnoDB transaction handle */ + const TABLE* form, /* in: information on table + columns and indexes */ + const char* norm_name) /* in: table name */ +{ + KEY* key; + uint key_num; /* index number */ + + for (key_num = 0; key_num < form->s->keys; key_num++) { + key = form->key_info + key_num; + + if (innobase_strcasecmp(key->name, + innobase_index_reserve_name) == 0) { + /* Push warning to mysql */ + push_warning_printf((THD*) trx->mysql_thd, + MYSQL_ERROR::WARN_LEVEL_WARN, + ER_CANT_CREATE_TABLE, + "Cannot Create Index with name " + "'%s'. The name is reserved " + "for the system default primary " + "index.", + innobase_index_reserve_name); + + return(true); + } + } + + return(false); +} #endif /* HAVE_INNOBASE_DB */ From 185e24d592e812db2191ddb2a48a4c98f45f0337 Mon Sep 17 00:00:00 2001 From: Martin Hansson Date: Wed, 25 Nov 2009 11:02:25 +0100 Subject: [PATCH 11/11] Bug#48459: valgrind errors with query using 'Range checked for each record' There was an error in an internal structure in the range optimizer (SEL_ARG). Bad design causes parts of a data structure not to be initialized when it is in a certain state. All client code must check that this state is not present before trying to access the structure's data. Fixed by - Checking the state before trying to access data (in several places, most of which not covered by test case.) - Copying the keypart id when cloning SEL_ARGs --- mysql-test/r/range.result | 33 ++++++++++++++++++++++++ mysql-test/t/range.test | 34 ++++++++++++++++++++++++- sql/opt_range.cc | 53 +++++++++++++++++++-------------------- 3 files changed, 92 insertions(+), 28 deletions(-) diff --git a/mysql-test/r/range.result b/mysql-test/r/range.result index ab40eefdc82..64e00521cd2 100644 --- a/mysql-test/r/range.result +++ b/mysql-test/r/range.result @@ -1604,6 +1604,39 @@ str_to_date('', '%Y-%m-%d') 0000-00-00 DROP TABLE t1, t2; # +# Bug#48459: valgrind errors with query using 'Range checked for each +# record' +# +CREATE TABLE t1 ( +a INT, +b CHAR(2), +c INT, +d INT, +KEY ( c ), +KEY ( d, a, b ( 2 ) ), +KEY ( b ( 1 ) ) +); +INSERT INTO t1 VALUES ( NULL, 'a', 1, 2 ), ( NULL, 'a', 1, 2 ), +( 1, 'a', 1, 2 ), ( 1, 'a', 1, 2 ); +CREATE TABLE t2 ( +a INT, +c INT, +e INT, +KEY ( e ) +); +INSERT INTO t2 VALUES ( 1, 1, NULL ), ( 1, 1, NULL ); +# Should not give Valgrind warnings +SELECT 1 +FROM t1, t2 +WHERE t1.d <> '1' AND t1.b > '1' +AND t1.a = t2.a AND t1.c = t2.c; +1 +1 +1 +1 +1 +DROP TABLE t1, t2; +# # Bug #48665: sql-bench's insert test fails due to wrong result # CREATE TABLE t1 (a INT, b INT, PRIMARY KEY (a)); diff --git a/mysql-test/t/range.test b/mysql-test/t/range.test index 736d65792c5..5d5ad180f1a 100644 --- a/mysql-test/t/range.test +++ b/mysql-test/t/range.test @@ -1260,6 +1260,39 @@ SELECT str_to_date('', '%Y-%m-%d'); DROP TABLE t1, t2; +--echo # +--echo # Bug#48459: valgrind errors with query using 'Range checked for each +--echo # record' +--echo # +CREATE TABLE t1 ( + a INT, + b CHAR(2), + c INT, + d INT, + KEY ( c ), + KEY ( d, a, b ( 2 ) ), + KEY ( b ( 1 ) ) +); + +INSERT INTO t1 VALUES ( NULL, 'a', 1, 2 ), ( NULL, 'a', 1, 2 ), + ( 1, 'a', 1, 2 ), ( 1, 'a', 1, 2 ); + +CREATE TABLE t2 ( + a INT, + c INT, + e INT, + KEY ( e ) +); + +INSERT INTO t2 VALUES ( 1, 1, NULL ), ( 1, 1, NULL ); + +--echo # Should not give Valgrind warnings +SELECT 1 +FROM t1, t2 +WHERE t1.d <> '1' AND t1.b > '1' +AND t1.a = t2.a AND t1.c = t2.c; + +DROP TABLE t1, t2; --echo # --echo # Bug #48665: sql-bench's insert test fails due to wrong result @@ -1280,5 +1313,4 @@ SELECT * FROM t1 FORCE INDEX (PRIMARY) DROP TABLE t1; - --echo End of 5.1 tests diff --git a/sql/opt_range.cc b/sql/opt_range.cc index 9d8d3bd43d9..94204962345 100644 --- a/sql/opt_range.cc +++ b/sql/opt_range.cc @@ -446,9 +446,9 @@ public: range_key, *range_key_flag); *range_key_flag|= key_tree->min_flag; if (key_tree->next_key_part && + key_tree->next_key_part->type == SEL_ARG::KEY_RANGE && key_tree->next_key_part->part == key_tree->part+1 && - !(*range_key_flag & (NO_MIN_RANGE | NEAR_MIN)) && - key_tree->next_key_part->type == SEL_ARG::KEY_RANGE) + !(*range_key_flag & (NO_MIN_RANGE | NEAR_MIN))) res+= key_tree->next_key_part->store_min_key(key, range_key, range_key_flag); return res; @@ -462,9 +462,9 @@ public: range_key, *range_key_flag); (*range_key_flag)|= key_tree->max_flag; if (key_tree->next_key_part && + key_tree->next_key_part->type == SEL_ARG::KEY_RANGE && key_tree->next_key_part->part == key_tree->part+1 && - !(*range_key_flag & (NO_MAX_RANGE | NEAR_MAX)) && - key_tree->next_key_part->type == SEL_ARG::KEY_RANGE) + !(*range_key_flag & (NO_MAX_RANGE | NEAR_MAX))) res+= key_tree->next_key_part->store_max_key(key, range_key, range_key_flag); return res; @@ -1700,6 +1700,7 @@ SEL_ARG *SEL_ARG::clone(RANGE_OPT_PARAM *param, SEL_ARG *new_parent, tmp->prev= *next_arg; // Link into next/prev chain (*next_arg)->next=tmp; (*next_arg)= tmp; + tmp->part= this->part; } else { @@ -7290,27 +7291,25 @@ int test_rb_tree(SEL_ARG *element,SEL_ARG *parent) } -/* - Count how many times SEL_ARG graph "root" refers to its part "key" +/** + Count how many times SEL_ARG graph "root" refers to its part "key" via + transitive closure. - SYNOPSIS - count_key_part_usage() - root An RB-Root node in a SEL_ARG graph. - key Another RB-Root node in that SEL_ARG graph. + @param root An RB-Root node in a SEL_ARG graph. + @param key Another RB-Root node in that SEL_ARG graph. - DESCRIPTION - The passed "root" node may refer to "key" node via root->next_key_part, - root->next->n + The passed "root" node may refer to "key" node via root->next_key_part, + root->next->n - This function counts how many times the node "key" is referred (via - SEL_ARG::next_key_part) by - - intervals of RB-tree pointed by "root", - - intervals of RB-trees that are pointed by SEL_ARG::next_key_part from - intervals of RB-tree pointed by "root", - - and so on. + This function counts how many times the node "key" is referred (via + SEL_ARG::next_key_part) by + - intervals of RB-tree pointed by "root", + - intervals of RB-trees that are pointed by SEL_ARG::next_key_part from + intervals of RB-tree pointed by "root", + - and so on. - Here is an example (horizontal links represent next_key_part pointers, - vertical links - next/prev prev pointers): + Here is an example (horizontal links represent next_key_part pointers, + vertical links - next/prev prev pointers): +----+ $ |root|-----------------+ @@ -7330,8 +7329,8 @@ int test_rb_tree(SEL_ARG *element,SEL_ARG *parent) ... +---+ $ | | |------------+ +---+ $ - RETURN - Number of links to "key" from nodes reachable from "root". + @return + Number of links to "key" from nodes reachable from "root". */ static ulong count_key_part_usage(SEL_ARG *root, SEL_ARG *key) @@ -7586,8 +7585,8 @@ check_quick_keys(PARAM *param, uint idx, SEL_ARG *key_tree, param->first_null_comp= key_tree->part+1; if (key_tree->next_key_part && - key_tree->next_key_part->part == key_tree->part+1 && - key_tree->next_key_part->type == SEL_ARG::KEY_RANGE) + key_tree->next_key_part->type == SEL_ARG::KEY_RANGE && + key_tree->next_key_part->part == key_tree->part+1) { // const key as prefix if (min_key_length == max_key_length && !memcmp(min_key, max_key, (uint) (tmp_max_key - max_key)) && @@ -7868,8 +7867,8 @@ get_quick_keys(PARAM *param,QUICK_RANGE_SELECT *quick,KEY_PART *key, &tmp_max_key,max_key_flag); if (key_tree->next_key_part && - key_tree->next_key_part->part == key_tree->part+1 && - key_tree->next_key_part->type == SEL_ARG::KEY_RANGE) + key_tree->next_key_part->type == SEL_ARG::KEY_RANGE && + key_tree->next_key_part->part == key_tree->part+1) { // const key as prefix if ((tmp_min_key - min_key) == (tmp_max_key - max_key) && memcmp(min_key, max_key, (uint)(tmp_max_key - max_key))==0 &&