我创建了一个自定义类型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, "%", 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
被存储为指向int32
值212
的指针。使用ereport
警告,我能够确定在输入函数的返回之前,result
指向正确的值:101
。
但现在真正奇怪的部分;最后INSERT
崩溃客户端。在解析该 gp 值时,它会打印错误:
psql:./gptest.sql:15: ERROR: compressed data is corrupted
LINE 1: INSERT INTO test VALUES ('101sp');
^
这个总是值101sp
发生,不管表状态或任何其他值在它旁边插入。使用ereport
警告,我能够在返回语句之前看到,result
指向正确的值:1010
。这也意味着崩溃发生在返回宏扩展或一些底层代码中。
所以我真的不知道发生了什么。我正在做palloc
所以覆盖内存不应该被允许,并且我想不出任何原因包含101
的值总是有问题-以及不同的问题取决于单位。int32
应该能够存储我正在测试的小值,所以它不是这样。Idk 如果这是如何实现的palloc
CREATE TYPE
需要大量可选参数,其中一些与数据的物理布局有关,这些参数需要与 I / O 函数期望 / 返回的结构一致。
文档似乎没有提到这些参数的默认值,但是提到“压缩数据”的错误表明您的值是TOASTed,即INTERNALLENGTH
默认为VARIABLE
。这样的类型可能会从varlena
头开始,描述值的总长度,这肯定不是您稍后返回的所有类型的 segults,
如果您的目标是创建一个内部表示为简单整数(固定长度,按值传递等)的类型,则可以复制内置类型的特征:
CREATE TYPE gp (input=gp_input, output=gp_output, like=integer);
然后,您应该能够取消palloc()
和指针,使用PG_GETARG_INT32(0)
获取您的参数,并返回PG_RETURN_INT32(amt)
。
如果你想要一个内置类型的所有行为,但使用自定义显示格式,它比你想象的要容易得多。
numeric
之类的内部 C 例程与您自己编写以实现此类类型的例程相同。因此,您可以创建自己的内置类型版本,只需复制粘贴其 SQL 级别的定义,然后将函数指向现有的 C 处理程序即可完成所有实际工作:
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
);
CREATE TABLE t (x gp(10,2), y gp);
INSERT INTO t VALUES ('123.45', '2387456987623498765324.2837654987364987269837456981');
SELECT * FROM t;
x | y
--------+-----------------------------------------------------
123.45 | 2387456987623498765324.2837654987364987269837456981
从那里,您可以用自己的 C 函数替换输入 / 输出处理程序,从internal functions复制粘贴代码作为起点。在您的情况下,最简单的方法可能是在函数开始时将 DnD 货币字符串转换为简单的十进制字符串,并让其余代码担心将其转换为Numeric
的混乱细节。
如果你想要算术 / 比较运算符,索引 opcl,最小 / 最大聚合,类型转换等,你可以从原始类型复制粘贴这些定义,只要你不弄乱内部二进制格式。
本站系公益性非盈利分享网址,本文来自用户投稿,不代表码文网立场,如若转载,请注明出处
评论列表(52条)