上一篇介绍了lightdb/postgresql public、pg_catalog schema的区别及pg_namespace概念,因为最近几个版本开发下来,遇到了很多兼容性挑战。所以这一节来专门讨论一下search_path,以及它是如何实现版本隔离和理论上可实现的租户隔离(实际上,lightdb在22.1-22.2实现了,但是不够放心22.3临时取消了,22.4大概率会加回来,因为这涉及到database级别immutable,所以比较复杂)。
search_path的用途是在语义分析的时候,确定函数、操作符、表等对象时让哪个命名空间中的生效。在Lightdb中,默认情况下,其取值为 "$user", public, lt_catalog。oracle模式,一般建议用户设置为"$user", public, lt_catalog, oracle。mysql模式则设置为"$user", public, lt_catalog, mysql。如果用户要添加一些额外的mysql/oracle兼容函数,可以考虑创建一个新的namepace,如$user_mysql_ext,然后在其中创建兼容函数,并添加到search_path的最后。这样当lightdb新版实现了兼容函数时,因为它会被添加到lt_catalog/mysql/oracle至少之一下,内置版本既能覆盖自定义版本,也不会带来额外的兼容性问题。可以从机制上很好的解决该兼容问题。
虽然search_path能够解决该问题,但是由于它太灵活,一定要避免随意设置,比如避免设置同时包含oracle和mysql,当然lightdb在内部就进行了规避,如果是开源postgresql,这是需要注意的。
对于oracle兼容,上述机制对自定义函数、存储过程、视图等都可以比较好的解决。但是对于oracle包(package),无论是内置包还是用户自定义包,事情要复杂一点。在lightdb中,oracle内置包按照schema存储,走search_path机制,自定义包是在lt_package中。包括集合类型、自定义类型、sys_refcursor、函数、过程等。
null以及特殊的一些函数如substring负数索引等存在一词多义的情况是兼容的主要问题,如下:
2022-11-14 20:04:19.330824T,DBeaver 22.0.2 - Metadata <lt_test>,zjh,lt_test,10.188.137.149(52068),client backend,PARSE,00000,2022-11-14 20:04:19 CST,0,180444,LOG: duration: 0.636 ms parse <unnamed>: SELECT t.oid,t.*,c.relkind,format_type(nullif(t.typbasetype, 0), t.typtypmod) as base_type_name, d.description FROM pg_catalog.pg_type t LEFT OUTER JOIN pg_catalog.pg_type et ON et.oid=t.typelem LEFT OUTER JOIN pg_catalog.pg_class c ON c.oid=t.typrelid LEFT OUTER JOIN pg_catalog.pg_description d ON t.oid=d.objoid WHERE t.typname IS NOT NULL AND (c.relkind IS NULL OR c.relkind = 'c') AND (et.typcategory IS NULL OR et.typcategory <> 'C')
因为不止postgres库会被第三方工具访问,业务库也可能会,如上所示,而且是三方初始化的一部分,无法修改SQL语句(通过rewrite插件也不行,因为不知道哪些是初始化的一部分,哪些不是)。所以还不能简单的将库的默认兼容改成oracle或mysql,否则就可能结果不正确。所以通过search_path执行是个正确的策略,不过幸好jdbc和libpq都可以通过option -c选项设置。
对于is null,在oracle下如下:
zjh@lt_test=# set lightdb_syntax_compatible_type = "Oracle"; SET zjh@lt_test=# select '' is null; ?column? ---------- t (1 row)
在pg/mysql模式下,如下:
zjh@lt_test=# select '' is null; ?column? ---------- f (1 row)
所以,可能就会出现不同的结果。