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

背景

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

原始代码demo

存储过程定义

sql 复制代码
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脚本

sql 复制代码
#!/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文件

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文件

规定命令退出时标准值

sql 复制代码
WHENEVER SQLERROR EXIT FAILURE
WHENEVER OSERROR EXIT FAILURE

声明会话级变量

sql 复制代码
VARIABLE O_CODE NUMBER;
VARIABLE O_MSG VARCHAR2(4000);

sql接收命令行入参
按顺序传递

sql 复制代码
i1:= '&1';
i2:= '&2';

存储过程出参赋给会话级变量以及会话级变量赋值

sql 复制代码
up_test_biz(i1,i2,:O_CODE,:O_MSG);

PRINT O_MSG

打印变量

sql 复制代码
PRINT O_MSG

指定命令退出时标准值

sql 复制代码
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

sql 复制代码
select 
set_config('cust.v1',:v1,false) v1;

declare
i1 number;
begin
i1:=current_setting('cust.v1');

存储过程出参赋给会话级变量以及会话级变量赋值
gaussdb由于 -v或者\set的变量无法接收sql的执行结果,因此只能使用自定义变量来处理,此时需要使用匿名块额外声明变量,先做出参接收,然后将这个变量的值赋给自定义变量

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输出

sql 复制代码
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),且由于匿名块最后捕获了错误,因此需要再根据匿名块记录的状态值来构造一个报错

sql 复制代码
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;
/

三、整合方案

存储过程定义

sql 复制代码
--新建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脚本

sql 复制代码
#!/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文件

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:

sql 复制代码
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:

sql 复制代码
 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 处理失败 ! 

构造输出执行正常日志

sql 复制代码
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 处理成功 ! 
sql 复制代码
 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不兼容,且其中的确可能会包含非数字的字符,需要进行相关代码的改造。
相关推荐
汽车仪器仪表相关领域1 天前
光轴精准校准,安全检测基石——JZD-1/2前照灯检测仪用校准灯项目实战分享
数据库·算法·安全·汽车·压力测试·可用性测试
爱可生开源社区1 天前
MySQL 优化从库延迟的一些思路
数据库·mysql·性能优化
week_泽1 天前
小程序云数据库增加操作_3
数据库·小程序
MindCareers1 天前
Beta Sprint Day 1-2: Alpha Issue Fixes Initiated + Mobile Project Setup
android·c语言·数据库·c++·qt·sprint·issue
煎蛋学姐1 天前
SSM校园兼职平台52t96(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面
数据库·用户管理·ssm 框架·校园兼职平台
独角鲸网络安全实验室1 天前
CVE-2025-61882深度分析:Oracle Concurrent Processing BI Publisher集成远程接管漏洞的技术原理与防御策略
数据库·网络安全·oracle·漏洞·ebs·cve-2025-61882·xml 注入
Leon-Ning Liu1 天前
Oracle 自动统计信息收集任务:维护窗口创建与删除
数据库·oracle
莳花微语1 天前
磐维数据库双中心容灾流复制集群搭建
服务器·数据库·oracle
小白学大数据1 天前
Redis 在定时增量爬虫中的去重机制与过期策略
开发语言·数据库·redis·爬虫