diff options
author | Alexander Barkov <bar@mariadb.org> | 2016-08-24 07:39:04 +0400 |
---|---|---|
committer | Alexander Barkov <bar@mariadb.org> | 2017-04-05 15:02:46 +0400 |
commit | c570636ba278e935701ca917af966630d38556ab (patch) | |
tree | 856f9cccf65da0ffae4b70def8452fcf25f23182 | |
parent | 71a0a12e61818d68201dfc985a64b58c19cfef44 (diff) | |
download | mariadb-git-c570636ba278e935701ca917af966630d38556ab.tar.gz |
MDEV-10580 sql_mode=ORACLE: FOR loop statement
Adding non-labeled FOR LOOP statement.
-rw-r--r-- | mysql-test/r/sp-error.result | 2 | ||||
-rw-r--r-- | mysql-test/suite/compat/oracle/r/sp-code.result | 67 | ||||
-rw-r--r-- | mysql-test/suite/compat/oracle/r/sp.result | 86 | ||||
-rw-r--r-- | mysql-test/suite/compat/oracle/t/sp-code.test | 44 | ||||
-rw-r--r-- | mysql-test/suite/compat/oracle/t/sp.test | 88 | ||||
-rw-r--r-- | sql/field.h | 13 | ||||
-rw-r--r-- | sql/sql_lex.cc | 124 | ||||
-rw-r--r-- | sql/sql_lex.h | 14 | ||||
-rw-r--r-- | sql/sql_yacc.yy | 2 | ||||
-rw-r--r-- | sql/sql_yacc_ora.yy | 56 | ||||
-rw-r--r-- | sql/structs.h | 10 |
11 files changed, 502 insertions, 4 deletions
diff --git a/mysql-test/r/sp-error.result b/mysql-test/r/sp-error.result index 6fbe2d62fd7..aee3a614391 100644 --- a/mysql-test/r/sp-error.result +++ b/mysql-test/r/sp-error.result @@ -1504,7 +1504,7 @@ 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 MariaDB 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 MariaDB server version for the right syntax to use near '.inexistent()' at line 1 +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '..inexistent()' at line 1 USE test; create function f1() returns int begin diff --git a/mysql-test/suite/compat/oracle/r/sp-code.result b/mysql-test/suite/compat/oracle/r/sp-code.result index 4f2dc58d6c8..84db01a37bb 100644 --- a/mysql-test/suite/compat/oracle/r/sp-code.result +++ b/mysql-test/suite/compat/oracle/r/sp-code.result @@ -560,3 +560,70 @@ Pos Instruction 8 preturn 9 hpop 1 DROP PROCEDURE p1; +# Testing FOR loop statement +CREATE FUNCTION f1 (a INT, b INT) RETURN INT +AS +total INT := 0; +BEGIN +FOR i IN 1 .. a +LOOP +total:= total + i; +IF i = b THEN +EXIT; +END IF; +END LOOP; +RETURN total; +END +/ +SHOW FUNCTION CODE f1; +Pos Instruction +0 set total@2 0 +1 set i@3 1 +2 set [upper_bound]@4 a@0 +3 jump_if_not 9(9) i@3 <= [upper_bound]@4 +4 set total@2 total@2 + i@3 +5 jump_if_not 7(7) i@3 = b@1 +6 jump 9 +7 set i@3 i@3 + 1 +8 jump 3 +9 freturn 3 total@2 +SELECT f1(3, 100) FROM DUAL; +f1(3, 100) +6 +SELECT f1(3, 2) FROM DUAL; +f1(3, 2) +3 +DROP FUNCTION f1; +CREATE FUNCTION f1 (a INT, b INT) RETURN INT +AS +total INT := 0; +BEGIN +FOR i IN REVERSE a..1 +LOOP +total:= total + i; +IF i = b THEN +EXIT; +END IF; +END LOOP; +RETURN total; +END +/ +SHOW FUNCTION CODE f1; +Pos Instruction +0 set total@2 0 +1 set i@3 a@0 +2 set [upper_bound]@4 1 +3 jump_if_not 9(9) i@3 >= [upper_bound]@4 +4 set total@2 total@2 + i@3 +5 jump_if_not 7(7) i@3 = b@1 +6 jump 9 +7 set i@3 i@3 + -1 +8 jump 3 +9 freturn 3 total@2 +SELECT f1(3, 100) FROM DUAL; +f1(3, 100) +6 +SELECT f1(3, 2) FROM DUAL; +f1(3, 2) +5 +DROP FUNCTION f1; diff --git a/mysql-test/suite/compat/oracle/r/sp.result b/mysql-test/suite/compat/oracle/r/sp.result index 50c74ae2d91..ee22dbbb5ec 100644 --- a/mysql-test/suite/compat/oracle/r/sp.result +++ b/mysql-test/suite/compat/oracle/r/sp.result @@ -774,3 +774,89 @@ SELECT @v; @v 6 DROP PROCEDURE p1; +# Testing the FOR loop statement +CREATE FUNCTION f1 (lower_bound INT, upper_bound INT, lim INT) RETURN INT +AS +total INT := 0; +BEGIN +FOR i IN lower_bound . . upper_bound +LOOP +NULL +END LOOP; +RETURN total; +END; +/ +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '. upper_bound +LOOP +NULL +END LOOP; +RETURN total; +END' at line 2 +CREATE FUNCTION f1 (lower_bound INT, upper_bound INT, lim INT) RETURN INT +AS +total INT := 0; +BEGIN +FOR i IN lower_bound .. upper_bound +LOOP +total:= total + i; +IF i = lim THEN +EXIT; +END IF; +-- Bounds are calculated only once. +-- The below assignments have no effect on the loop condition +lower_bound:= 900; +upper_bound:= 1000; +END LOOP; +RETURN total; +END; +/ +SELECT f1(1, 3, 100) FROM DUAL; +f1(1, 3, 100) +6 +SELECT f1(1, 3, 2) FROM DUAL; +f1(1, 3, 2) +3 +DROP FUNCTION f1; +CREATE FUNCTION f1 RETURN INT +AS +total INT := 0; +BEGIN +FOR i IN 1 .. 5 +LOOP +total:= total + 1000; +FOR j IN 1 .. 5 +LOOP +total:= total + 1; +IF j = 3 THEN +EXIT; -- End the internal loop +END IF; +END LOOP; +END LOOP; +RETURN total; +END; +/ +SELECT f1() FROM DUAL; +f1() +5015 +DROP FUNCTION f1; +CREATE FUNCTION f1 (a INT, b INT) RETURN INT +AS +total INT := 0; +BEGIN +FOR i IN REVERSE a..1 +LOOP +total:= total + i; +IF i = b THEN +EXIT; +END IF; +END LOOP; +RETURN total; +END +/ +SELECT f1(3, 100) FROM DUAL; +f1(3, 100) +6 +SELECT f1(3, 2) FROM DUAL; +f1(3, 2) +5 +DROP FUNCTION f1; diff --git a/mysql-test/suite/compat/oracle/t/sp-code.test b/mysql-test/suite/compat/oracle/t/sp-code.test index 082d6bc4249..a7033f640ff 100644 --- a/mysql-test/suite/compat/oracle/t/sp-code.test +++ b/mysql-test/suite/compat/oracle/t/sp-code.test @@ -441,3 +441,47 @@ END; DELIMITER ;/ SHOW PROCEDURE CODE p1; DROP PROCEDURE p1; + + +--echo # Testing FOR loop statement +DELIMITER /; +CREATE FUNCTION f1 (a INT, b INT) RETURN INT +AS + total INT := 0; +BEGIN + FOR i IN 1 .. a + LOOP + total:= total + i; + IF i = b THEN + EXIT; + END IF; + END LOOP; + RETURN total; +END +/ +DELIMITER ;/ +SHOW FUNCTION CODE f1; +SELECT f1(3, 100) FROM DUAL; +SELECT f1(3, 2) FROM DUAL; +DROP FUNCTION f1; + +DELIMITER /; +CREATE FUNCTION f1 (a INT, b INT) RETURN INT +AS + total INT := 0; +BEGIN + FOR i IN REVERSE a..1 + LOOP + total:= total + i; + IF i = b THEN + EXIT; + END IF; + END LOOP; + RETURN total; +END +/ +DELIMITER ;/ +SHOW FUNCTION CODE f1; +SELECT f1(3, 100) FROM DUAL; +SELECT f1(3, 2) FROM DUAL; +DROP FUNCTION f1; diff --git a/mysql-test/suite/compat/oracle/t/sp.test b/mysql-test/suite/compat/oracle/t/sp.test index 22258b24e02..bdf12cdbb7d 100644 --- a/mysql-test/suite/compat/oracle/t/sp.test +++ b/mysql-test/suite/compat/oracle/t/sp.test @@ -843,3 +843,91 @@ SET @v=0; CALL p1(@v); SELECT @v; DROP PROCEDURE p1; + + +--echo # Testing the FOR loop statement + +DELIMITER /; +--error ER_PARSE_ERROR +CREATE FUNCTION f1 (lower_bound INT, upper_bound INT, lim INT) RETURN INT +AS + total INT := 0; +BEGIN + FOR i IN lower_bound . . upper_bound + LOOP + NULL + END LOOP; + RETURN total; +END; +/ +DELIMITER ;/ + + +DELIMITER /; +CREATE FUNCTION f1 (lower_bound INT, upper_bound INT, lim INT) RETURN INT +AS + total INT := 0; +BEGIN + FOR i IN lower_bound .. upper_bound + LOOP + total:= total + i; + IF i = lim THEN + EXIT; + END IF; + -- Bounds are calculated only once. + -- The below assignments have no effect on the loop condition + lower_bound:= 900; + upper_bound:= 1000; + END LOOP; + RETURN total; +END; +/ +DELIMITER ;/ +SELECT f1(1, 3, 100) FROM DUAL; +SELECT f1(1, 3, 2) FROM DUAL; +DROP FUNCTION f1; + + +DELIMITER /; +CREATE FUNCTION f1 RETURN INT +AS + total INT := 0; +BEGIN + FOR i IN 1 .. 5 + LOOP + total:= total + 1000; + FOR j IN 1 .. 5 + LOOP + total:= total + 1; + IF j = 3 THEN + EXIT; -- End the internal loop + END IF; + END LOOP; + END LOOP; + RETURN total; +END; +/ +DELIMITER ;/ +SELECT f1() FROM DUAL; +DROP FUNCTION f1; + + +DELIMITER /; +CREATE FUNCTION f1 (a INT, b INT) RETURN INT +AS + total INT := 0; +BEGIN + FOR i IN REVERSE a..1 + LOOP + total:= total + i; + IF i = b THEN + EXIT; + END IF; + END LOOP; + RETURN total; +END +/ +DELIMITER ;/ +SELECT f1(3, 100) FROM DUAL; +SELECT f1(3, 2) FROM DUAL; +DROP FUNCTION f1; diff --git a/sql/field.h b/sql/field.h index caacb5f00b8..89b209379dd 100644 --- a/sql/field.h +++ b/sql/field.h @@ -3850,6 +3850,19 @@ public: interval_list.empty(); } + Column_definition(const char *name, enum_field_types type): + field_name(name), + comment(null_lex_str), + on_update(0), sql_type(type), length(0), decimals(0), + flags(0), pack_length(0), key_length(0), unireg_check(Field::NONE), + interval(0), charset(&my_charset_bin), + srid(0), geom_type(Field::GEOM_GEOMETRY), + option_list(NULL), + vcol_info(0), default_value(0), check_constraint(0) + { + interval_list.empty(); + } + Column_definition(THD *thd, Field *field, Field *orig_field); void set_attributes(const Lex_field_type_st &type, CHARSET_INFO *cs); void create_length_to_internal_length(void); diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 093ed597b0a..a47a0fe0a46 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -2007,8 +2007,13 @@ static int lex_one_token(YYSTYPE *yylval, THD *thd) /* Actually real shouldn't start with . but allow them anyhow */ case MY_LEX_REAL_OR_POINT: - if (my_isdigit(cs,lip->yyPeek())) + if (my_isdigit(cs,(c= lip->yyPeek()))) state = MY_LEX_REAL; // Real + else if (c == '.') + { + lip->yySkip(); + return DOT_DOT_SYM; + } else { state= MY_LEX_IDENT_SEP; // return '.' @@ -5243,6 +5248,123 @@ bool LEX::sp_variable_declarations_finalize(THD *thd, int nvars, } +/********************************************************************** + The FOR LOOP statement + + This syntax: + FOR i IN lower_bound .. upper_bound + LOOP + statements; + END LOOP; + + is translated into: + + DECLARE + i INT := lower_bound; + j INT := upper_bound; + BEGIN + WHILE i <= j + LOOP + statements; + i:= i + 1; + END LOOP; + END; +*/ + + +sp_variable *LEX::sp_add_for_loop_variable(THD *thd, const LEX_STRING name, + Item *value) +{ + sp_variable *spvar= spcont->add_variable(thd, name); + spcont->declare_var_boundary(1); + spvar->field_def= Column_definition(spvar->name.str, MYSQL_TYPE_LONGLONG); + if (sp_variable_declarations_finalize(thd, 1, spvar->field_def, value)) + return NULL; + return spvar; +} + + +/** + Generate a code for a FOR loop condition: + - Make Item_splocal for the FOR loop index variable + - Make Item_splocal for the FOR loop upper bound variable + - Make a comparison function item on top of these two variables +*/ +bool LEX::sp_for_loop_condition(THD *thd, const Lex_for_loop_st &loop) +{ + Item_splocal *args[2]; + for (uint i= 0 ; i < 2; i++) + { + 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->sql_type()); + if (args[i] == NULL) + return true; +#ifndef DBUG_OFF + args[i]->m_sp= sphead; +#endif + } + + Item *expr= loop.m_direction > 0 ? + (Item *) new (thd->mem_root) Item_func_le(thd, args[0], args[1]) : + (Item *) new (thd->mem_root) Item_func_ge(thd, args[0], args[1]); + return !expr || sp_while_loop_expression(thd, expr); +} + + +/** + Generate the FOR LOOP condition code in its own lex +*/ +bool LEX::sp_for_loop_index_and_bounds(THD *thd, const Lex_for_loop_st &loop) +{ + sphead->reset_lex(thd); + if (thd->lex->sp_for_loop_condition(thd, loop)) + return true; + return thd->lex->sphead->restore_lex(thd); +} + + +/** + Generate a code for a FOR loop index increment +*/ +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, + loop.m_index->sql_type()); + if (splocal == NULL) + return true; +#ifndef DBUG_OFF + splocal->m_sp= sphead; +#endif + Item_int *inc= new (thd->mem_root) Item_int(thd, loop.m_direction); + if (!inc) + return true; + Item *expr= new (thd->mem_root) Item_func_plus(thd, splocal, inc); + if (!expr || set_local_variable(loop.m_index, expr)) + return true; + return false; +} + + +bool LEX::sp_for_loop_finalize(THD *thd, const Lex_for_loop_st &loop) +{ + sphead->reset_lex(thd); + + // Generate FOR LOOP index increment in its own lex + DBUG_ASSERT(this != thd->lex); + if (thd->lex->sp_for_loop_increment(thd, loop) || + thd->lex->sphead->restore_lex(thd)) + return true; + + // Generate a jump to the beginning of the loop + DBUG_ASSERT(this == thd->lex); + return sp_while_loop_finalize(thd); +} + + +/***************************************************************************/ + bool LEX::sp_declare_cursor(THD *thd, const LEX_STRING name, LEX *cursor_stmt) { uint offp; diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 041a0c8f130..0d834c2e1d3 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -2633,6 +2633,10 @@ private: bool sp_change_context(THD *thd, const sp_pcontext *ctx, bool exclusive); bool sp_exit_block(THD *thd, sp_label *lab); bool sp_exit_block(THD *thd, sp_label *lab, Item *when); + + bool sp_for_loop_condition(THD *thd, const Lex_for_loop_st &loop); + bool sp_for_loop_increment(THD *thd, const Lex_for_loop_st &loop); + public: inline bool is_arena_for_set_stmt() {return arena_for_set_stmt != 0;} bool set_arena_for_set_stmt(Query_arena *backup); @@ -3167,6 +3171,16 @@ public: bool sp_while_loop_expression(THD *thd, Item *expr); bool sp_while_loop_finalize(THD *thd); + sp_variable *sp_add_for_loop_variable(THD *thd, const LEX_STRING name, + Item *value); + sp_variable *sp_add_for_loop_upper_bound(THD *thd, Item *value) + { + LEX_STRING name= { C_STRING_WITH_LEN("[upper_bound]") }; + return sp_add_for_loop_variable(thd, name, value); + } + bool sp_for_loop_index_and_bounds(THD *thd, const Lex_for_loop_st &loop); + bool sp_for_loop_finalize(THD *thd, const Lex_for_loop_st &loop); + // Check if "KEY IF NOT EXISTS name" used outside of ALTER context bool check_add_key(DDL_options_st ddl) { diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 31fe5cf617e..f0e36bf3784 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -803,6 +803,7 @@ Virtual_column_info *add_virtual_expression(THD *thd, Item *expr) Lex_cast_type_st Lex_cast_type; Lex_field_type_st Lex_field_type; Lex_dyncol_type_st Lex_dyncol_type; + Lex_for_loop_st for_loop; /* pointers */ Create_field *create_field; @@ -1060,6 +1061,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %token DOUBLE_SYM /* SQL-2003-R */ %token DO_DOMAIN_IDS_SYM %token DO_SYM +%token DOT_DOT_SYM %token DROP /* SQL-2003-R */ %token DUAL_SYM %token DUMPFILE diff --git a/sql/sql_yacc_ora.yy b/sql/sql_yacc_ora.yy index 0d1977e59df..863066228aa 100644 --- a/sql/sql_yacc_ora.yy +++ b/sql/sql_yacc_ora.yy @@ -177,6 +177,7 @@ void ORAerror(THD *thd, const char *s) Lex_cast_type_st Lex_cast_type; Lex_field_type_st Lex_field_type; Lex_dyncol_type_st Lex_dyncol_type; + Lex_for_loop_st for_loop; /* pointers */ Create_field *create_field; @@ -260,10 +261,10 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %parse-param { THD *thd } %lex-param { THD *thd } /* - Currently there are 102 shift/reduce conflicts. + Currently there are 103 shift/reduce conflicts. We should not introduce new conflicts any more. */ -%expect 102 +%expect 103 /* Comments for TOKENS. @@ -434,6 +435,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %token DOUBLE_SYM /* SQL-2003-R */ %token DO_DOMAIN_IDS_SYM %token DO_SYM +%token DOT_DOT_SYM %token DROP /* SQL-2003-R */ %token DUAL_SYM %token DUMPFILE @@ -1278,6 +1280,8 @@ END_OF_INPUT %type <lex> sp_cursor_stmt %type <spname> sp_name %type <spvar> sp_param_name sp_param_name_and_type +%type <for_loop> sp_for_loop_index_and_bounds +%type <num> opt_sp_for_loop_direction %type <spvar_mode> sp_opt_inout %type <index_hint> index_hint_type %type <num> index_hint_clause normal_join inner_join @@ -3471,6 +3475,32 @@ exception_handler: } ; +/* This adds one shift/reduce conflict */ +opt_sp_for_loop_direction: + /* Empty */ { $$= 1; } + | REVERSE_SYM { $$= -1; } + ; + +sp_for_loop_index_and_bounds: + ident_directly_assignable + { + Lex->sphead->reset_lex(thd); + } + IN_SYM opt_sp_for_loop_direction expr + { + if (!($<spvar>$= Lex->sp_add_for_loop_variable(thd, $1, $5))) + MYSQL_YYABORT; + Lex->sphead->reset_lex(thd); + } + DOT_DOT_SYM expr + { + $$.m_index= $<spvar>6; + if (!($$.m_upper_bound= Lex->sp_add_for_loop_upper_bound(thd, $8))) + MYSQL_YYABORT; + $$.m_direction= $4; + } + ; + loop_body: sp_proc_stmts1 END LOOP_SYM { @@ -3574,6 +3604,28 @@ sp_unlabeled_control: { Lex->sp_pop_loop_empty_label(thd); } + | FOR_SYM + { + // See "The FOR LOOP statement" comments in sql_lex.cc + Lex->sp_block_init(thd); // The outer DECLARE..BEGIN..END block + } + sp_for_loop_index_and_bounds + { + if (Lex->sp_push_loop_empty_label(thd)) // The inner WHILE block + MYSQL_YYABORT; + if (Lex->sp_for_loop_index_and_bounds(thd, $3)) + MYSQL_YYABORT; + } + LOOP_SYM + sp_proc_stmts1 + END LOOP_SYM + { + if (Lex->sp_for_loop_finalize(thd, $3)) + MYSQL_YYABORT; + Lex->sp_pop_loop_empty_label(thd); // The inner WHILE block + if (Lex->sp_block_finalize(thd)) // The outer DECLARE..BEGIN..END + MYSQL_YYABORT; + } | REPEAT_SYM { if (Lex->sp_push_loop_empty_label(thd)) diff --git a/sql/structs.h b/sql/structs.h index f0c3926e424..cf1b2ff4e94 100644 --- a/sql/structs.h +++ b/sql/structs.h @@ -668,4 +668,14 @@ public: } }; + +struct Lex_for_loop_st +{ +public: + class sp_variable *m_index; + class sp_variable *m_upper_bound; + int m_direction; +}; + + #endif /* STRUCTS_INCLUDED */ |