接上一个文章:
Oracle通过透明网关以DBLINK形式连接MogDB的方法
问题遗留:字段名必须小写且加双引号
遗留了一个问题, SQL通过字段名访问时dblink,必须明确写小写且必须加双引号,如:
运行以下SQL会报错:
select relname from pg_class@mogdb;
select relname from pg_class@mogdb
*
ERROR at line 1:
ORA-00904: "RELNAME": invalid identifier
原因分析
什么原因呢?
可以看到,报错时,字段名是大写的,因为Oracle默认认为,如果select 字段名不加双引号,就会转换为大写字段名。
而pg系(包括opengauss/MogDB), 字段名默认小写。
跟踪了一下,透明网关通过运行以下语句
在$ORACLE_HOME/hs/log/*.trc里面可以看到:
Entered hgodtab at 2022/09/08-12:56:20
count:1
table: pg_class
SQL text from hgodtab, id=0, len=36 ...
00: 73656C65 6374202A 2066726F 6D202270 [select * from "p]
10: 675F636C 61737322 20776865 72652031 [g_class" where 1]
20: 203D2030 [ = 0]
Entered hgodscr_process_sellist_description at 2022/09/08-12:56:20
Entered hgopcda at 2022/09/08-12:56:20
Column:1(relname): dtype:12 (VARCHAR), prc/scl:63/0, nullbl:0, octet:63, sign:1, radix:0
hoadaMOD bit-values found (0x200:TREAT_AS_CHAR,0x20:NEGATIVE_HOADADTY)
DTY NULL-OK LEN MAXBUFLEN PR/SC CST IND MOD name
12 VARCHAR N 63 63 0/ 0 0 0 200 relname
-18 UNSIGNED LONG N 4 4 0/ 0 0 0 20 relnamespace
-18 UNSIGNED LONG N 4 4 0/ 0 0 0 20 reltype
-18 UNSIGNED LONG N 4 4 0/ 0 0 0 20 reloftype
-18 UNSIGNED LONG N 4 4 0/ 0 0 0 20 relowner
-18 UNSIGNED LONG N 4 4 0/ 0 0 0 20 relam
-18 UNSIGNED LONG N 4 4 0/ 0 0 0 20 relfilenode
-18 UNSIGNED LONG N 4 4 0/ 0 0 0 20 reltablespace
8 DOUBLE N 8 8 53/ 0 0 0 0 relpages
8 DOUBLE N 8 8 53/ 0 0 0 0 reltuples
4 INTEGER N 4 4 0/ 0 0 0 0 relallvisible
-18 UNSIGNED LONG N 4 4 0/ 0 0 0 20 reltoastrelid
-18 UNSIGNED LONG N 4 4 0/ 0 0 0 20 reltoastidxid
-18 UNSIGNED LONG N 4 4 0/ 0 0 0 20 reldeltarelid
-18 UNSIGNED LONG N 4 4 0/ 0 0 0 20 reldeltaidx
-18 UNSIGNED LONG N 4 4 0/ 0 0 0 20 relcudescrelid
-18 UNSIGNED LONG N 4 4 0/ 0 0 0 20 relcudescidx
12 VARCHAR N 5 5 0/ 0 0 0 200 relhasindex
12 VARCHAR N 5 5 0/ 0 0 0 200 relisshared
1 CHAR N 1 1 0/ 0 0 0 200 relpersistence
1 CHAR N 1 1 0/ 0 0 0 200 relkind
5 SMALLINT N 2 2 0/ 0 0 0 0 relnatts
5 SMALLINT N 2 2 0/ 0 0 0 0 relchecks
12 VARCHAR N 5 5 0/ 0 0 0 200 relhasoids
12 VARCHAR N 5 5 0/ 0 0 0 200 relhaspkey
12 VARCHAR N 5 5 0/ 0 0 0 200 relhasrules
12 VARCHAR N 5 5 0/ 0 0 0 200 relhastriggers
12 VARCHAR N 5 5 0/ 0 0 0 200 relhassubclass
-7 BIT N 1 1 0/ 0 0 0 20 relcmprs
12 VARCHAR N 5 5 0/ 0 0 0 200 relhasclusterkey
12 VARCHAR N 5 5 0/ 0 0 0 200 relrowmovement
1 CHAR N 1 1 0/ 0 0 0 200 parttype
12 VARCHAR N 255 255 0/ 0 0 0 200 relfrozenxid
12 VARCHAR Y 255 255 0/ 0 0 0 200 relacl
12 VARCHAR Y 255 255 0/ 0 0 0 200 reloptions
1 CHAR Y 1 1 0/ 0 0 0 200 relreplident
-18 UNSIGNED LONG Y 4 4 0/ 0 0 0 20 relfrozenxid64
-18 UNSIGNED LONG Y 4 4 0/ 0 0 0 20 relbucket
12 VARCHAR Y 255 255 0/ 0 0 0 200 relbucketkey
-18 UNSIGNED LONG Y 4 4 0/ 0 0 0 20 relminmxid
因此,大小写不匹配,就报错了。
如何解决呢?显然,我们手上没有Oracle gateway的源码。但幸运的是,opengauss connector odbc的源码(后面简称OG ODBC)是有的。
OG ODBC源码分析
会是什么地方返回这个信息呢?
根据这个函数hgodscr_process_sellist_description 的名称猜测,大概率是调用ODBC的 SQLDecribeCol函数。而SQLDecribeCol在OG ODBC里对应的是PGAPI_DecribeCol

PGAPI_DecribeCol在results.c 里面
检查代码发现,字段名返回的是szColName这个变量。

变量在这里赋的值,是col_name(Local变量)的copy.

因此,在这里,把对应的col_name 改为大写,则Oracle gateway获取的列名就是大写。
修改源码
简单修改:

考虑到不能变动默认行为,新增了一个ODBC参数ForceUpperCaseIdentifier及变量force_upper_case_identifier,只有变量force_upper_case_identifier为真时,才会做这个动作,也就是说,如果不是特意修改force_upper_case_identifier这个变量,行为上和以前表现一样。
这个参数怎么加呢?可以参考另一个参数 LowerCaseIdentifier.
包括:
- 定义一个INI参数名
dlg_specific.h:#define INI_FORCEUPPERCASEIDENTIFIER "ForceUpperCaseIdentifier"
2,在Struct ConnInfo里面新增一个变量(位置根据lower_case_identifier找)
psqlodbc.h: signed char force_upper_case_identifier;
3,默认初始化为 -1 (False),(位置根据lower_case_identifier找)
dlg_specific.c: conninfo->force_upper_case_identifier = -1;
4, 根据INI配置文件里的值进行设置(位置根据lower_case_identifier找)
dlg_specific.c: ci->force_upper_case_identifier = atoi(temp);
dlg_specific.c: CORR_VALCPY(force_upper_case_identifier);
加上对results.c里的更改,就组成完整的代码了。
实施并测试
编译并安装OG ODBC
make && make install
调整上一篇文章中的odbc.ini ,在对应的DSN下面增加这一行
ForceUpperCaseIdentifier=1
重新测试,跑通了。

检查trc, 里面的字段名果然都变成大写了。

总结
对于实施人员而言,就是两个步骤
1, 使用定制版OG ODBC
2, odbc.ini里面增加 ForceUpperCaseIdentifier=1
新的遗留问题
当然,如果你在mogdb里面用了大小写敏感的字段名,想在Oracle里面通过双引号来访问他,就会不能用。
MogDB端:
create table test_case_intensive (test1 int, "Test2" int);
Oracle端:
13:58:58 SQL> select * from test_case_intensive@mogdb;
SQL> rollback;
select * from test_case_intensive@mogdb
*
ERROR at line 1:
ORA-28500: connection from ORACLE to a non-Oracle system returned this message:
ERROR: column a1.test2 does not exist;
No query has been executed with that handle {42703,NativeErr = 1}
ORA-02063: preceding 3 lines from MOGDB
13:59:30 SQL> select test1 from test_case_intensive@mogdb;
no rows selected13:59:44 SQL> select test2 from test_case_intensive@mogdb;
select test2 from test_case_intensive@mogdb
*
ERROR at line 1:
ORA-28500: connection from ORACLE to a non-Oracle system returned this message:
ERROR: column a1.test2 does not exist;
No query has been executed with that handle {42703,NativeErr = 1}
ORA-02063: preceding 3 lines from MOGDB
Elapsed: 00:00:00.00
13:59:51 SQL> rollback;
Rollback complete.
Elapsed: 00:00:00.00
14:00:32 SQL> select "Test2" from test_case_intensive@mogdb;
select "Test2" from test_case_intensive@mogdb
*
ERROR at line 1:
ORA-00904: "Test2": invalid identifier
Elapsed: 00:00:00.00
鱼和熊掌不可兼得,出现这种情况,只能是
- 把 odbc.ini里面的ForceUpperCaseIdentifier去掉,或者设置为0,
- 把init<GATEWAY_SID>.ora 里的HS_FDS_QUOTE_IDENTIFIER设为TRUE
- 然后Oracle段访问dblink时,统一加双引号了,默认的字段名用小写+双引号,默认表名用小写+双引号,特意大小写敏感字段名大小写根据实际情况加双引号。
修改odbc.ini/init.ora后:
14:04:18 SQL> select "test1","Test2" from "test_case_intensive"@mogdb;
no rows selected




