Commit 8049daf2 authored by pem@mysql.com's avatar pem@mysql.com

Made FUNCTIONs work in insert and select queries, as well as nested function invocations.

Had to add a cahing mechanism which is in parts an ugly kludge, but it will be
reworked once the real SP caching is implemented.
parent b6ab762d
use test;
drop table if exists t1;
drop table if exists t2;
create table t1 (
id char(16) not null,
data int not null
);
create table t2 (
s char(16) not null,
i int not null,
d double not null
);
create procedure foo42()
insert into test.t1 values ("foo", 42);
call foo42();
......@@ -409,9 +415,33 @@ end;
select fac(1), fac(2), fac(5), fac(10);
fac(1) fac(2) fac(5) fac(10)
1 2 120 3628800
create function fun(d double, i int, u int unsigned) returns double
return mul(inc(i), fac(u)) / e();
select fun(2.3, 3, 5);
fun(2.3, 3, 5)
176.58213176229
insert into t2 values (append("xxx", "yyy"), mul(4,3), e());
insert into t2 values (append("a", "b"), mul(2,mul(3,4)), fun(1.7, 4, 6));
select * from t2 where s = append("a", "b");
s i d
ab 24 1324.36598821719
select * from t2 where i = mul(4,3) or i = mul(mul(3,4),2);
s i d
xxxyyy 12 2.71828182845905
ab 24 1324.36598821719
select * from t2 where d = e();
s i d
xxxyyy 12 2.71828182845905
select * from t2;
s i d
xxxyyy 12 2.71828182845905
ab 24 1324.36598821719
delete from t2;
drop function e;
drop function inc;
drop function mul;
drop function append;
drop function fac;
drop function fun;
drop table t1;
drop table t2;
......@@ -7,12 +7,18 @@ use test;
--disable_warnings
drop table if exists t1;
drop table if exists t2;
--enable_warnings
create table t1 (
id char(16) not null,
data int not null
);
create table t2 (
s char(16) not null,
i int not null,
d double not null
);
# Single statement, no params.
......@@ -481,11 +487,32 @@ end|
select fac(1), fac(2), fac(5), fac(10)|
# Nested calls
create function fun(d double, i int, u int unsigned) returns double
return mul(inc(i), fac(u)) / e()|
select fun(2.3, 3, 5)|
# Various function calls in differen statements
insert into t2 values (append("xxx", "yyy"), mul(4,3), e())|
insert into t2 values (append("a", "b"), mul(2,mul(3,4)), fun(1.7, 4, 6))|
# These don't work yet.
select * from t2 where s = append("a", "b")|
select * from t2 where i = mul(4,3) or i = mul(mul(3,4),2)|
select * from t2 where d = e()|
select * from t2|
delete from t2|
drop function e|
drop function inc|
drop function mul|
drop function append|
drop function fac|
drop function fun|
delimiter ;|
drop table t1;
drop table t2;
......@@ -19,20 +19,23 @@
#include "sp.h"
#include "sp_head.h"
static sp_head *
sp_find_cached_function(THD *thd, char *name, uint namelen);
/*
*
* DB storage of Stored PROCEDUREs and FUNCTIONs
*
*/
// *openeed=true means we opened ourselves
static int
db_find_routine_aux(THD *thd, int type, char *name, uint namelen,
enum thr_lock_type ltype, TABLE **tablep)
enum thr_lock_type ltype, TABLE **tablep, bool *opened)
{
DBUG_ENTER("db_find_routine_aux");
DBUG_PRINT("enter", ("type: %d name: %*s", type, namelen, name));
TABLE *table;
TABLE_LIST tables;
byte key[65]; // We know name is 64 and the enum is 1 byte
uint keylen;
int ret;
......@@ -46,13 +49,25 @@ db_find_routine_aux(THD *thd, int type, char *name, uint namelen,
key[sizeof(key)-1]= type;
keylen= sizeof(key);
memset(&tables, 0, sizeof(tables));
tables.db= (char*)"mysql";
tables.real_name= tables.alias= (char*)"proc";
if (! (table= open_ltable(thd, &tables, ltype)))
for (table= thd->open_tables ; table ; table= table->next)
if (strcmp(table->table_cache_key, "mysql") == 0 &&
strcmp(table->real_name, "proc") == 0)
break;
if (table)
*opened= FALSE;
else
{
*tablep= NULL;
DBUG_RETURN(SP_OPEN_TABLE_FAILED);
TABLE_LIST tables;
memset(&tables, 0, sizeof(tables));
tables.db= (char*)"mysql";
tables.real_name= tables.alias= (char*)"proc";
if (! (table= open_ltable(thd, &tables, ltype)))
{
*tablep= NULL;
DBUG_RETURN(SP_OPEN_TABLE_FAILED);
}
*opened= TRUE;
}
if (table->file->index_read_idx(table->record[0], 0,
......@@ -77,9 +92,10 @@ db_find_routine(THD *thd, int type, char *name, uint namelen, sp_head **sphp)
TABLE *table;
const char *defstr;
int ret;
bool opened;
// QQ Set up our own mem_root here???
ret= db_find_routine_aux(thd, type, name, namelen, TL_READ, &table);
ret= db_find_routine_aux(thd, type, name, namelen, TL_READ, &table, &opened);
if (ret != SP_OK)
goto done;
if ((defstr= get_field(&thd->mem_root, table->field[2])) == NULL)
......@@ -87,8 +103,11 @@ db_find_routine(THD *thd, int type, char *name, uint namelen, sp_head **sphp)
ret= SP_GET_FIELD_FAILED;
goto done;
}
close_thread_tables(thd);
table= NULL;
if (opened)
{
close_thread_tables(thd);
table= NULL;
}
tmplex= lex_start(thd, (uchar*)defstr, strlen(defstr));
if (yyparse(thd) || thd->is_fatal_error || tmplex->sphead == NULL)
......@@ -97,7 +116,7 @@ db_find_routine(THD *thd, int type, char *name, uint namelen, sp_head **sphp)
*sphp= tmplex->sphead;
done:
if (table)
if (table && opened)
close_thread_tables(thd);
DBUG_RETURN(ret);
}
......@@ -122,9 +141,9 @@ db_create_routine(THD *thd, int type,
{
restore_record(table, 2); // Get default values for fields
table->field[0]->store(name, namelen, default_charset_info);
table->field[0]->store(name, namelen, system_charset_info);
table->field[1]->store((longlong)type);
table->field[2]->store(def, deflen, default_charset_info);
table->field[2]->store(def, deflen, system_charset_info);
if (table->file->write_row(table->record[0]))
ret= SP_WRITE_ROW_FAILED;
......@@ -143,15 +162,17 @@ db_drop_routine(THD *thd, int type, char *name, uint namelen)
DBUG_PRINT("enter", ("type: %d name: %*s", type, namelen, name));
TABLE *table;
int ret;
bool opened;
ret= db_find_routine_aux(thd, type, name, namelen, TL_WRITE, &table);
ret= db_find_routine_aux(thd, type, name, namelen, TL_WRITE, &table, &opened);
if (ret == SP_OK)
{
if (table->file->delete_row(table->record[0]))
ret= SP_DELETE_ROW_FAILED;
}
close_thread_tables(thd);
if (opened)
close_thread_tables(thd);
DBUG_RETURN(ret);
}
......@@ -216,10 +237,13 @@ sp_find_function(THD *thd, LEX_STRING *name)
DBUG_PRINT("enter", ("name: %*s", name->length, name->str));
if (db_find_routine(thd, TYPE_ENUM_FUNCTION,
name->str, name->length, &sp) != SP_OK)
sp= NULL;
sp= sp_find_cached_function(thd, name->str, name->length);
if (! sp)
{
if (db_find_routine(thd, TYPE_ENUM_FUNCTION,
name->str, name->length, &sp) != SP_OK)
sp= NULL;
}
DBUG_RETURN(sp);
}
......@@ -253,12 +277,115 @@ sp_function_exists(THD *thd, LEX_STRING *name)
{
TABLE *table;
bool ret= FALSE;
bool opened;
if (db_find_routine_aux(thd, TYPE_ENUM_FUNCTION,
name->str, name->length, TL_READ, &table) == SP_OK)
name->str, name->length, TL_READ,
&table, &opened) == SP_OK)
{
ret= TRUE;
}
close_thread_tables(thd);
if (opened)
close_thread_tables(thd);
return ret;
}
/*
*
* The temporary FUNCTION cache. (QQ This will be rehacked later, but
* it's needed now to make functions work at all.)
*
*/
void
sp_add_fun_to_lex(LEX *lex, LEX_STRING fun)
{
List_iterator_fast<char> li(lex->spfuns);
char *fn;
while ((fn= li++))
{
if (strncasecmp(fn, fun.str, fun.length) == 0)
break;
}
if (! fn)
{
char *s= sql_strmake(fun.str, fun.length);
lex->spfuns.push_back(s);
}
}
void
sp_merge_funs(LEX *dst, LEX *src)
{
List_iterator_fast<char> li(src->spfuns);
char *fn;
while ((fn= li++))
{
LEX_STRING lx;
lx.str= fn; lx.length= strlen(fn);
sp_add_fun_to_lex(dst, lx);
}
}
/* QQ Not terribly efficient right now, but it'll do for starters.
We should actually open the mysql.proc table just once. */
int
sp_cache_functions(THD *thd, LEX *lex)
{
List_iterator<char> li(lex->spfuns);
char *fn;
enum_sql_command cmd= lex->sql_command;
int ret= 0;
while ((fn= li++))
{
List_iterator_fast<sp_head> lisp(thd->spfuns);
sp_head *sp;
while ((sp= lisp++))
{
if (strcasecmp(fn, sp->name()) == 0)
break;
}
if (sp)
continue;
if (db_find_routine(thd, TYPE_ENUM_FUNCTION, fn, strlen(fn), &sp) == SP_OK)
{
ret= sp_cache_functions(thd, &thd->lex);
if (ret)
break;
thd->spfuns.push_back(sp);
}
else
{
send_error(thd, ER_SP_DOES_NOT_EXIST);
ret= 1;
}
}
lex->sql_command= cmd;
return ret;
}
void
sp_clear_function_cache(THD *thd)
{
thd->spfuns.empty();
}
static sp_head *
sp_find_cached_function(THD *thd, char *name, uint namelen)
{
List_iterator_fast<sp_head> li(thd->spfuns);
sp_head *sp;
while ((sp= li++))
{
if (strncasecmp(name, sp->name(), namelen) == 0)
break;
}
return sp;
}
......@@ -50,4 +50,16 @@ sp_drop_function(THD *thd, char *name, uint namelen);
bool
sp_function_exists(THD *thd, LEX_STRING *name);
// QQ More temporary stuff until the real cache is implemented. This is
// needed since we have to read the functions before we do anything else.
void
sp_add_fun_to_lex(LEX *lex, LEX_STRING fun);
void
sp_merge_funs(LEX *dst, LEX *src);
int
sp_cache_functions(THD *thd, LEX *lex);
void
sp_clear_function_cache(THD *thd);
#endif /* _SP_H_ */
......@@ -88,10 +88,10 @@ sp_head::sp_head(LEX_STRING *name, LEX *lex)
{
const char *dstr = (const char*)lex->buf;
m_call_lex= lex;
m_name= new Item_string(name->str, name->length, default_charset_info);
m_name= new Item_string(name->str, name->length, system_charset_info);
m_defstr= new Item_string(dstr, lex->end_of_query - lex->buf,
default_charset_info);
system_charset_info);
m_pcont= lex->spcont;
my_init_dynamic_array(&m_instr, sizeof(sp_instr *), 16, 8);
m_backpatch.empty();
}
......@@ -143,11 +143,10 @@ sp_head::execute(THD *thd)
int
sp_head::execute_function(THD *thd, Item **argp, uint argcount, Item **resp)
{
DBUG_ENTER("sp_head::execute");
DBUG_PRINT("executing", ("procedure %s", ((String *)m_name->const_string())->c_ptr()));
sp_pcontext *pctx = m_call_lex->spcont;
uint csize = pctx->max_framesize();
uint params = pctx->params();
DBUG_ENTER("sp_head::execute_function");
DBUG_PRINT("info", ("function %s", ((String *)m_name->const_string())->c_ptr()));
uint csize = m_pcont->max_framesize();
uint params = m_pcont->params();
sp_rcontext *octx = thd->spcont;
sp_rcontext *nctx = NULL;
uint i;
......@@ -157,7 +156,7 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount, Item **resp)
nctx= new sp_rcontext(csize);
for (i= 0 ; i < params && i < argcount ; i++)
{
sp_pvar_t *pvar = pctx->find_pvar(i);
sp_pvar_t *pvar = m_pcont->find_pvar(i);
nctx->push_item(eval_func_item(thd, *argp++, pvar->type));
}
......@@ -178,13 +177,12 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount, Item **resp)
int
sp_head::execute_procedure(THD *thd, List<Item> *args)
{
DBUG_ENTER("sp_head::execute");
DBUG_PRINT("executing", ("procedure %s", ((String *)m_name->const_string())->c_ptr()));
DBUG_ENTER("sp_head::execute_procedure");
DBUG_PRINT("info", ("procedure %s", ((String *)m_name->const_string())->c_ptr()));
int ret;
sp_instr *p;
sp_pcontext *pctx = m_call_lex->spcont;
uint csize = pctx->max_framesize();
uint params = pctx->params();
uint csize = m_pcont->max_framesize();
uint params = m_pcont->params();
sp_rcontext *octx = thd->spcont;
sp_rcontext *nctx = NULL;
my_bool tmp_octx = FALSE; // True if we have allocated a temporary octx
......@@ -204,7 +202,7 @@ sp_head::execute_procedure(THD *thd, List<Item> *args)
// QQ: No error checking whatsoever right now. Should do type checking?
for (i = 0 ; (it= li++) && i < params ; i++)
{
sp_pvar_t *pvar = pctx->find_pvar(i);
sp_pvar_t *pvar = m_pcont->find_pvar(i);
if (! pvar)
nctx->set_oindex(i, -1); // Shouldn't happen
......@@ -236,7 +234,7 @@ sp_head::execute_procedure(THD *thd, List<Item> *args)
// Don't copy back OUT values if we got an error
if (ret == 0 && csize > 0)
{
List_iterator_fast<Item> li(m_call_lex->value_list);
List_iterator_fast<Item> li(*args);
Item *it;
// Copy back all OUT or INOUT values to the previous frame, or
......@@ -324,6 +322,9 @@ sp_head::restore_lex(THD *thd)
m_lex.next_state= thd->lex.next_state;
// Collect some data from the sub statement lex.
sp_merge_funs(&m_lex, &thd->lex);
#if 0
// We're not using this at the moment.
if (thd->lex.sql_command == SQLCOM_CALL)
{
// It would be slightly faster to keep the list sorted, but we need
......@@ -362,6 +363,7 @@ sp_head::restore_lex(THD *thd)
m_tables.push_back(&tables->real_name);
}
}
#endif
memcpy(&thd->lex, &m_lex, sizeof(LEX)); // Restore lex
}
......
......@@ -46,8 +46,11 @@ public:
int m_type; // TYPE_ENUM_FUNCTION or TYPE_ENUM_PROCEDURE
enum enum_field_types m_returns; // For FUNCTIONs only
my_bool m_simple_case; // TRUE if parsing simple case, FALSE otherwise
#if 0
// We're not using this at the moment.
List<char *> m_calls; // Called procedures.
List<char *> m_tables; // Used tables.
#endif
static void *operator new(size_t size)
{
......@@ -59,7 +62,7 @@ public:
/* Empty */
}
sp_head(LEX_STRING *name, LEX* lex);
sp_head(LEX_STRING *name, LEX *lex);
int
create(THD *thd);
......@@ -118,7 +121,7 @@ private:
Item_string *m_name;
Item_string *m_defstr;
LEX *m_call_lex; // The CALL's own lex
sp_pcontext *m_pcont; // Parse context
LEX m_lex; // Temp. store for the other lex
DYNAMIC_ARRAY m_instr; // The "instructions"
typedef struct
......
......@@ -553,6 +553,7 @@ public:
bool prepare_command;
bool tmp_table_used;
sp_rcontext *spcont; // SP runtime context
List<sp_head> spfuns; // SP FUNCTIONs
/*
If we do a purge of binary logs, log index info of the threads
......
......@@ -176,6 +176,7 @@ LEX *lex_start(THD *thd, uchar *buf,uint length)
lex->sql_command=SQLCOM_END;
lex->sphead= NULL;
lex->spcont= NULL;
lex->spfuns.empty();
return lex;
}
......
......@@ -485,6 +485,7 @@ typedef struct st_lex
char *help_arg;
sp_head *sphead;
sp_pcontext *spcont;
List<char> spfuns; /* Called functions */
inline void uncacheable()
{
......
......@@ -1583,6 +1583,18 @@ mysql_execute_command(THD *thd)
SELECT_LEX_UNIT *unit= &lex->unit;
DBUG_ENTER("mysql_execute_command");
/*
Clear the SP function cache before each statement (QQ this is a temporary
solution; caching will be rehacked later), and the new ones.
*/
sp_clear_function_cache(thd);
if (lex->sql_command != SQLCOM_CREATE_PROCEDURE &&
lex->sql_command != SQLCOM_CREATE_FUNCTION)
{
if (sp_cache_functions(thd, lex))
DBUG_RETURN(-1);
}
/*
Reset warning count for each query that uses tables
A better approach would be to reset this for any commands
......
......@@ -37,6 +37,7 @@
#include "item_create.h"
#include "sp_head.h"
#include "sp_pcontext.h"
#include "sp.h"
#include <myisam.h>
#include <myisammrg.h>
......@@ -965,7 +966,7 @@ create_function_tail:
;
call:
CALL_SYM ident
CALL_SYM ident_or_spfunc
{
LEX *lex = Lex;
......@@ -2875,6 +2876,7 @@ simple_expr:
{ $$= new Item_int((char*) "TRUE",1,1); }
| SP_FUNC '(' udf_expr_list ')'
{
sp_add_fun_to_lex(Lex, $1);
if ($3)
$$= new Item_func_sp($1, *$3);
else
......
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