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

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

背景

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

原始代码demo

  1. 存储过程定义
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;
/
  1. shell脚本
bash 复制代码
#!/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文件
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. 规定命令退出时标准值
sql 复制代码
WHENEVER SQLERROR EXIT FAILURE
WHENEVER OSERROR EXIT FAILURE
  1. 声明会话级变量
sql 复制代码
VARIABLE O_CODE NUMBER;
VARIABLE O_MSG VARCHAR2(4000);
  1. sql接收命令行入参
    按顺序传递
sql 复制代码
i1:= '&1';
i2:= '&2';
  1. 存储过程出参赋给会话级变量以及会话级变量赋值
sql 复制代码
up_test_biz(i1,i2,:O_CODE,:O_MSG);
sql 复制代码
:O_CODE:=1;
  1. 打印变量
sql 复制代码
PRINT O_MSG
  1. 指定命令退出时标准值
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');
  1. 存储过程出参赋给会话级变量以及会话级变量赋值
    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); 
  1. 打印变量
    可以直接select 或者在匿名块内使用raise输出
sql 复制代码
select current_setting('cust.o_msg');
sql 复制代码
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),且由于匿名块最后捕获了错误,因此需要再根据匿名块记录的状态值来构造一个报错
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脚本

bash 复制代码
#!/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;
/

四、检查日志

构造输出执行报错日志

  1. oracle:
log 复制代码
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:
log 复制代码
 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 处理失败 ! 

构造输出执行正常日志

log 复制代码
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 处理成功 ! 
log 复制代码
 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不兼容,且其中的确可能会包含非数字的字符,需要进行相关代码的改造。
相关推荐
Wpa.wk2 小时前
接口自动化 - 了解接口自动化框架RESTAssured (Java版)
java·数据库·自动化
二等饼干~za8986682 小时前
GEO优化---关键词搜索排名源码开发思路分享
大数据·前端·网络·数据库·django
程序员柒叔2 小时前
Dify 集成-向量数据库
数据库·milvus·向量数据库·工作流·dify·向量库
月明长歌2 小时前
MySQL 视图:把复杂查询封装成表,并且还能控权限、做解耦
数据库·mysql
l1t2 小时前
postgresql 18版bytea 类型转换的改进
数据库·postgresql
小蒜学长2 小时前
python餐厅点餐系统(代码+数据库+LW)
数据库·spring boot·后端·python
岳麓丹枫0012 小时前
PostgreSQL 中 create database 中的注意事项
数据库·postgresql
梦想画家2 小时前
告别关键词!PostgreSQL+pgvector 玩转语义和图像检索
数据库·postgresql
DarkAthena2 小时前
【DOCKER+ORACLE】使用docker-compose一键拉起一个ORACLE-ADG一主一备环境
docker·oracle·容器