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.