33.8. Обработка ошибок

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

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

  • Подробную информацию об ошибке или предупреждении можно получить через переменную sqlca.

33.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.

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

Вызвать указанные функции C с заданными аргументами.

В стандарте 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;
}

33.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, sqlcabc, 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
===============

33.8.3. SQLSTATE и SQLCODE

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

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

SQLCODE — устаревшая схема, в которой коды ошибок представлены просто целыми числами. Значение 0 обозначает успешное завершение, положительное значение — успешное завершение с дополнительной информацией, а отрицательное говорит об ошибке. В стандарте SQL определено только положительное значение +100, показывающее, что последняя команда вернула или затронула ноль строк, но отрицательные значения не определены. Таким образом, с этой схемой нельзя рассчитывать на переносимость и она не имеет иерархической структуры. Исторически сложилось, что процессор встраиваемого SQL для PostgreSQL назначает некоторые определённые значения 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)

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

-205 (ECPG_UINT_FORMAT)

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

-206 (ECPG_FLOAT_FORMAT)

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

-207 (ECPG_NUMERIC_FORMAT)

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

-208 (ECPG_INTERVAL_FORMAT)

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

-209 (ECPG_DATE_FORMAT)

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

-210 (ECPG_TIMESTAMP_FORMAT)

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

-211 (ECPG_CONVERT_BOOL)

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

-212 (ECPG_EMPTY)

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

-213 (ECPG_MISSING_INDICATOR)

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

-214 (ECPG_NO_ARRAY)

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

-215 (ECPG_DATA_NOT_ARRAY)

База данных возвратила обычную переменную там, где требуется значение-массив. (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)

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

-244 (ECPG_VAR_NOT_CHAR)

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

-284 (ECPG_INFORMIX_SUBSELECT_NOT_ONE)

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

-400 (ECPG_PGSQL)

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

-401 (ECPG_TRANS)

Сервер PostgreSQL сообщает, что клиент не может запускать, фиксировать или отменять транзакцию. (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)