Область дескрипторов SQL представляет собой более совершенный метод обработки результата выполнения оператора SELECT, FETCH или
оператора DESCRIBE statement. Область дескрипторов SQL группирует данные одной строки вместе с метаданными в единую структуру данных. Метаданные особенно полезны при выполнении динамических операторов SQL, когда структура результирующих столбцов может быть неизвестна заранее. PostgreSQL предоставляет два способа использования областей дескрипторов (Descriptor Areas): именованные области дескрипторов SQL и структуры SQLDA на языке C.
Именованная область дескрипторов SQL состоит из заголовка, содержащего общую информацию о дескрипторе, и одной или нескольких областей дескрипторов элементов, каждая из которых описывает один столбец в результирующей строке.
Перед использованием области дескрипторов SQL ее необходимо инициализировать (выделить память):
EXEC SQL ALLOCATE DESCRIPTOR идентификатор;
Данный идентификатор служит в качестве «имени переменной» области дескриптора. Когда дескриптор больше не требуется, его следует удалить:
EXEC SQL DEALLOCATE DESCRIPTOR идентификатор;
Чтобы использовать область дескриптора, укажите её в качестве цели сохранения данных в
предложение INTO предложении, вместо перечисления главных переменных:
EXEC SQL FETCH NEXT FROM mycursor INTO SQL DESCRIPTOR mydesc;
Если результирующий набор пуст, область дескриптора всё равно будет содержать метаданные запроса, то есть имена полей.
Для еще не выполненных подготовленных запросов DESCRIBE
оператор может быть использован для получения метаданных результирующего набора:
EXEC SQL BEGIN DECLARE SECTION; char *sql_stmt = "SELECT * FROM table1"; EXEC SQL END DECLARE SECTION; EXEC SQL PREPARE stmt1 FROM :sql_stmt; EXEC SQL DESCRIBE stmt1 INTO SQL DESCRIPTOR mydesc;
До версии PostgreSQL 9.0 ключевое слово SQL было необязательным,
поэтому использование DESCRIPTOR и SQL DESCRIPTOR
создавало именованные области дескрипторов SQL. Теперь оно является обязательным; пропуск
этого ключевого слова SQL приводит к созданию областей дескрипторов SQLDA,
см. Раздел 4.3.7.2.
В DESCRIBE и FETCH операторах
ключевые слова предложение INTO и USING могут использоваться аналогичным образом: они помещают результирующий набор и метаданные в область дескриптора.
Как же извлечь данные из области дескриптора? Область дескриптора можно представить как структуру с именованными полями. Для получения значения поля из заголовка и его сохранения в главной переменной используйте следующую команду:
EXEC SQL GET DESCRIPTORимя:hostvar=field;
В настоящее время определено только одно поле заголовка:
COUNT, которое указывает количество существующих областей дескрипторов элементов (то есть количество столбцов в полученном результате). Главная переменная должна иметь целочисленный тип. Для получения значения поля из области дескрипторов элементов используйте следующую команду:
EXEC SQL GET DESCRIPTORимяVALUEnum:hostvar=field;
num может представлять собой целочисленный литерал или главную переменную, содержащую целое число. Доступные поля:
CARDINALITY (целое число) #количество строк в результирующем наборе
DATA #фактический элемент данных (следовательно, тип данных этого поля зависит от запроса)
DATETIME_INTERVAL_CODE (целое число) #
Когда TYPE имеет значение 9,
DATETIME_INTERVAL_CODE будет иметь значение
1 для DATE,
2 для TIME,
3 для TIMESTAMP,
4 для TIME WITH TIME ZONE, или
5 для TIMESTAMP WITH TIME ZONE.
DATETIME_INTERVAL_PRECISION (целое число) #не реализовано
INDICATOR (целое число) #индикатор (указывающий на значение NULL или усечение значения)
KEY_MEMBER (целое число) #не реализовано
LENGTH (целое число) #длина данных в символах
NAME (строка) #имя столбца
NULLABLE (целое число) #не реализовано
OCTET_LENGTH (целое число) #длина символьного представления данных в байтах
PRECISION (целое число) #
точность (для типа тип numeric)
RETURNED_LENGTH (целое число) #длина данных в символах
RETURNED_OCTET_LENGTH (целое число) #длина символьного представления данных в байтах
SCALE (целое число) #
масштаб (для типа тип numeric)
TYPE (целое число) #числовой код типа данных столбца
В EXECUTE, DECLARE и OPEN
инструкций, эффект предложение INTO и USING
ключевые слова различаются. Область дескрипторов также может быть создана вручную для передачи входных параметров в запрос или курсор, и
USING SQL DESCRIPTOR
— это способ передачи входных параметров в параметризованный запрос. Оператор
для создания именованной области дескрипторов SQL приведен ниже:
имя
EXEC SQL SET DESCRIPTORимяVALUEnumfield= :hostvar;
PostgreSQL поддерживает извлечение нескольких записей с помощью одной FETCH
команды; в этом случае сохранение данных в главные переменные предполагает, что переменная является массивом. Например:
EXEC SQL BEGIN DECLARE SECTION; int id[5]; EXEC SQL END DECLARE SECTION; EXEC SQL FETCH 5 FROM mycursor INTO SQL DESCRIPTOR mydesc; EXEC SQL GET DESCRIPTOR mydesc VALUE 1 :id = DATA;
Область дескрипторов SQLDA представляет собой структуру на языке C, которую также можно использовать для получения результирующего набора и метаданных запроса. Одна структура хранит одну запись из набора результатов.
EXEC SQL include sqlda.h; sqlda_t *mysqlda; команда EXEC SQL FETCH 3 предложение FROM mycursor предложение INTO DESCRIPTOR mysqlda;
Обратите внимание, что SQL ключевое слово опущено. Абзацы, описывающие варианты использования предложение INTO и USING
ключевых слов в Раздел 4.3.7.1 также применимы здесь с одним дополнением.
В DESCRIBE операторе DESCRIPTOR
ключевое слово может быть полностью опущено, если используется предложение INTO ключевое слово:
команда EXEC SQL DESCRIBE prepared_statement предложение INTO mysqlda;
Общий алгоритм работы программы, использующей SQLDA, следующий:
Подготовка запроса и объявление курсора для него.
Объявление структуры SQLDA для строк результата.
Объявление структуры SQLDA для входных параметров и их инициализация (выделение памяти, настройка параметров).
Открытие курсора оператором OPEN с использованием входной структуры SQLDA.
Извлечение строк из курсора и их сохранение в выходную структуру SQLDA.
Чтение значений из выходной структуры SQLDA в главные переменные (с преобразованием при необходимости).
Закрыть курсор.
Освободить область памяти, выделенную для входной структуры SQLDA.
SQLDA использует три типа структур данных: sqlda_t, sqlvar_t,
и struct sqlname.
SQLDA в PostgreSQL имеет структуру данных, аналогичную используемой в IBM DB2 Universal Database, поэтому техническая информация о SQLDA в DB2 может помочь лучше понять реализацию SQLDA в PostgreSQL.
Тип структуры sqlda_t является типом фактической структуры SQLDA. Она содержит одну запись. При этом две или более sqlda_t структуры могут быть объединены в связный список с помощью указателя в desc_next поле, представляя,
таким образом, упорядоченную коллекцию строк. Таким образом, при выборке двух или более строк приложение может прочитать их, следуя по desc_next указателю в
каждом sqlda_t узле.
Определение sqlda_t следующее:
struct sqlda_struct
{
char sqldaid[8];
long sqldabc;
short sqln;
short sqld;
struct sqlda_struct *desc_next;
struct sqlvar_struct sqlvar[1];
};
typedef struct sqlda_struct sqlda_t;
Значения полей следующие:
sqldaid #
Поле содержит строковый литерал "SQLDA ".
sqldabc #Поле содержит размер выделенной области памяти в байтах.
sqln #
Поле содержит количество входных параметров для параметризованного запроса в
случае передачи в OPEN, DECLARE или
EXECUTE операторы, использующие USING
ключевое слово. В случае использования в качестве выходных данных для SELECT,
EXECUTE или FETCH операторы,
его значение совпадает со значением sqld
оператор
sqld #Данный параметр содержит количество полей в результирующем наборе.
desc_next #
Если запрос возвращает более одной записи, возвращается несколько связанных
структур SQLDA, а поле desc_next содержит
указатель на следующий элемент в списке.
sqlvar #Это массив столбцов в результирующем наборе данных.
Тип структуры sqlvar_t содержит значение столбца
и метаданные, такие как тип и длина. Определение данного типа
выглядит следующим образом:
struct sqlvar_struct
{
short sqltype;
short sqllen;
тип данных char *sqldata;
short *sqlind;
struct sqlname sqlname;
};
typedef struct sqlvar_struct sqlvar_t;
Поля имеют следующее значение:
sqltype #
Содержит идентификатор типа поля. Список значений
см. в перечислении ECPGttype в ecpgtype.h.
sqllen #
Содержит бинарную длину поля, например, 4 байта для ECPGt_int.
sqldata #Указывает на данные. Формат данных описан в в Раздел 4.3.4.4.
sqlind #Указывает на индикатор null. Значение 0 означает NOT NULL, -1 означает NULL.
sqlname #Имя поля.
A struct sqlname структура содержит имя столбца. Она
используется как член sqlvar_t структуры. Определение структуры следующее:
#define NAMEDATALEN 64
struct sqlname
{
short length;
тип данных char data[NAMEDATALEN];
};
Описание полей:
Основные этапы получения набора результатов запроса через SQLDA:
Объявите параметр sqlda_t структура для получения результирующего набора.
Оператор EXECUTE FETCH/EXECUTE/DESCRIBE команды для обработки запроса с указанием объявленной структуры SQLDA.
Проверьте количество записей в результирующем наборе, обратившись к полю sqln, являющемуся членом структуры sqlda_t структура.
Получите значения каждого столбца из полей sqlvar[0], sqlvar[1]и последующих, являющихся членами структуры sqlda_t структура.
Перейдите к следующей строке (sqlda_t структура), следуя по указателю desc_next pointer, члену структуры sqlda_t структура.
Повторяйте вышеописанные действия по мере необходимости.
Ниже приведен пример получения результирующего набора с использованием структуры SQLDA.
Сначала объявите переменную типа sqlda_t структура для получения результирующего набора.
sqlda_t *sqlda1;
Затем укажите структуру SQLDA в операторе. Это FETCH пример команды.
команда EXEC SQL FETCH NEXT FROM предложение FROM имя курсора cur1 предложение INTO DESCRIPTOR sqlda1;
Запустите цикл по связанному списку для извлечения строк.
sqlda_t *cur_sqlda;
for (cur_sqlda = sqlda1;
cur_sqlda != NULL;
cur_sqlda = cur_sqlda->desc_next)
{
...
}
Внутри цикла запустите еще один цикл для извлечения данных каждого столбца
(sqlvar_t структуры) строки.
for (i = 0; i < cur_sqlda->sqld; i++)
{
sqlvar_t v = cur_sqlda->sqlvar[i];
тип данных char *sqldata = v.sqldata;
short sqllen = v.sqllen;
...
}
Чтобы получить значение столбца, проверьте sqltype значение,
являющееся членом sqlvar_t структуры. Затем выберите подходящий способ, в зависимости от типа столбца, для копирования данных из sqlvar поле в главную переменную.
char var_buf[1024];
switch (v.sqltype)
{
case ECPGt_char:
memset(&var_buf, 0, sizeof(var_buf));
memcpy(&var_buf, sqldata, (sizeof(var_buf) <= sqllen ? sizeof(var_buf) - 1 : sqllen));
break;
case ECPGt_int: /* integer */
memcpy(&intval, sqldata, sqllen);
snprintf(var_buf, sizeof(var_buf), "%d", intval);
break;
...
}
Основные этапы использования структуры SQLDA для передачи входных параметров в подготовленный запрос:
Создание подготовленного запроса (подготовленного оператора)
Объявление структуры sqlda_t в качестве входной структуры SQLDA.
Выделение области памяти (как структуры sqlda_t) для входной структуры SQLDA.
Задание (копирование) входных значений в выделенной области памяти.
Открытие курсора с указанием входной структуры SQLDA.
Ниже приведен пример.
Сначала создайте подготовленный оператор.
EXEC SQL BEGIN DECLARE SECTION; char query[1024] = "SELECT d.oid, * FROM pg_database d, pg_stat_database s WHERE d.oid = s.datid AND (d.datname = ? OR d.oid = ?)"; EXEC SQL END DECLARE SECTION; EXEC SQL PREPARE stmt1 FROM :query;
Затем выделите память для структуры SQLDA и установите количество входных параметров в поле sqln, являющемся переменной-членом
структуры sqlda_t структуры. Если для подготовленного запроса требуется два или более входных параметра, приложение должно выделить дополнительный объем памяти, который рассчитывается по формуле: (количество параметров - 1) * sizeof(sqlvar_t). В данном примере выделяется объем памяти, необходимый для двух входных параметров.
sqlda_t *sqlda2; sqlda2 = (sqlda_t *) malloc(sizeof(sqlda_t) + sizeof(sqlvar_t)); memset(sqlda2, 0, sizeof(sqlda_t) + sizeof(sqlvar_t)); sqlda2->sqln = 2; /* количество входных переменных */
После выделения памяти сохраните значения параметров в
sqlvar[] массиве. (Этот же массив используется для извлечения значений столбцов, когда структура SQLDA принимает результирующий набор.) В данном примере входными параметрами являются "postgres", имеющий строковый тип,
и 1, имеющий целочисленный тип.
sqlda2->sqlvar[0].sqltype = ECPGt_char; sqlda2->sqlvar[0].sqldata = "postgres"; sqlda2->sqlvar[0].sqllen = 8; int intval = 1; sqlda2->sqlvar[1].sqltype = ECPGt_int; sqlda2->sqlvar[1].sqldata = (char *) &intval; sqlda2->sqlvar[1].sqllen = sizeof(intval);
При открытии курсора и указании предварительно настроенной структуры SQLDA входные параметры передаются в подготовленный оператор.
команда EXEC SQL OPEN имя курсора cur1 USING DESCRIPTOR sqlda2;
Наконец, после использования входных структур SQLDA выделенную область памяти необходимо освободить явным образом, в отличие от структур SQLDA, используемых для получения результатов запроса.
free(sqlda2);
Ниже приведен пример программы, демонстрирующей процесс получения статистики доступа к базам данных, указанным с помощью входных параметров, из системных каталогов.
Данное приложение объединяет две системные таблицы — системный каталог pg_database и системную таблицу pg_stat_database по значению тип oid базы данных, а также получает и отображает статистику базы данных, извлеченную по двум входным параметрам (база данных postgres, и тип oid 1).
Сначала объявите структуру SQLDA для ввода и структуру SQLDA для вывода.
EXEC SQL include sqlda.h; sqlda_t *sqlda1; /* дескриптор вывода */ sqlda_t *sqlda2; /* дескриптор ввода */
Далее установите соединение с базой данных, подготовьте оператор и объявите курсор для подготовленного оператора.
int
main(void)
{
оператор EXEC SQL BEGIN DECLARE SECTION;
тип данных char query[1024] = "SELECT d.oid,* FROM pg_database d, pg_stat_database s WHERE d.oid=s.datid AND ( d.datname=? OR d.oid=? )";
оператор EXEC SQL END DECLARE SECTION;
оператор EXEC SQL CONNECT TO testdb AS имя соединения con1 USER testuser;
оператор EXEC SQL SELECT функция pg_catalog.set_config('search_path', '', false); оператор EXEC SQL COMMIT;
EXEC SQL PREPARE stmt1 предложение FROM :query;
EXEC SQL DECLARE имя курсора cur1 CURSOR FOR stmt1;
Затем поместите значения во входную структуру SQLDA для входных параметров. Выделите память для входной структуры SQLDA и установите количество входных параметров в значение sqln. Сохраните тип, значение и длину значения в предложение sqltype,
sqldata, а также sqllen в
sqlvar структуре.
/* Создание структуры SQLDA для входных параметров. */
sqlda2 = (sqlda_t *) malloc(sizeof(sqlda_t) + sizeof(sqlvar_t));
функция memset(sqlda2, 0, sizeof(sqlda_t) + sizeof(sqlvar_t));
sqlda2->sqln = 2; /* число входных переменных */
sqlda2->sqlvar[0].sqltype = ECPGt_char;
sqlda2->sqlvar[0].sqldata = "postgres";
sqlda2->sqlvar[0].sqllen = 8;
intval = 1;
sqlda2->sqlvar[1].sqltype = ECPGt_int;
sqlda2->sqlvar[1].sqldata = (char *)&intval;
sqlda2->sqlvar[1].sqllen = sizeof(intval);
После настройки входной структуры SQLDA откройте курсор с использованием данной структуры SQLDA.
/* Открытие курсора с входными параметрами. */
EXEC SQL OPEN cur1 USING DESCRIPTOR sqlda2;
Выполните выборку строк в выходную структуру SQLDA из открытого курсора.
(Как правило, необходимо выполнять вызов FETCH повторно
в цикле, чтобы извлечь все строки из результирующего набора.)
while (1)
{
sqlda_t *cur_sqlda;
/* Назначение дескриптора курсору */
EXEC SQL FETCH NEXT FROM cur1 INTO DESCRIPTOR sqlda1;
Затем извлеките полученные записи из структуры SQLDA, следуя по связанному списку sqlda_t структуры.
for (cur_sqlda = sqlda1 ;
cur_sqlda != NULL ;
cur_sqlda = cur_sqlda->desc_next)
{
...
Считайте каждый столбец в первой записи. Количество столбцов
хранится в sqld, а фактические данные первого
столбца хранятся в sqlvar[0], оба элемента
структуры sqlda_t структуре.
/* Вывод каждого столбца в строке. */
for (i = 0; i < sqlda1->sqld; i++)
{
sqlvar_t v = sqlda1->sqlvar[i];
char *sqldata = v.sqldata;
short sqllen = v.sqllen;
strncpy(name_buf, v.sqlname.data, v.sqlname.length);
name_buf[v.sqlname.length] = '\0';
Теперь данные столбца сохранены в переменной v. Скопируйте все данные в главные переменные, проверяя v.sqltype для определения типа столбца.
switch (v.sqltype) {
int intval;
double doubleval;
unsigned long long int longlongval;
case ECPGt_char:
memset(&var_buf, 0, sizeof(var_buf));
memcpy(&var_buf, sqldata, (sizeof(var_buf) <= sqllen ? sizeof(var_buf)-1 : sqllen));
break;
case ECPGt_int: /* integer */
memcpy(&intval, sqldata, sqllen);
snprintf(var_buf, sizeof(var_buf), "%d", intval);
break;
...
default:
...
}
printf("%s = %s (type: %d)\n", name_buf, var_buf, v.sqltype);
}
Закройте курсор после обработки всех записей и отключитесь от базы данных.
EXEC SQL CLOSE cur1;
EXEC SQL COMMIT;
EXEC SQL DISCONNECT ALL;
Полный текст программы приведён в Пример 4.3.1.
Пример 4.3.1. Пример программы с использованием SQLDA
#include#include #include #include #include EXEC SQL include sqlda.h; sqlda_t *sqlda1; /* дескриптор для вывода */ sqlda_t *sqlda2; /* дескриптор для ввода */ EXEC SQL WHENEVER NOT FOUND DO BREAK; EXEC SQL WHENEVER SQLERROR STOP; int main(void) { оператор EXEC SQL BEGIN DECLARE SECTION; тип данных char query[1024] = "SELECT d.oid,* FROM pg_database d, pg_stat_database s WHERE d.oid=s.datid AND ( d.datname=? OR d.oid=? )"; int intval; unsigned long long int longlongval; оператор EXEC SQL END DECLARE SECTION; оператор EXEC SQL CONNECT TO uptimedb AS con1 USER uptime; оператор EXEC SQL SELECT функция pg_catalog.set_config('search_path', '', false); оператор COMMIT; EXEC SQL PREPARE stmt1 предложение FROM :query; EXEC SQL DECLARE cur1 CURSOR FOR stmt1; /* Создание структуры SQLDA для входного параметра */ sqlda2 = (sqlda_t *)malloc(sizeof(sqlda_t) + sizeof(sqlvar_t)); функция memset(sqlda2, 0, sizeof(sqlda_t) + sizeof(sqlvar_t)); sqlda2->sqln = 2; /* количество входных переменных */ sqlda2->sqlvar[0].sqltype = ECPGt_char; sqlda2->sqlvar[0].sqldata = "postgres"; sqlda2->sqlvar[0].sqllen = 8; intval = 1; sqlda2->sqlvar[1].sqltype = ECPGt_int; sqlda2->sqlvar[1].sqldata = (char *) &intval; sqlda2->sqlvar[1].sqllen = sizeof(intval); /* Открытие курсора с входными параметрами. */ EXEC SQL OPEN cur1 USING DESCRIPTOR sqlda2; while (1) { sqlda_t *cur_sqlda; /* Назначение дескриптора курсору */ EXEC SQL FETCH NEXT FROM cur1 INTO DESCRIPTOR sqlda1; for (cur_sqlda = sqlda1 ; cur_sqlda != NULL ; cur_sqlda = cur_sqlda->desc_next) { int i; char name_buf[1024]; char var_buf[1024]; /* Вывод каждого столбца в строке. */ for (i=0 ; i sqld ; i++) { sqlvar_t v = cur_sqlda->sqlvar[i]; char *sqldata = v.sqldata; short sqllen = v.sqllen; strncpy(name_buf, v.sqlname.data, v.sqlname.length); name_buf[v.sqlname.length] = '\0'; switch (v.sqltype) { case ECPGt_char: memset(&var_buf, 0, sizeof(var_buf)); memcpy(&var_buf, sqldata, (sizeof(var_buf)<=sqllen ? sizeof(var_buf)-1 : sqllen) ); break; case ECPGt_int: /* integer */ memcpy(&intval, sqldata, sqllen); snprintf(var_buf, sizeof(var_buf), "%d", intval); break; case ECPGt_long_long: /* bigint */ memcpy(&longlongval, sqldata, sqllen); snprintf(var_buf, sizeof(var_buf), "%lld", longlongval); break; default: { int i; memset(var_buf, 0, sizeof(var_buf)); for (i = 0; i < sqllen; i++) { char tmpbuf[16]; snprintf(tmpbuf, sizeof(tmpbuf), "%02x ", (unsigned char) sqldata[i]); strncat(var_buf, tmpbuf, sizeof(var_buf)); } } break; } printf("%s = %s (type: %d)\n", name_buf, var_buf, v.sqltype); } printf("\n"); } } EXEC SQL CLOSE cur1; EXEC SQL COMMIT; EXEC SQL DISCONNECT ALL; return 0; }
Результат выполнения данного примера должен выглядеть примерно следующим образом (некоторые значения могут отличаться).
oid = 1 (type: 1)
datname = template1 (type: 1)
datdba = 10 (type: 1)
encoding = 0 (type: 5)
datistemplate = t (type: 1)
datallowconn = t (type: 1)
dathasloginevt = f (type: 1)
datconnlimit = -1 (type: 5)
datfrozenxid = 379 (type: 1)
dattablespace = 1663 (type: 1)
datconfig = (type: 1)
datacl = {=c/uptime,uptime=CTc/uptime} (type: 1)
datid = 1 (type: 1)
datname = template1 (type: 1)
numbackends = 0 (type: 5)
xact_commit = 113606 (type: 9)
xact_rollback = 0 (type: 9)
blks_read = 130 (type: 9)
blks_hit = 7341714 (type: 9)
tup_returned = 38262679 (type: 9)
tup_fetched = 1836281 (type: 9)
tup_inserted = 0 (type: 9)
tup_updated = 0 (type: 9)
tup_deleted = 0 (type: 9)
oid = 11511 (type: 1)
datname = postgres (type: 1)
datdba = 10 (type: 1)
encoding = 0 (type: 5)
datistemplate = f (type: 1)
datallowconn = t (type: 1)
dathasloginevt = f (type: 1)
datconnlimit = -1 (type: 5)
datfrozenxid = 379 (type: 1)
dattablespace = 1663 (type: 1)
datconfig = (type: 1)
datacl = (type: 1)
datid = 11511 (type: 1)
имя базы данных datname = postgres (type: 1)
numbackends = 0 (type: 5)
xact_commit = 221069 (type: 9)
xact_rollback = 18 (type: 9)
blks_read = 1176 (type: 9)
blks_hit = 13943750 (type: 9)
tup_returned = 77410091 (type: 9)
tup_fetched = 3253694 (type: 9)
tup_inserted = 0 (type: 9)
tup_updated = 0 (type: 9)
tup_deleted = 0 (type: 9)