FirebirdSQL logo
 Интерфейсы FirebirdНаписание плагинов 

Открытие курсора и извлечение данных из него

Единственный способ получить строки данных, возвращаемых операторомSELECT в OO API — это использовать интерфейсIResultSet. Этот интерфейсвозвращается методом openCursor() как в IAttachment, так и в IStatement.openCursor() в большинстве аспектов похож на execute(), и решение какимобразом открыть курсор (с использованием подготовленного оператора илинепосредственно из интерфейса подключения) то же. В примерах03.select.cpp и 04.print_table.cpp используются оба способа.Обратите внимание на одно отличие метода openCursor() по сравнению сexecute() — никто не передает буфер для выходного сообщения вopenCursor(), он будет передан позже, когда данные будут извлечены изкурсора. Это позволяет открывать курсор с неизвестным форматом выходногосообщения (NULL передается вместо выходных метаданных). В этом случаеFirebird использует формат сообщения по умолчанию, который может бытьзапрошен через интерфейс IResultSet:

const char* sql = "select * from ..."; // some select statement

IResultSet* curs = att->openCursor(&status, tra, 0, sql, SQL_DIALECT_V6,
    NULL, NULL, NULL, NULL, 0);

IMessageMetadata* meta = curs->getMetadata(&status);

Позже эти метаданные могут использоваться для выделения буфера дляданных и разбора извлечённых строк.

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

IStatement* stmt = att->prepare(&status, tra, 0, sql, SQL_DIALECT_V6,
    IStatement::PREPARE_PREFETCH_METADATA);

IMessageMetadata* meta = stmt->getOutputMetadata(&status);

IResultSet* curs = stmt->openCursor(&status, tra, NULL, NULL, NULL, 0);

Мы получили (тем или иным способом) экземпляр описания метаданныхвыходных полей (строк в наборе данных). Для работы с сообщением намтакже нужен буфер:

unsigned char* buffer = new unsigned char[meta->getMessageLength(&status)];

В IResultSet есть много различных методов выборки, но когда курсороткрыт не с параметром SCROLL, то работает только fetchNext(), то естьможно перемещаться по записям только вперед. В дополнение к ошибкам ипредупреждениям в статусе метод fetchNext() возвращает код завершения,который может иметь значения RESULT_OK (когда буфер заполняетсязначениями для следующей строки) или RESULT_NO_DATA (когда в курсоребольше строк не осталось). RESULT_NO_DATA не является состоянием ошибки,это нормальное состояние после завершения метода, которое сигнализирует,что данных в курсоре больше нет. Если используется оболочка статуса(Status Wrapper), то исключение не бросается в случае возврата ошибки.Может быть возвращено еще одно значение — RESULT_ERROR — оно означаетотсутствие данных в буфере и ошибки в статусе векторе. Метод fetchNext()обычно вызывается в цикле:

while (curs->fetchNext(&status, buffer) == IStatus::RESULT_OK)
{
    // row processing
}

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

unsigned char* field_N_ptr = buffer + meta->getOffset(&status, n);

где n - номер поля в сообщении. Этот указатель должен быть присвоенсоответствующему типу, в зависимости от типа поля. Например, для поляVARCHAR, следует использовать приведение к структуре vary:

vary* v_ptr = (vary*) (buffer + meta->getOffset(&status, n));

Теперь мы можем напечатать значение поля:

printf("field %s value is %*.*s\n",
       meta->getField(&status, n),
       v_ptr->vary_length,
       v_ptr->vary_length,
       v_ptr->vary_string);

Если вам нужна максимальная производительность, будет полезно кэшироватьнеобходимые значения метаданных, как это сделано в наших примерах03.select.cpp и 04.print_table.cpp.

Использование макросов FB_MESSAGE для статических сообщений

Работа с данными с использованием смещений довольно эффективна, нотребует написания большого количества кода. В C++ эту проблему можнорешить с помощью шаблонов, но даже по сравнению с ними наиболее удобнымспособом работы с сообщением является представление его в родном (длязаданного языка) форме — структуре в C/C++, записи в Pascal и т. д.Конечно это работает только в том случае, если формат сообщения известензаранее. Для создания таких структур в C++ в Firebird существуетспециальный макрос FB_MESSAGE.

FB_MESSAGE имеет 3 аргумента: имя сообщения (структуры), тип обёрткистатуса (status wrapper) и список полей. Использование первого и второгоаргумента очевидно, список полей содержит пары (field_type, field_name),где field_type является одним из следующих:

  • FB_BIGINT

  • FB_BLOB

  • FB_BOOLEAN

  • FB_CHAR(len)

  • FB_DATE

  • FB_DECFLOAT16

  • FB_DECFLOAT34

  • FB_DOUBLE

  • FB_FLOAT

  • FB_INTEGER

  • FB_INTL_CHAR(len, charSet)

  • FB_INTL_VARCHAR(len, charSet)

  • FB_SCALED_BIGINT(x)

  • FB_SCALED_INTEGER(x)

  • FB_SCALED_SMALLINT(x)

  • FB_SMALLINT

  • FB_TIME

  • FB_TIME_TZ

  • FB_TIME_TZ_EX

  • FB_TIMESTAMP

  • FB_TIMESTAMP_TZ

  • FB_TIMESTAMP_TZ_EX

  • FB_VARCHAR(len)

В сгенерированной препроцессором структуре типы integer и floatсопоставляются с соответствующими типами C, типы date и time — склассами FbDate иFbTime (все упомянутые здесь классынаходятся в пространстве имен Firebird), тип timestamp — с классомFbTimestamp, содержащим два публичныхчлена данных дату и время соответствующих классов, тип char — соструктурой FbChar и varchar — со структуройFbVarChar. Для каждого поля препроцессорсоздаст два члена данных — name для значения поля/параметра и nameNullдля индикатора NULL. Конструктор сообщений имеет 2 параметра — указательна оболочку статуса (status wrapper) и главный интерфейс (masterinterface):

FB_MESSAGE(Output, ThrowStatusWrapper,
    (FB_SMALLINT, relationId)
    (FB_CHAR(31), relationName)
    (FB_VARCHAR(100), description)
) output(&status, master);

Для статических сообщений использование FB_MESSAGE является самым лучшимвыбором, в то же время они легко могут быть переданы в методы execute,openCursor и fetch:

rs = att->openCursor(&status, tra, 0, sqlText,
      SQL_DIALECT_V6, NULL, NULL, output.getMetadata(), NULL, 0);

и используется для работы со значениями отдельных полей:

while (rs->fetchNext(&status, output.getData()) == IStatus::RESULT_OK)
{
  printf("%4d %31.31s %*.*s\n", output->relationId, output->relationName.str,
    output->descriptionNull ? 0 : output->description.length,
    output->descriptionNull ? 0 : output->description.length,
    output->description.str);
}

Пример использования макроса FB_MESSAGE для работы с сообщениямиприведен в примере 06.fb_message.cpp.

Работа с BLOB

Для BLOBs Firebird хранит в буфере сообщения идентификатор BLOB — 8байтовый объект, который должен быть выравнен по 4-байтной границе.Идентификатор имеет тип ISC_QUAD. ИнтерфейсIAttachment имеет 2 метода дляработы с BLOB — openBlob() и createBlob(), возвращающие интерфейсIBlob и имеющие одинаковый наборпараметров, но выполняющие несколько разные действия: openBlob()принимает BLOB идентификатор из сообщения и подготавливает BLOB длячтения, а createBlob() создает новый BLOB, помещает его идентификатор всообщение и подготавливает BLOB для записи.

Для работы с BLOBs прежде всего необходимо включить в сообщение ихBLOB-идентификаторы. Если вы получите метаданные из поля движка Firebirdсоответствующего типа, то этот идентификатор уже будет присутствовать. Вэтом случае вы просто используете его смещение (при условии, чтопеременная blobFieldNumber содержит номер поля BLOB) (и соответствующееNULL смещение для проверки NULL или установки NULL флага) для полученияуказателя в буфере сообщений:

ISC_QUAD* blobPtr =
  (ISC_QUAD*) &buffer[metadata->getOffset(&status, blobFieldNumber)];
ISC_SHORT* blobNullPtr =
  (ISC_SHORT*) &buffer[metadata->getNullOffset(&status, blobFieldNumber)];

Если вы используете статические сообщениями макрос FB_MESSAGE, то полеBLOB будет объявлено как тип FB_BLOB:

FB_MESSAGE(Msg, ThrowStatusWrapper,
    (FB_BLOB, b)
) message(&status, master);

ISC_QUAD* blobPtr = &message->b;
ISC_SHORT* blobNullPtr = &message->bNull;

Для создания нового BLOB, вызовите метод createBlob():

IBlob* blob = att->createBlob(status, tra, blobPtr, 0, NULL);

Последние два параметра требуются только в том случае, если вы хотитеиспользовать blob-фильтры или blob-поток, которые не рассматриваютсяздесь.

Теперь Blob интерфейс готов принять данные в BLOB. Используйте методputSegment() для отправки данных в движок:

void* segmentData;
unsigned segmentLength;
while (userFunctionProvidingBlobData(&segmentData, &segmentLength))
    blob->putSegment(&status, segmentLength, segmentData);

После отправки некоторых данных в BLOB не забудьте закрытьblob-интерфейс:

blob->close(&status);

Убедитесь, что null флаг не установлен (не требуется, если вы сбросиливесь буфер сообщений перед созданием BLOB):

*blobNullPtr = 0;

и сообщение, содержащее BLOB, может использоваться в операторе вставкиили обновления. После выполнения этого оператора новый BLOB будетсохранен в базе данных.

Чтобы прочитать blob, необходимо получить его идентификатор в сообщенииот ядра firebird. Это можно сделать с помощью методов fetch() илиexecute(). После этого используйте метод openBlob():

IBlob* blob = att->openBlob(status, tra, blobPtr, 0, NULL);

Blob интерфейс готов предоставить данные BLOB. Используйте методgetSegment() для получения данных из движка:

char buffer[BUFSIZE];
unsigned actualLength;

for(;;)
{
  switch (blob->getSegment(&status, sizeof(buffer), buffer, &actualLength))
  {
    case IStatus::RESULT_OK:
      userFunctionAcceptingBlobData(buffer, actualLength, true);
      continue;

    case IStatus::RESULT_SEGMENT:
      userFunctionAcceptingBlobData(buffer, actualLength, false);
      continue;

    default:
      break;
  }
}

Последний параметр в userFunctionAcceptingBlobData() — это флагдостижения конца сегмента — когда getSegment() возвращает код завершенияRESULT_SEGMENT, о чём будет уведомлена функция (в последний параметрпередан false), то есть этот сегмент прочитан не полностью, ипродолжение ожидается при следующем вызове.

Закончив работать с BLOB, не забудьте закрыть его:

blob->close(&status);

Пакетное изменение данных

Поскольку Firebird 4.0 поддерживает пакетное выполнение операторов с входными параметрами, это означаетотправку более чем одного набора параметров при выполнении оператора. Пакетный интерфейс разработан (в первую очередь)для удовлетворения требований JDBC по пакетной обработке подготовленных операторов, но имеет ряд серьезных отличий:

  • как и все операции с данными в firebird ориентированы на сообщения, а не на одно поле;

  • в качестве важного расширения пакетный интерфейс поддерживает встроенное использование BLOB-объектов(особенно эффективно при работе с небольшими BLOB-объектами);

  • метод execute() возвращает не простой массив целых чисел, а специальный интерфейс IBatchCompletionState,который может (в зависимости от параметров создания пакета) содержать как информацию об обновлениях записей,так и в дополнение к флагу ошибки подробные векторы состояния для сообщений, вызвавших ошибки выполнения.

Интерфейс IBatch (точно так же, как и IResultSet)может быть создан двумя способами – с использованием интерфейса IStatement илиIAttachment, в обоих случаях вызывается метод createBatch() соответствующего интерфейса.Во втором случае текст оператора SQL, который должен выполняться в пакете, передается непосредственно в createBatch().Настройка пакетной обработки осуществляется с помощью блока Batch parameters, формат которого более или менее похожна DPB v.2 – в начале тег (IBatch::CURRENT_VERSION), за которым следует набор широких скоплений: тег 1 байт, длина 4 байта,значение указанной длины байт. Возможные теги описаны в пакетном интерфейсе.Самый простой (и рекомендуемый) способ создать блок параметров для пакетного создания — использоватьсоответствующий интерфейс IXpbBuilder:

IXpbBuilder* pb = utl->getXpbBuilder(&status, IXpbBuilder::BATCH, NULL, 0);
pb->insertInt(&status, IBatch::RECORD_COUNTS, 1);

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

Чтобы создать пакетный интерфейс с нужными параметрами, передайте блок параметров в вызов createBatch():

IBatch* batch = att->createBatch(&status, tra, 0, sqlStmtText, SQL_DIALECT_V6, NULL,
  pb->getBufferLength(&status), pb->getBuffer(&status));

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

Для работы с созданным пакетным интерфейсом нам необходимо знать формат сообщений в нем.Его можно получить с помощью метода getMetadata():

IMessageMetadata* meta = batch->getMetadata(&status);

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

Далее я предполагаю, что существует некоторая функция fillNextMessage(unsigned char* data, IMessageMetadata* metadata)и она может заполнить буфер data в соответствии с переданным форматом metadata. Для работы с сообщениями нам нужен буфер для данных:

unsigned char* data = new unsigned char[meta->getMessageLength(&status)];

Теперь мы можем добавить в пакет несколько сообщений с заполненными данными:

fillNextMessage(data, meta);
batch->add(&status, 1, data);

fillNextMessage(data, meta);
batch->add(&status, 1, data);

Альтернативный способ работы с сообщениями (с помощью макроса FB_MESSAGE) присутствует в примере использованияпакетного интерфейса 11.batch.cpp.

Наконец, пакет должен быть выполнен:

IBatchCompletionState* cs = batch->execute(&status, tra);

Мы запросили учет количества измененных (вставленных, обновленных или удаленных) записей для каждого сообщение.Чтобы распечатать его, мы должны использовать интерфейс IBatchCompletionState.Определить общее количество сообщений, обработанных пакетом (оно может быть меньше количества сообщений,переданных в пакет, если произошла ошибка и не была включена опция возврата множества ошибок при пакетной обработке):

unsigned total = cs->getSize(&status);

Теперь выводим состояние каждого сообщения:

for (unsigned p = 0; p < total; ++p) printf("Msg %u state %d\n", p, cs->getState(&status, p));

Когда закончите анализ состояния завершения, не забудьте его удалить:

cs->dispose();

Полный пример печати содержимого IBatchCompletionState находитсяв функции print_cs() в примере 11.batch.cpp.

Если по какой-то причине вы хотите сделать пакетные буферы пустыми, не выполняя их(т.е. подготовиться к обработке новой порции сообщений), используйте метод cancel():

batch->cancel(&status);

Как и у остальных наших интерфейсов доступа к данным, у IBatch есть специальный метод для его закрытия:

batch->close(&status);

Вместо этого можно использовать стандартный вызов release(), если вас не волнуют ошибки:

batch->release();

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

Note
Замечание

JDBC не рекомендует использовать слишком большие пакеты, например, "Oracle рекомендует сохранять размеры пакетов в диапазоне от 50 до 100".Firebird поддерживает большие пакеты, но в любом случае должен ограничивать максимальный размер пакета — см. TAG_BUFFER_BYTES_SIZE.Если общий размер сообщений превышает этот предел, возвращается ошибка isc_batch_too_big.Обратите внимание, что из-за глубокой буферизации пакетных данных при их отправке по сети вы не получите эту ошибку сразу,а только при сбросе буферов от клиента к серверу. Это может происходить как в методах add(),так и в методах execute()execute() выполняет финальную сброс буфера. Если вы все еще хотите выполнить пакет с сообщениями,которые заполнили буфер, вы можете это сделать (когда функция execute() вернула ошибку, просто повторите ее).Фактическое количество обработанных сообщений будет возвращено в IBatchCompletionState.Оптимальный размер пакета должен быть найден для каждого конкретного случая, но скорее всего, если он более 1000,то вряд ли вы получите серьезный прирост производительности.

Можно добавить более одного сообщения за один вызов в пакет. При этом помните, что сообщения должны бытьправильно выровнены, чтобы эта функция работала правильно. Требуемое выравнивание и выровненный размер сообщениядолжны быть получены из интерфейса IMessageMetadata, например:

unsigned aligned = meta->getAlignedLength(&status);

Позже этот размер пригодится при выделении массива сообщений и работе с ним:

unsigned char* data = new unsigned char[aligned * N]; // N is desired number of messages

for (int n = 0; n < N; ++n)
  fillNextMessage(&data[aligned * n], meta);

batch->add(&status, N, data);

После этого может выполняться пакет или добавляться к нему следующая порция сообщений.

BLOBs в целом несовместимы с пакетами — пакет эффективен, когда нужно передать на сервер много мелких данных за один шаг,BLOBs рассматриваются как большие объекты, и поэтому в целом нет смысла использовать их в пакетах.Но на практике часто случается, что BLOB не слишком велики — и в этом случае использование традиционного BLOB API(создание BLOB, передача сегментов на сервер, закрытие BLOB, передача идентификатора BLOB в сообщении) убивает производительность,особенно при использовании через WAN. Поэтому в firebird пакет поддерживает передачу BLOB на сервер вместе с другими сообщениями.Чтобы использовать эту возможность, в первую очередь должна быть установлена политика использования BLOB-объектов длясоздаваемого пакета (как опция в блоке параметров):

pb->insertInt(&status, IBatch::BLOB_IDS, IBatch::BLOB_IDS_ENGINE);

В этом примере временные идентификаторы BLOB, необходимые для поддержания связи между BLOB и сообщением,в котором они используются, будут генерироваться движком firebird — это самое простое и довольно распространенное использование.Представьте, что сообщение описывается следующим образом:

FB_MESSAGE(Msg, ThrowStatusWrapper,
  (FB_VARCHAR(5), id)
  (FB_VARCHAR(10), name)
  (FB_BLOB, desc)
) project(&status, master);

В этом случае, чтобы отправить сообщение, содержащее blob, на сервер, можно сделать что-то вроде этого:

project->id = ++idCounter;
project->name.set(currentName);

batch->addBlob(&status, descriptionSize, descriptionText, &project->desc);

batch->add(&status, 1, project.getData());

Если какой-то BLOB оказался достаточно большим, чтобы не поместиться в ваш существующий буфер,вы можете вместо перераспределения буфера использовать метод appendBlobData().Он добавляет больше данных к последнему добавленному BLOB.

batch->addBlob(&status, descriptionSize, descriptionText, &project->desc, bpbLength, bpb);

После добавления первой части BLOB получите следующую часть данных в descriptionText, с размером descriptionSize после чего:

batch->appendBlobData(&status, descriptionSize, descriptionText);

Это можно сделать в цикле, но будьте осторожны, чтобы не переполнить внутренние пакетные буферы — их размерконтролируется параметром BUFFER_BYTES_SIZE при создании интерфейса IBatch, но не может превышать 256 МБ(по умолчанию 16 МБ). Если вам нужно обработать такой большой BLOB (например, на фоне множества мелких — этим можнообъяснить использование пакетной обработки), просто используйте стандартный API IBlob и метод registerBlob интерфейса IBatch.

Еще один возможный выбор политики BLOB — BLOB_IDS_USER. Использование на первый взгляд не сильно меняется —перед вызовом addBlob() правильный и уникальный для каждого пакета идентификатор должен быть помещен в память,на которую ссылается последний параметр. Конечно, тот же идентификатор должен быть передан в сообщении для BLOB.Принимая во внимание, что генерация BLOB-идентификаторов движком происходит очень быстро, такая политика может показаться бесполезной,но представьте случай, когда вы получаете BLOB и другие данные в относительно независимых потоках (например, в блоках файла),а некоторые хорошие идентификаторы уже присутствуют в них. В таком случае использование предоставленных пользователемидентификаторов BLOB может значительно упростить код.

Note
Обратите внимание

В отличие от BLOB-объектов, созданных с помощью обычного createBlob(), BLOB-объекты, созданные с помощью интерфейса IBatch,по умолчанию являются потоковыми, а не сегментированными. Сегментированные BLOB не представляют ничего интересногопо сравнению с потоковым и поэтому не рекомендуются для использования в новых разработках. Мы поддерживаем этот форматтолько по соображениям обратной совместимости. Если вам действительно нужны сегментированные BLOB, то это значениепо умолчанию можно переопределить, вызвав:

batch->setDefaultBpb(&status, bpbLength, bpb);

Разумеется, переданный BPB может содержать и любые другие параметры создания BLOB. Как вы, возможно, уже заметили,вы также можете передать BPB напрямую в addBlob(), но если большинство BLOB-объектов, которые вы собираетесь добавить,имеют одинаковый формат, отличный от формата по умолчанию, то использование setDefaultBpb() немного более эффективно.Возвращаясь к сегментированным BLOB — вызов addBlob() добавит первый сегмент в BLOB, последующие вызовы appendBlobData()добавят дополнительные сегменты. Не забывайте, что размер сегмента ограничен 64Кб – 1, попытка передать больше данныхза один вызов вызовет ошибку.

Следующий шаг - работа с существующими потоками BLOB для чего используется метод addBlobStream().Используя его, можно добавить более одного BLOB в пакет за один вызов. Поток BLOB — это последовательность BLOB,каждый из которых начинается с заголовка BLOB. Заголовок должен быть правильно выровнен — для этого в интерфейсе IBatchпредусмотрен специальный вызов:

unsigned alignment = batch->getBlobAlignment(&status);

Предполагается, что все компоненты потока BLOB в пакете должны быть выровнены как минимум по границе выравнивания,включая размер порций потока, передаваемых в addBlobStream(), который должен быть кратен этому выравниванию.Заголовок содержит 3 поля: 8-байтовый идентификатор BLOB (должен быть ненулевым), 4-байтовый общий размер BLOB и 4-байтовый размер BPB.Общий размер BLOB-объекта включает в себя BPB внутри, т. е. всегда можно найти следующий BLOB-объект в потоке в байтразмера BLOB-объекта после заголовка (с учетом выравнивания). BPB (если присутствует, т.е. если размер BPB не равен нулю)помещается сразу после заголовка. После передачи данных BLOB-объекта BPB их формат зависит от типа BLOB-объекта —потокового или сегментированного. В случае потокового BLOB это простая последовательность байтов, имеющая размер blob-size – BPB-size.С сегментированным BLOB-объектом все немного сложнее: данные BLOB-объекта представляют собой набор сегментов,где каждый сегмент имеет следующий формат: размер сегмента 2 байта (это должно быть выровнено по границе IBatch::BLOB_SEGHDR_ALIGN),за которым следуют хранящиеся в нем 2 байта количества байт.

Когда в поток добавляется BLOB, его размер не всегда известен заранее. Чтобы не было слишком большого буферадля этого BLOB (помните, что размер должен быть указан в заголовке BLOB перед данными BLOB), можно использовать записьпродолжения BLOB. В заголовке BLOB вы оставляете размер BLOB со значением, известным при создании этого заголовка,и добавляете запись продолжения, которая имеет формат, абсолютно такой же, как и заголовок BLOB, но здесь идентификаторBLOB должен быть равен нулю, а размер BPB всегда также должен быть равен нулю. Обычно вам потребуется иметь одну записьпродолжения для каждого вызова addBlobStream().

Последний метод, используемый для работы с BLOB, стоит особняком от первых трех, которые передают данные BLOB вместес остальными пакетными данными — он необходим для регистрации в идентификаторе пакета BLOB, созданного с использованием стандартного BLOB API.Это может быть неизбежным, если нужно передать в пакет действительно большой BLOB. Не используйте идентификатор такогоBLOB в пакете напрямую — это приведет к ошибке недопустимого идентификатора BLOB во время пакетного выполнения. Вместо этого выполните:

batch->registerBlob(&status, &realId, &msg->desc);

Если политика BLOB заставляет механизм Firebird генерировать идентификаторы BLOB, этого кода достаточно,чтобы правильно зарегистрировать существующий BLOB в пакете. В других случаях вам нужно будет назначить правильный (из пакета POV) идентификатордля msg→desc.

Почти все упомянутые методы используются в 11.batch.cpp — используйте его, чтобы увидеть живой пример пакетной обработки в firebird.

Пару слов о доступе к пакетам из ISC API - можно выполнить подготовленный оператор ISC в пакетном режиме.Для поддержки этого добавлено две новые функций API, а именно fb_get_transaction_interface иfb_get_statement_interface, которые позволяют получить доступ к соответствующим интерфейсам,идентичным существующим дескрипторам ISC. Пример этого присутствует в 12.batch_isc.cpp.