34.4. Использование переменных хоста#

34.4. Использование переменных хоста

34.4. Использование переменных хоста

В Раздел 34.3 вы увидели, как можно выполнить SQL-запросы из встроенной программы SQL. Некоторые из этих запросов использовали только фиксированные значения и не предоставляли возможности вставлять пользовательские значения в запросы или обрабатывать значения, возвращаемые запросом. Такие запросы на самом деле не очень полезны в реальных приложениях. В этом разделе подробно объясняется, как можно передавать данные между программой на языке C и встроенными SQL-запросами с помощью простого механизма, называемого переменными хоста. Во встроенной программе SQL мы рассматриваем SQL-запросы как гостей в коде программы на языке C, который является языком хоста. Поэтому переменные программы на языке C называются переменными хоста.

Еще один способ обмена значениями между серверами PostgreSQL и приложениями ECPG - это использование SQL-дескрипторов, описанных в Раздел 34.7.

34.4.1. Обзор

Передача данных между программой на языке C и SQL-запросами особенно проста во встроенном SQL. Вместо того чтобы вставлять данные программой в запрос, что влечет за собой различные сложности, такие как правильное экранирование значения, вы можете просто написать имя переменной на языке C в SQL-запросе, предварительно добавив двоеточие. Например:

EXEC SQL INSERT INTO sometable VALUES (:v1, 'foo', :v2);

Этот оператор относится к двум переменным на языке C с именами v1 и v2, а также использует обычную строковую литерал SQL, чтобы показать, что вы не ограничены использованием только одного типа данных или другого.

Этот стиль вставки переменных C в SQL-оператор работает везде, где ожидается выражение значения в SQL-операторе.

34.4.2. Объявление разделов

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

Этот раздел начинается с:

EXEC SQL BEGIN DECLARE SECTION;

и заканчивается:

EXEC SQL END DECLARE SECTION;

Между этими строками должны быть обычные объявления переменных на языке C, например:

int   x = 4;
char  foo[16], bar[16];

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

EXEC SQL int i = 4;

Вы можете иметь столько секций declare в программе, сколько вам нужно.

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

Определение структуры или объединения также должно быть перечислено внутри раздела DECLARE. В противном случае препроцессор не сможет обработать эти типы, так как он не знает их определения.

34.4.3. Получение результатов запроса

Теперь вы должны иметь возможность передавать данные, сгенерированные вашей программой, в SQL-команду. Но как получить результаты запроса? Для этой цели встроенный SQL предоставляет специальные варианты обычных команд SELECT и FETCH. Эти команды имеют специальное предложение INTO, которая указывает, в каких переменных хоста будут храниться полученные значения. Команда SELECT используется для запроса, который возвращает только одну строку, а команда FETCH используется для запроса, который возвращает несколько строк с использованием курсора.

Вот пример:

/*
 * assume this table:
 * CREATE TABLE test1 (a int, b varchar(50));
 */

EXEC SQL BEGIN DECLARE SECTION;
int v1;
VARCHAR v2;
EXEC SQL END DECLARE SECTION;

 ...

EXEC SQL SELECT a, b INTO :v1, :v2 FROM test;

Таким образом, предложение INTO появляется между списком выбора и предложением FROM. Количество элементов в выборке и списке после INTO (также называемом целевым списком) должно быть равным.

Вот пример использования команды FETCH:

EXEC SQL BEGIN DECLARE SECTION;
int v1;
VARCHAR v2;
EXEC SQL END DECLARE SECTION;

 ...

EXEC SQL DECLARE foo CURSOR FOR SELECT a, b FROM test;

 ...

do
{
    ...
    EXEC SQL FETCH NEXT FROM foo INTO :v1, :v2;
    ...
} while (...);

Здесь предложение INTO появляется после всех обычных предложений.

34.4.4. Сопоставление типов

Когда приложения ECPG обмениваются значениями между сервером PostgreSQL и приложением на языке C, например, при получении результатов запроса с сервера или выполнении SQL-запросов с входными параметрами, значения должны быть преобразованы между типами данных PostgreSQL и типами переменных языка хоста (конкретно, типами данных на языке C). Одним из основных преимуществ ECPG является то, что в большинстве случаев он автоматически заботится об этом.

В этом отношении существует два вида типов данных: некоторые простые типы данных PostgreSQL, такие как integer и text, могут быть прочитаны и записаны приложением напрямую. Другие типы данных PostgreSQL, такие как timestamp и numeric, могут быть доступны только через специальные библиотечные функции; см. Раздел 34.4.4.2.

Таблица 34.1 показывает, какие типы данных PostgreSQL соответствуют каким типам данных C. Когда вы хотите отправить или получить значение определенного типа данных PostgreSQL, вы должны объявить переменную C соответствующего типа данных C в разделе объявления.

Таблица 34.1. Соответствие между типами данных PostgreSQL и типами переменных C

Тип данных PostgreSQLТип переменной хоста
smallintshort
integerint
bigintlong long int
decimaldecimal[a]
numericnumeric[a]
realfloat
double precisiondouble
smallserialshort
serialint
bigseriallong long int
oidunsigned int
character(n), varchar(n), textchar[n+1], VARCHAR[n+1]
namechar[NAMEDATALEN]
timestamptimestamp[a]
intervalinterval[a]
datedate[a]
booleanbool[b]
byteachar *, bytea[n]

[a] Этот тип может быть доступен только через специальные функции библиотеки; см. Раздел 34.4.4.2.

[b] объявлено в ecpglib.h, если не является встроенным


34.4.4.1. Обработка символьных строк

Для работы с типами данных символьных строк SQL, таких как varchar и text, существует два возможных способа объявления переменных-хостов.

Один из способов - использовать char[], массив из char, который является наиболее распространенным способом работы с символьными данными в C.

EXEC SQL BEGIN DECLARE SECTION;
    char str[50];
EXEC SQL END DECLARE SECTION;

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

Другой способ - использовать тип VARCHAR, который является специальным типом, предоставляемым ECPG. Определение массива типа VARCHAR преобразуется в именованную структуру для каждой переменной. Объявление может выглядеть так:

VARCHAR var[180];

преобразуется в:

struct varchar_var { int len; char arr[180]; } var;

Член arr содержит строку, включая завершающий нулевой байт. Таким образом, для сохранения строки в переменной хоста типа VARCHAR, переменная хоста должна быть объявлена с длиной, включающей завершающий нулевой байт. Член len содержит длину строки, хранящейся в arr без завершающего нулевого байта. При использовании переменной хоста в качестве входных данных для запроса, если strlen(arr) и len различаются, используется более короткая из них.

VARCHAR может быть написан в верхнем или нижнем регистре, но не в смешанном регистре.

char и VARCHAR переменные хоста также могут содержать значения других типов SQL, которые будут сохранены в виде строк.

34.4.4.2. Доступ к специальным типам данных

ECPG содержит некоторые специальные типы, которые помогают взаимодействовать с некоторыми специальными типами данных с сервера PostgreSQL. В частности, он реализовал поддержку типов numeric, decimal, date, timestamp и interval. Эти типы данных не могут быть полезно отображены на примитивные типы переменных хоста (такие как int, long long int или char[]), потому что они имеют сложную внутреннюю структуру. Приложения работают с этими типами, объявляя переменные хоста в специальных типах и обращаясь к ним с помощью функций в библиотеке pgtypes. Библиотека pgtypes, подробно описанная в Раздел 34.6, содержит базовые функции для работы с этими типами, так что вам не нужно отправлять запрос на SQL-сервер только для добавления интервала к метке времени, например.

Следующие подразделы описывают эти специальные типы данных. Для получения более подробной информации о функциях библиотеки pgtypes см. Раздел 34.6.

34.4.4.2.1. timestamp, date

Вот шаблон для обработки переменных типа timestamp в хост-приложении ECPG.

Сначала программа должна включать заголовочный файл для типа timestamp:

#include <pgtypes_timestamp.h>

Далее, объявите переменную хоста типа timestamp в разделе объявления:

EXEC SQL BEGIN DECLARE SECTION;
timestamp ts;
EXEC SQL END DECLARE SECTION;

И после чтения значения в переменную хоста, обработайте его с помощью функций библиотеки pgtypes. В следующем примере значение timestamp преобразуется в текстовую (ASCII) форму с помощью функции PGTYPEStimestamp_to_asc():

EXEC SQL SELECT now()::timestamp INTO :ts;

printf("ts = %s\n", PGTYPEStimestamp_to_asc(ts));

Этот пример покажет некоторый результат, подобный следующему:

ts = 2010-06-27 18:03:56.949343

В дополнение к этому, тип DATE может быть обработан таким же образом. Программа должна включать файл pgtypes_date.h, объявлять переменную-хост как тип date и преобразовывать значение DATE в текстовую форму с помощью функции PGTYPESdate_to_asc(). Дополнительные сведения о функциях библиотеки pgtypes см. в разделе Раздел 34.6.

34.4.4.2.2. интервал

Обработка типа interval также аналогична типам timestamp и date. Однако требуется явно выделить память для значения типа interval. Другими словами, память для переменной должна быть выделена в куче, а не в стеке.

Вот пример программы:

#include <stdio.h>
#include <stdlib.h>
#include <pgtypes_interval.h>

int
main(void)
{
EXEC SQL BEGIN DECLARE SECTION;
    interval *in;
EXEC SQL END DECLARE SECTION;

    EXEC SQL CONNECT TO testdb;
    EXEC SQL SELECT pg_catalog.set_config('search_path', '', false); EXEC SQL COMMIT;

    in = PGTYPESinterval_new();
    EXEC SQL SELECT '1 min'::interval INTO :in;
    printf("interval = %s\n", PGTYPESinterval_to_asc(in));
    PGTYPESinterval_free(in);

    EXEC SQL COMMIT;
    EXEC SQL DISCONNECT ALL;
    return 0;
}

34.4.4.2.3. numeric, decimal

Обработка типов numeric и decimal аналогична обработке типа interval: требуется определение указателя, выделение некоторого пространства памяти в куче и доступ к переменной с использованием функций библиотеки pgtypes. Дополнительные сведения о функциях библиотеки pgtypes см. в разделе Раздел 34.6.

Для типа decimal не предусмотрены специальные функции. Приложение должно преобразовать его в переменную типа numeric с использованием функции библиотеки pgtypes для дальнейшей обработки.

Вот пример программы, обрабатывающей переменные типа numeric и decimal.

#include <stdio.h>
#include <stdlib.h>
#include <pgtypes_numeric.h>

EXEC SQL WHENEVER SQLERROR STOP;

int
main(void)
{
EXEC SQL BEGIN DECLARE SECTION;
    numeric *num;
    numeric *num2;
    decimal *dec;
EXEC SQL END DECLARE SECTION;

    EXEC SQL CONNECT TO testdb;
    EXEC SQL SELECT pg_catalog.set_config('search_path', '', false); EXEC SQL COMMIT;

    num = PGTYPESnumeric_new();
    dec = PGTYPESdecimal_new();

    EXEC SQL SELECT 12.345::numeric(4,2), 23.456::decimal(4,2) INTO :num, :dec;

    printf("numeric = %s\n", PGTYPESnumeric_to_asc(num, 0));
    printf("numeric = %s\n", PGTYPESnumeric_to_asc(num, 1));
    printf("numeric = %s\n", PGTYPESnumeric_to_asc(num, 2));

    /* Convert decimal to numeric to show a decimal value. */
    num2 = PGTYPESnumeric_new();
    PGTYPESnumeric_from_decimal(dec, num2);

    printf("decimal = %s\n", PGTYPESnumeric_to_asc(num2, 0));
    printf("decimal = %s\n", PGTYPESnumeric_to_asc(num2, 1));
    printf("decimal = %s\n", PGTYPESnumeric_to_asc(num2, 2));

    PGTYPESnumeric_free(num2);
    PGTYPESdecimal_free(dec);
    PGTYPESnumeric_free(num);

    EXEC SQL COMMIT;
    EXEC SQL DISCONNECT ALL;
    return 0;
}

34.4.4.2.4. байтовый массив

Обработка типа bytea аналогична обработке типа VARCHAR. Определение массива типа bytea преобразуется в именованную структуру для каждой переменной. Объявление может выглядеть так:

bytea var[180];

преобразуется в:

struct bytea_var { int len; char arr[180]; } var;

Член arr содержит данные в бинарном формате. Он также может обрабатывать символ '\0' как часть данных, в отличие от типа VARCHAR. Данные преобразуются из/в шестнадцатеричный формат и отправляются/получаются с помощью ecpglib.

Примечание

bytea переменная может быть использована только тогда, когда bytea_output установлен в hex.

34.4.4.3. Переменные хоста с не примитивными типами данных

Как переменную хоста вы также можете использовать массивы, typedef'ы, структуры и указатели.

34.4.4.3.1. Массивы

Существует два случая использования массивов в качестве переменных-хостов. Первый случай - это способ хранения некоторой текстовой строки в char[] или VARCHAR[], как объясняется в Раздел 34.4.4.1. Второй случай использования - это получение нескольких строк из результата запроса без использования курсора. Без массива для обработки результата запроса, состоящего из нескольких строк, требуется использовать курсор и команду FETCH. Но при использовании массивов в качестве переменных-хостов, несколько строк могут быть получены сразу. Длина массива должна быть определена, чтобы вместить все строки, иначе возможно переполнение буфера.

Следующий пример сканирует системную таблицу pg_database и показывает все OID и имена доступных баз данных:

int
main(void)
{
EXEC SQL BEGIN DECLARE SECTION;
    int dbid[8];
    char dbname[8][16];
    int i;
EXEC SQL END DECLARE SECTION;

    memset(dbname, 0, sizeof(char)* 16 * 8);
    memset(dbid, 0, sizeof(int) * 8);

    EXEC SQL CONNECT TO testdb;
    EXEC SQL SELECT pg_catalog.set_config('search_path', '', false); EXEC SQL COMMIT;

    /* Retrieve multiple rows into arrays at once. */
    EXEC SQL SELECT oid,datname INTO :dbid, :dbname FROM pg_database;

    for (i = 0; i < 8; i++)
        printf("oid=%d, dbname=%s\n", dbid[i], dbname[i]);

    EXEC SQL COMMIT;
    EXEC SQL DISCONNECT ALL;
    return 0;
}

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

oid=1, dbname=template1
oid=11510, dbname=template0
oid=11511, dbname=postgres
oid=313780, dbname=testdb
oid=0, dbname=
oid=0, dbname=
oid=0, dbname=

34.4.4.3.2. Структуры

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

Следующий пример извлекает OID, имена и размеры доступных баз данных из системной таблицы pg_database и использует функцию pg_database_size(). В этом примере структурная переменная dbinfo_t с элементами, имена которых соответствуют каждому столбцу в результате SELECT, используется для извлечения одной строки результата без использования нескольких переменных хоста в операторе FETCH.

EXEC SQL BEGIN DECLARE SECTION;
    typedef struct
    {
       int oid;
       char datname[65];
       long long int size;
    } dbinfo_t;

    dbinfo_t dbval;
EXEC SQL END DECLARE SECTION;

    memset(&dbval, 0, sizeof(dbinfo_t));

    EXEC SQL DECLARE cur1 CURSOR FOR SELECT oid, datname, pg_database_size(oid) AS size FROM pg_database;
    EXEC SQL OPEN cur1;

    /* when end of result set reached, break out of while loop */
    EXEC SQL WHENEVER NOT FOUND DO BREAK;

    while (1)
    {
        /* Fetch multiple columns into one structure. */
        EXEC SQL FETCH FROM cur1 INTO :dbval;

        /* Print members of the structure. */
        printf("oid=%d, datname=%s, size=%lld\n", dbval.oid, dbval.datname, dbval.size);
    }

    EXEC SQL CLOSE cur1;

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

oid=1, datname=template1, size=4324580
oid=11510, datname=template0, size=4243460
oid=11511, datname=postgres, size=4324580
oid=313780, datname=testdb, size=8183012

Структура переменных хоста поглощает столько столбцов, сколько полей в структуре. Дополнительные столбцы могут быть присвоены другим переменным хоста. Например, вышеуказанная программа также может быть переструктурирована следующим образом, с переменной size вне структуры:

EXEC SQL BEGIN DECLARE SECTION;
    typedef struct
    {
       int oid;
       char datname[65];
    } dbinfo_t;

    dbinfo_t dbval;
    long long int size;
EXEC SQL END DECLARE SECTION;

    memset(&dbval, 0, sizeof(dbinfo_t));

    EXEC SQL DECLARE cur1 CURSOR FOR SELECT oid, datname, pg_database_size(oid) AS size FROM pg_database;
    EXEC SQL OPEN cur1;

    /* when end of result set reached, break out of while loop */
    EXEC SQL WHENEVER NOT FOUND DO BREAK;

    while (1)
    {
        /* Fetch multiple columns into one structure. */
        EXEC SQL FETCH FROM cur1 INTO :dbval, :size;

        /* Print members of the structure. */
        printf("oid=%d, datname=%s, size=%lld\n", dbval.oid, dbval.datname, size);
    }

    EXEC SQL CLOSE cur1;

34.4.4.3.3. Typedefs

Используйте ключевое слово typedef для отображения новых типов на уже существующие типы.

EXEC SQL BEGIN DECLARE SECTION;
    typedef char mychartype[40];
    typedef long serial_t;
EXEC SQL END DECLARE SECTION;

Обратите внимание, что вы также можете использовать:

EXEC SQL TYPE serial_t IS long;

Это объявление не должно быть частью секции declare.

34.4.4.3.4. Указатели

Вы можете объявлять указатели на наиболее распространенные типы. Однако обратите внимание, что вы не можете использовать указатели в качестве целевых переменных запросов без автоматического выделения памяти. См. Раздел 34.7 для получения дополнительной информации об автоматическом выделении памяти.

EXEC SQL BEGIN DECLARE SECTION;
    int   *intp;
    char **charp;
EXEC SQL END DECLARE SECTION;

34.4.5. Обработка не примитивных типов данных SQL

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

34.4.5.1. Массивы

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

Если запрос обращается к элементам массива отдельно, то это избегает использования массивов в ECPG. Затем следует использовать переменную хоста с типом, который может быть сопоставлен с типом элемента. Например, если тип столбца - это массив integer, можно использовать переменную хоста типа int. Также, если тип элемента - это varchar или text, можно использовать переменную хоста типа char[] или VARCHAR[].

Вот пример. Предположим, что у нас есть следующая таблица:

CREATE TABLE t3 (
    ii integer[]
);

testdb=> SELECT * FROM t3;
     ii
-------------
 {1,2,3,4,5}
(1 row)

Следующая примерная программа извлекает 4-й элемент массива и сохраняет его в переменную хоста типа int:

EXEC SQL BEGIN DECLARE SECTION;
int ii;
EXEC SQL END DECLARE SECTION;

EXEC SQL DECLARE cur1 CURSOR FOR SELECT ii[4] FROM t3;
EXEC SQL OPEN cur1;

EXEC SQL WHENEVER NOT FOUND DO BREAK;

while (1)
{
    EXEC SQL FETCH FROM cur1 INTO :ii ;
    printf("ii=%d\n", ii);
}

EXEC SQL CLOSE cur1;

Этот пример показывает следующий результат:

ii=4

Для отображения нескольких элементов массива на несколько элементов в массиве типа переменных хоста каждый элемент столбца массива и каждый элемент массива переменных хоста должны быть управляемыми отдельно, например:

EXEC SQL BEGIN DECLARE SECTION;
int ii_a[8];
EXEC SQL END DECLARE SECTION;

EXEC SQL DECLARE cur1 CURSOR FOR SELECT ii[1], ii[2], ii[3], ii[4] FROM t3;
EXEC SQL OPEN cur1;

EXEC SQL WHENEVER NOT FOUND DO BREAK;

while (1)
{
    EXEC SQL FETCH FROM cur1 INTO :ii_a[0], :ii_a[1], :ii_a[2], :ii_a[3];
    ...
}

Обратите внимание, что

EXEC SQL BEGIN DECLARE SECTION;
int ii_a[8];
EXEC SQL END DECLARE SECTION;

EXEC SQL DECLARE cur1 CURSOR FOR SELECT ii FROM t3;
EXEC SQL OPEN cur1;

EXEC SQL WHENEVER NOT FOUND DO BREAK;

while (1)
{
    /* WRONG */
    EXEC SQL FETCH FROM cur1 INTO :ii_a;
    ...
}

в этом случае не сработает правильно, потому что нельзя напрямую сопоставить столбец типа массив с массивной переменной хоста.

Еще одним обходным путем является хранение массивов в их внешнем строковом представлении в переменных хоста типа char[] или VARCHAR[]. Дополнительные сведения об этом представлении см. в разделе Раздел 8.15.2. Обратите внимание, что это означает, что массив не может быть естественно доступен как массив в программе хоста (без дальнейшей обработки, которая разбирает текстовое представление).

34.4.5.2. Составные типы

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

Для следующих примеров предположим следующий тип и таблицу:

CREATE TYPE comp_t AS (intval integer, textval varchar(32));
CREATE TABLE t4 (compval comp_t);
INSERT INTO t4 VALUES ( (256, 'PostgreSQL') );

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

EXEC SQL BEGIN DECLARE SECTION;
int intval;
varchar textval[33];
EXEC SQL END DECLARE SECTION;

/* Put each element of the composite type column in the SELECT list. */
EXEC SQL DECLARE cur1 CURSOR FOR SELECT (compval).intval, (compval).textval FROM t4;
EXEC SQL OPEN cur1;

EXEC SQL WHENEVER NOT FOUND DO BREAK;

while (1)
{
    /* Fetch each element of the composite type column into host variables. */
    EXEC SQL FETCH FROM cur1 INTO :intval, :textval;

    printf("intval=%d, textval=%s\n", intval, textval.arr);
}

EXEC SQL CLOSE cur1;

Для улучшения этого примера, переменные хоста для хранения значений в команде FETCH могут быть собраны в одну структуру. Для получения более подробной информации о переменной хоста в форме структуры, см. Раздел 34.4.4.3.2. Для перехода к структуре пример может быть изменен следующим образом. Две переменные хоста, intval и textval, становятся членами структуры comp_t, и структура указывается в команде FETCH.

EXEC SQL BEGIN DECLARE SECTION;
typedef struct
{
    int intval;
    varchar textval[33];
} comp_t;

comp_t compval;
EXEC SQL END DECLARE SECTION;

/* Put each element of the composite type column in the SELECT list. */
EXEC SQL DECLARE cur1 CURSOR FOR SELECT (compval).intval, (compval).textval FROM t4;
EXEC SQL OPEN cur1;

EXEC SQL WHENEVER NOT FOUND DO BREAK;

while (1)
{
    /* Put all values in the SELECT list into one structure. */
    EXEC SQL FETCH FROM cur1 INTO :compval;

    printf("intval=%d, textval=%s\n", compval.intval, compval.textval.arr);
}

EXEC SQL CLOSE cur1;

Хотя в команде FETCH используется структура, имена атрибутов в предложении SELECT указываются по одному. Это можно улучшить, используя *, чтобы запросить все атрибуты значения составного типа.

...
EXEC SQL DECLARE cur1 CURSOR FOR SELECT (compval).* FROM t4;
EXEC SQL OPEN cur1;

EXEC SQL WHENEVER NOT FOUND DO BREAK;

while (1)
{
    /* Put all values in the SELECT list into one structure. */
    EXEC SQL FETCH FROM cur1 INTO :compval;

    printf("intval=%d, textval=%s\n", compval.intval, compval.textval.arr);
}
...

Таким образом, составные типы могут быть отображены в структуры практически без проблем, даже если ECPG не понимает сам составной тип.

Наконец, также возможно хранить значения составного типа в их внешнем строковом представлении в переменных хоста типа char[] или VARCHAR[]. Однако таким образом не так легко получить доступ к полям значения из программы хоста.

34.4.5.3. Пользовательские базовые типы

Новые пользовательские базовые типы не поддерживаются непосредственно ECPG. Вы можете использовать внешнее строковое представление и переменные хоста типа char[] или VARCHAR[], и это решение действительно подходит и достаточно для многих типов.

Вот пример использования типа данных complex из примера в Раздел 36.12. Внешнее строковое представление этого типа - (%f,%f), которое определено в функциях complex_in() и complex_out() в Раздел 36.12. В следующем примере вставляются значения типа complex (1,1) и (3,3) в столбцы a и b, а затем они выбираются из таблицы.

EXEC SQL BEGIN DECLARE SECTION;
    varchar a[64];
    varchar b[64];
EXEC SQL END DECLARE SECTION;

    EXEC SQL INSERT INTO test_complex VALUES ('(1,1)', '(3,3)');

    EXEC SQL DECLARE cur1 CURSOR FOR SELECT a, b FROM test_complex;
    EXEC SQL OPEN cur1;

    EXEC SQL WHENEVER NOT FOUND DO BREAK;

    while (1)
    {
        EXEC SQL FETCH FROM cur1 INTO :a, :b;
        printf("a=%s, b=%s\n", a.arr, b.arr);
    }

    EXEC SQL CLOSE cur1;

Этот пример показывает следующий результат:

a=(1,1), b=(3,3)

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

Например,

CREATE FUNCTION create_complex(r double, i double) RETURNS complex
LANGUAGE SQL
IMMUTABLE
AS $$ SELECT $1 * complex '(1,0')' + $2 * complex '(0,1)' $$;

После этого определения следующее

EXEC SQL BEGIN DECLARE SECTION;
double a, b, c, d;
EXEC SQL END DECLARE SECTION;

a = 1;
b = 2;
c = 3;
d = 4;

EXEC SQL INSERT INTO test_complex VALUES (create_complex(:a, :b), create_complex(:c, :d));

имеет тот же эффект, что и

EXEC SQL INSERT INTO test_complex VALUES ('(1,2)', '(3,4)');

34.4.6. Индикаторы

Приведенные выше примеры не обрабатывают значения null. Фактически, примеры извлечения вызовут ошибку, если они получат значение null из базы данных. Чтобы иметь возможность передавать значения null в базу данных или извлекать значения null из базы данных, необходимо добавить в каждую переменную хоста вторую спецификацию переменной хоста. Эта вторая хост-переменная называется indicator и содержит флаг, который сообщает является ли данная переменная нулевой, в этом случае значение реальной игнорируется. В этом случае значение реальной переменной хоста игнорируется. Вот пример, который правильно обрабатывает извлечение значений null:

EXEC SQL BEGIN DECLARE SECTION;
VARCHAR val;
int val_ind;
EXEC SQL END DECLARE SECTION:

 ...

EXEC SQL SELECT b INTO :val :val_ind FROM test1;

Индикаторная переменная val_ind будет равна нулю, если значение не является NULL, и она будет отрицательной, если значение является NULL. (См. Раздел 34.16 для включения специфичного для Oracle поведения).

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

Если аргумент -r no_indicator передается препроцессору ecpg, он работает в режиме без индикатора. В режиме без индикатора, если не указана переменная индикатора, для типов символьных строк значения null сигнализируются (при вводе и выводе) как пустая строка, а для целочисленных типов - как наименьшее возможное значение для данного типа (например, INT_MIN для int).