【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 中都不报错。
可以使用以下链接测试该正则表达式在不同开发语言中的表现
https://regex101.com/r/APc3is/1
四、相关源码
使用openGauss分析这个逻辑的时候,我断了几个点,找了几段源码
6 breakpoint keep y 0x0000000000fc4cd7 in parseqatom(vars*, int, int, state*, state*, subre*) at regcomp.cpp:915breakpoint already hit 2 times
7 breakpoint keep y 0x0000000000fc42c4 in parsebranch(vars*, int, int, state*, state*, int) at regcomp.cpp:719breakpoint 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:984regc_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);} elseFAILW(REG_BADBR);break;case CHR('\\'): /* BRE bound ends with \} */if (INCON(L_BBND) && NEXT1('}')) {v->now++;INTOCON(L_BRE);RET('}');} elseFAILW(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 许可协议。转载请注明出处
相关文章:

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

宏任务和微任务的区别
在 JavaScript 的异步编程模型中,宏任务(Macro Task)和微任务(Micro Task)是事件循环(Event Loop)机制中的两个重要概念。它们用于管理异步操作的执行顺序。 1. 宏任务 (Macro Task) 宏任务是较…...

数据库系统原理复习汇总
数据库系统原理复习汇总 一、数据库系统原理重点内容提纲 题型:主观题 1、简答题 第一章:数据库的基本概念:数据库、数据库管理系统、三级模式;两级映像、外码 第二章:什么是自然连接、等值连接; 第三…...

Linux day1204
五.安装lrzsz lrzsz 是用于在 Linux 系统中文件上传下载的软件。大家可能会存在疑问,我们用 MobaXterm 图形化界面就可以很方便的完成上传下载,为什么还要使用这个软件来 完成上传下载呢?实际上是这样的, Linux 的远程连接工具…...

如何在 Ubuntu 22.04 上安装并开始使用 RabbitMQ
简介 消息代理是中间应用程序,在不同服务之间提供可靠和稳定的通信方面发挥着关键作用。它们可以将传入的请求存储在队列中,并逐个提供给接收服务。通过以这种方式解耦服务,你可以使其更具可扩展性和性能。 RabbitMQ 是一种流行的开源消息代…...

【OpenGL ES】GLSL基础语法
1 前言 本文将介绍 GLSL 中数据类型、数组、结构体、宏、运算符、向量运算、矩阵运算、函数、流程控制、精度限定符、变量限定符(in、out、inout)、函数参数限定符等内容,另外提供了一个 include 工具,方便多文件管理 glsl 代码&a…...

如何使用交叉编译器调试C语言程序在安卓设备中运行
一、前言 随着移动设备的普及与技术的飞速发展,越来越多的开发者面临着在Android设备上运行和调试C语言等程序的需求。然而,在软件开发的世界里,不同硬件架构对程序运行的要求千差万别,这无疑增加了开发的复杂性。特别是在移动计…...

Java全栈项目 - 智能考勤管理系统
项目介绍 智能考勤管理系统是一个基于 Java 全栈技术开发的现代化企业考勤解决方案。该系统采用前后端分离架构,实现了员工考勤、请假管理、统计分析等核心功能,旨在帮助企业提高人力资源管理效率。 技术栈 后端技术 Spring Boot 2.6.xSpring Securi…...

Linux Shell : Process Substitution
注:本文为 “Process Substitution” 相关文章合辑。 英文引文机翻,未校。 Process Substitution. 进程替换允许使用文件名引用进程的输入或输出。它采取以下形式 <(list)or >(list)进程 list 异步运行,其输入或输出显示为文件名。…...

JOGL 从入门到精通:开启 Java 3D 图形编程之旅
一、引言 Java 作为一门广泛应用的编程语言,在图形编程领域也有着强大的工具和库。JOGL(Java OpenGL)便是其中之一,它为 Java 开发者提供了访问 OpenGL(Open Graphics Library)功能的接口,使得…...

汽车网络安全基线安全研究报告
一、引言 随着汽车行业朝着智能网联方向飞速发展,汽车网络安全已成为保障用户安全和行业健康发展的关键要素。本报告将深入探讨汽车网络安全相关内容,以及国际、国内重要的汽车网络安全标准基线和相应防护措施等内容。 二、汽车网络安全的重要性 &…...

Eclipse 修改项目栏字体大小
1、菜单栏选择window->preference,然后选择General->Appearance->Colors and Fonts,在搜索栏输入"tree",点击"Edit"修改字体。 2、修改字体,选择"四号字体",点击"确定&qu…...

【PCIe 总线及设备入门学习专栏 5.1 -- PCIe 引脚 PRSNT 与热插拔】
文章目录 OverviewPRSNT 与热插拔PRSNT 硬件设计 Overview Spec 定义的热插拔是把一个PCIe卡(设备)从一个正在运行的背板或者系统中插入/或者移除。这个过程需要不影响系统的其他功能。插入的新的设备可以正确工作。 显然,这里面需要考虑的问…...

【YOLO】YOLOv5原理
概述 YOLOv5的主要架构 Backbone(主干网络):负责提取输入图像的多层次特征 Neck(颈部网络):进行特征融合和多尺度特征处理,通常包含FPN(特征金字塔网络)和PAN࿰…...

uniapp中wx.getFuzzyLocation报错如何解决
一、用wx.getLocation接口审核不通过 用uniapp开发小程序时难免需要获取当前地理位置。 代码如下: uni.getLocation({type: wgs84,success: function (res) {console.log(当前位置的经度: res.longitude);console.log(当前位置的纬度: r…...

opencv图像直方图
【欢迎关注编码小哥,学习更多实用的编程方法和技巧】 1、基本直方图计算 // 灰度图直方图 cv::Mat calculateGrayscaleHistogram(const cv::Mat& image) {cv::Mat histogram;int histSize 256; // 灰度级别float range[] {0, 256};const float* histRange …...

OpenCV计算机视觉 03 椒盐噪声的添加与常见的平滑处理方式(均值、方框、高斯、中值)
上一篇文章:OpenCV计算机视觉 02 图片修改 图像运算 边缘填充 阈值处理 目录 添加椒盐噪声 图像平滑常见处理方式 均值滤波 (blur) 方框滤波 (boxFilter) 高斯滤波 (GaussianBlur) 中值滤波 (medianBlur) 添加椒盐噪声 def add_peppersalt_noise(image, n…...

【嵌入式C语言】内存分布
内存分布 内存分布图内存的属性:只读空间只读空间的特点编程注意事项 栈空间栈的工作原理栈的特点栈溢出与堆的区别 堆空间堆的特点内存分配函数内存泄漏总结 内存分布图 内存的属性: 在C语言中,内存的属性主要取决于它是如何分配的以及它在…...

【brainpan靶场渗透】
文章目录 一、基础信息 二、信息收集 三、反弹shell 四、提权 一、基础信息 Kali IP:192.168.20.146 靶机 IP:192.168.20.155 二、信息收集 似乎开放了9999,10000端口,访问页面没有太多内容,扫描一下目录 dirs…...

Java实现观察者模式
一、前言 观察者模式,又称为发布订阅模式,是一种行为设置模式,允许对象之间建立一对多的依赖关系,这样当一个对象状态改变时,它的所有依赖者(观察者)都会收到通知并自动更新。 二、具体实现 …...

通过百度api处理交通数据
通过百度api处理交通数据 1、读取excel获取道路数据 //道路名称Data EqualsAndHashCode public class RoadName {ExcelProperty("Name")private String name; }/*** 获取excel中的道路名称*/private static List<String> getRoadName() {// 定义文件路径&…...

探索CSDN博客数据:使用Python爬虫技术
探索CSDN博客数据:使用Python爬虫技术 在数字化的浪潮中,数据的获取与分析变得日益关键。CSDN作为中国领先的IT社区和服务平台,汇聚了海量的技术博客与文章,成为一座蕴藏丰富的数据宝库。本文将引领您穿梭于Python的requests和py…...

b站ip属地评论和主页不一样怎么回事
在浏览B站时,细心的用户可能会发现一个有趣的现象:某些用户的评论IP属地与主页显示的IP属地并不一致。这种差异引发了用户的好奇和猜测,究竟是什么原因导致了这种情况的发生呢?本文将对此进行深入解析,帮助大家揭开这一…...

如何查看服务器内存占用情况?
如何查看服务器的内存占用情况?你知道内存使用情况对服务器性能的重要性吗?内存是服务器运行的核心资源之一,了解内存的占用情况可以帮助你优化系统性能。 要查看服务器的内存占用情况,首先需要确定你使用的是哪种操作系统。不同…...

流架构的读书笔记(2)
流架构的读书笔记(2) 一、建模工具之一沃德利地图 推测技术的发展,交流和辩论思想的最有力的方法是沃德利地图 沃德利地图的制作步骤 1确定范围和用户需求 2确定满足用户需求所需的组件 3在一条范围从全新到被人们接受的演进轴上评估这些组成 部分的演…...

E6 中的 扩展运算符(Spread) 和 剩余运算符(Rest)
时间:2024.12.29 之前看到 Es6 中的 三点运算符,有如下的几种写法,有时候三点运算符放在左边,有时候三点运算符放在右边,老是混淆。今天记录下,加强理解。 先看一个问题 最近在看 《ECMAScript 6 入门》关于…...

Python的简单爬虫框架
爬虫为网络爬虫(又称为网页蜘蛛,网络机器人,在FOAF社区中间,更经常的称为网页追逐者),是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本。另外一些不常使用的名字还有蚂蚁、自动索引、…...

使用 uni-app 开发的微信小程序中,如何在从 B 页面回来时,重新拉取数据?
👨🏻💻 热爱摄影的程序员 👨🏻🎨 喜欢编码的设计师 🧕🏻 擅长设计的剪辑师 🧑🏻🏫 一位高冷无情的全栈工程师 欢迎分享 / 收藏 / 赞 / 在看…...

Windows API Set:那些“只存在但不被使用“的DLL
API Set 是什么? 想象一下,Windows就像一个大型图书馆,而API Set就是这个图书馆的索引系统。但这个索引系统非常特别:它是直接内置在Windows加载器中的"虚拟目录"。 // 一个典型的API Set映射示例 api-ms-win-core-mem…...

黑神话悟空鼠标光标分享
效果图: 鼠标光标特点 这套鼠标光标的设计灵感来源于《黑神话:悟空》游戏中的角色和元素,具有以下特点: • 主题鲜明:光标设计紧扣游戏主题,采用了游戏中的元素,让玩家在使用电脑时也能感受到…...