diff --git a/CN/modules/ROOT/nav.adoc b/CN/modules/ROOT/nav.adoc index 7ce1cf9f..0f8e70f7 100644 --- a/CN/modules/ROOT/nav.adoc +++ b/CN/modules/ROOT/nav.adoc @@ -21,6 +21,8 @@ *** xref:master/6.2.1.adoc[initdb过程] ** 兼容特性 *** xref:master/6.3.1.adoc[like] +*** xref:master/6.3.3.adoc[RowID] +*** xref:master/6.3.2.adoc[OUT 参数] * Oracle兼容功能列表 ** xref:master/7.1.adoc[1、框架设计] ** xref:master/7.2.adoc[2、GUC框架] @@ -36,6 +38,7 @@ ** xref:master/7.12.adoc[12、包] ** xref:master/7.13.adoc[13、不可见列] ** xref:master/7.14.adoc[14、RowID] +** xref:master/7.15.adoc[15、OUT 参数] * xref:master/8.adoc[社区贡献指南] * xref:master/9.adoc[工具参考] * xref:master/10.adoc[FAQ] diff --git a/CN/modules/ROOT/pages/master/6.3.2.adoc b/CN/modules/ROOT/pages/master/6.3.2.adoc new file mode 100644 index 00000000..07f1b652 --- /dev/null +++ b/CN/modules/ROOT/pages/master/6.3.2.adoc @@ -0,0 +1,78 @@ + +:sectnums: +:sectnumlevels: 5 + + += **功能概述** + +IvorySQL提供了兼容Oracle的out参数功能,包括含有out参数的函数与过程、匿名块支持out参数、libpq支持out参数。 + +== 实现原理 + +=== 含有out参数的函数 + +对于pliSQL函数,在创建函数时,系统表pg_proc中存储函数参数为全部参数个数(包括OUT参数个数)及对应的参数数据类型。 + +在处理函数参数的interpret_function_parameter_list()函数中,根据参数模式,判断如果是IN OUT,则参数不能有默认值。 + +在make_return_stmt函数中,去掉如果发现有out参数报错的处理。 + +通过修改FuncnameGetCandidates函数,在函数查找时匹配包括out参数在内的所有参数。 + +函数编译时,构造一个row变量来容纳OUT参数变量和返回值变量。修改编译好的函数返回类型(function->fn_rettype)为RECORDOID。 + +函数执行时,在ExecInitFunc函数中调用新函数ExecInitFuncOutParams, 构造OUT参数计算节点,通过计算函数plisql_out_param将函数返回值和OUT参数值从tuple中分离,并且为OUT参数向外赋值。 + +=== 匿名块支持out参数 + +为了支持冒号占位符形式的绑定变量,修改ora_scan.l文件,添加语法,遇到冒号占位符返回ORAPARAM。在ora_gram.y文件中,在c_expr和赋值plassign_target的语法中,添加对ORAPARAM添加处理,构造一个OraParamRef节点。 + +新增 DO + USING 语法: +``` + DO [ LANGUAGE lang_name ] code [USING IN | OUT | IN OUT, ...] +``` +修改ora_gram.y文件,增加DO+USING语法支持。DoStmt结构体中增加 paramsmode 用于存储匿名块中的绑定变量模式等信息的列表。 + +对PBE过程的修改包括:exec_parse_message函数中,根据应用接口传过来的参数类型oid,识别出实参的模式;在exec_bind_message函数中,对于匿名块DO+USING,识别出USING后面参数的模式,向执行器传递参数信息。 + +含OUT参数匿名块的执行 +1. 在PortalStart函数中对匿名块语句,调用CreateTupleDescFromParams函数,增加参数构造描述信息。 + +2. PLiSQL_function成员fn_prokind增加新值PROKIND_ANONYMOUS_BLOCK用来表示匿名块。 + +3. 在plisql_exec_function函数中,对有out参数的匿名块做判断,调用plisql_anonymous_return_out_parameter函数完成对OUT参数构造PLiSQL_row类型的变量,最后将row类型的变量求值当作匿名块函数的返回值。 + +=== libpq中调用含out参数的函数 + +修改Libpq以支持按位置和按参数名字绑定,涉及SQL端,PLiSQL端,libpq接口端。 + +1. SQL端:在服务器端实现系统函数 get_parameter_description,该函数根据SQL语句,返回变量名字与位置的关系。这个函数被用在libpq接口函数中。 +返回的第一行name显示SQL类型,后面行依次显示占位符名字、位置信息。 + +``` +ivorysql=# select * from get_parameter_description('insert into t values(:x, :y);'); + name | position +-------+---------- + false | 0 + :x | 1 + :y | 2 +(3 rows) +``` +对于匿名块语句,尚不支持。 + +2. PLiSQL端:主要是PL/iSQL块根据参数位置或参数名称调整参数内部标识。 +执行函数需要从绑定句柄中获取参数的值与类型的信息; + +对out参数的返回列名称做一个特殊处理。如果是out参数,那么其列名称为_column_xxx,其中xxx是out参数的位置,从而根据绑定位置与返回的位置从结果集中给out参数赋值; + +在PLiSQL执行端,根据参数名字出现的位置,调整名字转换成内部标识的变量,如$number。在返回到客户端的时候,发送描述信息给libpq, 从而达到返回的列名是由参数名字构造,LIBPQ端根据列名来给out参数赋值。 + +3. libpq接口端:提供准备、绑定、执行函数,这些函数与OCI接口相应函数类似。 +一般调用流程如下: +使用IvyHandleAlloc分配语句句柄和错误句柄。 +调用IvyStmtPrepare准备语句。 +调用IvyBindByPos或IvyBindByName 绑定参数。 +调用IvyStmtExecute 执行,可重复执行。 +调用IvyFreeHandle 释放语句句柄和错误句柄。 + +另外还实现了Ivyconnectdb,Ivystatus,Ivyexec,IvyresultStatus,IvyCreatePreparedStatement,IvybindOutParameterByPos,IvyexecPreparedStatement,IvyexecPreparedStatement2,Ivynfields,Ivyntuples,Ivyclear等一系列接口函数。 \ No newline at end of file diff --git a/CN/modules/ROOT/pages/master/6.3.3.adoc b/CN/modules/ROOT/pages/master/6.3.3.adoc new file mode 100644 index 00000000..a4b9952f --- /dev/null +++ b/CN/modules/ROOT/pages/master/6.3.3.adoc @@ -0,0 +1,88 @@ +:sectnums: +:sectnumlevels: 5 + +:imagesdir: ./_images + += RowID + +== 目的 + +IvorySQL提供了兼容Oracle RowID的功能。RowID是一种伪列,在创建表时由数据库自动生成,对于数据库中的每一行,RowID 伪列返回该行的地址。 + +RowID 应当具备以下特性: + +|==== +| 1. 逻辑的标识每一行,且值唯一 +| 2. 可以通过ROWID快速查询和修改表的其他列,自身不能被插入和修改 +| 3. 用户可以控制是否开启此功能 +|==== + +== 实现说明 + +在IvorySQL中系统列 ctid 字段代表了数据行在表中的物理位置,也就是行标识(tuple identifier),由一对数值组成(块编号和行索引),可以通过ctid快速的查找表中的数据行,这样和Oracle的RowID行为很相似,但是ctid值有可能会改变(例如当update/ vacuum full时),因此ctid不适合作为一个长期的行标识。 + +我们选择了表的oid加一个序列值组成的复合类型来做为RowID值,其中的序列是系统列。如果RowID功能被开启,则在建表的同时创建一个名为table-id_rowid_seq 的序列。同时在heap_form_tuple构造函数中,为 HeapTupleHeaderData 的长度增加8个字节,并标识td->t_infomask = HEAP_HASROWID 位来表示rowid的存在。 + +在开启了ROWID的GUC参数或建表时带上 WITH ROWID 选项,或对普通表执行 ALTER TABLE … SET WITH ROWID 时会通过增加序列创建命令来创建一个序列。 +``` +/* + * Build a CREATE SEQUENCE command to create the sequence object, + * and add it to the list of things to be done before this CREATE/ALTER TABLE + */ + seqstmt = makeNode(CreateSeqStmt); + seqstmt->with_rowid = true; + seqstmt->sequence = makeRangeVar(snamespace, sname, -1); + seqstmt->options = lcons(makeDefElem("as", + (Node *) makeTypeNameFromOid(INT8OID, -1), + -1), + seqstmt->options); + seqstmt->options = lcons(makeDefElem("nocache", + NULL, + -1), + seqstmt->options); +``` + +同时为了快速通过RowID伪列查询到一行数据,默认会在表的RowID列上创建一个UNIQUE索引,以提供快速查询功能。 + +RowID列做为系统属性列其实现是通过在 heap.c 中新增一个系统列来实现的。 +``` +/* + * Compatible Oracle ROWID pseudo column. + */ +static const FormData_pg_attribute a7 = { + .attname = {"rowid"}, + .atttypid = ROWIDOID, + .attlen = -1, + .attnum = RowIdAttributeNumber, + .attcacheoff = -1, + .atttypmod = -1, + .attbyval = false, + .attalign = TYPALIGN_SHORT, + .attstorage = TYPSTORAGE_PLAIN, + .attnotnull = true, + .attislocal = true, +}; +``` + +在pg_class系统表中增加一个 bool 类型的字段 relhasrowid,用于标识建表时的 WITH ROWID选项,如果建表时带了WITH ROWID选项,则 relhasrowid为 t,否则为f。 +用户在执行 ALTER table … SET WITH ROWID/ WITHOUT ROWID 命令时,也会修改这个值。 + +``` + /* T if we generate ROWIDs for rows of rel */ + bool relhasrowid BKI_DEFAULT(f); +``` + +在RowID的存储方面,如果启用了RowID 伪列功能,则在插入表之前 heap_form_tuple函数会根据参数TupleDesc 中tdhasrowid 是否为true 在 HeapTupleHeaderData 中增加8个字节来存储序列值。 +在heap_prepare_insert 函数中获取序列的nextval值,存在HeapTupleHeader 相应的位置。 + +``` + if (relation->rd_rel->relhasrowid) + { + // Get the sequence next value + seqnum = nextval_internal(relation->rd_rowdSeqid, true); + // Set the HeapTupleHeader + HeapTupleSetRowId(tup, seqnum); + } +``` + + diff --git a/CN/modules/ROOT/pages/master/7.14.adoc b/CN/modules/ROOT/pages/master/7.14.adoc index 8481066f..4e483b75 100644 --- a/CN/modules/ROOT/pages/master/7.14.adoc +++ b/CN/modules/ROOT/pages/master/7.14.adoc @@ -17,73 +17,6 @@ RowID 应当具备以下特性: | 3. 用户可以控制是否开启此功能 |==== -== 实现说明 -在IvorySQL中系统列 ctid 字段代表了数据行在表中的物理位置,也就是行标识(tuple identifier),由一对数值组成(块编号和行索引),可以通过ctid快速的查找表中的数据行,这样和Oracle的RowID行为很相似,但是ctid值有可能会改变(例如update/ vacuum full等),因此ctid不适合作为一个长期的行标识。 - -我们选择了表的oid加一个序列值组成的复合类型来做为RowID值,其中的序列是系统列。如果RowID功能被开启,则在建表的同时创建一个名为table-id_rowid_seq 的序列。同时在heap_form_tuple构造函数中,为HeapTupleHeaderData 的长度增加8个字节,并标识td->t_infomask = HEAP_HASROWID 位来表示rowid的存在。 - -在开启了支持ROWID的GUC参数或建表时带上 WITH ROWID 选项,以及对普通表执行 ALTER TABLE … SET WITH ROWID 时会通过增加序列创建命令来创建一个序列。 -``` -/* - * Build a CREATE SEQUENCE command to create the sequence object, - * and add it to the list of things to be done before this CREATE/ALTER TABLE - */ - seqstmt = makeNode(CreateSeqStmt); - seqstmt->with_rowid = true; - seqstmt->sequence = makeRangeVar(snamespace, sname, -1); - seqstmt->options = lcons(makeDefElem("as", - (Node *) makeTypeNameFromOid(INT8OID, -1), - -1), - seqstmt->options); - seqstmt->options = lcons(makeDefElem("nocache", - NULL, - -1), - seqstmt->options); -``` - -同时为了快速通过RowID伪列查询到一行数据,默认会在表的RowID列上创建一个UNIQUE索引,以提供快速查询功能。 - -RowID列做为系统属性列其实现是通过在 heap.c 中新增一个系统列来实现的。 -``` -/* - * Compatible Oracle ROWID pseudo column. - */ -static const FormData_pg_attribute a7 = { - .attname = {"rowid"}, - .atttypid = ROWIDOID, - .attlen = -1, - .attnum = RowIdAttributeNumber, - .attcacheoff = -1, - .atttypmod = -1, - .attbyval = false, - .attalign = TYPALIGN_SHORT, - .attstorage = TYPSTORAGE_PLAIN, - .attnotnull = true, - .attislocal = true, -}; -``` - -在pg_class系统表中增加一个 bool 类型的字段 relhasrowid,用于标识建表时的 WITH ROWID选项,如果建表时带了WITH ROWID选项,则 relhasrowid为 t,否则为f。 -用户在执行 ALTER table … SET WITH ROWID/ WITHOUT ROWID 命令时,也会修改这个值。 - -``` - /* T if we generate ROWIDs for rows of rel */ - bool relhasrowid BKI_DEFAULT(f); -``` - -在RowID的存储方面,如果启用了RowID 伪列功能,则在插入表之前 heap_form_tuple函数会根据参数TupleDesc 中tdhasrowid 是否为true 在 HeapTupleHeaderData 中增加8个字节来存储序列值。 -在heap_prepare_insert 函数中获取序列的nextval值,存在HeapTupleHeader 相应的位置。 - -``` - if (relation->rd_rel->relhasrowid) - { - // Get the sequence next value - seqnum = nextval_internal(relation->rd_rowdSeqid, true); - // Set the HeapTupleHeader - HeapTupleSetRowId(tup, seqnum); - } -``` - == 功能开启 IvorySQL提供了多种方式来开启RowID功能。 @@ -92,7 +25,6 @@ IvorySQL提供了多种方式来开启RowID功能。 在IvorySQL 的兼容Oracle 模式下,可以通过 set ivorysql.default_with_rowids to on 来开启RowID,这个参数默认值为off。打开后创建的表,默认就带有了RowID列,可以通过 \d+ 表名 来查看。 -在当前sesssion 中,如果这个GUC参数为 on,所有创建的表都将带上RowID列。 ``` ivorysql=# show ivorysql.default_with_rowids; ivorysql.default_with_rowids @@ -112,7 +44,7 @@ Access method: heap ``` === 通过建表语句中增加 WITH ROWID 选项开启 -在建表语句中增加 WITH ROWID 选项。用户可以选择在需要的表上带有这个选项,没有WITH ROWID选项,将会创建一个普通的表。 +用户可以选择在需要的表上带有这个选项,没有WITH ROWID选项,将会创建一个普通的表。 ``` ivorysql=# create table t2(a int) with rowid; diff --git a/CN/modules/ROOT/pages/master/7.15.adoc b/CN/modules/ROOT/pages/master/7.15.adoc new file mode 100644 index 00000000..13d63ad9 --- /dev/null +++ b/CN/modules/ROOT/pages/master/7.15.adoc @@ -0,0 +1,147 @@ +:sectnums: +:sectnumlevels: 5 + +:imagesdir: ./_images + += out参数 + +== 目的 + +IvorySQL提供了兼容Oracle的out参数功能,包括含有out参数的函数与过程、匿名块支持out参数、libpq支持out参数。 + +本文档旨在为使用人员介绍out参数的功能。 + +== 功能说明 + +IvorySQL提供了兼容Oracle的out参数功能,包括如下内容。 + +=== 含有out参数的函数 + +创建语法如下: +``` +CREATE [ OR REPLACE ] FUNCTION + name ( [ [ argmode ] [ argname ] argtype [ { DEFAULT | = } default_expr ] [, ...] ] ) + [ RETURNS rettype + | RETURNS TABLE ( column_name column_type [, ...] ) ] + { LANGUAGE lang_name + | TRANSFORM { FOR TYPE type_name } [, ... ] + | WINDOW + | { IMMUTABLE | STABLE | VOLATILE } + | [ NOT ] LEAKPROOF + | { CALLED ON NULL INPUT | RETURNS NULL ON NULL INPUT | STRICT } + | { [ EXTERNAL ] SECURITY INVOKER | [ EXTERNAL ] SECURITY DEFINER } + | PARALLEL { UNSAFE | RESTRICTED | SAFE } + | COST execution_cost + | ROWS result_rows + | SUPPORT support_function + | SET configuration_parameter { TO value | = value | FROM CURRENT } + | AS 'definition' + | AS 'obj_file', 'link_symbol' + | sql_body + } ... +``` +其中argmode可以是IN,OUT,INOUT(IN OUT),VARIADIC。如果不指定默认是IN。argmode也可以写在argname的后面。 + +不同于原生PG,兼容Oracle的out参数和返回值数据类型没有关联。IN OUT模式和OUT模式都不能有默认值。如果有out参数,则函数体中必须有RETURN语句。 + +=== 匿名块支持out参数 + +支持冒号占位符形式的绑定变量,例如: 1,:name。 + +新增新的DO+USING语法: DO [ LANGUAGE lang_name ] code [USING IN | OUT | IN OUT, ...] + +支持在libpq中按位置和按参数名字绑定变量,提供系统函数get_parameter_descr,该函数根据SQL语句,返回变量名字与位置的关系。 + +=== ibpq中调用含out参数的函数 + +libpq接口端提供准备、绑定、执行函数,这些函数与OCI相应函数类似。 + +使用流程为: +使用IvyHandleAlloc分配语句句柄和错误句柄。 +调用IvyStmtPrepare准备语句。 +调用IvyBindByPos或IvyBindByName 绑定参数。 +调用IvyStmtExecute 执行,可重复执行。 +调用IvyFreeHandle 释放语句句柄和错误句柄。 + +另外还实现了Ivyconnectdb,Ivystatus,Ivyexec,IvyresultStatus,IvyCreatePreparedStatement,IvybindOutParameterByPos,IvyexecPreparedStatement,IvyexecPreparedStatement2,Ivynfields,Ivyntuples,Ivyclear等一系列接口函数。 + +在源代码中 src/interfaces/libpq/ivytest 目录里可以找到示例程序。 + +== 测试用例 + +=== 含有out参数的函数 +1. out参数和返回值数据类型没有关联 +``` +ivorysql=# create or replace function test_return_out(id integer,price out integer,name out varchar) return varchar +ivorysql-# as +ivorysql-# begin +ivorysql-# price := 20000; +ivorysql-# name := 'test a char out'; +ivorysql-# return 'welcome to QingDao'; +ivorysql-# end; +ivorysql-# / +CREATE FUNCTION +``` + +2. IN OUT模式和OUT模式都不能有默认值 +``` +ivorysql=# create or replace function test_return_inout(id integer,price in out integer default 100,name out varchar) return varchar +ivorysql-# as +ivorysql-# begin +ivorysql-# price := 20000 + price; +ivorysql-# name := 'this is a test'; +ivorysql-# return 'welcome to QingDao'; +ivorysql-# end; +ivorysql-# / +ERROR: IN OUT formal parameters may have no default expressions +``` + +3. 如果有out参数,并且函数返回类型不是void,则函数体中必须有RETURN语句 +``` +--if function's return type is non-void, the function body must has RETURN statement +--if there is no RETURN statement, the function can be created, but when it is called, +--an error is raised +ivorysql=# create or replace function f2(id integer,price out integer) return varchar +ivorysql-# as +ivorysql-# begin +ivorysql-# price := 2; +ivorysql-# end; +ivorysql-# / +CREATE FUNCTION +ivorysql=# declare +ivorysql-# a varchar(20); +ivorysql-# b int; +ivorysql-# begin +ivorysql-# a := f2(1, b); +ivorysql-# end; +ivorysql-# / +ERROR: Function returned without value +CONTEXT: PL/iSQL function f2(pg_catalog.int4,pg_catalog.int4) line 0 at RETURN +PL/iSQL function inline_code_block line 5 at assignment +``` + +=== 匿名块支持out参数 +1. 支持冒号占位符形式的绑定变量,新增DO+USING语法 +``` +ivorysql=# do $$ +ivorysql$# declare +ivorysql$# a int; +ivorysql$# begin +ivorysql$# :x := 1; +ivorysql$# :y := 2; +ivorysql$# end; $$ using out, out; + $1 | $2 +----+---- + 1 | 2 +(1 row) +``` +2. 系统函数 get_parameter_descr +``` +ivorysql=# select * from get_parameter_description('insert into t values(:x,:y)'); + name | position +-------+---------- + false | 0 + :x | 1 + :y | 2 +(3 rows) +``` diff --git a/EN/modules/ROOT/nav.adoc b/EN/modules/ROOT/nav.adoc index 5cef59f0..30b12a22 100644 --- a/EN/modules/ROOT/nav.adoc +++ b/EN/modules/ROOT/nav.adoc @@ -21,6 +21,8 @@ *** xref:master/6.2.1.adoc[initdb Process] ** Compatibility Features *** xref:master/6.3.1.adoc[like] +*** xref:master/6.3.3.adoc[RowID] +*** xref:master/6.3.2.adoc[OUT Parameter] * List of Oracle compatible features ** xref:master/7.1.adoc[1、Ivorysql frame design] ** xref:master/7.2.adoc[2、GUC Framework] @@ -36,6 +38,7 @@ ** xref:master/7.12.adoc[12、Package] ** 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/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.2.adoc b/EN/modules/ROOT/pages/master/6.3.2.adoc new file mode 100644 index 00000000..5707e1e3 --- /dev/null +++ b/EN/modules/ROOT/pages/master/6.3.2.adoc @@ -0,0 +1,82 @@ + +:sectnums: +:sectnumlevels: 5 + + += **Functional Overview** + +IvorySQL provides Oracle-compatible OUT parameter functionality, including functions and procedures with OUT parameters, support for OUT parameters in anonymous blocks, and libpq support for OUT parameters. + +== Implementation Principles + +=== Functions with OUT Parameters + +For PL/pgSQL functions, when creating a function, the system table pg_proc stores the total number of parameters (including OUT parameters) and their corresponding data types. + +In the interpret_function_parameter_list() function, which processes function parameters, it checks the parameter mode. If the mode is IN OUT, the parameter cannot have a default value. + +In the make_return_stmt function, the error handling for detecting OUT parameters has been removed. + +By modifying the FuncnameGetCandidates function, function lookup now matches all parameters, including OUT parameters. + +During function compilation, a row variable is constructed to hold OUT parameter variables and the return value variable. The compiled function's return type (function->fn_rettype) is modified to RECORDOID. + +During function execution, the ExecInitFunc function calls the new ExecInitFuncOutParams function to construct OUT parameter computation nodes. The plisql_out_param function separates the function return value and OUT parameter values from the tuple and assigns values to the OUT parameters externally. + +=== Support for OUT Parameters in Anonymous Blocks + +To support binding variables in the form of colon placeholders, the ora_scan.l file was modified to add syntax that returns ORAPARAM when a colon placeholder is encountered. In the ora_gram.y file, handling for ORAPARAM was added to the c_expr and plassign_target assignment syntax, constructing an OraParamRef node. + +New DO + USING syntax was added: +``` + DO [ LANGUAGE lang_name ] code [USING IN | OUT | IN OUT, ...] +``` +The ora_gram.y file was modified to support the DO+USING syntax. The DoStmt structure now includes a paramsmode field to store a list of binding variable modes and other information for anonymous blocks. + +Modifications to the PBE (Parse, Bind, Execute) process include: +In the exec_parse_message function, the parameter mode is identified based on the parameter type OID passed from the application interface. In the exec_bind_message function, for anonymous blocks with DO+USING, the parameter modes following USING are identified, and parameter information is passed to the executor. + +Execution of anonymous blocks with OUT parameters: +1. In the PortalStart function, for anonymous block statements, the CreateTupleDescFromParams function is called to construct parameter description information. + +2. A new value, PROKIND_ANONYMOUS_BLOCK, was added to the PLiSQL_function member fn_prokind to indicate an anonymous block. + +3. In the plisql_exec_function function, anonymous blocks with OUT parameters are evaluated. The plisql_anonymous_return_out_parameter function is called to construct a PLiSQL_row type variable for OUT parameters, and the evaluated row type variable is used as the return value of the anonymous block function. + +=== Calling Functions with OUT Parameters in libpq + +The libpq interface was modified to support binding by position and by parameter name, involving changes to the SQL layer, PL/pgSQL layer, and libpq interface layer. + +1. SQL Layer: A system function get_parameter_description was implemented on the server side. This function returns the relationship between variable names and their positions based on the SQL statement. It is used in libpq interface functions. + +The first row of the result shows the SQL type in the name column, followed by rows displaying placeholder names and position information. + +``` +ivorysql=# select * from get_parameter_description('insert into t values(:x, :y);'); + name | position +-------+---------- + false | 0 + :x | 1 + :y | 2 +(3 rows) +``` +Support for anonymous block statements is not yet implemented. + +2. PL/pgSQL Layer: The PL/pgSQL block adjusts the internal identification of parameters based on their position or name. + +The execution function retrieves parameter values and type information from the binding handle. + +For OUT parameters, a special handling of return column names is applied. If a parameter is an OUT parameter, its column name is formatted as _column_xxx, where xxx is the position of the OUT parameter. This allows assigning values to OUT parameters from the result set based on the binding position and return position. + +At the PLiSQL execution layer, parameter names are converted to internal identifiers (e.g., $number) based on their position. When returning to the client, description information is sent to libpq, ensuring that the returned column names are constructed from parameter names. The libpq side assigns values to OUT parameters based on these column names. + +3. libpq Interface Layer: Provides functions for preparing, binding, and executing statements, similar to the corresponding OCI functions. + +The general calling process is as follows: +Use IvyHandleAlloc to allocate statement and error handles. +Call IvyStmtPrepare to prepare the statement. +Call IvyBindByPos or IvyBindByName to bind parameters. +Call IvyStmtExecute to execute, which can be repeated. +Call IvyFreeHandle to release the statement and error handles. + +Additionally, a series of interface functions have been implemented, including Ivyconnectdb, Ivystatus, Ivyexec, IvyresultStatus, IvyCreatePreparedStatement, IvybindOutParameterByPos, IvyexecPreparedStatement, IvyexecPreparedStatement2, Ivynfields, Ivyntuples, and Ivyclear. \ No newline at end of file diff --git a/EN/modules/ROOT/pages/master/6.3.3.adoc b/EN/modules/ROOT/pages/master/6.3.3.adoc new file mode 100644 index 00000000..c5cb7f52 --- /dev/null +++ b/EN/modules/ROOT/pages/master/6.3.3.adoc @@ -0,0 +1,88 @@ +:sectnums: +:sectnumlevels: 5 + +:imagesdir: ./_images + += RowID + +== Objective + +IvorySQL provides Oracle-compatible RowID functionality. RowID is a pseudo-column automatically generated by the database when a table is created, returning the address of each row in the database. + +RowID should have the following characteristics: + +|==== +| 1. Logically identifies each row with a unique value +| 2. Allows quick querying and modification of other columns in the table via ROWID, but cannot be inserted or modified itself +| 3. Users can control whether this feature is enabled +|==== + +== Implementation Principles + +In IvorySQL, the system column ctid represents the physical location of a data row in a table, also known as the tuple identifier, which consists of a pair of values (block number and row index). The ctid allows for quick lookup of data rows in a table, behaving similarly to Oracle's RowID. However, the ctid value may change (e.g., during UPDATE or VACUUM FULL), making it unsuitable as a long-term row identifier. + +We chose a composite type consisting of the table's OID and a sequence value as the RowID value, where the sequence is a system column. If the RowID functionality is enabled, a sequence named table-id_rowid_seq is created simultaneously with the table. Additionally, in the heap_form_tuple constructor, the length of HeapTupleHeaderData is increased by 8 bytes, and the td->t_infomask = HEAP_HASROWID bit is set to indicate the presence of the RowID. + +When the RowID functionality is enabled via the GUC parameter, or by the WITH ROWID option in table creation, or by executing ALTER TABLE … SET WITH ROWID on an existing table, a sequence is created by adding a sequence creation command. +``` +/* + * Build a CREATE SEQUENCE command to create the sequence object, + * and add it to the list of things to be done before this CREATE/ALTER TABLE + */ + seqstmt = makeNode(CreateSeqStmt); + seqstmt->with_rowid = true; + seqstmt->sequence = makeRangeVar(snamespace, sname, -1); + seqstmt->options = lcons(makeDefElem("as", + (Node *) makeTypeNameFromOid(INT8OID, -1), + -1), + seqstmt->options); + seqstmt->options = lcons(makeDefElem("nocache", + NULL, + -1), + seqstmt->options); +``` + +To enable fast querying of a row using the RowID pseudo-column, a `UNIQUE` index is automatically created on the RowID column of the table by default to provide efficient query performance. + +The implementation of the RowID column as a system attribute column is achieved by adding a new system column in heap.c. +``` +/* + * Compatible Oracle ROWID pseudo column. + */ +static const FormData_pg_attribute a7 = { + .attname = {"rowid"}, + .atttypid = ROWIDOID, + .attlen = -1, + .attnum = RowIdAttributeNumber, + .attcacheoff = -1, + .atttypmod = -1, + .attbyval = false, + .attalign = TYPALIGN_SHORT, + .attstorage = TYPSTORAGE_PLAIN, + .attnotnull = true, + .attislocal = true, +}; +``` + +A boolean field `relhasrowid` has been added to the `pg_class` system table to indicate whether the `WITH ROWID` option was specified during table creation. If the `WITH ROWID` option is included when creating a table, `relhasrowid` is set to `t`; otherwise, it is set to `f`. +This value is also updated when a user executes the `ALTER TABLE … SET WITH ROWID` or `ALTER TABLE … SET WITHOUT ROWID` command. + +``` + /* T if we generate ROWIDs for rows of rel */ + bool relhasrowid BKI_DEFAULT(f); +``` + +Regarding RowID storage, if the RowID pseudo-column functionality is enabled, the `heap_form_tuple` function will add 8 bytes to the `HeapTupleHeaderData` to store the sequence value, depending on whether `tdhasrowid` in the `TupleDesc` parameter is `true`. +In the `heap_prepare_insert` function, the `nextval` of the sequence is obtained and stored in the corresponding position in the `HeapTupleHeader`. + +``` + if (relation->rd_rel->relhasrowid) + { + // Get the sequence next value + seqnum = nextval_internal(relation->rd_rowdSeqid, true); + // Set the HeapTupleHeader + HeapTupleSetRowId(tup, seqnum); + } +``` + + diff --git a/EN/modules/ROOT/pages/master/7.14.adoc b/EN/modules/ROOT/pages/master/7.14.adoc index c913e5e5..c9555aa7 100644 --- a/EN/modules/ROOT/pages/master/7.14.adoc +++ b/EN/modules/ROOT/pages/master/7.14.adoc @@ -3,161 +3,77 @@ :imagesdir: ./_images -= RowID column += RowID == Objective -IvorySQL provides compatibility for Oracle packages. A package is an encapsulated collection of related program objects stored together in the database. Program objects are procedures, functions, variables, constants, cursors, and exceptions. +IvorySQL provides Oracle-compatible RowID functionality. RowID is a pseudo-column automatically generated by the database when a table is created, returning the address of each row in the database. -This document aims to provide a comprehensive understanding of the process of implementing custom packages. +RowID should have the following characteristics: -== Function descriptions - -IvorySQL provides compatibility for Oracle custom packages, including creation, alteration, and deletion of packages and package bodies. We also add support for package-related commands in the PostgreSQL interactive terminal (psql) with the new \dk command. - -=== Create the specification of a package - -The CREATE OR REPLACE PACKAGE statement is used to create or replace the specification of a package. A package is a collection of related stored procedures, functions, and other program objects, stored as a single unit in the database. The package specification declares these objects, and the package body defines them. - -To create or replace the specification of a package in your own schema, you must have the CREATE PROCEDURE system privilege. If you are creating or replacing a package in another user's schema, you need the CREATE ANY PROCEDURE system privilege. - -=== Create package body - -The CREATE OR REPLACE PACKAGE BODY statement is used to create or replace a package body. To create a package body requires the same privileges as creating the specification of a package, and it is required that the package body and package specification be in the same schema, with the package specification already existing. This statement defines the objects declared in the package specification. - -When the package specification contains cursors or subprograms, a package body must be present to define them. Otherwise, the package body is optional. - -=== Alter package - -The ALTER PACKAGE statement is used to modify the properties of a package.The privileges required to execute the ALTER PACKAGE statement are: you must be the owner of the package or have the ALTER ANY PROCEDURE privilege to modify packages owned by other users. - -=== Drop package and package body - -The DROP PACKAGE statement deletes a package in the database. This statement will remove both the package body and the package specification.The DROP PACKAGE BODY statement only deletes the package body. - -It is not possible to delete a single object within the package using this statement. The privileges required:the package must be in the user's schema, or the user must have the DROP ANY PROCEDURE system privilege. - -=== DISCARD PACKAGE - -The DISCARD PACKAGE functionality is implemented for compatibility with PostgreSQL's DISCARD feature. - -=== Use \dk[+] in psql for package and package body information - -In psql, \dk[+] is used to view the definition information of packages and package bodies. -[cols="^1,^2"] |==== -| Command | Descriptions -| \dk[+] | List the package information currently visible. -| \dk[+] xxx | List the package specification and package body content of the xxx package. +| 1. Logically identifies each row with a unique value +| 2. Allows quick querying and modification of other columns in the table via ROWID, but cannot be inserted or modified itself +| 3. Users can control whether this feature is enabled |==== -== Test cases +== Enable the functionality +IvorySQL provides multiple ways to enable the RowID functionality. + +=== Enable the functionality through GUC parameters + +In IvorySQL's Oracle-compatible mode, the RowID functionality can be enabled by setting ivorysql.default_with_rowids to on. The default value for this parameter is off. Once enabled, tables created will automatically include a RowID column, which can be viewed using `\d+ table_name`. -=== Create the specification of a package -``` -ivorysql=# create or replace package pkg is -ivorysql-# var1 integer; -ivorysql-# var2 integer; -ivorysql-# function test_f(id integer) return integer; -ivorysql-# procedure test_p(id integer); -ivorysql-# end; -ivorysql-# / -CREATE PACKAGE -``` -=== Create package body -``` -ivorysql=# create or replace package body pkg is -ivorysql-# var3 integer; -ivorysql-# function test_f(id integer) return integer is -ivorysql-# begin -ivorysql-# dbms_output.put_line('pkg test_f'); -ivorysql-# return id; -ivorysql-# end; -ivorysql-# procedure test_p(id integer) is -ivorysql-# begin -ivorysql-# dbms_output.put_line('pkg proc'); -ivorysql-# end; -ivorysql-# --privite function -ivorysql-# function test_piv1(id integer) return integer is -ivorysql-# begin -ivorysql-# return id; -ivorysql-# end; -ivorysql-# --privite procedure -ivorysql-# procedure test_piv2(id integer) is -ivorysql-# begin -ivorysql-# dbms_output.put_line('privite proc'); -ivorysql-# end; -ivorysql-# begin -ivorysql-# var1 := 1; -ivorysql-# var2 := 2; -ivorysql-# var3 := 4; -ivorysql-# end; -ivorysql-# / -CREATE PACKAGE BODY ``` -=== Alter package +ivorysql=# show ivorysql.default_with_rowids; + ivorysql.default_with_rowids +------------------------------ + off +(1 row) ``` -ivorysql=# alter package pkg noneditionable; -ALTER PACKAGE ``` -=== Drop package and package body +ivorysql=# create table t(a int); +CREATE TABLE +ivorysql=# \d+ t + Table "public.t" + Column | Type | Collation | Nullable | Default | Invisible | Storage | Compression | Stats target | Description +--------+-----------------+-----------+----------+---------+-----------+---------+-------------+--------------+------------- + a | pg_catalog.int4 | | | | | plain | | | +Access method: heap ``` -ivorysql=# Drop package pkg; -DROP PACKAGE +=== Enable the functionality by adding the `WITH ROWID` option in the table creation statement. + +Users can choose to include this option for tables that require it; without the `WITH ROWID` option, a regular table will be created. -ivorysql=# Drop package body pkg; -DROP PACKAGE BODY -``` -=== DISCARD PACKAGE -``` -ivorysql=# discard package; -DISCARD PACKAGES ``` -=== Use \dk[+] in psql for package and package body information +ivorysql=# create table t2(a int) with rowid; +CREATE TABLE +ivorysql=# \d+ t2 + Table "public.t2" + Column | Type | Collation | Nullable | Default | Invisible | Storage | Compression | Stats target | Description +--------+-----------------+-----------+----------+---------+-----------+---------+-------------+--------------+------------- + a | pg_catalog.int4 | | | | | plain | | | +Indexes: + "t2_16432_rowid_idx" btree (rowid) +Access method: heap +Has ROWID: yes ``` -ivorysql=# \dk - List of packages - Schema | Name | Owner ---------+----------+---------- - public | pkg | ivorysql - public | test_pkg | ivorysql -(2 rows) -ivorysql=# \dk pkg - List of packages - Schema | Name | Owner ---------+------+---------- - public | pkg | ivorysql -(1 row) +=== Enable the functionality by executing the command `ALTER TABLE … SET WITH ROWID` on an existing table. -ivorysql=# \dk pkg1 -Did not find any package named "pkg1". +This approach allows a regular table to add a ROWID column using the `ALTER` command when ROWID functionality is needed. +The ROWID can also be removed using the `ALTER TABLE … SET WITHOUT ROWID` command. -ivorysql=# \dk+ - List of packages - Schema | Name | Owner | Security | Editionable | Use Collation | Specification | Package Body - ---------+----------+----------+----------+-------------+---------------+-----------------------------------------------+----------------------------------------------------- ---------- - public | pkg | ivorysql | definer | Editionable | default | var1 integer; +| - | | | | | | var2 integer; +| - | | | | | | function test_f(id integer) return integer; +| - | | | | | | procedure test_p(id integer); +| - | | | | | | end | - public | test_pkg | ivorysql | definer | Editionable | default | var1 integer; +| FUNCTION test_f(id integer) RETURN integer IS - + - | | | | | | FUNCTION test_f(id integer) RETURN integer;+| BEGIN - + - | | | | | | end | dbms_output.put_line('invoke function test_pkg.t -est_f');+ - | | | | | | | RETURN 23; - + - | | | | | | | end; - + - | | | | | | | BEGIN - + - | | | | | | | var1 := 23; - + - | | | | | | | end -(2 rows) +``` +ivorysql=# create table t3(a int); +CREATE TABLE +ivorysql=# alter table t3 set with rowid; +ALTER TABLE +ivorysql=# \d+ t3; + Table "public.t3" + Column | Type | Collation | Nullable | Default | Invisible | Storage | Compression | Stats target | Description +--------+-----------------+-----------+----------+---------+-----------+---------+-------------+--------------+------------- + a | pg_catalog.int4 | | | | | plain | | | +Access method: heap +Has ROWID: yes ``` diff --git a/EN/modules/ROOT/pages/master/7.15.adoc b/EN/modules/ROOT/pages/master/7.15.adoc new file mode 100644 index 00000000..d7b6498f --- /dev/null +++ b/EN/modules/ROOT/pages/master/7.15.adoc @@ -0,0 +1,149 @@ +:sectnums: +:sectnumlevels: 5 + +:imagesdir: ./_images + += out parameter + +== Objective + +IvorySQL provides Oracle-compatible OUT parameter functionality, including functions and procedures with OUT parameters, support for OUT parameters in anonymous blocks, and libpq support for OUT parameters. + +This document aims to introduce the functionality of OUT parameters to users. + +== Function descriptions + +IvorySQL provides Oracle-compatible OUT parameter functionality, including the following features. + +=== functions with OUT parameters + +Syntax: +``` +CREATE [ OR REPLACE ] FUNCTION + name ( [ [ argmode ] [ argname ] argtype [ { DEFAULT | = } default_expr ] [, ...] ] ) + [ RETURNS rettype + | RETURNS TABLE ( column_name column_type [, ...] ) ] + { LANGUAGE lang_name + | TRANSFORM { FOR TYPE type_name } [, ... ] + | WINDOW + | { IMMUTABLE | STABLE | VOLATILE } + | [ NOT ] LEAKPROOF + | { CALLED ON NULL INPUT | RETURNS NULL ON NULL INPUT | STRICT } + | { [ EXTERNAL ] SECURITY INVOKER | [ EXTERNAL ] SECURITY DEFINER } + | PARALLEL { UNSAFE | RESTRICTED | SAFE } + | COST execution_cost + | ROWS result_rows + | SUPPORT support_function + | SET configuration_parameter { TO value | = value | FROM CURRENT } + | AS 'definition' + | AS 'obj_file', 'link_symbol' + | sql_body + } ... +``` +The argmode can be IN, OUT, INOUT (IN OUT), or VARIADIC. If not specified, the default is IN. The argmode can also be written after the argname. + +Unlike native PostgreSQL, Oracle-compatible OUT parameters are not associated with the return value data type. Neither IN OUT nor OUT modes can have default values. If there are OUT parameters and the function's return type is not void, the function body must include a RETURN statement. + +=== support for OUT parameters in anonymous blocks + +Supports binding variables in the form of colon placeholders, such as :1, :name. + +Added new DO+USING syntax: DO [ LANGUAGE lang_name ] code [USING IN | OUT | IN OUT, ...] + +Supports binding variables in libpq by position and by parameter name, providing the system function get_parameter_description, which returns the relationship between variable names and their positions based on the SQL statement. + +=== calling functions with OUT parameters in libpq + +The libpq interface provides functions for preparing, binding, and executing statements, which are similar to the corresponding OCI functions. +The usage process is as follows: + +Use IvyHandleAlloc to allocate statement and error handles. +Call IvyStmtPrepare to prepare the statement. +Call IvyBindByPos or IvyBindByName to bind parameters. +Call IvyStmtExecute to execute, which can be repeated. +Call IvyFreeHandle to release the statement and error handles. + +Additionally, a series of interface functions have been implemented, including Ivyconnectdb, Ivystatus, Ivyexec, IvyresultStatus, IvyCreatePreparedStatement, IvybindOutParameterByPos, IvyexecPreparedStatement, IvyexecPreparedStatement2, Ivynfields, Ivyntuples, and Ivyclear. + +Example programs can be found in the src/interfaces/libpq/ivytest directory of the source code. + +== test examples + +=== functions with OUT parameters +1. OUT parameters are not associated with the return value data type +``` +ivorysql=# create or replace function test_return_out(id integer,price out integer,name out varchar) return varchar +ivorysql-# as +ivorysql-# begin +ivorysql-# price := 20000; +ivorysql-# name := 'test a char out'; +ivorysql-# return 'welcome to QingDao'; +ivorysql-# end; +ivorysql-# / +CREATE FUNCTION +``` + +2. Neither IN OUT nor OUT modes can have default values +``` +ivorysql=# create or replace function test_return_inout(id integer,price in out integer default 100,name out varchar) return varchar +ivorysql-# as +ivorysql-# begin +ivorysql-# price := 20000 + price; +ivorysql-# name := 'this is a test'; +ivorysql-# return 'welcome to QingDao'; +ivorysql-# end; +ivorysql-# / +ERROR: IN OUT formal parameters may have no default expressions +``` + +3. If there are OUT parameters and the function's return type is not void, the function body must include a RETURN statement. +``` +--if function's return type is non-void, the function body must has RETURN statement +--if there is no RETURN statement, the function can be created, but when it is called, +--an error is raised +ivorysql=# create or replace function f2(id integer,price out integer) return varchar +ivorysql-# as +ivorysql-# begin +ivorysql-# price := 2; +ivorysql-# end; +ivorysql-# / +CREATE FUNCTION +ivorysql=# declare +ivorysql-# a varchar(20); +ivorysql-# b int; +ivorysql-# begin +ivorysql-# a := f2(1, b); +ivorysql-# end; +ivorysql-# / +ERROR: Function returned without value +CONTEXT: PL/iSQL function f2(pg_catalog.int4,pg_catalog.int4) line 0 at RETURN +PL/iSQL function inline_code_block line 5 at assignment +``` + +=== support OUT parameters in anonymous block +1. Supports binding variables in the form of colon placeholders and new DO+USING syntax +``` +ivorysql=# do $$ +ivorysql$# declare +ivorysql$# a int; +ivorysql$# begin +ivorysql$# :x := 1; +ivorysql$# :y := 2; +ivorysql$# end; $$ using out, out; + $1 | $2 +----+---- + 1 | 2 +(1 row) +``` +2. system function get_parameter_descr() +``` +ivorysql=# select * from get_parameter_description('insert into t values(:x,:y)'); + name | position +-------+---------- + false | 0 + :x | 1 + :y | 2 +(3 rows) +``` + +