36.4. Использование переменных среды #
В Разделе 36.3 вы увидели, как можно выполнять SQL-операторы в программе со встраиваемым SQL. Некоторые из этих операторов использовали только фиксированные значения и не давали возможности вставлять в операторы произвольные значения или обрабатывать значения, возвращённые запросом. Операторы такого вида не очень полезны в реальных приложениях. В этом разделе подробно описывается, как можно передавать данные между программой на C и встраиваемыми операторами SQL, используя простой механизм, так называемые переменные среды. В программе со встраиваемым SQL мы считаем SQL-операторы внедрёнными в код программы на C, языке среды. Таким образом, переменные программы на C называются переменными среды.
Ещё один способ передать значения данных между сервером Postgres Pro и приложениями ECPG заключается в использовании дескрипторов SQL, как описано в Разделе 36.7.
36.4.1. Обзор #
Передавать данные между программой C и SQL-операторами во встраиваемом SQL очень просто. Вместо того, чтобы вставлять данные в оператор, что влечёт дополнительные усложнения, в частности нужно правильно заключать значения в кавычки, можно просто записать имя переменной C в SQL-операторе, предварив его двоеточием. Например:
EXEC SQL INSERT INTO sometable VALUES (:v1, 'foo', :v2);
Этот оператор обращается к двум переменным C с именами v1
и v2
и также использует обычную строковую константу SQL, показывая тем самым, что можно свободно сочетать разные виды данных.
Этот метод включения переменных C в SQL-операторы работает везде, где SQL-оператор принимает выражение значения.
36.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;
Вы можете включать в программу столько секций объявлений, сколько захотите.
Эти объявления выводятся в результирующий файл как объявления обычных переменных C, так что эти переменные не нужно объявлять снова. Переменные, которые не предназначены для использования в командах SQL, можно объявить как обычно вне этих специальных секций.
Определение структуры или объединения тоже должно размещаться в секции DECLARE
. В противном случае препроцессор не сможет воспринять эти типы, так как не будет знать их определения.
36.4.3. Получение результатов запроса #
Теперь вы умеете передавать данные, подготовленные вашей программой, в команду SQL. Но как получить результаты запроса? Для этой цели во встраиваемом SQL есть особые вариации обычных команд SELECT
и FETCH
. У этих команд есть специальное предложение INTO
, определяющее, в какие переменные среды будут помещены получаемые значения. SELECT
используется для запросов, возвращающих только одну строку, а FETCH
применяется с курсором для запросов, возвращающих несколько строк.
Пример:
/* * предполагается существование такой таблицы: * 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
размещается после всех остальных обычных предложений.
36.4.4. Сопоставление типов #
Когда приложения ECPG передают данные между сервером Postgres Pro и программой на C, например, получая результаты запроса с сервера или выполняя операторы SQL с входными параметрам, эти данные должны преобразовываться из типов Postgres Pro в типы переменных языка среды (а именно типы языка C) и наоборот. Одно из главных качеств ECPG состоит в том, что в большинстве случаев он делает это автоматически.
В этом отношении можно выделить два вида типов данных. К первому относятся простые типы данных Postgres Pro, такие как integer
и text
, которые приложение может непосредственно читать и писать. С другими типами данных, такими как timestamp
и numeric
, можно работать только через специальные функции; см. Подраздел 36.4.4.2.
В Таблице 36.1 показано, как типы данных Postgres Pro соответствуют типам данных C. Когда нужно передать или получить значение определённого типа данных Postgres Pro, вы должны объявить переменную C соответствующего типа C в секции объявлений.
Таблица 36.1. Соответствие между типами данных Postgres Pro и типами переменных C
Тип данных Postgres Pro | Тип переменной среды С |
---|---|
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] С этим типом можно работать только через специальные функции; см. Подраздел 36.4.4.2. [b] объявляется в |
36.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
преобразуется в структуру (struct
) с собственным именем для каждой переменной. Объявление вида:
VARCHAR var[180];
преобразуется в:
struct varchar_var { int len; char arr[180]; } var;
Член структуры arr
содержит строку, включающую завершающий нулевой байт. Таким образом, чтобы сохранить строку в переменной типа VARCHAR
, эта переменная должна быть объявлена с длиной, учитывающей завершающий нулевой байт. Член структуры len
содержит длину строки, сохранённой в arr
, без завершающего нулевого байта. Когда на вход запросу подаётся переменная среды C, у которой strlen(arr)
отличается от len
, применяется наименьшее значение.
VARCHAR
можно записать в верхнем или нижнем регистре, но не в смешанном.
Переменные char
и VARCHAR
также могут содержать значения других типов SQL в их строковом представлении.
36.4.4.2. Обработка специальных типов данных #
ECPG представляет некоторые особые типы, которые должны помочь вам легко оперировать некоторыми специальными типами данных Postgres Pro. В частности, в нём реализована поддержка типов numeric
, decimal
, date
, timestamp
и interval
. Для этих типов нельзя подобрать полезное соответствие с примитивными типами среды (например, int
, long long int
или char[]
), так как они имеют сложную внутреннюю структуру. Приложения, работающие с этими типами, должны объявлять переменные особых типов и работать с ними, применяя функции из библиотеки pgtypes. Эта библиотека, подробно описанная в Разделе 36.6 содержит базовые функции для оперирования этими типами, чтобы вам не требовалось, например, передавать запрос SQL-серверу, когда нужно просто добавить интервал к значению времени.
Эти особые типы данных описаны в следующих подразделах. Чтобы подробнее узнать о функциях в библиотеке pgtypes, обратитесь к Разделу 36.6.
36.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, обратитесь к Разделу 36.6.
36.4.4.2.2. interval #
Принцип работы с типом 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; }
36.4.4.2.3. numeric, decimal #
Типы numeric
и decimal
обрабатываются так же, как и тип interval
: вы должны определить указатель, выделить некоторое пространство памяти в куче и обращаться к переменной, используя функции в библиотеке pgtypes. Чтобы подробнее узнать о функциях в библиотеке pgtypes, обратитесь к Разделу 36.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)); /* Преобразовать decimal в numeric, чтобы вывести десятичное значение. */ 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; }
36.4.4.2.4. bytea #
Работа с типом bytea
организуется так же, как и с VARCHAR
. Определение каждой переменной-массива типа bytea
преобразуется в именованную структуру. Например, объявление:
bytea var[180];
преобразуется в:
struct bytea_var { int len; char arr[180]; } var;
Двоичные данные формата помещаются в поле arr
. В отличие от типа VARCHAR
, в данных bytea
могут быть восприняты значения '\0'
. Эти данные записываются/считываются и переводятся из шестнадцатеричного формата и обратно средствами ecpglib.
Примечание
Переменные bytea
можно использовать, только когда параметр bytea_output равен hex
.
36.4.4.3. Переменные среды для непримитивных типов #
В качестве переменных среды также можно использовать массивы, определения типов, структуры и указатели.
36.4.4.3.1. Массивы #
Для применения массивов в качестве переменных среды есть два варианта использования. Во-первых, в массиве char[]
или VARCHAR[]
можно сохранить текстовую строку, как рассказывалось в Подразделе 36.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; /* Получить в массивы сразу несколько строк. */ 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=
36.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; /* по достижении конца набора результатов прервать цикл while */ EXEC SQL WHENEVER NOT FOUND DO BREAK; while (1) { /* Выбрать несколько столбцов в одну структуру. */ EXEC SQL FETCH FROM cur1 INTO :dbval; /* Напечатать члены структуры. */ 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; /* по достижении конца набора результатов прервать цикл while */ EXEC SQL WHENEVER NOT FOUND DO BREAK; while (1) { /* Выбрать несколько столбцов в одну структуру. */ EXEC SQL FETCH FROM cur1 INTO :dbval, :size; /* Напечатать члены структуры. */ printf("oid=%d, datname=%s, size=%lld\n", dbval.oid, dbval.datname, size); } EXEC SQL CLOSE cur1;
36.4.4.3.3. Определения типов #
Чтобы сопоставить новые типы с уже существующими, используйте ключевое слово 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;
Это объявление не обязательно должно находиться в секции объявлений, то есть также можно писать определения типов как обычные операторы 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, только как определение типа. (Если у вас есть такой конфликт и переименование определения типа кажется нецелесообразным, можно написать команду SQL, используя динамический SQL.)
Примечание
В выпусках PostgreSQL до 16 версии использование ключевых слов SQL в качестве имён определений типов скорее приводило к синтаксическим ошибкам, связанным с использованием самого определения типа, а не с использованием имени в качестве ключевого слова SQL. Новое поведение с меньшей вероятностью вызовет проблемы при перекомпиляции существующего приложения ECPG в новом выпуске PostgreSQL с новыми ключевыми словами.
36.4.4.3.4. Указатели #
Вы можете объявлять указатели на самые распространённые типы. Учтите, однако, что указатели нельзя использовать в качестве целевых переменных запросов без автовыделения. За дополнительными сведениями об автовыделении обратитесь к Разделу 36.7.
EXEC SQL BEGIN DECLARE SECTION; int *intp; char **charp; EXEC SQL END DECLARE SECTION;
36.4.5. Обработка непримитивных типов данных SQL #
В этом разделе описывается как работать с нескалярными и пользовательскими типами уровня SQL в приложениях ECPG. Заметьте, что этот подход отличается от использования переменных непримитивных типов, описанного в предыдущем разделе.
36.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
Чтобы сопоставить несколько элементов массива с несколькими элементами переменной-массивом среды, каждый элемент массива SQL нужно по отдельности связать с каждым элементом массива среды, например:
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) { /* НЕПРАВИЛЬНО */ EXEC SQL FETCH FROM cur1 INTO :ii_a; ... }
не будет работать корректно, так как столбец с типом массива нельзя напрямую сопоставить с переменной-массивом среды.
Можно также применить обходное решение — хранить массивы в их внешнем строковом представлении в переменных среды типа char[]
или VARCHAR[]
. Более подробно это представление описывается в Подразделе 8.15.2. Заметьте, это означает, что с таким массивом в программе нельзя будет работать естественным образом (без дополнительного разбора текстового представления).
36.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, 'Postgres Pro') );
Самое очевидное решение заключается в обращении к каждому атрибуту по отдельности. Следующая программа получает данные из тестовой таблицы, выбирая атрибуты типа comp_t
по одному:
EXEC SQL BEGIN DECLARE SECTION; int intval; varchar textval[33]; EXEC SQL END DECLARE SECTION; /* Указать каждый элемент столбца составного типа в списке SELECT. */ 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) { /* Выбрать каждый элемент столбца составного типа в переменную среды С. */ EXEC SQL FETCH FROM cur1 INTO :intval, :textval; printf("intval=%d, textval=%s\n", intval, textval.arr); } EXEC SQL CLOSE cur1;
В развитие этого примера, переменные среды, в которые помещаются результаты команды FETCH
, можно собрать в одну структуру. Подробнее переменные среды в форме структуры описываются в Подразделе 36.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; /* Поместить каждый элемент составного типа в список SELECT. */ 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) { /* Поместить все значения списка SELECT в одну структуру. */ 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) { /* Выбрать все значения в списке SELECT в одну структуру. */ EXEC SQL FETCH FROM cur1 INTO :compval; printf("intval=%d, textval=%s\n", compval.intval, compval.textval.arr); } ...
Таким образом составные типы можно сопоставить со структурами практически прозрачно, хотя ECPG и не понимает составные типы.
Наконец, также можно сохранить значения составного типа в их внешнем строковом представлении в переменных среды типа char[]
или VARCHAR[]
. Однако при таком подходе нет простой возможности обращаться из программы к полям значения.
36.4.5.3. Пользовательские базовые типы #
Определяемые пользователем базовые типы не поддерживаются ECPG напрямую. Для них можно использовать внешнее строковое представление и переменные среды типа char[]
или VARCHAR[]
, и это решение действительно будет подходящим и достаточным для большинства типов.
Следующий фрагмент кода демонстрирует использование типа данных complex
из примера в Разделе 38.13. Внешнее строковое представление этого типа имеет форму (%f,%f)
и определено в функциях complex_in()
и complex_out()
в Разделе 38.13. Следующий пример вставляет значения комплексного типа (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)');
36.4.6. Индикаторы #
Приведённые выше примеры никак не обрабатывали значения NULL. На самом деле, в примерах с извлечением данных возникнет ошибка, если они выберут из базы данных значение NULL. Чтобы можно было передавать значения NULL в базу данных или получать их из базы данных, вы должны добавить объявление второй переменной среды C для каждой переменной C, содержащей данные. Эта вторая переменная среды C называется индикатором и содержит флаг, показывающий, что в данных передаётся NULL, и при этом значение основной переменной среды C игнорируется. Следующий пример демонстрирует правильную обработку значений 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. (Особенности, связанные с режимом совместимости с Oracle, описаны в Разделе 36.16.)
Индикатор выполняет ещё одну функцию: если он содержит положительное число, это означает, что значение не NULL, но оно было обрезано, когда сохранялось в переменной среды С.
Если препроцессору ecpg
передаётся аргумент -r no_indicator
, он работает в режиме «без индикатора». В этом режиме, если переменная индикатора не определена, значения NULL обозначаются (при вводе и выводе) для символьных строк пустой строкой, а для целочисленных типов наименьшим возможным значением этого типа (например, INT_MIN
для int
).