Skip to content

Commit c955d84

Browse files
authored
Merge branch 'IvorySQL:master' into master
2 parents 9653ff3 + 6347a79 commit c955d84

File tree

11 files changed

+1160
-1
lines changed

11 files changed

+1160
-1
lines changed

.coderabbit.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
2+
# Minimal configuration for getting started
3+
# language: "zh-CN"
4+
language: "en-US"
5+
reviews:
6+
profile: "chill"
7+
high_level_summary: true
8+
auto_review:
9+
enabled: true
10+
drafts: false
11+
base_branches: ["master", "v5.0", "v4.6", "v1.17"]

CN/modules/ROOT/nav.adoc

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@
2424
**** xref:master/6.3.1.adoc[like]
2525
**** xref:master/6.3.3.adoc[RowID]
2626
**** xref:master/6.3.2.adoc[OUT 参数]
27-
**** xref:master/6.4.adoc[国标GB18030]
27+
**** xref:master/6.3.4.adoc[%TYPE、%ROWTYPE]
28+
**** xref:master/6.3.5.adoc[NLS 参数]
29+
*** xref:master/6.4.adoc[国标GB18030]
2830
** Oracle兼容功能列表
2931
*** xref:master/7.1.adoc[1、框架设计]
3032
*** xref:master/7.2.adoc[2、GUC框架]
@@ -41,6 +43,8 @@
4143
*** xref:master/7.13.adoc[13、不可见列]
4244
*** xref:master/7.14.adoc[14、RowID]
4345
*** xref:master/7.15.adoc[15、OUT 参数]
46+
*** xref:master/7.16.adoc[16、%TYPE、%ROWTYPE]
47+
*** xref:master/7.17.adoc[17、NLS 参数]
4448
** IvorySQL贡献指南
4549
*** xref:master/8.1.adoc[社区贡献指南]
4650
*** xref:master/8.2.adoc[asciidoc语法快速参考]
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
:sectnums:
2+
:sectnumlevels: 5
3+
4+
:imagesdir: ./_images
5+
6+
= %ROWTYPE、%TYPE
7+
8+
== 目的
9+
10+
IvorySQL提供了兼容Oracle的plsql数据类型功能,包括%TYPE、%ROWTYPE。
11+
12+
== 实现说明
13+
14+
=== 引用类型发生变化时,如何确保%TYPE声明的变量也随之相应改变
15+
16+
这是一个被动的过程。 当前的实现方案是记录函数(存储过程)与 tablename.columname 的依赖关系。当引用类型发生变化时,根据依赖关系,让函数(存储过程)缓存失效。这样一来,函数被调用时就会执行强制编译。从而确保函数获取最新的变量类型。
17+
18+
在系统表 pg_proc 中添加一个字段 prostatus 用来表示函数(存储过程)状态,有3种状态: validate(v),invalidate(i),N/A(n)。函数编译成功后,这个状态会被设置为valid。
19+
20+
在解析函数内容时,plisql_parse_cwordtype,plisql_parse_wordrowtype,plisql_parse_cwordrowtype 等函数识别出%TYPE, %ROWTYPE引用的对象,并记录在 plisql_referenced_object 链表中,最后添加到 pg_depend 系统表中。
21+
22+
增加一种新的依赖类型 DEPENDENCY_TYPE = 't',表示%TYPE或%ROWTYPE依赖。在添加对象引用关系到pg_depend系统表时,设置依赖类型为 't'。
23+
24+
对表进行操作时(修改表类型、删除表等),查看系统表pg_depend,如果存在依赖类型deptype=’t’,并且依赖对象是函数,则调用函数 plisql_free_function 删除函数缓存,并修改 pg_proc 系统表中的函数状态prostatus 为 N/A(n)。
25+
26+
27+
=== %TYPE声明的变量继承引用变量的约束
28+
29+
在 PLiSQL_type 结构体中添加成员 bool notnull;
30+
31+
```
32+
/*
33+
* Postgres data type
34+
*/
35+
typedef struct PLiSQL_type
36+
{
37+
bool notnull; /* the type is built by variable%type,
38+
* isnull or notnull of the variable */
39+
```
40+
41+
在负责解析%TYPE类型函数的plisql_parse_wordtype或plisql_parse_cwordtype函数中,判断如果引用变量类型指定了NOT NULL约束,为返回的datatype的成员 bool notnull 属性赋值为true。
42+
在pl_gram.y 文件的 decl_statement 语法中,根据PLiSQL_type 的成员 bool notnull 为变量PLiSQL_variable *var的notnull属性赋值。这样就继承了引用变量的约束。
43+
44+
45+
=== 表名%ROWTYPE或视图名%ROWTYPE作为函数或存储过程的参数类型和函数返回值类型
46+
47+
在 ora_gram.y 中为 func_type 添加%ROWTYPE支持。
48+
49+
```
50+
| type_function_name attrs '%' ROWTYPE
51+
{
52+
$$ = makeTypeNameFromNameList(lcons(makeString($1), $2));
53+
$$->row_type = true;
54+
$$->location = @1;
55+
}
56+
```
57+
58+
在 TypeName 结构体中添加成员 bool row_type 用来标记是否指定了%ROWTYPE。
59+
60+
```
61+
typedef struct TypeName
62+
{
63+
bool pct_type; /* %TYPE specified? */
64+
bool row_type; /* %ROWTYPE specified? */
65+
```
66+
67+
在 LookupTypeName 函数中,如果 TypeName 的成员 row_type 为 TRUE,则根据TypeName的成员names中获得schema名与表名,然后获取表的typeoid。
68+
69+
=== INSERT语句增强
70+
71+
在 ora_gram.y 中添加新的语法,支持 VALUES 后面不带括号’(‘。
72+
73+
```
74+
values_clause_no_parens:
75+
VALUES columnref
76+
{
77+
SelectStmt *n = makeNode(SelectStmt);
78+
n->valuesLists = list_make1(list_make1($2));
79+
n->valuesIsrow = true;
80+
$$ = (Node *) n;
81+
}
82+
```
83+
84+
为结构体 SelectStmt 添加一个字段 bool valuesIsrow 表示 values 是一个 row。
85+
86+
在为insert语句做 transform 时,也就是函数 transformInsertStmt 中,在处理 INSERT ... VALUES 语句时,如果 valuesIsrow 为true,调用新函数 transformRowExpression,将 row_variable 转换成等价的 row_variable.field1,...,row_variable.fieldN。
87+
88+
=== UPDATE语句增强
89+
90+
在UPDATE语句做transform时候,也就是transformUpdateStmt的时候,如果是Oralce兼容模式,调用新添加的 transformIvyUpdateTargetList 函数。
91+
在这个新函数中,对于参数origTlist(即targetList)中没有名字为row的情况,按原来UPDATE的transform流程执行 transformUpdateTargetList 函数。
92+
93+
对参数origTlist中有名字为row的情况:因为PostgreSQL中row可以作为列名,而Oracle 中row是保留关键字,不可以作为列名,所以需要区分row是否是表中的列,如果row不是要更新的表中的列,则调用新函数 transformUpdateRowTargetList 把语句
94+
```
95+
UPDATE table_name SET ROW = row_variable [WHERE …];
96+
```
97+
转换成等价的
98+
```
99+
UPDATE table_name SET table_name.column1 = row_variable.column1, table_name.column2 = row_variable.filed2,… table_name.columnN = row_variable.columnN [WHERE …];
100+
```
101+
102+
如果语句“UPDATE table_name SET ROW = row_variable”中的变量 row_variable不是组合类型,就按原来UPDATE的transform流程执行 transformUpdateTargetList 函数;
103+
如果语句中的变量 row_variable 是组合类型,表中的名为row的那一列也是组合类型,并且两者的类型Oid也一样,则按原来UPDATE的transform流程执行 transformUpdateTargetList 函数;
104+
其他所有情况,调用新函数 transformUpdateRowTargetList 处理;
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
2+
:sectnums:
3+
:sectnumlevels: 5
4+
5+
6+
= **功能概述**
7+
8+
IvorySQL提供了兼容Oracle的NLS参数功能,包含如下参数。
9+
10+
[cols="3,7"]
11+
|====
12+
|*参数名称*|*功能描述*
13+
|ivorysql.datetime_ignore_nls_mask | 表示日期格式是否忽略NLS参数影响,默认为0。
14+
|nls_length_semantics | 兼容Oracle的同名参数,表示char/varchar/varchar2的类型修饰符的大小单位是字节还是字符。
15+
|nls_date_format | 表示默认的日期格式,可以通过show命令查看,默认为‘YYYY-MM-DD’。
16+
|nls_timestamp_format | 兼容Oracle的同名参数,控制带时间的日期格式。
17+
|nls_timestamp_tz_format | 兼容Oracle的同名参数,控制带时区的日期时间格式。
18+
|nls_territory | 兼容Oracle的同名参数,指定数据库的默认区域。
19+
|nls_iso_currency | 兼容Oracle的同名参数,指定的国家和区域制定唯一的货币符号。
20+
|nls_currency | 兼容Oracle的同名参数,指定显示本地货币的符号,对应数字字符串格式中占位符L。
21+
|====
22+
23+
== 实现原理
24+
25+
=== `nls_length_semantics` 参数
26+
27+
IvorySQL中的数据类型存在一个属性修饰符 `typmod` ,是对类型的补充说明,比如在 `VARCHAR(n)` 类型中,`n` 就是类型修饰符。
28+
29+
在创建或修改表的列时可以指定长度类型,例如:
30+
```c
31+
ivorysql=# create table t1(name varchar2(2 byte));
32+
```
33+
34+
对于列类型为 `CHAR` 、 `VARCHAR` 和 `VARCHAR2` 字符型的列,当没有显式指定列的长度类型时,IvorySQL使用 `nls_length_semantics` 参数的值来决定长度类型,有 `byte` 和 `char` 两种值,默认为 `byte` 。
35+
36+
需要特别注意的是, `nls_length_semantics` 参数的值仅影响新创建的列,对已经存在的列不会产生任何影响。
37+
38+
在语法解析文件 ora_gram.y中,存在如下代码来根据 `nls_length_semantics` 把原本的 `char/varchar/varchar2` 类型改成 `oracharchar` 或者 `oracharbyte` :
39+
40+
```c
41+
CharacterWithLength: character '(' Iconst ')'
42+
{
43+
if (ORA_PARSER == compatible_db)
44+
{
45+
if (strcmp($1, "bpchar"))
46+
{
47+
if (nls_length_semantics == NLS_LENGTH_CHAR)
48+
$1 = "oravarcharchar";
49+
else
50+
$1 = "oravarcharbyte";
51+
}
52+
else
53+
{
54+
if (nls_length_semantics == NLS_LENGTH_CHAR)
55+
$1 = "oracharchar";
56+
else
57+
$1 = "oracharbyte";
58+
}
59+
60+
$$ = OracleSystemTypeName($1);
61+
$$->typmods = list_make1(makeIntConst($3, @3));
62+
$$->location = @1;
63+
}
64+
else
65+
{
66+
...
67+
}
68+
}
69+
;
70+
71+
```
72+
73+
IvorySQL 中数据类型 `oracharchar` 和 `oracharbyte` 的修饰符输入输出函数包括:
74+
```c
75+
oravarcharchartypmodout()
76+
oravarcharbytetypmodout()
77+
oracharbytetypmodout()
78+
oracharchartypmodout()
79+
```
80+
81+
上面这些函数调用C语言实现的函数 `anychar_typmodout()` ,后者根据 `nls_length_semantics` 的值来调整输出的内容是否包含 `byte/char` 的说明。
82+
83+
`nls_length_semantics` 另一个作用是限制表中的列长度:
84+
根据上述代码在语法解析文件 ora_gram.y中,如果原本的 `varchar` 类型被转换成了 `oracharchar` 类型,则函数 `oravarcharchar()` 会被调用,而 `pg_mbcharcliplen()` 函数计算字符长度,而不是字节长度。
85+
86+
```c
87+
Datum
88+
oravarcharchar(PG_FUNCTION_ARGS)
89+
{
90+
VarChar *source = PG_GETARG_VARCHAR_PP(0);
91+
int32 typmod = PG_GETARG_INT32(1);
92+
bool isExplicit = PG_GETARG_BOOL(2);
93+
int32 len,
94+
maxlen;
95+
size_t maxmblen;
96+
char *s_data;
97+
98+
len = VARSIZE_ANY_EXHDR(source);
99+
s_data = VARDATA_ANY(source);
100+
maxlen = typmod - VARHDRSZ;
101+
102+
/* No work if typmod is invalid or supplied data fits it already */
103+
if (maxlen < 0 || len <= maxlen)
104+
PG_RETURN_VARCHAR_P(source);
105+
106+
maxmblen = pg_mbcharcliplen(s_data, len, maxlen);
107+
108+
...
109+
}
110+
```
111+
112+
=== GUC参数 `datetime_ignore_nls_mask`
113+
114+
这个参数被定义为一个int值,低四位分别表示是否在相应的日期时间格式上忽略NLS参数的影响,掩码定义如下:
115+
```c
116+
#define ORADATE_MASK 0x01
117+
#define ORATIMESTAMP_MASK 0x02
118+
#define ORATIMESTAMPTZ_MASK 0x04
119+
#define ORATIMESTAMPLTZ_MASK 0x08
120+
```
121+
122+
在源代码中,这个GUC参数被用于下面这些函数:
123+
```c
124+
oradate_in()
125+
oratimestamp_in()
126+
oratimestampltz_in()
127+
oratimestamptz_in()
128+
```
129+
130+
如果相应的掩码被设置,则调用原生PG的处理函数,否则调用兼容代码并忽略NLS格式。
131+
132+
=== GUC参数 `nls_date_format`/`nls_timestamp_format`/`nls_timestamp_tz_format`
133+
134+
这三个GUC参数,在函数 `ora_do_to_timestamp()` 中做为格式字符串,对输入的字符串进行格式检查与模式识别。
135+
136+
下面是其默认值,可以通过设置其值为"pg"使其失效。"pg"表示禁用NLS特定行为,恢复为PostgreSQL的默认行为。
137+
```c
138+
char *nls_date_format = "YYYY-MM-DD";
139+
char *nls_timestamp_format = "YYYY-MM-DD HH24:MI:SS.FF6";
140+
char *nls_timestamp_tz_format = "YYYY-MM-DD HH24:MI:SS.FF6 TZH:TZM";
141+
```
142+
143+
=== GUC参数 `nls_currency`/`nls_iso_currency`/`nls_territory`
144+
145+
目前,`nls_territory` 和 `nls_iso_currency` 支持CHINA与AMERICA两个值。
146+
147+
默认值如下:
148+
149+
```c
150+
char *nls_territory = "AMERICA";
151+
char *nls_currency = "$";
152+
char *nls_iso_currency = "AMERICA";
153+
```
154+
155+
这三个参数将在oracle兼容函数 `to_number()` 中被使用。
156+
[NOTE]
157+
====
158+
`to_number()` 函数尚未实现。
159+
====

0 commit comments

Comments
 (0)