summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexander Barkov <bar@mariadb.org>2016-08-24 07:39:04 +0400
committerAlexander Barkov <bar@mariadb.org>2017-04-05 15:02:46 +0400
commitc570636ba278e935701ca917af966630d38556ab (patch)
tree856f9cccf65da0ffae4b70def8452fcf25f23182
parent71a0a12e61818d68201dfc985a64b58c19cfef44 (diff)
downloadmariadb-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.result2
-rw-r--r--mysql-test/suite/compat/oracle/r/sp-code.result67
-rw-r--r--mysql-test/suite/compat/oracle/r/sp.result86
-rw-r--r--mysql-test/suite/compat/oracle/t/sp-code.test44
-rw-r--r--mysql-test/suite/compat/oracle/t/sp.test88
-rw-r--r--sql/field.h13
-rw-r--r--sql/sql_lex.cc124
-rw-r--r--sql/sql_lex.h14
-rw-r--r--sql/sql_yacc.yy2
-rw-r--r--sql/sql_yacc_ora.yy56
-rw-r--r--sql/structs.h10
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 */