Commit c570636b authored by Alexander Barkov's avatar Alexander Barkov

MDEV-10580 sql_mode=ORACLE: FOR loop statement

Adding non-labeled FOR LOOP statement.
parent 71a0a12e
......@@ -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
......
......@@ -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;
......@@ -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;
......@@ -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;
......@@ -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;
......@@ -3850,6 +3850,19 @@ class Column_definition: public Sql_alloc
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);
......
......@@ -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;
......
......@@ -2633,6 +2633,10 @@ struct LEX: public Query_tables_list
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 @@ struct LEX: public Query_tables_list
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)
{
......
......@@ -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
......
......@@ -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))
......
......@@ -668,4 +668,14 @@ class Lex_spblock: public Lex_spblock_st
}
};
struct Lex_for_loop_st
{
public:
class sp_variable *m_index;
class sp_variable *m_upper_bound;
int m_direction;
};
#endif /* STRUCTS_INCLUDED */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment