Oracle兼容的整体思路
GreatDB的Oracle的兼容性处理的优先原则如下:
- GreatDB/MySQL风格的SQL和存储过程可以直接MYSQL默认模式下工作
- 与GreatDB/MySQL风格的SQL和存储过程不冲突的Oracle兼容功能也可以在MYSQL默认模式下直接工作
- 与GreatDB/MySQL风格的SQL和存储过程存在语法或语义冲突的功能,需要用户显式设定工作模式
GreatDB对Oracle的兼容性主要通过以下方式实现:
原生支持:Oracle、MySQL等关系型数据库都支持或部分支持通用的SQL标准,对于部分简单语句,无需调整即可兼容
兼容扩展:GreatDB对于部分Oracle独有的函数和语法,在SQL引擎层实现对MySQL的扩展,如:MERGE INTO, CONNECT BY,该类型兼容无需设定,直接使用即可
兼容模式:在GreatDB中开启:SET sql_mode='ORACLE'; 可以在当前会话中开启Oracle兼容模式,该设定解决Oracle语法与MySQL语法存在语法或语义上的冲突时,需要用户选择Oracle兼容模式
数据类型的映射
数据类型映射属于:扩展兼容(无需设定sql_mode),实现方式为:简单别名,即:在解析阶段将关键词进行替换,如果使用CLOB创建的表,在系统内会被转换成LONGTEXT。
具体实现的映射包括:
| Oracle类型 | GreatDB类型 | 兼容性 |
|---|---|---|
| CLOB | LONGTEXT | 简单别名 |
| NUMBER | DECIMAL | 简单别名 |
| VARCHAR2 | VARCHAR | 简单别名 |
| PLS_INTEGER | INT | 简单别名 |
Oracle SQL语法兼容
GreatDB实现的扩展兼容(无需设定sql_mode), 包括:
- DATETIME INTERVAL
- EXECUTE IMMEDIATE
- EXPLAIN PLAN FOR
- INSERT ALL INTO
- MERGE INTO
- CONNECT BY [START WITH] | START WITH CONNECT BY
- ORACLE HINT
- ROWNUM
- PRIOR
- CONNECT_BY_ROOT
- LEVEL
- CONNECT_BY_ISLEAF
- CONNECT_BY_ISCYCLE
- SEQUENCE
- 子查询无别名
- (+) 外连接
- 空串''与null等价开关
- KEEP FIRST/LAST
- MINUS
- SELECT...OFFSET...FETCH
- SELECT...FOR UPDATE OF COLUMNS
- ...
GreatDB实现的兼容模式(需设定sql_mode),包括:
- DATETIME运算(加减)
- 带双引号的存储过程创建
- MATERIALIZED VIEW
- DELETE语句支持不带FROM
- ORDER BY兼容
- RATIO_TO_REPORT
- SELECT...FOR UPDATE WAIT N
- UPDATE SET 多字段更新
- SQLCODE_SQLERRM_FUNCTION
- 全局临时表
- ...
Oracle函数兼容
GreatDB实现的扩展兼容(无需设定sql_mode),包括:
- ADD_MONTHS
- CHR
- DECODE
- DUMP
- SYS_GUID
- INITCAP
- LENGTHB
- LIST_AGG
- MONTHS_BETWEEN
- NVL
- NVL2
- REGEXP_COUNT
- REPLACE
- SUBSTRB
- SYSTIMESTAMP
- TO_CHAR
- TO_DATE
- TO_NUMBER
- TO_TIMESTAMP
- TRANSLATE
- TRUNC
- VSIZE
- SYS_CONNECT_BY_PATH
- ...
GreatDB实现的兼容模式(需设定sql_mode=oracle),包括:
- INSTR
- LPAD/RPAD
- TRIM/LTRIM/RTRIM
- SUBSTR
- LENGTH
- SYSDATE
- ...
NOTE: 以上函数在设定sql_mode后,行为与Oracle会更加接近;反之则保持GreatDB/MySQL的原生行为
存储过程的兼容
GreatDB实现的扩展兼容(无需设定sql_mode),包括:
- CREATE OR REPLACE
- ...
GreatDB实现的兼容模式(需设定sql_mode=oracle),包括:
存储过程/函数基础结构兼容
CURSOR游标
REF_CURSOR
EXIT/EXIT WHEN
FOR LOOP
FORALL LOOP
SELECT BULK_INTO
VAR_TYPE
TRIGGER
TYPE IS RECORD
TYPE IS TABLE
异常处理 EXCEPTION HANDLER
IF .. ELSIF支持
WHILE...LOOP... END LOOP
存储过程/函数支持默认参数(DEFAULT)
存储过程支持使用RETURN
匿名存储块
命名标记法传递参数
PACKAGE
...
NOTE: PACKAGE是ORACLE专有的功能,内部的存储过程和函数使用Oracle兼容的语法。
示例:
Oracle版本 存储过程:
CREATE OR REPLACE EDITIONABLE FUNCTION f0(delta INT DEFAULT 0) RETURN TIMESTAMP AS
cnt INT := 10;
BEGIN
RETURN SYSDATE + delta*cnt;
END;
SELECT f0(2) FROM DUAL ;
SELECT f0() FROM DUAL ;
GreatDB 原生版本 存储过程:
CREATE OR REPLACE FUNCTION f1(delta INT) RETURNS TIMESTAMP
BEGIN
DECLARE cnt INT DEFAULT 10;
SET delta = cnt * delta;
RETURN DATE_ADD(SYSDATE(), INTERVAL delta DAY);
END;
SELECT f0(2) FROM DUAL ;
GreatDB 兼容模式 版本存储过程:
SET sql_mode='ORACLE';
CREATE OR REPLACE FUNCTION f0(delta INT DEFAULT 0) RETURN TIMESTAMP AS
cnt INT := 10;
BEGIN
RETURN SYSDATE + delta*cnt;
END;
SELECT f0(2) FROM DUAL ;
SELECT f0() FROM DUAL ;
常见问题
引入兼容模式或者说 SQL_MODE='ORACLE' 对版本平滑升级有什么影响?
5.0.6中存在部分要求兼容模式的函数,在5.0.7以及后续版本中要求开启sql_mode='ORACLE'才能使用,但在5.0.7中开启兼容mode后,存储过程要按照oracle的格式进行调整会,因此会导致不兼容,无法平滑升级。该部分不兼容只能通过手动重建方式进行调整。如果存储过程完全按照GreatDB原生方式或者按照
兼容模式方式书写,则都可以平滑升级。oracle_mode是一个组合mode,存在添加的新的行为限制,为了兼容历史版本的行为,需要配合 shrink_sql_mode 进行组合
能否不显式使用 SQL_MODE='ORACLE' 来触发兼容模式?
- 在GreatDB配置档中添加
SQL_MODE='ORACLE'将导致系统功能异常,原因是:系统所依赖的基础存储过程并没有被执行兼容改造 - 在 GreatDB 初始化完成后,添加在GreatDB配置档中添加
SQL_MODE='ORACLE'并重启系统,默认新建连接使用oracle mode ( MGR方式的集群不确定是否可用) - 在 GreatDB 启动的数据库中进行全局修改 set global sql_mode=oracle,这样默认新建连接就是使用 oracle 模式, 缺点每次重启都需要重新设置一次,集群这样配置不确定生效
- 在特定场景下,可以将
SQL_MODE='ORACLE'在会话配置中固化,如:在JDBC连接串中添加:session_variables=sql_mode=ORACLE,这样连接中SQL就无需显式设定兼容模式
能否在存储过程内部切换兼容模式?
在GreatDB中兼容模式或者说sql_mode是存储过程的固有属性,决定了该存储过程如何被解析和运行,在存储过程中修改sql_mode 可以改变当前的兼容模式,但当该存储过程运行结束返回后,上层上下文当中的兼容模式会被还原(与存储过程运行前一致)
举例说明:
SET SQL_MODE=DEFAULT;
DROP PROCEDURE mode_test;
DELIMITER $ ;
CREATE PROCEDURE mode_test() BEGIN
SELECT LENGTH('测试'), @@SQL_MODE;
SET SQL_MODE='ORACLE';
SELECT LENGTH('测试'), @@SQL_MODE;
END ;
$
DELIMITER ; $
SET SQL_MODE=DEFAULT;
SELECT 'before test 1', LENGTH('测试'),@@SQL_MODE;
CALL mode_test();
SELECT 'after test 1', LENGTH('测试'),@@SQL_MODE;
该存储过程运行结果如下:
> SET SQL_MODE=DEFAULT;
-- 执行测试前调用LENGTH,输出按照GreatDB默认行为,显示字符字节长度=6
> SELECT 'before test 1', LENGTH('测试'),@@SQL_MODE;
| before test 1 | LENGTH('测试') | @@SQL_MODE |
| before test 1 | 6 | ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO NO_ENGINE_SUBSTITUTION |
1 row in set (0.00 sec)
> CALL mode_test();
-- 存储过程中,前调用LENGTH,输出按照GreatDB默认行为,显示字符字节长度=6
| LENGTH('测试') | @@SQL_MODE |
| 6 | ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION |
1 row in set (0.00 sec)
-- 存储过程中,设定Oracle兼容模式,调用LENGTH,输出按照Oracle兼容行为,显示字符字节长度=2
| LENGTH('测试') | @@SQL_MODE |
| 2 | PIPES_AS_CONCAT,ANSI_QUOTES,IGNORE_SPACE,ORACLE,SYSDATE_IS_NOW |
1 row in set (0.00 sec)
Query OK, 0 rows affected (0.00 sec)
> SELECT 'after test 1', LENGTH('测试'),@@SQL_MODE;
-- 返回存储过程后,调用LENGTH,输出按照GreatDB默认行为,显示字符字节长度=6
| before test 1 | 6 | ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO NO_ENGINE_SUBSTITUTION |
1 row in set (0.00 sec)
能否在触发器和事件中使用?
在GreatDB中定义触发器前设定兼容模式或者说sql_mode将影响触发器内SQL或函数的兼容风格,但由于GreatDB的触发器和事件兼容功能尚未发布,因此触发器和事件本身的语法还是使用GreatDB/MySQL原生的方式




