FirebirdSQL logo

Использование сервисов

Чтобы начать пользоваться сервисами (службами), прежде всего необходимоподключиться к менеджеру сервисов. Это делается с помощью методаattachServiceManager() интерфейсаIProvider. Этот метод возвращаетинтерфейс IService, который позжеиспользуется для связи с сервисом. Чтобы подготовить SPB для подключенияк диспетчеру сервисов, вы можете использовать IXpbBuilder:

IXpbBuilder* spb1 = utl->getXpbBuilder(&status, IXpbBuilder::SPB_ATTACH, NULL, 0);

spb1->insertString(&status, isc_spb_user_name, "sysdba");
spb1->insertString(&status, isc_spb_password, "masterkey");

и подключится:

IService* svc = prov->attachServiceManager(&status, "service_mgr",
    spb1->getBufferLength(&status), spb1->getBuffer(&status));

Используя IService, вы можете выполнять как доступные для служб действия— запускать службы, так и запрашивать различную информацию о запущенныхутилитах и сервере в целом. При запросе информации, есть одноограничение — формат блока параметров, используемый методом query(), вFirebird 4 не поддерживается IXpbBuilder. Вероятно, поддержка будетдобавлена в более поздних версиях, в Firebird 4 вам придется создавать ианализировать этот блок вручную. Формат этого блока повторяет старыйформат (используемый в ISC API) один в один.

Чтобы стартовать сервис, необходимо прежде всего создать соответствующийSPB:

IXpbBuilder* spb2 = utl->getXpbBuilder(&status, IXpbBuilder::SPB_START, NULL, 0);

и добавить к нему необходимые элементы. Например, для печати статистикишифрования для базы данных employee в SPB следует поместить следующее:

spb2->insertTag(&status, isc_action_svc_db_stats);
spb2->insertString(&status, isc_spb_dbname, "employee");
spb2->insertInt(&status, isc_spb_options, isc_spb_sts_encryption);

После этого сервис можно запустить с использованием метода start()интерфейса IService:

svc->start(&status, spb2->getBufferLength(&status), spb2->getBuffer(&status));

Многие запущенные службы (включая упомянутый здесь gstat) во времявыполнения возвращают текстовую информацию. Чтобы отобразить её,необходимо запросить эту информацию у запущенного сервиса построчно. Этоделается с помощью вызова метода query() интерфейсаIService с соответствующими блокамипараметров для приёма и отправки. Блок отправки может содержатьразличную вспомогательную информацию (например, тайм-аут запроса услужбы) или информацию, которая должна быть передана в служебнуюпрограмму stdin, или может быть пустым в простейшем случае. Блок приемадолжен содержать список тегов, которые вы хотите получать из службы. Длябольшинства утилит это единственный isc_info_svc_line:

const unsigned char receiveItems1[] = {isc_info_svc_line};

Кроме того, для запроса этой информации для неё необходим буфер:

unsigned char results[1024];

После этих предварительных шагов мы готовы запросить информацию изсервиса в цикле (каждая строка возвращается в одном вызове query()):

do
{
    svc->query(&status, 0, NULL,
               sizeof(receiveItems1), receiveItems1,
               sizeof(results), results);
} while (printInfo(results, sizeof(results)));

В этом примере мы предполагаем, что функция printInfo() возвращаетTRUE, пока сервис возвращает блок результатов, содержащий следующуювыходную строку, то есть до конца потока данных из сервиса. Форматблока результатов варьируется от сервиса к сервису, а некоторые сервисы,такие как gsec, создают исторические форматы, которые не являютсятривиальными для синтаксического анализа, но это выходит за рамки даннойглавы. Минимальный рабочий пример printInfo() присутствует в примере09.service.cpp.

Тот же метод запроса используется для извлечения информации о сервере,но в этом случае функция запроса не вызывается в цикле, т. е. буфердолжен быть достаточно большим, чтобы сразу вместить всю информацию. Этоне слишком сложно, так как обычно такие вызовы не возвращают многоданных. Как и в предыдущем случае, необходимо начать с того, чтобыразместить в блоке приема необходимые элементы — в нашем примере этоisc_info_svc_server_version:

const unsigned char receiveItems2[] = {isc_info_svc_server_version};

Существующий буфер результатов из предыдущего вызова может бытьиспользован повторно. В данном случае цикл не требуется:

svc->query(&status, 0, NULL,
           sizeof(receiveItems2), receiveItems2,
           sizeof(results), results);

printInfo(results, sizeof(results));

После завершения сервисных задач не забудьте отключить сервис:

svc->detach(&status);

Работа с транзакциями

Только создание пустых баз данных определенно недостаточно для работы сРСУБД. Мы хотим иметь возможность создавать в базе данных различныеобъекты (например, таблицы и т. д.) и вставлять данные в эти таблицы. ВFirebird любая операция с базой данных выполняется под управлениемтранзакций. Поэтому прежде всего мы должны научиться стартоватьтранзакцию. Здесь мы не обсуждаем распределенные транзакции(поддерживаемые интерфейсом IDtc), чтобыизбежать ненужных для большинства пользователей сложностей. Запуск нераспределенной транзакции очень прост и выполняется через интерфейсподключения:

ITransaction* tra = att->startTransaction(&status, 0, NULL);

В этом примере используются параметры транзакции по умолчанию — TPB непередается методу startTransaction(). Если вам нужна транзакция спараметрами отличными от параметров по умолчанию, вы можете создатьсоответствующий IXpbBuilder идобавить к нему необходимые элементы:

IXpbBuilder* tpb = utl->getXpbBuilder(&status, IXpbBuilder::TPB, NULL, 0);
tpb->insertTag(&status, isc_tpb_read_committed);

и передать готовый TPB в startTransaction():

ITransaction* tra = att->startTransaction(&status, tpb->getBufferLength(&status),
    tpb->getBuffer(&status));

Интерфейс транзакции используется как параметр во множестве другихвызовах API, но сам он не выполняет никаких действий, кромефиксации/отката (commit/rollback) транзакции, может быть с сохранениемконтекста транзакции (retaining):

tra->commit(&status);

Вы можете посмотреть, как начинать и подтверждать транзакцию в примерах01.create.cpp и 01.create.pas.

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

После старта транзакции мы готовы выполнять наши первые SQL операторы.Используемый для этого метод execute() вIAttachment является довольноуниверсальным, и может также использоваться для выполнения операторовSQL с входными и выходными параметрами (что типично для инструкцииEXECUTE PROCEDURE), но сейчас мы будем использовать наиболее простую егоформу. Могут быть выполнены как DDL, так и DML операторы:

att->execute(&status, tra, 0, "create table dates_table (d1 date)",
    SQL_DIALECT_V6, NULL, NULL, NULL, NULL);
tra->commitRetaining(&status);
att->execute(&status, tra, 0, "insert into dates_table values (CURRENT_DATE)",
    SQL_DIALECT_V6, NULL, NULL, NULL, NULL);

Как вы видите, интерфейс транзакции является обязательным параметром дляметода execute() (должен быть NULL, только если вы выполняете инструкциюSET TRANSACTION). Следующим параметром следует длина SQL оператора(может быть равна нулю, в этом случае используются правила C дляопределения длины строки), потом текст оператора и диалект SQL, которыйдолжен использоваться для него. Далее следует несколько NULL которыеподставляются для описания метаданных, и буферов входных параметров ивыходных данных. Полное описание этого метода представлено в интерфейсеIAttachment.

Выполнение 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++.