33.4. Использование переменных хоста#
33.4. Использование переменных хоста #
В Раздел 33.3 вы увидели, как можно выполнить SQL-запросы из встроенной программы SQL. Некоторые из этих запросов использовали только фиксированные значения и не предоставляли возможности вставлять пользовательские значения в запросы или обрабатывать значения, возвращаемые запросом. Такие запросы на самом деле не очень полезны в реальных приложениях. В этом разделе подробно объясняется, как можно передавать данные между программой на языке C и встроенными SQL-запросами с помощью простого механизма, называемого переменными хоста. Во встроенной программе SQL мы рассматриваем SQL-запросы как гостей в коде программы на языке C, который является языком хоста. Поэтому переменные программы на языке C называются переменными хоста.
Еще один способ обмена значениями между серверами PostgreSQL и приложениями ECPG - это использование SQL-дескрипторов, описанных в Раздел 33.7.
33.4.1. Обзор #
Передача данных между программой на языке C и SQL-запросами особенно проста во встроенном SQL. Вместо того чтобы вставлять данные программой в запрос, что влечет за собой различные сложности, такие как правильное экранирование значения, вы можете просто написать имя переменной на языке C в SQL-запросе, предварительно добавив двоеточие. Например:
EXEC SQL INSERT INTO sometable VALUES (:v1, 'foo', :v2);
Этот оператор относится к двум переменным на языке C с именами v1 и v2, а также использует обычную строковую литерал SQL, чтобы показать, что вы не ограничены использованием только одного типа данных или другого.
Этот стиль вставки переменных C в SQL-оператор работает везде, где ожидается выражение значения в SQL-операторе.
33.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. В противном случае препроцессор не сможет обработать эти типы, так как он не знает их определения.
33.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 появляется после всех обычных предложений.
33.4.4. Сопоставление типов #
Когда приложения ECPG обмениваются значениями между сервером PostgreSQL и приложением на языке C, например, при получении результатов запроса с сервера или выполнении SQL-запросов с входными параметрами, значения должны быть преобразованы между типами данных PostgreSQL и типами переменных языка хоста (конкретно, типами данных на языке C). Одним из основных преимуществ ECPG является то, что в большинстве случаев он автоматически заботится об этом.
В этом отношении существует два вида типов данных: некоторые простые типы данных PostgreSQL, такие как integer и text, могут быть прочитаны и записаны приложением напрямую. Другие типы данных PostgreSQL, такие как timestamp и numeric, могут быть доступны только через специальные библиотечные функции; см. Раздел 33.4.4.2.
Таблица 33.1 показывает, какие типы данных PostgreSQL соответствуют каким типам данных C. Когда нужно отправить или получить значение определенного типа данных PostgreSQL, вы должны объявить переменную C соответствующего типа данных C в разделе объявления.
Таблица 33.1. Соответствие между типами данных PostgreSQL и типами переменных C
| Тип данных PostgreSQL | Тип переменной хоста |
|---|---|
smallint | short |
integer | int |
bigint | long long int |
decimal | decimal[a] |
numeric | numeric[a] |
real | float |
double precision | double |
smallserial | short |
serial | int |
bigserial | long long int |
oid | unsigned int |
character(, varchar(, text | char[, VARCHAR[ |
name | char[NAMEDATALEN] |
timestamp | timestamp[a] |
interval | interval[a] |
date | date[a] |
boolean | bool[b] |
bytea | char *, bytea[ |
[a] Этот тип может быть доступен только через специальные функции библиотеки; см. Раздел 33.4.4.2. [b]
объявлено в | |
33.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, которые будут сохранены в виде строк.
33.4.4.2. Доступ к специальным типам данных #
ECPG содержит некоторые специальные типы, которые помогают взаимодействовать с некоторыми специальными типами данных с сервера PostgreSQL. В частности, он реализовал поддержку типов numeric, decimal, date, timestamp и interval. Эти типы данных не могут быть полезно отображены на примитивные типы переменных хоста (такие как int, long long int или char[]), потому что они имеют сложную внутреннюю структуру. Приложения работают с этими типами, объявляя переменные хоста в специальных типах и обращаясь к ним с помощью функций в библиотеке pgtypes. Библиотека pgtypes, подробно описанная в Раздел 33.6, содержит базовые функции для работы с этими типами, так что вам не нужно отправлять запрос на SQL-сервер только для добавления интервала к метке времени, например.
Следующие подразделы описывают эти специальные типы данных. Для получения более подробной информации о функциях библиотеки pgtypes см. Раздел 33.6.
33.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 см. в разделе Раздел 33.6.
33.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;
}
33.4.4.2.3. numeric, decimal #
Обработка типов numeric и decimal аналогична обработке типа interval: требуется определение указателя, выделение некоторого пространства памяти в куче и доступ к переменной с использованием функций библиотеки pgtypes. Дополнительные сведения о функциях библиотеки pgtypes см. в разделе Раздел 33.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;
}
33.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.
33.4.4.3. Переменные хоста с не примитивными типами данных #
Как переменную хоста вы также можете использовать массивы, typedef'ы, структуры и указатели.
33.4.4.3.1. Массивы #
Существует два случая использования массивов в качестве переменных-хостов. Первый случай - это способ хранения некоторой текстовой строки в char[] или VARCHAR[], как объясняется в Раздел 33.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=
33.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;
33.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; то есть, вы также можете писать typedef как обычные операторы C.
Любое слово, которое вы объявляете как typedef, не может быть использовано в качестве ключевого слова SQL
в командах EXEC SQL позже в той же программе.
Например, это не сработает:
EXEC SQL BEGIN DECLARE SECTION;
typedef int start;
EXEC SQL END DECLARE SECTION;
...
EXEC SQL START TRANSACTION;
ECPG сообщит об ошибке синтаксиса для START
TRANSACTION, потому что он больше не
распознает START как ключевое слово SQL,
только как typedef.
(Если у вас возникнет такой конфликт, и переименование typedef
кажется непрактичным, вы можете написать SQL-команду
используя динамический SQL.)
Примечание
В выпусках Tantor SE-1C до версии 16 использование SQL ключевых слов в качестве имен typedef, вероятно, приводило к синтаксическим ошибкам, связанным с использованием самого typedef, а не с использованием имени как SQL ключевого слова. Новое поведение менее вероятно вызовет проблемы при повторной компиляции существующего приложения ECPG в новом выпуске Tantor SE-1C с новыми ключевыми словами.
33.4.4.3.4. Указатели #
Вы можете объявлять указатели на наиболее распространенные типы. Однако обратите внимание, что вы не можете использовать указатели в качестве целевых переменных запросов без автоматического выделения памяти. См. Раздел 33.7 для получения дополнительной информации об автоматическом выделении памяти.
EXEC SQL BEGIN DECLARE SECTION;
int *intp;
char **charp;
EXEC SQL END DECLARE SECTION;
33.4.5. Обработка не примитивных типов данных SQL #
Этот раздел содержит информацию о том, как обрабатывать нескалярные и пользовательские типы данных на уровне SQL в приложениях ECPG. Обратите внимание, что это отличается от обработки переменных хоста не примитивных типов, описанной в предыдущем разделе.
33.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. Обратите внимание, что это означает, что массив не может быть естественно доступен как массив в программе хоста (без дальнейшей обработки, которая разбирает текстовое представление).
33.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 могут быть собраны в одну структуру. Для получения более подробной информации о переменной хоста в форме структуры, см. Раздел 33.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[]. Однако таким образом не так легко получить доступ к полям значения из программы хоста.
33.4.5.3. Пользовательские базовые типы #
Новые пользовательские базовые типы не поддерживаются непосредственно ECPG.
Вы можете использовать внешнее строковое представление и переменные хоста
типа char[] или VARCHAR[], и это
решение действительно подходит и достаточно для многих типов.
Следующий фрагмент кода демонстрирует использование типа данных complex из примера в Раздел 35.12. Внешнее строковое представление этого типа — (%f,%f), и определено в функциях complex_in() и complex_out() в Раздел 35.12. В следующем примере значения комплексного типа (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)');
33.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. (См. Раздел 33.16 для включения специфичного для Oracle поведения).
Индикатор имеет еще одну функцию: если значение индикатора положительное, это означает, что значение не является нулевым, но оно было усечено при сохранении в переменной хоста.
Если аргумент -r no_indicator передается препроцессору ecpg, он работает в режиме “без индикатора”. В режиме без индикатора, если не указана переменная индикатора, для типов символьных строк значения null сигнализируются (при вводе и выводе) как пустая строка, а для целочисленных типов - как наименьшее возможное значение для данного типа (например, INT_MIN для int).