×
Мы обрабатываем cookies, чтобы сделать наш сайт удобнее и персонализированнее для вас. Подробнее: политика использования «cookies» и «политики конфиденциальности».

Для самостоятельной настройки ознакомьтесь с инструкцией

Дополнительные настройки cookies в браузерах

Файлы cookie автоматически загружаются в ваш браузер при посещении веб-сайта. У вас есть возможность управлять этими файлами. Если Вы не согласны с использованием файлов cookies, запретите их сохранение на своём устройстве, удалите уже имеющиеся файлы cookies через настройки браузера или прекратите использование сайта.

При отключении обработки cookie наш сайт продолжит функционировать, однако будут использоваться исключительно необходимые технические файлы, без которых работа ресурса невозможна.

Инструкция по отключению cookies
Принять
Настроить
Отклонить

ДОКУМЕНТАЦИЯ

Выберите версию, форк и язык для СУБД Digital Q.DataBase, чтобы прочитать или скачать всю документацию.
Техподдержка
Документация
Диасофт
Авторские права © 2016–2025 ООО "Диасофт Экосистема"
Скачать всю документацию:

5.1.13. Пользовательские типы данных

5.1.13.1. Особенности TOAST

Как описано в разделе Раздел 5.1.2, Digital Q.DataBase может быть расширен для поддержки новых типов данных. Этот раздел описывает, как определять новые базовые типы, которые являются типами данных, определёнными ниже уровня языка SQL. Создание нового базового типа требует реализации функций для работы с типом на низкоуровневом языке, обычно C.

Примеры в этом разделе можно найти в complex.sql и complex.c в каталоге src/tutorial дистрибутива исходного кода. См. файл README в этом каталоге для инструкций о запуске примеров.

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

Предположим, мы хотим определить тип complex, который представляет комплексные числа. Естественным способом представления комплексного числа в памяти будет следующая структура C:

typedef struct Complex {
    double      x;
    double      y;
} Complex;

Нам нужно будет сделать этот тип передаваемым по ссылке, так как он слишком велик, чтобы поместиться в одно значение Datum.

В качестве внешнего строкового представления типа мы выбираем строку вида (x,y).

Входная и выходная функции обычно не сложны в написании, особенно выходная. Но при определении внешнего строкового представления типа помните, что вы должны в конечном итоге написать полный и надёжный парсер для этого представления в качестве вашей входной функции. Например:

PG_FUNCTION_INFO_V1(complex_in);

Datum
complex_in(PG_FUNCTION_ARGS)
{
    char       *str = PG_GETARG_CSTRING(0);
    double      x,
                y;
    Complex    *result;

    if (sscanf(str, " ( %lf , %lf )", &x, &y) != 2)
        ereport(ERROR,
                (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                 errmsg("invalid input syntax for type %s: \"%s\"",
                        "complex", str)));

    result = (Complex *) palloc(sizeof(Complex));
    result->x = x;
    result->y = y;
    PG_RETURN_POINTER(result);
}

Выходная функция может быть просто:

PG_FUNCTION_INFO_V1(complex_out);

Datum
complex_out(PG_FUNCTION_ARGS)
{
    Complex    *complex = (Complex *) PG_GETARG_POINTER(0);
    char       *result;

    result = psprintf("(%g,%g)", complex->x, complex->y);
    PG_RETURN_CSTRING(result);
}

Вы должны обеспечить, чтобы входная и выходная функции были обратными друг другу. Если вы этого не сделаете, у вас возникнут серьёзные проблемы, когда вам понадобится выгрузить ваши данные в файл и затем прочитать их обратно. Это особенно распространённая проблема, когда участвуют числа с плавающей точкой.

Опционально, определяемый пользователем тип может предоставлять двоичные входные и выходные процедуры. Двоичный ввод/вывод обычно быстрее, но менее переносим, чем текстовый ввод/вывод. Как и с текстовым вводом/выводом, вам решать, как именно определить внешнее двоичное представление. Большинство встроенных типов данных пытаются предоставить машинно-независимое двоичное представление. Для complex мы будем использовать преобразователи двоичного ввода/вывода для типа float8:

PG_FUNCTION_INFO_V1(complex_recv);

Datum
complex_recv(PG_FUNCTION_ARGS)
{
    StringInfo  buf = (StringInfo) PG_GETARG_POINTER(0);
    Complex    *result;

    result = (Complex *) palloc(sizeof(Complex));
    result->x = pq_getmsgfloat8(buf);
    result->y = pq_getmsgfloat8(buf);
    PG_RETURN_POINTER(result);
}

PG_FUNCTION_INFO_V1(complex_send);

Datum
complex_send(PG_FUNCTION_ARGS)
{
    Complex    *complex = (Complex *) PG_GETARG_POINTER(0);
    StringInfoData buf;

    pq_begintypsend(&buf);
    pq_sendfloat8(&buf, complex->x);
    pq_sendfloat8(&buf, complex->y);
    PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
}

Как только мы написали функции ввода/вывода и скомпилировали их в общую библиотеку, мы можем определить тип complex в SQL. Сначала мы объявляем его как тип-оболочку:

CREATE TYPE complex;

Это служит заполнителем, который позволяет нам ссылаться на тип во время определения его функций ввода/вывода. Теперь мы можем определить функции ввода/вывода:

CREATE FUNCTION complex_in(cstring)
    RETURNS complex
    AS 'filename'
    LANGUAGE C IMMUTABLE STRICT;

CREATE FUNCTION complex_out(complex)
    RETURNS cstring
    AS 'filename'
    LANGUAGE C IMMUTABLE STRICT;

CREATE FUNCTION complex_recv(internal)
   RETURNS complex
   AS 'filename'
   LANGUAGE C IMMUTABLE STRICT;

CREATE FUNCTION complex_send(complex)
   RETURNS bytea
   AS 'filename'
   LANGUAGE C IMMUTABLE STRICT;

Наконец, мы можем предоставить полное определение типа данных:

CREATE TYPE complex (
   internallength = 16,
   input = complex_in,
   output = complex_out,
   receive = complex_recv,
   send = complex_send,
   alignment = double
);

Когда вы определяете новый базовый тип, Digital Q.DataBase автоматически предоставляет поддержку массивов этого типа. Тип массива обычно имеет то же имя, что и базовый тип, с добавленным символом подчёркивания (_) в начале.

Как только тип данных существует, мы можем объявить дополнительные функции для предоставления полезных операций над типом данных. Затем операторы могут быть определены поверх функций, и, если необходимо, могут быть созданы классы операторов для поддержки индексации типа данных. Эти дополнительные слои обсуждаются в следующих разделах.

Если внутреннее представление типа данных имеет переменную длину, то внутреннее представление должно следовать стандартной схеме для данных переменной длины: первые четыре байта должны быть полем char[4], которое никогда не доступно напрямую (обычно называется vl_len_). Вы должны использовать макрос SET_VARSIZE() для сохранения общего размера данного (включая само поле длины) в этом поле и VARSIZE() для его извлечения. (Эти макросы существуют, потому что поле длины может быть закодировано в зависимости от платформы.)

Для дальнейших подробностей см. описание команды CREATE TYPE.

5.1.13.1. Особенности TOAST #

Если значения вашего типа данных различаются по размеру (во внутренней форме), то обычно желательно сделать тип данных поддерживающим TOAST (см. Раздел 7.15.2). Вы должны сделать это, даже если значения всегда слишком малы для сжатия или внешнего хранения, потому что TOAST может экономить место и на малых данных, уменьшая накладные расходы заголовка.

Для поддержки хранения TOAST, функции C, работающие с типом данных, всегда должны быть осторожны, чтобы распаковать любые поджаренные (toasted) значения, которые им переданы, используя PG_DETOAST_DATUM. (Эта деталь обычно скрыта определением специфичных для типа макросов GETARG_DATATYPE_P.) Затем, при запуске команды CREATE TYPE, укажите внутреннюю длину как variable и выберите подходящую опцию хранения, отличную от plain.

Если выравнивание данных неважно (либо только для конкретной функции, либо потому что тип данных в любом случае указывает выравнивание по байту), то возможно избежать некоторых накладных расходов от PG_DETOAST_DATUM. Вы можете использовать PG_DETOAST_DATUM_PACKED вместо этого (обычно скрыто определением макроса GETARG_DATATYPE_PP) и использовать макросы VARSIZE_ANY_EXHDR и VARDATA_ANY для доступа к потенциально упакованному данному. Опять же, данные, возвращаемые этими макросами, не выровнены, даже если определение типа данных указывает выравнивание. Если выравнивание важно, вы должны использовать обычный интерфейс PG_DETOAST_DATUM.

Примечание

В старом коде часто объявляется vl_len_ как поле int32 вместо char[4]. Это нормально, пока определение структуры имеет другие поля, которые имеют как минимум выравнивание int32. Но опасно использовать такое определение структуры при работе с потенциально невыровненным данным; компилятор может воспринять это как разрешение предполагать, что данное фактически выровнено, что приводит к дампам ядра на архитектурах, строгих к выравниванию.

Другая возможность, которую предоставляет поддержка TOAST, — это возможность иметь расширенное представление данных в памяти, которое более удобно для работы, чем формат, который хранится на диске. Обычный или «плоский» формат хранения varlena в конечном счёте является просто набором байтов; он не может, например, содержать указатели, так как может быть скопирован в другие места памяти. Для сложных типов данных плоский формат может быть довольно дорогим для работы, поэтому Digital Q.DataBase предоставляет способ «расширить» плоский формат в представление, более подходящее для вычислений, и затем передавать этот формат в памяти между функциями типа данных.

Чтобы использовать расширенное хранение, тип данных должен определить расширенный формат, который следует правилам, данным в src/include/utils/expandeddatum.h, и предоставить функции для «расширения» плоского значения varlena в расширенный формат и «сжатия» расширенного формата обратно в обычное представление varlena. Затем обеспечьте, чтобы все функции C для типа данных могли принимать любое представление, возможно, конвертируя одно в другое сразу после получения. Это не требует исправления всех существующих функций для типа данных сразу, потому что стандартный макрос PG_DETOAST_DATUM определён для конвертации расширенных входных данных в обычный плоский формат. Поэтому существующие функции, которые работают с плоским форматом varlena, будут продолжать работать, хотя и слегка неэффективно, с расширенными входными данными; их не нужно конвертировать, пока не станет важна лучшая производительность.

Функции C, которые знают, как работать с расширенным представлением, обычно попадают в две категории: те, которые могут обрабатывать только расширенный формат, и те, которые могут обрабатывать либо расширенные, либо плоские входные данные varlena. Первые легче написать, но могут быть менее эффективны в целом, потому что преобразование плоского ввода в расширенную форму для использования одной функцией может стоить больше, чем экономится работой в расширенном формате. Когда нужно обрабатывать только расширенный формат, преобразование плоских входных данных в расширенную форму можно скрыть внутри макроса получения аргумента, так что функция выглядит не более сложной, чем работающая с традиционным входным значением varlena. Чтобы обрабатывать оба типа входных данных, напишите функцию получения аргумента, которая будет распаковывать внешние, с коротким заголовком и сжатые входные данные varlena, но не расширенные входные данные. Такая функция может быть определена как возвращающая указатель на объединение плоского формата varlena и расширенного формата. Можно использовать макрос VARATT_IS_EXPANDED_HEADER() для определения, какой формат был получен фактически.

Инфраструктура TOAST не только позволяет различать обычные значения varlena и расширенные значения, но также различает указатели «для чтения-записи» и «только для чтения» на расширенные значения. Функции C, которым нужно только исследовать расширенное значение или будут изменять его только безопасными и семантически невидимыми способами, не должны заботиться о том, какой тип указателя они получают. Функции C, которые производят модифицированную версию входного значения, имеют право изменять расширенное входное значение на месте, если они получают указатель для чтения-записи, но не должны изменять входные данные, если они получают указатель только для чтения; в этом случае они должны сначала скопировать значение, создав новое значение для изменения. Функция C, которая сконструировала новое расширенное значение, должна всегда возвращать указатель для чтения-записи на него. Также, функция C, которая модифицирует расширенное значение для чтения-записи на месте, должна позаботиться оставить значение в согласованном состоянии, если она завершится неудачно на полпути.

Для примеров работы с расширенными значениями см. стандартную инфраструктуру массивов, в частности src/backend/utils/adt/array_expanded.c.

Наверх
свяжитесь
с нами
контакты
Для прямой связи с нами вы можете использовать контакты ниже, либо оставить заявку через форму обратной связи, и мы обязательно свяжемся с вами

*поля обязательные к заполнению