【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不兼容,且其中的确可能会包含非数字的字符,需要进行相关代码的改造。
相关推荐
oradh12 小时前
Oracle XTTS实现跨版本迁移和升级(Oracle 11g单库升级至19C RAC集群)
数据库·oracle·11g升级19c·xtts跨版本迁移和升级
z1234567898613 小时前
2026最新两款AI编程工具深度对比实测
java·数据库·ai编程
程序猿DD13 小时前
一个 API Key,统一调用大模型、生图和联网搜索
数据库·网关
TlSfoward14 小时前
抓包代理链路下的 TLS 指纹变化分析 TLSFOWARD抓包工具
数据库·爬虫·网络协议·搜索引擎·php
数智化管理手记14 小时前
账龄分析手工统计易遗漏?自动账龄分析工具怎么搭建
大数据·网络·数据库·人工智能·数据挖掘
LabVIEW开发15 小时前
NI Package Manager安装LabVIEW时InternalServerError错误
网络·数据库·labview·labview知识·labview功能·labview程序
SelectDB15 小时前
PostgreSQL 实时同步到 Apache Doris:一站式 CDC 解决方案
数据库
欲速不达882015 小时前
Mysql并发DML锁等待的优化
数据库·mysql
Database_Cool_15 小时前
AI Agent 长期记忆怎么存?阿里云 PolarDB-X 对接 Mem0 记忆框架实践
数据库·阿里云
会编程的土豆16 小时前
数据库范式
数据库·oracle