Postgresql поврежденные данные в функции ввода пользовательского базового типа данных - PullRequest
0 голосов
/ 13 февраля 2019

Я создал пользовательский тип 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 неверно скрыт, но затем подумайте, как можно ожидать, что кто-то определит пользовательский базовый тип данных.

1 Ответ

0 голосов
/ 13 февраля 2019

CREATE TYPE принимает тонну необязательных параметров, некоторые из которых относятся к физическому расположению ваших данных, и они должны согласовываться со структурами, которые ожидают / возвращают ваши функции ввода-вывода.

Документы, по-видимому, не упоминают значения по умолчанию для этих параметров, но ошибка с упоминанием "сжатых данных" предполагает, что ваши значения TOASTed , то есть INTERNALLENGTH по умолчанию VARIABLE.Ожидается, что такие типы будут начинаться с заголовка varlena, описывающего общую длину значения, которое, безусловно, не то, что вы возвращаете (хотя Postgres все равно будет интерпретировать его как таковой, что приведет ко всем видам странного поведения, не говоря уже осохранение огромных массивов случайных байтов в вашей таблице и, возможно, раннее или позднее сегментирование ...).

Если ваша цель - создать тип, который внутренне представляется в виде простого целого числа (фиксированной длины, проходапо значению и т. д.), вы можете просто скопировать характеристики встроенного типа:

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

После этого вы сможете покончить с palloc() и указателями, получить ваш аргументс PG_GETARG_INT32(0) и просто верните PG_RETURN_INT32(amt).


Если вам нужно все поведение встроенного типа, но с настраиваемым форматом отображения, это намного проще, чем вы ожидаете.

Внутренние подпрограммы C для чего-то вроде numeric идентичны тем, которые вы написали бы для реализации такого типа самостоятельно.В результате вы можете создать свою собственную версию такого встроенного типа, просто скопировав его определения уровня 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, скопировав код из внутренних функций в качестве отправной точки.Самый простой подход в вашем случае - это, вероятно, преобразовать строку валюты DnD в простую десятичную строку в начале функции и позволить остальной части кода беспокоиться о беспорядочных деталях преобразования ее в Numeric.

Если вам нужны операторы арифметики / сравнения, классы операций индекса, агрегаты min / max, типы типов и т. Д., Вы также можете скопировать и вставить эти определения из исходного типа, если только вы не связываетесь с внутренним двоичным файлом.формат.

...