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); ...@@ -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, extern void init_alloc_root(MEM_ROOT *mem_root, uint block_size,
uint pre_alloc_size); uint pre_alloc_size);
extern gptr alloc_root(MEM_ROOT *mem_root,unsigned int 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 free_root(MEM_ROOT *root, myf MyFLAGS);
extern void set_prealloc_root(MEM_ROOT *root, char *ptr); extern void set_prealloc_root(MEM_ROOT *root, char *ptr);
extern void reset_root_defaults(MEM_ROOT *mem_root, uint block_size, 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 \ ...@@ -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_string.cc sql_table.cc sql_test.cc sql_udf.cc \
sql_update.cc sql_yacc.cc table.cc thr_malloc.cc time.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 \ 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 \ 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 \ parse_file.cc sql_view.cc sql_trigger.cc my_decimal.cc \
ha_blackhole.cc ha_blackhole.cc
......
...@@ -2271,3 +2271,33 @@ select c1 from t1 where c1 like 'abcde111%' order by c1; ...@@ -2271,3 +2271,33 @@ select c1 from t1 where c1 like 'abcde111%' order by c1;
c1 c1
abcde111 abcde111
drop table t1; 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; ...@@ -13,3 +13,49 @@ select @value;
3 3
drop procedure test.longprocedure; drop procedure test.longprocedure;
drop table t1; 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'; ...@@ -1151,3 +1151,45 @@ SET collation_connection='ujis_bin';
-- source include/ctype_innodb_like.inc -- source include/ctype_innodb_like.inc
# End of 4.1 tests # 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; ...@@ -31,3 +31,52 @@ call test.longprocedure(@value); select @value;
drop procedure test.longprocedure; drop procedure test.longprocedure;
drop table t1; 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) ...@@ -221,6 +221,57 @@ gptr alloc_root(MEM_ROOT *mem_root,unsigned int Size)
#endif #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) #define TRASH_MEM(X) TRASH(((char*)(X) + ((X)->size-(X)->left)), (X)->left)
/* Mark all data in blocks free for reusage */ /* 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 \ ...@@ -61,7 +61,7 @@ noinst_HEADERS = item.h item_func.h item_sum.h item_cmpfunc.h \
tztime.h my_decimal.h\ tztime.h my_decimal.h\
sp_head.h sp_pcontext.h sp_rcontext.h sp.h sp_cache.h \ sp_head.h sp_pcontext.h sp_rcontext.h sp.h sp_cache.h \
parse_file.h sql_view.h sql_trigger.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_example.h examples/ha_archive.h \
examples/ha_tina.h ha_blackhole.h \ examples/ha_tina.h ha_blackhole.h \
ha_federated.h ha_federated.h
...@@ -94,7 +94,7 @@ mysqld_SOURCES = sql_lex.cc sql_handler.cc \ ...@@ -94,7 +94,7 @@ mysqld_SOURCES = sql_lex.cc sql_handler.cc \
client.c sql_client.cc mini_client_errors.c pack.c\ client.c sql_client.cc mini_client_errors.c pack.c\
stacktrace.c repl_failsafe.h repl_failsafe.cc \ stacktrace.c repl_failsafe.h repl_failsafe.cc \
sql_olap.cc sql_view.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\ tztime.cc my_time.c my_decimal.cc\
sp_head.cc sp_pcontext.cc sp_rcontext.cc sp.cc \ sp_head.cc sp_pcontext.cc sp_rcontext.cc sp.cc \
sp_cache.cc parse_file.cc sql_trigger.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) ...@@ -1339,11 +1339,9 @@ int handler::ha_open(const char *name, int mode, int test_if_locked)
table->db_stat|=HA_READ_ONLY; table->db_stat|=HA_READ_ONLY;
(void) extra(HA_EXTRA_NO_READCHECK); // Not needed in SQL (void) extra(HA_EXTRA_NO_READCHECK); // Not needed in SQL
if (!alloc_root_inited(&table->mem_root)) // If temporary table DBUG_ASSERT(alloc_root_inited(&table->mem_root));
ref=(byte*) sql_alloc(ALIGN_SIZE(ref_length)*2);
else if (!(ref= (byte*) alloc_root(&table->mem_root, ALIGN_SIZE(ref_length)*2)))
ref=(byte*) alloc_root(&table->mem_root, ALIGN_SIZE(ref_length)*2);
if (!ref)
{ {
close(); close();
error=HA_ERR_OUT_OF_MEM; error=HA_ERR_OUT_OF_MEM;
......
...@@ -1468,7 +1468,7 @@ int subselect_single_select_engine::prepare() ...@@ -1468,7 +1468,7 @@ int subselect_single_select_engine::prepare()
int subselect_union_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() int subselect_uniquesubquery_engine::prepare()
......
...@@ -150,30 +150,6 @@ class Protocol_prep :public Protocol ...@@ -150,30 +150,6 @@ class Protocol_prep :public Protocol
virtual bool store(Field *field); 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 send_warning(THD *thd, uint sql_errno, const char *err=0);
void net_printf_error(THD *thd, uint sql_errno, ...); void net_printf_error(THD *thd, uint sql_errno, ...);
void net_send_error(THD *thd, uint sql_errno=0, const char *err=0); 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, ...@@ -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); Item *it= sp_prepare_func_item(thd, it_addr);
uint rsize; uint rsize;
Query_arena backup_arena; Query_arena backup_arena;
Item *old_item_next, *old_free_list, **p_free_list;
DBUG_PRINT("info", ("type: %d", type)); DBUG_PRINT("info", ("type: %d", type));
if (!it) if (!it)
{
DBUG_RETURN(NULL); 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)) { switch (sp_map_result_type(type)) {
...@@ -312,15 +319,23 @@ sp_eval_func_item(THD *thd, Item **it_addr, enum enum_field_types type, ...@@ -312,15 +319,23 @@ sp_eval_func_item(THD *thd, Item **it_addr, enum enum_field_types type,
default: default:
DBUG_ASSERT(0); DBUG_ASSERT(0);
} }
it->rsize= rsize; goto end;
DBUG_RETURN(it);
return_null_item: return_null_item:
CREATE_ON_CALLERS_ARENA(it= new(reuse, &rsize) Item_null(), CREATE_ON_CALLERS_ARENA(it= new(reuse, &rsize) Item_null(),
use_callers_arena, &backup_arena); use_callers_arena, &backup_arena);
end:
it->rsize= rsize; 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); DBUG_RETURN(it);
} }
...@@ -1358,14 +1373,6 @@ int sp_head::execute_procedure(THD *thd, List<Item> *args) ...@@ -1358,14 +1373,6 @@ int sp_head::execute_procedure(THD *thd, List<Item> *args)
uint offset= static_cast<Item_splocal *>(it)->get_offset(); uint offset= static_cast<Item_splocal *>(it)->get_offset();
Item *val= nctx->get_item(i); Item *val= nctx->get_item(i);
Item *orig= octx->get_item(offset); 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 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) ...@@ -1380,15 +1387,6 @@ int sp_head::execute_procedure(THD *thd, List<Item> *args)
} }
if (copy != orig) if (copy != orig)
octx->set_item(offset, copy); 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 else
{ {
...@@ -2476,6 +2474,10 @@ sp_instr_cpop::backpatch(uint dest, sp_pcontext *dst_ctx) ...@@ -2476,6 +2474,10 @@ sp_instr_cpop::backpatch(uint dest, sp_pcontext *dst_ctx)
int int
sp_instr_copen::execute(THD *thd, uint *nextp) 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); sp_cursor *c= thd->spcont->get_cursor(m_cursor);
int res; int res;
DBUG_ENTER("sp_instr_copen::execute"); DBUG_ENTER("sp_instr_copen::execute");
...@@ -2484,41 +2486,33 @@ sp_instr_copen::execute(THD *thd, uint *nextp) ...@@ -2484,41 +2486,33 @@ sp_instr_copen::execute(THD *thd, uint *nextp)
res= -1; res= -1;
else else
{ {
sp_lex_keeper *lex_keeper= c->pre_open(thd); sp_lex_keeper *lex_keeper= c->get_lex_keeper();
if (!lex_keeper) // cursor already open or OOM Query_arena *old_arena= thd->stmt_arena;
{
res= -1;
*nextp= m_ip+1;
}
else
{
Query_arena *old_arena= thd->stmt_arena;
/* /*
Get the Query_arena from the cpush instruction, which contains Get the Query_arena from the cpush instruction, which contains
the free_list of the query, so new items (if any) are stored in the free_list of the query, so new items (if any) are stored in
the right free_list, and we can cleanup after each open. the right free_list, and we can cleanup after each open.
*/ */
thd->stmt_arena= c->get_instr(); thd->stmt_arena= c->get_instr();
res= lex_keeper->reset_lex_and_exec_core(thd, nextp, FALSE, this); res= lex_keeper->reset_lex_and_exec_core(thd, nextp, FALSE, this);
/* Cleanup the query's items */ /* Cleanup the query's items */
if (thd->stmt_arena->free_list) if (thd->stmt_arena->free_list)
cleanup_items(thd->stmt_arena->free_list); cleanup_items(thd->stmt_arena->free_list);
thd->stmt_arena= old_arena; thd->stmt_arena= old_arena;
/* /*
Work around the fact that errors in selects are not returned properly Work around the fact that errors in selects are not returned properly
(but instead converted into a warning), so if a condition handler (but instead converted into a warning), so if a condition handler
caught, we have lost the result code. caught, we have lost the result code.
*/ */
if (!res) if (!res)
{ {
uint dummy1, dummy2; uint dummy1, dummy2;
if (thd->spcont->found_handler(&dummy1, &dummy2)) if (thd->spcont->found_handler(&dummy1, &dummy2))
res= -1; res= -1;
}
c->post_open(thd, res ? FALSE : TRUE);
} }
/* TODO: Assert here that we either have an error or a cursor */
} }
DBUG_RETURN(res); DBUG_RETURN(res);
} }
...@@ -2527,7 +2521,8 @@ sp_instr_copen::execute(THD *thd, uint *nextp) ...@@ -2527,7 +2521,8 @@ sp_instr_copen::execute(THD *thd, uint *nextp)
int int
sp_instr_copen::exec_core(THD *thd, uint *nextp) 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; *nextp= m_ip+1;
return res; return res;
} }
...@@ -2582,14 +2577,7 @@ sp_instr_cfetch::execute(THD *thd, uint *nextp) ...@@ -2582,14 +2577,7 @@ sp_instr_cfetch::execute(THD *thd, uint *nextp)
Query_arena backup_arena; Query_arena backup_arena;
DBUG_ENTER("sp_instr_cfetch::execute"); DBUG_ENTER("sp_instr_cfetch::execute");
if (! c) res= c ? c->fetch(thd, &m_varlist) : -1;
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);
}
*nextp= m_ip+1; *nextp= m_ip+1;
DBUG_RETURN(res); DBUG_RETURN(res);
......
...@@ -860,6 +860,12 @@ class sp_instr_cpush : public sp_instr ...@@ -860,6 +860,12 @@ class sp_instr_cpush : public sp_instr
virtual void print(String *str); 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: private:
sp_lex_keeper m_lex_keeper; sp_lex_keeper m_lex_keeper;
...@@ -1041,4 +1047,7 @@ sp_add_to_query_tables(THD *thd, LEX *lex, ...@@ -1041,4 +1047,7 @@ sp_add_to_query_tables(THD *thd, LEX *lex,
const char *db, const char *name, const char *db, const char *name,
thr_lock_type locktype); 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_ */ #endif /* _SP_HEAD_H_ */
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
#include "mysql.h" #include "mysql.h"
#include "sp_head.h" #include "sp_head.h"
#include "sql_cursor.h"
#include "sp_rcontext.h" #include "sp_rcontext.h"
#include "sp_pcontext.h" #include "sp_pcontext.h"
...@@ -45,31 +46,18 @@ int ...@@ -45,31 +46,18 @@ int
sp_rcontext::set_item_eval(THD *thd, uint idx, Item **item_addr, sp_rcontext::set_item_eval(THD *thd, uint idx, Item **item_addr,
enum_field_types type) 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 *it;
Item *reuse_it; Item *reuse_it;
Item *old_item_next;
/* sp_eval_func_item will use callers_arena */ /* sp_eval_func_item will use callers_arena */
Item *old_free_list= thd->spcont->callers_arena->free_list;
int res; int res;
LINT_INIT(old_item_next);
if ((reuse_it= get_item(idx))) reuse_it= get_item(idx);
old_item_next= reuse_it->next;
it= sp_eval_func_item(thd, item_addr, type, reuse_it, TRUE); it= sp_eval_func_item(thd, item_addr, type, reuse_it, TRUE);
if (! it) if (! it)
res= -1; res= -1;
else else
{ {
res= 0; 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); set_item(idx, it);
} }
...@@ -170,7 +158,8 @@ sp_rcontext::pop_cursors(uint count) ...@@ -170,7 +158,8 @@ sp_rcontext::pop_cursors(uint count)
*/ */
sp_cursor::sp_cursor(sp_lex_keeper *lex_keeper, sp_instr_cpush *i) 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) m_i(i)
{ {
/* /*
...@@ -182,59 +171,37 @@ sp_cursor::sp_cursor(sp_lex_keeper *lex_keeper, sp_instr_cpush *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 SYNOPSIS
pre_open() open()
THD Thread handler 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 RETURN
0 ERROR 0 in case of success, -1 otherwise
*/ */
sp_lex_keeper* int
sp_cursor::pre_open(THD *thd) 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), my_message(ER_SP_CURSOR_ALREADY_OPEN, ER(ER_SP_CURSOR_ALREADY_OPEN),
MYF(0)); MYF(0));
return NULL; return -1;
} }
init_alloc_root(&m_mem_root, MEM_ROOT_BLOCK_SIZE, MEM_ROOT_PREALLOC); if (mysql_open_cursor(thd, (uint) ALWAYS_MATERIALIZED_CURSOR, &result,
if ((m_prot= new Protocol_cursor(thd, &m_mem_root)) == NULL) &server_side_cursor))
return NULL; return -1;
return 0;
/* 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;
} }
int int
sp_cursor::close(THD *thd) 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)); my_message(ER_SP_CURSOR_NOT_OPEN, ER(ER_SP_CURSOR_NOT_OPEN), MYF(0));
return -1; return -1;
...@@ -247,106 +214,82 @@ sp_cursor::close(THD *thd) ...@@ -247,106 +214,82 @@ sp_cursor::close(THD *thd)
void void
sp_cursor::destroy() sp_cursor::destroy()
{ {
if (m_prot) delete server_side_cursor;
{ server_side_cursor= 0;
delete m_prot;
m_prot= NULL;
free_root(&m_mem_root, MYF(0));
}
m_isopen= FALSE;
} }
int int
sp_cursor::fetch(THD *thd, List<struct sp_pvar> *vars) sp_cursor::fetch(THD *thd, List<struct sp_pvar> *vars)
{ {
List_iterator_fast<struct sp_pvar> li(*vars); if (! server_side_cursor)
sp_pvar_t *pv;
MYSQL_ROW row;
uint fldcount;
if (! m_isopen)
{ {
my_message(ER_SP_CURSOR_NOT_OPEN, ER(ER_SP_CURSOR_NOT_OPEN), MYF(0)); my_message(ER_SP_CURSOR_NOT_OPEN, ER(ER_SP_CURSOR_NOT_OPEN), MYF(0));
return -1; 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; return -1;
} }
row= m_current_row->data; result.set_spvar_list(vars);
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())
{
my_message(ER_SP_WRONG_NO_OF_FETCH_ARGS,
ER(ER_SP_WRONG_NO_OF_FETCH_ARGS), MYF(0));
return -1;
}
if ((reuse= thd->spcont->get_item(pv->offset))) /* Attempt to fetch one row */
old_item_next= reuse->next; if (server_side_cursor->is_open())
server_side_cursor->fetch(1);
s= row[fldcount]; /*
if (!s) If the cursor was pointing after the last row, the fetch will
it= new(reuse, &rsize) Item_null(); close it instead of sending any rows.
else */
{ if (! server_side_cursor->is_open())
/*
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
*/
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)
{
// 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;
}
thd->spcont->set_item(pv->offset, it);
}
if (fldcount < m_prot->get_field_count())
{ {
my_message(ER_SP_WRONG_NO_OF_FETCH_ARGS, my_message(ER_SP_FETCH_NO_DATA, ER(ER_SP_FETCH_NO_DATA), MYF(0));
ER(ER_SP_WRONG_NO_OF_FETCH_ARGS), MYF(0));
return -1; return -1;
} }
m_current_row= m_current_row->next;
return 0; return 0;
} }
/***************************************************************************
Select_fetch_into_spvars
****************************************************************************/
int Select_fetch_into_spvars::prepare(List<Item> &fields, SELECT_LEX_UNIT *u)
{
/*
Cache the number of columns in the result set in order to easily
return an error if column count does not match value count.
*/
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++; )
{
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);
}
return FALSE;
}
...@@ -216,6 +216,27 @@ class sp_rcontext : public Sql_alloc ...@@ -216,6 +216,27 @@ class sp_rcontext : public Sql_alloc
}; // 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 class sp_cursor : public Sql_alloc
{ {
public: public:
...@@ -227,12 +248,11 @@ class sp_cursor : public Sql_alloc ...@@ -227,12 +248,11 @@ class sp_cursor : public Sql_alloc
destroy(); 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 * sp_lex_keeper *
pre_open(THD *thd); get_lex_keeper() { return m_lex_keeper; }
void
post_open(THD *thd, my_bool was_opened); int
open(THD *thd);
int int
close(THD *thd); close(THD *thd);
...@@ -240,7 +260,7 @@ class sp_cursor : public Sql_alloc ...@@ -240,7 +260,7 @@ class sp_cursor : public Sql_alloc
inline my_bool inline my_bool
is_open() is_open()
{ {
return m_isopen; return test(server_side_cursor);
} }
int int
...@@ -251,18 +271,13 @@ class sp_cursor : public Sql_alloc ...@@ -251,18 +271,13 @@ class sp_cursor : public Sql_alloc
{ {
return m_i; return m_i;
} }
private: private:
MEM_ROOT m_mem_root; // My own mem_root Select_fetch_into_spvars result;
sp_lex_keeper *m_lex_keeper; sp_lex_keeper *m_lex_keeper;
Protocol_cursor *m_prot; Server_side_cursor *server_side_cursor;
my_bool m_isopen;
my_bool m_nseof; // Original no_send_eof
Protocol *m_oprot; // Original protcol
MYSQL_ROWS *m_current_row;
sp_instr_cpush *m_i; // My push instruction sp_instr_cpush *m_i; // My push instruction
void void
destroy(); destroy();
......
...@@ -1544,6 +1544,19 @@ void Query_arena::free_items() ...@@ -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 Statement functions
*/ */
...@@ -1601,12 +1614,6 @@ void Statement::restore_backup_statement(Statement *stmt, Statement *backup) ...@@ -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() void THD::end_statement()
{ {
/* Cleanup SQL processing state to resuse this statement in next query. */ /* 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) ...@@ -1648,13 +1655,6 @@ void THD::restore_active_arena(Query_arena *set, Query_arena *backup)
DBUG_VOID_RETURN; 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() Statement::~Statement()
{ {
/* /*
...@@ -1723,9 +1723,11 @@ int Statement_map::insert(Statement *statement) ...@@ -1723,9 +1723,11 @@ int Statement_map::insert(Statement *statement)
void Statement_map::close_transient_cursors() void Statement_map::close_transient_cursors()
{ {
#ifdef TO_BE_IMPLEMENTED
Statement *stmt; Statement *stmt;
while ((stmt= transient_cursor_list.head())) while ((stmt= transient_cursor_list.head()))
stmt->close_cursor(); /* deletes itself from the list */ stmt->close_cursor(); /* deletes itself from the list */
#endif
} }
......
...@@ -737,10 +737,12 @@ class Query_arena ...@@ -737,10 +737,12 @@ class Query_arena
void set_query_arena(Query_arena *set); void set_query_arena(Query_arena *set);
void free_items(); 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. State of a single command executed against this connection.
...@@ -816,7 +818,7 @@ class Statement: public ilink, public Query_arena ...@@ -816,7 +818,7 @@ class Statement: public ilink, public Query_arena
*/ */
char *query; char *query;
uint32 query_length; // current query length uint32 query_length; // current query length
Cursor *cursor; Server_side_cursor *cursor;
public: public:
...@@ -833,8 +835,6 @@ class Statement: public ilink, public Query_arena ...@@ -833,8 +835,6 @@ class Statement: public ilink, public Query_arena
void restore_backup_statement(Statement *stmt, Statement *backup); void restore_backup_statement(Statement *stmt, Statement *backup);
/* return class type */ /* return class type */
virtual Type type() const; 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 ...@@ -886,9 +886,6 @@ class Statement_map
} }
hash_delete(&st_hash, (byte *) statement); 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 Close all cursors of this connection that use tables of a storage
engine that has transaction-specific state and therefore can not engine that has transaction-specific state and therefore can not
...@@ -1812,18 +1809,21 @@ class TMP_TABLE_PARAM :public Sql_alloc ...@@ -1812,18 +1809,21 @@ class TMP_TABLE_PARAM :public Sql_alloc
} }
}; };
class select_union :public select_result_interceptor { class select_union :public select_result_interceptor
public: {
TABLE *table;
TMP_TABLE_PARAM tmp_table_param; TMP_TABLE_PARAM tmp_table_param;
public:
TABLE *table;
select_union(TABLE *table_par); select_union() :table(0) {}
~select_union();
int prepare(List<Item> &list, SELECT_LEX_UNIT *u); int prepare(List<Item> &list, SELECT_LEX_UNIT *u);
bool send_data(List<Item> &items); bool send_data(List<Item> &items);
bool send_eof(); bool send_eof();
bool flush(); 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 */ /* 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 @@ ...@@ -27,7 +27,7 @@
/* /*
call given derived table processor (preparing or filling tables) Call given derived table processor (preparing or filling tables)
SYNOPSIS SYNOPSIS
mysql_handle_derived() mysql_handle_derived()
...@@ -36,7 +36,6 @@ ...@@ -36,7 +36,6 @@
RETURN RETURN
0 ok 0 ok
-1 Error
1 Error and error message given 1 Error and error message given
*/ */
...@@ -97,14 +96,14 @@ mysql_handle_derived(LEX *lex, int (*processor)(THD*, LEX*, TABLE_LIST*)) ...@@ -97,14 +96,14 @@ mysql_handle_derived(LEX *lex, int (*processor)(THD*, LEX*, TABLE_LIST*))
RETURN RETURN
0 ok 0 ok
1 Error 1 Error and an error message was given
-1 Error and error message given */
*/
int mysql_derived_prepare(THD *thd, LEX *lex, TABLE_LIST *orig_table_list) int mysql_derived_prepare(THD *thd, LEX *lex, TABLE_LIST *orig_table_list)
{ {
SELECT_LEX_UNIT *unit= orig_table_list->derived; SELECT_LEX_UNIT *unit= orig_table_list->derived;
int res= 0; int res= 0;
ulonglong create_options;
DBUG_ENTER("mysql_derived_prepare"); DBUG_ENTER("mysql_derived_prepare");
if (unit) if (unit)
{ {
...@@ -118,21 +117,18 @@ int mysql_derived_prepare(THD *thd, LEX *lex, TABLE_LIST *orig_table_list) ...@@ -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()) for (SELECT_LEX *sl= first_select; sl; sl= sl->next_select())
sl->context.outer_context= 0; sl->context.outer_context= 0;
if (!(derived_result= new select_union(0))) if (!(derived_result= new select_union))
DBUG_RETURN(1); // out of memory DBUG_RETURN(1); // out of memory
// st_select_lex_unit::prepare correctly work for single select // 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; goto exit;
if (check_duplicate_names(unit->types, 0)) if ((res= check_duplicate_names(unit->types, 0)))
{
res= -1;
goto exit; goto exit;
}
derived_result->tmp_table_param.init(); create_options= (first_select->options | thd->options |
derived_result->tmp_table_param.field_count= unit->types.elements; TMP_TABLE_ALL_COLUMNS);
/* /*
Temp table is created so that it hounours if UNION without ALL is to be Temp table is created so that it hounours if UNION without ALL is to be
processed processed
...@@ -143,18 +139,12 @@ int mysql_derived_prepare(THD *thd, LEX *lex, TABLE_LIST *orig_table_list) ...@@ -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 !unit->union_distinct->next_select() (i.e. it is union and last distinct
SELECT is last SELECT of UNION). SELECT is last SELECT of UNION).
*/ */
if (!(table= create_tmp_table(thd, &derived_result->tmp_table_param, if ((res= derived_result->create_result_table(thd, &unit->types, FALSE,
unit->types, (ORDER*) 0, create_options,
FALSE, 1, orig_table_list->alias)))
(first_select->options | thd->options |
TMP_TABLE_ALL_COLUMNS),
HA_POS_ERROR,
orig_table_list->alias)))
{
res= -1;
goto exit; goto exit;
}
derived_result->set_table(table); table= derived_result->table;
exit: exit:
/* Hide "Unknown column" or "Unknown function" error */ /* Hide "Unknown column" or "Unknown function" error */
...@@ -231,9 +221,8 @@ int mysql_derived_prepare(THD *thd, LEX *lex, TABLE_LIST *orig_table_list) ...@@ -231,9 +221,8 @@ int mysql_derived_prepare(THD *thd, LEX *lex, TABLE_LIST *orig_table_list)
RETURN RETURN
0 ok 0 ok
1 Error 1 Error and an error message was given
-1 Error and error message given */
*/
int mysql_derived_filling(THD *thd, LEX *lex, TABLE_LIST *orig_table_list) 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 { ...@@ -432,10 +432,6 @@ class st_select_lex_unit: public st_select_lex_node {
{ {
return my_reinterpret_cast(st_select_lex*)(slave); 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() st_select_lex_unit* next_unit()
{ {
return my_reinterpret_cast(st_select_lex_unit*)(next); return my_reinterpret_cast(st_select_lex_unit*)(next);
...@@ -445,8 +441,7 @@ class st_select_lex_unit: public st_select_lex_node { ...@@ -445,8 +441,7 @@ class st_select_lex_unit: public st_select_lex_node {
void exclude_tree(); void exclude_tree();
/* UNION methods */ /* UNION methods */
bool prepare(THD *thd, select_result *result, ulong additional_options, bool prepare(THD *thd, select_result *result, ulong additional_options);
const char *tmp_table_alias);
bool exec(); bool exec();
bool cleanup(); bool cleanup();
inline void unclean() { cleaned= 0; } inline void unclean() { cleaned= 0; }
...@@ -462,7 +457,10 @@ class st_select_lex_unit: public st_select_lex_node { ...@@ -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 void lex_start(THD *thd, uchar *buf, uint length);
friend int subselect_union_engine::exec(); friend int subselect_union_engine::exec();
List<Item> *get_unit_column_types();
}; };
typedef class st_select_lex_unit SELECT_LEX_UNIT; typedef class st_select_lex_unit SELECT_LEX_UNIT;
/* /*
......
...@@ -32,6 +32,8 @@ class Sql_alloc ...@@ -32,6 +32,8 @@ class Sql_alloc
{ {
return (void*) sql_alloc((uint) size); 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) static void *operator new(size_t size, MEM_ROOT *mem_root)
{ return (void*) alloc_root(mem_root, (uint) size); } { return (void*) alloc_root(mem_root, (uint) size); }
static void operator delete(void *ptr, size_t size) { TRASH(ptr, 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 ...@@ -101,7 +101,7 @@ enum enum_nested_loop_state
typedef enum_nested_loop_state typedef enum_nested_loop_state
(*Next_select_func)(JOIN *, struct st_join_table *, bool); (*Next_select_func)(JOIN *, struct st_join_table *, bool);
typedef int (*Read_record_func)(struct st_join_table *tab); typedef int (*Read_record_func)(struct st_join_table *tab);
Next_select_func setup_end_select_func(JOIN *join);
typedef struct st_join_table { typedef struct st_join_table {
TABLE *table; TABLE *table;
...@@ -140,6 +140,11 @@ typedef struct st_join_table { ...@@ -140,6 +140,11 @@ typedef struct st_join_table {
void cleanup(); void cleanup();
} JOIN_TAB; } 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 */ typedef struct st_position /* Used in find_best */
{ {
...@@ -372,58 +377,6 @@ class JOIN :public Sql_alloc ...@@ -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 { typedef struct st_select_check {
uint const_ref,reg_ref; uint const_ref,reg_ref;
} SELECT_CHECK; } SELECT_CHECK;
......
This diff is collapsed.
...@@ -378,7 +378,7 @@ bool mysql_create_view(THD *thd, ...@@ -378,7 +378,7 @@ bool mysql_create_view(THD *thd,
/* prepare select to resolve all fields */ /* prepare select to resolve all fields */
lex->view_prepare_mode= 1; 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 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) ...@@ -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)); 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 calculate md5 of query
......
...@@ -267,6 +267,9 @@ struct st_table { ...@@ -267,6 +267,9 @@ struct st_table {
GRANT_INFO grant; GRANT_INFO grant;
FILESORT_INFO sort; FILESORT_INFO sort;
TABLE_SHARE share_not_to_be_used; /* To be deleted when true shares */ 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() ...@@ -13828,8 +13828,11 @@ static void test_bug10760()
rc= mysql_stmt_execute(stmt); rc= mysql_stmt_execute(stmt);
check_execute(stmt, rc); check_execute(stmt, rc);
rc= mysql_query(mysql, "update t1 set id=id+100"); 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)); printf("Got error (as expected): %s\n", mysql_error(mysql));
/* /*
2: check that MyISAM tables used in cursors survive 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