StarRocks to_binary 函数详解:将字符串按 hex / base64 / utf8 转换为二进制值
StarRocks to_binary 函数详解:将字符串按 hex / base64 / utf8 转换为二进制值
to_binary 是 StarRocks 提供的二进制类型转换函数,用于把 VARCHAR 字符串按照指定的二进制格式(binary_type)转换为二进制(BINARY/VARBINARY)值。它常与 BINARY/VARBINARY 数据类型配合使用,是"字符串 → 二进制"写入链路上的核心函数,也是 from_binary 的逆操作。读完本文,你将掌握 to_binary 的语法、三种格式(hex、encode64、utf8)的语义与边界行为,并通过源码与测试用例理解其底层实现原理。
函数概述
to_binary 将 VARCHAR 字符串转换为二进制值,支持以下三种二进制格式:hex、encode64、utf8。当未指定 binary_type 时,默认使用 hex。
官方定义见 to_binary 文档,其逆函数 from_binary 负责把二进制值反向编码为字符串。
语法与参数
to_binary(str[, binary_type])
参数说明
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
str |
VARCHAR | 是 | 待转换的字符串 |
binary_type |
VARCHAR 常量 | 否 | 转换使用的二进制格式,可选 hex、encode64、utf8,默认为 hex |
binary_type 三种格式语义
hex(默认):to_binary假定输入字符串是一个十六进制字符串,其字符必须全部位于'0123456789abcdef'范围内。输入不区分大小写。函数会将字符串直接转换为二进制,例如"abab"会被转换为x'abab'。若输入不是合法的十六进制字符串,则返回空二进制值(0x),不会抛出异常。encode64:to_binary假定输入字符串是 Base64 编码后的字符串,函数对其进行 Base64 解码,解码结果即为二进制值。例如"YWJhYg=="会被解码为x'abab'。若输入不是合法的 Base64 字符串,则返回空二进制值,同样不会抛出异常。utf8:to_binary不做任何转换,直接将输入字符串的原始字节序列作为二进制值返回。
返回值
返回 VARCHAR 值。实际上,从使用角度理解,返回值是一个二进制值(BINARY/VARBINARY),在使用 MySQL 客户端访问 StarRocks 时,若启用了 --binary-as-hex 选项,二进制数据将以十六进制表示法显示。
使用示例
以下示例假定在通过 MySQL 客户端访问 StarRocks 时启用了 --binary-as-hex 选项,因此二进制数据以十六进制表示法显示。
hex 格式:十六进制字符串 → 二进制
mysql> select to_binary('ABAB', 'hex');
+----------------------------------------------------+
| to_binary('ABAB', 'hex') |
+----------------------------------------------------+
| 0xABAB |
+----------------------------------------------------+
1 row in set (0.01 sec)
注意输入不区分大小写,'abab' 与 'ABAB' 等价,均得到 0xABAB。
encode64 格式:Base64 字符串 → 二进制
mysql> select to_binary('U1RBUlJPQ0tT', 'encode64');
+------------------------------------------------------------------------------+
| to_binary('U1RBUlJPQ0tT', 'encode64') |
+------------------------------------------------------------------------------+
| 0x53544152524F434B53 |
+------------------------------------------------------------------------------+
1 row in set (0.00 sec)
'U1RBUlJPQ0tT' 是 STARROCKS 的 Base64 编码,解码后得到字节序列 0x53544152524F434B53,与 utf8 格式下直接转换 'STARROCKS' 的结果一致。
utf8 格式:原始字节 → 二进制
mysql> select to_binary('STARROCKS', 'utf8');
+----------------------------------------------------------------+
| to_binary('STARROCKS', 'utf8') |
+----------------------------------------------------------------+
| 0x53544152524F434B53 |
+----------------------------------------------------------------+
1 row in set (0.00 sec)
'STARROCKS' 每个字符的 ASCII 码依次为 53 54 41 52 52 4F 43 4B 53,因此 utf8 格式直接得到 0x53544152524F434B53。
格式不匹配时返回空二进制
-- The input string does not match the binary format and an empty binary is returned.
mysql> select to_binary('U1RBUlJPQ0tT', 'hex');
+--------------------------------------------------------------------+
| to_binary('U1RBUlJPQ0tT', 'hex') |
+--------------------------------------------------------------------+
| 0x |
+--------------------------------------------------------------------+
1 row in set (0.00 sec)
'U1RBUlJPQ0tT' 中包含 U、R、J、P、K、T 等不在 0123456789abcdef 范围内的字符,不符合 hex 格式要求,因此返回空二进制 0x,而不是抛出异常。
与 from_binary 互为逆操作
to_binary 与 from_binary 是一对方向相反的函数:to_binary 把字符串编码为二进制,from_binary 把二进制编码回字符串,二者配合可以完成"字符串 → 二进制 → 字符串"的完整闭环。以下示例来自 from_binary 文档:
mysql> select from_binary(to_binary('ABAB', 'hex'), 'hex');
+----------------------------------------------+
| from_binary(to_binary('ABAB', 'hex'), 'hex') |
+----------------------------------------------+
| ABAB |
+----------------------------------------------+
1 row in set (0.02 sec)
mysql> select from_base64(from_binary(to_binary('U1RBUlJPQ0tT', 'encode64'), 'encode64'));
+-----------------------------------------------------------------------------+
| from_base64(from_binary(to_binary('U1RBUlJPQ0tT', 'encode64'), 'encode64')) |
+-----------------------------------------------------------------------------+
| STARROCKS |
+-----------------------------------------------------------------------------+
1 row in set (0.01 sec)
mysql> select from_binary(to_binary('STARROCKS', 'utf8'), 'utf8');
+-----------------------------------------------------+
| from_binary(to_binary('STARROCKS', 'utf8'), 'utf8') |
+-----------------------------------------------------+
| STARROCKS |
+-----------------------------------------------------+
1 row in set (0.01 sec)
源码实现解析
格式枚举与常量解析
在 BE 端,三种格式被定义为枚举类型 BinaryFormatType,见 be/src/exprs/binary_functions.h:
enum class BinaryFormatType {
HEX = 0,
ENCODE64 = 1,
UTF8 = 2,
};
struct BinaryFormatState {
BinaryFormatType to_binary_type;
static BinaryFormatType to_binary_format(const std::string& s) {
std::string str(s);
std::transform(str.begin(), str.end(), str.begin(), [](unsigned char c) { return std::tolower(c); });
if (str == "encode64") {
return BinaryFormatType::ENCODE64;
} else if (str == "utf8") {
return BinaryFormatType::UTF8;
} else {
// by default
return BinaryFormatType::HEX;
}
}
};
从 BinaryFormatState::to_binary_format 的实现可以看出两点:
- 格式字符串在解析前会被转换为小写,因此
'ENCODE64'、'UTF8'等大小写变体同样有效; - 格式字符串仅精确匹配
"encode64"与"utf8",其余任何字符串(包括'base64')都会回落为HEX。这解释了为什么 BINARY 数据类型文档 示例中INSERT INTO test_binary select 6, to_binary('abab', 'base64');得到的结果仍是0xABAB——'abab'恰好是合法十六进制字符串,因此按 HEX 处理也能得到相同结果。实际使用时应严格使用'encode64'。
prepare 阶段缓存格式常量
binary_type 参数在执行期是只读的常量,因此 to_binary 通过 to_binary_prepare 在 FRAGMENT_LOCAL 阶段解析一次格式,并在线程间共享,避免每个线程重复解析。实现见 be/src/exprs/binary_functions.cpp:
Status BinaryFunctions::to_binary_prepare(FunctionContext* context, FunctionContext::FunctionStateScope scope) {
// BinaryFormatState is an immutable format choice derived from a constant arg; it is
// read-only at eval time, so build it once in FRAGMENT_LOCAL and share it across threads
// instead of keeping a per-thread copy.
if (scope != FunctionContext::FRAGMENT_LOCAL) {
return Status::OK();
}
auto* state = new BinaryFormatState();
context->set_function_state(scope, state);
if (!context->is_notnull_constant_column(1)) {
return Status::OK();
}
auto column = context->get_constant_column(1);
auto to_binary_type = ColumnHelper::get_const_value<TYPE_VARCHAR>(column);
std::string to_binary_type_str = to_binary_type.to_string();
state->to_binary_type = BinaryFormatState::to_binary_format(to_binary_type_str);
return Status::OK();
}
to_binary_close 则在 FRAGMENT_LOCAL 作用域结束时释放 BinaryFormatState。
向量化执行核心
to_binary 的实际计算在 BinaryFunctions::to_binary 中完成,它根据解析出的格式委托给不同的底层函数,见 be/src/exprs/binary_functions.cpp:
StatusOr<ColumnPtr> BinaryFunctions::to_binary(FunctionContext* context, const Columns& columns) {
auto state = reinterpret_cast<BinaryFormatState*>(context->get_function_state(FunctionContext::FRAGMENT_LOCAL));
auto to_binary_type = state->to_binary_type;
switch (to_binary_type) {
case BinaryFormatType::UTF8: {
auto& src_column = columns[0];
return std::move(*src_column).mutate();
}
case BinaryFormatType::ENCODE64:
return EncryptionFunctions::from_base64(context, columns);
default:
return StringFunctions::unhex(context, columns);
}
return Status::OK();
}
三种格式的实现路径非常清晰:
utf8:直接对输入列调用mutate(),不经过任何转换,与文档"不做任何转换"的描述一致;encode64:委托给EncryptionFunctions::from_base64(Base64 解码),与from_base64字符串函数共用同一实现;hex(default 分支):委托给StringFunctions::unhex,即 be/src/exprs/string_functions.cpp 中基于unhexImpl的向量化严格一元函数实现,负责十六进制解码。
这也从源码层面印证了文档中"输入不合法时返回空二进制而非抛异常"的行为:unhex 与 from_base64 对非法输入按空结果处理。
测试用例佐证
BE 端提供了完整的单元测试 be/test/exprs/binary_functions_test.cpp,覆盖正常输入、非法输入与 NULL 输入三类场景:
- 正常输入(
TestToBinaryNormal):验证hex格式"abab"→ABAB、"00"→00;encode64格式"QUJBQg=="→ABAB、"MDA="→00;utf8格式原样保留(包括"0"、"0Xx"等非十六进制字符); - 非法输入:验证
hex格式下"0"(奇数长度)与"AX"(含非法字符)均返回空字符串,且不抛出异常(测试注释// TODO: Return null if input is invalid.表明当前实现返回空串而非 NULL); - NULL 输入(
TestToBinaryNull):输入为 NULL 常量列时,结果保持 NULL; - 往返一致性(
TestFromToBinaryNormal):验证to_binary与from_binary在同一种格式下互为逆操作,转换结果与原始字符串一致。
实际应用:向 VARBINARY 列写入数据
to_binary 最常见的实战场景是把 VARCHAR 数据转换为二进制后写入 VARBINARY 列。根据 BINARY/VARBINARY 数据类型文档,StarRocks 自 v3.0 起支持 BINARY/VARBINARY 类型(BINARY 是 VARBINARY 的别名),最大长度与 VARCHAR 一致,范围为 [1, 1048576] 字节,默认 1048576。
建表并写入二进制数据:
CREATE TABLE `test_binary` (
`id` INT(11) NOT NULL COMMENT "",
`j` VARBINARY NULL COMMENT ""
) ENGINE=OLAP
DUPLICATE KEY(`id`)
COMMENT "OLAP"
DISTRIBUTED BY HASH(`id`)
PROPERTIES (
"replication_num" = "3",
"storage_format" = "DEFAULT"
);
方法一:直接写入 x'' 前缀的二进制常量:
INSERT INTO test_binary (id, j) VALUES (1, x'abab');
INSERT INTO test_binary (id, j) VALUES (2, x'baba');
INSERT INTO test_binary (id, j) VALUES (3, x'010102');
INSERT INTO test_binary (id, j) VALUES (4, x'0000');
方法二:用 to_binary 将 VARCHAR 数据转换为二进制后写入:
INSERT INTO test_binary select 5, to_binary('abab', 'hex');
INSERT INTO test_binary select 6, to_binary('abab', 'encode64');
INSERT INTO test_binary select 7, to_binary('abab', 'utf8');
查询结果(启用 --binary-as-hex):
mysql> select * from test_binary;
+------+------------+
| id | j |
+------+------------+
| 1 | 0xABAB |
| 2 | 0xBABA |
| 3 | 0x010102 |
| 4 | 0x0000 |
| 5 | 0xABAB |
| 6 | 0xABAB |
| 7 | 0x61626162 |
+------+------------+
7 rows in set (0.08 sec)
注意第 7 行:utf8 格式不做任何解码,'abab' 的 ASCII 字节序列直接落库,因此得到 0x61626162(对应 a、b、a、b 的 ASCII 码),与 hex 格式的 0xABAB 完全不同。三种格式差异在此体现得十分直观。
除了 INSERT INTO,BINARY 数据还可通过 Broker Load(Parquet/ORC 文件的 BYTE_ARRAY/BINARY 类型直接映射)与 Stream Load(CSV 文件按 hex 格式解析)导入,具体见 BINARY/VARBINARY 文档。
生态与兼容性
to_binary 在 FE 端注册于 FunctionSet.java(TO_BINARY = "to_binary"、FROM_BINARY = "from_binary")。
在 Trino 兼容层中,StarRocks 将 Trino 的 to_utf8/from_utf8 函数自动映射为 to_binary/from_binary 并固定指定 utf8 格式,见 Trino2SRFunctionCallTransformer.java:
// to_utf8 -> to_binary
registerFunctionTransformer("to_utf8", 1, new FunctionCallExpr("to_binary",
List.of(new PlaceholderExpr(1, Expr.class), new StringLiteral("utf8"))));
// from_utf8 -> from_binary
registerFunctionTransformer("from_utf8", 1, new FunctionCallExpr("from_binary",
List.of(new PlaceholderExpr(1, Expr.class), new StringLiteral("utf8"))));
这意味着使用 Trino 语法的查询中,to_utf8(v) 会被等价改写为 to_binary(v, 'utf8'),保持了跨引擎 SQL 的兼容体验。
小结
to_binary(str[, binary_type])将 VARCHAR 字符串按hex(默认)、encode64、utf8三种格式转换为二进制值;- 输入格式不合法时返回空二进制(
0x)而非抛出异常;NULL 输入保持 NULL; - 三种格式内部实现分别委托给
StringFunctions::unhex、EncryptionFunctions::from_base64与列直传,binary_type常量在 prepare 阶段解析一次并跨线程共享; - 与 from_binary 互为逆操作,与 BINARY/VARBINARY 数据类型 配合使用即可完成字符串到二进制的落库闭环。