Commit 689bb84f authored by pem@mysql.telia.com's avatar pem@mysql.telia.com

WL#962: Added simple, read-only, non-scrolling, asensitive cursors in SPs, using the

(updated) Protocol_cursor class.
Also did some bug fixes.
parent e8634f80
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
which dispatches on the command code (in Lex) to the corresponding code for which dispatches on the command code (in Lex) to the corresponding code for
executing that particular query. executing that particular query.
There are thre structures involved in the execution of a query which are of There are three structures involved in the execution of a query which are of
interest to the stored procedure implementation: interest to the stored procedure implementation:
- Lex (mentioned above) is the "compiled" query, that is the output from - Lex (mentioned above) is the "compiled" query, that is the output from
...@@ -186,7 +186,7 @@ ...@@ -186,7 +186,7 @@
stored in the table mysql.proc with the name and type as the key, the stored in the table mysql.proc with the name and type as the key, the
type being one of the enum ("procedure","function"). type being one of the enum ("procedure","function").
A PROCEDURE is just stored int the mysql.proc table. A FUNCTION has an A PROCEDURE is just stored in the mysql.proc table. A FUNCTION has an
additional requirement. They will be called in expressions with the same additional requirement. They will be called in expressions with the same
syntax as UDFs, so UDFs and stored FUNCTIONs share the namespace. Thus, syntax as UDFs, so UDFs and stored FUNCTIONs share the namespace. Thus,
we must make sure that we do not have UDFs and FUNCTIONs with the same we must make sure that we do not have UDFs and FUNCTIONs with the same
...@@ -293,7 +293,7 @@ ...@@ -293,7 +293,7 @@
So, stored functions must be handled in a simpilar way, and as a So, stored functions must be handled in a simpilar way, and as a
consequence, UDFs and functions must not have the same name. consequence, UDFs and functions must not have the same name.
- Detecting and parsing a FUNCTION invokation - Detecting and parsing a FUNCTION invocation
The existance of UDFs are checked during the lexical analysis (in The existance of UDFs are checked during the lexical analysis (in
sql_lex.cc:find_keyword()). This has the drawback that they must sql_lex.cc:find_keyword()). This has the drawback that they must
...@@ -319,7 +319,7 @@ ...@@ -319,7 +319,7 @@
"on-the-fly" during the execution of *another* statement. "on-the-fly" during the execution of *another* statement.
This makes things a lot more complicated compared to CALL: This makes things a lot more complicated compared to CALL:
- We can't read and parse the FUNCTION from the mysql.proc table at the - We can't read and parse the FUNCTION from the mysql.proc table at the
point of invokation; the server requires that all tables used are point of invocation; the server requires that all tables used are
opened and locked at the beginning of the query execution. opened and locked at the beginning of the query execution.
One "obvious" solution would be to simply push "mysql.proc" to the list One "obvious" solution would be to simply push "mysql.proc" to the list
of tables used by the query, but this implies a "join" with this table of tables used by the query, but this implies a "join" with this table
...@@ -478,6 +478,67 @@ ...@@ -478,6 +478,67 @@
7 sp_instr_hpop(2) 7 sp_instr_hpop(2)
- Cursors
For stored procedures to be really useful, you want to have cursors.
MySQL doesn't yet have "real" cursor support (with API and ODBC support,
allowing updating, arbitrary scrolling, etc), but a simple asensitive,
non-scrolling, read-only cursor can be implemented in SPs using the
class Protocol_cursor.
This class intecepts the creation and sending of results sets and instead
stores it in-memory, as MYSQL_FIELDS and MYSQL_ROWS (as in the client API).
To support this, we need the usual name binding support in sp_pcontext
(similar to variables and conditions) to keep track on declared cursor
names, and a corresponding run-time mechanism in sp_rcontext.
Cursors are lexically scoped like everything with a body or BEGIN/END
block, so they are pushed and poped as usual (see conditions and variables
above).
The basic operations on a cursor are OPEN, FETCH and CLOSE, which will
each have a corresponding instruction. In addition, we need instructions
to push a new cursor (this will encapsulate the LEX of the SELECT statement
of the cursor), and a pop instruction:
- sp_instr_cpush
Push a cursor to the sp_rcontext. This instruction contains the LEX
for the select statement
- sp_instr_cpop
Pop a number of cursors from the sp_rcontext.
- sp_instr_copen
Open a cursor: This will execute the select and get the result set
in a sepeate memroot.
- sp_instr_cfetch
Fetch the next row from the in-memory result set. The instruction
contains a list of the variables (frame offsets) to set.
- sp_instr_cclose
Free the result set.
A cursor is a separate class, sp_cursor (defined in sp_rcontex.h) which
encapsulates the basic operations used by the above instructions.
This class contains the LEX, Protocol_cursor object, and its memroot,
as well as the cursor's current state.
Compiling and executing is fairly straight-forward. sp_instr_copen is
a subclass of sp_instr_stmt and uses its mechanism to execute a
substatement.
- Example:
begin
declare x int;
declare c cursor for select a from t1;
open c;
fetch c into x;
close c;
end
Pos. Instruction
0 sp_instr_cpush('select a from ...')
1 sp_instr_copen(0) # The 0'th cursor
2 sp_instr_cfetch(0) # Contains the variable list
3 sp_instr_cclose(0)
4 sp_instr_cpop(1)
- Class and function APIs - Class and function APIs
This is an outline of the key types. Some types and other details This is an outline of the key types. Some types and other details
in the actual files have been omitted for readability. in the actual files have been omitted for readability.
...@@ -569,6 +630,18 @@ ...@@ -569,6 +630,18 @@
// Returns the handler count // Returns the handler count
uint handlers(); uint handlers();
// Push a cursor
void push_cursor(LEX_STRING *name);
// Find a cursor
my_bool find_cursor(LEX_STRING *name, uint *poff);
// Pop 'num' cursors
void pop_cursor(uint num);
// Return the number of cursors
uint cursors();
} }
...@@ -589,8 +662,9 @@ ...@@ -589,8 +662,9 @@
class sp_rcontext class sp_rcontext
{ {
// 'fsize' is the max size of the context, 'hmax' the number of handlers // 'fsize' is the max size of the context, 'hmax' the number of handlers,
sp_rcontext(uint fsize, uint hmax); // 'cmax' the number of cursors
sp_rcontext(uint fsize, uint hmax, , uint cmax);
// Push value (parameter) 'i' to the frame // Push value (parameter) 'i' to the frame
void push_item(Item *i); void push_item(Item *i);
...@@ -645,6 +719,18 @@ ...@@ -645,6 +719,18 @@
// Restore saved variables from to frame index 'fp' and up. // Restore saved variables from to frame index 'fp' and up.
void restore_variables(uint fp); void restore_variables(uint fp);
// Push a cursor for the statement (lex)
void push_cursor(LEX *lex);
// Pop 'count' cursors
void pop_cursors(uint count);
// Pop all cursors
void pop_all_cursors();
// Get the 'i'th cursor
sp_cursor *get_cursor(uint i);
} }
...@@ -709,6 +795,7 @@ ...@@ -709,6 +795,7 @@
bool suid, char *comment, uint commentlen); bool suid, char *comment, uint commentlen);
} }
- Instructions - Instructions
- The base class: - The base class:
...@@ -816,6 +903,55 @@ ...@@ -816,6 +903,55 @@
int execute(THD *thd, uint *nextp); int execute(THD *thd, uint *nextp);
} }
- Push a CURSOR
class sp_instr_cpush : public sp_instr_stmt
{
// Push a cursor for statement 'lex'
sp_instr_cpush(uint ip, LEX *lex)
int execute(THD *thd, uint *nextp);
}
- Pop CURSORs
class sp_instr_cpop : public sp_instr_stmt
{
// Pop 'count' cursors
sp_instr_cpop(uint ip, uint count)
int execute(THD *thd, uint *nextp);
}
- Open a CURSOR
class sp_instr_copen : public sp_instr_stmt
{
// Open the 'c'th cursor
sp_instr_copen(uint ip, uint c);
int execute(THD *thd, uint *nextp);
}
- Close a CURSOR
class sp_instr_cclose : public sp_instr
{
// Close the 'c'th cursor
sp_instr_cclose(uint ip, uint c);
int execute(THD *thd, uint *nextp);
}
- Fetch a row with CURSOR
class sp_instr_cfetch : public sp_instr
{
// Fetch next with the 'c'th cursor
sp_instr_cfetch(uint ip, uint c);
int execute(THD *thd, uint *nextp);
// Add a target variable for the fetch
void add_to_varlist(struct sp_pvar *var);
}
- Utility functions: sp.h - Utility functions: sp.h
#define SP_OK 0 #define SP_OK 0
......
...@@ -8,8 +8,7 @@ Summary of Not Yet Implemented: ...@@ -8,8 +8,7 @@ Summary of Not Yet Implemented:
- Access control - Access control
- Routine characteristics (mostly used for external languages) - Routine characteristics (mostly used for external languages)
- SQL-99 COMMIT (related to BEGIN/END) - SQL-99 COMMIT (related to BEGIN/END)
- DECLARE CURSOR ... - FOR-loops
- FOR-loops (as it requires cursors)
- CASCADE/RESTRICT for ALTER and DROP - CASCADE/RESTRICT for ALTER and DROP
- ALTER/DROP METHOD (as it implies User Defined Types) - ALTER/DROP METHOD (as it implies User Defined Types)
- SIGNAL and RESIGNAL, and UNDO handlers - SIGNAL and RESIGNAL, and UNDO handlers
...@@ -25,6 +24,7 @@ Summary of what's implemented: ...@@ -25,6 +24,7 @@ Summary of what's implemented:
- "Non-query" FUNCTIONs only - "Non-query" FUNCTIONs only
- Prepared SP caching - Prepared SP caching
- CONDITIONs and HANDLERs - CONDITIONs and HANDLERs
- Simple read-only CURSORs.
List of what's implemented: List of what's implemented:
...@@ -86,6 +86,11 @@ List of what's implemented: ...@@ -86,6 +86,11 @@ List of what's implemented:
The semantics of CONDITIONs is expanded to allow catching MySQL error The semantics of CONDITIONs is expanded to allow catching MySQL error
codes as well. UNDO handlers are not implemented (since we don't have codes as well. UNDO handlers are not implemented (since we don't have
SQL-99 style transaction control yet). SQL-99 style transaction control yet).
- Simple read-only CURSORs are implemented, but not yet any of the
optional arguments to DECLARE (SCROLL, SENSITIVE, etc) or FETCH
(NEXT, PRIOR, etc). Cursors are ASENSITIVE, READ-ONLY, non-SCROLLing.
(The additional syntax will be added for completeness, but for the
most part unsupported with the current underlying cursor mechanism.)
Closed questions: Closed questions:
......
...@@ -314,4 +314,12 @@ ...@@ -314,4 +314,12 @@
#define ER_SP_COND_MISMATCH 1295 #define ER_SP_COND_MISMATCH 1295
#define ER_SP_NORETURN 1296 #define ER_SP_NORETURN 1296
#define ER_SP_NORETURNEND 1297 #define ER_SP_NORETURNEND 1297
#define ER_ERROR_MESSAGES 298 #define ER_SP_BAD_CURSOR_QUERY 1298
#define ER_SP_BAD_CURSOR_SELECT 1299
#define ER_SP_CURSOR_MISMATCH 1300
#define ER_SP_CURSOR_ALREADY_OPEN 1301
#define ER_SP_CURSOR_NOT_OPEN 1302
#define ER_SP_UNDECLARED_VAR 1303
#define ER_SP_WRONG_NO_OF_FETCH_ARGS 1304
#define ER_SP_FETCH_NO_DATA 1305
#define ER_ERROR_MESSAGES 306
...@@ -56,7 +56,7 @@ sqlsources = derror.cc field.cc field_conv.cc filesort.cc \ ...@@ -56,7 +56,7 @@ sqlsources = derror.cc field.cc field_conv.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 \ spatial.cc gstream.cc sql_help.cc protocol_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
libmysqld_int_a_SOURCES= $(libmysqld_sources) $(libmysqlsources) $(sqlsources) libmysqld_int_a_SOURCES= $(libmysqld_sources) $(libmysqlsources) $(sqlsources)
......
...@@ -138,3 +138,85 @@ end; ...@@ -138,3 +138,85 @@ end;
select f(10); select f(10);
ERROR HY000: FUNCTION f ended without RETURN ERROR HY000: FUNCTION f ended without RETURN
drop function f; drop function f;
create procedure p()
begin
declare c cursor for insert into test.t1 values ("foo", 42);
open c;
close c;
end;
ERROR HY000: Cursor statement must be a SELECT
create procedure p()
begin
declare x int;
declare c cursor for select * into x from test.t limit 1;
open c;
close c;
end;
ERROR HY000: Cursor SELECT must not have INTO
create procedure p()
begin
declare c cursor for select * from test.t;
open cc;
close c;
end;
ERROR HY000: Undefined CURSOR: cc
drop table if exists t1;
create table t1 (val int);
create procedure p()
begin
declare c cursor for select * from test.t1;
open c;
open c;
close c;
end;
call p();
ERROR HY000: Cursor is already open
drop procedure p;
create procedure p()
begin
declare c cursor for select * from test.t1;
open c;
close c;
close c;
end;
call p();
ERROR HY000: Cursor is not open
drop procedure p;
drop table t1;
drop table if exists t1;
create table t1 (val int, x float);
insert into t1 values (42, 3.1), (19, 1.2);
create procedure p()
begin
declare c cursor for select * from t1;
declare x int;
open c;
fetch c into x, y;
close c;
end;
ERROR HY000: Undeclared variable: y
create procedure p()
begin
declare c cursor for select * from t1;
declare x int;
open c;
fetch c into x;
close c;
end;
call p();
ERROR HY000: Wrong number of FETCH variables
drop procedure p;
create procedure p()
begin
declare c cursor for select * from t1;
declare x int;
declare y float;
declare z int;
open c;
fetch c into x, y, z;
close c;
end;
call p();
ERROR HY000: Wrong number of FETCH variables
drop procedure p;
drop table t1;
...@@ -500,6 +500,33 @@ id data ...@@ -500,6 +500,33 @@ id data
hndlr3 13 hndlr3 13
delete from t1; delete from t1;
drop procedure hndlr3; drop procedure hndlr3;
create procedure cur1()
begin
declare done int default 0;
declare continue handler for 1305 set done = 1;
declare c cursor for select * from test.t2;
declare a char(16);
declare b int;
declare c double;
open c;
repeat
fetch c into a, b, c;
if not done then
insert into test.t1 values (a, b+c);
end if;
until done end repeat;
close c;
end;
insert into t2 values ("foo", 42, -1.9), ("bar", 3, 12.1), ("zap", 666, -3.14);
call cur1();
select * from t1;
id data
foo 40
bar 15
zap 663
delete from t1;
delete from t2;
drop procedure cur1;
create procedure bug822(a_id char(16), a_data int) create procedure bug822(a_id char(16), a_data int)
begin begin
declare n int; declare n int;
......
...@@ -194,4 +194,110 @@ select f(10)| ...@@ -194,4 +194,110 @@ select f(10)|
drop function f| drop function f|
--error 1298
create procedure p()
begin
declare c cursor for insert into test.t1 values ("foo", 42);
open c;
close c;
end|
--error 1299
create procedure p()
begin
declare x int;
declare c cursor for select * into x from test.t limit 1;
open c;
close c;
end|
--error 1300
create procedure p()
begin
declare c cursor for select * from test.t;
open cc;
close c;
end|
--disable_warnings
drop table if exists t1|
--enable_warnings
create table t1 (val int)|
create procedure p()
begin
declare c cursor for select * from test.t1;
open c;
open c;
close c;
end|
--error 1301
call p()|
drop procedure p|
create procedure p()
begin
declare c cursor for select * from test.t1;
open c;
close c;
close c;
end|
--error 1302
call p()|
drop procedure p|
drop table t1|
--disable_warnings
drop table if exists t1|
--enable_warnings
create table t1 (val int, x float)|
insert into t1 values (42, 3.1), (19, 1.2)|
--error 1303
create procedure p()
begin
declare c cursor for select * from t1;
declare x int;
open c;
fetch c into x, y;
close c;
end|
create procedure p()
begin
declare c cursor for select * from t1;
declare x int;
open c;
fetch c into x;
close c;
end|
--error 1304
call p()|
drop procedure p|
create procedure p()
begin
declare c cursor for select * from t1;
declare x int;
declare y float;
declare z int;
open c;
fetch c into x, y, z;
close c;
end|
--error 1304
call p()|
drop procedure p|
drop table t1|
delimiter ;| delimiter ;|
...@@ -588,7 +588,38 @@ select * from t1| ...@@ -588,7 +588,38 @@ select * from t1|
delete from t1| delete from t1|
drop procedure hndlr3| drop procedure hndlr3|
#
# Cursors
#
create procedure cur1()
begin
declare done int default 0;
declare continue handler for 1305 set done = 1;
declare c cursor for select * from test.t2;
declare a char(16);
declare b int;
declare c double;
open c;
repeat
fetch c into a, b, c;
if not done then
insert into test.t1 values (a, b+c);
end if;
until done end repeat;
close c;
end|
insert into t2 values ("foo", 42, -1.9), ("bar", 3, 12.1), ("zap", 666, -3.14)|
call cur1()|
select * from t1|
delete from t1|
delete from t2|
drop procedure cur1|
#
# BUG#822
#
create procedure bug822(a_id char(16), a_data int) create procedure bug822(a_id char(16), a_data int)
begin begin
declare n int; declare n int;
......
...@@ -1145,12 +1145,3 @@ bool Protocol_prep::store_time(TIME *tm) ...@@ -1145,12 +1145,3 @@ bool Protocol_prep::store_time(TIME *tm)
buff[0]=(char) length; // Length is stored first buff[0]=(char) length; // Length is stored first
return packet->append(buff, length+1, PACKET_BUFFET_EXTRA_ALLOC); return packet->append(buff, length+1, PACKET_BUFFET_EXTRA_ALLOC);
} }
#ifdef EMBEDDED_LIBRARY
/* Should be removed when we define the Protocol_cursor's future */
bool Protocol_cursor::write()
{
return Protocol_simple::write();
}
#endif
...@@ -47,17 +47,13 @@ public: ...@@ -47,17 +47,13 @@ public:
Protocol(THD *thd) { init(thd); } Protocol(THD *thd) { init(thd); }
virtual ~Protocol() {} virtual ~Protocol() {}
void init(THD* thd); void init(THD* thd);
bool send_fields(List<Item> *list, uint flag); virtual bool send_fields(List<Item> *list, uint flag);
bool send_records_num(List<Item> *list, ulonglong records); bool send_records_num(List<Item> *list, ulonglong records);
bool store(I_List<i_string> *str_list); bool store(I_List<i_string> *str_list);
bool store(const char *from, CHARSET_INFO *cs); bool store(const char *from, CHARSET_INFO *cs);
String *storage_packet() { return packet; } String *storage_packet() { return packet; }
inline void free() { packet->free(); } inline void free() { packet->free(); }
#ifndef EMBEDDED_LIBRARY
bool write();
#else
virtual bool write(); virtual bool write();
#endif
inline bool store(uint32 from) inline bool store(uint32 from)
{ return store_long((longlong) from); } { return store_long((longlong) from); }
inline bool store(longlong from) inline bool store(longlong from)
...@@ -158,6 +154,7 @@ public: ...@@ -158,6 +154,7 @@ public:
Protocol_cursor(THD *thd, MEM_ROOT *ini_alloc) :Protocol_simple(thd), alloc(ini_alloc) {} Protocol_cursor(THD *thd, MEM_ROOT *ini_alloc) :Protocol_simple(thd), alloc(ini_alloc) {}
bool prepare_for_send(List<Item> *item_list) bool prepare_for_send(List<Item> *item_list)
{ {
row_count= 0;
fields= NULL; fields= NULL;
data= NULL; data= NULL;
prev_record= &data; prev_record= &data;
...@@ -165,6 +162,7 @@ public: ...@@ -165,6 +162,7 @@ public:
} }
bool send_fields(List<Item> *list, uint flag); bool send_fields(List<Item> *list, uint flag);
bool write(); 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);
......
...@@ -51,6 +51,7 @@ bool Protocol_cursor::send_fields(List<Item> *list, uint flag) ...@@ -51,6 +51,7 @@ bool Protocol_cursor::send_fields(List<Item> *list, uint flag)
client_field->name= strdup_root(alloc, server_field.col_name); client_field->name= strdup_root(alloc, server_field.col_name);
client_field->org_table= strdup_root(alloc, server_field.org_table_name); client_field->org_table= strdup_root(alloc, server_field.org_table_name);
client_field->org_name= strdup_root(alloc, server_field.org_col_name); client_field->org_name= strdup_root(alloc, server_field.org_col_name);
client_field->catalog= strdup_root(alloc, "");
client_field->length= server_field.length; client_field->length= server_field.length;
client_field->type= server_field.type; client_field->type= server_field.type;
client_field->flags= server_field.flags; client_field->flags= server_field.flags;
...@@ -60,6 +61,7 @@ bool Protocol_cursor::send_fields(List<Item> *list, uint flag) ...@@ -60,6 +61,7 @@ bool Protocol_cursor::send_fields(List<Item> *list, uint flag)
client_field->name_length= strlen(client_field->name); client_field->name_length= strlen(client_field->name);
client_field->org_name_length= strlen(client_field->org_name); client_field->org_name_length= strlen(client_field->org_name);
client_field->org_table_length= strlen(client_field->org_table); client_field->org_table_length= strlen(client_field->org_table);
client_field->catalog_length= 0;
client_field->charsetnr= server_field.charsetnr; client_field->charsetnr= server_field.charsetnr;
if (INTERNAL_NUM_FIELD(client_field)) if (INTERNAL_NUM_FIELD(client_field))
...@@ -106,11 +108,11 @@ bool Protocol_cursor::write() ...@@ -106,11 +108,11 @@ bool Protocol_cursor::write()
data= (byte **)(new_record + 1); data= (byte **)(new_record + 1);
new_record->data= (char **)data; new_record->data= (char **)data;
to= (byte *)(fields + field_count + 1); to= (byte *)data + (field_count + 1)*sizeof(char *);
for (; cur_field < fields_end; ++cur_field, ++data) for (; cur_field < fields_end; ++cur_field, ++data)
{ {
if ((len=net_field_length((uchar **)&cp))) if ((len= net_field_length((uchar **)&cp)) == 0)
{ {
*data= 0; *data= 0;
} }
...@@ -121,6 +123,7 @@ bool Protocol_cursor::write() ...@@ -121,6 +123,7 @@ bool Protocol_cursor::write()
// TODO error signal send_error(thd, CR_MALFORMED_PACKET); // TODO error signal send_error(thd, CR_MALFORMED_PACKET);
return TRUE; return TRUE;
} }
*data= to;
memcpy(to,(char*) cp,len); memcpy(to,(char*) cp,len);
to[len]=0; to[len]=0;
to+=len+1; to+=len+1;
...@@ -129,6 +132,7 @@ bool Protocol_cursor::write() ...@@ -129,6 +132,7 @@ bool Protocol_cursor::write()
cur_field->max_length=len; cur_field->max_length=len;
} }
} }
*data= 0;
*prev_record= new_record; *prev_record= new_record;
prev_record= &new_record->next; prev_record= &new_record->next;
...@@ -139,5 +143,3 @@ bool Protocol_cursor::write() ...@@ -139,5 +143,3 @@ bool Protocol_cursor::write()
// TODO error signal send_error(thd, ER_OUT_OF_RESOURCES); // TODO error signal send_error(thd, ER_OUT_OF_RESOURCES);
return TRUE; return TRUE;
} }
...@@ -310,3 +310,11 @@ character-set=latin2 ...@@ -310,3 +310,11 @@ character-set=latin2
"Undefined CONDITION: %s" "Undefined CONDITION: %s"
"No RETURN found in FUNCTION %s" "No RETURN found in FUNCTION %s"
"FUNCTION %s ended without RETURN" "FUNCTION %s ended without RETURN"
"Cursor statement must be a SELECT"
"Cursor SELECT must not have INTO"
"Undefined CURSOR: %s"
"Cursor is already open"
"Cursor is not open"
"Undeclared variable: %s"
"Wrong number of FETCH variables"
"No data to FETCH"
...@@ -304,3 +304,11 @@ character-set=latin1 ...@@ -304,3 +304,11 @@ character-set=latin1
"Undefined CONDITION: %s" "Undefined CONDITION: %s"
"No RETURN found in FUNCTION %s" "No RETURN found in FUNCTION %s"
"FUNCTION %s ended without RETURN" "FUNCTION %s ended without RETURN"
"Cursor statement must be a SELECT"
"Cursor SELECT must not have INTO"
"Undefined CURSOR: %s"
"Cursor is already open"
"Cursor is not open"
"Undeclared variable: %s"
"Wrong number of FETCH variables"
"No data to FETCH"
...@@ -312,3 +312,11 @@ character-set=latin1 ...@@ -312,3 +312,11 @@ character-set=latin1
"Undefined CONDITION: %s" "Undefined CONDITION: %s"
"No RETURN found in FUNCTION %s" "No RETURN found in FUNCTION %s"
"FUNCTION %s ended without RETURN" "FUNCTION %s ended without RETURN"
"Cursor statement must be a SELECT"
"Cursor SELECT must not have INTO"
"Undefined CURSOR: %s"
"Cursor is already open"
"Cursor is not open"
"Undeclared variable: %s"
"Wrong number of FETCH variables"
"No data to FETCH"
...@@ -301,3 +301,11 @@ character-set=latin1 ...@@ -301,3 +301,11 @@ character-set=latin1
"Undefined CONDITION: %s" "Undefined CONDITION: %s"
"No RETURN found in FUNCTION %s" "No RETURN found in FUNCTION %s"
"FUNCTION %s ended without RETURN" "FUNCTION %s ended without RETURN"
"Cursor statement must be a SELECT"
"Cursor SELECT must not have INTO"
"Undefined CURSOR: %s"
"Cursor is already open"
"Cursor is not open"
"Undeclared variable: %s"
"Wrong number of FETCH variables"
"No data to FETCH"
...@@ -306,3 +306,11 @@ character-set=latin7 ...@@ -306,3 +306,11 @@ character-set=latin7
"Undefined CONDITION: %s" "Undefined CONDITION: %s"
"No RETURN found in FUNCTION %s" "No RETURN found in FUNCTION %s"
"FUNCTION %s ended without RETURN" "FUNCTION %s ended without RETURN"
"Cursor statement must be a SELECT"
"Cursor SELECT must not have INTO"
"Undefined CURSOR: %s"
"Cursor is already open"
"Cursor is not open"
"Undeclared variable: %s"
"Wrong number of FETCH variables"
"No data to FETCH"
...@@ -301,3 +301,11 @@ character-set=latin1 ...@@ -301,3 +301,11 @@ character-set=latin1
"Undefined CONDITION: %s" "Undefined CONDITION: %s"
"No RETURN found in FUNCTION %s" "No RETURN found in FUNCTION %s"
"FUNCTION %s ended without RETURN" "FUNCTION %s ended without RETURN"
"Cursor statement must be a SELECT"
"Cursor SELECT must not have INTO"
"Undefined CURSOR: %s"
"Cursor is already open"
"Cursor is not open"
"Undeclared variable: %s"
"Wrong number of FETCH variables"
"No data to FETCH"
...@@ -310,3 +310,11 @@ character-set=latin1 ...@@ -310,3 +310,11 @@ character-set=latin1
"Undefined CONDITION: %s" "Undefined CONDITION: %s"
"No RETURN found in FUNCTION %s" "No RETURN found in FUNCTION %s"
"FUNCTION %s ended without RETURN" "FUNCTION %s ended without RETURN"
"Cursor statement must be a SELECT"
"Cursor SELECT must not have INTO"
"Undefined CURSOR: %s"
"Cursor is already open"
"Cursor is not open"
"Undeclared variable: %s"
"Wrong number of FETCH variables"
"No data to FETCH"
...@@ -301,3 +301,11 @@ character-set=greek ...@@ -301,3 +301,11 @@ character-set=greek
"Undefined CONDITION: %s" "Undefined CONDITION: %s"
"No RETURN found in FUNCTION %s" "No RETURN found in FUNCTION %s"
"FUNCTION %s ended without RETURN" "FUNCTION %s ended without RETURN"
"Cursor statement must be a SELECT"
"Cursor SELECT must not have INTO"
"Undefined CURSOR: %s"
"Cursor is already open"
"Cursor is not open"
"Undeclared variable: %s"
"Wrong number of FETCH variables"
"No data to FETCH"
...@@ -303,3 +303,11 @@ character-set=latin2 ...@@ -303,3 +303,11 @@ character-set=latin2
"Undefined CONDITION: %s" "Undefined CONDITION: %s"
"No RETURN found in FUNCTION %s" "No RETURN found in FUNCTION %s"
"FUNCTION %s ended without RETURN" "FUNCTION %s ended without RETURN"
"Cursor statement must be a SELECT"
"Cursor SELECT must not have INTO"
"Undefined CURSOR: %s"
"Cursor is already open"
"Cursor is not open"
"Undeclared variable: %s"
"Wrong number of FETCH variables"
"No data to FETCH"
...@@ -301,3 +301,11 @@ character-set=latin1 ...@@ -301,3 +301,11 @@ character-set=latin1
"Undefined CONDITION: %s" "Undefined CONDITION: %s"
"No RETURN found in FUNCTION %s" "No RETURN found in FUNCTION %s"
"FUNCTION %s ended without RETURN" "FUNCTION %s ended without RETURN"
"Cursor statement must be a SELECT"
"Cursor SELECT must not have INTO"
"Undefined CURSOR: %s"
"Cursor is already open"
"Cursor is not open"
"Undeclared variable: %s"
"Wrong number of FETCH variables"
"No data to FETCH"
...@@ -303,3 +303,11 @@ character-set=ujis ...@@ -303,3 +303,11 @@ character-set=ujis
"Undefined CONDITION: %s" "Undefined CONDITION: %s"
"No RETURN found in FUNCTION %s" "No RETURN found in FUNCTION %s"
"FUNCTION %s ended without RETURN" "FUNCTION %s ended without RETURN"
"Cursor statement must be a SELECT"
"Cursor SELECT must not have INTO"
"Undefined CURSOR: %s"
"Cursor is already open"
"Cursor is not open"
"Undeclared variable: %s"
"Wrong number of FETCH variables"
"No data to FETCH"
...@@ -301,3 +301,11 @@ character-set=euckr ...@@ -301,3 +301,11 @@ character-set=euckr
"Undefined CONDITION: %s" "Undefined CONDITION: %s"
"No RETURN found in FUNCTION %s" "No RETURN found in FUNCTION %s"
"FUNCTION %s ended without RETURN" "FUNCTION %s ended without RETURN"
"Cursor statement must be a SELECT"
"Cursor SELECT must not have INTO"
"Undefined CURSOR: %s"
"Cursor is already open"
"Cursor is not open"
"Undeclared variable: %s"
"Wrong number of FETCH variables"
"No data to FETCH"
...@@ -303,3 +303,11 @@ character-set=latin1 ...@@ -303,3 +303,11 @@ character-set=latin1
"Undefined CONDITION: %s" "Undefined CONDITION: %s"
"No RETURN found in FUNCTION %s" "No RETURN found in FUNCTION %s"
"FUNCTION %s ended without RETURN" "FUNCTION %s ended without RETURN"
"Cursor statement must be a SELECT"
"Cursor SELECT must not have INTO"
"Undefined CURSOR: %s"
"Cursor is already open"
"Cursor is not open"
"Undeclared variable: %s"
"Wrong number of FETCH variables"
"No data to FETCH"
...@@ -303,3 +303,11 @@ character-set=latin1 ...@@ -303,3 +303,11 @@ character-set=latin1
"Undefined CONDITION: %s" "Undefined CONDITION: %s"
"No RETURN found in FUNCTION %s" "No RETURN found in FUNCTION %s"
"FUNCTION %s ended without RETURN" "FUNCTION %s ended without RETURN"
"Cursor statement must be a SELECT"
"Cursor SELECT must not have INTO"
"Undefined CURSOR: %s"
"Cursor is already open"
"Cursor is not open"
"Undeclared variable: %s"
"Wrong number of FETCH variables"
"No data to FETCH"
...@@ -305,3 +305,11 @@ character-set=latin2 ...@@ -305,3 +305,11 @@ character-set=latin2
"Undefined CONDITION: %s" "Undefined CONDITION: %s"
"No RETURN found in FUNCTION %s" "No RETURN found in FUNCTION %s"
"FUNCTION %s ended without RETURN" "FUNCTION %s ended without RETURN"
"Cursor statement must be a SELECT"
"Cursor SELECT must not have INTO"
"Undefined CURSOR: %s"
"Cursor is already open"
"Cursor is not open"
"Undeclared variable: %s"
"Wrong number of FETCH variables"
"No data to FETCH"
...@@ -302,3 +302,11 @@ character-set=latin1 ...@@ -302,3 +302,11 @@ character-set=latin1
"Undefined CONDITION: %s" "Undefined CONDITION: %s"
"No RETURN found in FUNCTION %s" "No RETURN found in FUNCTION %s"
"FUNCTION %s ended without RETURN" "FUNCTION %s ended without RETURN"
"Cursor statement must be a SELECT"
"Cursor SELECT must not have INTO"
"Undefined CURSOR: %s"
"Cursor is already open"
"Cursor is not open"
"Undeclared variable: %s"
"Wrong number of FETCH variables"
"No data to FETCH"
...@@ -305,3 +305,11 @@ character-set=latin2 ...@@ -305,3 +305,11 @@ character-set=latin2
"Undefined CONDITION: %s" "Undefined CONDITION: %s"
"No RETURN found in FUNCTION %s" "No RETURN found in FUNCTION %s"
"FUNCTION %s ended without RETURN" "FUNCTION %s ended without RETURN"
"Cursor statement must be a SELECT"
"Cursor SELECT must not have INTO"
"Undefined CURSOR: %s"
"Cursor is already open"
"Cursor is not open"
"Undeclared variable: %s"
"Wrong number of FETCH variables"
"No data to FETCH"
...@@ -303,3 +303,11 @@ character-set=koi8r ...@@ -303,3 +303,11 @@ character-set=koi8r
"Undefined CONDITION: %s" "Undefined CONDITION: %s"
"No RETURN found in FUNCTION %s" "No RETURN found in FUNCTION %s"
"FUNCTION %s ended without RETURN" "FUNCTION %s ended without RETURN"
"Cursor statement must be a SELECT"
"Cursor SELECT must not have INTO"
"Undefined CURSOR: %s"
"Cursor is already open"
"Cursor is not open"
"Undeclared variable: %s"
"Wrong number of FETCH variables"
"No data to FETCH"
...@@ -296,3 +296,11 @@ character-set=cp1250 ...@@ -296,3 +296,11 @@ character-set=cp1250
"Undefined CONDITION: %s" "Undefined CONDITION: %s"
"No RETURN found in FUNCTION %s" "No RETURN found in FUNCTION %s"
"FUNCTION %s ended without RETURN" "FUNCTION %s ended without RETURN"
"Cursor statement must be a SELECT"
"Cursor SELECT must not have INTO"
"Undefined CURSOR: %s"
"Cursor is already open"
"Cursor is not open"
"Undeclared variable: %s"
"Wrong number of FETCH variables"
"No data to FETCH"
...@@ -309,3 +309,11 @@ character-set=latin2 ...@@ -309,3 +309,11 @@ character-set=latin2
"Undefined CONDITION: %s" "Undefined CONDITION: %s"
"No RETURN found in FUNCTION %s" "No RETURN found in FUNCTION %s"
"FUNCTION %s ended without RETURN" "FUNCTION %s ended without RETURN"
"Cursor statement must be a SELECT"
"Cursor SELECT must not have INTO"
"Undefined CURSOR: %s"
"Cursor is already open"
"Cursor is not open"
"Undeclared variable: %s"
"Wrong number of FETCH variables"
"No data to FETCH"
...@@ -303,3 +303,11 @@ character-set=latin1 ...@@ -303,3 +303,11 @@ character-set=latin1
"Undefined CONDITION: %s" "Undefined CONDITION: %s"
"No RETURN found in FUNCTION %s" "No RETURN found in FUNCTION %s"
"FUNCTION %s ended without RETURN" "FUNCTION %s ended without RETURN"
"Cursor statement must be a SELECT"
"Cursor SELECT must not have INTO"
"Undefined CURSOR: %s"
"Cursor is already open"
"Cursor is not open"
"Undeclared variable: %s"
"Wrong number of FETCH variables"
"No data to FETCH"
...@@ -301,3 +301,11 @@ character-set=latin1 ...@@ -301,3 +301,11 @@ character-set=latin1
"Undefined CONDITION: %s" "Undefined CONDITION: %s"
"No RETURN found in FUNCTION %s" "No RETURN found in FUNCTION %s"
"FUNCTION %s ended without RETURN" "FUNCTION %s ended without RETURN"
"Cursor statement must be a SELECT"
"Cursor SELECT must not have INTO"
"Undefined CURSOR: %s"
"Cursor is already open"
"Cursor is not open"
"Undeclared variable: %s"
"Wrong number of FETCH variables"
"No data to FETCH"
...@@ -306,3 +306,11 @@ character-set=koi8u ...@@ -306,3 +306,11 @@ character-set=koi8u
"Undefined CONDITION: %s" "Undefined CONDITION: %s"
"No RETURN found in FUNCTION %s" "No RETURN found in FUNCTION %s"
"FUNCTION %s ended without RETURN" "FUNCTION %s ended without RETURN"
"Cursor statement must be a SELECT"
"Cursor SELECT must not have INTO"
"Undefined CURSOR: %s"
"Cursor is already open"
"Cursor is not open"
"Undeclared variable: %s"
"Wrong number of FETCH variables"
"No data to FETCH"
...@@ -289,6 +289,7 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount, Item **resp) ...@@ -289,6 +289,7 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount, Item **resp)
uint csize = m_pcont->max_framesize(); uint csize = m_pcont->max_framesize();
uint params = m_pcont->params(); uint params = m_pcont->params();
uint hmax = m_pcont->handlers(); uint hmax = m_pcont->handlers();
uint cmax = m_pcont->cursors();
sp_rcontext *octx = thd->spcont; sp_rcontext *octx = thd->spcont;
sp_rcontext *nctx = NULL; sp_rcontext *nctx = NULL;
uint i; uint i;
...@@ -304,7 +305,7 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount, Item **resp) ...@@ -304,7 +305,7 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount, Item **resp)
} }
// QQ Should have some error checking here? (types, etc...) // QQ Should have some error checking here? (types, etc...)
nctx= new sp_rcontext(csize, hmax); nctx= new sp_rcontext(csize, hmax, cmax);
for (i= 0 ; i < params && i < argcount ; i++) for (i= 0 ; i < params && i < argcount ; i++)
{ {
sp_pvar_t *pvar = m_pcont->find_pvar(i); sp_pvar_t *pvar = m_pcont->find_pvar(i);
...@@ -335,6 +336,7 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount, Item **resp) ...@@ -335,6 +336,7 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount, Item **resp)
} }
} }
nctx->pop_all_cursors(); // To avoid memory leaks after an error
thd->spcont= octx; thd->spcont= octx;
DBUG_RETURN(ret); DBUG_RETURN(ret);
} }
...@@ -349,6 +351,7 @@ sp_head::execute_procedure(THD *thd, List<Item> *args) ...@@ -349,6 +351,7 @@ sp_head::execute_procedure(THD *thd, List<Item> *args)
uint csize = m_pcont->max_framesize(); uint csize = m_pcont->max_framesize();
uint params = m_pcont->params(); uint params = m_pcont->params();
uint hmax = m_pcont->handlers(); uint hmax = m_pcont->handlers();
uint cmax = m_pcont->cursors();
sp_rcontext *octx = thd->spcont; sp_rcontext *octx = thd->spcont;
sp_rcontext *nctx = NULL; sp_rcontext *nctx = NULL;
my_bool tmp_octx = FALSE; // True if we have allocated a temporary octx my_bool tmp_octx = FALSE; // True if we have allocated a temporary octx
...@@ -360,17 +363,17 @@ sp_head::execute_procedure(THD *thd, List<Item> *args) ...@@ -360,17 +363,17 @@ sp_head::execute_procedure(THD *thd, List<Item> *args)
DBUG_RETURN(-1); DBUG_RETURN(-1);
} }
if (csize > 0 || hmax > 0) if (csize > 0 || hmax > 0 || cmax > 0)
{ {
uint i; uint i;
List_iterator_fast<Item> li(*args); List_iterator_fast<Item> li(*args);
Item *it; Item *it;
nctx = new sp_rcontext(csize, hmax); nctx= new sp_rcontext(csize, hmax, cmax);
if (! octx) if (! octx)
{ // Create a temporary old context { // Create a temporary old context
octx = new sp_rcontext(csize, hmax); octx= new sp_rcontext(csize, hmax, cmax);
tmp_octx = TRUE; tmp_octx= TRUE;
} }
// QQ: Should do type checking? // QQ: Should do type checking?
for (i = 0 ; (it= li++) && i < params ; i++) for (i = 0 ; (it= li++) && i < params ; i++)
...@@ -443,12 +446,13 @@ sp_head::execute_procedure(THD *thd, List<Item> *args) ...@@ -443,12 +446,13 @@ sp_head::execute_procedure(THD *thd, List<Item> *args)
} }
} }
} }
}
if (tmp_octx) if (tmp_octx)
thd->spcont= NULL; octx= NULL;
else if (nctx)
nctx->pop_all_cursors(); // To avoid memory leaks after an error
thd->spcont= octx; thd->spcont= octx;
}
DBUG_RETURN(ret); DBUG_RETURN(ret);
} }
...@@ -596,12 +600,20 @@ sp_instr_stmt::execute(THD *thd, uint *nextp) ...@@ -596,12 +600,20 @@ sp_instr_stmt::execute(THD *thd, uint *nextp)
{ {
DBUG_ENTER("sp_instr_stmt::execute"); DBUG_ENTER("sp_instr_stmt::execute");
DBUG_PRINT("info", ("command: %d", m_lex->sql_command)); DBUG_PRINT("info", ("command: %d", m_lex->sql_command));
int res= exec_stmt(thd, m_lex);
*nextp = m_ip+1;
DBUG_RETURN(res);
}
int
sp_instr_stmt::exec_stmt(THD *thd, LEX *lex)
{
LEX *olex; // The other lex LEX *olex; // The other lex
Item *freelist; Item *freelist;
int res; int res;
olex= thd->lex; // Save the other lex olex= thd->lex; // Save the other lex
thd->lex= m_lex; // Use my own lex thd->lex= lex; // Use my own lex
thd->lex->thd = thd; // QQ Not reentrant! thd->lex->thd = thd; // QQ Not reentrant!
thd->lex->unit.thd= thd; // QQ Not reentrant thd->lex->unit.thd= thd; // QQ Not reentrant
freelist= thd->free_list; freelist= thd->free_list;
...@@ -610,10 +622,19 @@ sp_instr_stmt::execute(THD *thd, uint *nextp) ...@@ -610,10 +622,19 @@ sp_instr_stmt::execute(THD *thd, uint *nextp)
// Copy WHERE clause pointers to avoid damaging by optimisation // Copy WHERE clause pointers to avoid damaging by optimisation
// Also clear ref_pointer_arrays. // Also clear ref_pointer_arrays.
for (SELECT_LEX *sl= m_lex->all_selects_list ; for (SELECT_LEX *sl= lex->all_selects_list ;
sl ; sl ;
sl= sl->next_select_in_list()) sl= sl->next_select_in_list())
{ {
List_iterator_fast<Item> li(sl->item_list);
if (sl->with_wild)
{
// Copy item_list
sl->item_list_copy.empty();
while (Item *it= li++)
sl->item_list_copy.push_back(it);
}
sl->ref_pointer_array= 0; sl->ref_pointer_array= 0;
if (sl->prep_where) if (sl->prep_where)
sl->where= sl->prep_where->copy_andor_structure(thd); sl->where= sl->prep_where->copy_andor_structure(thd);
...@@ -628,11 +649,22 @@ sp_instr_stmt::execute(THD *thd, uint *nextp) ...@@ -628,11 +649,22 @@ sp_instr_stmt::execute(THD *thd, uint *nextp)
close_thread_tables(thd); /* Free tables */ close_thread_tables(thd); /* Free tables */
} }
for (SELECT_LEX *sl= lex->all_selects_list ;
sl ;
sl= sl->next_select_in_list())
{
if (sl->with_wild)
{
// Restore item_list
sl->item_list.empty();
while (Item *it= sl->item_list_copy.pop())
sl->item_list.push_back(it);
}
}
thd->lex= olex; // Restore the other lex thd->lex= olex; // Restore the other lex
thd->free_list= freelist; thd->free_list= freelist;
*nextp = m_ip+1; return res;
DBUG_RETURN(res);
} }
// //
...@@ -747,3 +779,96 @@ sp_instr_hreturn::execute(THD *thd, uint *nextp) ...@@ -747,3 +779,96 @@ sp_instr_hreturn::execute(THD *thd, uint *nextp)
*nextp= thd->spcont->pop_hstack(); *nextp= thd->spcont->pop_hstack();
DBUG_RETURN(0); DBUG_RETURN(0);
} }
//
// sp_instr_cpush
//
int
sp_instr_cpush::execute(THD *thd, uint *nextp)
{
DBUG_ENTER("sp_instr_cpush::execute");
thd->spcont->push_cursor(m_lex);
*nextp= m_ip+1;
DBUG_RETURN(0);
}
sp_instr_cpush::~sp_instr_cpush()
{
if (m_lex)
delete m_lex;
}
//
// sp_instr_cpop
//
int
sp_instr_cpop::execute(THD *thd, uint *nextp)
{
DBUG_ENTER("sp_instr_cpop::execute");
thd->spcont->pop_cursors(m_count);
*nextp= m_ip+1;
DBUG_RETURN(0);
}
//
// sp_instr_copen
//
int
sp_instr_copen::execute(THD *thd, uint *nextp)
{
sp_cursor *c= thd->spcont->get_cursor(m_cursor);
int res;
DBUG_ENTER("sp_instr_copen::execute");
if (! c)
res= -1;
else
{
LEX *lex= c->pre_open(thd);
if (! lex)
res= -1;
else
res= exec_stmt(thd, lex);
c->post_open(thd, (res == 0 ? TRUE : FALSE));
}
*nextp= m_ip+1;
DBUG_RETURN(res);
}
//
// sp_instr_cclose
//
int
sp_instr_cclose::execute(THD *thd, uint *nextp)
{
sp_cursor *c= thd->spcont->get_cursor(m_cursor);
int res;
DBUG_ENTER("sp_instr_cclose::execute");
if (! c)
res= -1;
else
res= c->close(thd);
*nextp= m_ip+1;
DBUG_RETURN(res);
}
//
// sp_instr_cfetch
//
int
sp_instr_cfetch::execute(THD *thd, uint *nextp)
{
sp_cursor *c= thd->spcont->get_cursor(m_cursor);
int res;
DBUG_ENTER("sp_instr_cfetch::execute");
if (! c)
res= -1;
else
res= c->fetch(thd, &m_varlist);
*nextp= m_ip+1;
DBUG_RETURN(res);
}
...@@ -33,10 +33,9 @@ Item_result ...@@ -33,10 +33,9 @@ Item_result
sp_map_result_type(enum enum_field_types type); sp_map_result_type(enum enum_field_types type);
struct sp_label; struct sp_label;
class sp_instr; class sp_instr;
struct sp_cond_type; struct sp_cond_type;
struct sp_pvar;
class sp_head : public Sql_alloc class sp_head : public Sql_alloc
{ {
...@@ -278,6 +277,10 @@ public: ...@@ -278,6 +277,10 @@ public:
return m_lex; return m_lex;
} }
protected:
int exec_stmt(THD *thd, LEX *lex); // Execute a statement
private: private:
LEX *m_lex; // My own lex LEX *m_lex; // My own lex
...@@ -503,4 +506,126 @@ private: ...@@ -503,4 +506,126 @@ private:
}; // class sp_instr_hreturn : public sp_instr }; // class sp_instr_hreturn : public sp_instr
class sp_instr_cpush : public sp_instr
{
sp_instr_cpush(const sp_instr_cpush &); /* Prevent use of these */
void operator=(sp_instr_cpush &);
public:
sp_instr_cpush(uint ip, LEX *lex)
: sp_instr(ip), m_lex(lex)
{}
virtual ~sp_instr_cpush();
virtual int execute(THD *thd, uint *nextp);
private:
LEX *m_lex;
}; // class sp_instr_cpush : public sp_instr
class sp_instr_cpop : public sp_instr
{
sp_instr_cpop(const sp_instr_cpop &); /* Prevent use of these */
void operator=(sp_instr_cpop &);
public:
sp_instr_cpop(uint ip, uint count)
: sp_instr(ip), m_count(count)
{}
virtual ~sp_instr_cpop()
{}
virtual int execute(THD *thd, uint *nextp);
private:
uint m_count;
}; // class sp_instr_cpop : public sp_instr
class sp_instr_copen : public sp_instr_stmt
{
sp_instr_copen(const sp_instr_copen &); /* Prevent use of these */
void operator=(sp_instr_copen &);
public:
sp_instr_copen(uint ip, uint c)
: sp_instr_stmt(ip), m_cursor(c)
{}
virtual ~sp_instr_copen()
{}
virtual int execute(THD *thd, uint *nextp);
private:
uint m_cursor; // Stack index
}; // class sp_instr_copen : public sp_instr_stmt
class sp_instr_cclose : public sp_instr
{
sp_instr_cclose(const sp_instr_cclose &); /* Prevent use of these */
void operator=(sp_instr_cclose &);
public:
sp_instr_cclose(uint ip, uint c)
: sp_instr(ip), m_cursor(c)
{}
virtual ~sp_instr_cclose()
{}
virtual int execute(THD *thd, uint *nextp);
private:
uint m_cursor;
}; // class sp_instr_cclose : public sp_instr
class sp_instr_cfetch : public sp_instr
{
sp_instr_cfetch(const sp_instr_cfetch &); /* Prevent use of these */
void operator=(sp_instr_cfetch &);
public:
sp_instr_cfetch(uint ip, uint c)
: sp_instr(ip), m_cursor(c)
{
m_varlist.empty();
}
virtual ~sp_instr_cfetch()
{}
virtual int execute(THD *thd, uint *nextp);
void add_to_varlist(struct sp_pvar *var)
{
m_varlist.push_back(var);
}
private:
uint m_cursor;
List<struct sp_pvar> m_varlist;
}; // class sp_instr_cfetch : public sp_instr
#endif /* _SP_HEAD_H_ */ #endif /* _SP_HEAD_H_ */
...@@ -27,10 +27,11 @@ ...@@ -27,10 +27,11 @@
#include "sp_head.h" #include "sp_head.h"
sp_pcontext::sp_pcontext() sp_pcontext::sp_pcontext()
: Sql_alloc(), m_params(0), m_framesize(0), m_handlers(0), m_genlab(0) : Sql_alloc(), m_params(0), m_framesize(0), m_handlers(0), m_cursmax(0)
{ {
VOID(my_init_dynamic_array(&m_pvar, sizeof(sp_pvar_t *), 16, 8)); VOID(my_init_dynamic_array(&m_pvar, sizeof(sp_pvar_t *), 16, 8));
VOID(my_init_dynamic_array(&m_cond, sizeof(sp_cond_type_t *), 16, 8)); VOID(my_init_dynamic_array(&m_cond, sizeof(sp_cond_type_t *), 16, 8));
VOID(my_init_dynamic_array(&m_cursor, sizeof(LEX_STRING), 16, 8));
m_label.empty(); m_label.empty();
} }
...@@ -39,6 +40,7 @@ sp_pcontext::destroy() ...@@ -39,6 +40,7 @@ sp_pcontext::destroy()
{ {
delete_dynamic(&m_pvar); delete_dynamic(&m_pvar);
delete_dynamic(&m_cond); delete_dynamic(&m_cond);
delete_dynamic(&m_cursor);
m_label.empty(); m_label.empty();
} }
...@@ -124,8 +126,6 @@ sp_pcontext::push_cond(LEX_STRING *name, sp_cond_type_t *val) ...@@ -124,8 +126,6 @@ sp_pcontext::push_cond(LEX_STRING *name, sp_cond_type_t *val)
if (p) if (p)
{ {
if (m_cond.elements == m_framesize)
m_framesize += 1;
p->name.str= name->str; p->name.str= name->str;
p->name.length= name->length; p->name.length= name->length;
p->val= val; p->val= val;
...@@ -155,3 +155,39 @@ sp_pcontext::find_cond(LEX_STRING *name) ...@@ -155,3 +155,39 @@ sp_pcontext::find_cond(LEX_STRING *name)
} }
return NULL; return NULL;
} }
void
sp_pcontext::push_cursor(LEX_STRING *name)
{
LEX_STRING n;
n.str= name->str;
n.length= name->length;
insert_dynamic(&m_cursor, (gptr)&n);
if (m_cursor.elements > m_cursmax)
m_cursmax= m_cursor.elements;
}
/*
* See comment for find_pvar() above
*/
my_bool
sp_pcontext::find_cursor(LEX_STRING *name, uint *poff)
{
uint i = m_cursor.elements;
while (i-- > 0)
{
LEX_STRING n;
get_dynamic(&m_cursor, (gptr)&n, i);
if (my_strnncoll(system_charset_info,
(const uchar *)name->str, name->length,
(const uchar *)n.str, n.length) == 0)
{
*poff= i;
return TRUE;
}
}
return FALSE;
}
...@@ -29,7 +29,7 @@ typedef enum ...@@ -29,7 +29,7 @@ typedef enum
sp_param_inout sp_param_inout
} sp_param_mode_t; } sp_param_mode_t;
typedef struct typedef struct sp_pvar
{ {
LEX_STRING name; LEX_STRING name;
enum enum_field_types type; enum enum_field_types type;
...@@ -200,17 +200,41 @@ class sp_pcontext : public Sql_alloc ...@@ -200,17 +200,41 @@ class sp_pcontext : public Sql_alloc
return m_handlers; return m_handlers;
} }
//
// Cursors
//
void
push_cursor(LEX_STRING *name);
my_bool
find_cursor(LEX_STRING *name, uint *poff);
inline void
pop_cursor(uint num)
{
while (num--)
pop_dynamic(&m_cursor);
}
inline uint
cursors()
{
return m_cursmax;
}
private: private:
uint m_params; // The number of parameters uint m_params; // The number of parameters
uint m_framesize; // The maximum framesize uint m_framesize; // The maximum framesize
uint m_handlers; // The total number of handlers uint m_handlers; // The total number of handlers
uint m_cursmax; // The maximum number of cursors
DYNAMIC_ARRAY m_pvar; // Parameters/variables DYNAMIC_ARRAY m_pvar; // Parameters/variables
DYNAMIC_ARRAY m_cond; // Conditions DYNAMIC_ARRAY m_cond; // Conditions
DYNAMIC_ARRAY m_cursor; // Cursors
List<sp_label_t> m_label; // The label list List<sp_label_t> m_label; // The label list
uint m_genlab; // Gen. label counter
}; // class sp_pcontext : public Sql_alloc }; // class sp_pcontext : public Sql_alloc
......
...@@ -23,16 +23,20 @@ ...@@ -23,16 +23,20 @@
#endif #endif
#include "mysql_priv.h" #include "mysql_priv.h"
#include "mysql.h"
#include "sp_head.h"
#include "sp_rcontext.h" #include "sp_rcontext.h"
#include "sp_pcontext.h" #include "sp_pcontext.h"
sp_rcontext::sp_rcontext(uint fsize, uint hmax) sp_rcontext::sp_rcontext(uint fsize, uint hmax, uint cmax)
: m_count(0), m_fsize(fsize), m_result(NULL), m_hcount(0), m_hsp(0) : m_count(0), m_fsize(fsize), m_result(NULL), m_hcount(0), m_hsp(0),
m_hfound(-1), m_ccount(0)
{ {
m_frame= (Item **)sql_alloc(fsize * sizeof(Item*)); m_frame= (Item **)sql_alloc(fsize * sizeof(Item*));
m_outs= (int *)sql_alloc(fsize * sizeof(int)); m_outs= (int *)sql_alloc(fsize * sizeof(int));
m_handler= (sp_handler_t *)sql_alloc(hmax * sizeof(sp_handler_t)); m_handler= (sp_handler_t *)sql_alloc(hmax * sizeof(sp_handler_t));
m_hstack= (uint *)sql_alloc(hmax * sizeof(uint)); m_hstack= (uint *)sql_alloc(hmax * sizeof(uint));
m_cstack= (sp_cursor **)sql_alloc(cmax * sizeof(sp_cursor *));
m_saved.empty(); m_saved.empty();
} }
...@@ -93,3 +97,141 @@ sp_rcontext::restore_variables(uint fp) ...@@ -93,3 +97,141 @@ sp_rcontext::restore_variables(uint fp)
while (i-- > fp) while (i-- > fp)
m_frame[i]= m_saved.pop(); m_frame[i]= m_saved.pop();
} }
void
sp_rcontext::push_cursor(LEX *lex)
{
m_cstack[m_ccount++]= new sp_cursor(lex);
}
void
sp_rcontext::pop_cursors(uint count)
{
while (count--)
{
delete m_cstack[--m_ccount];
}
}
/*
*
* sp_cursor
*
*/
// We have split this in two to make it easy for sp_instr_copen
// to reuse the sp_instr::exec_stmt() code.
LEX *
sp_cursor::pre_open(THD *thd)
{
int res;
if (m_isopen)
{
send_error(thd, ER_SP_CURSOR_ALREADY_OPEN);
return NULL;
}
bzero((char *)&m_mem_root, sizeof(m_mem_root));
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;
m_oprot= thd->protocol; // Save the original protocol
thd->protocol= m_prot;
m_ovio= thd->net.vio; // Prevent send_eof()
thd->net.vio= 0;
return m_lex;
}
void
sp_cursor::post_open(THD *thd, my_bool isopen)
{
thd->net.vio= m_ovio; // Restore the originals
thd->protocol= m_oprot;
m_isopen= isopen;
m_current_row= m_prot->data;
}
int
sp_cursor::close(THD *thd)
{
if (! m_isopen)
{
send_error(thd, ER_SP_CURSOR_NOT_OPEN);
return -1;
}
destroy();
return 0;
}
void
sp_cursor::destroy()
{
delete m_prot;
m_prot= NULL;
free_root(&m_mem_root, MYF(0));
bzero((char *)&m_mem_root, sizeof(m_mem_root));
m_isopen= FALSE;
}
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;
MYSQL_FIELD *fields= m_prot->fields;
if (! m_isopen)
{
send_error(thd, ER_SP_CURSOR_NOT_OPEN);
return -1;
}
if (m_current_row == NULL)
{
send_error(thd, ER_SP_FETCH_NO_DATA);
return -1;
}
row= m_current_row->data;
for (fldcount= 0 ; (pv= li++) ; fldcount++)
{
Item *it;
const char *s;
if (fldcount >= m_prot->get_field_count())
{
send_error(thd, ER_SP_WRONG_NO_OF_FETCH_ARGS);
return -1;
}
s= row[fldcount];
switch (sp_map_result_type(pv->type))
{
case INT_RESULT:
it= new Item_int(s);
break;
case REAL_RESULT:
it= new Item_real(s, strlen(s));
break;
default:
{
uint len= strlen(s);
it= new Item_string(thd->strmake(s, len), len, thd->db_charset);
break;
}
}
thd->spcont->set_item(pv->offset, it);
}
if (fldcount < m_prot->get_field_count())
{
send_error(thd, ER_SP_WRONG_NO_OF_FETCH_ARGS);
return -1;
}
m_current_row= m_current_row->next;
return 0;
}
...@@ -23,6 +23,8 @@ ...@@ -23,6 +23,8 @@
#endif #endif
struct sp_cond_type; struct sp_cond_type;
struct sp_cursor;
struct sp_pvar;
#define SP_HANDLER_NONE 0 #define SP_HANDLER_NONE 0
#define SP_HANDLER_EXIT 1 #define SP_HANDLER_EXIT 1
...@@ -44,7 +46,7 @@ class sp_rcontext : public Sql_alloc ...@@ -44,7 +46,7 @@ class sp_rcontext : public Sql_alloc
public: public:
sp_rcontext(uint fsize, uint hmax); sp_rcontext(uint fsize, uint hmax, uint cmax);
~sp_rcontext() ~sp_rcontext()
{ {
...@@ -155,22 +157,93 @@ class sp_rcontext : public Sql_alloc ...@@ -155,22 +157,93 @@ class sp_rcontext : public Sql_alloc
void void
restore_variables(uint fp); restore_variables(uint fp);
void
push_cursor(LEX *lex);
void
pop_cursors(uint count);
void
pop_all_cursors()
{
pop_cursors(m_ccount);
}
inline sp_cursor *
get_cursor(uint i)
{
return m_cstack[i];
}
private: private:
uint m_count; uint m_count;
uint m_fsize; uint m_fsize;
Item **m_frame; Item **m_frame;
int *m_outs; int *m_outs;
Item *m_result; // For FUNCTIONs Item *m_result; // For FUNCTIONs
sp_handler_t *m_handler; sp_handler_t *m_handler;
uint m_hcount; uint m_hcount;
uint *m_hstack; uint *m_hstack;
uint m_hsp; uint m_hsp;
int m_hfound; // Set by find_handler; -1 if not found int m_hfound; // Set by find_handler; -1 if not found
List<Item> m_saved; // Saved variables List<Item> m_saved; // Saved variables
sp_cursor **m_cstack;
uint m_ccount;
}; // class sp_rcontext : public Sql_alloc }; // class sp_rcontext : public Sql_alloc
class sp_cursor : public Sql_alloc
{
public:
sp_cursor(LEX *lex)
: m_lex(lex), m_isopen(0), m_current_row(NULL)
{
/* Empty */
}
virtual ~sp_cursor()
{
destroy();
}
// We have split this in two to make it easy for sp_instr_copen
// to reuse the sp_instr::exec_stmt() code.
LEX *
pre_open(THD *thd);
void
post_open(THD *thd, my_bool isopen);
int
close(THD *thd);
inline my_bool
is_open()
{
return m_isopen;
}
int
fetch(THD *, List<struct sp_pvar> *vars);
private:
MEM_ROOT m_mem_root; // My own mem_root
LEX *m_lex;
Protocol_cursor *m_prot;
my_bool m_isopen;
Vio *m_ovio; // Original vio
Protocol *m_oprot; // Original protcol
MYSQL_ROWS *m_current_row;
void
destroy();
}; // class sp_cursor : public Sql_alloc
#endif /* _SP_RCONTEXT_H_ */ #endif /* _SP_RCONTEXT_H_ */
...@@ -348,6 +348,7 @@ public: ...@@ -348,6 +348,7 @@ public:
enum olap_type olap; enum olap_type olap;
SQL_LIST table_list, group_list; /* FROM & GROUP BY clauses */ SQL_LIST table_list, group_list; /* FROM & GROUP BY clauses */
List<Item> item_list; /* list of fields & expressions */ List<Item> item_list; /* list of fields & expressions */
List<Item> item_list_copy; /* For SPs */
List<String> interval_list, use_index, *use_index_ptr, List<String> interval_list, use_index, *use_index_ptr,
ignore_index, *ignore_index_ptr; ignore_index, *ignore_index_ptr;
/* /*
......
...@@ -86,7 +86,8 @@ inline Item *or_or_concat(THD *thd, Item* A, Item* B) ...@@ -86,7 +86,8 @@ inline Item *or_or_concat(THD *thd, Item* A, Item* B)
st_select_lex *select_lex; st_select_lex *select_lex;
chooser_compare_func_creator boolfunc2creator; chooser_compare_func_creator boolfunc2creator;
struct sp_cond_type *spcondtype; struct sp_cond_type *spcondtype;
struct { int vars, conds, hndlrs; } spblock; struct { int vars, conds, hndlrs, curs; } spblock;
struct st_lex *lex;
} }
%{ %{
...@@ -748,6 +749,7 @@ END_OF_INPUT ...@@ -748,6 +749,7 @@ END_OF_INPUT
%type <num> sp_decl_idents sp_opt_inout sp_handler_type sp_hcond_list %type <num> sp_decl_idents sp_opt_inout sp_handler_type sp_hcond_list
%type <spcondtype> sp_cond sp_hcond %type <spcondtype> sp_cond sp_hcond
%type <spblock> sp_decls sp_decl %type <spblock> sp_decls sp_decl
%type <lex> sp_cursor_stmt
%type <NONE> %type <NONE>
'-' '+' '*' '/' '%' '(' ')' '-' '+' '*' '/' '%' '(' ')'
...@@ -1189,13 +1191,14 @@ sp_proc_stmts: ...@@ -1189,13 +1191,14 @@ sp_proc_stmts:
sp_decls: sp_decls:
/* Empty */ /* Empty */
{ {
$$.vars= $$.conds= $$.hndlrs= 0; $$.vars= $$.conds= $$.hndlrs= $$.curs= 0;
} }
| sp_decls sp_decl ';' | sp_decls sp_decl ';'
{ {
$$.vars= $1.vars + $2.vars; $$.vars= $1.vars + $2.vars;
$$.conds= $1.conds + $2.conds; $$.conds= $1.conds + $2.conds;
$$.hndlrs= $1.hndlrs + $2.hndlrs; $$.hndlrs= $1.hndlrs + $2.hndlrs;
$$.curs= $1.curs + $2.curs;
} }
; ;
...@@ -1222,12 +1225,12 @@ sp_decl: ...@@ -1222,12 +1225,12 @@ sp_decl:
} }
} }
$$.vars= $2; $$.vars= $2;
$$.conds= $$.hndlrs= 0; $$.conds= $$.hndlrs= $$.curs= 0;
} }
| DECLARE_SYM ident CONDITION_SYM FOR_SYM sp_cond | DECLARE_SYM ident CONDITION_SYM FOR_SYM sp_cond
{ {
YYTHD->lex->spcont->push_cond(&$2, $5); YYTHD->lex->spcont->push_cond(&$2, $5);
$$.vars= $$.hndlrs= 0; $$.vars= $$.hndlrs= $$.curs= 0;
$$.conds= 1; $$.conds= 1;
} }
| DECLARE_SYM sp_handler_type HANDLER_SYM FOR_SYM | DECLARE_SYM sp_handler_type HANDLER_SYM FOR_SYM
...@@ -1260,14 +1263,49 @@ sp_decl: ...@@ -1260,14 +1263,49 @@ sp_decl:
sp->push_backpatch(i, lex->spcont->last_label()); /* Block end */ sp->push_backpatch(i, lex->spcont->last_label()); /* Block end */
} }
lex->sphead->backpatch(hlab); lex->sphead->backpatch(hlab);
$$.vars= $$.conds= 0; $$.vars= $$.conds= $$.curs= 0;
$$.hndlrs= $6; $$.hndlrs= $6;
} }
/* QQ Not yet
| DECLARE_SYM ident CURSOR_SYM FOR_SYM sp_cursor_stmt | DECLARE_SYM ident CURSOR_SYM FOR_SYM sp_cursor_stmt
{ {
LEX *lex= Lex;
sp_head *sp= lex->sphead;
sp_instr_cpush *i= new sp_instr_cpush(sp->instructions(), $5);
sp->add_instr(i);
lex->spcont->push_cursor(&$2);
$$.vars= $$.conds= $$.hndlrs= 0; $$.vars= $$.conds= $$.hndlrs= 0;
}*/ $$.curs= 1;
}
;
sp_cursor_stmt:
{
Lex->sphead->reset_lex(YYTHD);
/* We use statement here just be able to get a better
error message. Using 'select' works too, but will then
result in a generic "syntax error" if a non-select
statement is given. */
}
statement
{
LEX *lex= Lex;
if (lex->sql_command != SQLCOM_SELECT)
{
send_error(YYTHD, ER_SP_BAD_CURSOR_QUERY);
YYABORT;
}
if (lex->result)
{
send_error(YYTHD, ER_SP_BAD_CURSOR_SELECT);
YYABORT;
}
lex->sp_lex_in_use= TRUE;
$$= lex;
lex->sphead->restore_lex(YYTHD);
}
; ;
sp_handler_type: sp_handler_type:
...@@ -1506,11 +1544,96 @@ sp_proc_stmt: ...@@ -1506,11 +1544,96 @@ sp_proc_stmt:
} }
} }
| OPEN_SYM ident | OPEN_SYM ident
{} {
| FETCH_SYM ident INTO select_var_list_init LEX *lex= Lex;
{} sp_head *sp= lex->sphead;
uint offset;
sp_instr_copen *i;
if (! lex->spcont->find_cursor(&$2, &offset))
{
net_printf(YYTHD, ER_SP_CURSOR_MISMATCH, $2.str);
YYABORT;
}
i= new sp_instr_copen(sp->instructions(), offset);
sp->add_instr(i);
}
| FETCH_SYM ident INTO
{
LEX *lex= Lex;
sp_head *sp= lex->sphead;
uint offset;
sp_instr_cfetch *i;
if (! lex->spcont->find_cursor(&$2, &offset))
{
net_printf(YYTHD, ER_SP_CURSOR_MISMATCH, $2.str);
YYABORT;
}
i= new sp_instr_cfetch(sp->instructions(), offset);
sp->add_instr(i);
}
sp_fetch_list
{ }
| CLOSE_SYM ident | CLOSE_SYM ident
{} {
LEX *lex= Lex;
sp_head *sp= lex->sphead;
uint offset;
sp_instr_cclose *i;
if (! lex->spcont->find_cursor(&$2, &offset))
{
net_printf(YYTHD, ER_SP_CURSOR_MISMATCH, $2.str);
YYABORT;
}
i= new sp_instr_cclose(sp->instructions(), offset);
sp->add_instr(i);
}
;
sp_fetch_list:
ident
{
LEX *lex= Lex;
sp_head *sp= lex->sphead;
sp_pcontext *spc= lex->spcont;
sp_pvar_t *spv;
if (!spc || !(spv = spc->find_pvar(&$1)))
{
net_printf(YYTHD, ER_SP_UNDECLARED_VAR, $1.str);
YYABORT;
}
else
{ /* An SP local variable */
sp_instr_cfetch *i= (sp_instr_cfetch *)sp->last_instruction();
i->add_to_varlist(spv);
spv->isset= TRUE;
}
}
|
sp_fetch_list ',' ident
{
LEX *lex= Lex;
sp_head *sp= lex->sphead;
sp_pcontext *spc= lex->spcont;
sp_pvar_t *spv;
if (!spc || !(spv = spc->find_pvar(&$3)))
{
net_printf(YYTHD, ER_SP_UNDECLARED_VAR, $3.str);
YYABORT;
}
else
{ /* An SP local variable */
sp_instr_cfetch *i= (sp_instr_cfetch *)sp->last_instruction();
i->add_to_varlist(spv);
spv->isset= TRUE;
}
}
; ;
sp_if: sp_if:
...@@ -1649,11 +1772,11 @@ sp_unlabeled_control: ...@@ -1649,11 +1772,11 @@ sp_unlabeled_control:
sp->backpatch(ctx->pop_label()); sp->backpatch(ctx->pop_label());
ctx->pop_pvar($3.vars); ctx->pop_pvar($3.vars);
ctx->pop_cond($3.conds); ctx->pop_cond($3.conds);
ctx->pop_cursor($3.curs);
if ($3.hndlrs) if ($3.hndlrs)
{ sp->add_instr(new sp_instr_hpop(sp->instructions(),$3.hndlrs));
sp_instr_hpop *i= new sp_instr_hpop(sp->instructions(),$3.hndlrs); if ($3.curs)
sp->add_instr(i); sp->add_instr(new sp_instr_cpop(sp->instructions(), $3.curs));
}
} }
| LOOP_SYM | LOOP_SYM
sp_proc_stmts END LOOP_SYM sp_proc_stmts END LOOP_SYM
...@@ -4206,7 +4329,7 @@ select_var_ident: ...@@ -4206,7 +4329,7 @@ select_var_ident:
sp_pvar_t *t; sp_pvar_t *t;
if (!(t=lex->spcont->find_pvar(&$1))) if (!(t=lex->spcont->find_pvar(&$1)))
{ {
send_error(lex->thd, ER_SYNTAX_ERROR); send_error(lex->thd, ER_SP_UNDECLARED_VAR);
YYABORT; YYABORT;
} }
if (! lex->result) if (! lex->result)
......
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