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 BE до версии 16 использование SQL ключевых слов в качестве имен typedef, вероятно, приводило к синтаксическим ошибкам, связанным с использованием самого typedef, а не с использованием имени как SQL ключевого слова. Новое поведение менее вероятно вызовет проблемы при повторной компиляции существующего приложения ECPG в новом выпуске Tantor BE с новыми ключевыми словами.
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. В следующем примере вставляются
значения типа 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)');
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
).