用C语言开发PostgreSQL用户自定义函数之内部函数调用

本文涉及的产品
PolarClaw,2核4GB
简介: 本教程详解如何用C语言开发PostgreSQL的Numeric类型自定义函数,涵盖加法、可变参数求和、默认值处理、数组求和及加权求和,深入讲解Datum、ArrayType、内存管理与内置函数调用,提升高性能计算能力。

本教程通过实现5个基于PostgreSQL的C语言自定义函数,使用PostgreSQL的numeric类型,实现了numeric加法、可变参数求和、带默认值的加法、数组求和、加权求和。目的是讲解如何在C语言自定义函数中使用PostgreSQL的内部类型、内置函数、可变参数、可变参数解析等内容。

一、核心概念说明

  • PostgreSQL C函数:PostgreSQL底层由C实现,自定义C函数性能远高于PL/pgSQL,适合高频计算场景。
  • Datum:PostgreSQL中表示数据的通用类型,所有数据在C函数中均以Datum传递。
  • ArrayType:PostgreSQL数组的C语言表示类型,需通过deconstruct_array解构为数组元素。
  • DirectFunctionCall2:调用PostgreSQL内置函数的宏(2表示2个参数),如numeric_add是内置的数值加法函数。

二、代码完整解析

先将源代码保存为numeric_add.c

#include "postgres.h"
#include "fmgr.h"
#include "utils/array.h"
#include "utils/fmgrprotos.h"
#include "catalog/pg_type.h"
#include "utils/lsyscache.h"

PG_MODULE_MAGIC;

/* 基本版本:numeric_add */
PG_FUNCTION_INFO_V1(my_numeric_add);

Datum
my_numeric_add(PG_FUNCTION_ARGS)
{
    Datum       num1;
    Datum       num2;
    Datum       result;

    /* 检查参数是否为NULL */
    if (PG_ARGISNULL(0) || PG_ARGISNULL(1))
        PG_RETURN_NULL();

    /* 获取Datum参数 */
    num1 = PG_GETARG_DATUM(0);
    num2 = PG_GETARG_DATUM(1);

    /* 调用PostgreSQL内置的numeric_add函数 */
    result = DirectFunctionCall2(numeric_add, num1, num2);

    PG_RETURN_DATUM(result);
}

/* 高级版本:支持多个参数相加,使用PostgreSQL的聚合函数风格 */
PG_FUNCTION_INFO_V1(my_numeric_sum);

Datum
my_numeric_sum(PG_FUNCTION_ARGS)
{
    int         nargs = PG_NARGS();
    Datum       sum = (Datum) 0;
    bool        sum_isnull = true;
    int         i;

    bool        variadic = get_fn_expr_variadic(fcinfo->flinfo);
    Datum       *args_res;
    bool       *nulls_res;
    Oid           *types_res;    

    if(variadic)
    {
        ArrayType  *array_in;
        Oid            element_type;
        bool        typbyval;
        char        typalign;
        int16        typlen;

        Assert(PG_NARGS() >= 1);

        if (PG_ARGISNULL(0))
            PG_RETURN_NULL();

        array_in = PG_GETARG_ARRAYTYPE_P(0);
        element_type = ARR_ELEMTYPE(array_in);

        get_typlenbyvalalign(element_type,
                             &typlen, &typbyval, &typalign);
        deconstruct_array(array_in, element_type, typlen, typbyval,
                          typalign, &args_res, &nulls_res,
                          &nargs);

        Datum total = args_res[0];
        for(i=1; i<nargs; i++)
        {
            if (!nulls_res[i])
            {
                Datum current = args_res[i];
                total =  DirectFunctionCall2(numeric_add, total, current);
            }

        }   
        sum = total; 
        sum_isnull = false;
    }
    else {
        /* 遍历所有参数 */
    for (i = 0; i < nargs; i++)
    {
        if (!PG_ARGISNULL(i))
        {
            Datum current = PG_GETARG_DATUM(i);

            if (sum_isnull)
            {
                /* 第一个非NULL值 */
                sum = current;
                sum_isnull = false;
            }
            else
            {
                /* 累加到当前和 */
                sum = DirectFunctionCall2(numeric_add, sum, current);
            }
        }
    }
    }    

    if (sum_isnull)
        PG_RETURN_NULL();
    else
        PG_RETURN_DATUM(sum);
}

/* 安全加法:处理NULL,返回默认值 */
PG_FUNCTION_INFO_V1(my_numeric_add_with_default);

Datum
my_numeric_add_with_default(PG_FUNCTION_ARGS)
{
    Datum       num1;
    Datum       num2;
    Datum       default_val;
    Datum       result;
    bool        num1_isnull = PG_ARGISNULL(0);
    bool        num2_isnull = PG_ARGISNULL(1);
    bool        default_isnull = PG_ARGISNULL(2);

    /* 如果两个参数都为NULL,返回NULL或默认值 */
    if (num1_isnull && num2_isnull)
    {
        if (default_isnull)
            PG_RETURN_NULL();
        else
        {
            default_val = PG_GETARG_DATUM(2);
            PG_RETURN_DATUM(default_val);
        }
    }

    /* 如果只有一个参数为NULL,用0或默认值替代 */
    if (num1_isnull)
    {
        if (default_isnull)
            num1 = DirectFunctionCall1(int4_numeric, Int32GetDatum(0));
        else
            num1 = PG_GETARG_DATUM(2);
    }
    else
    {
        num1 = PG_GETARG_DATUM(0);
    }

    if (num2_isnull)
    {
        if (default_isnull)
            num2 = DirectFunctionCall1(int4_numeric, Int32GetDatum(0));
        else
            num2 = PG_GETARG_DATUM(2);
    }
    else
    {
        num2 = PG_GETARG_DATUM(1);
    }

    /* 执行加法运算 */
    result = DirectFunctionCall2(numeric_add, num1, num2);

    PG_RETURN_DATUM(result);
}

/* 批量加法:处理numeric数组相加 */
PG_FUNCTION_INFO_V1(my_numeric_array_sum);

Datum
my_numeric_array_sum(PG_FUNCTION_ARGS)
{
    ArrayType  *input_array;
    Datum       sum = (Datum) 0;
    bool        sum_isnull = true;
    Datum      *elems;
    bool       *nulls;
    int         nelems;
    Oid         elem_type;
    int16       elem_width;
    bool        elem_byval;
    char        elem_align;
    int         i;

    /* 检查参数是否为NULL */
    if (PG_ARGISNULL(0))
        PG_RETURN_NULL();

    input_array = PG_GETARG_ARRAYTYPE_P(0);

    /* 验证数组元素类型是否为numeric */
    elem_type = ARR_ELEMTYPE(input_array);
    if (elem_type != NUMERICOID)
        ereport(ERROR,
                (errcode(ERRCODE_DATATYPE_MISMATCH),
                 errmsg("输入数组元素类型必须为numeric")));

    /* 获取数组信息并解构数组 */
    get_typlenbyvalalign(elem_type, &elem_width, &elem_byval, &elem_align);
    deconstruct_array(input_array, elem_type, elem_width, 
                      elem_byval, elem_align,
                      &elems, &nulls, &nelems);

    /* 遍历数组元素,计算总和 */
    for (i = 0; i < nelems; i++)
    {
        if (!nulls[i])
        {
            Datum current = elems[i];

            if (sum_isnull)
            {
                /* 第一个非NULL值 */
                sum = current;
                sum_isnull = false;
            }
            else
            {
                /* 累加到当前和 */
                sum = DirectFunctionCall2(numeric_add, sum, current);
            }
        }
    }

    /* 释放数组内存 */
    pfree(elems);
    pfree(nulls);

    if (sum_isnull)
        PG_RETURN_NULL();
    else
        PG_RETURN_DATUM(sum);
}

/* 加权求和:两个数组对应元素相乘后求和 */
PG_FUNCTION_INFO_V1(my_numeric_weighted_sum);

Datum
my_numeric_weighted_sum(PG_FUNCTION_ARGS)
{
    ArrayType  *values_array;
    ArrayType  *weights_array;
    Datum       result = (Datum) 0;
    bool        result_isnull = true;
    Datum         return_null = (Datum) 0;
    Datum      *values;
    Datum      *weights;
    bool       *values_nulls;
    bool       *weights_nulls;
    int         nvalues;
    int         nweights;
    Oid         elem_type;
    int16       elem_width;
    bool        elem_byval;
    char        elem_align;
    int         i;

    /* 检查参数是否为NULL */
    if (PG_ARGISNULL(0) || PG_ARGISNULL(1))
        PG_RETURN_NULL();

    values_array = PG_GETARG_ARRAYTYPE_P(0);
    weights_array = PG_GETARG_ARRAYTYPE_P(1);

    /* 验证数组元素类型是否为numeric */
    elem_type = ARR_ELEMTYPE(values_array);
    if (elem_type != NUMERICOID)
        ereport(ERROR,
                (errcode(ERRCODE_DATATYPE_MISMATCH),
                 errmsg("数值数组元素类型必须为numeric")));

    if (ARR_ELEMTYPE(weights_array) != NUMERICOID)
        ereport(ERROR,
                (errcode(ERRCODE_DATATYPE_MISMATCH),
                 errmsg("权重数组元素类型必须为numeric")));

    /* 检查数组长度 */
    nvalues = ARR_DIMS(values_array)[0];
    nweights = ARR_DIMS(weights_array)[0];

    if (nvalues != nweights)
        ereport(ERROR,
                (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
                 errmsg("数值数组和权重数组长度必须相同")));

    /* 获取数组信息并解构数组 */
    get_typlenbyvalalign(elem_type, &elem_width, &elem_byval, &elem_align);

    deconstruct_array(values_array, elem_type, elem_width, 
                      elem_byval, elem_align,
                      &values, &values_nulls, &nvalues);

    deconstruct_array(weights_array, elem_type, elem_width, 
                      elem_byval, elem_align,
                      &weights, &weights_nulls, &nweights);

    /* 计算加权和 */
    for (i = 0; i < nvalues; i++)
    {
        if (!values_nulls[i] && !weights_nulls[i])
        {
            Datum product = DirectFunctionCall2(numeric_mul, values[i], weights[i]);

            if (result_isnull)
            {
                result = product;
                result_isnull = false;
            }
            else
            {
                Datum new_result = DirectFunctionCall2(numeric_add, result, product);

                /* 释放中间结果的内存 */
                if (result != (Datum) 0)
                    pfree(DatumGetPointer(result));
                pfree(DatumGetPointer(product));

                result = new_result;
            }
        }
    }

    /* 释放数组内存 */
    pfree(values);
    pfree(values_nulls);
    pfree(weights);
    pfree(weights_nulls);

    if (result_isnull)
        PG_RETURN_NULL();
    else
        PG_RETURN_DATUM(result);
}

下面分函数解析核心逻辑:

2.1 基础加法函数(my_numeric_add)

#include "postgres.h"
#include "fmgr.h"
#include "utils/array.h"
#include "utils/fmgrprotos.h"
#include "catalog/pg_type.h"
#include "utils/lsyscache.h"

PG_MODULE_MAGIC; // 必须的模块标识,PostgreSQL强制要求

/* 基本版本:numeric_add */
PG_FUNCTION_INFO_V1(my_numeric_add); // 声明函数版本(V1是当前标准)

Datum
my_numeric_add(PG_FUNCTION_ARGS)
{
   
    Datum       num1;
    Datum       num2;
    Datum       result;

    /* 检查参数是否为NULL */
    if (PG_ARGISNULL(0) || PG_ARGISNULL(1))
        PG_RETURN_NULL(); // 任意参数NULL则返回NULL

    /* 获取Datum参数(0/1对应第1/2个参数) */
    num1 = PG_GETARG_DATUM(0);
    num2 = PG_GETARG_DATUM(1);

    /* 调用PostgreSQL内置的numeric_add函数(2个参数) */
    result = DirectFunctionCall2(numeric_add, num1, num2);

    PG_RETURN_DATUM(result); // 返回计算结果
}

核心逻辑
最基础的二元函数,检查参数NULL值 → 获取参数 → 调用内置加法 → 返回结果。
这个函数调用内置函数numeric_add实现numeric加法。根据PostgreSQL的编码规范,不能直接调用内置函数,根据使用场景,可使用:

  • DirectFunctionCallXXX系列函数
  • CallerFInfoFunctionCallXXX系列函数
  • OidFunctionCallXXX系列函数

如果内置函数不需要利用调用者函数的 flinfo 参数来初始化函数调用所需的 fcinfo 结构,可使用DirectFunctionCallXXX系列函数,否则需要使用CallerFInfoFunctionCallXXX或者OidFunctionCallXXX系列函数。CallerFInfoFunctionCallXXXOidFunctionCallXXX的区别在于参数个数,CallerFInfoFunctionCallXXX只提供了支持1个或者2个参数的函数,如果参数个数超过两个,就只能使用OidFunctionCallXXX系列函数了。
OidFunctionCallXXX的使用稍微麻烦些,需要先取得内置函数的OID,然后调用OidFunctionCallXXX系列函数。下面举个简单的例子:

char *value = "{1,2,3,4,5}";
Oid functionOid = fmgr_internal_function("array_in");
if(functionOid != InvalidOid) {
    res = OidFunctionCall3(
        functionOid,
        CStringGetDatum(value),
        Int32GetDatum(INT4OID),
        Int32GetDatum(-1));
}

调用array_in需要三个参数,并且使用了调用者的fcinfo,因此需要使用OidFunctionCall3fmgr_internal_function函数用于获取内置函数的Oid。array_in.png

2.2 可变参数求和(my_numeric_sum)

这是最核心的可变参数函数,支持两种调用方式:

  1. 直接传多个numeric参数(如my_numeric_sum(1,2,3));
  2. 传numeric数组(如my_numeric_sum(VARIADIC ARRAY[1,2,3]))。
PG_FUNCTION_INFO_V1(my_numeric_sum);

Datum
my_numeric_sum(PG_FUNCTION_ARGS)
{
   
    int         nargs = PG_NARGS(); // 获取参数总数
    Datum       sum = (Datum) 0;
    bool        sum_isnull = true; // 标记总和是否为NULL
    int         i;

    bool        variadic = get_fn_expr_variadic(fcinfo->flinfo); // 判断是否为可变数组调用
    Datum       *args_res; // 存储数组解构后的参数
    bool       *nulls_res; // 存储参数是否为NULL的标记
    Oid           *types_res;    

    if(variadic) // 方式2:可变数组调用(VARIADIC)
    {
   
        ArrayType  *array_in;
        Oid            element_type;
        bool        typbyval;
        char        typalign;
        int16        typlen;

        Assert(PG_NARGS() >= 1); // 断言:至少1个参数(数组)

        if (PG_ARGISNULL(0))
            PG_RETURN_NULL();

        array_in = PG_GETARG_ARRAYTYPE_P(0); // 获取数组参数
        element_type = ARR_ELEMTYPE(array_in); // 获取数组元素类型

        // 获取元素类型的内存属性(长度、是否传值、对齐方式)
        get_typlenbyvalalign(element_type, &typlen, &typbyval, &typalign);
        // 解构数组为元素数组
        deconstruct_array(array_in, element_type, typlen, typbyval,
                          typalign, &args_res, &nulls_res, &nargs);

        // 数组元素累加
        Datum total = args_res[0];
        for(i=1; i<nargs; i++)
        {
   
            if (!nulls_res[i]) // 跳过NULL元素
            {
   
                Datum current = args_res[i];
                total =  DirectFunctionCall2(numeric_add, total, current);
            }
        }   
        sum = total; 
        sum_isnull = false;
    }
    else {
    // 方式1:直接传多个参数
        /* 遍历所有参数 */
    for (i = 0; i < nargs; i++)
    {
   
        if (!PG_ARGISNULL(i)) // 跳过NULL参数
        {
   
            Datum current = PG_GETARG_DATUM(i);

            if (sum_isnull)
            {
   
                /* 第一个非NULL值:初始化总和 */
                sum = current;
                sum_isnull = false;
            }
            else
            {
   
                /* 累加到当前和 */
                sum = DirectFunctionCall2(numeric_add, sum, current);
            }
        }
    }
    }    

    if (sum_isnull)
        PG_RETURN_NULL(); // 所有参数都是NULL则返回NULL
    else
        PG_RETURN_DATUM(sum);
}

核心逻辑

  • get_fn_expr_variadic判断调用方式(普通参数/可变数组);
  • 数组调用时通过deconstruct_array解构数组;
  • 遍历所有非NULL参数,调用numeric_add累加。

2.3 带默认值的加法(my_numeric_add_with_default)

PG_FUNCTION_INFO_V1(my_numeric_add_with_default);

Datum
my_numeric_add_with_default(PG_FUNCTION_ARGS)
{
   
    Datum       num1;
    Datum       num2;
    Datum       default_val;
    Datum       result;
    bool        num1_isnull = PG_ARGISNULL(0); // 第1个参数是否NULL
    bool        num2_isnull = PG_ARGISNULL(1); // 第2个参数是否NULL
    bool        default_isnull = PG_ARGISNULL(2); // 第3个参数(默认值)是否NULL

    /* 两个参数都为NULL:返回默认值或NULL */
    if (num1_isnull && num2_isnull)
    {
   
        if (default_isnull)
            PG_RETURN_NULL();
        else
        {
   
            default_val = PG_GETARG_DATUM(2);
            PG_RETURN_DATUM(default_val);
        }
    }

    /* 单个参数NULL:用0或默认值替代 */
    if (num1_isnull)
    {
   
        if (default_isnull)
            num1 = DirectFunctionCall1(int4_numeric, Int32GetDatum(0)); // 0转numeric
        else
            num1 = PG_GETARG_DATUM(2);
    }
    else
    {
   
        num1 = PG_GETARG_DATUM(0);
    }

    if (num2_isnull)
    {
   
        if (default_isnull)
            num2 = DirectFunctionCall1(int4_numeric, Int32GetDatum(0));
        else
            num2 = PG_GETARG_DATUM(2);
    }
    else
    {
   
        num2 = PG_GETARG_DATUM(1);
    }

    /* 执行加法 */
    result = DirectFunctionCall2(numeric_add, num1, num2);

    PG_RETURN_DATUM(result);
}

核心逻辑
处理NULL参数的优雅方式,支持自定义默认值(无默认值时用0替代)。

2.4 数组求和(my_numeric_array_sum)

PG_FUNCTION_INFO_V1(my_numeric_array_sum);

Datum
my_numeric_array_sum(PG_FUNCTION_ARGS)
{
   
    ArrayType  *input_array;
    Datum       sum = (Datum) 0;
    bool        sum_isnull = true;
    Datum      *elems; // 数组元素
    bool       *nulls; // 元素NULL标记
    int         nelems; // 元素数量
    Oid         elem_type;
    int16       elem_width;
    bool        elem_byval;
    char        elem_align;
    int         i;

    /* 检查参数是否为NULL */
    if (PG_ARGISNULL(0))
        PG_RETURN_NULL();

    input_array = PG_GETARG_ARRAYTYPE_P(0); // 获取数组参数

    /* 强制校验数组元素类型为numeric */
    elem_type = ARR_ELEMTYPE(input_array);
    if (elem_type != NUMERICOID)
        ereport(ERROR,
                (errcode(ERRCODE_DATATYPE_MISMATCH),
                 errmsg("输入数组元素类型必须为numeric")));

    /* 获取数组元素属性并解构 */
    get_typlenbyvalalign(elem_type, &elem_width, &elem_byval, &elem_align);
    deconstruct_array(input_array, elem_type, elem_width, 
                      elem_byval, elem_align,
                      &elems, &nulls, &nelems);

    /* 遍历累加 */
    for (i = 0; i < nelems; i++)
    {
   
        if (!nulls[i])
        {
   
            Datum current = elems[i];

            if (sum_isnull)
            {
   
                sum = current;
                sum_isnull = false;
            }
            else
            {
   
                sum = DirectFunctionCall2(numeric_add, sum, current);
            }
        }
    }

    /* 释放数组内存(关键:避免内存泄漏) */
    pfree(elems);
    pfree(nulls);

    if (sum_isnull)
        PG_RETURN_NULL();
    else
        PG_RETURN_DATUM(sum);
}

核心逻辑

  • 强制校验数组元素类型为numeric;
  • 解构数组后遍历累加;
  • 手动释放数组内存(pfree),避免内存泄漏。

2.5 加权求和(my_numeric_weighted_sum)

PG_FUNCTION_INFO_V1(my_numeric_weighted_sum);

Datum
my_numeric_weighted_sum(PG_FUNCTION_ARGS)
{
   
    ArrayType  *values_array; // 数值数组
    ArrayType  *weights_array; // 权重数组
    Datum       result = (Datum) 0;
    bool        result_isnull = true;
    Datum      *values;
    Datum      *weights;
    bool       *values_nulls;
    bool       *weights_nulls;
    int         nvalues;
    int         nweights;
    Oid         elem_type;
    int16       elem_width;
    bool        elem_byval;
    char        elem_align;
    int         i;

    /* 检查参数是否为NULL */
    if (PG_ARGISNULL(0) || PG_ARGISNULL(1))
        PG_RETURN_NULL();

    values_array = PG_GETARG_ARRAYTYPE_P(0);
    weights_array = PG_GETARG_ARRAYTYPE_P(1);

    /* 校验数组元素类型 */
    elem_type = ARR_ELEMTYPE(values_array);
    if (elem_type != NUMERICOID)
        ereport(ERROR,
                (errcode(ERRCODE_DATATYPE_MISMATCH),
                 errmsg("数值数组元素类型必须为numeric")));

    if (ARR_ELEMTYPE(weights_array) != NUMERICOID)
        ereport(ERROR,
                (errcode(ERRCODE_DATATYPE_MISMATCH),
                 errmsg("权重数组元素类型必须为numeric")));

    /* 校验数组长度一致 */
    nvalues = ARR_DIMS(values_array)[0];
    nweights = ARR_DIMS(weights_array)[0];

    if (nvalues != nweights)
        ereport(ERROR,
                (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
                 errmsg("数值数组和权重数组长度必须相同")));

    /* 解构两个数组 */
    get_typlenbyvalalign(elem_type, &elem_width, &elem_byval, &elem_align);

    deconstruct_array(values_array, elem_type, elem_width, 
                      elem_byval, elem_align,
                      &values, &values_nulls, &nvalues);

    deconstruct_array(weights_array, elem_type, elem_width, 
                      elem_byval, elem_align,
                      &weights, &weights_nulls, &nweights);

    /* 计算加权和:value[i] * weight[i] 累加 */
    for (i = 0; i < nvalues; i++)
    {
   
        if (!values_nulls[i] && !weights_nulls[i])
        {
   
            // 元素相乘
            Datum product = DirectFunctionCall2(numeric_mul, values[i], weights[i]);

            if (result_isnull)
            {
   
                result = product;
                result_isnull = false;
            }
            else
            {
   
                // 累加到结果
                Datum new_result = DirectFunctionCall2(numeric_add, result, product);

                /* 释放中间结果内存(避免泄漏) */
                if (result != (Datum) 0)
                    pfree(DatumGetPointer(result));
                pfree(DatumGetPointer(product));

                result = new_result;
            }
        }
    }

    /* 释放数组内存 */
    pfree(values);
    pfree(values_nulls);
    pfree(weights);
    pfree(weights_nulls);

    if (result_isnull)
        PG_RETURN_NULL();
    else
        PG_RETURN_DATUM(result);
}

核心逻辑

  • 校验两个数组的类型和长度;
  • 对应元素相乘后累加;
  • 释放中间结果和数组内存,避免内存泄漏。

三、编译与安装

3.1 编译为共享库(CMakeLists.txt)

cmake_minimum_required(VERSION 3.25)
project(numeric_add C)

set(CMAKE_C_STANDARD 11)
set(CMAKE_BUILD_TYPE debug)

list(APPEND flags "-fPIC")

find_program(PG_CONFIG pg_config REQUIRED)

execute_process(COMMAND ${PG_CONFIG} --includedir-server
    OUTPUT_VARIABLE POSTGRESQL_INCLUDE_DIR
    OUTPUT_STRIP_TRAILING_WHITESPACE)

execute_process(COMMAND ${PG_CONFIG} --libdir
    OUTPUT_VARIABLE POSTGRESQL_LIB_DIR
    OUTPUT_STRIP_TRAILING_WHITESPACE)

execute_process(COMMAND ${PG_CONFIG} --libs
    OUTPUT_VARIABLE POSTGRESQL_LIBS
    OUTPUT_STRIP_TRAILING_WHITESPACE)

add_library(numeric_add SHARED numeric_add.c)

# 设置生成的共享库名称,去掉lib前缀
set_target_properties(numeric_add PROPERTIES PREFIX "")
# 最终so文件名
set_target_properties(numeric_add PROPERTIES OUTPUT_NAME numeric_add)

# 安装目录
install(TARGETS numeric_add LIBRARY DESTINATION ${POSTGRESQL_LIB_DIR})

target_compile_options(numeric_add PUBLIC "-fPIC" "-g3" "-O0")
target_link_options(numeric_add PUBLIC "-shared")
target_include_directories(numeric_add PUBLIC ${POSTGRESQL_INCLUDE_DIR})
target_link_directories(numeric_add PUBLIC ${POSTGRESQL_LIB_DIR})
cmake . -DCMAKE_EXPORT_COMPILE_COMMANDS=1
make

3.2 在PostgreSQL中创建函数

  1. 连接PostgreSQL:
    psql
    
  2. 依次创建5个函数(执行以下SQL):

    -- 1. 基础加法函数
    CREATE OR REPLACE FUNCTION my_numeric_add(numeric, numeric)
    RETURNS numeric
    AS '$libdir/numeric_functions'
    LANGUAGE C IMMUTABLE STRICT;
    
    -- 2. 可变参数求和函数(支持多参数/数组)
    CREATE OR REPLACE FUNCTION my_numeric_sum(VARIADIC numeric[])
    RETURNS numeric
    AS '$libdir/numeric_functions'
    LANGUAGE C IMMUTABLE;
    
    -- 3. 带默认值的加法函数
    CREATE OR REPLACE FUNCTION my_numeric_add_with_default(numeric, numeric, numeric)
    RETURNS numeric
    AS '$libdir/numeric_functions'
    LANGUAGE C IMMUTABLE;
    
    -- 4. 数组求和函数
    CREATE OR REPLACE FUNCTION my_numeric_array_sum(numeric[])
    RETURNS numeric
    AS '$libdir/numeric_functions'
    LANGUAGE C IMMUTABLE STRICT;
    
    -- 5. 加权求和函数
    CREATE OR REPLACE FUNCTION my_numeric_weighted_sum(numeric[], numeric[])
    RETURNS numeric
    AS '$libdir/numeric_functions'
    LANGUAGE C IMMUTABLE STRICT;
    
    • $libdir:PostgreSQL扩展目录的简写;
    • IMMUTABLE:函数结果仅依赖输入(无副作用),可优化性能;
    • STRICT:任意参数NULL则返回NULL(部分函数手动处理NULL,故不加)。

四、测试验证

4.1 测试基础加法(my_numeric_add)

-- 正常调用
SELECT my_numeric_add(1.5::numeric, 2.5::numeric); -- 输出4.0

-- NULL参数
SELECT my_numeric_add(NULL, 2.5::numeric); -- 输出NULL

4.2 测试可变参数求和(my_numeric_sum)

-- 方式1:直接传多参数
SELECT my_numeric_sum(1::numeric, 2::numeric, 3::numeric); -- 输出6.0

-- 方式2:传数组(需VARIADIC关键字)
SELECT my_numeric_sum(VARIADIC ARRAY[1::numeric, 2::numeric, 3::numeric]); -- 输出6.0

-- 包含NULL参数
SELECT my_numeric_sum(1::numeric, NULL, 3::numeric); -- 输出4.0

4.3 测试带默认值的加法(my_numeric_add_with_default)

-- 正常调用
SELECT my_numeric_add_with_default(1.5::numeric, 2.5::numeric, 0::numeric); -- 输出4.0

-- 第一个参数NULL,用0替代
SELECT my_numeric_add_with_default(NULL, 2.5::numeric, NULL); -- 输出2.5

-- 两个参数NULL,返回默认值
SELECT my_numeric_add_with_default(NULL, NULL, 10::numeric); -- 输出10.0

4.4 测试数组求和(my_numeric_array_sum)

-- 正常数组
SELECT my_numeric_array_sum(ARRAY[1::numeric, 2::numeric, 3::numeric]); -- 输出6.0

-- 包含NULL的数组
SELECT my_numeric_array_sum(ARRAY[1::numeric, NULL, 3::numeric]); -- 输出4.0

-- 非numeric数组(报错)
SELECT my_numeric_array_sum(ARRAY[1,2,3]); -- 报错:元素类型必须为numeric

4.5 测试加权求和(my_numeric_weighted_sum)

-- 正常调用:(1*0.1)+(2*0.2)+(3*0.3) = 0.1+0.4+0.9 = 1.4
SELECT my_numeric_weighted_sum(ARRAY[1::numeric,2::numeric,3::numeric], ARRAY[0.1::numeric,0.2::numeric,0.3::numeric]); -- 输出1.4

-- 数组长度不一致(报错)
SELECT my_numeric_weighted_sum(ARRAY[1::numeric,2::numeric], ARRAY[0.1::numeric]); -- 报错:长度必须相同
相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍如何基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
相关文章
|
3月前
|
SQL 关系型数据库 数据库
用C语言开发PostgreSQL用户自定义函数之数据查询篇
本教程教你用C语言编写PostgreSQL的UDF函数,通过SPI接口执行SQL查询,利用SRF机制返回多行数据。涵盖头文件引入、函数编写、编译部署及SQL调用全流程,并附内存管理与列序号等避坑提示,助你掌握C语言扩展PostgreSQL的核心技术。
|
3月前
|
SQL 存储 缓存
PL/pgSQL 入门教程(六):从避坑到吃透,聊聊事务、错误处理和底层那些事儿
本文深度解析PL/pgSQL开发避坑指南:详解RAISE多级错误处理与USING增强提示、EXCEPTION事务恢复机制、变量替换限制与计划缓存陷阱,并分享美元符引号、CREATE OR REPLACE调试、extra_warnings预警等实战技巧,助你写出健壮高效存储过程。
|
3月前
|
SQL 监控 关系型数据库
PL/pgSQL 入门教程(五):触发器
PostgreSQL触发器是数据库的“自动服务员”,可在INSERT/UPDATE/DELETE等操作时自动执行校验、日志记录、汇总更新等逻辑。支持BEFORE/AFTER/INSTEAD OF时机,ROW/STATEMENT级别,配合NEW/OLD变量实现灵活数据管控,大幅提升数据一致性与运维效率。
|
3月前
|
搜索推荐 关系型数据库 大数据
PL/pgSQL 入门教程(四):使用游标(cursor)
游标是PostgreSQL中“按需取数”的数据指针,避免大查询内存溢出;支持逐行处理、动态查询、精准更新/删除及函数返回大结果集。分未绑定(灵活)与绑定(固定)两类,核心操作为声明→打开→FETCH/MOVE/UPDATE→关闭,FOR循环可自动简化遍历。
|
3月前
|
Windows 自然语言处理
Ollama Modelfile 详细使用手册
想用Ollama打造专属模型?Modelfile就是你的“模型食谱”!本文以做菜为喻,零基础手把手教你写Modelfile:FROM选基模、PARAMETER调温度/记忆、SYSTEM定角色(如马里奥)、TEMPLATE规范格式、MESSAGE给示例。全程无术语,附实操步骤与避坑指南,看完即能创建并运行自己的第一个自定义模型。
|
3月前
|
数据库 C++ Perl
PL/pgSQL 入门教程(三):控制结构
本文详解PL/pgSQL核心编程:函数返回(RETURN单值、RETURN NEXT/QUERY多行)、条件判断(IF/CASE)、循环控制(LOOP/WHILE/FOR/FOREACH)及异常处理(EXCEPTION),附丰富示例与最佳实践,助你写出健壮高效的数据库逻辑。
|
3月前
|
SQL 缓存 安全
PL/pgSQL 入门教程(二):表达式和基础语句
本文详解PL/pgSQL核心语法:表达式由主SQL引擎以参数化SELECT执行,支持计划缓存;基础语句涵盖赋值(:=/=)、静态/动态SQL执行(INTO/PERFORM/EXECUTE)、结果处理(STRICT模式)、状态获取(FOUND/GET DIAGNOSTICS)及空操作NULL。
|
3月前
|
SQL 存储 关系型数据库
PL/pgSQL 入门教程(一):语法篇
本教程为PL/pgSQL入门首篇,系统讲解其核心基础与语法规则。涵盖函数创建、块结构、变量声明、参数传递、返回类型及排序规则等关键知识点,助你掌握在PostgreSQL中编写高效存储过程与函数的必备技能,提升数据库逻辑处理能力。
|
3月前
|
SQL 存储 关系型数据库
PostgreSQL SQL函数语法详解
本文深入讲解PostgreSQL中SQL语言函数的编写,涵盖参数引用、返回类型(基类型/复合类型/集合)、输出参数、可变参数、默认值、多态函数及排序规则等核心特性,系统阐述其语法、行为与最佳实践。
|
4月前
|
SQL 关系型数据库 数据库
Postgresql入门之psql用法详解(三)- 元命令详解(\dconfig-\if)
psql元命令以反斜杠开头,由psql客户端直接解析执行,用于增强数据库管理与脚本操作。支持参数引用、变量插值、shell命令执行及SQL语句联动,涵盖连接控制、对象查看、数据导入导出等功能,是PostgreSQL交互操作的重要工具。

热门文章

最新文章

下一篇
开通oss服务