diff options
author | Dmitry Shulga <Dmitry.Shulga@oracle.com> | 2011-08-13 13:34:00 +0700 |
---|---|---|
committer | Dmitry Shulga <Dmitry.Shulga@oracle.com> | 2011-08-13 13:34:00 +0700 |
commit | 28bed7d92bcc6e0b45fd657a32addcf1fb60abc3 (patch) | |
tree | fe01e7d32375d087c27c01c181f1087a25256111 | |
parent | 1e51c1c841f87970cb2d7158ed022049d6c1e5d2 (diff) | |
download | mariadb-git-28bed7d92bcc6e0b45fd657a32addcf1fb60abc3.tar.gz |
Fixed Bug#12621017 - CRASH IF A SP VARIABLE IS USED IN THE LIMIT CLAUSE OF A
SET STATEMENT.
Server built with debug asserts, without debug crashes if a user tries
to run a stored procedure that constains query with subquery that include
either LIMIT or LIMIT OFFSET clauses.
The problem was that Item::fix_fields() was not called for the items
representing LIMIT or OFFSET clauses.
The solution is to call Item::fix_fields() right before evaluation in
st_select_lex_unit::set_limit().
-rw-r--r-- | mysql-test/r/sp.result | 150 | ||||
-rw-r--r-- | mysql-test/t/sp.test | 156 | ||||
-rw-r--r-- | sql/item.h | 2 | ||||
-rw-r--r-- | sql/share/errmsg-utf8.txt | 2 | ||||
-rw-r--r-- | sql/sp_head.cc | 6 | ||||
-rw-r--r-- | sql/sql_lex.cc | 59 | ||||
-rw-r--r-- | sql/sql_yacc.yy | 3 |
7 files changed, 364 insertions, 14 deletions
diff --git a/mysql-test/r/sp.result b/mysql-test/r/sp.result index 104ddd3353b..1644c764431 100644 --- a/mysql-test/r/sp.result +++ b/mysql-test/r/sp.result @@ -7437,17 +7437,17 @@ ERROR 42000: Undeclared variable: a # Try to use data types not allowed in LIMIT # create procedure p1(p1 date, p2 date) select * from t1 limit p1, p2; -ERROR HY000: A variable of a non-integer type in LIMIT clause +ERROR HY000: A variable of a non-integer based type in LIMIT clause create procedure p1(p1 integer, p2 float) select * from t1 limit p1, p2; -ERROR HY000: A variable of a non-integer type in LIMIT clause +ERROR HY000: A variable of a non-integer based type in LIMIT clause create procedure p1(p1 integer, p2 char(1)) select * from t1 limit p1, p2; -ERROR HY000: A variable of a non-integer type in LIMIT clause +ERROR HY000: A variable of a non-integer based type in LIMIT clause create procedure p1(p1 varchar(5), p2 char(1)) select * from t1 limit p1, p2; -ERROR HY000: A variable of a non-integer type in LIMIT clause +ERROR HY000: A variable of a non-integer based type in LIMIT clause create procedure p1(p1 decimal, p2 decimal) select * from t1 limit p1, p2; -ERROR HY000: A variable of a non-integer type in LIMIT clause +ERROR HY000: A variable of a non-integer based type in LIMIT clause create procedure p1(p1 double, p2 double) select * from t1 limit p1, p2; -ERROR HY000: A variable of a non-integer type in LIMIT clause +ERROR HY000: A variable of a non-integer based type in LIMIT clause # # Finally, test the valid case. # @@ -7483,9 +7483,117 @@ call p1(3, 2); c1 4 5 +# Try to create a function that +# refers to non-existing variables. +create function f1(p1 integer, p2 integer) +returns int +begin +declare a int; +set a = (select count(*) from t1 limit a, b); +return a; +end| +ERROR 42000: Undeclared variable: b +create function f1() +returns int +begin +declare a, b, c int; +set a = (select count(*) from t1 limit b, c); +return a; +end| +# How do we handle NULL limit values? +select f1(); +f1() +NULL +drop function f1; +# +# Try to use data types not allowed in LIMIT +# +create function f1(p1 date, p2 date) +returns int +begin +declare a int; +set a = (select count(*) from t1 limit p1, p2); +return a; +end| +ERROR HY000: A variable of a non-integer based type in LIMIT clause +create function f1(p1 integer, p2 float) +returns int +begin +declare a int; +set a = (select count(*) from t1 limit p1, p2); +return a; +end| +ERROR HY000: A variable of a non-integer based type in LIMIT clause +create function f1(p1 integer, p2 char(1)) +returns int +begin +declare a int; +set a = (select count(*) from t1 limit p1, p2); +return a; +end| +ERROR HY000: A variable of a non-integer based type in LIMIT clause +create function f1(p1 varchar(5), p2 char(1)) +returns int +begin +declare a int; +set a = (select count(*) from t1 limit p1, p2); +return a; +end| +ERROR HY000: A variable of a non-integer based type in LIMIT clause +create function f1(p1 decimal, p2 decimal) +returns int +begin +declare a int; +set a = (select count(*) from t1 limit p1, p2); +return a; +end| +ERROR HY000: A variable of a non-integer based type in LIMIT clause +create function f1(p1 double, p2 double) +returns int +begin +declare a int; +set a = (select count(*) from t1 limit p1, p2); +return a; +end| +ERROR HY000: A variable of a non-integer based type in LIMIT clause +# +# Finally, test the valid case. +# +create function f1(p1 integer, p2 integer) +returns int +begin +declare count int; +set count= (select count(*) from (select * from t1 limit p1, p2) t_1); +return count; +end| +select f1(0, 0); +f1(0, 0) +0 +select f1(0, -1); +f1(0, -1) +5 +select f1(-1, 0); +f1(-1, 0) +0 +select f1(-1, -1); +f1(-1, -1) +0 +select f1(0, 1); +f1(0, 1) +1 +select f1(1, 0); +f1(1, 0) +0 +select f1(1, 5); +f1(1, 5) +4 +select f1(3, 2); +f1(3, 2) +2 # Cleanup drop table t1; drop procedure p1; +drop function f1; # # BUG#11766234: 59299: ASSERT (TABLE_REF->TABLE || TABLE_REF->VIEW) # FAILS IN SET_FIELD_ITERATOR @@ -7606,4 +7714,34 @@ b DROP TABLE t1; DROP PROCEDURE p1; +# +# Bug#12621017 - Crash if a sp variable is used in the +# limit clause of a set statement +# +DROP TABLE IF EXISTS t1; +DROP PROCEDURE IF EXISTS p1; +DROP PROCEDURE IF EXISTS p2; +CREATE TABLE t1 (c1 INT); +INSERT INTO t1 VALUES (1); +CREATE PROCEDURE p1() +BEGIN +DECLARE foo, cnt INT UNSIGNED DEFAULT 1; +SET foo = (SELECT MIN(c1) FROM t1 LIMIT cnt); +END| +CREATE PROCEDURE p2() +BEGIN +DECLARE iLimit INT; +DECLARE iVal INT; +DECLARE cur1 CURSOR FOR +SELECT c1 FROM t1 +LIMIT iLimit; +SET iLimit=1; +OPEN cur1; +FETCH cur1 INTO iVal; +END| +CALL p1(); +CALL p2(); +DROP PROCEDURE p1; +DROP PROCEDURE p2; +DROP TABLE t1; # End of 5.5 test diff --git a/mysql-test/t/sp.test b/mysql-test/t/sp.test index 07b8c065be4..3f6c50a9095 100644 --- a/mysql-test/t/sp.test +++ b/mysql-test/t/sp.test @@ -8755,11 +8755,117 @@ call p1(1, 0); call p1(1, 5); call p1(3, 2); +delimiter |; +--echo # Try to create a function that +--echo # refers to non-existing variables. +--error ER_SP_UNDECLARED_VAR +create function f1(p1 integer, p2 integer) + returns int +begin + declare a int; + set a = (select count(*) from t1 limit a, b); + return a; +end| + +create function f1() + returns int +begin + declare a, b, c int; + set a = (select count(*) from t1 limit b, c); + return a; +end| + +delimiter ;| +--echo # How do we handle NULL limit values? +select f1(); + +drop function f1; + +delimiter |; +--echo # +--echo # Try to use data types not allowed in LIMIT +--echo # +--error ER_WRONG_SPVAR_TYPE_IN_LIMIT +create function f1(p1 date, p2 date) + returns int +begin + declare a int; + set a = (select count(*) from t1 limit p1, p2); + return a; +end| + +--error ER_WRONG_SPVAR_TYPE_IN_LIMIT +create function f1(p1 integer, p2 float) + returns int +begin + declare a int; + set a = (select count(*) from t1 limit p1, p2); + return a; +end| + +--error ER_WRONG_SPVAR_TYPE_IN_LIMIT +create function f1(p1 integer, p2 char(1)) + returns int +begin + declare a int; + set a = (select count(*) from t1 limit p1, p2); + return a; +end| + +--error ER_WRONG_SPVAR_TYPE_IN_LIMIT +create function f1(p1 varchar(5), p2 char(1)) + returns int +begin + declare a int; + set a = (select count(*) from t1 limit p1, p2); + return a; +end| + +--error ER_WRONG_SPVAR_TYPE_IN_LIMIT +create function f1(p1 decimal, p2 decimal) + returns int +begin + declare a int; + set a = (select count(*) from t1 limit p1, p2); + return a; +end| + +--error ER_WRONG_SPVAR_TYPE_IN_LIMIT +create function f1(p1 double, p2 double) + returns int +begin + declare a int; + set a = (select count(*) from t1 limit p1, p2); + return a; +end| + +--echo # +--echo # Finally, test the valid case. +--echo # + +create function f1(p1 integer, p2 integer) +returns int +begin + declare count int; + set count= (select count(*) from (select * from t1 limit p1, p2) t_1); + return count; +end| + +delimiter ;| + +select f1(0, 0); +select f1(0, -1); +select f1(-1, 0); +select f1(-1, -1); +select f1(0, 1); +select f1(1, 0); +select f1(1, 5); +select f1(3, 2); --echo # Cleanup drop table t1; drop procedure p1; - +drop function f1; --echo # --echo # BUG#11766234: 59299: ASSERT (TABLE_REF->TABLE || TABLE_REF->VIEW) @@ -8881,4 +8987,52 @@ DROP TABLE t1; DROP PROCEDURE p1; --echo +--echo # +--echo # Bug#12621017 - Crash if a sp variable is used in the +--echo # limit clause of a set statement +--echo # + +--disable_warnings +DROP TABLE IF EXISTS t1; +DROP PROCEDURE IF EXISTS p1; +DROP PROCEDURE IF EXISTS p2; +--enable_warnings + +CREATE TABLE t1 (c1 INT); +INSERT INTO t1 VALUES (1); + +delimiter |; + +CREATE PROCEDURE p1() +BEGIN + DECLARE foo, cnt INT UNSIGNED DEFAULT 1; + SET foo = (SELECT MIN(c1) FROM t1 LIMIT cnt); +END| + +CREATE PROCEDURE p2() +BEGIN + +DECLARE iLimit INT; +DECLARE iVal INT; + +DECLARE cur1 CURSOR FOR + SELECT c1 FROM t1 + LIMIT iLimit; + +SET iLimit=1; + +OPEN cur1; +FETCH cur1 INTO iVal; + +END| + +delimiter ;| + +CALL p1(); +CALL p2(); + +DROP PROCEDURE p1; +DROP PROCEDURE p2; +DROP TABLE t1; + --echo # End of 5.5 test diff --git a/sql/item.h b/sql/item.h index 46916346ebe..3fa0f4bb50b 100644 --- a/sql/item.h +++ b/sql/item.h @@ -1371,7 +1371,7 @@ class Item_splocal :public Item_sp_variable, enum_field_types m_field_type; public: /* - Is this variable a parameter in LIMIT clause. + If this variable is a parameter in LIMIT clause. Used only during NAME_CONST substitution, to not append NAME_CONST to the resulting query and thus not break the slave. diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt index b8f46f090ab..fd943c4f4d8 100644 --- a/sql/share/errmsg-utf8.txt +++ b/sql/share/errmsg-utf8.txt @@ -6329,7 +6329,7 @@ ER_DATA_OUT_OF_RANGE 22003 eng "%s value is out of range in '%s'" ER_WRONG_SPVAR_TYPE_IN_LIMIT - eng "A variable of a non-integer type in LIMIT clause" + eng "A variable of a non-integer based type in LIMIT clause" ER_BINLOG_UNSAFE_MULTIPLE_ENGINES_AND_SELF_LOGGING_ENGINE eng "Mixing self-logging and non-self-logging engines in a statement is unsafe." diff --git a/sql/sp_head.cc b/sql/sp_head.cc index ce713504a38..eb0eb8edc60 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -1005,6 +1005,8 @@ subst_spvars(THD *thd, sp_instr *instr, LEX_STRING *query_str) if ((*splocal)->limit_clause_param) { res|= qbuf.append_ulonglong((*splocal)->val_uint()); + if (res) + break; continue; } @@ -1029,8 +1031,8 @@ subst_spvars(THD *thd, sp_instr *instr, LEX_STRING *query_str) thd->query_name_consts++; } - res|= qbuf.append(cur + prev_pos, query_str->length - prev_pos); - if (res) + if (res || + qbuf.append(cur + prev_pos, query_str->length - prev_pos)) DBUG_RETURN(TRUE); /* diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 3e6052d3561..f22f459e93e 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -2599,7 +2599,47 @@ void st_select_lex_unit::set_limit(st_select_lex *sl) ulonglong val; DBUG_ASSERT(! thd->stmt_arena->is_stmt_prepare()); - val= sl->select_limit ? sl->select_limit->val_uint() : HA_POS_ERROR; + if (sl->select_limit) + { + Item *item = sl->select_limit; + /* + fix_fields() has not been called for sl->select_limit. That's due to the + historical reasons -- this item could be only of type Item_int, and + Item_int does not require fix_fields(). Thus, fix_fields() was never + called for sl->select_limit. + + Some time ago, Item_splocal was also allowed for LIMIT / OFFSET clauses. + However, the fix_fields() behavior was not updated, which led to a crash + in some cases. + + There is no single place where to call fix_fields() for LIMIT / OFFSET + items during the fix-fields-phase. Thus, for the sake of readability, + it was decided to do it here, on the evaluation phase (which is a + violation of design, but we chose the lesser of two evils). + + We can call fix_fields() here, because sl->select_limit can be of two + types only: Item_int and Item_splocal. Item_int::fix_fields() is trivial, + and Item_splocal::fix_fields() (or rather Item_sp_variable::fix_fields()) + has the following specific: + 1) it does not affect other items; + 2) it does not fail. + + Nevertheless DBUG_ASSERT was added to catch future changes in + fix_fields() implementation. Also added runtime check against a result + of fix_fields() in order to handle error condition in non-debug build. + */ + bool fix_fields_successful= true; + if (!item->fixed) + { + fix_fields_successful= !item->fix_fields(thd, NULL); + + DBUG_ASSERT(fix_fields_successful); + } + val= fix_fields_successful ? item->val_uint() : HA_POS_ERROR; + } + else + val= HA_POS_ERROR; + select_limit_val= (ha_rows)val; #ifndef BIG_TABLES /* @@ -2609,7 +2649,22 @@ void st_select_lex_unit::set_limit(st_select_lex *sl) if (val != (ulonglong)select_limit_val) select_limit_val= HA_POS_ERROR; #endif - val= sl->offset_limit ? sl->offset_limit->val_uint() : ULL(0); + if (sl->offset_limit) + { + Item *item = sl->offset_limit; + // see comment for sl->select_limit branch. + bool fix_fields_successful= true; + if (!item->fixed) + { + fix_fields_successful= !item->fix_fields(thd, NULL); + + DBUG_ASSERT(fix_fields_successful); + } + val= fix_fields_successful ? item->val_uint() : HA_POS_ERROR; + } + else + val= ULL(0); + offset_limit_cnt= (ha_rows)val; #ifndef BIG_TABLES /* Check for truncation. */ diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 6fe9626b11d..3d30dbe614f 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -9961,7 +9961,8 @@ limit_option: } splocal->limit_clause_param= TRUE; $$= splocal; - } | param_marker + } + | param_marker { ((Item_param *) $1)->limit_clause_param= TRUE; } |