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

Выполнение SQL операторов с входными параметрами

Существует два способа выполнения оператора с входными параметрами.Выбор правильного метода зависит от того, нужно ли вам выполнять егоболее одного раза, и знаете ли вы заранее формат параметров. Когда этотформат известен, и оператор нужно запускать только один раз, тогда выможете воспользоваться одиночным вызовом IAttachment::execute(). Впротивном случае сначала необходимо подготовить SQL-запрос, после чегоего можно выполнять многократно с различными параметрами.

Чтобы подготовить SQL оператор для выполнения, используйте методprepare() интерфейса IAttachment:

IStatement* stmt = att->prepare(&status, tra, 0,
    "UPDATE department SET budget = ? * budget + budget WHERE dept_no = ?",
    SQL_DIALECT_V6, IStatement::PREPARE_PREFETCH_METADATA);

Если вы не собираетесь использовать описание параметров из Firebird(т.е. вы можете предоставить эту информацию самостоятельно), используйтеIStatement::PREPARE_PREFETCH_NONE вместоIStatement::PREPARE_PREFETCH_METADATA — это немного снизитклиент/серверный трафик и сохранит ресурсы.

В ISC API структура XSQLDA используется для описания формата параметровоператора. Новый API не использует XSQLDA — вместо неё используетсяинтерфейс IMessageMetadata.Набор входных параметров (а также запись, взятая из курсора) описываетсяв Firebird API таким же образом, далее называемый сообщением.IMessageMetadata передаётся в качестве параметра в методы обменасообщениями между программой и движком базы данных. Существует многоспособов получить экземпляр IMessageMetadata, вот некоторые из них:

  • получить из IStatement;

  • построить используяIMetadataBuilder интерфейс;

  • иметь собственную реализацию этого интерфейса.

Получить метаданные из подготовленного запроса очень просто — методgetInputMetadata() возвращает интерфейс, описывающий входное сообщение(т.е. параметры оператора), интерфейс, возвращаемый getOutputMetadata(),описывает выходное сообщение (т.е. строку выбранных данных или значения,возвращаемые процедурой). В нашем случае мы можем сделать так:

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

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

IMetadataBuilder* builder = master->getMetadataBuilder(&status, 2);

Второй параметр — это ожидаемое количество полей в сообщении, его можноизменить позже, т.е. он необходим только для оптимизации.

Теперь необходимо задать индивидуальные характеристики полей впостроителе. Минимально необходимыми являются типы полей и длина длястроковых полей:

builder->setType(&status, 0, SQL_DOUBLE + 1);

builder->setType(&status, 1, SQL_TEXT + 1);
builder->setLength(&status, 1, 3);

Новый API использует старые константы для типов SQL, наименьший бит, каки раньше, используется для обозначения возможности принимать nullзначение. В некоторых случаях имеет смысл установить подтип (для BLOB),набор символов (для текстовых полей) или масштаб (для числовых полей).Наконец, пришло время получить экземпляр IMessageMetadata:

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

Здесь мы не обсуждаем собственную реализацию IMessageMetadata. Если вамэто интересно, то вы можете посмотреть пример 05.user_metadata.cpp.

Итак, мы получили экземпляр описания метаданных входных параметров. Нодля работы с сообщением нам также необходим буфер. Размер буфераявляется одной из основных характеристик сообщений метаданных ивозвращается методом getMessageLength() из IMessageMetadata:

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

Чтобы иметь дело с отдельными значениями внутри буфера, смещение к нимдолжно быть принято в расчёт. IMessageMetadata знает о смещениях длявсех значений в сообщении, используя его, мы можем создавать указателина них:

double* percent_inc = (double*) &buffer[meta->getOffset(&status, 0)];
char* dept_no = &buffer[meta->getOffset(&status, 1)];

Кроме того, не забывайте установить NULL флаги:

short* flag = (short*)&buffer[meta->getNullOffset(&status, 0)];
*flag = 0;

flag = (short*) &buffer[meta->getNullOffset(&status, 1)];
*flag = 0;

После завершения манипуляций со смещениями, мы готовы получить значенияпараметров:

getInputValues(dept_no, percent_inc);

и выполнить подготовленный оператор:

stmt->execute(&status, tra, meta, buffer, NULL, NULL);

Два последних NULL в параметрах предназначены для выходных сообщений иобычно используются для оператора EXECUTE PROCEDURE.

Если вам не нужно получать метаданные из оператора и вы планируетевыполнить его только один раз, то вы можете выбрать более простой способ— используйте метод execute() из интерфейсаIAttachment:

att->execute(&status, tra, 0,
    "UPDATE department SET budget = ? * budget + budget WHERE dept_no = ?",
    SQL_DIALECT_V6, meta, buffer, NULL, NULL);

В этом случае вам вообще не нужно использоватьIStatement.

Пример того, как выполнить оператор UPDATE с параметрами, присутствует в02.update.cpp, вы также увидите, как возбужденное исключение втриггере/процедуре может быть перехвачено в программе на C++.

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

Единственный способ получить строки данных, возвращаемых оператором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.