暂无图片
暂无图片
2
暂无图片
暂无图片
暂无图片

【GaussDB】从 sqlplus 到 gsql:Shell 中执行 SQL 文件方案的迁移与改造

【GaussDB】从 sqlplus 到 gsql:Shell 中执行 SQL 文件方案的迁移与改造

背景

在客户应用系统中,有一套shell脚本来操作ORACLE数据库执行业务存储过程,现数据库需要迁移到GaussDB,因此这套shell脚本也要进行移植改写

原始代码demo

  1. 存储过程定义
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; /
  1. 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}
  1. 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文件

  1. sqlplus 执行sql文件
    使用@符号接SQL文件

  2. 传参给sql文件
    在sqlplus @SQL文件之后 ,按顺序给参数值

  3. 日志重定向
    使用>>符号重定向写入文件

  4. 命令执行结果标准值
    使用"$%"能判断sqlplus命令执行结果是否成功

SQL文件

  1. 规定命令退出时标准值
WHENEVER SQLERROR EXIT FAILURE WHENEVER OSERROR EXIT FAILURE
  1. 声明会话级变量
VARIABLE O_CODE NUMBER; VARIABLE O_MSG VARCHAR2(4000);
  1. sql接收命令行入参
    按顺序传递
i1:= '&1'; i2:= '&2';
  1. 存储过程出参赋给会话级变量以及会话级变量赋值
up_test_biz(i1,i2,:O_CODE,:O_MSG);
:O_CODE:=1;
  1. 打印变量
PRINT O_MSG
  1. 指定命令退出时标准值
EXIT :O_CODE

二、分析GaussDB支持情况

存储过程定义

GaussDB的sqlcode是字符类型,和ORACLE不一致,需要手动创建函数来显式转换(可参考文章https://www.darkathena.top/archives/opengauss-mogdb-sqlcode-datatype)。

shell文件

  1. gsql 执行sql文件
    使用 -f 指定sql文件

  2. 传参给sql文件
    使用 -v 指定参数

  3. 日志重定向
    可以使用>>,但是后面要接 2>&1 ,才能把标准错误也输出到日志文件中

  4. 命令执行结果标准值
    gsql默认无论执行成功还是报错,"$%"返回都是"0",需要加上参数 -v ON_ERROR_STOP=1 (或者在sql文件里执行\set ON_ERROR_STOP=1)

SQL文件

  1. 规定命令退出时标准值
    需要shell调用gsql时加上参数 -v ON_ERROR_STOP=1 (或者在sql文件里执行\set ON_ERROR_STOP=1) ,同上

  2. 声明会话级变量
    在gsql调用时指定-v 参数名="'值'"或者sql文件里执行\set 参数名="'值'",但这种本质上只是SQL字符串替换,即不是绑定变量,也无法接收sql的执行结果,建议使用自定义变量select set_config('cust.变量名称','变量值',false)

  3. 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');
  1. 存储过程出参赋给会话级变量以及会话级变量赋值
    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);
  1. 打印变量
    可以直接select 或者在匿名块内使用raise输出
select current_setting('cust.o_msg');
begin raise notice '%',current_setting('cust.o_msg'); end; /
  1. 当业务返回错误或者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; /

四、检查日志

构造输出执行报错日志

  1. 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 处理失败 ! 

  1. 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 处理成功 ! 

经核对,必要信息输出无遗漏,符合预期

五、总结

  1. 注意gsql执行sql的标准返回值,受参数 ON_ERROR_STOP影响,默认都是返回成功,即0 ,需设置ON_ERROR_STOP=on才会在错误时返回非0
  2. 注意gsql的错误会输出到标准错误,在标准输出中不含错误信息,如需记录错误到文件,需要 2>&1 ,将标准错误重定向到标出输出
  3. Gauss/PG系的plsql绑定变量出入参一直不太友好,好在能使用sql来进行会话级自定义变量的设置,跨越单条sql引用上下文,且不依赖客户端命令。
  4. GaussDB在Oracle兼容模式下的sqlcode是字符类型,和Oracle不兼容,且其中的确可能会包含非数字的字符,需要进行相关代码的改造。
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

文章被以下合辑收录

评论