自定义基本数据类型的输入函数中的Postgresql损坏数据

时间:2019-02-13 15:41:32

标签: c postgresql sql-function

我创建了一个自定义类型gp以对DND 5e货币系统建模。我在gp.c中定义了自定义输入和输出函数:

#include "postgres.h"
#include <string.h>
#include "fmgr.h"

#include <stdio.h>

#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif

static const char* inputFormat = " %i %s2 ";
static const char* invalidFormat = "invalid input syntax for gp: \"%s\"";

PG_FUNCTION_INFO_V1(gp_input);

Datum gp_input(PG_FUNCTION_ARGS) {
    char* raw = PG_GETARG_CSTRING(0);
    int32 amt;
    char unit[3];

    if (sscanf(raw, inputFormat, &amt, &unit[0]) != 2) {
        ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg(invalidFormat, raw)));
    }

    switch(unit[1]) {
        case 'p':
            break;
        default:
            ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg(invalidFormat, raw)));
    }
    switch(unit[0]) {
        case 'c':
            break;
        case 's':
            amt *= 10;
            break;
        case 'e':
            amt *= 50;
            break;
        case 'g':
            amt *= 100;
            break;
        case 'p':
            amt *= 1000;
            break;
        default:
            ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg(invalidFormat, raw)));
    }

    int32* result = (int32*)palloc(sizeof(int32));
    *result = amt;

    PG_RETURN_POINTER(result);
}

PG_FUNCTION_INFO_V1(gp_output);

Datum gp_output(PG_FUNCTION_ARGS) {
    int32* raw = (int32*)PG_GETARG_POINTER(0);
    int32 val = *raw;
    unsigned int bufsz = sizeof(unsigned char)*9 + 2;// allow up to 999999999[pgsc]p
    char* buf = (char*) palloc(bufsz+1); // +1 b/c '\0'

    if (val >= 10 && val % 10 == 0) {
        val /= 10;

        if (val >= 10 && val % 10 == 0) {
            val /= 10;

            if (val >= 10 && val % 10 == 0) {
                val /= 10;

                if (sprintf(buf, "%dpp", val) <= 0) {
                    ereport(ERROR, (errcode(ERRCODE_UNTRANSLATABLE_CHARACTER), errmsg("Bad value for gp")));
                }
            }
            else {
                if (sprintf(buf, "%dgp", val) <= 0) {
                    ereport(ERROR, (errcode(ERRCODE_UNTRANSLATABLE_CHARACTER), errmsg("Bad value for gp")));
                }
            }
        }
        else {
            if (sprintf(buf, "%dsp", val) <= 0) {
                ereport(ERROR, (errcode(ERRCODE_UNTRANSLATABLE_CHARACTER), errmsg("Bad value for gp")));
            }
        }
    }
    else {
        if (sprintf(buf, "%dcp", val) <= 0) {
            ereport(ERROR, (errcode(ERRCODE_UNTRANSLATABLE_CHARACTER), errmsg("Bad value for gp")));
        }
    }
    PG_RETURN_CSTRING(buf);
}

我知道我不是在检查数字是否超出范围或存储的值是否适合缓冲区,但是我还没有解决这个问题。我的问题是postgres似乎正在编辑,并且在某些情况下破坏了我存储的值。我有这个测试SQL文件:

DROP TYPE IF EXISTS gp CASCADE;
DROP TABLE IF EXISTS test;

CREATE TYPE gp;

CREATE FUNCTION gp_input(cstring) RETURNS gp AS '$libdir/gp.so' LANGUAGE C IMMUTABLE STRICT;
CREATE FUNCTION gp_output(gp) RETURNS cstring AS '$libdir/gp.so' LANGUAGE C IMMUTABLE STRICT;

CREATE TYPE gp (input=gp_input, output=gp_output);

CREATE TABLE test (val gp);

INSERT INTO test VALUES ('12sp'), ('100gp'), ('1000cp'), ('101cp');
SELECT * FROM test;
INSERT INTO test VALUES ('101sp');

SELECT的输出是:

  val  
-------
 12sp
 10pp
 1pp
 212cp
(4 rows)

因此,我们可以看到除最后一个值外,所有值均已正确存储和表示:101cp被存储为指向int32212的指针。使用ereport警告,我能够确定输入函数返回之前result指向正确的值:101。但是,作为参数传递给我的输出函数的指针指向我未存储的值:212。在我的输入代码的结尾和我的输出代码的开头之间的某个位置,postgres破坏了该值。 总是与输入字符串101cp一起发生,与表的状态或同时插入的任何其他值无关。

但是现在真正奇怪的部分是:最后INSERT使客户端崩溃。解析该gp值后,它会显示错误:

psql:./gptest.sql:15: ERROR:  compressed data is corrupted
LINE 1: INSERT INTO test VALUES ('101sp');
                                 ^

总是发生在值101sp上,无论表状态或在表旁边插入任何其他值。使用ereport警告,我能够在return语句result指向正确的值1010之前看到这一点。这也意味着崩溃发生在返回宏扩展或某些底层代码中。

所以我真的不知道发生了什么。我正在做palloc,所以不应该覆盖内存,而且我也无法想到包含101的值总是有问题-以及取决于单位的不同问题。 int32应该能够存储我正在测试的较小值,因此并非如此。如果这是应该实现的方式,则为Idk,但是我已经检查过,传递给输出的指针与其中任何一个值的result指针的地址都不相同,因此我认为它正在执行某些操作一种memcpy可能是错误的,但后来就知道应该如何让任何人定义自定义基本数据类型。

1 个答案:

答案 0 :(得分:1)

CREATE TYPE带有大量可选参数,其中一些与数据的物理布局有关,这些参数必须与I / O功能期望/返回的结构一致。

文档似乎并未提及这些参数的默认值,但提及“压缩数据”的错误提示您的值是TOASTed,即INTERNALLENGTH的默认值为{{1 }}。此类类型应该以{{1​​}}开头,以描述值的总长度,这肯定不是您要返回的值(尽管Postgres仍会这样解释,从而导致各种奇怪的行为,而不是提及将巨大的随机字节平板保存到表中,并且可能早晚进行段错误...)。

如果您要创建一个内部表示为简单整数(固定长度,传递值等)的类型,则只需复制内置类型的特征:

VARIABLE

然后,您应该可以消除varlena和指针,使用CREATE TYPE gp (input=gp_input, output=gp_output, like=integer); 获取参数,而只需返回palloc()


如果您希望使用内置类型的所有行为,但是具有自定义显示格式,则比您预期的要容易得多。

PG_GETARG_INT32(0)之类的内部C例程与您编写的用于自己实现这种类型的例程相同。因此,您只需复制粘贴其SQL级别的定义,然后使指向现有C处理程序的函数即可完成所有实际工作,即可创建自己的内置类型版本:

PG_RETURN_INT32(amt)
numeric

从那里,您可以用自己的C函数替换输入/输出处理程序,并从internal functions复制粘贴代码作为起点。在这种情况下,最简单的方法可能是在函数开始时将DnD货币字符串转换为简单的十进制字符串,然后让其余代码担心将其转换为CREATE TYPE gp; CREATE FUNCTION gp_in(cstring,oid,integer) RETURNS gp LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE AS 'numeric_in'; CREATE FUNCTION gp_out(gp) RETURNS cstring LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE AS 'numeric_out'; CREATE FUNCTION gp_send(gp) RETURNS bytea LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE AS 'numeric_send'; CREATE FUNCTION gp_recv(internal,oid,integer) RETURNS gp LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE AS 'numeric_recv'; CREATE FUNCTION gptypmodin(cstring[]) RETURNS integer LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE AS 'numerictypmodin'; CREATE FUNCTION gptypmodout(integer) RETURNS cstring LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE AS 'numerictypmodout'; CREATE TYPE gp ( INPUT = gp_in, OUTPUT = gp_out, RECEIVE = gp_recv, SEND = gp_send, TYPMOD_IN = gptypmodin, TYPMOD_OUT = gptypmodout, LIKE = numeric ); 的麻烦细节。 / p>

如果您需要算术/比较运算符,索引操作类,最小/最大聚合,类型转换等,只要您不弄乱内部二进制文件,也可以从原始类型中复制粘贴这些定义。格式。