38.8. Обработка ошибок #

В этом разделе описывается, как можно обрабатывать исключительные условия и предупреждения в программе со встраиваемым SQL. Для этого предназначены два средства, которые могут дополнять друг друга.

  • Можно настроить функции-обработчики для обработки предупреждений и ошибок, воспользовавшись командой WHENEVER.
  • Подробную информацию об ошибке или предупреждении можно получить через переменную sqlca.

38.8.1. Установка обработчиков #

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

EXEC SQL WHENEVER условие действие;

Здесь условие может быть следующим:

SQLERROR #

Указанное действие вызывается, когда при выполнении SQL-оператора происходит ошибка.

SQLWARNING #

Указанное действие вызывается, когда при выполнении SQL-оператора выдаётся предупреждение.

NOT FOUND #

Указанное действие вызывается, когда SQL-оператор получает или обрабатывает ноль строк. (Это обстоятельство не считается ошибкой, но бывает полезно отследить его.)

действие может быть следующим:

CONTINUE #

Это фактически означает, что условие игнорируется. Это поведение по умолчанию.

GOTO метка
GO TO метка #

Перейти к указанной метке (используя оператор goto языка C).

SQLPRINT #

Вывести сообщение в устройство стандартного вывода. Это полезно для простых программ или при разработке прототипов. Содержание этого сообщения не настраивается.

STOP #

Вызвать exit(1), что приведёт к завершению программы.

DO BREAK #

Выполнить оператор break языка C. Этот вариант следует использовать только в циклах или операторах switch.

DO CONTINUE #

Выполнить оператор continue языка C. Этот вариант следует использовать только в циклах. Данный оператор передаёт управление в начало цикла.

CALL имя (аргументы)
DO имя (аргументы) #

Вызвать указанные функции C с заданными аргументами. (Эти вызовы имеют смысловые отличия от CALL и DO в обычной грамматике Postgres Pro.)

В стандарте SQL описаны только действия CONTINUE и GOTOGO TO).

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

EXEC SQL WHENEVER SQLWARNING SQLPRINT;
EXEC SQL WHENEVER SQLERROR STOP;

Оператор EXEC SQL WHENEVER является директивой препроцессора SQL, а не оператором языка C. Устанавливаемое им действие при ошибках или предупреждениях применяется ко всем встраиваемым SQL-операторам ниже точки, где устанавливается обработчик, если только это действие не было изменено после первой команды EXEC SQL WHENEVER, и до SQL-оператора, вызвавшего это условие, вне зависимости от хода выполнения программы на C. Поэтому обе следующие программы на C не дадут желаемого эффекта:

/*
 * НЕПРАВИЛЬНО
 */
int main(int argc, char *argv[])
{
    ...
    if (verbose) {
        EXEC SQL WHENEVER SQLWARNING SQLPRINT;
    }
    ...
    EXEC SQL SELECT ...;
    ...
}

/*
 * НЕПРАВИЛЬНО
 */
int main(int argc, char *argv[])
{
    ...
    set_error_handler();
    ...
    EXEC SQL SELECT ...;
    ...
}

static void set_error_handler(void)
{
    EXEC SQL WHENEVER SQLERROR STOP;
}

38.8.2. sqlca #

Для более гибкой обработки ошибок в интерфейсе встраиваемого SQL представлена глобальная переменная с именем sqlca (SQL Communication Area, Область сведений SQL), имеющая следующую структуру:

struct
{
    char sqlcaid[8];
    long sqlabc;
    long sqlcode;
    struct
    {
        int sqlerrml;
        char sqlerrmc[SQLERRMC_LEN];
    } sqlerrm;
    char sqlerrp[8];
    long sqlerrd[6];
    char sqlwarn[8];
    char sqlstate[5];
} sqlca;

(В многопоточной программе каждый поток автоматически получает собственную копию sqlca. Это работает подобно стандартной в C глобальной переменной errno.)

Структура sqlca покрывает и предупреждения, и ошибки. Если в процессе выполнения оператора выдаётся несколько предупреждений или ошибок, sqlca будет содержать сведения только о последнем(ей) из них.

Если последний оператор SQL выполняется без ошибки, sqlca.sqlcode будет содержать 0, а sqlca.sqlstate"00000". Если выдаётся предупреждение или ошибка, в sqlca.sqlcode будет содержаться отрицательное число, а sqlca.sqlstate будет отличаться от "00000". Положительное значение sqlca.sqlcode устанавливается при нейтральном событии, например, когда последний запрос возвращает ноль строк. Поля sqlcode и sqlstate представляют две различные схемы кодов ошибок; подробнее они описаны ниже.

Если последний SQL-оператор был успешным, в sqlca.sqlerrd[1] содержится OID обработанной строки (если это уместно), а в sqlca.sqlerrd[2] количество обработанных или возвращённых строк (если это уместно для команды).

В случае ошибки или предупреждения sqlca.sqlerrm.sqlerrmc будет содержать строку, описывающую ошибку. Поле sqlca.sqlerrm.sqlerrml содержит длину сообщения об ошибке, которое хранится в sqlca.sqlerrm.sqlerrmc (результат функции strlen(), который не очень интересен для программиста C). Заметьте, что некоторые сообщения могут не умещаться в массив sqlerrmc фиксированного размера; они будут обрезаться.

В случае предупреждения, в sqlca.sqlwarn[2] записывается символ W. (Во всех других случаях значение будет отличным от W.) Символ W в sqlca.sqlwarn[1] показывает, что значение было обрезано при сохранении в переменной среды С. W в sqlca.sqlwarn[0] устанавливается, если предупреждение отмечается в каком-либо другом элементе массива.

Поля sqlcaid, sqlabc, sqlerrp и остальные элементы sqlerrd и sqlwarn в настоящее время не содержат полезной информации.

Структура sqlca не определена в стандарте SQL, но реализована в нескольких других СУБД SQL. Принципиально она определяется одинаково, но если вы хотите, чтобы ваши приложения были переносимыми, тщательно изучите различия реализаций.

В следующем примере, демонстрирующем применение WHENEVER в сочетании с sqlca, выводится содержимое sqlca при возникновении ошибки. Это может быть полезно для отладки или в прототипах, пока не реализован более «дружественный пользователю» обработчик ошибок.

EXEC SQL WHENEVER SQLERROR CALL print_sqlca();

void
print_sqlca()
{
    fprintf(stderr, "==== sqlca ====\n");
    fprintf(stderr, "sqlcode: %ld\n", sqlca.sqlcode);
    fprintf(stderr, "sqlerrm.sqlerrml: %d\n", sqlca.sqlerrm.sqlerrml);
    fprintf(stderr, "sqlerrm.sqlerrmc: %s\n", sqlca.sqlerrm.sqlerrmc);
    fprintf(stderr, "sqlerrd: %ld %ld %ld %ld %ld %ld\n", sqlca.sqlerrd[0],sqlca.sqlerrd[1],sqlca.sqlerrd[2],
                                                          sqlca.sqlerrd[3],sqlca.sqlerrd[4],sqlca.sqlerrd[5]);
    fprintf(stderr, "sqlwarn: %d %d %d %d %d %d %d %d\n", sqlca.sqlwarn[0], sqlca.sqlwarn[1], sqlca.sqlwarn[2],
                                                          sqlca.sqlwarn[3], sqlca.sqlwarn[4], sqlca.sqlwarn[5],
                                                          sqlca.sqlwarn[6], sqlca.sqlwarn[7]);
    fprintf(stderr, "sqlstate: %5s\n", sqlca.sqlstate);
    fprintf(stderr, "===============\n");
}

Результат может выглядеть следующим образом (при ошибке, вызванной опечаткой в имени таблицы):

==== sqlca ====
sqlcode: -400
sqlerrm.sqlerrml: 49
sqlerrm.sqlerrmc: relation "pg_databasep" does not exist on line 38
sqlerrd: 0 0 0 0 0 0
sqlwarn: 0 0 0 0 0 0 0 0
sqlstate: 42P01
===============

38.8.3. SQLSTATE и SQLCODE #

Поля sqlca.sqlstate и sqlca.sqlcode отражают две различные схемы, представляющие коды ошибок. Обе схемы пришли из стандарта SQL, но схема SQLCODE была признана устаревшей в редакции SQL-92 и исключена в последующих редакциях. Поэтому в новых приложениях настоятельно рекомендуется использовать SQLSTATE.

SQLSTATE задаётся в массиве из пяти символов. Эти пять символов содержат цифры или буквы в верхнем регистре, представляющие коды различных условий ошибок и предупреждений. SQLSTATE определяется по иерархической схеме: первые два символа обозначают общий класс условия, а следующие три — подкласс общего условия. Успешное состояние обозначается кодом 00000. По большей части коды SQLSTATE определены в стандарте SQL. Сервер Postgres Pro поддерживает коды ошибок SQLSTATE естественным образом; поэтому используя во всех приложениях именно эту схему кодов ошибок, можно добиться высокой степени согласованности. За дальнейшими сведениями обратитесь к Приложению A.

SQLCODE — устаревшая схема, в которой коды ошибок представлены просто целыми числами. Значение 0 обозначает успешное завершение, положительное значение — успешное завершение с дополнительной информацией, а отрицательное говорит об ошибке. В стандарте SQL определено только положительное значение +100, показывающее, что последняя команда вернула или затронула ноль строк, но отрицательные значения не определены. Таким образом, с этой схемой нельзя рассчитывать на переносимость и она не имеет иерархической структуры. Исторически сложилось, что процессор встраиваемого SQL для Postgres Pro назначает некоторые определённые значения SQLCODE для собственного использования; они перечислены ниже с числовыми значениями и символьными именами. Помните, что эти коды несовместимы с другими реализациями SQL. Поэтому для упрощения перевода приложений на схему SQLSTATE вместе с этими кодами перечисляются соответствующие значения SQLSTATE. Однако однозначного соответствия один-к-одному или один-ко-многим между этими двумя схемами не существует (на самом деле это соответствие многие-ко-многим), поэтому следует свериться со списком SQLSTATE в Приложении A в каждом случае.

SQLCODE может принимать следующие значения:

0 (ECPG_NO_ERROR) #

Показывает, что ошибки нет. (SQLSTATE 00000)

100 (ECPG_NOT_FOUND) #

Это нейтральное условие, показывающее, что последняя команда вернула или обработала ноль строк, либо курсор достиг конца. (SQLSTATE 02000)

Выбирая данные из курсора в цикле, можно проверять этот код, чтобы понять, когда нужно прервать цикл, следующим образом:

while (1)
{
    EXEC SQL FETCH ... ;
    if (sqlca.sqlcode == ECPG_NOT_FOUND)
        break;
}

Но WHENEVER NOT FOUND DO BREAK внутри по сути делает это же, поэтому такое явное условие обычно ничем не лучше.

-12 (ECPG_OUT_OF_MEMORY) #

Указывает, что закончилась виртуальная память. Числовое значение определено как -ENOMEM. (SQLSTATE YE001)

-200 (ECPG_UNSUPPORTED) #

Указывает, что препроцессор сгенерировал код, который не понимает библиотека. Возможно, вы используете несовместимые версии препроцессора и библиотеки. (SQLSTATE YE002)

-201 (ECPG_TOO_MANY_ARGUMENTS) #

Это означает, что в команде было указано больше переменных среды, чем она ожидает. (SQLSTATE 07001 или 07002)

-202 (ECPG_TOO_FEW_ARGUMENTS) #

Это означает, что в команде было указано меньше переменных среды, чем она ожидает. (SQLSTATE 07001 или 07002)

-203 (ECPG_TOO_MANY_MATCHES) #

Это означает, что запрос вернул несколько строк, но оператор был подготовлен только для одной строки результата (например, потому что переданные переменные — не массивы). (SQLSTATE 21000)

-204 (ECPG_INT_FORMAT) #

Переменная среды C типа int и данные в базе имеют разные типы, и в этих данных содержится значение, которое нельзя преобразовать в int. Для этого преобразования библиотека использует функцию strtol(). (SQLSTATE 42804)

-205 (ECPG_UINT_FORMAT) #

Переменная среды C типа unsigned int и данные в базе имеют разные типы, и в этих данных содержится значение, которое нельзя преобразовать в unsigned int. Для этого преобразования библиотека использует функцию strtoul(). (SQLSTATE 42804)

-206 (ECPG_FLOAT_FORMAT) #

Переменная среды C типа float и данные в базе имеют разные типы, и в этих данных содержится значение, которое нельзя преобразовать во float. Для этого преобразования библиотека использует функцию strtod(). (SQLSTATE 42804)

-207 (ECPG_NUMERIC_FORMAT) #

Переменная среды C типа numeric и данные в базе имеют разные типы, и в этих данных содержится значение, которое нельзя преобразовать в numeric. (SQLSTATE 42804)

-208 (ECPG_INTERVAL_FORMAT) #

Переменная среды C типа interval и данные в базе имеют разные типы, и в этих данных содержится значение, которое нельзя преобразовать в interval. (SQLSTATE 42804)

-209 (ECPG_DATE_FORMAT) #

Переменная среды C типа date и данные в базе имеют разные типы, и в этих данных содержится значение, которое нельзя преобразовать в date. (SQLSTATE 42804)

-210 (ECPG_TIMESTAMP_FORMAT) #

Переменная среды C типа timestamp и данные в базе имеют разные типы, и в этих данных содержится значение, которое нельзя преобразовать в timestamp. (SQLSTATE 42804)

-211 (ECPG_CONVERT_BOOL) #

Это означает, что переменная среды C имеет тип bool, а значение в базе данных отличается от 't' или 'f'. (SQLSTATE 42804)

-212 (ECPG_EMPTY) #

Серверу Postgres Pro был передан пустой оператор. (Этого обычно не должно происходить в программе со встраиваемым SQL, так что это может указывать на внутреннюю ошибку.) (SQLSTATE YE002)

-213 (ECPG_MISSING_INDICATOR) #

Возвращено значение NULL, но переменная-индикатор NULL не задана. (SQLSTATE 22002)

-214 (ECPG_NO_ARRAY) #

Там, где требуется массив, была передана обычная переменная. (SQLSTATE 42804)

-215 (ECPG_DATA_NOT_ARRAY) #

База данных возвратила обычную переменную там, где требуется значение-массив. (SQLSTATE 42804)

-216 (ECPG_ARRAY_INSERT) #

Не удалось вставить значение в массив. (SQLSTATE 42804)

-220 (ECPG_NO_CONN) #

Программа попыталась использовать несуществующее подключение. (SQLSTATE 08003)

-221 (ECPG_NOT_CONN) #

Программа попыталась использовать подключение, которое существует, но не было открыто. (Это внутренняя ошибка.) (SQLSTATE YE002)

-230 (ECPG_INVALID_STMT) #

Оператор, который вы пытаетесь выполнить, не был подготовлен. (SQLSTATE 26000)

-239 (ECPG_INFORMIX_DUPLICATE_KEY) #

Ошибка повторяющегося ключа, нарушение ограничения уникальности (режим совместимости с Informix). (SQLSTATE 23505)

-240 (ECPG_UNKNOWN_DESCRIPTOR) #

Указанный дескриптор не найден. Оператор, который вы пытаетесь использовать, не был подготовлен. (SQLSTATE 33000)

-241 (ECPG_INVALID_DESCRIPTOR_INDEX) #

Указанный индекс дескриптора вне диапазона. (SQLSTATE 07009)

-242 (ECPG_UNKNOWN_DESCRIPTOR_ITEM) #

Запрошен неверный элемент дескриптора. (Это внутренняя ошибка.) (SQLSTATE YE002)

-243 (ECPG_VAR_NOT_NUMERIC) #

При выполнении динамического оператора база данных возвратила числовое значение, тогда как переменная среды C — не числовая. (SQLSTATE 07006)

-244 (ECPG_VAR_NOT_CHAR) #

При выполнении динамического оператора база данных возвратила не числовое значение, тогда как переменная среды C — числовая. (SQLSTATE 07006)

-284 (ECPG_INFORMIX_SUBSELECT_NOT_ONE) #

Результат подзапроса представлен не одной строкой (режим совместимости с Informix). (SQLSTATE 21000)

-400 (ECPG_PGSQL) #

Ошибка произошла на стороне сервера Postgres Pro. В тексте ошибки содержится сообщение об ошибке от сервера Postgres Pro.

-401 (ECPG_TRANS) #

Сервер Postgres Pro сообщает, что клиент не может запускать, фиксировать или отменять транзакцию. (SQLSTATE 08007)

-402 (ECPG_CONNECT) #

Попытка подключения к базе данных была неудачной. (SQLSTATE 08001)

-403 (ECPG_DUPLICATE_KEY) #

Ошибка повторяющегося ключа, нарушение ограничения уникальности. (SQLSTATE 23505)

-404 (ECPG_SUBSELECT_NOT_ONE) #

Результат подзапроса представлен не одной строкой. (SQLSTATE 21000)

-602 (ECPG_WARNING_UNKNOWN_PORTAL) #

Указано неверное имя курсора. (SQLSTATE 34000)

-603 (ECPG_WARNING_IN_TRANSACTION) #

Транзакция в процессе выполнения. (SQLSTATE 25001)

-604 (ECPG_WARNING_NO_TRANSACTION) #

Нет активной (выполняющейся) транзакции. (SQLSTATE 25P01)

-605 (ECPG_WARNING_PORTAL_EXISTS) #

Было указано имя существующего курсора. (SQLSTATE 42P03)