|
| 1 | +:sectnums: |
| 2 | +:sectnumlevels: 5 |
| 3 | + |
| 4 | +:imagesdir: ./_images |
| 5 | + |
| 6 | += STRAGG 聚合函数的实现 |
| 7 | + |
| 8 | +== 目的 |
| 9 | + |
| 10 | +IvorySQL 在 `contrib/ivorysql_ora` 扩展中新增 `sys.stragg(text)` 聚合函数, |
| 11 | +实现与 Oracle 同名函数一致的字符串聚合行为:将组内所有非 NULL 值用逗号连接为一个字符串。 |
| 12 | + |
| 13 | +== 实现说明 |
| 14 | + |
| 15 | +=== 状态布局设计 |
| 16 | + |
| 17 | +STRAGG 的聚合状态复用 PostgreSQL 内置 `string_agg` 所使用的 `StringInfo` 布局, |
| 18 | +从而可以直接引用 `string_agg_finalfn`、`string_agg_combine`、 |
| 19 | +`string_agg_serialize`、`string_agg_deserialize` 四个内置函数, |
| 20 | +无需另行实现 finalize、并行合并及序列化逻辑。 |
| 21 | + |
| 22 | +`StringInfo` 状态的约定如下: |
| 23 | + |
| 24 | +[source,c] |
| 25 | +---- |
| 26 | +/* |
| 27 | + * data = "," + val1 + "," + val2 + ... |
| 28 | + * 首元素前也预置一个逗号,便于 finalfn 统一处理 |
| 29 | + * cursor = 1 |
| 30 | + * 记录前导分隔符的字节长度(逗号占 1 字节) |
| 31 | + * string_agg_finalfn 返回 &data[cursor],即自动去掉前导逗号 |
| 32 | + */ |
| 33 | +---- |
| 34 | + |
| 35 | +=== 转换函数(`stragg_transfn`) |
| 36 | + |
| 37 | +转换函数位于 |
| 38 | +`contrib/ivorysql_ora/src/builtin_functions/misc_functions.c`。 |
| 39 | + |
| 40 | +[source,c] |
| 41 | +---- |
| 42 | +PG_FUNCTION_INFO_V1(stragg_transfn); |
| 43 | +
|
| 44 | +Datum |
| 45 | +stragg_transfn(PG_FUNCTION_ARGS) |
| 46 | +{ |
| 47 | + StringInfo state; |
| 48 | + MemoryContext aggcontext; |
| 49 | + MemoryContext oldcontext; |
| 50 | +
|
| 51 | + if (!AggCheckCallContext(fcinfo, &aggcontext)) |
| 52 | + elog(ERROR, "stragg_transfn called in non-aggregate context"); |
| 53 | +
|
| 54 | + state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0); |
| 55 | +
|
| 56 | + /* 跳过 NULL 输入,与 Oracle STRAGG 行为一致 */ |
| 57 | + if (!PG_ARGISNULL(1)) |
| 58 | + { |
| 59 | + text *value = PG_GETARG_TEXT_PP(1); |
| 60 | +
|
| 61 | + if (state == NULL) |
| 62 | + { |
| 63 | + oldcontext = MemoryContextSwitchTo(aggcontext); |
| 64 | + state = makeStringInfo(); |
| 65 | + MemoryContextSwitchTo(oldcontext); |
| 66 | +
|
| 67 | + /* 首个值:预置分隔符,cursor 记录其长度 */ |
| 68 | + appendStringInfoChar(state, ','); |
| 69 | + state->cursor = 1; |
| 70 | + } |
| 71 | + else |
| 72 | + { |
| 73 | + appendStringInfoChar(state, ','); |
| 74 | + } |
| 75 | +
|
| 76 | + appendBinaryStringInfo(state, VARDATA_ANY(value), VARSIZE_ANY_EXHDR(value)); |
| 77 | + } |
| 78 | +
|
| 79 | + if (state) |
| 80 | + PG_RETURN_POINTER(state); |
| 81 | + PG_RETURN_NULL(); |
| 82 | +} |
| 83 | +---- |
| 84 | + |
| 85 | +关键设计点: |
| 86 | + |
| 87 | +* 状态在聚合上下文(`aggcontext`)中分配,生命周期覆盖整个聚合过程。 |
| 88 | +* `StringInfo` 内部缓冲区按需翻倍扩容,追加操作均摊 O(1),总时间复杂度 O(N), |
| 89 | + 优于纯 SQL 拼接方案的 O(N²)。 |
| 90 | +* NULL 输入由 `PG_ARGISNULL(1)` 判断并跳过,状态不受污染。 |
| 91 | + |
| 92 | +=== SQL 定义 |
| 93 | + |
| 94 | +转换函数和聚合定义位于 |
| 95 | +`contrib/ivorysql_ora/src/builtin_functions/builtin_functions--1.0.sql`。 |
| 96 | + |
| 97 | +[source,sql] |
| 98 | +---- |
| 99 | +CREATE FUNCTION sys.stragg_transfn(internal, text) |
| 100 | +RETURNS internal |
| 101 | +AS 'MODULE_PATHNAME', 'stragg_transfn' |
| 102 | +LANGUAGE C |
| 103 | +CALLED ON NULL INPUT |
| 104 | +PARALLEL SAFE; |
| 105 | +
|
| 106 | +CREATE AGGREGATE sys.stragg(text) ( |
| 107 | + SFUNC = sys.stragg_transfn, |
| 108 | + STYPE = internal, |
| 109 | + FINALFUNC = string_agg_finalfn, |
| 110 | + COMBINEFUNC = string_agg_combine, |
| 111 | + SERIALFUNC = string_agg_serialize, |
| 112 | + DESERIALFUNC = string_agg_deserialize, |
| 113 | + PARALLEL = SAFE |
| 114 | +); |
| 115 | +---- |
| 116 | + |
| 117 | +`FINALFUNC`、`COMBINEFUNC`、`SERIALFUNC`、`DESERIALFUNC` 均直接引用 PostgreSQL |
| 118 | +内置的 `string_agg` 系列函数,因为 STRAGG 与 `string_agg` 使用完全相同的 |
| 119 | +`StringInfo` 状态格式。 |
| 120 | + |
| 121 | +=== 并行聚合支持 |
| 122 | + |
| 123 | +通过指定 `COMBINEFUNC = string_agg_combine` 和序列化/反序列化函数, |
| 124 | +STRAGG 支持 PostgreSQL 的并行聚合执行路径。各并行工作进程独立维护局部 |
| 125 | +`StringInfo` 状态,最终由 leader 进程通过 `string_agg_combine` 合并。 |
| 126 | + |
| 127 | +=== 回归测试 |
| 128 | + |
| 129 | +测试文件:`contrib/ivorysql_ora/sql/ora_stragg.sql`, |
| 130 | +对应预期输出:`contrib/ivorysql_ora/expected/ora_stragg.out`。 |
| 131 | + |
| 132 | +在 `Makefile` 的 `ORA_REGRESS` 列表中已加入 `ora_stragg`, |
| 133 | +可通过以下命令运行: |
| 134 | + |
| 135 | +[source,bash] |
| 136 | +---- |
| 137 | +cd contrib/ivorysql_ora |
| 138 | +make installcheck |
| 139 | +---- |
0 commit comments