diff --git a/EN/modules/ROOT/nav.adoc b/EN/modules/ROOT/nav.adoc index bcaec0c1..ddeaaacb 100644 --- a/EN/modules/ROOT/nav.adoc +++ b/EN/modules/ROOT/nav.adoc @@ -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] @@ -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] diff --git a/EN/modules/ROOT/pages/master/6.3.4.adoc b/EN/modules/ROOT/pages/master/6.3.4.adoc new file mode 100644 index 00000000..2fd5a930 --- /dev/null +++ b/EN/modules/ROOT/pages/master/6.3.4.adoc @@ -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. diff --git a/EN/modules/ROOT/pages/master/7.16.adoc b/EN/modules/ROOT/pages/master/7.16.adoc new file mode 100644 index 00000000..54205c8e --- /dev/null +++ b/EN/modules/ROOT/pages/master/7.16.adoc @@ -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) +```