PL/SQL parser (MDEV-10142)

[MDEV-12133] sql_mode=ORACLE: table%ROWTYPE in variable declarations Created: 2017-02-27  Updated: 2018-08-31  Resolved: 2017-04-04

Status: Closed
Project: MariaDB Server
Component/s: Parser, Stored routines
Affects Version/s: 10.3
Fix Version/s: 10.3.0

Type: Technical task Priority: Major
Reporter: Alexander Barkov Assignee: Alexander Barkov
Resolution: Fixed Votes: 0
Labels: Compatibility

Issue Links:
Blocks
blocks MDEV-12011 sql_mode=ORACLE: cursor%ROWTYPE in va... Closed
blocks MDEV-12461 TYPE OF and ROW TYPE OF anchored data... Closed
is blocked by MDEV-10914 ROW data type for stored routine vari... Closed
Relates
relates to MDEV-13581 ROW TYPE OF t1 and t1%ROWTYPE for rou... Closed
Sprint: 10.2.2-3, 10.2.2-1, 10.2.2-2, 10.2.2-4, 10.1.18

 Description   

This task will implement Oracle-stype table%ROWTYPE declarations, for sql_mode=ORACLE.

Example:

rec t1%ROWTYPE;

The record variable rec can store the entire row of the data fetched from the table t1. There is no a need to specify column names and data types. They're automatically copied from t1.

Under scope of this task, we'll implement table%ROWTYPE for routine variables. Using table%ROWTYPE for routine parameters and function return values will be done separately.

A complete working example:

SET sql_mode=ORACLE;
DROP TABLE t1;
CREATE TABLE t1 (a INT, b VARCHAR(32));
INSERT INTO t1 VALUES (10,'b10');
DROP PROCEDURE p1;
DELIMITER $$
CREATE PROCEDURE p1 AS
  rec t1%ROWTYPE; 
  CURSOR c IS SELECT * FROM t1;
BEGIN
  OPEN c;
  LOOP
    FETCH c INTO rec;
    EXIT WHEN c%NOTFOUND;
    SELECT 'rec=(' || rec.a ||','|| rec.b||')' AS c FROM dual;
  END LOOP;
  CLOSE c;
END;
$$
DELIMITER ;
CALL p1();

rec=(10,b10)

Data types will be resolved at the very beginning of a routine execution, in sp_rcontext::create(). If the tables referenced in %ROWTYPE declarations are altered inside the routine, this will not affect structures of the referencing %ROWTYPE variables. In the below example the variable rec will have only two fields a and b, it will not have the field c. The fact that rec is declared after the ALTER statement does not matter. This implementations will be close to Oracle, who determines all data types at CREATE PROCEDURE time.

table%ROWTYPE and implicit ROW variables will be mutually assignable if they have the same number of fields. Note, Oracle has stricter rules, it also checks field names, but in a very strange way. See "Oracle implementation details". We won't check field names. Assignment will be done from left to right (the N'th source field is assigned to the N'th destination field).

In the below example all three variables rec0, rec1, rec2 will be mutually assignable, because they have the same number of fields:

SET sql_mode=ORACLE;
DROP TABLE IF EXISTS t1, t2;
CREATE TABLE t1 (a INT, b VARCHAR(32));
CREATE TABLE t2 (c INT, d VARCHAR(32));
DROP PROCEDURE p1;
DELIMITER $$
CREATE PROCEDURE p1 AS
  rec0 ROW(x INT,y INT);
  rec1 t1%ROWTYPE;
  rec2 t2%ROWTYPE;
BEGIN
  rec0:=rec1;
  rec0:=rec2;
  rec1:=rec0;
  rec1:=rec2;
  rec2:=rec0;
  rec2:=rec1;
END;
$$
DELIMITER ;
CALL p1();

It will be possible to pass a table%ROWTYPE variable into a routine with a compatible explicit ROW argument:

SET sql_mode=ORACLE;
DROP TABLE IF EXISTS t1;
CREATE TABLE t1 (a INT, b VARCHAR(32));
DROP PROCEDURE IF EXISTS p1;
DROP PROCEDURE IF EXISTS p2;
DELIMITER $$
CREATE PROCEDURE p1(a ROW(a INT, b VARCHAR(20)))
AS
BEGIN
  SELECT a.a, a.b;
END;
$$
CREATE PROCEDURE p2 AS
  rec1 t1%ROWTYPE:=ROW(10,'bb');
BEGIN
  CALL p1(rec1);
END;
$$
DELIMITER ;
CALL p2();

+------+------+
| a.a  | a.b  |
+------+------+
| 10   | bb   |
+------+------+



 Comments   
Comment by Alexander Barkov [ 2017-02-27 ]

Oracle implementation details

FETCH assigns fields of table%ROWTYPE variables from left to right. Field names are not important.

SET SERVEROUTPUT ON;
DROP TABLE t1;
DROP TABLE t2;
CREATE TABLE t1 (a VARCHAR(10), b VARCHAR(10));
INSERT INTO t1 VALUES ('A','B');
CREATE TABLE t2 (x VARCHAR(10), y VARCHAR(10));
DROP PROCEDURE p1;
CREATE PROCEDURE p1 AS
  rec1 t1%ROWTYPE;
  rec2 t2%ROWTYPE;
  CURSOR cur IS SELECT * FROM t1;
BEGIN
  OPEN cur;
  FETCH cur INTO rec1;
  CLOSE cur;
  DBMS_OUTPUT.PUT_LINE(rec1.a||' '||rec1.b);
 
  OPEN cur;
  FETCH cur INTO rec2;
  CLOSE cur;
  DBMS_OUTPUT.PUT_LINE(rec2.x||' '||rec2.y);
END;
/
CALL p1();

A B
A B

Two table%ROWTYPE variables are mutually assignable if they have the same set of equally named fields, but the order of the fields is not important. Strangely, fields are assigned by their ordinal position, from left to right:

SET SERVEROUTPUT ON;
DROP TABLE t1;
DROP TABLE t2;
CREATE TABLE t1 (a VARCHAR(10), b VARCHAR(10));
INSERT INTO t1 VALUES ('A','B');
CREATE TABLE t2 (b VARCHAR(10), a VARCHAR(10));
DROP PROCEDURE p1;
CREATE PROCEDURE p1 AS
  rec1 t1%ROWTYPE;
  rec2 t2%ROWTYPE;
  CURSOR cur IS SELECT * FROM t1;
BEGIN
  OPEN cur;
  FETCH cur INTO rec1;
  CLOSE cur;
  DBMS_OUTPUT.PUT_LINE(rec1.a||' '||rec1.b);
  rec2:=rec1;
  DBMS_OUTPUT.PUT_LINE(rec2.a||' '||rec2.b);
END;
/
CALL p1();

A B
B A

Comment by Alexander Barkov [ 2017-03-10 ]

Pushed to bb-10.2-compatibility

Comment by Michael Widenius [ 2017-03-11 ]

Review not done

Comment by Michael Widenius [ 2017-03-11 ]

Review done, some minor changes needed + some function comments

Comment by Alexander Barkov [ 2017-04-04 ]

Addressed Monty's review suggestions.
Pushed a cleanup patch to bb-10.2-compatibility.

Generated at Thu Feb 08 07:55:22 UTC 2024 using Jira 8.20.16#820016-sha1:9d11dbea5f4be3d4cc21f03a88dd11d8c8687422.