summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDmitry Shulga <Dmitry.Shulga@oracle.com>2011-08-13 13:34:00 +0700
committerDmitry Shulga <Dmitry.Shulga@oracle.com>2011-08-13 13:34:00 +0700
commit28bed7d92bcc6e0b45fd657a32addcf1fb60abc3 (patch)
treefe01e7d32375d087c27c01c181f1087a25256111
parent1e51c1c841f87970cb2d7158ed022049d6c1e5d2 (diff)
downloadmariadb-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.result150
-rw-r--r--mysql-test/t/sp.test156
-rw-r--r--sql/item.h2
-rw-r--r--sql/share/errmsg-utf8.txt2
-rw-r--r--sql/sp_head.cc6
-rw-r--r--sql/sql_lex.cc59
-rw-r--r--sql/sql_yacc.yy3
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;
}