Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions EN/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
*** xref:master/6.3.1.adoc[like]
*** xref:master/6.3.3.adoc[RowID]
*** xref:master/6.3.2.adoc[OUT Parameter]
*** xref:master/6.3.4.adoc[%Type & %Rowtype]
** xref:master/6.4.adoc[GB18030 Character Set]
* List of Oracle compatible features
** xref:master/7.1.adoc[1、Ivorysql frame design]
Expand All @@ -40,6 +41,7 @@
** xref:master/7.13.adoc[13、Invisible Columns]
** xref:master/7.14.adoc[14、RowID Column]
** xref:master/7.15.adoc[15、OUT Parameter]
** xref:master/7.16.adoc[16、%Type & %Rowtype]
* xref:master/8.adoc[Community contribution]
* xref:master/9.adoc[Tool Reference]
* xref:master/10.adoc[FAQ]
102 changes: 102 additions & 0 deletions EN/modules/ROOT/pages/master/6.3.4.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
:sectnums:
:sectnumlevels: 5

:imagesdir: ./_images

= %ROWTYPE、%TYPE

== Purpose

IvorySQL provides Oracle-compatible PL/SQL data type functionality, including %TYPE and %ROWTYPE.

== Implementation description

=== If the reference changes, variables declared with %TYPE or %ROWTYPE will change accordingly

This is a passive process. The current implementation records the dependency between functions (stored procedures) and tablename.columnname. When the referenced type changes, the function (stored procedure) cache is invalidated based on this dependency. As a result, when the function is called, it undergoes forced compilation, ensuring that the function retrieves the latest variable type.

A field named `prostatus` is added to the system table `pg_proc` to indicate the status of a function (stored procedure), with three possible states: validate (v), invalidate (i), and N/A (n). After a function is successfully compiled, this status is set to valid.

When parsing function content, the functions `plisql_parse_cwordtype`, `plisql_parse_wordrowtype`, and `plisql_parse_cwordrowtype` identify objects referenced by %TYPE and %ROWTYPE, record them in the `plisql_referenced_object` linked list, and finally add them to the `pg_depend` system table.

Add a new dependency type, DEPENDENCY_TYPE = 't', to represent %TYPE or %ROWTYPE dependencies. When adding object reference relationships to the `pg_depend` system table, set the dependency type to 't'.

When performing operations on a table (such as modifying the table type or deleting the table), check the `pg_depend` system table. If there exists a dependency type `deptype='t'` and the dependent object is a function, call the `plisql_free_function` function to clear the function cache and update the function status `prostatus` in the `pg_proc` system table to N/A (n).


=== Variables declared with %TYPE inherit the constraints of the referenced variable.

Add bool notnull member in structure PLiSQL_type;

```
/*
* Postgres data type
*/
typedef struct PLiSQL_type
{
bool notnull; /* the type is built by variable%type,
* isnull or notnull of the variable */
```

In the `plisql_parse_wordtype` or `plisql_parse_cwordtype` functions responsible for parsing %TYPE type functions, determine if the referenced variable type specifies a NOT NULL constraint, and set the `bool notnull` attribute of the returned `datatype` member to `true`. In the `decl_statement` grammar of the `pl_gram.y` file, assign the `notnull` attribute of the `PLiSQL_variable *var` variable based on the `bool notnull` member of `PLiSQL_type`. This way, the constraints of the referenced variable are inherited.


=== Use table_name%ROWTYPE or view_name%ROWTYPE as the parameter type of a function / stored procedure or the return type of a function

Add support of %ROWTYPE for func_type in ora_gram.y

```
| type_function_name attrs '%' ROWTYPE
{
$$ = makeTypeNameFromNameList(lcons(makeString($1), $2));
$$->row_type = true;
$$->location = @1;
}
```

Add a member `bool row_type` to the `TypeName` struct to indicate whether %ROWTYPE is specified.

```
typedef struct TypeName
{
bool pct_type; /* %TYPE specified? */
bool row_type; /* %ROWTYPE specified? */
```

In the `LookupTypeName` function, if the `row_type` member of `TypeName` is `TRUE`, obtain the schema name and table name from the `names` member of `TypeName`, and then retrieve the table's `typeoid`.

=== Enhancement to INSERT statement

In `ora_gram.y`, add new syntax to support `VALUES` without requiring parentheses '(' afterward.

```
values_clause_no_parens:
VALUES columnref
{
SelectStmt *n = makeNode(SelectStmt);
n->valuesLists = list_make1(list_make1($2));
n->valuesIsrow = true;
$$ = (Node *) n;
}
```

Add a field `bool valuesIsrow` to the `SelectStmt` struct to indicate that `values` is a row.

When the `transformInsertStmt` function processes the `INSERT ... VALUES` statement, if `valuesIsrow` is `true`, calls the new function `transformRowExpression` to convert `row_variable` into the equivalent `row_variable.field1, ..., row_variable.fieldN`.

=== Enhancement to UPDATE statement

When transforming an UPDATE statement, i.e., when calling the `transformUpdateStmt` function, if in Oracle compatibility mode, invoke the newly added `transformIvyUpdateTargetList` function. In this new function, for cases where the `origTlist` (i.e., `targetList`) does not contain a name of `row`, execute the `transformUpdateTargetList` function following the original UPDATE transform process.

For cases where the `origTlist` parameter contains a name `row`, since `row` can be used as a column name in PostgreSQL but is a reserved keyword in Oracle and cannot be used as a column name, it is necessary to determine whether `row` is a column in the table being updated. If `row` is not a column in the table to be updated, call the new function `transformUpdateRowTargetList` to convert the sql statement
```
UPDATE table_name SET ROW = row_variable [WHERE …];
```
into equivalent
```
UPDATE table_name SET table_name.column1 = row_variable.column1, table_name.column2 = row_variable.filed2,… table_name.columnN = row_variable.columnN [WHERE …];
```

If the variable `row_variable` in the statement `UPDATE table_name SET ROW = row_variable` is not a composite type, execute the `transformUpdateTargetList` function following the original UPDATE transform process.
If the variable `row_variable` in the statement is a composite type, the column named `row` in the table is also a composite type, and their type OIDs match, execute the `transformUpdateTargetList` function following the original UPDATE transform process.
In all other cases, call the new function `transformUpdateRowTargetList` for processing.
194 changes: 194 additions & 0 deletions EN/modules/ROOT/pages/master/7.16.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
:sectnums:
:sectnumlevels: 5

:imagesdir: ./_images

= %ROWTYPE、%TYPE

== Purpose

IvorySQL provides Oracle-compatible PL/SQL data type functionality, including %TYPE and %ROWTYPE.

This document aims to introduce users to the functionality of %TYPE and %ROWTYPE.

== Function descriptions

IvorySQL provides Oracle-compatible %TYPE and %ROWTYPE functionality, including the following content.

=== If the reference changes, variables declared with %TYPE or %ROWTYPE will change accordingly.

Create table and function.
```
CREATE TABLE t1(id int, name varchar(20));

--function's parameter datatype is tablename.columnname%TYPE
CREATE OR REPLACE FUNCTION fun1(v t1.id%TYPE) RETURN varchar AS
BEGIN
RETURN v;
END;
/
```

The state of function is valid and function can be executed successfully.
```
SELECT prostatus FROM pg_proc WHERE proname like 'fun1'; --v
prostatus
-----------
v
(1 row)

SELECT fun1(1) FROM dual;
fun1
------
1
(1 row)
```

Modifying the previously defined declaration of a reference causes the function's status to become invalid, but the function can still execute successfully.

```
ALTER TABLE t1 ALTER COLUMN id TYPE varchar(20);

SELECT prostatus FROM pg_proc WHERE proname like 'fun1'; --n
prostatus
-----------
n
(1 row)

--after changing the column id type from int to varchar, call the function again
SELECT fun1('a') FROM dual; --successfully
fun1
------
a
(1 row)
```

re-compile the function, its state become valid.
```
ALTER FUNCTION fun1 COMPILE;
SELECT prostatus FROM pg_proc WHERE proname like 'fun1'; --v
prostatus
-----------
v
(1 row)
```

=== Variables declared with %TYPE inherit the constraints of the referenced variable.

Example:
```
--the following testcase will fail
DECLARE
name VARCHAR(25) NOT NULL := 'Niu';
surname name%TYPE ;
BEGIN
raise notice 'name=%' ,name;
raise notice 'surname=%' ,surname;
END;
/

ERROR: variable "surname" must have a default value, since it's declared NOT NULL
LINE 3: surname name%TYPE ;
^
```

=== Use table_name%ROWTYPE or view_name%ROWTYPE as the parameter type of a function / stored procedure or the return type of a function

Example:
```
CREATE TABLE employees(first_name varchar(20) not null,
last_name varchar(20) not null,
phone_number varchar(50));

INSERT INTO employees VALUES ('Steven','Niu','1-650-555-1234');

CREATE OR REPLACE PROCEDURE p0(v employees%ROWTYPE) AS
BEGIN
raise notice 'v.first_name = %, v.last_name = %, v.phone_number = %',
v.first_name, v.last_name, v.phone_number;
END;
/

DECLARE
a employees%ROWTYPE;
BEGIN
select * into a from employees ;
raise notice 'a=%', a;
call p0(a);
END;
/

NOTICE: a=(Steven,Niu,1-650-555-1234)
NOTICE: v.first_name = Steven, v.last_name = Niu, v.phone_number = 1-650-555-1234

\df p0
List of functions
Schema | Name | Result data type | Argument data types | Type
--------+------+------------------+------------------------+------
public | p0 | | IN v employees%ROWTYPE | proc
(1 row)
```

=== Enhancement to INSERT statement

Support inserting a variables declared with %TYPE or %ROWTYPE into table.

grammar:
```
INSERT INTO table_name VALUES row_variable ;
```

Example:
```
CREATE TABLE t1(id int, name varchar(20));

DECLARE
v1 t1%ROWTYPE;
BEGIN
FOR i IN 1 .. 5 LOOP
v1.id := i;
v1.name := 'a' || i;
INSERT INTO t1 VALUES v1;
END LOOP;
END;
/


SELECT * FROM t1;
id | name
----+------
1 | a1
2 | a2
3 | a3
4 | a4
5 | a5
(5 rows)
```

=== Enhancement to UPDATE statement

example:
```
CREATE TABLE t1(id int, name varchar(20));

DELETE FROM t1;

DECLARE
v1 t1%ROWTYPE;
v2 t1%ROWTYPE;
BEGIN
v1.id := 11;
v1.name := 'abc';
INSERT INTO t1 VALUES v1;
v2.id := 22;
v2.name := 'new';
UPDATE t1 SET ROW = v2;
END;
/

SELECT * FROM t1;
id | name
----+------
22 | new
(1 row)
```