【openGauss】正则表达式次数符号“{}“在ORACLE和openGauss中的差异

一、前言

正则作为一种常用的字符串处理方式,在各种开发语言,甚至数据库中,都有自带的正则函数。但是正则函数有很多标准,不同标准对正则表达式的解析方式不一样,本次在迁移一个ORACLE数据库到openGauss时发现了一个关于 {}的差异点。

二、{}是做什么用的

在绝大部分的正则表达式规则中 {}表示对前面字符的重复次数,支持的形式为 {m}{m,}{,n}{m,n},其中m和n均为自然数,例如

表达式说明
b{1}匹配1次b
b{2,}匹配2次到无穷次b
b{,3}匹配0次到3次b
b{2,3}匹配2次到3次b

三、{}的使用歧义

以下三条SQL均可以在ORACLE中执行

--匹配一个 $符号,此时 {}里的1表示 $的出现次数
select regexp_substr('aaaa${1}bbb','(\${1})') from dual;

--匹配${0个或任意个数的1},此时{}以及{}内的字符按照字符串识别
select regexp_substr('aaaa${1}bbb','(\${1*})') from dual;

--匹配 ${一个空格加上0个或任意个数的1} ,此时{}以及{}内的字符按照字符串识别
select regexp_substr('aaaa${ }bbb','(\${ 1*})') from dual;

这里的规则在ORACLE中大概可以这么描述:
{}内如果不满足 {m}{m,}{,n}{m,n}这四者之一的格式,则 {}不作为次数的声明符号,而是作为常规字符串进行识别。

但是上面第二个表达式在openGaussDB中会报错,因为这里还有一个规则:
如果 {}内的第一个字符是数字,则开始进入次数的解析逻辑,若解析不符合次数的规则,就报错。
查看openGauss源码,发现这段逻辑来自1998年的PG源码,数十年来未曾变过。
这里注意,此处并非BUG,只是正则标准不一致,我使用了7种开发语言来验证,发现JAVA和RUST中也同样是报错的,而PHP/JS/PYTHON/.NET/GO 中都不报错。

image-ssmg.png

image-woln.png

可以使用以下链接测试该正则表达式在不同开发语言中的表现
https://regex101.com/r/APc3is/1

四、相关源码

使用openGauss分析这个逻辑的时候,我断了几个点,找了几段源码

6       breakpoint     keep y   0x0000000000fc4cd7 in parseqatom(vars*, int, int, state*, state*, subre*) at regcomp.cpp:915
        breakpoint already hit 2 times
7       breakpoint     keep y   0x0000000000fc42c4 in parsebranch(vars*, int, int, state*, state*, int) at regcomp.cpp:719
        breakpoint already hit 2 times
8       breakpoint     keep y   0x0000000000fc5040 in parseqatom(vars*, int, int, state*, state*, subre*) at regcomp.cpp:965
9       breakpoint     keep y   0x0000000000fc510c in parseqatom(vars*, int, int, state*, state*, subre*) at regcomp.cpp:984


regc_lex.cpp, line 412.
regcomp.cpp, line 966.

当第一个字符是数字,而第二个不是期望的字符(0-9以及",“和”}"),就走到default报错

case '{':
            NEXT();
            m = scannum(v); //扫描数字
static int scannum(struct vars* v)
{
    int n = 0;

    while (SEE(DIGIT) && n < DUPMAX) {
        n = n * 10 + v->nextvalue;
        NEXT();
    }
    if (SEE(DIGIT) || n > DUPMAX) {
        ERR(REG_BADBR);
        return 0;
    }
    return n;
}
case L_EBND:
            switch (c) {
                case CHR('0'):
                case CHR('1'):
                case CHR('2'):
                case CHR('3'):
                case CHR('4'):
                case CHR('5'):
                case CHR('6'):
                case CHR('7'):
                case CHR('8'):
                case CHR('9'):
                    RETV(DIGIT, (chr)DIGITVAL(c)); // {1*} 会在处理1的时候走到这里
                    break;
                case CHR(','):
                    RET(',');
                    break;
                case CHR('}'): /* ERE bound ends with } */
                    if (INCON(L_EBND)) {
                        INTOCON(L_ERE);
                        if ((v->cflags & REG_ADVF) && NEXT1('?')) {
                            v->now++;
                            NOTE(REG_UNONPOSIX);
                            RETV('}', 0);
                        }
                        RETV('}', 1);
                    } else
                        FAILW(REG_BADBR);
                    break;
                case CHR('\\'): /* BRE bound ends with \} */
                    if (INCON(L_BBND) && NEXT1('}')) {
                        v->now++;
                        INTOCON(L_BRE);
                        RET('}');
                    } else
                        FAILW(REG_BADBR);
                    break;
                default:
                    FAILW(REG_BADBR); // {1*} 会在处理*的时候走到这里
                    break;
            }

有兴趣的可以自己下载源码去调试分析一下,这里我就不详细解读源码了。

五、其他国产数据库对{}的处理

DM8和YASHAN和ORACLE保持一致,能在 {}内不为次数时正确当成字符串;而其他几款基于PG、OG的数据库以及纯自研的OCEANBASE在这种情况下都会报错(mysql系不报错,但执行返回空)。

  • DM 8
SQL> select regexp_substr('aaaa${1}bbb','(\${1*})') ;

LINEID     REGEXP_SUBSTR('aaaa${1}bbb','(\${1*})')
---------- ---------------------------------------
1          ${1}
  • YASHAN 23
SQL> select regexp_substr('aaaa${1}bbb','(\${1*})') from dual;

REGEXP_SUBSTR('AAAA$
--------------------
${1}
  • KINGBASE 9
kingbase=# select regexp_substr('aaaa${1}bbb','(\${1*})') ;
kingbase-# /
ERROR:  invalid regular expression: invalid repetition count(s)
  • HIGHGO 6
highgo=# select regexp_substr('aaaa${1}bbb','(\${1*})') ;
ERROR:  invalid regular expression: invalid
  • GAUSSDB 503
gaussdb=# select regexp_substr('aaaa${1}bbb','(\${1*})') ;
ERROR:  invalid regular expression: invalid repetition count(s)
CONTEXT:  referenced column: regexp_substr
  • OPENGAUSS 6.0
openGauss=# select regexp_substr('aaaa${1}bbb','(\${1*})') ;
ERROR:  invalid regular expression: invalid repetition count(s)
CONTEXT:  referenced column: regexp_substr
  • GBASE 8c
postgres=# select regexp_substr('aaaa${1}bbb','(\${1*})') ;
ERROR:  invalid regular expression: invalid repetition count(s)
CONTEXT:  referenced column: regexp_substr
  • VASTBASE v2.2 build 16
postgres=# select regexp_substr('aaaa${1}bbb','(\${1*})') ;
ERROR:  invalid regular expression: invalid repetition count(s)
CONTEXT:  referenced column: regexp_substr
  • OCEANBASE 4.3
执行以下 SQL 失败
select regexp_substr('aaaa${1}bbb','(\${1*})') from dual
失败原因:
ErrorCode = 600, SQLState = 42000, Details = OBE-00600: internal error code, arguments: -5115, Got error 'U_REGEX_BAD_INTERVAL' from regexp

六、回到业务应用

其实本文中这种歧义用法,虽然在ORACLE中不报错,但是正确的编码方式应该是,对于想要识别成字符的保留符号,需要加上\进行转义,即(\$\{1*\})
但结合实际业务规则来看,加转义的方式虽然看上去结果是对的,但逻辑其实是错的。

该段业务程序是在做模板字符串处理,系统中配置了多个字符串模板,模板中使用${1} ${2}这样的标记作为填充值的占位符。如果使用占位符使用到了 ${11} ,则(\$\{1*\})也能匹配上,导致结果错误。所以准确的做法应该为(\$\{1\}),即不应该有这个*,此时想替换第几个参数均能正确匹配。而为什么之前的业务代码中会有这个*,我猜想大概是当时的开发人员写的(\${1})匹配不到想要的数据时,发现加一个*就能匹配上,就这么用下去了,而该套系统多年以来,从未有超过9个参数的模板,因此该BUG一直未被人发现,直到进行本次国产化改造才挖出来。

七、总结

有很多所谓的"标准功能",在不同的环境下有不同的"标准",这些"标准"各有各的准则,经过多年的发展,很难强求其一致性。就连正则表达式这样常用的功能都有不同的标准,就不要指望ANSI SQL能让任意相同语句在每个数据库中执行结果完全一致了。在去O的过程中,经常能发现以往很多写得不标准的应用代码,此时正是好机会将这些代码变得更加规范。

  • 本文作者: DarkAthena
  • 本文链接: https://www.darkathena.top/archives/regexp-diff-with-repetition-count-between-opengauss-and-oracle
  • 版权声明: 本博客所有文章除特别声明外,均采用CC BY-NC-SA 3.0 许可协议。转载请注明出处

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/947087.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C# 修改项目类型 应用程序程序改类库

初级代码游戏的专栏介绍与文章目录-CSDN博客 我的github&#xff1a;codetoys&#xff0c;所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。 这些代码大部分以Linux为目标但部分代码是纯C的&#xff0c;可以在任何平台上使用。 源码指引&#xff1a;github源…

DataV数据可视化

阿里云 DataV 是一个强大的数据可视化工具&#xff0c;可以帮助用户通过创建丰富的图表、仪表盘、地图和互动视图&#xff0c;将复杂的数据转化为易于理解和分析的可视化信息。DataV主要用于大数据和实时数据的展示&#xff0c;可以帮助企业和个人更直观地理解数据背后的含义&a…

STM32-笔记18-呼吸灯

1、实验目的 使用定时器 4 通道 3 生成 PWM 波控制 LED1 &#xff0c;实现呼吸灯效果。 频率&#xff1a;2kHz&#xff0c;PSC71&#xff0c;ARR499 利用定时器溢出公式 周期等于频率的倒数。故Tout 1/2KHZ&#xff1b;Ft 72MHZ PSC71&#xff08;喜欢设置成Ft的倍数&…

【20250101】Nature正刊:纯仿真强化学习得到外骨骼机器人的自适应控制策略

基本信息 论文标题&#xff1a;Experiment-free exoskeleton assistance via learning in simulation 发表期刊&#xff1a;Nature 发表时间&#xff1a;2024年6月12日 访问链接 论文&#xff1a;https://www.nature.com/articles/s41586-024-07382-4?fromPaywallRectrue 代…

【从零开始】11. LLaMA-Factory 微调 Qwen 模型(番外篇)

书接上回&#xff0c;在完成了 RAGChecker 测试后&#xff0c;离 RAG 应用真正发布还差最后一步 - 基础信息指令微调。考虑到模型还是需要具备一定程度的“自我认知”&#xff0c;因此需要将公司信息“嵌入”到模型里面的。为此&#xff0c;我选择了 LLaMA-Factory&#xff08;…

PowerShell 信息,请参阅 https......_Execution_Policies

文章目录 1. 问题分析2. 检查当前执行策略3. 解决方案&#xff1a;更改执行策略4. 若问题依然存在5. 注意事项 信息&#xff0c;请参阅 https:/go.microsoft.com/fwlink/?LinkID135170 中的 about _Execution_Policies。 所在位置 行:1 字符: 3. D:\Users\Documents\WindowsPo…

二、github基础

Github基础 备用github.com网站一、用户界面-Overview&#xff08;概览&#xff09;1用户信息2 导航栏3 热门仓库4 贡献设置5贡献活动6搜索和筛选7自定义收藏8贡献统计9最近活动10其他链接 二、用户界面-Repositories&#xff08;仓库&#xff09;1 libusb_stm322 savedata3 Fi…

C语言----指针

目录 1.概念 2.格式 3.指针操作符 4.初始化 1. 将普通变量的地址赋值给指针变量 a. 将数组的首地址赋值给指针变量 b. 将指针变量里面保存的地址赋值给另一个指针变量 5.指针运算 5.1算术运算 5.2 关系运算 指针的大小 总结&#xff1a; 段错误 指针修饰 1. con…

青少年编程与数学 02-006 前端开发框架VUE 01课题、VUE简介

青少年编程与数学 02-006 前端开发框架VUE 01课题、VUE简介 一、前端开发一、前端框架二、Vue.js三、应用场景四、区别五、学习资源六、应用示例说明&#xff1a; 课题摘要:本文介绍了前端开发框架Vue.js的基本概念和特性。Vue.js是一个渐进式JavaScript框架&#xff0c;用于构…

STM32单片机芯片与内部57 SPI 数据手册 寄存器

目录 一、SPI寄存器 1、SPI控制寄存器 1(SPI_CR1)(I2S模式下不使用) 2、SPI控制寄存器 2(SPI_CR2) 3、SPI 状态寄存器(SPI_SR) 4、SPI 数据寄存器(SPI_DR) 5、SPI CRC多项式寄存器(SPI_CRCPR)(I2S模式下不使用&#xff09; 6、SPI Rx CRC寄存器(SPI_RXCRCR)(I2S模式下不…

QT-------------自定义插件和库

以下是一个使用 Qt 实现图表交互操作的示例&#xff0c;涵盖了自定义图表视图类、不同类型的柱状图和饼图等内容。 实现思路 自定义图表视图类&#xff1a;创建一个从 QChartView 派生的自定义类&#xff0c;用于处理图表的交互操作。主窗口设计初始化&#xff1a;在主窗口中…

【Python】闭包

闭包&#xff08;Closure&#xff09;是指一个函数记住了并可以访问它的词法作用域&#xff08;lexical scope&#xff09;&#xff0c;即使这个函数在词法作用域之外执行。 闭包其实就是延伸了作用域的函数&#xff0c;包括被延伸函数主体中引用的非全局变量和局部变量。这些…

矩阵运算提速——玩转opencv::Mat

介绍:用Eigen或opencv::Mat进行矩阵的运算&#xff0c;比用cpp的vector或vector进行矩阵运算要快吗? 使用 Eigen 或 OpenCV 的 cv::Mat 进行矩阵运算通常比使用 std::vector<int> 或 std::vector<double> 更快。这主要有以下几个原因&#xff1a; 优化的底层实现…

FastDeploy部署paddlecls分类模型(windows)

目录 写在前面 总体步骤 C SDK编译库 方式1&#xff1a;编译安装 方式2&#xff1a;下载预编译库 准备模型、文件、代码和数据 模型文件类型 samples代码 待预测图像 使用 FastDeploy C SDK 将cpp源码编译为exe 编写cpp代码 cpp代码编译exe 运行可执行程序exe 将…

【第二部分--Python之基础】03 容器类型的数据

Python内置的数据类型如序列&#xff08;列表、元组等&#xff09;、集合和字典等可以容纳多项数据&#xff0c;我们称它们为容器类型的数据。 序列 序列&#xff08;sequence&#xff09;是一种可迭代的、元素有序的容器类型的数据。 序列包括列表&#xff08;list&#xff…

linux shell脚本 【分支结构case...in 、循环结构、函数】内附练习

1.思维导图 2.练习 1.定义一个find函数&#xff0c;查找ubuntu和root的gid 2.定义一个数组&#xff0c;写一个函数完成对数组的冒泡排序 bubble() {n${#arr[*]}for((i0;i<n-1;i));dofor((j0;j<n-1-i;j));doif ((arr[j]>arr[j1]));thentemp${arr[j]}arr[j]${arr[j1]}a…

基于SpringBoot和OAuth2,实现通过Github授权登录应用

基于SpringBoot和OAuth2&#xff0c;实现通过Github授权登录应用 文章目录 基于SpringBoot和OAuth2&#xff0c;实现通过Github授权登录应用0. 引言1. 创建Github应用2. 创建SpringBoot测试项目2.1 初始化项目2.2 设置配置文件信息2.3 创建Controller层2.4 创建Html页面 3. 启动…

从单点 Redis 到 1 主 2 从 3 哨兵的架构演进之路

一、前言 我们有个项目中用的 MySQL、Redis、ES、微服务都是单节点部署的&#xff0c;没有做集群模式部署&#xff0c;为了提高整体的可用性&#xff0c;对项目的部署架构进行了升级&#xff0c;支持高可用。 二、部署拓扑图 我们项目的测试环境 12 台服务器已经部署好了&am…

SQL 中的 EXISTS

我们先从 SQL 中最基础的 WHERE 子句开始。 比如下面这条 SQL 语句&#xff1a; 很显然&#xff0c;在执行这条 SQL 语句的时候&#xff0c;DBMS 会扫描 Student 表中的每一条记录&#xff0c;然后把符合 Sdept IS 这个条件的所有记录筛选出来&#xff0c;并放到结果集里面去…

大型概念模型:在句子表示空间中的语言建模

《Large Concept Models: Language Modeling in a Sentence Representation Space》这篇论文介绍了一种新的语言模型方法&#xff0c;它在句子表示空间中进行建模。该方法旨在改进传统语言模型的局限性&#xff0c;通过捕捉更高级别的语义信息来提高自然语言处理任务的表现。 模…