Skip to content

Commit b03f58e

Browse files
authored
Merge pull request #173 from bigplaice/pli_func_proc_on_master
plisql doc for function and procedure
2 parents 6347a79 + 8dda4f0 commit b03f58e

File tree

6 files changed

+1561
-120
lines changed

6 files changed

+1561
-120
lines changed

CN/modules/ROOT/nav.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
**** xref:master/6.3.2.adoc[OUT 参数]
2727
**** xref:master/6.3.4.adoc[%TYPE、%ROWTYPE]
2828
**** xref:master/6.3.5.adoc[NLS 参数]
29+
**** xref:master/6.3.6.adoc[函数与存储过程]
2930
*** xref:master/6.4.adoc[国标GB18030]
3031
** Oracle兼容功能列表
3132
*** xref:master/7.1.adoc[1、框架设计]
Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
:sectnums:
2+
:sectnumlevels: 5
3+
4+
:imagesdir: ./_images
5+
6+
= PL/iSQL函数与存储过程
7+
8+
== 目的
9+
10+
PostgreSQL支持函数(FUNCTION)和存储过程(PROCEDURE),但是在语法上和Oracle有差异,为了让Oracle的PLSQL语句可以在IvorySQL上执行,也就是“语法兼容”,IvorySQL采取这样的解决方案:如果Oracle的子句在IvorySQL中存在相同功能的子句,则直接映射为对应的IvorySQL子句,否则只实现其语法,不实现其功能。
11+
12+
== 实现说明
13+
14+
PL/iSQL是IvorySQL中的过程语言名称,专门用来兼容Oracle的PLSQL语句。为了兼容Oracle风格的函数与存储过程语法,需要对psql客户端工具、SQL端以及PL/iSQL端做相应处理。
15+
16+
=== 客户端工具psql
17+
18+
Oracle的sqlplus工具使用斜线(/)来结束函数和存储过程,IvorySQL的客户端工具psql需要兼容同样的语法,也就是说我们常规的遇到分号发送语句给服务端的机制,当遇到Oracle风格的函数和存储过程命令时失效,改为使用斜线(/)发送命令。
19+
20+
为此在 PsqlScanStateData 结构体中增加如下字段:
21+
```
22+
bool cancel_semicolon_terminator; /* not send command when semicolon found */
23+
24+
/*
25+
* State to track boundaries of Oracle ANONYMOUS BLOCK.
26+
* Case 1: Statements starting with << ident >> is Oracle anonymous block.
27+
*/
28+
int token_count; /* # of tokens, not blank or newline since start of statement */
29+
bool anonymous_label_start; /* T if the first token is "<<" */
30+
bool anonymous_label_ident; /* T if the second token is an identifier */
31+
bool anonymous_label_end; /* T if the third token is ">>" */
32+
33+
/*
34+
* Case 2: DECLARE BEGIN ... END is Oracle anonymous block syntax.
35+
* DECLARE can also be a PostgreSQL cursor declaration statement, we need to tell this.
36+
*/
37+
bool maybe_anonymous_declare_start; /* T if the first token is DECLARE */
38+
int token_cursor_idx; /* the position of keyword CURSOR in SQL statement */
39+
40+
/*
41+
* Case 3: DECLARE BEGIN ... END is Oracle anonymous block syntax.
42+
* BEGIN can also be a PostgreSQL transaction statement.
43+
*/
44+
bool maybe_anonymous_begin_start; /* T if the first token is BEGIN */
45+
46+
```
47+
48+
同时修改 ora_psqlscan.l,添加和修改相应的词法规则, 以下是代码片段示例:
49+
```
50+
{
51+
if (is_oracle_slash(cur_state, cur_state->scanline))
52+
{
53+
/* Terminate lexing temporarily */
54+
cur_state->cancel_semicolon_terminator = false;
55+
cur_state->maybe_anonymous_declare_start = false;
56+
cur_state->maybe_anonymous_begin_start = false;
57+
cur_state->anonymous_label_start = false;
58+
cur_state->anonymous_label_ident = false;
59+
cur_state->anonymous_label_end = false;
60+
cur_state->start_state = YY_START;
61+
cur_state->token_count = 0;
62+
cur_state->token_cursor_idx = 0;
63+
cur_state->identifier_count = 0;
64+
cur_state->begin_depth = 0;
65+
cur_state->ora_plsql_expect_end_symbol = END_SYMBOL_INVALID;
66+
return LEXRES_SEMI;
67+
}
68+
ECHO;
69+
```
70+
71+
Psql工具需要检测斜线/的含义,避免将注释等部分的斜线判定为结束符,为此在oracle_fe_utils/ora_psqlscan.l文件中增加一个单独的接口is_oracle_slash来检测:
72+
```
73+
bool
74+
is_oracle_slash(PsqlScanState state, const char *line)
75+
{
76+
bool result = false;
77+
78+
switch (state->start_state)
79+
{
80+
case INITIAL:
81+
case xqs: /* treat these like INITIAL */
82+
{
83+
int len, i;
84+
bool has_slash = false;
85+
86+
len = strlen(line);
87+
for (i = 0; i < len; i++)
88+
{
89+
/* allow special char */
90+
if (line[i] == '\t' ||
91+
line[i] == '\n' ||
92+
line[i] == '\r' ||
93+
line[i] == ' ')
94+
continue;
95+
96+
if (line[i] == '/')
97+
{
98+
if (has_slash)
99+
break;
100+
has_slash = true;
101+
continue;
102+
}
103+
/* others */
104+
break;
105+
}
106+
107+
if (i == len && has_slash)
108+
result = true;
109+
}
110+
break;
111+
default:
112+
break;
113+
}
114+
115+
return result;
116+
}
117+
118+
```
119+
120+
=== SQL端
121+
122+
SQL端要能够识别函数和存储过程的创建语法,这是通过修改ora_base_yylex来实现的。这个函数预取并缓存token,如果是Oracle语法格式则组织一个SCONST发送给PLSQL端,否则从堆栈中获取之前预读的token,按照原生PG的逻辑进行处理。
123+
124+
在ora_base_yy_extra_type数据结构中增加如下字段:
125+
```
126+
/*
127+
* The native PG only cache one-token info include yylloc, yylval and token
128+
* number in yyextra, IvorySQL cache multiple tokens info using two arrays.
129+
*/
130+
int max_pushbacks; /* the max size of cache array */
131+
int loc_pushback; /* # of used tokens */
132+
int num_pushbacks; /* # of cached tokens */
133+
int *pushback_token; /* token number array */
134+
TokenAuxData *pushback_auxdata; /* auxdata array */
135+
136+
OraBodyStyle body_style;
137+
int body_start;
138+
int body_level;
139+
```
140+
141+
增加token堆栈的操作接口:
142+
|====
143+
| push_back_token
144+
| forward_token
145+
| ora_internal_yylex
146+
| internal_yylex
147+
|====
148+

149+
ora_base_yylex函数中在创建函数、过程、匿名块时会预读部分token,使用上述结构缓存到堆栈中,是为了构造一个符合Oracle PL/SQL语法的SCONST发送给PL/iSQL端去处理。具体请参考源代码。
150+
151+
152+
=== PL/iSQL端
153+
154+
该部分主要修改了pl_gram.y文件,以兼容PLSQL的函数和存储过程语法,在不影响PG原生的PL/pgSQL的前提下去兼容Oracle PL/SQL语法形式,如下是DECLARE部分兼容的代码示例,更多请参考IvorySQL源代码。
155+
156+
```
157+
/*
158+
* The declaration section of the outermost block in Oracle does not have the DECLARE keyword.
159+
*/
160+
ora_outermost_pl_block: ora_decl_sect K_BEGIN proc_sect exception_sect K_END opt_label
161+
{
162+
PLiSQL_stmt_block *new;
163+
164+
new = palloc0(sizeof(PLiSQL_stmt_block));
165+
166+
new->cmd_type = PLISQL_STMT_BLOCK;
167+
new->lineno = plisql_location_to_lineno(@2);
168+
new->stmtid = ++plisql_curr_compile->nstatements;
169+
new->label = $1.label;
170+
new->n_initvars = $1.n_initvars;
171+
new->initvarnos = $1.initvarnos;
172+
new->body = $3;
173+
new->exceptions = $4;
174+
175+
check_labels($1.label, $6, @6);
176+
plisql_ns_pop();
177+
178+
$$ = (PLiSQL_stmt *)new;
179+
}
180+
;
181+
182+
ora_decl_sect: opt_block_label opt_ora_decl_start opt_ora_decl_stmts
183+
{
184+
if ($2)
185+
{
186+
if ($1 == NULL)
187+
{
188+
plisql_ns_push(NULL, PLISQL_LABEL_BLOCK);
189+
}
190+
}
191+
}
192+
opt_ora_decl_stmts
193+
{
194+
if ($4)
195+
{
196+
plisql_IdentifierLookup = IDENTIFIER_LOOKUP_NORMAL;
197+
$$.label = ($1 == NULL ? plisql_curr_compile->namelabel : $1);
198+
if ($2 && $1 == NULL)
199+
$$.popname = true;
200+
else
201+
$$.popname = false;
202+
/* Remember variables declared in decl_stmts */
203+
$$.n_initvars = plisql_add_initdatums(&($$.initvarnos));
204+
}
205+
else
206+
{
207+
plisql_IdentifierLookup = IDENTIFIER_LOOKUP_NORMAL;
208+
$$.label = ($1 == NULL ? plisql_curr_compile->namelabel : $1);
209+
$$.n_initvars = 0;
210+
if ($2 && $1 == NULL)
211+
$$.popname = true;
212+
else
213+
$$.popname = false;
214+
$$.initvarnos = NULL;
215+
}
216+
}
217+
;
218+
219+
opt_ora_decl_start: K_DECLARE
220+
{
221+
/* Forget any variables created before block */
222+
plisql_add_initdatums(NULL);
223+
/*
224+
* Disable scanner lookup of identifiers while
225+
* we process the decl_stmts
226+
*/
227+
plisql_IdentifierLookup = IDENTIFIER_LOOKUP_DECLARE;
228+
$$ = true;
229+
}
230+
| /*EMPTY*/
231+
{
232+
/* Forget any variables created before block */
233+
plisql_add_initdatums(NULL);
234+
/*
235+
* Disable scanner lookup of identifiers while
236+
* we process the decl_stmts
237+
*/
238+
plisql_IdentifierLookup = IDENTIFIER_LOOKUP_DECLARE;
239+
$$ = false;
240+
}
241+
;
242+
243+
opt_ora_decl_stmts:
244+
ora_decl_stmts
245+
{
246+
$$ = true;
247+
}
248+
| /*EMPTY*/
249+
{
250+
$$ = false;
251+
}
252+
253+
ora_decl_stmts: ora_decl_stmts ora_decl_stmt
254+
| ora_decl_stmt
255+
;
256+
257+
ora_decl_stmt: decl_statement
258+
;
259+
260+
```
261+

0 commit comments

Comments
 (0)