Я создал пользовательский тип 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
сохраняется как указатель на значение int32
212
.Используя 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
предупреждений, я смог увидеть, что прямо перед оператором возврата result
указывает на правильное значение: 1010
.Это также означает, что сбой происходит в раскрытии макроса возврата или в каком-то скрытом коде.
Так что я действительно понятия не имею, что происходит.Я делаю palloc
, поэтому перезапись памяти не должна быть разрешена, и я не могу придумать причину того, что значения, содержащие 101
, всегда будут иметь проблемы - и различные проблемы в зависимости от единиц измерения.int32
должен быть способен хранить небольшие значения, которые я тестирую, так что это не так.ИДК, если это так, как это должно быть реализовано, но я проверил, и указатель, передаваемый на выход, НЕ совпадает с адресом указателя result
для любого из этих значений, поэтому я предполагаю, что он выполняет какие-тоmemcpy
неверно скрыт, но затем подумайте, как можно ожидать, что кто-то определит пользовательский базовый тип данных.