You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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:

  1. buffer是函数内的局部数组,存储在栈内存中,每次循环都会被新的转换结果覆盖。
  2. push_agtype_value函数仅保存字符串的指针,不会复制字符串内容。最终所有元素的字符串指针都指向同一个buffer,当循环结束时,buffer中存储的是最后一次转换的结果,导致所有元素显示为最后一个值。
  3. 额外问题: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));
}

修改要点

  1. 移除AGTV_STRING分支中无用的if(!elem)判断,修复逻辑错误。
  2. 对AGTV_FLOAT和AGTV_INTEGER类型,使用palloc为每个字符串分配独立内存,再用strcpy复制buffer中的内容,确保每个元素的字符串指针指向不同的内存块。
  3. 替换sprintf为snprintf,避免缓冲区溢出风险。
  4. 注释掉原代码中“单个元素返回NULL”的逻辑(若需保留原需求可自行恢复)。

内容的提问来源于stack exchange,提问作者Marcos Silva

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 17:17:03