Skip to content

Commit 43ff64d

Browse files
authored
Merge pull request #138 from bigplaice/master
add doc for RowID and OUT parameter
2 parents 23c185f + 6f5ab66 commit 43ff64d

File tree

10 files changed

+693
-207
lines changed

10 files changed

+693
-207
lines changed

CN/modules/ROOT/nav.adoc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
*** xref:master/6.2.1.adoc[initdb过程]
2222
** 兼容特性
2323
*** xref:master/6.3.1.adoc[like]
24+
*** xref:master/6.3.3.adoc[RowID]
25+
*** xref:master/6.3.2.adoc[OUT 参数]
2426
* Oracle兼容功能列表
2527
** xref:master/7.1.adoc[1、框架设计]
2628
** xref:master/7.2.adoc[2、GUC框架]
@@ -36,6 +38,7 @@
3638
** xref:master/7.12.adoc[12、包]
3739
** xref:master/7.13.adoc[13、不可见列]
3840
** xref:master/7.14.adoc[14、RowID]
41+
** xref:master/7.15.adoc[15、OUT 参数]
3942
* xref:master/8.adoc[社区贡献指南]
4043
* xref:master/9.adoc[工具参考]
4144
* xref:master/10.adoc[FAQ]
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
2+
:sectnums:
3+
:sectnumlevels: 5
4+
5+
6+
= **功能概述**
7+
8+
IvorySQL提供了兼容Oracle的out参数功能,包括含有out参数的函数与过程、匿名块支持out参数、libpq支持out参数。
9+
10+
== 实现原理
11+
12+
=== 含有out参数的函数
13+
14+
对于pliSQL函数,在创建函数时,系统表pg_proc中存储函数参数为全部参数个数(包括OUT参数个数)及对应的参数数据类型。
15+
16+
在处理函数参数的interpret_function_parameter_list()函数中,根据参数模式,判断如果是IN OUT,则参数不能有默认值。
17+
18+
在make_return_stmt函数中,去掉如果发现有out参数报错的处理。
19+
20+
通过修改FuncnameGetCandidates函数,在函数查找时匹配包括out参数在内的所有参数。
21+
22+
函数编译时,构造一个row变量来容纳OUT参数变量和返回值变量。修改编译好的函数返回类型(function->fn_rettype)为RECORDOID。
23+
24+
函数执行时,在ExecInitFunc函数中调用新函数ExecInitFuncOutParams, 构造OUT参数计算节点,通过计算函数plisql_out_param将函数返回值和OUT参数值从tuple中分离,并且为OUT参数向外赋值。
25+
26+
=== 匿名块支持out参数
27+
28+
为了支持冒号占位符形式的绑定变量,修改ora_scan.l文件,添加语法,遇到冒号占位符返回ORAPARAM。在ora_gram.y文件中,在c_expr和赋值plassign_target的语法中,添加对ORAPARAM添加处理,构造一个OraParamRef节点。
29+
30+
新增 DO + USING 语法:
31+
```
32+
DO [ LANGUAGE lang_name ] code [USING IN | OUT | IN OUT, ...]
33+
```
34+
修改ora_gram.y文件,增加DO+USING语法支持。DoStmt结构体中增加 paramsmode 用于存储匿名块中的绑定变量模式等信息的列表。
35+
36+
对PBE过程的修改包括:exec_parse_message函数中,根据应用接口传过来的参数类型oid,识别出实参的模式;在exec_bind_message函数中,对于匿名块DO+USING,识别出USING后面参数的模式,向执行器传递参数信息。
37+
38+
含OUT参数匿名块的执行
39+
1. 在PortalStart函数中对匿名块语句,调用CreateTupleDescFromParams函数,增加参数构造描述信息。
40+
41+
2. PLiSQL_function成员fn_prokind增加新值PROKIND_ANONYMOUS_BLOCK用来表示匿名块。
42+
43+
3. 在plisql_exec_function函数中,对有out参数的匿名块做判断,调用plisql_anonymous_return_out_parameter函数完成对OUT参数构造PLiSQL_row类型的变量,最后将row类型的变量求值当作匿名块函数的返回值。
44+
45+
=== libpq中调用含out参数的函数
46+
47+
修改Libpq以支持按位置和按参数名字绑定,涉及SQL端,PLiSQL端,libpq接口端。
48+
49+
1. SQL端:在服务器端实现系统函数 get_parameter_description,该函数根据SQL语句,返回变量名字与位置的关系。这个函数被用在libpq接口函数中。
50+
返回的第一行name显示SQL类型,后面行依次显示占位符名字、位置信息。
51+
52+
```
53+
ivorysql=# select * from get_parameter_description('insert into t values(:x, :y);');
54+
name | position
55+
-------+----------
56+
false | 0
57+
:x | 1
58+
:y | 2
59+
(3 rows)
60+
```
61+
对于匿名块语句,尚不支持。
62+
63+
2. PLiSQL端:主要是PL/iSQL块根据参数位置或参数名称调整参数内部标识。
64+
执行函数需要从绑定句柄中获取参数的值与类型的信息;
65+
66+
对out参数的返回列名称做一个特殊处理。如果是out参数,那么其列名称为_column_xxx,其中xxx是out参数的位置,从而根据绑定位置与返回的位置从结果集中给out参数赋值;
67+
68+
在PLiSQL执行端,根据参数名字出现的位置,调整名字转换成内部标识的变量,如$number。在返回到客户端的时候,发送描述信息给libpq, 从而达到返回的列名是由参数名字构造,LIBPQ端根据列名来给out参数赋值。
69+
70+
3. libpq接口端:提供准备、绑定、执行函数,这些函数与OCI接口相应函数类似。
71+
一般调用流程如下:
72+
使用IvyHandleAlloc分配语句句柄和错误句柄。
73+
调用IvyStmtPrepare准备语句。
74+
调用IvyBindByPos或IvyBindByName 绑定参数。
75+
调用IvyStmtExecute 执行,可重复执行。
76+
调用IvyFreeHandle 释放语句句柄和错误句柄。
77+
78+
另外还实现了Ivyconnectdb,Ivystatus,Ivyexec,IvyresultStatus,IvyCreatePreparedStatement,IvybindOutParameterByPos,IvyexecPreparedStatement,IvyexecPreparedStatement2,Ivynfields,Ivyntuples,Ivyclear等一系列接口函数。
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
:sectnums:
2+
:sectnumlevels: 5
3+
4+
:imagesdir: ./_images
5+
6+
= RowID
7+
8+
== 目的
9+
10+
IvorySQL提供了兼容Oracle RowID的功能。RowID是一种伪列,在创建表时由数据库自动生成,对于数据库中的每一行,RowID 伪列返回该行的地址。
11+
12+
RowID 应当具备以下特性:
13+
14+
|====
15+
| 1. 逻辑的标识每一行,且值唯一
16+
| 2. 可以通过ROWID快速查询和修改表的其他列,自身不能被插入和修改
17+
| 3. 用户可以控制是否开启此功能
18+
|====
19+
20+
== 实现说明
21+
22+
在IvorySQL中系统列 ctid 字段代表了数据行在表中的物理位置,也就是行标识(tuple identifier),由一对数值组成(块编号和行索引),可以通过ctid快速的查找表中的数据行,这样和Oracle的RowID行为很相似,但是ctid值有可能会改变(例如当update/ vacuum full时),因此ctid不适合作为一个长期的行标识。
23+
24+
我们选择了表的oid加一个序列值组成的复合类型来做为RowID值,其中的序列是系统列。如果RowID功能被开启,则在建表的同时创建一个名为table-id_rowid_seq 的序列。同时在heap_form_tuple构造函数中,为 HeapTupleHeaderData 的长度增加8个字节,并标识td->t_infomask = HEAP_HASROWID 位来表示rowid的存在。
25+
26+
在开启了ROWID的GUC参数或建表时带上 WITH ROWID 选项,或对普通表执行 ALTER TABLE … SET WITH ROWID 时会通过增加序列创建命令来创建一个序列。
27+
```
28+
/*
29+
* Build a CREATE SEQUENCE command to create the sequence object,
30+
* and add it to the list of things to be done before this CREATE/ALTER TABLE
31+
*/
32+
seqstmt = makeNode(CreateSeqStmt);
33+
seqstmt->with_rowid = true;
34+
seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
35+
seqstmt->options = lcons(makeDefElem("as",
36+
(Node *) makeTypeNameFromOid(INT8OID, -1),
37+
-1),
38+
seqstmt->options);
39+
seqstmt->options = lcons(makeDefElem("nocache",
40+
NULL,
41+
-1),
42+
seqstmt->options);
43+
```
44+
45+
同时为了快速通过RowID伪列查询到一行数据,默认会在表的RowID列上创建一个UNIQUE索引,以提供快速查询功能。
46+
47+
RowID列做为系统属性列其实现是通过在 heap.c 中新增一个系统列来实现的。
48+
```
49+
/*
50+
* Compatible Oracle ROWID pseudo column.
51+
*/
52+
static const FormData_pg_attribute a7 = {
53+
.attname = {"rowid"},
54+
.atttypid = ROWIDOID,
55+
.attlen = -1,
56+
.attnum = RowIdAttributeNumber,
57+
.attcacheoff = -1,
58+
.atttypmod = -1,
59+
.attbyval = false,
60+
.attalign = TYPALIGN_SHORT,
61+
.attstorage = TYPSTORAGE_PLAIN,
62+
.attnotnull = true,
63+
.attislocal = true,
64+
};
65+
```
66+
67+
在pg_class系统表中增加一个 bool 类型的字段 relhasrowid,用于标识建表时的 WITH ROWID选项,如果建表时带了WITH ROWID选项,则 relhasrowid为 t,否则为f。
68+
用户在执行 ALTER table … SET WITH ROWID/ WITHOUT ROWID 命令时,也会修改这个值。
69+
70+
```
71+
/* T if we generate ROWIDs for rows of rel */
72+
bool relhasrowid BKI_DEFAULT(f);
73+
```
74+
75+
在RowID的存储方面,如果启用了RowID 伪列功能,则在插入表之前 heap_form_tuple函数会根据参数TupleDesc 中tdhasrowid 是否为true 在 HeapTupleHeaderData 中增加8个字节来存储序列值。
76+
在heap_prepare_insert 函数中获取序列的nextval值,存在HeapTupleHeader 相应的位置。
77+
78+
```
79+
if (relation->rd_rel->relhasrowid)
80+
{
81+
// Get the sequence next value
82+
seqnum = nextval_internal(relation->rd_rowdSeqid, true);
83+
// Set the HeapTupleHeader
84+
HeapTupleSetRowId(tup, seqnum);
85+
}
86+
```
87+
88+

CN/modules/ROOT/pages/master/7.14.adoc

Lines changed: 1 addition & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -17,73 +17,6 @@ RowID 应当具备以下特性:
1717
| 3. 用户可以控制是否开启此功能
1818
|====
1919

20-
== 实现说明
21-
在IvorySQL中系统列 ctid 字段代表了数据行在表中的物理位置,也就是行标识(tuple identifier),由一对数值组成(块编号和行索引),可以通过ctid快速的查找表中的数据行,这样和Oracle的RowID行为很相似,但是ctid值有可能会改变(例如update/ vacuum full等),因此ctid不适合作为一个长期的行标识。
22-
23-
我们选择了表的oid加一个序列值组成的复合类型来做为RowID值,其中的序列是系统列。如果RowID功能被开启,则在建表的同时创建一个名为table-id_rowid_seq 的序列。同时在heap_form_tuple构造函数中,为HeapTupleHeaderData 的长度增加8个字节,并标识td->t_infomask = HEAP_HASROWID 位来表示rowid的存在。
24-
25-
在开启了支持ROWID的GUC参数或建表时带上 WITH ROWID 选项,以及对普通表执行 ALTER TABLE … SET WITH ROWID 时会通过增加序列创建命令来创建一个序列。
26-
```
27-
/*
28-
* Build a CREATE SEQUENCE command to create the sequence object,
29-
* and add it to the list of things to be done before this CREATE/ALTER TABLE
30-
*/
31-
seqstmt = makeNode(CreateSeqStmt);
32-
seqstmt->with_rowid = true;
33-
seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
34-
seqstmt->options = lcons(makeDefElem("as",
35-
(Node *) makeTypeNameFromOid(INT8OID, -1),
36-
-1),
37-
seqstmt->options);
38-
seqstmt->options = lcons(makeDefElem("nocache",
39-
NULL,
40-
-1),
41-
seqstmt->options);
42-
```
43-
44-
同时为了快速通过RowID伪列查询到一行数据,默认会在表的RowID列上创建一个UNIQUE索引,以提供快速查询功能。
45-
46-
RowID列做为系统属性列其实现是通过在 heap.c 中新增一个系统列来实现的。
47-
```
48-
/*
49-
* Compatible Oracle ROWID pseudo column.
50-
*/
51-
static const FormData_pg_attribute a7 = {
52-
.attname = {"rowid"},
53-
.atttypid = ROWIDOID,
54-
.attlen = -1,
55-
.attnum = RowIdAttributeNumber,
56-
.attcacheoff = -1,
57-
.atttypmod = -1,
58-
.attbyval = false,
59-
.attalign = TYPALIGN_SHORT,
60-
.attstorage = TYPSTORAGE_PLAIN,
61-
.attnotnull = true,
62-
.attislocal = true,
63-
};
64-
```
65-
66-
在pg_class系统表中增加一个 bool 类型的字段 relhasrowid,用于标识建表时的 WITH ROWID选项,如果建表时带了WITH ROWID选项,则 relhasrowid为 t,否则为f。
67-
用户在执行 ALTER table … SET WITH ROWID/ WITHOUT ROWID 命令时,也会修改这个值。
68-
69-
```
70-
/* T if we generate ROWIDs for rows of rel */
71-
bool relhasrowid BKI_DEFAULT(f);
72-
```
73-
74-
在RowID的存储方面,如果启用了RowID 伪列功能,则在插入表之前 heap_form_tuple函数会根据参数TupleDesc 中tdhasrowid 是否为true 在 HeapTupleHeaderData 中增加8个字节来存储序列值。
75-
在heap_prepare_insert 函数中获取序列的nextval值,存在HeapTupleHeader 相应的位置。
76-
77-
```
78-
if (relation->rd_rel->relhasrowid)
79-
{
80-
// Get the sequence next value
81-
seqnum = nextval_internal(relation->rd_rowdSeqid, true);
82-
// Set the HeapTupleHeader
83-
HeapTupleSetRowId(tup, seqnum);
84-
}
85-
```
86-
8720
== 功能开启
8821

8922
IvorySQL提供了多种方式来开启RowID功能。
@@ -92,7 +25,6 @@ IvorySQL提供了多种方式来开启RowID功能。
9225

9326
在IvorySQL 的兼容Oracle 模式下,可以通过 set ivorysql.default_with_rowids to on 来开启RowID,这个参数默认值为off。打开后创建的表,默认就带有了RowID列,可以通过 \d+ 表名 来查看。
9427

95-
在当前sesssion 中,如果这个GUC参数为 on,所有创建的表都将带上RowID列。
9628
```
9729
ivorysql=# show ivorysql.default_with_rowids;
9830
ivorysql.default_with_rowids
@@ -112,7 +44,7 @@ Access method: heap
11244
```
11345
=== 通过建表语句中增加 WITH ROWID 选项开启
11446
115-
在建表语句中增加 WITH ROWID 选项。用户可以选择在需要的表上带有这个选项,没有WITH ROWID选项,将会创建一个普通的表。
47+
用户可以选择在需要的表上带有这个选项,没有WITH ROWID选项,将会创建一个普通的表。
11648
11749
```
11850
ivorysql=# create table t2(a int) with rowid;

0 commit comments

Comments
 (0)