StarRocks regexp_replace 函数详解:语法、示例与 BE 向量化实现路径剖析
2026/9/18 10:48:24 网站建设 项目流程

StarRocks regexp_replace 函数详解:语法、示例与 BE 向量化实现路径剖析

【免费下载链接】starrocksThe world's fastest open query engine for sub-second analytics both on and off the data lakehouse. With the flexibility to support nearly any scenario, StarRocks provides best-in-class performance for multi-dimensional analytics, real-time analytics, and ad-hoc queries. A Linux Foundation project.项目地址: https://gitcode.com/GitHub_Trending/st/starrocks

本文基于 StarRocks 官方函数参考文档regexp_replace展开,介绍该正则替换函数的语法、参数语义与典型用法示例,并结合后端(BE)源码剖析其执行路径:从 RE2 逐行匹配到基于 Hyperscan 的向量化加速,帮助读者在数据清洗、日志脱敏等场景中既会用函数,又理解其底层性能机制。

函数功能与返回值

regexp_replace用于将字符串str中匹配正则表达式pattern的子串,替换为替换串repl。该函数属于 StarRocks 字符串函数族,在 SQL 层面注册于前端函数表,见 FunctionSet.java 中的常量定义:

public static final String REGEXP_REPLACE = "regexp_replace";

函数签名为:

VARCHAR regexp_replace(VARCHAR str, VARCHAR pattern, VARCHAR repl)
  • 入参strpatternrepl均为VARCHAR类型;
  • 返回值为VARCHAR类型,即替换后的完整字符串;
  • 在优化器常量折叠一侧,该函数被标注为可求值的常量函数(参数类型{VARCHAR, VARCHAR, VARCHAR}、返回VARCHAR),见 ScalarOperatorFunctions.java。

官方示例(原文档完整继承)

以下两个示例来自官方文档,覆盖了“普通子串替换”和“带反向引用(back-reference)的替换”两种核心用法:

MySQL > SELECT regexp_replace('a b c', " ", "-"); +-----------------------------------+ | regexp_replace('a b c', ' ', '-') | +-----------------------------------+ | a-b-c | +-----------------------------------+ MySQL > SELECT regexp_replace('a b c','(b)','<\\1>'); +----------------------------------------+ | regexp_replace('a b c', '(b)', '<\1>') | +----------------------------------------+ | a <b> c | +----------------------------------------+

两个示例说明的关键行为:

  1. 全局替换' '"a b c"中出现两次,结果中两处空格全部被替换为-,说明默认行为是对所有匹配项逐一替换,而非仅替换首个匹配;
  2. 替换串支持反向引用'<\\1>'中的\1引用 pattern 中第 1 个捕获组(b)的匹配内容,结果a <b> c表明捕获组内容被原样嵌入替换位置。这是用正则做 HTML 标签化、字段脱敏(如regexp_replace(phone, '(\\d{3})\\d{4}(\\d{4})', '$1****$2')这类脱敏写法)的基础能力。

语义细节:全局替换的边界条件

“是否全局替换”并非无条件成立。从 BE 源码结构看,StarRocks 根据 pattern 是否锚定来决定替换范围。在函数状态初始化逻辑中(见 string_functions.cpp):

state->global_mode = pattern_str.empty() || (!pattern_str.starts_with("^") && !pattern_str.ends_with("$"));

可以推断出其判定规则:

  • 当 pattern既不以^开头、也不以$结尾时,进入全局替换模式(GlobalReplace),替换所有匹配项——这与官方示例一的行为一致;
  • 当 pattern 以^开头或以$结尾(即带有位置锚定语义)时,走单次替换路径(Replace),只替换第一个匹配项;
  • 这一逻辑体现在常量 pattern 的执行分支中,全局模式与单次模式由模板参数global_mode区分编译(见 string_functions.cpp):
if constexpr (global_mode) { re2::RE2::GlobalReplace(&result_str, *const_re, rpl_str); } else { re2::RE2::Replace(&result_str, *const_re, rpl_str); }

因此在实践中,regexp_replace(str, 'abc$', '')(去除行尾标记)与regexp_replace(str, 'abc', '')(去除全部标记)的语义是不同类别的操作,使用时需留意 pattern 的锚定写法。

另外,pattern 以^开头或$结尾时的锚定判断是基于 pattern 首尾字符的启发式判断,并非完整的正则锚点解析(例如.*$不以$开头但含$),阅读源码可知这是一种面向常见写法的性能/语义折中。

空值与非法 pattern 的处理

BE 侧对每行输入做了显式的 NULL 与错误处理,实现位于通用执行路径(见 string_functions.cpp):

if (str_viewer.is_null(row) || ptn_viewer.is_null(row) || rpl_viewer.is_null(row)) { result.append_null(); continue; } // ... re2::RE2 local_re(ptn_value, *options); if (!local_re.ok()) { context->set_error(strings::Substitute("Invalid regex: $0", ptn_value).c_str()); result.append_null(); continue; }

由此可以确认两个实现事实:

  1. 三个参数任意一个为 NULL,结果即为 NULL,不会报错;
  2. 非法正则的报错时机取决于 pattern 是否常量:若 pattern 是常量列,会在 fragment 初始化阶段(regexp_replace_prepare,见 string_functions.cpp)就编译校验,非法时直接返回Invalid regex expression错误,查询提前失败;若 pattern 是逐行变化的列,则逐行编译,非法行输出 NULL 并通过set_error上报错误信息。

执行路径剖析:从常量优化到 Hyperscan 向量化

regexp_replace的 BE 入口是 string_functions.cpp 中的分发函数,它按“pattern 是否常量 + pattern 是否简单字面量”两个维度选择了最多四条执行路径:

StatusOr<ColumnPtr> StringFunctions::regexp_replace(FunctionContext* context, const Columns& columns) { // pattern 为常量且匹配简单子串模式时,走 Hyperscan 加速路径 if (state->use_hyperscan) { if (state->use_hyperscan_vec) { return regexp_replace_use_hyperscan_vec(state, columns); } else { return regexp_replace_use_hyperscan(state, columns); } } // pattern 为常量的 RE2 编译结果 if (state->const_pattern) { if (state->opt_const_rpl.has_value()) { // repl 也是常量:最优化路径 return regexp_replace_const_pattern_and_rpl<...>(const_re, columns, ...); } return regexp_replace_const<...>(const_re, columns); } // 兜底:pattern 逐行变化的通用路径 return regexp_replace_general(context, options, columns); }

各路径的适用条件与含义:

1. Hyperscan 加速路径

regexp_replace_prepare中(见 string_functions.cpp),StarRocks 用两个内部正则判断 pattern 是否“足够简单”:

static const RE2 SUBSTRING_RE(R"((?:\.\*)*([^\.\^\{\[\(\|\)\]\}\+\*\?\$\\]+)(?:\.\*)*)", ...); static const RE2 FIXED_LITERAL_RE(R"(^[^\.\^\{\[\(\|\)\]\}\+\*\?\$\\]+$)", ...);
  • 若 pattern 完整匹配SUBSTRING_RE(形如a.*b这类以字面量为主、仅含.*的模式),则启用 Hyperscan 编译(hs_compile_and_alloc_scratch),走regexp_replace_use_hyperscan
  • 若 pattern 进一步完整匹配FIXED_LITERAL_RE(纯字面量,不含任何正则元字符),则启用向量化版本regexp_replace_use_hyperscan_vec,该路径直接对整个 BinaryColumn 的字节缓冲做一次性扫描替换(见 string_functions.cpp),逐行解引用与字符串拷贝的开销被摊薄到批处理层面。

对应的专项测试 string_fn_regexp_replace_test.cpp 中,testHyperscanVec用 5%~95% 不同命中率的随机数据验证了向量化路径与逐行 Hyperscan 路径结果逐行一致(ASSERT_EQ(vec->debug_item(i), ori->debug_item(i))),保证了加速路径的正确性。该文件中的testMultipleRowsWithPackagePattern还针对_package_.*这类多行匹配场景做了正确性回归。

2. 常量 pattern 的 RE2 路径

pattern 是常量但不是简单子串模式时,RE2 只在 prepare 阶段编译一次(state->const_pattern = true),执行阶段对每行数据复用同一编译结果,避免逐行重复编译正则。repl是否常量进一步区分了regexp_replace_constregexp_replace_const_pattern_and_rpl两个模板实例,常量 repl 路径避免逐行拷贝替换串。

3. 通用路径

pattern 逐行变化(非常量列)时走regexp_replace_general,每行现场构造re2::RE2并调用RE2::GlobalReplace。这是兜底路径,也是唯一支持逐行不同 pattern 的路径。

RE2 选项配置

无论哪条路径,RE2 均使用统一的选项(见 string_functions.cpp):

state->options = std::make_unique<re2::RE2::Options>(); state->options->set_log_errors(false); state->options->set_longest_match(true); state->options->set_dot_nl(true);
  • longest_match(true):匹配采用最左最长策略,与常见正则习惯一致;
  • dot_nl(true).可以匹配换行符,即 pattern 中的.跨行生效;
  • log_errors(false):错误不走 RE2 默认日志通道,改由 StarRocks 自己的set_error机制上报。

Hyperscan 编译则使用HS_FLAG_ALLOWEMPTY | HS_FLAG_DOTALL | HS_FLAG_UTF8 | HS_FLAG_SOM_LEFTMOST标志(见 string_fn_regexp_replace_test.cpp 中的等价编译参数),其中DOTALL与 RE2 的dot_nl语义对齐,SOM_LEFTMOST保证最左匹配顺序。

性能建议与使用要点

结合上述源码结构,可以给出以下实践建议:

  1. 尽量把 pattern 写成常量字面量。pattern 为常量列是启用所有加速路径(常量 RE2 复用、Hyperscan、向量化)的前提;同一列对不同行使用不同 pattern 会退化为通用路径,每行都要重新编译正则。
  2. 纯字面量替换优先考虑更轻的写法。形如regexp_replace(col, '2024-', '')的纯字面量 pattern 会命中最快的hyperscan_vec路径;但如果只是精确子串替换、不涉及正则语义,使用replace函数同样可得到常量快速路径,语义更直观。
  3. 锚定 pattern 的语义差异。带^前缀或$后缀的 pattern 会走单次替换而非全局替换,编写“去前缀/去后缀”类转换时需按第 3 节的规则理解其行为。
  4. 正则方言是 RE2 语法。StarRocks 的 pattern 遵循 RE2 语法规则(不支持回溯等 PCRE 特性),替换串中可用\1形式的反向引用引用捕获组。

Trino 连接器中的两参形式

除标准三参形式外,仓库中还存在一个值得注意的适配点:Trino 兼容层会把 Trino 方言的两参regexp_replace(expr, pattern)调用转换为 StarRocks 的三参形式(补默认空串替换),见 Trino2SRFunctionCallTransformer.java:

// support regexp_replace with 2 param registerFunctionTransformer("regexp_replace", 2, new FunctionCallExpr("regexp_replace", ...));

也就是说,两参“按正则删除匹配内容”的写法是在 Trino 连接器语法翻译场景下获得的;在原生 StarRocks SQL 中,应按三参签名显式传入替换串。

总结

regexp_replace是 StarRocks 中做正则级数据清洗的核心函数:语法上遵循regexp_replace(str, pattern, repl)三参形式,默认全局替换、支持捕获组反向引用,任一参数为 NULL 时返回 NULL;实现上,StarRocks 从源码结构看按“pattern 是否常量、是否简单字面量”分流到 Hyperscan 向量化、常量 RE2 复用、逐行通用共四条路径,使得简单 pattern 的大批量清洗能获得接近内存扫描的速度,而复杂 pattern 的逐行语义仍由 RE2 保证。相关参考:函数文档 regexp_replace.md、BE 实现 string_functions.cpp、BE 函数声明 string_functions.h、前端注册 FunctionSet.java、专项测试 string_fn_regexp_replace_test.cpp。

【免费下载链接】starrocksThe world's fastest open query engine for sub-second analytics both on and off the data lakehouse. With the flexibility to support nearly any scenario, StarRocks provides best-in-class performance for multi-dimensional analytics, real-time analytics, and ad-hoc queries. A Linux Foundation project.项目地址: https://gitcode.com/GitHub_Trending/st/starrocks

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询