diff options
-rw-r--r-- | sql/Makefile.am | 6 | ||||
-rw-r--r-- | sql/item.cc | 19 | ||||
-rw-r--r-- | sql/item.h | 53 | ||||
-rw-r--r-- | sql/lex.h | 6 | ||||
-rw-r--r-- | sql/sp_head.cc | 384 | ||||
-rw-r--r-- | sql/sp_head.h | 200 | ||||
-rw-r--r-- | sql/sp_pcontext.cc | 91 | ||||
-rw-r--r-- | sql/sp_pcontext.h | 127 | ||||
-rw-r--r-- | sql/sp_rcontext.h | 82 | ||||
-rw-r--r-- | sql/sql_class.cc | 2 | ||||
-rw-r--r-- | sql/sql_class.h | 2 | ||||
-rw-r--r-- | sql/sql_lex.cc | 3 | ||||
-rw-r--r-- | sql/sql_lex.h | 8 | ||||
-rw-r--r-- | sql/sql_parse.cc | 80 | ||||
-rw-r--r-- | sql/sql_yacc.yy | 249 |
15 files changed, 1302 insertions, 10 deletions
diff --git a/sql/Makefile.am b/sql/Makefile.am index c5af51e8397..1cdfc05ffdc 100644 --- a/sql/Makefile.am +++ b/sql/Makefile.am @@ -57,7 +57,8 @@ noinst_HEADERS = item.h item_func.h item_sum.h item_cmpfunc.h \ lex.h lex_symbol.h sql_acl.h sql_crypt.h \ log_event.h mini_client.h sql_repl.h slave.h \ stacktrace.h sql_sort.h sql_cache.h set_var.h \ - spatial.h gstream.h + spatial.h gstream.h sp_head.h sp_pcontext.h \ + sp_rcontext.h mysqld_SOURCES = sql_lex.cc sql_handler.cc \ item.cc item_sum.cc item_buff.cc item_func.cc \ item_cmpfunc.cc item_strfunc.cc item_timefunc.cc \ @@ -85,7 +86,8 @@ mysqld_SOURCES = sql_lex.cc sql_handler.cc \ slave.cc sql_repl.cc sql_union.cc sql_derived.cc \ mini_client.cc mini_client_errors.c \ stacktrace.c repl_failsafe.h repl_failsafe.cc sql_olap.cc\ - gstream.cc spatial.cc sql_help.cc + gstream.cc spatial.cc sql_help.cc \ + sp_head.cc sp_pcontext.cc gen_lex_hash_SOURCES = gen_lex_hash.cc gen_lex_hash_LDADD = $(LDADD) $(CXXLDFLAGS) diff --git a/sql/item.cc b/sql/item.cc index f21cd0fc0f3..867b7f2174e 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -22,6 +22,7 @@ #include "mysql_priv.h" #include <m_ctype.h> #include "my_dir.h" +#include "sp_rcontext.h" /***************************************************************************** ** Item functions @@ -147,6 +148,24 @@ CHARSET_INFO * Item::thd_charset() const return current_thd->thd_charset; } + +Item * +Item_splocal::this_item() +{ + THD *thd= current_thd; + + return thd->spcont->get_item(m_offset); +} + +Item * +Item_splocal::this_const_item() const +{ + THD *thd= current_thd; + + return thd->spcont->get_item(m_offset); +} + + Item_field::Item_field(Field *f) :Item_ident(NullS,f->table_name,f->field_name) { set_field(f); diff --git a/sql/item.h b/sql/item.h index a189789ba24..bffb9212e3d 100644 --- a/sql/item.h +++ b/sql/item.h @@ -94,6 +94,8 @@ public: CHARSET_INFO *thd_charset() const; CHARSET_INFO *charset() const { return str_value.charset(); }; void set_charset(CHARSET_INFO *cs) { str_value.set_charset(cs); } + virtual Item *this_item() { return this; } /* For SPs mostly. */ + virtual Item *this_const_item() const { return const_cast<Item*>(this); } /* For SPs mostly. */ // Row emulation virtual uint cols() { return 1; } @@ -103,6 +105,57 @@ public: }; +// A local SP variable (incl. parameters), used in runtime +class Item_splocal : public Item +{ +private: + + uint m_offset; + +public: + + Item_splocal(uint offset) + : m_offset(offset) + {} + + Item *this_item(); + Item *this_const_item() const; + + inline uint get_offset() + { + return m_offset; + } + + // Abstract methods inherited from Item. Just defer the call to + // the item in the frame + inline enum Type type() const + { + return this_const_item()->type(); + } + + inline double val() + { + return this_item()->val(); + } + + inline longlong val_int() + { + return this_item()->val_int(); + } + + inline String *val_str(String *sp) + { + return this_item()->val_str(sp); + } + + inline void make_field(Send_field *field) + { + this_item()->make_field(field); + } + +}; + + /* Wrapper base class */ diff --git a/sql/lex.h b/sql/lex.h index eb03c0b36ec..bce68540b55 100644 --- a/sql/lex.h +++ b/sql/lex.h @@ -79,6 +79,7 @@ static SYMBOL symbols[] = { { "BY", SYM(BY),0,0}, { "BYTE", SYM(BYTE_SYM), 0, 0}, { "CACHE", SYM(CACHE_SYM),0,0}, + { "CALL", SYM(CALL_SYM),0,0}, { "CASCADE", SYM(CASCADE),0,0}, { "CASE", SYM(CASE_SYM),0,0}, { "CHAR", SYM(CHAR_SYM),0,0}, @@ -117,6 +118,7 @@ static SYMBOL symbols[] = { { "DAY_SECOND", SYM(DAY_SECOND_SYM),0,0}, { "DEC", SYM(DECIMAL_SYM),0,0}, { "DECIMAL", SYM(DECIMAL_SYM),0,0}, + { "DECLARE", SYM(DECLARE_SYM),0,0}, { "DES_KEY_FILE", SYM(DES_KEY_FILE),0,0}, { "DEFAULT", SYM(DEFAULT),0,0}, { "DELAYED", SYM(DELAYED_SYM),0,0}, @@ -193,6 +195,7 @@ static SYMBOL symbols[] = { { "INNER", SYM(INNER_SYM),0,0}, { "INNOBASE", SYM(INNOBASE_SYM),0,0}, { "INNODB", SYM(INNOBASE_SYM),0,0}, + { "INOUT", SYM(INOUT_SYM),0,0}, { "INSERT", SYM(INSERT),0,0}, { "INSERT_METHOD", SYM(INSERT_METHOD),0,0}, { "INT", SYM(INT_SYM),0,0}, @@ -279,6 +282,7 @@ static SYMBOL symbols[] = { { "OPTIONALLY", SYM(OPTIONALLY),0,0}, { "OR", SYM(OR),0,0}, { "ORDER", SYM(ORDER_SYM),0,0}, + { "OUT", SYM(OUT_SYM),0,0}, { "OUTER", SYM(OUTER),0,0}, { "OUTFILE", SYM(OUTFILE),0,0}, { "PACK_KEYS", SYM(PACK_KEYS_SYM),0,0}, @@ -337,6 +341,8 @@ static SYMBOL symbols[] = { { "SOME", SYM(ANY_SYM),0,0}, { "SONAME", SYM(UDF_SONAME_SYM),0,0}, { "SPATIAL", SYM(SPATIAL_SYM),0,0}, + { "SPECIFIC", SYM(SPECIFIC_SYM),0,0}, + { "SPSET", SYM(SPSET_SYM),0,0}, { "SQL_BIG_RESULT", SYM(SQL_BIG_RESULT),0,0}, { "SQL_BUFFER_RESULT", SYM(SQL_BUFFER_RESULT),0,0}, { "SQL_CACHE", SYM(SQL_CACHE_SYM), 0, 0}, diff --git a/sql/sp_head.cc b/sql/sp_head.cc new file mode 100644 index 00000000000..fcf3652095d --- /dev/null +++ b/sql/sp_head.cc @@ -0,0 +1,384 @@ +/* Copyright (C) 2002 MySQL 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 __GNUC__ +#pragma implementation +#endif + +#include "mysql_priv.h" +#include "sp_head.h" +#include "sp_pcontext.h" +#include "sp_rcontext.h" + +/* Evaluate a (presumed) func item. Always returns an item, the parameter +** if nothing else. +*/ +static Item * +eval_func_item(Item *it, enum enum_field_types type) +{ + it= it->this_item(); + + /* QQ Which way do we do this? Or is there some even better way? */ +#if 1 + /* QQ Obey the declared type of the variable */ + switch (type) + { + case MYSQL_TYPE_TINY: + case MYSQL_TYPE_SHORT: + case MYSQL_TYPE_LONG: + case MYSQL_TYPE_LONGLONG: + case MYSQL_TYPE_INT24: + it= new Item_int(it->val_int()); + break; + case MYSQL_TYPE_DECIMAL: + case MYSQL_TYPE_FLOAT: + case MYSQL_TYPE_DOUBLE: + it= new Item_real(it->val()); + break; + case MYSQL_TYPE_VAR_STRING: + case MYSQL_TYPE_STRING: + case MYSQL_TYPE_TIMESTAMP: + case MYSQL_TYPE_DATE: + case MYSQL_TYPE_TIME: + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_YEAR: + case MYSQL_TYPE_NEWDATE: + { + char buffer[MAX_FIELD_WIDTH]; + String tmp(buffer, sizeof(buffer), default_charset_info); + + (void)it->val_str(&tmp); + it= new Item_string(buffer, sizeof(buffer), default_charset_info); + break; + } + case MYSQL_TYPE_NULL: + it= new Item_null(); // A NULL is a NULL is a NULL... + break; + case MYSQL_TYPE_ENUM: + case MYSQL_TYPE_SET: + case MYSQL_TYPE_TINY_BLOB: + case MYSQL_TYPE_MEDIUM_BLOB: + case MYSQL_TYPE_LONG_BLOB: + case MYSQL_TYPE_BLOB: + case MYSQL_TYPE_GEOMETRY: + /* QQ Don't know what to do with the rest. */ + break; + } +#else + /* QQ This looks simpler, but is wrong? It disregards the variable's type. */ + switch (it->result_type()) + { + case REAL_RESULT: + it= new Item_real(it->val()); + break; + case INT_RESULT: + it= new Item_int(it->val_int()); + break; + case STRING_RESULT: + { + char buffer[MAX_FIELD_WIDTH]; + String tmp(buffer, sizeof(buffer), default_charset_info); + + (void)it->val_str(&tmp); + it= new Item_string(buffer, sizeof(buffer), default_charset_info); + break; + } + default: + /* QQ Don't know what to do with the rest. */ + break; + } +#endif + return it; +} + +sp_head::sp_head(LEX_STRING *name, LEX *lex) +{ + const char *dstr = (const char*)lex->buf; + + m_mylex= lex; + m_name= new Item_string(name->str, name->length, default_charset_info); + m_defstr= new Item_string(dstr, lex->end_of_query - lex->buf, + default_charset_info); + my_init_dynamic_array(&m_instr, sizeof(sp_instr *), 16, 8); +} + +int +sp_head::create(THD *thd) +{ + String *name= m_name->const_string(); + String *def= m_defstr->const_string(); + + return sp_create_procedure(thd, + name->c_ptr(), name->length(), + def->c_ptr(), def->length()); +} + +int +sp_head::execute(THD *thd) +{ + int ret= 0; + sp_instr *p; + sp_pcontext *pctx = m_mylex->spcont; + uint csize = pctx->max_framesize(); + uint params = pctx->params(); + sp_rcontext *octx = thd->spcont; + sp_rcontext *nctx = NULL; + + if (csize > 0) + { + uint i; + List_iterator_fast<Item> li(m_mylex->value_list); + Item *it = li++; // Skip first one, it's the procedure name + + nctx = new sp_rcontext(csize); + // QQ: No error checking whatsoever right now + for (i = 0 ; (it= li++) && i < params ; i++) + { + sp_pvar_t *pvar = pctx->find_pvar(i); + + // QQ Passing an argument is, in a sense, a "SET". We have to evaluate + // any expression at this point. + nctx->push_item(it->this_item()); + // Note: If it's OUT or INOUT, it must be a variable. + // QQ: Need to handle "global" user/host variables too!!! + if (!pvar || pvar->mode == sp_param_in) + nctx->set_oindex(i, -1); + else + nctx->set_oindex(i, static_cast<Item_splocal *>(it)->get_offset()); + } + // The rest of the frame are local variables which are all IN. + // QQ We haven't found any hint of what the value is when unassigned, + // so we set it to NULL for now. It's an error to refer to an + // unassigned variable (which should be detected by the parser). + for (; i < csize ; i++) + nctx->push_item(NULL); + thd->spcont= nctx; + } + + { // Execute instructions... + uint ip= 0; + my_bool nsok= thd->net.no_send_ok; + + thd->net.no_send_ok= TRUE; // Don't send_ok() during execution + + while (ret == 0) + { + int offset; + sp_instr *i; + + i = get_instr(ip); // Returns NULL when we're done. + if (i == NULL) + break; + ret= i->execute(thd, &offset); + ip += offset; + } + + thd->net.no_send_ok= nsok; + } + + // Don't copy back OUT values if we got an error + if (ret == 0 && csize > 0) + { + // Copy back all OUT or INOUT values to the previous frame + for (uint i = 0 ; i < params ; i++) + { + int oi = nctx->get_oindex(i); + + if (oi >= 0) + octx->set_item(nctx->get_oindex(i), nctx->get_item(i)); + } + + thd->spcont= octx; + } + + return ret; +} + + +void +sp_head::reset_lex(THD *thd) +{ + memcpy(&m_lex, &thd->lex, sizeof(LEX)); // Save old one + /* Reset most stuff. The length arguments doesn't matter here. */ + lex_start(thd, m_lex.buf, m_lex.end_of_query - m_lex.ptr); + /* We must reset ptr and end_of_query again */ + thd->lex.ptr= m_lex.ptr; + thd->lex.end_of_query= m_lex.end_of_query; + /* And keep the SP stuff too */ + thd->lex.sphead = m_lex.sphead; + thd->lex.spcont = m_lex.spcont; + /* QQ Why isn't this reset by lex_start() ??? */ + thd->lex.col_list.empty(); + thd->lex.ref_list.empty(); + thd->lex.drop_list.empty(); + thd->lex.alter_list.empty(); + thd->lex.interval_list.empty(); + thd->lex.users_list.empty(); + thd->lex.columns.empty(); + thd->lex.key_list.empty(); + thd->lex.create_list.empty(); + thd->lex.insert_list= NULL; + thd->lex.field_list.empty(); + thd->lex.value_list.empty(); + thd->lex.many_values.empty(); + thd->lex.var_list.empty(); + thd->lex.param_list.empty(); + thd->lex.proc_list.empty(); + thd->lex.auxilliary_table_list.empty(); +} + +void +sp_head::restore_lex(THD *thd) +{ + // Update some state in the old one first + m_lex.ptr= thd->lex.ptr; + m_lex.next_state= thd->lex.next_state; + // QQ Append tables, fields, etc. from the current lex to mine + memcpy(&thd->lex, &m_lex, sizeof(LEX)); // Restore lex +} + +// Finds the SP 'name'. Currently this always reads from the database +// and prepares (parse) it, but in the future it will first look in +// the in-memory cache for SPs. (And store newly prepared SPs there of +// course.) +sp_head * +sp_find(THD *thd, Item_string *iname) +{ + extern int yyparse(void *thd); + LEX *tmplex; + TABLE *table; + TABLE_LIST tables; + const char *defstr; + String *name; + sp_head *sp = NULL; + + name = iname->const_string(); + memset(&tables, 0, sizeof(tables)); + tables.db= (char*)"mysql"; + tables.real_name= tables.alias= (char*)"proc"; + if (! (table= open_ltable(thd, &tables, TL_READ))) + return NULL; + + if (table->file->index_read_idx(table->record[0], 0, + (byte*)name->c_ptr(), name->length(), + HA_READ_KEY_EXACT)) + goto done; + + if ((defstr= get_field(&thd->mem_root, table, 1)) == NULL) + goto done; + + // QQ Set up our own mem_root here??? + tmplex= lex_start(thd, (uchar*)defstr, strlen(defstr)); + if (yyparse(thd) || thd->fatal_error || tmplex->sphead == NULL) + goto done; // Error + else + sp = tmplex->sphead; + + done: + if (table) + close_thread_tables(thd); + return sp; +} + +int +sp_create_procedure(THD *thd, char *name, uint namelen, char *def, uint deflen) +{ + int ret= 0; + TABLE *table; + TABLE_LIST tables; + + memset(&tables, 0, sizeof(tables)); + tables.db= (char*)"mysql"; + tables.real_name= tables.alias= (char*)"proc"; + /* Allow creation of procedures even if we can't open proc table */ + if (! (table= open_ltable(thd, &tables, TL_WRITE))) + { + ret= -1; + goto done; + } + + restore_record(table, 2); // Get default values for fields + + table->field[0]->store(name, namelen, default_charset_info); + table->field[1]->store(def, deflen, default_charset_info); + + ret= table->file->write_row(table->record[0]); + + done: + close_thread_tables(thd); + return ret; +} + +int +sp_drop(THD *thd, char *name, uint namelen) +{ + TABLE *table; + TABLE_LIST tables; + + tables.db= (char *)"mysql"; + tables.real_name= tables.alias= (char *)"proc"; + if (! (table= open_ltable(thd, &tables, TL_WRITE))) + goto err; + if (! table->file->index_read_idx(table->record[0], 0, + (byte *)name, namelen, + HA_READ_KEY_EXACT)) + { + int error; + + if ((error= table->file->delete_row(table->record[0]))) + table->file->print_error(error, MYF(0)); + } + close_thread_tables(thd); + return 0; + + err: + close_thread_tables(thd); + return -1; +} + + + +// +// sp_instr_stmt +// +int +sp_instr_stmt::execute(THD *thd, int *offsetp) +{ + LEX olex; // The other lex + + memcpy(&olex, &thd->lex, sizeof(LEX)); // Save the other lex + + memcpy(&thd->lex, &m_lex, sizeof(LEX)); // Use my own lex + thd->lex.thd = thd; + + mysql_execute_command(thd); + + memcpy(&thd->lex, &olex, sizeof(LEX)); // Restore the other lex + + *offsetp = 1; + return 0; +} + +// +// sp_instr_set +// +int +sp_instr_set::execute(THD *thd, int *offsetp) +{ + thd->spcont->set_item(m_offset, eval_func_item(m_value, m_type)); + *offsetp = 1; + return 0; +} diff --git a/sql/sp_head.h b/sql/sp_head.h new file mode 100644 index 00000000000..72a5c90b011 --- /dev/null +++ b/sql/sp_head.h @@ -0,0 +1,200 @@ +/* -*- C++ -*- */ +/* Copyright (C) 2002 MySQL 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 */ + +#ifndef _SP_HEAD_H_ +#define _SP_HEAD_H_ + +#ifdef __GNUC__ +#pragma interface /* gcc class implementation */ +#endif + +#include <stddef.h> + +class sp_instr; + +class sp_head : public Sql_alloc +{ + sp_head(const sp_head &); /* Prevent use of these */ + void operator=(sp_head &); + +public: + + static void *operator new(size_t size) + { + return (void*) sql_alloc((uint) size); + } + + static void operator delete(void *ptr, size_t size) + { + /* Empty */ + } + + sp_head(LEX_STRING *name, LEX* lex); + + int + create(THD *thd); + + int + execute(THD *thd); + + inline void + add_instr(sp_instr *i) + { + insert_dynamic(&m_instr, (gptr)&i); + } + + inline uint + instructions() + { + return m_instr.elements; + } + + // Resets lex in 'thd' and keeps a copy of the old one. + void + reset_lex(THD *thd); + + // Restores lex in 'thd' from our copy, but keeps some status from the + // one in 'thd', like ptr, tables, fields, etc. + void + restore_lex(THD *thd); + +private: + + Item_string *m_name; + Item_string *m_defstr; + LEX *m_mylex; // My own lex + LEX m_lex; // Temp. store for the other lex + DYNAMIC_ARRAY m_instr; // The "instructions" + + inline sp_instr * + get_instr(uint i) + { + sp_instr *in= NULL; + + get_dynamic(&m_instr, (gptr)&in, i); + return in; + } + +}; // class sp_head : public Sql_alloc + + +// +// Find a stored procedure given its name. Returns NULL if not +// found. +// +sp_head * +sp_find(THD *thd, Item_string *name); + +int +sp_create_procedure(THD *thd, char *name, uint namelen, char *def, uint deflen); + +int +sp_drop(THD *thd, char *name, uint namelen); + + +class sp_instr : public Sql_alloc +{ + sp_instr(const sp_instr &); /* Prevent use of these */ + void operator=(sp_instr &); + +public: + + // Should give each a name or type code for debugging purposes? + sp_instr() + : Sql_alloc() + {} + + virtual ~sp_instr() + {} + + // Execute this instrution. '*offsetp' will be set to an offset to the + // next instruction to execute. (For most instruction this will be 1, + // i.e. the following instruction.) + // Returns 0 on success, non-zero if some error occured. + virtual int + execute(THD *thd, int *offsetp) + { // Default is a no-op. + *offsetp = 1; // Next instruction + return 0; + } + +}; // class sp_instr : public Sql_alloc + + +// +// Call out to some prepared SQL statement. +// +class sp_instr_stmt : public sp_instr +{ + sp_instr_stmt(const sp_instr_stmt &); /* Prevent use of these */ + void operator=(sp_instr_stmt &); + +public: + + sp_instr_stmt() + : sp_instr() + {} + + virtual ~sp_instr_stmt() + {} + + virtual int execute(THD *thd, int *offsetp); + + inline void + set_lex(LEX *lex) + { + memcpy(&m_lex, lex, sizeof(LEX)); + } + + inline LEX * + get_lex() + { + return &m_lex; + } + +private: + + LEX m_lex; // My own lex + +}; // class sp_instr_stmt : public sp_instr + + +class sp_instr_set : public sp_instr +{ + sp_instr_set(const sp_instr_set &); /* Prevent use of these */ + void operator=(sp_instr_set &); + +public: + + sp_instr_set(uint offset, Item *val, enum enum_field_types type) + : sp_instr(), m_offset(offset), m_value(val), m_type(type) + {} + + virtual ~sp_instr_set() + {} + + virtual int execute(THD *thd, int *offsetp); + +private: + + uint m_offset; + Item *m_value; + enum enum_field_types m_type; // The declared type + +}; // class sp_instr_set : public sp_instr + +#endif /* _SP_HEAD_H_ */ diff --git a/sql/sp_pcontext.cc b/sql/sp_pcontext.cc new file mode 100644 index 00000000000..d2ab8cb93ac --- /dev/null +++ b/sql/sp_pcontext.cc @@ -0,0 +1,91 @@ +/* Copyright (C) 2002 MySQL 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 __GNUC__ +#pragma implementation +#endif + +#if defined(WIN32) || defined(__WIN__) +#undef SAFEMALLOC /* Problems with threads */ +#endif + +#include "mysql_priv.h" +#include "sp_pcontext.h" + +sp_pcontext::sp_pcontext() + : m_params(0), m_framesize(0), m_i(0) +{ + m_pvar_size = 16; + m_pvar = (sp_pvar_t *)my_malloc(m_pvar_size * sizeof(sp_pvar_t), MYF(MY_WME)); + if (m_pvar) + memset(m_pvar, 0, m_pvar_size * sizeof(sp_pvar_t)); +} + +void +sp_pcontext::grow() +{ + uint sz = m_pvar_size + 8; + sp_pvar_t *a = (sp_pvar_t *)my_realloc((char *)m_pvar, + sz * sizeof(sp_pvar_t), + MYF(MY_WME | MY_ALLOW_ZERO_PTR)); + + if (a) + { + m_pvar_size = sz; + m_pvar = a; + } +} + +/* This does a linear search (from newer to older variables, in case +** we have shadowed names). +** It's possible to have a more efficient allocation and search method, +** but it might not be worth it. The typical number of parameters and +** variables will in most cases be low (a handfull). +** And this is only called during parsing. +*/ +sp_pvar_t * +sp_pcontext::find_pvar(LEX_STRING *name) +{ + String n(name->str, name->length, default_charset_info); + uint i = m_i; + + while (i-- > 0) + { + if (stringcmp(&n, m_pvar[i].name->const_string()) == 0) + return m_pvar + i; + } + return NULL; +} + +void +sp_pcontext::push(LEX_STRING *name, enum enum_field_types type, + sp_param_mode_t mode) +{ + if (m_i >= m_pvar_size) + grow(); + if (m_i < m_pvar_size) + { + if (m_i == m_framesize) + m_framesize += 1; + m_pvar[m_i].name= new Item_string(name->str, name->length, + default_charset_info); + m_pvar[m_i].type= type; + m_pvar[m_i].mode= mode; + m_pvar[m_i].offset= m_i; + m_pvar[m_i].isset= (mode == sp_param_out ? FALSE : TRUE); + m_i += 1; + } +} diff --git a/sql/sp_pcontext.h b/sql/sp_pcontext.h new file mode 100644 index 00000000000..956b8d99d4d --- /dev/null +++ b/sql/sp_pcontext.h @@ -0,0 +1,127 @@ +/* -*- C++ -*- */ +/* Copyright (C) 2002 MySQL 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 */ + +#ifndef _SP_PCONTEXT_H_ +#define _SP_PCONTEXT_H_ + +#ifdef __GNUC__ +#pragma interface /* gcc class implementation */ +#endif + +typedef enum +{ + sp_param_in, + sp_param_out, + sp_param_inout +} sp_param_mode_t; + +typedef struct +{ + Item_string *name; + enum enum_field_types type; + sp_param_mode_t mode; + uint offset; // Offset in current frame + my_bool isset; +} sp_pvar_t; + +class sp_pcontext : public Sql_alloc +{ + sp_pcontext(const sp_pcontext &); /* Prevent use of these */ + void operator=(sp_pcontext &); + + public: + + sp_pcontext(); + + inline uint + max_framesize() + { + return m_framesize; + } + + inline uint + current_framesize() + { + return m_i; + } + + inline uint + params() + { + return m_params; + } + + // Set the number of parameters to the current esize + inline void + set_params() + { + m_params= m_i; + } + + inline void + set_type(uint i, enum enum_field_types type) + { + if (i < m_i) + m_pvar[i].type= type; + } + + inline void + set_isset(uint i, my_bool val) + { + if (i < m_i) + m_pvar[i].isset= val; + } + + void + push(LEX_STRING *name, enum enum_field_types type, sp_param_mode_t mode); + + inline void + pop(uint num = 1) + { + if (num >= m_i) + m_i -= num; + } + + // Find by name + sp_pvar_t * + find_pvar(LEX_STRING *name); + + // Find by index + sp_pvar_t * + find_pvar(uint i) + { + if (i >= m_i) + return NULL; + return m_pvar+i; + } + +private: + + uint m_params; // The number of parameters + uint m_framesize; // The maximum framesize + uint m_i; // The current index (during parsing) + + sp_pvar_t *m_pvar; + uint m_pvar_size; // Current size of m_pvar. + + void + grow(); + +}; // class sp_pcontext : public Sql_alloc + + +#endif /* _SP_PCONTEXT_H_ */ diff --git a/sql/sp_rcontext.h b/sql/sp_rcontext.h new file mode 100644 index 00000000000..5ffbb0266e6 --- /dev/null +++ b/sql/sp_rcontext.h @@ -0,0 +1,82 @@ +/* -*- C++ -*- */ +/* Copyright (C) 2002 MySQL 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 */ + +#ifndef _SP_RCONTEXT_H_ +#define _SP_RCONTEXT_H_ + +class sp_rcontext : public Sql_alloc +{ + sp_rcontext(const sp_rcontext &); /* Prevent use of these */ + void operator=(sp_rcontext &); + + public: + + sp_rcontext(uint size) + : m_count(0), m_size(size) + { + m_frame = (Item **)sql_alloc(size * sizeof(Item*)); + m_outs = (int *)sql_alloc(size * sizeof(int)); + } + + ~sp_rcontext() + { + // Not needed? + //sql_element_free(m_frame); + } + + inline void + push_item(Item *i) + { + if (m_count < m_size) + m_frame[m_count++] = i; + } + + inline void + set_item(uint idx, Item *i) + { + if (idx < m_count) + m_frame[idx] = i; + } + + inline Item * + get_item(uint idx) + { + return m_frame[idx]; + } + + inline void + set_oindex(uint idx, int oidx) + { + m_outs[idx] = oidx; + } + + inline int + get_oindex(uint idx) + { + return m_outs[idx]; + } + +private: + + uint m_count; + uint m_size; + Item **m_frame; + int *m_outs; + +}; // class sp_rcontext : public Sql_alloc + +#endif /* _SP_RCONTEXT_H_ */ diff --git a/sql/sql_class.cc b/sql/sql_class.cc index ebd1d9d2b3c..855c97715ea 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -81,7 +81,7 @@ extern "C" void free_user_var(user_var_entry *entry) THD::THD():user_time(0), fatal_error(0), last_insert_id_used(0), insert_id_used(0), rand_used(0), in_lock_tables(0), - global_read_lock(0), bootstrap(0) + global_read_lock(0), bootstrap(0), spcont(NULL) { host=user=priv_user=db=query=ip=0; host_or_ip="unknown ip"; diff --git a/sql/sql_class.h b/sql/sql_class.h index ca56d2dcdf5..00236aee176 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -26,6 +26,7 @@ class Query_log_event; class Load_log_event; class Slave_log_event; +class sp_rcontext; enum enum_enable_or_disable { LEAVE_AS_IS, ENABLE, DISABLE }; enum enum_ha_read_modes { RFIRST, RNEXT, RPREV, RLAST, RKEY }; @@ -515,6 +516,7 @@ public: bool query_error, bootstrap, cleanup_done; bool volatile killed; bool prepare_command; + sp_rcontext *spcont; // SP runtime context /* If we do a purge of binary logs, log index info of the threads diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 833f36dbe9f..d40d968225f 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -155,6 +155,7 @@ LEX *lex_start(THD *thd, uchar *buf,uint length) { LEX *lex= &thd->lex; lex->next_state=STATE_START; + lex->buf= buf; lex->end_of_query=(lex->ptr=buf)+length; lex->yylineno = 1; lex->select_lex.create_refs=lex->in_comment=0; @@ -170,6 +171,8 @@ LEX *lex_start(THD *thd, uchar *buf,uint length) lex->slave_thd_opt=0; lex->sql_command=SQLCOM_END; lex->safe_to_cache_query= 1; + lex->sphead= NULL; + lex->spcont= NULL; bzero(&lex->mi,sizeof(lex->mi)); return lex; } diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 0c761baffa3..467ff26d5ab 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -21,6 +21,9 @@ class Table_ident; class sql_exchange; class LEX_COLUMN; +class sp_head; +class sp_instr; +class sp_pcontext; /* The following hack is needed because mysql_yacc.cc does not define @@ -72,6 +75,8 @@ enum enum_sql_command { SQLCOM_SHOW_WARNS, SQLCOM_EMPTY_QUERY, SQLCOM_SHOW_ERRORS, SQLCOM_SHOW_COLUMN_TYPES, SQLCOM_SHOW_TABLE_TYPES, SQLCOM_SHOW_PRIVILEGES, SQLCOM_HELP, + SQLCOM_CREATE_PROCEDURE, SQLCOM_CALL, SQLCOM_DROP_PROCEDURE, + SQLCOM_ALTER_PROCEDURE, /* This should be the last !!! */ SQLCOM_END @@ -402,6 +407,7 @@ typedef struct st_lex SELECT_LEX_NODE *current_select; /* list of all SELECT_LEX */ SELECT_LEX *all_selects_list; + uchar *buf; /* The beginning of string, used by SPs */ uchar *ptr,*tok_start,*tok_end,*end_of_query; char *length,*dec,*change,*name; char *backup_dir; /* For RESTORE/BACKUP */ @@ -459,6 +465,8 @@ typedef struct st_lex uint slave_thd_opt; CHARSET_INFO *charset; char *help_arg; + sp_head *sphead; + sp_pcontext *spcont; } LEX; diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 660fd545bb6..fba726c95b6 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -27,6 +27,8 @@ #include "ha_innodb.h" #endif +#include "sp_head.h" + #ifdef HAVE_OPENSSL /* Without SSL the handshake consists of one packet. This packet @@ -2739,6 +2741,84 @@ mysql_execute_command(THD *thd) res= -1; thd->options&= ~(ulong) (OPTION_BEGIN | OPTION_STATUS_NO_TRANS_UPDATE); break; + case SQLCOM_CREATE_PROCEDURE: + if (!lex->sphead) + res= -1; + else + { + res= lex->sphead->create(thd); + if (res < 0) + { + // QQ Error! + } + send_ok(thd); + } + break; + case SQLCOM_CALL: + { + Item_string *s; + sp_head *sp; + + s= (Item_string*)lex->value_list.head(); + sp= sp_find(thd, s); + if (! sp) + { + // QQ Error! + res= -1; + } + else + { + res= sp->execute(thd); + if (res == 0) + send_ok(thd); + } + } + break; + case SQLCOM_ALTER_PROCEDURE: + { + Item_string *s; + sp_head *sp; + + s= (Item_string*)lex->value_list.head(); + sp= sp_find(thd, s); + if (! sp) + { + // QQ Error! + res= -1; + } + else + { + /* QQ This is an no-op right now, since we haven't + put the characteristics in yet. */ + send_ok(thd); + } + } + break; + case SQLCOM_DROP_PROCEDURE: + { + Item_string *s; + sp_head *sp; + + s = (Item_string*)lex->value_list.head(); + sp = sp_find(thd, s); + if (! sp) + { + // QQ Error! + res= -1; + } + else + { + String *name = s->const_string(); + + res= sp_drop(thd, name->c_ptr(), name->length()); + if (res < 0) + { + // QQ Error! + } + send_ok(thd); + } + } + break; default: /* Impossible */ send_ok(thd); break; diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index ed8e8f0fb51..33f928f2efe 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -35,6 +35,8 @@ #include "sql_acl.h" #include "lex_symbol.h" #include "item_create.h" +#include "sp_head.h" +#include "sp_pcontext.h" #include <myisam.h> #include <myisammrg.h> @@ -120,6 +122,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b,int *yystacksize); %token AVG_SYM %token BEGIN_SYM %token BINLOG_SYM +%token CALL_SYM %token CHANGE %token CLIENT_SYM %token COMMENT_SYM @@ -198,6 +201,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b,int *yystacksize); %token CONVERT_SYM %token DATABASES %token DATA_SYM +%token DECLARE_SYM %token DEFAULT %token DELAYED_SYM %token DELAY_KEY_WRITE_SYM @@ -242,6 +246,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b,int *yystacksize); %token INFILE %token INNER_SYM %token INNOBASE_SYM +%token INOUT_SYM %token INTO %token IN_SYM %token ISOLATION @@ -256,6 +261,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b,int *yystacksize); %token LIKE %token LINES %token LOCAL_SYM +%token LOCATOR_SYM %token LOG_SYM %token LOGS_SYM %token LONG_NUM @@ -296,6 +302,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b,int *yystacksize); %token OR %token OR_OR_CONCAT %token ORDER_SYM +%token OUT_SYM %token OUTER %token OUTFILE %token DUMPFILE @@ -334,6 +341,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b,int *yystacksize); %token SIMPLE_SYM %token SHUTDOWN %token SPATIAL_SYM +%token SPECIFIC_SYM %token SSL_SYM %token STARTING %token STATUS_SYM @@ -518,6 +526,9 @@ bool my_yyoverflow(short **a, YYSTYPE **b,int *yystacksize); %token SQL_SMALL_RESULT %token SQL_BUFFER_RESULT +/* QQ This is a dummy, until we have solved the SET syntax problem. */ +%token SPSET_SYM + %token ISSUER_SYM %token SUBJECT_SYM %token CIPHER_SYM @@ -666,8 +677,12 @@ bool my_yyoverflow(short **a, YYSTYPE **b,int *yystacksize); union_clause union_list union_option precision subselect_start opt_and subselect_end select_var_list select_var_list_init help opt_len + statement END_OF_INPUT +%type <NONE> call sp_proc_body sp_proc_stmts sp_proc_stmt +%type <num> sp_decls sp_decl sp_decl_idents sp_opt_inout + %type <NONE> '-' '+' '*' '/' '%' '(' ')' ',' '!' '{' '}' '&' '|' AND OR OR_OR_CONCAT BETWEEN_SYM CASE_SYM @@ -693,10 +708,16 @@ query: | verb_clause END_OF_INPUT {}; verb_clause: + statement + | begin + ; + +/* Verb clauses, except begin */ +statement: alter | analyze | backup - | begin + | call | change | check | commit @@ -874,7 +895,177 @@ create: lex->udf.returns=(Item_result) $7; lex->udf.dl=$9.str; } - ; + | CREATE PROCEDURE ident + { + LEX *lex= Lex; + + lex->spcont = new sp_pcontext(); + lex->sphead = new sp_head(&$3, lex); + } + '(' sp_dparam_list ')' + { + Lex->spcont->set_params(); + } + sp_proc_body + { + Lex->sql_command= SQLCOM_CREATE_PROCEDURE; + } + ; + +call: + CALL_SYM ident + { + LEX *lex = Lex; + + lex->sql_command= SQLCOM_CALL; + lex->value_list.empty(); + lex->value_list.push_back( + (Item*)new Item_string($2.str, $2.length, default_charset_info)); + } + '(' sp_cparam_list ')' {} + ; + +/* CALL parameters */ +sp_cparam_list: + /* Empty */ + | sp_cparams + ; + +sp_cparams: + sp_cparams ',' expr + { + Lex->value_list.push_back($3); + } + | expr + { + Lex->value_list.push_back($1); + } + ; + +/* SP parameter declaration list */ +sp_dparam_list: + /* Empty */ + | sp_dparams + ; + +sp_dparams: + sp_dparams ',' sp_dparam + | sp_dparam + ; + +sp_dparam: + sp_opt_inout ident type sp_opt_locator + { + Lex->spcont->push(&$2, + (enum enum_field_types)$3, + (sp_param_mode_t)$1); + } + ; + +sp_opt_inout: + /* Empty */ { $$= sp_param_in; } + | IN_SYM { $$= sp_param_in; } + | OUT_SYM { $$= sp_param_out; } + | INOUT_SYM { $$= sp_param_inout; } + ; + +sp_opt_locator: + /* Empty */ + | AS LOCATOR_SYM + ; + +sp_proc_body: + { + Lex->sphead->reset_lex(YYTHD); + } + sp_proc_stmt + { + Lex->sphead->restore_lex(YYTHD); + } + | begin + sp_decls + sp_proc_stmts + END + { + Lex->spcont->pop($2); + } + ; + +sp_proc_stmts: + sp_proc_body ';' + | sp_proc_stmts sp_proc_body ';' + ; + +sp_decls: + /* Empty */ + { + $$= 0; + } + | sp_decls sp_decl ';' + { + $$= $1 + $2; + } + ; + +sp_decl: + DECLARE_SYM sp_decl_idents type + { + LEX *lex= Lex; + uint max= lex->spcont->current_framesize(); + + for (uint i = max-$2 ; i < max ; i++) + { + lex->spcont->set_type(i, (enum enum_field_types)$3); + lex->spcont->set_isset(i, FALSE); + } + $$= $2; + } + ; + +sp_decl_idents: + ident + { + Lex->spcont->push(&$1, (enum_field_types)0, sp_param_in); + $$= 1; + } + | sp_decl_idents ',' ident + { + Lex->spcont->push(&$3, (enum_field_types)0, sp_param_in); + $$= $1 + 1; + } + ; + +/* Dummy for the spset thing. Will go away when the SET problem is fixed. */ +sp_proc_stmt: + statement + { + LEX *lex= Lex; + sp_instr_stmt *i= new sp_instr_stmt(); + + i->set_lex(lex); + lex->sphead->add_instr(i); + } + | + /* QQ Dummy. We need to fix the old SET syntax to make it work for + local SP variables as well. */ + SPSET_SYM ident EQ expr + { + LEX *lex= Lex; + sp_pcontext *spc= lex->spcont; + sp_pvar_t *spv; + + if (!spc || !(spv = spc->find_pvar(&$2))) + YYABORT; /* Unknow variable */ + else + { + /* QQ Check type match! */ + sp_instr_set *i = new sp_instr_set(spv->offset, $4, spv->type); + + lex->sphead->add_instr(i); + spv->isset= TRUE; + } + } + ; create2: '(' field_list ')' opt_create_table_options create3 {} @@ -1393,8 +1584,27 @@ alter: lex->sql_command=SQLCOM_ALTER_DB; lex->name=$3.str; lex->create_info.table_charset=$4; - }; + } + | ALTER PROCEDURE opt_specific ident + /* QQ Characteristics missing for now */ + opt_restrict + { + LEX *lex=Lex; + + /* This is essensially an no-op right now, since we haven't + put the characteristics in yet. */ + lex->sql_command= SQLCOM_ALTER_PROCEDURE; + lex->value_list.empty(); + lex->value_list.push_back( + (Item*)new Item_string($4.str, $4.length, default_charset_info)); + } + ; +opt_specific: + /* Empty */ + | + SPECIFIC_SYM + ; alter_list: | alter_list_item @@ -2905,7 +3115,16 @@ drop: LEX *lex=Lex; lex->sql_command = SQLCOM_DROP_FUNCTION; lex->udf.name=$3.str; - }; + } + | DROP PROCEDURE ident opt_restrict + { + LEX *lex=Lex; + lex->sql_command = SQLCOM_DROP_PROCEDURE; + lex->value_list.empty(); + lex->value_list.push_back( + (Item*)new Item_string($3.str, $3.length, default_charset_info)); + } + ; table_list: @@ -2958,7 +3177,6 @@ replace: } insert_field_spec {} - {} ; insert_lock_option: @@ -3642,8 +3860,25 @@ order_ident: simple_ident: ident { - SELECT_LEX_NODE *sel=Select; - $$ = !sel->create_refs || sel->get_in_sum_expr() > 0 ? (Item*) new Item_field(NullS,NullS,$1.str) : (Item*) new Item_ref(NullS,NullS,$1.str); + sp_pvar_t *spv; + LEX *lex = Lex; + sp_pcontext *spc = lex->spcont; + + if (spc && (spv = spc->find_pvar(&$1))) + { /* We're compiling a stored procedure and found a variable */ + if (lex->sql_command != SQLCOM_CALL && ! spv->isset) + { + printf("QQ Referring to an unitialized variable\n"); + YYABORT; /* QQ Referring to an unitialized variable */ + } + else + $$ = (Item*) new Item_splocal(spv->offset); + } + else + { + SELECT_LEX_NODE *sel=Select; + $$ = !sel->create_refs || sel->get_in_sum_expr() > 0 ? (Item*) new Item_field(NullS,NullS,$1.str) : (Item*) new Item_ref(NullS,NullS,$1.str); + } } | ident '.' ident { |