【GaussDB】从 sqlplus 到 gsql:Shell 中执行 SQL 文件方案的迁移与改造
背景
在客户应用系统中,有一套shell脚本来操作ORACLE数据库执行业务存储过程,现数据库需要迁移到GaussDB,因此这套shell脚本也要进行移植改写
原始代码demo
- 存储过程定义
create or replace procedure up_test_biz(i1 number,i2 varchar2,o1 out number,o2 out varchar2) is
i number;
begin
o1:=0;
i:=i1;
i:=i2;
exception when others then
o1:=sqlcode;
o2:=sqlerrm;
end;
/
- shell脚本
#!/bin/bash
# 参数赋值
v1=1
v2=2a
# 调用SQL文件
sqlplus -S -L system/SysPassword1@192.168.163.227:1527/pdb1 @biz.sql ${v1} ${v2} >>biz.log
# 获取上一个命令成功失败结果
sqlresult="$?"
if [ ${sqlresult} -eq "0" ]
then
echo "biz 处理成功 ! " >>biz.log
else
echo "biz 处理失败 ! " >>biz.log
fi
echo "sqlresult is:" ${sqlresult}
- sql文件
WHENEVER SQLERROR EXIT FAILURE
WHENEVER OSERROR EXIT FAILURE
VARIABLE O_CODE NUMBER;
VARIABLE O_MSG VARCHAR2(4000);
declare
i1 number;
i2 varchar2(20);
begin
i1:= '&1';
i2:= '&2';
up_test_biz(i1,i2,:O_CODE,:O_MSG);
exception when others then
:O_CODE:=1;
end;
/
PRINT O_MSG
EXIT :O_CODE
QUIT;
一、原始代码关键点
存储过程定义
ORACLE的sqlcode是数字
shell文件
-
sqlplus 执行sql文件
使用@符号接SQL文件 -
传参给sql文件
在sqlplus@SQL文件之后 ,按顺序给参数值 -
日志重定向
使用>>符号重定向写入文件 -
命令执行结果标准值
使用"$%"能判断sqlplus命令执行结果是否成功
SQL文件
- 规定命令退出时标准值
WHENEVER SQLERROR EXIT FAILURE
WHENEVER OSERROR EXIT FAILURE
- 声明会话级变量
VARIABLE O_CODE NUMBER;
VARIABLE O_MSG VARCHAR2(4000);
- sql接收命令行入参
按顺序传递
i1:= '&1';
i2:= '&2';
- 存储过程出参赋给会话级变量以及会话级变量赋值
up_test_biz(i1,i2,:O_CODE,:O_MSG);
:O_CODE:=1;
- 打印变量
PRINT O_MSG
- 指定命令退出时标准值
EXIT :O_CODE
二、分析GaussDB支持情况
存储过程定义
GaussDB的sqlcode是字符类型,和ORACLE不一致,需要手动创建函数来显式转换(可参考文章https://www.darkathena.top/archives/opengauss-mogdb-sqlcode-datatype)。
shell文件
-
gsql 执行sql文件
使用-f指定sql文件 -
传参给sql文件
使用-v指定参数 -
日志重定向
可以使用>>,但是后面要接2>&1,才能把标准错误也输出到日志文件中 -
命令执行结果标准值
gsql默认无论执行成功还是报错,"$%"返回都是"0",需要加上参数-v ON_ERROR_STOP=1(或者在sql文件里执行\set ON_ERROR_STOP=1)
SQL文件
-
规定命令退出时标准值
需要shell调用gsql时加上参数-v ON_ERROR_STOP=1(或者在sql文件里执行\set ON_ERROR_STOP=1) ,同上 -
声明会话级变量
在gsql调用时指定-v 参数名="'值'"或者sql文件里执行\set 参数名="'值'",但这种本质上只是SQL字符串替换,即不是绑定变量,也无法接收sql的执行结果,建议使用自定义变量select set_config('cust.变量名称','变量值',false) -
sql接收命令行入参
使用:参数名接收,但plsql中不支持使用这种形式的变量,只能使用自定义变量(可参考文章https://www.darkathena.top/archives/openGauss-Unlocking-Hidden-Potential-Clever-Tricks-for-Custom-Parameters)
select
set_config('cust.v1',:v1,false) v1;
declare
i1 number;
begin
i1:=current_setting('cust.v1');
- 存储过程出参赋给会话级变量以及会话级变量赋值
gaussdb由于 -v或者\set的变量无法接收sql的执行结果,因此只能使用自定义变量来处理,此时需要使用匿名块额外声明变量,先做出参接收,然后将这个变量的值赋给自定义变量
declare
i1 number;
i2 varchar2(20);
--增加出参接收的变量
o1 number;
o2 varchar2(4000);
begin
i1:=current_setting('cust.v1');
i2:=current_setting('cust.v2');
up_test_biz(i1,i2,o1,o2);
--将出参赋值给自定义变量,以便脱离单条语句使用
set_config('cust.o_code',o1,false);
set_config('cust.o_msg',o2,false);
- 打印变量
可以直接select 或者在匿名块内使用raise输出
select current_setting('cust.o_msg');
begin
raise notice '%',current_setting('cust.o_msg');
end;
/
- 当业务返回错误或者SQL执行错误时,shell侧需要能识别到命令成功或者失败
gsql执行sql无论成功或者失败都是返回0,需要shell调用gsql时加上参数-v ON_ERROR_STOP=1(或者在sql文件里执行\set ON_ERROR_STOP=1),且由于匿名块最后捕获了错误,因此需要再根据匿名块记录的状态值来构造一个报错
begin
if current_setting('cust.o_code') <>'0' then
raise '%',current_setting('cust.o_msg');
else
raise notice '%',current_setting('cust.o_msg');
end if;
end;
/
三、整合方案
存储过程定义
--新建sqlcode转换函数
CREATE OR REPLACE FUNCTION pack_sql_state(sql_state text)
RETURNS INTEGER AS $$
DECLARE
result INTEGER := 0;
i INTEGER;
BEGIN
FOR i IN 1..5 LOOP
result := result << 6;
result := result | (ascii(substr(sql_state, -i, 1)) - ascii('0'));
END LOOP;
RETURN result;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION unpack_sql_state(sql_state INTEGER)
RETURNS text AS $$
DECLARE
result text := '';
i INTEGER;
BEGIN
FOR i IN 1..5 LOOP
result := result||(chr((sql_state & 63) + ascii('0'))) ;
sql_state := sql_state >> 6;
END LOOP;
RETURN result;
END;
$$ LANGUAGE plpgsql;
--存储过程改写(仅需识别sqlcode表达式,套一个转换函数即可,如果不想新建函数,也可以让o1赋值为1,把sqlcode拼接到o2里去)
create or replace procedure up_test_biz(i1 number,i2 varchar2,o1 out number,o2 out varchar2) is
i number;
begin
o1:=0;
i:=i1;
i:=i2;
o2:='biz success!';
exception when others then
o1:=pack_sql_state(sqlcode);
o2:=sqlerrm;
end;
/
shell脚本
#!/bin/bash
# 参数赋值
v1=1
v2=2a
# 调用SQL文件
gsql -h 192.168.163.131 -p 7456 -Uogadmin -WMogdb@123 -d postgres -f biz.sql -v ON_ERROR_STOP=on -v v1="'${v1}'" -v v2="'${v2}'" >>biz.log 2>&1
# 获取上一个命令成功失败结果
sqlresult="$?"
if [ ${sqlresult} -eq "0" ]
then
echo "biz 处理成功 ! " >>biz.log
else
echo "biz 处理失败 ! " >>biz.log
fi
echo "sqlresult is:" ${sqlresult}
sql文件
\set ON_ERROR_STOP ON
-- -v参数只能替换sql中的参数,不能替换plsql中的参数,因此使用自定义变量中转
select
set_config('cust.v1',:v1,false) v1,
set_config('cust.v2',:v2,false) v2;
declare
i1 number;
i2 varchar2(20);
o1 number;
o2 varchar2(4000);
begin
i1:=current_setting('cust.v1');
i2:=current_setting('cust.v2');
up_test_biz(i1,i2,o1,o2);
set_config('cust.o_code',o1,false);
set_config('cust.o_msg',o2,false);
exception when others then
set_config('cust.o_code',sqlcode,false);
set_config('cust.o_msg',sqlerrm,false);
end;
/
--打印结果
select current_setting('cust.o_msg') as o_msg;
--处理标准命令返回值
begin
if current_setting('cust.o_code') <>'0' then
raise '%',concat('o_code:',unpack_sql_state(current_setting('cust.o_code')),', o_msg:',current_setting('cust.o_msg'));
end if;
end;
/
四、检查日志
构造输出执行报错日志
- oracle:
old 5: i1:= '&1';
new 5: i1:= '1';
old 6: i2:= '&2';
new 6: i2:= '2a';
PL/SQL procedure successfully completed.
O_MSG
--------------------------------------------------------------------------------
ORA-06502: PL/SQL: numeric or value error: character to number conversion error
biz 处理失败 !
- gaussdb:
v1 | v2
----+----
1 | 2a
(1 row)
ANONYMOUS BLOCK EXECUTE
o_msg
---------------------------------------------
invalid input syntax for type numeric: "2a"
(1 row)
gsql:biz.sql:73: ERROR: o_code:22P02, o_msg:invalid input syntax for type numeric: "2a"
total time: 273 ms
biz 处理失败 !
构造输出执行正常日志
old 5: i1:= '&1';
new 5: i1:= '1';
old 6: i2:= '&2';
new 6: i2:= '2';
PL/SQL procedure successfully completed.
O_MSG
--------------------------------------------------------------------------------
biz success!
biz 处理成功 !
v1 | v2
----+----
1 | 2
(1 row)
ANONYMOUS BLOCK EXECUTE
o_msg
--------------
biz success!
(1 row)
ANONYMOUS BLOCK EXECUTE
total time: 276 ms
biz 处理成功 !
经核对,必要信息输出无遗漏,符合预期
五、总结
- 注意gsql执行sql的标准返回值,受参数
ON_ERROR_STOP影响,默认都是返回成功,即0,需设置ON_ERROR_STOP=on才会在错误时返回非0值 - 注意gsql的错误会输出到标准错误,在标准输出中不含错误信息,如需记录错误到文件,需要
2>&1,将标准错误重定向到标出输出 - Gauss/PG系的plsql绑定变量出入参一直不太友好,好在能使用sql来进行会话级自定义变量的设置,跨越单条sql引用上下文,且不依赖客户端命令。
- GaussDB在Oracle兼容模式下的sqlcode是字符类型,和Oracle不兼容,且其中的确可能会包含非数字的字符,需要进行相关代码的改造。
