From ad104d5bfd455f245bd3e9602fd01e60b6e9f783 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 9 Oct 2007 20:46:33 -0300 Subject: [PATCH 1/5] Bug#28318 CREATE FUNCTION (UDF) requires a schema Bug#29816 Syntactically wrong query fails with misleading error message The core problem is that an SQL-invoked function name can be a that contains no , but the mysql parser insists that all stored procedures (function, procedures and triggers) must have a , which is not true for functions. This problem is especially visible when trying to create a function or when a query contains a syntax error after a function call (in the same query), both will fail with a "No database selected" message if the session is not attached to a particular schema, but the first one should succeed and the second fail with a "syntax error" message. Part of the fix is to revamp the sp name handling so that a schema name may be omitted for functions -- this means that the internal function name representation may not have a dot, which represents that the function doesn't have a schema name. The other part is to place schema checks after the type (function, trigger or procedure) of the routine is known. mysql-test/r/sp-error.result: Add test case result for Bug#29816 mysql-test/r/udf.result: Add test case result for Bug#28318 mysql-test/t/sp-error.test: Add test case for Bug#29816 mysql-test/t/udf.test: Add test case for Bug#28318 sql/sp.cc: Copy the (last) nul byte of the stored routine key and move name parsing code to the sp_name class constructor. sql/sp_head.cc: Revamp routine name parsing for when no schema is specified and omit dot from the qualified name if the routine is not associated with a scheme name. sql/sp_head.h: Name parsing got bigger, uninline by moving to a single unit -- the sp_head.cc file. sql/sql_yacc.yy: Only copy the schema name if one is actually set and check for schema name presence only where it's necessary. --- mysql-test/r/sp-error.result | 13 +++++++++++++ mysql-test/r/udf.result | 7 +++++++ mysql-test/t/sp-error.test | 20 ++++++++++++++++++++ mysql-test/t/udf.test | 14 ++++++++++++++ sql/sp.cc | 13 +++---------- sql/sp_head.cc | 31 ++++++++++++++++++++++++++++--- sql/sp_head.h | 11 +---------- sql/sql_yacc.yy | 31 ++++++++++++++++++++++++++----- 8 files changed, 112 insertions(+), 28 deletions(-) diff --git a/mysql-test/r/sp-error.result b/mysql-test/r/sp-error.result index bd0640b2b14..cc217ecd093 100644 --- a/mysql-test/r/sp-error.result +++ b/mysql-test/r/sp-error.result @@ -1452,3 +1452,16 @@ end until true end repeat retry; end// ERROR 42000: LEAVE with no matching label: retry +DROP DATABASE IF EXISTS mysqltest; +CREATE DATABASE mysqltest; +USE mysqltest; +DROP DATABASE mysqltest; +SELECT inexistent(), 1 + ,; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' at line 1 +SELECT inexistent(); +ERROR 42000: FUNCTION inexistent does not exist +SELECT .inexistent(); +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '()' at line 1 +SELECT ..inexistent(); +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '.inexistent()' at line 1 +USE test; diff --git a/mysql-test/r/udf.result b/mysql-test/r/udf.result index 2e9cf217ed6..3e29c780ca8 100644 --- a/mysql-test/r/udf.result +++ b/mysql-test/r/udf.result @@ -296,4 +296,11 @@ Qcache_queries_in_cache 0 drop table t1; drop function metaphon; set GLOBAL query_cache_size=default; +DROP DATABASE IF EXISTS mysqltest; +CREATE DATABASE mysqltest; +USE mysqltest; +DROP DATABASE mysqltest; +CREATE FUNCTION metaphon RETURNS STRING SONAME "UDF_EXAMPLE_LIB"; +DROP FUNCTION metaphon; +USE test; End of 5.0 tests. diff --git a/mysql-test/t/sp-error.test b/mysql-test/t/sp-error.test index 240cda67edc..a1abf4852b0 100644 --- a/mysql-test/t/sp-error.test +++ b/mysql-test/t/sp-error.test @@ -2089,6 +2089,26 @@ end// delimiter ;// +# +# Bug#29816 Syntactically wrong query fails with misleading error message +# + +--disable_warnings +DROP DATABASE IF EXISTS mysqltest; +--enable_warnings +CREATE DATABASE mysqltest; +USE mysqltest; +DROP DATABASE mysqltest; +--error ER_PARSE_ERROR +SELECT inexistent(), 1 + ,; +--error ER_SP_DOES_NOT_EXIST +SELECT inexistent(); +--error ER_PARSE_ERROR +SELECT .inexistent(); +--error ER_PARSE_ERROR +SELECT ..inexistent(); +USE test; + # # BUG#NNNN: New bug synopsis # diff --git a/mysql-test/t/udf.test b/mysql-test/t/udf.test index 75af1f4be4b..6a516a29534 100644 --- a/mysql-test/t/udf.test +++ b/mysql-test/t/udf.test @@ -311,5 +311,19 @@ drop table t1; drop function metaphon; set GLOBAL query_cache_size=default; +# +# Bug#28318 CREATE FUNCTION (UDF) requires a schema +# + +--disable_warnings +DROP DATABASE IF EXISTS mysqltest; +--enable_warnings +CREATE DATABASE mysqltest; +USE mysqltest; +DROP DATABASE mysqltest; +--replace_result $UDF_EXAMPLE_LIB UDF_EXAMPLE_LIB +eval CREATE FUNCTION metaphon RETURNS STRING SONAME "$UDF_EXAMPLE_LIB"; +DROP FUNCTION metaphon; +USE test; --echo End of 5.0 tests. diff --git a/sql/sp.cc b/sql/sp.cc index 75d6fa4618f..0b84e1ad07f 100644 --- a/sql/sp.cc +++ b/sql/sp.cc @@ -1405,12 +1405,12 @@ static bool add_used_routine(LEX *lex, Query_arena *arena, { Sroutine_hash_entry *rn= (Sroutine_hash_entry *)arena->alloc(sizeof(Sroutine_hash_entry) + - key->length); + key->length + 1); if (!rn) // OOM. Error will be reported using fatal_error(). return FALSE; rn->key.length= key->length; rn->key.str= (char *)rn + sizeof(Sroutine_hash_entry); - memcpy(rn->key.str, key->str, key->length); + memcpy(rn->key.str, key->str, key->length + 1); my_hash_insert(&lex->sroutines, (byte *)rn); lex->sroutines_list.link_in_list((byte *)rn, (byte **)&rn->next); rn->belong_to_view= belong_to_view; @@ -1595,7 +1595,7 @@ sp_cache_routines_and_add_tables_aux(THD *thd, LEX *lex, for (Sroutine_hash_entry *rt= start; rt; rt= rt->next) { - sp_name name(rt->key.str, rt->key.length); + sp_name name(thd, rt->key.str, rt->key.length); int type= rt->key.str[0]; sp_head *sp; @@ -1603,13 +1603,6 @@ sp_cache_routines_and_add_tables_aux(THD *thd, LEX *lex, &thd->sp_func_cache : &thd->sp_proc_cache), &name))) { - name.m_name.str= strchr(name.m_qname.str, '.'); - name.m_db.length= name.m_name.str - name.m_qname.str; - name.m_db.str= strmake_root(thd->mem_root, name.m_qname.str, - name.m_db.length); - name.m_name.str+= 1; - name.m_name.length= name.m_qname.length - name.m_db.length - 1; - switch ((ret= db_find_routine(thd, type, &name, &sp))) { case SP_OK: diff --git a/sql/sp_head.cc b/sql/sp_head.cc index 5ad6625efb8..69dda9ec1e8 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -369,17 +369,42 @@ sp_eval_expr(THD *thd, Field *result_field, Item **expr_item_ptr) * */ +sp_name::sp_name(THD *thd, char *key, uint key_len) +{ + m_sroutines_key.str= key; + m_sroutines_key.length= key_len; + m_qname.str= ++key; + m_qname.length= key_len - 1; + if ((m_name.str= strchr(m_qname.str, '.'))) + { + m_db.length= m_name.str - key; + m_db.str= strmake_root(thd->mem_root, key, m_db.length); + m_name.str++; + m_name.length= m_qname.length - m_db.length - 1; + } + else + { + m_name.str= m_qname.str; + m_name.length= m_qname.length; + m_db.str= 0; + m_db.length= 0; + } + m_explicit_name= false; +} + void sp_name::init_qname(THD *thd) { - m_sroutines_key.length= m_db.length + m_name.length + 2; + const uint dot= !!m_db.length; + /* m_sroutines format: m_type + [database + dot] + name + nul */ + m_sroutines_key.length= 1 + m_db.length + dot + m_name.length; if (!(m_sroutines_key.str= thd->alloc(m_sroutines_key.length + 1))) return; m_qname.length= m_sroutines_key.length - 1; m_qname.str= m_sroutines_key.str + 1; - sprintf(m_qname.str, "%.*s.%.*s", + sprintf(m_qname.str, "%.*s%.*s%.*s", m_db.length, (m_db.length ? m_db.str : ""), - m_name.length, m_name.str); + dot, ".", m_name.length, m_name.str); } diff --git a/sql/sp_head.h b/sql/sp_head.h index ebe40ce9c87..7d042367985 100644 --- a/sql/sp_head.h +++ b/sql/sp_head.h @@ -72,16 +72,7 @@ public: Creates temporary sp_name object from key, used mainly for SP-cache lookups. */ - sp_name(char *key, uint key_len) - { - m_sroutines_key.str= key; - m_sroutines_key.length= key_len; - m_name.str= m_qname.str= key + 1; - m_name.length= m_qname.length= key_len - 1; - m_db.str= 0; - m_db.length= 0; - m_explicit_name= false; - } + sp_name(THD *thd, char *key, uint key_len); // Init. the qualified name from the db and name. void init_qname(THD *thd); // thd for memroot allocation diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index e0b9ab28594..7e3c11b5122 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -1569,13 +1569,15 @@ sp_name: | ident { LEX *lex= Lex; - LEX_STRING db; + LEX_STRING db= {0,0}; + THD *thd= YYTHD; + if (check_routine_name($1)) { my_error(ER_SP_WRONG_NAME, MYF(0), $1.str); MYSQL_YYABORT; } - if (lex->copy_db_to(&db.str, &db.length)) + if (thd->db && thd->copy_db_to(&db.str, &db.length)) MYSQL_YYABORT; $$= new sp_name(db, $1, false); if ($$) @@ -1625,6 +1627,13 @@ create_function_tail: my_error(ER_SP_NO_RECURSIVE_CREATE, MYF(0), "FUNCTION"); MYSQL_YYABORT; } + + if (!lex->spname->m_db.length) + { + my_message(ER_NO_DB_ERROR, ER(ER_NO_DB_ERROR), MYF(0)); + MYSQL_YYABORT; + } + /* Order is important here: new - reset - init */ sp= new sp_head(); sp->reset_thd_mem_root(thd); @@ -5194,8 +5203,8 @@ simple_expr: #endif /* HAVE_DLOPEN */ { THD *thd= lex->thd; - LEX_STRING db; - if (lex->copy_db_to(&db.str, &db.length)) + LEX_STRING db= {0,0}; + if (thd->db && thd->copy_db_to(&db.str, &db.length)) MYSQL_YYABORT; sp_name *name= new sp_name(db, $1, false); if (name) @@ -9730,7 +9739,13 @@ trigger_tail: my_error(ER_SP_NO_RECURSIVE_CREATE, MYF(0), "TRIGGER"); MYSQL_YYABORT; } - + + if (!$3->m_db.length) + { + my_message(ER_NO_DB_ERROR, ER(ER_NO_DB_ERROR), MYF(0)); + MYSQL_YYABORT; + } + if (!(sp= new sp_head())) MYSQL_YYABORT; sp->reset_thd_mem_root(thd); @@ -9813,6 +9828,12 @@ sp_tail: MYSQL_YYABORT; } + if (!$3->m_db.length) + { + my_message(ER_NO_DB_ERROR, ER(ER_NO_DB_ERROR), MYF(0)); + MYSQL_YYABORT; + } + lex->stmt_definition_begin= $2; /* Order is important here: new - reset - init */ From 77c50858822197afd1b50e34c65954fd9bf6931b Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 10 Oct 2007 14:42:29 +0400 Subject: [PATCH 2/5] Cleanup sp.test. mysql-test/r/sp.result: Update result file. --- mysql-test/r/sp.result | 6 +++--- mysql-test/t/sp.test | 10 +++------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/mysql-test/r/sp.result b/mysql-test/r/sp.result index b1120a22dfd..ef173b9661f 100644 --- a/mysql-test/r/sp.result +++ b/mysql-test/r/sp.result @@ -5667,7 +5667,6 @@ t3_id_1 t3_id_2 t4_id DROP PROCEDURE p1| DROP VIEW v1, v2| DROP TABLE t3, t4| -End of 5.0 tests DROP TABLE IF EXISTS bug23760| DROP TABLE IF EXISTS bug23760_log| DROP PROCEDURE IF EXISTS bug23760_update_log| @@ -6145,7 +6144,6 @@ Procedure sql_mode Create Procedure proc_21513 CREATE DEFINER=`root`@`localhost` PROCEDURE `proc_21513`() `my_label`:BEGIN END drop procedure proc_21513| -End of 5.0 tests. drop table t1,t2; CREATE TABLE t1 (a int auto_increment primary key) engine=MyISAM; CREATE TABLE t2 (a int auto_increment primary key, b int) engine=innodb; @@ -6565,4 +6563,6 @@ f1() DROP TABLE t1; DROP FUNCTION f1; -End of 5.0 tests +# ------------------------------------------------------------------ +# -- End of 5.0 tests +# ------------------------------------------------------------------ diff --git a/mysql-test/t/sp.test b/mysql-test/t/sp.test index 465585a693e..501d96c842a 100644 --- a/mysql-test/t/sp.test +++ b/mysql-test/t/sp.test @@ -6642,9 +6642,6 @@ DROP VIEW v1, v2| DROP TABLE t3, t4| ---echo End of 5.0 tests - - # # BUG#23760: ROW_COUNT() and store procedure not owrking together # @@ -7076,9 +7073,6 @@ show create procedure proc_21513| drop procedure proc_21513| -### ---echo End of 5.0 tests. - # # BUG#NNNN: New bug synopsis # @@ -7677,4 +7671,6 @@ DROP FUNCTION f1; ########################################################################### ---echo End of 5.0 tests +--echo # ------------------------------------------------------------------ +--echo # -- End of 5.0 tests +--echo # ------------------------------------------------------------------ From f1d7a96b0a9432793a93571c682aea5fd7e17966 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 15 Oct 2007 19:15:38 -0600 Subject: [PATCH 3/5] Bug#28318 (CREATE FUNCTION (UDF) requires a schema) -- part II The root cause of the issue was that the CREATE FUNCTION grammar, for User Defined Functions, was using the sp_name rule. The sp_name rule is intended for fully qualified stored procedure names, like either ident.ident, or just ident but with a default database implicitly selected. A UDF does not have a fully qualified name, only a name (ident), and should not use the sp_name grammar fragment during parsing. The fix is to re-organize the CREATE FUNCTION grammar, to better separate: - creating UDF (no definer, can have AGGREGATE, simple ident) - creating Stored Functions (definer, no AGGREGATE, fully qualified name) With the test case provided, another issue was exposed which is also fixed: the DROP FUNCTION statement was using sp_name and also failing when no database is implicitly selected, when droping UDF functions. The fix is also to change the grammar so that DROP FUNCTION works with both the ident.ident syntax (to drop a stored function), or just the ident syntax (to drop either a UDF or a Stored Function, in the current database) mysql-test/r/sp-error.result: Adjust test results mysql-test/r/udf.result: Adjust test results mysql-test/t/sp-error.test: Adjust test results mysql-test/t/udf.test: Adjust test results sql/sql_parse.cc: CREATE UDF FUNCTION does not use a fully qualified name. sql/sql_yacc.yy: Fix grammar for CREATE / DROP FUNCTION, FOR udf Improve error messages for select no_such_function() --- mysql-test/r/sp-error.result | 4 +- mysql-test/r/udf.result | 4 +- mysql-test/t/sp-error.test | 7 +- mysql-test/t/udf.test | 4 +- sql/sql_parse.cc | 6 - sql/sql_yacc.yy | 412 +++++++++++++++++++---------------- 6 files changed, 233 insertions(+), 204 deletions(-) diff --git a/mysql-test/r/sp-error.result b/mysql-test/r/sp-error.result index cc217ecd093..b4bcfbdc7f7 100644 --- a/mysql-test/r/sp-error.result +++ b/mysql-test/r/sp-error.result @@ -1211,7 +1211,7 @@ ERROR 42S02: Unknown table 'c' in field list drop procedure bug15091; drop function if exists bug16896; create aggregate function bug16896() returns int return 1; -ERROR 42000: AGGREGATE is not supported for stored functions +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '() returns int return 1' at line 1 DROP PROCEDURE IF EXISTS bug14702; CREATE IF NOT EXISTS PROCEDURE bug14702() BEGIN @@ -1457,7 +1457,7 @@ CREATE DATABASE mysqltest; USE mysqltest; DROP DATABASE mysqltest; SELECT inexistent(), 1 + ,; -ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' at line 1 +ERROR 42000: FUNCTION inexistent does not exist SELECT inexistent(); ERROR 42000: FUNCTION inexistent does not exist SELECT .inexistent(); diff --git a/mysql-test/r/udf.result b/mysql-test/r/udf.result index 3e29c780ca8..4a12e8e6d81 100644 --- a/mysql-test/r/udf.result +++ b/mysql-test/r/udf.result @@ -95,10 +95,10 @@ FR DROP TABLE bug19904; CREATE DEFINER=CURRENT_USER() FUNCTION should_not_parse RETURNS STRING SONAME "should_not_parse.so"; -ERROR HY000: Incorrect usage of SONAME and DEFINER +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'RETURNS STRING SONAME "should_not_parse.so"' at line 2 CREATE DEFINER=someone@somewhere FUNCTION should_not_parse RETURNS STRING SONAME "should_not_parse.so"; -ERROR HY000: Incorrect usage of SONAME and DEFINER +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'RETURNS STRING SONAME "should_not_parse.so"' at line 2 create table t1(f1 int); insert into t1 values(1),(2); explain select myfunc_int(f1) from t1 order by 1; diff --git a/mysql-test/t/sp-error.test b/mysql-test/t/sp-error.test index a1abf4852b0..8133a2271a1 100644 --- a/mysql-test/t/sp-error.test +++ b/mysql-test/t/sp-error.test @@ -1744,7 +1744,7 @@ drop procedure bug15091; drop function if exists bug16896; --enable_warnings ---error ER_SP_NO_AGGREGATE +--error ER_PARSE_ERROR create aggregate function bug16896() returns int return 1; @@ -2099,7 +2099,10 @@ DROP DATABASE IF EXISTS mysqltest; CREATE DATABASE mysqltest; USE mysqltest; DROP DATABASE mysqltest; ---error ER_PARSE_ERROR +# Both ER_SP_DOES_NOT_EXIST and ER_PARSE_ERROR are valid here, +# the result is implementation dependent: +# See Bug#29816 for details +--error ER_SP_DOES_NOT_EXIST SELECT inexistent(), 1 + ,; --error ER_SP_DOES_NOT_EXIST SELECT inexistent(); diff --git a/mysql-test/t/udf.test b/mysql-test/t/udf.test index 6a516a29534..14aef3361e4 100644 --- a/mysql-test/t/udf.test +++ b/mysql-test/t/udf.test @@ -113,11 +113,11 @@ DROP TABLE bug19904; # Bug#21269: DEFINER-clause is allowed for UDF-functions # ---error ER_WRONG_USAGE +--error ER_PARSE_ERROR CREATE DEFINER=CURRENT_USER() FUNCTION should_not_parse RETURNS STRING SONAME "should_not_parse.so"; ---error ER_WRONG_USAGE +--error ER_PARSE_ERROR CREATE DEFINER=someone@somewhere FUNCTION should_not_parse RETURNS STRING SONAME "should_not_parse.so"; # diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 8c61243e3c5..a111208cbf9 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -4016,12 +4016,6 @@ end_with_restore_list: if (check_access(thd,INSERT_ACL,"mysql",0,1,0,0)) break; #ifdef HAVE_DLOPEN - if (sp_find_routine(thd, TYPE_ENUM_FUNCTION, lex->spname, - &thd->sp_func_cache, FALSE)) - { - my_error(ER_UDF_EXISTS, MYF(0), lex->spname->m_name.str); - goto error; - } if (!(res = mysql_create_function(thd, &lex->udf))) send_ok(thd); #else diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 9c07add98d4..c1012b66bf4 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -1121,8 +1121,6 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %type cast_type -%type udf_func_type - %type FUNC_ARG0 FUNC_ARG1 FUNC_ARG2 FUNC_ARG3 keyword keyword_sp %type user grant_user @@ -1181,11 +1179,12 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); statement sp_suid sp_c_chistics sp_a_chistics sp_chistic sp_c_chistic xa load_data opt_field_or_var_spec fields_or_vars opt_load_data_set_spec - definer view_replace_or_algorithm view_replace view_algorithm_opt - view_algorithm view_or_trigger_or_sp view_or_trigger_or_sp_tail + view_replace_or_algorithm view_replace view_algorithm_opt + view_algorithm view_or_trigger_or_sp definer_tail view_suid view_tail view_list_opt view_list view_select - view_check_option trigger_tail sp_tail + view_check_option trigger_tail sp_tail sf_tail udf_tail case_stmt_specification simple_case_stmt searched_case_stmt + definer_opt no_definer definer END_OF_INPUT %type call sp_proc_stmts sp_proc_stmts1 sp_proc_stmt @@ -1570,15 +1569,14 @@ sp_name: | ident { LEX *lex= Lex; - LEX_STRING db= {0,0}; - THD *thd= YYTHD; + LEX_STRING db; if (check_routine_name($1)) { my_error(ER_SP_WRONG_NAME, MYF(0), $1.str); MYSQL_YYABORT; } - if (thd->db && thd->copy_db_to(&db.str, &db.length)) + if (lex->copy_db_to(&db.str, &db.length)) MYSQL_YYABORT; $$= new sp_name(db, $1, false); if ($$) @@ -1586,131 +1584,6 @@ sp_name: } ; -create_function_tail: - RETURNS_SYM udf_type UDF_SONAME_SYM TEXT_STRING_sys - { - LEX *lex=Lex; - if (lex->definer != NULL) - { - /* - DEFINER is a concept meaningful when interpreting SQL code. - UDF functions are compiled. - Using DEFINER with UDF has therefore no semantic, - and is considered a parsing error. - */ - my_error(ER_WRONG_USAGE, MYF(0), "SONAME", "DEFINER"); - MYSQL_YYABORT; - } - lex->sql_command = SQLCOM_CREATE_FUNCTION; - lex->udf.name = lex->spname->m_name; - lex->udf.returns=(Item_result) $2; - lex->udf.dl=$4.str; - } - | '(' - { - THD *thd= YYTHD; - LEX *lex= thd->lex; - Lex_input_stream *lip= thd->m_lip; - sp_head *sp; - - /* - First check if AGGREGATE was used, in that case it's a - syntax error. - */ - if (lex->udf.type == UDFTYPE_AGGREGATE) - { - my_error(ER_SP_NO_AGGREGATE, MYF(0)); - MYSQL_YYABORT; - } - - if (lex->sphead) - { - my_error(ER_SP_NO_RECURSIVE_CREATE, MYF(0), "FUNCTION"); - MYSQL_YYABORT; - } - - if (!lex->spname->m_db.length) - { - my_message(ER_NO_DB_ERROR, ER(ER_NO_DB_ERROR), MYF(0)); - MYSQL_YYABORT; - } - - /* Order is important here: new - reset - init */ - sp= new sp_head(); - sp->reset_thd_mem_root(thd); - sp->init(lex); - sp->init_sp_name(thd, lex->spname); - - sp->m_type= TYPE_ENUM_FUNCTION; - lex->sphead= sp; - /* - * We have to turn of CLIENT_MULTI_QUERIES while parsing a - * stored procedure, otherwise yylex will chop it into pieces - * at each ';'. - */ - sp->m_old_cmq= thd->client_capabilities & CLIENT_MULTI_QUERIES; - thd->client_capabilities &= ~CLIENT_MULTI_QUERIES; - lex->sphead->m_param_begin= lip->tok_start+1; - } - sp_fdparam_list ')' - { - THD *thd= YYTHD; - LEX *lex= thd->lex; - Lex_input_stream *lip= thd->m_lip; - - lex->sphead->m_param_end= lip->tok_start; - } - RETURNS_SYM - { - LEX *lex= Lex; - lex->charset= NULL; - lex->length= lex->dec= NULL; - lex->interval_list.empty(); - lex->type= 0; - } - type - { - LEX *lex= Lex; - sp_head *sp= lex->sphead; - - if (sp->fill_field_definition(YYTHD, lex, - (enum enum_field_types) $8, - &sp->m_return_field_def)) - MYSQL_YYABORT; - - bzero((char *)&lex->sp_chistics, sizeof(st_sp_chistics)); - } - sp_c_chistics - { - THD *thd= YYTHD; - LEX *lex= thd->lex; - Lex_input_stream *lip= thd->m_lip; - - lex->sphead->m_chistics= &lex->sp_chistics; - lex->sphead->m_body_begin= lip->tok_start; - } - sp_proc_stmt - { - LEX *lex= Lex; - sp_head *sp= lex->sphead; - - if (sp->is_not_allowed_in_function("function")) - MYSQL_YYABORT; - - lex->sql_command= SQLCOM_CREATE_SPFUNCTION; - sp->init_strings(YYTHD, lex); - if (!(sp->m_flags & sp_head::HAS_RETURN)) - { - my_error(ER_SP_NORETURN, MYF(0), sp->m_qname.str); - MYSQL_YYABORT; - } - /* Restore flag if it was cleared above */ - if (sp->m_old_cmq) - YYTHD->client_capabilities |= CLIENT_MULTI_QUERIES; - sp->restore_thd_mem_root(YYTHD); - } - ; - sp_a_chistics: /* Empty */ {} | sp_a_chistics sp_chistic {} @@ -3011,10 +2884,6 @@ opt_select_from: opt_limit_clause {} | select_from select_lock_type; -udf_func_type: - /* empty */ { $$ = UDFTYPE_FUNCTION; } - | AGGREGATE_SYM { $$ = UDFTYPE_AGGREGATE; }; - udf_type: STRING_SYM {$$ = (int) STRING_RESULT; } | REAL {$$ = (int) REAL_RESULT; } @@ -3678,7 +3547,7 @@ alter: lex->sql_command= SQLCOM_ALTER_FUNCTION; lex->spname= $3; } - | ALTER view_algorithm_opt definer view_suid + | ALTER view_algorithm_opt definer_opt view_suid VIEW_SYM table_ident { THD *thd= YYTHD; @@ -5204,9 +5073,31 @@ simple_expr: #endif /* HAVE_DLOPEN */ { THD *thd= lex->thd; - LEX_STRING db= {0,0}; - if (thd->db && thd->copy_db_to(&db.str, &db.length)) + LEX_STRING db; + if (! thd->db && ! lex->sphead) + { + /* + The proper error message should be in the lines of: + Can't resolve () to a function call, + because this function: + - is not a native function, + - is not a user defined function, + - can not match a stored function since no database is selected. + Reusing ER_SP_DOES_NOT_EXIST have a message consistent with + the case when a default database exist, see below. + */ + my_error(ER_SP_DOES_NOT_EXIST, MYF(0), + "FUNCTION", $1); MYSQL_YYABORT; + } + + if (lex->copy_db_to(&db.str, &db.length)) + MYSQL_YYABORT; + + /* + From here, the parser assumes () is a stored function, + as a last choice. This later can lead to ER_SP_DOES_NOT_EXIST. + */ sp_name *name= new sp_name(db, $1, false); if (name) name->init_qname(thd); @@ -6508,9 +6399,11 @@ drop: lex->drop_if_exists=$3; lex->name=$4.str; } - | DROP FUNCTION_SYM if_exists sp_name + | DROP FUNCTION_SYM if_exists ident '.' ident { - LEX *lex=Lex; + THD *thd= YYTHD; + LEX *lex= thd->lex; + sp_name *spname; if (lex->sphead) { my_error(ER_SP_NO_DROP_SP, MYF(0), "FUNCTION"); @@ -6518,7 +6411,28 @@ drop: } lex->sql_command = SQLCOM_DROP_FUNCTION; lex->drop_if_exists= $3; - lex->spname= $4; + spname= new sp_name($4, $6, true); + spname->init_qname(thd); + lex->spname= spname; + } + | DROP FUNCTION_SYM if_exists ident + { + THD *thd= YYTHD; + LEX *lex= thd->lex; + LEX_STRING db= {0, 0}; + sp_name *spname; + if (lex->sphead) + { + my_error(ER_SP_NO_DROP_SP, MYF(0), "FUNCTION"); + MYSQL_YYABORT; + } + if (thd->db && lex->copy_db_to(&db.str, &db.length)) + MYSQL_YYABORT; + lex->sql_command = SQLCOM_DROP_FUNCTION; + lex->drop_if_exists= $3; + spname= new sp_name(db, $4, false); + spname->init_qname(thd); + lex->spname= spname; } | DROP PROCEDURE if_exists sp_name { @@ -9567,19 +9481,27 @@ subselect_end: **************************************************************************/ view_or_trigger_or_sp: - definer view_or_trigger_or_sp_tail - {} - | view_replace_or_algorithm definer view_tail - {} + definer definer_tail + {} + | no_definer no_definer_tail + {} + | view_replace_or_algorithm definer_opt view_tail + {} ; -view_or_trigger_or_sp_tail: - view_tail - {} +definer_tail: + view_tail | trigger_tail - {} | sp_tail - {} + | sf_tail + ; + +no_definer_tail: + view_tail + | trigger_tail + | sp_tail + | sf_tail + | udf_tail ; /************************************************************************** @@ -9588,23 +9510,31 @@ view_or_trigger_or_sp_tail: **************************************************************************/ +definer_opt: + no_definer + | definer + ; + +no_definer: + /* empty */ + { + /* + We have to distinguish missing DEFINER-clause from case when + CURRENT_USER specified as definer explicitly in order to properly + handle CREATE TRIGGER statements which come to replication thread + from older master servers (i.e. to create non-suid trigger in this + case). + */ + YYTHD->lex->definer= 0; + } + ; + definer: - /* empty */ - { - /* - We have to distinguish missing DEFINER-clause from case when - CURRENT_USER specified as definer explicitly in order to properly - handle CREATE TRIGGER statements which come to replication thread - from older master servers (i.e. to create non-suid trigger in this - case). - */ - YYTHD->lex->definer= 0; - } - | DEFINER_SYM EQ user - { - YYTHD->lex->definer= get_current_user(YYTHD, $3); - } - ; + DEFINER_SYM EQ user + { + YYTHD->lex->definer= get_current_user(YYTHD, $3); + } +; /************************************************************************** @@ -9755,12 +9685,6 @@ trigger_tail: MYSQL_YYABORT; } - if (!$3->m_db.length) - { - my_message(ER_NO_DB_ERROR, ER(ER_NO_DB_ERROR), MYF(0)); - MYSQL_YYABORT; - } - if (!(sp= new sp_head())) MYSQL_YYABORT; sp->reset_thd_mem_root(thd); @@ -9822,17 +9746,131 @@ trigger_tail: **************************************************************************/ +udf_tail: + AGGREGATE_SYM remember_name FUNCTION_SYM ident + RETURNS_SYM udf_type UDF_SONAME_SYM TEXT_STRING_sys + { + LEX *lex=Lex; + lex->sql_command = SQLCOM_CREATE_FUNCTION; + lex->udf.type= UDFTYPE_AGGREGATE; + lex->stmt_definition_begin= $2; + lex->udf.name = $4; + lex->udf.returns=(Item_result) $6; + lex->udf.dl=$8.str; + } + | remember_name FUNCTION_SYM ident + RETURNS_SYM udf_type UDF_SONAME_SYM TEXT_STRING_sys + { + LEX *lex=Lex; + lex->sql_command = SQLCOM_CREATE_FUNCTION; + lex->udf.type= UDFTYPE_FUNCTION; + lex->stmt_definition_begin= $1; + lex->udf.name = $3; + lex->udf.returns=(Item_result) $5; + lex->udf.dl=$7.str; + } + ; + +sf_tail: + remember_name /* $1 */ + FUNCTION_SYM /* $2 */ + sp_name /* $3 */ + '(' /* 44 */ + { /* $5 */ + THD *thd= YYTHD; + LEX *lex= thd->lex; + Lex_input_stream *lip= thd->m_lip; + sp_head *sp; + + lex->stmt_definition_begin= $1; + lex->spname= $3; + + if (lex->sphead) + { + my_error(ER_SP_NO_RECURSIVE_CREATE, MYF(0), "FUNCTION"); + MYSQL_YYABORT; + } + + /* Order is important here: new - reset - init */ + sp= new sp_head(); + sp->reset_thd_mem_root(thd); + sp->init(lex); + sp->init_sp_name(thd, lex->spname); + + sp->m_type= TYPE_ENUM_FUNCTION; + lex->sphead= sp; + /* + * We have to turn of CLIENT_MULTI_QUERIES while parsing a + * stored procedure, otherwise yylex will chop it into pieces + * at each ';'. + */ + sp->m_old_cmq= thd->client_capabilities & CLIENT_MULTI_QUERIES; + thd->client_capabilities &= ~CLIENT_MULTI_QUERIES; + lex->sphead->m_param_begin= lip->tok_start+1; + } + sp_fdparam_list /* $6 */ + ')' /* $7 */ + { /* $8 */ + THD *thd= YYTHD; + LEX *lex= thd->lex; + Lex_input_stream *lip= thd->m_lip; + + lex->sphead->m_param_end= lip->tok_start; + } + RETURNS_SYM /* $9 */ + { /* $10 */ + LEX *lex= Lex; + lex->charset= NULL; + lex->length= lex->dec= NULL; + lex->interval_list.empty(); + lex->type= 0; + } + type /* $11 */ + { /* $12 */ + LEX *lex= Lex; + sp_head *sp= lex->sphead; + + if (sp->fill_field_definition(YYTHD, lex, + (enum enum_field_types) $11, + &sp->m_return_field_def)) + MYSQL_YYABORT; + + bzero((char *)&lex->sp_chistics, sizeof(st_sp_chistics)); + } + sp_c_chistics /* $13 */ + { /* $14 */ + THD *thd= YYTHD; + LEX *lex= thd->lex; + Lex_input_stream *lip= thd->m_lip; + + lex->sphead->m_chistics= &lex->sp_chistics; + lex->sphead->m_body_begin= lip->tok_start; + } + sp_proc_stmt /* $15 */ + { + LEX *lex= Lex; + sp_head *sp= lex->sphead; + + if (sp->is_not_allowed_in_function("function")) + MYSQL_YYABORT; + + lex->sql_command= SQLCOM_CREATE_SPFUNCTION; + sp->init_strings(YYTHD, lex); + if (!(sp->m_flags & sp_head::HAS_RETURN)) + { + my_error(ER_SP_NORETURN, MYF(0), sp->m_qname.str); + MYSQL_YYABORT; + } + /* Restore flag if it was cleared above */ + if (sp->m_old_cmq) + YYTHD->client_capabilities |= CLIENT_MULTI_QUERIES; + sp->restore_thd_mem_root(YYTHD); + } + ; + + sp_tail: - udf_func_type remember_name FUNCTION_SYM sp_name - { - LEX *lex=Lex; - lex->udf.type= $1; - lex->stmt_definition_begin= $2; - lex->spname= $4; - } - create_function_tail - {} - | PROCEDURE remember_name sp_name + PROCEDURE remember_name sp_name { LEX *lex= Lex; sp_head *sp; @@ -9843,12 +9881,6 @@ sp_tail: MYSQL_YYABORT; } - if (!$3->m_db.length) - { - my_message(ER_NO_DB_ERROR, ER(ER_NO_DB_ERROR), MYF(0)); - MYSQL_YYABORT; - } - lex->stmt_definition_begin= $2; /* Order is important here: new - reset - init */ From 28ef3b2a7fcfb67b9319251a264b274eb3617d8b Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 16 Oct 2007 11:16:31 -0600 Subject: [PATCH 4/5] Implementing code review comments mysql-test/r/sp.result: Added tests for coverage mysql-test/t/sp.test: Added tests for coverage sql/sql_udf.cc: Code cleanup --- mysql-test/r/sp.result | 15 +++++++++++++++ mysql-test/t/sp.test | 29 +++++++++++++++++++++++++++++ sql/sql_udf.cc | 4 ++-- 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/mysql-test/r/sp.result b/mysql-test/r/sp.result index ef173b9661f..061bbafd9a1 100644 --- a/mysql-test/r/sp.result +++ b/mysql-test/r/sp.result @@ -6563,6 +6563,21 @@ f1() DROP TABLE t1; DROP FUNCTION f1; +DROP PROCEDURE IF EXISTS db28318_a.t1; +DROP PROCEDURE IF EXISTS db28318_b.t2; +DROP DATABASE IF EXISTS db28318_a; +DROP DATABASE IF EXISTS db28318_b; +CREATE DATABASE db28318_a; +CREATE DATABASE db28318_b; +CREATE PROCEDURE db28318_a.t1() SELECT "db28318_a.t1"; +CREATE PROCEDURE db28318_b.t2() CALL t1(); +use db28318_a; +CALL db28318_b.t2(); +ERROR 42000: PROCEDURE db28318_b.t1 does not exist +DROP PROCEDURE db28318_a.t1; +DROP PROCEDURE db28318_b.t2; +DROP DATABASE db28318_a; +DROP DATABASE db28318_b; # ------------------------------------------------------------------ # -- End of 5.0 tests # ------------------------------------------------------------------ diff --git a/mysql-test/t/sp.test b/mysql-test/t/sp.test index 501d96c842a..785e7e3793c 100644 --- a/mysql-test/t/sp.test +++ b/mysql-test/t/sp.test @@ -7671,6 +7671,35 @@ DROP FUNCTION f1; ########################################################################### +# +# Bug#28318 (CREATE FUNCTION (UDF) requires a schema) +# + +--disable_warnings +DROP PROCEDURE IF EXISTS db28318_a.t1; +DROP PROCEDURE IF EXISTS db28318_b.t2; +DROP DATABASE IF EXISTS db28318_a; +DROP DATABASE IF EXISTS db28318_b; +--enable_warnings + +CREATE DATABASE db28318_a; +CREATE DATABASE db28318_b; + +CREATE PROCEDURE db28318_a.t1() SELECT "db28318_a.t1"; +CREATE PROCEDURE db28318_b.t2() CALL t1(); + +use db28318_a; + +# In db28318_b.t2, t1 refers to db28318_b.t1 +--error ER_SP_DOES_NOT_EXIST +CALL db28318_b.t2(); + +DROP PROCEDURE db28318_a.t1; +DROP PROCEDURE db28318_b.t2; +DROP DATABASE db28318_a; +DROP DATABASE db28318_b; + + --echo # ------------------------------------------------------------------ --echo # -- End of 5.0 tests --echo # ------------------------------------------------------------------ diff --git a/sql/sql_udf.cc b/sql/sql_udf.cc index 077660f0bb9..ce6cd43eace 100644 --- a/sql/sql_udf.cc +++ b/sql/sql_udf.cc @@ -426,14 +426,14 @@ int mysql_create_function(THD *thd,udf_func *udf) } if (udf->name.length > NAME_LEN) { - my_error(ER_TOO_LONG_IDENT, MYF(0), udf->name); + my_error(ER_TOO_LONG_IDENT, MYF(0), udf->name.str); DBUG_RETURN(1); } rw_wrlock(&THR_LOCK_udf); if ((hash_search(&udf_hash,(byte*) udf->name.str, udf->name.length))) { - my_error(ER_UDF_EXISTS, MYF(0), udf->name); + my_error(ER_UDF_EXISTS, MYF(0), udf->name.str); goto err; } if (!(dl = find_udf_dl(udf->dl))) From 42b6424327763192c7fb4701f0ae93b7a52af89d Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 16 Oct 2007 14:27:20 -0600 Subject: [PATCH 5/5] Fixed broken call to my_error sql/sql_yacc.yy: Fixed error message to use char*, not LEX_STRING --- sql/sql_yacc.yy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index c1012b66bf4..3fd8d339f31 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -5087,7 +5087,7 @@ simple_expr: the case when a default database exist, see below. */ my_error(ER_SP_DOES_NOT_EXIST, MYF(0), - "FUNCTION", $1); + "FUNCTION", $1.str); MYSQL_YYABORT; }