Работа с СУБД Oracle используя интерфейс OCCI
Статья предназначена для людей желающих работать с СУБД Oracle используя С++. Я не ставил своей целью написать полное руководство, для этого существуют соответствующая документация — которую нет смысле переписывать. Для максимально быстрого понимания изложение ведется в примерах, начиная с вопросов сборки программы (пример под Linux). Статься написана применительно к Oracle 9.2. Код тестировался на Oracle 9.2.0.4, SUSE Linux 9.3.
Минимальное использование OCCI
OCCI — расшифровывается как Oracle C++ Call Interface и представляет собой специализированное апи для работы с СУБД Oracle используя C++ что в общем то явствует из названия. Для использования необходимо подключить заголовочный файл “occi.h”.
Принятые именования объектов Environment* env;
Connection* conn;
Statement* stmt;
ResultSet* rs;
SQLException &sqlExcp;
Давайте рассмотрим пример небольшой тестовой программы использующей интерфейс OCCI:
#include
#include "occi.h"
using namespace std;
using namespace oracle::occi;
#define db_user_name "test"
#define db_password "test"
#define db_conn_str "service"
main(int argc,char* argv[])
{
try
{
/*
Создание среды управления памятью и ресурсами для объектов OCCI.
Предполагается использование объектных расширений — Environment::OBJECT
*/
Environment* env = Environment::createEnvironment(Environment::OBJECT);
/*
Создание соединения с базой данных
*/
Connection* conn = env->createConnection(db_user_name, db_password, db_conn_str);
/*
Работа с запросом
*/
Statement* stmt = conn->createStatement("Select 1 from dual");
/*
Получение результатов запроса
rs->getInt(номер_поля_начинается_единицы)
*/
ResultSet *rs = stmt->executeQuery();
int res = 0;
while (rs->next())
{
res = rs->getInt(1);
}
/*
Освобождение ресурсов
*/
stmt->closeResultSet(rs);
env->terminateConnection(conn);
Environment::terminateEnvironment(env);
}
catch(SQLException &sqlExcp)
{
cerr setAutoCommit(false); /* Новые объекты OCCI(представляют типы данных базы) */ Number CDRsequenceNumber = 0; Number CallingAddress = 0; Number IncomingCalledAddress = 0; Number OutgoingCalledAddress = 0; Date StartTimeStamp(env); Date AlertingTimeStamp(env); Date AnswerTimeStamp(env); Date ReleaseTimeStamp(env); // Контейнер с указателями на структуры данных которые будут помещаться в базу deque::iterator p = c_cdrs.begin();
while (p != c_cdrs.end())
{
/*
Обнуляем значения чисел
*/
CDRsequenceNumber.setNull();
CallingAddress.setNull();
IncomingCalledAddress.setNull();
OutgoingCalledAddress.setNull();
…
…
if (strlen((*p)->CDRsequenceNumber) > 0)
{
CDRsequenceNumber = 0;
CDRsequenceNumber.fromText(env,(*p)->CDRsequenceNumber,number_template,number_nls);
}
if (strlen((*p)->StartTimeStamp) > 0)
{
StartTimeStamp.setDate();
StartTimeStamp.fromText((*p)->StartTimeStamp,date_template,date_nls,env);
}
…
…
/*
Прямое назначение обьекту Number значение через оператор =
*/
OctSent = (*p)->OctSent;
OctRxd = (*p)->OctRxd;
PktLost = (*p)->PktLost;
/*
Устанавливаем переменные в курсор
*/
stmt->setNumber(1,CDRsequenceNumber);
stmt->setNumber(2,CallingAddress);
stmt->setInt(3,(*p)->CallingAddress_NAI);
stmt->setNumber(4,IncomingCalledAddress);
stmt->setInt(5,(*p)->IncomingCalledAddress_NAI);
stmt->setNumber(6,OutgoingCalledAddress);
stmt->setInt(7,(*p)->OutgoingCalledAddress_NAI);
stmt->setDate(8,StartTimeStamp);
stmt->setDate(9,AlertingTimeStamp);
stmt->setDate(10,AnswerTimeStamp);
stmt->setDate(11,ReleaseTimeStamp);
stmt->setDouble(12,(*p)->CallDuration);
stmt->setInt(13,(*p)->CauseIndicator);
…
…
stmt->setInt(22,(*p)->OutSigAddr_OPC);
stmt->setInt(23,(*p)->OutSigAddr_DPC);
/* Выполнение курсора и повторное его использование */
stmt->executeUpdate();
p++;
} // while
conn->commit();
conn->terminateStatement(stmt);
}
// ловим исключения при возникшие в процессе работы с базой
catch(SQLException &sqlExcp)
{
conn->rollback();
cerr setString(1,"123"); вторая итерация stmt->setString(1,"1234"); ORA-01461 can bind a LONG value only for insert into a LONG column Cause: An attempt was made to insert a value from a LONG datatype into another datatype. This is not allowed. Action: Do not try to insert LONG datatypes into other types of columns.
Работа с переменными IN/OUT, OUT
Мы рассмотрели только переменные передаваемые в режиме IN, но OCCI позволяет работать с OUT & IN/OUT. Такие переменные актуальны в случае использования так называемых callable statemens – имеется виду вызов процедур и функций на PL/SQL.
Рассмотрим работу с такими запросами. Предположим, в базе имеется функция на PL/SQL, вот ее прототип:
CREATE OR REPLACE FUNCTION insclient(ContractNumber IN VARCHAR, ClientPasswd IN OUT VARCHAR, Action IN VARCHAR ) RETURN INTEGER;
Пример работы с ней:
… string passwd; int res; … Statement *stmt = conn->createStatement("BEGIN :v1 := insclient(:v2,:v3,:v4); END;"); // устанавливаем параметры stmt->setString(2,login); // регистрируем выходные параметры stmt->registerOutParam(1,OCCIINT,sizeof(res)); stmt->registerOutParam(3,OCCISTRING,pass_length+2); // устанавливаем значение параметров для IN/OUT после регистрации !!! stmt->setString(3,pass_syms.c_str()); stmt->setString(4,command); stmt->execute(); // получаем результат выполнения res = stmt->getInt(1); // получаем указатель на результат passwd = stmt->getString(3); // освобождение ресурсов conn->terminateStatement(stmt); env->terminateConnection(conn);
Примечания:
Регистрировать параметры нужно указывая их максимальную длинуУстанавливать значения параметров IN/OUT после регистрации, иначе значение будет потеряно
Повышение производительности
Как упоминалось ранее для приложений, в которых скорость выполнения запросов является критичной можно использовать функцию setDataBuffer.
void setDataBuffer( int paramIndex, void *buffer, Type type, sb4 size, ub2 *length, sb2 *ind = NULL, ub2 *rc = NULL);
paramIndexНомер параметра bufferуказатель на буфер с данными typeТип данных в буфере sizeРазмер элемента в буфере lengthТекущая длина данных в текущей ячейке буфера indИндикатор. Указывает когда данные пустые, в случае если -1 было вставлено NULL – значение, в случае вызова подпрограммы в запросе -1 указывает что возвращено NULL. rcКод возврата. Этот параметр неприменим к данным передаваемым методами Statement-a, но для данных возвращаемых из вызовов процедур возвращает параметро-зависимый код ошибки.
При инициализации данных используя setDataBuffer данные считываются последовательно из памяти, т.е. после каждой итерации(количество заранее указывается) происходит смещение указателя на адрес следующего элемента. Метод setDataBuffer можно использовать совместно с Statement* stmt->setXXX или без него. Рассмотрим оба варианта использования этого метода
Допустим, в базе создана таблица следующей вида:
CREATE TABLE tb01 ( id INTEGER, data NUMBER(10), val VARCHAR(20) ) NOLOGGING
Код выполняющий вставку в таблицу:
/* Массивы данных для вставки */ int ids[] = {1,2,3,4}; ub2 ids_rc[] = {0,0,0,0}; unsigned long datas[] = {1000,2000,3000,4000}; /* Строковые переменные, в документации по OCCI не указана 2-я размерность массива(10) – ее нужно указывать */ char vals[4][10] = {"Value", "Value666", "Value677", "Val4545"}; stmt->setSQL("Insert into tb01(id,data,val) values(:1,:2,:3)"); int i = 0; /* Определяем массив длин значений в массиве строковых данных и заполняем длины */ ub2 valsLen[4]; for (i = 0; i setMaxIterations(4); /* Заполнение буфера, вызывается 1 раз на все значения. Указывается номер параметра, адрес данных,типа данных,размер ячейки данных (чтобы OCCI знал на сколько передвинуть указатель для позиционирования на следующую ячейку), указатель на длины данных в ячейках. Предролагается что данные идут в памяти последоваетельно – за концом одной строки начало следуюшей */ stmt->setDataBuffer(3,vals,OCCI_SQLT_STR, sizeof(vals[0]),valsLen); /* sizeof(vals[0]) == 10 для этого случая */ for (i = 0; i setInt(1,ids[i]); stmt->setInt(2,datas[i]); /* Добавление итерации + фактическое смещение указателя данных и накапливание данных из setInt */ if (i != 3) stmt->addIteration(); } /* Выполнение всех добавленых итераций */ stmt->executeUpdate();
Важно понимать, что в данном случае объявление char vals[4][10] = {"Value", "Value666", "Value677", "Val4545"}; нельзя заменить на char* vals[4] – и заполнить адреса произвольно, данные должны идти последовательно. Теперь рассмотрим тот же вариант, но когда все данные содержатся в массивах:
/* Массив id, а также массив длин элементов и массив кодов возврата(для примера). Естественно sizeof(ids[0]) == sizeof(ids[1]) == sizeof(ids[2]) == sizeof(ids[3]) – объявлено таким образом для наглядности */ int ids[] = {1,2,3,4}; ub2 ids_len[] = {sizeof(ids[0]),sizeof(ids[1]),sizeof(ids[2]),sizeof(ids[3])}; ub2 ids_rc[] = {0,0,0,0}; unsigned long datas[] = {1000,2000,3000,4000}; ub2 datas_len[] = {sizeof(datas[0]),sizeof(datas[1]),sizeof(datas[2]),sizeof(datas[3])}; char vals[4][10] = {"Value", "Value666", "Value677", "Val4545"}; ub2 valsLen[4]; for (i = 0; i setSQL("Insert into tb01(id,data,val) values(:1,:2,:3)"); int i = 0; /* Установка переменных */ stmt->setDataBuffer(1,ids,OCCIINT,sizeof(ids[0]),ids_len,NULL,ids_rc); stmt->setDataBuffer(2,datas,OCCIINT,sizeof(datas[0]),datas_len); stmt->setDataBuffer(3,vals,OCCI_SQLT_STR, sizeof(vals[0]),valsLen); /* Выполнить 4 итерации */ stmt->executeArrayUpdate(4);
В использовании stmt->setMaxIterations(4); нет необходимости Подобным образом можно не только вставлять данные, но и извлекать их. Код из документации по OCCI иллюстрирующий это:
int empno[5]; char ename[5][11]; ub2 enameLen[5]; ResultSet *resultSet = stmt->executeQuery("select empno, ename from emp"); resultSet->setDataBuffer(1, &empno, OCCIINT); resultSet->setDataBuffer(2, ename, OCCI_SQLT_STR, sizeof(ename[0]), enameLen); rs->next(5); // сливаем 5 строк, enameLen[i] хранит длину ename[i]
Заключение
В общем, это все что я хотел написать — надеюсь приведенные здесь примеры помогут вам быстро начать использование Oracle в ваших проектах на C++. OCCI очень простой и мощный интерфейс работы с Oracle. Здесь не затронуты вопросы использования BLOB, использование потоков Stream, объектное программирование и Object Type Translator (OTT), получение метаданных, разработка многопоточных приложений например используя ConnectionPool и т.д. По всем этим вопросам стоит обратиться к документации по Oracle — Oracle C++ Call Interface Programmer’s Guide ссылку на которую я приводил в начале статьи.
Автор: Павлов А.Ю.
www.intser.fatal.ru
Мой блог находят по следующим фразам