[MDEV-17253] Oracle compatibility: The REVERSE key word for FOR loop behaves incorrectly Created: 2018-09-20  Updated: 2018-12-26  Resolved: 2018-12-26

Status: Closed
Project: MariaDB Server
Component/s: Parser
Affects Version/s: 10.3.9
Fix Version/s: 10.3.11

Type: Bug Priority: Critical
Reporter: Daniel Lee (Inactive) Assignee: Alexander Barkov
Resolution: Fixed Votes: 0
Labels: None
Environment:

Centos 7



 Description   

Release tested: 10.3.9

The following loop did not produce any output. It worked correctly without the REVERSE key word.

SET SQL_MODE=ORACLE;
  delimiter //
  #
  DECLARE
     totalprice DECIMAL(12,2):=NULL;
     loop_start INTEGER := 1;
  BEGIN
     FOR idx IN REVERSE loop_start..10 LOOP
        SELECT idx;
     END LOOP;
  END;
  //

With the REVERSE key word, switching the lower and upper bound values also worked:

FOR idx IN REVERSE 10..loop_start LOOP

However, this behavior is different from Oracle's documentation below.

---------
If REVERSE is specified:

If upper_bound is less than lower_bound, the statements do not run, and control transfers to the statement after the FOR LOOP statement.
---------



 Comments   
Comment by Alexander Barkov [ 2018-09-21 ]

Sorry, can you clarify please?
In the above script you have a REVERSE loop and direct bound order. It performs no iterations and produces no output.
This looks correct.

I just checked all four combinations and I think they give correct results:

Direct loop, direct bound order (3 iterations as expected)

SET SQL_MODE=ORACLE;
delimiter //
#
DECLARE
  totalprice DECIMAL(12,2):=NULL;
  loop_start INTEGER := 1;
BEGIN
  FOR idx IN loop_start..3 LOOP
    SELECT idx;
  END LOOP;
END;
//
delimiter ;

+-----+
| idx |
+-----+
|   1 |
+-----+
1 row in set (0.01 sec)
 
+-----+
| idx |
+-----+
|   2 |
+-----+
1 row in set (0.01 sec)
 
+-----+
| idx |
+-----+
|   3 |
+-----+
1 row in set (0.01 sec)
 
Query OK, 0 rows affected (0.00 sec)

Direct loop, reverse bound order (no iterations as expected)

SET SQL_MODE=ORACLE;
delimiter //
#
DECLARE
  totalprice DECIMAL(12,2):=NULL;
  loop_start INTEGER := 1;
BEGIN
  FOR idx IN 3..loop_start LOOP
    SELECT idx;
  END LOOP;
END;
//
delimiter ;

Query OK, 0 rows affected (0.00 sec)

Reverse loop, reverse bound order (3 iterations as expected)

SET SQL_MODE=ORACLE;
delimiter //
#
DECLARE
  totalprice DECIMAL(12,2):=NULL;
  loop_start INTEGER := 1;
BEGIN
  FOR idx IN REVERSE 3..loop_start LOOP
    SELECT idx;
  END LOOP;
END;
//
delimiter ;

+-----+
| idx |
+-----+
|   3 |
+-----+
1 row in set (0.00 sec)
 
+-----+
| idx |
+-----+
|   2 |
+-----+
1 row in set (0.00 sec)
 
+-----+
| idx |
+-----+
|   1 |
+-----+
1 row in set (0.00 sec)
 
Query OK, 0 rows affected (0.00 sec)

Reverse loop, direct bound order (no iterations as expected)

SET SQL_MODE=ORACLE;
delimiter //
#
DECLARE
  totalprice DECIMAL(12,2):=NULL;
  loop_start INTEGER := 1;
BEGIN
  FOR idx IN REVERSE loop_start..3 LOOP
    SELECT idx;
  END LOOP;
END;
//
delimiter ;

Query OK, 0 rows affected (0.00 sec)

Comment by Daniel Lee (Inactive) [ 2018-09-21 ]

The following is from the oracle documentation. If I am reading it correctly, your example 3 above should not produce results, while example 4 should.

Thx

https://docs.oracle.com/database/121/LNPLS/for_loop_statement.htm#LNPLS1515

[ REVERSE ] lower_bound .. upper_bound

lower_bound and upper_bound must evaluate to numbers (see "Lower Bound and Upper Bound"). PL/SQL evaluates lower_bound and upper_bound once, when the FOR LOOP statement is entered, and stores them as temporary PLS_INTEGER values, rounding them to the nearest integer if necessary.

If lower_bound equals upper_bound, the statements run only once.

If lower_bound does not equal upper_bound when the FOR LOOP statement begins to run, then:

If REVERSE is omitted:

If lower_bound is greater than upper_bound, the statements do not run, and control transfers to the statement after the FOR LOOP statement.

Otherwise, lower_bound is assigned to index, the statements run, and control returns to the top of the loop, where index is compared to upper_bound. If index is less than upper_bound, index is incremented by one, the statements run again, and control returns to the top of the loop. When index is greater than upper_bound, control transfers to the statement after the FOR LOOP statement.

If REVERSE is specified:

If upper_bound is less than lower_bound, the statements do not run, and control transfers to the statement after the FOR LOOP statement.

Otherwise, upper_bound is assigned to index, the statements run, and control returns to the top of the loop, where index is compared to lower_bound. If index is greater than lower_bound, index is decremented by one, the statements run again, and control returns to the top of the loop. When index is less than lower_bound, control transfers to the statement after the FOR LOOP statement.

Comment by Alexander Barkov [ 2018-09-21 ]

Right, Oracle returns the following output:

SET SERVEROUTPUT ON;
DROP PROCEDURE p1;
CREATE PROCEDURE p1 AS
  loop_start INTEGER := 1;
BEGIN
  FOR idx IN REVERSE 3..loop_start LOOP
    DBMS_OUTPUT.PUT_LINE('idx='||idx);
  END LOOP;
END;
/
CALL p1();

Call completed.

SET SERVEROUTPUT ON;
DROP PROCEDURE p1;
CREATE PROCEDURE p1 AS
  loop_start INTEGER := 1;
BEGIN
  FOR idx IN REVERSE loop_start..3 LOOP
    DBMS_OUTPUT.PUT_LINE('idx='||idx);
  END LOOP;
END;
/
CALL p1();

idx=3
idx=2
idx=1
Call completed.

Sorry, I overlooked this.

Will fix MariaDB accordingly. Thanks for clarification.

Comment by Alexander Barkov [ 2018-12-26 ]

Forgot to close. Was fixed in 10.3.11 on Nov 13.

Generated at Thu Feb 08 08:35:04 UTC 2024 using Jira 8.20.16#820016-sha1:9d11dbea5f4be3d4cc21f03a88dd11d8c8687422.