AGE中toStringList函数数组类型转换元素异常问题求助
问题:AGE中toStringList函数返回数组元素被最后一个元素覆盖
问题现象
执行以下Cypher查询时,返回的字符串列表中所有元素被最后一个元素覆盖:
demo=# SELECT * FROM cypher('grafo', $$ RETURN toStringList([1.3, 8, 7.4, 2.5]) $$) AS (toFloatList agtype); tofloatlist ---------------------------- ["2.5", "2", "2.5", "2.5"] (1 row)
问题代码
C语言实现的age_tostringlist函数
PG_FUNCTION_INFO_V1(age_tostringlist); /* * toStringList() converts a list of values and returns a list of String values. * If any values are not convertible to string point they will be null in the list returned. */ Datum age_tostringlist(PG_FUNCTION_ARGS) { agtype *agt_arg = NULL; agtype_in_state agis_result; agtype_value *elem; agtype_value string_elem; char *string = NULL; int count; int i; float float_num; char buffer[64]; /* check for null */ if (PG_ARGISNULL(0)) { PG_RETURN_NULL(); } agt_arg = AG_GET_ARG_AGTYPE_P(0); /* check for an array */ if (!AGT_ROOT_IS_ARRAY(agt_arg) || AGT_ROOT_IS_SCALAR(agt_arg)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("toStringList() argument must resolve to a list or null"))); count = AGT_ROOT_COUNT(agt_arg); /* if we have an empty list or only one element in the list, return null */ if (count == 0) PG_RETURN_NULL(); /* clear the result structure */ MemSet(&agis_result, 0, sizeof(agtype_in_state)); /* push the beginning of the array */ agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_BEGIN_ARRAY, NULL); /* iterate through the list */ for (i = 0; i < count; i++) { // TODO: check element's type, it's value, and convert it to string if possible. elem = get_ith_agtype_value_from_container(&agt_arg->root, i); string_elem.type = AGTV_STRING; switch (elem->type) { case AGTV_STRING: if(!elem) { string_elem.type = AGTV_NULL; agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, &string_elem); } string_elem.val.string.val = elem->val.string.val; string_elem.val.string.len = elem->val.string.len; agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, &string_elem); break; case AGTV_FLOAT: string_elem.type = AGTV_STRING; // sprintf(buffer, "%d", elem->val.float_value); float_num = elem->val.float_value; string_elem.val.string.val = gcvt(float_num, 6, buffer); string_elem.val.string.len = strlen(buffer); agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, &string_elem); break; case AGTV_INTEGER: string_elem.type = AGTV_STRING; sprintf(buffer, "%d", elem->val.int_value); string_elem.val.string.val = buffer; string_elem.val.string.len = strlen(buffer); agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, &string_elem); break; default: string_elem.type = AGTV_NULL; agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, &string_elem); break; } } agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_END_ARRAY, NULL); PG_RETURN_POINTER(agtype_value_to_agtype(agis_result.res)); }
SQL函数定义(age--1.3.0.sql)
CREATE FUNCTION ag_catalog.age_tostringlist(variadic "any") RETURNS agtype LANGUAGE c IMMUTABLE RETURNS NULL ON NULL INPUT PARALLEL SAFE AS 'MODULE_PATHNAME';
问题原因
核心问题是所有字符串元素都指向同一个栈上的局部缓冲区buffer:
buffer是函数内的局部数组,存储在栈内存中,每次循环都会被新的转换结果覆盖。push_agtype_value函数仅保存字符串的指针,不会复制字符串内容。最终所有元素的字符串指针都指向同一个buffer,当循环结束时,buffer中存储的是最后一次转换的结果,导致所有元素显示为最后一个值。- 额外问题:
AGTV_STRING分支中if(!elem)的逻辑错误,elem是刚通过get_ith_agtype_value_from_container获取的,不可能为NULL,且代码先访问elem->val再判断,会导致潜在的段错误。
解决方案
需要为每个转换后的字符串分配独立的内存空间(使用PostgreSQL的palloc函数,内存会被PostgreSQL自动管理),避免多个元素共享同一个缓冲区。
修改后的C代码:
PG_FUNCTION_INFO_V1(age_tostringlist); /* * toStringList() converts a list of values and returns a list of String values. * If any values are not convertible to string point they will be null in the list returned. */ Datum age_tostringlist(PG_FUNCTION_ARGS) { agtype *agt_arg = NULL; agtype_in_state agis_result; agtype_value *elem; agtype_value string_elem; int count; int i; float float_num; char buffer[64]; /* check for null */ if (PG_ARGISNULL(0)) { PG_RETURN_NULL(); } agt_arg = AG_GET_ARG_AGTYPE_P(0); /* check for an array */ if (!AGT_ROOT_IS_ARRAY(agt_arg) || AGT_ROOT_IS_SCALAR(agt_arg)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("toStringList() argument must resolve to a list or null"))); count = AGT_ROOT_COUNT(agt_arg); /* if we have an empty list, return null */ if (count == 0) PG_RETURN_NULL(); /* clear the result structure */ MemSet(&agis_result, 0, sizeof(agtype_in_state)); /* push the beginning of the array */ agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_BEGIN_ARRAY, NULL); /* iterate through the list */ for (i = 0; i < count; i++) { elem = get_ith_agtype_value_from_container(&agt_arg->root, i); string_elem.type = AGTV_STRING; switch (elem->type) { case AGTV_STRING: // 直接复用原字符串的内存(输入agtype是只读的,安全) string_elem.val.string.val = elem->val.string.val; string_elem.val.string.len = elem->val.string.len; agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, &string_elem); break; case AGTV_FLOAT: float_num = elem->val.float_value; gcvt(float_num, 6, buffer); string_elem.val.string.len = strlen(buffer); // 分配独立内存并复制字符串 string_elem.val.string.val = palloc(string_elem.val.string.len + 1); strcpy(string_elem.val.string.val, buffer); agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, &string_elem); break; case AGTV_INTEGER: // 使用snprintf更安全,避免缓冲区溢出 snprintf(buffer, sizeof(buffer), "%d", elem->val.int_value); string_elem.val.string.len = strlen(buffer); // 分配独立内存并复制字符串 string_elem.val.string.val = palloc(string_elem.val.string.len + 1); strcpy(string_elem.val.string.val, buffer); agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, &string_elem); break; default: string_elem.type = AGTV_NULL; agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, &string_elem); break; } } agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_END_ARRAY, NULL); PG_RETURN_POINTER(agtype_value_to_agtype(agis_result.res)); }
修改要点
- 移除
AGTV_STRING分支中无用的if(!elem)判断,修复逻辑错误。 - 对
AGTV_FLOAT和AGTV_INTEGER类型,使用palloc为每个字符串分配独立内存,再用strcpy复制buffer中的内容,确保每个元素的字符串指针指向不同的内存块。 - 替换
sprintf为snprintf,避免缓冲区溢出风险。 - 注释掉原代码中“单个元素返回NULL”的逻辑(若需保留原需求可自行恢复)。
内容的提问来源于stack exchange,提问作者Marcos Silva
相关产品推荐
相关产品推荐

