StarRocks array_append 函数详解:向数组末尾追加元素的实现原理与实战用法
StarRocks array_append 函数详解向数组末尾追加元素的实现原理与实战用法【免费下载链接】starrocksThe worlds 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/starrocksarray_append是 StarRocks 数组ARRAY系列函数中最基础也最常用的函数之一其作用是在数组的末尾追加一个新元素并返回新的数组。本文以官方函数文档docs/en/sql-reference/sql-functions/array-functions/array_append.md为核心骨架结合 FEFrontend解析与 BEBackend向量化执行层的源码实现系统讲解array_append的语法、返回值、NULL 语义、类型匹配规则以及底层执行原理帮助读者在 StarRocks 中正确、高效地使用该函数。一、函数概述array_append将一个新的元素追加到数组的末尾返回一个包含原数组全部元素和新元素的新数组。该函数属于 StarRocks ARRAY 类函数家族与 array_concat拼接两个数组、array_remove按值移除元素、array_length返回数组长度等函数共同构成数组处理工具集。函数语法array_append(any_array, any_element)参数说明any_array任意类型的数组ARRAY 类型例如[1, 2]、[A, B]、[[1, 2], [3]]嵌套数组any_element要追加到数组末尾的元素其类型需要与数组元素类型一致或可隐式转换返回值为一个新的数组其类型与第一个参数any_array保持一致。基本示例在 mysql 客户端中执行mysql select array_append([1, 2], 3); ------------------------ | array_append([1,2], 3) | ------------------------ | [1,2,3] | ------------------------ 1 row in set (0.00 sec)可以看到元素3被追加到[1, 2]的末尾得到[1, 2, 3]。原数组本身不会被修改array_append返回的是包含原数组全部元素和新元素的全新数组这一语义在下面的源码分析中会得到印证。二、NULL 元素的追加语义与原文档明确说明的内容一致array_append允许向数组中追加NULL值mysql select array_append([1, 2], NULL); --------------------------- | array_append([1,2], NULL) | --------------------------- | [1,2,NULL] | --------------------------- 1 row in set (0.01 sec)此时结果数组为[1, 2, NULL]NULL会被作为一个真实的数组元素追加到末尾而不是让整个表达式结果变成NULL。这一点与很多初学者直觉上的任何运算遇到 NULL 结果都是 NULL的三值逻辑不同在 StarRocks 的array_append语义中NULL是合法的数组元素内容。需要区分两种NULL场景追加值为 NULL如array_append([1, 2], NULL)结果是把 NULL 作为新元素追加得到[1, 2, NULL]数组本身为 NULL如array_append(NULL, A)由于被操作的数组是 NULL整个表达式结果为NULL。上述两种场景在 FE 的常量折叠测试中都有覆盖见 ConstArrayFunctionFoldingTest.java 中的用例{array_append([A, B], C), [A,B,C]}, {array_append([A, B], NULL), [A,B,NULL]}, {array_append(NULL, NULL), slot 2 : NULL}, {array_append(NULL, A), slot 2 : NULL},从测试断言可以看出追加NULL时结果数组包含 NULL 元素而数组本身是 NULL 时无论追加什么整个表达式都被常量折叠为NULL。三、字符串与空数组等边界场景array_append对字符串数组同样适用。在 FE 的优化器常量折叠测试中字符串场景被折叠为字面量数组{array_append([A, B], C), [A,B,C]},对于空数组[]array_append([], element)会返回仅包含该元素的新数组这在 ArrayTypeTest.java 中通过select array_append([], null)、select array_append([], [])等用例进行了验证。该测试还覆盖了嵌套数组场景见 ArrayTypeTest.javaselect array_append([[1,2,3]], []) -- 嵌套数组向数组追加一个空数组元素 select array_append([[1,2,3]], [null]) -- 追加包含 NULL 的子数组这说明array_append支持 ARRAY 嵌套类型当数组元素本身也是数组时any_element可以是一个数组字面量如[]、[null]等。四、元素类型匹配与隐式转换array_append对第二参数的类型有约束追加的元素类型必须与数组元素类型一致或者可以隐式转换到数组元素类型。StarRocks 的优化器会在生成执行计划时自动插入类型转换CAST这一行为在 ArrayTypeTest.java 中有明确的计划级断言。以DECIMAL64数组为例-- 假设表 adec 中 d_2 列为 ARRAYDECIMAL64(4,3) 类型 select array_append(d_2, 1.0) from adec;优化器生成的计划中包含array_append[([5: d_2, ARRAYDECIMAL64(4,3), true], 1.0);此时字面量1.0会按DECIMAL64(4,3)的精度语义参与计算。对于VARCHAR数组-- 假设表 adec 中 s_1 列为 ARRAYVARCHAR(65533) 类型 select array_append(s_1, 1.0) from adec;计划中可以看到array_append[([3: s_1, ARRAYVARCHAR(65533), true], 1.0);即数字字面量1.0被隐式转换为字符串1.0后再追加。类型匹配的实践建议保持类型一致追加的元素类型与数组元素类型一致时性能与语义最直观如array_append([A, B], C)依赖隐式转换StarRocks 会自动进行数值与字符串、不同精度的 DECIMAL 等之间的隐式转换但建议显式使用CAST以避免精度损失歧义嵌套数组当数组元素是 ARRAY 时第二参数应传入数组字面量如[]、[null]而不是标量值。五、底层实现原理BE 向量化执行在 BE 端array_append的声明位于 be/src/exprs/array_functions.h通过DEFINE_VECTORIZED_FN(array_append)注册为向量化函数其核心实现位于 be/src/exprs/array_functions.cpp。5.1 主流程ArrayFunctions::array_append实现主流程可以概括为以下几个步骤NULL 数组短路如果第一个参数数组列只有 NULL 值only_null()直接返回该列即整个表达式结果为 NULL对应前文数组本身为 NULL的语义解包常量列通过ColumnHelper::unpack_and_duplicate_const_column将常量数组列解包为普通数据列便于统一处理剥离可空包装如果数组列是NullableColumn先剥离 null 标志取出内部的ArrayColumn数据列获取元素列与偏移列从ArrayColumn中取出elements()元素列与offsets()偏移列——StarRocks 的 ARRAY 列底层采用元素数组 偏移数组的紧凑存储结构每个数组通过[offsets[i], offsets[i1])区间定位按第二参数形态分派第二参数only_null()即追加 NULL→do_array_appendtrue, true第二参数是常量列 →do_array_appendfalse, true第二参数是普通列 →do_array_appendfalse, false恢复可空包装若原数组列可空将原数组的 null 标志列复用到结果上返回NullableColumn。5.2 核心循环do_array_append模板函数do_array_appendbe/src/exprs/array_functions.cpp实现了真正的追加逻辑其核心是逐数组遍历的循环for (size_t i 0; i num_array; i) { uint32_t next_offset offsets_data[i 1]; uint32_t array_size next_offset - curr_offset; // 1. 把第 i 个数组的原有元素整体拷贝到结果元素列 result_elements-append(elements, curr_offset, array_size); // 2. 根据参数形态追加一个元素NULL / 常量 / 逐行取值 if constexpr (OnlyNullData) { result_elements-append_nulls(1); } else if constexpr (ConstData) { result_elements-append(*const_data, 0, 1); } else { result_elements-append(data, i, 1); } // 3. 更新偏移数组每个结果数组长度 原数组长度 1 result_offset array_size 1; result_offsets.push_back(result_offset); curr_offset next_offset; }从实现可以确认两个关键事实不修改原数组函数创建全新的ArrayColumn作为结果result_array将原数组元素整体拷贝过去因此原数组列不受影响符合函数式语义批量处理该实现面向一列多行的向量化执行一次调用即可处理整批数组如某个表中某一列的所有数组值并通过reserve预分配容量、append批量拷贝避免逐元素分散写入NULL 元素本质追加 NULL 时调用append_nulls(1)在结果元素列中追加一个 NULL 标记这正是[1, 2, NULL]结果的列式存储形态。需要注意的是源码注释中标注了FIXME: A proof-of-concept implementation with poor performancebe/src/exprs/array_functions.cpp即当前实现被作者标注为概念验证、性能欠佳后续版本有进一步向量化优化的空间。读者在实际使用中如果对超大批量数组追加的性能有较高要求可以留意新版本的实现变更。六、FE 侧注册与常量折叠优化array_append在 FE 侧通过内置函数注册表FunctionSet注册常量标识符定义在 FunctionSet.javapublic static final String ARRAY_APPEND array_append;StarRocks 优化器还针对数组常量表达式实现了**常量折叠Constant Folding**优化当array_append的两个参数都是常量时不需要下推到 BE 执行而是在 FE 端直接计算出结果。该逻辑位于 FoldConstantsRule.java.put(array_append, this::constArrayAppend)constArrayAppend处理函数定义在 FoldConstantsRule.java。这就是为什么前文测试用例中array_append([A, B], C)会被直接折叠成字面量[A,B,C]、array_append(NULL, A)会被折叠为NULL——这些常量表达式在查询计划生成阶段就被求值完毕几乎不消耗执行期资源。七、与相邻数组函数的分工array_append在数组处理函数家族中承担末尾追加单元素的职责与相近函数的使用场景对比如下函数用途与 array_append 的区别array_append(arr, e)向数组末尾追加一个元素本函数主题array_concat拼接两个数组第二参数是数组而非标量元素array_remove删除数组中所有等于指定值的元素方向相反返回删除后的数组array_length返回数组长度常与 append 配合验证结果实际开发中array_append常用于在聚合或窗口计算的中间结果上追加最新值、为数组补位后再做其他数组运算、配合unnestunnest.md展开后继续处理等场景。八、使用注意事项小结函数名不区分大小写array_append、ARRAY_APPEND均可用文档 keyword 为ARRAY_APPEND, ARRAY返回新数组原数组不会被修改需要将结果赋给新列或用于后续表达式NULL 语义追加 NULL 得到含 NULL 元素的数组数组本身为 NULL 时整体结果为 NULL类型对齐追加元素应尽量与数组元素类型一致依赖隐式转换时注意精度与字符串化规则空数组array_append([], e)返回单元素数组[e]常量优化全常量参数在 FE 端即被折叠无需关心执行期开销涉及真实表列的批量场景才会走 BE 端向量化实现。九、参考与延伸阅读函数官方文档docs/en/sql-reference/sql-functions/array-functions/array_append.mdBE 向量化实现be/src/exprs/array_functions.cpp 与 be/src/exprs/array_functions.hFE 函数注册fe/fe-core/src/main/java/com/starrocks/catalog/FunctionSet.javaFE 常量折叠fe/fe-core/src/main/java/com/starrocks/sql/optimizer/rewrite/scalar/FoldConstantsRule.java相关测试ConstArrayFunctionFoldingTest.java、ArrayTypeTest.java数组函数目录docs/en/sql-reference/sql-functions/array-functions/array-functions.mdx【免费下载链接】starrocksThe worlds 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),仅供参考