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
3 changes: 3 additions & 0 deletions CN/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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框架]
Expand All @@ -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]
78 changes: 78 additions & 0 deletions CN/modules/ROOT/pages/master/6.3.2.adoc
Original file line number Diff line number Diff line change
@@ -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等一系列接口函数。
88 changes: 88 additions & 0 deletions CN/modules/ROOT/pages/master/6.3.3.adoc
Original file line number Diff line number Diff line change
@@ -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);
}
```


70 changes: 1 addition & 69 deletions CN/modules/ROOT/pages/master/7.14.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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功能。
Expand All @@ -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
Expand All @@ -112,7 +44,7 @@ Access method: heap
```
=== 通过建表语句中增加 WITH ROWID 选项开启

在建表语句中增加 WITH ROWID 选项。用户可以选择在需要的表上带有这个选项,没有WITH ROWID选项,将会创建一个普通的表。
用户可以选择在需要的表上带有这个选项,没有WITH ROWID选项,将会创建一个普通的表。

```
ivorysql=# create table t2(a int) with rowid;
Expand Down
Loading