Commit 644db664 authored by Dmitry Shulga's avatar Dmitry Shulga

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().

mysql-test/r/sp.result:
  Added testcase result for bug#12621017. Updated testcase result for
  bug 11918.
mysql-test/t/sp.test:
  Added testcase for bug#12621017. Addressed review comments for Bug 11918
  (added tests for use LIMIT at stored function).
sql/item.h:
  Addressed review comments for Bug 11918.
sql/share/errmsg-utf8.txt:
  Addressed review comments for Bug 11918.
sql/sp_head.cc:
  Addressed review comments for Bug 11918.
sql/sql_lex.cc:
  Added call fix_fields() for item just before its evaluation.
sql/sql_yacc.yy:
  Addressed review comments for Bug 11918.
parent 232b112e
...@@ -7437,17 +7437,17 @@ ERROR 42000: Undeclared variable: a ...@@ -7437,17 +7437,17 @@ ERROR 42000: Undeclared variable: a
# Try to use data types not allowed in LIMIT # Try to use data types not allowed in LIMIT
# #
create procedure p1(p1 date, p2 date) select * from t1 limit p1, p2; 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; 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; 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; 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; 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; 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. # Finally, test the valid case.
# #
...@@ -7483,9 +7483,117 @@ call p1(3, 2); ...@@ -7483,9 +7483,117 @@ call p1(3, 2);
c1 c1
4 4
5 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 # Cleanup
drop table t1; drop table t1;
drop procedure p1; drop procedure p1;
drop function f1;
# #
# BUG#11766234: 59299: ASSERT (TABLE_REF->TABLE || TABLE_REF->VIEW) # BUG#11766234: 59299: ASSERT (TABLE_REF->TABLE || TABLE_REF->VIEW)
# FAILS IN SET_FIELD_ITERATOR # FAILS IN SET_FIELD_ITERATOR
...@@ -7606,4 +7714,34 @@ b ...@@ -7606,4 +7714,34 @@ b
DROP TABLE t1; DROP TABLE t1;
DROP PROCEDURE p1; 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 # End of 5.5 test
...@@ -8755,11 +8755,117 @@ call p1(1, 0); ...@@ -8755,11 +8755,117 @@ call p1(1, 0);
call p1(1, 5); call p1(1, 5);
call p1(3, 2); 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 --echo # Cleanup
drop table t1; drop table t1;
drop procedure p1; drop procedure p1;
drop function f1;
--echo # --echo #
--echo # BUG#11766234: 59299: ASSERT (TABLE_REF->TABLE || TABLE_REF->VIEW) --echo # BUG#11766234: 59299: ASSERT (TABLE_REF->TABLE || TABLE_REF->VIEW)
...@@ -8881,4 +8987,52 @@ DROP TABLE t1; ...@@ -8881,4 +8987,52 @@ DROP TABLE t1;
DROP PROCEDURE p1; DROP PROCEDURE p1;
--echo --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 --echo # End of 5.5 test
...@@ -1371,7 +1371,7 @@ class Item_splocal :public Item_sp_variable, ...@@ -1371,7 +1371,7 @@ class Item_splocal :public Item_sp_variable,
enum_field_types m_field_type; enum_field_types m_field_type;
public: 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 Used only during NAME_CONST substitution, to not append
NAME_CONST to the resulting query and thus not break NAME_CONST to the resulting query and thus not break
the slave. the slave.
......
...@@ -6329,7 +6329,7 @@ ER_DATA_OUT_OF_RANGE 22003 ...@@ -6329,7 +6329,7 @@ ER_DATA_OUT_OF_RANGE 22003
eng "%s value is out of range in '%s'" eng "%s value is out of range in '%s'"
ER_WRONG_SPVAR_TYPE_IN_LIMIT 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 ER_BINLOG_UNSAFE_MULTIPLE_ENGINES_AND_SELF_LOGGING_ENGINE
eng "Mixing self-logging and non-self-logging engines in a statement is unsafe." eng "Mixing self-logging and non-self-logging engines in a statement is unsafe."
......
...@@ -1005,6 +1005,8 @@ subst_spvars(THD *thd, sp_instr *instr, LEX_STRING *query_str) ...@@ -1005,6 +1005,8 @@ subst_spvars(THD *thd, sp_instr *instr, LEX_STRING *query_str)
if ((*splocal)->limit_clause_param) if ((*splocal)->limit_clause_param)
{ {
res|= qbuf.append_ulonglong((*splocal)->val_uint()); res|= qbuf.append_ulonglong((*splocal)->val_uint());
if (res)
break;
continue; continue;
} }
...@@ -1029,8 +1031,8 @@ subst_spvars(THD *thd, sp_instr *instr, LEX_STRING *query_str) ...@@ -1029,8 +1031,8 @@ subst_spvars(THD *thd, sp_instr *instr, LEX_STRING *query_str)
thd->query_name_consts++; 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); DBUG_RETURN(TRUE);
/* /*
......
...@@ -2599,7 +2599,47 @@ void st_select_lex_unit::set_limit(st_select_lex *sl) ...@@ -2599,7 +2599,47 @@ void st_select_lex_unit::set_limit(st_select_lex *sl)
ulonglong val; ulonglong val;
DBUG_ASSERT(! thd->stmt_arena->is_stmt_prepare()); 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; select_limit_val= (ha_rows)val;
#ifndef BIG_TABLES #ifndef BIG_TABLES
/* /*
...@@ -2609,7 +2649,22 @@ void st_select_lex_unit::set_limit(st_select_lex *sl) ...@@ -2609,7 +2649,22 @@ void st_select_lex_unit::set_limit(st_select_lex *sl)
if (val != (ulonglong)select_limit_val) if (val != (ulonglong)select_limit_val)
select_limit_val= HA_POS_ERROR; select_limit_val= HA_POS_ERROR;
#endif #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; offset_limit_cnt= (ha_rows)val;
#ifndef BIG_TABLES #ifndef BIG_TABLES
/* Check for truncation. */ /* Check for truncation. */
......
...@@ -9961,7 +9961,8 @@ limit_option: ...@@ -9961,7 +9961,8 @@ limit_option:
} }
splocal->limit_clause_param= TRUE; splocal->limit_clause_param= TRUE;
$$= splocal; $$= splocal;
} | param_marker }
| param_marker
{ {
((Item_param *) $1)->limit_clause_param= TRUE; ((Item_param *) $1)->limit_clause_param= TRUE;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment