diff options
author | unknown <pem@mysql.com> | 2002-12-08 19:59:22 +0100 |
---|---|---|
committer | unknown <pem@mysql.com> | 2002-12-08 19:59:22 +0100 |
commit | 27de9ece815b04651db03ed3d413374f42c9d894 (patch) | |
tree | f825be850dfab95fff790962ad22638e59daa171 | |
parent | 3ec2f9656af1f02e1ffe4eaf3a7fa67756712d24 (diff) | |
download | mariadb-git-27de9ece815b04651db03ed3d413374f42c9d894.tar.gz |
Simplistic, experimental framework for Stored Procedures (SPs).
Implements creation and dropping of PROCEDUREs, IN, OUT, and INOUT parameters,
single-statement procedures, rudimentary multi-statement (begin-end) prodedures
(when the client can handle it), and local variables.
Missing most of the embedded SQL language, all attributes, FUNCTIONs, error handling,
reparses procedures at each call (no caching), etc, etc.
Certainly buggy too, but procedures can actually be created and called....
sql/Makefile.am:
Added SP files.
sql/item.cc:
Added this*_item() methods for Item_splocal. (SP local variable)
sql/item.h:
Added this*_item() methods for SPs in Item, and the new Item_splocal
class (SP local variable).
sql/lex.h:
Added new symbols for SPs. (Note: SPSET is temporary and will go away later.)
sql/sql_class.cc:
Initialize SP runtime context in THD.
sql/sql_class.h:
Add SP runtime context to THD.
sql/sql_lex.cc:
Init. buf pointer to beginning of command (needed by SPs).
Also initialize SP head and parse time context.
sql/sql_lex.h:
New SQLCOM_ tags for SPs, and added pointer to beginning of command pointer and
SP head and parse-time context to LEX.
sql/sql_parse.cc:
Added SQLCOM_CREATE_PROCEDURE, _CALL, _ALTER_PROCEDURE and _DROP_PROCEDURE cases.
(Still rudimentary and lacking proper error handling...)
sql/sql_yacc.yy:
Lots and lots of additions for SPs...
(Still even more missing, and no error messages...)
-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 { |