Commit 813fc410 authored by unknown's avatar unknown

A fix and a test case for Bug#6513 "Test Suite: Values inserted by using

cursor is interpreted latin1 character and Bug#9819 "Cursors: Mysql Server
Crash while fetching from table with 5 million records."
A fix for a possible memory leak when fetching into an SP cursor
in a long loop.
The patch uses a common implementation of cursors in the binary protocol and 
in stored procedures and implements materialized cursors.
For implementation details, see comments in sql_cursor.cc


include/my_sys.h:
  - declaration for multi_alloc_root
libmysqld/Makefile.am:
  - drop protocol_cursor.cc, add sql_cursor.cc (replaces the old
  implementation of cursors with a new one)
mysql-test/r/ctype_ujis.result:
  - test results fixed (a test case for Bug#6513)
mysql-test/r/sp-big.result:
  - test results fixed (a test case for Bug#9819)
mysql-test/t/ctype_ujis.test:
  Add a test case for Bug#6513 "Test Suite: Values inserted by using cursor is
   interpreted latin1 character"
mysql-test/t/sp-big.test:
  Add a restricted test case for Bug#9819 "Cursors: Mysql Server Crash
  while fetching from table with 5 million records."
mysys/my_alloc.c:
  - an implementation of multi_alloc_root; this is largely a copy-paste
    from mulalloc.c, but the function is small and there is no easy way
    to reuse the existing C function.
sql/Makefile.am:
  - add sql_cursor.h, sql_cursor.cc (a new implementation of stored procedure
  cursors) and drop protocol_cursor.cc (the old one)
sql/handler.cc:
  - now TABLE object has its mem_root always initialized.
    Adjust the implementation handler::ha_open
sql/item_subselect.cc:
  - adjust to the changed declaration of st_select_lex_unit::prepare
sql/protocol.h:
  - drop Protocol_cursor
sql/sp_head.cc:
  - move juggling with Query_arena::free_list and Item::next to
    sp_eval_func_item, as this is needed in 3 places already.
sql/sp_head.h:
  - declare a no-op implementation for cleanup_stmt in sp_instr_cpush.
    This method is needed for non-materializing cursors, which are yet not 
    used in stored procedures.
  - declaration for sp_eval_func_item
sql/sp_rcontext.cc:
  - reimplement sp_cursor using the new implementation of server side cursors.
  - use sp_eval_func_item to assign values of SP variables from the
    row fetched from a cursor. This should fix a possible memory leak in 
    the old implementation of sp_cursor::fetch
sql/sp_rcontext.h:
  - reimplement sp_cursor using the new implementation of server side cursors.
sql/sql_class.cc:
  - disable the functionality that closes transient cursors at commit/rollback;
    transient cursors are not used in 5.0, instead we use materialized ones.
    To be enabled in a later version.
sql/sql_class.h:
  - adjust to the rename Cursor -> Server_side_cursor
  - additional declarations of select_union used in materialized cursors
sql/sql_derived.cc:
  - reuse bits of tmp table code in UNION, derived tables, and materialized
    cursors
  - cleanup comments
sql/sql_lex.h:
  - declarations of auxiliary methods used by materialized cursors
  - a cleanup in st_select_lex_unit interface
sql/sql_list.h:
  - add an array operator new[] to class Sql_alloc
sql/sql_prepare.cc:
  - split the tight coupling of cursors and prepared statements to reuse 
    the same implementation in stored procedures
  - cleanups of error processing in Prepared_statement::{prepare,execute}
sql/sql_select.cc:
  - move the implementation of sensitive (non-materializing) cursors to 
    sql_cursor.cc
  - make temporary tables self-contained: the table, its record and fields
    are allocated in TABLE::mem_root. This implementation is not clean
    and resets thd->mem_root several times because of the way create_tmp_table 
    works (many additional things are done inside it).
  - adjust to the changed declaration of st_select_lex_unit::prepare
sql/sql_select.h:
  - move the declaration of sensitive (non-materializing) cursors to 
    sql_cursor.cc
sql/sql_union.cc:
  - move pieces of st_select_unit::prepare to select_union and st_table
    methods to be able to reuse code in the implementation of materialized
    cursors
sql/sql_view.cc:
  - adjust to the changed signature of st_select_lex_unit::prepare
sql/table.cc:
  - implement auxiliary st_table methods for use with temporary tables
sql/table.h:
  - add declarations for auxiliary methods of st_table used to work with 
   temporary tables
tests/mysql_client_test.c:
  - if cursors are materialized, a parallel update of the table used
    in the cursor may go through: update the test.
sql/sql_cursor.cc:
  New BitKeeper file ``sql/sql_cursor.cc'' -- implementation of server side
  cursors
sql/sql_cursor.h:
  New BitKeeper file ``sql/sql_cursor.h'' - declarations for
  server side cursors.
parent aa79e207
......@@ -775,6 +775,7 @@ extern void my_free_lock(byte *ptr,myf flags);
extern void init_alloc_root(MEM_ROOT *mem_root, uint block_size,
uint pre_alloc_size);
extern gptr alloc_root(MEM_ROOT *mem_root,unsigned int Size);
extern gptr multi_alloc_root(MEM_ROOT *mem_root, ...);
extern void free_root(MEM_ROOT *root, myf MyFLAGS);
extern void set_prealloc_root(MEM_ROOT *root, char *ptr);
extern void reset_root_defaults(MEM_ROOT *mem_root, uint block_size,
......
......@@ -60,7 +60,7 @@ sqlsources = derror.cc field.cc field_conv.cc strfunc.cc filesort.cc \
sql_string.cc sql_table.cc sql_test.cc sql_udf.cc \
sql_update.cc sql_yacc.cc table.cc thr_malloc.cc time.cc \
unireg.cc uniques.cc stacktrace.c sql_union.cc hash_filo.cc \
spatial.cc gstream.cc sql_help.cc tztime.cc protocol_cursor.cc \
spatial.cc gstream.cc sql_help.cc tztime.cc sql_cursor.cc \
sp_head.cc sp_pcontext.cc sp.cc sp_cache.cc sp_rcontext.cc \
parse_file.cc sql_view.cc sql_trigger.cc my_decimal.cc \
ha_blackhole.cc
......
......@@ -2271,3 +2271,33 @@ select c1 from t1 where c1 like 'abcde111%' order by c1;
c1
abcde111
drop table t1;
DROP TABLE IF EXISTS t1, t2;
DROP PROCEDURE IF EXISTS sp1;
set names ujis;
set character_set_database = ujis;
set character_set_server = ujis;
CREATE TABLE t1(c1 char(2)) default charset = ujis;
CREATE TABLE t2(c2 char(2)) default charset = ujis;
INSERT INTO t1 VALUES(_ujis 0xA4A2);
CREATE PROCEDURE sp1()
BEGIN
DECLARE a CHAR(1);
DECLARE cur1 CURSOR FOR SELECT c1 FROM t1;
OPEN cur1;
FETCH cur1 INTO a;
INSERT INTO t2 VALUES (a);
CLOSE cur1;
END|
CALL sp1();
SELECT c1,c2 FROM t1,t2;
c1 c2
あ あ
SELECT hex(convert(_latin1 0xA4A2 using ujis)),hex(c2) FROM t1,t2;
hex(convert(_latin1 0xA4A2 using ujis)) hex(c2)
8FA2F0A1F1 A4A2
DROP PROCEDURE sp1;
DROP TABLE t1;
DROP TABLE t2;
set names default;
set character_set_database=default;
set character_set_server=default;
......@@ -13,3 +13,49 @@ select @value;
3
drop procedure test.longprocedure;
drop table t1;
create table t1 (f1 char(100) , f2 mediumint , f3 int , f4 real, f5 numeric);
insert into t1 (f1, f2, f3, f4, f5) values
("This is a test case for for Bug#9819", 1, 2, 3.0, 4.598);
Warnings:
Note 1265 Data truncated for column 'f5' at row 1
create table t2 like t1;
select count(*) from t1;
count(*)
256
select count(*) from t2;
count(*)
0
create procedure p1()
begin
declare done integer default 0;
declare vf1 char(100) ;
declare vf2 mediumint;
declare vf3 int ;
declare vf4 real ;
declare vf5 numeric ;
declare cur1 cursor for select f1,f2,f3,f4,f5 from t1;
declare continue handler for sqlstate '02000' set done = 1;
open cur1;
while done <> 1 do
fetch cur1 into vf1, vf2, vf3, vf4, vf5;
if not done then
insert into t2 values (vf1, vf2, vf3, vf4, vf5);
end if;
end while;
close cur1;
end|
call p1();
select count(*) from t1;
count(*)
256
select count(*) from t2;
count(*)
256
select f1 from t1 limit 1;
f1
This is a test case for for Bug#9819
select f1 from t2 limit 1;
f1
This is a test case for for Bug#9819
drop procedure p1;
drop table t1, t2;
......@@ -1151,3 +1151,45 @@ SET collation_connection='ujis_bin';
-- source include/ctype_innodb_like.inc
# End of 4.1 tests
--disable_warnings
DROP TABLE IF EXISTS t1, t2;
DROP PROCEDURE IF EXISTS sp1;
--enable_warnings
set names ujis;
set character_set_database = ujis;
set character_set_server = ujis;
CREATE TABLE t1(c1 char(2)) default charset = ujis;
CREATE TABLE t2(c2 char(2)) default charset = ujis;
INSERT INTO t1 VALUES(_ujis 0xA4A2);
DELIMITER |;
CREATE PROCEDURE sp1()
BEGIN
DECLARE a CHAR(1);
DECLARE cur1 CURSOR FOR SELECT c1 FROM t1;
OPEN cur1;
FETCH cur1 INTO a;
INSERT INTO t2 VALUES (a);
CLOSE cur1;
END|
DELIMITER ;|
CALL sp1();
#The data in t1 and t2 should be the same but different
SELECT c1,c2 FROM t1,t2;
#Since the result of hex(convert(_latin1 0xA4A2 using ujis))
#equals to hex(c2), it seems that the value which was inserted
#by using cursor is interpreted as latin1 character set
SELECT hex(convert(_latin1 0xA4A2 using ujis)),hex(c2) FROM t1,t2;
DROP PROCEDURE sp1;
DROP TABLE t1;
DROP TABLE t2;
set names default;
set character_set_database=default;
set character_set_server=default;
......@@ -31,3 +31,52 @@ call test.longprocedure(@value); select @value;
drop procedure test.longprocedure;
drop table t1;
#
# Bug #9819 "Cursors: Mysql Server Crash while fetching from table with 5
# million records.":
# To really test the bug, increase the number of loop iterations ($1).
# For 4 millions set $1 to 22.
create table t1 (f1 char(100) , f2 mediumint , f3 int , f4 real, f5 numeric);
insert into t1 (f1, f2, f3, f4, f5) values
("This is a test case for for Bug#9819", 1, 2, 3.0, 4.598);
create table t2 like t1;
let $1=8;
--disable_query_log
--disable_result_log
while ($1)
{
eval insert into t1 select * from t1;
dec $1;
}
--enable_result_log
--enable_query_log
select count(*) from t1;
select count(*) from t2;
delimiter |;
create procedure p1()
begin
declare done integer default 0;
declare vf1 char(100) ;
declare vf2 mediumint;
declare vf3 int ;
declare vf4 real ;
declare vf5 numeric ;
declare cur1 cursor for select f1,f2,f3,f4,f5 from t1;
declare continue handler for sqlstate '02000' set done = 1;
open cur1;
while done <> 1 do
fetch cur1 into vf1, vf2, vf3, vf4, vf5;
if not done then
insert into t2 values (vf1, vf2, vf3, vf4, vf5);
end if;
end while;
close cur1;
end|
delimiter ;|
call p1();
select count(*) from t1;
select count(*) from t2;
select f1 from t1 limit 1;
select f1 from t2 limit 1;
drop procedure p1;
drop table t1, t2;
......@@ -221,6 +221,57 @@ gptr alloc_root(MEM_ROOT *mem_root,unsigned int Size)
#endif
}
/*
Allocate many pointers at the same time.
DESCRIPTION
ptr1, ptr2, etc all point into big allocated memory area.
SYNOPSIS
multi_alloc_root()
root Memory root
ptr1, length1 Multiple arguments terminated by a NULL pointer
ptr2, length2 ...
...
NULL
RETURN VALUE
A pointer to the beginning of the allocated memory block
in case of success or NULL if out of memory.
*/
gptr multi_alloc_root(MEM_ROOT *root, ...)
{
va_list args;
char **ptr, *start, *res;
uint tot_length, length;
DBUG_ENTER("multi_alloc_root");
va_start(args, root);
tot_length= 0;
while ((ptr= va_arg(args, char **)))
{
length= va_arg(args, uint);
tot_length+= ALIGN_SIZE(length);
}
va_end(args);
if (!(start= (char*) alloc_root(root, tot_length)))
DBUG_RETURN(0); /* purecov: inspected */
va_start(args, root);
res= start;
while ((ptr= va_arg(args, char **)))
{
*ptr= res;
length= va_arg(args, uint);
res+= ALIGN_SIZE(length);
}
va_end(args);
DBUG_RETURN((gptr) start);
}
#define TRASH_MEM(X) TRASH(((char*)(X) + ((X)->size-(X)->left)), (X)->left)
/* Mark all data in blocks free for reusage */
......
......@@ -61,7 +61,7 @@ noinst_HEADERS = item.h item_func.h item_sum.h item_cmpfunc.h \
tztime.h my_decimal.h\
sp_head.h sp_pcontext.h sp_rcontext.h sp.h sp_cache.h \
parse_file.h sql_view.h sql_trigger.h \
sql_array.h \
sql_array.h sql_cursor.h \
examples/ha_example.h examples/ha_archive.h \
examples/ha_tina.h ha_blackhole.h \
ha_federated.h
......@@ -94,7 +94,7 @@ mysqld_SOURCES = sql_lex.cc sql_handler.cc \
client.c sql_client.cc mini_client_errors.c pack.c\
stacktrace.c repl_failsafe.h repl_failsafe.cc \
sql_olap.cc sql_view.cc \
gstream.cc spatial.cc sql_help.cc protocol_cursor.cc \
gstream.cc spatial.cc sql_help.cc sql_cursor.cc \
tztime.cc my_time.c my_decimal.cc\
sp_head.cc sp_pcontext.cc sp_rcontext.cc sp.cc \
sp_cache.cc parse_file.cc sql_trigger.cc \
......
......@@ -1339,11 +1339,9 @@ int handler::ha_open(const char *name, int mode, int test_if_locked)
table->db_stat|=HA_READ_ONLY;
(void) extra(HA_EXTRA_NO_READCHECK); // Not needed in SQL
if (!alloc_root_inited(&table->mem_root)) // If temporary table
ref=(byte*) sql_alloc(ALIGN_SIZE(ref_length)*2);
else
ref=(byte*) alloc_root(&table->mem_root, ALIGN_SIZE(ref_length)*2);
if (!ref)
DBUG_ASSERT(alloc_root_inited(&table->mem_root));
if (!(ref= (byte*) alloc_root(&table->mem_root, ALIGN_SIZE(ref_length)*2)))
{
close();
error=HA_ERR_OUT_OF_MEM;
......
......@@ -1468,7 +1468,7 @@ int subselect_single_select_engine::prepare()
int subselect_union_engine::prepare()
{
return unit->prepare(thd, result, SELECT_NO_UNLOCK, "");
return unit->prepare(thd, result, SELECT_NO_UNLOCK);
}
int subselect_uniquesubquery_engine::prepare()
......
......@@ -150,30 +150,6 @@ class Protocol_prep :public Protocol
virtual bool store(Field *field);
};
class Protocol_cursor :public Protocol_simple
{
public:
MEM_ROOT *alloc;
MYSQL_FIELD *fields;
MYSQL_ROWS *data;
MYSQL_ROWS **prev_record;
ulong row_count;
Protocol_cursor() :data(NULL) {}
Protocol_cursor(THD *thd_arg, MEM_ROOT *ini_alloc) :Protocol_simple(thd_arg), alloc(ini_alloc), data(NULL) {}
bool prepare_for_send(List<Item> *item_list)
{
row_count= 0;
fields= NULL;
data= NULL;
prev_record= &data;
return Protocol_simple::prepare_for_send(item_list);
}
bool send_fields(List<Item> *list, uint flags);
bool write();
uint get_field_count() { return field_count; }
};
void send_warning(THD *thd, uint sql_errno, const char *err=0);
void net_printf_error(THD *thd, uint sql_errno, ...);
void net_send_error(THD *thd, uint sql_errno=0, const char *err=0);
......
......@@ -199,11 +199,18 @@ sp_eval_func_item(THD *thd, Item **it_addr, enum enum_field_types type,
Item *it= sp_prepare_func_item(thd, it_addr);
uint rsize;
Query_arena backup_arena;
Item *old_item_next, *old_free_list, **p_free_list;
DBUG_PRINT("info", ("type: %d", type));
if (!it)
{
DBUG_RETURN(NULL);
if (reuse)
{
old_item_next= reuse->next;
p_free_list= use_callers_arena ? &thd->spcont->callers_arena->free_list :
&thd->free_list;
old_free_list= *p_free_list;
}
switch (sp_map_result_type(type)) {
......@@ -312,15 +319,23 @@ sp_eval_func_item(THD *thd, Item **it_addr, enum enum_field_types type,
default:
DBUG_ASSERT(0);
}
it->rsize= rsize;
DBUG_RETURN(it);
goto end;
return_null_item:
CREATE_ON_CALLERS_ARENA(it= new(reuse, &rsize) Item_null(),
use_callers_arena, &backup_arena);
end:
it->rsize= rsize;
if (reuse && it == reuse)
{
/*
The Item constructor registered itself in the arena free list,
while the item slot is reused, so we have to restore the list.
*/
it->next= old_item_next;
*p_free_list= old_free_list;
}
DBUG_RETURN(it);
}
......@@ -1358,14 +1373,6 @@ int sp_head::execute_procedure(THD *thd, List<Item> *args)
uint offset= static_cast<Item_splocal *>(it)->get_offset();
Item *val= nctx->get_item(i);
Item *orig= octx->get_item(offset);
Item *o_item_next;
/* we'll use callers_arena in sp_eval_func_item */
Item *o_free_list= thd->spcont->callers_arena->free_list;
LINT_INIT(o_item_next);
if (orig)
o_item_next= orig->next;
/*
We might need to allocate new item if we weren't able to
......@@ -1380,15 +1387,6 @@ int sp_head::execute_procedure(THD *thd, List<Item> *args)
}
if (copy != orig)
octx->set_item(offset, copy);
if (orig && copy == orig)
{
/*
A reused item slot, where the constructor put it in the
free_list, so we have to restore the list.
*/
thd->spcont->callers_arena->free_list= o_free_list;
copy->next= o_item_next;
}
}
else
{
......@@ -2476,6 +2474,10 @@ sp_instr_cpop::backpatch(uint dest, sp_pcontext *dst_ctx)
int
sp_instr_copen::execute(THD *thd, uint *nextp)
{
/*
We don't store a pointer to the cursor in the instruction to be
able to reuse the same instruction among different threads in future.
*/
sp_cursor *c= thd->spcont->get_cursor(m_cursor);
int res;
DBUG_ENTER("sp_instr_copen::execute");
......@@ -2484,14 +2486,7 @@ sp_instr_copen::execute(THD *thd, uint *nextp)
res= -1;
else
{
sp_lex_keeper *lex_keeper= c->pre_open(thd);
if (!lex_keeper) // cursor already open or OOM
{
res= -1;
*nextp= m_ip+1;
}
else
{
sp_lex_keeper *lex_keeper= c->get_lex_keeper();
Query_arena *old_arena= thd->stmt_arena;
/*
......@@ -2517,8 +2512,7 @@ sp_instr_copen::execute(THD *thd, uint *nextp)
if (thd->spcont->found_handler(&dummy1, &dummy2))
res= -1;
}
c->post_open(thd, res ? FALSE : TRUE);
}
/* TODO: Assert here that we either have an error or a cursor */
}
DBUG_RETURN(res);
}
......@@ -2527,7 +2521,8 @@ sp_instr_copen::execute(THD *thd, uint *nextp)
int
sp_instr_copen::exec_core(THD *thd, uint *nextp)
{
int res= mysql_execute_command(thd);
sp_cursor *c= thd->spcont->get_cursor(m_cursor);
int res= c->open(thd);
*nextp= m_ip+1;
return res;
}
......@@ -2582,14 +2577,7 @@ sp_instr_cfetch::execute(THD *thd, uint *nextp)
Query_arena backup_arena;
DBUG_ENTER("sp_instr_cfetch::execute");
if (! c)
res= -1;
else
{
thd->set_n_backup_active_arena(thd->spcont->callers_arena, &backup_arena);
res= c->fetch(thd, &m_varlist);
thd->restore_active_arena(thd->spcont->callers_arena, &backup_arena);
}
res= c ? c->fetch(thd, &m_varlist) : -1;
*nextp= m_ip+1;
DBUG_RETURN(res);
......
......@@ -860,6 +860,12 @@ class sp_instr_cpush : public sp_instr
virtual void print(String *str);
/*
This call is used to cleanup the instruction when a sensitive
cursor is closed. For now stored procedures always use materialized
cursors and the call is not used.
*/
virtual void cleanup_stmt() { /* no op */ }
private:
sp_lex_keeper m_lex_keeper;
......@@ -1041,4 +1047,7 @@ sp_add_to_query_tables(THD *thd, LEX *lex,
const char *db, const char *name,
thr_lock_type locktype);
Item *sp_eval_func_item(THD *thd, Item **it, enum_field_types type,
Item *reuse, bool use_callers_arena);
#endif /* _SP_HEAD_H_ */
......@@ -25,6 +25,7 @@
#include "mysql.h"
#include "sp_head.h"
#include "sql_cursor.h"
#include "sp_rcontext.h"
#include "sp_pcontext.h"
......@@ -45,31 +46,18 @@ int
sp_rcontext::set_item_eval(THD *thd, uint idx, Item **item_addr,
enum_field_types type)
{
extern Item *sp_eval_func_item(THD *thd, Item **it, enum_field_types type,
Item *reuse, bool use_callers_arena);
Item *it;
Item *reuse_it;
Item *old_item_next;
/* sp_eval_func_item will use callers_arena */
Item *old_free_list= thd->spcont->callers_arena->free_list;
int res;
LINT_INIT(old_item_next);
if ((reuse_it= get_item(idx)))
old_item_next= reuse_it->next;
reuse_it= get_item(idx);
it= sp_eval_func_item(thd, item_addr, type, reuse_it, TRUE);
if (! it)
res= -1;
else
{
res= 0;
if (reuse_it && it == reuse_it)
{
// A reused item slot, where the constructor put it in the free_list,
// so we have to restore the list.
thd->spcont->callers_arena->free_list= old_free_list;
it->next= old_item_next;
}
set_item(idx, it);
}
......@@ -170,7 +158,8 @@ sp_rcontext::pop_cursors(uint count)
*/
sp_cursor::sp_cursor(sp_lex_keeper *lex_keeper, sp_instr_cpush *i)
:m_lex_keeper(lex_keeper), m_prot(NULL), m_isopen(0), m_current_row(NULL),
:m_lex_keeper(lex_keeper),
server_side_cursor(NULL),
m_i(i)
{
/*
......@@ -182,59 +171,37 @@ sp_cursor::sp_cursor(sp_lex_keeper *lex_keeper, sp_instr_cpush *i)
/*
pre_open cursor
Open an SP cursor
SYNOPSIS
pre_open()
open()
THD Thread handler
NOTES
We have to open cursor in two steps to make it easy for sp_instr_copen
to reuse the sp_instr::exec_stmt() code.
If this function returns 0, post_open should not be called
RETURN
0 ERROR
0 in case of success, -1 otherwise
*/
sp_lex_keeper*
sp_cursor::pre_open(THD *thd)
int
sp_cursor::open(THD *thd)
{
if (m_isopen)
if (server_side_cursor)
{
my_message(ER_SP_CURSOR_ALREADY_OPEN, ER(ER_SP_CURSOR_ALREADY_OPEN),
MYF(0));
return NULL;
return -1;
}
init_alloc_root(&m_mem_root, MEM_ROOT_BLOCK_SIZE, MEM_ROOT_PREALLOC);
if ((m_prot= new Protocol_cursor(thd, &m_mem_root)) == NULL)
return NULL;
/* Save for execution. Will be restored in post_open */
m_oprot= thd->protocol;
m_nseof= thd->net.no_send_eof;
/* Change protocol for execution */
thd->protocol= m_prot;
thd->net.no_send_eof= TRUE;
return m_lex_keeper;
}
void
sp_cursor::post_open(THD *thd, my_bool was_opened)
{
thd->net.no_send_eof= m_nseof; // Restore the originals
thd->protocol= m_oprot;
if ((m_isopen= was_opened))
m_current_row= m_prot->data;
if (mysql_open_cursor(thd, (uint) ALWAYS_MATERIALIZED_CURSOR, &result,
&server_side_cursor))
return -1;
return 0;
}
int
sp_cursor::close(THD *thd)
{
if (! m_isopen)
if (! server_side_cursor)
{
my_message(ER_SP_CURSOR_NOT_OPEN, ER(ER_SP_CURSOR_NOT_OPEN), MYF(0));
return -1;
......@@ -247,106 +214,82 @@ sp_cursor::close(THD *thd)
void
sp_cursor::destroy()
{
if (m_prot)
{
delete m_prot;
m_prot= NULL;
free_root(&m_mem_root, MYF(0));
}
m_isopen= FALSE;
delete server_side_cursor;
server_side_cursor= 0;
}
int
sp_cursor::fetch(THD *thd, List<struct sp_pvar> *vars)
{
List_iterator_fast<struct sp_pvar> li(*vars);
sp_pvar_t *pv;
MYSQL_ROW row;
uint fldcount;
if (! m_isopen)
if (! server_side_cursor)
{
my_message(ER_SP_CURSOR_NOT_OPEN, ER(ER_SP_CURSOR_NOT_OPEN), MYF(0));
return -1;
}
if (m_current_row == NULL)
if (vars->elements != result.get_field_count())
{
my_message(ER_SP_FETCH_NO_DATA, ER(ER_SP_FETCH_NO_DATA), MYF(0));
my_message(ER_SP_WRONG_NO_OF_FETCH_ARGS,
ER(ER_SP_WRONG_NO_OF_FETCH_ARGS), MYF(0));
return -1;
}
row= m_current_row->data;
for (fldcount= 0 ; (pv= li++) ; fldcount++)
{
Item *it;
Item *reuse;
uint rsize;
Item *old_item_next;
Item *old_free_list= thd->free_list;
const char *s;
LINT_INIT(old_item_next);
if (fldcount >= m_prot->get_field_count())
result.set_spvar_list(vars);
/* Attempt to fetch one row */
if (server_side_cursor->is_open())
server_side_cursor->fetch(1);
/*
If the cursor was pointing after the last row, the fetch will
close it instead of sending any rows.
*/
if (! server_side_cursor->is_open())
{
my_message(ER_SP_WRONG_NO_OF_FETCH_ARGS,
ER(ER_SP_WRONG_NO_OF_FETCH_ARGS), MYF(0));
my_message(ER_SP_FETCH_NO_DATA, ER(ER_SP_FETCH_NO_DATA), MYF(0));
return -1;
}
if ((reuse= thd->spcont->get_item(pv->offset)))
old_item_next= reuse->next;
return 0;
}
s= row[fldcount];
if (!s)
it= new(reuse, &rsize) Item_null();
else
{
/***************************************************************************
Select_fetch_into_spvars
****************************************************************************/
int Select_fetch_into_spvars::prepare(List<Item> &fields, SELECT_LEX_UNIT *u)
{
/*
Length of data can be calculated as:
pointer_to_next_not_null_object - s -1
where the last -1 is to remove the end \0
Cache the number of columns in the result set in order to easily
return an error if column count does not match value count.
*/
uint len;
MYSQL_ROW next= row+fldcount+1;
while (!*next) // Skip nulls
next++;
len= (*next -s)-1;
switch (sp_map_result_type(pv->type)) {
case INT_RESULT:
it= new(reuse, &rsize) Item_int(s);
break;
case REAL_RESULT:
it= new(reuse, &rsize) Item_float(s, len);
break;
case DECIMAL_RESULT:
it= new(reuse, &rsize) Item_decimal(s, len, thd->db_charset);
break;
case STRING_RESULT:
/* TODO: Document why we do an extra copy of the string 's' here */
it= new(reuse, &rsize) Item_string(thd->strmake(s, len), len,
thd->db_charset);
break;
case ROW_RESULT:
default:
DBUG_ASSERT(0);
}
}
it->rsize= rsize;
if (reuse && it == reuse)
field_count= fields.elements;
return select_result_interceptor::prepare(fields, u);
}
bool Select_fetch_into_spvars::send_data(List<Item> &items)
{
List_iterator_fast<struct sp_pvar> pv_iter(*spvar_list);
List_iterator_fast<Item> item_iter(items);
sp_pvar_t *pv;
Item *item;
/* Must be ensured by the caller */
DBUG_ASSERT(spvar_list->elements == items.elements);
/*
Assign the row fetched from a server side cursor to stored
procedure variables.
*/
for (; pv= pv_iter++, item= item_iter++; )
{
// A reused item slot, where the constructor put it in the free_list,
// so we have to restore the list.
thd->free_list= old_free_list;
it->next= old_item_next;
}
Item *reuse= thd->spcont->get_item(pv->offset);
/* Evaluate a new item on the arena of the calling instruction */
Item *it= sp_eval_func_item(thd, &item, pv->type, reuse, TRUE);
thd->spcont->set_item(pv->offset, it);
}
if (fldcount < m_prot->get_field_count())
{
my_message(ER_SP_WRONG_NO_OF_FETCH_ARGS,
ER(ER_SP_WRONG_NO_OF_FETCH_ARGS), MYF(0));
return -1;
}
m_current_row= m_current_row->next;
return 0;
return FALSE;
}
......@@ -216,6 +216,27 @@ class sp_rcontext : public Sql_alloc
}; // class sp_rcontext : public Sql_alloc
/*
An interceptor of cursor result set used to implement
FETCH <cname> INTO <varlist>.
*/
class Select_fetch_into_spvars: public select_result_interceptor
{
List<struct sp_pvar> *spvar_list;
uint field_count;
public:
uint get_field_count() { return field_count; }
void set_spvar_list(List<struct sp_pvar> *vars) { spvar_list= vars; }
virtual bool send_eof() { return FALSE; }
virtual bool send_data(List<Item> &items);
virtual int prepare(List<Item> &list, SELECT_LEX_UNIT *u);
};
/* A mediator between stored procedures and server side cursors */
class sp_cursor : public Sql_alloc
{
public:
......@@ -227,12 +248,11 @@ class sp_cursor : public Sql_alloc
destroy();
}
// We have split this in two to make it easy for sp_instr_copen
// to reuse the sp_instr::exec_stmt() code.
sp_lex_keeper *
pre_open(THD *thd);
void
post_open(THD *thd, my_bool was_opened);
get_lex_keeper() { return m_lex_keeper; }
int
open(THD *thd);
int
close(THD *thd);
......@@ -240,7 +260,7 @@ class sp_cursor : public Sql_alloc
inline my_bool
is_open()
{
return m_isopen;
return test(server_side_cursor);
}
int
......@@ -254,15 +274,10 @@ class sp_cursor : public Sql_alloc
private:
MEM_ROOT m_mem_root; // My own mem_root
Select_fetch_into_spvars result;
sp_lex_keeper *m_lex_keeper;
Protocol_cursor *m_prot;
my_bool m_isopen;
my_bool m_nseof; // Original no_send_eof
Protocol *m_oprot; // Original protcol
MYSQL_ROWS *m_current_row;
Server_side_cursor *server_side_cursor;
sp_instr_cpush *m_i; // My push instruction
void
destroy();
......
......@@ -1544,6 +1544,19 @@ void Query_arena::free_items()
}
void Query_arena::set_query_arena(Query_arena *set)
{
mem_root= set->mem_root;
free_list= set->free_list;
state= set->state;
}
void Query_arena::cleanup_stmt()
{
DBUG_ASSERT("Query_arena::cleanup_stmt()" == "not implemented");
}
/*
Statement functions
*/
......@@ -1601,12 +1614,6 @@ void Statement::restore_backup_statement(Statement *stmt, Statement *backup)
}
void Statement::close_cursor()
{
DBUG_ASSERT("Statement::close_cursor()" == "not implemented");
}
void THD::end_statement()
{
/* Cleanup SQL processing state to resuse this statement in next query. */
......@@ -1648,13 +1655,6 @@ void THD::restore_active_arena(Query_arena *set, Query_arena *backup)
DBUG_VOID_RETURN;
}
void Query_arena::set_query_arena(Query_arena *set)
{
mem_root= set->mem_root;
free_list= set->free_list;
state= set->state;
}
Statement::~Statement()
{
/*
......@@ -1723,9 +1723,11 @@ int Statement_map::insert(Statement *statement)
void Statement_map::close_transient_cursors()
{
#ifdef TO_BE_IMPLEMENTED
Statement *stmt;
while ((stmt= transient_cursor_list.head()))
stmt->close_cursor(); /* deletes itself from the list */
#endif
}
......
......@@ -737,10 +737,12 @@ class Query_arena
void set_query_arena(Query_arena *set);
void free_items();
/* Close the active state associated with execution of this statement */
virtual void cleanup_stmt();
};
class Cursor;
class Server_side_cursor;
/*
State of a single command executed against this connection.
......@@ -816,7 +818,7 @@ class Statement: public ilink, public Query_arena
*/
char *query;
uint32 query_length; // current query length
Cursor *cursor;
Server_side_cursor *cursor;
public:
......@@ -833,8 +835,6 @@ class Statement: public ilink, public Query_arena
void restore_backup_statement(Statement *stmt, Statement *backup);
/* return class type */
virtual Type type() const;
/* Close the cursor open for this statement, if there is one */
virtual void close_cursor();
};
......@@ -886,9 +886,6 @@ class Statement_map
}
hash_delete(&st_hash, (byte *) statement);
}
void add_transient_cursor(Statement *stmt)
{ transient_cursor_list.append(stmt); }
void erase_transient_cursor(Statement *stmt) { stmt->unlink(); }
/*
Close all cursors of this connection that use tables of a storage
engine that has transaction-specific state and therefore can not
......@@ -1812,18 +1809,21 @@ class TMP_TABLE_PARAM :public Sql_alloc
}
};
class select_union :public select_result_interceptor {
public:
TABLE *table;
class select_union :public select_result_interceptor
{
TMP_TABLE_PARAM tmp_table_param;
public:
TABLE *table;
select_union(TABLE *table_par);
~select_union();
select_union() :table(0) {}
int prepare(List<Item> &list, SELECT_LEX_UNIT *u);
bool send_data(List<Item> &items);
bool send_eof();
bool flush();
void set_table(TABLE *tbl) { table= tbl; }
bool create_result_table(THD *thd, List<Item> *column_types,
bool is_distinct, ulonglong options,
const char *alias);
};
/* Base subselect interface class */
......
This diff is collapsed.
#ifndef _sql_cursor_h_
#define _sql_cursor_h_
/* Copyright (C) 2005 MySQL AB & MySQL Finland AB & TCX DataKonsult AB
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
#ifdef USE_PRAGMA_INTERFACE
#pragma interface /* gcc class interface */
#endif
/*
Declarations for implementation of server side cursors. Only
read-only non-scrollable cursors are currently implemented.
*/
/*
Server_side_cursor -- an interface for materialized and
sensitive (non-materialized) implementation of cursors. All
cursors are self-contained (created in their own memory root).
For that reason they must be deleted only using a pointer to
Server_side_cursor, not to its base class.
*/
class Server_side_cursor: protected Query_arena, public Sql_alloc
{
protected:
/* Row destination used for fetch */
select_result *result;
public:
Server_side_cursor(MEM_ROOT *mem_root_arg, select_result *result_arg)
:Query_arena(mem_root_arg, INITIALIZED), result(result_arg)
{}
virtual bool is_open() const= 0;
virtual int open(JOIN *top_level_join)= 0;
virtual void fetch(ulong num_rows)= 0;
virtual void close()= 0;
virtual ~Server_side_cursor();
static void operator delete(void *ptr, size_t size);
};
int mysql_open_cursor(THD *thd, uint flags,
select_result *result,
Server_side_cursor **res);
/* Possible values for flags */
enum { ANY_CURSOR= 1, ALWAYS_MATERIALIZED_CURSOR= 2 };
#endif /* _sql_cusor_h_ */
......@@ -27,7 +27,7 @@
/*
call given derived table processor (preparing or filling tables)
Call given derived table processor (preparing or filling tables)
SYNOPSIS
mysql_handle_derived()
......@@ -36,7 +36,6 @@
RETURN
0 ok
-1 Error
1 Error and error message given
*/
......@@ -97,14 +96,14 @@ mysql_handle_derived(LEX *lex, int (*processor)(THD*, LEX*, TABLE_LIST*))
RETURN
0 ok
1 Error
-1 Error and error message given
*/
1 Error and an error message was given
*/
int mysql_derived_prepare(THD *thd, LEX *lex, TABLE_LIST *orig_table_list)
{
SELECT_LEX_UNIT *unit= orig_table_list->derived;
int res= 0;
ulonglong create_options;
DBUG_ENTER("mysql_derived_prepare");
if (unit)
{
......@@ -118,21 +117,18 @@ int mysql_derived_prepare(THD *thd, LEX *lex, TABLE_LIST *orig_table_list)
for (SELECT_LEX *sl= first_select; sl; sl= sl->next_select())
sl->context.outer_context= 0;
if (!(derived_result= new select_union(0)))
if (!(derived_result= new select_union))
DBUG_RETURN(1); // out of memory
// st_select_lex_unit::prepare correctly work for single select
if ((res= unit->prepare(thd, derived_result, 0, orig_table_list->alias)))
if ((res= unit->prepare(thd, derived_result, 0)))
goto exit;
if (check_duplicate_names(unit->types, 0))
{
res= -1;
if ((res= check_duplicate_names(unit->types, 0)))
goto exit;
}
derived_result->tmp_table_param.init();
derived_result->tmp_table_param.field_count= unit->types.elements;
create_options= (first_select->options | thd->options |
TMP_TABLE_ALL_COLUMNS);
/*
Temp table is created so that it hounours if UNION without ALL is to be
processed
......@@ -143,18 +139,12 @@ int mysql_derived_prepare(THD *thd, LEX *lex, TABLE_LIST *orig_table_list)
!unit->union_distinct->next_select() (i.e. it is union and last distinct
SELECT is last SELECT of UNION).
*/
if (!(table= create_tmp_table(thd, &derived_result->tmp_table_param,
unit->types, (ORDER*) 0,
FALSE, 1,
(first_select->options | thd->options |
TMP_TABLE_ALL_COLUMNS),
HA_POS_ERROR,
if ((res= derived_result->create_result_table(thd, &unit->types, FALSE,
create_options,
orig_table_list->alias)))
{
res= -1;
goto exit;
}
derived_result->set_table(table);
table= derived_result->table;
exit:
/* Hide "Unknown column" or "Unknown function" error */
......@@ -231,9 +221,8 @@ int mysql_derived_prepare(THD *thd, LEX *lex, TABLE_LIST *orig_table_list)
RETURN
0 ok
1 Error
-1 Error and error message given
*/
1 Error and an error message was given
*/
int mysql_derived_filling(THD *thd, LEX *lex, TABLE_LIST *orig_table_list)
{
......
......@@ -432,10 +432,6 @@ class st_select_lex_unit: public st_select_lex_node {
{
return my_reinterpret_cast(st_select_lex*)(slave);
}
st_select_lex* first_select_in_union()
{
return my_reinterpret_cast(st_select_lex*)(slave);
}
st_select_lex_unit* next_unit()
{
return my_reinterpret_cast(st_select_lex_unit*)(next);
......@@ -445,8 +441,7 @@ class st_select_lex_unit: public st_select_lex_node {
void exclude_tree();
/* UNION methods */
bool prepare(THD *thd, select_result *result, ulong additional_options,
const char *tmp_table_alias);
bool prepare(THD *thd, select_result *result, ulong additional_options);
bool exec();
bool cleanup();
inline void unclean() { cleaned= 0; }
......@@ -462,7 +457,10 @@ class st_select_lex_unit: public st_select_lex_node {
friend void lex_start(THD *thd, uchar *buf, uint length);
friend int subselect_union_engine::exec();
List<Item> *get_unit_column_types();
};
typedef class st_select_lex_unit SELECT_LEX_UNIT;
/*
......
......@@ -32,6 +32,8 @@ class Sql_alloc
{
return (void*) sql_alloc((uint) size);
}
static void *operator new[](size_t size, MEM_ROOT *mem_root)
{ return (void*) alloc_root(mem_root, (uint) size); }
static void *operator new(size_t size, MEM_ROOT *mem_root)
{ return (void*) alloc_root(mem_root, (uint) size); }
static void operator delete(void *ptr, size_t size) { TRASH(ptr, size); }
......
This diff is collapsed.
This diff is collapsed.
......@@ -101,7 +101,7 @@ enum enum_nested_loop_state
typedef enum_nested_loop_state
(*Next_select_func)(JOIN *, struct st_join_table *, bool);
typedef int (*Read_record_func)(struct st_join_table *tab);
Next_select_func setup_end_select_func(JOIN *join);
typedef struct st_join_table {
TABLE *table;
......@@ -140,6 +140,11 @@ typedef struct st_join_table {
void cleanup();
} JOIN_TAB;
enum_nested_loop_state sub_select_cache(JOIN *join, JOIN_TAB *join_tab, bool
end_of_records);
enum_nested_loop_state sub_select(JOIN *join,JOIN_TAB *join_tab, bool
end_of_records);
typedef struct st_position /* Used in find_best */
{
......@@ -372,58 +377,6 @@ class JOIN :public Sql_alloc
};
/*
Server-side cursor (now stands only for basic read-only cursor)
See class implementation in sql_select.cc
A cursor has its own runtime state - list of used items and memory root of
used memory - which is different from Prepared statement runtime: it must
be different at least for the purpose of reusing the same prepared
statement for many cursors.
*/
class Cursor: public Sql_alloc, public Query_arena
{
MEM_ROOT main_mem_root;
JOIN *join;
SELECT_LEX_UNIT *unit;
TABLE *open_tables;
MYSQL_LOCK *lock;
TABLE *derived_tables;
/* List of items created during execution */
query_id_t query_id;
struct Engine_info
{
const handlerton *ht;
void *read_view;
};
Engine_info ht_info[MAX_HA];
public:
Protocol_prep protocol;
Item_change_list change_list;
select_send result;
THR_LOCK_OWNER lock_id;
my_bool close_at_commit;
/* Temporary implementation as now we replace THD state by value */
/* Save THD state into cursor */
void init_from_thd(THD *thd);
/* bzero cursor state in THD */
void reset_thd(THD *thd);
int open(JOIN *join);
void fetch(ulong num_rows);
void reset() { join= 0; }
bool is_open() const { return join != 0; }
void close(bool is_active);
void set_unit(SELECT_LEX_UNIT *unit_arg) { unit= unit_arg; }
Cursor(THD *thd);
~Cursor() {}
};
typedef struct st_select_check {
uint const_ref,reg_ref;
} SELECT_CHECK;
......
This diff is collapsed.
......@@ -378,7 +378,7 @@ bool mysql_create_view(THD *thd,
/* prepare select to resolve all fields */
lex->view_prepare_mode= 1;
if (unit->prepare(thd, 0, 0, view->view_name.str))
if (unit->prepare(thd, 0, 0))
{
/*
some errors from prepare are reported to user, if is not then
......
......@@ -1687,6 +1687,63 @@ db_type get_table_type(THD *thd, const char *name)
DBUG_RETURN(ha_checktype(thd,(enum db_type) (uint) *(head+3),0,0));
}
/*
Create Item_field for each column in the table.
SYNPOSIS
st_table::fill_item_list()
item_list a pointer to an empty list used to store items
DESCRIPTION
Create Item_field object for each column in the table and
initialize it with the corresponding Field. New items are
created in the current THD memory root.
RETURN VALUE
0 success
1 out of memory
*/
bool st_table::fill_item_list(List<Item> *item_list) const
{
/*
All Item_field's created using a direct pointer to a field
are fixed in Item_field constructor.
*/
for (Field **ptr= field; *ptr; ptr++)
{
Item_field *item= new Item_field(*ptr);
if (!item || item_list->push_back(item))
return TRUE;
}
return FALSE;
}
/*
Reset an existing list of Item_field items to point to the
Fields of this table.
SYNPOSIS
st_table::fill_item_list()
item_list a non-empty list with Item_fields
DESCRIPTION
This is a counterpart of fill_item_list used to redirect
Item_fields to the fields of a newly created table.
The caller must ensure that number of items in the item_list
is the same as the number of columns in the table.
*/
void st_table::reset_item_list(List<Item> *item_list) const
{
List_iterator_fast<Item> it(*item_list);
for (Field **ptr= field; *ptr; ptr++)
{
Item_field *item_field= (Item_field*) it++;
DBUG_ASSERT(item_field != 0);
item_field->reset_field(*ptr);
}
}
/*
calculate md5 of query
......
......@@ -267,6 +267,9 @@ struct st_table {
GRANT_INFO grant;
FILESORT_INFO sort;
TABLE_SHARE share_not_to_be_used; /* To be deleted when true shares */
bool fill_item_list(List<Item> *item_list) const;
void reset_item_list(List<Item> *item_list) const;
};
......
......@@ -13828,8 +13828,11 @@ static void test_bug10760()
rc= mysql_stmt_execute(stmt);
check_execute(stmt, rc);
rc= mysql_query(mysql, "update t1 set id=id+100");
DIE_UNLESS(rc);
if (!opt_silent)
/*
If cursors are not materialized, the update will return an error;
we mainly test that it won't deadlock.
*/
if (rc && !opt_silent)
printf("Got error (as expected): %s\n", mysql_error(mysql));
/*
2: check that MyISAM tables used in cursors survive
......
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