diff options
author | Alexander Barkov <bar@mariadb.org> | 2017-08-18 23:36:42 +0400 |
---|---|---|
committer | Alexander Barkov <bar@mariadb.org> | 2018-02-25 21:08:19 +0400 |
commit | 583eb96c2492adb87e88a014b24eb0724fb00257 (patch) | |
tree | 501cb4e5e3855400e79df8911ac43ef1f89300b3 /sql | |
parent | 83ea839fb15dd5ed616d2b3152ccc5472ee5e5e6 (diff) | |
download | mariadb-git-bb-10.3-compatibility.tar.gz |
MDEV-11952 Oracle-style packages: stage#5mariadb-10.3.5bb-10.3-compatibility
- CREATE PACKAGE [BODY] statements are now
entirely written to mysql.proc with type='PACKAGE' and type='PACKAGE BODY'.
- CREATE PACKAGE BODY now supports IF NOT EXISTS
- DROP PACKAGE BODY now supports IF EXISTS
- CREATE OR REPLACE PACKAGE [BODY] is now supported
- CREATE PACKAGE [BODY] now support the DEFINER clause:
CREATE DEFINER user@host PACKAGE pkg ... END;
CREATE DEFINER user@host PACKAGE BODY pkg ... END;
- CREATE PACKAGE [BODY] now supports SQL SECURITY and COMMENT clauses, e.g.:
CREATE PACKAGE p1 SQL SECURITY INVOKER COMMENT "comment" AS ... END;
- Package routines are now created from the package CREATE PACKAGE BODY
statement and don't produce individual records in mysql.proc.
- CREATE PACKAGE BODY now supports package-wide variables.
Package variables can be read and set inside package routines.
Package variables are stored in a separate sp_rcontext,
which is cached in THD on the first packate routine call.
- CREATE PACKAGE BODY now supports the initialization section.
- All public routines (i.e. declared in CREATE PACKAGE)
must have implementations in CREATE PACKAGE BODY
- Only public package routines are available outside of the package
- {CREATE|DROP} PACKAGE [BODY] now respects CREATE ROUTINE and ALTER ROUTINE
privileges
- "GRANT EXECUTE ON PACKAGE BODY pkg" is now supported
- SHOW CREATE PACKAGE [BODY] is now supported
- SHOW PACKAGE [BODY] STATUS is now supported
- CREATE and DROP for PACKAGE [BODY] now works for non-current databases
- mysqldump now supports packages
- "SHOW {PROCEDURE|FUNCTION) CODE pkg.routine" now works for package routines
- "SHOW PACKAGE BODY CODE pkg" now works (the package initialization section)
- A new package body level MDL was added
- Recursive calls for package procedures are now possible
- Routine forward declarations in CREATE PACKATE BODY are now supported.
- Package body variables now work as SP OUT parameters
- Package body variables now work as SELECT INTO targets
- Package body variables now support ROW, %ROWTYPE, %TYPE
Diffstat (limited to 'sql')
-rw-r--r-- | sql/item.cc | 59 | ||||
-rw-r--r-- | sql/item.h | 52 | ||||
-rw-r--r-- | sql/item_create.cc | 17 | ||||
-rw-r--r-- | sql/item_func.cc | 11 | ||||
-rw-r--r-- | sql/item_func.h | 6 | ||||
-rw-r--r-- | sql/item_xmlfunc.cc | 7 | ||||
-rw-r--r-- | sql/lex.h | 2 | ||||
-rw-r--r-- | sql/mdl.cc | 2 | ||||
-rw-r--r-- | sql/mdl.h | 1 | ||||
-rw-r--r-- | sql/mysqld.cc | 11 | ||||
-rw-r--r-- | sql/share/errmsg-utf8.txt | 4 | ||||
-rw-r--r-- | sql/sp.cc | 667 | ||||
-rw-r--r-- | sql/sp.h | 244 | ||||
-rw-r--r-- | sql/sp_head.cc | 361 | ||||
-rw-r--r-- | sql/sp_head.h | 129 | ||||
-rw-r--r-- | sql/sp_pcontext.h | 3 | ||||
-rw-r--r-- | sql/sp_rcontext.cc | 27 | ||||
-rw-r--r-- | sql/sp_rcontext.h | 3 | ||||
-rw-r--r-- | sql/sql_acl.cc | 106 | ||||
-rw-r--r-- | sql/sql_base.cc | 33 | ||||
-rw-r--r-- | sql/sql_class.cc | 24 | ||||
-rw-r--r-- | sql/sql_class.h | 48 | ||||
-rw-r--r-- | sql/sql_cmd.h | 15 | ||||
-rw-r--r-- | sql/sql_lex.cc | 319 | ||||
-rw-r--r-- | sql/sql_lex.h | 47 | ||||
-rw-r--r-- | sql/sql_parse.cc | 33 | ||||
-rw-r--r-- | sql/sql_prepare.cc | 21 | ||||
-rw-r--r-- | sql/sql_show.cc | 28 | ||||
-rw-r--r-- | sql/sql_show.h | 2 | ||||
-rw-r--r-- | sql/sql_string.h | 4 | ||||
-rw-r--r-- | sql/sql_trigger.cc | 3 | ||||
-rw-r--r-- | sql/sql_yacc.yy | 50 | ||||
-rw-r--r-- | sql/sql_yacc_ora.yy | 411 |
33 files changed, 2529 insertions, 221 deletions
diff --git a/sql/item.cc b/sql/item.cc index d3345f69a40..9f0280b678c 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -1789,13 +1789,16 @@ void Item_sp_variable::make_field(THD *thd, Send_field *field) Item_splocal methods *****************************************************************************/ -Item_splocal::Item_splocal(THD *thd, const LEX_CSTRING *sp_var_name, +Item_splocal::Item_splocal(THD *thd, + const Sp_rcontext_handler *rh, + const LEX_CSTRING *sp_var_name, uint sp_var_idx, const Type_handler *handler, uint pos_in_q, uint len_in_q): Item_sp_variable(thd, sp_var_name), Rewritable_query_parameter(pos_in_q, len_in_q), Type_handler_hybrid_field_type(handler), + m_rcontext_handler(rh), m_var_idx(sp_var_idx) { maybe_null= TRUE; @@ -1803,9 +1806,21 @@ Item_splocal::Item_splocal(THD *thd, const LEX_CSTRING *sp_var_name, } +sp_rcontext *Item_splocal::get_rcontext(sp_rcontext *local_ctx) const +{ + return m_rcontext_handler->get_rcontext(local_ctx); +} + + +Item_field *Item_splocal::get_variable(sp_rcontext *ctx) const +{ + return get_rcontext(ctx)->get_variable(m_var_idx); +} + + bool Item_splocal::fix_fields(THD *thd, Item **ref) { - Item_field *item= thd->spcont->get_variable(m_var_idx); + Item *item= get_variable(thd->spcont); set_handler(item->type_handler()); return fix_fields_from_item(thd, ref, item); } @@ -1816,7 +1831,7 @@ Item_splocal::this_item() { DBUG_ASSERT(m_sp == m_thd->spcont->m_sp); DBUG_ASSERT(fixed); - return m_thd->spcont->get_variable(m_var_idx); + return get_variable(m_thd->spcont); } const Item * @@ -1824,7 +1839,7 @@ Item_splocal::this_item() const { DBUG_ASSERT(m_sp == m_thd->spcont->m_sp); DBUG_ASSERT(fixed); - return m_thd->spcont->get_variable(m_var_idx); + return get_variable(m_thd->spcont); } @@ -1833,13 +1848,15 @@ Item_splocal::this_item_addr(THD *thd, Item **) { DBUG_ASSERT(m_sp == thd->spcont->m_sp); DBUG_ASSERT(fixed); - return thd->spcont->get_variable_addr(m_var_idx); + return get_rcontext(thd->spcont)->get_variable_addr(m_var_idx); } void Item_splocal::print(String *str, enum_query_type) { - str->reserve(m_name.length+8); + const LEX_CSTRING *prefix= m_rcontext_handler->get_name_prefix(); + str->reserve(m_name.length + 8 + prefix->length); + str->append(prefix); str->append(&m_name); str->append('@'); str->qs_append(m_var_idx); @@ -1848,7 +1865,7 @@ void Item_splocal::print(String *str, enum_query_type) bool Item_splocal::set_value(THD *thd, sp_rcontext *ctx, Item **it) { - return ctx->set_variable(thd, get_var_idx(), it); + return get_rcontext(ctx)->set_variable(thd, get_var_idx(), it); } @@ -1930,7 +1947,7 @@ bool Item_splocal::check_cols(uint n) bool Item_splocal_row_field::fix_fields(THD *thd, Item **ref) { - Item *item= thd->spcont->get_variable(m_var_idx)->element_index(m_field_idx); + Item *item= get_variable(thd->spcont)->element_index(m_field_idx); return fix_fields_from_item(thd, ref, item); } @@ -1940,7 +1957,7 @@ Item_splocal_row_field::this_item() { DBUG_ASSERT(m_sp == m_thd->spcont->m_sp); DBUG_ASSERT(fixed); - return m_thd->spcont->get_variable(m_var_idx)->element_index(m_field_idx); + return get_variable(m_thd->spcont)->element_index(m_field_idx); } @@ -1949,7 +1966,7 @@ Item_splocal_row_field::this_item() const { DBUG_ASSERT(m_sp == m_thd->spcont->m_sp); DBUG_ASSERT(fixed); - return m_thd->spcont->get_variable(m_var_idx)->element_index(m_field_idx); + return get_variable(m_thd->spcont)->element_index(m_field_idx); } @@ -1958,13 +1975,15 @@ Item_splocal_row_field::this_item_addr(THD *thd, Item **) { DBUG_ASSERT(m_sp == thd->spcont->m_sp); DBUG_ASSERT(fixed); - return thd->spcont->get_variable(m_var_idx)->addr(m_field_idx); + return get_variable(thd->spcont)->addr(m_field_idx); } void Item_splocal_row_field::print(String *str, enum_query_type) { - str->reserve(m_name.length + m_field_name.length + 8); + const LEX_CSTRING *prefix= m_rcontext_handler->get_name_prefix(); + str->reserve(m_name.length + m_field_name.length + 8 + prefix->length); + str->append(prefix); str->append(&m_name); str->append('.'); str->append(&m_field_name); @@ -1978,18 +1997,19 @@ void Item_splocal_row_field::print(String *str, enum_query_type) bool Item_splocal_row_field::set_value(THD *thd, sp_rcontext *ctx, Item **it) { - return ctx->set_variable_row_field(thd, m_var_idx, m_field_idx, it); + return get_rcontext(ctx)->set_variable_row_field(thd, m_var_idx, m_field_idx, + it); } bool Item_splocal_row_field_by_name::fix_fields(THD *thd, Item **it) { m_thd= thd; - if (thd->spcont->find_row_field_by_name_or_error(&m_field_idx, - m_var_idx, - m_field_name)) + if (get_rcontext(thd->spcont)->find_row_field_by_name_or_error(&m_field_idx, + m_var_idx, + m_field_name)) return true; - Item *item= thd->spcont->get_variable(m_var_idx)->element_index(m_field_idx); + Item *item= get_variable(thd->spcont)->element_index(m_field_idx); set_handler(item->type_handler()); return fix_fields_from_item(thd, it, item); } @@ -1997,9 +2017,12 @@ bool Item_splocal_row_field_by_name::fix_fields(THD *thd, Item **it) void Item_splocal_row_field_by_name::print(String *str, enum_query_type) { + const LEX_CSTRING *prefix= m_rcontext_handler->get_name_prefix(); // +16 should be enough for .NNN@[""] - if (str->reserve(m_name.length + 2 * m_field_name.length + 16)) + if (str->reserve(m_name.length + 2 * m_field_name.length + + prefix->length + 16)) return; + str->qs_append(prefix); str->qs_append(&m_name); str->qs_append('.'); str->qs_append(&m_field_name); diff --git a/sql/item.h b/sql/item.h index 2a7b92dd601..42442468be9 100644 --- a/sql/item.h +++ b/sql/item.h @@ -367,6 +367,39 @@ typedef enum monotonicity_info class sp_rcontext; +class Sp_rcontext_handler +{ +public: + virtual ~Sp_rcontext_handler() {} + virtual const LEX_CSTRING *get_name_prefix() const= 0; + virtual sp_rcontext *get_rcontext(sp_rcontext *ctx) const= 0; +}; + + +class Sp_rcontext_handler_local: public Sp_rcontext_handler +{ +public: + const LEX_CSTRING *get_name_prefix() const; + sp_rcontext *get_rcontext(sp_rcontext *ctx) const; +}; + + +class Sp_rcontext_handler_package_body: public Sp_rcontext_handler +{ +public: + const LEX_CSTRING *get_name_prefix() const; + sp_rcontext *get_rcontext(sp_rcontext *ctx) const; +}; + + +extern MYSQL_PLUGIN_IMPORT + Sp_rcontext_handler_local sp_rcontext_handler_local; + + +extern MYSQL_PLUGIN_IMPORT + Sp_rcontext_handler_package_body sp_rcontext_handler_package_body; + + class Item_equal; @@ -2384,13 +2417,20 @@ class Item_splocal :public Item_sp_variable, public Type_handler_hybrid_field_type { protected: + const Sp_rcontext_handler *m_rcontext_handler; + uint m_var_idx; Type m_type; bool append_value_for_log(THD *thd, String *str); + + sp_rcontext *get_rcontext(sp_rcontext *local_ctx) const; + Item_field *get_variable(sp_rcontext *ctx) const; + public: - Item_splocal(THD *thd, const LEX_CSTRING *sp_var_name, uint sp_var_idx, + Item_splocal(THD *thd, const Sp_rcontext_handler *rh, + const LEX_CSTRING *sp_var_name, uint sp_var_idx, const Type_handler *handler, uint pos_in_q= 0, uint len_in_q= 0); @@ -2452,10 +2492,11 @@ class Item_splocal_with_delayed_data_type: public Item_splocal { public: Item_splocal_with_delayed_data_type(THD *thd, + const Sp_rcontext_handler *rh, const LEX_CSTRING *sp_var_name, uint sp_var_idx, uint pos_in_q, uint len_in_q) - :Item_splocal(thd, sp_var_name, sp_var_idx, &type_handler_null, + :Item_splocal(thd, rh, sp_var_name, sp_var_idx, &type_handler_null, pos_in_q, len_in_q) { } }; @@ -2474,13 +2515,13 @@ protected: bool set_value(THD *thd, sp_rcontext *ctx, Item **it); public: Item_splocal_row_field(THD *thd, + const Sp_rcontext_handler *rh, const LEX_CSTRING *sp_var_name, const LEX_CSTRING *sp_field_name, uint sp_var_idx, uint sp_field_idx, const Type_handler *handler, uint pos_in_q= 0, uint len_in_q= 0) - :Item_splocal(thd, sp_var_name, sp_var_idx, handler, - pos_in_q, len_in_q), + :Item_splocal(thd, rh, sp_var_name, sp_var_idx, handler, pos_in_q, len_in_q), m_field_name(*sp_field_name), m_field_idx(sp_field_idx) { } @@ -2498,12 +2539,13 @@ class Item_splocal_row_field_by_name :public Item_splocal_row_field bool set_value(THD *thd, sp_rcontext *ctx, Item **it); public: Item_splocal_row_field_by_name(THD *thd, + const Sp_rcontext_handler *rh, const LEX_CSTRING *sp_var_name, const LEX_CSTRING *sp_field_name, uint sp_var_idx, const Type_handler *handler, uint pos_in_q= 0, uint len_in_q= 0) - :Item_splocal_row_field(thd, sp_var_name, sp_field_name, + :Item_splocal_row_field(thd, rh, sp_var_name, sp_field_name, sp_var_idx, 0 /* field index will be set later */, handler, pos_in_q, len_in_q) { } diff --git a/sql/item_create.cc b/sql/item_create.cc index 548ce3bac94..385d8de98fc 100644 --- a/sql/item_create.cc +++ b/sql/item_create.cc @@ -3399,6 +3399,8 @@ Create_sp_func::create_with_db(THD *thd, LEX_CSTRING *db, LEX_CSTRING *name, Item *func= NULL; LEX *lex= thd->lex; sp_name *qname; + const Sp_handler *sph= &sp_handler_function; + Database_qualified_name pkgname(&null_clex_str, &null_clex_str); if (has_named_parameters(item_list)) { @@ -3419,13 +3421,18 @@ Create_sp_func::create_with_db(THD *thd, LEX_CSTRING *db, LEX_CSTRING *name, arg_count= item_list->elements; qname= new (thd->mem_root) sp_name(db, name, use_explicit_name); - sp_handler_function.add_used_routine(lex, thd, qname); - + if (sph->sp_resolve_package_routine(thd, thd->lex->sphead, + qname, &sph, &pkgname)) + return NULL; + sph->add_used_routine(lex, thd, qname); + if (pkgname.m_name.length) + sp_handler_package_body.add_used_routine(lex, thd, &pkgname); if (arg_count > 0) - func= new (thd->mem_root) Item_func_sp(thd, lex->current_context(), qname, - *item_list); + func= new (thd->mem_root) Item_func_sp(thd, lex->current_context(), + qname, sph, *item_list); else - func= new (thd->mem_root) Item_func_sp(thd, lex->current_context(), qname); + func= new (thd->mem_root) Item_func_sp(thd, lex->current_context(), + qname, sph); lex->safe_to_cache_query= 0; return func; diff --git a/sql/item_func.cc b/sql/item_func.cc index 4e79b45f5d6..52e593edefd 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -6295,16 +6295,17 @@ longlong Item_func_row_count::val_int() Item_func_sp::Item_func_sp(THD *thd, Name_resolution_context *context_arg, - sp_name *name): - Item_func(thd), Item_sp(thd, context_arg, name) + sp_name *name, const Sp_handler *sph): + Item_func(thd), Item_sp(thd, context_arg, name), m_handler(sph) { maybe_null= 1; } Item_func_sp::Item_func_sp(THD *thd, Name_resolution_context *context_arg, - sp_name *name_arg, List<Item> &list): - Item_func(thd, list), Item_sp(thd, context_arg, name_arg) + sp_name *name_arg, const Sp_handler *sph, + List<Item> &list): + Item_func(thd, list), Item_sp(thd, context_arg, name_arg), m_handler(sph) { maybe_null= 1; } @@ -6443,7 +6444,7 @@ Item_func_sp::fix_fields(THD *thd, Item **ref) bool res; DBUG_ENTER("Item_func_sp::fix_fields"); DBUG_ASSERT(fixed == 0); - sp_head *sp= sp_handler_function.sp_find_routine(thd, m_name, true); + sp_head *sp= m_handler->sp_find_routine(thd, m_name, true); /* Checking privileges to execute the function while creating view and diff --git a/sql/item_func.h b/sql/item_func.h index 38a40e71544..35db77891b1 100644 --- a/sql/item_func.h +++ b/sql/item_func.h @@ -2772,6 +2772,7 @@ class Item_func_sp :public Item_func, public Item_sp { private: + const Sp_handler *m_handler; bool execute(); @@ -2786,10 +2787,11 @@ protected: } public: - Item_func_sp(THD *thd, Name_resolution_context *context_arg, sp_name *name); + Item_func_sp(THD *thd, Name_resolution_context *context_arg, + sp_name *name, const Sp_handler *sph); Item_func_sp(THD *thd, Name_resolution_context *context_arg, - sp_name *name, List<Item> &list); + sp_name *name, const Sp_handler *sph, List<Item> &list); virtual ~Item_func_sp() {} diff --git a/sql/item_xmlfunc.cc b/sql/item_xmlfunc.cc index 5156bd08ef2..0b9e515a61f 100644 --- a/sql/item_xmlfunc.cc +++ b/sql/item_xmlfunc.cc @@ -2630,14 +2630,13 @@ my_xpath_parse_VariableReference(MY_XPATH *xpath) else { sp_variable *spv; - sp_pcontext *spc; + const Sp_rcontext_handler *rh; LEX *lex; if ((lex= thd->lex) && - (spc= lex->spcont) && - (spv= spc->find_variable(&name, false))) + (spv= lex->find_variable(&name, &rh))) { Item_splocal *splocal= new (thd->mem_root) - Item_splocal(thd, &name, spv->offset, spv->type_handler(), 0); + Item_splocal(thd, rh, &name, spv->offset, spv->type_handler(), 0); #ifdef DBUG_ASSERT_EXISTS if (splocal) splocal->m_sp= lex->sphead; diff --git a/sql/lex.h b/sql/lex.h index 41a81c737f7..9833809cd5d 100644 --- a/sql/lex.h +++ b/sql/lex.h @@ -90,6 +90,7 @@ static SYMBOL symbols[] = { { "BIT", SYM(BIT_SYM)}, { "BLOB", SYM(BLOB_SYM)}, { "BLOCK", SYM(BLOCK_SYM)}, + { "BODY", SYM(BODY_SYM)}, { "BOOL", SYM(BOOL_SYM)}, { "BOOLEAN", SYM(BOOLEAN_SYM)}, { "BOTH", SYM(BOTH)}, @@ -456,6 +457,7 @@ static SYMBOL symbols[] = { { "OUTFILE", SYM(OUTFILE)}, { "OVER", SYM(OVER_SYM)}, { "OWNER", SYM(OWNER_SYM)}, + { "PACKAGE", SYM(PACKAGE_SYM)}, { "PACK_KEYS", SYM(PACK_KEYS_SYM)}, { "PAGE", SYM(PAGE_SYM)}, { "PAGE_CHECKSUM", SYM(PAGE_CHECKSUM_SYM)}, diff --git a/sql/mdl.cc b/sql/mdl.cc index bf708c945f4..f03fc89fcc1 100644 --- a/sql/mdl.cc +++ b/sql/mdl.cc @@ -85,6 +85,7 @@ PSI_stage_info MDL_key::m_namespace_to_wait_state_name[NAMESPACE_END]= {0, "Waiting for table metadata lock", 0}, {0, "Waiting for stored function metadata lock", 0}, {0, "Waiting for stored procedure metadata lock", 0}, + {0, "Waiting for stored package body metadata lock", 0}, {0, "Waiting for trigger metadata lock", 0}, {0, "Waiting for event metadata lock", 0}, {0, "Waiting for commit lock", 0}, @@ -3043,6 +3044,7 @@ const char *wsrep_get_mdl_namespace_name(MDL_key::enum_mdl_namespace ns) case MDL_key::TABLE : return "TABLE"; case MDL_key::FUNCTION : return "FUNCTION"; case MDL_key::PROCEDURE : return "PROCEDURE"; + case MDL_key::PACKAGE_BODY: return "PACKAGE BODY"; case MDL_key::TRIGGER : return "TRIGGER"; case MDL_key::EVENT : return "EVENT"; case MDL_key::COMMIT : return "COMMIT"; diff --git a/sql/mdl.h b/sql/mdl.h index be9cc806ec2..a537022484f 100644 --- a/sql/mdl.h +++ b/sql/mdl.h @@ -299,6 +299,7 @@ public: TABLE, FUNCTION, PROCEDURE, + PACKAGE_BODY, TRIGGER, EVENT, COMMIT, diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 4ee9cda70bf..11c8b49d49c 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -3829,6 +3829,8 @@ SHOW_VAR com_status_vars[]= { {"create_event", STMT_STATUS(SQLCOM_CREATE_EVENT)}, {"create_function", STMT_STATUS(SQLCOM_CREATE_SPFUNCTION)}, {"create_index", STMT_STATUS(SQLCOM_CREATE_INDEX)}, + {"create_package", STMT_STATUS(SQLCOM_CREATE_PACKAGE)}, + {"create_package_body", STMT_STATUS(SQLCOM_CREATE_PACKAGE_BODY)}, {"create_procedure", STMT_STATUS(SQLCOM_CREATE_PROCEDURE)}, {"create_role", STMT_STATUS(SQLCOM_CREATE_ROLE)}, {"create_sequence", STMT_STATUS(SQLCOM_CREATE_SEQUENCE)}, @@ -3848,6 +3850,8 @@ SHOW_VAR com_status_vars[]= { {"drop_function", STMT_STATUS(SQLCOM_DROP_FUNCTION)}, {"drop_index", STMT_STATUS(SQLCOM_DROP_INDEX)}, {"drop_procedure", STMT_STATUS(SQLCOM_DROP_PROCEDURE)}, + {"drop_package", STMT_STATUS(SQLCOM_DROP_PACKAGE)}, + {"drop_package_body", STMT_STATUS(SQLCOM_DROP_PACKAGE_BODY)}, {"drop_role", STMT_STATUS(SQLCOM_DROP_ROLE)}, {"drop_server", STMT_STATUS(SQLCOM_DROP_SERVER)}, {"drop_sequence", STMT_STATUS(SQLCOM_DROP_SEQUENCE)}, @@ -3904,6 +3908,8 @@ SHOW_VAR com_status_vars[]= { {"show_create_db", STMT_STATUS(SQLCOM_SHOW_CREATE_DB)}, {"show_create_event", STMT_STATUS(SQLCOM_SHOW_CREATE_EVENT)}, {"show_create_func", STMT_STATUS(SQLCOM_SHOW_CREATE_FUNC)}, + {"show_create_package", STMT_STATUS(SQLCOM_SHOW_CREATE_PACKAGE)}, + {"show_create_package_body",STMT_STATUS(SQLCOM_SHOW_CREATE_PACKAGE_BODY)}, {"show_create_proc", STMT_STATUS(SQLCOM_SHOW_CREATE_PROC)}, {"show_create_table", STMT_STATUS(SQLCOM_SHOW_CREATE)}, {"show_create_trigger", STMT_STATUS(SQLCOM_SHOW_CREATE_TRIGGER)}, @@ -3925,6 +3931,11 @@ SHOW_VAR com_status_vars[]= { {"show_keys", STMT_STATUS(SQLCOM_SHOW_KEYS)}, {"show_master_status", STMT_STATUS(SQLCOM_SHOW_MASTER_STAT)}, {"show_open_tables", STMT_STATUS(SQLCOM_SHOW_OPEN_TABLES)}, + {"show_package_status", STMT_STATUS(SQLCOM_SHOW_STATUS_PACKAGE)}, +#ifndef DBUG_OFF + {"show_package_body_code", STMT_STATUS(SQLCOM_SHOW_PACKAGE_BODY_CODE)}, +#endif + {"show_package_body_status", STMT_STATUS(SQLCOM_SHOW_STATUS_PACKAGE_BODY)}, {"show_plugins", STMT_STATUS(SQLCOM_SHOW_PLUGINS)}, {"show_privileges", STMT_STATUS(SQLCOM_SHOW_PRIVILEGES)}, #ifndef DBUG_OFF diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt index 3e9e94bbbfd..c725bf39eb0 100644 --- a/sql/share/errmsg-utf8.txt +++ b/sql/share/errmsg-utf8.txt @@ -7788,6 +7788,10 @@ ER_WRONG_INSERT_INTO_SEQUENCE eng "Wrong INSERT into a SEQUENCE. One can only do single table INSERT into a sequence object (like with mysqldump). If you want to change the SEQUENCE, use ALTER SEQUENCE instead." ER_SP_STACK_TRACE eng "At line %u in %s" +ER_PACKAGE_ROUTINE_IN_SPEC_NOT_DEFINED_IN_BODY + eng "Subroutine '%-.192s' is declared in the package specification but is not defined in the package body" +ER_PACKAGE_ROUTINE_FORWARD_DECLARATION_NOT_DEFINED + eng "Subroutine '%-.192s' has a forward declaration but is not defined" ER_COMPRESSED_COLUMN_USED_AS_KEY eng "Compressed column '%-.192s' can't be used in key specification" ER_UNKNOWN_COMPRESSION_METHOD diff --git a/sql/sp.cc b/sql/sp.cc index 773a5479199..d75e40af6ce 100644 --- a/sql/sp.cc +++ b/sql/sp.cc @@ -45,6 +45,17 @@ 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; @@ -85,7 +96,23 @@ bool Sp_handler_procedure::add_instr_preturn(THD *thd, sp_head *sp, 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 @@ -690,7 +717,7 @@ Sp_handler::db_find_routine(THD *thd, table->field[MYSQL_PROC_FIELD_PARAM_LIST]->val_str_nopad(thd->mem_root, ¶ms); - if (type() == TYPE_ENUM_PROCEDURE) + if (type() != TYPE_ENUM_FUNCTION) returns= empty_clex_str; else if (table->field[MYSQL_PROC_FIELD_RETURNS]->val_str_nopad(thd->mem_root, &returns)) @@ -718,7 +745,7 @@ Sp_handler::db_find_routine(THD *thd, ret= db_load_routine(thd, name, sphp, sql_mode, params, returns, body, chistics, definer, - created, modified, creation_ctx); + created, modified, NULL, creation_ctx); done: /* Restore the time zone flag as the timezone usage in proc table @@ -788,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 @@ -796,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; @@ -816,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; @@ -886,6 +917,7 @@ Sp_handler::db_load_routine(THD *thd, const Database_qualified_name *name, 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; @@ -942,7 +974,7 @@ Sp_handler::db_load_routine(THD *thd, const Database_qualified_name *name, } { - *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 @@ -967,6 +999,22 @@ Sp_handler::db_load_routine(THD *thd, const Database_qualified_name *name, (*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 @@ -1058,6 +1106,32 @@ Sp_handler::sp_drop_routine_internal(THD *thd, } +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. @@ -1142,7 +1216,22 @@ Sp_handler::sp_create_routine(THD *thd, const sp_head *sp) const { if (lex->create_info.or_replace()) { - if ((ret= sp_drop_routine_internal(thd, sp, 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()) @@ -1400,6 +1489,49 @@ append_comment(String *buf, const LEX_CSTRING &comment) } +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. @@ -1433,10 +1565,7 @@ Sp_handler::sp_drop_routine(THD *thd, if (!(table= open_proc_table_for_update(thd))) DBUG_RETURN(SP_OPEN_TABLE_FAILED); - if ((ret= db_find_routine_aux(thd, name, table)) == SP_OK) - ret= sp_drop_routine_internal(thd, 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; /* @@ -1631,9 +1760,12 @@ bool lock_db_routines(THD *thd, const 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))); } @@ -1780,6 +1912,34 @@ Sp_handler::sp_show_create_routine(THD *thd, /* + 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 @@ -1794,9 +1954,17 @@ Sp_handler::sp_clone_and_link_routine(THD *thd, 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; @@ -1838,13 +2006,63 @@ Sp_handler::sp_clone_and_link_routine(THD *thd, sp_returns_type(thd, retstr, sp); returns= retstr.lex_cstring(); } - if (db_load_routine(thd, name, &new_sp, + + 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->get_creation_ctx()) == SP_OK) + 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; @@ -1878,7 +2096,7 @@ sp_head * Sp_handler::sp_find_routine(THD *thd, const Database_qualified_name *name, bool cache_only) const { - DBUG_ENTER("sp_find_routine"); + 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, @@ -1895,6 +2113,74 @@ Sp_handler::sp_find_routine(THD *thd, const Database_qualified_name *name, /** + 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); +} + + +/** + 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)); +} + + +/** This is used by sql_acl.cc:mysql_routine_grant() and is used to find the routines in 'routines'. @@ -1977,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, @@ -1994,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; } @@ -2001,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. @@ -2023,7 +2574,7 @@ void Sp_handler::add_used_routine(Query_tables_list *prelocking_ctx, const Database_qualified_name *rt) const { MDL_key key(get_mdl_type(), rt->m_db.str, rt->m_name.str); - (void) sp_add_used_routine(prelocking_ctx, arena, &key, 0); + (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; @@ -2116,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); } } @@ -2141,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); } @@ -2156,9 +2709,6 @@ int Sroutine_hash_entry::sp_cache_routine(THD *thd, { char qname_buff[NAME_LEN*2+1+1]; sp_name name(&mdl_request.key, qname_buff); - MDL_key::enum_mdl_namespace mdl_type= mdl_request.key.mdl_namespace(); - const Sp_handler *sph= Sp_handler::handler(mdl_type); - DBUG_ASSERT(sph); /* 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 @@ -2167,7 +2717,7 @@ int Sroutine_hash_entry::sp_cache_routine(THD *thd, */ DBUG_ASSERT(mdl_request.ticket || this == thd->lex->sroutines_list.first); - return sph->sp_cache_routine(thd, &name, lookup_only, sp); + return m_handler->sp_cache_routine(thd, &name, lookup_only, sp); } @@ -2198,7 +2748,7 @@ int Sp_handler::sp_cache_routine(THD *thd, int ret= 0; sp_cache **spc= get_cache(thd); - DBUG_ENTER("sp_cache_routine"); + DBUG_ENTER("Sp_handler::sp_cache_routine"); DBUG_ASSERT(spc); @@ -2249,6 +2799,75 @@ int Sp_handler::sp_cache_routine(THD *thd, /** + 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 @@ -2385,7 +3004,7 @@ Sp_handler::sp_load_for_information_schema(THD *thd, TABLE *proc_table, 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); @@ -31,7 +31,9 @@ class Sroutine_hash_entry; class THD; class sp_cache; class sp_head; +class sp_package; class sp_pcontext; +class sp_name; class Database_qualified_name; struct st_sp_chistics; class Stored_program_creation_ctx; @@ -49,13 +51,28 @@ enum stored_procedure_type { TYPE_ENUM_FUNCTION=1, TYPE_ENUM_PROCEDURE=2, - TYPE_ENUM_TRIGGER=3, - TYPE_ENUM_PROXY=4 + TYPE_ENUM_PACKAGE=3, + TYPE_ENUM_PACKAGE_BODY=4, + TYPE_ENUM_TRIGGER=5, + TYPE_ENUM_PROXY=6 }; class Sp_handler { + bool sp_resolve_package_routine_explicit(THD *thd, + sp_head *caller, + sp_name *name, + const Sp_handler **pkg_routine_hndlr, + Database_qualified_name *pkgname) + const; + bool sp_resolve_package_routine_implicit(THD *thd, + sp_head *caller, + sp_name *name, + const Sp_handler **pkg_routine_hndlr, + Database_qualified_name *pkgname) + const; +protected: int db_find_routine_aux(THD *thd, const Database_qualified_name *name, TABLE *table) const; int db_find_routine(THD *thd, const Database_qualified_name *name, @@ -72,6 +89,7 @@ class Sp_handler const st_sp_chistics &chistics, const AUTHID &definer, longlong created, longlong modified, + sp_package *parent, Stored_program_creation_ctx *creation_ctx) const; int sp_drop_routine_internal(THD *thd, const Database_qualified_name *name, @@ -80,11 +98,37 @@ class Sp_handler sp_head *sp_clone_and_link_routine(THD *thd, const Database_qualified_name *name, sp_head *sp) const; + int sp_cache_package_routine(THD *thd, + const LEX_CSTRING &pkgname_cstr, + const Database_qualified_name *name, + bool lookup_only, sp_head **sp) const; + int sp_cache_package_routine(THD *thd, + const Database_qualified_name *name, + bool lookup_only, sp_head **sp) const; + sp_head *sp_find_package_routine(THD *thd, + const LEX_CSTRING pkgname_str, + const Database_qualified_name *name, + bool cache_only) const; + sp_head *sp_find_package_routine(THD *thd, + const Database_qualified_name *name, + bool cache_only) const; +public: // TODO: make it private or protected + virtual int sp_find_and_drop_routine(THD *thd, TABLE *table, + const Database_qualified_name *name) + const; + public: virtual ~Sp_handler() {} static const Sp_handler *handler(enum enum_sql_command cmd); static const Sp_handler *handler(stored_procedure_type type); static const Sp_handler *handler(MDL_key::enum_mdl_namespace ns); + static bool eq_routine_name(const LEX_CSTRING &name1, + const LEX_CSTRING &name2) + { + return my_strnncoll(system_charset_info, + (const uchar *) name1.str, name1.length, + (const uchar *) name2.str, name2.length) == 0; + } const char *type_str() const { return type_lex_cstring().str; } virtual const char *show_create_routine_col1_caption() const { @@ -96,6 +140,10 @@ public: DBUG_ASSERT(0); return ""; } + virtual const Sp_handler *package_routine_handler() const + { + return this; + } virtual stored_procedure_type type() const= 0; virtual LEX_CSTRING type_lex_cstring() const= 0; virtual LEX_CSTRING empty_body_lex_cstring() const @@ -132,12 +180,22 @@ public: void add_used_routine(Query_tables_list *prelocking_ctx, Query_arena *arena, - const Database_qualified_name *rt) const; - - sp_head *sp_find_routine(THD *thd, const Database_qualified_name *name, - bool cache_only) const; - int sp_cache_routine(THD *thd, const Database_qualified_name *name, - bool lookup_only, sp_head **sp) const; + const Database_qualified_name *name) const; + + bool sp_resolve_package_routine(THD *thd, + sp_head *caller, + sp_name *name, + const Sp_handler **pkg_routine_handler, + Database_qualified_name *pkgname) const; + virtual sp_head *sp_find_routine(THD *thd, + const Database_qualified_name *name, + bool cache_only) const; + virtual int sp_cache_routine(THD *thd, const Database_qualified_name *name, + bool lookup_only, sp_head **sp) const; + + int sp_cache_routine_reentrant(THD *thd, + const Database_qualified_name *nm, + sp_head **sp) const; bool sp_exist_routines(THD *thd, TABLE_LIST *procs) const; bool sp_show_create_routine(THD *thd, @@ -163,16 +221,17 @@ public: @retval true on error @retval false on success */ - bool 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; + virtual bool 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; + }; @@ -202,6 +261,7 @@ public: { return MDL_key::PROCEDURE; } + const Sp_handler *package_routine_handler() const; sp_cache **get_cache(THD *) const; #ifndef NO_EMBEDDED_ACCESS_CHECKS HASH *get_priv_hash() const; @@ -212,6 +272,23 @@ public: }; +class Sp_handler_package_procedure: public Sp_handler_procedure +{ +public: + int sp_cache_routine(THD *thd, const Database_qualified_name *name, + bool lookup_only, sp_head **sp) const + { + return sp_cache_package_routine(thd, name, lookup_only, sp); + } + sp_head *sp_find_routine(THD *thd, + const Database_qualified_name *name, + bool cache_only) const + { + return sp_find_package_routine(thd, name, cache_only); + } +}; + + class Sp_handler_function: public Sp_handler { public: @@ -238,6 +315,7 @@ public: { return MDL_key::FUNCTION; } + const Sp_handler *package_routine_handler() const; sp_cache **get_cache(THD *) const; #ifndef NO_EMBEDDED_ACCESS_CHECKS HASH *get_priv_hash() const; @@ -247,6 +325,109 @@ public: }; +class Sp_handler_package_function: public Sp_handler_function +{ +public: + int sp_cache_routine(THD *thd, const Database_qualified_name *name, + bool lookup_only, sp_head **sp) const + { + return sp_cache_package_routine(thd, name, lookup_only, sp); + } + sp_head *sp_find_routine(THD *thd, + const Database_qualified_name *name, + bool cache_only) const + { + return sp_find_package_routine(thd, name, cache_only); + } +}; + + +class Sp_handler_package: public Sp_handler +{ +public: + bool 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; +}; + + +class Sp_handler_package_spec: public Sp_handler_package +{ +public: // TODO: make it private or protected + int sp_find_and_drop_routine(THD *thd, TABLE *table, + const Database_qualified_name *name) + const; +public: + stored_procedure_type type() const { return TYPE_ENUM_PACKAGE; } + LEX_CSTRING type_lex_cstring() const + { + static LEX_CSTRING m_type_str= {C_STRING_WITH_LEN("PACKAGE")}; + return m_type_str; + } + LEX_CSTRING empty_body_lex_cstring() const + { + static LEX_CSTRING m_empty_body= {C_STRING_WITH_LEN("BEGIN END")}; + return m_empty_body; + } + const char *show_create_routine_col1_caption() const + { + return "Package"; + } + const char *show_create_routine_col3_caption() const + { + return "Create Package"; + } + MDL_key::enum_mdl_namespace get_mdl_type() const + { + return MDL_key::PACKAGE_BODY; + } + sp_cache **get_cache(THD *) const; +#ifndef NO_EMBEDDED_ACCESS_CHECKS + HASH *get_priv_hash() const; +#endif +}; + + +class Sp_handler_package_body: public Sp_handler_package +{ +public: + stored_procedure_type type() const { return TYPE_ENUM_PACKAGE_BODY; } + LEX_CSTRING type_lex_cstring() const + { + static LEX_CSTRING m_type_str= {C_STRING_WITH_LEN("PACKAGE BODY")}; + return m_type_str; + } + LEX_CSTRING empty_body_lex_cstring() const + { + static LEX_CSTRING m_empty_body= {C_STRING_WITH_LEN("BEGIN END")}; + return m_empty_body; + } + const char *show_create_routine_col1_caption() const + { + return "Package body"; + } + const char *show_create_routine_col3_caption() const + { + return "Create Package Body"; + } + MDL_key::enum_mdl_namespace get_mdl_type() const + { + return MDL_key::PACKAGE_BODY; + } + sp_cache **get_cache(THD *) const; +#ifndef NO_EMBEDDED_ACCESS_CHECKS + HASH *get_priv_hash() const; +#endif +}; + + class Sp_handler_trigger: public Sp_handler { public: @@ -266,6 +447,10 @@ public: extern MYSQL_PLUGIN_IMPORT Sp_handler_function sp_handler_function; extern MYSQL_PLUGIN_IMPORT Sp_handler_procedure sp_handler_procedure; +extern MYSQL_PLUGIN_IMPORT Sp_handler_package_spec sp_handler_package_spec; +extern MYSQL_PLUGIN_IMPORT Sp_handler_package_body sp_handler_package_body; +extern MYSQL_PLUGIN_IMPORT Sp_handler_package_function sp_handler_package_function; +extern MYSQL_PLUGIN_IMPORT Sp_handler_package_procedure sp_handler_package_procedure; extern MYSQL_PLUGIN_IMPORT Sp_handler_trigger sp_handler_trigger; @@ -286,6 +471,17 @@ inline const Sp_handler *Sp_handler::handler(enum_sql_command cmd) case SQLCOM_SHOW_CREATE_FUNC: case SQLCOM_SHOW_STATUS_FUNC: return &sp_handler_function; + case SQLCOM_CREATE_PACKAGE: + case SQLCOM_DROP_PACKAGE: + case SQLCOM_SHOW_CREATE_PACKAGE: + case SQLCOM_SHOW_STATUS_PACKAGE: + return &sp_handler_package_spec; + case SQLCOM_CREATE_PACKAGE_BODY: + case SQLCOM_DROP_PACKAGE_BODY: + case SQLCOM_SHOW_CREATE_PACKAGE_BODY: + case SQLCOM_SHOW_STATUS_PACKAGE_BODY: + case SQLCOM_SHOW_PACKAGE_BODY_CODE: + return &sp_handler_package_body; default: break; } @@ -300,6 +496,10 @@ inline const Sp_handler *Sp_handler::handler(stored_procedure_type type) return &sp_handler_procedure; case TYPE_ENUM_FUNCTION: return &sp_handler_function; + case TYPE_ENUM_PACKAGE: + return &sp_handler_package_spec; + case TYPE_ENUM_PACKAGE_BODY: + return &sp_handler_package_body; case TYPE_ENUM_TRIGGER: return &sp_handler_trigger; case TYPE_ENUM_PROXY: @@ -316,6 +516,8 @@ inline const Sp_handler *Sp_handler::handler(MDL_key::enum_mdl_namespace type) return &sp_handler_function; case MDL_key::PROCEDURE: return &sp_handler_procedure; + case MDL_key::PACKAGE_BODY: + return &sp_handler_package_body; case MDL_key::GLOBAL: case MDL_key::SCHEMA: case MDL_key::TABLE: @@ -425,12 +627,16 @@ public: */ ulong m_sp_cache_version; + const Sp_handler *m_handler; + int sp_cache_routine(THD *thd, bool lookup_only, sp_head **sp) const; }; 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); void sp_remove_not_own_routines(Query_tables_list *prelocking_ctx); bool sp_update_sp_used_routines(HASH *dst, HASH *src); void sp_update_stmt_used_routines(THD *thd, Query_tables_list *prelocking_ctx, diff --git a/sql/sp_head.cc b/sql/sp_head.cc index cdb5e256e46..c088dc6ba12 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -192,6 +192,8 @@ sp_get_flags_for_command(LEX *lex) case SQLCOM_SHOW_CREATE_DB: case SQLCOM_SHOW_CREATE_FUNC: case SQLCOM_SHOW_CREATE_PROC: + case SQLCOM_SHOW_CREATE_PACKAGE: + case SQLCOM_SHOW_CREATE_PACKAGE_BODY: case SQLCOM_SHOW_CREATE_EVENT: case SQLCOM_SHOW_CREATE_TRIGGER: case SQLCOM_SHOW_CREATE_USER: @@ -211,11 +213,14 @@ sp_get_flags_for_command(LEX *lex) case SQLCOM_SHOW_PRIVILEGES: case SQLCOM_SHOW_PROCESSLIST: case SQLCOM_SHOW_PROC_CODE: + case SQLCOM_SHOW_PACKAGE_BODY_CODE: case SQLCOM_SHOW_SLAVE_HOSTS: case SQLCOM_SHOW_SLAVE_STAT: case SQLCOM_SHOW_STATUS: case SQLCOM_SHOW_STATUS_FUNC: case SQLCOM_SHOW_STATUS_PROC: + case SQLCOM_SHOW_STATUS_PACKAGE: + case SQLCOM_SHOW_STATUS_PACKAGE_BODY: case SQLCOM_SHOW_STORAGE_ENGINES: case SQLCOM_SHOW_TABLES: case SQLCOM_SHOW_TABLE_STATUS: @@ -260,6 +265,8 @@ sp_get_flags_for_command(LEX *lex) break; case SQLCOM_CREATE_INDEX: case SQLCOM_CREATE_DB: + case SQLCOM_CREATE_PACKAGE: + case SQLCOM_CREATE_PACKAGE_BODY: case SQLCOM_CREATE_VIEW: case SQLCOM_CREATE_TRIGGER: case SQLCOM_CREATE_USER: @@ -276,6 +283,8 @@ sp_get_flags_for_command(LEX *lex) case SQLCOM_RENAME_USER: case SQLCOM_DROP_INDEX: case SQLCOM_DROP_DB: + case SQLCOM_DROP_PACKAGE: + case SQLCOM_DROP_PACKAGE_BODY: case SQLCOM_REVOKE_ALL: case SQLCOM_DROP_USER: case SQLCOM_DROP_ROLE: @@ -502,9 +511,10 @@ sp_head::operator delete(void *ptr, size_t size) throw() } -sp_head::sp_head(const Sp_handler *sph) +sp_head::sp_head(sp_package *parent, const Sp_handler *sph) :Query_arena(&main_mem_root, STMT_INITIALIZED_FOR_SP), Database_qualified_name(&null_clex_str, &null_clex_str), + m_parent(parent), m_handler(sph), m_flags(0), m_tmp_query(NULL), @@ -529,6 +539,9 @@ sp_head::sp_head(const Sp_handler *sph) m_param_begin(NULL), m_param_end(NULL), m_body_begin(NULL), + m_thd_root(NULL), + m_thd(NULL), + m_pcont(new (&main_mem_root) sp_pcontext()), m_cont_level(0) { m_first_instance= this; @@ -543,6 +556,7 @@ sp_head::sp_head(const Sp_handler *sph) m_backpatch_goto.empty(); m_cont_backpatch.empty(); m_lex.empty(); + my_init_dynamic_array(&m_instr, sizeof(sp_instr *), 16, 8, MYF(0)); my_hash_init(&m_sptabs, system_charset_info, 0, 0, 0, sp_table_key, 0, 0); my_hash_init(&m_sroutines, system_charset_info, 0, 0, 0, sp_sroutine_key, 0, 0); @@ -551,12 +565,169 @@ sp_head::sp_head(const Sp_handler *sph) } +sp_package::sp_package(LEX *top_level_lex, + const sp_name *name, + const Sp_handler *sph) + :sp_head(NULL, sph), + m_current_routine(NULL), + m_top_level_lex(top_level_lex), + m_rcontext(NULL), + m_invoked_subroutine_count(0), + m_is_instantiated(false), + m_is_cloning_routine(false) +{ + init_sp_name(name); +} + + +sp_package::~sp_package() +{ + m_routine_implementations.cleanup(); + m_routine_declarations.cleanup(); + m_body= null_clex_str; + if (m_current_routine) + delete m_current_routine->sphead; + delete m_rcontext; +} + + +/* + Test if two routines have equal specifications +*/ +bool sp_head::eq_routine_spec(const sp_head *sp) const +{ + // TODO: Add tests for equal return data types (in case of FUNCTION) + // TODO: Add tests for equal argument data types + return + m_handler->type() == sp->m_handler->type() && + m_pcont->context_var_count() == sp->m_pcont->context_var_count(); +} + + +bool sp_package::validate_after_parser(THD *thd) +{ + if (m_handler->type() != TYPE_ENUM_PACKAGE_BODY) + return false; + sp_head *sp= sp_cache_lookup(&thd->sp_package_spec_cache, this); + sp_package *spec= sp ? sp->get_package() : NULL; + DBUG_ASSERT(spec); // CREATE PACKAGE must already be cached + return validate_public_routines(thd, spec) || + validate_private_routines(thd); +} + + +bool sp_package::validate_public_routines(THD *thd, sp_package *spec) +{ + /* + Check that all routines declared in CREATE PACKAGE + have implementations in CREATE PACKAGE BODY. + */ + List_iterator<LEX> it(spec->m_routine_declarations); + for (LEX *lex; (lex= it++); ) + { + bool found= false; + DBUG_ASSERT(lex->sphead); + List_iterator<LEX> it2(m_routine_implementations); + for (LEX *lex2; (lex2= it2++); ) + { + DBUG_ASSERT(lex2->sphead); + if (Sp_handler::eq_routine_name(lex2->sphead->m_name, + lex->sphead->m_name) && + lex2->sphead->eq_routine_spec(lex->sphead)) + { + found= true; + break; + } + } + if (!found) + { + my_error(ER_PACKAGE_ROUTINE_IN_SPEC_NOT_DEFINED_IN_BODY, MYF(0), + ErrConvDQName(lex->sphead).ptr()); + return true; + } + } + return false; +} + + +bool sp_package::validate_private_routines(THD *thd) +{ + /* + Check that all forwad declarations in + CREATE PACKAGE BODY have implementations. + */ + List_iterator<LEX> it(m_routine_declarations); + for (LEX *lex; (lex= it++); ) + { + bool found= false; + DBUG_ASSERT(lex->sphead); + List_iterator<LEX> it2(m_routine_implementations); + for (LEX *lex2; (lex2= it2++); ) + { + DBUG_ASSERT(lex2->sphead); + if (Sp_handler::eq_routine_name(lex2->sphead->m_name, + lex->sphead->m_name) && + lex2->sphead->eq_routine_spec(lex->sphead)) + { + found= true; + break; + } + } + if (!found) + { + my_error(ER_PACKAGE_ROUTINE_FORWARD_DECLARATION_NOT_DEFINED, MYF(0), + ErrConvDQName(lex->sphead).ptr()); + return true; + } + } + return false; +} + + +LEX *sp_package::LexList::find(const LEX_CSTRING &name, + stored_procedure_type type) +{ + List_iterator<LEX> it(*this); + for (LEX *lex; (lex= it++); ) + { + DBUG_ASSERT(lex->sphead); + const char *dot; + if (lex->sphead->m_handler->type() == type && + (dot= strrchr(lex->sphead->m_name.str, '.'))) + { + size_t ofs= dot + 1 - lex->sphead->m_name.str; + LEX_CSTRING non_qualified_sphead_name= lex->sphead->m_name; + non_qualified_sphead_name.str+= ofs; + non_qualified_sphead_name.length-= ofs; + if (Sp_handler::eq_routine_name(non_qualified_sphead_name, name)) + return lex; + } + } + return NULL; +} + + +LEX *sp_package::LexList::find_qualified(const LEX_CSTRING &name, + stored_procedure_type type) +{ + List_iterator<LEX> it(*this); + for (LEX *lex; (lex= it++); ) + { + DBUG_ASSERT(lex->sphead); + if (lex->sphead->m_handler->type() == type && + Sp_handler::eq_routine_name(lex->sphead->m_name, name)) + return lex; + } + return NULL; +} + + void sp_head::init(LEX *lex) { DBUG_ENTER("sp_head::init"); - lex->spcont= m_pcont= new sp_pcontext(); + lex->spcont= m_pcont; if (!lex->spcont) DBUG_VOID_RETURN; @@ -566,7 +737,6 @@ sp_head::init(LEX *lex) types of stored procedures to simplify reset_lex()/restore_lex() code. */ lex->trg_table_fields.empty(); - my_init_dynamic_array(&m_instr, sizeof(sp_instr *), 16, 8, MYF(0)); DBUG_VOID_RETURN; } @@ -584,9 +754,6 @@ sp_head::init_sp_name(const sp_name *spname) /* We have to copy strings to get them into the right memroot. */ Database_qualified_name::copy(&main_mem_root, spname->m_db, spname->m_name); m_explicit_name= spname->m_explicit_name; - - spname->make_qname(&main_mem_root, &m_qname); - DBUG_VOID_RETURN; } @@ -681,6 +848,17 @@ sp_head::~sp_head() } +void sp_package::LexList::cleanup() +{ + List_iterator<LEX> it(*this); + for (LEX *lex; (lex= it++); ) + { + lex_end(lex); + delete lex; + } +} + + /** This is only used for result fields from functions (both during fix_length_and_dec() and evaluation). @@ -992,6 +1170,8 @@ sp_head::execute(THD *thd, bool merge_da_on_success) DBUG_ASSERT(!(m_flags & IS_INVOKED)); m_flags|= IS_INVOKED; + if (m_parent) + m_parent->m_invoked_subroutine_count++; m_first_instance->m_first_free_instance= m_next_cached_sp; if (m_next_cached_sp) { @@ -1312,6 +1492,8 @@ sp_head::execute(THD *thd, bool merge_da_on_success) err_status|= mysql_change_db(thd, (LEX_CSTRING*) &saved_cur_db_name, TRUE); } m_flags&= ~IS_INVOKED; + if (m_parent) + m_parent->m_invoked_subroutine_count--; DBUG_PRINT("info", ("first free for %p --: %p->%p, level: %lu, flags %x", m_first_instance, @@ -1385,8 +1567,7 @@ set_routine_security_ctx(THD *thd, sp_head *sp, Security_context **save_ctx) statement that 'may' affect this. */ if (*save_ctx && - check_routine_access(thd, EXECUTE_ACL, - &sp->m_db, &sp->m_name, sp->m_handler, false)) + sp->check_execute_access(thd)) { sp->m_security_ctx.restore_security_context(thd, *save_ctx); *save_ctx= 0; @@ -1400,9 +1581,10 @@ set_routine_security_ctx(THD *thd, sp_head *sp, Security_context **save_ctx) bool sp_head::check_execute_access(THD *thd) const { - return check_routine_access(thd, EXECUTE_ACL, - &m_db, &m_name, - m_handler, false); + return m_parent ? m_parent->check_execute_access(thd) : + check_routine_access(thd, EXECUTE_ACL, + &m_db, &m_name, + m_handler, false); } @@ -1593,6 +1775,42 @@ err_with_cleanup: } +/* + Execute the package initialization section. +*/ +bool sp_package::instantiate_if_needed(THD *thd) +{ + List<Item> args; + if (m_is_instantiated) + return false; + /* + Set m_is_instantiated to true early, to avoid recursion in case if + the package initialization section calls routines from the same package. + */ + m_is_instantiated= true; + /* + Check that the initialization section doesn't contain Dynamic SQL + and doesn't return result sets: such stored procedures can't + be called from a function or trigger. + */ + if (thd->in_sub_stmt) + { + const char *where= (thd->in_sub_stmt & SUB_STMT_TRIGGER ? + "trigger" : "function"); + if (is_not_allowed_in_function(where)) + goto err; + } + + args.elements= 0; + if (execute_procedure(thd, &args)) + goto err; + return false; +err: + m_is_instantiated= false; + return true; +} + + /** Execute a function. @@ -1642,6 +1860,9 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount, DBUG_ENTER("sp_head::execute_function"); DBUG_PRINT("info", ("function %s", m_name.str)); + if (m_parent && m_parent->instantiate_if_needed(thd)) + DBUG_RETURN(true); + /* Check that the function is called with all specified arguments. @@ -1869,9 +2090,13 @@ sp_head::execute_procedure(THD *thd, List<Item> *args) sp_rcontext *nctx = NULL; bool save_enable_slow_log; bool save_log_general= false; + sp_package *pkg= get_package(); DBUG_ENTER("sp_head::execute_procedure"); DBUG_PRINT("info", ("procedure %s", m_name.str)); + if (m_parent && m_parent->instantiate_if_needed(thd)) + DBUG_RETURN(true); + if (args->elements != params) { my_error(ER_SP_WRONG_NO_OF_ARGS, MYF(0), "PROCEDURE", @@ -1895,11 +2120,32 @@ sp_head::execute_procedure(THD *thd, List<Item> *args) thd->spcont->callers_arena= thd; } - if (!(nctx= rcontext_create(thd, NULL, args))) + if (!pkg) { - delete nctx; /* Delete nctx if it was init() that failed. */ - thd->spcont= save_spcont; - DBUG_RETURN(TRUE); + if (!(nctx= rcontext_create(thd, NULL, args))) + { + delete nctx; /* Delete nctx if it was init() that failed. */ + thd->spcont= save_spcont; + DBUG_RETURN(TRUE); + } + } + else + { + if (!pkg->m_rcontext) + { + Query_arena backup_arena; + thd->set_n_backup_active_arena(this, &backup_arena); + nctx= pkg->rcontext_create(thd, NULL, args); + thd->restore_active_arena(this, &backup_arena); + if (!nctx) + { + thd->spcont= save_spcont; + DBUG_RETURN(TRUE); + } + pkg->m_rcontext= nctx; + } + else + nctx= pkg->m_rcontext; } if (params > 0) @@ -2105,7 +2351,8 @@ sp_head::execute_procedure(THD *thd, List<Item> *args) if (!save_spcont) delete octx; - delete nctx; + if (!pkg) + delete nctx; thd->spcont= save_spcont; thd->utime_after_lock= utime_before_sp_exec; @@ -3366,10 +3613,16 @@ sp_instr_set::execute(THD *thd, uint *nextp) } +sp_rcontext *sp_instr_set::get_rcontext(THD *thd) const +{ + return m_rcontext_handler->get_rcontext(thd->spcont); +} + + int sp_instr_set::exec_core(THD *thd, uint *nextp) { - int res= thd->spcont->set_variable(thd, m_offset, &m_value); + int res= get_rcontext(thd)->set_variable(thd, m_offset, &m_value); delete_explain_query(thd->lex); *nextp = m_ip+1; return res; @@ -3381,13 +3634,15 @@ sp_instr_set::print(String *str) /* set name@offset ... */ size_t rsrv = SP_INSTR_UINT_MAXLEN+6; sp_variable *var = m_ctx->find_variable(m_offset); + const LEX_CSTRING *prefix= m_rcontext_handler->get_name_prefix(); /* 'var' should always be non-null, but just in case... */ if (var) - rsrv+= var->name.length; + rsrv+= var->name.length + prefix->length; if (str->reserve(rsrv)) return; str->qs_append(STRING_WITH_LEN("set ")); + str->qs_append(prefix->str, prefix->length); if (var) { str->qs_append(&var->name); @@ -3407,8 +3662,9 @@ sp_instr_set::print(String *str) int sp_instr_set_row_field::exec_core(THD *thd, uint *nextp) { - int res= thd->spcont->set_variable_row_field(thd, m_offset, m_field_offset, - &m_value); + int res= get_rcontext(thd)->set_variable_row_field(thd, m_offset, + m_field_offset, + &m_value); delete_explain_query(thd->lex); *nextp= m_ip + 1; return res; @@ -3421,16 +3677,18 @@ sp_instr_set_row_field::print(String *str) /* set name@offset[field_offset] ... */ size_t rsrv= SP_INSTR_UINT_MAXLEN + 6 + 6 + 3; sp_variable *var= m_ctx->find_variable(m_offset); + const LEX_CSTRING *prefix= m_rcontext_handler->get_name_prefix(); DBUG_ASSERT(var); DBUG_ASSERT(var->field_def.is_row()); const Column_definition *def= var->field_def.row_field_definitions()->elem(m_field_offset); DBUG_ASSERT(def); - rsrv+= var->name.length + def->field_name.length; + rsrv+= var->name.length + def->field_name.length + prefix->length; if (str->reserve(rsrv)) return; str->qs_append(STRING_WITH_LEN("set ")); + str->qs_append(prefix); str->qs_append(&var->name); str->qs_append('.'); str->qs_append(&def->field_name); @@ -3452,9 +3710,9 @@ sp_instr_set_row_field::print(String *str) int sp_instr_set_row_field_by_name::exec_core(THD *thd, uint *nextp) { - int res= thd->spcont->set_variable_row_field_by_name(thd, m_offset, - m_field_name, - &m_value); + int res= get_rcontext(thd)->set_variable_row_field_by_name(thd, m_offset, + m_field_name, + &m_value); delete_explain_query(thd->lex); *nextp= m_ip + 1; return res; @@ -3467,14 +3725,16 @@ sp_instr_set_row_field_by_name::print(String *str) /* set name.field@offset["field"] ... */ size_t rsrv= SP_INSTR_UINT_MAXLEN + 6 + 6 + 3 + 2; sp_variable *var= m_ctx->find_variable(m_offset); + const LEX_CSTRING *prefix= m_rcontext_handler->get_name_prefix(); DBUG_ASSERT(var); DBUG_ASSERT(var->field_def.is_table_rowtype_ref() || var->field_def.is_cursor_rowtype_ref()); - rsrv+= var->name.length + 2 * m_field_name.length; + rsrv+= var->name.length + 2 * m_field_name.length + prefix->length; if (str->reserve(rsrv)) return; str->qs_append(STRING_WITH_LEN("set ")); + str->qs_append(prefix); str->qs_append(&var->name); str->qs_append('.'); str->qs_append(&m_field_name); @@ -4639,6 +4899,7 @@ Item *sp_head::adjust_assignment_source(THD *thd, Item *val, Item *val2) bool sp_head::set_local_variable(THD *thd, sp_pcontext *spcont, + const Sp_rcontext_handler *rh, sp_variable *spv, Item *val, LEX *lex, bool responsible_to_free_lex) { @@ -4646,7 +4907,7 @@ sp_head::set_local_variable(THD *thd, sp_pcontext *spcont, return true; sp_instr_set *sp_set= new (thd->mem_root) - sp_instr_set(instructions(), spcont, + sp_instr_set(instructions(), spcont, rh, spv->offset, val, lex, responsible_to_free_lex); @@ -4659,6 +4920,7 @@ sp_head::set_local_variable(THD *thd, sp_pcontext *spcont, */ bool sp_head::set_local_variable_row_field(THD *thd, sp_pcontext *spcont, + const Sp_rcontext_handler *rh, sp_variable *spv, uint field_idx, Item *val, LEX *lex) { @@ -4667,7 +4929,7 @@ sp_head::set_local_variable_row_field(THD *thd, sp_pcontext *spcont, sp_instr_set_row_field *sp_set= new (thd->mem_root) sp_instr_set_row_field(instructions(), - spcont, + spcont, rh, spv->offset, field_idx, val, lex, true); @@ -4677,6 +4939,7 @@ sp_head::set_local_variable_row_field(THD *thd, sp_pcontext *spcont, bool sp_head::set_local_variable_row_field_by_name(THD *thd, sp_pcontext *spcont, + const Sp_rcontext_handler *rh, sp_variable *spv, const LEX_CSTRING *field_name, Item *val, LEX *lex) @@ -4686,7 +4949,7 @@ sp_head::set_local_variable_row_field_by_name(THD *thd, sp_pcontext *spcont, sp_instr_set_row_field_by_name *sp_set= new (thd->mem_root) sp_instr_set_row_field_by_name(instructions(), - spcont, + spcont, rh, spv->offset, *field_name, val, @@ -4766,6 +5029,7 @@ sp_head::add_set_for_loop_cursor_param_variables(THD *thd, bool last= idx + 1 == parameters->argument_count(); sp_variable *spvar= param_spcont->get_context_variable(idx); if (set_local_variable(thd, param_spcont, + &sp_rcontext_handler_local, spvar, parameters->arguments()[idx], param_lex, last)) return true; @@ -4837,3 +5101,44 @@ bool sp_head::spvar_fill_table_rowtype_reference(THD *thd, fill_spvar_using_table_rowtype_reference(thd, spvar, ref); return false; } + + +/* + In Oracle mode stored routines have an optional name + at the end of a declaration: + PROCEDURE p1 AS + BEGIN + NULL + END p1; + Check that the first p1 and the last p1 match. +*/ +bool sp_head::check_package_routine_end_name(const LEX_CSTRING &end_name) const +{ + LEX_CSTRING non_qualified_name= m_name; + const char *errpos; + size_t ofs; + if (!end_name.length) + return false; // No end name + if (!(errpos= strrchr(m_name.str, '.'))) + { + errpos= m_name.str; + goto err; + } + errpos++; + ofs= errpos - m_name.str; + non_qualified_name.str+= ofs; + non_qualified_name.length-= ofs; + if (Sp_handler::eq_routine_name(end_name, non_qualified_name)) + return false; +err: + my_error(ER_END_IDENTIFIER_DOES_NOT_MATCH, MYF(0), end_name.str, errpos); + return true; +} + + +ulong sp_head::sp_cache_version() const +{ + return m_parent ? m_parent->sp_cache_version() : + m_sp_cache_version; + +} diff --git a/sql/sp_head.h b/sql/sp_head.h index 7f041058829..4cef33a76a7 100644 --- a/sql/sp_head.h +++ b/sql/sp_head.h @@ -132,6 +132,7 @@ class sp_head :private Query_arena, sp_head(const sp_head &); /**< Prevent use of these */ void operator=(sp_head &); +protected: MEM_ROOT main_mem_root; public: /** Possible values of m_flags */ @@ -170,6 +171,7 @@ public: HAS_AGGREGATE_INSTR= 16384 }; + sp_package *m_parent; const Sp_handler *m_handler; uint m_flags; // Boolean attributes of a stored routine @@ -204,13 +206,13 @@ public: /** Is this routine being executed? */ - bool is_invoked() const { return m_flags & IS_INVOKED; } + virtual bool is_invoked() const { return m_flags & IS_INVOKED; } /** Get the value of the SP cache version, as remembered when the routine was inserted into the cache. */ - ulong sp_cache_version() const { return m_sp_cache_version; } + ulong sp_cache_version() const; /** Set the value of the SP cache version. */ void set_sp_cache_version(ulong version_arg) const @@ -224,6 +226,7 @@ public: sp_rcontext *rcontext_create(THD *thd, Field *retval, Row_definition_list *list, bool switch_security_ctx); + bool eq_routine_spec(const sp_head *) const; private: /** Version of the stored routine cache at the moment when the @@ -319,7 +322,7 @@ public: static void operator delete(void *ptr, size_t size) throw (); - sp_head(const Sp_handler *handler); + sp_head(sp_package *parent, const Sp_handler *handler); /// Initialize after we have reset mem_root void @@ -399,15 +402,19 @@ public: @retval false - on success */ bool set_local_variable(THD *thd, sp_pcontext *spcont, + const Sp_rcontext_handler *rh, sp_variable *spv, Item *val, LEX *lex, bool responsible_to_free_lex); bool set_local_variable_row_field(THD *thd, sp_pcontext *spcont, + const Sp_rcontext_handler *rh, sp_variable *spv, uint field_idx, Item *val, LEX *lex); bool set_local_variable_row_field_by_name(THD *thd, sp_pcontext *spcont, + const Sp_rcontext_handler *rh, sp_variable *spv, const LEX_CSTRING *field_name, Item *val, LEX *lex); + bool check_package_routine_end_name(const LEX_CSTRING &end_name) const; private: /** Generate a code to set a single cursor parameter variable. @@ -430,7 +437,9 @@ private: */ DBUG_ASSERT(m_thd->free_list == NULL); m_thd->free_list= prm->get_free_list(); - if (set_local_variable(thd, param_spcont, spvar, prm->get_item(), prm, true)) + if (set_local_variable(thd, param_spcont, + &sp_rcontext_handler_local, + spvar, prm->get_item(), prm, true)) return true; /* Safety: @@ -830,9 +839,19 @@ public: sp_pcontext *get_parse_context() { return m_pcont; } + /* + Check EXECUTE access: + - in case of a standalone rotuine, for the routine itself + - in case of a package routine, for the owner package body + */ bool check_execute_access(THD *thd) const; -private: + virtual sp_package *get_package() + { + return NULL; + } + +protected: MEM_ROOT *m_thd_root; ///< Temp. store for thd's mem_root THD *m_thd; ///< Set if we have reset mem_root @@ -897,6 +916,92 @@ private: }; // class sp_head : public Sql_alloc +class sp_package: public sp_head +{ + bool validate_public_routines(THD *thd, sp_package *spec); + bool validate_private_routines(THD *thd); +public: + class LexList: public List<LEX> + { + public: + LexList() { elements= 0; } + // Find a package routine by a non qualified name + LEX *find(const LEX_CSTRING &name, stored_procedure_type type); + // Find a package routine by a package-qualified name, e.g. 'pkg.proc' + LEX *find_qualified(const LEX_CSTRING &name, stored_procedure_type type); + // Check if a routine with the given qualified name already exists + bool check_dup_qualified(const LEX_CSTRING &name, const Sp_handler *sph) + { + if (!find_qualified(name, sph->type())) + return false; + my_error(ER_SP_ALREADY_EXISTS, MYF(0), sph->type_str(), name.str); + return true; + } + bool check_dup_qualified(const sp_head *sp) + { + return check_dup_qualified(sp->m_name, sp->m_handler); + } + void cleanup(); + }; + /* + The LEX for a new package subroutine is initially assigned to + m_current_routine. After scanning parameters, return type and chistics, + the parser detects if we have a declaration or a definition, e.g.: + PROCEDURE p1(a INT); + vs + PROCEDURE p1(a INT) AS BEGIN NULL; END; + (i.e. either semicolon or the "AS" keyword) + m_current_routine is then added either to m_routine_implementations, + or m_routine_declarations, and then m_current_routine is set to NULL. + */ + LEX *m_current_routine; + LexList m_routine_implementations; + LexList m_routine_declarations; + + LEX *m_top_level_lex; + sp_rcontext *m_rcontext; + uint m_invoked_subroutine_count; + bool m_is_instantiated; + bool m_is_cloning_routine; + + sp_package(LEX *top_level_lex, + const sp_name *name, + const Sp_handler *sph); + ~sp_package(); + bool add_routine_declaration(LEX *lex) + { + return m_routine_declarations.check_dup_qualified(lex->sphead) || + m_routine_declarations.push_back(lex, &main_mem_root); + } + bool add_routine_implementation(LEX *lex) + { + return m_routine_implementations.check_dup_qualified(lex->sphead) || + m_routine_implementations.push_back(lex, &main_mem_root); + } + sp_package *get_package() { return this; } + bool is_invoked() const + { + /* + Cannot flush a package out of the SP cache when: + - its initialization block is running + - one of its subroutine is running + */ + return sp_head::is_invoked() || m_invoked_subroutine_count > 0; + } + sp_variable *find_package_variable(const LEX_CSTRING *name) const + { + /* + sp_head::m_pcont is a special level for routine parameters. + Variables declared inside CREATE PACKAGE BODY reside in m_children.at(0). + */ + sp_pcontext *ctx= m_pcont->child_context(0); + return ctx ? ctx->find_variable(name, true) : NULL; + } + bool validate_after_parser(THD *thd); + bool instantiate_if_needed(THD *thd); +}; + + class sp_lex_cursor: public sp_lex_local, public Query_arena { LEX_CSTRING m_cursor_name; @@ -1185,9 +1290,11 @@ class sp_instr_set : public sp_instr public: sp_instr_set(uint ip, sp_pcontext *ctx, + const Sp_rcontext_handler *rh, uint offset, Item *val, LEX *lex, bool lex_resp) - : sp_instr(ip, ctx), m_offset(offset), m_value(val), + : sp_instr(ip, ctx), + m_rcontext_handler(rh), m_offset(offset), m_value(val), m_lex_keeper(lex, lex_resp) {} @@ -1201,11 +1308,11 @@ public: virtual void print(String *str); protected: - + sp_rcontext *get_rcontext(THD *thd) const; + const Sp_rcontext_handler *m_rcontext_handler; uint m_offset; ///< Frame offset Item *m_value; sp_lex_keeper m_lex_keeper; - }; // class sp_instr_set : public sp_instr @@ -1223,10 +1330,11 @@ class sp_instr_set_row_field : public sp_instr_set public: sp_instr_set_row_field(uint ip, sp_pcontext *ctx, + const Sp_rcontext_handler *rh, uint offset, uint field_offset, Item *val, LEX *lex, bool lex_resp) - : sp_instr_set(ip, ctx, offset, val, lex, lex_resp), + : sp_instr_set(ip, ctx, rh, offset, val, lex, lex_resp), m_field_offset(field_offset) {} @@ -1265,10 +1373,11 @@ class sp_instr_set_row_field_by_name : public sp_instr_set public: sp_instr_set_row_field_by_name(uint ip, sp_pcontext *ctx, + const Sp_rcontext_handler *rh, uint offset, const LEX_CSTRING &field_name, Item *val, LEX *lex, bool lex_resp) - : sp_instr_set(ip, ctx, offset, val, lex, lex_resp), + : sp_instr_set(ip, ctx, rh, offset, val, lex, lex_resp), m_field_name(field_name) {} diff --git a/sql/sp_pcontext.h b/sql/sp_pcontext.h index 6a6920d89e1..e30af3fcde5 100644 --- a/sql/sp_pcontext.h +++ b/sql/sp_pcontext.h @@ -394,6 +394,9 @@ public: sp_pcontext *parent_context() const { return m_parent; } + sp_pcontext *child_context(uint i) const + { return i < m_children.elements() ? m_children.at(i) : NULL; } + /// Calculate and return the number of handlers to pop between the given /// context and this one. /// diff --git a/sql/sp_rcontext.cc b/sql/sp_rcontext.cc index 4009f8dce30..2e9ae23d7f9 100644 --- a/sql/sp_rcontext.cc +++ b/sql/sp_rcontext.cc @@ -30,6 +30,33 @@ #include "sql_acl.h" // SELECT_ACL #include "sql_parse.h" // check_table_access + +Sp_rcontext_handler_local sp_rcontext_handler_local; +Sp_rcontext_handler_package_body sp_rcontext_handler_package_body; + +sp_rcontext *Sp_rcontext_handler_local::get_rcontext(sp_rcontext *ctx) const +{ + return ctx; +} + +sp_rcontext *Sp_rcontext_handler_package_body::get_rcontext(sp_rcontext *ctx) const +{ + return ctx->m_sp->m_parent->m_rcontext; +} + +const LEX_CSTRING *Sp_rcontext_handler_local::get_name_prefix() const +{ + return &empty_clex_str; +} + +const LEX_CSTRING *Sp_rcontext_handler_package_body::get_name_prefix() const +{ + static const LEX_CSTRING sp_package_body_variable_prefix_clex_str= + {C_STRING_WITH_LEN("PACKAGE_BODY.")}; + return &sp_package_body_variable_prefix_clex_str; +} + + /////////////////////////////////////////////////////////////////////////// // sp_rcontext implementation. /////////////////////////////////////////////////////////////////////////// diff --git a/sql/sp_rcontext.h b/sql/sp_rcontext.h index c4f4cf182da..33d76e1c85a 100644 --- a/sql/sp_rcontext.h +++ b/sql/sp_rcontext.h @@ -183,7 +183,8 @@ public: uint instr_ptr; /// The stored program for which this runtime context is created. Used for - /// checking if correct runtime context is used for variable handling. + /// checking if correct runtime context is used for variable handling, + /// and to access the package run-time context. /// Also used by slow log. const sp_head *m_sp; diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index 96ed36da755..951471bca29 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -607,6 +607,7 @@ static MEM_ROOT acl_memroot, grant_memroot; static bool initialized=0; static bool allow_all_hosts=1; static HASH acl_check_hosts, column_priv_hash, proc_priv_hash, func_priv_hash; +static HASH package_spec_priv_hash, package_body_priv_hash; static DYNAMIC_ARRAY acl_wild_hosts; static Hash_filo<acl_entry> *acl_cache; static uint grant_version=0; /* Version of priv tables. incremented by acl_load */ @@ -650,6 +651,18 @@ HASH *Sp_handler_function::get_priv_hash() const } +HASH *Sp_handler_package_spec::get_priv_hash() const +{ + return &package_spec_priv_hash; +} + + +HASH *Sp_handler_package_body::get_priv_hash() const +{ + return &package_body_priv_hash; +} + + /* Enumeration of ACL/GRANT tables in the mysql database */ @@ -1312,6 +1325,8 @@ enum enum_acl_lists COLUMN_PRIVILEGES_HASH, PROC_PRIVILEGES_HASH, FUNC_PRIVILEGES_HASH, + PACKAGE_SPEC_PRIVILEGES_HASH, + PACKAGE_BODY_PRIVILEGES_HASH, PROXY_USERS_ACL, ROLES_MAPPINGS_HASH }; @@ -5401,7 +5416,10 @@ table_error: ******************************************************************/ struct PRIVS_TO_MERGE { - enum what { ALL, GLOBAL, DB, TABLE_COLUMN, PROC, FUNC } what; + enum what + { + ALL, GLOBAL, DB, TABLE_COLUMN, PROC, FUNC, PACKAGE_SPEC, PACKAGE_BODY + } what; const char *db, *name; }; @@ -5413,6 +5431,10 @@ static enum PRIVS_TO_MERGE::what sp_privs_to_merge(stored_procedure_type type) return PRIVS_TO_MERGE::FUNC; case TYPE_ENUM_PROCEDURE: return PRIVS_TO_MERGE::PROC; + case TYPE_ENUM_PACKAGE: + return PRIVS_TO_MERGE::PACKAGE_SPEC; + case TYPE_ENUM_PACKAGE_BODY: + return PRIVS_TO_MERGE::PACKAGE_BODY; case TYPE_ENUM_TRIGGER: case TYPE_ENUM_PROXY: break; @@ -6231,7 +6253,14 @@ static int merge_role_privileges(ACL_ROLE *role __attribute__((unused)), if (all || data->what == PRIVS_TO_MERGE::FUNC) changed|= merge_role_routine_grant_privileges(grantee, data->db, data->name, &role_hash, &func_priv_hash); - + if (all || data->what == PRIVS_TO_MERGE::PACKAGE_SPEC) + changed|= merge_role_routine_grant_privileges(grantee, + data->db, data->name, &role_hash, + &package_spec_priv_hash); + if (all || data->what == PRIVS_TO_MERGE::PACKAGE_BODY) + changed|= merge_role_routine_grant_privileges(grantee, + data->db, data->name, &role_hash, + &package_body_priv_hash); return !changed; // don't recurse into the subgraph if privs didn't change } @@ -7116,6 +7145,8 @@ void grant_free(void) my_hash_free(&column_priv_hash); my_hash_free(&proc_priv_hash); my_hash_free(&func_priv_hash); + my_hash_free(&package_spec_priv_hash); + my_hash_free(&package_body_priv_hash); free_root(&grant_memroot,MYF(0)); DBUG_VOID_RETURN; } @@ -7182,6 +7213,10 @@ static bool grant_load(THD *thd, 0,0,0, (my_hash_get_key) get_grant_table, 0,0); (void) my_hash_init(&func_priv_hash, &my_charset_utf8_bin, 0,0,0, (my_hash_get_key) get_grant_table, 0,0); + (void) my_hash_init(&package_spec_priv_hash, &my_charset_utf8_bin, + 0,0,0, (my_hash_get_key) get_grant_table, 0,0); + (void) my_hash_init(&package_body_priv_hash, &my_charset_utf8_bin, + 0,0,0, (my_hash_get_key) get_grant_table, 0,0); init_sql_alloc(&grant_memroot, "GRANT", ACL_ALLOC_BLOCK_SIZE, 0, MYF(0)); t_table= tables_priv.table(); @@ -7331,6 +7366,7 @@ static my_bool propagate_role_grants_action(void *role_ptr, bool grant_reload(THD *thd) { HASH old_column_priv_hash, old_proc_priv_hash, old_func_priv_hash; + HASH old_package_spec_priv_hash, old_package_body_priv_hash; MEM_ROOT old_mem; int result; DBUG_ENTER("grant_reload"); @@ -7350,6 +7386,8 @@ bool grant_reload(THD *thd) old_column_priv_hash= column_priv_hash; old_proc_priv_hash= proc_priv_hash; old_func_priv_hash= func_priv_hash; + old_package_spec_priv_hash= package_spec_priv_hash; + old_package_body_priv_hash= package_body_priv_hash; /* Create a new memory pool but save the current memory pool to make an undo @@ -7367,6 +7405,8 @@ bool grant_reload(THD *thd) column_priv_hash= old_column_priv_hash; /* purecov: deadcode */ proc_priv_hash= old_proc_priv_hash; func_priv_hash= old_func_priv_hash; + package_spec_priv_hash= old_package_spec_priv_hash; + package_body_priv_hash= old_package_body_priv_hash; grant_memroot= old_mem; /* purecov: deadcode */ } else @@ -7374,6 +7414,8 @@ bool grant_reload(THD *thd) my_hash_free(&old_column_priv_hash); my_hash_free(&old_proc_priv_hash); my_hash_free(&old_func_priv_hash); + my_hash_free(&old_package_spec_priv_hash); + my_hash_free(&old_package_body_priv_hash); free_root(&old_mem,MYF(0)); } @@ -8002,7 +8044,9 @@ bool check_grant_db(THD *thd, const char *db) if (error) error= check_grant_db_routine(thd, db, &proc_priv_hash) && - check_grant_db_routine(thd, db, &func_priv_hash); + check_grant_db_routine(thd, db, &func_priv_hash) && + check_grant_db_routine(thd, db, &package_spec_priv_hash) && + check_grant_db_routine(thd, db, &package_body_priv_hash); mysql_rwlock_unlock(&LOCK_grant); @@ -8399,6 +8443,14 @@ static bool print_grants_for_role(THD *thd, ACL_ROLE * role) buff, sizeof(buff))) return TRUE; + if (show_routine_grants(thd, role->user.str, "", &sp_handler_package_spec, + buff, sizeof(buff))) + return TRUE; + + if (show_routine_grants(thd, role->user.str, "", &sp_handler_package_body, + buff, sizeof(buff))) + return TRUE; + return FALSE; } @@ -8624,6 +8676,14 @@ bool mysql_show_grants(THD *thd, LEX_USER *lex_user) buff, sizeof(buff))) goto end; + if (show_routine_grants(thd, username, hostname, &sp_handler_package_spec, + buff, sizeof(buff))) + goto end; + + if (show_routine_grants(thd, username, hostname, &sp_handler_package_body, + buff, sizeof(buff))) + goto end; + if (show_proxy_grants(thd, username, hostname, buff, sizeof(buff))) goto end; } @@ -9559,6 +9619,14 @@ static int handle_grant_struct(enum enum_acl_lists struct_no, bool drop, grant_name_hash= &func_priv_hash; elements= grant_name_hash->records; break; + case PACKAGE_SPEC_PRIVILEGES_HASH: + grant_name_hash= &package_spec_priv_hash; + elements= grant_name_hash->records; + break; + case PACKAGE_BODY_PRIVILEGES_HASH: + grant_name_hash= &package_body_priv_hash; + elements= grant_name_hash->records; + break; case PROXY_USERS_ACL: elements= acl_proxy_users.elements; break; @@ -9597,6 +9665,8 @@ static int handle_grant_struct(enum enum_acl_lists struct_no, bool drop, case COLUMN_PRIVILEGES_HASH: case PROC_PRIVILEGES_HASH: case FUNC_PRIVILEGES_HASH: + case PACKAGE_SPEC_PRIVILEGES_HASH: + case PACKAGE_BODY_PRIVILEGES_HASH: grant_name= (GRANT_NAME*) my_hash_element(grant_name_hash, idx); user= grant_name->user; host= grant_name->host.hostname; @@ -9679,6 +9749,8 @@ static int handle_grant_struct(enum enum_acl_lists struct_no, bool drop, case COLUMN_PRIVILEGES_HASH: case PROC_PRIVILEGES_HASH: case FUNC_PRIVILEGES_HASH: + case PACKAGE_SPEC_PRIVILEGES_HASH: + case PACKAGE_BODY_PRIVILEGES_HASH: my_hash_delete(grant_name_hash, (uchar*) grant_name); /* In our HASH implementation on deletion one elements @@ -9724,6 +9796,8 @@ static int handle_grant_struct(enum enum_acl_lists struct_no, bool drop, case COLUMN_PRIVILEGES_HASH: case PROC_PRIVILEGES_HASH: case FUNC_PRIVILEGES_HASH: + case PACKAGE_SPEC_PRIVILEGES_HASH: + case PACKAGE_BODY_PRIVILEGES_HASH: { /* Save old hash key and its length to be able to properly update @@ -9906,6 +9980,26 @@ static int handle_grant_data(THD *thd, Grant_tables& tables, bool drop, if (search_only) goto end; } + /* Handle package spec array. */ + if ((handle_grant_struct(PACKAGE_SPEC_PRIVILEGES_HASH, + drop, user_from, user_to) || found) + && ! result) + { + result= 1; /* At least one record/element found. */ + /* If search is requested, we do not need to search further. */ + if (search_only) + goto end; + } + /* Handle package body array. */ + if ((handle_grant_struct(PACKAGE_BODY_PRIVILEGES_HASH, + drop, user_from, user_to) || found) + && ! result) + { + result= 1; /* At least one record/element found. */ + /* If search is requested, we do not need to search further. */ + if (search_only) + goto end; + } } /* Handle tables table. */ @@ -10632,7 +10726,9 @@ bool mysql_revoke_all(THD *thd, List <LEX_USER> &list) /* Remove procedure access */ if (mysql_revoke_sp_privs(thd, &tables, &sp_handler_function, lex_user) || - mysql_revoke_sp_privs(thd, &tables, &sp_handler_procedure, lex_user)) + mysql_revoke_sp_privs(thd, &tables, &sp_handler_procedure, lex_user) || + mysql_revoke_sp_privs(thd, &tables, &sp_handler_package_spec, lex_user) || + mysql_revoke_sp_privs(thd, &tables, &sp_handler_package_body, lex_user)) result= -1; ACL_USER_BASE *user_or_role; @@ -11177,6 +11273,8 @@ SHOW_VAR acl_statistics[] = { {"database_grants", (char*)&acl_dbs.elements, SHOW_UINT}, {"function_grants", (char*)&func_priv_hash.records, SHOW_ULONG}, {"procedure_grants", (char*)&proc_priv_hash.records, SHOW_ULONG}, + {"package_spec_grants", (char*)&package_spec_priv_hash.records, SHOW_ULONG}, + {"package_body_grants", (char*)&package_body_priv_hash.records, SHOW_ULONG}, {"proxy_users", (char*)&acl_proxy_users.elements, SHOW_UINT}, {"role_grants", (char*)&acl_roles_mappings.records, SHOW_ULONG}, {"roles", (char*)&acl_roles.records, SHOW_ULONG}, diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 902933ffed8..e7be6ed4e90 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -3169,6 +3169,17 @@ open_and_process_routine(THD *thd, Query_tables_list *prelocking_ctx, switch (mdl_type) { + case MDL_key::PACKAGE_BODY: + DBUG_ASSERT(rt != (Sroutine_hash_entry*)prelocking_ctx->sroutines_list.first); + /* + No need to cache the package body itself. + It gets cached during open_and_process_routine() + for the first used package routine. See the package related code + in the "case" below. + */ + if (sp_acquire_mdl(thd, rt, ot_ctx)) + DBUG_RETURN(TRUE); + break; case MDL_key::FUNCTION: case MDL_key::PROCEDURE: { @@ -3183,11 +3194,13 @@ open_and_process_routine(THD *thd, Query_tables_list *prelocking_ctx, if (rt != (Sroutine_hash_entry*)prelocking_ctx->sroutines_list.first || mdl_type != MDL_key::PROCEDURE) { + /* + TODO: If this is a package routine, we should not put MDL + TODO: on the routine itself. We should put only the package MDL. + */ if (sp_acquire_mdl(thd, rt, ot_ctx)) DBUG_RETURN(TRUE); - DEBUG_SYNC(thd, "after_shared_lock_pname"); - /* Ensures the routine is up-to-date and cached, if exists. */ if (rt->sp_cache_routine(thd, has_prelocking_list, &sp)) DBUG_RETURN(TRUE); @@ -3202,8 +3215,24 @@ open_and_process_routine(THD *thd, Query_tables_list *prelocking_ctx, *routine_modifies_data= sp->modifies_data(); if (!has_prelocking_list) + { prelocking_strategy->handle_routine(thd, prelocking_ctx, rt, sp, need_prelocking); + if (sp->m_parent) + { + /* + If it's a package routine, we need also to handle the + package body, as its initialization section can use + some tables and routine calls. + TODO: Only package public routines actually need this. + TODO: Skip package body handling for private routines. + */ + *routine_modifies_data|= sp->m_parent->modifies_data(); + prelocking_strategy->handle_routine(thd, prelocking_ctx, rt, + sp->m_parent, + need_prelocking); + } + } } } else diff --git a/sql/sql_class.cc b/sql/sql_class.cc index c696ee897f2..540777ea605 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -913,6 +913,8 @@ THD::THD(my_thread_id id, bool is_wsrep_applier, bool skip_global_sys_var_lock) sp_proc_cache= NULL; sp_func_cache= NULL; + sp_package_spec_cache= NULL; + sp_package_body_cache= NULL; /* For user vars replication*/ if (opt_bin_log) @@ -1478,6 +1480,8 @@ void THD::change_user(void) (my_hash_free_key) free_sequence_last, HASH_THREAD_SPECIFIC); sp_cache_clear(&sp_proc_cache); sp_cache_clear(&sp_func_cache); + sp_cache_clear(&sp_package_spec_cache); + sp_cache_clear(&sp_package_body_cache); } /** @@ -1604,6 +1608,8 @@ void THD::cleanup(void) my_hash_free(&sequences); sp_cache_clear(&sp_proc_cache); sp_cache_clear(&sp_func_cache); + sp_cache_clear(&sp_package_spec_cache); + sp_cache_clear(&sp_package_body_cache); auto_inc_intervals_forced.empty(); auto_inc_intervals_in_cur_stmt_for_binlog.empty(); @@ -3732,7 +3738,8 @@ int select_dumpvar::prepare(List<Item> &list, SELECT_LEX_UNIT *u) mvsp->type_handler() == &type_handler_row) { // SELECT INTO row_type_sp_variable - if (thd->spcont->get_variable(mvsp->offset)->cols() != list.elements) + if (mvsp->get_rcontext(thd->spcont)->get_variable(mvsp->offset)->cols() != + list.elements) goto error; m_var_sp_row= mvsp; return 0; @@ -4076,14 +4083,22 @@ bool my_var_user::set(THD *thd, Item *item) return suv->fix_fields(thd, 0) || suv->update(); } + +sp_rcontext *my_var_sp::get_rcontext(sp_rcontext *local_ctx) const +{ + return m_rcontext_handler->get_rcontext(local_ctx); +} + + bool my_var_sp::set(THD *thd, Item *item) { - return thd->spcont->set_variable(thd, offset, &item); + return get_rcontext(thd->spcont)->set_variable(thd, offset, &item); } bool my_var_sp_row_field::set(THD *thd, Item *item) { - return thd->spcont->set_variable_row_field(thd, offset, m_field_offset, &item); + return get_rcontext(thd->spcont)-> + set_variable_row_field(thd, offset, m_field_offset, &item); } @@ -4118,7 +4133,8 @@ int select_dumpvar::send_data(List<Item> &items) DBUG_RETURN(1); } if (m_var_sp_row ? - thd->spcont->set_variable_row(thd, m_var_sp_row->offset, items) : + m_var_sp_row->get_rcontext(thd->spcont)-> + set_variable_row(thd, m_var_sp_row->offset, items) : send_data_to_var_list(items)) DBUG_RETURN(1); diff --git a/sql/sql_class.h b/sql/sql_class.h index 7e06dcc7d39..a43f4dec0c9 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -3166,6 +3166,8 @@ public: sp_rcontext *spcont; // SP runtime context sp_cache *sp_proc_cache; sp_cache *sp_func_cache; + sp_cache *sp_package_spec_cache; + sp_cache *sp_package_body_cache; /** number of name_const() substitutions, see sp_head.cc:subst_spvars() */ uint query_name_consts; @@ -5902,6 +5904,7 @@ public: }; class my_var_sp: public my_var { + const Sp_rcontext_handler *m_rcontext_handler; const Type_handler *m_type_handler; public: uint offset; @@ -5910,13 +5913,17 @@ public: runtime context is used for variable handling. */ sp_head *sp; - my_var_sp(const LEX_CSTRING *j, uint o, const Type_handler *type_handler, + my_var_sp(const Sp_rcontext_handler *rcontext_handler, + const LEX_CSTRING *j, uint o, const Type_handler *type_handler, sp_head *s) - : my_var(j, LOCAL_VAR), m_type_handler(type_handler), offset(o), sp(s) { } + : my_var(j, LOCAL_VAR), + m_rcontext_handler(rcontext_handler), + m_type_handler(type_handler), offset(o), sp(s) { } ~my_var_sp() { } bool set(THD *thd, Item *val); my_var_sp *get_my_var_sp() { return this; } const Type_handler *type_handler() const { return m_type_handler; } + sp_rcontext *get_rcontext(sp_rcontext *local_ctx) const; }; /* @@ -5927,9 +5934,11 @@ class my_var_sp_row_field: public my_var_sp { uint m_field_offset; public: - my_var_sp_row_field(const LEX_CSTRING *varname, const LEX_CSTRING *fieldname, + my_var_sp_row_field(const Sp_rcontext_handler *rcontext_handler, + const LEX_CSTRING *varname, const LEX_CSTRING *fieldname, uint var_idx, uint field_idx, sp_head *s) - :my_var_sp(varname, var_idx, &type_handler_double/*Not really used*/, s), + :my_var_sp(rcontext_handler, varname, var_idx, + &type_handler_double/*Not really used*/, s), m_field_offset(field_idx) { } bool set(THD *thd, Item *val); @@ -6304,6 +6313,9 @@ public: Database_qualified_name(const LEX_CSTRING *db, const LEX_CSTRING *name) :m_db(*db), m_name(*name) { } + Database_qualified_name(const LEX_CSTRING &db, const LEX_CSTRING &name) + :m_db(db), m_name(name) + { } Database_qualified_name(const char *db, size_t db_length, const char *name, size_t name_length) { @@ -6353,6 +6365,34 @@ public: DBUG_SLOW_ASSERT(ok_for_lower_case_names(m_db.str)); return false; } + + bool make_package_routine_name(MEM_ROOT *mem_root, + const LEX_CSTRING &package, + const LEX_CSTRING &routine) + { + char *tmp; + size_t length= package.length + 1 + routine.length + 1; + if (!(tmp= (char *) alloc_root(mem_root, length))) + return true; + m_name.length= my_snprintf(tmp, length, "%.*s.%.*s", + (int) package.length, package.str, + (int) routine.length, routine.str); + m_name.str= tmp; + return false; + } + + bool make_package_routine_name(MEM_ROOT *mem_root, + const LEX_CSTRING &db, + const LEX_CSTRING &package, + const LEX_CSTRING &routine) + { + if (make_package_routine_name(mem_root, package, routine)) + return true; + if (!(m_db.str= strmake_root(mem_root, db.str, db.length))) + return true; + m_db.length= db.length; + return false; + } }; diff --git a/sql/sql_cmd.h b/sql/sql_cmd.h index d95b1c828b9..1c4c89eb132 100644 --- a/sql/sql_cmd.h +++ b/sql/sql_cmd.h @@ -99,6 +99,15 @@ enum enum_sql_command { SQLCOM_CREATE_SEQUENCE, SQLCOM_DROP_SEQUENCE, SQLCOM_ALTER_SEQUENCE, + SQLCOM_CREATE_PACKAGE, + SQLCOM_DROP_PACKAGE, + SQLCOM_CREATE_PACKAGE_BODY, + SQLCOM_DROP_PACKAGE_BODY, + SQLCOM_SHOW_CREATE_PACKAGE, + SQLCOM_SHOW_CREATE_PACKAGE_BODY, + SQLCOM_SHOW_STATUS_PACKAGE, + SQLCOM_SHOW_STATUS_PACKAGE_BODY, + SQLCOM_SHOW_PACKAGE_BODY_CODE, /* When a command is added here, be sure it's also added in mysqld.cc @@ -175,8 +184,10 @@ class Sql_cmd_call : public Sql_cmd { public: class sp_name *m_name; - Sql_cmd_call(class sp_name *name) - :m_name(name) + const class Sp_handler *m_handler; + Sql_cmd_call(class sp_name *name, const class Sp_handler *handler) + :m_name(name), + m_handler(handler) {} virtual ~Sql_cmd_call() diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index a2a9e5b2f16..b169b9e0b27 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -2964,9 +2964,28 @@ void LEX::cleanup_lex_after_parse_error(THD *thd) */ if (thd->lex->sphead) { + sp_package *pkg; thd->lex->sphead->restore_thd_mem_root(thd); - delete thd->lex->sphead; - thd->lex->sphead= NULL; + if ((pkg= thd->lex->sphead->m_parent)) + { + /* + If a syntax error happened inside a package routine definition, + then thd->lex points to the routine sublex. We need to restore to + the top level LEX. + */ + DBUG_ASSERT(pkg->m_top_level_lex); + DBUG_ASSERT(pkg == pkg->m_top_level_lex->sphead); + pkg->restore_thd_mem_root(thd); + LEX *top= pkg->m_top_level_lex; + delete pkg; + thd->lex= top; + thd->lex->sphead= NULL; + } + else + { + delete thd->lex->sphead; + thd->lex->sphead= NULL; + } } } @@ -5198,13 +5217,50 @@ void LEX::set_stmt_init() }; +/** + Find a local or a package body variable by name. + @param IN name - the variable name + @param OUT ctx - NULL, if the variable was not found, + or LEX::spcont (if a local variable was found) + or the package top level context + (if a package variable was found) + @param OUT handler - NULL, if the variable was not found, + or a pointer to rcontext handler + @retval - the variable (if found), or NULL otherwise. +*/ +sp_variable * +LEX::find_variable(const LEX_CSTRING *name, + sp_pcontext **ctx, + const Sp_rcontext_handler **rh) const +{ + sp_variable *spv; + if (spcont && (spv= spcont->find_variable(name, false))) + { + *ctx= spcont; + *rh= &sp_rcontext_handler_local; + return spv; + } + sp_package *pkg= sphead ? sphead->m_parent : NULL; + if (pkg && (spv= pkg->find_package_variable(name))) + { + *ctx= pkg->get_parse_context()->child_context(0); + *rh= &sp_rcontext_handler_package_body; + return spv; + } + *ctx= NULL; + *rh= NULL; + return NULL; +} + + bool LEX::init_internal_variable(struct sys_var_with_base *variable, const LEX_CSTRING *name) { sp_variable *spv; + const Sp_rcontext_handler *rh; /* Best effort lookup for system variable. */ - if (!spcont || !(spv = spcont->find_variable(name, false))) + if (!(spv= find_variable(name, &rh))) { struct sys_var_with_base tmp= {NULL, *name}; @@ -5323,7 +5379,8 @@ bool LEX::sp_variable_declarations_set_default(THD *thd, int nvars, /* The last instruction is responsible for freeing LEX. */ sp_instr_set *is= new (this->thd->mem_root) sp_instr_set(sphead->instructions(), - spcont, spvar->offset, dflt_value_item, + spcont, &sp_rcontext_handler_local, + spvar->offset, dflt_value_item, this, last); if (is == NULL || sphead->add_instr(is)) return true; @@ -5619,7 +5676,8 @@ sp_variable *LEX::sp_add_for_loop_variable(THD *thd, const LEX_CSTRING *name, spvar->default_value= value; sp_instr_set *is= new (this->thd->mem_root) sp_instr_set(sphead->instructions(), - spcont, spvar->offset, value, + spcont, &sp_rcontext_handler_local, + spvar->offset, value, this, true); if (is == NULL || sphead->add_instr(is)) return NULL; @@ -5697,7 +5755,8 @@ bool LEX::sp_for_loop_condition(THD *thd, const Lex_for_loop_st &loop) { sp_variable *src= i == 0 ? loop.m_index : loop.m_upper_bound; args[i]= new (thd->mem_root) - Item_splocal(thd, &src->name, src->offset, src->type_handler()); + Item_splocal(thd, &sp_rcontext_handler_local, + &src->name, src->offset, src->type_handler()); if (args[i] == NULL) return true; #ifdef DBUG_ASSERT_EXISTS @@ -5830,7 +5889,8 @@ bool LEX::sp_for_loop_cursor_declarations(THD *thd, bool LEX::sp_for_loop_increment(THD *thd, const Lex_for_loop_st &loop) { Item_splocal *splocal= new (thd->mem_root) - Item_splocal(thd, &loop.m_index->name, loop.m_index->offset, + Item_splocal(thd, &sp_rcontext_handler_local, + &loop.m_index->name, loop.m_index->offset, loop.m_index->type_handler()); if (splocal == NULL) return true; @@ -5842,7 +5902,8 @@ bool LEX::sp_for_loop_increment(THD *thd, const Lex_for_loop_st &loop) return true; Item *expr= new (thd->mem_root) Item_func_plus(thd, splocal, inc); if (!expr || - sphead->set_local_variable(thd, spcont, loop.m_index, expr, this, true)) + sphead->set_local_variable(thd, spcont, &sp_rcontext_handler_local, + loop.m_index, expr, this, true)) return true; return false; } @@ -6042,6 +6103,31 @@ sp_name *LEX::make_sp_name(THD *thd, const LEX_CSTRING *name) } +/** + When a package routine name is stored in memory in Database_qualified_name, + the dot character is used to delimit package name from the routine name, + e.g.: + m_db= 'test'; -- database 'test' + m_name= 'p1.p1'; -- package 'p1', routine 'p1' + See database_qualified_name::make_package_routine_name() for details. + Disallow package routine names with dots, + to avoid ambiguity when interpreting m_name='p1.p1.p1', between: + a. package 'p1.p1' + routine 'p1' + b. package 'p1' + routine 'p1.p1' + m_name='p1.p1.p1' will always mean (a). +*/ +sp_name *LEX::make_sp_name_package_routine(THD *thd, const LEX_CSTRING *name) +{ + sp_name *res= make_sp_name(thd, name); + if (res && strchr(res->m_name.str, '.')) + { + my_error(ER_SP_WRONG_NAME, MYF(0), res->m_name.str); + res= NULL; + } + return res; +} + + sp_name *LEX::make_sp_name(THD *thd, const LEX_CSTRING *name1, const LEX_CSTRING *name2) { @@ -6064,15 +6150,25 @@ sp_name *LEX::make_sp_name(THD *thd, const LEX_CSTRING *name1, sp_head *LEX::make_sp_head(THD *thd, const sp_name *name, const Sp_handler *sph) { + sp_package *package= get_sp_package(); sp_head *sp; /* Order is important here: new - reset - init */ - if ((sp= new sp_head(sph))) + if ((sp= new sp_head(package, sph))) { sp->reset_thd_mem_root(thd); sp->init(this); if (name) - sp->init_sp_name(name); + { + if (package) + sp->make_package_routine_name(sp->get_main_mem_root(), + package->m_db, + package->m_name, + name->m_name); + else + sp->init_sp_name(name); + sp->make_qname(sp->get_main_mem_root(), &sp->m_qname); + } sphead= sp; } sp_chistics.init(); @@ -6080,6 +6176,31 @@ sp_head *LEX::make_sp_head(THD *thd, const sp_name *name, } +sp_head *LEX::make_sp_head_no_recursive(THD *thd, const sp_name *name, + const Sp_handler *sph) +{ + sp_package *package= thd->lex->get_sp_package(); + /* + Sp_handler::sp_clone_and_link_routine() generates a standalone-alike + statement to clone package routines for recursion, e.g.: + CREATE PROCEDURE p1 AS BEGIN NULL; END; + Translate a standalone routine handler to the corresponding + package routine handler if we're cloning a package routine, e.g.: + sp_handler_procedure -> sp_handler_package_procedure + sp_handler_function -> sp_handler_package_function + */ + if (package && package->m_is_cloning_routine) + sph= sph->package_routine_handler(); + if (!sphead || + (package && + (sph == &sp_handler_package_procedure || + sph == &sp_handler_package_function))) + return make_sp_head(thd, name, sph); + my_error(ER_SP_NO_RECURSIVE_CREATE, MYF(0), sph->type_str()); + return NULL; +} + + bool LEX::sp_body_finalize_procedure(THD *thd) { if (sphead->check_unresolved_goto()) @@ -6170,6 +6291,14 @@ LEX::sp_block_with_exceptions_finalize_exceptions(THD *thd, } +bool LEX::sp_block_with_exceptions_add_empty(THD *thd) +{ + uint ip= sphead->instructions(); + return sp_block_with_exceptions_finalize_executable_section(thd, ip) || + sp_block_with_exceptions_finalize_exceptions(thd, ip, 0); +} + + bool LEX::sp_change_context(THD *thd, const sp_pcontext *ctx, bool exclusive) { uint n; @@ -6588,6 +6717,7 @@ Item *LEX::create_item_ident_nospvar(THD *thd, Item_splocal *LEX::create_item_spvar_row_field(THD *thd, + const Sp_rcontext_handler *rh, const LEX_CSTRING *a, const LEX_CSTRING *b, sp_variable *spv, @@ -6606,7 +6736,7 @@ Item_splocal *LEX::create_item_spvar_row_field(THD *thd, spv->field_def.is_cursor_rowtype_ref()) { if (!(item= new (thd->mem_root) - Item_splocal_row_field_by_name(thd, a, b, spv->offset, + Item_splocal_row_field_by_name(thd, rh, a, b, spv->offset, &type_handler_null, pos.pos(), pos.length()))) return NULL; @@ -6619,7 +6749,7 @@ Item_splocal *LEX::create_item_spvar_row_field(THD *thd, return NULL; if (!(item= new (thd->mem_root) - Item_splocal_row_field(thd, a, b, + Item_splocal_row_field(thd, rh, a, b, spv->offset, row_field_offset, def->type_handler(), pos.pos(), pos.length()))) @@ -6633,12 +6763,27 @@ Item_splocal *LEX::create_item_spvar_row_field(THD *thd, } +my_var *LEX::create_outvar(THD *thd, const LEX_CSTRING *name) +{ + const Sp_rcontext_handler *rh; + sp_variable *spv; + if ((spv= find_variable(name, &rh))) + return result ? new (thd->mem_root) + my_var_sp(rh, name, spv->offset, + spv->type_handler(), sphead) : + NULL /* EXPLAIN */; + my_error(ER_SP_UNDECLARED_VAR, MYF(0), name->str); + return NULL; +} + + my_var *LEX::create_outvar(THD *thd, const LEX_CSTRING *a, const LEX_CSTRING *b) { + const Sp_rcontext_handler *rh; sp_variable *t; - if (!spcont || !(t= spcont->find_variable(a, false))) + if (!(t= find_variable(a, &rh))) { my_error(ER_SP_UNDECLARED_VAR, MYF(0), a->str); return NULL; @@ -6647,9 +6792,9 @@ my_var *LEX::create_outvar(THD *thd, if (!t->find_row_field(a, b, &row_field_offset)) return NULL; return result ? - new (thd->mem_root) my_var_sp_row_field(a, b, t->offset, + new (thd->mem_root) my_var_sp_row_field(rh, a, b, t->offset, row_field_offset, sphead) : - NULL; + NULL /* EXPLAIN */; } @@ -6719,12 +6864,13 @@ Item *LEX::create_item_ident(THD *thd, const LEX_CSTRING *b, const char *start, const char *end) { + const Sp_rcontext_handler *rh; sp_variable *spv; - if (spcont && (spv= spcont->find_variable(a, false)) && + if ((spv= find_variable(a, &rh)) && (spv->field_def.is_row() || spv->field_def.is_table_rowtype_ref() || spv->field_def.is_cursor_rowtype_ref())) - return create_item_spvar_row_field(thd, a, b, spv, start, end); + return create_item_spvar_row_field(thd, rh, a, b, spv, start, end); if ((thd->variables.sql_mode & MODE_ORACLE) && b->length == 7) { @@ -6780,8 +6926,9 @@ Item *LEX::create_item_limit(THD *thd, const LEX_CSTRING *a, const char *start, const char *end) { + const Sp_rcontext_handler *rh; sp_variable *spv; - if (!spcont || !(spv= spcont->find_variable(a, false))) + if (!(spv= find_variable(a, &rh))) { my_error(ER_SP_UNDECLARED_VAR, MYF(0), a->str); return NULL; @@ -6789,7 +6936,7 @@ Item *LEX::create_item_limit(THD *thd, Query_fragment pos(thd, sphead, start, end); Item_splocal *item; - if (!(item= new (thd->mem_root) Item_splocal(thd, a, + if (!(item= new (thd->mem_root) Item_splocal(thd, rh, a, spv->offset, spv->type_handler(), pos.pos(), pos.length()))) return NULL; @@ -6813,8 +6960,9 @@ Item *LEX::create_item_limit(THD *thd, const LEX_CSTRING *b, const char *start, const char *end) { + const Sp_rcontext_handler *rh; sp_variable *spv; - if (!spcont || !(spv= spcont->find_variable(a, false))) + if (!(spv= find_variable(a, &rh))) { my_error(ER_SP_UNDECLARED_VAR, MYF(0), a->str); return NULL; @@ -6822,7 +6970,7 @@ Item *LEX::create_item_limit(THD *thd, // Qualified %TYPE variables are not possible DBUG_ASSERT(!spv->field_def.column_type_ref()); Item_splocal *item; - if (!(item= create_item_spvar_row_field(thd, a, b, spv, start, end))) + if (!(item= create_item_spvar_row_field(thd, rh, a, b, spv, start, end))) return NULL; if (item->type() != Item::INT_ITEM) { @@ -6868,10 +7016,13 @@ bool LEX::set_variable(struct sys_var_with_base *variable, Item *item) was previously checked by init_internal_variable(). */ DBUG_ASSERT(spcont); - sp_variable *spv= spcont->find_variable(&variable->base_name, false); + const Sp_rcontext_handler *rh; + sp_pcontext *ctx; + sp_variable *spv= find_variable(&variable->base_name, &ctx, &rh); DBUG_ASSERT(spv); - /* It is a local variable. */ - return sphead->set_local_variable(thd, spcont, spv, item, this, true); + DBUG_ASSERT(ctx); + DBUG_ASSERT(rh); + return sphead->set_local_variable(thd, ctx, rh, spv, item, this, true); } @@ -6891,10 +7042,11 @@ Item *LEX::create_item_ident_sp(THD *thd, LEX_CSTRING *name, const char *start, const char *end) { + const Sp_rcontext_handler *rh; sp_variable *spv; DBUG_ASSERT(spcont); DBUG_ASSERT(sphead); - if ((spv= spcont->find_variable(name, false))) + if ((spv= find_variable(name, &rh))) { /* We're compiling a stored procedure and found a variable */ if (!parsing_options.allows_variable) @@ -6905,11 +7057,11 @@ Item *LEX::create_item_ident_sp(THD *thd, LEX_CSTRING *name, Query_fragment pos(thd, sphead, start, end); Item_splocal *splocal= spv->field_def.is_column_type_ref() ? - new (thd->mem_root) Item_splocal_with_delayed_data_type(thd, name, + new (thd->mem_root) Item_splocal_with_delayed_data_type(thd, rh, name, spv->offset, pos.pos(), pos.length()) : - new (thd->mem_root) Item_splocal(thd, name, + new (thd->mem_root) Item_splocal(thd, rh, name, spv->offset, spv->type_handler(), pos.pos(), pos.length()); if (splocal == NULL) @@ -6940,18 +7092,21 @@ bool LEX::set_variable(const LEX_CSTRING *name1, const LEX_CSTRING *name2, Item *item) { + const Sp_rcontext_handler *rh; + sp_pcontext *ctx; sp_variable *spv; - if (spcont && (spv= spcont->find_variable(name1, false))) + if (spcont && (spv= find_variable(name1, &ctx, &rh))) { if (spv->field_def.is_table_rowtype_ref() || spv->field_def.is_cursor_rowtype_ref()) - return sphead->set_local_variable_row_field_by_name(thd, spcont, + return sphead->set_local_variable_row_field_by_name(thd, ctx, + rh, spv, name2, item, this); // A field of a ROW variable uint row_field_offset; return !spv->find_row_field(name1, name2, &row_field_offset) || - sphead->set_local_variable_row_field(thd, spcont, + sphead->set_local_variable_row_field(thd, ctx, rh, spv, row_field_offset, item, this); } @@ -7416,12 +7571,18 @@ bool LEX::add_create_view(THD *thd, DDL_options_st ddl, bool LEX::call_statement_start(THD *thd, sp_name *name) { + Database_qualified_name pkgname(&null_clex_str, &null_clex_str); + const Sp_handler *sph= &sp_handler_procedure; sql_command= SQLCOM_CALL; - spname= name; value_list.empty(); - if (!(m_sql_cmd= new (thd->mem_root) Sql_cmd_call(name))) + if (sph->sp_resolve_package_routine(thd, thd->lex->sphead, + name, &sph, &pkgname)) + return true; + if (!(m_sql_cmd= new (thd->mem_root) Sql_cmd_call(name, sph))) return true; - sp_handler_procedure.add_used_routine(this, thd, name); + sph->add_used_routine(this, thd, name); + if (pkgname.m_name.length) + sp_handler_package_body.add_used_routine(this, thd, &pkgname); return false; } @@ -7441,6 +7602,98 @@ bool LEX::call_statement_start(THD *thd, const LEX_CSTRING *name1, } +sp_package *LEX::get_sp_package() const +{ + return sphead ? sphead->get_package() : NULL; +} + + +sp_package *LEX::create_package_start(THD *thd, + enum_sql_command command, + const Sp_handler *sph, + const sp_name *name_arg, + DDL_options_st options) +{ + sp_package *pkg; + if (sphead) + { + my_error(ER_SP_NO_RECURSIVE_CREATE, MYF(0), sph->type_str()); + return NULL; + } + if (set_command_with_check(command, options)) + return NULL; + if (sph->type() == TYPE_ENUM_PACKAGE_BODY) + { + /* + If we start parsing a "CREATE PACKAGE BODY", we need to load + the corresponding "CREATE PACKAGE", for the following reasons: + 1. "CREATE PACKAGE BODY" is allowed only if "CREATE PACKAGE" + was done earlier for the same package name. + So if "CREATE PACKAGE" does not exist, we throw an error here. + 2. When parsing "CREATE PACKAGE BODY", we need to know all package + public and private routine names, to translate procedure and + function calls correctly. + For example, this statement inside a package routine: + CALL p; + can be translated to: + CALL db.pkg.p; -- p is a known (public or private) package routine + CALL db.p; -- p is not a known package routine + */ + sp_head *spec; + int ret= sp_handler_package_spec. + sp_cache_routine_reentrant(thd, name_arg, &spec); + if (!spec) + { + if (!ret) + my_error(ER_SP_DOES_NOT_EXIST, MYF(0), + "PACKAGE", ErrConvDQName(name_arg).ptr()); + return 0; + } + } + if (!(pkg= new sp_package(this, name_arg, sph))) + return NULL; + pkg->reset_thd_mem_root(thd); + pkg->init(this); + pkg->make_qname(pkg->get_main_mem_root(), &pkg->m_qname); + sphead= pkg; + return pkg; +} + + +bool LEX::create_package_finalize(THD *thd, + const sp_name *name, + const sp_name *name2, + const char *body_start, + const char *body_end) +{ + if (name2 && + (name2->m_explicit_name != name->m_explicit_name || + strcmp(name2->m_db.str, name->m_db.str) || + !Sp_handler::eq_routine_name(name2->m_name, name->m_name))) + { + bool exp= name2->m_explicit_name || name->m_explicit_name; + my_error(ER_END_IDENTIFIER_DOES_NOT_MATCH, MYF(0), + exp ? ErrConvDQName(name2).ptr() : name2->m_name.str, + exp ? ErrConvDQName(name).ptr() : name->m_name.str); + return true; + } + sphead->m_body.length= body_end - body_start; + if (!(sphead->m_body.str= thd->strmake(body_start, sphead->m_body.length))) + return true; + + size_t not_used; + Lex_input_stream *lip= & thd->m_parser_state->m_lip; + sphead->m_defstr.length= lip->get_cpp_ptr() - lip->get_cpp_buf(); + sphead->m_defstr.str= thd->strmake(lip->get_cpp_buf(), sphead->m_defstr.length); + trim_whitespace(thd->charset(), &sphead->m_defstr, ¬_used); + + sphead->restore_thd_mem_root(thd); + sp_package *pkg= sphead->get_package(); + DBUG_ASSERT(pkg); + return pkg->validate_after_parser(thd); +} + + bool LEX::add_grant_command(THD *thd, enum_sql_command sql_command_arg, stored_procedure_type type_arg) { diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 45fbe38e974..5710c990855 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -2343,6 +2343,19 @@ public: return m_cpp_ptr; } + /** + Get the current stream pointer, in the pre-processed buffer, + with traling spaces removed. + */ + const char *get_cpp_ptr_rtrim() + { + const char *p; + for (p= m_cpp_ptr; + p > m_cpp_buf && my_isspace(system_charset_info, p[-1]); + p--) + { } + return p; + } /** Get the utf8-body string. */ const char *get_body_utf8_str() { @@ -3212,15 +3225,10 @@ public: sp_name *make_sp_name(THD *thd, const LEX_CSTRING *name); sp_name *make_sp_name(THD *thd, const LEX_CSTRING *name1, const LEX_CSTRING *name2); + sp_name *make_sp_name_package_routine(THD *thd, const LEX_CSTRING *name); sp_head *make_sp_head(THD *thd, const sp_name *name, const Sp_handler *sph); sp_head *make_sp_head_no_recursive(THD *thd, const sp_name *name, - const Sp_handler *sph) - { - if (!sphead) - return make_sp_head(thd, name, sph); - my_error(ER_SP_NO_RECURSIVE_CREATE, MYF(0), sph->type_str()); - return NULL; - } + const Sp_handler *sph); sp_head *make_sp_head_no_recursive(THD *thd, DDL_options_st options, sp_name *name, const Sp_handler *sph) @@ -3231,10 +3239,29 @@ public: } bool sp_body_finalize_function(THD *); bool sp_body_finalize_procedure(THD *); + sp_package *create_package_start(THD *thd, + enum_sql_command command, + const Sp_handler *sph, + const sp_name *name, + DDL_options_st options); + bool create_package_finalize(THD *thd, + const sp_name *name, + const sp_name *name2, + const char *body_start, + const char *body_end); bool call_statement_start(THD *thd, sp_name *name); bool call_statement_start(THD *thd, const LEX_CSTRING *name); bool call_statement_start(THD *thd, const LEX_CSTRING *name1, const LEX_CSTRING *name2); + sp_variable *find_variable(const LEX_CSTRING *name, + sp_pcontext **ctx, + const Sp_rcontext_handler **rh) const; + sp_variable *find_variable(const LEX_CSTRING *name, + const Sp_rcontext_handler **rh) const + { + sp_pcontext *not_used_ctx; + return find_variable(name, ¬_used_ctx, rh); + } bool init_internal_variable(struct sys_var_with_base *variable, const LEX_CSTRING *name); bool init_internal_variable(struct sys_var_with_base *variable, @@ -3318,6 +3345,7 @@ public: /* Create an Item corresponding to a ROW field valiable: var.field @param THD - THD, for mem_root + @param rh [OUT] - the rcontext handler (local vs package variables) @param var - the ROW variable name @param field - the ROW variable field name @param spvar - the variable that was previously found by name @@ -3326,6 +3354,7 @@ public: @param end - end in the query (for binary log) */ Item_splocal *create_item_spvar_row_field(THD *thd, + const Sp_rcontext_handler *rh, const LEX_CSTRING *var, const LEX_CSTRING *field, sp_variable *spvar, @@ -3419,6 +3448,8 @@ public: Item *make_item_func_replace(THD *thd, Item *org, Item *find, Item *replace); Item *make_item_func_substr(THD *thd, Item *a, Item *b, Item *c); Item *make_item_func_substr(THD *thd, Item *a, Item *b); + my_var *create_outvar(THD *thd, const LEX_CSTRING *name); + /* Create a my_var instance for a ROW field variable that was used as an OUT SP parameter: CALL p1(var.field); @@ -3479,6 +3510,7 @@ public: bool sp_block_with_exceptions_finalize_exceptions(THD *thd, uint executable_section_ip, uint exception_count); + bool sp_block_with_exceptions_add_empty(THD *thd); bool sp_exit_statement(THD *thd, Item *when); bool sp_exit_statement(THD *thd, const LEX_CSTRING *label_name, Item *item); bool sp_leave_statement(THD *thd, const LEX_CSTRING *label_name); @@ -3728,6 +3760,7 @@ public: { return create_info.vers_info; } + sp_package *get_sp_package() const; }; diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 67181c6eb5e..f5fe955375d 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -558,6 +558,10 @@ void init_update_queries(void) CF_INSERTS_DATA; sql_command_flags[SQLCOM_CREATE_DB]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS | CF_DB_CHANGE; sql_command_flags[SQLCOM_DROP_DB]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS | CF_DB_CHANGE; + sql_command_flags[SQLCOM_CREATE_PACKAGE]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_DROP_PACKAGE]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_CREATE_PACKAGE_BODY]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_DROP_PACKAGE_BODY]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_ALTER_DB_UPGRADE]= CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_ALTER_DB]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_RENAME_TABLE]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; @@ -631,6 +635,8 @@ void init_update_queries(void) CF_OPTIMIZER_TRACE; // (1) sql_command_flags[SQLCOM_SHOW_STATUS_PROC]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE; + sql_command_flags[SQLCOM_SHOW_STATUS_PACKAGE]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE; + sql_command_flags[SQLCOM_SHOW_STATUS_PACKAGE_BODY]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE; sql_command_flags[SQLCOM_SHOW_STATUS]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE; sql_command_flags[SQLCOM_SHOW_DATABASES]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE; sql_command_flags[SQLCOM_SHOW_TRIGGERS]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE; @@ -665,10 +671,13 @@ void init_update_queries(void) sql_command_flags[SQLCOM_SHOW_SLAVE_STAT]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_CREATE_PROC]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_CREATE_FUNC]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_SHOW_CREATE_PACKAGE]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_SHOW_CREATE_PACKAGE_BODY]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_CREATE_TRIGGER]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_STATUS_FUNC]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE; sql_command_flags[SQLCOM_SHOW_PROC_CODE]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_FUNC_CODE]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_SHOW_PACKAGE_BODY_CODE]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_CREATE_EVENT]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_PROFILES]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_PROFILE]= CF_STATUS_COMMAND; @@ -829,6 +838,10 @@ void init_update_queries(void) sql_command_flags[SQLCOM_DROP_INDEX]|= CF_DISALLOW_IN_RO_TRANS; sql_command_flags[SQLCOM_CREATE_DB]|= CF_DISALLOW_IN_RO_TRANS; sql_command_flags[SQLCOM_DROP_DB]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_CREATE_PACKAGE]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_DROP_PACKAGE]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_CREATE_PACKAGE_BODY]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_DROP_PACKAGE_BODY]|= CF_DISALLOW_IN_RO_TRANS; sql_command_flags[SQLCOM_ALTER_DB_UPGRADE]|= CF_DISALLOW_IN_RO_TRANS; sql_command_flags[SQLCOM_ALTER_DB]|= CF_DISALLOW_IN_RO_TRANS; sql_command_flags[SQLCOM_CREATE_VIEW]|= CF_DISALLOW_IN_RO_TRANS; @@ -3124,7 +3137,7 @@ bool Sql_cmd_call::execute(THD *thd) By this moment all needed SPs should be in cache so no need to look into DB. */ - if (!(sp= sp_handler_procedure.sp_find_routine(thd, m_name, true))) + if (!(sp= m_handler->sp_find_routine(thd, m_name, true))) { /* If the routine is not found, let's still check EXECUTE_ACL to decide @@ -3705,6 +3718,8 @@ mysql_execute_command(THD *thd) /* fall through */ case SQLCOM_SHOW_STATUS_PROC: case SQLCOM_SHOW_STATUS_FUNC: + case SQLCOM_SHOW_STATUS_PACKAGE: + case SQLCOM_SHOW_STATUS_PACKAGE_BODY: case SQLCOM_SHOW_DATABASES: case SQLCOM_SHOW_TABLES: case SQLCOM_SHOW_TRIGGERS: @@ -5859,6 +5874,8 @@ end_with_restore_list: break; case SQLCOM_CREATE_PROCEDURE: case SQLCOM_CREATE_SPFUNCTION: + case SQLCOM_CREATE_PACKAGE: + case SQLCOM_CREATE_PACKAGE_BODY: { if (mysql_create_routine(thd, lex)) goto error; @@ -5908,6 +5925,8 @@ end_with_restore_list: } case SQLCOM_DROP_PROCEDURE: case SQLCOM_DROP_FUNCTION: + case SQLCOM_DROP_PACKAGE: + case SQLCOM_DROP_PACKAGE_BODY: { #ifdef HAVE_DLOPEN if (lex->sql_command == SQLCOM_DROP_FUNCTION && @@ -6022,6 +6041,8 @@ end_with_restore_list: } case SQLCOM_SHOW_CREATE_PROC: case SQLCOM_SHOW_CREATE_FUNC: + case SQLCOM_SHOW_CREATE_PACKAGE: + case SQLCOM_SHOW_CREATE_PACKAGE_BODY: { WSREP_SYNC_WAIT(thd, WSREP_SYNC_WAIT_BEFORE_SHOW); const Sp_handler *sph= Sp_handler::handler(lex->sql_command); @@ -6031,11 +6052,19 @@ end_with_restore_list: } case SQLCOM_SHOW_PROC_CODE: case SQLCOM_SHOW_FUNC_CODE: + case SQLCOM_SHOW_PACKAGE_BODY_CODE: { #ifndef DBUG_OFF + Database_qualified_name pkgname(&null_clex_str, &null_clex_str); sp_head *sp; const Sp_handler *sph= Sp_handler::handler(lex->sql_command); WSREP_SYNC_WAIT(thd, WSREP_SYNC_WAIT_BEFORE_SHOW); +#ifdef WITH_WSREP + if (WSREP_CLIENT(thd) && wsrep_sync_wait(thd)) goto error; +#endif /* WITH_WSREP */ + if (sph->sp_resolve_package_routine(thd, thd->lex->sphead, + lex->spname, &sph, &pkgname)) + return true; if (sph->sp_cache_routine(thd, lex->spname, false, &sp)) goto error; if (!sp || sp->show_routine_code(thd)) @@ -7997,6 +8026,8 @@ void mysql_parse(THD *thd, char *rawbuf, uint length, THD_STAGE_INFO(thd, stage_freeing_items); sp_cache_enforce_limit(thd->sp_proc_cache, stored_program_cache_size); sp_cache_enforce_limit(thd->sp_func_cache, stored_program_cache_size); + sp_cache_enforce_limit(thd->sp_package_spec_cache, stored_program_cache_size); + sp_cache_enforce_limit(thd->sp_package_body_cache, stored_program_cache_size); thd->end_statement(); thd->cleanup_after_query(); DBUG_ASSERT(thd->Item_change_list::is_empty()); diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index dbc03234c44..22b8640d9ee 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -2313,6 +2313,8 @@ static bool check_prepared_statement(Prepared_statement *stmt) case SQLCOM_SHOW_TABLE_STATUS: case SQLCOM_SHOW_STATUS_PROC: case SQLCOM_SHOW_STATUS_FUNC: + case SQLCOM_SHOW_STATUS_PACKAGE: + case SQLCOM_SHOW_STATUS_PACKAGE_BODY: case SQLCOM_SELECT: res= mysql_test_select(stmt, tables); if (res == 2) @@ -2385,6 +2387,21 @@ static bool check_prepared_statement(Prepared_statement *stmt) DBUG_RETURN(FALSE); } break; + case SQLCOM_SHOW_CREATE_PACKAGE: + if ((res= mysql_test_show_create_routine(stmt, &sp_handler_package_spec)) == 2) + { + /* Statement and field info has already been sent */ + DBUG_RETURN(FALSE); + } + break; + case SQLCOM_SHOW_CREATE_PACKAGE_BODY: + if ((res= mysql_test_show_create_routine(stmt, + &sp_handler_package_body)) == 2) + { + /* Statement and field info has already been sent */ + DBUG_RETURN(FALSE); + } + break; case SQLCOM_CREATE_VIEW: if (lex->create_view->mode == VIEW_ALTER) { @@ -2589,6 +2606,8 @@ void mysqld_stmt_prepare(THD *thd, const char *packet, uint packet_length) sp_cache_enforce_limit(thd->sp_proc_cache, stored_program_cache_size); sp_cache_enforce_limit(thd->sp_func_cache, stored_program_cache_size); + sp_cache_enforce_limit(thd->sp_package_spec_cache, stored_program_cache_size); + sp_cache_enforce_limit(thd->sp_package_body_cache, stored_program_cache_size); /* check_prepared_statemnt sends the metadata packet in case of success */ end: @@ -3155,6 +3174,8 @@ static void mysql_stmt_execute_common(THD *thd, sp_cache_enforce_limit(thd->sp_proc_cache, stored_program_cache_size); sp_cache_enforce_limit(thd->sp_func_cache, stored_program_cache_size); + sp_cache_enforce_limit(thd->sp_package_spec_cache, stored_program_cache_size); + sp_cache_enforce_limit(thd->sp_package_body_cache, stored_program_cache_size); /* Close connection socket; for use with client testing (Bug#43560). */ DBUG_EXECUTE_IF("close_conn_after_stmt_execute", vio_close(thd->net.vio);); diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 2b1b765a5be..fd38d4a7771 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -2546,6 +2546,17 @@ static const LEX_CSTRING *view_algorithm(TABLE_LIST *table) } } + +static bool append_at_host(THD *thd, String *buffer, const LEX_CSTRING *host) +{ + if (!host->str || !host->str[0]) + return false; + return + buffer->append('@') || + append_identifier(thd, buffer, host); +} + + /* Append DEFINER clause to the given buffer. @@ -2557,17 +2568,14 @@ static const LEX_CSTRING *view_algorithm(TABLE_LIST *table) definer_host [in] host name part of definer */ -void append_definer(THD *thd, String *buffer, const LEX_CSTRING *definer_user, +bool append_definer(THD *thd, String *buffer, const LEX_CSTRING *definer_user, const LEX_CSTRING *definer_host) { - buffer->append(STRING_WITH_LEN("DEFINER=")); - append_identifier(thd, buffer, definer_user); - if (definer_host->str && definer_host->str[0]) - { - buffer->append('@'); - append_identifier(thd, buffer, definer_host); - } - buffer->append(' '); + return + buffer->append(STRING_WITH_LEN("DEFINER=")) || + append_identifier(thd, buffer, definer_user) || + append_at_host(thd, buffer, definer_host) || + buffer->append(' '); } @@ -9028,7 +9036,7 @@ ST_FIELD_INFO proc_fields_info[]= SKIP_OPEN_TABLE}, {"ROUTINE_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, "Name", SKIP_OPEN_TABLE}, - {"ROUTINE_TYPE", 9, MYSQL_TYPE_STRING, 0, 0, "Type", SKIP_OPEN_TABLE}, + {"ROUTINE_TYPE", 13, MYSQL_TYPE_STRING, 0, 0, "Type", SKIP_OPEN_TABLE}, {"DATA_TYPE", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}, {"CHARACTER_MAXIMUM_LENGTH", 21 , MYSQL_TYPE_LONG, 0, 1, 0, SKIP_OPEN_TABLE}, {"CHARACTER_OCTET_LENGTH", 21 , MYSQL_TYPE_LONG, 0, 1, 0, SKIP_OPEN_TABLE}, diff --git a/sql/sql_show.h b/sql/sql_show.h index f6d5d4d2c3c..1b959121f89 100644 --- a/sql/sql_show.h +++ b/sql/sql_show.h @@ -106,7 +106,7 @@ bool mysqld_show_contributors(THD *thd); bool mysqld_show_privileges(THD *thd); char *make_backup_log_name(char *buff, const char *name, const char* log_ext); uint calc_sum_of_all_status(STATUS_VAR *to); -void append_definer(THD *thd, String *buffer, const LEX_CSTRING *definer_user, +bool append_definer(THD *thd, String *buffer, const LEX_CSTRING *definer_user, const LEX_CSTRING *definer_host); int add_status_vars(SHOW_VAR *list); void remove_status_vars(SHOW_VAR *list); diff --git a/sql/sql_string.h b/sql/sql_string.h index 5cabcc02aae..37531429f8d 100644 --- a/sql/sql_string.h +++ b/sql/sql_string.h @@ -487,6 +487,10 @@ public: ls->length == strlen(ls->str))); return append(ls->str, (uint32) ls->length); } + bool append(const LEX_CSTRING &ls) + { + return append(&ls); + } bool append(const char *s, size_t size); bool append(const char *s, size_t arg_length, CHARSET_INFO *cs); bool append_ulonglong(ulonglong val); diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc index 29bf7aeb205..4c1e2a51fbf 100644 --- a/sql/sql_trigger.cc +++ b/sql/sql_trigger.cc @@ -2253,7 +2253,8 @@ add_tables_and_routines_for_triggers(THD *thd, MDL_key key(MDL_key::TRIGGER, trigger->m_db.str, trigger->m_name.str); if (sp_add_used_routine(prelocking_ctx, thd->stmt_arena, - &key, table_list->belong_to_view)) + &key, &sp_handler_trigger, + table_list->belong_to_view)) { trigger->add_used_tables_to_table_list(thd, &prelocking_ctx->query_tables_last, diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 2ebcb057c32..35ec2d29d21 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -378,12 +378,13 @@ Item_splocal* LEX::create_item_for_sp_var(LEX_CSTRING *name, sp_variable *spvar, const char *start_in_q, const char *end_in_q) { + const Sp_rcontext_handler *rh; Item_splocal *item; uint pos_in_q, len_in_q; /* If necessary, look for the variable. */ if (spcont && !spvar) - spvar= spcont->find_variable(name, false); + spvar= find_variable(name, &rh); if (!spvar) { @@ -398,7 +399,7 @@ LEX::create_item_for_sp_var(LEX_CSTRING *name, sp_variable *spvar, len_in_q= (uint)(end_in_q - start_in_q); item= new (thd->mem_root) - Item_splocal(thd, name, spvar->offset, spvar->type_handler(), + Item_splocal(thd, rh, name, spvar->offset, spvar->type_handler(), pos_in_q, len_in_q); #ifdef DBUG_ASSERT_EXISTS @@ -951,6 +952,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize); %token BIT_XOR /* MYSQL-FUNC */ %token BLOB_SYM /* SQL-2003-R */ %token BLOCK_SYM +%token BODY_SYM /* Oracle-R */ %token BOOLEAN_SYM /* SQL-2003-R */ %token BOOL_SYM %token BOTH /* SQL-2003-R */ @@ -1340,6 +1342,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize); %token OUT_SYM /* SQL-2003-R */ %token OVER_SYM %token OWNER_SYM +%token PACKAGE_SYM /* Oracle-R */ %token PACK_KEYS_SYM %token PAGE_SYM %token PAGE_CHECKSUM_SYM @@ -12885,14 +12888,8 @@ select_outvar: } | ident_or_text { - sp_variable *t; - - if (!Lex->spcont || !(t= Lex->spcont->find_variable(&$1, false))) - my_yyabort_error((ER_SP_UNDECLARED_VAR, MYF(0), $1.str)); - $$ = Lex->result ? (new (thd->mem_root) - my_var_sp(&$1, t->offset, t->type_handler(), - Lex->sphead)) : - NULL; + if (!($$= Lex->create_outvar(thd, &$1)) && Lex->result) + MYSQL_YYABORT; } | ident '.' ident { @@ -13947,6 +13944,18 @@ show_param: lex->sql_command = SQLCOM_SHOW_CREATE_FUNC; lex->spname= $3; } + | CREATE PACKAGE_SYM sp_name + { + LEX *lex= Lex; + lex->sql_command = SQLCOM_SHOW_CREATE_PACKAGE; + lex->spname= $3; + } + | CREATE PACKAGE_SYM BODY_SYM sp_name + { + LEX *lex= Lex; + lex->sql_command = SQLCOM_SHOW_CREATE_PACKAGE_BODY; + lex->spname= $4; + } | CREATE TRIGGER_SYM sp_name { LEX *lex= Lex; @@ -13979,6 +13988,20 @@ show_param: if (prepare_schema_table(thd, lex, 0, SCH_PROCEDURES)) MYSQL_YYABORT; } + | PACKAGE_SYM STATUS_SYM wild_and_where + { + LEX *lex= Lex; + lex->sql_command= SQLCOM_SHOW_STATUS_PACKAGE; + if (prepare_schema_table(thd, lex, 0, SCH_PROCEDURES)) + MYSQL_YYABORT; + } + | PACKAGE_SYM BODY_SYM STATUS_SYM wild_and_where + { + LEX *lex= Lex; + lex->sql_command= SQLCOM_SHOW_STATUS_PACKAGE_BODY; + if (prepare_schema_table(thd, lex, 0, SCH_PROCEDURES)) + MYSQL_YYABORT; + } | PROCEDURE_SYM CODE_SYM sp_name { Lex->sql_command= SQLCOM_SHOW_PROC_CODE; @@ -13989,6 +14012,11 @@ show_param: Lex->sql_command= SQLCOM_SHOW_FUNC_CODE; Lex->spname= $3; } + | PACKAGE_SYM BODY_SYM CODE_SYM sp_name + { + Lex->sql_command= SQLCOM_SHOW_PACKAGE_BODY_CODE; + Lex->spname= $4; + } | CREATE EVENT_SYM sp_name { Lex->spname= $3; @@ -15485,6 +15513,7 @@ keyword_sp_not_data_type: | AVG_ROW_LENGTH {} | AVG_SYM {} | BLOCK_SYM {} + | BODY_SYM {} | BTREE_SYM {} | CASCADED {} | CATALOG_NAME_SYM {} @@ -15660,6 +15689,7 @@ keyword_sp_not_data_type: | ONE_SYM {} | ONLINE_SYM {} | ONLY_SYM {} + | PACKAGE_SYM {} | PACK_KEYS_SYM {} | PAGE_SYM {} | PARTIAL {} diff --git a/sql/sql_yacc_ora.yy b/sql/sql_yacc_ora.yy index 1a2b1630d38..c8f93c17dd4 100644 --- a/sql/sql_yacc_ora.yy +++ b/sql/sql_yacc_ora.yy @@ -237,6 +237,7 @@ void ORAerror(THD *thd, const char *s) st_trg_execution_order trg_execution_order; /* enums */ + enum enum_sp_suid_behaviour sp_suid; enum enum_view_suid view_suid; enum sub_select_type unit_type; enum Condition_information_item::Name cond_info_item_name; @@ -342,6 +343,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize); %token BIT_XOR /* MYSQL-FUNC */ %token BLOB_SYM /* SQL-2003-R */ %token BLOCK_SYM +%token BODY_SYM /* Oracle-R */ %token BOOLEAN_SYM /* SQL-2003-R */ %token BOOL_SYM %token BOTH /* SQL-2003-R */ @@ -731,6 +733,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize); %token OUT_SYM /* SQL-2003-R */ %token OVER_SYM %token OWNER_SYM +%token PACKAGE_SYM /* Oracle-R */ %token PACK_KEYS_SYM %token PAGE_SYM %token PAGE_CHECKSUM_SYM @@ -1044,6 +1047,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize); sp_opt_label BIN_NUM TEXT_STRING_filesystem ident_or_empty opt_constraint constraint opt_ident ident_directly_assignable + opt_package_routine_end_name sp_decl_ident sp_block_label opt_place opt_db @@ -1070,7 +1074,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize); optionally_qualified_column_ident %type <simple_string> - remember_name remember_end remember_tok_start + remember_name remember_end remember_end_opt remember_tok_start wild_and_where colon_with_pos @@ -1334,7 +1338,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize); opt_extended_describe shutdown opt_format_json prepare prepare_src execute deallocate - statement sp_suid + statement sp_c_chistics sp_a_chistics sp_chistic sp_c_chistic xa opt_field_or_var_spec fields_or_vars opt_load_data_set_spec view_list_opt view_list view_select @@ -1376,17 +1380,28 @@ END_OF_INPUT %type <num> view_algorithm view_check_option %type <view_suid> view_suid opt_view_suid +%type <sp_suid> sp_suid %type <num> sp_decl_idents sp_decl_idents_init_vars %type <num> sp_handler_type sp_hcond_list %type <spcondvalue> sp_cond sp_hcond sqlstate signal_value opt_signal_value %type <spblock> sp_decl_body_list opt_sp_decl_body_list +%type <spblock> sp_decl_vars %type <spblock> sp_decl_non_handler sp_decl_non_handler_list %type <spblock> sp_decl_handler sp_decl_handler_list opt_sp_decl_handler_list +%type <spblock> package_implementation_routine_definition +%type <spblock> package_implementation_item_declaration +%type <spblock> package_implementation_declare_section +%type <spblock> package_implementation_declare_section_list1 +%type <spblock> package_implementation_declare_section_list2 %type <spblock_handlers> sp_block_statements_and_exceptions +%type <spblock_handlers> package_implementation_executable_section %type <sp_instr_addr> sp_instr_addr %type <sp_cursor_name_and_offset> sp_cursor_name_and_offset %type <num> opt_exception_clause exception_handlers +%type <lex> remember_lex package_routine_lex + package_specification_function + package_specification_procedure %type <spname> sp_name opt_sp_name %type <spvar> sp_param_name sp_param_name_and_type %type <for_loop> sp_for_loop_index_and_bounds @@ -2115,6 +2130,248 @@ create: | create_or_replace { Lex->set_command(SQLCOM_CREATE_SERVER, $1); } server_def { } + | create_or_replace definer_opt PACKAGE_SYM + opt_if_not_exists sp_name opt_create_package_chistics_init + sp_tail_is + remember_name + { + sp_package *pkg; + if (!(pkg= Lex->create_package_start(thd, + SQLCOM_CREATE_PACKAGE, + &sp_handler_package_spec, + $5, $1 | $4))) + MYSQL_YYABORT; + pkg->set_chistics(Lex->sp_chistics); + } + opt_package_specification_element_list END + remember_end_opt opt_sp_name + { + if (Lex->create_package_finalize(thd, $5, $13, $8, $12)) + MYSQL_YYABORT; + } + | create_or_replace definer_opt PACKAGE_SYM BODY_SYM + opt_if_not_exists sp_name opt_create_package_chistics_init + sp_tail_is + remember_name + { + sp_package *pkg; + if (!(pkg= Lex->create_package_start(thd, + SQLCOM_CREATE_PACKAGE_BODY, + &sp_handler_package_body, + $6, $1 | $5))) + MYSQL_YYABORT; + pkg->set_chistics(Lex->sp_chistics); + Lex->sp_block_init(thd); + } + package_implementation_declare_section + { + if (Lex->sp_block_with_exceptions_finalize_declarations(thd)) + MYSQL_YYABORT; + } + package_implementation_executable_section + { + $11.hndlrs+= $13.hndlrs; + if (Lex->sp_block_finalize(thd, $11)) + MYSQL_YYABORT; + } + remember_end_opt opt_sp_name + { + if (Lex->create_package_finalize(thd, $6, $16, $9, $15)) + MYSQL_YYABORT; + } + ; + +package_implementation_executable_section: + END + { + if (Lex->sp_block_with_exceptions_add_empty(thd)) + MYSQL_YYABORT; + $$.init(0); + } + | BEGIN_SYM sp_block_statements_and_exceptions END { $$= $2; } + ; + +/* + Inside CREATE PACKATE BODY, package-wide items (e.g. variables) + must be declared before routine definitions. +*/ +package_implementation_declare_section: + package_implementation_declare_section_list1 + | package_implementation_declare_section_list2 + | package_implementation_declare_section_list1 + package_implementation_declare_section_list2 + { $$.join($1, $2); } + ; + +package_implementation_declare_section_list1: + package_implementation_item_declaration + | package_implementation_declare_section_list1 + package_implementation_item_declaration + { $$.join($1, $2); } + ; + +package_implementation_declare_section_list2: + package_implementation_routine_definition + | package_implementation_declare_section_list2 + package_implementation_routine_definition + { $$.join($1, $2); } + ; + +package_routine_lex: + { + if (!($$= new (thd->mem_root) sp_lex_local(thd, thd->lex))) + MYSQL_YYABORT; + thd->m_parser_state->m_yacc.reset_before_substatement(); + } + ; + + +package_specification_function: + remember_lex package_routine_lex ident + { + DBUG_ASSERT($1->sphead->get_package()); + $2->sql_command= SQLCOM_CREATE_FUNCTION; + sp_name *spname= $1->make_sp_name_package_routine(thd, &$3); + if (!spname) + MYSQL_YYABORT; + thd->lex= $2; + if (!$2->make_sp_head_no_recursive(thd, spname, + &sp_handler_package_function)) + MYSQL_YYABORT; + $1->sphead->get_package()->m_current_routine= $2; + (void) is_native_function_with_warn(thd, &$3); + } + opt_sp_parenthesized_fdparam_list + sf_return_type + sp_c_chistics + { + sp_head *sp= thd->lex->sphead; + sp->restore_thd_mem_root(thd); + thd->lex= $1; + $$= $2; + } + ; + +package_specification_procedure: + remember_lex package_routine_lex ident + { + DBUG_ASSERT($1->sphead->get_package()); + $2->sql_command= SQLCOM_CREATE_PROCEDURE; + sp_name *spname= $1->make_sp_name_package_routine(thd, &$3); + if (!spname) + MYSQL_YYABORT; + thd->lex= $2; + if (!$2->make_sp_head_no_recursive(thd, spname, + &sp_handler_package_procedure)) + MYSQL_YYABORT; + $1->sphead->get_package()->m_current_routine= $2; + } + opt_sp_parenthesized_pdparam_list + sp_c_chistics + { + sp_head *sp= thd->lex->sphead; + sp->restore_thd_mem_root(thd); + thd->lex= $1; + $$= $2; + + } + ; + + +package_implementation_routine_definition: + FUNCTION_SYM package_specification_function + package_implementation_function_body ';' + { + sp_package *pkg= Lex->get_sp_package(); + if (pkg->add_routine_implementation($2)) + MYSQL_YYABORT; + pkg->m_current_routine= NULL; + $$.init(); + } + | PROCEDURE_SYM package_specification_procedure + package_implementation_procedure_body ';' + { + sp_package *pkg= Lex->get_sp_package(); + if (pkg->add_routine_implementation($2)) + MYSQL_YYABORT; + pkg->m_current_routine= NULL; + $$.init(); + } + | package_specification_element { $$.init(); } + ; + + +package_implementation_function_body: + sp_tail_is remember_lex + { + sp_package *pkg= Lex->get_sp_package(); + sp_head *sp= pkg->m_current_routine->sphead; + thd->lex= pkg->m_current_routine; + sp->reset_thd_mem_root(thd); + sp->set_body_start(thd, YYLIP->get_cpp_tok_start()); + } + sp_body opt_package_routine_end_name + { + if (Lex->sphead->m_flags & sp_head::HAS_AGGREGATE_INSTR) + { + my_yyabort_error((ER_NOT_AGGREGATE_FUNCTION, MYF(0))); + } + Lex->sphead->set_chistics_agg_type(NOT_AGGREGATE); + if (thd->lex->sp_body_finalize_function(thd) || + thd->lex->sphead->check_package_routine_end_name($5)) + MYSQL_YYABORT; + thd->lex= $2; + } + ; + +package_implementation_procedure_body: + sp_tail_is remember_lex + { + sp_package *pkg= Lex->get_sp_package(); + sp_head *sp= pkg->m_current_routine->sphead; + thd->lex= pkg->m_current_routine; + sp->reset_thd_mem_root(thd); + sp->set_body_start(thd, YYLIP->get_cpp_tok_start()); + } + sp_body opt_package_routine_end_name + { + if (thd->lex->sp_body_finalize_procedure(thd) || + thd->lex->sphead->check_package_routine_end_name($5)) + MYSQL_YYABORT; + thd->lex= $2; + } + ; + + +package_implementation_item_declaration: + sp_decl_vars ';' + ; + +opt_package_specification_element_list: + /* Empty */ + | package_specification_element_list + ; + +package_specification_element_list: + package_specification_element + | package_specification_element_list package_specification_element + ; + +package_specification_element: + FUNCTION_SYM package_specification_function ';' + { + sp_package *pkg= Lex->get_sp_package(); + if (pkg->add_routine_declaration($2)) + MYSQL_YYABORT; + pkg->m_current_routine= NULL; + } + | PROCEDURE_SYM package_specification_procedure ';' + { + sp_package *pkg= Lex->get_sp_package(); + if (pkg->add_routine_declaration($2)) + MYSQL_YYABORT; + pkg->m_current_routine= NULL; + } ; create_function_tail: @@ -2515,7 +2772,29 @@ sp_chistic: | MODIFIES_SYM SQL_SYM DATA_SYM { Lex->sp_chistics.daccess= SP_MODIFIES_SQL_DATA; } | sp_suid - {} + { Lex->sp_chistics.suid= $1; } + ; + +create_package_chistic: + COMMENT_SYM TEXT_STRING_sys + { Lex->sp_chistics.comment= $2; } + | sp_suid + { Lex->sp_chistics.suid= $1; } + ; + +create_package_chistics: + create_package_chistic {} + | create_package_chistics create_package_chistic { } + ; + +opt_create_package_chistics: + /* Empty */ + | create_package_chistics { } + ; + +opt_create_package_chistics_init: + { Lex->sp_chistics.init(); } + opt_create_package_chistics ; /* Create characteristics */ @@ -2525,14 +2804,8 @@ sp_c_chistic: ; sp_suid: - SQL_SYM SECURITY_SYM DEFINER_SYM - { - Lex->sp_chistics.suid= SP_IS_SUID; - } - | SQL_SYM SECURITY_SYM INVOKER_SYM - { - Lex->sp_chistics.suid= SP_IS_NOT_SUID; - } + SQL_SYM SECURITY_SYM DEFINER_SYM { $$= SP_IS_SUID; } + | SQL_SYM SECURITY_SYM INVOKER_SYM { $$= SP_IS_NOT_SUID; } ; call: @@ -2844,7 +3117,7 @@ sp_decl_idents_init_vars: } ; -sp_decl_non_handler: +sp_decl_vars: sp_decl_idents_init_vars type_with_opt_collate sp_opt_default @@ -2878,6 +3151,10 @@ sp_decl_non_handler: MYSQL_YYABORT; $$.init_using_vars($1); } + ; + +sp_decl_non_handler: + sp_decl_vars | ident_directly_assignable CONDITION_SYM FOR_SYM sp_cond { if (Lex->spcont->declare_condition(thd, &$1, $4)) @@ -3604,6 +3881,12 @@ sp_proc_stmt_goto: ; +remember_lex: + { + $$= thd->lex; + } + ; + assignment_source_lex: { DBUG_ASSERT(Lex->sphead); @@ -8840,6 +9123,15 @@ remember_end: } ; +remember_end_opt: + { + if (yychar == YYEMPTY) + $$= (char*) YYLIP->get_cpp_ptr_rtrim(); + else + $$= (char*) YYLIP->get_cpp_tok_end_rtrim(); + } + ; + select_alias: /* empty */ { $$=null_clex_str;} | AS ident { $$=$2; } @@ -12347,14 +12639,8 @@ select_outvar: } | ident_or_text { - sp_variable *t; - - if (!Lex->spcont || !(t= Lex->spcont->find_variable(&$1, false))) - my_yyabort_error((ER_SP_UNDECLARED_VAR, MYF(0), $1.str)); - $$ = Lex->result ? (new (thd->mem_root) - my_var_sp(&$1, t->offset, t->type_handler(), - Lex->sphead)) : - NULL; + if (!($$= Lex->create_outvar(thd, &$1)) && Lex->result) + MYSQL_YYABORT; } | ident '.' ident { @@ -12454,6 +12740,22 @@ drop: lex->set_command(SQLCOM_DROP_DB, $3); lex->name= $4; } + | DROP PACKAGE_SYM opt_if_exists sp_name + { + LEX *lex= Lex; + lex->set_command(SQLCOM_DROP_PACKAGE, $3); + if (lex->sphead) + my_yyabort_error((ER_SP_NO_DROP_SP, MYF(0), "PACKAGE")); + lex->spname= $4; + } + | DROP PACKAGE_SYM BODY_SYM opt_if_exists sp_name + { + LEX *lex= Lex; + lex->set_command(SQLCOM_DROP_PACKAGE_BODY, $4); + if (lex->sphead) + my_yyabort_error((ER_SP_NO_DROP_SP, MYF(0), "PACKAGE BODY")); + lex->spname= $5; + } | DROP FUNCTION_SYM opt_if_exists ident '.' ident { LEX *lex= thd->lex; @@ -13384,6 +13686,18 @@ show_param: lex->sql_command = SQLCOM_SHOW_CREATE_FUNC; lex->spname= $3; } + | CREATE PACKAGE_SYM sp_name + { + LEX *lex= Lex; + lex->sql_command = SQLCOM_SHOW_CREATE_PACKAGE; + lex->spname= $3; + } + | CREATE PACKAGE_SYM BODY_SYM sp_name + { + LEX *lex= Lex; + lex->sql_command = SQLCOM_SHOW_CREATE_PACKAGE_BODY; + lex->spname= $4; + } | CREATE TRIGGER_SYM sp_name { LEX *lex= Lex; @@ -13416,6 +13730,20 @@ show_param: if (prepare_schema_table(thd, lex, 0, SCH_PROCEDURES)) MYSQL_YYABORT; } + | PACKAGE_SYM STATUS_SYM wild_and_where + { + LEX *lex= Lex; + lex->sql_command= SQLCOM_SHOW_STATUS_PACKAGE; + if (prepare_schema_table(thd, lex, 0, SCH_PROCEDURES)) + MYSQL_YYABORT; + } + | PACKAGE_SYM BODY_SYM STATUS_SYM wild_and_where + { + LEX *lex= Lex; + lex->sql_command= SQLCOM_SHOW_STATUS_PACKAGE_BODY; + if (prepare_schema_table(thd, lex, 0, SCH_PROCEDURES)) + MYSQL_YYABORT; + } | PROCEDURE_SYM CODE_SYM sp_name { Lex->sql_command= SQLCOM_SHOW_PROC_CODE; @@ -13426,6 +13754,11 @@ show_param: Lex->sql_command= SQLCOM_SHOW_FUNC_CODE; Lex->spname= $3; } + | PACKAGE_SYM BODY_SYM CODE_SYM sp_name + { + Lex->sql_command= SQLCOM_SHOW_PACKAGE_BODY_CODE; + Lex->spname= $4; + } | CREATE EVENT_SYM sp_name { Lex->spname= $3; @@ -14915,6 +15248,7 @@ keyword_directly_not_assignable: keyword_sp: keyword_sp_data_type | keyword_sp_not_data_type + | FUNCTION_SYM { /* Oracle-PLSQL-R */} ; @@ -15269,7 +15603,6 @@ keyword_sp_not_data_type: | TYPES_SYM {} | TYPE_SYM {} | UDF_RETURNS_SYM {} - | FUNCTION_SYM {} | UNCOMMITTED_SYM {} | UNDEFINED_SYM {} | UNDO_BUFFER_SIZE_SYM {} @@ -15966,6 +16299,18 @@ revoke_command: if (Lex->add_grant_command(thd, SQLCOM_REVOKE, TYPE_ENUM_PROCEDURE)) MYSQL_YYABORT; } + | grant_privileges ON PACKAGE_SYM grant_ident FROM user_and_role_list + { + if (Lex->add_grant_command(thd, SQLCOM_REVOKE, + TYPE_ENUM_PACKAGE)) + MYSQL_YYABORT; + } + | grant_privileges ON PACKAGE_SYM BODY_SYM grant_ident FROM user_and_role_list + { + if (Lex->add_grant_command(thd, SQLCOM_REVOKE, + TYPE_ENUM_PACKAGE_BODY)) + MYSQL_YYABORT; + } | ALL opt_privileges ',' GRANT OPTION FROM user_and_role_list { Lex->sql_command = SQLCOM_REVOKE_ALL; @@ -16017,6 +16362,20 @@ grant_command: if (Lex->add_grant_command(thd, SQLCOM_GRANT, TYPE_ENUM_PROCEDURE)) MYSQL_YYABORT; } + | grant_privileges ON PACKAGE_SYM grant_ident TO_SYM grant_list + opt_require_clause opt_grant_options + { + if (Lex->add_grant_command(thd, SQLCOM_GRANT, + TYPE_ENUM_PACKAGE)) + MYSQL_YYABORT; + } + | grant_privileges ON PACKAGE_SYM BODY_SYM grant_ident TO_SYM grant_list + opt_require_clause opt_grant_options + { + if (Lex->add_grant_command(thd, SQLCOM_GRANT, + TYPE_ENUM_PACKAGE_BODY)) + MYSQL_YYABORT; + } | PROXY_SYM ON user TO_SYM grant_list opt_grant_option { LEX *lex= Lex; @@ -17010,6 +17369,11 @@ sf_tail: { if (Lex->sp_body_finalize_function(thd)) MYSQL_YYABORT; + if (Lex->sphead->m_flags & sp_head::HAS_AGGREGATE_INSTR) + { + my_yyabort_error((ER_NOT_AGGREGATE_FUNCTION, MYF(0))); + } + Lex->sphead->set_chistics_agg_type(NOT_AGGREGATE); } ; @@ -17055,6 +17419,11 @@ sp_tail_standalone: } ; +opt_package_routine_end_name: + /* Empty */ { $$= null_clex_str; } + | ident { $$= $1; } + ; + sp_tail_is: IS | AS |