diff options
Diffstat (limited to 'sql/sp.cc')
-rw-r--r-- | sql/sp.cc | 1717 |
1 files changed, 1195 insertions, 522 deletions
diff --git a/sql/sp.cc b/sql/sp.cc index 207ece47356..f9cfc9ce6c8 100644 --- a/sql/sp.cc +++ b/sql/sp.cc @@ -15,11 +15,12 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -#include <my_global.h> +#include "mariadb.h" #include "sql_priv.h" #include "unireg.h" #include "sp.h" #include "sql_base.h" // close_thread_tables +#include "sql_lex.h" // empty_clex_str #include "sql_parse.h" // parse_sql #include "key.h" // key_copy #include "sql_show.h" // append_definer, append_identifier @@ -34,96 +35,163 @@ #include <my_user.h> -/* Used in error handling only */ -#define SP_TYPE_STRING(type) \ - (type == TYPE_ENUM_FUNCTION ? "FUNCTION" : "PROCEDURE") - -static int -db_load_routine(THD *thd, stored_procedure_type type, sp_name *name, - sp_head **sphp, - sql_mode_t sql_mode, const char *params, const char *returns, - const char *body, st_sp_chistics &chistics, - LEX_STRING *definer_user_name, LEX_STRING *definer_host_name, - longlong created, longlong modified, - Stored_program_creation_ctx *creation_ctx); +sp_cache **Sp_handler_procedure::get_cache(THD *thd) const +{ + return &thd->sp_proc_cache; +} + +sp_cache **Sp_handler_function::get_cache(THD *thd) const +{ + return &thd->sp_func_cache; +} + +sp_cache **Sp_handler_package_spec::get_cache(THD *thd) const +{ + return &thd->sp_package_spec_cache; +} + +sp_cache **Sp_handler_package_body::get_cache(THD *thd) const +{ + return &thd->sp_package_body_cache; +} + + +ulong Sp_handler_procedure::recursion_depth(THD *thd) const +{ + return thd->variables.max_sp_recursion_depth; +} + + +bool Sp_handler::add_instr_freturn(THD *thd, sp_head *sp, + sp_pcontext *spcont, + Item *item, LEX *lex) const +{ + my_error(ER_SP_BADRETURN, MYF(0)); + return true; +} + + +bool Sp_handler::add_instr_preturn(THD *thd, sp_head *sp, + sp_pcontext *spcont) const +{ + thd->parse_error(); + return true; +} + + +bool Sp_handler_function::add_instr_freturn(THD *thd, sp_head *sp, + sp_pcontext *spcont, + Item *item, LEX *lex) const +{ + return sp->add_instr_freturn(thd, spcont, item, lex); +} + + +bool Sp_handler_procedure::add_instr_preturn(THD *thd, sp_head *sp, + sp_pcontext *spcont) const +{ + return sp->add_instr_preturn(thd, spcont); +} + + +Sp_handler_procedure sp_handler_procedure; +Sp_handler_function sp_handler_function; +Sp_handler_package_spec sp_handler_package_spec; +Sp_handler_package_body sp_handler_package_body; +Sp_handler_trigger sp_handler_trigger; +Sp_handler_package_procedure sp_handler_package_procedure; +Sp_handler_package_function sp_handler_package_function; + + +const Sp_handler *Sp_handler_procedure::package_routine_handler() const +{ + return &sp_handler_package_procedure; +} + + +const Sp_handler *Sp_handler_function::package_routine_handler() const +{ + return &sp_handler_package_function; +} + static const TABLE_FIELD_TYPE proc_table_fields[MYSQL_PROC_FIELD_COUNT] = { { - { C_STRING_WITH_LEN("db") }, - { C_STRING_WITH_LEN("char(64)") }, - { C_STRING_WITH_LEN("utf8") } + { STRING_WITH_LEN("db") }, + { STRING_WITH_LEN("char(64)") }, + { STRING_WITH_LEN("utf8") } }, { - { C_STRING_WITH_LEN("name") }, - { C_STRING_WITH_LEN("char(64)") }, - { C_STRING_WITH_LEN("utf8") } + { STRING_WITH_LEN("name") }, + { STRING_WITH_LEN("char(64)") }, + { STRING_WITH_LEN("utf8") } }, { - { C_STRING_WITH_LEN("type") }, - { C_STRING_WITH_LEN("enum('FUNCTION','PROCEDURE')") }, + { STRING_WITH_LEN("type") }, + { STRING_WITH_LEN("enum('FUNCTION','PROCEDURE')") }, { NULL, 0 } }, { - { C_STRING_WITH_LEN("specific_name") }, - { C_STRING_WITH_LEN("char(64)") }, - { C_STRING_WITH_LEN("utf8") } + { STRING_WITH_LEN("specific_name") }, + { STRING_WITH_LEN("char(64)") }, + { STRING_WITH_LEN("utf8") } }, { - { C_STRING_WITH_LEN("language") }, - { C_STRING_WITH_LEN("enum('SQL')") }, + { STRING_WITH_LEN("language") }, + { STRING_WITH_LEN("enum('SQL')") }, { NULL, 0 } }, { - { C_STRING_WITH_LEN("sql_data_access") }, - { C_STRING_WITH_LEN("enum('CONTAINS_SQL','NO_SQL','READS_SQL_DATA','MODIFIES_SQL_DATA')") }, + { STRING_WITH_LEN("sql_data_access") }, + { STRING_WITH_LEN("enum('CONTAINS_SQL','NO_SQL','READS_SQL_DATA','MODIFIES_SQL_DATA')") }, { NULL, 0 } }, { - { C_STRING_WITH_LEN("is_deterministic") }, - { C_STRING_WITH_LEN("enum('YES','NO')") }, + { STRING_WITH_LEN("is_deterministic") }, + { STRING_WITH_LEN("enum('YES','NO')") }, { NULL, 0 } }, { - { C_STRING_WITH_LEN("security_type") }, - { C_STRING_WITH_LEN("enum('INVOKER','DEFINER')") }, + { STRING_WITH_LEN("security_type") }, + { STRING_WITH_LEN("enum('INVOKER','DEFINER')") }, { NULL, 0 } }, { - { C_STRING_WITH_LEN("param_list") }, - { C_STRING_WITH_LEN("blob") }, + { STRING_WITH_LEN("param_list") }, + { STRING_WITH_LEN("blob") }, { NULL, 0 } }, { - { C_STRING_WITH_LEN("returns") }, - { C_STRING_WITH_LEN("longblob") }, + { STRING_WITH_LEN("returns") }, + { STRING_WITH_LEN("longblob") }, { NULL, 0 } }, { - { C_STRING_WITH_LEN("body") }, - { C_STRING_WITH_LEN("longblob") }, + { STRING_WITH_LEN("body") }, + { STRING_WITH_LEN("longblob") }, { NULL, 0 } }, { - { C_STRING_WITH_LEN("definer") }, - { C_STRING_WITH_LEN("char(") }, - { C_STRING_WITH_LEN("utf8") } + { STRING_WITH_LEN("definer") }, + { STRING_WITH_LEN("char(") }, + { STRING_WITH_LEN("utf8") } }, { - { C_STRING_WITH_LEN("created") }, - { C_STRING_WITH_LEN("timestamp") }, + { STRING_WITH_LEN("created") }, + { STRING_WITH_LEN("timestamp") }, { NULL, 0 } }, { - { C_STRING_WITH_LEN("modified") }, - { C_STRING_WITH_LEN("timestamp") }, + { STRING_WITH_LEN("modified") }, + { STRING_WITH_LEN("timestamp") }, { NULL, 0 } }, { - { C_STRING_WITH_LEN("sql_mode") }, - { C_STRING_WITH_LEN("set('REAL_AS_FLOAT','PIPES_AS_CONCAT','ANSI_QUOTES'," + { STRING_WITH_LEN("sql_mode") }, + { STRING_WITH_LEN("set('REAL_AS_FLOAT','PIPES_AS_CONCAT','ANSI_QUOTES'," "'IGNORE_SPACE','IGNORE_BAD_TABLE_OPTIONS','ONLY_FULL_GROUP_BY'," "'NO_UNSIGNED_SUBTRACTION'," "'NO_DIR_IN_CREATE','POSTGRESQL','ORACLE','MSSQL','DB2','MAXDB'," @@ -131,32 +199,38 @@ TABLE_FIELD_TYPE proc_table_fields[MYSQL_PROC_FIELD_COUNT] = "'ANSI','NO_AUTO_VALUE_ON_ZERO','NO_BACKSLASH_ESCAPES','STRICT_TRANS_TABLES'," "'STRICT_ALL_TABLES','NO_ZERO_IN_DATE','NO_ZERO_DATE','INVALID_DATES'," "'ERROR_FOR_DIVISION_BY_ZERO','TRADITIONAL','NO_AUTO_CREATE_USER'," - "'HIGH_NOT_PRECEDENCE','NO_ENGINE_SUBSTITUTION','PAD_CHAR_TO_FULL_LENGTH')") }, + "'HIGH_NOT_PRECEDENCE','NO_ENGINE_SUBSTITUTION','PAD_CHAR_TO_FULL_LENGTH'," + "'EMPTY_STRING_IS_NULL','SIMULTANEOUS_ASSIGNMENT')") }, { NULL, 0 } }, { - { C_STRING_WITH_LEN("comment") }, - { C_STRING_WITH_LEN("text") }, - { C_STRING_WITH_LEN("utf8") } + { STRING_WITH_LEN("comment") }, + { STRING_WITH_LEN("text") }, + { STRING_WITH_LEN("utf8") } }, { - { C_STRING_WITH_LEN("character_set_client") }, - { C_STRING_WITH_LEN("char(32)") }, - { C_STRING_WITH_LEN("utf8") } + { STRING_WITH_LEN("character_set_client") }, + { STRING_WITH_LEN("char(32)") }, + { STRING_WITH_LEN("utf8") } }, { - { C_STRING_WITH_LEN("collation_connection") }, - { C_STRING_WITH_LEN("char(32)") }, - { C_STRING_WITH_LEN("utf8") } + { STRING_WITH_LEN("collation_connection") }, + { STRING_WITH_LEN("char(32)") }, + { STRING_WITH_LEN("utf8") } }, { - { C_STRING_WITH_LEN("db_collation") }, - { C_STRING_WITH_LEN("char(32)") }, - { C_STRING_WITH_LEN("utf8") } + { STRING_WITH_LEN("db_collation") }, + { STRING_WITH_LEN("char(32)") }, + { STRING_WITH_LEN("utf8") } }, { - { C_STRING_WITH_LEN("body_utf8") }, - { C_STRING_WITH_LEN("longblob") }, + { STRING_WITH_LEN("body_utf8") }, + { STRING_WITH_LEN("longblob") }, + { NULL, 0 } + }, + { + { STRING_WITH_LEN("aggregate") }, + { STRING_WITH_LEN("enum('NONE','GROUP')") }, { NULL, 0 } } }; @@ -176,7 +250,7 @@ class Stored_routine_creation_ctx : public Stored_program_creation_ctx, { public: static Stored_routine_creation_ctx * - load_from_db(THD *thd, const sp_name *name, TABLE *proc_tbl); + load_from_db(THD *thd, const Database_qualified_name *name, TABLE *proc_tbl); public: virtual Stored_program_creation_ctx *clone(MEM_ROOT *mem_root) @@ -214,15 +288,16 @@ bool load_charset(MEM_ROOT *mem_root, CHARSET_INFO *dflt_cs, CHARSET_INFO **cs) { - String cs_name; + LEX_CSTRING cs_name; - if (get_field(mem_root, field, &cs_name)) + if (field->val_str_nopad(mem_root, &cs_name)) { *cs= dflt_cs; return TRUE; } - *cs= get_charset_by_csname(cs_name.c_ptr(), MY_CS_PRIMARY, MYF(0)); + DBUG_ASSERT(cs_name.str[cs_name.length] == 0); + *cs= get_charset_by_csname(cs_name.str, MY_CS_PRIMARY, MYF(0)); if (*cs == NULL) { @@ -240,15 +315,16 @@ bool load_collation(MEM_ROOT *mem_root, CHARSET_INFO *dflt_cl, CHARSET_INFO **cl) { - String cl_name; + LEX_CSTRING cl_name; - if (get_field(mem_root, field, &cl_name)) + if (field->val_str_nopad(mem_root, &cl_name)) { *cl= dflt_cl; return TRUE; } - *cl= get_charset_by_name(cl_name.c_ptr(), MYF(0)); + DBUG_ASSERT(cl_name.str[cl_name.length] == 0); + *cl= get_charset_by_name(cl_name.str, MYF(0)); if (*cl == NULL) { @@ -263,8 +339,8 @@ bool load_collation(MEM_ROOT *mem_root, Stored_routine_creation_ctx * Stored_routine_creation_ctx::load_from_db(THD *thd, - const sp_name *name, - TABLE *proc_tbl) + const Database_qualified_name *name, + TABLE *proc_tbl) { /* Load character set/collation attributes. */ @@ -405,7 +481,7 @@ TABLE *open_proc_table_for_read(THD *thd, Open_tables_backup *backup) DBUG_ENTER("open_proc_table_for_read"); - table.init_one_table("mysql", 5, "proc", 4, "proc", TL_READ); + table.init_one_table(&MYSQL_SCHEMA_NAME, &MYSQL_PROC_NAME, NULL, TL_READ); if (open_system_tables_for_read(thd, &table, backup)) DBUG_RETURN(NULL); @@ -440,7 +516,7 @@ static TABLE *open_proc_table_for_update(THD *thd) MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint(); DBUG_ENTER("open_proc_table_for_update"); - table_list.init_one_table("mysql", 5, "proc", 4, "proc", TL_WRITE); + table_list.init_one_table(&MYSQL_SCHEMA_NAME, &MYSQL_PROC_NAME, NULL, TL_WRITE); if (!(table= open_system_table_for_update(thd, &table_list))) DBUG_RETURN(NULL); @@ -459,7 +535,6 @@ static TABLE *open_proc_table_for_update(THD *thd) Find row in open mysql.proc table representing stored routine. @param thd Thread context - @param type Type of routine to find (function or procedure) @param name Name of routine @param table TABLE object for open mysql.proc table. @@ -469,14 +544,16 @@ static TABLE *open_proc_table_for_update(THD *thd) SP_KEY_NOT_FOUND No routine with given name */ -static int -db_find_routine_aux(THD *thd, stored_procedure_type type, sp_name *name, - TABLE *table) +int +Sp_handler::db_find_routine_aux(THD *thd, + const Database_qualified_name *name, + TABLE *table) const { uchar key[MAX_KEY_LENGTH]; // db, name, optional key length type DBUG_ENTER("db_find_routine_aux"); - DBUG_PRINT("enter", ("type: %d name: %.*s", - type, (int) name->m_name.length, name->m_name.str)); + DBUG_PRINT("enter", ("type: %s name: %.*s", + type_str(), + (int) name->m_name.length, name->m_name.str)); /* Create key to find row. We have to use field->store() to be able to @@ -487,10 +564,9 @@ db_find_routine_aux(THD *thd, stored_procedure_type type, sp_name *name, */ if (name->m_name.length > table->field[1]->field_length) DBUG_RETURN(SP_KEY_NOT_FOUND); - table->field[0]->store(name->m_db.str, name->m_db.length, &my_charset_bin); - table->field[1]->store(name->m_name.str, name->m_name.length, - &my_charset_bin); - table->field[2]->store((longlong) type, TRUE); + table->field[0]->store(name->m_db, &my_charset_bin); + table->field[1]->store(name->m_name, &my_charset_bin); + table->field[2]->store((longlong) type(), true); key_copy(key, table->record[0], table->key_info, table->key_info->key_length); @@ -503,12 +579,83 @@ db_find_routine_aux(THD *thd, stored_procedure_type type, sp_name *name, } +bool st_sp_chistics::read_from_mysql_proc_row(THD *thd, TABLE *table) +{ + LEX_CSTRING str; + + if (table->field[MYSQL_PROC_FIELD_ACCESS]->val_str_nopad(thd->mem_root, + &str)) + return true; + + switch (str.str[0]) { + case 'N': + daccess= SP_NO_SQL; + break; + case 'C': + daccess= SP_CONTAINS_SQL; + break; + case 'R': + daccess= SP_READS_SQL_DATA; + break; + case 'M': + daccess= SP_MODIFIES_SQL_DATA; + break; + default: + daccess= SP_DEFAULT_ACCESS_MAPPING; + } + + if (table->field[MYSQL_PROC_FIELD_DETERMINISTIC]->val_str_nopad(thd->mem_root, + &str)) + return true; + detistic= str.str[0] == 'N' ? false : true; + + if (table->field[MYSQL_PROC_FIELD_SECURITY_TYPE]->val_str_nopad(thd->mem_root, + &str)) + return true; + suid= str.str[0] == 'I' ? SP_IS_NOT_SUID : SP_IS_SUID; + + if (table->field[MYSQL_PROC_FIELD_AGGREGATE]->val_str_nopad(thd->mem_root, + &str)) + return true; + + switch (str.str[0]) { + case 'N': + agg_type= NOT_AGGREGATE; + break; + case 'G': + agg_type= GROUP_AGGREGATE; + break; + default: + agg_type= DEFAULT_AGGREGATE; + } + + + if (table->field[MYSQL_PROC_FIELD_COMMENT]->val_str_nopad(thd->mem_root, + &comment)) + return true; + + return false; +} + + +bool AUTHID::read_from_mysql_proc_row(THD *thd, TABLE *table) +{ + LEX_CSTRING str; + if (table->field[MYSQL_PROC_FIELD_DEFINER]->val_str_nopad(thd->mem_root, + &str)) + return true; + parse(str.str, str.length); + if (user.str[user.length]) + ((char *) user.str)[user.length]= '\0'; // 0-terminate if was truncated + return false; +} + + /** Find routine definition in mysql.proc table and create corresponding sp_head object for it. @param thd Thread context - @param type Type of routine (TYPE_ENUM_PROCEDURE/...) @param name Name of routine @param sphp Out parameter in which pointer to created sp_head object is returned (0 in case of error). @@ -523,33 +670,27 @@ db_find_routine_aux(THD *thd, stored_procedure_type type, sp_name *name, non-0 Error (may be one of special codes like SP_KEY_NOT_FOUND) */ -static int -db_find_routine(THD *thd, stored_procedure_type type, sp_name *name, - sp_head **sphp) +int +Sp_handler::db_find_routine(THD *thd, + const Database_qualified_name *name, + sp_head **sphp) const { TABLE *table; - const char *params, *returns, *body; + LEX_CSTRING params, returns, body; int ret; - const char *definer; longlong created; longlong modified; - st_sp_chistics chistics; - char *ptr; - uint length; - char buff[65]; - String str(buff, sizeof(buff), &my_charset_bin); + Sp_chistics chistics; bool saved_time_zone_used= thd->time_zone_used; sql_mode_t sql_mode, saved_mode= thd->variables.sql_mode; Open_tables_backup open_tables_state_backup; Stored_program_creation_ctx *creation_ctx; - char definer_user_name_holder[USERNAME_LENGTH + 1]; - LEX_STRING definer_user_name= { definer_user_name_holder, USERNAME_LENGTH }; - char definer_host_name_holder[HOSTNAME_LENGTH + 1]; - LEX_STRING definer_host_name= { definer_host_name_holder, HOSTNAME_LENGTH }; + AUTHID definer; DBUG_ENTER("db_find_routine"); - DBUG_PRINT("enter", ("type: %d name: %.*s", - type, (int) name->m_name.length, name->m_name.str)); + DBUG_PRINT("enter", ("type: %s name: %.*s", + type_str(), + (int) name->m_name.length, name->m_name.str)); *sphp= 0; // In case of errors if (!(table= open_proc_table_for_read(thd, &open_tables_state_backup))) @@ -558,7 +699,7 @@ db_find_routine(THD *thd, stored_procedure_type type, sp_name *name, /* Reset sql_mode during data dictionary operations. */ thd->variables.sql_mode= 0; - if ((ret= db_find_routine_aux(thd, type, name, table)) != SP_OK) + if ((ret= db_find_routine_aux(thd, name, table)) != SP_OK) goto done; if (table->s->fields < MYSQL_PROC_FIELD_COUNT) @@ -567,107 +708,44 @@ db_find_routine(THD *thd, stored_procedure_type type, sp_name *name, goto done; } - bzero((char *)&chistics, sizeof(chistics)); - if ((ptr= get_field(thd->mem_root, - table->field[MYSQL_PROC_FIELD_ACCESS])) == NULL) + if (chistics.read_from_mysql_proc_row(thd, table) || + definer.read_from_mysql_proc_row(thd, table)) { ret= SP_GET_FIELD_FAILED; goto done; } - switch (ptr[0]) { - case 'N': - chistics.daccess= SP_NO_SQL; - break; - case 'C': - chistics.daccess= SP_CONTAINS_SQL; - break; - case 'R': - chistics.daccess= SP_READS_SQL_DATA; - break; - case 'M': - chistics.daccess= SP_MODIFIES_SQL_DATA; - break; - default: - chistics.daccess= SP_DEFAULT_ACCESS_MAPPING; - } - - if ((ptr= get_field(thd->mem_root, - table->field[MYSQL_PROC_FIELD_DETERMINISTIC])) == NULL) - { - ret= SP_GET_FIELD_FAILED; - goto done; - } - chistics.detistic= (ptr[0] == 'N' ? FALSE : TRUE); - - if ((ptr= get_field(thd->mem_root, - table->field[MYSQL_PROC_FIELD_SECURITY_TYPE])) == NULL) - { - ret= SP_GET_FIELD_FAILED; - goto done; - } - chistics.suid= (ptr[0] == 'I' ? SP_IS_NOT_SUID : SP_IS_SUID); - if ((params= get_field(thd->mem_root, - table->field[MYSQL_PROC_FIELD_PARAM_LIST])) == NULL) - { - params= ""; - } - - if (type == TYPE_ENUM_PROCEDURE) - returns= ""; - else if ((returns= get_field(thd->mem_root, - table->field[MYSQL_PROC_FIELD_RETURNS])) == NULL) + table->field[MYSQL_PROC_FIELD_PARAM_LIST]->val_str_nopad(thd->mem_root, + ¶ms); + if (type() != TYPE_ENUM_FUNCTION) + returns= empty_clex_str; + else if (table->field[MYSQL_PROC_FIELD_RETURNS]->val_str_nopad(thd->mem_root, + &returns)) { ret= SP_GET_FIELD_FAILED; goto done; } - if ((body= get_field(thd->mem_root, - table->field[MYSQL_PROC_FIELD_BODY])) == NULL) + if (table->field[MYSQL_PROC_FIELD_BODY]->val_str_nopad(thd->mem_root, + &body)) { ret= SP_GET_FIELD_FAILED; goto done; } // Get additional information - if ((definer= get_field(thd->mem_root, - table->field[MYSQL_PROC_FIELD_DEFINER])) == NULL) - { - ret= SP_GET_FIELD_FAILED; - goto done; - } - modified= table->field[MYSQL_PROC_FIELD_MODIFIED]->val_int(); created= table->field[MYSQL_PROC_FIELD_CREATED]->val_int(); - - sql_mode= (ulong) table->field[MYSQL_PROC_FIELD_SQL_MODE]->val_int(); - - table->field[MYSQL_PROC_FIELD_COMMENT]->val_str(&str, &str); - - ptr= 0; - if ((length= str.length())) - ptr= thd->strmake(str.ptr(), length); - chistics.comment.str= ptr; - chistics.comment.length= length; + sql_mode= (sql_mode_t) table->field[MYSQL_PROC_FIELD_SQL_MODE]->val_int(); creation_ctx= Stored_routine_creation_ctx::load_from_db(thd, name, table); close_system_tables(thd, &open_tables_state_backup); table= 0; - if (parse_user(definer, strlen(definer), - definer_user_name.str, &definer_user_name.length, - definer_host_name.str, &definer_host_name.length) && - definer_user_name.length && !definer_host_name.length) - { - // 'user@' -> 'user@%' - definer_host_name= host_not_specified; - } - - ret= db_load_routine(thd, type, name, sphp, - sql_mode, params, returns, body, chistics, - &definer_user_name, &definer_host_name, - created, modified, creation_ctx); + ret= db_load_routine(thd, name, sphp, + sql_mode, params, returns, body, chistics, definer, + created, modified, NULL, creation_ctx); done: /* Restore the time zone flag as the timezone usage in proc table @@ -681,6 +759,23 @@ db_find_routine(THD *thd, stored_procedure_type type, sp_name *name, } +int +Sp_handler::db_find_and_cache_routine(THD *thd, + const Database_qualified_name *name, + sp_head **sp) const +{ + int rc= db_find_routine(thd, name, sp); + if (rc == SP_OK) + { + sp_cache_insert(get_cache(thd), *sp); + DBUG_PRINT("info", ("added new: %p, level: %lu, flags %x", + sp[0], sp[0]->m_recursion_level, + sp[0]->m_flags)); + } + return rc; +} + + /** Silence DEPRECATED SYNTAX warnings when loading a stored procedure into the cache. @@ -720,6 +815,8 @@ Silence_deprecated_warning::handle_condition( @param[in] thd Thread handler @param[in] defstr CREATE... string @param[in] sql_mode SQL mode + @param[in] parent The owner package for package routines, + or NULL for standalone routines. @param[in] creation_ctx Creation context of stored routines @return Pointer on sp_head struct @@ -728,6 +825,7 @@ Silence_deprecated_warning::handle_condition( */ static sp_head *sp_compile(THD *thd, String *defstr, sql_mode_t sql_mode, + sp_package *parent, Stored_program_creation_ctx *creation_ctx) { sp_head *sp; @@ -748,6 +846,7 @@ static sp_head *sp_compile(THD *thd, String *defstr, sql_mode_t sql_mode, } lex_start(thd); + thd->lex->sphead= parent; thd->push_internal_handler(&warning_handler); thd->spcont= 0; @@ -808,14 +907,18 @@ Bad_db_error_handler::handle_condition(THD *thd, } -static int -db_load_routine(THD *thd, stored_procedure_type type, - sp_name *name, sp_head **sphp, - sql_mode_t sql_mode, const char *params, const char *returns, - const char *body, st_sp_chistics &chistics, - LEX_STRING *definer_user_name, LEX_STRING *definer_host_name, - longlong created, longlong modified, - Stored_program_creation_ctx *creation_ctx) +int +Sp_handler::db_load_routine(THD *thd, const Database_qualified_name *name, + sp_head **sphp, + sql_mode_t sql_mode, + const LEX_CSTRING ¶ms, + const LEX_CSTRING &returns, + const LEX_CSTRING &body, + const st_sp_chistics &chistics, + const AUTHID &definer, + longlong created, longlong modified, + sp_package *parent, + Stored_program_creation_ctx *creation_ctx) const { LEX *old_lex= thd->lex, newlex; String defstr; @@ -829,8 +932,6 @@ db_load_routine(THD *thd, stored_procedure_type type, thd->lex= &newlex; newlex.current_select= NULL; - // Resetting REPLACE and EXIST flags in create_info, for show_create_sp() - newlex.create_info.DDL_options_st::init(); defstr.set_charset(creation_ctx->get_client_cs()); @@ -840,15 +941,10 @@ db_load_routine(THD *thd, stored_procedure_type type, definition for SHOW CREATE PROCEDURE later. */ - if (!show_create_sp(thd, &defstr, - type, - NULL, 0, - name->m_name.str, name->m_name.length, - params, strlen(params), - returns, strlen(returns), - body, strlen(body), - &chistics, definer_user_name, definer_host_name, - sql_mode)) + if (show_create_sp(thd, &defstr, + null_clex_str, name->m_name, + params, returns, body, + chistics, definer, DDL_options(), sql_mode)) { ret= SP_INTERNAL_ERROR; goto end; @@ -878,14 +974,16 @@ db_load_routine(THD *thd, stored_procedure_type type, } { - *sphp= sp_compile(thd, &defstr, sql_mode, creation_ctx); + *sphp= sp_compile(thd, &defstr, sql_mode, parent, creation_ctx); /* Force switching back to the saved current database (if changed), because it may be NULL. In this case, mysql_change_db() would generate an error. */ - if (cur_db_changed && mysql_change_db(thd, &saved_cur_db_name, TRUE)) + if (cur_db_changed && mysql_change_db(thd, + (LEX_CSTRING*) &saved_cur_db_name, + TRUE)) { ret= SP_INTERNAL_ERROR; goto end; @@ -897,10 +995,26 @@ db_load_routine(THD *thd, stored_procedure_type type, goto end; } - (*sphp)->set_definer(definer_user_name, definer_host_name); - (*sphp)->set_info(created, modified, &chistics, sql_mode); + (*sphp)->set_definer(&definer.user, &definer.host); + (*sphp)->set_info(created, modified, chistics, sql_mode); (*sphp)->set_creation_ctx(creation_ctx); (*sphp)->optimize(); + + if (type() == TYPE_ENUM_PACKAGE_BODY) + { + sp_package *package= sphp[0]->get_package(); + List_iterator<LEX> it(package->m_routine_implementations); + for (LEX *lex; (lex= it++); ) + { + DBUG_ASSERT(lex->sphead); + lex->sphead->set_definer(&definer.user, &definer.host); + lex->sphead->set_suid(package->suid()); + lex->sphead->m_sql_mode= sql_mode; + lex->sphead->set_creation_ctx(creation_ctx); + lex->sphead->optimize(); + } + } + /* Not strictly necessary to invoke this method here, since we know that we've parsed CREATE PROCEDURE/FUNCTION and not an @@ -921,7 +1035,7 @@ end: void -sp_returns_type(THD *thd, String &result, sp_head *sp) +sp_returns_type(THD *thd, String &result, const sp_head *sp) { TABLE table; TABLE_SHARE share; @@ -957,7 +1071,6 @@ sp_returns_type(THD *thd, String &result, sp_head *sp) and invalidates the stored-routine cache. @param thd Thread context. - @param type Stored routine type (TYPE_ENUM_PROCEDURE or TYPE_ENUM_FUNCTION) @param name Stored routine name. @param table A pointer to the opened mysql.proc table @@ -965,9 +1078,10 @@ sp_returns_type(THD *thd, String &result, sp_head *sp) @return SP_OK on success, or SP_DELETE_ROW_FAILED on error. used to indicate about errors. */ -static int -sp_drop_routine_internal(THD *thd, stored_procedure_type type, - sp_name *name, TABLE *table) +int +Sp_handler::sp_drop_routine_internal(THD *thd, + const Database_qualified_name *name, + TABLE *table) const { DBUG_ENTER("sp_drop_routine_internal"); @@ -984,15 +1098,40 @@ sp_drop_routine_internal(THD *thd, stored_procedure_type type, local cache. */ sp_head *sp; - sp_cache **spc= (type == TYPE_ENUM_FUNCTION ? - &thd->sp_func_cache : &thd->sp_proc_cache); - sp= sp_cache_lookup(spc, name); - if (sp) + sp_cache **spc= get_cache(thd); + DBUG_ASSERT(spc); + if ((sp= sp_cache_lookup(spc, name))) sp_cache_flush_obsolete(spc, &sp); DBUG_RETURN(SP_OK); } +int +Sp_handler::sp_find_and_drop_routine(THD *thd, TABLE *table, + const Database_qualified_name *name) const +{ + int ret; + if (SP_OK != (ret= db_find_routine_aux(thd, name, table))) + return ret; + return sp_drop_routine_internal(thd, name, table); +} + + +int +Sp_handler_package_spec:: + sp_find_and_drop_routine(THD *thd, TABLE *table, + const Database_qualified_name *name) const +{ + int ret; + if (SP_OK != (ret= db_find_routine_aux(thd, name, table))) + return ret; + ret= sp_handler_package_body.sp_find_and_drop_routine(thd, table, name); + if (ret != SP_KEY_NOT_FOUND && ret != SP_OK) + return ret; + return Sp_handler::sp_find_and_drop_routine(thd, table, name); +} + + /** Write stored-routine object into mysql.proc. @@ -1000,8 +1139,6 @@ sp_drop_routine_internal(THD *thd, stored_procedure_type type, the mysql.proc. @param thd Thread context. - @param type Stored routine type - (TYPE_ENUM_PROCEDURE or TYPE_ENUM_FUNCTION). @param sp Stored routine object to store. @note Opens and closes the thread tables. Therefore assumes @@ -1018,16 +1155,14 @@ sp_drop_routine_internal(THD *thd, stored_procedure_type type, */ bool -sp_create_routine(THD *thd, stored_procedure_type type, sp_head *sp) +Sp_handler::sp_create_routine(THD *thd, const sp_head *sp) const { LEX *lex= thd->lex; bool ret= TRUE; TABLE *table; char definer_buf[USER_HOST_BUFF_SIZE]; - LEX_STRING definer; + LEX_CSTRING definer; sql_mode_t saved_mode= thd->variables.sql_mode; - MDL_key::enum_mdl_namespace mdl_type= type == TYPE_ENUM_FUNCTION ? - MDL_key::FUNCTION : MDL_key::PROCEDURE; CHARSET_INFO *db_cs= get_default_db_collation(thd, sp->m_db.str); @@ -1035,15 +1170,15 @@ sp_create_routine(THD *thd, stored_procedure_type type, sp_head *sp) bool store_failed= FALSE; DBUG_ENTER("sp_create_routine"); - DBUG_PRINT("enter", ("type: %d name: %.*s", (int) type, + DBUG_PRINT("enter", ("type: %s name: %.*s", + type_str(), (int) sp->m_name.length, sp->m_name.str)); + MDL_key::enum_mdl_namespace mdl_type= get_mdl_type(); + LEX_CSTRING returns= empty_clex_str; String retstr(64); retstr.set_charset(system_charset_info); - DBUG_ASSERT(type == TYPE_ENUM_PROCEDURE || - type == TYPE_ENUM_FUNCTION); - /* Grab an exclusive MDL lock. */ if (lock_object_name(thd, mdl_type, sp->m_db.str, sp->m_name.str)) { @@ -1071,17 +1206,32 @@ sp_create_routine(THD *thd, stored_procedure_type type, sp_head *sp) if (!(table= open_proc_table_for_update(thd))) { - my_error(ER_SP_STORE_FAILED, MYF(0), SP_TYPE_STRING(type),sp->m_name.str); + my_error(ER_SP_STORE_FAILED, MYF(0), type_str(), sp->m_name.str); goto done; } else { /* Checking if the routine already exists */ - if (db_find_routine_aux(thd, type, lex->spname, table) == SP_OK) + if (db_find_routine_aux(thd, sp, table) == SP_OK) { if (lex->create_info.or_replace()) { - if ((ret= sp_drop_routine_internal(thd, type, lex->spname, table))) + switch (type()) { + case TYPE_ENUM_PACKAGE: + // Drop together with its PACKAGE BODY mysql.proc record + ret= sp_handler_package_spec.sp_find_and_drop_routine(thd, table, sp); + break; + case TYPE_ENUM_PACKAGE_BODY: + case TYPE_ENUM_FUNCTION: + case TYPE_ENUM_PROCEDURE: + ret= sp_drop_routine_internal(thd, sp, table); + break; + case TYPE_ENUM_TRIGGER: + case TYPE_ENUM_PROXY: + DBUG_ASSERT(0); + ret= SP_OK; + } + if (ret) goto done; } else if (lex->create_info.if_not_exists()) @@ -1089,20 +1239,21 @@ sp_create_routine(THD *thd, stored_procedure_type type, sp_head *sp) push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, ER_SP_ALREADY_EXISTS, ER_THD(thd, ER_SP_ALREADY_EXISTS), - SP_TYPE_STRING(type), - lex->spname->m_name.str); + type_str(), sp->m_name.str); ret= FALSE; // Setting retstr as it is used for logging. - if (sp->m_type == TYPE_ENUM_FUNCTION) + if (type() == TYPE_ENUM_FUNCTION) + { sp_returns_type(thd, retstr, sp); + returns= retstr.lex_cstring(); + } goto log; } else { - my_error(ER_SP_ALREADY_EXISTS, MYF(0), - SP_TYPE_STRING(type), sp->m_name.str); + my_error(ER_SP_ALREADY_EXISTS, MYF(0), type_str(), sp->m_name.str); goto done; } } @@ -1114,8 +1265,7 @@ sp_create_routine(THD *thd, stored_procedure_type type, sp_head *sp) if (table->s->fields < MYSQL_PROC_FIELD_COUNT) { - my_error(ER_SP_STORE_FAILED, MYF(0), - SP_TYPE_STRING(type), sp->m_name.str); + my_error(ER_SP_STORE_FAILED, MYF(0), type_str(), sp->m_name.str); goto done; } @@ -1135,45 +1285,53 @@ sp_create_routine(THD *thd, stored_procedure_type type, sp_head *sp) store_failed= table->field[MYSQL_PROC_FIELD_DB]-> - store(sp->m_db.str, sp->m_db.length, system_charset_info); + store(sp->m_db, system_charset_info); store_failed= store_failed || table->field[MYSQL_PROC_FIELD_NAME]-> - store(sp->m_name.str, sp->m_name.length, system_charset_info); + store(sp->m_name, system_charset_info); + + if (sp->agg_type() != DEFAULT_AGGREGATE) + { + store_failed= store_failed || + table->field[MYSQL_PROC_FIELD_AGGREGATE]-> + store((longlong)sp->agg_type(),TRUE); + } store_failed= store_failed || table->field[MYSQL_PROC_MYSQL_TYPE]-> - store((longlong)type, TRUE); + store((longlong) type(), true); store_failed= store_failed || table->field[MYSQL_PROC_FIELD_SPECIFIC_NAME]-> - store(sp->m_name.str, sp->m_name.length, system_charset_info); + store(sp->m_name, system_charset_info); - if (sp->m_chistics->daccess != SP_DEFAULT_ACCESS) + if (sp->daccess() != SP_DEFAULT_ACCESS) { store_failed= store_failed || table->field[MYSQL_PROC_FIELD_ACCESS]-> - store((longlong)sp->m_chistics->daccess, TRUE); + store((longlong)sp->daccess(), TRUE); } store_failed= store_failed || table->field[MYSQL_PROC_FIELD_DETERMINISTIC]-> - store((longlong)(sp->m_chistics->detistic ? 1 : 2), TRUE); + store((longlong)(sp->detistic() ? 1 : 2), TRUE); - if (sp->m_chistics->suid != SP_IS_DEFAULT_SUID) + if (sp->suid() != SP_IS_DEFAULT_SUID) { store_failed= store_failed || table->field[MYSQL_PROC_FIELD_SECURITY_TYPE]-> - store((longlong)sp->m_chistics->suid, TRUE); + store((longlong)sp->suid(), TRUE); } store_failed= store_failed || table->field[MYSQL_PROC_FIELD_PARAM_LIST]-> - store(sp->m_params.str, sp->m_params.length, system_charset_info); + store(sp->m_params, system_charset_info); - if (sp->m_type == TYPE_ENUM_FUNCTION) + if (type() == TYPE_ENUM_FUNCTION) { sp_returns_type(thd, retstr, sp); + returns= retstr.lex_cstring(); store_failed= store_failed || table->field[MYSQL_PROC_FIELD_RETURNS]-> @@ -1182,11 +1340,11 @@ sp_create_routine(THD *thd, stored_procedure_type type, sp_head *sp) store_failed= store_failed || table->field[MYSQL_PROC_FIELD_BODY]-> - store(sp->m_body.str, sp->m_body.length, system_charset_info); + store(sp->m_body, system_charset_info); store_failed= store_failed || table->field[MYSQL_PROC_FIELD_DEFINER]-> - store(definer.str, definer.length, system_charset_info); + store(definer, system_charset_info); ((Field_timestamp *)table->field[MYSQL_PROC_FIELD_CREATED])->set_time(); ((Field_timestamp *)table->field[MYSQL_PROC_FIELD_MODIFIED])->set_time(); @@ -1195,26 +1353,25 @@ sp_create_routine(THD *thd, stored_procedure_type type, sp_head *sp) table->field[MYSQL_PROC_FIELD_SQL_MODE]-> store((longlong)saved_mode, TRUE); - if (sp->m_chistics->comment.str) + if (sp->comment().str) { store_failed= store_failed || table->field[MYSQL_PROC_FIELD_COMMENT]-> - store(sp->m_chistics->comment.str, sp->m_chistics->comment.length, - system_charset_info); + store(sp->comment(), system_charset_info); } - if ((sp->m_type == TYPE_ENUM_FUNCTION) && + if (type() == TYPE_ENUM_FUNCTION && !trust_function_creators && mysql_bin_log.is_open()) { - if (!sp->m_chistics->detistic) + if (!sp->detistic()) { /* Note that this test is not perfect; one could use a non-deterministic read-only function in an update statement. */ enum enum_sp_data_access access= - (sp->m_chistics->daccess == SP_DEFAULT_ACCESS) ? - SP_DEFAULT_ACCESS_MAPPING : sp->m_chistics->daccess; + (sp->daccess() == SP_DEFAULT_ACCESS) ? + SP_DEFAULT_ACCESS_MAPPING : sp->daccess(); if (access == SP_CONTAINS_SQL || access == SP_MODIFIES_SQL_DATA) { @@ -1251,7 +1408,7 @@ sp_create_routine(THD *thd, stored_procedure_type type, sp_head *sp) table->field[MYSQL_PROC_FIELD_BODY_UTF8]->set_notnull(); store_failed= store_failed || table->field[MYSQL_PROC_FIELD_BODY_UTF8]->store( - sp->m_body_utf8.str, sp->m_body_utf8.length, system_charset_info); + sp->m_body_utf8, system_charset_info); if (store_failed) { @@ -1261,8 +1418,7 @@ sp_create_routine(THD *thd, stored_procedure_type type, sp_head *sp) if (table->file->ha_write_row(table->record[0])) { - my_error(ER_SP_ALREADY_EXISTS, MYF(0), - SP_TYPE_STRING(type), sp->m_name.str); + my_error(ER_SP_ALREADY_EXISTS, MYF(0), type_str(), sp->m_name.str); goto done; } /* Make change permanent and avoid 'table is marked as crashed' errors */ @@ -1279,16 +1435,13 @@ log: StringBuffer<128> log_query(thd->variables.character_set_client); DBUG_ASSERT(log_query.charset()->mbminlen == 1); - if (!show_create_sp(thd, &log_query, - sp->m_type, - (sp->m_explicit_name ? sp->m_db.str : NULL), - (sp->m_explicit_name ? sp->m_db.length : 0), - sp->m_name.str, sp->m_name.length, - sp->m_params.str, sp->m_params.length, - retstr.ptr(), retstr.length(), - sp->m_body.str, sp->m_body.length, - sp->m_chistics, &(thd->lex->definer->user), - &(thd->lex->definer->host), + if (show_create_sp(thd, &log_query, + sp->m_explicit_name ? sp->m_db : null_clex_str, + sp->m_name, + sp->m_params, returns, sp->m_body, + sp->chistics(), + thd->lex->definer[0], + thd->lex->create_info, saved_mode)) { my_error(ER_OUT_OF_RESOURCES, MYF(0)); @@ -1316,6 +1469,69 @@ done: } +static bool +append_suid(String *buf, enum_sp_suid_behaviour suid) +{ + return suid == SP_IS_NOT_SUID && + buf->append(STRING_WITH_LEN(" SQL SECURITY INVOKER\n")); +} + + +static bool +append_comment(String *buf, const LEX_CSTRING &comment) +{ + if (!comment.length) + return false; + if (buf->append(STRING_WITH_LEN(" COMMENT "))) + return true; + append_unescaped(buf, comment.str, comment.length); + return buf->append('\n'); +} + + +static bool +append_package_chistics(String *buf, const st_sp_chistics &chistics) +{ + return append_suid(buf, chistics.suid) || + append_comment(buf, chistics.comment); +} + + +bool +Sp_handler_package::show_create_sp(THD *thd, String *buf, + const LEX_CSTRING &db, + const LEX_CSTRING &name, + const LEX_CSTRING ¶ms, + const LEX_CSTRING &returns, + const LEX_CSTRING &body, + const st_sp_chistics &chistics, + const AUTHID &definer, + const DDL_options_st ddl_options, + sql_mode_t sql_mode) const +{ + sql_mode_t old_sql_mode= thd->variables.sql_mode; + thd->variables.sql_mode= sql_mode; + bool rc= + buf->append(STRING_WITH_LEN("CREATE ")) || + (ddl_options.or_replace() && + buf->append(STRING_WITH_LEN("OR REPLACE "))) || + append_definer(thd, buf, &definer.user, &definer.host) || + buf->append(type_lex_cstring()) || + buf->append(" ", 1) || + (ddl_options.if_not_exists() && + buf->append(STRING_WITH_LEN("IF NOT EXISTS "))) || + (db.length > 0 && + (append_identifier(thd, buf, db.str, db.length) || + buf->append('.'))) || + append_identifier(thd, buf, name.str, name.length) || + append_package_chistics(buf, chistics) || + buf->append(" ", 1) || + buf->append(body.str, body.length); + thd->variables.sql_mode= old_sql_mode; + return rc; +} + + /** Delete the record for the stored routine object from mysql.proc and do binary logging. @@ -1324,8 +1540,6 @@ done: from the mysql.proc table and invalidates the stored-routine cache. @param thd Thread context. - @param type Stored routine type - (TYPE_ENUM_PROCEDURE or TYPE_ENUM_FUNCTION) @param name Stored routine name. @return Error code. SP_OK is returned on success. Other SP_ constants are @@ -1333,18 +1547,16 @@ done: */ int -sp_drop_routine(THD *thd, stored_procedure_type type, sp_name *name) +Sp_handler::sp_drop_routine(THD *thd, + const Database_qualified_name *name) const { TABLE *table; int ret; - MDL_key::enum_mdl_namespace mdl_type= type == TYPE_ENUM_FUNCTION ? - MDL_key::FUNCTION : MDL_key::PROCEDURE; DBUG_ENTER("sp_drop_routine"); - DBUG_PRINT("enter", ("type: %d name: %.*s", - type, (int) name->m_name.length, name->m_name.str)); - - DBUG_ASSERT(type == TYPE_ENUM_PROCEDURE || - type == TYPE_ENUM_FUNCTION); + DBUG_PRINT("enter", ("type: %s name: %.*s", + type_str(), + (int) name->m_name.length, name->m_name.str)); + MDL_key::enum_mdl_namespace mdl_type= get_mdl_type(); /* Grab an exclusive MDL lock. */ if (lock_object_name(thd, mdl_type, name->m_db.str, name->m_name.str)) @@ -1353,10 +1565,7 @@ sp_drop_routine(THD *thd, stored_procedure_type type, sp_name *name) if (!(table= open_proc_table_for_update(thd))) DBUG_RETURN(SP_OPEN_TABLE_FAILED); - if ((ret= db_find_routine_aux(thd, type, name, table)) == SP_OK) - ret= sp_drop_routine_internal(thd, type, name, table); - - if (ret == SP_OK && + if (SP_OK == (ret= sp_find_and_drop_routine(thd, table, name)) && write_bin_log(thd, TRUE, thd->query(), thd->query_length())) ret= SP_INTERNAL_ERROR; /* @@ -1377,8 +1586,6 @@ sp_drop_routine(THD *thd, stored_procedure_type type, sp_name *name) successful update, the cache is invalidated. @param thd Thread context. - @param type Stored routine type - (TYPE_ENUM_PROCEDURE or TYPE_ENUM_FUNCTION) @param name Stored routine name. @param chistics New values of stored routine attributes to write. @@ -1387,20 +1594,16 @@ sp_drop_routine(THD *thd, stored_procedure_type type, sp_name *name) */ int -sp_update_routine(THD *thd, stored_procedure_type type, sp_name *name, - st_sp_chistics *chistics) +Sp_handler::sp_update_routine(THD *thd, const Database_qualified_name *name, + const st_sp_chistics *chistics) const { TABLE *table; int ret; - MDL_key::enum_mdl_namespace mdl_type= type == TYPE_ENUM_FUNCTION ? - MDL_key::FUNCTION : MDL_key::PROCEDURE; DBUG_ENTER("sp_update_routine"); - DBUG_PRINT("enter", ("type: %d name: %.*s", - (int) type, + DBUG_PRINT("enter", ("type: %s name: %.*s", + type_str(), (int) name->m_name.length, name->m_name.str)); - - DBUG_ASSERT(type == TYPE_ENUM_PROCEDURE || - type == TYPE_ENUM_FUNCTION); + MDL_key::enum_mdl_namespace mdl_type= get_mdl_type(); /* Grab an exclusive MDL lock. */ if (lock_object_name(thd, mdl_type, name->m_db.str, name->m_name.str)) @@ -1409,9 +1612,9 @@ sp_update_routine(THD *thd, stored_procedure_type type, sp_name *name, if (!(table= open_proc_table_for_update(thd))) DBUG_RETURN(SP_OPEN_TABLE_FAILED); - if ((ret= db_find_routine_aux(thd, type, name, table)) == SP_OK) + if ((ret= db_find_routine_aux(thd, name, table)) == SP_OK) { - if (type == TYPE_ENUM_FUNCTION && ! trust_function_creators && + if (type() == TYPE_ENUM_FUNCTION && ! trust_function_creators && mysql_bin_log.is_open() && (chistics->daccess == SP_CONTAINS_SQL || chistics->daccess == SP_MODIFIES_SQL_DATA)) @@ -1444,9 +1647,11 @@ sp_update_routine(THD *thd, stored_procedure_type type, sp_name *name, table->field[MYSQL_PROC_FIELD_ACCESS]-> store((longlong)chistics->daccess, TRUE); if (chistics->comment.str) - table->field[MYSQL_PROC_FIELD_COMMENT]->store(chistics->comment.str, - chistics->comment.length, + table->field[MYSQL_PROC_FIELD_COMMENT]->store(chistics->comment, system_charset_info); + if (chistics->agg_type != DEFAULT_AGGREGATE) + table->field[MYSQL_PROC_FIELD_AGGREGATE]-> + store((longlong)chistics->agg_type, TRUE); if ((ret= table->file->ha_update_row(table->record[1],table->record[0])) && ret != HA_ERR_RECORD_IS_THE_SAME) ret= SP_WRITE_ROW_FAILED; @@ -1502,7 +1707,7 @@ public: cases. */ -bool lock_db_routines(THD *thd, char *db) +bool lock_db_routines(THD *thd, const char *db) { TABLE *table; uint key_len; @@ -1512,7 +1717,7 @@ bool lock_db_routines(THD *thd, char *db) uchar keybuf[MAX_KEY_LENGTH]; DBUG_ENTER("lock_db_routines"); - DBUG_ASSERT(ok_for_lower_case_names(db)); + DBUG_SLOW_ASSERT(ok_for_lower_case_names(db)); /* mysql.proc will be re-opened during deletion, so we can ignore @@ -1555,9 +1760,12 @@ bool lock_db_routines(THD *thd, char *db) longlong sp_type= table->field[MYSQL_PROC_MYSQL_TYPE]->val_int(); MDL_request *mdl_request= new (thd->mem_root) MDL_request; - mdl_request->init(sp_type == TYPE_ENUM_FUNCTION ? - MDL_key::FUNCTION : MDL_key::PROCEDURE, - db, sp_name, MDL_EXCLUSIVE, MDL_TRANSACTION); + const Sp_handler *sph= Sp_handler::handler((stored_procedure_type) + sp_type); + if (!sph) + sph= &sp_handler_procedure; + mdl_request->init(sph->get_mdl_type(), db, sp_name, + MDL_EXCLUSIVE, MDL_TRANSACTION); mdl_requests.push_front(mdl_request); } while (! (nxtres= table->file->ha_index_next_same(table->record[0], keybuf, key_len))); } @@ -1588,7 +1796,7 @@ bool lock_db_routines(THD *thd, char *db) */ int -sp_drop_db_routines(THD *thd, char *db) +sp_drop_db_routines(THD *thd, const char *db) { TABLE *table; int ret; @@ -1661,8 +1869,6 @@ err: calls sp_head::show_create_routine() for the object. @param thd Thread context. - @param type Stored routine type - (TYPE_ENUM_PROCEDURE or TYPE_ENUM_FUNCTION) @param name Stored routine name. @return Error status. @@ -1671,18 +1877,16 @@ err: */ bool -sp_show_create_routine(THD *thd, stored_procedure_type type, sp_name *name) +Sp_handler::sp_show_create_routine(THD *thd, + const Database_qualified_name *name) const { sp_head *sp; DBUG_ENTER("sp_show_create_routine"); - DBUG_PRINT("enter", ("name: %.*s", + DBUG_PRINT("enter", ("type: %s name: %.*s", + type_str(), (int) name->m_name.length, name->m_name.str)); - - DBUG_ASSERT(type == TYPE_ENUM_PROCEDURE || - type == TYPE_ENUM_FUNCTION); - /* @todo: Consider using prelocking for this code as well. Currently SHOW CREATE PROCEDURE/FUNCTION is a dirty read of the data @@ -1690,18 +1894,16 @@ sp_show_create_routine(THD *thd, stored_procedure_type type, sp_name *name) It is "safe" to do as long as it doesn't affect the results of the binary log or the query cache, which currently it does not. */ - if (sp_cache_routine(thd, type, name, FALSE, &sp)) + if (sp_cache_routine(thd, name, false, &sp)) DBUG_RETURN(TRUE); - if (sp == NULL || sp->show_create_routine(thd, type)) + if (sp == NULL || sp->show_create_routine(thd, this)) { /* If we have insufficient privileges, pretend the routine does not exist. */ - my_error(ER_SP_DOES_NOT_EXIST, MYF(0), - type == TYPE_ENUM_FUNCTION ? "FUNCTION" : "PROCEDURE", - name->m_name.str); + my_error(ER_SP_DOES_NOT_EXIST, MYF(0), type_str(), name->m_name.str); DBUG_RETURN(TRUE); } @@ -1709,12 +1911,176 @@ sp_show_create_routine(THD *thd, stored_procedure_type type, sp_name *name) } +/* + A helper class to split package name from a dot-qualified name + and return it as a 0-terminated string + 'pkg.name' -> 'pkg\0' +*/ +class Prefix_name_buf: public LEX_CSTRING +{ + char m_buf[SAFE_NAME_LEN + 1]; +public: + Prefix_name_buf(const THD *thd, const LEX_CSTRING &name) + { + const char *end; + if (!(end= strrchr(name.str, '.'))) + { + static_cast<LEX_CSTRING*>(this)[0]= null_clex_str; + } + else + { + str= m_buf; + length= end - name.str; + set_if_smaller(length, sizeof(m_buf) - 1); + memcpy(m_buf, name.str, length); + m_buf[length]= '\0'; + } + } +}; + + +/* + In case of recursions, we create multiple copies of the same SP. + This methods checks the current recursion depth. + In case if the recursion limit exceeded, it throws an error + and returns NULL. + Otherwise, depending on the current recursion level, it: + - either returns the original SP, + - or makes and returns a new clone of SP +*/ +sp_head * +Sp_handler::sp_clone_and_link_routine(THD *thd, + const Database_qualified_name *name, + sp_head *sp) const +{ + DBUG_ENTER("sp_link_routine"); + int rc; + ulong level; + sp_head *new_sp; + LEX_CSTRING returns= empty_clex_str; + Database_qualified_name lname(name->m_db, name->m_name); +#ifndef DBUG_OFF + uint parent_subroutine_count= + !sp->m_parent ? 0 : + sp->m_parent->m_routine_declarations.elements + + sp->m_parent->m_routine_implementations.elements; +#endif + + /* + String buffer for RETURNS data type must have system charset; + 64 -- size of "returns" column of mysql.proc. + */ + String retstr(64); + retstr.set_charset(sp->get_creation_ctx()->get_client_cs()); + + DBUG_PRINT("info", ("found: %p", sp)); + if (sp->m_first_free_instance) + { + DBUG_PRINT("info", ("first free: %p level: %lu flags %x", + sp->m_first_free_instance, + sp->m_first_free_instance->m_recursion_level, + sp->m_first_free_instance->m_flags)); + DBUG_ASSERT(!(sp->m_first_free_instance->m_flags & sp_head::IS_INVOKED)); + if (sp->m_first_free_instance->m_recursion_level > recursion_depth(thd)) + { + recursion_level_error(thd, sp); + DBUG_RETURN(0); + } + DBUG_RETURN(sp->m_first_free_instance); + } + /* + Actually depth could be +1 than the actual value in case a SP calls + SHOW CREATE PROCEDURE. Hence, the linked list could hold up to one more + instance. + */ + + level= sp->m_last_cached_sp->m_recursion_level + 1; + if (level > recursion_depth(thd)) + { + recursion_level_error(thd, sp); + DBUG_RETURN(0); + } + + if (type() == TYPE_ENUM_FUNCTION) + { + sp_returns_type(thd, retstr, sp); + returns= retstr.lex_cstring(); + } + + if (sp->m_parent) + { + /* + If we're cloning a recursively called package routine, + we need to take some special measures: + 1. Cut the package name prefix from the routine name: 'pkg1.p1' -> 'p1', + to have db_load_routine() generate and parse a query like this: + CREATE PROCEDURE p1 ...; + rether than: + CREATE PROCEDURE pkg1.p1 ...; + The latter would be misinterpreted by the parser as a standalone + routine 'p1' in the database 'pkg1', which is not what we need. + 2. We pass m_parent to db_load_routine() to have it set + thd->lex->sphead to sp->m_parent before calling parse_sql(). + These two measures allow to parse a package subroutine using + the grammar for standalone routines, e.g.: + CREATE PROCEDURE p1 ... END; + instead of going through a more complex query, e.g.: + CREATE PACKAGE BODY pkg1 AS + PROCEDURE p1 ... END; + END; + */ + size_t prefix_length= sp->m_parent->m_name.length + 1; + DBUG_ASSERT(prefix_length < lname.m_name.length); + DBUG_ASSERT(lname.m_name.str[sp->m_parent->m_name.length] == '.'); + lname.m_name.str+= prefix_length; + lname.m_name.length-= prefix_length; + sp->m_parent->m_is_cloning_routine= true; + } + + + rc= db_load_routine(thd, &lname, &new_sp, + sp->m_sql_mode, sp->m_params, returns, + sp->m_body, sp->chistics(), + sp->m_definer, + sp->m_created, sp->m_modified, + sp->m_parent, + sp->get_creation_ctx()); + if (sp->m_parent) + sp->m_parent->m_is_cloning_routine= false; + + if (rc == SP_OK) + { +#ifndef DBUG_OFF + /* + We've just called the parser to clone the routine. + In case of a package routine, make sure that the parser + has not added any new subroutines directly to the parent package. + The cloned subroutine instances get linked below to the first instance, + they must have no direct links from the parent package. + */ + DBUG_ASSERT(!sp->m_parent || + parent_subroutine_count == + sp->m_parent->m_routine_declarations.elements + + sp->m_parent->m_routine_implementations.elements); +#endif + sp->m_last_cached_sp->m_next_cached_sp= new_sp; + new_sp->m_recursion_level= level; + new_sp->m_first_instance= sp; + sp->m_last_cached_sp= sp->m_first_free_instance= new_sp; + DBUG_PRINT("info", ("added level: %p, level: %lu, flags %x", + new_sp, new_sp->m_recursion_level, + new_sp->m_flags)); + DBUG_RETURN(new_sp); + } + DBUG_RETURN(0); +} + + /** Obtain object representing stored procedure/function by its name from stored procedures cache and looking into mysql.proc if needed. @param thd thread context - @param type type of object (TYPE_ENUM_FUNCTION or TYPE_ENUM_PROCEDURE) @param name name of procedure @param cp hash to look routine in @param cache_only if true perform cache-only lookup @@ -1727,94 +2093,90 @@ sp_show_create_routine(THD *thd, stored_procedure_type type, sp_name *name) */ sp_head * -sp_find_routine(THD *thd, stored_procedure_type type, sp_name *name, - sp_cache **cp, bool cache_only) +Sp_handler::sp_find_routine(THD *thd, const Database_qualified_name *name, + bool cache_only) const { - sp_head *sp; - ulong depth= (type == TYPE_ENUM_PROCEDURE ? - thd->variables.max_sp_recursion_depth : - 0); - DBUG_ENTER("sp_find_routine"); - DBUG_PRINT("enter", ("name: %.*s.%.*s type: %d cache only %d", + DBUG_ENTER("Sp_handler::sp_find_routine"); + DBUG_PRINT("enter", ("name: %.*s.%.*s type: %s cache only %d", (int) name->m_db.length, name->m_db.str, (int) name->m_name.length, name->m_name.str, - type, cache_only)); + type_str(), cache_only)); + sp_cache **cp= get_cache(thd); + sp_head *sp; if ((sp= sp_cache_lookup(cp, name))) - { - ulong level; - sp_head *new_sp; - const char *returns= ""; + DBUG_RETURN(sp_clone_and_link_routine(thd, name, sp)); + if (!cache_only) + db_find_and_cache_routine(thd, name, &sp); + DBUG_RETURN(sp); +} - /* - String buffer for RETURNS data type must have system charset; - 64 -- size of "returns" column of mysql.proc. - */ - String retstr(64); - retstr.set_charset(sp->get_creation_ctx()->get_client_cs()); - DBUG_PRINT("info", ("found:%p", sp)); - if (sp->m_first_free_instance) - { - DBUG_PRINT("info", ("first free:%p level: %lu flags %x", - sp->m_first_free_instance, - sp->m_first_free_instance->m_recursion_level, - sp->m_first_free_instance->m_flags)); - DBUG_ASSERT(!(sp->m_first_free_instance->m_flags & sp_head::IS_INVOKED)); - if (sp->m_first_free_instance->m_recursion_level > depth) - { - sp->recursion_level_error(thd); - DBUG_RETURN(0); - } - DBUG_RETURN(sp->m_first_free_instance); - } - /* - Actually depth could be +1 than the actual value in case a SP calls - SHOW CREATE PROCEDURE. Hence, the linked list could hold up to one more - instance. - */ +/** + Find a package routine. + See sp_cache_routine() for more information on parameters and return value. + + @param thd - current THD + @param pkgname_str - package name + @param name - a mixed qualified name, with: + * name->m_db set to the database, e.g. "dbname" + * name->m_name set to a package-qualified name, + e.g. "pkgname.spname". + @param cache_only - don't load mysql.proc if not cached + @retval non-NULL - a pointer to an sp_head object + @retval NULL - an error happened. +*/ +sp_head * +Sp_handler::sp_find_package_routine(THD *thd, + const LEX_CSTRING pkgname_str, + const Database_qualified_name *name, + bool cache_only) const +{ + DBUG_ENTER("sp_find_package_routine"); + Database_qualified_name pkgname(&name->m_db, &pkgname_str); + sp_head *ph= sp_cache_lookup(&thd->sp_package_body_cache, &pkgname); + if (!ph && !cache_only) + sp_handler_package_body.db_find_and_cache_routine(thd, &pkgname, &ph); + if (ph) + { + LEX_CSTRING tmp= name->m_name; + const char *dot= strrchr(tmp.str, '.'); + size_t prefix_length= dot ? dot - tmp.str + 1 : 0; + sp_package *pkg= ph->get_package(); + tmp.str+= prefix_length; + tmp.length-= prefix_length; + LEX *plex= pkg ? pkg->m_routine_implementations.find(tmp, type()) : NULL; + sp_head *sp= plex ? plex->sphead : NULL; + if (sp) + DBUG_RETURN(sp_clone_and_link_routine(thd, name, sp)); + } + DBUG_RETURN(NULL); +} - level= sp->m_last_cached_sp->m_recursion_level + 1; - if (level > depth) - { - sp->recursion_level_error(thd); - DBUG_RETURN(0); - } - if (type == TYPE_ENUM_FUNCTION) - { - sp_returns_type(thd, retstr, sp); - returns= retstr.ptr(); - } - if (db_load_routine(thd, type, name, &new_sp, - sp->m_sql_mode, sp->m_params.str, returns, - sp->m_body.str, *sp->m_chistics, - &sp->m_definer_user, &sp->m_definer_host, - sp->m_created, sp->m_modified, - sp->get_creation_ctx()) == SP_OK) - { - sp->m_last_cached_sp->m_next_cached_sp= new_sp; - new_sp->m_recursion_level= level; - new_sp->m_first_instance= sp; - sp->m_last_cached_sp= sp->m_first_free_instance= new_sp; - DBUG_PRINT("info", ("added level:%p, level: %lu, flags %x", - new_sp, new_sp->m_recursion_level, - new_sp->m_flags)); - DBUG_RETURN(new_sp); - } - DBUG_RETURN(0); - } - if (!cache_only) - { - if (db_find_routine(thd, type, name, &sp) == SP_OK) - { - sp_cache_insert(cp, sp); - DBUG_PRINT("info", ("added new:%p, level: %lu, flags %x", - sp, sp->m_recursion_level, - sp->m_flags)); - } - } - DBUG_RETURN(sp); +/** + Find a package routine. + See sp_cache_routine() for more information on parameters and return value. + + @param thd - current THD + @param name - Qualified name with the following format: + * name->m_db is set to the database name, e.g. "dbname" + * name->m_name is set to a package-qualified name, + e.g. "pkgname.spname", as a single string with a + dot character as a separator. + @param cache_only - don't load mysql.proc if not cached + @retval non-NULL - a pointer to an sp_head object + @retval NULL - an error happened +*/ +sp_head * +Sp_handler::sp_find_package_routine(THD *thd, + const Database_qualified_name *name, + bool cache_only) const +{ + DBUG_ENTER("Sp_handler::sp_find_package_routine"); + Prefix_name_buf pkgname(thd, name->m_name); + DBUG_ASSERT(pkgname.length); + DBUG_RETURN(sp_find_package_routine(thd, pkgname, name, cache_only)); } @@ -1824,8 +2186,6 @@ sp_find_routine(THD *thd, stored_procedure_type type, sp_name *name, @param thd Thread handler @param routines List of needles in the hay stack - @param is_proc Indicates whether routines in the list are procedures - or functions. @return @retval FALSE Found. @@ -1833,7 +2193,7 @@ sp_find_routine(THD *thd, stored_procedure_type type, sp_name *name, */ bool -sp_exist_routines(THD *thd, TABLE_LIST *routines, bool is_proc) +Sp_handler::sp_exist_routines(THD *thd, TABLE_LIST *routines) const { TABLE_LIST *routine; bool sp_object_found; @@ -1841,25 +2201,18 @@ sp_exist_routines(THD *thd, TABLE_LIST *routines, bool is_proc) for (routine= routines; routine; routine= routine->next_global) { sp_name *name; - LEX_STRING lex_db; - LEX_STRING lex_name; - lex_db.length= strlen(routine->db); - lex_name.length= strlen(routine->table_name); - lex_db.str= thd->strmake(routine->db, lex_db.length); - lex_name.str= thd->strmake(routine->table_name, lex_name.length); - name= new sp_name(lex_db, lex_name, true); - name->init_qname(thd); - sp_object_found= is_proc ? sp_find_routine(thd, TYPE_ENUM_PROCEDURE, - name, &thd->sp_proc_cache, - FALSE) != NULL : - sp_find_routine(thd, TYPE_ENUM_FUNCTION, - name, &thd->sp_func_cache, - FALSE) != NULL; + LEX_CSTRING lex_db; + LEX_CSTRING lex_name; + thd->make_lex_string(&lex_db, routine->db.str, routine->db.length); + thd->make_lex_string(&lex_name, routine->table_name.str, + routine->table_name.length); + name= new sp_name(&lex_db, &lex_name, true); + sp_object_found= sp_find_routine(thd, name, false) != NULL; thd->get_stmt_da()->clear_warning_info(thd->query_id); if (! sp_object_found) { my_error(ER_SP_DOES_NOT_EXIST, MYF(0), "FUNCTION or PROCEDURE", - routine->table_name); + routine->table_name.str); DBUG_RETURN(TRUE); } } @@ -1910,7 +2263,9 @@ extern "C" uchar* sp_sroutine_key(const uchar *ptr, size_t *plen, */ bool sp_add_used_routine(Query_tables_list *prelocking_ctx, Query_arena *arena, - const MDL_key *key, TABLE_LIST *belong_to_view) + const MDL_key *key, + const Sp_handler *handler, + TABLE_LIST *belong_to_view) { my_hash_init_opt(&prelocking_ctx->sroutines, system_charset_info, Query_tables_list::START_SROUTINES_HASH_SIZE, @@ -1927,6 +2282,7 @@ bool sp_add_used_routine(Query_tables_list *prelocking_ctx, Query_arena *arena, return FALSE; prelocking_ctx->sroutines_list.link_in_list(rn, &rn->next); rn->belong_to_view= belong_to_view; + rn->m_handler= handler; rn->m_sp_cache_version= 0; return TRUE; } @@ -1934,6 +2290,268 @@ bool sp_add_used_routine(Query_tables_list *prelocking_ctx, Query_arena *arena, } +/* + Find and cache a routine in a parser-safe reentrant mode. + + If sp_head is not in the cache, + its loaded from mysql.proc, parsed using parse_sql(), and cached. + Note, as it is called from inside parse_sql() itself, + we need to preserve and restore the parser state. + + It's used during parsing of CREATE PACKAGE BODY, + to load the corresponding CREATE PACKAGE. +*/ +int +Sp_handler::sp_cache_routine_reentrant(THD *thd, + const Database_qualified_name *name, + sp_head **sp) const +{ + int ret; + Parser_state *oldps= thd->m_parser_state; + thd->m_parser_state= NULL; + ret= sp_cache_routine(thd, name, false, sp); + thd->m_parser_state= oldps; + return ret; +} + + +/* + Check if a routine has a declaration in the CREATE PACKAGE statement, + by looking up in thd->sp_package_spec_cache, and by loading from mysql.proc + if needed. + + @param thd current thd + @param db the database name + @param package the package name + @param name the routine name + @param type the routine type + @retval true, if the routine has a declaration + @retval false, if the routine does not have a declaration + + This function can be called in arbitrary context: + - inside a package routine + - inside a standalone routine + - inside a anonymous block + - outside of any routines + + The state of the package specification (i.e. the CREATE PACKAGE statement) + for "package" before the call of this function is not known: + it can be cached, or not cached. + After the call of this function, the package specification is always cached, + unless a fatal error happens. +*/ +static bool +is_package_public_routine(THD *thd, + const LEX_CSTRING &db, + const LEX_CSTRING &package, + const LEX_CSTRING &routine, + stored_procedure_type type) +{ + sp_head *sp= NULL; + Database_qualified_name tmp(db, package); + bool ret= sp_handler_package_spec. + sp_cache_routine_reentrant(thd, &tmp, &sp); + sp_package *spec= (!ret && sp) ? sp->get_package() : NULL; + return spec && spec->m_routine_declarations.find(routine, type); +} + + +/* + Check if a routine has a declaration in the CREATE PACKAGE statement + by looking up in sp_package_spec_cache. + + @param thd current thd + @param db the database name + @param pkgname the package name + @param name the routine name + @param type the routine type + @retval true, if the routine has a declaration + @retval false, if the routine does not have a declaration + + This function is called in the middle of CREATE PACKAGE BODY parsing, + to lookup the current package routines. + The package specification (i.e. the CREATE PACKAGE statement) for + the current package body must already be loaded and cached at this point. +*/ +static bool +is_package_public_routine_quick(THD *thd, + const LEX_CSTRING &db, + const LEX_CSTRING &pkgname, + const LEX_CSTRING &name, + stored_procedure_type type) +{ + Database_qualified_name tmp(db, pkgname); + sp_head *sp= sp_cache_lookup(&thd->sp_package_spec_cache, &tmp); + sp_package *pkg= sp ? sp->get_package() : NULL; + DBUG_ASSERT(pkg); // Must already be cached + return pkg && pkg->m_routine_declarations.find(name, type); +} + + +/* + Check if a qualified name, e.g. "CALL name1.name2", + refers to a known routine in the package body "pkg". +*/ +static bool +is_package_body_routine(THD *thd, sp_package *pkg, + const LEX_CSTRING &name1, + const LEX_CSTRING &name2, + stored_procedure_type type) +{ + return Sp_handler::eq_routine_name(pkg->m_name, name1) && + (pkg->m_routine_declarations.find(name2, type) || + pkg->m_routine_implementations.find(name2, type)); +} + + +/* + Resolve a qualified routine reference xxx.yyy(), between: + - A standalone routine: xxx.yyy + - A package routine: current_database.xxx.yyy +*/ +bool Sp_handler:: + sp_resolve_package_routine_explicit(THD *thd, + sp_head *caller, + sp_name *name, + const Sp_handler **pkg_routine_handler, + Database_qualified_name *pkgname) const +{ + sp_package *pkg; + + /* + If a qualified routine name was used, e.g. xxx.yyy(), + we possibly have a call to a package routine. + Rewrite name if name->m_db (xxx) is a known package, + and name->m_name (yyy) is a known routine in this package. + */ + LEX_CSTRING tmpdb= thd->db; + if (is_package_public_routine(thd, tmpdb, name->m_db, name->m_name, type()) || + // Check if a package routine calls a private routine + (caller && caller->m_parent && + is_package_body_routine(thd, caller->m_parent, + name->m_db, name->m_name, type())) || + // Check if a package initialization sections calls a private routine + (caller && (pkg= caller->get_package()) && + is_package_body_routine(thd, pkg, name->m_db, name->m_name, type()))) + { + pkgname->m_db= tmpdb; + pkgname->m_name= name->m_db; + *pkg_routine_handler= package_routine_handler(); + return name->make_package_routine_name(thd->mem_root, tmpdb, + name->m_db, name->m_name); + } + return false; +} + + +/* + Resolve a non-qualified routine reference yyy(), between: + - A standalone routine: current_database.yyy + - A package routine: current_database.current_package.yyy +*/ +bool Sp_handler:: + sp_resolve_package_routine_implicit(THD *thd, + sp_head *caller, + sp_name *name, + const Sp_handler **pkg_routine_handler, + Database_qualified_name *pkgname) const +{ + sp_package *pkg; + + if (!caller || !caller->m_name.length) + { + /* + We are either in a an anonymous block, + or not in a routine at all. + */ + return false; // A standalone routine is called + } + + if (caller->m_parent) + { + // A package routine calls a non-qualified routine + int ret= SP_OK; + Prefix_name_buf pkgstr(thd, caller->m_name); + DBUG_ASSERT(pkgstr.length); + LEX_CSTRING tmpname; // Non-qualified m_name + tmpname.str= caller->m_name.str + pkgstr.length + 1; + tmpname.length= caller->m_name.length - pkgstr.length - 1; + + /* + We're here if a package routine calls another non-qualified + function or procedure, e.g. yyy(). + We need to distinguish two cases: + - yyy() is another routine from the same package + - yyy() is a standalone routine from the same database + To detect if yyy() is a package (rather than a standalone) routine, + we check if: + - yyy() recursively calls itself + - yyy() is earlier implemented in the current CREATE PACKAGE BODY + - yyy() has a forward declaration + - yyy() is declared in the corresponding CREATE PACKAGE + */ + if (eq_routine_name(tmpname, name->m_name) || + caller->m_parent->m_routine_implementations.find(name->m_name, type()) || + caller->m_parent->m_routine_declarations.find(name->m_name, type()) || + is_package_public_routine_quick(thd, caller->m_db, + pkgstr, name->m_name, type())) + { + DBUG_ASSERT(ret == SP_OK); + pkgname->copy(thd->mem_root, caller->m_db, pkgstr); + *pkg_routine_handler= package_routine_handler(); + if (name->make_package_routine_name(thd->mem_root, pkgstr, name->m_name)) + return true; + } + return ret != SP_OK; + } + + if ((pkg= caller->get_package()) && + pkg->m_routine_implementations.find(name->m_name, type())) + { + pkgname->m_db= caller->m_db; + pkgname->m_name= caller->m_name; + // Package initialization section is calling a non-qualified routine + *pkg_routine_handler= package_routine_handler(); + return name->make_package_routine_name(thd->mem_root, + caller->m_name, name->m_name); + } + + return false; // A standalone routine is called + +} + + +/** + Detect cases when a package routine (rather than a standalone routine) + is called, and rewrite sp_name accordingly. + + @param thd Current thd + @param caller The caller routine (or NULL if outside of a routine) + @param [IN/OUT] name The called routine name + @param [OUT] pkgname If the routine is found to be a package routine, + pkgname is populated with the package name. + Otherwise, it's not touched. + @retval false on success + @retval true on error (e.g. EOM, could not read CREATE PACKAGE) +*/ +bool +Sp_handler::sp_resolve_package_routine(THD *thd, + sp_head *caller, + sp_name *name, + const Sp_handler **pkg_routine_handler, + Database_qualified_name *pkgname) const +{ + if (!thd->db.length || !(thd->variables.sql_mode & MODE_ORACLE)) + return false; + + return name->m_explicit_name ? + sp_resolve_package_routine_explicit(thd, caller, name, + pkg_routine_handler, pkgname) : + sp_resolve_package_routine_implicit(thd, caller, name, + pkg_routine_handler, pkgname); +} + + /** Add routine which is explicitly used by statement to the set of stored routines used by this statement. @@ -1945,20 +2563,18 @@ bool sp_add_used_routine(Query_tables_list *prelocking_ctx, Query_arena *arena, @param arena Arena in which memory for new element of the set will be allocated @param rt Routine name - @param rt_type Routine type (one of TYPE_ENUM_PROCEDURE/...) @note Will also add element to end of 'Query_tables_list::sroutines_list' list (and will take into account that this is an explicitly used routine). */ -void sp_add_used_routine(Query_tables_list *prelocking_ctx, Query_arena *arena, - sp_name *rt, enum stored_procedure_type rt_type) +void Sp_handler::add_used_routine(Query_tables_list *prelocking_ctx, + Query_arena *arena, + const Database_qualified_name *rt) const { - MDL_key key((rt_type == TYPE_ENUM_FUNCTION) ? MDL_key::FUNCTION : - MDL_key::PROCEDURE, - rt->m_db.str, rt->m_name.str); - (void)sp_add_used_routine(prelocking_ctx, arena, &key, 0); + MDL_key key(get_mdl_type(), rt->m_db.str, rt->m_name.str); + (void) sp_add_used_routine(prelocking_ctx, arena, &key, this, 0); prelocking_ctx->sroutines_list_own_last= prelocking_ctx->sroutines_list.next; prelocking_ctx->sroutines_list_own_elements= prelocking_ctx->sroutines_list.elements; @@ -2051,7 +2667,8 @@ sp_update_stmt_used_routines(THD *thd, Query_tables_list *prelocking_ctx, { Sroutine_hash_entry *rt= (Sroutine_hash_entry *)my_hash_element(src, i); (void)sp_add_used_routine(prelocking_ctx, thd->stmt_arena, - &rt->mdl_request.key, belong_to_view); + &rt->mdl_request.key, rt->m_handler, + belong_to_view); } } @@ -2076,7 +2693,8 @@ void sp_update_stmt_used_routines(THD *thd, Query_tables_list *prelocking_ctx, { for (Sroutine_hash_entry *rt= src->first; rt; rt= rt->next) (void)sp_add_used_routine(prelocking_ctx, thd->stmt_arena, - &rt->mdl_request.key, belong_to_view); + &rt->mdl_request.key, rt->m_handler, + belong_to_view); } @@ -2085,24 +2703,21 @@ void sp_update_stmt_used_routines(THD *thd, Query_tables_list *prelocking_ctx, prelocking until 'sp_name' is eradicated as a class. */ -int sp_cache_routine(THD *thd, Sroutine_hash_entry *rt, - bool lookup_only, sp_head **sp) +int Sroutine_hash_entry::sp_cache_routine(THD *thd, + bool lookup_only, + sp_head **sp) const { char qname_buff[NAME_LEN*2+1+1]; - sp_name name(&rt->mdl_request.key, qname_buff); - MDL_key::enum_mdl_namespace mdl_type= rt->mdl_request.key.mdl_namespace(); - stored_procedure_type type= ((mdl_type == MDL_key::FUNCTION) ? - TYPE_ENUM_FUNCTION : TYPE_ENUM_PROCEDURE); - + sp_name name(&mdl_request.key, qname_buff); /* Check that we have an MDL lock on this routine, unless it's a top-level CALL. The assert below should be unambiguous: the first element in sroutines_list has an MDL lock unless it's a top-level call, or a trigger, but triggers can't occur here (see the preceding assert). */ - DBUG_ASSERT(rt->mdl_request.ticket || rt == thd->lex->sroutines_list.first); + DBUG_ASSERT(mdl_request.ticket || this == thd->lex->sroutines_list.first); - return sp_cache_routine(thd, type, &name, lookup_only, sp); + return m_handler->sp_cache_routine(thd, &name, lookup_only, sp); } @@ -2113,7 +2728,6 @@ int sp_cache_routine(THD *thd, Sroutine_hash_entry *rt, loading. @param[in] thd Thread context. - @param[in] type Type of object (TYPE_ENUM_FUNCTION or TYPE_ENUM_PROCEDURE). @param[in] name Name of routine. @param[in] lookup_only Only check that the routine is in the cache. If it's not, don't try to load. If it is present, @@ -2126,16 +2740,17 @@ int sp_cache_routine(THD *thd, Sroutine_hash_entry *rt, @retval non-0 Error while loading routine from mysql,proc table. */ -int sp_cache_routine(THD *thd, enum stored_procedure_type type, sp_name *name, - bool lookup_only, sp_head **sp) +int Sp_handler::sp_cache_routine(THD *thd, + const Database_qualified_name *name, + bool lookup_only, + sp_head **sp) const { int ret= 0; - sp_cache **spc= (type == TYPE_ENUM_FUNCTION ? - &thd->sp_func_cache : &thd->sp_proc_cache); + sp_cache **spc= get_cache(thd); - DBUG_ENTER("sp_cache_routine"); + DBUG_ENTER("Sp_handler::sp_cache_routine"); - DBUG_ASSERT(type == TYPE_ENUM_FUNCTION || type == TYPE_ENUM_PROCEDURE); + DBUG_ASSERT(spc); *sp= sp_cache_lookup(spc, name); @@ -2149,10 +2764,9 @@ int sp_cache_routine(THD *thd, enum stored_procedure_type type, sp_name *name, DBUG_RETURN(SP_OK); } - switch ((ret= db_find_routine(thd, type, name, sp))) + switch ((ret= db_find_and_cache_routine(thd, name, sp))) { case SP_OK: - sp_cache_insert(spc, *sp); break; case SP_KEY_NOT_FOUND: ret= SP_OK; @@ -2175,20 +2789,8 @@ int sp_cache_routine(THD *thd, enum stored_procedure_type type, sp_name *name, */ if (! thd->is_error()) { - /* - SP allows full NAME_LEN chars thus he have to allocate enough - size in bytes. Otherwise there is stack overrun could happen - if multibyte sequence is `name`. `db` is still safe because the - rest of the server checks agains NAME_LEN bytes and not chars. - Hence, the overrun happens only if the name is in length > 32 and - uses multibyte (cyrillic, greek, etc.) - */ - char n[NAME_LEN*2+2]; - - /* m_qname.str is not always \0 terminated */ - memcpy(n, name->m_qname.str, name->m_qname.length); - n[name->m_qname.length]= '\0'; - my_error(ER_SP_PROC_TABLE_CORRUPT, MYF(0), n, ret); + my_error(ER_SP_PROC_TABLE_CORRUPT, MYF(0), + ErrConvDQName(name).ptr(), ret); } break; } @@ -2197,59 +2799,135 @@ int sp_cache_routine(THD *thd, enum stored_procedure_type type, sp_name *name, /** + Cache a package routine using its package name and a qualified name. + See sp_cache_routine() for more information on parameters and return values. + + @param thd - current THD + @param pkgname_str - package name, e.g. "pkgname" + @param name - name with the following format: + * name->m_db is a database name, e.g. "dbname" + * name->m_name is a package-qualified name, + e.g. "pkgname.spname" + @param lookup_only - don't load mysql.proc if not cached + @param [OUT] sp - the result is returned here. + @retval false - loaded or does not exists + @retval true - error while loading mysql.proc +*/ +int +Sp_handler::sp_cache_package_routine(THD *thd, + const LEX_CSTRING &pkgname_cstr, + const Database_qualified_name *name, + bool lookup_only, sp_head **sp) const +{ + DBUG_ENTER("sp_cache_package_routine"); + DBUG_ASSERT(type() == TYPE_ENUM_FUNCTION || type() == TYPE_ENUM_PROCEDURE); + sp_name pkgname(&name->m_db, &pkgname_cstr, false); + sp_head *ph= NULL; + int ret= sp_handler_package_body.sp_cache_routine(thd, &pkgname, + lookup_only, + &ph); + if (!ret) + { + sp_package *pkg= ph ? ph->get_package() : NULL; + LEX_CSTRING tmp= name->m_name; + const char *dot= strrchr(tmp.str, '.'); + size_t prefix_length= dot ? dot - tmp.str + 1 : NULL; + tmp.str+= prefix_length; + tmp.length-= prefix_length; + LEX *rlex= pkg ? pkg->m_routine_implementations.find(tmp, type()) : NULL; + *sp= rlex ? rlex->sphead : NULL; + } + + DBUG_RETURN(ret); +} + + +/** + Cache a package routine by its fully qualified name. + See sp_cache_routine() for more information on parameters and return values. + + @param thd - current THD + @param name - name with the following format: + * name->m_db is a database name, e.g. "dbname" + * name->m_name is a package-qualified name, + e.g. "pkgname.spname" + @param lookup_only - don't load mysql.proc if not cached + @param [OUT] sp - the result is returned here + @retval false - loaded or does not exists + @retval true - error while loading mysql.proc +*/ +int Sp_handler::sp_cache_package_routine(THD *thd, + const Database_qualified_name *name, + bool lookup_only, sp_head **sp) const +{ + DBUG_ENTER("Sp_handler::sp_cache_package_routine"); + Prefix_name_buf pkgname(thd, name->m_name); + DBUG_ASSERT(pkgname.length); + DBUG_RETURN(sp_cache_package_routine(thd, pkgname, name, lookup_only, sp)); +} + + +/** Generates the CREATE... string from the table information. @return - Returns TRUE on success, FALSE on (alloc) failure. + Returns false on success, true on (alloc) failure. */ bool -show_create_sp(THD *thd, String *buf, - stored_procedure_type type, - const char *db, ulong dblen, - const char *name, ulong namelen, - const char *params, ulong paramslen, - const char *returns, ulong returnslen, - const char *body, ulong bodylen, - st_sp_chistics *chistics, - const LEX_STRING *definer_user, - const LEX_STRING *definer_host, - sql_mode_t sql_mode) +Sp_handler::show_create_sp(THD *thd, String *buf, + const LEX_CSTRING &db, + const LEX_CSTRING &name, + const LEX_CSTRING ¶ms, + const LEX_CSTRING &returns, + const LEX_CSTRING &body, + const st_sp_chistics &chistics, + const AUTHID &definer, + const DDL_options_st ddl_options, + sql_mode_t sql_mode) const { sql_mode_t old_sql_mode= thd->variables.sql_mode; + size_t agglen= (chistics.agg_type == GROUP_AGGREGATE)? 10 : 0; + LEX_CSTRING tmp; + /* Make some room to begin with */ - if (buf->alloc(100 + dblen + 1 + namelen + paramslen + returnslen + bodylen + - chistics->comment.length + 10 /* length of " DEFINER= "*/ + - USER_HOST_BUFF_SIZE)) - return FALSE; + if (buf->alloc(100 + db.length + 1 + name.length + + params.length + returns.length + + chistics.comment.length + 10 /* length of " DEFINER= "*/ + + agglen + USER_HOST_BUFF_SIZE)) + return true; thd->variables.sql_mode= sql_mode; buf->append(STRING_WITH_LEN("CREATE ")); - if (thd->lex->create_info.or_replace()) + if (ddl_options.or_replace()) buf->append(STRING_WITH_LEN("OR REPLACE ")); - append_definer(thd, buf, definer_user, definer_host); - if (type == TYPE_ENUM_FUNCTION) - buf->append(STRING_WITH_LEN("FUNCTION ")); - else - buf->append(STRING_WITH_LEN("PROCEDURE ")); - if (thd->lex->create_info.if_not_exists()) + append_definer(thd, buf, &definer.user, &definer.host); + if (chistics.agg_type == GROUP_AGGREGATE) + buf->append(STRING_WITH_LEN("AGGREGATE ")); + tmp= type_lex_cstring(); + buf->append(&tmp); + buf->append(STRING_WITH_LEN(" ")); + if (ddl_options.if_not_exists()) buf->append(STRING_WITH_LEN("IF NOT EXISTS ")); - if (dblen > 0) + if (db.length > 0) { - append_identifier(thd, buf, db, dblen); + append_identifier(thd, buf, &db); buf->append('.'); } - append_identifier(thd, buf, name, namelen); + append_identifier(thd, buf, &name); buf->append('('); - buf->append(params, paramslen); + buf->append(¶ms); buf->append(')'); - if (type == TYPE_ENUM_FUNCTION) + if (type() == TYPE_ENUM_FUNCTION) { - buf->append(STRING_WITH_LEN(" RETURNS ")); - buf->append(returns, returnslen); + if (sql_mode & MODE_ORACLE) + buf->append(STRING_WITH_LEN(" RETURN ")); + else + buf->append(STRING_WITH_LEN(" RETURNS ")); + buf->append(&returns); } buf->append('\n'); - switch (chistics->daccess) { + switch (chistics.daccess) { case SP_NO_SQL: buf->append(STRING_WITH_LEN(" NO SQL\n")); break; @@ -2264,19 +2942,13 @@ show_create_sp(THD *thd, String *buf, /* Do nothing */ break; } - if (chistics->detistic) + if (chistics.detistic) buf->append(STRING_WITH_LEN(" DETERMINISTIC\n")); - if (chistics->suid == SP_IS_NOT_SUID) - buf->append(STRING_WITH_LEN(" SQL SECURITY INVOKER\n")); - if (chistics->comment.length) - { - buf->append(STRING_WITH_LEN(" COMMENT ")); - append_unescaped(buf, chistics->comment.str, chistics->comment.length); - buf->append('\n'); - } - buf->append(body, bodylen); + append_suid(buf, chistics.suid); + append_comment(buf, chistics.comment); + buf->append(body.str, body.length); // Not \0 terminated thd->variables.sql_mode= old_sql_mode; - return TRUE; + return false; } @@ -2301,28 +2973,19 @@ show_create_sp(THD *thd, String *buf, */ sp_head * -sp_load_for_information_schema(THD *thd, TABLE *proc_table, String *db, - String *name, sql_mode_t sql_mode, - stored_procedure_type type, - const char *returns, const char *params, - bool *free_sp_head) +Sp_handler::sp_load_for_information_schema(THD *thd, TABLE *proc_table, + const LEX_CSTRING &db, + const LEX_CSTRING &name, + const LEX_CSTRING ¶ms, + const LEX_CSTRING &returns, + sql_mode_t sql_mode, + bool *free_sp_head) const { - const char *sp_body; String defstr; - struct st_sp_chistics sp_chistics; - const LEX_STRING definer_user= {(char*)STRING_WITH_LEN("")}; - const LEX_STRING definer_host= {(char*)STRING_WITH_LEN("")}; - LEX_STRING sp_db_str; - LEX_STRING sp_name_str; + const AUTHID definer= {{STRING_WITH_LEN("")}, {STRING_WITH_LEN("")}}; sp_head *sp; - sp_cache **spc= ((type == TYPE_ENUM_PROCEDURE) ? - &thd->sp_proc_cache : &thd->sp_func_cache); - sp_db_str.str= db->c_ptr(); - sp_db_str.length= db->length(); - sp_name_str.str= name->c_ptr(); - sp_name_str.length= name->length(); - sp_name sp_name_obj(sp_db_str, sp_name_str, true); - sp_name_obj.init_qname(thd); + sp_cache **spc= get_cache(thd); + sp_name sp_name_obj(&db, &name, true); // This can change "name" *free_sp_head= 0; if ((sp= sp_cache_lookup(spc, &sp_name_obj))) { @@ -2332,21 +2995,16 @@ sp_load_for_information_schema(THD *thd, TABLE *proc_table, String *db, LEX *old_lex= thd->lex, newlex; Stored_program_creation_ctx *creation_ctx= Stored_routine_creation_ctx::load_from_db(thd, &sp_name_obj, proc_table); - sp_body= (type == TYPE_ENUM_FUNCTION ? "RETURN NULL" : "BEGIN END"); - bzero((char*) &sp_chistics, sizeof(sp_chistics)); defstr.set_charset(creation_ctx->get_client_cs()); - if (!show_create_sp(thd, &defstr, type, - sp_db_str.str, sp_db_str.length, - sp_name_obj.m_name.str, sp_name_obj.m_name.length, - params, strlen(params), - returns, strlen(returns), - sp_body, strlen(sp_body), - &sp_chistics, &definer_user, &definer_host, sql_mode)) + if (show_create_sp(thd, &defstr, + sp_name_obj.m_db, sp_name_obj.m_name, + params, returns, empty_body_lex_cstring(sql_mode), + Sp_chistics(), definer, DDL_options(), sql_mode)) return 0; thd->lex= &newlex; newlex.current_select= NULL; - sp= sp_compile(thd, &defstr, sql_mode, creation_ctx); + sp= sp_compile(thd, &defstr, sql_mode, NULL, creation_ctx); *free_sp_head= 1; thd->lex->sphead= NULL; lex_end(thd->lex); @@ -2354,3 +3012,18 @@ sp_load_for_information_schema(THD *thd, TABLE *proc_table, String *db, return sp; } + +LEX_CSTRING Sp_handler_procedure::empty_body_lex_cstring(sql_mode_t mode) const +{ + static LEX_CSTRING m_empty_body_std= {C_STRING_WITH_LEN("BEGIN END")}; + static LEX_CSTRING m_empty_body_ora= {C_STRING_WITH_LEN("AS BEGIN NULL; END")}; + return mode & MODE_ORACLE ? m_empty_body_ora : m_empty_body_std; +} + + +LEX_CSTRING Sp_handler_function::empty_body_lex_cstring(sql_mode_t mode) const +{ + static LEX_CSTRING m_empty_body_std= {C_STRING_WITH_LEN("RETURN NULL")}; + static LEX_CSTRING m_empty_body_ora= {C_STRING_WITH_LEN("AS BEGIN RETURN NULL; END")}; + return mode & MODE_ORACLE ? m_empty_body_ora : m_empty_body_std; +} |